@eslint-community/regexpp 4.11.2 → 4.12.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/index.mjs CHANGED
@@ -548,6 +548,17 @@ const CLASS_SET_RESERVED_PUNCTUATOR = new Set([
548
548
  GRAVE_ACCENT,
549
549
  TILDE,
550
550
  ]);
551
+ const FLAG_PROP_TO_CODEPOINT = {
552
+ global: LATIN_SMALL_LETTER_G,
553
+ ignoreCase: LATIN_SMALL_LETTER_I,
554
+ multiline: LATIN_SMALL_LETTER_M,
555
+ unicode: LATIN_SMALL_LETTER_U,
556
+ sticky: LATIN_SMALL_LETTER_Y,
557
+ dotAll: LATIN_SMALL_LETTER_S,
558
+ hasIndices: LATIN_SMALL_LETTER_D,
559
+ unicodeSets: LATIN_SMALL_LETTER_V,
560
+ };
561
+ const FLAG_CODEPOINT_TO_PROP = Object.fromEntries(Object.entries(FLAG_PROP_TO_CODEPOINT).map(([k, v]) => [v, k]));
551
562
  function isSyntaxCharacter(cp) {
552
563
  return SYNTAX_CHARACTER.has(cp);
553
564
  }
@@ -575,6 +586,11 @@ function isUnicodePropertyNameCharacter(cp) {
575
586
  function isUnicodePropertyValueCharacter(cp) {
576
587
  return isUnicodePropertyNameCharacter(cp) || isDecimalDigit(cp);
577
588
  }
589
+ function isRegularExpressionModifier(ch) {
590
+ return (ch === LATIN_SMALL_LETTER_I ||
591
+ ch === LATIN_SMALL_LETTER_M ||
592
+ ch === LATIN_SMALL_LETTER_S);
593
+ }
578
594
  class RegExpValidator {
579
595
  constructor(options) {
580
596
  this._reader = new Reader();
@@ -645,64 +661,8 @@ class RegExpValidator {
645
661
  }
646
662
  }
647
663
  validateFlagsInternal(source, start, end) {
648
- const existingFlags = new Set();
649
- let global = false;
650
- let ignoreCase = false;
651
- let multiline = false;
652
- let sticky = false;
653
- let unicode = false;
654
- let dotAll = false;
655
- let hasIndices = false;
656
- let unicodeSets = false;
657
- for (let i = start; i < end; ++i) {
658
- const flag = source.charCodeAt(i);
659
- if (existingFlags.has(flag)) {
660
- this.raise(`Duplicated flag '${source[i]}'`, { index: start });
661
- }
662
- existingFlags.add(flag);
663
- if (flag === LATIN_SMALL_LETTER_G) {
664
- global = true;
665
- }
666
- else if (flag === LATIN_SMALL_LETTER_I) {
667
- ignoreCase = true;
668
- }
669
- else if (flag === LATIN_SMALL_LETTER_M) {
670
- multiline = true;
671
- }
672
- else if (flag === LATIN_SMALL_LETTER_U &&
673
- this.ecmaVersion >= 2015) {
674
- unicode = true;
675
- }
676
- else if (flag === LATIN_SMALL_LETTER_Y &&
677
- this.ecmaVersion >= 2015) {
678
- sticky = true;
679
- }
680
- else if (flag === LATIN_SMALL_LETTER_S &&
681
- this.ecmaVersion >= 2018) {
682
- dotAll = true;
683
- }
684
- else if (flag === LATIN_SMALL_LETTER_D &&
685
- this.ecmaVersion >= 2022) {
686
- hasIndices = true;
687
- }
688
- else if (flag === LATIN_SMALL_LETTER_V &&
689
- this.ecmaVersion >= 2024) {
690
- unicodeSets = true;
691
- }
692
- else {
693
- this.raise(`Invalid flag '${source[i]}'`, { index: start });
694
- }
695
- }
696
- this.onRegExpFlags(start, end, {
697
- global,
698
- ignoreCase,
699
- multiline,
700
- unicode,
701
- sticky,
702
- dotAll,
703
- hasIndices,
704
- unicodeSets,
705
- });
664
+ const flags = this.parseFlags(source, start, end);
665
+ this.onRegExpFlags(start, end, flags);
706
666
  }
707
667
  _parseFlagsOptionToMode(uFlagOrFlags, sourceEnd) {
708
668
  let unicode = false;
@@ -797,6 +757,26 @@ class RegExpValidator {
797
757
  this._options.onGroupLeave(start, end);
798
758
  }
799
759
  }
760
+ onModifiersEnter(start) {
761
+ if (this._options.onModifiersEnter) {
762
+ this._options.onModifiersEnter(start);
763
+ }
764
+ }
765
+ onModifiersLeave(start, end) {
766
+ if (this._options.onModifiersLeave) {
767
+ this._options.onModifiersLeave(start, end);
768
+ }
769
+ }
770
+ onAddModifiers(start, end, flags) {
771
+ if (this._options.onAddModifiers) {
772
+ this._options.onAddModifiers(start, end, flags);
773
+ }
774
+ }
775
+ onRemoveModifiers(start, end, flags) {
776
+ if (this._options.onRemoveModifiers) {
777
+ this._options.onRemoveModifiers(start, end, flags);
778
+ }
779
+ }
800
780
  onCapturingGroupEnter(start, name) {
801
781
  if (this._options.onCapturingGroupEnter) {
802
782
  this._options.onCapturingGroupEnter(start, name);
@@ -1169,8 +1149,8 @@ class RegExpValidator {
1169
1149
  this.consumeDot() ||
1170
1150
  this.consumeReverseSolidusAtomEscape() ||
1171
1151
  Boolean(this.consumeCharacterClass()) ||
1172
- this.consumeUncapturingGroup() ||
1173
- this.consumeCapturingGroup());
1152
+ this.consumeCapturingGroup() ||
1153
+ this.consumeUncapturingGroup());
1174
1154
  }
1175
1155
  consumeDot() {
1176
1156
  if (this.eat(FULL_STOP)) {
@@ -1191,8 +1171,15 @@ class RegExpValidator {
1191
1171
  }
1192
1172
  consumeUncapturingGroup() {
1193
1173
  const start = this.index;
1194
- if (this.eat3(LEFT_PARENTHESIS, QUESTION_MARK, COLON)) {
1174
+ if (this.eat2(LEFT_PARENTHESIS, QUESTION_MARK)) {
1195
1175
  this.onGroupEnter(start);
1176
+ if (this.ecmaVersion >= 2025) {
1177
+ this.consumeModifiers();
1178
+ }
1179
+ if (!this.eat(COLON)) {
1180
+ this.rewind(start + 1);
1181
+ this.raise("Invalid group");
1182
+ }
1196
1183
  this.consumeDisjunction();
1197
1184
  if (!this.eat(RIGHT_PARENTHESIS)) {
1198
1185
  this.raise("Unterminated group");
@@ -1202,6 +1189,35 @@ class RegExpValidator {
1202
1189
  }
1203
1190
  return false;
1204
1191
  }
1192
+ consumeModifiers() {
1193
+ const start = this.index;
1194
+ const hasAddModifiers = this.eatModifiers();
1195
+ const addModifiersEnd = this.index;
1196
+ const hasHyphen = this.eat(HYPHEN_MINUS);
1197
+ if (!hasAddModifiers && !hasHyphen) {
1198
+ return false;
1199
+ }
1200
+ this.onModifiersEnter(start);
1201
+ const addModifiers = this.parseModifiers(start, addModifiersEnd);
1202
+ this.onAddModifiers(start, addModifiersEnd, addModifiers);
1203
+ if (hasHyphen) {
1204
+ const modifiersStart = this.index;
1205
+ if (!this.eatModifiers() &&
1206
+ !hasAddModifiers &&
1207
+ this.currentCodePoint === COLON) {
1208
+ this.raise("Invalid empty flags");
1209
+ }
1210
+ const modifiers = this.parseModifiers(modifiersStart, this.index);
1211
+ for (const [flagName] of Object.entries(modifiers).filter(([, enable]) => enable)) {
1212
+ if (addModifiers[flagName]) {
1213
+ this.raise(`Duplicated flag '${String.fromCodePoint(FLAG_PROP_TO_CODEPOINT[flagName])}'`);
1214
+ }
1215
+ }
1216
+ this.onRemoveModifiers(modifiersStart, this.index, modifiers);
1217
+ }
1218
+ this.onModifiersLeave(start, this.index);
1219
+ return true;
1220
+ }
1205
1221
  consumeCapturingGroup() {
1206
1222
  const start = this.index;
1207
1223
  if (this.eat(LEFT_PARENTHESIS)) {
@@ -1210,9 +1226,14 @@ class RegExpValidator {
1210
1226
  if (this.consumeGroupSpecifier()) {
1211
1227
  name = this._lastStrValue;
1212
1228
  }
1229
+ else if (this.currentCodePoint === QUESTION_MARK) {
1230
+ this.rewind(start);
1231
+ return false;
1232
+ }
1213
1233
  }
1214
1234
  else if (this.currentCodePoint === QUESTION_MARK) {
1215
- this.raise("Invalid group");
1235
+ this.rewind(start);
1236
+ return false;
1216
1237
  }
1217
1238
  this.onCapturingGroupEnter(start, name);
1218
1239
  this.consumeDisjunction();
@@ -1229,8 +1250,8 @@ class RegExpValidator {
1229
1250
  this.consumeReverseSolidusAtomEscape() ||
1230
1251
  this.consumeReverseSolidusFollowedByC() ||
1231
1252
  Boolean(this.consumeCharacterClass()) ||
1232
- this.consumeUncapturingGroup() ||
1233
1253
  this.consumeCapturingGroup() ||
1254
+ this.consumeUncapturingGroup() ||
1234
1255
  this.consumeInvalidBracedQuantifier() ||
1235
1256
  this.consumeExtendedPatternCharacter());
1236
1257
  }
@@ -1283,6 +1304,7 @@ class RegExpValidator {
1283
1304
  return false;
1284
1305
  }
1285
1306
  consumeGroupSpecifier() {
1307
+ const start = this.index;
1286
1308
  if (this.eat(QUESTION_MARK)) {
1287
1309
  if (this.eatGroupName()) {
1288
1310
  if (!this._groupSpecifiers.hasInScope(this._lastStrValue)) {
@@ -1291,7 +1313,7 @@ class RegExpValidator {
1291
1313
  }
1292
1314
  this.raise("Duplicate capture group name");
1293
1315
  }
1294
- this.raise("Invalid group");
1316
+ this.rewind(start);
1295
1317
  }
1296
1318
  return false;
1297
1319
  }
@@ -2039,6 +2061,63 @@ class RegExpValidator {
2039
2061
  }
2040
2062
  return true;
2041
2063
  }
2064
+ eatModifiers() {
2065
+ let ate = false;
2066
+ while (isRegularExpressionModifier(this.currentCodePoint)) {
2067
+ this.advance();
2068
+ ate = true;
2069
+ }
2070
+ return ate;
2071
+ }
2072
+ parseModifiers(start, end) {
2073
+ const { ignoreCase, multiline, dotAll } = this.parseFlags(this._reader.source, start, end);
2074
+ return { ignoreCase, multiline, dotAll };
2075
+ }
2076
+ parseFlags(source, start, end) {
2077
+ const flags = {
2078
+ global: false,
2079
+ ignoreCase: false,
2080
+ multiline: false,
2081
+ unicode: false,
2082
+ sticky: false,
2083
+ dotAll: false,
2084
+ hasIndices: false,
2085
+ unicodeSets: false,
2086
+ };
2087
+ const validFlags = new Set();
2088
+ validFlags.add(LATIN_SMALL_LETTER_G);
2089
+ validFlags.add(LATIN_SMALL_LETTER_I);
2090
+ validFlags.add(LATIN_SMALL_LETTER_M);
2091
+ if (this.ecmaVersion >= 2015) {
2092
+ validFlags.add(LATIN_SMALL_LETTER_U);
2093
+ validFlags.add(LATIN_SMALL_LETTER_Y);
2094
+ if (this.ecmaVersion >= 2018) {
2095
+ validFlags.add(LATIN_SMALL_LETTER_S);
2096
+ if (this.ecmaVersion >= 2021) {
2097
+ validFlags.add(LATIN_SMALL_LETTER_D);
2098
+ if (this.ecmaVersion >= 2024) {
2099
+ validFlags.add(LATIN_SMALL_LETTER_V);
2100
+ }
2101
+ }
2102
+ }
2103
+ }
2104
+ for (let i = start; i < end; ++i) {
2105
+ const flag = source.charCodeAt(i);
2106
+ if (validFlags.has(flag)) {
2107
+ const prop = FLAG_CODEPOINT_TO_PROP[flag];
2108
+ if (flags[prop]) {
2109
+ this.raise(`Duplicated flag '${source[i]}'`, {
2110
+ index: start,
2111
+ });
2112
+ }
2113
+ flags[prop] = true;
2114
+ }
2115
+ else {
2116
+ this.raise(`Invalid flag '${source[i]}'`, { index: start });
2117
+ }
2118
+ }
2119
+ return flags;
2120
+ }
2042
2121
  }
2043
2122
 
2044
2123
  const DUMMY_PATTERN = {};
@@ -2158,14 +2237,16 @@ class RegExpParserState {
2158
2237
  if (parent.type !== "Alternative") {
2159
2238
  throw new Error("UnknownError");
2160
2239
  }
2161
- this._node = {
2240
+ const group = {
2162
2241
  type: "Group",
2163
2242
  parent,
2164
2243
  start,
2165
2244
  end: start,
2166
2245
  raw: "",
2246
+ modifiers: null,
2167
2247
  alternatives: [],
2168
2248
  };
2249
+ this._node = group;
2169
2250
  parent.elements.push(this._node);
2170
2251
  }
2171
2252
  onGroupLeave(start, end) {
@@ -2177,6 +2258,63 @@ class RegExpParserState {
2177
2258
  node.raw = this.source.slice(start, end);
2178
2259
  this._node = node.parent;
2179
2260
  }
2261
+ onModifiersEnter(start) {
2262
+ const parent = this._node;
2263
+ if (parent.type !== "Group") {
2264
+ throw new Error("UnknownError");
2265
+ }
2266
+ this._node = {
2267
+ type: "Modifiers",
2268
+ parent,
2269
+ start,
2270
+ end: start,
2271
+ raw: "",
2272
+ add: null,
2273
+ remove: null,
2274
+ };
2275
+ parent.modifiers = this._node;
2276
+ }
2277
+ onModifiersLeave(start, end) {
2278
+ const node = this._node;
2279
+ if (node.type !== "Modifiers" || node.parent.type !== "Group") {
2280
+ throw new Error("UnknownError");
2281
+ }
2282
+ node.end = end;
2283
+ node.raw = this.source.slice(start, end);
2284
+ this._node = node.parent;
2285
+ }
2286
+ onAddModifiers(start, end, { ignoreCase, multiline, dotAll, }) {
2287
+ const parent = this._node;
2288
+ if (parent.type !== "Modifiers") {
2289
+ throw new Error("UnknownError");
2290
+ }
2291
+ parent.add = {
2292
+ type: "ModifierFlags",
2293
+ parent,
2294
+ start,
2295
+ end,
2296
+ raw: this.source.slice(start, end),
2297
+ ignoreCase,
2298
+ multiline,
2299
+ dotAll,
2300
+ };
2301
+ }
2302
+ onRemoveModifiers(start, end, { ignoreCase, multiline, dotAll, }) {
2303
+ const parent = this._node;
2304
+ if (parent.type !== "Modifiers") {
2305
+ throw new Error("UnknownError");
2306
+ }
2307
+ parent.remove = {
2308
+ type: "ModifierFlags",
2309
+ parent,
2310
+ start,
2311
+ end,
2312
+ raw: this.source.slice(start, end),
2313
+ ignoreCase,
2314
+ multiline,
2315
+ dotAll,
2316
+ };
2317
+ }
2180
2318
  onCapturingGroupEnter(start, name) {
2181
2319
  const parent = this._node;
2182
2320
  if (parent.type !== "Alternative") {
@@ -2662,6 +2800,12 @@ class RegExpVisitor {
2662
2800
  case "Group":
2663
2801
  this.visitGroup(node);
2664
2802
  break;
2803
+ case "Modifiers":
2804
+ this.visitModifiers(node);
2805
+ break;
2806
+ case "ModifierFlags":
2807
+ this.visitModifierFlags(node);
2808
+ break;
2665
2809
  case "Pattern":
2666
2810
  this.visitPattern(node);
2667
2811
  break;
@@ -2800,11 +2944,36 @@ class RegExpVisitor {
2800
2944
  if (this._handlers.onGroupEnter) {
2801
2945
  this._handlers.onGroupEnter(node);
2802
2946
  }
2947
+ if (node.modifiers) {
2948
+ this.visit(node.modifiers);
2949
+ }
2803
2950
  node.alternatives.forEach(this.visit, this);
2804
2951
  if (this._handlers.onGroupLeave) {
2805
2952
  this._handlers.onGroupLeave(node);
2806
2953
  }
2807
2954
  }
2955
+ visitModifiers(node) {
2956
+ if (this._handlers.onModifiersEnter) {
2957
+ this._handlers.onModifiersEnter(node);
2958
+ }
2959
+ if (node.add) {
2960
+ this.visit(node.add);
2961
+ }
2962
+ if (node.remove) {
2963
+ this.visit(node.remove);
2964
+ }
2965
+ if (this._handlers.onModifiersLeave) {
2966
+ this._handlers.onModifiersLeave(node);
2967
+ }
2968
+ }
2969
+ visitModifierFlags(node) {
2970
+ if (this._handlers.onModifierFlagsEnter) {
2971
+ this._handlers.onModifierFlagsEnter(node);
2972
+ }
2973
+ if (this._handlers.onModifierFlagsLeave) {
2974
+ this._handlers.onModifierFlagsLeave(node);
2975
+ }
2976
+ }
2808
2977
  visitPattern(node) {
2809
2978
  if (this._handlers.onPatternEnter) {
2810
2979
  this._handlers.onPatternEnter(node);