Skip to content

Commit 924bcef

Browse files
committed
[WIP] Handle all modules extending NodePattern::Macros
1 parent e6b12e1 commit 924bcef

4 files changed

Lines changed: 75 additions & 54 deletions

File tree

lib/tapioca/dsl/compilers/rubocop.rb

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
# typed: strict
22
# frozen_string_literal: true
33

4-
begin
5-
require "rubocop"
6-
rescue LoadError
7-
return
8-
end
4+
return unless defined?(RuboCop::AST::NodePattern::Macros)
95

106
module Tapioca
117
module Dsl
@@ -34,14 +30,17 @@ module Compilers
3430
# `without_defaults_*` methods
3531
class RuboCop < Compiler
3632
ConstantType = type_member do
37-
{ fixed: T.all(T.class_of(::RuboCop::AST::NodePattern::Macros), Extensions::RuboCop) }
33+
{ fixed: T.all(Module, Extensions::RuboCop) }
3834
end
3935

4036
class << self
4137
extend T::Sig
42-
sig { override.returns(T::Enumerable[T.class_of(::RuboCop::AST::NodePattern::Macros)]) }
38+
sig { override.returns(T::Array[T.all(Module, Extensions::RuboCop)]) }
4339
def gather_constants
44-
descendants_of(::RuboCop::AST::NodePattern::Macros).select { |constant| name_of(constant) }
40+
T.cast(
41+
extenders_of(::RuboCop::AST::NodePattern::Macros).select { |constant| name_of(constant) },
42+
T::Array[T.all(Module, Extensions::RuboCop)],
43+
)
4544
end
4645
end
4746

lib/tapioca/dsl/extensions/rubocop.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# typed: strict
22
# frozen_string_literal: true
33

4-
return unless defined?(RuboCop)
4+
return unless defined?(RuboCop::AST::NodePattern::Macros)
55

66
module Tapioca
77
module Dsl

lib/tapioca/runtime/reflection.rb

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def method_of(constant, method)
146146
METHOD_METHOD.bind_call(constant, method)
147147
end
148148

149-
# Returns an array with all modules that are < than the supplied module.
149+
# Returns an array with all classes that are < than the supplied class.
150150
#
151151
# class C; end
152152
# descendants_of(C) # => []
@@ -159,26 +159,30 @@ def method_of(constant, method)
159159
#
160160
# class D < C; end
161161
# descendants_of(C) # => [B, A, D]
162-
#
163-
# module M; end
164-
# class E
165-
# include M
166-
# end
167-
# descendants_of(M) # => [E]
168162
sig do
169163
type_parameters(:U)
170-
.params(mod: T.all(Module, T.type_parameter(:U)))
164+
.params(klass: T.all(T::Class[T.anything], T.type_parameter(:U)))
171165
.returns(T::Array[T.type_parameter(:U)])
172166
end
173-
def descendants_of(mod)
174-
result = ObjectSpace
175-
.each_object(Module)
176-
.select { |m| T.cast(m, Module) < mod }
177-
.reject { |m| T.cast(m, Module).singleton_class? || T.unsafe(m) == mod }
167+
def descendants_of(klass)
168+
result = ObjectSpace.each_object(klass.singleton_class).reject do |k|
169+
T.cast(k, Module).singleton_class? || T.unsafe(k) == klass
170+
end
178171

179172
T.unsafe(result)
180173
end
181174

175+
# Returns an array with all modules which extend the supplied module
176+
# (i.e. all modules whose singleton class, or ancestor thereof, includes the supplied module).
177+
sig { params(mod: Module).returns(T::Array[Module]) }
178+
def extenders_of(mod)
179+
result = ObjectSpace.each_object(Module).select do |m|
180+
T.cast(m, Module).singleton_class.included_modules.include?(mod)
181+
end
182+
183+
T.cast(result, T::Array[Module])
184+
end
185+
182186
# Examines the call stack to identify the closest location where a "require" is performed
183187
# by searching for the label "<top (required)>". If none is found, it returns the location
184188
# labeled "<main>", which is the original call site.

spec/tapioca/dsl/compilers/rubocop_spec.rb

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@
22
# frozen_string_literal: true
33

44
require "spec_helper"
5-
require "rubocop"
6-
require "rubocop-sorbet"
5+
# require "rubocop"
6+
# require "rubocop-sorbet"
77

88
module Tapioca
99
module Dsl
1010
module Compilers
1111
class RuboCopSpec < ::DslSpec
12-
# Collect constants from gems, before defining any in tests.
13-
EXISTING_CONSTANTS = T.let(
14-
Runtime::Reflection
15-
.descendants_of(::RuboCop::Cop::Base)
16-
.filter_map { |constant| Runtime::Reflection.name_of(constant) },
17-
T::Array[String],
18-
)
12+
# # Collect constants from gems, before defining any in tests.
13+
# EXISTING_CONSTANTS = T.let(
14+
# Runtime::Reflection
15+
# .extenders_of(::RuboCop::AST::NodePattern::Macros)
16+
# .filter_map { |constant| Runtime::Reflection.name_of(constant) },
17+
# T::Array[String],
18+
# )
1919

2020
class << self
2121
extend T::Sig
@@ -30,20 +30,24 @@ def target_class_file
3030
describe "Tapioca::Dsl::Compilers::RuboCop" do
3131
sig { void }
3232
def before_setup
33+
require "rubocop"
34+
require "rubocop-sorbet"
3335
require "tapioca/dsl/extensions/rubocop"
3436
super
3537
end
3638

3739
describe "initialize" do
3840
it "gathered constants exclude irrelevant classes" do
39-
add_ruby_file("content.rb", <<~RUBY)
40-
class Unrelated
41-
end
42-
RUBY
43-
assert_empty(relevant_gathered_constants)
41+
gathered_constants = gather_constants do
42+
add_ruby_file("content.rb", <<~RUBY)
43+
class Unrelated
44+
end
45+
RUBY
46+
end
47+
assert_empty(gathered_constants)
4448
end
4549

46-
it "gathers constants inheriting RuboCop::Cop::Base in gems" do
50+
it "gathers constants extending RuboCop::AST::NodePattern::Macros in gems" do
4751
# Sample of miscellaneous constants that should be found from Rubocop and plugins
4852
missing_constants = [
4953
"RuboCop::Cop::Bundler::GemVersion",
@@ -61,27 +65,33 @@ class Unrelated
6165
assert_empty(missing_constants, "expected constants to be gathered")
6266
end
6367

64-
it "gathers constants inheriting from RuboCop::Cop::Base in the host app" do
65-
add_ruby_file("content.rb", <<~RUBY)
66-
class MyCop < ::RuboCop::Cop::Base
67-
end
68+
it "gathers constants extending RuboCop::AST::NodePattern::Macros in the host app" do
69+
gathered_constants = gather_constants do
70+
add_ruby_file("content.rb", <<~RUBY)
71+
class MyCop < ::RuboCop::Cop::Base
72+
end
6873
69-
class MyLegacyCop < ::RuboCop::Cop::Cop
70-
end
74+
class MyLegacyCop < ::RuboCop::Cop::Cop
75+
end
7176
72-
module ::RuboCop
73-
module Cop
74-
module MyApp
75-
class MyNamespacedCop < Base
77+
module MyMacroModule
78+
extend ::RuboCop::AST::NodePattern::Macros
79+
end
80+
81+
module ::RuboCop
82+
module Cop
83+
module MyApp
84+
class MyNamespacedCop < Base
85+
end
7686
end
7787
end
7888
end
79-
end
80-
RUBY
89+
RUBY
90+
end
8191

8292
assert_equal(
83-
["MyCop", "MyLegacyCop", "RuboCop::Cop::MyApp::MyNamespacedCop"],
84-
relevant_gathered_constants,
93+
["MyCop", "MyLegacyCop", "MyMacroModule", "RuboCop::Cop::MyApp::MyNamespacedCop"],
94+
gathered_constants,
8595
)
8696
end
8797
end
@@ -155,9 +165,17 @@ def without_defaults_some_search_with_params_and_defaults(param0, param1, two:);
155165

156166
private
157167

158-
sig { returns(T::Array[String]) }
159-
def relevant_gathered_constants
160-
gathered_constants - EXISTING_CONSTANTS
168+
# Gathers constants introduced in the given block excluding constants that already existed prior to the block.
169+
sig { params(block: T.proc.void).returns(T::Array[String]) }
170+
def gather_constants(&block)
171+
existing_constants = T.let(
172+
Runtime::Reflection
173+
.extenders_of(::RuboCop::AST::NodePattern::Macros)
174+
.filter_map { |constant| Runtime::Reflection.name_of(constant) },
175+
T::Array[String],
176+
)
177+
yield
178+
gathered_constants - existing_constants
161179
end
162180
end
163181
end

0 commit comments

Comments
 (0)