UNPKG

6.74 kBJavaScriptView Raw
1var assert = require('assert');
2var chalk = require('chalk');
3var TokenAssert = require('./token-assert');
4
5/**
6 * Set of errors for specified file.
7 *
8 * @name Errors
9 * @param {JsFile} file
10 * @param {Boolean} verbose
11 */
12var Errors = function(file, verbose) {
13 this._errorList = [];
14 this._file = file;
15 this._currentRule = '';
16 this._verbose = verbose || false;
17
18 /**
19 * @type {TokenAssert}
20 * @public
21 */
22 this.assert = new TokenAssert(file);
23 this.assert.on('error', this._addError.bind(this));
24};
25
26Errors.prototype = {
27 /**
28 * Adds style error to the list
29 *
30 * @param {String} message
31 * @param {Number|Object} line
32 * @param {Number} [column] optional if line is an object
33 */
34 add: function(message, line, column) {
35 if (typeof line === 'object') {
36 column = line.column;
37 line = line.line;
38 }
39
40 var errorInfo = {
41 message: message,
42 line: line,
43 column: column
44 };
45
46 this._validateInput(errorInfo);
47 this._addError(errorInfo);
48 },
49
50 /**
51 * Adds style error to the list
52 *
53 * @param {Object} errorInfo
54 */
55 cast: function(errorInfo) {
56 var additional = errorInfo.additional;
57
58 assert(typeof additional !== undefined,
59 '`additional` argument should not be empty');
60
61 this._addError(errorInfo);
62 },
63
64 _validateInput: function(errorInfo) {
65 var line = errorInfo.line;
66 var column = errorInfo.column;
67
68 // line and column numbers should be explicit
69 assert(typeof line === 'number' && line > 0,
70 'Unable to add an error, `line` should be a number greater than 0 but ' +
71 line + ' given');
72
73 assert(typeof column === 'number' && column >= 0,
74 'Unable to add an error, `column` should be a positive number but ' +
75 column + ' given');
76 },
77
78 /**
79 * Adds error to error list.
80 *
81 * @param {Object} errorInfo
82 * @private
83 */
84 _addError: function(errorInfo) {
85 if (!this._file.isEnabledRule(this._currentRule, errorInfo.line)) {
86 return;
87 }
88
89 this._validateInput(errorInfo);
90
91 this._errorList.push({
92 filename: this._file.getFilename(),
93 rule: this._currentRule,
94 message: this._prepareMessage(errorInfo),
95 line: errorInfo.line,
96 column: errorInfo.column,
97 additional: errorInfo.additional,
98 fixed: errorInfo.fixed
99 });
100 },
101
102 /**
103 * Prepare error message.
104 *
105 * @param {Object} errorInfo
106 * @private
107 */
108 _prepareMessage: function(errorInfo) {
109 if (this._verbose && this._currentRule) {
110 return this._currentRule + ': ' + errorInfo.message;
111 }
112
113 return errorInfo.message;
114 },
115
116 /**
117 * Returns style error list.
118 *
119 * @returns {Object[]}
120 */
121 getErrorList: function() {
122 return this._errorList;
123 },
124
125 /**
126 * Returns filename of file this error list is for.
127 *
128 * @returns {String}
129 */
130 getFilename: function() {
131 return this._file.getFilename();
132 },
133
134 /**
135 * Returns true if no errors are added.
136 *
137 * @returns {Boolean}
138 */
139 isEmpty: function() {
140 return this._errorList.length === 0;
141 },
142
143 /**
144 * Returns amount of errors added by the rules.
145 *
146 * @returns {Number}
147 */
148 getErrorCount: function() {
149 return this._errorList.length;
150 },
151
152 /**
153 * Strips error list to the specified length.
154 *
155 * @param {Number} length
156 */
157 stripErrorList: function(length) {
158 this._errorList.splice(length);
159 },
160
161 /**
162 * Filters out errors based on the supplied filter function
163 *
164 * @param {Function} filter
165 */
166 filter: function(filter) {
167 this._errorList = this._errorList.filter(filter);
168 },
169
170 /**
171 * Formats error for further output.
172 *
173 * @param {Object} error
174 * @param {Boolean} [colorize = false]
175 * @returns {String}
176 */
177 explainError: function(error, colorize) {
178 var lineNumber = error.line - 1;
179 var lines = this._file.getLines();
180 var result = [
181 renderLine(lineNumber, lines[lineNumber], colorize),
182 renderPointer(error.column, colorize)
183 ];
184 var i = lineNumber - 1;
185 var linesAround = 2;
186 while (i >= 0 && i >= (lineNumber - linesAround)) {
187 result.unshift(renderLine(i, lines[i], colorize));
188 i--;
189 }
190 i = lineNumber + 1;
191 while (i < lines.length && i <= (lineNumber + linesAround)) {
192 result.push(renderLine(i, lines[i], colorize));
193 i++;
194 }
195 result.unshift(formatErrorMessage(error.message, this.getFilename(), colorize));
196 return result.join('\n');
197 },
198
199 /**
200 * Sets the current rule so that errors are aware
201 * of which rule triggered them.
202 *
203 * @param {String} rule
204 */
205 setCurrentRule: function(rule) {
206 this._currentRule = rule;
207 }
208
209};
210
211/**
212 * Formats error message header.
213 *
214 * @param {String} message
215 * @param {String} filename
216 * @param {Boolean} colorize
217 * @returns {String}
218 */
219function formatErrorMessage(message, filename, colorize) {
220 return (colorize ? chalk.bold(message) : message) +
221 ' at ' +
222 (colorize ? chalk.green(filename) : filename) + ' :';
223}
224
225/**
226 * Simple util for prepending spaces to the string until it fits specified size.
227 *
228 * @param {String} s
229 * @param {Number} len
230 * @returns {String}
231 */
232function prependSpaces(s, len) {
233 while (s.length < len) {
234 s = ' ' + s;
235 }
236 return s;
237}
238
239/**
240 * Renders single line of code in style error formatted output.
241 *
242 * @param {Number} n line number
243 * @param {String} line
244 * @param {Boolean} [colorize = false]
245 * @returns {String}
246 */
247function renderLine(n, line, colorize) {
248 // Convert tabs to spaces, so errors in code lines with tabs as indention symbol
249 // could be correctly rendered, plus it will provide less verbose output
250 line = line.replace(/\t/g, ' ');
251
252 // "n + 1" to print lines in human way (counted from 1)
253 var lineNumber = prependSpaces((n + 1).toString(), 5) + ' |';
254 return ' ' + (colorize ? chalk.grey(lineNumber) : lineNumber) + line;
255}
256
257/**
258 * Renders pointer:
259 * ---------------^
260 *
261 * @param {Number} column
262 * @param {Boolean} [colorize = false]
263 * @returns {String}
264 */
265function renderPointer(column, colorize) {
266 var res = (new Array(column + 9)).join('-') + '^';
267 return colorize ? chalk.grey(res) : res;
268}
269
270module.exports = Errors;