diff --git a/bin/lessc b/bin/lessc index e109ce08..60b8ebc6 100755 --- a/bin/lessc +++ b/bin/lessc @@ -37,7 +37,7 @@ var less = require('../lib/less-node'), modifyVars: null, urlArgs: '', plugins: plugins - }; + }; if (less.options) { for (var i = 0, keys = Object.keys(options); i < keys.length; i++) { @@ -310,7 +310,11 @@ function render() { } }, function(err) { - less.writeError(err, options); + if (!options.silent) { + console.error(err.toString({ + stylize: options.color && less.lesscHelper.stylize + })); + } process.exitCode = 1; }); }; diff --git a/lib/less-node/index.js b/lib/less-node/index.js index ab278336..4e379845 100644 --- a/lib/less-node/index.js +++ b/lib/less-node/index.js @@ -14,62 +14,6 @@ less.FileManager = FileManager; less.UrlFileManager = UrlFileManager; less.options = less.options || {}; -less.formatError = function(ctx, options) { - options = options || {}; - - var message = ""; - var extract = ctx.extract; - var error = []; - var stylize = options.color ? lesscHelper.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.hasOwnProperty('index') || !extract) { - return ctx.stack || ctx.message; - } - - if (typeof extract[0] === 'string') { - error.push(stylize((ctx.line - 1) + ' ' + extract[0], 'grey')); - } - - if (typeof extract[1] === 'string') { - var errorTxt = ctx.line + ' '; - if (extract[1]) { - errorTxt += extract[1].slice(0, ctx.column) + - stylize(stylize(stylize(extract[1].substr(ctx.column, 1), 'bold') + - extract[1].slice(ctx.column + 1), 'red'), 'inverse'); - } - error.push(errorTxt); - } - - if (typeof extract[2] === 'string') { - error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey')); - } - error = error.join('\n') + stylize('', 'reset') + '\n'; - - message += stylize(ctx.type + 'Error: ' + ctx.message, 'red'); - if (ctx.filename) { - message += stylize(' in ', 'red') + ctx.filename + - stylize(' on line ' + ctx.line + ', column ' + (ctx.column + 1) + ':', 'grey'); - } - - message += '\n' + error; - - if (ctx.callLine) { - message += stylize('from ', 'red') + (ctx.filename || '') + '/n'; - message += stylize(ctx.callLine, 'grey') + ' ' + ctx.callExtract + '/n'; - } - - return message; -}; - -less.writeError = function (ctx, options) { - options = options || {}; - if (options.silent) { return; } - console.error(less.formatError(ctx, options)); -}; - // provide image-size functionality require('./image-size')(less.environment); diff --git a/lib/less/less-error.js b/lib/less/less-error.js index acaac22b..d4a2cca6 100644 --- a/lib/less/less-error.js +++ b/lib/less/less-error.js @@ -1,7 +1,27 @@ -var utils = require("./utils"); - +var utils = require('./utils'); +/** + * This is a centralized class of any error that could be thrown internally (mostly by the parser). + * Besides standard .message it keeps some additional data like a path to the file where the error + * occurred along with line and column numbers. + * + * @class + * @extends Error + * @type {module.LessError} + * + * @prop {string} type + * @prop {string} filename + * @prop {number} index + * @prop {number} line + * @prop {number} column + * @prop {number} callLine + * @prop {number} callExtract + * @prop {string[]} extract + * + * @param {Object} e - An error object to wrap around or just a descriptive object + * @param {Object} importManager - An instance of ImportManager (see import-manager.js) + * @param {string} [currentFilename] + */ var LessError = module.exports = function LessError(e, importManager, currentFilename) { - Error.call(this); var filename = e.filename || currentFilename; @@ -40,3 +60,60 @@ if (typeof Object.create === 'undefined') { } LessError.prototype.constructor = LessError; + +/** + * An overridden version of the default Object.prototype.toString + * which uses additional information to create a helpful message. + * + * @param {Object} options + * @returns {string} + */ +LessError.prototype.toString = function(options) { + options = options || {}; + + var message = ''; + var extract = this.extract; + var error = []; + var stylize = function (str) { return str; }; + if (options.stylize) { + var type = typeof options.stylize; + if (type !== 'function') { + throw Error('options.stylize should be a function, got a ' + type + '!'); + } + stylize = options.stylize; + } + + if (typeof extract[0] === 'string') { + error.push(stylize((this.line - 1) + ' ' + extract[0], 'grey')); + } + + if (typeof extract[1] === 'string') { + var errorTxt = this.line + ' '; + if (extract[1]) { + errorTxt += extract[1].slice(0, this.column) + + stylize(stylize(stylize(extract[1].substr(this.column, 1), 'bold') + + extract[1].slice(this.column + 1), 'red'), 'inverse'); + } + error.push(errorTxt); + } + + if (typeof extract[2] === 'string') { + error.push(stylize((this.line + 1) + ' ' + extract[2], 'grey')); + } + error = error.join('\n') + stylize('', 'reset') + '\n'; + + message += stylize(this.type + 'Error: ' + this.message, 'red'); + if (this.filename) { + message += stylize(' in ', 'red') + this.filename + + stylize(' on line ' + this.line + ', column ' + (this.column + 1) + ':', 'grey'); + } + + message += '\n' + error; + + if (this.callLine) { + message += stylize('from ', 'red') + (this.filename || '') + '/n'; + message += stylize(this.callLine, 'grey') + ' ' + this.callExtract + '/n'; + } + + return message; +}; diff --git a/test/less-test.js b/test/less-test.js index 7a2a3e50..5b2be471 100644 --- a/test/less-test.js +++ b/test/less-test.js @@ -136,20 +136,20 @@ module.exports = function() { function testErrors(name, err, compiledLess, doReplacements, sourcemap, baseFolder) { fs.readFile(path.join(baseFolder, name) + '.txt', 'utf8', function (e, expectedErr) { - process.stdout.write("- " + path.join(baseFolder, name) + ": "); + process.stdout.write('- ' + path.join(baseFolder, name) + ": "); expectedErr = doReplacements(expectedErr, baseFolder); if (!err) { if (compiledLess) { - fail("No Error", 'red'); + fail('No Error', 'red'); } else { - fail("No Error, No Output"); + fail('No Error, No Output'); } } else { - var errMessage = less.formatError(err); + var errMessage = err.toString(); if (errMessage === expectedErr) { ok('OK'); } else { - difference("FAIL", expectedErr, errMessage); + difference('FAIL', expectedErr, errMessage); } } }); @@ -254,7 +254,7 @@ module.exports = function() { var doubleCallCheck = false; queue(function() { toCSS(options, path.join(baseFolder, foldername + file), function (err, result) { - + if (doubleCallCheck) { totalTests++; fail("less is calling back twice");