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 +4 -4
- data/README.md +2 -3
- data/exe/tapioca +5 -0
- data/lib/ruby_lsp/tapioca/addon.rb +132 -0
- data/lib/ruby_lsp/tapioca/server_addon.rb +30 -0
- data/lib/tapioca/cli.rb +7 -2
- data/lib/tapioca/commands/abstract_dsl.rb +13 -2
- data/lib/tapioca/commands/dsl_generate.rb +1 -1
- data/lib/tapioca/dsl/compiler.rb +11 -8
- data/lib/tapioca/dsl/compilers/aasm.rb +41 -1
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +43 -27
- data/lib/tapioca/dsl/pipeline.rb +10 -2
- data/lib/tapioca/helpers/git_attributes.rb +2 -1
- data/lib/tapioca/helpers/rbi_helper.rb +2 -26
- data/lib/tapioca/helpers/source_uri.rb +10 -3
- data/lib/tapioca/internal.rb +1 -0
- data/lib/tapioca/loaders/dsl.rb +26 -9
- data/lib/tapioca/runtime/reflection.rb +8 -2
- data/lib/tapioca/static/symbol_loader.rb +2 -0
- data/lib/tapioca/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8efd13a85ae108c14208f510ff83132f745b956a841afe2dd870ec35f67f19f6
|
4
|
+
data.tar.gz: ef25365cfc4c0b2442e57a9309f9554254f7ba27af19a77e7f187e1e7222fb27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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:
|
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:
|
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:
|
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
|
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)]) }
|
data/lib/tapioca/dsl/compiler.rb
CHANGED
@@ -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
|
-
|
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.
|
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(
|
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
|
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),
|
data/lib/tapioca/dsl/pipeline.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
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?
|
data/lib/tapioca/internal.rb
CHANGED
data/lib/tapioca/loaders/dsl.rb
CHANGED
@@ -10,16 +10,32 @@ module Tapioca
|
|
10
10
|
extend T::Sig
|
11
11
|
|
12
12
|
sig do
|
13
|
-
params(
|
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(
|
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
|
-
|
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
|
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)>"
|
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
|
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 || ""
|
data/lib/tapioca/version.rb
CHANGED
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.
|
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-
|
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.
|
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
|