tapioca 0.16.3 → 0.16.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 61d9bbc2b60ce6dda54eea3d826374ca132938ddcdcc8185a731124bbad31704
4
- data.tar.gz: 35aec9d2e19694a7737ff32b7a1d1863b24e255f327d87adbc42e435bcef048c
3
+ metadata.gz: 8efd13a85ae108c14208f510ff83132f745b956a841afe2dd870ec35f67f19f6
4
+ data.tar.gz: ef25365cfc4c0b2442e57a9309f9554254f7ba27af19a77e7f187e1e7222fb27
5
5
  SHA512:
6
- metadata.gz: 0d09845c43897689c8b04ca565d22a9dc817fd8100276624ffc21a83538d3584e70909df6007aee6d360abbd63ff2842132db1346483d1faf9615500e4b3c988
7
- data.tar.gz: b6ec214e91ed9b14fea2eefc00e396c4ea45157995dfdc52f98494da9498025bfa033b807324757f877768819e68ff04ff32dd0e10f93642dbb48e89dcd77451
6
+ metadata.gz: acfd5b028a620f619372ec911269e2c0386c5ab0e4a59d6f2d76718875e7b027518a4334f4eb5df6d5f5c8fc07982456b52c55c026bcb0e9db08a7c24435a7fa
7
+ data.tar.gz: 40b55848a0aa050064596c194a1c9ead65b1ac45ea34ec14d2dfad1d5fe14c89f97da8a35ad6e02d36ce478cbe9f56928246d15a243b8949ee1a1174a25a95fc
data/README.md CHANGED
@@ -489,8 +489,7 @@ Options:
489
489
  # Default: false
490
490
  -q, [--quiet], [--no-quiet], [--skip-quiet] # Suppresses file creation output
491
491
  # Default: false
492
- -w, [--workers=N] # Number of parallel workers to use when generating RBIs (default: 2)
493
- # Default: 2
492
+ -w, [--workers=N] # Number of parallel workers to use when generating RBIs (default: auto)
494
493
  [--rbi-max-line-length=N] # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
495
494
  # Default: 120
496
495
  -e, [--environment=ENVIRONMENT] # The Rack/Rails environment to use when generating RBIs
@@ -949,7 +948,7 @@ dsl:
949
948
  exclude: []
950
949
  verify: false
951
950
  quiet: false
952
- workers: 2
951
+ workers: 1
953
952
  rbi_max_line_length: 120
954
953
  environment: development
955
954
  list_compilers: false
data/exe/tapioca CHANGED
@@ -20,6 +20,11 @@ unless ENV["ENFORCE_TYPECHECKING"] == "1"
20
20
  end
21
21
  end
22
22
 
23
+ unless defined?(Bundler)
24
+ puts "Warning: You're running tapioca without Bundler. This isn't recommended and may cause issues. " \
25
+ "Please use the provided binstub through `bin/tapioca` instead."
26
+ end
27
+
23
28
  require_relative "../lib/tapioca/internal"
24
29
 
25
30
  Tapioca::Cli.start(ARGV)
@@ -0,0 +1,132 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ RubyLsp::Addon.depend_on_ruby_lsp!(">= 0.22.1", "< 0.23")
5
+
6
+ begin
7
+ # The Tapioca add-on depends on the Rails add-on to add a runtime component to the runtime server. We can allow the
8
+ # add-on to work outside of a Rails context in the future, but that may require Tapioca spawning its own runtime
9
+ # server
10
+ require "ruby_lsp/ruby_lsp_rails/runner_client"
11
+ rescue LoadError
12
+ return
13
+ end
14
+
15
+ require "zlib"
16
+
17
+ module RubyLsp
18
+ module Tapioca
19
+ class Addon < ::RubyLsp::Addon
20
+ extend T::Sig
21
+
22
+ sig { void }
23
+ def initialize
24
+ super
25
+
26
+ @global_state = T.let(nil, T.nilable(RubyLsp::GlobalState))
27
+ @rails_runner_client = T.let(nil, T.nilable(RubyLsp::Rails::RunnerClient))
28
+ @index = T.let(nil, T.nilable(RubyIndexer::Index))
29
+ @file_checksums = T.let({}, T::Hash[String, String])
30
+ @outgoing_queue = T.let(nil, T.nilable(Thread::Queue))
31
+ end
32
+
33
+ sig { override.params(global_state: RubyLsp::GlobalState, outgoing_queue: Thread::Queue).void }
34
+ def activate(global_state, outgoing_queue)
35
+ @global_state = global_state
36
+ return unless @global_state.enabled_feature?(:tapiocaAddon)
37
+
38
+ @index = @global_state.index
39
+ @outgoing_queue = outgoing_queue
40
+ Thread.new do
41
+ # Get a handle to the Rails add-on's runtime client. The call to `rails_runner_client` will block this thread
42
+ # until the server has finished booting, but it will not block the main LSP. This has to happen inside of a
43
+ # thread
44
+ addon = T.cast(::RubyLsp::Addon.get("Ruby LSP Rails", ">= 0.3.17", "< 0.4"), ::RubyLsp::Rails::Addon)
45
+ @rails_runner_client = addon.rails_runner_client
46
+ @outgoing_queue << Notification.window_log_message("Activating Tapioca add-on v#{version}")
47
+ @rails_runner_client.register_server_addon(File.expand_path("server_addon.rb", __dir__))
48
+ rescue IncompatibleApiError
49
+ # The requested version for the Rails add-on no longer matches. We need to upgrade and fix the breaking
50
+ # changes
51
+ @outgoing_queue << Notification.window_log_message(
52
+ "IncompatibleApiError: Cannot activate Tapioca LSP add-on",
53
+ type: Constant::MessageType::WARNING,
54
+ )
55
+ end
56
+ end
57
+
58
+ sig { override.void }
59
+ def deactivate
60
+ end
61
+
62
+ sig { override.returns(String) }
63
+ def name
64
+ "Tapioca"
65
+ end
66
+
67
+ sig { override.returns(String) }
68
+ def version
69
+ "0.1.0"
70
+ end
71
+
72
+ sig { params(changes: T::Array[{ uri: String, type: Integer }]).void }
73
+ def workspace_did_change_watched_files(changes)
74
+ return unless T.must(@global_state).enabled_feature?(:tapiocaAddon)
75
+ return unless @rails_runner_client # Client is not ready
76
+
77
+ constants = changes.flat_map do |change|
78
+ path = URI(change[:uri]).to_standardized_path
79
+ next if path.end_with?("_test.rb", "_spec.rb")
80
+ next unless file_updated?(change, path)
81
+
82
+ entries = T.must(@index).entries_for(path)
83
+ next unless entries
84
+
85
+ entries.filter_map do |entry|
86
+ entry.name if entry.class == RubyIndexer::Entry::Class || entry.class == RubyIndexer::Entry::Module
87
+ end
88
+ end.compact
89
+
90
+ return if constants.empty?
91
+
92
+ @rails_runner_client.trigger_reload
93
+ @rails_runner_client.delegate_notification(
94
+ server_addon_name: "Tapioca",
95
+ request_name: "dsl",
96
+ constants: constants,
97
+ )
98
+ end
99
+
100
+ private
101
+
102
+ sig { params(change: T::Hash[Symbol, T.untyped], path: String).returns(T::Boolean) }
103
+ def file_updated?(change, path)
104
+ case change[:type]
105
+ when Constant::FileChangeType::CREATED
106
+ @file_checksums[path] = Zlib.crc32(File.read(path)).to_s
107
+ return true
108
+ when Constant::FileChangeType::CHANGED
109
+ current_checksum = Zlib.crc32(File.read(path)).to_s
110
+ if @file_checksums[path] == current_checksum
111
+ T.must(@outgoing_queue) << Notification.window_log_message(
112
+ "File has not changed. Skipping #{path}",
113
+ type: Constant::MessageType::INFO,
114
+ )
115
+ else
116
+ @file_checksums[path] = current_checksum
117
+ return true
118
+ end
119
+ when Constant::FileChangeType::DELETED
120
+ @file_checksums.delete(path)
121
+ else
122
+ T.must(@outgoing_queue) << Notification.window_log_message(
123
+ "Unexpected file change type: #{change[:type]}",
124
+ type: Constant::MessageType::WARNING,
125
+ )
126
+ end
127
+
128
+ false
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,30 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require "tapioca/internal"
5
+
6
+ module RubyLsp
7
+ module Tapioca
8
+ class ServerAddon < ::RubyLsp::Rails::ServerAddon
9
+ def name
10
+ "Tapioca"
11
+ end
12
+
13
+ def execute(request, params)
14
+ case request
15
+ when "dsl"
16
+ fork do
17
+ dsl(params)
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def dsl(params)
25
+ load("tapioca/cli.rb") # Reload the CLI to reset thor defaults between requests
26
+ ::Tapioca::Cli.start(["dsl", "--lsp_addon", "--workers=1"] + params[:constants])
27
+ end
28
+ end
29
+ end
30
+ end
data/lib/tapioca/cli.rb CHANGED
@@ -111,8 +111,7 @@ module Tapioca
111
111
  option :workers,
112
112
  aliases: ["-w"],
113
113
  type: :numeric,
114
- desc: "Number of parallel workers to use when generating RBIs (default: 2)",
115
- default: 2
114
+ desc: "Number of parallel workers to use when generating RBIs (default: auto)"
116
115
  option :rbi_max_line_length,
117
116
  type: :numeric,
118
117
  desc: "Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped",
@@ -144,6 +143,11 @@ module Tapioca
144
143
  type: :hash,
145
144
  desc: "Options to pass to the DSL compilers",
146
145
  default: {}
146
+ option :lsp_addon,
147
+ type: :boolean,
148
+ desc: "Generate DSL RBIs from the LSP addon. Internal to tapioca and not intended for end-users",
149
+ default: false,
150
+ hide: true
147
151
  def dsl(*constant_or_paths)
148
152
  set_environment(options)
149
153
 
@@ -166,6 +170,7 @@ module Tapioca
166
170
  app_root: options[:app_root],
167
171
  halt_upon_load_error: options[:halt_upon_load_error],
168
172
  compiler_options: options[:compiler_options],
173
+ lsp_addon: options[:lsp_addon],
169
174
  }
170
175
 
171
176
  command = if options[:verify]
@@ -28,6 +28,7 @@ module Tapioca
28
28
  app_root: String,
29
29
  halt_upon_load_error: T::Boolean,
30
30
  compiler_options: T::Hash[String, T.untyped],
31
+ lsp_addon: T::Boolean,
31
32
  ).void
32
33
  end
33
34
  def initialize(
@@ -47,7 +48,8 @@ module Tapioca
47
48
  rbi_formatter: DEFAULT_RBI_FORMATTER,
48
49
  app_root: ".",
49
50
  halt_upon_load_error: true,
50
- compiler_options: {}
51
+ compiler_options: {},
52
+ lsp_addon: false
51
53
  )
52
54
  @requested_constants = requested_constants
53
55
  @requested_paths = requested_paths
@@ -66,6 +68,7 @@ module Tapioca
66
68
  @halt_upon_load_error = halt_upon_load_error
67
69
  @skip_constant = skip_constant
68
70
  @compiler_options = compiler_options
71
+ @lsp_addon = lsp_addon
69
72
 
70
73
  super()
71
74
  end
@@ -74,6 +77,10 @@ module Tapioca
74
77
 
75
78
  sig { params(outpath: Pathname, quiet: T::Boolean).returns(T::Set[Pathname]) }
76
79
  def generate_dsl_rbi_files(outpath, quiet:)
80
+ if @lsp_addon
81
+ pipeline.active_compilers.each(&:reset_state)
82
+ end
83
+
77
84
  existing_rbi_files = existing_rbi_filenames(all_requested_constants)
78
85
 
79
86
  generated_files = pipeline.run do |constant, contents|
@@ -116,6 +123,7 @@ module Tapioca
116
123
  eager_load: @requested_constants.empty? && @requested_paths.empty?,
117
124
  app_root: @app_root,
118
125
  halt_upon_load_error: @halt_upon_load_error,
126
+ lsp_addon: @lsp_addon,
119
127
  )
120
128
  end
121
129
 
@@ -133,6 +141,7 @@ module Tapioca
133
141
  skipped_constants: constantize(@skip_constant, ignore_missing: true),
134
142
  number_of_workers: @number_of_workers,
135
143
  compiler_options: @compiler_options,
144
+ lsp_addon: @lsp_addon,
136
145
  )
137
146
  end
138
147
 
@@ -170,7 +179,9 @@ module Tapioca
170
179
  raise Thor::Error, ""
171
180
  end
172
181
 
173
- processable_constants.map { |_, constant| constant }
182
+ processable_constants
183
+ .map { |_, constant| constant }
184
+ .grep(Module)
174
185
  end
175
186
 
176
187
  sig { params(compiler_names: T::Array[String]).returns(T::Array[T.class_of(Tapioca::Dsl::Compiler)]) }
@@ -19,7 +19,7 @@ module Tapioca
19
19
  purge_stale_dsl_rbi_files(rbi_files_to_purge)
20
20
  say("Done", :green)
21
21
 
22
- if @auto_strictness
22
+ if @auto_strictness && !@lsp_addon
23
23
  say("")
24
24
  validate_rbi_files(
25
25
  command: default_command(:dsl, all_requested_constants.join(" ")),
@@ -51,6 +51,13 @@ module Tapioca
51
51
  @@requested_constants = constants # rubocop:disable Style/ClassVars
52
52
  end
53
53
 
54
+ sig { void }
55
+ def reset_state
56
+ @processable_constants = nil
57
+ @all_classes = nil
58
+ @all_modules = nil
59
+ end
60
+
54
61
  private
55
62
 
56
63
  sig do
@@ -74,11 +81,7 @@ module Tapioca
74
81
  sig { returns(T::Enumerable[T::Class[T.anything]]) }
75
82
  def all_classes
76
83
  @all_classes ||= T.let(
77
- if @@requested_constants.any?
78
- @@requested_constants.grep(Class)
79
- else
80
- ObjectSpace.each_object(Class)
81
- end,
84
+ all_modules.grep(Class).freeze,
82
85
  T.nilable(T::Enumerable[T::Class[T.anything]]),
83
86
  )
84
87
  end
@@ -87,10 +90,10 @@ module Tapioca
87
90
  def all_modules
88
91
  @all_modules ||= T.let(
89
92
  if @@requested_constants.any?
90
- @@requested_constants.select { |k| k.is_a?(Module) }
93
+ @@requested_constants.grep(Module)
91
94
  else
92
- ObjectSpace.each_object(Module)
93
- end,
95
+ ObjectSpace.each_object(Module).to_a
96
+ end.freeze,
94
97
  T.nilable(T::Enumerable[Module]),
95
98
  )
96
99
  end
@@ -69,6 +69,17 @@ module Tapioca
69
69
  T::Array[String],
70
70
  )
71
71
 
72
+ TRANSITION_CALLBACKS =
73
+ T.let(
74
+ [
75
+ "on_transition",
76
+ "guard",
77
+ "after",
78
+ "success",
79
+ ].freeze,
80
+ T::Array[String],
81
+ )
82
+
72
83
  ConstantType = type_member { { fixed: T.all(T::Class[::AASM], ::AASM::ClassMethods) } }
73
84
 
74
85
  sig { override.void }
@@ -162,8 +173,37 @@ module Tapioca
162
173
  method,
163
174
  parameters: [
164
175
  create_opt_param("symbol", type: "T.nilable(Symbol)", default: "nil"),
165
- create_block_param("block", type: "T.nilable(T.proc.bind(#{constant_name}).void)"),
176
+ create_block_param(
177
+ "block",
178
+ type: "T.nilable(T.proc.bind(#{constant_name}).params(opts: T.untyped).void)",
179
+ ),
180
+ ],
181
+ )
182
+ end
183
+
184
+ event.create_method(
185
+ "transitions",
186
+ parameters: [
187
+ create_opt_param("definitions", default: "nil", type: "T.untyped"),
188
+ create_block_param("block", type: "T.nilable(T.proc.bind(PrivateAASMTransition).void)"),
189
+ ],
190
+ )
191
+ end
192
+
193
+ machine.create_class("PrivateAASMTransition", superclass_name: "AASM::Core::Transition") do |transition|
194
+ TRANSITION_CALLBACKS.each do |method|
195
+ return_type = "T.untyped"
196
+ return_type = "T::Boolean" if method == "guard"
197
+
198
+ transition.create_method(
199
+ method,
200
+ parameters: [
201
+ create_block_param(
202
+ "block",
203
+ type: "T.nilable(T.proc.bind(#{constant_name}).params(opts: T.untyped).void)",
204
+ ),
166
205
  ],
206
+ return_type: return_type,
167
207
  )
168
208
  end
169
209
  end
@@ -210,10 +210,6 @@ module Tapioca
210
210
  query_methods |= ActiveRecord::SpawnMethods.instance_methods(false)
211
211
  # Remove the ones we know are private API
212
212
  query_methods -= [:all, :arel, :build_subquery, :construct_join_dependency, :extensions, :spawn]
213
- # Remove "group" which needs a custom return type for GroupChains
214
- query_methods -= [:group]
215
- # Remove "where" which needs a custom return type for WhereChains
216
- query_methods -= [:where]
217
213
  # Remove the methods that ...
218
214
  query_methods
219
215
  .grep_v(/_clause$/) # end with "_clause"
@@ -419,7 +415,7 @@ module Tapioca
419
415
 
420
416
  sig { void }
421
417
  def create_relation_where_chain_class
422
- model.create_class(RelationWhereChainClassName, superclass_name: RelationClassName) do |klass|
418
+ model.create_class(RelationWhereChainClassName) do |klass|
423
419
  create_where_chain_methods(klass, RelationClassName)
424
420
  klass.create_type_variable("Elem", type: "type_member", fixed: constant_name)
425
421
  end
@@ -427,10 +423,7 @@ module Tapioca
427
423
 
428
424
  sig { void }
429
425
  def create_association_relation_where_chain_class
430
- model.create_class(
431
- AssociationRelationWhereChainClassName,
432
- superclass_name: AssociationRelationClassName,
433
- ) do |klass|
426
+ model.create_class(AssociationRelationWhereChainClassName) do |klass|
434
427
  create_where_chain_methods(klass, AssociationRelationClassName)
435
428
  klass.create_type_variable("Elem", type: "type_member", fixed: constant_name)
436
429
  end
@@ -560,27 +553,21 @@ module Tapioca
560
553
  sig { void }
561
554
  def create_relation_methods
562
555
  create_relation_method("all")
563
- create_relation_method(
564
- "group",
565
- parameters: [
566
- create_rest_param("args", type: "T.untyped"),
567
- create_block_param("blk", type: "T.untyped"),
568
- ],
569
- relation_return_type: RelationGroupChainClassName,
570
- association_return_type: AssociationRelationGroupChainClassName,
571
- )
572
- create_relation_method(
573
- "where",
574
- parameters: [
575
- create_rest_param("args", type: "T.untyped"),
576
- create_block_param("blk", type: "T.untyped"),
577
- ],
578
- relation_return_type: RelationWhereChainClassName,
579
- association_return_type: AssociationRelationWhereChainClassName,
580
- )
581
556
 
582
557
  QUERY_METHODS.each do |method_name|
583
558
  case method_name
559
+ when :where
560
+ create_where_relation_method
561
+ when :group
562
+ create_relation_method(
563
+ "group",
564
+ parameters: [
565
+ create_rest_param("args", type: "T.untyped"),
566
+ create_block_param("blk", type: "T.untyped"),
567
+ ],
568
+ relation_return_type: RelationGroupChainClassName,
569
+ association_return_type: AssociationRelationGroupChainClassName,
570
+ )
584
571
  when :distinct
585
572
  create_relation_method(
586
573
  method_name.to_s,
@@ -1056,6 +1043,35 @@ module Tapioca
1056
1043
  )
1057
1044
  end
1058
1045
 
1046
+ sig { void }
1047
+ def create_where_relation_method
1048
+ relation_methods_module.create_method("where") do |method|
1049
+ method.add_rest_param("args")
1050
+
1051
+ method.add_sig do |sig|
1052
+ sig.return_type = RelationWhereChainClassName
1053
+ end
1054
+
1055
+ method.add_sig do |sig|
1056
+ sig.add_param("args", "T.untyped")
1057
+ sig.return_type = RelationClassName
1058
+ end
1059
+ end
1060
+
1061
+ association_relation_methods_module.create_method("where") do |method|
1062
+ method.add_rest_param("args")
1063
+
1064
+ method.add_sig do |sig|
1065
+ sig.return_type = AssociationRelationWhereChainClassName
1066
+ end
1067
+
1068
+ method.add_sig do |sig|
1069
+ sig.add_param("args", "T.untyped")
1070
+ sig.return_type = AssociationRelationClassName
1071
+ end
1072
+ end
1073
+ end
1074
+
1059
1075
  sig do
1060
1076
  params(
1061
1077
  name: T.any(Symbol, String),
@@ -34,6 +34,7 @@ module Tapioca
34
34
  skipped_constants: T::Array[Module],
35
35
  number_of_workers: T.nilable(Integer),
36
36
  compiler_options: T::Hash[String, T.untyped],
37
+ lsp_addon: T::Boolean,
37
38
  ).void
38
39
  end
39
40
  def initialize(
@@ -44,7 +45,8 @@ module Tapioca
44
45
  error_handler: $stderr.method(:puts).to_proc,
45
46
  skipped_constants: [],
46
47
  number_of_workers: nil,
47
- compiler_options: {}
48
+ compiler_options: {},
49
+ lsp_addon: false
48
50
  )
49
51
  @active_compilers = T.let(
50
52
  gather_active_compilers(requested_compilers, excluded_compilers),
@@ -56,6 +58,7 @@ module Tapioca
56
58
  @skipped_constants = skipped_constants
57
59
  @number_of_workers = number_of_workers
58
60
  @compiler_options = compiler_options
61
+ @lsp_addon = lsp_addon
59
62
  @errors = T.let([], T::Array[String])
60
63
  end
61
64
 
@@ -177,7 +180,7 @@ module Tapioca
177
180
  # Find the constants that have been reloaded
178
181
  reloaded_constants = constants_by_name.select { |_, constants| constants.size > 1 }.keys
179
182
 
180
- unless reloaded_constants.empty?
183
+ unless reloaded_constants.empty? || @lsp_addon
181
184
  reloaded_constant_names = reloaded_constants.map { |name| "`#{name}`" }.join(", ")
182
185
 
183
186
  $stderr.puts("WARNING: Multiple constants with the same name: #{reloaded_constant_names}")
@@ -222,9 +225,14 @@ module Tapioca
222
225
 
223
226
  sig { void }
224
227
  def abort_if_pending_migrations!
228
+ # When running within the add-on, we cannot invoke the abort if pending migrations task because that will exit
229
+ # the process and crash the Rails runtime server. Instead, the Rails add-on checks for pending migrations and
230
+ # warns the user, so that they are aware they need to migrate their database
231
+ return if @lsp_addon
225
232
  return unless defined?(::Rake)
226
233
 
227
234
  Rails.application.load_tasks
235
+
228
236
  if Rake::Task.task_defined?("db:abort_if_pending_migrations")
229
237
  Rake::Task["db:abort_if_pending_migrations"].invoke
230
238
  end
@@ -28,7 +28,8 @@ class GitAttributes
28
28
  # exist, we just return.
29
29
  return unless path.exist?
30
30
 
31
- File.write(path.join(".gitattributes"), content)
31
+ git_attributes_path = path.join(".gitattributes")
32
+ File.write(git_attributes_path, content) unless git_attributes_path.exist?
32
33
  end
33
34
  end
34
35
  end
@@ -107,36 +107,12 @@ module Tapioca
107
107
 
108
108
  sig { params(name: String).returns(T::Boolean) }
109
109
  def valid_method_name?(name)
110
- # try to parse a method definition with this name
111
- iseq = RubyVM::InstructionSequence.compile("def #{name}; end", nil, nil, 0, false)
112
- # pull out the first operation in the instruction sequence and its first argument
113
- op, arg, _data = iseq.to_a.dig(-1, 0)
114
- # make sure that the operation is a method definition and the method that was
115
- # defined has the expected name, for example, for `def !foo; end` we don't get
116
- # a syntax error but instead get a method defined as `"foo"`
117
- op == :definemethod && arg == name.to_sym
118
- rescue SyntaxError
119
- false
110
+ Prism.parse_success?("def self.#{name}(a); end")
120
111
  end
121
112
 
122
113
  sig { params(name: String).returns(T::Boolean) }
123
114
  def valid_parameter_name?(name)
124
- sentinel_method_name = :sentinel_method_name
125
- # try to parse a method definition with this name as the name of a
126
- # keyword parameter. If we use a positional parameter, then parameter names
127
- # like `&` (and maybe others) will be treated like `def foo(&); end` and will
128
- # thus be considered valid. Using a required keyword parameter prevents that
129
- # confusion between Ruby syntax and parameter name.
130
- iseq = RubyVM::InstructionSequence.compile("def #{sentinel_method_name}(#{name}:); end", nil, nil, 0, false)
131
- # pull out the first operation in the instruction sequence and its first argument and data
132
- op, arg, data = iseq.to_a.dig(-1, 0)
133
- # make sure that:
134
- # 1. a method was defined, and
135
- # 2. the method has the expected method name, and
136
- # 3. the method has a keyword parameter with the expected name
137
- op == :definemethod && arg == sentinel_method_name && data.dig(11, :keyword, 0) == name.to_sym
138
- rescue SyntaxError
139
- false
115
+ Prism.parse_success?("def sentinel_method_name(#{name}:); end")
140
116
  end
141
117
  end
142
118
  end
@@ -25,9 +25,6 @@ module URI
25
25
  # have the uri gem in their own bundle and thus not use a compatible version.
26
26
  PARSER = T.let(const_defined?(:RFC2396_PARSER) ? RFC2396_PARSER : DEFAULT_PARSER, RFC2396_Parser)
27
27
 
28
- alias_method(:gem_name, :host)
29
- alias_method(:line_number, :fragment)
30
-
31
28
  sig { returns(T.nilable(String)) }
32
29
  attr_reader :gem_version
33
30
 
@@ -54,6 +51,16 @@ module URI
54
51
  end
55
52
  end
56
53
 
54
+ sig { returns(T.nilable(String)) }
55
+ def gem_name
56
+ host
57
+ end
58
+
59
+ sig { returns(T.nilable(String)) }
60
+ def line_number
61
+ fragment
62
+ end
63
+
57
64
  sig { params(v: T.nilable(String)).void }
58
65
  def set_path(v) # rubocop:disable Naming/AccessorMethodName
59
66
  return if v.nil?
@@ -23,6 +23,7 @@ require "tempfile"
23
23
  require "thor"
24
24
  require "yaml"
25
25
  require "yard-sorbet"
26
+ require "prism"
26
27
 
27
28
  require "tapioca/runtime/dynamic_mixin_compiler"
28
29
  require "tapioca/sorbet_ext/backcompat_patches"
@@ -10,16 +10,32 @@ module Tapioca
10
10
  extend T::Sig
11
11
 
12
12
  sig do
13
- params(tapioca_path: String, eager_load: T::Boolean, app_root: String, halt_upon_load_error: T::Boolean).void
13
+ params(
14
+ tapioca_path: String,
15
+ eager_load: T::Boolean,
16
+ app_root: String,
17
+ halt_upon_load_error: T::Boolean,
18
+ lsp_addon: T::Boolean,
19
+ ).void
14
20
  end
15
- def load_application(tapioca_path:, eager_load: true, app_root: ".", halt_upon_load_error: true)
21
+ def load_application(
22
+ tapioca_path:,
23
+ eager_load: true,
24
+ app_root: ".",
25
+ halt_upon_load_error: true,
26
+ lsp_addon: false
27
+ )
16
28
  loader = new(
17
29
  tapioca_path: tapioca_path,
18
30
  eager_load: eager_load,
19
31
  app_root: app_root,
20
32
  halt_upon_load_error: halt_upon_load_error,
21
33
  )
22
- loader.load
34
+ if lsp_addon
35
+ loader.load_dsl_extensions_and_compilers
36
+ else
37
+ loader.load
38
+ end
23
39
  end
24
40
  end
25
41
 
@@ -30,6 +46,12 @@ module Tapioca
30
46
  load_dsl_compilers
31
47
  end
32
48
 
49
+ sig { void }
50
+ def load_dsl_extensions_and_compilers
51
+ load_dsl_extensions
52
+ load_dsl_compilers
53
+ end
54
+
33
55
  protected
34
56
 
35
57
  sig do
@@ -63,12 +85,7 @@ module Tapioca
63
85
  def load_dsl_compilers
64
86
  say("Loading DSL compiler classes... ")
65
87
 
66
- # Load all built-in compilers
67
- Dir.glob("#{Tapioca::LIB_ROOT_DIR}/tapioca/dsl/compilers/*.rb").each do |compiler|
68
- require File.expand_path(compiler)
69
- end
70
-
71
- # Load all custom compilers exported from gems
88
+ # Load built-in compilers and custom compilers exported from gems
72
89
  ::Gem.find_files("tapioca/dsl/compilers/*.rb").each do |compiler|
73
90
  require File.expand_path(compiler)
74
91
  end
@@ -178,13 +178,19 @@ module Tapioca
178
178
  end
179
179
 
180
180
  # Examines the call stack to identify the closest location where a "require" is performed
181
- # by searching for the label "<top (required)>". If none is found, it returns the location
181
+ # by searching for the label "<top (required)>" or "block in <class:...>" in the
182
+ # case of an ActiveSupport.on_load hook. If none is found, it returns the location
182
183
  # labeled "<main>", which is the original call site.
183
184
  sig { params(locations: T.nilable(T::Array[Thread::Backtrace::Location])).returns(String) }
184
185
  def resolve_loc(locations)
185
186
  return "" unless locations
186
187
 
187
- resolved_loc = locations.find { |loc| REQUIRED_FROM_LABELS.include?(loc.label) }
188
+ resolved_loc = locations.find do |loc|
189
+ label = loc.label
190
+ next unless label
191
+
192
+ REQUIRED_FROM_LABELS.include?(label) || label.start_with?("block in <class:")
193
+ end
188
194
  return "" unless resolved_loc
189
195
 
190
196
  resolved_loc.absolute_path || ""
@@ -51,6 +51,8 @@ module Tapioca
51
51
 
52
52
  sig { params(paths: T::Array[Pathname]).returns(T::Set[String]) }
53
53
  def symbols_from_paths(paths)
54
+ return Set.new if paths.empty?
55
+
54
56
  output = Tempfile.create("sorbet") do |file|
55
57
  file.write(Array(paths).join("\n"))
56
58
  file.flush
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.16.3"
5
+ VERSION = "0.16.5"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapioca
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.3
4
+ version: 0.16.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ufuk Kayserilioglu
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2024-10-03 00:00:00.000000000 Z
14
+ date: 2024-11-27 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -136,6 +136,8 @@ files:
136
136
  - LICENSE.txt
137
137
  - README.md
138
138
  - exe/tapioca
139
+ - lib/ruby_lsp/tapioca/addon.rb
140
+ - lib/ruby_lsp/tapioca/server_addon.rb
139
141
  - lib/tapioca.rb
140
142
  - lib/tapioca/bundler_ext/auto_require_hook.rb
141
143
  - lib/tapioca/cli.rb
@@ -284,7 +286,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
284
286
  - !ruby/object:Gem::Version
285
287
  version: '0'
286
288
  requirements: []
287
- rubygems_version: 3.5.20
289
+ rubygems_version: 3.5.23
288
290
  signing_key:
289
291
  specification_version: 4
290
292
  summary: A Ruby Interface file generator for gems, core types and the Ruby standard