Skip to content
Merged
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
118 changes: 73 additions & 45 deletions lib/LessParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,25 @@ module.exports = class LessParser extends Parser {
variableNode(this.lastNode);
}

each(tokens) {
// prepend a space so the `name` will be parsed correctly
tokens[0][1] = ` ${tokens[0][1]}`;

const firstParenIndex = tokens.findIndex((t) => t[0] === '(');
const lastParen = tokens.reverse().find((t) => t[0] === ')');
const lastParenIndex = tokens.reverse().indexOf(lastParen);
const paramTokens = tokens.splice(firstParenIndex, lastParenIndex);
const params = paramTokens.map((t) => t[1]).join('');

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

this.atrule(this.tokenizer.nextToken());
this.lastNode.function = true;
this.lastNode.params = params;
}

init(node, line, column) {
super.init(node, line, column);
this.lastNode = node;
Expand All @@ -53,6 +72,53 @@ module.exports = class LessParser extends Parser {
}
}

mixin(tokens) {
const [first] = tokens;
const identifier = first[1].slice(0, 1);
const bracketsIndex = tokens.findIndex((t) => t[0] === 'brackets');
const firstParenIndex = tokens.findIndex((t) => t[0] === '(');
let important = '';

// fix for #86. if rulesets are mixin params, they need to be converted to a brackets token
if ((bracketsIndex < 0 || bracketsIndex > 3) && firstParenIndex > 0) {
const lastParenIndex = tokens.findIndex((t) => t[0] === ')');

const contents = tokens.slice(firstParenIndex, lastParenIndex + firstParenIndex);
const brackets = contents.map((t) => t[1]).join('');
const [paren] = tokens.slice(firstParenIndex);
const start = [paren[2], paren[3]];
const [last] = tokens.slice(lastParenIndex, lastParenIndex + 1);
const end = [last[2], last[3]];
const newToken = ['brackets', brackets].concat(start, end);

const tokensBefore = tokens.slice(0, firstParenIndex);
const tokensAfter = tokens.slice(lastParenIndex + 1);
tokens = tokensBefore;
tokens.push(newToken);
tokens = tokens.concat(tokensAfter);
}

const importantIndex = tokens.findIndex((t) => importantPattern.test(t[1]));

if (importantIndex > 0) {
[, important] = tokens[importantIndex];
tokens.splice(importantIndex, 1);
}

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

this.atrule(this.tokenizer.nextToken());
this.lastNode.mixin = true;
this.lastNode.raws.identifier = identifier;

if (important) {
this.lastNode.important = true;
this.lastNode.raws.important = important;
}
}

other(token) {
if (!isInlineComment.bind(this)(token)) {
super.other(token);
Expand Down Expand Up @@ -84,56 +150,18 @@ module.exports = class LessParser extends Parser {
unknownWord(tokens) {
// NOTE: keep commented for examining unknown structures
// console.log('unknown', tokens);
// console.log(this.root.first);

const [first] = tokens;

// #121 support `each` - http://lesscss.org/functions/#list-functions-each
if (tokens[0][1] === 'each' && tokens[1][0] === '(') {
this.each(tokens);
return;
}

// TODO: move this into a util function/file
if (isMixinToken(first)) {
const identifier = first[1].slice(0, 1);
const bracketsIndex = tokens.findIndex((t) => t[0] === 'brackets');
const firstParenIndex = tokens.findIndex((t) => t[0] === '(');
let important = '';

// fix for #86. if rulesets are mixin params, they need to be converted to a brackets token
if ((bracketsIndex < 0 || bracketsIndex > 3) && firstParenIndex > 0) {
const lastParenIndex = tokens.findIndex((t) => t[0] === ')');

const contents = tokens.slice(firstParenIndex, lastParenIndex + firstParenIndex);
const brackets = contents.map((t) => t[1]).join('');
const [paren] = tokens.slice(firstParenIndex);
const start = [paren[2], paren[3]];
const [last] = tokens.slice(lastParenIndex, lastParenIndex + 1);
const end = [last[2], last[3]];
const newToken = ['brackets', brackets].concat(start, end);

const tokensBefore = tokens.slice(0, firstParenIndex);
const tokensAfter = tokens.slice(lastParenIndex + 1);
tokens = tokensBefore;
tokens.push(newToken);
tokens = tokens.concat(tokensAfter);
}

const importantIndex = tokens.findIndex((t) => importantPattern.test(t[1]));

if (importantIndex > 0) {
[, important] = tokens[importantIndex];
tokens.splice(importantIndex, 1);
}

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

this.atrule(this.tokenizer.nextToken());
this.lastNode.mixin = true;
this.lastNode.raws.identifier = identifier;

if (important) {
this.lastNode.important = true;
this.lastNode.raws.important = important;
}

this.mixin(tokens);
return;
}

Expand Down
5 changes: 3 additions & 2 deletions lib/LessStringifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ const Stringifier = require('postcss/lib/stringifier');

module.exports = class LessStringifier extends Stringifier {
atrule(node, semicolon) {
if (!node.mixin && !node.variable) {
if (!node.mixin && !node.variable && !node.function) {
super.atrule(node, semicolon);
return;
}

let name = `${node.raws.identifier || '@'}${node.name}`;
const identifier = node.function ? '' : node.raws.identifier || '@';
let name = `${identifier}${node.name}`;
let params = node.params ? this.rawValue(node, 'params') : '';
const important = node.raws.important || '';

Expand Down
20 changes: 20 additions & 0 deletions test/parser/function.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const test = require('ava');

const { parse, nodeToString } = require('../../lib');

test('each (#121)', (t) => {
const params = `(@colors, {
.@{value}-color {
color: @value;
}
})`;
const less = `each${params};`;
const root = parse(less);
const { first } = root;

t.is(first.name, 'each');
t.is(first.params, params);
t.truthy(first.function);

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