167 lines
5.3 KiB
JavaScript
167 lines
5.3 KiB
JavaScript
import * as utils from './utils.js';
|
|
|
|
const anonymousFunc = /(<anonymous>|Function):(\d+):(\d+)/;
|
|
|
|
/**
|
|
* 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} fileContentMap - An object with file contents in 'contents' property (like importManager) @todo - move to fileManager?
|
|
* @param {string} [currentFilename]
|
|
*/
|
|
const LessError = function(e, fileContentMap, currentFilename) {
|
|
Error.call(this);
|
|
|
|
const filename = e.filename || currentFilename;
|
|
|
|
this.message = e.message;
|
|
this.stack = e.stack;
|
|
|
|
// Set type early so it's always available, even if fileContentMap is missing
|
|
this.type = e.type || 'Syntax';
|
|
|
|
if (fileContentMap && filename) {
|
|
const input = fileContentMap.contents[filename];
|
|
const loc = utils.getLocation(e.index, input);
|
|
var line = loc.line;
|
|
const col = loc.column;
|
|
const callLine = e.call && utils.getLocation(e.call, input).line;
|
|
const lines = input ? input.split('\n') : '';
|
|
|
|
this.filename = filename;
|
|
this.index = e.index;
|
|
this.line = typeof line === 'number' ? line + 1 : null;
|
|
this.column = col;
|
|
|
|
if (!this.line && this.stack) {
|
|
const found = this.stack.match(anonymousFunc);
|
|
|
|
/**
|
|
* We have to figure out how this environment stringifies anonymous functions
|
|
* so we can correctly map plugin errors.
|
|
*
|
|
* Note, in Node 8, the output of anonymous funcs varied based on parameters
|
|
* being present or not, so we inject dummy params.
|
|
*/
|
|
const func = new Function('a', 'throw new Error()');
|
|
let lineAdjust = 0;
|
|
try {
|
|
func();
|
|
} catch (e) {
|
|
const match = e.stack.match(anonymousFunc);
|
|
lineAdjust = 1 - parseInt(match[2]);
|
|
}
|
|
|
|
if (found) {
|
|
if (found[2]) {
|
|
this.line = parseInt(found[2]) + lineAdjust;
|
|
}
|
|
if (found[3]) {
|
|
this.column = parseInt(found[3]);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.callLine = callLine + 1;
|
|
this.callExtract = lines[callLine];
|
|
|
|
this.extract = [
|
|
lines[this.line - 2],
|
|
lines[this.line - 1],
|
|
lines[this.line]
|
|
];
|
|
}
|
|
|
|
};
|
|
|
|
if (typeof Object.create === 'undefined') {
|
|
const F = function () {};
|
|
F.prototype = Error.prototype;
|
|
LessError.prototype = new F();
|
|
} else {
|
|
LessError.prototype = Object.create(Error.prototype);
|
|
}
|
|
|
|
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 || {};
|
|
const isWarning = (this.type ?? '').toLowerCase().includes('warning');
|
|
const type = isWarning ? this.type : `${this.type}Error`;
|
|
const color = isWarning ? 'yellow' : 'red';
|
|
|
|
let message = '';
|
|
const extract = this.extract || [];
|
|
let error = [];
|
|
let stylize = function (str) { return str; };
|
|
if (options.stylize) {
|
|
const type = typeof options.stylize;
|
|
if (type !== 'function') {
|
|
throw Error(`options.stylize should be a function, got a ${type}!`);
|
|
}
|
|
stylize = options.stylize;
|
|
}
|
|
|
|
if (this.line !== null) {
|
|
if (!isWarning && typeof extract[0] === 'string') {
|
|
error.push(stylize(`${this.line - 1} ${extract[0]}`, 'grey'));
|
|
}
|
|
|
|
if (typeof extract[1] === 'string') {
|
|
let errorTxt = `${this.line} `;
|
|
if (extract[1]) {
|
|
errorTxt += extract[1].slice(0, this.column) +
|
|
stylize(stylize(stylize(extract[1].slice(this.column, this.column + 1), 'bold') +
|
|
extract[1].slice(this.column + 1), 'red'), 'inverse');
|
|
}
|
|
error.push(errorTxt);
|
|
}
|
|
|
|
if (!isWarning && typeof extract[2] === 'string') {
|
|
error.push(stylize(`${this.line + 1} ${extract[2]}`, 'grey'));
|
|
}
|
|
error = `${error.join('\n') + stylize('', 'reset')}\n`;
|
|
}
|
|
|
|
message += stylize(`${type}: ${this.message}`, color);
|
|
if (this.filename) {
|
|
message += stylize(' in ', color) + this.filename;
|
|
}
|
|
if (this.line) {
|
|
message += stylize(` on line ${this.line}, column ${this.column + 1}:`, 'grey');
|
|
}
|
|
|
|
message += `\n${error}`;
|
|
|
|
if (this.callLine) {
|
|
message += `${stylize('from ', color) + (this.filename || '')}/n`;
|
|
message += `${stylize(this.callLine, 'grey')} ${this.callExtract}/n`;
|
|
}
|
|
|
|
return message;
|
|
};
|
|
|
|
export default LessError;
|