Website Structure

This commit is contained in:
supalerk-ar66 2026-01-13 10:46:40 +07:00
parent 62812f2090
commit 71f0676a62
22365 changed files with 4265753 additions and 791 deletions

View file

@ -0,0 +1,4 @@
import type { RegExpLiteral, Pattern } from "@eslint-community/regexpp/ast";
import type { Rule } from "eslint";
import type { Expression } from "estree";
export declare function getRegExpNodeFromExpression(node: Expression, context: Rule.RuleContext): RegExpLiteral | Pattern | null;

View file

@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getRegExpNodeFromExpression = getRegExpNodeFromExpression;
const regexpp_1 = require("@eslint-community/regexpp");
const ast_utils_1 = require("../ast-utils");
const parser = new regexpp_1.RegExpParser();
function getRegExpNodeFromExpression(node, context) {
if (node.type === "Literal") {
if ("regex" in node && node.regex) {
try {
return parser.parsePattern(node.regex.pattern, 0, node.regex.pattern.length, {
unicode: node.regex.flags.includes("u"),
unicodeSets: node.regex.flags.includes("v"),
});
}
catch (_a) {
return null;
}
}
return null;
}
const evaluated = (0, ast_utils_1.getStaticValue)(context, node);
if (!evaluated || !(evaluated.value instanceof RegExp)) {
return null;
}
try {
return (0, regexpp_1.parseRegExpLiteral)(evaluated.value);
}
catch (_b) {
return null;
}
}

View file

@ -0,0 +1,5 @@
import type { Alternative, CharacterClassElement, Element, Pattern, StringAlternative } from "@eslint-community/regexpp/ast";
import type { ReadonlyFlags } from "regexp-ast-analysis";
export declare const getIgnoreCaseFlags: (key: ReadonlyFlags) => ReadonlyFlags;
export declare const getCaseSensitiveFlags: (key: ReadonlyFlags) => ReadonlyFlags;
export declare function isCaseVariant(element: Element | CharacterClassElement | StringAlternative | Alternative | Pattern, flags: ReadonlyFlags, wholeCharacterClass?: boolean): boolean;

View file

@ -0,0 +1,94 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCaseSensitiveFlags = exports.getIgnoreCaseFlags = void 0;
exports.isCaseVariant = isCaseVariant;
const regexp_ast_analysis_1 = require("regexp-ast-analysis");
const util_1 = require("../util");
exports.getIgnoreCaseFlags = (0, util_1.cachedFn)((flags) => {
return flags.ignoreCase
? flags
: (0, regexp_ast_analysis_1.toCache)({ ...flags, ignoreCase: true });
});
exports.getCaseSensitiveFlags = (0, util_1.cachedFn)((flags) => {
return flags.ignoreCase === false
? flags
: (0, regexp_ast_analysis_1.toCache)({ ...flags, ignoreCase: false });
});
function isCaseVariant(element, flags, wholeCharacterClass = true) {
const unicodeLike = Boolean(flags.unicode || flags.unicodeSets);
const iSet = (0, exports.getIgnoreCaseFlags)(flags);
const iUnset = (0, exports.getCaseSensitiveFlags)(flags);
function ccElementIsCaseVariant(e) {
switch (e.type) {
case "Character":
return (0, regexp_ast_analysis_1.toCharSet)(e, iSet).size !== 1;
case "CharacterClassRange":
return !(0, regexp_ast_analysis_1.toCharSet)(e, iSet).equals((0, regexp_ast_analysis_1.toCharSet)(e, iUnset));
case "CharacterSet":
switch (e.kind) {
case "word":
return unicodeLike;
case "property":
return !(0, regexp_ast_analysis_1.toUnicodeSet)(e, iSet).equals((0, regexp_ast_analysis_1.toUnicodeSet)(e, iUnset));
default:
return false;
}
case "CharacterClass":
if (!wholeCharacterClass) {
return e.elements.some(ccElementIsCaseVariant);
}
return !(0, regexp_ast_analysis_1.toUnicodeSet)(e, iSet).equals((0, regexp_ast_analysis_1.toUnicodeSet)(e, iUnset));
case "ExpressionCharacterClass":
return ccElementIsCaseVariant(e.expression);
case "ClassIntersection":
case "ClassSubtraction":
return !(0, regexp_ast_analysis_1.toUnicodeSet)(e, iSet).equals((0, regexp_ast_analysis_1.toUnicodeSet)(e, iUnset));
case "ClassStringDisjunction":
if (!wholeCharacterClass) {
return e.alternatives.some(ccElementIsCaseVariant);
}
return !(0, regexp_ast_analysis_1.toUnicodeSet)(e, iSet).equals((0, regexp_ast_analysis_1.toUnicodeSet)(e, iUnset));
case "StringAlternative":
return e.elements.some(ccElementIsCaseVariant);
default:
return (0, util_1.assertNever)(e);
}
}
return (0, regexp_ast_analysis_1.hasSomeDescendant)(element, (d) => {
switch (d.type) {
case "Assertion":
return unicodeLike && d.kind === "word";
case "Backreference": {
const outside = getReferencedGroupsFromBackreference(d).filter((resolved) => !(0, regexp_ast_analysis_1.hasSomeDescendant)(element, resolved));
if (outside.length === 0) {
return false;
}
return (!(0, regexp_ast_analysis_1.isEmptyBackreference)(d, flags) &&
outside.some((resolved) => isCaseVariant(resolved, flags)));
}
case "Character":
case "CharacterClassRange":
case "CharacterSet":
case "CharacterClass":
case "ExpressionCharacterClass":
case "ClassIntersection":
case "ClassSubtraction":
case "ClassStringDisjunction":
case "StringAlternative":
return ccElementIsCaseVariant(d);
default:
return false;
}
}, (d) => {
return (d.type !== "CharacterClass" &&
d.type !== "CharacterClassRange" &&
d.type !== "ExpressionCharacterClass" &&
d.type !== "ClassStringDisjunction");
});
}
function getReferencedGroupsFromBackreference(backRef) {
return [backRef.resolved].flat().filter((group) => {
const closestAncestor = (0, regexp_ast_analysis_1.getClosestAncestor)(backRef, group);
return (closestAncestor !== group && closestAncestor.type === "Alternative");
});
}

View file

@ -0,0 +1,11 @@
import type { Alternative, CapturingGroup, Element, Node, Pattern, RegExpLiteral } from "@eslint-community/regexpp/ast";
import type { FirstConsumedChar, MatchingDirection, ReadonlyFlags } from "regexp-ast-analysis";
export type ShortCircuit = (aNode: Node, bNode: Node) => boolean | null;
export declare function getFirstConsumedCharPlusAfter(element: Element | Alternative, direction: MatchingDirection, flags: ReadonlyFlags): FirstConsumedChar;
export interface CapturingGroups {
groups: CapturingGroup[];
names: Set<string>;
count: number;
}
export declare function extractCaptures(pattern: RegExpLiteral | Pattern): CapturingGroups;
export declare function hasCapturingGroup(node: Node): boolean;

View file

@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFirstConsumedCharPlusAfter = getFirstConsumedCharPlusAfter;
exports.extractCaptures = extractCaptures;
exports.hasCapturingGroup = hasCapturingGroup;
const regexpp_1 = require("@eslint-community/regexpp");
const regexp_ast_analysis_1 = require("regexp-ast-analysis");
function getFirstConsumedCharPlusAfter(element, direction, flags) {
const consumed = (0, regexp_ast_analysis_1.getFirstConsumedChar)(element, direction, flags);
if (!consumed.empty) {
return consumed;
}
return regexp_ast_analysis_1.FirstConsumedChars.concat([consumed, (0, regexp_ast_analysis_1.getFirstConsumedCharAfter)(element, direction, flags)], flags);
}
function extractCaptures(pattern) {
const groups = [];
(0, regexpp_1.visitRegExpAST)(pattern, {
onCapturingGroupEnter(group) {
groups.push(group);
},
});
groups.sort((a, b) => a.start - b.start);
const names = new Set();
for (const group of groups) {
if (group.name !== null) {
names.add(group.name);
}
}
return { groups, names, count: groups.length };
}
function hasCapturingGroup(node) {
return (0, regexp_ast_analysis_1.hasSomeDescendant)(node, (d) => d.type === "CapturingGroup");
}

View file

@ -0,0 +1,7 @@
export * from "./common";
export * from "./ast";
export * from "./is-covered";
export * from "./is-equals";
export * from "./quantifier";
export * from "./case-variation";
export * from "./simplify-quantifier";

View file

@ -0,0 +1,23 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./common"), exports);
__exportStar(require("./ast"), exports);
__exportStar(require("./is-covered"), exports);
__exportStar(require("./is-equals"), exports);
__exportStar(require("./quantifier"), exports);
__exportStar(require("./case-variation"), exports);
__exportStar(require("./simplify-quantifier"), exports);

View file

@ -0,0 +1,8 @@
import type { Node } from "@eslint-community/regexpp/ast";
import type { ReadonlyFlags } from "regexp-ast-analysis";
type Options = {
flags: ReadonlyFlags;
canOmitRight: boolean;
};
export declare function isCoveredNode(left: Node, right: Node, options: Options): boolean;
export {};

View file

@ -0,0 +1,370 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isCoveredNode = isCoveredNode;
const regexp_ast_analysis_1 = require("regexp-ast-analysis");
const util_1 = require("../util");
const is_equals_1 = require("./is-equals");
class NormalizedOther {
static fromNode(node) {
return new NormalizedOther(node);
}
constructor(node) {
this.type = "NormalizedOther";
this.node = node;
}
}
class NormalizedCharacter {
static fromElement(element, options) {
return new NormalizedCharacter((0, regexp_ast_analysis_1.toCharSet)(element, options.flags));
}
static fromChars(charSet) {
return new NormalizedCharacter(charSet);
}
constructor(charSet) {
this.type = "NormalizedCharacter";
this.charSet = charSet;
}
}
class NormalizedAlternative {
static fromAlternative(node, options) {
const normalizeElements = [
...NormalizedAlternative.normalizedElements(function* () {
for (const element of node.elements) {
const normal = normalizeNode(element, options);
if (normal.type === "NormalizedAlternative") {
yield* normal.elements;
}
else {
yield normal;
}
}
}),
];
if (normalizeElements.length === 1) {
return normalizeElements[0];
}
return new NormalizedAlternative(normalizeElements, node);
}
static fromQuantifier(node, options) {
const normalizeElements = [
...NormalizedAlternative.normalizedElements(function* () {
const normalizeElement = normalizeNode(node.element, options);
for (let index = 0; index < node.min; index++) {
yield normalizeElement;
}
}),
];
if (normalizeElements.length === 1) {
return normalizeElements[0];
}
return new NormalizedAlternative(normalizeElements, node);
}
static fromElements(elements, node) {
const normalizeElements = [
...NormalizedAlternative.normalizedElements(function* () {
yield* elements;
}),
];
return new NormalizedAlternative(normalizeElements, node);
}
static *normalizedElements(generate) {
for (const node of generate()) {
if (node.type === "NormalizedAlternative") {
yield* node.elements;
}
else {
yield node;
}
}
}
constructor(elements, node) {
this.type = "NormalizedAlternative";
this.raw = node.raw;
this.elements = elements;
}
}
class NormalizedDisjunctions {
static fromNode(node, options) {
if (node.alternatives.length === 1) {
return NormalizedAlternative.fromAlternative(node.alternatives[0], options);
}
return new NormalizedDisjunctions(node, () => {
return node.alternatives.map((alt) => {
const n = normalizeNode(alt, options);
if (n.type === "NormalizedAlternative") {
return n;
}
return NormalizedAlternative.fromElements([n], alt);
});
});
}
static fromAlternatives(alternatives, node) {
return new NormalizedDisjunctions(node, () => alternatives);
}
constructor(node, getAlternatives) {
this.type = "NormalizedDisjunctions";
this.raw = node.raw;
this.getAlternatives = getAlternatives;
}
get alternatives() {
if (!this.normalizedAlternatives) {
this.normalizedAlternatives = this.getAlternatives();
}
return this.normalizedAlternatives;
}
}
class NormalizedLookaroundAssertion {
static fromNode(node, options) {
return new NormalizedLookaroundAssertion(node, options);
}
constructor(node, options) {
this.type = "NormalizedLookaroundAssertion";
this.raw = node.raw;
this.node = node;
this.options = options;
}
get alternatives() {
if (this.normalizedAlternatives) {
return this.normalizedAlternatives;
}
this.normalizedAlternatives = [];
for (const alt of this.node.alternatives) {
const node = normalizeNode(alt, this.options);
if (node.type === "NormalizedAlternative") {
this.normalizedAlternatives.push(node);
}
else {
this.normalizedAlternatives.push(NormalizedAlternative.fromElements([node], alt));
}
}
return this.normalizedAlternatives;
}
get kind() {
return this.node.kind;
}
get negate() {
return this.node.negate;
}
}
class NormalizedOptional {
static fromQuantifier(node, options) {
let alt = null;
if (node.min > 0) {
alt = NormalizedAlternative.fromQuantifier(node, options);
}
const max = node.max - node.min;
if (max > 0) {
const optional = new NormalizedOptional(node, options, max);
if (alt) {
if (alt.type === "NormalizedAlternative") {
return NormalizedAlternative.fromElements([...alt.elements, optional], node);
}
return NormalizedAlternative.fromElements([alt, optional], node);
}
return optional;
}
if (alt) {
return alt;
}
return NormalizedOther.fromNode(node);
}
constructor(node, options, max) {
this.type = "NormalizedOptional";
this.raw = node.raw;
this.max = max;
this.node = node;
this.options = options;
}
get element() {
var _a;
return ((_a = this.normalizedElement) !== null && _a !== void 0 ? _a : (this.normalizedElement = normalizeNode(this.node.element, this.options)));
}
decrementMax(dec = 1) {
if (this.max <= dec) {
return null;
}
if (this.max === Infinity) {
return this;
}
const opt = new NormalizedOptional(this.node, this.options, this.max - dec);
opt.normalizedElement = this.normalizedElement;
return opt;
}
}
function isCoveredNode(left, right, options) {
const leftNode = normalizeNode(left, options);
const rightNode = normalizeNode(right, options);
return isCoveredForNormalizedNode(leftNode, rightNode, options);
}
function isCoveredForNormalizedNode(left, right, options) {
if (right.type === "NormalizedDisjunctions") {
return right.alternatives.every((r) => isCoveredForNormalizedNode(left, r, options));
}
if (left.type === "NormalizedDisjunctions") {
return isCoveredAnyNode(left.alternatives, right, options);
}
if (left.type === "NormalizedAlternative") {
if (right.type === "NormalizedAlternative") {
return isCoveredAltNodes(left.elements, right.elements, options);
}
return isCoveredAltNodes(left.elements, [right], options);
}
else if (right.type === "NormalizedAlternative") {
return isCoveredAltNodes([left], right.elements, options);
}
if (left.type === "NormalizedOptional" ||
right.type === "NormalizedOptional") {
return isCoveredAltNodes([left], [right], options);
}
if (left.type === "NormalizedOther" || right.type === "NormalizedOther") {
if (left.type === "NormalizedOther" &&
right.type === "NormalizedOther") {
return (0, is_equals_1.isEqualNodes)(left.node, right.node, options.flags);
}
return false;
}
if (left.type === "NormalizedLookaroundAssertion" ||
right.type === "NormalizedLookaroundAssertion") {
if (left.type === "NormalizedLookaroundAssertion" &&
right.type === "NormalizedLookaroundAssertion") {
if (left.kind === right.kind && !left.negate && !right.negate) {
return right.alternatives.every((r) => isCoveredAnyNode(left.alternatives, r, options));
}
return (0, is_equals_1.isEqualNodes)(left.node, right.node, options.flags);
}
return false;
}
if (right.type === "NormalizedCharacter") {
return right.charSet.isSubsetOf(left.charSet);
}
return false;
}
const cacheNormalizeNode = new WeakMap();
function normalizeNode(node, options) {
let n = cacheNormalizeNode.get(node);
if (n) {
return n;
}
n = normalizeNodeWithoutCache(node, options);
cacheNormalizeNode.set(node, n);
return n;
}
function normalizeNodeWithoutCache(node, options) {
switch (node.type) {
case "CharacterSet":
case "CharacterClass":
case "Character":
case "CharacterClassRange":
case "ExpressionCharacterClass":
case "ClassIntersection":
case "ClassSubtraction":
case "ClassStringDisjunction":
case "StringAlternative": {
const set = (0, regexp_ast_analysis_1.toUnicodeSet)(node, options.flags);
if (set.accept.isEmpty) {
return NormalizedCharacter.fromChars(set.chars);
}
const alternatives = set.wordSets.map((wordSet) => {
return NormalizedAlternative.fromElements(wordSet.map(NormalizedCharacter.fromChars), node);
});
return NormalizedDisjunctions.fromAlternatives(alternatives, node);
}
case "Alternative":
return NormalizedAlternative.fromAlternative(node, options);
case "Quantifier":
return NormalizedOptional.fromQuantifier(node, options);
case "CapturingGroup":
case "Group":
case "Pattern":
return NormalizedDisjunctions.fromNode(node, options);
case "Assertion":
if (node.kind === "lookahead" || node.kind === "lookbehind") {
return NormalizedLookaroundAssertion.fromNode(node, options);
}
return NormalizedOther.fromNode(node);
case "RegExpLiteral":
return normalizeNode(node.pattern, options);
case "Backreference":
case "Flags":
case "ModifierFlags":
case "Modifiers":
return NormalizedOther.fromNode(node);
default:
return (0, util_1.assertNever)(node);
}
}
function isCoveredAnyNode(left, right, options) {
for (const e of left) {
if (isCoveredForNormalizedNode(e, right, options)) {
return true;
}
}
return false;
}
function isCoveredAltNodes(leftNodes, rightNodes, options) {
const left = options.canOmitRight ? omitEnds(leftNodes) : [...leftNodes];
const right = options.canOmitRight ? omitEnds(rightNodes) : [...rightNodes];
while (left.length && right.length) {
const le = left.shift();
const re = right.shift();
if (re.type === "NormalizedOptional") {
if (le.type === "NormalizedOptional") {
if (!isCoveredForNormalizedNode(le.element, re.element, options)) {
return false;
}
const decrementLe = le.decrementMax(re.max);
if (decrementLe) {
return isCoveredAltNodes([decrementLe, ...left], right, options);
}
const decrementRe = re.decrementMax(le.max);
if (decrementRe) {
return isCoveredAltNodes(left, [decrementRe, ...right], options);
}
}
else {
if (!isCoveredForNormalizedNode(le, re.element, options)) {
return false;
}
if (!isCoveredAltNodes([le, ...left], right, options)) {
return false;
}
const decrementRe = re.decrementMax();
if (decrementRe) {
return isCoveredAltNodes(left, [decrementRe, ...right], options);
}
}
}
else if (le.type === "NormalizedOptional") {
if (isCoveredAltNodes(left, [re, ...right], options)) {
return true;
}
if (!isCoveredForNormalizedNode(le.element, re, options)) {
return false;
}
const decrementLe = le.decrementMax();
if (decrementLe) {
if (isCoveredAltNodes([decrementLe, ...left], right, options)) {
return true;
}
}
}
else if (!isCoveredForNormalizedNode(le, re, options)) {
return false;
}
}
if (!options.canOmitRight) {
if (right.length) {
return false;
}
}
return !left.length;
}
function omitEnds(nodes) {
for (let index = nodes.length - 1; index >= 0; index--) {
const node = nodes[index];
if (node.type !== "NormalizedOptional") {
return nodes.slice(0, index + 1);
}
}
return [];
}

View file

@ -0,0 +1,4 @@
import type { Node } from "@eslint-community/regexpp/ast";
import type { ReadonlyFlags } from "regexp-ast-analysis";
import type { ShortCircuit } from "./common";
export declare function isEqualNodes<N extends Node>(a: N, b: N, flags: ReadonlyFlags, shortCircuit?: ShortCircuit): boolean;

View file

@ -0,0 +1,180 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isEqualNodes = isEqualNodes;
const regexp_ast_analysis_1 = require("regexp-ast-analysis");
function isEqualChar(a, b, flags) {
if (a.type === "Character") {
if (b.type === "Character") {
if (a.value === b.value) {
return true;
}
}
else if (b.type === "CharacterSet") {
return false;
}
}
else if (a.type === "CharacterSet") {
if (b.type === "Character") {
return false;
}
else if (b.type === "CharacterSet") {
return a.raw === b.raw;
}
}
else if (a.type === "CharacterClassRange") {
if (b.type === "CharacterClassRange") {
return a.min.value === b.min.value && a.max.value === b.max.value;
}
}
if (a.raw === b.raw) {
return true;
}
return (0, regexp_ast_analysis_1.toUnicodeSet)(a, flags).equals((0, regexp_ast_analysis_1.toUnicodeSet)(b, flags));
}
const EQUALS_CHECKER = {
Alternative(a, b, flags, shortCircuit) {
return isEqualConcatenation(a.elements, b.elements, flags, shortCircuit);
},
Assertion(a, b, flags, shortCircuit) {
if (a.kind === "start" || a.kind === "end") {
return a.kind === b.kind;
}
if (a.kind === "word") {
return b.kind === "word" && a.negate === b.negate;
}
if (a.kind === "lookahead" || a.kind === "lookbehind") {
if (b.kind === a.kind && a.negate === b.negate) {
return isEqualSet(a.alternatives, b.alternatives, flags, shortCircuit);
}
return false;
}
return false;
},
Backreference(a, b) {
return a.ref === b.ref;
},
CapturingGroup(a, b, flags, shortCircuit) {
return (a.name === b.name &&
isEqualSet(a.alternatives, b.alternatives, flags, shortCircuit));
},
Character(a, b, flags) {
return isEqualChar(a, b, flags);
},
CharacterClass(a, b, flags) {
return isEqualChar(a, b, flags);
},
CharacterClassRange(a, b, flags) {
return isEqualChar(a, b, flags);
},
CharacterSet(a, b, flags) {
return isEqualChar(a, b, flags);
},
ClassIntersection(a, b, flags, shortCircuit) {
return isEqualSet([a.left, a.right], [b.left, b.right], flags, shortCircuit);
},
ClassStringDisjunction(a, b, flags, shortCircuit) {
return isEqualSet(a.alternatives, b.alternatives, flags, shortCircuit);
},
ClassSubtraction(a, b, flags, shortCircuit) {
return (isEqualNodes(a.left, b.left, flags, shortCircuit) &&
isEqualNodes(a.right, b.right, flags, shortCircuit));
},
ExpressionCharacterClass(a, b, flags) {
return (a.negate === b.negate &&
isEqualNodes(a.expression, b.expression, flags));
},
Flags(a, b) {
return (a.dotAll === b.dotAll &&
a.global === b.global &&
a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline &&
a.hasIndices === b.hasIndices &&
a.sticky === b.sticky &&
a.unicode === b.unicode &&
a.unicodeSets === b.unicodeSets);
},
Group(a, b, flags, shortCircuit) {
return isEqualSet(a.alternatives, b.alternatives, flags, shortCircuit);
},
ModifierFlags(a, b) {
return (a.dotAll === b.dotAll &&
a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline);
},
Modifiers(a, b, flags, shortCircuit) {
return (isEqualNodes(a.add, b.add, flags, shortCircuit) &&
((a.remove == null && b.remove == null) ||
(a.remove != null &&
b.remove != null &&
isEqualNodes(a.remove, b.remove, flags, shortCircuit))));
},
Pattern(a, b, flags, shortCircuit) {
return isEqualSet(a.alternatives, b.alternatives, flags, shortCircuit);
},
Quantifier(a, b, flags, shortCircuit) {
return (a.min === b.min &&
a.max === b.max &&
a.greedy === b.greedy &&
isEqualNodes(a.element, b.element, flags, shortCircuit));
},
RegExpLiteral(a, b, flags, shortCircuit) {
return (isEqualNodes(a.pattern, b.pattern, flags, shortCircuit) &&
isEqualNodes(a.flags, b.flags, flags, shortCircuit));
},
StringAlternative(a, b, flags, shortCircuit) {
return isEqualConcatenation(a.elements, b.elements, flags, shortCircuit);
},
};
function isToCharSetElement(node) {
return (node.type === "Character" ||
node.type === "CharacterClass" ||
node.type === "CharacterClassRange" ||
node.type === "CharacterSet");
}
function isEqualNodes(a, b, flags, shortCircuit) {
if (isToCharSetElement(a) && isToCharSetElement(b)) {
return isEqualChar(a, b, flags);
}
if (a.type !== b.type) {
return false;
}
if (shortCircuit) {
const kind = shortCircuit(a, b);
if (kind != null) {
return kind;
}
}
if (/[(*+?[\\{|]/u.test(a.raw) || /[(*+?[\\{|]/u.test(b.raw)) {
return EQUALS_CHECKER[a.type](a, b, flags, shortCircuit);
}
return a.raw === b.raw;
}
function isEqualConcatenation(a, b, flags, shortCircuit) {
if (a.length !== b.length) {
return false;
}
for (let index = 0; index < a.length; index++) {
const ae = a[index];
const be = b[index];
if (!isEqualNodes(ae, be, flags, shortCircuit)) {
return false;
}
}
return true;
}
function isEqualSet(a, b, flags, shortCircuit) {
if (a.length !== b.length) {
return false;
}
const beList = [...b];
for (const ae of a) {
const bIndex = beList.findIndex((be) => isEqualNodes(ae, be, flags, shortCircuit));
if (bIndex >= 0) {
beList.splice(bIndex, 1);
}
else {
return false;
}
}
return true;
}

View file

@ -0,0 +1,8 @@
import type { Quantifier } from "@eslint-community/regexpp/ast";
export declare function getQuantifierOffsets(qNode: Quantifier): [number, number];
export interface Quant {
min: number;
max: number;
greedy?: boolean;
}
export declare function quantToString(quant: Readonly<Quant>): string;

View file

@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getQuantifierOffsets = getQuantifierOffsets;
exports.quantToString = quantToString;
function getQuantifierOffsets(qNode) {
const startOffset = qNode.element.end - qNode.start;
const endOffset = qNode.raw.length - (qNode.greedy ? 0 : 1);
return [startOffset, endOffset];
}
function quantToString(quant) {
if (quant.max < quant.min ||
quant.min < 0 ||
!Number.isInteger(quant.min) ||
!(Number.isInteger(quant.max) || quant.max === Infinity)) {
throw new Error(`Invalid quantifier { min: ${quant.min}, max: ${quant.max} }`);
}
let value;
if (quant.min === 0 && quant.max === 1) {
value = "?";
}
else if (quant.min === 0 && quant.max === Infinity) {
value = "*";
}
else if (quant.min === 1 && quant.max === Infinity) {
value = "+";
}
else if (quant.min === quant.max) {
value = `{${quant.min}}`;
}
else if (quant.max === Infinity) {
value = `{${quant.min},}`;
}
else {
value = `{${quant.min},${quant.max}}`;
}
if (quant.greedy === false) {
return `${value}?`;
}
return value;
}

View file

@ -0,0 +1,12 @@
import type { Quantifier } from "@eslint-community/regexpp/ast";
import type { JS } from "refa";
import type { ReadonlyFlags } from "regexp-ast-analysis";
export type CanSimplify = {
readonly canSimplify: true;
readonly dependencies: Quantifier[];
};
export type CannotSimplify = {
readonly canSimplify: false;
};
export type SimplifyResult = CanSimplify | CannotSimplify;
export declare function canSimplifyQuantifier(quantifier: Quantifier, flags: ReadonlyFlags, parser: JS.Parser): SimplifyResult;

View file

@ -0,0 +1,245 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.canSimplifyQuantifier = canSimplifyQuantifier;
const refa_1 = require("refa");
const regexp_ast_analysis_1 = require("regexp-ast-analysis");
const util_1 = require("../util");
const containsAssertions = (0, util_1.cachedFn)((node) => {
return (0, regexp_ast_analysis_1.hasSomeDescendant)(node, (n) => n.type === "Assertion");
});
const cachedGetPossiblyConsumedChar = (0, util_1.cachedFn)((flags) => {
return (0, util_1.cachedFn)((element) => (0, regexp_ast_analysis_1.getConsumedChars)(element, flags));
});
const CANNOT_SIMPLIFY = { canSimplify: false };
function canSimplifyQuantifier(quantifier, flags, parser) {
if (quantifier.min === quantifier.max) {
return CANNOT_SIMPLIFY;
}
if ((0, regexp_ast_analysis_1.isZeroLength)(quantifier, flags)) {
return CANNOT_SIMPLIFY;
}
if (containsAssertions(quantifier)) {
return CANNOT_SIMPLIFY;
}
const direction = (0, regexp_ast_analysis_1.getMatchingDirection)(quantifier);
const preceding = getPrecedingQuantifiers(quantifier, direction, flags);
if (!preceding) {
return CANNOT_SIMPLIFY;
}
return canAbsorb(preceding, { direction, flags, parser, quantifier });
}
function canAbsorb(initialPreceding, options) {
const { direction, flags, parser, quantifier } = options;
const preceding = removeTargetQuantifier(initialPreceding, quantifier, direction, flags);
if (!preceding) {
return CANNOT_SIMPLIFY;
}
const dependencies = [...preceding];
const CAN_SIMPLIFY = {
canSimplify: true,
dependencies,
};
const fast = everyMaybe(preceding, (q) => canAbsorbElementFast(q, quantifier.element, flags));
if (typeof fast === "boolean") {
return fast ? CAN_SIMPLIFY : CANNOT_SIMPLIFY;
}
const formal = everyMaybe(fast, (q) => canAbsorbElementFormal(q, quantifier.element, parser));
if (typeof formal === "boolean") {
return formal ? CAN_SIMPLIFY : CANNOT_SIMPLIFY;
}
return formal.every((q) => {
const parts = splitQuantifierIntoTails(q, direction, flags);
if (!parts)
return false;
const result = canAbsorb(parts, options);
if (result.canSimplify)
dependencies.push(...result.dependencies);
return result.canSimplify;
})
? CAN_SIMPLIFY
: CANNOT_SIMPLIFY;
}
function everyMaybe(array, fn) {
const maybe = [];
for (const item of array) {
const result = fn(item);
if (result === false)
return false;
if (result === undefined)
maybe.push(item);
}
if (maybe.length === 0)
return true;
return maybe;
}
function canAbsorbElementFast(quantifier, element, flags) {
if (!quantifier.greedy) {
return false;
}
if (!isNonFinite(quantifier, flags)) {
return false;
}
const qChar = cachedGetPossiblyConsumedChar(flags)(quantifier.element);
const eChar = cachedGetPossiblyConsumedChar(flags)(element);
if (qChar.chars.isDisjointWith(eChar.chars)) {
return false;
}
if (eChar.exact && !eChar.chars.without(qChar.chars).isEmpty) {
return false;
}
if (containsAssertions(quantifier) || containsAssertions(element)) {
return undefined;
}
if (quantifier.element.type === "Character" ||
quantifier.element.type === "CharacterClass" ||
quantifier.element.type === "CharacterSet") {
if (quantifier.max !== Infinity) {
return false;
}
if (qChar.exact && qChar.chars.isSupersetOf(eChar.chars)) {
return true;
}
}
return undefined;
}
function isNonFinite(node, flags) {
return (0, regexp_ast_analysis_1.hasSomeDescendant)(node, (n) => n.type === "Quantifier" &&
n.max === Infinity &&
!(0, regexp_ast_analysis_1.isZeroLength)(n.element, flags), (n) => n.type !== "Assertion");
}
function toNfa(element, parser) {
const { expression, maxCharacter } = parser.parseElement(element, {
maxNodes: 1000,
assertions: "throw",
backreferences: "throw",
});
return refa_1.NFA.fromRegex(expression, { maxCharacter }, {}, new refa_1.NFA.LimitedNodeFactory(1000));
}
function canAbsorbElementFormal(quantifier, element, parser) {
if (containsAssertions(quantifier) || containsAssertions(element)) {
return undefined;
}
try {
const qNfa = toNfa(quantifier, parser);
const qDfa = refa_1.DFA.fromFA(qNfa, new refa_1.DFA.LimitedNodeFactory(1000));
const eNfa = toNfa(element, parser);
eNfa.quantify(0, 1);
qNfa.append(eNfa);
const qeDfa = refa_1.DFA.fromFA(qNfa, new refa_1.DFA.LimitedNodeFactory(1000));
qDfa.minimize();
qeDfa.minimize();
const equal = qDfa.structurallyEqual(qeDfa);
return equal;
}
catch (_a) {
}
return undefined;
}
function splitQuantifierIntoTails(quantifier, direction, flags) {
if ((0, regexp_ast_analysis_1.isPotentiallyZeroLength)(quantifier, flags)) {
return undefined;
}
return getTailQuantifiers(quantifier.element, direction, flags);
}
function removeTargetQuantifier(quantifiers, target, direction, flags) {
const result = [];
for (const q of quantifiers) {
if ((0, regexp_ast_analysis_1.hasSomeDescendant)(q, target)) {
const inner = splitQuantifierIntoTails(q, direction, flags);
if (inner === undefined) {
return undefined;
}
const mapped = removeTargetQuantifier(inner, target, direction, flags);
if (mapped === undefined) {
return undefined;
}
result.push(...mapped);
}
else {
result.push(q);
}
}
return result;
}
function unionQuantifiers(sets) {
const result = [];
for (const set of sets) {
if (set === undefined) {
return undefined;
}
result.push(...set);
}
if (result.length === 0)
return undefined;
return [...new Set(result)];
}
function getTailQuantifiers(element, direction, flags) {
switch (element.type) {
case "Assertion":
case "Backreference":
case "Character":
case "CharacterClass":
case "CharacterSet":
case "ExpressionCharacterClass":
return undefined;
case "Quantifier":
return [element];
case "Group":
case "CapturingGroup":
return unionQuantifiers(element.alternatives.map((a) => getTailQuantifiers(a, direction, flags)));
case "Alternative": {
const elements = direction === "ltr"
? (0, util_1.reversed)(element.elements)
: element.elements;
for (const e of elements) {
if ((0, regexp_ast_analysis_1.isEmpty)(e, flags))
continue;
if (e.type === "Quantifier") {
return [e];
}
return undefined;
}
const { parent } = element;
if (parent.type === "Pattern") {
return undefined;
}
if (parent.type === "Assertion") {
return undefined;
}
return getPrecedingQuantifiers(parent, direction, flags);
}
default:
return (0, util_1.assertNever)(element);
}
}
function getPrecedingQuantifiers(element, direction, flags) {
const parent = element.parent;
if (parent.type === "Quantifier") {
if (parent.max === 0) {
return undefined;
}
if (parent.max === 1) {
return getPrecedingQuantifiers(parent, direction, flags);
}
return unionQuantifiers([
getPrecedingQuantifiers(parent, direction, flags),
getTailQuantifiers(parent.element, direction, flags),
]);
}
if (parent.type !== "Alternative") {
return undefined;
}
const inc = direction === "ltr" ? -1 : +1;
const { elements } = parent;
const elementIndex = elements.indexOf(element);
for (let precedingIndex = elementIndex + inc; precedingIndex >= 0 && precedingIndex < elements.length; precedingIndex += inc) {
const preceding = parent.elements[precedingIndex];
if ((0, regexp_ast_analysis_1.isEmpty)(preceding, flags))
continue;
return getTailQuantifiers(preceding, direction, flags);
}
if (parent.parent.type === "Pattern") {
return undefined;
}
return getPrecedingQuantifiers(parent.parent, direction, flags);
}