Skip to content

Commit

Permalink
Rename Directive -> AtRule & Rule -> Declaration
Browse files Browse the repository at this point in the history
- Added tree shim for old node types and plugin visitor shim to upgrade node visitors to new node types
  • Loading branch information
matthew-dean committed Jul 2, 2016
1 parent c73f50e commit 52e9b5e
Show file tree
Hide file tree
Showing 16 changed files with 370 additions and 306 deletions.
38 changes: 19 additions & 19 deletions lib/less/parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ var Parser = function Parser(context, imports, fileInfo) {
//
// The basic structure of the syntax tree generated is as follows:
//
// Ruleset -> Rule -> Value -> Expression -> Entity
// Ruleset -> Declaration -> Value -> Expression -> Entity
//
// Here's some Less code:
//
Expand All @@ -211,9 +211,9 @@ var Parser = function Parser(context, imports, fileInfo) {
// And here's what the parse tree might look like:
//
// Ruleset (Selector '.class', [
// Rule ("color", Value ([Expression [Color #fff]]))
// Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
// Rule ("width", Value ([Expression [Operation " + " [Variable "@w"][Dimension 4px]]]))
// Declaration ("color", Value ([Expression [Color #fff]]))
// Declaration ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
// Declaration ("width", Value ([Expression [Operation " + " [Variable "@w"][Dimension 4px]]]))
// Ruleset (Selector [Element '>', '.child'], [...])
// ])
//
Expand All @@ -230,7 +230,7 @@ var Parser = function Parser(context, imports, fileInfo) {
// rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
// as represented by this simplified grammar:
//
// primary → (ruleset | rule)+
// primary → (ruleset | declaration)+
// ruleset → selector+ block
// block → '{' primary '}'
//
Expand Down Expand Up @@ -260,8 +260,8 @@ var Parser = function Parser(context, imports, fileInfo) {
continue;
}

node = mixin.definition() || this.rule() || this.ruleset() ||
mixin.call() || this.rulesetCall() || this.entities.call() || this.directive();
node = mixin.definition() || this.declaration() || this.ruleset() ||
mixin.call() || this.rulesetCall() || this.entities.call() || this.atrule();
if (node) {
root.push(node);
} else {
Expand Down Expand Up @@ -929,7 +929,7 @@ var Parser = function Parser(context, imports, fileInfo) {
},

//
// A Rule terminator. Note that we use `peek()` to check for '}',
// A Declaration terminator. Note that we use `peek()` to check for '}',
// because the `block` rule will be expecting it, but we still need to make sure
// it's there, if ';' was omitted.
//
Expand Down Expand Up @@ -1169,7 +1169,7 @@ var Parser = function Parser(context, imports, fileInfo) {
parserInput.restore();
}
},
rule: function (tryAnonymous) {
declaration: function (tryAnonymous) {
var name, value, startOfRule = parserInput.i, c = parserInput.currentChar(), important, merge, isVariable;

if (c === '.' || c === '#' || c === '&' || c === ':') { return; }
Expand Down Expand Up @@ -1203,7 +1203,7 @@ var Parser = function Parser(context, imports, fileInfo) {
if (value) {
parserInput.forget();
// anonymous values absorb the end ';' which is required for them to work
return new (tree.Rule)(name, value, false, merge, startOfRule, fileInfo);
return new (tree.Declaration)(name, value, false, merge, startOfRule, fileInfo);
}
}
if (!tryValueFirst && !value) {
Expand All @@ -1215,11 +1215,11 @@ var Parser = function Parser(context, imports, fileInfo) {

if (value && this.end()) {
parserInput.forget();
return new (tree.Rule)(name, value, important, merge, startOfRule, fileInfo);
return new (tree.Declaration)(name, value, important, merge, startOfRule, fileInfo);
} else {
parserInput.restore();
if (value && !tryAnonymous) {
return this.rule(true);
return this.declaration(true);
}
}
} else {
Expand All @@ -1234,7 +1234,7 @@ var Parser = function Parser(context, imports, fileInfo) {
},

//
// An @import directive
// An @import atrule
//
// @import "lib";
//
Expand Down Expand Up @@ -1315,7 +1315,7 @@ var Parser = function Parser(context, imports, fileInfo) {
e = this.value();
if (parserInput.$char(')')) {
if (p && e) {
nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, null, parserInput.i, fileInfo, true)));
nodes.push(new(tree.Paren)(new(tree.Declaration)(p, e, null, null, parserInput.i, fileInfo, true)));
} else if (e) {
nodes.push(new(tree.Paren)(e));
} else {
Expand Down Expand Up @@ -1384,7 +1384,7 @@ var Parser = function Parser(context, imports, fileInfo) {
},

//
// A @plugin directive, used to import scoped extensions dynamically.
// A @plugin atrule, used to import scoped extensions dynamically.
//
// @plugin "lib";
//
Expand Down Expand Up @@ -1418,11 +1418,11 @@ var Parser = function Parser(context, imports, fileInfo) {
},

//
// A CSS Directive
// A CSS AtRule
//
// @charset "utf-8";
//
directive: function () {
atrule: function () {
var index = parserInput.i, name, value, rules, nonVendorSpecificName,
hasIdentifier, hasExpression, hasUnknown, hasBlock = true, isRooted = true;

Expand Down Expand Up @@ -1493,13 +1493,13 @@ var Parser = function Parser(context, imports, fileInfo) {

if (rules || (!hasBlock && value && parserInput.$char(';'))) {
parserInput.forget();
return new (tree.Directive)(name, value, rules, index, fileInfo,
return new (tree.AtRule)(name, value, rules, index, fileInfo,
context.dumpLineNumbers ? getDebugInfo(index) : null,
isRooted
);
}

parserInput.restore("directive options not recognised");
parserInput.restore("atrule options not recognised");
},

//
Expand Down
33 changes: 33 additions & 0 deletions lib/less/plugin-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,45 @@ PluginManager.prototype.addPlugin = function(plugin, filename) {
PluginManager.prototype.get = function(filename) {
return this.pluginCache[filename];
};

// Object.getPrototypeOf shim for visitor upgrade
if(!Object.getPrototypeOf) {
if(({}).__proto__===Object.prototype&&([]).__proto__===Array.prototype) {
Object.getPrototypeOf=function getPrototypeOf(object) {
return object.__proto__;
};
} else {
Object.getPrototypeOf=function getPrototypeOf(object) {
// May break if the constructor has been changed or removed
return object.constructor?object.constructor.prototype:void 0;
};
}
}

function upgradeVisitors(visitor, oldType, newType) {

if (visitor['visit' + oldType] && !visitor['visit' + newType]) {
visitor['visit' + newType] = visitor['visit' + oldType];
}
if (visitor['visit' + oldType + 'Out'] && !visitor['visit' + newType + 'Out']) {
visitor['visit' + newType + 'Out'] = visitor['visit' + oldType + 'Out'];
}
}
/**
* Adds a visitor. The visitor object has options on itself to determine
* when it should run.
* @param visitor
*/
PluginManager.prototype.addVisitor = function(visitor) {
var proto;
// 2.x to 3.x visitor compatibility
try {
proto = Object.getPrototypeOf(visitor);
upgradeVisitors(proto, 'Directive', 'AtRule');
upgradeVisitors(proto, 'Rule', 'Declaration');
}
catch(e) {}

this.visitors.push(visitor);
};
/**
Expand Down
4 changes: 2 additions & 2 deletions lib/less/transform-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module.exports = function(root, options) {
//
// `{ color: new tree.Color('#f01') }` will become:
//
// new tree.Rule('@color',
// new tree.Declaration('@color',
// new tree.Value([
// new tree.Expression([
// new tree.Color('#f01')
Expand All @@ -31,7 +31,7 @@ module.exports = function(root, options) {
}
value = new tree.Value([value]);
}
return new tree.Rule('@' + k, value, false, null, 0);
return new tree.Declaration('@' + k, value, false, null, 0);
});
evalEnv.frames = [new tree.Ruleset(null, variables)];
}
Expand Down
134 changes: 134 additions & 0 deletions lib/less/tree/atrule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
var Node = require("./node"),
Selector = require("./selector"),
Ruleset = require("./ruleset");

var AtRule = function (name, value, rules, index, currentFileInfo, debugInfo, isRooted, visibilityInfo) {
var i;

this.name = name;
this.value = value;
if (rules) {
if (Array.isArray(rules)) {
this.rules = rules;
} else {
this.rules = [rules];
this.rules[0].selectors = (new Selector([], null, null, this.index, currentFileInfo)).createEmptySelectors();
}
for (i = 0; i < this.rules.length; i++) {
this.rules[i].allowImports = true;
}
}
this.index = index;
this.currentFileInfo = currentFileInfo;
this.debugInfo = debugInfo;
this.isRooted = isRooted || false;
this.copyVisibilityInfo(visibilityInfo);
this.allowRoot = true;
};

AtRule.prototype = new Node();
AtRule.prototype.type = "AtRule";
AtRule.prototype.accept = function (visitor) {
var value = this.value, rules = this.rules;
if (rules) {
this.rules = visitor.visitArray(rules);
}
if (value) {
this.value = visitor.visit(value);
}
};
AtRule.prototype.isRulesetLike = function() {
return this.rules || !this.isCharset();
};
AtRule.prototype.isCharset = function() {
return "@charset" === this.name;
};
AtRule.prototype.genCSS = function (context, output) {
var value = this.value, rules = this.rules;
output.add(this.name, this.currentFileInfo, this.index);
if (value) {
output.add(' ');
value.genCSS(context, output);
}
if (rules) {
this.outputRuleset(context, output, rules);
} else {
output.add(';');
}
};
AtRule.prototype.eval = function (context) {
var mediaPathBackup, mediaBlocksBackup, value = this.value, rules = this.rules;

//media stored inside other atrule should not bubble over it
//backpup media bubbling information
mediaPathBackup = context.mediaPath;
mediaBlocksBackup = context.mediaBlocks;
//deleted media bubbling information
context.mediaPath = [];
context.mediaBlocks = [];

if (value) {
value = value.eval(context);
}
if (rules) {
// assuming that there is only one rule at this point - that is how parser constructs the rule
rules = [rules[0].eval(context)];
rules[0].root = true;
}
//restore media bubbling information
context.mediaPath = mediaPathBackup;
context.mediaBlocks = mediaBlocksBackup;

return new AtRule(this.name, value, rules,
this.index, this.currentFileInfo, this.debugInfo, this.isRooted, this.visibilityInfo());
};
AtRule.prototype.variable = function (name) {
if (this.rules) {
// assuming that there is only one rule at this point - that is how parser constructs the rule
return Ruleset.prototype.variable.call(this.rules[0], name);
}
};
AtRule.prototype.find = function () {
if (this.rules) {
// assuming that there is only one rule at this point - that is how parser constructs the rule
return Ruleset.prototype.find.apply(this.rules[0], arguments);
}
};
AtRule.prototype.rulesets = function () {
if (this.rules) {
// assuming that there is only one rule at this point - that is how parser constructs the rule
return Ruleset.prototype.rulesets.apply(this.rules[0]);
}
};
AtRule.prototype.outputRuleset = function (context, output, rules) {
var ruleCnt = rules.length, i;
context.tabLevel = (context.tabLevel | 0) + 1;

// Compressed
if (context.compress) {
output.add('{');
for (i = 0; i < ruleCnt; i++) {
rules[i].genCSS(context, output);
}
output.add('}');
context.tabLevel--;
return;
}

// Non-compressed
var tabSetStr = '\n' + Array(context.tabLevel).join(" "), tabRuleStr = tabSetStr + " ";
if (!ruleCnt) {
output.add(" {" + tabSetStr + '}');
} else {
output.add(" {" + tabRuleStr);
rules[0].genCSS(context, output);
for (i = 1; i < ruleCnt; i++) {
output.add(tabRuleStr);
rules[i].genCSS(context, output);
}
output.add(tabSetStr + '}');
}

context.tabLevel--;
};
module.exports = AtRule;
Loading

0 comments on commit 52e9b5e

Please sign in to comment.