eslint 8.28.0 → 8.29.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.
@@ -52,7 +52,8 @@ module.exports = {
52
52
  enforceForArrowConditionals: { type: "boolean" },
53
53
  enforceForSequenceExpressions: { type: "boolean" },
54
54
  enforceForNewInMemberExpressions: { type: "boolean" },
55
- enforceForFunctionPrototypeMethods: { type: "boolean" }
55
+ enforceForFunctionPrototypeMethods: { type: "boolean" },
56
+ allowParensAfterCommentPattern: { type: "string" }
56
57
  },
57
58
  additionalProperties: false
58
59
  }
@@ -86,6 +87,7 @@ module.exports = {
86
87
  context.options[1].enforceForNewInMemberExpressions === false;
87
88
  const IGNORE_FUNCTION_PROTOTYPE_METHODS = ALL_NODES && context.options[1] &&
88
89
  context.options[1].enforceForFunctionPrototypeMethods === false;
90
+ const ALLOW_PARENS_AFTER_COMMENT_PATTERN = ALL_NODES && context.options[1] && context.options[1].allowParensAfterCommentPattern;
89
91
 
90
92
  const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
91
93
  const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
@@ -402,6 +404,19 @@ module.exports = {
402
404
  if (isIIFE(node) && !isParenthesised(node.callee)) {
403
405
  return;
404
406
  }
407
+
408
+ if (ALLOW_PARENS_AFTER_COMMENT_PATTERN) {
409
+ const commentsBeforeLeftParenToken = sourceCode.getCommentsBefore(leftParenToken);
410
+ const totalCommentsBeforeLeftParenTokenCount = commentsBeforeLeftParenToken.length;
411
+ const ignorePattern = new RegExp(ALLOW_PARENS_AFTER_COMMENT_PATTERN, "u");
412
+
413
+ if (
414
+ totalCommentsBeforeLeftParenTokenCount > 0 &&
415
+ ignorePattern.test(commentsBeforeLeftParenToken[totalCommentsBeforeLeftParenTokenCount - 1].value)
416
+ ) {
417
+ return;
418
+ }
419
+ }
405
420
  }
406
421
 
407
422
  /**
@@ -59,6 +59,20 @@ module.exports = {
59
59
  }
60
60
  }
61
61
 
62
+ /**
63
+ * Reports error with the provided message.
64
+ * @param {ASTNode} node The node holding the invalid RegExp
65
+ * @param {string} message The message to report.
66
+ * @returns {void}
67
+ */
68
+ function report(node, message) {
69
+ context.report({
70
+ node,
71
+ messageId: "regexMessage",
72
+ data: { message }
73
+ });
74
+ }
75
+
62
76
  /**
63
77
  * Check if node is a string
64
78
  * @param {ASTNode} node node to evaluate
@@ -108,10 +122,13 @@ module.exports = {
108
122
 
109
123
  /**
110
124
  * Check syntax error in a given flags.
111
- * @param {string} flags The RegExp flags to validate.
125
+ * @param {string|null} flags The RegExp flags to validate.
112
126
  * @returns {string|null} The syntax error.
113
127
  */
114
128
  function validateRegExpFlags(flags) {
129
+ if (!flags) {
130
+ return null;
131
+ }
115
132
  try {
116
133
  validator.validateFlags(flags);
117
134
  return null;
@@ -122,34 +139,39 @@ module.exports = {
122
139
 
123
140
  return {
124
141
  "CallExpression, NewExpression"(node) {
125
- if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp" || !isString(node.arguments[0])) {
142
+ if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp") {
126
143
  return;
127
144
  }
128
- const pattern = node.arguments[0].value;
145
+
129
146
  let flags = getFlags(node);
130
147
 
131
148
  if (flags && allowedFlags) {
132
149
  flags = flags.replace(allowedFlags, "");
133
150
  }
134
151
 
135
- const message =
136
- (
137
- flags && validateRegExpFlags(flags)
138
- ) ||
139
- (
152
+ let message = validateRegExpFlags(flags);
153
+
154
+ if (message) {
155
+ report(node, message);
156
+ return;
157
+ }
158
+
159
+ if (!isString(node.arguments[0])) {
160
+ return;
161
+ }
162
+
163
+ const pattern = node.arguments[0].value;
164
+
165
+ message = (
140
166
 
141
- // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag
142
- flags === null
143
- ? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false)
144
- : validateRegExpPattern(pattern, flags.includes("u"))
145
- );
167
+ // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag
168
+ flags === null
169
+ ? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false)
170
+ : validateRegExpPattern(pattern, flags.includes("u"))
171
+ );
146
172
 
147
173
  if (message) {
148
- context.report({
149
- node,
150
- messageId: "regexMessage",
151
- data: { message }
152
- });
174
+ report(node, message);
153
175
  }
154
176
  }
155
177
  };
@@ -23,6 +23,61 @@ const regexpp = require("regexpp");
23
23
 
24
24
  const parser = new regexpp.RegExpParser();
25
25
 
26
+ /**
27
+ * Creates fixer suggestions for the regex, if statically determinable.
28
+ * @param {number} groupStart Starting index of the regex group.
29
+ * @param {string} pattern The regular expression pattern to be checked.
30
+ * @param {string} rawText Source text of the regexNode.
31
+ * @param {ASTNode} regexNode AST node which contains the regular expression.
32
+ * @returns {Array<SuggestionResult>} Fixer suggestions for the regex, if statically determinable.
33
+ */
34
+ function suggestIfPossible(groupStart, pattern, rawText, regexNode) {
35
+ switch (regexNode.type) {
36
+ case "Literal":
37
+ if (typeof regexNode.value === "string" && rawText.includes("\\")) {
38
+ return null;
39
+ }
40
+ break;
41
+ case "TemplateLiteral":
42
+ if (regexNode.expressions.length || rawText.slice(1, -1) !== pattern) {
43
+ return null;
44
+ }
45
+ break;
46
+ default:
47
+ return null;
48
+ }
49
+
50
+ const start = regexNode.range[0] + groupStart + 2;
51
+
52
+ return [
53
+ {
54
+ fix(fixer) {
55
+ const existingTemps = pattern.match(/temp\d+/gu) || [];
56
+ const highestTempCount = existingTemps.reduce(
57
+ (previous, next) =>
58
+ Math.max(previous, Number(next.slice("temp".length))),
59
+ 0
60
+ );
61
+
62
+ return fixer.insertTextBeforeRange(
63
+ [start, start],
64
+ `?<temp${highestTempCount + 1}>`
65
+ );
66
+ },
67
+ messageId: "addGroupName"
68
+ },
69
+ {
70
+ fix(fixer) {
71
+ return fixer.insertTextBeforeRange(
72
+ [start, start],
73
+ "?:"
74
+ );
75
+ },
76
+ messageId: "addNonCapture"
77
+ }
78
+ ];
79
+ }
80
+
26
81
  //------------------------------------------------------------------------------
27
82
  // Rule Definition
28
83
  //------------------------------------------------------------------------------
@@ -38,23 +93,29 @@ module.exports = {
38
93
  url: "https://eslint.org/docs/rules/prefer-named-capture-group"
39
94
  },
40
95
 
96
+ hasSuggestions: true,
97
+
41
98
  schema: [],
42
99
 
43
100
  messages: {
101
+ addGroupName: "Add name to capture group.",
102
+ addNonCapture: "Convert group to non-capturing.",
44
103
  required: "Capture group '{{group}}' should be converted to a named or non-capturing group."
45
104
  }
46
105
  },
47
106
 
48
107
  create(context) {
108
+ const sourceCode = context.getSourceCode();
49
109
 
50
110
  /**
51
111
  * Function to check regular expression.
52
- * @param {string} pattern The regular expression pattern to be check.
53
- * @param {ASTNode} node AST node which contains regular expression.
112
+ * @param {string} pattern The regular expression pattern to be checked.
113
+ * @param {ASTNode} node AST node which contains the regular expression or a call/new expression.
114
+ * @param {ASTNode} regexNode AST node which contains the regular expression.
54
115
  * @param {boolean} uFlag Flag indicates whether unicode mode is enabled or not.
55
116
  * @returns {void}
56
117
  */
57
- function checkRegex(pattern, node, uFlag) {
118
+ function checkRegex(pattern, node, regexNode, uFlag) {
58
119
  let ast;
59
120
 
60
121
  try {
@@ -68,12 +129,16 @@ module.exports = {
68
129
  regexpp.visitRegExpAST(ast, {
69
130
  onCapturingGroupEnter(group) {
70
131
  if (!group.name) {
132
+ const rawText = sourceCode.getText(regexNode);
133
+ const suggest = suggestIfPossible(group.start, pattern, rawText, regexNode);
134
+
71
135
  context.report({
72
136
  node,
73
137
  messageId: "required",
74
138
  data: {
75
139
  group: group.raw
76
- }
140
+ },
141
+ suggest
77
142
  });
78
143
  }
79
144
  }
@@ -83,7 +148,7 @@ module.exports = {
83
148
  return {
84
149
  Literal(node) {
85
150
  if (node.regex) {
86
- checkRegex(node.regex.pattern, node, node.regex.flags.includes("u"));
151
+ checkRegex(node.regex.pattern, node, node, node.regex.flags.includes("u"));
87
152
  }
88
153
  },
89
154
  Program() {
@@ -101,7 +166,7 @@ module.exports = {
101
166
  const flags = getStringIfConstant(node.arguments[1]);
102
167
 
103
168
  if (regex) {
104
- checkRegex(regex, node, flags && flags.includes("u"));
169
+ checkRegex(regex, node, node.arguments[0], flags && flags.includes("u"));
105
170
  }
106
171
  }
107
172
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "8.28.0",
3
+ "version": "8.29.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {