@herb-tools/linter 0.8.6 → 0.8.8
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 +54 -2
- package/dist/herb-lint.js +17157 -31275
- package/dist/herb-lint.js.map +1 -1
- package/dist/index.cjs +473 -2113
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +468 -2115
- package/dist/index.js.map +1 -1
- package/dist/loader.cjs +6868 -11350
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.js +6862 -11351
- package/dist/loader.js.map +1 -1
- package/dist/package.json +9 -8
- package/dist/src/cli/argument-parser.js +18 -2
- package/dist/src/cli/argument-parser.js.map +1 -1
- package/dist/src/cli/file-processor.js +1 -1
- package/dist/src/cli/file-processor.js.map +1 -1
- package/dist/src/cli.js +25 -10
- package/dist/src/cli.js.map +1 -1
- package/dist/src/custom-rule-loader.js +2 -2
- package/dist/src/custom-rule-loader.js.map +1 -1
- package/dist/src/linter.js +16 -3
- package/dist/src/linter.js.map +1 -1
- package/dist/src/rules/erb-strict-locals-comment-syntax.js +206 -0
- package/dist/src/rules/erb-strict-locals-comment-syntax.js.map +1 -0
- package/dist/src/rules/erb-strict-locals-required.js +38 -0
- package/dist/src/rules/erb-strict-locals-required.js.map +1 -0
- package/dist/src/rules/file-utils.js +21 -0
- package/dist/src/rules/file-utils.js.map +1 -0
- package/dist/src/rules/html-head-only-elements.js +2 -0
- package/dist/src/rules/html-head-only-elements.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/src/rules/html-no-empty-headings.js +22 -36
- package/dist/src/rules/html-no-empty-headings.js.map +1 -1
- package/dist/src/rules/index.js +4 -0
- package/dist/src/rules/index.js.map +1 -1
- package/dist/src/rules/string-utils.js +72 -0
- package/dist/src/rules/string-utils.js.map +1 -0
- package/dist/src/rules.js +4 -0
- package/dist/src/rules.js.map +1 -1
- package/dist/src/types.js +6 -0
- package/dist/src/types.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/cli/argument-parser.d.ts +3 -0
- package/dist/types/cli/file-processor.d.ts +1 -0
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/linter.d.ts +5 -1
- package/dist/types/rules/erb-strict-locals-comment-syntax.d.ts +9 -0
- package/dist/types/rules/erb-strict-locals-required.d.ts +9 -0
- package/dist/types/rules/file-utils.d.ts +13 -0
- package/dist/types/rules/index.d.ts +4 -0
- package/dist/types/rules/string-utils.d.ts +15 -0
- package/dist/types/src/cli/argument-parser.d.ts +3 -0
- package/dist/types/src/cli/file-processor.d.ts +1 -0
- package/dist/types/src/cli.d.ts +1 -1
- package/dist/types/src/linter.d.ts +5 -1
- package/dist/types/src/rules/erb-strict-locals-comment-syntax.d.ts +9 -0
- package/dist/types/src/rules/erb-strict-locals-required.d.ts +9 -0
- package/dist/types/src/rules/file-utils.d.ts +13 -0
- package/dist/types/src/rules/index.d.ts +4 -0
- package/dist/types/src/rules/string-utils.d.ts +15 -0
- package/dist/types/src/types.d.ts +6 -0
- package/dist/types/types.d.ts +6 -0
- package/docs/rules/README.md +1 -0
- package/docs/rules/erb-strict-locals-comment-syntax.md +153 -0
- package/docs/rules/erb-strict-locals-required.md +107 -0
- package/package.json +9 -8
- package/src/cli/argument-parser.ts +21 -2
- package/src/cli/file-processor.ts +2 -1
- package/src/cli.ts +34 -11
- package/src/custom-rule-loader.ts +2 -2
- package/src/linter.ts +19 -3
- package/src/rules/erb-strict-locals-comment-syntax.ts +274 -0
- package/src/rules/erb-strict-locals-required.ts +52 -0
- package/src/rules/file-utils.ts +23 -0
- package/src/rules/html-head-only-elements.ts +1 -0
- package/src/rules/html-no-duplicate-attributes.ts +141 -26
- package/src/rules/html-no-empty-headings.ts +21 -44
- package/src/rules/index.ts +4 -0
- package/src/rules/string-utils.ts +72 -0
- package/src/rules.ts +4 -0
- package/src/types.ts +6 -0
|
@@ -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
|
|
|
@@ -1,31 +1,29 @@
|
|
|
1
1
|
import { BaseRuleVisitor, getTagName, getAttributes, findAttributeByName, getAttributeValue, HEADING_TAGS } from "./rule-utils.js"
|
|
2
|
+
import { isHTMLOpenTagNode, isLiteralNode, isHTMLTextNode, isHTMLElementNode } from "@herb-tools/core"
|
|
2
3
|
|
|
3
4
|
import { ParserRule } from "../types.js"
|
|
4
5
|
|
|
5
6
|
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
6
|
-
import type { HTMLElementNode, HTMLOpenTagNode, ParseResult
|
|
7
|
+
import type { HTMLElementNode, HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
|
|
7
8
|
|
|
8
9
|
class NoEmptyHeadingsVisitor extends BaseRuleVisitor {
|
|
9
10
|
visitHTMLElementNode(node: HTMLElementNode): void {
|
|
11
|
+
const tagName = getTagName(node.open_tag)?.toLowerCase()
|
|
12
|
+
if (tagName === "template") return
|
|
13
|
+
|
|
10
14
|
this.checkHeadingElement(node)
|
|
11
15
|
super.visitHTMLElementNode(node)
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
|
|
15
18
|
private checkHeadingElement(node: HTMLElementNode): void {
|
|
16
|
-
if (!node.open_tag
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
+
if (!node.open_tag) return
|
|
20
|
+
if (!isHTMLOpenTagNode(node.open_tag)) return
|
|
19
21
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (!tagName) {
|
|
24
|
-
return
|
|
25
|
-
}
|
|
22
|
+
const tagName = getTagName(node.open_tag)
|
|
23
|
+
if (!tagName) return
|
|
26
24
|
|
|
27
25
|
const isStandardHeading = HEADING_TAGS.has(tagName)
|
|
28
|
-
const isAriaHeading = this.hasHeadingRole(
|
|
26
|
+
const isAriaHeading = this.hasHeadingRole(node.open_tag)
|
|
29
27
|
|
|
30
28
|
if (!isStandardHeading && !isAriaHeading) {
|
|
31
29
|
return
|
|
@@ -43,7 +41,6 @@ class NoEmptyHeadingsVisitor extends BaseRuleVisitor {
|
|
|
43
41
|
}
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
|
|
47
44
|
private isEmptyHeading(node: HTMLElementNode): boolean {
|
|
48
45
|
if (!node.body || node.body.length === 0) {
|
|
49
46
|
return true
|
|
@@ -52,24 +49,13 @@ class NoEmptyHeadingsVisitor extends BaseRuleVisitor {
|
|
|
52
49
|
let hasAccessibleContent = false
|
|
53
50
|
|
|
54
51
|
for (const child of node.body) {
|
|
55
|
-
if (child
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (literalNode.content.trim().length > 0) {
|
|
52
|
+
if (isLiteralNode(child) || isHTMLTextNode(child)) {
|
|
53
|
+
if (child.content.trim().length > 0) {
|
|
59
54
|
hasAccessibleContent = true
|
|
60
55
|
break
|
|
61
56
|
}
|
|
62
|
-
} else if (child
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (textNode.content.trim().length > 0) {
|
|
66
|
-
hasAccessibleContent = true
|
|
67
|
-
break
|
|
68
|
-
}
|
|
69
|
-
} else if (child.type === "AST_HTML_ELEMENT_NODE") {
|
|
70
|
-
const elementNode = child as HTMLElementNode
|
|
71
|
-
|
|
72
|
-
if (this.isElementAccessible(elementNode)) {
|
|
57
|
+
} else if (isHTMLElementNode(child)) {
|
|
58
|
+
if (this.isElementAccessible(child)) {
|
|
73
59
|
hasAccessibleContent = true
|
|
74
60
|
break
|
|
75
61
|
}
|
|
@@ -95,12 +81,10 @@ class NoEmptyHeadingsVisitor extends BaseRuleVisitor {
|
|
|
95
81
|
}
|
|
96
82
|
|
|
97
83
|
private isElementAccessible(node: HTMLElementNode): boolean {
|
|
98
|
-
if (!node.open_tag
|
|
99
|
-
|
|
100
|
-
}
|
|
84
|
+
if (!node.open_tag) return true
|
|
85
|
+
if (!isHTMLOpenTagNode(node.open_tag)) return true
|
|
101
86
|
|
|
102
|
-
const
|
|
103
|
-
const attributes = getAttributes(openTag)
|
|
87
|
+
const attributes = getAttributes(node.open_tag)
|
|
104
88
|
const ariaHiddenAttribute = findAttributeByName(attributes, "aria-hidden")
|
|
105
89
|
|
|
106
90
|
if (ariaHiddenAttribute) {
|
|
@@ -116,19 +100,12 @@ class NoEmptyHeadingsVisitor extends BaseRuleVisitor {
|
|
|
116
100
|
}
|
|
117
101
|
|
|
118
102
|
for (const child of node.body) {
|
|
119
|
-
if (child
|
|
120
|
-
|
|
121
|
-
if (literalNode.content.trim().length > 0) {
|
|
122
|
-
return true
|
|
123
|
-
}
|
|
124
|
-
} else if (child.type === "AST_HTML_TEXT_NODE") {
|
|
125
|
-
const textNode = child as HTMLTextNode
|
|
126
|
-
if (textNode.content.trim().length > 0) {
|
|
103
|
+
if (isLiteralNode(child) || isHTMLTextNode(child)) {
|
|
104
|
+
if (child.content.trim().length > 0) {
|
|
127
105
|
return true
|
|
128
106
|
}
|
|
129
|
-
} else if (child
|
|
130
|
-
|
|
131
|
-
if (this.isElementAccessible(elementNode)) {
|
|
107
|
+
} else if (isHTMLElementNode(child)) {
|
|
108
|
+
if (this.isElementAccessible(child)) {
|
|
132
109
|
return true
|
|
133
110
|
}
|
|
134
111
|
} else {
|
package/src/rules/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from "./rule-utils.js"
|
|
2
|
+
export * from "./file-utils.js"
|
|
3
|
+
export * from "./string-utils.js"
|
|
2
4
|
export * from "./herb-disable-comment-base.js"
|
|
3
5
|
|
|
4
6
|
export * from "./erb-comment-syntax.js"
|
|
@@ -12,6 +14,8 @@ export * from "./erb-prefer-image-tag-helper.js"
|
|
|
12
14
|
export * from "./erb-require-trailing-newline.js"
|
|
13
15
|
export * from "./erb-require-whitespace-inside-tags.js"
|
|
14
16
|
export * from "./erb-right-trim.js"
|
|
17
|
+
export * from "./erb-strict-locals-comment-syntax.js"
|
|
18
|
+
export * from "./erb-strict-locals-required.js"
|
|
15
19
|
|
|
16
20
|
export * from "./herb-disable-comment-valid-rule-name.js"
|
|
17
21
|
export * from "./herb-disable-comment-no-redundant-all.js"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if parentheses in a string are balanced
|
|
3
|
+
* Returns false if there are more closing parens than opening at any point
|
|
4
|
+
*/
|
|
5
|
+
export function hasBalancedParentheses(content: string): boolean {
|
|
6
|
+
let depth = 0
|
|
7
|
+
|
|
8
|
+
for (const char of content) {
|
|
9
|
+
if (char === "(") depth++
|
|
10
|
+
if (char === ")") depth--
|
|
11
|
+
if (depth < 0) return false
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return depth === 0
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Splits a string by commas at the top level only
|
|
19
|
+
* Respects nested parentheses, brackets, braces, and strings
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* splitByTopLevelComma("a, b, c") // ["a", " b", " c"]
|
|
23
|
+
* splitByTopLevelComma("a, (b, c), d") // ["a", " (b, c)", " d"]
|
|
24
|
+
* splitByTopLevelComma('a, "b, c", d') // ["a", ' "b, c"', " d"]
|
|
25
|
+
*/
|
|
26
|
+
export function splitByTopLevelComma(str: string): string[] {
|
|
27
|
+
const result: string[] = []
|
|
28
|
+
|
|
29
|
+
let current = ""
|
|
30
|
+
let parenDepth = 0
|
|
31
|
+
let bracketDepth = 0
|
|
32
|
+
let braceDepth = 0
|
|
33
|
+
let inString = false
|
|
34
|
+
let stringChar = ""
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < str.length; i++) {
|
|
37
|
+
const char = str[i]
|
|
38
|
+
const previousChar = i > 0 ? str[i - 1] : ""
|
|
39
|
+
|
|
40
|
+
if ((char === '"' || char === "'") && previousChar !== "\\") {
|
|
41
|
+
if (!inString) {
|
|
42
|
+
inString = true
|
|
43
|
+
stringChar = char
|
|
44
|
+
} else if (char === stringChar) {
|
|
45
|
+
inString = false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!inString) {
|
|
50
|
+
if (char === "(") parenDepth++
|
|
51
|
+
if (char === ")") parenDepth--
|
|
52
|
+
if (char === "[") bracketDepth++
|
|
53
|
+
if (char === "]") bracketDepth--
|
|
54
|
+
if (char === "{") braceDepth++
|
|
55
|
+
if (char === "}") braceDepth--
|
|
56
|
+
|
|
57
|
+
if (char === "," && parenDepth === 0 && bracketDepth === 0 && braceDepth === 0) {
|
|
58
|
+
result.push(current)
|
|
59
|
+
current = ""
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
current += char
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (current) {
|
|
68
|
+
result.push(current)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return result
|
|
72
|
+
}
|
package/src/rules.ts
CHANGED
|
@@ -11,6 +11,8 @@ import { ERBPreferImageTagHelperRule } from "./rules/erb-prefer-image-tag-helper
|
|
|
11
11
|
import { ERBRequireTrailingNewlineRule } from "./rules/erb-require-trailing-newline.js"
|
|
12
12
|
import { ERBRequireWhitespaceRule } from "./rules/erb-require-whitespace-inside-tags.js"
|
|
13
13
|
import { ERBRightTrimRule } from "./rules/erb-right-trim.js"
|
|
14
|
+
import { ERBStrictLocalsCommentSyntaxRule } from "./rules/erb-strict-locals-comment-syntax.js"
|
|
15
|
+
import { ERBStrictLocalsRequiredRule } from "./rules/erb-strict-locals-required.js"
|
|
14
16
|
|
|
15
17
|
import { HerbDisableCommentValidRuleNameRule } from "./rules/herb-disable-comment-valid-rule-name.js"
|
|
16
18
|
import { HerbDisableCommentNoRedundantAllRule } from "./rules/herb-disable-comment-no-redundant-all.js"
|
|
@@ -67,6 +69,8 @@ export const rules: RuleClass[] = [
|
|
|
67
69
|
ERBRequireTrailingNewlineRule,
|
|
68
70
|
ERBRequireWhitespaceRule,
|
|
69
71
|
ERBRightTrimRule,
|
|
72
|
+
ERBStrictLocalsCommentSyntaxRule,
|
|
73
|
+
ERBStrictLocalsRequiredRule,
|
|
70
74
|
|
|
71
75
|
HerbDisableCommentValidRuleNameRule,
|
|
72
76
|
HerbDisableCommentNoRedundantAllRule,
|
package/src/types.ts
CHANGED
|
@@ -85,6 +85,8 @@ export abstract class ParserRule<TAutofixContext extends BaseAutofixContext = Ba
|
|
|
85
85
|
static type = "parser" as const
|
|
86
86
|
/** Indicates whether this rule supports autofix. Defaults to false. */
|
|
87
87
|
static autocorrectable = false
|
|
88
|
+
/** Indicates whether this rule supports unsafe autofix (requires --fix-unsafely). Defaults to false. */
|
|
89
|
+
static unsafeAutocorrectable = false
|
|
88
90
|
abstract name: string
|
|
89
91
|
|
|
90
92
|
get defaultConfig(): FullRuleConfig {
|
|
@@ -119,6 +121,8 @@ export abstract class LexerRule<TAutofixContext extends BaseAutofixContext = Bas
|
|
|
119
121
|
static type = "lexer" as const
|
|
120
122
|
/** Indicates whether this rule supports autofix. Defaults to false. */
|
|
121
123
|
static autocorrectable = false
|
|
124
|
+
/** Indicates whether this rule supports unsafe autofix (requires --fix-unsafely). Defaults to false. */
|
|
125
|
+
static unsafeAutocorrectable = false
|
|
122
126
|
abstract name: string
|
|
123
127
|
|
|
124
128
|
get defaultConfig(): FullRuleConfig {
|
|
@@ -176,6 +180,8 @@ export abstract class SourceRule<TAutofixContext extends BaseAutofixContext = Ba
|
|
|
176
180
|
static type = "source" as const
|
|
177
181
|
/** Indicates whether this rule supports autofix. Defaults to false. */
|
|
178
182
|
static autocorrectable = false
|
|
183
|
+
/** Indicates whether this rule supports unsafe autofix (requires --fix-unsafely). Defaults to false. */
|
|
184
|
+
static unsafeAutocorrectable = false
|
|
179
185
|
abstract name: string
|
|
180
186
|
|
|
181
187
|
get defaultConfig(): FullRuleConfig {
|