eslint 4.18.1 → 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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ v4.18.2 - March 2, 2018
2
+
3
+ * 6b71fd0 Fix: table@4.0.2, because 4.0.3 needs "ajv": "^6.0.1" (#10022) (Mathieu Seiler)
4
+ * 3c697de Chore: fix incorrect comment about linter.verify return value (#10030) (Teddy Katz)
5
+ * 9df8653 Chore: refactor parser-loading out of linter.verify (#10028) (Teddy Katz)
6
+ * f6901d0 Fix: remove catastrophic backtracking vulnerability (fixes #10002) (#10019) (Jamie Davis)
7
+ * e4f52ce Chore: Simplify dataflow in linter.verify (#10020) (Teddy Katz)
8
+ * 33177cd Chore: make library files non-executable (#10021) (Teddy Katz)
9
+ * 558ccba Chore: refactor directive comment processing (#10007) (Teddy Katz)
10
+ * 18e15d9 Chore: avoid useless catch clauses that just rethrow errors (#10010) (Teddy Katz)
11
+ * a1c3759 Chore: refactor populating configs with defaults in linter (#10006) (Teddy Katz)
12
+ * aea07dc Fix: Make max-len ignoreStrings ignore JSXText (fixes #9954) (#9985) (Rachael Sim)
13
+
1
14
  v4.18.1 - February 20, 2018
2
15
 
3
16
  * f417506 Fix: ensure no-await-in-loop reports the correct node (fixes #9992) (#9993) (Teddy Katz)
@@ -15,7 +15,9 @@ const globals = require("globals");
15
15
  //------------------------------------------------------------------------------
16
16
 
17
17
  module.exports = {
18
- builtin: globals.es5,
18
+ builtin: {
19
+ globals: globals.es5
20
+ },
19
21
  browser: {
20
22
 
21
23
  /*
File without changes
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,108 +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
- if (comment.type === "Block") {
311
- switch (match[1]) {
312
- case "exported":
313
- Object.assign(commentConfig.exported, parseBooleanConfig(value, comment));
314
- break;
315
-
316
- case "globals":
317
- case "global":
318
- Object.assign(commentConfig.astGlobals, parseBooleanConfig(value, comment));
319
- break;
320
-
321
- case "eslint-disable":
322
- [].push.apply(disableDirectives, createDisableDirectives("disable", comment.loc.start, value));
323
- break;
324
-
325
- case "eslint-disable-line":
326
- if (comment.loc.start.line === comment.loc.end.line) {
327
- [].push.apply(disableDirectives, createDisableDirectives("disable-line", comment.loc.start, value));
328
- }
329
- break;
330
-
331
- case "eslint-disable-next-line":
332
- if (comment.loc.start.line === comment.loc.end.line) {
333
- [].push.apply(disableDirectives, createDisableDirectives("disable-next-line", comment.loc.start, value));
334
- }
335
- break;
336
-
337
- case "eslint-enable":
338
- [].push.apply(disableDirectives, createDisableDirectives("enable", comment.loc.start, value));
339
- break;
340
-
341
- case "eslint": {
342
- const parseResult = parseJsonConfig(value, comment.loc);
343
-
344
- if (parseResult.success) {
345
- Object.keys(parseResult.config).forEach(name => {
346
- const ruleValue = parseResult.config[name];
347
-
348
- try {
349
- validator.validateRuleOptions(ruleMapper(name), name, ruleValue);
350
- } catch (err) {
351
- problems.push({
352
- ruleId: name,
353
- severity: 2,
354
- source: null,
355
- message: err.message,
356
- line: comment.loc.start.line,
357
- column: comment.loc.start.column + 1,
358
- endLine: comment.loc.end.line,
359
- endColumn: comment.loc.end.column + 1,
360
- nodeType: null
361
- });
362
- }
363
- commentRules[name] = ruleValue;
364
- });
365
- } else {
366
- problems.push(parseResult.error);
367
- }
281
+ if (!match) {
282
+ return;
283
+ }
368
284
 
369
- 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);
370
336
  }
371
337
 
372
- // no default
373
- }
374
- } else { // comment.type === "Line"
375
- if (match[1] === "eslint-disable-line") {
376
- [].push.apply(disableDirectives, createDisableDirectives("disable-line", comment.loc.start, value));
377
- } else if (match[1] === "eslint-disable-next-line") {
378
- [].push.apply(disableDirectives, createDisableDirectives("disable-next-line", comment.loc.start, value));
338
+ break;
379
339
  }
340
+
341
+ // no default
380
342
  }
381
343
  }
382
344
  });
383
345
 
384
- Object.assign(commentConfig.rules, commentRules);
385
-
386
346
  return {
387
- config: ConfigOps.merge(config, commentConfig),
347
+ configuredRules,
348
+ enabledGlobals,
349
+ exportedVariables,
388
350
  problems,
389
351
  disableDirectives
390
352
  };
@@ -414,81 +376,81 @@ function normalizeEcmaVersion(ecmaVersion, isModule) {
414
376
  return ecmaVersion;
415
377
  }
416
378
 
379
+ const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//g;
380
+
417
381
  /**
418
- * Process initial config to make it safe to extend by file comment config
419
- * @param {Object} config Initial config
420
- * @param {Environments} envContext Env context
421
- * @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.
422
385
  */
423
- function prepareConfig(config, envContext) {
424
- config.globals = config.globals || {};
425
- const copiedRules = {};
426
- let parserOptions = {};
386
+ function findEslintEnv(text) {
387
+ let match, retv;
427
388
 
428
- if (typeof config.rules === "object") {
429
- Object.keys(config.rules).forEach(k => {
430
- const rule = config.rules[k];
389
+ eslintEnvPattern.lastIndex = 0;
431
390
 
432
- if (rule === null) {
433
- throw new Error(`Invalid config for rule '${k}'.`);
434
- }
435
- if (Array.isArray(rule)) {
436
- copiedRules[k] = rule.slice();
437
- } else {
438
- copiedRules[k] = rule;
439
- }
440
- });
391
+ while ((match = eslintEnvPattern.exec(text))) {
392
+ retv = Object.assign(retv || {}, parseListConfig(match[1]));
441
393
  }
442
394
 
443
- // merge in environment parserOptions
444
- if (typeof config.env === "object") {
445
- Object.keys(config.env).forEach(envName => {
446
- const env = envContext.get(envName);
395
+ return retv;
396
+ }
447
397
 
448
- if (config.env[envName] && env && env.parserOptions) {
449
- parserOptions = ConfigOps.merge(parserOptions, env.parserOptions);
450
- }
451
- });
452
- }
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;
453
407
 
454
- const preparedConfig = {
455
- rules: copiedRules,
456
- parser: config.parser || defaultConfig.parser,
457
- globals: ConfigOps.merge(defaultConfig.globals, config.globals),
458
- env: ConfigOps.merge(defaultConfig.env, config.env || {}),
459
- settings: ConfigOps.merge(defaultConfig.settings, config.settings || {}),
460
- 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
461
412
  };
462
- 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";
463
429
 
464
430
  if (isModule) {
465
431
 
466
432
  // can't have global return inside of modules
467
- preparedConfig.parserOptions.ecmaFeatures = Object.assign({}, preparedConfig.parserOptions.ecmaFeatures, { globalReturn: false });
433
+ mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false });
468
434
  }
469
435
 
470
- preparedConfig.parserOptions.ecmaVersion = normalizeEcmaVersion(preparedConfig.parserOptions.ecmaVersion, isModule);
436
+ mergedParserOptions.ecmaVersion = normalizeEcmaVersion(mergedParserOptions.ecmaVersion, isModule);
471
437
 
472
- return preparedConfig;
438
+ return mergedParserOptions;
473
439
  }
474
440
 
475
- const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//g;
476
-
477
441
  /**
478
- * Checks whether or not there is a comment which has "eslint-env *" in a given text.
479
- * @param {string} text - A source code text to check.
480
- * @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
481
446
  */
482
- function findEslintEnv(text) {
483
- let match, retv;
484
-
485
- eslintEnvPattern.lastIndex = 0;
486
-
487
- while ((match = eslintEnvPattern.exec(text))) {
488
- retv = Object.assign(retv || {}, parseListConfig(match[1]));
489
- }
490
-
491
- 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
+ );
492
454
  }
493
455
 
494
456
  /**
@@ -551,13 +513,16 @@ function analyzeScope(ast, parserOptions, visitorKeys) {
551
513
  * as possible
552
514
  * @param {string} text The text to parse.
553
515
  * @param {Object} providedParserOptions Options to pass to the parser
554
- * @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
555
518
  * @param {string} filePath The path to the file being parsed.
556
519
  * @returns {{success: false, error: Problem}|{success: true, sourceCode: SourceCode}}
557
520
  * An object containing the AST and parser services if parsing was successful, or the error if parsing failed
558
521
  * @private
559
522
  */
560
- function parse(text, providedParserOptions, parser, filePath) {
523
+ function parse(text, providedParserOptions, parserName, parserMap, filePath) {
524
+
525
+
561
526
  const textToParse = stripUnicodeBOM(text).replace(astUtils.SHEBANG_MATCHER, (match, captured) => `//${captured}`);
562
527
  const parserOptions = Object.assign({}, providedParserOptions, {
563
528
  loc: true,
@@ -570,6 +535,25 @@ function parse(text, providedParserOptions, parser, filePath) {
570
535
  filePath
571
536
  });
572
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
+
573
557
  /*
574
558
  * Check for parsing errors first. If there's a parsing error, nothing
575
559
  * else can happen. However, a parsing error does not throw an error
@@ -688,6 +672,21 @@ function markVariableAsUsed(scopeManager, currentNode, parserOptions, name) {
688
672
  return false;
689
673
  }
690
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
+
691
690
  // methods that exist on SourceCode object
692
691
  const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
693
692
  getSource: "getText",
@@ -726,7 +725,157 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze(
726
725
  )
727
726
  );
728
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
+
729
877
  const lastSourceCodes = new WeakMap();
878
+ const loadedParserMaps = new WeakMap();
730
879
 
731
880
  //------------------------------------------------------------------------------
732
881
  // Public Interface
@@ -740,10 +889,10 @@ module.exports = class Linter {
740
889
 
741
890
  constructor() {
742
891
  lastSourceCodes.set(this, null);
892
+ loadedParserMaps.set(this, new Map());
743
893
  this.version = pkg.version;
744
894
 
745
895
  this.rules = new Rules();
746
- this._parsers = new Map();
747
896
  this.environments = new Environments();
748
897
  }
749
898
 
@@ -761,7 +910,7 @@ module.exports = class Linter {
761
910
  /**
762
911
  * Same as linter.verify, except without support for processors.
763
912
  * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
764
- * @param {ESLintConfig} config An ESLintConfig instance to configure everything.
913
+ * @param {ESLintConfig} providedConfig An ESLintConfig instance to configure everything.
765
914
  * @param {(string|Object)} [filenameOrOptions] The optional filename of the file being checked.
766
915
  * If this is not set, the filename will default to '<input>' in the rule context. If
767
916
  * an object, then it has "filename", "saveState", and "allowInlineConfig" properties.
@@ -769,23 +918,14 @@ module.exports = class Linter {
769
918
  * Useful if you want to validate JS without comments overriding rules.
770
919
  * @param {boolean} [filenameOrOptions.reportUnusedDisableDirectives=false] Adds reported errors for unused
771
920
  * eslint-disable directives
772
- * @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.
773
922
  */
774
- _verifyWithoutProcessors(textOrSourceCode, config, filenameOrOptions) {
775
- let text,
776
- allowInlineConfig,
777
- providedFilename,
778
- reportUnusedDisableDirectives;
923
+ _verifyWithoutProcessors(textOrSourceCode, providedConfig, filenameOrOptions) {
924
+ const config = providedConfig || {};
925
+ const options = normalizeVerifyOptions(filenameOrOptions);
926
+ let text;
779
927
 
780
928
  // evaluate arguments
781
- if (typeof filenameOrOptions === "object") {
782
- providedFilename = filenameOrOptions.filename;
783
- allowInlineConfig = filenameOrOptions.allowInlineConfig;
784
- reportUnusedDisableDirectives = filenameOrOptions.reportUnusedDisableDirectives;
785
- } else {
786
- providedFilename = filenameOrOptions;
787
- }
788
-
789
929
  if (typeof textOrSourceCode === "string") {
790
930
  lastSourceCodes.set(this, null);
791
931
  text = textOrSourceCode;
@@ -794,23 +934,18 @@ module.exports = class Linter {
794
934
  text = textOrSourceCode.text;
795
935
  }
796
936
 
797
- const filename = typeof providedFilename === "string" ? providedFilename : "<input>";
798
-
799
937
  // search and apply "eslint-env *".
800
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);
801
944
 
802
- config = Object.assign({}, config);
803
-
804
- if (envInFile) {
805
- if (config.env) {
806
- config.env = Object.assign({}, config.env, envInFile);
807
- } else {
808
- config.env = envInFile;
809
- }
810
- }
811
-
812
- // process initial config to make it safe to extend
813
- config = prepareConfig(config, this.environments);
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 || {};
814
949
 
815
950
  if (!lastSourceCodes.get(this)) {
816
951
 
@@ -820,26 +955,12 @@ module.exports = class Linter {
820
955
  return [];
821
956
  }
822
957
 
823
- let parser;
824
-
825
- try {
826
- parser = this._parsers.get(config.parser) || require(config.parser);
827
- } catch (ex) {
828
- return [{
829
- ruleId: null,
830
- fatal: true,
831
- severity: 2,
832
- source: null,
833
- message: ex.message,
834
- line: 0,
835
- column: 0
836
- }];
837
- }
838
958
  const parseResult = parse(
839
959
  text,
840
- config.parserOptions,
841
- parser,
842
- filename
960
+ parserOptions,
961
+ parserName,
962
+ loadedParserMaps.get(this),
963
+ options.filename
843
964
  );
844
965
 
845
966
  if (!parseResult.success) {
@@ -861,171 +982,41 @@ module.exports = class Linter {
861
982
  ast: lastSourceCode.ast,
862
983
  parserServices: lastSourceCode.parserServices,
863
984
  visitorKeys: lastSourceCode.visitorKeys,
864
- scopeManager: analyzeScope(lastSourceCode.ast, config.parserOptions)
985
+ scopeManager: analyzeScope(lastSourceCode.ast, parserOptions)
865
986
  }));
866
987
  }
867
988
  }
868
989
 
869
- const problems = [];
870
990
  const sourceCode = lastSourceCodes.get(this);
871
- let disableDirectives;
872
-
873
- // parse global comments and modify config
874
- if (allowInlineConfig !== false) {
875
- const modifyConfigResult = modifyConfigsFromComments(filename, sourceCode.ast, config, ruleId => this.rules.get(ruleId));
876
-
877
- config = modifyConfigResult.config;
878
- modifyConfigResult.problems.forEach(problem => problems.push(problem));
879
- disableDirectives = modifyConfigResult.disableDirectives;
880
- } else {
881
- disableDirectives = [];
882
- }
883
-
884
- const emitter = createEmitter();
885
- const traverser = new Traverser();
886
- const scopeManager = sourceCode.scopeManager;
887
-
888
- /*
889
- * Create a frozen object with the ruleContext properties and methods that are shared by all rules.
890
- * All rule contexts will inherit from this object. This avoids the performance penalty of copying all the
891
- * properties once for each rule.
892
- */
893
- const sharedTraversalContext = Object.freeze(
894
- Object.assign(
895
- Object.create(BASE_TRAVERSAL_CONTEXT),
896
- {
897
- getAncestors: () => traverser.parents(),
898
- getDeclaredVariables: scopeManager.getDeclaredVariables.bind(scopeManager),
899
- getFilename: () => filename,
900
- getScope: () => getScope(scopeManager, traverser.current(), config.parserOptions.ecmaVersion),
901
- getSourceCode: () => sourceCode,
902
- markVariableAsUsed: name => markVariableAsUsed(scopeManager, traverser.current(), config.parserOptions, name),
903
- parserOptions: config.parserOptions,
904
- parserPath: config.parser,
905
- parserServices: sourceCode.parserServices,
906
- settings: config.settings,
907
-
908
- /**
909
- * This is used to avoid breaking rules that used to monkeypatch the `Linter#report` method
910
- * by using the `_linter` property on rule contexts.
911
- *
912
- * This should be removed in a major release after we create a better way to
913
- * lint for unused disable comments.
914
- * https://github.com/eslint/eslint/issues/9193
915
- */
916
- _linter: {
917
- report() {},
918
- on: emitter.on
919
- }
920
- }
921
- )
922
- );
923
-
924
- // enable appropriate rules
925
- Object.keys(config.rules).forEach(ruleId => {
926
- const severity = ConfigOps.getRuleSeverity(config.rules[ruleId]);
927
-
928
- if (severity === 0) {
929
- return;
930
- }
931
-
932
- const rule = this.rules.get(ruleId);
933
- const messageIds = rule && rule.meta && rule.meta.messages;
934
- let reportTranslator = null;
935
- const ruleContext = Object.freeze(
936
- Object.assign(
937
- Object.create(sharedTraversalContext),
938
- {
939
- id: ruleId,
940
- options: getRuleOptions(config.rules[ruleId]),
941
- report() {
942
-
943
- /*
944
- * Create a report translator lazily.
945
- * In a vast majority of cases, any given rule reports zero errors on a given
946
- * piece of code. Creating a translator lazily avoids the performance cost of
947
- * creating a new translator function for each rule that usually doesn't get
948
- * called.
949
- *
950
- * Using lazy report translators improves end-to-end performance by about 3%
951
- * with Node 8.4.0.
952
- */
953
- if (reportTranslator === null) {
954
- reportTranslator = createReportTranslator({ ruleId, severity, sourceCode, messageIds });
955
- }
956
- const problem = reportTranslator.apply(null, arguments);
957
-
958
- if (problem.fix && rule.meta && !rule.meta.fixable) {
959
- throw new Error("Fixable rules should export a `meta.fixable` property.");
960
- }
961
- problems.push(problem);
962
-
963
- /*
964
- * This is used to avoid breaking rules that used monkeypatch Linter, and relied on
965
- * `linter.report` getting called with report info every time a rule reports a problem.
966
- * To continue to support this, make sure that `context._linter.report` is called every
967
- * time a problem is reported by a rule, even though `context._linter` is no longer a
968
- * `Linter` instance.
969
- *
970
- * This should be removed in a major release after we create a better way to
971
- * lint for unused disable comments.
972
- * https://github.com/eslint/eslint/issues/9193
973
- */
974
- sharedTraversalContext._linter.report( // eslint-disable-line no-underscore-dangle
975
- problem.ruleId,
976
- problem.severity,
977
- { loc: { start: { line: problem.line, column: problem.column - 1 } } },
978
- problem.message
979
- );
980
- }
981
- }
982
- )
983
- );
984
-
985
- try {
986
- const ruleListeners = rule.create(ruleContext);
987
-
988
- // add all the selectors from the rule as listeners
989
- Object.keys(ruleListeners).forEach(selector => {
990
- emitter.on(
991
- selector,
992
- timing.enabled
993
- ? timing.time(ruleId, ruleListeners[selector])
994
- : ruleListeners[selector]
995
- );
996
- });
997
- } catch (ex) {
998
- ex.message = `Error while loading rule '${ruleId}': ${ex.message}`;
999
- throw ex;
1000
- }
1001
- });
991
+ const commentDirectives = options.allowInlineConfig
992
+ ? getDirectiveComments(options.filename, sourceCode.ast, ruleId => this.rules.get(ruleId))
993
+ : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
1002
994
 
1003
995
  // augment global scope with declared global variables
1004
- addDeclaredGlobals(scopeManager.scopes[0], config, this.environments);
996
+ addDeclaredGlobals(
997
+ sourceCode.scopeManager.scopes[0],
998
+ configuredGlobals,
999
+ { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals }
1000
+ );
1005
1001
 
1006
- const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter));
1002
+ const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
1007
1003
 
1008
- /*
1009
- * Each node has a type property. Whenever a particular type of
1010
- * node is found, an event is fired. This allows any listeners to
1011
- * automatically be informed that this type of node has been found
1012
- * and react accordingly.
1013
- */
1014
- traverser.traverse(sourceCode.ast, {
1015
- enter(node, parent) {
1016
- node.parent = parent;
1017
- eventGenerator.enterNode(node);
1018
- },
1019
- leave(node) {
1020
- eventGenerator.leaveNode(node);
1021
- },
1022
- visitorKeys: sourceCode.visitorKeys
1023
- });
1004
+ const lintingProblems = runRules(
1005
+ sourceCode,
1006
+ configuredRules,
1007
+ ruleId => this.rules.get(ruleId),
1008
+ parserOptions,
1009
+ parserName,
1010
+ settings,
1011
+ options.filename
1012
+ );
1024
1013
 
1025
1014
  return applyDisableDirectives({
1026
- directives: disableDirectives,
1027
- problems: problems.sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
1028
- 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
1029
1020
  });
1030
1021
  }
1031
1022
 
@@ -1045,7 +1036,7 @@ module.exports = class Linter {
1045
1036
  * @param {function(Array<Object[]>): Object[]} [filenameOrOptions.postprocess] postprocessor for report messages. If provided,
1046
1037
  * this should accept an array of the message lists for each code block returned from the preprocessor,
1047
1038
  * apply a mapping to the messages as appropriate, and return a one-dimensional array of messages
1048
- * @returns {Object[]} The results as an array of messages or null if no messages.
1039
+ * @returns {Object[]} The results as an array of messages or an empty array if no messages.
1049
1040
  */
1050
1041
  verify(textOrSourceCode, config, filenameOrOptions) {
1051
1042
  const preprocess = filenameOrOptions && filenameOrOptions.preprocess || (rawText => [rawText]);
@@ -1102,7 +1093,7 @@ module.exports = class Linter {
1102
1093
  * @returns {void}
1103
1094
  */
1104
1095
  defineParser(parserId, parserModule) {
1105
- this._parsers.set(parserId, parserModule);
1096
+ loadedParserMaps.get(this).set(parserId, parserModule);
1106
1097
  }
1107
1098
 
1108
1099
  /**
@@ -213,7 +213,8 @@ module.exports = {
213
213
  * @returns {ASTNode[]} An array of string nodes.
214
214
  */
215
215
  function getAllStrings() {
216
- return sourceCode.ast.tokens.filter(token => token.type === "String");
216
+ return sourceCode.ast.tokens.filter(token => (token.type === "String" ||
217
+ (token.type === "JSXText" && sourceCode.getNodeByRangeIndex(token.range[0] - 1).type === "JSXAttribute")));
217
218
  }
218
219
 
219
220
  /**
File without changes
@@ -13,7 +13,11 @@ module.exports = (text, data) => {
13
13
  if (!data) {
14
14
  return text;
15
15
  }
16
- return text.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (fullMatch, term) => {
16
+
17
+ // Substitution content for any {{ }} markers.
18
+ return text.replace(/\{\{([^{}]+?)\}\}/g, (fullMatch, termWithWhitespace) => {
19
+ const term = termWithWhitespace.trim();
20
+
17
21
  if (term in data) {
18
22
  return data[term];
19
23
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "4.18.1",
3
+ "version": "4.18.2",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {
@@ -69,7 +69,7 @@
69
69
  "semver": "^5.3.0",
70
70
  "strip-ansi": "^4.0.0",
71
71
  "strip-json-comments": "~2.0.1",
72
- "table": "^4.0.1",
72
+ "table": "4.0.2",
73
73
  "text-table": "~0.2.0"
74
74
  },
75
75
  "devDependencies": {
@@ -1,29 +0,0 @@
1
- /**
2
- * @fileoverview Default config options
3
- * @author Teddy Katz
4
- */
5
-
6
- "use strict";
7
-
8
- /**
9
- * Freezes an object and all its nested properties
10
- * @param {Object} obj The object to deeply freeze
11
- * @returns {Object} `obj` after freezing it
12
- */
13
- function deepFreeze(obj) {
14
- if (obj === null || typeof obj !== "object") {
15
- return obj;
16
- }
17
-
18
- Object.keys(obj).map(key => obj[key]).forEach(deepFreeze);
19
- return Object.freeze(obj);
20
- }
21
-
22
- module.exports = deepFreeze({
23
- env: {},
24
- globals: {},
25
- rules: {},
26
- settings: {},
27
- parser: "espree",
28
- parserOptions: {}
29
- });