eslint 9.9.1 → 9.10.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.
@@ -10,7 +10,7 @@
10
10
 
11
11
  const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator;
12
12
  const validator = new RegExpValidator();
13
- const validFlags = /[dgimsuvy]/gu;
13
+ const validFlags = "dgimsuvy";
14
14
  const undefined1 = void 0;
15
15
 
16
16
  //------------------------------------------------------------------------------
@@ -49,13 +49,13 @@ module.exports = {
49
49
  create(context) {
50
50
 
51
51
  const options = context.options[0];
52
- let allowedFlags = null;
52
+ let allowedFlags = [];
53
53
 
54
54
  if (options && options.allowConstructorFlags) {
55
- const temp = options.allowConstructorFlags.join("").replace(validFlags, "");
55
+ const temp = options.allowConstructorFlags.join("").replace(new RegExp(`[${validFlags}]`, "gu"), "");
56
56
 
57
57
  if (temp) {
58
- allowedFlags = new RegExp(`[${temp}]`, "gu");
58
+ allowedFlags = [...new Set(temp)];
59
59
  }
60
60
  }
61
61
 
@@ -125,16 +125,19 @@ module.exports = {
125
125
  /**
126
126
  * Check syntax error in a given flags.
127
127
  * @param {string|null} flags The RegExp flags to validate.
128
+ * @param {string|null} flagsToCheck The RegExp invalid flags.
129
+ * @param {string} allFlags all valid and allowed flags.
128
130
  * @returns {string|null} The syntax error.
129
131
  */
130
- function validateRegExpFlags(flags) {
131
- if (!flags) {
132
- return null;
133
- }
134
- try {
135
- validator.validateFlags(flags);
136
- } catch {
137
- return `Invalid flags supplied to RegExp constructor '${flags}'`;
132
+ function validateRegExpFlags(flags, flagsToCheck, allFlags) {
133
+ const duplicateFlags = [];
134
+
135
+ if (typeof flagsToCheck === "string") {
136
+ for (const flag of flagsToCheck) {
137
+ if (allFlags.includes(flag)) {
138
+ duplicateFlags.push(flag);
139
+ }
140
+ }
138
141
  }
139
142
 
140
143
  /*
@@ -142,10 +145,19 @@ module.exports = {
142
145
  * but this rule may check only the flag when the pattern is unidentifiable, so check it here.
143
146
  * https://tc39.es/ecma262/multipage/text-processing.html#sec-parsepattern
144
147
  */
145
- if (flags.includes("u") && flags.includes("v")) {
148
+ if (flags && flags.includes("u") && flags.includes("v")) {
146
149
  return "Regex 'u' and 'v' flags cannot be used together";
147
150
  }
148
- return null;
151
+
152
+ if (duplicateFlags.length > 0) {
153
+ return `Duplicate flags ('${duplicateFlags.join("")}') supplied to RegExp constructor`;
154
+ }
155
+
156
+ if (!flagsToCheck) {
157
+ return null;
158
+ }
159
+
160
+ return `Invalid flags supplied to RegExp constructor '${flagsToCheck}'`;
149
161
  }
150
162
 
151
163
  return {
@@ -154,13 +166,17 @@ module.exports = {
154
166
  return;
155
167
  }
156
168
 
157
- let flags = getFlags(node);
169
+ const flags = getFlags(node);
170
+ let flagsToCheck = flags;
171
+ const allFlags = allowedFlags.length > 0 ? validFlags.split("").concat(allowedFlags) : validFlags.split("");
158
172
 
159
- if (flags && allowedFlags) {
160
- flags = flags.replace(allowedFlags, "");
173
+ if (flags) {
174
+ allFlags.forEach(flag => {
175
+ flagsToCheck = flagsToCheck.replace(flag, "");
176
+ });
161
177
  }
162
178
 
163
- let message = validateRegExpFlags(flags);
179
+ let message = validateRegExpFlags(flags, flagsToCheck, allFlags);
164
180
 
165
181
  if (message) {
166
182
  report(node, message);
@@ -18,6 +18,26 @@ const {
18
18
  const astUtils = require("./utils/ast-utils.js");
19
19
  const { isValidWithUnicodeFlag } = require("./utils/regular-expressions");
20
20
 
21
+ /**
22
+ * Checks whether the flag configuration should be treated as a missing flag.
23
+ * @param {"u"|"v"|undefined} requireFlag A particular flag to require
24
+ * @param {string} flags The regex flags
25
+ * @returns {boolean} Whether the flag configuration results in a missing flag.
26
+ */
27
+ function checkFlags(requireFlag, flags) {
28
+ let missingFlag;
29
+
30
+ if (requireFlag === "v") {
31
+ missingFlag = !flags.includes("v");
32
+ } else if (requireFlag === "u") {
33
+ missingFlag = !flags.includes("u");
34
+ } else {
35
+ missingFlag = !flags.includes("u") && !flags.includes("v");
36
+ }
37
+
38
+ return missingFlag;
39
+ }
40
+
21
41
  //------------------------------------------------------------------------------
22
42
  // Rule Definition
23
43
  //------------------------------------------------------------------------------
@@ -37,31 +57,65 @@ module.exports = {
37
57
 
38
58
  messages: {
39
59
  addUFlag: "Add the 'u' flag.",
40
- requireUFlag: "Use the 'u' flag."
60
+ addVFlag: "Add the 'v' flag.",
61
+ requireUFlag: "Use the 'u' flag.",
62
+ requireVFlag: "Use the 'v' flag."
41
63
  },
42
64
 
43
- schema: []
65
+ schema: [
66
+ {
67
+ type: "object",
68
+ properties: {
69
+ requireFlag: {
70
+ enum: ["u", "v"]
71
+ }
72
+ },
73
+ additionalProperties: false
74
+ }
75
+ ]
44
76
  },
45
77
 
46
78
  create(context) {
47
79
 
48
80
  const sourceCode = context.sourceCode;
49
81
 
82
+ const {
83
+ requireFlag
84
+ } = context.options[0] ?? {};
85
+
50
86
  return {
51
87
  "Literal[regex]"(node) {
52
88
  const flags = node.regex.flags || "";
53
89
 
54
- if (!flags.includes("u") && !flags.includes("v")) {
90
+ const missingFlag = checkFlags(requireFlag, flags);
91
+
92
+ if (missingFlag) {
55
93
  context.report({
56
- messageId: "requireUFlag",
94
+ messageId: requireFlag === "v" ? "requireVFlag" : "requireUFlag",
57
95
  node,
58
- suggest: isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)
96
+ suggest: isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern, requireFlag)
59
97
  ? [
60
98
  {
61
99
  fix(fixer) {
62
- return fixer.insertTextAfter(node, "u");
100
+ const replaceFlag = requireFlag ?? "u";
101
+ const regex = sourceCode.getText(node);
102
+ const slashPos = regex.lastIndexOf("/");
103
+
104
+ if (requireFlag) {
105
+ const flag = requireFlag === "u" ? "v" : "u";
106
+
107
+ if (regex.includes(flag, slashPos)) {
108
+ return fixer.replaceText(
109
+ node,
110
+ regex.slice(0, slashPos) +
111
+ regex.slice(slashPos).replace(flag, requireFlag)
112
+ );
113
+ }
114
+ }
115
+
116
+ return fixer.insertTextAfter(node, replaceFlag);
63
117
  },
64
- messageId: "addUFlag"
118
+ messageId: requireFlag === "v" ? "addVFlag" : "addUFlag"
65
119
  }
66
120
  ]
67
121
  : null
@@ -85,22 +139,49 @@ module.exports = {
85
139
  const pattern = getStringIfConstant(patternNode, scope);
86
140
  const flags = getStringIfConstant(flagsNode, scope);
87
141
 
88
- if (!flagsNode || (typeof flags === "string" && !flags.includes("u") && !flags.includes("v"))) {
142
+ let missingFlag = !flagsNode;
143
+
144
+ if (typeof flags === "string") {
145
+ missingFlag = checkFlags(requireFlag, flags);
146
+ }
147
+
148
+ if (missingFlag) {
89
149
  context.report({
90
- messageId: "requireUFlag",
150
+ messageId: requireFlag === "v" ? "requireVFlag" : "requireUFlag",
91
151
  node: refNode,
92
- suggest: typeof pattern === "string" && isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)
152
+ suggest: typeof pattern === "string" && isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern, requireFlag)
93
153
  ? [
94
154
  {
95
155
  fix(fixer) {
156
+ const replaceFlag = requireFlag ?? "u";
157
+
96
158
  if (flagsNode) {
97
159
  if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") {
98
160
  const flagsNodeText = sourceCode.getText(flagsNode);
161
+ const flag = requireFlag === "u" ? "v" : "u";
162
+
163
+ if (flags.includes(flag)) {
164
+
165
+ // Avoid replacing "u" in escapes like `\uXXXX`
166
+ if (flagsNode.type === "Literal" && flagsNode.raw.includes("\\")) {
167
+ return null;
168
+ }
169
+
170
+ // Avoid replacing "u" in expressions like "`${regularFlags}g`"
171
+ if (flagsNode.type === "TemplateLiteral" && (
172
+ flagsNode.expressions.length ||
173
+ flagsNode.quasis.some(({ value: { raw } }) => raw.includes("\\"))
174
+ )) {
175
+ return null;
176
+ }
177
+
178
+ return fixer.replaceText(flagsNode, flagsNodeText.replace(flag, replaceFlag));
179
+ }
99
180
 
100
181
  return fixer.replaceText(flagsNode, [
101
182
  flagsNodeText.slice(0, flagsNodeText.length - 1),
102
183
  flagsNodeText.slice(flagsNodeText.length - 1)
103
- ].join("u"));
184
+ ].join(replaceFlag));
104
185
  }
105
186
 
106
187
  // We intentionally don't suggest concatenating + "u" to non-literals
@@ -112,11 +193,11 @@ module.exports = {
112
193
  return fixer.insertTextAfter(
113
194
  penultimateToken,
114
195
  astUtils.isCommaToken(penultimateToken)
115
- ? ' "u",'
116
- : ', "u"'
196
+ ? ` "${replaceFlag}",`
197
+ : `, "${replaceFlag}"`
117
198
  );
118
199
  },
119
- messageId: "addUFlag"
200
+ messageId: requireFlag === "v" ? "addVFlag" : "addUFlag"
120
201
  }
121
202
  ]
122
203
  : null
@@ -14,12 +14,16 @@ const REGEXPP_LATEST_ECMA_VERSION = 2025;
14
14
  * Checks if the given regular expression pattern would be valid with the `u` flag.
15
15
  * @param {number} ecmaVersion ECMAScript version to parse in.
16
16
  * @param {string} pattern The regular expression pattern to verify.
17
+ * @param {"u"|"v"} flag The type of Unicode flag
17
18
  * @returns {boolean} `true` if the pattern would be valid with the `u` flag.
18
19
  * `false` if the pattern would be invalid with the `u` flag or the configured
19
20
  * ecmaVersion doesn't support the `u` flag.
20
21
  */
21
- function isValidWithUnicodeFlag(ecmaVersion, pattern) {
22
- if (ecmaVersion <= 5) { // ecmaVersion <= 5 doesn't support the 'u' flag
22
+ function isValidWithUnicodeFlag(ecmaVersion, pattern, flag = "u") {
23
+ if (flag === "u" && ecmaVersion <= 5) { // ecmaVersion <= 5 doesn't support the 'u' flag
24
+ return false;
25
+ }
26
+ if (flag === "v" && ecmaVersion <= 2023) {
23
27
  return false;
24
28
  }
25
29
 
@@ -28,7 +32,11 @@ function isValidWithUnicodeFlag(ecmaVersion, pattern) {
28
32
  });
29
33
 
30
34
  try {
31
- validator.validatePattern(pattern, void 0, void 0, { unicode: /* uFlag = */ true });
35
+ validator.validatePattern(pattern, void 0, void 0, flag === "u" ? {
36
+ unicode: /* uFlag = */ true
37
+ } : {
38
+ unicodeSets: true
39
+ });
32
40
  } catch {
33
41
  return false;
34
42
  }