eslint 8.4.1 → 8.8.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.
@@ -11,11 +11,15 @@
11
11
 
12
12
  const astUtils = require("./utils/ast-utils");
13
13
  const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("eslint-utils");
14
+ const { RegExpValidator, visitRegExpAST, RegExpParser } = require("regexpp");
15
+ const { canTokensBeAdjacent } = require("./utils/ast-utils");
14
16
 
15
17
  //------------------------------------------------------------------------------
16
18
  // Helpers
17
19
  //------------------------------------------------------------------------------
18
20
 
21
+ const REGEXPP_LATEST_ECMA_VERSION = 2022;
22
+
19
23
  /**
20
24
  * Determines whether the given node is a string literal.
21
25
  * @param {ASTNode} node Node to check.
@@ -43,6 +47,71 @@ function isStaticTemplateLiteral(node) {
43
47
  return node.type === "TemplateLiteral" && node.expressions.length === 0;
44
48
  }
45
49
 
50
+ const validPrecedingTokens = [
51
+ "(",
52
+ ";",
53
+ "[",
54
+ ",",
55
+ "=",
56
+ "+",
57
+ "*",
58
+ "-",
59
+ "?",
60
+ "~",
61
+ "%",
62
+ "**",
63
+ "!",
64
+ "typeof",
65
+ "instanceof",
66
+ "&&",
67
+ "||",
68
+ "??",
69
+ "return",
70
+ "...",
71
+ "delete",
72
+ "void",
73
+ "in",
74
+ "<",
75
+ ">",
76
+ "<=",
77
+ ">=",
78
+ "==",
79
+ "===",
80
+ "!=",
81
+ "!==",
82
+ "<<",
83
+ ">>",
84
+ ">>>",
85
+ "&",
86
+ "|",
87
+ "^",
88
+ ":",
89
+ "{",
90
+ "=>",
91
+ "*=",
92
+ "<<=",
93
+ ">>=",
94
+ ">>>=",
95
+ "^=",
96
+ "|=",
97
+ "&=",
98
+ "??=",
99
+ "||=",
100
+ "&&=",
101
+ "**=",
102
+ "+=",
103
+ "-=",
104
+ "/=",
105
+ "%=",
106
+ "/",
107
+ "do",
108
+ "break",
109
+ "continue",
110
+ "debugger",
111
+ "case",
112
+ "throw"
113
+ ];
114
+
46
115
 
47
116
  //------------------------------------------------------------------------------
48
117
  // Rule Definition
@@ -59,6 +128,8 @@ module.exports = {
59
128
  url: "https://eslint.org/docs/rules/prefer-regex-literals"
60
129
  },
61
130
 
131
+ hasSuggestions: true,
132
+
62
133
  schema: [
63
134
  {
64
135
  type: "object",
@@ -74,6 +145,7 @@ module.exports = {
74
145
 
75
146
  messages: {
76
147
  unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor.",
148
+ replaceWithLiteral: "Replace with an equivalent regular expression literal.",
77
149
  unexpectedRedundantRegExp: "Regular expression literal is unnecessarily wrapped within a 'RegExp' constructor.",
78
150
  unexpectedRedundantRegExpWithFlags: "Use regular expression literal with flags instead of the 'RegExp' constructor."
79
151
  }
@@ -81,6 +153,7 @@ module.exports = {
81
153
 
82
154
  create(context) {
83
155
  const [{ disallowRedundantWrapping = false } = {}] = context.options;
156
+ const sourceCode = context.getSourceCode();
84
157
 
85
158
  /**
86
159
  * Determines whether the given identifier node is a reference to a global variable.
@@ -107,6 +180,27 @@ module.exports = {
107
180
  isStaticTemplateLiteral(node.quasi);
108
181
  }
109
182
 
183
+ /**
184
+ * Gets the value of a string
185
+ * @param {ASTNode} node The node to get the string of.
186
+ * @returns {string|null} The value of the node.
187
+ */
188
+ function getStringValue(node) {
189
+ if (isStringLiteral(node)) {
190
+ return node.value;
191
+ }
192
+
193
+ if (isStaticTemplateLiteral(node)) {
194
+ return node.quasis[0].value.cooked;
195
+ }
196
+
197
+ if (isStringRawTaggedStaticTemplateLiteral(node)) {
198
+ return node.quasi.quasis[0].value.raw;
199
+ }
200
+
201
+ return null;
202
+ }
203
+
110
204
  /**
111
205
  * Determines whether the given node is considered to be a static string by the logic of this rule.
112
206
  * @param {ASTNode} node Node to check.
@@ -152,6 +246,53 @@ module.exports = {
152
246
  return false;
153
247
  }
154
248
 
249
+ /**
250
+ * Returns a ecmaVersion compatible for regexpp.
251
+ * @param {any} ecmaVersion The ecmaVersion to convert.
252
+ * @returns {import("regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp.
253
+ */
254
+ function getRegexppEcmaVersion(ecmaVersion) {
255
+ if (typeof ecmaVersion !== "number" || ecmaVersion <= 5) {
256
+ return 5;
257
+ }
258
+ return Math.min(ecmaVersion + 2009, REGEXPP_LATEST_ECMA_VERSION);
259
+ }
260
+
261
+ /**
262
+ * Makes a character escaped or else returns null.
263
+ * @param {string} character The character to escape.
264
+ * @returns {string} The resulting escaped character.
265
+ */
266
+ function resolveEscapes(character) {
267
+ switch (character) {
268
+ case "\n":
269
+ case "\\\n":
270
+ return "\\n";
271
+
272
+ case "\r":
273
+ case "\\\r":
274
+ return "\\r";
275
+
276
+ case "\t":
277
+ case "\\\t":
278
+ return "\\t";
279
+
280
+ case "\v":
281
+ case "\\\v":
282
+ return "\\v";
283
+
284
+ case "\f":
285
+ case "\\\f":
286
+ return "\\f";
287
+
288
+ case "/":
289
+ return "\\/";
290
+
291
+ default:
292
+ return null;
293
+ }
294
+ }
295
+
155
296
  return {
156
297
  Program() {
157
298
  const scope = context.getScope();
@@ -171,7 +312,82 @@ module.exports = {
171
312
  context.report({ node, messageId: "unexpectedRedundantRegExp" });
172
313
  }
173
314
  } else if (hasOnlyStaticStringArguments(node)) {
174
- context.report({ node, messageId: "unexpectedRegExp" });
315
+ let regexContent = getStringValue(node.arguments[0]);
316
+ let noFix = false;
317
+ let flags;
318
+
319
+ if (node.arguments[1]) {
320
+ flags = getStringValue(node.arguments[1]);
321
+ }
322
+
323
+ const regexppEcmaVersion = getRegexppEcmaVersion(context.parserOptions.ecmaVersion);
324
+ const RegExpValidatorInstance = new RegExpValidator({ ecmaVersion: regexppEcmaVersion });
325
+
326
+ try {
327
+ RegExpValidatorInstance.validatePattern(regexContent, 0, regexContent.length, flags ? flags.includes("u") : false);
328
+ if (flags) {
329
+ RegExpValidatorInstance.validateFlags(flags);
330
+ }
331
+ } catch {
332
+ noFix = true;
333
+ }
334
+
335
+ const tokenBefore = sourceCode.getTokenBefore(node);
336
+
337
+ if (tokenBefore && !validPrecedingTokens.includes(tokenBefore.value)) {
338
+ noFix = true;
339
+ }
340
+
341
+ if (!/^[-a-zA-Z0-9\\[\](){} \t\r\n\v\f!@#$%^&*+^_=/~`.><?,'"|:;]*$/u.test(regexContent)) {
342
+ noFix = true;
343
+ }
344
+
345
+ if (sourceCode.getCommentsInside(node).length > 0) {
346
+ noFix = true;
347
+ }
348
+
349
+ if (regexContent && !noFix) {
350
+ let charIncrease = 0;
351
+
352
+ const ast = new RegExpParser({ ecmaVersion: regexppEcmaVersion }).parsePattern(regexContent, 0, regexContent.length, flags ? flags.includes("u") : false);
353
+
354
+ visitRegExpAST(ast, {
355
+ onCharacterEnter(characterNode) {
356
+ const escaped = resolveEscapes(characterNode.raw);
357
+
358
+ if (escaped) {
359
+ regexContent =
360
+ regexContent.slice(0, characterNode.start + charIncrease) +
361
+ escaped +
362
+ regexContent.slice(characterNode.end + charIncrease);
363
+
364
+ if (characterNode.raw.length === 1) {
365
+ charIncrease += 1;
366
+ }
367
+ }
368
+ }
369
+ });
370
+ }
371
+
372
+ const newRegExpValue = `/${regexContent || "(?:)"}/${flags || ""}`;
373
+
374
+ context.report({
375
+ node,
376
+ messageId: "unexpectedRegExp",
377
+ suggest: noFix ? [] : [{
378
+ messageId: "replaceWithLiteral",
379
+ fix(fixer) {
380
+ const tokenAfter = sourceCode.getTokenAfter(node);
381
+
382
+ return fixer.replaceText(
383
+ node,
384
+ (tokenBefore && !canTokensBeAdjacent(tokenBefore, newRegExpValue) && tokenBefore.range[1] === node.range[0] ? " " : "") +
385
+ newRegExpValue +
386
+ (tokenAfter && !canTokensBeAdjacent(newRegExpValue, tokenAfter) && node.range[1] === tokenAfter.range[0] ? " " : "")
387
+ );
388
+ }
389
+ }]
390
+ });
175
391
  }
176
392
  }
177
393
  }
@@ -188,7 +188,7 @@ module.exports = {
188
188
  return sourceCode.getText(currentNode);
189
189
  }
190
190
 
191
- if (isConcatenation(currentNode) && hasStringLiteral(currentNode) && hasNonStringLiteral(currentNode)) {
191
+ if (isConcatenation(currentNode) && hasStringLiteral(currentNode)) {
192
192
  const plusSign = sourceCode.getFirstTokenBetween(currentNode.left, currentNode.right, token => token.value === "+");
193
193
  const textBeforePlus = getTextBetween(currentNode.left, plusSign);
194
194
  const textAfterPlus = getTextBetween(plusSign, currentNode.right);
@@ -223,9 +223,20 @@ module.exports = {
223
223
  // ModuleSpecifier.
224
224
  case "ImportDeclaration":
225
225
  case "ExportNamedDeclaration":
226
- case "ExportAllDeclaration":
227
226
  return parent.source === node;
228
227
 
228
+ // ModuleExportName or ModuleSpecifier.
229
+ case "ExportAllDeclaration":
230
+ return parent.exported === node || parent.source === node;
231
+
232
+ // ModuleExportName.
233
+ case "ImportSpecifier":
234
+ return parent.imported === node;
235
+
236
+ // ModuleExportName.
237
+ case "ExportSpecifier":
238
+ return parent.local === node || parent.exported === node;
239
+
229
240
  // Others don't allow.
230
241
  default:
231
242
  return false;
@@ -769,6 +769,25 @@ function getSwitchCaseColonToken(node, sourceCode) {
769
769
  return sourceCode.getFirstToken(node, 1);
770
770
  }
771
771
 
772
+ /**
773
+ * Gets ESM module export name represented by the given node.
774
+ * @param {ASTNode} node `Identifier` or string `Literal` node in a position
775
+ * that represents a module export name:
776
+ * - `ImportSpecifier#imported`
777
+ * - `ExportSpecifier#local` (if it is a re-export from another module)
778
+ * - `ExportSpecifier#exported`
779
+ * - `ExportAllDeclaration#exported`
780
+ * @returns {string} The module export name.
781
+ */
782
+ function getModuleExportName(node) {
783
+ if (node.type === "Identifier") {
784
+ return node.name;
785
+ }
786
+
787
+ // string literal
788
+ return node.value;
789
+ }
790
+
772
791
  //------------------------------------------------------------------------------
773
792
  // Public Interface
774
793
  //------------------------------------------------------------------------------
@@ -1898,5 +1917,6 @@ module.exports = {
1898
1917
  equalLiteralValue,
1899
1918
  isSameReference,
1900
1919
  isLogicalAssignmentOperator,
1901
- getSwitchCaseColonToken
1920
+ getSwitchCaseColonToken,
1921
+ getModuleExportName
1902
1922
  };
@@ -105,6 +105,21 @@ module.exports = {};
105
105
  * @property {Array<{desc?: string, messageId?: string, fix: {range: [number, number], text: string}}>} [suggestions] Information for suggestions.
106
106
  */
107
107
 
108
+ /**
109
+ * @typedef {Object} SuppressedLintMessage
110
+ * @property {number|undefined} column The 1-based column number.
111
+ * @property {number} [endColumn] The 1-based column number of the end location.
112
+ * @property {number} [endLine] The 1-based line number of the end location.
113
+ * @property {boolean} fatal If `true` then this is a fatal error.
114
+ * @property {{range:[number,number], text:string}} [fix] Information for autofix.
115
+ * @property {number|undefined} line The 1-based line number.
116
+ * @property {string} message The error message.
117
+ * @property {string|null} ruleId The ID of the rule which makes this message.
118
+ * @property {0|1|2} severity The severity of this message.
119
+ * @property {Array<{kind: string, justification: string}>} suppressions The suppression info.
120
+ * @property {Array<{desc?: string, messageId?: string, fix: {range: [number, number], text: string}}>} [suggestions] Information for suggestions.
121
+ */
122
+
108
123
  /**
109
124
  * @typedef {Object} SuggestionResult
110
125
  * @property {string} desc A short description.
@@ -6,7 +6,7 @@ module.exports = function(it) {
6
6
  return `
7
7
  ESLint couldn't find a configuration file. To set up a configuration file for this project, please run:
8
8
 
9
- eslint --init
9
+ npm init @eslint/config
10
10
 
11
11
  ESLint looked for configuration files in ${directoryPath} and its ancestors. If it found none, it then looked in your home directory.
12
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "8.4.1",
3
+ "version": "8.8.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {
@@ -54,12 +54,11 @@
54
54
  "cross-spawn": "^7.0.2",
55
55
  "debug": "^4.3.2",
56
56
  "doctrine": "^3.0.0",
57
- "enquirer": "^2.3.5",
58
57
  "escape-string-regexp": "^4.0.0",
59
58
  "eslint-scope": "^7.1.0",
60
59
  "eslint-utils": "^3.0.0",
61
- "eslint-visitor-keys": "^3.1.0",
62
- "espree": "^9.2.0",
60
+ "eslint-visitor-keys": "^3.2.0",
61
+ "espree": "^9.3.0",
63
62
  "esquery": "^1.4.0",
64
63
  "esutils": "^2.0.2",
65
64
  "fast-deep-equal": "^3.1.3",
@@ -67,7 +66,7 @@
67
66
  "functional-red-black-tree": "^1.0.1",
68
67
  "glob-parent": "^6.0.1",
69
68
  "globals": "^13.6.0",
70
- "ignore": "^4.0.6",
69
+ "ignore": "^5.2.0",
71
70
  "import-fresh": "^3.0.0",
72
71
  "imurmurhash": "^0.1.4",
73
72
  "is-glob": "^4.0.0",
@@ -78,9 +77,7 @@
78
77
  "minimatch": "^3.0.4",
79
78
  "natural-compare": "^1.4.0",
80
79
  "optionator": "^0.9.1",
81
- "progress": "^2.0.0",
82
80
  "regexpp": "^3.2.0",
83
- "semver": "^7.2.1",
84
81
  "strip-ansi": "^6.0.1",
85
82
  "strip-json-comments": "^3.1.0",
86
83
  "text-table": "^0.2.0",
@@ -118,16 +115,19 @@
118
115
  "load-perf": "^0.2.0",
119
116
  "markdownlint": "^0.24.0",
120
117
  "markdownlint-cli": "^0.30.0",
118
+ "marked": "^4.0.8",
121
119
  "memfs": "^3.0.1",
122
120
  "mocha": "^8.3.2",
123
121
  "mocha-junit-reporter": "^2.0.0",
124
122
  "node-polyfill-webpack-plugin": "^1.0.3",
125
123
  "npm-license": "^0.3.3",
126
124
  "nyc": "^15.0.1",
125
+ "progress": "^2.0.3",
127
126
  "proxyquire": "^2.0.1",
128
127
  "puppeteer": "^9.1.1",
129
128
  "recast": "^0.20.4",
130
129
  "regenerator-runtime": "^0.13.2",
130
+ "semver": "^7.3.5",
131
131
  "shelljs": "^0.8.2",
132
132
  "sinon": "^11.0.0",
133
133
  "temp": "^0.9.0",