steep 1.10.0.pre.3 → 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 +87 -0
  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
@@ -6,6 +6,7 @@ module Steep
6
6
  attr_reader :library_paths
7
7
  attr_reader :signature_paths
8
8
  attr_reader :code_paths
9
+ attr_reader :inline_paths
9
10
  attr_reader :priority_paths
10
11
  attr_reader :checked_paths
11
12
  attr_reader :work_done_progress
@@ -18,6 +19,7 @@ module Steep
18
19
  @library_paths = Set[]
19
20
  @signature_paths = Set[]
20
21
  @code_paths = Set[]
22
+ @inline_paths = Set[]
21
23
  @priority_paths = Set[]
22
24
  @checked_paths = Set[]
23
25
  @work_done_progress = progress
@@ -41,6 +43,7 @@ module Steep
41
43
  library_uris: assigned_uris(assignment, library_paths),
42
44
  signature_uris: assigned_uris(assignment, signature_paths),
43
45
  code_uris: assigned_uris(assignment, code_paths),
46
+ inline_uris: assigned_uris(assignment, inline_paths),
44
47
  priority_uris: priority_paths.map {|path| uri(path).to_s }
45
48
  }
46
49
  end
@@ -54,7 +57,7 @@ module Steep
54
57
  end
55
58
 
56
59
  def total
57
- library_paths.size + signature_paths.size + code_paths.size
60
+ library_paths.size + signature_paths.size + code_paths.size + inline_paths.size
58
61
  end
59
62
 
60
63
  def empty?
@@ -80,13 +83,14 @@ module Steep
80
83
  library_paths.each(&block)
81
84
  signature_paths.each(&block)
82
85
  code_paths.each(&block)
86
+ inline_paths.each(&block)
83
87
  else
84
88
  enum_for :each_target_path
85
89
  end
86
90
  end
87
91
 
88
92
  def checking_path?(target_path)
89
- [library_paths, signature_paths, code_paths].any? do |paths|
93
+ [library_paths, signature_paths, code_paths, inline_paths].any? do |paths|
90
94
  paths.include?(target_path)
91
95
  end
92
96
  end
@@ -160,25 +164,47 @@ module Steep
160
164
  end
161
165
  end
162
166
 
167
+ def each_unchecked_inline_target_path(&block)
168
+ if block
169
+ inline_paths.each do |target_path|
170
+ unless checked_paths.include?(target_path)
171
+ yield target_path
172
+ end
173
+ end
174
+ else
175
+ enum_for :each_unchecked_inline_target_path
176
+ end
177
+ end
178
+
163
179
  def merge!(request)
164
180
  library_paths.merge(request.each_unchecked_library_target_path)
165
181
  signature_paths.merge(request.each_unchecked_signature_target_path)
166
182
  code_paths.merge(request.each_unchecked_code_target_path)
183
+ inline_paths.merge(request.each_unchecked_inline_target_path)
167
184
 
168
185
  self
169
186
  end
170
187
  end
171
188
 
172
189
  attr_reader :project
173
- attr_reader :priority_paths
174
- attr_reader :changed_paths
190
+ attr_reader :open_paths
191
+ attr_reader :active_groups
192
+ attr_reader :new_active_groups
193
+ attr_reader :dirty_code_paths, :dirty_signature_paths, :dirty_inline_paths
175
194
  attr_reader :files
195
+ attr_reader :inline_path_changes
176
196
 
177
197
  def initialize(project:)
178
198
  @project = project
179
- @priority_paths = Set[]
180
- @changed_paths = Set[]
199
+
181
200
  @files = TargetGroupFiles.new(project)
201
+ @open_paths = Set[]
202
+ @active_groups = Set[].compare_by_identity
203
+ @new_active_groups = Set[].compare_by_identity
204
+ @dirty_code_paths = Set[]
205
+ @dirty_signature_paths = Set[]
206
+ @dirty_inline_paths = Set[]
207
+ @inline_path_changes = InlineSourceChangeDetector.new
182
208
  end
183
209
 
184
210
  def load(command_line_args:)
@@ -193,170 +219,297 @@ module Steep
193
219
 
194
220
  project.targets.each do |target|
195
221
  loader.each_path_in_target(target, command_line_args) do |path|
196
- path = project.absolute_path(path)
197
- self.files.add_path(path)
198
- files[project.relative_path(path).to_s] = path.read
222
+ absolute_path = project.absolute_path(path)
223
+ self.files.add_path(absolute_path)
224
+ content = absolute_path.read
225
+ files[path.to_s] = content
199
226
  if files.size > 1000
200
227
  yield files.dup
201
228
  files.clear
202
229
  end
230
+
231
+ if inline_path?(path)
232
+ inline_path_changes.add_source(path, content)
233
+ end
203
234
  end
204
235
  end
205
236
 
206
- changed_paths.merge(self.files.each_project_signature_path(nil))
207
- changed_paths.merge(self.files.each_project_source_path(nil))
208
-
209
237
  yield files.dup unless files.empty?
210
238
  end
211
239
 
212
- def push_changes(path)
240
+ def code_path?(path)
241
+ files.source_paths.registered_path?(path) || project.target_for_source_path(path) != nil
242
+ end
243
+
244
+ def signature_path?(path)
245
+ files.signature_paths.registered_path?(path) || project.target_for_signature_path(path) != nil
246
+ end
247
+
248
+ def inline_path?(path)
249
+ Steep.logger.debug { {
250
+ path: path,
251
+ inline: files.inline_paths.paths.map(&:to_s),
252
+ target: project.target_for_inline_source_path(path)&.name
253
+ }.inspect }
254
+ files.inline_paths.registered_path?(path) || project.target_for_inline_source_path(path) != nil
255
+ end
256
+
257
+ def add_dirty_code_path(path)
258
+ return if files.library_path?(path)
259
+ if code_path?(path)
260
+ files.add_path(path)
261
+ dirty_code_paths << path
262
+ end
263
+ end
264
+
265
+ def add_dirty_signature_path(path)
266
+ return if files.library_path?(path)
267
+ if signature_path?(path)
268
+ files.add_path(path)
269
+ dirty_signature_paths << path
270
+ end
271
+ end
272
+
273
+ def add_dirty_inline_path(path, update)
213
274
  return if files.library_path?(path)
275
+ if inline_path?(path)
276
+ files.add_path(path)
277
+ dirty_inline_paths << path
214
278
 
215
- if files.add_path(path)
216
- changed_paths << path
279
+ unless inline_path_changes.has_source?(path)
280
+ inline_path_changes.add_source(path, "")
281
+ end
282
+
283
+ if update.is_a?(String)
284
+ inline_path_changes.replace_source(path, update)
285
+ else
286
+ inline_path_changes.accumulate_change(path, update)
287
+ end
217
288
  end
218
289
  end
219
290
 
220
- def active_target?(target_group)
221
- priority_paths.any? do |path|
222
- if open_target = files.signature_paths.fetch(path, nil) || files.source_paths.fetch(path, nil)
223
- open_target == target_group
291
+ def open_inline_path(path, content)
292
+ return if files.library_path?(path)
293
+
294
+ if inline_path?(path)
295
+ open_path(path)
296
+
297
+ if inline_path_changes.has_source?(path)
298
+ inline_path_changes.replace_source(path, content)
299
+ else
300
+ inline_path_changes.add_source(path, content)
224
301
  end
225
302
  end
226
303
  end
227
304
 
228
- def push_changes_for_target(target_group)
229
- files.each_group_signature_path(target_group) do |path|
230
- push_changes path
305
+ def each_dirty_path(&block)
306
+ if block
307
+ dirty_code_paths.each(&block)
308
+ dirty_signature_paths.each(&block)
309
+ dirty_inline_paths.each(&block)
310
+ else
311
+ enum_for(:each_dirty_path)
231
312
  end
313
+ end
314
+
315
+ def active_group?(group)
316
+ active_groups.include?(group)
317
+ end
232
318
 
233
- files.each_group_source_path(target_group) do |path|
234
- push_changes path
319
+ def each_active_group(&block)
320
+ if block
321
+ active_groups.each(&block)
322
+ else
323
+ enum_for(_ = __method__)
235
324
  end
236
325
  end
237
326
 
238
- def update_priority(open: nil, close: nil)
239
- path = open || close or raise
327
+ def unreferenced?(group)
328
+ group = group.target if group.is_a?(Project::Group)
329
+ group.unreferenced
330
+ end
240
331
 
241
- return if files.library_path?(path)
242
- files.add_path(path)
332
+ def reset()
333
+ dirty_code_paths.clear()
334
+ dirty_signature_paths.clear()
335
+ dirty_inline_paths.clear()
336
+ new_active_groups.clear()
337
+ inline_path_changes.reset
338
+ end
339
+
340
+ def open_path(path)
341
+ return if open_paths.include?(path)
342
+
343
+ files.add_path(path) or return
344
+
345
+ if group = group_of(path)
346
+ open_paths << path
347
+
348
+ unless active_groups.include?(group)
349
+ new_active_groups << group
350
+ active_groups << group
351
+ end
352
+ end
353
+ end
354
+
355
+ def close_path(path)
356
+ if open_paths.include?(path)
357
+ closed_path_group = group_of(path) or raise
358
+
359
+ open_paths.delete(path)
360
+
361
+ if open_paths.none? { group_of(_1) == closed_path_group }
362
+ active_groups.delete(closed_path_group)
363
+ new_active_groups.delete(closed_path_group)
364
+ end
365
+ end
366
+ end
243
367
 
244
- case
245
- when open
246
- target_group = files.signature_paths.fetch(path, nil) || files.source_paths.fetch(path, nil) or return
368
+ def group_of(path)
369
+ files.signature_paths[path] ||
370
+ files.source_paths[path] ||
371
+ files.inline_paths[path]
372
+ end
247
373
 
248
- unless active_target?(target_group)
249
- push_changes_for_target(target_group)
374
+ def target_of(path)
375
+ if group = group_of(path)
376
+ if group.is_a?(Project::Group)
377
+ group.target
378
+ else
379
+ group
250
380
  end
251
- priority_paths << path
252
- when close
253
- priority_paths.delete path
254
381
  end
255
382
  end
256
383
 
257
- def make_group_request(groups, progress:)
384
+ def make_group_request(groups, guid: SecureRandom.uuid, progress:)
258
385
  TypeCheckController::Request.new(guid: progress.guid, progress: progress).tap do |request|
259
- if groups.empty?
260
- files.signature_paths.each do |path, target_group|
261
- target_group = target_group.target if target_group.is_a?(Project::Group)
262
- request.signature_paths << [target_group.name, path]
263
- end
264
- files.source_paths.each do |path, target_group|
265
- target_group = target_group.target if target_group.is_a?(Project::Group)
266
- request.code_paths << [target_group.name, path]
386
+ raise "At least one group/target must be specified" if groups.empty?
387
+
388
+ name_group_map = {} #: Hash[String, group]
389
+
390
+ project.targets.each do |target|
391
+ name_group_map[target.name.to_s] = target
392
+
393
+ target.groups.each do |group|
394
+ name_group_map["#{target.name}.#{group.name}"] = group
267
395
  end
268
- else
269
- group_set = groups.filter_map do |group_name|
270
- target_name, group_name = group_name.split(".", 2)
271
- target_name or raise
272
-
273
- target_name = target_name.to_sym
274
- group_name = group_name.to_sym if group_name
275
-
276
- if group_name
277
- if target = project.targets.find {|target| target.name == target_name }
278
- target.groups.find {|group| group.name == group_name }
279
- end
280
- else
281
- project.targets.find {|target| target.name == target_name }
282
- end
283
- end.to_set
396
+ end
284
397
 
285
- files.signature_paths.each do |path, target_group|
286
- if group_set.include?(target_group)
287
- target_group = target_group.target if target_group.is_a?(Project::Group)
288
- request.signature_paths << [target_group.name, path]
289
- end
398
+ groups.each do |group|
399
+ type_check_group = name_group_map.fetch(group)
400
+
401
+ new_active_groups.delete(type_check_group)
402
+
403
+ files.signature_paths.each_group_path(type_check_group) do |path, target|
404
+ request.signature_paths << [target.name, path]
405
+ dirty_signature_paths.delete(path)
290
406
  end
291
- files.source_paths.each do |path, target_group|
292
- if group_set.include?(target_group)
293
- target_group = target_group.target if target_group.is_a?(Project::Group)
294
- request.code_paths << [target_group.name, path]
295
- end
407
+ files.inline_paths.each_group_path(type_check_group) do |path, target|
408
+ request.inline_paths << [target.name, path]
409
+ dirty_inline_paths.delete(path)
410
+ end
411
+ files.source_paths.each_group_path(type_check_group) do |path, target|
412
+ request.code_paths << [target.name, path]
413
+ dirty_code_paths.delete(path)
296
414
  end
297
415
  end
416
+
417
+ request.priority_paths.merge(open_paths)
298
418
  end
299
419
  end
300
420
 
301
- def make_request(guid: SecureRandom.uuid, include_unchanged: false, progress:)
421
+ def make_all_request(guid: SecureRandom.uuid, progress:)
302
422
  TypeCheckController::Request.new(guid: guid, progress: progress).tap do |request|
303
- if include_unchanged
304
- files.signature_paths.each do |path, target_group|
305
- target_group = target_group.target if target_group.is_a?(Project::Group)
306
- request.signature_paths << [target_group.name, path]
307
- end
308
- files.source_paths.each do |path, target_group|
309
- target_group = target_group.target if target_group.is_a?(Project::Group)
310
- request.code_paths << [target_group.name, path]
311
- end
312
- else
313
- changed_paths.each do |path|
314
- if target_group = files.signature_paths.fetch(path, nil)
315
- case target_group
316
- when Project::Group
317
- target = target_group.target
318
-
319
- files.each_group_signature_path(target_group) do |path|
320
- request.signature_paths << [target.name, path]
321
- end
322
-
323
- files.each_group_source_path(target_group) do |path|
324
- request.code_paths << [target.name, path]
325
- end
326
- when Project::Target
327
- files.each_target_signature_path(target_group, nil) do |path|
328
- request.signature_paths << [target_group.name, path]
329
- end
330
-
331
- files.each_target_source_path(target_group, nil) do |path|
332
- request.code_paths << [target_group.name, path]
333
- end
334
- end
335
- end
423
+ files.signature_paths.each do |path, target|
424
+ request.signature_paths << [target.name, path]
425
+ end
426
+ files.source_paths.each do |path, target|
427
+ request.code_paths << [target.name, path]
428
+ end
429
+ files.inline_paths.each do |path, target|
430
+ request.inline_paths << [target.name, path]
431
+ end
336
432
 
337
- if target = files.source_path_target(path)
338
- request.code_paths << [target.name, path]
339
- end
433
+ request.priority_paths.merge(open_paths)
434
+
435
+ reset()
436
+ end
437
+ end
438
+
439
+ def make_request(guid: SecureRandom.uuid, progress:)
440
+ TypeCheckController::Request.new(guid: guid, progress: progress).tap do |request|
441
+ code_paths = Set[] #: Set[[group, Pathname]]
442
+ signature_paths = Set[] #: Set[[group, Pathname]]
443
+ inline_paths = Set[] #: Set[[group, Pathname]]
444
+
445
+ dirty_code_paths.each do |path|
446
+ group = files.source_paths[path] or raise "#{path} is not a code path"
447
+ code_paths << [group, path]
448
+ end
449
+
450
+ dirty_signature_paths.each do |path|
451
+ group = files.signature_paths[path] or raise "#{path} is not a signature path"
452
+ signature_paths << [group, path]
453
+ end
454
+
455
+ dirty_inline_paths.each do |path|
456
+ group = files.inline_paths[path] or raise "#{path} is not an inline path"
457
+ inline_paths << [group, path]
458
+ end
459
+
460
+ signature_updated_groups = Set[] #: Set[group]
461
+
462
+ signature_paths.each do |group, path|
463
+ signature_updated_groups << group
464
+ end
465
+
466
+ updated_inline_paths = inline_path_changes.type_updated_paths(dirty_inline_paths)
467
+ inline_paths.each do |group, path|
468
+ if updated_inline_paths.include?(path)
469
+ signature_updated_groups << group
340
470
  end
471
+ end
341
472
 
342
- unless request.signature_paths.empty?
343
- non_unref_targets = project.targets.reject { _1.unreferenced }.map(&:name).to_set
344
- if request.signature_paths.any? {|target_name, _| non_unref_targets.include?(target_name) }
345
- priority_paths.each do |path|
346
- if target = files.signature_path_target(path)
347
- request.signature_paths << [target.name, path]
348
- request.priority_paths << path
349
- end
350
- if target = files.source_path_target(path)
351
- request.code_paths << [target.name, path]
352
- request.priority_paths << path
353
- end
354
- end
355
- end
473
+ type_check_groups = Set[] #: Set[group]
474
+
475
+ type_check_groups.merge(signature_updated_groups)
476
+
477
+ unless signature_updated_groups.all? { unreferenced?(_1) }
478
+ type_check_groups.merge(active_groups)
479
+ end
480
+
481
+ type_check_groups.merge(new_active_groups)
482
+
483
+ type_check_groups.each do |group|
484
+ files.signature_paths.each_group_path(group) do |path, target|
485
+ signature_paths << [target, path]
486
+ end
487
+ files.inline_paths.each_group_path(group) do |path, target|
488
+ inline_paths << [target, path]
489
+ end
490
+ files.source_paths.each_group_path(group) do |path, target|
491
+ code_paths << [target, path]
356
492
  end
357
493
  end
358
494
 
359
- changed_paths.clear()
495
+ signature_paths.each do |_, path|
496
+ target = target_of(path) or raise
497
+ request.signature_paths << [target.name, path]
498
+ end
499
+
500
+ inline_paths.each do |_, path|
501
+ target = target_of(path) or raise
502
+ request.inline_paths << [target.name, path]
503
+ end
504
+
505
+ code_paths.each do |_, path|
506
+ target = target_of(path) or raise
507
+ request.code_paths << [target.name, path]
508
+ end
509
+
510
+ request.priority_paths.merge(open_paths)
511
+
512
+ reset()
360
513
 
361
514
  return nil if request.empty?
362
515
  end