eslint 6.0.0-rc.0 → 6.2.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 (52) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +4 -13
  3. package/bin/eslint.js +4 -1
  4. package/conf/config-schema.js +1 -0
  5. package/conf/environments.js +72 -15
  6. package/lib/cli-engine/cascading-config-array-factory.js +15 -3
  7. package/lib/cli-engine/cli-engine.js +13 -3
  8. package/lib/cli-engine/config-array/config-array.js +8 -2
  9. package/lib/cli-engine/config-array/extracted-config.js +16 -1
  10. package/lib/cli-engine/config-array-factory.js +9 -6
  11. package/lib/cli-engine/file-enumerator.js +5 -13
  12. package/lib/init/config-initializer.js +19 -9
  13. package/lib/init/npm-utils.js +2 -2
  14. package/lib/linter/code-path-analysis/code-path-analyzer.js +1 -0
  15. package/lib/linter/linter.js +49 -16
  16. package/lib/rule-tester/rule-tester.js +15 -3
  17. package/lib/rules/accessor-pairs.js +195 -35
  18. package/lib/rules/arrow-body-style.js +2 -2
  19. package/lib/rules/class-methods-use-this.js +10 -3
  20. package/lib/rules/dot-location.js +21 -17
  21. package/lib/rules/dot-notation.js +6 -2
  22. package/lib/rules/func-call-spacing.js +30 -20
  23. package/lib/rules/func-names.js +4 -0
  24. package/lib/rules/function-call-argument-newline.js +120 -0
  25. package/lib/rules/function-paren-newline.js +34 -22
  26. package/lib/rules/indent.js +13 -2
  27. package/lib/rules/index.js +1 -0
  28. package/lib/rules/max-len.js +7 -0
  29. package/lib/rules/multiline-comment-style.js +2 -1
  30. package/lib/rules/new-cap.js +2 -1
  31. package/lib/rules/no-dupe-keys.js +1 -1
  32. package/lib/rules/no-duplicate-case.js +10 -8
  33. package/lib/rules/no-else-return.js +127 -0
  34. package/lib/rules/no-extra-bind.js +1 -0
  35. package/lib/rules/no-extra-boolean-cast.js +44 -5
  36. package/lib/rules/no-extra-parens.js +295 -39
  37. package/lib/rules/no-mixed-operators.js +48 -13
  38. package/lib/rules/no-param-reassign.js +12 -1
  39. package/lib/rules/no-restricted-syntax.js +2 -2
  40. package/lib/rules/no-unused-vars.js +1 -1
  41. package/lib/rules/no-var.js +14 -1
  42. package/lib/rules/prefer-const.js +9 -3
  43. package/lib/rules/prefer-template.js +1 -10
  44. package/lib/rules/require-atomic-updates.js +63 -84
  45. package/lib/rules/sort-keys.js +11 -3
  46. package/lib/rules/utils/ast-utils.js +45 -3
  47. package/lib/rules/yoda.js +1 -1
  48. package/lib/{cli-engine → shared}/naming.js +0 -0
  49. package/lib/shared/types.js +2 -0
  50. package/messages/extend-config-missing.txt +2 -0
  51. package/messages/print-config-with-directory-path.txt +2 -0
  52. package/package.json +27 -30
@@ -526,6 +526,7 @@ function processCodePathToExit(analyzer, node) {
526
526
  break;
527
527
 
528
528
  case "CallExpression":
529
+ case "ImportExpression":
529
530
  case "MemberExpression":
530
531
  case "NewExpression":
531
532
  state.makeFirstThrowablePathInTryBlock();
@@ -198,14 +198,20 @@ function createMissingRuleMessage(ruleId) {
198
198
  /**
199
199
  * creates a linting problem
200
200
  * @param {Object} options to create linting error
201
- * @param {string} options.ruleId the ruleId to report
202
- * @param {Object} options.loc the loc to report
203
- * @param {string} options.message the error message to report
204
- * @returns {Problem} created problem, returns a missing-rule problem if only provided ruleId.
201
+ * @param {string} [options.ruleId] the ruleId to report
202
+ * @param {Object} [options.loc] the loc to report
203
+ * @param {string} [options.message] the error message to report
204
+ * @param {string} [options.severity] the error message to report
205
+ * @returns {LintMessage} created problem, returns a missing-rule problem if only provided ruleId.
205
206
  * @private
206
207
  */
207
208
  function createLintingProblem(options) {
208
- const { ruleId, loc = DEFAULT_ERROR_LOC, message = createMissingRuleMessage(options.ruleId) } = options;
209
+ const {
210
+ ruleId = null,
211
+ loc = DEFAULT_ERROR_LOC,
212
+ message = createMissingRuleMessage(options.ruleId),
213
+ severity = 2
214
+ } = options;
209
215
 
210
216
  return {
211
217
  ruleId,
@@ -214,7 +220,7 @@ function createLintingProblem(options) {
214
220
  column: loc.start.column + 1,
215
221
  endLine: loc.end.line,
216
222
  endColumn: loc.end.column + 1,
217
- severity: 2,
223
+ severity,
218
224
  nodeType: null
219
225
  };
220
226
  }
@@ -257,28 +263,42 @@ function createDisableDirectives(options) {
257
263
  * @param {string} filename The file being checked.
258
264
  * @param {ASTNode} ast The top node of the AST.
259
265
  * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
266
+ * @param {string|null} warnInlineConfig If a string then it should warn directive comments as disabled. The string value is the config name what the setting came from.
260
267
  * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}}
261
268
  * A collection of the directive comments that were found, along with any problems that occurred when parsing
262
269
  */
263
- function getDirectiveComments(filename, ast, ruleMapper) {
270
+ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
264
271
  const configuredRules = {};
265
- const enabledGlobals = {};
272
+ const enabledGlobals = Object.create(null);
266
273
  const exportedVariables = {};
267
274
  const problems = [];
268
275
  const disableDirectives = [];
269
276
 
270
277
  ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
271
278
  const trimmedCommentText = comment.value.trim();
272
- const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/u.exec(trimmedCommentText);
279
+ const match = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u.exec(trimmedCommentText);
273
280
 
274
281
  if (!match) {
275
282
  return;
276
283
  }
284
+ const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(match[1]);
285
+
286
+ if (warnInlineConfig && (lineCommentSupported || comment.type === "Block")) {
287
+ const kind = comment.type === "Block" ? `/*${match[1]}*/` : `//${match[1]}`;
288
+
289
+ problems.push(createLintingProblem({
290
+ ruleId: null,
291
+ message: `'${kind}' has no effect because you have 'noInlineConfig' setting in ${warnInlineConfig}.`,
292
+ loc: comment.loc,
293
+ severity: 1
294
+ }));
295
+ return;
296
+ }
277
297
 
278
298
  const directiveValue = trimmedCommentText.slice(match.index + match[1].length);
279
299
  let directiveType = "";
280
300
 
281
- if (/^eslint-disable-(next-)?line$/u.test(match[1])) {
301
+ if (lineCommentSupported) {
282
302
  if (comment.loc.start.line === comment.loc.end.line) {
283
303
  directiveType = match[1].slice("eslint-".length);
284
304
  } else {
@@ -441,16 +461,27 @@ function normalizeFilename(filename) {
441
461
  return index === -1 ? filename : parts.slice(index).join(path.sep);
442
462
  }
443
463
 
464
+ // eslint-disable-next-line valid-jsdoc
444
465
  /**
445
466
  * Normalizes the possible options for `linter.verify` and `linter.verifyAndFix` to a
446
467
  * consistent shape.
447
468
  * @param {VerifyOptions} providedOptions Options
448
- * @returns {Required<VerifyOptions>} Normalized options
469
+ * @param {ConfigData} config Config.
470
+ * @returns {Required<VerifyOptions> & { warnInlineConfig: string|null }} Normalized options
449
471
  */
450
- function normalizeVerifyOptions(providedOptions) {
472
+ function normalizeVerifyOptions(providedOptions, config) {
473
+ const disableInlineConfig = config.noInlineConfig === true;
474
+ const ignoreInlineConfig = providedOptions.allowInlineConfig === false;
475
+ const configNameOfNoInlineConfig = config.configNameOfNoInlineConfig
476
+ ? ` (${config.configNameOfNoInlineConfig})`
477
+ : "";
478
+
451
479
  return {
452
480
  filename: normalizeFilename(providedOptions.filename || "<input>"),
453
- allowInlineConfig: providedOptions.allowInlineConfig !== false,
481
+ allowInlineConfig: !ignoreInlineConfig,
482
+ warnInlineConfig: disableInlineConfig && !ignoreInlineConfig
483
+ ? `your config${configNameOfNoInlineConfig}`
484
+ : null,
454
485
  reportUnusedDisableDirectives: Boolean(providedOptions.reportUnusedDisableDirectives),
455
486
  disableFixes: Boolean(providedOptions.disableFixes)
456
487
  };
@@ -984,7 +1015,7 @@ class Linter {
984
1015
  _verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
985
1016
  const slots = internalSlotsMap.get(this);
986
1017
  const config = providedConfig || {};
987
- const options = normalizeVerifyOptions(providedOptions);
1018
+ const options = normalizeVerifyOptions(providedOptions, config);
988
1019
  let text;
989
1020
 
990
1021
  // evaluate arguments
@@ -1019,7 +1050,9 @@ class Linter {
1019
1050
  }
1020
1051
 
1021
1052
  // search and apply "eslint-env *".
1022
- const envInFile = findEslintEnv(text);
1053
+ const envInFile = options.allowInlineConfig && !options.warnInlineConfig
1054
+ ? findEslintEnv(text)
1055
+ : {};
1023
1056
  const resolvedEnvConfig = Object.assign({ builtin: true }, config.env, envInFile);
1024
1057
  const enabledEnvs = Object.keys(resolvedEnvConfig)
1025
1058
  .filter(envName => resolvedEnvConfig[envName])
@@ -1062,7 +1095,7 @@ class Linter {
1062
1095
 
1063
1096
  const sourceCode = slots.lastSourceCode;
1064
1097
  const commentDirectives = options.allowInlineConfig
1065
- ? getDirectiveComments(options.filename, sourceCode.ast, ruleId => getRule(slots, ruleId))
1098
+ ? getDirectiveComments(options.filename, sourceCode.ast, ruleId => getRule(slots, ruleId), options.warnInlineConfig)
1066
1099
  : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
1067
1100
 
1068
1101
  // augment global scope with declared global variables
@@ -123,6 +123,18 @@ function freezeDeeply(x) {
123
123
  }
124
124
  }
125
125
 
126
+ /**
127
+ * Replace control characters by `\u00xx` form.
128
+ * @param {string} text The text to sanitize.
129
+ * @returns {string} The sanitized text.
130
+ */
131
+ function sanitize(text) {
132
+ return text.replace(
133
+ /[\u0000-\u001f]/gu, // eslint-disable-line no-control-regex
134
+ c => `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}`
135
+ );
136
+ }
137
+
126
138
  //------------------------------------------------------------------------------
127
139
  // Public Interface
128
140
  //------------------------------------------------------------------------------
@@ -537,8 +549,8 @@ class RuleTester {
537
549
  assert(false, `Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`);
538
550
  }
539
551
  assert.strictEqual(
540
- error.messageId,
541
552
  message.messageId,
553
+ error.messageId,
542
554
  `messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.`
543
555
  );
544
556
  if (hasOwnProperty(error, "data")) {
@@ -613,7 +625,7 @@ class RuleTester {
613
625
  RuleTester.describe(ruleName, () => {
614
626
  RuleTester.describe("valid", () => {
615
627
  test.valid.forEach(valid => {
616
- RuleTester.it(typeof valid === "object" ? valid.code : valid, () => {
628
+ RuleTester.it(sanitize(typeof valid === "object" ? valid.code : valid), () => {
617
629
  testValidTemplate(valid);
618
630
  });
619
631
  });
@@ -621,7 +633,7 @@ class RuleTester {
621
633
 
622
634
  RuleTester.describe("invalid", () => {
623
635
  test.invalid.forEach(invalid => {
624
- RuleTester.it(invalid.code, () => {
636
+ RuleTester.it(sanitize(invalid.code), () => {
625
637
  testInvalidTemplate(invalid);
626
638
  });
627
639
  });
@@ -5,10 +5,87 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const astUtils = require("./utils/ast-utils");
13
+
14
+ //------------------------------------------------------------------------------
15
+ // Typedefs
16
+ //------------------------------------------------------------------------------
17
+
18
+ /**
19
+ * Property name if it can be computed statically, otherwise the list of the tokens of the key node.
20
+ * @typedef {string|Token[]} Key
21
+ */
22
+
23
+ /**
24
+ * Accessor nodes with the same key.
25
+ * @typedef {Object} AccessorData
26
+ * @property {Key} key Accessor's key
27
+ * @property {ASTNode[]} getters List of getter nodes.
28
+ * @property {ASTNode[]} setters List of setter nodes.
29
+ */
30
+
8
31
  //------------------------------------------------------------------------------
9
32
  // Helpers
10
33
  //------------------------------------------------------------------------------
11
34
 
35
+ /**
36
+ * Checks whether or not the given lists represent the equal tokens in the same order.
37
+ * Tokens are compared by their properties, not by instance.
38
+ * @param {Token[]} left First list of tokens.
39
+ * @param {Token[]} right Second list of tokens.
40
+ * @returns {boolean} `true` if the lists have same tokens.
41
+ */
42
+ function areEqualTokenLists(left, right) {
43
+ if (left.length !== right.length) {
44
+ return false;
45
+ }
46
+
47
+ for (let i = 0; i < left.length; i++) {
48
+ const leftToken = left[i],
49
+ rightToken = right[i];
50
+
51
+ if (leftToken.type !== rightToken.type || leftToken.value !== rightToken.value) {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ return true;
57
+ }
58
+
59
+ /**
60
+ * Checks whether or not the given keys are equal.
61
+ * @param {Key} left First key.
62
+ * @param {Key} right Second key.
63
+ * @returns {boolean} `true` if the keys are equal.
64
+ */
65
+ function areEqualKeys(left, right) {
66
+ if (typeof left === "string" && typeof right === "string") {
67
+
68
+ // Statically computed names.
69
+ return left === right;
70
+ }
71
+ if (Array.isArray(left) && Array.isArray(right)) {
72
+
73
+ // Token lists.
74
+ return areEqualTokenLists(left, right);
75
+ }
76
+
77
+ return false;
78
+ }
79
+
80
+ /**
81
+ * Checks whether or not a given node is of an accessor kind ('get' or 'set').
82
+ * @param {ASTNode} node - A node to check.
83
+ * @returns {boolean} `true` if the node is of an accessor kind.
84
+ */
85
+ function isAccessorKind(node) {
86
+ return node.kind === "get" || node.kind === "set";
87
+ }
88
+
12
89
  /**
13
90
  * Checks whether or not a given node is an `Identifier` node which was named a given name.
14
91
  * @param {ASTNode} node - A node to check.
@@ -97,69 +174,152 @@ module.exports = {
97
174
  }],
98
175
 
99
176
  messages: {
100
- getter: "Getter is not present.",
101
- setter: "Setter is not present."
177
+ missingGetterInPropertyDescriptor: "Getter is not present in property descriptor.",
178
+ missingSetterInPropertyDescriptor: "Setter is not present in property descriptor.",
179
+ missingGetterInObjectLiteral: "Getter is not present for {{ name }}.",
180
+ missingSetterInObjectLiteral: "Setter is not present for {{ name }}."
102
181
  }
103
182
  },
104
183
  create(context) {
105
184
  const config = context.options[0] || {};
106
185
  const checkGetWithoutSet = config.getWithoutSet === true;
107
186
  const checkSetWithoutGet = config.setWithoutGet !== false;
187
+ const sourceCode = context.getSourceCode();
108
188
 
109
189
  /**
110
- * Checks a object expression to see if it has setter and getter both present or none.
111
- * @param {ASTNode} node The node to check.
190
+ * Reports the given node.
191
+ * @param {ASTNode} node The node to report.
192
+ * @param {string} messageKind "missingGetter" or "missingSetter".
112
193
  * @returns {void}
113
194
  * @private
114
195
  */
115
- function checkLonelySetGet(node) {
116
- let isSetPresent = false;
117
- let isGetPresent = false;
118
- const isDescriptor = isPropertyDescriptor(node);
196
+ function report(node, messageKind) {
197
+ if (node.type === "Property") {
198
+ context.report({
199
+ node,
200
+ messageId: `${messageKind}InObjectLiteral`,
201
+ loc: astUtils.getFunctionHeadLoc(node.value, sourceCode),
202
+ data: { name: astUtils.getFunctionNameWithKind(node.value) }
203
+ });
204
+ } else {
205
+ context.report({
206
+ node,
207
+ messageId: `${messageKind}InPropertyDescriptor`
208
+ });
209
+ }
210
+ }
119
211
 
120
- for (let i = 0, end = node.properties.length; i < end; i++) {
121
- const property = node.properties[i];
212
+ /**
213
+ * Reports each of the nodes in the given list using the same messageId.
214
+ * @param {ASTNode[]} nodes Nodes to report.
215
+ * @param {string} messageKind "missingGetter" or "missingSetter".
216
+ * @returns {void}
217
+ * @private
218
+ */
219
+ function reportList(nodes, messageKind) {
220
+ for (const node of nodes) {
221
+ report(node, messageKind);
222
+ }
223
+ }
122
224
 
123
- let propToCheck = "";
225
+ /**
226
+ * Creates a new `AccessorData` object for the given getter or setter node.
227
+ * @param {ASTNode} node A getter or setter node.
228
+ * @returns {AccessorData} New `AccessorData` object that contains the given node.
229
+ * @private
230
+ */
231
+ function createAccessorData(node) {
232
+ const name = astUtils.getStaticPropertyName(node);
233
+ const key = (name !== null) ? name : sourceCode.getTokens(node.key);
124
234
 
125
- if (property.kind === "init") {
126
- if (isDescriptor && !property.computed) {
127
- propToCheck = property.key.name;
128
- }
129
- } else {
130
- propToCheck = property.kind;
131
- }
235
+ return {
236
+ key,
237
+ getters: node.kind === "get" ? [node] : [],
238
+ setters: node.kind === "set" ? [node] : []
239
+ };
240
+ }
132
241
 
133
- switch (propToCheck) {
134
- case "set":
135
- isSetPresent = true;
136
- break;
242
+ /**
243
+ * Merges the given `AccessorData` object into the given accessors list.
244
+ * @param {AccessorData[]} accessors The list to merge into.
245
+ * @param {AccessorData} accessorData The object to merge.
246
+ * @returns {AccessorData[]} The same instance with the merged object.
247
+ * @private
248
+ */
249
+ function mergeAccessorData(accessors, accessorData) {
250
+ const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key));
137
251
 
138
- case "get":
139
- isGetPresent = true;
140
- break;
252
+ if (equalKeyElement) {
253
+ equalKeyElement.getters.push(...accessorData.getters);
254
+ equalKeyElement.setters.push(...accessorData.setters);
255
+ } else {
256
+ accessors.push(accessorData);
257
+ }
141
258
 
142
- default:
259
+ return accessors;
260
+ }
143
261
 
144
- // Do nothing
145
- }
262
+ /**
263
+ * Checks accessor pairs in the given list of nodes.
264
+ * @param {ASTNode[]} nodes The list to check.
265
+ * @returns {void}
266
+ * @private
267
+ */
268
+ function checkList(nodes) {
269
+ const accessors = nodes
270
+ .filter(isAccessorKind)
271
+ .map(createAccessorData)
272
+ .reduce(mergeAccessorData, []);
146
273
 
147
- if (isSetPresent && isGetPresent) {
148
- break;
274
+ for (const { getters, setters } of accessors) {
275
+ if (checkSetWithoutGet && setters.length && !getters.length) {
276
+ reportList(setters, "missingGetter");
277
+ }
278
+ if (checkGetWithoutSet && getters.length && !setters.length) {
279
+ reportList(getters, "missingSetter");
149
280
  }
150
281
  }
282
+ }
151
283
 
152
- if (checkSetWithoutGet && isSetPresent && !isGetPresent) {
153
- context.report({ node, messageId: "getter" });
154
- } else if (checkGetWithoutSet && isGetPresent && !isSetPresent) {
155
- context.report({ node, messageId: "setter" });
284
+ /**
285
+ * Checks accessor pairs in an object literal.
286
+ * @param {ASTNode} node `ObjectExpression` node to check.
287
+ * @returns {void}
288
+ * @private
289
+ */
290
+ function checkObjectLiteral(node) {
291
+ checkList(node.properties.filter(p => p.type === "Property"));
292
+ }
293
+
294
+ /**
295
+ * Checks accessor pairs in a property descriptor.
296
+ * @param {ASTNode} node Property descriptor `ObjectExpression` node to check.
297
+ * @returns {void}
298
+ * @private
299
+ */
300
+ function checkPropertyDescriptor(node) {
301
+ const namesToCheck = node.properties
302
+ .filter(p => p.type === "Property" && p.kind === "init" && !p.computed)
303
+ .map(({ key }) => key.name);
304
+
305
+ const hasGetter = namesToCheck.includes("get");
306
+ const hasSetter = namesToCheck.includes("set");
307
+
308
+ if (checkSetWithoutGet && hasSetter && !hasGetter) {
309
+ report(node, "missingGetter");
310
+ }
311
+ if (checkGetWithoutSet && hasGetter && !hasSetter) {
312
+ report(node, "missingSetter");
156
313
  }
157
314
  }
158
315
 
159
316
  return {
160
317
  ObjectExpression(node) {
161
318
  if (checkSetWithoutGet || checkGetWithoutSet) {
162
- checkLonelySetGet(node);
319
+ checkObjectLiteral(node);
320
+ if (isPropertyDescriptor(node)) {
321
+ checkPropertyDescriptor(node);
322
+ }
163
323
  }
164
324
  }
165
325
  };
@@ -175,10 +175,10 @@ module.exports = {
175
175
  }
176
176
 
177
177
  /*
178
- * If the first token of the reutrn value is `{`,
178
+ * If the first token of the reutrn value is `{` or the return value is a sequence expression,
179
179
  * enclose the return value by parentheses to avoid syntax error.
180
180
  */
181
- if (astUtils.isOpeningBraceToken(firstValueToken)) {
181
+ if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression") {
182
182
  fixes.push(
183
183
  fixer.insertTextBefore(firstValueToken, "("),
184
184
  fixer.insertTextAfter(lastValueToken, ")")
@@ -5,6 +5,12 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const astUtils = require("./utils/ast-utils");
13
+
8
14
  //------------------------------------------------------------------------------
9
15
  // Rule Definition
10
16
  //------------------------------------------------------------------------------
@@ -34,7 +40,7 @@ module.exports = {
34
40
  }],
35
41
 
36
42
  messages: {
37
- missingThis: "Expected 'this' to be used by class method '{{name}}'."
43
+ missingThis: "Expected 'this' to be used by class {{name}}."
38
44
  }
39
45
  },
40
46
  create(context) {
@@ -70,7 +76,8 @@ module.exports = {
70
76
  * @private
71
77
  */
72
78
  function isIncludedInstanceMethod(node) {
73
- return isInstanceMethod(node) && !exceptMethods.has(node.key.name);
79
+ return isInstanceMethod(node) &&
80
+ (node.computed || !exceptMethods.has(node.key.name));
74
81
  }
75
82
 
76
83
  /**
@@ -89,7 +96,7 @@ module.exports = {
89
96
  node,
90
97
  messageId: "missingThis",
91
98
  data: {
92
- name: node.parent.key.name
99
+ name: astUtils.getFunctionNameWithKind(node)
93
100
  }
94
101
  });
95
102
  }
@@ -54,29 +54,31 @@ module.exports = {
54
54
  */
55
55
  function checkDotLocation(obj, prop, node) {
56
56
  const dot = sourceCode.getTokenBefore(prop);
57
- const textBeforeDot = sourceCode.getText().slice(obj.range[1], dot.range[0]);
57
+
58
+ // `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node.
59
+ const tokenBeforeDot = sourceCode.getTokenBefore(dot);
60
+
61
+ const textBeforeDot = sourceCode.getText().slice(tokenBeforeDot.range[1], dot.range[0]);
58
62
  const textAfterDot = sourceCode.getText().slice(dot.range[1], prop.range[0]);
59
63
 
60
- if (dot.type === "Punctuator" && dot.value === ".") {
61
- if (onObject) {
62
- if (!astUtils.isTokenOnSameLine(obj, dot)) {
63
- const neededTextAfterObj = astUtils.isDecimalInteger(obj) ? " " : "";
64
-
65
- context.report({
66
- node,
67
- loc: dot.loc.start,
68
- messageId: "expectedDotAfterObject",
69
- fix: fixer => fixer.replaceTextRange([obj.range[1], prop.range[0]], `${neededTextAfterObj}.${textBeforeDot}${textAfterDot}`)
70
- });
71
- }
72
- } else if (!astUtils.isTokenOnSameLine(dot, prop)) {
64
+ if (onObject) {
65
+ if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dot)) {
66
+ const neededTextAfterToken = astUtils.isDecimalIntegerNumericToken(tokenBeforeDot) ? " " : "";
67
+
73
68
  context.report({
74
69
  node,
75
70
  loc: dot.loc.start,
76
- messageId: "expectedDotBeforeProperty",
77
- fix: fixer => fixer.replaceTextRange([obj.range[1], prop.range[0]], `${textBeforeDot}${textAfterDot}.`)
71
+ messageId: "expectedDotAfterObject",
72
+ fix: fixer => fixer.replaceTextRange([tokenBeforeDot.range[1], prop.range[0]], `${neededTextAfterToken}.${textBeforeDot}${textAfterDot}`)
78
73
  });
79
74
  }
75
+ } else if (!astUtils.isTokenOnSameLine(dot, prop)) {
76
+ context.report({
77
+ node,
78
+ loc: dot.loc.start,
79
+ messageId: "expectedDotBeforeProperty",
80
+ fix: fixer => fixer.replaceTextRange([tokenBeforeDot.range[1], prop.range[0]], `${textBeforeDot}${textAfterDot}.`)
81
+ });
80
82
  }
81
83
  }
82
84
 
@@ -86,7 +88,9 @@ module.exports = {
86
88
  * @returns {void}
87
89
  */
88
90
  function checkNode(node) {
89
- checkDotLocation(node.object, node.property, node);
91
+ if (!node.computed) {
92
+ checkDotLocation(node.object, node.property, node);
93
+ }
90
94
  }
91
95
 
92
96
  return {
@@ -9,13 +9,16 @@
9
9
  //------------------------------------------------------------------------------
10
10
 
11
11
  const astUtils = require("./utils/ast-utils");
12
+ const keywords = require("./utils/keywords");
12
13
 
13
14
  //------------------------------------------------------------------------------
14
15
  // Rule Definition
15
16
  //------------------------------------------------------------------------------
16
17
 
17
18
  const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/u;
18
- const keywords = require("./utils/keywords");
19
+
20
+ // `null` literal must be handled separately.
21
+ const literalTypesToCheck = new Set(["string", "boolean"]);
19
22
 
20
23
  module.exports = {
21
24
  meta: {
@@ -115,7 +118,8 @@ module.exports = {
115
118
  MemberExpression(node) {
116
119
  if (
117
120
  node.computed &&
118
- node.property.type === "Literal"
121
+ node.property.type === "Literal" &&
122
+ (literalTypesToCheck.has(typeof node.property.value) || astUtils.isNullLiteral(node.property))
119
123
  ) {
120
124
  checkComputedProperty(node, node.property.value);
121
125
  }