58

I had read "whole internet", but can't find any examples about getting syntax tree (just like in Esprima) from TypeScrypt source. I mean how can i get object like this (Esprima Parser example)

{
    "type": "Program",
    "body": [
        {
            "type": "VariableDeclaration",
            "declarations": [
                {
                    "type": "VariableDeclarator",
                    "id": {
                        "type": "Identifier",
                        "name": "answer"
                    },
                    "init": {
                        "type": "BinaryExpression",
                        "operator": "*",
                        "left": {
                            "type": "Literal",
                            "value": 6,
                            "raw": "6"
                        },
                        "right": {
                            "type": "Literal",
                            "value": 7,
                            "raw": "7"
                        }
                    }
                }
            ],
            "kind": "var"
        }
    ]
}

from javascript code

var answer = 6 * 7;

only for TypeScript source text?

P.S. I hope very much for your help, because I do not want to write your own terrible bicycle)

P.P.S. I think the lib files typescript.ts(.js) and typescriptServices.ts(.js) to help me, but I do not know how :(

Solved

Thanks a lot to the user Steve Fenton. Here is my code, if anyone interested in:

// uses
var typeScriptLS =  new Harness.TypeScriptLS();
var ServicesFactory = new Services.TypeScriptServicesFactory();
var serviceShim = ServicesFactory.createLanguageServiceShim(typeScriptLS);

// add lib.d.ts
var _libText = window.document.getElementById('lib.d.ts').innerText;
typeScriptLS.addScript('lib.d.ts', _libText.replace(/\r\n?/g,"\n"), true);

// add greeter.ts
var _sourceText = window.document.getElementById('greeter.ts').innerText;
typeScriptLS.addScript('greeter.ts', _sourceText.replace(/\r\n?/g,"\n"), true);

// script name
var _scriptName = 'greeter.ts';
// get syntax tree
var _st = serviceShim.languageService.getSyntaxTree(_scriptName);
//console.log(_st);
console.log(JSON.stringify(_st, "", 2));
bukvaG
  • 597
  • 1
  • 4
  • 8
  • 5
    Where is `Harness` defined in the deployed files? I don't see "Harness" anywhere in tsc.js, typescript.js, or typescriptServices.js. Or more broadly, how do you run this Solved code sample? – Jason Kleban Jul 31 '14 at 13:38
  • I'm sorry if you've been waiting for. /src/harness/ on https://github.com/Microsoft/TypeScript. But now my code may become invalid – bukvaG May 14 '15 at 08:12
  • 2
    Possible duplicate of [How can we get the Syntax Tree of TypeScript?](http://stackoverflow.com/questions/18714501/how-can-we-get-the-syntax-tree-of-typescript) – ColinE Apr 26 '17 at 14:01
  • don't use Harness since is not a public API and you will require to do it inside the TypeScript project itself (so you will need to redistribute it) or access not public TypeScript APIs (risking your software to be broken if the user is not using the correct typescript compiler version. Is possible to do it using babylon parser - see my answer below – cancerbero Jun 20 '18 at 20:08

4 Answers4

28

The TypeScript parser doesn't directly produce a tree like that, but you can still use its object model to do all sorts of things. We use it in some tools to do syntax transforms for testing purposes, for example. Here's a snippet that you can use to print the syntax tree:

import ts from "typescript";
 
const code = "enum { x = 1 }";
const sc = ts.createSourceFile("x.ts", code, ts.ScriptTarget.Latest, true);

let indent = 0;
function print(node: ts.Node) {
    console.log(new Array(indent + 1).join(" ") + ts.SyntaxKind[node.kind]);
    indent++;
    ts.forEachChild(node, print);
    indent--;
}
 
print(sc);
may
  • 27
  • 5
Ryan Cavanaugh
  • 209,514
  • 56
  • 272
  • 235
  • How do I get this into the app.js file?: var TypeScript = require('./typescriptServices'); so that it runs? – Cameron Taggart Jun 01 '14 at 19:54
  • I explained the question in more detail here: http://stackoverflow.com/questions/23983998/how-do-i-require-the-typescriptservices-d-ts-in-a-node-js-app – Cameron Taggart Jun 01 '14 at 20:12
  • 2
    I figured it out and blogged about my findings at http://blog.ctaggart.com/2014/06/typescript-ast-from-nodejs.html – Cameron Taggart Jun 04 '14 at 05:15
  • 1
    Note that you need `var TypeScript = require('typescript-services');` to run this example on nodejs and not `var TypeScript = require('typescript');`. It took me quite a while to figure out where these strange undefined errors were coming from. But the later module does not have an export statement. – yankee Jun 23 '14 at 19:25
9

This question came up before back in September.

There isn't currently something that will do this for you - there is no magic getSyntaxTree method to call that will do this.

The TypeScript compiler is open-source, though - and written entirely in TypeScript so you can scan it to find out if there is something you can use / add a handle to.

The up-side of this is that you have a big opportunity to release your work as an open-source project as judging by the up-votes on the two questions, there is some demand for this.

Alternatively, grab the syntax tree from the compiled JavaScript (which is the code that will actually execute at runtime) using Esprima or SpiderMonkey.

Community
  • 1
  • 1
Fenton
  • 241,084
  • 71
  • 387
  • 401
  • 1
    parsing emitted JavaScript will loose a lot of information (all the types). I think the motivation of this question is just that. – cancerbero Jun 20 '18 at 20:05
5

Using recast and babylon@next is possible. Although you will have to trust in the syntax defined by these technologies for representing TypeScript code AST and that they will keep up to date - since TypeScript has new language features release by release (short period of time) - is not like other languages (JavaScript) where you have well defined versions and released in a standard - so if your users start using new language features these technologies (I guess babylon) should keep up to date or the parsing will fail

// npm install recast babylon@next
const source = `
interface I {
  color: string
}
class C implements I{
  color: string='blue'
}
`
const recast = require('recast')
const tsParser = require("recast/parsers/typescript")
const ast = recast.parse(source, {
  parser: tsParser
});
console.log(`
CODE: 

${source}

AST: 

${JSON.stringify(ast)}
`);
cancerbero
  • 6,799
  • 1
  • 32
  • 24
1

I found recast to be working very good. Example:

var recast = require('recast');
var ast = recast.parse(`var answer = 6 * 7;`);
console.log(ast);

This will output all needed information and event TypeAnnotation, so this lib is really amazing :)

[
   {
      "type": "VariableDeclaration",
      "declarations": [
         {
            "type": "VariableDeclarator",
            "id": {
               "type": "Identifier",
               "name": "answer",
               "typeAnnotation": {
                  "type": "TypeAnnotation",
                  "typeAnnotation": {
                     "type": "NumberTypeAnnotation",
                     "loc": {
                        "start": {
                           "line": 1,
                           "column": 12
                        },
                        "end": {
                           "line": 1,
                           "column": 18
                        },
                        "lines": {},
                        "indent": 0
                     }
                  },
                  "loc": {
                     "start": {
                        "line": 1,
                        "column": 10
                     },
                     "end": {
                        "line": 1,
                        "column": 18
                     },
                     "lines": {},
                     "indent": 0
                  }
               },
               "loc": {
                  "start": {
                     "line": 1,
                     "column": 4
                  },
                  "end": {
                     "line": 1,
                     "column": 18
                  },
                  "lines": {},
                  "indent": 0
               }
            },
            "init": {
               "type": "BinaryExpression",
               "operator": "*",
               "left": {
                  "type": "Literal",
                  "value": 6,
                  "raw": "6",
                  "loc": {
                     "start": {
                        "line": 1,
                        "column": 21
                     },
                     "end": {
                        "line": 1,
                        "column": 22
                     },
                     "lines": {},
                     "indent": 0
                  }
               },
               "right": {
                  "type": "Literal",
                  "value": 7,
                  "raw": "7",
                  "loc": {
                     "start": {
                        "line": 1,
                        "column": 25
                     },
                     "end": {
                        "line": 1,
                        "column": 26
                     },
                     "lines": {},
                     "indent": 0
                  }
               },
               "loc": {
                  "start": {
                     "line": 1,
                     "column": 21
                  },
                  "end": {
                     "line": 1,
                     "column": 26
                  },
                  "lines": {},
                  "indent": 0
               }
            },
            "loc": {
               "start": {
                  "line": 1,
                  "column": 4
               },
               "end": {
                  "line": 1,
                  "column": 26
               },
               "lines": {},
               "indent": 0
            }
         }
      ],
      "kind": "var",
      "loc": {
         "start": {
            "line": 1,
            "column": 0
         },
         "end": {
            "line": 1,
            "column": 27
         },
         "lines": {},
         "indent": 0
      }
   }
]
Andzej Maciusovic
  • 4,306
  • 1
  • 29
  • 40
  • 1
    As far as I have tried, recast won't recognize the full typescript syntax. – Jaime Apr 13 '17 at 09:27
  • almost there- that's JavaScript and it won't work for any TypeScript for example: `let a: string` but is possible using a custom parser, see my answer above. – cancerbero Jun 20 '18 at 20:09