eslint 7.0.0-alpha.2 → 7.1.0

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 (90) hide show
  1. package/CHANGELOG.md +332 -0
  2. package/README.md +9 -10
  3. package/bin/eslint.js +115 -77
  4. package/conf/category-list.json +0 -1
  5. package/conf/environments.js +2 -1
  6. package/lib/api.js +2 -0
  7. package/lib/cli-engine/cascading-config-array-factory.js +16 -2
  8. package/lib/cli-engine/cli-engine.js +53 -47
  9. package/lib/cli-engine/config-array/config-array.js +30 -1
  10. package/lib/cli-engine/config-array/ignore-pattern.js +7 -1
  11. package/lib/cli-engine/config-array-factory.js +244 -235
  12. package/lib/cli.js +181 -95
  13. package/lib/eslint/eslint.js +656 -0
  14. package/lib/eslint/index.js +7 -0
  15. package/lib/init/autoconfig.js +4 -4
  16. package/lib/init/config-file.js +2 -2
  17. package/lib/init/config-initializer.js +3 -4
  18. package/lib/init/source-code-utils.js +2 -2
  19. package/lib/linter/linter.js +2 -1
  20. package/lib/linter/node-event-generator.js +2 -2
  21. package/lib/options.js +0 -1
  22. package/lib/rule-tester/rule-tester.js +132 -22
  23. package/lib/rules/accessor-pairs.js +1 -1
  24. package/lib/rules/array-callback-return.js +3 -18
  25. package/lib/rules/arrow-parens.js +19 -3
  26. package/lib/rules/block-spacing.js +19 -2
  27. package/lib/rules/callback-return.js +4 -0
  28. package/lib/rules/camelcase.js +38 -1
  29. package/lib/rules/comma-style.js +3 -8
  30. package/lib/rules/func-call-spacing.js +4 -3
  31. package/lib/rules/getter-return.js +2 -12
  32. package/lib/rules/global-require.js +4 -0
  33. package/lib/rules/handle-callback-err.js +4 -0
  34. package/lib/rules/id-blacklist.js +138 -102
  35. package/lib/rules/index.js +1 -0
  36. package/lib/rules/key-spacing.js +1 -1
  37. package/lib/rules/linebreak-style.js +8 -2
  38. package/lib/rules/max-lines-per-function.js +1 -1
  39. package/lib/rules/new-cap.js +1 -1
  40. package/lib/rules/newline-per-chained-call.js +6 -3
  41. package/lib/rules/no-alert.js +5 -3
  42. package/lib/rules/no-buffer-constructor.js +4 -0
  43. package/lib/rules/no-empty-function.js +4 -2
  44. package/lib/rules/no-eval.js +2 -1
  45. package/lib/rules/no-extra-bind.js +1 -1
  46. package/lib/rules/no-extra-boolean-cast.js +102 -23
  47. package/lib/rules/no-extra-parens.js +9 -5
  48. package/lib/rules/no-implied-eval.js +83 -101
  49. package/lib/rules/no-inner-declarations.js +31 -39
  50. package/lib/rules/no-lone-blocks.js +1 -1
  51. package/lib/rules/no-loss-of-precision.js +198 -0
  52. package/lib/rules/no-magic-numbers.js +72 -37
  53. package/lib/rules/no-mixed-requires.js +4 -0
  54. package/lib/rules/no-new-func.js +22 -19
  55. package/lib/rules/no-new-object.js +15 -3
  56. package/lib/rules/no-new-require.js +4 -0
  57. package/lib/rules/no-new-symbol.js +2 -1
  58. package/lib/rules/no-new-wrappers.js +1 -1
  59. package/lib/rules/no-obj-calls.js +24 -5
  60. package/lib/rules/no-path-concat.js +4 -0
  61. package/lib/rules/no-plusplus.js +39 -3
  62. package/lib/rules/no-process-env.js +4 -0
  63. package/lib/rules/no-process-exit.js +4 -0
  64. package/lib/rules/no-prototype-builtins.js +1 -1
  65. package/lib/rules/no-restricted-modules.js +4 -0
  66. package/lib/rules/no-sync.js +4 -0
  67. package/lib/rules/no-unexpected-multiline.js +22 -12
  68. package/lib/rules/no-useless-concat.js +1 -1
  69. package/lib/rules/one-var-declaration-per-line.js +1 -1
  70. package/lib/rules/operator-assignment.js +3 -3
  71. package/lib/rules/operator-linebreak.js +4 -16
  72. package/lib/rules/padded-blocks.js +17 -4
  73. package/lib/rules/prefer-numeric-literals.js +3 -3
  74. package/lib/rules/prefer-object-spread.js +2 -2
  75. package/lib/rules/require-await.js +1 -1
  76. package/lib/rules/rest-spread-spacing.js +3 -6
  77. package/lib/rules/semi-spacing.js +32 -8
  78. package/lib/rules/space-before-function-paren.js +5 -2
  79. package/lib/rules/template-curly-spacing.js +59 -42
  80. package/lib/rules/utils/ast-utils.js +116 -10
  81. package/lib/rules/yoda.js +101 -51
  82. package/lib/shared/relative-module-resolver.js +1 -0
  83. package/lib/shared/types.js +9 -2
  84. package/lib/source-code/source-code.js +1 -0
  85. package/messages/extend-config-missing.txt +1 -1
  86. package/messages/no-config-found.txt +1 -1
  87. package/messages/plugin-conflict.txt +7 -0
  88. package/messages/plugin-missing.txt +1 -1
  89. package/messages/whitespace-found.txt +1 -1
  90. package/package.json +27 -26
@@ -45,7 +45,7 @@ function sortByKey(a, b) {
45
45
  function writeJSONConfigFile(config, filePath) {
46
46
  debug(`Writing JSON config file: ${filePath}`);
47
47
 
48
- const content = stringify(config, { cmp: sortByKey, space: 4 });
48
+ const content = `${stringify(config, { cmp: sortByKey, space: 4 })}\n`;
49
49
 
50
50
  fs.writeFileSync(filePath, content, "utf8");
51
51
  }
@@ -80,7 +80,7 @@ function writeJSConfigFile(config, filePath) {
80
80
  debug(`Writing JS config file: ${filePath}`);
81
81
 
82
82
  let contentToWrite;
83
- const stringifiedContent = `module.exports = ${stringify(config, { cmp: sortByKey, space: 4 })};`;
83
+ const stringifiedContent = `module.exports = ${stringify(config, { cmp: sortByKey, space: 4 })};\n`;
84
84
 
85
85
  try {
86
86
  const { CLIEngine } = require("../cli-engine");
@@ -15,6 +15,7 @@ const util = require("util"),
15
15
  inquirer = require("inquirer"),
16
16
  ProgressBar = require("progress"),
17
17
  semver = require("semver"),
18
+ espree = require("espree"),
18
19
  recConfig = require("../../conf/eslint-recommended"),
19
20
  ConfigOps = require("../shared/config-ops"),
20
21
  log = require("../shared/logging"),
@@ -31,8 +32,6 @@ const debug = require("debug")("eslint:config-initializer");
31
32
  // Private
32
33
  //------------------------------------------------------------------------------
33
34
 
34
- const DEFAULT_ECMA_VERSION = 2018;
35
-
36
35
  /* istanbul ignore next: hard to test fs function */
37
36
  /**
38
37
  * Create .eslintrc file in the current working directory
@@ -265,8 +264,7 @@ function processAnswers(answers) {
265
264
  extends: []
266
265
  };
267
266
 
268
- // set the latest ECMAScript version
269
- config.parserOptions.ecmaVersion = DEFAULT_ECMA_VERSION;
267
+ config.parserOptions.ecmaVersion = espree.latestEcmaVersion;
270
268
  config.env.es6 = true;
271
269
  config.globals = {
272
270
  Atomics: "readonly",
@@ -328,6 +326,7 @@ function processAnswers(answers) {
328
326
  }
329
327
  if (answers.typescript && config.extends.includes("eslint:recommended")) {
330
328
  config.extends.push("plugin:@typescript-eslint/eslint-recommended");
329
+ config.extends.push("plugin:@typescript-eslint/recommended");
331
330
  }
332
331
 
333
332
  // normalize extends
@@ -23,7 +23,7 @@ const { CLIEngine } = require("../cli-engine");
23
23
  * TODO1: Expose the API that enumerates target files.
24
24
  * TODO2: Extract the creation logic of `SourceCode` from `Linter` class.
25
25
  */
26
- const { getCLIEngineInternalSlots } = require("../cli-engine/cli-engine"); // eslint-disable-line no-restricted-modules
26
+ const { getCLIEngineInternalSlots } = require("../cli-engine/cli-engine"); // eslint-disable-line node/no-restricted-require
27
27
 
28
28
  const debug = require("debug")("eslint:source-code-utils");
29
29
 
@@ -97,7 +97,7 @@ function getSourceCodeOfFiles(patterns, options, callback) {
97
97
  sourceCodes[filename] = sourceCode;
98
98
  }
99
99
  if (callback) {
100
- callback(filenames.length); // eslint-disable-line callback-return
100
+ callback(filenames.length); // eslint-disable-line node/callback-return
101
101
  }
102
102
  });
103
103
 
@@ -938,7 +938,8 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
938
938
  });
939
939
  });
940
940
 
941
- const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter));
941
+ // only run code path analyzer if the top level node is "Program", skip otherwise
942
+ const eventGenerator = nodeQueue[0].node.type === "Program" ? new CodePathAnalyzer(new NodeEventGenerator(emitter)) : new NodeEventGenerator(emitter);
942
943
 
943
944
  nodeQueue.forEach(traversalInfo => {
944
945
  currentNode = traversalInfo.node;
@@ -159,8 +159,8 @@ function tryParseSelector(rawSelector) {
159
159
  try {
160
160
  return esquery.parse(rawSelector.replace(/:exit$/u, ""));
161
161
  } catch (err) {
162
- if (typeof err.offset === "number") {
163
- throw new SyntaxError(`Syntax error in selector "${rawSelector}" at position ${err.offset}: ${err.message}`);
162
+ if (err.location && err.location.start && typeof err.location.start.offset === "number") {
163
+ throw new SyntaxError(`Syntax error in selector "${rawSelector}" at position ${err.location.start.offset}: ${err.message}`);
164
164
  }
165
165
  throw err;
166
166
  }
package/lib/options.js CHANGED
@@ -46,7 +46,6 @@ module.exports = optionator({
46
46
  {
47
47
  option: "ext",
48
48
  type: "[String]",
49
- default: ".js",
50
49
  description: "Specify JavaScript file extensions"
51
50
  },
52
51
  {
@@ -45,16 +45,20 @@ const
45
45
  path = require("path"),
46
46
  util = require("util"),
47
47
  lodash = require("lodash"),
48
+ Traverser = require("../../lib/shared/traverser"),
48
49
  { getRuleOptionsSchema, validate } = require("../shared/config-validator"),
49
50
  { Linter, SourceCodeFixer, interpolate } = require("../linter");
50
51
 
51
52
  const ajv = require("../shared/ajv")({ strictDefaults: true });
52
53
 
54
+ const espreePath = require.resolve("espree");
53
55
 
54
56
  //------------------------------------------------------------------------------
55
57
  // Typedefs
56
58
  //------------------------------------------------------------------------------
57
59
 
60
+ /** @typedef {import("../shared/types").Parser} Parser */
61
+
58
62
  /**
59
63
  * A test case that is expected to pass lint.
60
64
  * @typedef {Object} ValidTestCase
@@ -141,6 +145,7 @@ const friendlyErrorObjectParameterList = `[${[...errorObjectParameters].map(key
141
145
  const suggestionObjectParameters = new Set([
142
146
  "desc",
143
147
  "messageId",
148
+ "data",
144
149
  "output"
145
150
  ]);
146
151
  const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`;
@@ -205,6 +210,70 @@ function sanitize(text) {
205
210
  );
206
211
  }
207
212
 
213
+ /**
214
+ * Define `start`/`end` properties as throwing error.
215
+ * @param {string} objName Object name used for error messages.
216
+ * @param {ASTNode} node The node to define.
217
+ * @returns {void}
218
+ */
219
+ function defineStartEndAsError(objName, node) {
220
+ Object.defineProperties(node, {
221
+ start: {
222
+ get() {
223
+ throw new Error(`Use ${objName}.range[0] instead of ${objName}.start`);
224
+ },
225
+ configurable: true,
226
+ enumerable: false
227
+ },
228
+ end: {
229
+ get() {
230
+ throw new Error(`Use ${objName}.range[1] instead of ${objName}.end`);
231
+ },
232
+ configurable: true,
233
+ enumerable: false
234
+ }
235
+ });
236
+ }
237
+
238
+ /**
239
+ * Define `start`/`end` properties of all nodes of the given AST as throwing error.
240
+ * @param {ASTNode} ast The root node to errorize `start`/`end` properties.
241
+ * @param {Object} [visitorKeys] Visitor keys to be used for traversing the given ast.
242
+ * @returns {void}
243
+ */
244
+ function defineStartEndAsErrorInTree(ast, visitorKeys) {
245
+ Traverser.traverse(ast, { visitorKeys, enter: defineStartEndAsError.bind(null, "node") });
246
+ ast.tokens.forEach(defineStartEndAsError.bind(null, "token"));
247
+ ast.comments.forEach(defineStartEndAsError.bind(null, "token"));
248
+ }
249
+
250
+ /**
251
+ * Wraps the given parser in order to intercept and modify return values from the `parse` and `parseForESLint` methods, for test purposes.
252
+ * In particular, to modify ast nodes, tokens and comments to throw on access to their `start` and `end` properties.
253
+ * @param {Parser} parser Parser object.
254
+ * @returns {Parser} Wrapped parser object.
255
+ */
256
+ function wrapParser(parser) {
257
+ if (typeof parser.parseForESLint === "function") {
258
+ return {
259
+ parseForESLint(...args) {
260
+ const ret = parser.parseForESLint(...args);
261
+
262
+ defineStartEndAsErrorInTree(ret.ast, ret.visitorKeys);
263
+ return ret;
264
+ }
265
+ };
266
+ }
267
+ return {
268
+ parse(...args) {
269
+ const ast = parser.parse(...args);
270
+
271
+ defineStartEndAsErrorInTree(ast);
272
+ return ast;
273
+ }
274
+ };
275
+ }
276
+
208
277
  //------------------------------------------------------------------------------
209
278
  // Public Interface
210
279
  //------------------------------------------------------------------------------
@@ -449,9 +518,12 @@ class RuleTester {
449
518
 
450
519
  if (typeof config.parser === "string") {
451
520
  assert(path.isAbsolute(config.parser), "Parsers provided as strings to RuleTester must be absolute paths");
452
- linter.defineParser(config.parser, require(config.parser));
521
+ } else {
522
+ config.parser = espreePath;
453
523
  }
454
524
 
525
+ linter.defineParser(config.parser, wrapParser(require(config.parser)));
526
+
455
527
  if (schema) {
456
528
  ajv.validateSchema(schema);
457
529
 
@@ -482,20 +554,21 @@ class RuleTester {
482
554
 
483
555
  // Verify the code.
484
556
  const messages = linter.verify(code, config, filename);
557
+ const fatalErrorMessage = messages.find(m => m.fatal);
485
558
 
486
- // Ignore syntax errors for backward compatibility if `errors` is a number.
487
- if (typeof item.errors !== "number") {
488
- const errorMessage = messages.find(m => m.fatal);
489
-
490
- assert(!errorMessage, `A fatal parsing error occurred: ${errorMessage && errorMessage.message}`);
491
- }
559
+ assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`);
492
560
 
493
561
  // Verify if autofix makes a syntax error or not.
494
562
  if (messages.some(m => m.fix)) {
495
563
  output = SourceCodeFixer.applyFixes(code, messages).output;
496
564
  const errorMessageInFix = linter.verify(output, config, filename).find(m => m.fatal);
497
565
 
498
- assert(!errorMessageInFix, `A fatal parsing error occurred in autofix: ${errorMessageInFix && errorMessageInFix.message}`);
566
+ assert(!errorMessageInFix, [
567
+ "A fatal parsing error occurred in autofix.",
568
+ `Error: ${errorMessageInFix && errorMessageInFix.message}`,
569
+ "Autofix output:",
570
+ output
571
+ ].join("\n"));
499
572
  } else {
500
573
  output = code;
501
574
  }
@@ -571,10 +644,12 @@ class RuleTester {
571
644
  assert.ok(item.errors || item.errors === 0,
572
645
  `Did not specify errors for an invalid test of ${ruleName}`);
573
646
 
647
+ const ruleHasMetaMessages = hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages");
648
+ const friendlyIDList = ruleHasMetaMessages ? `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]` : null;
649
+
574
650
  const result = runRuleForItem(item);
575
651
  const messages = result.messages;
576
652
 
577
-
578
653
  if (typeof item.errors === "number") {
579
654
  assert.strictEqual(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s",
580
655
  item.errors, item.errors === 1 ? "" : "s", messages.length, util.inspect(messages)));
@@ -620,12 +695,10 @@ class RuleTester {
620
695
  assertMessageMatches(message.message, error.message);
621
696
  } else if (hasOwnProperty(error, "messageId")) {
622
697
  assert.ok(
623
- hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages"),
698
+ ruleHasMetaMessages,
624
699
  "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'."
625
700
  );
626
701
  if (!hasOwnProperty(rule.meta.messages, error.messageId)) {
627
- const friendlyIDList = `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]`;
628
-
629
702
  assert(false, `Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`);
630
703
  }
631
704
  assert.strictEqual(
@@ -700,19 +773,50 @@ class RuleTester {
700
773
  });
701
774
 
702
775
  const actualSuggestion = message.suggestions[index];
776
+ const suggestionPrefix = `Error Suggestion at index ${index} :`;
777
+
778
+ if (hasOwnProperty(expectedSuggestion, "desc")) {
779
+ assert.ok(
780
+ !hasOwnProperty(expectedSuggestion, "data"),
781
+ `${suggestionPrefix} Test should not specify both 'desc' and 'data'.`
782
+ );
783
+ assert.strictEqual(
784
+ actualSuggestion.desc,
785
+ expectedSuggestion.desc,
786
+ `${suggestionPrefix} desc should be "${expectedSuggestion.desc}" but got "${actualSuggestion.desc}" instead.`
787
+ );
788
+ }
703
789
 
704
- /**
705
- * Tests equality of a suggestion key if that key is defined in the expected output.
706
- * @param {string} key Key to validate from the suggestion object
707
- * @returns {void}
708
- */
709
- function assertSuggestionKeyEquals(key) {
710
- if (hasOwnProperty(expectedSuggestion, key)) {
711
- assert.deepStrictEqual(actualSuggestion[key], expectedSuggestion[key], `Error suggestion at index: ${index} should have desc of: "${actualSuggestion[key]}"`);
790
+ if (hasOwnProperty(expectedSuggestion, "messageId")) {
791
+ assert.ok(
792
+ ruleHasMetaMessages,
793
+ `${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.`
794
+ );
795
+ assert.ok(
796
+ hasOwnProperty(rule.meta.messages, expectedSuggestion.messageId),
797
+ `${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.`
798
+ );
799
+ assert.strictEqual(
800
+ actualSuggestion.messageId,
801
+ expectedSuggestion.messageId,
802
+ `${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.`
803
+ );
804
+ if (hasOwnProperty(expectedSuggestion, "data")) {
805
+ const unformattedMetaMessage = rule.meta.messages[expectedSuggestion.messageId];
806
+ const rehydratedDesc = interpolate(unformattedMetaMessage, expectedSuggestion.data);
807
+
808
+ assert.strictEqual(
809
+ actualSuggestion.desc,
810
+ rehydratedDesc,
811
+ `${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".`
812
+ );
712
813
  }
814
+ } else {
815
+ assert.ok(
816
+ !hasOwnProperty(expectedSuggestion, "data"),
817
+ `${suggestionPrefix} Test must specify 'messageId' if 'data' is used.`
818
+ );
713
819
  }
714
- assertSuggestionKeyEquals("desc");
715
- assertSuggestionKeyEquals("messageId");
716
820
 
717
821
  if (hasOwnProperty(expectedSuggestion, "output")) {
718
822
  const codeWithAppliedSuggestion = SourceCodeFixer.applyFixes(item.code, [actualSuggestion]).output;
@@ -740,6 +844,12 @@ class RuleTester {
740
844
  } else {
741
845
  assert.strictEqual(result.output, item.output, "Output is incorrect.");
742
846
  }
847
+ } else {
848
+ assert.strictEqual(
849
+ result.output,
850
+ item.code,
851
+ "The rule fixed the code. Please add 'output' property."
852
+ );
743
853
  }
744
854
 
745
855
  assertASTDidntChange(result.beforeAST, result.afterAST);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @fileoverview Rule to flag wrapping non-iife in parens
2
+ * @fileoverview Rule to enforce getter and setter pairs in objects and classes.
3
3
  * @author Gyandeep Singh
4
4
  */
5
5
 
@@ -29,22 +29,6 @@ function isReachable(segment) {
29
29
  return segment.reachable;
30
30
  }
31
31
 
32
- /**
33
- * Gets a readable location.
34
- *
35
- * - FunctionExpression -> the function name or `function` keyword.
36
- * - ArrowFunctionExpression -> `=>` token.
37
- * @param {ASTNode} node A function node to get.
38
- * @param {SourceCode} sourceCode A source code to get tokens.
39
- * @returns {ASTNode|Token} The node or the token of a location.
40
- */
41
- function getLocation(node, sourceCode) {
42
- if (node.type === "ArrowFunctionExpression") {
43
- return sourceCode.getTokenBefore(node.body);
44
- }
45
- return node.id || node;
46
- }
47
-
48
32
  /**
49
33
  * Checks a given node is a MemberExpression node which has the specified name's
50
34
  * property.
@@ -179,6 +163,7 @@ module.exports = {
179
163
  create(context) {
180
164
 
181
165
  const options = context.options[0] || { allowImplicit: false, checkForEach: false };
166
+ const sourceCode = context.getSourceCode();
182
167
 
183
168
  let funcInfo = {
184
169
  arrayMethodName: null,
@@ -217,12 +202,12 @@ module.exports = {
217
202
  }
218
203
 
219
204
  if (messageId) {
220
- let name = astUtils.getFunctionNameWithKind(funcInfo.node);
205
+ let name = astUtils.getFunctionNameWithKind(node);
221
206
 
222
207
  name = messageId === "expectedNoReturnValue" ? lodash.upperFirst(name) : name;
223
208
  context.report({
224
209
  node,
225
- loc: getLocation(node, context.getSourceCode()).loc.start,
210
+ loc: astUtils.getFunctionHeadLoc(node, sourceCode),
226
211
  messageId,
227
212
  data: { name }
228
213
  });
@@ -105,10 +105,27 @@ module.exports = {
105
105
  ], `${shouldAddSpaceForAsync ? " " : ""}${paramToken.value}`);
106
106
  }
107
107
 
108
+ /**
109
+ * Checks whether there are comments inside the params or not.
110
+ * @returns {boolean} `true` if there are comments inside of parens, else `false`
111
+ */
112
+ function hasCommentsInParens() {
113
+ if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
114
+ const closingParenToken = sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken);
115
+
116
+ return closingParenToken && sourceCode.commentsExistBetween(firstTokenOfParam, closingParenToken);
117
+ }
118
+ return false;
119
+
120
+ }
121
+
122
+ if (hasCommentsInParens()) {
123
+ return;
124
+ }
125
+
108
126
  // "as-needed", { "requireForBlockBody": true }: x => x
109
127
  if (
110
128
  requireForBlockBody &&
111
- node.params.length === 1 &&
112
129
  node.params[0].type === "Identifier" &&
113
130
  !node.params[0].typeAnnotation &&
114
131
  node.body.type !== "BlockStatement" &&
@@ -144,7 +161,6 @@ module.exports = {
144
161
 
145
162
  // "as-needed": x => x
146
163
  if (asNeeded &&
147
- node.params.length === 1 &&
148
164
  node.params[0].type === "Identifier" &&
149
165
  !node.params[0].typeAnnotation &&
150
166
  !node.returnType
@@ -178,7 +194,7 @@ module.exports = {
178
194
  }
179
195
 
180
196
  return {
181
- ArrowFunctionExpression: parens
197
+ "ArrowFunctionExpression[params.length=1]": parens
182
198
  };
183
199
  }
184
200
  };
@@ -102,9 +102,18 @@ module.exports = {
102
102
 
103
103
  // Check.
104
104
  if (!isValid(openBrace, firstToken)) {
105
+ let loc = openBrace.loc;
106
+
107
+ if (messageId === "extra") {
108
+ loc = {
109
+ start: openBrace.loc.end,
110
+ end: firstToken.loc.start
111
+ };
112
+ }
113
+
105
114
  context.report({
106
115
  node,
107
- loc: openBrace.loc.start,
116
+ loc,
108
117
  messageId,
109
118
  data: {
110
119
  location: "after",
@@ -120,9 +129,17 @@ module.exports = {
120
129
  });
121
130
  }
122
131
  if (!isValid(lastToken, closeBrace)) {
132
+ let loc = closeBrace.loc;
133
+
134
+ if (messageId === "extra") {
135
+ loc = {
136
+ start: lastToken.loc.end,
137
+ end: closeBrace.loc.start
138
+ };
139
+ }
123
140
  context.report({
124
141
  node,
125
- loc: closeBrace.loc.start,
142
+ loc,
126
143
  messageId,
127
144
  data: {
128
145
  location: "before",
@@ -10,6 +10,10 @@
10
10
 
11
11
  module.exports = {
12
12
  meta: {
13
+ deprecated: true,
14
+
15
+ replacedBy: [],
16
+
13
17
  type: "suggestion",
14
18
 
15
19
  docs: {
@@ -125,6 +125,40 @@ module.exports = {
125
125
  return false;
126
126
  }
127
127
 
128
+ /**
129
+ * Checks whether the given node represents assignment target property in destructuring.
130
+ *
131
+ * For examples:
132
+ * ({a: b.foo} = c); // => true for `foo`
133
+ * ([a.foo] = b); // => true for `foo`
134
+ * ([a.foo = 1] = b); // => true for `foo`
135
+ * ({...a.foo} = b); // => true for `foo`
136
+ * @param {ASTNode} node An Identifier node to check
137
+ * @returns {boolean} True if the node is an assignment target property in destructuring.
138
+ */
139
+ function isAssignmentTargetPropertyInDestructuring(node) {
140
+ if (
141
+ node.parent.type === "MemberExpression" &&
142
+ node.parent.property === node &&
143
+ !node.parent.computed
144
+ ) {
145
+ const effectiveParent = node.parent.parent;
146
+
147
+ return (
148
+ effectiveParent.type === "Property" &&
149
+ effectiveParent.value === node.parent &&
150
+ effectiveParent.parent.type === "ObjectPattern" ||
151
+ effectiveParent.type === "ArrayPattern" ||
152
+ effectiveParent.type === "RestElement" ||
153
+ (
154
+ effectiveParent.type === "AssignmentPattern" &&
155
+ effectiveParent.left === node.parent
156
+ )
157
+ );
158
+ }
159
+ return false;
160
+ }
161
+
128
162
  /**
129
163
  * Reports an AST node as a rule violation.
130
164
  * @param {ASTNode} node The node to report.
@@ -170,6 +204,9 @@ module.exports = {
170
204
  // Report AssignmentExpressions only if they are the left side of the assignment
171
205
  } else if (effectiveParent.type === "AssignmentExpression" && nameIsUnderscored && (effectiveParent.right.type !== "MemberExpression" || effectiveParent.left.type === "MemberExpression" && effectiveParent.left.property.name === node.name)) {
172
206
  report(node);
207
+
208
+ } else if (isAssignmentTargetPropertyInDestructuring(node) && nameIsUnderscored) {
209
+ report(node);
173
210
  }
174
211
 
175
212
  /*
@@ -186,7 +223,7 @@ module.exports = {
186
223
 
187
224
  const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name;
188
225
 
189
- if (isUnderscored(name) && node.parent.computed) {
226
+ if (nameIsUnderscored && node.parent.computed) {
190
227
  report(node);
191
228
  }
192
229
 
@@ -146,10 +146,7 @@ module.exports = {
146
146
  // lone comma
147
147
  context.report({
148
148
  node: reportItem,
149
- loc: {
150
- line: commaToken.loc.end.line,
151
- column: commaToken.loc.start.column
152
- },
149
+ loc: commaToken.loc,
153
150
  messageId: "unexpectedLineBeforeAndAfterComma",
154
151
  fix: getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken)
155
152
  });
@@ -158,6 +155,7 @@ module.exports = {
158
155
 
159
156
  context.report({
160
157
  node: reportItem,
158
+ loc: commaToken.loc,
161
159
  messageId: "expectedCommaFirst",
162
160
  fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken)
163
161
  });
@@ -166,10 +164,7 @@ module.exports = {
166
164
 
167
165
  context.report({
168
166
  node: reportItem,
169
- loc: {
170
- line: commaToken.loc.end.line,
171
- column: commaToken.loc.end.column
172
- },
167
+ loc: commaToken.loc,
173
168
  messageId: "expectedCommaLast",
174
169
  fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken)
175
170
  });
@@ -63,7 +63,8 @@ module.exports = {
63
63
  },
64
64
 
65
65
  messages: {
66
- unexpected: "Unexpected newline between function name and paren.",
66
+ unexpectedWhitespace: "Unexpected whitespace between function name and paren.",
67
+ unexpectedNewline: "Unexpected newline between function name and paren.",
67
68
  missing: "Missing space between function name and paren."
68
69
  }
69
70
  },
@@ -116,7 +117,7 @@ module.exports = {
116
117
  context.report({
117
118
  node,
118
119
  loc: leftToken.loc.start,
119
- messageId: "unexpected",
120
+ messageId: "unexpectedWhitespace",
120
121
  fix(fixer) {
121
122
 
122
123
  /*
@@ -143,7 +144,7 @@ module.exports = {
143
144
  context.report({
144
145
  node,
145
146
  loc: leftToken.loc.start,
146
- messageId: "unexpected",
147
+ messageId: "unexpectedNewline",
147
148
  fix(fixer) {
148
149
  return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
149
150
  }
@@ -25,17 +25,6 @@ function isReachable(segment) {
25
25
  return segment.reachable;
26
26
  }
27
27
 
28
- /**
29
- * Gets a readable location.
30
- *
31
- * - FunctionExpression -> the function name or `function` keyword.
32
- * @param {ASTNode} node A function node to get.
33
- * @returns {ASTNode|Token} The node or the token of a location.
34
- */
35
- function getId(node) {
36
- return node.id || node;
37
- }
38
-
39
28
  //------------------------------------------------------------------------------
40
29
  // Rule Definition
41
30
  //------------------------------------------------------------------------------
@@ -75,6 +64,7 @@ module.exports = {
75
64
  create(context) {
76
65
 
77
66
  const options = context.options[0] || { allowImplicit: false };
67
+ const sourceCode = context.getSourceCode();
78
68
 
79
69
  let funcInfo = {
80
70
  upper: null,
@@ -99,7 +89,7 @@ module.exports = {
99
89
  ) {
100
90
  context.report({
101
91
  node,
102
- loc: getId(node).loc.start,
92
+ loc: astUtils.getFunctionHeadLoc(node, sourceCode),
103
93
  messageId: funcInfo.hasReturn ? "expectedAlways" : "expected",
104
94
  data: {
105
95
  name: astUtils.getFunctionNameWithKind(funcInfo.node)
@@ -48,6 +48,10 @@ function isShadowed(scope, node) {
48
48
 
49
49
  module.exports = {
50
50
  meta: {
51
+ deprecated: true,
52
+
53
+ replacedBy: [],
54
+
51
55
  type: "suggestion",
52
56
 
53
57
  docs: {