import * as escodegen from 'escodegen'; import * as estraverse from 'estraverse'; import * as ESTree from 'estree'; import { ecmaVersion } from '../constants/EcmaVersion'; import { ASTParserFacade } from '../ASTParserFacade'; import { NodeGuards } from './NodeGuards'; import { NodeMetadata } from './NodeMetadata'; export class NodeUtils { /** * @param {ESTree.Literal} literalNode * @returns {ESTree.Literal} */ public static addXVerbatimPropertyTo (literalNode: ESTree.Literal): ESTree.Literal { literalNode['x-verbatim-property'] = { content: literalNode.raw, precedence: escodegen.Precedence.Primary }; return literalNode; } /** * @param {T} astTree * @returns {T} */ public static clone (astTree: T): T { return NodeUtils.parentizeAst(NodeUtils.cloneRecursive(astTree)); } /** * @param {string} code * @returns {ESTree.Statement[]} */ public static convertCodeToStructure (code: string): ESTree.Statement[] { const structure: ESTree.Program = ASTParserFacade.parse( code, { ecmaVersion, sourceType: 'script' } ); estraverse.replace(structure, { enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node => { NodeUtils.parentizeNode(node, parentNode); if (NodeGuards.isLiteralNode(node)) { NodeUtils.addXVerbatimPropertyTo(node); } NodeMetadata.set(node, { ignoredNode: false }); return node; } }); return structure.body; } /** * @param {NodeGuards[]} structure * @returns {string} */ public static convertStructureToCode (structure: ESTree.Node[]): string { return structure.reduce((code: string, node: ESTree.Node) => { return code + escodegen.generate(node, { sourceMapWithCode: true }).code; }, ''); } /** * @param {UnaryExpression} unaryExpressionNode * @returns {NodeGuards} */ public static getUnaryExpressionArgumentNode (unaryExpressionNode: ESTree.UnaryExpression): ESTree.Node { if (NodeGuards.isUnaryExpressionNode(unaryExpressionNode.argument)) { return NodeUtils.getUnaryExpressionArgumentNode(unaryExpressionNode.argument); } return unaryExpressionNode.argument; } /** * @param {T} astTree * @returns {T} */ public static parentizeAst (astTree: T): T { const parentNode: ESTree.Node | null = astTree.parentNode ?? null; estraverse.replace(astTree, { enter: NodeUtils.parentizeNode }); if (parentNode) { astTree.parentNode = parentNode; } return astTree; } /** * @param {T} node * @param {Node} parentNode * @returns {T} */ public static parentizeNode (node: T, parentNode: ESTree.Node | null): T { node.parentNode = parentNode ?? node; return node; } /** * @param {T} node * @returns {T} */ private static cloneRecursive (node: T): T { if (node === null) { return node; } const copy: Partial = {}; const nodeKeys: (keyof T)[] = <(keyof T)[]>Object.keys(node); nodeKeys .forEach((property: keyof T) => { if (property === 'parentNode') { return; } const value: T[keyof T] | T[keyof T][] | null = node[property] ?? null; let clonedValue: T[keyof T] | T[keyof T][] | null; if (value === null || value instanceof RegExp) { clonedValue = value; } else if (value instanceof Array) { clonedValue = value.map(NodeUtils.cloneRecursive); } else if (typeof value === 'object') { clonedValue = NodeUtils.cloneRecursive(value); } else { clonedValue = value; } copy[property] = clonedValue; }); return copy; } }