|
| 1 | +import * as fs from "fs"; |
| 2 | +import * as path from "path"; |
| 3 | +import * as ts from "typescript"; |
| 4 | +import { CompilerOptions } from "../../CompilerOptions"; |
| 5 | +import { intersection, union } from "../../utils"; |
| 6 | + |
| 7 | +const libCache = new Map<string, ts.SourceFile>(); |
| 8 | +export function createVirtualProgram(input: Record<string, string>, options: CompilerOptions = {}): ts.Program { |
| 9 | + function notImplemented(): never { |
| 10 | + throw new Error("Not implemented"); |
| 11 | + } |
| 12 | + |
| 13 | + const getFileFromInput = (fileName: string) => |
| 14 | + input[fileName] ?? (fileName.startsWith("/") ? input[fileName.slice(1)] : undefined); |
| 15 | + |
| 16 | + const compilerHost: ts.CompilerHost = { |
| 17 | + useCaseSensitiveFileNames: () => false, |
| 18 | + getCanonicalFileName: fileName => fileName, |
| 19 | + getCurrentDirectory: () => "/", |
| 20 | + fileExists: fileName => fileName.startsWith("lib.") || getFileFromInput(fileName) !== undefined, |
| 21 | + readFile: notImplemented, |
| 22 | + writeFile: notImplemented, |
| 23 | + getDefaultLibFileName: ts.getDefaultLibFileName, |
| 24 | + getNewLine: () => "\n", |
| 25 | + |
| 26 | + getSourceFile(fileName) { |
| 27 | + const fileFromInput = getFileFromInput(fileName); |
| 28 | + if (fileFromInput !== undefined) { |
| 29 | + return ts.createSourceFile(fileName, fileFromInput, ts.ScriptTarget.Latest, false); |
| 30 | + } |
| 31 | + |
| 32 | + if (libCache.has(fileName)) return libCache.get(fileName)!; |
| 33 | + |
| 34 | + if (fileName.startsWith("lib.")) { |
| 35 | + const typeScriptDir = path.dirname(require.resolve("typescript")); |
| 36 | + const filePath = path.join(typeScriptDir, fileName); |
| 37 | + const content = fs.readFileSync(filePath, "utf8"); |
| 38 | + |
| 39 | + const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, false); |
| 40 | + libCache.set(fileName, sourceFile); |
| 41 | + return sourceFile; |
| 42 | + } |
| 43 | + }, |
| 44 | + }; |
| 45 | + |
| 46 | + return ts.createProgram(Object.keys(input), options, compilerHost); |
| 47 | +} |
| 48 | + |
| 49 | +export interface TranspiledFile { |
| 50 | + sourceFiles: ts.SourceFile[]; |
| 51 | + lua?: string; |
| 52 | + luaSourceMap?: string; |
| 53 | + declaration?: string; |
| 54 | + declarationMap?: string; |
| 55 | + /** @internal */ |
| 56 | + js?: string; |
| 57 | + /** @internal */ |
| 58 | + jsSourceMap?: string; |
| 59 | +} |
| 60 | + |
| 61 | +export function createEmitOutputCollector() { |
| 62 | + const files: TranspiledFile[] = []; |
| 63 | + const writeFile: ts.WriteFileCallback = (fileName, data, _bom, _onError, sourceFiles = []) => { |
| 64 | + let file = files.find(f => intersection(f.sourceFiles, sourceFiles).length > 0); |
| 65 | + if (!file) { |
| 66 | + file = { sourceFiles: [...sourceFiles] }; |
| 67 | + files.push(file); |
| 68 | + } else { |
| 69 | + file.sourceFiles = union(file.sourceFiles, sourceFiles); |
| 70 | + } |
| 71 | + |
| 72 | + if (fileName.endsWith(".lua")) { |
| 73 | + file.lua = data; |
| 74 | + } else if (fileName.endsWith(".lua.map")) { |
| 75 | + file.luaSourceMap = data; |
| 76 | + } else if (fileName.endsWith(".js")) { |
| 77 | + file.js = data; |
| 78 | + } else if (fileName.endsWith(".js.map")) { |
| 79 | + file.jsSourceMap = data; |
| 80 | + } else if (fileName.endsWith(".d.ts")) { |
| 81 | + file.declaration = data; |
| 82 | + } else if (fileName.endsWith(".d.ts.map")) { |
| 83 | + file.declarationMap = data; |
| 84 | + } |
| 85 | + }; |
| 86 | + |
| 87 | + return { writeFile, files }; |
| 88 | +} |
0 commit comments