@markuplint/rules 3.7.0 → 3.9.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.
Files changed (60) hide show
  1. package/lib/helpers.d.ts +2 -5
  2. package/lib/index.d.ts +240 -99
  3. package/lib/invalid-attr/index.d.ts +38 -31
  4. package/lib/no-refer-to-non-existent-id/index.d.ts +1 -2
  5. package/lib/no-refer-to-non-existent-id/index.js +2 -1
  6. package/lib/require-accessible-name/index.js +1 -1
  7. package/lib/required-attr/index.d.ts +1 -1
  8. package/lib/required-attr/index.js +1 -1
  9. package/lib/wai-aria/checkings/_.d.ts +15 -0
  10. package/lib/wai-aria/checkings/_.js +27 -0
  11. package/lib/wai-aria/checkings/required-prop copy.d.ts +15 -0
  12. package/lib/wai-aria/checkings/required-prop copy.js +27 -0
  13. package/lib/wai-aria/checkings/unadopted-explicit-roles.d.ts +15 -0
  14. package/lib/wai-aria/checkings/unadopted-explicit-roles.js +17 -0
  15. package/lib/wai-aria/index.js +2 -1
  16. package/lib/xxx/index.d.ts +2 -0
  17. package/lib/xxx/index.js +32 -0
  18. package/package.json +11 -13
  19. package/test/attr-check.spec.js +77 -0
  20. package/test/attr-duplication/index.spec.js +159 -0
  21. package/test/attr-value-quotes/index.spec.js +133 -0
  22. package/test/case-sensitive-attr-name/index.spec.js +65 -0
  23. package/test/case-sensitive-tag-name/index.spec.js +80 -0
  24. package/test/character-reference/index.spec.js +57 -0
  25. package/test/class-naming/index.spec.js +470 -0
  26. package/test/create-message.spec.js +497 -0
  27. package/test/deprecated-attr/index.spec.js +48 -0
  28. package/test/deprecated-element/index.spec.js +48 -0
  29. package/test/disallowed-element/index.spec.js +90 -0
  30. package/test/doctype/index.spec.js +50 -0
  31. package/test/end-tag/index.spec.js +162 -0
  32. package/test/id-duplication/index.spec.js +47 -0
  33. package/test/ineffective-attr/index.spec.js +31 -0
  34. package/test/invalid-attr/index.spec.js +1632 -0
  35. package/test/label-has-control/index.spec.js +29 -0
  36. package/test/landmark-roles/index.spec.js +187 -0
  37. package/test/no-boolean-attr-value/index.spec.js +41 -0
  38. package/test/no-default-value/index.spec.js +74 -0
  39. package/test/no-empty-palpable-content/index.spec.js +115 -0
  40. package/test/no-hard-code-id/index.spec.js +100 -0
  41. package/test/no-refer-to-non-existent-id/index.spec.js +254 -0
  42. package/test/no-use-event-handler-attr/index.spec.js +57 -0
  43. package/test/permitted-contents/choice.spec.js +174 -0
  44. package/test/permitted-contents/count-pattern.spec.js +308 -0
  45. package/test/permitted-contents/index.spec.js +1371 -0
  46. package/test/permitted-contents/matches-selector.spec.js +59 -0
  47. package/test/permitted-contents/order.spec.js +351 -0
  48. package/test/permitted-contents/recursive-branch.spec.js +41 -0
  49. package/test/permitted-contents/represent-transparent-nodes.spec.js +24 -0
  50. package/test/permitted-contents/start.spec.js +67 -0
  51. package/test/placeholder-label-option/index.spec.js +113 -0
  52. package/test/require-accessible-name/index.spec.js +446 -0
  53. package/test/require-datetime/index.spec.js +54 -0
  54. package/test/require-datetime/utils.spec.js +52 -0
  55. package/test/required-attr/index.spec.js +414 -0
  56. package/test/required-element/index.spec.js +188 -0
  57. package/test/required-h1/index.spec.js +69 -0
  58. package/test/use-list/index.spec.js +109 -0
  59. package/test/wai-aria/checkings/value.spec.js +33 -0
  60. package/test/wai-aria/index.spec.js +1173 -0
@@ -0,0 +1,254 @@
1
+ const { mlRuleTest } = require('markuplint');
2
+
3
+ const rule = require('../../lib/no-refer-to-non-existent-id').default;
4
+
5
+ test('label[for]', async () => {
6
+ const { violations } = await mlRuleTest(rule, '<label for="foo"></label>');
7
+ expect(violations).toStrictEqual([
8
+ {
9
+ severity: 'error',
10
+ line: 1,
11
+ col: 13,
12
+ raw: 'foo',
13
+ message: 'Missing "foo" ID',
14
+ },
15
+ ]);
16
+ });
17
+
18
+ test('td[headers]', async () => {
19
+ const { violations } = await mlRuleTest(
20
+ rule,
21
+ `<table>
22
+ <tr>
23
+ <th id="a"></th>
24
+ <th id="b"></th>
25
+ <td headers="a c"></td>
26
+ </tr>
27
+ </table>`,
28
+ {
29
+ parser: {
30
+ '.*': '@markuplint/jsx-parser',
31
+ },
32
+ },
33
+ );
34
+ expect(violations).toStrictEqual([
35
+ {
36
+ severity: 'error',
37
+ line: 5,
38
+ col: 18,
39
+ raw: 'a c',
40
+ message: 'Missing "c" ID',
41
+ },
42
+ ]);
43
+ });
44
+
45
+ test('td[headers] (Dynamic)', async () => {
46
+ const { violations } = await mlRuleTest(
47
+ rule,
48
+ `<table>
49
+ <tr>
50
+ <th id={a}></th>
51
+ <th id={b}></th>
52
+ <td headers="a c"></td>
53
+ </tr>
54
+ </table>`,
55
+ {
56
+ parser: {
57
+ '.*': '@markuplint/jsx-parser',
58
+ },
59
+ },
60
+ );
61
+ expect(violations.length).toBe(0);
62
+ });
63
+
64
+ test('td[headers] (Dynamic)', async () => {
65
+ const { violations } = await mlRuleTest(
66
+ rule,
67
+ // cspell: disable
68
+ `<table>
69
+ <tr>
70
+ <th id="a"></th>
71
+ <th id="b"></th>
72
+ <td headers={aandb}></td>
73
+ </tr>
74
+ </table>`,
75
+ // cspell: enable
76
+ {
77
+ parser: {
78
+ '.*': '@markuplint/jsx-parser',
79
+ },
80
+ },
81
+ );
82
+ expect(violations.length).toBe(0);
83
+ });
84
+
85
+ test('aria-describedby', async () => {
86
+ const { violations } = await mlRuleTest(rule, '<section aria-describedby="foo"></section>');
87
+ expect(violations).toStrictEqual([
88
+ {
89
+ severity: 'error',
90
+ line: 1,
91
+ col: 28,
92
+ raw: 'foo',
93
+ message: 'Missing "foo" ID',
94
+ },
95
+ ]);
96
+ });
97
+
98
+ test('fragment', async () => {
99
+ expect((await mlRuleTest(rule, '<a href="#foo"></a>')).violations).toStrictEqual([
100
+ {
101
+ severity: 'error',
102
+ line: 1,
103
+ col: 10,
104
+ raw: '#foo',
105
+ message: 'Missing "foo" ID',
106
+ },
107
+ ]);
108
+
109
+ expect((await mlRuleTest(rule, '<a href="#"></a>')).violations).toStrictEqual([]);
110
+
111
+ expect((await mlRuleTest(rule, '<a href="#top"></a>')).violations).toStrictEqual([]);
112
+
113
+ expect((await mlRuleTest(rule, '<a href="#TOP"></a>')).violations).toStrictEqual([]);
114
+
115
+ expect(
116
+ (await mlRuleTest(rule, `<a href="#${encodeURI('あいうえお')}"></a><div id="あいうえお"></div>`)).violations,
117
+ ).toStrictEqual([]);
118
+
119
+ expect(
120
+ (
121
+ await mlRuleTest(rule, '<><a href="#foo" /><div id="foo" /></>', {
122
+ parser: {
123
+ '.*': '@markuplint/jsx-parser',
124
+ },
125
+ })
126
+ ).violations,
127
+ ).toStrictEqual([]);
128
+
129
+ expect(
130
+ (
131
+ await mlRuleTest(rule, '<><a href="#foo" /><div id={foo} /></>', {
132
+ parser: {
133
+ '.*': '@markuplint/jsx-parser',
134
+ },
135
+ })
136
+ ).violations,
137
+ ).toStrictEqual([]);
138
+
139
+ expect(
140
+ (
141
+ await mlRuleTest(rule, '<><a href="#foo" /><div name="foo" /></>', {
142
+ parser: {
143
+ '.*': '@markuplint/jsx-parser',
144
+ },
145
+ })
146
+ ).violations,
147
+ ).toStrictEqual([
148
+ {
149
+ severity: 'error',
150
+ line: 1,
151
+ col: 12,
152
+ raw: '#foo',
153
+ message: 'Missing "foo" ID',
154
+ },
155
+ ]);
156
+
157
+ expect(
158
+ (
159
+ await mlRuleTest(rule, '<><a href="#foo" /><div name={foo} /></>', {
160
+ parser: {
161
+ '.*': '@markuplint/jsx-parser',
162
+ },
163
+ })
164
+ ).violations,
165
+ ).toStrictEqual([
166
+ {
167
+ severity: 'error',
168
+ line: 1,
169
+ col: 12,
170
+ raw: '#foo',
171
+ message: 'Missing "foo" ID',
172
+ },
173
+ ]);
174
+
175
+ expect(
176
+ (
177
+ await mlRuleTest(rule, '<><a href="#foo" /><div name="foo" /></>', {
178
+ parser: {
179
+ '.*': '@markuplint/jsx-parser',
180
+ },
181
+ rule: {
182
+ options: {
183
+ fragmentRefersNameAttr: true,
184
+ },
185
+ },
186
+ })
187
+ ).violations,
188
+ ).toStrictEqual([]);
189
+
190
+ expect(
191
+ (
192
+ await mlRuleTest(rule, '<><a href="#foo" /><div name={foo} /></>', {
193
+ parser: {
194
+ '.*': '@markuplint/jsx-parser',
195
+ },
196
+ rule: {
197
+ options: {
198
+ fragmentRefersNameAttr: true,
199
+ },
200
+ },
201
+ })
202
+ ).violations,
203
+ ).toStrictEqual([]);
204
+ });
205
+
206
+ describe('Issues', () => {
207
+ test('#748', async () => {
208
+ expect(
209
+ (
210
+ await mlRuleTest(
211
+ rule,
212
+ `
213
+ <main>
214
+ <?php foo() ?>
215
+ <a href="#foo">link</a>
216
+ <div id="foo"></div>
217
+ </main>`,
218
+ {
219
+ parser: {
220
+ '.*': '@markuplint/php-parser',
221
+ },
222
+ },
223
+ )
224
+ ).violations,
225
+ ).toStrictEqual([]);
226
+ });
227
+
228
+ test('#776', async () => {
229
+ expect(
230
+ (
231
+ await mlRuleTest(
232
+ rule,
233
+ /* cSpell:disable */
234
+ `
235
+ <a href="#apple%3Aorange">apple:orange</a>
236
+ <p id="apple:orange">apple:orange</p>
237
+
238
+ <a href="#apple%26orange">apple&amp;orange</a>
239
+ <p id="apple&amp;orange">apple&amp;orange</p>
240
+
241
+ <a href="#apple%26lemon">apple&amp;lemon</a>
242
+ <p id="apple&lemon">apple&amp;lemon</p>
243
+ `,
244
+ /* cSpell:enable */
245
+ {
246
+ parser: {
247
+ '.*': '@markuplint/php-parser',
248
+ },
249
+ },
250
+ )
251
+ ).violations,
252
+ ).toStrictEqual([]);
253
+ });
254
+ });
@@ -0,0 +1,57 @@
1
+ const { mlRuleTest } = require('markuplint');
2
+
3
+ const rule = require('../../lib/no-use-event-handler-attr').default;
4
+
5
+ it('disallows onclick', async () => {
6
+ const { violations } = await mlRuleTest(rule, '<div onclick="e => e"></div>');
7
+ expect(violations).toStrictEqual([
8
+ {
9
+ severity: 'warning',
10
+ line: 1,
11
+ col: 6,
12
+ raw: 'onclick="e => e"',
13
+ message: 'The "onclick" attribute is disallowed',
14
+ },
15
+ ]);
16
+ });
17
+
18
+ it('allows onclick because ignores it', async () => {
19
+ const { violations } = await mlRuleTest(rule, '<div onclick="e => e"></div>', {
20
+ rule: {
21
+ options: {
22
+ ignore: 'onclick',
23
+ },
24
+ },
25
+ });
26
+ expect(violations).toStrictEqual([]);
27
+ });
28
+
29
+ it('✔ onclick, ✘ onmouseleave', async () => {
30
+ const { violations } = await mlRuleTest(rule, '<div onclick="e => e" onmouseleave="e => e"></div>', {
31
+ rule: {
32
+ options: {
33
+ ignore: 'onclick',
34
+ },
35
+ },
36
+ });
37
+ expect(violations).toStrictEqual([
38
+ {
39
+ severity: 'warning',
40
+ line: 1,
41
+ col: 23,
42
+ raw: 'onmouseleave="e => e"',
43
+ message: 'The "onmouseleave" attribute is disallowed',
44
+ },
45
+ ]);
46
+ });
47
+
48
+ it('ignore by regex', async () => {
49
+ const { violations } = await mlRuleTest(rule, '<div onclick="e => e"></div>', {
50
+ rule: {
51
+ options: {
52
+ ignore: '/^onc/',
53
+ },
54
+ },
55
+ });
56
+ expect(violations).toStrictEqual([]);
57
+ });
@@ -0,0 +1,174 @@
1
+ const specs = require('@markuplint/html-spec');
2
+ const { createTestElement } = require('@markuplint/ml-core');
3
+
4
+ const { choice } = require('../../lib/permitted-contents/choice');
5
+
6
+ function c(models, innerHtml) {
7
+ const el = createTestElement(`<div>${innerHtml}</div>`);
8
+ return choice(models, Array.from(el.childNodes), specs, { ignoreHasMutableChildren: true }, 0);
9
+ }
10
+
11
+ it('ordered requires', () => {
12
+ const models = {
13
+ choice: [
14
+ //
15
+ [{ require: 'a' }],
16
+ [{ require: 'b' }],
17
+ [{ require: 'c' }],
18
+ ],
19
+ };
20
+
21
+ expect(c(models, '').type).toBe('MISSING_NODE_REQUIRED');
22
+ expect(c(models, '<a></a>').type).toBe('MATCHED');
23
+ expect(c(models, '<a></a><a></a>').type).toBe('UNEXPECTED_EXTRA_NODE');
24
+ expect(c(models, '<b></b>').type).toBe('MATCHED');
25
+ expect(c(models, '<b></b><c></c>').type).toBe('UNEXPECTED_EXTRA_NODE');
26
+ expect(c(models, '<c></c>').type).toBe('MATCHED');
27
+ expect(c(models, '<d></d>').type).toBe('MISSING_NODE_REQUIRED');
28
+ });
29
+
30
+ test('optional', () => {
31
+ const models = {
32
+ choice: [
33
+ [
34
+ {
35
+ require: 'a',
36
+ },
37
+ ],
38
+ [
39
+ {
40
+ require: 'b',
41
+ },
42
+ ],
43
+ [
44
+ {
45
+ optional: 'c',
46
+ },
47
+ ],
48
+ ],
49
+ };
50
+
51
+ expect(c(models, '').type).toBe('MATCHED_ZERO');
52
+ expect(c(models, '<a></a>').type).toBe('MATCHED');
53
+ expect(c(models, '<b></b>').type).toBe('MATCHED');
54
+ expect(c(models, '<c></c>').type).toBe('MATCHED');
55
+ expect(c(models, '<d></d>').type).toBe('UNEXPECTED_EXTRA_NODE');
56
+ });
57
+
58
+ test('interleave', () => {
59
+ const models = {
60
+ choice: [
61
+ [
62
+ {
63
+ require: 'a',
64
+ },
65
+ {
66
+ require: 'b',
67
+ },
68
+ ],
69
+ [
70
+ {
71
+ require: 'b',
72
+ },
73
+ {
74
+ require: 'a',
75
+ },
76
+ ],
77
+ ],
78
+ };
79
+
80
+ expect(c(models, '<a></a>').type).toBe('MISSING_NODE_REQUIRED');
81
+ expect(c(models, '<b></b>').type).toBe('MISSING_NODE_REQUIRED');
82
+ expect(c(models, '<a></a><b></b>').type).toBe('MATCHED');
83
+ expect(c(models, '<b></b><a></a>').type).toBe('MATCHED');
84
+ expect(c(models, '<b></b><a></a><c></c>').type).toBe('UNEXPECTED_EXTRA_NODE');
85
+ expect(c(models, '<b></b><a></a><a></a>').type).toBe('UNEXPECTED_EXTRA_NODE');
86
+ });
87
+
88
+ test('the dl element', () => {
89
+ const models = {
90
+ choice: [
91
+ [
92
+ {
93
+ oneOrMore: [
94
+ {
95
+ zeroOrMore: ':model(script-supporting)',
96
+ },
97
+ {
98
+ oneOrMore: 'dt',
99
+ },
100
+ {
101
+ zeroOrMore: ':model(script-supporting)',
102
+ },
103
+ {
104
+ oneOrMore: 'dd',
105
+ },
106
+ {
107
+ zeroOrMore: ':model(script-supporting)',
108
+ },
109
+ ],
110
+ },
111
+ ],
112
+ [
113
+ {
114
+ zeroOrMore: ':model(script-supporting)',
115
+ },
116
+ {
117
+ oneOrMore: 'div',
118
+ },
119
+ {
120
+ zeroOrMore: ':model(script-supporting)',
121
+ },
122
+ ],
123
+ ],
124
+ };
125
+
126
+ expect(c(models, '<dt></dt><dd></dd>').type).toBe('MATCHED');
127
+ expect(c(models, '').type).toBe('MISSING_NODE_ONE_OR_MORE');
128
+ expect(c(models, '').query).toBe('dt');
129
+ expect(c(models, '<dt></dt>').type).toBe('MISSING_NODE_ONE_OR_MORE');
130
+ expect(c(models, '<div></div>').type).toBe('MATCHED');
131
+ expect(c(models, '<div></div><div></div>').type).toBe('MATCHED');
132
+ expect(c(models, '<dt></dt><dd></dd><dt></dt>').type).toBe('MISSING_NODE_ONE_OR_MORE');
133
+ });
134
+
135
+ it('part of the ruby element', () => {
136
+ const models = {
137
+ // 2. One or the other of the following:
138
+ choice: [
139
+ [
140
+ {
141
+ // - One or more rt elements
142
+ oneOrMore: 'rt',
143
+ },
144
+ ],
145
+ [
146
+ {
147
+ // - An rp element
148
+ require: 'rp',
149
+ },
150
+ {
151
+ // followed by one or more rt elements, each of which is itself followed by an rp element
152
+ oneOrMore: [
153
+ {
154
+ require: 'rt',
155
+ },
156
+ {
157
+ require: 'rp',
158
+ },
159
+ ],
160
+ },
161
+ ],
162
+ ],
163
+ };
164
+
165
+ expect(c(models, '<rt></rt>').type).toBe('MATCHED');
166
+ expect(c(models, '<rp></rp>').type).toBe('MISSING_NODE_REQUIRED');
167
+ expect(c(models, '<rp></rp>').query).toBe('rt');
168
+ expect(c(models, '<rt></rt><rt></rt>').type).toBe('MATCHED');
169
+ expect(c(models, '<rp></rp><rt></rt><rp></rp>').type).toBe('MATCHED');
170
+ expect(c(models, '<rp></rp><rt></rt>').type).toBe('MISSING_NODE_REQUIRED');
171
+ expect(c(models, '<rp></rp><rt></rt>').query).toBe('rp');
172
+ expect(c(models, '<rt></rt><rp></rp>').type).toBe('UNEXPECTED_EXTRA_NODE');
173
+ expect(c(models, '<rt></rt><rp></rp>').query).toBe('rt');
174
+ });