@herb-tools/linter 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/bin/herb-lint +3 -0
- package/dist/herb-lint.js +16505 -0
- package/dist/herb-lint.js.map +1 -0
- package/dist/index.cjs +834 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +820 -0
- package/dist/index.js.map +1 -0
- package/dist/package.json +49 -0
- package/dist/src/cli/argument-parser.js +96 -0
- package/dist/src/cli/argument-parser.js.map +1 -0
- package/dist/src/cli/file-processor.js +58 -0
- package/dist/src/cli/file-processor.js.map +1 -0
- package/dist/src/cli/formatters/base-formatter.js +3 -0
- package/dist/src/cli/formatters/base-formatter.js.map +1 -0
- package/dist/src/cli/formatters/detailed-formatter.js +62 -0
- package/dist/src/cli/formatters/detailed-formatter.js.map +1 -0
- package/dist/src/cli/formatters/index.js +4 -0
- package/dist/src/cli/formatters/index.js.map +1 -0
- package/dist/src/cli/formatters/simple-formatter.js +31 -0
- package/dist/src/cli/formatters/simple-formatter.js.map +1 -0
- package/dist/src/cli/index.js +5 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/cli/summary-reporter.js +96 -0
- package/dist/src/cli/summary-reporter.js.map +1 -0
- package/dist/src/cli.js +50 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/default-rules.js +31 -0
- package/dist/src/default-rules.js.map +1 -0
- package/dist/src/herb-lint.js +5 -0
- package/dist/src/herb-lint.js.map +1 -0
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/linter.js +39 -0
- package/dist/src/linter.js.map +1 -0
- package/dist/src/rules/erb-no-empty-tags.js +23 -0
- package/dist/src/rules/erb-no-empty-tags.js.map +1 -0
- package/dist/src/rules/erb-no-output-control-flow.js +47 -0
- package/dist/src/rules/erb-no-output-control-flow.js.map +1 -0
- package/dist/src/rules/erb-require-whitespace-inside-tags.js +43 -0
- package/dist/src/rules/erb-require-whitespace-inside-tags.js.map +1 -0
- package/dist/src/rules/html-anchor-require-href.js +25 -0
- package/dist/src/rules/html-anchor-require-href.js.map +1 -0
- package/dist/src/rules/html-aria-role-heading-requires-level.js +26 -0
- package/dist/src/rules/html-aria-role-heading-requires-level.js.map +1 -0
- package/dist/src/rules/html-attribute-double-quotes.js +21 -0
- package/dist/src/rules/html-attribute-double-quotes.js.map +1 -0
- package/dist/src/rules/html-attribute-values-require-quotes.js +22 -0
- package/dist/src/rules/html-attribute-values-require-quotes.js.map +1 -0
- package/dist/src/rules/html-boolean-attributes-no-value.js +19 -0
- package/dist/src/rules/html-boolean-attributes-no-value.js.map +1 -0
- package/dist/src/rules/html-img-require-alt.js +29 -0
- package/dist/src/rules/html-img-require-alt.js.map +1 -0
- package/dist/src/rules/html-no-block-inside-inline.js +59 -0
- package/dist/src/rules/html-no-block-inside-inline.js.map +1 -0
- package/dist/src/rules/html-no-duplicate-attributes.js +43 -0
- package/dist/src/rules/html-no-duplicate-attributes.js.map +1 -0
- package/dist/src/rules/html-no-empty-headings.js +148 -0
- package/dist/src/rules/html-no-empty-headings.js.map +1 -0
- package/dist/src/rules/html-no-nested-links.js +45 -0
- package/dist/src/rules/html-no-nested-links.js.map +1 -0
- package/dist/src/rules/html-tag-name-lowercase.js +39 -0
- package/dist/src/rules/html-tag-name-lowercase.js.map +1 -0
- package/dist/src/rules/index.js +13 -0
- package/dist/src/rules/index.js.map +1 -0
- package/dist/src/rules/rule-utils.js +198 -0
- package/dist/src/rules/rule-utils.js.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/cli/argument-parser.d.ts +14 -0
- package/dist/types/cli/file-processor.d.ts +21 -0
- package/dist/types/cli/formatters/base-formatter.d.ts +6 -0
- package/dist/types/cli/formatters/detailed-formatter.d.ts +13 -0
- package/dist/types/cli/formatters/index.d.ts +3 -0
- package/dist/types/cli/formatters/simple-formatter.d.ts +7 -0
- package/dist/types/cli/summary-reporter.d.ts +22 -0
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/default-rules.d.ts +2 -0
- package/dist/types/herb-lint.d.ts +2 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/linter.d.ts +18 -0
- package/dist/types/rules/erb-no-empty-tags.d.ts +6 -0
- package/dist/types/rules/erb-no-output-control-flow.d.ts +6 -0
- package/dist/types/rules/erb-require-whitespace-inside-tags.d.ts +6 -0
- package/dist/types/rules/html-anchor-require-href.d.ts +6 -0
- package/dist/types/rules/html-aria-role-heading-requires-level.d.ts +6 -0
- package/dist/types/rules/html-attribute-double-quotes.d.ts +6 -0
- package/dist/types/rules/html-attribute-values-require-quotes.d.ts +6 -0
- package/dist/types/rules/html-boolean-attributes-no-value.d.ts +6 -0
- package/dist/types/rules/html-img-require-alt.d.ts +6 -0
- package/dist/types/rules/html-no-block-inside-inline.d.ts +6 -0
- package/dist/types/rules/html-no-duplicate-attributes.d.ts +6 -0
- package/dist/types/rules/html-no-empty-headings.d.ts +6 -0
- package/dist/types/rules/html-no-nested-links.d.ts +6 -0
- package/dist/types/rules/html-tag-name-lowercase.d.ts +6 -0
- package/dist/types/rules/index.d.ts +12 -0
- package/dist/types/rules/rule-utils.d.ts +89 -0
- package/dist/types/src/cli/argument-parser.d.ts +14 -0
- package/dist/types/src/cli/file-processor.d.ts +21 -0
- package/dist/types/src/cli/formatters/base-formatter.d.ts +6 -0
- package/dist/types/src/cli/formatters/detailed-formatter.d.ts +13 -0
- package/dist/types/src/cli/formatters/index.d.ts +3 -0
- package/dist/types/src/cli/formatters/simple-formatter.d.ts +7 -0
- package/dist/types/src/cli/index.d.ts +4 -0
- package/dist/types/src/cli/summary-reporter.d.ts +22 -0
- package/dist/types/src/cli.d.ts +6 -0
- package/dist/types/src/default-rules.d.ts +2 -0
- package/dist/types/src/herb-lint.d.ts +2 -0
- package/dist/types/src/index.d.ts +3 -0
- package/dist/types/src/linter.d.ts +18 -0
- package/dist/types/src/rules/erb-no-empty-tags.d.ts +6 -0
- package/dist/types/src/rules/erb-no-output-control-flow.d.ts +6 -0
- package/dist/types/src/rules/erb-require-whitespace-inside-tags.d.ts +6 -0
- package/dist/types/src/rules/html-anchor-require-href.d.ts +6 -0
- package/dist/types/src/rules/html-aria-role-heading-requires-level.d.ts +6 -0
- package/dist/types/src/rules/html-attribute-double-quotes.d.ts +6 -0
- package/dist/types/src/rules/html-attribute-values-require-quotes.d.ts +6 -0
- package/dist/types/src/rules/html-boolean-attributes-no-value.d.ts +6 -0
- package/dist/types/src/rules/html-img-require-alt.d.ts +6 -0
- package/dist/types/src/rules/html-no-block-inside-inline.d.ts +6 -0
- package/dist/types/src/rules/html-no-duplicate-attributes.d.ts +6 -0
- package/dist/types/src/rules/html-no-empty-headings.d.ts +6 -0
- package/dist/types/src/rules/html-no-nested-links.d.ts +6 -0
- package/dist/types/src/rules/html-tag-name-lowercase.d.ts +6 -0
- package/dist/types/src/rules/index.d.ts +12 -0
- package/dist/types/src/rules/rule-utils.d.ts +89 -0
- package/dist/types/src/types.d.ts +26 -0
- package/dist/types/types.d.ts +26 -0
- package/docs/rules/README.md +39 -0
- package/docs/rules/erb-no-empty-tags.md +38 -0
- package/docs/rules/erb-no-output-control-flow.md +45 -0
- package/docs/rules/erb-require-whitespace-inside-tags.md +43 -0
- package/docs/rules/html-anchor-require-href.md +32 -0
- package/docs/rules/html-aria-role-heading-requires-level.md +34 -0
- package/docs/rules/html-attribute-double-quotes.md +43 -0
- package/docs/rules/html-attribute-values-require-quotes.md +43 -0
- package/docs/rules/html-boolean-attributes-no-value.md +39 -0
- package/docs/rules/html-img-require-alt.md +44 -0
- package/docs/rules/html-no-block-inside-inline.md +66 -0
- package/docs/rules/html-no-duplicate-attributes.md +35 -0
- package/docs/rules/html-no-empty-headings.md +78 -0
- package/docs/rules/html-no-nested-links.md +44 -0
- package/docs/rules/html-tag-name-lowercase.md +44 -0
- package/package.json +49 -0
- package/src/cli/argument-parser.ts +125 -0
- package/src/cli/file-processor.ts +86 -0
- package/src/cli/formatters/base-formatter.ts +11 -0
- package/src/cli/formatters/detailed-formatter.ts +74 -0
- package/src/cli/formatters/index.ts +3 -0
- package/src/cli/formatters/simple-formatter.ts +40 -0
- package/src/cli/index.ts +4 -0
- package/src/cli/summary-reporter.ts +127 -0
- package/src/cli.ts +60 -0
- package/src/default-rules.ts +33 -0
- package/src/herb-lint.ts +6 -0
- package/src/index.ts +3 -0
- package/src/linter.ts +50 -0
- package/src/rules/erb-no-empty-tags.ts +34 -0
- package/src/rules/erb-no-output-control-flow.ts +61 -0
- package/src/rules/erb-require-whitespace-inside-tags.ts +61 -0
- package/src/rules/html-anchor-require-href.ts +39 -0
- package/src/rules/html-aria-role-heading-requires-level.ts +44 -0
- package/src/rules/html-attribute-double-quotes.ts +28 -0
- package/src/rules/html-attribute-values-require-quotes.ts +30 -0
- package/src/rules/html-boolean-attributes-no-value.ts +27 -0
- package/src/rules/html-img-require-alt.ts +42 -0
- package/src/rules/html-no-block-inside-inline.ts +84 -0
- package/src/rules/html-no-duplicate-attributes.ts +59 -0
- package/src/rules/html-no-empty-headings.ts +185 -0
- package/src/rules/html-no-nested-links.ts +65 -0
- package/src/rules/html-tag-name-lowercase.ts +50 -0
- package/src/rules/index.ts +12 -0
- package/src/rules/rule-utils.ts +257 -0
- package/src/types.ts +32 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Linter Rule: Enforce whitespace around ERB tag contents
|
|
2
|
+
|
|
3
|
+
**Rule:** `erb-require-whitespace-inside-tags`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Require a single space before and after Ruby code inside ERB tags (`<% ... %>` and `<%= ... %>`). This improves readability and keeps ERB code visually consistent with Ruby style guides.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
Without spacing, ERB tags can become hard to read and visually cramped:
|
|
12
|
+
|
|
13
|
+
* difficult to scan: `<%=user.name%>`
|
|
14
|
+
* harder to read: `<%if admin%><%end%>`
|
|
15
|
+
|
|
16
|
+
By enforcing consistent spacing around Ruby expressions, templates become easier to read, review, and maintain. It also aligns with standard Ruby formatting conventions, where spaces are used around control keywords and operators.
|
|
17
|
+
|
|
18
|
+
## Examples
|
|
19
|
+
|
|
20
|
+
### ✅ Good
|
|
21
|
+
|
|
22
|
+
```erb
|
|
23
|
+
<%= user.name %>
|
|
24
|
+
|
|
25
|
+
<% if admin %>
|
|
26
|
+
Hello, admin.
|
|
27
|
+
<% end %>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 🚫 Bad
|
|
31
|
+
|
|
32
|
+
```erb
|
|
33
|
+
<%=user.name %>
|
|
34
|
+
|
|
35
|
+
<%if admin %>
|
|
36
|
+
|
|
37
|
+
Hello, admin.
|
|
38
|
+
<% end%>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## References
|
|
42
|
+
|
|
43
|
+
\-
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Linter Rule: Require `href` attribute on `<a>` tags
|
|
2
|
+
|
|
3
|
+
**Rule:** `html-anchor-require-href`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Disallow the use of anchor tags without anhref attribute in HTML templates. Use if you want to perform an action without having the user navigated to a new URL.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
Anchor tags without href are unfocusable if user is using keyboard navigation, or is unseen by screen readers.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
### ✅ Good
|
|
16
|
+
|
|
17
|
+
```erb
|
|
18
|
+
<a href="https://alink.com">I'm a real link</a>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 🚫 Bad
|
|
22
|
+
|
|
23
|
+
```erb
|
|
24
|
+
<a data-action="click->doSomething">I'm a fake link</a>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## References
|
|
28
|
+
|
|
29
|
+
* https://marcysutton.com/links-vs-buttons-in-modern-web-applications
|
|
30
|
+
* https://a11y-101.com/design/button-vs-link
|
|
31
|
+
* https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/button_role
|
|
32
|
+
* https://www.scottohara.me/blog/2021/05/28/disabled-links.html#w3c/html-aria#305
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Linter Rule: ARIA role with heading requires level
|
|
2
|
+
|
|
3
|
+
**Rule:** `html-aria-role-heading-requires-level`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Ensure that any element with `role="heading"` also has a valid `aria-level` attribute. The `aria-level` defines the heading level (1–6) and is required for assistive technologies to properly interpret the document structure.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
In HTML, semantic heading elements like `<h1>` through `<h6>` implicitly define their level. When using `role="heading"` on non-semantic elements (e.g., `<div>`, `<span>`), the level must be explicitly declared using `aria-level`, otherwise screen readers and accessibility tools may not understand the document hierarchy.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
### ✅ Good
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<div role="heading" aria-level="2">Section Title</div>
|
|
19
|
+
|
|
20
|
+
<span role="heading" aria-level="1">Main Title</span>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 🚫 Bad
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<div role="heading">Section Title</div>
|
|
27
|
+
|
|
28
|
+
<span role="heading">Main Title</span>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## References
|
|
32
|
+
|
|
33
|
+
* [ARIA: `heading` role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/heading_role)
|
|
34
|
+
* [ARIA: `aria-level` attribute](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-level)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Linter Rule: Prefer double quotes for HTML Attribute values
|
|
2
|
+
|
|
3
|
+
**Rule:** `html-attribute-double-quotes`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Prefer using double quotes (`"`) around HTML attribute values instead of single quotes (`'`).
|
|
8
|
+
|
|
9
|
+
**Exception:** Single quotes are allowed when the attribute value contains double quotes, as this avoids the need for escaping.
|
|
10
|
+
|
|
11
|
+
## Rationale
|
|
12
|
+
|
|
13
|
+
Double quotes are the most widely used and expected style for HTML attributes. Consistent use of double quotes improves readability, reduces visual noise when mixing with embedded Ruby (which often uses single quotes), and avoids escaping conflicts when embedding attribute values that contain single quotes.
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
### ✅ Good
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<input type="text">
|
|
21
|
+
|
|
22
|
+
<a href="/profile">Profile</a>
|
|
23
|
+
|
|
24
|
+
<div data-action="click->dropdown#toggle"></div>
|
|
25
|
+
|
|
26
|
+
<!-- Exception: Single quotes allowed when value contains double quotes -->
|
|
27
|
+
<div id='"hello"' title='Say "Hello" to the world'></div>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### 🚫 Bad
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<input type='text'>
|
|
35
|
+
|
|
36
|
+
<a href='/profile'>Profile</a>
|
|
37
|
+
|
|
38
|
+
<div data-action='click->dropdown#toggle'></div>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## References
|
|
42
|
+
|
|
43
|
+
* [HTML Living Standard - Attributes](https://html.spec.whatwg.org/multipage/syntax.html#attributes-2)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Linter Rule: Always quote attribute values
|
|
2
|
+
|
|
3
|
+
**Rule:** `html-attribute-values-require-quotes`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Always wrap HTML attribute values in quotes, even when they are technically optional according to the HTML specification.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
While some attribute values can be written without quotes if they don't contain spaces or special characters, omitting quotes makes the code harder to read, more error-prone, and inconsistent. Always quoting attribute values ensures:
|
|
12
|
+
|
|
13
|
+
- consistent appearance across all attributes,
|
|
14
|
+
- fewer surprises when attribute values contain special characters,
|
|
15
|
+
- easier editing and maintenance.
|
|
16
|
+
|
|
17
|
+
Additionally, always quoting is the common convention in most HTML formatters, linters, and developer tools.
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
### ✅ Good
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<div id="hello"></div>
|
|
25
|
+
|
|
26
|
+
<input type="text">
|
|
27
|
+
|
|
28
|
+
<a href="/profile">Profile</a>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 🚫 Bad
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<div id=hello></div>
|
|
35
|
+
|
|
36
|
+
<input type=text>
|
|
37
|
+
|
|
38
|
+
<a href=/profile></a>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## References
|
|
42
|
+
|
|
43
|
+
* [HTML Living Standard - Attributes](https://html.spec.whatwg.org/multipage/syntax.html#attributes-2)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Linter Rule: Omit values for boolean attributes
|
|
2
|
+
|
|
3
|
+
**Rule:** `html-boolean-attributes-no-value`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Omit attribute values for boolean HTML attributes. For boolean attributes, their presence alone represents `true`, and their absence represents `false`. There is no need to assign a value or use quotes.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
Using the canonical form for boolean attributes improves readability, keeps HTML concise, and avoids unnecessary characters. This also matches HTML specifications and the output of many HTML formatters.
|
|
12
|
+
|
|
13
|
+
For example, instead of writing `disabled="disabled"` or `disabled="true"`, simply write `disabled`.
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
### ✅ Good
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<input type="checkbox" checked>
|
|
21
|
+
|
|
22
|
+
<button disabled>Submit</button>
|
|
23
|
+
|
|
24
|
+
<select multiple>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 🚫 Bad
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<input type="checkbox" checked="checked">
|
|
31
|
+
|
|
32
|
+
<button disabled="true">Submit</button>
|
|
33
|
+
|
|
34
|
+
<select multiple="multiple">
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## References
|
|
38
|
+
|
|
39
|
+
* [HTML Living Standard - Boolean Attributes](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Linter Rule: Require `alt` attribute on `<img>` tags
|
|
2
|
+
|
|
3
|
+
**Rule:** `html-img-require-alt`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Enforce that all `<img>` elements include an `alt` attribute.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
The `alt` attribute provides alternative text for images, which is essential for accessibility (screen readers, assistive technologies), SEO, and proper fallback behavior when images fail to load. Even if the image is purely decorative, an empty `alt=""` should be provided to indicate that the image should be ignored by assistive technologies.
|
|
12
|
+
|
|
13
|
+
Omitting the `alt` attribute entirely leads to poor accessibility and can negatively affect user experience.
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
### ✅ Good
|
|
18
|
+
|
|
19
|
+
```erb
|
|
20
|
+
<img src="/logo.png" alt="Company logo">
|
|
21
|
+
|
|
22
|
+
<img src="/avatar.jpg" alt="<%= user.name %>'s profile picture">
|
|
23
|
+
|
|
24
|
+
<img src="/divider.png" alt="">
|
|
25
|
+
|
|
26
|
+
<%= image_tag image_path("logo.png"), alt: "Company logo" %>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 🚫 Bad
|
|
30
|
+
|
|
31
|
+
```erb
|
|
32
|
+
<img src="/logo.png">
|
|
33
|
+
|
|
34
|
+
<img src="/avatar.jpg" alt> <!-- TODO -->
|
|
35
|
+
|
|
36
|
+
<img src="/divider.png" alt=> <!-- TODO -->
|
|
37
|
+
|
|
38
|
+
<%= image_tag image_path("logo.png") %> <!-- TODO -->
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## References
|
|
42
|
+
|
|
43
|
+
* [W3C: Alternative Text](https://www.w3.org/WAI/tutorials/images/)
|
|
44
|
+
* [WCAG 2.1: Non-text Content](https://www.w3.org/WAI/WCAG22/quickref/?versions=2.1#non-text-content)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Linter Rule: No block elements inside inline elements
|
|
2
|
+
|
|
3
|
+
**Rule:** `html-no-block-inside-inline`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Prevent block-level elements from being placed inside inline elements.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
Placing block-level elements (like `<div>`, `<p>`, `<section>`) inside inline elements (like `<span>`, `<a>`, `<strong>`) violates HTML content model rules and may lead to unpredictable rendering behavior across browsers.
|
|
12
|
+
|
|
13
|
+
This practice can cause:
|
|
14
|
+
- Invalid HTML that fails validation
|
|
15
|
+
- Inconsistent rendering across different browsers
|
|
16
|
+
- Layout issues and unexpected visual results
|
|
17
|
+
- Accessibility problems with screen readers
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### ✅ Good
|
|
23
|
+
|
|
24
|
+
```erb
|
|
25
|
+
<span>
|
|
26
|
+
Hello <strong>World</strong>
|
|
27
|
+
</span>
|
|
28
|
+
|
|
29
|
+
<div>
|
|
30
|
+
<p>Paragraph inside div (valid)</p>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<a href="#">
|
|
34
|
+
<img src="icon.png" alt="Icon">
|
|
35
|
+
<span>Link text</span>
|
|
36
|
+
</a>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 🚫 Bad
|
|
40
|
+
|
|
41
|
+
```erb
|
|
42
|
+
<span>
|
|
43
|
+
<div>Invalid block inside span</div>
|
|
44
|
+
</span>
|
|
45
|
+
|
|
46
|
+
<span>
|
|
47
|
+
<p>Paragraph inside span (invalid)</p>
|
|
48
|
+
</span>
|
|
49
|
+
|
|
50
|
+
<a href="#">
|
|
51
|
+
<div class="card">
|
|
52
|
+
<h2>Card title</h2>
|
|
53
|
+
<p>Card content</p>
|
|
54
|
+
</div>
|
|
55
|
+
</a>
|
|
56
|
+
|
|
57
|
+
<strong>
|
|
58
|
+
<section>Section inside strong</section>
|
|
59
|
+
</strong>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## References
|
|
63
|
+
|
|
64
|
+
* [HTML Living Standard - Content models](https://html.spec.whatwg.org/multipage/dom.html#content-models)
|
|
65
|
+
* [MDN - Block-level elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements)
|
|
66
|
+
* [MDN - Inline elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Linter Rule: Disallow duplicate attributes on the same tag
|
|
2
|
+
|
|
3
|
+
**Rule:** `html-no-duplicate-attributes`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Disallow having multiple attributes with the same name on a single HTML tag.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
Duplicate attributes on an HTML element are invalid and may lead to undefined or unexpected behavior across browsers. When duplicate attributes exist, the browser typically uses the last occurrence, but this behavior is not guaranteed to be consistent across all engines or future specifications.
|
|
12
|
+
|
|
13
|
+
Catching duplicates early helps prevent subtle bugs, improves code correctness, and avoids accidental overwrites of attribute values.
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
### ✅ Good
|
|
18
|
+
|
|
19
|
+
```erb
|
|
20
|
+
<input type="text" name="username" id="user-id">
|
|
21
|
+
|
|
22
|
+
<button type="submit" disabled>Submit</button>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 🚫 Bad
|
|
26
|
+
|
|
27
|
+
```erb
|
|
28
|
+
<input type="text" type="password" name="username">
|
|
29
|
+
|
|
30
|
+
<button type="submit" type="button" disabled>Submit</button>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## References
|
|
34
|
+
|
|
35
|
+
* [HTML Living Standard - Attributes](https://html.spec.whatwg.org/multipage/syntax.html#attributes-2)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Linter Rule: Disallow empty headings
|
|
2
|
+
|
|
3
|
+
**Rule:** `html-no-empty-headings`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Disallow headings (`h1`, `h2`, etc.) with no accessible text content.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
Headings relay the structure of a webpage and provide a meaningful, hierarchical order of its content. If headings are empty or its text contents are inaccessible, this could confuse users or prevent them accessing sections of interest.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
### ✅ Good
|
|
16
|
+
|
|
17
|
+
```erb
|
|
18
|
+
<h*>Heading Content</h*>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```erb
|
|
22
|
+
<h*><span>Text</span><h*>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```erb
|
|
26
|
+
<div role="heading" aria-level="1">Heading Content</div>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```erb
|
|
30
|
+
<h* aria-hidden="true">Heading Content</h*>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```erb
|
|
34
|
+
<h* hidden>Heading Content</h*>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 🚫 Bad
|
|
38
|
+
|
|
39
|
+
```erb
|
|
40
|
+
<h1></h1>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```erb
|
|
44
|
+
<h2></h2>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```erb
|
|
48
|
+
<h3></h3>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```erb
|
|
52
|
+
<h4></h4>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```erb
|
|
56
|
+
<h5></h5>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```erb
|
|
60
|
+
<h6></h6>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```erb
|
|
64
|
+
<div role="heading" aria-level="1"></div>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```erb
|
|
68
|
+
<h1><span aria-hidden="true">Inaccessible text</span></h1>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## References
|
|
72
|
+
|
|
73
|
+
- [`<h1>`–`<h6>`: The HTML Section Heading elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/Heading_Elements)
|
|
74
|
+
- [ARIA: `heading` role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/heading_role)
|
|
75
|
+
- [ARIA: `aria-hidden` attribute](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-hidden)
|
|
76
|
+
- [ARIA: `aria-level` attribute](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-level)
|
|
77
|
+
|
|
78
|
+
Inspired by [ember-template-lint](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-empty-headings.md)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Linter Rule: Disallow nested links
|
|
2
|
+
|
|
3
|
+
**Rule:** `html-no-nested-links`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Disallow placing one `<a>` element inside another `<a>` element. Links must not contain other links as descendants.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
The HTML specification forbids nesting one anchor (`<a>`) inside another. Nested links result in invalid HTML, unpredictable click behavior, and inconsistent rendering across browsers.
|
|
12
|
+
|
|
13
|
+
Browsers may attempt error recovery when encountering nested links, but behavior varies and cannot be relied upon. This rule ensures strictly valid document structure and avoids subtle user interaction issues.
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
### ✅ Good
|
|
18
|
+
|
|
19
|
+
```erb
|
|
20
|
+
<a href="/products">View products</a>
|
|
21
|
+
<a href="/about">About us</a>
|
|
22
|
+
|
|
23
|
+
<%= link_to "View products", products_path %>
|
|
24
|
+
<%= link_to about_path do %>
|
|
25
|
+
About us
|
|
26
|
+
<% end %>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 🚫 Bad
|
|
30
|
+
|
|
31
|
+
```erb
|
|
32
|
+
<a href="/products">
|
|
33
|
+
View <a href="/special-offer">special offer</a>
|
|
34
|
+
</a>
|
|
35
|
+
|
|
36
|
+
<%= link_to "Products", products_path do %>
|
|
37
|
+
<%= link_to "Special offer", offer_path %> <!-- TODO -->
|
|
38
|
+
<% end %>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## References
|
|
42
|
+
|
|
43
|
+
* [HTML Living Standard - The a element](https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-a-element)
|
|
44
|
+
* [Rails `link_to` helper](https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Linter Rule: Enforce lowercase tag names
|
|
2
|
+
|
|
3
|
+
**Rule:** `html-tag-name-lowercase`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Enforce that all HTML tag names are written in lowercase.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
HTML is case-insensitive for tag names, but lowercase is the widely accepted convention for writing HTML. Consistent lowercase tag names improve readability, maintain consistency across codebases, and align with the output of most HTML formatters and validators.
|
|
12
|
+
|
|
13
|
+
Writing tags in uppercase or mixed case can lead to inconsistent code and unnecessary diffs during reviews and merges.
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### ✅ Good
|
|
19
|
+
|
|
20
|
+
```erb
|
|
21
|
+
<div class="container"></div>
|
|
22
|
+
|
|
23
|
+
<input type="text" name="username" />
|
|
24
|
+
|
|
25
|
+
<span>Label</span>
|
|
26
|
+
|
|
27
|
+
<%= content_tag(:div, "Hello world!") %>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 🚫 Bad
|
|
31
|
+
|
|
32
|
+
```erb
|
|
33
|
+
<DIV class="container"></DIV>
|
|
34
|
+
|
|
35
|
+
<Input type="text" name="username" />
|
|
36
|
+
|
|
37
|
+
<Span>Label</Span>
|
|
38
|
+
|
|
39
|
+
<%= content_tag(:DiV, "Hello world!") %> <!-- TODO -->
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## References
|
|
43
|
+
|
|
44
|
+
* [HTML Living Standard - Tag Syntax](https://html.spec.whatwg.org/multipage/syntax.html#syntax-tags)
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@herb-tools/linter",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "HTML+ERB linter for validating HTML structure and enforcing best practices",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://herb-tools.dev",
|
|
7
|
+
"bugs": "https://github.com/marcoroth/herb/issues/new?title=Package%20%60@herb-tools/linter%60:%20",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/marcoroth/herb.git",
|
|
11
|
+
"directory": "javascript/packages/linter"
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.cjs",
|
|
14
|
+
"module": "./dist/index.js",
|
|
15
|
+
"types": "./dist/types/index.d.ts",
|
|
16
|
+
"bin": {
|
|
17
|
+
"herb-lint": "./bin/herb-lint"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"clean": "rimraf dist",
|
|
21
|
+
"build": "yarn clean && tsc -b && rollup -c",
|
|
22
|
+
"watch": "tsc -b -w",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"prepublishOnly": "yarn clean && yarn build && yarn test"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
"./package.json": "./package.json",
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/types/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js",
|
|
31
|
+
"require": "./dist/index.cjs",
|
|
32
|
+
"default": "./dist/index.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@herb-tools/core": "0.4.0",
|
|
37
|
+
"@herb-tools/highlighter": "0.4.0",
|
|
38
|
+
"@herb-tools/node-wasm": "0.4.0",
|
|
39
|
+
"glob": "^11.0.3"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"package.json",
|
|
43
|
+
"README.md",
|
|
44
|
+
"docs/",
|
|
45
|
+
"src/",
|
|
46
|
+
"bin/",
|
|
47
|
+
"dist/"
|
|
48
|
+
]
|
|
49
|
+
}
|