Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
21 changes: 20 additions & 1 deletion packages/less/lib/less/parser/parser-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ export default () => {
* group array (or null). `[...]`/`{...}` do NOT shield a reference — only
* `(...)` (a declaration-value group) does.
*/
parserInput.$parseUntil = (tok, detectBareVar) => {
parserInput.$parseUntil = (tok, detectBareVar, stripLineComments) => {
let quote = '';
let returnVal = null;
let inComment = false;
Expand Down Expand Up @@ -278,6 +278,25 @@ export default () => {
i++;
inComment = true;
blockDepth++;
} else if (stripLineComments &&
input.charAt(i + 1) === '/' &&
/\s/.test(input.charAt(i - 1))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Whitespace Misclassifies URLs

The preceding-whitespace check cannot reliably distinguish comments from URLs. For example, a valid protocol-relative URL with whitespace after url(, such as @supports (background: url( //cdn.example/x)), meets this condition, so the scanner removes the URL and the rest of the line, including its closing delimiters. Conversely, a Less comment directly adjacent to prelude content, such as @supports (display: grid)// comment, fails the condition and remains in the generated CSS. Unknown at-rule preludes are therefore corrupted for both realistic forms.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both cases were real, thanks. Fixed in 3c7bf86. The whitespace test is gone. A // is now left alone only inside url(, url-prefix( or domain( (the value parser also stops absorbing comments there), or right after a : (a scheme). url( //cdn.example.com/y.png) keeps its address, and (display: flex)// comment is stripped. Both are in the fixture now, and the previous parser fails them. grunt test:node passes.

// A `//` comment, which is not CSS and must not
// reach the output. Only when whitespace precedes
// it: everywhere else the scanner absorbs comments
// while skipping whitespace, which is exactly why
// `http://host` and a protocol-relative
// `url(//host/x.png)` are not comments.
const before = input.slice(lastPos, i);
if (before) {
parseGroups.push(before);
}
let nextNewLine = input.indexOf('\n', i + 2);
if (nextNewLine < 0) {
nextNewLine = length;
}
lastPos = nextNewLine;
i = nextNewLine - 1;
}
break;
case '\'':
Expand Down
6 changes: 3 additions & 3 deletions packages/less/lib/less/parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -1874,7 +1874,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
* prelude (non-value position); accept `@{var}` interpolation and warn
* on a bare `@var` reference (which resolves today but is deprecated).
*/
permissiveValue: function (untilTokens, deprecateVariables) {
permissiveValue: function (untilTokens, deprecateVariables, stripLineComments) {
const entities = this.entities;
let i;
let e;
Expand Down Expand Up @@ -1942,7 +1942,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
}
parserInput.save();

value = parserInput.$parseUntil(tok, deprecateVariables);
value = parserInput.$parseUntil(tok, deprecateVariables, stripLineComments);

if (value) {
if (typeof value === 'string') {
Expand Down Expand Up @@ -2350,7 +2350,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
return e;
},
atruleUnknown: function (value, name, hasBlock) {
value = this.permissiveValue(/^[{;]/, true);
value = this.permissiveValue(/^[{;]/, true, true);
hasBlock = (parserInput.currentChar() === '{');
if (!value) {
if (!hasBlock && parserInput.currentChar() !== ';') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@supports selector(
:focus-visible ) {
a {
color: red;
}
}
@supports (display: grid) {
.grid {
display: grid;
}
}
@supports (background: url(http://example.com/a.png)) {
.absolute-url {
color: blue;
}
}
@document url-prefix(http://example.com/) {
.doc {
color: green;
}
}
@supports (background: url(//cdn.example.com/x.png)) {
.protocol-relative {
background: url(//cdn.example.com/x.png);
}
}
@supports (a: b) /* keep me */ {
.block-comment {
color: teal;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// A `//` comment inside an unknown at-rule prelude is scanned as raw text by
// $parseUntil, so it used to survive into the CSS and swallow the rest of the
// line with it.

@supports selector(
:focus-visible // a line comment
) {
a {
color: red;
}
}

@supports (display: grid) // trailing comment
{
.grid {
display: grid;
}
}

// A `//` that is not preceded by whitespace is not a comment: it is part of a
// URL. These must be left alone.
@supports (background: url(http://example.com/a.png)) {
.absolute-url {
color: blue;
}
}

@document url-prefix(http://example.com/) {
.doc {
color: green;
}
}

@supports (background: url(//cdn.example.com/x.png)) {
.protocol-relative {
background: url(//cdn.example.com/x.png);
}
}

// Block comments are valid CSS and stay.
@supports (a: b) /* keep me */ {
.block-comment {
color: teal;
}
}
Loading