Merge branch 'master' into 2_0_0

Conflicts:
	package.json
This commit is contained in:
Luke Page
2014-02-22 15:31:32 +00:00
38 changed files with 581 additions and 696 deletions

View File

@@ -218,19 +218,25 @@ tree.functions = {
escape: function (str) {
return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29"));
},
'%': function (quoted /* arg, arg, ...*/) {
replace: function (string, pattern, replacement, flags) {
var result = string.value;
result = result.replace(new RegExp(pattern.value, flags ? flags.value : ''), replacement.value);
return new(tree.Quoted)(string.quote || '', result, string.escaped);
},
'%': function (string /* arg, arg, ...*/) {
var args = Array.prototype.slice.call(arguments, 1),
str = quoted.value;
result = string.value;
for (var i = 0; i < args.length; i++) {
/*jshint loopfunc:true */
str = str.replace(/%[sda]/i, function(token) {
result = result.replace(/%[sda]/i, function(token) {
var value = token.match(/s/i) ? args[i].value : args[i].toCSS();
return token.match(/[A-Z]$/) ? encodeURIComponent(value) : value;
});
}
str = str.replace(/%%/g, '%');
return new(tree.Quoted)('"' + str + '"', str);
result = result.replace(/%%/g, '%');
return new(tree.Quoted)(string.quote || '', result, string.escaped);
},
unit: function (val, unit) {
if(!(val instanceof tree.Dimension)) {

View File

@@ -94,6 +94,7 @@ var less = {
require('./tree/color');
require('./tree/directive');
require('./tree/detached-ruleset');
require('./tree/operation');
require('./tree/dimension');
require('./tree/keyword');
@@ -120,6 +121,7 @@ require('./tree/media');
require('./tree/unicode-descriptor');
require('./tree/negative');
require('./tree/extend');
require('./tree/ruleset-call');
var isUrlRe = /^(?:https?:)?\/\//i;

View File

@@ -43,8 +43,7 @@ less.Parser = function Parser(env) {
var input, // LeSS input string
i, // current index in `input`
j, // current chunk
temp, // temporarily holds a chunk's state, for backtracking
memo, // temporarily holds `i`, when backtracking
saveStack = [], // holds state for backtracking
furthest, // furthest index the parser has gone to
chunks, // chunkified input
current, // current chunk
@@ -111,8 +110,9 @@ less.Parser = function Parser(env) {
}
};
function save() { temp = current; memo = currentPos = i; }
function restore() { current = temp; currentPos = i = memo; }
function save() { currentPos = i; saveStack.push( { current: current, i: i, j: j }); }
function restore() { var state = saveStack.pop(); current = state.current; currentPos = i = state.i; j = state.j; }
function forget() { saveStack.pop(); }
function sync() {
if (i > currentPos) {
@@ -418,7 +418,7 @@ less.Parser = function Parser(env) {
if (--level < 0) {
return fail("missing opening `{`");
}
if (!level) { emitChunk(); }
if (!level && !parenLevel) { emitChunk(); }
continue;
case 92: // \
if (parserCurrentIndex < len - 1) { parserCurrentIndex++; continue; }
@@ -723,7 +723,7 @@ less.Parser = function Parser(env) {
while (current)
{
node = this.extendRule() || mixin.definition() || this.rule() || this.ruleset() ||
mixin.call() || this.comment() || this.directive();
mixin.call() || this.comment() || this.rulesetCall() || this.directive();
if (node) {
root.push(node);
} else {
@@ -731,6 +731,9 @@ less.Parser = function Parser(env) {
break;
}
}
if (peekChar('}')) {
break;
}
}
return root;
@@ -1015,6 +1018,19 @@ less.Parser = function Parser(env) {
if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*:/))) { return name[1]; }
},
//
// The variable part of a variable definition. Used in the `rule` parser
//
// @fink();
//
rulesetCall: function () {
var name;
if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*\(\s*\)\s*;/))) {
return new tree.RulesetCall(name[1]);
}
},
//
// extend syntax - used to extend selectors
//
@@ -1100,6 +1116,7 @@ less.Parser = function Parser(env) {
}
if (parsers.end()) {
forget();
return new(tree.mixin.Call)(elements, args, index, env.currentFileInfo, important);
}
}
@@ -1112,9 +1129,11 @@ less.Parser = function Parser(env) {
expressions = [], argsSemiColon = [], argsComma = [],
isSemiColonSeperated, expressionContainsNamed, name, nameLoop, value, arg;
save();
while (true) {
if (isCall) {
arg = parsers.expression();
arg = parsers.detachedRuleset() || parsers.expression();
} else {
parsers.comments();
if (input.charAt(i) === '.' && $re(/^\.{3}/)) {
@@ -1142,7 +1161,7 @@ less.Parser = function Parser(env) {
if (isCall) {
// Variable
if (arg.value.length == 1) {
if (arg.value && arg.value.length == 1) {
val = arg.value[0];
}
} else {
@@ -1157,7 +1176,21 @@ less.Parser = function Parser(env) {
}
expressionContainsNamed = true;
}
value = expect(parsers.expression);
// we do not support setting a ruleset as a default variable - it doesn't make sense
// However if we do want to add it, there is nothing blocking it, just don't error
// and remove isCall dependency below
value = (isCall && parsers.detachedRuleset()) || parsers.expression();
if (!value) {
if (isCall) {
error("could not understand value for named argument");
} else {
restore();
returner.args = [];
return returner;
}
}
nameLoop = (name = val.name);
} else if (!isCall && $re(/^\.{3}/)) {
returner.variadic = true;
@@ -1202,6 +1235,7 @@ less.Parser = function Parser(env) {
}
}
forget();
returner.args = isSemiColonSeperated ? argsSemiColon : argsComma;
return returner;
},
@@ -1242,10 +1276,14 @@ less.Parser = function Parser(env) {
variadic = argInfo.variadic;
// .mixincall("@{a}");
// looks a bit like a mixin definition.. so we have to be nice and restore
// looks a bit like a mixin definition..
// also
// .mixincall(@a: {rule: set;});
// so we have to be nice and restore
if (!$char(')')) {
furthest = i;
restore();
return;
}
parsers.comments();
@@ -1257,10 +1295,13 @@ less.Parser = function Parser(env) {
ruleset = parsers.block();
if (ruleset) {
forget();
return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic);
} else {
restore();
}
} else {
forget();
}
}
},
@@ -1324,10 +1365,16 @@ less.Parser = function Parser(env) {
this.entities.variableCurly();
if (! e) {
save();
if ($char('(')) {
if ((v = this.selector()) && $char(')')) {
e = new(tree.Paren)(v);
forget();
} else {
restore();
}
} else {
forget();
}
}
@@ -1430,6 +1477,22 @@ less.Parser = function Parser(env) {
}
},
blockRuleset: function() {
var block = this.block();
if (block) {
block = new tree.Ruleset(null, block);
}
return block;
},
detachedRuleset: function() {
var blockRuleset = this.blockRuleset();
if (blockRuleset) {
return new tree.DetachedRuleset(blockRuleset);
}
},
//
// div, .class, body > p {...}
//
@@ -1460,6 +1523,7 @@ less.Parser = function Parser(env) {
}
if (selectors && (rules = this.block())) {
forget();
var ruleset = new(tree.Ruleset)(selectors, rules, env.strictImports);
if (env.dumpLineNumbers) {
ruleset.debugInfo = debugInfo;
@@ -1472,28 +1536,38 @@ less.Parser = function Parser(env) {
}
},
rule: function (tryAnonymous) {
var name, value, c = input.charAt(i), important, merge;
save();
var name, value, startOfRule = i, c = input.charAt(startOfRule), important, merge, isVariable;
if (c === '.' || c === '#' || c === '&') { return; }
save();
name = this.variable() || this.ruleProperty();
if (name) {
// prefer to try to parse first if its a variable or we are compressing
// but always fallback on the other one
value = !tryAnonymous && (env.compress || (name.charAt && (name.charAt(0) === '@'))) ?
(this.value() || this.anonymousValue()) :
(this.anonymousValue() || this.value());
important = this.important();
isVariable = typeof name === "string";
// a name returned by this.ruleProperty() is always an array of the form:
// [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
// where each item is a tree.Keyword or tree.Variable
merge = name.pop && name.pop().value;
if (isVariable) {
value = this.detachedRuleset();
}
if (!value) {
// prefer to try to parse first if its a variable or we are compressing
// but always fallback on the other one
value = !tryAnonymous && (env.compress || isVariable) ?
(this.value() || this.anonymousValue()) :
(this.anonymousValue() || this.value());
important = this.important();
// a name returned by this.ruleProperty() is always an array of the form:
// [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
// where each item is a tree.Keyword or tree.Variable
merge = !isVariable && name.pop().value;
}
if (value && this.end()) {
return new (tree.Rule)(name, value, important, merge, memo, env.currentFileInfo);
forget();
return new (tree.Rule)(name, value, important, merge, startOfRule, env.currentFileInfo);
} else {
furthest = i;
restore();
@@ -1501,6 +1575,8 @@ less.Parser = function Parser(env) {
return this.rule(true);
}
}
} else {
forget();
}
},
anonymousValue: function () {
@@ -1534,6 +1610,7 @@ less.Parser = function Parser(env) {
if (dir && (path = this.entities.quoted() || this.entities.url())) {
features = this.mediaFeatures();
if ($char(';')) {
forget();
features = features && new(tree.Value)(features);
return new(tree.Import)(path, features, options, index, env.currentFileInfo);
}
@@ -1730,13 +1807,11 @@ less.Parser = function Parser(env) {
}
if (hasBlock) {
rules = this.block();
if (rules) {
rules = new(tree.Ruleset)(null, rules);
}
rules = this.blockRuleset();
}
if (rules || (!hasBlock && value && $char(';'))) {
forget();
return new(tree.Directive)(name, value, rules, index, env.currentFileInfo,
env.dumpLineNumbers ? getDebugInfo(index, input, env) : null);
}

View File

@@ -0,0 +1,20 @@
(function (tree) {
tree.DetachedRuleset = function (ruleset, frames) {
this.ruleset = ruleset;
this.frames = frames;
};
tree.DetachedRuleset.prototype = {
type: "DetachedRuleset",
accept: function (visitor) {
this.ruleset = visitor.visit(this.ruleset);
},
eval: function (env) {
var frames = this.frames || env.frames.slice(0);
return new tree.DetachedRuleset(this.ruleset, frames);
},
callEval: function (env) {
return this.ruleset.eval(this.frames ? new(tree.evalEnv)(env, this.frames.concat(env.frames)) : env);
}
};
})(require('../tree'));

View File

@@ -74,6 +74,7 @@ tree.Media.prototype = {
},
markReferenced: function () {
var i, rules = this.rules[0].rules;
this.rules[0].markReferenced();
this.isReferenced = true;
for (i = 0; i < rules.length; i++) {
if (rules[i].markReferenced) {
@@ -147,6 +148,8 @@ tree.Media.prototype = {
}
},
bubbleSelectors: function (selectors) {
if (!selectors)
return;
this.rules = [new(tree.Ruleset)(selectors.slice(0), [this.rules[0]])];
}
};

View File

@@ -103,7 +103,7 @@ tree.mixin.Call.prototype = {
mixin.originalRuleset = mixins[m].originalRuleset || mixins[m];
}
Array.prototype.push.apply(
rules, mixin.eval(env, args, this.important).rules);
rules, mixin.evalCall(env, args, this.important).rules);
} catch (e) {
throw { message: e.message, index: this.index, filename: this.currentFileInfo.filename, stack: e.stack };
}
@@ -150,7 +150,7 @@ tree.mixin.Call.prototype = {
}
};
tree.mixin.Definition = function (name, params, rules, condition, variadic) {
tree.mixin.Definition = function (name, params, rules, condition, variadic, frames) {
this.name = name;
this.selectors = [new(tree.Selector)([new(tree.Element)(null, name, this.index, this.currentFileInfo)])];
this.params = params;
@@ -164,7 +164,7 @@ tree.mixin.Definition = function (name, params, rules, condition, variadic) {
else { return count; }
}, 0);
this.parent = tree.Ruleset.prototype;
this.frames = [];
this.frames = frames;
};
tree.mixin.Definition.prototype = {
type: "MixinDefinition",
@@ -258,9 +258,12 @@ tree.mixin.Definition.prototype = {
return frame;
},
eval: function (env, args, important) {
eval: function (env) {
return new tree.mixin.Definition(this.name, this.params, this.rules, this.condition, this.variadic, this.frames || env.frames.slice(0));
},
evalCall: function (env, args, important) {
var _arguments = [],
mixinFrames = this.frames.concat(env.frames),
mixinFrames = this.frames ? this.frames.concat(env.frames) : env.frames,
frame = this.evalParams(env, new(tree.evalEnv)(env, mixinFrames), args, _arguments),
rules, ruleset;

View File

@@ -2,7 +2,7 @@
tree.Rule = function (name, value, important, merge, index, currentFileInfo, inline) {
this.name = name;
this.value = (value instanceof tree.Value) ? value : new(tree.Value)([value]);
this.value = (value instanceof tree.Value || value instanceof tree.Ruleset) ? value : new(tree.Value)([value]);
this.important = important ? ' ' + important.trim() : '';
this.merge = merge;
this.index = index;
@@ -30,7 +30,7 @@ tree.Rule.prototype = {
},
toCSS: tree.toCSS,
eval: function (env) {
var strictMathBypass = false, name = this.name;
var strictMathBypass = false, name = this.name, evaldValue;
if (typeof name !== "string") {
// expand 'primitive' name directly to get
// things faster (~10% for benchmark.less):
@@ -43,14 +43,24 @@ tree.Rule.prototype = {
env.strictMath = true;
}
try {
evaldValue = this.value.eval(env);
if (!this.variable && evaldValue.type === "DetachedRuleset") {
throw { message: "Rulesets cannot be evaluated on a property.",
index: this.index, filename: this.currentFileInfo.filename };
}
return new(tree.Rule)(name,
this.value.eval(env),
evaldValue,
this.important,
this.merge,
this.index, this.currentFileInfo, this.inline);
}
catch(e) {
e.index = e.index || this.index;
if (typeof e.index !== 'number') {
e.index = this.index;
e.filename = this.currentFileInfo.filename;
}
throw e;
}
finally {

View File

@@ -0,0 +1,16 @@
(function (tree) {
tree.RulesetCall = function (variable) {
this.variable = variable;
};
tree.RulesetCall.prototype = {
type: "RulesetCall",
accept: function (visitor) {
},
eval: function (env) {
var detachedRuleset = new(tree.Variable)(this.variable).eval(env);
return detachedRuleset.callEval(env);
}
};
})(require('../tree'));

View File

@@ -20,7 +20,8 @@ tree.Ruleset.prototype = {
},
eval: function (env) {
var thisSelectors = this.selectors, selectors,
selCnt, i, defaultFunc = tree.defaultFunc;
selCnt, selector, i, defaultFunc = tree.defaultFunc, hasOnePassingSelector = false;
if (thisSelectors && (selCnt = thisSelectors.length)) {
selectors = [];
defaultFunc.error({
@@ -28,9 +29,15 @@ tree.Ruleset.prototype = {
message: "it is currently only allowed in parametric mixin guards,"
});
for (i = 0; i < selCnt; i++) {
selectors.push(thisSelectors[i].eval(env));
selector = thisSelectors[i].eval(env);
selectors.push(selector);
if (selector.evaldCondition) {
hasOnePassingSelector = true;
}
}
defaultFunc.reset();
} else {
hasOnePassingSelector = true;
}
var rules = this.rules ? this.rules.slice(0) : null,
@@ -45,6 +52,10 @@ tree.Ruleset.prototype = {
if(this.debugInfo) {
ruleset.debugInfo = this.debugInfo;
}
if (!hasOnePassingSelector) {
rules.length = 0;
}
// push the current ruleset to the frames stack
var envFrames = env.frames;
@@ -66,8 +77,8 @@ tree.Ruleset.prototype = {
// so they can be evaluated like closures when the time comes.
var rsRules = ruleset.rules, rsRuleCnt = rsRules ? rsRules.length : 0;
for (i = 0; i < rsRuleCnt; i++) {
if (rsRules[i] instanceof tree.mixin.Definition) {
rsRules[i].frames = envFrames.slice(0);
if (rsRules[i] instanceof tree.mixin.Definition || rsRules[i] instanceof tree.DetachedRuleset) {
rsRules[i] = rsRules[i].eval(env);
}
}
@@ -90,28 +101,43 @@ tree.Ruleset.prototype = {
rsRuleCnt += rules.length - 1;
i += rules.length-1;
ruleset.resetCache();
} else if (rsRules[i] instanceof tree.RulesetCall) {
/*jshint loopfunc:true */
rules = rsRules[i].eval(env).rules.filter(function(r) {
if ((r instanceof tree.Rule) && r.variable) {
// do not pollute the scope at all
return false;
}
return true;
});
rsRules.splice.apply(rsRules, [i, 1].concat(rules));
rsRuleCnt += rules.length - 1;
i += rules.length-1;
ruleset.resetCache();
}
}
// Evaluate everything else
for (i = 0; i < rsRules.length; i++) {
rule = rsRules[i];
if (! (rule instanceof tree.mixin.Definition)) {
if (! (rule instanceof tree.mixin.Definition || rule instanceof tree.DetachedRuleset)) {
rsRules[i] = rule = rule.eval ? rule.eval(env) : rule;
// for rulesets, check if it is a css guard and can be removed
if (rule instanceof tree.Ruleset && rule.selectors && rule.selectors.length === 1) {
// check if it can be folded in (e.g. & where)
if (rule.selectors[0].isJustParentSelector()) {
rsRules.splice(i--, 1);
// cannot call if there is no selector, so we can just continue
if (!rule.selectors[0].evaldCondition) {
continue;
}
for(var j = 0; j < rule.rules.length; j++) {
subRule = rule.rules[j];
if (!(subRule instanceof tree.Rule) || !subRule.variable) {
rsRules.splice(++i, 0, subRule);
}
}
}
// Evaluate everything else
for (i = 0; i < rsRules.length; i++) {
rule = rsRules[i];
// for rulesets, check if it is a css guard and can be removed
if (rule instanceof tree.Ruleset && rule.selectors && rule.selectors.length === 1) {
// check if it can be folded in (e.g. & where)
if (rule.selectors[0].isJustParentSelector()) {
rsRules.splice(i--, 1);
for(var j = 0; j < rule.rules.length; j++) {
subRule = rule.rules[j];
if (!(subRule instanceof tree.Rule) || !subRule.variable) {
rsRules.splice(++i, 0, subRule);
}
}
}
@@ -345,6 +371,9 @@ tree.Ruleset.prototype = {
toCSS: tree.toCSS,
markReferenced: function () {
if (!this.selectors) {
return;
}
for (var s = 0; s < this.selectors.length; s++) {
this.selectors[s].markReferenced();
}