@markuplint/rules 2.0.0-dev.20211209.0 → 2.0.0-dev.20211213.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/attr-check.d.ts +8 -1
- package/lib/attr-check.js +51 -455
- package/lib/case-sensitive-attr-name/index.js +2 -3
- package/lib/create-message.d.ts +1 -1
- package/lib/create-message.js +31 -17
- package/lib/deprecated-attr/index.js +1 -2
- package/lib/helpers.d.ts +8 -3
- package/lib/helpers.js +8 -41
- package/lib/index.d.ts +9 -2
- package/lib/index.js +4 -0
- package/lib/ineffective-attr/index.d.ts +2 -0
- package/lib/ineffective-attr/index.js +42 -0
- package/lib/invalid-attr/index.d.ts +3 -1
- package/lib/invalid-attr/index.js +15 -5
- package/lib/no-boolean-attr-value/index.d.ts +2 -0
- package/lib/no-boolean-attr-value/index.js +49 -0
- package/lib/required-attr/index.d.ts +5 -1
- package/lib/required-attr/index.js +60 -18
- package/lib/wai-aria/index.js +2 -2
- package/package.json +10 -5
- package/tsconfig.tsbuildinfo +1 -1
package/lib/create-message.js
CHANGED
|
@@ -25,6 +25,7 @@ function __createMessageValueExpected(t, baseTarget, expected, matches) {
|
|
|
25
25
|
let target = baseTarget;
|
|
26
26
|
let reasonPart;
|
|
27
27
|
let expectPart;
|
|
28
|
+
let unnecessaryPart;
|
|
28
29
|
let candicatePart;
|
|
29
30
|
let expectOrNeed = 'expects';
|
|
30
31
|
if (matches.partName) {
|
|
@@ -52,6 +53,10 @@ function __createMessageValueExpected(t, baseTarget, expected, matches) {
|
|
|
52
53
|
case 'extra-token': {
|
|
53
54
|
const token = matches.partName || 'token';
|
|
54
55
|
reasonPart = t('Found {0}', t('extra {0}', token)) + ' ' + t([matches.raw]);
|
|
56
|
+
if (matches.extra) {
|
|
57
|
+
const extra = expectValueToWord(t, matches.extra, 'Any');
|
|
58
|
+
unnecessaryPart = t("It doesn't need {0}", extra);
|
|
59
|
+
}
|
|
55
60
|
break;
|
|
56
61
|
}
|
|
57
62
|
case 'illegal-combination': {
|
|
@@ -86,6 +91,12 @@ function __createMessageValueExpected(t, baseTarget, expected, matches) {
|
|
|
86
91
|
expectOrNeed = 'needs';
|
|
87
92
|
break;
|
|
88
93
|
}
|
|
94
|
+
case 'must-be-percent-encoded': {
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case 'must-be-serialized': {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
89
100
|
case 'out-of-range': {
|
|
90
101
|
break;
|
|
91
102
|
}
|
|
@@ -129,7 +140,7 @@ function __createMessageValueExpected(t, baseTarget, expected, matches) {
|
|
|
129
140
|
expectedDigits = t('{0} digits', gte);
|
|
130
141
|
}
|
|
131
142
|
else if (lte != null) {
|
|
132
|
-
|
|
143
|
+
expectedDigits = t('{0} to {1} digits', gte, lte);
|
|
133
144
|
}
|
|
134
145
|
else {
|
|
135
146
|
expectedDigits = t('{0} or more digits', gte);
|
|
@@ -180,7 +191,7 @@ function __createMessageValueExpected(t, baseTarget, expected, matches) {
|
|
|
180
191
|
if (matches.candicate) {
|
|
181
192
|
candicatePart = t('Did you mean "{0*}"?', matches.candicate);
|
|
182
193
|
}
|
|
183
|
-
let message = [reasonPart, expectPart, candicatePart].filter(s => s).join(t('. '));
|
|
194
|
+
let message = [reasonPart, unnecessaryPart, expectPart, candicatePart].filter(s => s).join(t('. '));
|
|
184
195
|
if (matches.ref) {
|
|
185
196
|
message += ` (${matches.ref})`;
|
|
186
197
|
}
|
|
@@ -192,21 +203,7 @@ function createExpectedObject(type, matches, t) {
|
|
|
192
203
|
const expectedObject = [];
|
|
193
204
|
if ((_a = matches.expects) === null || _a === void 0 ? void 0 : _a.length) {
|
|
194
205
|
expectedObject.push(...matches.expects.map(expect => {
|
|
195
|
-
|
|
196
|
-
case 'common':
|
|
197
|
-
return expect.value;
|
|
198
|
-
case 'const':
|
|
199
|
-
return expect.value ? `%${expect.value}%` : '';
|
|
200
|
-
case 'format':
|
|
201
|
-
return t('the {0} format', expect.value);
|
|
202
|
-
case 'regxp':
|
|
203
|
-
return t('{0} ({1})', 'regular expression', expect.value);
|
|
204
|
-
case 'syntax':
|
|
205
|
-
if ((0, types_1.isKeyword)(type) && type[0] === '<') {
|
|
206
|
-
return t('the CSS Syntax "{0}"', expect.value);
|
|
207
|
-
}
|
|
208
|
-
return t('{0} syntax', expect.value);
|
|
209
|
-
}
|
|
206
|
+
return expectValueToWord(t, expect, type);
|
|
210
207
|
}));
|
|
211
208
|
}
|
|
212
209
|
else if ((0, types_1.isKeyword)(type)) {
|
|
@@ -227,6 +224,23 @@ function createExpectedObject(type, matches, t) {
|
|
|
227
224
|
: expectedObject[0];
|
|
228
225
|
return expects;
|
|
229
226
|
}
|
|
227
|
+
function expectValueToWord(t, expect, type) {
|
|
228
|
+
switch (expect.type) {
|
|
229
|
+
case 'common':
|
|
230
|
+
return expect.value;
|
|
231
|
+
case 'const':
|
|
232
|
+
return expect.value ? `%${expect.value}%` : '';
|
|
233
|
+
case 'format':
|
|
234
|
+
return t('the {0} format', expect.value);
|
|
235
|
+
case 'regxp':
|
|
236
|
+
return t('{0} ({1})', 'regular expression', expect.value);
|
|
237
|
+
case 'syntax':
|
|
238
|
+
if ((0, types_1.isKeyword)(type) && type[0] === '<') {
|
|
239
|
+
return t('the CSS Syntax "{0}"', expect.value);
|
|
240
|
+
}
|
|
241
|
+
return t('{0} syntax', expect.value);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
230
244
|
function createExpectedNumber(t, type) {
|
|
231
245
|
if (type.gt != null) {
|
|
232
246
|
if (type.lt != null) {
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const ml_core_1 = require("@markuplint/ml-core");
|
|
4
|
-
const helpers_1 = require("../helpers");
|
|
5
4
|
exports.default = (0, ml_core_1.createRule)({
|
|
6
5
|
defaultValue: null,
|
|
7
6
|
defaultOptions: null,
|
|
8
7
|
async verify({ document, report, t }) {
|
|
9
8
|
await document.walkOn('Element', async (element) => {
|
|
10
|
-
const attrSpecs = (0,
|
|
9
|
+
const attrSpecs = (0, ml_core_1.getAttrSpecs)(element.nameWithNS, document.specs);
|
|
11
10
|
if (!attrSpecs) {
|
|
12
11
|
return;
|
|
13
12
|
}
|
package/lib/helpers.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import type { Log } from './debug';
|
|
1
2
|
import type { Translator } from '@markuplint/i18n';
|
|
2
3
|
import type { Element, RuleConfigValue } from '@markuplint/ml-core';
|
|
3
|
-
import type { ARIRRoleAttribute, Attribute,
|
|
4
|
-
export declare function getAttrSpecs(nameWithNS: string, { specs, def }: MLMLSpec): Attribute[] | null;
|
|
4
|
+
import type { ARIRRoleAttribute, Attribute, PermittedRoles } from '@markuplint/ml-spec';
|
|
5
5
|
export declare function attrMatches<T extends RuleConfigValue, R>(node: Element<T, R>, condition: Attribute['condition']): boolean;
|
|
6
6
|
export declare function match(needle: string, pattern: string): boolean;
|
|
7
7
|
/**
|
|
@@ -21,9 +21,14 @@ export declare function match(needle: string, pattern: string): boolean;
|
|
|
21
21
|
*/
|
|
22
22
|
export declare const rePCENChar: string;
|
|
23
23
|
export declare function htmlSpec(nameWithNS: string): import("@markuplint/ml-spec").ElementSpec | null;
|
|
24
|
-
export declare function isValidAttr(t: Translator, name: string, value: string, isDynamicValue: boolean, node: Element<any, any>, attrSpecs: Attribute[]): false | {
|
|
24
|
+
export declare function isValidAttr(t: Translator, name: string, value: string, isDynamicValue: boolean, node: Element<any, any>, attrSpecs: Attribute[], log?: Log): false | {
|
|
25
25
|
invalidType: "non-existent" | "invalid-value";
|
|
26
26
|
message: string;
|
|
27
|
+
loc?: {
|
|
28
|
+
raw: string;
|
|
29
|
+
line: number;
|
|
30
|
+
col: number;
|
|
31
|
+
} | undefined;
|
|
27
32
|
};
|
|
28
33
|
export declare function ariaSpec(): {
|
|
29
34
|
roles: ARIRRoleAttribute[];
|
package/lib/helpers.js
CHANGED
|
@@ -1,45 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.checkAria = exports.checkAriaValue = exports.getComputedRole = exports.getImplicitRole = exports.getPermittedRoles = exports.getRoleSpec = exports.ariaSpec = exports.isValidAttr = exports.htmlSpec = exports.rePCENChar = exports.match = exports.attrMatches =
|
|
3
|
+
exports.checkAria = exports.checkAriaValue = exports.getComputedRole = exports.getImplicitRole = exports.getPermittedRoles = exports.getRoleSpec = exports.ariaSpec = exports.isValidAttr = exports.htmlSpec = exports.rePCENChar = exports.match = exports.attrMatches = void 0;
|
|
4
4
|
const html_spec_1 = require("@markuplint/html-spec");
|
|
5
5
|
const attr_check_1 = require("./attr-check");
|
|
6
|
-
function getAttrSpecs(nameWithNS, { specs, def }) {
|
|
7
|
-
const spec = specs.find(spec => spec.name === nameWithNS);
|
|
8
|
-
if (!spec) {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
const globalAttrs = def['#globalAttrs'];
|
|
12
|
-
const attrs = [];
|
|
13
|
-
if (!/[a-z]:[a-z]/i.test(nameWithNS)) {
|
|
14
|
-
// It's HTML tag
|
|
15
|
-
const hasGlobalAttr = spec.attributes.some(attr => attr === '#globalAttrs');
|
|
16
|
-
if (hasGlobalAttr) {
|
|
17
|
-
attrs.push(...(globalAttrs['#HTMLGlobalAttrs'] || []));
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
for (const attr of spec.attributes) {
|
|
21
|
-
if (typeof attr === 'string') {
|
|
22
|
-
const globalAttr = globalAttrs[attr];
|
|
23
|
-
if (!globalAttr) {
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
attrs.push(...globalAttr);
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
const definedIndex = attrs.findIndex(a => a.name === attr.name);
|
|
30
|
-
if (definedIndex !== -1) {
|
|
31
|
-
attrs[definedIndex] = {
|
|
32
|
-
...attrs[definedIndex],
|
|
33
|
-
...attr,
|
|
34
|
-
};
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
attrs.push(attr);
|
|
38
|
-
}
|
|
39
|
-
attrs.push(...(globalAttrs['#extends'] || []));
|
|
40
|
-
return attrs;
|
|
41
|
-
}
|
|
42
|
-
exports.getAttrSpecs = getAttrSpecs;
|
|
43
6
|
function attrMatches(node, condition) {
|
|
44
7
|
if (!condition) {
|
|
45
8
|
return true;
|
|
@@ -53,7 +16,10 @@ function attrMatches(node, condition) {
|
|
|
53
16
|
let _node = node.parentNode;
|
|
54
17
|
while (_node) {
|
|
55
18
|
if (_node.type === 'Element') {
|
|
56
|
-
|
|
19
|
+
const condSelector = Array.isArray(condition.ancestor)
|
|
20
|
+
? condition.ancestor.join(',')
|
|
21
|
+
: condition.ancestor;
|
|
22
|
+
if (_node.matches(condSelector)) {
|
|
57
23
|
matched = true;
|
|
58
24
|
break;
|
|
59
25
|
}
|
|
@@ -114,9 +80,10 @@ function htmlSpec(nameWithNS) {
|
|
|
114
80
|
return spec || null;
|
|
115
81
|
}
|
|
116
82
|
exports.htmlSpec = htmlSpec;
|
|
117
|
-
function isValidAttr(t, name, value, isDynamicValue, node, attrSpecs) {
|
|
83
|
+
function isValidAttr(t, name, value, isDynamicValue, node, attrSpecs, log) {
|
|
118
84
|
let invalid = false;
|
|
119
|
-
const spec = attrSpecs.find(s => s.name === name);
|
|
85
|
+
const spec = attrSpecs.find(s => s.name.toLowerCase() === name.toLowerCase());
|
|
86
|
+
log && log('Spec of the %s attr: %o', name, spec);
|
|
120
87
|
invalid = (0, attr_check_1.attrCheck)(t, name, value, false, spec);
|
|
121
88
|
if (!invalid && spec && spec.condition && !node.hasSpreadAttr && !attrMatches(node, spec.condition)) {
|
|
122
89
|
invalid = {
|
package/lib/index.d.ts
CHANGED
|
@@ -15,13 +15,16 @@ declare const _default: {
|
|
|
15
15
|
}>;
|
|
16
16
|
'id-duplication': import("packages/@markuplint/ml-core/lib").RuleSeed<null, null>;
|
|
17
17
|
indentation: import("packages/@markuplint/ml-core/lib").RuleSeed<import("./indentation").Value, import("./indentation").IndentationOptions>;
|
|
18
|
+
'ineffective-attr': import("packages/@markuplint/ml-core/lib").RuleSeed<boolean, null>;
|
|
18
19
|
'invalid-attr': import("packages/@markuplint/ml-core/lib").RuleSeed<true, {
|
|
19
20
|
attrs?: Record<string, {
|
|
20
|
-
enum: string[];
|
|
21
|
+
enum: [string, ...string[]];
|
|
21
22
|
} | {
|
|
22
23
|
pattern: string;
|
|
23
24
|
} | {
|
|
24
25
|
type: import("packages/@markuplint/ml-spec/lib").AttributeType;
|
|
26
|
+
} | {
|
|
27
|
+
disallowed: true;
|
|
25
28
|
}> | undefined;
|
|
26
29
|
ignoreAttrNamePrefix?: string | string[] | undefined;
|
|
27
30
|
}>;
|
|
@@ -29,10 +32,14 @@ declare const _default: {
|
|
|
29
32
|
ignoreRoles?: (("banner" | "main" | "complementary" | "contentinfo") | "form" | "navigation" | "region")[] | undefined;
|
|
30
33
|
labelEachArea?: boolean | undefined;
|
|
31
34
|
}>;
|
|
35
|
+
'no-boolean-attr-value': import("packages/@markuplint/ml-core/lib").RuleSeed<boolean, null>;
|
|
32
36
|
'permitted-contents': import("packages/@markuplint/ml-core/lib").RuleSeed<import("packages/@markuplint/ml-spec/lib").PermittedStructuresSchema[], {
|
|
33
37
|
ignoreHasMutableChildren: boolean;
|
|
34
38
|
}>;
|
|
35
|
-
'required-attr': import("packages/@markuplint/ml-core/lib").RuleSeed<string | string
|
|
39
|
+
'required-attr': import("packages/@markuplint/ml-core/lib").RuleSeed<string | (string | {
|
|
40
|
+
name: string;
|
|
41
|
+
value: string | string[];
|
|
42
|
+
})[], null>;
|
|
36
43
|
'required-h1': import("packages/@markuplint/ml-core/lib").RuleSeed<boolean, import("./required-h1").RequiredH1Options>;
|
|
37
44
|
'wai-aria': import("packages/@markuplint/ml-core/lib").RuleSeed<true, {
|
|
38
45
|
checkingValue?: boolean | undefined;
|
package/lib/index.js
CHANGED
|
@@ -15,8 +15,10 @@ const deprecated_element_1 = (0, tslib_1.__importDefault)(require("./deprecated-
|
|
|
15
15
|
const doctype_1 = (0, tslib_1.__importDefault)(require("./doctype"));
|
|
16
16
|
const id_duplication_1 = (0, tslib_1.__importDefault)(require("./id-duplication"));
|
|
17
17
|
const indentation_1 = (0, tslib_1.__importDefault)(require("./indentation"));
|
|
18
|
+
const ineffective_attr_1 = (0, tslib_1.__importDefault)(require("./ineffective-attr"));
|
|
18
19
|
const invalid_attr_1 = (0, tslib_1.__importDefault)(require("./invalid-attr"));
|
|
19
20
|
const landmark_roles_1 = (0, tslib_1.__importDefault)(require("./landmark-roles"));
|
|
21
|
+
const no_boolean_attr_value_1 = (0, tslib_1.__importDefault)(require("./no-boolean-attr-value"));
|
|
20
22
|
const permitted_contents_1 = (0, tslib_1.__importDefault)(require("./permitted-contents"));
|
|
21
23
|
const required_attr_1 = (0, tslib_1.__importDefault)(require("./required-attr"));
|
|
22
24
|
const required_h1_1 = (0, tslib_1.__importDefault)(require("./required-h1"));
|
|
@@ -36,8 +38,10 @@ exports.default = {
|
|
|
36
38
|
doctype: doctype_1.default,
|
|
37
39
|
'id-duplication': id_duplication_1.default,
|
|
38
40
|
indentation: indentation_1.default,
|
|
41
|
+
'ineffective-attr': ineffective_attr_1.default,
|
|
39
42
|
'invalid-attr': invalid_attr_1.default,
|
|
40
43
|
'landmark-roles': landmark_roles_1.default,
|
|
44
|
+
'no-boolean-attr-value': no_boolean_attr_value_1.default,
|
|
41
45
|
'permitted-contents': permitted_contents_1.default,
|
|
42
46
|
'required-attr': required_attr_1.default,
|
|
43
47
|
'required-h1': required_h1_1.default,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ml_core_1 = require("@markuplint/ml-core");
|
|
4
|
+
exports.default = (0, ml_core_1.createRule)({
|
|
5
|
+
defaultServerity: 'warning',
|
|
6
|
+
defaultValue: true,
|
|
7
|
+
defaultOptions: null,
|
|
8
|
+
async verify({ document, report, t }) {
|
|
9
|
+
document.walkOn('Element', el => {
|
|
10
|
+
const attrSpec = (0, ml_core_1.getAttrSpecs)(el.nameWithNS, document.specs);
|
|
11
|
+
if (!attrSpec) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
for (const attr of el.attributes) {
|
|
15
|
+
if (attr.attrType === 'ps-attr') {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const name = attr.getName().potential;
|
|
19
|
+
const spec = attrSpec.find(s => s.name === name);
|
|
20
|
+
if (!spec) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const ineffectiveConditions = spec.ineffective
|
|
24
|
+
? Array.isArray(spec.ineffective)
|
|
25
|
+
? spec.ineffective
|
|
26
|
+
: [spec.ineffective]
|
|
27
|
+
: [];
|
|
28
|
+
if (ineffectiveConditions.some(selector => el.matches(selector))) {
|
|
29
|
+
report({
|
|
30
|
+
scope: el,
|
|
31
|
+
raw: attr.raw,
|
|
32
|
+
line: attr.startLine,
|
|
33
|
+
col: attr.startCol,
|
|
34
|
+
message: t('{0} is {1:c}', t('the "{0}" {1}', name, 'attribute'), 'ineffective') +
|
|
35
|
+
t('. ') +
|
|
36
|
+
t("It doesn't need {0}", t('the {0}', 'attribute')),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
});
|
|
@@ -4,11 +4,13 @@ declare type Option = {
|
|
|
4
4
|
ignoreAttrNamePrefix?: string | string[];
|
|
5
5
|
};
|
|
6
6
|
declare type Rule = {
|
|
7
|
-
enum: string[];
|
|
7
|
+
enum: [string, ...string[]];
|
|
8
8
|
} | {
|
|
9
9
|
pattern: string;
|
|
10
10
|
} | {
|
|
11
11
|
type: AttributeType;
|
|
12
|
+
} | {
|
|
13
|
+
disallowed: true;
|
|
12
14
|
};
|
|
13
15
|
declare const _default: import("@markuplint/ml-core").RuleSeed<true, Option>;
|
|
14
16
|
export default _default;
|
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const ml_core_1 = require("@markuplint/ml-core");
|
|
4
4
|
const attr_check_1 = require("../attr-check");
|
|
5
|
+
const debug_1 = require("../debug");
|
|
5
6
|
const helpers_1 = require("../helpers");
|
|
7
|
+
const log = debug_1.log.extend('invalid-attr');
|
|
6
8
|
exports.default = (0, ml_core_1.createRule)({
|
|
7
9
|
defaultValue: true,
|
|
8
10
|
defaultOptions: {},
|
|
9
11
|
async verify({ document, report, t }) {
|
|
10
12
|
await document.walkOn('Element', async (node) => {
|
|
11
|
-
|
|
13
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
14
|
+
const attrSpecs = (0, ml_core_1.getAttrSpecs)(node.nameWithNS, document.specs);
|
|
12
15
|
for (const attr of node.attributes) {
|
|
13
16
|
if (attr.attrType === 'html-attr' && attr.isDirective) {
|
|
14
17
|
continue;
|
|
@@ -61,9 +64,16 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
61
64
|
else if ('type' in customRule) {
|
|
62
65
|
invalid = (0, attr_check_1.attrCheck)(t, name, value, true, { name, type: customRule.type, description: '' });
|
|
63
66
|
}
|
|
67
|
+
else if ('disallowed' in customRule && customRule.disallowed) {
|
|
68
|
+
invalid = {
|
|
69
|
+
invalidType: 'non-existent',
|
|
70
|
+
message: t('{0} is disallowed', t('the "{0}" {1}', name, 'attribute')),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
64
73
|
}
|
|
65
74
|
else if (!node.isCustomElement && attrSpecs) {
|
|
66
|
-
|
|
75
|
+
log('Checking %s[%s="%s"]', node.nodeName, name, value);
|
|
76
|
+
invalid = (0, helpers_1.isValidAttr)(t, name, value, (attr.attrType === 'html-attr' && attr.isDynamicValue) || false, node, attrSpecs, log);
|
|
67
77
|
}
|
|
68
78
|
if (invalid) {
|
|
69
79
|
switch (invalid.invalidType) {
|
|
@@ -71,9 +81,9 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
71
81
|
report({
|
|
72
82
|
scope: node,
|
|
73
83
|
message: invalid.message,
|
|
74
|
-
line: attrValue.line,
|
|
75
|
-
col: attrValue.col,
|
|
76
|
-
raw: value,
|
|
84
|
+
line: attrValue.line + ((_b = (_a = invalid.loc) === null || _a === void 0 ? void 0 : _a.line) !== null && _b !== void 0 ? _b : 0),
|
|
85
|
+
col: ((_c = invalid.loc) === null || _c === void 0 ? void 0 : _c.line) ? ((_d = invalid.loc) === null || _d === void 0 ? void 0 : _d.col) + 1 : attrValue.col + ((_f = (_e = invalid.loc) === null || _e === void 0 ? void 0 : _e.col) !== null && _f !== void 0 ? _f : 0),
|
|
86
|
+
raw: (_h = (_g = invalid.loc) === null || _g === void 0 ? void 0 : _g.raw) !== null && _h !== void 0 ? _h : value,
|
|
77
87
|
});
|
|
78
88
|
break;
|
|
79
89
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ml_core_1 = require("@markuplint/ml-core");
|
|
4
|
+
exports.default = (0, ml_core_1.createRule)({
|
|
5
|
+
defaultServerity: 'warning',
|
|
6
|
+
defaultValue: true,
|
|
7
|
+
defaultOptions: null,
|
|
8
|
+
async verify({ document, report, t }) {
|
|
9
|
+
document.walkOn('Element', el => {
|
|
10
|
+
const attrSpec = (0, ml_core_1.getAttrSpecs)(el.nameWithNS, document.specs);
|
|
11
|
+
if (!attrSpec) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
for (const attr of el.attributes) {
|
|
15
|
+
if (attr.attrType === 'ps-attr') {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const name = attr.getName().potential;
|
|
19
|
+
const spec = attrSpec.find(s => s.name === name);
|
|
20
|
+
if (!spec) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (spec.type !== 'Boolean') {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const extraTokens = [
|
|
27
|
+
attr.spacesBeforeEqual,
|
|
28
|
+
attr.equal,
|
|
29
|
+
attr.spacesAfterEqual,
|
|
30
|
+
attr.startQuote,
|
|
31
|
+
attr.value,
|
|
32
|
+
attr.endQuote,
|
|
33
|
+
];
|
|
34
|
+
const extraRaw = extraTokens.reduce((raw, t) => raw + t.raw, '');
|
|
35
|
+
if (extraRaw) {
|
|
36
|
+
report({
|
|
37
|
+
scope: el,
|
|
38
|
+
raw: extraRaw,
|
|
39
|
+
line: attr.spacesBeforeEqual.startLine,
|
|
40
|
+
col: attr.spacesBeforeEqual.startCol,
|
|
41
|
+
message: t('{0} is {1}', t('the "{0}" {1}', name, 'attribute'), t('a {0}', 'boolean attribute')) +
|
|
42
|
+
t('. ') +
|
|
43
|
+
t("It doesn't need {0}", t('the {0}', 'value')),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
declare type RequiredAttributes = string | string[];
|
|
1
|
+
declare type RequiredAttributes = string | (string | Attr)[];
|
|
2
|
+
declare type Attr = {
|
|
3
|
+
name: string;
|
|
4
|
+
value: string | string[];
|
|
5
|
+
};
|
|
2
6
|
declare const _default: import("@markuplint/ml-core").RuleSeed<RequiredAttributes, null>;
|
|
3
7
|
export default _default;
|
|
@@ -7,29 +7,45 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
7
7
|
defaultOptions: null,
|
|
8
8
|
async verify({ document, report, t }) {
|
|
9
9
|
await document.walkOn('Element', async (node) => {
|
|
10
|
+
var _a;
|
|
10
11
|
if (node.hasSpreadAttr) {
|
|
11
12
|
return;
|
|
12
13
|
}
|
|
13
14
|
const customRequiredAttrs = typeof node.rule.value === 'string' ? [node.rule.value] : node.rule.value;
|
|
14
|
-
const attrSpec = (0,
|
|
15
|
-
const attributeSpecs =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (required) {
|
|
19
|
-
|
|
20
|
-
...
|
|
21
|
-
|
|
15
|
+
const attrSpec = (0, ml_core_1.getAttrSpecs)(node.nameWithNS, document.specs);
|
|
16
|
+
const attributeSpecs = {};
|
|
17
|
+
if (attrSpec) {
|
|
18
|
+
for (const spec of attrSpec) {
|
|
19
|
+
if (spec.required || spec.requiredEither || spec.condition) {
|
|
20
|
+
attributeSpecs[spec.name] = {
|
|
21
|
+
...spec,
|
|
22
|
+
values: [],
|
|
22
23
|
};
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const req of customRequiredAttrs) {
|
|
28
|
+
let name;
|
|
29
|
+
const values = [];
|
|
30
|
+
if (typeof req === 'string') {
|
|
31
|
+
name = req;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
name = req.name;
|
|
35
|
+
if (Array.isArray(req.value)) {
|
|
36
|
+
values.push(...req.value);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
values.push(req.value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
attributeSpecs[name] = {
|
|
43
|
+
name,
|
|
44
|
+
values,
|
|
28
45
|
required: true,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
for (const spec of attributeSpecs) {
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
for (const spec of Object.values(attributeSpecs)) {
|
|
33
49
|
const didntHave = !node.hasAttribute(spec.name);
|
|
34
50
|
let invalid = false;
|
|
35
51
|
if (spec.requiredEither) {
|
|
@@ -41,17 +57,43 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
41
57
|
}
|
|
42
58
|
else if (spec.required) {
|
|
43
59
|
if ('ancestor' in spec.required && spec.required.ancestor) {
|
|
44
|
-
const
|
|
60
|
+
const ancestorList = Array.isArray(spec.required.ancestor)
|
|
61
|
+
? spec.required.ancestor
|
|
62
|
+
: [spec.required.ancestor];
|
|
63
|
+
const ancestors = ancestorList
|
|
64
|
+
.join(',')
|
|
65
|
+
.split(',')
|
|
66
|
+
.map(a => a.trim());
|
|
45
67
|
invalid = ancestors.some(a => node.closest(a)) && didntHave;
|
|
46
68
|
}
|
|
47
69
|
}
|
|
48
70
|
if (invalid) {
|
|
49
|
-
const message = t('{0} expects {1}', t('the "{0}" {1}',
|
|
71
|
+
const message = t('{0} expects {1}', t('the "{0}" {1}', node.nodeName, 'element'), t('the "{0}" {1}', spec.name, 'attribute'));
|
|
50
72
|
report({
|
|
51
73
|
scope: node,
|
|
52
74
|
message,
|
|
53
75
|
});
|
|
54
76
|
}
|
|
77
|
+
const value = node.getAttribute(spec.name);
|
|
78
|
+
if (spec.values.length && value) {
|
|
79
|
+
const matched = spec.values.some(pattern => (0, helpers_1.match)(value, pattern));
|
|
80
|
+
if (!matched) {
|
|
81
|
+
const expects = spec.values.length === 1 ? t(spec.values) : t('either {0}', t(spec.values));
|
|
82
|
+
const message = t('{0} expects {1}', t('the "{0}" {1}', spec.name, 'attribute'), expects);
|
|
83
|
+
const attrToken = node.getAttributeToken(spec.name);
|
|
84
|
+
const valueToken = ((_a = attrToken[0]) === null || _a === void 0 ? void 0 : _a.attrType) === 'html-attr' ? attrToken[0].value : null;
|
|
85
|
+
const token = valueToken || attrToken[0];
|
|
86
|
+
report({
|
|
87
|
+
scope: {
|
|
88
|
+
rule: node.rule,
|
|
89
|
+
raw: token.raw,
|
|
90
|
+
startCol: token.startCol,
|
|
91
|
+
startLine: token.startLine,
|
|
92
|
+
},
|
|
93
|
+
message,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
55
97
|
}
|
|
56
98
|
});
|
|
57
99
|
},
|
package/lib/wai-aria/index.js
CHANGED
|
@@ -14,7 +14,7 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
14
14
|
},
|
|
15
15
|
async verify({ document, report, t }) {
|
|
16
16
|
await document.walkOn('Element', async (node) => {
|
|
17
|
-
const attrSpecs = (0,
|
|
17
|
+
const attrSpecs = (0, ml_core_1.getAttrSpecs)(node.nameWithNS, document.specs);
|
|
18
18
|
const html = (0, helpers_1.htmlSpec)(node.nodeName);
|
|
19
19
|
const { roles, ariaAttrs } = (0, helpers_1.ariaSpec)();
|
|
20
20
|
if (!html || !attrSpecs) {
|
|
@@ -167,7 +167,7 @@ exports.default = (0, ml_core_1.createRule)({
|
|
|
167
167
|
scope: node,
|
|
168
168
|
message: t('{0:c} on {1}', t('{0} is {1:c}', t('the "{0}"', value), 'disallowed'), t('the "{0}" {1}', attrName, 'ARIA state/property')) +
|
|
169
169
|
('enum' in result && result.enum.length
|
|
170
|
-
? t('. ') + t('
|
|
170
|
+
? t('. ') + t('Allowed values are: {0}', t(result.enum))
|
|
171
171
|
: ''),
|
|
172
172
|
line: attr.startLine,
|
|
173
173
|
col: attr.startCol,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/rules",
|
|
3
|
-
"version": "2.0.0-dev.
|
|
3
|
+
"version": "2.0.0-dev.20211213.0",
|
|
4
4
|
"description": "Rules for markuplint",
|
|
5
5
|
"repository": "git@github.com:markuplint/markuplint.git",
|
|
6
6
|
"author": "Yusuke Hirao <yusukehirao@me.com>",
|
|
@@ -17,10 +17,15 @@
|
|
|
17
17
|
"clean": "tsc --build --clean"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@markuplint/html-spec": "2.0.0-dev.
|
|
21
|
-
"@markuplint/ml-core": "2.0.0-dev.
|
|
22
|
-
"@markuplint/ml-spec": "2.0.0-dev.
|
|
20
|
+
"@markuplint/html-spec": "2.0.0-dev.20211213.0",
|
|
21
|
+
"@markuplint/ml-core": "2.0.0-dev.20211213.0",
|
|
22
|
+
"@markuplint/ml-spec": "2.0.0-dev.20211213.0",
|
|
23
|
+
"@markuplint/types": "1.0.0-dev.20211213.0",
|
|
24
|
+
"debug": "^4.3.2",
|
|
23
25
|
"tslib": "^2.3.1"
|
|
24
26
|
},
|
|
25
|
-
"
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/debug": "^4.1.7"
|
|
29
|
+
},
|
|
30
|
+
"gitHead": "8f4682ca3e0937082af8c39b999443afa4a1aef3"
|
|
26
31
|
}
|