Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
31 changes: 31 additions & 0 deletions lib/LessParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,42 @@ module.exports = class LessParser extends Parser {
return;
}

this.mergeParamsInterpolation();

super.atrule(token);
importNode(this.lastNode);
variableNode(this.lastNode);
}

// #180: `@media @{mq}` - merge `@{…}` sequences in the prelude into single word tokens,
// otherwise the interpolation's `{` is mistaken for the start of the at-rule block
mergeParamsInterpolation() {
const tokens = [];
let token = this.tokenizer.nextToken();

while (token) {
if (token[0] === 'at-word' && token[1] === '@' && interpolation.bind(this)(token)) {
token = this.tokenizer.nextToken();

if (!token) {
break;
}
}

tokens.push(token);

if (token[0] === '{' || token[0] === '}' || token[0] === ';') {
break;
}

token = this.tokenizer.nextToken();
}

for (const tokn of tokens.reverse()) {
this.tokenizer.back(tokn);
}
}

decl(...args) {
super.decl(...args);

Expand Down
40 changes: 40 additions & 0 deletions test/parser/interpolation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,46 @@ test('parses non-interpolation', (t) => {
t.is(root.first.selector, '\\@');
});

test('parses interpolation in at-rule params (#180)', (t) => {
const root = parse('@media @{mq-xs} { .a { color: red } }');

t.is(root.first.type, 'atrule');
t.is(root.first.name, 'media');
t.is(root.first.params, '@{mq-xs}');
t.is(root.first.first.selector, '.a');
});

test('parses interpolation in at-rule params without space before bracket (#180)', (t) => {
const root = parse('@media @{mq-xs}{ .a { color: red } }');

t.is(root.first.params, '@{mq-xs}');
t.is(root.first.first.selector, '.a');
});

test('parses interpolation followed by a media feature (#180)', (t) => {
const root = parse('@media @{mq-xs} and (min-width: 5px) { .a { color: red } }');

t.is(root.first.params, '@{mq-xs} and (min-width: 5px)');
});

test('parses multiple interpolations in at-rule params (#180)', (t) => {
const root = parse('@media @{a} and @{b} { .a { color: red } }');

t.is(root.first.params, '@{a} and @{b}');
});

test('parses interpolation inside at-rule params word (#180)', (t) => {
const root = parse('@media screen and (max-width: @{w}) { .a { color: red } }');

t.is(root.first.params, 'screen and (max-width: @{w})');
});

test('parses a variable in at-rule params (#180)', (t) => {
const root = parse('@media @mq-xs { .a { color: red } }');

t.is(root.first.params, '@mq-xs');
});

// TODO: interpolation doesn't quite work yet
test('interpolation', (t) => {
const root = parse('@{selector}:hover { @{prop}-size: @{color} }');
Expand Down
6 changes: 6 additions & 0 deletions test/stringify.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,9 @@ test('mixin with !important', async (t) => {
const result = run(less);
t.is(result, less);
});

test('at-rule with interpolated params (#180)', (t) => {
const less = '@media @{mq-xs} and (min-width: 5px) { .a { color: red } }';
const result = run(less);
t.is(result, less);
});