spoom 1.8.7 → 1.8.8

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.
@@ -73,15 +73,27 @@ module Spoom
73
73
  end
74
74
 
75
75
  # A node representing either a file or a directory inside a FileTree
76
- class Node < T::Struct
76
+ class Node
77
77
  # Node parent or `nil` if the node is a root one
78
- const :parent, T.nilable(Node)
78
+ #: Node?
79
+ attr_reader :parent
79
80
 
80
81
  # File or dir name
81
- const :name, String
82
+
83
+ #: String
84
+ attr_reader :name
82
85
 
83
86
  # Children of this node (if not empty, it means it's a dir)
84
- const :children, T::Hash[String, Node], default: {}
87
+
88
+ #: Hash[String, Node]
89
+ attr_reader :children
90
+
91
+ #: (name: String, ?parent: Node?, ?children: Hash[String, Node]) -> void
92
+ def initialize(name:, parent: nil, children: {})
93
+ @parent = parent
94
+ @name = name
95
+ @children = children
96
+ end
85
97
 
86
98
  # Full path to this node from root
87
99
  #: -> String
@@ -226,7 +226,7 @@ module Spoom
226
226
  current_namespace.mixins << Extend.new(arg.slice)
227
227
  end
228
228
  when :public, :private, :protected
229
- @visibility_stack << Visibility.from_serialized(node.name.to_s)
229
+ @visibility_stack << Visibility.from_string(node.name.to_s)
230
230
  if node.arguments
231
231
  super
232
232
  @visibility_stack.pop
@@ -192,12 +192,34 @@ module Spoom
192
192
  class AttrWriter < Attr; end
193
193
  class AttrAccessor < Attr; end
194
194
 
195
- class Visibility < T::Enum
196
- enums do
197
- Public = new("public")
198
- Protected = new("protected")
199
- Private = new("private")
195
+ class Visibility
196
+ class << self
197
+ #: (String) -> Visibility
198
+ def from_string(name)
199
+ case name
200
+ when "public" then Public
201
+ when "protected" then Protected
202
+ when "private" then Private
203
+ else
204
+ raise Error, "Invalid visibility: #{name}"
205
+ end
206
+ end
207
+ end
208
+
209
+ #: (String) -> void
210
+ def initialize(name)
211
+ @name = name
200
212
  end
213
+
214
+ # @override
215
+ #: -> String
216
+ def to_s
217
+ @name
218
+ end
219
+
220
+ Public = new("public") #: Visibility
221
+ Protected = new("protected") #: Visibility
222
+ Private = new("private") #: Visibility
201
223
  end
202
224
 
203
225
  # A mixin (include, prepend, extend) to a namespace
@@ -7,12 +7,20 @@ module Spoom
7
7
  #
8
8
  # Constants could be classes, modules, or actual constants.
9
9
  # Methods could be accessors, instance or class methods, aliases, etc.
10
- class Reference < T::Struct
11
- class Kind < T::Enum
12
- enums do
13
- Constant = new("constant")
14
- Method = new("method")
10
+ class Reference
11
+ class Kind
12
+ #: (String) -> void
13
+ def initialize(name)
14
+ @name = name
15
15
  end
16
+
17
+ #: -> String
18
+ def to_s
19
+ @name
20
+ end
21
+
22
+ Constant = new("constant") #: Kind
23
+ Method = new("method") #: Kind
16
24
  end
17
25
 
18
26
  class << self
@@ -27,9 +35,21 @@ module Spoom
27
35
  end
28
36
  end
29
37
 
30
- const :kind, Kind
31
- const :name, String
32
- const :location, Spoom::Location
38
+ #: Kind
39
+ attr_reader :kind
40
+
41
+ #: String
42
+ attr_reader :name
43
+
44
+ #: Spoom::Location
45
+ attr_reader :location
46
+
47
+ #: (kind: Kind, name: String, location: Spoom::Location) -> void
48
+ def initialize(kind:, name:, location:)
49
+ @kind = kind
50
+ @name = name
51
+ @location = location
52
+ end
33
53
 
34
54
  #: -> bool
35
55
  def constant?
@@ -4,7 +4,6 @@
4
4
  module Spoom
5
5
  module LSP
6
6
  # Base messaging
7
- # We don't use T::Struct for those so we can subclass them
8
7
 
9
8
  # A general message as defined by JSON-RPC.
10
9
  #
@@ -13,11 +13,20 @@ module Spoom
13
13
  def accept_printer(printer) = raise NotImplementedError, "Abstract method called"
14
14
  end
15
15
 
16
- class Position < T::Struct
16
+ class Position
17
17
  include PrintableSymbol
18
18
 
19
- const :line, Integer
20
- const :char, Integer
19
+ #: Integer
20
+ attr_reader :line
21
+
22
+ #: Integer
23
+ attr_reader :char
24
+
25
+ #: (line: Integer, char: Integer) -> void
26
+ def initialize(line:, char:)
27
+ @line = line
28
+ @char = char
29
+ end
21
30
 
22
31
  class << self
23
32
  #: (Hash[untyped, untyped] json) -> Position
@@ -41,18 +50,27 @@ module Spoom
41
50
  end
42
51
  end
43
52
 
44
- class Range < T::Struct
53
+ class Range
45
54
  include PrintableSymbol
46
55
 
47
- const :start, Position
48
- const :end, Position
56
+ #: Position
57
+ attr_reader :start_pos
58
+
59
+ #: Position
60
+ attr_reader :end_pos
61
+
62
+ #: (start_pos: Position, end_pos: Position) -> void
63
+ def initialize(start_pos:, end_pos:)
64
+ @start_pos = start_pos
65
+ @end_pos = end_pos
66
+ end
49
67
 
50
68
  class << self
51
69
  #: (Hash[untyped, untyped] json) -> Range
52
70
  def from_json(json)
53
71
  Range.new(
54
- start: Position.from_json(json["start"]),
55
- end: Position.from_json(json["end"]),
72
+ start_pos: Position.from_json(json["start"]),
73
+ end_pos: Position.from_json(json["end"]),
56
74
  )
57
75
  end
58
76
  end
@@ -60,22 +78,31 @@ module Spoom
60
78
  # @override
61
79
  #: (SymbolPrinter printer) -> void
62
80
  def accept_printer(printer)
63
- printer.print_object(start)
81
+ printer.print_object(start_pos)
64
82
  printer.print_colored("-", Color::LIGHT_BLACK)
65
- printer.print_object(self.end)
83
+ printer.print_object(end_pos)
66
84
  end
67
85
 
68
86
  #: -> String
69
87
  def to_s
70
- "#{start}-#{self.end}"
88
+ "#{start_pos}-#{end_pos}"
71
89
  end
72
90
  end
73
91
 
74
- class Hover < T::Struct
92
+ class Hover
75
93
  include PrintableSymbol
76
94
 
77
- const :contents, String
78
- const :range, T.nilable(Range)
95
+ #: String
96
+ attr_reader :contents
97
+
98
+ #: Range?
99
+ attr_reader :range
100
+
101
+ #: (contents: String, ?range: Range?) -> void
102
+ def initialize(contents:, range: nil)
103
+ @contents = contents
104
+ @range = range
105
+ end
79
106
 
80
107
  class << self
81
108
  #: (Hash[untyped, untyped] json) -> Hover
@@ -100,11 +127,20 @@ module Spoom
100
127
  end
101
128
  end
102
129
 
103
- class Location < T::Struct
130
+ class Location
104
131
  include PrintableSymbol
105
132
 
106
- const :uri, String
107
- const :range, LSP::Range
133
+ #: String
134
+ attr_reader :uri
135
+
136
+ #: LSP::Range
137
+ attr_reader :range
138
+
139
+ #: (uri: String, range: LSP::Range) -> void
140
+ def initialize(uri:, range:)
141
+ @uri = uri
142
+ @range = range
143
+ end
108
144
 
109
145
  class << self
110
146
  #: (Hash[untyped, untyped] json) -> Location
@@ -129,12 +165,26 @@ module Spoom
129
165
  end
130
166
  end
131
167
 
132
- class SignatureHelp < T::Struct
168
+ class SignatureHelp
133
169
  include PrintableSymbol
134
170
 
135
- const :label, T.nilable(String)
136
- const :doc, Object # TODO
137
- const :params, T::Array[T.untyped] # TODO
171
+ #: String?
172
+ attr_reader :label
173
+
174
+ # TODO
175
+ #: Object
176
+ attr_reader :doc
177
+
178
+ # TODO
179
+ #: Array[untyped]
180
+ attr_reader :params
181
+
182
+ #: (doc: Object, params: Array[untyped], ?label: String?) -> void
183
+ def initialize(doc:, params:, label: nil)
184
+ @label = label
185
+ @doc = doc
186
+ @params = params
187
+ end
138
188
 
139
189
  class << self
140
190
  #: (Hash[untyped, untyped] json) -> SignatureHelp
@@ -162,13 +212,28 @@ module Spoom
162
212
  end
163
213
  end
164
214
 
165
- class Diagnostic < T::Struct
215
+ class Diagnostic
166
216
  include PrintableSymbol
167
217
 
168
- const :range, LSP::Range
169
- const :code, Integer
170
- const :message, String
171
- const :information, Object
218
+ #: LSP::Range
219
+ attr_reader :range
220
+
221
+ #: Integer
222
+ attr_reader :code
223
+
224
+ #: String
225
+ attr_reader :message
226
+
227
+ #: Object
228
+ attr_reader :information
229
+
230
+ #: (range: LSP::Range, code: Integer, message: String, information: Object) -> void
231
+ def initialize(range:, code:, message:, information:)
232
+ @range = range
233
+ @code = code
234
+ @message = message
235
+ @information = information
236
+ end
172
237
 
173
238
  class << self
174
239
  #: (Hash[untyped, untyped] json) -> Diagnostic
@@ -194,15 +259,43 @@ module Spoom
194
259
  end
195
260
  end
196
261
 
197
- class DocumentSymbol < T::Struct
262
+ class DocumentSymbol
198
263
  include PrintableSymbol
199
264
 
200
- const :name, String
201
- const :detail, T.nilable(String)
202
- const :kind, Integer
203
- const :location, T.nilable(Location)
204
- const :range, T.nilable(Range)
205
- const :children, T::Array[DocumentSymbol]
265
+ #: String
266
+ attr_reader :name
267
+
268
+ #: String?
269
+ attr_reader :detail
270
+
271
+ #: Integer
272
+ attr_reader :kind
273
+
274
+ #: Location?
275
+ attr_reader :location
276
+
277
+ #: LSP::Range?
278
+ attr_reader :range
279
+
280
+ #: Array[DocumentSymbol]
281
+ attr_reader :children
282
+
283
+ #: (
284
+ #| name: String,
285
+ #| kind: Integer,
286
+ #| children: Array[DocumentSymbol],
287
+ #| ?detail: String?,
288
+ #| ?location: Location?,
289
+ #| ?range: LSP::Range?
290
+ #| ) -> void
291
+ def initialize(name:, kind:, children:, detail: nil, location: nil, range: nil)
292
+ @name = name
293
+ @detail = detail
294
+ @kind = kind
295
+ @location = location
296
+ @range = range
297
+ @children = children
298
+ end
206
299
 
207
300
  class << self
208
301
  #: (Hash[untyped, untyped] json) -> DocumentSymbol
@@ -221,10 +314,7 @@ module Spoom
221
314
  # @override
222
315
  #: (SymbolPrinter printer) -> void
223
316
  def accept_printer(printer)
224
- h = serialize.hash
225
- return if printer.seen.include?(h)
226
-
227
- printer.seen.add(h)
317
+ return unless printer.seen.add?(deduplication_key)
228
318
 
229
319
  printer.printt
230
320
  printer.print(kind_string)
@@ -256,6 +346,28 @@ module Spoom
256
346
  SYMBOL_KINDS[kind] || "<unknown:#{kind}>"
257
347
  end
258
348
 
349
+ protected
350
+
351
+ #: -> Array[untyped]
352
+ def deduplication_key
353
+ symbol_location = location
354
+ [
355
+ name,
356
+ detail,
357
+ kind,
358
+ symbol_location && [symbol_location.uri, range_key(symbol_location.range)],
359
+ range_key(range),
360
+ children.map { |child| child.deduplication_key },
361
+ ]
362
+ end
363
+
364
+ #: (LSP::Range?) -> Array[Integer]?
365
+ def range_key(range)
366
+ return unless range
367
+
368
+ [range.start_pos.line, range.start_pos.char, range.end_pos.line, range.end_pos.char]
369
+ end
370
+
259
371
  SYMBOL_KINDS = {
260
372
  1 => "file",
261
373
  2 => "module",
@@ -287,7 +399,7 @@ module Spoom
287
399
  end
288
400
 
289
401
  class SymbolPrinter < Printer
290
- #: Set[Integer]
402
+ #: Set[Array[untyped]]
291
403
  attr_reader :seen
292
404
 
293
405
  #: String?
@@ -296,7 +408,7 @@ module Spoom
296
408
  #: (?out: (IO | StringIO), ?colors: bool, ?indent_level: Integer, ?prefix: String?) -> void
297
409
  def initialize(out: $stdout, colors: true, indent_level: 0, prefix: nil)
298
410
  super(out: out, colors: colors, indent_level: indent_level)
299
- @seen = Set.new #: Set[Integer]
411
+ @seen = Set.new #: Set[Array[untyped]]
300
412
  @out = out
301
413
  @colors = colors
302
414
  @indent_level = indent_level
@@ -231,12 +231,10 @@ module Spoom
231
231
  node.expression.location.end_offset
232
232
  end
233
233
 
234
- # Only translate (and `extend T::Helpers`) when there's at least one *known* class
234
+ # Only translate (and `extend ::T::Helpers`) when there's at least one *known* class
235
235
  # annotation. A node with only unknown annotations (e.g. `@private`) is left untouched.
236
236
  if comments.class_annotations.any?
237
- unless already_extends?(node, /^(::)?T::Helpers$/)
238
- extend_with("T::Helpers", into: node, at: insert_pos)
239
- end
237
+ extend_with("::T::Helpers", into: node, at: insert_pos)
240
238
 
241
239
  comments.annotations.reverse_each do |annotation|
242
240
  content = case annotation.string
@@ -286,9 +284,7 @@ module Spoom
286
284
  next
287
285
  end
288
286
 
289
- unless already_extends?(node, /^(::)?T::Generic$/)
290
- extend_with("T::Generic", into: node, at: insert_pos)
291
- end
287
+ extend_with("::T::Generic", into: node, at: insert_pos)
292
288
 
293
289
  type_params.each do |type_param|
294
290
  type_member = "#{type_param.name} = type_member"
@@ -395,22 +391,6 @@ module Spoom
395
391
  #: (String mixin_name, into: PrismTypes::anyScopeNode, at: Integer) -> void
396
392
  def extend_with(mixin_name, into:, at:) = raise
397
393
 
398
- #: (PrismTypes::anyScopeNode, Regexp) -> bool
399
- def already_extends?(node, constant_regex)
400
- node.child_nodes.any? do |c|
401
- next false unless c.is_a?(Prism::CallNode)
402
- next false unless c.message == "extend"
403
- next false unless c.receiver.nil? || c.receiver.is_a?(Prism::SelfNode)
404
- next false unless c.arguments&.arguments&.size == 1
405
-
406
- arg = c.arguments&.arguments&.first
407
- next false unless arg.is_a?(Prism::ConstantPathNode)
408
- next false unless arg.slice.match?(constant_regex)
409
-
410
- true
411
- end
412
- end
413
-
414
394
  #: (Array[Prism::Comment]) -> Array[Spoom::RBS::TypeAlias]
415
395
  def collect_type_aliases(comments)
416
396
  type_aliases = [] #: Array[Spoom::RBS::TypeAlias]
@@ -19,25 +19,23 @@ module Spoom
19
19
  RBS_REWRITE_PATTERN = Regexp.union(["#:", "#|", *RBS_ANNOTATION_MARKERS]).freeze #: Regexp
20
20
  private_constant :RBS_ANNOTATION_MARKERS, :RBS_REWRITE_PATTERN
21
21
 
22
- #: (String source) -> bool
23
- def contains_rbs_syntax?(source)
24
- Sigils.contains_valid_sigil?(source) && source.match?(RBS_REWRITE_PATTERN)
25
- end
26
-
27
22
  #: (
28
23
  #| String ruby_contents,
29
24
  #| file: String,
30
25
  #| ?max_line_length: Integer?,
31
26
  #| ?overloads_strategy: Symbol,
32
- #| ?erase_generic_types: bool) -> String
27
+ #| ?erase_generic_types: bool,
28
+ #| ?force: bool) -> String
33
29
  def rewrite_if_needed(
34
30
  ruby_contents,
35
31
  file:,
36
32
  max_line_length: nil,
37
33
  overloads_strategy: :translate_all,
38
- erase_generic_types: false
34
+ erase_generic_types: false,
35
+ force: false
39
36
  )
40
- return ruby_contents unless contains_rbs_syntax?(ruby_contents)
37
+ return ruby_contents unless ruby_contents.match?(RBS_REWRITE_PATTERN) &&
38
+ (force || Sigils.contains_valid_sigil?(ruby_contents))
41
39
 
42
40
  options = Options.new(
43
41
  overloads_strategy:,
@@ -67,6 +67,7 @@ module Spoom
67
67
  #: (Prism::DefNode) -> void
68
68
  def visit_def_node(node)
69
69
  last_sigs = collect_last_sigs
70
+ last_sigs.reject! { |_, sig| sig.is_abstract } unless @translate_abstract_methods
70
71
  return if last_sigs.empty?
71
72
 
72
73
  apply_member_annotations(last_sigs)
@@ -77,8 +78,6 @@ module Spoom
77
78
  rbi_node = builder.tree.nodes.first #: as RBI::Method
78
79
 
79
80
  last_sigs.each do |node, sig|
80
- next if sig.is_abstract && !@translate_abstract_methods
81
-
82
81
  preserve_multiline_signatures = !!(@preserve_multiline_signatures && sig.loc&.multiline?)
83
82
 
84
83
  out = rbs_print(
@@ -90,7 +89,7 @@ module Spoom
90
89
  @rewriter << Source::Replace.new(node.location.start_offset, node.location.end_offset, out)
91
90
  end
92
91
 
93
- if @translate_abstract_methods && last_sigs.any? { |_, sig| sig.is_abstract }
92
+ if last_sigs.any? { |_, sig| sig.is_abstract }
94
93
  @rewriter << Source::Replace.new(
95
94
  node.rparen_loc&.end_offset || node.name_loc.end_offset,
96
95
  node.location.end_offset - 1,
@@ -299,7 +298,7 @@ module Spoom
299
298
  @rewriter << Source::Insert.new(insert_pos, "# @final\n#{indent}")
300
299
  end
301
300
 
302
- if sigs.any? { |_, sig| sig.is_abstract } && @translate_abstract_methods
301
+ if sigs.any? { |_, sig| sig.is_abstract }
303
302
  @rewriter << Source::Insert.new(insert_pos, "# @abstract\n#{indent}")
304
303
  end
305
304
 
@@ -60,15 +60,16 @@ module Spoom
60
60
  # Converts all the RBS comments in the given Ruby code to `sig` nodes.
61
61
  # It also handles type members and class annotations.
62
62
  #: (String ruby_contents, file: String, ?max_line_length: Integer?,
63
- #| ?overloads_strategy: Symbol, ?erase_generic_types: bool) -> String
63
+ #| ?overloads_strategy: Symbol, ?erase_generic_types: bool, ?force: bool) -> String
64
64
  def rbs_comments_to_sorbet_sigs(ruby_contents, file:, max_line_length: nil, overloads_strategy: :translate_all,
65
- erase_generic_types: false)
65
+ erase_generic_types: false, force: false)
66
66
  RBSCommentsToSorbetSigs.rewrite_if_needed(
67
67
  ruby_contents,
68
68
  file: file,
69
69
  max_line_length: max_line_length,
70
70
  overloads_strategy: overloads_strategy,
71
71
  erase_generic_types: erase_generic_types,
72
+ force: force,
72
73
  )
73
74
  end
74
75
 
data/lib/spoom/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Spoom
5
- VERSION = "1.8.7"
5
+ VERSION = "1.8.8"
6
6
  end