eslint 4.18.0 → 4.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/README.md +2 -2
- package/conf/environments.js +3 -1
- package/conf/eslint-recommended.js +0 -0
- package/lib/ast-utils.js +25 -29
- package/lib/cli-engine.js +29 -28
- package/lib/code-path-analysis/code-path-state.js +5 -5
- package/lib/code-path-analysis/code-path.js +11 -6
- package/lib/code-path-analysis/fork-context.js +10 -12
- package/lib/config/config-file.js +20 -11
- package/lib/config/config-ops.js +8 -10
- package/lib/config/config-rule.js +2 -3
- package/lib/config/plugins.js +20 -0
- package/lib/config.js +7 -8
- package/lib/file-finder.js +9 -10
- package/lib/ignored-paths.js +4 -4
- package/lib/linter.js +397 -406
- package/lib/load-rules.js +6 -7
- package/lib/rules/accessor-pairs.js +4 -4
- package/lib/rules/array-callback-return.js +8 -6
- package/lib/rules/array-element-newline.js +4 -4
- package/lib/rules/curly.js +11 -10
- package/lib/rules/generator-star-spacing.js +1 -2
- package/lib/rules/indent-legacy.js +7 -10
- package/lib/rules/indent.js +51 -29
- package/lib/rules/keyword-spacing.js +6 -18
- package/lib/rules/max-len.js +12 -5
- package/lib/rules/no-await-in-loop.js +1 -1
- package/lib/rules/no-buffer-constructor.js +1 -1
- package/lib/rules/no-control-regex.js +51 -72
- package/lib/rules/no-else-return.js +7 -6
- package/lib/rules/no-empty-character-class.js +1 -1
- package/lib/rules/no-eval.js +7 -8
- package/lib/rules/no-extra-parens.js +5 -4
- package/lib/rules/no-implicit-coercion.js +6 -9
- package/lib/rules/no-invalid-regexp.js +53 -36
- package/lib/rules/no-irregular-whitespace.js +1 -1
- package/lib/rules/no-loop-func.js +9 -11
- package/lib/rules/no-magic-numbers.js +17 -10
- package/lib/rules/no-return-assign.js +4 -3
- package/lib/rules/no-unexpected-multiline.js +1 -1
- package/lib/rules/no-unsafe-finally.js +7 -4
- package/lib/rules/no-useless-escape.js +2 -2
- package/lib/rules/no-useless-return.js +10 -9
- package/lib/rules/no-var.js +8 -8
- package/lib/rules/object-curly-newline.js +2 -1
- package/lib/rules/one-var.js +140 -97
- package/lib/rules/padding-line-between-statements.js +6 -4
- package/lib/rules/prefer-arrow-callback.js +5 -4
- package/lib/rules/prefer-template.js +5 -3
- package/lib/rules/space-unary-ops.js +1 -3
- package/lib/rules/spaced-comment.js +3 -7
- package/lib/rules/template-tag-spacing.js +0 -0
- package/lib/rules/valid-jsdoc.js +6 -6
- package/lib/rules/vars-on-top.js +7 -17
- package/lib/timing.js +3 -5
- package/lib/util/glob-util.js +11 -11
- package/lib/util/interpolate.js +5 -1
- package/lib/util/naming.js +11 -10
- package/lib/util/npm-util.js +4 -6
- package/lib/util/path-util.js +6 -8
- package/lib/util/source-code-util.js +23 -26
- package/lib/util/source-code.js +4 -3
- package/package.json +4 -3
- package/conf/default-config-options.js +0 -29
package/lib/linter.js
CHANGED
@@ -14,7 +14,6 @@ const eslintScope = require("eslint-scope"),
|
|
14
14
|
levn = require("levn"),
|
15
15
|
lodash = require("lodash"),
|
16
16
|
blankScriptAST = require("../conf/blank-script.json"),
|
17
|
-
defaultConfig = require("../conf/default-config-options.js"),
|
18
17
|
CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
|
19
18
|
ConfigOps = require("./config/config-ops"),
|
20
19
|
validator = require("./config/config-validator"),
|
@@ -33,6 +32,7 @@ const eslintScope = require("eslint-scope"),
|
|
33
32
|
|
34
33
|
const debug = require("debug")("eslint:linter");
|
35
34
|
const MAX_AUTOFIX_PASSES = 10;
|
35
|
+
const DEFAULT_PARSER_NAME = "espree";
|
36
36
|
|
37
37
|
//------------------------------------------------------------------------------
|
38
38
|
// Typedefs
|
@@ -71,25 +71,25 @@ function parseBooleanConfig(string, comment) {
|
|
71
71
|
const items = {};
|
72
72
|
|
73
73
|
// Collapse whitespace around `:` and `,` to make parsing easier
|
74
|
-
|
74
|
+
const trimmedString = string.replace(/\s*([:,])\s*/g, "$1");
|
75
75
|
|
76
|
-
|
76
|
+
trimmedString.split(/\s|,+/).forEach(name => {
|
77
77
|
if (!name) {
|
78
78
|
return;
|
79
79
|
}
|
80
80
|
const pos = name.indexOf(":");
|
81
|
-
let value;
|
82
81
|
|
83
|
-
if (pos
|
84
|
-
|
85
|
-
|
82
|
+
if (pos === -1) {
|
83
|
+
items[name] = {
|
84
|
+
value: false,
|
85
|
+
comment
|
86
|
+
};
|
87
|
+
} else {
|
88
|
+
items[name.slice(0, pos)] = {
|
89
|
+
value: name.slice(pos + 1) === "true",
|
90
|
+
comment
|
91
|
+
};
|
86
92
|
}
|
87
|
-
|
88
|
-
items[name] = {
|
89
|
-
value: (value === "true"),
|
90
|
-
comment
|
91
|
-
};
|
92
|
-
|
93
93
|
});
|
94
94
|
return items;
|
95
95
|
}
|
@@ -127,9 +127,10 @@ function parseJsonConfig(string, location) {
|
|
127
127
|
* But we are supporting that. So this is a fallback for that.
|
128
128
|
*/
|
129
129
|
items = {};
|
130
|
-
|
130
|
+
const normalizedString = string.replace(/([a-zA-Z0-9\-/]+):/g, "\"$1\":").replace(/(]|[0-9])\s+(?=")/, "$1,");
|
131
|
+
|
131
132
|
try {
|
132
|
-
items = JSON.parse(`{${
|
133
|
+
items = JSON.parse(`{${normalizedString}}`);
|
133
134
|
} catch (ex) {
|
134
135
|
return {
|
135
136
|
success: false,
|
@@ -138,7 +139,7 @@ function parseJsonConfig(string, location) {
|
|
138
139
|
fatal: true,
|
139
140
|
severity: 2,
|
140
141
|
source: null,
|
141
|
-
message: `Failed to parse JSON from '${
|
142
|
+
message: `Failed to parse JSON from '${normalizedString}': ${ex.message}`,
|
142
143
|
line: location.start.line,
|
143
144
|
column: location.start.column + 1
|
144
145
|
}
|
@@ -161,14 +162,12 @@ function parseListConfig(string) {
|
|
161
162
|
const items = {};
|
162
163
|
|
163
164
|
// Collapse whitespace around ,
|
164
|
-
string
|
165
|
+
string.replace(/\s*,\s*/g, ",").split(/,+/).forEach(name => {
|
166
|
+
const trimmedName = name.trim();
|
165
167
|
|
166
|
-
|
167
|
-
|
168
|
-
if (!name) {
|
169
|
-
return;
|
168
|
+
if (trimmedName) {
|
169
|
+
items[trimmedName] = true;
|
170
170
|
}
|
171
|
-
items[name] = true;
|
172
171
|
});
|
173
172
|
return items;
|
174
173
|
}
|
@@ -178,32 +177,12 @@ function parseListConfig(string) {
|
|
178
177
|
* and any globals declared by special block comments, are present in the global
|
179
178
|
* scope.
|
180
179
|
* @param {Scope} globalScope The global scope.
|
181
|
-
* @param {Object}
|
182
|
-
* @param {
|
180
|
+
* @param {Object} configGlobals The globals declared in configuration
|
181
|
+
* @param {{exportedVariables: Object, enabledGlobals: Object}} commentDirectives Directives from comment configuration
|
183
182
|
* @returns {void}
|
184
183
|
*/
|
185
|
-
function addDeclaredGlobals(globalScope,
|
186
|
-
|
187
|
-
exportedGlobals = {},
|
188
|
-
explicitGlobals = {},
|
189
|
-
builtin = envContext.get("builtin");
|
190
|
-
|
191
|
-
Object.assign(declaredGlobals, builtin);
|
192
|
-
|
193
|
-
Object.keys(config.env).filter(name => config.env[name]).forEach(name => {
|
194
|
-
const env = envContext.get(name),
|
195
|
-
environmentGlobals = env && env.globals;
|
196
|
-
|
197
|
-
if (environmentGlobals) {
|
198
|
-
Object.assign(declaredGlobals, environmentGlobals);
|
199
|
-
}
|
200
|
-
});
|
201
|
-
|
202
|
-
Object.assign(exportedGlobals, config.exported);
|
203
|
-
Object.assign(declaredGlobals, config.globals);
|
204
|
-
Object.assign(explicitGlobals, config.astGlobals);
|
205
|
-
|
206
|
-
Object.keys(declaredGlobals).forEach(name => {
|
184
|
+
function addDeclaredGlobals(globalScope, configGlobals, commentDirectives) {
|
185
|
+
Object.keys(configGlobals).forEach(name => {
|
207
186
|
let variable = globalScope.set.get(name);
|
208
187
|
|
209
188
|
if (!variable) {
|
@@ -212,24 +191,24 @@ function addDeclaredGlobals(globalScope, config, envContext) {
|
|
212
191
|
globalScope.variables.push(variable);
|
213
192
|
globalScope.set.set(name, variable);
|
214
193
|
}
|
215
|
-
variable.writeable =
|
194
|
+
variable.writeable = configGlobals[name];
|
216
195
|
});
|
217
196
|
|
218
|
-
Object.keys(
|
197
|
+
Object.keys(commentDirectives.enabledGlobals).forEach(name => {
|
219
198
|
let variable = globalScope.set.get(name);
|
220
199
|
|
221
200
|
if (!variable) {
|
222
201
|
variable = new eslintScope.Variable(name, globalScope);
|
223
202
|
variable.eslintExplicitGlobal = true;
|
224
|
-
variable.eslintExplicitGlobalComment =
|
203
|
+
variable.eslintExplicitGlobalComment = commentDirectives.enabledGlobals[name].comment;
|
225
204
|
globalScope.variables.push(variable);
|
226
205
|
globalScope.set.set(name, variable);
|
227
206
|
}
|
228
|
-
variable.writeable =
|
207
|
+
variable.writeable = commentDirectives.enabledGlobals[name].value;
|
229
208
|
});
|
230
209
|
|
231
210
|
// mark all exported variables as such
|
232
|
-
Object.keys(
|
211
|
+
Object.keys(commentDirectives.exportedVariables).forEach(name => {
|
233
212
|
const variable = globalScope.set.get(name);
|
234
213
|
|
235
214
|
if (variable) {
|
@@ -283,108 +262,90 @@ function createDisableDirectives(type, loc, value) {
|
|
283
262
|
* where reporting is disabled or enabled and merges them with reporting config.
|
284
263
|
* @param {string} filename The file being checked.
|
285
264
|
* @param {ASTNode} ast The top node of the AST.
|
286
|
-
* @param {Object} config The existing configuration data.
|
287
265
|
* @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
|
288
|
-
* @returns {{
|
289
|
-
*
|
266
|
+
* @returns {{configuredRules: Object, enabledGlobals: Object, exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}}
|
267
|
+
* A collection of the directive comments that were found, along with any problems that occurred when parsing
|
290
268
|
*/
|
291
|
-
function
|
292
|
-
|
293
|
-
const
|
294
|
-
|
295
|
-
astGlobals: {},
|
296
|
-
rules: {},
|
297
|
-
env: {}
|
298
|
-
};
|
299
|
-
const commentRules = {};
|
269
|
+
function getDirectiveComments(filename, ast, ruleMapper) {
|
270
|
+
const configuredRules = {};
|
271
|
+
const enabledGlobals = {};
|
272
|
+
const exportedVariables = {};
|
300
273
|
const problems = [];
|
301
274
|
const disableDirectives = [];
|
302
275
|
|
303
276
|
ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
|
277
|
+
const trimmedCommentText = comment.value.trim();
|
278
|
+
const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(trimmedCommentText);
|
304
279
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
if (match) {
|
309
|
-
value = value.slice(match.index + match[1].length);
|
310
|
-
if (comment.type === "Block") {
|
311
|
-
switch (match[1]) {
|
312
|
-
case "exported":
|
313
|
-
Object.assign(commentConfig.exported, parseBooleanConfig(value, comment));
|
314
|
-
break;
|
315
|
-
|
316
|
-
case "globals":
|
317
|
-
case "global":
|
318
|
-
Object.assign(commentConfig.astGlobals, parseBooleanConfig(value, comment));
|
319
|
-
break;
|
320
|
-
|
321
|
-
case "eslint-disable":
|
322
|
-
[].push.apply(disableDirectives, createDisableDirectives("disable", comment.loc.start, value));
|
323
|
-
break;
|
324
|
-
|
325
|
-
case "eslint-disable-line":
|
326
|
-
if (comment.loc.start.line === comment.loc.end.line) {
|
327
|
-
[].push.apply(disableDirectives, createDisableDirectives("disable-line", comment.loc.start, value));
|
328
|
-
}
|
329
|
-
break;
|
330
|
-
|
331
|
-
case "eslint-disable-next-line":
|
332
|
-
if (comment.loc.start.line === comment.loc.end.line) {
|
333
|
-
[].push.apply(disableDirectives, createDisableDirectives("disable-next-line", comment.loc.start, value));
|
334
|
-
}
|
335
|
-
break;
|
336
|
-
|
337
|
-
case "eslint-enable":
|
338
|
-
[].push.apply(disableDirectives, createDisableDirectives("enable", comment.loc.start, value));
|
339
|
-
break;
|
340
|
-
|
341
|
-
case "eslint": {
|
342
|
-
const parseResult = parseJsonConfig(value, comment.loc);
|
343
|
-
|
344
|
-
if (parseResult.success) {
|
345
|
-
Object.keys(parseResult.config).forEach(name => {
|
346
|
-
const ruleValue = parseResult.config[name];
|
347
|
-
|
348
|
-
try {
|
349
|
-
validator.validateRuleOptions(ruleMapper(name), name, ruleValue);
|
350
|
-
} catch (err) {
|
351
|
-
problems.push({
|
352
|
-
ruleId: name,
|
353
|
-
severity: 2,
|
354
|
-
source: null,
|
355
|
-
message: err.message,
|
356
|
-
line: comment.loc.start.line,
|
357
|
-
column: comment.loc.start.column + 1,
|
358
|
-
endLine: comment.loc.end.line,
|
359
|
-
endColumn: comment.loc.end.column + 1,
|
360
|
-
nodeType: null
|
361
|
-
});
|
362
|
-
}
|
363
|
-
commentRules[name] = ruleValue;
|
364
|
-
});
|
365
|
-
} else {
|
366
|
-
problems.push(parseResult.error);
|
367
|
-
}
|
280
|
+
if (!match) {
|
281
|
+
return;
|
282
|
+
}
|
368
283
|
|
369
|
-
|
284
|
+
const directiveValue = trimmedCommentText.slice(match.index + match[1].length);
|
285
|
+
|
286
|
+
if (/^eslint-disable-(next-)?line$/.test(match[1]) && comment.loc.start.line === comment.loc.end.line) {
|
287
|
+
const directiveType = match[1].slice("eslint-".length);
|
288
|
+
|
289
|
+
[].push.apply(disableDirectives, createDisableDirectives(directiveType, comment.loc.start, directiveValue));
|
290
|
+
} else if (comment.type === "Block") {
|
291
|
+
switch (match[1]) {
|
292
|
+
case "exported":
|
293
|
+
Object.assign(exportedVariables, parseBooleanConfig(directiveValue, comment));
|
294
|
+
break;
|
295
|
+
|
296
|
+
case "globals":
|
297
|
+
case "global":
|
298
|
+
Object.assign(enabledGlobals, parseBooleanConfig(directiveValue, comment));
|
299
|
+
break;
|
300
|
+
|
301
|
+
case "eslint-disable":
|
302
|
+
[].push.apply(disableDirectives, createDisableDirectives("disable", comment.loc.start, directiveValue));
|
303
|
+
break;
|
304
|
+
|
305
|
+
case "eslint-enable":
|
306
|
+
[].push.apply(disableDirectives, createDisableDirectives("enable", comment.loc.start, directiveValue));
|
307
|
+
break;
|
308
|
+
|
309
|
+
case "eslint": {
|
310
|
+
const parseResult = parseJsonConfig(directiveValue, comment.loc);
|
311
|
+
|
312
|
+
if (parseResult.success) {
|
313
|
+
Object.keys(parseResult.config).forEach(name => {
|
314
|
+
const ruleValue = parseResult.config[name];
|
315
|
+
|
316
|
+
try {
|
317
|
+
validator.validateRuleOptions(ruleMapper(name), name, ruleValue);
|
318
|
+
} catch (err) {
|
319
|
+
problems.push({
|
320
|
+
ruleId: name,
|
321
|
+
severity: 2,
|
322
|
+
source: null,
|
323
|
+
message: err.message,
|
324
|
+
line: comment.loc.start.line,
|
325
|
+
column: comment.loc.start.column + 1,
|
326
|
+
endLine: comment.loc.end.line,
|
327
|
+
endColumn: comment.loc.end.column + 1,
|
328
|
+
nodeType: null
|
329
|
+
});
|
330
|
+
}
|
331
|
+
configuredRules[name] = ruleValue;
|
332
|
+
});
|
333
|
+
} else {
|
334
|
+
problems.push(parseResult.error);
|
370
335
|
}
|
371
336
|
|
372
|
-
|
373
|
-
}
|
374
|
-
} else { // comment.type === "Line"
|
375
|
-
if (match[1] === "eslint-disable-line") {
|
376
|
-
[].push.apply(disableDirectives, createDisableDirectives("disable-line", comment.loc.start, value));
|
377
|
-
} else if (match[1] === "eslint-disable-next-line") {
|
378
|
-
[].push.apply(disableDirectives, createDisableDirectives("disable-next-line", comment.loc.start, value));
|
337
|
+
break;
|
379
338
|
}
|
339
|
+
|
340
|
+
// no default
|
380
341
|
}
|
381
342
|
}
|
382
343
|
});
|
383
344
|
|
384
|
-
Object.assign(commentConfig.rules, commentRules);
|
385
|
-
|
386
345
|
return {
|
387
|
-
|
346
|
+
configuredRules,
|
347
|
+
enabledGlobals,
|
348
|
+
exportedVariables,
|
388
349
|
problems,
|
389
350
|
disableDirectives
|
390
351
|
};
|
@@ -400,7 +361,7 @@ function normalizeEcmaVersion(ecmaVersion, isModule) {
|
|
400
361
|
|
401
362
|
// Need at least ES6 for modules
|
402
363
|
if (isModule && (!ecmaVersion || ecmaVersion < 6)) {
|
403
|
-
|
364
|
+
return 6;
|
404
365
|
}
|
405
366
|
|
406
367
|
/*
|
@@ -408,87 +369,87 @@ function normalizeEcmaVersion(ecmaVersion, isModule) {
|
|
408
369
|
* ES2015, which corresponds with ES6 (or a difference of 2009).
|
409
370
|
*/
|
410
371
|
if (ecmaVersion >= 2015) {
|
411
|
-
ecmaVersion
|
372
|
+
return ecmaVersion - 2009;
|
412
373
|
}
|
413
374
|
|
414
375
|
return ecmaVersion;
|
415
376
|
}
|
416
377
|
|
378
|
+
const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//g;
|
379
|
+
|
417
380
|
/**
|
418
|
-
*
|
419
|
-
* @param
|
420
|
-
* @
|
421
|
-
* @returns {Object} Processed config
|
381
|
+
* Checks whether or not there is a comment which has "eslint-env *" in a given text.
|
382
|
+
* @param {string} text - A source code text to check.
|
383
|
+
* @returns {Object|null} A result of parseListConfig() with "eslint-env *" comment.
|
422
384
|
*/
|
423
|
-
function
|
424
|
-
|
425
|
-
const copiedRules = {};
|
426
|
-
let parserOptions = {};
|
385
|
+
function findEslintEnv(text) {
|
386
|
+
let match, retv;
|
427
387
|
|
428
|
-
|
429
|
-
Object.keys(config.rules).forEach(k => {
|
430
|
-
const rule = config.rules[k];
|
388
|
+
eslintEnvPattern.lastIndex = 0;
|
431
389
|
|
432
|
-
|
433
|
-
|
434
|
-
}
|
435
|
-
if (Array.isArray(rule)) {
|
436
|
-
copiedRules[k] = rule.slice();
|
437
|
-
} else {
|
438
|
-
copiedRules[k] = rule;
|
439
|
-
}
|
440
|
-
});
|
390
|
+
while ((match = eslintEnvPattern.exec(text))) {
|
391
|
+
retv = Object.assign(retv || {}, parseListConfig(match[1]));
|
441
392
|
}
|
442
393
|
|
443
|
-
|
444
|
-
|
445
|
-
Object.keys(config.env).forEach(envName => {
|
446
|
-
const env = envContext.get(envName);
|
394
|
+
return retv;
|
395
|
+
}
|
447
396
|
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
397
|
+
/**
|
398
|
+
* Normalizes the possible options for `linter.verify` and `linter.verifyAndFix` to a
|
399
|
+
* consistent shape.
|
400
|
+
* @param {(string|{reportUnusedDisableDirectives: boolean, filename: string, allowInlineConfig: boolean})} providedOptions Options
|
401
|
+
* @returns {{reportUnusedDisableDirectives: boolean, filename: string, allowInlineConfig: boolean}} Normalized options
|
402
|
+
*/
|
403
|
+
function normalizeVerifyOptions(providedOptions) {
|
404
|
+
const isObjectOptions = typeof providedOptions === "object";
|
405
|
+
const providedFilename = isObjectOptions ? providedOptions.filename : providedOptions;
|
453
406
|
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
env: ConfigOps.merge(defaultConfig.env, config.env || {}),
|
459
|
-
settings: ConfigOps.merge(defaultConfig.settings, config.settings || {}),
|
460
|
-
parserOptions: ConfigOps.merge(parserOptions, config.parserOptions || {})
|
407
|
+
return {
|
408
|
+
filename: typeof providedFilename === "string" ? providedFilename : "<input>",
|
409
|
+
allowInlineConfig: !isObjectOptions || providedOptions.allowInlineConfig !== false,
|
410
|
+
reportUnusedDisableDirectives: isObjectOptions && !!providedOptions.reportUnusedDisableDirectives
|
461
411
|
};
|
462
|
-
|
412
|
+
}
|
413
|
+
|
414
|
+
/**
|
415
|
+
* Combines the provided parserOptions with the options from environments
|
416
|
+
* @param {Object} providedOptions The provided 'parserOptions' key in a config
|
417
|
+
* @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
|
418
|
+
* @returns {Object} Resulting parser options after merge
|
419
|
+
*/
|
420
|
+
function resolveParserOptions(providedOptions, enabledEnvironments) {
|
421
|
+
const parserOptionsFromEnv = enabledEnvironments
|
422
|
+
.filter(env => env.parserOptions)
|
423
|
+
.reduce((parserOptions, env) => ConfigOps.merge(parserOptions, env.parserOptions), {});
|
424
|
+
|
425
|
+
const mergedParserOptions = ConfigOps.merge(parserOptionsFromEnv, providedOptions || {});
|
426
|
+
|
427
|
+
const isModule = mergedParserOptions.sourceType === "module";
|
463
428
|
|
464
429
|
if (isModule) {
|
465
430
|
|
466
431
|
// can't have global return inside of modules
|
467
|
-
|
432
|
+
mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false });
|
468
433
|
}
|
469
434
|
|
470
|
-
|
435
|
+
mergedParserOptions.ecmaVersion = normalizeEcmaVersion(mergedParserOptions.ecmaVersion, isModule);
|
471
436
|
|
472
|
-
return
|
437
|
+
return mergedParserOptions;
|
473
438
|
}
|
474
439
|
|
475
|
-
const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//g;
|
476
|
-
|
477
440
|
/**
|
478
|
-
*
|
479
|
-
* @param {
|
480
|
-
* @
|
441
|
+
* Combines the provided globals object with the globals from environments
|
442
|
+
* @param {Object} providedGlobals The 'globals' key in a config
|
443
|
+
* @param {Environments[]} enabledEnvironments The environments enabled in configuration and with inline comments
|
444
|
+
* @returns {Object} The resolved globals object
|
481
445
|
*/
|
482
|
-
function
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
}
|
490
|
-
|
491
|
-
return retv;
|
446
|
+
function resolveGlobals(providedGlobals, enabledEnvironments) {
|
447
|
+
return Object.assign.apply(
|
448
|
+
null,
|
449
|
+
[{}]
|
450
|
+
.concat(enabledEnvironments.filter(env => env.globals).map(env => env.globals))
|
451
|
+
.concat(providedGlobals)
|
452
|
+
);
|
492
453
|
}
|
493
454
|
|
494
455
|
/**
|
@@ -551,13 +512,16 @@ function analyzeScope(ast, parserOptions, visitorKeys) {
|
|
551
512
|
* as possible
|
552
513
|
* @param {string} text The text to parse.
|
553
514
|
* @param {Object} providedParserOptions Options to pass to the parser
|
554
|
-
* @param {
|
515
|
+
* @param {string} parserName The name of the parser
|
516
|
+
* @param {Map<string, Object>} parserMap A map from names to loaded parsers
|
555
517
|
* @param {string} filePath The path to the file being parsed.
|
556
518
|
* @returns {{success: false, error: Problem}|{success: true, sourceCode: SourceCode}}
|
557
519
|
* An object containing the AST and parser services if parsing was successful, or the error if parsing failed
|
558
520
|
* @private
|
559
521
|
*/
|
560
|
-
function parse(text, providedParserOptions,
|
522
|
+
function parse(text, providedParserOptions, parserName, parserMap, filePath) {
|
523
|
+
|
524
|
+
|
561
525
|
const textToParse = stripUnicodeBOM(text).replace(astUtils.SHEBANG_MATCHER, (match, captured) => `//${captured}`);
|
562
526
|
const parserOptions = Object.assign({}, providedParserOptions, {
|
563
527
|
loc: true,
|
@@ -570,6 +534,25 @@ function parse(text, providedParserOptions, parser, filePath) {
|
|
570
534
|
filePath
|
571
535
|
});
|
572
536
|
|
537
|
+
let parser;
|
538
|
+
|
539
|
+
try {
|
540
|
+
parser = parserMap.get(parserName) || require(parserName);
|
541
|
+
} catch (ex) {
|
542
|
+
return {
|
543
|
+
success: false,
|
544
|
+
error: {
|
545
|
+
ruleId: null,
|
546
|
+
fatal: true,
|
547
|
+
severity: 2,
|
548
|
+
source: null,
|
549
|
+
message: ex.message,
|
550
|
+
line: 0,
|
551
|
+
column: 0
|
552
|
+
}
|
553
|
+
};
|
554
|
+
}
|
555
|
+
|
573
556
|
/*
|
574
557
|
* Check for parsing errors first. If there's a parsing error, nothing
|
575
558
|
* else can happen. However, a parsing error does not throw an error
|
@@ -688,6 +671,21 @@ function markVariableAsUsed(scopeManager, currentNode, parserOptions, name) {
|
|
688
671
|
return false;
|
689
672
|
}
|
690
673
|
|
674
|
+
/**
|
675
|
+
* Runs a rule, and gets its listeners
|
676
|
+
* @param {Rule} rule A normalized rule with a `create` method
|
677
|
+
* @param {Context} ruleContext The context that should be passed to the rule
|
678
|
+
* @returns {Object} A map of selector listeners provided by the rule
|
679
|
+
*/
|
680
|
+
function createRuleListeners(rule, ruleContext) {
|
681
|
+
try {
|
682
|
+
return rule.create(ruleContext);
|
683
|
+
} catch (ex) {
|
684
|
+
ex.message = `Error while loading rule '${ruleContext.id}': ${ex.message}`;
|
685
|
+
throw ex;
|
686
|
+
}
|
687
|
+
}
|
688
|
+
|
691
689
|
// methods that exist on SourceCode object
|
692
690
|
const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
|
693
691
|
getSource: "getText",
|
@@ -726,7 +724,157 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze(
|
|
726
724
|
)
|
727
725
|
);
|
728
726
|
|
727
|
+
/**
|
728
|
+
* Runs the given rules on the given SourceCode object
|
729
|
+
* @param {SourceCode} sourceCode A SourceCode object for the given text
|
730
|
+
* @param {Object} configuredRules The rules configuration
|
731
|
+
* @param {function(string): Rule} ruleMapper A mapper function from rule names to rules
|
732
|
+
* @param {Object} parserOptions The options that were passed to the parser
|
733
|
+
* @param {string} parserName The name of the parser in the config
|
734
|
+
* @param {Object} settings The settings that were enabled in the config
|
735
|
+
* @param {string} filename The reported filename of the code
|
736
|
+
* @returns {Problem[]} An array of reported problems
|
737
|
+
*/
|
738
|
+
function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename) {
|
739
|
+
const emitter = createEmitter();
|
740
|
+
const traverser = new Traverser();
|
741
|
+
|
742
|
+
/*
|
743
|
+
* Create a frozen object with the ruleContext properties and methods that are shared by all rules.
|
744
|
+
* All rule contexts will inherit from this object. This avoids the performance penalty of copying all the
|
745
|
+
* properties once for each rule.
|
746
|
+
*/
|
747
|
+
const sharedTraversalContext = Object.freeze(
|
748
|
+
Object.assign(
|
749
|
+
Object.create(BASE_TRAVERSAL_CONTEXT),
|
750
|
+
{
|
751
|
+
getAncestors: () => traverser.parents(),
|
752
|
+
getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager),
|
753
|
+
getFilename: () => filename,
|
754
|
+
getScope: () => getScope(sourceCode.scopeManager, traverser.current(), parserOptions.ecmaVersion),
|
755
|
+
getSourceCode: () => sourceCode,
|
756
|
+
markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, traverser.current(), parserOptions, name),
|
757
|
+
parserOptions,
|
758
|
+
parserPath: parserName,
|
759
|
+
parserServices: sourceCode.parserServices,
|
760
|
+
settings,
|
761
|
+
|
762
|
+
/**
|
763
|
+
* This is used to avoid breaking rules that used to monkeypatch the `Linter#report` method
|
764
|
+
* by using the `_linter` property on rule contexts.
|
765
|
+
*
|
766
|
+
* This should be removed in a major release after we create a better way to
|
767
|
+
* lint for unused disable comments.
|
768
|
+
* https://github.com/eslint/eslint/issues/9193
|
769
|
+
*/
|
770
|
+
_linter: {
|
771
|
+
report() {},
|
772
|
+
on: emitter.on
|
773
|
+
}
|
774
|
+
}
|
775
|
+
)
|
776
|
+
);
|
777
|
+
|
778
|
+
|
779
|
+
const lintingProblems = [];
|
780
|
+
|
781
|
+
Object.keys(configuredRules).forEach(ruleId => {
|
782
|
+
const severity = ConfigOps.getRuleSeverity(configuredRules[ruleId]);
|
783
|
+
|
784
|
+
if (severity === 0) {
|
785
|
+
return;
|
786
|
+
}
|
787
|
+
|
788
|
+
const rule = ruleMapper(ruleId);
|
789
|
+
const messageIds = rule.meta && rule.meta.messages;
|
790
|
+
let reportTranslator = null;
|
791
|
+
const ruleContext = Object.freeze(
|
792
|
+
Object.assign(
|
793
|
+
Object.create(sharedTraversalContext),
|
794
|
+
{
|
795
|
+
id: ruleId,
|
796
|
+
options: getRuleOptions(configuredRules[ruleId]),
|
797
|
+
report() {
|
798
|
+
|
799
|
+
/*
|
800
|
+
* Create a report translator lazily.
|
801
|
+
* In a vast majority of cases, any given rule reports zero errors on a given
|
802
|
+
* piece of code. Creating a translator lazily avoids the performance cost of
|
803
|
+
* creating a new translator function for each rule that usually doesn't get
|
804
|
+
* called.
|
805
|
+
*
|
806
|
+
* Using lazy report translators improves end-to-end performance by about 3%
|
807
|
+
* with Node 8.4.0.
|
808
|
+
*/
|
809
|
+
if (reportTranslator === null) {
|
810
|
+
reportTranslator = createReportTranslator({ ruleId, severity, sourceCode, messageIds });
|
811
|
+
}
|
812
|
+
const problem = reportTranslator.apply(null, arguments);
|
813
|
+
|
814
|
+
if (problem.fix && rule.meta && !rule.meta.fixable) {
|
815
|
+
throw new Error("Fixable rules should export a `meta.fixable` property.");
|
816
|
+
}
|
817
|
+
lintingProblems.push(problem);
|
818
|
+
|
819
|
+
/*
|
820
|
+
* This is used to avoid breaking rules that used monkeypatch Linter, and relied on
|
821
|
+
* `linter.report` getting called with report info every time a rule reports a problem.
|
822
|
+
* To continue to support this, make sure that `context._linter.report` is called every
|
823
|
+
* time a problem is reported by a rule, even though `context._linter` is no longer a
|
824
|
+
* `Linter` instance.
|
825
|
+
*
|
826
|
+
* This should be removed in a major release after we create a better way to
|
827
|
+
* lint for unused disable comments.
|
828
|
+
* https://github.com/eslint/eslint/issues/9193
|
829
|
+
*/
|
830
|
+
sharedTraversalContext._linter.report( // eslint-disable-line no-underscore-dangle
|
831
|
+
problem.ruleId,
|
832
|
+
problem.severity,
|
833
|
+
{ loc: { start: { line: problem.line, column: problem.column - 1 } } },
|
834
|
+
problem.message
|
835
|
+
);
|
836
|
+
}
|
837
|
+
}
|
838
|
+
)
|
839
|
+
);
|
840
|
+
|
841
|
+
const ruleListeners = createRuleListeners(rule, ruleContext);
|
842
|
+
|
843
|
+
// add all the selectors from the rule as listeners
|
844
|
+
Object.keys(ruleListeners).forEach(selector => {
|
845
|
+
emitter.on(
|
846
|
+
selector,
|
847
|
+
timing.enabled
|
848
|
+
? timing.time(ruleId, ruleListeners[selector])
|
849
|
+
: ruleListeners[selector]
|
850
|
+
);
|
851
|
+
});
|
852
|
+
});
|
853
|
+
|
854
|
+
const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter));
|
855
|
+
|
856
|
+
/*
|
857
|
+
* Each node has a type property. Whenever a particular type of
|
858
|
+
* node is found, an event is fired. This allows any listeners to
|
859
|
+
* automatically be informed that this type of node has been found
|
860
|
+
* and react accordingly.
|
861
|
+
*/
|
862
|
+
traverser.traverse(sourceCode.ast, {
|
863
|
+
enter(node, parent) {
|
864
|
+
node.parent = parent;
|
865
|
+
eventGenerator.enterNode(node);
|
866
|
+
},
|
867
|
+
leave(node) {
|
868
|
+
eventGenerator.leaveNode(node);
|
869
|
+
},
|
870
|
+
visitorKeys: sourceCode.visitorKeys
|
871
|
+
});
|
872
|
+
|
873
|
+
return lintingProblems;
|
874
|
+
}
|
875
|
+
|
729
876
|
const lastSourceCodes = new WeakMap();
|
877
|
+
const loadedParserMaps = new WeakMap();
|
730
878
|
|
731
879
|
//------------------------------------------------------------------------------
|
732
880
|
// Public Interface
|
@@ -740,10 +888,10 @@ module.exports = class Linter {
|
|
740
888
|
|
741
889
|
constructor() {
|
742
890
|
lastSourceCodes.set(this, null);
|
891
|
+
loadedParserMaps.set(this, new Map());
|
743
892
|
this.version = pkg.version;
|
744
893
|
|
745
894
|
this.rules = new Rules();
|
746
|
-
this._parsers = new Map();
|
747
895
|
this.environments = new Environments();
|
748
896
|
}
|
749
897
|
|
@@ -761,7 +909,7 @@ module.exports = class Linter {
|
|
761
909
|
/**
|
762
910
|
* Same as linter.verify, except without support for processors.
|
763
911
|
* @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
|
764
|
-
* @param {ESLintConfig}
|
912
|
+
* @param {ESLintConfig} providedConfig An ESLintConfig instance to configure everything.
|
765
913
|
* @param {(string|Object)} [filenameOrOptions] The optional filename of the file being checked.
|
766
914
|
* If this is not set, the filename will default to '<input>' in the rule context. If
|
767
915
|
* an object, then it has "filename", "saveState", and "allowInlineConfig" properties.
|
@@ -769,23 +917,14 @@ module.exports = class Linter {
|
|
769
917
|
* Useful if you want to validate JS without comments overriding rules.
|
770
918
|
* @param {boolean} [filenameOrOptions.reportUnusedDisableDirectives=false] Adds reported errors for unused
|
771
919
|
* eslint-disable directives
|
772
|
-
* @returns {Object[]} The results as an array of messages or
|
920
|
+
* @returns {Object[]} The results as an array of messages or an empty array if no messages.
|
773
921
|
*/
|
774
|
-
_verifyWithoutProcessors(textOrSourceCode,
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
reportUnusedDisableDirectives;
|
922
|
+
_verifyWithoutProcessors(textOrSourceCode, providedConfig, filenameOrOptions) {
|
923
|
+
const config = providedConfig || {};
|
924
|
+
const options = normalizeVerifyOptions(filenameOrOptions);
|
925
|
+
let text;
|
779
926
|
|
780
927
|
// evaluate arguments
|
781
|
-
if (typeof filenameOrOptions === "object") {
|
782
|
-
providedFilename = filenameOrOptions.filename;
|
783
|
-
allowInlineConfig = filenameOrOptions.allowInlineConfig;
|
784
|
-
reportUnusedDisableDirectives = filenameOrOptions.reportUnusedDisableDirectives;
|
785
|
-
} else {
|
786
|
-
providedFilename = filenameOrOptions;
|
787
|
-
}
|
788
|
-
|
789
928
|
if (typeof textOrSourceCode === "string") {
|
790
929
|
lastSourceCodes.set(this, null);
|
791
930
|
text = textOrSourceCode;
|
@@ -794,23 +933,18 @@ module.exports = class Linter {
|
|
794
933
|
text = textOrSourceCode.text;
|
795
934
|
}
|
796
935
|
|
797
|
-
const filename = typeof providedFilename === "string" ? providedFilename : "<input>";
|
798
|
-
|
799
936
|
// search and apply "eslint-env *".
|
800
937
|
const envInFile = findEslintEnv(text);
|
938
|
+
const resolvedEnvConfig = Object.assign({ builtin: true }, config.env, envInFile);
|
939
|
+
const enabledEnvs = Object.keys(resolvedEnvConfig)
|
940
|
+
.filter(envName => resolvedEnvConfig[envName])
|
941
|
+
.map(envName => this.environments.get(envName))
|
942
|
+
.filter(env => env);
|
801
943
|
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
config.env = Object.assign({}, config.env, envInFile);
|
807
|
-
} else {
|
808
|
-
config.env = envInFile;
|
809
|
-
}
|
810
|
-
}
|
811
|
-
|
812
|
-
// process initial config to make it safe to extend
|
813
|
-
config = prepareConfig(config, this.environments);
|
944
|
+
const parserOptions = resolveParserOptions(config.parserOptions || {}, enabledEnvs);
|
945
|
+
const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs);
|
946
|
+
const parserName = config.parser || DEFAULT_PARSER_NAME;
|
947
|
+
const settings = config.settings || {};
|
814
948
|
|
815
949
|
if (!lastSourceCodes.get(this)) {
|
816
950
|
|
@@ -820,26 +954,12 @@ module.exports = class Linter {
|
|
820
954
|
return [];
|
821
955
|
}
|
822
956
|
|
823
|
-
let parser;
|
824
|
-
|
825
|
-
try {
|
826
|
-
parser = this._parsers.get(config.parser) || require(config.parser);
|
827
|
-
} catch (ex) {
|
828
|
-
return [{
|
829
|
-
ruleId: null,
|
830
|
-
fatal: true,
|
831
|
-
severity: 2,
|
832
|
-
source: null,
|
833
|
-
message: ex.message,
|
834
|
-
line: 0,
|
835
|
-
column: 0
|
836
|
-
}];
|
837
|
-
}
|
838
957
|
const parseResult = parse(
|
839
958
|
text,
|
840
|
-
|
841
|
-
|
842
|
-
|
959
|
+
parserOptions,
|
960
|
+
parserName,
|
961
|
+
loadedParserMaps.get(this),
|
962
|
+
options.filename
|
843
963
|
);
|
844
964
|
|
845
965
|
if (!parseResult.success) {
|
@@ -861,171 +981,41 @@ module.exports = class Linter {
|
|
861
981
|
ast: lastSourceCode.ast,
|
862
982
|
parserServices: lastSourceCode.parserServices,
|
863
983
|
visitorKeys: lastSourceCode.visitorKeys,
|
864
|
-
scopeManager: analyzeScope(lastSourceCode.ast,
|
984
|
+
scopeManager: analyzeScope(lastSourceCode.ast, parserOptions)
|
865
985
|
}));
|
866
986
|
}
|
867
987
|
}
|
868
988
|
|
869
|
-
const problems = [];
|
870
989
|
const sourceCode = lastSourceCodes.get(this);
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
if (allowInlineConfig !== false) {
|
875
|
-
const modifyConfigResult = modifyConfigsFromComments(filename, sourceCode.ast, config, ruleId => this.rules.get(ruleId));
|
876
|
-
|
877
|
-
config = modifyConfigResult.config;
|
878
|
-
modifyConfigResult.problems.forEach(problem => problems.push(problem));
|
879
|
-
disableDirectives = modifyConfigResult.disableDirectives;
|
880
|
-
} else {
|
881
|
-
disableDirectives = [];
|
882
|
-
}
|
883
|
-
|
884
|
-
const emitter = createEmitter();
|
885
|
-
const traverser = new Traverser();
|
886
|
-
const scopeManager = sourceCode.scopeManager;
|
887
|
-
|
888
|
-
/*
|
889
|
-
* Create a frozen object with the ruleContext properties and methods that are shared by all rules.
|
890
|
-
* All rule contexts will inherit from this object. This avoids the performance penalty of copying all the
|
891
|
-
* properties once for each rule.
|
892
|
-
*/
|
893
|
-
const sharedTraversalContext = Object.freeze(
|
894
|
-
Object.assign(
|
895
|
-
Object.create(BASE_TRAVERSAL_CONTEXT),
|
896
|
-
{
|
897
|
-
getAncestors: () => traverser.parents(),
|
898
|
-
getDeclaredVariables: scopeManager.getDeclaredVariables.bind(scopeManager),
|
899
|
-
getFilename: () => filename,
|
900
|
-
getScope: () => getScope(scopeManager, traverser.current(), config.parserOptions.ecmaVersion),
|
901
|
-
getSourceCode: () => sourceCode,
|
902
|
-
markVariableAsUsed: name => markVariableAsUsed(scopeManager, traverser.current(), config.parserOptions, name),
|
903
|
-
parserOptions: config.parserOptions,
|
904
|
-
parserPath: config.parser,
|
905
|
-
parserServices: sourceCode.parserServices,
|
906
|
-
settings: config.settings,
|
907
|
-
|
908
|
-
/**
|
909
|
-
* This is used to avoid breaking rules that used to monkeypatch the `Linter#report` method
|
910
|
-
* by using the `_linter` property on rule contexts.
|
911
|
-
*
|
912
|
-
* This should be removed in a major release after we create a better way to
|
913
|
-
* lint for unused disable comments.
|
914
|
-
* https://github.com/eslint/eslint/issues/9193
|
915
|
-
*/
|
916
|
-
_linter: {
|
917
|
-
report() {},
|
918
|
-
on: emitter.on
|
919
|
-
}
|
920
|
-
}
|
921
|
-
)
|
922
|
-
);
|
923
|
-
|
924
|
-
// enable appropriate rules
|
925
|
-
Object.keys(config.rules).forEach(ruleId => {
|
926
|
-
const severity = ConfigOps.getRuleSeverity(config.rules[ruleId]);
|
927
|
-
|
928
|
-
if (severity === 0) {
|
929
|
-
return;
|
930
|
-
}
|
931
|
-
|
932
|
-
const rule = this.rules.get(ruleId);
|
933
|
-
const messageIds = rule && rule.meta && rule.meta.messages;
|
934
|
-
let reportTranslator = null;
|
935
|
-
const ruleContext = Object.freeze(
|
936
|
-
Object.assign(
|
937
|
-
Object.create(sharedTraversalContext),
|
938
|
-
{
|
939
|
-
id: ruleId,
|
940
|
-
options: getRuleOptions(config.rules[ruleId]),
|
941
|
-
report() {
|
942
|
-
|
943
|
-
/*
|
944
|
-
* Create a report translator lazily.
|
945
|
-
* In a vast majority of cases, any given rule reports zero errors on a given
|
946
|
-
* piece of code. Creating a translator lazily avoids the performance cost of
|
947
|
-
* creating a new translator function for each rule that usually doesn't get
|
948
|
-
* called.
|
949
|
-
*
|
950
|
-
* Using lazy report translators improves end-to-end performance by about 3%
|
951
|
-
* with Node 8.4.0.
|
952
|
-
*/
|
953
|
-
if (reportTranslator === null) {
|
954
|
-
reportTranslator = createReportTranslator({ ruleId, severity, sourceCode, messageIds });
|
955
|
-
}
|
956
|
-
const problem = reportTranslator.apply(null, arguments);
|
957
|
-
|
958
|
-
if (problem.fix && rule.meta && !rule.meta.fixable) {
|
959
|
-
throw new Error("Fixable rules should export a `meta.fixable` property.");
|
960
|
-
}
|
961
|
-
problems.push(problem);
|
962
|
-
|
963
|
-
/*
|
964
|
-
* This is used to avoid breaking rules that used monkeypatch Linter, and relied on
|
965
|
-
* `linter.report` getting called with report info every time a rule reports a problem.
|
966
|
-
* To continue to support this, make sure that `context._linter.report` is called every
|
967
|
-
* time a problem is reported by a rule, even though `context._linter` is no longer a
|
968
|
-
* `Linter` instance.
|
969
|
-
*
|
970
|
-
* This should be removed in a major release after we create a better way to
|
971
|
-
* lint for unused disable comments.
|
972
|
-
* https://github.com/eslint/eslint/issues/9193
|
973
|
-
*/
|
974
|
-
sharedTraversalContext._linter.report( // eslint-disable-line no-underscore-dangle
|
975
|
-
problem.ruleId,
|
976
|
-
problem.severity,
|
977
|
-
{ loc: { start: { line: problem.line, column: problem.column - 1 } } },
|
978
|
-
problem.message
|
979
|
-
);
|
980
|
-
}
|
981
|
-
}
|
982
|
-
)
|
983
|
-
);
|
984
|
-
|
985
|
-
try {
|
986
|
-
const ruleListeners = rule.create(ruleContext);
|
987
|
-
|
988
|
-
// add all the selectors from the rule as listeners
|
989
|
-
Object.keys(ruleListeners).forEach(selector => {
|
990
|
-
emitter.on(
|
991
|
-
selector,
|
992
|
-
timing.enabled
|
993
|
-
? timing.time(ruleId, ruleListeners[selector])
|
994
|
-
: ruleListeners[selector]
|
995
|
-
);
|
996
|
-
});
|
997
|
-
} catch (ex) {
|
998
|
-
ex.message = `Error while loading rule '${ruleId}': ${ex.message}`;
|
999
|
-
throw ex;
|
1000
|
-
}
|
1001
|
-
});
|
990
|
+
const commentDirectives = options.allowInlineConfig
|
991
|
+
? getDirectiveComments(options.filename, sourceCode.ast, ruleId => this.rules.get(ruleId))
|
992
|
+
: { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
|
1002
993
|
|
1003
994
|
// augment global scope with declared global variables
|
1004
|
-
addDeclaredGlobals(
|
995
|
+
addDeclaredGlobals(
|
996
|
+
sourceCode.scopeManager.scopes[0],
|
997
|
+
configuredGlobals,
|
998
|
+
{ exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals }
|
999
|
+
);
|
1005
1000
|
|
1006
|
-
const
|
1001
|
+
const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
|
1007
1002
|
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
eventGenerator.enterNode(node);
|
1018
|
-
},
|
1019
|
-
leave(node) {
|
1020
|
-
eventGenerator.leaveNode(node);
|
1021
|
-
},
|
1022
|
-
visitorKeys: sourceCode.visitorKeys
|
1023
|
-
});
|
1003
|
+
const lintingProblems = runRules(
|
1004
|
+
sourceCode,
|
1005
|
+
configuredRules,
|
1006
|
+
ruleId => this.rules.get(ruleId),
|
1007
|
+
parserOptions,
|
1008
|
+
parserName,
|
1009
|
+
settings,
|
1010
|
+
options.filename
|
1011
|
+
);
|
1024
1012
|
|
1025
1013
|
return applyDisableDirectives({
|
1026
|
-
directives: disableDirectives,
|
1027
|
-
problems:
|
1028
|
-
|
1014
|
+
directives: commentDirectives.disableDirectives,
|
1015
|
+
problems: lintingProblems
|
1016
|
+
.concat(commentDirectives.problems)
|
1017
|
+
.sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
|
1018
|
+
reportUnusedDisableDirectives: options.reportUnusedDisableDirectives
|
1029
1019
|
});
|
1030
1020
|
}
|
1031
1021
|
|
@@ -1045,7 +1035,7 @@ module.exports = class Linter {
|
|
1045
1035
|
* @param {function(Array<Object[]>): Object[]} [filenameOrOptions.postprocess] postprocessor for report messages. If provided,
|
1046
1036
|
* this should accept an array of the message lists for each code block returned from the preprocessor,
|
1047
1037
|
* apply a mapping to the messages as appropriate, and return a one-dimensional array of messages
|
1048
|
-
* @returns {Object[]} The results as an array of messages or
|
1038
|
+
* @returns {Object[]} The results as an array of messages or an empty array if no messages.
|
1049
1039
|
*/
|
1050
1040
|
verify(textOrSourceCode, config, filenameOrOptions) {
|
1051
1041
|
const preprocess = filenameOrOptions && filenameOrOptions.preprocess || (rawText => [rawText]);
|
@@ -1102,7 +1092,7 @@ module.exports = class Linter {
|
|
1102
1092
|
* @returns {void}
|
1103
1093
|
*/
|
1104
1094
|
defineParser(parserId, parserModule) {
|
1105
|
-
this.
|
1095
|
+
loadedParserMaps.get(this).set(parserId, parserModule);
|
1106
1096
|
}
|
1107
1097
|
|
1108
1098
|
/**
|
@@ -1127,7 +1117,8 @@ module.exports = class Linter {
|
|
1127
1117
|
let messages = [],
|
1128
1118
|
fixedResult,
|
1129
1119
|
fixed = false,
|
1130
|
-
passNumber = 0
|
1120
|
+
passNumber = 0,
|
1121
|
+
currentText = text;
|
1131
1122
|
const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`;
|
1132
1123
|
const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true;
|
1133
1124
|
|
@@ -1144,10 +1135,10 @@ module.exports = class Linter {
|
|
1144
1135
|
passNumber++;
|
1145
1136
|
|
1146
1137
|
debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`);
|
1147
|
-
messages = this.verify(
|
1138
|
+
messages = this.verify(currentText, config, options);
|
1148
1139
|
|
1149
1140
|
debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`);
|
1150
|
-
fixedResult = SourceCodeFixer.applyFixes(
|
1141
|
+
fixedResult = SourceCodeFixer.applyFixes(currentText, messages, shouldFix);
|
1151
1142
|
|
1152
1143
|
/*
|
1153
1144
|
* stop if there are any syntax errors.
|
@@ -1161,7 +1152,7 @@ module.exports = class Linter {
|
|
1161
1152
|
fixed = fixed || fixedResult.fixed;
|
1162
1153
|
|
1163
1154
|
// update to use the fixed output instead of the original text
|
1164
|
-
|
1155
|
+
currentText = fixedResult.output;
|
1165
1156
|
|
1166
1157
|
} while (
|
1167
1158
|
fixedResult.fixed &&
|
@@ -1173,12 +1164,12 @@ module.exports = class Linter {
|
|
1173
1164
|
* the most up-to-date information.
|
1174
1165
|
*/
|
1175
1166
|
if (fixedResult.fixed) {
|
1176
|
-
fixedResult.messages = this.verify(
|
1167
|
+
fixedResult.messages = this.verify(currentText, config, options);
|
1177
1168
|
}
|
1178
1169
|
|
1179
1170
|
// ensure the last result properly reflects if fixes were done
|
1180
1171
|
fixedResult.fixed = fixed;
|
1181
|
-
fixedResult.output =
|
1172
|
+
fixedResult.output = currentText;
|
1182
1173
|
|
1183
1174
|
return fixedResult;
|
1184
1175
|
}
|