typeprof 0.31.1 → 0.33.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 +31 -6
- data/lib/typeprof/core/ast/call.rb +120 -39
- data/lib/typeprof/core/ast/const.rb +22 -11
- data/lib/typeprof/core/ast/control.rb +62 -32
- data/lib/typeprof/core/ast/meta.rb +279 -14
- data/lib/typeprof/core/ast/method.rb +82 -24
- data/lib/typeprof/core/ast/misc.rb +41 -12
- data/lib/typeprof/core/ast/module.rb +38 -4
- data/lib/typeprof/core/ast/pattern.rb +51 -23
- data/lib/typeprof/core/ast/sig_decl.rb +141 -27
- data/lib/typeprof/core/ast/sig_type.rb +113 -32
- data/lib/typeprof/core/ast/value.rb +14 -6
- data/lib/typeprof/core/ast/variable.rb +17 -4
- data/lib/typeprof/core/ast.rb +98 -14
- data/lib/typeprof/core/builtin.rb +184 -12
- data/lib/typeprof/core/env/method.rb +343 -6
- data/lib/typeprof/core/env/method_entity.rb +18 -15
- data/lib/typeprof/core/env/module_entity.rb +116 -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 +113 -23
- data/lib/typeprof/core/graph/box.rb +508 -63
- data/lib/typeprof/core/graph/change_set.rb +64 -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 +340 -24
- 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
|
|
@@ -396,7 +405,9 @@ module TypeProf::Core
|
|
|
396
405
|
break
|
|
397
406
|
end
|
|
398
407
|
end
|
|
399
|
-
|
|
408
|
+
# The base class has no name to show
|
|
409
|
+
owner = mod.cpath.last == AST::StructNewNode::BASE_CNAME ? base_ty.mod : mod
|
|
410
|
+
yield mid, "#{ owner.cpath.join("::" )}#{ singleton ? "." : "#" }#{ mid } : #{ sig }" if sig
|
|
400
411
|
end
|
|
401
412
|
end
|
|
402
413
|
end
|
|
@@ -405,15 +416,55 @@ module TypeProf::Core
|
|
|
405
416
|
end
|
|
406
417
|
end
|
|
407
418
|
|
|
419
|
+
def format_declared_const_path(cpath, stack)
|
|
420
|
+
scope_cpath =
|
|
421
|
+
stack.reverse_each.find do |entry|
|
|
422
|
+
(entry.is_a?(AST::ClassNode) || entry.is_a?(AST::ModuleNode)) &&
|
|
423
|
+
entry.static_cpath &&
|
|
424
|
+
!entry.static_cpath.empty?
|
|
425
|
+
end&.static_cpath
|
|
426
|
+
|
|
427
|
+
return cpath.join("::") unless scope_cpath
|
|
428
|
+
return cpath.join("::") unless cpath[0, scope_cpath.size] == scope_cpath
|
|
429
|
+
|
|
430
|
+
rel_cpath = cpath.drop(scope_cpath.size)
|
|
431
|
+
rel_cpath.empty? ? cpath.join("::") : rel_cpath.join("::")
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def format_superclass_path(node, superclass)
|
|
435
|
+
sc_path = superclass.show_cpath
|
|
436
|
+
|
|
437
|
+
if node.is_a?(AST::ClassNode) && node.superclass_cpath
|
|
438
|
+
sc_node = node.superclass_cpath
|
|
439
|
+
if superclass_source_is_toplevel?(sc_node)
|
|
440
|
+
return "::#{ sc_path }"
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
if node.static_cpath && node.static_cpath.size > 1 &&
|
|
444
|
+
superclass.cpath == [node.static_cpath.last]
|
|
445
|
+
return "::#{ sc_path }"
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
sc_path
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def superclass_source_is_toplevel?(const_node)
|
|
453
|
+
return false unless const_node.is_a?(AST::ConstantReadNode)
|
|
454
|
+
return true if const_node.toplevel
|
|
455
|
+
const_node.cbase ? superclass_source_is_toplevel?(const_node.cbase) : false
|
|
456
|
+
end
|
|
457
|
+
|
|
408
458
|
def dump_declarations(path)
|
|
409
459
|
stack = []
|
|
410
460
|
out = []
|
|
461
|
+
seen_consts = Set.empty
|
|
411
462
|
@rb_text_nodes[path]&.traverse do |event, node|
|
|
412
463
|
case node
|
|
413
464
|
when AST::ModuleNode
|
|
414
465
|
if node.static_cpath
|
|
415
466
|
if event == :enter
|
|
416
|
-
out << " " * stack.size + "module #{ node.static_cpath
|
|
467
|
+
out << " " * stack.size + "module #{ format_declared_const_path(node.static_cpath, stack) }"
|
|
417
468
|
if stack == [:toplevel]
|
|
418
469
|
out << "end"
|
|
419
470
|
stack.pop
|
|
@@ -424,18 +475,22 @@ module TypeProf::Core
|
|
|
424
475
|
out << " " * stack.size + "end"
|
|
425
476
|
end
|
|
426
477
|
end
|
|
427
|
-
when AST::ClassNode, AST::SingletonClassNode
|
|
478
|
+
when AST::ClassNode, AST::SingletonClassNode, AST::StructNewNode
|
|
428
479
|
if node.static_cpath
|
|
429
480
|
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
481
|
|
|
431
482
|
if event == :enter
|
|
432
|
-
s = "class #{ node.static_cpath
|
|
483
|
+
s = "class #{ format_declared_const_path(node.static_cpath, stack) }"
|
|
433
484
|
mod = @genv.resolve_cpath(node.static_cpath)
|
|
434
485
|
superclass = mod.superclass
|
|
486
|
+
# The base class is not shown; its members are printed inline
|
|
487
|
+
while superclass && superclass.cpath.last == AST::StructNewNode::BASE_CNAME
|
|
488
|
+
superclass = superclass.superclass
|
|
489
|
+
end
|
|
435
490
|
if superclass == nil
|
|
436
491
|
s << " # failed to identify its superclass"
|
|
437
492
|
elsif superclass.cpath != []
|
|
438
|
-
s << " < #{ superclass
|
|
493
|
+
s << " < #{ format_superclass_path(node, superclass) }"
|
|
439
494
|
end
|
|
440
495
|
if stack == [:toplevel]
|
|
441
496
|
out << "end"
|
|
@@ -448,6 +503,18 @@ module TypeProf::Core
|
|
|
448
503
|
out << " " * stack.size + "include #{ inc_mod.show_cpath }"
|
|
449
504
|
end
|
|
450
505
|
end
|
|
506
|
+
mod.extended_modules.each do |ext_def, ext_mod|
|
|
507
|
+
if ext_def.is_a?(AST::ConstantReadNode) && ext_def.lenv.path == path
|
|
508
|
+
out << " " * stack.size + "extend #{ ext_mod.show_cpath }"
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
# Output method definitions from meta nodes (StructNewNode etc.)
|
|
512
|
+
node.boxes(:mdef) do |mdef|
|
|
513
|
+
# A user-written method overrides the generated one
|
|
514
|
+
next if node.is_a?(AST::StructNewNode) &&
|
|
515
|
+
!@genv.resolve_method(node.static_cpath, mdef.singleton, mdef.mid).defs.empty?
|
|
516
|
+
out << " " * stack.size + "def #{ mdef.singleton ? "self." : "" }#{ mdef.mid }: " + mdef.show(@options[:output_parameter_names])
|
|
517
|
+
end
|
|
451
518
|
else
|
|
452
519
|
stack.pop
|
|
453
520
|
out << " " * stack.size + "end"
|
|
@@ -456,7 +523,14 @@ module TypeProf::Core
|
|
|
456
523
|
when AST::ConstantWriteNode
|
|
457
524
|
if node.static_cpath
|
|
458
525
|
if event == :enter
|
|
459
|
-
|
|
526
|
+
unless seen_consts.include?(node.static_cpath)
|
|
527
|
+
seen_consts << node.static_cpath
|
|
528
|
+
cdef = @genv.resolve_const(node.static_cpath)
|
|
529
|
+
# Use the assigned value for a single write so a same-named class
|
|
530
|
+
# or module (e.g. declared in RBS) is not mixed in; union the types otherwise.
|
|
531
|
+
ret = cdef.defs.size > 1 ? cdef.vtx : node.ret
|
|
532
|
+
out << " " * stack.size + "#{ format_declared_const_path(node.static_cpath, stack) }: #{ ret.show }"
|
|
533
|
+
end
|
|
460
534
|
end
|
|
461
535
|
end
|
|
462
536
|
else
|
|
@@ -496,10 +570,14 @@ module TypeProf::Core
|
|
|
496
570
|
output.puts
|
|
497
571
|
end
|
|
498
572
|
|
|
573
|
+
# Analyze RBS files first so that type declarations are available during RB type inference
|
|
574
|
+
rbs_files, rb_files = separate_rbs_and_rb(files)
|
|
575
|
+
sorted_files = rbs_files + rb_files
|
|
576
|
+
|
|
499
577
|
i = 0
|
|
500
|
-
show_files =
|
|
578
|
+
show_files = sorted_files.select do |file|
|
|
501
579
|
if @options[:display_indicator]
|
|
502
|
-
$stderr << "\r[%d/%d] %s\e[K" % [i,
|
|
580
|
+
$stderr << "\r[%d/%d] %s\e[K" % [i, sorted_files.size, file]
|
|
503
581
|
i += 1
|
|
504
582
|
end
|
|
505
583
|
|
|
@@ -532,6 +610,244 @@ module TypeProf::Core
|
|
|
532
610
|
end
|
|
533
611
|
output.puts dump_declarations(file)
|
|
534
612
|
end
|
|
613
|
+
|
|
614
|
+
if @options[:output_stats]
|
|
615
|
+
rb_files = show_files.reject {|f| File.extname(f) == ".rbs" }
|
|
616
|
+
stats = collect_stats(rb_files)
|
|
617
|
+
output.puts
|
|
618
|
+
output.puts format_stats(stats)
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
def collect_stats(files)
|
|
623
|
+
file_stats = []
|
|
624
|
+
|
|
625
|
+
files.each do |path|
|
|
626
|
+
methods = []
|
|
627
|
+
constants = []
|
|
628
|
+
seen_ivars = Set.empty
|
|
629
|
+
ivars = []
|
|
630
|
+
seen_cvars = Set.empty
|
|
631
|
+
cvars = []
|
|
632
|
+
seen_gvars = Set.empty
|
|
633
|
+
gvars = []
|
|
634
|
+
|
|
635
|
+
@rb_text_nodes[path]&.traverse do |event, node|
|
|
636
|
+
next unless event == :enter
|
|
637
|
+
|
|
638
|
+
node.boxes(:mdef) do |mdef|
|
|
639
|
+
param_slots = []
|
|
640
|
+
f = mdef.f_args
|
|
641
|
+
[f.req_positionals, f.opt_positionals, f.post_positionals, f.req_keywords, f.opt_keywords].each do |ary|
|
|
642
|
+
ary.each {|vtx| param_slots << classify_vertex(vtx) }
|
|
643
|
+
end
|
|
644
|
+
[f.rest_positionals, f.rest_keywords].each do |vtx|
|
|
645
|
+
param_slots << classify_vertex(vtx) if vtx
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
is_initialize = mdef.mid == :initialize
|
|
649
|
+
ret_slots = is_initialize ? [] : [classify_vertex(mdef.ret)]
|
|
650
|
+
|
|
651
|
+
blk = mdef.record_block
|
|
652
|
+
block_param_slots = []
|
|
653
|
+
block_ret_slots = []
|
|
654
|
+
if blk.used
|
|
655
|
+
blk.f_args.each {|vtx| block_param_slots << classify_vertex(vtx) }
|
|
656
|
+
block_ret_slots << classify_vertex(blk.ret)
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
methods << {
|
|
660
|
+
mid: mdef.mid,
|
|
661
|
+
singleton: mdef.singleton,
|
|
662
|
+
param_slots: param_slots,
|
|
663
|
+
ret_slots: ret_slots,
|
|
664
|
+
block_param_slots: block_param_slots,
|
|
665
|
+
block_ret_slots: block_ret_slots,
|
|
666
|
+
}
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
if node.is_a?(AST::ConstantWriteNode) && node.static_cpath
|
|
670
|
+
constants << classify_vertex(node.ret)
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
if node.is_a?(AST::InstanceVariableWriteNode)
|
|
674
|
+
scope = node.lenv.cref.scope_level
|
|
675
|
+
if scope == :class || scope == :instance
|
|
676
|
+
key = [node.lenv.cref.cpath, scope == :class, node.var]
|
|
677
|
+
unless seen_ivars.include?(key)
|
|
678
|
+
seen_ivars << key
|
|
679
|
+
ve = @genv.resolve_ivar(key[0], key[1], key[2])
|
|
680
|
+
ivars << classify_vertex(ve.vtx)
|
|
681
|
+
end
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
if node.is_a?(AST::ClassVariableWriteNode)
|
|
686
|
+
key = [node.lenv.cref.cpath, node.var]
|
|
687
|
+
unless seen_cvars.include?(key)
|
|
688
|
+
seen_cvars << key
|
|
689
|
+
ve = @genv.resolve_cvar(key[0], key[1])
|
|
690
|
+
cvars << classify_vertex(ve.vtx)
|
|
691
|
+
end
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
if node.is_a?(AST::GlobalVariableWriteNode)
|
|
695
|
+
unless seen_gvars.include?(node.var)
|
|
696
|
+
seen_gvars << node.var
|
|
697
|
+
ve = @genv.resolve_gvar(node.var)
|
|
698
|
+
gvars << classify_vertex(ve.vtx)
|
|
699
|
+
end
|
|
700
|
+
end
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
file_stats << {
|
|
704
|
+
path: path,
|
|
705
|
+
methods: methods,
|
|
706
|
+
constants: constants,
|
|
707
|
+
ivars: ivars,
|
|
708
|
+
cvars: cvars,
|
|
709
|
+
gvars: gvars,
|
|
710
|
+
}
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
file_stats
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
def classify_vertex(vtx)
|
|
717
|
+
vtx.types.empty? ? :untyped : :typed
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
def format_stats(stats)
|
|
721
|
+
total_methods = 0
|
|
722
|
+
fully_typed = 0
|
|
723
|
+
partially_typed = 0
|
|
724
|
+
fully_untyped = 0
|
|
725
|
+
|
|
726
|
+
slot_categories = %i[param ret blk_param blk_ret const ivar cvar gvar]
|
|
727
|
+
typed = Hash.new(0)
|
|
728
|
+
untyped = Hash.new(0)
|
|
729
|
+
|
|
730
|
+
file_summaries = []
|
|
731
|
+
|
|
732
|
+
stats.each do |file|
|
|
733
|
+
f_typed = 0
|
|
734
|
+
f_total = 0
|
|
735
|
+
|
|
736
|
+
file[:methods].each do |m|
|
|
737
|
+
total_methods += 1
|
|
738
|
+
|
|
739
|
+
method_slot_keys = %i[param_slots ret_slots block_param_slots block_ret_slots]
|
|
740
|
+
category_keys = %i[param ret blk_param blk_ret]
|
|
741
|
+
|
|
742
|
+
all_slots = method_slot_keys.flat_map {|k| m[k] }
|
|
743
|
+
|
|
744
|
+
method_slot_keys.zip(category_keys) do |slot_key, cat|
|
|
745
|
+
m[slot_key].each do |s|
|
|
746
|
+
if s == :typed
|
|
747
|
+
typed[cat] += 1
|
|
748
|
+
else
|
|
749
|
+
untyped[cat] += 1
|
|
750
|
+
end
|
|
751
|
+
end
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
if all_slots.empty? || all_slots.all? {|s| s == :typed }
|
|
755
|
+
fully_typed += 1
|
|
756
|
+
elsif all_slots.none? {|s| s == :typed }
|
|
757
|
+
fully_untyped += 1
|
|
758
|
+
else
|
|
759
|
+
partially_typed += 1
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
f_typed += all_slots.count(:typed)
|
|
763
|
+
f_total += all_slots.size
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
%i[constants ivars cvars gvars].zip(%i[const ivar cvar gvar]) do |data_key, cat|
|
|
767
|
+
file[data_key].each do |s|
|
|
768
|
+
f_total += 1
|
|
769
|
+
if s == :typed
|
|
770
|
+
typed[cat] += 1
|
|
771
|
+
f_typed += 1
|
|
772
|
+
else
|
|
773
|
+
untyped[cat] += 1
|
|
774
|
+
end
|
|
775
|
+
end
|
|
776
|
+
end
|
|
777
|
+
|
|
778
|
+
if f_total > 0
|
|
779
|
+
file_summaries << {
|
|
780
|
+
path: file[:path],
|
|
781
|
+
methods: file[:methods].size,
|
|
782
|
+
typed: f_typed,
|
|
783
|
+
total: f_total,
|
|
784
|
+
}
|
|
785
|
+
end
|
|
786
|
+
end
|
|
787
|
+
|
|
788
|
+
overall_typed = slot_categories.sum {|c| typed[c] }
|
|
789
|
+
overall_untyped = slot_categories.sum {|c| untyped[c] }
|
|
790
|
+
overall_total = overall_typed + overall_untyped
|
|
791
|
+
|
|
792
|
+
labels = {
|
|
793
|
+
param: "Parameter slots",
|
|
794
|
+
ret: "Return slots",
|
|
795
|
+
blk_param: "Block parameter slots",
|
|
796
|
+
blk_ret: "Block return slots",
|
|
797
|
+
const: "Constants",
|
|
798
|
+
ivar: "Instance variables",
|
|
799
|
+
cvar: "Class variables",
|
|
800
|
+
gvar: "Global variables",
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
lines = []
|
|
804
|
+
lines << "# TypeProf Evaluation Statistics"
|
|
805
|
+
lines << "#"
|
|
806
|
+
lines << "# Total methods: #{ total_methods }"
|
|
807
|
+
lines << "# Fully typed: #{ fully_typed }"
|
|
808
|
+
lines << "# Partially typed: #{ partially_typed }"
|
|
809
|
+
lines << "# Fully untyped: #{ fully_untyped }"
|
|
810
|
+
|
|
811
|
+
slot_categories.each do |cat|
|
|
812
|
+
total = typed[cat] + untyped[cat]
|
|
813
|
+
lines << "#"
|
|
814
|
+
lines << "# #{ labels[cat] }: #{ total }"
|
|
815
|
+
lines << "# Typed: #{ typed[cat] } (#{ pct(typed[cat], total) })"
|
|
816
|
+
lines << "# Untyped: #{ untyped[cat] } (#{ pct(untyped[cat], total) })"
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
lines << "#"
|
|
820
|
+
lines << "# Overall: #{ overall_typed }/#{ overall_total } typed (#{ pct(overall_typed, overall_total) })"
|
|
821
|
+
lines << "# #{ overall_untyped }/#{ overall_total } untyped (#{ pct(overall_untyped, overall_total) })"
|
|
822
|
+
|
|
823
|
+
if file_summaries.size > 1
|
|
824
|
+
lines << "#"
|
|
825
|
+
lines << "# Per-file breakdown:"
|
|
826
|
+
file_summaries.each do |fs|
|
|
827
|
+
lines << "# #{ fs[:path] }: #{ fs[:methods] } methods, #{ fs[:typed] }/#{ fs[:total] } typed (#{ pct(fs[:typed], fs[:total]) })"
|
|
828
|
+
end
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
lines.join("\n")
|
|
832
|
+
end
|
|
833
|
+
|
|
834
|
+
def pct(n, total)
|
|
835
|
+
return "0.0%" if total == 0
|
|
836
|
+
"#{ (n * 100.0 / total).round(1) }%"
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
private
|
|
840
|
+
|
|
841
|
+
def separate_rbs_and_rb(files)
|
|
842
|
+
files
|
|
843
|
+
.reject { |file| exclude_files.include?(File.expand_path(file)) }
|
|
844
|
+
.partition { |file| File.extname(file) == ".rbs" }
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
def exclude_files
|
|
848
|
+
@exclude_files ||= (@options[:exclude_patterns] || []).each_with_object(::Set.new) { |pattern, set|
|
|
849
|
+
Dir.glob(File.expand_path(pattern)) { |path| set << path }
|
|
850
|
+
}
|
|
535
851
|
end
|
|
536
852
|
end
|
|
537
853
|
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.33.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.16
|
|
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: []
|