Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions javascript/packages/linter/src/rules/html-no-self-closing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ export class HTMLNoSelfClosingRule extends ParserRule<NoSelfClosingAutofixContex
return { action_view_helpers: true }
}

private isIndentation(precedingNode: Node | null, node: Node): boolean {
return !!precedingNode &&
isWhitespaceNode(node) &&
isWhitespaceNode(precedingNode) &&
(precedingNode.value?.value?.includes("\n") || false)
}

check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<NoSelfClosingAutofixContext>[] {
const visitor = new NoSelfClosingVisitor(this.ruleName, context)

Expand All @@ -76,8 +83,13 @@ export class HTMLNoSelfClosingRule extends ParserRule<NoSelfClosingAutofixContex
if (node.children && Array.isArray(node.children)) {
const children = node.children as Node[]

if (children.length > 0 && isWhitespaceNode(children[children.length - 1])) {
node.children = children.slice(0, -1)
if (children.length > 0) {
const lastChild = children[children.length - 1]
const secondToLastChild = children[children.length - 2] ?? null

if (isWhitespaceNode(lastChild) && !this.isIndentation(secondToLastChild, lastChild)) {
node.children = children.slice(0, -1)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import dedent from "dedent"
import { describe, test, expect, beforeAll } from "vitest"
import { Herb } from "@herb-tools/node-wasm"
import { Linter } from "../../src/linter.js"
Expand Down Expand Up @@ -86,6 +87,40 @@ describe("html-no-self-closing autofix", () => {
expect(result.fixed).toHaveLength(1)
})

test("preserves indentation of closing tag for nested void element", () => {
const input = dedent`
<figure>
<img
src="/image.png"
alt="An image"
/>
</figure>`
const expected = dedent`
<figure>
<img
src="/image.png"
alt="An image"
>
</figure>`

const linter = new Linter(Herb, [HTMLNoSelfClosingRule])
const result = linter.autofix(input)

expect(result.source).toBe(expected)
expect(result.fixed).toHaveLength(1)
})

test("drops newline before closing when /> is on its own line without indentation", () => {
const input = '<img class="x"\n/>'
const expected = '<img class="x">'

const linter = new Linter(Herb, [HTMLNoSelfClosingRule])
const result = linter.autofix(input)

expect(result.source).toBe(expected)
expect(result.fixed).toHaveLength(1)
})

test("does not affect SVG elements", () => {
const input = '<svg><circle cx="50" cy="50" r="40" /></svg>'

Expand Down