src/typescript/ts-compiler.ts (78 lines of code) (raw):

import * as ts from 'typescript'; export class TypeScriptCompiler { private readonly realHost = ts.createCompilerHost(STANDARD_COMPILER_OPTIONS, true); /** * A compiler-scoped cache to avoid having to re-parse the same library files for every compilation */ private readonly fileCache = new Map<string, ts.SourceFile | undefined>(); public createInMemoryCompilerHost( sourcePath: string, sourceContents: string, currentDirectory?: string, ): ts.CompilerHost { const realHost = this.realHost; const sourceFile = ts.createSourceFile(sourcePath, sourceContents, ts.ScriptTarget.Latest); return { ...realHost, fileExists: (filePath) => filePath === sourcePath || this.fileCache.has(filePath) || realHost.fileExists(filePath), getCurrentDirectory: currentDirectory != null ? () => currentDirectory : realHost.getCurrentDirectory, getSourceFile: (fileName, languageVersion, onError, shouldCreateNewSourceFile) => { if (fileName === sourcePath) { return sourceFile; } const existing = this.fileCache.get(fileName); if (existing) { return existing; } const parsed = realHost.getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile); this.fileCache.set(fileName, parsed); return parsed; }, readFile: (filePath) => (filePath === sourcePath ? sourceContents : realHost.readFile(filePath)), writeFile: () => void undefined, }; } public compileInMemory(filename: string, contents: string, currentDirectory?: string): CompilationResult { if (!filename.endsWith('.ts')) { // Necessary or the TypeScript compiler won't compile the file. filename += '.ts'; } const program = ts.createProgram({ rootNames: [filename], options: STANDARD_COMPILER_OPTIONS, host: this.createInMemoryCompilerHost(filename, contents, currentDirectory), }); const rootFile = program.getSourceFile(filename); if (rootFile == null) { throw new Error(`Oopsie -- couldn't find root file back: ${filename}`); } return { program, rootFile }; } } export interface CompilationResult { program: ts.Program; rootFile: ts.SourceFile; } export const STANDARD_COMPILER_OPTIONS: ts.CompilerOptions = { alwaysStrict: true, charset: 'utf8', declaration: true, declarationMap: true, experimentalDecorators: true, inlineSourceMap: true, inlineSources: true, lib: ['lib.es2020.d.ts'], module: ts.ModuleKind.CommonJS, noEmitOnError: true, noFallthroughCasesInSwitch: true, noImplicitAny: true, noImplicitReturns: true, noImplicitThis: true, noUnusedLocals: false, // Important, becomes super annoying without this noUnusedParameters: false, // Important, becomes super annoying without this resolveJsonModule: true, strict: true, strictNullChecks: true, strictPropertyInitialization: true, stripInternal: true, target: ts.ScriptTarget.ES2020, // Incremental builds incremental: true, tsBuildInfoFile: '.tsbuildinfo', };