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/LICENSE +21 -0
- package/README.md +164 -0
- package/bin/regex-inspector.js +137 -0
- package/dist/analyze.d.ts +19 -0
- package/dist/analyze.d.ts.map +1 -0
- package/dist/analyze.js +608 -0
- package/dist/analyze.js.map +1 -0
- package/dist/ast.d.ts +108 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +5 -0
- package/dist/ast.js.map +1 -0
- package/dist/fix.d.ts +12 -0
- package/dist/fix.d.ts.map +1 -0
- package/dist/fix.js +169 -0
- package/dist/fix.js.map +1 -0
- package/dist/generator.d.ts +7 -0
- package/dist/generator.d.ts.map +1 -0
- package/dist/generator.js +191 -0
- package/dist/generator.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +3 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +776 -0
- package/dist/parser.js.map +1 -0
- package/dist/preset.d.ts +39 -0
- package/dist/preset.d.ts.map +1 -0
- package/dist/preset.js +96 -0
- package/dist/preset.js.map +1 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 RezaLabs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# regex-inspector
|
|
2
|
+
|
|
3
|
+
regex-inspector is a zero-dependency TypeScript library and CLI that parses any JavaScript regular expression into a typed AST, reconstructs it back to a string, analyzes it for catastrophic backtracking vulnerabilities, and fixes unsafe patterns automatically. Most regex testing tools only tell you whether a pattern matches your input. They do not tell you whether that pattern will explode when given a long non-matching string. regex-inspector performs static analysis of the regex structure itself, identifying patterns that force exponential backtracking: the root cause of ReDoS (regular expression denial of service) that has caused real outages at Cloudflare, Stack Overflow, and other major platforms.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Full regex-to-AST parsing:** Tokenizes any JavaScript regex pattern into a typed AST covering all standard syntax including named groups, lookbehinds, modifier groups, Unicode property escapes, v-flag `\q{...}` and set operations, and octalescent backreference disambiguation.
|
|
8
|
+
- **AST-to-string generation:** Round-trip reconstruction of regex strings from AST nodes with shorthand set normalization (`\d`, `\w`, `\s`, `.`).
|
|
9
|
+
- **ReDoS detection:** Static analysis for three classes of catastrophic backtracking: nested repetition (star height analysis), alternation prefix overlap, and sequential overlapping quantifiers (`.*?<head>.*?<title>.*?</title>` cascading O(N^k) backtracking without explicit nesting).
|
|
10
|
+
- **Severity scoring:** Four levels (`none`, `low`, `high`, `critical`) with automatic mitigation detection from anchoring (`^...$`) and exclusive static suffixes.
|
|
11
|
+
- **Auto-fix:** Strips redundant outer quantifiers (`(a+)+` to `(a+)`) and collapses same-character alternatives (`(a|aa|aaa)+` to `a+`). Every fix is verified safe before return.
|
|
12
|
+
- **CLI with exit codes:** Quick safety check (`npx regex-inspector '(a+)+'`), detailed JSON analysis (`-a`), and auto-fix output (`-f`) with configurable repetition limit.
|
|
13
|
+
- **Convenience API:** `inspect()` combines parse, analyze, and fix in a single call accepting strings, `RegExp` objects, and cross-realm-safe detection.
|
|
14
|
+
- **Zero dependencies:** Fully self-contained TypeScript implementation with no runtime dependencies.
|
|
15
|
+
|
|
16
|
+
### ReDoS Detection Details
|
|
17
|
+
|
|
18
|
+
**Nested repetition** (star height > 1): `(a+)+y`, `(x+x+)+y`; quantifiers inside quantifiers creating exponential backtracking paths. Automatically accounts for unambiguous inner structure where disjoint charsets make nested quantifiers safe (e.g., `(a+b+)+`).
|
|
19
|
+
|
|
20
|
+
**Alternation prefix overlap**: `(a|aa|aaa)+`, `(ab|abc)+`; alternatives inside a quantifier sharing a common prefix, allowing the engine to partition input in exponentially many ways. Extends to character classes (`[a-z]|[a-z][a-z]`), predefined sets (`\d|\d\d`), the dot metacharacter, and mixed CHAR/SET overlap.
|
|
21
|
+
|
|
22
|
+
**Sequential overlapping quantifiers**: `.*?<head>.*?<title>.*?</title>`, `. +?a.+?a.+?a`, or `(.*?,){11}P`; consecutive quantifiers whose match domains overlap with the token that follows them. Each quantifier can over-consume the delimiter, creating cascading O(N^k) backtracking when later tokens fail. A single adjacency inside a repeated group (e.g., `(.*?,){11}`) is similarly dangerous because the outer repetition amplifies the overlap.
|
|
23
|
+
|
|
24
|
+
### Severity Levels
|
|
25
|
+
|
|
26
|
+
| Level | Meaning |
|
|
27
|
+
|-------|---------|
|
|
28
|
+
| `none` | Safe |
|
|
29
|
+
| `low` | Minor issues or mitigated by anchoring/suffix |
|
|
30
|
+
| `high` | Nested repetition, alternation overlap, or sequential overlap |
|
|
31
|
+
| `critical` | Star height >= 3 |
|
|
32
|
+
|
|
33
|
+
Mitigating factors (`^...$` anchoring, trailing literal not matchable by preceding quantifiers) reduce severity. A trailing `y` after `(a+)+` helps because `a` cannot match `y`. A trailing `P` after `(.*?,){11}` does not help because dot also matches `P`.
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install regex-inspector
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Basic Usage
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
import { parse, generate, inspect, fix } from 'regex-inspector';
|
|
47
|
+
|
|
48
|
+
// Parse a regex into an AST
|
|
49
|
+
const ast = parse('(a+)+y');
|
|
50
|
+
|
|
51
|
+
// Reconstruct the pattern string from the AST
|
|
52
|
+
generate(ast); // => '(a+)+y'
|
|
53
|
+
|
|
54
|
+
// Inspect for ReDoS vulnerabilities
|
|
55
|
+
inspect('(a+)+y');
|
|
56
|
+
// => { safe: false, severity: 'low', starHeight: 2, fix: '(a+)y', ... }
|
|
57
|
+
|
|
58
|
+
// Auto-fix unsafe patterns
|
|
59
|
+
fix('(a+)+');
|
|
60
|
+
// => { safe: false, fixed: '(a+)', original: '(a+)+', semanticChange: true }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
# Quick safety check (exit code)
|
|
65
|
+
npx regex-inspector '(a+)+' # => critical (exit 1)
|
|
66
|
+
npx regex-inspector '^[a-z]+$' # => safe (exit 0)
|
|
67
|
+
|
|
68
|
+
# Detailed analysis
|
|
69
|
+
npx regex-inspector -a '(x+x+)+y'
|
|
70
|
+
|
|
71
|
+
# Auto-fix output
|
|
72
|
+
npx regex-inspector -f '(x+x+)+y' # => (x+x+)y
|
|
73
|
+
|
|
74
|
+
# Custom repetition limit
|
|
75
|
+
npx regex-inspector -l 50 '(a+)+'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
For more complete scenarios, see the [CLI docs](./docs/cli.md) and [API reference](./docs/api.md).
|
|
79
|
+
|
|
80
|
+
## Configuration
|
|
81
|
+
|
|
82
|
+
regex-inspector works out of the box. The optional `limit` parameter controls the maximum allowed repetition depth:
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
import { inspect } from 'regex-inspector';
|
|
86
|
+
|
|
87
|
+
const result = inspect('(a+)+', { limit: 50 });
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Security Advisory
|
|
91
|
+
|
|
92
|
+
If you maintain a service that accepts user-provided regex patterns (search fields, filtering tools, log analyzers), every regex you execute is a potential denial-of-service vector. An attacker who submits a long string against a vulnerable regex can pin a CPU core with a single HTTP request.
|
|
93
|
+
|
|
94
|
+
regex-inspector detects these patterns so you can reject, fix, or sandbox them before execution. It does not make them safe by itself.
|
|
95
|
+
|
|
96
|
+
## API Reference
|
|
97
|
+
|
|
98
|
+
Every public function is fully typed and has a corresponding set of unit tests.
|
|
99
|
+
|
|
100
|
+
| Function | Input | Output | Purpose |
|
|
101
|
+
|----------|-------|--------|---------|
|
|
102
|
+
| `parse` | `string` | `RootNode` | Tokenize regex into AST |
|
|
103
|
+
| `generate` | `Node` | `string` | Reconstruct regex from AST |
|
|
104
|
+
| `inspect` | `string \| RegExp` | `AnalysisResult` | Parse + analyze + suggest fix |
|
|
105
|
+
| `fix` | `string \| RegExp` | `FixResult` | Auto-fix unsafe patterns |
|
|
106
|
+
| `analyze` | `Node` | `AnalysisResult` | Analyze pre-parsed AST |
|
|
107
|
+
| `fixRegex` | `string \| Node` | `FixResult` | Fix pre-parsed AST |
|
|
108
|
+
|
|
109
|
+
The unit tests (in [`test/`](./test)) serve as the definitive, always-correct specification for edge behaviour.
|
|
110
|
+
|
|
111
|
+
Full details:
|
|
112
|
+
|
|
113
|
+
- **[`/docs` directory](./docs)** for detailed markdown references
|
|
114
|
+
- **[docs/api.md](./docs/api.md)** for the full API reference
|
|
115
|
+
- **[docs/ast.md](./docs/ast.md)** for AST node type documentation
|
|
116
|
+
- **[docs/cli.md](./docs/cli.md)** for CLI usage and options
|
|
117
|
+
- **[docs/syntax.md](./docs/syntax.md)** for supported regex syntax
|
|
118
|
+
- **[docs/errors.md](./docs/errors.md)** for error handling details
|
|
119
|
+
|
|
120
|
+
## Contributing
|
|
121
|
+
|
|
122
|
+
Pull requests are not accepted. This project is AI-assisted and single-maintainer; every line is curated through a consistent workflow that external PRs would disrupt.
|
|
123
|
+
|
|
124
|
+
What is accepted:
|
|
125
|
+
|
|
126
|
+
- **Bug reports** with reproduction steps.
|
|
127
|
+
- **Feature requests** that align with the project's core principles.
|
|
128
|
+
- **Documentation corrections** for errors or omissions.
|
|
129
|
+
|
|
130
|
+
Read [`CONTRIBUTING.md`](./CONTRIBUTING.md) for details.
|
|
131
|
+
|
|
132
|
+
This project is maintained by [RezaLabs](https://rezalabs.com).
|
|
133
|
+
|
|
134
|
+
## Changelog
|
|
135
|
+
|
|
136
|
+
Notable changes between versions are documented in [`CHANGELOG.md`](./CHANGELOG.md). The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses [Semantic Versioning](https://semver.org/).
|
|
137
|
+
|
|
138
|
+
## Development Process
|
|
139
|
+
|
|
140
|
+
This project is built with heavy assistance from large language models.
|
|
141
|
+
|
|
142
|
+
**Why?** The entire codebase, from architecture decisions down to individual line implementations, is produced through iterative prompting and review with AI. This is intentional. The goal is to test the limits of what AI can generate when held to strict quality standards.
|
|
143
|
+
|
|
144
|
+
**What this means for you:**
|
|
145
|
+
- Every commit and every release is reviewed and approved by a human. AI generates proposals; I accept, reject, or modify them.
|
|
146
|
+
- The project is a deliberate exercise in AI-assisted engineering. The output is curated, tested, and documented.
|
|
147
|
+
- If you find an issue, it is my failure as the maintainer to catch it, not an excuse that "the AI wrote it." I own all results.
|
|
148
|
+
|
|
149
|
+
This project is as much a product of AI capability as it is of human editorial judgment. You are welcome to judge both.
|
|
150
|
+
|
|
151
|
+
## Support
|
|
152
|
+
|
|
153
|
+
If this project saves you time or solves a problem you would otherwise pay to fix, consider supporting its continued development.
|
|
154
|
+
|
|
155
|
+
- [Buy Me a Coffee](https://buymeacoffee.com/rezalabs)
|
|
156
|
+
- [Ko-fi](https://ko-fi.com/rezalabs)
|
|
157
|
+
|
|
158
|
+
Sponsorship is never required, but always appreciated. It funds maintenance, tooling, and the compute needed to iterate with AI assistance at this scale.
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
MIT License. See the full text in [`LICENSE`](./LICENSE).
|
|
163
|
+
|
|
164
|
+
Copyright (c) 2026 [RezaLabs](https://rezalabs.com)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
import { parseArgs } from "node:util";
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const pkgPath = path.resolve(__dirname, "..", "package.json");
|
|
10
|
+
const { version } = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
11
|
+
|
|
12
|
+
const { values: opts, positionals } = parseArgs({
|
|
13
|
+
allowPositionals: true,
|
|
14
|
+
options: {
|
|
15
|
+
version: { type: "boolean", short: "v", default: false },
|
|
16
|
+
help: { type: "boolean", short: "h", default: false },
|
|
17
|
+
analyze: { type: "boolean", short: "a", default: false },
|
|
18
|
+
fix: { type: "boolean", short: "f", default: false },
|
|
19
|
+
limit: { type: "string", short: "l", default: undefined },
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const HELP = `Usage: regex-inspector [options] <regex>
|
|
24
|
+
|
|
25
|
+
Parse, inspect, and fix regular expressions: detect ReDoS vulnerabilities,
|
|
26
|
+
extract AST structure, or auto-fix unsafe patterns.
|
|
27
|
+
|
|
28
|
+
When called without options, performs a quick safety check and exits with a
|
|
29
|
+
status code (0 = safe, 1 = unsafe). Use --analyze for a full diagnostic report
|
|
30
|
+
or --fix to generate a safe alternative.
|
|
31
|
+
|
|
32
|
+
Exit codes:
|
|
33
|
+
0 Pattern is safe
|
|
34
|
+
1 Pattern is unsafe, or an error occurred
|
|
35
|
+
|
|
36
|
+
Options:
|
|
37
|
+
-v, --version Display the version number
|
|
38
|
+
-h, --help Display this help message
|
|
39
|
+
-a, --analyze Show detailed analysis with severity, reasons, and suggested fix
|
|
40
|
+
-f, --fix Output an auto-fixed safe version of the regex
|
|
41
|
+
-l, --limit <n> Maximum allowed repetitions before flagging (default: 25)
|
|
42
|
+
<regex> The regular expression pattern to inspect
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
regex-inspector '(a+)+' Quick safety check (exit code indicates safe/unsafe)
|
|
46
|
+
regex-inspector -a '(a+)+' Detailed analysis report
|
|
47
|
+
regex-inspector -f '(x+x+)+y' Auto-fix output
|
|
48
|
+
regex-inspector -l 50 '(a+)+' Custom repetition limit`;
|
|
49
|
+
|
|
50
|
+
if (opts.help) {
|
|
51
|
+
console.log(HELP);
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (opts.version) {
|
|
56
|
+
console.log(version);
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (positionals.length === 0) {
|
|
61
|
+
console.error("Error: Missing regex argument.");
|
|
62
|
+
console.log(HELP);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (positionals.length > 1) {
|
|
67
|
+
console.error("Error: Too many positional arguments.");
|
|
68
|
+
console.log(HELP);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let inspect, fix;
|
|
73
|
+
try {
|
|
74
|
+
const distPath = path.resolve(__dirname, "..", "dist", "index.js");
|
|
75
|
+
const dist = await import(pathToFileURL(distPath).href);
|
|
76
|
+
inspect = dist.inspect;
|
|
77
|
+
fix = dist.fix ?? dist.fixRegex;
|
|
78
|
+
} catch (_err) {
|
|
79
|
+
console.error(
|
|
80
|
+
'regex-inspector: Build not found at dist/index.js. Run "npm run build" first.',
|
|
81
|
+
);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const pattern = positionals[0];
|
|
86
|
+
const limit = opts.limit !== undefined ? parseInt(opts.limit, 10) : undefined;
|
|
87
|
+
|
|
88
|
+
if (limit !== undefined && (Number.isNaN(limit) || limit < 1)) {
|
|
89
|
+
console.error("Error: --limit must be a positive integer.");
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const options = limit !== undefined ? { limit } : undefined;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
if (opts.analyze) {
|
|
97
|
+
const result = inspect(pattern, options);
|
|
98
|
+
if (result.safe) {
|
|
99
|
+
console.log("✓ Pattern is safe.");
|
|
100
|
+
} else {
|
|
101
|
+
console.log(`✗ Pattern is unsafe (severity: ${result.severity}).`);
|
|
102
|
+
}
|
|
103
|
+
if (result.reasons.length > 0) {
|
|
104
|
+
console.log("\nReasons:");
|
|
105
|
+
for (const reason of result.reasons) {
|
|
106
|
+
console.log(` • ${reason}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (result.fix) {
|
|
110
|
+
console.log(`\nSuggested fix: ${result.fix}`);
|
|
111
|
+
}
|
|
112
|
+
console.log("\nFull report:");
|
|
113
|
+
console.log(JSON.stringify(result, null, 2));
|
|
114
|
+
} else if (opts.fix) {
|
|
115
|
+
const result = fix(pattern, options);
|
|
116
|
+
if (result.fixed) {
|
|
117
|
+
console.log(result.fixed);
|
|
118
|
+
} else if (result.safe) {
|
|
119
|
+
console.log("Pattern is already safe; no fix needed.");
|
|
120
|
+
} else {
|
|
121
|
+
console.error("Could not auto-fix this pattern.");
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
const result = inspect(pattern, options);
|
|
126
|
+
if (result.safe) {
|
|
127
|
+
console.log("✓ safe");
|
|
128
|
+
process.exit(0);
|
|
129
|
+
} else {
|
|
130
|
+
console.log(`✗ ${result.severity}`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.error("Error:", err.message);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Node } from "./ast.js";
|
|
2
|
+
export type Severity = "none" | "low" | "high" | "critical";
|
|
3
|
+
export type AnalysisResult = {
|
|
4
|
+
safe: boolean;
|
|
5
|
+
severity: Severity;
|
|
6
|
+
reasons: string[];
|
|
7
|
+
starHeight: number;
|
|
8
|
+
repCount: number;
|
|
9
|
+
hasAlternationReDoS: boolean;
|
|
10
|
+
hasSequentialOverlap: boolean;
|
|
11
|
+
anchored: boolean;
|
|
12
|
+
hasStaticSuffix: boolean;
|
|
13
|
+
fix: string | null;
|
|
14
|
+
};
|
|
15
|
+
export type AnalyzeOptions = {
|
|
16
|
+
limit?: number;
|
|
17
|
+
};
|
|
18
|
+
export declare function analyze(ast: Node, opts?: AnalyzeOptions): AnalysisResult;
|
|
19
|
+
//# sourceMappingURL=analyze.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../src/analyze.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEX,IAAI,EAIJ,MAAM,UAAU,CAAC;AAIlB,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,UAAU,CAAC;AAE5D,MAAM,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAwkBF,wBAAgB,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,GAAE,cAAmB,GAAG,cAAc,CAuE5E"}
|