@herb-tools/linter 0.4.2 → 0.5.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 +221 -10
- package/dist/herb-lint.js +817 -292
- package/dist/herb-lint.js.map +1 -1
- package/dist/index.cjs +360 -81
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +355 -83
- package/dist/index.js.map +1 -1
- package/dist/package.json +4 -4
- package/dist/src/cli/argument-parser.js +28 -18
- package/dist/src/cli/argument-parser.js.map +1 -1
- package/dist/src/cli/file-processor.js +21 -17
- package/dist/src/cli/file-processor.js.map +1 -1
- package/dist/src/cli/formatters/detailed-formatter.js +9 -9
- package/dist/src/cli/formatters/detailed-formatter.js.map +1 -1
- package/dist/src/cli/formatters/github-actions-formatter.js +50 -0
- package/dist/src/cli/formatters/github-actions-formatter.js.map +1 -0
- package/dist/src/cli/formatters/index.js +2 -0
- package/dist/src/cli/formatters/index.js.map +1 -1
- package/dist/src/cli/formatters/json-formatter.js +58 -0
- package/dist/src/cli/formatters/json-formatter.js.map +1 -0
- package/dist/src/cli/formatters/simple-formatter.js +15 -15
- package/dist/src/cli/formatters/simple-formatter.js.map +1 -1
- package/dist/src/cli/output-manager.js +120 -0
- package/dist/src/cli/output-manager.js.map +1 -0
- package/dist/src/cli/summary-reporter.js +22 -22
- package/dist/src/cli/summary-reporter.js.map +1 -1
- package/dist/src/cli.js +41 -26
- package/dist/src/cli.js.map +1 -1
- package/dist/src/default-rules.js +8 -0
- package/dist/src/default-rules.js.map +1 -1
- package/dist/src/linter.js +37 -6
- package/dist/src/linter.js.map +1 -1
- package/dist/src/rules/erb-no-empty-tags.js +5 -4
- package/dist/src/rules/erb-no-empty-tags.js.map +1 -1
- package/dist/src/rules/erb-no-output-control-flow.js +5 -4
- package/dist/src/rules/erb-no-output-control-flow.js.map +1 -1
- package/dist/src/rules/erb-prefer-image-tag-helper.js +93 -0
- package/dist/src/rules/erb-prefer-image-tag-helper.js.map +1 -0
- package/dist/src/rules/erb-require-whitespace-inside-tags.js +5 -4
- package/dist/src/rules/erb-require-whitespace-inside-tags.js.map +1 -1
- package/dist/src/rules/erb-requires-trailing-newline.js +22 -0
- package/dist/src/rules/erb-requires-trailing-newline.js.map +1 -0
- package/dist/src/rules/html-anchor-require-href.js +5 -4
- package/dist/src/rules/html-anchor-require-href.js.map +1 -1
- package/dist/src/rules/html-aria-attribute-must-be-valid.js +5 -4
- package/dist/src/rules/html-aria-attribute-must-be-valid.js.map +1 -1
- package/dist/src/rules/html-aria-level-must-be-valid.js +27 -0
- package/dist/src/rules/html-aria-level-must-be-valid.js.map +1 -0
- package/dist/src/rules/html-aria-role-heading-requires-level.js +5 -4
- package/dist/src/rules/html-aria-role-heading-requires-level.js.map +1 -1
- package/dist/src/rules/html-aria-role-must-be-valid.js +5 -4
- package/dist/src/rules/html-aria-role-must-be-valid.js.map +1 -1
- package/dist/src/rules/html-attribute-double-quotes.js +5 -4
- package/dist/src/rules/html-attribute-double-quotes.js.map +1 -1
- package/dist/src/rules/html-attribute-values-require-quotes.js +5 -4
- package/dist/src/rules/html-attribute-values-require-quotes.js.map +1 -1
- package/dist/src/rules/html-boolean-attributes-no-value.js +5 -4
- package/dist/src/rules/html-boolean-attributes-no-value.js.map +1 -1
- package/dist/src/rules/html-img-require-alt.js +5 -4
- package/dist/src/rules/html-img-require-alt.js.map +1 -1
- package/dist/src/rules/html-no-block-inside-inline.js +7 -6
- package/dist/src/rules/html-no-block-inside-inline.js.map +1 -1
- package/dist/src/rules/html-no-duplicate-attributes.js +5 -4
- package/dist/src/rules/html-no-duplicate-attributes.js.map +1 -1
- package/dist/src/rules/html-no-duplicate-ids.js +5 -4
- package/dist/src/rules/html-no-duplicate-ids.js.map +1 -1
- package/dist/src/rules/html-no-empty-headings.js +5 -4
- package/dist/src/rules/html-no-empty-headings.js.map +1 -1
- package/dist/src/rules/html-no-nested-links.js +5 -4
- package/dist/src/rules/html-no-nested-links.js.map +1 -1
- package/dist/src/rules/html-tag-name-lowercase.js +5 -4
- package/dist/src/rules/html-tag-name-lowercase.js.map +1 -1
- package/dist/src/rules/index.js +3 -0
- package/dist/src/rules/index.js.map +1 -1
- package/dist/src/rules/parser-no-errors.js +18 -0
- package/dist/src/rules/parser-no-errors.js.map +1 -0
- package/dist/src/rules/rule-utils.js +125 -2
- package/dist/src/rules/rule-utils.js.map +1 -1
- package/dist/src/rules/svg-tag-name-capitalization.js +5 -4
- package/dist/src/rules/svg-tag-name-capitalization.js.map +1 -1
- package/dist/src/types.js +15 -1
- package/dist/src/types.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/cli/argument-parser.d.ts +2 -1
- package/dist/types/cli/file-processor.d.ts +6 -5
- package/dist/types/cli/formatters/base-formatter.d.ts +2 -2
- package/dist/types/cli/formatters/detailed-formatter.d.ts +2 -2
- package/dist/types/cli/formatters/github-actions-formatter.d.ts +12 -0
- package/dist/types/cli/formatters/index.d.ts +2 -0
- package/dist/types/cli/formatters/json-formatter.d.ts +42 -0
- package/dist/types/cli/formatters/simple-formatter.d.ts +2 -2
- package/dist/types/cli/output-manager.d.ts +31 -0
- package/dist/types/cli/summary-reporter.d.ts +3 -3
- package/dist/types/cli.d.ts +3 -1
- package/dist/types/linter.d.ts +20 -5
- package/dist/types/rules/erb-no-empty-tags.d.ts +5 -4
- package/dist/types/rules/erb-no-output-control-flow.d.ts +5 -4
- package/dist/types/rules/erb-prefer-image-tag-helper.d.ts +7 -0
- package/dist/types/rules/erb-require-whitespace-inside-tags.d.ts +5 -4
- package/dist/types/rules/erb-requires-trailing-newline.d.ts +6 -0
- package/dist/types/rules/html-anchor-require-href.d.ts +5 -4
- package/dist/types/rules/html-aria-attribute-must-be-valid.d.ts +5 -4
- package/dist/types/rules/html-aria-level-must-be-valid.d.ts +7 -0
- package/dist/types/rules/html-aria-role-heading-requires-level.d.ts +5 -4
- package/dist/types/rules/html-aria-role-must-be-valid.d.ts +5 -4
- package/dist/types/rules/html-attribute-double-quotes.d.ts +5 -4
- package/dist/types/rules/html-attribute-values-require-quotes.d.ts +5 -4
- package/dist/types/rules/html-boolean-attributes-no-value.d.ts +5 -4
- package/dist/types/rules/html-img-require-alt.d.ts +5 -4
- package/dist/types/rules/html-no-block-inside-inline.d.ts +5 -4
- package/dist/types/rules/html-no-duplicate-attributes.d.ts +5 -4
- package/dist/types/rules/html-no-duplicate-ids.d.ts +5 -4
- package/dist/types/rules/html-no-empty-headings.d.ts +5 -4
- package/dist/types/rules/html-no-nested-links.d.ts +5 -4
- package/dist/types/rules/html-tag-name-lowercase.d.ts +5 -4
- package/dist/types/rules/index.d.ts +3 -0
- package/dist/types/rules/parser-no-errors.d.ts +8 -0
- package/dist/types/rules/rule-utils.d.ts +73 -4
- package/dist/types/rules/svg-tag-name-capitalization.d.ts +5 -4
- package/dist/types/src/cli/argument-parser.d.ts +2 -1
- package/dist/types/src/cli/file-processor.d.ts +6 -5
- package/dist/types/src/cli/formatters/base-formatter.d.ts +2 -2
- package/dist/types/src/cli/formatters/detailed-formatter.d.ts +2 -2
- package/dist/types/src/cli/formatters/github-actions-formatter.d.ts +12 -0
- package/dist/types/src/cli/formatters/index.d.ts +2 -0
- package/dist/types/src/cli/formatters/json-formatter.d.ts +42 -0
- package/dist/types/src/cli/formatters/simple-formatter.d.ts +2 -2
- package/dist/types/src/cli/output-manager.d.ts +31 -0
- package/dist/types/src/cli/summary-reporter.d.ts +3 -3
- package/dist/types/src/cli.d.ts +3 -1
- package/dist/types/src/linter.d.ts +20 -5
- package/dist/types/src/rules/erb-no-empty-tags.d.ts +5 -4
- package/dist/types/src/rules/erb-no-output-control-flow.d.ts +5 -4
- package/dist/types/src/rules/erb-prefer-image-tag-helper.d.ts +7 -0
- package/dist/types/src/rules/erb-require-whitespace-inside-tags.d.ts +5 -4
- package/dist/types/src/rules/erb-requires-trailing-newline.d.ts +6 -0
- package/dist/types/src/rules/html-anchor-require-href.d.ts +5 -4
- package/dist/types/src/rules/html-aria-attribute-must-be-valid.d.ts +5 -4
- package/dist/types/src/rules/html-aria-level-must-be-valid.d.ts +7 -0
- package/dist/types/src/rules/html-aria-role-heading-requires-level.d.ts +5 -4
- package/dist/types/src/rules/html-aria-role-must-be-valid.d.ts +5 -4
- package/dist/types/src/rules/html-attribute-double-quotes.d.ts +5 -4
- package/dist/types/src/rules/html-attribute-values-require-quotes.d.ts +5 -4
- package/dist/types/src/rules/html-boolean-attributes-no-value.d.ts +5 -4
- package/dist/types/src/rules/html-img-require-alt.d.ts +5 -4
- package/dist/types/src/rules/html-no-block-inside-inline.d.ts +5 -4
- package/dist/types/src/rules/html-no-duplicate-attributes.d.ts +5 -4
- package/dist/types/src/rules/html-no-duplicate-ids.d.ts +5 -4
- package/dist/types/src/rules/html-no-empty-headings.d.ts +5 -4
- package/dist/types/src/rules/html-no-nested-links.d.ts +5 -4
- package/dist/types/src/rules/html-tag-name-lowercase.d.ts +5 -4
- package/dist/types/src/rules/index.d.ts +3 -0
- package/dist/types/src/rules/parser-no-errors.d.ts +8 -0
- package/dist/types/src/rules/rule-utils.d.ts +73 -4
- package/dist/types/src/rules/svg-tag-name-capitalization.d.ts +5 -4
- package/dist/types/src/types.d.ts +50 -7
- package/dist/types/types.d.ts +50 -7
- package/docs/rules/README.md +5 -1
- package/docs/rules/erb-prefer-image-tag-helper.md +65 -0
- package/docs/rules/erb-requires-trailing-newline.md +37 -0
- package/docs/rules/html-anchor-require-href.md +1 -1
- package/docs/rules/html-aria-level-must-be-valid.md +37 -0
- package/docs/rules/parser-no-errors.md +84 -0
- package/package.json +4 -4
- package/src/cli/argument-parser.ts +33 -19
- package/src/cli/file-processor.ts +27 -21
- package/src/cli/formatters/base-formatter.ts +2 -2
- package/src/cli/formatters/detailed-formatter.ts +9 -9
- package/src/cli/formatters/github-actions-formatter.ts +70 -0
- package/src/cli/formatters/index.ts +2 -0
- package/src/cli/formatters/json-formatter.ts +107 -0
- package/src/cli/formatters/simple-formatter.ts +15 -15
- package/src/cli/output-manager.ts +143 -0
- package/src/cli/summary-reporter.ts +24 -24
- package/src/cli.ts +48 -31
- package/src/default-rules.ts +8 -0
- package/src/linter.ts +42 -8
- package/src/rules/erb-no-empty-tags.ts +7 -6
- package/src/rules/erb-no-output-control-flow.ts +8 -6
- package/src/rules/erb-prefer-image-tag-helper.ts +124 -0
- package/src/rules/erb-require-whitespace-inside-tags.ts +7 -6
- package/src/rules/erb-requires-trailing-newline.ts +29 -0
- package/src/rules/html-anchor-require-href.ts +7 -6
- package/src/rules/html-aria-attribute-must-be-valid.ts +7 -7
- package/src/rules/html-aria-level-must-be-valid.ts +42 -0
- package/src/rules/html-aria-role-heading-requires-level.ts +7 -6
- package/src/rules/html-aria-role-must-be-valid.ts +7 -6
- package/src/rules/html-attribute-double-quotes.ts +7 -6
- package/src/rules/html-attribute-values-require-quotes.ts +7 -6
- package/src/rules/html-boolean-attributes-no-value.ts +7 -6
- package/src/rules/html-img-require-alt.ts +7 -6
- package/src/rules/html-no-block-inside-inline.ts +9 -8
- package/src/rules/html-no-duplicate-attributes.ts +7 -6
- package/src/rules/html-no-duplicate-ids.ts +7 -7
- package/src/rules/html-no-empty-headings.ts +7 -6
- package/src/rules/html-no-nested-links.ts +7 -6
- package/src/rules/html-tag-name-lowercase.ts +7 -6
- package/src/rules/index.ts +3 -0
- package/src/rules/parser-no-errors.ts +25 -0
- package/src/rules/rule-utils.ts +156 -4
- package/src/rules/svg-tag-name-capitalization.ts +7 -6
- package/src/types.ts +61 -7
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { BaseRuleVisitor, getTagName, findAttributeByName, getAttributes } from "./rule-utils.js"
|
|
2
|
+
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { HTMLOpenTagNode, HTMLSelfCloseTagNode, HTMLAttributeValueNode, ERBContentNode, LiteralNode, ParseResult } from "@herb-tools/core"
|
|
6
|
+
|
|
7
|
+
class ERBPreferImageTagHelperVisitor extends BaseRuleVisitor {
|
|
8
|
+
visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
|
|
9
|
+
this.checkImgTag(node)
|
|
10
|
+
super.visitHTMLOpenTagNode(node)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
visitHTMLSelfCloseTagNode(node: HTMLSelfCloseTagNode): void {
|
|
14
|
+
this.checkImgTag(node)
|
|
15
|
+
super.visitHTMLSelfCloseTagNode(node)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private checkImgTag(node: HTMLOpenTagNode | HTMLSelfCloseTagNode): void {
|
|
19
|
+
const tagName = getTagName(node)
|
|
20
|
+
|
|
21
|
+
if (tagName !== "img") {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const attributes = getAttributes(node)
|
|
26
|
+
const srcAttribute = findAttributeByName(attributes, "src")
|
|
27
|
+
|
|
28
|
+
if (!srcAttribute) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!srcAttribute.value) {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const valueNode = srcAttribute.value as HTMLAttributeValueNode
|
|
37
|
+
const hasERBContent = this.containsERBContent(valueNode)
|
|
38
|
+
|
|
39
|
+
if (hasERBContent) {
|
|
40
|
+
const suggestedExpression = this.buildSuggestedExpression(valueNode)
|
|
41
|
+
|
|
42
|
+
this.addOffense(
|
|
43
|
+
`Prefer \`image_tag\` helper over manual \`<img>\` with dynamic ERB expressions. Use \`<%= image_tag ${suggestedExpression}, alt: "..." %>\` instead.`,
|
|
44
|
+
srcAttribute.location,
|
|
45
|
+
"warning"
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private containsERBContent(valueNode: HTMLAttributeValueNode): boolean {
|
|
51
|
+
if (!valueNode.children) return false
|
|
52
|
+
|
|
53
|
+
return valueNode.children.some(child => child.type === "AST_ERB_CONTENT_NODE")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private buildSuggestedExpression(valueNode: HTMLAttributeValueNode): string {
|
|
57
|
+
if (!valueNode.children) return "expression"
|
|
58
|
+
|
|
59
|
+
let hasText = false
|
|
60
|
+
let hasERB = false
|
|
61
|
+
|
|
62
|
+
for (const child of valueNode.children) {
|
|
63
|
+
if (child.type === "AST_ERB_CONTENT_NODE") {
|
|
64
|
+
hasERB = true
|
|
65
|
+
} else if (child.type === "AST_LITERAL_NODE") {
|
|
66
|
+
const literalNode = child as LiteralNode
|
|
67
|
+
|
|
68
|
+
if (literalNode.content && literalNode.content.trim()) {
|
|
69
|
+
hasText = true
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (hasText && hasERB) {
|
|
75
|
+
let result = '"'
|
|
76
|
+
|
|
77
|
+
for (const child of valueNode.children) {
|
|
78
|
+
if (child.type === "AST_ERB_CONTENT_NODE") {
|
|
79
|
+
const erbNode = child as ERBContentNode
|
|
80
|
+
|
|
81
|
+
result += `#{${(erbNode.content?.value || "").trim()}}`
|
|
82
|
+
} else if (child.type === "AST_LITERAL_NODE") {
|
|
83
|
+
const literalNode = child as LiteralNode
|
|
84
|
+
|
|
85
|
+
result += literalNode.content || ""
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
result += '"'
|
|
90
|
+
|
|
91
|
+
return result
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (hasERB && !hasText) {
|
|
95
|
+
const erbNodes = valueNode.children.filter(child => child.type === "AST_ERB_CONTENT_NODE") as ERBContentNode[]
|
|
96
|
+
|
|
97
|
+
if (erbNodes.length === 1) {
|
|
98
|
+
return (erbNodes[0].content?.value || "").trim()
|
|
99
|
+
} else if (erbNodes.length > 1) {
|
|
100
|
+
let result = '"'
|
|
101
|
+
|
|
102
|
+
for (const erbNode of erbNodes) {
|
|
103
|
+
result += `#{${(erbNode.content?.value || "").trim()}}`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
result += '"'
|
|
107
|
+
|
|
108
|
+
return result
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return "expression"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export class ERBPreferImageTagHelperRule extends ParserRule {
|
|
117
|
+
name = "erb-prefer-image-tag-helper"
|
|
118
|
+
|
|
119
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
120
|
+
const visitor = new ERBPreferImageTagHelperVisitor(this.name, context)
|
|
121
|
+
visitor.visit(result.value)
|
|
122
|
+
return visitor.offenses
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ParseResult, Token, Node } from "@herb-tools/core"
|
|
2
2
|
import { isERBNode } from "@herb-tools/core";
|
|
3
|
-
import
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
4
5
|
import { BaseRuleVisitor } from "./rule-utils.js"
|
|
5
6
|
|
|
6
7
|
class RequireWhitespaceInsideTags extends BaseRuleVisitor {
|
|
@@ -81,12 +82,12 @@ class RequireWhitespaceInsideTags extends BaseRuleVisitor {
|
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
export class ERBRequireWhitespaceRule
|
|
85
|
+
export class ERBRequireWhitespaceRule extends ParserRule {
|
|
85
86
|
name = "erb-require-whitespace-inside-tags"
|
|
86
87
|
|
|
87
|
-
check(
|
|
88
|
-
const visitor = new RequireWhitespaceInsideTags(this.name)
|
|
89
|
-
visitor.visit(
|
|
88
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
89
|
+
const visitor = new RequireWhitespaceInsideTags(this.name, context)
|
|
90
|
+
visitor.visit(result.value)
|
|
90
91
|
return visitor.offenses
|
|
91
92
|
}
|
|
92
93
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BaseSourceRuleVisitor, createEndOfFileLocation } from "./rule-utils.js"
|
|
2
|
+
import { SourceRule } from "../types.js"
|
|
3
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
4
|
+
|
|
5
|
+
class ERBRequiresTrailingNewlineVisitor extends BaseSourceRuleVisitor {
|
|
6
|
+
protected visitSource(source: string): void {
|
|
7
|
+
if (source.length === 0) return
|
|
8
|
+
if (source.endsWith('\n')) return
|
|
9
|
+
if (!this.context.fileName) return
|
|
10
|
+
|
|
11
|
+
this.addOffense(
|
|
12
|
+
"File must end with trailing newline",
|
|
13
|
+
createEndOfFileLocation(source),
|
|
14
|
+
"error"
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class ERBRequiresTrailingNewlineRule extends SourceRule {
|
|
20
|
+
name = "erb-requires-trailing-newline"
|
|
21
|
+
|
|
22
|
+
check(source: string, context?: Partial<LintContext>): LintOffense[] {
|
|
23
|
+
const visitor = new ERBRequiresTrailingNewlineVisitor(this.name, context)
|
|
24
|
+
|
|
25
|
+
visitor.visit(source)
|
|
26
|
+
|
|
27
|
+
return visitor.offenses
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BaseRuleVisitor, getTagName, hasAttribute } from "./rule-utils.js"
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
|
|
5
6
|
|
|
6
7
|
class AnchorRechireHrefVisitor extends BaseRuleVisitor {
|
|
7
8
|
visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
|
|
@@ -26,13 +27,13 @@ class AnchorRechireHrefVisitor extends BaseRuleVisitor {
|
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
export class HTMLAnchorRequireHrefRule
|
|
30
|
+
export class HTMLAnchorRequireHrefRule extends ParserRule {
|
|
30
31
|
name = "html-anchor-require-href"
|
|
31
32
|
|
|
32
|
-
check(
|
|
33
|
-
const visitor = new AnchorRechireHrefVisitor(this.name)
|
|
33
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
34
|
+
const visitor = new AnchorRechireHrefVisitor(this.name, context)
|
|
34
35
|
|
|
35
|
-
visitor.visit(
|
|
36
|
+
visitor.visit(result.value)
|
|
36
37
|
|
|
37
38
|
return visitor.offenses
|
|
38
39
|
}
|
|
@@ -2,13 +2,13 @@ import {
|
|
|
2
2
|
ARIA_ATTRIBUTES,
|
|
3
3
|
AttributeVisitorMixin,
|
|
4
4
|
} from "./rule-utils.js";
|
|
5
|
-
|
|
6
|
-
import type { LintOffense,
|
|
5
|
+
import { ParserRule } from "../types.js";
|
|
6
|
+
import type { LintOffense, LintContext } from "../types.js";
|
|
7
7
|
import type {
|
|
8
8
|
HTMLAttributeNode,
|
|
9
9
|
HTMLOpenTagNode,
|
|
10
10
|
HTMLSelfCloseTagNode,
|
|
11
|
-
|
|
11
|
+
ParseResult,
|
|
12
12
|
} from "@herb-tools/core";
|
|
13
13
|
|
|
14
14
|
class AriaAttributeMustBeValid extends AttributeVisitorMixin {
|
|
@@ -31,12 +31,12 @@ class AriaAttributeMustBeValid extends AttributeVisitorMixin {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export class HTMLAriaAttributeMustBeValid
|
|
34
|
+
export class HTMLAriaAttributeMustBeValid extends ParserRule {
|
|
35
35
|
name = "html-aria-attribute-must-be-valid";
|
|
36
36
|
|
|
37
|
-
check(
|
|
38
|
-
const visitor = new AriaAttributeMustBeValid(this.name);
|
|
39
|
-
visitor.visit(
|
|
37
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
38
|
+
const visitor = new AriaAttributeMustBeValid(this.name, context);
|
|
39
|
+
visitor.visit(result.value);
|
|
40
40
|
return visitor.offenses;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { AttributeVisitorMixin } from "./rule-utils.js"
|
|
2
|
+
import { ParserRule } from "../types.js"
|
|
3
|
+
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { ParseResult, HTMLAttributeNode, HTMLOpenTagNode, HTMLSelfCloseTagNode } from "@herb-tools/core"
|
|
6
|
+
|
|
7
|
+
class HTMLAriaLevelMustBeValidVisitor extends AttributeVisitorMixin {
|
|
8
|
+
protected checkAttribute(attributeName: string, attributeValue: string | null, attributeNode: HTMLAttributeNode, _parentNode: HTMLOpenTagNode | HTMLSelfCloseTagNode): void {
|
|
9
|
+
if (attributeName !== "aria-level") return
|
|
10
|
+
if (attributeValue !== null && attributeValue.includes("<%")) return
|
|
11
|
+
|
|
12
|
+
if (attributeValue === null || attributeValue === "") {
|
|
13
|
+
this.addOffense(
|
|
14
|
+
`The \`aria-level\` attribute must be an integer between 1 and 6, got an empty value.`,
|
|
15
|
+
attributeNode.location,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const number = parseInt(attributeValue)
|
|
22
|
+
|
|
23
|
+
if (isNaN(number) || number < 1 || number > 6 || attributeValue !== number.toString()) {
|
|
24
|
+
this.addOffense(
|
|
25
|
+
`The \`aria-level\` attribute must be an integer between 1 and 6, got \`${attributeValue}\`.`,
|
|
26
|
+
attributeNode.location,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class HTMLAriaLevelMustBeValidRule extends ParserRule {
|
|
33
|
+
name = "html-aria-level-must-be-valid"
|
|
34
|
+
|
|
35
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
36
|
+
const visitor = new HTMLAriaLevelMustBeValidVisitor(this.name, context)
|
|
37
|
+
|
|
38
|
+
visitor.visit(result.value)
|
|
39
|
+
|
|
40
|
+
return visitor.offenses
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AttributeVisitorMixin, getAttributeName, getAttributes } from "./rule-utils.js"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { ParseResult, HTMLAttributeNode, HTMLOpenTagNode, HTMLSelfCloseTagNode } from "@herb-tools/core"
|
|
5
6
|
|
|
6
7
|
class AriaRoleHeadingRequiresLevel extends AttributeVisitorMixin {
|
|
7
8
|
|
|
@@ -33,12 +34,12 @@ class AriaRoleHeadingRequiresLevel extends AttributeVisitorMixin {
|
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
export class HTMLAriaRoleHeadingRequiresLevelRule
|
|
37
|
+
export class HTMLAriaRoleHeadingRequiresLevelRule extends ParserRule {
|
|
37
38
|
name = "html-aria-role-heading-requires-level"
|
|
38
39
|
|
|
39
|
-
check(
|
|
40
|
-
const visitor = new AriaRoleHeadingRequiresLevel(this.name)
|
|
41
|
-
visitor.visit(
|
|
40
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
41
|
+
const visitor = new AriaRoleHeadingRequiresLevel(this.name, context)
|
|
42
|
+
visitor.visit(result.value)
|
|
42
43
|
return visitor.offenses
|
|
43
44
|
}
|
|
44
45
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AttributeVisitorMixin, VALID_ARIA_ROLES } from "./rule-utils.js"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { ParseResult, HTMLAttributeNode } from "@herb-tools/core"
|
|
5
6
|
|
|
6
7
|
class AriaRoleMustBeValid extends AttributeVisitorMixin {
|
|
7
8
|
checkAttribute(attributeName: string, attributeValue: string | null, attributeNode: HTMLAttributeNode,): void {
|
|
@@ -17,13 +18,13 @@ class AriaRoleMustBeValid extends AttributeVisitorMixin {
|
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
export class HTMLAriaRoleMustBeValidRule
|
|
21
|
+
export class HTMLAriaRoleMustBeValidRule extends ParserRule {
|
|
21
22
|
name = "html-aria-role-must-be-valid"
|
|
22
23
|
|
|
23
|
-
check(
|
|
24
|
-
const visitor = new AriaRoleMustBeValid(this.name)
|
|
24
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
25
|
+
const visitor = new AriaRoleMustBeValid(this.name, context)
|
|
25
26
|
|
|
26
|
-
visitor.visit(
|
|
27
|
+
visitor.visit(result.value)
|
|
27
28
|
|
|
28
29
|
return visitor.offenses
|
|
29
30
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AttributeVisitorMixin, getAttributeValueQuoteType, hasAttributeValue } from "./rule-utils.js"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { ParseResult, HTMLAttributeNode } from "@herb-tools/core"
|
|
5
6
|
|
|
6
7
|
class AttributeDoubleQuotesVisitor extends AttributeVisitorMixin {
|
|
7
8
|
protected checkAttribute(attributeName: string, attributeValue: string | null, attributeNode: HTMLAttributeNode): void {
|
|
@@ -17,12 +18,12 @@ class AttributeDoubleQuotesVisitor extends AttributeVisitorMixin {
|
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
export class HTMLAttributeDoubleQuotesRule
|
|
21
|
+
export class HTMLAttributeDoubleQuotesRule extends ParserRule {
|
|
21
22
|
name = "html-attribute-double-quotes"
|
|
22
23
|
|
|
23
|
-
check(
|
|
24
|
-
const visitor = new AttributeDoubleQuotesVisitor(this.name)
|
|
25
|
-
visitor.visit(
|
|
24
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
25
|
+
const visitor = new AttributeDoubleQuotesVisitor(this.name, context)
|
|
26
|
+
visitor.visit(result.value)
|
|
26
27
|
return visitor.offenses
|
|
27
28
|
}
|
|
28
29
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AttributeVisitorMixin } from "./rule-utils.js"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { HTMLAttributeNode, HTMLAttributeValueNode, ParseResult } from "@herb-tools/core"
|
|
5
6
|
|
|
6
7
|
class AttributeValuesRequireQuotesVisitor extends AttributeVisitorMixin {
|
|
7
8
|
protected checkAttribute(attributeName: string, _attributeValue: string | null, attributeNode: HTMLAttributeNode): void {
|
|
@@ -19,12 +20,12 @@ class AttributeValuesRequireQuotesVisitor extends AttributeVisitorMixin {
|
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
export class HTMLAttributeValuesRequireQuotesRule
|
|
23
|
+
export class HTMLAttributeValuesRequireQuotesRule extends ParserRule {
|
|
23
24
|
name = "html-attribute-values-require-quotes"
|
|
24
25
|
|
|
25
|
-
check(
|
|
26
|
-
const visitor = new AttributeValuesRequireQuotesVisitor(this.name)
|
|
27
|
-
visitor.visit(
|
|
26
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
27
|
+
const visitor = new AttributeValuesRequireQuotesVisitor(this.name, context)
|
|
28
|
+
visitor.visit(result.value)
|
|
28
29
|
return visitor.offenses
|
|
29
30
|
}
|
|
30
31
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AttributeVisitorMixin, isBooleanAttribute, hasAttributeValue } from "./rule-utils.js"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { HTMLAttributeNode, ParseResult } from "@herb-tools/core"
|
|
5
6
|
|
|
6
7
|
class BooleanAttributesNoValueVisitor extends AttributeVisitorMixin {
|
|
7
8
|
protected checkAttribute(attributeName: string, _attributeValue: string | null, attributeNode: HTMLAttributeNode): void {
|
|
@@ -16,12 +17,12 @@ class BooleanAttributesNoValueVisitor extends AttributeVisitorMixin {
|
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
export class HTMLBooleanAttributesNoValueRule
|
|
20
|
+
export class HTMLBooleanAttributesNoValueRule extends ParserRule {
|
|
20
21
|
name = "html-boolean-attributes-no-value"
|
|
21
22
|
|
|
22
|
-
check(
|
|
23
|
-
const visitor = new BooleanAttributesNoValueVisitor(this.name)
|
|
24
|
-
visitor.visit(
|
|
23
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
24
|
+
const visitor = new BooleanAttributesNoValueVisitor(this.name, context)
|
|
25
|
+
visitor.visit(result.value)
|
|
25
26
|
return visitor.offenses
|
|
26
27
|
}
|
|
27
28
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BaseRuleVisitor, getTagName, hasAttribute } from "./rule-utils.js"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { HTMLOpenTagNode, HTMLSelfCloseTagNode, ParseResult } from "@herb-tools/core"
|
|
5
6
|
|
|
6
7
|
class ImgRequireAltVisitor extends BaseRuleVisitor {
|
|
7
8
|
visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
|
|
@@ -31,12 +32,12 @@ class ImgRequireAltVisitor extends BaseRuleVisitor {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
export class HTMLImgRequireAltRule
|
|
35
|
+
export class HTMLImgRequireAltRule extends ParserRule {
|
|
35
36
|
name = "html-img-require-alt"
|
|
36
37
|
|
|
37
|
-
check(
|
|
38
|
-
const visitor = new ImgRequireAltVisitor(this.name)
|
|
39
|
-
visitor.visit(
|
|
38
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
39
|
+
const visitor = new ImgRequireAltVisitor(this.name, context)
|
|
40
|
+
visitor.visit(result.value)
|
|
40
41
|
return visitor.offenses
|
|
41
42
|
}
|
|
42
43
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BaseRuleVisitor, isInlineElement, isBlockElement } from "./rule-utils.js"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { HTMLOpenTagNode, HTMLElementNode, ParseResult } from "@herb-tools/core"
|
|
5
6
|
|
|
6
7
|
class BlockInsideInlineVisitor extends BaseRuleVisitor {
|
|
7
8
|
private inlineStack: string[] = []
|
|
@@ -18,7 +19,7 @@ class BlockInsideInlineVisitor extends BaseRuleVisitor {
|
|
|
18
19
|
return { isInline, isBlock, isUnknown }
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
private
|
|
22
|
+
private addOffenseMessage(tagName: string, isBlock: boolean, openTag: HTMLOpenTagNode): void {
|
|
22
23
|
const parentInline = this.inlineStack[this.inlineStack.length - 1]
|
|
23
24
|
const elementType = isBlock ? "Block-level" : "Unknown"
|
|
24
25
|
|
|
@@ -61,7 +62,7 @@ class BlockInsideInlineVisitor extends BaseRuleVisitor {
|
|
|
61
62
|
const { isInline, isBlock, isUnknown } = this.getElementType(tagName)
|
|
62
63
|
|
|
63
64
|
if ((isBlock || isUnknown) && this.inlineStack.length > 0) {
|
|
64
|
-
this.
|
|
65
|
+
this.addOffenseMessage(tagName, isBlock, openTag)
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
if (isInline) {
|
|
@@ -73,12 +74,12 @@ class BlockInsideInlineVisitor extends BaseRuleVisitor {
|
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
export class HTMLNoBlockInsideInlineRule
|
|
77
|
+
export class HTMLNoBlockInsideInlineRule extends ParserRule {
|
|
77
78
|
name = "html-no-block-inside-inline"
|
|
78
79
|
|
|
79
|
-
check(
|
|
80
|
-
const visitor = new BlockInsideInlineVisitor(this.name)
|
|
81
|
-
visitor.visit(
|
|
80
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
81
|
+
const visitor = new BlockInsideInlineVisitor(this.name, context)
|
|
82
|
+
visitor.visit(result.value)
|
|
82
83
|
return visitor.offenses
|
|
83
84
|
}
|
|
84
85
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BaseRuleVisitor, forEachAttribute } from "./rule-utils.js"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { HTMLOpenTagNode, HTMLSelfCloseTagNode, HTMLAttributeNameNode, ParseResult } from "@herb-tools/core"
|
|
5
6
|
|
|
6
7
|
class NoDuplicateAttributesVisitor extends BaseRuleVisitor {
|
|
7
8
|
visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
|
|
@@ -48,12 +49,12 @@ class NoDuplicateAttributesVisitor extends BaseRuleVisitor {
|
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
export class HTMLNoDuplicateAttributesRule
|
|
52
|
+
export class HTMLNoDuplicateAttributesRule extends ParserRule {
|
|
52
53
|
name = "html-no-duplicate-attributes"
|
|
53
54
|
|
|
54
|
-
check(
|
|
55
|
-
const visitor = new NoDuplicateAttributesVisitor(this.name)
|
|
56
|
-
visitor.visit(
|
|
55
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
56
|
+
const visitor = new NoDuplicateAttributesVisitor(this.name, context)
|
|
57
|
+
visitor.visit(result.value)
|
|
57
58
|
return visitor.offenses
|
|
58
59
|
}
|
|
59
60
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AttributeVisitorMixin } from "./rule-utils"
|
|
2
|
-
|
|
3
|
-
import type { Node } from "@herb-tools/core"
|
|
4
|
-
import type { LintOffense,
|
|
2
|
+
import { ParserRule } from "../types"
|
|
3
|
+
import type { ParseResult, Node } from "@herb-tools/core"
|
|
4
|
+
import type { LintOffense, LintContext } from "../types"
|
|
5
5
|
|
|
6
6
|
class NoDuplicateIdsVisitor extends AttributeVisitorMixin {
|
|
7
7
|
private documentIds: Set<string> = new Set<string>()
|
|
@@ -26,13 +26,13 @@ class NoDuplicateIdsVisitor extends AttributeVisitorMixin {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export class HTMLNoDuplicateIdsRule
|
|
29
|
+
export class HTMLNoDuplicateIdsRule extends ParserRule {
|
|
30
30
|
name = "html-no-duplicate-ids"
|
|
31
31
|
|
|
32
|
-
check(
|
|
33
|
-
const visitor = new NoDuplicateIdsVisitor(this.name)
|
|
32
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
33
|
+
const visitor = new NoDuplicateIdsVisitor(this.name, context)
|
|
34
34
|
|
|
35
|
-
visitor.visit(
|
|
35
|
+
visitor.visit(result.value)
|
|
36
36
|
|
|
37
37
|
return visitor.offenses
|
|
38
38
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BaseRuleVisitor, getTagName, getAttributes, findAttributeByName, getAttributeValue, HEADING_TAGS } from "./rule-utils.js"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { HTMLElementNode, HTMLOpenTagNode, HTMLSelfCloseTagNode, ParseResult, LiteralNode, HTMLTextNode } from "@herb-tools/core"
|
|
5
6
|
|
|
6
7
|
class NoEmptyHeadingsVisitor extends BaseRuleVisitor {
|
|
7
8
|
visitHTMLElementNode(node: HTMLElementNode): void {
|
|
@@ -174,12 +175,12 @@ class NoEmptyHeadingsVisitor extends BaseRuleVisitor {
|
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
177
|
|
|
177
|
-
export class HTMLNoEmptyHeadingsRule
|
|
178
|
+
export class HTMLNoEmptyHeadingsRule extends ParserRule {
|
|
178
179
|
name = "html-no-empty-headings"
|
|
179
180
|
|
|
180
|
-
check(
|
|
181
|
-
const visitor = new NoEmptyHeadingsVisitor(this.name)
|
|
182
|
-
visitor.visit(
|
|
181
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
182
|
+
const visitor = new NoEmptyHeadingsVisitor(this.name, context)
|
|
183
|
+
visitor.visit(result.value)
|
|
183
184
|
return visitor.offenses
|
|
184
185
|
}
|
|
185
186
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BaseRuleVisitor, getTagName } from "./rule-utils.js"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { HTMLOpenTagNode, HTMLElementNode, ParseResult } from "@herb-tools/core"
|
|
5
6
|
|
|
6
7
|
class NestedLinkVisitor extends BaseRuleVisitor {
|
|
7
8
|
private linkStack: HTMLOpenTagNode[] = []
|
|
@@ -54,12 +55,12 @@ class NestedLinkVisitor extends BaseRuleVisitor {
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
export class HTMLNoNestedLinksRule
|
|
58
|
+
export class HTMLNoNestedLinksRule extends ParserRule {
|
|
58
59
|
name = "html-no-nested-links"
|
|
59
60
|
|
|
60
|
-
check(
|
|
61
|
-
const visitor = new NestedLinkVisitor(this.name)
|
|
62
|
-
visitor.visit(
|
|
61
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
62
|
+
const visitor = new NestedLinkVisitor(this.name, context)
|
|
63
|
+
visitor.visit(result.value)
|
|
63
64
|
return visitor.offenses
|
|
64
65
|
}
|
|
65
66
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BaseRuleVisitor } from "./rule-utils.js"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { HTMLElementNode, HTMLOpenTagNode, HTMLCloseTagNode, HTMLSelfCloseTagNode, ParseResult } from "@herb-tools/core"
|
|
5
6
|
|
|
6
7
|
class TagNameLowercaseVisitor extends BaseRuleVisitor {
|
|
7
8
|
visitHTMLElementNode(node: HTMLElementNode): void {
|
|
@@ -54,12 +55,12 @@ class TagNameLowercaseVisitor extends BaseRuleVisitor {
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
export class HTMLTagNameLowercaseRule
|
|
58
|
+
export class HTMLTagNameLowercaseRule extends ParserRule {
|
|
58
59
|
name = "html-tag-name-lowercase"
|
|
59
60
|
|
|
60
|
-
check(
|
|
61
|
-
const visitor = new TagNameLowercaseVisitor(this.name)
|
|
62
|
-
visitor.visit(
|
|
61
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
62
|
+
const visitor = new TagNameLowercaseVisitor(this.name, context)
|
|
63
|
+
visitor.visit(result.value)
|
|
63
64
|
return visitor.offenses
|
|
64
65
|
}
|
|
65
66
|
}
|
package/src/rules/index.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export * from "./erb-no-empty-tags.js"
|
|
2
2
|
export * from "./erb-no-output-control-flow.js"
|
|
3
|
+
export * from "./erb-prefer-image-tag-helper.js"
|
|
4
|
+
export * from "./erb-requires-trailing-newline.js"
|
|
3
5
|
export * from "./html-anchor-require-href.js"
|
|
6
|
+
export * from "./html-aria-level-must-be-valid.js"
|
|
4
7
|
export * from "./html-aria-role-heading-requires-level.js"
|
|
5
8
|
export * from "./html-aria-role-must-be-valid.js"
|
|
6
9
|
export * from "./html-attribute-double-quotes.js"
|