diff --git a/javascript/packages/linter/src/rules/html-no-self-closing.ts b/javascript/packages/linter/src/rules/html-no-self-closing.ts
index b6fffce40..410f4d6d0 100644
--- a/javascript/packages/linter/src/rules/html-no-self-closing.ts
+++ b/javascript/packages/linter/src/rules/html-no-self-closing.ts
@@ -1,25 +1,30 @@
import { ParserRule } from "../types.js"
-import { BaseRuleVisitor, getTagName, isVoidElement } from "./rule-utils.js"
+import { BaseRuleVisitor, isVoidElement } from "./rule-utils.js"
+import { getTagName } from "@herb-tools/core"
import type { LintContext, LintOffense } from "../types.js"
-import type { HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
+import type { HTMLOpenTagNode, HTMLElementNode, ParseResult } from "@herb-tools/core"
class NoSelfClosingVisitor extends BaseRuleVisitor {
+ visitHTMLElementNode(node: HTMLElementNode): void {
+ if (getTagName(node) === "svg") {
+ this.visit(node.open_tag)
+ } else {
+ this.visitChildNodes(node)
+ }
+ }
+
visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
if (node.tag_closing?.value === "/>") {
const tagName = getTagName(node)
-
- const shouldBeVoid = tagName ? isVoidElement(tagName) : false
- const instead = shouldBeVoid ? `Use \`<${tagName}>\` instead.` : `Use \`<${tagName}>${tagName}>\` instead.`
+ const instead = isVoidElement(tagName) ? `<${tagName}>` : `<${tagName}>${tagName}>`
this.addOffense(
- `Self-closing syntax \`<${tagName} />\` is not allowed in HTML. ${instead}`,
+ `Use \`${instead}\` instead of self-closing \`<${tagName} />\` for HTML compatibility.`,
node.location,
"error"
)
}
-
- super.visitHTMLOpenTagNode(node)
}
}
diff --git a/javascript/packages/linter/test/__snapshots__/cli.test.ts.snap b/javascript/packages/linter/test/__snapshots__/cli.test.ts.snap
index d65715633..870d50e49 100644
--- a/javascript/packages/linter/test/__snapshots__/cli.test.ts.snap
+++ b/javascript/packages/linter/test/__snapshots__/cli.test.ts.snap
@@ -151,7 +151,7 @@ test/fixtures/multiple-rule-offenses.html.erb:10:4
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ [10/11] ⎯⎯⎯⎯
-[error] Self-closing syntax \`\` is not allowed in HTML. Use \`\` instead. (html-no-self-closing)
+[error] Use \`\` instead of self-closing \`\` for HTML compatibility. (html-no-self-closing)
test/fixtures/multiple-rule-offenses.html.erb:4:2
diff --git a/javascript/packages/linter/test/rules/html-no-self-closing.test.ts b/javascript/packages/linter/test/rules/html-no-self-closing.test.ts
index 8f39af888..c0d648cb5 100644
--- a/javascript/packages/linter/test/rules/html-no-self-closing.test.ts
+++ b/javascript/packages/linter/test/rules/html-no-self-closing.test.ts
@@ -32,20 +32,22 @@ describe("html-no-self-closing", () => {
+
`;
const linter = new Linter(Herb, [HTMLNoSelfClosingRule]);
const lintResult = linter.lint(html);
- expect(lintResult.errors).toBe(4);
- expect(lintResult.offenses).toHaveLength(4);
+ expect(lintResult.errors).toBe(5);
+ expect(lintResult.offenses).toHaveLength(5);
expect(lintResult.offenses[0].rule).toBe("html-no-self-closing");
expect(lintResult.offenses[0].severity).toBe("error");
- expect(lintResult.offenses[0].message).toBe('Self-closing syntax `
` is not allowed in HTML. Use `` instead.')
- expect(lintResult.offenses[1].message).toBe('Self-closing syntax `` is not allowed in HTML. Use `` instead.')
- expect(lintResult.offenses[2].message).toBe('Self-closing syntax `` is not allowed in HTML. Use `` instead.')
- expect(lintResult.offenses[3].message).toBe('Self-closing syntax `` is not allowed in HTML. Use `` instead.')
+ expect(lintResult.offenses[0].message).toBe('Use `` instead of self-closing `` for HTML compatibility.')
+ expect(lintResult.offenses[1].message).toBe('Use `` instead of self-closing `` for HTML compatibility.')
+ expect(lintResult.offenses[2].message).toBe('Use `` instead of self-closing `` for HTML compatibility.')
+ expect(lintResult.offenses[3].message).toBe('Use `` instead of self-closing `` for HTML compatibility.')
+ expect(lintResult.offenses[4].message).toBe('Use `` instead of self-closing `` for HTML compatibility.')
});
test("fails for self-closing void elements", () => {
@@ -64,10 +66,10 @@ describe("html-no-self-closing", () => {
expect(lintResult.offenses[0].rule).toBe("html-no-self-closing");
expect(lintResult.offenses[0].severity).toBe("error");
- expect(lintResult.offenses[0].message).toBe('Self-closing syntax `` is not allowed in HTML. Use `` instead.')
- expect(lintResult.offenses[1].message).toBe('Self-closing syntax `` is not allowed in HTML. Use `` instead.')
- expect(lintResult.offenses[2].message).toBe('Self-closing syntax ` ` is not allowed in HTML. Use ` ` instead.')
- expect(lintResult.offenses[3].message).toBe('Self-closing syntax `` is not allowed in HTML. Use `` instead.')
+ expect(lintResult.offenses[0].message).toBe('Use `` instead of self-closing `` for HTML compatibility.')
+ expect(lintResult.offenses[1].message).toBe('Use `` instead of self-closing `` for HTML compatibility.')
+ expect(lintResult.offenses[2].message).toBe('Use ` ` instead of self-closing ` ` for HTML compatibility.')
+ expect(lintResult.offenses[3].message).toBe('Use `` instead of self-closing `` for HTML compatibility.')
});
test("passes for mixed correct and incorrect tags", () => {
@@ -83,8 +85,8 @@ describe("html-no-self-closing", () => {
expect(lintResult.errors).toBe(2);
expect(lintResult.offenses).toHaveLength(2);
- expect(lintResult.offenses[0].message).toBe('Self-closing syntax `` is not allowed in HTML. Use `` instead.')
- expect(lintResult.offenses[1].message).toBe('Self-closing syntax `` is not allowed in HTML. Use `` instead.')
+ expect(lintResult.offenses[0].message).toBe('Use `` instead of self-closing `` for HTML compatibility.')
+ expect(lintResult.offenses[1].message).toBe('Use `` instead of self-closing `` for HTML compatibility.')
});
test("passes for nested non-self-closing tags", () => {
@@ -114,8 +116,8 @@ describe("html-no-self-closing", () => {
expect(lintResult.errors).toBe(2);
expect(lintResult.offenses).toHaveLength(2);
- expect(lintResult.offenses[0].message).toBe('Self-closing syntax `` is not allowed in HTML. Use `` instead.')
- expect(lintResult.offenses[1].message).toBe('Self-closing syntax `` is not allowed in HTML. Use `` instead.')
+ expect(lintResult.offenses[0].message).toBe('Use `` instead of self-closing `` for HTML compatibility.')
+ expect(lintResult.offenses[1].message).toBe('Use `` instead of self-closing `` for HTML compatibility.')
});
test("passes for custom elements without self-closing", () => {
@@ -141,8 +143,8 @@ describe("html-no-self-closing", () => {
expect(lintResult.errors).toBe(2);
expect(lintResult.offenses).toHaveLength(2);
- expect(lintResult.offenses[0].message).toBe('Self-closing syntax `` is not allowed in HTML. Use `` instead.')
- expect(lintResult.offenses[1].message).toBe('Self-closing syntax `` is not allowed in HTML. Use `` instead.')
+ expect(lintResult.offenses[0].message).toBe('Use `` instead of self-closing `` for HTML compatibility.')
+ expect(lintResult.offenses[1].message).toBe('Use `` instead of self-closing `` for HTML compatibility.')
});
test("passes for void elements without self-closing", () => {
@@ -158,4 +160,40 @@ describe("html-no-self-closing", () => {
expect(lintResult.errors).toBe(0);
expect(lintResult.offenses).toHaveLength(0);
});
+
+ test("passes for self-closing elements inside SVG", () => {
+ const html = `
+
+
+
+ `;
+ const linter = new Linter(Herb, [HTMLNoSelfClosingRule]);
+ const lintResult = linter.lint(html);
+
+ expect(lintResult.errors).toBe(0);
+ expect(lintResult.offenses).toHaveLength(0);
+ });
+
+ test("fails for self-closing elements outside SVG but passes inside SVG", () => {
+ const html = `
+
+
+
+ `;
+ const linter = new Linter(Herb, [HTMLNoSelfClosingRule]);
+ const lintResult = linter.lint(html);
+
+ expect(lintResult.errors).toBe(2);
+ expect(lintResult.offenses).toHaveLength(2);
+
+ expect(lintResult.offenses[0].message).toBe('Use `` instead of self-closing `` for HTML compatibility.');
+ expect(lintResult.offenses[1].message).toBe('Use `` instead of self-closing `` for HTML compatibility.');
+ });
});