Skip to content

Commit c29e582

Browse files
authored
Merge pull request #968 from jeffcarbs/dsl-by-path
Support generating DSL RBI by path
2 parents d5a83e3 + fe1ce98 commit c29e582

File tree

5 files changed

+167
-23
lines changed

5 files changed

+167
-23
lines changed

lib/tapioca/cli.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,15 @@ def todo
130130
type: :string,
131131
desc: "The path to the Rails application",
132132
default: "."
133-
def dsl(*constants)
133+
def dsl(*constant_or_paths)
134134
set_environment(options)
135135

136+
# Assume anything starting with a capital letter or colon is a class, otherwise a path
137+
constants, paths = constant_or_paths.partition { |c| c =~ /\A[A-Z:]/ }
138+
136139
command = Commands::Dsl.new(
137140
requested_constants: constants,
141+
requested_paths: paths.map { |p| Pathname.new(p) },
138142
outpath: Pathname.new(options[:outdir]),
139143
only: options[:only],
140144
exclude: options[:exclude],

lib/tapioca/commands/dsl.rb

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class Dsl < CommandWithoutTracker
1010
sig do
1111
params(
1212
requested_constants: T::Array[String],
13+
requested_paths: T::Array[Pathname],
1314
outpath: Pathname,
1415
only: T::Array[String],
1516
exclude: T::Array[String],
@@ -27,6 +28,7 @@ class Dsl < CommandWithoutTracker
2728
end
2829
def initialize(
2930
requested_constants:,
31+
requested_paths:,
3032
outpath:,
3133
only:,
3234
exclude:,
@@ -42,6 +44,7 @@ def initialize(
4244
app_root: "."
4345
)
4446
@requested_constants = requested_constants
47+
@requested_paths = requested_paths
4548
@outpath = outpath
4649
@only = only
4750
@exclude = exclude
@@ -63,7 +66,7 @@ def initialize(
6366
def list_compilers
6467
Loaders::Dsl.load_application(
6568
tapioca_path: @tapioca_path,
66-
eager_load: @requested_constants.empty?,
69+
eager_load: @requested_constants.empty? && @requested_paths.empty?,
6770
app_root: @app_root,
6871
)
6972

@@ -101,6 +104,15 @@ def execute
101104
end
102105
say("")
103106

107+
unless @requested_paths.empty?
108+
constants_from_paths = Static::SymbolLoader.symbols_from_paths(@requested_paths).to_a
109+
if constants_from_paths.empty?
110+
say_error("\nWarning: No constants found in: #{@requested_paths.map(&:to_s).join(", ")}", :yellow)
111+
end
112+
113+
@requested_constants += constants_from_paths
114+
end
115+
104116
outpath = @should_verify ? Pathname.new(Dir.mktmpdir) : @outpath
105117
rbi_files_to_purge = existing_rbi_filenames(@requested_constants)
106118

@@ -153,6 +165,7 @@ def execute
153165
def create_pipeline
154166
Tapioca::Dsl::Pipeline.new(
155167
requested_constants: constantize(@requested_constants),
168+
requested_paths: @requested_paths,
156169
requested_compilers: constantize_compilers(@only),
157170
excluded_compilers: constantize_compilers(@exclude),
158171
error_handler: ->(error) {
@@ -167,8 +180,9 @@ def existing_rbi_filenames(requested_constants, path: @outpath)
167180
filenames = if requested_constants.empty?
168181
Pathname.glob(path / "**/*.rbi")
169182
else
170-
requested_constants.map do |constant_name|
171-
dsl_rbi_filename(constant_name)
183+
requested_constants.filter_map do |constant_name|
184+
filename = dsl_rbi_filename(constant_name)
185+
filename if File.exist?(filename)
172186
end
173187
end
174188

lib/tapioca/dsl/pipeline.rb

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ class Pipeline
1212
sig { returns(T::Array[Module]) }
1313
attr_reader :requested_constants
1414

15+
sig { returns(T::Array[Pathname]) }
16+
attr_reader :requested_paths
17+
1518
sig { returns(T.proc.params(error: String).void) }
1619
attr_reader :error_handler
1720

@@ -21,6 +24,7 @@ class Pipeline
2124
sig do
2225
params(
2326
requested_constants: T::Array[Module],
27+
requested_paths: T::Array[Pathname],
2428
requested_compilers: T::Array[T.class_of(Compiler)],
2529
excluded_compilers: T::Array[T.class_of(Compiler)],
2630
error_handler: T.proc.params(error: String).void,
@@ -29,6 +33,7 @@ class Pipeline
2933
end
3034
def initialize(
3135
requested_constants:,
36+
requested_paths: [],
3237
requested_compilers: [],
3338
excluded_compilers: [],
3439
error_handler: $stderr.method(:puts).to_proc,
@@ -39,6 +44,7 @@ def initialize(
3944
T::Enumerable[T.class_of(Compiler)],
4045
)
4146
@requested_constants = requested_constants
47+
@requested_paths = requested_paths
4248
@error_handler = error_handler
4349
@number_of_workers = number_of_workers
4450
@errors = T.let([], T::Array[String])
@@ -50,11 +56,12 @@ def initialize(
5056
).returns(T::Array[T.type_parameter(:T)])
5157
end
5258
def run(&blk)
53-
constants_to_process = gather_constants(requested_constants)
59+
constants_to_process = gather_constants(requested_constants, requested_paths)
5460
.select { |c| Module === c } # Filter value constants out
5561
.sort_by! { |c| T.must(Runtime::Reflection.name_of(c)) }
5662

57-
if constants_to_process.empty?
63+
# It's OK if there are no constants to process if we received a valid file/path.
64+
if constants_to_process.empty? && requested_paths.select { |p| File.exist?(p) }.empty?
5865
report_error(<<~ERROR)
5966
No classes/modules can be matched for RBI generation.
6067
Please check that the requested classes/modules include processable DSL methods.
@@ -115,12 +122,12 @@ def gather_active_compilers(requested_compilers, excluded_compilers)
115122
active_compilers
116123
end
117124

118-
sig { params(requested_constants: T::Array[Module]).returns(T::Set[Module]) }
119-
def gather_constants(requested_constants)
125+
sig { params(requested_constants: T::Array[Module], requested_paths: T::Array[Pathname]).returns(T::Set[Module]) }
126+
def gather_constants(requested_constants, requested_paths)
120127
constants = active_compilers.map(&:processable_constants).reduce(Set.new, :union)
121128
constants = filter_anonymous_and_reloaded_constants(constants)
122129

123-
constants &= requested_constants unless requested_constants.empty?
130+
constants &= requested_constants unless requested_constants.empty? && requested_paths.empty?
124131
constants
125132
end
126133

lib/tapioca/static/symbol_loader.rb

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@ def gem_symbols(gem)
4141
symbols_from_paths(gem.files)
4242
end
4343

44+
sig { params(paths: T::Array[Pathname]).returns(T::Set[String]) }
45+
def symbols_from_paths(paths)
46+
output = Tempfile.create("sorbet") do |file|
47+
file.write(Array(paths).join("\n"))
48+
file.flush
49+
50+
symbol_table_json_from("@#{file.path.shellescape}")
51+
end
52+
53+
return Set.new if output.empty?
54+
55+
SymbolTableParser.parse_json(output)
56+
end
57+
4458
private
4559

4660
sig { returns(T::Array[T.class_of(Rails::Engine)]) }
@@ -59,20 +73,6 @@ def engines
5973
def symbol_table_json_from(input, table_type: "symbol-table-json")
6074
sorbet("--no-config", "--quiet", "--print=#{table_type}", input).out
6175
end
62-
63-
sig { params(paths: T::Array[Pathname]).returns(T::Set[String]) }
64-
def symbols_from_paths(paths)
65-
output = Tempfile.create("sorbet") do |file|
66-
file.write(Array(paths).join("\n"))
67-
file.flush
68-
69-
symbol_table_json_from("@#{file.path.shellescape}")
70-
end
71-
72-
return Set.new if output.empty?
73-
74-
SymbolTableParser.parse_json(output)
75-
end
7676
end
7777
end
7878
end

spec/tapioca/cli/dsl_spec.rb

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,125 @@ class User; end
726726
assert_success_status(result)
727727
end
728728

729+
it "can be called by path" do
730+
@project.write("lib/models/post.rb", <<~RB)
731+
require "smart_properties"
732+
733+
class Post
734+
include SmartProperties
735+
property :title, accepts: String
736+
end
737+
RB
738+
739+
@project.write("lib/models/nested/user.rb", <<~RB)
740+
require "smart_properties"
741+
742+
module Nested
743+
class User
744+
include SmartProperties
745+
property :name, accepts: String
746+
end
747+
end
748+
RB
749+
750+
@project.write("lib/job.rb", <<~RB)
751+
require "smart_properties"
752+
753+
class User
754+
include SmartProperties
755+
property :name, accepts: String
756+
end
757+
RB
758+
759+
result = @project.tapioca("dsl lib/models")
760+
761+
assert_equal(<<~OUT, result.out)
762+
Loading Rails application... Done
763+
Loading DSL compiler classes... Done
764+
Compiling DSL RBI files...
765+
766+
create sorbet/rbi/dsl/nested/user.rbi
767+
create sorbet/rbi/dsl/post.rbi
768+
769+
Done
770+
771+
Checking generated RBI files... Done
772+
No errors found
773+
774+
All operations performed in working directory.
775+
Please review changes and commit them.
776+
OUT
777+
778+
assert_empty_stderr(result)
779+
780+
assert_project_file_exist("sorbet/rbi/dsl/post.rbi")
781+
assert_project_file_exist("sorbet/rbi/dsl/nested/user.rbi")
782+
refute_project_file_exist("sorbet/rbi/dsl/job.rbi")
783+
784+
assert_success_status(result)
785+
end
786+
787+
it "does not generate anything and errors for non-existent paths" do
788+
@project.write("lib/models/post.rb", <<~RB)
789+
require "smart_properties"
790+
791+
class Post
792+
include SmartProperties
793+
property :title, accepts: String
794+
end
795+
RB
796+
797+
result = @project.tapioca("dsl path/to/nowhere.rb")
798+
799+
assert_equal(<<~OUT, result.out)
800+
Loading Rails application... Done
801+
Loading DSL compiler classes... Done
802+
Compiling DSL RBI files...
803+
804+
OUT
805+
806+
assert_equal(<<~ERR, result.err)
807+
808+
Warning: No constants found in: path/to/nowhere.rb
809+
No classes/modules can be matched for RBI generation.
810+
Please check that the requested classes/modules include processable DSL methods.
811+
ERR
812+
813+
refute_project_file_exist("sorbet/rbi/dsl/post.rbi")
814+
815+
refute_success_status(result)
816+
end
817+
818+
it "does not generate anything but succeeds for real paths with no processable DSL" do
819+
@project.write("lib/models/post.rb", <<~RB)
820+
class Foo
821+
end
822+
RB
823+
824+
result = @project.tapioca("dsl lib/models")
825+
826+
assert_equal(<<~OUT, result.out)
827+
Loading Rails application... Done
828+
Loading DSL compiler classes... Done
829+
Compiling DSL RBI files...
830+
831+
832+
Done
833+
834+
Checking generated RBI files... Done
835+
No errors found
836+
837+
All operations performed in working directory.
838+
Please review changes and commit them.
839+
OUT
840+
841+
assert_empty_stderr(result)
842+
843+
refute_project_file_exist("sorbet/rbi/dsl/post.rbi")
844+
845+
assert_success_status(result)
846+
end
847+
729848
it "must run custom compilers" do
730849
@project.write("lib/post.rb", <<~RB)
731850
require "smart_properties"

0 commit comments

Comments
 (0)