UNPKG

jscs

Version:

JavaScript Code Style

356 lines (320 loc) 8.82 kB
var chalk = require('chalk'); var TokenAssert = require('./token-assert'); var LINE_SEPARATOR = /\r\n|\r|\n/g; var EMPTY_POS = { line: 1, column: 0 }; /** * Set of errors for specified file. * * @name Errors * @param {JsFile} file */ var Errors = function(file) { this._errorList = []; this._file = file; this._currentRule = ''; /** * @type {TokenAssert} * @public */ this.assert = new TokenAssert(file); this.assert.on('error', this._addError.bind(this)); }; Errors.prototype = { /** * Adds style error to the list * * @param {String | Error} message * @param {cst.types.Element} element * @param {Number} [offset] relative offset */ add: function(message, element, offset) { if (message instanceof Error) { this._addParseError(message); return; } this._addError({ message: message, element: element, offset: offset }); }, /** * Adds style error to the list * * @param {Object} errorInfo */ cast: function(errorInfo) { this._addError(errorInfo); }, /** * Adds parser error to error list. * * @param {Object} errorInfo * @private */ _addParseError: function(errorInfo) { this._errorList.push({ filename: this._file.getFilename(), rule: 'parseError', message: errorInfo.message, line: errorInfo.loc ? errorInfo.loc.line : 1, column: errorInfo.loc ? errorInfo.loc.column : 0 }); }, /** * Adds error to error list. * * @param {Object} errorInfo * @private */ _addError: function(errorInfo) { this._errorList.push({ filename: this._file.getFilename(), rule: this._currentRule, message: this._prepareMessage(errorInfo), element: errorInfo.element, offset: errorInfo.offset, additional: errorInfo.additional, fixed: false, fix: errorInfo.fix }); }, /** * Prepare error message. * * @param {Object} errorInfo * @private */ _prepareMessage: function(errorInfo) { var rule = errorInfo instanceof Error ? 'parseError' : this._currentRule; if (rule) { return rule + ': ' + errorInfo.message; } return errorInfo.message; }, /** * Returns style error list. * * @returns {Object[]} */ getErrorList: function() { return this._errorList; }, /** * Returns filename of file this error list is for. * * @returns {String} */ getFilename: function() { return this._file.getFilename(); }, /** * Returns true if no errors are added. * * @returns {Boolean} */ isEmpty: function() { return this._errorList.length === 0; }, /** * Returns amount of errors added by the rules. * * @returns {Number} */ getValidationErrorCount: function() { return this._errorList.filter(function(error) { return error.rule !== 'parseError' && error.rule !== 'internalError'; }); }, /** * Returns amount of errors added by the rules. * * @returns {Number} */ getErrorCount: function() { return this._errorList.length; }, /** * Strips error list to the specified length. * * @param {Number} length */ stripErrorList: function(length) { this._errorList.splice(length); }, /** * Filters out errors based on the supplied filter function * * @param {Function} filter */ filter: function(filter) { this._errorList = this._errorList.filter(filter); }, /** * @param {TokenIndex} tokenIndex */ calculateErrorLocations: function(tokenIndex) { this._errorList.forEach(function(error) { var pos = Errors.getPosition(error, tokenIndex); error.line = pos.line; error.column = pos.column; }); }, /** * Formats error for further output. * * @param {Object} error * @param {Boolean} [colorize = false] * @returns {String} */ explainError: function(error, colorize) { var lineNumber = error.line - 1; var lines = this._file.getLines(); var result = [ renderLine(lineNumber, lines[lineNumber], colorize), renderPointer(error.column, colorize) ]; var i = lineNumber - 1; var linesAround = 2; while (i >= 0 && i >= (lineNumber - linesAround)) { result.unshift(renderLine(i, lines[i], colorize)); i--; } i = lineNumber + 1; while (i < lines.length && i <= (lineNumber + linesAround)) { result.push(renderLine(i, lines[i], colorize)); i++; } result.unshift(formatErrorMessage(error.message, this.getFilename(), colorize)); return result.join('\n'); }, /** * Sets the current rule so that errors are aware * of which rule triggered them. * * @param {String} rule */ setCurrentRule: function(rule) { this._currentRule = rule; } }; /** * Formats error message header. * * @param {String} message * @param {String} filename * @param {Boolean} colorize * @returns {String} */ function formatErrorMessage(message, filename, colorize) { return (colorize ? chalk.bold(message) : message) + ' at ' + (colorize ? chalk.green(filename) : filename) + ' :'; } /** * Simple util for prepending spaces to the string until it fits specified size. * * @param {String} s * @param {Number} len * @returns {String} */ function prependSpaces(s, len) { while (s.length < len) { s = ' ' + s; } return s; } /** * Renders single line of code in style error formatted output. * * @param {Number} n line number * @param {String} line * @param {Boolean} [colorize = false] * @returns {String} */ function renderLine(n, line, colorize) { // Convert tabs to spaces, so errors in code lines with tabs as indention symbol // could be correctly rendered, plus it will provide less verbose output line = line.replace(/\t/g, ' '); // "n + 1" to print lines in human way (counted from 1) var lineNumber = prependSpaces((n + 1).toString(), 5) + ' |'; return ' ' + (colorize ? chalk.grey(lineNumber) : lineNumber) + line; } /** * Renders pointer: * ---------------^ * * @param {Number} column * @param {Boolean} [colorize = false] * @returns {String} */ function renderPointer(column, colorize) { var res = (new Array(column + 9)).join('-') + '^'; return colorize ? chalk.grey(res) : res; } /** * Get position of the element * * @param {Error} [error] * @param {TokenIndex} [tokenIndex] * @return {Object} */ Errors.getPosition = function(error, tokenIndex) { var element = error.element; var offset = error.offset; var rule = error.rule; if (!element) { return EMPTY_POS; } if (offset === undefined) { // TODO: probably should be generalized if (rule === 'validateQuoteMarks') { offset = 0; } else if (element.getSourceCodeLength() === 1) { offset = 0; } else { offset = (element.getNewlineCount() === 0 && Math.ceil(element.getSourceCodeLength() / 2)) || 0; } } var pos = tokenIndex ? tokenIndex.getElementLoc(element) : element.getLoc().start; if (!pos) { return EMPTY_POS; } if (offset === 0) { return pos; } var newlineCount = element.getNewlineCount(); if (newlineCount > 0) { var code = element.getSourceCode(); LINE_SEPARATOR.lastIndex = 0; var lineOffset = 0; var match; var previousOffset = 0; var firstLineColumnOffset = pos.column; while ((match = LINE_SEPARATOR.exec(code)) !== null) { var currentOffset = match.index; if (offset <= currentOffset) { return { line: pos.line + lineOffset, column: firstLineColumnOffset + offset - previousOffset }; } previousOffset = currentOffset + match[0].length; firstLineColumnOffset = 0; lineOffset++; } return { line: pos.line + newlineCount, column: offset - previousOffset }; } else { return { line: pos.line, column: pos.column + offset }; } }; module.exports = Errors;