shellscape / postcss-less

PostCSS Syntax for parsing LESS
MIT License
122 stars 39 forks source link

Reports incorrect node locations for files with comments containing single quote #163

Open nwalters512 opened 3 years ago

nwalters512 commented 3 years ago

I originally reported this problem in stylelint (https://github.com/stylelint/stylelint/issues/5438), but eventually determined it was caused by postcss-less.

LESS

This example may look kind of contrived, but it's based on many real-world examples in our code (long, rambling explanations that happen to single quotes). This is as simplified as I could get it.

a {
  // '
  color: pink;
}

/** ' */

JavaScript

Here is the test I added to test/parser/comments.test.js to reproduce the problem:

test('handles single quotes in comments', (t) => {
  const less = `a {\n  // '\n  color: pink;\n}\n\n/** ' */`;

  const root = parse(less);

  const [ruleNode, commentNode] = root.nodes;

  t.is(ruleNode.type, 'rule');
  t.is(commentNode.type, 'comment');

  const [innerCommentNode, declarationNode] = ruleNode.nodes;

  t.is(innerCommentNode.type, 'comment');
  t.is(declarationNode.type, 'decl');

  t.is(declarationNode.source.start.line, 3);
  t.is(declarationNode.source.start.column, 3);
  t.is(declarationNode.source.end.line, 3);
  t.is(declarationNode.source.end.column, 14);

  t.is(nodeToString(root), less);
});

All assertions about declarationNode.source.*.line fail.

Expected Behavior

I would expect that the declaration node for color: pink to be marked as starting and ending on line 3.

Actual Behavior

The declaration node for color: pink is marked as starting and ending on line 2. As you can see from the original LESS document, this is incorrect; line 2 is a comment.

How can we reproduce the behavior?

If you add the above test to test/parser/comments.test.js and run npm run test -- test/parser/comments.test.js, you can observe the test failing:

> ava "test/parser/comments.test.js"

  handles single quotes in comments

  test/parser/comments.test.js:198

   197:                                                
   198:   t.is(declarationNode.source.start.line, 3);  
   199:   t.is(declarationNode.source.start.column, 3);

  Difference:

  - 2
  + 3

  › test/parser/comments.test.js:198:5

  ─

  1 test failed

Potential fix

I spent a good deal of time trying to develop a fix for this, but I haven't yet figured out a way to work around the fact that the inline-comment node creates a new tokenizer in the middle of parsing (source). My current thinking is to try to do some kind of post-processing on the tree in the parse function - does that seem like a reasonable approach to you?