Skip to content

fix: parse "/*/" as an unclosed comment - #101

Open
mahirhir wants to merge 1 commit into
postcss:mainfrom
mahirhir:fix-slash-star-comment
Open

mahirhir wants to merge 1 commit into
postcss:mainfrom
mahirhir:fix-slash-star-comment

Conversation

@mahirhir

Copy link
Copy Markdown

valueParser("/*/").toString() returns /**/, so the round trip is not lossless for this input.

The comment branch looks for the closing */ with value.indexOf("*/", pos), which starts the search at the same index as the opening /*. For the input /*/ that search matches the * of the opening delimiter plus the following /, so the parser reports a closed, empty comment ({ value: "", sourceEndIndex: 3 }) and never sets the unclosed flag. On stringify it then emits /* + "" + */, which is /**/.

/*/ is actually an unterminated comment: after /* the only content is / and there is no closing */. postcss core agrees and reports Unclosed comment for the same input. The parser already handles unterminated comments correctly for other inputs (for example /*x yields { value: "x", unclosed: true }); only the slash right after the opener slipped through.

Starting the closing search at pos + 2, where the comment body begins, stops the opening /* from being matched as its own terminator. /*/ now parses to { value: "/", unclosed: true } and round trips back to /*/. Empty and normal comments are unchanged.

Added a parse test for /*/. The existing suite still passes.

The comment branch searches for the closing "*/" with
value.indexOf("*/", pos), starting at the same index as the opening
"/*". For the input "/*/" that search matches the "*" of the opening
delimiter plus the following "/", so the parser reports a closed empty
comment and never sets the unclosed flag. On stringify it then emits
"/**/", so the round trip is not lossless.

"/*/" is an unterminated comment, which postcss core also reports as an
unclosed comment. The parser already handles this for other inputs such
as "/*x"; only the slash right after the opener slipped through. Start
the closing search at pos + 2 so the opening "/*" cannot be matched as
its own terminator.
@mahirhir

Copy link
Copy Markdown
Author

No review on this since June. Rather than describe it again, here it is run against lib/parse.js exactly as it stands on main. That file has zero require calls, so nothing was stubbed to make this work.

lib/parse.js sha256 = 9ca60ba57cbb621b   bytes = 8356

The line in question:

// lib/parse.js:106
next = value.indexOf("*/", pos);

pos is the index of the / that opens the comment. So the search for the terminator starts one character before the * of the opening /*, and for an input like /*/ it finds that * paired with the trailing /. The opening and closing delimiters overlap by one character, and a comment that never closes is reported as closed.

Parsing six inputs through the current file and through the same file with this PR's change:

input       main                                          with the patch
"/* ok */"  comment(" ok ")                               comment(" ok ")
"/**/"      comment("")                                   comment("")
"/*"        comment("",unclosed)                          comment("",unclosed)
"/*/"       comment("")                                   comment("/",unclosed)
"/*/*/"     comment("") word("*") div("/")                comment("/")
"a /*/ b"   word("a") space(" ") comment("") space(" ")   word("a") space(" ")
            word("b")                                     comment("/ b",unclosed)

inputs whose parse changed = 3 of 6

Three rows move and three do not. A well-formed comment, an empty comment and a plain unterminated /* all parse identically, so this is not a change to comment handling in general. It only reaches inputs where a / immediately follows the opening /*.

The last row is the one with consequences. In a /*/ b the comment never terminates, so b is commented-out text. On main it comes back as a word token outside the comment, which means anything downstream that rewrites or minifies tokens is acting on content the author commented out. The /*/*/ row is milder but the same shape: main emits a word("*") and a div("/") that correspond to nothing in the source's reading.

The one-character change and its controls:

mutation applied: bytes 8356 -> 8360 (+4)
mutation site unique = yes
CONTROL "/* ok */" identical on both = true

The control matters here because starting the search later could plausibly break the ordinary case, and it does not.

One thing I am not claiming:

$ gh pr checks 101 --repo postcss/postcss-value-parser
no checks reported on the 'fix-slash-star-comment' branch

Nothing runs on this branch, so the table above is mine, produced locally against your main file rather than by your CI. The added fixture in test/parse.js is the thing to run.

Happy to rebase, or to take the one-character change directly and close this. No attribution needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant