From b2c8d2c7b7fc2fb3f9341d7ff27fd82d0fc3bffe Mon Sep 17 00:00:00 2001 From: Jarrod Overson Date: Mon, 6 Jul 2020 23:51:01 -0400 Subject: [PATCH] add GlobalState manager and separated it from RefactorSession --- src/index.ts | 2 +- src/refactor-plugin-common.ts | 14 +- src/refactor-plugin-unsafe.ts | 12 +- src/refactor-session-chainable.ts | 19 +- src/refactor-session.ts | 441 ++++++++++++++-------------- src/util.ts | 14 +- test/index.test.ts | 8 +- test/refactor-chainable-api.test.ts | 10 +- test/refactor-session.test.ts | 12 +- 9 files changed, 268 insertions(+), 264 deletions(-) diff --git a/src/index.ts b/src/index.ts index b3f8dd7..9785f63 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import { RefactorSessionChainable } from './refactor-session-chainable'; import pluginUnsafe from './refactor-plugin-unsafe'; import pluginCommon from './refactor-plugin-common'; -export { RefactorSession } from './refactor-session'; +export { RefactorSession, GlobalState as GlobalSession } from './refactor-session'; export { RefactorSessionChainable } from './refactor-session-chainable' // export * as Shift from 'shift-ast'; diff --git a/src/refactor-plugin-common.ts b/src/refactor-plugin-common.ts index 2d5ce6e..9b36265 100644 --- a/src/refactor-plugin-common.ts +++ b/src/refactor-plugin-common.ts @@ -127,7 +127,7 @@ export default function pluginCommon() { }, unshorten(this: RefactorSessionChainable) { - const lookupTable = this.session.getLookupTable(); + const lookupTable = this.session.globalSession.getLookupTable(); this.nodes.forEach((node: Node) => { if (node.type !== 'VariableDeclarator') { @@ -143,9 +143,9 @@ export default function pluginCommon() { const lookup = lookupTable.variableMap.get(from); lookup[0].declarations.forEach((decl: Declaration) => (decl.node.name = to.name)); lookup[0].references.forEach((ref: Reference) => (ref.node.name = to.name)); - this.session._queueDeletion(node); + this.session.globalSession._queueDeletion(node); }); - return this.session.conditionalCleanup(); + return this.session.globalSession.conditionalCleanup(); }, expandBoolean(this: RefactorSessionChainable) { @@ -157,14 +157,14 @@ export default function pluginCommon() { `UnaryExpression[operator="!"][operand.value=1]`, () => new LiteralBooleanExpression({ value: false }), ); - return this.session.conditionalCleanup(); + return this.session.globalSession.conditionalCleanup(); }, normalizeIdentifiers(this: RefactorSessionChainable, seed = 1) { - const lookupTable = this.session.getLookupTable(); + const lookupTable = this.session.globalSession.getLookupTable(); const idGenerator = new MemorableIdGenerator(seed); - renameScope(lookupTable.scope, idGenerator, this.session.parentMap); - return this.session.conditionalCleanup(); + renameScope(lookupTable.scope, idGenerator, this.session.globalSession.parentMap); + return this.session.globalSession.conditionalCleanup(); } } diff --git a/src/refactor-plugin-unsafe.ts b/src/refactor-plugin-unsafe.ts index d04a178..fbb8cf9 100644 --- a/src/refactor-plugin-unsafe.ts +++ b/src/refactor-plugin-unsafe.ts @@ -17,12 +17,12 @@ export default function pluginUnsafe() { massRename(this: RefactorSessionChainable, namePairs: string[][]) { namePairs.forEach(([from, to]) => { - this.session.lookupVariableByName(from).forEach((lookup: Variable) => this.session.renameInPlace(lookup, to)); + this.lookupVariableByName(from).forEach((lookup: Variable) => this.session.renameInPlace(lookup, to)); }); }, inlineLiterals(this: RefactorSessionChainable) { - for (const variable of this.session.variables.values()) { + for (const variable of this.session.globalSession.variables.values()) { // Haven't thought about how to deal with this yet. Might be easy. PR welcome. if (variable.declarations.length !== 1) continue; const declaration = variable.declarations[0]; @@ -43,7 +43,7 @@ export default function pluginUnsafe() { } } } - this.session.conditionalCleanup(); + this.session.globalSession.conditionalCleanup(); }, removeDeadVariables(this: RefactorSessionChainable) { @@ -55,8 +55,8 @@ export default function pluginUnsafe() { // TODO handle this at some point. if (nameNode.type === 'ArrayBinding' || nameNode.type === 'ObjectBinding') return; - const lookup = this.session.lookupVariable(nameNode); - const scope = this.session.lookupScope(lookup) as Scope; + const lookup = this.session.globalSession.lookupVariable(nameNode); + const scope = this.session.globalSession.lookupScope(lookup) as Scope; if (scope.type === ScopeType.GLOBAL) return; @@ -103,7 +103,7 @@ export default function pluginUnsafe() { this.session.delete(decl); } }); - return this.session.conditionalCleanup(); + return this.session.globalSession.conditionalCleanup(); } } diff --git a/src/refactor-session-chainable.ts b/src/refactor-session-chainable.ts index 84c3a7c..c293248 100644 --- a/src/refactor-session-chainable.ts +++ b/src/refactor-session-chainable.ts @@ -2,7 +2,7 @@ import { Node } from 'shift-ast'; import { Declaration, Reference, Variable } from 'shift-scope'; import pluginCommon from './refactor-plugin-common'; import pluginUnsafe from './refactor-plugin-unsafe'; -import { RefactorSession } from './refactor-session'; +import { RefactorSession, GlobalState } from './refactor-session'; import { Replacer, SelectorOrNode, SimpleIdentifier, SimpleIdentifierOwner } from './types'; // type ApiExtension = { [key: string]: any }; @@ -243,12 +243,12 @@ export class RefactorSessionChainable { return this.$(this.session.findOne(selectorOrNode)); } - references(): Reference[] { - return this.session.findReferences(this.first() as SimpleIdentifier | SimpleIdentifierOwner); + references(): Reference[][] { + return this.nodes.map(node => this.session.globalSession.findReferences(node as SimpleIdentifier | SimpleIdentifierOwner)); } - declarations(): Declaration[] { - return this.session.findDeclarations(this.first() as SimpleIdentifier | SimpleIdentifierOwner) + declarations(): Declaration[][] { + return this.nodes.map(node => this.session.globalSession.findDeclarations(node as SimpleIdentifier | SimpleIdentifierOwner)); } closest(closestSelector: string) { @@ -257,11 +257,11 @@ export class RefactorSessionChainable { lookupVariable(): Variable { const id = this.first() as SimpleIdentifierOwner | SimpleIdentifierOwner[] | SimpleIdentifier | SimpleIdentifier[]; - return this.session.lookupVariable(id); + return this.session.globalSession.lookupVariable(id); } lookupVariableByName(name: string): Variable[] { - return this.session.lookupVariableByName(name); + return this.session.globalSession.lookupVariableByName(name); } generate() { @@ -282,6 +282,7 @@ export const RefactorChainableWithPlugins = RefactorSessionChainable.with(plugin * @alpha */ export function refactor(input: string | Node, { autoCleanup = true } = {}) { - const globalSession = new RefactorSession(input, { autoCleanup }); - return RefactorSessionChainable.with(pluginUnsafe).with(pluginCommon).create(globalSession); + const globalSession = new GlobalState(input, { autoCleanup }); + const refactorSession = new RefactorSession(globalSession.root, globalSession); + return RefactorSessionChainable.with(pluginUnsafe).with(pluginCommon).create(refactorSession); } diff --git a/src/refactor-session.ts b/src/refactor-session.ts index 01a7322..38fb1ef 100644 --- a/src/refactor-session.ts +++ b/src/refactor-session.ts @@ -14,7 +14,6 @@ import shiftScope, { Declaration, Reference, Scope, ScopeLookup, Variable } from import traverser from 'shift-traverser'; import { default as isValid } from 'shift-validator'; import { query } from './query'; -import { RefactorPlugin } from './refactor-plugin'; import { RefactorError, Replacer, SelectorOrNode, SimpleIdentifier, SimpleIdentifierOwner } from './types'; import { buildParentMap, @@ -26,11 +25,9 @@ import { isFunction, isShiftNode, isStatement, - isString, - identityLogger + isString } from './util'; import { waterfallMap } from './waterfall'; -import { RefactorSessionChainable } from './refactor-session-chainable'; /** * Parse JavaScript source with shift-parser @@ -56,65 +53,29 @@ export interface RefactorConfig { export class RefactorSession { nodes: Node[]; _root?: Node; - globalSession: RefactorSession; - autoCleanup = true; - - private dirty = false; + globalSession: GlobalState; - scopeMap: WeakMap = new WeakMap(); - scopeOwnerMap: WeakMap = new WeakMap(); - parentMap: WeakMap = new WeakMap(); - variables: Set = new Set(); - - private replacements = new WeakMap(); - private deletions = new WeakSet(); - private insertions = new WeakMap(); - private lookupTable: ScopeLookup | undefined; - - constructor(sourceOrNodes: string | Node | Node[], config: RefactorConfig = {}) { + constructor(sourceOrNodes: Node | Node[] | string, globalSession?: GlobalState) { let nodes: Node[], tree: Node; - if (config.parentSession) { - this.globalSession = config.parentSession.globalSession; - if (isString(sourceOrNodes)) throw new Error('Can not initialize child RefactorSession with a new AST'); - if (isArray(sourceOrNodes)) { - //@ts-ignore : as any[] cast tracker https://github.com/microsoft/TypeScript/issues/36390 - nodes = (sourceOrNodes as any[]).filter((x: string | Node): x is Node => typeof x !== 'string'); - } else { - nodes = [sourceOrNodes]; - } + if (!globalSession) { + if (typeof sourceOrNodes === 'string' || !isArray(sourceOrNodes)) this.globalSession = new GlobalState(sourceOrNodes); + else throw new Error('Only source or a single Script/Module node can be passed as input'); } else { - if (isString(sourceOrNodes)) { - try { - tree = parseScript(sourceOrNodes); - this._root = tree; - } catch (e) { - throw new RefactorError(`Could not parse passed source: ${e}`); - } - nodes = [tree]; - } else { - if (isArray(sourceOrNodes)) throw new RefactorError('Must initialize root session with a JavaScript source program (JS source or single Node)'); - nodes = [sourceOrNodes]; - this._root = sourceOrNodes; - } - this.globalSession = this; + this.globalSession = globalSession; } - this.nodes = nodes; - - if (config.autoCleanup) this.autoCleanup = config.autoCleanup; - this.rebuildParentMap(); - - this.getLookupTable(); + if (isArray(sourceOrNodes)) { + nodes = (sourceOrNodes as any[]).filter((x: string | Node): x is Node => typeof x !== 'string'); + } else { + if (!isString(sourceOrNodes)) nodes = [sourceOrNodes]; + else nodes = [this.globalSession.root]; + } + this.nodes = nodes; } get root(): Node { - if (this._root) { - return this._root; - } else { - if (this === this.globalSession && !this._root) throw new Error('why'); - return this.globalSession.root; - } + return this.globalSession.root; } get length(): number { @@ -127,21 +88,12 @@ export class RefactorSession { subSession(querySessionOrNodes: SelectorOrNode | RefactorSession) { const nodes = querySessionOrNodes instanceof RefactorSession ? querySessionOrNodes.nodes : findNodes(this.nodes, querySessionOrNodes); - const subSession = new RefactorSession(nodes, { parentSession: this }); + const subSession = new RefactorSession(nodes, this.globalSession); return subSession; } - use(Plugin: new (session: RefactorSession) => T) { - const plugin = new Plugin(this); - plugin.register(); - } - - private rebuildParentMap() { - this.parentMap = buildParentMap(this.nodes); - } - rename(selectorOrNode: SelectorOrNode, newName: string) { - const lookupTable = this.getLookupTable(); + const lookupTable = this.globalSession.getLookupTable(); const nodes = findNodes(this.nodes, selectorOrNode); @@ -164,9 +116,9 @@ export class RefactorSession { delete(selectorOrNode: SelectorOrNode = this.nodes) { const nodes = findNodes(this.nodes, selectorOrNode); if (nodes.length > 0) { - nodes.forEach((node: Node) => this._queueDeletion(node)); + nodes.forEach((node: Node) => this.globalSession._queueDeletion(node)); } - return this.conditionalCleanup(); + return this.globalSession.conditionalCleanup(); } replace(selectorOrNode: SelectorOrNode, replacer: Replacer) { @@ -205,14 +157,14 @@ export class RefactorSession { } } if (node && replacement !== node) { - this._queueReplacement(node, replacement); + this.globalSession._queueReplacement(node, replacement); return true; } else { return false; } }); - this.conditionalCleanup(); + this.globalSession.conditionalCleanup(); return replaced.filter((wasReplaced: any) => wasReplaced).length; } @@ -240,21 +192,21 @@ export class RefactorSession { } if (node && replacement !== node) { - this._queueReplacement(node, replacement); + this.globalSession._queueReplacement(node, replacement); return true; } else { return false; } }); - this.conditionalCleanup(); + this.globalSession.conditionalCleanup(); return promiseResults.filter(result => result).length; } replaceRecursive(selectorOrNode: SelectorOrNode, replacer: Replacer) { const nodesReplaced = this.replace(selectorOrNode, replacer); - this.cleanup(); + this.globalSession.cleanup(); if (nodesReplaced > 0) this.replaceRecursive(selectorOrNode, replacer); return this; } @@ -263,42 +215,8 @@ export class RefactorSession { return this.nodes[0]; } - private insert(selectorOrNode: SelectorOrNode, replacer: Replacer, after = false): ReturnType { - if (this !== this.globalSession) { - return this.globalSession.insert(selectorOrNode, replacer, after); - } - const nodes = findNodes(this.nodes, selectorOrNode); - - let insertion: Node | null = null; - let getInsertion = (program: Replacer, node: Node) => { - if (isFunction(program)) { - const result = program(node); - if (isShiftNode(result)) return result; - return parseScript(result).statements[0]; - } else { - if (insertion) return copy(insertion); - if (isShiftNode(program)) return copy(program); - return (insertion = parseScript(program).statements[0]); - } - }; - - nodes.forEach((node: Node) => { - if (!isStatement(node)) throw new RefactorError('Can only insert before or after Statements or Declarations'); - this.isDirty(true); - const toInsert = getInsertion(replacer, node); - if (!isStatement(toInsert)) throw new RefactorError('Will not insert anything but a Statement or Declaration'); - this.insertions.set(node, { - after, - statement: getInsertion(replacer, node), - }); - }); - - return this.conditionalCleanup(); - } - findParents(selectorOrNode: SelectorOrNode): Node[] { - const nodes = findNodes(this.nodes, selectorOrNode); - return nodes.map(identityLogger).map(node => this.globalSession.parentMap.get(node)).map(identityLogger).filter((node): node is Node => !!node); + return this.globalSession.findParents(selectorOrNode); } prepend(selectorOrNode: SelectorOrNode, replacer: Replacer) { @@ -309,103 +227,6 @@ export class RefactorSession { return this.globalSession.insert(selectorOrNode, replacer, true); } - _queueDeletion(node: Node): void { - if (this !== this.globalSession) { - return this.globalSession._queueDeletion(node); - } - this.isDirty(true); - this.deletions.add(node); - } - - _queueReplacement(from: Node, to: Node): void { - if (this !== this.globalSession) { - return this.globalSession._queueReplacement(from, to); - } - this.isDirty(true); - this.replacements.set(from, to); - } - - getLookupTable(): ScopeLookup { - if (this.lookupTable) return this.lookupTable; - const globalScope = shiftScope(this.root); - this.lookupTable = new ScopeLookup(globalScope); - this._rebuildScopeMap(); - return this.lookupTable; - } - - _rebuildScopeMap() { - const lookupTable = this.getLookupTable(); - this.scopeMap = new WeakMap(); - this.variables = new Set(); - const recurse = (scope: Scope) => { - this.scopeOwnerMap.set(scope.astNode, scope); - scope.variableList.forEach((variable: Variable) => { - this.variables.add(variable); - this.scopeMap.set(variable, scope); - }); - scope.children.forEach(recurse); - }; - recurse(lookupTable.scope); - } - - isDirty(dirty?: boolean) { - if (dirty !== undefined) this.dirty = dirty; - return this.dirty; - } - - validate() { - return isValid(this.nodes); - } - - conditionalCleanup() { - if (this.autoCleanup) this.cleanup(); - return this; - } - - cleanup(): RefactorSession { - if (this !== this.globalSession) { - return this.globalSession.cleanup(); - } - if (!this.isDirty()) return this; - const _this = this; - const result = this.nodes.map(node => traverser.replace(node, { - leave: function (node: Node, parent: Node) { - if (node.type === 'VariableDeclarationStatement') { - if (node.declaration.declarators.length === 0) return this.remove(); - } - if (_this.replacements.has(node)) { - const newNode = _this.replacements.get(node); - _this.replacements.delete(node); - return newNode; - } - if (_this.insertions.has(node)) { - if (isStatement(node)) { - const insertion = _this.insertions.get(node); - if ('statements' in parent) { - let statementIndex = parent.statements.indexOf(node); - if (insertion.after) statementIndex++; - parent.statements.splice(statementIndex, 0, insertion.statement); - _this.insertions.delete(node); - } else { - debug(`Tried to insert ${node.type} but I lost track of my parent block :-(`); - } - } else { - debug(`Tried to insert a non-Statement (${node.type}). Skipping.`); - } - } - if (_this.deletions.has(node)) { - _this.replacements.delete(node); - this.remove(); - } - }, - })); - this.lookupTable = undefined; - this.rebuildParentMap(); - this.isDirty(false); - this.nodes = result; - return this; - } - query(selector: string | string[]) { return query(this.nodes, selector); } @@ -443,23 +264,23 @@ export class RefactorSession { return []; } - findOne(selectorOrNode: string) { - const nodes = this.query(selectorOrNode); - if (nodes.length !== 1) - throw new Error(`findOne('${selectorOrNode}') found ${nodes.length} nodes. If this is intentional, use .find()`); - return nodes[0]; - } - findReferences(node: SimpleIdentifier | SimpleIdentifierOwner): Reference[] { - const lookup = this.lookupVariable(node); + const lookup = this.globalSession.lookupVariable(node); return lookup.references; } findDeclarations(node: SimpleIdentifier | SimpleIdentifierOwner): Declaration[] { - const lookup = this.lookupVariable(node); + const lookup = this.globalSession.lookupVariable(node); return lookup.declarations; } + findOne(selectorOrNode: string) { + const nodes = this.query(selectorOrNode); + if (nodes.length !== 1) + throw new Error(`findOne('${selectorOrNode}') found ${nodes.length} nodes. If this is intentional, use .find()`); + return nodes[0]; + } + closest(originSelector: SelectorOrNode, closestSelector: string): Node[] { const nodes = findNodes(this.nodes, originSelector); @@ -474,6 +295,59 @@ export class RefactorSession { return nodes.flatMap((node: Node) => recurse(node, closestSelector)); } + cleanup() { + this.globalSession.cleanup(); + return this; + } + + generate(ast?: Node) { + const generator = new FormattedCodeGen(); + return codegen(ast || this.first(), generator); + } +} + +export class GlobalState { + autoCleanup = true; + + private dirty = false; + private _root: Node; + + + scopeMap: WeakMap = new WeakMap(); + scopeOwnerMap: WeakMap = new WeakMap(); + parentMap: WeakMap = new WeakMap(); + variables: Set = new Set(); + + private replacements = new WeakMap(); + private deletions = new WeakSet(); + private insertions = new WeakMap(); + + private lookupTable: ScopeLookup | undefined; + + constructor(sourceOrNode: string | Node, config: RefactorConfig = {}) { + let tree; + if (isString(sourceOrNode)) { + try { + tree = parseScript(sourceOrNode); + } catch (e) { + throw new RefactorError(`Could not parse passed source: ${e}`); + } + } else { + tree = sourceOrNode; + } + this._root = tree; + + if (config.autoCleanup) this.autoCleanup = config.autoCleanup; + + this.rebuildParentMap(); + + this.getLookupTable(); + } + + get root(): Node { + return this._root; + } + lookupScope(variableLookup: Variable | Variable[] | SimpleIdentifierOwner | SimpleIdentifierOwner[] | SimpleIdentifier | SimpleIdentifier[]) { if (isArray(variableLookup)) variableLookup = variableLookup[0]; @@ -482,6 +356,16 @@ export class RefactorSession { return this.scopeMap.get(variableLookup); } + findReferences(node: SimpleIdentifier | SimpleIdentifierOwner): Reference[] { + const lookup = this.lookupVariable(node); + return lookup.references; + } + + findDeclarations(node: SimpleIdentifier | SimpleIdentifierOwner): Declaration[] { + const lookup = this.lookupVariable(node); + return lookup.declarations; + } + getInnerScope(node: FunctionDeclaration) { return this.scopeOwnerMap.get(node); } @@ -527,16 +411,139 @@ export class RefactorSession { return Array.from(varSet) as Variable[]; } + _queueDeletion(node: Node): void { + this.isDirty(true); + this.deletions.add(node); + } + + _queueReplacement(from: Node, to: Node): void { + this.isDirty(true); + this.replacements.set(from, to); + } + + getLookupTable(): ScopeLookup { + if (this.lookupTable) return this.lookupTable; + const globalScope = shiftScope(this.root); + this.lookupTable = new ScopeLookup(globalScope); + this._rebuildScopeMap(); + return this.lookupTable; + } + + _rebuildScopeMap() { + const lookupTable = this.getLookupTable(); + this.scopeMap = new WeakMap(); + this.variables = new Set(); + const recurse = (scope: Scope) => { + this.scopeOwnerMap.set(scope.astNode, scope); + scope.variableList.forEach((variable: Variable) => { + this.variables.add(variable); + this.scopeMap.set(variable, scope); + }); + scope.children.forEach(recurse); + }; + recurse(lookupTable.scope); + } + + isDirty(dirty?: boolean) { + if (dirty !== undefined) this.dirty = dirty; + return this.dirty; + } + + validate() { + return isValid(this.root); + } + + conditionalCleanup() { + if (this.autoCleanup) this.cleanup(); + return this; + } + + cleanup() { + if (!this.isDirty()) return this; + const _this = this; + const result = traverser.replace(this.root, { + leave: function (node: Node, parent: Node) { + if (node.type === 'VariableDeclarationStatement') { + if (node.declaration.declarators.length === 0) return this.remove(); + } + if (_this.replacements.has(node)) { + const newNode = _this.replacements.get(node); + _this.replacements.delete(node); + return newNode; + } + if (_this.insertions.has(node)) { + if (isStatement(node)) { + const insertion = _this.insertions.get(node); + if ('statements' in parent) { + let statementIndex = parent.statements.indexOf(node); + if (insertion.after) statementIndex++; + parent.statements.splice(statementIndex, 0, insertion.statement); + _this.insertions.delete(node); + } else { + debug(`Tried to insert ${node.type} but I lost track of my parent block :-(`); + } + } else { + debug(`Tried to insert a non-Statement (${node.type}). Skipping.`); + } + } + if (_this.deletions.has(node)) { + _this.replacements.delete(node); + this.remove(); + } + }, + }); + this.lookupTable = undefined; + this.rebuildParentMap(); + this.isDirty(false); + this._root = result; + return this; + } + + insert(selectorOrNode: SelectorOrNode, replacer: Replacer, after = false): ReturnType { + const nodes = findNodes([this._root], selectorOrNode); + + let insertion: Node | null = null; + let getInsertion = (program: Replacer, node: Node) => { + if (isFunction(program)) { + const result = program(node); + if (isShiftNode(result)) return result; + return parseScript(result).statements[0]; + } else { + if (insertion) return copy(insertion); + if (isShiftNode(program)) return copy(program); + return (insertion = parseScript(program).statements[0]); + } + }; + + nodes.forEach((node: Node) => { + if (!isStatement(node)) throw new RefactorError('Can only insert before or after Statements or Declarations'); + this.isDirty(true); + const toInsert = getInsertion(replacer, node); + if (!isStatement(toInsert)) throw new RefactorError('Will not insert anything but a Statement or Declaration'); + this.insertions.set(node, { + after, + statement: getInsertion(replacer, node), + }); + }); + + return this.conditionalCleanup(); + } + + findParents(selectorOrNode: SelectorOrNode): Node[] { + const nodes = findNodes([this._root], selectorOrNode); + return nodes.map(node => this.parentMap.get(node)).filter((node): node is Node => !!node); + } + generate(ast?: Node) { if (this.isDirty()) throw new RefactorError( 'refactor .print() called with a dirty AST. This is almost always a bug. Call .cleanup() before printing.', ); - return codegen(ast || this.first(), new FormattedCodeGen()); + return codegen(ast || this._root, new FormattedCodeGen()); } -} -export class GlobalSession extends RefactorSession { - type = 'GlobalSession'; + private rebuildParentMap() { + this.parentMap = buildParentMap(this._root); + } } \ No newline at end of file diff --git a/src/util.ts b/src/util.ts index 977a2d5..728a28f 100644 --- a/src/util.ts +++ b/src/util.ts @@ -125,15 +125,13 @@ export function renameScope(scope: Scope, idGenerator: BaseIdGenerator, parentMa scope.children.forEach(_ => renameScope(_, idGenerator, parentMap)); } -export function buildParentMap(trees: Node[]) { +export function buildParentMap(tree: Node) { const parentMap = new WeakMap(); - trees.forEach(ast => { - traverser.traverse(ast, { - enter: (node: Node, parent: Node) => { - parentMap.set(node, parent); - }, - }); - }) + traverser.traverse(tree, { + enter: (node: Node, parent: Node) => { + parentMap.set(node, parent); + }, + }); return parentMap; } diff --git a/test/index.test.ts b/test/index.test.ts index f6ad05a..7d081b6 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { describe } from 'mocha'; -import { refactor, RefactorSession } from '../src/index'; +import { refactor } from '../src/index'; describe('shift-refactor', function () { it('should export refactor', () => { @@ -8,10 +8,4 @@ describe('shift-refactor', function () { expect($script(`FunctionDeclaration[name.name="foo"]`).length).to.equal(1); }); - it('should export RefactorSession', () => { - const session = new RefactorSession(`function foo(){}\nfoo();`); - const nodes = session.query(`FunctionDeclaration[name.name="foo"]`); - expect(nodes.length).to.equal(1); - }); - }); diff --git a/test/refactor-chainable-api.test.ts b/test/refactor-chainable-api.test.ts index 4a4441f..64155ca 100644 --- a/test/refactor-chainable-api.test.ts +++ b/test/refactor-chainable-api.test.ts @@ -1,8 +1,12 @@ import { expect } from 'chai'; import { describe } from 'mocha'; -import { parseScript as parse } from 'shift-parser'; +import { parseScript } from 'shift-parser'; import { refactor } from '../src/refactor-session-chainable'; -import { LiteralNumericExpression } from 'shift-ast'; +import { LiteralNumericExpression, Script } from 'shift-ast'; + +function parse(src: string): Script { + return parseScript(src); +} describe('chainable interface', () => { it('should be able to take a single source as input', () => { @@ -28,7 +32,7 @@ describe('chainable interface', () => { const $script = refactor(src); $script('CallExpression').closest(':statement').prepend(`a()`); - expect($script.root).to.deep.equal(parse(`a();b();`)); + expect($script.root).to.deep.equal(parse(`a();b(1);`)); }) it('should have .forEach to iterate over nodes', () => { const src = `var a = [1,2,3,4]`; diff --git a/test/refactor-session.test.ts b/test/refactor-session.test.ts index 09fa0ac..4661cc8 100644 --- a/test/refactor-session.test.ts +++ b/test/refactor-session.test.ts @@ -10,7 +10,7 @@ import { BindingIdentifier, } from 'shift-ast'; import { parseScript as parse, parseScript } from 'shift-parser'; -import { RefactorSession } from '../src/refactor-session'; +import { RefactorSession, GlobalState } from '../src/refactor-session'; import { RefactorError } from '../src/types'; import { describe } from 'mocha'; //@ts-ignore VSCode bug? VSC is complaining about this import but TypeScript is fine with it. @@ -327,9 +327,9 @@ describe('RefactorSession', () => { it('should return variables by name', () => { let ast = parse(`var a = 2; var b = 3; (function(b){ var a = "foo" }())`); const refactor = new RefactorSession(ast); - const varsA = refactor.lookupVariableByName('a'); + const varsA = refactor.globalSession.lookupVariableByName('a'); expect(varsA).to.be.lengthOf(2); - const varsB = refactor.lookupVariableByName('b'); + const varsB = refactor.globalSession.lookupVariableByName('b'); expect(varsB).to.be.lengthOf(2); }); }); @@ -338,7 +338,7 @@ describe('RefactorSession', () => { let ast = parse(`var a = 2; function foo(){var b = 4}`); const refactor = new RefactorSession(ast); const innerBinding = refactor.query('BindingIdentifier[name="b"]') as BindingIdentifier[]; - const lookup = refactor.lookupVariable(innerBinding); + const lookup = refactor.globalSession.lookupVariable(innerBinding); expect(lookup).to.be.ok; expect(lookup.declarations.length).to.equal(1); }); @@ -346,13 +346,13 @@ describe('RefactorSession', () => { let ast = parse(`var a = 2; function foo(){var b = 4}`); const refactor = new RefactorSession(ast); const innerBinding = refactor.query('BindingIdentifier[name="b"]') as BindingIdentifier[]; - const lookup = refactor.lookupScope(innerBinding) as Scope; + const lookup = refactor.globalSession.lookupScope(innerBinding) as Scope; expect(lookup).to.be.ok; expect(lookup.astNode).to.equal(ast.statements[1]); }); it('.cleanup()', () => { let ast = parse(``); const refactor = new RefactorSession(ast); - expect(() => refactor.cleanup).to.not.throw(); + expect(() => refactor.cleanup()).to.not.throw(); }); });