From 9f086324a259b3c5a20098a1588385a3910d82c6 Mon Sep 17 00:00:00 2001 From: agatronic Date: Tue, 19 Mar 2013 10:08:55 +0000 Subject: [PATCH 001/114] Add interface for a file loader rather than an importer --- lib/less/parser.js | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index 819253b1..077fc33c 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -79,23 +79,30 @@ less.Parser = function Parser(env) { mime: env.mime, // MIME type of .less files error: null, // Error in parsing/evaluating an import push: function (path, currentFileInfo, callback) { - var parserImporter = this; + var parserImports = this; this.queue.push(path); - // - // Import a file asynchronously - // - less.Parser.importer(path, currentFileInfo, function (e, root, fullPath) { - parserImporter.queue.splice(parserImporter.queue.indexOf(path), 1); // Remove the path from the queue + var fileParsedFunc = function (e, root, fullPath) { + parserImports.queue.splice(parserImports.queue.indexOf(path), 1); // Remove the path from the queue - var imported = fullPath in parserImporter.files; + var imported = fullPath in parserImports.files; - parserImporter.files[fullPath] = root; // Store the root + parserImports.files[fullPath] = root; // Store the root + + if (e && !parserImports.error) { parserImports.error = e; } - if (e && !parserImporter.error) { parserImporter.error = e; } - callback(e, root, imported); - }, env); + }; + + if (less.Parser.importer) { + less.Parser.importer(path, currentFileInfo, fileParsedFunc, env); + } else { + less.Parser.fileLoader(path, currentFileInfo, function(e, contents, fullPath, env) { + new(less.Parser)(env).parse(contents, function (e, root) { + fileParsedFunc(e, root, fullPath); + }); + }, env) + } } }; From c2adad8341d9218d5e8e0734b6bd9dd26c9fcba6 Mon Sep 17 00:00:00 2001 From: agatronic Date: Tue, 19 Mar 2013 10:23:45 +0000 Subject: [PATCH 002/114] Change node to use the file loader interface --- lib/less/index.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/less/index.js b/lib/less/index.js index fe96fcd7..fa598d93 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -7,7 +7,6 @@ var path = require('path'), var less = { version: [1, 4, 1], Parser: require('./parser').Parser, - importer: require('./parser').importer, tree: require('./tree'), render: function (input, options, callback) { options = options || {}; @@ -103,7 +102,7 @@ var less = { var isUrlRe = /^(?:https?:)?\/\//i; -less.Parser.importer = function (file, currentFileInfo, callback, env) { +less.Parser.fileLoader = function (file, currentFileInfo, callback, env) { var pathname, dirname, data, newFileInfo = { relativeUrls: env.relativeUrls, @@ -112,9 +111,7 @@ less.Parser.importer = function (file, currentFileInfo, callback, env) { rootFilename: currentFileInfo.rootFilename }; - function parseFile(e, data) { - if (e) { return callback(e); } - + function handleDataAndCallCallback(data) { env = new less.tree.parseEnv(env); env.processImports = false; @@ -137,9 +134,8 @@ less.Parser.importer = function (file, currentFileInfo, callback, env) { env.contents[pathname] = data; // Updating top importing parser content cache. env.currentFileInfo = newFileInfo; - new(less.Parser)(env).parse(data, function (e, root) { - callback(e, root, pathname); - }); + + callback(null, data, pathname, env); }; var isUrl = isUrlRe.test( file ); @@ -174,7 +170,7 @@ less.Parser.importer = function (file, currentFileInfo, callback, env) { } pathname = urlStr; dirname = urlObj.protocol +'//'+ urlObj.host + urlObj.pathname.replace(/[^\/]*$/, ''); - parseFile(null, body); + handleDataAndCallCallback(body); }); } else { @@ -202,12 +198,15 @@ less.Parser.importer = function (file, currentFileInfo, callback, env) { if (env.syncImport) { try { data = fs.readFileSync(pathname, 'utf-8'); - parseFile(null, data); + handleDataAndCallCallback(data); } catch (e) { - parseFile(e); + callback(e); } } else { - fs.readFile(pathname, 'utf-8', parseFile); + fs.readFile(pathname, 'utf-8', function(e, data) { + if (e) { callback(e); } + handleDataAndCallCallback(data); + }); } } } From 806ddb64e66d0c2c56471b4cb2654b38bfe01bfa Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 19 Mar 2013 10:50:35 +0000 Subject: [PATCH 003/114] start seperating browser load stylesheet and loadFile --- lib/less/browser.js | 13 +++++++++++-- lib/less/parser.js | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index 33342ffd..7037d95f 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -248,6 +248,15 @@ function extractUrlParts(url, baseUrl) { } function loadStyleSheet(sheet, callback, reload, remaining) { + loadFile(sheet, function(e, root, data, sheet, webInfo, path) { + if (webInfo) { + webInfo.remaining = remaining; + } + callback(e, root, data, sheet, webInfo, path); + }, reload); +} + +function loadFile(sheet, callback, reload) { // sheet may be set to the stylesheet for the initial load or a collection of properties including // some env variables for imports @@ -295,7 +304,7 @@ function loadStyleSheet(sheet, callback, reload, remaining) { new(Date)(styles.timestamp).valueOf())) { // Use local copy createCSS(styles.css, sheet); - callback(null, null, data, sheet, { local: true, remaining: remaining }, href); + callback(null, null, data, sheet, { local: true }, href); } else { // Use remote copy (re-parse) try { @@ -306,7 +315,7 @@ function loadStyleSheet(sheet, callback, reload, remaining) { new(less.Parser)(env).parse(data, function (e, root) { if (e) { return callback(e, null, null, sheet); } try { - callback(e, root, data, sheet, { local: false, lastModified: lastModified, remaining: remaining }, href); + callback(e, root, data, sheet, { local: false, lastModified: lastModified }, href); //TODO - there must be a better way? A generic less-to-css function that can both call error //and removeNode where appropriate //should also add tests diff --git a/lib/less/parser.js b/lib/less/parser.js index 077fc33c..8d32c447 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1611,7 +1611,7 @@ if (less.mode === 'browser' || less.mode === 'rhino') { // as we need this to evaluate the current stylesheet. loadStyleSheet(sheetEnv, function (e, root, data, sheet, _, path) { - callback.call(null, e, root, path); + callback(e, root, path); }, true); }; } From d907cccf6ffc134b8892f04ea952556fd510f6a5 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 19 Mar 2013 11:14:28 +0000 Subject: [PATCH 004/114] Move parsing into loadStylesheet from loadFile --- lib/less/browser.js | 38 ++++++++++++++++++++++---------------- lib/less/parser.js | 2 +- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index 7037d95f..f8c68dce 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -248,11 +248,23 @@ function extractUrlParts(url, baseUrl) { } function loadStyleSheet(sheet, callback, reload, remaining) { - loadFile(sheet, function(e, root, data, sheet, webInfo, path) { + loadFile(sheet, function(e, root, data, sheet, webInfo, path, env) { if (webInfo) { webInfo.remaining = remaining; } - callback(e, root, data, sheet, webInfo, path); + + if (data) { + new(less.Parser)(env).parse(data, function (e, root) { + if (e) { return callback(e, null, null, sheet); } + try { + callback(e, root, data, sheet, webInfo, path); + } catch (e) { + callback(e, null, null, sheet); + } + }); + } else { + callback(e, root, data, sheet, webInfo, path); + } }, reload); } @@ -312,20 +324,14 @@ function loadFile(sheet, callback, reload) { env.paths = [hrefParts.path]; env.currentFileInfo = newFileInfo; - new(less.Parser)(env).parse(data, function (e, root) { - if (e) { return callback(e, null, null, sheet); } - try { - callback(e, root, data, sheet, { local: false, lastModified: lastModified }, href); - //TODO - there must be a better way? A generic less-to-css function that can both call error - //and removeNode where appropriate - //should also add tests - if (env.currentFileInfo.rootFilename === href) { - removeNode(document.getElementById('less-error-message:' + extractId(href))); - } - } catch (e) { - callback(e, null, null, sheet); - } - }); + //TODO - there must be a better way? A generic less-to-css function that can both call error + //and removeNode where appropriate + //should also add tests + if (newFileInfo.rootFilename === href) { + removeNode(document.getElementById('less-error-message:' + extractId(href))); + } + + callback(null, null, data, sheet, { local: false, lastModified: lastModified }, href, env) } catch (e) { callback(e, null, null, sheet); } diff --git a/lib/less/parser.js b/lib/less/parser.js index 8d32c447..e6b4d4ad 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1610,7 +1610,7 @@ if (less.mode === 'browser' || less.mode === 'rhino') { // This is so we can get the syntax tree as opposed to just the CSS output, // as we need this to evaluate the current stylesheet. loadStyleSheet(sheetEnv, - function (e, root, data, sheet, _, path) { + function (e, root, data, sheet, webInfo, path) { callback(e, root, path); }, true); }; From ede491b02d54dbe9325f14bab190c2673b8fa870 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 19 Mar 2013 11:41:09 +0000 Subject: [PATCH 005/114] move browser to parse inside parser --- lib/less/browser.js | 12 ++++++------ lib/less/parser.js | 10 ++++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index f8c68dce..36b3bfbd 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -248,7 +248,7 @@ function extractUrlParts(url, baseUrl) { } function loadStyleSheet(sheet, callback, reload, remaining) { - loadFile(sheet, function(e, root, data, sheet, webInfo, path, env) { + loadFile(sheet, function(e, data, sheet, webInfo, path, env) { if (webInfo) { webInfo.remaining = remaining; } @@ -263,7 +263,7 @@ function loadStyleSheet(sheet, callback, reload, remaining) { } }); } else { - callback(e, root, data, sheet, webInfo, path); + callback(e, null, null, sheet, webInfo, path); } }, reload); } @@ -316,7 +316,7 @@ function loadFile(sheet, callback, reload) { new(Date)(styles.timestamp).valueOf())) { // Use local copy createCSS(styles.css, sheet); - callback(null, null, data, sheet, { local: true }, href); + callback(null, data, sheet, { local: true }, href); } else { // Use remote copy (re-parse) try { @@ -331,13 +331,13 @@ function loadFile(sheet, callback, reload) { removeNode(document.getElementById('less-error-message:' + extractId(href))); } - callback(null, null, data, sheet, { local: false, lastModified: lastModified }, href, env) + callback(null, data, sheet, { local: false, lastModified: lastModified }, href, env) } catch (e) { - callback(e, null, null, sheet); + callback(e, null, sheet); } } }, function (status, url) { - callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, null, sheet); + callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, sheet); }); } diff --git a/lib/less/parser.js b/lib/less/parser.js index e6b4d4ad..c06a914a 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -98,6 +98,8 @@ less.Parser = function Parser(env) { less.Parser.importer(path, currentFileInfo, fileParsedFunc, env); } else { less.Parser.fileLoader(path, currentFileInfo, function(e, contents, fullPath, env) { + if (e) {fileParsedFunc(e); return;} + new(less.Parser)(env).parse(contents, function (e, root) { fileParsedFunc(e, root, fullPath); }); @@ -1598,7 +1600,7 @@ if (less.mode === 'browser' || less.mode === 'rhino') { // // Used by `@import` directives // - less.Parser.importer = function (path, currentFileInfo, callback, env) { + less.Parser.fileLoader = function (path, currentFileInfo, callback, env) { if (!/^([a-z-]+:)?\//.test(path) && currentFileInfo.currentDirectory) { path = currentFileInfo.currentDirectory + path; } @@ -1609,9 +1611,9 @@ if (less.mode === 'browser' || less.mode === 'rhino') { // We pass `true` as 3rd argument, to force the reload of the import. // This is so we can get the syntax tree as opposed to just the CSS output, // as we need this to evaluate the current stylesheet. - loadStyleSheet(sheetEnv, - function (e, root, data, sheet, webInfo, path) { - callback(e, root, path); + loadFile(sheetEnv, + function (e, data, sheet, webInfo, path, env) { + callback(e, data, path, env); }, true); }; } From c391e72c5edf290ae78f22bcfe42e1e7fb8c2ba7 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 19 Mar 2013 12:00:56 +0000 Subject: [PATCH 006/114] seperate sheet and env --- lib/less/browser.js | 16 +++++++++------- lib/less/env.js | 8 -------- lib/less/index.js | 3 +-- lib/less/parser.js | 7 +++---- 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index 36b3bfbd..168a1e9b 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -248,7 +248,12 @@ function extractUrlParts(url, baseUrl) { } function loadStyleSheet(sheet, callback, reload, remaining) { - loadFile(sheet, function(e, data, sheet, webInfo, path, env) { + + var env = new less.tree.parseEnv(less); + env.mime = sheet.type; + env.currentFileInfo = null; + + loadFile(sheet, env, function(e, data, sheet, webInfo, path, env) { if (webInfo) { webInfo.remaining = remaining; } @@ -268,7 +273,7 @@ function loadStyleSheet(sheet, callback, reload, remaining) { }, reload); } -function loadFile(sheet, callback, reload) { +function loadFile(sheet, env, callback, reload) { // sheet may be set to the stylesheet for the initial load or a collection of properties including // some env variables for imports @@ -277,21 +282,18 @@ function loadFile(sheet, callback, reload) { var css = cache && cache.getItem(href); var timestamp = cache && cache.getItem(href + ':timestamp'); var styles = { css: css, timestamp: timestamp }; - var env; var newFileInfo = { relativeUrls: less.relativeUrls, currentDirectory: hrefParts.path, filename: href }; - if (sheet instanceof less.tree.parseEnv) { - env = new less.tree.parseEnv(sheet); + if (env.currentFileInfo) { + env = new less.tree.parseEnv(env); newFileInfo.entryPath = env.currentFileInfo.entryPath; newFileInfo.rootpath = env.currentFileInfo.rootpath; newFileInfo.rootFilename = env.currentFileInfo.rootFilename; } else { - env = new less.tree.parseEnv(less); - env.mime = sheet.type; newFileInfo.entryPath = hrefParts.path; newFileInfo.rootpath = less.rootpath || hrefParts.path; newFileInfo.rootFilename = href; diff --git a/lib/less/env.js b/lib/less/env.js index 91b0027e..6d46f393 100644 --- a/lib/less/env.js +++ b/lib/less/env.js @@ -46,14 +46,6 @@ } }; - tree.parseEnv.prototype.toSheet = function (path) { - var env = new tree.parseEnv(this); - env.href = path; - //env.title = path; - env.type = this.mime; - return env; - }; - var evalCopyProperties = [ 'silent', // whether to swallow errors and warnings 'verbose', // whether to log more activity diff --git a/lib/less/index.js b/lib/less/index.js index fa598d93..0207f4c1 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -113,8 +113,7 @@ less.Parser.fileLoader = function (file, currentFileInfo, callback, env) { function handleDataAndCallCallback(data) { env = new less.tree.parseEnv(env); - env.processImports = false; - + var j = file.lastIndexOf('/'); // Pass on an updated rootpath if path of imported file is relative and file diff --git a/lib/less/parser.js b/lib/less/parser.js index c06a914a..f3c5e186 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -100,6 +100,8 @@ less.Parser = function Parser(env) { less.Parser.fileLoader(path, currentFileInfo, function(e, contents, fullPath, env) { if (e) {fileParsedFunc(e); return;} + env.processImports = false; + new(less.Parser)(env).parse(contents, function (e, root) { fileParsedFunc(e, root, fullPath); }); @@ -1604,14 +1606,11 @@ if (less.mode === 'browser' || less.mode === 'rhino') { if (!/^([a-z-]+:)?\//.test(path) && currentFileInfo.currentDirectory) { path = currentFileInfo.currentDirectory + path; } - var sheetEnv = env.toSheet(path); - sheetEnv.processImports = false; - sheetEnv.currentFileInfo = currentFileInfo; // We pass `true` as 3rd argument, to force the reload of the import. // This is so we can get the syntax tree as opposed to just the CSS output, // as we need this to evaluate the current stylesheet. - loadFile(sheetEnv, + loadFile({href: path, type: env.mime }, env, function (e, data, sheet, webInfo, path, env) { callback(e, data, path, env); }, true); From e82536bfcac5d573b2eb508eb4069481366b9744 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 19 Mar 2013 13:54:08 +0000 Subject: [PATCH 007/114] Simplify more of loadFile and make more of the env creation common between node and browser --- lib/less/browser.js | 78 +++++++++++++++++++++------------------------ lib/less/env.js | 1 + lib/less/index.js | 1 - lib/less/parser.js | 15 ++++----- 4 files changed, 44 insertions(+), 51 deletions(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index 168a1e9b..0d777fd4 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -251,14 +251,30 @@ function loadStyleSheet(sheet, callback, reload, remaining) { var env = new less.tree.parseEnv(less); env.mime = sheet.type; - env.currentFileInfo = null; - loadFile(sheet, env, function(e, data, sheet, webInfo, path, env) { + loadFile(sheet, env, null, function(e, data, sheet, webInfo, path, newFileInfo) { if (webInfo) { webInfo.remaining = remaining; + + var css = cache && cache.getItem(path), + timestamp = cache && cache.getItem(path + ':timestamp'); + + if (!reload && timestamp && webInfo.lastModified && + (new(Date)(webInfo.lastModified).valueOf() === + new(Date)(timestamp).valueOf())) { + // Use local copy + createCSS(css, sheet); + webInfo.local = true; + callback(null, null, data, sheet, webInfo, path); + return; + } } + //TODO add tests around how this behaves when reloading + removeNode(document.getElementById('less-error-message:' + extractId(path))); + if (data) { + env.currentFileInfo = newFileInfo; new(less.Parser)(env).parse(data, function (e, root) { if (e) { return callback(e, null, null, sheet); } try { @@ -273,37 +289,36 @@ function loadStyleSheet(sheet, callback, reload, remaining) { }, reload); } -function loadFile(sheet, env, callback, reload) { +function loadFile(sheet, env, currentFileInfo, callback) { + + if (currentFileInfo && currentFileInfo.currentDirectory && !/^([a-z-]+:)?\//.test(sheet.href)) { + sheet.href = currentFileInfo.currentDirectory + sheet.href; + } // sheet may be set to the stylesheet for the initial load or a collection of properties including // some env variables for imports var hrefParts = extractUrlParts(sheet.href, window.location.href); var href = hrefParts.url; - var css = cache && cache.getItem(href); - var timestamp = cache && cache.getItem(href + ':timestamp'); - var styles = { css: css, timestamp: timestamp }; var newFileInfo = { - relativeUrls: less.relativeUrls, currentDirectory: hrefParts.path, filename: href }; - if (env.currentFileInfo) { - env = new less.tree.parseEnv(env); - newFileInfo.entryPath = env.currentFileInfo.entryPath; - newFileInfo.rootpath = env.currentFileInfo.rootpath; - newFileInfo.rootFilename = env.currentFileInfo.rootFilename; + if (currentFileInfo) { + newFileInfo.entryPath = currentFileInfo.entryPath; + newFileInfo.rootpath = currentFileInfo.rootpath; + newFileInfo.rootFilename = currentFileInfo.rootFilename; + newFileInfo.relativeUrls = currentFileInfo.relativeUrls; } else { newFileInfo.entryPath = hrefParts.path; newFileInfo.rootpath = less.rootpath || hrefParts.path; newFileInfo.rootFilename = href; + newFileInfo.relativeUrls = env.relativeUrls; } - if (env.relativeUrls) { - //todo - this relies on option being set on less object rather than being passed in as an option - // - need an originalRootpath - if (less.rootpath) { - newFileInfo.rootpath = extractUrlParts(less.rootpath + pathDiff(hrefParts.path, newFileInfo.entryPath)).path; + if (newFileInfo.relativeUrls) { + if (env.rootpath) { + newFileInfo.rootpath = extractUrlParts(env.rootpath + pathDiff(hrefParts.path, newFileInfo.entryPath)).path; } else { newFileInfo.rootpath = hrefParts.path; } @@ -313,30 +328,11 @@ function loadFile(sheet, env, callback, reload) { // Store data this session session_cache += data.replace(/@import .+?;/ig, ''); - if (!reload && styles && lastModified && - (new(Date)(lastModified).valueOf() === - new(Date)(styles.timestamp).valueOf())) { - // Use local copy - createCSS(styles.css, sheet); - callback(null, data, sheet, { local: true }, href); - } else { - // Use remote copy (re-parse) - try { - env.contents[href] = data; // Updating content cache - env.paths = [hrefParts.path]; - env.currentFileInfo = newFileInfo; - - //TODO - there must be a better way? A generic less-to-css function that can both call error - //and removeNode where appropriate - //should also add tests - if (newFileInfo.rootFilename === href) { - removeNode(document.getElementById('less-error-message:' + extractId(href))); - } - - callback(null, data, sheet, { local: false, lastModified: lastModified }, href, env) - } catch (e) { - callback(e, null, sheet); - } + // Use remote copy (re-parse) + try { + callback(null, data, sheet, { lastModified: lastModified }, href, newFileInfo) + } catch (e) { + callback(e, null, sheet); } }, function (status, url) { callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, sheet); diff --git a/lib/less/env.js b/lib/less/env.js index 6d46f393..35bc0a6a 100644 --- a/lib/less/env.js +++ b/lib/less/env.js @@ -6,6 +6,7 @@ 'files', // list of files that have been imported, used for import-once 'contents', // browser-only, contents of all the files 'relativeUrls', // option - whether to adjust URL's to be relative + 'rootpath', // option - rootpath to append to URL's 'strictImports', // option - 'dumpLineNumbers', // option - whether to dump line numbers 'compress', // option - whether to compress diff --git a/lib/less/index.js b/lib/less/index.js index 0207f4c1..3d4a32d2 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -131,7 +131,6 @@ less.Parser.fileLoader = function (file, currentFileInfo, callback, env) { newFileInfo.currentDirectory = pathname.replace(/[^\\\/]*$/, ""); newFileInfo.filename = pathname; - env.contents[pathname] = data; // Updating top importing parser content cache. env.currentFileInfo = newFileInfo; callback(null, data, pathname, env); diff --git a/lib/less/parser.js b/lib/less/parser.js index f3c5e186..21b68ae5 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -101,6 +101,7 @@ less.Parser = function Parser(env) { if (e) {fileParsedFunc(e); return;} env.processImports = false; + env.contents[fullPath] = contents; new(less.Parser)(env).parse(contents, function (e, root) { fileParsedFunc(e, root, fullPath); @@ -1603,17 +1604,13 @@ if (less.mode === 'browser' || less.mode === 'rhino') { // Used by `@import` directives // less.Parser.fileLoader = function (path, currentFileInfo, callback, env) { - if (!/^([a-z-]+:)?\//.test(path) && currentFileInfo.currentDirectory) { - path = currentFileInfo.currentDirectory + path; - } - // We pass `true` as 3rd argument, to force the reload of the import. - // This is so we can get the syntax tree as opposed to just the CSS output, - // as we need this to evaluate the current stylesheet. - loadFile({href: path, type: env.mime }, env, - function (e, data, sheet, webInfo, path, env) { + loadFile({href: path, type: env.mime }, env, currentFileInfo, + function (e, data, sheet, webInfo, path, newFileInfo) { + env = new less.tree.parseEnv(env); + env.currentFileInfo = newFileInfo; callback(e, data, path, env); - }, true); + }); }; } From 40ce2bc4b4cb9160fa2fcd90b852b0d60329cb70 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 19 Mar 2013 14:07:16 +0000 Subject: [PATCH 008/114] move new parseEnv into parser --- lib/less/index.js | 6 +----- lib/less/parser.js | 15 ++++++++------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/less/index.js b/lib/less/index.js index 3d4a32d2..3333f9c9 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -112,8 +112,6 @@ less.Parser.fileLoader = function (file, currentFileInfo, callback, env) { }; function handleDataAndCallCallback(data) { - env = new less.tree.parseEnv(env); - var j = file.lastIndexOf('/'); // Pass on an updated rootpath if path of imported file is relative and file @@ -131,9 +129,7 @@ less.Parser.fileLoader = function (file, currentFileInfo, callback, env) { newFileInfo.currentDirectory = pathname.replace(/[^\\\/]*$/, ""); newFileInfo.filename = pathname; - env.currentFileInfo = newFileInfo; - - callback(null, data, pathname, env); + callback(null, data, pathname, newFileInfo); }; var isUrl = isUrlRe.test( file ); diff --git a/lib/less/parser.js b/lib/less/parser.js index 21b68ae5..478c78e4 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -97,13 +97,16 @@ less.Parser = function Parser(env) { if (less.Parser.importer) { less.Parser.importer(path, currentFileInfo, fileParsedFunc, env); } else { - less.Parser.fileLoader(path, currentFileInfo, function(e, contents, fullPath, env) { + less.Parser.fileLoader(path, currentFileInfo, function(e, contents, fullPath, newFileInfo) { if (e) {fileParsedFunc(e); return;} - env.processImports = false; - env.contents[fullPath] = contents; + var newEnv = new tree.parseEnv(env); - new(less.Parser)(env).parse(contents, function (e, root) { + newEnv.currentFileInfo = newFileInfo; + newEnv.processImports = false; + newEnv.contents[fullPath] = contents; + + new(less.Parser)(newEnv).parse(contents, function (e, root) { fileParsedFunc(e, root, fullPath); }); }, env) @@ -1607,9 +1610,7 @@ if (less.mode === 'browser' || less.mode === 'rhino') { loadFile({href: path, type: env.mime }, env, currentFileInfo, function (e, data, sheet, webInfo, path, newFileInfo) { - env = new less.tree.parseEnv(env); - env.currentFileInfo = newFileInfo; - callback(e, data, path, env); + callback(e, data, path, newFileInfo); }); }; } From 08fca7a7cca6680945beced19ab2e4c0455964e2 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 19 Mar 2013 14:19:02 +0000 Subject: [PATCH 009/114] Align the callback param orders --- lib/less/browser.js | 20 ++++++++++---------- lib/less/parser.js | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index 0d777fd4..0be96213 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -252,7 +252,7 @@ function loadStyleSheet(sheet, callback, reload, remaining) { var env = new less.tree.parseEnv(less); env.mime = sheet.type; - loadFile(sheet, env, null, function(e, data, sheet, webInfo, path, newFileInfo) { + loadFile(sheet.href, null, function(e, data, path, newFileInfo, webInfo) { if (webInfo) { webInfo.remaining = remaining; @@ -286,18 +286,18 @@ function loadStyleSheet(sheet, callback, reload, remaining) { } else { callback(e, null, null, sheet, webInfo, path); } - }, reload); + }, env); } -function loadFile(sheet, env, currentFileInfo, callback) { +function loadFile(originalHref, currentFileInfo, callback, env) { - if (currentFileInfo && currentFileInfo.currentDirectory && !/^([a-z-]+:)?\//.test(sheet.href)) { - sheet.href = currentFileInfo.currentDirectory + sheet.href; + if (currentFileInfo && currentFileInfo.currentDirectory && !/^([a-z-]+:)?\//.test(originalHref)) { + originalHref = currentFileInfo.currentDirectory + originalHref; } // sheet may be set to the stylesheet for the initial load or a collection of properties including // some env variables for imports - var hrefParts = extractUrlParts(sheet.href, window.location.href); + var hrefParts = extractUrlParts(originalHref, window.location.href); var href = hrefParts.url; var newFileInfo = { currentDirectory: hrefParts.path, @@ -324,18 +324,18 @@ function loadFile(sheet, env, currentFileInfo, callback) { } } - xhr(href, sheet.type, function (data, lastModified) { + xhr(href, env.mime, function (data, lastModified) { // Store data this session session_cache += data.replace(/@import .+?;/ig, ''); // Use remote copy (re-parse) try { - callback(null, data, sheet, { lastModified: lastModified }, href, newFileInfo) + callback(null, data, href, newFileInfo, { lastModified: lastModified }) } catch (e) { - callback(e, null, sheet); + callback(e); } }, function (status, url) { - callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, sheet); + callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }); }); } diff --git a/lib/less/parser.js b/lib/less/parser.js index 478c78e4..2f919f09 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1608,10 +1608,10 @@ if (less.mode === 'browser' || less.mode === 'rhino') { // less.Parser.fileLoader = function (path, currentFileInfo, callback, env) { - loadFile({href: path, type: env.mime }, env, currentFileInfo, - function (e, data, sheet, webInfo, path, newFileInfo) { + loadFile(path, currentFileInfo, + function (e, data, path, newFileInfo) { callback(e, data, path, newFileInfo); - }); + }, env); }; } From 6fc6dc2301b36889b756d69372cd74e02c210daa Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 19 Mar 2013 15:20:46 +0000 Subject: [PATCH 010/114] Add import inline option. Fixes #1209 --- lib/less/browser.js | 2 ++ lib/less/import-visitor.js | 21 +++++++++++++-------- lib/less/parser.js | 31 +++++++++++-------------------- lib/less/tree/import.js | 13 +++++++++---- test/css/import-inline.css | 3 +++ test/less/import-inline.less | 2 ++ test/less/import/invalid-css.less | 1 + 7 files changed, 41 insertions(+), 32 deletions(-) create mode 100644 test/css/import-inline.css create mode 100644 test/less/import-inline.less create mode 100644 test/less/import/invalid-css.less diff --git a/lib/less/browser.js b/lib/less/browser.js index 0be96213..27e400dd 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -339,6 +339,8 @@ function loadFile(originalHref, currentFileInfo, callback, env) { }); } +less.Parser.fileLoader = loadFile; + function extractId(href) { return href.replace(/^[a-z-]+:\/+?[^\/]+/, '' ) // Remove protocol & domain .replace(/^\//, '' ) // Remove root / diff --git a/lib/less/import-visitor.js b/lib/less/import-visitor.js index 5548430e..ac37f51c 100644 --- a/lib/less/import-visitor.js +++ b/lib/less/import-visitor.js @@ -27,9 +27,10 @@ }, visitImport: function (importNode, visitArgs) { var importVisitor = this, - evaldImportNode; + evaldImportNode, + inlineCSS = importNode.options.inline; - if (!importNode.css) { + if (!importNode.css || inlineCSS) { try { evaldImportNode = importNode.evalForImport(this.env); @@ -41,11 +42,12 @@ importNode.error = e; } - if (evaldImportNode && !evaldImportNode.css) { + if (evaldImportNode && (!evaldImportNode.css || inlineCSS)) { importNode = evaldImportNode; this.importCount++; var env = new tree.evalEnv(this.env, this.env.frames.slice(0)); - this._importer.push(importNode.getPath(), importNode.currentFileInfo, function (e, root, imported) { + + this._importer.push(importNode.getPath(), importNode.currentFileInfo, inlineCSS, function (e, root, imported) { if (e && !e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; } if (imported && !importNode.options.multiple) { importNode.skip = imported; } @@ -59,11 +61,14 @@ if (root) { importNode.root = root; - new(tree.importVisitor)(importVisitor._importer, subFinish, env) - .run(root); - } else { - subFinish(); + if (!inlineCSS) { + new(tree.importVisitor)(importVisitor._importer, subFinish, env) + .run(root); + return; + } } + + subFinish(); }); } } diff --git a/lib/less/parser.js b/lib/less/parser.js index 2f919f09..40d8e425 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -78,20 +78,20 @@ less.Parser = function Parser(env) { contents: env.contents, // Holds the imported file contents mime: env.mime, // MIME type of .less files error: null, // Error in parsing/evaluating an import - push: function (path, currentFileInfo, callback) { + push: function (path, currentFileInfo, inlineCSS, callback) { var parserImports = this; this.queue.push(path); var fileParsedFunc = function (e, root, fullPath) { parserImports.queue.splice(parserImports.queue.indexOf(path), 1); // Remove the path from the queue - var imported = fullPath in parserImports.files; + var importedPreviously = fullPath in parserImports.files; parserImports.files[fullPath] = root; // Store the root if (e && !parserImports.error) { parserImports.error = e; } - callback(e, root, imported); + callback(e, root, importedPreviously); }; if (less.Parser.importer) { @@ -106,9 +106,13 @@ less.Parser = function Parser(env) { newEnv.processImports = false; newEnv.contents[fullPath] = contents; - new(less.Parser)(newEnv).parse(contents, function (e, root) { - fileParsedFunc(e, root, fullPath); - }); + if (inlineCSS) { + fileParsedFunc(null, contents, fullPath); + } else { + new(less.Parser)(newEnv).parse(contents, function (e, root) { + fileParsedFunc(e, root, fullPath); + }); + } }, env) } } @@ -1303,7 +1307,7 @@ less.Parser = function Parser(env) { }, importOption: function() { - var opt = $(/^(less|css|multiple|once)/); + var opt = $(/^(less|css|multiple|once|inline)/); if (opt) { return opt[1]; } @@ -1602,16 +1606,3 @@ less.Parser = function Parser(env) { }; }; -if (less.mode === 'browser' || less.mode === 'rhino') { - // - // Used by `@import` directives - // - less.Parser.fileLoader = function (path, currentFileInfo, callback, env) { - - loadFile(path, currentFileInfo, - function (e, data, path, newFileInfo) { - callback(e, data, path, newFileInfo); - }, env); - }; -} - diff --git a/lib/less/tree/import.js b/lib/less/tree/import.js index 9a93baaf..f81b09c0 100644 --- a/lib/less/tree/import.js +++ b/lib/less/tree/import.js @@ -20,8 +20,8 @@ tree.Import = function (path, features, options, index, currentFileInfo) { this.features = features; this.currentFileInfo = currentFileInfo; - if (this.options.less !== undefined) { - this.css = !this.options.less; + if (this.options.less !== undefined || this.options.inline) { + this.css = !this.options.less || this.options.inline; } else { var pathValue = this.getPath(); if (pathValue && /css([\?;].*)?$/.test(pathValue)) { @@ -44,7 +44,9 @@ tree.Import.prototype = { accept: function (visitor) { this.features = visitor.visit(this.features); this.path = visitor.visit(this.path); - this.root = visitor.visit(this.root); + if (!this.options.inline) { + this.root = visitor.visit(this.root); + } }, toCSS: function (env) { var features = this.features ? ' ' + this.features.toCSS(env) : ''; @@ -84,7 +86,10 @@ tree.Import.prototype = { if (this.skip) { return []; } - if (this.css) { + if (this.options.inline) { + var contents = new(tree.Anonymous)(this.root); + return this.features ? new(tree.Media)([contents], this.features.value) : [contents]; + } else if (this.css) { var newImport = new(tree.Import)(this.evalPath(env), features, this.options, this.index); if (!newImport.css && this.error) { throw this.error; diff --git a/test/css/import-inline.css b/test/css/import-inline.css new file mode 100644 index 00000000..1c17ab2a --- /dev/null +++ b/test/css/import-inline.css @@ -0,0 +1,3 @@ +this isn't very valid CSS. @media (min-width: 600px) { + #css { color: yellow; } +} diff --git a/test/less/import-inline.less b/test/less/import-inline.less new file mode 100644 index 00000000..95a11896 --- /dev/null +++ b/test/less/import-inline.less @@ -0,0 +1,2 @@ +@import (inline) url("import/import-test-d.css") (min-width:600px); +@import (inline, css) url("import/invalid-css.less"); \ No newline at end of file diff --git a/test/less/import/invalid-css.less b/test/less/import/invalid-css.less new file mode 100644 index 00000000..b7cad97b --- /dev/null +++ b/test/less/import/invalid-css.less @@ -0,0 +1 @@ +this isn't very valid CSS. \ No newline at end of file From f068d2da681267dfd08ae5cc3460a2a913fdde1d Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 20 Mar 2013 08:51:47 +0000 Subject: [PATCH 011/114] multi comments in selectors and other places --- lib/less/parser.js | 18 ++++++++++++++---- test/css/comments.css | 3 ++- test/less/comments.less | 8 +++++++- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index 40d8e425..7d2fa6bf 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -577,6 +577,16 @@ less.Parser = function Parser(env) { } }, + comments: function () { + var comment, comments = []; + + while(comment = $(this.comment)) { + comments.push(comment); + } + + return comments; + }, + // // Entities are tokens which can be found inside an Expression // @@ -893,7 +903,7 @@ less.Parser = function Parser(env) { if (isCall) { arg = $(this.expression); } else { - $(this.comment); + $(this.comments); if (input.charAt(i) === '.' && $(/^\.{3}/)) { returner.variadic = true; if ($(";") && !isSemiColonSeperated) { @@ -1023,7 +1033,7 @@ less.Parser = function Parser(env) { restore(); } - $(this.comment); + $(this.comments); if ($(/^when/)) { // Guard cond = expect(this.conditions, 'expected condition'); @@ -1199,9 +1209,9 @@ less.Parser = function Parser(env) { while (s = $(this.selector)) { selectors.push(s); - $(this.comment); + $(this.comments); if (! $(',')) { break } - $(this.comment); + $(this.comments); } if (selectors.length > 0 && (rules = $(this.block))) { diff --git a/test/css/comments.css b/test/css/comments.css index 03e82de4..de9f6c11 100644 --- a/test/css/comments.css +++ b/test/css/comments.css @@ -26,7 +26,8 @@ */ /* @group Variables ------------------- */ -#comments { +#comments, +.comments { /**/ color: red; /* A C-style comment */ diff --git a/test/less/comments.less b/test/less/comments.less index b803ff75..7859911e 100644 --- a/test/less/comments.less +++ b/test/less/comments.less @@ -34,7 +34,13 @@ /* @group Variables ------------------- */ -#comments /* boo */ { +#comments /* boo *//* boo again*/, +//.commented_out1 +//.commented_out2 +//.commented_out3 +.comments //end of comments1 +//end of comments2 +{ /**/ // An empty comment color: red; /* A C-style comment */ /* A C-style comment */ background-color: orange; // A little comment From b726b0d96cb739bd2a1ef5ff8b6294db6e15591a Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 21 Mar 2013 10:11:56 +0000 Subject: [PATCH 012/114] Fix modifyVars to use a per file cache so less is re-evaluated properly. Also add tests and \n in case of comment on the last line --- lib/less/browser.js | 68 ++++++++++++------- lib/less/env.js | 1 + test/browser-test-prepare.js | 3 +- test/browser/css/modify-vars/simple.css | 7 ++ .../less/modify-vars/imports/simple2.less | 4 ++ test/browser/less/modify-vars/simple.less | 6 ++ test/browser/runner-modify-vars.js | 14 ++++ 7 files changed, 77 insertions(+), 26 deletions(-) create mode 100644 test/browser/css/modify-vars/simple.css create mode 100644 test/browser/less/modify-vars/imports/simple2.less create mode 100644 test/browser/less/modify-vars/simple.less create mode 100644 test/browser/runner-modify-vars.js diff --git a/lib/less/browser.js b/lib/less/browser.js index 27e400dd..edb9c244 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -98,23 +98,17 @@ for (var i = 0; i < links.length; i++) { // With this function, it's possible to alter variables and re-render // CSS without reloading less-files // -var session_cache = ''; +var fileCache = {}; less.modifyVars = function(record) { - var str = session_cache; + var newVars = ""; for (var name in record) { - str += ((name.slice(0,1) === '@')? '' : '@') + name +': '+ + newVars += ((name.slice(0,1) === '@')? '' : '@') + name +': '+ ((record[name].slice(-1) === ';')? record[name] : record[name] +';'); } - new(less.Parser)(new less.tree.parseEnv(less)).parse(str, function (e, root) { - if (e) { - error(e, "session_cache"); - } else { - createCSS(root.toCSS(less), less.sheets[less.sheets.length - 1]); - } - }); + less.refresh(false, newVars) }; -less.refresh = function (reload) { +less.refresh = function (reload, newVars) { var startTime, endTime; startTime = endTime = new(Date); @@ -131,22 +125,27 @@ less.refresh = function (reload) { 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); + }, reload, newVars); - loadStyles(); + loadStyles(newVars); }; less.refreshStyles = loadStyles; less.refresh(less.env === 'development'); -function loadStyles() { +function loadStyles(newVars) { var styles = document.getElementsByTagName('style'); for (var i = 0; i < styles.length; i++) { if (styles[i].type.match(typePattern)) { - var env = new less.tree.parseEnv(less); + var env = new less.tree.parseEnv(less), + lessText = styles[i].innerHTML || ''; env.filename = document.location.href.replace(/#.*$/, ''); + if (newVars) { + env.useFileCache = true; + lessText += "\n" + newVars; + } - new(less.Parser)(env).parse(styles[i].innerHTML || '', function (e, cssAST) { + new(less.Parser)(env).parse(lessText, function (e, cssAST) { if (e) { return error(e, "inline"); } @@ -163,9 +162,9 @@ function loadStyles() { } } -function loadStyleSheets(callback, reload) { +function loadStyleSheets(callback, reload, newVars) { for (var i = 0; i < less.sheets.length; i++) { - loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1)); + loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1), newVars); } } @@ -247,12 +246,17 @@ function extractUrlParts(url, baseUrl) { return returner; } -function loadStyleSheet(sheet, callback, reload, remaining) { +function loadStyleSheet(sheet, callback, reload, remaining, newVars) { var env = new less.tree.parseEnv(less); env.mime = sheet.type; + if (newVars) { + env.useFileCache = true; + } + loadFile(sheet.href, null, function(e, data, path, newFileInfo, webInfo) { + if (webInfo) { webInfo.remaining = remaining; @@ -286,10 +290,10 @@ function loadStyleSheet(sheet, callback, reload, remaining) { } else { callback(e, null, null, sheet, webInfo, path); } - }, env); + }, env, newVars); } -function loadFile(originalHref, currentFileInfo, callback, env) { +function loadFile(originalHref, currentFileInfo, callback, env, newVars) { if (currentFileInfo && currentFileInfo.currentDirectory && !/^([a-z-]+:)?\//.test(originalHref)) { originalHref = currentFileInfo.currentDirectory + originalHref; @@ -324,18 +328,31 @@ function loadFile(originalHref, currentFileInfo, callback, env) { } } + if (env.useFileCache && fileCache[href]) { + try { + var lessText = fileCache[href]; + if (newVars) { + lessText += "\n" + newVars; + } + callback(null, lessText, href, newFileInfo, { lastModified: new Date() }) + } catch (e) { + callback(e, null, href); + } + return; + } + xhr(href, env.mime, function (data, lastModified) { - // Store data this session - session_cache += data.replace(/@import .+?;/ig, ''); + // per file cache + fileCache[href] = data; // Use remote copy (re-parse) try { callback(null, data, href, newFileInfo, { lastModified: lastModified }) } catch (e) { - callback(e); + callback(e, null, href); } }, function (status, url) { - callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }); + callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, href); }); } @@ -414,6 +431,7 @@ function xhr(url, type, callback, errback) { if (typeof(xhr.overrideMimeType) === 'function') { xhr.overrideMimeType('text/css'); } + log("XHR: Getting '" + url + "'"); xhr.open('GET', url, async); xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5'); xhr.send(null); diff --git a/lib/less/env.js b/lib/less/env.js index 35bc0a6a..d53d889c 100644 --- a/lib/less/env.js +++ b/lib/less/env.js @@ -13,6 +13,7 @@ 'processImports', // option - whether to process imports. if false then imports will not be imported 'syncImport', // option - whether to import synchronously 'mime', // browser only - mime type for sheet import + 'useFileCache', // browser only - whether to use the per file session cache 'currentFileInfo' // information about the current file - for error reporting and importing and making urls relative etc. ]; diff --git a/test/browser-test-prepare.js b/test/browser-test-prepare.js index 0272279b..31d3eaa8 100644 --- a/test/browser-test-prepare.js +++ b/test/browser-test-prepare.js @@ -43,4 +43,5 @@ createTestRunnerPage("browser", null, "browser"); createTestRunnerPage("browser", null, "relative-urls", "relative-urls"); createTestRunnerPage("browser", null, "rootpath", "rootpath"); createTestRunnerPage("browser", null, "rootpath-relative", "rootpath-relative"); -createTestRunnerPage("browser", null, "production"); \ No newline at end of file +createTestRunnerPage("browser", null, "production"); +createTestRunnerPage("browser", null, "modify-vars", "modify-vars"); \ No newline at end of file diff --git a/test/browser/css/modify-vars/simple.css b/test/browser/css/modify-vars/simple.css new file mode 100644 index 00000000..4cb81bac --- /dev/null +++ b/test/browser/css/modify-vars/simple.css @@ -0,0 +1,7 @@ +.testisimported { + color: gainsboro; +} +.test { + color1: #008000; + color2: #800080; +} diff --git a/test/browser/less/modify-vars/imports/simple2.less b/test/browser/less/modify-vars/imports/simple2.less new file mode 100644 index 00000000..49fcf875 --- /dev/null +++ b/test/browser/less/modify-vars/imports/simple2.less @@ -0,0 +1,4 @@ +@var2: blue; +.testisimported { + color: gainsboro; +} \ No newline at end of file diff --git a/test/browser/less/modify-vars/simple.less b/test/browser/less/modify-vars/simple.less new file mode 100644 index 00000000..64d99302 --- /dev/null +++ b/test/browser/less/modify-vars/simple.less @@ -0,0 +1,6 @@ +@import "imports/simple2"; +@var1: red; +.test { + color1: @var1; + color2: @var2; +} \ No newline at end of file diff --git a/test/browser/runner-modify-vars.js b/test/browser/runner-modify-vars.js new file mode 100644 index 00000000..4918d8f8 --- /dev/null +++ b/test/browser/runner-modify-vars.js @@ -0,0 +1,14 @@ + +setTimeout(function(){ + less.modifyVars({var1: "green", var2: "purple"}); +}, 1000); + +describe("less.js modify vars", function() { + testLessEqualsInDocument(); + it("Should log only 2 XHR requests", function() { + var xhrLogMessages = logMessages.filter(function(item) { + return /XHR: Getting '/.test(item); + }) + expect(xhrLogMessages.length).toEqual(2); + }); +}); \ No newline at end of file From ab1a9c55985d1725a52f1b78913b2bb955e26fa3 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 21 Mar 2013 10:46:02 +0000 Subject: [PATCH 013/114] 1.4.1 so far changelog changes --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b508969..17e1fcc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.4.1 WIP + +- 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 + # 1.4.1 2013-07-05 @@ -13,7 +18,7 @@ - fix passing of strict maths option # 1.4.0 Beta 4 - + 2013-05-04 - change strictMaths to strictMath. Enable this with --strict-math=on in lessc and strictMath:true in JavaScript. From 898e27f52547b282f03e89ed881565879bf0be86 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 21 Mar 2013 13:37:44 +0000 Subject: [PATCH 014/114] Add silent option --- lib/less/env.js | 3 ++- lib/less/import-visitor.js | 2 +- lib/less/parser.js | 10 +++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/less/env.js b/lib/less/env.js index d53d889c..65952fb6 100644 --- a/lib/less/env.js +++ b/lib/less/env.js @@ -23,7 +23,8 @@ // 'rootpath' - path to append to normal URLs for this node // 'currentDirectory' - path to the current file, absolute // 'rootFilename' - filename of the base file - // 'entryPath' = absolute path to the entry file + // 'entryPath' - absolute path to the entry file + // 'silent' - whether the file should be silent and only output parts that are referenced tree.parseEnv = function(options) { copyFromOriginal(options, this, parseCopyProperties); diff --git a/lib/less/import-visitor.js b/lib/less/import-visitor.js index ac37f51c..3d3a2156 100644 --- a/lib/less/import-visitor.js +++ b/lib/less/import-visitor.js @@ -47,7 +47,7 @@ this.importCount++; var env = new tree.evalEnv(this.env, this.env.frames.slice(0)); - this._importer.push(importNode.getPath(), importNode.currentFileInfo, inlineCSS, function (e, root, imported) { + this._importer.push(importNode.getPath(), importNode.currentFileInfo, importNode.options, function (e, root, imported) { if (e && !e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; } if (imported && !importNode.options.multiple) { importNode.skip = imported; } diff --git a/lib/less/parser.js b/lib/less/parser.js index 7d2fa6bf..59f956ed 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -78,7 +78,7 @@ less.Parser = function Parser(env) { contents: env.contents, // Holds the imported file contents mime: env.mime, // MIME type of .less files error: null, // Error in parsing/evaluating an import - push: function (path, currentFileInfo, inlineCSS, callback) { + push: function (path, currentFileInfo, importOptions, callback) { var parserImports = this; this.queue.push(path); @@ -106,7 +106,11 @@ less.Parser = function Parser(env) { newEnv.processImports = false; newEnv.contents[fullPath] = contents; - if (inlineCSS) { + if (currentFileInfo.silent || importOptions.silent) { + newFileInfo.silent = true; + } + + if (importOptions.inline) { fileParsedFunc(null, contents, fullPath); } else { new(less.Parser)(newEnv).parse(contents, function (e, root) { @@ -1317,7 +1321,7 @@ less.Parser = function Parser(env) { }, importOption: function() { - var opt = $(/^(less|css|multiple|once|inline)/); + var opt = $(/^(less|css|multiple|once|inline|silent)/); if (opt) { return opt[1]; } From 5df82b69ce492388034cb59b4174d611b84f8650 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 21 Mar 2013 15:09:10 +0000 Subject: [PATCH 015/114] Basic functionality and basic tests for import silent --- lib/less/parser.js | 2 +- lib/less/tree/ruleset.js | 42 ++++++++++++++++++----------- lib/less/tree/selector.js | 8 ++++-- test/css/import-silent.css | 3 +++ test/less/import-silent.less | 6 +++++ test/less/import/import-silent.less | 3 +++ 6 files changed, 46 insertions(+), 18 deletions(-) create mode 100644 test/css/import-silent.css create mode 100644 test/less/import-silent.less create mode 100644 test/less/import/import-silent.less diff --git a/lib/less/parser.js b/lib/less/parser.js index 59f956ed..41f007fd 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1168,7 +1168,7 @@ less.Parser = function Parser(env) { if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') { break } } - if (elements.length > 0) { return new(tree.Selector)(elements, extendList); } + if (elements.length > 0) { return new(tree.Selector)(elements, extendList, i, env.currentFileInfo); } if (extendList.length) { error("Extend must be used to extend a selector, it cannot be used on its own"); } }, attribute: function () { diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 76c7ec41..5b48e067 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -241,24 +241,36 @@ tree.Ruleset.prototype = { } else { if (rules.length > 0) { debugInfo = tree.debugInfo(env, this); - selector = this.paths.map(function (p) { - return p.map(function (s) { - return s.toCSS(env); - }).join('').trim(); - }).join(env.compress ? ',' : ',\n'); + selector = this.paths + .filter(function(p) { + var i; + for(i = 0; i < p.length; i++) { + if (!p[i].isSilent()) { + return true; + } + return false; + } + }) + .map(function (p) { + return p.map(function (s) { + return s.toCSS(env); + }).join('').trim(); + }).join(env.compress ? ',' : ',\n'); - // 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) { + // 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; + rules = _rules; - css.push(debugInfo + selector + - (env.compress ? '{' : ' {\n ') + - rules.join(env.compress ? '' : '\n ') + - (env.compress ? '}' : '\n}\n')); + css.push(debugInfo + selector + + (env.compress ? '{' : ' {\n ') + + rules.join(env.compress ? '' : '\n ') + + (env.compress ? '}' : '\n}\n')); + } } } css.push(rulesets); diff --git a/lib/less/tree/selector.js b/lib/less/tree/selector.js index f122e55f..38f2b287 100644 --- a/lib/less/tree/selector.js +++ b/lib/less/tree/selector.js @@ -1,8 +1,9 @@ (function (tree) { -tree.Selector = function (elements, extendList) { +tree.Selector = function (elements, extendList, index, currentFileInfo) { this.elements = elements; this.extendList = extendList || []; + this.currentFileInfo = currentFileInfo || {}; // TODO remove }; tree.Selector.prototype = { type: "Selector", @@ -36,7 +37,7 @@ tree.Selector.prototype = { return e.eval(env); }), this.extendList.map(function(extend) { return extend.eval(env); - })); + }), this.index, this.currentFileInfo); }, toCSS: function (env) { if (this._css) { return this._css } @@ -56,6 +57,9 @@ tree.Selector.prototype = { }).join(''); return this._css; + }, + isSilent: function() { + return this.currentFileInfo.silent; } }; diff --git a/test/css/import-silent.css b/test/css/import-silent.css new file mode 100644 index 00000000..b28d1ab0 --- /dev/null +++ b/test/css/import-silent.css @@ -0,0 +1,3 @@ +.b { + color: red; +} diff --git a/test/less/import-silent.less b/test/less/import-silent.less new file mode 100644 index 00000000..bc94234e --- /dev/null +++ b/test/less/import-silent.less @@ -0,0 +1,6 @@ +@import (silent) url("import-once.less"); +@import (silent) url("import/import-silent.less"); + +.b { + .a(); +} \ No newline at end of file diff --git a/test/less/import/import-silent.less b/test/less/import/import-silent.less new file mode 100644 index 00000000..b3a5ee84 --- /dev/null +++ b/test/less/import/import-silent.less @@ -0,0 +1,3 @@ +.a { + color: red; +} \ No newline at end of file From 33c5ecd100436323975a3a2a2a21c4a015a19fc9 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 21 Mar 2013 15:21:56 +0000 Subject: [PATCH 016/114] import silent - handle selectors containing amp --- lib/less/tree/ruleset.js | 6 +++--- lib/less/tree/selector.js | 3 +++ test/css/import-silent.css | 3 +++ test/less/import/import-silent.less | 8 ++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 5b48e067..830ce843 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -376,11 +376,11 @@ tree.Ruleset.prototype = { if (sel.length > 0) { newSelectorPath = sel.slice(0); lastSelector = newSelectorPath.pop(); - newJoinedSelector = new(tree.Selector)(lastSelector.elements.slice(0), selector.extendList); + newJoinedSelector = selector.createDerived(lastSelector.elements.slice(0)); newJoinedSelectorEmpty = false; } else { - newJoinedSelector = new(tree.Selector)([], selector.extendList); + newJoinedSelector = selector.createDerived([]); } //put together the parent selectors after the join @@ -442,7 +442,7 @@ tree.Ruleset.prototype = { // if the previous thing in sel is a parent this needs to join on to it if (sel.length > 0) { - sel[sel.length - 1] = new(tree.Selector)(sel[sel.length - 1].elements.concat(elements), sel[sel.length - 1].extendList); + sel[sel.length - 1] = sel[sel.length - 1].createDerived(sel[sel.length - 1].elements.concat(elements)); } else { sel.push(new(tree.Selector)(elements)); diff --git a/lib/less/tree/selector.js b/lib/less/tree/selector.js index 38f2b287..5001c351 100644 --- a/lib/less/tree/selector.js +++ b/lib/less/tree/selector.js @@ -11,6 +11,9 @@ tree.Selector.prototype = { this.elements = visitor.visit(this.elements); this.extendList = visitor.visit(this.extendList) }, + createDerived: function(elements) { + return new(tree.Selector)(elements, this.extendList, this.index, this.currentFileInfo); + }, match: function (other) { var elements = this.elements, len = elements.length, diff --git a/test/css/import-silent.css b/test/css/import-silent.css index b28d1ab0..d3a2fe1b 100644 --- a/test/css/import-silent.css +++ b/test/css/import-silent.css @@ -1,3 +1,6 @@ .b { color: red; } +.b .c { + color: green; +} diff --git a/test/less/import/import-silent.less b/test/less/import/import-silent.less index b3a5ee84..74a5fb7a 100644 --- a/test/less/import/import-silent.less +++ b/test/less/import/import-silent.less @@ -1,3 +1,11 @@ .a { color: red; + .c { + color: green; + } +} +.d { + &:hover { + color: purple; + } } \ No newline at end of file From b912a971d38796e69fec188e7055f9922a672ccd Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 21 Mar 2013 15:30:40 +0000 Subject: [PATCH 017/114] import silent: add tests for extends --- test/css/import-silent.css | 29 +++++++++++++++++++++++++++++ test/less/import-silent.less | 4 ++++ test/less/import/import-silent.less | 21 +++++++++++++++++++-- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/test/css/import-silent.css b/test/css/import-silent.css index d3a2fe1b..688ddfd3 100644 --- a/test/css/import-silent.css +++ b/test/css/import-silent.css @@ -1,6 +1,35 @@ +.visible { + color: red; +} +.visible .c { + color: green; +} +.visible { + color: green; +} +.visible:hover { + color: green; +} +.visible { + color: green; +} +.only-with-visible + .visible, +.visible + .only-with-visible, +.visible + .visible { + color: green; +} +.only-with-visible + .visible .sub, +.visible + .only-with-visible .sub, +.visible + .visible .sub { + color: green; +} .b { color: red; + color: green; } .b .c { color: green; } +.visible { + extend: test; +} diff --git a/test/less/import-silent.less b/test/less/import-silent.less index bc94234e..8b36dece 100644 --- a/test/less/import-silent.less +++ b/test/less/import-silent.less @@ -3,4 +3,8 @@ .b { .a(); +} + +.visible:extend(.a all) { + extend: test; } \ No newline at end of file diff --git a/test/less/import/import-silent.less b/test/less/import/import-silent.less index 74a5fb7a..ab04d7a3 100644 --- a/test/less/import/import-silent.less +++ b/test/less/import/import-silent.less @@ -4,8 +4,25 @@ color: green; } } -.d { +.only-with-visible, +.a { + color: green; &:hover { - color: purple; + color: green; + } + & { + color: green; + } + & + & { + color: green; + .sub { + color: green; + } + } +} + +& { + .hidden { + hidden: true; } } \ No newline at end of file From aa802bd84cdd735871bfa7aef7911534eb95091e Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 21 Mar 2013 16:38:56 +0000 Subject: [PATCH 018/114] import silent to work with media queries and directives --- lib/less/parser.js | 6 +++--- lib/less/tree/directive.js | 10 ++++++++-- lib/less/tree/media.js | 20 ++++++++++++++------ test/css/import-silent.css | 11 +++++++++++ test/css/media.css | 3 --- test/less/import-silent.less | 11 +++++++++-- test/less/import/import-silent.less | 10 ++++++++-- 7 files changed, 53 insertions(+), 18 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index 41f007fd..a87a0e2e 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1379,7 +1379,7 @@ less.Parser = function Parser(env) { features = $(this.mediaFeatures); if (rules = $(this.block)) { - media = new(tree.Media)(rules, features); + media = new(tree.Media)(rules, features, i, env.currentFileInfo); if(env.dumpLineNumbers) media.debugInfo = debugInfo; return media; @@ -1455,11 +1455,11 @@ less.Parser = function Parser(env) { if (hasBlock) { if (rules = $(this.block)) { - return new(tree.Directive)(name, rules); + return new(tree.Directive)(name, rules, i, env.currentFileInfo); } } else { if ((value = hasExpression ? $(this.expression) : $(this.entity)) && $(';')) { - var directive = new(tree.Directive)(name, value); + var directive = new(tree.Directive)(name, value, i, env.currentFileInfo); if (env.dumpLineNumbers) { directive.debugInfo = getDebugInfo(i, input, env); } diff --git a/lib/less/tree/directive.js b/lib/less/tree/directive.js index 3fb33e8d..d9e66fd6 100644 --- a/lib/less/tree/directive.js +++ b/lib/less/tree/directive.js @@ -1,6 +1,6 @@ (function (tree) { -tree.Directive = function (name, value) { +tree.Directive = function (name, value, index, currentFileInfo) { this.name = name; if (Array.isArray(value)) { @@ -9,6 +9,7 @@ tree.Directive = function (name, value) { } else { this.value = value; } + this.currentFileInfo = currentFileInfo; }; tree.Directive.prototype = { type: "Directive", @@ -17,6 +18,11 @@ tree.Directive.prototype = { this.value = visitor.visit(this.value); }, toCSS: function (env) { + + if (this.currentFileInfo.silent) { + return ""; + } + if (this.ruleset) { this.ruleset.root = true; return this.name + (env.compress ? '{' : ' {\n ') + @@ -30,7 +36,7 @@ tree.Directive.prototype = { var evaldDirective = this; if (this.ruleset) { env.frames.unshift(this); - evaldDirective = new(tree.Directive)(this.name); + evaldDirective = new(tree.Directive)(this.name, null, this.index, this.currentFileInfo); evaldDirective.ruleset = this.ruleset.eval(env); env.frames.shift(); } diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index 0fea084e..4870509c 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -1,6 +1,9 @@ (function (tree) { -tree.Media = function (value, features) { +tree.Media = function (value, features, index, currentFileInfo) { + this.index = index; + this.currentFileInfo = currentFileInfo; + var selectors = this.emptySelectors(); this.features = new(tree.Value)(features); @@ -16,9 +19,14 @@ tree.Media.prototype = { toCSS: function (env) { var features = this.features.toCSS(env); - return '@media ' + features + (env.compress ? '{' : ' {\n ') + - this.ruleset.toCSS(env).trim().replace(/\n/g, '\n ') + - (env.compress ? '}': '\n}\n'); + var content = this.ruleset.toCSS(env).trim().replace(/\n/g, '\n '); + + if (content.match(/\S/)) { + return '@media ' + features + (env.compress ? '{' : ' {\n ') + content + + (env.compress ? '}': '\n}\n'); + } else { + return ""; + } }, eval: function (env) { if (!env.mediaBlocks) { @@ -26,7 +34,7 @@ tree.Media.prototype = { env.mediaPath = []; } - var media = new(tree.Media)([], []); + var media = new(tree.Media)([], [], this.index, this.currentFileInfo); if(this.debugInfo) { this.ruleset.debugInfo = this.debugInfo; media.debugInfo = this.debugInfo; @@ -62,7 +70,7 @@ tree.Media.prototype = { rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) }, emptySelectors: function() { var el = new(tree.Element)('', '&', 0); - return [new(tree.Selector)([el])]; + return [new(tree.Selector)([el], null, this.index, this.currentFileInfo)]; }, evalTop: function (env) { diff --git a/test/css/import-silent.css b/test/css/import-silent.css index 688ddfd3..53040b1e 100644 --- a/test/css/import-silent.css +++ b/test/css/import-silent.css @@ -1,3 +1,14 @@ +@media only screen and (max-width: 200px) { + width: 480px; +} +@media (max-width: 1200px) { + /* a comment */ +} +/* + The media statements - 1 is invalid (no selector) and 1 is just a comment + We should band invalid media queries solving the 1st case and not treat comments as rules, + solving the 2nd issue. +*/ .visible { color: red; } diff --git a/test/css/media.css b/test/css/media.css index 5aa1d4e8..9bd80b31 100644 --- a/test/css/media.css +++ b/test/css/media.css @@ -52,9 +52,6 @@ .sidebar { width: 500px; } -} -@media a { - } @media a and b { .first .second .third { diff --git a/test/less/import-silent.less b/test/less/import-silent.less index 8b36dece..78c37819 100644 --- a/test/less/import-silent.less +++ b/test/less/import-silent.less @@ -1,10 +1,17 @@ @import (silent) url("import-once.less"); +@import (silent) url("css-3.less"); +@import (silent) url("media.less"); +/* + The media statements - 1 is invalid (no selector) and 1 is just a comment + We should band invalid media queries solving the 1st case and not treat comments as rules, + solving the 2nd issue. +*/ @import (silent) url("import/import-silent.less"); .b { - .a(); + .z(); } -.visible:extend(.a all) { +.visible:extend(.z all) { extend: test; } \ No newline at end of file diff --git a/test/less/import/import-silent.less b/test/less/import/import-silent.less index ab04d7a3..266523b5 100644 --- a/test/less/import/import-silent.less +++ b/test/less/import/import-silent.less @@ -1,11 +1,11 @@ -.a { +.z { color: red; .c { color: green; } } .only-with-visible, -.a { +.z { color: green; &:hover { color: green; @@ -25,4 +25,10 @@ .hidden { hidden: true; } +} + +@media tv { + .hidden { + hidden: true; + } } \ No newline at end of file From 493d6facf2a66521fb185ff8acef55e89ae3de5e Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 21 Mar 2013 16:51:04 +0000 Subject: [PATCH 019/114] support comments. Add failing test case for when mixin call does not have parent selectors --- lib/less/parser.js | 4 ++-- lib/less/tree/comment.js | 7 ++++--- test/css/import-silent.css | 12 ++++++------ test/less/import-silent.less | 7 ++++--- test/less/import/import-silent.less | 9 +++++++++ 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index a87a0e2e..8d39aab6 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -575,9 +575,9 @@ less.Parser = function Parser(env) { if (input.charAt(i) !== '/') return; if (input.charAt(i + 1) === '/') { - return new(tree.Comment)($(/^\/\/.*/), true); + return new(tree.Comment)($(/^\/\/.*/), true, i, env.currentFileInfo); } else if (comment = $(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/)) { - return new(tree.Comment)(comment); + return new(tree.Comment)(comment, false, i, env.currentFileInfo); } }, diff --git a/lib/less/tree/comment.js b/lib/less/tree/comment.js index 2123b4b9..820b7efa 100644 --- a/lib/less/tree/comment.js +++ b/lib/less/tree/comment.js @@ -1,15 +1,16 @@ (function (tree) { -tree.Comment = function (value, silent) { +tree.Comment = function (value, silent, index, currentFileInfo) { this.value = value; this.silent = !!silent; + this.currentFileInfo = currentFileInfo; }; tree.Comment.prototype = { type: "Comment", toCSS: function (env) { - return env.compress ? '' : this.value; + return (env.compress || (this.currentFileInfo && this.currentFileInfo.silent)) ? '' : this.value; }, - eval: function () { return this } + eval: function () { return this; } }; })(require('../tree')); diff --git a/test/css/import-silent.css b/test/css/import-silent.css index 53040b1e..29928611 100644 --- a/test/css/import-silent.css +++ b/test/css/import-silent.css @@ -1,13 +1,9 @@ @media only screen and (max-width: 200px) { width: 480px; } -@media (max-width: 1200px) { - /* a comment */ -} /* - The media statements - 1 is invalid (no selector) and 1 is just a comment - We should band invalid media queries solving the 1st case and not treat comments as rules, - solving the 2nd issue. + The media statement above is invalid (no selector) + We should ban invalid media queries with properties and no selector? */ .visible { color: red; @@ -41,6 +37,10 @@ .b .c { color: green; } +.y { + pulled-in: yes; +} +/* comment pulled in */ .visible { extend: test; } diff --git a/test/less/import-silent.less b/test/less/import-silent.less index 78c37819..f401e07a 100644 --- a/test/less/import-silent.less +++ b/test/less/import-silent.less @@ -2,9 +2,8 @@ @import (silent) url("css-3.less"); @import (silent) url("media.less"); /* - The media statements - 1 is invalid (no selector) and 1 is just a comment - We should band invalid media queries solving the 1st case and not treat comments as rules, - solving the 2nd issue. + The media statement above is invalid (no selector) + We should ban invalid media queries with properties and no selector? */ @import (silent) url("import/import-silent.less"); @@ -12,6 +11,8 @@ .z(); } +.zz(); + .visible:extend(.z all) { extend: test; } \ No newline at end of file diff --git a/test/less/import/import-silent.less b/test/less/import/import-silent.less index 266523b5..9eac45fc 100644 --- a/test/less/import/import-silent.less +++ b/test/less/import/import-silent.less @@ -31,4 +31,13 @@ .hidden { hidden: true; } +} + +/* comment is not output */ + +.zz { + .y { + pulled-in: yes; + } + /* comment pulled in */ } \ No newline at end of file From 47b7bda64bee6bb3c9b2ca22df3489fb284113b9 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 21 Mar 2013 16:57:10 +0000 Subject: [PATCH 020/114] todo to fix last part of import silent --- lib/less/tree/mixin.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/less/tree/mixin.js b/lib/less/tree/mixin.js index 09f08731..5029c135 100644 --- a/lib/less/tree/mixin.js +++ b/lib/less/tree/mixin.js @@ -49,6 +49,10 @@ tree.mixin.Call.prototype = { } } if (match) { + if (!this.currentFileInfo || !this.currentFileInfo.silent) { + //todo tell the rules that they are not silent + // make sure the rules pass that information when eval'd + } return rules; } } From e591bc56edfca4b65242782abaf24648aeb1fd18 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 21 Mar 2013 16:59:23 +0000 Subject: [PATCH 021/114] changelog info --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17e1fcc5..a1d1e369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # 1.4.1 WIP -- 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 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 silent option to reference external css, but not output it. Any mixin calls or extend's will be output. # 1.4.1 From dce452421f929c90921b44eebfa6c7ed426dea3f Mon Sep 17 00:00:00 2001 From: Luke Page Date: Fri, 22 Mar 2013 10:14:20 +0000 Subject: [PATCH 022/114] Fix the rest of import silent --- lib/less/tree/comment.js | 7 +++++-- lib/less/tree/directive.js | 16 ++++++++++++++-- lib/less/tree/media.js | 10 ++++++++++ lib/less/tree/mixin.js | 10 +++++++--- lib/less/tree/ruleset.js | 6 ++++++ lib/less/tree/selector.js | 18 +++++++++++------- test/css/import-silent.css | 12 ++++++++++++ 7 files changed, 65 insertions(+), 14 deletions(-) diff --git a/lib/less/tree/comment.js b/lib/less/tree/comment.js index 820b7efa..8d81f8fd 100644 --- a/lib/less/tree/comment.js +++ b/lib/less/tree/comment.js @@ -8,9 +8,12 @@ tree.Comment = function (value, silent, index, currentFileInfo) { tree.Comment.prototype = { type: "Comment", toCSS: function (env) { - return (env.compress || (this.currentFileInfo && this.currentFileInfo.silent)) ? '' : this.value; + return (env.compress || (this.currentFileInfo && this.currentFileInfo.silent && !this.isNotSilent)) ? '' : this.value; }, - eval: function () { return this; } + eval: function () { return this; }, + markNotSilent: function () { + this.isNotSilent = true; + } }; })(require('../tree')); diff --git a/lib/less/tree/directive.js b/lib/less/tree/directive.js index d9e66fd6..6105f0e4 100644 --- a/lib/less/tree/directive.js +++ b/lib/less/tree/directive.js @@ -19,7 +19,7 @@ tree.Directive.prototype = { }, toCSS: function (env) { - if (this.currentFileInfo.silent) { + if (this.currentFileInfo.silent && !this.isNotSilent) { return ""; } @@ -44,7 +44,19 @@ tree.Directive.prototype = { }, variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) }, find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) }, - rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) } + rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) }, + markNotSilent: function () { + var rule, i; + this.isNotSilent = true; + if (this.ruleset) { + for (i = 0; i < this.ruleset.rules.length; i++) { + rule = this.ruleset.rules[i]; + if (rule.markNotSilent) { + rule.markNotSilent(); + } + } + } + } }; })(require('../tree')); diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index 4870509c..49b0f1e5 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -72,6 +72,16 @@ tree.Media.prototype = { var el = new(tree.Element)('', '&', 0); return [new(tree.Selector)([el], null, this.index, this.currentFileInfo)]; }, + markNotSilent: function () { + var rule, i; + this.isNotSilent = true; + for (i = 0; i < this.ruleset.rules.length; i++) { + rule = this.ruleset.rules[i]; + if (rule.markNotSilent) { + rule.markNotSilent(); + } + } + }, evalTop: function (env) { var result = this; diff --git a/lib/less/tree/mixin.js b/lib/less/tree/mixin.js index 5029c135..3d05f62a 100644 --- a/lib/less/tree/mixin.js +++ b/lib/less/tree/mixin.js @@ -15,7 +15,7 @@ tree.mixin.Call.prototype = { this.arguments = visitor.visit(this.arguments); }, eval: function (env) { - var mixins, mixin, args, rules = [], match = false, i, m, f, isRecursive, isOneFound; + var mixins, mixin, args, rules = [], match = false, i, m, f, isRecursive, isOneFound, rule; args = this.arguments && this.arguments.map(function (a) { return { name: a.name, value: a.value.eval(env) }; @@ -50,8 +50,12 @@ tree.mixin.Call.prototype = { } if (match) { if (!this.currentFileInfo || !this.currentFileInfo.silent) { - //todo tell the rules that they are not silent - // make sure the rules pass that information when eval'd + for (i = 0; i < rules.length; i++) { + rule = rules[i]; + if (rule.markNotSilent) { + rule.markNotSilent(); + } + } } return rules; } diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 830ce843..3401556a 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -278,6 +278,12 @@ tree.Ruleset.prototype = { return css.join('') + (env.compress ? '\n' : ''); }, + markNotSilent: function () { + for (var s = 0; s < this.selectors.length; s++) { + this.selectors[s].markNotSilent(); + } + }, + joinSelectors: function (paths, context, selectors) { for (var s = 0; s < selectors.length; s++) { this.joinSelector(paths, context, selectors[s]); diff --git a/lib/less/tree/selector.js b/lib/less/tree/selector.js index 5001c351..72c8c26e 100644 --- a/lib/less/tree/selector.js +++ b/lib/less/tree/selector.js @@ -1,9 +1,10 @@ (function (tree) { -tree.Selector = function (elements, extendList, index, currentFileInfo) { +tree.Selector = function (elements, extendList, index, currentFileInfo, isNotSilent) { this.elements = elements; this.extendList = extendList || []; - this.currentFileInfo = currentFileInfo || {}; // TODO remove + this.currentFileInfo = currentFileInfo || {}; + this.isNotSilent = isNotSilent; }; tree.Selector.prototype = { type: "Selector", @@ -11,8 +12,8 @@ tree.Selector.prototype = { this.elements = visitor.visit(this.elements); this.extendList = visitor.visit(this.extendList) }, - createDerived: function(elements) { - return new(tree.Selector)(elements, this.extendList, this.index, this.currentFileInfo); + createDerived: function(elements, extendList) { + return new(tree.Selector)(elements, extendList || this.extendList, this.index, this.currentFileInfo, this.isNotSilent); }, match: function (other) { var elements = this.elements, @@ -36,11 +37,11 @@ tree.Selector.prototype = { return true; }, eval: function (env) { - return new(tree.Selector)(this.elements.map(function (e) { + return this.createDerived(this.elements.map(function (e) { return e.eval(env); }), this.extendList.map(function(extend) { return extend.eval(env); - }), this.index, this.currentFileInfo); + })); }, toCSS: function (env) { if (this._css) { return this._css } @@ -61,8 +62,11 @@ tree.Selector.prototype = { return this._css; }, + markNotSilent: function () { + this.isNotSilent = true; + }, isSilent: function() { - return this.currentFileInfo.silent; + return this.currentFileInfo.silent && !this.isNotSilent; } }; diff --git a/test/css/import-silent.css b/test/css/import-silent.css index 29928611..811145d2 100644 --- a/test/css/import-silent.css +++ b/test/css/import-silent.css @@ -37,6 +37,18 @@ .b .c { color: green; } +.b:hover { + color: green; +} +.b { + color: green; +} +.b + .b { + color: green; +} +.b + .b .sub { + color: green; +} .y { pulled-in: yes; } From f4902f809c3560265bebe430b6946cb1d776d2d3 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Fri, 22 Mar 2013 10:29:17 +0000 Subject: [PATCH 023/114] rename import silent to import mute. Fixes #1210 --- CHANGELOG.md | 2 +- lib/less/env.js | 2 +- lib/less/index.js | 2 +- lib/less/parser.js | 6 +++--- lib/less/tree/comment.js | 6 +++--- lib/less/tree/directive.js | 10 +++++----- lib/less/tree/media.js | 8 ++++---- lib/less/tree/mixin.js | 6 +++--- lib/less/tree/ruleset.js | 6 +++--- lib/less/tree/selector.js | 14 +++++++------- test/css/{import-silent.css => import-mute.css} | 0 test/less/{import-silent.less => import-mute.less} | 8 ++++---- .../{import-silent.less => import-mute.less} | 0 13 files changed, 35 insertions(+), 35 deletions(-) rename test/css/{import-silent.css => import-mute.css} (100%) rename test/less/{import-silent.less => import-mute.less} (54%) rename test/less/import/{import-silent.less => import-mute.less} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1d1e369..984cc9fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ - 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 silent option to reference external css, but not output it. Any mixin calls or extend's will be output. + - support for import mute option to reference external css, but not output it. Any mixin calls or extend's will be output. # 1.4.1 diff --git a/lib/less/env.js b/lib/less/env.js index 65952fb6..7819a93d 100644 --- a/lib/less/env.js +++ b/lib/less/env.js @@ -24,7 +24,7 @@ // 'currentDirectory' - path to the current file, absolute // 'rootFilename' - filename of the base file // 'entryPath' - absolute path to the entry file - // 'silent' - whether the file should be silent and only output parts that are referenced + // 'mute' - whether the file should be mute and only output parts that are referenced tree.parseEnv = function(options) { copyFromOriginal(options, this, parseCopyProperties); diff --git a/lib/less/index.js b/lib/less/index.js index 3333f9c9..cc5551ba 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -83,7 +83,7 @@ var less = { }, writeError: function (ctx, options) { options = options || {}; - if (options.silent) { return } + if (options.silent) { return; } sys.error(less.formatError(ctx, options)); } }; diff --git a/lib/less/parser.js b/lib/less/parser.js index 8d39aab6..70fc059c 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -106,8 +106,8 @@ less.Parser = function Parser(env) { newEnv.processImports = false; newEnv.contents[fullPath] = contents; - if (currentFileInfo.silent || importOptions.silent) { - newFileInfo.silent = true; + if (currentFileInfo.mute || importOptions.mute) { + newFileInfo.mute = true; } if (importOptions.inline) { @@ -1321,7 +1321,7 @@ less.Parser = function Parser(env) { }, importOption: function() { - var opt = $(/^(less|css|multiple|once|inline|silent)/); + var opt = $(/^(less|css|multiple|once|inline|mute)/); if (opt) { return opt[1]; } diff --git a/lib/less/tree/comment.js b/lib/less/tree/comment.js index 8d81f8fd..eb93448a 100644 --- a/lib/less/tree/comment.js +++ b/lib/less/tree/comment.js @@ -8,11 +8,11 @@ tree.Comment = function (value, silent, index, currentFileInfo) { tree.Comment.prototype = { type: "Comment", toCSS: function (env) { - return (env.compress || (this.currentFileInfo && this.currentFileInfo.silent && !this.isNotSilent)) ? '' : this.value; + return (env.compress || (this.currentFileInfo && this.currentFileInfo.mute && !this.isNotMute)) ? '' : this.value; }, eval: function () { return this; }, - markNotSilent: function () { - this.isNotSilent = true; + markNotMute: function () { + this.isNotMute = true; } }; diff --git a/lib/less/tree/directive.js b/lib/less/tree/directive.js index 6105f0e4..44642665 100644 --- a/lib/less/tree/directive.js +++ b/lib/less/tree/directive.js @@ -19,7 +19,7 @@ tree.Directive.prototype = { }, toCSS: function (env) { - if (this.currentFileInfo.silent && !this.isNotSilent) { + if (this.currentFileInfo.mute && !this.isNotMute) { return ""; } @@ -45,14 +45,14 @@ tree.Directive.prototype = { variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) }, find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) }, rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) }, - markNotSilent: function () { + markNotMute: function () { var rule, i; - this.isNotSilent = true; + this.isNotMute = true; if (this.ruleset) { for (i = 0; i < this.ruleset.rules.length; i++) { rule = this.ruleset.rules[i]; - if (rule.markNotSilent) { - rule.markNotSilent(); + if (rule.markNotMute) { + rule.markNotMute(); } } } diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index 49b0f1e5..0f76c635 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -72,13 +72,13 @@ tree.Media.prototype = { var el = new(tree.Element)('', '&', 0); return [new(tree.Selector)([el], null, this.index, this.currentFileInfo)]; }, - markNotSilent: function () { + markNotMute: function () { var rule, i; - this.isNotSilent = true; + this.isNotMute = true; for (i = 0; i < this.ruleset.rules.length; i++) { rule = this.ruleset.rules[i]; - if (rule.markNotSilent) { - rule.markNotSilent(); + if (rule.markNotMute) { + rule.markNotMute(); } } }, diff --git a/lib/less/tree/mixin.js b/lib/less/tree/mixin.js index 3d05f62a..1b6eb402 100644 --- a/lib/less/tree/mixin.js +++ b/lib/less/tree/mixin.js @@ -49,11 +49,11 @@ tree.mixin.Call.prototype = { } } if (match) { - if (!this.currentFileInfo || !this.currentFileInfo.silent) { + if (!this.currentFileInfo || !this.currentFileInfo.mute) { for (i = 0; i < rules.length; i++) { rule = rules[i]; - if (rule.markNotSilent) { - rule.markNotSilent(); + if (rule.markNotMute) { + rule.markNotMute(); } } } diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 3401556a..4de3604f 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -245,7 +245,7 @@ tree.Ruleset.prototype = { .filter(function(p) { var i; for(i = 0; i < p.length; i++) { - if (!p[i].isSilent()) { + if (!p[i].isMute()) { return true; } return false; @@ -278,9 +278,9 @@ tree.Ruleset.prototype = { return css.join('') + (env.compress ? '\n' : ''); }, - markNotSilent: function () { + markNotMute: function () { for (var s = 0; s < this.selectors.length; s++) { - this.selectors[s].markNotSilent(); + this.selectors[s].markNotMute(); } }, diff --git a/lib/less/tree/selector.js b/lib/less/tree/selector.js index 72c8c26e..180ebd77 100644 --- a/lib/less/tree/selector.js +++ b/lib/less/tree/selector.js @@ -1,10 +1,10 @@ (function (tree) { -tree.Selector = function (elements, extendList, index, currentFileInfo, isNotSilent) { +tree.Selector = function (elements, extendList, index, currentFileInfo, isNotMute) { this.elements = elements; this.extendList = extendList || []; this.currentFileInfo = currentFileInfo || {}; - this.isNotSilent = isNotSilent; + this.isNotMute = isNotMute; }; tree.Selector.prototype = { type: "Selector", @@ -13,7 +13,7 @@ tree.Selector.prototype = { this.extendList = visitor.visit(this.extendList) }, createDerived: function(elements, extendList) { - return new(tree.Selector)(elements, extendList || this.extendList, this.index, this.currentFileInfo, this.isNotSilent); + return new(tree.Selector)(elements, extendList || this.extendList, this.index, this.currentFileInfo, this.isNotMute); }, match: function (other) { var elements = this.elements, @@ -62,11 +62,11 @@ tree.Selector.prototype = { return this._css; }, - markNotSilent: function () { - this.isNotSilent = true; + markNotMute: function () { + this.isNotMute = true; }, - isSilent: function() { - return this.currentFileInfo.silent && !this.isNotSilent; + isMute: function() { + return this.currentFileInfo.mute && !this.isNotMute; } }; diff --git a/test/css/import-silent.css b/test/css/import-mute.css similarity index 100% rename from test/css/import-silent.css rename to test/css/import-mute.css diff --git a/test/less/import-silent.less b/test/less/import-mute.less similarity index 54% rename from test/less/import-silent.less rename to test/less/import-mute.less index f401e07a..df5d561c 100644 --- a/test/less/import-silent.less +++ b/test/less/import-mute.less @@ -1,11 +1,11 @@ -@import (silent) url("import-once.less"); -@import (silent) url("css-3.less"); -@import (silent) url("media.less"); +@import (mute) url("import-once.less"); +@import (mute) url("css-3.less"); +@import (mute) url("media.less"); /* The media statement above is invalid (no selector) We should ban invalid media queries with properties and no selector? */ -@import (silent) url("import/import-silent.less"); +@import (mute) url("import/import-mute.less"); .b { .z(); diff --git a/test/less/import/import-silent.less b/test/less/import/import-mute.less similarity index 100% rename from test/less/import/import-silent.less rename to test/less/import/import-mute.less From 446e16444441059f0166a97addda486d69c5e08f Mon Sep 17 00:00:00 2001 From: agatronic Date: Wed, 24 Apr 2013 13:25:48 +0100 Subject: [PATCH 024/114] rename mute to reference --- CHANGELOG.md | 2 +- lib/less/env.js | 2 +- lib/less/parser.js | 6 +++--- lib/less/tree/comment.js | 6 +++--- lib/less/tree/directive.js | 10 +++++----- lib/less/tree/media.js | 8 ++++---- lib/less/tree/mixin.js | 6 +++--- lib/less/tree/ruleset.js | 6 +++--- lib/less/tree/selector.js | 14 +++++++------- test/css/{import-mute.css => import-reference.css} | 0 .../{import-mute.less => import-reference.less} | 8 ++++---- .../{import-mute.less => import-reference.less} | 0 12 files changed, 34 insertions(+), 34 deletions(-) rename test/css/{import-mute.css => import-reference.css} (100%) rename test/less/{import-mute.less => import-reference.less} (52%) rename test/less/import/{import-mute.less => import-reference.less} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 984cc9fd..a26f2876 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ - 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 mute option to reference external css, but not output it. Any mixin calls or extend's will be output. + - support for import reference option to reference external css, but not output it. Any mixin calls or extend's will be output. # 1.4.1 diff --git a/lib/less/env.js b/lib/less/env.js index 7819a93d..9f88cef4 100644 --- a/lib/less/env.js +++ b/lib/less/env.js @@ -24,7 +24,7 @@ // 'currentDirectory' - path to the current file, absolute // 'rootFilename' - filename of the base file // 'entryPath' - absolute path to the entry file - // 'mute' - whether the file should be mute and only output parts that are referenced + // 'reference' - whether the file should not be output and only output parts that are referenced tree.parseEnv = function(options) { copyFromOriginal(options, this, parseCopyProperties); diff --git a/lib/less/parser.js b/lib/less/parser.js index 70fc059c..009108ed 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -106,8 +106,8 @@ less.Parser = function Parser(env) { newEnv.processImports = false; newEnv.contents[fullPath] = contents; - if (currentFileInfo.mute || importOptions.mute) { - newFileInfo.mute = true; + if (currentFileInfo.reference || importOptions.reference) { + newFileInfo.reference = true; } if (importOptions.inline) { @@ -1321,7 +1321,7 @@ less.Parser = function Parser(env) { }, importOption: function() { - var opt = $(/^(less|css|multiple|once|inline|mute)/); + var opt = $(/^(less|css|multiple|once|inline|reference)/); if (opt) { return opt[1]; } diff --git a/lib/less/tree/comment.js b/lib/less/tree/comment.js index eb93448a..4da401f9 100644 --- a/lib/less/tree/comment.js +++ b/lib/less/tree/comment.js @@ -8,11 +8,11 @@ tree.Comment = function (value, silent, index, currentFileInfo) { tree.Comment.prototype = { type: "Comment", toCSS: function (env) { - return (env.compress || (this.currentFileInfo && this.currentFileInfo.mute && !this.isNotMute)) ? '' : this.value; + return (env.compress || (this.currentFileInfo && this.currentFileInfo.reference && !this.isReferenced)) ? '' : this.value; }, eval: function () { return this; }, - markNotMute: function () { - this.isNotMute = true; + markReferenced: function () { + this.isReferenced = true; } }; diff --git a/lib/less/tree/directive.js b/lib/less/tree/directive.js index 44642665..d905eaaf 100644 --- a/lib/less/tree/directive.js +++ b/lib/less/tree/directive.js @@ -19,7 +19,7 @@ tree.Directive.prototype = { }, toCSS: function (env) { - if (this.currentFileInfo.mute && !this.isNotMute) { + if (this.currentFileInfo.reference && !this.isReferenced) { return ""; } @@ -45,14 +45,14 @@ tree.Directive.prototype = { variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) }, find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) }, rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) }, - markNotMute: function () { + markReferenced: function () { var rule, i; - this.isNotMute = true; + this.isReferenced = true; if (this.ruleset) { for (i = 0; i < this.ruleset.rules.length; i++) { rule = this.ruleset.rules[i]; - if (rule.markNotMute) { - rule.markNotMute(); + if (rule.markReferenced) { + rule.markReferenced(); } } } diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index 0f76c635..90e5b96d 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -72,13 +72,13 @@ tree.Media.prototype = { var el = new(tree.Element)('', '&', 0); return [new(tree.Selector)([el], null, this.index, this.currentFileInfo)]; }, - markNotMute: function () { + markReferenced: function () { var rule, i; - this.isNotMute = true; + this.isReferenced = true; for (i = 0; i < this.ruleset.rules.length; i++) { rule = this.ruleset.rules[i]; - if (rule.markNotMute) { - rule.markNotMute(); + if (rule.markReferenced) { + rule.markReferenced(); } } }, diff --git a/lib/less/tree/mixin.js b/lib/less/tree/mixin.js index 1b6eb402..b7e7f3e8 100644 --- a/lib/less/tree/mixin.js +++ b/lib/less/tree/mixin.js @@ -49,11 +49,11 @@ tree.mixin.Call.prototype = { } } if (match) { - if (!this.currentFileInfo || !this.currentFileInfo.mute) { + if (!this.currentFileInfo || !this.currentFileInfo.reference) { for (i = 0; i < rules.length; i++) { rule = rules[i]; - if (rule.markNotMute) { - rule.markNotMute(); + if (rule.markReferenced) { + rule.markReferenced(); } } } diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 4de3604f..495ccb8d 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -245,7 +245,7 @@ tree.Ruleset.prototype = { .filter(function(p) { var i; for(i = 0; i < p.length; i++) { - if (!p[i].isMute()) { + if (p[i].getIsReferenced()) { return true; } return false; @@ -278,9 +278,9 @@ tree.Ruleset.prototype = { return css.join('') + (env.compress ? '\n' : ''); }, - markNotMute: function () { + markReferenced: function () { for (var s = 0; s < this.selectors.length; s++) { - this.selectors[s].markNotMute(); + this.selectors[s].markReferenced(); } }, diff --git a/lib/less/tree/selector.js b/lib/less/tree/selector.js index 180ebd77..36156747 100644 --- a/lib/less/tree/selector.js +++ b/lib/less/tree/selector.js @@ -1,10 +1,10 @@ (function (tree) { -tree.Selector = function (elements, extendList, index, currentFileInfo, isNotMute) { +tree.Selector = function (elements, extendList, index, currentFileInfo, isReferenced) { this.elements = elements; this.extendList = extendList || []; this.currentFileInfo = currentFileInfo || {}; - this.isNotMute = isNotMute; + this.isReferenced = isReferenced; }; tree.Selector.prototype = { type: "Selector", @@ -13,7 +13,7 @@ tree.Selector.prototype = { this.extendList = visitor.visit(this.extendList) }, createDerived: function(elements, extendList) { - return new(tree.Selector)(elements, extendList || this.extendList, this.index, this.currentFileInfo, this.isNotMute); + return new(tree.Selector)(elements, extendList || this.extendList, this.index, this.currentFileInfo, this.isReferenced); }, match: function (other) { var elements = this.elements, @@ -62,11 +62,11 @@ tree.Selector.prototype = { return this._css; }, - markNotMute: function () { - this.isNotMute = true; + markReferenced: function () { + this.isReferenced = true; }, - isMute: function() { - return this.currentFileInfo.mute && !this.isNotMute; + getIsReferenced: function() { + return !this.currentFileInfo.reference || this.isReferenced; } }; diff --git a/test/css/import-mute.css b/test/css/import-reference.css similarity index 100% rename from test/css/import-mute.css rename to test/css/import-reference.css diff --git a/test/less/import-mute.less b/test/less/import-reference.less similarity index 52% rename from test/less/import-mute.less rename to test/less/import-reference.less index df5d561c..cf9da168 100644 --- a/test/less/import-mute.less +++ b/test/less/import-reference.less @@ -1,11 +1,11 @@ -@import (mute) url("import-once.less"); -@import (mute) url("css-3.less"); -@import (mute) url("media.less"); +@import (reference) url("import-once.less"); +@import (reference) url("css-3.less"); +@import (reference) url("media.less"); /* The media statement above is invalid (no selector) We should ban invalid media queries with properties and no selector? */ -@import (mute) url("import/import-mute.less"); +@import (reference) url("import/import-reference.less"); .b { .z(); diff --git a/test/less/import/import-mute.less b/test/less/import/import-reference.less similarity index 100% rename from test/less/import/import-mute.less rename to test/less/import/import-reference.less From de27a6fd888aebfa8cd4040349498f9140af0b69 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 30 Apr 2013 18:58:35 +0100 Subject: [PATCH 025/114] Add bypass for saturate(5%) filter function. Helps issue #1299 --- lib/less/functions.js | 5 +++++ test/css/functions.css | 1 + test/less/functions.less | 1 + 3 files changed, 7 insertions(+) diff --git a/lib/less/functions.js b/lib/less/functions.js index fbde54ff..361be6f4 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -96,6 +96,11 @@ tree.functions = { return new(tree.Dimension)(Math.round(color.luma() * color.alpha * 100), '%'); }, saturate: function (color, amount) { + // filter: saturate(3.2); + // should be kept as is, so check for color + if (!color.rgb) { + return null; + } var hsl = color.toHSL(); hsl.s += amount.value / 100; diff --git a/test/css/functions.css b/test/css/functions.css index eacebd8d..d1392c35 100644 --- a/test/css/functions.css +++ b/test/css/functions.css @@ -26,6 +26,7 @@ luma-cyan: 79%; luma-white-alpha: 50%; contrast-filter: contrast(30%); + saturate-filter: saturate(5%); contrast-white: #000000; contrast-black: #ffffff; contrast-red: #ffffff; diff --git a/test/less/functions.less b/test/less/functions.less index f60dab47..337e9eb6 100644 --- a/test/less/functions.less +++ b/test/less/functions.less @@ -30,6 +30,7 @@ luma-cyan: luma(#00ffff); luma-white-alpha: luma(rgba(255,255,255,0.5)); contrast-filter: contrast(30%); + saturate-filter: saturate(5%); contrast-white: contrast(#fff); contrast-black: contrast(#000); contrast-red: contrast(#ff0000); From 5dde7b3381bcb44c2f4e288fdef8c5527e614820 Mon Sep 17 00:00:00 2001 From: Daniel Katz Date: Wed, 6 Feb 2013 22:39:13 +0200 Subject: [PATCH 026/114] Added support and tests for !merge() syntax #700 --- lib/less/parser.js | 24 ++++++++++++++--- lib/less/tree/rule.js | 5 +++- lib/less/tree/ruleset.js | 36 +++++++++++++++++++++++++ lib/less/tree/value.js | 5 ++-- test/css/merge.css | 26 ++++++++++++++++++ test/less/merge.less | 57 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 146 insertions(+), 7 deletions(-) create mode 100644 test/css/merge.css create mode 100644 test/less/merge.less diff --git a/lib/less/parser.js b/lib/less/parser.js index 009108ed..ff1c7888 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -425,7 +425,7 @@ less.Parser = function Parser(env) { } value = new(tree.Value)([value]); } - return new(tree.Rule)('@' + k, value, false, 0); + return new(tree.Rule)('@' + k, value, false, null, 0); }); evalEnv.frames = [new(tree.Ruleset)(null, variables)]; } @@ -1230,7 +1230,7 @@ less.Parser = function Parser(env) { } }, rule: function (tryAnonymous) { - var name, value, c = input.charAt(i), important; + var name, value, c = input.charAt(i), important, merge, match; save(); if (c === '.' || c === '#' || c === '&') { return } @@ -1242,10 +1242,12 @@ less.Parser = function Parser(env) { ($(this.value) || $(this.anonymousValue)) : ($(this.anonymousValue) || $(this.value)); + important = $(this.important); + merge = $(this.merge); if (value && $(this.end)) { - return new(tree.Rule)(name, value, important, memo, env.currentFileInfo); + return new (tree.Rule)(name, value, important, merge, memo, env.currentFileInfo); } else { furthest = i; restore(); @@ -1338,7 +1340,7 @@ less.Parser = function Parser(env) { e = $(this.value); if ($(')')) { if (p && e) { - nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, i, env.currentFileInfo, true))); + nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, null, i, env.currentFileInfo, true))); } else if (e) { nodes.push(new(tree.Paren)(e)); } else { @@ -1495,6 +1497,20 @@ less.Parser = function Parser(env) { return $(/^! *important/); } }, + merge: function () { + var separator; + if (input.charAt(i) === '!') { + if ($(/^! *merge\(/)) { + if (input.charAt(i) === ")") { + separator = " "; + } else { + separator = $(/[^)]/); + } + expect(")"); + } + } + return separator; + }, sub: function () { var a, e; diff --git a/lib/less/tree/rule.js b/lib/less/tree/rule.js index b2e5d243..eacd5419 100644 --- a/lib/less/tree/rule.js +++ b/lib/less/tree/rule.js @@ -1,9 +1,10 @@ (function (tree) { -tree.Rule = function (name, value, important, index, currentFileInfo, inline) { +tree.Rule = function (name, value, important, merge, index, currentFileInfo, inline) { this.name = name; this.value = (value instanceof tree.Value) ? value : new(tree.Value)([value]); this.important = important ? ' ' + important.trim() : ''; + this.merge = merge; this.index = index; this.currentFileInfo = currentFileInfo; this.inline = inline || false; @@ -43,6 +44,7 @@ tree.Rule.prototype = { return new(tree.Rule)(this.name, this.value.eval(env), this.important, + this.merge, this.index, this.currentFileInfo, this.inline); } finally { @@ -55,6 +57,7 @@ tree.Rule.prototype = { return new(tree.Rule)(this.name, this.value, "!important", + this.merge, this.index, this.currentFileInfo, this.inline); } }; diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 495ccb8d..a7525b2b 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -178,6 +178,7 @@ tree.Ruleset.prototype = { debugInfo, // Line number debugging rule; + this.mergeRules(); // Compile rules and rulesets for (var i = 0; i < this.rules.length; i++) { rule = this.rules[i]; @@ -454,6 +455,41 @@ 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 ? "!" : "", + rule.merge].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) { + parts[0].value = new (tree.Value)(parts.map(function (p) { + return p.value; + }), rule.merge); + } + }); } }; })(require('../tree')); diff --git a/lib/less/tree/value.js b/lib/less/tree/value.js index 5aae88eb..7ee2067f 100644 --- a/lib/less/tree/value.js +++ b/lib/less/tree/value.js @@ -1,7 +1,8 @@ (function (tree) { -tree.Value = function (value) { +tree.Value = function (value, separator) { this.value = value; + this.separator = separator ? separator : ","; }; tree.Value.prototype = { type: "Value", @@ -20,7 +21,7 @@ tree.Value.prototype = { toCSS: function (env) { return this.value.map(function (e) { return e.toCSS(env); - }).join(env.compress ? ',' : ', '); + }).join(this.separator + ((env.compress || this.separator === " ") ? "" : " ")); } }; diff --git a/test/css/merge.css b/test/css/merge.css new file mode 100644 index 00000000..86e0b473 --- /dev/null +++ b/test/css/merge.css @@ -0,0 +1,26 @@ +.test1 { + transform: rotate(90deg) skew(30deg) scale(2, 4); +} +.test2 { + transform: rotate(90deg) skew(30deg); + transform: scaleX(45deg); +} +.test3 { + background: url(img1.png), url(img2.png); +} +.test4 { + transform: rotate(90deg) skew(30deg); + transform: scaleX(45deg); +} +.test5 { + transform: scaleX(45deg); + background: url(img1.png); +} +.test6 { + transform: rotate(90deg) skew(30deg); + transform: scale(2, 4) !important; +} +.test7 { + transform: rotate(90deg) skew(30deg); + transform: scale(2, 4) !important; +} diff --git a/test/less/merge.less b/test/less/merge.less new file mode 100644 index 00000000..e7801a13 --- /dev/null +++ b/test/less/merge.less @@ -0,0 +1,57 @@ +.first-transform() { + transform: rotate(90deg) skew(30deg) !merge(); +} +.second-transform() { + transform: scale(2,4) !merge(); +} +.third-transform() { + transform: scaleX(45deg); +} +.fourth-transform() { + transform: scaleX(45deg) !merge(,); +} +.fifth-transform() { + transform: scale(2,4) !important !merge(); +} +.first-background() { + background: url(img1.png) !merge(,); +} +.second-background() { + background: url(img2.png) !merge(,); +} + +.test1 { + // Can merge values with space separator + .first-transform(); + .second-transform(); +} +.test2 { + // Wont merge values without !merge directive, for backwards compatibility with css + .first-transform(); + .third-transform(); +} +.test3 { + // Can merge values with explicit separator + .first-background(); + .second-background(); +} +.test4 { + // Wont merge values from two sources with different seperators + .first-transform(); + .fourth-transform(); +} +.test5 { + // Wont merge values from two sources with the same seperators but different properties + .fourth-transform(); + .first-background(); +} +.test6 { + // Wont merge values from sources that merked as !important, for backwards compatibility with css + .first-transform(); + .fifth-transform(); +} +.test7 { + // Wont merge values from mixins that merked as !important, for backwards compatibility with css + .first-transform(); + .second-transform() !important; +} \ No newline at end of file From ff029c34bd5aa14fdf2b84f55a473d458af87394 Mon Sep 17 00:00:00 2001 From: Daniel Katz Date: Thu, 7 Feb 2013 22:22:56 +0200 Subject: [PATCH 027/114] Syntax changed to !merge(space | comma) * Syntax changed to !merge(space | comma) * !merge(space) implemented by Expression instead of Value. * Added test for lonely property with !merge directive --- lib/less/parser.js | 13 +++++++------ lib/less/tree/ruleset.js | 8 +++++--- lib/less/tree/value.js | 5 ++--- test/css/merge.css | 3 +++ test/less/merge.less | 18 +++++++++++------- 5 files changed, 28 insertions(+), 19 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index ff1c7888..8c47d8db 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1501,12 +1501,13 @@ less.Parser = function Parser(env) { var separator; if (input.charAt(i) === '!') { if ($(/^! *merge\(/)) { - if (input.charAt(i) === ")") { - separator = " "; - } else { - separator = $(/[^)]/); - } - expect(")"); + separator = expect(/^ *space|comma */); + if (separator) { + separator = (separator.trim() === 'space') + ? ' ' + : ','; + } + expect(')'); } } return separator; diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index a7525b2b..7055785c 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -485,9 +485,11 @@ tree.Ruleset.prototype = { parts = groups[k]; if (parts.length > 1) { - parts[0].value = new (tree.Value)(parts.map(function (p) { - return p.value; - }), rule.merge); + rule = parts[0]; + + rule.value = new ((rule.merge === ' ') ? tree.Expression : tree.Value)(parts.map(function (p) { + return p.value; + })); } }); } diff --git a/lib/less/tree/value.js b/lib/less/tree/value.js index 7ee2067f..5aae88eb 100644 --- a/lib/less/tree/value.js +++ b/lib/less/tree/value.js @@ -1,8 +1,7 @@ (function (tree) { -tree.Value = function (value, separator) { +tree.Value = function (value) { this.value = value; - this.separator = separator ? separator : ","; }; tree.Value.prototype = { type: "Value", @@ -21,7 +20,7 @@ tree.Value.prototype = { toCSS: function (env) { return this.value.map(function (e) { return e.toCSS(env); - }).join(this.separator + ((env.compress || this.separator === " ") ? "" : " ")); + }).join(env.compress ? ',' : ', '); } }; diff --git a/test/css/merge.css b/test/css/merge.css index 86e0b473..861e41ba 100644 --- a/test/css/merge.css +++ b/test/css/merge.css @@ -24,3 +24,6 @@ transform: rotate(90deg) skew(30deg); transform: scale(2, 4) !important; } +.test8 { + transform: scale(2, 4); +} diff --git a/test/less/merge.less b/test/less/merge.less index e7801a13..5246f1c3 100644 --- a/test/less/merge.less +++ b/test/less/merge.less @@ -1,23 +1,23 @@ .first-transform() { - transform: rotate(90deg) skew(30deg) !merge(); + transform: rotate(90deg) skew(30deg) !merge(space); } .second-transform() { - transform: scale(2,4) !merge(); + transform: scale(2,4) !merge(space); } .third-transform() { transform: scaleX(45deg); } .fourth-transform() { - transform: scaleX(45deg) !merge(,); + transform: scaleX(45deg) !merge(comma); } .fifth-transform() { - transform: scale(2,4) !important !merge(); + transform: scale(2,4) !important !merge(space); } .first-background() { - background: url(img1.png) !merge(,); + background: url(img1.png) !merge(comma); } .second-background() { - background: url(img2.png) !merge(,); + background: url(img2.png) !merge(comma); } .test1 { @@ -31,7 +31,7 @@ .third-transform(); } .test3 { - // Can merge values with explicit separator + // Can merge values with comma separator .first-background(); .second-background(); } @@ -54,4 +54,8 @@ // Wont merge values from mixins that merked as !important, for backwards compatibility with css .first-transform(); .second-transform() !important; +} +.test8 { + // Ignores !merge if no peers found + .second-transform(); } \ No newline at end of file From 577d24cc9752ad5f7c2da5886978c47cf6aadc8e Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 30 Apr 2013 19:10:21 +0100 Subject: [PATCH 028/114] Fix merge tests when running in phantomjs --- test/css/merge.css | 4 ++-- test/less/merge.less | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/css/merge.css b/test/css/merge.css index 861e41ba..e27aa789 100644 --- a/test/css/merge.css +++ b/test/css/merge.css @@ -6,7 +6,7 @@ transform: scaleX(45deg); } .test3 { - background: url(img1.png), url(img2.png); + background: url(data://img1.png), url(data://img2.png); } .test4 { transform: rotate(90deg) skew(30deg); @@ -14,7 +14,7 @@ } .test5 { transform: scaleX(45deg); - background: url(img1.png); + background: url(data://img1.png); } .test6 { transform: rotate(90deg) skew(30deg); diff --git a/test/less/merge.less b/test/less/merge.less index 5246f1c3..29c122ca 100644 --- a/test/less/merge.less +++ b/test/less/merge.less @@ -14,10 +14,10 @@ transform: scale(2,4) !important !merge(space); } .first-background() { - background: url(img1.png) !merge(comma); + background: url(data://img1.png) !merge(comma); } .second-background() { - background: url(img2.png) !merge(comma); + background: url(data://img2.png) !merge(comma); } .test1 { From 5cb5f561c81d34fc60713cc4e13166da145e0295 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 30 Apr 2013 20:54:06 +0100 Subject: [PATCH 029/114] Convert property merging to the new agreed syntax of +: --- lib/less/parser.js | 31 +++++++++++++------------------ lib/less/tree/rule.js | 2 +- lib/less/tree/ruleset.js | 8 ++++---- test/css/merge.css | 27 ++++++++++----------------- test/less/merge.less | 34 ++++++++++++---------------------- 5 files changed, 40 insertions(+), 62 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index 8c47d8db..4977aafd 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1230,12 +1230,12 @@ less.Parser = function Parser(env) { } }, rule: function (tryAnonymous) { - var name, value, c = input.charAt(i), important, merge, match; + var name, value, c = input.charAt(i), important, merge = false, match; save(); if (c === '.' || c === '#' || c === '&') { return } - if (name = $(this.variable) || $(this.property)) { + if (name = $(this.variable) || $(this.ruleProperty)) { // prefer to try to parse first if its a variable or we are compressing // but always fallback on the other one value = !tryAnonymous && (env.compress || (name.charAt(0) === '@')) ? @@ -1244,7 +1244,10 @@ less.Parser = function Parser(env) { important = $(this.important); - merge = $(this.merge); + if (name[name.length-1] === "+") { + merge = true; + name = name.substr(0, name.length - 1); + } if (value && $(this.end)) { return new (tree.Rule)(name, value, important, merge, memo, env.currentFileInfo); @@ -1497,21 +1500,6 @@ less.Parser = function Parser(env) { return $(/^! *important/); } }, - merge: function () { - var separator; - if (input.charAt(i) === '!') { - if ($(/^! *merge\(/)) { - separator = expect(/^ *space|comma */); - if (separator) { - separator = (separator.trim() === 'space') - ? ' ' - : ','; - } - expect(')'); - } - } - return separator; - }, sub: function () { var a, e; @@ -1632,6 +1620,13 @@ less.Parser = function Parser(env) { if (name = $(/^(\*?-?[_a-z0-9-]+)\s*:/)) { return name[1]; } + }, + ruleProperty: function () { + var name; + + if (name = $(/^(\*?-?[_a-z0-9-]+)\s*(\+?)\s*:/)) { + return name[1] + (name[2] || ""); + } } } }; diff --git a/lib/less/tree/rule.js b/lib/less/tree/rule.js index eacd5419..f0ef9930 100644 --- a/lib/less/tree/rule.js +++ b/lib/less/tree/rule.js @@ -20,7 +20,7 @@ tree.Rule.prototype = { this.value = visitor.visit(this.value); }, toCSS: function (env) { - if (this.variable) { return "" } + if (this.variable) { return ""; } else { try { return this.name + (env.compress ? ':' : ': ') + diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 7055785c..a579ec43 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -178,7 +178,8 @@ tree.Ruleset.prototype = { debugInfo, // Line number debugging rule; - this.mergeRules(); + this.mergeRules(); + // Compile rules and rulesets for (var i = 0; i < this.rules.length; i++) { rule = this.rules[i]; @@ -468,8 +469,7 @@ tree.Ruleset.prototype = { if ((rule instanceof tree.Rule) && rule.merge) { key = [rule.name, - rule.important ? "!" : "", - rule.merge].join(","); + rule.important ? "!" : ""].join(","); if (!groups[key]) { parts = groups[key] = []; @@ -487,7 +487,7 @@ tree.Ruleset.prototype = { if (parts.length > 1) { rule = parts[0]; - rule.value = new ((rule.merge === ' ') ? tree.Expression : tree.Value)(parts.map(function (p) { + rule.value = new (tree.Value)(parts.map(function (p) { return p.value; })); } diff --git a/test/css/merge.css b/test/css/merge.css index e27aa789..4cf8c579 100644 --- a/test/css/merge.css +++ b/test/css/merge.css @@ -1,29 +1,22 @@ .test1 { - transform: rotate(90deg) skew(30deg) scale(2, 4); + transform: rotate(90deg), skew(30deg), scale(2, 4); } .test2 { - transform: rotate(90deg) skew(30deg); + transform: rotate(90deg), skew(30deg); transform: scaleX(45deg); } .test3 { - background: url(data://img1.png), url(data://img2.png); -} -.test4 { - transform: rotate(90deg) skew(30deg); - transform: scaleX(45deg); -} -.test5 { transform: scaleX(45deg); background: url(data://img1.png); } +.test4 { + transform: rotate(90deg), skew(30deg); + transform: scale(2, 4) !important; +} +.test5 { + transform: rotate(90deg), skew(30deg); + transform: scale(2, 4) !important; +} .test6 { - transform: rotate(90deg) skew(30deg); - transform: scale(2, 4) !important; -} -.test7 { - transform: rotate(90deg) skew(30deg); - transform: scale(2, 4) !important; -} -.test8 { transform: scale(2, 4); } diff --git a/test/less/merge.less b/test/less/merge.less index 29c122ca..52d0796e 100644 --- a/test/less/merge.less +++ b/test/less/merge.less @@ -1,61 +1,51 @@ .first-transform() { - transform: rotate(90deg) skew(30deg) !merge(space); + transform+: rotate(90deg), skew(30deg); } .second-transform() { - transform: scale(2,4) !merge(space); + transform+: scale(2,4); } .third-transform() { transform: scaleX(45deg); } .fourth-transform() { - transform: scaleX(45deg) !merge(comma); + transform+: scaleX(45deg); } .fifth-transform() { - transform: scale(2,4) !important !merge(space); + transform+: scale(2,4) !important; } .first-background() { - background: url(data://img1.png) !merge(comma); + background+: url(data://img1.png); } .second-background() { - background: url(data://img2.png) !merge(comma); + background+: url(data://img2.png); } .test1 { - // Can merge values with space separator + // Can merge values .first-transform(); .second-transform(); } .test2 { - // Wont merge values without !merge directive, for backwards compatibility with css + // Wont merge values without +: merge directive, for backwards compatibility with css .first-transform(); .third-transform(); } .test3 { - // Can merge values with comma separator + // Wont merge values from two sources with different properties + .fourth-transform(); .first-background(); - .second-background(); } .test4 { - // Wont merge values from two sources with different seperators - .first-transform(); - .fourth-transform(); -} -.test5 { - // Wont merge values from two sources with the same seperators but different properties - .fourth-transform(); - .first-background(); -} -.test6 { // Wont merge values from sources that merked as !important, for backwards compatibility with css .first-transform(); .fifth-transform(); } -.test7 { +.test5 { // Wont merge values from mixins that merked as !important, for backwards compatibility with css .first-transform(); .second-transform() !important; } -.test8 { +.test6 { // Ignores !merge if no peers found .second-transform(); } \ No newline at end of file From 5444e9482b96dc15cf6bd73eab418e2eb0c0dba2 Mon Sep 17 00:00:00 2001 From: Kim Joar Bekkelund Date: Mon, 6 May 2013 12:52:01 +0200 Subject: [PATCH 030/114] Simplify ifs --- lib/less/parser.js | 6 +----- lib/less/tree/rule.js | 5 +---- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index 4977aafd..1fca4673 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -223,11 +223,7 @@ less.Parser = function Parser(env) { if (typeof(tok) === 'string') { return input.charAt(i) === tok; } else { - if (tok.test(chunks[j])) { - return true; - } else { - return false; - } + return tok.test(chunks[j]); } } diff --git a/lib/less/tree/rule.js b/lib/less/tree/rule.js index f0ef9930..5fab422d 100644 --- a/lib/less/tree/rule.js +++ b/lib/less/tree/rule.js @@ -8,10 +8,7 @@ tree.Rule = function (name, value, important, merge, index, currentFileInfo, inl this.index = index; this.currentFileInfo = currentFileInfo; this.inline = inline || false; - - if (name.charAt(0) === '@') { - this.variable = true; - } else { this.variable = false } + this.variable = (name.charAt(0) === '@'); }; tree.Rule.prototype = { From 11197b34e3d9720f81d8289b629c490ed98cdda2 Mon Sep 17 00:00:00 2001 From: Kim Joar Bekkelund Date: Mon, 6 May 2013 12:52:21 +0200 Subject: [PATCH 031/114] Remove ; --- lib/less/parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index 1fca4673..ca7164f6 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -371,7 +371,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) { From bf88f660f2eed95baaffff2788f36fd003167489 Mon Sep 17 00:00:00 2001 From: Byron Wong Date: Fri, 10 May 2013 15:07:56 -0400 Subject: [PATCH 032/114] initial fix in browser.js and a small test addition --- lib/less/browser.js | 47 +++++++++++++++++++++++------------- test/browser-test-prepare.js | 7 +++--- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index edb9c244..e69ed45e 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -43,7 +43,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; }; @@ -134,9 +134,18 @@ less.refreshStyles = loadStyles; less.refresh(less.env === 'development'); function loadStyles(newVars) { - var styles = document.getElementsByTagName('style'); - for (var i = 0; i < styles.length; i++) { + // returned list of style tags gets updated automatically. because we track + // indexes, we need a non-mutating local copy to work with. + var nodeList = document.getElementsByTagName('style'); + var styles = new Array(nodeList.length); + var i; + // Array.slice not guaranteed to work on host objects, forcing a manual loop + for (i = 0; i < nodeList.length; i++) { + styles[i] = nodeList[i]; + } + for (i = 0; i < styles.length; i++) { if (styles[i].type.match(typePattern)) { + console.log(i); var env = new less.tree.parseEnv(less), lessText = styles[i].innerHTML || ''; env.filename = document.location.href.replace(/#.*$/, ''); @@ -145,19 +154,23 @@ function loadStyles(newVars) { lessText += "\n" + newVars; } - new(less.Parser)(env).parse(lessText, function (e, cssAST) { - if (e) { - return error(e, "inline"); + // use closure to store current value of i + var callback = (function(i) { + return function (e, cssAST) { + if (e) { + return error(e, "inline"); + } + var css = cssAST.toCSS(less); + var style = styles[i]; + style.type = 'text/css'; + if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.innerHTML = css; + } } - var css = cssAST.toCSS(less); - var style = styles[i]; - style.type = 'text/css'; - if (style.styleSheet) { - style.styleSheet.cssText = css; - } else { - style.innerHTML = css; - } - }); + })(i); + new(less.Parser)(env).parse(lessText, callback); } } } @@ -207,7 +220,7 @@ function extractUrlParts(url, baseUrl) { throw new Error("Could not parse sheet href - '"+url+"'"); } - // Stylesheets in IE don't always return the full path + // Stylesheets in IE don't always return the full path if (!urlParts[1] || urlParts[2]) { baseUrlParts = baseUrl.match(urlPartsRegex); if (!baseUrlParts) { @@ -218,7 +231,7 @@ function extractUrlParts(url, baseUrl) { urlParts[3] = baseUrlParts[3] + urlParts[3]; } } - + if (urlParts[3]) { directories = urlParts[3].replace("\\", "/").split("/"); diff --git a/test/browser-test-prepare.js b/test/browser-test-prepare.js index 31d3eaa8..f9c81343 100644 --- a/test/browser-test-prepare.js +++ b/test/browser-test-prepare.js @@ -15,11 +15,12 @@ var createTestRunnerPage = function(dir, exclude, testSuiteName, dir2) { readDirFilesSync(path.join("test", dir, 'less', dir2 || ""), /\.less$/, function (file) { var name = path.basename(file, '.less'), id = (dir ? dir + '-' : "") + 'less-' + (dir2 ? dir2 + "-" : "") + name; - + if (exclude && name.match(exclude)) { return; } - + output += '\n'; output += '\n'; + output += '\n'; }); output += String(fs.readFileSync(path.join('test/browser', 'template.htm'))).replace("{runner-name}", testSuiteName); @@ -44,4 +45,4 @@ createTestRunnerPage("browser", null, "relative-urls", "relative-urls"); createTestRunnerPage("browser", null, "rootpath", "rootpath"); createTestRunnerPage("browser", null, "rootpath-relative", "rootpath-relative"); createTestRunnerPage("browser", null, "production"); -createTestRunnerPage("browser", null, "modify-vars", "modify-vars"); \ No newline at end of file +createTestRunnerPage("browser", null, "modify-vars", "modify-vars"); From cd45ac90a3a94c0fcf6f201dc20e8924b3beafac Mon Sep 17 00:00:00 2001 From: Byron Wong Date: Fri, 10 May 2013 15:22:15 -0400 Subject: [PATCH 033/114] take out log --- lib/less/browser.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index e69ed45e..9391a9dd 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -145,7 +145,6 @@ function loadStyles(newVars) { } for (i = 0; i < styles.length; i++) { if (styles[i].type.match(typePattern)) { - console.log(i); var env = new less.tree.parseEnv(less), lessText = styles[i].innerHTML || ''; env.filename = document.location.href.replace(/#.*$/, ''); From c4dc89b74b08f2d200ca87661c65968da6c9834c Mon Sep 17 00:00:00 2001 From: Byron Wong Date: Thu, 16 May 2013 09:05:35 -0400 Subject: [PATCH 034/114] take out previous inline test attempt. add new test to runner-browser to test inline LESS --- test/browser-test-prepare.js | 1 - test/browser/runner-browser.js | 37 +++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/test/browser-test-prepare.js b/test/browser-test-prepare.js index f9c81343..5e487884 100644 --- a/test/browser-test-prepare.js +++ b/test/browser-test-prepare.js @@ -20,7 +20,6 @@ var createTestRunnerPage = function(dir, exclude, testSuiteName, dir2) { output += '\n'; output += '\n'; - output += '\n'; }); output += String(fs.readFileSync(path.join('test/browser', 'template.htm'))).replace("{runner-name}", testSuiteName); diff --git a/test/browser/runner-browser.js b/test/browser/runner-browser.js index 79c5e1c9..e34b1790 100644 --- a/test/browser/runner-browser.js +++ b/test/browser/runner-browser.js @@ -1,7 +1,42 @@ describe("less.js browser behaviour", function() { testLessEqualsInDocument(); - + it("has some log messages", function() { expect(logMessages.length).toBeGreaterThan(0); }); + + // test inline less in style tags by grabbing an assortment of less files and doing `@import`s + var testFiles = ['charsets', 'colors', 'comments', 'css-3', 'strings', 'media', 'mixins'], + testSheets = []; + + // setup style tags with less and link tags pointing to expected css output + for (var i = 0; i < testFiles.length; i++) { + var file = testFiles[i], + lessPath = '/less/' + file + '.less', + cssPath = '/css/' + file + '.css', + lessStyle = document.createElement('style'), + cssLink = document.createElement('link'), + lessText = '@import "' + lessPath + '";'; + lessStyle.type = 'text/less'; + lessStyle.id = file; + if (lessStyle.styleSheet) { + lessStyle.styleSheet.cssText = lessText; + } else { + lessStyle.innerHTML = lessText; + } + cssLink.rel = 'stylesheet'; + cssLink.type = 'text/css'; + cssLink.href = cssPath; + cssLink.id = 'expected-' + file; + + var head = document.getElementsByTagName('head')[0]; + head.appendChild(lessStyle); + head.appendChild(cssLink); + testSheets[i] = lessStyle; + } + + for (var i = 0; i < testFiles.length; i++) { + var sheet = testSheets[i]; + testSheet(sheet); + } }); \ No newline at end of file From 8ed519fd9807de9ba18544b7ea90ae947fff7284 Mon Sep 17 00:00:00 2001 From: Byron Wong Date: Fri, 7 Jun 2013 09:20:28 -0400 Subject: [PATCH 035/114] simplify by saving reference to style tag instead of saving index --- lib/less/browser.js | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index 9391a9dd..61f7d56c 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -134,19 +134,13 @@ less.refreshStyles = loadStyles; less.refresh(less.env === 'development'); function loadStyles(newVars) { - // returned list of style tags gets updated automatically. because we track - // indexes, we need a non-mutating local copy to work with. - var nodeList = document.getElementsByTagName('style'); - var styles = new Array(nodeList.length); - var i; - // Array.slice not guaranteed to work on host objects, forcing a manual loop - for (i = 0; i < nodeList.length; i++) { - styles[i] = nodeList[i]; - } - for (i = 0; i < styles.length; i++) { - if (styles[i].type.match(typePattern)) { + var styles = document.getElementsByTagName('style'), + style; + for (var i = 0; i < styles.length; i++) { + style = styles[i]; + if (style.type.match(typePattern)) { var env = new less.tree.parseEnv(less), - lessText = styles[i].innerHTML || ''; + lessText = style.innerHTML || ''; env.filename = document.location.href.replace(/#.*$/, ''); if (newVars) { env.useFileCache = true; @@ -154,13 +148,12 @@ function loadStyles(newVars) { } // use closure to store current value of i - var callback = (function(i) { + var callback = (function(style) { return function (e, cssAST) { if (e) { return error(e, "inline"); } var css = cssAST.toCSS(less); - var style = styles[i]; style.type = 'text/css'; if (style.styleSheet) { style.styleSheet.cssText = css; @@ -168,7 +161,7 @@ function loadStyles(newVars) { style.innerHTML = css; } } - })(i); + })(style); new(less.Parser)(env).parse(lessText, callback); } } From cc277d4f03d52404bc7c5193bca579a291e565cb Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 13 Jun 2013 11:33:50 +0100 Subject: [PATCH 036/114] move call to initiate loading of styles to the end of browser.js in case of syncronous load - make sure all the functions have been assigned before starting up --- lib/less/browser.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index 61f7d56c..0fc98811 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -131,8 +131,6 @@ less.refresh = function (reload, newVars) { }; less.refreshStyles = loadStyles; -less.refresh(less.env === 'development'); - function loadStyles(newVars) { var styles = document.getElementsByTagName('style'), style; @@ -585,3 +583,5 @@ function error(e, rootHref) { }, 10); } } + +less.refresh(less.env === 'development'); From 823ba78380309ac2785f8f494f3bb2ad408c991a Mon Sep 17 00:00:00 2001 From: David Cornu Date: Thu, 9 May 2013 13:21:27 -0300 Subject: [PATCH 037/114] Catch errors thrown by root.toCSS() --- lib/less/index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/less/index.js b/lib/less/index.js index cc5551ba..0e39c48b 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -20,15 +20,17 @@ var less = { if (callback) { parser.parse(input, function (e, root) { - callback(e, root && root.toCSS && root.toCSS(options)); + try { callback(e, root && root.toCSS && root.toCSS(options)); } + catch (err) { callback(err); } }); } else { ee = new(require('events').EventEmitter); process.nextTick(function () { parser.parse(input, function (e, root) { - if (e) { ee.emit('error', e) } - else { ee.emit('success', root.toCSS(options)) } + if (e) { return ee.emit('error', e); } + try { ee.emit('success', root.toCSS(options)); } + catch (err) { ee.emit('error', err); } }); }); return ee; From 9de0e30c647ebf9408bcb3dda6ed099e216df0eb Mon Sep 17 00:00:00 2001 From: ForbesLindesay Date: Thu, 13 Jun 2013 14:30:24 +0100 Subject: [PATCH 038/114] Fix global variable leaks --- lib/less/parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index ca7164f6..b65e3a03 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1226,7 +1226,7 @@ less.Parser = function Parser(env) { } }, rule: function (tryAnonymous) { - var name, value, c = input.charAt(i), important, merge = false, match; + var name, value, c = input.charAt(i), important, merge = false; save(); if (c === '.' || c === '#' || c === '&') { return } From 77966d44572b2bd8bb13400a12f6e36ed7cb0850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20O=C5=BEana?= Date: Wed, 27 Mar 2013 12:38:27 +0100 Subject: [PATCH 039/114] Improve comments stripping in compress Ignore comment during compress if starts with exclamation mark --- lib/less/tree/comment.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/less/tree/comment.js b/lib/less/tree/comment.js index 4da401f9..360c4119 100644 --- a/lib/less/tree/comment.js +++ b/lib/less/tree/comment.js @@ -8,7 +8,9 @@ tree.Comment = function (value, silent, index, currentFileInfo) { tree.Comment.prototype = { type: "Comment", toCSS: function (env) { - return (env.compress || (this.currentFileInfo && this.currentFileInfo.reference && !this.isReferenced)) ? '' : this.value; + var isReference = (this.currentFileInfo && this.currentFileInfo.reference && !this.isReferenced), + isCompressed = env.compress && !this.value.match(/^\/\*!/); + return (isReference || isCompressed) ? '' : this.value; }, eval: function () { return this; }, markReferenced: function () { From 7056f7b4d769144f0bf0a2efc7f062413f9c0e2e Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 13 Jun 2013 17:13:59 +0100 Subject: [PATCH 040/114] add tests and fix whitespace issue --- lib/less/parser.js | 2 +- test/css/compression/compression.css | 7 +++++-- test/less/compression/compression.less | 5 +++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index b65e3a03..0ad053fc 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -446,7 +446,7 @@ less.Parser = function Parser(env) { if (options.yuicompress && less.mode === 'node') { return require('ycssmin').cssmin(css, options.maxLineLen); } else if (options.compress) { - return css.replace(/(\s)+/g, "$1"); + return css.replace(/(^(\s)+)|((\s)+$)/g, ""); } else { return css; } diff --git a/test/css/compression/compression.css b/test/css/compression/compression.css index 2f831a9d..3fdd6a21 100644 --- a/test/css/compression/compression.css +++ b/test/css/compression/compression.css @@ -1,2 +1,5 @@ -#colours{color1:#fea;color2:#fea;color3:rgba(255,238,170,0.1);string:"#ffeeaa"} -dimensions{val:.1px;val:0;val:4cm;val:.2;val:5;angles-must-have-unit:0deg;width:auto\9} +#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/less/compression/compression.less b/test/less/compression/compression.less index c1a10e02..71e8e2a6 100644 --- a/test/less/compression/compression.less +++ b/test/less/compression/compression.less @@ -4,6 +4,11 @@ color3: rgba(255, 238, 170, 0.1); @color1: #fea; string: "@{color1}"; + /* comments are stripped */ + // both types! + /*! but not this type + Note preserved whitespace + */ } dimensions { val: 0.1px; From d2255e64f624d072d8031046fe533fe6e4b13115 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 19 Jun 2013 17:40:08 +0100 Subject: [PATCH 041/114] do not use spaces between | namespace operators. Fixes #1300 --- lib/less/tree/element.js | 2 +- test/css/css-3.css | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/less/tree/element.js b/lib/less/tree/element.js index 79cf6d18..55d47903 100644 --- a/lib/less/tree/element.js +++ b/lib/less/tree/element.js @@ -77,7 +77,7 @@ tree.Combinator.prototype = { '+' : env.compress ? '+' : ' + ', '~' : env.compress ? '~' : ' ~ ', '>' : env.compress ? '>' : ' > ', - '|' : env.compress ? '|' : ' | ' + '|' : '|' }[this.value]; } }; diff --git a/test/css/css-3.css b/test/css/css-3.css index fb5c02ba..5fd1dd2c 100644 --- a/test/css/css-3.css +++ b/test/css/css-3.css @@ -97,16 +97,16 @@ p::before { font-size: 10px; } @namespace foo url(http://www.example.com); -foo | h1 { +foo|h1 { color: blue; } -foo | * { +foo|* { color: yellow; } -| h1 { +|h1 { color: red; } -* | h1 { +*|h1 { color: green; } h1 { From 6df4e4897681dae1668d582bbb64bfebf0cbc3e3 Mon Sep 17 00:00:00 2001 From: Kevin Gillette Date: Fri, 14 Jun 2013 01:06:16 -0600 Subject: [PATCH 042/114] Added min and max builtins. --- lib/less/functions.js | 35 +++++++++++++++++++++++++++++++++++ test/css/functions.css | 6 ++++++ test/less/functions.less | 6 ++++++ 3 files changed, 47 insertions(+) diff --git a/lib/less/functions.js b/lib/less/functions.js index 361be6f4..016e8a96 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -266,6 +266,41 @@ tree.functions = { throw { type: "Argument", message: "argument must be a number" }; } }, + _minmax: function (name, args) { + args = Array.prototype.slice.call(args); + switch(args.length) { + case 0: throw { type: "Argument", message: "one or more arguments required" }; + case 1: return args[0]; + } + var isMin = name == 'min'; + function f (a, b) { + if(!(a instanceof tree.Dimension && b instanceof tree.Dimension)) { + return null; + } + var ua = a.unify(); + var ub = b.unify(); + if(ua.unit.compare(ub.unit) != 0) { + return null; + } + var val = ua.compare(ub); + if(isMin && val > 0 || !isMin && val < 0) { + return b; + } + return a; + } + var val = args.reduce(f); + if(val !== null) { + return val; + } + return new(tree.Anonymous)(name + + "(" + args.map(function (a) { return a.toCSS(); }).join(', ') + ")"); + }, + min: function () { + return this._minmax('min', arguments); + }, + max: function () { + return this._minmax('max', arguments); + }, argb: function (color) { return new(tree.Anonymous)(color.toARGB()); diff --git a/test/css/functions.css b/test/css/functions.css index d1392c35..12a831a0 100644 --- a/test/css/functions.css +++ b/test/css/functions.css @@ -81,6 +81,12 @@ pow: 64px; pow: 64; pow: 27; + min: min(3, 3em); + min: min(3pt, 3em); + min: 1%; + min: min(1%, 5px); + min: 3pt; + max: 3; percentage: 20%; color: #ff0011; tint: #898989; diff --git a/test/less/functions.less b/test/less/functions.less index 337e9eb6..929595b6 100644 --- a/test/less/functions.less +++ b/test/less/functions.less @@ -87,6 +87,12 @@ pow: pow(8px, 2); pow: pow(4, 3); pow: pow(3, 3em); + min: min(3, 3em); + min: min(3pt, 3em); + min: min(1%, 5%); + min: min(1%, 5px); + min: min(1pc, 3pt); + max: max(1, 3); percentage: percentage((10px / 50)); color: color("#ff0011"); tint: tint(#777777, 13); From 54a5f74c09e027a62b74777cd024a4980a3fc859 Mon Sep 17 00:00:00 2001 From: Kevin Gillette Date: Fri, 14 Jun 2013 18:15:15 -0600 Subject: [PATCH 043/114] min/max now always reduce compatible terms. --- lib/less/functions.js | 39 +++++++++++++++++++++++---------------- test/css/functions.css | 7 +++---- test/less/functions.less | 7 +++---- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/lib/less/functions.js b/lib/less/functions.js index 016e8a96..fd8a291c 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -272,28 +272,35 @@ tree.functions = { case 0: throw { type: "Argument", message: "one or more arguments required" }; case 1: return args[0]; } - var isMin = name == 'min'; - function f (a, b) { - if(!(a instanceof tree.Dimension && b instanceof tree.Dimension)) { - return null; + var i, j, cur, ucur, uref, u, + isMin = name == 'min', + order = [], // elems only contains original argument values. + values = {}; // key is the unit.toString() for unified tree.Dimension values, + // value is the index into the order array. + for(i = 0; i < args.length; i++) { + cur = args[i]; + if(!(cur instanceof tree.Dimension)) { + order.push(cur); + continue; } - var ua = a.unify(); - var ub = b.unify(); - if(ua.unit.compare(ub.unit) != 0) { - return null; + ucur = cur.unify(); + u = ucur.unit.toString(); + j = values[u]; + if(j === undefined) { + values[u] = order.length; + order.push(cur); + continue; } - var val = ua.compare(ub); - if(isMin && val > 0 || !isMin && val < 0) { - return b; + uref = order[j].unify(); + if(isMin && ucur.value < uref.value || !isMin && ucur.value > uref.value) { + order[j] = cur; } - return a; } - var val = args.reduce(f); - if(val !== null) { - return val; + if(order.length == 1) { + return order[0]; } return new(tree.Anonymous)(name + - "(" + args.map(function (a) { return a.toCSS(); }).join(', ') + ")"); + "(" + order.map(function (a) { return a.toCSS(); }).join(', ') + ")"); }, min: function () { return this._minmax('min', arguments); diff --git a/test/css/functions.css b/test/css/functions.css index 12a831a0..c1781b6c 100644 --- a/test/css/functions.css +++ b/test/css/functions.css @@ -81,12 +81,11 @@ pow: 64px; pow: 64; pow: 27; - min: min(3, 3em); - min: min(3pt, 3em); - min: 1%; - min: min(1%, 5px); + min: 0; + min: min("junk", 5); min: 3pt; max: 3; + max: max(8%, 1cm); percentage: 20%; color: #ff0011; tint: #898989; diff --git a/test/less/functions.less b/test/less/functions.less index 929595b6..32fe44b0 100644 --- a/test/less/functions.less +++ b/test/less/functions.less @@ -87,12 +87,11 @@ pow: pow(8px, 2); pow: pow(4, 3); pow: pow(3, 3em); - min: min(3, 3em); - min: min(3pt, 3em); - min: min(1%, 5%); - min: min(1%, 5px); + min: min(0); + min: min("junk", 6, 5); min: min(1pc, 3pt); max: max(1, 3); + max: max(3%, 1cm, 8%, 2mm); percentage: percentage((10px / 50)); color: color("#ff0011"); tint: tint(#777777, 13); From 3d46350a405853d006de071f43112a4234bf9c4f Mon Sep 17 00:00:00 2001 From: Kevin Gillette Date: Thu, 20 Jun 2013 14:40:30 -0600 Subject: [PATCH 044/114] Cleanup. Now responds to env.compress --- lib/less/functions.js | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/lib/less/functions.js b/lib/less/functions.js index fd8a291c..83291d33 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -266,47 +266,48 @@ tree.functions = { throw { type: "Argument", message: "argument must be a number" }; } }, - _minmax: function (name, args) { + _minmax: function (isMin, args) { args = Array.prototype.slice.call(args); switch(args.length) { case 0: throw { type: "Argument", message: "one or more arguments required" }; case 1: return args[0]; } - var i, j, cur, ucur, uref, u, - isMin = name == 'min', + var i, j, current, currentUnified, referenceUnified, unit, order = [], // elems only contains original argument values. values = {}; // key is the unit.toString() for unified tree.Dimension values, // value is the index into the order array. - for(i = 0; i < args.length; i++) { - cur = args[i]; - if(!(cur instanceof tree.Dimension)) { - order.push(cur); + for (i = 0; i < args.length; i++) { + current = args[i]; + if (!(current instanceof tree.Dimension)) { + order.push(current); continue; } - ucur = cur.unify(); - u = ucur.unit.toString(); - j = values[u]; - if(j === undefined) { - values[u] = order.length; - order.push(cur); + currentUnified = current.unify(); + unit = currentUnified.unit.toString(); + j = values[unit]; + if (j === undefined) { + values[unit] = order.length; + order.push(current); continue; } - uref = order[j].unify(); - if(isMin && ucur.value < uref.value || !isMin && ucur.value > uref.value) { - order[j] = cur; + referenceUnified = order[j].unify(); + if ( isMin && currentUnified.value < referenceUnified.value || + !isMin && currentUnified.value > referenceUnified.value) { + order[j] = current; } } - if(order.length == 1) { + if (order.length == 1) { return order[0]; } - return new(tree.Anonymous)(name + - "(" + order.map(function (a) { return a.toCSS(); }).join(', ') + ")"); + args = order.map(function (a) { return a.toCSS(this.env); }) + .join(this.env.compress ? "," : ", "); + return new(tree.Anonymous)((isMin ? "min" : "max") + "(" + args + ")"); }, min: function () { - return this._minmax('min', arguments); + return this._minmax(true, arguments); }, max: function () { - return this._minmax('max', arguments); + return this._minmax(false, arguments); }, argb: function (color) { return new(tree.Anonymous)(color.toARGB()); From cfe9ae7798c0de54b3ff7c2d0c16ee9df59b7e4b Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 27 Jun 2013 13:57:24 +0100 Subject: [PATCH 045/114] Add svg-gradient function. Fixes #1383 --- lib/less/functions.js | 77 +++++++++++++++++++++++++++++ test/browser/css/urls.css | 5 ++ test/browser/less/urls.less | 8 +++ test/css/urls.css | 5 ++ test/less/errors/svg-gradient1.less | 3 ++ test/less/errors/svg-gradient1.txt | 4 ++ test/less/errors/svg-gradient2.less | 3 ++ test/less/errors/svg-gradient2.txt | 4 ++ test/less/errors/svg-gradient3.less | 3 ++ test/less/errors/svg-gradient3.txt | 4 ++ test/less/urls.less | 9 ++++ 11 files changed, 125 insertions(+) create mode 100644 test/less/errors/svg-gradient1.less create mode 100644 test/less/errors/svg-gradient1.txt create mode 100644 test/less/errors/svg-gradient2.less create mode 100644 test/less/errors/svg-gradient2.txt create mode 100644 test/less/errors/svg-gradient3.less create mode 100644 test/less/errors/svg-gradient3.txt diff --git a/lib/less/functions.js b/lib/less/functions.js index 83291d33..93b13fdf 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -494,6 +494,83 @@ tree.functions = { var uri = "'data:" + mimetype + ',' + buf + "'"; return new(tree.URL)(new(tree.Anonymous)(uri)); + }, + + "svg-gradient": function(direction) { + + function throwArgumentDescriptor() { + throw { type: "Argument", message: "svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]" }; + } + + if (arguments.length < 3) { + throwArgumentDescriptor(); + } + var stops = Array.prototype.slice.call(arguments, 1), + gradientDirectionSvg, + gradientType = "linear", + rectangleDimension = 'x="0" y="0" width="1" height="1"', + useBase64 = true, + renderEnv = {compress: false}, + returner, + directionValue = direction.toCSS(renderEnv), + i, color, position, positionValue, alpha; + + switch (directionValue) { + case "to bottom": + gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"'; + break; + case "to right": + gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"'; + break; + case "to bottom right": + gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"'; + break; + case "to top right": + gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"'; + break; + case "radial": + gradientType = "radial"; + gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"'; + rectangleDimension = 'x="-50" y="-50" width="101" height="101"'; + break + default: + throw { type: "Argument", message: "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'radial'" }; + } + returner = '' + + '' + + '<' + gradientType + 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' + gradientDirectionSvg + '>'; + + for (i = 0; i < stops.length; i+= 1) { + if (stops[i].value) { + color = stops[i].value[0]; + position = stops[i].value[1]; + } else { + color = stops[i]; + position = undefined; + } + + if (!(color instanceof tree.Color) || (!((i === 0 || i+1 === stops.length) && position === undefined) && !(position instanceof tree.Dimension))) { + throwArgumentDescriptor(); + } + positionValue = position ? position.toCSS(renderEnv) : i === 0 ? "0%" : "100%"; + alpha = color.alpha; + color.alpha = 1; + returner += ''; + } + returner += '' + + ''; + + if (useBase64) { + // only works in node, needs interface to what is supported in environment + try { + returner = new Buffer(returner).toString('base64'); + } catch(e) { + useBase64 = false; + } + } + + returner = "'data:image/svg+xml" + (useBase64 ? ";base64" : "") + "," + returner + "'"; + return new(tree.URL)(new(tree.Anonymous)(returner)); } }; diff --git a/test/browser/css/urls.css b/test/browser/css/urls.css index 19ba27e6..ddb46b15 100644 --- a/test/browser/css/urls.css +++ b/test/browser/css/urls.css @@ -47,3 +47,8 @@ #data-uri-toobig { uri: url('http://localhost:8081/browser/less/../../data/data-uri-fail.png'); } +#svg-functions { + background-image: url('data:image/svg+xml,'); + background-image: url('data:image/svg+xml,'); + background-image: url('data:image/svg+xml,'); +} diff --git a/test/browser/less/urls.less b/test/browser/less/urls.less index 1f39a921..e640693b 100644 --- a/test/browser/less/urls.less +++ b/test/browser/less/urls.less @@ -47,3 +47,11 @@ #data-uri-toobig { uri: data-uri('../../data/data-uri-fail.png'); } +#svg-functions { + background-image: svg-gradient(to bottom, black, white); + background-image: svg-gradient(to bottom, black, orange 3%, white); + @green_5: green 5%; + @orange_percentage: 3%; + @orange_color: orange; + background-image: svg-gradient(to bottom, (mix(black, white) + #444) 1%, @orange_color @orange_percentage, ((@green_5)), white 95%); +} diff --git a/test/css/urls.css b/test/css/urls.css index f736962c..21b33bca 100644 --- a/test/css/urls.css +++ b/test/css/urls.css @@ -57,3 +57,8 @@ #data-uri-toobig { uri: url('../data/data-uri-fail.png'); } +#svg-functions { + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHZpZXdCb3g9IjAgMCAxIDEiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjwvbGluZWFyR3JhZGllbnQ+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkaWVudCkiIC8+PC9zdmc+'); + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHZpZXdCb3g9IjAgMCAxIDEiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIvPjxzdG9wIG9mZnNldD0iMyUiIHN0b3AtY29sb3I9IiNmZmE1MDAiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48L2xpbmVhckdyYWRpZW50PjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIGZpbGw9InVybCgjZ3JhZGllbnQpIiAvPjwvc3ZnPg=='); + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHZpZXdCb3g9IjAgMCAxIDEiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIxJSIgc3RvcC1jb2xvcj0iI2M0YzRjNCIvPjxzdG9wIG9mZnNldD0iMyUiIHN0b3AtY29sb3I9IiNmZmE1MDAiLz48c3RvcCBvZmZzZXQ9IjUlIiBzdG9wLWNvbG9yPSIjMDA4MDAwIi8+PHN0b3Agb2Zmc2V0PSI5NSUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48L2xpbmVhckdyYWRpZW50PjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIGZpbGw9InVybCgjZ3JhZGllbnQpIiAvPjwvc3ZnPg=='); +} diff --git a/test/less/errors/svg-gradient1.less b/test/less/errors/svg-gradient1.less new file mode 100644 index 00000000..c069ff72 --- /dev/null +++ b/test/less/errors/svg-gradient1.less @@ -0,0 +1,3 @@ +.a { + a: svg-gradient(horizontal, black, white); +} \ No newline at end of file diff --git a/test/less/errors/svg-gradient1.txt b/test/less/errors/svg-gradient1.txt new file mode 100644 index 00000000..c31bcb98 --- /dev/null +++ b/test/less/errors/svg-gradient1.txt @@ -0,0 +1,4 @@ +ArgumentError: error evaluating function `svg-gradient`: svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'radial' in {path}svg-gradient1.less on line 2, column 6: +1 .a { +2 a: svg-gradient(horizontal, black, white); +3 } diff --git a/test/less/errors/svg-gradient2.less b/test/less/errors/svg-gradient2.less new file mode 100644 index 00000000..ff14ef11 --- /dev/null +++ b/test/less/errors/svg-gradient2.less @@ -0,0 +1,3 @@ +.a { + a: svg-gradient(to bottom, black, orange, 45%, white); +} \ No newline at end of file diff --git a/test/less/errors/svg-gradient2.txt b/test/less/errors/svg-gradient2.txt new file mode 100644 index 00000000..7004115f --- /dev/null +++ b/test/less/errors/svg-gradient2.txt @@ -0,0 +1,4 @@ +ArgumentError: error evaluating function `svg-gradient`: svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position] in {path}svg-gradient2.less on line 2, column 6: +1 .a { +2 a: svg-gradient(to bottom, black, orange, 45%, white); +3 } diff --git a/test/less/errors/svg-gradient3.less b/test/less/errors/svg-gradient3.less new file mode 100644 index 00000000..8f185246 --- /dev/null +++ b/test/less/errors/svg-gradient3.less @@ -0,0 +1,3 @@ +.a { + a: svg-gradient(black, orange); +} \ No newline at end of file diff --git a/test/less/errors/svg-gradient3.txt b/test/less/errors/svg-gradient3.txt new file mode 100644 index 00000000..eb0b483e --- /dev/null +++ b/test/less/errors/svg-gradient3.txt @@ -0,0 +1,4 @@ +ArgumentError: error evaluating function `svg-gradient`: svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position] in {path}svg-gradient3.less on line 2, column 6: +1 .a { +2 a: svg-gradient(black, orange); +3 } diff --git a/test/less/urls.less b/test/less/urls.less index 82f2df07..3b1e81cf 100644 --- a/test/less/urls.less +++ b/test/less/urls.less @@ -55,3 +55,12 @@ } .add_an_import("file.css"); + +#svg-functions { + background-image: svg-gradient(to bottom, black, white); + background-image: svg-gradient(to bottom, black, orange 3%, white); + @green_5: green 5%; + @orange_percentage: 3%; + @orange_color: orange; + background-image: svg-gradient(to bottom, (mix(black, white) + #444) 1%, @orange_color @orange_percentage, ((@green_5)), white 95%); +} From 366f6755aa13cda53587d203148b5cede101660b Mon Sep 17 00:00:00 2001 From: Luke Page Date: Mon, 1 Jul 2013 09:46:42 +0100 Subject: [PATCH 046/114] guards on css styles, first draft. Only allows guards on 1 selector in the block, that being the last one. --- lib/less/parser.js | 29 +++++++++++++++++++++-------- lib/less/tree/media.js | 2 +- lib/less/tree/ruleset.js | 12 +++++++++++- lib/less/tree/selector.js | 23 ++++++++++++++++++----- test/css/css-guards.css | 12 ++++++++++++ test/less/css-guards.less | 31 +++++++++++++++++++++++++++++++ 6 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 test/css/css-guards.css create mode 100644 test/less/css-guards.less diff --git a/lib/less/parser.js b/lib/less/parser.js index 0ad053fc..d4207879 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1138,7 +1138,13 @@ less.Parser = function Parser(env) { return new(tree.Combinator)(null); } }, - + // + // A CSS selector (see selector below) + // with less extensions e.g. the ability to extend and guard + // + lessSelector: function () { + return this.selector(true); + }, // // A CSS Selector // @@ -1147,11 +1153,15 @@ less.Parser = function Parser(env) { // // Selectors are made out of one or more Elements, see above. // - selector: function () { - var sel, e, elements = [], c, extend, extendList = []; + selector: function (isLess) { + var sel, e, elements = [], c, extend, extendList = [], when, condition; - while ((extend = $(this.extend)) || (e = $(this.element))) { - if (extend) { + while ((isLess && (extend = $(this.extend))) || (isLess && (when = $(/^when/))) || (e = $(this.element))) { + if (when) { + condition = expect(this.conditions, 'expected condition'); + } else if (condition) { + error("CSS guard can only be used at the end of selector"); + } else if (extend) { extendList.push.apply(extendList, extend); } else { if (extendList.length) { @@ -1164,7 +1174,7 @@ less.Parser = function Parser(env) { if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') { break } } - if (elements.length > 0) { return new(tree.Selector)(elements, extendList, i, env.currentFileInfo); } + if (elements.length > 0) { return new(tree.Selector)(elements, extendList, condition, i, env.currentFileInfo); } if (extendList.length) { error("Extend must be used to extend a selector, it cannot be used on its own"); } }, attribute: function () { @@ -1207,10 +1217,13 @@ less.Parser = function Parser(env) { if (env.dumpLineNumbers) debugInfo = getDebugInfo(i, input, env); - while (s = $(this.selector)) { + while (s = $(this.lessSelector)) { selectors.push(s); $(this.comments); - if (! $(',')) { break } + if (! $(',')) { break; } + if (s.condition) { + error("Guards are only currently allowed on a single selector"); + } $(this.comments); } diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index 90e5b96d..a6c924a5 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -70,7 +70,7 @@ tree.Media.prototype = { rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) }, emptySelectors: function() { var el = new(tree.Element)('', '&', 0); - return [new(tree.Selector)([el], null, this.index, this.currentFileInfo)]; + return [new(tree.Selector)([el], null, null, this.index, this.currentFileInfo)]; }, markReferenced: function () { var rule, i; diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index a579ec43..03279363 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -116,6 +116,16 @@ tree.Ruleset.prototype = { matchArgs: function (args) { return !args || args.length === 0; }, + matchCondition: function (args, env) { + var lastSelector = this.selectors[this.selectors.length-1]; + if (lastSelector.condition && + !lastSelector.condition.eval( + new(tree.evalEnv)(env, + env.frames))) { + return false; + } + return true; + }, resetCache: function () { this._rulesets = null; this._variables = null; @@ -247,7 +257,7 @@ tree.Ruleset.prototype = { .filter(function(p) { var i; for(i = 0; i < p.length; i++) { - if (p[i].getIsReferenced()) { + if (p[i].getIsReferenced() && p[i].getIsOutput()) { return true; } return false; diff --git a/lib/less/tree/selector.js b/lib/less/tree/selector.js index 36156747..0fc8582d 100644 --- a/lib/less/tree/selector.js +++ b/lib/less/tree/selector.js @@ -1,19 +1,27 @@ (function (tree) { -tree.Selector = function (elements, extendList, index, currentFileInfo, isReferenced) { +tree.Selector = function (elements, extendList, condition, index, currentFileInfo, isReferenced) { this.elements = elements; this.extendList = extendList || []; + this.condition = condition; this.currentFileInfo = currentFileInfo || {}; this.isReferenced = isReferenced; + if (!condition) { + this.evaldCondition = true; + } }; tree.Selector.prototype = { type: "Selector", accept: function (visitor) { this.elements = visitor.visit(this.elements); - this.extendList = visitor.visit(this.extendList) + this.extendList = visitor.visit(this.extendList); + this.condition = visitor.visit(this.condition); }, - createDerived: function(elements, extendList) { - return new(tree.Selector)(elements, extendList || this.extendList, this.index, this.currentFileInfo, this.isReferenced); + createDerived: function(elements, extendList, evaldCondition) { + 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; + return newSelector; }, match: function (other) { var elements = this.elements, @@ -37,11 +45,13 @@ tree.Selector.prototype = { return true; }, eval: function (env) { + var evaldCondition = this.condition && this.condition.eval(env); + return this.createDerived(this.elements.map(function (e) { return e.eval(env); }), this.extendList.map(function(extend) { return extend.eval(env); - })); + }), evaldCondition); }, toCSS: function (env) { if (this._css) { return this._css } @@ -67,6 +77,9 @@ tree.Selector.prototype = { }, getIsReferenced: function() { return !this.currentFileInfo.reference || this.isReferenced; + }, + getIsOutput: function() { + return this.evaldCondition; } }; diff --git a/test/css/css-guards.css b/test/css/css-guards.css new file mode 100644 index 00000000..e190f0b0 --- /dev/null +++ b/test/css/css-guards.css @@ -0,0 +1,12 @@ +.light { + color: green; +} +.see-the { + color: orange; +} +.hide-the { + color: green; +} +.multiple-conditions-1 { + color: red; +} diff --git a/test/less/css-guards.less b/test/less/css-guards.less new file mode 100644 index 00000000..64e72589 --- /dev/null +++ b/test/less/css-guards.less @@ -0,0 +1,31 @@ + +.light when (lightness(@a) > 50%) { + color: green; +} +.dark when (lightness(@a) < 50%) { + color: orange; +} +@a: #ddd; + +.see-the { + @a: #444; // this mirrors what mixins do - they evaluate guards at the point of execution + .light(); + .dark(); +} + +.hide-the { + .light(); + .dark(); +} + +.multiple-conditions-1 when (@b = 1), (@c = 2), (@d = 3) { + color: red; +} + +.multiple-conditions-2 when (@b = 1), (@c = 2), (@d = 2) { + color: blue; +} + +@b: 2; +@c: 3; +@d: 3; \ No newline at end of file From 45184377cefb8bbc14b77346548717c305485b2a Mon Sep 17 00:00:00 2001 From: Luke Page Date: Mon, 1 Jul 2013 09:58:49 +0100 Subject: [PATCH 047/114] update changelog --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a26f2876..c88ed011 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,15 @@ -# 1.4.1 WIP +# 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";` - 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) + - Added min/max functions + - fix bad spaces between namespace operators + - do not compress comment if it begind with an exclamation mark + - change to not throw exceptions in toCSS - always return an error object + - allow property merging through the +: syntax + - Fix the saturate function to pass through when using the CSS syntax # 1.4.1 From 308bfe2e2bf3d1fb098674b3fb7007dcf9ee11d5 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Mon, 1 Jul 2013 10:16:37 +0100 Subject: [PATCH 048/114] update changelog to add svg-gradient function --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c88ed011..135da7b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - change to not throw exceptions in toCSS - always return an error object - allow property merging through the +: syntax - Fix the saturate function to pass through when using the CSS syntax + - Added svg-gradient function # 1.4.1 From 98f2fef2df317f94cf4f8bf01491a511916e1b6f Mon Sep 17 00:00:00 2001 From: Luke Page Date: Mon, 1 Jul 2013 10:43:14 +0100 Subject: [PATCH 049/114] add option to disable javascript. Fixes #688 --- CHANGELOG.md | 1 + bin/lessc | 3 ++ lib/less/env.js | 1 + lib/less/lessc_helper.js | 5 +-- lib/less/parser.js | 9 +++-- test/browser-test-prepare.js | 1 + test/browser/runner-no-js-errors.js | 6 +++ test/less-test.js | 49 ++++++++++++++---------- test/less/no-js-errors/no-js-errors.less | 3 ++ test/less/no-js-errors/no-js-errors.txt | 4 ++ 10 files changed, 56 insertions(+), 26 deletions(-) create mode 100644 test/browser/runner-no-js-errors.js create mode 100644 test/less/no-js-errors/no-js-errors.less create mode 100644 test/less/no-js-errors/no-js-errors.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 135da7b7..4e237734 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - allow property merging through the +: syntax - 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 # 1.4.1 diff --git a/bin/lessc b/bin/lessc index ec058215..91114875 100755 --- a/bin/lessc +++ b/bin/lessc @@ -111,6 +111,9 @@ args = args.filter(function (arg) { case 'no-ie-compat': options.ieCompat = false; break; + case 'no-js': + options.javascriptEnabled = false; + break; case 'include-path': if (checkArgFunc(arg, match[2])) { options.paths = match[2].split(os.type().match(/Windows/) ? ';' : ':') diff --git a/lib/less/env.js b/lib/less/env.js index 9f88cef4..203830e0 100644 --- a/lib/less/env.js +++ b/lib/less/env.js @@ -12,6 +12,7 @@ 'compress', // option - whether to compress 'processImports', // option - whether to process imports. if false then imports will not be imported 'syncImport', // option - whether to import synchronously + 'javascriptEnabled',// option - whether JavaScript is enabled. if undefined, defaults to true 'mime', // browser only - mime type for sheet import 'useFileCache', // browser only - whether to use the per file session cache 'currentFileInfo' // information about the current file - for error reporting and importing and making urls relative etc. diff --git a/lib/less/lessc_helper.js b/lib/less/lessc_helper.js index 576a8060..08d29992 100644 --- a/lib/less/lessc_helper.js +++ b/lib/less/lessc_helper.js @@ -33,6 +33,7 @@ var lessc_helper = { sys.puts(" -M, --depends Output a makefile import dependency list to stdout"); sys.puts(" --no-color Disable colorized output."); sys.puts(" --no-ie-compat Disable IE compatibility checks."); + sys.puts(" --no-js Disable JavaScript in less files"); sys.puts(" -l, --lint Syntax check only (lint)."); sys.puts(" -s, --silent Suppress output of error messages."); sys.puts(" --strict-imports Force evaluation of imports."); @@ -63,9 +64,7 @@ var lessc_helper = { sys.puts("Report bugs to: http://github.com/cloudhead/less.js/issues"); sys.puts("Home page: "); } - - -} +}; // Exports helper functions for (var h in lessc_helper) { exports[h] = lessc_helper[h] } diff --git a/lib/less/parser.js b/lib/less/parser.js index d4207879..71c4121a 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -790,10 +790,13 @@ less.Parser = function Parser(env) { javascript: function () { var str, j = i, e; - if (input.charAt(j) === '~') { j++, e = true } // Escaped strings - if (input.charAt(j) !== '`') { return } + if (input.charAt(j) === '~') { j++; e = true; } // Escaped strings + if (input.charAt(j) !== '`') { return; } + if (env.javascriptEnabled !== undefined && !env.javascriptEnabled) { + error("You are using JavaScript, which has been disabled."); + } - e && $('~'); + if (e) { $('~'); } if (str = $(/^`([^`]*)`/)) { return new(tree.JavaScript)(str[1], i, e); diff --git a/test/browser-test-prepare.js b/test/browser-test-prepare.js index 5e487884..1a1556f6 100644 --- a/test/browser-test-prepare.js +++ b/test/browser-test-prepare.js @@ -39,6 +39,7 @@ removeFiles("test/browser", /test-runner-[a-zA-Z-]*\.htm$/); createTestRunnerPage("", /javascript|urls/, "main"); createTestRunnerPage("", null, "legacy", "legacy"); createTestRunnerPage("", /javascript/, "errors", "errors"); +createTestRunnerPage("", null, "no-js-errors", "no-js-errors"); createTestRunnerPage("browser", null, "browser"); createTestRunnerPage("browser", null, "relative-urls", "relative-urls"); createTestRunnerPage("browser", null, "rootpath", "rootpath"); diff --git a/test/browser/runner-no-js-errors.js b/test/browser/runner-no-js-errors.js new file mode 100644 index 00000000..7c53aa98 --- /dev/null +++ b/test/browser/runner-no-js-errors.js @@ -0,0 +1,6 @@ +less.strictUnits = true; +less.javascriptEnabled = false; + +describe("less.js javascript disabled error tests", function() { + testLessErrorsInDocument(); +}); \ No newline at end of file diff --git a/test/less-test.js b/test/less-test.js index 3bddf333..50b868ca 100644 --- a/test/less-test.js +++ b/test/less-test.js @@ -26,8 +26,34 @@ less.tree.functions._color = function (str) { sys.puts("\n" + stylize("LESS", 'underline') + "\n"); runTestSet({strictMath: true, relativeUrls: true, silent: true}); +runTestSet({strictMath: true, strictUnits: true}, "errors/", + testErrors, null, getErrorPathReplacementFunction("errors")); +runTestSet({strictMath: true, strictUnits: true, javascriptEnabled: false}, "no-js-errors/", + testErrors, null, getErrorPathReplacementFunction("no-js-errors")); +runTestSet({strictMath: true, dumpLineNumbers: 'comments'}, "debug/", null, + function(name) { return name + '-comments'; }); +runTestSet({strictMath: true, dumpLineNumbers: 'mediaquery'}, "debug/", null, + function(name) { return name + '-mediaquery'; }); +runTestSet({strictMath: true, dumpLineNumbers: 'all'}, "debug/", null, + function(name) { return name + '-all'; }); +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}, "errors/", function(name, err, compiledLess, doReplacements) { +testNoOptions(); + +function getErrorPathReplacementFunction(dir) { + return function(input) { + return input.replace( + "{path}", path.join(process.cwd(), "/test/less/" + dir + "/")) + .replace("{pathrel}", path.join("test", "less", dir + "/")) + .replace("{pathhref}", "") + .replace("{404status}", "") + .replace(/\r\n/g, '\n'); + }; +} + +function testErrors(name, err, compiledLess, doReplacements) { fs.readFile(path.join('test/less/', name) + '.txt', 'utf8', function (e, expectedErr) { sys.print("- " + name + ": "); expectedErr = doReplacements(expectedErr, 'test/less/errors/'); @@ -40,31 +66,14 @@ runTestSet({strictMath: true, strictUnits: true}, "errors/", function(name, err, } else { var errMessage = less.formatError(err); if (errMessage === expectedErr) { - ok('OK'); + ok('OK'); } else { difference("FAIL", expectedErr, errMessage); } } sys.puts(""); - });}, null, function(input, directory) { - return input.replace( - "{path}", path.join(process.cwd(), "/test/less/errors/")) - .replace("{pathrel}", path.join("test", "less", "errors/")) - .replace("{pathhref}", "") - .replace("{404status}", "") - .replace(/\r\n/g, '\n'); }); - -runTestSet({strictMath: true, dumpLineNumbers: 'comments'}, "debug/", null, - function(name) { return name + '-comments'; }); -runTestSet({strictMath: true, dumpLineNumbers: 'mediaquery'}, "debug/", null, - function(name) { return name + '-mediaquery'; }); -runTestSet({strictMath: true, dumpLineNumbers: 'all'}, "debug/", null, - function(name) { return name + '-all'; }); -runTestSet({strictMath: true, relativeUrls: false, rootpath: "folder (1)/"}, "static-urls/"); -runTestSet({strictMath: true, compress: true}, "compression/"); -runTestSet({strictMath: false}, "legacy/"); -testNoOptions(); +} function globalReplacements(input, directory) { var p = path.join(process.cwd(), directory), diff --git a/test/less/no-js-errors/no-js-errors.less b/test/less/no-js-errors/no-js-errors.less new file mode 100644 index 00000000..15ef8a45 --- /dev/null +++ b/test/less/no-js-errors/no-js-errors.less @@ -0,0 +1,3 @@ +.a { + a: `1 + 1`; +} \ No newline at end of file diff --git a/test/less/no-js-errors/no-js-errors.txt b/test/less/no-js-errors/no-js-errors.txt new file mode 100644 index 00000000..d81dd2bd --- /dev/null +++ b/test/less/no-js-errors/no-js-errors.txt @@ -0,0 +1,4 @@ +SyntaxError: You are using JavaScript, which has been disabled. in {path}no-js-errors.less on line 2, column 6: +1 .a { +2 a: `1 + 1`; +3 } From 8ea150d4cbf6751ab257838825da14b697bc8439 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Mon, 1 Jul 2013 10:58:56 +0100 Subject: [PATCH 050/114] Fix typo in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e237734..904c3c38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - support for guards on selectors (currently only if you have a single selector) - Added min/max functions - fix bad spaces between namespace operators - - do not compress comment if it begind with an exclamation mark + - do not compress comment if it begins with an exclamation mark - change to not throw exceptions in toCSS - always return an error object - allow property merging through the +: syntax - Fix the saturate function to pass through when using the CSS syntax From 800b4218d5b59535609ffb20c1e707b9fd4f6d80 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 4 Jul 2013 21:30:29 +0100 Subject: [PATCH 051/114] Start abstracting re-organising logic into a visitor before css output. Will allow nodes to just be 'read' and debugInfo written into a sourcemap. part 1. --- Makefile | 2 ++ lib/less/index.js | 1 + lib/less/join-selector-visitor.js | 2 +- lib/less/parser.js | 3 ++ lib/less/to-css-visitor.js | 60 +++++++++++++++++++++++++++++++ lib/less/tree/alpha.js | 2 +- lib/less/tree/comment.js | 6 +++- lib/less/tree/directive.js | 42 ++++++++++++---------- lib/less/tree/media.js | 37 ++++++++++--------- lib/less/tree/ruleset.js | 20 +---------- lib/less/visitor.js | 13 ++++++- 11 files changed, 130 insertions(+), 58 deletions(-) create mode 100644 lib/less/to-css-visitor.js diff --git a/Makefile b/Makefile index b6c74172..db27cf04 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ less: ${SRC}/visitor.js\ ${SRC}/import-visitor.js\ ${SRC}/join-selector-visitor.js\ + ${SRC}/to-css-visitor.js\ ${SRC}/extend-visitor.js\ ${SRC}/browser.js\ build/amd.js >> ${DIST} @@ -68,6 +69,7 @@ rhino: ${SRC}/visitor.js\ ${SRC}/import-visitor.js\ ${SRC}/join-selector-visitor.js\ + ${SRC}/to-css-visitor.js\ ${SRC}/extend-visitor.js\ ${SRC}/functions.js\ ${SRC}/colors.js\ diff --git a/lib/less/index.js b/lib/less/index.js index 0e39c48b..94631555 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -214,5 +214,6 @@ require('./visitor.js'); require('./import-visitor.js'); require('./extend-visitor.js'); require('./join-selector-visitor.js'); +require('./to-css-visitor.js'); for (var k in less) { exports[k] = less[k]; } diff --git a/lib/less/join-selector-visitor.js b/lib/less/join-selector-visitor.js index c478447f..3a4c464b 100644 --- a/lib/less/join-selector-visitor.js +++ b/lib/less/join-selector-visitor.js @@ -30,7 +30,7 @@ }, visitMedia: function (mediaNode, visitArgs) { var context = this.contexts[this.contexts.length - 1]; - mediaNode.ruleset.root = (context.length === 0 || context[0].multiMedia); + mediaNode.rules[0].root = (context.length === 0 || context[0].multiMedia); } }; diff --git a/lib/less/parser.js b/lib/less/parser.js index 71c4121a..00690213 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -435,6 +435,9 @@ less.Parser = function Parser(env) { new(tree.processExtendsVisitor)() .run(evaldRoot); + new(tree.toCSSVisitor)() + .run(evaldRoot); + var css = evaldRoot.toCSS({ compress: Boolean(options.compress), dumpLineNumbers: env.dumpLineNumbers, diff --git a/lib/less/to-css-visitor.js b/lib/less/to-css-visitor.js new file mode 100644 index 00000000..098b8c87 --- /dev/null +++ b/lib/less/to-css-visitor.js @@ -0,0 +1,60 @@ +(function (tree) { + tree.toCSSVisitor = function() { + this._visitor = new tree.visitor(this); + }; + + tree.toCSSVisitor.prototype = { + isReplacing: true, + run: function (root) { + return this._visitor.visit(root); + }, + + visitRule: function (ruleNode, visitArgs) { + visitArgs.visitDeeper = false; + return ruleNode; + }, + + visitDirective: function(directiveNode, visitArgs) { + 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 + // be considered illegal css as it has to be on the first line + if (this.charset) { + if (directiveNode.debugInfo) { + var comment = new tree.Comment("/* " + directiveNode.toCSS({}).replace(/\n/g, "")+" */\n"); + comment.debugInfo = directiveNode.debugInfo; + return this._visitor.visit(comment); + } + return []; + } + this.charset = true; + } + return directiveNode; + }, + + visitRuleset: function (rulesetNode, visitArgs) { + var rule, rulesets = []; + if (! rulesetNode.root) { + // Compile rules and rulesets + for (var i = 0; i < rulesetNode.rules.length; i++) { + rule = rulesetNode.rules[i]; + + if (rule.rules) { + rulesets.push(this._visitor.visit(rule)); + rulesetNode.rules.splice(i, 1); + i--; + continue; + } + } + if (rulesets.length > 0 && rulesetNode.rules.length > 0) { + rulesets.splice(0, 0, rulesetNode); + } + } + if (rulesets.length === 0) { + rulesets = rulesetNode; + } + return rulesets; + } + }; + +})(require('./tree')); \ No newline at end of file diff --git a/lib/less/tree/alpha.js b/lib/less/tree/alpha.js index 2b10dd42..cb8db113 100644 --- a/lib/less/tree/alpha.js +++ b/lib/less/tree/alpha.js @@ -9,7 +9,7 @@ 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) { this.value = this.value.eval(env); } return this; }, toCSS: function () { diff --git a/lib/less/tree/comment.js b/lib/less/tree/comment.js index 360c4119..5d00c88a 100644 --- a/lib/less/tree/comment.js +++ b/lib/less/tree/comment.js @@ -8,9 +8,13 @@ tree.Comment = function (value, silent, index, currentFileInfo) { tree.Comment.prototype = { type: "Comment", toCSS: function (env) { + var debugInfo = ""; + if (this.debugInfo) { + debugInfo = tree.debugInfo(env, this); + } var isReference = (this.currentFileInfo && this.currentFileInfo.reference && !this.isReferenced), isCompressed = env.compress && !this.value.match(/^\/\*!/); - return (isReference || isCompressed) ? '' : this.value; + return (isReference || isCompressed) ? '' : (debugInfo + this.value); }, eval: function () { return this; }, markReferenced: function () { diff --git a/lib/less/tree/directive.js b/lib/less/tree/directive.js index d905eaaf..1e9500bc 100644 --- a/lib/less/tree/directive.js +++ b/lib/less/tree/directive.js @@ -4,8 +4,8 @@ tree.Directive = function (name, value, index, currentFileInfo) { this.name = name; if (Array.isArray(value)) { - this.ruleset = new(tree.Ruleset)([], value); - this.ruleset.allowImports = true; + this.rules = [new(tree.Ruleset)([], value)]; + this.rules[0].allowImports = true; } else { this.value = value; } @@ -14,7 +14,7 @@ tree.Directive = function (name, value, index, currentFileInfo) { tree.Directive.prototype = { type: "Directive", accept: function (visitor) { - this.ruleset = visitor.visit(this.ruleset); + this.rules = visitor.visit(this.rules); this.value = visitor.visit(this.value); }, toCSS: function (env) { @@ -23,36 +23,40 @@ tree.Directive.prototype = { return ""; } - if (this.ruleset) { - this.ruleset.root = true; - return this.name + (env.compress ? '{' : ' {\n ') + - this.ruleset.toCSS(env).trim().replace(/\n/g, '\n ') + - (env.compress ? '}': '\n}\n'); + 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 = css.trim().replace(/\n/g, '\n '); + return this.name + (env.compress ? '{' : ' {\n ') + css + (env.compress ? '}': '\n}\n'); } else { return this.name + ' ' + this.value.toCSS() + ';\n'; } }, eval: function (env) { var evaldDirective = this; - if (this.ruleset) { + if (this.rules) { env.frames.unshift(this); evaldDirective = new(tree.Directive)(this.name, null, this.index, this.currentFileInfo); - evaldDirective.ruleset = this.ruleset.eval(env); + evaldDirective.rules = [this.rules[0].eval(env)]; + evaldDirective.rules[0].root = true; env.frames.shift(); } return evaldDirective; }, - variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) }, - find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) }, - rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) }, + 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); }, + rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.rules[0]); }, markReferenced: function () { - var rule, i; + var i, rules; this.isReferenced = true; - if (this.ruleset) { - for (i = 0; i < this.ruleset.rules.length; i++) { - rule = this.ruleset.rules[i]; - if (rule.markReferenced) { - rule.markReferenced(); + if (this.rules) { + rules = this.rules[0].rules; + for (i = 0; i < rules.length; i++) { + if (rules[i].markReferenced) { + rules[i].markReferenced(); } } } diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index a6c924a5..57d68a2d 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -7,19 +7,25 @@ tree.Media = function (value, features, index, currentFileInfo) { var selectors = this.emptySelectors(); this.features = new(tree.Value)(features); - this.ruleset = new(tree.Ruleset)(selectors, value); - this.ruleset.allowImports = true; + this.rules = [new(tree.Ruleset)(selectors, value)]; + this.rules[0].allowImports = true; }; tree.Media.prototype = { type: "Media", accept: function (visitor) { this.features = visitor.visit(this.features); - this.ruleset = visitor.visit(this.ruleset); + this.rules = visitor.visit(this.rules); }, toCSS: function (env) { var features = this.features.toCSS(env); - var content = this.ruleset.toCSS(env).trim().replace(/\n/g, '\n '); + var content = ""; + + for(var i = 0; i < this.rules.length; i++) { + content += this.rules[i].toCSS(env).trim() + "\n"; + } + + content = content.trim().replace(/\n/g, '\n '); if (content.match(/\S/)) { return '@media ' + features + (env.compress ? '{' : ' {\n ') + content + @@ -36,7 +42,7 @@ tree.Media.prototype = { var media = new(tree.Media)([], [], this.index, this.currentFileInfo); if(this.debugInfo) { - this.ruleset.debugInfo = this.debugInfo; + this.rules[0].debugInfo = this.debugInfo; media.debugInfo = this.debugInfo; } var strictMathBypass = false; @@ -56,8 +62,8 @@ tree.Media.prototype = { env.mediaPath.push(media); env.mediaBlocks.push(media); - env.frames.unshift(this.ruleset); - media.ruleset = this.ruleset.eval(env); + env.frames.unshift(this.rules[0]); + media.rules = [this.rules[0].eval(env)]; env.frames.shift(); env.mediaPath.pop(); @@ -65,20 +71,19 @@ tree.Media.prototype = { return env.mediaPath.length === 0 ? media.evalTop(env) : media.evalNested(env) }, - variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) }, - find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) }, - rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) }, + 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); }, + rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.rules[0]); }, emptySelectors: function() { var el = new(tree.Element)('', '&', 0); return [new(tree.Selector)([el], null, null, this.index, this.currentFileInfo)]; }, markReferenced: function () { - var rule, i; + var i, rules = this.rules[0].rules; this.isReferenced = true; - for (i = 0; i < this.ruleset.rules.length; i++) { - rule = this.ruleset.rules[i]; - if (rule.markReferenced) { - rule.markReferenced(); + for (i = 0; i < rules.length; i++) { + if (rules[i].markReferenced) { + rules[i].markReferenced(); } } }, @@ -148,7 +153,7 @@ tree.Media.prototype = { } }, bubbleSelectors: function (selectors) { - this.ruleset = new(tree.Ruleset)(selectors.slice(0), [this.ruleset]); + this.rules = [new(tree.Ruleset)(selectors.slice(0), [this.rules[0]])]; } }; diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 03279363..a4d76e09 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -194,26 +194,8 @@ tree.Ruleset.prototype = { for (var i = 0; i < this.rules.length; i++) { rule = this.rules[i]; - if (rule.rules || (rule instanceof tree.Media)) { + if (rule.rules || (rule instanceof tree.Media) || rule instanceof tree.Directive) { rulesets.push(rule.toCSS(env)); - } else if (rule instanceof tree.Directive) { - var cssValue = rule.toCSS(env); - // Output only the first @charset definition as such - convert the others - // to comments in case debug is enabled - if (rule.name === "@charset") { - // Only output the debug info together with subsequent @charset definitions - // a comment (or @media statement) before the actual @charset directive would - // be considered illegal css as it has to be on the first line - if (env.charset) { - if (rule.debugInfo) { - rulesets.push(tree.debugInfo(env, rule)); - rulesets.push(new tree.Comment("/* "+cssValue.replace(/\n/g, "")+" */\n").toCSS(env)); - } - continue; - } - env.charset = true; - } - rulesets.push(cssValue); } else if (rule instanceof tree.Comment) { if (!rule.silent) { if (this.root) { diff --git a/lib/less/visitor.js b/lib/less/visitor.js index 7323281e..cd289902 100644 --- a/lib/less/visitor.js +++ b/lib/less/visitor.js @@ -39,7 +39,7 @@ for(i = 0; i < nodes.length; i++) { var evald = this.visit(nodes[i]); if (evald instanceof Array) { - newNodes = newNodes.concat(evald); + newNodes = newNodes.concat(this.flatten(evald)); } else { newNodes.push(evald); } @@ -48,6 +48,17 @@ return newNodes; } return nodes; + }, + flatten: function(arr, master) { + return arr.reduce(this.flattenReduce.bind(this), master || []); + }, + flattenReduce: function(sum, element) { + if (element instanceof Array) { + sum = this.flatten(element, sum); + } else { + sum.push(element); + } + return sum; } }; From 831e3432163b118e96d4702923809c16b9883682 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 4 Jul 2013 22:07:44 +0100 Subject: [PATCH 052/114] move comment filtering into visitor --- lib/less/parser.js | 2 +- lib/less/to-css-visitor.js | 18 +++++++++++++----- lib/less/tree/comment.js | 5 ++++- lib/less/tree/ruleset.js | 10 ++++------ lib/less/visitor.js | 9 +++++++-- test/css/comments.css | 2 +- 6 files changed, 30 insertions(+), 16 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index 00690213..9c7f878e 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -435,7 +435,7 @@ less.Parser = function Parser(env) { new(tree.processExtendsVisitor)() .run(evaldRoot); - new(tree.toCSSVisitor)() + new(tree.toCSSVisitor)({compress: Boolean(options.compress)}) .run(evaldRoot); var css = evaldRoot.toCSS({ diff --git a/lib/less/to-css-visitor.js b/lib/less/to-css-visitor.js index 098b8c87..f27dc328 100644 --- a/lib/less/to-css-visitor.js +++ b/lib/less/to-css-visitor.js @@ -1,6 +1,7 @@ (function (tree) { - tree.toCSSVisitor = function() { + tree.toCSSVisitor = function(env) { this._visitor = new tree.visitor(this); + this._env = env; }; tree.toCSSVisitor.prototype = { @@ -9,9 +10,16 @@ return this._visitor.visit(root); }, - visitRule: function (ruleNode, visitArgs) { - visitArgs.visitDeeper = false; - return ruleNode; + //visitRule: function (ruleNode, visitArgs) { + // visitArgs.visitDeeper = false; + // return ruleNode; + //}, + + visitComment: function (commentNode, visitArgs) { + if (commentNode.isSilent(this._env)) { + return []; + } + return commentNode; }, visitDirective: function(directiveNode, visitArgs) { @@ -21,7 +29,7 @@ // be considered illegal css as it has to be on the first line if (this.charset) { if (directiveNode.debugInfo) { - var comment = new tree.Comment("/* " + directiveNode.toCSS({}).replace(/\n/g, "")+" */\n"); + var comment = new tree.Comment("/* " + directiveNode.toCSS(this._env).replace(/\n/g, "")+" */\n"); comment.debugInfo = directiveNode.debugInfo; return this._visitor.visit(comment); } diff --git a/lib/less/tree/comment.js b/lib/less/tree/comment.js index 5d00c88a..b9724633 100644 --- a/lib/less/tree/comment.js +++ b/lib/less/tree/comment.js @@ -12,9 +12,12 @@ tree.Comment.prototype = { if (this.debugInfo) { debugInfo = tree.debugInfo(env, this); } + return debugInfo + this.value; + }, + isSilent: function(env) { var isReference = (this.currentFileInfo && this.currentFileInfo.reference && !this.isReferenced), isCompressed = env.compress && !this.value.match(/^\/\*!/); - return (isReference || isCompressed) ? '' : (debugInfo + this.value); + return this.silent || isReference || isCompressed; }, eval: function () { return this; }, markReferenced: function () { diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index a4d76e09..f205f90e 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -197,12 +197,10 @@ tree.Ruleset.prototype = { if (rule.rules || (rule instanceof tree.Media) || rule instanceof tree.Directive) { rulesets.push(rule.toCSS(env)); } else if (rule instanceof tree.Comment) { - if (!rule.silent) { - if (this.root) { - rulesets.push(rule.toCSS(env)); - } else { - rules.push(rule.toCSS(env)); - } + if (this.root) { + rulesets.push(rule.toCSS(env)); + } else { + rules.push(rule.toCSS(env)); } } else { if (rule.toCSS && !rule.variable) { diff --git a/lib/less/visitor.js b/lib/less/visitor.js index cd289902..617c2fee 100644 --- a/lib/less/visitor.js +++ b/lib/less/visitor.js @@ -35,11 +35,13 @@ return node; }, visitArray: function(nodes) { - var i, newNodes = []; + var i, newNodes = [], visitor = this; for(i = 0; i < nodes.length; i++) { var evald = this.visit(nodes[i]); if (evald instanceof Array) { - newNodes = newNodes.concat(this.flatten(evald)); + evald = this.flatten(evald); + evald.forEach(this.doAccept, this); + newNodes = newNodes.concat(evald); } else { newNodes.push(evald); } @@ -49,6 +51,9 @@ } return nodes; }, + doAccept: function (node) { + node.accept(this); + }, flatten: function(arr, master) { return arr.reduce(this.flattenReduce.bind(this), master || []); }, diff --git a/test/css/comments.css b/test/css/comments.css index de9f6c11..2306c6a8 100644 --- a/test/css/comments.css +++ b/test/css/comments.css @@ -54,7 +54,7 @@ -moz-border-radius: 8px /* moz only with operation */; } .test { - color: 1px //put in @b - causes problems! --->; + color: 1px; } #last { color: #0000ff; From 01fd5d679ef976d15971b04f295417b3ad5c8ee6 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Fri, 5 Jul 2013 17:13:27 +0100 Subject: [PATCH 053/114] remove variables before toCSS --- lib/less/to-css-visitor.js | 10 ++++++---- lib/less/tree/ruleset.js | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/less/to-css-visitor.js b/lib/less/to-css-visitor.js index f27dc328..856340a4 100644 --- a/lib/less/to-css-visitor.js +++ b/lib/less/to-css-visitor.js @@ -10,10 +10,12 @@ return this._visitor.visit(root); }, - //visitRule: function (ruleNode, visitArgs) { - // visitArgs.visitDeeper = false; - // return ruleNode; - //}, + visitRule: function (ruleNode, visitArgs) { + if (ruleNode.variable) { + return []; + } + return ruleNode; + }, visitComment: function (commentNode, visitArgs) { if (commentNode.isSilent(this._env)) { diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index f205f90e..a3ed1d3a 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -203,13 +203,13 @@ tree.Ruleset.prototype = { rules.push(rule.toCSS(env)); } } else { - if (rule.toCSS && !rule.variable) { + if (rule.toCSS) { if (this.firstRoot && rule instanceof tree.Rule) { throw { message: "properties must be inside selector blocks, they cannot be in the root.", index: rule.index, filename: rule.currentFileInfo ? rule.currentFileInfo.filename : null}; } rules.push(rule.toCSS(env)); - } else if (rule.value && !rule.variable) { + } else if (rule.value) { rules.push(rule.value.toString()); } } From 42dfeb8d3bf6f9ee0b5ecd6e3ea49b3cb6802874 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Fri, 5 Jul 2013 20:56:59 +0100 Subject: [PATCH 054/114] move the property check out of the ruleset and fix an issue in the visitor --- lib/less/to-css-visitor.js | 41 ++++++++++++++++++++++++++++++++++- lib/less/tree/ruleset.js | 16 +++----------- lib/less/visitor.js | 2 +- test/css/import-reference.css | 3 --- test/css/media.css | 4 +++- test/less/media.less | 4 +++- 6 files changed, 50 insertions(+), 20 deletions(-) diff --git a/lib/less/to-css-visitor.js b/lib/less/to-css-visitor.js index 856340a4..0af0f157 100644 --- a/lib/less/to-css-visitor.js +++ b/lib/less/to-css-visitor.js @@ -17,6 +17,10 @@ return ruleNode; }, + visitMixinDefinition: function (mixinNode, visitArgs) { + return []; + }, + visitComment: function (commentNode, visitArgs) { if (commentNode.isSilent(this._env)) { return []; @@ -42,21 +46,56 @@ return directiveNode; }, + checkPropertiesInRoot: function(rules) { + var ruleNode; + for(var i = 0; i < rules.length; i++) { + ruleNode = rules[i]; + if (ruleNode instanceof tree.Rule && !ruleNode.variable) { + throw { message: "properties must be inside selector blocks, they cannot be in the root.", + index: ruleNode.index, filename: ruleNode.currentFileInfo ? ruleNode.currentFileInfo.filename : null}; + } + } + }, + visitRuleset: function (rulesetNode, visitArgs) { var rule, rulesets = []; + if (rulesetNode.firstRoot) { + this.checkPropertiesInRoot(rulesetNode.rules); + } if (! rulesetNode.root) { + + rulesetNode.paths = rulesetNode.paths + .filter(function(p) { + var i; + for(i = 0; i < p.length; i++) { + if (p[i].getIsReferenced() && p[i].getIsOutput()) { + return true; + } + return false; + } + }); + // Compile rules and rulesets for (var i = 0; i < rulesetNode.rules.length; i++) { rule = rulesetNode.rules[i]; if (rule.rules) { + // visit because we are moving them out from being a child rulesets.push(this._visitor.visit(rule)); rulesetNode.rules.splice(i, 1); i--; continue; } } - if (rulesets.length > 0 && rulesetNode.rules.length > 0) { + // 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) { + 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) { rulesets.splice(0, 0, rulesetNode); } } diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index a3ed1d3a..9f7f0574 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -204,10 +204,6 @@ tree.Ruleset.prototype = { } } else { if (rule.toCSS) { - if (this.firstRoot && rule instanceof tree.Rule) { - throw { message: "properties must be inside selector blocks, they cannot be in the root.", - index: rule.index, filename: rule.currentFileInfo ? rule.currentFileInfo.filename : null}; - } rules.push(rule.toCSS(env)); } else if (rule.value) { rules.push(rule.value.toString()); @@ -234,15 +230,6 @@ tree.Ruleset.prototype = { if (rules.length > 0) { debugInfo = tree.debugInfo(env, this); selector = this.paths - .filter(function(p) { - var i; - for(i = 0; i < p.length; i++) { - if (p[i].getIsReferenced() && p[i].getIsOutput()) { - return true; - } - return false; - } - }) .map(function (p) { return p.map(function (s) { return s.toCSS(env); @@ -270,6 +257,9 @@ tree.Ruleset.prototype = { return css.join('') + (env.compress ? '\n' : ''); }, + toCSSRoot: function (env) { + }, + markReferenced: function () { for (var s = 0; s < this.selectors.length; s++) { this.selectors[s].markReferenced(); diff --git a/lib/less/visitor.js b/lib/less/visitor.js index 617c2fee..5a4cd402 100644 --- a/lib/less/visitor.js +++ b/lib/less/visitor.js @@ -40,7 +40,7 @@ var evald = this.visit(nodes[i]); if (evald instanceof Array) { evald = this.flatten(evald); - evald.forEach(this.doAccept, this); + //evald.forEach(this.doAccept, this); newNodes = newNodes.concat(evald); } else { newNodes.push(evald); diff --git a/test/css/import-reference.css b/test/css/import-reference.css index 811145d2..c2d055a7 100644 --- a/test/css/import-reference.css +++ b/test/css/import-reference.css @@ -1,6 +1,3 @@ -@media only screen and (max-width: 200px) { - width: 480px; -} /* The media statement above is invalid (no selector) We should ban invalid media queries with properties and no selector? diff --git a/test/css/media.css b/test/css/media.css index 9bd80b31..aeba6e77 100644 --- a/test/css/media.css +++ b/test/css/media.css @@ -103,7 +103,9 @@ } } @media only screen and (max-width: 200px) { - width: 480px; + body { + width: 480px; + } } @media print { @page :left { diff --git a/test/less/media.less b/test/less/media.less index 3d55d31d..f4ab7e5d 100644 --- a/test/less/media.less +++ b/test/less/media.less @@ -109,7 +109,9 @@ body { } @smartphone: ~"only screen and (max-width: 200px)"; @media @smartphone { - width: 480px; + body { + width: 480px; + } } @media print { From 5547e8a27e3b8598d78d3fcb08ac77b85f6a346b Mon Sep 17 00:00:00 2001 From: Daniel Stockman Date: Mon, 8 Jul 2013 14:55:30 -0700 Subject: [PATCH 055/114] 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 056/114] 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) + ':

    ' + - '
      ' + error.join('') + '
    '; + '
      ' + errors.join('') + '
    '; } 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 057/114] 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 058/114] 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 059/114] 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 060/114] 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 061/114] 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 062/114] 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 063/114] 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 064/114] 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 065/114] 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 066/114] 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 067/114] 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 068/114] 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 069/114] 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 070/114] 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 071/114] 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 072/114] 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 073/114] 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 074/114] 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 075/114] 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 076/114] 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 077/114] 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 078/114] 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 079/114] 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 080/114] 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 081/114] 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 082/114] 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 083/114] 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 084/114] 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 bbd18f7dd2d3a2f2c216f58d3006b1f6a7d6dc3b Mon Sep 17 00:00:00 2001 From: TooBug Date: Tue, 23 Jul 2013 23:27:41 +0800 Subject: [PATCH 085/114] fix an error about extract The example of `extract` should output 14, not 3 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c66c964..62f7007b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,7 +55,7 @@ - significant bug fixes to our debug options - other parameters can be used as defaults in mixins e.g. .a(@a, @b:@a) - an error is shown if properties are used outside of a ruleset - - added extract function which picks a value out of a list, e.g. extract(12 13 14, 3) => 3 + - added extract function which picks a value out of a list, e.g. extract(12 13 14, 3) => 14 - added luma, hsvhue, hsvsaturation, hsvvalue functions - added pow, pi, mod, tan, sin, cos, atan, asin, acos and sqrt math functions - added convert function, e.g. convert(1rad, deg) => value in degrees From 3a4a2074aac7b5eb3460b792f04cf0b3db062406 Mon Sep 17 00:00:00 2001 From: Rik Date: Wed, 24 Jul 2013 10:52:04 +0200 Subject: [PATCH 086/114] created less.log_level setting This will control the amount of logging in the javascript console that less will do while parsing. Options are: 2 - Information and errors 1 - Errors 0 - None Defaults to 2 --- lib/less/browser.js | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index bb800f5d..4e5532bf 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -11,6 +11,13 @@ less.env = less.env || (location.hostname == '127.0.0.1' || isFileProtocol ? 'development' : 'production'); +// The amount of logging in the javascript console. +// 2 - Information and errors +// 1 - Errors +// 0 - None +// Defaults to 2 +less.log_level = typeof(less.log_level) != 'undefined' ? less.log_level : 2 + // Load styles asynchronously (default: false) // // This is set to `false` by default, so that the body @@ -117,13 +124,13 @@ less.refresh = function (reload, newVars) { return error(e, sheet.href); } if (env.local) { - log("loading " + sheet.href + " from cache."); + log("loading " + sheet.href + " from cache.", 2); } else { - log("parsed " + sheet.href + " successfully."); + log("parsed " + sheet.href + " successfully.", 2); 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'); + log("css for " + sheet.href + " generated in " + (new(Date) - endTime) + 'ms', 2); + (env.remaining === 0) && log("css generated in " + (new(Date) - startTime) + 'ms', 2); endTime = new(Date); }, reload, newVars); @@ -416,13 +423,13 @@ function createCSS(styles, sheet, lastModified) { // Don't update the local store if the file wasn't modified if (lastModified && cache) { - log('saving ' + href + ' to cache.'); + log('saving ' + href + ' to cache.', 2); try { cache.setItem(href, styles); cache.setItem(href + ':timestamp', lastModified); } catch(e) { //TODO - could do with adding more robust error handling - log('failed to save'); + log('failed to save', 1); } } } @@ -434,7 +441,7 @@ function xhr(url, type, callback, errback) { if (typeof(xhr.overrideMimeType) === 'function') { xhr.overrideMimeType('text/css'); } - log("XHR: Getting '" + url + "'"); + log("XHR: Getting '" + url + "'", 2); xhr.open('GET', url, async); xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5'); xhr.send(null); @@ -472,7 +479,7 @@ function getXMLHttpRequest() { try { return new(ActiveXObject)("MSXML2.XMLHTTP.3.0"); } catch (e) { - log("browser doesn't support AJAX."); + log("browser doesn't support AJAX.", 1); return null; } } @@ -482,8 +489,10 @@ function removeNode(node) { return node && node.parentNode.removeChild(node); } -function log(str) { - if (less.env == 'development' && typeof(console) !== "undefined") { console.log('less: ' + str) } +function log(str, level) { + if (less.env == 'development' && typeof(console) !== 'undefined' && less.log_level >= level) { + console.log('less: ' + str) + } } function error(e, rootHref) { From adf55b225ecb6111217d04bf345fe9a2a33bb955 Mon Sep 17 00:00:00 2001 From: Rik Date: Wed, 24 Jul 2013 11:03:05 +0200 Subject: [PATCH 087/114] added semi-colons to end of lines where missing --- lib/less/browser.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index 4e5532bf..4c1c5d8a 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -16,7 +16,7 @@ less.env = less.env || (location.hostname == '127.0.0.1' || // 1 - Errors // 0 - None // Defaults to 2 -less.log_level = typeof(less.log_level) != 'undefined' ? less.log_level : 2 +less.log_level = typeof(less.log_level) != 'undefined' ? less.log_level : 2; // Load styles asynchronously (default: false) // @@ -491,7 +491,7 @@ function removeNode(node) { function log(str, level) { if (less.env == 'development' && typeof(console) !== 'undefined' && less.log_level >= level) { - console.log('less: ' + str) + console.log('less: ' + str); } } From f16e5142cf3b01c490395c36dfe7f851053fa3db Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 24 Jul 2013 13:46:20 +0100 Subject: [PATCH 088/114] 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 089/114] 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 b711d16f0ddb68239545e85291d336a6fad87771 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Mon, 8 Jul 2013 13:39:48 +0100 Subject: [PATCH 090/114] 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 0809b5d0..c7fe2a55 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.2 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 d32b15b1..81d4473e 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 b20dba56..c07e9651 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 c68352dc17c0a4ab83b1080499149af640b988eb Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 10 Jul 2013 17:16:56 +0100 Subject: [PATCH 091/114] Fix issue with svg-gradient breaking the opacity --- lib/less/functions.js | 3 +-- lib/less/tree/color.js | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/less/functions.js b/lib/less/functions.js index 93b13fdf..39ac6e82 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -554,8 +554,7 @@ tree.functions = { } positionValue = position ? position.toCSS(renderEnv) : i === 0 ? "0%" : "100%"; alpha = color.alpha; - color.alpha = 1; - returner += ''; + returner += ''; } returner += '' + ''; diff --git a/lib/less/tree/color.js b/lib/less/tree/color.js index 95d5a101..ae8878ec 100644 --- a/lib/less/tree/color.js +++ b/lib/less/tree/color.js @@ -40,24 +40,18 @@ tree.Color.prototype = { return Math.round(c); }).concat(this.alpha).join(',' + (compress ? '' : ' ')) + ")"; } else { - var color = this.rgb.map(function (i) { - i = Math.round(i); - i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); - return i.length === 1 ? '0' + i : i; - }).join(''); + var color = this.toRGB(); if (compress) { - color = color.split(''); + var splitcolor = color.split(''); // Convert color to short format - if (color[0] == color[1] && color[2] == color[3] && color[4] == color[5]) { - color = color[0] + color[2] + color[4]; - } else { - color = color.join(''); + if (splitcolor[1] == splitcolor[2] && splitcolor[3] == splitcolor[4] && splitcolor[5] == splitcolor[6]) { + color = '#' + splitcolor[1] + splitcolor[3] + splitcolor[5]; } } - return '#' + color; + return color; } }, @@ -80,6 +74,14 @@ tree.Color.prototype = { return new(tree.Color)(result, this.alpha + other.alpha); }, + toRGB: function () { + return '#' + this.rgb.map(function (i) { + i = Math.round(i); + i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); + return i.length === 1 ? '0' + i : i; + }).join(''); + }, + toHSL: function () { var r = this.rgb[0] / 255, g = this.rgb[1] / 255, From 41f53f2fc2f2ca514ad3a5a4e706655e039bc1d1 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 11 Jul 2013 08:44:03 +0100 Subject: [PATCH 092/114] correct radial gradient format to w3c spec --- lib/less/functions.js | 5 +++-- test/less/errors/svg-gradient1.txt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/less/functions.js b/lib/less/functions.js index 39ac6e82..cddfdb20 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -528,13 +528,14 @@ tree.functions = { case "to top right": gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"'; break; - case "radial": + case "ellipse": + case "ellipse at center": gradientType = "radial"; gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"'; rectangleDimension = 'x="-50" y="-50" width="101" height="101"'; break default: - throw { type: "Argument", message: "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'radial'" }; + throw { type: "Argument", message: "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" }; } returner = '' + '' + diff --git a/test/less/errors/svg-gradient1.txt b/test/less/errors/svg-gradient1.txt index c31bcb98..ec662fe6 100644 --- a/test/less/errors/svg-gradient1.txt +++ b/test/less/errors/svg-gradient1.txt @@ -1,4 +1,4 @@ -ArgumentError: error evaluating function `svg-gradient`: svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'radial' in {path}svg-gradient1.less on line 2, column 6: +ArgumentError: error evaluating function `svg-gradient`: svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center' in {path}svg-gradient1.less on line 2, column 6: 1 .a { 2 a: svg-gradient(horizontal, black, white); 3 } From 4bedef4fd3748257cf04a42c8e0181c0116d754b Mon Sep 17 00:00:00 2001 From: Luke Page Date: Fri, 12 Jul 2013 14:24:17 +0100 Subject: [PATCH 093/114] Fix issue with css guards not hiding inner classes --- lib/less/join-selector-visitor.js | 4 ++++ lib/less/tree/directive.js | 1 + test/css/css-guards.css | 6 ++++++ test/less/css-guards.less | 35 ++++++++++++++++++++++++++++++- 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/less/join-selector-visitor.js b/lib/less/join-selector-visitor.js index 3a4c464b..7c9a1af8 100644 --- a/lib/less/join-selector-visitor.js +++ b/lib/less/join-selector-visitor.js @@ -21,6 +21,10 @@ this.contexts.push(paths); if (! rulesetNode.root) { + rulesetNode.selectors = rulesetNode.selectors.filter(function(selector) { return selector.getIsOutput(); }); + if (rulesetNode.selectors.length === 0) { + rulesetNode.rules.length = 0; + } rulesetNode.joinSelectors(paths, context, rulesetNode.selectors); rulesetNode.paths = paths; } diff --git a/lib/less/tree/directive.js b/lib/less/tree/directive.js index 1e9500bc..23c6016a 100644 --- a/lib/less/tree/directive.js +++ b/lib/less/tree/directive.js @@ -10,6 +10,7 @@ tree.Directive = function (name, value, index, currentFileInfo) { this.value = value; } this.currentFileInfo = currentFileInfo; + }; tree.Directive.prototype = { type: "Directive", diff --git a/test/css/css-guards.css b/test/css/css-guards.css index e190f0b0..dbb27dae 100644 --- a/test/css/css-guards.css +++ b/test/css/css-guards.css @@ -10,3 +10,9 @@ .multiple-conditions-1 { color: red; } +.inheritance .test { + color: black; +} +.inheritance:hover { + color: pink; +} diff --git a/test/less/css-guards.less b/test/less/css-guards.less index 64e72589..41fbfbf5 100644 --- a/test/less/css-guards.less +++ b/test/less/css-guards.less @@ -28,4 +28,37 @@ @b: 2; @c: 3; -@d: 3; \ No newline at end of file +@d: 3; + +.inheritance when (@b = 2) { + .test { + color: black; + } + &:hover { + color: pink; + } + .hideme when (@b = 1) { + color: green; + } + & when (@b = 1) { + hideme: green; + } +} + +.hideme when (@b = 1) { + .test { + color: black; + } + &:hover { + color: pink; + } + .hideme when (@b = 1) { + color: green; + } +} + +& when (@b = 1) { + .hideme { + color: red; + } +} \ No newline at end of file From dc45573cf9ead19da355872e6fbbd6270737453f Mon Sep 17 00:00:00 2001 From: Matt Brennan Date: Tue, 30 Jul 2013 16:56:14 +0100 Subject: [PATCH 094/114] Better "JavaScript evaluation error" message Less's messages for JavaScript evaluation errors do not contain the actual error thrown, making them very hard to debug. This PR adds the error message to the log. --- lib/less/tree/javascript.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/less/tree/javascript.js b/lib/less/tree/javascript.js index eaa9c0fe..b196b2b7 100644 --- a/lib/less/tree/javascript.js +++ b/lib/less/tree/javascript.js @@ -19,7 +19,7 @@ tree.JavaScript.prototype = { try { expression = new(Function)('return (' + expression + ')'); } catch (e) { - throw { message: "JavaScript evaluation error: `" + expression + "`" , + throw { message: "JavaScript evaluation error: " + e.message + " from `" + expression + "`" , index: this.index }; } From 1563d5c0874e2e3bece3fbf089a9bb149ab9c217 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 31 Jul 2013 21:01:15 +0100 Subject: [PATCH 095/114] 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 096/114] 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 From e0561e3d9558db419a553be9018d87cea0cf8738 Mon Sep 17 00:00:00 2001 From: rjgotten Date: Wed, 7 Aug 2013 10:44:12 +0200 Subject: [PATCH 097/114] Normalize URLs in generated CSS Adds a normalizePath method to tree.evalEnv.prototype with which to normalize paths, i.e. , remove /../ or /./ segments stuck in the middle. Unit tests have been updated to reflect these changes. --- lib/less/env.js | 27 +++++++++++++++++++++++++++ lib/less/tree/import.js | 15 ++++++++++----- lib/less/tree/url.js | 2 ++ test/css/static-urls/urls.css | 6 +++--- test/css/urls.css | 6 +++--- 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/lib/less/env.js b/lib/less/env.js index 91b0027e..86f72681 100644 --- a/lib/less/env.js +++ b/lib/less/env.js @@ -89,6 +89,33 @@ return !/^(?:[a-z-]+:|\/)/.test(path); }; + tree.evalEnv.prototype.normalizePath = function( path ) { + var + segments = path.split("/").reverse(), + segment; + + path = []; + while (segments.length !== 0 ) { + segment = segments.pop(); + switch( segment ) { + case ".": + break; + case "..": + if ((path.length === 0) || (path[path.length - 1] === "..")) { + path.push( segment ); + } else { + path.pop(); + } + break; + default: + path.push( segment ); + break; + } + } + + return path.join("/"); + }; + //todo - do the same for the toCSS env //tree.toCSSEnv = function (options) { //}; diff --git a/lib/less/tree/import.js b/lib/less/tree/import.js index 9a93baaf..50be1086 100644 --- a/lib/less/tree/import.js +++ b/lib/less/tree/import.js @@ -70,13 +70,18 @@ tree.Import.prototype = { evalPath: function (env) { var path = this.path.eval(env); var rootpath = this.currentFileInfo && this.currentFileInfo.rootpath; - if (rootpath && !(path instanceof tree.URL)) { - var pathValue = path.value; - // Add the base path if the import is relative - if (pathValue && env.isPathRelative(pathValue)) { - path.value = rootpath + pathValue; + + if (!(path instanceof tree.URL)) { + if (rootpath) { + var pathValue = path.value; + // Add the base path if the import is relative + if (pathValue && env.isPathRelative(pathValue)) { + path.value = rootpath + pathValue; + } } + path.value = env.normalizePath(path.value); } + return path; }, eval: function (env) { diff --git a/lib/less/tree/url.js b/lib/less/tree/url.js index 82ef8d7a..0be75acd 100644 --- a/lib/less/tree/url.js +++ b/lib/less/tree/url.js @@ -24,6 +24,8 @@ tree.URL.prototype = { val.value = rootpath + val.value; } + val.value = ctx.normalizePath(val.value); + return new(tree.URL)(val, null); } }; diff --git a/test/css/static-urls/urls.css b/test/css/static-urls/urls.css index b5a690e9..71b36115 100644 --- a/test/css/static-urls/urls.css +++ b/test/css/static-urls/urls.css @@ -1,4 +1,4 @@ -@import "folder (1)/../css/background.css"; +@import "css/background.css"; @import "folder (1)/import-test-d.css"; @font-face { @@ -31,11 +31,11 @@ #logo { width: 100px; height: 100px; - background: url('folder (1)/../assets/logo.png'); + background: url('assets/logo.png'); } @font-face { font-family: xecret; - src: url('folder (1)/../assets/xecret.ttf'); + src: url('assets/xecret.ttf'); } #secret { font-family: xecret, sans-serif; diff --git a/test/css/urls.css b/test/css/urls.css index f736962c..bbc3beac 100644 --- a/test/css/urls.css +++ b/test/css/urls.css @@ -1,4 +1,4 @@ -@import "import/../css/background.css"; +@import "css/background.css"; @import "import/import-test-d.css"; @@ -35,11 +35,11 @@ #logo { width: 100px; height: 100px; - background: url('import/imports/../assets/logo.png'); + background: url('import/assets/logo.png'); } @font-face { font-family: xecret; - src: url('import/imports/../assets/xecret.ttf'); + src: url('import/assets/xecret.ttf'); } #secret { font-family: xecret, sans-serif; From ee1d54a173120840767a908a3ed4f45bfc1dc72b Mon Sep 17 00:00:00 2001 From: Oliver Wong Date: Fri, 9 Aug 2013 21:44:52 -0500 Subject: [PATCH 098/114] fix dom manipulation --- lib/less/browser.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index 844a4af1..ca3afa7d 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -410,10 +410,14 @@ function createCSS(styles, sheet, lastModified) { // to replace oldCss with an updated stylesheet if (oldCss === null || keepOldCss === false) { var nextEl = sheet && sheet.nextSibling || null; - (nextEl || document.getElementsByTagName('head')[0]).parentNode.insertBefore(css, nextEl); + if (nextEl) { + nextEl.parentNode.insertBefore(css, nextEl); + } else { + document.getElementsByTagName('head')[0].appendChild(css); + } } if (oldCss && keepOldCss === false) { - head.removeChild(oldCss); + oldCss.parentNode.removeChild(oldCss); } // Don't update the local store if the file wasn't modified From 0bf2354f12f9f9fdc28ede0bae292c44a6d53004 Mon Sep 17 00:00:00 2001 From: Oliver Wong Date: Fri, 9 Aug 2013 22:02:16 -0500 Subject: [PATCH 099/114] use already existing head var --- lib/less/browser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index ca3afa7d..79267092 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -413,7 +413,7 @@ function createCSS(styles, sheet, lastModified) { if (nextEl) { nextEl.parentNode.insertBefore(css, nextEl); } else { - document.getElementsByTagName('head')[0].appendChild(css); + head.appendChild(css); } } if (oldCss && keepOldCss === false) { From a10be4d6a6c454a0e253254a056de209556e9ee5 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 15 Aug 2013 21:30:17 +0100 Subject: [PATCH 100/114] Fix possible race condition in the import --- lib/less/import-visitor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/less/import-visitor.js b/lib/less/import-visitor.js index 3d3a2156..5a972211 100644 --- a/lib/less/import-visitor.js +++ b/lib/less/import-visitor.js @@ -61,7 +61,7 @@ if (root) { importNode.root = root; - if (!inlineCSS) { + if (!inlineCSS && !importNode.skip) { new(tree.importVisitor)(importVisitor._importer, subFinish, env) .run(root); return; From bc568f7bd85c725c1f58abcbfbddad4a0db8ea46 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 15 Aug 2013 21:58:02 +0100 Subject: [PATCH 101/114] Fix bad reliance on env variable, causing error when interpolating a comma seperated value --- lib/less/tree/value.js | 2 +- test/css/strings.css | 3 +++ test/less/strings.less | 6 ++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/less/tree/value.js b/lib/less/tree/value.js index 7f110f65..93630c3c 100644 --- a/lib/less/tree/value.js +++ b/lib/less/tree/value.js @@ -22,7 +22,7 @@ tree.Value.prototype = { for(i = 0; i < this.value.length; i++) { this.value[i].genCSS(env, output); if (i+1 < this.value.length) { - output.add(env.compress ? ',' : ', '); + output.add((env && env.compress) ? ',' : ', '); } } }, diff --git a/test/css/strings.css b/test/css/strings.css index 522bae5e..cd6d6020 100644 --- a/test/css/strings.css +++ b/test/css/strings.css @@ -38,3 +38,6 @@ color: #000000; color: #ffa500; } +.watermark { + family: Univers, Arial, Verdana, San-Serif; +} diff --git a/test/less/strings.less b/test/less/strings.less index 32fad721..c43e368d 100644 --- a/test/less/strings.less +++ b/test/less/strings.less @@ -49,3 +49,9 @@ .mix-mul(black); .mix-mul(orange); } + +@test: Arial, Verdana, San-Serif; +.watermark { + @family: ~"Univers, @{test}"; + family: @family; +} From c1fe375cdc814d060cfbb79399d3ff06200c93ea Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 15 Aug 2013 22:23:24 +0100 Subject: [PATCH 102/114] Fix IE8 issue if 'less' is a dom element --- build/browser-header.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/browser-header.js b/build/browser-header.js index e0022b71..eaff5bfc 100644 --- a/build/browser-header.js +++ b/build/browser-header.js @@ -1,4 +1,4 @@ -if (typeof(window.less) === 'undefined') { window.less = {}; } +if (typeof(window.less) === 'undefined' || typeof(window.less.nodeType) !== 'undefined') { window.less = {}; } less = window.less; tree = window.less.tree = {}; less.mode = 'browser'; From 6c5072ebbca97138f81d02b19a4c2e70b13bb2f8 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 21 Aug 2013 19:35:28 +0100 Subject: [PATCH 103/114] implement log to console and fix browser tests --- Makefile | 2 +- lib/less/browser.js | 69 ++++++++++++++++++++++--- test/browser-test-prepare.js | 4 +- test/browser/css/relative-urls/urls.css | 1 - test/browser/css/rootpath/urls.css | 1 - test/browser/es5.js | 27 ++++++++++ test/browser/template.htm | 3 +- 7 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 test/browser/es5.js diff --git a/Makefile b/Makefile index c9920c5e..ed898a9e 100644 --- a/Makefile +++ b/Makefile @@ -39,8 +39,8 @@ less: ${SRC}/parser.js\ ${SRC}/functions.js\ ${SRC}/colors.js\ - ${SRC}/tree/*.js\ ${SRC}/tree.js\ + ${SRC}/tree/*.js\ ${SRC}/env.js\ ${SRC}/visitor.js\ ${SRC}/import-visitor.js\ diff --git a/lib/less/browser.js b/lib/less/browser.js index 79267092..2cef7ea4 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -124,7 +124,9 @@ less.refresh = function (reload, newVars) { 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'); + if (env.remaining === 0) { + log("css generated in " + (new Date() - startTime) + 'ms'); + } endTime = new Date(); }, reload, newVars); @@ -280,7 +282,7 @@ function loadStyleSheet(sheet, callback, reload, remaining, newVars) { } //TODO add tests around how this behaves when reloading - removeNode(document.getElementById('less-error-message:' + extractId(path))); + removeError(path); if (data) { env.currentFileInfo = newFileInfo; @@ -485,15 +487,70 @@ function getXMLHttpRequest() { } } -function removeNode(node) { - return node && node.parentNode.removeChild(node); -} - function log(str) { if (less.env == 'development' && typeof(console) !== "undefined") { console.log('less: ' + str); } } function error(e, rootHref) { + if (!less.errorReporting || less.errorReporting === "html") { + errorHTML(e, rootHref); + } else if (less.errorReporting === "console") { + errorConsole(e, rootHref); + } else if (typeof less.errorReporting === 'function') { + less.errorReporting("add", e, rootHref); + } +} + +function removeError(path) { + if (!less.errorReporting || less.errorReporting === "html") { + removeErrorHTML(path); + } else if (less.errorReporting === "console") { + removeErrorConsole(path); + } else if (typeof less.errorReporting === 'function') { + less.errorReporting("remove", path); + } +} + +function removeErrorHTML(path) { + var node = document.getElementById('less-error-message:' + extractId(path)); + if (node) { + node.parentNode.removeChild(node); + } +} + +function removeErrorConsole(path) { + //no action +} + +function errorConsole(e, rootHref) { + var template = '{line}\n{content}'; + var filename = e.filename || rootHref; + var filenameNoPath = filename.match(/([^\/]+(\?.*)?)$/)[1]; + + content = (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') + + "'" + filename + "'"; + + var errorline = function (e, i, classname) { + 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])); + } + }; + + if (e.extract) { + errorline(e, 0, ''); + errorline(e, 1, 'line'); + errorline(e, 2, ''); + content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':\n' + + errors.join('\n'); + } else if (e.stack) { + content += e.stack; + } + log(content); +} + +function errorHTML(e, rootHref) { var id = 'less-error-message:' + extractId(rootHref || ""); var template = '
  • {content}
  • '; var elem = document.createElement('div'), timer, content, errors = []; diff --git a/test/browser-test-prepare.js b/test/browser-test-prepare.js index 95a99703..bb9ac3d1 100644 --- a/test/browser-test-prepare.js +++ b/test/browser-test-prepare.js @@ -17,8 +17,8 @@ var createTestRunnerPage = function(dir, exclude, testSuiteName, dir2) { if (exclude && name.match(exclude)) { return; } - output += '\n'; - output += '\n'; + output += '\n'; + output += '\n'; }); output += String(fs.readFileSync(path.join('test/browser', 'template.htm'))).replace("{runner-name}", testSuiteName); diff --git a/test/browser/css/relative-urls/urls.css b/test/browser/css/relative-urls/urls.css index d0da4990..96bbf604 100644 --- a/test/browser/css/relative-urls/urls.css +++ b/test/browser/css/relative-urls/urls.css @@ -1,5 +1,4 @@ @import "http://localhost:8081/browser/less/imports/modify-this.css"; - @import "http://localhost:8081/browser/less/imports/modify-again.css"; .modify { my-url: url("http://localhost:8081/browser/less/imports/a.png"); diff --git a/test/browser/css/rootpath/urls.css b/test/browser/css/rootpath/urls.css index a3ef5d9e..9618e8e5 100644 --- a/test/browser/css/rootpath/urls.css +++ b/test/browser/css/rootpath/urls.css @@ -1,5 +1,4 @@ @import "https://www.github.com/modify-this.css"; - @import "https://www.github.com/modify-again.css"; .modify { my-url: url("https://www.github.com/a.png"); diff --git a/test/browser/es5.js b/test/browser/es5.js new file mode 100644 index 00000000..c4fcc802 --- /dev/null +++ b/test/browser/es5.js @@ -0,0 +1,27 @@ +/* + PhantomJS does not implement bind. this is from + https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind + */ +if (!Function.prototype.bind) { + Function.prototype.bind = function (oThis) { + if (typeof this !== "function") { + // closest thing possible to the ECMAScript 5 internal IsCallable function + throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); + } + + var aArgs = Array.prototype.slice.call(arguments, 1), + fToBind = this, + fNOP = function () {}, + fBound = function () { + return fToBind.apply(this instanceof fNOP && oThis + ? this + : oThis, + aArgs.concat(Array.prototype.slice.call(arguments))); + }; + + fNOP.prototype = this.prototype; + fBound.prototype = new fNOP(); + + return fBound; + }; +} \ No newline at end of file diff --git a/test/browser/template.htm b/test/browser/template.htm index c000fdc2..41be7c0e 100644 --- a/test/browser/template.htm +++ b/test/browser/template.htm @@ -1,9 +1,10 @@ + - + From 03a718363739fc1430bfaa908fcbcd529337512e Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 21 Aug 2013 20:35:32 +0100 Subject: [PATCH 104/114] add tests and fix log to console feature --- lib/less/browser.js | 11 +++-- test/browser-test-prepare.js | 1 + test/browser/common.js | 43 +++++++++++++++++-- .../less/console-errors/test-error.less | 3 ++ .../less/console-errors/test-error.txt | 2 + test/browser/runner-console-errors.js | 5 +++ 6 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 test/browser/less/console-errors/test-error.less create mode 100644 test/browser/less/console-errors/test-error.txt create mode 100644 test/browser/runner-console-errors.js diff --git a/lib/less/browser.js b/lib/less/browser.js index 2cef7ea4..fe5304db 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -523,12 +523,11 @@ function removeErrorConsole(path) { } function errorConsole(e, rootHref) { - var template = '{line}\n{content}'; + var template = '{line} {content}'; var filename = e.filename || rootHref; - var filenameNoPath = filename.match(/([^\/]+(\?.*)?)$/)[1]; - - content = (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') + - "'" + filename + "'"; + var errors = []; + var content = (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') + + " in " + filename + " "; var errorline = function (e, i, classname) { if (e.extract[i] !== undefined) { @@ -547,7 +546,7 @@ function errorConsole(e, rootHref) { } else if (e.stack) { content += e.stack; } - log(content); + console.log(content); } function errorHTML(e, rootHref) { diff --git a/test/browser-test-prepare.js b/test/browser-test-prepare.js index bb9ac3d1..b8724641 100644 --- a/test/browser-test-prepare.js +++ b/test/browser-test-prepare.js @@ -39,6 +39,7 @@ createTestRunnerPage("", /javascript|urls/, "main"); createTestRunnerPage("", null, "legacy", "legacy"); createTestRunnerPage("", /javascript/, "errors", "errors"); createTestRunnerPage("", null, "no-js-errors", "no-js-errors"); +createTestRunnerPage("browser", null, "console-errors", "console-errors"); createTestRunnerPage("browser", null, "browser"); createTestRunnerPage("browser", null, "relative-urls", "relative-urls"); createTestRunnerPage("browser", null, "rootpath", "rootpath"); diff --git a/test/browser/common.js b/test/browser/common.js index 74f12fe0..5460fd44 100644 --- a/test/browser/common.js +++ b/test/browser/common.js @@ -13,8 +13,8 @@ var testLessEqualsInDocument = function() { testLessInDocument(testSheet); }; -var testLessErrorsInDocument = function() { - testLessInDocument(testErrorSheet); +var testLessErrorsInDocument = function(isConsole) { + testLessInDocument(isConsole ? testErrorSheetConsole : testErrorSheet); }; var testLessInDocument = function(testFunc) { @@ -43,7 +43,7 @@ var testSheet = function(sheet) { runs(function() { // use sheet to do testing - expect(lessOutput).toEqual(expectedOutput.text); + expect(expectedOutput.text).toEqual(lessOutput); }); }); }; @@ -76,7 +76,7 @@ var testErrorSheet = function(sheet) { .replace("{pathrel}", "") .replace("{pathhref}", "http://localhost:8081/less/errors/") .replace("{404status}", " (404)"); - expect(actualErrorMsg).toEqual(errorTxt); + expect(errorTxt).toEqual(actualErrorMsg); if (errorTxt == actualErrorMsg) { actualErrorElement.style.display = "none"; } @@ -84,6 +84,41 @@ var testErrorSheet = function(sheet) { }); }; +var testErrorSheetConsole = function(sheet) { + it(sheet.id + " should match an error", function() { + var lessHref = sheet.href, + id = sheet.id.replace(/^original-less:/, "less-error-message:"), + errorHref = lessHref.replace(/.less$/, ".txt"), + errorFile = loadFile(errorHref), + actualErrorElement = document.getElementById(id), + actualErrorMsg = logMessages[logMessages.length - 1]; + + describe("the error", function() { + expect(actualErrorElement).toBe(null); + + }); + + /*actualErrorMsg = actualErrorElement.innerText + .replace(/\n\d+/g, function(lineNo) { return lineNo + " "; }) + .replace(/\n\s*in /g, " in ") + .replace("\n\n", "\n");*/ + + waitsFor(function() { + return errorFile.loaded; + }, "failed to load expected outout", 10000); + + runs(function() { + var errorTxt = errorFile.text + .replace("{path}", "") + .replace("{pathrel}", "") + .replace("{pathhref}", "http://localhost:8081/browser/less/") + .replace("{404status}", " (404)") + .trim(); + expect(errorTxt).toEqual(actualErrorMsg); + }); + }); +}; + var loadFile = function(href) { var request = new XMLHttpRequest(), response = { loaded: false, text: ""}; diff --git a/test/browser/less/console-errors/test-error.less b/test/browser/less/console-errors/test-error.less new file mode 100644 index 00000000..7c60c004 --- /dev/null +++ b/test/browser/less/console-errors/test-error.less @@ -0,0 +1,3 @@ +.a { + prop: (3 / #fff); +} \ No newline at end of file diff --git a/test/browser/less/console-errors/test-error.txt b/test/browser/less/console-errors/test-error.txt new file mode 100644 index 00000000..68501ae5 --- /dev/null +++ b/test/browser/less/console-errors/test-error.txt @@ -0,0 +1,2 @@ +OperationError: Can't substract or divide a color from a number in {pathhref}console-errors/test-error.less on line null, column 0: +1 prop: (3 / #fff); diff --git a/test/browser/runner-console-errors.js b/test/browser/runner-console-errors.js new file mode 100644 index 00000000..7af7bdb1 --- /dev/null +++ b/test/browser/runner-console-errors.js @@ -0,0 +1,5 @@ +less.errorReporting = 'console'; + +describe("less.js error reporting console test", function() { + testLessErrorsInDocument(true); +}); \ No newline at end of file From 2ed6f34afb63ec7aa157ed9fe84de2d6d8370b4c Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 22 Aug 2013 19:11:23 +0100 Subject: [PATCH 105/114] Use the log method for logging errors and turn the number into an enumeration. --- lib/less/browser.js | 26 ++++++++++++------- .../less/console-errors/test-error.txt | 2 +- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/less/browser.js b/lib/less/browser.js index b9b2a43e..da357a4d 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -12,12 +12,18 @@ less.env = less.env || (location.hostname == '127.0.0.1' || isFileProtocol ? 'development' : 'production'); +var logLevel = { + info: 2, + errors: 1, + none: 0 +}; + // The amount of logging in the javascript console. // 2 - Information and errors // 1 - Errors // 0 - None // Defaults to 2 -less.log_level = typeof(less.log_level) != 'undefined' ? less.log_level : 2; +less.log_level = typeof(less.log_level) != 'undefined' ? less.log_level : logLevel.info; // Load styles asynchronously (default: false) // @@ -125,14 +131,14 @@ less.refresh = function (reload, newVars) { return error(e, sheet.href); } if (env.local) { - log("loading " + sheet.href + " from cache.", 2); + log("loading " + sheet.href + " from cache.", logLevel.info); } else { - log("parsed " + sheet.href + " successfully.", 2); + log("parsed " + sheet.href + " successfully.", logLevel.info); createCSS(root.toCSS(less), sheet, env.lastModified); } - log("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms', 2); + log("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms', logLevel.info); if (env.remaining === 0) { - log("css generated in " + (new Date() - startTime) + 'ms', 2); + log("css generated in " + (new Date() - startTime) + 'ms', logLevel.info); } endTime = new Date(); }, reload, newVars); @@ -431,13 +437,13 @@ function createCSS(styles, sheet, lastModified) { // Don't update the local store if the file wasn't modified if (lastModified && cache) { - log('saving ' + href + ' to cache.', 2); + log('saving ' + href + ' to cache.', logLevel.info); try { cache.setItem(href, styles); cache.setItem(href + ':timestamp', lastModified); } catch(e) { //TODO - could do with adding more robust error handling - log('failed to save', 1); + log('failed to save', logLevel.errors); } } } @@ -449,7 +455,7 @@ function doXHR(url, type, callback, errback) { if (typeof(xhr.overrideMimeType) === 'function') { xhr.overrideMimeType('text/css'); } - log("XHR: Getting '" + url + "'", 2); + log("XHR: Getting '" + url + "'", logLevel.info); xhr.open('GET', url, async); xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5'); xhr.send(null); @@ -488,7 +494,7 @@ function getXMLHttpRequest() { /*global ActiveXObject */ return new ActiveXObject("MSXML2.XMLHTTP.3.0"); } catch (e) { - log("browser doesn't support AJAX.", 1); + log("browser doesn't support AJAX.", logLevel.errors); return null; } } @@ -555,7 +561,7 @@ function errorConsole(e, rootHref) { } else if (e.stack) { content += e.stack; } - console.log(content); + log(content, logLevel.errors); } function errorHTML(e, rootHref) { diff --git a/test/browser/less/console-errors/test-error.txt b/test/browser/less/console-errors/test-error.txt index 68501ae5..643bd593 100644 --- a/test/browser/less/console-errors/test-error.txt +++ b/test/browser/less/console-errors/test-error.txt @@ -1,2 +1,2 @@ -OperationError: Can't substract or divide a color from a number in {pathhref}console-errors/test-error.less on line null, column 0: +less: OperationError: Can't substract or divide a color from a number in {pathhref}console-errors/test-error.less on line null, column 0: 1 prop: (3 / #fff); From 6fe11743828addd1c7d7bcf320364cc28dc21d8d Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 22 Aug 2013 19:30:15 +0100 Subject: [PATCH 106/114] rename log_level to logLevel and update some of the changelog --- CHANGELOG.md | 3 +++ lib/less/browser.js | 4 ++-- lib/less/index.js | 2 +- package.json | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d226720..564a1209 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # 1.5.0 WIP + - sourcemap support - 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. @@ -13,6 +14,8 @@ - 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 + - Browser: added logLevel option to control logging (2 = everything, 1 = errors only, 0 = no logging) + - Browser: added errorReporting option which can be "html" (default) or "console" or a function # 1.4.2 diff --git a/lib/less/browser.js b/lib/less/browser.js index da357a4d..cc8b8fe7 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -23,7 +23,7 @@ var logLevel = { // 1 - Errors // 0 - None // Defaults to 2 -less.log_level = typeof(less.log_level) != 'undefined' ? less.log_level : logLevel.info; +less.logLevel = typeof(less.logLevel) != 'undefined' ? less.logLevel : logLevel.info; // Load styles asynchronously (default: false) // @@ -501,7 +501,7 @@ function getXMLHttpRequest() { } function log(str, level) { - if (less.env == 'development' && typeof(console) !== 'undefined' && less.log_level >= level) { + if (less.env == 'development' && typeof(console) !== 'undefined' && less.logLevel >= level) { console.log('less: ' + str); } } diff --git a/lib/less/index.js b/lib/less/index.js index cb95ca88..03b8014d 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -5,7 +5,7 @@ var path = require('path'), fs = require('fs'); var less = { - version: [1, 4, 2], + version: [1, 5, 0], Parser: require('./parser').Parser, tree: require('./tree'), render: function (input, options, callback) { diff --git a/package.json b/package.json index 0f3c4c24..00535957 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "less", - "version": "1.4.2", + "version": "1.5.0", "description": "Leaner CSS", "homepage": "http://lesscss.org", "author": "Alexis Sellier ", @@ -22,7 +22,7 @@ "test": "./test" }, "jam": { - "main": "./dist/less-1.4.2.js" + "main": "./dist/less-1.5.0.js" }, "engines": { "node": ">=0.4.2" From 944742125889a87dab7008c229955a6e3b214d2c Mon Sep 17 00:00:00 2001 From: Luke Page Date: Sun, 1 Sep 2013 12:34:20 +0100 Subject: [PATCH 107/114] switch on latedef option and fix issues. Fixes #1521 --- .jshintrc | 2 + lib/less/browser.js | 957 +++++++++++++++++++++--------------------- lib/less/functions.js | 16 +- lib/less/rhino.js | 60 +-- test/less-test.js | 1 + 5 files changed, 520 insertions(+), 516 deletions(-) diff --git a/.jshintrc b/.jshintrc index 2f830954..0d5dbf2a 100644 --- a/.jshintrc +++ b/.jshintrc @@ -3,7 +3,9 @@ "boss": true, "expr": true, "laxbreak": true, + "latedef": true, "node": true, + "undef": true, "unused": "vars", "noarg": true } diff --git a/lib/less/browser.js b/lib/less/browser.js index cc8b8fe7..4e1c68cd 100644 --- a/lib/less/browser.js +++ b/lib/less/browser.js @@ -1,7 +1,7 @@ // // browser.js - client-side engine // -/*global less */ +/*global less, window, document, XMLHttpRequest, location */ var isFileProtocol = /^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol); @@ -49,341 +49,49 @@ if (dumpLineNumbers) { less.dumpLineNumbers = dumpLineNumbers[1]; } -// -// Watch mode -// -less.watch = function () { - if (!less.watchMode ){ - less.env = 'development'; - initRunningMode(); - } - return this.watchMode = true; -}; - -less.unwatch = function () {clearInterval(less.watchTimer); return this.watchMode = false; }; - -function initRunningMode(){ - if (less.env === 'development') { - less.optimization = 0; - less.watchTimer = setInterval(function () { - if (less.watchMode) { - loadStyleSheets(function (e, root, _, sheet, env) { - if (e) { - error(e, sheet.href); - } else if (root) { - createCSS(root.toCSS(less), sheet, env.lastModified); - } - }); - } - }, less.poll); - } else { - less.optimization = 3; - } -} - -if (/!watch/.test(location.hash)) { - less.watch(); -} - -var cache = null; - -if (less.env != 'development') { - try { - cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage; - } catch (_) {} -} - -// -// Get all tags with the 'rel' attribute set to "stylesheet/less" -// -var links = document.getElementsByTagName('link'); var typePattern = /^text\/(x-)?less$/; - -less.sheets = []; - -for (var i = 0; i < links.length; i++) { - if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) && - (links[i].type.match(typePattern)))) { - less.sheets.push(links[i]); - } -} - -// -// With this function, it's possible to alter variables and re-render -// CSS without reloading less-files -// +var cache = null; var fileCache = {}; -less.modifyVars = function(record) { - var newVars = ""; - for (var name in record) { - newVars += ((name.slice(0,1) === '@')? '' : '@') + name +': '+ - ((record[name].slice(-1) === ';')? record[name] : record[name] +';'); - } - less.refresh(false, newVars); -}; -less.refresh = function (reload, newVars) { - var startTime, endTime; - startTime = endTime = new Date(); - - loadStyleSheets(function (e, root, _, sheet, env) { - if (e) { - return error(e, sheet.href); - } - if (env.local) { - log("loading " + sheet.href + " from cache.", logLevel.info); - } else { - log("parsed " + sheet.href + " successfully.", logLevel.info); - createCSS(root.toCSS(less), sheet, env.lastModified); - } - log("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms', logLevel.info); - if (env.remaining === 0) { - log("css generated in " + (new Date() - startTime) + 'ms', logLevel.info); - } - endTime = new Date(); - }, reload, newVars); - - loadStyles(newVars); -}; -less.refreshStyles = loadStyles; - -function loadStyles(newVars) { - var styles = document.getElementsByTagName('style'), - style; - for (var i = 0; i < styles.length; i++) { - style = styles[i]; - if (style.type.match(typePattern)) { - var env = new less.tree.parseEnv(less), - lessText = style.innerHTML || ''; - env.filename = document.location.href.replace(/#.*$/, ''); - if (newVars) { - env.useFileCache = true; - lessText += "\n" + newVars; - } - - /*jshint loopfunc:true */ - // use closure to store current value of i - var callback = (function(style) { - return function (e, cssAST) { - if (e) { - return error(e, "inline"); - } - var css = cssAST.toCSS(less); - style.type = 'text/css'; - if (style.styleSheet) { - style.styleSheet.cssText = css; - } else { - style.innerHTML = css; - } - }; - })(style); - new(less.Parser)(env).parse(lessText, callback); - } +function log(str, level) { + if (less.env == 'development' && typeof(console) !== 'undefined' && less.logLevel >= level) { + console.log('less: ' + str); } } -function loadStyleSheets(callback, reload, newVars) { - for (var i = 0; i < less.sheets.length; i++) { - loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1), newVars); - } -} - -function pathDiff(url, baseUrl) { - // diff between two paths to create a relative path - - var urlParts = extractUrlParts(url), - baseUrlParts = extractUrlParts(baseUrl), - i, max, urlDirectories, baseUrlDirectories, diff = ""; - if (urlParts.hostPart !== baseUrlParts.hostPart) { - return ""; - } - max = Math.max(baseUrlParts.directories.length, urlParts.directories.length); - for(i = 0; i < max; i++) { - if (baseUrlParts.directories[i] !== urlParts.directories[i]) { break; } - } - baseUrlDirectories = baseUrlParts.directories.slice(i); - urlDirectories = urlParts.directories.slice(i); - for(i = 0; i < baseUrlDirectories.length-1; i++) { - diff += "../"; - } - for(i = 0; i < urlDirectories.length-1; i++) { - diff += urlDirectories[i] + "/"; - } - return diff; -} - -function extractUrlParts(url, baseUrl) { - // urlParts[1] = protocol&hostname || / - // urlParts[2] = / if path relative to host base - // urlParts[3] = directories - // urlParts[4] = filename - // urlParts[5] = parameters - - var urlPartsRegex = /^((?:[a-z-]+:)?\/+?(?:[^\/\?#]*\/)|([\/\\]))?((?:[^\/\\\?#]*[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/i, - urlParts = url.match(urlPartsRegex), - returner = {}, directories = [], i, baseUrlParts; - - if (!urlParts) { - throw new Error("Could not parse sheet href - '"+url+"'"); - } - - // Stylesheets in IE don't always return the full path - if (!urlParts[1] || urlParts[2]) { - baseUrlParts = baseUrl.match(urlPartsRegex); - if (!baseUrlParts) { - throw new Error("Could not parse page url - '"+baseUrl+"'"); - } - urlParts[1] = urlParts[1] || baseUrlParts[1] || ""; - if (!urlParts[2]) { - urlParts[3] = baseUrlParts[3] + urlParts[3]; - } - } - - if (urlParts[3]) { - directories = urlParts[3].replace(/\\/g, "/").split("/"); - - // extract out . before .. so .. doesn't absorb a non-directory - for(i = 0; i < directories.length; i++) { - if (directories[i] === ".") { - directories.splice(i, 1); - i -= 1; - } - } - - for(i = 0; i < directories.length; i++) { - if (directories[i] === ".." && i > 0) { - directories.splice(i-1, 2); - i -= 2; - } - } - } - - returner.hostPart = urlParts[1]; - returner.directories = directories; - returner.path = urlParts[1] + directories.join("/"); - returner.fileUrl = returner.path + (urlParts[4] || ""); - returner.url = returner.fileUrl + (urlParts[5] || ""); - return returner; -} - -function loadStyleSheet(sheet, callback, reload, remaining, newVars) { - - var env = new less.tree.parseEnv(less); - env.mime = sheet.type; - - if (newVars) { - env.useFileCache = true; - } - - loadFile(sheet.href, null, function(e, data, path, newFileInfo, webInfo) { - - if (webInfo) { - webInfo.remaining = remaining; - - var css = cache && cache.getItem(path), - timestamp = cache && cache.getItem(path + ':timestamp'); - - if (!reload && timestamp && webInfo.lastModified && - (new(Date)(webInfo.lastModified).valueOf() === - new(Date)(timestamp).valueOf())) { - // Use local copy - createCSS(css, sheet); - webInfo.local = true; - callback(null, null, data, sheet, webInfo, path); - return; - } - } - - //TODO add tests around how this behaves when reloading - removeError(path); - - if (data) { - env.currentFileInfo = newFileInfo; - new(less.Parser)(env).parse(data, function (e, root) { - if (e) { return callback(e, null, null, sheet); } - try { - callback(e, root, data, sheet, webInfo, path); - } catch (e) { - callback(e, null, null, sheet); - } - }); - } else { - callback(e, null, null, sheet, webInfo, path); - } - }, env, newVars); -} - -function loadFile(originalHref, currentFileInfo, callback, env, newVars) { - - if (currentFileInfo && currentFileInfo.currentDirectory && !/^([a-z-]+:)?\//.test(originalHref)) { - originalHref = currentFileInfo.currentDirectory + originalHref; - } - - // sheet may be set to the stylesheet for the initial load or a collection of properties including - // some env variables for imports - var hrefParts = extractUrlParts(originalHref, window.location.href); - var href = hrefParts.url; - var newFileInfo = { - currentDirectory: hrefParts.path, - filename: href - }; - - if (currentFileInfo) { - newFileInfo.entryPath = currentFileInfo.entryPath; - newFileInfo.rootpath = currentFileInfo.rootpath; - newFileInfo.rootFilename = currentFileInfo.rootFilename; - newFileInfo.relativeUrls = currentFileInfo.relativeUrls; - } else { - newFileInfo.entryPath = hrefParts.path; - newFileInfo.rootpath = less.rootpath || hrefParts.path; - newFileInfo.rootFilename = href; - newFileInfo.relativeUrls = env.relativeUrls; - } - - if (newFileInfo.relativeUrls) { - if (env.rootpath) { - newFileInfo.rootpath = extractUrlParts(env.rootpath + pathDiff(hrefParts.path, newFileInfo.entryPath)).path; - } else { - newFileInfo.rootpath = hrefParts.path; - } - } - - if (env.useFileCache && fileCache[href]) { - try { - var lessText = fileCache[href]; - if (newVars) { - lessText += "\n" + newVars; - } - callback(null, lessText, href, newFileInfo, { lastModified: new Date() }); - } catch (e) { - callback(e, null, href); - } - return; - } - - 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 }); - } catch (e) { - callback(e, null, href); - } - }, function (status, url) { - callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, href); - }); -} - -less.Parser.fileLoader = loadFile; - function extractId(href) { return href.replace(/^[a-z-]+:\/+?[^\/]+/, '' ) // Remove protocol & domain - .replace(/^\//, '' ) // Remove root / - .replace(/\.[a-zA-Z]+$/, '' ) // Remove simple extension - .replace(/[^\.\w-]+/g, '-') // Replace illegal characters - .replace(/\./g, ':'); // Replace dots with colons(for valid id) + .replace(/^\//, '' ) // Remove root / + .replace(/\.[a-zA-Z]+$/, '' ) // Remove simple extension + .replace(/[^\.\w-]+/g, '-') // Replace illegal characters + .replace(/\./g, ':'); // Replace dots with colons(for valid id) +} + +function errorConsole(e, rootHref) { + var template = '{line} {content}'; + var filename = e.filename || rootHref; + var errors = []; + var content = (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') + + " in " + filename + " "; + + var errorline = function (e, i, classname) { + 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])); + } + }; + + if (e.extract) { + errorline(e, 0, ''); + errorline(e, 1, 'line'); + errorline(e, 2, ''); + content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':\n' + + errors.join('\n'); + } else if (e.stack) { + content += e.stack; + } + log(content, logLevel.errors); } function createCSS(styles, sheet, lastModified) { @@ -448,101 +156,18 @@ function createCSS(styles, sheet, lastModified) { } } -function doXHR(url, type, callback, errback) { - var xhr = getXMLHttpRequest(); - var async = isFileProtocol ? less.fileAsync : less.async; - - if (typeof(xhr.overrideMimeType) === 'function') { - xhr.overrideMimeType('text/css'); - } - log("XHR: Getting '" + url + "'", logLevel.info); - xhr.open('GET', url, async); - xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5'); - xhr.send(null); - - if (isFileProtocol && !less.fileAsync) { - if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) { - callback(xhr.responseText); - } else { - errback(xhr.status, url); - } - } else if (async) { - xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - handleResponse(xhr, callback, errback); - } - }; - } else { - handleResponse(xhr, callback, errback); - } - - function handleResponse(xhr, callback, errback) { - if (xhr.status >= 200 && xhr.status < 300) { - callback(xhr.responseText, - xhr.getResponseHeader("Last-Modified")); - } else if (typeof(errback) === 'function') { - errback(xhr.status, url); - } - } -} - -function getXMLHttpRequest() { - if (window.XMLHttpRequest) { - return new XMLHttpRequest(); - } else { - try { - /*global ActiveXObject */ - return new ActiveXObject("MSXML2.XMLHTTP.3.0"); - } catch (e) { - log("browser doesn't support AJAX.", logLevel.errors); - return null; - } - } -} - -function log(str, level) { - if (less.env == 'development' && typeof(console) !== 'undefined' && less.logLevel >= level) { - console.log('less: ' + str); - } -} - -function error(e, rootHref) { - if (!less.errorReporting || less.errorReporting === "html") { - errorHTML(e, rootHref); - } else if (less.errorReporting === "console") { - errorConsole(e, rootHref); - } else if (typeof less.errorReporting === 'function') { - less.errorReporting("add", e, rootHref); - } -} - -function removeError(path) { - if (!less.errorReporting || less.errorReporting === "html") { - removeErrorHTML(path); - } else if (less.errorReporting === "console") { - removeErrorConsole(path); - } else if (typeof less.errorReporting === 'function') { - less.errorReporting("remove", path); - } -} - -function removeErrorHTML(path) { - var node = document.getElementById('less-error-message:' + extractId(path)); - if (node) { - node.parentNode.removeChild(node); - } -} - -function removeErrorConsole(path) { - //no action -} - -function errorConsole(e, rootHref) { - var template = '{line} {content}'; +function errorHTML(e, rootHref) { + var id = 'less-error-message:' + extractId(rootHref || ""); + var template = '
  • {content}
  • '; + var elem = document.createElement('div'), timer, content, errors = []; var filename = e.filename || rootHref; - var errors = []; - var content = (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') + - " in " + filename + " "; + var filenameNoPath = filename.match(/([^\/]+(\?.*)?)$/)[1]; + + elem.id = id; + elem.className = "less-error-message"; + + content = '

    ' + (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') + + '

    ' + '

    in ' + filenameNoPath + " "; var errorline = function (e, i, classname) { if (e.extract[i] !== undefined) { @@ -552,45 +177,12 @@ function errorConsole(e, rootHref) { } }; - if (e.extract) { - errorline(e, 0, ''); - errorline(e, 1, 'line'); - errorline(e, 2, ''); - content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':\n' + - errors.join('\n'); - } else if (e.stack) { - content += e.stack; - } - log(content, logLevel.errors); -} - -function errorHTML(e, rootHref) { - var id = 'less-error-message:' + extractId(rootHref || ""); - var template = '

  • {content}
  • '; - var elem = document.createElement('div'), timer, content, errors = []; - var filename = e.filename || rootHref; - var filenameNoPath = filename.match(/([^\/]+(\?.*)?)$/)[1]; - - elem.id = id; - elem.className = "less-error-message"; - - content = '

    ' + (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') + - '

    ' + '

    in ' + filenameNoPath + " "; - - var errorline = function (e, i, classname) { - 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])); - } - }; - if (e.extract) { errorline(e, 0, ''); errorline(e, 1, 'line'); errorline(e, 2, ''); content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':

    ' + - '
      ' + errors.join('') + '
    '; + '
      ' + errors.join('') + '
    '; } else if (e.stack) { content += '
    ' + e.stack.split('\n').slice(1).join('
    '); } @@ -599,40 +191,40 @@ function errorHTML(e, rootHref) { // CSS for error messages createCSS([ '.less-error-message ul, .less-error-message li {', - 'list-style-type: none;', - 'margin-right: 15px;', - 'padding: 4px 0;', - 'margin: 0;', + 'list-style-type: none;', + 'margin-right: 15px;', + 'padding: 4px 0;', + 'margin: 0;', '}', '.less-error-message label {', - 'font-size: 12px;', - 'margin-right: 15px;', - 'padding: 4px 0;', - 'color: #cc7777;', + 'font-size: 12px;', + 'margin-right: 15px;', + 'padding: 4px 0;', + 'color: #cc7777;', '}', '.less-error-message pre {', - 'color: #dd6666;', - 'padding: 4px 0;', - 'margin: 0;', - 'display: inline-block;', + 'color: #dd6666;', + 'padding: 4px 0;', + 'margin: 0;', + 'display: inline-block;', '}', '.less-error-message pre.line {', - 'color: #ff0000;', + 'color: #ff0000;', '}', '.less-error-message h3 {', - 'font-size: 20px;', - 'font-weight: bold;', - 'padding: 15px 0 5px 0;', - 'margin: 0;', + 'font-size: 20px;', + 'font-weight: bold;', + 'padding: 15px 0 5px 0;', + 'margin: 0;', '}', '.less-error-message a {', - 'color: #10a', + 'color: #10a', '}', '.less-error-message .error {', - 'color: red;', - 'font-weight: bold;', - 'padding-bottom: 2px;', - 'border-bottom: 1px dashed red;', + 'color: red;', + 'font-weight: bold;', + 'padding-bottom: 2px;', + 'border-bottom: 1px dashed red;', '}' ].join('\n'), { title: 'error-message' }); @@ -662,4 +254,413 @@ function errorHTML(e, rootHref) { } } +function error(e, rootHref) { + if (!less.errorReporting || less.errorReporting === "html") { + errorHTML(e, rootHref); + } else if (less.errorReporting === "console") { + errorConsole(e, rootHref); + } else if (typeof less.errorReporting === 'function') { + less.errorReporting("add", e, rootHref); + } +} + +function removeErrorHTML(path) { + var node = document.getElementById('less-error-message:' + extractId(path)); + if (node) { + node.parentNode.removeChild(node); + } +} + +function removeErrorConsole(path) { + //no action +} + +function removeError(path) { + if (!less.errorReporting || less.errorReporting === "html") { + removeErrorHTML(path); + } else if (less.errorReporting === "console") { + removeErrorConsole(path); + } else if (typeof less.errorReporting === 'function') { + less.errorReporting("remove", path); + } +} + +function loadStyles(newVars) { + var styles = document.getElementsByTagName('style'), + style; + for (var i = 0; i < styles.length; i++) { + style = styles[i]; + if (style.type.match(typePattern)) { + var env = new less.tree.parseEnv(less), + lessText = style.innerHTML || ''; + env.filename = document.location.href.replace(/#.*$/, ''); + if (newVars) { + env.useFileCache = true; + lessText += "\n" + newVars; + } + + /*jshint loopfunc:true */ + // use closure to store current value of i + var callback = (function(style) { + return function (e, cssAST) { + if (e) { + return error(e, "inline"); + } + var css = cssAST.toCSS(less); + style.type = 'text/css'; + if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.innerHTML = css; + } + }; + })(style); + new(less.Parser)(env).parse(lessText, callback); + } + } +} + +function extractUrlParts(url, baseUrl) { + // urlParts[1] = protocol&hostname || / + // urlParts[2] = / if path relative to host base + // urlParts[3] = directories + // urlParts[4] = filename + // urlParts[5] = parameters + + var urlPartsRegex = /^((?:[a-z-]+:)?\/+?(?:[^\/\?#]*\/)|([\/\\]))?((?:[^\/\\\?#]*[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/i, + urlParts = url.match(urlPartsRegex), + returner = {}, directories = [], i, baseUrlParts; + + if (!urlParts) { + throw new Error("Could not parse sheet href - '"+url+"'"); + } + + // Stylesheets in IE don't always return the full path + if (!urlParts[1] || urlParts[2]) { + baseUrlParts = baseUrl.match(urlPartsRegex); + if (!baseUrlParts) { + throw new Error("Could not parse page url - '"+baseUrl+"'"); + } + urlParts[1] = urlParts[1] || baseUrlParts[1] || ""; + if (!urlParts[2]) { + urlParts[3] = baseUrlParts[3] + urlParts[3]; + } + } + + if (urlParts[3]) { + directories = urlParts[3].replace(/\\/g, "/").split("/"); + + // extract out . before .. so .. doesn't absorb a non-directory + for(i = 0; i < directories.length; i++) { + if (directories[i] === ".") { + directories.splice(i, 1); + i -= 1; + } + } + + for(i = 0; i < directories.length; i++) { + if (directories[i] === ".." && i > 0) { + directories.splice(i-1, 2); + i -= 2; + } + } + } + + returner.hostPart = urlParts[1]; + returner.directories = directories; + returner.path = urlParts[1] + directories.join("/"); + returner.fileUrl = returner.path + (urlParts[4] || ""); + returner.url = returner.fileUrl + (urlParts[5] || ""); + return returner; +} + +function pathDiff(url, baseUrl) { + // diff between two paths to create a relative path + + var urlParts = extractUrlParts(url), + baseUrlParts = extractUrlParts(baseUrl), + i, max, urlDirectories, baseUrlDirectories, diff = ""; + if (urlParts.hostPart !== baseUrlParts.hostPart) { + return ""; + } + max = Math.max(baseUrlParts.directories.length, urlParts.directories.length); + for(i = 0; i < max; i++) { + if (baseUrlParts.directories[i] !== urlParts.directories[i]) { break; } + } + baseUrlDirectories = baseUrlParts.directories.slice(i); + urlDirectories = urlParts.directories.slice(i); + for(i = 0; i < baseUrlDirectories.length-1; i++) { + diff += "../"; + } + for(i = 0; i < urlDirectories.length-1; i++) { + diff += urlDirectories[i] + "/"; + } + return diff; +} + +function getXMLHttpRequest() { + if (window.XMLHttpRequest) { + return new XMLHttpRequest(); + } else { + try { + /*global ActiveXObject */ + return new ActiveXObject("MSXML2.XMLHTTP.3.0"); + } catch (e) { + log("browser doesn't support AJAX.", logLevel.errors); + return null; + } + } +} + +function doXHR(url, type, callback, errback) { + var xhr = getXMLHttpRequest(); + var async = isFileProtocol ? less.fileAsync : less.async; + + if (typeof(xhr.overrideMimeType) === 'function') { + xhr.overrideMimeType('text/css'); + } + log("XHR: Getting '" + url + "'", logLevel.info); + xhr.open('GET', url, async); + xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5'); + xhr.send(null); + + function handleResponse(xhr, callback, errback) { + if (xhr.status >= 200 && xhr.status < 300) { + callback(xhr.responseText, + xhr.getResponseHeader("Last-Modified")); + } else if (typeof(errback) === 'function') { + errback(xhr.status, url); + } + } + + if (isFileProtocol && !less.fileAsync) { + if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) { + callback(xhr.responseText); + } else { + errback(xhr.status, url); + } + } else if (async) { + xhr.onreadystatechange = function () { + if (xhr.readyState == 4) { + handleResponse(xhr, callback, errback); + } + }; + } else { + handleResponse(xhr, callback, errback); + } +} + +function loadFile(originalHref, currentFileInfo, callback, env, newVars) { + + if (currentFileInfo && currentFileInfo.currentDirectory && !/^([a-z-]+:)?\//.test(originalHref)) { + originalHref = currentFileInfo.currentDirectory + originalHref; + } + + // sheet may be set to the stylesheet for the initial load or a collection of properties including + // some env variables for imports + var hrefParts = extractUrlParts(originalHref, window.location.href); + var href = hrefParts.url; + var newFileInfo = { + currentDirectory: hrefParts.path, + filename: href + }; + + if (currentFileInfo) { + newFileInfo.entryPath = currentFileInfo.entryPath; + newFileInfo.rootpath = currentFileInfo.rootpath; + newFileInfo.rootFilename = currentFileInfo.rootFilename; + newFileInfo.relativeUrls = currentFileInfo.relativeUrls; + } else { + newFileInfo.entryPath = hrefParts.path; + newFileInfo.rootpath = less.rootpath || hrefParts.path; + newFileInfo.rootFilename = href; + newFileInfo.relativeUrls = env.relativeUrls; + } + + if (newFileInfo.relativeUrls) { + if (env.rootpath) { + newFileInfo.rootpath = extractUrlParts(env.rootpath + pathDiff(hrefParts.path, newFileInfo.entryPath)).path; + } else { + newFileInfo.rootpath = hrefParts.path; + } + } + + if (env.useFileCache && fileCache[href]) { + try { + var lessText = fileCache[href]; + if (newVars) { + lessText += "\n" + newVars; + } + callback(null, lessText, href, newFileInfo, { lastModified: new Date() }); + } catch (e) { + callback(e, null, href); + } + return; + } + + 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 }); + } catch (e) { + callback(e, null, href); + } + }, function (status, url) { + callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, href); + }); +} + +function loadStyleSheet(sheet, callback, reload, remaining, newVars) { + + var env = new less.tree.parseEnv(less); + env.mime = sheet.type; + + if (newVars) { + env.useFileCache = true; + } + + loadFile(sheet.href, null, function(e, data, path, newFileInfo, webInfo) { + + if (webInfo) { + webInfo.remaining = remaining; + + var css = cache && cache.getItem(path), + timestamp = cache && cache.getItem(path + ':timestamp'); + + if (!reload && timestamp && webInfo.lastModified && + (new(Date)(webInfo.lastModified).valueOf() === + new(Date)(timestamp).valueOf())) { + // Use local copy + createCSS(css, sheet); + webInfo.local = true; + callback(null, null, data, sheet, webInfo, path); + return; + } + } + + //TODO add tests around how this behaves when reloading + removeError(path); + + if (data) { + env.currentFileInfo = newFileInfo; + new(less.Parser)(env).parse(data, function (e, root) { + if (e) { return callback(e, null, null, sheet); } + try { + callback(e, root, data, sheet, webInfo, path); + } catch (e) { + callback(e, null, null, sheet); + } + }); + } else { + callback(e, null, null, sheet, webInfo, path); + } + }, env, newVars); +} + +function loadStyleSheets(callback, reload, newVars) { + for (var i = 0; i < less.sheets.length; i++) { + loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1), newVars); + } +} + +function initRunningMode(){ + if (less.env === 'development') { + less.optimization = 0; + less.watchTimer = setInterval(function () { + if (less.watchMode) { + loadStyleSheets(function (e, root, _, sheet, env) { + if (e) { + error(e, sheet.href); + } else if (root) { + createCSS(root.toCSS(less), sheet, env.lastModified); + } + }); + } + }, less.poll); + } else { + less.optimization = 3; + } +} + +// +// Watch mode +// +less.watch = function () { + if (!less.watchMode ){ + less.env = 'development'; + initRunningMode(); + } + return this.watchMode = true; +}; + +less.unwatch = function () {clearInterval(less.watchTimer); return this.watchMode = false; }; + +if (/!watch/.test(location.hash)) { + less.watch(); +} + +if (less.env != 'development') { + try { + cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage; + } catch (_) {} +} + +// +// Get all tags with the 'rel' attribute set to "stylesheet/less" +// +var links = document.getElementsByTagName('link'); + +less.sheets = []; + +for (var i = 0; i < links.length; i++) { + if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) && + (links[i].type.match(typePattern)))) { + less.sheets.push(links[i]); + } +} + +// +// With this function, it's possible to alter variables and re-render +// CSS without reloading less-files +// +less.modifyVars = function(record) { + var newVars = ""; + for (var name in record) { + newVars += ((name.slice(0,1) === '@')? '' : '@') + name +': '+ + ((record[name].slice(-1) === ';')? record[name] : record[name] +';'); + } + less.refresh(false, newVars); +}; + +less.refresh = function (reload, newVars) { + var startTime, endTime; + startTime = endTime = new Date(); + + loadStyleSheets(function (e, root, _, sheet, env) { + if (e) { + return error(e, sheet.href); + } + if (env.local) { + log("loading " + sheet.href + " from cache.", logLevel.info); + } else { + log("parsed " + sheet.href + " successfully.", logLevel.info); + createCSS(root.toCSS(less), sheet, env.lastModified); + } + log("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms', logLevel.info); + if (env.remaining === 0) { + log("css generated in " + (new Date() - startTime) + 'ms', logLevel.info); + } + endTime = new Date(); + }, reload, newVars); + + loadStyles(newVars); +}; + +less.refreshStyles = loadStyles; + +less.Parser.fileLoader = loadFile; + less.refresh(less.env === 'development'); diff --git a/lib/less/functions.js b/lib/less/functions.js index 2806bfde..249aaf17 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -13,6 +13,14 @@ tree.functions = { return this.hsla(h, s, l, 1.0); }, hsla: function (h, s, l, a) { + 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; } + } + h = (number(h) % 360) / 360; s = clamp(number(s)); l = clamp(number(l)); a = clamp(number(a)); @@ -23,14 +31,6 @@ tree.functions = { hue(h) * 255, hue(h - 1/3) * 255, a); - - 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; } - } }, hsv: function(h, s, v) { diff --git a/lib/less/rhino.js b/lib/less/rhino.js index 5feb2e74..08f31102 100644 --- a/lib/less/rhino.js +++ b/lib/less/rhino.js @@ -2,6 +2,35 @@ /*global name:true, less, loadStyleSheet */ var name; +function error(e, filename) { + + var content = "Error : " + filename + "\n"; + + filename = e.filename || filename; + + if (e.message) { + content += e.message + "\n"; + } + + var errorline = function (e, i, classname) { + if (e.extract[i]) { + content += + String(parseInt(e.line, 10) + (i - 1)) + + ":" + e.extract[i] + "\n"; + } + }; + + if (e.stack) { + content += e.stack; + } else if (e.extract) { + content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':\n'; + errorline(e, 0); + errorline(e, 1); + errorline(e, 2); + } + print(content); +} + function loadStyleSheet(sheet, callback, reload, remaining) { var endOfPath = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\')), sheetName = name.slice(0, endOfPath + 1) + sheet.href, @@ -96,33 +125,4 @@ function writeFile(filename, content) { quit(1); } print("done"); -}(arguments)); - -function error(e, filename) { - - var content = "Error : " + filename + "\n"; - - filename = e.filename || filename; - - if (e.message) { - content += e.message + "\n"; - } - - var errorline = function (e, i, classname) { - if (e.extract[i]) { - content += - String(parseInt(e.line, 10) + (i - 1)) + - ":" + e.extract[i] + "\n"; - } - }; - - if (e.stack) { - content += e.stack; - } else if (e.extract) { - content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':\n'; - errorline(e, 0); - errorline(e, 1); - errorline(e, 2); - } - print(content); -} +}(arguments)); \ No newline at end of file diff --git a/test/less-test.js b/test/less-test.js index bc00bc6c..30e62c2b 100644 --- a/test/less-test.js +++ b/test/less-test.js @@ -1,3 +1,4 @@ +/*jshint latedef: nofunc */ var path = require('path'), fs = require('fs'), sys = require('util'); From cae65e4ba0edeb2d24f3dc42074f500e28b7017c Mon Sep 17 00:00:00 2001 From: Luke Page Date: Sun, 1 Sep 2013 14:59:19 +0100 Subject: [PATCH 108/114] Fix bug with extends. Fixes issue with added space with extend. Now all unit tests pass again --- lib/less/extend-visitor.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/less/extend-visitor.js b/lib/less/extend-visitor.js index aead49f2..8fb793d8 100644 --- a/lib/less/extend-visitor.js +++ b/lib/less/extend-visitor.js @@ -327,7 +327,8 @@ matchIndex, selector, firstElement, - match; + match, + newElements; for (matchIndex = 0; matchIndex < matches.length; matchIndex++) { match = matches[matchIndex]; @@ -345,14 +346,21 @@ currentSelectorPathIndex++; } - path = path.concat(selectorPath.slice(currentSelectorPathIndex, match.pathIndex)); + newElements = selector.elements + .slice(currentSelectorPathElementIndex, match.index) + .concat([firstElement]) + .concat(replacementSelector.elements.slice(1)); - path.push(new tree.Selector( - selector.elements - .slice(currentSelectorPathElementIndex, match.index) - .concat([firstElement]) - .concat(replacementSelector.elements.slice(1)) - )); + if (currentSelectorPathIndex === match.pathIndex && matchIndex > 0) { + path[path.length - 1].elements = + path[path.length - 1].elements.concat(newElements); + } else { + path = path.concat(selectorPath.slice(currentSelectorPathIndex, match.pathIndex)); + + path.push(new tree.Selector( + newElements + )); + } currentSelectorPathIndex = match.endPathIndex; currentSelectorPathElementIndex = match.endPathElementIndex; if (currentSelectorPathElementIndex >= selector.elements.length) { From f37de83082907af2356d12f0db90c7141c132be2 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Sun, 1 Sep 2013 15:04:21 +0100 Subject: [PATCH 109/114] Fix another extend issue - Fixes #1404 --- lib/less/extend-visitor.js | 2 +- test/css/extend-selector.css | 8 ++++++++ test/less/extend-selector.less | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/less/extend-visitor.js b/lib/less/extend-visitor.js index 8fb793d8..9e66c089 100644 --- a/lib/less/extend-visitor.js +++ b/lib/less/extend-visitor.js @@ -363,7 +363,7 @@ } currentSelectorPathIndex = match.endPathIndex; currentSelectorPathElementIndex = match.endPathElementIndex; - if (currentSelectorPathElementIndex >= selector.elements.length) { + if (currentSelectorPathElementIndex >= selectorPath[currentSelectorPathIndex].elements.length) { currentSelectorPathElementIndex = 0; currentSelectorPathIndex++; } diff --git a/test/css/extend-selector.css b/test/css/extend-selector.css index 4a525746..da47254b 100644 --- a/test/css/extend-selector.css +++ b/test/css/extend-selector.css @@ -70,3 +70,11 @@ div.ext7, .attributes .attributes .attribute-test { extend: attributes2; } +.header .header-nav, +.footer .footer-nav { + background: red; +} +.header .header-nav:before, +.footer .footer-nav:before { + background: blue; +} diff --git a/test/less/extend-selector.less b/test/less/extend-selector.less index 7d6f044c..c7588ee0 100644 --- a/test/less/extend-selector.less +++ b/test/less/extend-selector.less @@ -81,4 +81,19 @@ div.ext5, .attribute-test { &:extend([data="test3"] all); } +} + +.header { + .header-nav { + background: red; + &:before { + background: blue; + } + } +} + +.footer { + .footer-nav { + &:extend( .header .header-nav all ); + } } \ No newline at end of file From 3918f940d0d55c4713733ed32629a5131f0fc605 Mon Sep 17 00:00:00 2001 From: "Hector G. Parra" Date: Thu, 22 Aug 2013 23:16:45 -0700 Subject: [PATCH 110/114] Report bugs to: http://github.com/less/less.js/issues --- lib/less/lessc_helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/less/lessc_helper.js b/lib/less/lessc_helper.js index 1a2bba15..14e70cca 100644 --- a/lib/less/lessc_helper.js +++ b/lib/less/lessc_helper.js @@ -63,7 +63,7 @@ var lessc_helper = { sys.puts(" -su=on|off Allow mixed units, e.g. 1px+1em or 1px*1px which have units"); sys.puts(" --strict-units=on|off that cannot be represented."); sys.puts(""); - sys.puts("Report bugs to: http://github.com/cloudhead/less.js/issues"); + sys.puts("Report bugs to: http://github.com/less/less.js/issues"); sys.puts("Home page: "); } }; From 1b3acd403a7b067bda1db309f514c22425e25056 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Sun, 1 Sep 2013 17:56:59 +0100 Subject: [PATCH 111/114] fix media query bug. Fixes #1502 --- lib/less/tree/mixin.js | 3 +++ test/css/media.css | 10 ++++++++++ test/less/media.less | 15 +++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/lib/less/tree/mixin.js b/lib/less/tree/mixin.js index cd8bf173..ebd32036 100644 --- a/lib/less/tree/mixin.js +++ b/lib/less/tree/mixin.js @@ -39,6 +39,9 @@ tree.mixin.Call.prototype = { if (mixin.matchArgs(args, env)) { if (!mixin.matchCondition || mixin.matchCondition(args, env)) { try { + if (!(mixin instanceof tree.mixin.Definition)) { + mixin = new tree.mixin.Definition("", [], mixin.rules, null, false); + } Array.prototype.push.apply( rules, mixin.eval(env, args, this.important).rules); } catch (e) { diff --git a/test/css/media.css b/test/css/media.css index d90c1e8d..437292ba 100644 --- a/test/css/media.css +++ b/test/css/media.css @@ -201,3 +201,13 @@ body { font-size: 11px; } } +@media (min-width: 480px) { + .nav-justified > li { + display: table-cell; + } +} +@media (min-width: 768px) and (min-width: 480px) { + .menu > li { + display: table-cell; + } +} diff --git a/test/less/media.less b/test/less/media.less index f4ab7e5d..f4e83161 100644 --- a/test/less/media.less +++ b/test/less/media.less @@ -210,3 +210,18 @@ body { body { font-size: 11px; } } } + +.nav-justified { + @media (min-width: 480px) { + > li { + display: table-cell; + } + } +} + +.menu +{ + @media (min-width: 768px) { + .nav-justified(); + } +} From 0d9160d82d87696d37e12811526b5ae324fccac8 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Sun, 1 Sep 2013 18:21:47 +0100 Subject: [PATCH 112/114] Fix browser tests after removing .. path elements --- test/browser/css/urls.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/browser/css/urls.css b/test/browser/css/urls.css index 7001990e..9a9f99e3 100644 --- a/test/browser/css/urls.css +++ b/test/browser/css/urls.css @@ -34,17 +34,17 @@ url: url('http://localhost:8081/browser/less/Trebuchet'); } #data-uri { - uri: url('http://localhost:8081/browser/less/../../data/image.jpg'); + uri: url('http://localhost:8081/data/image.jpg'); } #data-uri-guess { - uri: url('http://localhost:8081/browser/less/../../data/image.jpg'); + uri: url('http://localhost:8081/data/image.jpg'); } #data-uri-ascii { - uri-1: url('http://localhost:8081/browser/less/../../data/page.html'); - uri-2: url('http://localhost:8081/browser/less/../../data/page.html'); + uri-1: url('http://localhost:8081/data/page.html'); + uri-2: url('http://localhost:8081/data/page.html'); } #data-uri-toobig { - uri: url('http://localhost:8081/browser/less/../../data/data-uri-fail.png'); + uri: url('http://localhost:8081/data/data-uri-fail.png'); } #svg-functions { background-image: url('data:image/svg+xml,'); From 090a386a4b24fa88a5a1c221f01178bcb1406d20 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Sun, 1 Sep 2013 18:34:18 +0100 Subject: [PATCH 113/114] don't strip units when compressing for durations --- lib/less/tree/dimension.js | 6 +++--- test/css/compression/compression.css | 2 +- test/less/compression/compression.less | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/less/tree/dimension.js b/lib/less/tree/dimension.js index 7d5044d9..c64cd89a 100644 --- a/lib/less/tree/dimension.js +++ b/lib/less/tree/dimension.js @@ -35,7 +35,7 @@ tree.Dimension.prototype = { if (env && env.compress) { // Zero values doesn't need a unit - if (value === 0 && !this.unit.isAngle()) { + if (value === 0 && this.unit.isLength()) { output.add(strValue); return; } @@ -216,8 +216,8 @@ tree.Unit.prototype = { return this.toString() === unitString; }, - isAngle: function () { - return tree.UnitConversions.angle.hasOwnProperty(this.toCSS()); + isLength: function () { + return Boolean(this.toCSS().match(/px|em|%|in|cm|mm|pc|pt|ex/)); }, isEmpty: function () { diff --git a/test/css/compression/compression.css b/test/css/compression/compression.css index e3582bf3..ada7d3f3 100644 --- a/test/css/compression/compression.css +++ b/test/css/compression/compression.css @@ -1,4 +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 +dimensions{val:.1px;val:0;val:4cm;val:.2;val:5;angles-must-have-unit:0deg;durations-must-have-unit:0s;length-doesnt-have-unit:0;width:auto\9} \ No newline at end of file diff --git a/test/less/compression/compression.less b/test/less/compression/compression.less index 71e8e2a6..83155cc2 100644 --- a/test/less/compression/compression.less +++ b/test/less/compression/compression.less @@ -17,5 +17,7 @@ dimensions { val: 0.2; val: 5; angles-must-have-unit: 0deg; + durations-must-have-unit: 0s; + length-doesnt-have-unit: 0px; width: auto\9; } \ No newline at end of file From 3cc76e6541102e2d307b1828b74976a0fa0419fc Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 3 Sep 2013 07:19:49 +0100 Subject: [PATCH 114/114] 1.5.0 beta 1 --- CHANGELOG.md | 5 +- dist/less-1.5.0.js | 6660 ++++++++++++++++++++++++++++++++++++++++ dist/less-1.5.0.min.js | 11 + lib/less/index.js | 2 +- package.json | 4 +- 5 files changed, 6678 insertions(+), 4 deletions(-) create mode 100644 dist/less-1.5.0.js create mode 100644 dist/less-1.5.0.min.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 564a1209..0eb931f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ -# 1.5.0 WIP +# 1.5.0 Beta 1 + +2013-09-01 - sourcemap support - support for import inline option to include css that you do NOT want less to parse e.g. `@import (inline) "file.css";` @@ -16,6 +18,7 @@ - switched from the little supported and buggy cssmin (previously ycssmin) to clean-css - Browser: added logLevel option to control logging (2 = everything, 1 = errors only, 0 = no logging) - Browser: added errorReporting option which can be "html" (default) or "console" or a function + - A few bug fixes for media queries and extends # 1.4.2 diff --git a/dist/less-1.5.0.js b/dist/less-1.5.0.js new file mode 100644 index 00000000..dcb52cab --- /dev/null +++ b/dist/less-1.5.0.js @@ -0,0 +1,6660 @@ +/* + * LESS - Leaner CSS v1.5.0 + * http://lesscss.org + * + * Copyright (c) 2009-2013, Alexis Sellier + * Licensed under the Apache 2.0 License. + * + * @licence + */ +(function (window, undefined) { +// +// Stub out `require` in the browser +// +function require(arg) { + return window.less[arg.split('/')[1]]; +}; + +if (typeof(window.less) === 'undefined' || typeof(window.less.nodeType) !== 'undefined') { window.less = {}; } +less = window.less; +tree = window.less.tree = {}; +less.mode = 'browser'; +var less, tree; + +// Node.js does not have a header file added which defines less +if (less === undefined) { + less = exports; + tree = require('./tree'); + less.mode = 'node'; +} +// +// less.js - parser +// +// A relatively straight-forward predictive parser. +// There is no tokenization/lexing stage, the input is parsed +// in one sweep. +// +// To make the parser fast enough to run in the browser, several +// optimization had to be made: +// +// - Matching and slicing on a huge input is often cause of slowdowns. +// The solution is to chunkify the input into smaller strings. +// The chunks are stored in the `chunks` var, +// `j` holds the current chunk index, and `current` holds +// the index of the current chunk in relation to `input`. +// This gives us an almost 4x speed-up. +// +// - In many cases, we don't need to match individual tokens; +// for example, if a value doesn't hold any variables, operations +// or dynamic references, the parser can effectively 'skip' it, +// treating it as a literal. +// An example would be '1px solid #000' - which evaluates to itself, +// we don't need to know what the individual components are. +// The drawback, of course is that you don't get the benefits of +// syntax-checking on the CSS. This gives us a 50% speed-up in the parser, +// and a smaller speed-up in the code-gen. +// +// +// Token matching is done with the `$` function, which either takes +// a terminal string or regexp, or a non-terminal function to call. +// It also takes care of moving all the indices forwards. +// +// +less.Parser = function Parser(env) { + var input, // LeSS input string + i, // current index in `input` + j, // current chunk + temp, // temporarily holds a chunk's state, for backtracking + memo, // temporarily holds `i`, when backtracking + furthest, // furthest index the parser has gone to + chunks, // chunkified input + current, // index of current chunk, in `input` + parser; + + // 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)) { + env = new tree.parseEnv(env); + } + + var imports = this.imports = { + paths: env.paths || [], // Search paths, when importing + queue: [], // Files which haven't been imported yet + files: env.files, // Holds the imported parse trees + contents: env.contents, // Holds the imported file contents + mime: env.mime, // MIME type of .less files + error: null, // Error in parsing/evaluating an import + push: function (path, currentFileInfo, importOptions, callback) { + var parserImports = this; + this.queue.push(path); + + var fileParsedFunc = function (e, root, fullPath) { + parserImports.queue.splice(parserImports.queue.indexOf(path), 1); // Remove the path from the queue + + var importedPreviously = fullPath in parserImports.files; + + parserImports.files[fullPath] = root; // Store the root + + if (e && !parserImports.error) { parserImports.error = e; } + + callback(e, root, importedPreviously); + }; + + if (less.Parser.importer) { + less.Parser.importer(path, currentFileInfo, fileParsedFunc, env); + } else { + less.Parser.fileLoader(path, currentFileInfo, function(e, contents, fullPath, newFileInfo) { + if (e) {fileParsedFunc(e); return;} + + var newEnv = new tree.parseEnv(env); + + newEnv.currentFileInfo = newFileInfo; + newEnv.processImports = false; + newEnv.contents[fullPath] = contents; + + if (currentFileInfo.reference || importOptions.reference) { + newFileInfo.reference = true; + } + + if (importOptions.inline) { + fileParsedFunc(null, contents, fullPath); + } else { + new(less.Parser)(newEnv).parse(contents, function (e, root) { + fileParsedFunc(e, root, fullPath); + }); + } + }, env); + } + } + }; + + function save() { temp = chunks[j], memo = i, current = i; } + function restore() { chunks[j] = temp, i = memo, current = i; } + + function sync() { + if (i > current) { + chunks[j] = chunks[j].slice(i - current); + current = i; + } + } + function isWhitespace(c) { + // Could change to \s? + var code = c.charCodeAt(0); + return code === 32 || code === 10 || code === 9; + } + // + // Parse from a token, regexp or string, and move forward if match + // + function $(tok) { + var match, length; + + // + // Non-terminal + // + if (tok instanceof Function) { + return tok.call(parser.parsers); + // + // Terminal + // + // Either match a single character in the input, + // or match a regexp in the current chunk (chunk[j]). + // + } else if (typeof(tok) === 'string') { + match = input.charAt(i) === tok ? tok : null; + length = 1; + sync (); + } else { + sync (); + + if (match = tok.exec(chunks[j])) { + length = match[0].length; + } else { + return null; + } + } + + // The match is confirmed, add the match length to `i`, + // and consume any extra white-space characters (' ' || '\n') + // which come after that. The reason for this is that LeSS's + // grammar is mostly white-space insensitive. + // + if (match) { + skipWhitespace(length); + + if(typeof(match) === 'string') { + return match; + } else { + return match.length === 1 ? match[0] : match; + } + } + } + + function skipWhitespace(length) { + var oldi = i, oldj = j, + endIndex = i + chunks[j].length, + mem = i += length; + + while (i < endIndex) { + 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++; } + + return oldi !== i || oldj !== j; + } + + function expect(arg, msg) { + var result = $(arg); + if (! result) { + error(msg || (typeof(arg) === 'string' ? "expected '" + arg + "' got '" + input.charAt(i) + "'" + : "unexpected token")); + } else { + return result; + } + } + + function error(msg, type) { + var e = new Error(msg); + e.index = i; + e.type = type || 'Syntax'; + throw e; + } + + // Same as $(), but don't change the state of the parser, + // just return the match. + function peek(tok) { + if (typeof(tok) === 'string') { + return input.charAt(i) === tok; + } else { + return tok.test(chunks[j]); + } + } + + function getInput(e, env) { + if (e.filename && env.currentFileInfo.filename && (e.filename !== env.currentFileInfo.filename)) { + return parser.imports.contents[e.filename]; + } else { + return input; + } + } + + function getLocation(index, inputStream) { + var n = index + 1, + line = null, + column = -1; + + 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) { + var filename = env.currentFileInfo.filename; + if(less.mode !== 'browser' && less.mode !== 'rhino') { + filename = require('path').resolve(filename); + } + + return { + lineNumber: getLocation(index, inputStream).line + 1, + fileName: filename + }; + } + + function LessError(e, env) { + var input = getInput(e, 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'; + this.message = e.message; + this.filename = e.filename || env.currentFileInfo.filename; + this.index = e.index; + this.line = typeof(line) === 'number' ? line + 1 : null; + this.callLine = callLine + 1; + this.callExtract = lines[callLine]; + this.stack = e.stack; + this.column = col; + this.extract = [ + lines[line - 1], + lines[line], + lines[line + 1] + ]; + } + + LessError.prototype = new Error(); + LessError.prototype.constructor = LessError; + + this.env = env = env || {}; + + // The optimization level dictates the thoroughness of the parser, + // the lower the number, the less nodes it will create in the tree. + // This could matter for debugging, or if you want to access + // the individual nodes in the tree. + this.optimization = ('optimization' in this.env) ? this.env.optimization : 1; + + // + // The Parser + // + return parser = { + + imports: imports, + // + // Parse an input string into an abstract syntax tree, + // call `callback` when done. + // + parse: function (str, callback) { + var root, line, lines, error = null; + + i = j = current = furthest = 0; + input = str.replace(/\r\n/g, '\n'); + + // 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, + skip = /(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g, + comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g, + string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g, + level = 0, + match, + chunk = chunks[0], + inParam; + + for (var i = 0, c, cc; i < input.length;) { + skip.lastIndex = i; + if (match = skip.exec(input)) { + if (match.index === i) { + i += match[0].length; + chunk.push(match[0]); + } + } + c = input.charAt(i); + comment.lastIndex = string.lastIndex = i; + + if (match = string.exec(input)) { + if (match.index === i) { + i += match[0].length; + chunk.push(match[0]); + continue; + } + } + + if (!inParam && c === '/') { + cc = input.charAt(i + 1); + if (cc === '/' || cc === '*') { + if (match = comment.exec(input)) { + if (match.index === i) { + i += match[0].length; + chunk.push(match[0]); + continue; + } + } + } + } + + switch (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) { + error = new(LessError)({ + index: i-1, + type: 'Parse', + message: (level > 0) ? "missing closing `}`" : "missing opening `{`", + filename: env.currentFileInfo.filename + }, env); + } + + return chunks.map(function (c) { return c.join(''); }); + })([[]]); + + if (error) { + return callback(new(LessError)(error, env)); + } + + // Start with the primary rule. + // The whole syntax tree is held under a Ruleset node, + // with the `root` property set to true, so no `{}` are + // output. The callback is called when the input is parsed. + try { + root = new(tree.Ruleset)([], $(this.parsers.primary)); + root.root = true; + root.firstRoot = true; + } catch (e) { + return callback(new(LessError)(e, env)); + } + + root.toCSS = (function (evaluate) { + return function (options, variables) { + options = options || {}; + var evaldRoot, + css, + evalEnv = new tree.evalEnv(options); + + // + // Allows setting variables with a hash, so: + // + // `{ color: new(tree.Color)('#f01') }` will become: + // + // new(tree.Rule)('@color', + // new(tree.Value)([ + // new(tree.Expression)([ + // new(tree.Color)('#f01') + // ]) + // ]) + // ) + // + if (typeof(variables) === 'object' && !Array.isArray(variables)) { + variables = Object.keys(variables).map(function (k) { + var value = variables[k]; + + if (! (value instanceof tree.Value)) { + if (! (value instanceof tree.Expression)) { + value = new(tree.Expression)([value]); + } + value = new(tree.Value)([value]); + } + return new(tree.Rule)('@' + k, value, false, null, 0); + }); + evalEnv.frames = [new(tree.Ruleset)(null, variables)]; + } + + try { + evaldRoot = evaluate.call(this, evalEnv); + + new(tree.joinSelectorVisitor)() + .run(evaldRoot); + + new(tree.processExtendsVisitor)() + .run(evaldRoot); + + new(tree.toCSSVisitor)({compress: Boolean(options.compress)}) + .run(evaldRoot); + + if (options.sourceMap) { + evaldRoot = new tree.sourceMapOutput( + { + writeSourceMap: options.writeSourceMap, + rootNode: evaldRoot, + contentsMap: parser.imports.contents, + sourceMapFilename: options.sourceMapFilename, + outputFilename: options.sourceMapOutputFilename, + sourceMapBasepath: options.sourceMapBasepath, + sourceMapRootpath: options.sourceMapRootpath, + outputSourceFiles: options.outputSourceFiles + }); + } + + css = evaldRoot.toCSS({ + compress: Boolean(options.compress), + dumpLineNumbers: env.dumpLineNumbers, + strictUnits: Boolean(options.strictUnits)}); + } catch (e) { + throw new(LessError)(e, env); + } + + if (options.cleancss && less.mode === 'node') { + return require('clean-css').process(css); + } else if (options.compress) { + return css.replace(/(^(\s)+)|((\s)+$)/g, ""); + } else { + return css; + } + }; + })(root.eval); + + // If `i` is smaller than the `input.length - 1`, + // it means the parser wasn't able to parse the whole + // string, so we've got a parsing error. + // + // We try to extract a \n delimited string, + // showing the line where the parse error occured. + // We split it up into two parts (the part which parsed, + // 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 = loc.line + 1; + + error = { + type: "Parse", + message: "Unrecognised input", + index: i, + filename: env.currentFileInfo.filename, + line: line, + column: loc.column, + extract: [ + lines[line - 2], + lines[line - 1], + lines[line] + ] + }; + } + + var finish = function (e) { + e = error || e || parser.imports.error; + + if (e) { + if (!(e instanceof LessError)) { + e = new(LessError)(e, env); + } + + callback(e); + } + else { + callback(null, root); + } + }; + + if (env.processImports !== false) { + new tree.importVisitor(this.imports, finish) + .run(root); + } else { + finish(); + } + }, + + // + // Here in, the parsing rules/functions + // + // The basic structure of the syntax tree generated is as follows: + // + // Ruleset -> Rule -> Value -> Expression -> Entity + // + // Here's some LESS code: + // + // .class { + // color: #fff; + // border: 1px solid #000; + // width: @w + 4px; + // > .child {...} + // } + // + // And here's what the parse tree might look like: + // + // Ruleset (Selector '.class', [ + // Rule ("color", Value ([Expression [Color #fff]])) + // Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]])) + // Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]])) + // Ruleset (Selector [Element '>', '.child'], [...]) + // ]) + // + // In general, most rules will try to parse a token with the `$()` function, and if the return + // value is truly, will return a new node, of the relevant type. Sometimes, we need to check + // first, before parsing, that's when we use `peek()`. + // + parsers: { + // + // The `primary` rule is the *entry* and *exit* point of the parser. + // The rules here can appear at any level of the parse tree. + // + // The recursive nature of the grammar is an interplay between the `block` + // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule, + // as represented by this simplified grammar: + // + // primary → (ruleset | rule)+ + // ruleset → selector+ block + // block → '{' primary '}' + // + // Only at one point is the primary rule not called from the + // block rule: at the root level. + // + primary: function () { + var node, root = []; + + while ((node = $(this.extendRule) || $(this.mixin.definition) || $(this.rule) || $(this.ruleset) || + $(this.mixin.call) || $(this.comment) || $(this.directive)) + || $(/^[\s\n]+/) || $(/^;+/)) { + node && root.push(node); + } + return root; + }, + + // We create a Comment node for CSS comments `/* */`, + // but keep the LeSS comments `//` silent, by just skipping + // over them. + comment: function () { + var comment; + + if (input.charAt(i) !== '/') { return; } + + if (input.charAt(i + 1) === '/') { + return new(tree.Comment)($(/^\/\/.*/), true, i, env.currentFileInfo); + } else if (comment = $(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/)) { + return new(tree.Comment)(comment, false, i, env.currentFileInfo); + } + }, + + comments: function () { + var comment, comments = []; + + while(comment = $(this.comment)) { + comments.push(comment); + } + + return comments; + }, + + // + // Entities are tokens which can be found inside an Expression + // + entities: { + // + // A string, which supports escaping " and ' + // + // "milky way" 'he\'s the one!' + // + 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; } + + e && $('~'); + + if (str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/)) { + return new(tree.Quoted)(str[0], str[1] || str[2], e, index, env.currentFileInfo); + } + }, + + // + // A catch-all word, such as: + // + // black border-collapse + // + keyword: function () { + var k; + + if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) { + if (tree.colors.hasOwnProperty(k)) { + // detect named color + return new(tree.Color)(tree.colors[k].slice(1)); + } else { + return new(tree.Keyword)(k); + } + } + }, + + // + // A function call + // + // rgb(255, 0, 255) + // + // We also try to catch IE's `alpha()`, but let the `alpha` parser + // deal with the details. + // + // The arguments are parsed with the `entities.arguments` parser. + // + call: function () { + var name, nameLC, args, alpha_ret, index = i; + + 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 === 'alpha') { + alpha_ret = $(this.alpha); + if(typeof alpha_ret !== 'undefined') { + return alpha_ret; + } + } + + $('('); // Parse the '(' and consume whitespace. + + args = $(this.entities.arguments); + + if (! $(')')) { + return; + } + + if (name) { return new(tree.Call)(name, args, index, env.currentFileInfo); } + }, + arguments: function () { + var args = [], arg; + + while (arg = $(this.entities.assignment) || $(this.expression)) { + args.push(arg); + if (! $(',')) { + break; + } + } + return args; + }, + literal: function () { + return $(this.entities.dimension) || + $(this.entities.color) || + $(this.entities.quoted) || + $(this.entities.unicodeDescriptor); + }, + + // Assignments are argument entities for calls. + // They are present in ie filter properties as shown below. + // + // filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* ) + // + + assignment: function () { + var key, value; + if ((key = $(/^\w+(?=\s?=)/i)) && $('=') && (value = $(this.entity))) { + return new(tree.Assignment)(key, value); + } + }, + + // + // Parse url() tokens + // + // We use a specific rule for urls, because they don't really behave like + // standard function calls. The difference is that the argument doesn't have + // to be enclosed within a string, so it can't be parsed as an Expression. + // + url: function () { + var value; + + 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); + }, + + // + // A Variable entity, such as `@fink`, in + // + // width: @fink + 2px + // + // We use a different parser for variable definitions, + // see `parsers.variable`. + // + variable: function () { + var name, index = i; + + if (input.charAt(i) === '@' && (name = $(/^@@?[\w-]+/))) { + return new(tree.Variable)(name, index, env.currentFileInfo); + } + }, + + // A variable entity useing the protective {} e.g. @{var} + variableCurly: function () { + var curly, index = i; + + if (input.charAt(i) === '@' && (curly = $(/^@\{([\w-]+)\}/))) { + return new(tree.Variable)("@" + curly[1], index, env.currentFileInfo); + } + }, + + // + // A Hexadecimal color + // + // #4F3C2F + // + // `rgb` and `hsl` colors are parsed through the `entities.call` parser. + // + color: function () { + var rgb; + + if (input.charAt(i) === '#' && (rgb = $(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/))) { + return new(tree.Color)(rgb[1]); + } + }, + + // + // A Dimension, that is, a number and a unit + // + // 0.5em 95% + // + 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 (value = $(/^([+-]?\d*\.?\d+)(%|[a-z]+)?/)) { + return new(tree.Dimension)(value[1], value[2]); + } + }, + + // + // A unicode descriptor, as is used in unicode-range + // + // U+0?? or U+00A1-00A9 + // + unicodeDescriptor: function () { + var ud; + + if (ud = $(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/)) { + return new(tree.UnicodeDescriptor)(ud[0]); + } + }, + + // + // JavaScript code to be evaluated + // + // `window.location.href` + // + javascript: function () { + var str, j = i, e; + + if (input.charAt(j) === '~') { j++; e = true; } // Escaped strings + if (input.charAt(j) !== '`') { return; } + if (env.javascriptEnabled !== undefined && !env.javascriptEnabled) { + error("You are using JavaScript, which has been disabled."); + } + + if (e) { $('~'); } + + if (str = $(/^`([^`]*)`/)) { + return new(tree.JavaScript)(str[1], i, e); + } + } + }, + + // + // The variable part of a variable definition. Used in the `rule` parser + // + // @fink: + // + variable: function () { + var name; + + if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1]; } + }, + + // + // extend syntax - used to extend selectors + // + extend: function(isRule) { + var elements, e, index = i, option, extendList = []; + + if (!$(isRule ? /^&:extend\(/ : /^:extend\(/)) { return; } + + do { + option = null; + elements = []; + while (true) { + option = $(/^(all)(?=\s*(\)|,))/); + if (option) { break; } + e = $(this.element); + if (!e) { break; } + elements.push(e); + } + + option = option && option[1]; + + extendList.push(new(tree.Extend)(new(tree.Selector)(elements), option, index)); + + } while($(",")); + + expect(/^\)/); + + if (isRule) { + expect(/^;/); + } + + return extendList; + }, + + // + // extendRule - used in a rule to extend all the parent selectors + // + extendRule: function() { + return this.extend(true); + }, + + // + // Mixins + // + mixin: { + // + // A Mixin call, with an optional argument list + // + // #mixins > .square(#fff); + // .rounded(4px, black); + // .button; + // + // The `while` loop is there because mixins can be + // namespaced, but we only support the child and descendant + // selector for now. + // + call: function () { + var elements = [], e, c, args, index = i, s = input.charAt(i), important = false; + + if (s !== '.' && s !== '#') { return; } + + 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, env.currentFileInfo)); + c = $('>'); + } + if ($('(')) { + args = this.mixin.args.call(this, true).args; + expect(')'); + } + + args = args || []; + + if ($(this.important)) { + important = true; + } + + if (elements.length > 0 && ($(';') || peek('}'))) { + return new(tree.mixin.Call)(elements, args, index, env.currentFileInfo, important); + } + + restore(); + }, + args: function (isCall) { + var expressions = [], argsSemiColon = [], isSemiColonSeperated, argsComma = [], expressionContainsNamed, name, nameLoop, value, arg, + returner = {args:null, variadic: false}; + while (true) { + if (isCall) { + arg = $(this.expression); + } else { + $(this.comments); + if (input.charAt(i) === '.' && $(/^\.{3}/)) { + returner.variadic = true; + if ($(";") && !isSemiColonSeperated) { + isSemiColonSeperated = true; + } + (isSemiColonSeperated ? argsSemiColon : argsComma) + .push({ variadic: true }); + break; + } + arg = $(this.entities.variable) || $(this.entities.literal) + || $(this.entities.keyword); + } + + if (!arg) { + break; + } + + nameLoop = null; + if (arg.throwAwayComments) { + arg.throwAwayComments(); + } + value = arg; + var val = null; + + if (isCall) { + // Variable + if (arg.value.length == 1) { + val = arg.value[0]; + } + } else { + val = arg; + } + + if (val && val instanceof tree.Variable) { + if ($(':')) { + if (expressions.length > 0) { + if (isSemiColonSeperated) { + error("Cannot mix ; and , as delimiter types"); + } + expressionContainsNamed = true; + } + value = expect(this.expression); + nameLoop = (name = val.name); + } else if (!isCall && $(/^\.{3}/)) { + returner.variadic = true; + if ($(";") && !isSemiColonSeperated) { + isSemiColonSeperated = true; + } + (isSemiColonSeperated ? argsSemiColon : argsComma) + .push({ name: arg.name, variadic: true }); + break; + } else if (!isCall) { + name = nameLoop = val.name; + value = null; + } + } + + if (value) { + expressions.push(value); + } + + argsComma.push({ name:nameLoop, value:value }); + + if ($(',')) { + continue; + } + + if ($(';') || isSemiColonSeperated) { + + if (expressionContainsNamed) { + error("Cannot mix ; and , as delimiter types"); + } + + isSemiColonSeperated = true; + + if (expressions.length > 1) { + value = new(tree.Value)(expressions); + } + argsSemiColon.push({ name:name, value:value }); + + name = null; + expressions = []; + expressionContainsNamed = false; + } + } + + returner.args = isSemiColonSeperated ? argsSemiColon : argsComma; + return returner; + }, + // + // A Mixin definition, with a list of parameters + // + // .rounded (@radius: 2px, @color) { + // ... + // } + // + // Until we have a finer grained state-machine, we have to + // do a look-ahead, to make sure we don't have a mixin call. + // See the `rule` function for more information. + // + // We start by matching `.rounded (`, and then proceed on to + // the argument list, which has optional default values. + // We store the parameters in `params`, with a `value` key, + // if there is a value, such as in the case of `@radius`. + // + // Once we've got our params list, and a closing `)`, we parse + // the `{...}` block. + // + definition: function () { + var name, params = [], match, ruleset, cond, variadic = false; + if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') || + peek(/^[^{]*\}/)) { + return; + } + + save(); + + if (match = $(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)) { + name = match[1]; + + var argInfo = this.mixin.args.call(this, false); + params = argInfo.args; + variadic = argInfo.variadic; + + // .mixincall("@{a}"); + // looks a bit like a mixin definition.. so we have to be nice and restore + if (!$(')')) { + furthest = i; + restore(); + } + + $(this.comments); + + if ($(/^when/)) { // Guard + cond = expect(this.conditions, 'expected condition'); + } + + ruleset = $(this.block); + + if (ruleset) { + return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic); + } else { + restore(); + } + } + } + }, + + // + // Entities are the smallest recognized token, + // and can be found inside a rule's value. + // + entity: function () { + return $(this.entities.literal) || $(this.entities.variable) || $(this.entities.url) || + $(this.entities.call) || $(this.entities.keyword) ||$(this.entities.javascript) || + $(this.comment); + }, + + // + // A Rule terminator. Note that we use `peek()` to check for '}', + // because the `block` rule will be expecting it, but we still need to make sure + // it's there, if ';' was ommitted. + // + end: function () { + return $(';') || peek('}'); + }, + + // + // IE's alpha function + // + // alpha(opacity=88) + // + alpha: function () { + var value; + + if (! $(/^\(opacity=/i)) { return; } + if (value = $(/^\d+/) || $(this.entities.variable)) { + expect(')'); + return new(tree.Alpha)(value); + } + }, + + // + // A Selector Element + // + // div + // + h1 + // #socks + // input[type="text"] + // + // Elements are the building blocks for Selectors, + // they are made out of a `Combinator` (see combinator rule), + // and an element name, such as a tag a class, or `*`. + // + element: function () { + var e, c, v; + + c = $(this.combinator); + + e = $(/^(?:\d+\.\d+|\d+)%/) || $(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) || + $('*') || $('&') || $(this.attribute) || $(/^\([^()@]+\)/) || $(/^[\.#](?=@)/) || $(this.entities.variableCurly); + + if (! e) { + if ($('(')) { + if ((v = ($(this.selector))) && + $(')')) { + e = new(tree.Paren)(v); + } + } + } + + if (e) { return new(tree.Element)(c, e, i, env.currentFileInfo); } + }, + + // + // Combinators combine elements together, in a Selector. + // + // Because our parser isn't white-space sensitive, special care + // has to be taken, when parsing the descendant combinator, ` `, + // as it's an empty space. We have to check the previous character + // in the input, to see if it's a ` ` character. More info on how + // we deal with this in *combinator.js*. + // + combinator: function () { + var c = input.charAt(i); + + if (c === '>' || c === '+' || c === '~' || c === '|') { + 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)(" "); + } else { + return new(tree.Combinator)(null); + } + }, + // + // A CSS selector (see selector below) + // with less extensions e.g. the ability to extend and guard + // + lessSelector: function () { + return this.selector(true); + }, + // + // A CSS Selector + // + // .class > div + h1 + // li a:hover + // + // Selectors are made out of one or more Elements, see above. + // + selector: function (isLess) { + var e, elements = [], c, extend, extendList = [], when, condition; + + while ((isLess && (extend = $(this.extend))) || (isLess && (when = $(/^when/))) || (e = $(this.element))) { + if (when) { + condition = expect(this.conditions, 'expected condition'); + } else if (condition) { + error("CSS guard can only be used at the end of selector"); + } else if (extend) { + extendList.push.apply(extendList, extend); + } else { + if (extendList.length) { + error("Extend can only be used at the end of selector"); + } + c = input.charAt(i); + elements.push(e); + e = null; + } + if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') { + break; + } + } + + if (elements.length > 0) { return new(tree.Selector)(elements, extendList, condition, i, env.currentFileInfo); } + if (extendList.length) { error("Extend must be used to extend a selector, it cannot be used on its own"); } + }, + attribute: function () { + var key, val, op; + + if (! $('[')) { return; } + + if (!(key = $(this.entities.variableCurly))) { + key = expect(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/); + } + + if ((op = $(/^[|~*$^]?=/))) { + val = $(this.entities.quoted) || $(/^[\w-]+/) || $(this.entities.variableCurly); + } + + expect(']'); + + return new(tree.Attribute)(key, op, val); + }, + + // + // The `block` rule is used by `ruleset` and `mixin.definition`. + // It's a wrapper around the `primary` rule, with added `{}`. + // + block: function () { + var content; + if ($('{') && (content = $(this.primary)) && $('}')) { + return content; + } + }, + + // + // div, .class, body > p {...} + // + ruleset: function () { + var selectors = [], s, rules, debugInfo; + + save(); + + if (env.dumpLineNumbers) { + debugInfo = getDebugInfo(i, input, env); + } + + while (s = $(this.lessSelector)) { + selectors.push(s); + $(this.comments); + if (! $(',')) { break; } + if (s.condition) { + error("Guards are only currently allowed on a single selector"); + } + $(this.comments); + } + + if (selectors.length > 0 && (rules = $(this.block))) { + var ruleset = new(tree.Ruleset)(selectors, rules, env.strictImports); + if (env.dumpLineNumbers) { + ruleset.debugInfo = debugInfo; + } + return ruleset; + } else { + // Backtrack + furthest = i; + restore(); + } + }, + rule: function (tryAnonymous) { + var name, value, c = input.charAt(i), important, merge = false; + save(); + + 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 + // but always fallback on the other one + value = !tryAnonymous && (env.compress || (name.charAt(0) === '@')) ? + ($(this.value) || $(this.anonymousValue)) : + ($(this.anonymousValue) || $(this.value)); + + + important = $(this.important); + if (name[name.length-1] === "+") { + merge = true; + name = name.substr(0, name.length - 1); + } + + if (value && $(this.end)) { + return new (tree.Rule)(name, value, important, merge, memo, env.currentFileInfo); + } else { + furthest = i; + restore(); + if (value && !tryAnonymous) { + return this.rule(true); + } + } + } + }, + anonymousValue: function () { + var match; + if (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j])) { + i += match[0].length - 1; + return new(tree.Anonymous)(match[1]); + } + }, + + // + // An @import directive + // + // @import "lib"; + // + // Depending on our environemnt, importing is done differently: + // In the browser, it's an XHR request, in Node, it would be a + // file-system operation. The function used for importing is + // stored in `import`, which we pass to the Import constructor. + // + "import": function () { + var path, features, index = i; + + save(); + + var dir = $(/^@import?\s+/); + + var options = (dir ? $(this.importOptions) : null) || {}; + + if (dir && (path = $(this.entities.quoted) || $(this.entities.url))) { + features = $(this.mediaFeatures); + if ($(';')) { + features = features && new(tree.Value)(features); + return new(tree.Import)(path, features, options, index, env.currentFileInfo); + } + } + + restore(); + }, + + importOptions: function() { + var o, options = {}, optionName, value; + + // list of options, surrounded by parens + if (! $('(')) { return null; } + do { + if (o = $(this.importOption)) { + optionName = o; + value = true; + switch(optionName) { + case "css": + optionName = "less"; + value = false; + break; + case "once": + optionName = "multiple"; + value = false; + break; + } + options[optionName] = value; + if (! $(',')) { break; } + } + } while (o); + expect(')'); + return options; + }, + + importOption: function() { + var opt = $(/^(less|css|multiple|once|inline|reference)/); + if (opt) { + return opt[1]; + } + }, + + mediaFeature: function () { + var e, p, nodes = []; + + do { + if (e = $(this.entities.keyword)) { + nodes.push(e); + } else if ($('(')) { + p = $(this.property); + e = $(this.value); + if ($(')')) { + if (p && e) { + nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, null, i, env.currentFileInfo, true))); + } else if (e) { + nodes.push(new(tree.Paren)(e)); + } else { + return null; + } + } else { return null; } + } + } while (e); + + if (nodes.length > 0) { + return new(tree.Expression)(nodes); + } + }, + + mediaFeatures: function () { + var e, features = []; + + do { + if (e = $(this.mediaFeature)) { + features.push(e); + if (! $(',')) { break; } + } else if (e = $(this.entities.variable)) { + features.push(e); + if (! $(',')) { break; } + } + } while (e); + + return features.length > 0 ? features : null; + }, + + media: function () { + var features, rules, media, debugInfo; + + 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) { + media.debugInfo = debugInfo; + } + return media; + } + } + }, + + // + // A CSS Directive + // + // @charset "utf-8"; + // + directive: function () { + var name, value, rules, nonVendorSpecificName, + hasBlock, hasIdentifier, hasExpression; + + if (input.charAt(i) !== '@') { return; } + + if (value = $(this['import']) || $(this.media)) { + return value; + } + + save(); + + name = $(/^@[a-z-]+/); + + if (!name) { return; } + + nonVendorSpecificName = name; + if (name.charAt(1) == '-' && name.indexOf('-', 2) > 0) { + nonVendorSpecificName = "@" + name.slice(name.indexOf('-', 2) + 1); + } + + switch(nonVendorSpecificName) { + case "@font-face": + hasBlock = true; + break; + case "@viewport": + case "@top-left": + case "@top-left-corner": + case "@top-center": + case "@top-right": + case "@top-right-corner": + case "@bottom-left": + case "@bottom-left-corner": + case "@bottom-center": + case "@bottom-right": + case "@bottom-right-corner": + case "@left-top": + case "@left-middle": + case "@left-bottom": + case "@right-top": + case "@right-middle": + case "@right-bottom": + hasBlock = true; + break; + case "@page": + case "@document": + case "@supports": + case "@keyframes": + hasBlock = true; + hasIdentifier = true; + break; + case "@namespace": + hasExpression = true; + break; + } + + if (hasIdentifier) { + name += " " + ($(/^[^{]+/) || '').trim(); + } + + if (hasBlock) + { + if (rules = $(this.block)) { + return new(tree.Directive)(name, rules, i, env.currentFileInfo); + } + } else { + if ((value = hasExpression ? $(this.expression) : $(this.entity)) && $(';')) { + var directive = new(tree.Directive)(name, value, i, env.currentFileInfo); + if (env.dumpLineNumbers) { + directive.debugInfo = getDebugInfo(i, input, env); + } + return directive; + } + } + + restore(); + }, + + // + // A Value is a comma-delimited list of Expressions + // + // font-family: Baskerville, Georgia, serif; + // + // In a Rule, a Value represents everything after the `:`, + // and before the `;`. + // + value: function () { + var e, expressions = []; + + while (e = $(this.expression)) { + expressions.push(e); + if (! $(',')) { break; } + } + + if (expressions.length > 0) { + return new(tree.Value)(expressions); + } + }, + important: function () { + if (input.charAt(i) === '!') { + return $(/^! *important/); + } + }, + sub: function () { + var a, e; + + if ($('(')) { + if (a = $(this.addition)) { + e = new(tree.Expression)([a]); + expect(')'); + e.parens = true; + return e; + } + } + }, + multiplication: function () { + var m, a, op, operation, isSpaced; + if (m = $(this.operand)) { + isSpaced = isWhitespace(input.charAt(i - 1)); + while (!peek(/^\/[*\/]/) && (op = ($('/') || $('*')))) { + if (a = $(this.operand)) { + m.parensInOp = true; + a.parensInOp = true; + operation = new(tree.Operation)(op, [operation || m, a], isSpaced); + isSpaced = isWhitespace(input.charAt(i - 1)); + } else { + break; + } + } + return operation || m; + } + }, + addition: function () { + var m, a, op, operation, isSpaced; + if (m = $(this.multiplication)) { + isSpaced = isWhitespace(input.charAt(i - 1)); + while ((op = $(/^[-+]\s+/) || (!isSpaced && ($('+') || $('-')))) && + (a = $(this.multiplication))) { + m.parensInOp = true; + a.parensInOp = true; + operation = new(tree.Operation)(op, [operation || m, a], isSpaced); + isSpaced = isWhitespace(input.charAt(i - 1)); + } + return operation || m; + } + }, + conditions: function () { + var a, b, index = i, condition; + + if (a = $(this.condition)) { + while ($(',') && (b = $(this.condition))) { + condition = new(tree.Condition)('or', condition || a, b, index); + } + return condition || a; + } + }, + condition: function () { + var a, b, c, op, index = i, negate = false; + + if ($(/^not/)) { negate = true; } + expect('('); + if (a = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) { + if (op = $(/^(?:>=|=<|[<=>])/)) { + if (b = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) { + c = new(tree.Condition)(op, a, b, index, negate); + } else { + error('expected expression'); + } + } else { + c = new(tree.Condition)('=', a, new(tree.Keyword)('true'), index, negate); + } + expect(')'); + return $(/^and/) ? new(tree.Condition)('and', c, $(this.condition)) : c; + } + }, + + // + // An operand is anything that can be part of an operation, + // such as a Color, or a Variable + // + operand: function () { + var negate, p = input.charAt(i + 1); + + if (input.charAt(i) === '-' && (p === '@' || p === '(')) { negate = $('-'); } + var o = $(this.sub) || $(this.entities.dimension) || + $(this.entities.color) || $(this.entities.variable) || + $(this.entities.call); + + if (negate) { + o.parensInOp = true; + o = new(tree.Negative)(o); + } + + return o; + }, + + // + // Expressions either represent mathematical operations, + // or white-space delimited Entities. + // + // 1px solid black + // @var * 2 + // + expression: function () { + var e, delim, entities = []; + + while (e = $(this.addition) || $(this.entity)) { + entities.push(e); + // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here + if (!peek(/^\/[\/*]/) && (delim = $('/'))) { + entities.push(new(tree.Anonymous)(delim)); + } + } + if (entities.length > 0) { + return new(tree.Expression)(entities); + } + }, + property: function () { + var name; + + if (name = $(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/)) { + return name[1]; + } + }, + ruleProperty: function () { + var name; + + if (name = $(/^(\*?-?[_a-zA-Z0-9-]+)\s*(\+?)\s*:/)) { + return name[1] + (name[2] || ""); + } + } + } + }; +}; + +(function (tree) { + +tree.functions = { + rgb: function (r, g, b) { + return this.rgba(r, g, b, 1.0); + }, + rgba: function (r, g, b, a) { + var rgb = [r, g, b].map(function (c) { return scaled(c, 256); }); + a = number(a); + return new(tree.Color)(rgb, a); + }, + hsl: function (h, s, l) { + return this.hsla(h, s, l, 1.0); + }, + hsla: function (h, s, l, a) { + 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; } + } + + h = (number(h) % 360) / 360; + s = clamp(number(s)); l = clamp(number(l)); a = clamp(number(a)); + + var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; + var m1 = l * 2 - m2; + + return this.rgba(hue(h + 1/3) * 255, + hue(h) * 255, + hue(h - 1/3) * 255, + a); + }, + + hsv: function(h, s, v) { + return this.hsva(h, s, v, 1.0); + }, + + hsva: function(h, s, v, a) { + h = ((number(h) % 360) / 360) * 360; + s = number(s); v = number(v); a = number(a); + + var i, f; + i = Math.floor((h / 60) % 6); + f = (h / 60) - i; + + var vs = [v, + v * (1 - s), + v * (1 - f * s), + v * (1 - (1 - f) * s)]; + var perm = [[0, 3, 1], + [2, 0, 1], + [1, 0, 3], + [1, 2, 0], + [3, 1, 0], + [0, 1, 2]]; + + return this.rgba(vs[perm[i][0]] * 255, + vs[perm[i][1]] * 255, + vs[perm[i][2]] * 255, + a); + }, + + hue: function (color) { + return new(tree.Dimension)(Math.round(color.toHSL().h)); + }, + saturation: function (color) { + return new(tree.Dimension)(Math.round(color.toHSL().s * 100), '%'); + }, + lightness: function (color) { + return new(tree.Dimension)(Math.round(color.toHSL().l * 100), '%'); + }, + hsvhue: function(color) { + return new(tree.Dimension)(Math.round(color.toHSV().h)); + }, + hsvsaturation: function (color) { + return new(tree.Dimension)(Math.round(color.toHSV().s * 100), '%'); + }, + hsvvalue: function (color) { + return new(tree.Dimension)(Math.round(color.toHSV().v * 100), '%'); + }, + red: function (color) { + return new(tree.Dimension)(color.rgb[0]); + }, + green: function (color) { + return new(tree.Dimension)(color.rgb[1]); + }, + blue: function (color) { + return new(tree.Dimension)(color.rgb[2]); + }, + alpha: function (color) { + return new(tree.Dimension)(color.toHSL().a); + }, + luma: function (color) { + return new(tree.Dimension)(Math.round(color.luma() * color.alpha * 100), '%'); + }, + saturate: function (color, amount) { + // filter: saturate(3.2); + // should be kept as is, so check for color + if (!color.rgb) { + return null; + } + var hsl = color.toHSL(); + + hsl.s += amount.value / 100; + hsl.s = clamp(hsl.s); + return hsla(hsl); + }, + desaturate: function (color, amount) { + var hsl = color.toHSL(); + + hsl.s -= amount.value / 100; + hsl.s = clamp(hsl.s); + return hsla(hsl); + }, + lighten: function (color, amount) { + var hsl = color.toHSL(); + + hsl.l += amount.value / 100; + hsl.l = clamp(hsl.l); + return hsla(hsl); + }, + darken: function (color, amount) { + var hsl = color.toHSL(); + + hsl.l -= amount.value / 100; + hsl.l = clamp(hsl.l); + return hsla(hsl); + }, + fadein: function (color, amount) { + var hsl = color.toHSL(); + + hsl.a += amount.value / 100; + hsl.a = clamp(hsl.a); + return hsla(hsl); + }, + fadeout: function (color, amount) { + var hsl = color.toHSL(); + + hsl.a -= amount.value / 100; + hsl.a = clamp(hsl.a); + return hsla(hsl); + }, + fade: function (color, amount) { + var hsl = color.toHSL(); + + hsl.a = amount.value / 100; + hsl.a = clamp(hsl.a); + return hsla(hsl); + }, + spin: function (color, amount) { + var hsl = color.toHSL(); + var hue = (hsl.h + amount.value) % 360; + + hsl.h = hue < 0 ? 360 + hue : hue; + + return hsla(hsl); + }, + // + // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein + // http://sass-lang.com + // + mix: function (color1, color2, weight) { + if (!weight) { + weight = new(tree.Dimension)(50); + } + var p = weight.value / 100.0; + var w = p * 2 - 1; + var a = color1.toHSL().a - color2.toHSL().a; + + var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + var w2 = 1 - w1; + + var rgb = [color1.rgb[0] * w1 + color2.rgb[0] * w2, + color1.rgb[1] * w1 + color2.rgb[1] * w2, + color1.rgb[2] * w1 + color2.rgb[2] * w2]; + + var alpha = color1.alpha * p + color2.alpha * (1 - p); + + return new(tree.Color)(rgb, alpha); + }, + greyscale: function (color) { + return this.desaturate(color, new(tree.Dimension)(100)); + }, + contrast: function (color, dark, light, threshold) { + // filter: contrast(3.2); + // should be kept as is, so check for color + if (!color.rgb) { + return null; + } + if (typeof light === 'undefined') { + light = this.rgba(255, 255, 255, 1.0); + } + if (typeof dark === 'undefined') { + dark = this.rgba(0, 0, 0, 1.0); + } + //Figure out which is actually light and dark! + if (dark.luma() > light.luma()) { + var t = light; + light = dark; + dark = t; + } + if (typeof threshold === 'undefined') { + threshold = 0.43; + } else { + threshold = number(threshold); + } + if ((color.luma() * color.alpha) < threshold) { + return light; + } else { + return dark; + } + }, + e: function (str) { + return new(tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str); + }, + escape: function (str) { + return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29")); + }, + '%': function (quoted /* arg, arg, ...*/) { + var args = Array.prototype.slice.call(arguments, 1), + 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; + }); + } + str = str.replace(/%%/g, '%'); + return new(tree.Quoted)('"' + str + '"', str); + }, + unit: function (val, unit) { + return new(tree.Dimension)(val.value, unit ? unit.toCSS() : ""); + }, + convert: function (val, unit) { + return val.convertTo(unit.value); + }, + round: function (n, f) { + var fraction = typeof(f) === "undefined" ? 0 : f.value; + return this._math(function(num) { return num.toFixed(fraction); }, null, n); + }, + pi: function () { + return new(tree.Dimension)(Math.PI); + }, + mod: function(a, b) { + return new(tree.Dimension)(a.value % b.value, a.unit); + }, + pow: function(x, y) { + if (typeof x === "number" && typeof y === "number") { + x = new(tree.Dimension)(x); + y = new(tree.Dimension)(y); + } else if (!(x instanceof tree.Dimension) || !(y instanceof tree.Dimension)) { + throw { type: "Argument", message: "arguments must be numbers" }; + } + + return new(tree.Dimension)(Math.pow(x.value, y.value), x.unit); + }, + _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); + } else { + throw { type: "Argument", message: "argument must be a number" }; + } + }, + _minmax: function (isMin, args) { + args = Array.prototype.slice.call(args); + switch(args.length) { + case 0: throw { type: "Argument", message: "one or more arguments required" }; + case 1: return args[0]; + } + var i, j, current, currentUnified, referenceUnified, unit, + order = [], // elems only contains original argument values. + values = {}; // key is the unit.toString() for unified tree.Dimension values, + // value is the index into the order array. + for (i = 0; i < args.length; i++) { + current = args[i]; + if (!(current instanceof tree.Dimension)) { + order.push(current); + continue; + } + currentUnified = current.unify(); + unit = currentUnified.unit.toString(); + j = values[unit]; + if (j === undefined) { + values[unit] = order.length; + order.push(current); + continue; + } + referenceUnified = order[j].unify(); + if ( isMin && currentUnified.value < referenceUnified.value || + !isMin && currentUnified.value > referenceUnified.value) { + order[j] = current; + } + } + if (order.length == 1) { + return order[0]; + } + args = order.map(function (a) { return a.toCSS(this.env); }) + .join(this.env.compress ? "," : ", "); + return new(tree.Anonymous)((isMin ? "min" : "max") + "(" + args + ")"); + }, + min: function () { + return this._minmax(true, arguments); + }, + max: function () { + return this._minmax(false, arguments); + }, + argb: function (color) { + return new(tree.Anonymous)(color.toARGB()); + + }, + percentage: function (n) { + return new(tree.Dimension)(n.value * 100, '%'); + }, + color: function (n) { + if (n instanceof tree.Quoted) { + return new(tree.Color)(n.value.slice(1)); + } else { + throw { type: "Argument", message: "argument must be a string" }; + } + }, + iscolor: function (n) { + return this._isa(n, tree.Color); + }, + isnumber: function (n) { + return this._isa(n, tree.Dimension); + }, + isstring: function (n) { + return this._isa(n, tree.Quoted); + }, + iskeyword: function (n) { + return this._isa(n, tree.Keyword); + }, + isurl: function (n) { + return this._isa(n, tree.URL); + }, + ispixel: function (n) { + return this.isunit(n, 'px'); + }, + ispercentage: function (n) { + return this.isunit(n, '%'); + }, + isem: function (n) { + return this.isunit(n, 'em'); + }, + isunit: function (n, unit) { + return (n instanceof tree.Dimension) && n.unit.is(unit.value || unit) ? tree.True : tree.False; + }, + _isa: function (n, Type) { + return (n instanceof Type) ? tree.True : tree.False; + }, + + /* Blending modes */ + + multiply: function(color1, color2) { + var r = color1.rgb[0] * color2.rgb[0] / 255; + var g = color1.rgb[1] * color2.rgb[1] / 255; + var b = color1.rgb[2] * color2.rgb[2] / 255; + return this.rgb(r, g, b); + }, + screen: function(color1, color2) { + var r = 255 - (255 - color1.rgb[0]) * (255 - color2.rgb[0]) / 255; + var g = 255 - (255 - color1.rgb[1]) * (255 - color2.rgb[1]) / 255; + var b = 255 - (255 - color1.rgb[2]) * (255 - color2.rgb[2]) / 255; + return this.rgb(r, g, b); + }, + overlay: function(color1, color2) { + var r = color1.rgb[0] < 128 ? 2 * color1.rgb[0] * color2.rgb[0] / 255 : 255 - 2 * (255 - color1.rgb[0]) * (255 - color2.rgb[0]) / 255; + var g = color1.rgb[1] < 128 ? 2 * color1.rgb[1] * color2.rgb[1] / 255 : 255 - 2 * (255 - color1.rgb[1]) * (255 - color2.rgb[1]) / 255; + var b = color1.rgb[2] < 128 ? 2 * color1.rgb[2] * color2.rgb[2] / 255 : 255 - 2 * (255 - color1.rgb[2]) * (255 - color2.rgb[2]) / 255; + return this.rgb(r, g, b); + }, + softlight: function(color1, color2) { + var t = color2.rgb[0] * color1.rgb[0] / 255; + var r = t + color1.rgb[0] * (255 - (255 - color1.rgb[0]) * (255 - color2.rgb[0]) / 255 - t) / 255; + t = color2.rgb[1] * color1.rgb[1] / 255; + var g = t + color1.rgb[1] * (255 - (255 - color1.rgb[1]) * (255 - color2.rgb[1]) / 255 - t) / 255; + t = color2.rgb[2] * color1.rgb[2] / 255; + var b = t + color1.rgb[2] * (255 - (255 - color1.rgb[2]) * (255 - color2.rgb[2]) / 255 - t) / 255; + return this.rgb(r, g, b); + }, + hardlight: function(color1, color2) { + var r = color2.rgb[0] < 128 ? 2 * color2.rgb[0] * color1.rgb[0] / 255 : 255 - 2 * (255 - color2.rgb[0]) * (255 - color1.rgb[0]) / 255; + var g = color2.rgb[1] < 128 ? 2 * color2.rgb[1] * color1.rgb[1] / 255 : 255 - 2 * (255 - color2.rgb[1]) * (255 - color1.rgb[1]) / 255; + var b = color2.rgb[2] < 128 ? 2 * color2.rgb[2] * color1.rgb[2] / 255 : 255 - 2 * (255 - color2.rgb[2]) * (255 - color1.rgb[2]) / 255; + return this.rgb(r, g, b); + }, + difference: function(color1, color2) { + var r = Math.abs(color1.rgb[0] - color2.rgb[0]); + var g = Math.abs(color1.rgb[1] - color2.rgb[1]); + var b = Math.abs(color1.rgb[2] - color2.rgb[2]); + return this.rgb(r, g, b); + }, + exclusion: function(color1, color2) { + var r = color1.rgb[0] + color2.rgb[0] * (255 - color1.rgb[0] - color1.rgb[0]) / 255; + var g = color1.rgb[1] + color2.rgb[1] * (255 - color1.rgb[1] - color1.rgb[1]) / 255; + var b = color1.rgb[2] + color2.rgb[2] * (255 - color1.rgb[2] - color1.rgb[2]) / 255; + return this.rgb(r, g, b); + }, + average: function(color1, color2) { + var r = (color1.rgb[0] + color2.rgb[0]) / 2; + var g = (color1.rgb[1] + color2.rgb[1]) / 2; + var b = (color1.rgb[2] + color2.rgb[2]) / 2; + return this.rgb(r, g, b); + }, + negation: function(color1, color2) { + var r = 255 - Math.abs(255 - color2.rgb[0] - color1.rgb[0]); + var g = 255 - Math.abs(255 - color2.rgb[1] - color1.rgb[1]); + var b = 255 - Math.abs(255 - color2.rgb[2] - color1.rgb[2]); + return this.rgb(r, g, b); + }, + tint: function(color, amount) { + return this.mix(this.rgb(255,255,255), color, amount); + }, + shade: function(color, amount) { + return this.mix(this.rgb(0, 0, 0), color, amount); + }, + extract: function(values, index) { + index = index.value - 1; // (1-based index) + return values.value[index]; + }, + + "data-uri": function(mimetypeNode, filePathNode) { + + if (typeof window !== 'undefined') { + return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env); + } + + var mimetype = mimetypeNode.value; + var filePath = (filePathNode && filePathNode.value); + + var fs = require("fs"), + path = require("path"), + useBase64 = false; + + if (arguments.length < 2) { + filePath = mimetype; + } + + if (this.env.isPathRelative(filePath)) { + if (this.currentFileInfo.relativeUrls) { + filePath = path.join(this.currentFileInfo.currentDirectory, filePath); + } else { + filePath = path.join(this.currentFileInfo.entryPath, filePath); + } + } + + // detect the mimetype if not given + if (arguments.length < 2) { + var mime; + try { + mime = require('mime'); + } catch (ex) { + mime = tree._mime; + } + + mimetype = mime.lookup(filePath); + + // 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'; } + } + else { + useBase64 = /;base64$/.test(mimetype); + } + + var buf = fs.readFileSync(filePath); + + // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded + // and the --ieCompat flag is enabled, return a normal url() instead. + var DATA_URI_MAX_KB = 32, + fileSizeInKB = parseInt((buf.length / 1024), 10); + if (fileSizeInKB >= DATA_URI_MAX_KB) { + + if (this.env.ieCompat !== false) { + if (!this.env.silent) { + console.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB); + } + + return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env); + } else if (!this.env.silent) { + // if explicitly disabled (via --no-ie-compat on CLI, or env.ieCompat === false), merely warn + console.warn("WARNING: Embedding %s (%dKB) exceeds IE8's data-uri size limit of %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB); + } + } + + buf = useBase64 ? buf.toString('base64') + : encodeURIComponent(buf); + + var uri = "'data:" + mimetype + ',' + buf + "'"; + return new(tree.URL)(new(tree.Anonymous)(uri)); + }, + + "svg-gradient": function(direction) { + + function throwArgumentDescriptor() { + throw { type: "Argument", message: "svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]" }; + } + + if (arguments.length < 3) { + throwArgumentDescriptor(); + } + var stops = Array.prototype.slice.call(arguments, 1), + gradientDirectionSvg, + gradientType = "linear", + rectangleDimension = 'x="0" y="0" width="1" height="1"', + useBase64 = true, + renderEnv = {compress: false}, + returner, + directionValue = direction.toCSS(renderEnv), + i, color, position, positionValue, alpha; + + switch (directionValue) { + case "to bottom": + gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"'; + break; + case "to right": + gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"'; + break; + case "to bottom right": + gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"'; + break; + case "to top right": + gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"'; + break; + case "ellipse": + case "ellipse at center": + gradientType = "radial"; + gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"'; + rectangleDimension = 'x="-50" y="-50" width="101" height="101"'; + break; + default: + throw { type: "Argument", message: "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" }; + } + returner = '' + + '' + + '<' + gradientType + 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' + gradientDirectionSvg + '>'; + + for (i = 0; i < stops.length; i+= 1) { + if (stops[i].value) { + color = stops[i].value[0]; + position = stops[i].value[1]; + } else { + color = stops[i]; + position = undefined; + } + + if (!(color instanceof tree.Color) || (!((i === 0 || i+1 === stops.length) && position === undefined) && !(position instanceof tree.Dimension))) { + throwArgumentDescriptor(); + } + positionValue = position ? position.toCSS(renderEnv) : i === 0 ? "0%" : "100%"; + alpha = color.alpha; + returner += ''; + } + returner += '' + + ''; + + if (useBase64) { + // only works in node, needs interface to what is supported in environment + try { + returner = new Buffer(returner).toString('base64'); + } catch(e) { + useBase64 = false; + } + } + + returner = "'data:image/svg+xml" + (useBase64 ? ";base64" : "") + "," + returner + "'"; + return new(tree.URL)(new(tree.Anonymous)(returner)); + } +}; + +// these static methods are used as a fallback when the optional 'mime' dependency is missing +tree._mime = { + // this map is intentionally incomplete + // if you want more, install 'mime' dep + _types: { + '.htm' : 'text/html', + '.html': 'text/html', + '.gif' : 'image/gif', + '.jpg' : 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png' : 'image/png' + }, + lookup: function (filepath) { + var ext = require('path').extname(filepath), + type = tree._mime._types[ext]; + if (type === undefined) { + throw new Error('Optional dependency "mime" is required for ' + ext); + } + return type; + }, + charsets: { + lookup: function (type) { + // assumes all text types are UTF-8 + return type && (/^text\//).test(type) ? 'UTF-8' : ''; + } + } +}; + +var mathFunctions = [{name:"ceil"}, {name:"floor"}, {name: "sqrt"}, {name:"abs"}, + {name:"tan", unit: ""}, {name:"sin", unit: ""}, {name:"cos", unit: ""}, + {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(); + } + return this._math(Math[name], unit, n); + }; + }; + +for(var i = 0; i < mathFunctions.length; i++) { + tree.functions[mathFunctions[i].name] = createMathFunction(mathFunctions[i].name, mathFunctions[i].unit); +} + +function hsla(color) { + return tree.functions.hsla(color.h, color.s, color.l, color.a); +} + +function scaled(n, size) { + if (n instanceof tree.Dimension && n.unit.is('%')) { + return parseFloat(n.value * size / 100); + } else { + return number(n); + } +} + +function number(n) { + if (n instanceof tree.Dimension) { + return parseFloat(n.unit.is('%') ? n.value / 100 : n.value); + } else if (typeof(n) === 'number') { + return n; + } else { + throw { + error: "RuntimeError", + message: "color functions take numbers as parameters" + }; + } +} + +function clamp(val) { + return Math.min(1, Math.max(0, val)); +} + +tree.functionCall = function(env, currentFileInfo) { + this.env = env; + this.currentFileInfo = currentFileInfo; +}; + +tree.functionCall.prototype = tree.functions; + +})(require('./tree')); +(function (tree) { + tree.colors = { + 'aliceblue':'#f0f8ff', + 'antiquewhite':'#faebd7', + 'aqua':'#00ffff', + 'aquamarine':'#7fffd4', + 'azure':'#f0ffff', + 'beige':'#f5f5dc', + 'bisque':'#ffe4c4', + 'black':'#000000', + 'blanchedalmond':'#ffebcd', + 'blue':'#0000ff', + 'blueviolet':'#8a2be2', + 'brown':'#a52a2a', + 'burlywood':'#deb887', + 'cadetblue':'#5f9ea0', + 'chartreuse':'#7fff00', + 'chocolate':'#d2691e', + 'coral':'#ff7f50', + 'cornflowerblue':'#6495ed', + 'cornsilk':'#fff8dc', + 'crimson':'#dc143c', + 'cyan':'#00ffff', + 'darkblue':'#00008b', + 'darkcyan':'#008b8b', + 'darkgoldenrod':'#b8860b', + 'darkgray':'#a9a9a9', + 'darkgrey':'#a9a9a9', + 'darkgreen':'#006400', + 'darkkhaki':'#bdb76b', + 'darkmagenta':'#8b008b', + 'darkolivegreen':'#556b2f', + 'darkorange':'#ff8c00', + 'darkorchid':'#9932cc', + 'darkred':'#8b0000', + 'darksalmon':'#e9967a', + 'darkseagreen':'#8fbc8f', + 'darkslateblue':'#483d8b', + 'darkslategray':'#2f4f4f', + 'darkslategrey':'#2f4f4f', + 'darkturquoise':'#00ced1', + 'darkviolet':'#9400d3', + 'deeppink':'#ff1493', + 'deepskyblue':'#00bfff', + 'dimgray':'#696969', + 'dimgrey':'#696969', + 'dodgerblue':'#1e90ff', + 'firebrick':'#b22222', + 'floralwhite':'#fffaf0', + 'forestgreen':'#228b22', + 'fuchsia':'#ff00ff', + 'gainsboro':'#dcdcdc', + 'ghostwhite':'#f8f8ff', + 'gold':'#ffd700', + 'goldenrod':'#daa520', + 'gray':'#808080', + 'grey':'#808080', + 'green':'#008000', + 'greenyellow':'#adff2f', + 'honeydew':'#f0fff0', + 'hotpink':'#ff69b4', + 'indianred':'#cd5c5c', + 'indigo':'#4b0082', + 'ivory':'#fffff0', + 'khaki':'#f0e68c', + 'lavender':'#e6e6fa', + 'lavenderblush':'#fff0f5', + 'lawngreen':'#7cfc00', + 'lemonchiffon':'#fffacd', + 'lightblue':'#add8e6', + 'lightcoral':'#f08080', + 'lightcyan':'#e0ffff', + 'lightgoldenrodyellow':'#fafad2', + 'lightgray':'#d3d3d3', + 'lightgrey':'#d3d3d3', + 'lightgreen':'#90ee90', + 'lightpink':'#ffb6c1', + 'lightsalmon':'#ffa07a', + 'lightseagreen':'#20b2aa', + 'lightskyblue':'#87cefa', + 'lightslategray':'#778899', + 'lightslategrey':'#778899', + 'lightsteelblue':'#b0c4de', + 'lightyellow':'#ffffe0', + 'lime':'#00ff00', + 'limegreen':'#32cd32', + 'linen':'#faf0e6', + 'magenta':'#ff00ff', + 'maroon':'#800000', + 'mediumaquamarine':'#66cdaa', + 'mediumblue':'#0000cd', + 'mediumorchid':'#ba55d3', + 'mediumpurple':'#9370d8', + 'mediumseagreen':'#3cb371', + 'mediumslateblue':'#7b68ee', + 'mediumspringgreen':'#00fa9a', + 'mediumturquoise':'#48d1cc', + 'mediumvioletred':'#c71585', + 'midnightblue':'#191970', + 'mintcream':'#f5fffa', + 'mistyrose':'#ffe4e1', + 'moccasin':'#ffe4b5', + 'navajowhite':'#ffdead', + 'navy':'#000080', + 'oldlace':'#fdf5e6', + 'olive':'#808000', + 'olivedrab':'#6b8e23', + 'orange':'#ffa500', + 'orangered':'#ff4500', + 'orchid':'#da70d6', + 'palegoldenrod':'#eee8aa', + 'palegreen':'#98fb98', + 'paleturquoise':'#afeeee', + 'palevioletred':'#d87093', + 'papayawhip':'#ffefd5', + 'peachpuff':'#ffdab9', + 'peru':'#cd853f', + 'pink':'#ffc0cb', + 'plum':'#dda0dd', + 'powderblue':'#b0e0e6', + 'purple':'#800080', + 'red':'#ff0000', + 'rosybrown':'#bc8f8f', + 'royalblue':'#4169e1', + 'saddlebrown':'#8b4513', + 'salmon':'#fa8072', + 'sandybrown':'#f4a460', + 'seagreen':'#2e8b57', + 'seashell':'#fff5ee', + 'sienna':'#a0522d', + 'silver':'#c0c0c0', + 'skyblue':'#87ceeb', + 'slateblue':'#6a5acd', + 'slategray':'#708090', + 'slategrey':'#708090', + 'snow':'#fffafa', + 'springgreen':'#00ff7f', + 'steelblue':'#4682b4', + 'tan':'#d2b48c', + 'teal':'#008080', + 'thistle':'#d8bfd8', + 'tomato':'#ff6347', + // 'transparent':'rgba(0,0,0,0)', + 'turquoise':'#40e0d0', + 'violet':'#ee82ee', + 'wheat':'#f5deb3', + 'white':'#ffffff', + 'whitesmoke':'#f5f5f5', + 'yellow':'#ffff00', + 'yellowgreen':'#9acd32' + }; +})(require('./tree')); +(function (tree) { + +tree.debugInfo = function(env, ctx, lineSeperator) { + var result=""; + if (env.dumpLineNumbers && !env.compress) { + switch(env.dumpLineNumbers) { + case 'comments': + result = tree.debugInfo.asComment(ctx); + break; + case 'mediaquery': + result = tree.debugInfo.asMediaQuery(ctx); + break; + case 'all': + result = tree.debugInfo.asComment(ctx) + (lineSeperator || "") + tree.debugInfo.asMediaQuery(ctx); + break; + } + } + return result; +}; + +tree.debugInfo.asComment = function(ctx) { + return '/* line ' + ctx.debugInfo.lineNumber + ', ' + ctx.debugInfo.fileName + ' */\n'; +}; + +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; + }) + + '}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; } + } + 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(', ') + ']'; + } else { + return obj.toCSS(false); + } +}; + +tree.toCSS = function (env) { + var strs = []; + this.genCSS(env, { + add: function(chunk, node) { + strs.push(chunk); + } + }); + 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')); +(function (tree) { + +tree.Alpha = function (val) { + this.value = val; +}; +tree.Alpha.prototype = { + type: "Alpha", + accept: function (visitor) { + this.value = visitor.visit(this.value); + }, + eval: function (env) { + if (this.value.eval) { return new tree.Alpha(this.value.eval(env)); } + return this; + }, + 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')); +(function (tree) { + +tree.Anonymous = function (string) { + this.value = string.value || string; +}; +tree.Anonymous.prototype = { + type: "Anonymous", + eval: function () { return this; }, + compare: function (x) { + if (!x.toCSS) { + return -1; + } + + var left = this.toCSS(), + right = x.toCSS(); + + if (left === right) { + return 0; + } + + return left < right ? -1 : 1; + }, + genCSS: function (env, output) { + output.add(this.value); + }, + toCSS: tree.toCSS +}; + +})(require('../tree')); +(function (tree) { + +tree.Assignment = function (key, val) { + this.key = key; + this.value = val; +}; +tree.Assignment.prototype = { + type: "Assignment", + accept: function (visitor) { + this.value = visitor.visit(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')); +(function (tree) { + +// +// A function call node. +// +tree.Call = function (name, args, index, currentFileInfo) { + this.name = name; + this.args = args; + this.index = index; + this.currentFileInfo = currentFileInfo; +}; +tree.Call.prototype = { + type: "Call", + accept: function (visitor) { + this.args = visitor.visit(this.args); + }, + // + // When evaluating a function call, + // we either find the function in `tree.functions` [1], + // in which case we call it, passing the evaluated arguments, + // if this returns null or we cannot find the function, we + // simply print it out as it appeared originally [2]. + // + // The *functions.js* file contains the built-in functions. + // + // The reason why we evaluate the arguments, is in the case where + // we try to pass a variable to a function, like: `saturate(@color)`. + // The function should receive the value, not the variable. + // + eval: function (env) { + var args = this.args.map(function (a) { return a.eval(env); }), + nameLC = this.name.toLowerCase(), + result, func; + + if (nameLC in tree.functions) { // 1. + try { + func = new tree.functionCall(env, this.currentFileInfo); + result = func[nameLC].apply(func, args); + /*jshint eqnull:true */ + if (result != null) { + return result; + } + } catch (e) { + throw { type: e.type || "Runtime", + message: "error evaluating function `" + this.name + "`" + + (e.message ? ': ' + e.message : ''), + index: this.index, filename: this.currentFileInfo.filename }; + } + } + + return new tree.Call(this.name, args, this.index, this.currentFileInfo); + }, + + genCSS: function (env, output) { + output.add(this.name + "(", this.currentFileInfo, this.index); + + 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')); +(function (tree) { +// +// RGB Colors - #ff0014, #eee +// +tree.Color = function (rgb, a) { + // + // The end goal here, is to parse the arguments + // into an integer triplet, such as `128, 255, 0` + // + // This facilitates operations and conversions. + // + if (Array.isArray(rgb)) { + this.rgb = rgb; + } else if (rgb.length == 6) { + this.rgb = rgb.match(/.{2}/g).map(function (c) { + return parseInt(c, 16); + }); + } else { + this.rgb = rgb.split('').map(function (c) { + return parseInt(c + c, 16); + }); + } + this.alpha = typeof(a) === 'number' ? a : 1; +}; +tree.Color.prototype = { + type: "Color", + 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) { + 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); + }).concat(this.alpha).join(',' + (compress ? '' : ' ')) + ")"; + } else { + var color = this.toRGB(); + + if (compress) { + var splitcolor = color.split(''); + + // Convert color to short format + if (splitcolor[1] === splitcolor[2] && splitcolor[3] === splitcolor[4] && splitcolor[5] === splitcolor[6]) { + color = '#' + splitcolor[1] + splitcolor[3] + splitcolor[5]; + } + } + + return color; + } + }, + + // + // Operations have to be done per-channel, if not, + // channels will spill onto each other. Once we have + // our result, in the form of an integer triplet, + // we create a new Color node to hold the result. + // + operate: function (env, op, other) { + var result = []; + + if (! (other instanceof tree.Color)) { + other = other.toColor(); + } + + for (var c = 0; c < 3; c++) { + result[c] = tree.operate(env, op, this.rgb[c], other.rgb[c]); + } + return new(tree.Color)(result, this.alpha + other.alpha); + }, + + toRGB: function () { + return '#' + this.rgb.map(function (i) { + i = Math.round(i); + i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); + return i.length === 1 ? '0' + i : i; + }).join(''); + }, + + toHSL: function () { + var r = this.rgb[0] / 255, + g = this.rgb[1] / 255, + b = this.rgb[2] / 255, + a = this.alpha; + + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, l = (max + min) / 2, d = max - min; + + if (max === min) { + h = s = 0; + } else { + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + return { h: h * 360, s: s, l: l, a: a }; + }, + //Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript + toHSV: function () { + var r = this.rgb[0] / 255, + g = this.rgb[1] / 255, + b = this.rgb[2] / 255, + a = this.alpha; + + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, v = max; + + var d = max - min; + if (max === 0) { + s = 0; + } else { + s = d / max; + } + + if (max === min) { + h = 0; + } else { + switch(max){ + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + return { h: h * 360, s: s, v: v, a: a }; + }, + toARGB: function () { + var argb = [Math.round(this.alpha * 255)].concat(this.rgb); + return '#' + argb.map(function (i) { + i = Math.round(i); + i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); + return i.length === 1 ? '0' + i : i; + }).join(''); + }, + compare: function (x) { + if (!x.rgb) { + return -1; + } + + return (x.rgb[0] === this.rgb[0] && + x.rgb[1] === this.rgb[1] && + x.rgb[2] === this.rgb[2] && + x.alpha === this.alpha) ? 0 : -1; + } +}; + + +})(require('../tree')); +(function (tree) { + +tree.Comment = function (value, silent, index, currentFileInfo) { + this.value = value; + this.silent = !!silent; + this.currentFileInfo = currentFileInfo; +}; +tree.Comment.prototype = { + type: "Comment", + genCSS: function (env, output) { + if (this.debugInfo) { + 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 + }, + toCSS: tree.toCSS, + isSilent: function(env) { + var isReference = (this.currentFileInfo && this.currentFileInfo.reference && !this.isReferenced), + isCompressed = env.compress && !this.value.match(/^\/\*!/); + return this.silent || isReference || isCompressed; + }, + eval: function () { return this; }, + markReferenced: function () { + this.isReferenced = true; + } +}; + +})(require('../tree')); +(function (tree) { + +tree.Condition = function (op, l, r, i, negate) { + this.op = op.trim(); + this.lvalue = l; + this.rvalue = r; + this.index = i; + this.negate = negate; +}; +tree.Condition.prototype = { + type: "Condition", + accept: function (visitor) { + this.lvalue = visitor.visit(this.lvalue); + this.rvalue = visitor.visit(this.rvalue); + }, + eval: function (env) { + var a = this.lvalue.eval(env), + b = this.rvalue.eval(env); + + var i = this.index, result; + + result = (function (op) { + switch (op) { + case 'and': + return a && b; + case 'or': + return a || b; + default: + if (a.compare) { + result = a.compare(b); + } else if (b.compare) { + result = b.compare(a); + } else { + throw { type: "Type", + message: "Unable to perform comparison", + index: i }; + } + switch (result) { + case -1: return op === '<' || op === '=<'; + case 0: return op === '=' || op === '>=' || op === '=<'; + case 1: return op === '>' || op === '>='; + } + } + })(this.op); + return this.negate ? !result : result; + } +}; + +})(require('../tree')); +(function (tree) { + +// +// A number with a unit +// +tree.Dimension = function (value, unit) { + this.value = parseFloat(value); + this.unit = (unit && unit instanceof tree.Unit) ? unit : + new(tree.Unit)(unit ? [unit] : undefined); +}; + +tree.Dimension.prototype = { + type: "Dimension", + accept: function (visitor) { + this.unit = visitor.visit(this.unit); + }, + eval: function (env) { + return this; + }, + toColor: function () { + return new(tree.Color)([this.value, this.value, this.value]); + }, + genCSS: function (env, output) { + 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()); + } + + var value = this.value, + strValue = String(value); + + if (value !== 0 && value < 0.000001 && value > -0.000001) { + // would be output 1e-6 etc. + strValue = value.toFixed(20).replace(/0+$/, ""); + } + + if (env && env.compress) { + // Zero values doesn't need a unit + if (value === 0 && this.unit.isLength()) { + output.add(strValue); + return; + } + + // Float values doesn't need a leading zero + if (value > 0 && value < 1) { + strValue = (strValue).substr(1); + } + } + + 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, + // 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(); + + if (op === '+' || op === '-') { + 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) { + // do nothing + } else { + other = other.convertTo(this.unit.usedUnits()); + + if(env.strictUnits && other.unit.toString() !== unit.toString()) { + throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '" + unit.toString() + + "' and '" + other.unit.toString() + "'."); + } + + value = tree.operate(env, op, this.value, other.value); + } + } else if (op === '*') { + unit.numerator = unit.numerator.concat(other.unit.numerator).sort(); + unit.denominator = unit.denominator.concat(other.unit.denominator).sort(); + unit.cancel(); + } else if (op === '/') { + unit.numerator = unit.numerator.concat(other.unit.denominator).sort(); + unit.denominator = unit.denominator.concat(other.unit.numerator).sort(); + unit.cancel(); + } + return new(tree.Dimension)(value, unit); + }, + + compare: function (other) { + if (other instanceof tree.Dimension) { + var a = this.unify(), b = other.unify(), + aValue = a.value, bValue = b.value; + + if (bValue > aValue) { + return -1; + } else if (bValue < aValue) { + return 1; + } else { + if (!b.unit.isEmpty() && a.unit.compare(b.unit) !== 0) { + return -1; + } + return 0; + } + } else { + return -1; + } + }, + + unify: function () { + return this.convertTo({ length: 'm', duration: 's', angle: 'rad' }); + }, + + convertTo: function (conversions) { + var value = this.value, unit = this.unit.clone(), + i, groupName, group, targetUnit, derivedConversions = {}, applyUnit; + + 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) { + /*jshint loopfunc:true */ + if (group.hasOwnProperty(atomicUnit)) { + if (denominator) { + value = value / (group[atomicUnit] / group[targetUnit]); + } else { + value = value * (group[atomicUnit] / group[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(); + + 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 + } +}; + +tree.Unit = function (numerator, denominator, 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); + }, + genCSS: function (env, output) { + if (this.numerator.length >= 1) { + output.add(this.numerator[0]); + } else + if (this.denominator.length >= 1) { + output.add(this.denominator[0]); + } else + if ((!env || !env.strictUnits) && this.backupUnit) { + output.add(this.backupUnit); + } + }, + toCSS: tree.toCSS, + + 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; + }, + + isLength: function () { + return Boolean(this.toCSS().match(/px|em|%|in|cm|mm|pc|pt|ex/)); + }, + + isEmpty: function () { + return this.numerator.length === 0 && this.denominator.length === 0; + }, + + isSingular: function() { + return this.numerator.length <= 1 && this.denominator.length === 0; + }, + + map: function(callback) { + var i; + + for (i = 0; i < this.numerator.length; i++) { + this.numerator[i] = callback(this.numerator[i], false); + } + + for (i = 0; i < this.denominator.length; i++) { + this.denominator[i] = callback(this.denominator[i], true); + } + }, + + usedUnits: function() { + var group, result = {}, mapUnit; + + mapUnit = function (atomicUnit) { + /*jshint loopfunc:true */ + if (group.hasOwnProperty(atomicUnit) && !result[groupName]) { + result[groupName] = atomicUnit; + } + + return atomicUnit; + }; + + for (var groupName in tree.UnitConversions) { + if (tree.UnitConversions.hasOwnProperty(groupName)) { + group = tree.UnitConversions[groupName]; + + this.map(mapUnit); + } + } + + 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; + } + 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')); +(function (tree) { + +tree.Directive = function (name, value, index, currentFileInfo) { + this.name = name; + + if (Array.isArray(value)) { + this.rules = [new(tree.Ruleset)([], value)]; + this.rules[0].allowImports = true; + } else { + this.value = value; + } + this.currentFileInfo = currentFileInfo; + +}; +tree.Directive.prototype = { + type: "Directive", + accept: function (visitor) { + this.rules = visitor.visit(this.rules); + this.value = visitor.visit(this.value); + }, + genCSS: function (env, output) { + output.add(this.name, this.currentFileInfo, this.index); + if (this.rules) { + tree.outputRuleset(env, output, this.rules); + } else { + output.add(' '); + this.value.genCSS(env, output); + output.add(';'); + } + }, + toCSS: tree.toCSS, + eval: function (env) { + var evaldDirective = this; + if (this.rules) { + env.frames.unshift(this); + evaldDirective = new(tree.Directive)(this.name, null, this.index, this.currentFileInfo); + evaldDirective.rules = [this.rules[0].eval(env)]; + evaldDirective.rules[0].root = true; + env.frames.shift(); + } + return evaldDirective; + }, + 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); }, + rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.rules[0]); }, + markReferenced: function () { + var i, rules; + this.isReferenced = true; + if (this.rules) { + rules = this.rules[0].rules; + for (i = 0; i < rules.length; i++) { + if (rules[i].markReferenced) { + rules[i].markReferenced(); + } + } + } + } +}; + +})(require('../tree')); +(function (tree) { + +tree.Element = function (combinator, value, index, currentFileInfo) { + this.combinator = combinator instanceof tree.Combinator ? + combinator : new(tree.Combinator)(combinator); + + if (typeof(value) === 'string') { + this.value = value.trim(); + } else if (value) { + this.value = value; + } else { + this.value = ""; + } + this.index = index; + this.currentFileInfo = currentFileInfo; +}; +tree.Element.prototype = { + type: "Element", + accept: function (visitor) { + this.combinator = visitor.visit(this.combinator); + this.value = visitor.visit(this.value); + }, + eval: function (env) { + return new(tree.Element)(this.combinator, + this.value.eval ? this.value.eval(env) : this.value, + this.index, + this.currentFileInfo); + }, + genCSS: function (env, output) { + output.add(this.toCSS(env), this.currentFileInfo, this.index); + }, + toCSS: function (env) { + var value = (this.value.toCSS ? this.value.toCSS(env) : this.value); + if (value === '' && this.combinator.value.charAt(0) === '&') { + return ''; + } else { + return this.combinator.toCSS(env || {}) + value; + } + } +}; + +tree.Attribute = function (key, op, value) { + this.key = key; + this.op = op; + this.value = value; +}; +tree.Attribute.prototype = { + type: "Attribute", + accept: function (visitor) { + this.value = visitor.visit(this.value); + }, + eval: function (env) { + 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; + + if (this.op) { + value += this.op; + value += (this.value.toCSS ? this.value.toCSS(env) : this.value); + } + + return '[' + value + ']'; + } +}; + +tree.Combinator = function (value) { + if (value === ' ') { + this.value = ' '; + } else { + this.value = value ? value.trim() : ""; + } +}; +tree.Combinator.prototype = { + type: "Combinator", + _outputMap: { + '' : '', + ' ' : ' ', + ':' : ' :', + '+' : ' + ', + '~' : ' ~ ', + '>' : ' > ', + '|' : '|' + }, + _outputMapCompressed: { + '' : '', + ' ' : ' ', + ':' : ' :', + '+' : '+', + '~' : '~', + '>' : '>', + '|' : '|' + }, + genCSS: function (env, output) { + output.add((env.compress ? this._outputMapCompressed : this._outputMap)[this.value]); + }, + toCSS: tree.toCSS +}; + +})(require('../tree')); +(function (tree) { + +tree.Expression = function (value) { this.value = value; }; +tree.Expression.prototype = { + type: "Expression", + accept: function (visitor) { + this.value = visitor.visit(this.value); + }, + eval: function (env) { + var returnValue, + inParenthesis = this.parens && !this.parensInOp, + doubleParen = false; + if (inParenthesis) { + env.inParenthesis(); + } + if (this.value.length > 1) { + returnValue = new(tree.Expression)(this.value.map(function (e) { + return e.eval(env); + })); + } else if (this.value.length === 1) { + if (this.value[0].parens && !this.value[0].parensInOp) { + doubleParen = true; + } + returnValue = this.value[0].eval(env); + } else { + returnValue = this; + } + if (inParenthesis) { + env.outOfParenthesis(); + } + if (this.parens && this.parensInOp && !(env.isMathOn()) && !doubleParen) { + returnValue = new(tree.Paren)(returnValue); + } + return returnValue; + }, + 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); + }); + } +}; + +})(require('../tree')); +(function (tree) { + +tree.Extend = function Extend(selector, option, index) { + this.selector = selector; + this.option = option; + this.index = index; + + switch(option) { + case "all": + this.allowBefore = true; + this.allowAfter = true; + break; + default: + this.allowBefore = false; + this.allowAfter = false; + break; + } +}; + +tree.Extend.prototype = { + type: "Extend", + accept: function (visitor) { + this.selector = visitor.visit(this.selector); + }, + eval: function (env) { + return new(tree.Extend)(this.selector.eval(env), this.option, this.index); + }, + clone: function (env) { + return new(tree.Extend)(this.selector, this.option, this.index); + }, + findSelfSelectors: function (selectors) { + var selfElements = [], + i; + + for(i = 0; i < selectors.length; i++) { + selfElements = selfElements.concat(selectors[i].elements); + } + + this.selfSelectors = [{ elements: selfElements }]; + } +}; + +})(require('../tree')); +(function (tree) { +// +// CSS @import node +// +// The general strategy here is that we don't want to wait +// for the parsing to be completed, before we start importing +// the file. That's because in the context of a browser, +// most of the time will be spent waiting for the server to respond. +// +// On creation, we push the import path to our import queue, though +// `import,push`, we also pass it a callback, which it'll call once +// the file has been fetched, and parsed. +// +tree.Import = function (path, features, options, index, currentFileInfo) { + this.options = options; + this.index = index; + this.path = path; + this.features = features; + this.currentFileInfo = currentFileInfo; + + if (this.options.less !== undefined || this.options.inline) { + this.css = !this.options.less || this.options.inline; + } else { + var pathValue = this.getPath(); + if (pathValue && /css([\?;].*)?$/.test(pathValue)) { + this.css = true; + } + } +}; + +// +// The actual import node doesn't return anything, when converted to CSS. +// The reason is that it's used at the evaluation stage, so that the rules +// it imports can be treated like any other rules. +// +// In `eval`, we make sure all Import nodes get evaluated, recursively, so +// we end up with a flat structure, which can easily be imported in the parent +// ruleset. +// +tree.Import.prototype = { + type: "Import", + accept: function (visitor) { + this.features = visitor.visit(this.features); + this.path = visitor.visit(this.path); + if (!this.options.inline) { + this.root = visitor.visit(this.root); + } + }, + genCSS: function (env, output) { + if (this.css) { + output.add("@import ", this.currentFileInfo, this.index); + 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; + return (this.css !== undefined || /(\.[a-z]*$)|([\?;].*)$/.test(path)) ? path : path + '.less'; + } else if (this.path instanceof tree.URL) { + return this.path.value.value; + } + return null; + }, + evalForImport: function (env) { + return new(tree.Import)(this.path.eval(env), this.features, this.options, this.index, this.currentFileInfo); + }, + evalPath: function (env) { + var path = this.path.eval(env); + var rootpath = this.currentFileInfo && this.currentFileInfo.rootpath; + + if (!(path instanceof tree.URL)) { + if (rootpath) { + var pathValue = path.value; + // Add the base path if the import is relative + if (pathValue && env.isPathRelative(pathValue)) { + path.value = rootpath + pathValue; + } + } + path.value = env.normalizePath(path.value); + } + + return path; + }, + eval: function (env) { + var ruleset, features = this.features && this.features.eval(env); + + if (this.skip) { return []; } + + if (this.options.inline) { + var contents = new(tree.Anonymous)(this.root); + return this.features ? new(tree.Media)([contents], this.features.value) : [contents]; + } else if (this.css) { + var newImport = new(tree.Import)(this.evalPath(env), features, this.options, this.index); + if (!newImport.css && this.error) { + throw this.error; + } + return newImport; + } else { + ruleset = new(tree.Ruleset)([], this.root.rules.slice(0)); + + ruleset.evalImports(env); + + return this.features ? new(tree.Media)(ruleset.rules, this.features.value) : ruleset.rules; + } + } +}; + +})(require('../tree')); +(function (tree) { + +tree.JavaScript = function (string, index, escaped) { + this.escaped = escaped; + this.expression = string; + this.index = index; +}; +tree.JavaScript.prototype = { + type: "JavaScript", + eval: function (env) { + var result, + that = this, + context = {}; + + var expression = this.expression.replace(/@\{([\w-]+)\}/g, function (_, name) { + return tree.jsify(new(tree.Variable)('@' + name, that.index).eval(env)); + }); + + try { + expression = new(Function)('return (' + expression + ')'); + } catch (e) { + throw { message: "JavaScript evaluation error: " + e.message + " from `" + expression + "`" , + index: this.index }; + } + + for (var k in env.frames[0].variables()) { + /*jshint loopfunc:true */ + context[k.slice(1)] = { + value: env.frames[0].variables()[k].value, + toJS: function () { + return this.value.eval(env).toCSS(); + } + }; + } + + try { + result = expression.call(context); + } catch (e) { + throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message + "'" , + index: this.index }; + } + if (typeof(result) === 'string') { + return new(tree.Quoted)('"' + result + '"', result, this.escaped, this.index); + } else if (Array.isArray(result)) { + return new(tree.Anonymous)(result.join(', ')); + } else { + return new(tree.Anonymous)(result); + } + } +}; + +})(require('../tree')); + +(function (tree) { + +tree.Keyword = function (value) { this.value = value; }; +tree.Keyword.prototype = { + type: "Keyword", + eval: function () { return this; }, + genCSS: function (env, output) { + output.add(this.value); + }, + toCSS: tree.toCSS, + compare: function (other) { + if (other instanceof tree.Keyword) { + return other.value === this.value ? 0 : 1; + } else { + return -1; + } + } +}; + +tree.True = new(tree.Keyword)('true'); +tree.False = new(tree.Keyword)('false'); + +})(require('../tree')); +(function (tree) { + +tree.Media = function (value, features, index, currentFileInfo) { + this.index = index; + this.currentFileInfo = currentFileInfo; + + var selectors = this.emptySelectors(); + + this.features = new(tree.Value)(features); + this.rules = [new(tree.Ruleset)(selectors, value)]; + this.rules[0].allowImports = true; +}; +tree.Media.prototype = { + type: "Media", + accept: function (visitor) { + this.features = visitor.visit(this.features); + this.rules = visitor.visit(this.rules); + }, + genCSS: function (env, output) { + output.add('@media ', this.currentFileInfo, this.index); + this.features.genCSS(env, output); + tree.outputRuleset(env, output, this.rules); + }, + toCSS: tree.toCSS, + eval: function (env) { + if (!env.mediaBlocks) { + env.mediaBlocks = []; + env.mediaPath = []; + } + + var media = new(tree.Media)([], [], this.index, this.currentFileInfo); + if(this.debugInfo) { + this.rules[0].debugInfo = this.debugInfo; + media.debugInfo = this.debugInfo; + } + var strictMathBypass = false; + if (!env.strictMath) { + strictMathBypass = true; + env.strictMath = true; + } + try { + media.features = this.features.eval(env); + } + finally { + if (strictMathBypass) { + env.strictMath = false; + } + } + + env.mediaPath.push(media); + env.mediaBlocks.push(media); + + env.frames.unshift(this.rules[0]); + media.rules = [this.rules[0].eval(env)]; + env.frames.shift(); + + env.mediaPath.pop(); + + return env.mediaPath.length === 0 ? media.evalTop(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); }, + rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.rules[0]); }, + emptySelectors: function() { + var el = new(tree.Element)('', '&', this.index, this.currentFileInfo); + return [new(tree.Selector)([el], null, null, this.index, this.currentFileInfo)]; + }, + markReferenced: function () { + var i, rules = this.rules[0].rules; + this.isReferenced = true; + for (i = 0; i < rules.length; i++) { + if (rules[i].markReferenced) { + rules[i].markReferenced(); + } + } + }, + + evalTop: function (env) { + var result = this; + + // Render all dependent Media blocks. + if (env.mediaBlocks.length > 1) { + var selectors = this.emptySelectors(); + result = new(tree.Ruleset)(selectors, env.mediaBlocks); + result.multiMedia = true; + } + + delete env.mediaBlocks; + delete env.mediaPath; + + return result; + }, + evalNested: function (env) { + var i, value, + path = env.mediaPath.concat([this]); + + // Extract the media-query conditions separated with `,` (OR). + for (i = 0; i < path.length; i++) { + value = path[i].features instanceof tree.Value ? + path[i].features.value : path[i].features; + path[i] = Array.isArray(value) ? value : [value]; + } + + // Trace all permutations to generate the resulting media-query. + // + // (a, b and c) with nested (d, e) -> + // a and d + // a and e + // b and c and d + // b and c and e + this.features = new(tree.Value)(this.permute(path).map(function (path) { + path = path.map(function (fragment) { + return fragment.toCSS ? fragment : new(tree.Anonymous)(fragment); + }); + + for(i = path.length - 1; i > 0; i--) { + path.splice(i, 0, new(tree.Anonymous)("and")); + } + + return new(tree.Expression)(path); + })); + + // Fake a tree-node that doesn't output anything. + return new(tree.Ruleset)([], []); + }, + permute: function (arr) { + if (arr.length === 0) { + return []; + } else if (arr.length === 1) { + return arr[0]; + } else { + var result = []; + var rest = this.permute(arr.slice(1)); + for (var i = 0; i < rest.length; i++) { + for (var j = 0; j < arr[0].length; j++) { + result.push([arr[0][j]].concat(rest[i])); + } + } + return result; + } + }, + bubbleSelectors: function (selectors) { + this.rules = [new(tree.Ruleset)(selectors.slice(0), [this.rules[0]])]; + } +}; + +})(require('../tree')); +(function (tree) { + +tree.mixin = {}; +tree.mixin.Call = function (elements, args, index, currentFileInfo, important) { + this.selector = new(tree.Selector)(elements); + this.arguments = args; + this.index = index; + this.currentFileInfo = currentFileInfo; + this.important = important; +}; +tree.mixin.Call.prototype = { + type: "MixinCall", + accept: function (visitor) { + this.selector = visitor.visit(this.selector); + this.arguments = visitor.visit(this.arguments); + }, + eval: function (env) { + var mixins, mixin, args, rules = [], match = false, i, m, f, isRecursive, isOneFound, rule; + + args = this.arguments && this.arguments.map(function (a) { + return { name: a.name, value: a.value.eval(env) }; + }); + + for (i = 0; i < env.frames.length; i++) { + if ((mixins = env.frames[i].find(this.selector)).length > 0) { + isOneFound = true; + for (m = 0; m < mixins.length; m++) { + mixin = mixins[m]; + isRecursive = false; + for(f = 0; f < env.frames.length; f++) { + if ((!(mixin instanceof tree.mixin.Definition)) && mixin === (env.frames[f].originalRuleset || env.frames[f])) { + isRecursive = true; + break; + } + } + if (isRecursive) { + continue; + } + if (mixin.matchArgs(args, env)) { + if (!mixin.matchCondition || mixin.matchCondition(args, env)) { + try { + if (!(mixin instanceof tree.mixin.Definition)) { + mixin = new tree.mixin.Definition("", [], mixin.rules, null, false); + } + Array.prototype.push.apply( + rules, mixin.eval(env, args, this.important).rules); + } catch (e) { + throw { message: e.message, index: this.index, filename: this.currentFileInfo.filename, stack: e.stack }; + } + } + match = true; + } + } + if (match) { + if (!this.currentFileInfo || !this.currentFileInfo.reference) { + for (i = 0; i < rules.length; i++) { + rule = rules[i]; + if (rule.markReferenced) { + rule.markReferenced(); + } + } + } + return rules; + } + } + } + if (isOneFound) { + throw { type: 'Runtime', + message: 'No matching definition was found for `' + + this.selector.toCSS().trim() + '(' + + (args ? args.map(function (a) { + var argValue = ""; + if (a.name) { + argValue += a.name + ":"; + } + if (a.value.toCSS) { + argValue += a.value.toCSS(); + } else { + argValue += "???"; + } + return argValue; + }).join(', ') : "") + ")`", + index: this.index, filename: this.currentFileInfo.filename }; + } else { + throw { type: 'Name', + message: this.selector.toCSS().trim() + " is undefined", + index: this.index, filename: this.currentFileInfo.filename }; + } + } +}; + +tree.mixin.Definition = function (name, params, rules, condition, variadic) { + this.name = name; + this.selectors = [new(tree.Selector)([new(tree.Element)(null, name, this.index, this.currentFileInfo)])]; + this.params = params; + this.condition = condition; + this.variadic = variadic; + this.arity = params.length; + 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; } + }, 0); + this.parent = tree.Ruleset.prototype; + this.frames = []; +}; +tree.mixin.Definition.prototype = { + type: "MixinDefinition", + accept: function (visitor) { + this.params = visitor.visit(this.params); + this.rules = visitor.visit(this.rules); + this.condition = visitor.visit(this.condition); + }, + 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); }, + 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), + i, j, val, name, isNamedFound, argIndex; + + mixinEnv = new tree.evalEnv(mixinEnv, [frame].concat(mixinEnv.frames)); + + if (args) { + args = args.slice(0); + + for(i = 0; i < args.length; i++) { + arg = args[i]; + if (name = (arg && arg.name)) { + isNamedFound = false; + for(j = 0; j < params.length; j++) { + if (!evaldArguments[j] && name === params[j].name) { + evaldArguments[j] = arg.value.eval(env); + frame.rules.unshift(new(tree.Rule)(name, arg.value.eval(env))); + isNamedFound = true; + break; + } + } + if (isNamedFound) { + args.splice(i, 1); + i--; + continue; + } else { + throw { type: 'Runtime', message: "Named argument for " + this.name + + ' ' + args[i].name + ' not found' }; + } + } + } + } + argIndex = 0; + for (i = 0; i < params.length; i++) { + if (evaldArguments[i]) { continue; } + + arg = args && args[argIndex]; + + if (name = params[i].name) { + if (params[i].variadic && args) { + varargs = []; + for (j = argIndex; j < args.length; j++) { + varargs.push(args[j].value.eval(env)); + } + frame.rules.unshift(new(tree.Rule)(name, new(tree.Expression)(varargs).eval(env))); + } else { + val = arg && arg.value; + if (val) { + val = val.eval(env); + } else if (params[i].value) { + val = params[i].value.eval(mixinEnv); + frame.resetCache(); + } else { + throw { type: 'Runtime', message: "wrong number of arguments for " + this.name + + ' (' + args.length + ' for ' + this.arity + ')' }; + } + + frame.rules.unshift(new(tree.Rule)(name, val)); + evaldArguments[i] = val; + } + } + + if (params[i].variadic && args) { + for (j = argIndex; j < args.length; j++) { + evaldArguments[j] = args[j].value.eval(env); + } + } + argIndex++; + } + + return frame; + }, + eval: function (env, args, important) { + var _arguments = [], + mixinFrames = this.frames.concat(env.frames), + frame = this.evalParams(env, new(tree.evalEnv)(env, mixinFrames), args, _arguments), + rules, ruleset; + + frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env))); + + rules = important ? + this.parent.makeImportant.apply(this).rules : this.rules.slice(0); + + ruleset = new(tree.Ruleset)(null, rules).eval(new(tree.evalEnv)(env, + [this, frame].concat(mixinFrames))); + ruleset.originalRuleset = this; + return ruleset; + }, + matchCondition: function (args, env) { + + if (this.condition && !this.condition.eval( + new(tree.evalEnv)(env, + [this.evalParams(env, new(tree.evalEnv)(env, this.frames.concat(env.frames)), args, [])] + .concat(env.frames)))) { + return false; + } + return true; + }, + matchArgs: function (args, env) { + var argsLength = (args && args.length) || 0, len; + + 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; } + } + + len = Math.min(argsLength, this.arity); + + for (var i = 0; i < len; i++) { + if (!this.params[i].name && !this.params[i].variadic) { + if (args[i].value.eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) { + return false; + } + } + } + return true; + } +}; + +})(require('../tree')); +(function (tree) { + +tree.Negative = function (node) { + this.value = node; +}; +tree.Negative.prototype = { + type: "Negative", + accept: function (visitor) { + this.value = visitor.visit(this.value); + }, + genCSS: function (env, output) { + 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); + } + return new(tree.Negative)(this.value.eval(env)); + } +}; + +})(require('../tree')); +(function (tree) { + +tree.Operation = function (op, operands, isSpaced) { + this.op = op.trim(); + this.operands = operands; + this.isSpaced = isSpaced; +}; +tree.Operation.prototype = { + type: "Operation", + accept: function (visitor) { + this.operands = visitor.visit(this.operands); + }, + eval: function (env) { + var a = this.operands[0].eval(env), + b = this.operands[1].eval(env), + temp; + + if (env.isMathOn()) { + if (a instanceof tree.Dimension && b instanceof tree.Color) { + if (this.op === '*' || this.op === '+') { + temp = b, b = a, a = temp; + } else { + throw { type: "Operation", + message: "Can't substract or divide a color from a number" }; + } + } + if (!a.operate) { + throw { type: "Operation", + message: "Operation on an invalid type" }; + } + + return a.operate(env, this.op, b); + } else { + return new(tree.Operation)(this.op, [a, b], this.isSpaced); + } + }, + genCSS: function (env, output) { + 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: tree.toCSS +}; + +tree.operate = function (env, op, a, b) { + switch (op) { + case '+': return a + b; + case '-': return a - b; + case '*': return a * b; + case '/': return a / b; + } +}; + +})(require('../tree')); + +(function (tree) { + +tree.Paren = function (node) { + this.value = node; +}; +tree.Paren.prototype = { + type: "Paren", + accept: function (visitor) { + this.value = visitor.visit(this.value); + }, + genCSS: function (env, output) { + output.add('('); + this.value.genCSS(env, output); + output.add(')'); + }, + toCSS: tree.toCSS, + eval: function (env) { + return new(tree.Paren)(this.value.eval(env)); + } +}; + +})(require('../tree')); +(function (tree) { + +tree.Quoted = function (str, content, escaped, index, currentFileInfo) { + this.escaped = escaped; + this.value = content || ''; + this.quote = str.charAt(0); + this.index = index; + this.currentFileInfo = currentFileInfo; +}; +tree.Quoted.prototype = { + type: "Quoted", + genCSS: function (env, output) { + if (!this.escaped) { + output.add(this.quote, this.currentFileInfo, this.index); + } + 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) { + return new(tree.JavaScript)(exp, that.index, true).eval(env).value; + }).replace(/@\{([\w-]+)\}/g, function (_, name) { + var v = new(tree.Variable)('@' + name, that.index, that.currentFileInfo).eval(env, true); + return (v instanceof tree.Quoted) ? v.value : v.toCSS(); + }); + return new(tree.Quoted)(this.quote + value + this.quote, value, this.escaped, this.index); + }, + compare: function (x) { + if (!x.toCSS) { + return -1; + } + + var left = this.toCSS(), + right = x.toCSS(); + + if (left === right) { + return 0; + } + + return left < right ? -1 : 1; + } +}; + +})(require('../tree')); +(function (tree) { + +tree.Rule = function (name, value, important, merge, index, currentFileInfo, inline) { + this.name = name; + this.value = (value instanceof tree.Value) ? value : new(tree.Value)([value]); + this.important = important ? ' ' + important.trim() : ''; + this.merge = merge; + this.index = index; + this.currentFileInfo = currentFileInfo; + this.inline = inline || false; + this.variable = (name.charAt(0) === '@'); +}; + +tree.Rule.prototype = { + type: "Rule", + accept: function (visitor) { + this.value = visitor.visit(this.value); + }, + genCSS: function (env, output) { + output.add(this.name + (env.compress ? ':' : ': '), this.currentFileInfo, this.index); + 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)) ? "" : ";"), this.currentFileInfo, this.index); + }, + toCSS: tree.toCSS, + eval: function (env) { + var strictMathBypass = false; + if (this.name === "font" && !env.strictMath) { + strictMathBypass = true; + env.strictMath = true; + } + try { + return new(tree.Rule)(this.name, + this.value.eval(env), + this.important, + this.merge, + this.index, this.currentFileInfo, this.inline); + } + finally { + if (strictMathBypass) { + env.strictMath = false; + } + } + }, + makeImportant: function () { + return new(tree.Rule)(this.name, + this.value, + "!important", + this.merge, + this.index, this.currentFileInfo, this.inline); + } +}; + +})(require('../tree')); +(function (tree) { + +tree.Ruleset = function (selectors, rules, strictImports) { + this.selectors = selectors; + this.rules = rules; + this._lookups = {}; + this.strictImports = strictImports; +}; +tree.Ruleset.prototype = { + type: "Ruleset", + accept: function (visitor) { + 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) { + 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; + ruleset.firstRoot = this.firstRoot; + ruleset.allowImports = this.allowImports; + + if(this.debugInfo) { + ruleset.debugInfo = this.debugInfo; + } + + // push the current ruleset to the frames stack + env.frames.unshift(ruleset); + + // currrent selectors + if (!env.selectors) { + env.selectors = []; + } + env.selectors.unshift(this.selectors); + + // Evaluate imports + if (ruleset.root || ruleset.allowImports || !ruleset.strictImports) { + ruleset.evalImports(env); + } + + // Store the frames around mixin definitions, + // so they can be evaluated like closures when the time comes. + for (i = 0; i < ruleset.rules.length; i++) { + if (ruleset.rules[i] instanceof tree.mixin.Definition) { + ruleset.rules[i].frames = env.frames.slice(0); + } + } + + var mediaBlockCount = (env.mediaBlocks && env.mediaBlocks.length) || 0; + + // Evaluate mixin calls. + 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 + // already there. consider returning false here + // but we need a way to "return" variable from mixins + return !(ruleset.variable(r.name)); + } + return true; + }); + ruleset.rules.splice.apply(ruleset.rules, [i, 1].concat(rules)); + i += rules.length-1; + ruleset.resetCache(); + } + } + + // Evaluate everything else + for (i = 0; i < ruleset.rules.length; i++) { + rule = ruleset.rules[i]; + + if (! (rule instanceof tree.mixin.Definition)) { + ruleset.rules[i] = rule.eval ? rule.eval(env) : rule; + } + } + + // Pop the stack + env.frames.shift(); + env.selectors.shift(); + + if (env.mediaBlocks) { + for (i = mediaBlockCount; i < env.mediaBlocks.length; i++) { + env.mediaBlocks[i].bubbleSelectors(selectors); + } + } + + return ruleset; + }, + evalImports: function(env) { + var i, rules; + for (i = 0; i < this.rules.length; i++) { + if (this.rules[i] instanceof tree.Import) { + rules = this.rules[i].eval(env); + if (typeof rules.length === "number") { + this.rules.splice.apply(this.rules, [i, 1].concat(rules)); + i+= rules.length-1; + } else { + this.rules.splice(i, 1, rules); + } + this.resetCache(); + } + } + }, + makeImportant: function() { + return new tree.Ruleset(this.selectors, this.rules.map(function (r) { + if (r.makeImportant) { + return r.makeImportant(); + } else { + return r; + } + }), this.strictImports); + }, + matchArgs: function (args) { + return !args || args.length === 0; + }, + matchCondition: function (args, env) { + var lastSelector = this.selectors[this.selectors.length-1]; + if (lastSelector.condition && + !lastSelector.condition.eval( + new(tree.evalEnv)(env, + env.frames))) { + return false; + } + return true; + }, + resetCache: function () { + this._rulesets = null; + this._variables = null; + this._lookups = {}; + }, + variables: function () { + if (this._variables) { return this._variables; } + else { + return this._variables = this.rules.reduce(function (hash, r) { + if (r instanceof tree.Rule && r.variable === true) { + hash[r.name] = r; + } + return hash; + }, {}); + } + }, + variable: function (name) { + return this.variables()[name]; + }, + rulesets: function () { + return this.rules.filter(function (r) { + return (r instanceof tree.Ruleset) || (r instanceof tree.mixin.Definition); + }); + }, + find: function (selector, self) { + self = self || this; + var rules = [], match, + key = selector.toCSS(); + + if (key in this._lookups) { return this._lookups[key]; } + + this.rulesets().forEach(function (rule) { + if (rule !== self) { + for (var j = 0; j < rule.selectors.length; j++) { + if (match = selector.match(rule.selectors[j])) { + if (selector.elements.length > rule.selectors[j].elements.length) { + Array.prototype.push.apply(rules, rule.find( + new(tree.Selector)(selector.elements.slice(1)), self)); + } else { + rules.push(rule); + } + break; + } + } + } + }); + return this._lookups[key] = rules; + }, + genCSS: function (env, output) { + var i, j, + ruleNodes = [], + rulesetNodes = [], + debugInfo, // Line number debugging + rule, + firstRuleset = true, + path; + + env.tabLevel = (env.tabLevel || 0); + + if (!this.root) { + env.tabLevel++; + } + + var tabRuleStr = env.compress ? '' : Array(env.tabLevel + 1).join(" "), + tabSetStr = env.compress ? '' : Array(env.tabLevel).join(" "); + + for (i = 0; i < this.rules.length; i++) { + rule = this.rules[i]; + if (rule.rules || (rule instanceof tree.Media) || rule instanceof tree.Directive || (this.root && rule instanceof tree.Comment)) { + rulesetNodes.push(rule); + } else { + ruleNodes.push(rule); + } + } + + // If this is the root node, we don't render + // a selector, or {}. + if (!this.root) { + debugInfo = tree.debugInfo(env, this, tabSetStr); + + if (debugInfo) { + output.add(debugInfo); + output.add(tabSetStr); + } + + for(i = 0; i < this.paths.length; i++) { + path = this.paths[i]; + env.firstSelector = true; + for(j = 0; j < path.length; j++) { + output.add(path[j].genCSS(env, output)); + env.firstSelector = false; + } + if (i + 1 < this.paths.length) { + output.add(env.compress ? ',' : (',\n' + tabSetStr)); + } + } + + output.add((env.compress ? '{' : ' {\n') + tabRuleStr); + } + + // Compile rules and rulesets + for (i = 0; i < ruleNodes.length; i++) { + rule = ruleNodes[i]; + + if (i + 1 === ruleNodes.length) { + env.lastRule = true; + } + + if (rule.toCSS) { + output.add(rule.genCSS(env, output)); + } else if (rule.value) { + output.add(rule.value.toString()); + } + + if (!env.lastRule) { + output.add(env.compress ? '' : ('\n' + tabRuleStr)); + } else { + env.lastRule = false; + } + } + + if (!this.root) { + output.add((env.compress ? '}' : '\n' + tabSetStr + '}')); + env.tabLevel--; + } + + for (i = 0; i < rulesetNodes.length; i++) { + if (ruleNodes.length && firstRuleset) { + output.add("\n" + (this.root ? tabRuleStr : tabSetStr)); + } + if (!firstRuleset) { + output.add('\n' + (this.root ? tabRuleStr : tabSetStr)); + } + firstRuleset = false; + output.add(rulesetNodes[i].genCSS(env, output)); + } + + 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(); + } + }, + + joinSelectors: function (paths, context, selectors) { + for (var s = 0; s < selectors.length; s++) { + this.joinSelector(paths, context, selectors[s]); + } + }, + + joinSelector: function (paths, context, selector) { + + var i, j, k, + hasParentSelector, newSelectors, el, sel, parentSel, + newSelectorPath, afterParentJoin, newJoinedSelector, + newJoinedSelectorEmpty, lastSelector, currentElements, + selectorsMultiplied; + + for (i = 0; i < selector.elements.length; i++) { + el = selector.elements[i]; + if (el.value === '&') { + hasParentSelector = true; + } + } + + if (!hasParentSelector) { + if (context.length > 0) { + for (i = 0; i < context.length; i++) { + paths.push(context[i].concat(selector)); + } + } + else { + paths.push([selector]); + } + return; + } + + // The paths are [[Selector]] + // The first list is a list of comma seperated selectors + // The inner list is a list of inheritance seperated selectors + // e.g. + // .a, .b { + // .c { + // } + // } + // == [[.a] [.c]] [[.b] [.c]] + // + + // the elements from the current selector so far + currentElements = []; + // the current list of new selectors to add to the path. + // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors + // by the parents + newSelectors = [[]]; + + for (i = 0; i < selector.elements.length; i++) { + el = selector.elements[i]; + // non parent reference elements just get added + if (el.value !== "&") { + currentElements.push(el); + } else { + // the new list of selectors to add + selectorsMultiplied = []; + + // merge the current list of non parent selector elements + // on to the current list of selectors to add + if (currentElements.length > 0) { + this.mergeElementsOnToSelectors(currentElements, newSelectors); + } + + // loop through our current selectors + 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) { + // the combinator used on el should now be applied to the next element instead so that + // 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, el.index, el.currentFileInfo)); + } + selectorsMultiplied.push(sel); + } + else { + // and the parent selectors + 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 + + // our new selector path + newSelectorPath = []; + // selectors from the parent after the join + afterParentJoin = []; + newJoinedSelectorEmpty = true; + + //construct the joined selector - if & is the first thing this will be empty, + // if not newJoinedSelector will be the last set of elements in the selector + if (sel.length > 0) { + newSelectorPath = sel.slice(0); + lastSelector = newSelectorPath.pop(); + newJoinedSelector = selector.createDerived(lastSelector.elements.slice(0)); + newJoinedSelectorEmpty = false; + } + else { + newJoinedSelector = selector.createDerived([]); + } + + //put together the parent selectors after the join + if (parentSel.length > 1) { + afterParentJoin = afterParentJoin.concat(parentSel.slice(1)); + } + + if (parentSel.length > 0) { + 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, el.index, el.currentFileInfo)); + newJoinedSelector.elements = newJoinedSelector.elements.concat(parentSel[0].elements.slice(1)); + } + + if (!newJoinedSelectorEmpty) { + // now add the joined selector + newSelectorPath.push(newJoinedSelector); + } + + // and the rest of the parent + newSelectorPath = newSelectorPath.concat(afterParentJoin); + + // add that to our new set of selectors + selectorsMultiplied.push(newSelectorPath); + } + } + } + + // our new selectors has been multiplied, so reset the state + newSelectors = selectorsMultiplied; + currentElements = []; + } + } + + // if we have any elements left over (e.g. .a& .b == .b) + // add them on to all the current selectors + if (currentElements.length > 0) { + this.mergeElementsOnToSelectors(currentElements, newSelectors); + } + + for (i = 0; i < newSelectors.length; i++) { + if (newSelectors[i].length > 0) { + paths.push(newSelectors[i]); + } + } + }, + + mergeElementsOnToSelectors: function(elements, selectors) { + var i, sel; + + if (selectors.length === 0) { + selectors.push([ new(tree.Selector)(elements) ]); + return; + } + + 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 + if (sel.length > 0) { + sel[sel.length - 1] = sel[sel.length - 1].createDerived(sel[sel.length - 1].elements.concat(elements)); + } + else { + sel.push(new(tree.Selector)(elements)); + } + } + } +}; +})(require('../tree')); +(function (tree) { + +tree.Selector = function (elements, extendList, condition, index, currentFileInfo, isReferenced) { + this.elements = elements; + this.extendList = extendList || []; + this.condition = condition; + this.currentFileInfo = currentFileInfo || {}; + this.isReferenced = isReferenced; + if (!condition) { + this.evaldCondition = true; + } +}; +tree.Selector.prototype = { + type: "Selector", + accept: function (visitor) { + this.elements = visitor.visit(this.elements); + this.extendList = visitor.visit(this.extendList); + 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; + return newSelector; + }, + match: function (other) { + var elements = this.elements, + len = elements.length, + oelements, olen, max, i; + + oelements = other.elements.slice( + (other.elements.length && other.elements[0].value === "&") ? 1 : 0); + olen = oelements.length; + max = Math.min(len, olen); + + if (olen === 0 || len < olen) { + return false; + } else { + for (i = 0; i < max; i++) { + if (elements[i].value !== oelements[i].value) { + return false; + } + } + } + return true; + }, + eval: function (env) { + var evaldCondition = this.condition && this.condition.eval(env); + + return this.createDerived(this.elements.map(function (e) { + return e.eval(env); + }), this.extendList.map(function(extend) { + return extend.eval(env); + }), evaldCondition); + }, + genCSS: function (env, output) { + var i, element; + if ((!env || !env.firstSelector) && this.elements[0].combinator.value === "") { + output.add(' ', this.currentFileInfo, this.index); + } + if (!this._css) { + //TODO caching? speed comparison? + for(i = 0; i < this.elements.length; i++) { + element = this.elements[i]; + element.genCSS(env, output); + } + } + }, + toCSS: tree.toCSS, + markReferenced: function () { + this.isReferenced = true; + }, + getIsReferenced: function() { + return !this.currentFileInfo.reference || this.isReferenced; + }, + getIsOutput: function() { + return this.evaldCondition; + } +}; + +})(require('../tree')); +(function (tree) { + +tree.UnicodeDescriptor = function (value) { + this.value = value; +}; +tree.UnicodeDescriptor.prototype = { + type: "UnicodeDescriptor", + genCSS: function (env, output) { + output.add(this.value); + }, + toCSS: tree.toCSS, + eval: function () { return this; } +}; + +})(require('../tree')); +(function (tree) { + +tree.URL = function (val, currentFileInfo) { + this.value = val; + this.currentFileInfo = currentFileInfo; +}; +tree.URL.prototype = { + type: "Url", + accept: function (visitor) { + this.value = visitor.visit(this.value); + }, + genCSS: function (env, output) { + output.add("url("); + this.value.genCSS(env, output); + output.add(")"); + }, + toCSS: tree.toCSS, + eval: function (ctx) { + var val = this.value.eval(ctx), rootpath; + + // Add the base path if the URL is relative + rootpath = this.currentFileInfo && this.currentFileInfo.rootpath; + if (rootpath && typeof val.value === "string" && ctx.isPathRelative(val.value)) { + if (!val.quote) { + rootpath = rootpath.replace(/[\(\)'"\s]/g, function(match) { return "\\"+match; }); + } + val.value = rootpath + val.value; + } + + val.value = ctx.normalizePath(val.value); + + return new(tree.URL)(val, null); + } +}; + +})(require('../tree')); +(function (tree) { + +tree.Value = function (value) { + this.value = value; +}; +tree.Value.prototype = { + type: "Value", + accept: function (visitor) { + this.value = visitor.visit(this.value); + }, + eval: function (env) { + if (this.value.length === 1) { + return this.value[0].eval(env); + } else { + return new(tree.Value)(this.value.map(function (v) { + return v.eval(env); + })); + } + }, + genCSS: function (env, output) { + 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 && env.compress) ? ',' : ', '); + } + } + }, + toCSS: tree.toCSS +}; + +})(require('../tree')); +(function (tree) { + +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) { + name = '@' + new(tree.Variable)(name.slice(1)).eval(env).value; + } + + if (this.evaluating) { + throw { type: 'Name', + message: "Recursive variable definition for " + name, + filename: this.currentFileInfo.file, + index: this.index }; + } + + this.evaluating = true; + + if (variable = tree.find(env.frames, function (frame) { + if (v = frame.variable(name)) { + return v.value.eval(env); + } + })) { + this.evaluating = false; + return variable; + } + else { + throw { type: 'Name', + message: "variable " + name + " is undefined", + filename: this.currentFileInfo.filename, + index: this.index }; + } + } +}; + +})(require('../tree')); +(function (tree) { + + var parseCopyProperties = [ + 'paths', // option - unmodified - paths to search for imports on + 'optimization', // option - optimization level (for the chunker) + 'files', // list of files that have been imported, used for import-once + 'contents', // browser-only, contents of all the files + 'relativeUrls', // option - whether to adjust URL's to be relative + 'rootpath', // option - rootpath to append to URL's + 'strictImports', // option - + 'dumpLineNumbers', // option - whether to dump line numbers + 'compress', // option - whether to compress + 'processImports', // option - whether to process imports. if false then imports will not be imported + 'syncImport', // option - whether to import synchronously + 'javascriptEnabled',// option - whether JavaScript is enabled. if undefined, defaults to true + 'mime', // browser only - mime type for sheet import + 'useFileCache', // browser only - whether to use the per file session cache + 'currentFileInfo' // information about the current file - for error reporting and importing and making urls relative etc. + ]; + + //currentFileInfo = { + // 'relativeUrls' - option - whether to adjust URL's to be relative + // 'filename' - full resolved filename of current file + // 'rootpath' - path to append to normal URLs for this node + // 'currentDirectory' - path to the current file, absolute + // 'rootFilename' - filename of the base file + // 'entryPath' - absolute path to the entry file + // 'reference' - whether the file should not be output and only output parts that are referenced + + tree.parseEnv = function(options) { + copyFromOriginal(options, this, parseCopyProperties); + + if (!this.contents) { this.contents = {}; } + if (!this.files) { this.files = {}; } + + if (!this.currentFileInfo) { + var filename = (options && options.filename) || "input"; + var entryPath = filename.replace(/[^\/\\]*$/, ""); + if (options) { + options.filename = null; + } + this.currentFileInfo = { + filename: filename, + relativeUrls: this.relativeUrls, + rootpath: (options && options.rootpath) || "", + currentDirectory: entryPath, + entryPath: entryPath, + rootFilename: filename + }; + } + }; + + var evalCopyProperties = [ + 'silent', // whether to swallow errors and warnings + 'verbose', // whether to log more activity + 'compress', // whether to compress + '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 + 'cleancss', // whether to compress with clean-css + 'sourceMap' // whether to output a source map + ]; + + tree.evalEnv = function(options, frames) { + copyFromOriginal(options, this, evalCopyProperties); + + this.frames = frames || []; + }; + + tree.evalEnv.prototype.inParenthesis = function () { + if (!this.parensStack) { + this.parensStack = []; + } + this.parensStack.push(true); + }; + + tree.evalEnv.prototype.outOfParenthesis = function () { + this.parensStack.pop(); + }; + + tree.evalEnv.prototype.isMathOn = function () { + return this.strictMath ? (this.parensStack && this.parensStack.length) : true; + }; + + tree.evalEnv.prototype.isPathRelative = function (path) { + return !/^(?:[a-z-]+:|\/)/.test(path); + }; + + tree.evalEnv.prototype.normalizePath = function( path ) { + var + segments = path.split("/").reverse(), + segment; + + path = []; + while (segments.length !== 0 ) { + segment = segments.pop(); + switch( segment ) { + case ".": + break; + case "..": + if ((path.length === 0) || (path[path.length - 1] === "..")) { + path.push( segment ); + } else { + path.pop(); + } + break; + default: + path.push( segment ); + break; + } + } + + return path.join("/"); + }; + + //todo - do the same for the toCSS env + //tree.toCSSEnv = function (options) { + //}; + + var copyFromOriginal = function(original, destination, propertiesToCopy) { + if (!original) { return; } + + for(var i = 0; i < propertiesToCopy.length; i++) { + if (original.hasOwnProperty(propertiesToCopy[i])) { + destination[propertiesToCopy[i]] = original[propertiesToCopy[i]]; + } + } + }; + +})(require('./tree')); +(function (tree) { + + tree.visitor = function(implementation) { + this._implementation = implementation; + }; + + tree.visitor.prototype = { + visit: function(node) { + + if (node instanceof Array) { + return this.visitArray(node); + } + + if (!node || !node.type) { + return node; + } + + var funcName = "visit" + node.type, + func = this._implementation[funcName], + visitArgs, newNode; + if (func) { + visitArgs = {visitDeeper: true}; + newNode = func.call(this._implementation, node, visitArgs); + if (this._implementation.isReplacing) { + node = newNode; + } + } + if ((!visitArgs || visitArgs.visitDeeper) && node && node.accept) { + node.accept(this); + } + funcName = funcName + "Out"; + if (this._implementation[funcName]) { + this._implementation[funcName](node); + } + return node; + }, + visitArray: function(nodes) { + var i, newNodes = []; + for(i = 0; i < nodes.length; i++) { + var evald = this.visit(nodes[i]); + if (evald instanceof Array) { + evald = this.flatten(evald); + newNodes = newNodes.concat(evald); + } else { + newNodes.push(evald); + } + } + if (this._implementation.isReplacing) { + return newNodes; + } + return nodes; + }, + doAccept: function (node) { + node.accept(this); + }, + flatten: function(arr, master) { + return arr.reduce(this.flattenReduce.bind(this), master || []); + }, + flattenReduce: function(sum, element) { + if (element instanceof Array) { + sum = this.flatten(element, sum); + } else { + sum.push(element); + } + return sum; + } + }; + +})(require('./tree'));(function (tree) { + tree.importVisitor = function(importer, finish, evalEnv) { + this._visitor = new tree.visitor(this); + this._importer = importer; + this._finish = finish; + this.env = evalEnv || new tree.evalEnv(); + this.importCount = 0; + }; + + tree.importVisitor.prototype = { + isReplacing: true, + run: function (root) { + var error; + try { + // process the contents + this._visitor.visit(root); + } + catch(e) { + error = e; + } + + this.isFinished = true; + + if (this.importCount === 0) { + this._finish(error); + } + }, + visitImport: function (importNode, visitArgs) { + var importVisitor = this, + evaldImportNode, + inlineCSS = importNode.options.inline; + + if (!importNode.css || inlineCSS) { + + try { + evaldImportNode = importNode.evalForImport(this.env); + } catch(e){ + if (!e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; } + // attempt to eval properly and treat as css + importNode.css = true; + // if that fails, this error will be thrown + importNode.error = e; + } + + if (evaldImportNode && (!evaldImportNode.css || inlineCSS)) { + importNode = evaldImportNode; + this.importCount++; + var env = new tree.evalEnv(this.env, this.env.frames.slice(0)); + + this._importer.push(importNode.getPath(), importNode.currentFileInfo, importNode.options, function (e, root, imported) { + if (e && !e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; } + if (imported && !importNode.options.multiple) { importNode.skip = imported; } + + var subFinish = function(e) { + importVisitor.importCount--; + + if (importVisitor.importCount === 0 && importVisitor.isFinished) { + importVisitor._finish(e); + } + }; + + if (root) { + importNode.root = root; + if (!inlineCSS && !importNode.skip) { + new(tree.importVisitor)(importVisitor._importer, subFinish, env) + .run(root); + return; + } + } + + subFinish(); + }); + } + } + visitArgs.visitDeeper = false; + return importNode; + }, + visitRule: function (ruleNode, visitArgs) { + visitArgs.visitDeeper = false; + return ruleNode; + }, + visitDirective: function (directiveNode, visitArgs) { + this.env.frames.unshift(directiveNode); + return directiveNode; + }, + visitDirectiveOut: function (directiveNode) { + this.env.frames.shift(); + }, + visitMixinDefinition: function (mixinDefinitionNode, visitArgs) { + this.env.frames.unshift(mixinDefinitionNode); + return mixinDefinitionNode; + }, + visitMixinDefinitionOut: function (mixinDefinitionNode) { + this.env.frames.shift(); + }, + visitRuleset: function (rulesetNode, visitArgs) { + this.env.frames.unshift(rulesetNode); + return rulesetNode; + }, + visitRulesetOut: function (rulesetNode) { + this.env.frames.shift(); + }, + visitMedia: function (mediaNode, visitArgs) { + this.env.frames.unshift(mediaNode.ruleset); + return mediaNode; + }, + visitMediaOut: function (mediaNode) { + this.env.frames.shift(); + } + }; + +})(require('./tree'));(function (tree) { + tree.joinSelectorVisitor = function() { + this.contexts = [[]]; + this._visitor = new tree.visitor(this); + }; + + tree.joinSelectorVisitor.prototype = { + run: function (root) { + return this._visitor.visit(root); + }, + visitRule: function (ruleNode, visitArgs) { + visitArgs.visitDeeper = false; + }, + visitMixinDefinition: function (mixinDefinitionNode, visitArgs) { + visitArgs.visitDeeper = false; + }, + + visitRuleset: function (rulesetNode, visitArgs) { + var context = this.contexts[this.contexts.length - 1]; + var paths = []; + this.contexts.push(paths); + + if (! rulesetNode.root) { + rulesetNode.selectors = rulesetNode.selectors.filter(function(selector) { return selector.getIsOutput(); }); + if (rulesetNode.selectors.length === 0) { + rulesetNode.rules.length = 0; + } + rulesetNode.joinSelectors(paths, context, rulesetNode.selectors); + rulesetNode.paths = paths; + } + }, + visitRulesetOut: function (rulesetNode) { + this.contexts.length = this.contexts.length - 1; + }, + visitMedia: function (mediaNode, visitArgs) { + var context = this.contexts[this.contexts.length - 1]; + mediaNode.rules[0].root = (context.length === 0 || context[0].multiMedia); + } + }; + +})(require('./tree'));(function (tree) { + tree.toCSSVisitor = function(env) { + this._visitor = new tree.visitor(this); + this._env = env; + }; + + tree.toCSSVisitor.prototype = { + isReplacing: true, + run: function (root) { + return this._visitor.visit(root); + }, + + visitRule: function (ruleNode, visitArgs) { + if (ruleNode.variable) { + return []; + } + return ruleNode; + }, + + visitMixinDefinition: function (mixinNode, visitArgs) { + return []; + }, + + visitExtend: function (extendNode, visitArgs) { + return []; + }, + + visitComment: function (commentNode, visitArgs) { + if (commentNode.isSilent(this._env)) { + return []; + } + 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 []; + } + 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 + // be considered illegal css as it has to be on the first line + if (this.charset) { + if (directiveNode.debugInfo) { + var comment = new tree.Comment("/* " + directiveNode.toCSS(this._env).replace(/\n/g, "")+" */\n"); + comment.debugInfo = directiveNode.debugInfo; + return this._visitor.visit(comment); + } + return []; + } + this.charset = true; + } + return directiveNode; + }, + + checkPropertiesInRoot: function(rules) { + var ruleNode; + for(var i = 0; i < rules.length; i++) { + ruleNode = rules[i]; + if (ruleNode instanceof tree.Rule && !ruleNode.variable) { + throw { message: "properties must be inside selector blocks, they cannot be in the root.", + index: ruleNode.index, filename: ruleNode.currentFileInfo ? ruleNode.currentFileInfo.filename : null}; + } + } + }, + + visitRuleset: function (rulesetNode, visitArgs) { + var rule, rulesets = []; + if (rulesetNode.firstRoot) { + this.checkPropertiesInRoot(rulesetNode.rules); + } + if (! rulesetNode.root) { + + 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; + } + return false; + } + }); + + // Compile rules and rulesets + for (var i = 0; i < rulesetNode.rules.length; i++) { + rule = rulesetNode.rules[i]; + + if (rule.rules) { + // visit because we are moving them out from being a child + rulesets.push(this._visitor.visit(rule)); + rulesetNode.rules.splice(i, 1); + i--; + continue; + } + } + // 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.accept(this._visitor); + } + visitArgs.visitDeeper = false; + + this._mergeRules(rulesetNode.rules); + 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); + } + } else { + rulesetNode.accept(this._visitor); + visitArgs.visitDeeper = false; + if (rulesetNode.firstRoot || rulesetNode.rules.length > 0) { + rulesets.splice(0, 0, rulesetNode); + } + } + if (rulesets.length === 1) { + 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); + } + } + } + } + }, + + _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; + })); + } + }); + } + }; + +})(require('./tree'));(function (tree) { + /*jshint loopfunc:true */ + + tree.extendFinderVisitor = function() { + this._visitor = new tree.visitor(this); + this.contexts = []; + this.allExtendsStack = [[]]; + }; + + tree.extendFinderVisitor.prototype = { + run: function (root) { + root = this._visitor.visit(root); + root.allExtends = this.allExtendsStack[0]; + return root; + }, + visitRule: function (ruleNode, visitArgs) { + visitArgs.visitDeeper = false; + }, + visitMixinDefinition: function (mixinDefinitionNode, visitArgs) { + visitArgs.visitDeeper = false; + }, + visitRuleset: function (rulesetNode, visitArgs) { + + if (rulesetNode.root) { + return; + } + + var i, j, extend, allSelectorsExtendList = [], extendList; + + // get &:extend(.a); rules which apply to all selectors in this ruleset + for(i = 0; i < rulesetNode.rules.length; i++) { + if (rulesetNode.rules[i] instanceof tree.Extend) { + allSelectorsExtendList.push(rulesetNode.rules[i]); + } + } + + // now find every selector and apply the extends that apply to all extends + // and the ones which apply to an individual extend + for(i = 0; i < rulesetNode.paths.length; i++) { + var selectorPath = rulesetNode.paths[i], + selector = selectorPath[selectorPath.length-1]; + extendList = selector.extendList.slice(0).concat(allSelectorsExtendList).map(function(allSelectorsExtend) { + return allSelectorsExtend.clone(); + }); + for(j = 0; j < extendList.length; j++) { + this.foundExtends = true; + extend = extendList[j]; + extend.findSelfSelectors(selectorPath); + extend.ruleset = rulesetNode; + if (j === 0) { extend.firstExtendOnThisSelectorPath = true; } + this.allExtendsStack[this.allExtendsStack.length-1].push(extend); + } + } + + this.contexts.push(rulesetNode.selectors); + }, + visitRulesetOut: function (rulesetNode) { + if (!rulesetNode.root) { + this.contexts.length = this.contexts.length - 1; + } + }, + visitMedia: function (mediaNode, visitArgs) { + mediaNode.allExtends = []; + this.allExtendsStack.push(mediaNode.allExtends); + }, + visitMediaOut: function (mediaNode) { + this.allExtendsStack.length = this.allExtendsStack.length - 1; + }, + visitDirective: function (directiveNode, visitArgs) { + directiveNode.allExtends = []; + this.allExtendsStack.push(directiveNode.allExtends); + }, + visitDirectiveOut: function (directiveNode) { + this.allExtendsStack.length = this.allExtendsStack.length - 1; + } + }; + + tree.processExtendsVisitor = function() { + this._visitor = new tree.visitor(this); + }; + + tree.processExtendsVisitor.prototype = { + run: function(root) { + var extendFinder = new tree.extendFinderVisitor(); + extendFinder.run(root); + if (!extendFinder.foundExtends) { return root; } + root.allExtends = root.allExtends.concat(this.doExtendChaining(root.allExtends, root.allExtends)); + this.allExtendsStack = [root.allExtends]; + return this._visitor.visit(root); + }, + doExtendChaining: function (extendsList, extendsListTarget, iterationCount) { + // + // chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting + // the selector we would do normally, but we are also adding an extend with the same target selector + // this means this new extend can then go and alter other extends + // + // this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors + // this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if + // we look at each selector at a time, as is done in visitRuleset + + var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath, extend, targetExtend, newExtend; + + iterationCount = iterationCount || 0; + + //loop through comparing every extend with every target extend. + // a target extend is the one on the ruleset we are looking at copy/edit/pasting in place + // e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one + // and the second is the target. + // the seperation into two lists allows us to process a subset of chains with a bigger set, as is the + // case when processing media queries + for(extendIndex = 0; extendIndex < extendsList.length; extendIndex++){ + for(targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++){ + + extend = extendsList[extendIndex]; + targetExtend = extendsListTarget[targetExtendIndex]; + + // look for circular references + if (this.inInheritanceChain(targetExtend, extend)) { continue; } + + // find a match in the target extends self selector (the bit before :extend) + selectorPath = [targetExtend.selfSelectors[0]]; + matches = extendVisitor.findMatch(extend, selectorPath); + + if (matches.length) { + + // we found a match, so for each self selector.. + extend.selfSelectors.forEach(function(selfSelector) { + + // process the extend as usual + newSelector = extendVisitor.extendSelector(matches, selectorPath, selfSelector); + + // but now we create a new extend from it + newExtend = new(tree.Extend)(targetExtend.selector, targetExtend.option, 0); + newExtend.selfSelectors = newSelector; + + // add the extend onto the list of extends for that selector + newSelector[newSelector.length-1].extendList = [newExtend]; + + // record that we need to add it. + extendsToAdd.push(newExtend); + newExtend.ruleset = targetExtend.ruleset; + + //remember its parents for circular references + newExtend.parents = [targetExtend, extend]; + + // only process the selector once.. if we have :extend(.a,.b) then multiple + // extends will look at the same selector path, so when extending + // we know that any others will be duplicates in terms of what is added to the css + if (targetExtend.firstExtendOnThisSelectorPath) { + newExtend.firstExtendOnThisSelectorPath = true; + targetExtend.ruleset.paths.push(newSelector); + } + }); + } + } + } + + if (extendsToAdd.length) { + // try to detect circular references to stop a stack overflow. + // may no longer be needed. + this.extendChainCount++; + if (iterationCount > 100) { + var selectorOne = "{unable to calculate}"; + var selectorTwo = "{unable to calculate}"; + try + { + selectorOne = extendsToAdd[0].selfSelectors[0].toCSS(); + selectorTwo = extendsToAdd[0].selector.toCSS(); + } + catch(e) {} + throw {message: "extend circular reference detected. One of the circular extends is currently:"+selectorOne+":extend(" + selectorTwo+")"}; + } + + // now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e... + return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount+1)); + } else { + return extendsToAdd; + } + }, + inInheritanceChain: function (possibleParent, possibleChild) { + if (possibleParent === possibleChild) { + return true; + } + if (possibleChild.parents) { + if (this.inInheritanceChain(possibleParent, possibleChild.parents[0])) { + return true; + } + if (this.inInheritanceChain(possibleParent, possibleChild.parents[1])) { + return true; + } + } + return false; + }, + visitRule: function (ruleNode, visitArgs) { + visitArgs.visitDeeper = false; + }, + visitMixinDefinition: function (mixinDefinitionNode, visitArgs) { + visitArgs.visitDeeper = false; + }, + visitSelector: function (selectorNode, visitArgs) { + visitArgs.visitDeeper = false; + }, + visitRuleset: function (rulesetNode, visitArgs) { + if (rulesetNode.root) { + return; + } + var matches, pathIndex, extendIndex, allExtends = this.allExtendsStack[this.allExtendsStack.length-1], selectorsToAdd = [], extendVisitor = this, selectorPath; + + // look at each selector path in the ruleset, find any extend matches and then copy, find and replace + + for(extendIndex = 0; extendIndex < allExtends.length; extendIndex++) { + for(pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) { + + selectorPath = rulesetNode.paths[pathIndex]; + + // extending extends happens initially, before the main pass + if (selectorPath[selectorPath.length-1].extendList.length) { continue; } + + matches = this.findMatch(allExtends[extendIndex], selectorPath); + + if (matches.length) { + + allExtends[extendIndex].selfSelectors.forEach(function(selfSelector) { + selectorsToAdd.push(extendVisitor.extendSelector(matches, selectorPath, selfSelector)); + }); + } + } + } + rulesetNode.paths = rulesetNode.paths.concat(selectorsToAdd); + }, + findMatch: function (extend, haystackSelectorPath) { + // + // look through the haystack selector path to try and find the needle - extend.selector + // returns an array of selector matches that can then be replaced + // + var haystackSelectorIndex, hackstackSelector, hackstackElementIndex, haystackElement, + targetCombinator, i, + extendVisitor = this, + needleElements = extend.selector.elements, + potentialMatches = [], potentialMatch, matches = []; + + // loop through the haystack elements + for(haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) { + hackstackSelector = haystackSelectorPath[haystackSelectorIndex]; + + for(hackstackElementIndex = 0; hackstackElementIndex < hackstackSelector.elements.length; hackstackElementIndex++) { + + 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)) { + potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0, initialCombinator: haystackElement.combinator}); + } + + for(i = 0; i < potentialMatches.length; i++) { + potentialMatch = potentialMatches[i]; + + // selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't + // 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) { + targetCombinator = ' '; + } + + // if we don't match, null our match to indicate failure + if (!extendVisitor.isElementValuesEqual(needleElements[potentialMatch.matched].value, haystackElement.value) || + (potentialMatch.matched > 0 && needleElements[potentialMatch.matched].combinator.value !== targetCombinator)) { + potentialMatch = null; + } else { + potentialMatch.matched++; + } + + // if we are still valid and have finished, test whether we have elements after and whether these are allowed + if (potentialMatch) { + potentialMatch.finished = potentialMatch.matched === needleElements.length; + if (potentialMatch.finished && + (!extend.allowAfter && (hackstackElementIndex+1 < hackstackSelector.elements.length || haystackSelectorIndex+1 < haystackSelectorPath.length))) { + potentialMatch = null; + } + } + // if null we remove, if not, we are still valid, so either push as a valid match or continue + if (potentialMatch) { + if (potentialMatch.finished) { + potentialMatch.length = needleElements.length; + potentialMatch.endPathIndex = haystackSelectorIndex; + potentialMatch.endPathElementIndex = hackstackElementIndex + 1; // index after end of match + potentialMatches.length = 0; // we don't allow matches to overlap, so start matching again + matches.push(potentialMatch); + } + } else { + potentialMatches.splice(i, 1); + i--; + } + } + } + } + return matches; + }, + isElementValuesEqual: function(elementValue1, elementValue2) { + if (typeof elementValue1 === "string" || typeof elementValue2 === "string") { + return elementValue1 === elementValue2; + } + if (elementValue1 instanceof tree.Attribute) { + if (elementValue1.op !== elementValue2.op || elementValue1.key !== elementValue2.key) { + return false; + } + if (!elementValue1.value || !elementValue2.value) { + if (elementValue1.value || elementValue2.value) { + return false; + } + return true; + } + elementValue1 = elementValue1.value.value || elementValue1.value; + elementValue2 = elementValue2.value.value || elementValue2.value; + return elementValue1 === elementValue2; + } + return false; + }, + extendSelector:function (matches, selectorPath, replacementSelector) { + + //for a set of matches, replace each match with the replacement selector + + var currentSelectorPathIndex = 0, + currentSelectorPathElementIndex = 0, + path = [], + matchIndex, + selector, + firstElement, + match, + newElements; + + for (matchIndex = 0; matchIndex < matches.length; matchIndex++) { + match = matches[matchIndex]; + selector = selectorPath[match.pathIndex]; + firstElement = new tree.Element( + match.initialCombinator, + replacementSelector.elements[0].value, + replacementSelector.elements[0].index, + replacementSelector.elements[0].currentFileInfo + ); + + if (match.pathIndex > currentSelectorPathIndex && currentSelectorPathElementIndex > 0) { + path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex)); + currentSelectorPathElementIndex = 0; + currentSelectorPathIndex++; + } + + newElements = selector.elements + .slice(currentSelectorPathElementIndex, match.index) + .concat([firstElement]) + .concat(replacementSelector.elements.slice(1)); + + if (currentSelectorPathIndex === match.pathIndex && matchIndex > 0) { + path[path.length - 1].elements = + path[path.length - 1].elements.concat(newElements); + } else { + path = path.concat(selectorPath.slice(currentSelectorPathIndex, match.pathIndex)); + + path.push(new tree.Selector( + newElements + )); + } + currentSelectorPathIndex = match.endPathIndex; + currentSelectorPathElementIndex = match.endPathElementIndex; + if (currentSelectorPathElementIndex >= selectorPath[currentSelectorPathIndex].elements.length) { + currentSelectorPathElementIndex = 0; + currentSelectorPathIndex++; + } + } + + if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) { + path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex)); + currentSelectorPathElementIndex = 0; + currentSelectorPathIndex++; + } + + path = path.concat(selectorPath.slice(currentSelectorPathIndex, selectorPath.length)); + + return path; + }, + visitRulesetOut: function (rulesetNode) { + }, + visitMedia: function (mediaNode, visitArgs) { + var newAllExtends = mediaNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]); + newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, mediaNode.allExtends)); + this.allExtendsStack.push(newAllExtends); + }, + visitMediaOut: function (mediaNode) { + this.allExtendsStack.length = this.allExtendsStack.length - 1; + }, + visitDirective: function (directiveNode, visitArgs) { + var newAllExtends = directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]); + newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, directiveNode.allExtends)); + this.allExtendsStack.push(newAllExtends); + }, + visitDirectiveOut: function (directiveNode) { + this.allExtendsStack.length = this.allExtendsStack.length - 1; + } + }; + +})(require('./tree')); +// +// browser.js - client-side engine +// +/*global less, window, document, XMLHttpRequest, location */ + +var isFileProtocol = /^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol); + +less.env = less.env || (location.hostname == '127.0.0.1' || + location.hostname == '0.0.0.0' || + location.hostname == 'localhost' || + location.port.length > 0 || + isFileProtocol ? 'development' + : 'production'); + +var logLevel = { + info: 2, + errors: 1, + none: 0 +}; + +// The amount of logging in the javascript console. +// 2 - Information and errors +// 1 - Errors +// 0 - None +// Defaults to 2 +less.logLevel = typeof(less.logLevel) != 'undefined' ? less.logLevel : logLevel.info; + +// Load styles asynchronously (default: false) +// +// This is set to `false` by default, so that the body +// doesn't start loading before the stylesheets are parsed. +// Setting this to `true` can result in flickering. +// +less.async = less.async || false; +less.fileAsync = less.fileAsync || false; + +// Interval between watch polls +less.poll = less.poll || (isFileProtocol ? 1000 : 1500); + +//Setup user functions +if (less.functions) { + for(var func in less.functions) { + less.tree.functions[func] = less.functions[func]; + } +} + +var dumpLineNumbers = /!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash); +if (dumpLineNumbers) { + less.dumpLineNumbers = dumpLineNumbers[1]; +} + +var typePattern = /^text\/(x-)?less$/; +var cache = null; +var fileCache = {}; + +function log(str, level) { + if (less.env == 'development' && typeof(console) !== 'undefined' && less.logLevel >= level) { + console.log('less: ' + str); + } +} + +function extractId(href) { + return href.replace(/^[a-z-]+:\/+?[^\/]+/, '' ) // Remove protocol & domain + .replace(/^\//, '' ) // Remove root / + .replace(/\.[a-zA-Z]+$/, '' ) // Remove simple extension + .replace(/[^\.\w-]+/g, '-') // Replace illegal characters + .replace(/\./g, ':'); // Replace dots with colons(for valid id) +} + +function errorConsole(e, rootHref) { + var template = '{line} {content}'; + var filename = e.filename || rootHref; + var errors = []; + var content = (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') + + " in " + filename + " "; + + var errorline = function (e, i, classname) { + 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])); + } + }; + + if (e.extract) { + errorline(e, 0, ''); + errorline(e, 1, 'line'); + errorline(e, 2, ''); + content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':\n' + + errors.join('\n'); + } else if (e.stack) { + content += e.stack; + } + log(content, logLevel.errors); +} + +function createCSS(styles, sheet, lastModified) { + // Strip the query-string + var href = sheet.href || ''; + + // If there is no title set, use the filename, minus the extension + var id = 'less:' + (sheet.title || extractId(href)); + + // If this has already been inserted into the DOM, we may need to replace it + var oldCss = document.getElementById(id); + var keepOldCss = false; + + // Create a new stylesheet node for insertion or (if necessary) replacement + var css = document.createElement('style'); + css.setAttribute('type', 'text/css'); + if (sheet.media) { + css.setAttribute('media', sheet.media); + } + css.id = id; + + if (css.styleSheet) { // IE + try { + css.styleSheet.cssText = styles; + } catch (e) { + throw new(Error)("Couldn't reassign styleSheet.cssText."); + } + } else { + css.appendChild(document.createTextNode(styles)); + + // If new contents match contents of oldCss, don't replace oldCss + keepOldCss = (oldCss !== null && oldCss.childNodes.length > 0 && css.childNodes.length > 0 && + oldCss.firstChild.nodeValue === css.firstChild.nodeValue); + } + + var head = document.getElementsByTagName('head')[0]; + + // 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) { + var nextEl = sheet && sheet.nextSibling || null; + if (nextEl) { + nextEl.parentNode.insertBefore(css, nextEl); + } else { + head.appendChild(css); + } + } + if (oldCss && keepOldCss === false) { + oldCss.parentNode.removeChild(oldCss); + } + + // Don't update the local store if the file wasn't modified + if (lastModified && cache) { + log('saving ' + href + ' to cache.', logLevel.info); + try { + cache.setItem(href, styles); + cache.setItem(href + ':timestamp', lastModified); + } catch(e) { + //TODO - could do with adding more robust error handling + log('failed to save', logLevel.errors); + } + } +} + +function errorHTML(e, rootHref) { + var id = 'less-error-message:' + extractId(rootHref || ""); + var template = '
  • {content}
  • '; + var elem = document.createElement('div'), timer, content, errors = []; + var filename = e.filename || rootHref; + var filenameNoPath = filename.match(/([^\/]+(\?.*)?)$/)[1]; + + elem.id = id; + elem.className = "less-error-message"; + + content = '

    ' + (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') + + '

    ' + '

    in ' + filenameNoPath + " "; + + var errorline = function (e, i, classname) { + 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])); + } + }; + + if (e.extract) { + errorline(e, 0, ''); + errorline(e, 1, 'line'); + errorline(e, 2, ''); + content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':

    ' + + '
      ' + errors.join('') + '
    '; + } else if (e.stack) { + content += '
    ' + e.stack.split('\n').slice(1).join('
    '); + } + elem.innerHTML = content; + + // CSS for error messages + createCSS([ + '.less-error-message ul, .less-error-message li {', + 'list-style-type: none;', + 'margin-right: 15px;', + 'padding: 4px 0;', + 'margin: 0;', + '}', + '.less-error-message label {', + 'font-size: 12px;', + 'margin-right: 15px;', + 'padding: 4px 0;', + 'color: #cc7777;', + '}', + '.less-error-message pre {', + 'color: #dd6666;', + 'padding: 4px 0;', + 'margin: 0;', + 'display: inline-block;', + '}', + '.less-error-message pre.line {', + 'color: #ff0000;', + '}', + '.less-error-message h3 {', + 'font-size: 20px;', + 'font-weight: bold;', + 'padding: 15px 0 5px 0;', + 'margin: 0;', + '}', + '.less-error-message a {', + 'color: #10a', + '}', + '.less-error-message .error {', + 'color: red;', + 'font-weight: bold;', + 'padding-bottom: 2px;', + 'border-bottom: 1px dashed red;', + '}' + ].join('\n'), { title: 'error-message' }); + + elem.style.cssText = [ + "font-family: Arial, sans-serif", + "border: 1px solid #e00", + "background-color: #eee", + "border-radius: 5px", + "-webkit-border-radius: 5px", + "-moz-border-radius: 5px", + "color: #e00", + "padding: 15px", + "margin-bottom: 15px" + ].join(';'); + + if (less.env == 'development') { + timer = setInterval(function () { + if (document.body) { + if (document.getElementById(id)) { + document.body.replaceChild(elem, document.getElementById(id)); + } else { + document.body.insertBefore(elem, document.body.firstChild); + } + clearInterval(timer); + } + }, 10); + } +} + +function error(e, rootHref) { + if (!less.errorReporting || less.errorReporting === "html") { + errorHTML(e, rootHref); + } else if (less.errorReporting === "console") { + errorConsole(e, rootHref); + } else if (typeof less.errorReporting === 'function') { + less.errorReporting("add", e, rootHref); + } +} + +function removeErrorHTML(path) { + var node = document.getElementById('less-error-message:' + extractId(path)); + if (node) { + node.parentNode.removeChild(node); + } +} + +function removeErrorConsole(path) { + //no action +} + +function removeError(path) { + if (!less.errorReporting || less.errorReporting === "html") { + removeErrorHTML(path); + } else if (less.errorReporting === "console") { + removeErrorConsole(path); + } else if (typeof less.errorReporting === 'function') { + less.errorReporting("remove", path); + } +} + +function loadStyles(newVars) { + var styles = document.getElementsByTagName('style'), + style; + for (var i = 0; i < styles.length; i++) { + style = styles[i]; + if (style.type.match(typePattern)) { + var env = new less.tree.parseEnv(less), + lessText = style.innerHTML || ''; + env.filename = document.location.href.replace(/#.*$/, ''); + if (newVars) { + env.useFileCache = true; + lessText += "\n" + newVars; + } + + /*jshint loopfunc:true */ + // use closure to store current value of i + var callback = (function(style) { + return function (e, cssAST) { + if (e) { + return error(e, "inline"); + } + var css = cssAST.toCSS(less); + style.type = 'text/css'; + if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.innerHTML = css; + } + }; + })(style); + new(less.Parser)(env).parse(lessText, callback); + } + } +} + +function extractUrlParts(url, baseUrl) { + // urlParts[1] = protocol&hostname || / + // urlParts[2] = / if path relative to host base + // urlParts[3] = directories + // urlParts[4] = filename + // urlParts[5] = parameters + + var urlPartsRegex = /^((?:[a-z-]+:)?\/+?(?:[^\/\?#]*\/)|([\/\\]))?((?:[^\/\\\?#]*[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/i, + urlParts = url.match(urlPartsRegex), + returner = {}, directories = [], i, baseUrlParts; + + if (!urlParts) { + throw new Error("Could not parse sheet href - '"+url+"'"); + } + + // Stylesheets in IE don't always return the full path + if (!urlParts[1] || urlParts[2]) { + baseUrlParts = baseUrl.match(urlPartsRegex); + if (!baseUrlParts) { + throw new Error("Could not parse page url - '"+baseUrl+"'"); + } + urlParts[1] = urlParts[1] || baseUrlParts[1] || ""; + if (!urlParts[2]) { + urlParts[3] = baseUrlParts[3] + urlParts[3]; + } + } + + if (urlParts[3]) { + directories = urlParts[3].replace(/\\/g, "/").split("/"); + + // extract out . before .. so .. doesn't absorb a non-directory + for(i = 0; i < directories.length; i++) { + if (directories[i] === ".") { + directories.splice(i, 1); + i -= 1; + } + } + + for(i = 0; i < directories.length; i++) { + if (directories[i] === ".." && i > 0) { + directories.splice(i-1, 2); + i -= 2; + } + } + } + + returner.hostPart = urlParts[1]; + returner.directories = directories; + returner.path = urlParts[1] + directories.join("/"); + returner.fileUrl = returner.path + (urlParts[4] || ""); + returner.url = returner.fileUrl + (urlParts[5] || ""); + return returner; +} + +function pathDiff(url, baseUrl) { + // diff between two paths to create a relative path + + var urlParts = extractUrlParts(url), + baseUrlParts = extractUrlParts(baseUrl), + i, max, urlDirectories, baseUrlDirectories, diff = ""; + if (urlParts.hostPart !== baseUrlParts.hostPart) { + return ""; + } + max = Math.max(baseUrlParts.directories.length, urlParts.directories.length); + for(i = 0; i < max; i++) { + if (baseUrlParts.directories[i] !== urlParts.directories[i]) { break; } + } + baseUrlDirectories = baseUrlParts.directories.slice(i); + urlDirectories = urlParts.directories.slice(i); + for(i = 0; i < baseUrlDirectories.length-1; i++) { + diff += "../"; + } + for(i = 0; i < urlDirectories.length-1; i++) { + diff += urlDirectories[i] + "/"; + } + return diff; +} + +function getXMLHttpRequest() { + if (window.XMLHttpRequest) { + return new XMLHttpRequest(); + } else { + try { + /*global ActiveXObject */ + return new ActiveXObject("MSXML2.XMLHTTP.3.0"); + } catch (e) { + log("browser doesn't support AJAX.", logLevel.errors); + return null; + } + } +} + +function doXHR(url, type, callback, errback) { + var xhr = getXMLHttpRequest(); + var async = isFileProtocol ? less.fileAsync : less.async; + + if (typeof(xhr.overrideMimeType) === 'function') { + xhr.overrideMimeType('text/css'); + } + log("XHR: Getting '" + url + "'", logLevel.info); + xhr.open('GET', url, async); + xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5'); + xhr.send(null); + + function handleResponse(xhr, callback, errback) { + if (xhr.status >= 200 && xhr.status < 300) { + callback(xhr.responseText, + xhr.getResponseHeader("Last-Modified")); + } else if (typeof(errback) === 'function') { + errback(xhr.status, url); + } + } + + if (isFileProtocol && !less.fileAsync) { + if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) { + callback(xhr.responseText); + } else { + errback(xhr.status, url); + } + } else if (async) { + xhr.onreadystatechange = function () { + if (xhr.readyState == 4) { + handleResponse(xhr, callback, errback); + } + }; + } else { + handleResponse(xhr, callback, errback); + } +} + +function loadFile(originalHref, currentFileInfo, callback, env, newVars) { + + if (currentFileInfo && currentFileInfo.currentDirectory && !/^([a-z-]+:)?\//.test(originalHref)) { + originalHref = currentFileInfo.currentDirectory + originalHref; + } + + // sheet may be set to the stylesheet for the initial load or a collection of properties including + // some env variables for imports + var hrefParts = extractUrlParts(originalHref, window.location.href); + var href = hrefParts.url; + var newFileInfo = { + currentDirectory: hrefParts.path, + filename: href + }; + + if (currentFileInfo) { + newFileInfo.entryPath = currentFileInfo.entryPath; + newFileInfo.rootpath = currentFileInfo.rootpath; + newFileInfo.rootFilename = currentFileInfo.rootFilename; + newFileInfo.relativeUrls = currentFileInfo.relativeUrls; + } else { + newFileInfo.entryPath = hrefParts.path; + newFileInfo.rootpath = less.rootpath || hrefParts.path; + newFileInfo.rootFilename = href; + newFileInfo.relativeUrls = env.relativeUrls; + } + + if (newFileInfo.relativeUrls) { + if (env.rootpath) { + newFileInfo.rootpath = extractUrlParts(env.rootpath + pathDiff(hrefParts.path, newFileInfo.entryPath)).path; + } else { + newFileInfo.rootpath = hrefParts.path; + } + } + + if (env.useFileCache && fileCache[href]) { + try { + var lessText = fileCache[href]; + if (newVars) { + lessText += "\n" + newVars; + } + callback(null, lessText, href, newFileInfo, { lastModified: new Date() }); + } catch (e) { + callback(e, null, href); + } + return; + } + + 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 }); + } catch (e) { + callback(e, null, href); + } + }, function (status, url) { + callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, href); + }); +} + +function loadStyleSheet(sheet, callback, reload, remaining, newVars) { + + var env = new less.tree.parseEnv(less); + env.mime = sheet.type; + + if (newVars) { + env.useFileCache = true; + } + + loadFile(sheet.href, null, function(e, data, path, newFileInfo, webInfo) { + + if (webInfo) { + webInfo.remaining = remaining; + + var css = cache && cache.getItem(path), + timestamp = cache && cache.getItem(path + ':timestamp'); + + if (!reload && timestamp && webInfo.lastModified && + (new(Date)(webInfo.lastModified).valueOf() === + new(Date)(timestamp).valueOf())) { + // Use local copy + createCSS(css, sheet); + webInfo.local = true; + callback(null, null, data, sheet, webInfo, path); + return; + } + } + + //TODO add tests around how this behaves when reloading + removeError(path); + + if (data) { + env.currentFileInfo = newFileInfo; + new(less.Parser)(env).parse(data, function (e, root) { + if (e) { return callback(e, null, null, sheet); } + try { + callback(e, root, data, sheet, webInfo, path); + } catch (e) { + callback(e, null, null, sheet); + } + }); + } else { + callback(e, null, null, sheet, webInfo, path); + } + }, env, newVars); +} + +function loadStyleSheets(callback, reload, newVars) { + for (var i = 0; i < less.sheets.length; i++) { + loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1), newVars); + } +} + +function initRunningMode(){ + if (less.env === 'development') { + less.optimization = 0; + less.watchTimer = setInterval(function () { + if (less.watchMode) { + loadStyleSheets(function (e, root, _, sheet, env) { + if (e) { + error(e, sheet.href); + } else if (root) { + createCSS(root.toCSS(less), sheet, env.lastModified); + } + }); + } + }, less.poll); + } else { + less.optimization = 3; + } +} + +// +// Watch mode +// +less.watch = function () { + if (!less.watchMode ){ + less.env = 'development'; + initRunningMode(); + } + return this.watchMode = true; +}; + +less.unwatch = function () {clearInterval(less.watchTimer); return this.watchMode = false; }; + +if (/!watch/.test(location.hash)) { + less.watch(); +} + +if (less.env != 'development') { + try { + cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage; + } catch (_) {} +} + +// +// Get all tags with the 'rel' attribute set to "stylesheet/less" +// +var links = document.getElementsByTagName('link'); + +less.sheets = []; + +for (var i = 0; i < links.length; i++) { + if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) && + (links[i].type.match(typePattern)))) { + less.sheets.push(links[i]); + } +} + +// +// With this function, it's possible to alter variables and re-render +// CSS without reloading less-files +// +less.modifyVars = function(record) { + var newVars = ""; + for (var name in record) { + newVars += ((name.slice(0,1) === '@')? '' : '@') + name +': '+ + ((record[name].slice(-1) === ';')? record[name] : record[name] +';'); + } + less.refresh(false, newVars); +}; + +less.refresh = function (reload, newVars) { + var startTime, endTime; + startTime = endTime = new Date(); + + loadStyleSheets(function (e, root, _, sheet, env) { + if (e) { + return error(e, sheet.href); + } + if (env.local) { + log("loading " + sheet.href + " from cache.", logLevel.info); + } else { + log("parsed " + sheet.href + " successfully.", logLevel.info); + createCSS(root.toCSS(less), sheet, env.lastModified); + } + log("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms', logLevel.info); + if (env.remaining === 0) { + log("css generated in " + (new Date() - startTime) + 'ms', logLevel.info); + } + endTime = new Date(); + }, reload, newVars); + + loadStyles(newVars); +}; + +less.refreshStyles = loadStyles; + +less.Parser.fileLoader = loadFile; + +less.refresh(less.env === 'development'); +// amd.js +// +// Define Less as an AMD module. +if (typeof define === "function" && define.amd) { + define(function () { return less; } ); +} +})(window); diff --git a/dist/less-1.5.0.min.js b/dist/less-1.5.0.min.js new file mode 100644 index 00000000..c94eb0fc --- /dev/null +++ b/dist/less-1.5.0.min.js @@ -0,0 +1,11 @@ +/* + * LESS - Leaner CSS v1.5.0 + * http://lesscss.org + * + * Copyright (c) 2009-2013, Alexis Sellier + * Licensed under the Apache 2.0 License. + * + * @licence + */(function(e,t){function n(t){return e.less[t.split("/")[1]]}function h(e,t){r.env=="development"&&typeof console!="undefined"&&r.logLevel>=t&&console.log("less: "+e)}function p(e){return e.replace(/^[a-z-]+:\/+?[^\/]+/,"").replace(/^\//,"").replace(/\.[a-zA-Z]+$/,"").replace(/[^\.\w-]+/g,"-").replace(/\./g,":")}function d(e,n){var r="{line} {content}",i=e.filename||n,s=[],u=(e.type||"Syntax")+"Error: "+(e.message||"There is an error in your .less file")+" in "+i+" ",a=function(e,n,i){e.extract[n]!==t&&s.push(r.replace(/\{line\}/,(parseInt(e.line,10)||0)+(n-1)).replace(/\{class\}/,i).replace(/\{content\}/,e.extract[n]))};e.extract?(a(e,0,""),a(e,1,"line"),a(e,2,""),u+="on line "+e.line+", column "+(e.column+1)+":\n"+s.join("\n")):e.stack&&(u+=e.stack),h(u,o.errors)}function v(e,t,n){var r=t.href||"",i="less:"+(t.title||p(r)),s=document.getElementById(i),u=!1,a=document.createElement("style");a.setAttribute("type","text/css"),t.media&&a.setAttribute("media",t.media),a.id=i;if(a.styleSheet)try{a.styleSheet.cssText=e}catch(f){throw new Error("Couldn't reassign styleSheet.cssText.")}else a.appendChild(document.createTextNode(e)),u=s!==null&&s.childNodes.length>0&&a.childNodes.length>0&&s.firstChild.nodeValue===a.firstChild.nodeValue;var c=document.getElementsByTagName("head")[0];if(s===null||u===!1){var d=t&&t.nextSibling||null;d?d.parentNode.insertBefore(a,d):c.appendChild(a)}s&&u===!1&&s.parentNode.removeChild(s);if(n&&l){h("saving "+r+" to cache.",o.info);try{l.setItem(r,e),l.setItem(r+":timestamp",n)}catch(f){h("failed to save",o.errors)}}}function m(e,n){var i="less-error-message:"+p(n||""),s='
  • {content}
  • ',o=document.createElement("div"),u,a,f=[],l=e.filename||n,c=l.match(/([^\/]+(\?.*)?)$/)[1];o.id=i,o.className="less-error-message",a="

    "+(e.type||"Syntax")+"Error: "+(e.message||"There is an error in your .less file")+"

    "+'

    in '+c+" ";var h=function(e,n,r){e.extract[n]!==t&&f.push(s.replace(/\{line\}/,(parseInt(e.line,10)||0)+(n-1)).replace(/\{class\}/,r).replace(/\{content\}/,e.extract[n]))};e.extract?(h(e,0,""),h(e,1,"line"),h(e,2,""),a+="on line "+e.line+", column "+(e.column+1)+":

    "+"
      "+f.join("")+"
    "):e.stack&&(a+="
    "+e.stack.split("\n").slice(1).join("
    ")),o.innerHTML=a,v([".less-error-message ul, .less-error-message li {","list-style-type: none;","margin-right: 15px;","padding: 4px 0;","margin: 0;","}",".less-error-message label {","font-size: 12px;","margin-right: 15px;","padding: 4px 0;","color: #cc7777;","}",".less-error-message pre {","color: #dd6666;","padding: 4px 0;","margin: 0;","display: inline-block;","}",".less-error-message pre.line {","color: #ff0000;","}",".less-error-message h3 {","font-size: 20px;","font-weight: bold;","padding: 15px 0 5px 0;","margin: 0;","}",".less-error-message a {","color: #10a","}",".less-error-message .error {","color: red;","font-weight: bold;","padding-bottom: 2px;","border-bottom: 1px dashed red;","}"].join("\n"),{title:"error-message"}),o.style.cssText=["font-family: Arial, sans-serif","border: 1px solid #e00","background-color: #eee","border-radius: 5px","-webkit-border-radius: 5px","-moz-border-radius: 5px","color: #e00","padding: 15px","margin-bottom: 15px"].join(";"),r.env=="development"&&(u=setInterval(function(){document.body&&(document.getElementById(i)?document.body.replaceChild(o,document.getElementById(i)):document.body.insertBefore(o,document.body.firstChild),clearInterval(u))},10))}function g(e,t){!r.errorReporting||r.errorReporting==="html"?m(e,t):r.errorReporting==="console"?d(e,t):typeof r.errorReporting=="function"&&r.errorReporting("add",e,t)}function y(e){var t=document.getElementById("less-error-message:"+p(e));t&&t.parentNode.removeChild(t)}function b(e){}function w(e){!r.errorReporting||r.errorReporting==="html"?y(e):r.errorReporting==="console"?b(e):typeof r.errorReporting=="function"&&r.errorReporting("remove",e)}function E(e){var t=document.getElementsByTagName("style"),n;for(var i=0;i0&&(s.splice(o-1,2),o-=2)}return i.hostPart=r[1],i.directories=s,i.path=r[1]+s.join("/"),i.fileUrl=i.path+(r[4]||""),i.url=i.fileUrl+(r[5]||""),i}function x(e,t){var n=S(e),r=S(t),i,s,o,u,a="";if(n.hostPart!==r.hostPart)return"";s=Math.max(r.directories.length,n.directories.length);for(i=0;i=200&&t.status<300?n(t.responseText,t.getResponseHeader("Last-Modified")):typeof r=="function"&&r(t.status,e)}var u=T(),a=s?r.fileAsync:r.async;typeof u.overrideMimeType=="function"&&u.overrideMimeType("text/css"),h("XHR: Getting '"+e+"'",o.info),u.open("GET",e,a),u.setRequestHeader("Accept",t||"text/x-less, text/css; q=0.9, */*; q=0.5"),u.send(null),s&&!r.fileAsync?u.status===0||u.status>=200&&u.status<300?n(u.responseText):i(u.status,e):a?u.onreadystatechange=function(){u.readyState==4&&f(u,n,i)}:f(u,n,i)}function C(t,n,i,s,o){n&&n.currentDirectory&&!/^([a-z-]+:)?\//.test(t)&&(t=n.currentDirectory+t);var u=S(t,e.location.href),a=u.url,f={currentDirectory:u.path,filename:a};n?(f.entryPath=n.entryPath,f.rootpath=n.rootpath,f.rootFilename=n.rootFilename,f.relativeUrls=n.relativeUrls):(f.entryPath=u.path,f.rootpath=r.rootpath||u.path,f.rootFilename=a,f.relativeUrls=s.relativeUrls),f.relativeUrls&&(s.rootpath?f.rootpath=S(s.rootpath+x(u.path,f.entryPath)).path:f.rootpath=u.path);if(s.useFileCache&&c[a]){try{var l=c[a];o&&(l+="\n"+o),i(null,l,a,f,{lastModified:new Date})}catch(h){i(h,null,a)}return}N(a,s.mime,function(e,t){c[a]=e;try{i(null,e,a,f,{lastModified:t})}catch(n){i(n,null,a)}},function(e,t){i({type:"File",message:"'"+t+"' wasn't found ("+e+")"},null,a)})}function k(e,t,n,i,s){var o=new r.tree.parseEnv(r);o.mime=e.type,s&&(o.useFileCache=!0),C(e.href,null,function(s,u,a,f,c){if(c){c.remaining=i;var h=l&&l.getItem(a),p=l&&l.getItem(a+":timestamp");if(!n&&p&&c.lastModified&&(new Date(c.lastModified)).valueOf()===(new Date(p)).valueOf()){v(h,e),c.local=!0,t(null,null,u,e,c,a);return}}w(a),u?(o.currentFileInfo=f,(new r.Parser(o)).parse(u,function(n,r){if(n)return t(n,null,null,e);try{t(n,r,u,e,c,a)}catch(n){t(n,null,null,e)}})):t(s,null,null,e,c,a)},o,s)}function L(e,t,n){for(var i=0;ip&&(h[a]=h[a].slice(u-p),p=u)}function b(e){var t=e.charCodeAt(0);return t===32||t===10||t===9}function w(e){var t,n;if(e instanceof Function)return e.call(d.parsers);if(typeof e=="string")t=o.charAt(u)===e?e:null,n=1,y();else{y();if(!(t=e.exec(h[a])))return null;n=t[0].length}if(t)return E(n),typeof t=="string"?t:t.length===1?t[0]:t}function E(e){var t=u,n=a,r=u+h[a].length,i=u+=e;while(u=0&&t.charAt(n)!=="\n")i++;return typeof e=="number"&&(r=(t.slice(0,e).match(/\n/g)||"").length),{line:r,column:i}}function k(e,t,i){var s=i.currentFileInfo.filename;return r.mode!=="browser"&&r.mode!=="rhino"&&(s=n("path").resolve(s)),{lineNumber:C(e,t).line+1,fileName:s}}function L(e,t){var n=N(e,t),r=C(e.index,n),i=r.line,s=r.column,o=e.call&&C(e.call,n).line,u=n.split("\n");this.type=e.type||"Syntax",this.message=e.message,this.filename=e.filename||t.currentFileInfo.filename,this.index=e.index,this.line=typeof i=="number"?i+1:null,this.callLine=o+1,this.callExtract=u[o],this.stack=e.stack,this.column=s,this.extract=[u[i-1],u[i],u[i+1]]}var o,u,a,f,l,c,h,p,d;s instanceof i.parseEnv||(s=new i.parseEnv(s));var v=this.imports={paths:s.paths||[],queue:[],files:s.files,contents:s.contents,mime:s.mime,error:null,push:function(e,t,n,o){var u=this;this.queue.push(e);var a=function(t,n,r){u.queue.splice(u.queue.indexOf(e),1);var i=r in u.files;u.files[r]=n,t&&!u.error&&(u.error=t),o(t,n,i)};r.Parser.importer?r.Parser.importer(e,t,a,s):r.Parser.fileLoader(e,t,function(e,o,u,f){if(e){a(e);return}var l=new i.parseEnv(s);l.currentFileInfo=f,l.processImports=!1,l.contents[u]=o;if(t.reference||n.reference)f.reference=!0;n.inline?a(null,o,u):(new r.Parser(l)).parse(o,function(e,t){a(e,t,u)})},s)}};return L.prototype=new Error,L.prototype.constructor=L,this.env=s=s||{},this.optimization="optimization"in this.env?this.env.optimization:1,d={imports:v,parse:function(e,t){var f,l,v,m=null;u=a=p=c=0,o=e.replace(/\r\n/g,"\n"),o=o.replace(/^\uFEFF/,""),d.imports.contents[s.currentFileInfo.filename]=o,h=function(e){var t=0,n=/(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g,r=/\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,i=/"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g,u=0,a,f=e[0],l;for(var c=0,h,p;c0?"missing closing `}`":"missing opening `{`",filename:s.currentFileInfo.filename},s)),e.map(function(e){return e.join("")})}([[]]);if(m)return t(new L(m,s));try{f=new i.Ruleset([],w(this.parsers.primary)),f.root=!0,f.firstRoot=!0}catch(g){return t(new L(g,s))}f.toCSS=function(e){return function(t,o){t=t||{};var u,a,f=new i.evalEnv(t);typeof o=="object"&&!Array.isArray(o)&&(o=Object.keys(o).map(function(e){var t=o[e];return t instanceof i.Value||(t instanceof i.Expression||(t=new i.Expression([t])),t=new i.Value([t])),new i.Rule("@"+e,t,!1,null,0)}),f.frames=[new i.Ruleset(null,o)]);try{u=e.call(this,f),(new i.joinSelectorVisitor).run(u),(new i.processExtendsVisitor).run(u),(new i.toCSSVisitor({compress:Boolean(t.compress)})).run(u),t.sourceMap&&(u=new i.sourceMapOutput({writeSourceMap:t.writeSourceMap,rootNode:u,contentsMap:d.imports.contents,sourceMapFilename:t.sourceMapFilename,outputFilename:t.sourceMapOutputFilename,sourceMapBasepath:t.sourceMapBasepath,sourceMapRootpath:t.sourceMapRootpath,outputSourceFiles:t.outputSourceFiles})),a=u.toCSS({compress:Boolean(t.compress),dumpLineNumbers:s.dumpLineNumbers,strictUnits:Boolean(t.strictUnits)})}catch(l){throw new L(l,s)}return t.cleancss&&r.mode==="node"?n("clean-css").process(a):t.compress?a.replace(/(^(\s)+)|((\s)+$)/g,""):a}}(f.eval);if(u57||t<43||t===47||t==44)return;if(e=w(/^([+-]?\d*\.?\d+)(%|[a-z]+)?/))return new i.Dimension(e[1],e[2])},unicodeDescriptor:function(){var e;if(e=w(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/))return new i.UnicodeDescriptor(e[0])},javascript:function(){var e,n=u,r;o.charAt(n)==="~"&&(n++,r=!0);if(o.charAt(n)!=="`")return;s.javascriptEnabled!==t&&!s.javascriptEnabled&&x("You are using JavaScript, which has been disabled."),r&&w("~");if(e=w(/^`([^`]*)`/))return new i.JavaScript(e[1],u,r)}},variable:function(){var e;if(o.charAt(u)==="@"&&(e=w(/^(@[\w-]+)\s*:/)))return e[1]},extend:function(e){var t,n,r=u,s,o=[];if(!w(e?/^&:extend\(/:/^:extend\(/))return;do{s=null,t=[];for(;;){s=w(/^(all)(?=\s*(\)|,))/);if(s)break;n=w(this.element);if(!n)break;t.push(n)}s=s&&s[1],o.push(new i.Extend(new i.Selector(t),s,r))}while(w(","));return S(/^\)/),e&&S(/^;/),o},extendRule:function(){return this.extend(!0)},mixin:{call:function(){var e=[],t,n,r,a=u,f=o.charAt(u),l=!1;if(f!=="."&&f!=="#")return;m();while(t=w(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/))e.push(new i.Element(n,t,u,s.currentFileInfo)),n=w(">");w("(")&&(r=this.mixin.args.call(this,!0).args,S(")")),r=r||[],w(this.important)&&(l=!0);if(e.length>0&&(w(";")||T("}")))return new i.mixin.Call(e,r,a,s.currentFileInfo,l);g()},args:function(e){var t=[],n=[],r,s=[],a,f,l,c,h,p={args:null,variadic:!1};for(;;){if(e)h=w(this.expression);else{w(this.comments);if(o.charAt(u)==="."&&w(/^\.{3}/)){p.variadic=!0,w(";")&&!r&&(r=!0),(r?n:s).push({variadic:!0});break}h=w(this.entities.variable)||w(this.entities.literal)||w(this.entities.keyword)}if(!h)break;l=null,h.throwAwayComments&&h.throwAwayComments(),c=h;var d=null;e?h.value.length==1&&(d=h.value[0]):d=h;if(d&&d instanceof i.Variable)if(w(":"))t.length>0&&(r&&x("Cannot mix ; and , as delimiter types"),a=!0),c=S(this.expression),l=f=d.name;else{if(!e&&w(/^\.{3}/)){p.variadic=!0,w(";")&&!r&&(r=!0),(r?n:s).push({name:h.name,variadic:!0});break}e||(f=l=d.name,c=null)}c&&t.push(c),s.push({name:l,value:c});if(w(","))continue;if(w(";")||r)a&&x("Cannot mix ; and , as delimiter types"),r=!0,t.length>1&&(c=new i.Value(t)),n.push({name:f,value:c}),f=null,t=[],a=!1}return p.args=r?n:s,p},definition:function(){var e,t=[],n,r,s,a=!1;if(o.charAt(u)!=="."&&o.charAt(u)!=="#"||T(/^[^{]*\}/))return;m();if(n=w(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)){e=n[1];var f=this.mixin.args.call(this,!1);t=f.args,a=f.variadic,w(")")||(c=u,g()),w(this.comments),w(/^when/)&&(s=S(this.conditions,"expected condition")),r=w(this.block);if(r)return new i.mixin.Definition(e,t,r,s,a);g()}}},entity:function(){return w(this.entities.literal)||w(this.entities.variable)||w(this.entities.url)||w(this.entities.call)||w(this.entities.keyword)||w(this.entities.javascript)||w(this.comment)},end:function(){return w(";")||T("}")},alpha:function(){var e;if(!w(/^\(opacity=/i))return;if(e=w(/^\d+/)||w(this.entities.variable))return S(")"),new i.Alpha(e)},element:function(){var e,t,n;t=w(this.combinator),e=w(/^(?:\d+\.\d+|\d+)%/)||w(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)||w("*")||w("&")||w(this.attribute)||w(/^\([^()@]+\)/)||w(/^[\.#](?=@)/)||w(this.entities.variableCurly),e||w("(")&&(n=w(this.selector))&&w(")")&&(e=new i.Paren(n));if(e)return new i.Element(t,e,u,s.currentFileInfo)},combinator:function(){var e=o.charAt(u);if(e===">"||e==="+"||e==="~"||e==="|"){u++;while(o.charAt(u).match(/\s/))u++;return new i.Combinator(e)}return o.charAt(u-1).match(/\s/)?new i.Combinator(" "):new i.Combinator(null)},lessSelector:function(){return this.selector(!0)},selector:function(e){var t,n=[],r,a,f=[],l,c;while(e&&(a=w(this.extend))||e&&(l=w(/^when/))||(t=w(this.element))){l?c=S(this.conditions,"expected condition"):c?x("CSS guard can only be used at the end of selector"):a?f.push.apply(f,a):(f.length&&x("Extend can only be used at the end of selector"),r=o.charAt(u),n.push(t),t=null);if(r==="{"||r==="}"||r===";"||r===","||r===")")break}if(n.length>0)return new i.Selector(n,f,c,u,s.currentFileInfo);f.length&&x("Extend must be used to extend a selector, it cannot be used on its own")},attribute:function(){var e,t,n;if(!w("["))return;(e=w(this.entities.variableCurly))||(e=S(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/));if(n=w(/^[|~*$^]?=/))t=w(this.entities.quoted)||w(/^[\w-]+/)||w(this.entities.variableCurly);return S("]"),new i.Attribute(e,n,t)},block:function(){var e;if(w("{")&&(e=w(this.primary))&&w("}"))return e},ruleset:function(){var e=[],t,n,r;m(),s.dumpLineNumbers&&(r=k(u,o,s));while(t=w(this.lessSelector)){e.push(t),w(this.comments);if(!w(","))break;t.condition&&x("Guards are only currently allowed on a single selector"),w(this.comments)}if(e.length>0&&(n=w(this.block))){var a=new i.Ruleset(e,n,s.strictImports);return s.dumpLineNumbers&&(a.debugInfo=r),a}c=u,g()},rule:function(e){var t,n,r=o.charAt(u),a,f=!1;m();if(r==="."||r==="#"||r==="&")return;if(t=w(this.variable)||w(this.ruleProperty)){n=!e&&(s.compress||t.charAt(0)==="@")?w(this.value)||w(this.anonymousValue):w(this.anonymousValue)||w(this.value),a=w(this.important),t[t.length-1]==="+"&&(f=!0,t=t.substr(0,t.length-1));if(n&&w(this.end))return new i.Rule(t,n,a,f,l,s.currentFileInfo);c=u,g();if(n&&!e)return this.rule(!0)}},anonymousValue:function(){var e;if(e=/^([^@+\/'"*`(;{}-]*);/.exec(h[a]))return u+=e[0].length-1,new i.Anonymous(e[1])},"import":function(){var e,t,n=u;m();var r=w(/^@import?\s+/),o=(r?w(this.importOptions):null)||{};if(r&&(e=w(this.entities.quoted)||w(this.entities.url))){t=w(this.mediaFeatures);if(w(";"))return t=t&&new i.Value(t),new i.Import(e,t,o,n,s.currentFileInfo)}g()},importOptions:function(){var e,t={},n,r;if(!w("("))return null;do if(e=w(this.importOption)){n=e,r=!0;switch(n){case"css":n="less",r=!1;break;case"once":n="multiple",r=!1}t[n]=r;if(!w(","))break}while(e);return S(")"),t},importOption:function(){var e=w(/^(less|css|multiple|once|inline|reference)/);if(e)return e[1]},mediaFeature:function(){var e,t,n=[];do if(e=w(this.entities.keyword))n.push(e);else if(w("(")){t=w(this.property),e=w(this.value);if(!w(")"))return null;if(t&&e)n.push(new i.Paren(new i.Rule(t,e,null,null,u,s.currentFileInfo,!0)));else{if(!e)return null;n.push(new i.Paren(e))}}while(e);if(n.length>0)return new i.Expression(n)},mediaFeatures:function(){var e,t=[];do if(e=w(this.mediaFeature)){t.push(e);if(!w(","))break}else if(e=w(this.entities.variable)){t.push(e);if(!w(","))break}while(e);return t.length>0?t:null},media:function(){var e,t,n,r;s.dumpLineNumbers&&(r=k(u,o,s));if(w(/^@media/)){e=w(this.mediaFeatures);if(t=w(this.block))return n=new i.Media(t,e,u,s.currentFileInfo),s.dumpLineNumbers&&(n.debugInfo=r),n}},directive:function(){var e,t,n,r,a,f,l;if(o.charAt(u)!=="@")return;if(t=w(this["import"])||w(this.media))return t;m(),e=w(/^@[a-z-]+/);if(!e)return;r=e,e.charAt(1)=="-"&&e.indexOf("-",2)>0&&(r="@"+e.slice(e.indexOf("-",2)+1));switch(r){case"@font-face":a=!0;break;case"@viewport":case"@top-left":case"@top-left-corner":case"@top-center":case"@top-right":case"@top-right-corner":case"@bottom-left":case"@bottom-left-corner":case"@bottom-center":case"@bottom-right":case"@bottom-right-corner":case"@left-top":case"@left-middle":case"@left-bottom":case"@right-top":case"@right-middle":case"@right-bottom":a=!0;break;case"@page":case"@document":case"@supports":case"@keyframes":a=!0,f=!0;break;case"@namespace":l=!0}f&&(e+=" "+(w(/^[^{]+/)||"").trim());if(a){if(n=w(this.block))return new i.Directive(e,n,u,s.currentFileInfo)}else if((t=l?w(this.expression):w(this.entity))&&w(";")){var c=new i.Directive(e,t,u,s.currentFileInfo);return s.dumpLineNumbers&&(c.debugInfo=k(u,o,s)),c}g()},value:function(){var e,t=[];while(e=w(this.expression)){t.push(e);if(!w(","))break}if(t.length>0)return new i.Value(t)},important:function(){if(o.charAt(u)==="!")return w(/^! *important/)},sub:function(){var e,t;if(w("("))if(e=w(this.addition))return t=new i.Expression([e]),S(")"),t.parens=!0,t},multiplication:function(){var e,t,n,r,s;if(e=w(this.operand)){s=b(o.charAt(u-1));while(!T(/^\/[*\/]/)&&(n=w("/")||w("*"))){if(!(t=w(this.operand)))break;e.parensInOp=!0,t.parensInOp=!0,r=new i.Operation(n,[r||e,t],s),s=b(o.charAt(u-1))}return r||e}},addition:function(){var e,t,n,r,s;if(e=w(this.multiplication)){s=b(o.charAt(u-1));while((n=w(/^[-+]\s+/)||!s&&(w("+")||w("-")))&&(t=w(this.multiplication)))e.parensInOp=!0,t.parensInOp=!0,r=new i.Operation(n,[r||e,t],s),s=b(o.charAt(u-1));return r||e}},conditions:function(){var e,t,n=u,r;if(e=w(this.condition)){while(w(",")&&(t=w(this.condition)))r=new i.Condition("or",r||e,t,n);return r||e}},condition:function(){var e,t,n,r,s=u,o=!1;w(/^not/)&&(o=!0),S("(");if(e=w(this.addition)||w(this.entities.keyword)||w(this.entities.quoted))return(r=w(/^(?:>=|=<|[<=>])/))?(t=w(this.addition)||w(this.entities.keyword)||w(this.entities.quoted))?n=new i.Condition(r,e,t,s,o):x("expected expression"):n=new i.Condition("=",e,new i.Keyword("true"),s,o),S(")"),w(/^and/)?new i.Condition("and",n,w(this.condition)):n},operand:function(){var e,t=o.charAt(u+1);o.charAt(u)==="-"&&(t==="@"||t==="(")&&(e=w("-"));var n=w(this.sub)||w(this.entities.dimension)||w(this.entities.color)||w(this.entities.variable)||w(this.entities.call);return e&&(n.parensInOp=!0,n=new i.Negative(n)),n},expression:function(){var e,t,n=[];while(e=w(this.addition)||w(this.entity))n.push(e),!T(/^\/[\/*]/)&&(t=w("/"))&&n.push(new i.Anonymous(t));if(n.length>0)return new i.Expression(n)},property:function(){var e;if(e=w(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/))return e[1]},ruleProperty:function(){var e;if(e=w(/^(\*?-?[_a-zA-Z0-9-]+)\s*(\+?)\s*:/))return e[1]+(e[2]||"")}}}},function(r){function u(e){return r.functions.hsla(e.h,e.s,e.l,e.a)}function a(e,t){return e instanceof r.Dimension&&e.unit.is("%")?parseFloat(e.value*t/100):f(e)}function f(e){if(e instanceof r.Dimension)return parseFloat(e.unit.is("%")?e.value/100:e.value);if(typeof e=="number")return e;throw{error:"RuntimeError",message:"color functions take numbers as parameters"}}function l(e){return Math.min(1,Math.max(0,e))}r.functions={rgb:function(e,t,n){return this.rgba(e,t,n,1)},rgba:function(e,t,n,i){var s=[e,t,n].map(function(e){return a(e,256)});return i=f(i),new r.Color(s,i)},hsl:function(e,t,n){return this.hsla(e,t,n,1)},hsla:function(e,t,n,r){function i(e){return e=e<0?e+1:e>1?e-1:e,e*6<1?o+(s-o)*e*6:e*2<1?s:e*3<2?o+(s-o)*(2/3-e)*6:o}e=f(e)%360/360,t=l(f(t)),n=l(f(n)),r=l(f(r));var s=n<=.5?n*(t+1):n+t-n*t,o=n*2-s;return this.rgba(i(e+1/3)*255,i(e)*255,i(e-1/3)*255,r)},hsv:function(e,t,n){return this.hsva(e,t,n,1)},hsva:function(e,t,n,r){e=f(e)%360/360*360,t=f(t),n=f(n),r=f(r);var i,s;i=Math.floor(e/60%6),s=e/60-i;var o=[n,n*(1-t),n*(1-s*t),n*(1-(1-s)*t)],u=[[0,3,1],[2,0,1],[1,0,3],[1,2,0],[3,1,0],[0,1,2]];return this.rgba(o[u[i][0]]*255,o[u[i][1]]*255,o[u[i][2]]*255,r)},hue:function(e){return new r.Dimension(Math.round(e.toHSL().h))},saturation:function(e){return new r.Dimension(Math.round(e.toHSL().s*100),"%")},lightness:function(e){return new r.Dimension(Math.round(e.toHSL().l*100),"%")},hsvhue:function(e){return new r.Dimension(Math.round(e.toHSV().h))},hsvsaturation:function(e){return new r.Dimension(Math.round(e.toHSV().s*100),"%")},hsvvalue:function(e){return new r.Dimension(Math.round(e.toHSV().v*100),"%")},red:function(e){return new r.Dimension(e.rgb[0])},green:function(e){return new r.Dimension(e.rgb[1])},blue:function(e){return new r.Dimension(e.rgb[2])},alpha:function(e){return new r.Dimension(e.toHSL().a)},luma:function(e){return new r.Dimension(Math.round(e.luma()*e.alpha*100),"%")},saturate:function(e,t){if(!e.rgb)return null;var n=e.toHSL();return n.s+=t.value/100,n.s=l(n.s),u(n)},desaturate:function(e,t){var n=e.toHSL();return n.s-=t.value/100,n.s=l(n.s),u(n)},lighten:function(e,t){var n=e.toHSL();return n.l+=t.value/100,n.l=l(n.l),u(n)},darken:function(e,t){var n=e.toHSL();return n.l-=t.value/100,n.l=l(n.l),u(n)},fadein:function(e,t){var n=e.toHSL();return n.a+=t.value/100,n.a=l(n.a),u(n)},fadeout:function(e,t){var n=e.toHSL();return n.a-=t.value/100,n.a=l(n.a),u(n)},fade:function(e,t){var n=e.toHSL();return n.a=t.value/100,n.a=l(n.a),u(n)},spin:function(e,t){var n=e.toHSL(),r=(n.h+t.value)%360;return n.h=r<0?360+r:r,u(n)},mix:function(e,t,n){n||(n=new r.Dimension(50));var i=n.value/100,s=i*2-1,o=e.toHSL().a-t.toHSL().a,u=((s*o==-1?s:(s+o)/(1+s*o))+1)/2,a=1-u,f=[e.rgb[0]*u+t.rgb[0]*a,e.rgb[1]*u+t.rgb[1]*a,e.rgb[2]*u+t.rgb[2]*a],l=e.alpha*i+t.alpha*(1-i);return new r.Color(f,l)},greyscale:function(e){return this.desaturate(e,new r.Dimension(100))},contrast:function(e,t,n,r){if(!e.rgb)return null;typeof n=="undefined"&&(n=this.rgba(255,255,255,1)),typeof t=="undefined"&&(t=this.rgba(0,0,0,1));if(t.luma()>n.luma()){var i=n;n=t,t=i}return typeof r=="undefined"?r=.43:r=f(r),e.luma()*e.alphaa.value)l[s]=o}return l.length==1?l[0]:(n=l.map(function(e){return e.toCSS(this.env)}).join(this.env.compress?",":", "),new r.Anonymous((e?"min":"max")+"("+n+")"))},min:function(){return this._minmax(!0,arguments)},max:function(){return this._minmax(!1,arguments)},argb:function(e){return new r.Anonymous(e.toARGB())},percentage:function(e){return new r.Dimension(e.value*100,"%")},color:function(e){if(e instanceof r.Quoted)return new r.Color(e.value.slice(1));throw{type:"Argument",message:"argument must be a string"}},iscolor:function(e){return this._isa(e,r.Color)},isnumber:function(e){return this._isa(e,r.Dimension)},isstring:function(e){return this._isa(e,r.Quoted)},iskeyword:function(e){return this._isa(e,r.Keyword)},isurl:function(e){return this._isa(e,r.URL)},ispixel:function(e){return this.isunit(e,"px")},ispercentage:function(e){return this.isunit(e,"%")},isem:function(e){return this.isunit(e,"em")},isunit:function(e,t){return e instanceof r.Dimension&&e.unit.is(t.value||t)?r.True:r.False},_isa:function(e,t){return e instanceof t?r.True:r.False},multiply:function(e,t){var n=e.rgb[0]*t.rgb[0]/255,r=e.rgb[1]*t.rgb[1]/255,i=e.rgb[2]*t.rgb[2]/255;return this.rgb(n,r,i)},screen:function(e,t){var n=255-(255-e.rgb[0])*(255-t.rgb[0])/255,r=255-(255-e.rgb[1])*(255-t.rgb[1])/255,i=255-(255-e.rgb[2])*(255-t.rgb[2])/255;return this.rgb(n,r,i)},overlay:function(e,t){var n=e.rgb[0]<128?2*e.rgb[0]*t.rgb[0]/255:255-2*(255-e.rgb[0])*(255-t.rgb[0])/255,r=e.rgb[1]<128?2*e.rgb[1]*t.rgb[1]/255:255-2*(255-e.rgb[1])*(255-t.rgb[1])/255,i=e.rgb[2]<128?2*e.rgb[2]*t.rgb[2]/255:255-2*(255-e.rgb[2])*(255-t.rgb[2])/255;return this.rgb(n,r,i)},softlight:function(e,t){var n=t.rgb[0]*e.rgb[0]/255,r=n+e.rgb[0]*(255-(255-e.rgb[0])*(255-t.rgb[0])/255-n)/255;n=t.rgb[1]*e.rgb[1]/255;var i=n+e.rgb[1]*(255-(255-e.rgb[1])*(255-t.rgb[1])/255-n)/255;n=t.rgb[2]*e.rgb[2]/255;var s=n+e.rgb[2]*(255-(255-e.rgb[2])*(255-t.rgb[2])/255-n)/255;return this.rgb(r,i,s)},hardlight:function(e,t){var n=t.rgb[0]<128?2*t.rgb[0]*e.rgb[0]/255:255-2*(255-t.rgb[0])*(255-e.rgb[0])/255,r=t.rgb[1]<128?2*t.rgb[1]*e.rgb[1]/255:255-2*(255-t.rgb[1])*(255-e.rgb[1])/255,i=t.rgb[2]<128?2*t.rgb[2]*e.rgb[2]/255:255-2*(255-t.rgb[2])*(255-e.rgb[2])/255;return this.rgb(n,r,i)},difference:function(e,t){var n=Math.abs(e.rgb[0]-t.rgb[0]),r=Math.abs(e.rgb[1]-t.rgb[1]),i=Math.abs(e.rgb[2]-t.rgb[2]);return this.rgb(n,r,i)},exclusion:function(e,t){var n=e.rgb[0]+t.rgb[0]*(255-e.rgb[0]-e.rgb[0])/255,r=e.rgb[1]+t.rgb[1]*(255-e.rgb[1]-e.rgb[1])/255,i=e.rgb[2]+t.rgb[2]*(255-e.rgb[2]-e.rgb[2])/255;return this.rgb(n,r,i)},average:function(e,t){var n=(e.rgb[0]+t.rgb[0])/2,r=(e.rgb[1]+t.rgb[1])/2,i=(e.rgb[2]+t.rgb[2])/2;return this.rgb(n,r,i)},negation:function(e,t){var n=255-Math.abs(255-t.rgb[0]-e.rgb +[0]),r=255-Math.abs(255-t.rgb[1]-e.rgb[1]),i=255-Math.abs(255-t.rgb[2]-e.rgb[2]);return this.rgb(n,r,i)},tint:function(e,t){return this.mix(this.rgb(255,255,255),e,t)},shade:function(e,t){return this.mix(this.rgb(0,0,0),e,t)},extract:function(e,t){return t=t.value-1,e.value[t]},"data-uri":function(t,i){if(typeof e!="undefined")return(new r.URL(i||t,this.currentFileInfo)).eval(this.env);var s=t.value,o=i&&i.value,u=n("fs"),a=n("path"),f=!1;arguments.length<2&&(o=s),this.env.isPathRelative(o)&&(this.currentFileInfo.relativeUrls?o=a.join(this.currentFileInfo.currentDirectory,o):o=a.join(this.currentFileInfo.entryPath,o));if(arguments.length<2){var l;try{l=n("mime")}catch(c){l=r._mime}s=l.lookup(o);var h=l.charsets.lookup(s);f=["US-ASCII","UTF-8"].indexOf(h)<0,f&&(s+=";base64")}else f=/;base64$/.test(s);var p=u.readFileSync(o),d=32,v=parseInt(p.length/1024,10);if(v>=d){if(this.env.ieCompat!==!1)return this.env.silent||console.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!",o,v,d),(new r.URL(i||t,this.currentFileInfo)).eval(this.env);this.env.silent||console.warn("WARNING: Embedding %s (%dKB) exceeds IE8's data-uri size limit of %dKB!",o,v,d)}p=f?p.toString("base64"):encodeURIComponent(p);var m="'data:"+s+","+p+"'";return new r.URL(new r.Anonymous(m))},"svg-gradient":function(e){function n(){throw{type:"Argument",message:"svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]"}}arguments.length<3&&n();var i=Array.prototype.slice.call(arguments,1),s,o="linear",u='x="0" y="0" width="1" height="1"',a=!0,f={compress:!1},l,c=e.toCSS(f),h,p,d,v,m;switch(c){case"to bottom":s='x1="0%" y1="0%" x2="0%" y2="100%"';break;case"to right":s='x1="0%" y1="0%" x2="100%" y2="0%"';break;case"to bottom right":s='x1="0%" y1="0%" x2="100%" y2="100%"';break;case"to top right":s='x1="0%" y1="100%" x2="100%" y2="0%"';break;case"ellipse":case"ellipse at center":o="radial",s='cx="50%" cy="50%" r="75%"',u='x="-50" y="-50" width="101" height="101"';break;default:throw{type:"Argument",message:"svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'"}}l='<'+o+'Gradient id="gradient" gradientUnits="userSpaceOnUse" '+s+">";for(h=0;h";l+=""+"';if(a)try{l=(new Buffer(l)).toString("base64")}catch(g){a=!1}return l="'data:image/svg+xml"+(a?";base64":"")+","+l+"'",new r.URL(new r.Anonymous(l))}},r._mime={_types:{".htm":"text/html",".html":"text/html",".gif":"image/gif",".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png"},lookup:function(e){var i=n("path").extname(e),s=r._mime._types[i];if(s===t)throw new Error('Optional dependency "mime" is required for '+i);return s},charsets:{lookup:function(e){return e&&/^text\//.test(e)?"UTF-8":""}}};var i=[{name:"ceil"},{name:"floor"},{name:"sqrt"},{name:"abs"},{name:"tan",unit:""},{name:"sin",unit:""},{name:"cos",unit:""},{name:"atan",unit:"rad"},{name:"asin",unit:"rad"},{name:"acos",unit:"rad"}],s=function(e,t){return function(n){return t!=null&&(n=n.unify()),this._math(Math[e],t,n)}};for(var o=0;o1?"["+e.value.map(function(e){return e.toCSS(!1)}).join(", ")+"]":e.toCSS(!1)},e.toCSS=function(e){var t=[];return this.genCSS(e,{add:function(e,n){t.push(e)}}),t.join("")},e.outputRuleset=function(e,t,n){t.add(e.compress?"{":" {\n"),e.tabLevel=(e.tabLevel||0)+1;var r=e.compress?"":Array(e.tabLevel+1).join(" "),i=e.compress?"":Array(e.tabLevel).join(" ");for(var s=0;s255?255:e<0?0:e).toString(16),e.length===1?"0"+e:e}).join("")},toHSL:function(){var e=this.rgb[0]/255,t=this.rgb[1]/255,n=this.rgb[2]/255,r=this.alpha,i=Math.max(e,t,n),s=Math.min(e,t,n),o,u,a=(i+s)/2,f=i-s;if(i===s)o=u=0;else{u=a>.5?f/(2-i-s):f/(i+s);switch(i){case e:o=(t-n)/f+(t255?255:e<0?0:e).toString(16),e.length===1?"0"+e:e}).join("")},compare:function(e){return e.rgb?e.rgb[0]===this.rgb[0]&&e.rgb[1]===this.rgb[1]&&e.rgb[2]===this.rgb[2]&&e.alpha===this.alpha?0:-1:-1}}}(n("../tree")),function(e){e.Comment=function(e,t,n,r){this.value=e,this.silent=!!t,this.currentFileInfo=r},e.Comment.prototype={type:"Comment",genCSS:function(t,n){this.debugInfo&&n.add(e.debugInfo(t,this),this.currentFileInfo,this.index),n.add(this.value.trim())},toCSS:e.toCSS,isSilent:function(e){var t=this.currentFileInfo&&this.currentFileInfo.reference&&!this.isReferenced,n=e.compress&&!this.value.match(/^\/\*!/);return this.silent||t||n},eval:function(){return this},markReferenced:function(){this.isReferenced=!0}}}(n("../tree")),function(e){e.Condition=function(e,t,n,r,i){this.op=e.trim(),this.lvalue=t,this.rvalue=n,this.index=r,this.negate=i},e.Condition.prototype={type:"Condition",accept:function(e){this.lvalue=e.visit(this.lvalue),this.rvalue=e.visit(this.rvalue)},eval:function(e){var t=this.lvalue.eval(e),n=this.rvalue.eval(e),r=this.index,i;return i=function(e){switch(e){case"and":return t&&n;case"or":return t||n;default:if(t.compare)i=t.compare(n);else{if(!n.compare)throw{type:"Type",message:"Unable to perform comparison",index:r};i=n.compare(t)}switch(i){case-1:return e==="<"||e==="=<";case 0:return e==="="||e===">="||e==="=<";case 1:return e===">"||e===">="}}}(this.op),this.negate?!i:i}}}(n("../tree")),function(e){e.Dimension=function(n,r){this.value=parseFloat(n),this.unit=r&&r instanceof e.Unit?r:new e.Unit(r?[r]:t)},e.Dimension.prototype={type:"Dimension",accept:function(e){this.unit=e.visit(this.unit)},eval:function(e){return this},toColor:function(){return new e.Color([this.value,this.value,this.value])},genCSS:function(e,t){if(e&&e.strictUnits&&!this.unit.isSingular())throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: "+this.unit.toString());var n=this.value,r=String(n);n!==0&&n<1e-6&&n>-0.000001&&(r=n.toFixed(20).replace(/0+$/,""));if(e&&e.compress){if(n===0&&this.unit.isLength()){t.add(r);return}n>0&&n<1&&(r=r.substr(1))}t.add(r),this.unit.genCSS(e,t)},toCSS:e.toCSS,operate:function(t,n,r){var i=e.operate(t,n,this.value,r.value),s=this.unit.clone();if(n==="+"||n==="-"){if(s.numerator.length===0&&s.denominator.length===0)s.numerator=r.unit.numerator.slice(0),s.denominator=r.unit.denominator.slice(0);else if(r.unit.numerator.length!==0||s.denominator.length!==0){r=r.convertTo(this.unit.usedUnits());if(t.strictUnits&&r.unit.toString()!==s.toString())throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '"+s.toString()+"' and '"+r.unit.toString()+"'.");i=e.operate(t,n,this.value,r.value)}}else n==="*"?(s.numerator=s.numerator.concat(r.unit.numerator).sort(),s.denominator=s.denominator.concat(r.unit.denominator).sort(),s.cancel()):n==="/"&&(s.numerator=s.numerator.concat(r.unit.denominator).sort(),s.denominator=s.denominator.concat(r.unit.numerator).sort(),s.cancel());return new e.Dimension(i,s)},compare:function(t){if(t instanceof e.Dimension){var n=this.unify(),r=t.unify(),i=n.value,s=r.value;return s>i?-1:s=1?t.add(this.numerator[0]):this.denominator.length>=1?t.add(this.denominator[0]):(!e||!e.strictUnits)&&this.backupUnit&&t.add(this.backupUnit)},toCSS:e.toCSS,toString:function(){var e,t=this.numerator.join("*");for(e=0;e0)for(n=0;n":" > ","|":"|"},_outputMapCompressed:{"":""," ":" ",":":" :","+":"+","~":"~",">":">","|":"|"},genCSS:function(e,t){t.add((e.compress?this._outputMapCompressed:this._outputMap)[this.value])},toCSS:e.toCSS}}(n("../tree")),function(e){e.Expression=function(e){this.value=e},e.Expression.prototype={type:"Expression",accept:function(e){this.value=e.visit(this.value)},eval:function(t){var n,r=this.parens&&!this.parensInOp,i=!1;return r&&t.inParenthesis(),this.value.length>1?n=new e.Expression(this.value.map(function(e){return e.eval(t)})):this.value.length===1?(this.value[0].parens&&!this.value[0].parensInOp&&(i=!0),n=this.value[0].eval(t)):n=this,r&&t.outOfParenthesis(),this.parens&&this.parensInOp&&!t.isMathOn()&&!i&&(n=new e.Paren(n)),n},genCSS:function(e,t){for(var n=0;n1){var r=this.emptySelectors();n=new e.Ruleset(r,t.mediaBlocks),n.multiMedia=!0}return delete t.mediaBlocks,delete t.mediaPath,n},evalNested:function(t){var n,r,i=t.mediaPath.concat([this]);for(n=0;n0;n--)t.splice(n,0,new e.Anonymous("and"));return new e.Expression(t)})),new e.Ruleset([],[])},permute:function(e){if(e.length===0)return[];if(e.length===1)return e[0];var t=[],n=this.permute(e.slice(1));for(var r=0;r0){c=!0;for(a=0;athis.params.length)return!1;if(this.required>0&&n>this.params.length)return!1}r=Math.min(n,this.arity);for(var i=0;is.selectors[o].elements.length?Array.prototype.push.apply(r,s.find(new e.Selector(t.elements.slice(1)),n)):r.push(s);break}}),this._lookups[s]=r)},genCSS:function(t,n){var r,i,s=[],o=[],u,a,f=!0,l;t.tabLevel=t.tabLevel||0,this.root||t.tabLevel++;var c=t.compress?"":Array(t.tabLevel+1).join(" "),h=t.compress?"":Array(t.tabLevel).join(" ");for(r=0;r0)for(i=0;i0&&this.mergeElementsOnToSelectors(g,a);for(s=0;s0&&(l[0].elements=l[0].elements.slice(0),l[0].elements.push(new e.Element(f.combinator,"",0,f.index,f.currentFileInfo))),y.push(l);else for(o=0;o0?(h=l.slice(0),m=h.pop(),d=r.createDerived(m.elements.slice(0)),v=!1):d=r.createDerived([]),c.length>1&&(p=p.concat(c.slice(1))),c.length>0&&(v=!1,d.elements.push(new e.Element(f.combinator,c[0].elements[0].value,f.index,f.currentFileInfo)),d.elements=d.elements.concat(c[0].elements.slice(1))),v||h.push(d),h=h.concat(p),y.push(h)}a=y,g=[]}}g.length>0&&this.mergeElementsOnToSelectors(g,a);for(i=0;i0&&t.push(a[i])},mergeElementsOnToSelectors:function(t,n){var r,i;if(n.length===0){n.push([new e.Selector(t)]);return}for(r=0;r0?i[i.length-1]=i[i.length-1].createDerived(i[i.length-1].elements.concat(t)):i.push(new e.Selector(t))}}}(n("../tree")),function(e){e.Selector=function(e,t,n,r,i,s){this.elements=e,this.extendList=t||[],this.condition=n,this.currentFileInfo=i||{},this.isReferenced=s,n||(this.evaldCondition=!0)},e.Selector.prototype={type:"Selector",accept:function(e){this.elements=e.visit(this.elements),this.extendList=e.visit(this.extendList),this.condition=e.visit(this.condition)},createDerived:function(t,n,r){r=r!=null?r:this.evaldCondition;var i=new e.Selector(t,n||this.extendList,this.condition,this.index,this.currentFileInfo,this.isReferenced);return i.evaldCondition=r,i},match:function(e){var t=this.elements,n=t.length,r,i,s,o;r=e.elements.slice(e.elements.length&&e.elements[0].value==="&"?1:0),i=r.length,s=Math.min(n,i);if(i===0||n0&&t.accept(this._visitor),n.visitDeeper=!1,this._mergeRules(t.rules),this._removeDuplicateRules(t.rules),t.rules.length>0&&t.paths.length>0&&i.splice(0,0,t)}else t.accept(this._visitor),n.visitDeeper=!1,(t.firstRoot||t.rules.length>0)&&i.splice(0,0,t);return i.length===1?i[0]:i},_removeDuplicateRules:function(t){var n={},r,i,s;for(s=t.length-1;s>=0;s--){i=t[s];if(i instanceof e.Rule)if(!n[i.name])n[i.name]=i;else{r=n[i.name],r instanceof e.Rule&&(r=n[i.name]=[n[i.name].toCSS(this._env)]);var o=i.toCSS(this._env);r.indexOf(o)!==-1?t.splice(s,1):r.push(o)}}},_mergeRules:function(t){var n={},r,i,s;for(var o=0;o1&&(i=r[0],i.value=new e.Value(r.map(function(e){return e.value})))})}}}(n("./tree")),function(e){e.extendFinderVisitor=function(){this._visitor=new e.visitor(this),this.contexts=[],this.allExtendsStack=[[]]},e.extendFinderVisitor.prototype={run:function(e){return e=this._visitor.visit(e),e.allExtends=this.allExtendsStack[0],e},visitRule:function(e,t){t.visitDeeper=!1},visitMixinDefinition:function(e,t){t.visitDeeper=!1},visitRuleset:function(t,n){if(t.root)return;var r,i,s,o=[],u;for(r=0;r100){var d="{unable to calculate}",v="{unable to calculate}";try{d=u[0].selfSelectors[0].toCSS(),v=u[0].selector.toCSS()}catch(m){}throw{message:"extend circular reference detected. One of the circular extends is currently:"+d+":extend("+v+")"}}return u.concat(f.doExtendChaining(u,n,r+1))}return u},inInheritanceChain:function(e,t){if(e===t)return!0;if(t.parents){if(this.inInheritanceChain(e,t.parents[0]))return!0;if(this.inInheritanceChain(e,t.parents[1]))return!0}return!1},visitRule:function(e,t){t.visitDeeper=!1},visitMixinDefinition:function(e,t){t.visitDeeper=!1},visitSelector:function(e,t){t.visitDeeper=!1},visitRuleset:function(e,t){if(e.root)return;var n,r,i,s=this.allExtendsStack[this.allExtendsStack.length-1],o=[],u=this,a;for(i=0;i0&&f[c.matched].combinator.value!==o?c=null:c.matched++,c&&(c.finished=c.matched===f.length,c.finished&&!e.allowAfter&&(i+1i&&s>0&&(o[o.length-1].elements=o[o.length-1].elements.concat(n[i].elements.slice(s)),s=0,i++),c=a.elements.slice(s,l.index).concat([f]).concat(r.elements.slice(1)),i===l.pathIndex&&u>0?o[o.length-1].elements=o[o.length-1].elements.concat(c):(o=o.concat(n.slice(i,l.pathIndex)),o.push(new e.Selector(c))),i=l.endPathIndex,s=l.endPathElementIndex,s>=n[i].elements.length&&(s=0,i++);return i0&&(o[o.length-1].elements=o[o.length-1].elements.concat(n[i].elements.slice(s)),s=0,i++),o=o.concat(n.slice(i,n.length)),o},visitRulesetOut:function(e){},visitMedia:function(e,t){var n=e.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);n=n.concat(this.doExtendChaining(n,e.allExtends)),this.allExtendsStack.push(n)},visitMediaOut:function(e){this.allExtendsStack.length=this.allExtendsStack.length-1},visitDirective:function(e,t){var n=e.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);n=n.concat(this.doExtendChaining(n,e.allExtends)),this.allExtendsStack.push(n)},visitDirectiveOut:function(e){this.allExtendsStack.length=this.allExtendsStack.length-1}}}(n("./tree"));var s=/^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol);r.env=r.env||(location.hostname=="127.0.0.1"||location.hostname=="0.0.0.0"||location.hostname=="localhost"||location.port.length>0||s?"development":"production");var o={info:2,errors:1,none:0};r.logLevel=typeof r.logLevel!="undefined"?r.logLevel:o.info,r.async=r.async||!1,r.fileAsync=r.fileAsync||!1,r.poll=r.poll||(s?1e3:1500);if(r.functions)for(var u in r.functions)r.tree.functions[u]=r.functions[u];var a=/!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash);a&&(r.dumpLineNumbers=a[1]);var f=/^text\/(x-)?less$/,l=null,c={};r.watch=function(){return r.watchMode||(r.env="development",A()),this.watchMode=!0},r.unwatch=function(){return clearInterval(r.watchTimer),this.watchMode=!1},/!watch/.test(location.hash)&&r.watch();if(r.env!="development")try{l=typeof e.localStorage=="undefined"?null:e.localStorage}catch(O){}var M=document.getElementsByTagName("link");r.sheets=[];for(var _=0;_", @@ -22,7 +22,7 @@ "test": "./test" }, "jam": { - "main": "./dist/less-1.5.0.js" + "main": "./dist/less-1.4.2.js" }, "engines": { "node": ">=0.4.2"