Skip to content

Commit

Permalink
[Feature] Namespaced values (#3242)
Browse files Browse the repository at this point in the history
* calc() fix - fixes #974
* Parses and retrieves a namespaced value
* Adds a bunch of new tests for aliasing and namespacing
* Added more CSS Grid tests
* Added tests for passing mixins into mixins, since it's just another value
* Release v3.5.0-beta.4
  • Loading branch information
matthew-dean committed Jun 30, 2018
1 parent 99162b6 commit 6237e13
Show file tree
Hide file tree
Showing 37 changed files with 959 additions and 153 deletions.
16 changes: 16 additions & 0 deletions .vscode/launch.json
@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Less test",
"program": "${workspaceFolder}/test/index.js",
"cwd": "${workspaceFolder}",
"console": "integratedTerminal"
}
]
}
461 changes: 356 additions & 105 deletions dist/less.js

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions dist/less.min.js

Large diffs are not rendered by default.

186 changes: 154 additions & 32 deletions lib/less/parser/parser.js
Expand Up @@ -573,11 +573,26 @@ var Parser = function Parser(context, imports, fileInfo) {
// see `parsers.variable`.
//
variable: function () {
var name, index = parserInput.i;
var ch, name, index = parserInput.i;

parserInput.save();
if (parserInput.currentChar() === '@' && (name = parserInput.$re(/^@@?[\w-]+/))) {
ch = parserInput.currentChar();
if (ch === '(' || ch === '[') {
// this may be a VariableCall lookup
var result = parsers.variableCall(name);
if (!result) {
return parserInput.restore();
}
else {
parserInput.forget();
return result;
}
}
parserInput.forget();
return new(tree.Variable)(name, index, fileInfo);
}
parserInput.restore();
},

// A variable entity using the protective {} e.g. @{var}
Expand Down Expand Up @@ -718,18 +733,49 @@ var Parser = function Parser(context, imports, fileInfo) {
},

//
// Call a variable value
// Call a variable value to retrieve a detached ruleset
// or a value from a detached ruleset's rules.
//
// @fink()
// @fink();
// @fink;
// color: @fink[@color];
//
variableCall: function () {
var name;
variableCall: function (parsedName) {
var lookups, important, i = parserInput.i,
inValue = !!parsedName, name = parsedName;

parserInput.save();

if (name || (parserInput.currentChar() === '@'
&& (name = parserInput.$re(/^(@[\w-]+)(\(\s*\))?/)))) {

lookups = this.mixin.ruleLookups();

if (parserInput.currentChar() === '@'
&& (name = parserInput.$re(/^(@[\w-]+)\(\s*\)/))
&& parsers.end()) {
return new tree.VariableCall(name[1]);
if (!lookups && name[2] !== '()') {
parserInput.restore('Missing \'[...]\' lookup in variable call');
return;
}

if (!inValue) {
name = name[1];
}

if (lookups && parsers.important()) {
important = true;
}

var call = new tree.VariableCall(name, i, fileInfo);
if (!inValue && parsers.end()) {
parserInput.forget();
return call;
}
else {
parserInput.forget();
return new tree.NamespaceValue(call, lookups, important, i, fileInfo);
}
}

parserInput.restore();
},

//
Expand Down Expand Up @@ -793,54 +839,85 @@ var Parser = function Parser(context, imports, fileInfo) {
// A Mixin call, with an optional argument list
//
// #mixins > .square(#fff);
// #mixins.square(#fff);
// .rounded(4px, black);
// .button;
//
// We can lookup / return a value using the lookup syntax:
//
// color: #mixin.square(#fff)[@color];
//
// The `while` loop is there because mixins can be
// namespaced, but we only support the child and descendant
// selector for now.
//
call: function () {
var s = parserInput.currentChar(), important = false, index = parserInput.i, elemIndex,
elements, elem, e, c, args;
call: function (inValue) {
var s = parserInput.currentChar(), important = false,
index = parserInput.i, elements, args, hasParens;

if (s !== '.' && s !== '#') { return; }

parserInput.save(); // stop us absorbing part of an invalid selector

while (true) {
elemIndex = parserInput.i;
e = parserInput.$re(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/);
if (!e) {
break;
}
elem = new(tree.Element)(c, e, false, elemIndex, fileInfo);
if (elements) {
elements.push(elem);
} else {
elements = [ elem ];
}
c = parserInput.$char('>');
}
elements = this.elements();

if (elements) {
if (parserInput.$char('(')) {
args = this.args(true).args;
expectChar(')');
hasParens = true;
}

var lookups = this.ruleLookups();

if (inValue && !lookups && !hasParens) {
// This isn't a valid in-value mixin call
parserInput.restore();
return;
}

if (parsers.important()) {
if (!inValue && parsers.important()) {
important = true;
}

if (parsers.end()) {
if (inValue || parsers.end()) {
parserInput.forget();
return new(tree.mixin.Call)(elements, args, index, fileInfo, important);
var mixin = new(tree.mixin.Call)(elements, args, index, fileInfo, !lookups && important);
if (lookups) {
return new tree.NamespaceValue(mixin, lookups, important);
}
else {
return mixin;
}
}
}

parserInput.restore();
},
/**
* Matching elements for mixins
* (Start with . or # and can have > )
*/
elements: function() {
var elements, e, c, elem, elemIndex,
re = /^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/;
while (true) {
elemIndex = parserInput.i;
e = parserInput.$re(re);

if (!e) {
break;
}
elem = new(tree.Element)(c, e, false, elemIndex, fileInfo);
if (elements) {
elements.push(elem);
} else {
elements = [ elem ];
}
c = parserInput.$char('>');
}
return elements;
},
args: function (isCall) {
var entities = parsers.entities,
returner = { args:null, variadic: false },
Expand Down Expand Up @@ -1022,9 +1099,54 @@ var Parser = function Parser(context, imports, fileInfo) {
} else {
parserInput.forget();
}
},

ruleLookups: function() {
var rule, args, lookups = [];

if (parserInput.currentChar() !== '[') {
return;
}

while (true) {
parserInput.save();
args = null;
rule = this.lookupValue();
if (!rule) {
parserInput.restore();
break;
}
lookups.push(rule);
parserInput.forget();
}
if (lookups.length > 0) {
return lookups;
}
},

lookupValue: function() {
parserInput.save();

if (!parserInput.$char('[')) {
parserInput.restore();
return;
}

var name = parserInput.$re(/^(?:@{0,2}|\$)[_a-zA-Z0-9-]+/);

if (!parserInput.$char(']')) {
parserInput.restore();
return;
}

if (name) {
parserInput.forget();
return name;
}

parserInput.restore();
}
},

//
// Entities are the smallest recognized token,
// and can be found inside a rule's value.
Expand All @@ -1033,7 +1155,7 @@ var Parser = function Parser(context, imports, fileInfo) {
var entities = this.entities;

return this.comment() || entities.literal() || entities.variable() || entities.url() ||
entities.property() || entities.call() || entities.keyword() || entities.javascript();
entities.property() || entities.call() || entities.keyword() || this.mixin.call(true) || entities.javascript();
},

//
Expand Down Expand Up @@ -1339,7 +1461,7 @@ var Parser = function Parser(context, imports, fileInfo) {
},
anonymousValue: function () {
var index = parserInput.i;
var match = parserInput.$re(/^([^@\$+\/'"*`(;{}-]*);/);
var match = parserInput.$re(/^([^.#@\$+\/'"*`(;{}-]*);/);
if (match) {
return new(tree.Anonymous)(match[1], index);
}
Expand Down
1 change: 1 addition & 0 deletions lib/less/tree/index.js
Expand Up @@ -37,5 +37,6 @@ tree.UnicodeDescriptor = require('./unicode-descriptor');
tree.Negative = require('./negative');
tree.Extend = require('./extend');
tree.VariableCall = require('./variable-call');
tree.NamespaceValue = require('./namespace-value');

module.exports = tree;
2 changes: 2 additions & 0 deletions lib/less/tree/mixin-call.js
Expand Up @@ -28,6 +28,8 @@ MixinCall.prototype.eval = function (context) {
candidates = [], candidate, conditionResult = [], defaultResult, defFalseEitherCase = -1,
defNone = 0, defTrue = 1, defFalse = 2, count, originalRuleset, noArgumentsFilter;

this.selector = this.selector.eval(context);

function calcDefGroup(mixin, mixinPath) {
var f, p, namespace;

Expand Down
9 changes: 8 additions & 1 deletion lib/less/tree/mixin-definition.js
Expand Up @@ -2,6 +2,7 @@ var Selector = require('./selector'),
Element = require('./element'),
Ruleset = require('./ruleset'),
Declaration = require('./declaration'),
DetachedRuleset = require('./detached-ruleset'),
Expression = require('./expression'),
contexts = require('../contexts'),
utils = require('../utils');
Expand Down Expand Up @@ -97,7 +98,13 @@ Definition.prototype.evalParams = function (context, mixinEnv, args, evaldArgume
} else {
val = arg && arg.value;
if (val) {
val = val.eval(context);
// This was a mixin call, pass in a detached ruleset of it's eval'd rules
if (Array.isArray(val)) {
val = new DetachedRuleset(new Ruleset('', val));
}
else {
val = val.eval(context);
}
} else if (params[i].value) {
val = params[i].value.eval(mixinEnv);
frame.resetCache();
Expand Down

0 comments on commit 6237e13

Please sign in to comment.