@herb-tools/linter 0.6.1 → 0.7.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 +60 -16
- package/dist/herb-lint.js +364 -181
- package/dist/herb-lint.js.map +1 -1
- package/dist/index.cjs +321 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +270 -89
- package/dist/index.js.map +1 -1
- package/dist/package.json +11 -5
- package/dist/src/cli/argument-parser.js +11 -6
- package/dist/src/cli/argument-parser.js.map +1 -1
- package/dist/src/cli/file-processor.js +5 -6
- package/dist/src/cli/file-processor.js.map +1 -1
- package/dist/src/cli/formatters/detailed-formatter.js +3 -5
- package/dist/src/cli/formatters/detailed-formatter.js.map +1 -1
- package/dist/src/cli/formatters/github-actions-formatter.js +55 -11
- package/dist/src/cli/formatters/github-actions-formatter.js.map +1 -1
- package/dist/src/cli/index.js +1 -0
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/cli/output-manager.js +23 -5
- package/dist/src/cli/output-manager.js.map +1 -1
- package/dist/src/cli/summary-reporter.js +2 -11
- package/dist/src/cli/summary-reporter.js.map +1 -1
- package/dist/src/cli.js +88 -4
- package/dist/src/cli.js.map +1 -1
- package/dist/src/default-rules.js +8 -4
- package/dist/src/default-rules.js.map +1 -1
- package/dist/src/linter.js.map +1 -1
- package/dist/src/rules/erb-prefer-image-tag-helper.js +1 -1
- package/dist/src/rules/erb-prefer-image-tag-helper.js.map +1 -1
- package/dist/src/rules/html-boolean-attributes-no-value.js +8 -8
- package/dist/src/rules/html-boolean-attributes-no-value.js.map +1 -1
- package/dist/src/rules/html-no-empty-attributes.js +56 -0
- package/dist/src/rules/html-no-empty-attributes.js.map +1 -0
- package/dist/src/rules/html-no-positive-tab-index.js +1 -1
- package/dist/src/rules/html-no-positive-tab-index.js.map +1 -1
- package/dist/src/rules/html-no-underscores-in-attribute-names.js +36 -0
- package/dist/src/rules/html-no-underscores-in-attribute-names.js.map +1 -0
- package/dist/src/rules/index.js +3 -0
- package/dist/src/rules/index.js.map +1 -1
- package/dist/src/rules/rule-utils.js +11 -7
- package/dist/src/rules/rule-utils.js.map +1 -1
- package/dist/src/rules/svg-tag-name-capitalization.js +2 -2
- package/dist/src/rules/svg-tag-name-capitalization.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 -1
- package/dist/types/cli/formatters/github-actions-formatter.d.ts +6 -1
- package/dist/types/cli/index.d.ts +1 -0
- package/dist/types/cli/output-manager.d.ts +1 -0
- package/dist/types/cli.d.ts +20 -5
- package/dist/types/linter.d.ts +7 -7
- package/dist/types/rules/html-no-empty-attributes.d.ts +7 -0
- package/dist/types/rules/html-no-underscores-in-attribute-names.d.ts +7 -0
- package/dist/types/rules/index.d.ts +3 -0
- package/dist/types/rules/rule-utils.d.ts +7 -5
- package/dist/types/src/cli/argument-parser.d.ts +2 -1
- package/dist/types/src/cli/file-processor.d.ts +6 -1
- package/dist/types/src/cli/formatters/github-actions-formatter.d.ts +6 -1
- package/dist/types/src/cli/index.d.ts +1 -0
- package/dist/types/src/cli/output-manager.d.ts +1 -0
- package/dist/types/src/cli.d.ts +20 -5
- package/dist/types/src/linter.d.ts +7 -7
- package/dist/types/src/rules/html-no-empty-attributes.d.ts +7 -0
- package/dist/types/src/rules/html-no-underscores-in-attribute-names.d.ts +7 -0
- package/dist/types/src/rules/index.d.ts +3 -0
- package/dist/types/src/rules/rule-utils.d.ts +7 -5
- package/docs/rules/README.md +2 -0
- package/docs/rules/html-img-require-alt.md +0 -2
- package/docs/rules/html-no-empty-attributes.md +77 -0
- package/docs/rules/html-no-underscores-in-attribute-names.md +45 -0
- package/package.json +11 -5
- package/src/cli/argument-parser.ts +15 -7
- package/src/cli/file-processor.ts +11 -7
- package/src/cli/formatters/detailed-formatter.ts +5 -7
- package/src/cli/formatters/github-actions-formatter.ts +64 -11
- package/src/cli/index.ts +2 -0
- package/src/cli/output-manager.ts +27 -5
- package/src/cli/summary-reporter.ts +3 -11
- package/src/cli.ts +125 -20
- package/src/default-rules.ts +8 -4
- package/src/linter.ts +6 -6
- package/src/rules/erb-no-silent-tag-in-attribute-name.ts +1 -1
- package/src/rules/erb-prefer-image-tag-helper.ts +2 -2
- package/src/rules/erb-require-whitespace-inside-tags.ts +2 -2
- package/src/rules/html-attribute-double-quotes.ts +1 -1
- package/src/rules/html-boolean-attributes-no-value.ts +9 -11
- package/src/rules/html-no-empty-attributes.ts +75 -0
- package/src/rules/html-no-positive-tab-index.ts +1 -1
- package/src/rules/html-no-underscores-in-attribute-names.ts +58 -0
- package/src/rules/html-tag-name-lowercase.ts +1 -1
- package/src/rules/index.ts +3 -0
- package/src/rules/rule-utils.ts +15 -11
- package/src/rules/svg-tag-name-capitalization.ts +2 -2
|
@@ -58,7 +58,7 @@ class RequireWhitespaceInsideTags extends BaseRuleVisitor {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
private checkOpenTagWhitespace(openTag: Token, content:string):void {
|
|
61
|
-
if (content.startsWith(" ") ||
|
|
61
|
+
if (content.startsWith(" ") || content.startsWith("\n")) {
|
|
62
62
|
return
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -70,7 +70,7 @@ class RequireWhitespaceInsideTags extends BaseRuleVisitor {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
private checkCloseTagWhitespace(closeTag: Token, content:string):void {
|
|
73
|
-
if (content.endsWith(" ") ||
|
|
73
|
+
if (content.endsWith(" ") || content.endsWith("\n")) {
|
|
74
74
|
return
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ParserRule } from "../types.js"
|
|
2
2
|
import { AttributeVisitorMixin, StaticAttributeStaticValueParams, StaticAttributeDynamicValueParams, getAttributeValueQuoteType, hasAttributeValue } from "./rule-utils.js"
|
|
3
|
-
import {
|
|
3
|
+
import { filterLiteralNodes } from "@herb-tools/core"
|
|
4
4
|
|
|
5
5
|
import type { LintOffense, LintContext } from "../types.js"
|
|
6
6
|
import type { ParseResult } from "@herb-tools/core"
|
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
import { ParserRule } from "../types.js"
|
|
2
2
|
import { AttributeVisitorMixin, StaticAttributeStaticValueParams, StaticAttributeDynamicValueParams, isBooleanAttribute, hasAttributeValue } from "./rule-utils.js"
|
|
3
|
+
import { IdentityPrinter } from "@herb-tools/printer"
|
|
3
4
|
|
|
4
5
|
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
-
import type { ParseResult } from "@herb-tools/core"
|
|
6
|
+
import type { ParseResult, HTMLAttributeNode } from "@herb-tools/core"
|
|
6
7
|
|
|
7
8
|
class BooleanAttributesNoValueVisitor extends AttributeVisitorMixin {
|
|
8
|
-
protected checkStaticAttributeStaticValue({
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
protected checkStaticAttributeStaticValue({ originalAttributeName, attributeNode }: StaticAttributeStaticValueParams) {
|
|
10
|
+
this.checkAttribute(originalAttributeName, attributeNode)
|
|
11
|
+
}
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
attributeNode.value!.location,
|
|
15
|
-
"error"
|
|
16
|
-
)
|
|
13
|
+
protected checkStaticAttributeDynamicValue({ originalAttributeName, attributeNode }: StaticAttributeDynamicValueParams) {
|
|
14
|
+
this.checkAttribute(originalAttributeName, attributeNode)
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
private checkAttribute(attributeName: string, attributeNode: HTMLAttributeNode) {
|
|
20
18
|
if (!isBooleanAttribute(attributeName)) return
|
|
21
19
|
if (!hasAttributeValue(attributeNode)) return
|
|
22
20
|
|
|
23
21
|
this.addOffense(
|
|
24
|
-
`Boolean attribute \`${
|
|
22
|
+
`Boolean attribute \`${IdentityPrinter.print(attributeNode.name)}\` should not have a value. Use \`${attributeName.toLowerCase()}\` instead of \`${IdentityPrinter.print(attributeNode)}\`.`,
|
|
25
23
|
attributeNode.value!.location,
|
|
26
24
|
"error"
|
|
27
25
|
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ParserRule } from "../types.js"
|
|
2
|
+
import { AttributeVisitorMixin, StaticAttributeStaticValueParams, DynamicAttributeStaticValueParams } from "./rule-utils.js"
|
|
3
|
+
|
|
4
|
+
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { ParseResult } from "@herb-tools/core"
|
|
6
|
+
|
|
7
|
+
// Attributes that must not have empty values
|
|
8
|
+
const RESTRICTED_ATTRIBUTES = new Set([
|
|
9
|
+
'id',
|
|
10
|
+
'class',
|
|
11
|
+
'name',
|
|
12
|
+
'for',
|
|
13
|
+
'src',
|
|
14
|
+
'href',
|
|
15
|
+
'title',
|
|
16
|
+
'data',
|
|
17
|
+
'role'
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
// Check if attribute name matches any restricted patterns
|
|
21
|
+
function isRestrictedAttribute(attributeName: string): boolean {
|
|
22
|
+
// Check direct matches
|
|
23
|
+
if (RESTRICTED_ATTRIBUTES.has(attributeName)) {
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Check for data-* attributes
|
|
28
|
+
if (attributeName.startsWith('data-')) {
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check for aria-* attributes
|
|
33
|
+
if (attributeName.startsWith('aria-')) {
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class NoEmptyAttributesVisitor extends AttributeVisitorMixin {
|
|
41
|
+
protected checkStaticAttributeStaticValue({ attributeName, attributeValue, attributeNode }: StaticAttributeStaticValueParams): void {
|
|
42
|
+
if (!isRestrictedAttribute(attributeName)) return
|
|
43
|
+
if (attributeValue.trim() !== "") return
|
|
44
|
+
|
|
45
|
+
this.addOffense(
|
|
46
|
+
`Attribute \`${attributeName}\` must not be empty. Either provide a meaningful value or remove the attribute entirely.`,
|
|
47
|
+
attributeNode.name!.location,
|
|
48
|
+
"warning"
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
protected checkDynamicAttributeStaticValue({ combinedName, attributeValue, attributeNode }: DynamicAttributeStaticValueParams): void {
|
|
53
|
+
const name = (combinedName || "").toLowerCase()
|
|
54
|
+
if (!isRestrictedAttribute(name)) return
|
|
55
|
+
if (attributeValue.trim() !== "") return
|
|
56
|
+
|
|
57
|
+
this.addOffense(
|
|
58
|
+
`Attribute \`${combinedName}\` must not be empty. Either provide a meaningful value or remove the attribute entirely.`,
|
|
59
|
+
attributeNode.name!.location,
|
|
60
|
+
"warning"
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class HTMLNoEmptyAttributesRule extends ParserRule {
|
|
66
|
+
name = "html-no-empty-attributes"
|
|
67
|
+
|
|
68
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
69
|
+
const visitor = new NoEmptyAttributesVisitor(this.name, context)
|
|
70
|
+
|
|
71
|
+
visitor.visit(result.value)
|
|
72
|
+
|
|
73
|
+
return visitor.offenses
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -12,7 +12,7 @@ class NoPositiveTabIndexVisitor extends AttributeVisitorMixin {
|
|
|
12
12
|
|
|
13
13
|
if (!isNaN(tabIndexValue) && tabIndexValue > 0) {
|
|
14
14
|
this.addOffense(
|
|
15
|
-
`Do not use positive \`tabindex\` values as they are error prone and can severely disrupt navigation experience for keyboard users. Use \`tabindex="0"\` to make an element focusable or \`tabindex
|
|
15
|
+
`Do not use positive \`tabindex\` values as they are error prone and can severely disrupt navigation experience for keyboard users. Use \`tabindex="0"\` to make an element focusable or \`tabindex="-1"\` to remove it from the tab sequence.`,
|
|
16
16
|
attributeNode.location,
|
|
17
17
|
"error"
|
|
18
18
|
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ParserRule } from "../types.js"
|
|
2
|
+
import {
|
|
3
|
+
AttributeVisitorMixin,
|
|
4
|
+
StaticAttributeStaticValueParams,
|
|
5
|
+
StaticAttributeDynamicValueParams,
|
|
6
|
+
DynamicAttributeStaticValueParams,
|
|
7
|
+
DynamicAttributeDynamicValueParams
|
|
8
|
+
} from "./rule-utils.js"
|
|
9
|
+
import { getStaticContentFromNodes } from "@herb-tools/core"
|
|
10
|
+
import { IdentityPrinter } from "@herb-tools/printer"
|
|
11
|
+
import type { LintContext, LintOffense } from "../types.js"
|
|
12
|
+
import type { ParseResult, HTMLAttributeNode } from "@herb-tools/core"
|
|
13
|
+
|
|
14
|
+
class HTMLNoUnderscoresInAttributeNamesVisitor extends AttributeVisitorMixin {
|
|
15
|
+
protected checkStaticAttributeStaticValue({ attributeName, attributeNode }: StaticAttributeStaticValueParams): void {
|
|
16
|
+
this.check(attributeName, attributeNode)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
protected checkStaticAttributeDynamicValue({ attributeName, attributeNode }: StaticAttributeDynamicValueParams): void {
|
|
20
|
+
this.check(attributeName, attributeNode)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
protected checkDynamicAttributeStaticValue({ nameNodes, attributeNode }: DynamicAttributeStaticValueParams) {
|
|
24
|
+
const attributeName = getStaticContentFromNodes(nameNodes)
|
|
25
|
+
|
|
26
|
+
this.check(attributeName, attributeNode)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected checkDynamicAttributeDynamicValue({ nameNodes, attributeNode }: DynamicAttributeDynamicValueParams) {
|
|
30
|
+
const attributeName = getStaticContentFromNodes(nameNodes)
|
|
31
|
+
|
|
32
|
+
this.check(attributeName, attributeNode)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private check(attributeName: string | null, attributeNode: HTMLAttributeNode): void {
|
|
36
|
+
if (!attributeName) return
|
|
37
|
+
|
|
38
|
+
if (attributeName.includes("_")) {
|
|
39
|
+
this.addOffense(
|
|
40
|
+
`Attribute \`${IdentityPrinter.print(attributeNode.name)}\` should not contain underscores. Use hyphens (-) instead.`,
|
|
41
|
+
attributeNode.value!.location,
|
|
42
|
+
"warning"
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class HTMLNoUnderscoresInAttributeNamesRule extends ParserRule {
|
|
49
|
+
name = "html-no-underscores-in-attribute-names"
|
|
50
|
+
|
|
51
|
+
check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
|
|
52
|
+
const visitor = new HTMLNoUnderscoresInAttributeNamesVisitor(this.name, context)
|
|
53
|
+
|
|
54
|
+
visitor.visit(result.value)
|
|
55
|
+
|
|
56
|
+
return visitor.offenses
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -36,7 +36,7 @@ class TagNameLowercaseVisitor extends BaseRuleVisitor {
|
|
|
36
36
|
this.checkTagName(node)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
private checkTagName(node: HTMLOpenTagNode | HTMLCloseTagNode |
|
|
39
|
+
private checkTagName(node: HTMLOpenTagNode | HTMLCloseTagNode | null): void {
|
|
40
40
|
if (!node) return
|
|
41
41
|
|
|
42
42
|
const tagName = getTagName(node)
|
package/src/rules/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from "./rule-utils.js"
|
|
1
2
|
export * from "./erb-no-empty-tags.js"
|
|
2
3
|
export * from "./erb-no-output-control-flow.js"
|
|
3
4
|
export * from "./erb-no-silent-tag-in-attribute-name.js"
|
|
@@ -20,6 +21,7 @@ export * from "./html-no-aria-hidden-on-focusable.js"
|
|
|
20
21
|
export * from "./html-no-block-inside-inline.js"
|
|
21
22
|
export * from "./html-no-duplicate-attributes.js"
|
|
22
23
|
export * from "./html-no-duplicate-ids.js"
|
|
24
|
+
export * from "./html-no-empty-attributes.js"
|
|
23
25
|
export * from "./html-no-empty-headings.js"
|
|
24
26
|
export * from "./html-no-nested-links.js"
|
|
25
27
|
export * from "./html-no-positive-tab-index.js"
|
|
@@ -27,3 +29,4 @@ export * from "./html-no-self-closing.js"
|
|
|
27
29
|
export * from "./html-no-title-attribute.js"
|
|
28
30
|
export * from "./html-tag-name-lowercase.js"
|
|
29
31
|
export * from "./svg-tag-name-capitalization.js"
|
|
32
|
+
export * from "./html-no-underscores-in-attribute-names.js"
|
package/src/rules/rule-utils.ts
CHANGED
|
@@ -24,8 +24,6 @@ import type {
|
|
|
24
24
|
Node
|
|
25
25
|
} from "@herb-tools/core"
|
|
26
26
|
|
|
27
|
-
import { IdentityPrinter } from "@herb-tools/printer"
|
|
28
|
-
|
|
29
27
|
import { DEFAULT_LINT_CONTEXT } from "../types.js"
|
|
30
28
|
|
|
31
29
|
import type * as Nodes from "@herb-tools/core"
|
|
@@ -180,11 +178,13 @@ export function getTagName(node: HTMLOpenTagNode): string | null {
|
|
|
180
178
|
* Gets the attribute name from an HTMLAttributeNode (lowercased)
|
|
181
179
|
* Returns null if the attribute name contains dynamic content (ERB)
|
|
182
180
|
*/
|
|
183
|
-
export function getAttributeName(attributeNode: HTMLAttributeNode): string | null {
|
|
181
|
+
export function getAttributeName(attributeNode: HTMLAttributeNode, lowercase = true): string | null {
|
|
184
182
|
if (attributeNode.name?.type === "AST_HTML_ATTRIBUTE_NAME_NODE") {
|
|
185
183
|
const nameNode = attributeNode.name as HTMLAttributeNameNode
|
|
186
184
|
const staticName = getStaticAttributeName(nameNode)
|
|
187
185
|
|
|
186
|
+
if (!lowercase) return staticName
|
|
187
|
+
|
|
188
188
|
return staticName ? staticName.toLowerCase() : null
|
|
189
189
|
}
|
|
190
190
|
|
|
@@ -287,7 +287,7 @@ export function getStaticAttributeValueContent(attributeNode: HTMLAttributeNode)
|
|
|
287
287
|
* Gets the attribute value content from an HTMLAttributeValueNode
|
|
288
288
|
*/
|
|
289
289
|
export function getAttributeValue(attributeNode: HTMLAttributeNode): string | null {
|
|
290
|
-
const valueNode: HTMLAttributeValueNode |
|
|
290
|
+
const valueNode: HTMLAttributeValueNode | null = attributeNode.value as HTMLAttributeValueNode
|
|
291
291
|
|
|
292
292
|
if (valueNode === null) return null
|
|
293
293
|
|
|
@@ -381,7 +381,7 @@ export function hasAttribute(node: HTMLOpenTagNode, attributeName: string): bool
|
|
|
381
381
|
/**
|
|
382
382
|
* Checks if a tag has a specific attribute
|
|
383
383
|
*/
|
|
384
|
-
export function getAttribute(node: HTMLOpenTagNode, attributeName: string): HTMLAttributeNode |
|
|
384
|
+
export function getAttribute(node: HTMLOpenTagNode, attributeName: string): HTMLAttributeNode | null {
|
|
385
385
|
const attributes = getAttributes(node)
|
|
386
386
|
|
|
387
387
|
return findAttributeByName(attributes, attributeName)
|
|
@@ -486,6 +486,7 @@ export interface StaticAttributeStaticValueParams {
|
|
|
486
486
|
attributeName: string
|
|
487
487
|
attributeValue: string
|
|
488
488
|
attributeNode: HTMLAttributeNode
|
|
489
|
+
originalAttributeName: string
|
|
489
490
|
parentNode: HTMLOpenTagNode
|
|
490
491
|
}
|
|
491
492
|
|
|
@@ -493,6 +494,7 @@ export interface StaticAttributeDynamicValueParams {
|
|
|
493
494
|
attributeName: string
|
|
494
495
|
valueNodes: Node[]
|
|
495
496
|
attributeNode: HTMLAttributeNode
|
|
497
|
+
originalAttributeName: string
|
|
496
498
|
parentNode: HTMLOpenTagNode
|
|
497
499
|
combinedValue?: string | null
|
|
498
500
|
}
|
|
@@ -632,6 +634,7 @@ export abstract class AttributeVisitorMixin extends BaseRuleVisitor {
|
|
|
632
634
|
private checkAttributesOnNode(node: HTMLOpenTagNode): void {
|
|
633
635
|
forEachAttribute(node, (attributeNode) => {
|
|
634
636
|
const staticAttributeName = getAttributeName(attributeNode)
|
|
637
|
+
const originalAttributeName = getAttributeName(attributeNode, false) || ""
|
|
635
638
|
const isDynamicName = hasDynamicAttributeName(attributeNode)
|
|
636
639
|
const staticAttributeValue = getStaticAttributeValue(attributeNode)
|
|
637
640
|
const valueNodes = getAttributeValueNodes(attributeNode)
|
|
@@ -643,16 +646,17 @@ export abstract class AttributeVisitorMixin extends BaseRuleVisitor {
|
|
|
643
646
|
attributeName: staticAttributeName,
|
|
644
647
|
attributeValue: staticAttributeValue,
|
|
645
648
|
attributeNode,
|
|
649
|
+
originalAttributeName,
|
|
646
650
|
parentNode: node
|
|
647
651
|
})
|
|
648
652
|
} else if (staticAttributeName && isEffectivelyStaticValue && !hasOutputERB) {
|
|
649
653
|
const validatableContent = getValidatableStaticContent(valueNodes) || ""
|
|
650
654
|
|
|
651
|
-
this.checkStaticAttributeStaticValue({ attributeName: staticAttributeName, attributeValue: validatableContent, attributeNode, parentNode: node })
|
|
655
|
+
this.checkStaticAttributeStaticValue({ attributeName: staticAttributeName, attributeValue: validatableContent, attributeNode, originalAttributeName, parentNode: node })
|
|
652
656
|
} else if (staticAttributeName && hasOutputERB) {
|
|
653
657
|
const combinedValue = getAttributeValue(attributeNode)
|
|
654
658
|
|
|
655
|
-
this.checkStaticAttributeDynamicValue({ attributeName: staticAttributeName, valueNodes, attributeNode, parentNode: node, combinedValue })
|
|
659
|
+
this.checkStaticAttributeDynamicValue({ attributeName: staticAttributeName, valueNodes, attributeNode, parentNode: node, originalAttributeName, combinedValue })
|
|
656
660
|
} else if (isDynamicName && staticAttributeValue !== null) {
|
|
657
661
|
const nameNode = attributeNode.name as HTMLAttributeNameNode
|
|
658
662
|
const nameNodes = nameNode.children || []
|
|
@@ -673,28 +677,28 @@ export abstract class AttributeVisitorMixin extends BaseRuleVisitor {
|
|
|
673
677
|
/**
|
|
674
678
|
* Static attribute name with static value: class="container"
|
|
675
679
|
*/
|
|
676
|
-
protected checkStaticAttributeStaticValue(
|
|
680
|
+
protected checkStaticAttributeStaticValue(_params: StaticAttributeStaticValueParams): void {
|
|
677
681
|
// Default implementation does nothing
|
|
678
682
|
}
|
|
679
683
|
|
|
680
684
|
/**
|
|
681
685
|
* Static attribute name with dynamic value: class="<%= css_class %>"
|
|
682
686
|
*/
|
|
683
|
-
protected checkStaticAttributeDynamicValue(
|
|
687
|
+
protected checkStaticAttributeDynamicValue(_params: StaticAttributeDynamicValueParams): void {
|
|
684
688
|
// Default implementation does nothing
|
|
685
689
|
}
|
|
686
690
|
|
|
687
691
|
/**
|
|
688
692
|
* Dynamic attribute name with static value: data-<%= key %>="foo"
|
|
689
693
|
*/
|
|
690
|
-
protected checkDynamicAttributeStaticValue(
|
|
694
|
+
protected checkDynamicAttributeStaticValue(_params: DynamicAttributeStaticValueParams): void {
|
|
691
695
|
// Default implementation does nothing
|
|
692
696
|
}
|
|
693
697
|
|
|
694
698
|
/**
|
|
695
699
|
* Dynamic attribute name with dynamic value: data-<%= key %>="<%= value %>"
|
|
696
700
|
*/
|
|
697
|
-
protected checkDynamicAttributeDynamicValue(
|
|
701
|
+
protected checkDynamicAttributeDynamicValue(_params: DynamicAttributeDynamicValueParams): void {
|
|
698
702
|
// Default implementation does nothing
|
|
699
703
|
}
|
|
700
704
|
}
|
|
@@ -44,8 +44,8 @@ class SVGTagNameCapitalizationVisitor extends BaseRuleVisitor {
|
|
|
44
44
|
if (correctCamelCase && tagName !== correctCamelCase) {
|
|
45
45
|
let type: string = node.type
|
|
46
46
|
|
|
47
|
-
if (node.type
|
|
48
|
-
if (node.type
|
|
47
|
+
if (node.type === "AST_HTML_OPEN_TAG_NODE") type = "Opening"
|
|
48
|
+
if (node.type === "AST_HTML_CLOSE_TAG_NODE") type = "Closing"
|
|
49
49
|
|
|
50
50
|
this.addOffense(
|
|
51
51
|
`${type} SVG tag name \`${tagName}\` should use proper capitalization. Use \`${correctCamelCase}\` instead.`,
|