Skip to content

Commit

Permalink
feat: support less each function (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
shellscape committed Nov 14, 2018
1 parent 9392246 commit e3731c3
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 47 deletions.
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);
});

0 comments on commit e3731c3

Please sign in to comment.