From 5547e8a27e3b8598d78d3fcb08ac77b85f6a346b Mon Sep 17 00:00:00 2001 From: Daniel Stockman Date: Mon, 8 Jul 2013 14:55:30 -0700 Subject: [PATCH 01/34] Refactor parser's private getLocation method for clarity, and reuse appropriately. --- lib/less/parser.js | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index 9c7f878e..f5a1826a 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -235,13 +235,23 @@ less.Parser = function Parser(env) { } } - function getLocation(index, input) { - for (var n = index, column = -1; - n >= 0 && input.charAt(n) !== '\n'; - n--) { column++ } + function getLocation(index, inputStream) { + var n = index + 1, + line = null, + column = -1; - return { line: typeof(index) === 'number' ? (input.slice(0, index).match(/\n/g) || "").length : null, - column: column }; + while (--n >= 0 && inputStream.charAt(n) !== '\n') { + column++; + } + + if (typeof index === 'number') { + line = (inputStream.slice(0, index).match(/\n/g) || "").length; + } + + return { + line: line, + column: column + }; } function getDebugInfo(index, inputStream, env) { @@ -261,6 +271,7 @@ less.Parser = function Parser(env) { loc = getLocation(e.index, input), line = loc.line, col = loc.column, + callLine = e.call && getLocation(e.call, input).line, lines = input.split('\n'); this.type = e.type || 'Syntax'; @@ -268,8 +279,8 @@ less.Parser = function Parser(env) { this.filename = e.filename || env.currentFileInfo.filename; this.index = e.index; this.line = typeof(line) === 'number' ? line + 1 : null; - this.callLine = e.call && (getLocation(e.call, input).line + 1); - this.callExtract = lines[getLocation(e.call, input).line]; + this.callLine = callLine + 1; + this.callExtract = lines[callLine]; this.stack = e.stack; this.column = col; this.extract = [ @@ -466,10 +477,9 @@ less.Parser = function Parser(env) { // and the part which didn't), so we can color them differently. if (i < input.length - 1) { i = furthest; + var loc = getLocation(i, input); lines = input.split('\n'); - line = (input.slice(0, i).match(/\n/g) || "").length + 1; - - for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ } + line = loc.line + 1; error = { type: "Parse", @@ -477,7 +487,7 @@ less.Parser = function Parser(env) { index: i, filename: env.currentFileInfo.filename, line: line, - column: column, + column: loc.column, extract: [ lines[line - 2], lines[line - 1], From 8eeaf87a791b095434be41d86fd18dda04be76ec Mon Sep 17 00:00:00 2001 From: Daniel Stockman Date: Mon, 8 Jul 2013 19:05:01 -0700 Subject: [PATCH 02/34] JSHint lib/* and test runners. --- lib/less/browser.js | 41 ++++---- lib/less/env.js | 5 +- lib/less/extend-visitor.js | 8 +- lib/less/functions.js | 17 ++-- lib/less/index.js | 10 +- lib/less/lessc_helper.js | 2 +- lib/less/parser.js | 153 ++++++++++++++++++---------- lib/less/rhino.js | 8 +- lib/less/tree.js | 11 +- lib/less/tree/anonymous.js | 2 +- lib/less/tree/assignment.js | 2 +- lib/less/tree/call.js | 1 + lib/less/tree/color.js | 2 +- lib/less/tree/condition.js | 2 +- lib/less/tree/dimension.js | 9 +- lib/less/tree/element.js | 2 +- lib/less/tree/javascript.js | 1 + lib/less/tree/keyword.js | 2 +- lib/less/tree/media.js | 2 +- lib/less/tree/mixin.js | 13 +-- lib/less/tree/ruleset.js | 38 +++---- lib/less/tree/selector.js | 3 +- lib/less/tree/unicode-descriptor.js | 2 +- lib/less/tree/variable.js | 8 +- test/browser-test-prepare.js | 4 +- test/less-test.js | 10 +- 26 files changed, 217 insertions(+), 141 deletions(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index 0fc98811..f7f6a964 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -1,6 +1,7 @@ // // browser.js - client-side engine // +/*global less */ var isFileProtocol = /^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol); @@ -43,7 +44,7 @@ less.watch = function () { less.env = 'development'; initRunningMode(); } - return this.watchMode = true + return this.watchMode = true; }; less.unwatch = function () {clearInterval(less.watchTimer); return this.watchMode = false; }; @@ -105,12 +106,12 @@ less.modifyVars = function(record) { newVars += ((name.slice(0,1) === '@')? '' : '@') + name +': '+ ((record[name].slice(-1) === ';')? record[name] : record[name] +';'); } - less.refresh(false, newVars) + less.refresh(false, newVars); }; less.refresh = function (reload, newVars) { var startTime, endTime; - startTime = endTime = new(Date); + startTime = endTime = new Date(); loadStyleSheets(function (e, root, _, sheet, env) { if (e) { @@ -122,9 +123,9 @@ less.refresh = function (reload, newVars) { log("parsed " + sheet.href + " successfully."); createCSS(root.toCSS(less), sheet, env.lastModified); } - log("css for " + sheet.href + " generated in " + (new(Date) - endTime) + 'ms'); - (env.remaining === 0) && log("css generated in " + (new(Date) - startTime) + 'ms'); - endTime = new(Date); + log("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms'); + (env.remaining === 0) && log("css generated in " + (new Date() - startTime) + 'ms'); + endTime = new Date(); }, reload, newVars); loadStyles(newVars); @@ -145,6 +146,7 @@ function loadStyles(newVars) { lessText += "\n" + newVars; } + /*jshint loopfunc:true */ // use closure to store current value of i var callback = (function(style) { return function (e, cssAST) { @@ -158,7 +160,7 @@ function loadStyles(newVars) { } else { style.innerHTML = css; } - } + }; })(style); new(less.Parser)(env).parse(lessText, callback); } @@ -337,20 +339,20 @@ function loadFile(originalHref, currentFileInfo, callback, env, newVars) { if (newVars) { lessText += "\n" + newVars; } - callback(null, lessText, href, newFileInfo, { lastModified: new Date() }) + callback(null, lessText, href, newFileInfo, { lastModified: new Date() }); } catch (e) { callback(e, null, href); } return; } - xhr(href, env.mime, function (data, lastModified) { + doXHR(href, env.mime, function (data, lastModified) { // per file cache fileCache[href] = data; // Use remote copy (re-parse) try { - callback(null, data, href, newFileInfo, { lastModified: lastModified }) + callback(null, data, href, newFileInfo, { lastModified: lastModified }); } catch (e) { callback(e, null, href); } @@ -406,7 +408,7 @@ function createCSS(styles, sheet, lastModified) { // If there is no oldCss, just append; otherwise, only append if we need // to replace oldCss with an updated stylesheet - if (oldCss == null || keepOldCss === false) { + if (oldCss === null || keepOldCss === false) { var nextEl = sheet && sheet.nextSibling || null; (nextEl || document.getElementsByTagName('head')[0]).parentNode.insertBefore(css, nextEl); } @@ -427,7 +429,7 @@ function createCSS(styles, sheet, lastModified) { } } -function xhr(url, type, callback, errback) { +function doXHR(url, type, callback, errback) { var xhr = getXMLHttpRequest(); var async = isFileProtocol ? less.fileAsync : less.async; @@ -467,10 +469,11 @@ function xhr(url, type, callback, errback) { function getXMLHttpRequest() { if (window.XMLHttpRequest) { - return new(XMLHttpRequest); + return new XMLHttpRequest(); } else { try { - return new(ActiveXObject)("MSXML2.XMLHTTP.3.0"); + /*global ActiveXObject */ + return new ActiveXObject("MSXML2.XMLHTTP.3.0"); } catch (e) { log("browser doesn't support AJAX."); return null; @@ -483,13 +486,13 @@ function removeNode(node) { } function log(str) { - if (less.env == 'development' && typeof(console) !== "undefined") { console.log('less: ' + str) } + if (less.env == 'development' && typeof(console) !== "undefined") { console.log('less: ' + str); } } function error(e, rootHref) { var id = 'less-error-message:' + extractId(rootHref || ""); var template = '
  • {content}
  • '; - var elem = document.createElement('div'), timer, content, error = []; + var elem = document.createElement('div'), timer, content, errors = []; var filename = e.filename || rootHref; var filenameNoPath = filename.match(/([^\/]+(\?.*)?)$/)[1]; @@ -500,8 +503,8 @@ function error(e, rootHref) { '' + '

    in ' + filenameNoPath + " "; var errorline = function (e, i, classname) { - if (e.extract[i] != undefined) { - error.push(template.replace(/\{line\}/, (parseInt(e.line) || 0) + (i - 1)) + if (e.extract[i] !== undefined) { + errors.push(template.replace(/\{line\}/, (parseInt(e.line, 10) || 0) + (i - 1)) .replace(/\{class\}/, classname) .replace(/\{content\}/, e.extract[i])); } @@ -512,7 +515,7 @@ function error(e, rootHref) { errorline(e, 1, 'line'); errorline(e, 2, ''); content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':

    ' + - ''; + ''; } else if (e.stack) { content += '
    ' + e.stack.split('\n').slice(1).join('
    '); } diff --git a/lib/less/env.js b/lib/less/env.js index 203830e0..18d8d5c4 100644 --- a/lib/less/env.js +++ b/lib/less/env.js @@ -97,5 +97,6 @@ destination[propertiesToCopy[i]] = original[propertiesToCopy[i]]; } } - } -})(require('./tree')); \ No newline at end of file + }; + +})(require('./tree')); diff --git a/lib/less/extend-visitor.js b/lib/less/extend-visitor.js index cd9d9458..6d04ff1f 100644 --- a/lib/less/extend-visitor.js +++ b/lib/less/extend-visitor.js @@ -1,4 +1,6 @@ (function (tree) { + /*jshint loopfunc:true */ + tree.extendFinderVisitor = function() { this._visitor = new tree.visitor(this); this.contexts = []; @@ -246,7 +248,7 @@ haystackElement = hackstackSelector.elements[hackstackElementIndex]; // if we allow elements before our match we can add a potential match every time. otherwise only at the first element. - if (extend.allowBefore || (haystackSelectorIndex == 0 && hackstackElementIndex == 0)) { + if (extend.allowBefore || (haystackSelectorIndex === 0 && hackstackElementIndex === 0)) { potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0, initialCombinator: haystackElement.combinator}); } @@ -257,7 +259,7 @@ // then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out // what the resulting combinator will be targetCombinator = haystackElement.combinator.value; - if (targetCombinator == '' && hackstackElementIndex === 0) { + if (targetCombinator === '' && hackstackElementIndex === 0) { targetCombinator = ' '; } @@ -388,4 +390,4 @@ } }; -})(require('./tree')); \ No newline at end of file +})(require('./tree')); diff --git a/lib/less/functions.js b/lib/less/functions.js index 93b13fdf..cee8379a 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -26,10 +26,10 @@ tree.functions = { function hue(h) { h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h); - if (h * 6 < 1) return m1 + (m2 - m1) * h * 6; - else if (h * 2 < 1) return m2; - else if (h * 3 < 2) return m1 + (m2 - m1) * (2/3 - h) * 6; - else return m1; + if (h * 6 < 1) { return m1 + (m2 - m1) * h * 6; } + else if (h * 2 < 1) { return m2; } + else if (h * 3 < 2) { return m1 + (m2 - m1) * (2/3 - h) * 6; } + else { return m1; } } }, @@ -223,6 +223,7 @@ tree.functions = { str = quoted.value; for (var i = 0; i < args.length; i++) { + /*jshint loopfunc:true */ str = str.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; @@ -259,6 +260,7 @@ tree.functions = { }, _math: function (fn, unit, n) { if (n instanceof tree.Dimension) { + /*jshint eqnull:true */ return new(tree.Dimension)(fn(parseFloat(n.value)), unit == null ? n.unit : unit); } else if (typeof(n) === 'number') { return fn(n); @@ -463,10 +465,10 @@ tree.functions = { // use base 64 unless it's an ASCII or UTF-8 format var charset = mime.charsets.lookup(mimetype); useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0; - if (useBase64) mimetype += ';base64'; + if (useBase64) { mimetype += ';base64'; } } else { - useBase64 = /;base64$/.test(mimetype) + useBase64 = /;base64$/.test(mimetype); } var buf = fs.readFileSync(filePath); @@ -532,7 +534,7 @@ tree.functions = { gradientType = "radial"; gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"'; rectangleDimension = 'x="-50" y="-50" width="101" height="101"'; - break + break; default: throw { type: "Argument", message: "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'radial'" }; } @@ -607,6 +609,7 @@ var mathFunctions = [{name:"ceil"}, {name:"floor"}, {name: "sqrt"}, {name:"abs"} {name:"atan", unit: "rad"}, {name:"asin", unit: "rad"}, {name:"acos", unit: "rad"}], createMathFunction = function(name, unit) { return function(n) { + /*jshint eqnull:true */ if (unit != null) { n = n.unify(); } diff --git a/lib/less/index.js b/lib/less/index.js index 94631555..297a7152 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -24,7 +24,7 @@ var less = { catch (err) { callback(err); } }); } else { - ee = new(require('events').EventEmitter); + ee = new (require('events').EventEmitter)(); process.nextTick(function () { parser.parse(input, function (e, root) { @@ -42,10 +42,10 @@ var less = { var message = ""; var extract = ctx.extract; var error = []; - var stylize = options.color ? require('./lessc_helper').stylize : function (str) { return str }; + var stylize = options.color ? require('./lessc_helper').stylize : function (str) { return str; }; // only output a stack if it isn't a less error - if (ctx.stack && !ctx.type) { return stylize(ctx.stack, 'red') } + if (ctx.stack && !ctx.type) { return stylize(ctx.stack, 'red'); } if (!ctx.hasOwnProperty('index') || !extract) { return ctx.stack || ctx.message; @@ -132,7 +132,7 @@ less.Parser.fileLoader = function (file, currentFileInfo, callback, env) { newFileInfo.filename = pathname; callback(null, data, pathname, newFileInfo); - }; + } var isUrl = isUrlRe.test( file ); if (isUrl || isUrlRe.test(currentFileInfo.currentDirectory)) { @@ -205,7 +205,7 @@ less.Parser.fileLoader = function (file, currentFileInfo, callback, env) { }); } } -} +}; require('./env'); require('./functions'); diff --git a/lib/less/lessc_helper.js b/lib/less/lessc_helper.js index 08d29992..ed5df671 100644 --- a/lib/less/lessc_helper.js +++ b/lib/less/lessc_helper.js @@ -67,4 +67,4 @@ var lessc_helper = { }; // Exports helper functions -for (var h in lessc_helper) { exports[h] = lessc_helper[h] } +for (var h in lessc_helper) { exports[h] = lessc_helper[h]; } diff --git a/lib/less/parser.js b/lib/less/parser.js index 9c7f878e..e2c60339 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1,21 +1,22 @@ var less, tree, charset; +/*global environment */ if (typeof environment === "object" && ({}).toString.call(environment) === "[object Environment]") { // Rhino // Details on how to detect Rhino: https://github.com/ringo/ringojs/issues/88 - if (typeof(window) === 'undefined') { less = {} } - else { less = window.less = {} } + if (typeof(window) === 'undefined') { less = {}; } + else { less = window.less = {}; } tree = less.tree = {}; less.mode = 'rhino'; } else if (typeof(window) === 'undefined') { // Node.js - less = exports, + less = exports; tree = require('./tree'); less.mode = 'node'; } else { // Browser - if (typeof(window.less) === 'undefined') { window.less = {} } - less = window.less, + if (typeof(window.less) === 'undefined') { window.less = {}; } + less = window.less; tree = window.less.tree = {}; less.mode = 'browser'; } @@ -117,7 +118,7 @@ less.Parser = function Parser(env) { fileParsedFunc(e, root, fullPath); }); } - }, env) + }, env); } } }; @@ -189,13 +190,13 @@ less.Parser = function Parser(env) { mem = i += length; while (i < endIndex) { - if (! isWhitespace(input.charAt(i))) { break } + if (! isWhitespace(input.charAt(i))) { break; } i++; } chunks[j] = chunks[j].slice(length + (i - mem)); current = i; - if (chunks[j].length === 0 && j < chunks.length - 1) { j++ } + if (chunks[j].length === 0 && j < chunks.length - 1) { j++; } return oldi !== i || oldj !== j; } @@ -238,7 +239,7 @@ less.Parser = function Parser(env) { function getLocation(index, input) { for (var n = index, column = -1; n >= 0 && input.charAt(n) !== '\n'; - n--) { column++ } + n--) { column++; } return { line: typeof(index) === 'number' ? (input.slice(0, index).match(/\n/g) || "").length : null, column: column }; @@ -353,16 +354,42 @@ less.Parser = function Parser(env) { } switch (c) { - case '{': if (! inParam) { level ++; chunk.push(c); break } - case '}': if (! inParam) { level --; chunk.push(c); chunks[++j] = chunk = []; break } - case '(': if (! inParam) { inParam = true; chunk.push(c); break } - case ')': if ( inParam) { inParam = false; chunk.push(c); break } - default: chunk.push(c); + case '{': + if (!inParam) { + level++; + chunk.push(c); + break; + } + /* falls through */ + case '}': + if (!inParam) { + level--; + chunk.push(c); + chunks[++j] = chunk = []; + break; + } + /* falls through */ + case '(': + if (!inParam) { + inParam = true; + chunk.push(c); + break; + } + /* falls through */ + case ')': + if (inParam) { + inParam = false; + chunk.push(c); + break; + } + /* falls through */ + default: + chunk.push(c); } i++; } - if (level != 0) { + if (level !== 0) { error = new(LessError)({ index: i-1, type: 'Parse', @@ -371,7 +398,7 @@ less.Parser = function Parser(env) { }, env); } - return chunks.map(function (c) { return c.join('') }); + return chunks.map(function (c) { return c.join(''); }); })([[]]); if (error) { @@ -396,6 +423,8 @@ less.Parser = function Parser(env) { return function (options, variables) { options = options || {}; var importError, + evaldRoot, + css, evalEnv = new tree.evalEnv(options); // @@ -427,7 +456,7 @@ less.Parser = function Parser(env) { } try { - var evaldRoot = evaluate.call(this, evalEnv); + evaldRoot = evaluate.call(this, evalEnv); new(tree.joinSelectorVisitor)() .run(evaldRoot); @@ -438,7 +467,7 @@ less.Parser = function Parser(env) { new(tree.toCSSVisitor)({compress: Boolean(options.compress)}) .run(evaldRoot); - var css = evaldRoot.toCSS({ + css = evaldRoot.toCSS({ compress: Boolean(options.compress), dumpLineNumbers: env.dumpLineNumbers, strictUnits: Boolean(options.strictUnits)}); @@ -469,7 +498,7 @@ less.Parser = function Parser(env) { lines = input.split('\n'); line = (input.slice(0, i).match(/\n/g) || "").length + 1; - for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ } + for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++; } error = { type: "Parse", @@ -571,7 +600,7 @@ less.Parser = function Parser(env) { comment: function () { var comment; - if (input.charAt(i) !== '/') return; + if (input.charAt(i) !== '/') { return; } if (input.charAt(i + 1) === '/') { return new(tree.Comment)($(/^\/\/.*/), true, i, env.currentFileInfo); @@ -602,8 +631,8 @@ less.Parser = function Parser(env) { quoted: function () { var str, j = i, e, index = i; - if (input.charAt(j) === '~') { j++, e = true } // Escaped strings - if (input.charAt(j) !== '"' && input.charAt(j) !== "'") return; + if (input.charAt(j) === '~') { j++, e = true; } // Escaped strings + if (input.charAt(j) !== '"' && input.charAt(j) !== "'") { return; } e && $('~'); @@ -643,13 +672,13 @@ less.Parser = function Parser(env) { call: function () { var name, nameLC, args, alpha_ret, index = i; - if (! (name = /^([\w-]+|%|progid:[\w\.]+)\(/.exec(chunks[j]))) return; + if (! (name = /^([\w-]+|%|progid:[\w\.]+)\(/.exec(chunks[j]))) { return; } name = name[1]; nameLC = name.toLowerCase(); - if (nameLC === 'url') { return null } - else { i += name.length } + if (nameLC === 'url') { return null; } + else { i += name.length; } if (nameLC === 'alpha') { alpha_ret = $(this.alpha); @@ -673,7 +702,9 @@ less.Parser = function Parser(env) { while (arg = $(this.entities.assignment) || $(this.expression)) { args.push(arg); - if (! $(',')) { break } + if (! $(',')) { + break; + } } return args; }, @@ -707,12 +738,16 @@ less.Parser = function Parser(env) { url: function () { var value; - if (input.charAt(i) !== 'u' || !$(/^url\(/)) return; + if (input.charAt(i) !== 'u' || !$(/^url\(/)) { + return; + } + value = $(this.entities.quoted) || $(this.entities.variable) || $(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/) || ""; expect(')'); + /*jshint eqnull:true */ return new(tree.URL)((value.value != null || value instanceof tree.Variable) ? value : new(tree.Anonymous)(value), env.currentFileInfo); }, @@ -765,7 +800,9 @@ less.Parser = function Parser(env) { dimension: function () { var value, c = input.charCodeAt(i); //Is the first char of the dimension 0-9, '.', '+' or '-' - if ((c > 57 || c < 43) || c === 47 || c == 44) return; + if ((c > 57 || c < 43) || c === 47 || c == 44) { + return; + } if (value = $(/^([+-]?\d*\.?\d+)(%|[a-z]+)?/)) { return new(tree.Dimension)(value[1], value[2]); @@ -815,7 +852,7 @@ less.Parser = function Parser(env) { variable: function () { var name; - if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1] } + if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1]; } }, // @@ -841,7 +878,7 @@ less.Parser = function Parser(env) { extendList.push(new(tree.Extend)(new(tree.Selector)(elements), option, index)); - } while($(",")) + } while($(",")); expect(/^\)/); @@ -877,7 +914,7 @@ less.Parser = function Parser(env) { call: function () { var elements = [], e, c, args, delim, arg, index = i, s = input.charAt(i), important = false; - if (s !== '.' && s !== '#') { return } + if (s !== '.' && s !== '#') { return; } save(); // stop us absorbing part of an invalid selector @@ -937,7 +974,7 @@ less.Parser = function Parser(env) { if (isCall) { // Variable if (arg.value.length == 1) { - var val = arg.value[0]; + val = arg.value[0]; } } else { val = arg; @@ -986,7 +1023,7 @@ less.Parser = function Parser(env) { isSemiColonSeperated = true; if (expressions.length > 1) { - value = new (tree.Value)(expressions); + value = new(tree.Value)(expressions); } argsSemiColon.push({ name:name, value:value }); @@ -1021,7 +1058,9 @@ less.Parser = function Parser(env) { definition: function () { var name, params = [], match, ruleset, param, value, cond, variadic = false; if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') || - peek(/^[^{]*\}/)) return; + peek(/^[^{]*\}/)) { + return; + } save(); @@ -1083,7 +1122,7 @@ less.Parser = function Parser(env) { alpha: function () { var value; - if (! $(/^\(opacity=/i)) return; + if (! $(/^\(opacity=/i)) { return; } if (value = $(/^\d+/) || $(this.entities.variable)) { expect(')'); return new(tree.Alpha)(value); @@ -1119,7 +1158,7 @@ less.Parser = function Parser(env) { } } - if (e) { return new(tree.Element)(c, e, i) } + if (e) { return new(tree.Element)(c, e, i); } }, // @@ -1136,7 +1175,7 @@ less.Parser = function Parser(env) { if (c === '>' || c === '+' || c === '~' || c === '|') { i++; - while (input.charAt(i).match(/\s/)) { i++ } + while (input.charAt(i).match(/\s/)) { i++; } return new(tree.Combinator)(c); } else if (input.charAt(i - 1).match(/\s/)) { return new(tree.Combinator)(" "); @@ -1174,10 +1213,12 @@ less.Parser = function Parser(env) { error("Extend can only be used at the end of selector"); } c = input.charAt(i); - elements.push(e) + elements.push(e); e = null; } - if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') { break } + if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') { + break; + } } if (elements.length > 0) { return new(tree.Selector)(elements, extendList, condition, i, env.currentFileInfo); } @@ -1186,7 +1227,7 @@ less.Parser = function Parser(env) { attribute: function () { var attr = '', key, val, op; - if (! $('[')) return; + if (! $('[')) { return; } if (!(key = $(this.entities.variableCurly))) { key = expect(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/); @@ -1220,8 +1261,9 @@ less.Parser = function Parser(env) { save(); - if (env.dumpLineNumbers) + if (env.dumpLineNumbers) { debugInfo = getDebugInfo(i, input, env); + } while (s = $(this.lessSelector)) { selectors.push(s); @@ -1235,8 +1277,9 @@ less.Parser = function Parser(env) { if (selectors.length > 0 && (rules = $(this.block))) { var ruleset = new(tree.Ruleset)(selectors, rules, env.strictImports); - if (env.dumpLineNumbers) + if (env.dumpLineNumbers) { ruleset.debugInfo = debugInfo; + } return ruleset; } else { // Backtrack @@ -1248,7 +1291,7 @@ less.Parser = function Parser(env) { var name, value, c = input.charAt(i), important, merge = false; save(); - if (c === '.' || c === '#' || c === '&') { return } + if (c === '.' || c === '#' || c === '&') { return; } if (name = $(this.variable) || $(this.ruleProperty)) { // prefer to try to parse first if its a variable or we are compressing @@ -1333,7 +1376,7 @@ less.Parser = function Parser(env) { break; } options[optionName] = value; - if (! $(',')) { break } + if (! $(',')) { break; } } } while (o); expect(')'); @@ -1364,7 +1407,7 @@ less.Parser = function Parser(env) { } else { return null; } - } else { return null } + } else { return null; } } } while (e); @@ -1379,10 +1422,10 @@ less.Parser = function Parser(env) { do { if (e = $(this.mediaFeature)) { features.push(e); - if (! $(',')) { break } + if (! $(',')) { break; } } else if (e = $(this.entities.variable)) { features.push(e); - if (! $(',')) { break } + if (! $(',')) { break; } } } while (e); @@ -1392,16 +1435,18 @@ less.Parser = function Parser(env) { media: function () { var features, rules, media, debugInfo; - if (env.dumpLineNumbers) + if (env.dumpLineNumbers) { debugInfo = getDebugInfo(i, input, env); + } if ($(/^@media/)) { features = $(this.mediaFeatures); if (rules = $(this.block)) { media = new(tree.Media)(rules, features, i, env.currentFileInfo); - if(env.dumpLineNumbers) + if (env.dumpLineNumbers) { media.debugInfo = debugInfo; + } return media; } } @@ -1416,7 +1461,7 @@ less.Parser = function Parser(env) { var name, value, rules, identifier, e, nodes, nonVendorSpecificName, hasBlock, hasIdentifier, hasExpression; - if (input.charAt(i) !== '@') return; + if (input.charAt(i) !== '@') { return; } if (value = $(this['import']) || $(this.media)) { return value; @@ -1426,7 +1471,7 @@ less.Parser = function Parser(env) { name = $(/^@[a-z-]+/); - if (!name) return; + if (!name) { return; } nonVendorSpecificName = name; if (name.charAt(1) == '-' && name.indexOf('-', 2) > 0) { @@ -1503,7 +1548,7 @@ less.Parser = function Parser(env) { while (e = $(this.expression)) { expressions.push(e); - if (! $(',')) { break } + if (! $(',')) { break; } } if (expressions.length > 0) { @@ -1571,7 +1616,7 @@ less.Parser = function Parser(env) { condition: function () { var a, b, c, op, index = i, negate = false; - if ($(/^not/)) { negate = true } + if ($(/^not/)) { negate = true; } expect('('); if (a = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) { if (op = $(/^(?:>=|=<|[<=>])/)) { @@ -1595,7 +1640,7 @@ less.Parser = function Parser(env) { operand: function () { var negate, p = input.charAt(i + 1); - if (input.charAt(i) === '-' && (p === '@' || p === '(')) { negate = $('-') } + if (input.charAt(i) === '-' && (p === '@' || p === '(')) { negate = $('-'); } var o = $(this.sub) || $(this.entities.dimension) || $(this.entities.color) || $(this.entities.variable) || $(this.entities.call); diff --git a/lib/less/rhino.js b/lib/less/rhino.js index fe917a2b..08eba88a 100644 --- a/lib/less/rhino.js +++ b/lib/less/rhino.js @@ -1,3 +1,5 @@ +/*jshint rhino:true */ +/*global name:true, less */ var name; function loadStyleSheet(sheet, callback, reload, remaining) { @@ -61,7 +63,7 @@ function writeFile(filename, content) { print('No files present in the fileset; Check your pattern match in build.xml'); quit(1); } - path = name.split("/");path.pop();path=path.join("/") + path = name.split("/");path.pop();path=path.join("/"); var input = readFile(name); @@ -109,7 +111,7 @@ function error(e, filename) { var errorline = function (e, i, classname) { if (e.extract[i]) { content += - String(parseInt(e.line) + (i - 1)) + + String(parseInt(e.line, 10) + (i - 1)) + ":" + e.extract[i] + "\n"; } }; @@ -123,4 +125,4 @@ function error(e, filename) { errorline(e, 2); } print(content); -} \ No newline at end of file +} diff --git a/lib/less/tree.js b/lib/less/tree.js index 9aee4613..924aa891 100644 --- a/lib/less/tree.js +++ b/lib/less/tree.js @@ -24,19 +24,24 @@ tree.debugInfo.asComment = function(ctx) { tree.debugInfo.asMediaQuery = function(ctx) { return '@media -sass-debug-info{filename{font-family:' + - ('file://' + ctx.debugInfo.fileName).replace(/([.:/\\])/g, function(a){if(a=='\\') a = '\/'; return '\\' + a}) + + ('file://' + ctx.debugInfo.fileName).replace(/([.:/\\])/g, function (a) { + if (a == '\\') { + a = '\/'; + } + return '\\' + a; + }) + '}line{font-family:\\00003' + ctx.debugInfo.lineNumber + '}}\n'; }; tree.find = function (obj, fun) { for (var i = 0, r; i < obj.length; i++) { - if (r = fun.call(obj, obj[i])) { return r } + if (r = fun.call(obj, obj[i])) { return r; } } return null; }; tree.jsify = function (obj) { if (Array.isArray(obj.value) && (obj.value.length > 1)) { - return '[' + obj.value.map(function (v) { return v.toCSS(false) }).join(', ') + ']'; + return '[' + obj.value.map(function (v) { return v.toCSS(false); }).join(', ') + ']'; } else { return obj.toCSS(false); } diff --git a/lib/less/tree/anonymous.js b/lib/less/tree/anonymous.js index a009cf27..b78e0b94 100644 --- a/lib/less/tree/anonymous.js +++ b/lib/less/tree/anonymous.js @@ -8,7 +8,7 @@ tree.Anonymous.prototype = { toCSS: function () { return this.value; }, - eval: function () { return this }, + eval: function () { return this; }, compare: function (x) { if (!x.toCSS) { return -1; diff --git a/lib/less/tree/assignment.js b/lib/less/tree/assignment.js index b0e2c555..1ba43685 100644 --- a/lib/less/tree/assignment.js +++ b/lib/less/tree/assignment.js @@ -20,4 +20,4 @@ tree.Assignment.prototype = { } }; -})(require('../tree')); \ No newline at end of file +})(require('../tree')); diff --git a/lib/less/tree/call.js b/lib/less/tree/call.js index 20a1fc0e..2bb88441 100644 --- a/lib/less/tree/call.js +++ b/lib/less/tree/call.js @@ -36,6 +36,7 @@ tree.Call.prototype = { try { func = new tree.functionCall(env, this.currentFileInfo); result = func[nameLC].apply(func, args); + /*jshint eqnull:true */ if (result != null) { return result; } diff --git a/lib/less/tree/color.js b/lib/less/tree/color.js index 95d5a101..a0465dbd 100644 --- a/lib/less/tree/color.js +++ b/lib/less/tree/color.js @@ -24,7 +24,7 @@ tree.Color = function (rgb, a) { }; tree.Color.prototype = { type: "Color", - eval: function () { return this }, + eval: function () { return this; }, luma: function () { return (0.2126 * this.rgb[0] / 255) + (0.7152 * this.rgb[1] / 255) + (0.0722 * this.rgb[2] / 255); }, // diff --git a/lib/less/tree/condition.js b/lib/less/tree/condition.js index 8608b69d..47b1d008 100644 --- a/lib/less/tree/condition.js +++ b/lib/less/tree/condition.js @@ -19,7 +19,7 @@ tree.Condition.prototype = { var i = this.index, result; - var result = (function (op) { + result = (function (op) { switch (op) { case 'and': return a && b; diff --git a/lib/less/tree/dimension.js b/lib/less/tree/dimension.js index 9850e6ff..bd58efe4 100644 --- a/lib/less/tree/dimension.js +++ b/lib/less/tree/dimension.js @@ -52,6 +52,7 @@ tree.Dimension.prototype = { // we default to the first Dimension's unit, // so `1px + 2` will yield `3px`. operate: function (env, op, other) { + /*jshint noempty:false */ var value = tree.operate(env, op, this.value, other.value), unit = this.unit.clone(); @@ -59,7 +60,7 @@ tree.Dimension.prototype = { if (unit.numerator.length === 0 && unit.denominator.length === 0) { unit.numerator = other.unit.numerator.slice(0); unit.denominator = other.unit.denominator.slice(0); - } else if (other.unit.numerator.length == 0 && unit.denominator.length == 0) { + } else if (other.unit.numerator.length === 0 && unit.denominator.length === 0) { // do nothing } else { other = other.convertTo(this.unit.usedUnits()); @@ -126,6 +127,7 @@ tree.Dimension.prototype = { targetUnit = conversions[groupName]; group = tree.UnitConversions[groupName]; + /*jshint loopfunc:true */ unit.map(function (atomicUnit, denominator) { if (group.hasOwnProperty(atomicUnit)) { if (denominator) { @@ -216,11 +218,11 @@ tree.Unit.prototype = { }, isEmpty: function () { - return this.numerator.length == 0 && this.denominator.length == 0; + return this.numerator.length === 0 && this.denominator.length === 0; }, isSingular: function() { - return this.numerator.length <= 1 && this.denominator.length == 0; + return this.numerator.length <= 1 && this.denominator.length === 0; }, map: function(callback) { @@ -242,6 +244,7 @@ tree.Unit.prototype = { if (tree.UnitConversions.hasOwnProperty(groupName)) { group = tree.UnitConversions[groupName]; + /*jshint loopfunc:true */ this.map(function (atomicUnit) { if (group.hasOwnProperty(atomicUnit) && !result[groupName]) { result[groupName] = atomicUnit; diff --git a/lib/less/tree/element.js b/lib/less/tree/element.js index 55d47903..8adc28ac 100644 --- a/lib/less/tree/element.js +++ b/lib/less/tree/element.js @@ -26,7 +26,7 @@ tree.Element.prototype = { }, toCSS: function (env) { var value = (this.value.toCSS ? this.value.toCSS(env) : this.value); - if (value == '' && this.combinator.value.charAt(0) == '&') { + if (value === '' && this.combinator.value.charAt(0) === '&') { return ''; } else { return this.combinator.toCSS(env || {}) + value; diff --git a/lib/less/tree/javascript.js b/lib/less/tree/javascript.js index eaa9c0fe..428a56d6 100644 --- a/lib/less/tree/javascript.js +++ b/lib/less/tree/javascript.js @@ -24,6 +24,7 @@ tree.JavaScript.prototype = { } for (var k in env.frames[0].variables()) { + /*jshint loopfunc:true */ context[k.slice(1)] = { value: env.frames[0].variables()[k].value, toJS: function () { diff --git a/lib/less/tree/keyword.js b/lib/less/tree/keyword.js index 3184fce2..25ab7851 100644 --- a/lib/less/tree/keyword.js +++ b/lib/less/tree/keyword.js @@ -1,6 +1,6 @@ (function (tree) { -tree.Keyword = function (value) { this.value = value }; +tree.Keyword = function (value) { this.value = value; }; tree.Keyword.prototype = { type: "Keyword", eval: function () { return this; }, diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index 57d68a2d..55105929 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -69,7 +69,7 @@ tree.Media.prototype = { env.mediaPath.pop(); return env.mediaPath.length === 0 ? media.evalTop(env) : - media.evalNested(env) + media.evalNested(env); }, variable: function (name) { return tree.Ruleset.prototype.variable.call(this.rules[0], name); }, find: function () { return tree.Ruleset.prototype.find.apply(this.rules[0], arguments); }, diff --git a/lib/less/tree/mixin.js b/lib/less/tree/mixin.js index b7e7f3e8..2908d981 100644 --- a/lib/less/tree/mixin.js +++ b/lib/less/tree/mixin.js @@ -96,8 +96,8 @@ tree.mixin.Definition = function (name, params, rules, condition, variadic) { this.rules = rules; this._lookups = {}; this.required = params.reduce(function (count, p) { - if (!p.name || (p.name && !p.value)) { return count + 1 } - else { return count } + if (!p.name || (p.name && !p.value)) { return count + 1; } + else { return count; } }, 0); this.parent = tree.Ruleset.prototype; this.frames = []; @@ -116,6 +116,7 @@ tree.mixin.Definition.prototype = { rulesets: function () { return this.parent.rulesets.apply(this); }, evalParams: function (env, mixinEnv, args, evaldArguments) { + /*jshint boss:true */ var frame = new(tree.Ruleset)(null, []), varargs, arg, params = this.params.slice(0), @@ -151,7 +152,7 @@ tree.mixin.Definition.prototype = { } argIndex = 0; for (i = 0; i < params.length; i++) { - if (evaldArguments[i]) continue; + if (evaldArguments[i]) { continue; } arg = args && args[argIndex]; @@ -219,9 +220,9 @@ tree.mixin.Definition.prototype = { var argsLength = (args && args.length) || 0, len, frame; if (! this.variadic) { - if (argsLength < this.required) { return false } - if (argsLength > this.params.length) { return false } - if ((this.required > 0) && (argsLength > this.params.length)) { return false } + if (argsLength < this.required) { return false; } + if (argsLength > this.params.length) { return false; } + if ((this.required > 0) && (argsLength > this.params.length)) { return false; } } len = Math.min(argsLength, this.arity); diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 9f7f0574..817b57c7 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -13,9 +13,11 @@ tree.Ruleset.prototype = { this.rules = visitor.visit(this.rules); }, eval: function (env) { - var selectors = this.selectors && this.selectors.map(function (s) { return s.eval(env) }); + var selectors = this.selectors && this.selectors.map(function (s) { return s.eval(env); }); var ruleset = new(tree.Ruleset)(selectors, this.rules.slice(0), this.strictImports); var rules; + var rule; + var i; ruleset.originalRuleset = this; ruleset.root = this.root; @@ -42,7 +44,7 @@ tree.Ruleset.prototype = { // Store the frames around mixin definitions, // so they can be evaluated like closures when the time comes. - for (var i = 0; i < ruleset.rules.length; i++) { + for (i = 0; i < ruleset.rules.length; i++) { if (ruleset.rules[i] instanceof tree.mixin.Definition) { ruleset.rules[i].frames = env.frames.slice(0); } @@ -51,8 +53,9 @@ tree.Ruleset.prototype = { var mediaBlockCount = (env.mediaBlocks && env.mediaBlocks.length) || 0; // Evaluate mixin calls. - for (var i = 0; i < ruleset.rules.length; i++) { + for (i = 0; i < ruleset.rules.length; i++) { if (ruleset.rules[i] instanceof tree.mixin.Call) { + /*jshint loopfunc:true */ rules = ruleset.rules[i].eval(env).filter(function(r) { if ((r instanceof tree.Rule) && r.variable) { // do not pollute the scope if the variable is @@ -69,7 +72,7 @@ tree.Ruleset.prototype = { } // Evaluate everything else - for (var i = 0, rule; i < ruleset.rules.length; i++) { + for (i = 0; i < ruleset.rules.length; i++) { rule = ruleset.rules[i]; if (! (rule instanceof tree.mixin.Definition)) { @@ -82,7 +85,7 @@ tree.Ruleset.prototype = { env.selectors.shift(); if (env.mediaBlocks) { - for(var i = mediaBlockCount; i < env.mediaBlocks.length; i++) { + for (i = mediaBlockCount; i < env.mediaBlocks.length; i++) { env.mediaBlocks[i].bubbleSelectors(selectors); } } @@ -132,7 +135,7 @@ tree.Ruleset.prototype = { this._lookups = {}; }, variables: function () { - if (this._variables) { return this._variables } + if (this._variables) { return this._variables; } else { return this._variables = this.rules.reduce(function (hash, r) { if (r instanceof tree.Rule && r.variable === true) { @@ -155,7 +158,7 @@ tree.Ruleset.prototype = { var rules = [], rule, match, key = selector.toCSS(); - if (key in this._lookups) { return this._lookups[key] } + if (key in this._lookups) { return this._lookups[key]; } this.rulesets().forEach(function (rule) { if (rule !== self) { @@ -186,12 +189,13 @@ tree.Ruleset.prototype = { rulesets = [], // node.Ruleset instances selector, // The fully rendered selector debugInfo, // Line number debugging - rule; + rule, + i; this.mergeRules(); // Compile rules and rulesets - for (var i = 0; i < this.rules.length; i++) { + for (i = 0; i < this.rules.length; i++) { rule = this.rules[i]; if (rule.rules || (rule instanceof tree.Media) || rule instanceof tree.Directive) { @@ -238,7 +242,7 @@ tree.Ruleset.prototype = { if (selector) { // Remove duplicates - for (var i = rules.length - 1; i >= 0; i--) { + for (i = rules.length - 1; i >= 0; i--) { if (rules[i].slice(0, 2) === "/*" || _rules.indexOf(rules[i]) === -1) { _rules.unshift(rules[i]); } @@ -289,7 +293,7 @@ tree.Ruleset.prototype = { if (!hasParentSelector) { if (context.length > 0) { - for(i = 0; i < context.length; i++) { + for (i = 0; i < context.length; i++) { paths.push(context[i].concat(selector)); } } @@ -333,11 +337,11 @@ tree.Ruleset.prototype = { } // loop through our current selectors - for(j = 0; j < newSelectors.length; j++) { + for (j = 0; j < newSelectors.length; j++) { sel = newSelectors[j]; // if we don't have any parent paths, the & might be in a mixin so that it can be used // whether there are parents or not - if (context.length == 0) { + if (context.length === 0) { // the combinator used on el should now be applied to the next element instead so that // it is not lost if (sel.length > 0) { @@ -348,7 +352,7 @@ tree.Ruleset.prototype = { } else { // and the parent selectors - for(k = 0; k < context.length; k++) { + for (k = 0; k < context.length; k++) { parentSel = context[k]; // We need to put the current selectors // then join the last selector's elements on to the parents selectors @@ -410,7 +414,7 @@ tree.Ruleset.prototype = { this.mergeElementsOnToSelectors(currentElements, newSelectors); } - for(i = 0; i < newSelectors.length; i++) { + for (i = 0; i < newSelectors.length; i++) { if (newSelectors[i].length > 0) { paths.push(newSelectors[i]); } @@ -420,12 +424,12 @@ tree.Ruleset.prototype = { mergeElementsOnToSelectors: function(elements, selectors) { var i, sel, extendList; - if (selectors.length == 0) { + if (selectors.length === 0) { selectors.push([ new(tree.Selector)(elements) ]); return; } - for(i = 0; i < selectors.length; i++) { + for (i = 0; i < selectors.length; i++) { sel = selectors[i]; // if the previous thing in sel is a parent this needs to join on to it diff --git a/lib/less/tree/selector.js b/lib/less/tree/selector.js index 0fc8582d..943b694a 100644 --- a/lib/less/tree/selector.js +++ b/lib/less/tree/selector.js @@ -18,6 +18,7 @@ tree.Selector.prototype = { this.condition = visitor.visit(this.condition); }, createDerived: function(elements, extendList, evaldCondition) { + /*jshint eqnull:true */ evaldCondition = evaldCondition != null ? evaldCondition : this.evaldCondition; var newSelector = new(tree.Selector)(elements, extendList || this.extendList, this.condition, this.index, this.currentFileInfo, this.isReferenced); newSelector.evaldCondition = evaldCondition; @@ -54,7 +55,7 @@ tree.Selector.prototype = { }), evaldCondition); }, toCSS: function (env) { - if (this._css) { return this._css } + if (this._css) { return this._css; } if (this.elements[0].combinator.value === "") { this._css = ' '; diff --git a/lib/less/tree/unicode-descriptor.js b/lib/less/tree/unicode-descriptor.js index 3f725127..cf4ff853 100644 --- a/lib/less/tree/unicode-descriptor.js +++ b/lib/less/tree/unicode-descriptor.js @@ -8,7 +8,7 @@ tree.UnicodeDescriptor.prototype = { toCSS: function (env) { return this.value; }, - eval: function () { return this } + eval: function () { return this; } }; })(require('../tree')); diff --git a/lib/less/tree/variable.js b/lib/less/tree/variable.js index ff57494c..8f146932 100644 --- a/lib/less/tree/variable.js +++ b/lib/less/tree/variable.js @@ -1,12 +1,16 @@ (function (tree) { -tree.Variable = function (name, index, currentFileInfo) { this.name = name, this.index = index, this.currentFileInfo = currentFileInfo }; +tree.Variable = function (name, index, currentFileInfo) { + this.name = name; + this.index = index; + this.currentFileInfo = currentFileInfo; +}; tree.Variable.prototype = { type: "Variable", eval: function (env) { var variable, v, name = this.name; - if (name.indexOf('@@') == 0) { + if (name.indexOf('@@') === 0) { name = '@' + new(tree.Variable)(name.slice(1)).eval(env).value; } diff --git a/test/browser-test-prepare.js b/test/browser-test-prepare.js index 1a1556f6..644ac6ff 100644 --- a/test/browser-test-prepare.js +++ b/test/browser-test-prepare.js @@ -7,7 +7,7 @@ var readDirFilesSync = function(dir, regex, callback) { if (! regex.test(file)) { return; } callback(file); }); -} +}; var createTestRunnerPage = function(dir, exclude, testSuiteName, dir2) { var output = '\n'; @@ -33,7 +33,7 @@ var removeFiles = function(dir, regex) { console.log("Failed to delete " + file); }); }); -} +}; removeFiles("test/browser", /test-runner-[a-zA-Z-]*\.htm$/); createTestRunnerPage("", /javascript|urls/, "main"); diff --git a/test/less-test.js b/test/less-test.js index 50b868ca..5b984004 100644 --- a/test/less-test.js +++ b/test/less-test.js @@ -20,7 +20,7 @@ less.tree.functions.increment = function (a) { return new(less.tree.Dimension)(a.value + 1); }; less.tree.functions._color = function (str) { - if (str.value === "evil red") { return new(less.tree.Color)("600") } + if (str.value === "evil red") { return new(less.tree.Color)("600"); } }; sys.puts("\n" + stylize("LESS", 'underline') + "\n"); @@ -101,7 +101,7 @@ function runTestSet(options, foldername, verifyFunction, nameModifier, doReplace doReplacements = globalReplacements; fs.readdirSync(path.join('test/less/', foldername)).forEach(function (file) { - if (! /\.less/.test(file)) { return } + if (! /\.less/.test(file)) { return; } var name = foldername + path.basename(file, '.less'); @@ -117,7 +117,7 @@ function runTestSet(options, foldername, verifyFunction, nameModifier, doReplace var css_name = name; if(nameModifier) css_name=nameModifier(name); fs.readFile(path.join('test/css', css_name) + '.css', 'utf8', function (e, css) { - sys.print("- " + css_name + ": ") + sys.print("- " + css_name + ": "); css = css && doReplacements(css, 'test/less/' + foldername); if (less === css) { ok('OK'); } @@ -189,7 +189,7 @@ function toCSS(options, path, callback) { var tree, css; options = options || {}; fs.readFile(path, 'utf8', function (e, str) { - if (e) { return callback(e) } + if (e) { return callback(e); } options.paths = [require('path').dirname(path)]; options.filename = require('path').resolve(process.cwd(), path); @@ -214,7 +214,7 @@ function testNoOptions() { totalTests++; try { sys.print("- Integration - creating parser without options: "); - new(less.Parser); + new(less.Parser)(); } catch(e) { fail(stylize("FAIL\n", "red")); return; From f000522855b5739650e28e14d3f8761f6515c5e0 Mon Sep 17 00:00:00 2001 From: Daniel Stockman Date: Mon, 8 Jul 2013 19:09:52 -0700 Subject: [PATCH 03/34] Run JSHint in pretest phase, with basic config and ignores. --- .jshintignore | 5 +++++ .jshintrc | 7 +++++++ package.json | 4 +++- 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 .jshintignore create mode 100644 .jshintrc diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 00000000..ccc70d75 --- /dev/null +++ b/.jshintignore @@ -0,0 +1,5 @@ +benchmark/ +build/ +dist/ +node_modules/ +test/browser/ diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 00000000..2265f328 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,7 @@ +{ + "evil": true, + "boss": true, + "expr": true, + "laxbreak": true, + "node": true +} diff --git a/package.json b/package.json index 1a12f314..ed77b6df 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "node": ">=0.4.2" }, "scripts": { + "pretest": "./node_modules/.bin/jshint --config ./.jshintrc .", "test": "make test" }, "optionalDependencies": { @@ -37,7 +38,8 @@ "ycssmin": ">=1.0.1" }, "devDependencies": { - "diff": "~1.0" + "diff": "~1.0", + "jshint": "~2.1.4" }, "keywords": [ "compile less", From 66a9890c3951a1347167a4a07743414603a6b28e Mon Sep 17 00:00:00 2001 From: Daniel Stockman Date: Mon, 8 Jul 2013 19:17:04 -0700 Subject: [PATCH 04/34] Log stacks when errors are caught in tests to aid debugging. --- test/less-test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/less-test.js b/test/less-test.js index 50b868ca..e7b2bb47 100644 --- a/test/less-test.js +++ b/test/less-test.js @@ -9,6 +9,8 @@ var globals = Object.keys(global); var oneTestOnly = process.argv[2]; +var isVerbose = process.env.npm_config_loglevel === 'verbose'; + var totalTests = 0, failedTests = 0, passedTests = 0; @@ -123,6 +125,10 @@ function runTestSet(options, foldername, verifyFunction, nameModifier, doReplace if (less === css) { ok('OK'); } else if (err) { fail("ERROR: " + (err && err.message)); + if (isVerbose) { + console.error(); + console.error(err.stack); + } } else { difference("FAIL", css, less); } From 283d623a9891184c915d6d11b128e7f158ddbe81 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Mon, 8 Jul 2013 13:39:48 +0100 Subject: [PATCH 05/34] switch to use the clean-css compressor. #1349 --- CHANGELOG.md | 3 ++- bin/lessc | 15 ++++++++++++--- lib/less/env.js | 3 ++- lib/less/lessc_helper.js | 3 +-- lib/less/parser.js | 4 ++-- package.json | 2 +- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 904c3c38..ab830774 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # 1.5.0 WIP - - support for import inline option to include css that you do not want less to parse e.g. `@import (inline) "file.css";` + - support for import inline option to include css that you do NOT want less to parse e.g. `@import (inline) "file.css";` - better support for modifyVars (refresh styles with new variables, using a file cache), is now more resiliant - support for import reference option to reference external css, but not output it. Any mixin calls or extend's will be output. - support for guards on selectors (currently only if you have a single selector) @@ -12,6 +12,7 @@ - Fix the saturate function to pass through when using the CSS syntax - Added svg-gradient function - Added no-js option to lessc (in browser, use javascriptEnabled: false) which disallows JavaScript in less files + - switched from the little supported and buggy cssmin (previously ycssmin) to clean-css # 1.4.1 diff --git a/bin/lessc b/bin/lessc index 91114875..01de8320 100755 --- a/bin/lessc +++ b/bin/lessc @@ -11,7 +11,7 @@ var args = process.argv.slice(1); var options = { depends: false, compress: false, - yuicompress: false, + cleancss: false, max_line_len: -1, optimization: 1, silent: false, @@ -52,6 +52,8 @@ var checkBooleanArg = function(arg) { return Boolean(onOff[2]); }; +var warningMessages = ""; + args = args.filter(function (arg) { var match; @@ -95,7 +97,11 @@ args = args.filter(function (arg) { options.depends = true; break; case 'yui-compress': - options.yuicompress = true; + warningMessages += "yui-compress option has been removed. assuming clean-css."; + options.cleancss = true; + break; + case 'clean-css': + options.cleancss = true; break; case 'max-line-len': if (checkArgFunc(arg, match[2])) { @@ -170,6 +176,9 @@ var output = args[2]; var outputbase = args[2]; if (output) { output = path.resolve(process.cwd(), output); + if (warningMessages) { + sys.puts(warningMessages); + } } if (! input) { @@ -230,7 +239,7 @@ var parseLessFile = function (e, data) { verbose: options.verbose, ieCompat: options.ieCompat, compress: options.compress, - yuicompress: options.yuicompress, + cleancss: options.cleancss, maxLineLen: options.maxLineLen, strictMath: options.strictMath, strictUnits: options.strictUnits diff --git a/lib/less/env.js b/lib/less/env.js index 203830e0..feb79baa 100644 --- a/lib/less/env.js +++ b/lib/less/env.js @@ -57,7 +57,8 @@ 'yuicompress', // whether to compress with the outside tool yui compressor 'ieCompat', // whether to enforce IE compatibility (IE8 data-uri) 'strictMath', // whether math has to be within parenthesis - 'strictUnits' // whether units need to evaluate correctly + 'strictUnits', // whether units need to evaluate correctly + 'cleancss' // whether to compress with clean-css ]; tree.evalEnv = function(options, frames) { diff --git a/lib/less/lessc_helper.js b/lib/less/lessc_helper.js index 08d29992..6c9bb051 100644 --- a/lib/less/lessc_helper.js +++ b/lib/less/lessc_helper.js @@ -40,8 +40,7 @@ var lessc_helper = { sys.puts(" --verbose Be verbose."); sys.puts(" -v, --version Print version number and exit."); sys.puts(" -x, --compress Compress output by removing some whitespaces."); - sys.puts(" --yui-compress Compress output using ycssmin"); - sys.puts(" --max-line-len=LINELEN Max line length used by ycssmin"); + sys.puts(" --clean-css Compress output using clean-css"); sys.puts(" -O0, -O1, -O2 Set the parser's optimization level. The lower"); sys.puts(" the number, the less nodes it will create in the"); sys.puts(" tree. This could matter for debugging, or if you"); diff --git a/lib/less/parser.js b/lib/less/parser.js index 9c7f878e..f537d5f6 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -446,8 +446,8 @@ less.Parser = function Parser(env) { throw new(LessError)(e, env); } - if (options.yuicompress && less.mode === 'node') { - return require('ycssmin').cssmin(css, options.maxLineLen); + if (options.cleancss && less.mode === 'node') { + return require('clean-css').process(css); } else if (options.compress) { return css.replace(/(^(\s)+)|((\s)+$)/g, ""); } else { diff --git a/package.json b/package.json index 1a12f314..50a5325a 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "mime": "1.2.x", "request": ">=2.12.0", "mkdirp": "~0.3.4", - "ycssmin": ">=1.0.1" + "clean-css": "1.0.x" }, "devDependencies": { "diff": "~1.0" From 24e2b01d6e58fc1432d9ae53a14015bee550b1e1 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 9 Jul 2013 13:29:58 +0100 Subject: [PATCH 06/34] better environment detection --- Makefile | 2 ++ build/browser-header.js | 4 ++++ build/rhino-header.js | 4 ++++ lib/less/parser.js | 21 ++++----------------- 4 files changed, 14 insertions(+), 17 deletions(-) create mode 100644 build/browser-header.js create mode 100644 build/rhino-header.js diff --git a/Makefile b/Makefile index db27cf04..94d25109 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,7 @@ less: @@cat ${HEADER} | sed s/@VERSION/${VERSION}/ > ${DIST} @@echo "(function (window, undefined) {" >> ${DIST} @@cat build/require.js\ + build/browser-header.js\ ${SRC}/parser.js\ ${SRC}/functions.js\ ${SRC}/colors.js\ @@ -64,6 +65,7 @@ rhino: @@mkdir -p dist @@touch ${RHINO} @@cat build/require-rhino.js\ + build/rhino-header.js\ ${SRC}/parser.js\ ${SRC}/env.js\ ${SRC}/visitor.js\ diff --git a/build/browser-header.js b/build/browser-header.js new file mode 100644 index 00000000..e0022b71 --- /dev/null +++ b/build/browser-header.js @@ -0,0 +1,4 @@ +if (typeof(window.less) === 'undefined') { window.less = {}; } +less = window.less; +tree = window.less.tree = {}; +less.mode = 'browser'; diff --git a/build/rhino-header.js b/build/rhino-header.js new file mode 100644 index 00000000..891f0943 --- /dev/null +++ b/build/rhino-header.js @@ -0,0 +1,4 @@ +if (typeof(window) === 'undefined') { less = {} } +else { less = window.less = {} } +tree = less.tree = {}; +less.mode = 'rhino'; \ No newline at end of file diff --git a/lib/less/parser.js b/lib/less/parser.js index f537d5f6..c0e4b327 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1,23 +1,10 @@ -var less, tree, charset; +var less, tree; -if (typeof environment === "object" && ({}).toString.call(environment) === "[object Environment]") { - // Rhino - // Details on how to detect Rhino: https://github.com/ringo/ringojs/issues/88 - if (typeof(window) === 'undefined') { less = {} } - else { less = window.less = {} } - tree = less.tree = {}; - less.mode = 'rhino'; -} else if (typeof(window) === 'undefined') { - // Node.js - less = exports, +// Node.js does not have a header file added which defines less +if (less === undefined) { + less = exports; tree = require('./tree'); less.mode = 'node'; -} else { - // Browser - if (typeof(window.less) === 'undefined') { window.less = {} } - less = window.less, - tree = window.less.tree = {}; - less.mode = 'browser'; } // // less.js - parser From 4db7c883cf35fb1a67d375b6ad708d6659d001bd Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 11 Jul 2013 22:08:38 +0100 Subject: [PATCH 07/34] start refactoring toCSS so we will be able to collect sourcemap information at the same time --- lib/less/to-css-visitor.js | 3 + lib/less/tree.js | 11 + lib/less/tree/alpha.js | 18 +- lib/less/tree/anonymous.js | 11 +- lib/less/tree/assignment.js | 14 +- lib/less/tree/call.js | 23 ++- lib/less/tree/color.js | 16 +- lib/less/tree/comment.js | 8 +- lib/less/tree/dimension.js | 310 ++++++++++++++-------------- lib/less/tree/directive.js | 14 +- lib/less/tree/element.js | 9 + lib/less/tree/expression.js | 12 +- lib/less/tree/import.js | 3 + lib/less/tree/keyword.js | 3 + lib/less/tree/media.js | 3 + lib/less/tree/negative.js | 3 + lib/less/tree/operation.js | 3 + lib/less/tree/paren.js | 3 + lib/less/tree/quoted.js | 3 + lib/less/tree/rule.js | 3 + lib/less/tree/ruleset.js | 3 + lib/less/tree/selector.js | 3 + lib/less/tree/unicode-descriptor.js | 5 +- lib/less/tree/url.js | 3 + lib/less/tree/value.js | 3 + package.json | 9 +- 26 files changed, 300 insertions(+), 199 deletions(-) diff --git a/lib/less/to-css-visitor.js b/lib/less/to-css-visitor.js index 0af0f157..c3d67bb4 100644 --- a/lib/less/to-css-visitor.js +++ b/lib/less/to-css-visitor.js @@ -29,6 +29,9 @@ }, visitDirective: function(directiveNode, visitArgs) { + if (directiveNode.currentFileInfo.reference && !directiveNode.isReferenced) { + return []; + } if (directiveNode.name === "@charset") { // Only output the debug info together with subsequent @charset definitions // a comment (or @media statement) before the actual @charset directive would diff --git a/lib/less/tree.js b/lib/less/tree.js index 9aee4613..290e8aaa 100644 --- a/lib/less/tree.js +++ b/lib/less/tree.js @@ -34,6 +34,7 @@ tree.find = function (obj, fun) { } return null; }; + tree.jsify = function (obj) { if (Array.isArray(obj.value) && (obj.value.length > 1)) { return '[' + obj.value.map(function (v) { return v.toCSS(false) }).join(', ') + ']'; @@ -42,4 +43,14 @@ tree.jsify = function (obj) { } }; +tree.toCSS = function (env) { + var strs = []; + this.genCSS(env, { + add: function(chunk, node) { + strs.push(chunk); + } + }); + return strs.join(''); +}; + })(require('./tree')); diff --git a/lib/less/tree/alpha.js b/lib/less/tree/alpha.js index cb8db113..e827fd08 100644 --- a/lib/less/tree/alpha.js +++ b/lib/less/tree/alpha.js @@ -9,13 +9,21 @@ tree.Alpha.prototype = { this.value = visitor.visit(this.value); }, eval: function (env) { - if (this.value.eval) { this.value = this.value.eval(env); } + if (this.value.eval) { return new tree.Alpha(this.value.eval(env)); } return this; }, - toCSS: function () { - return "alpha(opacity=" + - (this.value.toCSS ? this.value.toCSS() : this.value) + ")"; - } + genCSS: function (env, output) { + output.add("alpha(opacity="); + + if (this.value.genCSS) { + this.value.genCSS(env, output); + } else { + output.add(this.value); + } + + output.add(")"); + }, + toCSS: tree.toCSS }; })(require('../tree')); diff --git a/lib/less/tree/anonymous.js b/lib/less/tree/anonymous.js index a009cf27..216a43db 100644 --- a/lib/less/tree/anonymous.js +++ b/lib/less/tree/anonymous.js @@ -5,10 +5,7 @@ tree.Anonymous = function (string) { }; tree.Anonymous.prototype = { type: "Anonymous", - toCSS: function () { - return this.value; - }, - eval: function () { return this }, + eval: function () { return this; }, compare: function (x) { if (!x.toCSS) { return -1; @@ -22,7 +19,11 @@ tree.Anonymous.prototype = { } return left < right ? -1 : 1; - } + }, + genCSS: function (env, output) { + output.add(this.value); + }, + toCSS: tree.toCSS }; })(require('../tree')); diff --git a/lib/less/tree/assignment.js b/lib/less/tree/assignment.js index b0e2c555..c04fda03 100644 --- a/lib/less/tree/assignment.js +++ b/lib/less/tree/assignment.js @@ -9,15 +9,21 @@ tree.Assignment.prototype = { accept: function (visitor) { this.value = visitor.visit(this.value); }, - toCSS: function () { - return this.key + '=' + (this.value.toCSS ? this.value.toCSS() : this.value); - }, eval: function (env) { if (this.value.eval) { return new(tree.Assignment)(this.key, this.value.eval(env)); } return this; - } + }, + genCSS: function (env, output) { + output.add(this.key + '='); + if (this.value.genCSS) { + this.value.genCSS(env, output); + } else { + output.add(this.value); + } + }, + toCSS: tree.toCSS }; })(require('../tree')); \ No newline at end of file diff --git a/lib/less/tree/call.js b/lib/less/tree/call.js index 20a1fc0e..a90aa203 100644 --- a/lib/less/tree/call.js +++ b/lib/less/tree/call.js @@ -46,15 +46,24 @@ tree.Call.prototype = { index: this.index, filename: this.currentFileInfo.filename }; } } - - // 2. - return new(tree.Anonymous)(this.name + - "(" + args.map(function (a) { return a.toCSS(env); }).join(', ') + ")"); + + return new tree.Call(this.name, args, this.index, this.currentFileInfo); }, - toCSS: function (env) { - return this.eval(env).toCSS(); - } + genCSS: function (env, output) { + output.add(this.name + "("); + + for(var i = 0; i < this.args.length; i++) { + this.args[i].genCSS(env, output); + if (i + 1 < this.args.length) { + output.add(", "); + } + } + + output.add(")"); + }, + + toCSS: tree.toCSS }; })(require('../tree')); diff --git a/lib/less/tree/color.js b/lib/less/tree/color.js index 95d5a101..9e2c7c57 100644 --- a/lib/less/tree/color.js +++ b/lib/less/tree/color.js @@ -27,14 +27,16 @@ tree.Color.prototype = { eval: function () { return this }, luma: function () { return (0.2126 * this.rgb[0] / 255) + (0.7152 * this.rgb[1] / 255) + (0.0722 * this.rgb[2] / 255); }, - // - // If we have some transparency, the only way to represent it - // is via `rgba`. Otherwise, we use the hex representation, - // which has better compatibility with older browsers. - // Values are capped between `0` and `255`, rounded and zero-padded. - // + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function (env, doNotCompress) { var compress = env && env.compress && !doNotCompress; + + // If we have some transparency, the only way to represent it + // is via `rgba`. Otherwise, we use the hex representation, + // which has better compatibility with older browsers. + // Values are capped between `0` and `255`, rounded and zero-padded. if (this.alpha < 1.0) { return "rgba(" + this.rgb.map(function (c) { return Math.round(c); @@ -50,7 +52,7 @@ tree.Color.prototype = { color = color.split(''); // Convert color to short format - if (color[0] == color[1] && color[2] == color[3] && color[4] == color[5]) { + if (color[0] === color[1] && color[2] === color[3] && color[4] === color[5]) { color = color[0] + color[2] + color[4]; } else { color = color.join(''); diff --git a/lib/less/tree/comment.js b/lib/less/tree/comment.js index b9724633..29aece4d 100644 --- a/lib/less/tree/comment.js +++ b/lib/less/tree/comment.js @@ -7,13 +7,13 @@ tree.Comment = function (value, silent, index, currentFileInfo) { }; tree.Comment.prototype = { type: "Comment", - toCSS: function (env) { - var debugInfo = ""; + genCSS: function (env, output) { if (this.debugInfo) { - debugInfo = tree.debugInfo(env, this); + output.add(tree.debugInfo(env, this)); } - return debugInfo + this.value; + output.add(this.value); }, + toCSS: tree.toCSS, isSilent: function(env) { var isReference = (this.currentFileInfo && this.currentFileInfo.reference && !this.isReferenced), isCompressed = env.compress && !this.value.match(/^\/\*!/); diff --git a/lib/less/tree/dimension.js b/lib/less/tree/dimension.js index 9850e6ff..371d5bb3 100644 --- a/lib/less/tree/dimension.js +++ b/lib/less/tree/dimension.js @@ -20,6 +20,9 @@ tree.Dimension.prototype = { toColor: function () { return new(tree.Color)([this.value, this.value, this.value]); }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function (env) { if ((env && env.strictUnits) && !this.unit.isSingular()) { throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: "+this.unit.toString()); @@ -104,202 +107,207 @@ tree.Dimension.prototype = { }, unify: function () { - return this.convertTo({ length: 'm', duration: 's', angle: 'rad' }); + return this.convertTo({ length: 'm', duration: 's', angle: 'rad' }); }, convertTo: function (conversions) { - var value = this.value, unit = this.unit.clone(), - i, groupName, group, conversion, targetUnit, derivedConversions = {}; + var value = this.value, unit = this.unit.clone(), + i, groupName, group, conversion, targetUnit, derivedConversions = {}, applyUnit; - if (typeof conversions === 'string') { - for(i in tree.UnitConversions) { - if (tree.UnitConversions[i].hasOwnProperty(conversions)) { - derivedConversions = {}; - derivedConversions[i] = conversions; - } - } - conversions = derivedConversions; - } - - for (groupName in conversions) { - if (conversions.hasOwnProperty(groupName)) { - targetUnit = conversions[groupName]; - group = tree.UnitConversions[groupName]; - - unit.map(function (atomicUnit, denominator) { + if (typeof conversions === 'string') { + for(i in tree.UnitConversions) { + if (tree.UnitConversions[i].hasOwnProperty(conversions)) { + derivedConversions = {}; + derivedConversions[i] = conversions; + } + } + conversions = derivedConversions; + } + applyUnit = function (atomicUnit, denominator) { if (group.hasOwnProperty(atomicUnit)) { - if (denominator) { - value = value / (group[atomicUnit] / group[targetUnit]); - } else { - value = value * (group[atomicUnit] / group[targetUnit]); - } + if (denominator) { + value = value / (group[atomicUnit] / group[targetUnit]); + } else { + value = value * (group[atomicUnit] / group[targetUnit]); + } - return targetUnit; + return targetUnit; } return atomicUnit; - }); + }; + + for (groupName in conversions) { + if (conversions.hasOwnProperty(groupName)) { + targetUnit = conversions[groupName]; + group = tree.UnitConversions[groupName]; + + unit.map(applyUnit); + } } - } - unit.cancel(); + unit.cancel(); - return new(tree.Dimension)(value, unit); + return new(tree.Dimension)(value, unit); } }; // http://www.w3.org/TR/css3-values/#absolute-lengths tree.UnitConversions = { - length: { - 'm': 1, - 'cm': 0.01, - 'mm': 0.001, - 'in': 0.0254, - 'pt': 0.0254 / 72, - 'pc': 0.0254 / 72 * 12 - }, - duration: { - 's': 1, - 'ms': 0.001 - }, - angle: { - 'rad': 1/(2*Math.PI), - 'deg': 1/360, - 'grad': 1/400, - 'turn': 1 - } + length: { + 'm': 1, + 'cm': 0.01, + 'mm': 0.001, + 'in': 0.0254, + 'pt': 0.0254 / 72, + 'pc': 0.0254 / 72 * 12 + }, + duration: { + 's': 1, + 'ms': 0.001 + }, + angle: { + 'rad': 1/(2*Math.PI), + 'deg': 1/360, + 'grad': 1/400, + 'turn': 1 + } }; tree.Unit = function (numerator, denominator, backupUnit) { - this.numerator = numerator ? numerator.slice(0).sort() : []; - this.denominator = denominator ? denominator.slice(0).sort() : []; - this.backupUnit = backupUnit; + this.numerator = numerator ? numerator.slice(0).sort() : []; + this.denominator = denominator ? denominator.slice(0).sort() : []; + this.backupUnit = backupUnit; }; tree.Unit.prototype = { - type: "Unit", - clone: function () { - return new tree.Unit(this.numerator.slice(0), this.denominator.slice(0), this.backupUnit); - }, + type: "Unit", + clone: function () { + return new tree.Unit(this.numerator.slice(0), this.denominator.slice(0), this.backupUnit); + }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, + toCSS: function (env) { + if (this.numerator.length >= 1) { + return this.numerator[0]; + } + if (this.denominator.length >= 1) { + return this.denominator[0]; + } + if ((!env || !env.strictUnits) && this.backupUnit) { + return this.backupUnit; + } + return ""; + }, - toCSS: function (env) { - if (this.numerator.length >= 1) { - return this.numerator[0]; - } - if (this.denominator.length >= 1) { - return this.denominator[0]; - } - if ((!env || !env.strictUnits) && this.backupUnit) { - return this.backupUnit; - } - return ""; - }, - - toString: function () { + toString: function () { var i, returnStr = this.numerator.join("*"); for (i = 0; i < this.denominator.length; i++) { returnStr += "/" + this.denominator[i]; } return returnStr; - }, - - compare: function (other) { - return this.is(other.toString()) ? 0 : -1; - }, + }, - is: function (unitString) { - return this.toString() === unitString; - }, + compare: function (other) { + return this.is(other.toString()) ? 0 : -1; + }, - isAngle: function () { - return tree.UnitConversions.angle.hasOwnProperty(this.toCSS()); - }, + is: function (unitString) { + return this.toString() === unitString; + }, - isEmpty: function () { - return this.numerator.length == 0 && this.denominator.length == 0; - }, + isAngle: function () { + return tree.UnitConversions.angle.hasOwnProperty(this.toCSS()); + }, - isSingular: function() { - return this.numerator.length <= 1 && this.denominator.length == 0; - }, + isEmpty: function () { + return this.numerator.length == 0 && this.denominator.length == 0; + }, - map: function(callback) { - var i; + isSingular: function() { + return this.numerator.length <= 1 && this.denominator.length == 0; + }, - for (i = 0; i < this.numerator.length; i++) { - this.numerator[i] = callback(this.numerator[i], false); - } + map: function(callback) { + var i; - for (i = 0; i < this.denominator.length; i++) { - this.denominator[i] = callback(this.denominator[i], true); - } - }, - - usedUnits: function() { - var group, groupName, result = {}; - - for (groupName in tree.UnitConversions) { - if (tree.UnitConversions.hasOwnProperty(groupName)) { - group = tree.UnitConversions[groupName]; - - this.map(function (atomicUnit) { - if (group.hasOwnProperty(atomicUnit) && !result[groupName]) { - result[groupName] = atomicUnit; - } - - return atomicUnit; - }); - } - } - - return result; - }, - - cancel: function () { - var counter = {}, atomicUnit, i, backup; - - for (i = 0; i < this.numerator.length; i++) { - atomicUnit = this.numerator[i]; - if (!backup) { - backup = atomicUnit; + for (i = 0; i < this.numerator.length; i++) { + this.numerator[i] = callback(this.numerator[i], false); } - counter[atomicUnit] = (counter[atomicUnit] || 0) + 1; - } - for (i = 0; i < this.denominator.length; i++) { - atomicUnit = this.denominator[i]; - if (!backup) { - backup = atomicUnit; + for (i = 0; i < this.denominator.length; i++) { + this.denominator[i] = callback(this.denominator[i], true); } - counter[atomicUnit] = (counter[atomicUnit] || 0) - 1; - } + }, - this.numerator = []; - this.denominator = []; + usedUnits: function() { + var group, groupName, result = {}, mapUnit; - for (atomicUnit in counter) { - if (counter.hasOwnProperty(atomicUnit)) { - var count = counter[atomicUnit]; + mapUnit = function (atomicUnit) { + if (group.hasOwnProperty(atomicUnit) && !result[groupName]) { + result[groupName] = atomicUnit; + } - if (count > 0) { - for (i = 0; i < count; i++) { - this.numerator.push(atomicUnit); - } - } else if (count < 0) { - for (i = 0; i < -count; i++) { - this.denominator.push(atomicUnit); - } + return atomicUnit; + }; + + for (groupName in tree.UnitConversions) { + if (tree.UnitConversions.hasOwnProperty(groupName)) { + group = tree.UnitConversions[groupName]; + + this.map(mapUnit); + } } - } - } - if (this.numerator.length === 0 && this.denominator.length === 0 && backup) { - this.backupUnit = backup; - } + return result; + }, - this.numerator.sort(); - this.denominator.sort(); - } + cancel: function () { + var counter = {}, atomicUnit, i, backup; + + for (i = 0; i < this.numerator.length; i++) { + atomicUnit = this.numerator[i]; + if (!backup) { + backup = atomicUnit; + } + counter[atomicUnit] = (counter[atomicUnit] || 0) + 1; + } + + for (i = 0; i < this.denominator.length; i++) { + atomicUnit = this.denominator[i]; + if (!backup) { + backup = atomicUnit; + } + counter[atomicUnit] = (counter[atomicUnit] || 0) - 1; + } + + this.numerator = []; + this.denominator = []; + + for (atomicUnit in counter) { + if (counter.hasOwnProperty(atomicUnit)) { + var count = counter[atomicUnit]; + + if (count > 0) { + for (i = 0; i < count; i++) { + this.numerator.push(atomicUnit); + } + } else if (count < 0) { + for (i = 0; i < -count; i++) { + this.denominator.push(atomicUnit); + } + } + } + } + + if (this.numerator.length === 0 && this.denominator.length === 0 && backup) { + this.backupUnit = backup; + } + + this.numerator.sort(); + this.denominator.sort(); + } }; })(require('../tree')); diff --git a/lib/less/tree/directive.js b/lib/less/tree/directive.js index 1e9500bc..53b7771d 100644 --- a/lib/less/tree/directive.js +++ b/lib/less/tree/directive.js @@ -17,17 +17,17 @@ tree.Directive.prototype = { this.rules = visitor.visit(this.rules); this.value = visitor.visit(this.value); }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function (env) { - - if (this.currentFileInfo.reference && !this.isReferenced) { - return ""; - } - if (this.rules) { var css = ""; for(var i = 0; i < this.rules.length; i++) { - //this.rules[i].root = true; - css += this.rules[i].toCSS(env).trim() + "\n"; + css += this.rules[i].toCSS(env);// + "\n"; + if (i + 1 < this.rules.length) { + css += "\n"; + } } css = css.trim().replace(/\n/g, '\n '); return this.name + (env.compress ? '{' : ' {\n ') + css + (env.compress ? '}': '\n}\n'); diff --git a/lib/less/tree/element.js b/lib/less/tree/element.js index 55d47903..f357ca08 100644 --- a/lib/less/tree/element.js +++ b/lib/less/tree/element.js @@ -24,6 +24,9 @@ tree.Element.prototype = { this.value.eval ? this.value.eval(env) : this.value, this.index); }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function (env) { var value = (this.value.toCSS ? this.value.toCSS(env) : this.value); if (value == '' && this.combinator.value.charAt(0) == '&') { @@ -48,6 +51,9 @@ tree.Attribute.prototype = { return new(tree.Attribute)(this.key.eval ? this.key.eval(env) : this.key, this.op, (this.value && this.value.eval) ? this.value.eval(env) : this.value); }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function (env) { var value = this.key.toCSS ? this.key.toCSS(env) : this.key; @@ -69,6 +75,9 @@ tree.Combinator = function (value) { }; tree.Combinator.prototype = { type: "Combinator", + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function (env) { return { '' : '', diff --git a/lib/less/tree/expression.js b/lib/less/tree/expression.js index 790c2b6f..23969297 100644 --- a/lib/less/tree/expression.js +++ b/lib/less/tree/expression.js @@ -33,11 +33,15 @@ tree.Expression.prototype = { } return returnValue; }, - toCSS: function (env) { - return this.value.map(function (e) { - return e.toCSS ? e.toCSS(env) : ''; - }).join(' '); + genCSS: function (env, output) { + for(var i = 0; i < this.value.length; i++) { + this.value[i].genCSS(env, output); + if (i + 1 < this.value.length) { + output.add(" "); + } + } }, + toCSS: tree.toCSS, throwAwayComments: function () { this.value = this.value.filter(function(v) { return !(v instanceof tree.Comment); diff --git a/lib/less/tree/import.js b/lib/less/tree/import.js index f81b09c0..5a9c66a0 100644 --- a/lib/less/tree/import.js +++ b/lib/less/tree/import.js @@ -48,6 +48,9 @@ tree.Import.prototype = { this.root = visitor.visit(this.root); } }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function (env) { var features = this.features ? ' ' + this.features.toCSS(env) : ''; diff --git a/lib/less/tree/keyword.js b/lib/less/tree/keyword.js index 3184fce2..dc654ef3 100644 --- a/lib/less/tree/keyword.js +++ b/lib/less/tree/keyword.js @@ -4,6 +4,9 @@ tree.Keyword = function (value) { this.value = value }; tree.Keyword.prototype = { type: "Keyword", eval: function () { return this; }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function () { return this.value; }, compare: function (other) { if (other instanceof tree.Keyword) { diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index 57d68a2d..6a98e75e 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -16,6 +16,9 @@ tree.Media.prototype = { this.features = visitor.visit(this.features); this.rules = visitor.visit(this.rules); }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function (env) { var features = this.features.toCSS(env); diff --git a/lib/less/tree/negative.js b/lib/less/tree/negative.js index 5971d70b..3342d82e 100644 --- a/lib/less/tree/negative.js +++ b/lib/less/tree/negative.js @@ -8,6 +8,9 @@ tree.Negative.prototype = { accept: function (visitor) { this.value = visitor.visit(this.value); }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function (env) { return '-' + this.value.toCSS(env); }, diff --git a/lib/less/tree/operation.js b/lib/less/tree/operation.js index d9d4af3e..807d8f88 100644 --- a/lib/less/tree/operation.js +++ b/lib/less/tree/operation.js @@ -34,6 +34,9 @@ tree.Operation.prototype = { return new(tree.Operation)(this.op, [a, b], this.isSpaced); } }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function (env) { var separator = this.isSpaced ? " " : ""; return this.operands[0].toCSS() + separator + this.op + separator + this.operands[1].toCSS(); diff --git a/lib/less/tree/paren.js b/lib/less/tree/paren.js index df6fa95c..3bd65d41 100644 --- a/lib/less/tree/paren.js +++ b/lib/less/tree/paren.js @@ -9,6 +9,9 @@ tree.Paren.prototype = { accept: function (visitor) { this.value = visitor.visit(this.value); }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function (env) { return '(' + this.value.toCSS(env).trim() + ')'; }, diff --git a/lib/less/tree/quoted.js b/lib/less/tree/quoted.js index 9ca4b010..188cc2bb 100644 --- a/lib/less/tree/quoted.js +++ b/lib/less/tree/quoted.js @@ -9,6 +9,9 @@ tree.Quoted = function (str, content, escaped, index, currentFileInfo) { }; tree.Quoted.prototype = { type: "Quoted", + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function () { if (this.escaped) { return this.value; diff --git a/lib/less/tree/rule.js b/lib/less/tree/rule.js index 5fab422d..6f0bbd74 100644 --- a/lib/less/tree/rule.js +++ b/lib/less/tree/rule.js @@ -16,6 +16,9 @@ tree.Rule.prototype = { accept: function (visitor) { this.value = visitor.visit(this.value); }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function (env) { if (this.variable) { return ""; } else { diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 9f7f0574..cf5fb9c5 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -174,6 +174,9 @@ tree.Ruleset.prototype = { }); return this._lookups[key] = rules; }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, // // Entry point for code generation // diff --git a/lib/less/tree/selector.js b/lib/less/tree/selector.js index 0fc8582d..1008fb75 100644 --- a/lib/less/tree/selector.js +++ b/lib/less/tree/selector.js @@ -53,6 +53,9 @@ tree.Selector.prototype = { return extend.eval(env); }), evaldCondition); }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function (env) { if (this._css) { return this._css } diff --git a/lib/less/tree/unicode-descriptor.js b/lib/less/tree/unicode-descriptor.js index 3f725127..aa465686 100644 --- a/lib/less/tree/unicode-descriptor.js +++ b/lib/less/tree/unicode-descriptor.js @@ -5,10 +5,13 @@ tree.UnicodeDescriptor = function (value) { }; tree.UnicodeDescriptor.prototype = { type: "UnicodeDescriptor", + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function (env) { return this.value; }, - eval: function () { return this } + eval: function () { return this; } }; })(require('../tree')); diff --git a/lib/less/tree/url.js b/lib/less/tree/url.js index 82ef8d7a..c8b72059 100644 --- a/lib/less/tree/url.js +++ b/lib/less/tree/url.js @@ -9,6 +9,9 @@ tree.URL.prototype = { accept: function (visitor) { this.value = visitor.visit(this.value); }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function () { return "url(" + this.value.toCSS() + ")"; }, diff --git a/lib/less/tree/value.js b/lib/less/tree/value.js index 5aae88eb..2deebb57 100644 --- a/lib/less/tree/value.js +++ b/lib/less/tree/value.js @@ -17,6 +17,9 @@ tree.Value.prototype = { })); } }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, toCSS: function (env) { return this.value.map(function (e) { return e.toCSS(env); diff --git a/package.json b/package.json index 50a5325a..520925e4 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,11 @@ "The Core Less Team" ], "bugs": { - "url": "https://github.com/cloudhead/less.js/issues" + "url": "https://github.com/less/less.js/issues" }, "repository": { "type": "git", - "url": "https://github.com/cloudhead/less.js.git" + "url": "https://github.com/less/less.js.git" }, "bin": { "lessc": "./bin/lessc" @@ -34,7 +34,8 @@ "mime": "1.2.x", "request": ">=2.12.0", "mkdirp": "~0.3.4", - "clean-css": "1.0.x" + "clean-css": "1.0.x", + "source-map": "0.1.x" }, "devDependencies": { "diff": "~1.0" @@ -67,7 +68,7 @@ "licenses": [ { "type": "Apache v2", - "url": "https://github.com/cloudhead/less.js/blob/master/LICENSE" + "url": "https://github.com/less/less.js/blob/master/LICENSE" } ] } From 8529f93b4876acba60a8ca193cb578d0264ac610 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Sat, 13 Jul 2013 15:43:09 +0100 Subject: [PATCH 08/34] start moving the tab indent so that css is not modified once output --- lib/less/to-css-visitor.js | 14 ++++++++++---- lib/less/tree/directive.js | 13 +++++++------ lib/less/tree/media.js | 18 ++++++++++-------- lib/less/tree/rule.js | 3 ++- lib/less/tree/ruleset.js | 39 +++++++++++++++++++++++++++++++------- test/css/media.css | 3 ++- 6 files changed, 63 insertions(+), 27 deletions(-) diff --git a/lib/less/to-css-visitor.js b/lib/less/to-css-visitor.js index c3d67bb4..3b9f4a89 100644 --- a/lib/less/to-css-visitor.js +++ b/lib/less/to-css-visitor.js @@ -92,18 +92,24 @@ } // accept the visitor to remove rules and refactor itself // then we can decide now whether we want it or not - if (rulesetNode.rules.length > 0 && rulesetNode.paths.length > 0) { + if (rulesetNode.rules.length > 0) { rulesetNode.accept(this._visitor); } visitArgs.visitDeeper = false; // now decide whether we keep the ruleset - if (rulesets.length > 0 && rulesetNode.rules.length > 0 && rulesetNode.paths.length > 0) { + if (rulesetNode.rules.length > 0 && rulesetNode.paths.length > 0) { + rulesets.splice(0, 0, rulesetNode); + } + } else { + rulesetNode.accept(this._visitor); + visitArgs.visitDeeper = false; + if (rulesetNode.firstRoot || rulesetNode.rules.length > 0) { rulesets.splice(0, 0, rulesetNode); } } - if (rulesets.length === 0) { - rulesets = rulesetNode; + if (rulesets.length === 1) { + return rulesets[0]; } return rulesets; } diff --git a/lib/less/tree/directive.js b/lib/less/tree/directive.js index 53b7771d..8ccb1ae4 100644 --- a/lib/less/tree/directive.js +++ b/lib/less/tree/directive.js @@ -22,15 +22,16 @@ tree.Directive.prototype = { }, toCSS: function (env) { if (this.rules) { + //todo put in a common place, also done in media.js + env.tabLevel = (env.tabLevel || 0) + 1; + var tabRuleStr = env.compress ? '' : Array(env.tabLevel + 1).join(" "), + tabSetStr = env.compress ? '' : Array(env.tabLevel).join(" "); var css = ""; for(var i = 0; i < this.rules.length; i++) { - css += this.rules[i].toCSS(env);// + "\n"; - if (i + 1 < this.rules.length) { - css += "\n"; - } + css += tabRuleStr + this.rules[i].toCSS(env) + (env.compress ? "" : "\n"); } - css = css.trim().replace(/\n/g, '\n '); - return this.name + (env.compress ? '{' : ' {\n ') + css + (env.compress ? '}': '\n}\n'); + env.tabLevel--; + return this.name + (env.compress ? '{' : ' {\n') + css + (env.compress ? '}': (tabSetStr + '}\n')); } else { return this.name + ' ' + this.value.toCSS() + ';\n'; } diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index 6a98e75e..1eadce1a 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -22,17 +22,19 @@ tree.Media.prototype = { toCSS: function (env) { var features = this.features.toCSS(env); - var content = ""; - + //todo put in a common place, also done in directive.js + env.tabLevel = (env.tabLevel || 0) + 1; + var tabRuleStr = env.compress ? '' : Array(env.tabLevel + 1).join(" "), + tabSetStr = env.compress ? '' : Array(env.tabLevel).join(" "); + var css = ""; for(var i = 0; i < this.rules.length; i++) { - content += this.rules[i].toCSS(env).trim() + "\n"; + css += tabRuleStr + this.rules[i].toCSS(env) + (env.compress ? '\n' : ''); } + env.tabLevel--; - content = content.trim().replace(/\n/g, '\n '); - - if (content.match(/\S/)) { - return '@media ' + features + (env.compress ? '{' : ' {\n ') + content + - (env.compress ? '}': '\n}\n'); + if (css.match(/\S/)) { + return '@media ' + features + (env.compress ? '{' : ' {\n') + css + + (env.compress ? '}': '}\n'); } else { return ""; } diff --git a/lib/less/tree/rule.js b/lib/less/tree/rule.js index 6f0bbd74..6b75606d 100644 --- a/lib/less/tree/rule.js +++ b/lib/less/tree/rule.js @@ -23,9 +23,10 @@ tree.Rule.prototype = { if (this.variable) { return ""; } else { try { - return this.name + (env.compress ? ':' : ': ') + + var css = this.name + (env.compress ? ':' : ': ') + this.value.toCSS(env) + this.important + (this.inline ? "" : ";"); + return css; } catch(e) { e.index = this.index; diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index cf5fb9c5..6a5e2c6f 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -193,6 +193,12 @@ tree.Ruleset.prototype = { this.mergeRules(); + env.tabLevel = (env.tabLevel || 0); + + if (!this.root) { + env.tabLevel++; + } + // Compile rules and rulesets for (var i = 0; i < this.rules.length; i++) { rule = this.rules[i]; @@ -222,13 +228,18 @@ tree.Ruleset.prototype = { } } - rulesets = rulesets.join(''); + var tabRuleStr = env.compress ? '' : Array(env.tabLevel + 1).join(" "), + tabSetStr = env.compress ? '' : Array(env.tabLevel).join(" "); + + rulesets = rulesets.join(this.root ? tabRuleStr : tabSetStr); // If this is the root node, we don't render // a selector, or {}. // Otherwise, only output if this ruleset has rules. if (this.root) { - css.push(rules.join(env.compress ? '' : '\n')); + if (rules.length > 0) { + css.push(rules.join(env.compress ? '' : ('\n' + tabRuleStr))); + } } else { if (rules.length > 0) { debugInfo = tree.debugInfo(env, this); @@ -237,7 +248,7 @@ tree.Ruleset.prototype = { return p.map(function (s) { return s.toCSS(env); }).join('').trim(); - }).join(env.compress ? ',' : ',\n'); + }).join(env.compress ? ',' : (',\n' + tabSetStr)); if (selector) { // Remove duplicates @@ -248,13 +259,27 @@ tree.Ruleset.prototype = { } rules = _rules; - css.push(debugInfo + selector + - (env.compress ? '{' : ' {\n ') + - rules.join(env.compress ? '' : '\n ') + - (env.compress ? '}' : '\n}\n')); + if (debugInfo) { + css.push(debugInfo); + css.push(tabSetStr); + } + + css.push(selector + + (env.compress ? '{' : ' {\n') + tabRuleStr + + rules.join(env.compress ? '' : ('\n' + tabRuleStr)) + + (env.compress ? '}' : '\n' + tabSetStr + '}\n')); } } } + + if (!this.root) { + env.tabLevel--; + } + + if (rules.length && rulesets.length) { + css.push("\n" + (this.root ? tabRuleStr : tabSetStr)); + } + css.push(rulesets); return css.join('') + (env.compress ? '\n' : ''); diff --git a/test/css/media.css b/test/css/media.css index aeba6e77..d90c1e8d 100644 --- a/test/css/media.css +++ b/test/css/media.css @@ -118,7 +118,8 @@ margin: 1cm; } @page :first { - size: 8.5in 11in;@top-left { + size: 8.5in 11in; + @top-left { margin: 1cm; } @top-left-corner { From a554b8e0887d8a6be4e83c0254f568c2dbc9255c Mon Sep 17 00:00:00 2001 From: Luke Page Date: Sun, 14 Jul 2013 23:04:09 +0100 Subject: [PATCH 09/34] get closer moving tab control away replacing text after converting to css --- lib/less/tree/comment.js | 2 +- lib/less/tree/directive.js | 4 ++-- lib/less/tree/media.js | 4 ++-- lib/less/tree/ruleset.js | 16 ++++++++++------ test/less-test.js | 3 ++- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/less/tree/comment.js b/lib/less/tree/comment.js index 29aece4d..8998b961 100644 --- a/lib/less/tree/comment.js +++ b/lib/less/tree/comment.js @@ -11,7 +11,7 @@ tree.Comment.prototype = { if (this.debugInfo) { output.add(tree.debugInfo(env, this)); } - output.add(this.value); + output.add(this.value.trim()); //TODO shouldn't need to trim, we shouldn't grab the \n }, toCSS: tree.toCSS, isSilent: function(env) { diff --git a/lib/less/tree/directive.js b/lib/less/tree/directive.js index 8ccb1ae4..e66a21ad 100644 --- a/lib/less/tree/directive.js +++ b/lib/less/tree/directive.js @@ -31,9 +31,9 @@ tree.Directive.prototype = { css += tabRuleStr + this.rules[i].toCSS(env) + (env.compress ? "" : "\n"); } env.tabLevel--; - return this.name + (env.compress ? '{' : ' {\n') + css + (env.compress ? '}': (tabSetStr + '}\n')); + return this.name + (env.compress ? '{' : ' {\n') + css + (env.compress ? '}': (tabSetStr + '}')); } else { - return this.name + ' ' + this.value.toCSS() + ';\n'; + return this.name + ' ' + this.value.toCSS() + ';'; } }, eval: function (env) { diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index 1eadce1a..1dd06f17 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -28,13 +28,13 @@ tree.Media.prototype = { tabSetStr = env.compress ? '' : Array(env.tabLevel).join(" "); var css = ""; for(var i = 0; i < this.rules.length; i++) { - css += tabRuleStr + this.rules[i].toCSS(env) + (env.compress ? '\n' : ''); + css += tabRuleStr + this.rules[i].toCSS(env) + (env.compress ? '' : '\n'); } env.tabLevel--; if (css.match(/\S/)) { return '@media ' + features + (env.compress ? '{' : ' {\n') + css + - (env.compress ? '}': '}\n'); + '}'; } else { return ""; } diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 6a5e2c6f..813791cb 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -189,7 +189,8 @@ tree.Ruleset.prototype = { rulesets = [], // node.Ruleset instances selector, // The fully rendered selector debugInfo, // Line number debugging - rule; + rule, + ruleCSS; this.mergeRules(); @@ -218,7 +219,10 @@ tree.Ruleset.prototype = { rules.push(rule.value.toString()); } } - } + } + + rules = rules.filter(function(rule) { return !!rule; }); + rulesets = rulesets.filter(function(rule) { return !!rule; }); // Remove last semicolon if (env.compress && rules.length) { @@ -231,14 +235,14 @@ tree.Ruleset.prototype = { var tabRuleStr = env.compress ? '' : Array(env.tabLevel + 1).join(" "), tabSetStr = env.compress ? '' : Array(env.tabLevel).join(" "); - rulesets = rulesets.join(this.root ? tabRuleStr : tabSetStr); + rulesets = rulesets.join('\n' + (this.root ? tabRuleStr : tabSetStr)); // If this is the root node, we don't render // a selector, or {}. // Otherwise, only output if this ruleset has rules. if (this.root) { if (rules.length > 0) { - css.push(rules.join(env.compress ? '' : ('\n' + tabRuleStr))); + css.push(rules.join(env.compress ? '' : ('\n' + tabRuleStr))/* + '\n'*/); } } else { if (rules.length > 0) { @@ -267,7 +271,7 @@ tree.Ruleset.prototype = { css.push(selector + (env.compress ? '{' : ' {\n') + tabRuleStr + rules.join(env.compress ? '' : ('\n' + tabRuleStr)) + - (env.compress ? '}' : '\n' + tabSetStr + '}\n')); + (env.compress ? '}' : '\n' + tabSetStr + '}')); } } } @@ -282,7 +286,7 @@ tree.Ruleset.prototype = { css.push(rulesets); - return css.join('') + (env.compress ? '\n' : ''); + return css.join('') + (!env.compress && this.firstRoot ? '\n' : ''); }, toCSSRoot: function (env) { diff --git a/test/less-test.js b/test/less-test.js index 50b868ca..a8cfa401 100644 --- a/test/less-test.js +++ b/test/less-test.js @@ -136,7 +136,8 @@ function diff(left, right) { sys.puts(""); require('diff').diffLines(left, right).forEach(function(item) { if(item.added || item.removed) { - sys.print(stylize(item.value, item.added ? 'green' : 'red')); + var text = item.value.replace("\n", String.fromCharCode(182) + "\n");; + sys.print(stylize(text, item.added ? 'green' : 'red')); } else { sys.print(item.value); } From 037cdb5916fc884041aa172bbf4b202bb2453b30 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Mon, 15 Jul 2013 22:03:52 +0100 Subject: [PATCH 10/34] fix tests --- lib/less/tree.js | 4 ++-- lib/less/tree/import.js | 2 +- lib/less/tree/ruleset.js | 2 +- test/browser/css/rootpath-relative/urls.css | 1 - test/browser/css/urls.css | 1 - test/css/comments.css | 8 ++++++-- test/css/compression/compression.css | 3 +-- test/css/import-inline.css | 4 +++- test/css/import.css | 2 -- test/css/static-urls/urls.css | 1 - test/css/urls.css | 2 -- test/less/import/invalid-css.less | 2 +- 12 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/less/tree.js b/lib/less/tree.js index 290e8aaa..423837ae 100644 --- a/lib/less/tree.js +++ b/lib/less/tree.js @@ -1,6 +1,6 @@ (function (tree) { -tree.debugInfo = function(env, ctx) { +tree.debugInfo = function(env, ctx, lineSeperator) { var result=""; if (env.dumpLineNumbers && !env.compress) { switch(env.dumpLineNumbers) { @@ -11,7 +11,7 @@ tree.debugInfo = function(env, ctx) { result = tree.debugInfo.asMediaQuery(ctx); break; case 'all': - result = tree.debugInfo.asComment(ctx)+tree.debugInfo.asMediaQuery(ctx); + result = tree.debugInfo.asComment(ctx) + (lineSeperator || "") + tree.debugInfo.asMediaQuery(ctx); break; } } diff --git a/lib/less/tree/import.js b/lib/less/tree/import.js index 5a9c66a0..7ad06ee8 100644 --- a/lib/less/tree/import.js +++ b/lib/less/tree/import.js @@ -55,7 +55,7 @@ tree.Import.prototype = { var features = this.features ? ' ' + this.features.toCSS(env) : ''; if (this.css) { - return "@import " + this.path.toCSS() + features + ';\n'; + return "@import " + this.path.toCSS() + features + ';'; } else { return ""; } diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 813791cb..2842719e 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -246,7 +246,7 @@ tree.Ruleset.prototype = { } } else { if (rules.length > 0) { - debugInfo = tree.debugInfo(env, this); + debugInfo = tree.debugInfo(env, this, tabSetStr); selector = this.paths .map(function (p) { return p.map(function (s) { diff --git a/test/browser/css/rootpath-relative/urls.css b/test/browser/css/rootpath-relative/urls.css index 817d5818..20b08339 100644 --- a/test/browser/css/rootpath-relative/urls.css +++ b/test/browser/css/rootpath-relative/urls.css @@ -1,5 +1,4 @@ @import "https://www.github.com/cloudhead/imports/modify-this.css"; - @import "https://www.github.com/cloudhead/imports/modify-again.css"; .modify { my-url: url("https://www.github.com/cloudhead/imports/a.png"); diff --git a/test/browser/css/urls.css b/test/browser/css/urls.css index ddb46b15..7001990e 100644 --- a/test/browser/css/urls.css +++ b/test/browser/css/urls.css @@ -1,5 +1,4 @@ @import "http://localhost:8081/browser/less/modify-this.css"; - @import "http://localhost:8081/browser/less/modify-again.css"; .modify { my-url: url("http://localhost:8081/browser/less/a.png"); diff --git a/test/css/comments.css b/test/css/comments.css index 2306c6a8..b85f5b4f 100644 --- a/test/css/comments.css +++ b/test/css/comments.css @@ -32,7 +32,6 @@ color: red; /* A C-style comment */ /* A C-style comment */ - background-color: orange; font-size: 12px; /* lost comment */ @@ -59,7 +58,12 @@ #last { color: #0000ff; } -/* *//* { *//* *//* *//* */#div { +/* */ +/* { */ +/* */ +/* */ +/* */ +#div { color: #A33; } /* } */ diff --git a/test/css/compression/compression.css b/test/css/compression/compression.css index 3fdd6a21..e3582bf3 100644 --- a/test/css/compression/compression.css +++ b/test/css/compression/compression.css @@ -1,5 +1,4 @@ #colours{color1:#fea;color2:#fea;color3:rgba(255,238,170,0.1);string:"#ffeeaa";/*! but not this type Note preserved whitespace - */ -} + */} dimensions{val:.1px;val:0;val:4cm;val:.2;val:5;angles-must-have-unit:0deg;width:auto\9} \ No newline at end of file diff --git a/test/css/import-inline.css b/test/css/import-inline.css index 1c17ab2a..f198d3c1 100644 --- a/test/css/import-inline.css +++ b/test/css/import-inline.css @@ -1,3 +1,5 @@ -this isn't very valid CSS. @media (min-width: 600px) { +this isn't very valid CSS. +@media (min-width: 600px) { #css { color: yellow; } + } diff --git a/test/css/import.css b/test/css/import.css index 616657ac..a3749181 100644 --- a/test/css/import.css +++ b/test/css/import.css @@ -1,7 +1,5 @@ @import url(http://fonts.googleapis.com/css?family=Open+Sans); - @import url(/absolute/something.css) screen and (color) and (max-width: 600px); - @import url("//ha.com/file.css") (min-width: 100px); #import-test { height: 10px; diff --git a/test/css/static-urls/urls.css b/test/css/static-urls/urls.css index b5a690e9..565ccb44 100644 --- a/test/css/static-urls/urls.css +++ b/test/css/static-urls/urls.css @@ -1,5 +1,4 @@ @import "folder (1)/../css/background.css"; - @import "folder (1)/import-test-d.css"; @font-face { src: url("/fonts/garamond-pro.ttf"); diff --git a/test/css/urls.css b/test/css/urls.css index 21b33bca..ef812605 100644 --- a/test/css/urls.css +++ b/test/css/urls.css @@ -1,7 +1,5 @@ @import "import/../css/background.css"; - @import "import/import-test-d.css"; - @import "file.css"; @font-face { src: url("/fonts/garamond-pro.ttf"); diff --git a/test/less/import/invalid-css.less b/test/less/import/invalid-css.less index b7cad97b..ed585d63 100644 --- a/test/less/import/invalid-css.less +++ b/test/less/import/invalid-css.less @@ -1 +1 @@ -this isn't very valid CSS. \ No newline at end of file +this isn't very valid CSS. \ No newline at end of file From 2a0df97291476f14d01cd52d3a5b815b3a42166c Mon Sep 17 00:00:00 2001 From: Luke Page Date: Mon, 15 Jul 2013 23:05:27 +0100 Subject: [PATCH 11/34] move rule/ruleset re-ordering away from toCSS --- lib/less/tree/ruleset.js | 51 ++++++++++++++++++++++---------------- test/css/import-inline.css | 2 +- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 2842719e..3238f97a 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -183,14 +183,16 @@ tree.Ruleset.prototype = { // `context` holds an array of arrays. // toCSS: function (env) { - var css = [], // The CSS output + var i, css = [], // The CSS output rules = [], // node.Rule instances _rules = [], // rulesets = [], // node.Ruleset instances + ruleNodes = [], + rulesetNodes = [], selector, // The fully rendered selector debugInfo, // Line number debugging rule, - ruleCSS; + importNodes = []; this.mergeRules(); @@ -200,24 +202,34 @@ tree.Ruleset.prototype = { env.tabLevel++; } - // Compile rules and rulesets - for (var i = 0; i < this.rules.length; i++) { - rule = this.rules[i]; + var tabRuleStr = env.compress ? '' : Array(env.tabLevel + 1).join(" "), + tabSetStr = env.compress ? '' : Array(env.tabLevel).join(" "); - if (rule.rules || (rule instanceof tree.Media) || rule instanceof tree.Directive) { - rulesets.push(rule.toCSS(env)); - } else if (rule instanceof tree.Comment) { - if (this.root) { - rulesets.push(rule.toCSS(env)); - } else { - rules.push(rule.toCSS(env)); - } + for (i = 0; i < this.rules.length; i++) { + rule = this.rules[i]; + if (rule instanceof tree.Import) { + importNodes.push(rule); + } else if (this.root || rule.rules || (rule instanceof tree.Media) || rule instanceof tree.Directive) { + rulesetNodes.push(rule); } else { - if (rule.toCSS) { - rules.push(rule.toCSS(env)); - } else if (rule.value) { - rules.push(rule.value.toString()); - } + ruleNodes.push(rule); + } + } + + rulesetNodes = importNodes.concat(rulesetNodes); + + for (i = 0; i < rulesetNodes.length; i++) { + rulesets.push(rulesetNodes[i].toCSS(env)); + } + + // Compile rules and rulesets + for (i = 0; i < ruleNodes.length; i++) { + rule = ruleNodes[i]; + + if (rule.toCSS) { + rules.push(rule.toCSS(env)); + } else if (rule.value) { + rules.push(rule.value.toString()); } } @@ -232,9 +244,6 @@ tree.Ruleset.prototype = { } } - var tabRuleStr = env.compress ? '' : Array(env.tabLevel + 1).join(" "), - tabSetStr = env.compress ? '' : Array(env.tabLevel).join(" "); - rulesets = rulesets.join('\n' + (this.root ? tabRuleStr : tabSetStr)); // If this is the root node, we don't render diff --git a/test/css/import-inline.css b/test/css/import-inline.css index f198d3c1..f28e19c6 100644 --- a/test/css/import-inline.css +++ b/test/css/import-inline.css @@ -1,5 +1,5 @@ -this isn't very valid CSS. @media (min-width: 600px) { #css { color: yellow; } } +this isn't very valid CSS. \ No newline at end of file From f032f20206bcce1b9ef7c360a21ef5fe0f92fd33 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 16 Jul 2013 20:24:44 +0100 Subject: [PATCH 12/34] Fix error in previous commit --- lib/less/tree/ruleset.js | 9 ++------- test/css/import-inline.css | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 3238f97a..bdd4ed16 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -191,8 +191,7 @@ tree.Ruleset.prototype = { rulesetNodes = [], selector, // The fully rendered selector debugInfo, // Line number debugging - rule, - importNodes = []; + rule; this.mergeRules(); @@ -207,17 +206,13 @@ tree.Ruleset.prototype = { for (i = 0; i < this.rules.length; i++) { rule = this.rules[i]; - if (rule instanceof tree.Import) { - importNodes.push(rule); - } else if (this.root || rule.rules || (rule instanceof tree.Media) || rule instanceof tree.Directive) { + if (rule.rules || (rule instanceof tree.Media) || rule instanceof tree.Directive || (this.root && rule instanceof tree.Comment)) { rulesetNodes.push(rule); } else { ruleNodes.push(rule); } } - rulesetNodes = importNodes.concat(rulesetNodes); - for (i = 0; i < rulesetNodes.length; i++) { rulesets.push(rulesetNodes[i].toCSS(env)); } diff --git a/test/css/import-inline.css b/test/css/import-inline.css index f28e19c6..f198d3c1 100644 --- a/test/css/import-inline.css +++ b/test/css/import-inline.css @@ -1,5 +1,5 @@ +this isn't very valid CSS. @media (min-width: 600px) { #css { color: yellow; } } -this isn't very valid CSS. \ No newline at end of file From 967543cf08724ee611d40988066c100578b6da2a Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 16 Jul 2013 20:56:02 +0100 Subject: [PATCH 13/34] housekeeping ready for next part of refactoring --- lib/less/tree/ruleset.js | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index bdd4ed16..2008285d 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -183,7 +183,7 @@ tree.Ruleset.prototype = { // `context` holds an array of arrays. // toCSS: function (env) { - var i, css = [], // The CSS output + var i, css = [], // The CSS output rules = [], // node.Rule instances _rules = [], // rulesets = [], // node.Ruleset instances @@ -213,10 +213,6 @@ tree.Ruleset.prototype = { } } - for (i = 0; i < rulesetNodes.length; i++) { - rulesets.push(rulesetNodes[i].toCSS(env)); - } - // Compile rules and rulesets for (i = 0; i < ruleNodes.length; i++) { rule = ruleNodes[i]; @@ -228,9 +224,10 @@ tree.Ruleset.prototype = { } } + // TODO - remove in toCSS visitor rules = rules.filter(function(rule) { return !!rule; }); - rulesets = rulesets.filter(function(rule) { return !!rule; }); + //TODO move to env and rule.js // Remove last semicolon if (env.compress && rules.length) { rule = rules[rules.length - 1]; @@ -239,8 +236,6 @@ tree.Ruleset.prototype = { } } - rulesets = rulesets.join('\n' + (this.root ? tabRuleStr : tabSetStr)); - // If this is the root node, we don't render // a selector, or {}. // Otherwise, only output if this ruleset has rules. @@ -259,6 +254,8 @@ tree.Ruleset.prototype = { }).join(env.compress ? ',' : (',\n' + tabSetStr)); if (selector) { + //TODO need to do this in the toCSS visitor + //only bother doing if compression is on? // Remove duplicates for (var i = rules.length - 1; i >= 0; i--) { if (rules[i].slice(0, 2) === "/*" || _rules.indexOf(rules[i]) === -1) { @@ -284,11 +281,23 @@ tree.Ruleset.prototype = { env.tabLevel--; } - if (rules.length && rulesets.length) { - css.push("\n" + (this.root ? tabRuleStr : tabSetStr)); + var firstRuleset = true; + for (i = 0; i < rulesetNodes.length; i++) { + var rulesetCSS = rulesetNodes[i].toCSS(env); + //TODO need to remove empty rulesets in the toCSS visitor + if (rulesetCSS) { + if (rules.length && firstRuleset) { + rulesets.push("\n" + (this.root ? tabRuleStr : tabSetStr)); + } + if (!firstRuleset) { + rulesets.push('\n' + (this.root ? tabRuleStr : tabSetStr)); + } + firstRuleset = false; + rulesets.push(rulesetCSS); + } } - css.push(rulesets); + css = css.concat(rulesets); return css.join('') + (!env.compress && this.firstRoot ? '\n' : ''); }, From 190bcca19fdaf539cb2370879199ce3f372c2baf Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 16 Jul 2013 21:41:38 +0100 Subject: [PATCH 14/34] Move more toCSS logic into the toCSS visitor --- lib/less/to-css-visitor.js | 14 ++++++ lib/less/tree/media.js | 2 +- lib/less/tree/rule.js | 2 +- lib/less/tree/ruleset.js | 93 ++++++++++++++++---------------------- 4 files changed, 54 insertions(+), 57 deletions(-) diff --git a/lib/less/to-css-visitor.js b/lib/less/to-css-visitor.js index 3b9f4a89..868cb0f4 100644 --- a/lib/less/to-css-visitor.js +++ b/lib/less/to-css-visitor.js @@ -21,6 +21,10 @@ return []; }, + visitExtend: function (extendNode, visitArgs) { + return []; + }, + visitComment: function (commentNode, visitArgs) { if (commentNode.isSilent(this._env)) { return []; @@ -28,6 +32,16 @@ return commentNode; }, + visitMedia: function(mediaNode, visitArgs) { + mediaNode.accept(this._visitor); + visitArgs.visitDeeper = false; + + if (!mediaNode.rules.length) { + return []; + } + return mediaNode; + }, + visitDirective: function(directiveNode, visitArgs) { if (directiveNode.currentFileInfo.reference && !directiveNode.isReferenced) { return []; diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index 1dd06f17..8e14c1ef 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -32,7 +32,7 @@ tree.Media.prototype = { } env.tabLevel--; - if (css.match(/\S/)) { + if (this.rules.length) { //css.match(/\S/)) { return '@media ' + features + (env.compress ? '{' : ' {\n') + css + '}'; } else { diff --git a/lib/less/tree/rule.js b/lib/less/tree/rule.js index 6b75606d..cafe6d08 100644 --- a/lib/less/tree/rule.js +++ b/lib/less/tree/rule.js @@ -25,7 +25,7 @@ tree.Rule.prototype = { try { var css = this.name + (env.compress ? ':' : ': ') + this.value.toCSS(env) + - this.important + (this.inline ? "" : ";"); + this.important + ((this.inline || (env.lastRule && env.compress)) ? "" : ";"); return css; } catch(e) { diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 2008285d..92f5540c 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -217,63 +217,53 @@ tree.Ruleset.prototype = { for (i = 0; i < ruleNodes.length; i++) { rule = ruleNodes[i]; + if (i + 1 === ruleNodes.length) { + env.lastRule = true; + } + if (rule.toCSS) { rules.push(rule.toCSS(env)); } else if (rule.value) { rules.push(rule.value.toString()); } - } - // TODO - remove in toCSS visitor - rules = rules.filter(function(rule) { return !!rule; }); - - //TODO move to env and rule.js - // Remove last semicolon - if (env.compress && rules.length) { - rule = rules[rules.length - 1]; - if (rule.charAt(rule.length - 1) === ';') { - rules[rules.length - 1] = rule.substring(0, rule.length - 1); - } + env.lastRule = false; } // If this is the root node, we don't render // a selector, or {}. // Otherwise, only output if this ruleset has rules. if (this.root) { - if (rules.length > 0) { - css.push(rules.join(env.compress ? '' : ('\n' + tabRuleStr))/* + '\n'*/); - } + css.push(rules.join(env.compress ? '' : ('\n' + tabRuleStr))); } else { - if (rules.length > 0) { - debugInfo = tree.debugInfo(env, this, tabSetStr); - selector = this.paths - .map(function (p) { - return p.map(function (s) { - return s.toCSS(env); - }).join('').trim(); - }).join(env.compress ? ',' : (',\n' + tabSetStr)); + debugInfo = tree.debugInfo(env, this, tabSetStr); + selector = this.paths + .map(function (p) { + return p.map(function (s) { + return s.toCSS(env); + }).join('').trim(); + }).join(env.compress ? ',' : (',\n' + tabSetStr)); - if (selector) { - //TODO need to do this in the toCSS visitor - //only bother doing if compression is on? - // Remove duplicates - for (var i = rules.length - 1; i >= 0; i--) { - if (rules[i].slice(0, 2) === "/*" || _rules.indexOf(rules[i]) === -1) { - _rules.unshift(rules[i]); - } + if (selector) { + //TODO need to do this in the toCSS visitor + //only bother doing if compression is on? + // Remove duplicates + for (var i = rules.length - 1; i >= 0; i--) { + if (rules[i].slice(0, 2) === "/*" || _rules.indexOf(rules[i]) === -1) { + _rules.unshift(rules[i]); } - rules = _rules; - - if (debugInfo) { - css.push(debugInfo); - css.push(tabSetStr); - } - - css.push(selector + - (env.compress ? '{' : ' {\n') + tabRuleStr + - rules.join(env.compress ? '' : ('\n' + tabRuleStr)) + - (env.compress ? '}' : '\n' + tabSetStr + '}')); } + rules = _rules; + + if (debugInfo) { + css.push(debugInfo); + css.push(tabSetStr); + } + + css.push(selector + + (env.compress ? '{' : ' {\n') + tabRuleStr + + rules.join(env.compress ? '' : ('\n' + tabRuleStr)) + + (env.compress ? '}' : '\n' + tabSetStr + '}')); } } @@ -283,18 +273,14 @@ tree.Ruleset.prototype = { var firstRuleset = true; for (i = 0; i < rulesetNodes.length; i++) { - var rulesetCSS = rulesetNodes[i].toCSS(env); - //TODO need to remove empty rulesets in the toCSS visitor - if (rulesetCSS) { - if (rules.length && firstRuleset) { - rulesets.push("\n" + (this.root ? tabRuleStr : tabSetStr)); - } - if (!firstRuleset) { - rulesets.push('\n' + (this.root ? tabRuleStr : tabSetStr)); - } - firstRuleset = false; - rulesets.push(rulesetCSS); + if (rules.length && firstRuleset) { + rulesets.push("\n" + (this.root ? tabRuleStr : tabSetStr)); } + if (!firstRuleset) { + rulesets.push('\n' + (this.root ? tabRuleStr : tabSetStr)); + } + firstRuleset = false; + rulesets.push(rulesetNodes[i].toCSS(env)); } css = css.concat(rulesets); @@ -302,9 +288,6 @@ tree.Ruleset.prototype = { return css.join('') + (!env.compress && this.firstRoot ? '\n' : ''); }, - toCSSRoot: function (env) { - }, - markReferenced: function () { for (var s = 0; s < this.selectors.length; s++) { this.selectors[s].markReferenced(); From 1464d22183c12da2af953bc77f5b274ed9d83ae3 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 16 Jul 2013 22:25:23 +0100 Subject: [PATCH 15/34] move rule duplication removal into the toCSS visitor --- lib/less/to-css-visitor.js | 27 +++++++++++++++++++++++++++ lib/less/tree/ruleset.js | 11 ----------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/lib/less/to-css-visitor.js b/lib/less/to-css-visitor.js index 868cb0f4..03994a90 100644 --- a/lib/less/to-css-visitor.js +++ b/lib/less/to-css-visitor.js @@ -111,6 +111,8 @@ } visitArgs.visitDeeper = false; + this._removeDuplicateRules(rulesetNode.rules); + // now decide whether we keep the ruleset if (rulesetNode.rules.length > 0 && rulesetNode.paths.length > 0) { rulesets.splice(0, 0, rulesetNode); @@ -126,6 +128,31 @@ return rulesets[0]; } return rulesets; + }, + + _removeDuplicateRules: function(rules) { + // remove duplicates + var ruleCache = {}, + ruleList, rule, i; + for(i = rules.length - 1; i >= 0 ; i--) { + rule = rules[i]; + if (rule instanceof tree.Rule) { + if (!ruleCache[rule.name]) { + ruleCache[rule.name] = rule; + } else { + ruleList = ruleCache[rule.name]; + if (ruleList instanceof tree.Rule) { + ruleList = ruleCache[rule.name] = [ruleCache[rule.name].toCSS(this._env)]; + } + var ruleCSS = rule.toCSS(this._env); + if (ruleList.indexOf(ruleCSS) !== -1) { + rules.splice(i, 1); + } else { + ruleList.push(ruleCSS); + } + } + } + } } }; diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 92f5540c..f97b6034 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -185,7 +185,6 @@ tree.Ruleset.prototype = { toCSS: function (env) { var i, css = [], // The CSS output rules = [], // node.Rule instances - _rules = [], // rulesets = [], // node.Ruleset instances ruleNodes = [], rulesetNodes = [], @@ -245,16 +244,6 @@ tree.Ruleset.prototype = { }).join(env.compress ? ',' : (',\n' + tabSetStr)); if (selector) { - //TODO need to do this in the toCSS visitor - //only bother doing if compression is on? - // Remove duplicates - for (var i = rules.length - 1; i >= 0; i--) { - if (rules[i].slice(0, 2) === "/*" || _rules.indexOf(rules[i]) === -1) { - _rules.unshift(rules[i]); - } - } - rules = _rules; - if (debugInfo) { css.push(debugInfo); css.push(tabSetStr); From 45bc539b571c085fe68ac3140bc57f89de4f2172 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 16 Jul 2013 22:45:21 +0100 Subject: [PATCH 16/34] refactor toCSS to be in output order --- lib/less/tree/ruleset.js | 76 ++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index f97b6034..c2e650a9 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -152,10 +152,10 @@ tree.Ruleset.prototype = { }, find: function (selector, self) { self = self || this; - var rules = [], rule, match, + var rules = [], match, key = selector.toCSS(); - if (key in this._lookups) { return this._lookups[key] } + if (key in this._lookups) { return this._lookups[key]; } this.rulesets().forEach(function (rule) { if (rule !== self) { @@ -185,14 +185,14 @@ tree.Ruleset.prototype = { toCSS: function (env) { var i, css = [], // The CSS output rules = [], // node.Rule instances - rulesets = [], // node.Ruleset instances ruleNodes = [], rulesetNodes = [], selector, // The fully rendered selector debugInfo, // Line number debugging - rule; + rule, + firstRuleset = true; - this.mergeRules(); + this.mergeRules(); //todo move to toCSS Visitor env.tabLevel = (env.tabLevel || 0); @@ -212,6 +212,25 @@ tree.Ruleset.prototype = { } } + // If this is the root node, we don't render + // a selector, or {}. + if (!this.root) { + debugInfo = tree.debugInfo(env, this, tabSetStr); + selector = this.paths + .map(function (p) { + return p.map(function (s) { + return s.toCSS(env); + }).join('').trim(); + }).join(env.compress ? ',' : (',\n' + tabSetStr)); + + if (debugInfo) { + css.push(debugInfo); + css.push(tabSetStr); + } + + css.push(selector + (env.compress ? '{' : ' {\n') + tabRuleStr); + } + // Compile rules and rulesets for (i = 0; i < ruleNodes.length; i++) { rule = ruleNodes[i]; @@ -221,59 +240,34 @@ tree.Ruleset.prototype = { } if (rule.toCSS) { - rules.push(rule.toCSS(env)); + css.push(rule.toCSS(env)); } else if (rule.value) { - rules.push(rule.value.toString()); + css.push(rule.value.toString()); } - env.lastRule = false; - } - - // If this is the root node, we don't render - // a selector, or {}. - // Otherwise, only output if this ruleset has rules. - if (this.root) { - css.push(rules.join(env.compress ? '' : ('\n' + tabRuleStr))); - } else { - debugInfo = tree.debugInfo(env, this, tabSetStr); - selector = this.paths - .map(function (p) { - return p.map(function (s) { - return s.toCSS(env); - }).join('').trim(); - }).join(env.compress ? ',' : (',\n' + tabSetStr)); - - if (selector) { - if (debugInfo) { - css.push(debugInfo); - css.push(tabSetStr); - } - - css.push(selector + - (env.compress ? '{' : ' {\n') + tabRuleStr + - rules.join(env.compress ? '' : ('\n' + tabRuleStr)) + - (env.compress ? '}' : '\n' + tabSetStr + '}')); + if (!env.lastRule) { + css.push(env.compress ? '' : ('\n' + tabRuleStr)); + } else { + env.lastRule = false; } } if (!this.root) { + css.push((env.compress ? '}' : '\n' + tabSetStr + '}')); env.tabLevel--; } - var firstRuleset = true; for (i = 0; i < rulesetNodes.length; i++) { - if (rules.length && firstRuleset) { - rulesets.push("\n" + (this.root ? tabRuleStr : tabSetStr)); + if (ruleNodes.length && firstRuleset) { + css.push("\n" + (this.root ? tabRuleStr : tabSetStr)); } if (!firstRuleset) { - rulesets.push('\n' + (this.root ? tabRuleStr : tabSetStr)); + css.push('\n' + (this.root ? tabRuleStr : tabSetStr)); } firstRuleset = false; - rulesets.push(rulesetNodes[i].toCSS(env)); + css.push(rulesetNodes[i].toCSS(env)); } - css = css.concat(rulesets); - return css.join('') + (!env.compress && this.firstRoot ? '\n' : ''); }, From d6f386727c5ddd5273f67c2e160d98412aeec276 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 16 Jul 2013 22:48:29 +0100 Subject: [PATCH 17/34] move mergerules into toCSS visitor --- lib/less/to-css-visitor.js | 37 +++++++++++++++++++++++++++++++++++++ lib/less/tree/ruleset.js | 38 -------------------------------------- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/lib/less/to-css-visitor.js b/lib/less/to-css-visitor.js index 03994a90..9f421b74 100644 --- a/lib/less/to-css-visitor.js +++ b/lib/less/to-css-visitor.js @@ -111,6 +111,7 @@ } visitArgs.visitDeeper = false; + this._mergeRules(rulesetNode.rules); this._removeDuplicateRules(rulesetNode.rules); // now decide whether we keep the ruleset @@ -153,6 +154,42 @@ } } } + }, + + _mergeRules: function (rules) { + var groups = {}, + parts, + rule, + key; + + for (var i = 0; i < rules.length; i++) { + rule = rules[i]; + + if ((rule instanceof tree.Rule) && rule.merge) { + key = [rule.name, + rule.important ? "!" : ""].join(","); + + if (!groups[key]) { + parts = groups[key] = []; + } else { + rules.splice(i--, 1); + } + + parts.push(rule); + } + } + + Object.keys(groups).map(function (k) { + parts = groups[k]; + + if (parts.length > 1) { + rule = parts[0]; + + rule.value = new (tree.Value)(parts.map(function (p) { + return p.value; + })); + } + }); } }; diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index c2e650a9..b6ebd7d0 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -192,8 +192,6 @@ tree.Ruleset.prototype = { rule, firstRuleset = true; - this.mergeRules(); //todo move to toCSS Visitor - env.tabLevel = (env.tabLevel || 0); if (!this.root) { @@ -447,42 +445,6 @@ tree.Ruleset.prototype = { sel.push(new(tree.Selector)(elements)); } } - }, - - mergeRules: function () { - var groups = {}, - parts, - rule, - key; - - for (var i = 0; i < this.rules.length; i++) { - rule = this.rules[i]; - - if ((rule instanceof tree.Rule) && rule.merge) { - key = [rule.name, - rule.important ? "!" : ""].join(","); - - if (!groups[key]) { - parts = groups[key] = []; - } else { - this.rules.splice(i--, 1); - } - - parts.push(rule); - } - } - - Object.keys(groups).map(function (k) { - parts = groups[k]; - - if (parts.length > 1) { - rule = parts[0]; - - rule.value = new (tree.Value)(parts.map(function (p) { - return p.value; - })); - } - }); } }; })(require('../tree')); From 8ca2bb74d29e02a41432de4ec067fc24b31d91ba Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 17 Jul 2013 20:26:21 +0100 Subject: [PATCH 18/34] edit to show bug in extends. move selector toCSS to be concurrent --- lib/less/to-css-visitor.js | 3 +++ lib/less/tree/ruleset.js | 30 +++++++++++++++++++++--------- lib/less/tree/selector.js | 26 ++++++++++++++------------ test/css/extend-selector.css | 4 ++-- 4 files changed, 40 insertions(+), 23 deletions(-) diff --git a/lib/less/to-css-visitor.js b/lib/less/to-css-visitor.js index 9f421b74..a9988c6f 100644 --- a/lib/less/to-css-visitor.js +++ b/lib/less/to-css-visitor.js @@ -84,6 +84,9 @@ rulesetNode.paths = rulesetNode.paths .filter(function(p) { var i; + if (p[0].elements[0].combinator.value === ' ') { + p[0].elements[0].combinator = new(tree.Combinator)(''); + } for(i = 0; i < p.length; i++) { if (p[i].getIsReferenced() && p[i].getIsOutput()) { return true; diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index b6ebd7d0..17113c5b 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -9,7 +9,13 @@ tree.Ruleset = function (selectors, rules, strictImports) { tree.Ruleset.prototype = { type: "Ruleset", accept: function (visitor) { - this.selectors = visitor.visit(this.selectors); + if (this.paths) { + for(var i = 0; i < this.paths.length; i++) { + this.paths[i] = visitor.visit(this.paths[i]); + } + } else { + this.selectors = visitor.visit(this.selectors); + } this.rules = visitor.visit(this.rules); }, eval: function (env) { @@ -184,7 +190,6 @@ tree.Ruleset.prototype = { // toCSS: function (env) { var i, css = [], // The CSS output - rules = [], // node.Rule instances ruleNodes = [], rulesetNodes = [], selector, // The fully rendered selector @@ -214,19 +219,26 @@ tree.Ruleset.prototype = { // a selector, or {}. if (!this.root) { debugInfo = tree.debugInfo(env, this, tabSetStr); - selector = this.paths - .map(function (p) { - return p.map(function (s) { - return s.toCSS(env); - }).join('').trim(); - }).join(env.compress ? ',' : (',\n' + tabSetStr)); if (debugInfo) { css.push(debugInfo); css.push(tabSetStr); } - css.push(selector + (env.compress ? '{' : ' {\n') + tabRuleStr); + var j, path; + for(i = 0; i < this.paths.length; i++) { + path = this.paths[i]; + env.firstSelector = true; + for(j = 0; j < path.length; j++) { + css.push(path[j].toCSS(env)); + env.firstSelector = false; + } + if (i + 1 < this.paths.length) { + css.push(env.compress ? ',' : (',\n' + tabSetStr)); + } + } + + css.push((env.compress ? '{' : ' {\n') + tabRuleStr); } // Compile rules and rulesets diff --git a/lib/less/tree/selector.js b/lib/less/tree/selector.js index 1008fb75..049650cd 100644 --- a/lib/less/tree/selector.js +++ b/lib/less/tree/selector.js @@ -57,21 +57,23 @@ tree.Selector.prototype = { output.add(this.toCSS(env)); }, toCSS: function (env) { - if (this._css) { return this._css } - if (this.elements[0].combinator.value === "") { - this._css = ' '; - } else { - this._css = ''; + if (!this._css) { + //surprised this caching works since the first element combinator is changed from ' ' to '' + //in the toCSS visitor, when toCSS may have already been called? + //is this caching worth it? + this._css = this.elements.map(function (e) { + if (typeof(e) === 'string') { + return ' ' + e.trim(); + } else { + return e.toCSS(env); + } + }).join(''); } - this._css += this.elements.map(function (e) { - if (typeof(e) === 'string') { - return ' ' + e.trim(); - } else { - return e.toCSS(env); - } - }).join(''); + if ((!env || !env.firstSelector) && this.elements[0].combinator.value === "") { + return ' ' + this._css; + } return this._css; }, diff --git a/test/css/extend-selector.css b/test/css/extend-selector.css index dc03d1cd..4a525746 100644 --- a/test/css/extend-selector.css +++ b/test/css/extend-selector.css @@ -52,9 +52,9 @@ div.ext7, .c.replace + .replace .replace, .replace.replace .c, .c.replace + .replace .c, -.rep_ace .rep_ace .rep_ace, +.rep_ace.rep_ace .rep_ace, .c.rep_ace + .rep_ace .rep_ace, -.rep_ace .rep_ace .c, +.rep_ace.rep_ace .c, .c.rep_ace + .rep_ace .c { prop: copy-paste-replace; } From f99d29cfad5921ff0cb40dc7a9c66e2179847959 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 17 Jul 2013 21:59:43 +0100 Subject: [PATCH 19/34] continue moving to genCSS --- lib/less/tree/color.js | 2 +- lib/less/tree/ruleset.js | 43 +++++++++++++++++----------------------- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/lib/less/tree/color.js b/lib/less/tree/color.js index 9e2c7c57..c537143e 100644 --- a/lib/less/tree/color.js +++ b/lib/less/tree/color.js @@ -24,7 +24,7 @@ tree.Color = function (rgb, a) { }; tree.Color.prototype = { type: "Color", - eval: function () { return this }, + eval: function () { return this; }, luma: function () { return (0.2126 * this.rgb[0] / 255) + (0.7152 * this.rgb[1] / 255) + (0.0722 * this.rgb[2] / 255); }, genCSS: function (env, output) { diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 17113c5b..fb61495c 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -181,21 +181,13 @@ tree.Ruleset.prototype = { return this._lookups[key] = rules; }, genCSS: function (env, output) { - output.add(this.toCSS(env)); - }, - // - // Entry point for code generation - // - // `context` holds an array of arrays. - // - toCSS: function (env) { - var i, css = [], // The CSS output + var i, j, ruleNodes = [], rulesetNodes = [], - selector, // The fully rendered selector debugInfo, // Line number debugging rule, - firstRuleset = true; + firstRuleset = true, + path; env.tabLevel = (env.tabLevel || 0); @@ -221,24 +213,23 @@ tree.Ruleset.prototype = { debugInfo = tree.debugInfo(env, this, tabSetStr); if (debugInfo) { - css.push(debugInfo); - css.push(tabSetStr); + output.add(debugInfo); + output.add(tabSetStr); } - var j, path; for(i = 0; i < this.paths.length; i++) { path = this.paths[i]; env.firstSelector = true; for(j = 0; j < path.length; j++) { - css.push(path[j].toCSS(env)); + output.add(path[j].genCSS(env, output)); env.firstSelector = false; } if (i + 1 < this.paths.length) { - css.push(env.compress ? ',' : (',\n' + tabSetStr)); + output.add(env.compress ? ',' : (',\n' + tabSetStr)); } } - css.push((env.compress ? '{' : ' {\n') + tabRuleStr); + output.add((env.compress ? '{' : ' {\n') + tabRuleStr); } // Compile rules and rulesets @@ -250,37 +241,39 @@ tree.Ruleset.prototype = { } if (rule.toCSS) { - css.push(rule.toCSS(env)); + output.add(rule.genCSS(env, output)); } else if (rule.value) { - css.push(rule.value.toString()); + output.add(rule.value.toString()); } if (!env.lastRule) { - css.push(env.compress ? '' : ('\n' + tabRuleStr)); + output.add(env.compress ? '' : ('\n' + tabRuleStr)); } else { env.lastRule = false; } } if (!this.root) { - css.push((env.compress ? '}' : '\n' + tabSetStr + '}')); + output.add((env.compress ? '}' : '\n' + tabSetStr + '}')); env.tabLevel--; } for (i = 0; i < rulesetNodes.length; i++) { if (ruleNodes.length && firstRuleset) { - css.push("\n" + (this.root ? tabRuleStr : tabSetStr)); + output.add("\n" + (this.root ? tabRuleStr : tabSetStr)); } if (!firstRuleset) { - css.push('\n' + (this.root ? tabRuleStr : tabSetStr)); + output.add('\n' + (this.root ? tabRuleStr : tabSetStr)); } firstRuleset = false; - css.push(rulesetNodes[i].toCSS(env)); + output.add(rulesetNodes[i].genCSS(env, output)); } - return css.join('') + (!env.compress && this.firstRoot ? '\n' : ''); + output.add(!env.compress && this.firstRoot ? '\n' : ''); }, + toCSS: tree.toCSS, + markReferenced: function () { for (var s = 0; s < this.selectors.length; s++) { this.selectors[s].markReferenced(); From aab9c1b24ff26ff1c2438a387f2503a7dd639033 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 18 Jul 2013 06:21:11 +0100 Subject: [PATCH 20/34] Fix jshint errors after merge and add jshint to makefile --- Makefile | 3 +++ lib/less/tree/dimension.js | 4 ++-- package.json | 2 +- test/less-test.js | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 94d25109..ad621787 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,9 @@ browser-test: browser-prepare browser-test-server: browser-prepare phantomjs test/browser/phantom-runner.js --no-tests +jshint: + node_modules/.bin/jshint --config ./.jshintrc . + rhino: @@mkdir -p dist @@touch ${RHINO} diff --git a/lib/less/tree/dimension.js b/lib/less/tree/dimension.js index e6cd9d58..c203fd0f 100644 --- a/lib/less/tree/dimension.js +++ b/lib/less/tree/dimension.js @@ -244,7 +244,7 @@ tree.Unit.prototype = { }, usedUnits: function() { - var group, groupName, result = {}, mapUnit; + var group, result = {}, mapUnit; mapUnit = function (atomicUnit) { /*jshint loopfunc:true */ @@ -255,7 +255,7 @@ tree.Unit.prototype = { return atomicUnit; }; - for (groupName in tree.UnitConversions) { + for (var groupName in tree.UnitConversions) { if (tree.UnitConversions.hasOwnProperty(groupName)) { group = tree.UnitConversions[groupName]; diff --git a/package.json b/package.json index 976b6a63..51b65ee7 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "node": ">=0.4.2" }, "scripts": { - "pretest": "./node_modules/.bin/jshint --config ./.jshintrc .", + "pretest": "make jshint", "test": "make test" }, "optionalDependencies": { diff --git a/test/less-test.js b/test/less-test.js index 92af79b9..b483c4fb 100644 --- a/test/less-test.js +++ b/test/less-test.js @@ -142,7 +142,7 @@ function diff(left, right) { sys.puts(""); require('diff').diffLines(left, right).forEach(function(item) { if(item.added || item.removed) { - var text = item.value.replace("\n", String.fromCharCode(182) + "\n");; + var text = item.value.replace("\n", String.fromCharCode(182) + "\n"); sys.print(stylize(text, item.added ? 'green' : 'red')); } else { sys.print(item.value); From fb75c42e4bea62eb070b9a46c1a933b092bc1e34 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 18 Jul 2013 06:48:32 +0100 Subject: [PATCH 21/34] move more files over to use genCSS --- lib/less/tree.js | 14 ++++++++++++++ lib/less/tree/dimension.js | 25 +++++++++++-------------- lib/less/tree/directive.js | 20 ++++++-------------- lib/less/tree/element.js | 34 +++++++++++++++++++++------------- lib/less/tree/import.js | 16 ++++++++-------- lib/less/tree/keyword.js | 4 ++-- lib/less/tree/media.js | 25 ++++--------------------- 7 files changed, 66 insertions(+), 72 deletions(-) diff --git a/lib/less/tree.js b/lib/less/tree.js index b974b877..d44d2339 100644 --- a/lib/less/tree.js +++ b/lib/less/tree.js @@ -58,4 +58,18 @@ tree.toCSS = function (env) { return strs.join(''); }; +tree.outputRuleset = function (env, output, rules) { + output.add((env.compress ? '{' : ' {\n')); + env.tabLevel = (env.tabLevel || 0) + 1; + var tabRuleStr = env.compress ? '' : Array(env.tabLevel + 1).join(" "), + tabSetStr = env.compress ? '' : Array(env.tabLevel).join(" "); + for(var i = 0; i < rules.length; i++) { + output.add(tabRuleStr); + rules[i].genCSS(env, output); + output.add(env.compress ? '' : '\n'); + } + env.tabLevel--; + output.add(tabSetStr + "}"); +}; + })(require('./tree')); diff --git a/lib/less/tree/dimension.js b/lib/less/tree/dimension.js index c203fd0f..2097e128 100644 --- a/lib/less/tree/dimension.js +++ b/lib/less/tree/dimension.js @@ -21,9 +21,6 @@ tree.Dimension.prototype = { return new(tree.Color)([this.value, this.value, this.value]); }, genCSS: function (env, output) { - output.add(this.toCSS(env)); - }, - toCSS: function (env) { if ((env && env.strictUnits) && !this.unit.isSingular()) { throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: "+this.unit.toString()); } @@ -39,7 +36,8 @@ tree.Dimension.prototype = { if (env && env.compress) { // Zero values doesn't need a unit if (value === 0 && !this.unit.isAngle()) { - return strValue; + output.add(strValue); + return; } // Float values doesn't need a leading zero @@ -48,8 +46,10 @@ tree.Dimension.prototype = { } } - return strValue + this.unit.toCSS(env); + output.add(strValue); + this.unit.genCSS(env, output); }, + toCSS: tree.toCSS, // In an operation between two Dimensions, // we default to the first Dimension's unit, @@ -188,20 +188,17 @@ tree.Unit.prototype = { return new tree.Unit(this.numerator.slice(0), this.denominator.slice(0), this.backupUnit); }, genCSS: function (env, output) { - output.add(this.toCSS(env)); - }, - toCSS: function (env) { if (this.numerator.length >= 1) { - return this.numerator[0]; - } + output.add(this.numerator[0]); + } else if (this.denominator.length >= 1) { - return this.denominator[0]; - } + output.add(this.denominator[0]); + } else if ((!env || !env.strictUnits) && this.backupUnit) { - return this.backupUnit; + output.add(this.backupUnit); } - return ""; }, + toCSS: tree.toCSS, toString: function () { var i, returnStr = this.numerator.join("*"); diff --git a/lib/less/tree/directive.js b/lib/less/tree/directive.js index e66a21ad..9f2a0f5e 100644 --- a/lib/less/tree/directive.js +++ b/lib/less/tree/directive.js @@ -18,24 +18,16 @@ tree.Directive.prototype = { this.value = visitor.visit(this.value); }, genCSS: function (env, output) { - output.add(this.toCSS(env)); - }, - toCSS: function (env) { + output.add(this.name); if (this.rules) { - //todo put in a common place, also done in media.js - env.tabLevel = (env.tabLevel || 0) + 1; - var tabRuleStr = env.compress ? '' : Array(env.tabLevel + 1).join(" "), - tabSetStr = env.compress ? '' : Array(env.tabLevel).join(" "); - var css = ""; - for(var i = 0; i < this.rules.length; i++) { - css += tabRuleStr + this.rules[i].toCSS(env) + (env.compress ? "" : "\n"); - } - env.tabLevel--; - return this.name + (env.compress ? '{' : ' {\n') + css + (env.compress ? '}': (tabSetStr + '}')); + tree.outputRuleset(env, output, this.rules); } else { - return this.name + ' ' + this.value.toCSS() + ';'; + output.add(' '); + this.value.genCSS(env, output); + output.add(';'); } }, + toCSS: tree.toCSS, eval: function (env) { var evaldDirective = this; if (this.rules) { diff --git a/lib/less/tree/element.js b/lib/less/tree/element.js index 6605b548..b827d84e 100644 --- a/lib/less/tree/element.js +++ b/lib/less/tree/element.js @@ -75,20 +75,28 @@ tree.Combinator = function (value) { }; tree.Combinator.prototype = { type: "Combinator", - genCSS: function (env, output) { - output.add(this.toCSS(env)); + _outputMap: { + '' : '', + ' ' : ' ', + ':' : ' :', + '+' : ' + ', + '~' : ' ~ ', + '>' : ' > ', + '|' : '|' }, - toCSS: function (env) { - return { - '' : '', - ' ' : ' ', - ':' : ' :', - '+' : env.compress ? '+' : ' + ', - '~' : env.compress ? '~' : ' ~ ', - '>' : env.compress ? '>' : ' > ', - '|' : '|' - }[this.value]; - } + _outputMapCompressed: { + '' : '', + ' ' : ' ', + ':' : ' :', + '+' : '+', + '~' : '~', + '>' : '>', + '|' : '|' + }, + genCSS: function (env, output) { + output.add((env.compress ? this._outputMapCompressed : this._outputMap)[this.value]); + }, + toCSS: tree.toCSS }; })(require('../tree')); diff --git a/lib/less/tree/import.js b/lib/less/tree/import.js index 7ad06ee8..b917d6c0 100644 --- a/lib/less/tree/import.js +++ b/lib/less/tree/import.js @@ -49,17 +49,17 @@ tree.Import.prototype = { } }, genCSS: function (env, output) { - output.add(this.toCSS(env)); - }, - toCSS: function (env) { - var features = this.features ? ' ' + this.features.toCSS(env) : ''; - if (this.css) { - return "@import " + this.path.toCSS() + features + ';'; - } else { - return ""; + output.add("@import "); + this.path.genCSS(env, output); + if (this.features) { + output.add(" "); + this.features.genCSS(env, output); + } + output.add(';'); } }, + toCSS: tree.toCSS, getPath: function () { if (this.path instanceof tree.Quoted) { var path = this.path.value; diff --git a/lib/less/tree/keyword.js b/lib/less/tree/keyword.js index 46090d7f..8cfbb09c 100644 --- a/lib/less/tree/keyword.js +++ b/lib/less/tree/keyword.js @@ -5,9 +5,9 @@ tree.Keyword.prototype = { type: "Keyword", eval: function () { return this; }, genCSS: function (env, output) { - output.add(this.toCSS(env)); + output.add(this.value); }, - toCSS: function () { return this.value; }, + toCSS: tree.toCSS, compare: function (other) { if (other instanceof tree.Keyword) { return other.value === this.value ? 0 : 1; diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index 59e6e4fd..08198404 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -17,28 +17,11 @@ tree.Media.prototype = { this.rules = visitor.visit(this.rules); }, genCSS: function (env, output) { - output.add(this.toCSS(env)); - }, - toCSS: function (env) { - var features = this.features.toCSS(env); - - //todo put in a common place, also done in directive.js - env.tabLevel = (env.tabLevel || 0) + 1; - var tabRuleStr = env.compress ? '' : Array(env.tabLevel + 1).join(" "), - tabSetStr = env.compress ? '' : Array(env.tabLevel).join(" "); - var css = ""; - for(var i = 0; i < this.rules.length; i++) { - css += tabRuleStr + this.rules[i].toCSS(env) + (env.compress ? '' : '\n'); - } - env.tabLevel--; - - if (this.rules.length) { //css.match(/\S/)) { - return '@media ' + features + (env.compress ? '{' : ' {\n') + css + - '}'; - } else { - return ""; - } + output.add('@media '); + this.features.genCSS(env, output); + tree.outputRuleset(env, output, this.rules); }, + toCSS: tree.toCSS, eval: function (env) { if (!env.mediaBlocks) { env.mediaBlocks = []; From 1cc63d11b472b9de0f7546a9cf07e653b3cee45b Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 18 Jul 2013 12:47:11 +0100 Subject: [PATCH 22/34] convert the rest of the nodes to use genCSS --- lib/less/tree/mixin.js | 1 - lib/less/tree/negative.js | 7 +++---- lib/less/tree/operation.js | 15 ++++++++++----- lib/less/tree/paren.js | 8 ++++---- lib/less/tree/quoted.js | 14 +++++++------- lib/less/tree/rule.js | 26 ++++++++++--------------- lib/less/tree/selector.js | 30 ++++++++++------------------- lib/less/tree/unicode-descriptor.js | 6 ++---- lib/less/tree/url.js | 8 ++++---- lib/less/tree/value.js | 14 ++++++++------ 10 files changed, 58 insertions(+), 71 deletions(-) diff --git a/lib/less/tree/mixin.js b/lib/less/tree/mixin.js index 2908d981..fbb6780e 100644 --- a/lib/less/tree/mixin.js +++ b/lib/less/tree/mixin.js @@ -109,7 +109,6 @@ tree.mixin.Definition.prototype = { this.rules = visitor.visit(this.rules); this.condition = visitor.visit(this.condition); }, - toCSS: function () { return ""; }, variable: function (name) { return this.parent.variable.call(this, name); }, variables: function () { return this.parent.variables.call(this); }, find: function () { return this.parent.find.apply(this, arguments); }, diff --git a/lib/less/tree/negative.js b/lib/less/tree/negative.js index 3342d82e..e36b6084 100644 --- a/lib/less/tree/negative.js +++ b/lib/less/tree/negative.js @@ -9,11 +9,10 @@ tree.Negative.prototype = { this.value = visitor.visit(this.value); }, genCSS: function (env, output) { - output.add(this.toCSS(env)); - }, - toCSS: function (env) { - return '-' + this.value.toCSS(env); + output.add('-'); + this.value.genCSS(env, output) }, + toCSS: tree.toCSS, eval: function (env) { if (env.isMathOn()) { return (new(tree.Operation)('*', [new(tree.Dimension)(-1), this.value])).eval(env); diff --git a/lib/less/tree/operation.js b/lib/less/tree/operation.js index 807d8f88..8909f14f 100644 --- a/lib/less/tree/operation.js +++ b/lib/less/tree/operation.js @@ -35,12 +35,17 @@ tree.Operation.prototype = { } }, genCSS: function (env, output) { - output.add(this.toCSS(env)); + this.operands[0].genCSS(env, output); + if (this.isSpaced) { + output.add(" "); + } + output.add(this.op); + if (this.isSpaced) { + output.add(" "); + } + this.operands[1].genCSS(env, output) }, - toCSS: function (env) { - var separator = this.isSpaced ? " " : ""; - return this.operands[0].toCSS() + separator + this.op + separator + this.operands[1].toCSS(); - } + toCSS: tree.toCSS }; tree.operate = function (env, op, a, b) { diff --git a/lib/less/tree/paren.js b/lib/less/tree/paren.js index 3bd65d41..e27b8d66 100644 --- a/lib/less/tree/paren.js +++ b/lib/less/tree/paren.js @@ -10,11 +10,11 @@ tree.Paren.prototype = { this.value = visitor.visit(this.value); }, genCSS: function (env, output) { - output.add(this.toCSS(env)); - }, - toCSS: function (env) { - return '(' + this.value.toCSS(env).trim() + ')'; + output.add('('); + this.value.genCSS(env, output); + output.add(')'); }, + toCSS: tree.toCSS, eval: function (env) { return new(tree.Paren)(this.value.eval(env)); } diff --git a/lib/less/tree/quoted.js b/lib/less/tree/quoted.js index 188cc2bb..e78accad 100644 --- a/lib/less/tree/quoted.js +++ b/lib/less/tree/quoted.js @@ -10,15 +10,15 @@ tree.Quoted = function (str, content, escaped, index, currentFileInfo) { tree.Quoted.prototype = { type: "Quoted", genCSS: function (env, output) { - output.add(this.toCSS(env)); - }, - toCSS: function () { - if (this.escaped) { - return this.value; - } else { - return this.quote + this.value + this.quote; + if (!this.escaped) { + output.add(this.quote); + } + output.add(this.value); + if (!this.escaped) { + output.add(this.quote); } }, + toCSS: tree.toCSS, eval: function (env) { var that = this; var value = this.value.replace(/`([^`]+)`/g, function (_, exp) { diff --git a/lib/less/tree/rule.js b/lib/less/tree/rule.js index cafe6d08..e8fe085d 100644 --- a/lib/less/tree/rule.js +++ b/lib/less/tree/rule.js @@ -17,24 +17,18 @@ tree.Rule.prototype = { this.value = visitor.visit(this.value); }, genCSS: function (env, output) { - output.add(this.toCSS(env)); - }, - toCSS: function (env) { - if (this.variable) { return ""; } - else { - try { - var css = this.name + (env.compress ? ':' : ': ') + - this.value.toCSS(env) + - this.important + ((this.inline || (env.lastRule && env.compress)) ? "" : ";"); - return css; - } - catch(e) { - e.index = this.index; - e.filename = this.currentFileInfo.filename; - throw e; - } + output.add(this.name + (env.compress ? ':' : ': ')); + try { + this.value.genCSS(env, output); } + catch(e) { + e.index = this.index; + e.filename = this.currentFileInfo.filename; + throw e; + } + output.add(this.important + ((this.inline || (env.lastRule && env.compress)) ? "" : ";")); }, + toCSS: tree.toCSS, eval: function (env) { var strictMathBypass = false; if (this.name === "font" && env.strictMath === false) { diff --git a/lib/less/tree/selector.js b/lib/less/tree/selector.js index 2a2f1cdc..1e10be14 100644 --- a/lib/less/tree/selector.js +++ b/lib/less/tree/selector.js @@ -55,29 +55,19 @@ tree.Selector.prototype = { }), evaldCondition); }, genCSS: function (env, output) { - output.add(this.toCSS(env)); - }, - toCSS: function (env) { - - if (!this._css) { - //surprised this caching works since the first element combinator is changed from ' ' to '' - //in the toCSS visitor, when toCSS may have already been called? - //is this caching worth it? - this._css = this.elements.map(function (e) { - if (typeof(e) === 'string') { - return ' ' + e.trim(); - } else { - return e.toCSS(env); - } - }).join(''); - } - + var i, element; if ((!env || !env.firstSelector) && this.elements[0].combinator.value === "") { - return ' ' + this._css; + output.add(' '); + } + if (!this._css) { + //TODO caching? speed comparison? + for(i = 0; i < this.elements.length; i++) { + element = this.elements[i]; + element.genCSS(env, output); + } } - - return this._css; }, + toCSS: tree.toCSS, markReferenced: function () { this.isReferenced = true; }, diff --git a/lib/less/tree/unicode-descriptor.js b/lib/less/tree/unicode-descriptor.js index aa465686..7bf98ea5 100644 --- a/lib/less/tree/unicode-descriptor.js +++ b/lib/less/tree/unicode-descriptor.js @@ -6,11 +6,9 @@ tree.UnicodeDescriptor = function (value) { tree.UnicodeDescriptor.prototype = { type: "UnicodeDescriptor", genCSS: function (env, output) { - output.add(this.toCSS(env)); - }, - toCSS: function (env) { - return this.value; + output.add(this.value); }, + toCSS: tree.toCSS, eval: function () { return this; } }; diff --git a/lib/less/tree/url.js b/lib/less/tree/url.js index c8b72059..b2b91e8b 100644 --- a/lib/less/tree/url.js +++ b/lib/less/tree/url.js @@ -10,11 +10,11 @@ tree.URL.prototype = { this.value = visitor.visit(this.value); }, genCSS: function (env, output) { - output.add(this.toCSS(env)); - }, - toCSS: function () { - return "url(" + this.value.toCSS() + ")"; + output.add("url("); + this.value.genCSS(env, output) + output.add(")"); }, + toCSS: tree.toCSS, eval: function (ctx) { var val = this.value.eval(ctx), rootpath; diff --git a/lib/less/tree/value.js b/lib/less/tree/value.js index 2deebb57..7f110f65 100644 --- a/lib/less/tree/value.js +++ b/lib/less/tree/value.js @@ -18,13 +18,15 @@ tree.Value.prototype = { } }, genCSS: function (env, output) { - output.add(this.toCSS(env)); + var i; + for(i = 0; i < this.value.length; i++) { + this.value[i].genCSS(env, output); + if (i+1 < this.value.length) { + output.add(env.compress ? ',' : ', '); + } + } }, - toCSS: function (env) { - return this.value.map(function (e) { - return e.toCSS(env); - }).join(env.compress ? ',' : ', '); - } + toCSS: tree.toCSS }; })(require('../tree')); From 1ec0563c9a60fe378d5393038c7242fd96167b5e Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 18 Jul 2013 13:06:56 +0100 Subject: [PATCH 23/34] add sourcemapper class --- bin/lessc | 6 ++++++ lib/less/env.js | 3 ++- lib/less/index.js | 1 + lib/less/lessc_helper.js | 1 + lib/less/parser.js | 4 ++++ lib/less/source-map-output.js | 17 +++++++++++++++++ 6 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 lib/less/source-map-output.js diff --git a/bin/lessc b/bin/lessc index 01de8320..27d341f5 100755 --- a/bin/lessc +++ b/bin/lessc @@ -138,6 +138,11 @@ args = args.filter(function (arg) { options.dumpLineNumbers = match[2]; } break; + case 'source-map': + if (checkArgFunc(arg, match[2])) { + options.sourceMap = match[2]; + } + break; case 'rp': case 'rootpath': if (checkArgFunc(arg, match[2])) { @@ -240,6 +245,7 @@ var parseLessFile = function (e, data) { ieCompat: options.ieCompat, compress: options.compress, cleancss: options.cleancss, + sourceMap: options.sourceMap, maxLineLen: options.maxLineLen, strictMath: options.strictMath, strictUnits: options.strictUnits diff --git a/lib/less/env.js b/lib/less/env.js index 60ee1952..dd4d5a5f 100644 --- a/lib/less/env.js +++ b/lib/less/env.js @@ -58,7 +58,8 @@ 'ieCompat', // whether to enforce IE compatibility (IE8 data-uri) 'strictMath', // whether math has to be within parenthesis 'strictUnits', // whether units need to evaluate correctly - 'cleancss' // whether to compress with clean-css + 'cleancss', // whether to compress with clean-css + 'sourceMap' // whether to output a source map ]; tree.evalEnv = function(options, frames) { diff --git a/lib/less/index.js b/lib/less/index.js index 297a7152..9d6248d9 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -215,5 +215,6 @@ require('./import-visitor.js'); require('./extend-visitor.js'); require('./join-selector-visitor.js'); require('./to-css-visitor.js'); +require('./source-map-output.js'); for (var k in less) { exports[k] = less[k]; } diff --git a/lib/less/lessc_helper.js b/lib/less/lessc_helper.js index 536c5e95..0e711a77 100644 --- a/lib/less/lessc_helper.js +++ b/lib/less/lessc_helper.js @@ -51,6 +51,7 @@ var lessc_helper = { sys.puts(" that will output the information within a fake"); sys.puts(" media query which is compatible with the SASS"); sys.puts(" format, and 'all' which will do both."); + sys.puts(" --source-map=FILENAME Outputs a sourcemap to the filename."); sys.puts(" -rp, --rootpath=URL Set rootpath for url rewriting in relative imports and urls."); sys.puts(" Works with or without the relative-urls option."); sys.puts(" -ru, --relative-urls re-write relative urls to the base less file."); diff --git a/lib/less/parser.js b/lib/less/parser.js index 9c0866f8..2651029c 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -465,6 +465,10 @@ less.Parser = function Parser(env) { new(tree.toCSSVisitor)({compress: Boolean(options.compress)}) .run(evaldRoot); + if (options.sourceMap) { + evaldRoot = new tree.sourceMapOutput(options.sourceMap, evaldRoot); + } + css = evaldRoot.toCSS({ compress: Boolean(options.compress), dumpLineNumbers: env.dumpLineNumbers, diff --git a/lib/less/source-map-output.js b/lib/less/source-map-output.js new file mode 100644 index 00000000..e316fdc4 --- /dev/null +++ b/lib/less/source-map-output.js @@ -0,0 +1,17 @@ +(function (tree) { + tree.sourceMapOutput = function (sourceMapFilename, rootNode) { + this._css = []; + this._rootNode = rootNode; + this._sourceMapFilename = sourceMapFilename; + }; + + tree.sourceMapOutput.prototype.add = function(chunk, node) { + this._css.push(chunk); + }; + + tree.sourceMapOutput.prototype.toCSS = function(env) { + this._rootNode.genCSS(env, this); + return this._css.join(''); + }; + +})(require('./tree')); \ No newline at end of file From 8a4e7b97c6a58e9a9c546111b5818663bbcfe8e3 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 18 Jul 2013 13:58:41 +0100 Subject: [PATCH 24/34] hack in prototype exporting empty source-map --- lib/less/source-map-output.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/less/source-map-output.js b/lib/less/source-map-output.js index e316fdc4..b936d30b 100644 --- a/lib/less/source-map-output.js +++ b/lib/less/source-map-output.js @@ -1,16 +1,42 @@ (function (tree) { + var sourceMap = require("source-map"); + tree.sourceMapOutput = function (sourceMapFilename, rootNode) { this._css = []; this._rootNode = rootNode; this._sourceMapFilename = sourceMapFilename; + + this._lineNumber = 0; + this._column = 0; }; - tree.sourceMapOutput.prototype.add = function(chunk, node) { + tree.sourceMapOutput.prototype.add = function(chunk, index, fileInfo) { + if (true) { //fileInfo.filename hasn't had source mapped + //this._sourceMapGenerator.setSourceContent(fileInfo.filename, fileInfo.source); + } + if (fileInfo) { + this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber, column: this._column}, original: { line: 1, column: 1}, source: fileInfo.filename}); + } + if (!chunk) { + //TODO what is calling this with undefined? + return; + } + var lines = chunk.split("\n"), + columns = lines[lines.length-1]; + + this._lineNumber += lines.length - 1; + this._column += columns.length - 1; + this._css.push(chunk); }; tree.sourceMapOutput.prototype.toCSS = function(env) { + this._sourceMapGenerator = new sourceMap.SourceMapGenerator({ file:"outputFilenameTODO.css", sourceRoot:"http://blah.com/TODO" }); this._rootNode.genCSS(env, this); + + //TODO write this to sourceMapFilename + console.warn(this._sourceMapGenerator.toJSON()); + return this._css.join(''); }; From f14f86136c5e773221c5c61267698de08a4c832d Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 18 Jul 2013 22:21:59 +0100 Subject: [PATCH 25/34] add tests for sourcemaps and get prototype working --- bin/lessc | 6 ++++++ lib/less/parser.js | 9 ++++++++- lib/less/source-map-output.js | 36 +++++++++++++++++++++------------ lib/less/tree/negative.js | 2 +- lib/less/tree/operation.js | 2 +- lib/less/tree/rule.js | 4 ++-- lib/less/tree/url.js | 2 +- test/less-test.js | 27 ++++++++++++++++++++++--- test/less/sourcemaps/basic.less | 9 +++++++++ test/sourcemaps/basic.json | 1 + 10 files changed, 76 insertions(+), 22 deletions(-) create mode 100644 test/less/sourcemaps/basic.less create mode 100644 test/sourcemaps/basic.json diff --git a/bin/lessc b/bin/lessc index 27d341f5..e7388a37 100755 --- a/bin/lessc +++ b/bin/lessc @@ -216,6 +216,11 @@ if (options.depends) { sys.print(outputbase + ": "); } +var writeSourceMap = function(output) { + ensureDirectory(options.sourceMap); + fs.writeFileSync(options.sourceMap, output, 'utf8'); +}; + var parseLessFile = function (e, data) { if (e) { sys.puts("lessc: " + e.message); @@ -246,6 +251,7 @@ var parseLessFile = function (e, data) { compress: options.compress, cleancss: options.cleancss, sourceMap: options.sourceMap, + writeSourceMap: writeSourceMap, maxLineLen: options.maxLineLen, strictMath: options.strictMath, strictUnits: options.strictUnits diff --git a/lib/less/parser.js b/lib/less/parser.js index 2651029c..cd8e12e8 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -308,6 +308,8 @@ less.Parser = function Parser(env) { // Remove potential UTF Byte Order Mark input = input.replace(/^\uFEFF/, ''); + parser.imports.contents[env.currentFileInfo.filename] = input; + // Split the input into chunks. chunks = (function (chunks) { var j = 0, @@ -466,7 +468,12 @@ less.Parser = function Parser(env) { .run(evaldRoot); if (options.sourceMap) { - evaldRoot = new tree.sourceMapOutput(options.sourceMap, evaldRoot); + evaldRoot = new tree.sourceMapOutput( + { + writeSourceMap: options.writeSourceMap, + rootNode: evaldRoot, + contentsMap: parser.imports.contents + }); } css = evaldRoot.toCSS({ diff --git a/lib/less/source-map-output.js b/lib/less/source-map-output.js index b936d30b..a86c7ec6 100644 --- a/lib/less/source-map-output.js +++ b/lib/less/source-map-output.js @@ -1,28 +1,34 @@ (function (tree) { var sourceMap = require("source-map"); - tree.sourceMapOutput = function (sourceMapFilename, rootNode) { + tree.sourceMapOutput = function (options) { this._css = []; - this._rootNode = rootNode; - this._sourceMapFilename = sourceMapFilename; + this._rootNode = options.rootNode; + this._writeSourceMap = options.writeSourceMap; + this._contentsMap = options.contentsMap; this._lineNumber = 0; this._column = 0; }; - tree.sourceMapOutput.prototype.add = function(chunk, index, fileInfo) { - if (true) { //fileInfo.filename hasn't had source mapped - //this._sourceMapGenerator.setSourceContent(fileInfo.filename, fileInfo.source); - } - if (fileInfo) { - this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber, column: this._column}, original: { line: 1, column: 1}, source: fileInfo.filename}); - } + tree.sourceMapOutput.prototype.add = function(chunk, fileInfo, index) { + if (!chunk) { //TODO what is calling this with undefined? return; } - var lines = chunk.split("\n"), + + var lines, + columns; + + if (fileInfo) { + var inputSource = this._contentsMap[fileInfo.filename].substring(0, index); + lines = inputSource.split("\n"); columns = lines[lines.length-1]; + this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber, column: this._column}, original: { line: lines.length - 1, column: columns.length}, source: fileInfo.filename}); + } + lines = chunk.split("\n"); + columns = lines[lines.length-1]; this._lineNumber += lines.length - 1; this._column += columns.length - 1; @@ -32,10 +38,14 @@ tree.sourceMapOutput.prototype.toCSS = function(env) { this._sourceMapGenerator = new sourceMap.SourceMapGenerator({ file:"outputFilenameTODO.css", sourceRoot:"http://blah.com/TODO" }); + + for(var filename in this._contentsMap) { + this._sourceMapGenerator.setSourceContent(filename, this._contentsMap[filename]); + } + this._rootNode.genCSS(env, this); - //TODO write this to sourceMapFilename - console.warn(this._sourceMapGenerator.toJSON()); + this._writeSourceMap(JSON.stringify(this._sourceMapGenerator.toJSON())); return this._css.join(''); }; diff --git a/lib/less/tree/negative.js b/lib/less/tree/negative.js index e36b6084..12dbcc63 100644 --- a/lib/less/tree/negative.js +++ b/lib/less/tree/negative.js @@ -10,7 +10,7 @@ tree.Negative.prototype = { }, genCSS: function (env, output) { output.add('-'); - this.value.genCSS(env, output) + this.value.genCSS(env, output); }, toCSS: tree.toCSS, eval: function (env) { diff --git a/lib/less/tree/operation.js b/lib/less/tree/operation.js index 8909f14f..ec806e29 100644 --- a/lib/less/tree/operation.js +++ b/lib/less/tree/operation.js @@ -43,7 +43,7 @@ tree.Operation.prototype = { if (this.isSpaced) { output.add(" "); } - this.operands[1].genCSS(env, output) + this.operands[1].genCSS(env, output); }, toCSS: tree.toCSS }; diff --git a/lib/less/tree/rule.js b/lib/less/tree/rule.js index e8fe085d..e9fe23ab 100644 --- a/lib/less/tree/rule.js +++ b/lib/less/tree/rule.js @@ -17,7 +17,7 @@ tree.Rule.prototype = { this.value = visitor.visit(this.value); }, genCSS: function (env, output) { - output.add(this.name + (env.compress ? ':' : ': ')); + output.add(this.name + (env.compress ? ':' : ': '), this.currentFileInfo, this.index); try { this.value.genCSS(env, output); } @@ -26,7 +26,7 @@ tree.Rule.prototype = { e.filename = this.currentFileInfo.filename; throw e; } - output.add(this.important + ((this.inline || (env.lastRule && env.compress)) ? "" : ";")); + output.add(this.important + ((this.inline || (env.lastRule && env.compress)) ? "" : ";"), this.currentFileInfo, this.index); }, toCSS: tree.toCSS, eval: function (env) { diff --git a/lib/less/tree/url.js b/lib/less/tree/url.js index b2b91e8b..7ba562fa 100644 --- a/lib/less/tree/url.js +++ b/lib/less/tree/url.js @@ -11,7 +11,7 @@ tree.URL.prototype = { }, genCSS: function (env, output) { output.add("url("); - this.value.genCSS(env, output) + this.value.genCSS(env, output); output.add(")"); }, toCSS: tree.toCSS, diff --git a/test/less-test.js b/test/less-test.js index b483c4fb..62a732b6 100644 --- a/test/less-test.js +++ b/test/less-test.js @@ -41,6 +41,8 @@ runTestSet({strictMath: true, dumpLineNumbers: 'all'}, "debug/", null, runTestSet({strictMath: true, relativeUrls: false, rootpath: "folder (1)/"}, "static-urls/"); runTestSet({strictMath: true, compress: true}, "compression/"); runTestSet({strictMath: false}, "legacy/"); +runTestSet({strictMath: true, strictUnits: true, sourceMap: true }, "sourcemaps/", + testSourcemap, null, null, function(filename) { return path.join('test/sourcemaps', filename) + '.json'; }); testNoOptions(); @@ -55,6 +57,18 @@ function getErrorPathReplacementFunction(dir) { }; } +function testSourcemap(name, err, compiledLess, doReplacements, sourcemap) { + fs.readFile(path.join('test/sourcemaps', name) + '.json', 'utf8', function (e, expectedSourcemap) { + sys.print("- " + name + ": "); + if (sourcemap === expectedSourcemap) { + ok('OK'); + } else { + difference("FAIL", expectedSourcemap, sourcemap); + } + sys.puts(""); + }); +} + function testErrors(name, err, compiledLess, doReplacements) { fs.readFile(path.join('test/less/', name) + '.txt', 'utf8', function (e, expectedErr) { sys.print("- " + name + ": "); @@ -96,7 +110,7 @@ function checkGlobalLeaks() { }); } -function runTestSet(options, foldername, verifyFunction, nameModifier, doReplacements) { +function runTestSet(options, foldername, verifyFunction, nameModifier, doReplacements, getFilename) { foldername = foldername || ""; if(!doReplacements) @@ -111,13 +125,20 @@ function runTestSet(options, foldername, verifyFunction, nameModifier, doReplace totalTests++; + if (options.sourceMap) { + var sourceMapOutput; + options.writeSourceMap = function(output) { + sourceMapOutput = output; + }; + } + toCSS(options, path.join('test/less/', foldername + file), function (err, less) { if (verifyFunction) { - return verifyFunction(name, err, less, doReplacements); + return verifyFunction(name, err, less, doReplacements, sourceMapOutput); } var css_name = name; - if(nameModifier) css_name=nameModifier(name); + if(nameModifier) { css_name = nameModifier(name); } fs.readFile(path.join('test/css', css_name) + '.css', 'utf8', function (e, css) { sys.print("- " + css_name + ": "); diff --git a/test/less/sourcemaps/basic.less b/test/less/sourcemaps/basic.less new file mode 100644 index 00000000..b064c722 --- /dev/null +++ b/test/less/sourcemaps/basic.less @@ -0,0 +1,9 @@ +.a() { + color: red; +} + +.b { + color: green; + .a(); + color: blue; +} \ No newline at end of file diff --git a/test/sourcemaps/basic.json b/test/sourcemaps/basic.json new file mode 100644 index 00000000..7191a765 --- /dev/null +++ b/test/sourcemaps/basic.json @@ -0,0 +1 @@ +// ha \ No newline at end of file From 536bfa273cdf8f1bf3f45b0a15862cb95bebf272 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 18 Jul 2013 22:40:59 +0100 Subject: [PATCH 26/34] tweaks and start of output --- lib/less/source-map-output.js | 7 +++++-- test/sourcemaps/basic.json | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/less/source-map-output.js b/lib/less/source-map-output.js index a86c7ec6..b8bd45e2 100644 --- a/lib/less/source-map-output.js +++ b/lib/less/source-map-output.js @@ -39,8 +39,11 @@ tree.sourceMapOutput.prototype.toCSS = function(env) { this._sourceMapGenerator = new sourceMap.SourceMapGenerator({ file:"outputFilenameTODO.css", sourceRoot:"http://blah.com/TODO" }); - for(var filename in this._contentsMap) { - this._sourceMapGenerator.setSourceContent(filename, this._contentsMap[filename]); + //TODO option to include source in sourcemaps? + if (this._outputSourceFiles) { + for(var filename in this._contentsMap) { + this._sourceMapGenerator.setSourceContent(filename, this._contentsMap[filename]); + } } this._rootNode.genCSS(env, this); diff --git a/test/sourcemaps/basic.json b/test/sourcemaps/basic.json index 7191a765..415d3e5a 100644 --- a/test/sourcemaps/basic.json +++ b/test/sourcemaps/basic.json @@ -1 +1 @@ -// ha \ No newline at end of file +{"version":3,"file":"basic.css","sources":["basic.less"],"names":[],"mappings":"EAIE,UAAA;aAJA,QAAA;sBAMA,SAAA","sourceRoot":""} \ No newline at end of file From 8c3e3049669b2ac79f9dbf18ffd374559dfeeb1b Mon Sep 17 00:00:00 2001 From: Luke Page Date: Fri, 19 Jul 2013 06:54:02 +0100 Subject: [PATCH 27/34] get the map file outputting and reference file from the end of the css file --- bin/lessc | 15 +++++++++++++-- lib/less/lessc_helper.js | 2 +- lib/less/parser.js | 3 ++- lib/less/source-map-output.js | 5 +++++ test/less-test.js | 2 +- 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/bin/lessc b/bin/lessc index e7388a37..a8511093 100755 --- a/bin/lessc +++ b/bin/lessc @@ -139,7 +139,9 @@ args = args.filter(function (arg) { } break; case 'source-map': - if (checkArgFunc(arg, match[2])) { + if (!match[2]) { + options.sourceMap = true; + } else { options.sourceMap = match[2]; } break; @@ -186,6 +188,14 @@ if (output) { } } +if (options.sourceMap === true) { + if (!output) { + sys.puts("the sourcemap option only has an optional filename if the css filename is given"); + return; + } + options.sourceMap = output + ".map"; +} + if (! input) { sys.puts("lessc: no input files"); sys.puts(""); @@ -250,7 +260,8 @@ var parseLessFile = function (e, data) { ieCompat: options.ieCompat, compress: options.compress, cleancss: options.cleancss, - sourceMap: options.sourceMap, + sourceMap: Boolean(options.sourceMap), + sourceMapFilename: options.sourceMap, writeSourceMap: writeSourceMap, maxLineLen: options.maxLineLen, strictMath: options.strictMath, diff --git a/lib/less/lessc_helper.js b/lib/less/lessc_helper.js index 0e711a77..6e857861 100644 --- a/lib/less/lessc_helper.js +++ b/lib/less/lessc_helper.js @@ -51,7 +51,7 @@ var lessc_helper = { sys.puts(" that will output the information within a fake"); sys.puts(" media query which is compatible with the SASS"); sys.puts(" format, and 'all' which will do both."); - sys.puts(" --source-map=FILENAME Outputs a sourcemap to the filename."); + sys.puts(" --source-map[=FILENAME] Outputs a v3 sourcemap to the filename (or output filename.map)"); sys.puts(" -rp, --rootpath=URL Set rootpath for url rewriting in relative imports and urls."); sys.puts(" Works with or without the relative-urls option."); sys.puts(" -ru, --relative-urls re-write relative urls to the base less file."); diff --git a/lib/less/parser.js b/lib/less/parser.js index cd8e12e8..e3f1405b 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -472,7 +472,8 @@ less.Parser = function Parser(env) { { writeSourceMap: options.writeSourceMap, rootNode: evaldRoot, - contentsMap: parser.imports.contents + contentsMap: parser.imports.contents, + sourceMapFilename: options.sourceMapFilename }); } diff --git a/lib/less/source-map-output.js b/lib/less/source-map-output.js index b8bd45e2..b3c4125e 100644 --- a/lib/less/source-map-output.js +++ b/lib/less/source-map-output.js @@ -6,6 +6,7 @@ this._rootNode = options.rootNode; this._writeSourceMap = options.writeSourceMap; this._contentsMap = options.contentsMap; + this._sourceMapFilename = options.sourceMapFilename; this._lineNumber = 0; this._column = 0; @@ -50,6 +51,10 @@ this._writeSourceMap(JSON.stringify(this._sourceMapGenerator.toJSON())); + if (this._sourceMapFilename) { + this._css.push("/*# sourceMappingURL=" + this._sourceMapFilename + " */"); + } + return this._css.join(''); }; diff --git a/test/less-test.js b/test/less-test.js index 62a732b6..ae8cb6ae 100644 --- a/test/less-test.js +++ b/test/less-test.js @@ -58,7 +58,7 @@ function getErrorPathReplacementFunction(dir) { } function testSourcemap(name, err, compiledLess, doReplacements, sourcemap) { - fs.readFile(path.join('test/sourcemaps', name) + '.json', 'utf8', function (e, expectedSourcemap) { + fs.readFile(path.join('test/', name) + '.json', 'utf8', function (e, expectedSourcemap) { sys.print("- " + name + ": "); if (sourcemap === expectedSourcemap) { ok('OK'); From 63109417c740d3e7d4c06b03463c9a97f359b10d Mon Sep 17 00:00:00 2001 From: Luke Page Date: Fri, 19 Jul 2013 19:26:24 +0100 Subject: [PATCH 28/34] small fixes to sourcemaps --- lib/less/extend-visitor.js | 3 ++- lib/less/parser.js | 4 ++-- lib/less/source-map-output.js | 2 +- lib/less/tree/element.js | 8 +++++--- lib/less/tree/media.js | 2 +- lib/less/tree/mixin.js | 2 +- lib/less/tree/ruleset.js | 4 ++-- lib/less/tree/selector.js | 2 +- test/less-test.js | 6 ++++++ 9 files changed, 21 insertions(+), 12 deletions(-) diff --git a/lib/less/extend-visitor.js b/lib/less/extend-visitor.js index 6d04ff1f..aead49f2 100644 --- a/lib/less/extend-visitor.js +++ b/lib/less/extend-visitor.js @@ -335,7 +335,8 @@ firstElement = new tree.Element( match.initialCombinator, replacementSelector.elements[0].value, - replacementSelector.elements[0].index + replacementSelector.elements[0].index, + replacementSelector.elements[0].currentFileInfo ); if (match.pathIndex > currentSelectorPathIndex && currentSelectorPathElementIndex > 0) { diff --git a/lib/less/parser.js b/lib/less/parser.js index e3f1405b..6a955769 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -928,7 +928,7 @@ less.Parser = function Parser(env) { save(); // stop us absorbing part of an invalid selector while (e = $(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)) { - elements.push(new(tree.Element)(c, e, i)); + elements.push(new(tree.Element)(c, e, i, env.currentFileInfo)); c = $('>'); } if ($('(')) { @@ -1167,7 +1167,7 @@ less.Parser = function Parser(env) { } } - if (e) { return new(tree.Element)(c, e, i); } + if (e) { return new(tree.Element)(c, e, i, env.currentFileInfo); } }, // diff --git a/lib/less/source-map-output.js b/lib/less/source-map-output.js index b3c4125e..07cb9e88 100644 --- a/lib/less/source-map-output.js +++ b/lib/less/source-map-output.js @@ -26,7 +26,7 @@ var inputSource = this._contentsMap[fileInfo.filename].substring(0, index); lines = inputSource.split("\n"); columns = lines[lines.length-1]; - this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber, column: this._column}, original: { line: lines.length - 1, column: columns.length}, source: fileInfo.filename}); + this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber + 1, column: this._column}, original: { line: lines.length, column: columns.length}, source: fileInfo.filename}); } lines = chunk.split("\n"); columns = lines[lines.length-1]; diff --git a/lib/less/tree/element.js b/lib/less/tree/element.js index b827d84e..1c25aeec 100644 --- a/lib/less/tree/element.js +++ b/lib/less/tree/element.js @@ -1,6 +1,6 @@ (function (tree) { -tree.Element = function (combinator, value, index) { +tree.Element = function (combinator, value, index, currentFileInfo) { this.combinator = combinator instanceof tree.Combinator ? combinator : new(tree.Combinator)(combinator); @@ -12,6 +12,7 @@ tree.Element = function (combinator, value, index) { this.value = ""; } this.index = index; + this.currentFileInfo = currentFileInfo; }; tree.Element.prototype = { type: "Element", @@ -22,10 +23,11 @@ tree.Element.prototype = { eval: function (env) { return new(tree.Element)(this.combinator, this.value.eval ? this.value.eval(env) : this.value, - this.index); + this.index, + this.currentFileInfo); }, genCSS: function (env, output) { - output.add(this.toCSS(env)); + output.add(this.toCSS(env), this.currentFileInfo, this.index); }, toCSS: function (env) { var value = (this.value.toCSS ? this.value.toCSS(env) : this.value); diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index 08198404..da8e03ec 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -63,7 +63,7 @@ tree.Media.prototype = { find: function () { return tree.Ruleset.prototype.find.apply(this.rules[0], arguments); }, rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.rules[0]); }, emptySelectors: function() { - var el = new(tree.Element)('', '&', 0); + var el = new(tree.Element)('', '&', this.index, this.currentFileInfo); return [new(tree.Selector)([el], null, null, this.index, this.currentFileInfo)]; }, markReferenced: function () { diff --git a/lib/less/tree/mixin.js b/lib/less/tree/mixin.js index fbb6780e..470bce34 100644 --- a/lib/less/tree/mixin.js +++ b/lib/less/tree/mixin.js @@ -88,7 +88,7 @@ tree.mixin.Call.prototype = { tree.mixin.Definition = function (name, params, rules, condition, variadic) { this.name = name; - this.selectors = [new(tree.Selector)([new(tree.Element)(null, name)])]; + this.selectors = [new(tree.Selector)([new(tree.Element)(null, name, this.index, this.currentFileInfo)])]; this.params = params; this.condition = condition; this.variadic = variadic; diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 8294dbb0..e6034c3a 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -359,7 +359,7 @@ tree.Ruleset.prototype = { // it is not lost if (sel.length > 0) { sel[0].elements = sel[0].elements.slice(0); - sel[0].elements.push(new(tree.Element)(el.combinator, '', 0)); //new Element(el.Combinator, "")); + sel[0].elements.push(new(tree.Element)(el.combinator, '', 0, el.index, el.currentFileInfo)); } selectorsMultiplied.push(sel); } @@ -397,7 +397,7 @@ tree.Ruleset.prototype = { newJoinedSelectorEmpty = false; // join the elements so far with the first part of the parent - newJoinedSelector.elements.push(new(tree.Element)(el.combinator, parentSel[0].elements[0].value, 0)); + newJoinedSelector.elements.push(new(tree.Element)(el.combinator, parentSel[0].elements[0].value, el.index, el.currentFileInfo)); newJoinedSelector.elements = newJoinedSelector.elements.concat(parentSel[0].elements.slice(1)); } diff --git a/lib/less/tree/selector.js b/lib/less/tree/selector.js index 1e10be14..ac38b993 100644 --- a/lib/less/tree/selector.js +++ b/lib/less/tree/selector.js @@ -57,7 +57,7 @@ tree.Selector.prototype = { genCSS: function (env, output) { var i, element; if ((!env || !env.firstSelector) && this.elements[0].combinator.value === "") { - output.add(' '); + output.add(' ', this.currentFileInfo, this.index); } if (!this._css) { //TODO caching? speed comparison? diff --git a/test/less-test.js b/test/less-test.js index ae8cb6ae..f5b7174b 100644 --- a/test/less-test.js +++ b/test/less-test.js @@ -62,6 +62,12 @@ function testSourcemap(name, err, compiledLess, doReplacements, sourcemap) { sys.print("- " + name + ": "); if (sourcemap === expectedSourcemap) { ok('OK'); + } else if (err) { + fail("ERROR: " + (err && err.message)); + if (isVerbose) { + console.error(); + console.error(err.stack); + } } else { difference("FAIL", expectedSourcemap, sourcemap); } From fc35190d385d6b079586bd7e016a8a0679dc19ba Mon Sep 17 00:00:00 2001 From: Luke Page Date: Sat, 20 Jul 2013 22:44:13 +0100 Subject: [PATCH 29/34] unused variable cleanup --- .jshintrc | 4 +++- lib/less/index.js | 7 +------ lib/less/parser.js | 32 +++++++++++++------------------- lib/less/rhino.js | 4 ++-- lib/less/tree/dimension.js | 2 +- lib/less/tree/import.js | 2 -- lib/less/tree/mixin.js | 4 ++-- lib/less/tree/ruleset.js | 2 +- lib/less/visitor.js | 3 +-- test/browser-test-prepare.js | 3 +-- test/less-test.js | 2 +- 11 files changed, 26 insertions(+), 39 deletions(-) diff --git a/.jshintrc b/.jshintrc index 2265f328..2f830954 100644 --- a/.jshintrc +++ b/.jshintrc @@ -3,5 +3,7 @@ "boss": true, "expr": true, "laxbreak": true, - "node": true + "node": true, + "unused": "vars", + "noarg": true } diff --git a/lib/less/index.js b/lib/less/index.js index 30738d3c..cb95ca88 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -146,12 +146,7 @@ less.Parser.fileLoader = function (file, currentFileInfo, callback, env) { } var urlStr = isUrl ? file : url.resolve(currentFileInfo.currentDirectory, file), - urlObj = url.parse(urlStr), - req = { - host: urlObj.hostname, - port: urlObj.port || 80, - path: urlObj.pathname + (urlObj.search||'') - }; + urlObj = url.parse(urlStr); request.get(urlStr, function (error, res, body) { if (res.statusCode === 404) { diff --git a/lib/less/parser.js b/lib/less/parser.js index 249a10ed..de37ec34 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1,6 +1,5 @@ var less, tree; -/*global environment */ // Node.js does not have a header file added which defines less if (less === undefined) { less = exports; @@ -51,8 +50,6 @@ less.Parser = function Parser(env) { current, // index of current chunk, in `input` parser; - var that = this; - // Top parser on an import tree must be sure there is one "env" // which will then be passed around by reference. if (!(env instanceof tree.parseEnv)) { @@ -128,7 +125,7 @@ less.Parser = function Parser(env) { // Parse from a token, regexp or string, and move forward if match // function $(tok) { - var match, args, length, index, k; + var match, length; // // Non-terminal @@ -300,7 +297,7 @@ less.Parser = function Parser(env) { // call `callback` when done. // parse: function (str, callback) { - var root, start, end, zone, line, lines, buff = [], c, error = null; + var root, line, lines, error = null; i = j = current = furthest = 0; input = str.replace(/\r\n/g, '\n'); @@ -418,12 +415,9 @@ less.Parser = function Parser(env) { } root.toCSS = (function (evaluate) { - var line, lines, column; - return function (options, variables) { options = options || {}; - var importError, - evaldRoot, + var evaldRoot, css, evalEnv = new tree.evalEnv(options); @@ -779,7 +773,7 @@ less.Parser = function Parser(env) { // A variable entity useing the protective {} e.g. @{var} variableCurly: function () { - var name, curly, index = i; + var curly, index = i; if (input.charAt(i) === '@' && (curly = $(/^@\{([\w-]+)\}/))) { return new(tree.Variable)("@" + curly[1], index, env.currentFileInfo); @@ -921,7 +915,7 @@ less.Parser = function Parser(env) { // selector for now. // call: function () { - var elements = [], e, c, args, delim, arg, index = i, s = input.charAt(i), important = false; + var elements = [], e, c, args, index = i, s = input.charAt(i), important = false; if (s !== '.' && s !== '#') { return; } @@ -1065,7 +1059,7 @@ less.Parser = function Parser(env) { // the `{...}` block. // definition: function () { - var name, params = [], match, ruleset, param, value, cond, variadic = false; + var name, params = [], match, ruleset, cond, variadic = false; if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') || peek(/^[^{]*\}/)) { return; @@ -1151,7 +1145,7 @@ less.Parser = function Parser(env) { // and an element name, such as a tag a class, or `*`. // element: function () { - var e, t, c, v; + var e, c, v; c = $(this.combinator); @@ -1208,7 +1202,7 @@ less.Parser = function Parser(env) { // Selectors are made out of one or more Elements, see above. // selector: function (isLess) { - var sel, e, elements = [], c, extend, extendList = [], when, condition; + var e, elements = [], c, extend, extendList = [], when, condition; while ((isLess && (extend = $(this.extend))) || (isLess && (when = $(/^when/))) || (e = $(this.element))) { if (when) { @@ -1234,7 +1228,7 @@ less.Parser = function Parser(env) { if (extendList.length) { error("Extend must be used to extend a selector, it cannot be used on its own"); } }, attribute: function () { - var attr = '', key, val, op; + var key, val, op; if (! $('[')) { return; } @@ -1467,7 +1461,7 @@ less.Parser = function Parser(env) { // @charset "utf-8"; // directive: function () { - var name, value, rules, identifier, e, nodes, nonVendorSpecificName, + var name, value, rules, nonVendorSpecificName, hasBlock, hasIdentifier, hasExpression; if (input.charAt(i) !== '@') { return; } @@ -1553,7 +1547,7 @@ less.Parser = function Parser(env) { // and before the `;`. // value: function () { - var e, expressions = [], important; + var e, expressions = []; while (e = $(this.expression)) { expressions.push(e); @@ -1582,7 +1576,7 @@ less.Parser = function Parser(env) { } }, multiplication: function () { - var m, a, op, operation, isSpaced, expression = []; + var m, a, op, operation, isSpaced; if (m = $(this.operand)) { isSpaced = isWhitespace(input.charAt(i - 1)); while (!peek(/^\/[*\/]/) && (op = ($('/') || $('*')))) { @@ -1670,7 +1664,7 @@ less.Parser = function Parser(env) { // @var * 2 // expression: function () { - var e, delim, entities = [], d; + var e, delim, entities = []; while (e = $(this.addition) || $(this.entity)) { entities.push(e); diff --git a/lib/less/rhino.js b/lib/less/rhino.js index 08eba88a..5feb2e74 100644 --- a/lib/less/rhino.js +++ b/lib/less/rhino.js @@ -1,5 +1,5 @@ -/*jshint rhino:true */ -/*global name:true, less */ +/*jshint rhino:true, unused: false */ +/*global name:true, less, loadStyleSheet */ var name; function loadStyleSheet(sheet, callback, reload, remaining) { diff --git a/lib/less/tree/dimension.js b/lib/less/tree/dimension.js index 2097e128..7d5044d9 100644 --- a/lib/less/tree/dimension.js +++ b/lib/less/tree/dimension.js @@ -113,7 +113,7 @@ tree.Dimension.prototype = { convertTo: function (conversions) { var value = this.value, unit = this.unit.clone(), - i, groupName, group, conversion, targetUnit, derivedConversions = {}, applyUnit; + i, groupName, group, targetUnit, derivedConversions = {}, applyUnit; if (typeof conversions === 'string') { for(i in tree.UnitConversions) { diff --git a/lib/less/tree/import.js b/lib/less/tree/import.js index b917d6c0..8a8326f6 100644 --- a/lib/less/tree/import.js +++ b/lib/less/tree/import.js @@ -12,8 +12,6 @@ // the file has been fetched, and parsed. // tree.Import = function (path, features, options, index, currentFileInfo) { - var that = this; - this.options = options; this.index = index; this.path = path; diff --git a/lib/less/tree/mixin.js b/lib/less/tree/mixin.js index 470bce34..cd8bf173 100644 --- a/lib/less/tree/mixin.js +++ b/lib/less/tree/mixin.js @@ -193,7 +193,7 @@ tree.mixin.Definition.prototype = { var _arguments = [], mixinFrames = this.frames.concat(env.frames), frame = this.evalParams(env, new(tree.evalEnv)(env, mixinFrames), args, _arguments), - context, rules, start, ruleset; + rules, ruleset; frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env))); @@ -216,7 +216,7 @@ tree.mixin.Definition.prototype = { return true; }, matchArgs: function (args, env) { - var argsLength = (args && args.length) || 0, len, frame; + var argsLength = (args && args.length) || 0, len; if (! this.variadic) { if (argsLength < this.required) { return false; } diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index e6034c3a..25a92e38 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -435,7 +435,7 @@ tree.Ruleset.prototype = { }, mergeElementsOnToSelectors: function(elements, selectors) { - var i, sel, extendList; + var i, sel; if (selectors.length === 0) { selectors.push([ new(tree.Selector)(elements) ]); diff --git a/lib/less/visitor.js b/lib/less/visitor.js index 5a4cd402..52f734f1 100644 --- a/lib/less/visitor.js +++ b/lib/less/visitor.js @@ -35,12 +35,11 @@ return node; }, visitArray: function(nodes) { - var i, newNodes = [], visitor = this; + var i, newNodes = []; for(i = 0; i < nodes.length; i++) { var evald = this.visit(nodes[i]); if (evald instanceof Array) { evald = this.flatten(evald); - //evald.forEach(this.doAccept, this); newNodes = newNodes.concat(evald); } else { newNodes.push(evald); diff --git a/test/browser-test-prepare.js b/test/browser-test-prepare.js index 644ac6ff..95a99703 100644 --- a/test/browser-test-prepare.js +++ b/test/browser-test-prepare.js @@ -1,6 +1,5 @@ var path = require('path'), - fs = require('fs'), - sys = require('util'); + fs = require('fs'); var readDirFilesSync = function(dir, regex, callback) { fs.readdirSync(dir).forEach(function (file) { diff --git a/test/less-test.js b/test/less-test.js index bb787131..a1e10be8 100644 --- a/test/less-test.js +++ b/test/less-test.js @@ -220,7 +220,7 @@ function endTest() { } function toCSS(options, path, callback) { - var tree, css; + var css; options = options || {}; fs.readFile(path, 'utf8', function (e, str) { if (e) { return callback(e); } From b2a445c46cb6c30f57b2190d0b7427fbf9530ccc Mon Sep 17 00:00:00 2001 From: Luke Page Date: Sat, 20 Jul 2013 23:02:28 +0100 Subject: [PATCH 30/34] pass more lines and columns to the sourcemap generator. start passing the filenames to the sourcemap generator. --- bin/lessc | 2 ++ lib/less/parser.js | 3 ++- lib/less/source-map-output.js | 3 ++- lib/less/tree/call.js | 2 +- lib/less/tree/comment.js | 2 +- lib/less/tree/directive.js | 2 +- lib/less/tree/import.js | 2 +- lib/less/tree/media.js | 2 +- lib/less/tree/quoted.js | 2 +- test/less-test.js | 1 + 10 files changed, 13 insertions(+), 8 deletions(-) diff --git a/bin/lessc b/bin/lessc index a8511093..820c4aad 100755 --- a/bin/lessc +++ b/bin/lessc @@ -186,6 +186,7 @@ if (output) { if (warningMessages) { sys.puts(warningMessages); } + options.sourceMapOutputFilename = output; } if (options.sourceMap === true) { @@ -262,6 +263,7 @@ var parseLessFile = function (e, data) { cleancss: options.cleancss, sourceMap: Boolean(options.sourceMap), sourceMapFilename: options.sourceMap, + sourceMapOutputFilename: options.sourceMapOutputFilename, writeSourceMap: writeSourceMap, maxLineLen: options.maxLineLen, strictMath: options.strictMath, diff --git a/lib/less/parser.js b/lib/less/parser.js index de37ec34..37d26a97 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -467,7 +467,8 @@ less.Parser = function Parser(env) { writeSourceMap: options.writeSourceMap, rootNode: evaldRoot, contentsMap: parser.imports.contents, - sourceMapFilename: options.sourceMapFilename + sourceMapFilename: options.sourceMapFilename, + outputFilename: options.sourceMapOutputFilename }); } diff --git a/lib/less/source-map-output.js b/lib/less/source-map-output.js index 07cb9e88..45331829 100644 --- a/lib/less/source-map-output.js +++ b/lib/less/source-map-output.js @@ -7,6 +7,7 @@ this._writeSourceMap = options.writeSourceMap; this._contentsMap = options.contentsMap; this._sourceMapFilename = options.sourceMapFilename; + this._outputFilename = options.outputFilename; this._lineNumber = 0; this._column = 0; @@ -38,7 +39,7 @@ }; tree.sourceMapOutput.prototype.toCSS = function(env) { - this._sourceMapGenerator = new sourceMap.SourceMapGenerator({ file:"outputFilenameTODO.css", sourceRoot:"http://blah.com/TODO" }); + this._sourceMapGenerator = new sourceMap.SourceMapGenerator({ file: this._outputFilename, sourceRoot: null }); //TODO option to include source in sourcemaps? if (this._outputSourceFiles) { diff --git a/lib/less/tree/call.js b/lib/less/tree/call.js index dc8da72a..5e488831 100644 --- a/lib/less/tree/call.js +++ b/lib/less/tree/call.js @@ -52,7 +52,7 @@ tree.Call.prototype = { }, genCSS: function (env, output) { - output.add(this.name + "("); + output.add(this.name + "(", this.index, this.currentFileInfo); for(var i = 0; i < this.args.length; i++) { this.args[i].genCSS(env, output); diff --git a/lib/less/tree/comment.js b/lib/less/tree/comment.js index 8998b961..67167092 100644 --- a/lib/less/tree/comment.js +++ b/lib/less/tree/comment.js @@ -9,7 +9,7 @@ tree.Comment.prototype = { type: "Comment", genCSS: function (env, output) { if (this.debugInfo) { - output.add(tree.debugInfo(env, this)); + output.add(tree.debugInfo(env, this), this.index, this.currentFileInfo); } output.add(this.value.trim()); //TODO shouldn't need to trim, we shouldn't grab the \n }, diff --git a/lib/less/tree/directive.js b/lib/less/tree/directive.js index 9f2a0f5e..7808bfe4 100644 --- a/lib/less/tree/directive.js +++ b/lib/less/tree/directive.js @@ -18,7 +18,7 @@ tree.Directive.prototype = { this.value = visitor.visit(this.value); }, genCSS: function (env, output) { - output.add(this.name); + output.add(this.name, this.index, this.currentFileInfo); if (this.rules) { tree.outputRuleset(env, output, this.rules); } else { diff --git a/lib/less/tree/import.js b/lib/less/tree/import.js index 8a8326f6..0c6f1188 100644 --- a/lib/less/tree/import.js +++ b/lib/less/tree/import.js @@ -48,7 +48,7 @@ tree.Import.prototype = { }, genCSS: function (env, output) { if (this.css) { - output.add("@import "); + output.add("@import ", this.index, this.currentFileInfo); this.path.genCSS(env, output); if (this.features) { output.add(" "); diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index da8e03ec..b3148261 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -17,7 +17,7 @@ tree.Media.prototype = { this.rules = visitor.visit(this.rules); }, genCSS: function (env, output) { - output.add('@media '); + output.add('@media ', this.index, this.currentFileInfo); this.features.genCSS(env, output); tree.outputRuleset(env, output, this.rules); }, diff --git a/lib/less/tree/quoted.js b/lib/less/tree/quoted.js index e78accad..59931ac0 100644 --- a/lib/less/tree/quoted.js +++ b/lib/less/tree/quoted.js @@ -11,7 +11,7 @@ tree.Quoted.prototype = { type: "Quoted", genCSS: function (env, output) { if (!this.escaped) { - output.add(this.quote); + output.add(this.quote, this.index, this.currentFileInfo); } output.add(this.value); if (!this.escaped) { diff --git a/test/less-test.js b/test/less-test.js index a1e10be8..2f7c144c 100644 --- a/test/less-test.js +++ b/test/less-test.js @@ -136,6 +136,7 @@ function runTestSet(options, foldername, verifyFunction, nameModifier, doReplace options.writeSourceMap = function(output) { sourceMapOutput = output; }; + options.sourceMapOutputFilename = name + ".css"; } toCSS(options, path.join('test/less/', foldername + file), function (err, less) { From f16e5142cf3b01c490395c36dfe7f851053fa3db Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 24 Jul 2013 13:46:20 +0100 Subject: [PATCH 31/34] test some more advanced sourcemaps and correct a bug in the output column --- lib/less/source-map-output.js | 8 ++++++-- test/less/sourcemaps/basic.less | 17 +++++++++++++++++ test/sourcemaps/basic.json | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/less/source-map-output.js b/lib/less/source-map-output.js index 45331829..35abd5a6 100644 --- a/lib/less/source-map-output.js +++ b/lib/less/source-map-output.js @@ -32,8 +32,12 @@ lines = chunk.split("\n"); columns = lines[lines.length-1]; - this._lineNumber += lines.length - 1; - this._column += columns.length - 1; + if (lines.length === 1) { + this._column += columns.length; + } else { + this._lineNumber += lines.length - 1; + this._column = columns.length; + } this._css.push(chunk); }; diff --git a/test/less/sourcemaps/basic.less b/test/less/sourcemaps/basic.less index b064c722..a3f4bbc5 100644 --- a/test/less/sourcemaps/basic.less +++ b/test/less/sourcemaps/basic.less @@ -1,3 +1,5 @@ +@var: black; + .a() { color: red; } @@ -6,4 +8,19 @@ color: green; .a(); color: blue; + background: @var; +} + +.a, .b { + background: green; + .c, .d { + background: gray; + & + & { + color: red; + } + } +} + +.extend:extend(.a all) { + color: pink; } \ No newline at end of file diff --git a/test/sourcemaps/basic.json b/test/sourcemaps/basic.json index 415d3e5a..8d678657 100644 --- a/test/sourcemaps/basic.json +++ b/test/sourcemaps/basic.json @@ -1 +1 @@ -{"version":3,"file":"basic.css","sources":["basic.less"],"names":[],"mappings":"EAIE,UAAA;aAJA,QAAA;sBAMA,SAAA","sourceRoot":""} \ No newline at end of file +{"version":3,"file":"sourcemaps/basic.css","sources":["sourcemaps/basic.less"],"names":[],"mappings":"AAMG;EACD,YAAA;EAJA,UAAA;EAWA,iBAAA;EALA,WAAA;EACA,mBAAA;;AAJC,EASC;AATD,EASM;EACL,gBAAA;;AACE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;EAGH,UAAA;;AALJ;AAAK;AAUA;EATL,iBAAA;;AADA,EAEE;AAFG,EAEH;AAFF,EAEO;AAFF,EAEE;AAQF,OARH;AAQG,OARE;EACL,gBAAA;;AACE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFD;AAQF,OARH,GAQG,UARH;AAQG,OARH,GAEM,KAFN;AAQG,OARH,GAQG,UARE;AAQF,OARH,GAEM,KAFD;AAEH,EAFF,GAQG,UARH;AAEE,EAFF,GAQG,UARE;AAQF,OARE,GAQF,UARH;AAQG,OARE,GAEC,KAFN;AAQG,OARE,GAQF,UARE;AAQF,OARE,GAEC,KAFD;AAEH,EAFG,GAQF,UARH;AAEE,EAFG,GAQF,UARE;EAGH,UAAA;;AAKC;EACL,WAAA,"sourceRoot":""} \ No newline at end of file From 37c4e1126a1914f21a600318eac621b0a5abc619 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Fri, 26 Jul 2013 17:06:04 +0100 Subject: [PATCH 32/34] correct some paths --- bin/lessc | 7 +++++-- lib/less/parser.js | 3 ++- lib/less/source-map-output.js | 17 +++++++++++++++-- test/less-test.js | 1 + test/sourcemaps/basic.json | 2 +- 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/bin/lessc b/bin/lessc index 820c4aad..f7c0d82c 100755 --- a/bin/lessc +++ b/bin/lessc @@ -182,19 +182,21 @@ if (input && input != '-') { var output = args[2]; var outputbase = args[2]; if (output) { + options.sourceMapOutputFilename = output; output = path.resolve(process.cwd(), output); if (warningMessages) { sys.puts(warningMessages); } - options.sourceMapOutputFilename = output; } +options.sourceMapRootpath = process.cwd(); + if (options.sourceMap === true) { if (!output) { sys.puts("the sourcemap option only has an optional filename if the css filename is given"); return; } - options.sourceMap = output + ".map"; + options.sourceMap = options.sourceMapOutputFilename + ".map"; } if (! input) { @@ -264,6 +266,7 @@ var parseLessFile = function (e, data) { sourceMap: Boolean(options.sourceMap), sourceMapFilename: options.sourceMap, sourceMapOutputFilename: options.sourceMapOutputFilename, + sourceMapRootpath: options.sourceMapRootpath, writeSourceMap: writeSourceMap, maxLineLen: options.maxLineLen, strictMath: options.strictMath, diff --git a/lib/less/parser.js b/lib/less/parser.js index 37d26a97..abd40978 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -468,7 +468,8 @@ less.Parser = function Parser(env) { rootNode: evaldRoot, contentsMap: parser.imports.contents, sourceMapFilename: options.sourceMapFilename, - outputFilename: options.sourceMapOutputFilename + outputFilename: options.sourceMapOutputFilename, + sourceMapRootpath: options.sourceMapRootpath }); } diff --git a/lib/less/source-map-output.js b/lib/less/source-map-output.js index 35abd5a6..b356550f 100644 --- a/lib/less/source-map-output.js +++ b/lib/less/source-map-output.js @@ -8,11 +8,22 @@ this._contentsMap = options.contentsMap; this._sourceMapFilename = options.sourceMapFilename; this._outputFilename = options.outputFilename; + this._sourceMapRootpath = options.sourceMapRootpath; this._lineNumber = 0; this._column = 0; }; + tree.sourceMapOutput.prototype.normalizeFilename = function(filename) { + if (this._sourceMapRootpath && filename.indexOf(this._sourceMapRootpath) === 0) { + filename = filename.substring(this._sourceMapRootpath.length); + if (filename.charAt(0) === '\\' || filename.charAt(0) === '/') { + filename = filename.substring(1); + } + } + return filename.replace(/\\/g, '/'); + }; + tree.sourceMapOutput.prototype.add = function(chunk, fileInfo, index) { if (!chunk) { @@ -27,7 +38,9 @@ var inputSource = this._contentsMap[fileInfo.filename].substring(0, index); lines = inputSource.split("\n"); columns = lines[lines.length-1]; - this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber + 1, column: this._column}, original: { line: lines.length, column: columns.length}, source: fileInfo.filename}); + this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber + 1, column: this._column}, + original: { line: lines.length, column: columns.length}, + source: this.normalizeFilename(fileInfo.filename)}); } lines = chunk.split("\n"); columns = lines[lines.length-1]; @@ -48,7 +61,7 @@ //TODO option to include source in sourcemaps? if (this._outputSourceFiles) { for(var filename in this._contentsMap) { - this._sourceMapGenerator.setSourceContent(filename, this._contentsMap[filename]); + this._sourceMapGenerator.setSourceContent(this.normalizeFilename(filename), this._contentsMap[filename]); } } diff --git a/test/less-test.js b/test/less-test.js index 2f7c144c..d716916c 100644 --- a/test/less-test.js +++ b/test/less-test.js @@ -137,6 +137,7 @@ function runTestSet(options, foldername, verifyFunction, nameModifier, doReplace sourceMapOutput = output; }; options.sourceMapOutputFilename = name + ".css"; + options.sourceMapRootpath = path.join(process.cwd(), "test/less"); } toCSS(options, path.join('test/less/', foldername + file), function (err, less) { diff --git a/test/sourcemaps/basic.json b/test/sourcemaps/basic.json index 8d678657..a2b38c45 100644 --- a/test/sourcemaps/basic.json +++ b/test/sourcemaps/basic.json @@ -1 +1 @@ -{"version":3,"file":"sourcemaps/basic.css","sources":["sourcemaps/basic.less"],"names":[],"mappings":"AAMG;EACD,YAAA;EAJA,UAAA;EAWA,iBAAA;EALA,WAAA;EACA,mBAAA;;AAJC,EASC;AATD,EASM;EACL,gBAAA;;AACE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;EAGH,UAAA;;AALJ;AAAK;AAUA;EATL,iBAAA;;AADA,EAEE;AAFG,EAEH;AAFF,EAEO;AAFF,EAEE;AAQF,OARH;AAQG,OARE;EACL,gBAAA;;AACE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFD;AAQF,OARH,GAQG,UARH;AAQG,OARH,GAEM,KAFN;AAQG,OARH,GAQG,UARE;AAQF,OARH,GAEM,KAFD;AAEH,EAFF,GAQG,UARH;AAEE,EAFF,GAQG,UARE;AAQF,OARE,GAQF,UARH;AAQG,OARE,GAEC,KAFN;AAQG,OARE,GAQF,UARE;AAQF,OARE,GAEC,KAFD;AAEH,EAFG,GAQF,UARH;AAEE,EAFG,GAQF,UARE;EAGH,UAAA;;AAKC;EACL,WAAA,"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"sourcemaps/basic.css","sources":["sourcemaps/basic.less"],"names":[],"mappings":"AAMG;EACD,YAAA;EAJA,UAAA;EAWA,iBAAA;EALA,WAAA;EACA,mBAAA;;AAJC,EASC;AATD,EASM;EACL,gBAAA;;AACE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;EAGH,UAAA;;AALJ;AAAK;AAUA;EATL,iBAAA;;AADA,EAEE;AAFG,EAEH;AAFF,EAEO;AAFF,EAEE;AAQF,OARH;AAQG,OARE;EACL,gBAAA;;AACE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFD;AAQF,OARH,GAQG,UARH;AAQG,OARH,GAEM,KAFN;AAQG,OARH,GAQG,UARE;AAQF,OARH,GAEM,KAFD;AAEH,EAFF,GAQG,UARH;AAEE,EAFF,GAQG,UARE;AAQF,OARE,GAQF,UARH;AAQG,OARE,GAEC,KAFN;AAQG,OARE,GAQF,UARE;AAQF,OARE,GAEC,KAFD;AAEH,EAFG,GAQF,UARH;AAEE,EAFG,GAQF,UARE;EAGH,UAAA;;AAKC;EACL,WAAA"} \ No newline at end of file From 1563d5c0874e2e3bece3fbf089a9bb149ab9c217 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 31 Jul 2013 21:01:15 +0100 Subject: [PATCH 33/34] sourcemaps: rename rootpath to basepath and add a rootpath option --- bin/lessc | 9 +++++++-- lib/less/parser.js | 1 + lib/less/source-map-output.js | 13 +++++++++---- test/less-test.js | 3 ++- test/sourcemaps/basic.json | 2 +- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/bin/lessc b/bin/lessc index f7c0d82c..021c0074 100755 --- a/bin/lessc +++ b/bin/lessc @@ -145,6 +145,10 @@ args = args.filter(function (arg) { options.sourceMap = match[2]; } break; + case 'source-map-rootpath': + if (checkArgFunc(arg, match[2])) { + options.sourceMapRootpath = match[2]; + } case 'rp': case 'rootpath': if (checkArgFunc(arg, match[2])) { @@ -189,7 +193,7 @@ if (output) { } } -options.sourceMapRootpath = process.cwd(); +options.sourceMapBasepath = process.cwd(); if (options.sourceMap === true) { if (!output) { @@ -266,7 +270,8 @@ var parseLessFile = function (e, data) { sourceMap: Boolean(options.sourceMap), sourceMapFilename: options.sourceMap, sourceMapOutputFilename: options.sourceMapOutputFilename, - sourceMapRootpath: options.sourceMapRootpath, + sourceMapBasepath: options.sourceMapBasepath, + sourceMapRootpath: options.sourceMapRootpath || "", writeSourceMap: writeSourceMap, maxLineLen: options.maxLineLen, strictMath: options.strictMath, diff --git a/lib/less/parser.js b/lib/less/parser.js index abd40978..5546c128 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -469,6 +469,7 @@ less.Parser = function Parser(env) { contentsMap: parser.imports.contents, sourceMapFilename: options.sourceMapFilename, outputFilename: options.sourceMapOutputFilename, + sourceMapBasepath: options.sourceMapBasepath, sourceMapRootpath: options.sourceMapRootpath }); } diff --git a/lib/less/source-map-output.js b/lib/less/source-map-output.js index b356550f..6f911b95 100644 --- a/lib/less/source-map-output.js +++ b/lib/less/source-map-output.js @@ -8,20 +8,25 @@ this._contentsMap = options.contentsMap; this._sourceMapFilename = options.sourceMapFilename; this._outputFilename = options.outputFilename; + this._sourceMapBasepath = options.sourceMapBasepath; this._sourceMapRootpath = options.sourceMapRootpath; + if (this._sourceMapRootpath && this._sourceMapRootpath.charAt(this._sourceMapRootpath.length-1) !== '/') { + this._sourceMapRootpath += '/'; + } + this._lineNumber = 0; this._column = 0; }; tree.sourceMapOutput.prototype.normalizeFilename = function(filename) { - if (this._sourceMapRootpath && filename.indexOf(this._sourceMapRootpath) === 0) { - filename = filename.substring(this._sourceMapRootpath.length); + if (this._sourceMapBasepath && filename.indexOf(this._sourceMapBasepath) === 0) { + filename = filename.substring(this._sourceMapBasepath.length); if (filename.charAt(0) === '\\' || filename.charAt(0) === '/') { filename = filename.substring(1); } } - return filename.replace(/\\/g, '/'); + return this._sourceMapRootpath + filename.replace(/\\/g, '/'); }; tree.sourceMapOutput.prototype.add = function(chunk, fileInfo, index) { @@ -70,7 +75,7 @@ this._writeSourceMap(JSON.stringify(this._sourceMapGenerator.toJSON())); if (this._sourceMapFilename) { - this._css.push("/*# sourceMappingURL=" + this._sourceMapFilename + " */"); + this._css.push("/*# sourceMappingURL=" + this._sourceMapRootpath + this._sourceMapFilename + " */"); } return this._css.join(''); diff --git a/test/less-test.js b/test/less-test.js index d716916c..bc00bc6c 100644 --- a/test/less-test.js +++ b/test/less-test.js @@ -137,7 +137,8 @@ function runTestSet(options, foldername, verifyFunction, nameModifier, doReplace sourceMapOutput = output; }; options.sourceMapOutputFilename = name + ".css"; - options.sourceMapRootpath = path.join(process.cwd(), "test/less"); + options.sourceMapBasepath = path.join(process.cwd(), "test/less"); + options.sourceMapRootpath = "testweb/"; } toCSS(options, path.join('test/less/', foldername + file), function (err, less) { diff --git a/test/sourcemaps/basic.json b/test/sourcemaps/basic.json index a2b38c45..50e568d5 100644 --- a/test/sourcemaps/basic.json +++ b/test/sourcemaps/basic.json @@ -1 +1 @@ -{"version":3,"file":"sourcemaps/basic.css","sources":["sourcemaps/basic.less"],"names":[],"mappings":"AAMG;EACD,YAAA;EAJA,UAAA;EAWA,iBAAA;EALA,WAAA;EACA,mBAAA;;AAJC,EASC;AATD,EASM;EACL,gBAAA;;AACE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;EAGH,UAAA;;AALJ;AAAK;AAUA;EATL,iBAAA;;AADA,EAEE;AAFG,EAEH;AAFF,EAEO;AAFF,EAEE;AAQF,OARH;AAQG,OARE;EACL,gBAAA;;AACE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFD;AAQF,OARH,GAQG,UARH;AAQG,OARH,GAEM,KAFN;AAQG,OARH,GAQG,UARE;AAQF,OARH,GAEM,KAFD;AAEH,EAFF,GAQG,UARH;AAEE,EAFF,GAQG,UARE;AAQF,OARE,GAQF,UARH;AAQG,OARE,GAEC,KAFN;AAQG,OARE,GAQF,UARE;AAQF,OARE,GAEC,KAFD;AAEH,EAFG,GAQF,UARH;AAEE,EAFG,GAQF,UARE;EAGH,UAAA;;AAKC;EACL,WAAA"} \ No newline at end of file +{"version":3,"file":"sourcemaps/basic.css","sources":["testweb/sourcemaps/basic.less"],"names":[],"mappings":"AAMG;EACD,YAAA;EAJA,UAAA;EAWA,iBAAA;EALA,WAAA;EACA,mBAAA;;AAJC,EASC;AATD,EASM;EACL,gBAAA;;AACE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;EAGH,UAAA;;AALJ;AAAK;AAUA;EATL,iBAAA;;AADA,EAEE;AAFG,EAEH;AAFF,EAEO;AAFF,EAEE;AAQF,OARH;AAQG,OARE;EACL,gBAAA;;AACE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFD;AAQF,OARH,GAQG,UARH;AAQG,OARH,GAEM,KAFN;AAQG,OARH,GAQG,UARE;AAQF,OARH,GAEM,KAFD;AAEH,EAFF,GAQG,UARH;AAEE,EAFF,GAQG,UARE;AAQF,OARE,GAQF,UARH;AAQG,OARE,GAEC,KAFN;AAQG,OARE,GAQF,UARE;AAQF,OARE,GAEC,KAFD;AAEH,EAFG,GAQF,UARH;AAEE,EAFG,GAQF,UARE;EAGH,UAAA;;AAKC;EACL,WAAA"} \ No newline at end of file From 969e70a573cbfeae1ff77163b534983af9323393 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 31 Jul 2013 22:11:53 +0100 Subject: [PATCH 34/34] sourcemaps: Fix some issues with output, add an inline flag, add a test harness --- .gitignore | 2 ++ Makefile | 5 +++++ bin/lessc | 13 ++++++++++--- lib/less/lessc_helper.js | 2 ++ lib/less/parser.js | 3 ++- lib/less/source-map-output.js | 2 +- lib/less/tree/call.js | 2 +- lib/less/tree/comment.js | 2 +- lib/less/tree/directive.js | 2 +- lib/less/tree/import.js | 2 +- lib/less/tree/media.js | 2 +- lib/less/tree/quoted.js | 2 +- package.json | 3 ++- test/sourcemaps/index.html | 16 ++++++++++++++++ 14 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 test/sourcemaps/index.html diff --git a/.gitignore b/.gitignore index a0db3828..6b5c221c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ node_modules .idea test/browser/less.js test/browser/test-runner-*.htm +test/sourcemaps/*.map +test/sourcemaps/*.css diff --git a/Makefile b/Makefile index ad621787..c9920c5e 100644 --- a/Makefile +++ b/Makefile @@ -64,6 +64,11 @@ browser-test-server: browser-prepare jshint: node_modules/.bin/jshint --config ./.jshintrc . +test-sourcemaps: + node bin/lessc --source-map --source-map-inline test/less/import.less test/sourcemaps/import.css + node bin/lessc --source-map --source-map-inline test/less/sourcemaps/basic.less test/sourcemaps/basic.css + node node_modules/http-server/bin/http-server test/sourcemaps -p 8083 + rhino: @@mkdir -p dist @@touch ${RHINO} diff --git a/bin/lessc b/bin/lessc index 021c0074..c830488b 100755 --- a/bin/lessc +++ b/bin/lessc @@ -149,6 +149,10 @@ args = args.filter(function (arg) { if (checkArgFunc(arg, match[2])) { options.sourceMapRootpath = match[2]; } + break; + case 'source-map-inline': + options.outputSourceFiles = true; + break; case 'rp': case 'rootpath': if (checkArgFunc(arg, match[2])) { @@ -200,7 +204,8 @@ if (options.sourceMap === true) { sys.puts("the sourcemap option only has an optional filename if the css filename is given"); return; } - options.sourceMap = options.sourceMapOutputFilename + ".map"; + options.sourceMapFullFilename = options.sourceMapOutputFilename + ".map"; + options.sourceMap = path.basename(options.sourceMapFullFilename); } if (! input) { @@ -234,8 +239,9 @@ if (options.depends) { } var writeSourceMap = function(output) { - ensureDirectory(options.sourceMap); - fs.writeFileSync(options.sourceMap, output, 'utf8'); + var filename = options.sourceMapFullFilename || options.sourceMap; + ensureDirectory(filename); + fs.writeFileSync(filename, output, 'utf8'); }; var parseLessFile = function (e, data) { @@ -272,6 +278,7 @@ var parseLessFile = function (e, data) { sourceMapOutputFilename: options.sourceMapOutputFilename, sourceMapBasepath: options.sourceMapBasepath, sourceMapRootpath: options.sourceMapRootpath || "", + outputSourceFiles: options.outputSourceFiles, writeSourceMap: writeSourceMap, maxLineLen: options.maxLineLen, strictMath: options.strictMath, diff --git a/lib/less/lessc_helper.js b/lib/less/lessc_helper.js index 6e857861..1a2bba15 100644 --- a/lib/less/lessc_helper.js +++ b/lib/less/lessc_helper.js @@ -52,6 +52,8 @@ var lessc_helper = { sys.puts(" media query which is compatible with the SASS"); sys.puts(" format, and 'all' which will do both."); sys.puts(" --source-map[=FILENAME] Outputs a v3 sourcemap to the filename (or output filename.map)"); + sys.puts(" --source-map-rootpath=X adds this path onto the sourcemap filename and less file paths"); + sys.puts(" --source-map-inline puts the less files into the map instead of referencing them"); sys.puts(" -rp, --rootpath=URL Set rootpath for url rewriting in relative imports and urls."); sys.puts(" Works with or without the relative-urls option."); sys.puts(" -ru, --relative-urls re-write relative urls to the base less file."); diff --git a/lib/less/parser.js b/lib/less/parser.js index 5546c128..1dd9a26e 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -470,7 +470,8 @@ less.Parser = function Parser(env) { sourceMapFilename: options.sourceMapFilename, outputFilename: options.sourceMapOutputFilename, sourceMapBasepath: options.sourceMapBasepath, - sourceMapRootpath: options.sourceMapRootpath + sourceMapRootpath: options.sourceMapRootpath, + outputSourceFiles: options.outputSourceFiles }); } diff --git a/lib/less/source-map-output.js b/lib/less/source-map-output.js index 6f911b95..8988eb23 100644 --- a/lib/less/source-map-output.js +++ b/lib/less/source-map-output.js @@ -10,6 +10,7 @@ this._outputFilename = options.outputFilename; this._sourceMapBasepath = options.sourceMapBasepath; this._sourceMapRootpath = options.sourceMapRootpath; + this._outputSourceFiles = options.outputSourceFiles; if (this._sourceMapRootpath && this._sourceMapRootpath.charAt(this._sourceMapRootpath.length-1) !== '/') { this._sourceMapRootpath += '/'; @@ -63,7 +64,6 @@ tree.sourceMapOutput.prototype.toCSS = function(env) { this._sourceMapGenerator = new sourceMap.SourceMapGenerator({ file: this._outputFilename, sourceRoot: null }); - //TODO option to include source in sourcemaps? if (this._outputSourceFiles) { for(var filename in this._contentsMap) { this._sourceMapGenerator.setSourceContent(this.normalizeFilename(filename), this._contentsMap[filename]); diff --git a/lib/less/tree/call.js b/lib/less/tree/call.js index 5e488831..6d262b81 100644 --- a/lib/less/tree/call.js +++ b/lib/less/tree/call.js @@ -52,7 +52,7 @@ tree.Call.prototype = { }, genCSS: function (env, output) { - output.add(this.name + "(", this.index, this.currentFileInfo); + output.add(this.name + "(", this.currentFileInfo, this.index); for(var i = 0; i < this.args.length; i++) { this.args[i].genCSS(env, output); diff --git a/lib/less/tree/comment.js b/lib/less/tree/comment.js index 67167092..c39606e0 100644 --- a/lib/less/tree/comment.js +++ b/lib/less/tree/comment.js @@ -9,7 +9,7 @@ tree.Comment.prototype = { type: "Comment", genCSS: function (env, output) { if (this.debugInfo) { - output.add(tree.debugInfo(env, this), this.index, this.currentFileInfo); + output.add(tree.debugInfo(env, this), this.currentFileInfo, this.index); } output.add(this.value.trim()); //TODO shouldn't need to trim, we shouldn't grab the \n }, diff --git a/lib/less/tree/directive.js b/lib/less/tree/directive.js index 7808bfe4..e161b412 100644 --- a/lib/less/tree/directive.js +++ b/lib/less/tree/directive.js @@ -18,7 +18,7 @@ tree.Directive.prototype = { this.value = visitor.visit(this.value); }, genCSS: function (env, output) { - output.add(this.name, this.index, this.currentFileInfo); + output.add(this.name, this.currentFileInfo, this.index); if (this.rules) { tree.outputRuleset(env, output, this.rules); } else { diff --git a/lib/less/tree/import.js b/lib/less/tree/import.js index 0c6f1188..e4cfd9e2 100644 --- a/lib/less/tree/import.js +++ b/lib/less/tree/import.js @@ -48,7 +48,7 @@ tree.Import.prototype = { }, genCSS: function (env, output) { if (this.css) { - output.add("@import ", this.index, this.currentFileInfo); + output.add("@import ", this.currentFileInfo, this.index); this.path.genCSS(env, output); if (this.features) { output.add(" "); diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index b3148261..c5cdbd78 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -17,7 +17,7 @@ tree.Media.prototype = { this.rules = visitor.visit(this.rules); }, genCSS: function (env, output) { - output.add('@media ', this.index, this.currentFileInfo); + output.add('@media ', this.currentFileInfo, this.index); this.features.genCSS(env, output); tree.outputRuleset(env, output, this.rules); }, diff --git a/lib/less/tree/quoted.js b/lib/less/tree/quoted.js index 59931ac0..29fc22b0 100644 --- a/lib/less/tree/quoted.js +++ b/lib/less/tree/quoted.js @@ -11,7 +11,7 @@ tree.Quoted.prototype = { type: "Quoted", genCSS: function (env, output) { if (!this.escaped) { - output.add(this.quote, this.index, this.currentFileInfo); + output.add(this.quote, this.currentFileInfo, this.index); } output.add(this.value); if (!this.escaped) { diff --git a/package.json b/package.json index 6cf18294..0f3c4c24 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ }, "devDependencies": { "diff": "~1.0", - "jshint": "~2.1.4" + "jshint": "~2.1.4", + "http-server": "~0.5.5" }, "keywords": [ "compile less", diff --git a/test/sourcemaps/index.html b/test/sourcemaps/index.html new file mode 100644 index 00000000..205bd44f --- /dev/null +++ b/test/sourcemaps/index.html @@ -0,0 +1,16 @@ + + + + + + +
    id import-test
    +
    id import-test
    +
    class mixin
    +
    class a
    +
    class b
    +
    class b
    class c
    +
    class a
    class d
    +
    class extend
    class c
    + + \ No newline at end of file