redos-linter 0.1.0__tar.gz

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.
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.3
2
+ Name: redos-linter
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ Author: Lev Vereshchagin
6
+ Author-email: Lev Vereshchagin <mail@vrslev.com>
7
+ Requires-Dist: deno>=2.6.8
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+
11
+ # ReDoS Linter
12
+
13
+ A Python linter that detects Regular Expression Denial of Service (ReDoS) vulnerabilities in your code. ReDoS attacks occur when malicious input causes exponential backtracking in regular expressions, leading to denial of service.
14
+
15
+ ## Features
16
+
17
+ - Scans Python files for regular expressions
18
+ - Detects vulnerable regex patterns using the [recheck](https://github.com/makenowjust-labs/recheck) engine
19
+ - Provides detailed attack vectors when vulnerabilities are found
20
+ - Supports both file and directory scanning
21
+ - Clean, colored output for better readability
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install redos-linter
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ### Command Line
32
+
33
+ Check specific files or directories:
34
+
35
+ ```bash
36
+ # Check a single file
37
+ redos-linter myfile.py
38
+
39
+ # Check multiple files
40
+ redos-linter file1.py file2.py
41
+
42
+ # Check a directory (recursively scans all .py files)
43
+ redos-linter src/
44
+
45
+ # Check multiple directories
46
+ redos-linter src/ tests/
47
+ ```
48
+
49
+ ### Python Module
50
+
51
+ You can also run it as a Python module:
52
+
53
+ ```bash
54
+ python -m redos_linter src/
55
+ ```
56
+
57
+ ## Output
58
+
59
+ The linter provides clear output indicating:
60
+
61
+ - ✅ **Safe**: No ReDoS vulnerabilities detected
62
+ - ❌ **Vulnerable**: ReDoS vulnerability found with attack vector details
63
+
64
+ When vulnerabilities are detected, the output includes:
65
+ - The vulnerable regular expression
66
+ - File location (line and column)
67
+ - Source code context
68
+ - Attack string that can trigger the ReDoS
69
+ - Pump strings for the attack
70
+
71
+ ## Examples of Vulnerable Patterns
72
+
73
+ ```python
74
+ import re
75
+
76
+ # Exponential backtracking due to nested quantifiers
77
+ vulnerable_1 = re.compile(r"^(a+)+$")
78
+
79
+ # Exponential backtracking due to overlapping quantifiers
80
+ vulnerable_2 = re.compile(r"(a|aa)+")
81
+
82
+ # Complex nested pattern
83
+ vulnerable_3 = re.compile(r"([a-z]+)+$")
84
+
85
+ # Real-world example
86
+ vulnerable_4 = re.compile(r"^(name|email|phone),([a-zA-Z0-9_]+,)*([a-zA-Z0-9_]+)$")
87
+ ```
88
+
89
+ ## Examples of Safe Patterns
90
+
91
+ ```python
92
+ import re
93
+
94
+ # Simple safe regex
95
+ safe_1 = re.compile(r"^[a-zA-Z0-9_]+$")
96
+
97
+ # Email pattern (properly structured)
98
+ safe_2 = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
99
+
100
+ # Non-overlapping alternation
101
+ safe_3 = re.compile(r"^(cat|dog)$")
102
+ ```
103
+
104
+ ## Development
105
+
106
+ Install in development mode:
107
+
108
+ ```bash
109
+ # Clone the repository
110
+ git clone <repository-url>
111
+ cd redos-linter
112
+
113
+ # Install in development mode
114
+ uv sync
115
+
116
+ # Run tests
117
+ uv run pytest
118
+
119
+ # Run linter on test file
120
+ uv run python -m redos_linter test.py
121
+ ```
122
+
123
+ ## How It Works
124
+
125
+ 1. **AST Analysis**: Extracts all regular expression literals from Python source code using AST parsing
126
+ 2. **ReDoS Detection**: Uses the recheck engine to analyze each regex for potential exponential backtracking
127
+ 3. **Attack Generation**: When vulnerabilities are found, generates specific attack strings that demonstrate the issue
128
+ 4. **Reporting**: Provides clear, actionable output with source context and attack vectors
129
+
130
+ ## Requirements
131
+
132
+ - Python 3.10+
133
+ - Deno runtime (automatically managed via the deno Python package)
@@ -0,0 +1,123 @@
1
+ # ReDoS Linter
2
+
3
+ A Python linter that detects Regular Expression Denial of Service (ReDoS) vulnerabilities in your code. ReDoS attacks occur when malicious input causes exponential backtracking in regular expressions, leading to denial of service.
4
+
5
+ ## Features
6
+
7
+ - Scans Python files for regular expressions
8
+ - Detects vulnerable regex patterns using the [recheck](https://github.com/makenowjust-labs/recheck) engine
9
+ - Provides detailed attack vectors when vulnerabilities are found
10
+ - Supports both file and directory scanning
11
+ - Clean, colored output for better readability
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install redos-linter
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### Command Line
22
+
23
+ Check specific files or directories:
24
+
25
+ ```bash
26
+ # Check a single file
27
+ redos-linter myfile.py
28
+
29
+ # Check multiple files
30
+ redos-linter file1.py file2.py
31
+
32
+ # Check a directory (recursively scans all .py files)
33
+ redos-linter src/
34
+
35
+ # Check multiple directories
36
+ redos-linter src/ tests/
37
+ ```
38
+
39
+ ### Python Module
40
+
41
+ You can also run it as a Python module:
42
+
43
+ ```bash
44
+ python -m redos_linter src/
45
+ ```
46
+
47
+ ## Output
48
+
49
+ The linter provides clear output indicating:
50
+
51
+ - ✅ **Safe**: No ReDoS vulnerabilities detected
52
+ - ❌ **Vulnerable**: ReDoS vulnerability found with attack vector details
53
+
54
+ When vulnerabilities are detected, the output includes:
55
+ - The vulnerable regular expression
56
+ - File location (line and column)
57
+ - Source code context
58
+ - Attack string that can trigger the ReDoS
59
+ - Pump strings for the attack
60
+
61
+ ## Examples of Vulnerable Patterns
62
+
63
+ ```python
64
+ import re
65
+
66
+ # Exponential backtracking due to nested quantifiers
67
+ vulnerable_1 = re.compile(r"^(a+)+$")
68
+
69
+ # Exponential backtracking due to overlapping quantifiers
70
+ vulnerable_2 = re.compile(r"(a|aa)+")
71
+
72
+ # Complex nested pattern
73
+ vulnerable_3 = re.compile(r"([a-z]+)+$")
74
+
75
+ # Real-world example
76
+ vulnerable_4 = re.compile(r"^(name|email|phone),([a-zA-Z0-9_]+,)*([a-zA-Z0-9_]+)$")
77
+ ```
78
+
79
+ ## Examples of Safe Patterns
80
+
81
+ ```python
82
+ import re
83
+
84
+ # Simple safe regex
85
+ safe_1 = re.compile(r"^[a-zA-Z0-9_]+$")
86
+
87
+ # Email pattern (properly structured)
88
+ safe_2 = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
89
+
90
+ # Non-overlapping alternation
91
+ safe_3 = re.compile(r"^(cat|dog)$")
92
+ ```
93
+
94
+ ## Development
95
+
96
+ Install in development mode:
97
+
98
+ ```bash
99
+ # Clone the repository
100
+ git clone <repository-url>
101
+ cd redos-linter
102
+
103
+ # Install in development mode
104
+ uv sync
105
+
106
+ # Run tests
107
+ uv run pytest
108
+
109
+ # Run linter on test file
110
+ uv run python -m redos_linter test.py
111
+ ```
112
+
113
+ ## How It Works
114
+
115
+ 1. **AST Analysis**: Extracts all regular expression literals from Python source code using AST parsing
116
+ 2. **ReDoS Detection**: Uses the recheck engine to analyze each regex for potential exponential backtracking
117
+ 3. **Attack Generation**: When vulnerabilities are found, generates specific attack strings that demonstrate the issue
118
+ 4. **Reporting**: Provides clear, actionable output with source context and attack vectors
119
+
120
+ ## Requirements
121
+
122
+ - Python 3.10+
123
+ - Deno runtime (automatically managed via the deno Python package)
@@ -0,0 +1,60 @@
1
+ [project]
2
+ name = "redos-linter"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ authors = [{ name = "Lev Vereshchagin", email = "mail@vrslev.com" }]
7
+ requires-python = ">=3.10"
8
+ dependencies = ["deno>=2.6.8"]
9
+
10
+ [project.scripts]
11
+ redos-linter = "redos_linter:main"
12
+
13
+ [build-system]
14
+ requires = ["uv_build"]
15
+ build-backend = "uv_build"
16
+
17
+ [dependency-groups]
18
+ dev = ["mypy", "ruff", "pytest", "pytest-mock", "pytest-cov"]
19
+
20
+ [tool.ruff]
21
+ fix = true
22
+ unsafe-fixes = true
23
+ line-length = 120
24
+
25
+ [tool.ruff.format]
26
+ docstring-code-format = true
27
+
28
+ [tool.ruff.lint]
29
+ select = ["ALL"]
30
+ ignore = [
31
+ "EM",
32
+ "FBT",
33
+ "TRY003",
34
+ "D1",
35
+ "D203",
36
+ "D213",
37
+ "G004",
38
+ "FA",
39
+ "COM812",
40
+ "ISC001",
41
+ ]
42
+
43
+ [tool.ruff.lint.isort]
44
+ no-lines-before = ["standard-library", "local-folder"]
45
+ known-third-party = []
46
+ known-local-folder = []
47
+ lines-after-imports = 2
48
+
49
+ [tool.ruff.lint.extend-per-file-ignores]
50
+ "tests/*.py" = ["S101", "S311"]
51
+
52
+ [tool.mypy]
53
+ strict = true
54
+
55
+ [tool.flake8]
56
+ select = ["COP"]
57
+ exclude = [".venv"]
58
+
59
+ [tool.coverage.report]
60
+ exclude_also = ["if typing.TYPE_CHECKING:"]
@@ -0,0 +1,338 @@
1
+ import argparse
2
+ import ast
3
+ import json
4
+ import os
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import TypedDict, cast
9
+
10
+
11
+ try:
12
+ import deno # type: ignore[import-untyped]
13
+ except ImportError:
14
+ deno = None
15
+
16
+
17
+ # ANSI color codes for better output
18
+ class Colors:
19
+ RED = "\033[91m"
20
+ GREEN = "\033[92m"
21
+ YELLOW = "\033[93m"
22
+ BLUE = "\033[94m"
23
+ MAGENTA = "\033[95m"
24
+ CYAN = "\033[96m"
25
+ WHITE = "\033[97m"
26
+ BOLD = "\033[1m"
27
+ UNDERLINE = "\033[4m"
28
+ END = "\033[0m"
29
+
30
+
31
+ def use_colors() -> bool:
32
+ """Check if colors should be used (TTY and not disabled by environment)."""
33
+ return sys.stdout.isatty() and os.environ.get("NO_COLOR") is None
34
+
35
+
36
+ def get_deno_path() -> str:
37
+ python_executable = sys.executable
38
+ bin_dir = Path(python_executable).parent
39
+ deno_path = bin_dir / "deno"
40
+ if deno_path.exists():
41
+ return str(deno_path)
42
+
43
+ if deno is None:
44
+ raise FileNotFoundError("Could not find the deno executable: deno package not installed")
45
+
46
+ deno_dir = Path(deno.__file__).parent
47
+ deno_path = deno_dir / "bin" / "deno"
48
+ if deno_path.exists():
49
+ return str(deno_path)
50
+
51
+ raise FileNotFoundError("Could not find the deno executable.")
52
+
53
+
54
+ class RegexInfo(TypedDict):
55
+ regex: str
56
+ line: int
57
+ col: int
58
+
59
+
60
+ class RegexExtractor(ast.NodeVisitor):
61
+ def __init__(self) -> None:
62
+ self.regexes: list[RegexInfo] = []
63
+
64
+ def visit_Call(self, node: ast.Call) -> None:
65
+ if (
66
+ (
67
+ isinstance(node.func, ast.Attribute)
68
+ and isinstance(node.func.value, ast.Name)
69
+ and node.func.value.id == "re"
70
+ and node.func.attr
71
+ in (
72
+ "compile",
73
+ "search",
74
+ "match",
75
+ "fullmatch",
76
+ "split",
77
+ "findall",
78
+ "finditer",
79
+ "sub",
80
+ "subn",
81
+ )
82
+ )
83
+ and node.args
84
+ and isinstance(node.args[0], ast.Constant)
85
+ and isinstance(node.args[0].value, str)
86
+ ):
87
+ self.regexes.append(
88
+ {
89
+ "regex": node.args[0].value,
90
+ "line": node.lineno,
91
+ "col": node.col_offset,
92
+ }
93
+ )
94
+ self.generic_visit(node)
95
+
96
+
97
+ class RegexInfoWithContext(TypedDict):
98
+ regex: str
99
+ line: int
100
+ col: int
101
+ source_lines: list[str]
102
+
103
+
104
+ def extract_regexes_from_file(filepath: str) -> list[RegexInfoWithContext]:
105
+ with Path(filepath).open() as f:
106
+ code = f.read()
107
+ tree = ast.parse(code, filename=filepath)
108
+ extractor = RegexExtractor()
109
+ extractor.visit(tree)
110
+
111
+ lines = code.splitlines()
112
+ return [
113
+ RegexInfoWithContext(
114
+ regex=ri["regex"],
115
+ line=ri["line"],
116
+ col=ri["col"],
117
+ source_lines=get_source_context(lines, ri["line"]),
118
+ )
119
+ for ri in extractor.regexes
120
+ ]
121
+
122
+
123
+ def get_source_context(lines: list[str], line_num: int, context: int = 2) -> list[str]:
124
+ """Get source lines with context (before and after the target line)."""
125
+ start = max(0, line_num - context - 1) # -1 because line_num is 1-indexed
126
+ end = min(len(lines), line_num + context)
127
+
128
+ context_lines = []
129
+ for i in range(start, end):
130
+ prefix = ">>> " if i == line_num - 1 else " "
131
+ context_lines.append(f"{prefix}{i + 1:3d}: {lines[i]}")
132
+
133
+ return context_lines
134
+
135
+
136
+ def collect_files(paths: list[str]) -> list[str]:
137
+ """Collect Python files from the given paths."""
138
+ files_to_check: list[str] = []
139
+ for p in paths:
140
+ path = Path(p)
141
+ if path.is_dir():
142
+ files_to_check.extend(str(f) for f in path.rglob("*.py"))
143
+ else:
144
+ files_to_check.append(p)
145
+ return [f for f in files_to_check if ".venv" not in f and "node_modules" not in f]
146
+
147
+
148
+ class RegexInfoWithFile(TypedDict):
149
+ regex: str
150
+ filePath: str
151
+ line: int
152
+ col: int
153
+ source_lines: list[str]
154
+
155
+
156
+ def collect_all_regexes(files: list[str]) -> list[RegexInfoWithFile]:
157
+ """Extract all regexes from the given files."""
158
+ regexes_with_paths: list[RegexInfoWithFile] = []
159
+ for file_path in files:
160
+ regexes = extract_regexes_from_file(file_path)
161
+ regexes_with_paths.extend(
162
+ {
163
+ "regex": regex_info["regex"],
164
+ "filePath": file_path,
165
+ "line": regex_info["line"],
166
+ "col": regex_info["col"],
167
+ "source_lines": regex_info["source_lines"],
168
+ }
169
+ for regex_info in regexes
170
+ )
171
+ return regexes_with_paths
172
+
173
+
174
+ class RecheckResult(TypedDict):
175
+ status: str
176
+ sourceLines: list[str]
177
+ regex: str
178
+ filePath: str
179
+ line: int
180
+ col: int
181
+ attack: dict[str, object | None]
182
+
183
+
184
+ class AttackInfo(TypedDict):
185
+ string: str
186
+ base: int
187
+ pumps: list[dict[str, str]]
188
+
189
+
190
+ def check_regexes_with_deno(regexes: list[RegexInfoWithFile]) -> list[RecheckResult] | None:
191
+ """Check regexes for vulnerabilities using Deno."""
192
+ deno_path = get_deno_path()
193
+ checker_path = Path(__file__).parent / "checker.js"
194
+ bundle_path = Path(__file__).parent / "recheck.bundle.js"
195
+
196
+ env = os.environ.copy()
197
+ env["RECHECK_BACKEND"] = "pure"
198
+
199
+ process = subprocess.run( # noqa: S603
200
+ [deno_path, "run", "--allow-read", str(checker_path), str(bundle_path)],
201
+ input=json.dumps(regexes).encode("utf-8"),
202
+ capture_output=True,
203
+ env=env,
204
+ check=False,
205
+ )
206
+
207
+ if process.stderr:
208
+ if use_colors():
209
+ sys.stderr.write(f"{Colors.RED}Error: {Colors.END}{process.stderr.decode('utf-8')}")
210
+ else:
211
+ sys.stderr.write(f"Error: {process.stderr.decode('utf-8')}")
212
+ return None
213
+
214
+ output = process.stdout.decode("utf-8")
215
+ if not output:
216
+ if use_colors():
217
+ sys.stdout.write(f"{Colors.GREEN}No vulnerable regexes found.{Colors.END}\n")
218
+ else:
219
+ sys.stdout.write("No vulnerable regexes found.\n")
220
+ return None
221
+
222
+ try:
223
+ return cast("list[RecheckResult] | None", json.loads(output))
224
+ except json.JSONDecodeError:
225
+ if use_colors():
226
+ sys.stderr.write(f"{Colors.RED}Error: Invalid response from checker{Colors.END}\n")
227
+ else:
228
+ sys.stderr.write("Error: Invalid response from checker\n")
229
+ return None
230
+
231
+
232
+ def main() -> None: # noqa: PLR0912,PLR0915,C901
233
+ """Run the ReDoS linter."""
234
+ parser = argparse.ArgumentParser(
235
+ description="ReDoS Linter - Detects Regular Expression Denial of Service vulnerabilities"
236
+ )
237
+ parser.add_argument(
238
+ "paths",
239
+ metavar="path",
240
+ type=str,
241
+ nargs="+",
242
+ help="Files or directories to check",
243
+ )
244
+ args = parser.parse_args()
245
+
246
+ files_to_check = collect_files(args.paths)
247
+ regexes_with_paths = collect_all_regexes(files_to_check)
248
+
249
+ if not regexes_with_paths:
250
+ if use_colors():
251
+ sys.stdout.write(f"{Colors.GREEN}No vulnerable regexes found.{Colors.END}\n")
252
+ else:
253
+ sys.stdout.write("No vulnerable regexes found.\n")
254
+ return
255
+
256
+ results = check_regexes_with_deno(regexes_with_paths)
257
+ if results is None:
258
+ return
259
+
260
+ total_regexes = len(results)
261
+ vulnerable_count = sum(1 for r in results if r["status"] == "vulnerable")
262
+
263
+ suffix = "s" if total_regexes != 1 else ""
264
+ analyzing_msg = f"Analyzing {total_regexes} regular expression{suffix}...\n\n"
265
+ if use_colors():
266
+ sys.stdout.write(f"{Colors.BLUE}{analyzing_msg}{Colors.END}")
267
+ else:
268
+ sys.stdout.write(analyzing_msg)
269
+
270
+ for result in results:
271
+ if result["status"] == "vulnerable":
272
+ attack: AttackInfo | None = result.get("attack") # type: ignore[assignment]
273
+ location = f"{result.get('filePath', 'unknown')}:{result.get('line', '?')}:{result.get('col', '?')}"
274
+ if use_colors():
275
+ sys.stdout.write(f"{Colors.RED}VULNERABLE:{Colors.END} {location}\n")
276
+ sys.stdout.write(f" {Colors.YELLOW}Pattern:{Colors.END} {Colors.CYAN}{result['regex']}{Colors.END}\n")
277
+ sys.stdout.write(
278
+ f" {Colors.YELLOW}Issue:{Colors.END} Exponential backtracking due to nested quantifiers\n"
279
+ )
280
+ if attack:
281
+ attack_str = json.dumps(attack.get("string", "unknown"))
282
+ sys.stdout.write(
283
+ f" {Colors.YELLOW}Attack string:{Colors.END} {Colors.MAGENTA}{attack_str}{Colors.END}\n"
284
+ )
285
+ if attack.get("pumps") and attack["pumps"]:
286
+ pump = attack["pumps"][0]
287
+ pump_msg = (
288
+ f'Repeating {Colors.CYAN}"{pump["pump"]}"{Colors.END} causes catastrophic backtracking'
289
+ )
290
+ sys.stdout.write(f" {Colors.YELLOW}Exploit:{Colors.END} {pump_msg}\n")
291
+ complexity = attack.get("base", "unknown")
292
+ sys.stdout.write(
293
+ f" {Colors.YELLOW}Complexity:{Colors.END} {complexity} character repetitions\n"
294
+ )
295
+ sys.stdout.write(f" {Colors.YELLOW}Source context:{Colors.END}\n")
296
+ for line in result.get("sourceLines", []):
297
+ sys.stdout.write(f" {line}\n")
298
+ sys.stdout.write("\n")
299
+ else:
300
+ sys.stdout.write(f"VULNERABLE: {location}\n")
301
+ sys.stdout.write(f" Pattern: {result['regex']}\n")
302
+ sys.stdout.write(" Issue: Exponential backtracking due to nested quantifiers\n")
303
+ if attack:
304
+ sys.stdout.write(f" Attack string: {json.dumps(attack.get('string', 'unknown'))}\n")
305
+ if attack.get("pumps") and attack["pumps"]:
306
+ pump = attack["pumps"][0]
307
+ sys.stdout.write(f' Exploit: Repeating "{pump["pump"]}" causes catastrophic backtracking\n')
308
+ sys.stdout.write(f" Complexity: {attack.get('base', 'unknown')} character repetitions\n")
309
+ sys.stdout.write(" Source context:\n")
310
+ for line in result.get("sourceLines", []):
311
+ sys.stdout.write(f" {line}\n")
312
+ sys.stdout.write("\n")
313
+
314
+ if vulnerable_count == 0:
315
+ safe_msg = f"All {total_regexes} regex{'es' if total_regexes != 1 else ''} appear safe from ReDoS attacks.\n"
316
+ if use_colors():
317
+ sys.stdout.write(f"{Colors.GREEN}{safe_msg}{Colors.END}")
318
+ else:
319
+ sys.stdout.write(safe_msg)
320
+ else:
321
+ vuln_msg = f"Found {vulnerable_count} vulnerable regex{'es' if vulnerable_count != 1 else ''} out of {total_regexes} total.\n" # noqa: E501
322
+ if use_colors():
323
+ sys.stdout.write(f"{Colors.RED}{vuln_msg}{Colors.END}")
324
+ else:
325
+ sys.stdout.write(vuln_msg)
326
+ sys.stdout.write("\n")
327
+ if use_colors():
328
+ sys.stdout.write(f"{Colors.BLUE}Recommendations:{Colors.END}\n")
329
+ else:
330
+ sys.stdout.write("Recommendations:\n")
331
+ sys.stdout.write(" - Use atomic grouping or possessive quantifiers where possible\n")
332
+ sys.stdout.write(" - Avoid nested quantifiers like (a+)+ or (a*)*\n")
333
+ sys.stdout.write(" - Consider using re.compile with re.IGNORECASE carefully\n")
334
+ sys.stdout.write(" - Test regexes with long, malformed input strings\n")
335
+
336
+
337
+ if __name__ == "__main__":
338
+ main()
@@ -0,0 +1,5 @@
1
+ from . import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -0,0 +1,37 @@
1
+ const bundlePath = Deno.args[0];
2
+ const { recheck } = await import(bundlePath);
3
+
4
+ function main(content) {
5
+ const regexesWithPaths = JSON.parse(content);
6
+ const results = [];
7
+
8
+ for (const item of regexesWithPaths) {
9
+ const { regex, filePath, line, col, source_lines } = item;
10
+ const result = recheck.checkSync(regex, '');
11
+ results.push({
12
+ regex: regex,
13
+ filePath: filePath,
14
+ line: line,
15
+ col: col,
16
+ sourceLines: source_lines,
17
+ status: result.status,
18
+ attack: result.attack
19
+ });
20
+ }
21
+
22
+ console.log(JSON.stringify(results));
23
+ }
24
+
25
+ (async () => {
26
+ const reader = Deno.stdin.readable.getReader();
27
+ let content = '';
28
+ const decoder = new TextDecoder();
29
+ while (true) {
30
+ const { done, value } = await reader.read();
31
+ if (done) {
32
+ break;
33
+ }
34
+ content += decoder.decode(value);
35
+ }
36
+ main(content);
37
+ })();
File without changes
@@ -0,0 +1 @@
1
+ export * as recheck from 'recheck';