eslint 6.0.0 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +3 -5
  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 +7 -0
  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 +1 -1
  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/new-cap.js +2 -1
  29. package/lib/rules/no-dupe-keys.js +1 -1
  30. package/lib/rules/no-duplicate-case.js +10 -8
  31. package/lib/rules/no-extra-bind.js +1 -0
  32. package/lib/rules/no-extra-boolean-cast.js +44 -5
  33. package/lib/rules/no-extra-parens.js +295 -39
  34. package/lib/rules/no-mixed-operators.js +48 -13
  35. package/lib/rules/no-param-reassign.js +12 -1
  36. package/lib/rules/no-restricted-syntax.js +2 -2
  37. package/lib/rules/no-unused-vars.js +1 -1
  38. package/lib/rules/prefer-const.js +9 -3
  39. package/lib/rules/prefer-template.js +1 -10
  40. package/lib/rules/sort-keys.js +11 -3
  41. package/lib/rules/utils/ast-utils.js +45 -3
  42. package/lib/rules/yoda.js +1 -1
  43. package/lib/{cli-engine → shared}/naming.js +0 -0
  44. package/lib/shared/types.js +2 -0
  45. package/messages/extend-config-missing.txt +2 -0
  46. package/messages/print-config-with-directory-path.txt +2 -0
  47. package/package.json +22 -21
@@ -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
@@ -549,8 +549,8 @@ class RuleTester {
549
549
  assert(false, `Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`);
550
550
  }
551
551
  assert.strictEqual(
552
- error.messageId,
553
552
  message.messageId,
553
+ error.messageId,
554
554
  `messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.`
555
555
  );
556
556
  if (hasOwnProperty(error, "data")) {
@@ -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
  }
@@ -78,21 +78,13 @@ module.exports = {
78
78
  /**
79
79
  * Check if open space is present in a function name
80
80
  * @param {ASTNode} node node to evaluate
81
+ * @param {Token} leftToken The last token of the callee. This may be the closing parenthesis that encloses the callee.
82
+ * @param {Token} rightToken Tha first token of the arguments. this is the opening parenthesis that encloses the arguments.
81
83
  * @returns {void}
82
84
  * @private
83
85
  */
84
- function checkSpacing(node) {
85
- const lastToken = sourceCode.getLastToken(node);
86
- const lastCalleeToken = sourceCode.getLastToken(node.callee);
87
- const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken);
88
- const prevToken = parenToken && sourceCode.getTokenBefore(parenToken);
89
-
90
- // Parens in NewExpression are optional
91
- if (!(parenToken && parenToken.range[1] < node.range[1])) {
92
- return;
93
- }
94
-
95
- const textBetweenTokens = text.slice(prevToken.range[1], parenToken.range[0]).replace(/\/\*.*?\*\//gu, "");
86
+ function checkSpacing(node, leftToken, rightToken) {
87
+ const textBetweenTokens = text.slice(leftToken.range[1], rightToken.range[0]).replace(/\/\*.*?\*\//gu, "");
96
88
  const hasWhitespace = /\s/u.test(textBetweenTokens);
97
89
  const hasNewline = hasWhitespace && astUtils.LINEBREAK_MATCHER.test(textBetweenTokens);
98
90
 
@@ -123,7 +115,7 @@ module.exports = {
123
115
  if (never && hasWhitespace) {
124
116
  context.report({
125
117
  node,
126
- loc: lastCalleeToken.loc.start,
118
+ loc: leftToken.loc.start,
127
119
  messageId: "unexpected",
128
120
  fix(fixer) {
129
121
 
@@ -132,7 +124,7 @@ module.exports = {
132
124
  * https://github.com/eslint/eslint/issues/7787
133
125
  */
134
126
  if (!hasNewline) {
135
- return fixer.removeRange([prevToken.range[1], parenToken.range[0]]);
127
+ return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
136
128
  }
137
129
 
138
130
  return null;
@@ -141,27 +133,45 @@ module.exports = {
141
133
  } else if (!never && !hasWhitespace) {
142
134
  context.report({
143
135
  node,
144
- loc: lastCalleeToken.loc.start,
136
+ loc: leftToken.loc.start,
145
137
  messageId: "missing",
146
138
  fix(fixer) {
147
- return fixer.insertTextBefore(parenToken, " ");
139
+ return fixer.insertTextBefore(rightToken, " ");
148
140
  }
149
141
  });
150
142
  } else if (!never && !allowNewlines && hasNewline) {
151
143
  context.report({
152
144
  node,
153
- loc: lastCalleeToken.loc.start,
145
+ loc: leftToken.loc.start,
154
146
  messageId: "unexpected",
155
147
  fix(fixer) {
156
- return fixer.replaceTextRange([prevToken.range[1], parenToken.range[0]], " ");
148
+ return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
157
149
  }
158
150
  });
159
151
  }
160
152
  }
161
153
 
162
154
  return {
163
- CallExpression: checkSpacing,
164
- NewExpression: checkSpacing
155
+ "CallExpression, NewExpression"(node) {
156
+ const lastToken = sourceCode.getLastToken(node);
157
+ const lastCalleeToken = sourceCode.getLastToken(node.callee);
158
+ const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken);
159
+ const prevToken = parenToken && sourceCode.getTokenBefore(parenToken);
160
+
161
+ // Parens in NewExpression are optional
162
+ if (!(parenToken && parenToken.range[1] < node.range[1])) {
163
+ return;
164
+ }
165
+
166
+ checkSpacing(node, prevToken, parenToken);
167
+ },
168
+
169
+ ImportExpression(node) {
170
+ const leftToken = sourceCode.getFirstToken(node);
171
+ const rightToken = sourceCode.getTokenAfter(leftToken);
172
+
173
+ checkSpacing(node, leftToken, rightToken);
174
+ }
165
175
  };
166
176
 
167
177
  }