From 4b97e68f589c085683698cd277c06bad024f0a2e Mon Sep 17 00:00:00 2001 From: Dylan Piercey Date: Fri, 3 Jul 2026 02:03:54 +0000 Subject: [PATCH] Fix trailing line comment classified as enclosed in validators A `//` line comment that runs to the end of a validated expression (no terminating newline) consumes everything that would follow it on the line, so the value is not safe to emit verbatim as an attribute value. `isValid` only downgraded `enclosed` -> `valid` when it saw a literal newline byte, so a value ending in a trailing line comment (e.g. `"hello" // note`) was wrongly reported as `enclosed`, regressing prettier-plugin-marko which then emitted the value bare and commented out the tag close. Flag the enclosing expression as having an unguarded newline when a line comment reaches EOF while no group is open, mirroring the existing unguarded-newline handling. This is done in EXPRESSION.return so it only affects line comments whose parent is a JS expression. A comment guarded by a group (`("hello" // c\n)`) or a self-closing block comment stays `enclosed`. --- .changeset/tidy-carrots-repair.md | 7 +++++++ src/__tests__/validate.test.ts | 30 ++++++++++++++++++++++++++++++ src/states/EXPRESSION.ts | 9 +++++++++ 3 files changed, 46 insertions(+) create mode 100644 .changeset/tidy-carrots-repair.md diff --git a/.changeset/tidy-carrots-repair.md b/.changeset/tidy-carrots-repair.md new file mode 100644 index 00000000..3e1a13f3 --- /dev/null +++ b/.changeset/tidy-carrots-repair.md @@ -0,0 +1,7 @@ +--- +"htmljs-parser": patch +--- + +Fix `isValidAttrValue` (and `isValidStatement` / `isValidScriptlet`) reporting `enclosed` for a value that ends in a trailing `//` line comment (e.g. `"hello" // note`). + +A line comment with no terminating newline consumes everything that would follow it on the line, so such a value is not safe to emit verbatim. It is now classified as `valid` (needs wrapping when placed inline), matching the existing behavior for an unguarded trailing newline. A comment guarded by a group (e.g. `("hello" // c\n)`) or a self-closing block comment (`"hello" /* c */`) still classifies as `enclosed`. diff --git a/src/__tests__/validate.test.ts b/src/__tests__/validate.test.ts index 636b6322..9c4c471d 100644 --- a/src/__tests__/validate.test.ts +++ b/src/__tests__/validate.test.ts @@ -124,5 +124,35 @@ describe("validation helpers", () => { it("rejects keyword operator with no operand", () => { assert.equal(isValidAttrValue("a as ", true), 0); }); + + it("treats a trailing line comment as unguarded", () => { + assert.equal(isValidAttrValue('"hello" // c', false), 1); + assert.equal(isValidAttrValue('"hello" // c', true), 1); + assert.equal(isValidAttrValue("foo // c", false), 1); + assert.equal(isValidAttrValue("1 + 2 // c", false), 1); + assert.equal(isValidAttrValue('"hello" /* c */ // d', false), 1); + }); + + it("keeps a self-closing block comment enclosed", () => { + assert.equal(isValidAttrValue('"hello" /* c */', false), 2); + }); + + it("keeps a line comment guarded by a group enclosed", () => { + assert.equal(isValidAttrValue('("hello" // c\n)', false), 2); + }); + + it("treats a newline after a value as unguarded", () => { + assert.equal(isValidAttrValue('"hello"\n// c', false), 1); + }); + }); + + describe("trailing line comments", () => { + it("downgrades statements with a trailing line comment", () => { + assert.equal(isValidStatement("foo // c"), 1); + }); + + it("downgrades scriptlets with a trailing line comment", () => { + assert.equal(isValidScriptlet("foo // c"), 1); + }); }); }); diff --git a/src/states/EXPRESSION.ts b/src/states/EXPRESSION.ts index a7e1bbb4..63f4eb11 100644 --- a/src/states/EXPRESSION.ts +++ b/src/states/EXPRESSION.ts @@ -385,6 +385,15 @@ export const EXPRESSION: StateDefinition = { return(child, expression) { if (child.state === STATE.JS_COMMENT_LINE) { expression.wasComment = true; + // A line comment that runs to the end of the input (rather than being + // terminated by a newline or a closing tag) consumes everything that + // would follow it on the line, exactly like an unguarded newline. Flag + // it as unguarded so the value is not classified as `enclosed` (safe to + // inline verbatim) by the validation helpers — otherwise emitting it + // bare would comment out whatever comes next. + if (this.pos === this.maxPos && !expression.groupStack.length) { + expression.hadUnguardedNewline = true; + } } }, };