steep 1.10.0 → 2.0.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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +84 -1
  3. data/CLAUDE.md +114 -0
  4. data/README.md +1 -1
  5. data/Rakefile +15 -3
  6. data/Steepfile +13 -13
  7. data/lib/steep/annotation_parser.rb +5 -1
  8. data/lib/steep/annotations_helper.rb +12 -2
  9. data/lib/steep/ast/node/type_application.rb +22 -16
  10. data/lib/steep/ast/node/type_assertion.rb +7 -4
  11. data/lib/steep/ast/types/factory.rb +3 -2
  12. data/lib/steep/cli.rb +246 -2
  13. data/lib/steep/daemon/configuration.rb +19 -0
  14. data/lib/steep/daemon/server.rb +476 -0
  15. data/lib/steep/daemon.rb +201 -0
  16. data/lib/steep/diagnostic/ruby.rb +50 -8
  17. data/lib/steep/diagnostic/signature.rb +31 -8
  18. data/lib/steep/drivers/check.rb +301 -140
  19. data/lib/steep/drivers/print_project.rb +9 -10
  20. data/lib/steep/drivers/query.rb +102 -0
  21. data/lib/steep/drivers/start_server.rb +19 -0
  22. data/lib/steep/drivers/stop_server.rb +20 -0
  23. data/lib/steep/drivers/watch.rb +2 -2
  24. data/lib/steep/index/rbs_index.rb +38 -13
  25. data/lib/steep/index/signature_symbol_provider.rb +24 -3
  26. data/lib/steep/interface/builder.rb +48 -15
  27. data/lib/steep/interface/shape.rb +13 -5
  28. data/lib/steep/locator.rb +377 -0
  29. data/lib/steep/project/dsl.rb +26 -5
  30. data/lib/steep/project/group.rb +8 -2
  31. data/lib/steep/project/target.rb +16 -2
  32. data/lib/steep/project.rb +21 -2
  33. data/lib/steep/server/base_worker.rb +2 -2
  34. data/lib/steep/server/change_buffer.rb +2 -1
  35. data/lib/steep/server/custom_methods.rb +12 -0
  36. data/lib/steep/server/inline_source_change_detector.rb +94 -0
  37. data/lib/steep/server/interaction_worker.rb +51 -74
  38. data/lib/steep/server/lsp_formatter.rb +48 -12
  39. data/lib/steep/server/master.rb +100 -18
  40. data/lib/steep/server/target_group_files.rb +124 -151
  41. data/lib/steep/server/type_check_controller.rb +276 -123
  42. data/lib/steep/server/type_check_worker.rb +104 -3
  43. data/lib/steep/services/completion_provider/rbs.rb +74 -0
  44. data/lib/steep/services/completion_provider/ruby.rb +652 -0
  45. data/lib/steep/services/completion_provider/type_name.rb +243 -0
  46. data/lib/steep/services/completion_provider.rb +39 -662
  47. data/lib/steep/services/content_change.rb +14 -1
  48. data/lib/steep/services/file_loader.rb +4 -2
  49. data/lib/steep/services/goto_service.rb +271 -68
  50. data/lib/steep/services/hover_provider/content.rb +67 -0
  51. data/lib/steep/services/hover_provider/rbs.rb +8 -9
  52. data/lib/steep/services/hover_provider/ruby.rb +123 -64
  53. data/lib/steep/services/hover_provider/singleton_methods.rb +4 -0
  54. data/lib/steep/services/signature_service.rb +129 -54
  55. data/lib/steep/services/type_check_service.rb +72 -27
  56. data/lib/steep/signature/validator.rb +30 -18
  57. data/lib/steep/source/ignore_ranges.rb +14 -4
  58. data/lib/steep/source.rb +16 -2
  59. data/lib/steep/tagged_logging.rb +39 -0
  60. data/lib/steep/type_construction.rb +94 -21
  61. data/lib/steep/type_inference/block_params.rb +7 -7
  62. data/lib/steep/type_inference/context.rb +4 -2
  63. data/lib/steep/type_inference/logic_type_interpreter.rb +21 -3
  64. data/lib/steep/type_inference/method_call.rb +4 -0
  65. data/lib/steep/type_inference/type_env.rb +1 -1
  66. data/lib/steep/typing.rb +0 -2
  67. data/lib/steep/version.rb +1 -1
  68. data/lib/steep.rb +42 -32
  69. data/manual/ruby-diagnostics.md +67 -0
  70. data/sample/Steepfile +1 -0
  71. data/sample/lib/conference.rb +1 -0
  72. data/sample/lib/deprecated.rb +6 -0
  73. data/sample/lib/inline.rb +43 -0
  74. data/sample/sig/generics.rbs +3 -0
  75. data/steep.gemspec +4 -5
  76. metadata +26 -26
  77. data/lib/steep/services/type_name_completion.rb +0 -236
@@ -7,10 +7,12 @@ module Steep
7
7
 
8
8
  WorkspaceSymbolJob = _ = Struct.new(:query, :id, keyword_init: true)
9
9
  StatsJob = _ = Struct.new(:id, keyword_init: true)
10
+ QueryDefinitionJob = _ = Struct.new(:id, :name, keyword_init: true)
10
11
  StartTypeCheckJob = _ = Struct.new(:guid, :changes, keyword_init: true)
11
12
  TypeCheckCodeJob = _ = Struct.new(:guid, :path, :target, keyword_init: true)
12
13
  ValidateAppSignatureJob = _ = Struct.new(:guid, :path, :target, keyword_init: true)
13
14
  ValidateLibrarySignatureJob = _ = Struct.new(:guid, :path, :target, keyword_init: true)
15
+ TypeCheckInlineCodeJob = _ = Struct.new(:guid, :path, :target, keyword_init: true)
14
16
  class GotoJob < Struct.new(:id, :kind, :params, keyword_init: true)
15
17
  def self.implementation(id:, params:)
16
18
  new(
@@ -52,6 +54,7 @@ module Steep
52
54
  include ChangeBuffer
53
55
 
54
56
  attr_reader :io_socket
57
+ attr_reader :need_to_warmup
55
58
 
56
59
  def initialize(project:, reader:, writer:, assignment:, commandline_args:, io_socket: nil, buffered_changes: nil, service: nil)
57
60
  super(project: project, reader: reader, writer: writer)
@@ -65,6 +68,7 @@ module Steep
65
68
  @io_socket = io_socket
66
69
  @service = service if service
67
70
  @child_pids = []
71
+ @need_to_warmup = defined?(Process.warmup)
68
72
 
69
73
  if io_socket
70
74
  Signal.trap "SIGCHLD" do
@@ -105,6 +109,9 @@ module Steep
105
109
  when CustomMethods::TypeCheck__Start::METHOD
106
110
  params = request[:params] #: CustomMethods::TypeCheck__Start::params
107
111
  enqueue_typecheck_jobs(params)
112
+ when CustomMethods::Query__Definition::METHOD
113
+ params = request[:params] #: CustomMethods::Query__Definition::params
114
+ queue << QueryDefinitionJob.new(id: request[:id], name: params[:name])
108
115
  when "textDocument/definition"
109
116
  queue << GotoJob.definition(id: request[:id], params: request[:params])
110
117
  when "textDocument/implementation"
@@ -118,6 +125,11 @@ module Steep
118
125
  stdin = io_socket.recv_io
119
126
  stdout = io_socket.recv_io
120
127
 
128
+ if need_to_warmup
129
+ Process.warmup
130
+ @need_to_warmup = false
131
+ end
132
+
121
133
  if pid = fork
122
134
  stdin.close
123
135
  stdout.close
@@ -137,9 +149,11 @@ module Steep
137
149
 
138
150
  worker = self.class.new(project: project, reader: reader, writer: writer, assignment: assignment, commandline_args: commandline_args, io_socket: nil, buffered_changes: buffered_changes, service: service)
139
151
 
140
- tags = Steep.logger.formatter.current_tags.dup
141
- tags[tags.find_index("typecheck:typecheck@0")] = "typecheck:typecheck@#{index}-reforked"
142
- Steep.logger.formatter.push_tags(tags)
152
+ tags = Steep.logger.current_tags.dup
153
+ if (index = tags.find_index("typecheck:typecheck@0"))
154
+ tags[index] = "typecheck:typecheck@#{index}-reforked"
155
+ end
156
+ Steep.logger.push_tags(*tags)
143
157
  worker.run()
144
158
 
145
159
  raise "unreachable"
@@ -165,10 +179,12 @@ module Steep
165
179
  libraries = params[:library_uris].map {|target_name, uri| [targets.fetch(target_name), Steep::PathHelper.to_pathname!(uri)] } #: Array[[Project::Target, Pathname]]
166
180
  signatures = params[:signature_uris].map {|target_name, uri| [targets.fetch(target_name), Steep::PathHelper.to_pathname!(uri)] } #: Array[[Project::Target, Pathname]]
167
181
  codes = params[:code_uris].map {|target_name, uri| [targets.fetch(target_name), Steep::PathHelper.to_pathname!(uri)] } #: Array[[Project::Target, Pathname]]
182
+ inlines = params[:inline_uris].map {|target_name, uri| [targets.fetch(target_name), Steep::PathHelper.to_pathname!(uri)] } #: Array[[Project::Target, Pathname]]
168
183
 
169
184
  priority_libs, non_priority_libs = libraries.partition {|_, path| priority_paths.include?(path) }
170
185
  priority_sigs, non_priority_sigs = signatures.partition {|_, path| priority_paths.include?(path) }
171
186
  priority_codes, non_priority_codes = codes.partition {|_, path| priority_paths.include?(path) }
187
+ priority_inlines, non_priority_inlines = inlines.partition {|_, path| priority_paths.include?(path) }
172
188
 
173
189
  priority_codes.each do |target, path|
174
190
  Steep.logger.info { "Enqueueing TypeCheckCodeJob for guid=#{guid}, path=#{path}, target=#{target.name}" }
@@ -185,6 +201,11 @@ module Steep
185
201
  queue << ValidateLibrarySignatureJob.new(guid: guid, path: path, target: target)
186
202
  end
187
203
 
204
+ priority_inlines.each do |target, path|
205
+ Steep.logger.info { "Enqueueing TypeCheckInlineCodeJob for guid=#{guid}, path=#{path}, target=#{target.name}" }
206
+ queue << TypeCheckInlineCodeJob.new(guid: guid, path: path, target: target)
207
+ end
208
+
188
209
  non_priority_codes.each do |target, path|
189
210
  Steep.logger.info { "Enqueueing TypeCheckCodeJob for guid=#{guid}, path=#{path}, target=#{target.name}" }
190
211
  queue << TypeCheckCodeJob.new(guid: guid, path: path, target: target)
@@ -199,6 +220,11 @@ module Steep
199
220
  Steep.logger.info { "Enqueueing ValidateLibrarySignatureJob for guid=#{guid}, path=#{path}, target=#{target.name}" }
200
221
  queue << ValidateLibrarySignatureJob.new(guid: guid, path: path, target: target)
201
222
  end
223
+
224
+ non_priority_inlines.each do |target, path|
225
+ Steep.logger.info { "Enqueueing TypeCheckInlineCodeJob for guid=#{guid}, path=#{path}, target=#{target.name}" }
226
+ queue << TypeCheckInlineCodeJob.new(guid: guid, path: path, target: target)
227
+ end
202
228
  end
203
229
 
204
230
  def handle_job(job)
@@ -243,6 +269,25 @@ module Steep
243
269
  typecheck_progress(path: job.path, guid: job.guid, target: job.target, diagnostics: diagnostics&.filter_map { formatter.format(_1) })
244
270
  end
245
271
 
272
+ when TypeCheckInlineCodeJob
273
+ if job.guid == current_type_check_guid
274
+ Steep.logger.info { "Processing TypeCheckInlineCodeJob for guid=#{job.guid}, path=#{job.path}, target=#{job.target.name}" }
275
+ group_target = project.group_for_inline_source_path(job.path) || job.target
276
+ formatter = Diagnostic::LSPFormatter.new(group_target.code_diagnostics_config)
277
+ relative_path = project.relative_path(job.path)
278
+ diagnostics = service.typecheck_source(path: relative_path, target: job.target) #: Array[Diagnostic::Ruby::Base | Diagnostic::Signature::Base] | nil
279
+ signature_diagnostics = service.validate_signature(path: relative_path, target: job.target)
280
+ if diagnostics
281
+ diagnostics.concat(signature_diagnostics)
282
+ else
283
+ unless signature_diagnostics.empty?
284
+ diagnostics = signature_diagnostics
285
+ end
286
+ end
287
+
288
+ typecheck_progress(path: job.path, guid: job.guid, target: job.target, diagnostics: diagnostics&.filter_map { formatter.format(_1) })
289
+ end
290
+
246
291
  when WorkspaceSymbolJob
247
292
  writer.write(
248
293
  id: job.id,
@@ -258,6 +303,10 @@ module Steep
258
303
  id: job.id,
259
304
  result: goto(job)
260
305
  )
306
+ when QueryDefinitionJob
307
+ writer.write(
308
+ CustomMethods::Query__Definition.response(job.id, query_definition_result(job.name))
309
+ )
261
310
  end
262
311
  end
263
312
 
@@ -309,6 +358,58 @@ module Steep
309
358
  end
310
359
  end
311
360
 
361
+ def query_definition_result(name_string)
362
+ name = Services::GotoService.parse_name(name_string)
363
+
364
+ kind =
365
+ case name
366
+ when RBS::TypeName
367
+ "type_name"
368
+ when InstanceMethodName
369
+ "instance_method"
370
+ when SingletonMethodName
371
+ "singleton_method"
372
+ else
373
+ "unknown"
374
+ end #: CustomMethods::Query__Definition::kind
375
+
376
+ locations = [] #: Array[CustomMethods::Query__Definition::location]
377
+
378
+ if name
379
+ goto_service = Services::GotoService.new(type_check: service, assignment: assignment)
380
+ goto_service.query_definition(name).each do |loc|
381
+ case loc
382
+ when RBS::Location
383
+ path = Pathname(loc.buffer.name)
384
+ source = "rbs" #: CustomMethods::Query__Definition::source
385
+ if path.extname == ".rb"
386
+ source = "ruby" #: CustomMethods::Query__Definition::source
387
+ end
388
+ path = project.absolute_path(path)
389
+ locations << {
390
+ uri: Steep::PathHelper.to_uri(path).to_s,
391
+ range: loc.as_lsp_range,
392
+ source: source
393
+ }
394
+ else
395
+ path = Pathname(loc.source_buffer.name)
396
+ path = project.absolute_path(path)
397
+ locations << {
398
+ uri: Steep::PathHelper.to_uri(path).to_s,
399
+ range: loc.as_lsp_range,
400
+ source: "ruby"
401
+ }
402
+ end
403
+ end
404
+ end
405
+
406
+ {
407
+ name: name_string,
408
+ kind: kind,
409
+ locations: locations
410
+ }
411
+ end
412
+
312
413
  def goto(job)
313
414
  path = Steep::PathHelper.to_pathname(job.params[:textDocument][:uri]) or return []
314
415
  line = job.params[:position][:line] + 1
@@ -0,0 +1,74 @@
1
+ module Steep
2
+ module Services
3
+ module CompletionProvider
4
+ class RBS
5
+ attr_reader :project, :signature, :path
6
+
7
+ def initialize(path, signature)
8
+ @path = path
9
+ @signature = signature
10
+ end
11
+
12
+ def run(line, column)
13
+ context = nil #: RBS::Resolver::context
14
+
15
+ case signature.status
16
+ when Services::SignatureService::SyntaxErrorStatus, Services::SignatureService::AncestorErrorStatus
17
+ if source = signature.latest_env.each_rbs_source.find { _1.buffer.name == path }
18
+ dirs = source.directives
19
+ else
20
+ dirs = [] #: Array[RBS::AST::Directives::t]
21
+ end
22
+ else
23
+ file = signature.files.fetch(path)
24
+ file.is_a?(Services::SignatureService::RBSFileStatus) or raise
25
+ source = file.source
26
+ source.is_a?(::RBS::Source::RBS) or raise
27
+ buffer = source.buffer
28
+ dirs = source.directives
29
+ decls = source.declarations
30
+
31
+ locator = ::RBS::Locator.new(buffer: buffer, dirs: dirs, decls: decls)
32
+
33
+ _hd, tail = locator.find2(line: line, column: column)
34
+ tail ||= [] #: Array[RBS::Locator::component]
35
+
36
+ tail.reverse_each do |t|
37
+ case t
38
+ when ::RBS::AST::Declarations::Module, ::RBS::AST::Declarations::Class
39
+ if (last_type_name = context&.[](1)).is_a?(::RBS::TypeName)
40
+ context = [context, last_type_name + t.name]
41
+ else
42
+ context = [context, t.name.absolute!]
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ content =
49
+ case file = signature.files.fetch(path)
50
+ when ::RBS::Source::Ruby
51
+ file.buffer.content
52
+ when Services::SignatureService::RBSFileStatus
53
+ file.content
54
+ end
55
+ buffer = ::RBS::Buffer.new(name: path, content: content)
56
+ prefix = Services::CompletionProvider::TypeName::Prefix.parse(buffer, line: line, column: column)
57
+
58
+ completion = Services::CompletionProvider::TypeName.new(env: signature.latest_env, context: context, dirs: dirs)
59
+ type_names = completion.find_type_names(prefix)
60
+ prefix_size = prefix ? prefix.size : 0
61
+
62
+ [
63
+ prefix_size,
64
+ type_names.filter_map do |type_name|
65
+ if (absolute_name, relative_name = completion.resolve_name_in_context(type_name))
66
+ [absolute_name, relative_name]
67
+ end
68
+ end
69
+ ]
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end