yarbs 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14192c2d967c20c40a4ed4e9d24d810b6bcaf8fa957261c27d904301f5d6e980
4
- data.tar.gz: 7a2bc9f86103deaf360be3c6fabcd5895d6bca0280ab1f0c95a426921852448f
3
+ metadata.gz: ad6ad92289f8a7d901231eadb81be04511912ffd65f183d0cd00d4f9d2b9b851
4
+ data.tar.gz: 1cae1aaf74795fef87a77eafd33b32c7a8252dd3c0660032d11c9985213ab965
5
5
  SHA512:
6
- metadata.gz: 50961f4fa46908f161a8748602fa2a282cbb00a018a44d7f701b96043e7ceb1332359526318359efd7d86cdce8fa944aac2a442d7daedd70a7caa5eb0e24b74e
7
- data.tar.gz: 5f59a649d92e618223e4b60bd23d05b7e2a7f68a08818a70083bf17b5981e2e11cf2bc2a55f9821528fc1359c36da04d976cb4b1db021209b79d98dc119e16d3
6
+ metadata.gz: dce1a1abee4e2d215abf7fc9ca707c00dc166790d86daa644d2bd43ab29348dade7eda387ada1ac39d0bad428e19f4d19321cf57af465d5490376d06fb02b78d
7
+ data.tar.gz: 84096e966259189840b11c1e7d02d5e2b1cbd29fe429d262e9e8733d0d503505ae92706ac3effcbdaafe6fb903fe152bb69cad59159d979862f7cbd37726451e
data/README.md CHANGED
@@ -102,6 +102,8 @@ see:
102
102
  (`@yieldparam`/`@yieldreturn`), and typing a `Proc`/lambda parameter
103
103
  - [docs/classes.md](docs/classes.md) — classes, modules, nesting,
104
104
  inheritance, mixins, `attr_*`, constants, and visibility
105
+ - [docs/deprecated.md](docs/deprecated.md) — `@deprecated`, turned into
106
+ `%a{deprecated}` so Steep warns wherever deprecated code is used
105
107
 
106
108
  ## Development
107
109
 
@@ -114,12 +116,6 @@ for yarbs' own dependencies. From there:
114
116
  - `bundle exec exe/yarbs "lib/**/*.rb" -o sig` regenerates yarbs' own `sig/`
115
117
  from its own docs (it's self-hosting)
116
118
 
117
- To install this gem onto your local machine, run `bundle exec rake install`.
118
- To release a new version, update the version number in `version.rb`, and
119
- then run `bundle exec rake release`, which will create a git tag for the
120
- version, push git commits and the created tag, and push the `.gem` file to
121
- [rubygems.org](https://rubygems.org).
122
-
123
119
  ## Contributing
124
120
 
125
121
  Bug reports and pull requests are welcome on GitHub at
@@ -30,7 +30,10 @@ module Yarbs
30
30
  case decl
31
31
  when RBS::AST::Declarations::Class, RBS::AST::Declarations::Module
32
32
  inner_namespace = namespace + [decl.name.to_s]
33
- decl.update(members: visit_all(decl.members, inner_namespace))
33
+ decl.update(
34
+ members: visit_all(decl.members, inner_namespace),
35
+ annotations: with_deprecation(decl.annotations, YARD::Registry.at(inner_namespace.join("::")))
36
+ )
34
37
  when RBS::AST::Declarations::Constant
35
38
  annotate_constant(decl, namespace)
36
39
  when RBS::AST::Members::MethodDefinition
@@ -47,14 +50,13 @@ module Yarbs
47
50
  def annotate_constant(decl, namespace)
48
51
  yard_object = YARD::Registry.at("#{namespace.join("::")}::#{decl.name}")
49
52
  types = return_types_of(yard_object)
50
- return decl if types.empty?
51
53
 
52
54
  RBS::AST::Declarations::Constant.new(
53
55
  name: decl.name,
54
- type: convert(types),
56
+ type: types.empty? ? decl.type : convert(types),
55
57
  location: decl.location,
56
58
  comment: decl.comment,
57
- annotations: decl.annotations
59
+ annotations: with_deprecation(decl.annotations, yard_object)
58
60
  )
59
61
  end
60
62
 
@@ -64,7 +66,7 @@ module Yarbs
64
66
  return member unless yard_method
65
67
 
66
68
  overloads = member.overloads.map { |overload| annotate_overload(overload, yard_method) }
67
- member.update(overloads: overloads)
69
+ member.update(overloads: overloads, annotations: with_deprecation(member.annotations, yard_method))
68
70
  end
69
71
 
70
72
  def annotate_attribute(member, namespace)
@@ -75,9 +77,11 @@ module Yarbs
75
77
  suffix = member.is_a?(RBS::AST::Members::AttrWriter) ? "=" : ""
76
78
  yard_attr = YARD::Registry.at("#{namespace.join("::")}#{sep}#{member.name}#{suffix}")
77
79
  types = return_types_of(yard_attr)
78
- return member if types.empty?
79
80
 
80
- member.update(type: convert(types))
81
+ member.update(
82
+ type: types.empty? ? member.type : convert(types),
83
+ annotations: with_deprecation(member.annotations, yard_attr)
84
+ )
81
85
  end
82
86
 
83
87
  def annotate_overload(overload, yard_method)
@@ -130,7 +134,29 @@ module Yarbs
130
134
  function
131
135
  end
132
136
 
133
- RBS::Types::Block.new(type: new_function, required: block.required, self_type: block.self_type)
137
+ required = block.required && !optional_block?(block, yard_method)
138
+ RBS::Types::Block.new(type: new_function, required: required, self_type: block.self_type)
139
+ end
140
+
141
+ # `rbs prototype rb` marks an explicit `&block` as required unless the
142
+ # body checks `block_given?` or only uses the block conditionally, so a
143
+ # block that's merely captured or forwarded (`other(&block)`) comes out
144
+ # required. Treat the block as optional when:
145
+ #
146
+ # - its `@param` tag includes `nil` (`@param block [Proc, nil]`), or
147
+ # - the body never `yield`s (the block type is an `UntypedFunction`) and
148
+ # no `@yield`/`@yieldparam`/`@yieldreturn` tag documents a contract
149
+ # for callers to fulfil.
150
+ def optional_block?(block, yard_method)
151
+ block_param = yard_method.parameters.find { |name, _| name.to_s.start_with?("&") }&.first
152
+ return false unless block_param
153
+
154
+ name = block_param.to_s.delete_prefix("&")
155
+ tag = yard_method.tags(:param).find { |param| param.name.to_s.delete_prefix("&") == name }
156
+ return true if tag&.types&.include?("nil")
157
+
158
+ block.type.is_a?(RBS::Types::UntypedFunction) &&
159
+ %i[yield yieldparam yieldreturn].none? { |tag_name| yard_method.has_tag?(tag_name) }
134
160
  end
135
161
 
136
162
  def annotate_block_function(function, yield_params, new_return_type)
@@ -263,6 +289,24 @@ module Yarbs
263
289
  yard_object.tags(:return).flat_map { |tag| tag.types || [] }
264
290
  end
265
291
 
292
+ # Appends a `%a{deprecated}` annotation when the YARD object has a
293
+ # `@deprecated` tag, which Steep reports as `Ruby::DeprecatedReference`
294
+ # wherever the declaration is used. The tag's text becomes the message
295
+ # (`%a{deprecated: Use #bar instead}`), flattened onto one line since
296
+ # Steep only reads the message up to the first newline.
297
+ def with_deprecation(annotations, yard_object)
298
+ tag = yard_object&.tag(:deprecated)
299
+ return annotations unless tag
300
+
301
+ message = tag.text.to_s.split.join(" ")
302
+ string = message.empty? ? "deprecated" : "deprecated: #{message}"
303
+ # RBS::Writer picks whichever %a delimiter pair doesn't appear in the
304
+ # string, and raises if every one of them does.
305
+ string = "deprecated" if ["}", ")", "]", ">", "|"].all? { |char| string.include?(char) }
306
+
307
+ annotations + [RBS::AST::Annotation.new(string: string, location: nil)]
308
+ end
309
+
266
310
  def convert(types)
267
311
  TypeConverter.convert(types, strict: @strict)
268
312
  end
@@ -49,7 +49,7 @@ module Yarbs
49
49
 
50
50
  out_path = @output_dir.join(relative_sig_path(file))
51
51
  out_path.dirname.mkpath
52
- out_path.open("w") { |io| RBS::Writer.new(out: io).write(annotated) }
52
+ out_path.open("w") { |io| Writer.new(out: io).write(annotated) }
53
53
 
54
54
  out_path
55
55
  rescue SyntaxError => e
@@ -21,7 +21,8 @@ module Yarbs
21
21
  source = yard_types.map { |type| rewrite(type) }.join(" | ")
22
22
 
23
23
  begin
24
- RBS::Parser.parse_type(source, require_eof: true) || UNTYPED
24
+ type = RBS::Parser.parse_type(source, require_eof: true)
25
+ type ? fit_generic_args(type) : UNTYPED
25
26
  rescue RBS::ParsingError => e
26
27
  message = "yarbs: could not convert YARD type `#{yard_types.join(", ")}` to RBS (#{e.message})"
27
28
  raise Error, message if strict
@@ -31,6 +32,50 @@ module Yarbs
31
32
  end
32
33
  end
33
34
 
35
+ # Number of type parameters for the core generic classes, which YARD
36
+ # lets you write with any number of arguments (or none) but RBS doesn't.
37
+ GENERIC_ARITY = {
38
+ Array: 1,
39
+ Set: 1,
40
+ Range: 1,
41
+ Enumerable: 1,
42
+ Enumerator: 2,
43
+ Hash: 2
44
+ }.freeze
45
+ private_constant :GENERIC_ARITY
46
+
47
+ # Fits the arguments of core generics to what RBS expects, recursively:
48
+ #
49
+ # - bare `Array`/`Hash` -> `Array[untyped]`/`Hash[untyped, untyped]`
50
+ # - `Array<String, Regexp>` (YARD for "Strings or Regexps") ->
51
+ # `Array[String | Regexp]`
52
+ # - missing trailing arguments are filled with `untyped`
53
+ #
54
+ # @param type [RBS::Types::t]
55
+ # @return [RBS::Types::t]
56
+ def self.fit_generic_args(type)
57
+ case type
58
+ when RBS::Types::ClassInstance
59
+ args = type.args.map { |arg| fit_generic_args(arg) }
60
+ arity = type.name.namespace.path.empty? ? GENERIC_ARITY[type.name.name] : nil
61
+
62
+ if arity
63
+ if arity == 1 && args.size > 1
64
+ args = [RBS::Types::Union.new(types: args, location: nil)]
65
+ elsif args.size < arity
66
+ args += Array.new(arity - args.size, UNTYPED)
67
+ end
68
+ end
69
+
70
+ RBS::Types::ClassInstance.new(name: type.name, args: args, location: type.location)
71
+ when RBS::Types::Union, RBS::Types::Intersection, RBS::Types::Optional, RBS::Types::Tuple, RBS::Types::Record, RBS::Types::Proc
72
+ type.map_type { |inner| fit_generic_args(inner) }
73
+ else
74
+ type
75
+ end
76
+ end
77
+ private_class_method :fit_generic_args
78
+
34
79
  # `Proc<(ArgType, ...), ReturnType>` -> `^(ArgType, ...) -> ReturnType`,
35
80
  # RBS's proc-literal type (a real, typed callable signature).
36
81
  #
data/lib/yarbs/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Yarbs
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbs"
4
+
5
+ module Yarbs
6
+ # An +RBS::Writer+ that also writes a constant's annotations, which the
7
+ # stock writer drops (even though the RBS parser accepts +%a{...}+ on a
8
+ # constant, and Steep reads it). Without this, a +@deprecated+ constant
9
+ # would silently lose the +%a{deprecated}+ that {Annotator} gives it.
10
+ class Writer < RBS::Writer
11
+ private
12
+
13
+ def write_decl(decl)
14
+ return super unless decl.is_a?(RBS::AST::Declarations::Constant)
15
+
16
+ write_comment decl.comment
17
+ write_annotation decl.annotations
18
+ puts "#{decl.name}: #{decl.type}"
19
+ end
20
+ end
21
+ end
data/lib/yarbs.rb CHANGED
@@ -7,6 +7,7 @@ require_relative "yarbs/version"
7
7
  require_relative "yarbs/type_converter"
8
8
  require_relative "yarbs/prototype_builder"
9
9
  require_relative "yarbs/annotator"
10
+ require_relative "yarbs/writer"
10
11
  require_relative "yarbs/generator"
11
12
  require_relative "yarbs/watcher"
12
13
  require_relative "yarbs/cli"
@@ -68,6 +68,13 @@ module Yarbs
68
68
 
69
69
  def return_types_of: (untyped yard_object) -> (::Array[untyped] | untyped)
70
70
 
71
+ # Appends a `%a{deprecated}` annotation when the YARD object has a
72
+ # `@deprecated` tag, which Steep reports as `Ruby::DeprecatedReference`
73
+ # wherever the declaration is used. The tag's text becomes the message
74
+ # (`%a{deprecated: Use #bar instead}`), flattened onto one line since
75
+ # Steep only reads the message up to the first newline.
76
+ def with_deprecation: (untyped annotations, untyped yard_object) -> untyped
77
+
71
78
  def convert: (untyped types) -> untyped
72
79
 
73
80
  def safe_symbol: (untyped name) -> (nil | untyped)
@@ -0,0 +1,11 @@
1
+ module Yarbs
2
+ # An +RBS::Writer+ that also writes a constant's annotations, which the
3
+ # stock writer drops (even though the RBS parser accepts +%a{...}+ on a
4
+ # constant, and Steep reads it). Without this, a +@deprecated+ constant
5
+ # would silently lose the +%a{deprecated}+ that {Annotator} gives it.
6
+ class Writer < RBS::Writer
7
+ private
8
+
9
+ def write_decl: (untyped decl) -> untyped
10
+ end
11
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yarbs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - tobidelius
@@ -70,6 +70,7 @@ files:
70
70
  - lib/yarbs/type_converter.rb
71
71
  - lib/yarbs/version.rb
72
72
  - lib/yarbs/watcher.rb
73
+ - lib/yarbs/writer.rb
73
74
  - sig/yarbs.rbs
74
75
  - sig/yarbs/annotator.rbs
75
76
  - sig/yarbs/cli.rbs
@@ -78,6 +79,7 @@ files:
78
79
  - sig/yarbs/type_converter.rbs
79
80
  - sig/yarbs/version.rbs
80
81
  - sig/yarbs/watcher.rbs
82
+ - sig/yarbs/writer.rbs
81
83
  homepage: https://github.com/tobidelius/yarbs
82
84
  licenses:
83
85
  - MIT
@@ -96,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
98
  - !ruby/object:Gem::Version
97
99
  version: '0'
98
100
  requirements: []
99
- rubygems_version: 4.0.16
101
+ rubygems_version: 4.0.20
100
102
  specification_version: 4
101
103
  summary: Generate RBS files from YARD documentation.
102
104
  test_files: []