@herb-tools/linter 0.4.0 → 0.4.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/README.md +1 -1
- package/dist/herb-lint.js +161 -101
- package/dist/herb-lint.js.map +1 -1
- package/dist/index.cjs +215 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +213 -68
- package/dist/index.js.map +1 -1
- package/dist/package.json +4 -4
- package/dist/src/default-rules.js +8 -2
- package/dist/src/default-rules.js.map +1 -1
- package/dist/src/rules/html-aria-attribute-must-be-valid.js +24 -0
- package/dist/src/rules/html-aria-attribute-must-be-valid.js.map +1 -0
- package/dist/src/rules/html-aria-role-must-be-valid.js +21 -0
- package/dist/src/rules/html-aria-role-must-be-valid.js.map +1 -0
- package/dist/src/rules/html-no-duplicate-ids.js +25 -0
- package/dist/src/rules/html-no-duplicate-ids.js.map +1 -0
- package/dist/src/rules/index.js +3 -0
- package/dist/src/rules/index.js.map +1 -1
- package/dist/src/rules/rule-utils.js +83 -8
- package/dist/src/rules/rule-utils.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/rules/html-aria-attribute-must-be-valid.d.ts +6 -0
- package/dist/types/rules/html-aria-role-must-be-valid.d.ts +6 -0
- package/dist/types/rules/html-no-duplicate-ids.d.ts +6 -0
- package/dist/types/rules/index.d.ts +3 -0
- package/dist/types/rules/rule-utils.d.ts +2 -0
- package/dist/types/src/rules/html-aria-attribute-must-be-valid.d.ts +6 -0
- package/dist/types/src/rules/html-aria-role-must-be-valid.d.ts +6 -0
- package/dist/types/src/rules/html-no-duplicate-ids.d.ts +6 -0
- package/dist/types/src/rules/index.d.ts +3 -0
- package/dist/types/src/rules/rule-utils.d.ts +2 -0
- package/docs/rules/README.md +4 -0
- package/docs/rules/html-aria-attribute-must-be-valid.md +45 -0
- package/docs/rules/html-aria-role-must-be-valid.md +45 -0
- package/docs/rules/html-no-duplicate-ids.md +49 -0
- package/package.json +4 -4
- package/src/default-rules.ts +8 -2
- package/src/rules/html-aria-attribute-must-be-valid.ts +42 -0
- package/src/rules/html-aria-role-must-be-valid.ts +30 -0
- package/src/rules/html-no-duplicate-ids.ts +39 -0
- package/src/rules/index.ts +3 -0
- package/src/rules/rule-utils.ts +98 -17
|
@@ -57,16 +57,29 @@ export function getAttributeName(attributeNode) {
|
|
|
57
57
|
* Gets the attribute value content from an HTMLAttributeValueNode
|
|
58
58
|
*/
|
|
59
59
|
export function getAttributeValue(attributeNode) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
const valueNode = attributeNode.value;
|
|
61
|
+
if (valueNode === null)
|
|
62
|
+
return null;
|
|
63
|
+
if (valueNode.type !== "AST_HTML_ATTRIBUTE_VALUE_NODE" || !valueNode.children?.length) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
let result = "";
|
|
67
|
+
for (const child of valueNode.children) {
|
|
68
|
+
switch (child.type) {
|
|
69
|
+
case "AST_ERB_CONTENT_NODE": {
|
|
70
|
+
const erbNode = child;
|
|
71
|
+
if (erbNode.content) {
|
|
72
|
+
result += `${erbNode.tag_opening?.value}${erbNode.content.value}${erbNode.tag_closing?.value}`;
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case "AST_LITERAL_NODE": {
|
|
77
|
+
result += child.content;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
67
80
|
}
|
|
68
81
|
}
|
|
69
|
-
return
|
|
82
|
+
return result;
|
|
70
83
|
}
|
|
71
84
|
/**
|
|
72
85
|
* Checks if an attribute has a value
|
|
@@ -132,6 +145,68 @@ export const HTML_BOOLEAN_ATTRIBUTES = new Set([
|
|
|
132
145
|
"noresize", "noshade", "nowrap", "sortable", "truespeed", "typemustmatch"
|
|
133
146
|
]);
|
|
134
147
|
export const HEADING_TAGS = new Set(["h1", "h2", "h3", "h4", "h5", "h6"]);
|
|
148
|
+
export const VALID_ARIA_ROLES = new Set([
|
|
149
|
+
"banner", "complementary", "contentinfo", "form", "main", "navigation", "region", "search",
|
|
150
|
+
"article", "cell", "columnheader", "definition", "directory", "document", "feed", "figure",
|
|
151
|
+
"group", "heading", "img", "list", "listitem", "math", "none", "note", "presentation",
|
|
152
|
+
"row", "rowgroup", "rowheader", "separator", "table", "term", "tooltip",
|
|
153
|
+
"alert", "alertdialog", "button", "checkbox", "combobox", "dialog", "grid", "gridcell", "link",
|
|
154
|
+
"listbox", "menu", "menubar", "menuitem", "menuitemcheckbox", "menuitemradio", "option",
|
|
155
|
+
"progressbar", "radio", "radiogroup", "scrollbar", "searchbox", "slider", "spinbutton",
|
|
156
|
+
"status", "switch", "tab", "tablist", "tabpanel", "textbox", "timer", "toolbar", "tree",
|
|
157
|
+
"treegrid", "treeitem",
|
|
158
|
+
"log", "marquee"
|
|
159
|
+
]);
|
|
160
|
+
export const ARIA_ATTRIBUTES = new Set([
|
|
161
|
+
'aria-activedescendant',
|
|
162
|
+
'aria-atomic',
|
|
163
|
+
'aria-autocomplete',
|
|
164
|
+
'aria-busy',
|
|
165
|
+
'aria-checked',
|
|
166
|
+
'aria-colcount',
|
|
167
|
+
'aria-colindex',
|
|
168
|
+
'aria-colspan',
|
|
169
|
+
'aria-controls',
|
|
170
|
+
'aria-current',
|
|
171
|
+
'aria-describedby',
|
|
172
|
+
'aria-details',
|
|
173
|
+
'aria-disabled',
|
|
174
|
+
'aria-dropeffect',
|
|
175
|
+
'aria-errormessage',
|
|
176
|
+
'aria-expanded',
|
|
177
|
+
'aria-flowto',
|
|
178
|
+
'aria-grabbed',
|
|
179
|
+
'aria-haspopup',
|
|
180
|
+
'aria-hidden',
|
|
181
|
+
'aria-invalid',
|
|
182
|
+
'aria-keyshortcuts',
|
|
183
|
+
'aria-label',
|
|
184
|
+
'aria-labelledby',
|
|
185
|
+
'aria-level',
|
|
186
|
+
'aria-live',
|
|
187
|
+
'aria-modal',
|
|
188
|
+
'aria-multiline',
|
|
189
|
+
'aria-multiselectable',
|
|
190
|
+
'aria-orientation',
|
|
191
|
+
'aria-owns',
|
|
192
|
+
'aria-placeholder',
|
|
193
|
+
'aria-posinset',
|
|
194
|
+
'aria-pressed',
|
|
195
|
+
'aria-readonly',
|
|
196
|
+
'aria-relevant',
|
|
197
|
+
'aria-required',
|
|
198
|
+
'aria-roledescription',
|
|
199
|
+
'aria-rowcount',
|
|
200
|
+
'aria-rowindex',
|
|
201
|
+
'aria-rowspan',
|
|
202
|
+
'aria-selected',
|
|
203
|
+
'aria-setsize',
|
|
204
|
+
'aria-sort',
|
|
205
|
+
'aria-valuemax',
|
|
206
|
+
'aria-valuemin',
|
|
207
|
+
'aria-valuenow',
|
|
208
|
+
'aria-valuetext',
|
|
209
|
+
]);
|
|
135
210
|
/**
|
|
136
211
|
* Checks if an element is inline
|
|
137
212
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-utils.js","sourceRoot":"","sources":["../../../src/rules/rule-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACR,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"rule-utils.js","sourceRoot":"","sources":["../../../src/rules/rule-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/cli.ts","../src/default-rules.ts","../src/herb-lint.ts","../src/index.ts","../src/linter.ts","../src/types.ts","../src/cli/argument-parser.ts","../src/cli/file-processor.ts","../src/cli/index.ts","../src/cli/summary-reporter.ts","../src/cli/formatters/base-formatter.ts","../src/cli/formatters/detailed-formatter.ts","../src/cli/formatters/index.ts","../src/cli/formatters/simple-formatter.ts","../src/rules/erb-no-empty-tags.ts","../src/rules/erb-no-output-control-flow.ts","../src/rules/erb-require-whitespace-inside-tags.ts","../src/rules/html-anchor-require-href.ts","../src/rules/html-aria-role-heading-requires-level.ts","../src/rules/html-attribute-double-quotes.ts","../src/rules/html-attribute-values-require-quotes.ts","../src/rules/html-boolean-attributes-no-value.ts","../src/rules/html-img-require-alt.ts","../src/rules/html-no-block-inside-inline.ts","../src/rules/html-no-duplicate-attributes.ts","../src/rules/html-no-empty-headings.ts","../src/rules/html-no-nested-links.ts","../src/rules/html-tag-name-lowercase.ts","../src/rules/index.ts","../src/rules/rule-utils.ts"],"version":"5.8.3"}
|
|
1
|
+
{"root":["../src/cli.ts","../src/default-rules.ts","../src/herb-lint.ts","../src/index.ts","../src/linter.ts","../src/types.ts","../src/cli/argument-parser.ts","../src/cli/file-processor.ts","../src/cli/index.ts","../src/cli/summary-reporter.ts","../src/cli/formatters/base-formatter.ts","../src/cli/formatters/detailed-formatter.ts","../src/cli/formatters/index.ts","../src/cli/formatters/simple-formatter.ts","../src/rules/erb-no-empty-tags.ts","../src/rules/erb-no-output-control-flow.ts","../src/rules/erb-require-whitespace-inside-tags.ts","../src/rules/html-anchor-require-href.ts","../src/rules/html-aria-attribute-must-be-valid.ts","../src/rules/html-aria-role-heading-requires-level.ts","../src/rules/html-aria-role-must-be-valid.ts","../src/rules/html-attribute-double-quotes.ts","../src/rules/html-attribute-values-require-quotes.ts","../src/rules/html-boolean-attributes-no-value.ts","../src/rules/html-img-require-alt.ts","../src/rules/html-no-block-inside-inline.ts","../src/rules/html-no-duplicate-attributes.ts","../src/rules/html-no-duplicate-ids.ts","../src/rules/html-no-empty-headings.ts","../src/rules/html-no-nested-links.ts","../src/rules/html-tag-name-lowercase.ts","../src/rules/index.ts","../src/rules/rule-utils.ts"],"version":"5.8.3"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
export * from "./erb-no-empty-tags.js";
|
|
2
2
|
export * from "./erb-no-output-control-flow.js";
|
|
3
3
|
export * from "./html-anchor-require-href.js";
|
|
4
|
+
export * from "./html-aria-role-heading-requires-level.js";
|
|
5
|
+
export * from "./html-aria-role-must-be-valid.js";
|
|
4
6
|
export * from "./html-attribute-double-quotes.js";
|
|
5
7
|
export * from "./html-attribute-values-require-quotes.js";
|
|
6
8
|
export * from "./html-boolean-attributes-no-value.js";
|
|
7
9
|
export * from "./html-img-require-alt.js";
|
|
8
10
|
export * from "./html-no-block-inside-inline.js";
|
|
9
11
|
export * from "./html-no-duplicate-attributes.js";
|
|
12
|
+
export * from "./html-no-duplicate-ids.js";
|
|
10
13
|
export * from "./html-no-empty-headings.js";
|
|
11
14
|
export * from "./html-no-nested-links.js";
|
|
12
15
|
export * from "./html-tag-name-lowercase.js";
|
|
@@ -56,6 +56,8 @@ export declare const HTML_INLINE_ELEMENTS: Set<string>;
|
|
|
56
56
|
export declare const HTML_BLOCK_ELEMENTS: Set<string>;
|
|
57
57
|
export declare const HTML_BOOLEAN_ATTRIBUTES: Set<string>;
|
|
58
58
|
export declare const HEADING_TAGS: Set<string>;
|
|
59
|
+
export declare const VALID_ARIA_ROLES: Set<string>;
|
|
60
|
+
export declare const ARIA_ATTRIBUTES: Set<string>;
|
|
59
61
|
/**
|
|
60
62
|
* Checks if an element is inline
|
|
61
63
|
*/
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
export * from "./erb-no-empty-tags.js";
|
|
2
2
|
export * from "./erb-no-output-control-flow.js";
|
|
3
3
|
export * from "./html-anchor-require-href.js";
|
|
4
|
+
export * from "./html-aria-role-heading-requires-level.js";
|
|
5
|
+
export * from "./html-aria-role-must-be-valid.js";
|
|
4
6
|
export * from "./html-attribute-double-quotes.js";
|
|
5
7
|
export * from "./html-attribute-values-require-quotes.js";
|
|
6
8
|
export * from "./html-boolean-attributes-no-value.js";
|
|
7
9
|
export * from "./html-img-require-alt.js";
|
|
8
10
|
export * from "./html-no-block-inside-inline.js";
|
|
9
11
|
export * from "./html-no-duplicate-attributes.js";
|
|
12
|
+
export * from "./html-no-duplicate-ids.js";
|
|
10
13
|
export * from "./html-no-empty-headings.js";
|
|
11
14
|
export * from "./html-no-nested-links.js";
|
|
12
15
|
export * from "./html-tag-name-lowercase.js";
|
|
@@ -56,6 +56,8 @@ export declare const HTML_INLINE_ELEMENTS: Set<string>;
|
|
|
56
56
|
export declare const HTML_BLOCK_ELEMENTS: Set<string>;
|
|
57
57
|
export declare const HTML_BOOLEAN_ATTRIBUTES: Set<string>;
|
|
58
58
|
export declare const HEADING_TAGS: Set<string>;
|
|
59
|
+
export declare const VALID_ARIA_ROLES: Set<string>;
|
|
60
|
+
export declare const ARIA_ATTRIBUTES: Set<string>;
|
|
59
61
|
/**
|
|
60
62
|
* Checks if an element is inline
|
|
61
63
|
*/
|
package/docs/rules/README.md
CHANGED
|
@@ -7,12 +7,16 @@ This page contains documentation for all Herb Linter rules.
|
|
|
7
7
|
- [`erb-no-empty-tags`](./erb-no-empty-tags.md) - Disallow empty ERB tags
|
|
8
8
|
- [`erb-no-output-control-flow`](./erb-no-output-control-flow.md) - Prevents outputting control flow blocks
|
|
9
9
|
- [`erb-require-whitespace-inside-tags`](./erb-require-whitespace-inside-tags.md) - Requires whitespace around erb tags
|
|
10
|
+
- [`html-anchor-require-href`](./html-anchor-require-href.md) - Requires an href attribute on anchor tags
|
|
11
|
+
- [`html-aria-attribute-must-be-valid`](./html-aria-attribute-must-be-valid.md) - Disallow invalid or unknown `aria-*` attributes.
|
|
10
12
|
- [`html-aria-role-heading-requires-level`](./html-aria-role-heading-requires-level.md) - Requires `aria-level` when supplying a `role`
|
|
13
|
+
- [`html-aria-role-must-be-valid`](./html-aria-role-must-be-valid.md) - The `role` attribute must have a valid WAI-ARIA Role.
|
|
11
14
|
- [`html-attribute-double-quotes`](./html-attribute-double-quotes.md) - Enforces double quotes for attribute values
|
|
12
15
|
- [`html-attribute-values-require-quotes`](./html-attribute-values-require-quotes.md) - Requires quotes around attribute values
|
|
13
16
|
- [`html-boolean-attributes-no-value`](./html-boolean-attributes-no-value.md) - Prevents values on boolean attributes
|
|
14
17
|
- [`html-img-require-alt`](./html-img-require-alt.md) - Requires alt attributes on img tags
|
|
15
18
|
- [`html-no-block-inside-inline`](./html-no-block-inside-inline.md) - Prevents block-level elements inside inline elements
|
|
19
|
+
- [`html-no-duplicate-ids`](./html-no-duplicate-ids.md) - Prevents duplicate IDs within a document
|
|
16
20
|
- [`html-no-duplicate-attributes`](./html-no-duplicate-attributes.md) - Prevents duplicate attributes on HTML elements
|
|
17
21
|
- [`html-no-nested-links`](./html-no-nested-links.md) - Prevents nested anchor tags
|
|
18
22
|
- [`html-tag-name-lowercase`](./html-tag-name-lowercase.md) - Enforces lowercase tag names in HTML
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Linter Rule: Disallow invalid or unknown `aria-*` attributes.
|
|
2
|
+
|
|
3
|
+
**Rule:** `html-aria-attribute-must-be-valid`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Disallow unknown or invalid `aria-*` attributes. Only attributes defined in the WAI-ARIA specification should be used. This rule helps catch typos (e.g. `aria-lable`), misuse, or outdated attribute names that won't be interpreted by assistive technologies.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
ARIA attributes are powerful accessibility tools, but **only if used correctly**. Mistyped or unsupported attributes:
|
|
12
|
+
|
|
13
|
+
- Are silently ignored by browsers and screen readers
|
|
14
|
+
- Fail to communicate intent
|
|
15
|
+
- Give a false sense of accessibility
|
|
16
|
+
|
|
17
|
+
Validating against a known list ensures you're using correct and effective ARIA patterns.
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
### ✅ Good
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<div role="button" aria-pressed="false">Toggle</div>
|
|
25
|
+
<input type="text" aria-label="Search" />
|
|
26
|
+
<span role="heading" aria-level="2">Title</span>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 🚫 Bad
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<!-- typo -->
|
|
33
|
+
<div role="button" aria-presed="false">Toggle</div>
|
|
34
|
+
|
|
35
|
+
<!-- typo -->
|
|
36
|
+
<input type="text" aria-lable="Search" />
|
|
37
|
+
|
|
38
|
+
<!-- invalid -->
|
|
39
|
+
<span aria-size="large" role="heading" aria-level="2">Title</span>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## References
|
|
43
|
+
|
|
44
|
+
- [ARIA states and properties (attributes)](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes)
|
|
45
|
+
- [NPM Package: `aria-attributes`](https://github.com/wooorm/aria-attributes)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Linter Rule: Disallow invalid values for the `role` attribute
|
|
2
|
+
|
|
3
|
+
**Rule:** `html-aria-role-must-be-valid`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Disallow invalid or unknown values for the `role` attribute. The `role` attribute must match one of the recognized ARIA role values as defined by the [WAI-ARIA specification](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles).
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
ARIA `role` attributes are used to define the purpose of an element to assistive technologies. Using invalid, misspelled, or non-standard roles results in:
|
|
12
|
+
|
|
13
|
+
* Screen readers ignoring the role
|
|
14
|
+
* Broken accessibility semantics
|
|
15
|
+
* False sense of correctness
|
|
16
|
+
|
|
17
|
+
Validating against the official list of ARIA roles prevents silent accessibility failures.
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
### ✅ Good
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<div role="button">Click me</div>
|
|
25
|
+
<nav role="navigation">...</nav>
|
|
26
|
+
<section role="region">...</section>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 🚫 Bad
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<!-- typo -->
|
|
33
|
+
<div role="buton">Click me</div>
|
|
34
|
+
|
|
35
|
+
<!-- not a valid role -->
|
|
36
|
+
<nav role="nav">...</nav>
|
|
37
|
+
|
|
38
|
+
<!-- not in the ARIA spec -->
|
|
39
|
+
<section role="header">...</section>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## References
|
|
43
|
+
|
|
44
|
+
* [ARIA 1.2 Specification - Roles](https://www.w3.org/TR/wai-aria/#roles)
|
|
45
|
+
* [MDN: ARIA roles](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Linter Rule: Disallow duplicate IDs in the same document
|
|
2
|
+
|
|
3
|
+
**Rule:** `html-no-duplicate-ids`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Ensure that `id` attribute is unique within a document.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
Duplicate IDs in an HTML document can lead to unexpected behavior, especially when using JavaScript or CSS that relies on unique identifiers. Browsers may not handle duplicate IDs consistently, which can cause issues with element selection, styling, and event handling.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
### ✅ Good
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<div id="header">Header</div>
|
|
19
|
+
<div id="main-content">Main Content</div>
|
|
20
|
+
<div id="footer">Footer</div>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```erb
|
|
24
|
+
<div id="<%= dom_id("header") %>">Header</div>
|
|
25
|
+
<div id="<%= dom_id("main_content") %>">Main Content</div>
|
|
26
|
+
<div id="<%= dom_id("footer") %>">Footer</div>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 🚫 Bad
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<div id="header">Header</div>
|
|
33
|
+
|
|
34
|
+
<div id="header">Duplicate Header</div>
|
|
35
|
+
|
|
36
|
+
<div id="footer">Footer</div>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```erb
|
|
40
|
+
<div id="<%= dom_id("header") %>">Header</div>
|
|
41
|
+
|
|
42
|
+
<div id="<%= dom_id("header") %>">Duplicate Header</div>
|
|
43
|
+
|
|
44
|
+
<div id="<%= dom_id("footer") %>">Footer</div>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## References
|
|
48
|
+
* [W3 org - The id attribute](https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#the-id-attribute)
|
|
49
|
+
* [Rails `ActionView::RecordIdentifier#dom_id`](https://api.rubyonrails.org/classes/ActionView/RecordIdentifier.html#method-i-dom_id)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herb-tools/linter",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "HTML+ERB linter for validating HTML structure and enforcing best practices",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://herb-tools.dev",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@herb-tools/core": "0.4.
|
|
37
|
-
"@herb-tools/highlighter": "0.4.
|
|
38
|
-
"@herb-tools/node-wasm": "0.4.
|
|
36
|
+
"@herb-tools/core": "0.4.1",
|
|
37
|
+
"@herb-tools/highlighter": "0.4.1",
|
|
38
|
+
"@herb-tools/node-wasm": "0.4.1",
|
|
39
39
|
"glob": "^11.0.3"
|
|
40
40
|
},
|
|
41
41
|
"files": [
|
package/src/default-rules.ts
CHANGED
|
@@ -4,13 +4,16 @@ import { ERBNoEmptyTagsRule } from "./rules/erb-no-empty-tags.js"
|
|
|
4
4
|
import { ERBNoOutputControlFlowRule } from "./rules/erb-no-output-control-flow.js"
|
|
5
5
|
import { ERBRequireWhitespaceRule } from "./rules/erb-require-whitespace-inside-tags.js"
|
|
6
6
|
import { HTMLAnchorRequireHrefRule } from "./rules/html-anchor-require-href.js"
|
|
7
|
+
import { HTMLAriaAttributeMustBeValid } from "./rules/html-aria-attribute-must-be-valid.js"
|
|
7
8
|
import { HTMLAriaRoleHeadingRequiresLevelRule } from "./rules/html-aria-role-heading-requires-level.js"
|
|
9
|
+
import { HTMLAriaRoleMustBeValidRule } from "./rules/html-aria-role-must-be-valid.js"
|
|
8
10
|
import { HTMLAttributeDoubleQuotesRule } from "./rules/html-attribute-double-quotes.js"
|
|
9
11
|
import { HTMLAttributeValuesRequireQuotesRule } from "./rules/html-attribute-values-require-quotes.js"
|
|
10
12
|
import { HTMLBooleanAttributesNoValueRule } from "./rules/html-boolean-attributes-no-value.js"
|
|
11
13
|
import { HTMLImgRequireAltRule } from "./rules/html-img-require-alt.js"
|
|
12
|
-
import { HTMLNoBlockInsideInlineRule } from "./rules/html-no-block-inside-inline.js"
|
|
14
|
+
// import { HTMLNoBlockInsideInlineRule } from "./rules/html-no-block-inside-inline.js"
|
|
13
15
|
import { HTMLNoDuplicateAttributesRule } from "./rules/html-no-duplicate-attributes.js"
|
|
16
|
+
import { HTMLNoDuplicateIdsRule } from "./rules/html-no-duplicate-ids.js"
|
|
14
17
|
import { HTMLNoEmptyHeadingsRule } from "./rules/html-no-empty-headings.js"
|
|
15
18
|
import { HTMLNoNestedLinksRule } from "./rules/html-no-nested-links.js"
|
|
16
19
|
import { HTMLTagNameLowercaseRule } from "./rules/html-tag-name-lowercase.js"
|
|
@@ -20,13 +23,16 @@ export const defaultRules: RuleClass[] = [
|
|
|
20
23
|
ERBNoOutputControlFlowRule,
|
|
21
24
|
ERBRequireWhitespaceRule,
|
|
22
25
|
HTMLAnchorRequireHrefRule,
|
|
26
|
+
HTMLAriaAttributeMustBeValid,
|
|
23
27
|
HTMLAriaRoleHeadingRequiresLevelRule,
|
|
28
|
+
HTMLAriaRoleMustBeValidRule,
|
|
24
29
|
HTMLAttributeDoubleQuotesRule,
|
|
25
30
|
HTMLAttributeValuesRequireQuotesRule,
|
|
26
31
|
HTMLBooleanAttributesNoValueRule,
|
|
27
32
|
HTMLImgRequireAltRule,
|
|
28
|
-
HTMLNoBlockInsideInlineRule,
|
|
33
|
+
// HTMLNoBlockInsideInlineRule,
|
|
29
34
|
HTMLNoDuplicateAttributesRule,
|
|
35
|
+
HTMLNoDuplicateIdsRule,
|
|
30
36
|
HTMLNoEmptyHeadingsRule,
|
|
31
37
|
HTMLNoNestedLinksRule,
|
|
32
38
|
HTMLTagNameLowercaseRule,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ARIA_ATTRIBUTES,
|
|
3
|
+
AttributeVisitorMixin,
|
|
4
|
+
} from "./rule-utils.js";
|
|
5
|
+
|
|
6
|
+
import type { LintOffense, Rule } from "../types.js";
|
|
7
|
+
import type {
|
|
8
|
+
HTMLAttributeNode,
|
|
9
|
+
HTMLOpenTagNode,
|
|
10
|
+
HTMLSelfCloseTagNode,
|
|
11
|
+
Node,
|
|
12
|
+
} from "@herb-tools/core";
|
|
13
|
+
|
|
14
|
+
class AriaAttributeMustBeValid extends AttributeVisitorMixin {
|
|
15
|
+
checkAttribute(
|
|
16
|
+
attributeName: string,
|
|
17
|
+
_attributeValue: string | null,
|
|
18
|
+
attributeNode: HTMLAttributeNode,
|
|
19
|
+
_parentNode: HTMLOpenTagNode | HTMLSelfCloseTagNode,
|
|
20
|
+
): void {
|
|
21
|
+
if (!attributeName.startsWith("aria-")) return;
|
|
22
|
+
|
|
23
|
+
if (!ARIA_ATTRIBUTES.has(attributeName)){
|
|
24
|
+
this.offenses.push({
|
|
25
|
+
message: `The attribute \`${attributeName}\` is not a valid ARIA attribute. ARIA attributes must match the WAI-ARIA specification.`,
|
|
26
|
+
severity: "error",
|
|
27
|
+
location: attributeNode.location,
|
|
28
|
+
rule: this.ruleName,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class HTMLAriaAttributeMustBeValid implements Rule {
|
|
35
|
+
name = "html-aria-attribute-must-be-valid";
|
|
36
|
+
|
|
37
|
+
check(node: Node): LintOffense[] {
|
|
38
|
+
const visitor = new AriaAttributeMustBeValid(this.name);
|
|
39
|
+
visitor.visit(node);
|
|
40
|
+
return visitor.offenses;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AttributeVisitorMixin, VALID_ARIA_ROLES } from "./rule-utils.js"
|
|
2
|
+
|
|
3
|
+
import type { Rule, LintOffense } from "../types.js"
|
|
4
|
+
import type { Node, HTMLAttributeNode } from "@herb-tools/core"
|
|
5
|
+
|
|
6
|
+
class AriaRoleMustBeValid extends AttributeVisitorMixin {
|
|
7
|
+
checkAttribute(attributeName: string, attributeValue: string | null, attributeNode: HTMLAttributeNode,): void {
|
|
8
|
+
if (attributeName !== "role") return
|
|
9
|
+
if (attributeValue === null) return
|
|
10
|
+
if (VALID_ARIA_ROLES.has(attributeValue)) return
|
|
11
|
+
|
|
12
|
+
this.addOffense(
|
|
13
|
+
`The \`role\` attribute must be a valid ARIA role. Role \`${attributeValue}\` is not recognized.`,
|
|
14
|
+
attributeNode.location,
|
|
15
|
+
"error"
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class HTMLAriaRoleMustBeValidRule implements Rule {
|
|
21
|
+
name = "html-aria-role-must-be-valid"
|
|
22
|
+
|
|
23
|
+
check(node: Node): LintOffense[] {
|
|
24
|
+
const visitor = new AriaRoleMustBeValid(this.name)
|
|
25
|
+
|
|
26
|
+
visitor.visit(node)
|
|
27
|
+
|
|
28
|
+
return visitor.offenses
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { AttributeVisitorMixin } from "./rule-utils"
|
|
2
|
+
|
|
3
|
+
import type { Node } from "@herb-tools/core"
|
|
4
|
+
import type { LintOffense, Rule } from "../types"
|
|
5
|
+
|
|
6
|
+
class NoDuplicateIdsVisitor extends AttributeVisitorMixin {
|
|
7
|
+
private documentIds: Set<string> = new Set<string>()
|
|
8
|
+
|
|
9
|
+
protected checkAttribute(attributeName: string, attributeValue: string | null, attributeNode: Node): void {
|
|
10
|
+
if (attributeName.toLowerCase() !== "id") return
|
|
11
|
+
if (!attributeValue) return
|
|
12
|
+
|
|
13
|
+
const id = attributeValue.trim()
|
|
14
|
+
|
|
15
|
+
if (this.documentIds.has(id)) {
|
|
16
|
+
this.addOffense(
|
|
17
|
+
`Duplicate ID \`${id}\` found. IDs must be unique within a document.`,
|
|
18
|
+
attributeNode.location,
|
|
19
|
+
"error"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.documentIds.add(id)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class HTMLNoDuplicateIdsRule implements Rule {
|
|
30
|
+
name = "html-no-duplicate-ids"
|
|
31
|
+
|
|
32
|
+
check(node: Node): LintOffense[] {
|
|
33
|
+
const visitor = new NoDuplicateIdsVisitor(this.name)
|
|
34
|
+
|
|
35
|
+
visitor.visit(node)
|
|
36
|
+
|
|
37
|
+
return visitor.offenses
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/rules/index.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
export * from "./erb-no-empty-tags.js"
|
|
2
2
|
export * from "./erb-no-output-control-flow.js"
|
|
3
3
|
export * from "./html-anchor-require-href.js"
|
|
4
|
+
export * from "./html-aria-role-heading-requires-level.js"
|
|
5
|
+
export * from "./html-aria-role-must-be-valid.js"
|
|
4
6
|
export * from "./html-attribute-double-quotes.js"
|
|
5
7
|
export * from "./html-attribute-values-require-quotes.js"
|
|
6
8
|
export * from "./html-boolean-attributes-no-value.js"
|
|
7
9
|
export * from "./html-img-require-alt.js"
|
|
8
10
|
export * from "./html-no-block-inside-inline.js"
|
|
9
11
|
export * from "./html-no-duplicate-attributes.js"
|
|
12
|
+
export * from "./html-no-duplicate-ids.js"
|
|
10
13
|
export * from "./html-no-empty-headings.js"
|
|
11
14
|
export * from "./html-no-nested-links.js"
|
|
12
15
|
export * from "./html-tag-name-lowercase.js"
|