Permalink
Cannot retrieve contributors at this time
245 lines (229 sloc)
6.69 KB
| let supportsColor = require('supports-color') | |
| let chalk = require('chalk') | |
| let terminalHighlight = require('./terminal-highlight') | |
| /** | |
| * The CSS parser throws this error for broken CSS. | |
| * | |
| * Custom parsers can throw this error for broken custom syntax using | |
| * the {@link Node#error} method. | |
| * | |
| * PostCSS will use the input source map to detect the original error location. | |
| * If you wrote a Sass file, compiled it to CSS and then parsed it with PostCSS, | |
| * PostCSS will show the original position in the Sass file. | |
| * | |
| * If you need the position in the PostCSS input | |
| * (e.g., to debug the previous compiler), use `error.input.file`. | |
| * | |
| * @example | |
| * // Catching and checking syntax error | |
| * try { | |
| * postcss.parse('a{') | |
| * } catch (error) { | |
| * if (error.name === 'CssSyntaxError') { | |
| * error //=> CssSyntaxError | |
| * } | |
| * } | |
| * | |
| * @example | |
| * // Raising error from plugin | |
| * throw node.error('Unknown variable', { plugin: 'postcss-vars' }) | |
| */ | |
| class CssSyntaxError extends Error { | |
| /** | |
| * @param {string} message Error message. | |
| * @param {number} [line] Source line of the error. | |
| * @param {number} [column] Source column of the error. | |
| * @param {string} [source] Source code of the broken file. | |
| * @param {string} [file] Absolute path to the broken file. | |
| * @param {string} [plugin] PostCSS plugin name, if error came from plugin. | |
| */ | |
| constructor (message, line, column, source, file, plugin) { | |
| super(message) | |
| /** | |
| * Always equal to `'CssSyntaxError'`. You should always check error type | |
| * by `error.name === 'CssSyntaxError'` | |
| * instead of `error instanceof CssSyntaxError`, | |
| * because npm could have several PostCSS versions. | |
| * | |
| * @type {string} | |
| * | |
| * @example | |
| * if (error.name === 'CssSyntaxError') { | |
| * error //=> CssSyntaxError | |
| * } | |
| */ | |
| this.name = 'CssSyntaxError' | |
| /** | |
| * Error message. | |
| * | |
| * @type {string} | |
| * | |
| * @example | |
| * error.message //=> 'Unclosed block' | |
| */ | |
| this.reason = message | |
| if (file) { | |
| /** | |
| * Absolute path to the broken file. | |
| * | |
| * @type {string} | |
| * | |
| * @example | |
| * error.file //=> 'a.sass' | |
| * error.input.file //=> 'a.css' | |
| */ | |
| this.file = file | |
| } | |
| if (source) { | |
| /** | |
| * Source code of the broken file. | |
| * | |
| * @type {string} | |
| * | |
| * @example | |
| * error.source //=> 'a { b {} }' | |
| * error.input.column //=> 'a b { }' | |
| */ | |
| this.source = source | |
| } | |
| if (plugin) { | |
| /** | |
| * Plugin name, if error came from plugin. | |
| * | |
| * @type {string} | |
| * | |
| * @example | |
| * error.plugin //=> 'postcss-vars' | |
| */ | |
| this.plugin = plugin | |
| } | |
| if (typeof line !== 'undefined' && typeof column !== 'undefined') { | |
| /** | |
| * Source line of the error. | |
| * | |
| * @type {number} | |
| * | |
| * @example | |
| * error.line //=> 2 | |
| * error.input.line //=> 4 | |
| */ | |
| this.line = line | |
| /** | |
| * Source column of the error. | |
| * | |
| * @type {number} | |
| * | |
| * @example | |
| * error.column //=> 1 | |
| * error.input.column //=> 4 | |
| */ | |
| this.column = column | |
| } | |
| this.setMessage() | |
| if (Error.captureStackTrace) { | |
| Error.captureStackTrace(this, CssSyntaxError) | |
| } | |
| } | |
| setMessage () { | |
| /** | |
| * Full error text in the GNU error format | |
| * with plugin, file, line and column. | |
| * | |
| * @type {string} | |
| * | |
| * @example | |
| * error.message //=> 'a.css:1:1: Unclosed block' | |
| */ | |
| this.message = this.plugin ? this.plugin + ': ' : '' | |
| this.message += this.file ? this.file : '<css input>' | |
| if (typeof this.line !== 'undefined') { | |
| this.message += ':' + this.line + ':' + this.column | |
| } | |
| this.message += ': ' + this.reason | |
| } | |
| /** | |
| * Returns a few lines of CSS source that caused the error. | |
| * | |
| * If the CSS has an input source map without `sourceContent`, | |
| * this method will return an empty string. | |
| * | |
| * @param {boolean} [color] Whether arrow will be colored red by terminal | |
| * color codes. By default, PostCSS will detect | |
| * color support by `process.stdout.isTTY` | |
| * and `process.env.NODE_DISABLE_COLORS`. | |
| * | |
| * @example | |
| * error.showSourceCode() //=> " 4 | } | |
| * // 5 | a { | |
| * // > 6 | bad | |
| * // | ^ | |
| * // 7 | } | |
| * // 8 | b {" | |
| * | |
| * @return {string} Few lines of CSS source that caused the error. | |
| */ | |
| showSourceCode (color) { | |
| if (!this.source) return '' | |
| let css = this.source | |
| if (terminalHighlight) { | |
| if (typeof color === 'undefined') color = supportsColor.stdout | |
| if (color) css = terminalHighlight(css) | |
| } | |
| let lines = css.split(/\r?\n/) | |
| let start = Math.max(this.line - 3, 0) | |
| let end = Math.min(this.line + 2, lines.length) | |
| let maxWidth = String(end).length | |
| function mark (text) { | |
| if (color && chalk.red) { | |
| return chalk.red.bold(text) | |
| } | |
| return text | |
| } | |
| function aside (text) { | |
| if (color && chalk.gray) { | |
| return chalk.gray(text) | |
| } | |
| return text | |
| } | |
| return lines.slice(start, end).map((line, index) => { | |
| let number = start + 1 + index | |
| let gutter = ' ' + (' ' + number).slice(-maxWidth) + ' | ' | |
| if (number === this.line) { | |
| let spacing = aside(gutter.replace(/\d/g, ' ')) + | |
| line.slice(0, this.column - 1).replace(/[^\t]/g, ' ') | |
| return mark('>') + aside(gutter) + line + '\n ' + spacing + mark('^') | |
| } | |
| return ' ' + aside(gutter) + line | |
| }).join('\n') | |
| } | |
| /** | |
| * Returns error position, message and source code of the broken part. | |
| * | |
| * @example | |
| * error.toString() //=> "CssSyntaxError: app.css:1:1: Unclosed block | |
| * // > 1 | a { | |
| * // | ^" | |
| * | |
| * @return {string} Error position, message and source code. | |
| */ | |
| toString () { | |
| let code = this.showSourceCode() | |
| if (code) { | |
| code = '\n\n' + code + '\n' | |
| } | |
| return this.name + ': ' + this.message + code | |
| } | |
| /** | |
| * @memberof CssSyntaxError# | |
| * @member {Input} input Input object with PostCSS internal information | |
| * about input file. If input has source map | |
| * from previous tool, PostCSS will use origin | |
| * (for example, Sass) source. You can use this | |
| * object to get PostCSS input source. | |
| * | |
| * @example | |
| * error.input.file //=> 'a.css' | |
| * error.file //=> 'a.sass' | |
| */ | |
| } | |
| module.exports = CssSyntaxError |