It's easy to create a SoureFile object from a plain string:
ts.createSourceFile(fileName, sourceText, languageVersion, setParentNodes, scriptKind)
However, I don't see a way to create one from an array of Statement-nodes (created by the various factory functions).
I tried to bodge a solution like this:
const source = ts.createSourceFile(fileName, '', languageVersion);
source.statements = myNodeArray;
But this (perhaps unsurprisingly) doesn't work. I also tried (ab)using the transformer API like this:
function createSourcefile(filename: string, ast: ts.Node[], languageVersion: ts.ScriptTarget): ts.SourceFile {
    const dummy = ts.createSourceFile(filename, 'dummy', languageVersion); // need at least 1 node
    return ts.transform(
        dummy,
        [ transformContext => sourceFile => ts.visitEachChild(sourceFile, node => ast, transformContext) ]
    ).transformed[0];
}
But this doesn't appear to work either.
With both methods I get get following error during the emit process:
Error: start < 0
  at createTextSpan (node_modules\typescript\lib\typescript.js:10263:19)
  at Object.createTextSpanFromBounds (node_modules\typescript\lib\typescript.js:10272:16)
  at getErrorSpanForNode (node_modules\typescript\lib\typescript.js:13544:19)
  at createDiagnosticForNodeInSourceFile (node_modules\typescript\lib\typescript.js:13449:20)
  at Object.createDiagnosticForNode (node_modules\typescript\lib\typescript.js:13440:16)
  at lookupOrIssueError (node_modules\typescript\lib\typescript.js:34976:22)
  at addDuplicateDeclarationError (node_modules\typescript\lib\typescript.js:35177:23)
  at \node_modules\typescript\lib\typescript.js:35173:17
  at Object.forEach (node_modules\typescript\lib\typescript.js:317:30)
  at addDuplicateDeclarationErrorsForSymbols (node_modules\typescript\lib\typescript.js:35171:16)
  at mergeSymbol (node_modules\typescript\lib\typescript.js:35158:21)
  at \node_modules\typescript\lib\typescript.js:35200:47
  at Map.forEach (<anonymous>)
  at mergeSymbolTable (node_modules\typescript\lib\typescript.js:35198:20)
  at initializeTypeChecker (node_modules\typescript\lib\typescript.js:66463:21)
  at Object.createTypeChecker (node_modules\typescript\lib\typescript.js:34935:9)
  at getDiagnosticsProducingTypeChecker (node_modules\typescript\lib\typescript.js:98560:93)
  at emitWorker (node_modules\typescript\lib\typescript.js:98588:32)
  at \node_modules\typescript\lib\typescript.js:98569:66
  at runWithCancellationToken (node_modules\typescript\lib\typescript.js:98665:24)
  at Object.emit (node_modules\typescript\lib\typescript.js:98569:20)
  *snip*
Is there a way to get this to work?
I guess I could in theory use a printer to convert the AST into a string, but this would obviously be a massive waste.
I've made a gist with a self-contained example using a 'virtual compiler host' and David Sherret's range stripping suggestion.
Strangely enough, I discovered that this error doesn't happen with all node types. In my (limited) testing I only encountered it when the AST contained an ImportDeclaration node.
Thanks for the reproducible example! I've completely changed my answer so see the history for my past answer.
I looked into this and what's occurring is the following:
"Cannot find module 'bar'." (Note: This is after fixing ts.createImportDeclaration to provide a string literal and not an identifier for the module specifier)createTextSpan function throws because the synthetic nodes have a position less than zero. This is regardless of my previous stripRanges suggestion because ts.createImportDeclaration creates a node with negative positions.So after doing this investigation I've been reminded that the type checker often makes the assumption that the nodes will refer to positions in the source file text. This is because the transformation phase of compiling happens after type checking.
If you want type checking on your created source file, I think you'll need to print it to a string and reparse to get a new source file containing descendant nodes with correct positions, then use that.
If you don't care about type checking then unfortunately at the moment I don't think there's a way to transform the code without type checking using the current API. It might be possible to hack something into custom transformers when emitting though... perhaps have an empty file, then add the statements in during custom transformation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With