sandlot 0.1.3 → 0.2.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/dist/browser/bundler.d.ts +68 -0
- package/dist/browser/bundler.d.ts.map +1 -0
- package/dist/browser/executor.d.ts +46 -0
- package/dist/browser/executor.d.ts.map +1 -0
- package/dist/browser/index.d.ts +9 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +2692 -0
- package/dist/browser/preset.d.ts +63 -0
- package/dist/browser/preset.d.ts.map +1 -0
- package/dist/commands/index.d.ts +20 -11
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/types.d.ts +31 -132
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/core/bundler-utils.d.ts +142 -0
- package/dist/core/bundler-utils.d.ts.map +1 -0
- package/dist/core/esm-types-resolver.d.ts +125 -0
- package/dist/core/esm-types-resolver.d.ts.map +1 -0
- package/dist/core/executor.d.ts +35 -0
- package/dist/core/executor.d.ts.map +1 -0
- package/dist/{fs.d.ts → core/fs.d.ts} +27 -29
- package/dist/core/fs.d.ts.map +1 -0
- package/dist/core/sandbox.d.ts +30 -0
- package/dist/core/sandbox.d.ts.map +1 -0
- package/dist/core/sandlot.d.ts +30 -0
- package/dist/core/sandlot.d.ts.map +1 -0
- package/dist/core/shared-module-registry.d.ts +46 -0
- package/dist/core/shared-module-registry.d.ts.map +1 -0
- package/dist/core/typechecker.d.ts +60 -0
- package/dist/core/typechecker.d.ts.map +1 -0
- package/dist/index.d.ts +11 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1405 -2049
- package/dist/node/bundler.d.ts +48 -0
- package/dist/node/bundler.d.ts.map +1 -0
- package/dist/node/executor.d.ts +48 -0
- package/dist/node/executor.d.ts.map +1 -0
- package/dist/node/index.d.ts +9 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +2646 -0
- package/dist/node/preset.d.ts +62 -0
- package/dist/node/preset.d.ts.map +1 -0
- package/dist/types.d.ts +525 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +27 -8
- package/src/browser/bundler.ts +294 -0
- package/src/browser/executor.ts +71 -0
- package/src/browser/index.ts +57 -0
- package/src/browser/preset.ts +179 -0
- package/src/commands/index.ts +526 -43
- package/src/commands/types.ts +82 -146
- package/src/core/bundler-utils.ts +630 -0
- package/src/core/esm-types-resolver.ts +432 -0
- package/src/core/executor.ts +161 -0
- package/src/{fs.ts → core/fs.ts} +59 -37
- package/src/core/sandbox.ts +621 -0
- package/src/core/sandlot.ts +77 -0
- package/src/core/shared-module-registry.ts +138 -0
- package/src/core/typechecker.ts +607 -0
- package/src/index.ts +104 -139
- package/src/node/bundler.ts +194 -0
- package/src/node/executor.ts +87 -0
- package/src/node/index.ts +39 -0
- package/src/node/preset.ts +178 -0
- package/src/types.ts +668 -0
- package/README.md +0 -243
- package/dist/build-emitter.d.ts +0 -47
- package/dist/build-emitter.d.ts.map +0 -1
- package/dist/builder.d.ts +0 -370
- package/dist/builder.d.ts.map +0 -1
- package/dist/bundler.d.ts +0 -148
- package/dist/bundler.d.ts.map +0 -1
- package/dist/commands/compile.d.ts +0 -13
- package/dist/commands/compile.d.ts.map +0 -1
- package/dist/commands/packages.d.ts +0 -17
- package/dist/commands/packages.d.ts.map +0 -1
- package/dist/commands/run.d.ts +0 -40
- package/dist/commands/run.d.ts.map +0 -1
- package/dist/commands.d.ts +0 -179
- package/dist/commands.d.ts.map +0 -1
- package/dist/fs.d.ts.map +0 -1
- package/dist/internal.d.ts +0 -79
- package/dist/internal.d.ts.map +0 -1
- package/dist/internal.js +0 -1976
- package/dist/loader.d.ts +0 -164
- package/dist/loader.d.ts.map +0 -1
- package/dist/packages.d.ts +0 -199
- package/dist/packages.d.ts.map +0 -1
- package/dist/runner.d.ts +0 -314
- package/dist/runner.d.ts.map +0 -1
- package/dist/sandbox-manager.d.ts +0 -261
- package/dist/sandbox-manager.d.ts.map +0 -1
- package/dist/sandbox.d.ts +0 -267
- package/dist/sandbox.d.ts.map +0 -1
- package/dist/shared-modules.d.ts +0 -148
- package/dist/shared-modules.d.ts.map +0 -1
- package/dist/shared-resources.d.ts +0 -102
- package/dist/shared-resources.d.ts.map +0 -1
- package/dist/ts-libs.d.ts +0 -98
- package/dist/ts-libs.d.ts.map +0 -1
- package/dist/typechecker.d.ts +0 -127
- package/dist/typechecker.d.ts.map +0 -1
- package/src/build-emitter.ts +0 -64
- package/src/builder.ts +0 -498
- package/src/bundler.ts +0 -542
- package/src/commands/compile.ts +0 -236
- package/src/commands/packages.ts +0 -154
- package/src/commands/run.ts +0 -245
- package/src/internal.ts +0 -119
- package/src/loader.ts +0 -229
- package/src/packages.ts +0 -936
- package/src/sandbox.ts +0 -396
- package/src/shared-modules.ts +0 -280
- package/src/shared-resources.ts +0 -166
- package/src/ts-libs.ts +0 -320
- package/src/typechecker.ts +0 -635
package/src/typechecker.ts
DELETED
|
@@ -1,635 +0,0 @@
|
|
|
1
|
-
import type { IFileSystem } from "just-bash/browser";
|
|
2
|
-
import ts from "typescript";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Options for type checking
|
|
6
|
-
*/
|
|
7
|
-
export interface TypecheckOptions {
|
|
8
|
-
/**
|
|
9
|
-
* The virtual filesystem to read source files from
|
|
10
|
-
*/
|
|
11
|
-
fs: IFileSystem;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Entry point path (absolute path in the virtual filesystem).
|
|
15
|
-
* TypeScript will discover all imported files from this root.
|
|
16
|
-
*/
|
|
17
|
-
entryPoint: string;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Path to tsconfig.json in the virtual filesystem.
|
|
21
|
-
* Default: "/tsconfig.json"
|
|
22
|
-
*/
|
|
23
|
-
tsconfigPath?: string;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Pre-loaded TypeScript lib files (e.g., lib.dom.d.ts, lib.es2020.d.ts).
|
|
27
|
-
* Map from lib name (e.g., "dom", "es2020") to file content.
|
|
28
|
-
* If provided, enables proper type checking for built-in APIs.
|
|
29
|
-
* Use fetchAndCacheLibs() from ts-libs to fetch these.
|
|
30
|
-
*/
|
|
31
|
-
libFiles?: Map<string, string>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* A single diagnostic message from type checking
|
|
36
|
-
*/
|
|
37
|
-
export interface Diagnostic {
|
|
38
|
-
/**
|
|
39
|
-
* File path where the error occurred (null for global errors)
|
|
40
|
-
*/
|
|
41
|
-
file: string | null;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 1-based line number (null if not applicable)
|
|
45
|
-
*/
|
|
46
|
-
line: number | null;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* 1-based column number (null if not applicable)
|
|
50
|
-
*/
|
|
51
|
-
column: number | null;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* TypeScript error code (e.g., 2322 for type mismatch)
|
|
55
|
-
*/
|
|
56
|
-
code: number;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Severity category
|
|
60
|
-
*/
|
|
61
|
-
category: "error" | "warning" | "suggestion" | "message";
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Human-readable error message
|
|
65
|
-
*/
|
|
66
|
-
message: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Result of type checking
|
|
71
|
-
*/
|
|
72
|
-
export interface TypecheckResult {
|
|
73
|
-
/**
|
|
74
|
-
* All diagnostics from type checking
|
|
75
|
-
*/
|
|
76
|
-
diagnostics: Diagnostic[];
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* True if there are any errors (not just warnings)
|
|
80
|
-
*/
|
|
81
|
-
hasErrors: boolean;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* List of files that were checked
|
|
85
|
-
*/
|
|
86
|
-
checkedFiles: string[];
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Map category enum to string
|
|
91
|
-
*/
|
|
92
|
-
function categoryToString(
|
|
93
|
-
category: ts.DiagnosticCategory
|
|
94
|
-
): "error" | "warning" | "suggestion" | "message" {
|
|
95
|
-
switch (category) {
|
|
96
|
-
case ts.DiagnosticCategory.Error:
|
|
97
|
-
return "error";
|
|
98
|
-
case ts.DiagnosticCategory.Warning:
|
|
99
|
-
return "warning";
|
|
100
|
-
case ts.DiagnosticCategory.Suggestion:
|
|
101
|
-
return "suggestion";
|
|
102
|
-
case ts.DiagnosticCategory.Message:
|
|
103
|
-
return "message";
|
|
104
|
-
default:
|
|
105
|
-
return "error";
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Convert TypeScript diagnostic to our Diagnostic format
|
|
111
|
-
*/
|
|
112
|
-
function convertDiagnostic(diag: ts.Diagnostic): Diagnostic {
|
|
113
|
-
let file: string | null = null;
|
|
114
|
-
let line: number | null = null;
|
|
115
|
-
let column: number | null = null;
|
|
116
|
-
|
|
117
|
-
if (diag.file && diag.start !== undefined) {
|
|
118
|
-
file = diag.file.fileName;
|
|
119
|
-
const pos = diag.file.getLineAndCharacterOfPosition(diag.start);
|
|
120
|
-
line = pos.line + 1; // Convert to 1-based
|
|
121
|
-
column = pos.character + 1; // Convert to 1-based
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
file,
|
|
126
|
-
line,
|
|
127
|
-
column,
|
|
128
|
-
code: diag.code,
|
|
129
|
-
category: categoryToString(diag.category),
|
|
130
|
-
message: ts.flattenDiagnosticMessageText(diag.messageText, "\n"),
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Normalize path to absolute
|
|
136
|
-
*/
|
|
137
|
-
function normalizePath(path: string): string {
|
|
138
|
-
if (!path.startsWith("/")) {
|
|
139
|
-
return "/" + path;
|
|
140
|
-
}
|
|
141
|
-
return path;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Pre-load files from the async filesystem into a sync cache
|
|
146
|
-
*/
|
|
147
|
-
async function preloadFiles(
|
|
148
|
-
fs: IFileSystem
|
|
149
|
-
): Promise<Map<string, string>> {
|
|
150
|
-
const cache = new Map<string, string>();
|
|
151
|
-
const allPaths = fs.getAllPaths();
|
|
152
|
-
|
|
153
|
-
for (const path of allPaths) {
|
|
154
|
-
// Only cache TypeScript-related files
|
|
155
|
-
if (
|
|
156
|
-
path.endsWith(".ts") ||
|
|
157
|
-
path.endsWith(".tsx") ||
|
|
158
|
-
path.endsWith(".js") ||
|
|
159
|
-
path.endsWith(".jsx") ||
|
|
160
|
-
path.endsWith(".json") ||
|
|
161
|
-
path.endsWith(".d.ts")
|
|
162
|
-
) {
|
|
163
|
-
try {
|
|
164
|
-
const stat = await fs.stat(path);
|
|
165
|
-
if (stat.isFile) {
|
|
166
|
-
const content = await fs.readFile(path);
|
|
167
|
-
cache.set(path, content);
|
|
168
|
-
}
|
|
169
|
-
} catch {
|
|
170
|
-
// Skip files that can't be read
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return cache;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Parse tsconfig.json and return compiler options
|
|
180
|
-
*
|
|
181
|
-
* @param configText - The content of tsconfig.json
|
|
182
|
-
* @param _configPath - Path to the tsconfig (unused but kept for future reference resolution)
|
|
183
|
-
*/
|
|
184
|
-
function parseTsConfig(
|
|
185
|
-
configText: string,
|
|
186
|
-
_configPath: string
|
|
187
|
-
): ts.CompilerOptions {
|
|
188
|
-
try {
|
|
189
|
-
const config = JSON.parse(configText);
|
|
190
|
-
const compilerOptions = config.compilerOptions || {};
|
|
191
|
-
|
|
192
|
-
// Map string values to TypeScript enums
|
|
193
|
-
const options: ts.CompilerOptions = {
|
|
194
|
-
...getDefaultCompilerOptions(),
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
// Target
|
|
198
|
-
if (compilerOptions.target) {
|
|
199
|
-
const targetMap: Record<string, ts.ScriptTarget> = {
|
|
200
|
-
es5: ts.ScriptTarget.ES5,
|
|
201
|
-
es6: ts.ScriptTarget.ES2015,
|
|
202
|
-
es2015: ts.ScriptTarget.ES2015,
|
|
203
|
-
es2016: ts.ScriptTarget.ES2016,
|
|
204
|
-
es2017: ts.ScriptTarget.ES2017,
|
|
205
|
-
es2018: ts.ScriptTarget.ES2018,
|
|
206
|
-
es2019: ts.ScriptTarget.ES2019,
|
|
207
|
-
es2020: ts.ScriptTarget.ES2020,
|
|
208
|
-
es2021: ts.ScriptTarget.ES2021,
|
|
209
|
-
es2022: ts.ScriptTarget.ES2022,
|
|
210
|
-
esnext: ts.ScriptTarget.ESNext,
|
|
211
|
-
};
|
|
212
|
-
options.target = targetMap[compilerOptions.target.toLowerCase()] ?? ts.ScriptTarget.ES2020;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Module
|
|
216
|
-
if (compilerOptions.module) {
|
|
217
|
-
const moduleMap: Record<string, ts.ModuleKind> = {
|
|
218
|
-
commonjs: ts.ModuleKind.CommonJS,
|
|
219
|
-
amd: ts.ModuleKind.AMD,
|
|
220
|
-
umd: ts.ModuleKind.UMD,
|
|
221
|
-
system: ts.ModuleKind.System,
|
|
222
|
-
es6: ts.ModuleKind.ES2015,
|
|
223
|
-
es2015: ts.ModuleKind.ES2015,
|
|
224
|
-
es2020: ts.ModuleKind.ES2020,
|
|
225
|
-
es2022: ts.ModuleKind.ES2022,
|
|
226
|
-
esnext: ts.ModuleKind.ESNext,
|
|
227
|
-
node16: ts.ModuleKind.Node16,
|
|
228
|
-
nodenext: ts.ModuleKind.NodeNext,
|
|
229
|
-
};
|
|
230
|
-
options.module = moduleMap[compilerOptions.module.toLowerCase()] ?? ts.ModuleKind.ESNext;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Module resolution
|
|
234
|
-
if (compilerOptions.moduleResolution) {
|
|
235
|
-
const resolutionMap: Record<string, ts.ModuleResolutionKind> = {
|
|
236
|
-
classic: ts.ModuleResolutionKind.Classic,
|
|
237
|
-
node: ts.ModuleResolutionKind.NodeJs,
|
|
238
|
-
node10: ts.ModuleResolutionKind.NodeJs,
|
|
239
|
-
node16: ts.ModuleResolutionKind.Node16,
|
|
240
|
-
nodenext: ts.ModuleResolutionKind.NodeNext,
|
|
241
|
-
bundler: ts.ModuleResolutionKind.Bundler,
|
|
242
|
-
};
|
|
243
|
-
options.moduleResolution =
|
|
244
|
-
resolutionMap[compilerOptions.moduleResolution.toLowerCase()] ?? ts.ModuleResolutionKind.Bundler;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// JSX
|
|
248
|
-
if (compilerOptions.jsx) {
|
|
249
|
-
const jsxMap: Record<string, ts.JsxEmit> = {
|
|
250
|
-
preserve: ts.JsxEmit.Preserve,
|
|
251
|
-
react: ts.JsxEmit.React,
|
|
252
|
-
"react-jsx": ts.JsxEmit.ReactJSX,
|
|
253
|
-
"react-jsxdev": ts.JsxEmit.ReactJSXDev,
|
|
254
|
-
"react-native": ts.JsxEmit.ReactNative,
|
|
255
|
-
};
|
|
256
|
-
options.jsx = jsxMap[compilerOptions.jsx.toLowerCase()] ?? ts.JsxEmit.ReactJSX;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Boolean options
|
|
260
|
-
if (compilerOptions.strict !== undefined) options.strict = compilerOptions.strict;
|
|
261
|
-
if (compilerOptions.esModuleInterop !== undefined) options.esModuleInterop = compilerOptions.esModuleInterop;
|
|
262
|
-
if (compilerOptions.skipLibCheck !== undefined) options.skipLibCheck = compilerOptions.skipLibCheck;
|
|
263
|
-
if (compilerOptions.allowJs !== undefined) options.allowJs = compilerOptions.allowJs;
|
|
264
|
-
if (compilerOptions.resolveJsonModule !== undefined) options.resolveJsonModule = compilerOptions.resolveJsonModule;
|
|
265
|
-
if (compilerOptions.noImplicitAny !== undefined) options.noImplicitAny = compilerOptions.noImplicitAny;
|
|
266
|
-
if (compilerOptions.strictNullChecks !== undefined) options.strictNullChecks = compilerOptions.strictNullChecks;
|
|
267
|
-
// Note: noLib is intentionally not configurable - we always use our fetched lib files
|
|
268
|
-
|
|
269
|
-
// Lib array (e.g., ["ES2020", "DOM"])
|
|
270
|
-
if (Array.isArray(compilerOptions.lib)) {
|
|
271
|
-
options.lib = compilerOptions.lib.map((lib: string) =>
|
|
272
|
-
lib.toLowerCase().startsWith("lib.") ? lib : `lib.${lib.toLowerCase()}.d.ts`
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Always ensure noEmit for type checking
|
|
277
|
-
options.noEmit = true;
|
|
278
|
-
|
|
279
|
-
return options;
|
|
280
|
-
} catch (err) {
|
|
281
|
-
console.warn("Error parsing tsconfig.json:", err);
|
|
282
|
-
return getDefaultCompilerOptions();
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Get default compiler options for type checking
|
|
288
|
-
*/
|
|
289
|
-
function getDefaultCompilerOptions(): ts.CompilerOptions {
|
|
290
|
-
return {
|
|
291
|
-
target: ts.ScriptTarget.ES2020,
|
|
292
|
-
module: ts.ModuleKind.ESNext,
|
|
293
|
-
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
294
|
-
esModuleInterop: true,
|
|
295
|
-
strict: true,
|
|
296
|
-
skipLibCheck: true, // Skip type-checking lib files for performance
|
|
297
|
-
noEmit: true, // We only want type checking, no output
|
|
298
|
-
jsx: ts.JsxEmit.ReactJSX,
|
|
299
|
-
allowJs: true,
|
|
300
|
-
resolveJsonModule: true,
|
|
301
|
-
noLib: false, // We provide lib files via the compiler host
|
|
302
|
-
lib: [
|
|
303
|
-
"lib.es2020.d.ts",
|
|
304
|
-
"lib.dom.d.ts",
|
|
305
|
-
"lib.dom.iterable.d.ts",
|
|
306
|
-
],
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* The virtual path where lib files are stored in the cache
|
|
312
|
-
*/
|
|
313
|
-
const LIB_PATH_PREFIX = "/node_modules/typescript/lib/";
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Create a custom compiler host that reads from the file cache
|
|
317
|
-
* Note: We don't use ts.createCompilerHost as it requires Node.js fs module
|
|
318
|
-
*
|
|
319
|
-
* @param fileCache - Map of file paths to content (includes both user files and lib files)
|
|
320
|
-
* @param libFiles - Map of lib names to content (e.g., "dom" -> content of lib.dom.d.ts)
|
|
321
|
-
* @param _options - Compiler options (unused but kept for potential future use)
|
|
322
|
-
*/
|
|
323
|
-
function createVfsCompilerHost(
|
|
324
|
-
fileCache: Map<string, string>,
|
|
325
|
-
libFiles: Map<string, string>,
|
|
326
|
-
_options: ts.CompilerOptions
|
|
327
|
-
): ts.CompilerHost {
|
|
328
|
-
/**
|
|
329
|
-
* Try to get content for a lib file request.
|
|
330
|
-
* TypeScript may request libs by full path or just filename.
|
|
331
|
-
*/
|
|
332
|
-
function getLibContent(fileName: string): string | undefined {
|
|
333
|
-
// Extract lib name from various path formats:
|
|
334
|
-
// - "/node_modules/typescript/lib/lib.dom.d.ts" -> "dom"
|
|
335
|
-
// - "lib.dom.d.ts" -> "dom"
|
|
336
|
-
const libMatch = fileName.match(/lib\.([^/]+)\.d\.ts$/);
|
|
337
|
-
if (libMatch && libMatch[1]) {
|
|
338
|
-
return libFiles.get(libMatch[1]);
|
|
339
|
-
}
|
|
340
|
-
return undefined;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
return {
|
|
344
|
-
getSourceFile(
|
|
345
|
-
fileName: string,
|
|
346
|
-
languageVersion: ts.ScriptTarget,
|
|
347
|
-
onError?: (message: string) => void
|
|
348
|
-
): ts.SourceFile | undefined {
|
|
349
|
-
// First, try the file cache (user files)
|
|
350
|
-
const normalizedPath = normalizePath(fileName);
|
|
351
|
-
const content = fileCache.get(normalizedPath);
|
|
352
|
-
|
|
353
|
-
if (content !== undefined) {
|
|
354
|
-
return ts.createSourceFile(normalizedPath, content, languageVersion, true);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Try without leading slash
|
|
358
|
-
const altContent = fileCache.get(fileName);
|
|
359
|
-
if (altContent !== undefined) {
|
|
360
|
-
return ts.createSourceFile(fileName, altContent, languageVersion, true);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Try lib files
|
|
364
|
-
const libContent = getLibContent(fileName);
|
|
365
|
-
if (libContent !== undefined) {
|
|
366
|
-
return ts.createSourceFile(fileName, libContent, languageVersion, true);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// File not found
|
|
370
|
-
if (onError) {
|
|
371
|
-
onError(`File not found: ${fileName}`);
|
|
372
|
-
}
|
|
373
|
-
return undefined;
|
|
374
|
-
},
|
|
375
|
-
|
|
376
|
-
getDefaultLibFileName(options: ts.CompilerOptions): string {
|
|
377
|
-
return LIB_PATH_PREFIX + ts.getDefaultLibFileName(options);
|
|
378
|
-
},
|
|
379
|
-
|
|
380
|
-
writeFile(): void {
|
|
381
|
-
// No-op: we don't emit files
|
|
382
|
-
},
|
|
383
|
-
|
|
384
|
-
getCurrentDirectory(): string {
|
|
385
|
-
return "/";
|
|
386
|
-
},
|
|
387
|
-
|
|
388
|
-
getCanonicalFileName(fileName: string): string {
|
|
389
|
-
return fileName;
|
|
390
|
-
},
|
|
391
|
-
|
|
392
|
-
useCaseSensitiveFileNames(): boolean {
|
|
393
|
-
return true;
|
|
394
|
-
},
|
|
395
|
-
|
|
396
|
-
getNewLine(): string {
|
|
397
|
-
return "\n";
|
|
398
|
-
},
|
|
399
|
-
|
|
400
|
-
fileExists(fileName: string): boolean {
|
|
401
|
-
const normalizedPath = normalizePath(fileName);
|
|
402
|
-
if (fileCache.has(normalizedPath) || fileCache.has(fileName)) {
|
|
403
|
-
return true;
|
|
404
|
-
}
|
|
405
|
-
// Check if it's a lib file
|
|
406
|
-
return getLibContent(fileName) !== undefined;
|
|
407
|
-
},
|
|
408
|
-
|
|
409
|
-
readFile(fileName: string): string | undefined {
|
|
410
|
-
const normalizedPath = normalizePath(fileName);
|
|
411
|
-
const content = fileCache.get(normalizedPath) ?? fileCache.get(fileName);
|
|
412
|
-
if (content !== undefined) {
|
|
413
|
-
return content;
|
|
414
|
-
}
|
|
415
|
-
// Try lib files
|
|
416
|
-
return getLibContent(fileName);
|
|
417
|
-
},
|
|
418
|
-
|
|
419
|
-
directoryExists(directoryName: string): boolean {
|
|
420
|
-
const normalizedDir = normalizePath(directoryName);
|
|
421
|
-
|
|
422
|
-
// Check user files (includes installed packages in node_modules)
|
|
423
|
-
for (const path of fileCache.keys()) {
|
|
424
|
-
if (path.startsWith(normalizedDir + "/")) {
|
|
425
|
-
return true;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Check if it's a TypeScript lib directory and we have lib files
|
|
430
|
-
if (
|
|
431
|
-
normalizedDir === "/node_modules/typescript/lib" ||
|
|
432
|
-
normalizedDir === "/node_modules/typescript"
|
|
433
|
-
) {
|
|
434
|
-
return libFiles.size > 0;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// Check /node_modules - exists if we have lib files OR installed packages
|
|
438
|
-
if (normalizedDir === "/node_modules") {
|
|
439
|
-
return libFiles.size > 0 ||
|
|
440
|
-
Array.from(fileCache.keys()).some(p => p.startsWith("/node_modules/"));
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// Check @types directory specifically
|
|
444
|
-
if (normalizedDir === "/node_modules/@types") {
|
|
445
|
-
return Array.from(fileCache.keys()).some(p => p.startsWith("/node_modules/@types/"));
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
return false;
|
|
449
|
-
},
|
|
450
|
-
|
|
451
|
-
getDirectories(path: string): string[] {
|
|
452
|
-
const normalizedPath = normalizePath(path);
|
|
453
|
-
const prefix = normalizedPath === "/" ? "/" : normalizedPath + "/";
|
|
454
|
-
const dirs = new Set<string>();
|
|
455
|
-
|
|
456
|
-
for (const filePath of fileCache.keys()) {
|
|
457
|
-
if (filePath.startsWith(prefix)) {
|
|
458
|
-
const relative = filePath.slice(prefix.length);
|
|
459
|
-
const firstSlash = relative.indexOf("/");
|
|
460
|
-
if (firstSlash > 0) {
|
|
461
|
-
dirs.add(relative.slice(0, firstSlash));
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// Add node_modules if we have lib files or packages and path is root
|
|
467
|
-
if (normalizedPath === "/") {
|
|
468
|
-
// Check if there are any files in node_modules (from packages or libs)
|
|
469
|
-
const hasNodeModules = libFiles.size > 0 ||
|
|
470
|
-
Array.from(fileCache.keys()).some(p => p.startsWith("/node_modules/"));
|
|
471
|
-
if (hasNodeModules) {
|
|
472
|
-
dirs.add("node_modules");
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
return Array.from(dirs);
|
|
477
|
-
},
|
|
478
|
-
|
|
479
|
-
realpath(path: string): string {
|
|
480
|
-
return path;
|
|
481
|
-
},
|
|
482
|
-
|
|
483
|
-
getEnvironmentVariable(): string | undefined {
|
|
484
|
-
return undefined;
|
|
485
|
-
},
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* Type check TypeScript files from a virtual filesystem
|
|
491
|
-
*
|
|
492
|
-
* @example
|
|
493
|
-
* ```ts
|
|
494
|
-
* const fs = Filesystem.create({
|
|
495
|
-
* initialFiles: {
|
|
496
|
-
* "/tsconfig.json": JSON.stringify({
|
|
497
|
-
* compilerOptions: {
|
|
498
|
-
* target: "ES2020",
|
|
499
|
-
* module: "ESNext",
|
|
500
|
-
* strict: true
|
|
501
|
-
* }
|
|
502
|
-
* }),
|
|
503
|
-
* "/src/index.ts": `
|
|
504
|
-
* const x: number = "hello"; // Type error!
|
|
505
|
-
* export { x };
|
|
506
|
-
* `,
|
|
507
|
-
* }
|
|
508
|
-
* });
|
|
509
|
-
*
|
|
510
|
-
* const result = await typecheck({
|
|
511
|
-
* fs,
|
|
512
|
-
* entryPoint: "/src/index.ts",
|
|
513
|
-
* });
|
|
514
|
-
*
|
|
515
|
-
* console.log(result.hasErrors); // true
|
|
516
|
-
* console.log(result.diagnostics[0].message); // Type 'string' is not assignable...
|
|
517
|
-
* ```
|
|
518
|
-
*/
|
|
519
|
-
export async function typecheck(options: TypecheckOptions): Promise<TypecheckResult> {
|
|
520
|
-
const {
|
|
521
|
-
fs,
|
|
522
|
-
entryPoint,
|
|
523
|
-
tsconfigPath = "/tsconfig.json",
|
|
524
|
-
libFiles = new Map(),
|
|
525
|
-
} = options;
|
|
526
|
-
|
|
527
|
-
// Normalize entry point
|
|
528
|
-
const normalizedEntry = normalizePath(entryPoint);
|
|
529
|
-
|
|
530
|
-
// Verify entry point exists
|
|
531
|
-
if (!(await fs.exists(normalizedEntry))) {
|
|
532
|
-
throw new Error(`Entry point not found: ${normalizedEntry}`);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Pre-load all files into sync cache
|
|
536
|
-
const fileCache = await preloadFiles(fs);
|
|
537
|
-
|
|
538
|
-
// Parse tsconfig if it exists
|
|
539
|
-
let compilerOptions = getDefaultCompilerOptions();
|
|
540
|
-
const tsconfigContent = fileCache.get(normalizePath(tsconfigPath));
|
|
541
|
-
if (tsconfigContent) {
|
|
542
|
-
compilerOptions = {
|
|
543
|
-
...parseTsConfig(tsconfigContent, tsconfigPath),
|
|
544
|
-
noEmit: true, // Always ensure noEmit for type checking
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// Create compiler host with lib files
|
|
549
|
-
const host = createVfsCompilerHost(fileCache, libFiles, compilerOptions);
|
|
550
|
-
|
|
551
|
-
// Create program and get diagnostics
|
|
552
|
-
const program = ts.createProgram([normalizedEntry], compilerOptions, host);
|
|
553
|
-
|
|
554
|
-
// Collect all diagnostics
|
|
555
|
-
const allDiagnostics = [
|
|
556
|
-
...program.getSyntacticDiagnostics(),
|
|
557
|
-
...program.getSemanticDiagnostics(),
|
|
558
|
-
...program.getDeclarationDiagnostics(),
|
|
559
|
-
];
|
|
560
|
-
|
|
561
|
-
// Convert to our format
|
|
562
|
-
const diagnostics = allDiagnostics.map(convertDiagnostic);
|
|
563
|
-
|
|
564
|
-
// Get list of checked files
|
|
565
|
-
const checkedFiles = program
|
|
566
|
-
.getSourceFiles()
|
|
567
|
-
.map((sf) => sf.fileName)
|
|
568
|
-
.filter((f) => !f.includes("node_modules/typescript/lib"));
|
|
569
|
-
|
|
570
|
-
// Check for errors
|
|
571
|
-
const hasErrors = diagnostics.some((d) => d.category === "error");
|
|
572
|
-
|
|
573
|
-
return {
|
|
574
|
-
diagnostics,
|
|
575
|
-
hasErrors,
|
|
576
|
-
checkedFiles,
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* Format diagnostics as a human-readable string
|
|
582
|
-
*
|
|
583
|
-
* @example
|
|
584
|
-
* ```ts
|
|
585
|
-
* const result = await typecheck({ fs, entryPoint: "/src/index.ts" });
|
|
586
|
-
* console.log(formatDiagnostics(result.diagnostics));
|
|
587
|
-
* // /src/index.ts:3:7 - error TS2322: Type 'string' is not assignable to type 'number'.
|
|
588
|
-
* ```
|
|
589
|
-
*/
|
|
590
|
-
export function formatDiagnostics(diagnostics: Diagnostic[]): string {
|
|
591
|
-
return diagnostics
|
|
592
|
-
.map((d) => {
|
|
593
|
-
const location = d.file
|
|
594
|
-
? `${d.file}${d.line ? `:${d.line}` : ""}${d.column ? `:${d.column}` : ""}`
|
|
595
|
-
: "(global)";
|
|
596
|
-
return `${location} - ${d.category} TS${d.code}: ${d.message}`;
|
|
597
|
-
})
|
|
598
|
-
.join("\n");
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
/**
|
|
602
|
-
* Format diagnostics in a concise format suitable for AI agents
|
|
603
|
-
*
|
|
604
|
-
* @example
|
|
605
|
-
* ```ts
|
|
606
|
-
* const result = await typecheck({ fs, entryPoint: "/src/index.ts" });
|
|
607
|
-
* console.log(formatDiagnosticsForAgent(result.diagnostics));
|
|
608
|
-
* // Error in /src/index.ts at line 3: Type 'string' is not assignable to type 'number'. (TS2322)
|
|
609
|
-
* ```
|
|
610
|
-
*/
|
|
611
|
-
export function formatDiagnosticsForAgent(diagnostics: Diagnostic[]): string {
|
|
612
|
-
if (diagnostics.length === 0) {
|
|
613
|
-
return "No type errors found.";
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
const errorCount = diagnostics.filter((d) => d.category === "error").length;
|
|
617
|
-
const warningCount = diagnostics.filter((d) => d.category === "warning").length;
|
|
618
|
-
|
|
619
|
-
const summary =
|
|
620
|
-
errorCount > 0 || warningCount > 0
|
|
621
|
-
? `Found ${errorCount} error(s) and ${warningCount} warning(s):\n\n`
|
|
622
|
-
: "";
|
|
623
|
-
|
|
624
|
-
const formatted = diagnostics
|
|
625
|
-
.map((d) => {
|
|
626
|
-
const location = d.file
|
|
627
|
-
? `${d.file}${d.line ? ` at line ${d.line}` : ""}`
|
|
628
|
-
: "Global";
|
|
629
|
-
const prefix = d.category === "error" ? "Error" : d.category === "warning" ? "Warning" : "Info";
|
|
630
|
-
return `${prefix} in ${location}: ${d.message} (TS${d.code})`;
|
|
631
|
-
})
|
|
632
|
-
.join("\n\n");
|
|
633
|
-
|
|
634
|
-
return summary + formatted;
|
|
635
|
-
}
|