Search code examples
javascriptgoogle-closure-compiler

Using google closure compiler to parse javascript source


I would like to parse a given ES6 source file using the google closure compiler in order to build a custom 'object-oriented' representation of the program. This representation would include details of all the classes in the source file and the methods and variables contained within those classes. I have completed this task for Java programs where I used Antlr - once you have a suitable grammar you can register enter and exit listeners for any desired grammar rule (class declaration, method declaration, etc...) which made the implementation fairly straight forward. I would appreciate any help being able to parse JavaScript code using the google closure compiler in order to extract similar information about the source code.

So far I have the following code which will parse a given javascript source file:

Compiler compiler = new Compiler();
CompilerOptions options = new CompilerOptions();
options.setIdeMode(true);
compiler.initOptions(options);
Node root = new JsAst(SourceFile.fromCode(file.name(), file.content())).getAstRoot(compiler);
NodeTraversal.traverseEs6(compiler, root, new JavaScriptParsePass());

The JavaScriptParsePass class simply outputs the type and qualified name of every Node processed, it looks like the following:

public class JavaScriptParsePass extends AbstractPostOrderCallback implements CompilerPass {

    @Override
    public void process(Node externs, Node root) {
    }

    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
        System.out.println(n.getType() + ": " + n.getQualifiedName());
    }
}

Running this program on the input: class Model { constructor(properties) { this.properties = properties; }

Produces output:

38: Model
124: null
38: null
38: properties
83: null
42: this
40: null
33: this.properties
38: properties
86: null
130: null
125: null
105: null
160: null
159: null
158: null
132: null`

I would appreciate an explanation of this output as the ordering and nulls do not make sense to me along with any general guidance on how to tackle the original problem.


Solution

  • The following code prints out all methods and classes in a given JavaScript program and outlines the basic method for analyzing JavaScript code using the Google Closure-Compiler's Java API, for more details see this post I wrote.

    First we need to extend the AbstractShallowCallback class which provides a way to iterate through the nodes in the parse tree. We provide an implementation for the visit method which will output the Node's value if it is a node we are interested in.

    public class JavaScriptAnalyzer extends AbstractShallowCallback {
    
    @Override
        public void visit(NodeTraversal t, Node n, Node parent) {
            if (n.isClass()) {
                System.out.println(n.getFirstChild().getString());
            }
            if (n.isMemberFunctionDef() || n.isGetterDef() || n.isSetterDef()) {
                System.out.println(n.getString());
            }
            if (n.isFunction()) {
                System.out.println(n.getFirstChild().getString());
            }
            // there is more work required to detect all types of methods that
            // has been left out for brevity...
        }
    }
    

    Next we initialize a compiler and run our created JavaScript analyzer on a given JavaScript source file.

    public void parse(String jsFileContent, String jsName) throws Exception {
        Compiler compiler = new Compiler();
        CompilerOptions options = new CompilerOptions();
        options.setIdeMode(true);
        compiler.initOptions(options);
        Node root = new JsAst(SourceFile.fromCode(jsName, jsFileContent)).getAstRoot(compiler);
        JavaScriptAnalyzer jsListener = new JavaScriptAnalyzer();
        NodeTraversal.traverseEs6(compiler, root, jsListener);
    }
    

    Running the above code on the following source file:

    class Polygon {
       constructor(height, width) {}
       logWidth() {}
       set width(value) {}
       get height(value) {}
    }
    

    Produces the following output as expected:

    constructor
    logWidth
    width
    height
    Polygon