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.
- checksums.yaml +4 -4
- data/README.md +2 -1
- data/doc/report_guide.md +88 -0
- data/lib/typeprof/cli/cli.rb +9 -3
- data/lib/typeprof/code_range.rb +7 -5
- data/lib/typeprof/core/ast/base.rb +18 -6
- data/lib/typeprof/core/ast/call.rb +96 -32
- data/lib/typeprof/core/ast/const.rb +12 -9
- data/lib/typeprof/core/ast/control.rb +60 -30
- data/lib/typeprof/core/ast/meta.rb +194 -2
- data/lib/typeprof/core/ast/method.rb +74 -20
- data/lib/typeprof/core/ast/misc.rb +27 -7
- data/lib/typeprof/core/ast/module.rb +33 -3
- data/lib/typeprof/core/ast/sig_decl.rb +85 -24
- data/lib/typeprof/core/ast/sig_type.rb +77 -31
- data/lib/typeprof/core/ast/value.rb +14 -6
- data/lib/typeprof/core/ast/variable.rb +11 -4
- data/lib/typeprof/core/ast.rb +95 -14
- data/lib/typeprof/core/builtin.rb +184 -12
- data/lib/typeprof/core/env/method.rb +171 -6
- data/lib/typeprof/core/env/method_entity.rb +18 -15
- data/lib/typeprof/core/env/module_entity.rb +56 -18
- data/lib/typeprof/core/env/static_read.rb +4 -4
- data/lib/typeprof/core/env/type_alias_entity.rb +1 -1
- data/lib/typeprof/core/env/value_entity.rb +25 -3
- data/lib/typeprof/core/env.rb +79 -17
- data/lib/typeprof/core/graph/box.rb +379 -52
- data/lib/typeprof/core/graph/change_set.rb +59 -46
- data/lib/typeprof/core/graph/filter.rb +8 -5
- data/lib/typeprof/core/graph/vertex.rb +20 -19
- data/lib/typeprof/core/service.rb +317 -23
- data/lib/typeprof/core/type.rb +41 -7
- data/lib/typeprof/core/util.rb +6 -0
- data/lib/typeprof/lsp/messages.rb +5 -0
- data/lib/typeprof/lsp/server.rb +35 -4
- data/lib/typeprof/version.rb +1 -1
- 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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
556
|
+
show_files = sorted_files.select do |file|
|
|
501
557
|
if @options[:display_indicator]
|
|
502
|
-
$stderr << "\r[%d/%d] %s\e[K" % [i,
|
|
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
|
data/lib/typeprof/core/type.rb
CHANGED
|
@@ -16,16 +16,33 @@ module TypeProf::Core
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def self.extract_hash_value_type(s)
|
|
19
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
|
data/lib/typeprof/core/util.rb
CHANGED
|
@@ -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
|
data/lib/typeprof/lsp/server.rb
CHANGED
|
@@ -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",
|
|
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(
|
|
140
|
+
core = @cores[dir] = TypeProf::Core::Service.new(service_options)
|
|
110
141
|
core.add_workspace(dir, @rbs_dir)
|
|
111
142
|
end
|
|
112
143
|
else
|
data/lib/typeprof/version.rb
CHANGED
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.
|
|
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:
|
|
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: []
|