Scenario 1 - Utilizing Memory Only without File System Access (e.g. on the web)
Completing this task is not simple and may require some time to accomplish. While there might be a more straightforward approach, I have yet to discover one.
- Create a
ts.CompilerHost
with methods like fileExists
, readFile
, directoryExists
, getDirectories()
, etc., that retrieve data from memory instead of the actual file system.
- Import the relevant lib files into your in-memory file system based on your requirements (e.g., lib.es6.d.ts or lib.dom.d.ts).
- Add your in-memory file to the in-memory file system as well.
- Develop a program (using
ts.createProgram
) and provide your custom ts.CompilerHost
.
- Use
ts.getPreEmitDiagnostics(program)
to obtain the diagnostics.
Imperfect Sample
Below is a brief imperfect example that lacks proper implementation of an in-memory file system and fails to load the lib files (resulting in global diagnostic errors... which can be ignored or addressed by calling specific methods on program
other than program.getGlobalDiagnostics()
. For information about the behavior of ts.getPreEmitDiagnostics
, refer to the link here):
import * as ts from "typescript";
console.log(getDiagnosticsForText("const t: number = '';").map(d => d.messageText));
function getDiagnosticsForText(text: string) {
const dummyFilePath = "/file.ts";
const textAst = ts.createSourceFile(dummyFilePath, text, ts.ScriptTarget.Latest);
const options: ts.CompilerOptions = {};
const host: ts.CompilerHost = {
fileExists: filePath => filePath === dummyFilePath,
directoryExists: dirPath => dirPath === "/",
getCurrentDirectory: () => "/",
getDirectories: () => [],
getCanonicalFileName: fileName => fileName,
getNewLine: () => "\n",
getDefaultLibFileName: () => "",
getSourceFile: filePath => filePath === dummyFilePath ? textAst : undefined,
readFile: filePath => filePath === dummyFilePath ? text : undefined,
useCaseSensitiveFileNames: () => true,
writeFile: () => {}
};
const program = ts.createProgram({
options,
rootNames: [dummyFilePath],
host
});
return ts.getPreEmitDiagnostics(program);
}
Scenario 2 - File System Accessibility
If you have access to the file system, the process becomes much simpler, and you can utilize a function similar to the one provided below:
import * as path from "path";
function getDiagnosticsForText(
rootDir: string,
text: string,
options?: ts.CompilerOptions,
cancellationToken?: ts.CancellationToken
) {
options = options || ts.getDefaultCompilerOptions();
const inMemoryFilePath = path.resolve(path.join(rootDir, "__dummy-file.ts"));
const textAst = ts.createSourceFile(inMemoryFilePath, text, options.target || ts.ScriptTarget.Latest);
const host = ts.createCompilerHost(options, true);
overrideIfInMemoryFile("getSourceFile", textAst);
overrideIfInMemoryFile("readFile", text);
overrideIfInMemoryFile("fileExists", true);
const program = ts.createProgram({
options,
rootNames: [inMemoryFilePath],
host
});
return ts.getPreEmitDiagnostics(program, textAst, cancellationToken);
function overrideIfInMemoryFile(methodName: keyof ts.CompilerHost, inMemoryValue: any) {
const originalMethod = host[methodName] as Function;
host[methodName] = (...args: unknown[]) => {
// resolve the path because TypeScript will normalize it
// to forward slashes on Windows
const filePath = path.resolve(args[0] as string);
if (filePath === inMemoryFilePath)
return inMemoryValue;
return originalMethod.apply(host, args);
};
}
}
// example...
console.log(getDiagnosticsForText(
__dirname,
"import * as ts from 'typescript';\n const t: string = ts.createProgram;"
));
By following this method, the compiler will search for a node_modules
folder in the provided rootDir
and utilize the typings located there (without requiring them to be loaded into memory separately).
Update: Simplified Solution
A library named @ts-morph/bootstrap has been developed to streamline the setup process with the Compiler API. It also automatically loads TypeScript lib files when using an in-memory file system.
import { createProject, ts } from "@ts-morph/bootstrap";
const project = await createProject({ useInMemoryFileSystem: true });
const myClassFile = project.createSourceFile(
"MyClass.ts",
"export class MyClass { prop: string; }",
);
const program = project.createProgram();
ts.getPreEmitDiagnostics(program); // verify these