eslint 4.4.0 → 4.6.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 +56 -0
- package/bin/eslint.js +2 -1
- package/conf/eslint-recommended.js +1 -0
- package/lib/ast-utils.js +11 -17
- 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 +18 -9
- package/lib/code-path-analysis/code-path.js +2 -1
- package/lib/code-path-analysis/fork-context.js +2 -1
- package/lib/config/config-initializer.js +3 -1
- package/lib/config.js +8 -12
- package/lib/formatters/junit.js +2 -8
- package/lib/formatters/stylish.js +2 -1
- package/lib/ignored-paths.js +0 -2
- package/lib/linter.js +320 -318
- 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 +2 -1
- package/lib/rules/indent.js +137 -64
- package/lib/rules/no-extra-parens.js +37 -32
- package/lib/rules/no-invalid-this.js +2 -1
- package/lib/rules/no-multi-spaces.js +5 -2
- 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/testers/rule-tester.js +11 -9
- package/lib/timing.js +2 -2
- 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/package.json +3 -2
- 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,10 +9,10 @@
|
|
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
18
|
replacements = require("../conf/replacements.json"),
|
@@ -23,7 +23,7 @@ const assert = require("assert"),
|
|
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
|
|
@@ -318,7 +323,8 @@ function enableReporting(reportingConfig, start, rulesToEnable) {
|
|
318
323
|
* @param {ASTNode} ast The top node of the AST.
|
319
324
|
* @param {Object} config The existing configuration data.
|
320
325
|
* @param {Linter} linterContext Linter context object
|
321
|
-
* @returns {Object} Modified config object
|
326
|
+
* @returns {{config: Object, problems: Problem[]}} Modified config object, along with any problems encountered
|
327
|
+
* while parsing config comments
|
322
328
|
*/
|
323
329
|
function modifyConfigsFromComments(filename, ast, config, linterContext) {
|
324
330
|
|
@@ -329,7 +335,7 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) {
|
|
329
335
|
env: {}
|
330
336
|
};
|
331
337
|
const commentRules = {};
|
332
|
-
const
|
338
|
+
const problems = [];
|
333
339
|
const reportingConfig = linterContext.reportingConfig;
|
334
340
|
|
335
341
|
ast.comments.forEach(comment => {
|
@@ -364,14 +370,19 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) {
|
|
364
370
|
break;
|
365
371
|
|
366
372
|
case "eslint": {
|
367
|
-
const
|
373
|
+
const parseResult = parseJsonConfig(value, comment.loc);
|
368
374
|
|
369
|
-
|
370
|
-
|
375
|
+
if (parseResult.success) {
|
376
|
+
Object.keys(parseResult.config).forEach(name => {
|
377
|
+
const ruleValue = parseResult.config[name];
|
378
|
+
|
379
|
+
validator.validateRuleOptions(name, ruleValue, `${filename} line ${comment.loc.start.line}`, linterContext.rules);
|
380
|
+
commentRules[name] = ruleValue;
|
381
|
+
});
|
382
|
+
} else {
|
383
|
+
problems.push(parseResult.error);
|
384
|
+
}
|
371
385
|
|
372
|
-
validator.validateRuleOptions(name, ruleValue, `${filename} line ${comment.loc.start.line}`, linterContext.rules);
|
373
|
-
commentRules[name] = ruleValue;
|
374
|
-
});
|
375
386
|
break;
|
376
387
|
}
|
377
388
|
|
@@ -399,14 +410,17 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) {
|
|
399
410
|
});
|
400
411
|
Object.assign(commentConfig.rules, commentRules);
|
401
412
|
|
402
|
-
return
|
413
|
+
return {
|
414
|
+
config: ConfigOps.merge(config, commentConfig),
|
415
|
+
problems
|
416
|
+
};
|
403
417
|
}
|
404
418
|
|
405
419
|
/**
|
406
420
|
* Check if message of rule with ruleId should be ignored in location
|
407
421
|
* @param {Object[]} reportingConfig Collection of ignore records
|
408
422
|
* @param {string} ruleId Id of rule
|
409
|
-
* @param {Object} location
|
423
|
+
* @param {Object} location 1-indexed location of message
|
410
424
|
* @returns {boolean} True if message should be ignored, false otherwise
|
411
425
|
*/
|
412
426
|
function isDisabledByReportingConfig(reportingConfig, ruleId, location) {
|
@@ -416,8 +430,8 @@ function isDisabledByReportingConfig(reportingConfig, ruleId, location) {
|
|
416
430
|
const ignore = reportingConfig[i];
|
417
431
|
|
418
432
|
if ((!ignore.rule || ignore.rule === ruleId) &&
|
419
|
-
(location.line > ignore.start.line || (location.line === ignore.start.line && location.column
|
420
|
-
(!ignore.end || (location.line < ignore.end.line || (location.line === ignore.end.line && location.column <= ignore.end.column)))) {
|
433
|
+
(location.line > ignore.start.line || (location.line === ignore.start.line && location.column > ignore.start.column)) &&
|
434
|
+
(!ignore.end || (location.line < ignore.end.line || (location.line === ignore.end.line && location.column - 1 <= ignore.end.column)))) {
|
421
435
|
return true;
|
422
436
|
}
|
423
437
|
}
|
@@ -531,6 +545,8 @@ function createStubRule(message) {
|
|
531
545
|
if (message) {
|
532
546
|
return createRuleModule;
|
533
547
|
}
|
548
|
+
|
549
|
+
/* istanbul ignore next */
|
534
550
|
throw new Error("No message passed to stub rule");
|
535
551
|
|
536
552
|
}
|
@@ -595,13 +611,7 @@ function stripUnicodeBOM(text) {
|
|
595
611
|
* @returns {number} 0, 1, or 2, indicating rule severity
|
596
612
|
*/
|
597
613
|
function getRuleSeverity(ruleConfig) {
|
598
|
-
|
599
|
-
return ruleConfig;
|
600
|
-
} else if (Array.isArray(ruleConfig)) {
|
601
|
-
return ruleConfig[0];
|
602
|
-
}
|
603
|
-
return 0;
|
604
|
-
|
614
|
+
return Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
|
605
615
|
}
|
606
616
|
|
607
617
|
/**
|
@@ -622,45 +632,41 @@ function getRuleOptions(ruleConfig) {
|
|
622
632
|
* optimization of functions, so it's best to keep the try-catch as isolated
|
623
633
|
* as possible
|
624
634
|
* @param {string} text The text to parse.
|
625
|
-
* @param {Object}
|
635
|
+
* @param {Object} providedParserOptions Options to pass to the parser
|
636
|
+
* @param {string} parserName The name of the parser
|
626
637
|
* @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
|
638
|
+
* @returns {{success: false, error: Problem}|{success: true,ast: ASTNode, services: Object}}
|
639
|
+
* An object containing the AST and parser services if parsing was successful, or the error if parsing failed
|
631
640
|
* @private
|
632
641
|
*/
|
633
|
-
function parse(text,
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
642
|
+
function parse(text, providedParserOptions, parserName, filePath) {
|
643
|
+
|
644
|
+
const parserOptions = Object.assign({}, providedParserOptions, {
|
645
|
+
loc: true,
|
646
|
+
range: true,
|
647
|
+
raw: true,
|
648
|
+
tokens: true,
|
649
|
+
comment: true,
|
650
|
+
filePath
|
651
|
+
});
|
652
|
+
|
653
|
+
let parser;
|
644
654
|
|
645
655
|
try {
|
646
|
-
parser = require(
|
656
|
+
parser = require(parserName);
|
647
657
|
} 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);
|
658
|
+
return {
|
659
|
+
success: false,
|
660
|
+
error: {
|
661
|
+
ruleId: null,
|
662
|
+
fatal: true,
|
663
|
+
severity: 2,
|
664
|
+
source: null,
|
665
|
+
message: ex.message,
|
666
|
+
line: 0,
|
667
|
+
column: 0
|
668
|
+
}
|
669
|
+
};
|
664
670
|
}
|
665
671
|
|
666
672
|
/*
|
@@ -671,31 +677,79 @@ function parse(text, config, filePath, messages) {
|
|
671
677
|
*/
|
672
678
|
try {
|
673
679
|
if (typeof parser.parseForESLint === "function") {
|
674
|
-
|
680
|
+
const parseResult = parser.parseForESLint(text, parserOptions);
|
681
|
+
|
682
|
+
return {
|
683
|
+
success: true,
|
684
|
+
ast: parseResult.ast,
|
685
|
+
services: parseResult.services || {}
|
686
|
+
};
|
675
687
|
}
|
676
|
-
return parser.parse(text, parserOptions);
|
677
688
|
|
689
|
+
return {
|
690
|
+
success: true,
|
691
|
+
ast: parser.parse(text, parserOptions),
|
692
|
+
services: {}
|
693
|
+
};
|
678
694
|
} catch (ex) {
|
679
695
|
|
680
696
|
// 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
|
-
messages.push({
|
685
|
-
ruleId: null,
|
686
|
-
fatal: true,
|
687
|
-
severity: 2,
|
688
|
-
source,
|
689
|
-
message: `Parsing error: ${message}`,
|
690
|
-
|
691
|
-
line: ex.lineNumber,
|
692
|
-
column: ex.column
|
693
|
-
});
|
697
|
+
const message = `Parsing error: ${ex.message.replace(/^line \d+:/i, "").trim()}`;
|
698
|
+
const source = ex.lineNumber ? SourceCode.splitLines(text)[ex.lineNumber - 1] : null;
|
694
699
|
|
695
|
-
return
|
700
|
+
return {
|
701
|
+
success: false,
|
702
|
+
error: {
|
703
|
+
ruleId: null,
|
704
|
+
fatal: true,
|
705
|
+
severity: 2,
|
706
|
+
source,
|
707
|
+
message,
|
708
|
+
line: ex.lineNumber,
|
709
|
+
column: ex.column
|
710
|
+
}
|
711
|
+
};
|
696
712
|
}
|
697
713
|
}
|
698
714
|
|
715
|
+
// methods that exist on SourceCode object
|
716
|
+
const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
|
717
|
+
getSource: "getText",
|
718
|
+
getSourceLines: "getLines",
|
719
|
+
getAllComments: "getAllComments",
|
720
|
+
getNodeByRangeIndex: "getNodeByRangeIndex",
|
721
|
+
getComments: "getComments",
|
722
|
+
getCommentsBefore: "getCommentsBefore",
|
723
|
+
getCommentsAfter: "getCommentsAfter",
|
724
|
+
getCommentsInside: "getCommentsInside",
|
725
|
+
getJSDocComment: "getJSDocComment",
|
726
|
+
getFirstToken: "getFirstToken",
|
727
|
+
getFirstTokens: "getFirstTokens",
|
728
|
+
getLastToken: "getLastToken",
|
729
|
+
getLastTokens: "getLastTokens",
|
730
|
+
getTokenAfter: "getTokenAfter",
|
731
|
+
getTokenBefore: "getTokenBefore",
|
732
|
+
getTokenByRangeStart: "getTokenByRangeStart",
|
733
|
+
getTokens: "getTokens",
|
734
|
+
getTokensAfter: "getTokensAfter",
|
735
|
+
getTokensBefore: "getTokensBefore",
|
736
|
+
getTokensBetween: "getTokensBetween"
|
737
|
+
};
|
738
|
+
|
739
|
+
const BASE_TRAVERSAL_CONTEXT = Object.freeze(
|
740
|
+
Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).reduce(
|
741
|
+
(contextInfo, methodName) =>
|
742
|
+
Object.assign(contextInfo, {
|
743
|
+
[methodName]() {
|
744
|
+
const sourceCode = this.getSourceCode();
|
745
|
+
|
746
|
+
return sourceCode[DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]].apply(sourceCode, arguments);
|
747
|
+
}
|
748
|
+
}),
|
749
|
+
{}
|
750
|
+
)
|
751
|
+
);
|
752
|
+
|
699
753
|
//------------------------------------------------------------------------------
|
700
754
|
// Public Interface
|
701
755
|
//------------------------------------------------------------------------------
|
@@ -704,13 +758,11 @@ function parse(text, config, filePath, messages) {
|
|
704
758
|
* Object that is responsible for verifying JavaScript text
|
705
759
|
* @name eslint
|
706
760
|
*/
|
707
|
-
class Linter
|
761
|
+
class Linter {
|
708
762
|
|
709
763
|
constructor() {
|
710
|
-
super();
|
711
764
|
this.messages = [];
|
712
765
|
this.currentConfig = null;
|
713
|
-
this.currentScopes = null;
|
714
766
|
this.scopeManager = null;
|
715
767
|
this.currentFilename = null;
|
716
768
|
this.traverser = null;
|
@@ -720,9 +772,6 @@ class Linter extends EventEmitter {
|
|
720
772
|
|
721
773
|
this.rules = new Rules();
|
722
774
|
this.environments = new Environments();
|
723
|
-
|
724
|
-
// set unlimited listeners (see https://github.com/eslint/eslint/issues/524)
|
725
|
-
this.setMaxListeners(0);
|
726
775
|
}
|
727
776
|
|
728
777
|
/**
|
@@ -730,10 +779,8 @@ class Linter extends EventEmitter {
|
|
730
779
|
* @returns {void}
|
731
780
|
*/
|
732
781
|
reset() {
|
733
|
-
this.removeAllListeners();
|
734
782
|
this.messages = [];
|
735
783
|
this.currentConfig = null;
|
736
|
-
this.currentScopes = null;
|
737
784
|
this.scopeManager = null;
|
738
785
|
this.traverser = null;
|
739
786
|
this.reportingConfig = [];
|
@@ -765,11 +812,18 @@ class Linter extends EventEmitter {
|
|
765
812
|
* @returns {Object[]} The results as an array of messages or null if no messages.
|
766
813
|
*/
|
767
814
|
verify(textOrSourceCode, config, filenameOrOptions, saveState) {
|
768
|
-
|
769
|
-
|
770
|
-
parseResult,
|
815
|
+
let text,
|
816
|
+
parserServices,
|
771
817
|
allowInlineConfig;
|
772
818
|
|
819
|
+
if (typeof textOrSourceCode === "string") {
|
820
|
+
this.sourceCode = null;
|
821
|
+
text = textOrSourceCode;
|
822
|
+
} else {
|
823
|
+
this.sourceCode = textOrSourceCode;
|
824
|
+
text = this.sourceCode.text;
|
825
|
+
}
|
826
|
+
|
773
827
|
// evaluate arguments
|
774
828
|
if (typeof filenameOrOptions === "object") {
|
775
829
|
this.currentFilename = filenameOrOptions.filename;
|
@@ -784,7 +838,7 @@ class Linter extends EventEmitter {
|
|
784
838
|
}
|
785
839
|
|
786
840
|
// search and apply "eslint-env *".
|
787
|
-
const envInFile = findEslintEnv(text
|
841
|
+
const envInFile = findEslintEnv(text);
|
788
842
|
|
789
843
|
config = Object.assign({}, config);
|
790
844
|
|
@@ -799,136 +853,191 @@ class Linter extends EventEmitter {
|
|
799
853
|
// process initial config to make it safe to extend
|
800
854
|
config = prepareConfig(config, this.environments);
|
801
855
|
|
802
|
-
|
803
|
-
|
856
|
+
if (this.sourceCode) {
|
857
|
+
parserServices = {};
|
858
|
+
} else {
|
804
859
|
|
805
860
|
// there's no input, just exit here
|
806
861
|
if (text.trim().length === 0) {
|
807
862
|
this.sourceCode = new SourceCode(text, blankScriptAST);
|
808
|
-
return
|
863
|
+
return [];
|
809
864
|
}
|
810
865
|
|
811
|
-
parseResult = parse(
|
866
|
+
const parseResult = parse(
|
812
867
|
stripUnicodeBOM(text).replace(astUtils.SHEBANG_MATCHER, (match, captured) => `//${captured}`),
|
813
|
-
config,
|
814
|
-
|
815
|
-
this.
|
868
|
+
config.parserOptions,
|
869
|
+
config.parser,
|
870
|
+
this.currentFilename
|
816
871
|
);
|
817
872
|
|
818
|
-
|
819
|
-
|
820
|
-
ast = parseResult.ast;
|
821
|
-
} else {
|
822
|
-
ast = parseResult;
|
823
|
-
parseResult = null;
|
824
|
-
}
|
825
|
-
|
826
|
-
if (ast) {
|
827
|
-
this.sourceCode = new SourceCode(text, ast);
|
873
|
+
if (!parseResult.success) {
|
874
|
+
return [parseResult.error];
|
828
875
|
}
|
829
876
|
|
830
|
-
|
831
|
-
this.sourceCode =
|
832
|
-
ast = this.sourceCode.ast;
|
877
|
+
parserServices = parseResult.services;
|
878
|
+
this.sourceCode = new SourceCode(text, parseResult.ast);
|
833
879
|
}
|
834
880
|
|
835
|
-
//
|
836
|
-
if (
|
881
|
+
// parse global comments and modify config
|
882
|
+
if (allowInlineConfig !== false) {
|
883
|
+
const modifyConfigResult = modifyConfigsFromComments(this.currentFilename, this.sourceCode.ast, config, this);
|
837
884
|
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
}
|
842
|
-
|
843
|
-
// ensure that severities are normalized in the config
|
844
|
-
ConfigOps.normalize(config);
|
845
|
-
|
846
|
-
// enable appropriate rules
|
847
|
-
Object.keys(config.rules).filter(key => getRuleSeverity(config.rules[key]) > 0).forEach(key => {
|
848
|
-
let ruleCreator;
|
885
|
+
config = modifyConfigResult.config;
|
886
|
+
modifyConfigResult.problems.forEach(problem => this.messages.push(problem));
|
887
|
+
}
|
849
888
|
|
850
|
-
|
889
|
+
// ensure that severities are normalized in the config
|
890
|
+
ConfigOps.normalize(config);
|
851
891
|
|
852
|
-
|
853
|
-
const replacementMsg = getRuleReplacementMessage(key);
|
892
|
+
const emitter = new EventEmitter().setMaxListeners(Infinity);
|
854
893
|
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
894
|
+
/*
|
895
|
+
* Create a frozen object with the ruleContext properties and methods that are shared by all rules.
|
896
|
+
* All rule contexts will inherit from this object. This avoids the performance penalty of copying all the
|
897
|
+
* properties once for each rule.
|
898
|
+
*/
|
899
|
+
const sharedTraversalContext = Object.freeze(
|
900
|
+
Object.assign(
|
901
|
+
Object.create(BASE_TRAVERSAL_CONTEXT),
|
902
|
+
{
|
903
|
+
getAncestors: this.getAncestors.bind(this),
|
904
|
+
getDeclaredVariables: this.getDeclaredVariables.bind(this),
|
905
|
+
getFilename: this.getFilename.bind(this),
|
906
|
+
getScope: this.getScope.bind(this),
|
907
|
+
getSourceCode: () => this.sourceCode,
|
908
|
+
markVariableAsUsed: this.markVariableAsUsed.bind(this),
|
909
|
+
parserOptions: config.parserOptions,
|
910
|
+
parserPath: config.parser,
|
911
|
+
parserServices,
|
912
|
+
settings: config.settings,
|
913
|
+
|
914
|
+
/**
|
915
|
+
* This is used to avoid breaking rules that used to monkeypatch the `Linter#report` method
|
916
|
+
* by using the `_linter` property on rule contexts.
|
917
|
+
*
|
918
|
+
* This should be removed in a major release after we create a better way to
|
919
|
+
* lint for unused disable comments.
|
920
|
+
* https://github.com/eslint/eslint/issues/9193
|
921
|
+
*/
|
922
|
+
_linter: {
|
923
|
+
report() {},
|
924
|
+
on: emitter.on.bind(emitter)
|
859
925
|
}
|
860
|
-
this.rules.define(key, ruleCreator);
|
861
926
|
}
|
927
|
+
)
|
928
|
+
);
|
862
929
|
|
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
|
-
);
|
930
|
+
// enable appropriate rules
|
931
|
+
Object.keys(config.rules).filter(ruleId => getRuleSeverity(config.rules[ruleId]) > 0).forEach(ruleId => {
|
932
|
+
let ruleCreator = this.rules.get(ruleId);
|
873
933
|
|
874
|
-
|
875
|
-
|
934
|
+
if (!ruleCreator) {
|
935
|
+
const replacementMsg = getRuleReplacementMessage(ruleId);
|
876
936
|
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
: rule[selector]
|
882
|
-
);
|
883
|
-
});
|
884
|
-
} catch (ex) {
|
885
|
-
ex.message = `Error while loading rule '${key}': ${ex.message}`;
|
886
|
-
throw ex;
|
937
|
+
if (replacementMsg) {
|
938
|
+
ruleCreator = createStubRule(replacementMsg);
|
939
|
+
} else {
|
940
|
+
ruleCreator = createStubRule(`Definition for rule '${ruleId}' was not found`);
|
887
941
|
}
|
888
|
-
|
942
|
+
this.rules.define(ruleId, ruleCreator);
|
943
|
+
}
|
889
944
|
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
945
|
+
const severity = getRuleSeverity(config.rules[ruleId]);
|
946
|
+
const ruleContext = Object.freeze(
|
947
|
+
Object.assign(
|
948
|
+
Object.create(sharedTraversalContext),
|
949
|
+
{
|
950
|
+
id: ruleId,
|
951
|
+
options: getRuleOptions(config.rules[ruleId]),
|
952
|
+
report: lodash.flow([
|
953
|
+
createReportTranslator({ ruleId, severity, sourceCode: this.sourceCode }),
|
954
|
+
problem => {
|
955
|
+
if (problem.fix && ruleCreator.meta && !ruleCreator.meta.fixable) {
|
956
|
+
throw new Error("Fixable rules should export a `meta.fixable` property.");
|
957
|
+
}
|
958
|
+
if (!isDisabledByReportingConfig(this.reportingConfig, ruleId, problem)) {
|
959
|
+
this.messages.push(problem);
|
960
|
+
}
|
961
|
+
|
962
|
+
/*
|
963
|
+
* This is used to avoid breaking rules that used monkeypatch Linter, and relied on
|
964
|
+
* `linter.report` getting called with report info every time a rule reports a problem.
|
965
|
+
* To continue to support this, make sure that `context._linter.report` is called every
|
966
|
+
* time a problem is reported by a rule, even though `context._linter` is no longer a
|
967
|
+
* `Linter` instance.
|
968
|
+
*
|
969
|
+
* This should be removed in a major release after we create a better way to
|
970
|
+
* lint for unused disable comments.
|
971
|
+
* https://github.com/eslint/eslint/issues/9193
|
972
|
+
*/
|
973
|
+
sharedTraversalContext._linter.report( // eslint-disable-line no-underscore-dangle
|
974
|
+
ruleId,
|
975
|
+
severity,
|
976
|
+
{ loc: { start: { line: problem.line, column: problem.column - 1 } } },
|
977
|
+
problem.message
|
978
|
+
);
|
979
|
+
}
|
980
|
+
])
|
981
|
+
}
|
982
|
+
)
|
983
|
+
);
|
906
984
|
|
907
|
-
|
985
|
+
try {
|
986
|
+
const rule = ruleCreator.create
|
987
|
+
? ruleCreator.create(ruleContext)
|
988
|
+
: ruleCreator(ruleContext);
|
908
989
|
|
909
|
-
|
910
|
-
|
990
|
+
// add all the selectors from the rule as listeners
|
991
|
+
Object.keys(rule).forEach(selector => {
|
992
|
+
emitter.on(
|
993
|
+
selector, timing.enabled
|
994
|
+
? timing.time(ruleId, rule[selector])
|
995
|
+
: rule[selector]
|
996
|
+
);
|
997
|
+
});
|
998
|
+
} catch (ex) {
|
999
|
+
ex.message = `Error while loading rule '${ruleId}': ${ex.message}`;
|
1000
|
+
throw ex;
|
1001
|
+
}
|
1002
|
+
});
|
911
1003
|
|
912
|
-
|
1004
|
+
// save config so rules can access as necessary
|
1005
|
+
this.currentConfig = config;
|
1006
|
+
this.traverser = new Traverser();
|
1007
|
+
|
1008
|
+
const ecmaFeatures = this.currentConfig.parserOptions.ecmaFeatures || {};
|
1009
|
+
const ecmaVersion = this.currentConfig.parserOptions.ecmaVersion || 5;
|
1010
|
+
|
1011
|
+
// gather scope data that may be needed by the rules
|
1012
|
+
this.scopeManager = eslintScope.analyze(this.sourceCode.ast, {
|
1013
|
+
ignoreEval: true,
|
1014
|
+
nodejsScope: ecmaFeatures.globalReturn,
|
1015
|
+
impliedStrict: ecmaFeatures.impliedStrict,
|
1016
|
+
ecmaVersion,
|
1017
|
+
sourceType: this.currentConfig.parserOptions.sourceType || "script",
|
1018
|
+
fallback: Traverser.getKeys
|
1019
|
+
});
|
913
1020
|
|
914
|
-
|
1021
|
+
// augment global scope with declared global variables
|
1022
|
+
addDeclaredGlobals(this.sourceCode.ast, this.scopeManager.scopes[0], this.currentConfig, this.environments);
|
915
1023
|
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
1024
|
+
const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter));
|
1025
|
+
|
1026
|
+
/*
|
1027
|
+
* Each node has a type property. Whenever a particular type of
|
1028
|
+
* node is found, an event is fired. This allows any listeners to
|
1029
|
+
* automatically be informed that this type of node has been found
|
1030
|
+
* and react accordingly.
|
1031
|
+
*/
|
1032
|
+
this.traverser.traverse(this.sourceCode.ast, {
|
1033
|
+
enter(node, parent) {
|
1034
|
+
node.parent = parent;
|
1035
|
+
eventGenerator.enterNode(node);
|
1036
|
+
},
|
1037
|
+
leave(node) {
|
1038
|
+
eventGenerator.leaveNode(node);
|
1039
|
+
}
|
1040
|
+
});
|
932
1041
|
|
933
1042
|
// sort by line and column
|
934
1043
|
this.messages.sort((a, b) => {
|
@@ -944,88 +1053,6 @@ class Linter extends EventEmitter {
|
|
944
1053
|
return this.messages;
|
945
1054
|
}
|
946
1055
|
|
947
|
-
/**
|
948
|
-
* Reports a message from one of the rules.
|
949
|
-
* @param {string} ruleId The ID of the rule causing the message.
|
950
|
-
* @param {number} severity The severity level of the rule as configured.
|
951
|
-
* @param {ASTNode} node The AST node that the message relates to.
|
952
|
-
* @param {Object=} location An object containing the error line and column
|
953
|
-
* numbers. If location is not provided the node's start location will
|
954
|
-
* be used.
|
955
|
-
* @param {string} message The actual message.
|
956
|
-
* @param {Object} opts Optional template data which produces a formatted message
|
957
|
-
* with symbols being replaced by this object's values.
|
958
|
-
* @param {Object} fix A fix command description.
|
959
|
-
* @param {Object} meta Metadata of the rule
|
960
|
-
* @returns {void}
|
961
|
-
*/
|
962
|
-
report(ruleId, severity, node, location, message, opts, fix, meta) {
|
963
|
-
if (node) {
|
964
|
-
assert.strictEqual(typeof node, "object", "Node must be an object");
|
965
|
-
}
|
966
|
-
|
967
|
-
let endLocation;
|
968
|
-
|
969
|
-
if (typeof location === "string") {
|
970
|
-
assert.ok(node, "Node must be provided when reporting error if location is not provided");
|
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);
|
1027
|
-
}
|
1028
|
-
|
1029
1056
|
/**
|
1030
1057
|
* Gets the SourceCode object representing the parsed source.
|
1031
1058
|
* @returns {SourceCode} The SourceCode object.
|
@@ -1083,7 +1110,7 @@ class Linter extends EventEmitter {
|
|
1083
1110
|
|
1084
1111
|
}
|
1085
1112
|
|
1086
|
-
return this.
|
1113
|
+
return this.scopeManager.scopes[0];
|
1087
1114
|
}
|
1088
1115
|
|
1089
1116
|
/**
|
@@ -1210,7 +1237,7 @@ class Linter extends EventEmitter {
|
|
1210
1237
|
fixed = false,
|
1211
1238
|
passNumber = 0;
|
1212
1239
|
const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`;
|
1213
|
-
const shouldFix = options && options.fix
|
1240
|
+
const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true;
|
1214
1241
|
|
1215
1242
|
/**
|
1216
1243
|
* This loop continues until one of the following is true:
|
@@ -1228,7 +1255,7 @@ class Linter extends EventEmitter {
|
|
1228
1255
|
messages = this.verify(text, config, options);
|
1229
1256
|
|
1230
1257
|
debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`);
|
1231
|
-
fixedResult = SourceCodeFixer.applyFixes(
|
1258
|
+
fixedResult = SourceCodeFixer.applyFixes(text, messages, shouldFix);
|
1232
1259
|
|
1233
1260
|
// stop if there are any syntax errors.
|
1234
1261
|
// 'fixedResult.output' is a empty string.
|
@@ -1263,33 +1290,8 @@ class Linter extends EventEmitter {
|
|
1263
1290
|
}
|
1264
1291
|
}
|
1265
1292
|
|
1266
|
-
|
1267
|
-
const
|
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
|
-
};
|
1289
|
-
|
1290
|
-
// copy over methods
|
1291
|
-
Object.keys(externalMethods).forEach(methodName => {
|
1292
|
-
const exMethodName = externalMethods[methodName];
|
1293
|
+
Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).forEach(methodName => {
|
1294
|
+
const exMethodName = DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName];
|
1293
1295
|
|
1294
1296
|
// Applies the SourceCode methods to the Linter prototype
|
1295
1297
|
Object.defineProperty(Linter.prototype, methodName, {
|