eslint 4.16.0 → 4.18.2

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 (89) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/conf/environments.js +3 -1
  3. package/conf/eslint-recommended.js +0 -0
  4. package/lib/linter.js +368 -366
  5. package/lib/rules/accessor-pairs.js +7 -3
  6. package/lib/rules/array-bracket-newline.js +11 -5
  7. package/lib/rules/array-bracket-spacing.js +11 -5
  8. package/lib/rules/array-callback-return.js +11 -5
  9. package/lib/rules/array-element-newline.js +8 -3
  10. package/lib/rules/arrow-body-style.js +16 -8
  11. package/lib/rules/arrow-parens.js +13 -9
  12. package/lib/rules/arrow-spacing.js +13 -5
  13. package/lib/rules/block-scoped-var.js +6 -2
  14. package/lib/rules/block-spacing.js +13 -6
  15. package/lib/rules/brace-style.js +16 -14
  16. package/lib/rules/callback-return.js +6 -2
  17. package/lib/rules/camelcase.js +6 -2
  18. package/lib/rules/capitalized-comments.js +11 -8
  19. package/lib/rules/class-methods-use-this.js +7 -3
  20. package/lib/rules/comma-dangle.js +7 -4
  21. package/lib/rules/comma-spacing.js +13 -10
  22. package/lib/rules/comma-style.js +9 -4
  23. package/lib/rules/complexity.js +6 -2
  24. package/lib/rules/computed-property-spacing.js +13 -5
  25. package/lib/rules/consistent-return.js +12 -7
  26. package/lib/rules/consistent-this.js +9 -4
  27. package/lib/rules/constructor-super.js +17 -8
  28. package/lib/rules/curly.js +59 -80
  29. package/lib/rules/default-case.js +6 -2
  30. package/lib/rules/dot-location.js +8 -3
  31. package/lib/rules/dot-notation.js +10 -5
  32. package/lib/rules/eol-last.js +7 -3
  33. package/lib/rules/eqeqeq.js +6 -2
  34. package/lib/rules/indent.js +0 -1
  35. package/lib/rules/key-spacing.js +3 -2
  36. package/lib/rules/keyword-spacing.js +6 -1
  37. package/lib/rules/max-len.js +2 -1
  38. package/lib/rules/no-alert.js +19 -18
  39. package/lib/rules/no-array-constructor.js +6 -2
  40. package/lib/rules/no-await-in-loop.js +75 -57
  41. package/lib/rules/no-bitwise.js +6 -2
  42. package/lib/rules/no-buffer-constructor.js +6 -3
  43. package/lib/rules/no-caller.js +6 -2
  44. package/lib/rules/no-case-declarations.js +6 -2
  45. package/lib/rules/no-catch-shadow.js +6 -2
  46. package/lib/rules/no-class-assign.js +6 -2
  47. package/lib/rules/no-compare-neg-zero.js +5 -2
  48. package/lib/rules/no-cond-assign.js +10 -4
  49. package/lib/rules/no-confusing-arrow.js +6 -2
  50. package/lib/rules/no-console.js +6 -2
  51. package/lib/rules/no-const-assign.js +6 -2
  52. package/lib/rules/no-constant-condition.js +6 -3
  53. package/lib/rules/no-continue.js +6 -2
  54. package/lib/rules/no-control-regex.js +7 -3
  55. package/lib/rules/no-debugger.js +5 -2
  56. package/lib/rules/no-delete-var.js +6 -2
  57. package/lib/rules/no-div-regex.js +6 -2
  58. package/lib/rules/no-dupe-args.js +6 -2
  59. package/lib/rules/no-dupe-class-members.js +6 -2
  60. package/lib/rules/no-dupe-keys.js +6 -2
  61. package/lib/rules/no-duplicate-case.js +6 -2
  62. package/lib/rules/no-else-return.js +7 -2
  63. package/lib/rules/no-empty-character-class.js +6 -2
  64. package/lib/rules/no-empty-function.js +6 -2
  65. package/lib/rules/no-empty-pattern.js +7 -3
  66. package/lib/rules/no-empty.js +7 -3
  67. package/lib/rules/no-eq-null.js +6 -2
  68. package/lib/rules/no-eval.js +6 -2
  69. package/lib/rules/no-ex-assign.js +6 -2
  70. package/lib/rules/no-extend-native.js +6 -2
  71. package/lib/rules/no-extra-bind.js +6 -2
  72. package/lib/rules/no-extra-boolean-cast.js +8 -3
  73. package/lib/rules/no-extra-label.js +6 -2
  74. package/lib/rules/no-extra-parens.js +5 -1
  75. package/lib/rules/no-extra-semi.js +6 -2
  76. package/lib/rules/no-self-assign.js +3 -1
  77. package/lib/rules/no-unused-vars.js +1 -1
  78. package/lib/rules/object-curly-newline.js +67 -19
  79. package/lib/rules/object-shorthand.js +9 -7
  80. package/lib/rules/padding-line-between-statements.js +6 -0
  81. package/lib/rules/require-await.js +5 -0
  82. package/lib/rules/rest-spread-spacing.js +6 -0
  83. package/lib/rules/space-unary-ops.js +1 -10
  84. package/lib/rules/template-tag-spacing.js +0 -0
  85. package/lib/util/glob-util.js +17 -4
  86. package/lib/util/interpolate.js +5 -1
  87. package/lib/util/npm-util.js +1 -1
  88. package/package.json +2 -2
  89. package/conf/default-config-options.js +0 -29
package/lib/linter.js CHANGED
@@ -14,7 +14,6 @@ const eslintScope = require("eslint-scope"),
14
14
  levn = require("levn"),
15
15
  lodash = require("lodash"),
16
16
  blankScriptAST = require("../conf/blank-script.json"),
17
- defaultConfig = require("../conf/default-config-options.js"),
18
17
  CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
19
18
  ConfigOps = require("./config/config-ops"),
20
19
  validator = require("./config/config-validator"),
@@ -33,6 +32,7 @@ const eslintScope = require("eslint-scope"),
33
32
 
34
33
  const debug = require("debug")("eslint:linter");
35
34
  const MAX_AUTOFIX_PASSES = 10;
35
+ const DEFAULT_PARSER_NAME = "espree";
36
36
 
37
37
  //------------------------------------------------------------------------------
38
38
  // Typedefs
@@ -178,32 +178,12 @@ function parseListConfig(string) {
178
178
  * and any globals declared by special block comments, are present in the global
179
179
  * scope.
180
180
  * @param {Scope} globalScope The global scope.
181
- * @param {Object} config The existing configuration data.
182
- * @param {Environments} envContext Env context
181
+ * @param {Object} configGlobals The globals declared in configuration
182
+ * @param {{exportedVariables: Object, enabledGlobals: Object}} commentDirectives Directives from comment configuration
183
183
  * @returns {void}
184
184
  */
185
- function addDeclaredGlobals(globalScope, config, envContext) {
186
- const declaredGlobals = {},
187
- exportedGlobals = {},
188
- explicitGlobals = {},
189
- builtin = envContext.get("builtin");
190
-
191
- Object.assign(declaredGlobals, builtin);
192
-
193
- Object.keys(config.env).filter(name => config.env[name]).forEach(name => {
194
- const env = envContext.get(name),
195
- environmentGlobals = env && env.globals;
196
-
197
- if (environmentGlobals) {
198
- Object.assign(declaredGlobals, environmentGlobals);
199
- }
200
- });
201
-
202
- Object.assign(exportedGlobals, config.exported);
203
- Object.assign(declaredGlobals, config.globals);
204
- Object.assign(explicitGlobals, config.astGlobals);
205
-
206
- Object.keys(declaredGlobals).forEach(name => {
185
+ function addDeclaredGlobals(globalScope, configGlobals, commentDirectives) {
186
+ Object.keys(configGlobals).forEach(name => {
207
187
  let variable = globalScope.set.get(name);
208
188
 
209
189
  if (!variable) {
@@ -212,24 +192,24 @@ function addDeclaredGlobals(globalScope, config, envContext) {
212
192
  globalScope.variables.push(variable);
213
193
  globalScope.set.set(name, variable);
214
194
  }
215
- variable.writeable = declaredGlobals[name];
195
+ variable.writeable = configGlobals[name];
216
196
  });
217
197
 
218
- Object.keys(explicitGlobals).forEach(name => {
198
+ Object.keys(commentDirectives.enabledGlobals).forEach(name => {
219
199
  let variable = globalScope.set.get(name);
220
200
 
221
201
  if (!variable) {
222
202
  variable = new eslintScope.Variable(name, globalScope);
223
203
  variable.eslintExplicitGlobal = true;
224
- variable.eslintExplicitGlobalComment = explicitGlobals[name].comment;
204
+ variable.eslintExplicitGlobalComment = commentDirectives.enabledGlobals[name].comment;
225
205
  globalScope.variables.push(variable);
226
206
  globalScope.set.set(name, variable);
227
207
  }
228
- variable.writeable = explicitGlobals[name].value;
208
+ variable.writeable = commentDirectives.enabledGlobals[name].value;
229
209
  });
230
210
 
231
211
  // mark all exported variables as such
232
- Object.keys(exportedGlobals).forEach(name => {
212
+ Object.keys(commentDirectives.exportedVariables).forEach(name => {
233
213
  const variable = globalScope.set.get(name);
234
214
 
235
215
  if (variable) {
@@ -283,97 +263,90 @@ function createDisableDirectives(type, loc, value) {
283
263
  * where reporting is disabled or enabled and merges them with reporting config.
284
264
  * @param {string} filename The file being checked.
285
265
  * @param {ASTNode} ast The top node of the AST.
286
- * @param {Object} config The existing configuration data.
287
266
  * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
288
- * @returns {{config: Object, problems: Problem[], disableDirectives: DisableDirective[]}}
289
- * Modified config object, along with any problems encountered while parsing config comments
267
+ * @returns {{configuredRules: Object, enabledGlobals: Object, exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}}
268
+ * A collection of the directive comments that were found, along with any problems that occurred when parsing
290
269
  */
291
- function modifyConfigsFromComments(filename, ast, config, ruleMapper) {
292
-
293
- const commentConfig = {
294
- exported: {},
295
- astGlobals: {},
296
- rules: {},
297
- env: {}
298
- };
299
- const commentRules = {};
270
+ function getDirectiveComments(filename, ast, ruleMapper) {
271
+ const configuredRules = {};
272
+ const enabledGlobals = {};
273
+ const exportedVariables = {};
300
274
  const problems = [];
301
275
  const disableDirectives = [];
302
276
 
303
277
  ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
278
+ const trimmedCommentText = comment.value.trim();
279
+ const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(trimmedCommentText);
304
280
 
305
- let value = comment.value.trim();
306
- const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(value);
307
-
308
- if (match) {
309
- value = value.slice(match.index + match[1].length);
310
-
311
- if (comment.type === "Block") {
312
- switch (match[1]) {
313
- case "exported":
314
- Object.assign(commentConfig.exported, parseBooleanConfig(value, comment));
315
- break;
316
-
317
- case "globals":
318
- case "global":
319
- Object.assign(commentConfig.astGlobals, parseBooleanConfig(value, comment));
320
- break;
321
-
322
- case "eslint-disable":
323
- [].push.apply(disableDirectives, createDisableDirectives("disable", comment.loc.start, value));
324
- break;
325
-
326
- case "eslint-enable":
327
- [].push.apply(disableDirectives, createDisableDirectives("enable", comment.loc.start, value));
328
- break;
329
-
330
- case "eslint": {
331
- const parseResult = parseJsonConfig(value, comment.loc);
332
-
333
- if (parseResult.success) {
334
- Object.keys(parseResult.config).forEach(name => {
335
- const ruleValue = parseResult.config[name];
336
-
337
- try {
338
- validator.validateRuleOptions(ruleMapper(name), name, ruleValue);
339
- } catch (err) {
340
- problems.push({
341
- ruleId: name,
342
- severity: 2,
343
- source: null,
344
- message: err.message,
345
- line: comment.loc.start.line,
346
- column: comment.loc.start.column + 1,
347
- endLine: comment.loc.end.line,
348
- endColumn: comment.loc.end.column + 1,
349
- nodeType: null
350
- });
351
- }
352
- commentRules[name] = ruleValue;
353
- });
354
- } else {
355
- problems.push(parseResult.error);
356
- }
281
+ if (!match) {
282
+ return;
283
+ }
357
284
 
358
- break;
285
+ const directiveValue = trimmedCommentText.slice(match.index + match[1].length);
286
+
287
+ if (/^eslint-disable-(next-)?line$/.test(match[1]) && comment.loc.start.line === comment.loc.end.line) {
288
+ const directiveType = match[1].slice("eslint-".length);
289
+
290
+ [].push.apply(disableDirectives, createDisableDirectives(directiveType, comment.loc.start, directiveValue));
291
+ } else if (comment.type === "Block") {
292
+ switch (match[1]) {
293
+ case "exported":
294
+ Object.assign(exportedVariables, parseBooleanConfig(directiveValue, comment));
295
+ break;
296
+
297
+ case "globals":
298
+ case "global":
299
+ Object.assign(enabledGlobals, parseBooleanConfig(directiveValue, comment));
300
+ break;
301
+
302
+ case "eslint-disable":
303
+ [].push.apply(disableDirectives, createDisableDirectives("disable", comment.loc.start, directiveValue));
304
+ break;
305
+
306
+ case "eslint-enable":
307
+ [].push.apply(disableDirectives, createDisableDirectives("enable", comment.loc.start, directiveValue));
308
+ break;
309
+
310
+ case "eslint": {
311
+ const parseResult = parseJsonConfig(directiveValue, comment.loc);
312
+
313
+ if (parseResult.success) {
314
+ Object.keys(parseResult.config).forEach(name => {
315
+ const ruleValue = parseResult.config[name];
316
+
317
+ try {
318
+ validator.validateRuleOptions(ruleMapper(name), name, ruleValue);
319
+ } catch (err) {
320
+ problems.push({
321
+ ruleId: name,
322
+ severity: 2,
323
+ source: null,
324
+ message: err.message,
325
+ line: comment.loc.start.line,
326
+ column: comment.loc.start.column + 1,
327
+ endLine: comment.loc.end.line,
328
+ endColumn: comment.loc.end.column + 1,
329
+ nodeType: null
330
+ });
331
+ }
332
+ configuredRules[name] = ruleValue;
333
+ });
334
+ } else {
335
+ problems.push(parseResult.error);
359
336
  }
360
337
 
361
- // no default
362
- }
363
- } else { // comment.type === "Line"
364
- if (match[1] === "eslint-disable-line") {
365
- [].push.apply(disableDirectives, createDisableDirectives("disable-line", comment.loc.start, value));
366
- } else if (match[1] === "eslint-disable-next-line") {
367
- [].push.apply(disableDirectives, createDisableDirectives("disable-next-line", comment.loc.start, value));
338
+ break;
368
339
  }
340
+
341
+ // no default
369
342
  }
370
343
  }
371
344
  });
372
345
 
373
- Object.assign(commentConfig.rules, commentRules);
374
-
375
346
  return {
376
- config: ConfigOps.merge(config, commentConfig),
347
+ configuredRules,
348
+ enabledGlobals,
349
+ exportedVariables,
377
350
  problems,
378
351
  disableDirectives
379
352
  };
@@ -403,81 +376,81 @@ function normalizeEcmaVersion(ecmaVersion, isModule) {
403
376
  return ecmaVersion;
404
377
  }
405
378
 
379
+ const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//g;
380
+
406
381
  /**
407
- * Process initial config to make it safe to extend by file comment config
408
- * @param {Object} config Initial config
409
- * @param {Environments} envContext Env context
410
- * @returns {Object} Processed config
382
+ * Checks whether or not there is a comment which has "eslint-env *" in a given text.
383
+ * @param {string} text - A source code text to check.
384
+ * @returns {Object|null} A result of parseListConfig() with "eslint-env *" comment.
411
385
  */
412
- function prepareConfig(config, envContext) {
413
- config.globals = config.globals || {};
414
- const copiedRules = {};
415
- let parserOptions = {};
386
+ function findEslintEnv(text) {
387
+ let match, retv;
416
388
 
417
- if (typeof config.rules === "object") {
418
- Object.keys(config.rules).forEach(k => {
419
- const rule = config.rules[k];
389
+ eslintEnvPattern.lastIndex = 0;
420
390
 
421
- if (rule === null) {
422
- throw new Error(`Invalid config for rule '${k}'.`);
423
- }
424
- if (Array.isArray(rule)) {
425
- copiedRules[k] = rule.slice();
426
- } else {
427
- copiedRules[k] = rule;
428
- }
429
- });
391
+ while ((match = eslintEnvPattern.exec(text))) {
392
+ retv = Object.assign(retv || {}, parseListConfig(match[1]));
430
393
  }
431
394
 
432
- // merge in environment parserOptions
433
- if (typeof config.env === "object") {
434
- Object.keys(config.env).forEach(envName => {
435
- const env = envContext.get(envName);
395
+ return retv;
396
+ }
436
397
 
437
- if (config.env[envName] && env && env.parserOptions) {
438
- parserOptions = ConfigOps.merge(parserOptions, env.parserOptions);
439
- }
440
- });
441
- }
398
+ /**
399
+ * Normalizes the possible options for `linter.verify` and `linter.verifyAndFix` to a
400
+ * consistent shape.
401
+ * @param {(string|{reportUnusedDisableDirectives: boolean, filename: string, allowInlineConfig: boolean})} providedOptions Options
402
+ * @returns {{reportUnusedDisableDirectives: boolean, filename: string, allowInlineConfig: boolean}} Normalized options
403
+ */
404
+ function normalizeVerifyOptions(providedOptions) {
405
+ const isObjectOptions = typeof providedOptions === "object";
406
+ const providedFilename = isObjectOptions ? providedOptions.filename : providedOptions;
442
407
 
443
- const preparedConfig = {
444
- rules: copiedRules,
445
- parser: config.parser || defaultConfig.parser,
446
- globals: ConfigOps.merge(defaultConfig.globals, config.globals),
447
- env: ConfigOps.merge(defaultConfig.env, config.env || {}),
448
- settings: ConfigOps.merge(defaultConfig.settings, config.settings || {}),
449
- parserOptions: ConfigOps.merge(parserOptions, config.parserOptions || {})
408
+ return {
409
+ filename: typeof providedFilename === "string" ? providedFilename : "<input>",
410
+ allowInlineConfig: !isObjectOptions || providedOptions.allowInlineConfig !== false,
411
+ reportUnusedDisableDirectives: isObjectOptions && !!providedOptions.reportUnusedDisableDirectives
450
412
  };
451
- const isModule = preparedConfig.parserOptions.sourceType === "module";
413
+ }
414
+
415
+ /**
416
+ * Combines the provided parserOptions with the options from environments
417
+ * @param {Object} providedOptions The provided 'parserOptions' key in a config
418
+ * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
419
+ * @returns {Object} Resulting parser options after merge
420
+ */
421
+ function resolveParserOptions(providedOptions, enabledEnvironments) {
422
+ const parserOptionsFromEnv = enabledEnvironments
423
+ .filter(env => env.parserOptions)
424
+ .reduce((parserOptions, env) => ConfigOps.merge(parserOptions, env.parserOptions), {});
425
+
426
+ const mergedParserOptions = ConfigOps.merge(parserOptionsFromEnv, providedOptions || {});
427
+
428
+ const isModule = mergedParserOptions.sourceType === "module";
452
429
 
453
430
  if (isModule) {
454
431
 
455
432
  // can't have global return inside of modules
456
- preparedConfig.parserOptions.ecmaFeatures = Object.assign({}, preparedConfig.parserOptions.ecmaFeatures, { globalReturn: false });
433
+ mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false });
457
434
  }
458
435
 
459
- preparedConfig.parserOptions.ecmaVersion = normalizeEcmaVersion(preparedConfig.parserOptions.ecmaVersion, isModule);
436
+ mergedParserOptions.ecmaVersion = normalizeEcmaVersion(mergedParserOptions.ecmaVersion, isModule);
460
437
 
461
- return preparedConfig;
438
+ return mergedParserOptions;
462
439
  }
463
440
 
464
- const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//g;
465
-
466
441
  /**
467
- * Checks whether or not there is a comment which has "eslint-env *" in a given text.
468
- * @param {string} text - A source code text to check.
469
- * @returns {Object|null} A result of parseListConfig() with "eslint-env *" comment.
442
+ * Combines the provided globals object with the globals from environments
443
+ * @param {Object} providedGlobals The 'globals' key in a config
444
+ * @param {Environments[]} enabledEnvironments The environments enabled in configuration and with inline comments
445
+ * @returns {Object} The resolved globals object
470
446
  */
471
- function findEslintEnv(text) {
472
- let match, retv;
473
-
474
- eslintEnvPattern.lastIndex = 0;
475
-
476
- while ((match = eslintEnvPattern.exec(text))) {
477
- retv = Object.assign(retv || {}, parseListConfig(match[1]));
478
- }
479
-
480
- return retv;
447
+ function resolveGlobals(providedGlobals, enabledEnvironments) {
448
+ return Object.assign.apply(
449
+ null,
450
+ [{}]
451
+ .concat(enabledEnvironments.filter(env => env.globals).map(env => env.globals))
452
+ .concat(providedGlobals)
453
+ );
481
454
  }
482
455
 
483
456
  /**
@@ -540,13 +513,16 @@ function analyzeScope(ast, parserOptions, visitorKeys) {
540
513
  * as possible
541
514
  * @param {string} text The text to parse.
542
515
  * @param {Object} providedParserOptions Options to pass to the parser
543
- * @param {Object} parser The parser module
516
+ * @param {string} parserName The name of the parser
517
+ * @param {Map<string, Object>} parserMap A map from names to loaded parsers
544
518
  * @param {string} filePath The path to the file being parsed.
545
519
  * @returns {{success: false, error: Problem}|{success: true, sourceCode: SourceCode}}
546
520
  * An object containing the AST and parser services if parsing was successful, or the error if parsing failed
547
521
  * @private
548
522
  */
549
- function parse(text, providedParserOptions, parser, filePath) {
523
+ function parse(text, providedParserOptions, parserName, parserMap, filePath) {
524
+
525
+
550
526
  const textToParse = stripUnicodeBOM(text).replace(astUtils.SHEBANG_MATCHER, (match, captured) => `//${captured}`);
551
527
  const parserOptions = Object.assign({}, providedParserOptions, {
552
528
  loc: true,
@@ -559,6 +535,25 @@ function parse(text, providedParserOptions, parser, filePath) {
559
535
  filePath
560
536
  });
561
537
 
538
+ let parser;
539
+
540
+ try {
541
+ parser = parserMap.get(parserName) || require(parserName);
542
+ } catch (ex) {
543
+ return {
544
+ success: false,
545
+ error: {
546
+ ruleId: null,
547
+ fatal: true,
548
+ severity: 2,
549
+ source: null,
550
+ message: ex.message,
551
+ line: 0,
552
+ column: 0
553
+ }
554
+ };
555
+ }
556
+
562
557
  /*
563
558
  * Check for parsing errors first. If there's a parsing error, nothing
564
559
  * else can happen. However, a parsing error does not throw an error
@@ -677,6 +672,21 @@ function markVariableAsUsed(scopeManager, currentNode, parserOptions, name) {
677
672
  return false;
678
673
  }
679
674
 
675
+ /**
676
+ * Runs a rule, and gets its listeners
677
+ * @param {Rule} rule A normalized rule with a `create` method
678
+ * @param {Context} ruleContext The context that should be passed to the rule
679
+ * @returns {Object} A map of selector listeners provided by the rule
680
+ */
681
+ function createRuleListeners(rule, ruleContext) {
682
+ try {
683
+ return rule.create(ruleContext);
684
+ } catch (ex) {
685
+ ex.message = `Error while loading rule '${ruleContext.id}': ${ex.message}`;
686
+ throw ex;
687
+ }
688
+ }
689
+
680
690
  // methods that exist on SourceCode object
681
691
  const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
682
692
  getSource: "getText",
@@ -715,7 +725,157 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze(
715
725
  )
716
726
  );
717
727
 
728
+ /**
729
+ * Runs the given rules on the given SourceCode object
730
+ * @param {SourceCode} sourceCode A SourceCode object for the given text
731
+ * @param {Object} configuredRules The rules configuration
732
+ * @param {function(string): Rule} ruleMapper A mapper function from rule names to rules
733
+ * @param {Object} parserOptions The options that were passed to the parser
734
+ * @param {string} parserName The name of the parser in the config
735
+ * @param {Object} settings The settings that were enabled in the config
736
+ * @param {string} filename The reported filename of the code
737
+ * @returns {Problem[]} An array of reported problems
738
+ */
739
+ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename) {
740
+ const emitter = createEmitter();
741
+ const traverser = new Traverser();
742
+
743
+ /*
744
+ * Create a frozen object with the ruleContext properties and methods that are shared by all rules.
745
+ * All rule contexts will inherit from this object. This avoids the performance penalty of copying all the
746
+ * properties once for each rule.
747
+ */
748
+ const sharedTraversalContext = Object.freeze(
749
+ Object.assign(
750
+ Object.create(BASE_TRAVERSAL_CONTEXT),
751
+ {
752
+ getAncestors: () => traverser.parents(),
753
+ getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager),
754
+ getFilename: () => filename,
755
+ getScope: () => getScope(sourceCode.scopeManager, traverser.current(), parserOptions.ecmaVersion),
756
+ getSourceCode: () => sourceCode,
757
+ markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, traverser.current(), parserOptions, name),
758
+ parserOptions,
759
+ parserPath: parserName,
760
+ parserServices: sourceCode.parserServices,
761
+ settings,
762
+
763
+ /**
764
+ * This is used to avoid breaking rules that used to monkeypatch the `Linter#report` method
765
+ * by using the `_linter` property on rule contexts.
766
+ *
767
+ * This should be removed in a major release after we create a better way to
768
+ * lint for unused disable comments.
769
+ * https://github.com/eslint/eslint/issues/9193
770
+ */
771
+ _linter: {
772
+ report() {},
773
+ on: emitter.on
774
+ }
775
+ }
776
+ )
777
+ );
778
+
779
+
780
+ const lintingProblems = [];
781
+
782
+ Object.keys(configuredRules).forEach(ruleId => {
783
+ const severity = ConfigOps.getRuleSeverity(configuredRules[ruleId]);
784
+
785
+ if (severity === 0) {
786
+ return;
787
+ }
788
+
789
+ const rule = ruleMapper(ruleId);
790
+ const messageIds = rule.meta && rule.meta.messages;
791
+ let reportTranslator = null;
792
+ const ruleContext = Object.freeze(
793
+ Object.assign(
794
+ Object.create(sharedTraversalContext),
795
+ {
796
+ id: ruleId,
797
+ options: getRuleOptions(configuredRules[ruleId]),
798
+ report() {
799
+
800
+ /*
801
+ * Create a report translator lazily.
802
+ * In a vast majority of cases, any given rule reports zero errors on a given
803
+ * piece of code. Creating a translator lazily avoids the performance cost of
804
+ * creating a new translator function for each rule that usually doesn't get
805
+ * called.
806
+ *
807
+ * Using lazy report translators improves end-to-end performance by about 3%
808
+ * with Node 8.4.0.
809
+ */
810
+ if (reportTranslator === null) {
811
+ reportTranslator = createReportTranslator({ ruleId, severity, sourceCode, messageIds });
812
+ }
813
+ const problem = reportTranslator.apply(null, arguments);
814
+
815
+ if (problem.fix && rule.meta && !rule.meta.fixable) {
816
+ throw new Error("Fixable rules should export a `meta.fixable` property.");
817
+ }
818
+ lintingProblems.push(problem);
819
+
820
+ /*
821
+ * This is used to avoid breaking rules that used monkeypatch Linter, and relied on
822
+ * `linter.report` getting called with report info every time a rule reports a problem.
823
+ * To continue to support this, make sure that `context._linter.report` is called every
824
+ * time a problem is reported by a rule, even though `context._linter` is no longer a
825
+ * `Linter` instance.
826
+ *
827
+ * This should be removed in a major release after we create a better way to
828
+ * lint for unused disable comments.
829
+ * https://github.com/eslint/eslint/issues/9193
830
+ */
831
+ sharedTraversalContext._linter.report( // eslint-disable-line no-underscore-dangle
832
+ problem.ruleId,
833
+ problem.severity,
834
+ { loc: { start: { line: problem.line, column: problem.column - 1 } } },
835
+ problem.message
836
+ );
837
+ }
838
+ }
839
+ )
840
+ );
841
+
842
+ const ruleListeners = createRuleListeners(rule, ruleContext);
843
+
844
+ // add all the selectors from the rule as listeners
845
+ Object.keys(ruleListeners).forEach(selector => {
846
+ emitter.on(
847
+ selector,
848
+ timing.enabled
849
+ ? timing.time(ruleId, ruleListeners[selector])
850
+ : ruleListeners[selector]
851
+ );
852
+ });
853
+ });
854
+
855
+ const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter));
856
+
857
+ /*
858
+ * Each node has a type property. Whenever a particular type of
859
+ * node is found, an event is fired. This allows any listeners to
860
+ * automatically be informed that this type of node has been found
861
+ * and react accordingly.
862
+ */
863
+ traverser.traverse(sourceCode.ast, {
864
+ enter(node, parent) {
865
+ node.parent = parent;
866
+ eventGenerator.enterNode(node);
867
+ },
868
+ leave(node) {
869
+ eventGenerator.leaveNode(node);
870
+ },
871
+ visitorKeys: sourceCode.visitorKeys
872
+ });
873
+
874
+ return lintingProblems;
875
+ }
876
+
718
877
  const lastSourceCodes = new WeakMap();
878
+ const loadedParserMaps = new WeakMap();
719
879
 
720
880
  //------------------------------------------------------------------------------
721
881
  // Public Interface
@@ -729,10 +889,10 @@ module.exports = class Linter {
729
889
 
730
890
  constructor() {
731
891
  lastSourceCodes.set(this, null);
892
+ loadedParserMaps.set(this, new Map());
732
893
  this.version = pkg.version;
733
894
 
734
895
  this.rules = new Rules();
735
- this._parsers = new Map();
736
896
  this.environments = new Environments();
737
897
  }
738
898
 
@@ -750,7 +910,7 @@ module.exports = class Linter {
750
910
  /**
751
911
  * Same as linter.verify, except without support for processors.
752
912
  * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
753
- * @param {ESLintConfig} config An ESLintConfig instance to configure everything.
913
+ * @param {ESLintConfig} providedConfig An ESLintConfig instance to configure everything.
754
914
  * @param {(string|Object)} [filenameOrOptions] The optional filename of the file being checked.
755
915
  * If this is not set, the filename will default to '<input>' in the rule context. If
756
916
  * an object, then it has "filename", "saveState", and "allowInlineConfig" properties.
@@ -758,23 +918,14 @@ module.exports = class Linter {
758
918
  * Useful if you want to validate JS without comments overriding rules.
759
919
  * @param {boolean} [filenameOrOptions.reportUnusedDisableDirectives=false] Adds reported errors for unused
760
920
  * eslint-disable directives
761
- * @returns {Object[]} The results as an array of messages or null if no messages.
921
+ * @returns {Object[]} The results as an array of messages or an empty array if no messages.
762
922
  */
763
- _verifyWithoutProcessors(textOrSourceCode, config, filenameOrOptions) {
764
- let text,
765
- allowInlineConfig,
766
- providedFilename,
767
- reportUnusedDisableDirectives;
923
+ _verifyWithoutProcessors(textOrSourceCode, providedConfig, filenameOrOptions) {
924
+ const config = providedConfig || {};
925
+ const options = normalizeVerifyOptions(filenameOrOptions);
926
+ let text;
768
927
 
769
928
  // evaluate arguments
770
- if (typeof filenameOrOptions === "object") {
771
- providedFilename = filenameOrOptions.filename;
772
- allowInlineConfig = filenameOrOptions.allowInlineConfig;
773
- reportUnusedDisableDirectives = filenameOrOptions.reportUnusedDisableDirectives;
774
- } else {
775
- providedFilename = filenameOrOptions;
776
- }
777
-
778
929
  if (typeof textOrSourceCode === "string") {
779
930
  lastSourceCodes.set(this, null);
780
931
  text = textOrSourceCode;
@@ -783,23 +934,18 @@ module.exports = class Linter {
783
934
  text = textOrSourceCode.text;
784
935
  }
785
936
 
786
- const filename = typeof providedFilename === "string" ? providedFilename : "<input>";
787
-
788
937
  // search and apply "eslint-env *".
789
938
  const envInFile = findEslintEnv(text);
939
+ const resolvedEnvConfig = Object.assign({ builtin: true }, config.env, envInFile);
940
+ const enabledEnvs = Object.keys(resolvedEnvConfig)
941
+ .filter(envName => resolvedEnvConfig[envName])
942
+ .map(envName => this.environments.get(envName))
943
+ .filter(env => env);
790
944
 
791
- config = Object.assign({}, config);
792
-
793
- if (envInFile) {
794
- if (config.env) {
795
- config.env = Object.assign({}, config.env, envInFile);
796
- } else {
797
- config.env = envInFile;
798
- }
799
- }
800
-
801
- // process initial config to make it safe to extend
802
- config = prepareConfig(config, this.environments);
945
+ const parserOptions = resolveParserOptions(config.parserOptions || {}, enabledEnvs);
946
+ const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs);
947
+ const parserName = config.parser || DEFAULT_PARSER_NAME;
948
+ const settings = config.settings || {};
803
949
 
804
950
  if (!lastSourceCodes.get(this)) {
805
951
 
@@ -809,26 +955,12 @@ module.exports = class Linter {
809
955
  return [];
810
956
  }
811
957
 
812
- let parser;
813
-
814
- try {
815
- parser = this._parsers.get(config.parser) || require(config.parser);
816
- } catch (ex) {
817
- return [{
818
- ruleId: null,
819
- fatal: true,
820
- severity: 2,
821
- source: null,
822
- message: ex.message,
823
- line: 0,
824
- column: 0
825
- }];
826
- }
827
958
  const parseResult = parse(
828
959
  text,
829
- config.parserOptions,
830
- parser,
831
- filename
960
+ parserOptions,
961
+ parserName,
962
+ loadedParserMaps.get(this),
963
+ options.filename
832
964
  );
833
965
 
834
966
  if (!parseResult.success) {
@@ -850,171 +982,41 @@ module.exports = class Linter {
850
982
  ast: lastSourceCode.ast,
851
983
  parserServices: lastSourceCode.parserServices,
852
984
  visitorKeys: lastSourceCode.visitorKeys,
853
- scopeManager: analyzeScope(lastSourceCode.ast, config.parserOptions)
985
+ scopeManager: analyzeScope(lastSourceCode.ast, parserOptions)
854
986
  }));
855
987
  }
856
988
  }
857
989
 
858
- const problems = [];
859
990
  const sourceCode = lastSourceCodes.get(this);
860
- let disableDirectives;
861
-
862
- // parse global comments and modify config
863
- if (allowInlineConfig !== false) {
864
- const modifyConfigResult = modifyConfigsFromComments(filename, sourceCode.ast, config, ruleId => this.rules.get(ruleId));
865
-
866
- config = modifyConfigResult.config;
867
- modifyConfigResult.problems.forEach(problem => problems.push(problem));
868
- disableDirectives = modifyConfigResult.disableDirectives;
869
- } else {
870
- disableDirectives = [];
871
- }
872
-
873
- const emitter = createEmitter();
874
- const traverser = new Traverser();
875
- const scopeManager = sourceCode.scopeManager;
876
-
877
- /*
878
- * Create a frozen object with the ruleContext properties and methods that are shared by all rules.
879
- * All rule contexts will inherit from this object. This avoids the performance penalty of copying all the
880
- * properties once for each rule.
881
- */
882
- const sharedTraversalContext = Object.freeze(
883
- Object.assign(
884
- Object.create(BASE_TRAVERSAL_CONTEXT),
885
- {
886
- getAncestors: () => traverser.parents(),
887
- getDeclaredVariables: scopeManager.getDeclaredVariables.bind(scopeManager),
888
- getFilename: () => filename,
889
- getScope: () => getScope(scopeManager, traverser.current(), config.parserOptions.ecmaVersion),
890
- getSourceCode: () => sourceCode,
891
- markVariableAsUsed: name => markVariableAsUsed(scopeManager, traverser.current(), config.parserOptions, name),
892
- parserOptions: config.parserOptions,
893
- parserPath: config.parser,
894
- parserServices: sourceCode.parserServices,
895
- settings: config.settings,
896
-
897
- /**
898
- * This is used to avoid breaking rules that used to monkeypatch the `Linter#report` method
899
- * by using the `_linter` property on rule contexts.
900
- *
901
- * This should be removed in a major release after we create a better way to
902
- * lint for unused disable comments.
903
- * https://github.com/eslint/eslint/issues/9193
904
- */
905
- _linter: {
906
- report() {},
907
- on: emitter.on
908
- }
909
- }
910
- )
911
- );
912
-
913
- // enable appropriate rules
914
- Object.keys(config.rules).forEach(ruleId => {
915
- const severity = ConfigOps.getRuleSeverity(config.rules[ruleId]);
916
-
917
- if (severity === 0) {
918
- return;
919
- }
920
-
921
- const rule = this.rules.get(ruleId);
922
- const messageIds = rule && rule.meta && rule.meta.messages;
923
- let reportTranslator = null;
924
- const ruleContext = Object.freeze(
925
- Object.assign(
926
- Object.create(sharedTraversalContext),
927
- {
928
- id: ruleId,
929
- options: getRuleOptions(config.rules[ruleId]),
930
- report() {
931
-
932
- /*
933
- * Create a report translator lazily.
934
- * In a vast majority of cases, any given rule reports zero errors on a given
935
- * piece of code. Creating a translator lazily avoids the performance cost of
936
- * creating a new translator function for each rule that usually doesn't get
937
- * called.
938
- *
939
- * Using lazy report translators improves end-to-end performance by about 3%
940
- * with Node 8.4.0.
941
- */
942
- if (reportTranslator === null) {
943
- reportTranslator = createReportTranslator({ ruleId, severity, sourceCode, messageIds });
944
- }
945
- const problem = reportTranslator.apply(null, arguments);
946
-
947
- if (problem.fix && rule.meta && !rule.meta.fixable) {
948
- throw new Error("Fixable rules should export a `meta.fixable` property.");
949
- }
950
- problems.push(problem);
951
-
952
- /*
953
- * This is used to avoid breaking rules that used monkeypatch Linter, and relied on
954
- * `linter.report` getting called with report info every time a rule reports a problem.
955
- * To continue to support this, make sure that `context._linter.report` is called every
956
- * time a problem is reported by a rule, even though `context._linter` is no longer a
957
- * `Linter` instance.
958
- *
959
- * This should be removed in a major release after we create a better way to
960
- * lint for unused disable comments.
961
- * https://github.com/eslint/eslint/issues/9193
962
- */
963
- sharedTraversalContext._linter.report( // eslint-disable-line no-underscore-dangle
964
- problem.ruleId,
965
- problem.severity,
966
- { loc: { start: { line: problem.line, column: problem.column - 1 } } },
967
- problem.message
968
- );
969
- }
970
- }
971
- )
972
- );
973
-
974
- try {
975
- const ruleListeners = rule.create(ruleContext);
976
-
977
- // add all the selectors from the rule as listeners
978
- Object.keys(ruleListeners).forEach(selector => {
979
- emitter.on(
980
- selector,
981
- timing.enabled
982
- ? timing.time(ruleId, ruleListeners[selector])
983
- : ruleListeners[selector]
984
- );
985
- });
986
- } catch (ex) {
987
- ex.message = `Error while loading rule '${ruleId}': ${ex.message}`;
988
- throw ex;
989
- }
990
- });
991
+ const commentDirectives = options.allowInlineConfig
992
+ ? getDirectiveComments(options.filename, sourceCode.ast, ruleId => this.rules.get(ruleId))
993
+ : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
991
994
 
992
995
  // augment global scope with declared global variables
993
- addDeclaredGlobals(scopeManager.scopes[0], config, this.environments);
996
+ addDeclaredGlobals(
997
+ sourceCode.scopeManager.scopes[0],
998
+ configuredGlobals,
999
+ { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals }
1000
+ );
994
1001
 
995
- const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter));
1002
+ const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
996
1003
 
997
- /*
998
- * Each node has a type property. Whenever a particular type of
999
- * node is found, an event is fired. This allows any listeners to
1000
- * automatically be informed that this type of node has been found
1001
- * and react accordingly.
1002
- */
1003
- traverser.traverse(sourceCode.ast, {
1004
- enter(node, parent) {
1005
- node.parent = parent;
1006
- eventGenerator.enterNode(node);
1007
- },
1008
- leave(node) {
1009
- eventGenerator.leaveNode(node);
1010
- },
1011
- visitorKeys: sourceCode.visitorKeys
1012
- });
1004
+ const lintingProblems = runRules(
1005
+ sourceCode,
1006
+ configuredRules,
1007
+ ruleId => this.rules.get(ruleId),
1008
+ parserOptions,
1009
+ parserName,
1010
+ settings,
1011
+ options.filename
1012
+ );
1013
1013
 
1014
1014
  return applyDisableDirectives({
1015
- directives: disableDirectives,
1016
- problems: problems.sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
1017
- reportUnusedDisableDirectives
1015
+ directives: commentDirectives.disableDirectives,
1016
+ problems: lintingProblems
1017
+ .concat(commentDirectives.problems)
1018
+ .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
1019
+ reportUnusedDisableDirectives: options.reportUnusedDisableDirectives
1018
1020
  });
1019
1021
  }
1020
1022
 
@@ -1034,7 +1036,7 @@ module.exports = class Linter {
1034
1036
  * @param {function(Array<Object[]>): Object[]} [filenameOrOptions.postprocess] postprocessor for report messages. If provided,
1035
1037
  * this should accept an array of the message lists for each code block returned from the preprocessor,
1036
1038
  * apply a mapping to the messages as appropriate, and return a one-dimensional array of messages
1037
- * @returns {Object[]} The results as an array of messages or null if no messages.
1039
+ * @returns {Object[]} The results as an array of messages or an empty array if no messages.
1038
1040
  */
1039
1041
  verify(textOrSourceCode, config, filenameOrOptions) {
1040
1042
  const preprocess = filenameOrOptions && filenameOrOptions.preprocess || (rawText => [rawText]);
@@ -1091,7 +1093,7 @@ module.exports = class Linter {
1091
1093
  * @returns {void}
1092
1094
  */
1093
1095
  defineParser(parserId, parserModule) {
1094
- this._parsers.set(parserId, parserModule);
1096
+ loadedParserMaps.get(this).set(parserId, parserModule);
1095
1097
  }
1096
1098
 
1097
1099
  /**