rbs 0.2.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 +7 -0
- data/.github/workflows/ruby.yml +28 -0
- data/.gitignore +12 -0
- data/.rubocop.yml +15 -0
- data/BSDL +22 -0
- data/CHANGELOG.md +9 -0
- data/COPYING +56 -0
- data/Gemfile +6 -0
- data/README.md +93 -0
- data/Rakefile +142 -0
- data/bin/annotate-with-rdoc +157 -0
- data/bin/console +14 -0
- data/bin/query-rdoc +103 -0
- data/bin/setup +10 -0
- data/bin/sort +89 -0
- data/bin/test_runner.rb +16 -0
- data/docs/CONTRIBUTING.md +97 -0
- data/docs/sigs.md +148 -0
- data/docs/stdlib.md +152 -0
- data/docs/syntax.md +528 -0
- data/exe/rbs +7 -0
- data/lib/rbs.rb +64 -0
- data/lib/rbs/ast/annotation.rb +27 -0
- data/lib/rbs/ast/comment.rb +27 -0
- data/lib/rbs/ast/declarations.rb +395 -0
- data/lib/rbs/ast/members.rb +362 -0
- data/lib/rbs/buffer.rb +50 -0
- data/lib/rbs/builtin_names.rb +55 -0
- data/lib/rbs/cli.rb +558 -0
- data/lib/rbs/constant.rb +26 -0
- data/lib/rbs/constant_table.rb +150 -0
- data/lib/rbs/definition.rb +170 -0
- data/lib/rbs/definition_builder.rb +919 -0
- data/lib/rbs/environment.rb +281 -0
- data/lib/rbs/environment_loader.rb +136 -0
- data/lib/rbs/environment_walker.rb +124 -0
- data/lib/rbs/errors.rb +187 -0
- data/lib/rbs/location.rb +102 -0
- data/lib/rbs/method_type.rb +123 -0
- data/lib/rbs/namespace.rb +91 -0
- data/lib/rbs/parser.y +1344 -0
- data/lib/rbs/prototype/rb.rb +553 -0
- data/lib/rbs/prototype/rbi.rb +587 -0
- data/lib/rbs/prototype/runtime.rb +381 -0
- data/lib/rbs/substitution.rb +46 -0
- data/lib/rbs/test.rb +26 -0
- data/lib/rbs/test/errors.rb +61 -0
- data/lib/rbs/test/hook.rb +294 -0
- data/lib/rbs/test/setup.rb +58 -0
- data/lib/rbs/test/spy.rb +325 -0
- data/lib/rbs/test/test_helper.rb +183 -0
- data/lib/rbs/test/type_check.rb +254 -0
- data/lib/rbs/type_name.rb +70 -0
- data/lib/rbs/types.rb +936 -0
- data/lib/rbs/variance_calculator.rb +138 -0
- data/lib/rbs/vendorer.rb +47 -0
- data/lib/rbs/version.rb +3 -0
- data/lib/rbs/writer.rb +269 -0
- data/lib/ruby/signature.rb +7 -0
- data/rbs.gemspec +46 -0
- data/stdlib/abbrev/abbrev.rbs +60 -0
- data/stdlib/base64/base64.rbs +71 -0
- data/stdlib/benchmark/benchmark.rbs +372 -0
- data/stdlib/builtin/array.rbs +1997 -0
- data/stdlib/builtin/basic_object.rbs +280 -0
- data/stdlib/builtin/binding.rbs +177 -0
- data/stdlib/builtin/builtin.rbs +45 -0
- data/stdlib/builtin/class.rbs +145 -0
- data/stdlib/builtin/comparable.rbs +116 -0
- data/stdlib/builtin/complex.rbs +400 -0
- data/stdlib/builtin/constants.rbs +37 -0
- data/stdlib/builtin/data.rbs +5 -0
- data/stdlib/builtin/deprecated.rbs +2 -0
- data/stdlib/builtin/dir.rbs +413 -0
- data/stdlib/builtin/encoding.rbs +607 -0
- data/stdlib/builtin/enumerable.rbs +404 -0
- data/stdlib/builtin/enumerator.rbs +260 -0
- data/stdlib/builtin/errno.rbs +781 -0
- data/stdlib/builtin/errors.rbs +582 -0
- data/stdlib/builtin/exception.rbs +194 -0
- data/stdlib/builtin/false_class.rbs +40 -0
- data/stdlib/builtin/fiber.rbs +68 -0
- data/stdlib/builtin/fiber_error.rbs +12 -0
- data/stdlib/builtin/file.rbs +1076 -0
- data/stdlib/builtin/file_test.rbs +59 -0
- data/stdlib/builtin/float.rbs +696 -0
- data/stdlib/builtin/gc.rbs +243 -0
- data/stdlib/builtin/hash.rbs +1029 -0
- data/stdlib/builtin/integer.rbs +707 -0
- data/stdlib/builtin/io.rbs +683 -0
- data/stdlib/builtin/kernel.rbs +576 -0
- data/stdlib/builtin/marshal.rbs +161 -0
- data/stdlib/builtin/match_data.rbs +271 -0
- data/stdlib/builtin/math.rbs +369 -0
- data/stdlib/builtin/method.rbs +185 -0
- data/stdlib/builtin/module.rbs +1104 -0
- data/stdlib/builtin/nil_class.rbs +82 -0
- data/stdlib/builtin/numeric.rbs +409 -0
- data/stdlib/builtin/object.rbs +824 -0
- data/stdlib/builtin/proc.rbs +429 -0
- data/stdlib/builtin/process.rbs +1227 -0
- data/stdlib/builtin/random.rbs +267 -0
- data/stdlib/builtin/range.rbs +226 -0
- data/stdlib/builtin/rational.rbs +424 -0
- data/stdlib/builtin/rb_config.rbs +57 -0
- data/stdlib/builtin/regexp.rbs +1083 -0
- data/stdlib/builtin/ruby_vm.rbs +14 -0
- data/stdlib/builtin/signal.rbs +55 -0
- data/stdlib/builtin/string.rbs +1901 -0
- data/stdlib/builtin/string_io.rbs +284 -0
- data/stdlib/builtin/struct.rbs +40 -0
- data/stdlib/builtin/symbol.rbs +228 -0
- data/stdlib/builtin/thread.rbs +1108 -0
- data/stdlib/builtin/thread_group.rbs +23 -0
- data/stdlib/builtin/time.rbs +1047 -0
- data/stdlib/builtin/trace_point.rbs +290 -0
- data/stdlib/builtin/true_class.rbs +46 -0
- data/stdlib/builtin/unbound_method.rbs +153 -0
- data/stdlib/builtin/warning.rbs +17 -0
- data/stdlib/coverage/coverage.rbs +62 -0
- data/stdlib/csv/csv.rbs +773 -0
- data/stdlib/erb/erb.rbs +392 -0
- data/stdlib/find/find.rbs +40 -0
- data/stdlib/ipaddr/ipaddr.rbs +247 -0
- data/stdlib/json/json.rbs +335 -0
- data/stdlib/pathname/pathname.rbs +1093 -0
- data/stdlib/prime/integer-extension.rbs +23 -0
- data/stdlib/prime/prime.rbs +188 -0
- data/stdlib/securerandom/securerandom.rbs +9 -0
- data/stdlib/set/set.rbs +301 -0
- data/stdlib/tmpdir/tmpdir.rbs +53 -0
- metadata +292 -0
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "rbs"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/query-rdoc
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "rdoc"
|
|
4
|
+
|
|
5
|
+
def store_for_class(name, stores:)
|
|
6
|
+
stores.find do |store|
|
|
7
|
+
store.find_class_named(name) || store.find_module_named(name)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def format_comment(comment)
|
|
12
|
+
out = RDoc::Markup::Document.new
|
|
13
|
+
out << comment
|
|
14
|
+
formatter = RDoc::Markup::ToMarkdown.new
|
|
15
|
+
out.accept(formatter)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def comment_for_constant(name, stores:)
|
|
19
|
+
*class_components, const_name = name.split(/::/)
|
|
20
|
+
class_name = class_components.join("::")
|
|
21
|
+
|
|
22
|
+
klass = store_for_class(class_name, stores: stores)&.yield_self {|store|
|
|
23
|
+
store.find_class_named(class_name) || store.find_module_named(class_name)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
constant = klass.constants.find do |const|
|
|
27
|
+
const.name == const_name
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if constant&.documented?
|
|
31
|
+
format_comment(constant.comment)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def comment_for_class(class_name, stores:)
|
|
36
|
+
klass = store_for_class(class_name, stores: stores)&.yield_self {|store|
|
|
37
|
+
store.find_class_named(class_name) || store.find_module_named(class_name)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if klass&.documented?
|
|
41
|
+
format_comment(klass.comment)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def comment_for_method(klass, method, stores:)
|
|
46
|
+
method = store_for_class(klass, stores: stores)&.load_method(klass, method)
|
|
47
|
+
|
|
48
|
+
if method&.documented?
|
|
49
|
+
out = RDoc::Markup::Document.new
|
|
50
|
+
|
|
51
|
+
out << method.comment
|
|
52
|
+
|
|
53
|
+
if method.arglists
|
|
54
|
+
out << RDoc::Markup::Heading.new(1, "arglists 💪👽🚨 << Delete this section")
|
|
55
|
+
arglists = method.arglists.chomp.split("\n").map {|line| line + "\n" }
|
|
56
|
+
out << RDoc::Markup::Verbatim.new(*arglists)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
out.accept(RDoc::Markup::ToMarkdown.new)
|
|
60
|
+
end
|
|
61
|
+
rescue RDoc::Store::MissingFileError
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if ARGV.empty?
|
|
66
|
+
puts 'query-rdoc [subject]'
|
|
67
|
+
puts " subject ::= ClassName (class, module, or constant)"
|
|
68
|
+
puts " | ClassName.method (singleton method)"
|
|
69
|
+
puts " | ClassName#method (instance method)"
|
|
70
|
+
exit
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
stores = []
|
|
74
|
+
RDoc::RI::Paths.each true, true, false, false do |path, type|
|
|
75
|
+
STDERR.puts "Loading store from #{path}..."
|
|
76
|
+
store = RDoc::RI::Store.new(path, type)
|
|
77
|
+
store.load_all
|
|
78
|
+
stores << store
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
subject = ARGV[0]
|
|
82
|
+
|
|
83
|
+
case
|
|
84
|
+
when match = subject.match(/(?<constant_name>[^#]+)#(?<method_name>.+)/)
|
|
85
|
+
STDERR.puts "Finding instance method #{match[:constant_name]} # #{match[:method_name]} ..."
|
|
86
|
+
comment = comment_for_method(match[:constant_name], "##{match[:method_name]}", stores: stores)
|
|
87
|
+
when match = subject.match(/(?<constant_name>[^.]+)\.(?<method_name>.+)/)
|
|
88
|
+
STDERR.puts "Finding singleton method #{match[:constant_name]} . #{match[:method_name]} ..."
|
|
89
|
+
comment = comment_for_method(match[:constant_name], "::#{match[:method_name]}", stores: stores)
|
|
90
|
+
else
|
|
91
|
+
STDERR.puts "Finding class/module/constant #{subject} ..."
|
|
92
|
+
comment = comment_for_class(subject, stores: stores) || comment_for_constant(subject, stores: stores)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
if comment
|
|
96
|
+
STDERR.puts "Printing document..."
|
|
97
|
+
comment.each_line do |line|
|
|
98
|
+
puts "# #{line}"
|
|
99
|
+
end
|
|
100
|
+
else
|
|
101
|
+
STDERR.puts "Nothing to print; failed to query the document..."
|
|
102
|
+
exit 1
|
|
103
|
+
end
|
data/bin/setup
ADDED
data/bin/sort
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "rbs"
|
|
5
|
+
|
|
6
|
+
Members = RBS::AST::Members
|
|
7
|
+
|
|
8
|
+
def group(member)
|
|
9
|
+
case member
|
|
10
|
+
when Members::Include, Members::Extend, Members::Prepend
|
|
11
|
+
0
|
|
12
|
+
when Members::ClassVariable
|
|
13
|
+
-3
|
|
14
|
+
when Members::ClassInstanceVariable
|
|
15
|
+
-2
|
|
16
|
+
when Members::InstanceVariable
|
|
17
|
+
-1
|
|
18
|
+
when Members::AttrAccessor, Members::AttrWriter, Members::AttrReader
|
|
19
|
+
2
|
|
20
|
+
when Members::MethodDefinition
|
|
21
|
+
if member.singleton?
|
|
22
|
+
if member.name == :new
|
|
23
|
+
0.4
|
|
24
|
+
else
|
|
25
|
+
1
|
|
26
|
+
end
|
|
27
|
+
else
|
|
28
|
+
if member.name == :initialize
|
|
29
|
+
0.5
|
|
30
|
+
else
|
|
31
|
+
3
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
when Members::Alias
|
|
35
|
+
if member.singleton?
|
|
36
|
+
1
|
|
37
|
+
else
|
|
38
|
+
3
|
|
39
|
+
end
|
|
40
|
+
when Members::Private, Members::Public
|
|
41
|
+
-4
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def key(member)
|
|
46
|
+
case member
|
|
47
|
+
when Members::Include, Members::Extend, Members::Prepend
|
|
48
|
+
member.name.to_s
|
|
49
|
+
when Members::ClassVariable, Members::ClassInstanceVariable, Members::InstanceVariable
|
|
50
|
+
member.name.to_s
|
|
51
|
+
when Members::AttrAccessor, Members::AttrWriter, Members::AttrReader
|
|
52
|
+
member.name.to_s
|
|
53
|
+
when Members::MethodDefinition
|
|
54
|
+
member.name.to_s
|
|
55
|
+
when Members::Alias
|
|
56
|
+
member.new_name.to_s
|
|
57
|
+
else
|
|
58
|
+
1
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
ARGV.map {|f| Pathname(f) }.each do |path|
|
|
63
|
+
puts "Opening #{path}..."
|
|
64
|
+
|
|
65
|
+
buffer = RBS::Buffer.new(name: path, content: path.read)
|
|
66
|
+
sigs = RBS::Parser.parse_signature(buffer)
|
|
67
|
+
|
|
68
|
+
sigs.each do |sig|
|
|
69
|
+
case sig
|
|
70
|
+
when RBS::AST::Declarations::Class, RBS::AST::Declarations::Module, RBS::AST::Declarations::Interface
|
|
71
|
+
sig.members.sort! do |m1, m2|
|
|
72
|
+
group1 = group(m1)
|
|
73
|
+
group2 = group(m2)
|
|
74
|
+
|
|
75
|
+
if group1 == group2
|
|
76
|
+
key(m1) <=> key(m2)
|
|
77
|
+
else
|
|
78
|
+
group1 <=> group2
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
puts "Writing #{path}..."
|
|
85
|
+
path.open('w') do |out|
|
|
86
|
+
writer = RBS::Writer.new(out: out)
|
|
87
|
+
writer.write sigs
|
|
88
|
+
end
|
|
89
|
+
end
|
data/bin/test_runner.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
RUBY_27 = Gem::Version.new(RUBY_VERSION).yield_self do |ruby_version|
|
|
4
|
+
Gem::Version.new('2.7.0') <= ruby_version && ruby_version < Gem::Version.new("2.8.0")
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
unless RUBY_27
|
|
8
|
+
STDERR.puts "🚨🚨🚨 stdlib test requires Ruby 2.7 but RUBY_VERSION==#{RUBY_VERSION}, exiting... 🚨🚨🚨"
|
|
9
|
+
exit
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require "pathname"
|
|
13
|
+
|
|
14
|
+
ARGV.each do |arg|
|
|
15
|
+
load arg
|
|
16
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Standard Library Signatures Contribution Guide
|
|
2
|
+
|
|
3
|
+
## Guides
|
|
4
|
+
|
|
5
|
+
* [Stdlib Signatures Guide](stdlib.md)
|
|
6
|
+
* [Syntax](syntax.md)
|
|
7
|
+
* [Writing Signature Guide](sigs.md)
|
|
8
|
+
|
|
9
|
+
## Steps for Contribution
|
|
10
|
+
|
|
11
|
+
1. Pick the class/library you will work for.
|
|
12
|
+
2. Assign yourself on the [work spreadsheet](https://docs.google.com/spreadsheets/d/199rRB93I16H0k4TGZS3EGojns2R0W1oCsN8UPJzOpyU/edit#gid=1383307992) (optional but recommended to avoid duplication).
|
|
13
|
+
3. Sort RBS members (if there is RBS files for the classes).
|
|
14
|
+
- Use `bin/sort stdlib/path/to/signature.rbs` command and confirm it does not break definitions.
|
|
15
|
+
- Committing the sorted RBSs is recommended.
|
|
16
|
+
4. Add method prototypes.
|
|
17
|
+
- Use `rbs prototype runtime --merge CLASS_NAME` command to generate the missing method definitions.
|
|
18
|
+
- Committing the auto generated signatures is recommended.
|
|
19
|
+
5. Annotate with RDoc.
|
|
20
|
+
- Use `bin/annotate-with-rdoc stdlib/path/to/signature.rbs` to annotate the RBS files.
|
|
21
|
+
- Committing the generated annotations is recommended.
|
|
22
|
+
6. Fix method types and comments.
|
|
23
|
+
- The auto generated RDoc comments include `arglists` section, which we don't expect to be included the RBS files.
|
|
24
|
+
- Delete the `arglists` sections.
|
|
25
|
+
- Give methods correct types.
|
|
26
|
+
- Write tests, if possible. (If it is too difficult to write test, skip it.)
|
|
27
|
+
|
|
28
|
+
## The Target Version
|
|
29
|
+
|
|
30
|
+
* The standard library signatures targets Ruby 2.7 for now.
|
|
31
|
+
* The library code targets Ruby 2.6, 2.7, and 3.0.
|
|
32
|
+
|
|
33
|
+
## Stdlib Worksheet
|
|
34
|
+
|
|
35
|
+
You can find the list of classes/libraries:
|
|
36
|
+
|
|
37
|
+
* https://docs.google.com/spreadsheets/d/199rRB93I16H0k4TGZS3EGojns2R0W1oCsN8UPJzOpyU/edit#gid=1383307992
|
|
38
|
+
|
|
39
|
+
Assign yourself when you start working for a class or library.
|
|
40
|
+
After reviewing and merging your pull request, I will update the status of the library.
|
|
41
|
+
|
|
42
|
+
You may find the *Good for first contributor* column where you can find some classes are recommended for new contributors (👍), and some classes are not-recommended (👎).
|
|
43
|
+
|
|
44
|
+
## Useful Tools
|
|
45
|
+
|
|
46
|
+
* `rbs prototype runtime --merge String`
|
|
47
|
+
* Generate a prototype using runtime API.
|
|
48
|
+
* `--merge` tells to use the method types in RBS if exists.
|
|
49
|
+
* `rbs prototype runtime --merge --method-owner=Numeric Integer`
|
|
50
|
+
* You can use --method-owner if you want to print method of other classes too, for documentation purpose.
|
|
51
|
+
* `bin/annotate-with-rdoc stdlib/builtin/string.rbs`
|
|
52
|
+
* Write comments using RDoc.
|
|
53
|
+
* It contains arglists section, but I don't think we should have it in RBS files.
|
|
54
|
+
* `bin/query-rdoc String#initialize`
|
|
55
|
+
* Print RDoc documents in the format you can copy-and-paste to RBS.
|
|
56
|
+
* `bin/sort stdlib/builtin/string.rbs`
|
|
57
|
+
* Sort declarations members in RBS files.
|
|
58
|
+
* `rbs validate -r LIB`
|
|
59
|
+
Validate the syntax and some of the semantics.
|
|
60
|
+
* `rake generate:stdlib_test[String]`
|
|
61
|
+
Scaffold the stdlib test.
|
|
62
|
+
|
|
63
|
+
## Standard STDLIB Members Order
|
|
64
|
+
|
|
65
|
+
We define the standard members order so that ordering doesn't bother reading diffs or git-annotate outputs.
|
|
66
|
+
|
|
67
|
+
1. `def self.new` or `def initialize`
|
|
68
|
+
2. Mixins
|
|
69
|
+
3. Attributes
|
|
70
|
+
4. Singleton methods
|
|
71
|
+
5. `public` & public instance methods
|
|
72
|
+
6. `private` & private instance methods
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
class HelloWorld[X]
|
|
76
|
+
def self.new: [A] () { (void) -> A } -> HelloWorld[A] # new or initialize comes first
|
|
77
|
+
def initialize: () -> void
|
|
78
|
+
|
|
79
|
+
include Enumerable[X, void] # Mixin comes next
|
|
80
|
+
|
|
81
|
+
attr_reader language: (:ja | :en) # Attributes
|
|
82
|
+
|
|
83
|
+
def self.all_languages: () -> Array[Symbol] # Singleton methods
|
|
84
|
+
|
|
85
|
+
public # Public instance methods
|
|
86
|
+
|
|
87
|
+
def each: () { (A) -> void } -> void # Members are sorted dicionary order
|
|
88
|
+
|
|
89
|
+
def to_s: (?Locale) -> String
|
|
90
|
+
|
|
91
|
+
private # Private instance methods
|
|
92
|
+
|
|
93
|
+
alias validate validate_locale
|
|
94
|
+
|
|
95
|
+
def validate_locale: () -> void
|
|
96
|
+
end
|
|
97
|
+
```
|
data/docs/sigs.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Writing Signatures Guide
|
|
2
|
+
|
|
3
|
+
You can write the signature of your applications and libraries.
|
|
4
|
+
Signature of your Ruby program would help:
|
|
5
|
+
|
|
6
|
+
1. Understanding the code structure
|
|
7
|
+
2. Finding APIs
|
|
8
|
+
|
|
9
|
+
And if you ship your gem with signature, the gem users can type check their applications!
|
|
10
|
+
|
|
11
|
+
## Writing signatures
|
|
12
|
+
|
|
13
|
+
You first need to write your program's signature.
|
|
14
|
+
See [syntax guide](syntax.md).
|
|
15
|
+
|
|
16
|
+
## Testing signatures
|
|
17
|
+
|
|
18
|
+
When you finish writing signature, you may want to test the signature.
|
|
19
|
+
ruby-signature provides a feature to test your signature.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
$ RBS_TEST_TARGET='Foo::*' bundle exec ruby -r rbs/test/setup test/foo_test.rb
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The test installs instrumentations to spy the method calls and check if arguments/return values are correct with respect to the type of the method in signature.
|
|
26
|
+
If errors are reported by the test, you will fix the signature.
|
|
27
|
+
You will be sure that you ship a correct signature finally.
|
|
28
|
+
|
|
29
|
+
The instrumentations are implemneted using `Module#prepend`.
|
|
30
|
+
It defines a module with same name of methods, which asserts the type of arguments/return values and calls `super`.
|
|
31
|
+
|
|
32
|
+
## Type errors
|
|
33
|
+
|
|
34
|
+
If the test detects type errors, it will print error messages.
|
|
35
|
+
|
|
36
|
+
### ArgumentTypeError, BlockArgumentTypeError
|
|
37
|
+
|
|
38
|
+
The message means there is an unexpected type of argument or block argument.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
ERROR -- : [Kaigi::Speaker.new] ArgumentTypeError: expected `::String` (email) but given `:"matsumoto@soutaro.com"`
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### ArgumentError, BlockArgumentError
|
|
45
|
+
|
|
46
|
+
The message means there is an unexpected argument or missing argument.
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
[Kaigi::Speaker.new] ArgumentError: expected method type (size: ::Symbol, email: ::String, name: ::String) -> ::Kaigi::Speaker
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### ReturnTypeError, BlockReturnTypeError
|
|
53
|
+
|
|
54
|
+
The message means the return value from method or block is incorrect.
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
ERROR -- : [Kaigi::Conference#each_speaker] ReturnTypeError: expected `self` but returns `[#<Kaigi::Speaker:0x00007fb2b249e5a0 @name="Soutaro Matsumoto", @email=:"matsumoto@soutaro.com">]`
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### UnexpectedBlockError, MissingBlockError
|
|
61
|
+
|
|
62
|
+
The errors are reported when required block is not given or unused block is given.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
ERROR -- : [Kaigi::Conference#speakers] UnexpectedBlockError: unexpected block is given for `() -> ::Array[::Kaigi::Speaker]`
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### UnresolvedOverloadingError
|
|
69
|
+
|
|
70
|
+
The error means there is a type error on overloaded methods.
|
|
71
|
+
The `ruby-signature` test framework tries to the best error message for overloaded methods too, but it reports the `UnresolvedOverloadingError` when it fails.
|
|
72
|
+
|
|
73
|
+
## Setting up the test
|
|
74
|
+
|
|
75
|
+
The design of the signature testing aims to be non-intrusive. The setup is done in two steps:
|
|
76
|
+
|
|
77
|
+
1. Loading the testing library
|
|
78
|
+
2. Setting up the test through environment variables
|
|
79
|
+
|
|
80
|
+
### Loading the library
|
|
81
|
+
|
|
82
|
+
You need to require `rbs/test/setup` for signature testing.
|
|
83
|
+
You can do it using `-r` option through command line argument or the `RUBYOPT` environment variable.
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
$ ruby -r rbs/test/setup run_tests.rb
|
|
87
|
+
$ RUBYOPT='-rruby/signature/test/setup' rake test
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
When you are using Bundler, you may need to require `bundler/setup` explicitly.
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
$ RUBYOPT='-rbundler/setup -rruby/signature/test/setup' bundle exec rake test
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Environment variables
|
|
97
|
+
|
|
98
|
+
You need to specify `RBS_TEST_TARGET` to run the test, and you can customize the test with the following environment variables.
|
|
99
|
+
|
|
100
|
+
- `RBS_TEST_SKIP` (optional)
|
|
101
|
+
- `RBS_TEST_OPT` (optional)
|
|
102
|
+
- `RBS_TEST_LOGLEVEL` (optional)
|
|
103
|
+
- `RBS_TEST_RAISE` (optional)
|
|
104
|
+
|
|
105
|
+
`RBS_TEST_TARGET` is to specify the classes you want to test. `RBS_TEST_TARGET` can contain comma-separated class name pattern, which is one of an exact class name or with wildcard `*`.
|
|
106
|
+
|
|
107
|
+
- `RBS_TEST_TARGET=Foo::Bar,Foo::Baz` comma separated exact class names
|
|
108
|
+
- `RBS_TEST_TARGET=Foo::*` using wildcard
|
|
109
|
+
|
|
110
|
+
`RBS_TEST_SKIP` is to skip some of the classes which matches with `RBS_TEST_TARGET`.
|
|
111
|
+
|
|
112
|
+
`RBS_TEST_OPT` is to pass the options for ruby signature handling.
|
|
113
|
+
You may need to specify `-r` or `-I` to load signatures.
|
|
114
|
+
The default is `-I sig`.
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
RBS_TEST_OPT='-r set -r pathname -I sig'
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`RBS_TEST_LOGLEVEL` can be used to configure log level. Defaults to `info`.
|
|
121
|
+
|
|
122
|
+
`RBS_TEST_RAISE` may help to debug the type signatures.
|
|
123
|
+
If the environment variable is set, it raises an exception when a type error is detected.
|
|
124
|
+
You can see the backtrace how the type error is caused and debug your program or signature.
|
|
125
|
+
|
|
126
|
+
So, a typical command line to start the test would look like the following:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
$ RBS_TEST_LOGLEVEL=error \
|
|
130
|
+
RBS_TEST_TARGET='Kaigi::*' \
|
|
131
|
+
RBS_TEST_SKIP='Kaigi::MonkeyPatch' \
|
|
132
|
+
RBS_TEST_OPT='-rset -rpathname -Isig -Iprivate' \
|
|
133
|
+
RBS_TEST_RAISE=true \
|
|
134
|
+
RUBYOPT='-rbundler/setup -rruby/signature/test/setup' \
|
|
135
|
+
bundle exec rake test
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Testing tips
|
|
139
|
+
|
|
140
|
+
### Skipping a method
|
|
141
|
+
|
|
142
|
+
You can skip installing the instrumentation per-method basis using `rbs:test:skip` annotation.
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
class String
|
|
146
|
+
%a{rbs:test:skip} def =~: (Regexp) -> Integer?
|
|
147
|
+
end
|
|
148
|
+
```
|