tapioca 0.6.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +8 -2
  3. data/README.md +27 -15
  4. data/Rakefile +10 -14
  5. data/lib/tapioca/cli.rb +65 -80
  6. data/lib/tapioca/{generators/base.rb → commands/command.rb} +16 -9
  7. data/lib/tapioca/{generators → commands}/dsl.rb +59 -45
  8. data/lib/tapioca/{generators → commands}/gem.rb +93 -30
  9. data/lib/tapioca/{generators → commands}/init.rb +9 -13
  10. data/lib/tapioca/{generators → commands}/require.rb +8 -10
  11. data/lib/tapioca/commands/todo.rb +84 -0
  12. data/lib/tapioca/commands.rb +13 -0
  13. data/lib/tapioca/dsl/compiler.rb +185 -0
  14. data/lib/tapioca/{compilers/dsl → dsl/compilers}/aasm.rb +12 -9
  15. data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_controller_helpers.rb +13 -20
  16. data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_mailer.rb +10 -8
  17. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_job.rb +11 -9
  18. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_attributes.rb +13 -11
  19. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_secure_password.rb +10 -12
  20. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_associations.rb +28 -34
  21. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_columns.rb +18 -16
  22. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_enum.rb +14 -12
  23. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_fixtures.rb +10 -8
  24. data/lib/tapioca/dsl/compilers/active_record_relations.rb +712 -0
  25. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_scope.rb +21 -20
  26. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_typed_store.rb +11 -16
  27. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_resource.rb +10 -8
  28. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_storage.rb +11 -11
  29. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_concern.rb +19 -14
  30. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_current_attributes.rb +16 -21
  31. data/lib/tapioca/{compilers/dsl → dsl/compilers}/config.rb +10 -8
  32. data/lib/tapioca/{compilers/dsl → dsl/compilers}/frozen_record.rb +13 -11
  33. data/lib/tapioca/{compilers/dsl → dsl/compilers}/identity_cache.rb +23 -22
  34. data/lib/tapioca/{compilers/dsl → dsl/compilers}/mixed_in_class_attributes.rb +12 -10
  35. data/lib/tapioca/{compilers/dsl → dsl/compilers}/protobuf.rb +10 -8
  36. data/lib/tapioca/{compilers/dsl → dsl/compilers}/rails_generators.rb +12 -13
  37. data/lib/tapioca/{compilers/dsl → dsl/compilers}/sidekiq_worker.rb +14 -13
  38. data/lib/tapioca/{compilers/dsl → dsl/compilers}/smart_properties.rb +11 -9
  39. data/lib/tapioca/{compilers/dsl → dsl/compilers}/state_machines.rb +12 -10
  40. data/lib/tapioca/{compilers/dsl → dsl/compilers}/url_helpers.rb +16 -14
  41. data/lib/tapioca/dsl/compilers.rb +31 -0
  42. data/lib/tapioca/{compilers/dsl → dsl}/extensions/frozen_record.rb +2 -2
  43. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +114 -0
  44. data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +29 -0
  45. data/lib/tapioca/{compilers/dsl → dsl/helpers}/param_helper.rb +2 -2
  46. data/lib/tapioca/{compilers/dsl_compiler.rb → dsl/pipeline.rb} +41 -33
  47. data/lib/tapioca/gem/events.rb +120 -0
  48. data/lib/tapioca/gem/listeners/base.rb +48 -0
  49. data/lib/tapioca/gem/listeners/dynamic_mixins.rb +32 -0
  50. data/lib/tapioca/gem/listeners/methods.rb +183 -0
  51. data/lib/tapioca/gem/listeners/mixins.rb +101 -0
  52. data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +21 -0
  53. data/lib/tapioca/gem/listeners/sorbet_enums.rb +26 -0
  54. data/lib/tapioca/gem/listeners/sorbet_helpers.rb +29 -0
  55. data/lib/tapioca/gem/listeners/sorbet_props.rb +33 -0
  56. data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +23 -0
  57. data/lib/tapioca/gem/listeners/sorbet_signatures.rb +79 -0
  58. data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +51 -0
  59. data/lib/tapioca/gem/listeners/subconstants.rb +37 -0
  60. data/lib/tapioca/gem/listeners/yard_doc.rb +96 -0
  61. data/lib/tapioca/gem/listeners.rb +16 -0
  62. data/lib/tapioca/gem/pipeline.rb +365 -0
  63. data/lib/tapioca/helpers/cli_helper.rb +7 -0
  64. data/lib/tapioca/helpers/config_helper.rb +5 -8
  65. data/lib/tapioca/helpers/rbi_helper.rb +17 -0
  66. data/lib/tapioca/helpers/shims_helper.rb +87 -0
  67. data/lib/tapioca/helpers/sorbet_helper.rb +57 -0
  68. data/lib/tapioca/helpers/test/dsl_compiler.rb +118 -0
  69. data/lib/tapioca/helpers/test/isolation.rb +1 -1
  70. data/lib/tapioca/helpers/test/template.rb +13 -2
  71. data/lib/tapioca/internal.rb +17 -10
  72. data/lib/tapioca/rbi_ext/model.rb +2 -48
  73. data/lib/tapioca/rbi_formatter.rb +37 -0
  74. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +227 -0
  75. data/lib/tapioca/runtime/generic_type_registry.rb +166 -0
  76. data/lib/tapioca/runtime/loader.rb +123 -0
  77. data/lib/tapioca/runtime/reflection.rb +153 -0
  78. data/lib/tapioca/runtime/trackers/autoload.rb +72 -0
  79. data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -0
  80. data/lib/tapioca/runtime/trackers/mixin.rb +80 -0
  81. data/lib/tapioca/runtime/trackers/required_ancestor.rb +50 -0
  82. data/lib/tapioca/{trackers.rb → runtime/trackers.rb} +4 -3
  83. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +33 -15
  84. data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
  85. data/lib/tapioca/{compilers → static}/requires_compiler.rb +2 -2
  86. data/lib/tapioca/static/symbol_loader.rb +83 -0
  87. data/lib/tapioca/static/symbol_table_parser.rb +63 -0
  88. data/lib/tapioca/version.rb +1 -1
  89. data/lib/tapioca.rb +2 -7
  90. metadata +80 -60
  91. data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -720
  92. data/lib/tapioca/compilers/dsl/base.rb +0 -195
  93. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
  94. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -223
  95. data/lib/tapioca/compilers/sorbet.rb +0 -59
  96. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
  97. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
  98. data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
  99. data/lib/tapioca/compilers/todos_compiler.rb +0 -32
  100. data/lib/tapioca/generators/todo.rb +0 -76
  101. data/lib/tapioca/generators.rb +0 -9
  102. data/lib/tapioca/generic_type_registry.rb +0 -164
  103. data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -108
  104. data/lib/tapioca/loader.rb +0 -119
  105. data/lib/tapioca/reflection.rb +0 -151
  106. data/lib/tapioca/trackers/autoload.rb +0 -70
  107. data/lib/tapioca/trackers/constant_definition.rb +0 -42
  108. data/lib/tapioca/trackers/mixin.rb +0 -78
@@ -2,8 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- module Generators
6
- class Dsl < Base
5
+ module Commands
6
+ class Dsl < Command
7
7
  sig do
8
8
  params(
9
9
  requested_constants: T::Array[String],
@@ -13,12 +13,11 @@ module Tapioca
13
13
  file_header: T::Boolean,
14
14
  compiler_path: String,
15
15
  tapioca_path: String,
16
- default_command: String,
17
- file_writer: Thor::Actions,
18
16
  should_verify: T::Boolean,
19
17
  quiet: T::Boolean,
20
18
  verbose: T::Boolean,
21
19
  number_of_workers: T.nilable(Integer),
20
+ rbi_formatter: RBIFormatter
22
21
  ).void
23
22
  end
24
23
  def initialize(
@@ -29,12 +28,11 @@ module Tapioca
29
28
  file_header:,
30
29
  compiler_path:,
31
30
  tapioca_path:,
32
- default_command:,
33
- file_writer: FileWriter.new,
34
31
  should_verify: false,
35
32
  quiet: false,
36
33
  verbose: false,
37
- number_of_workers: nil
34
+ number_of_workers: nil,
35
+ rbi_formatter: DEFAULT_RBI_FORMATTER
38
36
  )
39
37
  @requested_constants = requested_constants
40
38
  @outpath = outpath
@@ -47,18 +45,19 @@ module Tapioca
47
45
  @quiet = quiet
48
46
  @verbose = verbose
49
47
  @number_of_workers = number_of_workers
48
+ @rbi_formatter = rbi_formatter
50
49
 
51
- super(default_command: default_command, file_writer: file_writer)
50
+ super()
52
51
 
53
- @loader = T.let(nil, T.nilable(Loader))
52
+ @loader = T.let(nil, T.nilable(Runtime::Loader))
54
53
  end
55
54
 
56
55
  sig { override.void }
57
- def generate
56
+ def execute
58
57
  load_dsl_extensions
59
58
  load_application(eager_load: @requested_constants.empty?)
60
59
  abort_if_pending_migrations!
61
- load_dsl_generators
60
+ load_dsl_compilers
62
61
 
63
62
  if @should_verify
64
63
  say("Checking for out-of-date RBIs...")
@@ -70,18 +69,18 @@ module Tapioca
70
69
  outpath = @should_verify ? Pathname.new(Dir.mktmpdir) : @outpath
71
70
  rbi_files_to_purge = existing_rbi_filenames(@requested_constants)
72
71
 
73
- compiler = Compilers::DslCompiler.new(
72
+ pipeline = Tapioca::Dsl::Pipeline.new(
74
73
  requested_constants: constantize(@requested_constants),
75
- requested_generators: constantize_generators(@only),
76
- excluded_generators: constantize_generators(@exclude),
74
+ requested_compilers: constantize_compilers(@only),
75
+ excluded_compilers: constantize_compilers(@exclude),
77
76
  error_handler: ->(error) {
78
77
  say_error(error, :bold, :red)
79
78
  },
80
79
  number_of_workers: @number_of_workers
81
80
  )
82
81
 
83
- processed_files = compiler.run do |constant, contents|
84
- constant_name = T.must(Reflection.name_of(constant))
82
+ processed_files = pipeline.run do |constant, contents|
83
+ constant_name = T.must(Tapioca::Runtime::Reflection.name_of(constant))
85
84
 
86
85
  if @verbose && !@quiet
87
86
  say_status(:processing, constant_name, :yellow)
@@ -91,7 +90,7 @@ module Tapioca
91
90
  constant_name,
92
91
  contents,
93
92
  outpath: outpath,
94
- quiet: @should_verify || @quiet && !@verbose
93
+ quiet: @should_verify || (@quiet && !@verbose)
95
94
  )
96
95
  end
97
96
 
@@ -137,14 +136,15 @@ module Tapioca
137
136
  end
138
137
 
139
138
  sig { void }
140
- def load_dsl_generators
141
- say("Loading DSL generator classes... ")
139
+ def load_dsl_compilers
140
+ say("Loading DSL compiler classes... ")
142
141
 
143
142
  Dir.glob([
144
143
  "#{@compiler_path}/*.rb",
145
- "#{@tapioca_path}/generators/**/*.rb",
146
- ]).each do |generator|
147
- require File.expand_path(generator)
144
+ "#{@tapioca_path}/generators/**/*.rb", # TODO: Here for backcompat, remove later
145
+ "#{@tapioca_path}/compilers/**/*.rb",
146
+ ]).each do |compiler|
147
+ require File.expand_path(compiler)
148
148
  end
149
149
 
150
150
  say("Done", :green)
@@ -165,11 +165,11 @@ module Tapioca
165
165
 
166
166
  sig { params(constant_names: T::Array[String]).returns(T::Array[Module]) }
167
167
  def constantize(constant_names)
168
- constant_map = constant_names.map do |name|
168
+ constant_map = constant_names.to_h do |name|
169
169
  [name, Object.const_get(name)]
170
170
  rescue NameError
171
171
  [name, nil]
172
- end.to_h
172
+ end
173
173
 
174
174
  unprocessable_constants = constant_map.select { |_, v| v.nil? }
175
175
  unless unprocessable_constants.empty?
@@ -185,22 +185,35 @@ module Tapioca
185
185
  constant_map.values
186
186
  end
187
187
 
188
- sig { params(generator_names: T::Array[String]).returns(T::Array[T.class_of(Compilers::Dsl::Base)]) }
189
- def constantize_generators(generator_names)
190
- generator_map = generator_names.to_h do |name|
191
- [name, Compilers::Dsl::Base.resolve(name)]
188
+ sig { params(compiler_names: T::Array[String]).returns(T::Array[T.class_of(Tapioca::Dsl::Compiler)]) }
189
+ def constantize_compilers(compiler_names)
190
+ compiler_map = compiler_names.to_h do |name|
191
+ [name, resolve(name)]
192
192
  end
193
193
 
194
- unprocessable_generators = generator_map.select { |_, v| v.nil? }
195
- unless unprocessable_generators.empty?
196
- unprocessable_generators.each do |name, _|
197
- say("Error: Cannot find generator '#{name}'", :red)
194
+ unprocessable_compilers = compiler_map.select { |_, v| v.nil? }
195
+ unless unprocessable_compilers.empty?
196
+ unprocessable_compilers.each do |name, _|
197
+ say("Error: Cannot find compiler '#{name}'", :red)
198
198
  end
199
199
 
200
200
  exit(1)
201
201
  end
202
202
 
203
- T.cast(generator_map.values, T::Array[T.class_of(Compilers::Dsl::Base)])
203
+ T.cast(compiler_map.values, T::Array[T.class_of(Tapioca::Dsl::Compiler)])
204
+ end
205
+
206
+ sig { params(name: String).returns(T.nilable(T.class_of(Tapioca::Dsl::Compiler))) }
207
+ def resolve(name)
208
+ # Try to find built-in tapioca compiler first, then globally defined compiler.
209
+ potentials = Tapioca::Dsl::Compilers::NAMESPACES.map do |namespace|
210
+ Object.const_get(namespace + name)
211
+ rescue NameError
212
+ # Skip if we can't find compiler by the potential name
213
+ nil
214
+ end
215
+
216
+ potentials.compact.first
204
217
  end
205
218
 
206
219
  sig do
@@ -216,13 +229,14 @@ module Tapioca
216
229
 
217
230
  filename = outpath / rbi_filename_for(constant_name)
218
231
 
219
- rbi.set_file_header(
232
+ @rbi_formatter.write_header!(
233
+ rbi,
220
234
  generate_command_for(constant_name),
221
- reason: "dynamic methods in `#{constant_name}`",
222
- display_heading: @file_header
223
- )
235
+ reason: "dynamic methods in `#{constant_name}`"
236
+ ) if @file_header
224
237
 
225
- create_file(filename, rbi.transformed_string, verbose: !quiet)
238
+ rbi_string = @rbi_formatter.print_file(rbi)
239
+ create_file(filename, rbi_string, verbose: !quiet)
226
240
 
227
241
  filename
228
242
  end
@@ -231,7 +245,7 @@ module Tapioca
231
245
  def perform_dsl_verification(dir)
232
246
  diff = verify_dsl_rbi(tmp_dir: dir)
233
247
 
234
- report_diff_and_exit_if_out_of_date(diff, "dsl")
248
+ report_diff_and_exit_if_out_of_date(diff, :dsl)
235
249
  ensure
236
250
  FileUtils.remove_entry(dir)
237
251
  end
@@ -294,13 +308,13 @@ module Tapioca
294
308
  " File(s) #{cause}:\n - #{filenames}"
295
309
  end
296
310
 
297
- sig { params(diff: T::Hash[String, Symbol], command: String).void }
311
+ sig { params(diff: T::Hash[String, Symbol], command: Symbol).void }
298
312
  def report_diff_and_exit_if_out_of_date(diff, command)
299
313
  if diff.empty?
300
314
  say("Nothing to do, all RBIs are up-to-date.")
301
315
  else
302
316
  say("RBI files are out-of-date. In your development environment, please run:", :green)
303
- say(" `#{@default_command} #{command}`", [:green, :bold])
317
+ say(" `#{default_command(command)}`", [:green, :bold])
304
318
  say("Once it is complete, be sure to commit and push any changes", :green)
305
319
 
306
320
  say("")
@@ -321,9 +335,9 @@ module Tapioca
321
335
  end.sort
322
336
  end
323
337
 
324
- sig { returns(Loader) }
338
+ sig { returns(Runtime::Loader) }
325
339
  def loader
326
- @loader ||= Loader.new
340
+ @loader ||= Runtime::Loader.new
327
341
  end
328
342
 
329
343
  sig { params(class_name: String).returns(String) }
@@ -345,12 +359,12 @@ module Tapioca
345
359
 
346
360
  sig { params(constant: String).returns(String) }
347
361
  def generate_command_for(constant)
348
- "#{@default_command} dsl #{constant}"
362
+ default_command(:dsl, constant)
349
363
  end
350
364
 
351
365
  sig { void }
352
366
  def load_dsl_extensions
353
- Dir["#{__dir__}/../compilers/dsl/extensions/*.rb"].sort.each { |f| require(f) }
367
+ Dir["#{__dir__}/../dsl/extensions/*.rb"].sort.each { |f| require(f) }
354
368
  end
355
369
  end
356
370
  end
@@ -2,8 +2,10 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- module Generators
6
- class Gem < Base
5
+ module Commands
6
+ class Gem < Command
7
+ include SorbetHelper
8
+
7
9
  sig do
8
10
  params(
9
11
  gem_names: T::Array[String],
@@ -11,13 +13,14 @@ module Tapioca
11
13
  prerequire: T.nilable(String),
12
14
  postrequire: String,
13
15
  typed_overrides: T::Hash[String, String],
14
- default_command: String,
15
16
  outpath: Pathname,
16
17
  file_header: T::Boolean,
17
18
  doc: T::Boolean,
18
19
  include_exported_rbis: T::Boolean,
19
- file_writer: Thor::Actions,
20
- number_of_workers: T.nilable(Integer)
20
+ number_of_workers: T.nilable(Integer),
21
+ auto_strictness: T::Boolean,
22
+ dsl_dir: String,
23
+ rbi_formatter: RBIFormatter
21
24
  ).void
22
25
  end
23
26
  def initialize(
@@ -26,13 +29,14 @@ module Tapioca
26
29
  prerequire:,
27
30
  postrequire:,
28
31
  typed_overrides:,
29
- default_command:,
30
32
  outpath:,
31
33
  file_header:,
32
34
  doc:,
33
35
  include_exported_rbis:,
34
- file_writer: FileWriter.new,
35
- number_of_workers: nil
36
+ number_of_workers: nil,
37
+ auto_strictness: true,
38
+ dsl_dir: DEFAULT_DSL_DIR,
39
+ rbi_formatter: DEFAULT_RBI_FORMATTER
36
40
  )
37
41
  @gem_names = gem_names
38
42
  @exclude = exclude
@@ -42,10 +46,13 @@ module Tapioca
42
46
  @outpath = outpath
43
47
  @file_header = file_header
44
48
  @number_of_workers = number_of_workers
49
+ @auto_strictness = auto_strictness
50
+ @dsl_dir = dsl_dir
51
+ @rbi_formatter = rbi_formatter
45
52
 
46
- super(default_command: default_command, file_writer: file_writer)
53
+ super()
47
54
 
48
- @loader = T.let(nil, T.nilable(Loader))
55
+ @loader = T.let(nil, T.nilable(Runtime::Loader))
49
56
  @bundle = T.let(nil, T.nilable(Gemfile))
50
57
  @existing_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
51
58
  @expected_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
@@ -54,7 +61,7 @@ module Tapioca
54
61
  end
55
62
 
56
63
  sig { override.void }
57
- def generate
64
+ def execute
58
65
  require_gem_file
59
66
 
60
67
  gem_queue = gems_to_generate(@gem_names).reject { |gem| @exclude.include?(gem.name) }
@@ -71,6 +78,8 @@ module Tapioca
71
78
  end
72
79
 
73
80
  if anything_done
81
+ update_strictnesses(gem_queue.map(&:name), gem_dir: @outpath.to_s, dsl_dir: @dsl_dir) if @auto_strictness
82
+
74
83
  say("All operations performed in working directory.", [:green, :bold])
75
84
  say("Please review changes and commit them.", [:green, :bold])
76
85
  else
@@ -93,6 +102,8 @@ module Tapioca
93
102
  ].any?
94
103
 
95
104
  if anything_done
105
+ update_strictnesses([], gem_dir: @outpath.to_s, dsl_dir: @dsl_dir) if @auto_strictness
106
+
96
107
  say("All operations performed in working directory.", [:green, :bold])
97
108
  say("Please review changes and commit them.", [:green, :bold])
98
109
  else
@@ -104,9 +115,9 @@ module Tapioca
104
115
 
105
116
  private
106
117
 
107
- sig { returns(Loader) }
118
+ sig { returns(Runtime::Loader) }
108
119
  def loader
109
- @loader ||= Loader.new
120
+ @loader ||= Runtime::Loader.new
110
121
  end
111
122
 
112
123
  sig { returns(Gemfile) }
@@ -124,7 +135,7 @@ module Tapioca
124
135
  exit(1)
125
136
  end
126
137
 
127
- Tapioca::Trackers::Autoload.eager_load_all!
138
+ Runtime::Trackers::Autoload.eager_load_all!
128
139
 
129
140
  say(" Done", :green)
130
141
  unless bundle.missing_specs.empty?
@@ -153,24 +164,24 @@ module Tapioca
153
164
  gem_name = set_color(gem.name, :yellow, :bold)
154
165
 
155
166
  rbi = RBI::File.new(strictness: @typed_overrides[gem.name] || "true")
156
- rbi.set_file_header(
157
- "#{@default_command} gem #{gem.name}",
158
- reason: "types exported from the `#{gem.name}` gem",
159
- display_heading: @file_header
160
- )
161
167
 
162
- Compilers::SymbolTableCompiler.new.compile(gem, rbi, 0, @doc)
168
+ @rbi_formatter.write_header!(rbi,
169
+ default_command(:gem, gem.name),
170
+ reason: "types exported from the `#{gem.name}` gem",) if @file_header
171
+
172
+ rbi.root = Tapioca::Gem::Pipeline.new(gem, include_doc: @doc).compile
163
173
 
164
174
  merge_with_exported_rbi(gem, rbi) if @include_exported_rbis
165
175
 
166
176
  if rbi.empty?
167
- rbi.set_empty_body_content
177
+ @rbi_formatter.write_empty_body_comment!(rbi)
168
178
  say("Compiled #{gem_name} (empty output)", :yellow)
169
179
  else
170
180
  say("Compiled #{gem_name}", :green)
171
181
  end
172
182
 
173
- create_file(@outpath / gem.rbi_file_name, rbi.transformed_string)
183
+ rbi_string = @rbi_formatter.print_file(rbi)
184
+ create_file(@outpath / gem.rbi_file_name, rbi_string)
174
185
 
175
186
  T.unsafe(Pathname).glob((@outpath / "#{gem.name}@*.rbi").to_s) do |file|
176
187
  remove_file(file) unless file.basename.to_s == gem.rbi_file_name
@@ -191,7 +202,7 @@ module Tapioca
191
202
  diff[filename] = gem_rbi_exists?(gem_name) ? :changed : :added
192
203
  end
193
204
 
194
- report_diff_and_exit_if_out_of_date(diff, "gem")
205
+ report_diff_and_exit_if_out_of_date(diff, :gem)
195
206
  end
196
207
 
197
208
  sig { void }
@@ -265,7 +276,7 @@ module Tapioca
265
276
  say_error("If you populated ", :yellow)
266
277
  say_error("#{file} ", :bold, :blue)
267
278
  say_error("with ", :yellow)
268
- say_error("`#{@default_command} require`", :bold, :blue)
279
+ say_error("`#{default_command(:require)}`", :bold, :blue)
269
280
  say_error("you should probably review it and remove the faulty line.", :yellow)
270
281
  end
271
282
 
@@ -296,13 +307,13 @@ module Tapioca
296
307
  existing_rbis.key?(gem_name)
297
308
  end
298
309
 
299
- sig { params(diff: T::Hash[String, Symbol], command: String).void }
310
+ sig { params(diff: T::Hash[String, Symbol], command: Symbol).void }
300
311
  def report_diff_and_exit_if_out_of_date(diff, command)
301
312
  if diff.empty?
302
313
  say("Nothing to do, all RBIs are up-to-date.")
303
314
  else
304
315
  say("RBI files are out-of-date. In your development environment, please run:", :green)
305
- say(" `#{@default_command} #{command}`", [:green, :bold])
316
+ say(" `#{default_command(command)}`", [:green, :bold])
306
317
  say("Once it is complete, be sure to commit and push any changes", :green)
307
318
 
308
319
  say("")
@@ -325,16 +336,14 @@ module Tapioca
325
336
  sig { returns(T::Hash[String, String]) }
326
337
  def existing_rbis
327
338
  @existing_rbis ||= Pathname.glob((@outpath / "*@*.rbi").to_s)
328
- .map { |f| T.cast(f.basename(".*").to_s.split("@", 2), [String, String]) }
329
- .to_h
339
+ .to_h { |f| T.cast(f.basename(".*").to_s.split("@", 2), [String, String]) }
330
340
  end
331
341
 
332
342
  sig { returns(T::Hash[String, String]) }
333
343
  def expected_rbis
334
344
  @expected_rbis ||= bundle.dependencies
335
345
  .reject { |gem| @exclude.include?(gem.name) }
336
- .map { |gem| [gem.name, gem.version.to_s] }
337
- .to_h
346
+ .to_h { |gem| [gem.name, gem.version.to_s] }
338
347
  end
339
348
 
340
349
  sig { params(gem_name: String, version: String).returns(Pathname) }
@@ -370,6 +379,60 @@ module Tapioca
370
379
  say_error("\n\n RBIs exported by `#{gem.name}` contain errors and can't be used:", :yellow)
371
380
  say_error("Cause: #{e.message} (#{e.location})")
372
381
  end
382
+
383
+ sig { params(gem_names: T::Array[String], gem_dir: String, dsl_dir: String).void }
384
+ def update_strictnesses(gem_names, gem_dir: DEFAULT_GEM_DIR, dsl_dir: DEFAULT_DSL_DIR)
385
+ return unless File.directory?(dsl_dir)
386
+
387
+ error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
388
+
389
+ say("Typechecking RBI files... ")
390
+ res = sorbet(
391
+ "--no-config",
392
+ "--error-url-base=#{error_url_base}",
393
+ "--isolate-error-code 4010",
394
+ dsl_dir,
395
+ gem_dir
396
+ )
397
+ say(" Done", :green)
398
+
399
+ errors = Spoom::Sorbet::Errors::Parser.parse_string(res.err)
400
+
401
+ if errors.empty?
402
+ say("No error found", [:green, :bold])
403
+ return
404
+ end
405
+
406
+ files = []
407
+
408
+ errors.each do |error|
409
+ # Collect the file with error
410
+ files << error.file
411
+ error.more.each do |line|
412
+ # Also collect the conflicting definition file paths
413
+ next unless line.include?("Previous definition")
414
+ files << line.split(":").first&.strip
415
+ end
416
+ end
417
+
418
+ files
419
+ .uniq
420
+ .sort
421
+ .select do |file|
422
+ name = gem_name_from_rbi_path(file)
423
+ file.start_with?(gem_dir) && (gem_names.empty? || gem_names.include?(name))
424
+ end.each do |file|
425
+ Spoom::Sorbet::Sigils.change_sigil_in_file(file, "false")
426
+ say("\n Changed strictness of #{file} to `typed: false` (conflicting with DSL files)", [:yellow, :bold])
427
+ end
428
+
429
+ say("\n")
430
+ end
431
+
432
+ sig { params(path: String).returns(String) }
433
+ def gem_name_from_rbi_path(path)
434
+ T.must(File.basename(path, ".rbi").split("@").first)
435
+ end
373
436
  end
374
437
  end
375
438
  end
@@ -2,36 +2,32 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- module Generators
6
- class Init < Base
5
+ module Commands
6
+ class Init < Command
7
7
  sig do
8
8
  params(
9
9
  sorbet_config: String,
10
10
  tapioca_config: String,
11
- default_postrequire: String,
12
- default_command: String,
13
- file_writer: Thor::Actions
11
+ default_postrequire: String
14
12
  ).void
15
13
  end
16
14
  def initialize(
17
15
  sorbet_config:,
18
16
  tapioca_config:,
19
- default_postrequire:,
20
- default_command:,
21
- file_writer: FileWriter.new
17
+ default_postrequire:
22
18
  )
23
19
  @sorbet_config = sorbet_config
24
20
  @tapioca_config = tapioca_config
25
21
  @default_postrequire = default_postrequire
26
22
 
27
- super(default_command: default_command, file_writer: file_writer)
23
+ super()
28
24
 
29
25
  @installer = T.let(nil, T.nilable(Bundler::Installer))
30
26
  @spec = T.let(nil, T.nilable(Bundler::StubSpecification))
31
27
  end
32
28
 
33
29
  sig { override.void }
34
- def generate
30
+ def execute
35
31
  create_sorbet_config
36
32
  create_tapioca_config
37
33
  create_post_require
@@ -73,19 +69,19 @@ module Tapioca
73
69
  # typed: true
74
70
  # frozen_string_literal: true
75
71
 
76
- # Add your extra requires here (`#{@default_command} require` can be used to boostrap this list)
72
+ # Add your extra requires here (`#{default_command(:require)}` can be used to boostrap this list)
77
73
  CONTENT
78
74
  end
79
75
 
80
76
  sig { void }
81
77
  def create_binstub
82
- force = File.exist?(@default_command)
78
+ force = File.exist?(Tapioca::BINARY_FILE)
83
79
 
84
80
  installer.generate_bundler_executable_stubs(spec, { force: force })
85
81
 
86
82
  say_status(
87
83
  force ? :force : :create,
88
- @default_command,
84
+ Tapioca::BINARY_FILE,
89
85
  force ? :yellow : :green
90
86
  )
91
87
  end
@@ -2,26 +2,24 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- module Generators
6
- class Require < Base
5
+ module Commands
6
+ class Require < Command
7
7
  sig do
8
8
  params(
9
9
  requires_path: String,
10
- sorbet_config_path: String,
11
- default_command: String,
12
- file_writer: Thor::Actions
10
+ sorbet_config_path: String
13
11
  ).void
14
12
  end
15
- def initialize(requires_path:, sorbet_config_path:, default_command:, file_writer: FileWriter.new)
13
+ def initialize(requires_path:, sorbet_config_path:)
16
14
  @requires_path = requires_path
17
15
  @sorbet_config_path = sorbet_config_path
18
16
 
19
- super(default_command: default_command, file_writer: file_writer)
17
+ super()
20
18
  end
21
19
 
22
20
  sig { override.void }
23
- def generate
24
- compiler = Compilers::RequiresCompiler.new(@sorbet_config_path)
21
+ def execute
22
+ compiler = Static::RequiresCompiler.new(@sorbet_config_path)
25
23
  name = set_color(@requires_path, :yellow, :bold)
26
24
  say("Compiling #{name}, this may take a few seconds... ")
27
25
 
@@ -44,7 +42,7 @@ module Tapioca
44
42
  say("Done", :green)
45
43
 
46
44
  say("All requires from this application have been written to #{name}.", [:green, :bold])
47
- cmd = set_color("#{@default_command} gem", :yellow, :bold)
45
+ cmd = set_color(default_command(:gem), :yellow, :bold)
48
46
  say("Please review changes and commit them, then run `#{cmd}`.", [:green, :bold])
49
47
  end
50
48
  end
@@ -0,0 +1,84 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Commands
6
+ class Todo < Command
7
+ include SorbetHelper
8
+
9
+ sig do
10
+ params(
11
+ todo_file: String,
12
+ file_header: T::Boolean
13
+ ).void
14
+ end
15
+ def initialize(todo_file:, file_header:)
16
+ @todo_file = todo_file
17
+ @file_header = file_header
18
+
19
+ super()
20
+ end
21
+
22
+ sig { override.void }
23
+ def execute
24
+ say("Finding all unresolved constants, this may take a few seconds... ")
25
+
26
+ # Clean all existing unresolved constants before regenerating the list
27
+ # so Sorbet won't grab them as already resolved.
28
+ File.delete(@todo_file) if File.exist?(@todo_file)
29
+
30
+ constants = unresolved_constants
31
+
32
+ if constants.empty?
33
+ say("Nothing to do", :green)
34
+ return
35
+ end
36
+
37
+ say("Done", :green)
38
+ contents = rbi(constants, command: default_command(:todo))
39
+ create_file(@todo_file, contents.string, verbose: false)
40
+
41
+ name = set_color(@todo_file, :yellow, :bold)
42
+ say("\nAll unresolved constants have been written to #{name}.", [:green, :bold])
43
+ say("Please review changes and commit them.", [:green, :bold])
44
+ end
45
+
46
+ private
47
+
48
+ sig { params(constants: T::Array[String], command: String).returns(RBI::File) }
49
+ def rbi(constants, command:)
50
+ file = RBI::File.new
51
+
52
+ if @file_header
53
+ file.comments << RBI::Comment.new("DO NOT EDIT MANUALLY")
54
+ file.comments << RBI::Comment.new("This is an autogenerated file for unresolved constants.")
55
+ file.comments << RBI::Comment.new("Please instead update this file by running `#{command}`.")
56
+ file.comments << RBI::BlankLine.new
57
+ end
58
+
59
+ file.comments << RBI::Comment.new("typed: false")
60
+
61
+ constants.each do |name|
62
+ file << RBI::Module.new(name)
63
+ end
64
+
65
+ file
66
+ end
67
+
68
+ sig { returns(T::Array[String]) }
69
+ def unresolved_constants
70
+ # Taken from https://github.com/sorbet/sorbet/blob/master/gems/sorbet/lib/todo-rbi.rb
71
+ sorbet("--print=missing-constants", "--quiet", "--stdout-hup-hack", "--no-error-count")
72
+ .out
73
+ .strip
74
+ .each_line
75
+ .map do |line|
76
+ next if line.include?("<")
77
+ next if line.include?("class_of")
78
+ line.strip.gsub("T.untyped::", "")
79
+ end
80
+ .compact
81
+ end
82
+ end
83
+ end
84
+ end