Situation 1 - Using only memory - No Access to File System (Ex. on the web)
This is not a straightforward task and may take a little while to do. Perhaps there is an easier way, but I haven't found one yet.
- Implement a
ts.CompilerHost
where methods like fileExists
, readFile
, directoryExists
, getDirectories()
, etc. read from memory instead of the actual file system.
- Load in the appropriate lib files into your in memory file system depending on what you need (ex. lib.es6.d.ts or lib.dom.d.ts).
- Add your in memory file to the in memory file system as well.
- Create a program (using
ts.createProgram
) and pass in your custom ts.CompilerHost
.
- Call
ts.getPreEmitDiagnostics(program)
to get the diagnostics.
Imperfect Example
Here's a short imperfect example that does not properly implement an in memory file system and does not load the lib files (so there will be global diagnostic errors... those can be ignored or you could call specific methods on program
other than program.getGlobalDiagnostics()
. Note the behaviour of ts.getPreEmitDiagnostics
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);
}
Situation 2 - Access to the file system
If you have access to the file system then this is a lot easier and you can use a function similar to the one 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;"
));
Doing it this way, the compiler will search the provided rootDir
for a node_modules
folder and use the typings in there (they don't need to be loaded into memory in some other way).
Update: Easiest Solution
I've created a library called @ts-morph/bootstrap that makes getting setup with the Compiler API much easier. It will load in TypeScript lib files for you too even 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); // check these