@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.
Files changed (42) hide show
  1. package/README.md +1 -1
  2. package/dist/herb-lint.js +161 -101
  3. package/dist/herb-lint.js.map +1 -1
  4. package/dist/index.cjs +215 -67
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.js +213 -68
  7. package/dist/index.js.map +1 -1
  8. package/dist/package.json +4 -4
  9. package/dist/src/default-rules.js +8 -2
  10. package/dist/src/default-rules.js.map +1 -1
  11. package/dist/src/rules/html-aria-attribute-must-be-valid.js +24 -0
  12. package/dist/src/rules/html-aria-attribute-must-be-valid.js.map +1 -0
  13. package/dist/src/rules/html-aria-role-must-be-valid.js +21 -0
  14. package/dist/src/rules/html-aria-role-must-be-valid.js.map +1 -0
  15. package/dist/src/rules/html-no-duplicate-ids.js +25 -0
  16. package/dist/src/rules/html-no-duplicate-ids.js.map +1 -0
  17. package/dist/src/rules/index.js +3 -0
  18. package/dist/src/rules/index.js.map +1 -1
  19. package/dist/src/rules/rule-utils.js +83 -8
  20. package/dist/src/rules/rule-utils.js.map +1 -1
  21. package/dist/tsconfig.tsbuildinfo +1 -1
  22. package/dist/types/rules/html-aria-attribute-must-be-valid.d.ts +6 -0
  23. package/dist/types/rules/html-aria-role-must-be-valid.d.ts +6 -0
  24. package/dist/types/rules/html-no-duplicate-ids.d.ts +6 -0
  25. package/dist/types/rules/index.d.ts +3 -0
  26. package/dist/types/rules/rule-utils.d.ts +2 -0
  27. package/dist/types/src/rules/html-aria-attribute-must-be-valid.d.ts +6 -0
  28. package/dist/types/src/rules/html-aria-role-must-be-valid.d.ts +6 -0
  29. package/dist/types/src/rules/html-no-duplicate-ids.d.ts +6 -0
  30. package/dist/types/src/rules/index.d.ts +3 -0
  31. package/dist/types/src/rules/rule-utils.d.ts +2 -0
  32. package/docs/rules/README.md +4 -0
  33. package/docs/rules/html-aria-attribute-must-be-valid.md +45 -0
  34. package/docs/rules/html-aria-role-must-be-valid.md +45 -0
  35. package/docs/rules/html-no-duplicate-ids.md +49 -0
  36. package/package.json +4 -4
  37. package/src/default-rules.ts +8 -2
  38. package/src/rules/html-aria-attribute-must-be-valid.ts +42 -0
  39. package/src/rules/html-aria-role-must-be-valid.ts +30 -0
  40. package/src/rules/html-no-duplicate-ids.ts +39 -0
  41. package/src/rules/index.ts +3 -0
  42. 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
- if (attributeNode.value?.type === "AST_HTML_ATTRIBUTE_VALUE_NODE") {
61
- const valueNode = attributeNode.value;
62
- if (valueNode.children && valueNode.children.length > 0) {
63
- return valueNode.children
64
- .filter(child => child.type === "AST_LITERAL_NODE")
65
- .map(child => child.content)
66
- .join("");
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 null;
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":"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"}
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"}
@@ -0,0 +1,6 @@
1
+ import type { LintOffense, Rule } from "../types.js";
2
+ import type { Node } from "@herb-tools/core";
3
+ export declare class HTMLAriaAttributeMustBeValid implements Rule {
4
+ name: string;
5
+ check(node: Node): LintOffense[];
6
+ }
@@ -0,0 +1,6 @@
1
+ import type { Rule, LintOffense } from "../types.js";
2
+ import type { Node } from "@herb-tools/core";
3
+ export declare class HTMLAriaRoleMustBeValidRule implements Rule {
4
+ name: string;
5
+ check(node: Node): LintOffense[];
6
+ }
@@ -0,0 +1,6 @@
1
+ import type { Node } from "@herb-tools/core";
2
+ import type { LintOffense, Rule } from "../types";
3
+ export declare class HTMLNoDuplicateIdsRule implements Rule {
4
+ name: string;
5
+ check(node: Node): LintOffense[];
6
+ }
@@ -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
  */
@@ -0,0 +1,6 @@
1
+ import type { LintOffense, Rule } from "../types.js";
2
+ import type { Node } from "@herb-tools/core";
3
+ export declare class HTMLAriaAttributeMustBeValid implements Rule {
4
+ name: string;
5
+ check(node: Node): LintOffense[];
6
+ }
@@ -0,0 +1,6 @@
1
+ import type { Rule, LintOffense } from "../types.js";
2
+ import type { Node } from "@herb-tools/core";
3
+ export declare class HTMLAriaRoleMustBeValidRule implements Rule {
4
+ name: string;
5
+ check(node: Node): LintOffense[];
6
+ }
@@ -0,0 +1,6 @@
1
+ import type { Node } from "@herb-tools/core";
2
+ import type { LintOffense, Rule } from "../types";
3
+ export declare class HTMLNoDuplicateIdsRule implements Rule {
4
+ name: string;
5
+ check(node: Node): LintOffense[];
6
+ }
@@ -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
  */
@@ -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.0",
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.0",
37
- "@herb-tools/highlighter": "0.4.0",
38
- "@herb-tools/node-wasm": "0.4.0",
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": [
@@ -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
+ }
@@ -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"