@safets-org/cli 1.0.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dioman Keita
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,172 @@
1
+ # SafeTS
2
+
3
+ **Finds common runtime crashes TypeScript can't detect.**
4
+
5
+ TypeScript catches type errors at compile time, but some crash patterns slip through even with `strict: true`. SafeTS uses the TypeScript Compiler API to detect them before they hit production.
6
+
7
+ ```
8
+ Cannot read properties of undefined (reading 'name')
9
+ ```
10
+
11
+ ---
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install --save-dev @safets-org/cli typescript
17
+ ```
18
+
19
+ No runtime TypeScript loader is required. SafeTS uses the TypeScript compiler already in your project.
20
+
21
+ The npm package is scoped as `@safets-org/cli`, but the installed command is still `safets`.
22
+
23
+ ---
24
+
25
+ ## Usage
26
+
27
+ ```bash
28
+ safets doctor
29
+ safets doctor --include-tests
30
+ safets fix
31
+ safets debt
32
+ safets baseline
33
+ safets doctor --fail-on-new
34
+ safets doctor --json
35
+ ```
36
+
37
+ Common flags:
38
+
39
+ ```bash
40
+ safets --help
41
+ safets --version
42
+ ```
43
+
44
+ ### JSON Output
45
+
46
+ Use `--json` when SafeTS output needs to be consumed by CI, scripts, bots, or editor integrations.
47
+
48
+ ```bash
49
+ safets doctor --json
50
+ safets debt --json
51
+ safets fix --json
52
+ safets baseline --json
53
+ ```
54
+
55
+ The JSON schema is versioned with `schemaVersion`. Each report includes:
56
+
57
+ - `safetsVersion`, `command`, and scan `options`
58
+ - `program.strategy`, `program.configFiles`, `program.fallback`, and `program.warnings`
59
+ - `baseline.present`, `baseline.compatible`, `baseline.mismatch`, and saved baseline metadata when relevant
60
+ - `summary.total`, `summary.new`, `summary.known`, `summary.byPattern`, and fallback count
61
+ - `debt[]` entries with current count, baseline count, and delta when a compatible baseline exists
62
+ - `crashes[]` entries with project-relative file path, location, expression, root expression, type, pattern, confidence, baseline status, crash path, and suggestions
63
+
64
+ `doctor --json --fail-on-new` still exits with code `1` when new crashes are found, but prints the JSON report first.
65
+
66
+ ---
67
+
68
+ ## How It Works
69
+
70
+ **SafeTS is read-only.** It never modifies your source code. `safets fix` only prints suggestions to stdout, and you apply them manually.
71
+
72
+ SafeTS scans your TypeScript files using the Compiler API, builds a type-checked program, and walks the AST looking for patterns that TypeScript's own checker allows but that can still crash at runtime.
73
+
74
+ Test files (`*.test.ts`, `*.spec.ts`, `/__tests__/`, and similar paths) are excluded by default. Use `--include-tests` to include them.
75
+
76
+ Files over 5000 lines, such as compiled bundles or generated Prisma clients, are automatically skipped.
77
+
78
+ ---
79
+
80
+ ## Real-World Validation
81
+
82
+ SafeTS v1.0.0 was tested in zero-setup mode against public TypeScript repositories: clone, build SafeTS, then run `safets doctor --json` without installing each target repo's dependencies. Test files and test-only tsconfigs are excluded by default, matching normal SafeTS CLI behavior.
83
+
84
+ | Repository | TS/TSX files | Strategy | Result | Duration | Perf | Fallback | Findings |
85
+ | --- | ---: | --- | --- | ---: | --- | --- | ---: |
86
+ | `google-gemini/gemini-cli` | 2108 | root-tsconfig | ok | 16s | ok | false | 247 |
87
+ | `vitejs/vite` | 563 | workspace-tsconfigs | ok | 21s | ok | false | 43 |
88
+ | `prisma/prisma` | 2701 | root-tsconfig | ok | 15s | ok | false | 267 |
89
+ | `supabase/supabase` | 6669 | root-tsconfig | ok | 24s | ok | false | 157 |
90
+ | `vitest-dev/vitest` | 2038 | workspace-tsconfigs | ok | 35s | ok | false | 298 |
91
+ | `withastro/astro` | 2094 | workspace-tsconfigs | ok | 24s | ok | false | 394 |
92
+
93
+ No target fell back to AST-only mode. See [docs/real-world-validation.md](./docs/real-world-validation.md) for commits, pattern breakdowns, methodology, and follow-up notes.
94
+
95
+ ---
96
+
97
+ ## The 9 Patterns
98
+
99
+ | Pattern | Confidence | Example |
100
+ | --- | --- | --- |
101
+ | Unsafe property access | HIGH | `user.profile.name` when `user` is `User \| undefined` |
102
+ | Unsafe destructuring | HIGH | `const { name } = user` when `user` is nullable |
103
+ | Unsafe array index access | HIGH | `arr[0].name` when `arr[0]` may be `undefined` |
104
+ | Unprotected JSON.parse | HIGH | `JSON.parse(input)` without `try/catch` |
105
+ | Unsafe process.env access | HIGH | `process.env.API_KEY` used directly |
106
+ | Non-null assertion on nullable | MEDIUM | `value!.method()` when `value` may be `undefined` |
107
+ | Unsafe access after await | MEDIUM | narrowing becomes stale after an `await` boundary |
108
+ | Unsafe Promise.all destructuring | MEDIUM | `const [a] = await Promise.all([...])` when result may be `undefined` |
109
+ | Unsafe Map/Record access | HIGH | `map[key].value` when key may not exist |
110
+
111
+ ---
112
+
113
+ ## Baseline And CI
114
+
115
+ The baseline system lets you track debt without blocking existing work.
116
+
117
+ ```bash
118
+ safets baseline
119
+ # creates .safets-baseline.json at the project root
120
+
121
+ git add .safets-baseline.json
122
+ git commit -m "chore: add SafeTS baseline"
123
+
124
+ safets doctor --fail-on-new
125
+ ```
126
+
127
+ `.safets-baseline.json` should be committed to version control so every teammate and CI job compares against the same snapshot.
128
+
129
+ The baseline stores scan options such as `includeTests`. If you run `doctor --fail-on-new` with different options than the saved baseline, SafeTS will refuse the comparison and ask you to regenerate the baseline.
130
+
131
+ ### GitHub Action
132
+
133
+ SafeTS can run directly in GitHub Actions:
134
+
135
+ ```yaml
136
+ - uses: Dioman-Keita/safets@v1.0.0
137
+ with:
138
+ fail-on-new: "true"
139
+ ```
140
+
141
+ See [docs/github-action.md](./docs/github-action.md) for the full workflow, inputs, and baseline setup.
142
+
143
+ ### Debt Tracking
144
+
145
+ ```bash
146
+ safets debt
147
+ ```
148
+
149
+ When a baseline exists, `debt` shows the delta per category since the snapshot.
150
+
151
+ ## Exit Codes
152
+
153
+ - `0`: successful run
154
+ - `1`: invalid CLI usage, incompatible `doctor --fail-on-new` baseline, or new crashes found with `--fail-on-new`
155
+
156
+ ---
157
+
158
+ ## License
159
+
160
+ MIT
161
+
162
+ ## Releases
163
+
164
+ SafeTS releases follow the documented workflow in [docs/release.md](./docs/release.md).
165
+
166
+ ## Contributing
167
+
168
+ Detector architecture and contribution expectations are documented in [docs/detectors.md](./docs/detectors.md).
169
+
170
+ ## Roadmap
171
+
172
+ The launch plan is tracked in [ROADMAP.md](./ROADMAP.md).
@@ -0,0 +1,3 @@
1
+ import type { CrashReport, ProgramResult } from "./utils/types.ts";
2
+ export declare function loadProgramRobust(projectRoot: string, includeTests: boolean): ProgramResult;
3
+ export declare function analyze(programResult: ProgramResult): CrashReport[];
@@ -0,0 +1,330 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import ts from "typescript";
4
+ import { detectFallbackPatterns, detectNonNullAssertionOnNullable, detectUnsafeAccessAfterAwait, detectUnsafeArrayAccess, detectUnsafeDestructuring, detectUnsafeEnvAccess, detectUnsafeJsonParse, detectUnsafeMapAccess, detectUnsafePromiseAllDestructuring, detectUnsafePropertyAccess, } from "./detectors/index.js";
5
+ import { findTsConfigFiles, findTsFiles, isAnalyzableTsFile, isTestFile, normalizeFilePath } from "./utils/files.js";
6
+ function filterProgramFiles(fileNames) {
7
+ return fileNames
8
+ .map((fileName) => normalizeFilePath(fileName))
9
+ .filter((fileName) => isAnalyzableTsFile(fileName));
10
+ }
11
+ function isCheckerUsable(program) {
12
+ try {
13
+ const checker = program.getTypeChecker();
14
+ const sourceFiles = program.getSourceFiles();
15
+ const firstUserFile = sourceFiles.find((sf) => !sf.isDeclarationFile && !sf.fileName.includes("node_modules"));
16
+ if (!firstUserFile) {
17
+ return false;
18
+ }
19
+ checker.getSymbolsInScope(firstUserFile, ts.SymbolFlags.Variable);
20
+ return true;
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ function loadTsConfigProgram(configPath, createProgram = true) {
27
+ try {
28
+ const { config, error } = ts.readConfigFile(configPath, ts.sys.readFile);
29
+ if (error) {
30
+ return {
31
+ program: null,
32
+ options: {},
33
+ fileNames: [],
34
+ filteredFileNames: [],
35
+ errors: [error],
36
+ };
37
+ }
38
+ const { options, fileNames, errors } = ts.parseJsonConfigFileContent(config, ts.sys, path.dirname(configPath), undefined, configPath);
39
+ const filteredFileNames = filterProgramFiles(fileNames);
40
+ if (filteredFileNames.length === 0 || !createProgram) {
41
+ return { program: null, options, fileNames, filteredFileNames, errors };
42
+ }
43
+ const program = ts.createProgram(filteredFileNames, {
44
+ ...options,
45
+ noEmit: true,
46
+ skipLibCheck: true,
47
+ });
48
+ return { program, options, fileNames, filteredFileNames, errors };
49
+ }
50
+ catch (error) {
51
+ return {
52
+ program: null,
53
+ options: {},
54
+ fileNames: [],
55
+ filteredFileNames: [],
56
+ errors: [error],
57
+ };
58
+ }
59
+ }
60
+ function uniqueFiles(fileNames) {
61
+ return [...new Set(fileNames.map((fileName) => normalizeFilePath(fileName)))];
62
+ }
63
+ export function loadProgramRobust(projectRoot, includeTests) {
64
+ const warnings = [];
65
+ const rootConfigPath = path.join(projectRoot, "tsconfig.json");
66
+ try {
67
+ if (ts.sys.fileExists(rootConfigPath)) {
68
+ const result = loadTsConfigProgram(rootConfigPath);
69
+ if (result.program && isCheckerUsable(result.program)) {
70
+ if (result.errors.length > 0) {
71
+ warnings.push(`tsconfig has ${result.errors.length} issue(s) - analysis may be partial`);
72
+ }
73
+ if (result.filteredFileNames.length !== result.fileNames.length) {
74
+ warnings.push(`Filtered ${result.fileNames.length - result.filteredFileNames.length} generated or bundled file(s) from tsconfig inputs`);
75
+ }
76
+ return {
77
+ program: result.program,
78
+ fallback: false,
79
+ warnings,
80
+ includeTests,
81
+ strategy: "root-tsconfig",
82
+ configFiles: [rootConfigPath],
83
+ rootFileCount: result.filteredFileNames.length,
84
+ filteredFileCount: result.fileNames.length - result.filteredFileNames.length,
85
+ };
86
+ }
87
+ if (result.filteredFileNames.length > 0) {
88
+ warnings.push(result.errors.length > 0
89
+ ? "tsconfig.json found but could not be parsed"
90
+ : "TypeChecker built but unusable - trying fallback options");
91
+ }
92
+ else if (result.errors.length > 0) {
93
+ warnings.push("tsconfig.json found but could not be parsed");
94
+ }
95
+ }
96
+ }
97
+ catch (error) {
98
+ warnings.push(`tsconfig load error: ${error.message}`);
99
+ }
100
+ try {
101
+ const workspaceConfigPaths = findTsConfigFiles(projectRoot, includeTests)
102
+ .filter((configPath) => normalizeFilePath(configPath) !== normalizeFilePath(rootConfigPath))
103
+ .sort();
104
+ const allFileNames = [];
105
+ const allFilteredFileNames = [];
106
+ const workspaceResults = [];
107
+ let totalErrors = 0;
108
+ for (const configPath of workspaceConfigPaths) {
109
+ const result = loadTsConfigProgram(configPath, false);
110
+ totalErrors += result.errors.length;
111
+ if (result.filteredFileNames.length > 0) {
112
+ allFileNames.push(...result.fileNames);
113
+ allFilteredFileNames.push(...result.filteredFileNames);
114
+ workspaceResults.push({ configPath, result });
115
+ }
116
+ }
117
+ const uniqueFilteredFileNames = uniqueFiles(allFilteredFileNames.filter((fileName) => includeTests || !isTestFile(fileName)));
118
+ const directFiles = findTsFiles(projectRoot).filter((fileName) => includeTests || !isTestFile(fileName));
119
+ if (uniqueFilteredFileNames.length > 0) {
120
+ const coveredFiles = new Set(uniqueFilteredFileNames);
121
+ const uncoveredFiles = directFiles.filter((fileName) => !coveredFiles.has(normalizeFilePath(fileName)));
122
+ const programInputs = [];
123
+ const scheduledFiles = new Set();
124
+ for (const { configPath, result } of workspaceResults) {
125
+ const fileNames = [];
126
+ for (const fileName of result.filteredFileNames) {
127
+ const normalizedFileName = normalizeFilePath(fileName);
128
+ if (!includeTests && isTestFile(normalizedFileName)) {
129
+ continue;
130
+ }
131
+ if (scheduledFiles.has(normalizedFileName)) {
132
+ continue;
133
+ }
134
+ scheduledFiles.add(normalizedFileName);
135
+ fileNames.push(normalizedFileName);
136
+ }
137
+ if (fileNames.length === 0) {
138
+ continue;
139
+ }
140
+ programInputs.push({
141
+ configFile: configPath,
142
+ fileNames,
143
+ options: {
144
+ ...result.options,
145
+ noEmit: true,
146
+ skipLibCheck: true,
147
+ },
148
+ rootFileCount: fileNames.length,
149
+ filteredFileCount: result.fileNames.length - result.filteredFileNames.length,
150
+ });
151
+ }
152
+ if (uncoveredFiles.length > 0) {
153
+ warnings.push(`Nested tsconfig files cover ${uniqueFilteredFileNames.length}/${directFiles.length} TypeScript file(s) - scanning ${uncoveredFiles.length} uncovered file(s) directly`);
154
+ const directFileNames = uncoveredFiles.filter((fileName) => {
155
+ const normalizedFileName = normalizeFilePath(fileName);
156
+ if (scheduledFiles.has(normalizedFileName)) {
157
+ return false;
158
+ }
159
+ scheduledFiles.add(normalizedFileName);
160
+ return true;
161
+ });
162
+ if (directFileNames.length > 0) {
163
+ programInputs.push({
164
+ configFile: null,
165
+ fileNames: directFileNames,
166
+ options: {
167
+ target: ts.ScriptTarget.ESNext,
168
+ module: ts.ModuleKind.CommonJS,
169
+ strict: true,
170
+ noUncheckedIndexedAccess: true,
171
+ skipLibCheck: true,
172
+ noEmit: true,
173
+ },
174
+ rootFileCount: directFileNames.length,
175
+ filteredFileCount: 0,
176
+ });
177
+ }
178
+ }
179
+ if (programInputs.length > 0) {
180
+ warnings.push(`No root tsconfig.json found - using ${workspaceResults.length} nested tsconfig.json file(s)`);
181
+ if (totalErrors > 0) {
182
+ warnings.push(`Nested tsconfig files have ${totalErrors} issue(s) - analysis may be partial`);
183
+ }
184
+ if (allFilteredFileNames.length !== allFileNames.length) {
185
+ warnings.push(`Filtered ${allFileNames.length - allFilteredFileNames.length} generated or bundled file(s) from nested tsconfig inputs`);
186
+ }
187
+ return {
188
+ program: null,
189
+ programInputs,
190
+ fallback: false,
191
+ warnings,
192
+ includeTests,
193
+ strategy: "workspace-tsconfigs",
194
+ configFiles: workspaceResults.map(({ configPath }) => configPath),
195
+ rootFileCount: uniqueFiles([...uniqueFilteredFileNames, ...uncoveredFiles]).length,
196
+ filteredFileCount: allFileNames.length - allFilteredFileNames.length,
197
+ };
198
+ }
199
+ else {
200
+ warnings.push("Nested tsconfig scan produced unusable TypeChecker");
201
+ }
202
+ }
203
+ }
204
+ catch (error) {
205
+ warnings.push(`Nested tsconfig scan error: ${error.message}`);
206
+ }
207
+ try {
208
+ const files = findTsFiles(projectRoot).filter((fileName) => includeTests || !isTestFile(fileName));
209
+ if (files.length > 0) {
210
+ const program = ts.createProgram(files, {
211
+ target: ts.ScriptTarget.ESNext,
212
+ module: ts.ModuleKind.CommonJS,
213
+ strict: true,
214
+ noUncheckedIndexedAccess: true,
215
+ skipLibCheck: true,
216
+ noEmit: true,
217
+ });
218
+ if (isCheckerUsable(program)) {
219
+ warnings.push("No usable tsconfig found - scanning TypeScript files directly");
220
+ return {
221
+ program,
222
+ fallback: false,
223
+ warnings,
224
+ includeTests,
225
+ strategy: "direct-scan",
226
+ configFiles: [],
227
+ rootFileCount: files.length,
228
+ filteredFileCount: 0,
229
+ };
230
+ }
231
+ warnings.push("Direct scan produced unusable TypeChecker");
232
+ }
233
+ else {
234
+ warnings.push("No TypeScript files found in project");
235
+ }
236
+ }
237
+ catch (error) {
238
+ warnings.push(`Direct scan error: ${error.message}`);
239
+ }
240
+ warnings.push("Running in AST-only fallback mode - results will be partial");
241
+ return {
242
+ program: null,
243
+ fallback: true,
244
+ warnings,
245
+ includeTests,
246
+ strategy: "fallback",
247
+ configFiles: [],
248
+ rootFileCount: 0,
249
+ filteredFileCount: 0,
250
+ };
251
+ }
252
+ function getUserSourceFiles(program, includeTests) {
253
+ try {
254
+ return program
255
+ .getRootFileNames()
256
+ .map((fileName) => program.getSourceFile(fileName))
257
+ .filter((sf) => sf !== undefined &&
258
+ !sf.isDeclarationFile &&
259
+ !sf.fileName.includes("node_modules") &&
260
+ isAnalyzableTsFile(sf.fileName) &&
261
+ (includeTests || !isTestFile(sf.fileName)));
262
+ }
263
+ catch {
264
+ return program.getSourceFiles().filter((sf) => !sf.isDeclarationFile &&
265
+ !sf.fileName.includes("node_modules") &&
266
+ isAnalyzableTsFile(sf.fileName) &&
267
+ (includeTests || !isTestFile(sf.fileName)));
268
+ }
269
+ }
270
+ export function analyze(programResult) {
271
+ const { includeTests } = programResult;
272
+ if (programResult.programInputs && programResult.programInputs.length > 0) {
273
+ const seen = new Set();
274
+ const all = [];
275
+ let oldProgram;
276
+ for (const entry of programResult.programInputs) {
277
+ try {
278
+ const program = ts.createProgram(entry.fileNames, entry.options, undefined, oldProgram);
279
+ oldProgram = program;
280
+ for (const crash of analyzeProgram(program, includeTests)) {
281
+ const key = [
282
+ normalizeFilePath(crash.file),
283
+ crash.line,
284
+ crash.col,
285
+ crash.expr,
286
+ crash.pattern,
287
+ ].join("\0");
288
+ if (!seen.has(key)) {
289
+ seen.add(key);
290
+ all.push(crash);
291
+ }
292
+ }
293
+ }
294
+ catch (error) {
295
+ programResult.warnings.push(`Failed to analyze workspace slice for ${entry.configFile ?? "uncovered files"}: ${error.message}`);
296
+ }
297
+ }
298
+ return all;
299
+ }
300
+ if (programResult.fallback || !programResult.program) {
301
+ const files = findTsFiles(process.cwd()).filter((filePath) => includeTests || !isTestFile(filePath));
302
+ const all = [];
303
+ for (const filePath of files) {
304
+ try {
305
+ const content = fs.readFileSync(filePath, "utf-8");
306
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.ESNext, true);
307
+ all.push(...detectFallbackPatterns(sourceFile));
308
+ }
309
+ catch {
310
+ // Ignore unreadable files in fallback mode.
311
+ }
312
+ }
313
+ return all;
314
+ }
315
+ return analyzeProgram(programResult.program, includeTests);
316
+ }
317
+ function analyzeProgram(program, includeTests) {
318
+ const checker = program.getTypeChecker();
319
+ const sourceFiles = getUserSourceFiles(program, includeTests);
320
+ const all = [];
321
+ for (const sourceFile of sourceFiles) {
322
+ try {
323
+ all.push(...detectUnsafePropertyAccess(sourceFile, checker), ...detectUnsafeDestructuring(sourceFile, checker), ...detectUnsafeArrayAccess(sourceFile, checker), ...detectUnsafeJsonParse(sourceFile), ...detectUnsafeEnvAccess(sourceFile, checker), ...detectNonNullAssertionOnNullable(sourceFile, checker), ...detectUnsafeAccessAfterAwait(sourceFile, checker), ...detectUnsafePromiseAllDestructuring(sourceFile, checker), ...detectUnsafeMapAccess(sourceFile, checker));
324
+ }
325
+ catch {
326
+ // Skip files that fail analysis instead of crashing the CLI.
327
+ }
328
+ }
329
+ return all;
330
+ }
@@ -0,0 +1,12 @@
1
+ import ts from "typescript";
2
+ import type { CrashReport } from "../utils/types.ts";
3
+ export declare function detectFallbackPatterns(sf: ts.SourceFile): CrashReport[];
4
+ export declare function detectUnsafePropertyAccess(sf: ts.SourceFile, checker: ts.TypeChecker): CrashReport[];
5
+ export declare function detectUnsafeDestructuring(sf: ts.SourceFile, checker: ts.TypeChecker): CrashReport[];
6
+ export declare function detectUnsafeArrayAccess(sf: ts.SourceFile, checker: ts.TypeChecker): CrashReport[];
7
+ export declare function detectUnsafeJsonParse(sf: ts.SourceFile): CrashReport[];
8
+ export declare function detectUnsafeEnvAccess(sf: ts.SourceFile, checker: ts.TypeChecker): CrashReport[];
9
+ export declare function detectNonNullAssertionOnNullable(sf: ts.SourceFile, checker: ts.TypeChecker): CrashReport[];
10
+ export declare function detectUnsafeAccessAfterAwait(sf: ts.SourceFile, checker: ts.TypeChecker): CrashReport[];
11
+ export declare function detectUnsafePromiseAllDestructuring(sf: ts.SourceFile, checker: ts.TypeChecker): CrashReport[];
12
+ export declare function detectUnsafeMapAccess(sf: ts.SourceFile, checker: ts.TypeChecker): CrashReport[];