dsherret / ts-morph

TypeScript Compiler API wrapper for static analysis and programmatic code changes.
https://ts-morph.com
MIT License
4.9k stars 194 forks source link

Deep structures allowing for one parse #1562

Open AustinGrey opened 2 months ago

AustinGrey commented 2 months ago

Is your feature request related to a problem? Please describe.

I'm building an interface to allow someone to not only view an AST, but edit it using a GUI. Think a linter that when it detects that an identifier is misspelled, it opens up a dropdown for a few suggestions to update it to. ts-morph is a clear fit to make this possible, but for our initial prototypes we built a subset of the ts morph AST with a small handful of handwritten, simple, object interfaces in order to prove the concept. Simple objects are nice, because they can be passed to Signal/Proxy based libraries like Vue without issue. I can modify a portion of the interface and GUI components update accordingly. We would like to not have our own set of interfaces, since that would mean duplicating effort and implementing an interface for each of the node kinds that ts-morph and typescript define. However Proxies are incompatible with the Node structures that ts-morph provides -> which are classes with private members. If I pass a SourceFile node to vue with ref(mySourceFileNode) it will choke and die when it tries to proxy the private members of the SourceFile class instance.

Structures seem suitable for this problem, they are plain objects so they should be compatible with Proxies. But they seem to be limited to a certain depth. For example a file like const foo = bar(); Produces a SourceFile > VariableStatement > VariableDeclaration structure, but the variable declaration lists the initializer as the text bar(), rather than what I would expect to be a CallExpression structure with an Identifier structure for the bar identifier being called. I'm unsure why the structures just seem to stop at this arbitrary depth. Because of this, that would mean I would need to reparse initializer text as it's own program to get it's structure, or somehow keep a connection to the original nodes so I can look up what node was actually used as the initializer and then get it's structure.

Describe the solution you'd like

I'd like for structures to be arbitrarily deep, or for a 'deep structure' option to be available, so that a single structure can be used for all nodes in a file.

Describe alternatives you've considered

We could continue down the path of writing a subset of interfaces, our own deep structure system. But owing to how large typescript is, we inevitably make simplifying assumptions that lead us down the wrong path and paint us into corners.

We also tried an interface that operated on the Node directly, and tried to manage reactivity of the UI manually so that we didn't need proxies. But manually managing reactivity of the 100 or so components we would need to make for this is a nightmare scenario - brittle, and hard to maintain.