typeprof 0.31.1 → 0.32.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/doc/report_guide.md +88 -0
  4. data/lib/typeprof/cli/cli.rb +9 -3
  5. data/lib/typeprof/code_range.rb +7 -5
  6. data/lib/typeprof/core/ast/base.rb +18 -6
  7. data/lib/typeprof/core/ast/call.rb +96 -32
  8. data/lib/typeprof/core/ast/const.rb +12 -9
  9. data/lib/typeprof/core/ast/control.rb +60 -30
  10. data/lib/typeprof/core/ast/meta.rb +194 -2
  11. data/lib/typeprof/core/ast/method.rb +74 -20
  12. data/lib/typeprof/core/ast/misc.rb +27 -7
  13. data/lib/typeprof/core/ast/module.rb +33 -3
  14. data/lib/typeprof/core/ast/sig_decl.rb +85 -24
  15. data/lib/typeprof/core/ast/sig_type.rb +77 -31
  16. data/lib/typeprof/core/ast/value.rb +14 -6
  17. data/lib/typeprof/core/ast/variable.rb +11 -4
  18. data/lib/typeprof/core/ast.rb +95 -14
  19. data/lib/typeprof/core/builtin.rb +184 -12
  20. data/lib/typeprof/core/env/method.rb +171 -6
  21. data/lib/typeprof/core/env/method_entity.rb +18 -15
  22. data/lib/typeprof/core/env/module_entity.rb +56 -18
  23. data/lib/typeprof/core/env/static_read.rb +4 -4
  24. data/lib/typeprof/core/env/type_alias_entity.rb +1 -1
  25. data/lib/typeprof/core/env/value_entity.rb +25 -3
  26. data/lib/typeprof/core/env.rb +79 -17
  27. data/lib/typeprof/core/graph/box.rb +379 -52
  28. data/lib/typeprof/core/graph/change_set.rb +59 -46
  29. data/lib/typeprof/core/graph/filter.rb +8 -5
  30. data/lib/typeprof/core/graph/vertex.rb +20 -19
  31. data/lib/typeprof/core/service.rb +317 -23
  32. data/lib/typeprof/core/type.rb +41 -7
  33. data/lib/typeprof/core/util.rb +6 -0
  34. data/lib/typeprof/lsp/messages.rb +5 -0
  35. data/lib/typeprof/lsp/server.rb +35 -4
  36. data/lib/typeprof/version.rb +1 -1
  37. metadata +3 -2
@@ -7,7 +7,7 @@ module TypeProf::Core
7
7
  @rbs_text_nodes = {}
8
8
 
9
9
  @genv = GlobalEnv.new
10
- @genv.load_core_rbs(load_rbs_declarations(@options[:rbs_collection]).declarations)
10
+ @genv.load_core_rbs(load_rbs_declarations(@options[:rbs_collection]).declarations, @options[:position_encoding])
11
11
 
12
12
  Builtin.new(genv).deploy
13
13
  end
@@ -38,12 +38,12 @@ module TypeProf::Core
38
38
  end
39
39
 
40
40
  def add_workspace(rb_folder, rbs_folder)
41
- Dir.glob(File.expand_path(rb_folder + "/**/*.{rb,rbs}")) do |path|
42
- update_file(path, nil)
43
- end
44
- Dir.glob(File.expand_path(rbs_folder + "/**/*.{rb,rbs}")) do |path|
45
- update_file(path, nil)
46
- end
41
+ # Analyze RBS files first so that type declarations are available during RB type inference
42
+ all_files = [rb_folder, rbs_folder].flat_map { |folder| Dir.glob(File.expand_path(folder + "/**/*.{rb,rbs}")) }
43
+ rbs_files, rb_files = separate_rbs_and_rb(all_files.uniq)
44
+
45
+ rbs_files.each { |path| update_rbs_file(path, nil) }
46
+ rb_files.each { |path| update_rb_file(path, nil) }
47
47
  end
48
48
 
49
49
  def update_file(path, code)
@@ -58,7 +58,7 @@ module TypeProf::Core
58
58
  prev_node = @rb_text_nodes[path]
59
59
 
60
60
  code = File.read(path) unless code
61
- node = AST.parse_rb(path, code)
61
+ node = AST.parse_rb(path, code, @options[:position_encoding])
62
62
  return false unless node
63
63
 
64
64
  node.diff(@rb_text_nodes[path]) if prev_node
@@ -76,13 +76,13 @@ module TypeProf::Core
76
76
  if prev_node
77
77
  live_vtxs = []
78
78
  node.get_vertexes(live_vtxs)
79
- set = Set[]
79
+ set = Set.empty
80
80
  live_vtxs.uniq.each {|vtx| set << vtx }
81
81
  live_vtxs = set
82
82
 
83
83
  dead_vtxs = []
84
84
  prev_node.get_vertexes(dead_vtxs)
85
- set = Set[]
85
+ set = Set.empty
86
86
  dead_vtxs.uniq.each {|vtx| set << vtx }
87
87
  dead_vtxs = set
88
88
 
@@ -93,7 +93,7 @@ module TypeProf::Core
93
93
 
94
94
  global_vtxs = []
95
95
  @genv.get_vertexes(global_vtxs)
96
- set = Set[]
96
+ set = Set.empty
97
97
  global_vtxs.uniq.each {|vtx| set << vtx }
98
98
  global_vtxs = set
99
99
 
@@ -119,7 +119,7 @@ module TypeProf::Core
119
119
 
120
120
  code = File.read(path) unless code
121
121
  begin
122
- decls = AST.parse_rbs(path, code)
122
+ decls = AST.parse_rbs(path, code, @options[:position_encoding])
123
123
  rescue RBS::ParsingError
124
124
  return false
125
125
  end
@@ -203,11 +203,20 @@ module TypeProf::Core
203
203
  if node.ret
204
204
  ty_defs = []
205
205
  node.ret.types.map do |ty, _source|
206
- if ty.is_a?(Type::Instance)
207
- ty.mod.module_decls.each do |mdecl|
208
- # TODO
206
+ mod = case ty
207
+ when Type::Instance, Type::Singleton
208
+ ty.mod
209
+ else
210
+ base = ty.base_type(@genv)
211
+ base.mod if base.is_a?(Type::Instance)
212
+ end
213
+
214
+ if mod
215
+ mod.module_decls.each do |mdecl|
216
+ decl_path = mdecl.lenv.path
217
+ ty_defs << [decl_path, mdecl.code_range] if decl_path
209
218
  end
210
- ty.mod.module_defs.each do |mdef_node|
219
+ mod.module_defs.each do |mdef_node|
211
220
  ty_defs << [mdef_node.lenv.path, mdef_node.code_range]
212
221
  end
213
222
  end
@@ -405,6 +414,45 @@ module TypeProf::Core
405
414
  end
406
415
  end
407
416
 
417
+ def format_declared_const_path(cpath, stack)
418
+ scope_cpath =
419
+ stack.reverse_each.find do |entry|
420
+ (entry.is_a?(AST::ClassNode) || entry.is_a?(AST::ModuleNode)) &&
421
+ entry.static_cpath &&
422
+ !entry.static_cpath.empty?
423
+ end&.static_cpath
424
+
425
+ return cpath.join("::") unless scope_cpath
426
+ return cpath.join("::") unless cpath[0, scope_cpath.size] == scope_cpath
427
+
428
+ rel_cpath = cpath.drop(scope_cpath.size)
429
+ rel_cpath.empty? ? cpath.join("::") : rel_cpath.join("::")
430
+ end
431
+
432
+ def format_superclass_path(node, superclass)
433
+ sc_path = superclass.show_cpath
434
+
435
+ if node.is_a?(AST::ClassNode) && node.superclass_cpath
436
+ sc_node = node.superclass_cpath
437
+ if superclass_source_is_toplevel?(sc_node)
438
+ return "::#{ sc_path }"
439
+ end
440
+
441
+ if node.static_cpath && node.static_cpath.size > 1 &&
442
+ superclass.cpath == [node.static_cpath.last]
443
+ return "::#{ sc_path }"
444
+ end
445
+ end
446
+
447
+ sc_path
448
+ end
449
+
450
+ def superclass_source_is_toplevel?(const_node)
451
+ return false unless const_node.is_a?(AST::ConstantReadNode)
452
+ return true if const_node.toplevel
453
+ const_node.cbase ? superclass_source_is_toplevel?(const_node.cbase) : false
454
+ end
455
+
408
456
  def dump_declarations(path)
409
457
  stack = []
410
458
  out = []
@@ -413,7 +461,7 @@ module TypeProf::Core
413
461
  when AST::ModuleNode
414
462
  if node.static_cpath
415
463
  if event == :enter
416
- out << " " * stack.size + "module #{ node.static_cpath.join("::") }"
464
+ out << " " * stack.size + "module #{ format_declared_const_path(node.static_cpath, stack) }"
417
465
  if stack == [:toplevel]
418
466
  out << "end"
419
467
  stack.pop
@@ -424,18 +472,18 @@ module TypeProf::Core
424
472
  out << " " * stack.size + "end"
425
473
  end
426
474
  end
427
- when AST::ClassNode, AST::SingletonClassNode
475
+ when AST::ClassNode, AST::SingletonClassNode, AST::StructNewNode
428
476
  if node.static_cpath
429
477
  next if stack.any? { node.is_a?(AST::SingletonClassNode) && (_1.is_a?(AST::ClassNode) || _1.is_a?(AST::ModuleNode)) && node.static_cpath == _1.static_cpath }
430
478
 
431
479
  if event == :enter
432
- s = "class #{ node.static_cpath.join("::") }"
480
+ s = "class #{ format_declared_const_path(node.static_cpath, stack) }"
433
481
  mod = @genv.resolve_cpath(node.static_cpath)
434
482
  superclass = mod.superclass
435
483
  if superclass == nil
436
484
  s << " # failed to identify its superclass"
437
485
  elsif superclass.cpath != []
438
- s << " < #{ superclass.show_cpath }"
486
+ s << " < #{ format_superclass_path(node, superclass) }"
439
487
  end
440
488
  if stack == [:toplevel]
441
489
  out << "end"
@@ -448,6 +496,10 @@ module TypeProf::Core
448
496
  out << " " * stack.size + "include #{ inc_mod.show_cpath }"
449
497
  end
450
498
  end
499
+ # Output method definitions from meta nodes (StructNewNode etc.)
500
+ node.boxes(:mdef) do |mdef|
501
+ out << " " * stack.size + "def #{ mdef.singleton ? "self." : "" }#{ mdef.mid }: " + mdef.show(@options[:output_parameter_names])
502
+ end
451
503
  else
452
504
  stack.pop
453
505
  out << " " * stack.size + "end"
@@ -456,7 +508,7 @@ module TypeProf::Core
456
508
  when AST::ConstantWriteNode
457
509
  if node.static_cpath
458
510
  if event == :enter
459
- out << " " * stack.size + "#{ node.static_cpath.join("::") }: #{ node.ret.show }"
511
+ out << " " * stack.size + "#{ format_declared_const_path(node.static_cpath, stack) }: #{ node.ret.show }"
460
512
  end
461
513
  end
462
514
  else
@@ -496,10 +548,14 @@ module TypeProf::Core
496
548
  output.puts
497
549
  end
498
550
 
551
+ # Analyze RBS files first so that type declarations are available during RB type inference
552
+ rbs_files, rb_files = separate_rbs_and_rb(files)
553
+ sorted_files = rbs_files + rb_files
554
+
499
555
  i = 0
500
- show_files = files.select do |file|
556
+ show_files = sorted_files.select do |file|
501
557
  if @options[:display_indicator]
502
- $stderr << "\r[%d/%d] %s\e[K" % [i, files.size, file]
558
+ $stderr << "\r[%d/%d] %s\e[K" % [i, sorted_files.size, file]
503
559
  i += 1
504
560
  end
505
561
 
@@ -532,6 +588,244 @@ module TypeProf::Core
532
588
  end
533
589
  output.puts dump_declarations(file)
534
590
  end
591
+
592
+ if @options[:output_stats]
593
+ rb_files = show_files.reject {|f| File.extname(f) == ".rbs" }
594
+ stats = collect_stats(rb_files)
595
+ output.puts
596
+ output.puts format_stats(stats)
597
+ end
598
+ end
599
+
600
+ def collect_stats(files)
601
+ file_stats = []
602
+
603
+ files.each do |path|
604
+ methods = []
605
+ constants = []
606
+ seen_ivars = Set.empty
607
+ ivars = []
608
+ seen_cvars = Set.empty
609
+ cvars = []
610
+ seen_gvars = Set.empty
611
+ gvars = []
612
+
613
+ @rb_text_nodes[path]&.traverse do |event, node|
614
+ next unless event == :enter
615
+
616
+ node.boxes(:mdef) do |mdef|
617
+ param_slots = []
618
+ f = mdef.f_args
619
+ [f.req_positionals, f.opt_positionals, f.post_positionals, f.req_keywords, f.opt_keywords].each do |ary|
620
+ ary.each {|vtx| param_slots << classify_vertex(vtx) }
621
+ end
622
+ [f.rest_positionals, f.rest_keywords].each do |vtx|
623
+ param_slots << classify_vertex(vtx) if vtx
624
+ end
625
+
626
+ is_initialize = mdef.mid == :initialize
627
+ ret_slots = is_initialize ? [] : [classify_vertex(mdef.ret)]
628
+
629
+ blk = mdef.record_block
630
+ block_param_slots = []
631
+ block_ret_slots = []
632
+ if blk.used
633
+ blk.f_args.each {|vtx| block_param_slots << classify_vertex(vtx) }
634
+ block_ret_slots << classify_vertex(blk.ret)
635
+ end
636
+
637
+ methods << {
638
+ mid: mdef.mid,
639
+ singleton: mdef.singleton,
640
+ param_slots: param_slots,
641
+ ret_slots: ret_slots,
642
+ block_param_slots: block_param_slots,
643
+ block_ret_slots: block_ret_slots,
644
+ }
645
+ end
646
+
647
+ if node.is_a?(AST::ConstantWriteNode) && node.static_cpath
648
+ constants << classify_vertex(node.ret)
649
+ end
650
+
651
+ if node.is_a?(AST::InstanceVariableWriteNode)
652
+ scope = node.lenv.cref.scope_level
653
+ if scope == :class || scope == :instance
654
+ key = [node.lenv.cref.cpath, scope == :class, node.var]
655
+ unless seen_ivars.include?(key)
656
+ seen_ivars << key
657
+ ve = @genv.resolve_ivar(key[0], key[1], key[2])
658
+ ivars << classify_vertex(ve.vtx)
659
+ end
660
+ end
661
+ end
662
+
663
+ if node.is_a?(AST::ClassVariableWriteNode)
664
+ key = [node.lenv.cref.cpath, node.var]
665
+ unless seen_cvars.include?(key)
666
+ seen_cvars << key
667
+ ve = @genv.resolve_cvar(key[0], key[1])
668
+ cvars << classify_vertex(ve.vtx)
669
+ end
670
+ end
671
+
672
+ if node.is_a?(AST::GlobalVariableWriteNode)
673
+ unless seen_gvars.include?(node.var)
674
+ seen_gvars << node.var
675
+ ve = @genv.resolve_gvar(node.var)
676
+ gvars << classify_vertex(ve.vtx)
677
+ end
678
+ end
679
+ end
680
+
681
+ file_stats << {
682
+ path: path,
683
+ methods: methods,
684
+ constants: constants,
685
+ ivars: ivars,
686
+ cvars: cvars,
687
+ gvars: gvars,
688
+ }
689
+ end
690
+
691
+ file_stats
692
+ end
693
+
694
+ def classify_vertex(vtx)
695
+ vtx.types.empty? ? :untyped : :typed
696
+ end
697
+
698
+ def format_stats(stats)
699
+ total_methods = 0
700
+ fully_typed = 0
701
+ partially_typed = 0
702
+ fully_untyped = 0
703
+
704
+ slot_categories = %i[param ret blk_param blk_ret const ivar cvar gvar]
705
+ typed = Hash.new(0)
706
+ untyped = Hash.new(0)
707
+
708
+ file_summaries = []
709
+
710
+ stats.each do |file|
711
+ f_typed = 0
712
+ f_total = 0
713
+
714
+ file[:methods].each do |m|
715
+ total_methods += 1
716
+
717
+ method_slot_keys = %i[param_slots ret_slots block_param_slots block_ret_slots]
718
+ category_keys = %i[param ret blk_param blk_ret]
719
+
720
+ all_slots = method_slot_keys.flat_map {|k| m[k] }
721
+
722
+ method_slot_keys.zip(category_keys) do |slot_key, cat|
723
+ m[slot_key].each do |s|
724
+ if s == :typed
725
+ typed[cat] += 1
726
+ else
727
+ untyped[cat] += 1
728
+ end
729
+ end
730
+ end
731
+
732
+ if all_slots.empty? || all_slots.all? {|s| s == :typed }
733
+ fully_typed += 1
734
+ elsif all_slots.none? {|s| s == :typed }
735
+ fully_untyped += 1
736
+ else
737
+ partially_typed += 1
738
+ end
739
+
740
+ f_typed += all_slots.count(:typed)
741
+ f_total += all_slots.size
742
+ end
743
+
744
+ %i[constants ivars cvars gvars].zip(%i[const ivar cvar gvar]) do |data_key, cat|
745
+ file[data_key].each do |s|
746
+ f_total += 1
747
+ if s == :typed
748
+ typed[cat] += 1
749
+ f_typed += 1
750
+ else
751
+ untyped[cat] += 1
752
+ end
753
+ end
754
+ end
755
+
756
+ if f_total > 0
757
+ file_summaries << {
758
+ path: file[:path],
759
+ methods: file[:methods].size,
760
+ typed: f_typed,
761
+ total: f_total,
762
+ }
763
+ end
764
+ end
765
+
766
+ overall_typed = slot_categories.sum {|c| typed[c] }
767
+ overall_untyped = slot_categories.sum {|c| untyped[c] }
768
+ overall_total = overall_typed + overall_untyped
769
+
770
+ labels = {
771
+ param: "Parameter slots",
772
+ ret: "Return slots",
773
+ blk_param: "Block parameter slots",
774
+ blk_ret: "Block return slots",
775
+ const: "Constants",
776
+ ivar: "Instance variables",
777
+ cvar: "Class variables",
778
+ gvar: "Global variables",
779
+ }
780
+
781
+ lines = []
782
+ lines << "# TypeProf Evaluation Statistics"
783
+ lines << "#"
784
+ lines << "# Total methods: #{ total_methods }"
785
+ lines << "# Fully typed: #{ fully_typed }"
786
+ lines << "# Partially typed: #{ partially_typed }"
787
+ lines << "# Fully untyped: #{ fully_untyped }"
788
+
789
+ slot_categories.each do |cat|
790
+ total = typed[cat] + untyped[cat]
791
+ lines << "#"
792
+ lines << "# #{ labels[cat] }: #{ total }"
793
+ lines << "# Typed: #{ typed[cat] } (#{ pct(typed[cat], total) })"
794
+ lines << "# Untyped: #{ untyped[cat] } (#{ pct(untyped[cat], total) })"
795
+ end
796
+
797
+ lines << "#"
798
+ lines << "# Overall: #{ overall_typed }/#{ overall_total } typed (#{ pct(overall_typed, overall_total) })"
799
+ lines << "# #{ overall_untyped }/#{ overall_total } untyped (#{ pct(overall_untyped, overall_total) })"
800
+
801
+ if file_summaries.size > 1
802
+ lines << "#"
803
+ lines << "# Per-file breakdown:"
804
+ file_summaries.each do |fs|
805
+ lines << "# #{ fs[:path] }: #{ fs[:methods] } methods, #{ fs[:typed] }/#{ fs[:total] } typed (#{ pct(fs[:typed], fs[:total]) })"
806
+ end
807
+ end
808
+
809
+ lines.join("\n")
810
+ end
811
+
812
+ def pct(n, total)
813
+ return "0.0%" if total == 0
814
+ "#{ (n * 100.0 / total).round(1) }%"
815
+ end
816
+
817
+ private
818
+
819
+ def separate_rbs_and_rb(files)
820
+ files
821
+ .reject { |file| exclude_files.include?(File.expand_path(file)) }
822
+ .partition { |file| File.extname(file) == ".rbs" }
823
+ end
824
+
825
+ def exclude_files
826
+ @exclude_files ||= (@options[:exclude_patterns] || []).each_with_object(::Set.new) { |pattern, set|
827
+ Dir.glob(File.expand_path(pattern)) { |path| set << path }
828
+ }
535
829
  end
536
830
  end
537
831
  end
@@ -16,16 +16,33 @@ module TypeProf::Core
16
16
  end
17
17
 
18
18
  def self.extract_hash_value_type(s)
19
- if s.start_with?("Hash[") && s.end_with?("]")
19
+ begin
20
20
  type = RBS::Parser.parse_type(s)
21
+ collect_hash_value_types(type).uniq.join(" | ")
22
+ rescue
23
+ s
24
+ end
25
+ end
21
26
 
22
- if type.is_a?(RBS::Types::Union)
23
- type.types.map {|t| t.args[1].to_s }.join(" | ")
27
+ # Returns an array of value type strings from hash-like types
28
+ def self.collect_hash_value_types(type)
29
+ case type
30
+ when RBS::Types::Record
31
+ # Extract value types from record fields
32
+ # all_fields returns { key => [type, required] }
33
+ type.all_fields.values.map { |t, _required| t.to_s }
34
+ when RBS::Types::ClassInstance
35
+ # Handle Hash[K, V]
36
+ if type.name.name == :Hash && type.args.size == 2
37
+ [type.args[1].to_s]
24
38
  else
25
- type.args[1].to_s
39
+ [type.to_s]
26
40
  end
41
+ when RBS::Types::Union
42
+ # Handle union of types - extract value types from all hash-like types
43
+ type.types.flat_map { |t| collect_hash_value_types(t) }
27
44
  else
28
- s
45
+ [type.to_s]
29
46
  end
30
47
  end
31
48
 
@@ -60,7 +77,7 @@ module TypeProf::Core
60
77
 
61
78
  def get_instance_type(genv)
62
79
  params = @mod.type_params
63
- Instance.new(genv, @mod, params ? params.map { Source.new } : [])
80
+ Instance.new(genv, @mod, params ? params.map { Source.new } : []) # TODO: respect param_default_types
64
81
  end
65
82
  end
66
83
 
@@ -188,7 +205,24 @@ module TypeProf::Core
188
205
  end
189
206
 
190
207
  def show
191
- "<Proc>"
208
+ "Proc"
209
+ end
210
+ end
211
+
212
+ class Method < Type
213
+ def initialize(genv, recv_ty, mid)
214
+ @recv_ty = recv_ty
215
+ @mid = mid
216
+ end
217
+
218
+ attr_reader :recv_ty, :mid
219
+
220
+ def base_type(genv)
221
+ genv.method_type
222
+ end
223
+
224
+ def show
225
+ "Method"
192
226
  end
193
227
  end
194
228
 
@@ -6,6 +6,10 @@ module TypeProf::Core
6
6
  new(h)
7
7
  end
8
8
 
9
+ def self.empty
10
+ new(Hash.new(false))
11
+ end
12
+
9
13
  def initialize(hash)
10
14
  @hash = hash
11
15
  end
@@ -77,5 +81,7 @@ module TypeProf::Core
77
81
  end
78
82
  q.text "]"
79
83
  end
84
+
85
+ EMPTY = empty.freeze
80
86
  end
81
87
  end
@@ -56,6 +56,10 @@ module TypeProf::LSP
56
56
  class Message::Initialize < Message
57
57
  METHOD = "initialize" # request (required)
58
58
  def run
59
+ # Must negotiate encoding before add_workspaces so newly created Services honor it.
60
+ client_encodings = @params.dig(:capabilities, :general, :positionEncodings)
61
+ @server.negotiate_position_encoding(client_encodings)
62
+
59
63
  folders = @params[:workspaceFolders].map do |folder|
60
64
  folder => { uri:, }
61
65
  @server.uri_to_path(uri)
@@ -65,6 +69,7 @@ module TypeProf::LSP
65
69
 
66
70
  respond(
67
71
  capabilities: {
72
+ positionEncoding: @server.lsp_position_encoding,
68
73
  textDocumentSync: {
69
74
  openClose: true,
70
75
  change: 2, # Incremental
@@ -21,8 +21,8 @@ module TypeProf::LSP
21
21
  new(core_options, reader, writer).run
22
22
  end
23
23
 
24
- def self.start_socket(core_options)
25
- Socket.tcp_server_sockets("localhost", nil) do |servs|
24
+ def self.start_socket(core_options, port = 0)
25
+ Socket.tcp_server_sockets("localhost", port) do |servs|
26
26
  serv = servs[0].local_address
27
27
  $stdout << JSON.generate({
28
28
  host: serv.ip_address,
@@ -47,6 +47,15 @@ module TypeProf::LSP
47
47
  end
48
48
  end
49
49
 
50
+ # see: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocuments
51
+ LSP_POSITION_ENCODINGS = {
52
+ Encoding::UTF_8 => "utf-8",
53
+ Encoding::UTF_16LE => "utf-16",
54
+ Encoding::UTF_32LE => "utf-32",
55
+ }.freeze
56
+
57
+ private_constant :LSP_POSITION_ENCODINGS
58
+
50
59
  def initialize(core_options, reader, writer, url_schema: nil)
51
60
  @core_options = core_options
52
61
  @cores = {}
@@ -62,9 +71,29 @@ module TypeProf::LSP
62
71
  @diagnostic_severity = :error
63
72
  end
64
73
 
65
- attr_reader :open_texts
74
+ attr_reader :open_texts, :position_encoding
66
75
  attr_accessor :signature_enabled
67
76
 
77
+ # Pick the first mutually-supported encoding from the client's preference-ordered list
78
+ # and store it. Falls back to UTF-16LE (mandatory per LSP 3.17 spec).
79
+ def negotiate_position_encoding(client_encodings)
80
+ @position_encoding = pick_position_encoding(client_encodings)
81
+ end
82
+
83
+ def lsp_position_encoding
84
+ LSP_POSITION_ENCODINGS.fetch(@position_encoding)
85
+ end
86
+
87
+ def pick_position_encoding(client_encodings)
88
+ return Encoding::UTF_16LE unless client_encodings.is_a?(Array)
89
+ client_encodings.each do |enc|
90
+ encoding = LSP_POSITION_ENCODINGS.key(enc)
91
+ return encoding if encoding
92
+ end
93
+ Encoding::UTF_16LE
94
+ end
95
+ private :pick_position_encoding
96
+
68
97
  #: (String) -> String
69
98
  def path_to_uri(path)
70
99
  @url_schema + File.expand_path(path).split("/").map {|s| CGI.escapeURIComponent(s) }.join("/")
@@ -104,9 +133,11 @@ module TypeProf::LSP
104
133
  puts "unknown severity: #{ severity }"
105
134
  end
106
135
  end
136
+ @core_options[:exclude_patterns] = conf[:exclude] if conf[:exclude]
137
+ service_options = @core_options.merge(position_encoding: @position_encoding)
107
138
  conf[:analysis_unit_dirs].each do |dir|
108
139
  dir = File.expand_path(dir, path)
109
- core = @cores[dir] = TypeProf::Core::Service.new(@core_options)
140
+ core = @cores[dir] = TypeProf::Core::Service.new(service_options)
110
141
  core.add_workspace(dir, @rbs_dir)
111
142
  end
112
143
  else
@@ -1,3 +1,3 @@
1
1
  module TypeProf
2
- VERSION = "0.31.1"
2
+ VERSION = "0.32.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typeprof
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.31.1
4
+ version: 0.32.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke Endoh
@@ -53,6 +53,7 @@ files:
53
53
  - bin/typeprof
54
54
  - doc/doc.ja.md
55
55
  - doc/doc.md
56
+ - doc/report_guide.md
56
57
  - lib/typeprof.rb
57
58
  - lib/typeprof/cli.rb
58
59
  - lib/typeprof/cli/cli.rb
@@ -117,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
118
  - !ruby/object:Gem::Version
118
119
  version: '0'
119
120
  requirements: []
120
- rubygems_version: 3.6.9
121
+ rubygems_version: 4.0.10
121
122
  specification_version: 4
122
123
  summary: TypeProf is a type analysis tool for Ruby code based on abstract interpretation
123
124
  test_files: []