eslint 4.5.0 → 4.7.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 +98 -0
- package/bin/eslint.js +2 -1
- package/conf/eslint-recommended.js +1 -0
- package/lib/ast-utils.js +20 -17
- package/lib/cli-engine.js +51 -124
- package/lib/code-path-analysis/code-path-analyzer.js +8 -4
- package/lib/code-path-analysis/code-path-segment.js +2 -1
- package/lib/code-path-analysis/code-path-state.js +21 -14
- package/lib/code-path-analysis/code-path.js +3 -2
- package/lib/code-path-analysis/fork-context.js +2 -1
- package/lib/config/autoconfig.js +2 -4
- package/lib/config/config-initializer.js +9 -5
- package/lib/config/config-ops.js +15 -15
- package/lib/config.js +8 -12
- package/lib/formatters/codeframe.js +1 -1
- package/lib/formatters/stylish.js +1 -1
- package/lib/ignored-paths.js +0 -2
- package/lib/linter.js +468 -638
- package/lib/report-translator.js +274 -0
- package/lib/rules/function-paren-newline.js +221 -0
- package/lib/rules/generator-star-spacing.js +70 -19
- package/lib/rules/indent-legacy.js +3 -2
- package/lib/rules/indent.js +15 -6
- package/lib/rules/key-spacing.js +2 -1
- package/lib/rules/newline-per-chained-call.js +20 -3
- package/lib/rules/no-extra-parens.js +75 -33
- package/lib/rules/no-invalid-this.js +2 -1
- package/lib/rules/no-tabs.js +1 -1
- package/lib/rules/no-undef-init.js +4 -0
- package/lib/rules/no-unmodified-loop-condition.js +1 -1
- package/lib/rules/no-unused-vars.js +47 -4
- package/lib/rules/padded-blocks.js +2 -2
- package/lib/rules/prefer-arrow-callback.js +1 -2
- package/lib/rules/quote-props.js +4 -2
- package/lib/rules/quotes.js +1 -2
- package/lib/rules/space-before-blocks.js +1 -1
- package/lib/rules/valid-jsdoc.js +2 -2
- package/lib/rules.js +48 -3
- package/lib/testers/rule-tester.js +27 -51
- package/lib/timing.js +2 -2
- package/lib/util/apply-disable-directives.js +131 -0
- package/lib/util/fix-tracker.js +1 -2
- package/lib/util/npm-util.js +21 -4
- package/lib/util/source-code-fixer.js +5 -14
- package/lib/util/source-code.js +3 -5
- package/package.json +8 -8
- package/lib/rule-context.js +0 -241
- package/lib/testers/event-generator-tester.js +0 -62
- package/lib/testers/test-parser.js +0 -48
package/lib/linter.js
CHANGED
@@ -9,21 +9,21 @@
|
|
9
9
|
// Requirements
|
10
10
|
//------------------------------------------------------------------------------
|
11
11
|
|
12
|
-
const
|
13
|
-
EventEmitter = require("events").EventEmitter,
|
12
|
+
const EventEmitter = require("events").EventEmitter,
|
14
13
|
eslintScope = require("eslint-scope"),
|
15
14
|
levn = require("levn"),
|
15
|
+
lodash = require("lodash"),
|
16
16
|
blankScriptAST = require("../conf/blank-script.json"),
|
17
17
|
defaultConfig = require("../conf/default-config-options.js"),
|
18
|
-
replacements = require("../conf/replacements.json"),
|
19
18
|
CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
|
20
19
|
ConfigOps = require("./config/config-ops"),
|
21
20
|
validator = require("./config/config-validator"),
|
22
21
|
Environments = require("./config/environments"),
|
22
|
+
applyDisableDirectives = require("./util/apply-disable-directives"),
|
23
23
|
NodeEventGenerator = require("./util/node-event-generator"),
|
24
24
|
SourceCode = require("./util/source-code"),
|
25
25
|
Traverser = require("./util/traverser"),
|
26
|
-
|
26
|
+
createReportTranslator = require("./report-translator"),
|
27
27
|
Rules = require("./rules"),
|
28
28
|
timing = require("./timing"),
|
29
29
|
astUtils = require("./ast-utils"),
|
@@ -87,10 +87,9 @@ function parseBooleanConfig(string, comment) {
|
|
87
87
|
* Parses a JSON-like config.
|
88
88
|
* @param {string} string The string to parse.
|
89
89
|
* @param {Object} location Start line and column of comments for potential error message.
|
90
|
-
* @
|
91
|
-
* @returns {Object} Result map object
|
90
|
+
* @returns {({success: true, config: Object}|{success: false, error: Problem})} Result map object
|
92
91
|
*/
|
93
|
-
function parseJsonConfig(string, location
|
92
|
+
function parseJsonConfig(string, location) {
|
94
93
|
let items = {};
|
95
94
|
|
96
95
|
// Parses a JSON-like comment by the same way as parsing CLI option.
|
@@ -102,7 +101,10 @@ function parseJsonConfig(string, location, messages) {
|
|
102
101
|
// "no-alert: 2 no-console: 2" --> {"no-alert": "2 no-console: 2"}
|
103
102
|
// Should ignore that case as well.
|
104
103
|
if (ConfigOps.isEverySeverityValid(items)) {
|
105
|
-
return
|
104
|
+
return {
|
105
|
+
success: true,
|
106
|
+
config: items
|
107
|
+
};
|
106
108
|
}
|
107
109
|
} catch (ex) {
|
108
110
|
|
@@ -116,20 +118,25 @@ function parseJsonConfig(string, location, messages) {
|
|
116
118
|
try {
|
117
119
|
items = JSON.parse(`{${string}}`);
|
118
120
|
} catch (ex) {
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
121
|
+
return {
|
122
|
+
success: false,
|
123
|
+
error: {
|
124
|
+
ruleId: null,
|
125
|
+
fatal: true,
|
126
|
+
severity: 2,
|
127
|
+
source: null,
|
128
|
+
message: `Failed to parse JSON from '${string}': ${ex.message}`,
|
129
|
+
line: location.start.line,
|
130
|
+
column: location.start.column + 1
|
131
|
+
}
|
132
|
+
};
|
129
133
|
|
130
134
|
}
|
131
135
|
|
132
|
-
return
|
136
|
+
return {
|
137
|
+
success: true,
|
138
|
+
config: items
|
139
|
+
};
|
133
140
|
}
|
134
141
|
|
135
142
|
/**
|
@@ -171,14 +178,12 @@ function addDeclaredGlobals(program, globalScope, config, envContext) {
|
|
171
178
|
|
172
179
|
Object.assign(declaredGlobals, builtin);
|
173
180
|
|
174
|
-
Object.keys(config.env).forEach(name => {
|
175
|
-
|
176
|
-
|
177
|
-
environmentGlobals = env && env.globals;
|
181
|
+
Object.keys(config.env).filter(name => config.env[name]).forEach(name => {
|
182
|
+
const env = envContext.get(name),
|
183
|
+
environmentGlobals = env && env.globals;
|
178
184
|
|
179
|
-
|
180
|
-
|
181
|
-
}
|
185
|
+
if (environmentGlobals) {
|
186
|
+
Object.assign(declaredGlobals, environmentGlobals);
|
182
187
|
}
|
183
188
|
});
|
184
189
|
|
@@ -246,68 +251,23 @@ function addDeclaredGlobals(program, globalScope, config, envContext) {
|
|
246
251
|
}
|
247
252
|
|
248
253
|
/**
|
249
|
-
*
|
250
|
-
*
|
251
|
-
* @param
|
252
|
-
* @param
|
253
|
-
*
|
254
|
-
* @returns {
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
reportingConfig.push({
|
261
|
-
start,
|
262
|
-
end: null,
|
263
|
-
rule
|
264
|
-
});
|
265
|
-
});
|
266
|
-
} else {
|
267
|
-
reportingConfig.push({
|
268
|
-
start,
|
269
|
-
end: null,
|
270
|
-
rule: null
|
271
|
-
});
|
272
|
-
}
|
273
|
-
}
|
274
|
-
|
275
|
-
/**
|
276
|
-
* Add data to reporting configuration to enable reporting for list of rules
|
277
|
-
* starting from start location
|
278
|
-
* @param {Object[]} reportingConfig Current reporting configuration
|
279
|
-
* @param {Object} start Position to start
|
280
|
-
* @param {string[]} rulesToEnable List of rules
|
281
|
-
* @returns {void}
|
254
|
+
* Creates a collection of disable directives from a comment
|
255
|
+
* @param {("disable"|"enable"|"disable-line"|"disable-next-line")} type The type of directive comment
|
256
|
+
* @param {{line: number, column: number}} loc The 0-based location of the comment token
|
257
|
+
* @param {string} value The value after the directive in the comment
|
258
|
+
* comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`)
|
259
|
+
* @returns {{
|
260
|
+
* type: ("disable"|"enable"|"disable-line"|"disable-next-line"),
|
261
|
+
* line: number,
|
262
|
+
* column: number,
|
263
|
+
* ruleId: (string|null)
|
264
|
+
* }[]} Directives from the comment
|
282
265
|
*/
|
283
|
-
function
|
284
|
-
|
285
|
-
|
286
|
-
if (rulesToEnable.length) {
|
287
|
-
rulesToEnable.forEach(rule => {
|
288
|
-
for (i = reportingConfig.length - 1; i >= 0; i--) {
|
289
|
-
if (!reportingConfig[i].end && reportingConfig[i].rule === rule) {
|
290
|
-
reportingConfig[i].end = start;
|
291
|
-
break;
|
292
|
-
}
|
293
|
-
}
|
294
|
-
});
|
295
|
-
} else {
|
296
|
-
|
297
|
-
// find all previous disabled locations if they was started as list of rules
|
298
|
-
let prevStart;
|
299
|
-
|
300
|
-
for (i = reportingConfig.length - 1; i >= 0; i--) {
|
301
|
-
if (prevStart && prevStart !== reportingConfig[i].start) {
|
302
|
-
break;
|
303
|
-
}
|
266
|
+
function createDisableDirectives(type, loc, value) {
|
267
|
+
const ruleIds = Object.keys(parseListConfig(value));
|
268
|
+
const directiveRules = ruleIds.length ? ruleIds : [null];
|
304
269
|
|
305
|
-
|
306
|
-
reportingConfig[i].end = start;
|
307
|
-
prevStart = reportingConfig[i].start;
|
308
|
-
}
|
309
|
-
}
|
310
|
-
}
|
270
|
+
return directiveRules.map(ruleId => ({ type, line: loc.line, column: loc.column + 1, ruleId }));
|
311
271
|
}
|
312
272
|
|
313
273
|
/**
|
@@ -318,7 +278,17 @@ function enableReporting(reportingConfig, start, rulesToEnable) {
|
|
318
278
|
* @param {ASTNode} ast The top node of the AST.
|
319
279
|
* @param {Object} config The existing configuration data.
|
320
280
|
* @param {Linter} linterContext Linter context object
|
321
|
-
* @returns {
|
281
|
+
* @returns {{
|
282
|
+
* config: Object,
|
283
|
+
* problems: Problem[],
|
284
|
+
* disableDirectives: {
|
285
|
+
* type: ("disable"|"enable"|"disable-line"|"disable-next-line"),
|
286
|
+
* line: number,
|
287
|
+
* column: number,
|
288
|
+
* ruleId: (string|null)
|
289
|
+
* }[]
|
290
|
+
* }} Modified config object, along with any problems encountered
|
291
|
+
* while parsing config comments
|
322
292
|
*/
|
323
293
|
function modifyConfigsFromComments(filename, ast, config, linterContext) {
|
324
294
|
|
@@ -329,10 +299,10 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) {
|
|
329
299
|
env: {}
|
330
300
|
};
|
331
301
|
const commentRules = {};
|
332
|
-
const
|
333
|
-
const
|
302
|
+
const problems = [];
|
303
|
+
const disableDirectives = [];
|
334
304
|
|
335
|
-
ast.comments.forEach(comment => {
|
305
|
+
ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
|
336
306
|
|
337
307
|
let value = comment.value.trim();
|
338
308
|
const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(value);
|
@@ -356,22 +326,27 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) {
|
|
356
326
|
break;
|
357
327
|
|
358
328
|
case "eslint-disable":
|
359
|
-
|
329
|
+
[].push.apply(disableDirectives, createDisableDirectives("disable", comment.loc.start, value));
|
360
330
|
break;
|
361
331
|
|
362
332
|
case "eslint-enable":
|
363
|
-
|
333
|
+
[].push.apply(disableDirectives, createDisableDirectives("enable", comment.loc.start, value));
|
364
334
|
break;
|
365
335
|
|
366
336
|
case "eslint": {
|
367
|
-
const
|
337
|
+
const parseResult = parseJsonConfig(value, comment.loc);
|
338
|
+
|
339
|
+
if (parseResult.success) {
|
340
|
+
Object.keys(parseResult.config).forEach(name => {
|
341
|
+
const ruleValue = parseResult.config[name];
|
368
342
|
|
369
|
-
|
370
|
-
|
343
|
+
validator.validateRuleOptions(name, ruleValue, `${filename} line ${comment.loc.start.line}`, linterContext.rules);
|
344
|
+
commentRules[name] = ruleValue;
|
345
|
+
});
|
346
|
+
} else {
|
347
|
+
problems.push(parseResult.error);
|
348
|
+
}
|
371
349
|
|
372
|
-
validator.validateRuleOptions(name, ruleValue, `${filename} line ${comment.loc.start.line}`, linterContext.rules);
|
373
|
-
commentRules[name] = ruleValue;
|
374
|
-
});
|
375
350
|
break;
|
376
351
|
}
|
377
352
|
|
@@ -379,11 +354,9 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) {
|
|
379
354
|
}
|
380
355
|
} else { // comment.type === "Line"
|
381
356
|
if (match[1] === "eslint-disable-line") {
|
382
|
-
|
383
|
-
enableReporting(reportingConfig, comment.loc.end, Object.keys(parseListConfig(value)));
|
357
|
+
[].push.apply(disableDirectives, createDisableDirectives("disable-line", comment.loc.start, value));
|
384
358
|
} else if (match[1] === "eslint-disable-next-line") {
|
385
|
-
|
386
|
-
enableReporting(reportingConfig, { line: comment.loc.start.line + 2 }, Object.keys(parseListConfig(value)));
|
359
|
+
[].push.apply(disableDirectives, createDisableDirectives("disable-next-line", comment.loc.start, value));
|
387
360
|
}
|
388
361
|
}
|
389
362
|
}
|
@@ -399,30 +372,11 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) {
|
|
399
372
|
});
|
400
373
|
Object.assign(commentConfig.rules, commentRules);
|
401
374
|
|
402
|
-
return
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
* @param {Object[]} reportingConfig Collection of ignore records
|
408
|
-
* @param {string} ruleId Id of rule
|
409
|
-
* @param {Object} location Location of message
|
410
|
-
* @returns {boolean} True if message should be ignored, false otherwise
|
411
|
-
*/
|
412
|
-
function isDisabledByReportingConfig(reportingConfig, ruleId, location) {
|
413
|
-
|
414
|
-
for (let i = 0, c = reportingConfig.length; i < c; i++) {
|
415
|
-
|
416
|
-
const ignore = reportingConfig[i];
|
417
|
-
|
418
|
-
if ((!ignore.rule || ignore.rule === ruleId) &&
|
419
|
-
(location.line > ignore.start.line || (location.line === ignore.start.line && location.column >= ignore.start.column)) &&
|
420
|
-
(!ignore.end || (location.line < ignore.end.line || (location.line === ignore.end.line && location.column <= ignore.end.column)))) {
|
421
|
-
return true;
|
422
|
-
}
|
423
|
-
}
|
424
|
-
|
425
|
-
return false;
|
375
|
+
return {
|
376
|
+
config: ConfigOps.merge(config, commentConfig),
|
377
|
+
problems,
|
378
|
+
disableDirectives
|
379
|
+
};
|
426
380
|
}
|
427
381
|
|
428
382
|
/**
|
@@ -505,51 +459,6 @@ function prepareConfig(config, envContext) {
|
|
505
459
|
return preparedConfig;
|
506
460
|
}
|
507
461
|
|
508
|
-
/**
|
509
|
-
* Provide a stub rule with a given message
|
510
|
-
* @param {string} message The message to be displayed for the rule
|
511
|
-
* @returns {Function} Stub rule function
|
512
|
-
*/
|
513
|
-
function createStubRule(message) {
|
514
|
-
|
515
|
-
/**
|
516
|
-
* Creates a fake rule object
|
517
|
-
* @param {Object} context context object for each rule
|
518
|
-
* @returns {Object} collection of node to listen on
|
519
|
-
*/
|
520
|
-
function createRuleModule(context) {
|
521
|
-
return {
|
522
|
-
Program() {
|
523
|
-
context.report({
|
524
|
-
loc: { line: 1, column: 0 },
|
525
|
-
message
|
526
|
-
});
|
527
|
-
}
|
528
|
-
};
|
529
|
-
}
|
530
|
-
|
531
|
-
if (message) {
|
532
|
-
return createRuleModule;
|
533
|
-
}
|
534
|
-
throw new Error("No message passed to stub rule");
|
535
|
-
|
536
|
-
}
|
537
|
-
|
538
|
-
/**
|
539
|
-
* Provide a rule replacement message
|
540
|
-
* @param {string} ruleId Name of the rule
|
541
|
-
* @returns {string} Message detailing rule replacement
|
542
|
-
*/
|
543
|
-
function getRuleReplacementMessage(ruleId) {
|
544
|
-
if (ruleId in replacements.rules) {
|
545
|
-
const newRules = replacements.rules[ruleId];
|
546
|
-
|
547
|
-
return `Rule '${ruleId}' was removed and replaced by: ${newRules.join(", ")}`;
|
548
|
-
}
|
549
|
-
|
550
|
-
return null;
|
551
|
-
}
|
552
|
-
|
553
462
|
const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//g;
|
554
463
|
|
555
464
|
/**
|
@@ -588,22 +497,6 @@ function stripUnicodeBOM(text) {
|
|
588
497
|
return text;
|
589
498
|
}
|
590
499
|
|
591
|
-
/**
|
592
|
-
* Get the severity level of a rule (0 - none, 1 - warning, 2 - error)
|
593
|
-
* Returns 0 if the rule config is not valid (an Array or a number)
|
594
|
-
* @param {Array|number} ruleConfig rule configuration
|
595
|
-
* @returns {number} 0, 1, or 2, indicating rule severity
|
596
|
-
*/
|
597
|
-
function getRuleSeverity(ruleConfig) {
|
598
|
-
if (typeof ruleConfig === "number") {
|
599
|
-
return ruleConfig;
|
600
|
-
} else if (Array.isArray(ruleConfig)) {
|
601
|
-
return ruleConfig[0];
|
602
|
-
}
|
603
|
-
return 0;
|
604
|
-
|
605
|
-
}
|
606
|
-
|
607
500
|
/**
|
608
501
|
* Get the options for a rule (not including severity), if any
|
609
502
|
* @param {Array|number} ruleConfig rule configuration
|
@@ -622,45 +515,41 @@ function getRuleOptions(ruleConfig) {
|
|
622
515
|
* optimization of functions, so it's best to keep the try-catch as isolated
|
623
516
|
* as possible
|
624
517
|
* @param {string} text The text to parse.
|
625
|
-
* @param {Object}
|
518
|
+
* @param {Object} providedParserOptions Options to pass to the parser
|
519
|
+
* @param {string} parserName The name of the parser
|
626
520
|
* @param {string} filePath The path to the file being parsed.
|
627
|
-
* @returns {
|
628
|
-
*
|
629
|
-
* @param {Array<Object>} messages Messages array for the linter object
|
630
|
-
* @returns {*} parsed text if successful otherwise null
|
521
|
+
* @returns {{success: false, error: Problem}|{success: true,ast: ASTNode, services: Object}}
|
522
|
+
* An object containing the AST and parser services if parsing was successful, or the error if parsing failed
|
631
523
|
* @private
|
632
524
|
*/
|
633
|
-
function parse(text,
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
525
|
+
function parse(text, providedParserOptions, parserName, filePath) {
|
526
|
+
|
527
|
+
const parserOptions = Object.assign({}, providedParserOptions, {
|
528
|
+
loc: true,
|
529
|
+
range: true,
|
530
|
+
raw: true,
|
531
|
+
tokens: true,
|
532
|
+
comment: true,
|
533
|
+
filePath
|
534
|
+
});
|
535
|
+
|
536
|
+
let parser;
|
644
537
|
|
645
538
|
try {
|
646
|
-
parser = require(
|
539
|
+
parser = require(parserName);
|
647
540
|
} catch (ex) {
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
// merge in any additional parser options
|
662
|
-
if (config.parserOptions) {
|
663
|
-
parserOptions = Object.assign({}, config.parserOptions, parserOptions);
|
541
|
+
return {
|
542
|
+
success: false,
|
543
|
+
error: {
|
544
|
+
ruleId: null,
|
545
|
+
fatal: true,
|
546
|
+
severity: 2,
|
547
|
+
source: null,
|
548
|
+
message: ex.message,
|
549
|
+
line: 0,
|
550
|
+
column: 0
|
551
|
+
}
|
552
|
+
};
|
664
553
|
}
|
665
554
|
|
666
555
|
/*
|
@@ -671,31 +560,160 @@ function parse(text, config, filePath, messages) {
|
|
671
560
|
*/
|
672
561
|
try {
|
673
562
|
if (typeof parser.parseForESLint === "function") {
|
674
|
-
|
563
|
+
const parseResult = parser.parseForESLint(text, parserOptions);
|
564
|
+
|
565
|
+
return {
|
566
|
+
success: true,
|
567
|
+
ast: parseResult.ast,
|
568
|
+
services: parseResult.services || {}
|
569
|
+
};
|
675
570
|
}
|
676
|
-
return parser.parse(text, parserOptions);
|
677
571
|
|
572
|
+
return {
|
573
|
+
success: true,
|
574
|
+
ast: parser.parse(text, parserOptions),
|
575
|
+
services: {}
|
576
|
+
};
|
678
577
|
} catch (ex) {
|
679
578
|
|
680
579
|
// If the message includes a leading line number, strip it:
|
681
|
-
const message = ex.message.replace(/^line \d+:/i, "").trim()
|
682
|
-
const source =
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
580
|
+
const message = `Parsing error: ${ex.message.replace(/^line \d+:/i, "").trim()}`;
|
581
|
+
const source = ex.lineNumber ? SourceCode.splitLines(text)[ex.lineNumber - 1] : null;
|
582
|
+
|
583
|
+
return {
|
584
|
+
success: false,
|
585
|
+
error: {
|
586
|
+
ruleId: null,
|
587
|
+
fatal: true,
|
588
|
+
severity: 2,
|
589
|
+
source,
|
590
|
+
message,
|
591
|
+
line: ex.lineNumber,
|
592
|
+
column: ex.column
|
593
|
+
}
|
594
|
+
};
|
595
|
+
}
|
596
|
+
}
|
597
|
+
|
598
|
+
/**
|
599
|
+
* Gets the scope for the current node
|
600
|
+
* @param {ScopeManager} scopeManager The scope manager for this AST
|
601
|
+
* @param {ASTNode} currentNode The node to get the scope of
|
602
|
+
* @param {number} ecmaVersion The `ecmaVersion` setting that this code was parsed with
|
603
|
+
* @returns {eslint-scope.Scope} The scope information for this node
|
604
|
+
*/
|
605
|
+
function getScope(scopeManager, currentNode, ecmaVersion) {
|
606
|
+
let initialNode;
|
607
|
+
|
608
|
+
// if current node introduces a scope, add it to the list
|
609
|
+
if (
|
610
|
+
["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(currentNode.type) >= 0 ||
|
611
|
+
ecmaVersion >= 6 && ["BlockStatement", "SwitchStatement", "CatchClause"].indexOf(currentNode.type) >= 0
|
612
|
+
) {
|
613
|
+
initialNode = currentNode;
|
614
|
+
} else {
|
615
|
+
initialNode = currentNode.parent;
|
616
|
+
}
|
617
|
+
|
618
|
+
// Ascend the current node's parents
|
619
|
+
for (let node = initialNode; node; node = node.parent) {
|
620
|
+
|
621
|
+
// Get the innermost scope
|
622
|
+
const scope = scopeManager.acquire(node, true);
|
623
|
+
|
624
|
+
if (scope) {
|
625
|
+
if (scope.type === "function-expression-name") {
|
626
|
+
return scope.childScopes[0];
|
627
|
+
}
|
628
|
+
return scope;
|
629
|
+
}
|
630
|
+
}
|
631
|
+
|
632
|
+
return scopeManager.scopes[0];
|
633
|
+
}
|
634
|
+
|
635
|
+
/**
|
636
|
+
* Marks a variable as used in the current scope
|
637
|
+
* @param {ScopeManager} scopeManager The scope manager for this AST. The scope may be mutated by this function.
|
638
|
+
* @param {ASTNode} currentNode The node currently being traversed
|
639
|
+
* @param {Object} parserOptions The options used to parse this text
|
640
|
+
* @param {string} name The name of the variable that should be marked as used.
|
641
|
+
* @returns {boolean} True if the variable was found and marked as used, false if not.
|
642
|
+
*/
|
643
|
+
function markVariableAsUsed(scopeManager, currentNode, parserOptions, name) {
|
644
|
+
const hasGlobalReturn = parserOptions.ecmaFeatures && parserOptions.ecmaFeatures.globalReturn;
|
645
|
+
const specialScope = hasGlobalReturn || parserOptions.sourceType === "module";
|
646
|
+
const currentScope = getScope(scopeManager, currentNode, parserOptions.ecmaVersion);
|
647
|
+
|
648
|
+
// Special Node.js scope means we need to start one level deeper
|
649
|
+
const initialScope = currentScope.type === "global" && specialScope ? currentScope.childScopes[0] : currentScope;
|
650
|
+
|
651
|
+
for (let scope = initialScope; scope; scope = scope.upper) {
|
652
|
+
const variable = scope.variables.find(scopeVar => scopeVar.name === name);
|
653
|
+
|
654
|
+
if (variable) {
|
655
|
+
variable.eslintUsed = true;
|
656
|
+
return true;
|
657
|
+
}
|
658
|
+
}
|
659
|
+
|
660
|
+
return false;
|
661
|
+
}
|
694
662
|
|
695
|
-
|
663
|
+
/**
|
664
|
+
* Gets all the ancestors of a given node
|
665
|
+
* @param {ASTNode} node The node
|
666
|
+
* @returns {ASTNode[]} All the ancestor nodes in the AST, not including the provided node, starting
|
667
|
+
* from the root node and going inwards to the parent node.
|
668
|
+
*/
|
669
|
+
function getAncestors(node) {
|
670
|
+
if (node.parent) {
|
671
|
+
const parentAncestors = getAncestors(node.parent);
|
672
|
+
|
673
|
+
parentAncestors.push(node.parent);
|
674
|
+
return parentAncestors;
|
696
675
|
}
|
676
|
+
return [];
|
697
677
|
}
|
698
678
|
|
679
|
+
// methods that exist on SourceCode object
|
680
|
+
const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
|
681
|
+
getSource: "getText",
|
682
|
+
getSourceLines: "getLines",
|
683
|
+
getAllComments: "getAllComments",
|
684
|
+
getNodeByRangeIndex: "getNodeByRangeIndex",
|
685
|
+
getComments: "getComments",
|
686
|
+
getCommentsBefore: "getCommentsBefore",
|
687
|
+
getCommentsAfter: "getCommentsAfter",
|
688
|
+
getCommentsInside: "getCommentsInside",
|
689
|
+
getJSDocComment: "getJSDocComment",
|
690
|
+
getFirstToken: "getFirstToken",
|
691
|
+
getFirstTokens: "getFirstTokens",
|
692
|
+
getLastToken: "getLastToken",
|
693
|
+
getLastTokens: "getLastTokens",
|
694
|
+
getTokenAfter: "getTokenAfter",
|
695
|
+
getTokenBefore: "getTokenBefore",
|
696
|
+
getTokenByRangeStart: "getTokenByRangeStart",
|
697
|
+
getTokens: "getTokens",
|
698
|
+
getTokensAfter: "getTokensAfter",
|
699
|
+
getTokensBefore: "getTokensBefore",
|
700
|
+
getTokensBetween: "getTokensBetween"
|
701
|
+
};
|
702
|
+
|
703
|
+
const BASE_TRAVERSAL_CONTEXT = Object.freeze(
|
704
|
+
Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).reduce(
|
705
|
+
(contextInfo, methodName) =>
|
706
|
+
Object.assign(contextInfo, {
|
707
|
+
[methodName]() {
|
708
|
+
const sourceCode = this.getSourceCode();
|
709
|
+
|
710
|
+
return sourceCode[DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]].apply(sourceCode, arguments);
|
711
|
+
}
|
712
|
+
}),
|
713
|
+
{}
|
714
|
+
)
|
715
|
+
);
|
716
|
+
|
699
717
|
//------------------------------------------------------------------------------
|
700
718
|
// Public Interface
|
701
719
|
//------------------------------------------------------------------------------
|
@@ -704,40 +722,14 @@ function parse(text, config, filePath, messages) {
|
|
704
722
|
* Object that is responsible for verifying JavaScript text
|
705
723
|
* @name eslint
|
706
724
|
*/
|
707
|
-
class Linter
|
725
|
+
module.exports = class Linter {
|
708
726
|
|
709
727
|
constructor() {
|
710
|
-
super();
|
711
|
-
this.messages = [];
|
712
|
-
this.currentConfig = null;
|
713
|
-
this.currentScopes = null;
|
714
|
-
this.scopeManager = null;
|
715
|
-
this.currentFilename = null;
|
716
|
-
this.traverser = null;
|
717
|
-
this.reportingConfig = [];
|
718
728
|
this.sourceCode = null;
|
719
729
|
this.version = pkg.version;
|
720
730
|
|
721
731
|
this.rules = new Rules();
|
722
732
|
this.environments = new Environments();
|
723
|
-
|
724
|
-
// set unlimited listeners (see https://github.com/eslint/eslint/issues/524)
|
725
|
-
this.setMaxListeners(0);
|
726
|
-
}
|
727
|
-
|
728
|
-
/**
|
729
|
-
* Resets the internal state of the object.
|
730
|
-
* @returns {void}
|
731
|
-
*/
|
732
|
-
reset() {
|
733
|
-
this.removeAllListeners();
|
734
|
-
this.messages = [];
|
735
|
-
this.currentConfig = null;
|
736
|
-
this.currentScopes = null;
|
737
|
-
this.scopeManager = null;
|
738
|
-
this.traverser = null;
|
739
|
-
this.reportingConfig = [];
|
740
|
-
this.sourceCode = null;
|
741
733
|
}
|
742
734
|
|
743
735
|
/**
|
@@ -752,39 +744,42 @@ class Linter extends EventEmitter {
|
|
752
744
|
*/
|
753
745
|
|
754
746
|
/**
|
755
|
-
*
|
747
|
+
* Same as linter.verify, except without support for processors.
|
756
748
|
* @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
|
757
749
|
* @param {ESLintConfig} config An ESLintConfig instance to configure everything.
|
758
750
|
* @param {(string|Object)} [filenameOrOptions] The optional filename of the file being checked.
|
759
751
|
* If this is not set, the filename will default to '<input>' in the rule context. If
|
760
752
|
* an object, then it has "filename", "saveState", and "allowInlineConfig" properties.
|
761
|
-
* @param {boolean} [saveState] Indicates if the state from the last run should be saved.
|
762
|
-
* Mostly useful for testing purposes.
|
763
753
|
* @param {boolean} [filenameOrOptions.allowInlineConfig] Allow/disallow inline comments' ability to change config once it is set. Defaults to true if not supplied.
|
764
754
|
* Useful if you want to validate JS without comments overriding rules.
|
765
755
|
* @returns {Object[]} The results as an array of messages or null if no messages.
|
766
756
|
*/
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
757
|
+
_verifyWithoutProcessors(textOrSourceCode, config, filenameOrOptions) {
|
758
|
+
let text,
|
759
|
+
parserServices,
|
760
|
+
allowInlineConfig,
|
761
|
+
providedFilename;
|
772
762
|
|
773
763
|
// evaluate arguments
|
774
764
|
if (typeof filenameOrOptions === "object") {
|
775
|
-
|
765
|
+
providedFilename = filenameOrOptions.filename;
|
776
766
|
allowInlineConfig = filenameOrOptions.allowInlineConfig;
|
777
|
-
saveState = filenameOrOptions.saveState;
|
778
767
|
} else {
|
779
|
-
|
768
|
+
providedFilename = filenameOrOptions;
|
780
769
|
}
|
781
770
|
|
782
|
-
|
783
|
-
|
771
|
+
const filename = typeof providedFilename === "string" ? providedFilename : "<input>";
|
772
|
+
|
773
|
+
if (typeof textOrSourceCode === "string") {
|
774
|
+
this.sourceCode = null;
|
775
|
+
text = textOrSourceCode;
|
776
|
+
} else {
|
777
|
+
this.sourceCode = textOrSourceCode;
|
778
|
+
text = this.sourceCode.text;
|
784
779
|
}
|
785
780
|
|
786
781
|
// search and apply "eslint-env *".
|
787
|
-
const envInFile = findEslintEnv(text
|
782
|
+
const envInFile = findEslintEnv(text);
|
788
783
|
|
789
784
|
config = Object.assign({}, config);
|
790
785
|
|
@@ -799,231 +794,233 @@ class Linter extends EventEmitter {
|
|
799
794
|
// process initial config to make it safe to extend
|
800
795
|
config = prepareConfig(config, this.environments);
|
801
796
|
|
802
|
-
|
803
|
-
|
797
|
+
if (this.sourceCode) {
|
798
|
+
parserServices = {};
|
799
|
+
} else {
|
804
800
|
|
805
801
|
// there's no input, just exit here
|
806
802
|
if (text.trim().length === 0) {
|
807
803
|
this.sourceCode = new SourceCode(text, blankScriptAST);
|
808
|
-
return
|
804
|
+
return [];
|
809
805
|
}
|
810
806
|
|
811
|
-
parseResult = parse(
|
807
|
+
const parseResult = parse(
|
812
808
|
stripUnicodeBOM(text).replace(astUtils.SHEBANG_MATCHER, (match, captured) => `//${captured}`),
|
813
|
-
config,
|
814
|
-
|
815
|
-
|
809
|
+
config.parserOptions,
|
810
|
+
config.parser,
|
811
|
+
filename
|
816
812
|
);
|
817
813
|
|
818
|
-
|
819
|
-
|
820
|
-
ast = parseResult.ast;
|
821
|
-
} else {
|
822
|
-
ast = parseResult;
|
823
|
-
parseResult = null;
|
814
|
+
if (!parseResult.success) {
|
815
|
+
return [parseResult.error];
|
824
816
|
}
|
825
817
|
|
826
|
-
|
827
|
-
|
828
|
-
}
|
829
|
-
|
830
|
-
} else {
|
831
|
-
this.sourceCode = textOrSourceCode;
|
832
|
-
ast = this.sourceCode.ast;
|
818
|
+
parserServices = parseResult.services;
|
819
|
+
this.sourceCode = new SourceCode(text, parseResult.ast);
|
833
820
|
}
|
834
821
|
|
835
|
-
|
836
|
-
|
822
|
+
const problems = [];
|
823
|
+
const sourceCode = this.sourceCode;
|
824
|
+
let disableDirectives;
|
837
825
|
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
}
|
826
|
+
// parse global comments and modify config
|
827
|
+
if (allowInlineConfig !== false) {
|
828
|
+
const modifyConfigResult = modifyConfigsFromComments(filename, sourceCode.ast, config, this);
|
842
829
|
|
843
|
-
|
844
|
-
|
830
|
+
config = modifyConfigResult.config;
|
831
|
+
modifyConfigResult.problems.forEach(problem => problems.push(problem));
|
832
|
+
disableDirectives = modifyConfigResult.disableDirectives;
|
833
|
+
} else {
|
834
|
+
disableDirectives = [];
|
835
|
+
}
|
845
836
|
|
846
|
-
|
847
|
-
|
848
|
-
|
837
|
+
const emitter = new EventEmitter().setMaxListeners(Infinity);
|
838
|
+
const ecmaFeatures = config.parserOptions.ecmaFeatures || {};
|
839
|
+
const ecmaVersion = config.parserOptions.ecmaVersion || 5;
|
840
|
+
const scopeManager = eslintScope.analyze(sourceCode.ast, {
|
841
|
+
ignoreEval: true,
|
842
|
+
nodejsScope: ecmaFeatures.globalReturn,
|
843
|
+
impliedStrict: ecmaFeatures.impliedStrict,
|
844
|
+
ecmaVersion,
|
845
|
+
sourceType: config.parserOptions.sourceType || "script",
|
846
|
+
fallback: Traverser.getKeys
|
847
|
+
});
|
849
848
|
|
850
|
-
|
849
|
+
let currentNode = sourceCode.ast;
|
850
|
+
const nodeQueue = [];
|
851
851
|
|
852
|
-
|
853
|
-
|
852
|
+
new Traverser().traverse(sourceCode.ast, {
|
853
|
+
enter(node, parent) {
|
854
|
+
node.parent = parent;
|
855
|
+
nodeQueue.push({ isEntering: true, node });
|
856
|
+
},
|
857
|
+
leave(node) {
|
858
|
+
nodeQueue.push({ isEntering: false, node });
|
859
|
+
}
|
860
|
+
});
|
854
861
|
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
862
|
+
/*
|
863
|
+
* Create a frozen object with the ruleContext properties and methods that are shared by all rules.
|
864
|
+
* All rule contexts will inherit from this object. This avoids the performance penalty of copying all the
|
865
|
+
* properties once for each rule.
|
866
|
+
*/
|
867
|
+
const sharedTraversalContext = Object.freeze(
|
868
|
+
Object.assign(
|
869
|
+
Object.create(BASE_TRAVERSAL_CONTEXT),
|
870
|
+
{
|
871
|
+
getAncestors: () => getAncestors(currentNode),
|
872
|
+
getDeclaredVariables: scopeManager.getDeclaredVariables.bind(scopeManager),
|
873
|
+
getFilename: () => filename,
|
874
|
+
getScope: () => getScope(scopeManager, currentNode, config.parserOptions.ecmaVersion),
|
875
|
+
getSourceCode: () => sourceCode,
|
876
|
+
markVariableAsUsed: name => markVariableAsUsed(scopeManager, currentNode, config.parserOptions, name),
|
877
|
+
parserOptions: config.parserOptions,
|
878
|
+
parserPath: config.parser,
|
879
|
+
parserServices,
|
880
|
+
settings: config.settings,
|
881
|
+
|
882
|
+
/**
|
883
|
+
* This is used to avoid breaking rules that used to monkeypatch the `Linter#report` method
|
884
|
+
* by using the `_linter` property on rule contexts.
|
885
|
+
*
|
886
|
+
* This should be removed in a major release after we create a better way to
|
887
|
+
* lint for unused disable comments.
|
888
|
+
* https://github.com/eslint/eslint/issues/9193
|
889
|
+
*/
|
890
|
+
_linter: {
|
891
|
+
report() {},
|
892
|
+
on: emitter.on.bind(emitter)
|
859
893
|
}
|
860
|
-
this.rules.define(key, ruleCreator);
|
861
894
|
}
|
895
|
+
)
|
896
|
+
);
|
862
897
|
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
try {
|
867
|
-
const ruleContext = new RuleContext(
|
868
|
-
key, this, severity, options,
|
869
|
-
config.settings, config.parserOptions, config.parser,
|
870
|
-
ruleCreator.meta,
|
871
|
-
(parseResult && parseResult.services ? parseResult.services : {})
|
872
|
-
);
|
873
|
-
|
874
|
-
const rule = ruleCreator.create ? ruleCreator.create(ruleContext)
|
875
|
-
: ruleCreator(ruleContext);
|
876
|
-
|
877
|
-
// add all the selectors from the rule as listeners
|
878
|
-
Object.keys(rule).forEach(selector => {
|
879
|
-
this.on(selector, timing.enabled
|
880
|
-
? timing.time(key, rule[selector])
|
881
|
-
: rule[selector]
|
882
|
-
);
|
883
|
-
});
|
884
|
-
} catch (ex) {
|
885
|
-
ex.message = `Error while loading rule '${key}': ${ex.message}`;
|
886
|
-
throw ex;
|
887
|
-
}
|
888
|
-
});
|
889
|
-
|
890
|
-
// save config so rules can access as necessary
|
891
|
-
this.currentConfig = config;
|
892
|
-
this.traverser = new Traverser();
|
893
|
-
|
894
|
-
const ecmaFeatures = this.currentConfig.parserOptions.ecmaFeatures || {};
|
895
|
-
const ecmaVersion = this.currentConfig.parserOptions.ecmaVersion || 5;
|
898
|
+
// enable appropriate rules
|
899
|
+
Object.keys(config.rules).forEach(ruleId => {
|
900
|
+
const severity = ConfigOps.getRuleSeverity(config.rules[ruleId]);
|
896
901
|
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
nodejsScope: ecmaFeatures.globalReturn,
|
901
|
-
impliedStrict: ecmaFeatures.impliedStrict,
|
902
|
-
ecmaVersion,
|
903
|
-
sourceType: this.currentConfig.parserOptions.sourceType || "script",
|
904
|
-
fallback: Traverser.getKeys
|
905
|
-
});
|
902
|
+
if (severity === 0) {
|
903
|
+
return;
|
904
|
+
}
|
906
905
|
|
907
|
-
|
906
|
+
const rule = this.rules.get(ruleId);
|
907
|
+
let reportTranslator = null;
|
908
|
+
const ruleContext = Object.freeze(
|
909
|
+
Object.assign(
|
910
|
+
Object.create(sharedTraversalContext),
|
911
|
+
{
|
912
|
+
id: ruleId,
|
913
|
+
options: getRuleOptions(config.rules[ruleId]),
|
914
|
+
report() {
|
915
|
+
|
916
|
+
/*
|
917
|
+
* Create a report translator lazily.
|
918
|
+
* In a vast majority of cases, any given rule reports zero errors on a given
|
919
|
+
* piece of code. Creating a translator lazily avoids the performance cost of
|
920
|
+
* creating a new translator function for each rule that usually doesn't get
|
921
|
+
* called.
|
922
|
+
*
|
923
|
+
* Using lazy report translators improves end-to-end performance by about 3%
|
924
|
+
* with Node 8.4.0.
|
925
|
+
*/
|
926
|
+
if (reportTranslator === null) {
|
927
|
+
reportTranslator = createReportTranslator({ ruleId, severity, sourceCode });
|
928
|
+
}
|
929
|
+
const problem = reportTranslator.apply(null, arguments);
|
930
|
+
|
931
|
+
if (problem.fix && rule.meta && !rule.meta.fixable) {
|
932
|
+
throw new Error("Fixable rules should export a `meta.fixable` property.");
|
933
|
+
}
|
934
|
+
problems.push(problem);
|
935
|
+
|
936
|
+
/*
|
937
|
+
* This is used to avoid breaking rules that used monkeypatch Linter, and relied on
|
938
|
+
* `linter.report` getting called with report info every time a rule reports a problem.
|
939
|
+
* To continue to support this, make sure that `context._linter.report` is called every
|
940
|
+
* time a problem is reported by a rule, even though `context._linter` is no longer a
|
941
|
+
* `Linter` instance.
|
942
|
+
*
|
943
|
+
* This should be removed in a major release after we create a better way to
|
944
|
+
* lint for unused disable comments.
|
945
|
+
* https://github.com/eslint/eslint/issues/9193
|
946
|
+
*/
|
947
|
+
sharedTraversalContext._linter.report( // eslint-disable-line no-underscore-dangle
|
948
|
+
problem.ruleId,
|
949
|
+
problem.severity,
|
950
|
+
{ loc: { start: { line: problem.line, column: problem.column - 1 } } },
|
951
|
+
problem.message
|
952
|
+
);
|
953
|
+
}
|
954
|
+
}
|
955
|
+
)
|
956
|
+
);
|
908
957
|
|
909
|
-
|
910
|
-
|
958
|
+
try {
|
959
|
+
const ruleListeners = rule.create(ruleContext);
|
911
960
|
|
912
|
-
|
961
|
+
// add all the selectors from the rule as listeners
|
962
|
+
Object.keys(ruleListeners).forEach(selector => {
|
963
|
+
emitter.on(
|
964
|
+
selector,
|
965
|
+
timing.enabled
|
966
|
+
? timing.time(ruleId, ruleListeners[selector])
|
967
|
+
: ruleListeners[selector]
|
968
|
+
);
|
969
|
+
});
|
970
|
+
} catch (ex) {
|
971
|
+
ex.message = `Error while loading rule '${ruleId}': ${ex.message}`;
|
972
|
+
throw ex;
|
973
|
+
}
|
974
|
+
});
|
913
975
|
|
914
|
-
|
976
|
+
// augment global scope with declared global variables
|
977
|
+
addDeclaredGlobals(sourceCode.ast, scopeManager.scopes[0], config, this.environments);
|
915
978
|
|
916
|
-
|
917
|
-
* Each node has a type property. Whenever a particular type of
|
918
|
-
* node is found, an event is fired. This allows any listeners to
|
919
|
-
* automatically be informed that this type of node has been found
|
920
|
-
* and react accordingly.
|
921
|
-
*/
|
922
|
-
this.traverser.traverse(ast, {
|
923
|
-
enter(node, parent) {
|
924
|
-
node.parent = parent;
|
925
|
-
eventGenerator.enterNode(node);
|
926
|
-
},
|
927
|
-
leave(node) {
|
928
|
-
eventGenerator.leaveNode(node);
|
929
|
-
}
|
930
|
-
});
|
931
|
-
}
|
979
|
+
const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter));
|
932
980
|
|
933
|
-
|
934
|
-
|
935
|
-
const lineDiff = a.line - b.line;
|
981
|
+
nodeQueue.forEach(traversalInfo => {
|
982
|
+
currentNode = traversalInfo.node;
|
936
983
|
|
937
|
-
if (
|
938
|
-
|
984
|
+
if (traversalInfo.isEntering) {
|
985
|
+
eventGenerator.enterNode(currentNode);
|
986
|
+
} else {
|
987
|
+
eventGenerator.leaveNode(currentNode);
|
939
988
|
}
|
940
|
-
return lineDiff;
|
941
|
-
|
942
989
|
});
|
943
990
|
|
944
|
-
return
|
991
|
+
return applyDisableDirectives({
|
992
|
+
directives: disableDirectives,
|
993
|
+
problems: problems.sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column)
|
994
|
+
});
|
945
995
|
}
|
946
996
|
|
947
997
|
/**
|
948
|
-
*
|
949
|
-
* @param {string}
|
950
|
-
* @param {
|
951
|
-
* @param {
|
952
|
-
*
|
953
|
-
*
|
954
|
-
*
|
955
|
-
*
|
956
|
-
* @param {
|
957
|
-
*
|
958
|
-
* @param {
|
959
|
-
*
|
960
|
-
* @
|
998
|
+
* Verifies the text against the rules specified by the second argument.
|
999
|
+
* @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
|
1000
|
+
* @param {ESLintConfig} config An ESLintConfig instance to configure everything.
|
1001
|
+
* @param {(string|Object)} [filenameOrOptions] The optional filename of the file being checked.
|
1002
|
+
* If this is not set, the filename will default to '<input>' in the rule context. If
|
1003
|
+
* an object, then it has "filename", "saveState", and "allowInlineConfig" properties.
|
1004
|
+
* @param {boolean} [saveState] Indicates if the state from the last run should be saved.
|
1005
|
+
* Mostly useful for testing purposes.
|
1006
|
+
* @param {boolean} [filenameOrOptions.allowInlineConfig] Allow/disallow inline comments' ability to change config once it is set. Defaults to true if not supplied.
|
1007
|
+
* Useful if you want to validate JS without comments overriding rules.
|
1008
|
+
* @param {function(string): string[]} [filenameOrOptions.preprocess] preprocessor for source text. If provided,
|
1009
|
+
* this should accept a string of source text, and return an array of code blocks to lint.
|
1010
|
+
* @param {function(Array<Object[]>): Object[]} [filenameOrOptions.postprocess] postprocessor for report messages. If provided,
|
1011
|
+
* this should accept an array of the message lists for each code block returned from the preprocessor,
|
1012
|
+
* apply a mapping to the messages as appropriate, and return a one-dimensional array of messages
|
1013
|
+
* @returns {Object[]} The results as an array of messages or null if no messages.
|
961
1014
|
*/
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
meta = fix;
|
973
|
-
fix = opts;
|
974
|
-
opts = message;
|
975
|
-
message = location;
|
976
|
-
location = node.loc.start;
|
977
|
-
endLocation = node.loc.end;
|
978
|
-
} else {
|
979
|
-
endLocation = location.end;
|
980
|
-
}
|
981
|
-
|
982
|
-
location = location.start || location;
|
983
|
-
|
984
|
-
if (isDisabledByReportingConfig(this.reportingConfig, ruleId, location)) {
|
985
|
-
return;
|
986
|
-
}
|
987
|
-
|
988
|
-
if (opts) {
|
989
|
-
message = message.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (fullMatch, term) => {
|
990
|
-
if (term in opts) {
|
991
|
-
return opts[term];
|
992
|
-
}
|
993
|
-
|
994
|
-
// Preserve old behavior: If parameter name not provided, don't replace it.
|
995
|
-
return fullMatch;
|
996
|
-
});
|
997
|
-
}
|
998
|
-
|
999
|
-
const problem = {
|
1000
|
-
ruleId,
|
1001
|
-
severity,
|
1002
|
-
message,
|
1003
|
-
line: location.line,
|
1004
|
-
column: location.column + 1, // switch to 1-base instead of 0-base
|
1005
|
-
nodeType: node && node.type,
|
1006
|
-
source: this.sourceCode.lines[location.line - 1] || ""
|
1007
|
-
};
|
1008
|
-
|
1009
|
-
// Define endLine and endColumn if exists.
|
1010
|
-
if (endLocation) {
|
1011
|
-
problem.endLine = endLocation.line;
|
1012
|
-
problem.endColumn = endLocation.column + 1; // switch to 1-base instead of 0-base
|
1013
|
-
}
|
1014
|
-
|
1015
|
-
// ensure there's range and text properties, otherwise it's not a valid fix
|
1016
|
-
if (fix && Array.isArray(fix.range) && (typeof fix.text === "string")) {
|
1017
|
-
|
1018
|
-
// If rule uses fix, has metadata, but has no metadata.fixable, we should throw
|
1019
|
-
if (meta && !meta.fixable) {
|
1020
|
-
throw new Error("Fixable rules should export a `meta.fixable` property.");
|
1021
|
-
}
|
1022
|
-
|
1023
|
-
problem.fix = fix;
|
1024
|
-
}
|
1025
|
-
|
1026
|
-
this.messages.push(problem);
|
1015
|
+
verify(textOrSourceCode, config, filenameOrOptions) {
|
1016
|
+
const preprocess = filenameOrOptions && filenameOrOptions.preprocess || (rawText => [rawText]);
|
1017
|
+
const postprocess = filenameOrOptions && filenameOrOptions.postprocess || lodash.flatten;
|
1018
|
+
|
1019
|
+
return postprocess(
|
1020
|
+
preprocess(textOrSourceCode).map(
|
1021
|
+
textBlock => this._verifyWithoutProcessors(textBlock, config, filenameOrOptions)
|
1022
|
+
)
|
1023
|
+
);
|
1027
1024
|
}
|
1028
1025
|
|
1029
1026
|
/**
|
@@ -1034,103 +1031,6 @@ class Linter extends EventEmitter {
|
|
1034
1031
|
return this.sourceCode;
|
1035
1032
|
}
|
1036
1033
|
|
1037
|
-
/**
|
1038
|
-
* Gets nodes that are ancestors of current node.
|
1039
|
-
* @returns {ASTNode[]} Array of objects representing ancestors.
|
1040
|
-
*/
|
1041
|
-
getAncestors() {
|
1042
|
-
return this.traverser.parents();
|
1043
|
-
}
|
1044
|
-
|
1045
|
-
/**
|
1046
|
-
* Gets the scope for the current node.
|
1047
|
-
* @returns {Object} An object representing the current node's scope.
|
1048
|
-
*/
|
1049
|
-
getScope() {
|
1050
|
-
const parents = this.traverser.parents();
|
1051
|
-
|
1052
|
-
// Don't do this for Program nodes - they have no parents
|
1053
|
-
if (parents.length) {
|
1054
|
-
|
1055
|
-
// if current node introduces a scope, add it to the list
|
1056
|
-
const current = this.traverser.current();
|
1057
|
-
|
1058
|
-
if (this.currentConfig.parserOptions.ecmaVersion >= 6) {
|
1059
|
-
if (["BlockStatement", "SwitchStatement", "CatchClause", "FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(current.type) >= 0) {
|
1060
|
-
parents.push(current);
|
1061
|
-
}
|
1062
|
-
} else {
|
1063
|
-
if (["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(current.type) >= 0) {
|
1064
|
-
parents.push(current);
|
1065
|
-
}
|
1066
|
-
}
|
1067
|
-
|
1068
|
-
// Ascend the current node's parents
|
1069
|
-
for (let i = parents.length - 1; i >= 0; --i) {
|
1070
|
-
|
1071
|
-
// Get the innermost scope
|
1072
|
-
const scope = this.scopeManager.acquire(parents[i], true);
|
1073
|
-
|
1074
|
-
if (scope) {
|
1075
|
-
if (scope.type === "function-expression-name") {
|
1076
|
-
return scope.childScopes[0];
|
1077
|
-
}
|
1078
|
-
return scope;
|
1079
|
-
|
1080
|
-
}
|
1081
|
-
|
1082
|
-
}
|
1083
|
-
|
1084
|
-
}
|
1085
|
-
|
1086
|
-
return this.currentScopes[0];
|
1087
|
-
}
|
1088
|
-
|
1089
|
-
/**
|
1090
|
-
* Record that a particular variable has been used in code
|
1091
|
-
* @param {string} name The name of the variable to mark as used
|
1092
|
-
* @returns {boolean} True if the variable was found and marked as used,
|
1093
|
-
* false if not.
|
1094
|
-
*/
|
1095
|
-
markVariableAsUsed(name) {
|
1096
|
-
const hasGlobalReturn = this.currentConfig.parserOptions.ecmaFeatures && this.currentConfig.parserOptions.ecmaFeatures.globalReturn,
|
1097
|
-
specialScope = hasGlobalReturn || this.currentConfig.parserOptions.sourceType === "module";
|
1098
|
-
let scope = this.getScope(),
|
1099
|
-
i,
|
1100
|
-
len;
|
1101
|
-
|
1102
|
-
// Special Node.js scope means we need to start one level deeper
|
1103
|
-
if (scope.type === "global" && specialScope) {
|
1104
|
-
scope = scope.childScopes[0];
|
1105
|
-
}
|
1106
|
-
|
1107
|
-
do {
|
1108
|
-
const variables = scope.variables;
|
1109
|
-
|
1110
|
-
for (i = 0, len = variables.length; i < len; i++) {
|
1111
|
-
if (variables[i].name === name) {
|
1112
|
-
variables[i].eslintUsed = true;
|
1113
|
-
return true;
|
1114
|
-
}
|
1115
|
-
}
|
1116
|
-
} while ((scope = scope.upper));
|
1117
|
-
|
1118
|
-
return false;
|
1119
|
-
}
|
1120
|
-
|
1121
|
-
/**
|
1122
|
-
* Gets the filename for the currently parsed source.
|
1123
|
-
* @returns {string} The filename associated with the source being parsed.
|
1124
|
-
* Defaults to "<input>" if no filename info is present.
|
1125
|
-
*/
|
1126
|
-
getFilename() {
|
1127
|
-
if (typeof this.currentFilename === "string") {
|
1128
|
-
return this.currentFilename;
|
1129
|
-
}
|
1130
|
-
return "<input>";
|
1131
|
-
|
1132
|
-
}
|
1133
|
-
|
1134
1034
|
/**
|
1135
1035
|
* Defines a new linting rule.
|
1136
1036
|
* @param {string} ruleId A unique rule identifier
|
@@ -1152,14 +1052,6 @@ class Linter extends EventEmitter {
|
|
1152
1052
|
});
|
1153
1053
|
}
|
1154
1054
|
|
1155
|
-
/**
|
1156
|
-
* Gets the default eslint configuration.
|
1157
|
-
* @returns {Object} Object mapping rule IDs to their default configurations
|
1158
|
-
*/
|
1159
|
-
defaults() { // eslint-disable-line class-methods-use-this
|
1160
|
-
return defaultConfig;
|
1161
|
-
}
|
1162
|
-
|
1163
1055
|
/**
|
1164
1056
|
* Gets an object with all loaded rules.
|
1165
1057
|
* @returns {Map} All loaded rules
|
@@ -1168,29 +1060,6 @@ class Linter extends EventEmitter {
|
|
1168
1060
|
return this.rules.getAllLoadedRules();
|
1169
1061
|
}
|
1170
1062
|
|
1171
|
-
/**
|
1172
|
-
* Gets variables that are declared by a specified node.
|
1173
|
-
*
|
1174
|
-
* The variables are its `defs[].node` or `defs[].parent` is same as the specified node.
|
1175
|
-
* Specifically, below:
|
1176
|
-
*
|
1177
|
-
* - `VariableDeclaration` - variables of its all declarators.
|
1178
|
-
* - `VariableDeclarator` - variables.
|
1179
|
-
* - `FunctionDeclaration`/`FunctionExpression` - its function name and parameters.
|
1180
|
-
* - `ArrowFunctionExpression` - its parameters.
|
1181
|
-
* - `ClassDeclaration`/`ClassExpression` - its class name.
|
1182
|
-
* - `CatchClause` - variables of its exception.
|
1183
|
-
* - `ImportDeclaration` - variables of its all specifiers.
|
1184
|
-
* - `ImportSpecifier`/`ImportDefaultSpecifier`/`ImportNamespaceSpecifier` - a variable.
|
1185
|
-
* - others - always an empty array.
|
1186
|
-
*
|
1187
|
-
* @param {ASTNode} node A node to get.
|
1188
|
-
* @returns {eslint-scope.Variable[]} Variables that are declared by the node.
|
1189
|
-
*/
|
1190
|
-
getDeclaredVariables(node) {
|
1191
|
-
return (this.scopeManager && this.scopeManager.getDeclaredVariables(node)) || [];
|
1192
|
-
}
|
1193
|
-
|
1194
1063
|
/**
|
1195
1064
|
* Performs multiple autofix passes over the text until as many fixes as possible
|
1196
1065
|
* have been applied.
|
@@ -1201,6 +1070,11 @@ class Linter extends EventEmitter {
|
|
1201
1070
|
* @param {boolean} options.allowInlineConfig Flag indicating if inline comments
|
1202
1071
|
* should be allowed.
|
1203
1072
|
* @param {boolean|Function} options.fix Determines whether fixes should be applied
|
1073
|
+
* @param {Function} options.preprocess preprocessor for source text. If provided, this should
|
1074
|
+
* accept a string of source text, and return an array of code blocks to lint.
|
1075
|
+
* @param {Function} options.postprocess postprocessor for report messages. If provided,
|
1076
|
+
* this should accept an array of the message lists for each code block returned from the preprocessor,
|
1077
|
+
* apply a mapping to the messages as appropriate, and return a one-dimensional array of messages
|
1204
1078
|
* @returns {Object} The result of the fix operation as returned from the
|
1205
1079
|
* SourceCodeFixer.
|
1206
1080
|
*/
|
@@ -1228,7 +1102,7 @@ class Linter extends EventEmitter {
|
|
1228
1102
|
messages = this.verify(text, config, options);
|
1229
1103
|
|
1230
1104
|
debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`);
|
1231
|
-
fixedResult = SourceCodeFixer.applyFixes(
|
1105
|
+
fixedResult = SourceCodeFixer.applyFixes(text, messages, shouldFix);
|
1232
1106
|
|
1233
1107
|
// stop if there are any syntax errors.
|
1234
1108
|
// 'fixedResult.output' is a empty string.
|
@@ -1261,48 +1135,4 @@ class Linter extends EventEmitter {
|
|
1261
1135
|
|
1262
1136
|
return fixedResult;
|
1263
1137
|
}
|
1264
|
-
}
|
1265
|
-
|
1266
|
-
// methods that exist on SourceCode object
|
1267
|
-
const externalMethods = {
|
1268
|
-
getSource: "getText",
|
1269
|
-
getSourceLines: "getLines",
|
1270
|
-
getAllComments: "getAllComments",
|
1271
|
-
getNodeByRangeIndex: "getNodeByRangeIndex",
|
1272
|
-
getComments: "getComments",
|
1273
|
-
getCommentsBefore: "getCommentsBefore",
|
1274
|
-
getCommentsAfter: "getCommentsAfter",
|
1275
|
-
getCommentsInside: "getCommentsInside",
|
1276
|
-
getJSDocComment: "getJSDocComment",
|
1277
|
-
getFirstToken: "getFirstToken",
|
1278
|
-
getFirstTokens: "getFirstTokens",
|
1279
|
-
getLastToken: "getLastToken",
|
1280
|
-
getLastTokens: "getLastTokens",
|
1281
|
-
getTokenAfter: "getTokenAfter",
|
1282
|
-
getTokenBefore: "getTokenBefore",
|
1283
|
-
getTokenByRangeStart: "getTokenByRangeStart",
|
1284
|
-
getTokens: "getTokens",
|
1285
|
-
getTokensAfter: "getTokensAfter",
|
1286
|
-
getTokensBefore: "getTokensBefore",
|
1287
|
-
getTokensBetween: "getTokensBetween"
|
1288
1138
|
};
|
1289
|
-
|
1290
|
-
// copy over methods
|
1291
|
-
Object.keys(externalMethods).forEach(methodName => {
|
1292
|
-
const exMethodName = externalMethods[methodName];
|
1293
|
-
|
1294
|
-
// Applies the SourceCode methods to the Linter prototype
|
1295
|
-
Object.defineProperty(Linter.prototype, methodName, {
|
1296
|
-
value() {
|
1297
|
-
if (this.sourceCode) {
|
1298
|
-
return this.sourceCode[exMethodName].apply(this.sourceCode, arguments);
|
1299
|
-
}
|
1300
|
-
return null;
|
1301
|
-
},
|
1302
|
-
configurable: true,
|
1303
|
-
writable: true,
|
1304
|
-
enumerable: false
|
1305
|
-
});
|
1306
|
-
});
|
1307
|
-
|
1308
|
-
module.exports = Linter;
|