Skip to content

Instantly share code, notes, and snippets.

@HoldYourWaffle
Last active April 14, 2020 17:05
Show Gist options
  • Save HoldYourWaffle/e88534027c669ab17d88b946d4411cbd to your computer and use it in GitHub Desktop.
Save HoldYourWaffle/e88534027c669ab17d88b946d4411cbd to your computer and use it in GitHub Desktop.
A self-contained example for emitting a synthesized SourceFile (which currently results in a "start < 0" error)
import ts from 'typescript'
import fs from 'fs'
function synthesizeSourcefile(filename: string, ast: ts.Statement[], languageVersion: ts.ScriptTarget): ts.SourceFile {
const dummy = ts.createSourceFile(filename, '', languageVersion);
return stripRanges(ts.updateSourceFileNode(dummy, ast));
}
function stripRanges<T extends ts.Node>(node: T): T {
node.pos = -1;
node.end = -1;
ts.forEachChild(node, stripRanges);
return node;
}
/** Virtual compiler host that can get SourceFile's from an internal array as well as the filesystem */
class VirtualCompilerHost implements ts.CompilerHost {
private fallback = ts.createCompilerHost(this.compilerOptions);
private sources: { [ filename: string ]: ts.SourceFile } = {};
constructor(
private readonly compilerOptions: ts.CompilerOptions,
cachedSources: ts.SourceFile[]
) {
for (const source of cachedSources) {
this.sources[ source.fileName ] = source;
}
}
/** @override */
writeFile(fileName: string, data: string, writeByteOrderMark?: boolean, onError?: (message: string) => void, sourceFiles?: readonly ts.SourceFile[]) {
try {
fs.writeFileSync(fileName, data);
} catch (e) {
onError?.(e);
}
}
/** @override */
fileExists(fileName: string): boolean {
return this.sources[ fileName ] !== undefined || fs.existsSync(fileName);
}
/** @override */
readFile(fileName: string): string {
return fs.readFileSync(fileName, 'utf8');
}
/** @override */
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): ts.SourceFile | undefined {
// Try to get from cache
const cachedSource = this.sources[ fileName ];
if (cachedSource !== undefined) {
return cachedSource;
}
try {
// Try to get from fs
return ts.createSourceFile(fileName, this.readFile(fileName), languageVersion, true);
} catch (e) {
if (e.code === 'ENOENT') {
// no panic needed, we just don't have it
return undefined;
}
onError?.(e);
return undefined;
}
}
// Methods using fallback
getDefaultLibFileName = this.fallback.getDefaultLibFileName
getCurrentDirectory = this.fallback.getCurrentDirectory
getCanonicalFileName = this.fallback.getCanonicalFileName
useCaseSensitiveFileNames = this.fallback.useCaseSensitiveFileNames
getNewLine = this.fallback.getNewLine
}
const compilerOptions: ts.CompilerOptions = {};
const filename = 'synthetic.ts';
const ast = [
// Created using the lovely https://ts-ast-viewer.com ;)
ts.createImportDeclaration(
undefined,
undefined,
ts.createImportClause(
ts.createIdentifier("foo"),
undefined,
false
),
ts.createIdentifier("bar")
)
]
const sourceFile = synthesizeSourcefile(filename, ast, ts.ScriptTarget.Latest);
const program = ts.createProgram({
options: compilerOptions,
rootNames: [ filename ],
host: new VirtualCompilerHost(compilerOptions, [ sourceFile ])
})
program.emit();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment