diff --git a/.jscsrc b/.jscsrc index a576705f..d0231574 100644 --- a/.jscsrc +++ b/.jscsrc @@ -1,5 +1,5 @@ { - "disallowImplicitTypeConversion": ["numeric", "boolean", "binary", "string"], + "disallowImplicitTypeConversion": ["numeric", "binary", "string"], "disallowKeywords": ["with"], "disallowMixedSpacesAndTabs": true, "disallowMultipleLineBreaks": true, @@ -14,7 +14,7 @@ "disallowSpacesInNamedFunctionExpression": { "beforeOpeningRoundBrace": true}, "disallowTrailingComma": true, - "disallowTrailingWhitespace": true, + "disallowTrailingWhitespace": false, "maximumLineLength": 160, "requireCommaBeforeLineBreak": true, "requireCurlyBraces": [ "if", diff --git a/Gruntfile.js b/Gruntfile.js index 5c2d00ad..92fea041 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -31,7 +31,7 @@ module.exports = function (grunt) { shell: { options: {stdout: true, failOnError: true}, test: { - command: 'node test' + command: 'node test/index.js' }, benchmark: { command: 'node benchmark/index.js' @@ -241,14 +241,6 @@ module.exports = function (grunt) { outfile: 'tmp/browser/test-runner-global-vars.html' } }, - postProcessor: { - src: ['test/browser/less/postProcessor/*.less'], - options: { - helpers: 'test/browser/runner-postProcessor-options.js', - specs: 'test/browser/runner-postProcessor.js', - outfile: 'tmp/browser/test-runner-post-processor.html' - } - }, postProcessorPlugin: { src: ['test/less/postProcessorPlugin/*.less'], options: { diff --git a/bin/lessc b/bin/lessc index d733323c..e109ce08 100755 --- a/bin/lessc +++ b/bin/lessc @@ -14,31 +14,43 @@ try { var less = require('../lib/less-node'), pluginLoader = new less.PluginLoader(less), - plugin, - plugins = []; - -var args = process.argv.slice(1); -var silent = false, + plugins = [], + queuePlugins = [], + args = process.argv.slice(1), + silent = false, verbose = false, options = { - depends: false, - compress: false, - max_line_len: -1, - lint: false, - paths: [], - color: true, - strictImports: false, - insecure: false, - rootpath: '', - relativeUrls: false, - ieCompat: true, - strictMath: false, - strictUnits: false, - globalVars: null, - modifyVars: null, - urlArgs: '', - plugins: plugins -}; + depends: false, + compress: false, + max_line_len: -1, + lint: false, + paths: [], + color: true, + strictImports: false, + insecure: false, + rootpath: '', + relativeUrls: false, + ieCompat: true, + strictMath: false, + strictUnits: false, + globalVars: null, + modifyVars: null, + urlArgs: '', + plugins: plugins + }; + +if (less.options) { + for (var i = 0, keys = Object.keys(options); i < keys.length; i++) { + if (!less.options[keys[i]]) { + less.options[keys[i]] = options[keys[i]]; + } + } + options = less.options; +} +else { + less.options = options; +} + var sourceMapOptions = {}; var continueProcessing = true; @@ -94,194 +106,7 @@ function printUsage() { pluginLoader.printUsage(plugins); continueProcessing = false; } - -// self executing function so we can return -(function() { - args = args.filter(function (arg) { - var match; - - match = arg.match(/^-I(.+)$/); - if (match) { - options.paths.push(match[1]); - return false; - } - - match = arg.match(/^--?([a-z][0-9a-z-]*)(?:=(.*))?$/i); - if (match) { - arg = match[1]; - } else { - return arg; - } - - switch (arg) { - case 'v': - case 'version': - console.log("lessc " + less.version.join('.') + " (Less Compiler) [JavaScript]"); - continueProcessing = false; - break; - case 'verbose': - verbose = true; - break; - case 's': - case 'silent': - silent = true; - break; - case 'l': - case 'lint': - options.lint = true; - break; - case 'strict-imports': - options.strictImports = true; - break; - case 'h': - case 'help': - printUsage(); - break; - case 'x': - case 'compress': - options.compress = true; - break; - case 'insecure': - options.insecure = true; - break; - case 'M': - case 'depends': - options.depends = true; - break; - case 'max-line-len': - if (checkArgFunc(arg, match[2])) { - options.maxLineLen = parseInt(match[2], 10); - if (options.maxLineLen <= 0) { - options.maxLineLen = -1; - } - } - break; - case 'no-color': - options.color = false; - break; - case 'no-ie-compat': - options.ieCompat = false; - break; - case 'no-js': - options.javascriptEnabled = false; - break; - case 'include-path': - if (checkArgFunc(arg, match[2])) { - // ; supported on windows. - // : supported on windows and linux, excluding a drive letter like C:\ so C:\file:D:\file parses to 2 - options.paths = match[2] - .split(os.type().match(/Windows/) ? /:(?!\\)|;/ : ':') - .map(function(p) { - if (p) { - return path.resolve(process.cwd(), p); - } - }); - } - break; - case 'line-numbers': - if (checkArgFunc(arg, match[2])) { - options.dumpLineNumbers = match[2]; - } - break; - case 'source-map': - options.sourceMap = true; - if (match[2]) { - sourceMapOptions.sourceMapFullFilename = match[2]; - } - break; - case 'source-map-rootpath': - if (checkArgFunc(arg, match[2])) { - sourceMapOptions.sourceMapRootpath = match[2]; - } - break; - case 'source-map-basepath': - if (checkArgFunc(arg, match[2])) { - sourceMapOptions.sourceMapBasepath = match[2]; - } - break; - case 'source-map-map-inline': - sourceMapFileInline = true; - options.sourceMap = true; - break; - case 'source-map-less-inline': - sourceMapOptions.outputSourceFiles = true; - break; - case 'source-map-url': - if (checkArgFunc(arg, match[2])) { - sourceMapOptions.sourceMapURL = match[2]; - } - break; - case 'rp': - case 'rootpath': - if (checkArgFunc(arg, match[2])) { - options.rootpath = match[2].replace(/\\/g, '/'); - } - break; - case "ru": - case "relative-urls": - options.relativeUrls = true; - break; - case "sm": - case "strict-math": - if (checkArgFunc(arg, match[2])) { - options.strictMath = checkBooleanArg(match[2]); - } - break; - case "su": - case "strict-units": - if (checkArgFunc(arg, match[2])) { - options.strictUnits = checkBooleanArg(match[2]); - } - break; - case "global-var": - if (checkArgFunc(arg, match[2])) { - if (!options.globalVars) { - options.globalVars = {}; - } - parseVariableOption(match[2], options.globalVars); - } - break; - case "modify-var": - if (checkArgFunc(arg, match[2])) { - if (!options.modifyVars) { - options.modifyVars = {}; - } - - parseVariableOption(match[2], options.modifyVars); - } - break; - case 'url-args': - if (checkArgFunc(arg, match[2])) { - options.urlArgs = match[2]; - } - break; - case 'plugin': - var splitupArg = match[2].match(/^([^=]+)(=(.*))?/), - name = splitupArg[1], - pluginOptions = splitupArg[3]; - - plugin = pluginLoader.tryLoadPlugin(name, pluginOptions); - if (plugin) { - plugins.push(plugin); - } else { - console.error("Unable to load plugin " + name + - " please make sure that it is installed under or at the same level as less"); - process.exitCode = 1; - } - break; - default: - plugin = pluginLoader.tryLoadPlugin("less-plugin-" + arg, match[2]); - if (plugin) { - plugins.push(plugin); - } else { - console.error("Unable to interpret argument " + arg + - " - if it is a plugin (less-plugin-" + arg + "), make sure that it is installed under or at" + - " the same level as less"); - process.exitCode = 1; - } - break; - } - }); +function render() { if (!continueProcessing) { return; @@ -505,4 +330,219 @@ function printUsage() { parseLessFile(false, buffer); }); } +} + +function processPluginQueue() { + var x = 0; + + function pluginError(name) { + console.error("Unable to load plugin " + name + + " please make sure that it is installed under or at the same level as less"); + process.exitCode = 1; + } + function pluginFinished(plugin) { + x++; + plugins.push(plugin); + if (x === queuePlugins.length) { + render(); + } + } + queuePlugins.forEach(function(queue) { + pluginLoader.tryLoadPlugin(queue.name, function(err, data) { + if (err) { + pluginError(queue.name); + } + else { + pluginFinished({ + fileContent: data.contents, + filename: data.filename, + options: queue.options + }); + } + }); + }); +} + +// self executing function so we can return +(function() { + args = args.filter(function (arg) { + var match; + + match = arg.match(/^-I(.+)$/); + if (match) { + options.paths.push(match[1]); + return false; + } + + match = arg.match(/^--?([a-z][0-9a-z-]*)(?:=(.*))?$/i); + if (match) { + arg = match[1]; + } else { + return arg; + } + + switch (arg) { + case 'v': + case 'version': + console.log("lessc " + less.version.join('.') + " (Less Compiler) [JavaScript]"); + continueProcessing = false; + break; + case 'verbose': + verbose = true; + break; + case 's': + case 'silent': + silent = true; + break; + case 'l': + case 'lint': + options.lint = true; + break; + case 'strict-imports': + options.strictImports = true; + break; + case 'h': + case 'help': + printUsage(); + break; + case 'x': + case 'compress': + options.compress = true; + break; + case 'insecure': + options.insecure = true; + break; + case 'M': + case 'depends': + options.depends = true; + break; + case 'max-line-len': + if (checkArgFunc(arg, match[2])) { + options.maxLineLen = parseInt(match[2], 10); + if (options.maxLineLen <= 0) { + options.maxLineLen = -1; + } + } + break; + case 'no-color': + options.color = false; + break; + case 'no-ie-compat': + options.ieCompat = false; + break; + case 'inline-js': + options.javascriptEnabled = true; + break; + case 'no-js': + console.error('The "--no-js" argument is deprecated. Use "--inline-js" to enable inline JavaScript.'); + break; + case 'include-path': + if (checkArgFunc(arg, match[2])) { + // ; supported on windows. + // : supported on windows and linux, excluding a drive letter like C:\ so C:\file:D:\file parses to 2 + options.paths = match[2] + .split(os.type().match(/Windows/) ? /:(?!\\)|;/ : ':') + .map(function(p) { + if (p) { + return path.resolve(process.cwd(), p); + } + }); + } + break; + case 'line-numbers': + if (checkArgFunc(arg, match[2])) { + options.dumpLineNumbers = match[2]; + } + break; + case 'source-map': + options.sourceMap = true; + if (match[2]) { + sourceMapOptions.sourceMapFullFilename = match[2]; + } + break; + case 'source-map-rootpath': + if (checkArgFunc(arg, match[2])) { + sourceMapOptions.sourceMapRootpath = match[2]; + } + break; + case 'source-map-basepath': + if (checkArgFunc(arg, match[2])) { + sourceMapOptions.sourceMapBasepath = match[2]; + } + break; + case 'source-map-map-inline': + sourceMapFileInline = true; + options.sourceMap = true; + break; + case 'source-map-less-inline': + sourceMapOptions.outputSourceFiles = true; + break; + case 'source-map-url': + if (checkArgFunc(arg, match[2])) { + sourceMapOptions.sourceMapURL = match[2]; + } + break; + case 'rp': + case 'rootpath': + if (checkArgFunc(arg, match[2])) { + options.rootpath = match[2].replace(/\\/g, '/'); + } + break; + case "ru": + case "relative-urls": + options.relativeUrls = true; + break; + case "sm": + case "strict-math": + if (checkArgFunc(arg, match[2])) { + options.strictMath = checkBooleanArg(match[2]); + } + break; + case "su": + case "strict-units": + if (checkArgFunc(arg, match[2])) { + options.strictUnits = checkBooleanArg(match[2]); + } + break; + case "global-var": + if (checkArgFunc(arg, match[2])) { + if (!options.globalVars) { + options.globalVars = {}; + } + parseVariableOption(match[2], options.globalVars); + } + break; + case "modify-var": + if (checkArgFunc(arg, match[2])) { + if (!options.modifyVars) { + options.modifyVars = {}; + } + + parseVariableOption(match[2], options.modifyVars); + } + break; + case 'url-args': + if (checkArgFunc(arg, match[2])) { + options.urlArgs = match[2]; + } + break; + case 'plugin': + var splitupArg = match[2].match(/^([^=]+)(=(.*))?/), + name = splitupArg[1], + pluginOptions = splitupArg[3]; + queuePlugins.push({ name: name, options: pluginOptions }); + break; + default: + queuePlugins.push({ name: arg, options: match[2], default: true }); + break; + } + }); + + if (queuePlugins.length > 0) { + processPluginQueue(); + } + else { + render(); + } + })(); diff --git a/lib/less-browser/add-default-options.js b/lib/less-browser/add-default-options.js index b06c58f1..27fcb9e2 100644 --- a/lib/less-browser/add-default-options.js +++ b/lib/less-browser/add-default-options.js @@ -43,4 +43,6 @@ module.exports = function(window, options) { options.onReady = true; } + options.javascriptEnabled = (options.javascriptEnabled || options.inlineJavaScript) ? true : false; + }; diff --git a/lib/less-browser/index.js b/lib/less-browser/index.js index 6a1fbf49..5fc2a2b4 100644 --- a/lib/less-browser/index.js +++ b/lib/less-browser/index.js @@ -8,8 +8,7 @@ var addDataAttr = require("./utils").addDataAttr, module.exports = function(window, options) { var document = window.document; var less = require('../less')(); - - //module.exports = less; + less.options = options; var environment = less.environment, FileManager = require("./file-manager")(options, less.logger), @@ -23,20 +22,13 @@ module.exports = function(window, options) { var cache = less.cache = options.cache || require("./cache")(window, options, less.logger); require('./image-size')(less.environment); - //Setup user functions + //Setup user functions - Deprecate? if (options.functions) { less.functions.functionRegistry.addMultiple(options.functions); } var typePattern = /^text\/(x-)?less$/; - function postProcessCSS(styles) { // deprecated, use a plugin for postprocesstasks - if (options.postProcessor && typeof options.postProcessor === 'function') { - styles = options.postProcessor.call(styles, styles) || styles; - } - return styles; - } - function clone(obj) { var cloned = {}; for (var prop in obj) { @@ -133,7 +125,6 @@ module.exports = function(window, options) { e.href = path; callback(e); } else { - result.css = postProcessCSS(result.css); cache.setCSS(sheet.href, webInfo.lastModified, instanceOptions.modifyVars, result.css); callback(null, result.css, data, sheet, webInfo, path); } diff --git a/lib/less-browser/plugin-loader.js b/lib/less-browser/plugin-loader.js index e80828de..52bf6eb8 100644 --- a/lib/less-browser/plugin-loader.js +++ b/lib/less-browser/plugin-loader.js @@ -1,5 +1,4 @@ -var path = require("path"), - AbstractPluginLoader = require("../less/environment/abstract-plugin-loader.js"); +var AbstractPluginLoader = require("../less/environment/abstract-plugin-loader.js"); /** * Browser Plugin Loader @@ -11,24 +10,48 @@ var PluginLoader = function(less) { PluginLoader.prototype = new AbstractPluginLoader(); -PluginLoader.prototype.tryLoadFromEnvironment = function(filename, basePath, callback) { - - if(basePath && !filename) { - filename = path.join(basePath, name); - } - if(filename) { - var fileManager = new this.less.FileManager(); - - filename = fileManager.tryAppendExtension(filename,'.js'); - fileManager.loadFile(filename).then( - function(data) { +PluginLoader.prototype.tryLoadPlugin = function(name, basePath, callback) { + var self = this; + var prefix = name.slice(0, 1); + var explicit = prefix === "." || prefix === "/" || name.slice(-3).toLowerCase() === ".js"; + this.tryLoadFromEnvironment(name, basePath, explicit, function(err, data) { + if (explicit) { + callback(err, data); + } + else { + if (!err) { callback(null, data); - }, - - function(err) { - callback(err); } - ); + else { + self.tryLoadFromEnvironment('less-plugin-' + name, basePath, explicit, function(err2, data) { + callback(err, data); + }); + } + } + }); + +}; + +PluginLoader.prototype.tryLoadFromEnvironment = function(filename, basePath, explicit, callback) { + var fileManager = new this.less.FileManager(); + + if (basePath) { + filename = (fileManager.extractUrlParts(filename, basePath)).url; + } + + if (filename) { + + filename = fileManager.tryAppendExtension(filename, '.js'); + + var done = function(err, data) { + if (err) { + callback(err); + } else { + callback(null, data); + } + }; + fileManager.loadFile(filename, null, null, null, done); + } else { callback({ message: 'Plugin could not be found.'}); diff --git a/lib/less-node/index.js b/lib/less-node/index.js index 9baaf824..ab278336 100644 --- a/lib/less-node/index.js +++ b/lib/less-node/index.js @@ -12,6 +12,8 @@ less.PluginLoader = require("./plugin-loader"); less.fs = require("./fs"); less.FileManager = FileManager; less.UrlFileManager = UrlFileManager; +less.options = less.options || {}; + less.formatError = function(ctx, options) { options = options || {}; diff --git a/lib/less-node/lessc-helper.js b/lib/less-node/lessc-helper.js index 7b57ce29..3aa84c99 100644 --- a/lib/less-node/lessc-helper.js +++ b/lib/less-node/lessc-helper.js @@ -31,7 +31,7 @@ var lessc_helper = { console.log(" -M, --depends Outputs a makefile import dependency list to stdout."); console.log(" --no-color Disables colorized output."); console.log(" --no-ie-compat Disables IE compatibility checks."); - console.log(" --no-js Disables JavaScript in less files"); + console.log(" --inline-js Enables inline JavaScript in less files"); console.log(" -l, --lint Syntax check only (lint)."); console.log(" -s, --silent Suppresses output of error messages."); console.log(" --strict-imports Forces evaluation of imports."); diff --git a/lib/less-node/plugin-loader.js b/lib/less-node/plugin-loader.js index 90dd17ba..d65880c9 100644 --- a/lib/less-node/plugin-loader.js +++ b/lib/less-node/plugin-loader.js @@ -11,61 +11,53 @@ var PluginLoader = function(less) { prefix = path.dirname(prefix); return function(id) { var str = id.substr(0, 2); - if(str === '..' || str === './') { + if (str === '..' || str === './') { return require(path.join(prefix, id)); } else { return require(id); } - } + }; }; }; PluginLoader.prototype = new AbstractPluginLoader(); -PluginLoader.prototype.tryLoadFromEnvironment = function(name, basePath, callback) { - var filename; +PluginLoader.prototype.tryLoadPlugin = function(name, basePath, callback) { + var self = this; + var prefix = name.slice(0, 1); + var explicit = prefix === "." || prefix === "/" || name.slice(-3).toLowerCase() === ".js"; + if (explicit) { + this.tryLoadFromEnvironment(name, basePath, explicit, callback); + } + else { + this.tryLoadFromEnvironment('less-plugin-' + name, basePath, explicit, function(err, data) { + if (!err) { + callback(null, data); + } + else { + self.tryLoadFromEnvironment(name, basePath, explicit, callback); + } + }); + } + +}; + +PluginLoader.prototype.tryLoadFromEnvironment = function(name, basePath, explicit, callback) { + var filename = name; var self = this; - try { - filename = require.resolve(path.join("../../../", name)); - } - catch(e) { - } - // is installed as a sub dependency of the current folder - try { - filename = require.resolve(path.join(process.cwd(), "node_modules", name)); - } - catch(e) { - } - // is referenced relative to the current directory - try { - filename = require.resolve(path.join(process.cwd(), name)); - } - catch(e) { - } - // unlikely - would have to be a dependency of where this code was running (less.js)... - if (name[0] !== '.') { - try { - filename = require.resolve(name); - } - catch(e) { - } - } - if(basePath && !filename) { - filename = path.join(basePath, name); - } - if(filename) { - var fileManager = new this.less.FileManager(); + function getFile(filename) { + var fileManager = new self.less.FileManager(); - filename = fileManager.tryAppendExtension(filename,'.js'); + filename = fileManager.tryAppendExtension(filename, '.js'); fileManager.loadFile(filename).then( function(data) { try { self.require = self.requireRelative(filename); } catch(e) { - console.log(e.stack.toString()); + callback(e); } callback(null, data); }, @@ -75,8 +67,48 @@ PluginLoader.prototype.tryLoadFromEnvironment = function(name, basePath, callbac } ); } + if (explicit) { + if (basePath) { + filename = path.join(basePath, name); + } + getFile(filename); + } else { - callback({ message: 'Plugin could not be found.'}); + // Search node_modules for a possible plugin name match + try { + filename = require.resolve(path.join("../../../", name)); + } + catch(e) { + } + // is installed as a sub dependency of the current folder + try { + filename = require.resolve(path.join(process.cwd(), "node_modules", name)); + } + catch(e) { + } + // is referenced relative to the current directory + try { + filename = require.resolve(path.join(process.cwd(), name)); + } + catch(e) { + } + // unlikely - would have to be a dependency of where this code was running (less.js)... + if (name[0] !== '.') { + try { + filename = require.resolve(name); + } + catch(e) { + } + } + if (basePath) { + filename = path.join(basePath, name); + } + if (filename) { + getFile(filename); + } + else { + callback({ message: 'Plugin could not be found.'}); + } } }; diff --git a/lib/less/contexts.js b/lib/less/contexts.js index 545280b3..c7f6bc0f 100644 --- a/lib/less/contexts.js +++ b/lib/less/contexts.js @@ -48,7 +48,7 @@ var evalCopyProperties = [ 'sourceMap', // whether to output a source map 'importMultiple', // whether we are currently importing multiple copies 'urlArgs', // whether to add args into url tokens - 'javascriptEnabled',// option - whether JavaScript is enabled. if undefined, defaults to true + 'javascriptEnabled',// option - whether Inline JavaScript is enabled. if undefined, defaults to false 'pluginManager', // Used as the plugin manager for the session 'importantScope' // used to bubble up !important statements ]; diff --git a/lib/less/environment/abstract-file-manager.js b/lib/less/environment/abstract-file-manager.js index d9a2f90b..7c0e2063 100644 --- a/lib/less/environment/abstract-file-manager.js +++ b/lib/less/environment/abstract-file-manager.js @@ -35,13 +35,14 @@ abstractFileManager.prototype.alwaysMakePathsAbsolute = function() { abstractFileManager.prototype.isPathAbsolute = function(filename) { return (/^(?:[a-z-]+:|\/|\\|#)/i).test(filename); }; - +// TODO: pull out - this is part of Node & Browserify abstractFileManager.prototype.join = function(basePath, laterPath) { if (!basePath) { return laterPath; } return basePath + laterPath; }; + abstractFileManager.prototype.pathDiff = function pathDiff(url, baseUrl) { // diff between two paths to create a relative path diff --git a/lib/less/environment/abstract-plugin-loader.js b/lib/less/environment/abstract-plugin-loader.js index 55858cb5..9d86fcf2 100644 --- a/lib/less/environment/abstract-plugin-loader.js +++ b/lib/less/environment/abstract-plugin-loader.js @@ -1,4 +1,6 @@ -var functionRegistry = require("../functions/function-registry"); +var functionRegistry = require("../functions/function-registry"), + LessError = require('../less-error'); + var AbstractPluginLoader = function() { }; @@ -10,26 +12,40 @@ function error(msg, type) { } ); } -AbstractPluginLoader.prototype.evalPlugin = function(contents, context, fileInfo, callback) { +AbstractPluginLoader.prototype.evalPlugin = function(contents, context, pluginOptions, fileInfo) { var loader, registry, pluginObj, localModule, - localExports; + localExports, + pluginManager, + filename; + pluginManager = context.pluginManager; - pluginObj = context.pluginManager.get(fileInfo.filename); - - if(pluginObj) { - if(pluginObj.use) { - pluginObj.use(this.less); + if (fileInfo) { + if (typeof fileInfo === "string") { + filename = fileInfo; + } + else { + filename = fileInfo.filename; } - return callback(null, pluginObj); } - localModule = { + if (filename) { + pluginObj = pluginManager.get(filename); + + if (pluginObj) { + this.trySetOptions(pluginObj, filename, pluginOptions); + if (pluginObj.use) { + pluginObj.use(this.less); + } + return pluginObj; + } + } + localModule = { exports: {}, - pluginManager: context.pluginManager, + pluginManager: pluginManager, fileInfo: fileInfo }; localExports = localModule.exports; @@ -39,74 +55,71 @@ AbstractPluginLoader.prototype.evalPlugin = function(contents, context, fileInfo loader = new Function("module", "require", "functions", "tree", "fileInfo", "less", contents); pluginObj = loader(localModule, this.require, registry, this.less.tree, fileInfo, this.less); - if(!pluginObj) { + if (!pluginObj) { pluginObj = localModule.exports; } - pluginObj = this.validatePlugin(pluginObj); - if(pluginObj) { + pluginObj = this.validatePlugin(pluginObj, filename); + + if (pluginObj) { // Run on first load - context.pluginManager.addPlugin(pluginObj, fileInfo.filename); + pluginManager.addPlugin(pluginObj, fileInfo.filename); pluginObj.functions = registry.getLocalFunctions(); + this.trySetOptions(pluginObj, filename, pluginOptions); + // Run every @plugin call - if(pluginObj.use) { + if (pluginObj.use) { pluginObj.use(this.less); } } else { - throw new Error(); + throw new SyntaxError("Not a valid plugin"); } } catch(e) { // TODO pass the error - console.log(e.stack.toString()); - callback(new this.less.LessError({ + console.log(e); + return new this.less.LessError({ message: "Plugin evaluation error: '" + e.name + ': ' + e.message.replace(/["]/g, "'") + "'" , - filename: this.fileInfo.filename, + filename: filename, line: this.line, col: this.column - }), null ); + }); } - callback(null, pluginObj); + return pluginObj; }; -AbstractPluginLoader.prototype.tryLoadPlugin = function(name, argument, basePath, callback) { - var self = this; - this.tryLoadFromEnvironment(name, basePath, function(err, data) { - if(!err) { - callback(null, data); - } - else { - self.tryLoadFromEnvironment('less-plugin-' + name, basePath, callback); - } - }); +AbstractPluginLoader.prototype.trySetOptions = function(plugin, filename, options) { + var name = require('path').basename(filename); + if (options) { + if (!plugin.setOptions) { + error("Options have been provided but the plugin " + name + " does not support any options."); + return null; + } + try { + plugin.setOptions(options); + } + catch(e) { + error("Error setting options on plugin " + name + '\n' + e.message); + return null; + } + } }; -AbstractPluginLoader.prototype.validatePlugin = function(plugin, argument) { + +AbstractPluginLoader.prototype.validatePlugin = function(plugin, filename) { if (plugin) { // support plugins being a function // so that the plugin can be more usable programmatically if (typeof plugin === "function") { plugin = new plugin(); } + var name = require('path').basename(filename); if (plugin.minVersion) { if (this.compareVersion(plugin.minVersion, this.less.version) < 0) { - error("plugin " + name + " requires version " + this.versionToString(plugin.minVersion)); - return null; - } - } - if (argument) { - if (!plugin.setOptions) { - error("options have been provided but the plugin " + name + "does not support any options"); - return null; - } - try { - plugin.setOptions(argument); - } - catch(e) { - error("Error setting options on plugin " + name + '\n' + e.message); + error("Plugin " + name + " requires version " + this.versionToString(plugin.minVersion)); return null; } } diff --git a/lib/less/import-manager.js b/lib/less/import-manager.js index 05ad8f00..3cbc4e21 100644 --- a/lib/less/import-manager.js +++ b/lib/less/import-manager.js @@ -1,5 +1,6 @@ var contexts = require("./contexts"), - Parser = require('./parser/parser'); + Parser = require('./parser/parser'), + LessError = require('./less-error'); module.exports = function(environment) { @@ -35,7 +36,7 @@ module.exports = function(environment) { */ ImportManager.prototype.push = function (path, tryAppendExtension, currentFileInfo, importOptions, callback) { var importManager = this, - pluginLoader = new this.less.PluginLoader(this.less); + pluginLoader = this.context.pluginManager.Loader; this.queue.push(path); @@ -72,7 +73,8 @@ module.exports = function(environment) { } var loadFileCallback = function(loadedFile) { - var resolvedFilename = loadedFile.filename, + var plugin, + resolvedFilename = loadedFile.filename, contents = loadedFile.contents.replace(/^\uFEFF/, ''); // Pass on an updated rootpath if path of imported file is relative and file @@ -105,9 +107,13 @@ module.exports = function(environment) { } if (importOptions.isPlugin) { - pluginLoader.evalPlugin(contents, newEnv, newFileInfo, function (e, root) { - fileParsedFunc(e, root, resolvedFilename); - }); + plugin = pluginLoader.evalPlugin(contents, newEnv, importOptions.pluginArgs, newFileInfo); + if (plugin instanceof LessError) { + fileParsedFunc(plugin, null, resolvedFilename); + } + else { + fileParsedFunc(null, plugin, resolvedFilename); + } } else if (importOptions.inline) { fileParsedFunc(null, contents, resolvedFilename); } else { @@ -124,10 +130,9 @@ module.exports = function(environment) { loadFileCallback(loadedFile); } }; - if(importOptions.isPlugin) { - // TODO: implement options for plugins + if (importOptions.isPlugin) { try { - pluginLoader.tryLoadPlugin(path, null, currentFileInfo.currentDirectory, done); + pluginLoader.tryLoadPlugin(path, currentFileInfo.currentDirectory, done); } catch(e) { callback(e); diff --git a/lib/less/parse.js b/lib/less/parse.js index a6048aa0..5dbad10e 100644 --- a/lib/less/parse.js +++ b/lib/less/parse.js @@ -1,7 +1,8 @@ var PromiseConstructor, contexts = require("./contexts"), Parser = require('./parser/parser'), - PluginManager = require('./plugin-manager'); + PluginManager = require('./plugin-manager'), + LessError = require('./less-error'); module.exports = function(environment, ParseTree, ImportManager) { var parse = function (input, options, callback) { @@ -29,10 +30,8 @@ module.exports = function(environment, ParseTree, ImportManager) { } else { var context, rootFileInfo, - less = this, pluginManager = new PluginManager(this, true); - pluginManager.addPlugins(options.plugins); options.pluginManager = pluginManager; context = new contexts.Parse(options); @@ -57,7 +56,27 @@ module.exports = function(environment, ParseTree, ImportManager) { } var imports = new ImportManager(this, context, rootFileInfo); - + + if (options.plugins) { + options.plugins.forEach(function(plugin) { + var evalResult, contents; + if (plugin.fileContent) { + contents = plugin.fileContent.replace(/^\uFEFF/, ''); + evalResult = pluginManager.Loader.evalPlugin(contents, context, plugin.options, plugin.filename); + if (!(evalResult instanceof LessError)) { + pluginManager.addPlugin(plugin); + } + else { + return callback(evalResult); + } + + } + else { + pluginManager.addPlugin(plugin); + } + }); + } + new Parser(context, imports, rootFileInfo) .parse(input, function (e, root) { if (e) { return callback(e); } diff --git a/lib/less/parser/parser.js b/lib/less/parser/parser.js index ad9269fd..53000487 100644 --- a/lib/less/parser/parser.js +++ b/lib/less/parser/parser.js @@ -1384,22 +1384,28 @@ var Parser = function Parser(context, imports, fileInfo) { }, // - // A @plugin atrule, used to import scoped extensions dynamically. + + // A @plugin directive, used to import plugins dynamically. // - // @plugin "lib"; - // - // Depending on our environment, 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. + // @plugin (args) "lib"; // plugin: function () { - var path, + var path, args, options, index = parserInput.i, dir = parserInput.$re(/^@plugin?\s+/); if (dir) { - var options = { isPlugin : true }; + args = this.pluginArgs(); + + if (args) { + options = { + pluginArgs: args, + isPlugin: true + }; + } + else { + options = { isPlugin: true }; + } if ((path = this.entities.quoted() || this.entities.url())) { @@ -1407,7 +1413,6 @@ var Parser = function Parser(context, imports, fileInfo) { parserInput.i = index; error("missing semi-colon on @plugin"); } - return new(tree.Import)(path, null, options, index, fileInfo); } else { @@ -1417,6 +1422,24 @@ var Parser = function Parser(context, imports, fileInfo) { } }, + pluginArgs: function() { + // list of options, surrounded by parens + parserInput.save(); + if (! parserInput.$char('(')) { + parserInput.restore(); + return null; + } + var args = parserInput.$re(/^\s*([^\);]+)\)\s*/); + if (args[1]) { + parserInput.forget(); + return args[1].trim(); + } + else { + parserInput.restore(); + return null; + } + }, + // // A CSS AtRule // diff --git a/lib/less/plugin-manager.js b/lib/less/plugin-manager.js index 542d0f76..42386398 100644 --- a/lib/less/plugin-manager.js +++ b/lib/less/plugin-manager.js @@ -10,6 +10,7 @@ var PluginManager = function(less) { this.fileManagers = []; this.iterator = -1; this.pluginCache = {}; + this.Loader = new less.PluginLoader(less); }; var pm, PluginManagerFactory = function(less, newFactory) { @@ -37,8 +38,10 @@ PluginManager.prototype.addPlugins = function(plugins) { */ PluginManager.prototype.addPlugin = function(plugin, filename) { this.installedPlugins.push(plugin); - this.pluginCache[filename] = plugin; - if(plugin.install) { + if (filename) { + this.pluginCache[filename] = plugin; + } + if (plugin.install) { plugin.install(this.less, this); } }; @@ -166,10 +169,10 @@ PluginManager.prototype.visitor = function() { return self.visitors[self.iterator]; }, get: function() { - self.iterator+=1; + self.iterator += 1; return self.visitors[self.iterator]; } - } + }; }; /** * diff --git a/lib/less/transform-tree.js b/lib/less/transform-tree.js index e026a9db..47510793 100644 --- a/lib/less/transform-tree.js +++ b/lib/less/transform-tree.js @@ -47,8 +47,8 @@ module.exports = function(root, options) { if (options.pluginManager) { visitorIterator = options.pluginManager.visitor(); visitorIterator.first(); - while(v = visitorIterator.get()) { - if(v.isPreEvalVisitor) { + while ((v = visitorIterator.get())) { + if (v.isPreEvalVisitor) { v.run(root); } } @@ -62,8 +62,10 @@ module.exports = function(root, options) { if (options.pluginManager) { visitorIterator.first(); - while(v = visitorIterator.get()) { - v.run(root); + while ((v = visitorIterator.get())) { + if (!v.isPreEvalVisitor) { + v.run(evaldRoot); + } } } diff --git a/lib/less/tree/import.js b/lib/less/tree/import.js index 10385141..ef9636a9 100644 --- a/lib/less/tree/import.js +++ b/lib/less/tree/import.js @@ -127,8 +127,8 @@ Import.prototype.doEval = function (context) { features = this.features && this.features.eval(context); if (this.options.isPlugin) { - if(this.root && this.root.setContext) { - this.root.setContext(context); + if (this.root && this.root.eval) { + this.root.eval(context); } registry = context.frames[0] && context.frames[0].functionRegistry; if ( registry && this.root && this.root.functions ) { diff --git a/lib/less/tree/js-eval-node.js b/lib/less/tree/js-eval-node.js index 30fbbfae..a4410b23 100644 --- a/lib/less/tree/js-eval-node.js +++ b/lib/less/tree/js-eval-node.js @@ -10,8 +10,8 @@ JsEvalNode.prototype.evaluateJavaScript = function (expression, context) { that = this, evalContext = {}; - if (context.javascriptEnabled !== undefined && !context.javascriptEnabled) { - throw { message: "You are using JavaScript, which has been disabled.", + if (!context.javascriptEnabled) { + throw { message: "Inline JavaScript is not enabled. Is it set in your options?", filename: this.currentFileInfo.filename, index: this.index }; } diff --git a/test/browser/common.js b/test/browser/common.js index 887e9cc6..58ef5060 100644 --- a/test/browser/common.js +++ b/test/browser/common.js @@ -1,6 +1,7 @@ /* Add js reporter for sauce */ jasmine.getEnv().addReporter(new jasmine.JSReporter2()); +jasmine.getEnv().defaultTimeoutInterval = 3000; /* record log messages for testing */ diff --git a/test/browser/runner-browser-options.js b/test/browser/runner-browser-options.js index 64037bbb..b90ec3b0 100644 --- a/test/browser/runner-browser-options.js +++ b/test/browser/runner-browser-options.js @@ -1,4 +1,4 @@ -var less = {logLevel: 4, errorReporting: "console"}; +var less = {logLevel: 4, errorReporting: "console", javascriptEnabled: true}; // There originally run inside describe method. However, since they have not // been inside it, they run at jasmine compile time (not runtime). It all diff --git a/test/browser/runner-errors-options.js b/test/browser/runner-errors-options.js index 8ba00e27..97b211d9 100644 --- a/test/browser/runner-errors-options.js +++ b/test/browser/runner-errors-options.js @@ -1,4 +1,6 @@ var less = { strictUnits: true, strictMath: true, - logLevel: 4 }; + logLevel: 4, + javascriptEnabled: true +}; diff --git a/test/browser/runner-postProcessor-options.js b/test/browser/runner-postProcessor-options.js deleted file mode 100644 index fe7111b6..00000000 --- a/test/browser/runner-postProcessor-options.js +++ /dev/null @@ -1,5 +0,0 @@ -var less = {logLevel: 4, - errorReporting: "console"}; -less.postProcessor = function(styles) { - return 'hr {height:50px;}\n' + styles; -}; diff --git a/test/browser/runner-postProcessor.js b/test/browser/runner-postProcessor.js deleted file mode 100644 index 64937669..00000000 --- a/test/browser/runner-postProcessor.js +++ /dev/null @@ -1,3 +0,0 @@ -describe("less.js postProcessor (deprecated)", function() { - testLessEqualsInDocument(); -}); diff --git a/test/browser/test-runner-template.tmpl b/test/browser/test-runner-template.tmpl index c02c38f3..9fef5680 100644 --- a/test/browser/test-runner-template.tmpl +++ b/test/browser/test-runner-template.tmpl @@ -3,7 +3,6 @@