Repository navigation
Replies: 2 comments
|
@apiology curious for your take on how this would land in solargraph-rails. The walker implementation here was originally borrowed from there, and the manual AST traversal it carried over has been bugging me ever since. |
|
Update on "The concern: parser → rubocop-ast bridging": turns out the bridging isn't needed. Solargraph already parses through Since I put this together in #1386 (draft), along with the walker and the node processors rewritten on top of node patterns. 🤖 Drafted with Claude Code; the findings are verified on the branch. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Idea: write Solargraph plugins like RuboCop extensions
I've been experimenting with a different shape for Solargraph plugin authoring in lekemula/solargraph-rspec#33, and I'd love to get reactions before pushing it further.
The idea
Make writing a Solargraph plugin feel the same as writing a RuboCop extension:
def_node_matcher/def_node_searchfromrubocop-astand bind them to handler methods.inherited— no manual wiring.contexthash that processors can read/write to cooperate without coupling to each other.Example processor:
Why I think this is worth it
'(block (send _ #let_block_method? ...) ...)'replace brittle manual traversals. Intent is visible at a glance.What this fixes today — real examples from solargraph-rspec
mainThe status quo is reaching into
Parser::AST::Nodechildren by integer index. A few snippets straight from the currentmainbranch:1. "Get the
letmethod name" — five chained safe-navigations, plus a RuboCop disableTo read this you have to hold the full
(block (send _ _ (sym :name)) (args) (body))shape in your head and remember thatchildren[0].children[2]is the first arg of the inner send, whose first child is the:sym. With a node pattern the same lookup is:2. The walker reinvented (badly) pattern matching
The current
Walker::Hookcarries its own positional matcher with a special:anytoken, because plainParser::AST::Nodegives you nothing better:That's a (worse) reimplementation of what
def_node_matcheralready provides for free — and every Solargraph plugin that needs more than "match by type" tends to grow its own variant of it.3. Magic indices spread across helpers
Nothing in the source tells a reader which index means what. If the
parsergem ever changes a node shape, or you want to support a slightly different call form, every magic index is a separate place to update.4. Hand-built fake AST nodes for "let → def" transformation
Fine in isolation, but the children list is a positional contract that only a
parser-gem expert can sanity-check. Withrubocop-astbuilders you'd get a typed node back (DefNode) and the same code reads as(def name (args) body).None of these are "bad code" in isolation — they're what you end up writing when the only API is "an array of children with magic indices". A shared
rubocop-ast-based plugin layer would let every plugin author skip this entire category of code.The concern: parser → rubocop-ast bridging
Solargraph's parser produces
Parser::AST::Node, butrubocop-astpatterns needRuboCop::AST::Node. In the experiment I bridge this with a recursive conversion:RuboCop::AST::Nodeis a subclass ofParser::AST::Node, so this is mostly a re-wrap — but it's still a full recursive pass over every analyzed file, which partly defeats the "parse once" goal above.I think this could be fixed upstream in
rubocop-ast.ProcessedSourcealready has a pre-parsed escape hatch for Prism (PrismPreparsed). A similarParserPreparsed-style entry point would let Solargraph handrubocop-astits already-built AST with the right builder and skip the conversion entirely. Solargraph already depends onrubocoptransitively, so the dependency surface wouldn't change.Expected changes in Solargraph
Concretely, I'd expect the work in Solargraph itself to be scoped to a parallel reimplementation of
lib/solargraph/parser/parser_gem/node_processors— the per-node-type processors that build pins during source mapping — written againstrubocop-astnodes and registered alongside the existing ones, so the two paths can run side by side under a flag while the new pipeline is validated.Because
RuboCop::AST::Nodeis a subclass ofParser::AST::Node, the surrounding plumbing (chains, region tracking, location info, pin shapes) should keep working unchanged — that's the only part I expect to stay the same. Everything that today reaches intochildren[i]by index would migrate to typed accessors / node patterns eventually, as wanted.What I'd like to know
rubocop-astnodes, given the conversion cost can be addressed upstream?Happy to break this up into smaller proposals if it helps.
🤖 Drafted with Claude Code; the idea and concerns are mine.
All reactions