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.
Files changed (49) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/bin/eslint.js +2 -1
  3. package/conf/eslint-recommended.js +1 -0
  4. package/lib/ast-utils.js +20 -17
  5. package/lib/cli-engine.js +51 -124
  6. package/lib/code-path-analysis/code-path-analyzer.js +8 -4
  7. package/lib/code-path-analysis/code-path-segment.js +2 -1
  8. package/lib/code-path-analysis/code-path-state.js +21 -14
  9. package/lib/code-path-analysis/code-path.js +3 -2
  10. package/lib/code-path-analysis/fork-context.js +2 -1
  11. package/lib/config/autoconfig.js +2 -4
  12. package/lib/config/config-initializer.js +9 -5
  13. package/lib/config/config-ops.js +15 -15
  14. package/lib/config.js +8 -12
  15. package/lib/formatters/codeframe.js +1 -1
  16. package/lib/formatters/stylish.js +1 -1
  17. package/lib/ignored-paths.js +0 -2
  18. package/lib/linter.js +468 -638
  19. package/lib/report-translator.js +274 -0
  20. package/lib/rules/function-paren-newline.js +221 -0
  21. package/lib/rules/generator-star-spacing.js +70 -19
  22. package/lib/rules/indent-legacy.js +3 -2
  23. package/lib/rules/indent.js +15 -6
  24. package/lib/rules/key-spacing.js +2 -1
  25. package/lib/rules/newline-per-chained-call.js +20 -3
  26. package/lib/rules/no-extra-parens.js +75 -33
  27. package/lib/rules/no-invalid-this.js +2 -1
  28. package/lib/rules/no-tabs.js +1 -1
  29. package/lib/rules/no-undef-init.js +4 -0
  30. package/lib/rules/no-unmodified-loop-condition.js +1 -1
  31. package/lib/rules/no-unused-vars.js +47 -4
  32. package/lib/rules/padded-blocks.js +2 -2
  33. package/lib/rules/prefer-arrow-callback.js +1 -2
  34. package/lib/rules/quote-props.js +4 -2
  35. package/lib/rules/quotes.js +1 -2
  36. package/lib/rules/space-before-blocks.js +1 -1
  37. package/lib/rules/valid-jsdoc.js +2 -2
  38. package/lib/rules.js +48 -3
  39. package/lib/testers/rule-tester.js +27 -51
  40. package/lib/timing.js +2 -2
  41. package/lib/util/apply-disable-directives.js +131 -0
  42. package/lib/util/fix-tracker.js +1 -2
  43. package/lib/util/npm-util.js +21 -4
  44. package/lib/util/source-code-fixer.js +5 -14
  45. package/lib/util/source-code.js +3 -5
  46. package/package.json +8 -8
  47. package/lib/rule-context.js +0 -241
  48. package/lib/testers/event-generator-tester.js +0 -62
  49. 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 assert = require("assert"),
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
- RuleContext = require("./rule-context"),
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
- * @param {Object[]} messages The messages queue for potential error message.
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, messages) {
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 items;
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
- messages.push({
121
- ruleId: null,
122
- fatal: true,
123
- severity: 2,
124
- source: null,
125
- message: `Failed to parse JSON from '${string}': ${ex.message}`,
126
- line: location.start.line,
127
- column: location.start.column + 1
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 items;
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
- if (config.env[name]) {
176
- const env = envContext.get(name),
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
- if (environmentGlobals) {
180
- Object.assign(declaredGlobals, environmentGlobals);
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
- * Add data to reporting configuration to disable reporting for list of rules
250
- * starting from start location
251
- * @param {Object[]} reportingConfig Current reporting configuration
252
- * @param {Object} start Position to start
253
- * @param {string[]} rulesToDisable List of rules
254
- * @returns {void}
255
- */
256
- function disableReporting(reportingConfig, start, rulesToDisable) {
257
-
258
- if (rulesToDisable.length) {
259
- rulesToDisable.forEach(rule => {
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 enableReporting(reportingConfig, start, rulesToEnable) {
284
- let i;
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
- if (!reportingConfig[i].end) {
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 {Object} Modified config object
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 messages = linterContext.messages;
333
- const reportingConfig = linterContext.reportingConfig;
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
- disableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value)));
329
+ [].push.apply(disableDirectives, createDisableDirectives("disable", comment.loc.start, value));
360
330
  break;
361
331
 
362
332
  case "eslint-enable":
363
- enableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value)));
333
+ [].push.apply(disableDirectives, createDisableDirectives("enable", comment.loc.start, value));
364
334
  break;
365
335
 
366
336
  case "eslint": {
367
- const items = parseJsonConfig(value, comment.loc, messages);
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
- Object.keys(items).forEach(name => {
370
- const ruleValue = items[name];
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
- disableReporting(reportingConfig, { line: comment.loc.start.line, column: 0 }, Object.keys(parseListConfig(value)));
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
- disableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value)));
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 ConfigOps.merge(config, commentConfig);
403
- }
404
-
405
- /**
406
- * Check if message of rule with ruleId should be ignored in location
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} config The ESLint configuration 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 {ASTNode|CustomParseResult} The AST or parse result if successful,
628
- * or null if not.
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, config, filePath, messages) {
634
-
635
- let parser,
636
- parserOptions = {
637
- loc: true,
638
- range: true,
639
- raw: true,
640
- tokens: true,
641
- comment: true,
642
- filePath
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(config.parser);
539
+ parser = require(parserName);
647
540
  } catch (ex) {
648
- messages.push({
649
- ruleId: null,
650
- fatal: true,
651
- severity: 2,
652
- source: null,
653
- message: ex.message,
654
- line: 0,
655
- column: 0
656
- });
657
-
658
- return null;
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
- return parser.parseForESLint(text, parserOptions);
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 = (ex.lineNumber) ? SourceCode.splitLines(text)[ex.lineNumber - 1] : null;
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
- });
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
- return null;
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 extends EventEmitter {
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
- * Verifies the text against the rules specified by the second argument.
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
- verify(textOrSourceCode, config, filenameOrOptions, saveState) {
768
- const text = (typeof textOrSourceCode === "string") ? textOrSourceCode : null;
769
- let ast,
770
- parseResult,
771
- allowInlineConfig;
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
- this.currentFilename = filenameOrOptions.filename;
765
+ providedFilename = filenameOrOptions.filename;
776
766
  allowInlineConfig = filenameOrOptions.allowInlineConfig;
777
- saveState = filenameOrOptions.saveState;
778
767
  } else {
779
- this.currentFilename = filenameOrOptions;
768
+ providedFilename = filenameOrOptions;
780
769
  }
781
770
 
782
- if (!saveState) {
783
- this.reset();
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 || textOrSourceCode.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
- // only do this for text
803
- if (text !== null) {
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 this.messages;
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
- this.currentFilename,
815
- this.messages
809
+ config.parserOptions,
810
+ config.parser,
811
+ filename
816
812
  );
817
813
 
818
- // if this result is from a parseForESLint() method, normalize
819
- if (parseResult && parseResult.ast) {
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
- if (ast) {
827
- this.sourceCode = new SourceCode(text, ast);
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
- // if espree failed to parse the file, there's no sense in setting up rules
836
- if (ast) {
822
+ const problems = [];
823
+ const sourceCode = this.sourceCode;
824
+ let disableDirectives;
837
825
 
838
- // parse global comments and modify config
839
- if (allowInlineConfig !== false) {
840
- config = modifyConfigsFromComments(this.currentFilename, ast, config, this);
841
- }
826
+ // parse global comments and modify config
827
+ if (allowInlineConfig !== false) {
828
+ const modifyConfigResult = modifyConfigsFromComments(filename, sourceCode.ast, config, this);
842
829
 
843
- // ensure that severities are normalized in the config
844
- ConfigOps.normalize(config);
830
+ config = modifyConfigResult.config;
831
+ modifyConfigResult.problems.forEach(problem => problems.push(problem));
832
+ disableDirectives = modifyConfigResult.disableDirectives;
833
+ } else {
834
+ disableDirectives = [];
835
+ }
845
836
 
846
- // enable appropriate rules
847
- Object.keys(config.rules).filter(key => getRuleSeverity(config.rules[key]) > 0).forEach(key => {
848
- let ruleCreator;
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
- ruleCreator = this.rules.get(key);
849
+ let currentNode = sourceCode.ast;
850
+ const nodeQueue = [];
851
851
 
852
- if (!ruleCreator) {
853
- const replacementMsg = getRuleReplacementMessage(key);
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
- if (replacementMsg) {
856
- ruleCreator = createStubRule(replacementMsg);
857
- } else {
858
- ruleCreator = createStubRule(`Definition for rule '${key}' was not found`);
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
- const severity = getRuleSeverity(config.rules[key]);
864
- const options = getRuleOptions(config.rules[key]);
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
- // gather scope data that may be needed by the rules
898
- this.scopeManager = eslintScope.analyze(ast, {
899
- ignoreEval: true,
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
- this.currentScopes = this.scopeManager.scopes;
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
- // augment global scope with declared global variables
910
- addDeclaredGlobals(ast, this.currentScopes[0], this.currentConfig, this.environments);
958
+ try {
959
+ const ruleListeners = rule.create(ruleContext);
911
960
 
912
- let eventGenerator = new NodeEventGenerator(this);
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
- eventGenerator = new CodePathAnalyzer(eventGenerator);
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
- // sort by line and column
934
- this.messages.sort((a, b) => {
935
- const lineDiff = a.line - b.line;
981
+ nodeQueue.forEach(traversalInfo => {
982
+ currentNode = traversalInfo.node;
936
983
 
937
- if (lineDiff === 0) {
938
- return a.column - b.column;
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 this.messages;
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
- * 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}
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
- 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);
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(this.getSourceCode(), messages, shouldFix);
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;