Jql ast - Traversing the ast

The @atlaskit/jql-ast package currently exposes 2 API’s consumers can use to traverse the AST, the listener and visitor API’s.

Listener API

The consumer will implement methods in the JastListener interface corresponding to nodes they wish to inspect. Once a listener has been defined the consumer can call walkAST which will traverse the AST and invoke the correct methods in the listener. For example if I wanted to count the number of field nodes in an AST:

import { walkAST, JastListener, Jast, Field } from '@atlaskit/jql-ast'; class FieldCountListener implements JastListener { count: number = 0; enterField = (field: Field) => { this.count += 1; }; } const countFields = (jast: Jast): number => { const listener = new FieldCountListener(); walkAST(listener, jast); return listener.count; };

Visitor API

Using this API, consumers define a visitor which implements one or more methods to visit nodes of a given type.

In most cases you'll want to extend the provided AbstractJastVisitor which includes a default implementation to perform a depth-first traversal of the tree. Rather than having to navigate the entire tree structure yourself, you can extend this class and implement visitor functions for node types that you wish to process.

You'll need to implement the defaultResult method which is the default value that will be initialised when visiting children of a node. You can also override the aggregateResult method to specify how return values of children should be aggregated.

We could implement our conditional counting example from above like so:

import { AbstractJastVisitor, Jast, Field } from '@atlaskit/jql-ast'; class FieldCountVisitor extends AbstractJastVisitor<number> { visitField = (field: Field): number => { return 1; }; protected aggregateResult(aggregate: number, nextResult: number): number { return aggregate + nextResult; } protected defaultResult(): number { return 0; } } const countFields = (jast: Jast): number => { const visitor = new FieldCountVisitor(); return visitor.visit(jast.query); };

Differences

The main difference between a listener and visitor is that a listener cannot control how the tree is traversed. With a visitor you can return values from each visitor function and stop visiting of subtrees entirely.

In the above example we have implemented the visitField method, meaning that unless we explicitly visit children in our implementation (i.e. this.visitChildren(field)) we won't traverse any deeper into the tree.

In general, visitors provide a more flexible API and are better suited to gathering information from the tree as you can define explicit return types for your functions.