tapioca 0.10.4 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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]) }