Skip to content

Fixes #3205, partial 3.0 math regression #1880 #3228

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

Merged
merged 5 commits into from
Jun 27, 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
5 changes: 1 addition & 4 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@
"node": true
},
"globals": {},

"rules": {
"quotes": [
1,
"single"
],
"no-eval": 2,
"no-use-before-define": [
2,
Expand Down
2 changes: 1 addition & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ module.exports = function (grunt) {
main: {
// src is used to build list of less files to compile
src: [
'test/less/*.less',
'test/less/plugin.less',
// Don't test NPM import, obviously
'!test/less/plugin-module.less',
'!test/less/import-module.less',
Expand Down
5 changes: 4 additions & 1 deletion lib/less/parser/parser-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,6 @@ module.exports = function() {
case '/':
if (input.charAt(i + 1) === '*') {
i++;
console.log(input.substr(lastPos, i - lastPos));
inComment = true;
blockDepth++;
}
Expand Down Expand Up @@ -324,6 +323,10 @@ module.exports = function() {
return input.charAt(parserInput.i);
};

parserInput.prevChar = function() {
return input.charAt(parserInput.i - 1);
};

parserInput.getInput = function() {
return input;
};
Expand Down
79 changes: 64 additions & 15 deletions lib/less/parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -1297,7 +1297,7 @@ var Parser = function Parser(context, imports, fileInfo) {

// Custom property values get permissive parsing
if (name[0].value && name[0].value.slice(0, 2) === '--') {
value = this.permissiveValue(';');
value = this.permissiveValue();
}
// Try to store values as anonymous
// If we need the value later we'll re-parse it in ruleset.parseValue
Expand All @@ -1313,13 +1313,12 @@ var Parser = function Parser(context, imports, fileInfo) {
if (!value) {
value = this.value();
}

important = this.important();

// As a last resort, let a variable try to be parsed as a permissive value
// As a last resort, try permissiveValue
if (!value && isVariable) {
value = this.permissiveValue(';');
value = this.permissiveValue();
}

important = this.important();
}

if (value && this.end()) {
Expand All @@ -1341,27 +1340,76 @@ var Parser = function Parser(context, imports, fileInfo) {
}
},
/**
* Used for custom properties and custom at-rules
* Used for custom properties, at-rules, and variables (as fallback)
* Parses almost anything inside of {} [] () "" blocks
* until it reaches outer-most tokens.
*
* First, it will try to parse comments and entities to reach
* the end. This is mostly like the Expression parser except no
* math is allowed.
*/
permissiveValue: function (untilTokens) {
var i, index = parserInput.i,
value = parserInput.$parseUntil(untilTokens);
var i, e, done, value,
tok = untilTokens || ';',
index = parserInput.i, result = [];

function testCurrentChar() {
var char = parserInput.currentChar();
if (typeof tok === 'string') {
return char === tok;
} else {
return tok.test(char);
}
}
if (testCurrentChar()) {
return;
}
value = [];
do {
e = this.comment();
if (e) {
value.push(e);
continue;
}
e = this.entity();
if (e) {
value.push(e);
}
} while (e);

done = testCurrentChar();

if (value.length > 0) {
value = new(tree.Expression)(value);
if (done) {
return value;
}
else {
result.push(value);
}
// Preserve space before $parseUntil as it will not
if (parserInput.prevChar() === ' ') {
result.push(new tree.Anonymous(' ', index));
}
}
parserInput.save();

value = parserInput.$parseUntil(tok);

if (value) {
if (typeof value === 'string') {
error('Expected \'' + value + '\'', 'Parse');
}
if (value.length === 1 && value[0] === ' ') {
parserInput.forget();
return new tree.Anonymous('', index);
}
var item, args = [];
var item;
for (i = 0; i < value.length; i++) {
item = value[i];
if (Array.isArray(item)) {
// Treat actual quotes as normal quoted values
args.push(new tree.Quoted(item[0], item[1], true, index, fileInfo));
result.push(new tree.Quoted(item[0], item[1], true, index, fileInfo));
}
else {
if (i === value.length - 1) {
Expand All @@ -1371,12 +1419,13 @@ var Parser = function Parser(context, imports, fileInfo) {
var quote = new tree.Quoted('\'', item, true, index, fileInfo);
quote.variableRegex = /@([\w-]+)/g;
quote.propRegex = /\$([\w-]+)/g;
quote.reparse = true;
args.push(quote);
result.push(quote);
}
}
return new tree.Expression(args, true);
parserInput.forget();
return new tree.Expression(result, true);
}
parserInput.restore();
},

//
Expand Down Expand Up @@ -1458,7 +1507,7 @@ var Parser = function Parser(context, imports, fileInfo) {
nodes.push(e);
} else if (parserInput.$char('(')) {
p = this.property();
e = this.value();
e = this.permissiveValue(')');
if (parserInput.$char(')')) {
if (p && e) {
nodes.push(new(tree.Paren)(new(tree.Declaration)(p, e, null, null, parserInput.i, fileInfo, true)));
Expand Down
36 changes: 28 additions & 8 deletions lib/less/transform-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,29 +41,49 @@ module.exports = function(root, options) {
new visitor.MarkVisibleSelectorsVisitor(true),
new visitor.ExtendVisitor(),
new visitor.ToCSSVisitor({compress: Boolean(options.compress)})
], v, visitorIterator;
], preEvalVisitors = [], v, visitorIterator;

// first() / get() allows visitors to be added while visiting
/**
* first() / get() allows visitors to be added while visiting
*
* @todo Add scoping for visitors just like functions for @plugin; right now they're global
*/
if (options.pluginManager) {
visitorIterator = options.pluginManager.visitor();
visitorIterator.first();
while ((v = visitorIterator.get())) {
if (v.isPreEvalVisitor) {
v.run(root);
for (var i = 0; i < 2; i++) {
visitorIterator.first();
while ((v = visitorIterator.get())) {
if (v.isPreEvalVisitor) {
if (i === 0 || preEvalVisitors.indexOf(v) === -1) {
preEvalVisitors.push(v);
v.run(root);
}
}
else {
if (i === 0 || visitors.indexOf(v) === -1) {
if (v.isPreVisitor) {
visitors.unshift(v);
}
else {
visitors.push(v);
}
}
}
}
}
}

evaldRoot = root.eval(evalEnv);

for (var i = 0; i < visitors.length; i++) {
visitors[i].run(evaldRoot);
}

// Run any remaining visitors added after eval pass
if (options.pluginManager) {
visitorIterator.first();
while ((v = visitorIterator.get())) {
if (!v.isPreEvalVisitor) {
if (visitors.indexOf(v) === -1 && preEvalVisitors.indexOf(v) === -1) {
v.run(evaldRoot);
}
}
Expand Down
3 changes: 3 additions & 0 deletions lib/less/tree/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ Expression.prototype.eval = function (context) {
}
if (this.value.length > 1) {
returnValue = new Expression(this.value.map(function (e) {
if (!e.eval) {
return e;
}
return e.eval(context);
}), this.noSpacing);
} else if (this.value.length === 1) {
Expand Down
22 changes: 12 additions & 10 deletions lib/less/visitors/visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ function indexNodeTypes(parent, ticker) {

var Visitor = function(implementation) {
this._implementation = implementation;
this._visitFnCache = [];
this._visitInCache = {};
this._visitOutCache = {};

if (!_hasIndexed) {
indexNodeTypes(tree, 1);
Expand All @@ -48,15 +49,16 @@ Visitor.prototype = {

var nodeTypeIndex = node.typeIndex;
if (!nodeTypeIndex) {
// MixinCall args aren't a node type?
if (node.value && node.value.typeIndex) {
this.visit(node.value);
}
return node;
}

var visitFnCache = this._visitFnCache,
impl = this._implementation,
aryIndx = nodeTypeIndex << 1,
outAryIndex = aryIndx | 1,
func = visitFnCache[aryIndx],
funcOut = visitFnCache[outAryIndex],
var impl = this._implementation,
func = this._visitInCache[nodeTypeIndex],
funcOut = this._visitOutCache[nodeTypeIndex],
visitArgs = _visitArgs,
fnName;

Expand All @@ -66,13 +68,13 @@ Visitor.prototype = {
fnName = 'visit' + node.type;
func = impl[fnName] || _noop;
funcOut = impl[fnName + 'Out'] || _noop;
visitFnCache[aryIndx] = func;
visitFnCache[outAryIndex] = funcOut;
this._visitInCache[nodeTypeIndex] = func;
this._visitOutCache[nodeTypeIndex] = funcOut;
}

if (func !== _noop) {
var newNode = func.call(impl, node, visitArgs);
if (impl.isReplacing) {
if (node && impl.isReplacing) {
node = newNode;
}
}
Expand Down
9 changes: 9 additions & 0 deletions test/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"env": {
"node": true,
"browser": true
},
"parserOptions": {
"ecmaVersion": 6
}
}
7 changes: 3 additions & 4 deletions test/css/permissive-parse.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
basically anything until final semi-colon;
even other stuff; // i\'m serious;
};
--custom-color: #ff3333;
custom-color: #ff3333;
--custom-color: #ff3333 #ff3333;
custom-color: #ff3333 #ff3333;
}
.var {
--fortran: read (*, *, iostat=1) radius, height;
Expand All @@ -30,7 +30,6 @@
}
}
.test-comment {
--value: ;
--value: a /* { ; } */;
--comment-within: ( /* okay?; comment; */ );
--empty: ;
}
3 changes: 3 additions & 0 deletions test/css/plugin-preeval.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:root.two .one {
--foo: bar !important;
}
7 changes: 3 additions & 4 deletions test/less/errors/at-rules-unmatching-block.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
SyntaxError: @unknown rule is missing block or ending semi-colon in {path}at-rules-unmatching-block.less on line 2, column 10:
1
2 @unknown url( {
3 50% {width: 20px;}
SyntaxError: expected ')' got '' in {path}at-rules-unmatching-block.less on line 5, column 1:
4 }
5
2 changes: 1 addition & 1 deletion test/less/media.less
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
@ratio_large: 16;
@ratio_small: 9;

@media all and (device-aspect-ratio: ~"@{ratio_large} / @{ratio_small}") {
@media all and (device-aspect-ratio: @ratio_large / @ratio_small) {
body { max-width: 800px; }
}

Expand Down
5 changes: 2 additions & 3 deletions test/less/permissive-parse.less
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
};
--that: @this;
@red: lighten(red, 10%);
--custom-color: @red;
--custom-color: @red lighten(red, 10%);
custom-color: $--custom-color;
}

Expand Down Expand Up @@ -45,7 +45,6 @@
}
// @todo - fix comment absorption after property
.test-comment {
--value: /* { ; } */;
--value: a/* { ; } */;
--comment-within: ( /* okay?; comment; */ );
--empty: ;
}
15 changes: 15 additions & 0 deletions test/less/plugin-preeval.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@plugin "./plugin/plugin-preeval";

.two(@rules: {}) {
:root.two & {
@rules();
}
}

.one {
.two({
--foo: @replace !important;
});
}

@stop: end;
Loading