diff --git a/lib/less/environment/abstract-plugin-loader.js b/lib/less/environment/abstract-plugin-loader.js index fba27a1b..ab8b5c5b 100644 --- a/lib/less/environment/abstract-plugin-loader.js +++ b/lib/less/environment/abstract-plugin-loader.js @@ -54,8 +54,8 @@ AbstractPluginLoader.prototype.evalPlugin = function(contents, context, pluginOp registry = functionRegistry.create(); try { - loader = new Function("module", "require", "functions", "tree", "fileInfo", contents); - pluginObj = loader(localModule, this.require, registry, this.less.tree, fileInfo); + loader = new Function("module", "require", "functions", "tree", "less", "fileInfo", contents); + pluginObj = loader(localModule, this.require, registry, this.less.tree, this.less, fileInfo); if (!pluginObj) { pluginObj = localModule.exports; diff --git a/lib/less/index.js b/lib/less/index.js index 31ed76ab..1c3c00e3 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -1,7 +1,7 @@ module.exports = function(environment, fileManagers) { var SourceMapOutput, SourceMapBuilder, ParseTree, ImportManager, Environment; - var less = { + var initial = { version: [3, 0, 0], data: require('./data'), tree: require('./tree'), @@ -26,5 +26,28 @@ module.exports = function(environment, fileManagers) { logger: require('./logger') }; - return less; + // Create a public API + + var ctor = function(t) { + return function() { + var obj = Object.create(t.prototype); + t.apply(obj, Array.prototype.slice.call(arguments, 0)); + return obj; + }; + }; + var t, api = Object.create(initial); + for (var n in initial.tree) { + t = initial.tree[n]; + if (typeof t === "function") { + api[n] = ctor(t); + } + else { + api[n] = Object.create(null); + for (var o in t) { + api[n][o] = ctor(t[o]); + } + } + } + + return api; }; diff --git a/lib/less/parser/parser.js b/lib/less/parser/parser.js index 494927f1..13d50ac6 100644 --- a/lib/less/parser/parser.js +++ b/lib/less/parser/parser.js @@ -80,6 +80,53 @@ var Parser = function Parser(context, imports, fileInfo) { }; } + /** + * Used after initial parsing to create nodes on the fly + * + * @param {String} str - string to parse + * @param {Array} parseList - array of parsers to run input through e.g. ["value", "important"] + * @param {Number} currentIndex - start number to begin indexing + * @param {Object} fileInfo - fileInfo to attach to created nodes + */ + function parseNode(str, parseList, currentIndex, fileInfo, callback) { + var result, returnNodes = []; + var parser = parserInput; + + try { + parser.start(str, false, function fail(msg, index) { + callback({ + message: msg, + index: index + currentIndex + }); + }); + for(var x = 0, p, i; (p = parseList[x]); x++) { + i = parser.i; + result = parsers[p](); + if (result) { + result._index = i + currentIndex; + result._fileInfo = fileInfo; + returnNodes.push(result); + } + else { + returnNodes.push(null); + } + } + + var endInfo = parser.end(); + if (endInfo.isFinished) { + callback(null, returnNodes); + } + else { + callback(true, null); + } + } catch (e) { + throw new LessError({ + index: e.index + currentIndex, + message: e.message + }, imports, fileInfo.filename); + } + } + // // The Parser // @@ -87,6 +134,7 @@ var Parser = function Parser(context, imports, fileInfo) { parserInput: parserInput, imports: imports, fileInfo: fileInfo, + parseNode: parseNode, // // Parse an input string into an abstract syntax tree, // @param str A string containing 'less' markup @@ -133,8 +181,8 @@ var Parser = function Parser(context, imports, fileInfo) { }); tree.Node.prototype.parse = this; - - root = new(tree.Ruleset)(null, this.parsers.primary()); + root = new tree.Ruleset(null, this.parsers.primary()); + tree.Node.prototype.rootNode = root; root.root = true; root.firstRoot = true; @@ -1008,7 +1056,7 @@ var Parser = function Parser(context, imports, fileInfo) { if (! e) { parserInput.save(); if (parserInput.$char('(')) { - if ((v = this.selector()) && parserInput.$char(')')) { + if ((v = this.selector(false)) && parserInput.$char(')')) { e = new(tree.Paren)(v); parserInput.forget(); } else { @@ -1059,14 +1107,8 @@ var Parser = function Parser(context, imports, fileInfo) { } }, // - // A CSS selector (see selector below) - // with less extensions e.g. the ability to extend and guard - // - lessSelector: function () { - return this.selector(true); - }, - // // A CSS Selector + // with less extensions e.g. the ability to extend and guard // // .class > div + h1 // li a:hover @@ -1075,7 +1117,7 @@ var Parser = function Parser(context, imports, fileInfo) { // selector: function (isLess) { var index = parserInput.i, elements, extendList, c, e, allExtends, when, condition; - + isLess = isLess !== false; while ((isLess && (extendList = this.extend())) || (isLess && (when = parserInput.$str("when"))) || (e = this.element())) { if (when) { condition = expect(this.conditions, 'expected condition'); @@ -1165,7 +1207,7 @@ var Parser = function Parser(context, imports, fileInfo) { } while (true) { - s = this.lessSelector(); + s = this.selector(); if (!s) { break; } diff --git a/lib/less/plugin-manager.js b/lib/less/plugin-manager.js index dcbc0b0c..bc338652 100644 --- a/lib/less/plugin-manager.js +++ b/lib/less/plugin-manager.js @@ -1,3 +1,4 @@ +var utils = require('./utils'); /** * Plugin Manager */ @@ -53,13 +54,6 @@ PluginManager.prototype.get = function(filename) { return this.pluginCache[filename]; }; -// Object.getPrototypeOf shim for visitor upgrade -if (!Object.getPrototypeOf) { - Object.getPrototypeOf = function getPrototypeOf(object) { - return object.constructor ? object.constructor.prototype : void 0; - }; -} - function upgradeVisitors(visitor, oldType, newType) { if (visitor['visit' + oldType] && !visitor['visit' + newType]) { @@ -78,7 +72,7 @@ PluginManager.prototype.addVisitor = function(visitor) { var proto; // 2.x to 3.x visitor compatibility try { - proto = Object.getPrototypeOf(visitor); + proto = utils.getPrototype(visitor); upgradeVisitors(proto, 'Directive', 'AtRule'); upgradeVisitors(proto, 'Rule', 'Declaration'); } diff --git a/lib/less/tree/atrule.js b/lib/less/tree/atrule.js index 178ce8de..480bfcef 100644 --- a/lib/less/tree/atrule.js +++ b/lib/less/tree/atrule.js @@ -1,12 +1,14 @@ var Node = require("./node"), Selector = require("./selector"), - Ruleset = require("./ruleset"); + Ruleset = require("./ruleset"), + Value = require('./value'), + Anonymous = require('./anonymous'); var AtRule = function (name, value, rules, index, currentFileInfo, debugInfo, isRooted, visibilityInfo) { var i; this.name = name; - this.value = value; + this.value = (value instanceof Node) ? value : (value ? new Anonymous(value) : value); if (rules) { if (Array.isArray(rules)) { this.rules = rules; diff --git a/lib/less/tree/declaration.js b/lib/less/tree/declaration.js index f4a4e3a8..478b6702 100644 --- a/lib/less/tree/declaration.js +++ b/lib/less/tree/declaration.js @@ -1,10 +1,11 @@ var Node = require("./node"), Value = require("./value"), - Keyword = require("./keyword"); + Keyword = require("./keyword"), + Anonymous = require("./anonymous"); var Declaration = function (name, value, important, merge, index, currentFileInfo, inline, variable) { this.name = name; - this.value = (value instanceof Node) ? value : new Value([value]); //value instanceof tree.Value || value instanceof tree.Ruleset ?? + this.value = (value instanceof Node) ? value : new Value([value ? new Anonymous(value) : null]); this.important = important ? ' ' + important.trim() : ''; this.merge = merge; this._index = index; diff --git a/lib/less/tree/index.js b/lib/less/tree/index.js index 858becf1..5c8f0ddf 100644 --- a/lib/less/tree/index.js +++ b/lib/less/tree/index.js @@ -1,4 +1,4 @@ -var tree = {}; +var tree = Object.create(null); tree.Node = require('./node'); tree.Alpha = require('./alpha'); diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 5ffbe094..24b67f19 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -298,26 +298,21 @@ Ruleset.prototype.parseValue = function(toParse) { var self = this; function transformDeclaration(decl) { if (decl.value instanceof Anonymous && !decl.parsed) { - try { - this.parse.parserInput.start(decl.value.value, false, function fail(msg, index) { - decl.parsed = true; - return decl; + this.parse.parseNode( + decl.value.value, + ["value", "important"], + decl.value.getIndex(), + decl.fileInfo(), + function(err, result) { + if (err) { + decl.parsed = true; + } + if (result) { + decl.value = result[0]; + decl.important = result[1] || ''; + decl.parsed = true; + } }); - var result = this.parse.parsers.value(); - var important = this.parse.parsers.important(); - - var endInfo = this.parse.parserInput.end(); - if (endInfo.isFinished) { - decl.value = result; - decl.imporant = important; - decl.parsed = true; - } - } catch (e) { - throw new LessError({ - index: e.index + decl.value.getIndex(), - message: e.message - }, this.parse.imports, decl.fileInfo().filename); - } return decl; } diff --git a/lib/less/tree/selector.js b/lib/less/tree/selector.js index 20bd9401..b263cd23 100644 --- a/lib/less/tree/selector.js +++ b/lib/less/tree/selector.js @@ -1,12 +1,13 @@ var Node = require("./node"), - Element = require("./element"); + Element = require("./element"), + LessError = require("../less-error"); var Selector = function (elements, extendList, condition, index, currentFileInfo, visibilityInfo) { - this.elements = elements; this.extendList = extendList; this.condition = condition; this._index = index; this._fileInfo = currentFileInfo; + this.elements = this.getElements(elements); if (!condition) { this.evaldCondition = true; } @@ -27,6 +28,7 @@ Selector.prototype.accept = function (visitor) { } }; Selector.prototype.createDerived = function(elements, extendList, evaldCondition) { + elements = this.getElements(elements); var info = this.visibilityInfo(); evaldCondition = (evaldCondition != null) ? evaldCondition : this.evaldCondition; var newSelector = new Selector(elements, extendList || this.extendList, null, this.getIndex(), this.fileInfo(), info); @@ -34,6 +36,25 @@ Selector.prototype.createDerived = function(elements, extendList, evaldCondition newSelector.mediaEmpty = this.mediaEmpty; return newSelector; }; +Selector.prototype.getElements = function(els) { + if (typeof els === "string") { + this.parse.parseNode( + els, + ["selector"], + this._index, + this._fileInfo, + function(err, result) { + if (err) { + throw new LessError({ + index: e.index + currentIndex, + message: e.message + }, this.parse.imports, this._fileInfo.filename); + } + els = result[0].elements; + }); + } + return els; +}; Selector.prototype.createEmptySelectors = function() { var el = new Element('', '&', this._index, this._fileInfo), sels = [new Selector([el], null, null, this._index, this._fileInfo)]; @@ -45,7 +66,7 @@ Selector.prototype.match = function (other) { len = elements.length, olen, i; - other.CacheElements(); + other.cacheElements(); olen = other._elements.length; if (olen === 0 || len < olen) { @@ -60,7 +81,7 @@ Selector.prototype.match = function (other) { return olen; // return number of matched elements }; -Selector.prototype.CacheElements = function() { +Selector.prototype.cacheElements = function() { if (this._elements) { return; } diff --git a/lib/less/tree/value.js b/lib/less/tree/value.js index 1a03886c..1f849ebc 100644 --- a/lib/less/tree/value.js +++ b/lib/less/tree/value.js @@ -1,10 +1,17 @@ -var Node = require("./node"); +var Node = require("./node"), + Anonymous = require("./anonymous"); + var Value = function (value) { - this.value = value; if (!value) { throw new Error("Value requires an array argument"); } + if (!Array.isArray(value)) { + this.value = [ value ]; + } + else { + this.value = value; + } }; Value.prototype = new Node(); Value.prototype.type = "Value"; diff --git a/lib/less/utils.js b/lib/less/utils.js index d32a7e92..ade845da 100644 --- a/lib/less/utils.js +++ b/lib/less/utils.js @@ -25,5 +25,18 @@ module.exports = { copy[i] = arr[i]; } return copy; + }, + getPrototype: function(obj) { + if (Object.getPrototypeOf) { + return Object.getPrototypeOf(obj); + } + else { + if ("".__proto__ === String.prototype) { + return obj.__proto__; + } + else if (obj.constructor) { + return obj.constructor.prototype; + } + } } }; diff --git a/lib/less/visitors/visitor.js b/lib/less/visitors/visitor.js index afa05d70..44491c1a 100644 --- a/lib/less/visitors/visitor.js +++ b/lib/less/visitors/visitor.js @@ -10,21 +10,20 @@ function _noop(node) { function indexNodeTypes(parent, ticker) { // add .typeIndex to tree node types for lookup table var key, child; - for (key in parent) { - if (parent.hasOwnProperty(key)) { - child = parent[key]; - switch (typeof child) { - case "function": - // ignore bound functions directly on tree which do not have a prototype - // or aren't nodes - if (child.prototype && child.prototype.type) { - child.prototype.typeIndex = ticker++; - } - break; - case "object": - ticker = indexNodeTypes(child, ticker); - break; - } + for (key in parent) { + child = parent[key]; + switch (typeof child) { + case "function": + // ignore bound functions directly on tree which do not have a prototype + // or aren't nodes + if (child.prototype && child.prototype.type) { + child.prototype.typeIndex = ticker++; + } + break; + case "object": + ticker = indexNodeTypes(child, ticker); + break; + } } return ticker; diff --git a/test/less/plugin.less b/test/less/plugin.less index 955a7268..1089be4b 100644 --- a/test/less/plugin.less +++ b/test/less/plugin.less @@ -92,7 +92,7 @@ val2: foo; } -test-directive("@charset"; '"utf-8"'); -test-directive("@arbitrary"; "value after ()"); +test-atrule("@charset"; '"utf-8"'); +test-atrule("@arbitrary"; "value after ()"); diff --git a/test/less/plugin/plugin-tree-nodes.js b/test/less/plugin/plugin-tree-nodes.js index b3fcf180..11f5a1de 100644 --- a/test/less/plugin/plugin-tree-nodes.js +++ b/test/less/plugin/plugin-tree-nodes.js @@ -1,10 +1,10 @@ functions.addMultiple({ "test-comment": function() { - return new tree.Combinator(' '); + return less.Combinator(' '); }, - "test-directive": function(arg1, arg2) { - return new tree.Directive(arg1.value, new tree.Anonymous(arg2.value)); + "test-atrule": function(arg1, arg2) { + return less.AtRule(arg1.value, arg2.value); }, "test-extend": function() { //TODO @@ -22,7 +22,7 @@ functions.addMultiple({ //TODO }, "test-ruleset-call": function() { - return new tree.Combinator(' '); + return less.Combinator(' '); }, // Functions must return something, even if it's false/true "test-undefined": function() { @@ -33,52 +33,53 @@ functions.addMultiple({ }, // These cause root errors "test-alpha": function() { - return new tree.Alpha(30); + return less.Alpha(30); }, "test-assignment": function() { - return new tree.Assignment("bird", "robin"); + return less.Assignment("bird", "robin"); }, "test-attribute": function() { - return new tree.Attribute("foo", "=", "bar"); + return less.Attribute("foo", "=", "bar"); }, "test-call": function() { - return new tree.Call("foo"); + return less.Call("foo"); }, "test-color": function() { - return new tree.Color([50, 50, 50]); + return less.Color([50, 50, 50]); }, "test-condition": function() { - return new tree.Condition('<', new tree.Value([0]), new tree.Value([1])); + return less.Condition('<', less.Value([0]), less.Value([1])); }, "test-detached-ruleset" : function() { - var decl = new tree.Declaration('prop', new tree.Anonymous('value')); - return new tree.DetachedRuleset(new tree.Ruleset("", [ decl ])); + var decl = less.Declaration('prop', 'value'); + return less.DetachedRuleset(less.Ruleset("", [ decl ])); }, "test-dimension": function() { - return new tree.Dimension(1, 'px'); + return less.Dimension(1, 'px'); }, "test-element": function() { - return new tree.Element('+', 'a'); + return less.Element('+', 'a'); }, "test-expression": function() { - return new tree.Expression([1, 2, 3]); + return less.Expression([1, 2, 3]); }, "test-keyword": function() { - return new tree.Keyword('foo'); + return less.Keyword('foo'); }, "test-operation": function() { - return new tree.Operation('+', [1, 2]); + return less.Operation('+', [1, 2]); }, "test-quoted": function() { - return new tree.Quoted('"', 'foo'); + return less.Quoted('"', 'foo'); }, "test-selector": function() { - return new tree.Selector([new tree.Element('a')]); + var sel = less.Selector('.a.b'); + return sel; }, "test-url": function() { - return new tree.URL('http://google.com'); + return less.URL('http://google.com'); }, "test-value": function() { - return new tree.Value([1]); + return less.Value([1]); } }); \ No newline at end of file diff --git a/test/less/property-accessors.less b/test/less/property-accessors.less index 24dbd604..e679f0d2 100644 --- a/test/less/property-accessors.less +++ b/test/less/property-accessors.less @@ -2,7 +2,8 @@ .block_1 { color: red; background-color: $color; - width: 50px; + @width: 50px; + width: @width; height: ($width / 2); @color: red; border: 1px solid lighten($color, 10%);