|
1 | 1 | import type * as ESTree from "estree"
|
2 | 2 | import type { RuleListener, RuleModule, PartialRuleModule } from "../types"
|
3 | 3 | import type { RegExpVisitor } from "regexpp/visitor"
|
4 |
| -import type { Node as RegExpNode, Quantifier } from "regexpp/ast" |
| 4 | +import type { |
| 5 | + Alternative, |
| 6 | + AnyCharacterSet, |
| 7 | + Assertion, |
| 8 | + Backreference, |
| 9 | + CapturingGroup, |
| 10 | + CharacterClass, |
| 11 | + Group, |
| 12 | + Node as RegExpNode, |
| 13 | + Quantifier, |
| 14 | +} from "regexpp/ast" |
5 | 15 | import { RegExpParser, visitRegExpAST } from "regexpp"
|
6 | 16 | import {
|
7 | 17 | CALL,
|
@@ -345,3 +355,112 @@ export function getQuantifierOffsets(qNode: Quantifier): [number, number] {
|
345 | 355 | const endOffset = qNode.raw.length - (qNode.greedy ? 0 : 1)
|
346 | 356 | return [startOffset, endOffset]
|
347 | 357 | }
|
| 358 | + |
| 359 | +/* eslint-disable complexity -- X( */ |
| 360 | +/** |
| 361 | + * Check the siblings to see if the regex doesn't change when unwrapped. |
| 362 | + */ |
| 363 | +export function canUnwrapped( |
| 364 | + /* eslint-enable complexity -- X( */ |
| 365 | + node: |
| 366 | + | CharacterClass |
| 367 | + | Group |
| 368 | + | CapturingGroup |
| 369 | + | Assertion |
| 370 | + | Quantifier |
| 371 | + | AnyCharacterSet |
| 372 | + | Backreference, |
| 373 | + text: string, |
| 374 | +): boolean { |
| 375 | + const { alternative, index } = getAlternativeAndIndex() |
| 376 | + if (index === 0) { |
| 377 | + return true |
| 378 | + } |
| 379 | + if (/^\d+$/u.test(text)) { |
| 380 | + let prevIndex = index - 1 |
| 381 | + let prev = alternative.elements[prevIndex] |
| 382 | + if (prev.type === "Backreference") { |
| 383 | + // e.g. /()\1[0]/ -> /()\10/ |
| 384 | + return false |
| 385 | + } |
| 386 | + |
| 387 | + while ( |
| 388 | + prev.type === "Character" && |
| 389 | + /^\d+$/u.test(prev.raw) && |
| 390 | + prevIndex > 0 |
| 391 | + ) { |
| 392 | + prevIndex-- |
| 393 | + prev = alternative.elements[prevIndex] |
| 394 | + } |
| 395 | + if (prev.type === "Character" && prev.raw === "{") { |
| 396 | + // e.g. /a{[0]}/ -> /a{0}/ |
| 397 | + return false |
| 398 | + } |
| 399 | + } |
| 400 | + if (/^[0-7]+$/u.test(text)) { |
| 401 | + const prev = alternative.elements[index - 1] |
| 402 | + if (prev.type === "Character" && /^\\[0-7]+$/u.test(prev.raw)) { |
| 403 | + // e.g. /\0[1]/ -> /\01/ |
| 404 | + return false |
| 405 | + } |
| 406 | + } |
| 407 | + if (/^[\da-f]+$/iu.test(text)) { |
| 408 | + let prevIndex = index - 1 |
| 409 | + let prev = alternative.elements[prevIndex] |
| 410 | + while ( |
| 411 | + prev.type === "Character" && |
| 412 | + /^[\da-f]+$/iu.test(prev.raw) && |
| 413 | + prevIndex > 0 |
| 414 | + ) { |
| 415 | + prevIndex-- |
| 416 | + prev = alternative.elements[prevIndex] |
| 417 | + } |
| 418 | + if ( |
| 419 | + prev.type === "Character" && |
| 420 | + (prev.raw === "\\x" || prev.raw === "\\u") |
| 421 | + ) { |
| 422 | + // e.g. /\xF[F]/ -> /\xFF/ |
| 423 | + // e.g. /\uF[F]FF/ -> /\xFFFF/ |
| 424 | + return false |
| 425 | + } |
| 426 | + } |
| 427 | + if (/^[a-z]+$/iu.test(text)) { |
| 428 | + if (index > 1) { |
| 429 | + const prev = alternative.elements[index - 1] |
| 430 | + if (prev.type === "Character" && prev.raw === "c") { |
| 431 | + const prev2 = alternative.elements[index - 2] |
| 432 | + if (prev2.type === "Character" && prev2.raw === "\\") { |
| 433 | + // e.g. /\c[M]/ -> /\cM/ |
| 434 | + return false |
| 435 | + } |
| 436 | + } |
| 437 | + } |
| 438 | + } |
| 439 | + |
| 440 | + return true |
| 441 | + |
| 442 | + /** Get alternative and element index */ |
| 443 | + function getAlternativeAndIndex() { |
| 444 | + const parent = node.parent |
| 445 | + let target: |
| 446 | + | CharacterClass |
| 447 | + | Group |
| 448 | + | CapturingGroup |
| 449 | + | Assertion |
| 450 | + | Quantifier |
| 451 | + | AnyCharacterSet |
| 452 | + | Backreference, |
| 453 | + alt: Alternative |
| 454 | + if (parent.type === "Quantifier") { |
| 455 | + alt = parent.parent |
| 456 | + target = parent |
| 457 | + } else { |
| 458 | + alt = parent |
| 459 | + target = node |
| 460 | + } |
| 461 | + return { |
| 462 | + alternative: alt, |
| 463 | + index: alt.elements.indexOf(target), |
| 464 | + } |
| 465 | + } |
| 466 | +} |
0 commit comments