@typed/virtual-modules 1.0.0-beta.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/README.md +135 -0
- package/dist/CompilerHostAdapter.d.ts +3 -0
- package/dist/CompilerHostAdapter.d.ts.map +1 -0
- package/dist/CompilerHostAdapter.js +160 -0
- package/dist/LanguageServiceAdapter.d.ts +3 -0
- package/dist/LanguageServiceAdapter.d.ts.map +1 -0
- package/dist/LanguageServiceAdapter.js +488 -0
- package/dist/LanguageServiceSession.d.ts +16 -0
- package/dist/LanguageServiceSession.d.ts.map +1 -0
- package/dist/LanguageServiceSession.js +122 -0
- package/dist/NodeModulePluginLoader.d.ts +8 -0
- package/dist/NodeModulePluginLoader.d.ts.map +1 -0
- package/dist/NodeModulePluginLoader.js +175 -0
- package/dist/PluginManager.d.ts +10 -0
- package/dist/PluginManager.d.ts.map +1 -0
- package/dist/PluginManager.js +151 -0
- package/dist/TypeInfoApi.d.ts +71 -0
- package/dist/TypeInfoApi.d.ts.map +1 -0
- package/dist/TypeInfoApi.js +855 -0
- package/dist/VmcConfigLoader.d.ts +31 -0
- package/dist/VmcConfigLoader.d.ts.map +1 -0
- package/dist/VmcConfigLoader.js +231 -0
- package/dist/VmcResolverLoader.d.ts +30 -0
- package/dist/VmcResolverLoader.d.ts.map +1 -0
- package/dist/VmcResolverLoader.js +71 -0
- package/dist/collectTypeTargetSpecs.d.ts +6 -0
- package/dist/collectTypeTargetSpecs.d.ts.map +1 -0
- package/dist/collectTypeTargetSpecs.js +19 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/internal/VirtualRecordStore.d.ts +60 -0
- package/dist/internal/VirtualRecordStore.d.ts.map +1 -0
- package/dist/internal/VirtualRecordStore.js +199 -0
- package/dist/internal/materializeVirtualFile.d.ts +12 -0
- package/dist/internal/materializeVirtualFile.d.ts.map +1 -0
- package/dist/internal/materializeVirtualFile.js +28 -0
- package/dist/internal/path.d.ts +40 -0
- package/dist/internal/path.d.ts.map +1 -0
- package/dist/internal/path.js +71 -0
- package/dist/internal/sanitize.d.ts +6 -0
- package/dist/internal/sanitize.d.ts.map +1 -0
- package/dist/internal/sanitize.js +15 -0
- package/dist/internal/tsInternal.d.ts +65 -0
- package/dist/internal/tsInternal.d.ts.map +1 -0
- package/dist/internal/tsInternal.js +99 -0
- package/dist/internal/validation.d.ts +28 -0
- package/dist/internal/validation.d.ts.map +1 -0
- package/dist/internal/validation.js +66 -0
- package/dist/typeTargetBootstrap.d.ts +33 -0
- package/dist/typeTargetBootstrap.d.ts.map +1 -0
- package/dist/typeTargetBootstrap.js +57 -0
- package/dist/types.d.ts +405 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/package.json +38 -0
- package/src/CompilerHostAdapter.test.ts +180 -0
- package/src/CompilerHostAdapter.ts +316 -0
- package/src/LanguageServiceAdapter.test.ts +521 -0
- package/src/LanguageServiceAdapter.ts +631 -0
- package/src/LanguageServiceSession.ts +160 -0
- package/src/LanguageServiceTester.integration.test.ts +268 -0
- package/src/NodeModulePluginLoader.test.ts +170 -0
- package/src/NodeModulePluginLoader.ts +268 -0
- package/src/PluginManager.test.ts +178 -0
- package/src/PluginManager.ts +218 -0
- package/src/TypeInfoApi.test.ts +1211 -0
- package/src/TypeInfoApi.ts +1228 -0
- package/src/VmcConfigLoader.test.ts +108 -0
- package/src/VmcConfigLoader.ts +297 -0
- package/src/VmcResolverLoader.test.ts +181 -0
- package/src/VmcResolverLoader.ts +116 -0
- package/src/collectTypeTargetSpecs.ts +22 -0
- package/src/index.ts +35 -0
- package/src/internal/VirtualRecordStore.ts +304 -0
- package/src/internal/materializeVirtualFile.ts +38 -0
- package/src/internal/path.ts +106 -0
- package/src/internal/sanitize.ts +16 -0
- package/src/internal/tsInternal.ts +127 -0
- package/src/internal/validation.ts +85 -0
- package/src/typeTargetBootstrap.ts +75 -0
- package/src/types.ts +535 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import ts from "typescript";
|
|
5
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
6
|
+
import { attachCompilerHostAdapter } from "./CompilerHostAdapter.js";
|
|
7
|
+
import { PluginManager } from "./PluginManager.js";
|
|
8
|
+
|
|
9
|
+
const tempDirs: string[] = [];
|
|
10
|
+
|
|
11
|
+
const createTempDir = (): string => {
|
|
12
|
+
const dir = mkdtempSync(join(tmpdir(), "typed-vm-compiler-"));
|
|
13
|
+
tempDirs.push(dir);
|
|
14
|
+
return dir;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
while (tempDirs.length > 0) {
|
|
19
|
+
const dir = tempDirs.pop();
|
|
20
|
+
if (dir) {
|
|
21
|
+
rmSync(dir, { recursive: true, force: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("attachCompilerHostAdapter", () => {
|
|
27
|
+
it("injects virtual modules into a Program graph", () => {
|
|
28
|
+
const dir = createTempDir();
|
|
29
|
+
const entry = join(dir, "entry.ts");
|
|
30
|
+
|
|
31
|
+
writeFileSync(
|
|
32
|
+
entry,
|
|
33
|
+
`
|
|
34
|
+
import type { Foo } from "virtual:foo";
|
|
35
|
+
export const value: Foo = { n: 1 };
|
|
36
|
+
`,
|
|
37
|
+
"utf8",
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const manager = new PluginManager([
|
|
41
|
+
{
|
|
42
|
+
name: "virtual",
|
|
43
|
+
shouldResolve: (id) => id === "virtual:foo",
|
|
44
|
+
build: () => `export interface Foo { n: number }`,
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
const compilerOptions: ts.CompilerOptions = {
|
|
49
|
+
strict: true,
|
|
50
|
+
noEmit: true,
|
|
51
|
+
target: ts.ScriptTarget.ESNext,
|
|
52
|
+
module: ts.ModuleKind.ESNext,
|
|
53
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
54
|
+
skipLibCheck: true,
|
|
55
|
+
};
|
|
56
|
+
const host = ts.createCompilerHost(compilerOptions);
|
|
57
|
+
const adapter = attachCompilerHostAdapter({
|
|
58
|
+
ts,
|
|
59
|
+
compilerHost: host,
|
|
60
|
+
resolver: manager,
|
|
61
|
+
projectRoot: dir,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const program = ts.createProgram([entry], compilerOptions, host);
|
|
65
|
+
const diagnostics = ts.getPreEmitDiagnostics(program);
|
|
66
|
+
|
|
67
|
+
expect(diagnostics).toHaveLength(0);
|
|
68
|
+
expect(
|
|
69
|
+
program.getSourceFiles().some((sourceFile) => sourceFile.fileName.includes("__virtual_")),
|
|
70
|
+
).toBe(true);
|
|
71
|
+
|
|
72
|
+
adapter.dispose();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("evicts virtual record when importer no longer exists (fileExists returns false)", () => {
|
|
76
|
+
const dir = createTempDir();
|
|
77
|
+
const entry1 = join(dir, "entry1.ts");
|
|
78
|
+
const entry2 = join(dir, "entry2.ts");
|
|
79
|
+
writeFileSync(
|
|
80
|
+
entry1,
|
|
81
|
+
`import type { Foo } from "virtual:foo"; export const value: Foo = { n: 1 };`,
|
|
82
|
+
"utf8",
|
|
83
|
+
);
|
|
84
|
+
writeFileSync(
|
|
85
|
+
entry2,
|
|
86
|
+
`import type { Bar } from "virtual:bar"; export const value: Bar = { s: "x" };`,
|
|
87
|
+
"utf8",
|
|
88
|
+
);
|
|
89
|
+
const compilerOptions: ts.CompilerOptions = {
|
|
90
|
+
strict: true,
|
|
91
|
+
noEmit: true,
|
|
92
|
+
target: ts.ScriptTarget.ESNext,
|
|
93
|
+
module: ts.ModuleKind.ESNext,
|
|
94
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
95
|
+
skipLibCheck: true,
|
|
96
|
+
};
|
|
97
|
+
const baseHost = ts.createCompilerHost(compilerOptions);
|
|
98
|
+
const missingPaths = new Set<string>();
|
|
99
|
+
const host = {
|
|
100
|
+
...baseHost,
|
|
101
|
+
fileExists: (fileName: string) => {
|
|
102
|
+
if (missingPaths.has(fileName)) return false;
|
|
103
|
+
return baseHost.fileExists(fileName);
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
const manager = new PluginManager([
|
|
107
|
+
{
|
|
108
|
+
name: "virtual-foo",
|
|
109
|
+
shouldResolve: (id) => id === "virtual:foo",
|
|
110
|
+
build: () => `export interface Foo { n: number }`,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "virtual-bar",
|
|
114
|
+
shouldResolve: (id) => id === "virtual:bar",
|
|
115
|
+
build: () => `export interface Bar { s: string }`,
|
|
116
|
+
},
|
|
117
|
+
]);
|
|
118
|
+
const adapter = attachCompilerHostAdapter({
|
|
119
|
+
ts,
|
|
120
|
+
compilerHost: host,
|
|
121
|
+
resolver: manager,
|
|
122
|
+
projectRoot: dir,
|
|
123
|
+
});
|
|
124
|
+
const program1 = ts.createProgram([entry1, entry2], compilerOptions, host);
|
|
125
|
+
const virtualFooFile = program1
|
|
126
|
+
.getSourceFiles()
|
|
127
|
+
.find((sf) => sf.fileName.includes("__virtual_") && sf.fileName.includes("virtual-foo"));
|
|
128
|
+
expect(virtualFooFile).toBeDefined();
|
|
129
|
+
const virtualFooFileName = virtualFooFile!.fileName;
|
|
130
|
+
|
|
131
|
+
missingPaths.add(entry1);
|
|
132
|
+
ts.createProgram([entry2], compilerOptions, host);
|
|
133
|
+
const afterEviction = host.getSourceFile(virtualFooFileName, ts.ScriptTarget.ESNext);
|
|
134
|
+
expect(afterEviction).toBeUndefined();
|
|
135
|
+
|
|
136
|
+
adapter.dispose();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("dispose then getSourceFile does not throw and returns original behavior", () => {
|
|
140
|
+
const dir = createTempDir();
|
|
141
|
+
const entry = join(dir, "entry.ts");
|
|
142
|
+
writeFileSync(
|
|
143
|
+
entry,
|
|
144
|
+
`import type { Foo } from "virtual:foo"; export const value: Foo = { n: 1 };`,
|
|
145
|
+
"utf8",
|
|
146
|
+
);
|
|
147
|
+
const manager = new PluginManager([
|
|
148
|
+
{
|
|
149
|
+
name: "virtual",
|
|
150
|
+
shouldResolve: (id) => id === "virtual:foo",
|
|
151
|
+
build: () => `export interface Foo { n: number }`,
|
|
152
|
+
},
|
|
153
|
+
]);
|
|
154
|
+
const compilerOptions: ts.CompilerOptions = {
|
|
155
|
+
strict: true,
|
|
156
|
+
noEmit: true,
|
|
157
|
+
target: ts.ScriptTarget.ESNext,
|
|
158
|
+
module: ts.ModuleKind.ESNext,
|
|
159
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
160
|
+
skipLibCheck: true,
|
|
161
|
+
};
|
|
162
|
+
const host = ts.createCompilerHost(compilerOptions);
|
|
163
|
+
const adapter = attachCompilerHostAdapter({
|
|
164
|
+
ts,
|
|
165
|
+
compilerHost: host,
|
|
166
|
+
resolver: manager,
|
|
167
|
+
projectRoot: dir,
|
|
168
|
+
});
|
|
169
|
+
const program = ts.createProgram([entry], compilerOptions, host);
|
|
170
|
+
const virtualFile = program.getSourceFiles().find((sf) => sf.fileName.includes("__virtual_"));
|
|
171
|
+
expect(virtualFile).toBeDefined();
|
|
172
|
+
const virtualFileName = virtualFile!.fileName;
|
|
173
|
+
|
|
174
|
+
adapter.dispose();
|
|
175
|
+
|
|
176
|
+
expect(() => host.getSourceFile(virtualFileName, ts.ScriptTarget.ESNext)).not.toThrow();
|
|
177
|
+
const after = host.getSourceFile(virtualFileName, ts.ScriptTarget.ESNext);
|
|
178
|
+
expect(after).toBeUndefined();
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import type * as ts from "typescript";
|
|
2
|
+
import {
|
|
3
|
+
type CompilerHostAdapterOptions,
|
|
4
|
+
type VirtualModuleAdapterHandle,
|
|
5
|
+
type VirtualModuleDiagnostic,
|
|
6
|
+
} from "./types.js";
|
|
7
|
+
import { rewriteSourceForPreviewLocation } from "./internal/materializeVirtualFile.js";
|
|
8
|
+
import {
|
|
9
|
+
createVirtualRecordStore,
|
|
10
|
+
toResolvedModule,
|
|
11
|
+
type MutableVirtualRecord,
|
|
12
|
+
} from "./internal/VirtualRecordStore.js";
|
|
13
|
+
import { VIRTUAL_NODE_MODULES_RELATIVE } from "./internal/path.js";
|
|
14
|
+
|
|
15
|
+
export const attachCompilerHostAdapter = (
|
|
16
|
+
options: CompilerHostAdapterOptions,
|
|
17
|
+
): VirtualModuleAdapterHandle => {
|
|
18
|
+
if (typeof options.projectRoot !== "string" || options.projectRoot.trim() === "") {
|
|
19
|
+
throw new Error("projectRoot must be a non-empty string");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const host = options.compilerHost as ts.CompilerHost & Record<string, unknown>;
|
|
23
|
+
const watchHost = options.watchHost;
|
|
24
|
+
|
|
25
|
+
const invalidatedPaths = new Set<string>();
|
|
26
|
+
|
|
27
|
+
const originalResolveModuleNameLiterals = (
|
|
28
|
+
host as {
|
|
29
|
+
resolveModuleNameLiterals?: (...args: readonly unknown[]) => readonly unknown[];
|
|
30
|
+
}
|
|
31
|
+
).resolveModuleNameLiterals?.bind(host);
|
|
32
|
+
const originalResolveModuleNames = (
|
|
33
|
+
host as {
|
|
34
|
+
resolveModuleNames?: (
|
|
35
|
+
...args: readonly unknown[]
|
|
36
|
+
) => readonly (ts.ResolvedModule | undefined)[];
|
|
37
|
+
}
|
|
38
|
+
).resolveModuleNames?.bind(host);
|
|
39
|
+
const originalGetSourceFile = host.getSourceFile.bind(host);
|
|
40
|
+
const originalGetSourceFileByPath = (
|
|
41
|
+
host as {
|
|
42
|
+
getSourceFileByPath?: (...args: readonly unknown[]) => ts.SourceFile | undefined;
|
|
43
|
+
}
|
|
44
|
+
).getSourceFileByPath?.bind(host);
|
|
45
|
+
const originalFileExists = host.fileExists.bind(host);
|
|
46
|
+
const originalReadFile = host.readFile.bind(host);
|
|
47
|
+
const originalHasInvalidatedResolutions = (
|
|
48
|
+
host as {
|
|
49
|
+
hasInvalidatedResolutions?: (...args: readonly unknown[]) => boolean;
|
|
50
|
+
}
|
|
51
|
+
).hasInvalidatedResolutions?.bind(host);
|
|
52
|
+
|
|
53
|
+
const store = createVirtualRecordStore({
|
|
54
|
+
projectRoot: options.projectRoot,
|
|
55
|
+
resolver: options.resolver,
|
|
56
|
+
createTypeInfoApiSession: options.createTypeInfoApiSession,
|
|
57
|
+
debounceMs: options.debounceMs,
|
|
58
|
+
watchHost,
|
|
59
|
+
shouldEvictRecord: (record) => !originalFileExists(record.importer),
|
|
60
|
+
onMarkStale: (record) => {
|
|
61
|
+
invalidatedPaths.add(record.importer);
|
|
62
|
+
invalidatedPaths.add(record.virtualFileName);
|
|
63
|
+
},
|
|
64
|
+
onRecordResolved: (record) => {
|
|
65
|
+
invalidatedPaths.delete(record.importer);
|
|
66
|
+
invalidatedPaths.delete(record.virtualFileName);
|
|
67
|
+
},
|
|
68
|
+
onEvictRecord: (record) => {
|
|
69
|
+
invalidatedPaths.delete(record.importer);
|
|
70
|
+
invalidatedPaths.delete(record.virtualFileName);
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const { recordsByVirtualFile } = store;
|
|
75
|
+
|
|
76
|
+
const getOrBuildRecord = (id: string, importer: string): MutableVirtualRecord | undefined => {
|
|
77
|
+
const result = store.getOrBuildRecord(id, importer);
|
|
78
|
+
return result.status === "resolved" ? result.record : undefined;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const ADAPTER_DIAGNOSTIC_CODE = 99001;
|
|
82
|
+
|
|
83
|
+
const rebuildIfStale = (record: MutableVirtualRecord): MutableVirtualRecord => {
|
|
84
|
+
if (!record.stale) {
|
|
85
|
+
return record;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const rebuilt = store.resolveRecord(record.id, record.importer, record);
|
|
89
|
+
if (rebuilt.status === "resolved") {
|
|
90
|
+
return rebuilt.record;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (rebuilt.status === "error" && options.reportDiagnostic) {
|
|
94
|
+
const diag = rebuilt.diagnostic as VirtualModuleDiagnostic;
|
|
95
|
+
const message = `Virtual module rebuild failed: ${diag.message}`;
|
|
96
|
+
options.reportDiagnostic({
|
|
97
|
+
category: options.ts.DiagnosticCategory.Error,
|
|
98
|
+
code: ADAPTER_DIAGNOSTIC_CODE,
|
|
99
|
+
file: undefined,
|
|
100
|
+
start: 0,
|
|
101
|
+
length: 0,
|
|
102
|
+
messageText: message,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return record;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const fallbackResolveModule = (
|
|
109
|
+
moduleName: string,
|
|
110
|
+
containingFile: string,
|
|
111
|
+
compilerOptions: ts.CompilerOptions | undefined,
|
|
112
|
+
): ts.ResolvedModuleFull | undefined => {
|
|
113
|
+
const result = options.ts.resolveModuleName(
|
|
114
|
+
moduleName,
|
|
115
|
+
containingFile,
|
|
116
|
+
compilerOptions ?? {},
|
|
117
|
+
host,
|
|
118
|
+
);
|
|
119
|
+
return result.resolvedModule as ts.ResolvedModuleFull | undefined;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
(host as ts.CompilerHost).resolveModuleNames = (
|
|
123
|
+
moduleNames: readonly string[],
|
|
124
|
+
containingFile: string,
|
|
125
|
+
reusedNames: readonly string[] | undefined,
|
|
126
|
+
redirectedReference: ts.ResolvedProjectReference | undefined,
|
|
127
|
+
compilerOptions: ts.CompilerOptions,
|
|
128
|
+
containingSourceFile?: ts.SourceFile,
|
|
129
|
+
): (ts.ResolvedModule | undefined)[] => {
|
|
130
|
+
const fallback = originalResolveModuleNames
|
|
131
|
+
? originalResolveModuleNames(
|
|
132
|
+
moduleNames,
|
|
133
|
+
containingFile,
|
|
134
|
+
reusedNames,
|
|
135
|
+
redirectedReference,
|
|
136
|
+
compilerOptions,
|
|
137
|
+
containingSourceFile,
|
|
138
|
+
)
|
|
139
|
+
: moduleNames.map((moduleName) =>
|
|
140
|
+
fallbackResolveModule(moduleName, containingFile, compilerOptions),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return moduleNames.map((moduleName, index) => {
|
|
144
|
+
const record = getOrBuildRecord(moduleName, containingFile);
|
|
145
|
+
if (!record) {
|
|
146
|
+
return fallback[index];
|
|
147
|
+
}
|
|
148
|
+
return toResolvedModule(options.ts, record.virtualFileName);
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const assignResolveModuleNameLiterals = (
|
|
153
|
+
moduleLiterals: readonly { readonly text: string }[],
|
|
154
|
+
containingFile: string,
|
|
155
|
+
redirectedReference: ts.ResolvedProjectReference | undefined,
|
|
156
|
+
compilerOptions: ts.CompilerOptions,
|
|
157
|
+
containingSourceFile: ts.SourceFile | undefined,
|
|
158
|
+
reusedNames?: readonly { readonly text: string }[],
|
|
159
|
+
): readonly ts.ResolvedModuleWithFailedLookupLocations[] => {
|
|
160
|
+
const fallback = originalResolveModuleNameLiterals
|
|
161
|
+
? (originalResolveModuleNameLiterals(
|
|
162
|
+
moduleLiterals as unknown as readonly ts.StringLiteralLike[],
|
|
163
|
+
containingFile,
|
|
164
|
+
redirectedReference,
|
|
165
|
+
compilerOptions,
|
|
166
|
+
containingSourceFile,
|
|
167
|
+
reusedNames as readonly ts.StringLiteralLike[] | undefined,
|
|
168
|
+
) as readonly ts.ResolvedModuleWithFailedLookupLocations[])
|
|
169
|
+
: moduleLiterals.map((moduleLiteral) => ({
|
|
170
|
+
resolvedModule: fallbackResolveModule(
|
|
171
|
+
moduleLiteral.text,
|
|
172
|
+
containingFile,
|
|
173
|
+
compilerOptions,
|
|
174
|
+
),
|
|
175
|
+
}));
|
|
176
|
+
|
|
177
|
+
return moduleLiterals.map((moduleLiteral, index) => {
|
|
178
|
+
const record = getOrBuildRecord(moduleLiteral.text, containingFile);
|
|
179
|
+
if (!record) {
|
|
180
|
+
return fallback[index];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
resolvedModule: toResolvedModule(options.ts, record.virtualFileName),
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
(host as ts.CompilerHost).resolveModuleNameLiterals = assignResolveModuleNameLiterals;
|
|
189
|
+
|
|
190
|
+
const getSourceTextForRecord = (record: MutableVirtualRecord): string => {
|
|
191
|
+
const fresh = rebuildIfStale(record);
|
|
192
|
+
if (record.virtualFileName.includes(VIRTUAL_NODE_MODULES_RELATIVE)) {
|
|
193
|
+
return rewriteSourceForPreviewLocation(
|
|
194
|
+
fresh.sourceText,
|
|
195
|
+
fresh.importer,
|
|
196
|
+
record.virtualFileName,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return fresh.sourceText;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
host.getSourceFile = (
|
|
203
|
+
fileName: string,
|
|
204
|
+
languageVersionOrOptions: ts.ScriptTarget | ts.CreateSourceFileOptions,
|
|
205
|
+
onError?: (message: string) => void,
|
|
206
|
+
shouldCreateNewSourceFile?: boolean,
|
|
207
|
+
): ts.SourceFile | undefined => {
|
|
208
|
+
const record = recordsByVirtualFile.get(fileName);
|
|
209
|
+
if (!record) {
|
|
210
|
+
return originalGetSourceFile(
|
|
211
|
+
fileName,
|
|
212
|
+
languageVersionOrOptions,
|
|
213
|
+
onError,
|
|
214
|
+
shouldCreateNewSourceFile,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const sourceText = getSourceTextForRecord(record);
|
|
219
|
+
return options.ts.createSourceFile(
|
|
220
|
+
fileName,
|
|
221
|
+
sourceText,
|
|
222
|
+
languageVersionOrOptions as ts.ScriptTarget,
|
|
223
|
+
true,
|
|
224
|
+
options.ts.ScriptKind.TS,
|
|
225
|
+
);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
if (originalGetSourceFileByPath) {
|
|
229
|
+
(host as ts.CompilerHost).getSourceFileByPath = (
|
|
230
|
+
fileName: string,
|
|
231
|
+
path: ts.Path,
|
|
232
|
+
languageVersionOrOptions: ts.ScriptTarget | ts.CreateSourceFileOptions,
|
|
233
|
+
onError?: (message: string) => void,
|
|
234
|
+
shouldCreateNewSourceFile?: boolean,
|
|
235
|
+
): ts.SourceFile | undefined => {
|
|
236
|
+
const record = recordsByVirtualFile.get(fileName);
|
|
237
|
+
if (!record) {
|
|
238
|
+
return originalGetSourceFileByPath(
|
|
239
|
+
fileName,
|
|
240
|
+
path,
|
|
241
|
+
languageVersionOrOptions,
|
|
242
|
+
onError,
|
|
243
|
+
shouldCreateNewSourceFile,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const sourceText = getSourceTextForRecord(record);
|
|
248
|
+
return options.ts.createSourceFile(
|
|
249
|
+
fileName,
|
|
250
|
+
sourceText,
|
|
251
|
+
languageVersionOrOptions as ts.ScriptTarget,
|
|
252
|
+
true,
|
|
253
|
+
options.ts.ScriptKind.TS,
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
host.fileExists = (fileName: string): boolean => {
|
|
259
|
+
if (recordsByVirtualFile.has(fileName)) {
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return originalFileExists(fileName);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
host.readFile = (fileName: string): string | undefined => {
|
|
267
|
+
const record = recordsByVirtualFile.get(fileName);
|
|
268
|
+
if (!record) {
|
|
269
|
+
return originalReadFile(fileName);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return getSourceTextForRecord(record);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
if (originalHasInvalidatedResolutions) {
|
|
276
|
+
(
|
|
277
|
+
host as { hasInvalidatedResolutions: (...args: readonly unknown[]) => boolean }
|
|
278
|
+
).hasInvalidatedResolutions = (...args: readonly unknown[]) => {
|
|
279
|
+
if (invalidatedPaths.size > 0) {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
return originalHasInvalidatedResolutions(...args);
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
dispose(): void {
|
|
288
|
+
(
|
|
289
|
+
host as { resolveModuleNameLiterals?: (...args: readonly unknown[]) => readonly unknown[] }
|
|
290
|
+
).resolveModuleNameLiterals = originalResolveModuleNameLiterals;
|
|
291
|
+
(
|
|
292
|
+
host as {
|
|
293
|
+
resolveModuleNames?: (
|
|
294
|
+
...args: readonly unknown[]
|
|
295
|
+
) => readonly (ts.ResolvedModule | undefined)[];
|
|
296
|
+
}
|
|
297
|
+
).resolveModuleNames = originalResolveModuleNames;
|
|
298
|
+
host.getSourceFile = originalGetSourceFile;
|
|
299
|
+
if (originalGetSourceFileByPath) {
|
|
300
|
+
(
|
|
301
|
+
host as {
|
|
302
|
+
getSourceFileByPath?: (...args: readonly unknown[]) => ts.SourceFile | undefined;
|
|
303
|
+
}
|
|
304
|
+
).getSourceFileByPath = originalGetSourceFileByPath;
|
|
305
|
+
}
|
|
306
|
+
host.fileExists = originalFileExists;
|
|
307
|
+
host.readFile = originalReadFile;
|
|
308
|
+
(
|
|
309
|
+
host as { hasInvalidatedResolutions?: (...args: readonly unknown[]) => boolean }
|
|
310
|
+
).hasInvalidatedResolutions = originalHasInvalidatedResolutions;
|
|
311
|
+
|
|
312
|
+
store.dispose();
|
|
313
|
+
invalidatedPaths.clear();
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
};
|