@herb-tools/linter 0.8.6 → 0.8.7
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 +28 -2
- package/dist/herb-lint.js +37406 -41271
- package/dist/herb-lint.js.map +1 -1
- package/dist/index.cjs +92 -2076
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +92 -2076
- package/dist/index.js.map +1 -1
- package/dist/loader.cjs +7808 -5610
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.js +7808 -5610
- package/dist/loader.js.map +1 -1
- package/dist/package.json +9 -8
- package/dist/src/cli/argument-parser.js +14 -1
- package/dist/src/cli/argument-parser.js.map +1 -1
- package/dist/src/cli.js +12 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/linter.js +2 -2
- package/dist/src/linter.js.map +1 -1
- package/dist/src/rules/html-no-duplicate-attributes.js +91 -21
- package/dist/src/rules/html-no-duplicate-attributes.js.map +1 -1
- package/dist/types/cli/argument-parser.d.ts +2 -0
- package/dist/types/src/cli/argument-parser.d.ts +2 -0
- package/package.json +9 -8
- package/src/cli/argument-parser.ts +16 -1
- package/src/cli.ts +17 -4
- package/src/linter.ts +2 -2
- package/src/rules/html-no-duplicate-attributes.ts +141 -26
|
@@ -1,48 +1,163 @@
|
|
|
1
|
-
import { ParserRule } from "../types.js"
|
|
2
|
-
import {
|
|
1
|
+
import { ParserRule, BaseAutofixContext } from "../types.js"
|
|
2
|
+
import { ControlFlowTrackingVisitor, ControlFlowType, getAttributeName } from "./rule-utils.js"
|
|
3
3
|
|
|
4
4
|
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
5
|
-
import type { HTMLOpenTagNode, HTMLAttributeNode, ParseResult } from "@herb-tools/core"
|
|
5
|
+
import type { HTMLOpenTagNode, HTMLAttributeNode, ParseResult, Location } from "@herb-tools/core"
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
interface ControlFlowState {
|
|
8
|
+
previousBranchAttributes: Set<string>
|
|
9
|
+
previousControlFlowAttributes: Set<string>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface BranchState {
|
|
13
|
+
previousBranchAttributes: Set<string>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class NoDuplicateAttributesVisitor extends ControlFlowTrackingVisitor<
|
|
17
|
+
BaseAutofixContext,
|
|
18
|
+
ControlFlowState,
|
|
19
|
+
BranchState
|
|
20
|
+
> {
|
|
21
|
+
private tagAttributes = new Set<string>()
|
|
22
|
+
private currentBranchAttributes = new Set<string>()
|
|
23
|
+
private controlFlowAttributes = new Set<string>()
|
|
9
24
|
|
|
10
25
|
visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
|
|
11
|
-
this.
|
|
26
|
+
this.tagAttributes = new Set()
|
|
27
|
+
this.currentBranchAttributes = new Set()
|
|
28
|
+
this.controlFlowAttributes = new Set()
|
|
12
29
|
super.visitHTMLOpenTagNode(node)
|
|
13
|
-
this.reportDuplicates()
|
|
14
30
|
}
|
|
15
31
|
|
|
32
|
+
visitHTMLAttributeNode(node: HTMLAttributeNode): void {
|
|
33
|
+
this.checkAttribute(node)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected onEnterControlFlow(_controlFlowType: ControlFlowType, wasAlreadyInControlFlow: boolean): ControlFlowState {
|
|
37
|
+
const stateToRestore: ControlFlowState = {
|
|
38
|
+
previousBranchAttributes: this.currentBranchAttributes,
|
|
39
|
+
previousControlFlowAttributes: this.controlFlowAttributes,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.currentBranchAttributes = new Set()
|
|
43
|
+
|
|
44
|
+
if (!wasAlreadyInControlFlow) {
|
|
45
|
+
this.controlFlowAttributes = new Set()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return stateToRestore
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected onExitControlFlow(
|
|
52
|
+
controlFlowType: ControlFlowType,
|
|
53
|
+
wasAlreadyInControlFlow: boolean,
|
|
54
|
+
stateToRestore: ControlFlowState,
|
|
55
|
+
): void {
|
|
56
|
+
if (controlFlowType === ControlFlowType.CONDITIONAL && !wasAlreadyInControlFlow) {
|
|
57
|
+
this.controlFlowAttributes.forEach((attr) => this.tagAttributes.add(attr))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.currentBranchAttributes = stateToRestore.previousBranchAttributes
|
|
61
|
+
this.controlFlowAttributes = stateToRestore.previousControlFlowAttributes
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected onEnterBranch(): BranchState {
|
|
65
|
+
const stateToRestore: BranchState = {
|
|
66
|
+
previousBranchAttributes: this.currentBranchAttributes,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (this.isInControlFlow) {
|
|
70
|
+
this.currentBranchAttributes = new Set()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return stateToRestore
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
protected onExitBranch(_stateToRestore: BranchState): void {}
|
|
77
|
+
|
|
78
|
+
private checkAttribute(attributeNode: HTMLAttributeNode): void {
|
|
79
|
+
const identifier = getAttributeName(attributeNode)
|
|
80
|
+
if (!identifier) return
|
|
16
81
|
|
|
17
|
-
|
|
18
|
-
this.trackAttributeName(attributeName, attributeNode)
|
|
82
|
+
this.processAttributeDuplicate(identifier, attributeNode)
|
|
19
83
|
}
|
|
20
84
|
|
|
21
|
-
|
|
22
|
-
this.
|
|
85
|
+
private processAttributeDuplicate(identifier: string, attributeNode: HTMLAttributeNode): void {
|
|
86
|
+
if (!this.isInControlFlow) {
|
|
87
|
+
this.handleHTMLAttribute(identifier, attributeNode)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.currentControlFlowType === ControlFlowType.LOOP) {
|
|
92
|
+
this.handleLoopAttribute(identifier, attributeNode)
|
|
93
|
+
} else {
|
|
94
|
+
this.handleConditionalAttribute(identifier, attributeNode)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.currentBranchAttributes.add(identifier)
|
|
23
98
|
}
|
|
24
99
|
|
|
25
|
-
private
|
|
26
|
-
if (
|
|
27
|
-
this.
|
|
100
|
+
private handleHTMLAttribute(identifier: string, attributeNode: HTMLAttributeNode): void {
|
|
101
|
+
if (this.tagAttributes.has(identifier)) {
|
|
102
|
+
this.addDuplicateAttributeOffense(identifier, attributeNode.name!.location)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.tagAttributes.add(identifier)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private handleLoopAttribute(identifier: string, attributeNode: HTMLAttributeNode): void {
|
|
109
|
+
if (this.currentBranchAttributes.has(identifier)) {
|
|
110
|
+
this.addSameLoopIterationOffense(identifier, attributeNode.name!.location)
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (this.tagAttributes.has(identifier)) {
|
|
115
|
+
this.addDuplicateAttributeOffense(identifier, attributeNode.name!.location)
|
|
116
|
+
return
|
|
28
117
|
}
|
|
29
118
|
|
|
30
|
-
this.
|
|
119
|
+
this.addLoopWillDuplicateOffense(identifier, attributeNode.name!.location)
|
|
31
120
|
}
|
|
32
121
|
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
122
|
+
private handleConditionalAttribute(identifier: string, attributeNode: HTMLAttributeNode): void {
|
|
123
|
+
if (this.currentBranchAttributes.has(identifier)) {
|
|
124
|
+
this.addSameBranchOffense(identifier, attributeNode.name!.location)
|
|
125
|
+
return
|
|
126
|
+
}
|
|
38
127
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
attributeNode.name!.location,
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
128
|
+
if (this.tagAttributes.has(identifier)) {
|
|
129
|
+
this.addDuplicateAttributeOffense(identifier, attributeNode.name!.location)
|
|
45
130
|
}
|
|
131
|
+
|
|
132
|
+
this.controlFlowAttributes.add(identifier)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private addDuplicateAttributeOffense(identifier: string, location: Location): void {
|
|
136
|
+
this.addOffense(
|
|
137
|
+
`Duplicate attribute \`${identifier}\`. Browsers only use the first occurrence and ignore duplicate attributes. Remove the duplicate or merge the values.`,
|
|
138
|
+
location,
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private addSameLoopIterationOffense(identifier: string, location: Location): void {
|
|
143
|
+
this.addOffense(
|
|
144
|
+
`Duplicate attribute \`${identifier}\` in same loop iteration. Each iteration will produce an element with duplicate attributes. Remove one or merge the values.`,
|
|
145
|
+
location,
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private addLoopWillDuplicateOffense(identifier: string, location: Location): void {
|
|
150
|
+
this.addOffense(
|
|
151
|
+
`Attribute \`${identifier}\` inside loop will appear multiple times on this element. Use a dynamic attribute name like \`${identifier}-<%= index %>\` or move the attribute outside the loop.`,
|
|
152
|
+
location,
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private addSameBranchOffense(identifier: string, location: Location): void {
|
|
157
|
+
this.addOffense(
|
|
158
|
+
`Duplicate attribute \`${identifier}\` in same branch. This branch will produce an element with duplicate attributes. Remove one or merge the values.`,
|
|
159
|
+
location,
|
|
160
|
+
)
|
|
46
161
|
}
|
|
47
162
|
}
|
|
48
163
|
|