Jql ast - Transforming the ast

We define a collection of transformation functions on various node types within the AST. This can be used in conjunction with the creators API to add, remove and update nodes within the tree. The modified tree can be printed to a JQL string using the print API.

Before you start

These functions will mutate the structure of your AST, meaning any positional data of nodes within the tree may be nullified or otherwise inaccurate. As such it's recommended that you clone the AST object before performing any transformations if this state is shared across multiple places in your code.

Query

The following transformations can be applied to the root Query object in the AST:

appendClause

Append the provided clause to the query. If there is no previous where clause then it will be set to the provided value, otherwise a compound clause will be formed using the provided compound operator.

enterQuery: (query: Query) => { const newClause = creators.terminalClause( creators.field('status'), creators.operator(OPERATOR_EQUALS), creators.valueOperand('open') ); query.appendClause(newClause, COMPOUND_OPERATOR_AND); }

removeClause

Remove the provided clause from the node. If the clause to remove is not found as a child of the current node then no changes will be made.

enterQuery: (query: Query) => { if (query.where) { query.removeClause(query.where); } }

replaceClause

Replace the matching child clause with the provided nextClause node. If the clause to replace is not found as a child of the current node then no changes will be made.

enterQuery: (query: Query) => { if (query.where) { const newClause = creators.terminalClause( creators.field('status'), creators.operator(OPERATOR_EQUALS), creators.valueOperand('open') ); query.replaceClause(query.where, newClause); } }

prependOrderField

Prepend the provided order by field to the list of fields in the order by node. If orderBy is undefined then a new order by node is set with the provided field.

enterQuery: (query: Query) => { const newOrderField = creators.orderByField( creators.field('key'), creators.orderByDirection(ORDER_BY_DIRECTION_DESC), ); query.prependOrderField(newOrderField); }

setOrderDirection

Set the direction of the primary order by field to the provided value. If there is no primary order by field then this function is a noop.

enterQuery: (query: Query) => { query.setOrderDirection( creators.orderByDirection(ORDER_BY_DIRECTION_DESC) ); }

replaceOrderBy

Replace existing orderBy with the provided orderBy node. If the orderBy node does not contain any fields, then the orderBy node is removed from the query.

enterQuery: (query: Query) => { const newOrderField = creators.orderByField( creators.field('key'), creators.orderByDirection(ORDER_BY_DIRECTION_DESC), ); const orderByNode = creators.orderBy([newOrderField]); query.replaceOrderBy(orderByNode); }

removeOrderBy

Remove the existing orderBy from the query.

enterQuery: (query: Query) => { query.removeOrderBy(); }

Clause

The following transformations can be applied to all Clause nodes in the AST, i.e. CompoundClause, NotClause and TerminalClause:

remove

Remove the current node from its parent.

enterTerminalClause: (terminalClause: TerminalClause) => { if (terminalClause.field.value === 'assignee') { terminalClause.remove(); } }

replace

Replace the current node from its parent with the provided node.

enterTerminalClause: (terminalClause: TerminalClause) => { if (terminalClause.field.value === 'assignee') { const newClause = creators.terminalClause( creators.field('status'), creators.operator(OPERATOR_EQUALS), creators.valueOperand('open') ); terminalClause.replace(newClause); } }

TerminalClause

The following transformations can be applied to terminal clauses in the AST, e.g. issuetype = bug:

setOperator

Set the operator of the current terminal clause.

enterTerminalClause: (terminalClause: TerminalClause) => { terminalClause.setOperator(creators.operator(OPERATOR_NOT_EQUALS)); }

setOperand

Set the operand of the current terminal clause.

enterTerminalClause: (terminalClause: TerminalClause) => { terminalClause.setOperand(creators.keywordOperand()); }

appendOperand

Append the provided operand to the end of this terminal clause. If the existing operand is a singular value then it will be converted into a list operand and the provided value will be appended.

enterTerminalClause: (terminalClause: TerminalClause) => { terminalClause.appendOperand(creators.keywordOperand()); }

CompoundClause

The following transformations can be applied to compound clauses in the AST, e.g. issuetype = bug and created > -1w:

appendClause

Append the provided clause to this compound clause. If the clause to append is also a compound clause sharing the same operator as this node then the two compound clauses will be merged.

enterCompoundClause: (compoundClause: CompoundClause) => { const newClause = creators.terminalClause( creators.field('status'), creators.operator(OPERATOR_EQUALS), creators.valueOperand('open') ); compoundClause.appendClause(newClause); }

removeClause

Remove the provided clause from the node. If the clause to remove is not found as a child of the current node then no changes will be made.

If the CompoundClause has only 1 child clause remaining after the operation, then the current node will be replaced with the child clause (flattening the tree structure). If there are 0 child clauses remaining then the compound clause will be removed entirely.

enterCompoundClause: (compoundClause: CompoundClause) => { compoundClause.removeClause(compoundClause.clauses[0]); }

replaceClause

Replace the matching child clause with the provided nextClause node. If the clause to replace is not found as a child of the current node then no changes will be made.

enterCompoundClause: (compoundClause: CompoundClause) => { const newClause = creators.terminalClause( creators.field('status'), creators.operator(OPERATOR_EQUALS), creators.valueOperand('open') ); compoundClause.replaceClause(compoundClause.clauses[0], newClause); }

NotClause

The following transformations can be applied to not clauses in the AST, e.g. not issuetype = bug:

removeClause

If the clause to remove is not found as a child of the current node then no changes will be made. Otherwise, this node will be removed entirely.

enterNotClause: (notClause: NotClause) => { notClause.removeClause(notClause.clause); }

replaceClause

Replace the matching child clause with the provided nextClause node. If the clause to replace is not found as a child of the current node then no changes will be made.

enterNotClause: (notClause: NotClause) => { const newClause = creators.terminalClause( creators.field('status'), creators.operator(OPERATOR_EQUALS), creators.valueOperand('open') ); notClause.replaceClause(notClause.clause, newClause); }

ListOperand

The following transformations can be applied to list based operands in the AST, e.g. (bug, task, subTaskIssueTypes()):

appendOperand

Add the provided operand at the end of the current list. If the provided value is also a list then it will be merged into the current operand.

enterListOperand: (listOperand: ListOperand) => { listOperand.appendOperand(creators.valueOperand('open')); }

OrderBy

The following transformations can be applied to the order by clause in the AST, e.g. ORDER BY key DESC:

prependOrderField

Prepend the provided order by field to the list of fields in this node.

enterOrderBy: (orderBy: OrderBy) => { const newOrderField = creators.orderByField( creators.field('key'), creators.orderByDirection(ORDER_BY_DIRECTION_DESC), ); orderBy.prependOrderField(newOrderField); }

setOrderDirection

Set the direction of the primary order by field to the provided value. If there is no primary order by field then this function is a noop.

enterOrderBy: (orderBy: OrderBy) => { orderBy.setOrderDirection( creators.orderByDirection(ORDER_BY_DIRECTION_DESC) ); }

replace

Replace the orderBy with the provided orderBy node. If the orderBy node does not contain any fields, then the orderBy node is removed.

enterOrderBy: (orderBy: OrderBy) => { const newField = creators.orderByField( creators.field('key'), creators.orderByDirection(ORDER_BY_DIRECTION_DESC), ); const newOrderBy = creators.orderBy([newField]); orderBy.replace(newOrderBy); }

replaceOrderField

Replace the matching child field with the provided nextOrderByField node. If the field to replace is not found then no changes will be made.

enterOrderBy: (orderBy: OrderBy) => { const nextOrderByField = creators.orderByField( creators.field('key'), creators.orderByDirection(ORDER_BY_DIRECTION_DESC), ); orderBy.replaceOrderByField(orderBy.fields[0], nextOrderByField); }

remove

Remove the existing orderBy node from the query.

enterOrderBy: (orderBy: OrderBy) => { orderBy.remove(); }

removeOrderField

Remove the provided orderBy field from the node. If the field to remove is not found as a child of the current node then no changes will be made.

If there are 0 child fields remaining then the orderBy node will be removed entirely.

enterOrderBy: (orderBy: OrderBy) => { orderBy.removeOrderField(orderBy.fields[0]); }

OrderByField

The following transformations can be applied to the order by fields in the AST, e.g. key DESC:

setOrderDirection

Set the direction of this order by field to the provided value.

enterOrderByField: (orderByField: OrderByField) => { orderByField.setOrderDirection( creators.orderByDirection(ORDER_BY_DIRECTION_DESC) ); }

replace

Replace the current orderBy field with the provided orderBy field.

enterOrderByField: (orderByField: OrderByField) => { const fieldNew = creators.orderByField( creators.field('key'), creators.orderByDirection(ORDER_BY_DIRECTION_DESC), ); orderByField.replace(fieldNew); }

remove

Remove the current orderBy field from the orderBy node.

enterOrderByField: (orderByField: OrderByField) => { orderByField.remove(); }