mirror of
https://github.com/jehna/humanify.git
synced 2026-04-28 10:05:52 +03:00
[GH-ISSUE #330] Refactor visitAllIdentifiers to leverage babel's built in scope tracking better #65
Labels
No labels
bug
enhancement
pull-request
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/humanify#65
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @0xdevalias on GitHub (Feb 18, 2025).
Original GitHub issue: https://github.com/jehna/humanify/issues/330
This might be my own naive misunderstanding of how
visitAllIdentifiersworks currently, but from a quick skim through it feels kind of convoluted in how it's usingfindScopes,hasVisited,markVisited, etc; when, if I remember correctly,babel's AST parser +@babel/traversehas built in support for scope tracking/renames/etc that I believe make most of this redundant?I haven't tested this specifically, but after a few rounds of iteration with GitHub Copilot, this is feeling pared down/closer to how I would have expected this feature to have been implemented, leveraging more of Babel's scope tracking/etc:
That said, I could be missing/misunderstanding some nuance of how it's currently implemented that isn't handled properly in this new version, etc.
@j4k0xb commented on GitHub (Feb 18, 2025):
All the magic happens in
smallestScope.scope.rename(smallestScopeNode.name, safeRenamed);, the rest of the code is only used for the LLM to get some context of how the variable is used@0xdevalias commented on GitHub (Feb 21, 2025):
@j4k0xb Hrmm, fair.
Skimming through the logic:
visitAllIdentifiersbabel'sparseAsyncto get the ASTSet's forrenames/visitedfindScopestraverse's the AST and for eachBindingIdentifiernode, callsclosestSurroundingContextPathand calculates thepathSize, then stores that inscopesas[nodePath: NodePath<Identifier>, scopeSize: number][]scopesby theirpathSize.map's them to just theirnodePathnumRenamesExpectedfromscopes.lengthscopeshasVisitedsurroundingCodewithscopeToStringvisitorfunction to get therenamedvariablesafeRenamedcheckingsmallestScope.scope.renamemarkVisitedonProgressIt's been a while since I was looking at this PoC code of mine, so I might be misremembering it, but in this one I was using
Scopablewithbabel.traverseto output all of the scopes:github.com/0xdevalias/poc-ast-tools@05a34a9e74/babel_v1_0_scope_debug.js (L68-L72)And there was also:
path.scope.bindingspath.scope.getAllBindings()And the scopes seemed to have
uid's and types as well:binding.scope.uidbinding.scope.path.typeAnd
path.scope.dump()was useful for debugging too:github.com/0xdevalias/poc-ast-tools@05a34a9e74/babel_v1_3.js (L146-L180)I can't remember off hand if there was a way to just directly access all of the scopes/bindings from the original parsed AST without even needing to
traverseit.I'd need to dig deeper into it to be certain, but my immediate gut feel here is that the current code here in
humanifyfeels closer to my original explorations in my own code, where I was doing a lot of reinventing the wheel to try and process scope/rename things thatbabelalready handled for me.Like here is an earlier iteration where I was manually ensuring I only processed binding renames once:
github.com/0xdevalias/poc-ast-tools@05a34a9e74/babel_v1_1.js (L55-L111)And yet in later versions I switched away from that because there was no need to:
github.com/0xdevalias/poc-ast-tools@05a34a9e74/babel_v1_3_clean.js (L353-L400)Like if you take all of the debug logging out of that, I think the main implementation ends up being:
(Then obviously add back in the 'safe rename' checks using
path.scope.hasBinding(newName)/similar)See also:
path.scope.generateUidIdentifierpath.scope.generateUidIdentifierBasedOnNodepath.scope.renamepath.scope.rename("n", "x")path.scope.rename("n")@0xdevalias commented on GitHub (May 2, 2025):
Some tangentially related notes I wrote on email recently:
@brianjenkins94 commented on GitHub (Jun 2, 2025):
Any thought given to using the TypeScript Compiler API/LSP?
courtesy of ChatGPT
@0xdevalias commented on GitHub (Jun 2, 2025):
@brianjenkins94 For what specifically?
Resurrecting the link from your edit history to try and get more context:
Babel's AST parsing already provides support for renaming identifiers, which is what
humanifyis currently using; so I'm not sure what benefits/differences you're thinking would be possible with this?@brianjenkins94 While I appreciate the intent behind this, based on a quick skim through it, I don't believe it does anything new/different/better than what we're already able to (and doing) directly with Babel's own AST parsing + identifier renaming.
You may find this breakdown / explanation / Babel based example helpful to understand this deeper:
@brianjenkins94 commented on GitHub (Jun 2, 2025):
The current implementation doesn't rename class or static fields so I thought leveraging the TypeScript API could yield better, more contextually-aware results.
@0xdevalias commented on GitHub (Jun 2, 2025):
@brianjenkins94 Ah, ok. This would seem to be the more relevant issue for that, so your comment on this one was somewhat disconnected from that context:
While you may have already seen it, my original exploration into the AST was here:
And this comment seemed to suggest that Babel's default scope tracking doesn't cover private class fields/methods, but linked to one potential solution using Babel's own
babel-helper-create-class-features-plugin'sprivateNameVisitorFactory:I haven't deeply explored that option, nor whether TypeScript's LSP/etc would do better in this case.
But in any case, if that were to be explored deeper, the other issue would be a more appropriate place for that to be done/discussed than here.
Edit: I have copied across the relevant parts of this discussion to that issue: