typeprof 0.21.11 → 0.30.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 +15 -31
- data/bin/typeprof +5 -0
- data/doc/doc.ja.md +134 -0
- data/doc/doc.md +136 -0
- data/lib/typeprof/cli/cli.rb +180 -0
- data/lib/typeprof/cli.rb +2 -133
- data/lib/typeprof/code_range.rb +112 -0
- data/lib/typeprof/core/ast/base.rb +263 -0
- data/lib/typeprof/core/ast/call.rb +251 -0
- data/lib/typeprof/core/ast/const.rb +126 -0
- data/lib/typeprof/core/ast/control.rb +432 -0
- data/lib/typeprof/core/ast/meta.rb +150 -0
- data/lib/typeprof/core/ast/method.rb +335 -0
- data/lib/typeprof/core/ast/misc.rb +263 -0
- data/lib/typeprof/core/ast/module.rb +123 -0
- data/lib/typeprof/core/ast/pattern.rb +140 -0
- data/lib/typeprof/core/ast/sig_decl.rb +471 -0
- data/lib/typeprof/core/ast/sig_type.rb +663 -0
- data/lib/typeprof/core/ast/value.rb +319 -0
- data/lib/typeprof/core/ast/variable.rb +315 -0
- data/lib/typeprof/core/ast.rb +472 -0
- data/lib/typeprof/core/builtin.rb +146 -0
- data/lib/typeprof/core/env/method.rb +137 -0
- data/lib/typeprof/core/env/method_entity.rb +55 -0
- data/lib/typeprof/core/env/module_entity.rb +408 -0
- data/lib/typeprof/core/env/static_read.rb +155 -0
- data/lib/typeprof/core/env/type_alias_entity.rb +27 -0
- data/lib/typeprof/core/env/value_entity.rb +32 -0
- data/lib/typeprof/core/env.rb +360 -0
- data/lib/typeprof/core/graph/box.rb +991 -0
- data/lib/typeprof/core/graph/change_set.rb +224 -0
- data/lib/typeprof/core/graph/filter.rb +155 -0
- data/lib/typeprof/core/graph/vertex.rb +222 -0
- data/lib/typeprof/core/graph.rb +3 -0
- data/lib/typeprof/core/service.rb +522 -0
- data/lib/typeprof/core/type.rb +348 -0
- data/lib/typeprof/core/util.rb +81 -0
- data/lib/typeprof/core.rb +32 -0
- data/lib/typeprof/diagnostic.rb +35 -0
- data/lib/typeprof/lsp/messages.rb +430 -0
- data/lib/typeprof/lsp/server.rb +177 -0
- data/lib/typeprof/lsp/text.rb +69 -0
- data/lib/typeprof/lsp/util.rb +61 -0
- data/lib/typeprof/lsp.rb +4 -907
- data/lib/typeprof/version.rb +1 -1
- data/lib/typeprof.rb +4 -18
- data/typeprof.gemspec +5 -7
- metadata +48 -35
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/main.yml +0 -39
- data/.gitignore +0 -9
- data/Gemfile +0 -17
- data/Gemfile.lock +0 -41
- data/Rakefile +0 -10
- data/exe/typeprof +0 -10
- data/lib/typeprof/analyzer.rb +0 -2598
- data/lib/typeprof/arguments.rb +0 -414
- data/lib/typeprof/block.rb +0 -176
- data/lib/typeprof/builtin.rb +0 -893
- data/lib/typeprof/code-range.rb +0 -177
- data/lib/typeprof/config.rb +0 -158
- data/lib/typeprof/container-type.rb +0 -912
- data/lib/typeprof/export.rb +0 -589
- data/lib/typeprof/import.rb +0 -852
- data/lib/typeprof/insns-def.rb +0 -65
- data/lib/typeprof/iseq.rb +0 -864
- data/lib/typeprof/method.rb +0 -355
- data/lib/typeprof/type.rb +0 -1140
- data/lib/typeprof/utils.rb +0 -212
- data/tools/coverage.rb +0 -14
- data/tools/setup-insns-def.rb +0 -30
- data/typeprof-lsp +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4faf061f0c69941bd42ce81df939d3204319da2891d175ac64b0165d4d660c18
|
4
|
+
data.tar.gz: 4cd3d5666361cfa335f2acc398e8571b8fc38c9ff2911fb55ce394f3df8d1fc9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af755e8f52cc2c315cb245083b2cef95741c5ccb8dfe98f47ef41e40fd087b6e641746f797d8bfaa68097bcb5aa6f52364e5e69651e7c368da4cd951a34a068c
|
7
|
+
data.tar.gz: ced1fc8642f7020340caac9de194d83c0cffc01515e020f126f081a9f9262ccb6bd522927ce90583415bdfdde5e167a84783c71b907cfaff1c3fac2cd74ed7f2
|
data/README.md
CHANGED
@@ -1,41 +1,25 @@
|
|
1
|
-
# TypeProf
|
1
|
+
# TypeProf
|
2
2
|
|
3
|
-
|
3
|
+
An experimental type-level Ruby interpreter for testing and understanding Ruby code.
|
4
4
|
|
5
|
-
|
6
|
-
gem install typeprof
|
7
|
-
typeprof app.rb
|
8
|
-
```
|
5
|
+
## Development
|
9
6
|
|
10
|
-
|
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
|
-
|
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
|
-
|
19
|
+
## More details
|
36
20
|
|
37
|
-
|
21
|
+
https://speakerdeck.com/mame/good-first-issues-of-typeprof
|
38
22
|
|
39
|
-
|
23
|
+
## LICENSE
|
40
24
|
|
41
|
-
|
25
|
+
See [LICENSE](LICENSE) file.
|
data/bin/typeprof
ADDED
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
|
-
|
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"
|