Implement Rubocop DSL RBI compiler
Motivation
When authoring a custom cop, one is likely to use the def_node_matcher or def_node_search macros, which generate methods. Sorbet will complain if the generated methods are used, as it does not know they exist, requiring the developer to write a shim.
Let's automate that.
Implementation
I looked at a couple other DSL compilers and based my work off of them.
~One tricky thing is that Rubocop expects custom cops to be defined in its namespace (i.e. RuboCop::Cop::<department name>::<cop name>). Therefore, we need a way to figure out which constants to generate DSLs for, and which to skip. The approach I came up with was to check if the const_source_location is in a gem, or within the host app. Although there may be a better way to do this.~
I ended up scrapping all this and just generating DSL RBI files for everything, and in tests just ignoring the gathered constants which already existed at the start of the test. I think this is in line with the end result of other compilers, which often end up producing RBI files not just for constants defined by the host app, but also for constants defined by gems (including sometimes the very gem the compiler is for).
Tests
I've added simple tests that check a couple uses of the macros, which result in slightly different method signatures.
⚠️ Before Merging
- [x] Tidy up commits
I've addressed feedback, written docs, and tidied up the commits.
One thing I considered was seeing if we could specify that the first parameter to the methods should be a ::RuboCop::AST::Node. However, the methods both default to using self if no node is passed in, as well as implement a guard clause, so technically anything is allowed to be passed in, so for simplicity I stuck with the generated sig whose first param is T.untyped.
I haven't read through all the comments, but I see this got an approval, is something preventing it from being merged?
I've pushed some updates, mainly consisting of:
- Adding a
extenders_ofhelper method (not sure about this name). - Extending gathered constants to include all modules which extend
RuboCop::AST::NodePattern::Macros, rather than descendants ofRuboCop::Cop::Base. - Updating the lazy compiler definition approach from using rescuing
LoadErrorto checking if constants aredefined?.
I think it should be good to go, but given the changes it might be worth another look.
Extending gathered constants to include all modules which extend RuboCop::AST::NodePattern::Macros, rather than descendants of RuboCop::Cop::Base.
What's the advantage of this? Wouldn't all cops inherit from RuboCop::Cop::Base?
Also, are all cops we want to generate RBIs for required when Tapioca loads the application? For example, extensions like rubocop-rails are required through the .rubocop.yml configuration file and you can do the same thing for custom cops defined inside the application.
Just trying to understand if custom cops will need to be manually required in the require.rb file.