eslint 6.1.0 → 6.3.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 (41) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +8 -4
  3. package/conf/config-schema.js +2 -0
  4. package/conf/default-cli-options.js +1 -1
  5. package/conf/environments.js +72 -15
  6. package/lib/cli-engine/config-array/config-array.js +13 -0
  7. package/lib/cli-engine/config-array/extracted-config.js +22 -1
  8. package/lib/cli-engine/config-array-factory.js +4 -0
  9. package/lib/cli-engine/formatters/stylish.js +2 -1
  10. package/lib/init/config-initializer.js +29 -0
  11. package/lib/init/npm-utils.js +10 -10
  12. package/lib/linter/apply-disable-directives.js +17 -9
  13. package/lib/linter/code-path-analysis/code-path-analyzer.js +1 -0
  14. package/lib/linter/linter.js +70 -17
  15. package/lib/options.js +1 -1
  16. package/lib/rules/accessor-pairs.js +195 -35
  17. package/lib/rules/class-methods-use-this.js +10 -3
  18. package/lib/rules/dot-notation.js +6 -2
  19. package/lib/rules/func-call-spacing.js +30 -20
  20. package/lib/rules/func-name-matching.js +1 -0
  21. package/lib/rules/func-names.js +4 -0
  22. package/lib/rules/function-call-argument-newline.js +120 -0
  23. package/lib/rules/function-paren-newline.js +36 -24
  24. package/lib/rules/indent.js +13 -2
  25. package/lib/rules/index.js +1 -0
  26. package/lib/rules/new-cap.js +2 -1
  27. package/lib/rules/no-dupe-keys.js +1 -1
  28. package/lib/rules/no-duplicate-case.js +10 -8
  29. package/lib/rules/no-extra-bind.js +1 -0
  30. package/lib/rules/no-extra-boolean-cast.js +54 -5
  31. package/lib/rules/no-extra-parens.js +62 -23
  32. package/lib/rules/no-mixed-operators.js +48 -13
  33. package/lib/rules/no-restricted-syntax.js +2 -2
  34. package/lib/rules/no-self-assign.js +11 -1
  35. package/lib/rules/no-unused-vars.js +1 -1
  36. package/lib/rules/prefer-template.js +1 -10
  37. package/lib/rules/sort-keys.js +11 -3
  38. package/lib/rules/utils/ast-utils.js +19 -2
  39. package/lib/rules/yoda.js +12 -3
  40. package/lib/shared/types.js +4 -0
  41. package/package.json +5 -5
@@ -54,6 +54,11 @@ const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, colum
54
54
  /** @typedef {import("../shared/types").Processor} Processor */
55
55
  /** @typedef {import("../shared/types").Rule} Rule */
56
56
 
57
+ /**
58
+ * @template T
59
+ * @typedef {{ [P in keyof T]-?: T[P] }} Required
60
+ */
61
+
57
62
  /**
58
63
  * @typedef {Object} DisableDirective
59
64
  * @property {("disable"|"enable"|"disable-line"|"disable-next-line")} type
@@ -79,7 +84,7 @@ const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, colum
79
84
  * @property {boolean} [disableFixes] if `true` then the linter doesn't make `fix`
80
85
  * properties into the lint result.
81
86
  * @property {string} [filename] the filename of the source code.
82
- * @property {boolean} [reportUnusedDisableDirectives] Adds reported errors for
87
+ * @property {boolean | "off" | "warn" | "error"} [reportUnusedDisableDirectives] Adds reported errors for
83
88
  * unused `eslint-disable` directives.
84
89
  */
85
90
 
@@ -103,6 +108,12 @@ const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, colum
103
108
  * whether fixes should be applied.
104
109
  */
105
110
 
111
+ /**
112
+ * @typedef {Object} InternalOptions
113
+ * @property {string | null} warnInlineConfig The config name what `noInlineConfig` setting came from. If `noInlineConfig` setting didn't exist, this is null. If this is a config name, then the linter warns directive comments.
114
+ * @property {"off" | "warn" | "error"} reportUnusedDisableDirectives (boolean values were normalized)
115
+ */
116
+
106
117
  //------------------------------------------------------------------------------
107
118
  // Helpers
108
119
  //------------------------------------------------------------------------------
@@ -198,14 +209,20 @@ function createMissingRuleMessage(ruleId) {
198
209
  /**
199
210
  * creates a linting problem
200
211
  * @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.
212
+ * @param {string} [options.ruleId] the ruleId to report
213
+ * @param {Object} [options.loc] the loc to report
214
+ * @param {string} [options.message] the error message to report
215
+ * @param {string} [options.severity] the error message to report
216
+ * @returns {LintMessage} created problem, returns a missing-rule problem if only provided ruleId.
205
217
  * @private
206
218
  */
207
219
  function createLintingProblem(options) {
208
- const { ruleId, loc = DEFAULT_ERROR_LOC, message = createMissingRuleMessage(options.ruleId) } = options;
220
+ const {
221
+ ruleId = null,
222
+ loc = DEFAULT_ERROR_LOC,
223
+ message = createMissingRuleMessage(options.ruleId),
224
+ severity = 2
225
+ } = options;
209
226
 
210
227
  return {
211
228
  ruleId,
@@ -214,7 +231,7 @@ function createLintingProblem(options) {
214
231
  column: loc.start.column + 1,
215
232
  endLine: loc.end.line,
216
233
  endColumn: loc.end.column + 1,
217
- severity: 2,
234
+ severity,
218
235
  nodeType: null
219
236
  };
220
237
  }
@@ -257,10 +274,11 @@ function createDisableDirectives(options) {
257
274
  * @param {string} filename The file being checked.
258
275
  * @param {ASTNode} ast The top node of the AST.
259
276
  * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
277
+ * @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
278
  * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}}
261
279
  * A collection of the directive comments that were found, along with any problems that occurred when parsing
262
280
  */
263
- function getDirectiveComments(filename, ast, ruleMapper) {
281
+ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
264
282
  const configuredRules = {};
265
283
  const enabledGlobals = Object.create(null);
266
284
  const exportedVariables = {};
@@ -269,16 +287,29 @@ function getDirectiveComments(filename, ast, ruleMapper) {
269
287
 
270
288
  ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
271
289
  const trimmedCommentText = comment.value.trim();
272
- const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/u.exec(trimmedCommentText);
290
+ const match = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u.exec(trimmedCommentText);
273
291
 
274
292
  if (!match) {
275
293
  return;
276
294
  }
295
+ const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(match[1]);
296
+
297
+ if (warnInlineConfig && (lineCommentSupported || comment.type === "Block")) {
298
+ const kind = comment.type === "Block" ? `/*${match[1]}*/` : `//${match[1]}`;
299
+
300
+ problems.push(createLintingProblem({
301
+ ruleId: null,
302
+ message: `'${kind}' has no effect because you have 'noInlineConfig' setting in ${warnInlineConfig}.`,
303
+ loc: comment.loc,
304
+ severity: 1
305
+ }));
306
+ return;
307
+ }
277
308
 
278
309
  const directiveValue = trimmedCommentText.slice(match.index + match[1].length);
279
310
  let directiveType = "";
280
311
 
281
- if (/^eslint-disable-(next-)?line$/u.test(match[1])) {
312
+ if (lineCommentSupported) {
282
313
  if (comment.loc.start.line === comment.loc.end.line) {
283
314
  directiveType = match[1].slice("eslint-".length);
284
315
  } else {
@@ -441,17 +472,37 @@ function normalizeFilename(filename) {
441
472
  return index === -1 ? filename : parts.slice(index).join(path.sep);
442
473
  }
443
474
 
475
+ // eslint-disable-next-line valid-jsdoc
444
476
  /**
445
477
  * Normalizes the possible options for `linter.verify` and `linter.verifyAndFix` to a
446
478
  * consistent shape.
447
479
  * @param {VerifyOptions} providedOptions Options
448
- * @returns {Required<VerifyOptions>} Normalized options
480
+ * @param {ConfigData} config Config.
481
+ * @returns {Required<VerifyOptions> & InternalOptions} Normalized options
449
482
  */
450
- function normalizeVerifyOptions(providedOptions) {
483
+ function normalizeVerifyOptions(providedOptions, config) {
484
+ const disableInlineConfig = config.noInlineConfig === true;
485
+ const ignoreInlineConfig = providedOptions.allowInlineConfig === false;
486
+ const configNameOfNoInlineConfig = config.configNameOfNoInlineConfig
487
+ ? ` (${config.configNameOfNoInlineConfig})`
488
+ : "";
489
+
490
+ let reportUnusedDisableDirectives = providedOptions.reportUnusedDisableDirectives;
491
+
492
+ if (typeof reportUnusedDisableDirectives === "boolean") {
493
+ reportUnusedDisableDirectives = reportUnusedDisableDirectives ? "error" : "off";
494
+ }
495
+ if (typeof reportUnusedDisableDirectives !== "string") {
496
+ reportUnusedDisableDirectives = config.reportUnusedDisableDirectives ? "warn" : "off";
497
+ }
498
+
451
499
  return {
452
500
  filename: normalizeFilename(providedOptions.filename || "<input>"),
453
- allowInlineConfig: providedOptions.allowInlineConfig !== false,
454
- reportUnusedDisableDirectives: Boolean(providedOptions.reportUnusedDisableDirectives),
501
+ allowInlineConfig: !ignoreInlineConfig,
502
+ warnInlineConfig: disableInlineConfig && !ignoreInlineConfig
503
+ ? `your config${configNameOfNoInlineConfig}`
504
+ : null,
505
+ reportUnusedDisableDirectives,
455
506
  disableFixes: Boolean(providedOptions.disableFixes)
456
507
  };
457
508
  }
@@ -984,7 +1035,7 @@ class Linter {
984
1035
  _verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
985
1036
  const slots = internalSlotsMap.get(this);
986
1037
  const config = providedConfig || {};
987
- const options = normalizeVerifyOptions(providedOptions);
1038
+ const options = normalizeVerifyOptions(providedOptions, config);
988
1039
  let text;
989
1040
 
990
1041
  // evaluate arguments
@@ -1019,7 +1070,9 @@ class Linter {
1019
1070
  }
1020
1071
 
1021
1072
  // search and apply "eslint-env *".
1022
- const envInFile = findEslintEnv(text);
1073
+ const envInFile = options.allowInlineConfig && !options.warnInlineConfig
1074
+ ? findEslintEnv(text)
1075
+ : {};
1023
1076
  const resolvedEnvConfig = Object.assign({ builtin: true }, config.env, envInFile);
1024
1077
  const enabledEnvs = Object.keys(resolvedEnvConfig)
1025
1078
  .filter(envName => resolvedEnvConfig[envName])
@@ -1062,7 +1115,7 @@ class Linter {
1062
1115
 
1063
1116
  const sourceCode = slots.lastSourceCode;
1064
1117
  const commentDirectives = options.allowInlineConfig
1065
- ? getDirectiveComments(options.filename, sourceCode.ast, ruleId => getRule(slots, ruleId))
1118
+ ? getDirectiveComments(options.filename, sourceCode.ast, ruleId => getRule(slots, ruleId), options.warnInlineConfig)
1066
1119
  : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
1067
1120
 
1068
1121
  // augment global scope with declared global variables
package/lib/options.js CHANGED
@@ -192,7 +192,7 @@ module.exports = optionator({
192
192
  {
193
193
  option: "report-unused-disable-directives",
194
194
  type: "Boolean",
195
- default: false,
195
+ default: void 0,
196
196
  description: "Adds reported errors for unused eslint-disable directives"
197
197
  },
198
198
  {
@@ -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
  };
@@ -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
  }
@@ -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
  }
@@ -118,6 +118,7 @@ module.exports = {
118
118
  return false;
119
119
  }
120
120
  return node.type === "CallExpression" &&
121
+ node.callee.type === "MemberExpression" &&
121
122
  node.callee.object.name === objName &&
122
123
  node.callee.property.name === funcName;
123
124
  }
@@ -69,6 +69,8 @@ module.exports = {
69
69
 
70
70
  create(context) {
71
71
 
72
+ const sourceCode = context.getSourceCode();
73
+
72
74
  /**
73
75
  * Returns the config option for the given node.
74
76
  * @param {ASTNode} node - A node to get the config for.
@@ -130,6 +132,7 @@ module.exports = {
130
132
  context.report({
131
133
  node,
132
134
  messageId: "unnamed",
135
+ loc: astUtils.getFunctionHeadLoc(node, sourceCode),
133
136
  data: { name: astUtils.getFunctionNameWithKind(node) }
134
137
  });
135
138
  }
@@ -143,6 +146,7 @@ module.exports = {
143
146
  context.report({
144
147
  node,
145
148
  messageId: "named",
149
+ loc: astUtils.getFunctionHeadLoc(node, sourceCode),
146
150
  data: { name: astUtils.getFunctionNameWithKind(node) }
147
151
  });
148
152
  }