typeprof 0.21.11 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -31
  3. data/bin/typeprof +5 -0
  4. data/doc/doc.ja.md +134 -0
  5. data/doc/doc.md +136 -0
  6. data/lib/typeprof/cli/cli.rb +180 -0
  7. data/lib/typeprof/cli.rb +2 -133
  8. data/lib/typeprof/code_range.rb +112 -0
  9. data/lib/typeprof/core/ast/base.rb +263 -0
  10. data/lib/typeprof/core/ast/call.rb +251 -0
  11. data/lib/typeprof/core/ast/const.rb +126 -0
  12. data/lib/typeprof/core/ast/control.rb +432 -0
  13. data/lib/typeprof/core/ast/meta.rb +150 -0
  14. data/lib/typeprof/core/ast/method.rb +335 -0
  15. data/lib/typeprof/core/ast/misc.rb +263 -0
  16. data/lib/typeprof/core/ast/module.rb +123 -0
  17. data/lib/typeprof/core/ast/pattern.rb +140 -0
  18. data/lib/typeprof/core/ast/sig_decl.rb +471 -0
  19. data/lib/typeprof/core/ast/sig_type.rb +663 -0
  20. data/lib/typeprof/core/ast/value.rb +319 -0
  21. data/lib/typeprof/core/ast/variable.rb +315 -0
  22. data/lib/typeprof/core/ast.rb +472 -0
  23. data/lib/typeprof/core/builtin.rb +146 -0
  24. data/lib/typeprof/core/env/method.rb +137 -0
  25. data/lib/typeprof/core/env/method_entity.rb +55 -0
  26. data/lib/typeprof/core/env/module_entity.rb +408 -0
  27. data/lib/typeprof/core/env/static_read.rb +155 -0
  28. data/lib/typeprof/core/env/type_alias_entity.rb +27 -0
  29. data/lib/typeprof/core/env/value_entity.rb +32 -0
  30. data/lib/typeprof/core/env.rb +360 -0
  31. data/lib/typeprof/core/graph/box.rb +991 -0
  32. data/lib/typeprof/core/graph/change_set.rb +224 -0
  33. data/lib/typeprof/core/graph/filter.rb +155 -0
  34. data/lib/typeprof/core/graph/vertex.rb +222 -0
  35. data/lib/typeprof/core/graph.rb +3 -0
  36. data/lib/typeprof/core/service.rb +522 -0
  37. data/lib/typeprof/core/type.rb +348 -0
  38. data/lib/typeprof/core/util.rb +81 -0
  39. data/lib/typeprof/core.rb +32 -0
  40. data/lib/typeprof/diagnostic.rb +35 -0
  41. data/lib/typeprof/lsp/messages.rb +430 -0
  42. data/lib/typeprof/lsp/server.rb +177 -0
  43. data/lib/typeprof/lsp/text.rb +69 -0
  44. data/lib/typeprof/lsp/util.rb +61 -0
  45. data/lib/typeprof/lsp.rb +4 -907
  46. data/lib/typeprof/version.rb +1 -1
  47. data/lib/typeprof.rb +4 -18
  48. data/typeprof.gemspec +5 -7
  49. metadata +48 -35
  50. data/.github/dependabot.yml +0 -6
  51. data/.github/workflows/main.yml +0 -39
  52. data/.gitignore +0 -9
  53. data/Gemfile +0 -17
  54. data/Gemfile.lock +0 -41
  55. data/Rakefile +0 -10
  56. data/exe/typeprof +0 -10
  57. data/lib/typeprof/analyzer.rb +0 -2598
  58. data/lib/typeprof/arguments.rb +0 -414
  59. data/lib/typeprof/block.rb +0 -176
  60. data/lib/typeprof/builtin.rb +0 -893
  61. data/lib/typeprof/code-range.rb +0 -177
  62. data/lib/typeprof/config.rb +0 -158
  63. data/lib/typeprof/container-type.rb +0 -912
  64. data/lib/typeprof/export.rb +0 -589
  65. data/lib/typeprof/import.rb +0 -852
  66. data/lib/typeprof/insns-def.rb +0 -65
  67. data/lib/typeprof/iseq.rb +0 -864
  68. data/lib/typeprof/method.rb +0 -355
  69. data/lib/typeprof/type.rb +0 -1140
  70. data/lib/typeprof/utils.rb +0 -212
  71. data/tools/coverage.rb +0 -14
  72. data/tools/setup-insns-def.rb +0 -30
  73. data/typeprof-lsp +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2fe080a0c812f2be50d024eb51bd7c4ba751c2a720aaa99489e4a6271edba1e2
4
- data.tar.gz: 6bd7b34c3428855355ff644ecf9987cda4db8645ba3a495a0f412f25c98a9972
3
+ metadata.gz: 4faf061f0c69941bd42ce81df939d3204319da2891d175ac64b0165d4d660c18
4
+ data.tar.gz: 4cd3d5666361cfa335f2acc398e8571b8fc38c9ff2911fb55ce394f3df8d1fc9
5
5
  SHA512:
6
- metadata.gz: ac0242124986985239123f8a15e2cea01c77815cf2a9e5e586178fdece251d0e13172429ea09773d7b5136aed568db4e7e25fadcabb5a7afd000f57454c28b79
7
- data.tar.gz: 4b36374f2af922eab5803e77666046896392153475d3dc892cc689e25beb84c83a6b5a164c344768b2d0951ba790832785907d1cf65b1f37cc91d9f1472cf957
6
+ metadata.gz: af755e8f52cc2c315cb245083b2cef95741c5ccb8dfe98f47ef41e40fd087b6e641746f797d8bfaa68097bcb5aa6f52364e5e69651e7c368da4cd951a34a068c
7
+ data.tar.gz: ced1fc8642f7020340caac9de194d83c0cffc01515e020f126f081a9f9262ccb6bd522927ce90583415bdfdde5e167a84783c71b907cfaff1c3fac2cd74ed7f2
data/README.md CHANGED
@@ -1,41 +1,25 @@
1
- # TypeProf: A type analysis tool for Ruby code based on abstract interpretation
1
+ # TypeProf
2
2
 
3
- ## Synopsis
3
+ An experimental type-level Ruby interpreter for testing and understanding Ruby code.
4
4
 
5
- ```sh
6
- gem install typeprof
7
- typeprof app.rb
8
- ```
5
+ ## Development
9
6
 
10
- ## Demo
7
+ 1. Install Ruby 3.3 or later.
8
+ 2. Git clone this repository: `git clone https://github.com/ruby/typeprof.git`
9
+ 3. Install VSCode extension: `code --install-extension mame.ruby-typeprof`
10
+ 4. Open the repository in VSCode: `code typeprof`
11
11
 
12
- ```rb
13
- # test.rb
14
- def foo(x)
15
- if x > 10
16
- x.to_s
17
- else
18
- nil
19
- end
20
- end
21
-
22
- foo(42)
23
- ```
12
+ ### Testing
24
13
 
14
+ ```sh
15
+ $ bundle install
16
+ $ bundle exec rake test
25
17
  ```
26
- $ typeprof test.rb
27
- # Classes
28
- class Object
29
- def foo : (Integer) -> String?
30
- end
31
- ```
32
-
33
- ## Documentation
34
18
 
35
- [English](doc/doc.md) / [日本語](doc/doc.ja.md)
19
+ ## More details
36
20
 
37
- ## Playground
21
+ https://speakerdeck.com/mame/good-first-issues-of-typeprof
38
22
 
39
- You can try typeprof gem on the Web via the URL below.
23
+ ## LICENSE
40
24
 
41
- https://mame.github.io/typeprof-playground/
25
+ See [LICENSE](LICENSE) file.
data/bin/typeprof ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/typeprof"
4
+
5
+ TypeProf::CLI::CLI.new(ARGV).run
data/doc/doc.ja.md ADDED
@@ -0,0 +1,134 @@
1
+ # TypeProf: 抽象解釈に基づくRubyの型解析器
2
+
3
+ ## TypeProfの使い方 - CLIツールとして
4
+
5
+ app.rb を解析する。
6
+
7
+ ```
8
+ $ typeprof app.rb
9
+ ```
10
+
11
+ 一部のメソッドの型を指定した sig/app.rbs とともに app.rb を解析する。
12
+
13
+ ```
14
+ $ typeprof sig/app.rbs app.rb
15
+ ```
16
+
17
+ 典型的な使用法は次の通り。
18
+
19
+ ```
20
+ $ typeprof sig/app.rbs app.rb -o sig/app.gen.rbs
21
+ ```
22
+
23
+ ## TypeProfの使い方 - Language Serverとして
24
+
25
+ [RubyKaigi 2024の発表資料](https://speakerdeck.com/mame/good-first-issues-of-typeprof)を参照ください。
26
+
27
+ ## TypeProfの解析方法
28
+
29
+ TypeProfは、Rubyプログラムを型レベルで抽象的に実行するインタプリタです。
30
+ 解析対象のプログラムを実行し、メソッドが受け取ったり返したりする型、インスタンス変数に代入される型を集めて出力します。
31
+ すべての値はオブジェクトそのものではなく、原則としてオブジェクトの所属するクラスに抽象化されます(次節で詳説)。
32
+
33
+ メソッドを呼び出す例を用いて説明します。
34
+
35
+ ```
36
+ def foo(n)
37
+ p n #=> Integer
38
+ n.to_s
39
+ end
40
+
41
+ p foo(42) #=> String
42
+ ```
43
+
44
+ TypeProfの解析結果は次の通り。
45
+
46
+ ```
47
+ $ typeprof test.rb
48
+ # Revealed types
49
+ # test.rb:2 #=> Integer
50
+ # test.rb:6 #=> String
51
+
52
+ # Classes
53
+ class Object
54
+ def foo : (Integer) -> String
55
+ end
56
+ ```
57
+
58
+ `foo(42)`というメソッド呼び出しが実行されると、`Integer`オブジェクトの`42`ではなく、「`Integer`」という型(抽象値)が渡されます。
59
+ メソッド`foo`は`n.to_s`が実行します。
60
+ すると、組み込みメソッドの`Integer#to_s`が呼び出され、「String」という型が得られるので、メソッド`foo`はそれを返します。
61
+ これらの実行結果の観察を集めて、TypeProfは「メソッド`foo`は`Integer`を受け取り、`String`を返す」という情報をRBSの形式で出力します。
62
+ また、`p`の引数は`Revealed types`として出力されます。
63
+
64
+ インスタンス変数は、通常のRubyではオブジェクトごとに記憶される変数ですが、TypeProfではクラス単位に集約されます。
65
+
66
+ ```
67
+ class Foo
68
+ def initialize
69
+ @a = 42
70
+ end
71
+
72
+ attr_accessor :a
73
+ end
74
+
75
+ Foo.new.a = "str"
76
+
77
+ p Foo.new.a #=> Integer | String
78
+ ```
79
+
80
+ ```
81
+ $ typeprof test.rb
82
+ # Revealed types
83
+ # test.rb:11 #=> Integer | String
84
+
85
+ # Classes
86
+ class Foo
87
+ attr_accessor a : Integer | String
88
+ def initialize : -> Integer
89
+ end
90
+ ```
91
+
92
+ ## TypeProfの扱う抽象値
93
+
94
+ 前述の通り、TypeProfはRubyの値を型のようなレベルに抽象化して扱います。
95
+ ただし、クラスオブジェクトなど、一部の値は抽象化しません。
96
+ 紛らわしいので、TypeProfが使う抽象化された値のことを「抽象値」と呼びます。
97
+
98
+ TypeProfが扱う抽象値は次のとおりです。
99
+
100
+ * クラスのインスタンス
101
+ * クラスオブジェクト
102
+ * シンボル
103
+ * `untyped`
104
+ * 抽象値のユニオン
105
+ * コンテナクラスのインスタンス
106
+ * Procオブジェクト
107
+
108
+ クラスのインスタンスはもっとも普通の値です。
109
+ `Foo.new`というRubyコードが返す抽象値は、クラス`Foo`のインスタンスで、少し紛らわしいですがこれはRBS出力の中で`Foo`と表現されます。
110
+ `42`という整数リテラルは`Integer`のインスタンス、`"str"`という文字列リテラルは`String`のインスタンスになります。
111
+
112
+ クラスオブジェクトは、クラスそのものを表す値で、たとえば定数`Integer`や`String`に入っているオブジェクトです。
113
+ このオブジェクトは厳密にはクラス`Class`のインスタンスですが、`Class`に抽象化はされません。
114
+ 抽象化してしまうと、定数の参照やクラスメソッドが使えなくなるためです。
115
+
116
+ シンボルは、`:foo`のようなSymbolリテラルが返す値です。
117
+ シンボルは、キーワード引数、JSONデータのキー、`Module#attr_reader`の引数など、具体的な値が必要になることが多いので、抽象化されません。
118
+ ただし、`String#to_sym`で生成されるSymbolや、式展開を含むSymbolリテラル(`:"foo_#{ x }"`など)はクラス`Symbol`のインスタンスとして扱われます。
119
+
120
+ `untyped`は、解析の限界や制限などによって追跡ができない場合に生成される抽象値です。
121
+ `untyped`に対する演算やメソッド呼び出しは無視され、評価結果は`untyped`となります。
122
+
123
+ 抽象値のユニオンは、抽象値に複数の可能性があることを表現する値です。
124
+ 人工的ですが、`rand < 0.5 ? 42 : "str"`の結果は`Integer | String`という抽象値になります。
125
+
126
+ コンテナクラスのインスタンスは、ArrayやHashのように他の抽象値を要素とするオブジェクトです。
127
+ いまのところ、ArrayとEnumeratorとHashのみ対応しています。
128
+ 詳細は後述します。
129
+
130
+ Procオブジェクトは、ラムダ式(`-> { ... }`)やブロック仮引数(`&blk`)で作られるクロージャです。
131
+ これらは抽象化されず、コード片と結びついた具体的な値として扱われます。
132
+ これらに渡された引数や返された値によってRBS出力されます。
133
+
134
+ TBD
data/doc/doc.md ADDED
@@ -0,0 +1,136 @@
1
+ # TypeProf: A type analysis tool for Ruby code based on abstract interpretation
2
+
3
+ ## How to use TypeProf as a CLI tool
4
+
5
+ Analyze app.rb:
6
+
7
+ ```
8
+ $ typeprof app.rb
9
+ ```
10
+
11
+ Analyze app.rb with sig/app.rbs that specifies some method types:
12
+
13
+ ```
14
+ $ typeprof sig/app.rbs app.rb
15
+ ```
16
+
17
+ Here is a typical use case:
18
+
19
+ ```
20
+ $ typeprof sig/app.rbs app.rb -o sig/app.gen.rbs
21
+ ```
22
+
23
+ ## How to use TypeProf as a Language Server
24
+
25
+ See [the slide deck of my talk in RubyKaigi 2024](https://speakerdeck.com/mame/good-first-issues-of-typeprof) for now.
26
+
27
+ ## What is a TypeProf?
28
+
29
+ TypeProf is a Ruby interpreter that *abstractly* executes Ruby programs at the type level.
30
+ It executes a given program and observes what types are passed to and returned from methods and what types are assigned to instance variables.
31
+ All values are, in principle, abstracted to the class to which the object belongs, not the object itself (detailed in the next section).
32
+
33
+ Here is an example of a method call.
34
+
35
+ ```
36
+ def foo(n)
37
+ p n #=> Integer
38
+ n.to_s
39
+ end
40
+
41
+ p foo(42) #=> String
42
+ ```
43
+
44
+ The analysis results of TypeProf are as follows.
45
+
46
+ ```
47
+ $ ruby exe/typeprof test.rb
48
+ # Revealed types
49
+ # test.rb:2 #=> Integer
50
+ # test.rb:6 #=> String
51
+
52
+ # Classes
53
+ class Object
54
+ def foo : (Integer) -> String
55
+ end
56
+ ```
57
+
58
+ When the method call `foo(42)` is executed, the type (abstract value) "`Integer`" is passed instead of the `Integer` object 42.
59
+ The method `foo` executes `n.to_s`.
60
+ Then, the built-in method `Integer#to_s` is called and you get the type "`String`", which the method `foo` returns.
61
+ Collecting observations of these execution results, TypeProf outputs, "the method `foo` receives `Integer` and returns `String`" in the RBS format.
62
+ Also, the argument of `p` is output in the `Revealed types` section.
63
+
64
+ Instance variables are stored in each object in Ruby, but are aggregated in class units in TypeProf.
65
+
66
+ ```
67
+ class Foo
68
+ def initialize
69
+ @a = 42
70
+ end
71
+
72
+ attr_accessor :a
73
+ end
74
+
75
+ Foo.new.a = "str"
76
+
77
+ p Foo.new.a #=> Integer | String
78
+ ```
79
+
80
+ ```
81
+ $ ruby exe/typeprof test.rb
82
+ # Revealed types
83
+ # test.rb:11 #=> Integer | String
84
+
85
+ # Classes
86
+ class Foo
87
+ attr_accessor a : Integer | String
88
+ def initialize : -> Integer
89
+ end
90
+ ```
91
+
92
+
93
+ ## Abstract values
94
+
95
+ As mentioned above, TypeProf abstracts almost all Ruby values to the type level, with some exceptions like class objects.
96
+ To avoid confusion with normal Ruby values, we use the word "abstract value" to refer the values that TypeProf handles.
97
+
98
+ TypeProf handles the following abstract values.
99
+
100
+ * Instance of a class
101
+ * Class object
102
+ * Symbol
103
+ * `untyped`
104
+ * Union of abstract values
105
+ * Instance of a container class
106
+ * Proc object
107
+
108
+ Instances of classes are the most common values.
109
+ A Ruby code `Foo.new` returns an instance of the class `Foo`.
110
+ This abstract value is represented as `Foo` in the RBS format, though it is a bit confusing.
111
+ The integer literal `42` generates an instance of `Integer` and the string literal `"str"` generates an instance of `String`.
112
+
113
+ A class object is a value that represents the class itself.
114
+ For example, the constants `Integer` and `String` has class objects.
115
+ In Ruby semantics, a class object is an instance of the class `Class`, but it is not abstracted into `Class` in TypeProf.
116
+ This is because, if it is abstracted, TypeProf cannot handle constant references and class methods correctly.
117
+
118
+ A symbol is an abstract value returned by Symbol literals like `:foo`.
119
+ A symbol object is not abstracted to an instance of the class `Symbol` because its concrete value is often required in many cases, such as keyword arguments, JSON data keys, the argument of `Module#attr_reader`, etc.
120
+ Note that some Symbol objects are handled as instances of the class `Symbol`, for example, the return value of `String#to_sym` and Symbol literals that contains interpolation like `:"foo_#{ x }"`.
121
+
122
+ `untyped` is an abstract value generated when TypeProf fails to trace values due to analysis limits or restrictions.
123
+ Any operations and method calls on `untyped` are ignored, and the evaluation result is also `untyped`.
124
+
125
+ A union of abstract values is a value that represents multiple possibilities.,
126
+ For (a bit artificial) example, the result of `rand < 0.5 ? 42 : "str"` is a union, `Integer | String`.
127
+
128
+ An instance of a container class, such as Array and Hash, is an object that contains other abstract values as elements.
129
+ At present, only Array, Enumerator and Hash are supported.
130
+ Details will be described later.
131
+
132
+ A Proc object is a closure produced by lambda expressions (`-> {... }`) and block parameters (`&blk`).
133
+ During the interpretation, these objects are not abstracted but treated as concrete values associated with a piece of code.
134
+ In the RBS result, they are represented by using anonymous proc type, whose types they accepted and returned.
135
+
136
+ TODO: write more
@@ -0,0 +1,180 @@
1
+ require "io/console"
2
+
3
+ module TypeProf::CLI
4
+ class CLI
5
+ def initialize(argv)
6
+ opt = OptionParser.new
7
+
8
+ opt.banner = "Usage: #{ opt.program_name } [options] files_or_dirs..."
9
+
10
+ core_options = {}
11
+ lsp_options = {}
12
+ cli_options = {}
13
+
14
+ output = nil
15
+ rbs_collection_path = nil
16
+
17
+ opt.separator ""
18
+ opt.separator "Options:"
19
+ opt.on("-o OUTFILE", "Output to OUTFILE instead of stdout") {|v| output = v }
20
+ opt.on("-q", "--quiet", "Quiet mode") do
21
+ core_options[:display_indicator] = false
22
+ end
23
+ opt.on("-v", "--verbose", "Verbose mode") do
24
+ core_options[:show_errors] = true
25
+ end
26
+ opt.on("--version", "Display typeprof version") { cli_options[:display_version] = true }
27
+ opt.on("--collection PATH", "File path of collection configuration") { |v| rbs_collection_path = v }
28
+ opt.on("--no-collection", "Ignore collection configuration") { rbs_collection_path = :no }
29
+ opt.on("--lsp", "LSP server mode") do |v|
30
+ core_options[:display_indicator] = false
31
+ cli_options[:lsp] = true
32
+ end
33
+
34
+ opt.separator ""
35
+ opt.separator "Analysis output options:"
36
+ opt.on("--[no-]show-typeprof-version", "Display TypeProf version in a header") {|v| core_options[:output_typeprof_version] = v }
37
+ opt.on("--[no-]show-errors", "Display possible errors found during the analysis") {|v| core_options[:output_diagnostics] = v }
38
+ opt.on("--[no-]show-parameter-names", "Display parameter names for methods") {|v| core_options[:output_parameter_names] = v }
39
+ opt.on("--[no-]show-source-locations", "Display definition source locations for methods") {|v| core_options[:output_source_locations] = v }
40
+
41
+ opt.separator ""
42
+ opt.separator "Advanced options:"
43
+ opt.on("--[no-]stackprof MODE", /\Acpu|wall|object\z/, "Enable stackprof (for debugging purpose)") {|v| cli_options[:stackprof] = v.to_sym }
44
+
45
+ opt.separator ""
46
+ opt.separator "LSP options:"
47
+ opt.on("--port PORT", Integer, "Specify a port number to listen for requests on") {|v| lsp_options[:port] = v }
48
+ opt.on("--stdio", "Use stdio for LSP transport") {|v| lsp_options[:stdio] = v }
49
+
50
+ opt.parse!(argv)
51
+
52
+ if cli_options[:lsp] && !lsp_options.empty?
53
+ raise OptionParser::InvalidOption.new("lsp options with non-lsp mode")
54
+ end
55
+
56
+ @core_options = {
57
+ rbs_collection: setup_rbs_collection(rbs_collection_path),
58
+ display_indicator: $stderr.tty?,
59
+ output_typeprof_version: true,
60
+ output_errors: false,
61
+ output_parameter_names: false,
62
+ output_source_locations: false,
63
+ }.merge(core_options)
64
+
65
+ @lsp_options = {
66
+ port: 0,
67
+ stdio: false,
68
+ }.merge(lsp_options)
69
+
70
+ @cli_options = {
71
+ argv:,
72
+ output: output ? open(output, "w") : $stdout.dup,
73
+ display_version: false,
74
+ stackprof: nil,
75
+ lsp: false,
76
+ }.merge(cli_options)
77
+
78
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument
79
+ puts $!
80
+ exit 1
81
+ end
82
+
83
+ def setup_rbs_collection(path)
84
+ return nil if path == :no
85
+
86
+ unless path
87
+ path = RBS::Collection::Config::PATH.exist? ? RBS::Collection::Config::PATH.to_s : nil
88
+ return nil unless path
89
+ end
90
+
91
+ if !File.readable?(path)
92
+ raise OptionParser::InvalidOption.new("file not found: #{ path }")
93
+ end
94
+
95
+ lock_path = RBS::Collection::Config.to_lockfile_path(Pathname(path))
96
+ if !File.readable?(lock_path)
97
+ raise OptionParser::InvalidOption.new("file not found: #{ lock_path.to_s }; please run 'rbs collection install")
98
+ end
99
+
100
+ RBS::Collection::Config::Lockfile.from_lockfile(lockfile_path: lock_path, data: YAML.load_file(lock_path))
101
+ end
102
+
103
+ attr_reader :core_options, :lsp_options, :cli_options
104
+
105
+ def run
106
+ core = TypeProf::Core::Service.new(@core_options)
107
+
108
+ if @cli_options[:lsp]
109
+ run_lsp(core)
110
+ else
111
+ run_cli(core)
112
+ end
113
+ end
114
+
115
+ def run_lsp(core)
116
+ if @lsp_options[:stdio]
117
+ TypeProf::LSP::Server.start_stdio(core)
118
+ else
119
+ TypeProf::LSP::Server.start_socket(core)
120
+ end
121
+ rescue Exception
122
+ puts $!.detailed_message(highlight: false).gsub(/^/, "---")
123
+ raise
124
+ end
125
+
126
+ def run_cli(core)
127
+ puts "typeprof #{ TypeProf::VERSION }" if @cli_options[:display_version]
128
+
129
+ files = find_files
130
+
131
+ set_profiler do
132
+ output = @cli_options[:output]
133
+
134
+ core.batch(files, @cli_options[:output])
135
+
136
+ output.close
137
+ end
138
+
139
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument
140
+ puts $!
141
+ exit 1
142
+ end
143
+
144
+ def find_files
145
+ files = []
146
+ @cli_options[:argv].each do |path|
147
+ if File.directory?(path)
148
+ files.concat(Dir.glob("#{ path }/**/*.{rb,rbs}"))
149
+ elsif File.file?(path)
150
+ files << path
151
+ else
152
+ raise OptionParser::InvalidOption.new("no such file or directory -- #{ path }")
153
+ end
154
+ end
155
+
156
+ if files.empty?
157
+ exit if @cli_options[:display_version]
158
+ raise OptionParser::InvalidOption.new("no input files")
159
+ end
160
+
161
+ files
162
+ end
163
+
164
+ def set_profiler
165
+ if @cli_options[:stackprof]
166
+ require "stackprof"
167
+ out = "typeprof-stackprof-#{ @cli_options[:stackprof] }.dump"
168
+ StackProf.start(mode: @cli_options[:stackprof], out: out, raw: true)
169
+ end
170
+
171
+ yield
172
+
173
+ ensure
174
+ if @cli_options[:stackprof] && defined?(StackProf)
175
+ StackProf.stop
176
+ StackProf.results
177
+ end
178
+ end
179
+ end
180
+ end
data/lib/typeprof/cli.rb CHANGED
@@ -1,135 +1,4 @@
1
1
  require "optparse"
2
+ require "pathname"
2
3
 
3
- module TypeProf
4
- module CLI
5
- module_function
6
-
7
- def parse(argv)
8
- opt = OptionParser.new
9
-
10
- opt.banner = "Usage: #{ opt.program_name } [options] files..."
11
-
12
- output = nil
13
-
14
- # Verbose level:
15
- # * 0: none
16
- # * 1: default level
17
- # * 2: debugging level
18
- verbose = 1
19
-
20
- options = {}
21
- lsp_options = {}
22
- dir_filter = nil
23
- gem_rbs_features = []
24
- show_version = false
25
- max_sec = max_iter = nil
26
- collection_path = nil
27
- no_collection = false
28
-
29
- load_path_ext = []
30
-
31
- opt.separator ""
32
- opt.separator "Options:"
33
- opt.on("-o OUTFILE", "Output to OUTFILE instead of stdout") {|v| output = v }
34
- opt.on("-q", "--quiet", "Do not display progress indicator") { options[:show_indicator] = false }
35
- opt.on("-v", "--verbose", "Alias to --show-errors") { options[:show_errors] = true }
36
- opt.on("--version", "Display typeprof version") { show_version = true }
37
- opt.on("-I DIR", "Add DIR to the load/require path") {|v| load_path_ext << v }
38
- opt.on("-r FEATURE", "Require RBS of the FEATURE gem") {|v| gem_rbs_features << v }
39
- opt.on("--collection PATH", "File path of collection configuration") { |v| collection_path = Pathname(v) }
40
- opt.on("--no-collection", "Ignore collection configuration") { no_collection = true }
41
- opt.on("--lsp", "LSP mode") {|v| options[:lsp] = true }
42
-
43
- opt.separator ""
44
- opt.separator "Analysis output options:"
45
- opt.on("--include-dir DIR", "Include the analysis result of .rb file in DIR") do |dir|
46
- # When `--include-dir` option is specified as the first directory option,
47
- # typeprof will exclude any files by default unless a file path matches the explicit option
48
- dir_filter ||= [[:exclude]]
49
- dir_filter << [:include, File.expand_path(dir)]
50
- end
51
- opt.on("--exclude-dir DIR", "Exclude the analysis result of .rb file in DIR") do |dir|
52
- # When `--exclude-dir` option is specified as the first directory option,
53
- # typeprof will include any files by default, except Ruby's install directory and Gem directories
54
- dir_filter ||= ConfigData::DEFAULT_DIR_FILTER
55
- dir_filter << [:exclude, File.expand_path(dir)]
56
- end
57
- opt.on("--exclude-untyped", "Exclude (comment out) all entries including untyped") {|v| options[:exclude_untyped] = v }
58
- opt.on("--[no-]show-typeprof-version", "Display TypeProf version in a header") {|v| options[:show_typeprof_version] = v }
59
- opt.on("--[no-]show-errors", "Display possible errors found during the analysis") {|v| options[:show_errors] = v }
60
- opt.on("--[no-]show-untyped", "Display \"Foo | untyped\" instead of \"Foo\"") {|v| options[:show_untyped] = v }
61
- opt.on("--[no-]show-parameter-names", "Display parameter names for methods") {|v| options[:show_parameter_names] = v }
62
- opt.on("--[no-]show-source-locations", "Display definition source locations for methods") {|v| options[:show_source_locations] = v }
63
-
64
- opt.separator ""
65
- opt.separator "Analysis limit options:"
66
- opt.on("--max-second SECOND", Float, "Limit the maxium time of analysis (in second)") {|v| max_sec = v }
67
- opt.on("--max-iteration TIMES", Integer, "Limit the maxium instruction count of analysis") {|v| max_iter = v }
68
-
69
- opt.separator ""
70
- opt.separator "Advanced options:"
71
- opt.on("--[no-]stub-execution", "Force to call all unreachable methods with \"untyped\" arguments") {|v| options[:stub_execution] = v }
72
- opt.on("--type-depth-limit DEPTH", Integer, "Limit the maximum depth of nested types") {|v| options[:type_depth_limit] = v }
73
- opt.on("--union-width-limit WIDTH", Integer, "Limit the maximum count of class instances in one union type") {|v| options[:union_width_limit] = v }
74
- opt.on("--debug", "Display analysis log (for debugging purpose)") { verbose = 2 }
75
- opt.on("--[no-]stackprof MODE", /\Acpu|wall|object\z/, "Enable stackprof (for debugging purpose)") {|v| options[:stackprof] = v.to_sym }
76
-
77
- opt.separator ""
78
- opt.separator "LSP options:"
79
- opt.on("--port PORT", Integer, "Specify a port number to listen for requests on") {|v| lsp_options[:port] = v }
80
- opt.on("--stdio", "Use stdio for LSP transport") {|v| lsp_options[:stdio] = v }
81
-
82
- opt.parse!(argv)
83
-
84
- $LOAD_PATH.unshift(*load_path_ext)
85
-
86
- dir_filter ||= ConfigData::DEFAULT_DIR_FILTER
87
- rb_files = []
88
- rbs_files = []
89
- argv.each do |path|
90
- if File.extname(path) == ".rbs"
91
- rbs_files << path
92
- else
93
- rb_files << path
94
- end
95
- end
96
-
97
- puts "typeprof #{ VERSION }" if show_version
98
- if rb_files.empty? && !options[:lsp]
99
- exit if show_version
100
- raise OptionParser::InvalidOption.new("no input files")
101
- end
102
-
103
- if !options[:lsp] && !lsp_options.empty?
104
- exit if show_version
105
- raise OptionParser::InvalidOption.new("lsp options with non-lsp mode")
106
- end
107
-
108
- if !no_collection && collection_path && !collection_path.exist?
109
- exit if show_version
110
- raise OptionParser::InvalidOption.new("collection path not found (#{collection_path})")
111
- end
112
-
113
- collection_path ||= RBS::Collection::Config::PATH
114
- collection_path = nil if no_collection
115
-
116
- ConfigData.new(
117
- rb_files: rb_files,
118
- rbs_files: rbs_files,
119
- output: output,
120
- gem_rbs_features: gem_rbs_features,
121
- collection_path: collection_path,
122
- verbose: verbose,
123
- dir_filter: dir_filter,
124
- max_sec: max_sec,
125
- max_iter: max_iter,
126
- options: options,
127
- lsp_options: lsp_options,
128
- )
129
-
130
- rescue OptionParser::InvalidOption, OptionParser::MissingArgument
131
- puts $!
132
- exit 1
133
- end
134
- end
135
- end
4
+ require_relative "cli/cli"