regex-inspector 1.0.1

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/dist/ast.d.ts ADDED
@@ -0,0 +1,108 @@
1
+ /** A single literal character, stored as its Unicode code point. */
2
+ export type CharNode = {
3
+ kind: "char";
4
+ /** Unicode code point. */
5
+ value: number;
6
+ };
7
+ /** A position anchor: ^, $, \b, \B. */
8
+ export type PositionNode = {
9
+ kind: "position";
10
+ value: "^" | "$" | "b" | "B";
11
+ };
12
+ /** A Unicode property escape: \p{...} or \P{...}. */
13
+ export type UnicodePropertyNode = {
14
+ kind: "unicode_property";
15
+ /** The property name (e.g. "L", "Script=Latin"). */
16
+ property: string;
17
+ /** True for \P{...}, false for \p{...}. */
18
+ negated: boolean;
19
+ };
20
+ /** A backreference to a capturing group by index. */
21
+ export type BackreferenceNode = {
22
+ kind: "backreference";
23
+ /** 1-based capturing group index. */
24
+ index: number;
25
+ };
26
+ /** A character range inside a character class: `a-z`. */
27
+ export type RangeMember = {
28
+ kind: "range";
29
+ /** Inclusive start code point. */
30
+ from: number;
31
+ /** Inclusive end code point. */
32
+ to: number;
33
+ };
34
+ /** A single character inside a character class. */
35
+ export type CharMember = {
36
+ kind: "char";
37
+ /** Unicode code point. */
38
+ value: number;
39
+ };
40
+ /** A v-mode string literal inside a character class: `\q{abc|def}`. */
41
+ export type StringMemberNode = {
42
+ kind: "string_member";
43
+ /** Alternatives separated by `|` inside `\q{...}`. Each inner array is a string of code points. */
44
+ strings: number[][];
45
+ /** True for `\P{...}` style negation? No. `\q` has no negated form. Kept for uniform SetMember handling. */
46
+ negated: boolean;
47
+ };
48
+ /** A v-mode set operation: `[a-z--[ab]]` (subtract) or `[a-z&&[ab]]` (intersect). */
49
+ export type SetOpNode = {
50
+ kind: "set_op";
51
+ /** "subtract" for `--`, "intersect" for `&&`. */
52
+ operator: "subtract" | "intersect";
53
+ left: SetMember;
54
+ right: SetMember;
55
+ };
56
+ /** A member of a character class set. */
57
+ export type SetMember = CharMember | RangeMember | CharSetNode | UnicodePropertyNode | StringMemberNode | SetOpNode;
58
+ /** A character class: `[...]` or `[^...]`. */
59
+ export type CharSetNode = {
60
+ kind: "charset";
61
+ /** True for `[^...]`, false for `[...]`. */
62
+ negated: boolean;
63
+ /** The tokens inside the class. */
64
+ members: SetMember[];
65
+ };
66
+ /** A quantifier (repetition) wrapping a child token. */
67
+ export type RepetitionNode = {
68
+ kind: "repetition";
69
+ /** Minimum repetitions. */
70
+ min: number;
71
+ /** Maximum repetitions; `Infinity` for unbounded. */
72
+ max: number;
73
+ /** True for greedy, false for lazy. */
74
+ greedy: boolean;
75
+ /** The token being repeated. */
76
+ child: Token;
77
+ };
78
+ /** A group: `(...)`, `(?:...)`, `(?=...)`, `(?<name>...)`, etc. */
79
+ export type GroupNode = {
80
+ kind: "group";
81
+ /** True for capturing groups `(...)` and `(?<name>...)`. */
82
+ capturing: boolean;
83
+ /** Alternatives separated by `|`. A linear group has one branch. */
84
+ branches: Token[][];
85
+ /** Positive lookahead `(?=...)`. */
86
+ lookahead?: boolean;
87
+ /** Negative lookahead `(?!...)`. */
88
+ negatedLookahead?: boolean;
89
+ /** Positive lookbehind `(?<=...)`. */
90
+ lookbehind?: boolean;
91
+ /** Negative lookbehind `(?<!...)`. */
92
+ negatedLookbehind?: boolean;
93
+ /** Named capture group name (from `(?<name>...)`). */
94
+ name?: string;
95
+ /** Modifier flags string (from `(?ims-ims:...)` or `(?i)`). */
96
+ modifiers?: string;
97
+ };
98
+ /** The root node of a regex pattern. */
99
+ export type RootNode = {
100
+ kind: "root";
101
+ /** Alternatives separated by top-level `|`. A linear pattern has one branch. */
102
+ branches: Token[][];
103
+ };
104
+ /** Any AST node that can appear inside a branch (root or group). */
105
+ export type Token = CharNode | PositionNode | UnicodePropertyNode | BackreferenceNode | CharSetNode | RepetitionNode | GroupNode;
106
+ /** All AST node types. */
107
+ export type Node = RootNode | Token;
108
+ //# sourceMappingURL=ast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"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"}
package/dist/ast.js ADDED
@@ -0,0 +1,5 @@
1
+ // ── AST Node Types ─────────────────────────────────────────────────────────
2
+ // All nodes use a `kind` string discriminant instead of numeric enums for
3
+ // better debuggability, JSON serialization, and IDE autocompletion.
4
+ export {};
5
+ //# sourceMappingURL=ast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast.js","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,0EAA0E;AAC1E,oEAAoE"}
package/dist/fix.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { Node } from "./ast.js";
2
+ export type FixResult = {
3
+ safe: boolean;
4
+ fixed: string | null;
5
+ original: string;
6
+ semanticChange: boolean;
7
+ };
8
+ export type FixOptions = {
9
+ limit?: number;
10
+ };
11
+ export declare function fixRegex(pattern: string | Node, opts?: FixOptions): FixResult;
12
+ //# sourceMappingURL=fix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fix.d.ts","sourceRoot":"","sources":["../src/fix.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,IAAI,EAAyB,MAAM,UAAU,CAAC;AAMvE,MAAM,MAAM,SAAS,GAAG;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAkHF,wBAAgB,QAAQ,CACvB,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,IAAI,GAAE,UAAe,GACnB,SAAS,CAqEX"}
package/dist/fix.js ADDED
@@ -0,0 +1,169 @@
1
+ import { analyze } from "./analyze.js";
2
+ import { generate } from "./generator.js";
3
+ import { tokenize } from "./parser.js";
4
+ // ── AST utilities ─────────────────────────────────────────────────────────
5
+ function cloneNode(node) {
6
+ return JSON.parse(JSON.stringify(node), (_key, value) => {
7
+ if (value === null && _key === "max")
8
+ return Infinity;
9
+ return value;
10
+ });
11
+ }
12
+ function hasDeepRepetition(node, depth, maxDepth) {
13
+ if (node.kind === "repetition") {
14
+ depth++;
15
+ if (depth > maxDepth)
16
+ return true;
17
+ return hasDeepRepetition(node.child, depth, maxDepth);
18
+ }
19
+ if (node.kind === "group") {
20
+ for (const branch of node.branches) {
21
+ for (const child of branch) {
22
+ if (hasDeepRepetition(child, depth, maxDepth))
23
+ return true;
24
+ }
25
+ }
26
+ }
27
+ return false;
28
+ }
29
+ // ── Fix strategies ────────────────────────────────────────────────────────
30
+ /**
31
+ * Collapses same-character alternatives inside a quantifier.
32
+ * (a|aa|aaa)+ → a+
33
+ */
34
+ function fixAlternationReDoS(repNode) {
35
+ const group = findGroupWithAlternatives(repNode.child);
36
+ if (!group)
37
+ return null;
38
+ let allSameChar = true;
39
+ let firstChar = null;
40
+ for (const branch of group.branches) {
41
+ for (const token of branch) {
42
+ if (token.kind === "char") {
43
+ if (firstChar === null)
44
+ firstChar = token.value;
45
+ if (token.value !== firstChar)
46
+ allSameChar = false;
47
+ }
48
+ else {
49
+ allSameChar = false;
50
+ break;
51
+ }
52
+ }
53
+ if (!allSameChar)
54
+ break;
55
+ }
56
+ if (allSameChar && firstChar !== null) {
57
+ return {
58
+ kind: "repetition",
59
+ min: repNode.min,
60
+ max: repNode.max,
61
+ greedy: repNode.greedy,
62
+ child: { kind: "char", value: firstChar },
63
+ };
64
+ }
65
+ return null;
66
+ }
67
+ function findGroupWithAlternatives(node) {
68
+ if (node.kind === "group" && node.branches.length >= 2)
69
+ return node;
70
+ if (node.kind === "group") {
71
+ for (const branch of node.branches) {
72
+ for (const child of branch) {
73
+ const found = findGroupWithAlternatives(child);
74
+ if (found)
75
+ return found;
76
+ }
77
+ }
78
+ }
79
+ if (node.kind === "repetition")
80
+ return findGroupWithAlternatives(node.child);
81
+ return null;
82
+ }
83
+ // ── Recursive fixer ───────────────────────────────────────────────────────
84
+ function fixNode(node, limit) {
85
+ if (node.kind === "repetition") {
86
+ // Strip outer quantifier if nested repetition detected
87
+ if (hasDeepRepetition(node.child, 1, 1)) {
88
+ return fixNode(node.child, limit);
89
+ }
90
+ // Collapse same-character alternatives
91
+ const altFix = fixAlternationReDoS(node);
92
+ if (altFix)
93
+ return altFix;
94
+ const result = cloneNode(node);
95
+ result.child = fixNode(result.child, limit);
96
+ return result;
97
+ }
98
+ if (node.kind === "group") {
99
+ const result = cloneNode(node);
100
+ result.branches = result.branches.map((branch) => branch.map((child) => fixNode(child, limit)));
101
+ return result;
102
+ }
103
+ return node;
104
+ }
105
+ // ── Public API ────────────────────────────────────────────────────────────
106
+ export function fixRegex(pattern, opts = {}) {
107
+ const limit = opts.limit ?? 25;
108
+ const source = typeof pattern === "string" ? pattern : generate(pattern);
109
+ let ast;
110
+ try {
111
+ ast = typeof pattern === "string" ? tokenize(pattern) : pattern;
112
+ }
113
+ catch {
114
+ return {
115
+ safe: false,
116
+ fixed: null,
117
+ original: source,
118
+ semanticChange: false,
119
+ };
120
+ }
121
+ const analysis = analyze(ast, { limit });
122
+ if (analysis.safe) {
123
+ return {
124
+ safe: true,
125
+ fixed: null,
126
+ original: source,
127
+ semanticChange: false,
128
+ };
129
+ }
130
+ const fixedAst = cloneNode(ast);
131
+ if (fixedAst.kind === "root") {
132
+ fixedAst.branches = fixedAst.branches.map((branch) => branch.map((child) => fixNode(child, limit)));
133
+ }
134
+ let fixed;
135
+ try {
136
+ fixed = generate(fixedAst);
137
+ }
138
+ catch {
139
+ return {
140
+ safe: false,
141
+ fixed: null,
142
+ original: source,
143
+ semanticChange: false,
144
+ };
145
+ }
146
+ let verifyAst;
147
+ try {
148
+ verifyAst = tokenize(fixed);
149
+ }
150
+ catch {
151
+ return {
152
+ safe: false,
153
+ fixed: null,
154
+ original: source,
155
+ semanticChange: false,
156
+ };
157
+ }
158
+ const verifyResult = analyze(verifyAst, { limit });
159
+ if (!verifyResult.safe) {
160
+ return {
161
+ safe: false,
162
+ fixed: null,
163
+ original: source,
164
+ semanticChange: false,
165
+ };
166
+ }
167
+ return { safe: true, fixed, original: source, semanticChange: true };
168
+ }
169
+ //# sourceMappingURL=fix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fix.js","sourceRoot":"","sources":["../src/fix.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { Node } from "./ast.js";
2
+ /**
3
+ * Reconstructs a regex pattern string from an AST node.
4
+ * This is the inverse of `tokenize()`.
5
+ */
6
+ export declare function generate(node: Node): string;
7
+ //# sourceMappingURL=generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,IAAI,EAAoB,MAAM,UAAU,CAAC;AAgMpE;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAK3C"}
@@ -0,0 +1,191 @@
1
+ import { DIGITS_LOOKUP, LINE_TERMINATORS_LOOKUP, WHITESPACE_LOOKUP, WORD_CHARS_LOOKUP, } from "./preset.js";
2
+ // ── Character escaping ────────────────────────────────────────────────────
3
+ /**
4
+ * Returns the string representation of a code point for use inside a
5
+ * character class, escaping ^, \, ], and - as needed.
6
+ */
7
+ function setChar(cp) {
8
+ if (cp === 94)
9
+ return "\\^";
10
+ if (cp === 92)
11
+ return "\\\\";
12
+ if (cp === 93)
13
+ return "\\]";
14
+ if (cp === 45)
15
+ return "\\-";
16
+ return String.fromCodePoint(cp);
17
+ }
18
+ // Characters that must be escaped outside a character class.
19
+ // [ must be escaped because bare [ starts a new char class.
20
+ // { } must be escaped because bare { starts a quantifier.
21
+ // ] is safe: bare ] outside a class is always literal.
22
+ const METACHAR_REGEX = /[[\\{}$^.|?*+()]/;
23
+ /**
24
+ * Returns the string representation of a code point for use outside a
25
+ * character class, escaping special regex metacharacters.
26
+ */
27
+ function literalChar(cp) {
28
+ const ch = String.fromCodePoint(cp);
29
+ if (METACHAR_REGEX.test(ch))
30
+ return `\\${ch}`;
31
+ return ch;
32
+ }
33
+ // ── Set comparison ────────────────────────────────────────────────────────
34
+ /**
35
+ * Checks if a character set matches a predefined lookup for shorthand
36
+ * optimization (e.g., output \d instead of [0-9]).
37
+ */
38
+ function matchesLookup(members, lookup) {
39
+ if (members.length !== lookup.size)
40
+ return false;
41
+ for (const m of members) {
42
+ if (m.kind !== "char" && m.kind !== "range")
43
+ return false; // nested set or unicode property
44
+ const key = m.kind === "char" ? String(m.value) : `${m.from}-${m.to}`;
45
+ if (!lookup.entries.has(key))
46
+ return false;
47
+ }
48
+ return true;
49
+ }
50
+ // ── Set member rendering ──────────────────────────────────────────────────
51
+ function renderSetMember(m, _nested) {
52
+ switch (m.kind) {
53
+ case "char":
54
+ return setChar(m.value);
55
+ case "range":
56
+ return `${setChar(m.from)}-${setChar(m.to)}`;
57
+ case "unicode_property":
58
+ return `\\${m.negated ? "P" : "p"}{${m.property}}`;
59
+ case "charset":
60
+ return renderCharSet(m, _nested);
61
+ case "string_member": {
62
+ if (m.strings.length === 0)
63
+ return "";
64
+ const parts = m.strings.map((s) => s.map((cp) => String.fromCodePoint(cp)).join(""));
65
+ return `\\q{${parts.join("|")}}`;
66
+ }
67
+ case "set_op": {
68
+ const renderOp = (op) => {
69
+ // Only bare chars can appear without brackets in v-mode.
70
+ // Ranges, charsets, and nested set ops need wrapping.
71
+ const needsBrackets = op.kind !== "char" && op.kind !== "string_member";
72
+ if (!needsBrackets)
73
+ return renderSetMember(op, true);
74
+ const inner = renderSetMember(op, true);
75
+ return `[${inner}]`;
76
+ };
77
+ return `${renderOp(m.left)}${m.operator === "subtract" ? "--" : "&&"}${renderOp(m.right)}`;
78
+ }
79
+ default:
80
+ throw new Error(`Unknown set member kind: ${m.kind}`);
81
+ }
82
+ }
83
+ /**
84
+ * Reconstructs a character class node into its regex string representation.
85
+ */
86
+ function renderCharSet(set, nested = false) {
87
+ const { members, negated } = set;
88
+ // Shorthand detection: if a set exactly matches a predefined set,
89
+ // output the shorthand escape instead of the expanded form.
90
+ if (!nested && matchesLookup(members, DIGITS_LOOKUP)) {
91
+ return negated ? "\\D" : "\\d";
92
+ }
93
+ if (!nested && matchesLookup(members, WORD_CHARS_LOOKUP)) {
94
+ return negated ? "\\W" : "\\w";
95
+ }
96
+ if (!nested && matchesLookup(members, WHITESPACE_LOOKUP)) {
97
+ return negated ? "\\S" : "\\s";
98
+ }
99
+ if (!nested && negated && matchesLookup(members, LINE_TERMINATORS_LOOKUP)) {
100
+ return ".";
101
+ }
102
+ const content = members
103
+ .map((m) => renderSetMember(m, true))
104
+ .join("");
105
+ const body = `${negated ? "^" : ""}${content}`;
106
+ return nested ? body : `[${body}]`;
107
+ }
108
+ // ── Token rendering ───────────────────────────────────────────────────────
109
+ function renderBranch(tokens) {
110
+ return tokens.map(renderToken).join("");
111
+ }
112
+ function renderBranches(branches) {
113
+ return branches.map((b) => renderBranch(b)).join("|");
114
+ }
115
+ function renderToken(token) {
116
+ switch (token.kind) {
117
+ case "char":
118
+ return literalChar(token.value);
119
+ case "position":
120
+ if (token.value === "^" || token.value === "$") {
121
+ return token.value;
122
+ }
123
+ return `\\${token.value}`;
124
+ case "backreference":
125
+ return `\\${token.index}`;
126
+ case "unicode_property":
127
+ return `\\${token.negated ? "P" : "p"}{${token.property}}`;
128
+ case "charset":
129
+ return renderCharSet(token);
130
+ case "repetition": {
131
+ const { min, max, greedy, child } = token;
132
+ let suffix;
133
+ if (min === 0 && max === 1)
134
+ suffix = "?";
135
+ else if (min === 1 && max === Infinity)
136
+ suffix = "+";
137
+ else if (min === 0 && max === Infinity)
138
+ suffix = "*";
139
+ else if (max === Infinity)
140
+ suffix = `{${min},}`;
141
+ else if (min === max)
142
+ suffix = `{${min}}`;
143
+ else
144
+ suffix = `{${min},${max}}`;
145
+ return `${renderToken(child)}${suffix}${greedy ? "" : "?"}`;
146
+ }
147
+ case "group": {
148
+ let prefix;
149
+ if (token.modifiers) {
150
+ const hasContent = token.branches.some((b) => b.length > 0);
151
+ prefix = `?${token.modifiers}${hasContent ? ":" : ""}`;
152
+ }
153
+ else if (token.lookbehind) {
154
+ prefix = "?<=";
155
+ }
156
+ else if (token.negatedLookbehind) {
157
+ prefix = "?<!";
158
+ }
159
+ else if (token.lookahead) {
160
+ prefix = "?=";
161
+ }
162
+ else if (token.negatedLookahead) {
163
+ prefix = "?!";
164
+ }
165
+ else if (token.name) {
166
+ prefix = `?<${token.name}>`;
167
+ }
168
+ else if (!token.capturing) {
169
+ prefix = "?:";
170
+ }
171
+ else {
172
+ prefix = "";
173
+ }
174
+ return `(${prefix}${renderBranches(token.branches)})`;
175
+ }
176
+ default:
177
+ throw new Error(`Unknown token kind: ${token.kind}`);
178
+ }
179
+ }
180
+ // ── Public API ────────────────────────────────────────────────────────────
181
+ /**
182
+ * Reconstructs a regex pattern string from an AST node.
183
+ * This is the inverse of `tokenize()`.
184
+ */
185
+ export function generate(node) {
186
+ if (node.kind === "root") {
187
+ return renderBranches(node.branches);
188
+ }
189
+ return renderToken(node);
190
+ }
191
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export { analyze } from "./analyze.js";
2
+ export { fixRegex } from "./fix.js";
3
+ export { generate } from "./generator.js";
4
+ export { tokenize as parse } from "./parser.js";
5
+ export type { AnalysisResult, AnalyzeOptions, Severity, } from "./analyze.js";
6
+ export type { BackreferenceNode, CharMember, CharNode, CharSetNode, GroupNode, Node, PositionNode, RangeMember, RepetitionNode, RootNode, SetMember, SetOpNode, StringMemberNode, Token, UnicodePropertyNode, } from "./ast.js";
7
+ export type { FixOptions, FixResult, } from "./fix.js";
8
+ import type { AnalysisResult, AnalyzeOptions } from "./analyze.js";
9
+ import type { FixOptions, FixResult } from "./fix.js";
10
+ /**
11
+ * Parses a regex pattern and returns a full ReDoS analysis including
12
+ * severity level, reasons, and a suggested fix if unsafe.
13
+ *
14
+ * Accepts strings, RegExp objects, or any value coercible to string.
15
+ */
16
+ export declare function inspect(pattern: string | RegExp | unknown, opts?: AnalyzeOptions): AnalysisResult;
17
+ /**
18
+ * Attempts to automatically fix a ReDoS-vulnerable regex pattern.
19
+ *
20
+ * Accepts strings, RegExp objects, or any value coercible to string.
21
+ */
22
+ export declare function fix(pattern: string | RegExp | unknown, opts?: FixOptions): FixResult;
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,QAAQ,IAAI,KAAK,EAAE,MAAM,aAAa,CAAC;AAIhD,YAAY,EACX,cAAc,EACd,cAAc,EACd,QAAQ,GACR,MAAM,cAAc,CAAC;AACtB,YAAY,EACX,iBAAiB,EACjB,UAAU,EACV,QAAQ,EACR,WAAW,EACX,SAAS,EACT,IAAI,EACJ,YAAY,EACZ,WAAW,EACX,cAAc,EACd,QAAQ,EACR,SAAS,EACT,SAAS,EACT,gBAAgB,EAChB,KAAK,EACL,mBAAmB,GACnB,MAAM,UAAU,CAAC;AAElB,YAAY,EACX,UAAU,EACV,SAAS,GACT,MAAM,UAAU,CAAC;AAIlB,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnE,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAqBtD;;;;;GAKG;AACH,wBAAgB,OAAO,CACtB,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAClC,IAAI,CAAC,EAAE,cAAc,GACnB,cAAc,CA4BhB;AAED;;;;GAIG;AACH,wBAAgB,GAAG,CAClB,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAClC,IAAI,CAAC,EAAE,UAAU,GACf,SAAS,CAGX"}
package/dist/index.js ADDED
@@ -0,0 +1,78 @@
1
+ // ── Public API ────────────────────────────────────────────────────────────
2
+ //
3
+ // regex-inspector: Parse, reconstruct, and analyze regex patterns for ReDoS.
4
+ //
5
+ // Usage:
6
+ // import { parse, generate, inspect, fix } from 'regex-inspector';
7
+ //
8
+ // const ast = parse('(a+)+y');
9
+ // const result = inspect(ast);
10
+ // const fixed = fix('(a+)+');
11
+ // const str = generate(ast);
12
+ // ── Core exports ─────────────────────────────────────────────────────────
13
+ export { analyze } from "./analyze.js";
14
+ export { fixRegex } from "./fix.js";
15
+ export { generate } from "./generator.js";
16
+ export { tokenize as parse } from "./parser.js";
17
+ import { analyze } from "./analyze.js";
18
+ import { fixRegex as fixImpl } from "./fix.js";
19
+ import { tokenize } from "./parser.js";
20
+ /**
21
+ * Coerces any input to a string: RegExp → .source, else → String().
22
+ * Uses cross-realm-safe detection via Object.prototype.toString
23
+ * instead of instanceof, which fails across iframes/vm contexts.
24
+ */
25
+ function coerceToString(input) {
26
+ if (input !== null &&
27
+ input !== undefined &&
28
+ Object.prototype.toString.call(input) === "[object RegExp]") {
29
+ return input.source;
30
+ }
31
+ if (typeof input === "string")
32
+ return input;
33
+ return String(input);
34
+ }
35
+ /**
36
+ * Parses a regex pattern and returns a full ReDoS analysis including
37
+ * severity level, reasons, and a suggested fix if unsafe.
38
+ *
39
+ * Accepts strings, RegExp objects, or any value coercible to string.
40
+ */
41
+ export function inspect(pattern, opts) {
42
+ try {
43
+ const source = coerceToString(pattern);
44
+ const ast = tokenize(source);
45
+ const result = analyze(ast, opts);
46
+ if (!result.safe) {
47
+ const fixResult = fixImpl(ast, { limit: opts?.limit });
48
+ if (fixResult.fixed) {
49
+ result.fix = fixResult.fixed;
50
+ }
51
+ }
52
+ return result;
53
+ }
54
+ catch (err) {
55
+ return {
56
+ safe: false,
57
+ severity: "high",
58
+ reasons: [`Invalid regex syntax: ${err.message}`],
59
+ starHeight: 0,
60
+ repCount: 0,
61
+ hasAlternationReDoS: false,
62
+ hasSequentialOverlap: false,
63
+ anchored: false,
64
+ hasStaticSuffix: false,
65
+ fix: null,
66
+ };
67
+ }
68
+ }
69
+ /**
70
+ * Attempts to automatically fix a ReDoS-vulnerable regex pattern.
71
+ *
72
+ * Accepts strings, RegExp objects, or any value coercible to string.
73
+ */
74
+ export function fix(pattern, opts) {
75
+ const source = coerceToString(pattern);
76
+ return fixImpl(source, opts);
77
+ }
78
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,EAAE;AACF,6EAA6E;AAC7E,EAAE;AACF,SAAS;AACT,qEAAqE;AACrE,EAAE;AACF,iCAAiC;AACjC,iCAAiC;AACjC,gCAAgC;AAChC,+BAA+B;AAE/B,4EAA4E;AAE5E,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,QAAQ,IAAI,KAAK,EAAE,MAAM,aAAa,CAAC;AAmChD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,QAAQ,IAAI,OAAO,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC;;;;GAIG;AACH,SAAS,cAAc,CAAC,KAAc;IACrC,IACC,KAAK,KAAK,IAAI;QACd,KAAK,KAAK,SAAS;QACnB,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,iBAAiB,EAC1D,CAAC;QACF,OAAQ,KAAgB,CAAC,MAAM,CAAC;IACjC,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACtB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CACtB,OAAkC,EAClC,IAAqB;IAErB,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAElC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAClB,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACvD,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;gBACrB,MAAM,CAAC,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC;YAC9B,CAAC;QACF,CAAC;QAED,OAAO,MAAM,CAAC;IACf,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,OAAO;YACN,IAAI,EAAE,KAAK;YACX,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,CAAC,yBAA0B,GAAa,CAAC,OAAO,EAAE,CAAC;YAC5D,UAAU,EAAE,CAAC;YACb,QAAQ,EAAE,CAAC;YACX,mBAAmB,EAAE,KAAK;YAC1B,oBAAoB,EAAE,KAAK;YAC3B,QAAQ,EAAE,KAAK;YACf,eAAe,EAAE,KAAK;YACtB,GAAG,EAAE,IAAI;SACT,CAAC;IACH,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,GAAG,CAClB,OAAkC,EAClC,IAAiB;IAEjB,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACvC,OAAO,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { RootNode } from "./ast.js";
2
+ export declare function tokenize(pattern: string): RootNode;
3
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIX,QAAQ,EAGR,MAAM,UAAU,CAAC;AA0GlB,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CA4tBlD"}