eslint 7.26.0 → 7.30.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 (67) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +12 -7
  3. package/bin/eslint.js +2 -12
  4. package/lib/cli-engine/file-enumerator.js +1 -1
  5. package/lib/cli-engine/formatters/html.js +193 -9
  6. package/lib/config/default-config.js +52 -0
  7. package/lib/config/flat-config-array.js +125 -0
  8. package/lib/config/flat-config-schema.js +452 -0
  9. package/lib/config/rule-validator.js +169 -0
  10. package/lib/eslint/eslint.js +38 -2
  11. package/lib/init/autoconfig.js +2 -2
  12. package/lib/init/npm-utils.js +1 -2
  13. package/lib/linter/apply-disable-directives.js +15 -3
  14. package/lib/linter/linter.js +31 -21
  15. package/lib/linter/node-event-generator.js +43 -6
  16. package/lib/rule-tester/rule-tester.js +89 -23
  17. package/lib/rules/arrow-body-style.js +21 -11
  18. package/lib/rules/comma-dangle.js +16 -7
  19. package/lib/rules/comma-spacing.js +1 -1
  20. package/lib/rules/comma-style.js +1 -2
  21. package/lib/rules/complexity.js +2 -3
  22. package/lib/rules/consistent-return.js +2 -2
  23. package/lib/rules/dot-notation.js +3 -3
  24. package/lib/rules/eol-last.js +2 -7
  25. package/lib/rules/indent.js +10 -13
  26. package/lib/rules/max-lines-per-function.js +2 -3
  27. package/lib/rules/max-lines.js +32 -7
  28. package/lib/rules/max-params.js +2 -3
  29. package/lib/rules/max-statements.js +2 -3
  30. package/lib/rules/no-duplicate-imports.js +214 -66
  31. package/lib/rules/no-fallthrough.js +18 -13
  32. package/lib/rules/no-implicit-coercion.js +21 -2
  33. package/lib/rules/no-restricted-imports.js +61 -24
  34. package/lib/rules/no-unused-vars.js +40 -10
  35. package/lib/rules/no-useless-backreference.js +1 -2
  36. package/lib/rules/no-useless-computed-key.js +8 -2
  37. package/lib/rules/no-warning-comments.js +1 -1
  38. package/lib/rules/object-curly-newline.js +19 -4
  39. package/lib/rules/prefer-arrow-callback.js +4 -4
  40. package/lib/rules/spaced-comment.js +2 -2
  41. package/lib/rules/use-isnan.js +4 -1
  42. package/lib/rules/utils/ast-utils.js +2 -2
  43. package/lib/shared/deprecation-warnings.js +12 -3
  44. package/lib/shared/string-utils.js +22 -0
  45. package/lib/source-code/source-code.js +8 -7
  46. package/lib/source-code/token-store/utils.js +4 -12
  47. package/messages/{all-files-ignored.txt → all-files-ignored.js} +10 -2
  48. package/messages/extend-config-missing.js +13 -0
  49. package/messages/failed-to-read-json.js +11 -0
  50. package/messages/file-not-found.js +10 -0
  51. package/messages/{no-config-found.txt → no-config-found.js} +9 -1
  52. package/messages/plugin-conflict.js +22 -0
  53. package/messages/plugin-invalid.js +16 -0
  54. package/messages/plugin-missing.js +19 -0
  55. package/messages/{print-config-with-directory-path.txt → print-config-with-directory-path.js} +6 -0
  56. package/messages/whitespace-found.js +11 -0
  57. package/package.json +9 -7
  58. package/lib/cli-engine/formatters/html-template-message.html +0 -8
  59. package/lib/cli-engine/formatters/html-template-page.html +0 -115
  60. package/lib/cli-engine/formatters/html-template-result.html +0 -6
  61. package/messages/extend-config-missing.txt +0 -5
  62. package/messages/failed-to-read-json.txt +0 -3
  63. package/messages/file-not-found.txt +0 -2
  64. package/messages/plugin-conflict.txt +0 -7
  65. package/messages/plugin-invalid.txt +0 -8
  66. package/messages/plugin-missing.txt +0 -11
  67. package/messages/whitespace-found.txt +0 -3
@@ -4,12 +4,6 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
- //------------------------------------------------------------------------------
8
- // Requirements
9
- //------------------------------------------------------------------------------
10
-
11
- const lodash = require("lodash");
12
-
13
7
  //------------------------------------------------------------------------------
14
8
  // Helpers
15
9
  //------------------------------------------------------------------------------
@@ -17,15 +11,26 @@ const lodash = require("lodash");
17
11
  const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
18
12
 
19
13
  /**
20
- * Checks whether or not a given node has a fallthrough comment.
21
- * @param {ASTNode} node A SwitchCase node to get comments.
14
+ * Checks whether or not a given case has a fallthrough comment.
15
+ * @param {ASTNode} caseWhichFallsThrough SwitchCase node which falls through.
16
+ * @param {ASTNode} subsequentCase The case after caseWhichFallsThrough.
22
17
  * @param {RuleContext} context A rule context which stores comments.
23
18
  * @param {RegExp} fallthroughCommentPattern A pattern to match comment to.
24
- * @returns {boolean} `true` if the node has a valid fallthrough comment.
19
+ * @returns {boolean} `true` if the case has a valid fallthrough comment.
25
20
  */
26
- function hasFallthroughComment(node, context, fallthroughCommentPattern) {
21
+ function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) {
27
22
  const sourceCode = context.getSourceCode();
28
- const comment = lodash.last(sourceCode.getCommentsBefore(node));
23
+
24
+ if (caseWhichFallsThrough.consequent.length === 1 && caseWhichFallsThrough.consequent[0].type === "BlockStatement") {
25
+ const trailingCloseBrace = sourceCode.getLastToken(caseWhichFallsThrough.consequent[0]);
26
+ const commentInBlock = sourceCode.getCommentsBefore(trailingCloseBrace).pop();
27
+
28
+ if (commentInBlock && fallthroughCommentPattern.test(commentInBlock.value)) {
29
+ return true;
30
+ }
31
+ }
32
+
33
+ const comment = sourceCode.getCommentsBefore(subsequentCase).pop();
29
34
 
30
35
  return Boolean(comment && fallthroughCommentPattern.test(comment.value));
31
36
  }
@@ -114,7 +119,7 @@ module.exports = {
114
119
  * Checks whether or not there is a fallthrough comment.
115
120
  * And reports the previous fallthrough node if that does not exist.
116
121
  */
117
- if (fallthroughCase && !hasFallthroughComment(node, context, fallthroughCommentPattern)) {
122
+ if (fallthroughCase && !hasFallthroughComment(fallthroughCase, node, context, fallthroughCommentPattern)) {
118
123
  context.report({
119
124
  messageId: node.test ? "case" : "default",
120
125
  node
@@ -133,7 +138,7 @@ module.exports = {
133
138
  */
134
139
  if (currentCodePath.currentSegments.some(isReachable) &&
135
140
  (node.consequent.length > 0 || hasBlankLinesBetween(node, nextToken)) &&
136
- lodash.last(node.parent.cases) !== node) {
141
+ node.parent.cases[node.parent.cases.length - 1] !== node) {
137
142
  fallthroughCase = node;
138
143
  }
139
144
  }
@@ -109,6 +109,20 @@ function getNonNumericOperand(node) {
109
109
  return null;
110
110
  }
111
111
 
112
+ /**
113
+ * Checks whether an expression evaluates to a string.
114
+ * @param {ASTNode} node node that represents the expression to check.
115
+ * @returns {boolean} Whether or not the expression evaluates to a string.
116
+ */
117
+ function isStringType(node) {
118
+ return astUtils.isStringLiteral(node) ||
119
+ (
120
+ node.type === "CallExpression" &&
121
+ node.callee.type === "Identifier" &&
122
+ node.callee.name === "String"
123
+ );
124
+ }
125
+
112
126
  /**
113
127
  * Checks whether a node is an empty string literal or not.
114
128
  * @param {ASTNode} node The node to check.
@@ -126,8 +140,8 @@ function isEmptyString(node) {
126
140
  */
127
141
  function isConcatWithEmptyString(node) {
128
142
  return node.operator === "+" && (
129
- (isEmptyString(node.left) && !astUtils.isStringLiteral(node.right)) ||
130
- (isEmptyString(node.right) && !astUtils.isStringLiteral(node.left))
143
+ (isEmptyString(node.left) && !isStringType(node.right)) ||
144
+ (isEmptyString(node.right) && !isStringType(node.left))
131
145
  );
132
146
  }
133
147
 
@@ -332,6 +346,11 @@ module.exports = {
332
346
  return;
333
347
  }
334
348
 
349
+ // if the expression is already a string, then this isn't a coercion
350
+ if (isStringType(node.expressions[0])) {
351
+ return;
352
+ }
353
+
335
354
  const code = sourceCode.getText(node.expressions[0]);
336
355
  const recommendation = `String(${code})`;
337
356
 
@@ -10,12 +10,6 @@
10
10
 
11
11
  const ignore = require("ignore");
12
12
 
13
- const arrayOfStrings = {
14
- type: "array",
15
- items: { type: "string" },
16
- uniqueItems: true
17
- };
18
-
19
13
  const arrayOfStringsOrObjects = {
20
14
  type: "array",
21
15
  items: {
@@ -44,6 +38,41 @@ const arrayOfStringsOrObjects = {
44
38
  uniqueItems: true
45
39
  };
46
40
 
41
+ const arrayOfStringsOrObjectPatterns = {
42
+ anyOf: [
43
+ {
44
+ type: "array",
45
+ items: {
46
+ type: "string"
47
+ },
48
+ uniqueItems: true
49
+ },
50
+ {
51
+ type: "array",
52
+ items: {
53
+ type: "object",
54
+ properties: {
55
+ group: {
56
+ type: "array",
57
+ items: {
58
+ type: "string"
59
+ },
60
+ minItems: 1,
61
+ uniqueItems: true
62
+ },
63
+ message: {
64
+ type: "string",
65
+ minLength: 1
66
+ }
67
+ },
68
+ additionalProperties: false,
69
+ required: ["group"]
70
+ },
71
+ uniqueItems: true
72
+ }
73
+ ]
74
+ };
75
+
47
76
  module.exports = {
48
77
  meta: {
49
78
  type: "suggestion",
@@ -61,6 +90,8 @@ module.exports = {
61
90
  pathWithCustomMessage: "'{{importSource}}' import is restricted from being used. {{customMessage}}",
62
91
 
63
92
  patterns: "'{{importSource}}' import is restricted from being used by a pattern.",
93
+ // eslint-disable-next-line eslint-plugin/report-message-format
94
+ patternWithCustomMessage: "'{{importSource}}' import is restricted from being used by a pattern. {{customMessage}}",
64
95
 
65
96
  everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.",
66
97
  // eslint-disable-next-line eslint-plugin/report-message-format
@@ -80,7 +111,7 @@ module.exports = {
80
111
  type: "object",
81
112
  properties: {
82
113
  paths: arrayOfStringsOrObjects,
83
- patterns: arrayOfStrings
114
+ patterns: arrayOfStringsOrObjectPatterns
84
115
  },
85
116
  additionalProperties: false
86
117
  }],
@@ -98,13 +129,6 @@ module.exports = {
98
129
  (Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns"));
99
130
 
100
131
  const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
101
- const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
102
-
103
- // if no imports are restricted we don"t need to check
104
- if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
105
- return {};
106
- }
107
-
108
132
  const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {
109
133
  if (typeof importSource === "string") {
110
134
  memo[importSource] = { message: null };
@@ -117,7 +141,16 @@ module.exports = {
117
141
  return memo;
118
142
  }, {});
119
143
 
120
- const restrictedPatternsMatcher = ignore().add(restrictedPatterns);
144
+ // Handle patterns too, either as strings or groups
145
+ const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
146
+ const restrictedPatternGroups = restrictedPatterns.length > 0 && typeof restrictedPatterns[0] === "string"
147
+ ? [{ matcher: ignore().add(restrictedPatterns) }]
148
+ : restrictedPatterns.map(({ group, message }) => ({ matcher: ignore().add(group), customMessage: message }));
149
+
150
+ // if no imports are restricted we don"t need to check
151
+ if (Object.keys(restrictedPaths).length === 0 && restrictedPatternGroups.length === 0) {
152
+ return {};
153
+ }
121
154
 
122
155
  /**
123
156
  * Report a restricted path.
@@ -184,17 +217,19 @@ module.exports = {
184
217
  /**
185
218
  * Report a restricted path specifically for patterns.
186
219
  * @param {node} node representing the restricted path reference
220
+ * @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails
187
221
  * @returns {void}
188
222
  * @private
189
223
  */
190
- function reportPathForPatterns(node) {
224
+ function reportPathForPatterns(node, group) {
191
225
  const importSource = node.source.value.trim();
192
226
 
193
227
  context.report({
194
228
  node,
195
- messageId: "patterns",
229
+ messageId: group.customMessage ? "patternWithCustomMessage" : "patterns",
196
230
  data: {
197
- importSource
231
+ importSource,
232
+ customMessage: group.customMessage
198
233
  }
199
234
  });
200
235
  }
@@ -202,11 +237,12 @@ module.exports = {
202
237
  /**
203
238
  * Check if the given importSource is restricted by a pattern.
204
239
  * @param {string} importSource path of the import
240
+ * @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails
205
241
  * @returns {boolean} whether the variable is a restricted pattern or not
206
242
  * @private
207
243
  */
208
- function isRestrictedPattern(importSource) {
209
- return restrictedPatterns.length > 0 && restrictedPatternsMatcher.ignores(importSource);
244
+ function isRestrictedPattern(importSource, group) {
245
+ return group.matcher.ignores(importSource);
210
246
  }
211
247
 
212
248
  /**
@@ -249,10 +285,11 @@ module.exports = {
249
285
  }
250
286
 
251
287
  checkRestrictedPathAndReport(importSource, importNames, node);
252
-
253
- if (isRestrictedPattern(importSource)) {
254
- reportPathForPatterns(node);
255
- }
288
+ restrictedPatternGroups.forEach(group => {
289
+ if (isRestrictedPattern(importSource, group)) {
290
+ reportPathForPatterns(node, group);
291
+ }
292
+ });
256
293
  }
257
294
 
258
295
  return {
@@ -410,6 +410,31 @@ module.exports = {
410
410
  );
411
411
  }
412
412
 
413
+ /**
414
+ * Checks whether a given node is unused expression or not.
415
+ * @param {ASTNode} node The node itself
416
+ * @returns {boolean} The node is an unused expression.
417
+ * @private
418
+ */
419
+ function isUnusedExpression(node) {
420
+ const parent = node.parent;
421
+
422
+ if (parent.type === "ExpressionStatement") {
423
+ return true;
424
+ }
425
+
426
+ if (parent.type === "SequenceExpression") {
427
+ const isLastExpression = parent.expressions[parent.expressions.length - 1] === node;
428
+
429
+ if (!isLastExpression) {
430
+ return true;
431
+ }
432
+ return isUnusedExpression(parent);
433
+ }
434
+
435
+ return false;
436
+ }
437
+
413
438
  /**
414
439
  * Checks whether a given reference is a read to update itself or not.
415
440
  * @param {eslint-scope.Reference} ref A reference to check.
@@ -420,23 +445,28 @@ module.exports = {
420
445
  function isReadForItself(ref, rhsNode) {
421
446
  const id = ref.identifier;
422
447
  const parent = id.parent;
423
- const grandparent = parent.parent;
424
448
 
425
449
  return ref.isRead() && (
426
450
 
427
451
  // self update. e.g. `a += 1`, `a++`
428
- (// in RHS of an assignment for itself. e.g. `a = a + 1`
429
- ((
452
+ (
453
+ (
430
454
  parent.type === "AssignmentExpression" &&
431
- grandparent.type === "ExpressionStatement" &&
432
- parent.left === id
455
+ parent.left === id &&
456
+ isUnusedExpression(parent)
433
457
  ) ||
458
+ (
459
+ parent.type === "UpdateExpression" &&
460
+ isUnusedExpression(parent)
461
+ )
462
+ ) ||
463
+
464
+ // in RHS of an assignment for itself. e.g. `a = a + 1`
434
465
  (
435
- parent.type === "UpdateExpression" &&
436
- grandparent.type === "ExpressionStatement"
437
- ) || rhsNode &&
438
- isInside(id, rhsNode) &&
439
- !isInsideOfStorableFunction(id, rhsNode)))
466
+ rhsNode &&
467
+ isInside(id, rhsNode) &&
468
+ !isInsideOfStorableFunction(id, rhsNode)
469
+ )
440
470
  );
441
471
  }
442
472
 
@@ -11,7 +11,6 @@
11
11
 
12
12
  const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils");
13
13
  const { RegExpParser, visitRegExpAST } = require("regexpp");
14
- const lodash = require("lodash");
15
14
 
16
15
  //------------------------------------------------------------------------------
17
16
  // Helpers
@@ -137,7 +136,7 @@ module.exports = {
137
136
 
138
137
  // the opposite of the previous when the regex is matching backward in a lookbehind context.
139
138
  messageId = "backward";
140
- } else if (lodash.last(groupCut).type === "Alternative") {
139
+ } else if (groupCut[groupCut.length - 1].type === "Alternative") {
141
140
 
142
141
  // group's and bref's ancestor nodes below the lowest common ancestor are sibling alternatives => they're disjunctive.
143
142
  messageId = "disjunctive";
@@ -8,7 +8,6 @@
8
8
  // Requirements
9
9
  //------------------------------------------------------------------------------
10
10
 
11
- const lodash = require("lodash");
12
11
  const astUtils = require("./utils/ast-utils");
13
12
 
14
13
  //------------------------------------------------------------------------------
@@ -95,9 +94,16 @@ module.exports = {
95
94
  }
96
95
  }
97
96
 
97
+ /**
98
+ * A no-op function to act as placeholder for checking a node when the `enforceForClassMembers` option is `false`.
99
+ * @returns {void}
100
+ * @private
101
+ */
102
+ function noop() {}
103
+
98
104
  return {
99
105
  Property: check,
100
- MethodDefinition: enforceForClassMembers ? check : lodash.noop
106
+ MethodDefinition: enforceForClassMembers ? check : noop
101
107
  };
102
108
  }
103
109
  };
@@ -5,7 +5,7 @@
5
5
 
6
6
  "use strict";
7
7
 
8
- const { escapeRegExp } = require("lodash");
8
+ const escapeRegExp = require("escape-string-regexp");
9
9
  const astUtils = require("./utils/ast-utils");
10
10
 
11
11
  const CHAR_LIMIT = 40;
@@ -10,7 +10,6 @@
10
10
  //------------------------------------------------------------------------------
11
11
 
12
12
  const astUtils = require("./utils/ast-utils");
13
- const lodash = require("lodash");
14
13
 
15
14
  //------------------------------------------------------------------------------
16
15
  // Helpers
@@ -69,6 +68,24 @@ function normalizeOptionValue(value) {
69
68
  return { multiline, minProperties, consistent };
70
69
  }
71
70
 
71
+ /**
72
+ * Checks if a value is an object.
73
+ * @param {any} value The value to check
74
+ * @returns {boolean} `true` if the value is an object, otherwise `false`
75
+ */
76
+ function isObject(value) {
77
+ return typeof value === "object" && value !== null;
78
+ }
79
+
80
+ /**
81
+ * Checks if an option is a node-specific option
82
+ * @param {any} option The option to check
83
+ * @returns {boolean} `true` if the option is node-specific, otherwise `false`
84
+ */
85
+ function isNodeSpecificOption(option) {
86
+ return isObject(option) || typeof option === "string";
87
+ }
88
+
72
89
  /**
73
90
  * Normalizes a given option value.
74
91
  * @param {string|Object|undefined} options An option value to parse.
@@ -80,9 +97,7 @@ function normalizeOptionValue(value) {
80
97
  * }} Normalized option object.
81
98
  */
82
99
  function normalizeOptions(options) {
83
- const isNodeSpecificOption = lodash.overSome([lodash.isPlainObject, lodash.isString]);
84
-
85
- if (lodash.isPlainObject(options) && Object.values(options).some(isNodeSpecificOption)) {
100
+ if (isObject(options) && Object.values(options).some(isNodeSpecificOption)) {
86
101
  return {
87
102
  ObjectExpression: normalizeOptionValue(options.ObjectExpression),
88
103
  ObjectPattern: normalizeOptionValue(options.ObjectPattern),
@@ -295,7 +295,7 @@ module.exports = {
295
295
  * If the callback function has duplicates in its list of parameters (possible in sloppy mode),
296
296
  * don't replace it with an arrow function, because this is a SyntaxError with arrow functions.
297
297
  */
298
- return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
298
+ return;
299
299
  }
300
300
 
301
301
  // Remove `.bind(this)` if exists.
@@ -307,7 +307,7 @@ module.exports = {
307
307
  * E.g. `(foo || function(){}).bind(this)`
308
308
  */
309
309
  if (memberNode.type !== "MemberExpression") {
310
- return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
310
+ return;
311
311
  }
312
312
 
313
313
  const callNode = memberNode.parent;
@@ -320,12 +320,12 @@ module.exports = {
320
320
  * ^^^^^^^^^^^^
321
321
  */
322
322
  if (astUtils.isParenthesised(sourceCode, memberNode)) {
323
- return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
323
+ return;
324
324
  }
325
325
 
326
326
  // If comments exist in the `.bind(this)`, don't remove those.
327
327
  if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) {
328
- return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
328
+ return;
329
329
  }
330
330
 
331
331
  yield fixer.removeRange([firstTokenToRemove.range[0], lastTokenToRemove.range[1]]);
@@ -4,7 +4,7 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
- const lodash = require("lodash");
7
+ const escapeRegExp = require("escape-string-regexp");
8
8
  const astUtils = require("./utils/ast-utils");
9
9
 
10
10
  //------------------------------------------------------------------------------
@@ -17,7 +17,7 @@ const astUtils = require("./utils/ast-utils");
17
17
  * @returns {string} An escaped string.
18
18
  */
19
19
  function escape(s) {
20
- return `(?:${lodash.escapeRegExp(s)})`;
20
+ return `(?:${escapeRegExp(s)})`;
21
21
  }
22
22
 
23
23
  /**
@@ -21,7 +21,10 @@ const astUtils = require("./utils/ast-utils");
21
21
  * @returns {boolean} `true` if the node is 'NaN' identifier.
22
22
  */
23
23
  function isNaNIdentifier(node) {
24
- return Boolean(node) && node.type === "Identifier" && node.name === "NaN";
24
+ return Boolean(node) && (
25
+ astUtils.isSpecificId(node, "NaN") ||
26
+ astUtils.isSpecificMemberAccess(node, "Number", "NaN")
27
+ );
25
28
  }
26
29
 
27
30
  //------------------------------------------------------------------------------
@@ -11,7 +11,7 @@
11
11
 
12
12
  const esutils = require("esutils");
13
13
  const espree = require("espree");
14
- const lodash = require("lodash");
14
+ const escapeRegExp = require("escape-string-regexp");
15
15
  const {
16
16
  breakableTypePattern,
17
17
  createGlobalLinebreakMatcher,
@@ -1756,7 +1756,7 @@ module.exports = {
1756
1756
  * @returns {SourceLocation} The `loc` object.
1757
1757
  */
1758
1758
  getNameLocationInGlobalDirectiveComment(sourceCode, comment, name) {
1759
- const namePattern = new RegExp(`[\\s,]${lodash.escapeRegExp(name)}(?:$|[\\s,:])`, "gu");
1759
+ const namePattern = new RegExp(`[\\s,]${escapeRegExp(name)}(?:$|[\\s,:])`, "gu");
1760
1760
 
1761
1761
  // To ignore the first text "global".
1762
1762
  namePattern.lastIndex = comment.value.indexOf("global") + 6;
@@ -9,7 +9,6 @@
9
9
  //------------------------------------------------------------------------------
10
10
 
11
11
  const path = require("path");
12
- const lodash = require("lodash");
13
12
 
14
13
  //------------------------------------------------------------------------------
15
14
  // Private
@@ -28,6 +27,8 @@ const deprecationWarningMessages = {
28
27
  "projects in order to avoid loading '~/.eslintrc.*' accidentally."
29
28
  };
30
29
 
30
+ const sourceFileErrorCache = new Set();
31
+
31
32
  /**
32
33
  * Emits a deprecation warning containing a given filepath. A new deprecation warning is emitted
33
34
  * for each unique file path, but repeated invocations with the same file path have no effect.
@@ -36,7 +37,15 @@ const deprecationWarningMessages = {
36
37
  * @param {string} errorCode The warning message to show.
37
38
  * @returns {void}
38
39
  */
39
- const emitDeprecationWarning = lodash.memoize((source, errorCode) => {
40
+ function emitDeprecationWarning(source, errorCode) {
41
+ const cacheKey = JSON.stringify({ source, errorCode });
42
+
43
+ if (sourceFileErrorCache.has(cacheKey)) {
44
+ return;
45
+ }
46
+
47
+ sourceFileErrorCache.add(cacheKey);
48
+
40
49
  const rel = path.relative(process.cwd(), source);
41
50
  const message = deprecationWarningMessages[errorCode];
42
51
 
@@ -45,7 +54,7 @@ const emitDeprecationWarning = lodash.memoize((source, errorCode) => {
45
54
  "DeprecationWarning",
46
55
  errorCode
47
56
  );
48
- }, (...args) => JSON.stringify(args));
57
+ }
49
58
 
50
59
  //------------------------------------------------------------------------------
51
60
  // Public Interface
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @fileoverview Utilities to operate on strings.
3
+ * @author Stephen Wade
4
+ */
5
+
6
+ "use strict";
7
+
8
+ /**
9
+ * Converts the first letter of a string to uppercase.
10
+ * @param {string} string The string to operate on
11
+ * @returns {string} The converted string
12
+ */
13
+ function upperCaseFirst(string) {
14
+ if (string.length <= 1) {
15
+ return string.toUpperCase();
16
+ }
17
+ return string[0].toUpperCase() + string.slice(1);
18
+ }
19
+
20
+ module.exports = {
21
+ upperCaseFirst
22
+ };
@@ -12,8 +12,7 @@ const
12
12
  { isCommentToken } = require("eslint-utils"),
13
13
  TokenStore = require("./token-store"),
14
14
  astUtils = require("../shared/ast-utils"),
15
- Traverser = require("../shared/traverser"),
16
- lodash = require("lodash");
15
+ Traverser = require("../shared/traverser");
17
16
 
18
17
  //------------------------------------------------------------------------------
19
18
  // Private
@@ -350,7 +349,7 @@ class SourceCode extends TokenStore {
350
349
  let currentToken = this.getTokenBefore(node, { includeComments: true });
351
350
 
352
351
  while (currentToken && isCommentToken(currentToken)) {
353
- if (node.parent && (currentToken.start < node.parent.start)) {
352
+ if (node.parent && node.parent.type !== "Program" && (currentToken.start < node.parent.start)) {
354
353
  break;
355
354
  }
356
355
  comments.leading.push(currentToken);
@@ -362,7 +361,7 @@ class SourceCode extends TokenStore {
362
361
  currentToken = this.getTokenAfter(node, { includeComments: true });
363
362
 
364
363
  while (currentToken && isCommentToken(currentToken)) {
365
- if (node.parent && (currentToken.end > node.parent.end)) {
364
+ if (node.parent && node.parent.type !== "Program" && (currentToken.end > node.parent.end)) {
366
365
  break;
367
366
  }
368
367
  comments.trailing.push(currentToken);
@@ -531,10 +530,12 @@ class SourceCode extends TokenStore {
531
530
  }
532
531
 
533
532
  /*
534
- * To figure out which line rangeIndex is on, determine the last index at which rangeIndex could
535
- * be inserted into lineIndices to keep the list sorted.
533
+ * To figure out which line index is on, determine the last place at which index could
534
+ * be inserted into lineStartIndices to keep the list sorted.
536
535
  */
537
- const lineNumber = lodash.sortedLastIndex(this.lineStartIndices, index);
536
+ const lineNumber = index >= this.lineStartIndices[this.lineStartIndices.length - 1]
537
+ ? this.lineStartIndices.length
538
+ : this.lineStartIndices.findIndex(el => index < el);
538
539
 
539
540
  return { line: lineNumber, column: index - this.lineStartIndices[lineNumber - 1] };
540
541
  }
@@ -4,12 +4,6 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
- //------------------------------------------------------------------------------
8
- // Requirements
9
- //------------------------------------------------------------------------------
10
-
11
- const lodash = require("lodash");
12
-
13
7
  //------------------------------------------------------------------------------
14
8
  // Helpers
15
9
  //------------------------------------------------------------------------------
@@ -29,18 +23,16 @@ function getStartLocation(token) {
29
23
  //------------------------------------------------------------------------------
30
24
 
31
25
  /**
32
- * Binary-searches the index of the first token which is after the given location.
26
+ * Finds the index of the first token which is after the given location.
33
27
  * If it was not found, this returns `tokens.length`.
34
28
  * @param {(Token|Comment)[]} tokens It searches the token in this list.
35
29
  * @param {number} location The location to search.
36
30
  * @returns {number} The found index or `tokens.length`.
37
31
  */
38
32
  exports.search = function search(tokens, location) {
39
- return lodash.sortedIndexBy(
40
- tokens,
41
- { range: [location] },
42
- getStartLocation
43
- );
33
+ const index = tokens.findIndex(el => location <= getStartLocation(el));
34
+
35
+ return index === -1 ? tokens.length : index;
44
36
  };
45
37
 
46
38
  /**