UNPKG

9.68 kBJavaScriptView Raw
1var Errors = require('./errors');
2var JsFile = require('./js-file');
3var TokenIndex = require('./token-index');
4var Configuration = require('./config/configuration');
5
6var MAX_FIX_ATTEMPTS = 5;
7
8function getInternalErrorMessage(rule, e) {
9 return 'Error running rule ' + rule + ': ' +
10 'This is an issue with JSCS and not your codebase.\n' +
11 'Please file an issue (with the stack trace below) at: ' +
12 'https://github.com/jscs-dev/node-jscs/issues/new\n' + e.stack;
13}
14
15/**
16 * Starts Code Style checking process.
17 *
18 * @name StringChecker
19 */
20var StringChecker = function() {
21 this._configuredRules = [];
22
23 this._errorsFound = 0;
24 this._maxErrorsExceeded = false;
25
26 this._configuration = this._createConfiguration();
27 this._configuration.registerDefaultPresets();
28};
29
30StringChecker.prototype = {
31 /**
32 * Registers single Code Style checking rule.
33 *
34 * @param {Rule} rule
35 */
36 registerRule: function(rule) {
37 this._configuration.registerRule(rule);
38 },
39
40 /**
41 * Registers built-in Code Style checking rules.
42 */
43 registerDefaultRules: function() {
44 this._configuration.registerDefaultRules();
45 },
46
47 /**
48 * Get processed config.
49 *
50 * @return {Object}
51 */
52 getProcessedConfig: function() {
53 return this._configuration.getProcessedConfig();
54 },
55
56 /**
57 * Loads configuration from JS Object. Activates and configures required rules.
58 *
59 * @param {Object} config
60 */
61 configure: function(config) {
62 this._configuration.load(config);
63
64 this._configuredRules = this._configuration.getConfiguredRules();
65 this._maxErrors = this._configuration.getMaxErrors();
66 },
67
68 /**
69 * Checks file provided with a string.
70 *
71 * @param {String} source
72 * @param {String} [filename='input']
73 * @returns {Errors}
74 */
75 checkString: function(source, filename) {
76 filename = filename || 'input';
77
78 var file = this._createJsFileInstance(filename, source);
79
80 var errors = new Errors(file);
81
82 file.getParseErrors().forEach(function(parseError) {
83 if (!this._maxErrorsExceeded) {
84 this._addParseError(errors, parseError, file);
85 }
86 }, this);
87
88 if (!file._program || file._program.firstChild.type === 'EOF') {
89 return errors;
90 }
91
92 this._checkJsFile(file, errors);
93
94 return errors;
95 },
96
97 /**
98 * Apply fix for common errors.
99 *
100 * @param {Error} error
101 * @return {Boolean} whether the correction was carried out
102 * @private
103 */
104 _fixCommonError: function(error) {
105 if (error.fix) {
106 // "error.fixed = true" should go first, so rule can
107 // decide for itself (with "error.fixed = false")
108 // if it can fix this particular error
109 error.fixed = true;
110 error.fix();
111 }
112
113 return !!error.fixed;
114 },
115
116 /**
117 * Apply fix for specific error.
118 *
119 * @param {JsFile} file
120 * @param {Error} error
121 * @return {Boolean} whether the correction was carried out
122 * @private
123 */
124 _fixSpecificError: function(file, error) {
125 var configuration = this.getConfiguration();
126 var instance = configuration.getConfiguredRule(error.rule);
127
128 if (instance && instance._fix) {
129 // "error.fixed = true" should go first, so rule can
130 // decide for itself (with "error.fixed = false")
131 // if it can fix this particular error
132 error.fixed = true;
133 instance._fix(file, error);
134 }
135
136 return !!error.fixed;
137 },
138
139 /**
140 * Apply specific and common fixes.
141 *
142 * @param {JsFile} file
143 * @param {Errors} errors
144 * @protected
145 */
146 _fixJsFile: function(file, errors) {
147 errors.getErrorList().forEach(function(error) {
148 if (error.fixed) {
149 return;
150 }
151
152 try {
153 // Try to apply fixes for common errors
154 var isFixed = this._fixCommonError(error);
155
156 // Apply specific fix
157 if (!isFixed) {
158 this._fixSpecificError(file, error);
159 }
160 } catch (e) {
161 error.fixed = false;
162 errors.add(
163 getInternalErrorMessage(error.rule, e),
164 file.getProgram()
165 );
166 }
167 }, this);
168 },
169
170 /**
171 * Checks a file specified using JsFile instance.
172 * Fills Errors instance with validation errors.
173 *
174 * @param {JsFile} file
175 * @param {Errors} errors
176 * @protected
177 */
178 _checkJsFile: function(file, errors) {
179 if (this._maxErrorsExceeded) {
180 return;
181 }
182
183 var errorFilter = this._configuration.getErrorFilter();
184
185 this._configuredRules.forEach(function(rule) {
186 errors.setCurrentRule(rule.getOptionName());
187
188 try {
189 rule.check(file, errors);
190 } catch (e) {
191 errors.setCurrentRule('internalError');
192 errors.add(getInternalErrorMessage(rule.getOptionName(), e), file.getProgram());
193 }
194 }, this);
195
196 this._configuration.getUnsupportedRuleNames().forEach(function(rulename) {
197 errors.add('Unsupported rule: ' + rulename, file.getProgram());
198 });
199
200 var program = file.getProgram();
201 var tokenIndex = new TokenIndex(program.getFirstToken());
202 errors.calculateErrorLocations(tokenIndex);
203 errors.filter(function(error) {
204 if (error.element) {
205 return tokenIndex.isRuleEnabled(error.rule, error.element);
206 } else {
207 return true;
208 }
209 });
210
211 // sort errors list to show errors as they appear in source
212 errors.getErrorList().sort(function(a, b) {
213 return (a.line - b.line) || (a.column - b.column);
214 });
215
216 if (errorFilter) {
217 errors.filter(errorFilter);
218 }
219
220 if (this.maxErrorsEnabled()) {
221 if (this._maxErrors === -1 || this._maxErrors === null) {
222 this._maxErrorsExceeded = false;
223
224 } else {
225 this._maxErrorsExceeded = this._errorsFound + errors.getErrorCount() > this._maxErrors;
226 errors.stripErrorList(Math.max(0, this._maxErrors - this._errorsFound));
227 }
228 }
229
230 this._errorsFound += errors.getErrorCount();
231 },
232
233 /**
234 * Adds parse error to the error list.
235 *
236 * @param {Errors} errors
237 * @param {Error} parseError
238 * @param {JsFile} file
239 * @private
240 */
241 _addParseError: function(errors, parseError, file) {
242 if (this._maxErrorsExceeded) {
243 return;
244 }
245
246 errors.add(parseError, file.getProgram());
247
248 if (this.maxErrorsEnabled()) {
249 this._errorsFound += 1;
250 this._maxErrorsExceeded = this._errorsFound >= this._maxErrors;
251 }
252 },
253
254 /**
255 * Creates configured JsFile instance.
256 *
257 * @param {String} filename
258 * @param {String} source
259 * @private
260 */
261 _createJsFileInstance: function(filename, source) {
262 return new JsFile({
263 filename: filename,
264 source: source,
265 es3: this._configuration.isES3Enabled()
266 });
267 },
268
269 /**
270 * Checks and fix file provided with a string.
271 *
272 * @param {String} source
273 * @param {String} [filename='input']
274 * @returns {{output: String, errors: Errors}}
275 */
276 fixString: function(source, filename) {
277 filename = filename || 'input';
278
279 var file = this._createJsFileInstance(filename, source);
280 var errors = new Errors(file);
281
282 var parseErrors = file.getParseErrors();
283 if (parseErrors.length > 0) {
284 parseErrors.forEach(function(parseError) {
285 this._addParseError(errors, parseError, file);
286 }, this);
287
288 return {output: source, errors: errors};
289 } else {
290 var attempt = 0;
291 do {
292
293 // Fill in errors list
294 this._checkJsFile(file, errors);
295
296 // Apply fixes
297 this._fixJsFile(file, errors);
298
299 var hasFixes = errors.getErrorList().some(function(err) {
300 return err.fixed;
301 });
302
303 if (!hasFixes) {
304 break;
305 }
306
307 file = this._createJsFileInstance(filename, file.render());
308 errors = new Errors(file);
309 attempt++;
310 } while (attempt < MAX_FIX_ATTEMPTS);
311
312 return {output: file.getSource(), errors: errors};
313 }
314 },
315
316 /**
317 * Returns `true` if max erros limit is enabled.
318 *
319 * @returns {Boolean}
320 */
321 maxErrorsEnabled: function() {
322 return this._maxErrors !== null && this._maxErrors !== -1;
323 },
324
325 /**
326 * Returns `true` if error count exceeded `maxErrors` option value.
327 *
328 * @returns {Boolean}
329 */
330 maxErrorsExceeded: function() {
331 return this._maxErrorsExceeded;
332 },
333
334 /**
335 * Returns new configuration instance.
336 *
337 * @protected
338 * @returns {Configuration}
339 */
340 _createConfiguration: function() {
341 return new Configuration();
342 },
343
344 /**
345 * Returns current configuration instance.
346 *
347 * @returns {Configuration}
348 */
349 getConfiguration: function() {
350 return this._configuration;
351 }
352};
353
354module.exports = StringChecker;