/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ // @flow import type { Node, TraversalAncestors } from "@babel/types"; export type { Node, TraversalAncestors }; export default function createSimplePath(ancestors: TraversalAncestors) { if (ancestors.length === 0) { return null; } // Slice the array because babel-types traverse may continue mutating // the ancestors array in later traversal logic. return new SimplePath(ancestors.slice()); } export type { SimplePath }; /** * Mimics @babel/traverse's NodePath API in a simpler fashion that isn't as * heavy, but still allows the ease of passing paths around to process nested * AST structures. */ class SimplePath { _index: number; _ancestors: TraversalAncestors; _ancestor: $ElementType; _parentPath: SimplePath | null | void; constructor( ancestors: TraversalAncestors, index: number = ancestors.length - 1 ) { if (index < 0 || index >= ancestors.length) { console.error(ancestors); throw new Error("Created invalid path"); } this._ancestors = ancestors; this._ancestor = ancestors[index]; this._index = index; } get parentPath(): SimplePath | null { let path = this._parentPath; if (path === undefined) { if (this._index === 0) { path = null; } else { path = new SimplePath(this._ancestors, this._index - 1); } this._parentPath = path; } return path; } get parent(): Node { return this._ancestor.node; } get node(): Node { const { node, key, index } = this._ancestor; if (typeof index === "number") { return node[key][index]; } return node[key]; } get key(): string { return this._ancestor.key; } set node(replacement: Node): void { if (this.type !== "Identifier") { throw new Error( "Replacing anything other than leaf nodes is undefined behavior " + "in t.traverse()" ); } const { node, key, index } = this._ancestor; if (typeof index === "number") { node[key][index] = replacement; } else { node[key] = replacement; } } get type(): string { return this.node.type; } get inList(): boolean { return typeof this._ancestor.index === "number"; } get containerIndex(): number { const { index } = this._ancestor; if (typeof index !== "number") { throw new Error("Cannot get index of non-array node"); } return index; } get depth(): number { return this._index; } replace(node: Node) { this.node = node; } find(predicate: SimplePath => boolean): SimplePath | null { for (let path = this; path; path = path.parentPath) { if (predicate(path)) { return path; } } return null; } findParent(predicate: SimplePath => boolean): SimplePath | null { if (!this.parentPath) { throw new Error("Cannot use findParent on root path"); } return this.parentPath.find(predicate); } getSibling(offset: number): ?SimplePath { const { node, key, index } = this._ancestor; if (typeof index !== "number") { throw new Error("Non-array nodes do not have siblings"); } const container = node[key]; const siblingIndex = index + offset; if (siblingIndex < 0 || siblingIndex >= container.length) { return null; } return new SimplePath( this._ancestors.slice(0, -1).concat([{ node, key, index: siblingIndex }]) ); } }