@markuplint/rules 2.0.0-rc.0 → 2.0.0-rc.1
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 +52 -454
- package/lib/attr-duplication/index.d.ts +1 -1
- package/lib/attr-duplication/index.js +0 -2
- package/lib/attr-equal-space-after/index.js +0 -1
- package/lib/attr-equal-space-before/index.js +0 -1
- package/lib/attr-spacing/index.js +0 -1
- package/lib/attr-value-quotes/index.js +0 -1
- package/lib/case-sensitive-attr-name/index.js +2 -4
- package/lib/case-sensitive-tag-name/index.js +0 -1
- package/lib/character-reference/index.d.ts +1 -2
- package/lib/character-reference/index.js +0 -2
- package/lib/class-naming/index.js +23 -23
- package/lib/create-message.d.ts +5 -0
- package/lib/create-message.js +270 -0
- package/lib/debug.d.ts +3 -0
- package/lib/debug.js +6 -0
- package/lib/deprecated-attr/index.d.ts +1 -1
- package/lib/deprecated-attr/index.js +1 -4
- package/lib/deprecated-element/index.d.ts +1 -1
- package/lib/deprecated-element/index.js +0 -2
- package/lib/disallowed-element/index.d.ts +2 -0
- package/lib/disallowed-element/index.js +34 -0
- package/lib/helpers.d.ts +9 -3
- package/lib/helpers.js +37 -41
- package/lib/id-duplication/index.d.ts +1 -1
- package/lib/id-duplication/index.js +0 -2
- package/lib/index.d.ts +27 -10
- package/lib/index.js +16 -0
- package/lib/ineffective-attr/index.d.ts +2 -0
- package/lib/ineffective-attr/index.js +40 -0
- package/lib/invalid-attr/index.d.ts +4 -2
- package/lib/invalid-attr/index.js +16 -7
- package/lib/landmark-roles/index.js +0 -1
- package/lib/no-boolean-attr-value/index.d.ts +2 -0
- package/lib/no-boolean-attr-value/index.js +47 -0
- package/lib/no-default-value/index.d.ts +2 -0
- package/lib/no-default-value/index.js +38 -0
- package/lib/no-hard-code-id/index.d.ts +2 -0
- package/lib/no-hard-code-id/index.js +27 -0
- package/lib/no-refer-to-non-existent-id/index.d.ts +2 -0
- package/lib/no-refer-to-non-existent-id/index.js +110 -0
- package/lib/no-use-event-handler-attr/index.d.ts +5 -0
- package/lib/no-use-event-handler-attr/index.js +37 -0
- package/lib/permitted-contents/index.js +11 -0
- package/lib/require-element/index.d.ts +2 -0
- package/lib/require-element/index.js +38 -0
- package/lib/required-attr/index.d.ts +5 -1
- package/lib/required-attr/index.js +60 -19
- package/lib/required-element/index.d.ts +5 -0
- package/lib/required-element/index.js +44 -0
- package/lib/required-h1/index.d.ts +2 -3
- package/lib/required-h1/index.js +0 -1
- package/lib/wai-aria/index.d.ts +1 -1
- package/lib/wai-aria/index.js +2 -3
- package/package.json +10 -5
- package/schema.json +24 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/primitive-check.d.ts +0 -48
- package/lib/primitive-check.js +0 -109
|
@@ -5,31 +5,31 @@ const helpers_1 = require("../helpers");
|
|
|
5
5
|
exports.default = (0, ml_core_1.createRule)({
|
|
6
6
|
defaultServerity: 'warning',
|
|
7
7
|
defaultValue: null,
|
|
8
|
-
defaultOptions: null,
|
|
9
8
|
async verify({ document, report, t }) {
|
|
10
9
|
await document.walkOn('Element', async (node) => {
|
|
11
|
-
if (node.rule.value) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
10
|
+
if (!node.rule.value) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const classPatterns = Array.isArray(node.rule.value) ? node.rule.value : [node.rule.value];
|
|
14
|
+
const attrs = node.getAttributeToken('class');
|
|
15
|
+
for (const attr of attrs) {
|
|
16
|
+
if (attr.attrType === 'html-attr' && attr.isDynamicValue) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const classAttr = attr.getValue();
|
|
20
|
+
const classList = classAttr.potential
|
|
21
|
+
.split(/\s+/g)
|
|
22
|
+
.map(c => c.trim())
|
|
23
|
+
.filter(c => c);
|
|
24
|
+
for (const className of classList) {
|
|
25
|
+
if (!classPatterns.some(pattern => (0, helpers_1.match)(className, pattern))) {
|
|
26
|
+
report({
|
|
27
|
+
scope: node,
|
|
28
|
+
message: t('{0} is unmatched with the below patterns: {1}', t('the "{0}" {1}', className, 'class name'), `"${classPatterns.join('", "')}"`),
|
|
29
|
+
line: classAttr.line,
|
|
30
|
+
col: classAttr.col,
|
|
31
|
+
raw: classAttr.raw,
|
|
32
|
+
});
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Translator } from '@markuplint/i18n';
|
|
2
|
+
import type { AttributeType } from '@markuplint/ml-spec';
|
|
3
|
+
import type { UnmatchedResult } from '@markuplint/types';
|
|
4
|
+
export declare function createMessageValueExpected(t: Translator, baseTarget: string, type: AttributeType, matches: UnmatchedResult): string;
|
|
5
|
+
export declare function __createMessageValueExpected(t: Translator, baseTarget: string, expected: string | null, matches: Pick<UnmatchedResult, 'partName' | 'reason' | 'raw' | 'candicate' | 'ref' | 'extra'>): string;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.__createMessageValueExpected = exports.createMessageValueExpected = void 0;
|
|
4
|
+
const types_1 = require("@markuplint/types");
|
|
5
|
+
function createMessageValueExpected(t, baseTarget, type, matches) {
|
|
6
|
+
let target = baseTarget;
|
|
7
|
+
let listDescriptionPart;
|
|
8
|
+
let tokenType;
|
|
9
|
+
if ((0, types_1.isList)(type)) {
|
|
10
|
+
listDescriptionPart = t('{0} expects {1:c}', target, type.separator === 'space' ? 'space-separated list' : 'comma-separated list');
|
|
11
|
+
tokenType = type.token;
|
|
12
|
+
target = 'the content of the list';
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
tokenType = type;
|
|
16
|
+
}
|
|
17
|
+
const expected = createExpectedObject(tokenType, matches, t);
|
|
18
|
+
const message = [listDescriptionPart, __createMessageValueExpected(t, target, expected, matches)]
|
|
19
|
+
.filter(s => s)
|
|
20
|
+
.join(t('. '));
|
|
21
|
+
return message;
|
|
22
|
+
}
|
|
23
|
+
exports.createMessageValueExpected = createMessageValueExpected;
|
|
24
|
+
function __createMessageValueExpected(t, baseTarget, expected, matches) {
|
|
25
|
+
let target = baseTarget;
|
|
26
|
+
let reasonPart;
|
|
27
|
+
let expectPart;
|
|
28
|
+
let unnecessaryPart;
|
|
29
|
+
let candicatePart;
|
|
30
|
+
let expectOrNeed = 'expects';
|
|
31
|
+
if (matches.partName) {
|
|
32
|
+
if (matches.partName === 'the content of the list') {
|
|
33
|
+
target = 'the content of the list';
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
target = t('{0} part of {1}', t('the {0}', matches.partName), target);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
switch (matches.reason) {
|
|
40
|
+
case 'doesnt-exist-in-enum': {
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case 'duplicated': {
|
|
44
|
+
reasonPart = t('{0} is {1:c}', target, 'duplicated');
|
|
45
|
+
target = 'it';
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case 'empty-token': {
|
|
49
|
+
reasonPart = t('{0} must not be {1}', target, 'empty');
|
|
50
|
+
target = 'it';
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case 'extra-token': {
|
|
54
|
+
const token = matches.partName || 'token';
|
|
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
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case 'illegal-combination': {
|
|
63
|
+
reasonPart = t('Found {0}', t('an {0}', 'illegal combination'));
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case 'illegal-order': {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
case 'missing-comma': {
|
|
70
|
+
if (matches.partName) {
|
|
71
|
+
reasonPart = t('Missing {0} in {1}', t('a {0}', 'comma'), t('the {0} part', matches.partName));
|
|
72
|
+
target = 'it';
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
reasonPart = t('Missing {0}', t('a {0}', 'comma'));
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case 'missing-token': {
|
|
80
|
+
if (matches.partName) {
|
|
81
|
+
const part = t('the {0} part', matches.partName);
|
|
82
|
+
reasonPart = t('Missing {0}', part);
|
|
83
|
+
if (!expected) {
|
|
84
|
+
expected = part;
|
|
85
|
+
target = baseTarget;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
reasonPart = t('Missing {0}', t('a {0}', 'token'));
|
|
90
|
+
}
|
|
91
|
+
expectOrNeed = 'needs';
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case 'must-be-percent-encoded': {
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case 'must-be-serialized': {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
case 'out-of-range': {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
case 'syntax-error': {
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
case 'typo': {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 'unexpected-comma': {
|
|
110
|
+
reasonPart = t('Found {0}', t('unexpected {0}', 'comma'));
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case 'unexpected-newline': {
|
|
114
|
+
reasonPart = t('Found {0}', t('unexpected {0}', 'newline'));
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case 'unexpected-space': {
|
|
118
|
+
reasonPart = t('Found {0}', t('unexpected {0}', 'whitespace'));
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case 'unexpected-token': {
|
|
122
|
+
const chars = t('unexpected {0}', 'characters');
|
|
123
|
+
if (matches.partName) {
|
|
124
|
+
const part = t('the {0} part', matches.partName);
|
|
125
|
+
reasonPart = t('{0} includes {1}', part, chars);
|
|
126
|
+
target = 'it';
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
reasonPart = t('It includes {0}', chars);
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
default: {
|
|
134
|
+
if ('type' in matches.reason) {
|
|
135
|
+
switch (matches.reason.type) {
|
|
136
|
+
case 'out-of-range-length-digit': {
|
|
137
|
+
const { gte, lte } = matches.reason;
|
|
138
|
+
let expectedDigits = null;
|
|
139
|
+
if (lte != null && gte === lte) {
|
|
140
|
+
expectedDigits = t('{0} digits', gte);
|
|
141
|
+
}
|
|
142
|
+
else if (lte != null) {
|
|
143
|
+
expectedDigits = t('{0} to {1} digits', gte, lte);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
expectedDigits = t('{0} or more digits', gte);
|
|
147
|
+
}
|
|
148
|
+
if (!expected) {
|
|
149
|
+
expected = expectedDigits;
|
|
150
|
+
}
|
|
151
|
+
else if (expected && expectedDigits) {
|
|
152
|
+
expected = t('{0:c} and {1:c}', expectedDigits, expected);
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case 'out-of-range': {
|
|
157
|
+
const expectedNum = createExpectedNumber(t, {
|
|
158
|
+
...matches.reason,
|
|
159
|
+
type: 'integer',
|
|
160
|
+
});
|
|
161
|
+
if (!expected) {
|
|
162
|
+
expected = expectedNum;
|
|
163
|
+
}
|
|
164
|
+
else if (expected) {
|
|
165
|
+
expected = t('{0:c} and {1:c}', expectedNum, expected);
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (expected) {
|
|
174
|
+
if (target === 'it') {
|
|
175
|
+
if (expectOrNeed === 'expects') {
|
|
176
|
+
expectPart = t('It expects {0:c}', expected);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
expectPart = t('It needs {0}', expected);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
if (expectOrNeed === 'expects') {
|
|
184
|
+
expectPart = t('{0} expects {1:c}', target, expected);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
expectPart = t('{0} needs {1}', target, expected);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (matches.candicate) {
|
|
192
|
+
candicatePart = t('Did you mean "{0*}"?', matches.candicate);
|
|
193
|
+
}
|
|
194
|
+
let message = [reasonPart, unnecessaryPart, expectPart, candicatePart].filter(s => s).join(t('. '));
|
|
195
|
+
if (matches.ref) {
|
|
196
|
+
message += ` (${matches.ref})`;
|
|
197
|
+
}
|
|
198
|
+
return message;
|
|
199
|
+
}
|
|
200
|
+
exports.__createMessageValueExpected = __createMessageValueExpected;
|
|
201
|
+
function createExpectedObject(type, matches, t) {
|
|
202
|
+
var _a;
|
|
203
|
+
const expectedObject = [];
|
|
204
|
+
if ((_a = matches.expects) === null || _a === void 0 ? void 0 : _a.length) {
|
|
205
|
+
expectedObject.push(...matches.expects.map(expect => {
|
|
206
|
+
return expectValueToWord(t, expect, type);
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
else if ((0, types_1.isKeyword)(type)) {
|
|
210
|
+
if (type[0] === '<') {
|
|
211
|
+
expectedObject.push(t('the CSS Syntax "{0}"', type));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else if ((0, types_1.isEnum)(type)) {
|
|
215
|
+
expectedObject.push(...type.enum);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
expectedObject.push(createExpectedNumber(t, type));
|
|
219
|
+
}
|
|
220
|
+
const expects = expectedObject.length === 0
|
|
221
|
+
? null
|
|
222
|
+
: 1 < expectedObject.length
|
|
223
|
+
? t('either {0}', t(expectedObject))
|
|
224
|
+
: expectedObject[0];
|
|
225
|
+
return expects;
|
|
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
|
+
}
|
|
244
|
+
function createExpectedNumber(t, type) {
|
|
245
|
+
if (type.gt != null) {
|
|
246
|
+
if (type.lt != null) {
|
|
247
|
+
return t('{0} greater than {1} less than {2}', type.type, type.gt, type.lt);
|
|
248
|
+
}
|
|
249
|
+
if (type.lte != null) {
|
|
250
|
+
return t('{0} greater than {1} less than or equal to {2}', type.type, type.gt, type.lte);
|
|
251
|
+
}
|
|
252
|
+
return t('{0} greater than {1}', type.type, type.gt);
|
|
253
|
+
}
|
|
254
|
+
if (type.gte != null) {
|
|
255
|
+
if (type.lt != null) {
|
|
256
|
+
return t('{0} greater than or equal to {1} less than {2}', type.type, type.gte, type.lt);
|
|
257
|
+
}
|
|
258
|
+
if (type.lte != null) {
|
|
259
|
+
return t('{0} greater than or equal to {1} less than or equal to {2}', type.type, type.gte, type.lte);
|
|
260
|
+
}
|
|
261
|
+
return t('{0} greater than or equal to {1}', type.type, type.gte);
|
|
262
|
+
}
|
|
263
|
+
if (type.lt != null) {
|
|
264
|
+
return t('{0} less than {1}', type.type, type.lt);
|
|
265
|
+
}
|
|
266
|
+
if (type.lte != null) {
|
|
267
|
+
return t('{0} less than or equal to {1}', type.type, type.lte);
|
|
268
|
+
}
|
|
269
|
+
return type.type;
|
|
270
|
+
}
|
package/lib/debug.d.ts
ADDED
package/lib/debug.js
ADDED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("@markuplint/ml-core").RuleSeed<
|
|
1
|
+
declare const _default: import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, null>;
|
|
2
2
|
export default _default;
|
|
@@ -1,13 +1,10 @@
|
|
|
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
|
-
defaultValue: null,
|
|
7
|
-
defaultOptions: null,
|
|
8
5
|
async verify({ document, report, t }) {
|
|
9
6
|
await document.walkOn('Element', async (element) => {
|
|
10
|
-
const attrSpecs = (0,
|
|
7
|
+
const attrSpecs = (0, ml_core_1.getAttrSpecs)(element.nameWithNS, document.specs);
|
|
11
8
|
if (!attrSpecs) {
|
|
12
9
|
return;
|
|
13
10
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("@markuplint/ml-core").RuleSeed<
|
|
1
|
+
declare const _default: import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, null>;
|
|
2
2
|
export default _default;
|
|
@@ -5,8 +5,6 @@ const html_spec_1 = (0, tslib_1.__importDefault)(require("@markuplint/html-spec"
|
|
|
5
5
|
const ml_core_1 = require("@markuplint/ml-core");
|
|
6
6
|
const ml_spec_1 = require("@markuplint/ml-spec");
|
|
7
7
|
exports.default = (0, ml_core_1.createRule)({
|
|
8
|
-
defaultValue: null,
|
|
9
|
-
defaultOptions: null,
|
|
10
8
|
async verify({ document, report, t }) {
|
|
11
9
|
await document.walkOn('Element', async (el) => {
|
|
12
10
|
const ns = el.ns;
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
defaultValue: [],
|
|
6
|
+
defaultOptions: null,
|
|
7
|
+
async verify({ document, report, t, globalRule }) {
|
|
8
|
+
for (const query of globalRule.value) {
|
|
9
|
+
const elements = document.matchNodes(query);
|
|
10
|
+
for (const el of elements) {
|
|
11
|
+
const message = t('{0} is disallowed', t('the "{0*}" {1}', query, 'element'));
|
|
12
|
+
report({
|
|
13
|
+
scope: el,
|
|
14
|
+
message,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
await document.walkOn('Element', el => {
|
|
19
|
+
if (el.rule.value === globalRule.value) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
for (const query of el.rule.value) {
|
|
23
|
+
const elements = el.querySelectorAll(query);
|
|
24
|
+
for (const el of elements) {
|
|
25
|
+
const message = t('{0} is disallowed', t('the "{0*}" {1}', query, 'element'));
|
|
26
|
+
report({
|
|
27
|
+
scope: el,
|
|
28
|
+
message,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
});
|
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,10 +21,16 @@ 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
|
};
|
|
33
|
+
export declare function toNormalizedValue(value: string, spec: Attribute): string;
|
|
28
34
|
export declare function ariaSpec(): {
|
|
29
35
|
roles: ARIRRoleAttribute[];
|
|
30
36
|
ariaAttrs: import("@markuplint/ml-spec").ARIAAttribute[];
|
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.toNormalizedValue = 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 = {
|
|
@@ -130,6 +97,35 @@ function isValidAttr(t, name, value, isDynamicValue, node, attrSpecs) {
|
|
|
130
97
|
return invalid;
|
|
131
98
|
}
|
|
132
99
|
exports.isValidAttr = isValidAttr;
|
|
100
|
+
function toNormalizedValue(value, spec) {
|
|
101
|
+
let normalized = value;
|
|
102
|
+
if (!spec.caseSensitive) {
|
|
103
|
+
normalized = normalized.toLowerCase();
|
|
104
|
+
}
|
|
105
|
+
if (typeof spec.type === 'string') {
|
|
106
|
+
if (spec.type[0] === '<') {
|
|
107
|
+
normalized = normalized.toLowerCase().trim().replace(/\s+/g, ' ');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
if ('token' in spec.type) {
|
|
112
|
+
if (spec.type.caseInsensitive) {
|
|
113
|
+
normalized = normalized.toLowerCase();
|
|
114
|
+
}
|
|
115
|
+
if (!spec.type.disallowToSurroundBySpaces) {
|
|
116
|
+
normalized = normalized.trim();
|
|
117
|
+
}
|
|
118
|
+
if (spec.type.separator === 'space') {
|
|
119
|
+
normalized = normalized.replace(/\s+/g, ' ');
|
|
120
|
+
}
|
|
121
|
+
if (spec.type.separator === 'comma') {
|
|
122
|
+
normalized = normalized.replace(/\s*,\s*/g, ',');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return normalized;
|
|
127
|
+
}
|
|
128
|
+
exports.toNormalizedValue = toNormalizedValue;
|
|
133
129
|
function ariaSpec() {
|
|
134
130
|
const roles = html_spec_1.def['#roles'];
|
|
135
131
|
const ariaAttrs = html_spec_1.def['#ariaAttrs'];
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("@markuplint/ml-core").RuleSeed<
|
|
1
|
+
declare const _default: import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, null>;
|
|
2
2
|
export default _default;
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const ml_core_1 = require("@markuplint/ml-core");
|
|
4
4
|
exports.default = (0, ml_core_1.createRule)({
|
|
5
|
-
defaultValue: null,
|
|
6
|
-
defaultOptions: null,
|
|
7
5
|
async verify({ document, report, t }) {
|
|
8
6
|
const message = t('{0} is {1:c}', t('{0} of {1}', t('the {0}', 'value'), t('the "{0}" {1}', 'id', 'attribute')), 'duplicated');
|
|
9
7
|
const idStack = [];
|
package/lib/index.d.ts
CHANGED
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
declare const _default: {
|
|
2
|
-
'attr-duplication': import("packages/@markuplint/ml-core/lib").RuleSeed<
|
|
2
|
+
'attr-duplication': import("packages/@markuplint/ml-core/lib").RuleSeed<import("packages/@markuplint/ml-config/src").RuleConfigValue, null>;
|
|
3
3
|
'attr-equal-space-after': import("packages/@markuplint/ml-core/lib").RuleSeed<"always" | "never" | "always-single-line" | "never-single-line", null>;
|
|
4
4
|
'attr-equal-space-before': import("packages/@markuplint/ml-core/lib").RuleSeed<"always" | "never" | "always-single-line" | "never-single-line", null>;
|
|
5
5
|
'attr-spacing': import("packages/@markuplint/ml-core/lib").RuleSeed<boolean, import("./attr-spacing").AttrSpasingOptions>;
|
|
6
6
|
'attr-value-quotes': import("packages/@markuplint/ml-core/lib").RuleSeed<import("./attr-value-quotes").Type, null>;
|
|
7
7
|
'case-sensitive-attr-name': import("packages/@markuplint/ml-core/lib").RuleSeed<import("./case-sensitive-attr-name").Value, null>;
|
|
8
8
|
'case-sensitive-tag-name': import("packages/@markuplint/ml-core/lib").RuleSeed<import("./case-sensitive-tag-name").Value, null>;
|
|
9
|
-
'character-reference': import("packages/@markuplint/ml-core/lib").RuleSeed<
|
|
9
|
+
'character-reference': import("packages/@markuplint/ml-core/lib").RuleSeed<import("packages/@markuplint/ml-config/src").RuleConfigValue, null>;
|
|
10
10
|
'class-naming': import("packages/@markuplint/ml-core/lib").RuleSeed<import("./class-naming").Value, null>;
|
|
11
|
-
'deprecated-attr': import("packages/@markuplint/ml-core/lib").RuleSeed<
|
|
12
|
-
'deprecated-element': import("packages/@markuplint/ml-core/lib").RuleSeed<
|
|
11
|
+
'deprecated-attr': import("packages/@markuplint/ml-core/lib").RuleSeed<import("packages/@markuplint/ml-config/src").RuleConfigValue, null>;
|
|
12
|
+
'deprecated-element': import("packages/@markuplint/ml-core/lib").RuleSeed<import("packages/@markuplint/ml-config/src").RuleConfigValue, null>;
|
|
13
|
+
'disallowed-element': import("packages/@markuplint/ml-core/lib").RuleSeed<string[], null>;
|
|
13
14
|
doctype: import("packages/@markuplint/ml-core/lib").RuleSeed<"always", {
|
|
14
15
|
denyObsolateType: boolean;
|
|
15
16
|
}>;
|
|
16
|
-
'id-duplication': import("packages/@markuplint/ml-core/lib").RuleSeed<
|
|
17
|
+
'id-duplication': import("packages/@markuplint/ml-core/lib").RuleSeed<import("packages/@markuplint/ml-config/src").RuleConfigValue, null>;
|
|
17
18
|
indentation: import("packages/@markuplint/ml-core/lib").RuleSeed<import("./indentation").Value, import("./indentation").IndentationOptions>;
|
|
18
|
-
'
|
|
19
|
+
'ineffective-attr': import("packages/@markuplint/ml-core/lib").RuleSeed<import("packages/@markuplint/ml-config/src").RuleConfigValue, null>;
|
|
20
|
+
'invalid-attr': import("packages/@markuplint/ml-core/lib").RuleSeed<boolean, {
|
|
19
21
|
attrs?: Record<string, {
|
|
20
|
-
enum: string[];
|
|
22
|
+
enum: [string, ...string[]];
|
|
21
23
|
} | {
|
|
22
24
|
pattern: string;
|
|
23
25
|
} | {
|
|
24
26
|
type: import("packages/@markuplint/ml-spec/lib").AttributeType;
|
|
27
|
+
} | {
|
|
28
|
+
disallowed: true;
|
|
25
29
|
}> | undefined;
|
|
26
30
|
ignoreAttrNamePrefix?: string | string[] | undefined;
|
|
27
31
|
}>;
|
|
@@ -29,12 +33,25 @@ declare const _default: {
|
|
|
29
33
|
ignoreRoles?: (("banner" | "main" | "complementary" | "contentinfo") | "form" | "navigation" | "region")[] | undefined;
|
|
30
34
|
labelEachArea?: boolean | undefined;
|
|
31
35
|
}>;
|
|
36
|
+
'no-boolean-attr-value': import("packages/@markuplint/ml-core/lib").RuleSeed<import("packages/@markuplint/ml-config/src").RuleConfigValue, null>;
|
|
37
|
+
'no-default-value': import("packages/@markuplint/ml-core/lib").RuleSeed<import("packages/@markuplint/ml-config/src").RuleConfigValue, null>;
|
|
38
|
+
'no-hard-code-id': import("packages/@markuplint/ml-core/lib").RuleSeed<import("packages/@markuplint/ml-config/src").RuleConfigValue, null>;
|
|
39
|
+
'no-refer-to-non-existent-id': import("packages/@markuplint/ml-core/lib").RuleSeed<import("packages/@markuplint/ml-config/src").RuleConfigValue, null>;
|
|
40
|
+
'no-use-event-handler-attr': import("packages/@markuplint/ml-core/lib").RuleSeed<boolean, {
|
|
41
|
+
ignore?: string | string[] | undefined;
|
|
42
|
+
}>;
|
|
32
43
|
'permitted-contents': import("packages/@markuplint/ml-core/lib").RuleSeed<import("packages/@markuplint/ml-spec/lib").PermittedStructuresSchema[], {
|
|
33
44
|
ignoreHasMutableChildren: boolean;
|
|
34
45
|
}>;
|
|
35
|
-
'required-attr': import("packages/@markuplint/ml-core/lib").RuleSeed<string | string
|
|
36
|
-
|
|
37
|
-
|
|
46
|
+
'required-attr': import("packages/@markuplint/ml-core/lib").RuleSeed<string | (string | {
|
|
47
|
+
name: string;
|
|
48
|
+
value: string | string[];
|
|
49
|
+
})[], null>;
|
|
50
|
+
'required-element': import("packages/@markuplint/ml-core/lib").RuleSeed<string[], {
|
|
51
|
+
ignoreHasMutableContents?: boolean | undefined;
|
|
52
|
+
}>;
|
|
53
|
+
'required-h1': import("packages/@markuplint/ml-core/lib").RuleSeed<boolean, import("./required-h1").Options>;
|
|
54
|
+
'wai-aria': import("packages/@markuplint/ml-core/lib").RuleSeed<boolean, {
|
|
38
55
|
checkingValue?: boolean | undefined;
|
|
39
56
|
checkingDeprecatedProps?: boolean | undefined;
|
|
40
57
|
permittedAriaRoles?: boolean | undefined;
|