rbs-trace 0.3.2 → 0.4.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/.rubocop.yml +11 -1
- data/CHANGELOG.md +26 -0
- data/README.md +44 -24
- data/Rakefile +7 -0
- data/exe/rbs-trace +8 -0
- data/lib/rbs/trace/builder.rb +124 -0
- data/lib/rbs/trace/cli/inline.rb +62 -0
- data/lib/rbs/trace/cli/merge.rb +102 -0
- data/lib/rbs/trace/cli.rb +40 -0
- data/lib/rbs/trace/file.rb +66 -21
- data/lib/rbs/trace/helpers.rb +75 -0
- data/lib/rbs/trace/inline_comment_visitor.rb +63 -0
- data/lib/rbs/trace/overload_compact.rb +85 -0
- data/lib/rbs/trace/return_value_visitor.rb +63 -0
- data/lib/rbs/trace/version.rb +2 -2
- data/lib/rbs/trace.rb +153 -4
- data/rbs_collection.lock.yaml +0 -4
- data/sig/generated/rbs/trace/builder.rbs +33 -0
- data/sig/generated/rbs/trace/cli/inline.rbs +19 -0
- data/sig/generated/rbs/trace/cli/merge.rbs +24 -0
- data/sig/generated/rbs/trace/cli.rbs +17 -0
- data/sig/generated/rbs/trace/file.rbs +20 -9
- data/sig/generated/rbs/trace/helpers.rbs +33 -0
- data/sig/generated/rbs/trace/inline_comment_visitor.rbs +27 -0
- data/sig/generated/rbs/trace/overload_compact.rbs +21 -0
- data/sig/generated/rbs/trace/return_value_visitor.rbs +27 -0
- data/sig/generated/rbs/trace/version.rbs +1 -1
- data/sig/generated/rbs/trace.rbs +63 -1
- data/tmp/.keep +0 -0
- metadata +38 -11
- data/lib/rbs/trace/declaration.rb +0 -107
- data/lib/rbs/trace/definition.rb +0 -33
- data/lib/rbs/trace/method_tracing.rb +0 -182
- data/sig/generated/rbs/trace/declaration.rbs +0 -36
- data/sig/generated/rbs/trace/definition.rbs +0 -25
- data/sig/generated/rbs/trace/method_tracing.rbs +0 -63
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e7d9ae59c5c4d3427c486422dcda9158bb65b1d5054d0ab5713a9714de9f39c
|
4
|
+
data.tar.gz: fc377a935ef972ac2e153d40f336154c03cb1e42b88dc732132f2e71bba83047
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e3ff05d28fbef44e66500377dd38e74a0a3890da197d00bbf61b461358d404fef1933d43e30655a5dffce3850f1a71b4d535b88db8e209d2d1100ba7c1f502e9
|
7
|
+
data.tar.gz: 446d6c0d49e72fae943708bc146bf80365cd107bac3d9c509950a0184611f19982dc244f0398964ff50d4d462756a3004ce73e5d5d0488a1ae9e41257555a447
|
data/.rubocop.yml
CHANGED
@@ -16,7 +16,7 @@ Style/Documentation:
|
|
16
16
|
Enabled: false
|
17
17
|
|
18
18
|
RSpec/ExampleLength:
|
19
|
-
Max:
|
19
|
+
Max: 50
|
20
20
|
|
21
21
|
Naming/MethodParameterName:
|
22
22
|
Enabled: false
|
@@ -25,8 +25,18 @@ Naming/FileName:
|
|
25
25
|
Exclude:
|
26
26
|
- "lib/rbs-trace.rb"
|
27
27
|
|
28
|
+
Naming/VariableNumber:
|
29
|
+
Enabled: false
|
30
|
+
|
28
31
|
Layout/LeadingCommentSpace:
|
29
32
|
Enabled: false
|
30
33
|
|
31
34
|
Style/AccessorGrouping:
|
32
35
|
Enabled: false
|
36
|
+
|
37
|
+
RSpec/DescribeClass:
|
38
|
+
Exclude:
|
39
|
+
- "spec/smoke_spec.rb"
|
40
|
+
|
41
|
+
RSpec/MultipleExpectations:
|
42
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,31 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.4.0] - 2025-02-23
|
4
|
+
|
5
|
+
- feat: Implement the function to save as RBS files
|
6
|
+
- feat: Add CLI commands to support parallel testing
|
7
|
+
- fix: Ignore if path is "inline template"
|
8
|
+
|
9
|
+
### BREAKING CHANGE
|
10
|
+
|
11
|
+
- chore!: Re-design API
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
# before
|
15
|
+
tracing = RBS::Trace::MethodTracing.new
|
16
|
+
tracing.enable do
|
17
|
+
# something
|
18
|
+
end
|
19
|
+
tracing.insert_rbs
|
20
|
+
|
21
|
+
# after
|
22
|
+
trace = RBS::Trace.new
|
23
|
+
trace.enable do
|
24
|
+
# something
|
25
|
+
end
|
26
|
+
trace.save_comments
|
27
|
+
```
|
28
|
+
|
3
29
|
## [0.3.2] - 2024-12-29
|
4
30
|
|
5
31
|
- fix: Do not insert comments if comment `#:` exists
|
data/README.md
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
[](https://badge.fury.io/rb/rbs-trace)
|
2
2
|
[](https://github.com/sinsoku/rbs-trace/actions/workflows/test.yml)
|
3
|
+
[](https://codecov.io/gh/sinsoku/rbs-trace)
|
3
4
|
|
4
5
|
# RBS::Trace
|
5
6
|
|
6
|
-
RBS::Trace collects argument
|
7
|
+
RBS::Trace automatically collects argument and return types and saves RBS type declarations as RBS files or comments.
|
7
8
|
|
8
9
|
## Installation
|
9
10
|
|
@@ -36,21 +37,22 @@ class User
|
|
36
37
|
end
|
37
38
|
```
|
38
39
|
|
39
|
-
Call target methods within the `enable` method block, and call the `
|
40
|
+
Call target methods within the `enable` method block, and call the `save_comments` method.
|
40
41
|
|
41
42
|
```ruby
|
42
|
-
|
43
|
+
trace = RBS::Trace.new
|
43
44
|
|
44
45
|
# Collects the types of methods called in the block.
|
45
|
-
|
46
|
+
trace.enable do
|
46
47
|
user = User.new("Nanoha", "Takamachi")
|
47
48
|
user.say_hello
|
48
49
|
end
|
49
50
|
|
50
|
-
|
51
|
+
# Save RBS type declarations as embedded comments
|
52
|
+
trace.save_comments
|
51
53
|
```
|
52
54
|
|
53
|
-
Automatically
|
55
|
+
Automatically insert comments into the file.
|
54
56
|
|
55
57
|
```ruby
|
56
58
|
class User
|
@@ -79,36 +81,28 @@ end
|
|
79
81
|
Add the following code to `spec/support/rbs_trace.rb`.
|
80
82
|
|
81
83
|
```ruby
|
82
|
-
return unless ENV["RBS_TRACE"]
|
83
|
-
|
84
84
|
RSpec.configure do |config|
|
85
|
-
|
85
|
+
trace = RBS::Trace.new
|
86
86
|
|
87
|
-
config.before(:suite) {
|
87
|
+
config.before(:suite) { trace.enable }
|
88
88
|
config.after(:suite) do
|
89
|
-
|
90
|
-
|
89
|
+
trace.disable
|
90
|
+
trace.save_comments
|
91
91
|
end
|
92
92
|
end
|
93
93
|
```
|
94
94
|
|
95
|
-
Then run RSpec with the environment variables.
|
96
|
-
|
97
|
-
```console
|
98
|
-
$ RBS_TRACE=1 bundle exec rspec
|
99
|
-
```
|
100
|
-
|
101
95
|
### Minitest
|
102
96
|
|
103
97
|
Add the following code to `test_helper.rb`.
|
104
98
|
|
105
99
|
```ruby
|
106
|
-
|
107
|
-
|
100
|
+
trace = RBS::Trace.new
|
101
|
+
trace.enable
|
108
102
|
|
109
103
|
Minitest.after_run do
|
110
|
-
|
111
|
-
|
104
|
+
trace.disable
|
105
|
+
trace.save_comments
|
112
106
|
end
|
113
107
|
```
|
114
108
|
|
@@ -117,11 +111,37 @@ end
|
|
117
111
|
### Insert RBS declarations for specific files only
|
118
112
|
|
119
113
|
```ruby
|
120
|
-
|
114
|
+
trace.files.each do |path, file|
|
121
115
|
file.rewrite if path.include?("app/models/")
|
122
116
|
end
|
123
117
|
```
|
124
118
|
|
119
|
+
### Save RBS declarations as files
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
trace.save_files(out_dir: "sig/trace/")
|
123
|
+
```
|
124
|
+
|
125
|
+
### Parallel testing
|
126
|
+
|
127
|
+
If you are using a parallel testing gem such as [parallel_tests](https://github.com/grosser/parallel_tests) or [flatware](https://github.com/briandunn/flatware), first save the type definitions in RBS files.
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
trace.save_files(out_dir: "tmp/sig-#{ENV["TEST_ENV_NUMBER"]}")
|
131
|
+
```
|
132
|
+
|
133
|
+
Then use `rbs-trace merge` to merge multiple RBS files into one.
|
134
|
+
|
135
|
+
```bash
|
136
|
+
$ rbs-trace merge --sig-dir='tmp/sig-*' > sig/merged.rbs
|
137
|
+
```
|
138
|
+
|
139
|
+
Finally, insert comments using the merged RBS files.
|
140
|
+
|
141
|
+
```bash
|
142
|
+
$ rbs-trace inline --rb-dir=app --rb-dir=lib
|
143
|
+
```
|
144
|
+
|
125
145
|
### Enable debug logging
|
126
146
|
|
127
147
|
If you want to enable debug logging, specify the environment variable `RBS_TRACE_DEBUG`.
|
@@ -142,4 +162,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
142
162
|
|
143
163
|
## Code of Conduct
|
144
164
|
|
145
|
-
Everyone interacting in the
|
165
|
+
Everyone interacting in the RBS::Trace project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/sinsoku/rbs-trace/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
CHANGED
@@ -12,6 +12,13 @@ RuboCop::RakeTask.new
|
|
12
12
|
desc "Generate rbs files"
|
13
13
|
task :rbs_inline do
|
14
14
|
sh "rbs-inline --output --opt-out lib"
|
15
|
+
|
16
|
+
# If the Ruby file is deleted, delete the RBS file
|
17
|
+
Dir.glob("sig/generated/**/*.rbs").each do |path|
|
18
|
+
rbs_path = Pathname(path)
|
19
|
+
rb_path = rbs_path.sub(%r{^sig/generated}, "lib").sub_ext(".rb")
|
20
|
+
rbs_path.delete unless File.exist?(rb_path)
|
21
|
+
end
|
15
22
|
end
|
16
23
|
|
17
24
|
desc "Run Steep"
|
data/exe/rbs-trace
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RBS
|
4
|
+
class Trace
|
5
|
+
class Builder
|
6
|
+
include Helpers
|
7
|
+
|
8
|
+
GENERICS_SIZE = {
|
9
|
+
Array => 1,
|
10
|
+
Range => 1,
|
11
|
+
Hash => 2
|
12
|
+
}.freeze
|
13
|
+
private_constant :GENERICS_SIZE
|
14
|
+
|
15
|
+
# @rbs (bind: Binding, parameters: Array[__todo__], void: bool) -> void
|
16
|
+
def method_call(bind:, parameters:, void:)
|
17
|
+
method_type = parse_parameters(bind, parameters)
|
18
|
+
return_type = type_void if void
|
19
|
+
|
20
|
+
stack_traces << [method_type, return_type]
|
21
|
+
end
|
22
|
+
|
23
|
+
# @rbs (__todo__) -> AST::Members::MethodDefinition::Overload
|
24
|
+
def method_return(return_value)
|
25
|
+
method_type, return_type = stack_traces.pop
|
26
|
+
|
27
|
+
type = return_type || parse_object(return_value)
|
28
|
+
new_type = method_type.type.with_return_type(type)
|
29
|
+
method_type = method_type.update(type: new_type) # rubocop:disable Style/RedundantSelfAssignment
|
30
|
+
|
31
|
+
AST::Members::MethodDefinition::Overload.new(method_type:, annotations: [])
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def stack_traces
|
37
|
+
@stack_traces ||= {} #: Hash[Thread, Array[__todo__]]
|
38
|
+
@stack_traces[Thread.current] ||= [] # steep:ignore UnannotatedEmptyCollection
|
39
|
+
end
|
40
|
+
|
41
|
+
# @rbs (Binding, Array[__todo__]) -> MethodType
|
42
|
+
def parse_parameters(bind, parameters) # rubocop:disable Metrics
|
43
|
+
fn = Types::Function.empty(type_void)
|
44
|
+
|
45
|
+
parameters.each do |kind, name| # rubocop:disable Metrics/BlockLength
|
46
|
+
case kind
|
47
|
+
when :req
|
48
|
+
value = bind.local_variable_get(name)
|
49
|
+
fn.required_positionals << Types::Function::Param.new(name: nil, type: parse_object(value))
|
50
|
+
when :opt
|
51
|
+
value = bind.local_variable_get(name)
|
52
|
+
fn.optional_positionals << Types::Function::Param.new(name: nil, type: parse_object(value))
|
53
|
+
when :rest
|
54
|
+
type = if name.nil? || name == :*
|
55
|
+
type_untyped
|
56
|
+
else
|
57
|
+
value = bind.local_variable_get(name)
|
58
|
+
parse_classes(value.map { |v| obj_to_class(v) }.uniq)
|
59
|
+
end
|
60
|
+
fn = fn.update(rest_positionals: Types::Function::Param.new(name: nil, type:)) # rubocop:disable Style/RedundantSelfAssignment
|
61
|
+
when :keyreq
|
62
|
+
value = bind.local_variable_get(name)
|
63
|
+
fn.required_keywords[name] = Types::Function::Param.new(name: nil, type: parse_object(value))
|
64
|
+
when :key
|
65
|
+
value = bind.local_variable_get(name)
|
66
|
+
fn.optional_keywords[name] = Types::Function::Param.new(name: nil, type: parse_object(value))
|
67
|
+
when :keyrest
|
68
|
+
type = if name.nil? || name == :**
|
69
|
+
type_untyped
|
70
|
+
else
|
71
|
+
value = bind.local_variable_get(name)
|
72
|
+
parse_classes(value.values.map { |v| obj_to_class(v) }.uniq)
|
73
|
+
end
|
74
|
+
fn = fn.update(rest_keywords: Types::Function::Param.new(name: nil, type:)) # rubocop:disable Style/RedundantSelfAssignment
|
75
|
+
when :block
|
76
|
+
# TODO: support block argument
|
77
|
+
next
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
MethodType.new(
|
82
|
+
type_params: [],
|
83
|
+
type: fn,
|
84
|
+
block: nil,
|
85
|
+
location: nil
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @rbs (Array[untyped]) -> Types::t
|
90
|
+
def parse_classes(classes)
|
91
|
+
types = classes.filter_map { |klass| parse_class(klass) unless klass == NilClass }.uniq
|
92
|
+
return type_nil if types.empty?
|
93
|
+
|
94
|
+
type = types.one? ? types.first : Types::Union.new(types:, location: nil) #: Types::t
|
95
|
+
if classes.include?(NilClass)
|
96
|
+
Types::Optional.new(type:, location: nil)
|
97
|
+
else
|
98
|
+
type
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# @rbs (untyped) -> Types::t
|
103
|
+
def parse_class(klass) # rubocop:disable Metrics/MethodLength
|
104
|
+
if [TrueClass, FalseClass].include?(klass)
|
105
|
+
type_bool
|
106
|
+
elsif klass == NilClass
|
107
|
+
type_nil
|
108
|
+
elsif klass == Object || klass.name.nil?
|
109
|
+
type_untyped
|
110
|
+
else
|
111
|
+
size = GENERICS_SIZE[klass].to_i
|
112
|
+
args = Array.new(size) { type_untyped }
|
113
|
+
Types::ClassInstance.new(name: TypeName.parse(klass.name), args:, location: nil)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# @rbs (BasicObject) -> Types::t
|
118
|
+
def parse_object(object)
|
119
|
+
klass = obj_to_class(object)
|
120
|
+
parse_class(klass)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RBS
|
4
|
+
class Trace
|
5
|
+
class CLI
|
6
|
+
class Inline
|
7
|
+
BANNER = <<~USAGE
|
8
|
+
Usage: rbs-trace inline --sig-dir=DIR --rb-dir=DIR
|
9
|
+
|
10
|
+
Insert RBS inline comments from RBS files.
|
11
|
+
|
12
|
+
Examples:
|
13
|
+
# Insert inline comments to `app/**/*.rb`.
|
14
|
+
$ rbs-trace inline --sig-dir=sig --rb-dir=app
|
15
|
+
|
16
|
+
# Generate RBS files with rbs-inline.
|
17
|
+
$ rbs-inline --output --opt-out app
|
18
|
+
|
19
|
+
# Remove method definitions that have been migrated to inline comments.
|
20
|
+
$ rbs subtract --write sig sig/generated/
|
21
|
+
|
22
|
+
Options:
|
23
|
+
USAGE
|
24
|
+
|
25
|
+
# @rbs (Array[String]) -> void
|
26
|
+
def run(args) # rubocop:disable Metrics
|
27
|
+
sig_dir = Pathname.pwd.join("sig").to_s
|
28
|
+
rb_dirs = [] #: Array[String]
|
29
|
+
|
30
|
+
opts = OptionParser.new(BANNER)
|
31
|
+
opts.on("--sig-dir DIR") { |dir| sig_dir = dir }
|
32
|
+
opts.on("--rb-dir DIR") { |dir| rb_dirs << dir }
|
33
|
+
opts.parse!(args)
|
34
|
+
|
35
|
+
if rb_dirs.empty?
|
36
|
+
puts opts.help
|
37
|
+
else
|
38
|
+
env = load_env(sig_dir) # steep:ignore ArgumentTypeMismatch
|
39
|
+
decls = env.class_decls.transform_values { |v| v.primary.decl }
|
40
|
+
|
41
|
+
rb_files = rb_dirs.flat_map { |rb_dir| Dir.glob("#{rb_dir}/**/*.rb") }
|
42
|
+
rb_files.each do |path|
|
43
|
+
file = File.new(path, decls)
|
44
|
+
file.rewrite
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# @rbs (String) -> Environment
|
52
|
+
def load_env(dir)
|
53
|
+
Environment.new.tap do |env|
|
54
|
+
loader = EnvironmentLoader.new(core_root: nil)
|
55
|
+
loader.add(path: Pathname(dir))
|
56
|
+
loader.load(env:)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RBS
|
4
|
+
class Trace
|
5
|
+
class CLI
|
6
|
+
class Merge
|
7
|
+
BANNER = <<~USAGE
|
8
|
+
Usage: rbs-trace merge --sig-dir=DIR
|
9
|
+
|
10
|
+
Merge multiple RBS files into one.
|
11
|
+
|
12
|
+
Examples:
|
13
|
+
# Merge RBS files in `tmp/sig-1/` and `tmp/sig-2/`.
|
14
|
+
$ rbs-trace merge --sig-dir=tmp/sig-1 --sig-dir=tmp/sig-2
|
15
|
+
|
16
|
+
# Or you can specify a glob pattern.
|
17
|
+
$ rbs-trace merge --sig-dir=tmp/sig-*
|
18
|
+
|
19
|
+
Options:
|
20
|
+
USAGE
|
21
|
+
|
22
|
+
# @rbs (Array[String]) -> void
|
23
|
+
def run(args) # rubocop:disable Metrics
|
24
|
+
sig_dirs = [] #: Array[String]
|
25
|
+
|
26
|
+
opts = OptionParser.new(BANNER)
|
27
|
+
opts.on("--sig-dir DIR") { |dir| sig_dirs << dir }
|
28
|
+
opts.parse!(args)
|
29
|
+
|
30
|
+
if sig_dirs.empty?
|
31
|
+
puts opts.help
|
32
|
+
else
|
33
|
+
envs = sig_dirs.flat_map { |sig_dir| Dir.glob(sig_dir) }
|
34
|
+
.map { |dir| load_env(dir) }
|
35
|
+
env = merge_envs(envs)
|
36
|
+
|
37
|
+
out = StringIO.new
|
38
|
+
writer = Writer.new(out:)
|
39
|
+
writer.write(env.declarations)
|
40
|
+
|
41
|
+
puts out.string
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @rbs (Array[Environment]) -> Environment
|
46
|
+
def merge_envs(others) # rubocop:disable Metrics
|
47
|
+
Environment.new.tap do |env|
|
48
|
+
others.each do |other|
|
49
|
+
other.declarations.each do |decl|
|
50
|
+
next unless decl.is_a?(AST::Declarations::Class) || decl.is_a?(AST::Declarations::Module)
|
51
|
+
|
52
|
+
entry = env.module_class_entry(decl.name.absolute!)
|
53
|
+
|
54
|
+
if entry.is_a?(Environment::MultiEntry)
|
55
|
+
decl.members.each { |member| merge(entry.primary.decl, member) }
|
56
|
+
else
|
57
|
+
env << decl
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# @rbs (String) -> Environment
|
67
|
+
def load_env(dir)
|
68
|
+
Environment.new.tap do |env|
|
69
|
+
loader = EnvironmentLoader.new(core_root: nil)
|
70
|
+
loader.add(path: Pathname(dir))
|
71
|
+
loader.load(env:)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def merge(decl, member) # rubocop:disable Metrics
|
76
|
+
case member
|
77
|
+
when AST::Declarations::Class, AST::Declarations::Module
|
78
|
+
d = decl.members.find { |m| m.is_a?(member.class) && m.name == member.name }
|
79
|
+
|
80
|
+
if d
|
81
|
+
member.members.each { |m| merge(d, m) }
|
82
|
+
else
|
83
|
+
decl.members << member
|
84
|
+
end
|
85
|
+
when AST::Members::MethodDefinition
|
86
|
+
found = decl.members.find { |m| m.is_a?(member.class) && m.name == member.name && m.kind == member.kind }
|
87
|
+
|
88
|
+
if found
|
89
|
+
(member.overloads - found.overloads).each do |overload|
|
90
|
+
found.overloads << overload
|
91
|
+
end
|
92
|
+
else
|
93
|
+
decl.members << member
|
94
|
+
end
|
95
|
+
else
|
96
|
+
decl.members << member unless decl.members.include?(member)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RBS
|
4
|
+
class Trace
|
5
|
+
class CLI
|
6
|
+
BANNER = <<~USAGE
|
7
|
+
Usage: rbs-trace <command> [<args>]
|
8
|
+
|
9
|
+
Available commands: inline, merge
|
10
|
+
USAGE
|
11
|
+
|
12
|
+
# @rbs (Array[String]) -> void
|
13
|
+
def run(args = ARGV)
|
14
|
+
opts = OptionParser.new(BANNER)
|
15
|
+
opts.version = RBS::Trace::VERSION
|
16
|
+
opts.order!(args)
|
17
|
+
command = args.shift&.to_sym
|
18
|
+
|
19
|
+
klass = command_class(command)
|
20
|
+
if klass
|
21
|
+
klass.new.run(args)
|
22
|
+
else
|
23
|
+
puts opts.help
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# @rbs (Symbol?) -> (singleton(Inline) | singleton(Merge))?
|
30
|
+
def command_class(command)
|
31
|
+
case command
|
32
|
+
when :inline
|
33
|
+
Inline
|
34
|
+
when :merge
|
35
|
+
Merge
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/rbs/trace/file.rb
CHANGED
@@ -1,29 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "stringio"
|
4
|
+
|
3
5
|
module RBS
|
4
|
-
|
6
|
+
class Trace
|
5
7
|
class File
|
6
|
-
|
7
|
-
|
8
|
+
include Helpers
|
9
|
+
|
10
|
+
# @rbs (String, ?Hash[TypeName, AST::Declarations::t]) -> void
|
11
|
+
def initialize(path, decls = {})
|
8
12
|
@path = path
|
13
|
+
@decls = decls
|
14
|
+
@members = {} #: Hash[TypeName, AST::Declarations::t]
|
9
15
|
end
|
10
16
|
|
11
|
-
# @rbs () ->
|
12
|
-
def
|
13
|
-
|
17
|
+
# @rbs (untyped, Class, Symbol) -> AST::Members::MethodDefinition?
|
18
|
+
def find_or_new_method_definition(object, defined_class, name)
|
19
|
+
klass = defined_class.singleton_class? ? object : defined_class
|
20
|
+
receiver_decl = find_or_new_receiver_decl(klass)
|
21
|
+
return unless receiver_decl
|
22
|
+
|
23
|
+
kind = defined_class.singleton_class? ? :singleton : :instance
|
24
|
+
key = [receiver_decl.name, name, kind]
|
25
|
+
@members[key] ||= new_method_definition(name:, kind:).tap do |member| # steep:ignore ArgumentTypeMismatch
|
26
|
+
receiver_decl.members << member
|
27
|
+
end
|
14
28
|
end
|
15
29
|
|
16
30
|
# @rbs () -> String
|
17
31
|
def with_rbs
|
18
|
-
|
19
|
-
|
20
|
-
|
32
|
+
result = Prism.parse_file(@path)
|
33
|
+
comments = {} #: Hash[Integer, String]
|
34
|
+
result.value.accept(InlineCommentVisitor.new(@decls, comments))
|
21
35
|
|
22
|
-
|
23
|
-
|
24
|
-
next
|
36
|
+
lines = result.source.source.lines
|
37
|
+
comments.keys.sort.reverse_each do |i|
|
38
|
+
next if skip_insert?(lines, i)
|
25
39
|
|
26
|
-
lines.insert(
|
40
|
+
lines.insert(i, comments[i])
|
27
41
|
end
|
28
42
|
lines.join
|
29
43
|
end
|
@@ -33,22 +47,53 @@ module RBS
|
|
33
47
|
::File.write(@path, with_rbs)
|
34
48
|
end
|
35
49
|
|
50
|
+
# @rbs () -> String
|
51
|
+
def to_rbs
|
52
|
+
out = StringIO.new
|
53
|
+
writer = Writer.new(out:)
|
54
|
+
writer.write(@decls.values)
|
55
|
+
|
56
|
+
out.string
|
57
|
+
end
|
58
|
+
|
59
|
+
# @rbs (String) -> void
|
60
|
+
def save_rbs(out_dir)
|
61
|
+
rbs_path = calc_rbs_path(out_dir)
|
62
|
+
|
63
|
+
rbs_path.parent.mkpath unless rbs_path.parent.exist?
|
64
|
+
rbs_path.write(to_rbs)
|
65
|
+
end
|
66
|
+
|
36
67
|
private
|
37
68
|
|
38
|
-
# @rbs (Array[String],
|
39
|
-
def skip_insert?(lines,
|
40
|
-
current = definition.lineno - 1
|
69
|
+
# @rbs (Array[String], Integer) -> boolish
|
70
|
+
def skip_insert?(lines, current)
|
41
71
|
prev = current - 1
|
42
72
|
|
43
|
-
|
44
|
-
lines[prev]&.include?("# @rbs") ||
|
73
|
+
lines[prev]&.include?("# @rbs") ||
|
45
74
|
lines[prev]&.include?("#:") ||
|
46
75
|
lines[current]&.include?("#:")
|
47
76
|
end
|
48
77
|
|
49
|
-
# @rbs () ->
|
50
|
-
def
|
51
|
-
|
78
|
+
# @rbs (Class | Module) -> (AST::Declarations::Class | AST::Declarations::Module)?
|
79
|
+
def find_or_new_receiver_decl(klass)
|
80
|
+
return unless klass.name
|
81
|
+
return if klass.name.is_a?(Symbol)
|
82
|
+
|
83
|
+
# Remove anonymous class names
|
84
|
+
class_name = klass.name.to_s.split("::").grep_v(/^#/).join("::")
|
85
|
+
name = TypeName.parse(class_name)
|
86
|
+
|
87
|
+
@decls[name.absolute!] ||= klass.is_a?(Class) ? new_class_decl(name:) : new_module_decl(name:)
|
88
|
+
end
|
89
|
+
|
90
|
+
# @rbs (String) -> Pathname
|
91
|
+
def calc_rbs_path(out_dir)
|
92
|
+
rb_path = Pathname(@path).sub(Pathname.pwd.to_s, "")
|
93
|
+
rb_path = rb_path.relative_path_from("/") if rb_path.absolute?
|
94
|
+
rbs_path = rb_path.sub_ext(".rbs")
|
95
|
+
|
96
|
+
Pathname(out_dir) + rbs_path
|
52
97
|
end
|
53
98
|
end
|
54
99
|
end
|