From b2e8490f6adb0b5e73f66c61b907cb0316a2cb49 Mon Sep 17 00:00:00 2001 From: "James D. Forrester" Date: Mon, 27 Jul 2026 17:18:25 +0100 Subject: [PATCH] fix: preserve source.input.file when retokenizing inline comments The replacement Input built for the remainder of the file was constructed without options, so `from` was dropped and every node after an inline comment reported `source.input.file === undefined`. lib/index.js already repairs the start/end offsets the sub-tokenizer corrupts, but not `source.input`, so the missing path reached the returned AST. Input does not retain its opts, so `from` is reconstructed from `this.input.file` rather than forwarded wholesale. --- lib/nodes/inline-comment.js | 2 +- test/parser/comments.test.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/nodes/inline-comment.js b/lib/nodes/inline-comment.js index b933949..6d4a461 100644 --- a/lib/nodes/inline-comment.js +++ b/lib/nodes/inline-comment.js @@ -45,7 +45,7 @@ module.exports = { // Replace tokenizer to retokenize the rest of the string // we need replace it after we added new token with inline comment because token position is calculated for old input (#145) if (remainingInput) { - this.input = new Input(remainingInput); + this.input = new Input(remainingInput, { from: this.input.file }); this.tokenizer = tokenizer(this.input); } diff --git a/test/parser/comments.test.js b/test/parser/comments.test.js index 6f7fdc1..952c472 100644 --- a/test/parser/comments.test.js +++ b/test/parser/comments.test.js @@ -212,3 +212,32 @@ test('handles single quotes in comments (#163)', (t) => { t.is(nodeToString(root), less); }); + +test('preserves source.input.file when retokenizing inline comments', (t) => { + const from = '/repro.less'; + // The quote in the inline comment, plus a later quote, makes the tokenizer run + // the string past the newline, which sends `isInlineComment` down the path that + // builds a replacement Input for the remainder of the file. + const less = `a {\n // it's\n color: red;\n}\nb { content: 'x'; }\n`; + + const root = parse(less, { from }); + + const nodes = []; + root.walk((node) => nodes.push(node)); + + // Nodes produced by the replacement tokenizer must keep the original path. + for (const node of nodes) { + t.is(node.source.input.file, from); + } + + // The position fix-up in lib/index.js still applies. + t.deepEqual(nodes.map((node) => [node.type, node.source.start.line]), [ + ['rule', 1], + ['comment', 2], + ['decl', 3], + ['rule', 5], + ['decl', 5] + ]); + + t.is(nodeToString(root), less); +});