tapioca 0.18.0 → 0.19.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 659ca0696f2d5827518829428768bcd7b679532b7f554b893d16915c6d2389df
4
- data.tar.gz: 2500137b33f2a4c4491200c579f6fc762c8a183257f741bfbd86148c7561db4d
3
+ metadata.gz: 1af20de26e446268513156110cac416ce7ba2b35f96f4bb7898960d640aa4333
4
+ data.tar.gz: 4d760ed85be418d9c9dc166d61ea59ccaf312c929bd4930709abc4b946f3876f
5
5
  SHA512:
6
- metadata.gz: 54af5a5d84ce82b7ed58ff41d0656f1979f180edbc90bd08750e6767e1cfb400a92522bb8f9ba23739c1f00739db0049a019f1635516e1fbe4789dfb4a10bf4d
7
- data.tar.gz: 5e3e2784bdd12d0bc9f79d7aefc21ad07b71d4b50962c4f4e391ec9be9958f6accf1e82a1e076c88f243209487066ee23d13377a864bf7bc7d6cd8b10db8e79a
6
+ metadata.gz: 1bf6bbf64b782f072a23109ca90cf9c3743b82501bf304badcd97b816583f2fff3ec3574d58df22f05af716d750ba86589074f43a1ee5d3f9e383fdb83c8ee02
7
+ data.tar.gz: a4d066179177ee84ee349bb7a8514d9b2ad8836051b33adc45ca3708948ddc01a002d6d54fd9c42cc0993ca5e30f74a61f5f5ad8a603ef493b50bee0c7d6ef93
data/README.md CHANGED
@@ -161,7 +161,7 @@ All operations performed in working directory.
161
161
  Please review changes and commit them.
162
162
  ```
163
163
 
164
- This will load your application, find all the gems required by it and generate an RBI file for each gem under the `sorbet/rbi/gems` directory for each of those gems. This process will also import signatures that can be found inside each gem sources, and, optionally, any YARD documentation inside the gem.
164
+ This will load your application, find all the gems required by it and generate an RBI file for each gem under the `sorbet/rbi/gems` directory for each of those gems. This process will also import signatures that can be found inside each gem sources, and, optionally, any documentation inside the gem.
165
165
 
166
166
  <!-- START_HELP_COMMAND_GEM -->
167
167
  ```shell
@@ -187,7 +187,7 @@ Options:
187
187
  # Default: {"activesupport" => "false"}
188
188
  [--verify], [--no-verify], [--skip-verify] # Verify RBIs are up-to-date
189
189
  # Default: false
190
- [--doc], [--no-doc], [--skip-doc] # Include YARD documentation from sources when generating RBIs. Warning: this might be slow
190
+ [--doc], [--no-doc], [--skip-doc] # Include documentation from sources when generating RBIs
191
191
  # Default: true
192
192
  [--loc], [--no-loc], [--skip-loc] # Include comments with source location when generating RBIs
193
193
  # Default: true
data/lib/tapioca/cli.rb CHANGED
@@ -225,7 +225,7 @@ module Tapioca
225
225
  default: false
226
226
  option :doc,
227
227
  type: :boolean,
228
- desc: "Include YARD documentation from sources when generating RBIs. Warning: this might be slow",
228
+ desc: "Include documentation from sources when generating RBIs",
229
229
  default: true
230
230
  option :loc,
231
231
  type: :boolean,
@@ -139,11 +139,16 @@ module Tapioca
139
139
 
140
140
  #: (RBI::Scope scope, (Method | UnboundMethod) method_def, ?class_method: bool) -> void
141
141
  def create_method_from_def(scope, method_def, class_method: false)
142
+ parameters = compile_method_parameters_to_rbi(method_def)
143
+ return_type = compile_method_return_type_to_rbi(method_def)
144
+ type_params = extract_type_parameters(parameters.map(&:type).push(return_type))
145
+
142
146
  scope.create_method(
143
147
  method_def.name.to_s,
144
- parameters: compile_method_parameters_to_rbi(method_def),
145
- return_type: compile_method_return_type_to_rbi(method_def),
148
+ parameters: parameters,
149
+ return_type: return_type,
146
150
  class_method: class_method,
151
+ type_params: type_params,
147
152
  )
148
153
  end
149
154
 
@@ -90,7 +90,10 @@ module Tapioca
90
90
  end
91
91
 
92
92
  # Create all of the methods for each event
93
- parameters = [create_rest_param("opts", type: "T.untyped")]
93
+ parameters = [
94
+ create_rest_param("opts", type: "T.untyped"),
95
+ create_block_param("block", type: "T.nilable(T.proc.void)"),
96
+ ]
94
97
  state_machine.events.each do |event|
95
98
  model.create_method(event.name.to_s, parameters: parameters)
96
99
  model.create_method("#{event.name}!", parameters: parameters)
@@ -0,0 +1,94 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class Documentation < Base
8
+ IGNORED_COMMENTS = [
9
+ ":doc:",
10
+ ":nodoc:",
11
+ "typed:",
12
+ "frozen_string_literal:",
13
+ "encoding:",
14
+ "warn_indent:",
15
+ "shareable_constant_value:",
16
+ "rubocop:",
17
+ "@requires_ancestor:",
18
+ ] #: Array[String]
19
+
20
+ #: (Pipeline pipeline, Rubydex::Graph gem_graph) -> void
21
+ def initialize(pipeline, gem_graph)
22
+ super(pipeline)
23
+
24
+ @gem_graph = gem_graph
25
+ end
26
+
27
+ private
28
+
29
+ #: (String line) -> bool
30
+ def rbs_comment?(line)
31
+ line.start_with?(": ", "| ")
32
+ end
33
+
34
+ # @override
35
+ #: (ConstNodeAdded event) -> void
36
+ def on_const(event)
37
+ event.node.comments = documentation_comments(event.symbol)
38
+ end
39
+
40
+ # @override
41
+ #: (ScopeNodeAdded event) -> void
42
+ def on_scope(event)
43
+ event.node.comments = documentation_comments(event.symbol)
44
+ end
45
+
46
+ # @override
47
+ #: (MethodNodeAdded event) -> void
48
+ def on_method(event)
49
+ name = if event.constant.singleton_class?
50
+ "#{event.symbol}::<#{event.symbol.split("::").last}>##{event.node.name}()"
51
+ else
52
+ "#{event.symbol}##{event.node.name}()"
53
+ end
54
+ event.node.comments = documentation_comments(name, sigs: event.node.sigs)
55
+ end
56
+
57
+ #: (String name, ?sigs: Array[RBI::Sig]) -> Array[RBI::Comment]
58
+ def documentation_comments(name, sigs: [])
59
+ declaration = @gem_graph[name]
60
+ # For attr_writer methods (name ending in =), fall back to reader docs
61
+ if declaration.nil? && name.end_with?("=()")
62
+ declaration = @gem_graph[name.delete_suffix("=()") + "()"]
63
+ end
64
+ # For singleton methods (Class::<Class>#method()), fall back to instance method docs.
65
+ # This handles module_function and extend self methods which Rubydex indexes
66
+ # only under the instance method name.
67
+ if declaration.nil? && name.include?("::<")
68
+ declaration = @gem_graph[name.sub(/::<[^>]+>#/, "#")]
69
+ end
70
+ return [] unless declaration
71
+
72
+ comments = declaration.definitions.flat_map(&:comments)
73
+ comments.uniq!
74
+ return [] if comments.empty?
75
+
76
+ lines = comments
77
+ .map { |comment| comment.string.gsub(/^#+ ?/, "") }
78
+ .reject { |line| IGNORED_COMMENTS.any? { |comment| line.include?(comment) } || rbs_comment?(line) }
79
+
80
+ # Strip leading and trailing blank lines, matching YARD's behavior
81
+ lines = lines.reverse_each.drop_while(&:empty?).reverse_each.drop_while(&:empty?)
82
+
83
+ lines.map! { |line| RBI::Comment.new(line) }
84
+ end
85
+
86
+ # @override
87
+ #: (NodeAdded event) -> bool
88
+ def ignore?(event)
89
+ event.is_a?(Tapioca::Gem::ForeignScopeNodeAdded)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -8,8 +8,6 @@ module Tapioca
8
8
  include Runtime::Reflection
9
9
  include RBIHelper
10
10
 
11
- TYPE_PARAMETER_MATCHER = /T\.type_parameter\(:?([[:word:]]+)\)/
12
-
13
11
  private
14
12
 
15
13
  # @override
@@ -42,9 +40,7 @@ module Tapioca
42
40
  sig.return_type = return_type
43
41
  @pipeline.push_symbol(return_type)
44
42
 
45
- parameter_types.values.join(", ").scan(TYPE_PARAMETER_MATCHER).flatten.uniq.each do |k, _|
46
- sig.type_params << k
47
- end
43
+ sig.type_params.concat(extract_type_parameters(parameter_types.values.map(&:to_s).push(return_type)))
48
44
 
49
45
  case signature.mode
50
46
  when "abstract"
@@ -14,5 +14,5 @@ require "tapioca/gem/listeners/sorbet_signatures"
14
14
  require "tapioca/gem/listeners/sorbet_type_variables"
15
15
  require "tapioca/gem/listeners/subconstants"
16
16
  require "tapioca/gem/listeners/foreign_constants"
17
- require "tapioca/gem/listeners/yard_doc"
17
+ require "tapioca/gem/listeners/documentation"
18
18
  require "tapioca/gem/listeners/source_location"
@@ -32,6 +32,7 @@ module Tapioca
32
32
 
33
33
  @payload_symbols = Static::SymbolLoader.payload_symbols #: Set[String]
34
34
  @bootstrap_symbols = load_bootstrap_symbols(@gem) #: Set[String]
35
+ gem_graph = Static::SymbolLoader.graph_from_paths(@gem.files) if include_doc
35
36
 
36
37
  @bootstrap_symbols.each { |symbol| push_symbol(symbol) }
37
38
 
@@ -46,7 +47,7 @@ module Tapioca
46
47
  @node_listeners << Gem::Listeners::SorbetRequiredAncestors.new(self)
47
48
  @node_listeners << Gem::Listeners::SorbetSignatures.new(self)
48
49
  @node_listeners << Gem::Listeners::Subconstants.new(self)
49
- @node_listeners << Gem::Listeners::YardDoc.new(self) if include_doc
50
+ @node_listeners << Gem::Listeners::Documentation.new(self, gem_graph) if include_doc
50
51
  @node_listeners << Gem::Listeners::ForeignConstants.new(self)
51
52
  @node_listeners << Gem::Listeners::SourceLocation.new(self) if include_loc
52
53
  @node_listeners << Gem::Listeners::RemoveEmptyPayloadScopes.new(self)
@@ -177,22 +177,6 @@ module Tapioca
177
177
  end
178
178
  end
179
179
 
180
- #: -> void
181
- def parse_yard_docs
182
- files.each do |path|
183
- YARD.parse(path.to_s, [], Logger::Severity::FATAL)
184
- rescue RangeError
185
- # In some circumstances, YARD will raise an error when parsing a file
186
- # that is actually valid Ruby. We don't want tapioca to halt in these
187
- # cases, so we'll rescue the error, pretend like there was no
188
- # documentation, and move on.
189
- #
190
- # This can be removed when https://github.com/lsegal/yard/issues/1536
191
- # is resolved and released.
192
- []
193
- end
194
- end
195
-
196
180
  #: -> Array[String]
197
181
  def exported_rbi_files
198
182
  @exported_rbi_files ||= Dir.glob("#{full_gem_path}/rbi/**/*.rbi").sort
@@ -94,6 +94,13 @@ module Tapioca
94
94
  end
95
95
  end
96
96
 
97
+ TYPE_PARAMETER_MATCHER = /T\.type_parameter\(:?([[:word:]]+)\)/
98
+
99
+ #: (Array[String] type_strings) -> Array[String]
100
+ def extract_type_parameters(type_strings)
101
+ type_strings.join(", ").scan(TYPE_PARAMETER_MATCHER).flatten.uniq
102
+ end
103
+
97
104
  #: (String name) -> bool
98
105
  def valid_method_name?(name)
99
106
  Prism.parse_success?("def self.#{name}(a); end")
@@ -41,7 +41,7 @@ require "shellwords"
41
41
  require "tempfile"
42
42
  require "thor"
43
43
  require "yaml"
44
- require "yard-sorbet"
44
+ require "rubydex"
45
45
  require "prism"
46
46
 
47
47
  require "tapioca/helpers/gem_helper"
@@ -65,10 +65,11 @@ module RBI
65
65
  #| ?return_type: String?,
66
66
  #| ?class_method: bool,
67
67
  #| ?visibility: RBI::Visibility,
68
- #| ?comments: Array[RBI::Comment]
68
+ #| ?comments: Array[RBI::Comment],
69
+ #| ?type_params: Array[String]
69
70
  #| ) ?{ (RBI::Method node) -> void } -> void
70
71
  def create_method(name, parameters: [], return_type: nil, class_method: false, visibility: RBI::Public.new,
71
- comments: [], &block)
72
+ comments: [], type_params: [], &block)
72
73
  return unless Tapioca::RBIHelper.valid_method_name?(name)
73
74
 
74
75
  sigs = []
@@ -77,7 +78,7 @@ module RBI
77
78
  # If there is no block, and the params and return type have not been supplied, then
78
79
  # we create a single signature with the given parameters and return type
79
80
  params = parameters.map { |param| RBI::SigParam.new(param.param.name.to_s, param.type) }
80
- sigs << RBI::Sig.new(params: params, return_type: return_type || "T.untyped")
81
+ sigs << RBI::Sig.new(params: params, return_type: return_type || "T.untyped", type_params: type_params)
81
82
  end
82
83
 
83
84
  method = RBI::Method.new(
@@ -213,9 +213,16 @@ module Tapioca
213
213
 
214
214
  #: (T::Module[top] constant) -> Set[String]
215
215
  def file_candidates_for(constant)
216
- relevant_methods_for(constant).filter_map do |method|
216
+ # Grab all source files for (relevant) methods defined on the constant
217
+ candidates = relevant_methods_for(constant).filter_map do |method|
217
218
  method.source_location&.first
218
219
  end.to_set
220
+
221
+ # Add the source file for the constant definition itself, if available.
222
+ source_location_candidate = const_source_location(name_of(constant).to_s)&.file
223
+ candidates.add(source_location_candidate) if source_location_candidate
224
+
225
+ candidates
219
226
  end
220
227
 
221
228
  #: (T::Module[top] constant) -> untyped
@@ -18,6 +18,19 @@ module Tapioca
18
18
  T.must(@payload_symbols)
19
19
  end
20
20
 
21
+ #: (Array[Pathname] paths) -> Rubydex::Graph
22
+ def graph_from_paths(paths)
23
+ graph = Rubydex::Graph.new
24
+ graph.index_all(paths.map(&:to_s))
25
+ graph.resolve
26
+ graph
27
+ end
28
+
29
+ #: (Gemfile::GemSpec gem) -> Set[String]
30
+ def gem_symbols(gem)
31
+ symbols_from_paths(gem.files)
32
+ end
33
+
21
34
  #: (Gemfile::GemSpec gem) -> Set[String]
22
35
  def engine_symbols(gem)
23
36
  gem_engine = engines.find do |engine|
@@ -43,11 +56,6 @@ module Tapioca
43
56
  Set.new
44
57
  end
45
58
 
46
- #: (Gemfile::GemSpec gem) -> Set[String]
47
- def gem_symbols(gem)
48
- symbols_from_paths(gem.files)
49
- end
50
-
51
59
  #: (Array[Pathname] paths) -> Set[String]
52
60
  def symbols_from_paths(paths)
53
61
  return Set.new if paths.empty?
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.18.0"
5
+ VERSION = "0.19.0"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapioca
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ufuk Kayserilioglu
@@ -83,47 +83,47 @@ dependencies:
83
83
  - !ruby/object:Gem::Version
84
84
  version: 0.2.2
85
85
  - !ruby/object:Gem::Dependency
86
- name: sorbet-static-and-runtime
86
+ name: rubydex
87
87
  requirement: !ruby/object:Gem::Requirement
88
88
  requirements:
89
89
  - - ">="
90
90
  - !ruby/object:Gem::Version
91
- version: 0.5.11087
91
+ version: 0.1.0.beta10
92
92
  type: :runtime
93
93
  prerelease: false
94
94
  version_requirements: !ruby/object:Gem::Requirement
95
95
  requirements:
96
96
  - - ">="
97
97
  - !ruby/object:Gem::Version
98
- version: 0.5.11087
98
+ version: 0.1.0.beta10
99
99
  - !ruby/object:Gem::Dependency
100
- name: thor
100
+ name: sorbet-static-and-runtime
101
101
  requirement: !ruby/object:Gem::Requirement
102
102
  requirements:
103
103
  - - ">="
104
104
  - !ruby/object:Gem::Version
105
- version: 1.2.0
105
+ version: 0.5.11087
106
106
  type: :runtime
107
107
  prerelease: false
108
108
  version_requirements: !ruby/object:Gem::Requirement
109
109
  requirements:
110
110
  - - ">="
111
111
  - !ruby/object:Gem::Version
112
- version: 1.2.0
112
+ version: 0.5.11087
113
113
  - !ruby/object:Gem::Dependency
114
- name: yard-sorbet
114
+ name: thor
115
115
  requirement: !ruby/object:Gem::Requirement
116
116
  requirements:
117
117
  - - ">="
118
118
  - !ruby/object:Gem::Version
119
- version: '0'
119
+ version: 1.2.0
120
120
  type: :runtime
121
121
  prerelease: false
122
122
  version_requirements: !ruby/object:Gem::Requirement
123
123
  requirements:
124
124
  - - ">="
125
125
  - !ruby/object:Gem::Version
126
- version: '0'
126
+ version: 1.2.0
127
127
  - !ruby/object:Gem::Dependency
128
128
  name: rbi
129
129
  requirement: !ruby/object:Gem::Requirement
@@ -253,6 +253,7 @@ files:
253
253
  - lib/tapioca/gem/events.rb
254
254
  - lib/tapioca/gem/listeners.rb
255
255
  - lib/tapioca/gem/listeners/base.rb
256
+ - lib/tapioca/gem/listeners/documentation.rb
256
257
  - lib/tapioca/gem/listeners/dynamic_mixins.rb
257
258
  - lib/tapioca/gem/listeners/foreign_constants.rb
258
259
  - lib/tapioca/gem/listeners/methods.rb
@@ -266,7 +267,6 @@ files:
266
267
  - lib/tapioca/gem/listeners/sorbet_type_variables.rb
267
268
  - lib/tapioca/gem/listeners/source_location.rb
268
269
  - lib/tapioca/gem/listeners/subconstants.rb
269
- - lib/tapioca/gem/listeners/yard_doc.rb
270
270
  - lib/tapioca/gem/pipeline.rb
271
271
  - lib/tapioca/gem_info.rb
272
272
  - lib/tapioca/gemfile.rb
@@ -1,110 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- module Tapioca
5
- module Gem
6
- module Listeners
7
- class YardDoc < Base
8
- IGNORED_COMMENTS = [
9
- ":doc:",
10
- ":nodoc:",
11
- "typed:",
12
- "frozen_string_literal:",
13
- "encoding:",
14
- "warn_indent:",
15
- "shareable_constant_value:",
16
- "rubocop:",
17
- "@requires_ancestor:",
18
- ] #: Array[String]
19
-
20
- IGNORED_SIG_TAGS = ["param", "return"] #: Array[String]
21
-
22
- #: (Pipeline pipeline) -> void
23
- def initialize(pipeline)
24
- YARD::Registry.clear
25
- super(pipeline)
26
- pipeline.gem.parse_yard_docs
27
- end
28
-
29
- private
30
-
31
- #: (String line) -> bool
32
- def rbs_comment?(line)
33
- line.start_with?(": ", "| ")
34
- end
35
-
36
- # @override
37
- #: (ConstNodeAdded event) -> void
38
- def on_const(event)
39
- event.node.comments = documentation_comments(event.symbol)
40
- end
41
-
42
- # @override
43
- #: (ScopeNodeAdded event) -> void
44
- def on_scope(event)
45
- event.node.comments = documentation_comments(event.symbol)
46
- end
47
-
48
- # @override
49
- #: (MethodNodeAdded event) -> void
50
- def on_method(event)
51
- separator = event.constant.singleton_class? ? "." : "#"
52
- event.node.comments = documentation_comments(
53
- "#{event.symbol}#{separator}#{event.node.name}",
54
- sigs: event.node.sigs,
55
- )
56
- end
57
-
58
- #: (String name, ?sigs: Array[RBI::Sig]) -> Array[RBI::Comment]
59
- def documentation_comments(name, sigs: [])
60
- yard_docs = YARD::Registry.at(name)
61
- return [] unless yard_docs
62
-
63
- docstring = yard_docs.docstring
64
- return [] if /(copyright|license)/i.match?(docstring)
65
-
66
- comments = docstring.lines
67
- .reject { |line| IGNORED_COMMENTS.any? { |comment| line.include?(comment) } || rbs_comment?(line) }
68
- .map! { |line| RBI::Comment.new(line) }
69
-
70
- tags = yard_docs.tags
71
- tags.reject! { |tag| IGNORED_SIG_TAGS.include?(tag.tag_name) } unless sigs.empty?
72
-
73
- comments << RBI::Comment.new("") if comments.any? && tags.any?
74
-
75
- tags.sort_by { |tag| [tag.tag_name, tag.name.to_s] }.each do |tag|
76
- line = +"@#{tag.tag_name}"
77
-
78
- tag_name = tag.name
79
- line << " #{tag_name}" if tag_name
80
-
81
- tag_types = tag.types
82
- line << " [#{tag_types.join(", ")}]" if tag_types&.any?
83
-
84
- tag_text = tag.text
85
- if tag_text && !tag_text.empty?
86
- text_lines = tag_text.lines
87
-
88
- # Example are a special case because we want the text to start on the next line
89
- line << " #{text_lines.shift&.strip}" unless tag.tag_name == "example"
90
-
91
- text_lines.each do |text_line|
92
- line << "\n #{text_line.strip}"
93
- end
94
- end
95
-
96
- comments << RBI::Comment.new(line)
97
- end
98
-
99
- comments
100
- end
101
-
102
- # @override
103
- #: (NodeAdded event) -> bool
104
- def ignore?(event)
105
- event.is_a?(Tapioca::Gem::ForeignScopeNodeAdded)
106
- end
107
- end
108
- end
109
- end
110
- end