Skip to content

Commit 566f428

Browse files
authored
Fixes #3205, partial 3.0 math regression #1880 (#3228)
* Fixes Mixin call args not being visited * Add ability to use ES6 in tests * Fixes #3205 and partial 3.0 math regression #1880
1 parent b8140d4 commit 566f428

15 files changed

+175
-51
lines changed

.eslintrc.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@
33
"node": true
44
},
55
"globals": {},
6+
67
"rules": {
7-
"quotes": [
8-
1,
9-
"single"
10-
],
118
"no-eval": 2,
129
"no-use-before-define": [
1310
2,

Gruntfile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ module.exports = function (grunt) {
295295
main: {
296296
// src is used to build list of less files to compile
297297
src: [
298-
'test/less/*.less',
298+
'test/less/plugin.less',
299299
// Don't test NPM import, obviously
300300
'!test/less/plugin-module.less',
301301
'!test/less/import-module.less',

lib/less/parser/parser-input.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,6 @@ module.exports = function() {
239239
case '/':
240240
if (input.charAt(i + 1) === '*') {
241241
i++;
242-
console.log(input.substr(lastPos, i - lastPos));
243242
inComment = true;
244243
blockDepth++;
245244
}
@@ -324,6 +323,10 @@ module.exports = function() {
324323
return input.charAt(parserInput.i);
325324
};
326325

326+
parserInput.prevChar = function() {
327+
return input.charAt(parserInput.i - 1);
328+
};
329+
327330
parserInput.getInput = function() {
328331
return input;
329332
};

lib/less/parser/parser.js

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,7 +1302,7 @@ var Parser = function Parser(context, imports, fileInfo) {
13021302

13031303
// Custom property values get permissive parsing
13041304
if (name[0].value && name[0].value.slice(0, 2) === '--') {
1305-
value = this.permissiveValue(';');
1305+
value = this.permissiveValue();
13061306
}
13071307
// Try to store values as anonymous
13081308
// If we need the value later we'll re-parse it in ruleset.parseValue
@@ -1318,13 +1318,12 @@ var Parser = function Parser(context, imports, fileInfo) {
13181318
if (!value) {
13191319
value = this.value();
13201320
}
1321-
1322-
important = this.important();
1323-
1324-
// As a last resort, let a variable try to be parsed as a permissive value
1321+
// As a last resort, try permissiveValue
13251322
if (!value && isVariable) {
1326-
value = this.permissiveValue(';');
1323+
value = this.permissiveValue();
13271324
}
1325+
1326+
important = this.important();
13281327
}
13291328

13301329
if (value && this.end()) {
@@ -1346,27 +1345,76 @@ var Parser = function Parser(context, imports, fileInfo) {
13461345
}
13471346
},
13481347
/**
1349-
* Used for custom properties and custom at-rules
1348+
* Used for custom properties, at-rules, and variables (as fallback)
13501349
* Parses almost anything inside of {} [] () "" blocks
13511350
* until it reaches outer-most tokens.
1351+
*
1352+
* First, it will try to parse comments and entities to reach
1353+
* the end. This is mostly like the Expression parser except no
1354+
* math is allowed.
13521355
*/
13531356
permissiveValue: function (untilTokens) {
1354-
var i, index = parserInput.i,
1355-
value = parserInput.$parseUntil(untilTokens);
1357+
var i, e, done, value,
1358+
tok = untilTokens || ';',
1359+
index = parserInput.i, result = [];
1360+
1361+
function testCurrentChar() {
1362+
var char = parserInput.currentChar();
1363+
if (typeof tok === 'string') {
1364+
return char === tok;
1365+
} else {
1366+
return tok.test(char);
1367+
}
1368+
}
1369+
if (testCurrentChar()) {
1370+
return;
1371+
}
1372+
value = [];
1373+
do {
1374+
e = this.comment();
1375+
if (e) {
1376+
value.push(e);
1377+
continue;
1378+
}
1379+
e = this.entity();
1380+
if (e) {
1381+
value.push(e);
1382+
}
1383+
} while (e);
1384+
1385+
done = testCurrentChar();
1386+
1387+
if (value.length > 0) {
1388+
value = new(tree.Expression)(value);
1389+
if (done) {
1390+
return value;
1391+
}
1392+
else {
1393+
result.push(value);
1394+
}
1395+
// Preserve space before $parseUntil as it will not
1396+
if (parserInput.prevChar() === ' ') {
1397+
result.push(new tree.Anonymous(' ', index));
1398+
}
1399+
}
1400+
parserInput.save();
1401+
1402+
value = parserInput.$parseUntil(tok);
13561403

13571404
if (value) {
13581405
if (typeof value === 'string') {
13591406
error('Expected \'' + value + '\'', 'Parse');
13601407
}
13611408
if (value.length === 1 && value[0] === ' ') {
1409+
parserInput.forget();
13621410
return new tree.Anonymous('', index);
13631411
}
1364-
var item, args = [];
1412+
var item;
13651413
for (i = 0; i < value.length; i++) {
13661414
item = value[i];
13671415
if (Array.isArray(item)) {
13681416
// Treat actual quotes as normal quoted values
1369-
args.push(new tree.Quoted(item[0], item[1], true, index, fileInfo));
1417+
result.push(new tree.Quoted(item[0], item[1], true, index, fileInfo));
13701418
}
13711419
else {
13721420
if (i === value.length - 1) {
@@ -1376,12 +1424,13 @@ var Parser = function Parser(context, imports, fileInfo) {
13761424
var quote = new tree.Quoted('\'', item, true, index, fileInfo);
13771425
quote.variableRegex = /@([\w-]+)/g;
13781426
quote.propRegex = /\$([\w-]+)/g;
1379-
quote.reparse = true;
1380-
args.push(quote);
1427+
result.push(quote);
13811428
}
13821429
}
1383-
return new tree.Expression(args, true);
1430+
parserInput.forget();
1431+
return new tree.Expression(result, true);
13841432
}
1433+
parserInput.restore();
13851434
},
13861435

13871436
//
@@ -1463,7 +1512,7 @@ var Parser = function Parser(context, imports, fileInfo) {
14631512
nodes.push(e);
14641513
} else if (parserInput.$char('(')) {
14651514
p = this.property();
1466-
e = this.value();
1515+
e = this.permissiveValue(')');
14671516
if (parserInput.$char(')')) {
14681517
if (p && e) {
14691518
nodes.push(new(tree.Paren)(new(tree.Declaration)(p, e, null, null, parserInput.i, fileInfo, true)));

lib/less/transform-tree.js

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,29 +41,49 @@ module.exports = function(root, options) {
4141
new visitor.MarkVisibleSelectorsVisitor(true),
4242
new visitor.ExtendVisitor(),
4343
new visitor.ToCSSVisitor({compress: Boolean(options.compress)})
44-
], v, visitorIterator;
44+
], preEvalVisitors = [], v, visitorIterator;
4545

46-
// first() / get() allows visitors to be added while visiting
46+
/**
47+
* first() / get() allows visitors to be added while visiting
48+
*
49+
* @todo Add scoping for visitors just like functions for @plugin; right now they're global
50+
*/
4751
if (options.pluginManager) {
4852
visitorIterator = options.pluginManager.visitor();
49-
visitorIterator.first();
50-
while ((v = visitorIterator.get())) {
51-
if (v.isPreEvalVisitor) {
52-
v.run(root);
53+
for (var i = 0; i < 2; i++) {
54+
visitorIterator.first();
55+
while ((v = visitorIterator.get())) {
56+
if (v.isPreEvalVisitor) {
57+
if (i === 0 || preEvalVisitors.indexOf(v) === -1) {
58+
preEvalVisitors.push(v);
59+
v.run(root);
60+
}
61+
}
62+
else {
63+
if (i === 0 || visitors.indexOf(v) === -1) {
64+
if (v.isPreVisitor) {
65+
visitors.unshift(v);
66+
}
67+
else {
68+
visitors.push(v);
69+
}
70+
}
71+
}
5372
}
5473
}
5574
}
56-
75+
5776
evaldRoot = root.eval(evalEnv);
5877

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

82+
// Run any remaining visitors added after eval pass
6383
if (options.pluginManager) {
6484
visitorIterator.first();
6585
while ((v = visitorIterator.get())) {
66-
if (!v.isPreEvalVisitor) {
86+
if (visitors.indexOf(v) === -1 && preEvalVisitors.indexOf(v) === -1) {
6787
v.run(evaldRoot);
6888
}
6989
}

lib/less/tree/expression.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ Expression.prototype.eval = function (context) {
2424
}
2525
if (this.value.length > 1) {
2626
returnValue = new Expression(this.value.map(function (e) {
27+
if (!e.eval) {
28+
return e;
29+
}
2730
return e.eval(context);
2831
}), this.noSpacing);
2932
} else if (this.value.length === 1) {

lib/less/visitors/visitor.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ function indexNodeTypes(parent, ticker) {
3232

3333
var Visitor = function(implementation) {
3434
this._implementation = implementation;
35-
this._visitFnCache = [];
35+
this._visitInCache = {};
36+
this._visitOutCache = {};
3637

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

4950
var nodeTypeIndex = node.typeIndex;
5051
if (!nodeTypeIndex) {
52+
// MixinCall args aren't a node type?
53+
if (node.value && node.value.typeIndex) {
54+
this.visit(node.value);
55+
}
5156
return node;
5257
}
5358

54-
var visitFnCache = this._visitFnCache,
55-
impl = this._implementation,
56-
aryIndx = nodeTypeIndex << 1,
57-
outAryIndex = aryIndx | 1,
58-
func = visitFnCache[aryIndx],
59-
funcOut = visitFnCache[outAryIndex],
59+
var impl = this._implementation,
60+
func = this._visitInCache[nodeTypeIndex],
61+
funcOut = this._visitOutCache[nodeTypeIndex],
6062
visitArgs = _visitArgs,
6163
fnName;
6264

@@ -66,13 +68,13 @@ Visitor.prototype = {
6668
fnName = 'visit' + node.type;
6769
func = impl[fnName] || _noop;
6870
funcOut = impl[fnName + 'Out'] || _noop;
69-
visitFnCache[aryIndx] = func;
70-
visitFnCache[outAryIndex] = funcOut;
71+
this._visitInCache[nodeTypeIndex] = func;
72+
this._visitOutCache[nodeTypeIndex] = funcOut;
7173
}
7274

7375
if (func !== _noop) {
7476
var newNode = func.call(impl, node, visitArgs);
75-
if (impl.isReplacing) {
77+
if (node && impl.isReplacing) {
7678
node = newNode;
7779
}
7880
}

test/.eslintrc.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"env": {
3+
"node": true,
4+
"browser": true
5+
},
6+
"parserOptions": {
7+
"ecmaVersion": 6
8+
}
9+
}

test/css/permissive-parse.css

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
basically anything until final semi-colon;
1313
even other stuff; // i\'m serious;
1414
};
15-
--custom-color: #ff3333;
16-
custom-color: #ff3333;
15+
--custom-color: #ff3333 #ff3333;
16+
custom-color: #ff3333 #ff3333;
1717
}
1818
.var {
1919
--fortran: read (*, *, iostat=1) radius, height;
@@ -32,7 +32,6 @@ foo[attr="blah"] {
3232
}
3333
}
3434
.test-comment {
35-
--value: ;
35+
--value: a /* { ; } */;
3636
--comment-within: ( /* okay?; comment; */ );
37-
--empty: ;
3837
}

test/css/plugin-preeval.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:root.two .one {
2+
--foo: bar !important;
3+
}
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
SyntaxError: @unknown rule is missing block or ending semi-colon in {path}at-rules-unmatching-block.less on line 2, column 10:
2-
1
3-
2 @unknown url( {
4-
3 50% {width: 20px;}
1+
SyntaxError: expected ')' got '' in {path}at-rules-unmatching-block.less on line 5, column 1:
2+
4 }
3+
5

test/less/media.less

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
@ratio_large: 16;
2424
@ratio_small: 9;
2525

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

test/less/permissive-parse.less

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
};
1818
--that: @this;
1919
@red: lighten(red, 10%);
20-
--custom-color: @red;
20+
--custom-color: @red lighten(red, 10%);
2121
custom-color: $--custom-color;
2222
}
2323

@@ -45,7 +45,6 @@
4545
}
4646
// @todo - fix comment absorption after property
4747
.test-comment {
48-
--value: /* { ; } */;
48+
--value: a/* { ; } */;
4949
--comment-within: ( /* okay?; comment; */ );
50-
--empty: ;
5150
}

test/less/plugin-preeval.less

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@plugin "./plugin/plugin-preeval";
2+
3+
.two(@rules: {}) {
4+
:root.two & {
5+
@rules();
6+
}
7+
}
8+
9+
.one {
10+
.two({
11+
--foo: @replace !important;
12+
});
13+
}
14+
15+
@stop: end;

0 commit comments

Comments
 (0)