diff --git a/lib/processCss.js b/lib/processCss.js index 53bfac98..084918d4 100644 --- a/lib/processCss.js +++ b/lib/processCss.js @@ -2,6 +2,7 @@ MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ +var formatCodeFrame = require("babel-code-frame"); var Tokenizer = require("css-selector-tokenizer"); var postcss = require("postcss"); var loaderUtils = require("loader-utils"); @@ -208,6 +209,43 @@ module.exports = function processCss(inputSource, inputMap, options, callback) { urlItemRegExp: /___CSS_LOADER_URL___([0-9]+)___/ }); }).catch(function(err) { - callback(err); + if (err.name === 'CssSyntaxError') { + var wrappedError = new CSSLoaderError( + 'Syntax Error', + err.reason, + err.line != null && err.column != null + ? {line: err.line, column: err.column} + : null, + err.input.source + ); + callback(wrappedError); + } else { + callback(err); + } }); }; + +function formatMessage(message, loc, source) { + var formatted = message; + if (loc) { + formatted = formatted + + ' (' + loc.line + ':' + loc.column + ')'; + } + if (loc && source) { + formatted = formatted + + '\n\n' + formatCodeFrame(source, loc.line, loc.column) + '\n'; + } + return formatted; +} + +function CSSLoaderError(name, message, loc, source, error) { + Error.call(this); + Error.captureStackTrace(this, CSSLoaderError); + this.name = name; + this.error = error; + this.message = formatMessage(message, loc, source); + this.hideStack = true; +} + +CSSLoaderError.prototype = Object.create(Error.prototype); +CSSLoaderError.prototype.constructor = CSSLoaderError; diff --git a/package.json b/package.json index 4df48ddc..a4129aa5 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,12 @@ "node": ">=0.12.0" }, "dependencies": { + "babel-code-frame": "^6.11.0", "css-selector-tokenizer": "^0.6.0", "cssnano": ">=2.6.1 <4", "loader-utils": "~0.2.2", - "object-assign": "^4.0.1", "lodash.camelcase": "^3.0.1", + "object-assign": "^4.0.1", "postcss": "^5.0.6", "postcss-modules-extract-imports": "^1.0.0", "postcss-modules-local-by-default": "^1.0.1", diff --git a/test/helpers.js b/test/helpers.js index fc99c8d3..8e4248e5 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -69,6 +69,23 @@ exports.test = function test(name, input, result, query, modules) { }); }; +exports.testError = function test(name, input, onError) { + it(name, function(done) { + runLoader(cssLoader, input, undefined, {}, function(err, output) { + if (!err) { + done(new Error('Expected error to be thrown')); + } else { + try { + onError(err); + } catch (error) { + return done(error); + } + done(); + } + }); + }); +}; + exports.testWithMap = function test(name, input, map, result, query, modules) { it(name, function(done) { runLoader(cssLoader, input, map, { diff --git a/test/simpleTest.js b/test/simpleTest.js index 31dc6a86..8ff5a662 100644 --- a/test/simpleTest.js +++ b/test/simpleTest.js @@ -1,6 +1,8 @@ /*globals describe */ +var assert = require('assert'); var test = require("./helpers").test; +var testError = require("./helpers").testError; var testMinimize = require("./helpers").testMinimize; describe("simple", function() { @@ -19,4 +21,15 @@ describe("simple", function() { testMinimize("minimized simple", ".class { a: b c d; }", [ [1, ".class{a:b c d}", ""] ]); + testError("error formatting", ".some {\n invalid css;\n}", function(err) { + assert.equal(err.message, [ + 'Unknown word (2:2)', + '', + ' 1 | .some {', + '> 2 | invalid css;', + ' | ^', + ' 3 | }', + '', + ].join('\n')); + }); });