UNPKG

kitchensink

Version:

Dispatch's awesome components and style guide

271 lines (239 loc) 6.74 kB
var assert = require('assert'); var chalk = require('chalk'); var TokenAssert = require('./token-assert'); /** * Set of errors for specified file. * * @name Errors * @param {JsFile} file * @param {Boolean} verbose */ var Errors = function(file, verbose) { this._errorList = []; this._file = file; this._currentRule = ''; this._verbose = verbose || false; /** * @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} message * @param {Number|Object} line * @param {Number} [column] optional if line is an object */ add: function(message, line, column) { if (typeof line === 'object') { column = line.column; line = line.line; } var errorInfo = { message: message, line: line, column: column }; this._validateInput(errorInfo); this._addError(errorInfo); }, /** * Adds style error to the list * * @param {Object} errorInfo */ cast: function(errorInfo) { var additional = errorInfo.additional; assert(typeof additional !== undefined, '`additional` argument should not be empty'); this._addError(errorInfo); }, _validateInput: function(errorInfo) { var line = errorInfo.line; var column = errorInfo.column; // line and column numbers should be explicit assert(typeof line === 'number' && line > 0, 'Unable to add an error, `line` should be a number greater than 0 but ' + line + ' given'); assert(typeof column === 'number' && column >= 0, 'Unable to add an error, `column` should be a positive number but ' + column + ' given'); }, /** * Adds error to error list. * * @param {Object} errorInfo * @private */ _addError: function(errorInfo) { if (!this._file.isEnabledRule(this._currentRule, errorInfo.line)) { return; } this._validateInput(errorInfo); this._errorList.push({ filename: this._file.getFilename(), rule: this._currentRule, message: this._prepareMessage(errorInfo), line: errorInfo.line, column: errorInfo.column, additional: errorInfo.additional, fixed: errorInfo.fixed }); }, /** * Prepare error message. * * @param {Object} errorInfo * @private */ _prepareMessage: function(errorInfo) { if (this._verbose && this._currentRule) { return this._currentRule + ': ' + 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} */ 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); }, /** * 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; } module.exports = Errors;