eslint 8.4.0 → 8.7.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.
package/README.md CHANGED
@@ -54,7 +54,7 @@ $ npm install eslint --save-dev
54
54
  You should then set up a configuration file:
55
55
 
56
56
  ```sh
57
- $ ./node_modules/.bin/eslint --init
57
+ $ npm init @eslint/config
58
58
  ```
59
59
 
60
60
  After that, you can run ESLint on any file or directory like this:
@@ -65,7 +65,7 @@ $ ./node_modules/.bin/eslint yourfile.js
65
65
 
66
66
  ## <a name="configuration"></a>Configuration
67
67
 
68
- After running `eslint --init`, you'll have a `.eslintrc` file in your directory. In it, you'll see some rules configured like this:
68
+ After running `npm init @eslint/config`, you'll have a `.eslintrc` file in your directory. In it, you'll see some rules configured like this:
69
69
 
70
70
  ```json
71
71
  {
@@ -294,7 +294,7 @@ The following companies, organizations, and individuals support ESLint's ongoing
294
294
  <p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
295
295
  <p><a href="https://contra.com"><img src="https://images.opencollective.com/contra1/c70f93f/logo.png" alt="Contra" height="96"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="96"></a> <a href="https://google.com/chrome"><img src="https://images.opencollective.com/chrome/dc55bd4/logo.png" alt="Chrome's Web Framework & Tools Performance Fund" height="96"></a> <a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://coinbase.com"><img src="https://avatars.githubusercontent.com/u/1885080?v=4" alt="Coinbase" height="96"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="96"></a> <a href="https://substack.com/"><img src="https://avatars.githubusercontent.com/u/53023767?v=4" alt="Substack" height="96"></a></p><h3>Silver Sponsors</h3>
296
296
  <p><a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a></p><h3>Bronze Sponsors</h3>
297
- <p><a href="https://sumatosoft.com/"><img src="https://images.opencollective.com/sumatosoft1/cab6013/logo.png" alt="SumatoSoft" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a> <a href="https://www.practiceignition.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Practice Ignition" height="32"></a></p>
297
+ <p><a href="https://launchdarkly.com"><img src="https://images.opencollective.com/launchdarkly/574bb9e/logo.png" alt="launchdarkly" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.practiceignition.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Practice Ignition" height="32"></a></p>
298
298
  <!--sponsorsend-->
299
299
 
300
300
  ## <a name="technology-sponsors"></a>Technology Sponsors
package/bin/eslint.js CHANGED
@@ -124,7 +124,13 @@ ${message}`);
124
124
 
125
125
  // Call the config initializer if `--init` is present.
126
126
  if (process.argv.includes("--init")) {
127
- await require("../lib/init/config-initializer").initializeConfig();
127
+
128
+ // `eslint --init` has been moved to `@eslint/create-config`
129
+ console.warn("You can also run this command directly using 'npm init @eslint/config'.");
130
+
131
+ const spawn = require("cross-spawn");
132
+
133
+ spawn.sync("npm", ["init", "@eslint/config"], { encoding: "utf8", stdio: "inherit" });
128
134
  return;
129
135
  }
130
136
 
@@ -92,6 +92,7 @@ const validFixTypes = new Set(["directive", "problem", "suggestion", "layout"]);
92
92
  * @property {string} filePath The path to the file that was linted.
93
93
  * @property {LintMessage[]} messages All of the messages for the result.
94
94
  * @property {number} errorCount Number of errors for the result.
95
+ * @property {number} fatalErrorCount Number of fatal errors for the result.
95
96
  * @property {number} warningCount Number of warnings for the result.
96
97
  * @property {number} fixableErrorCount Number of fixable errors for the result.
97
98
  * @property {number} fixableWarningCount Number of fixable warnings for the result.
@@ -104,6 +105,7 @@ const validFixTypes = new Set(["directive", "problem", "suggestion", "layout"]);
104
105
  * @typedef {Object} LintReport
105
106
  * @property {LintResult[]} results All of the result.
106
107
  * @property {number} errorCount Number of errors for the result.
108
+ * @property {number} fatalErrorCount Number of fatal errors for the result.
107
109
  * @property {number} warningCount Number of warnings for the result.
108
110
  * @property {number} fixableErrorCount Number of fixable errors for the result.
109
111
  * @property {number} fixableWarningCount Number of fixable warnings for the result.
@@ -308,6 +310,7 @@ function createIgnoreResult(filePath, baseDir) {
308
310
  }
309
311
  ],
310
312
  errorCount: 0,
313
+ fatalErrorCount: 0,
311
314
  warningCount: 1,
312
315
  fixableErrorCount: 0,
313
316
  fixableWarningCount: 0
@@ -408,7 +411,7 @@ function isErrorMessage(message) {
408
411
  * a directory or looks like a directory (ends in `path.sep`), in which case the file
409
412
  * name will be the `cacheFile/.cache_hashOfCWD`
410
413
  *
411
- * if cacheFile points to a file or looks like a file then in will just use that file
414
+ * if cacheFile points to a file or looks like a file then it will just use that file
412
415
  * @param {string} cacheFile The name of file to be used to store the cache
413
416
  * @param {string} cwd Current working directory
414
417
  * @returns {string} the resolved path to the cache file
@@ -79,6 +79,7 @@ const { version } = require("../../package.json");
79
79
  * @property {string} filePath The path to the file that was linted.
80
80
  * @property {LintMessage[]} messages All of the messages for the result.
81
81
  * @property {number} errorCount Number of errors for the result.
82
+ * @property {number} fatalErrorCount Number of fatal errors for the result.
82
83
  * @property {number} warningCount Number of warnings for the result.
83
84
  * @property {number} fixableErrorCount Number of fixable errors for the result.
84
85
  * @property {number} fixableWarningCount Number of fixable warnings for the result.
@@ -43,7 +43,7 @@ function groupByParentComment(directives) {
43
43
  * Creates removal details for a set of directives within the same comment.
44
44
  * @param {Directive[]} directives Unused directives to be removed.
45
45
  * @param {Token} commentToken The backing Comment token.
46
- * @returns {{ description, fix, position }[]} Details for later creation of output Problems.
46
+ * @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems.
47
47
  */
48
48
  function createIndividualDirectivesRemoval(directives, commentToken) {
49
49
 
@@ -138,7 +138,7 @@ function createIndividualDirectivesRemoval(directives, commentToken) {
138
138
  ],
139
139
  text: ""
140
140
  },
141
- position: directive.unprocessedDirective
141
+ unprocessedDirective: directive.unprocessedDirective
142
142
  };
143
143
  });
144
144
  }
@@ -147,7 +147,7 @@ function createIndividualDirectivesRemoval(directives, commentToken) {
147
147
  * Creates a description of deleting an entire unused disable comment.
148
148
  * @param {Directive[]} directives Unused directives to be removed.
149
149
  * @param {Token} commentToken The backing Comment token.
150
- * @returns {{ description, fix, position }} Details for later creation of an output Problem.
150
+ * @returns {{ description, fix, unprocessedDirective }} Details for later creation of an output Problem.
151
151
  */
152
152
  function createCommentRemoval(directives, commentToken) {
153
153
  const { range } = commentToken;
@@ -161,14 +161,14 @@ function createCommentRemoval(directives, commentToken) {
161
161
  range,
162
162
  text: " "
163
163
  },
164
- position: directives[0].unprocessedDirective
164
+ unprocessedDirective: directives[0].unprocessedDirective
165
165
  };
166
166
  }
167
167
 
168
168
  /**
169
169
  * Parses details from directives to create output Problems.
170
170
  * @param {Directive[]} allDirectives Unused directives to be removed.
171
- * @returns {{ description, fix, position }[]} Details for later creation of output Problems.
171
+ * @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems.
172
172
  */
173
173
  function processUnusedDisableDirectives(allDirectives) {
174
174
  const directiveGroups = groupByParentComment(allDirectives);
@@ -261,17 +261,21 @@ function applyDirectives(options) {
261
261
  const processed = processUnusedDisableDirectives(unusedDisableDirectivesToReport);
262
262
 
263
263
  const unusedDisableDirectives = processed
264
- .map(({ description, fix, position }) => ({
265
- ruleId: null,
266
- message: description
267
- ? `Unused eslint-disable directive (no problems were reported from ${description}).`
268
- : "Unused eslint-disable directive (no problems were reported).",
269
- line: position.line,
270
- column: position.column,
271
- severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2,
272
- nodeType: null,
273
- ...options.disableFixes ? {} : { fix }
274
- }));
264
+ .map(({ description, fix, unprocessedDirective }) => {
265
+ const { parentComment, type, line, column } = unprocessedDirective;
266
+
267
+ return {
268
+ ruleId: null,
269
+ message: description
270
+ ? `Unused eslint-disable directive (no problems were reported from ${description}).`
271
+ : "Unused eslint-disable directive (no problems were reported).",
272
+ line: type === "disable-next-line" ? parentComment.commentToken.loc.start.line : line,
273
+ column: type === "disable-next-line" ? parentComment.commentToken.loc.start.column + 1 : column,
274
+ severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2,
275
+ nodeType: null,
276
+ ...options.disableFixes ? {} : { fix }
277
+ };
278
+ });
275
279
 
276
280
  return { problems, unusedDisableDirectives };
277
281
  }
@@ -305,7 +305,11 @@ function createDisableDirectives(options) {
305
305
 
306
306
  // push to directives, if the rule is defined(including null, e.g. /*eslint enable*/)
307
307
  if (ruleId === null || !!ruleMapper(ruleId)) {
308
- result.directives.push({ parentComment, type, line: commentToken.loc.start.line, column: commentToken.loc.start.column + 1, ruleId });
308
+ if (type === "disable-next-line") {
309
+ result.directives.push({ parentComment, type, line: commentToken.loc.end.line, column: commentToken.loc.end.column + 1, ruleId });
310
+ } else {
311
+ result.directives.push({ parentComment, type, line: commentToken.loc.start.line, column: commentToken.loc.start.column + 1, ruleId });
312
+ }
309
313
  } else {
310
314
  result.directiveProblems.push(createLintingProblem({ ruleId, loc: commentToken.loc }));
311
315
  }
@@ -326,14 +330,13 @@ function stripDirectiveComment(value) {
326
330
  * Parses comments in file to extract file-specific config of rules, globals
327
331
  * and environments and merges them with global config; also code blocks
328
332
  * where reporting is disabled or enabled and merges them with reporting config.
329
- * @param {string} filename The file being checked.
330
333
  * @param {ASTNode} ast The top node of the AST.
331
334
  * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
332
335
  * @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.
333
336
  * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}}
334
337
  * A collection of the directive comments that were found, along with any problems that occurred when parsing
335
338
  */
336
- function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
339
+ function getDirectiveComments(ast, ruleMapper, warnInlineConfig) {
337
340
  const configuredRules = {};
338
341
  const enabledGlobals = Object.create(null);
339
342
  const exportedVariables = {};
@@ -369,7 +372,7 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
369
372
  return;
370
373
  }
371
374
 
372
- if (lineCommentSupported && comment.loc.start.line !== comment.loc.end.line) {
375
+ if (directiveText === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) {
373
376
  const message = `${directiveText} comment should not span multiple lines.`;
374
377
 
375
378
  problems.push(createLintingProblem({
@@ -1332,7 +1335,7 @@ class Linter {
1332
1335
 
1333
1336
  const sourceCode = slots.lastSourceCode;
1334
1337
  const commentDirectives = options.allowInlineConfig
1335
- ? getDirectiveComments(options.filename, sourceCode.ast, ruleId => getRule(slots, ruleId), options.warnInlineConfig)
1338
+ ? getDirectiveComments(sourceCode.ast, ruleId => getRule(slots, ruleId), options.warnInlineConfig)
1336
1339
  : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
1337
1340
 
1338
1341
  // augment global scope with declared global variables
@@ -1593,7 +1596,6 @@ class Linter {
1593
1596
  const sourceCode = slots.lastSourceCode;
1594
1597
  const commentDirectives = options.allowInlineConfig
1595
1598
  ? getDirectiveComments(
1596
- options.filename,
1597
1599
  sourceCode.ast,
1598
1600
  ruleId => getRuleFromConfig(ruleId, config),
1599
1601
  options.warnInlineConfig
@@ -216,6 +216,9 @@ function freezeDeeply(x) {
216
216
  * @returns {string} The sanitized text.
217
217
  */
218
218
  function sanitize(text) {
219
+ if (typeof text !== "string") {
220
+ return "";
221
+ }
219
222
  return text.replace(
220
223
  /[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex -- Escaping controls
221
224
  c => `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}`
@@ -691,6 +694,13 @@ class RuleTester {
691
694
  * @private
692
695
  */
693
696
  function testValidTemplate(item) {
697
+ const code = typeof item === "object" ? item.code : item;
698
+
699
+ assert.ok(typeof code === "string", "Test case must specify a string value for 'code'");
700
+ if (item.name) {
701
+ assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
702
+ }
703
+
694
704
  const result = runRuleForItem(item);
695
705
  const messages = result.messages;
696
706
 
@@ -731,6 +741,10 @@ class RuleTester {
731
741
  * @private
732
742
  */
733
743
  function testInvalidTemplate(item) {
744
+ assert.ok(typeof item.code === "string", "Test case must specify a string value for 'code'");
745
+ if (item.name) {
746
+ assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
747
+ }
734
748
  assert.ok(item.errors || item.errors === 0,
735
749
  `Did not specify errors for an invalid test of ${ruleName}`);
736
750
 
@@ -963,10 +977,10 @@ class RuleTester {
963
977
  * This creates a mocha test suite and pipes all supplied info through
964
978
  * one of the templates above.
965
979
  */
966
- RuleTester.describe(ruleName, () => {
967
- RuleTester.describe("valid", () => {
980
+ this.constructor.describe(ruleName, () => {
981
+ this.constructor.describe("valid", () => {
968
982
  test.valid.forEach(valid => {
969
- RuleTester[valid.only ? "itOnly" : "it"](
983
+ this.constructor[valid.only ? "itOnly" : "it"](
970
984
  sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
971
985
  () => {
972
986
  testValidTemplate(valid);
@@ -975,9 +989,9 @@ class RuleTester {
975
989
  });
976
990
  });
977
991
 
978
- RuleTester.describe("invalid", () => {
992
+ this.constructor.describe("invalid", () => {
979
993
  test.invalid.forEach(invalid => {
980
- RuleTester[invalid.only ? "itOnly" : "it"](
994
+ this.constructor[invalid.only ? "itOnly" : "it"](
981
995
  sanitize(invalid.name || invalid.code),
982
996
  () => {
983
997
  testInvalidTemplate(invalid);
@@ -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
  //------------------------------------------------------------------------------
@@ -165,7 +171,7 @@ module.exports = {
165
171
  case "ImportSpecifier":
166
172
  return (
167
173
  parent.local === node &&
168
- parent.imported.name === localName
174
+ astUtils.getModuleExportName(parent.imported) === localName
169
175
  );
170
176
 
171
177
  default:
@@ -67,6 +67,8 @@ module.exports = {
67
67
  onlyDeclarations = !!options.onlyDeclarations,
68
68
  ignoreDestructuring = !!options.ignoreDestructuring;
69
69
 
70
+ let globalScope;
71
+
70
72
  //--------------------------------------------------------------------------
71
73
  // Helpers
72
74
  //--------------------------------------------------------------------------
@@ -77,6 +79,19 @@ module.exports = {
77
79
  const DECLARATION_TYPES = new Set(["FunctionDeclaration", "VariableDeclarator"]);
78
80
  const IMPORT_TYPES = new Set(["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"]);
79
81
 
82
+ /**
83
+ * Checks whether the given node represents a reference to a global variable that is not declared in the source code.
84
+ * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
85
+ * @param {ASTNode} node `Identifier` node to check.
86
+ * @returns {boolean} `true` if the node is a reference to a global variable.
87
+ */
88
+ function isReferenceToGlobalVariable(node) {
89
+ const variable = globalScope.set.get(node.name);
90
+
91
+ return variable && variable.defs.length === 0 &&
92
+ variable.references.some(ref => ref.identifier === node);
93
+ }
94
+
80
95
  /**
81
96
  * Checks if a string matches the provided pattern
82
97
  * @param {string} name The string to check.
@@ -155,11 +170,19 @@ module.exports = {
155
170
 
156
171
  return {
157
172
 
173
+ Program() {
174
+ globalScope = context.getScope();
175
+ },
176
+
158
177
  Identifier(node) {
159
178
  const name = node.name,
160
179
  parent = node.parent,
161
180
  effectiveParent = (parent.type === "MemberExpression") ? parent.parent : parent;
162
181
 
182
+ if (isReferenceToGlobalVariable(node)) {
183
+ return;
184
+ }
185
+
163
186
  if (parent.type === "MemberExpression") {
164
187
 
165
188
  if (!checkProperties) {
@@ -188,6 +211,17 @@ module.exports = {
188
211
  }
189
212
  }
190
213
 
214
+ // For https://github.com/eslint/eslint/issues/15123
215
+ } else if (
216
+ parent.type === "Property" &&
217
+ parent.parent.type === "ObjectExpression" &&
218
+ parent.key === node &&
219
+ !parent.computed
220
+ ) {
221
+ if (checkProperties && isInvalid(name)) {
222
+ report(node);
223
+ }
224
+
191
225
  /*
192
226
  * Properties have their own rules, and
193
227
  * AssignmentPattern nodes can be treated like Properties:
@@ -216,7 +250,7 @@ module.exports = {
216
250
  }
217
251
 
218
252
  // never check properties or always ignore destructuring
219
- if (!checkProperties || (ignoreDestructuring && isInsideObjectPattern(node))) {
253
+ if ((!checkProperties && !parent.computed) || (ignoreDestructuring && isInsideObjectPattern(node))) {
220
254
  return;
221
255
  }
222
256
 
@@ -255,6 +255,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
255
255
  "prefer-exponentiation-operator": () => require("./prefer-exponentiation-operator"),
256
256
  "prefer-named-capture-group": () => require("./prefer-named-capture-group"),
257
257
  "prefer-numeric-literals": () => require("./prefer-numeric-literals"),
258
+ "prefer-object-has-own": () => require("./prefer-object-has-own"),
258
259
  "prefer-object-spread": () => require("./prefer-object-spread"),
259
260
  "prefer-promise-reject-errors": () => require("./prefer-promise-reject-errors"),
260
261
  "prefer-reflect": () => require("./prefer-reflect"),
@@ -469,6 +469,7 @@ module.exports = {
469
469
  const asToken = sourceCode.getTokenBefore(node.exported);
470
470
 
471
471
  checkSpacingBefore(asToken, PREV_TOKEN_M);
472
+ checkSpacingAfter(asToken, NEXT_TOKEN_M);
472
473
  }
473
474
 
474
475
  if (node.source) {
@@ -479,6 +480,35 @@ module.exports = {
479
480
  }
480
481
  }
481
482
 
483
+ /**
484
+ * Reports `as` keyword of a given node if usage of spacing around this
485
+ * keyword is invalid.
486
+ * @param {ASTNode} node An `ImportSpecifier` node to check.
487
+ * @returns {void}
488
+ */
489
+ function checkSpacingForImportSpecifier(node) {
490
+ if (node.imported.range[0] !== node.local.range[0]) {
491
+ const asToken = sourceCode.getTokenBefore(node.local);
492
+
493
+ checkSpacingBefore(asToken, PREV_TOKEN_M);
494
+ }
495
+ }
496
+
497
+ /**
498
+ * Reports `as` keyword of a given node if usage of spacing around this
499
+ * keyword is invalid.
500
+ * @param {ASTNode} node An `ExportSpecifier` node to check.
501
+ * @returns {void}
502
+ */
503
+ function checkSpacingForExportSpecifier(node) {
504
+ if (node.local.range[0] !== node.exported.range[0]) {
505
+ const asToken = sourceCode.getTokenBefore(node.exported);
506
+
507
+ checkSpacingBefore(asToken, PREV_TOKEN_M);
508
+ checkSpacingAfter(asToken, NEXT_TOKEN_M);
509
+ }
510
+ }
511
+
482
512
  /**
483
513
  * Reports `as` keyword of a given node if usage of spacing around this
484
514
  * keyword is invalid.
@@ -588,6 +618,8 @@ module.exports = {
588
618
  YieldExpression: checkSpacingBeforeFirstToken,
589
619
 
590
620
  // Others
621
+ ImportSpecifier: checkSpacingForImportSpecifier,
622
+ ExportSpecifier: checkSpacingForExportSpecifier,
591
623
  ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier,
592
624
  MethodDefinition: checkSpacingForProperty,
593
625
  PropertyDefinition: checkSpacingForProperty,
@@ -80,7 +80,7 @@ module.exports = {
80
80
  OPTIONS_OR_INTEGER_SCHEMA
81
81
  ],
82
82
  messages: {
83
- exceed: "{{name}} has exceeded the limit of lines allowed by {{linesExceed}}. Maximum allowed number of lines per function is {{maxLines}}."
83
+ exceed: "{{name}} has too many lines ({{lineCount}}). Maximum allowed is {{maxLines}}."
84
84
  }
85
85
  },
86
86
 
@@ -170,26 +170,18 @@ module.exports = {
170
170
  return;
171
171
  }
172
172
  let lineCount = 0;
173
- let comments = 0;
174
- let blankLines = 0;
175
173
 
176
174
  for (let i = node.loc.start.line - 1; i < node.loc.end.line; ++i) {
177
175
  const line = lines[i];
178
176
 
179
177
  if (skipComments) {
180
178
  if (commentLineNumbers.has(i + 1) && isFullLineComment(line, i + 1, commentLineNumbers.get(i + 1))) {
181
- if (lineCount <= maxLines) {
182
- comments++;
183
- }
184
179
  continue;
185
180
  }
186
181
  }
187
182
 
188
183
  if (skipBlankLines) {
189
184
  if (line.match(/^\s*$/u)) {
190
- if (lineCount <= maxLines) {
191
- blankLines++;
192
- }
193
185
  continue;
194
186
  }
195
187
  }
@@ -199,21 +191,11 @@ module.exports = {
199
191
 
200
192
  if (lineCount > maxLines) {
201
193
  const name = upperCaseFirst(astUtils.getFunctionNameWithKind(funcNode));
202
- const linesExceed = lineCount - maxLines;
203
-
204
- const loc = {
205
- start: {
206
- line: node.loc.start.line + maxLines + (comments + blankLines),
207
- column: 0
208
- },
209
- end: node.loc.end
210
- };
211
194
 
212
195
  context.report({
213
196
  node,
214
- loc,
215
197
  messageId: "exceed",
216
- data: { name, linesExceed, maxLines }
198
+ data: { name, lineCount, maxLines }
217
199
  });
218
200
  }
219
201
  }
@@ -124,7 +124,8 @@ module.exports = {
124
124
  * Checks if a node has a constant truthiness value.
125
125
  * @param {ASTNode} node The AST node to check.
126
126
  * @param {boolean} inBooleanPosition `false` if checking branch of a condition.
127
- * `true` in all other cases
127
+ * `true` in all other cases. When `false`, checks if -- for both string and
128
+ * number -- if coerced to that type, the value will be constant.
128
129
  * @returns {Bool} true when node's truthiness is constant
129
130
  * @private
130
131
  */
@@ -138,15 +139,31 @@ module.exports = {
138
139
  case "Literal":
139
140
  case "ArrowFunctionExpression":
140
141
  case "FunctionExpression":
141
- case "ObjectExpression":
142
+ return true;
142
143
  case "ClassExpression":
144
+ case "ObjectExpression":
145
+
146
+ /**
147
+ * In theory objects like:
148
+ *
149
+ * `{toString: () => a}`
150
+ * `{valueOf: () => a}`
151
+ *
152
+ * Or a classes like:
153
+ *
154
+ * `class { static toString() { return a } }`
155
+ * `class { static valueOf() { return a } }`
156
+ *
157
+ * Are not constant verifiably when `inBooleanPosition` is
158
+ * false, but it's an edge case we've opted not to handle.
159
+ */
143
160
  return true;
144
161
  case "TemplateLiteral":
145
162
  return (inBooleanPosition && node.quasis.some(quasi => quasi.value.cooked.length)) ||
146
- node.expressions.every(exp => isConstant(exp, inBooleanPosition));
163
+ node.expressions.every(exp => isConstant(exp, false));
147
164
 
148
165
  case "ArrayExpression": {
149
- if (node.parent.type === "BinaryExpression" && node.parent.operator === "+") {
166
+ if (!inBooleanPosition) {
150
167
  return node.elements.every(element => isConstant(element, false));
151
168
  }
152
169
  return true;
@@ -196,6 +213,8 @@ module.exports = {
196
213
 
197
214
  case "SequenceExpression":
198
215
  return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition);
216
+ case "SpreadElement":
217
+ return isConstant(node.argument, inBooleanPosition);
199
218
 
200
219
  // no default
201
220
  }