Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support less each function #127

Merged
merged 1 commit into from
Nov 14, 2018
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);
});