tapioca 0.10.4 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/tapioca/cli.rb +14 -5
  3. data/lib/tapioca/commands/annotations.rb +2 -0
  4. data/lib/tapioca/commands/configure.rb +1 -0
  5. data/lib/tapioca/commands/dsl.rb +17 -3
  6. data/lib/tapioca/commands/gem.rb +4 -2
  7. data/lib/tapioca/dsl/compilers/aasm.rb +78 -17
  8. data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +1 -1
  9. data/lib/tapioca/dsl/compilers/active_record_columns.rb +3 -3
  10. data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +8 -5
  11. data/lib/tapioca/dsl/compilers/active_record_relations.rb +140 -83
  12. data/lib/tapioca/dsl/compilers/active_record_scope.rb +1 -1
  13. data/lib/tapioca/dsl/compilers/active_record_secure_token.rb +74 -0
  14. data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +14 -11
  15. data/lib/tapioca/dsl/compilers/active_resource.rb +22 -15
  16. data/lib/tapioca/dsl/compilers/active_storage.rb +4 -2
  17. data/lib/tapioca/dsl/compilers/graphql_input_object.rb +21 -1
  18. data/lib/tapioca/dsl/compilers/kredis.rb +130 -0
  19. data/lib/tapioca/dsl/compilers/smart_properties.rb +7 -4
  20. data/lib/tapioca/dsl/compilers/url_helpers.rb +7 -4
  21. data/lib/tapioca/dsl/extensions/active_record.rb +9 -0
  22. data/lib/tapioca/dsl/extensions/kredis.rb +114 -0
  23. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +37 -27
  24. data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +1 -0
  25. data/lib/tapioca/dsl/pipeline.rb +12 -5
  26. data/lib/tapioca/gem/listeners/sorbet_enums.rb +1 -1
  27. data/lib/tapioca/gem/listeners/yard_doc.rb +13 -10
  28. data/lib/tapioca/gem/pipeline.rb +14 -0
  29. data/lib/tapioca/gemfile.rb +6 -2
  30. data/lib/tapioca/helpers/rbi_files_helper.rb +12 -6
  31. data/lib/tapioca/helpers/sorbet_helper.rb +7 -4
  32. data/lib/tapioca/helpers/source_uri.rb +10 -7
  33. data/lib/tapioca/loaders/gem.rb +4 -2
  34. data/lib/tapioca/loaders/loader.rb +99 -35
  35. data/lib/tapioca/rbi_ext/model.rb +8 -3
  36. data/lib/tapioca/rbi_formatter.rb +11 -8
  37. data/lib/tapioca/runtime/attached_class_of_32.rb +20 -0
  38. data/lib/tapioca/runtime/attached_class_of_legacy.rb +27 -0
  39. data/lib/tapioca/runtime/reflection.rb +11 -10
  40. data/lib/tapioca/runtime/trackers.rb +17 -0
  41. data/lib/tapioca/static/symbol_loader.rb +14 -14
  42. data/lib/tapioca/version.rb +1 -1
  43. data/lib/tapioca.rb +8 -5
  44. metadata +7 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbed9bfddb52a94735d9455ed4351c4ef1389236ceba2e19ce46d999a05536b0
4
- data.tar.gz: b1a35528c1e9cfa5fd735a64d4988bfd72c0f980835d58e4a37c62d5694e3daa
3
+ metadata.gz: f32d4fd559ee85f540926b3faa454aaf2ddd919b502438bf2deda24a4b04f855
4
+ data.tar.gz: 9368c2329923554e639310003630d36f9bf7a2f895df619cfe844dacd9a2c176
5
5
  SHA512:
6
- metadata.gz: 6138060ec0cea66dda6f96484329626d613494404cf99f851d1dbd59249546ffdfa0b0dd58236ef63d65b93acd250eb3a46b5a3eb1a3f8491014125b18a50800
7
- data.tar.gz: 266205a5dd212e87836d1d26f1f018ab83d64aaacc9b793e9a01b88c6213bda834991194896a602ce6f267ef96e3ef8f78d14464005cefcfc010bb666ddc6f3e
6
+ metadata.gz: c997e7598a5cef84e52935b0f9681ca636fef5fb866d30f1e6d19e2912ae38b5ec7499e69cde6cd147b4a723214d80abb109dbd523e48f143eae0b1edbefd46e
7
+ data.tar.gz: 33882ab5de1b8b43d775bdb20b0fab39d97ebe2ce7b346bb9828b64e27dd99bb7d46814ac441dd1b6b7aadb3b4aa02f93a652d4e6fa42c34ac83241eb9eb78d7
data/lib/tapioca/cli.rb CHANGED
@@ -25,9 +25,12 @@ module Tapioca
25
25
 
26
26
  desc "init", "get project ready for type checking"
27
27
  def init
28
- invoke(:configure)
29
- invoke(:annotations)
30
- invoke(:gem)
28
+ # We need to make sure that trackers stay enabled until the `gem` command is invoked
29
+ Runtime::Trackers.with_trackers_enabled do
30
+ invoke(:configure)
31
+ invoke(:annotations)
32
+ invoke(:gem)
33
+ end
31
34
  invoke(:todo)
32
35
 
33
36
  print_init_next_steps
@@ -127,11 +130,15 @@ module Tapioca
127
130
  type: :string,
128
131
  desc: "The path to the Rails application",
129
132
  default: "."
130
- def dsl(*constants)
133
+ def dsl(*constant_or_paths)
131
134
  set_environment(options)
132
135
 
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
+
133
139
  command = Commands::Dsl.new(
134
140
  requested_constants: constants,
141
+ requested_paths: paths.map { |p| Pathname.new(p) },
135
142
  outpath: Pathname.new(options[:outdir]),
136
143
  only: options[:only],
137
144
  exclude: options[:exclude],
@@ -293,7 +300,9 @@ module Tapioca
293
300
  end
294
301
 
295
302
  desc "annotations", "Pull gem RBI annotations from remote sources"
296
- option :sources, type: :array, default: [CENTRAL_REPO_ROOT_URI],
303
+ option :sources,
304
+ type: :array,
305
+ default: [CENTRAL_REPO_ROOT_URI],
297
306
  desc: "URIs of the sources to pull gem RBI annotations from"
298
307
  option :netrc, type: :boolean, default: true, desc: "Use .netrc to authenticate to private sources"
299
308
  option :netrc_file, type: :string, desc: "Path to .netrc file"
@@ -189,6 +189,8 @@ module Tapioca
189
189
 
190
190
  sig { params(name: String, content: String).returns(String) }
191
191
  def add_header(name, content)
192
+ # WARNING: Changing this header could impact how GitHub determines if the file should be hidden:
193
+ # https://github.com/github/linguist/pull/6143
192
194
  header = <<~COMMENT
193
195
  # DO NOT EDIT MANUALLY
194
196
  # This file was pulled from a central RBI files repository.
@@ -41,6 +41,7 @@ module Tapioca
41
41
  create_file(@sorbet_config, <<~CONTENT, skip: true, force: false)
42
42
  --dir
43
43
  .
44
+ --ignore=tmp/
44
45
  --ignore=vendor/
45
46
  CONTENT
46
47
  end
@@ -10,6 +10,7 @@ module Tapioca
10
10
  sig do
11
11
  params(
12
12
  requested_constants: T::Array[String],
13
+ requested_paths: T::Array[Pathname],
13
14
  outpath: Pathname,
14
15
  only: T::Array[String],
15
16
  exclude: T::Array[String],
@@ -27,6 +28,7 @@ module Tapioca
27
28
  end
28
29
  def initialize(
29
30
  requested_constants:,
31
+ requested_paths:,
30
32
  outpath:,
31
33
  only:,
32
34
  exclude:,
@@ -42,6 +44,7 @@ module Tapioca
42
44
  app_root: "."
43
45
  )
44
46
  @requested_constants = requested_constants
47
+ @requested_paths = requested_paths
45
48
  @outpath = outpath
46
49
  @only = only
47
50
  @exclude = exclude
@@ -63,7 +66,7 @@ module Tapioca
63
66
  def list_compilers
64
67
  Loaders::Dsl.load_application(
65
68
  tapioca_path: @tapioca_path,
66
- eager_load: @requested_constants.empty?,
69
+ eager_load: @requested_constants.empty? && @requested_paths.empty?,
67
70
  app_root: @app_root,
68
71
  )
69
72
 
@@ -101,6 +104,15 @@ module Tapioca
101
104
  end
102
105
  say("")
103
106
 
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
+
104
116
  outpath = @should_verify ? Pathname.new(Dir.mktmpdir) : @outpath
105
117
  rbi_files_to_purge = existing_rbi_filenames(@requested_constants)
106
118
 
@@ -153,6 +165,7 @@ module Tapioca
153
165
  def create_pipeline
154
166
  Tapioca::Dsl::Pipeline.new(
155
167
  requested_constants: constantize(@requested_constants),
168
+ requested_paths: @requested_paths,
156
169
  requested_compilers: constantize_compilers(@only),
157
170
  excluded_compilers: constantize_compilers(@exclude),
158
171
  error_handler: ->(error) {
@@ -167,8 +180,9 @@ module Tapioca
167
180
  filenames = if requested_constants.empty?
168
181
  Pathname.glob(path / "**/*.rbi")
169
182
  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)
172
186
  end
173
187
  end
174
188
 
@@ -156,9 +156,11 @@ module Tapioca
156
156
 
157
157
  rbi = RBI::File.new(strictness: @typed_overrides[gem.name] || "true")
158
158
 
159
- @rbi_formatter.write_header!(rbi,
159
+ @rbi_formatter.write_header!(
160
+ rbi,
160
161
  default_command(:gem, gem.name),
161
- reason: "types exported from the `#{gem.name}` gem") if @file_header
162
+ reason: "types exported from the `#{gem.name}` gem",
163
+ ) if @file_header
162
164
 
163
165
  rbi.root = Tapioca::Gem::Pipeline.new(gem, include_doc: @include_doc, include_loc: @include_loc).compile
164
166
 
@@ -44,8 +44,33 @@ module Tapioca
44
44
  # https://github.com/aasm/aasm/blob/0e03746/lib/aasm/core/event.rb#L21-L29
45
45
  EVENT_CALLBACKS =
46
46
  T.let(
47
- ["after", "after_commit", "after_transaction", "before", "before_transaction", "ensure", "error",
48
- "before_success", "success",].freeze,
47
+ [
48
+ "after",
49
+ "after_commit",
50
+ "after_transaction",
51
+ "before",
52
+ "before_transaction",
53
+ "ensure",
54
+ "error",
55
+ "before_success",
56
+ "success",
57
+ ].freeze,
58
+ T::Array[String],
59
+ )
60
+
61
+ # Taken directly from the AASM::Base class, here:
62
+ # https://github.com/aasm/aasm/blob/0e03746a2b86558ee1bf7bd7db873938cbb3b29b/lib/aasm/base.rb#L145-L171
63
+ GLOBAL_CALLBACKS =
64
+ T.let(
65
+ [
66
+ "after_all_transitions",
67
+ "after_all_transactions",
68
+ "before_all_transactions",
69
+ "before_all_events",
70
+ "after_all_events",
71
+ "error_on_all_events",
72
+ "ensure_on_all_events",
73
+ ].freeze,
49
74
  T::Array[String],
50
75
  )
51
76
 
@@ -53,23 +78,47 @@ module Tapioca
53
78
 
54
79
  sig { override.void }
55
80
  def decorate
56
- aasm = constant.aasm
57
- return if !aasm || aasm.states.empty?
81
+ state_machine_store = ::AASM::StateMachineStore.fetch(constant)
82
+ return unless state_machine_store
83
+
84
+ state_machines = state_machine_store.machine_names.map { |n| constant.aasm(n) }
85
+ return if state_machines.all? { |m| m.states.empty? }
58
86
 
59
87
  root.create_path(constant) do |model|
60
- # Create all of the constants and methods for each state
61
- aasm.states.each do |state|
62
- model.create_constant("STATE_#{state.name.upcase}", value: "T.let(T.unsafe(nil), Symbol)")
63
- model.create_method("#{state.name}?", return_type: "T::Boolean")
64
- end
88
+ state_machines.each do |state_machine|
89
+ namespace = state_machine.__send__(:namespace)
90
+
91
+ # Create all of the constants and methods for each state
92
+ state_machine.states.each do |state|
93
+ name = state.name
94
+ name = "#{namespace}_#{name}" if namespace
95
+
96
+ model.create_constant("STATE_#{name.upcase}", value: "T.let(T.unsafe(nil), Symbol)")
97
+ model.create_method("#{name}?", return_type: "T::Boolean")
98
+ end
65
99
 
66
- # Create all of the methods for each event
67
- parameters = [create_rest_param("opts", type: "T.untyped")]
68
- aasm.events.each do |event|
69
- model.create_method(event.name.to_s, parameters: parameters)
70
- model.create_method("#{event.name}!", parameters: parameters)
71
- model.create_method("#{event.name}_without_validation!", parameters: parameters)
72
- model.create_method("may_#{event.name}?", return_type: "T::Boolean")
100
+ # Create all of the methods for each event
101
+ parameters = [create_rest_param("opts", type: "T.untyped")]
102
+ state_machine.events.each do |event|
103
+ model.create_method(event.name.to_s, parameters: parameters)
104
+ model.create_method("#{event.name}!", parameters: parameters)
105
+ model.create_method("#{event.name}_without_validation!", parameters: parameters)
106
+ model.create_method("may_#{event.name}?", return_type: "T::Boolean")
107
+
108
+ # For events, if there's a namespace the default methods are created in addition to
109
+ # namespaced ones.
110
+ next unless namespace
111
+
112
+ name = "#{event.name}_#{namespace}"
113
+
114
+ model.create_method(name.to_s, parameters: parameters)
115
+ model.create_method("#{name}!", parameters: parameters)
116
+ model.create_method("may_#{name}?", return_type: "T::Boolean")
117
+
118
+ # There's no namespaced method created for `_without_validation`, so skip
119
+ # defining a method for:
120
+ # "#{name}_without_validation!"
121
+ end
73
122
  end
74
123
 
75
124
  # Create the overall state machine method, which will return an
@@ -97,6 +146,18 @@ module Tapioca
97
146
  ],
98
147
  )
99
148
 
149
+ constant_name = name_of(constant)
150
+
151
+ GLOBAL_CALLBACKS.each do |method|
152
+ machine.create_method(
153
+ method,
154
+ parameters: [
155
+ create_opt_param("symbol", type: "T.nilable(Symbol)", default: "nil"),
156
+ create_block_param("block", type: "T.nilable(T.proc.bind(#{constant_name}).void)"),
157
+ ],
158
+ )
159
+ end
160
+
100
161
  # Create a private event class that we can pass around for the
101
162
  # purpose of binding all of the callbacks without having to
102
163
  # explicitly bind self in each one.
@@ -106,7 +167,7 @@ module Tapioca
106
167
  method,
107
168
  parameters: [
108
169
  create_opt_param("symbol", type: "T.nilable(Symbol)", default: "nil"),
109
- create_block_param("block", type: "T.nilable(T.proc.bind(#{name_of(constant)}).void)"),
170
+ create_block_param("block", type: "T.nilable(T.proc.bind(#{constant_name}).void)"),
110
171
  ],
111
172
  )
112
173
  end
@@ -156,7 +156,7 @@ module Tapioca
156
156
  sig { params(mod: Module).returns(T::Array[String]) }
157
157
  def gather_includes(mod)
158
158
  mod.ancestors
159
- .reject { |ancestor| ancestor.is_a?(Class) || ancestor == mod || ancestor.name.nil? }
159
+ .reject { |ancestor| ancestor.is_a?(Class) || ancestor == mod || name_of(ancestor).nil? }
160
160
  .map { |ancestor| T.must(qualified_name_of(ancestor)) }
161
161
  .reverse
162
162
  end
@@ -16,7 +16,8 @@ module Tapioca
16
16
  # `Tapioca::Dsl::Compilers::ActiveRecordColumns` refines RBI files for subclasses of
17
17
  # [`ActiveRecord::Base`](https://api.rubyonrails.org/classes/ActiveRecord/Base.html).
18
18
  # This compiler is only responsible for defining the attribute methods that would be
19
- # created for the columns that are defined in the Active Record model.
19
+ # created for columns and virtual attributes that are defined in the Active Record
20
+ # model.
20
21
  #
21
22
  # For example, with the following model class:
22
23
  # ~~~rb
@@ -109,8 +110,7 @@ module Tapioca
109
110
 
110
111
  root.create_path(constant) do |model|
111
112
  model.create_module(AttributeMethodsModuleName) do |mod|
112
- constant.columns_hash.each_key do |column_name|
113
- column_name = column_name.to_s
113
+ constant.attribute_names.each do |column_name|
114
114
  add_methods_for_attribute(mod, column_name)
115
115
  end
116
116
 
@@ -72,11 +72,14 @@ module Tapioca
72
72
 
73
73
  sig { returns(Class) }
74
74
  def fixture_loader
75
- @fixture_loader ||= T.let(Class.new do
76
- T.unsafe(self).include(ActiveRecord::TestFixtures)
77
- T.unsafe(self).fixture_path = Rails.root.join("test", "fixtures")
78
- T.unsafe(self).fixtures(:all)
79
- end, T.nilable(Class))
75
+ @fixture_loader ||= T.let(
76
+ Class.new do
77
+ T.unsafe(self).include(ActiveRecord::TestFixtures)
78
+ T.unsafe(self).fixture_path = Rails.root.join("test", "fixtures")
79
+ T.unsafe(self).fixtures(:all)
80
+ end,
81
+ T.nilable(Class),
82
+ )
80
83
  end
81
84
 
82
85
  sig { returns(T::Array[String]) }