rfmt 1.7.0-x86_64-linux → 2.0.0.beta1-x86_64-linux

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.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rfmt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 2.0.0.beta1
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - fujitani sora
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-06-04 00:00:00.000000000 Z
11
+ date: 2026-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs
@@ -66,20 +66,6 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.24'
69
- - !ruby/object:Gem::Dependency
70
- name: prism
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '1.6'
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '1.6'
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: thor
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -109,6 +95,17 @@ files:
109
95
  - exe/rfmt
110
96
  - exe/rfmt-lsp
111
97
  - exe/rfmt_fast
98
+ - ext/rfmt/tests/fixtures/parity/comments_mixed.rb
99
+ - ext/rfmt/tests/fixtures/parity/constructs.rb
100
+ - ext/rfmt/tests/fixtures/parity/embdoc.rb
101
+ - ext/rfmt/tests/fixtures/parity/heredoc_assign.rb
102
+ - ext/rfmt/tests/fixtures/parity/heredoc_call_args.rb
103
+ - ext/rfmt/tests/fixtures/parity/metadata_classes.rb
104
+ - ext/rfmt/tests/fixtures/parity/metadata_conditionals.rb
105
+ - ext/rfmt/tests/fixtures/parity/metadata_defs.rb
106
+ - ext/rfmt/tests/fixtures/parity/multibyte.rb
107
+ - ext/rfmt/tests/fixtures/parity/numeric.rb
108
+ - ext/rfmt/tests/fixtures/parity/plain.rb
112
109
  - lib/rfmt.rb
113
110
  - lib/rfmt/3.3/rfmt.so
114
111
  - lib/rfmt/3.4/rfmt.so
@@ -123,8 +120,6 @@ files:
123
120
  - lib/rfmt/lsp/uri.rb
124
121
  - lib/rfmt/lsp/workspace.rb
125
122
  - lib/rfmt/native_extension_loader.rb
126
- - lib/rfmt/prism_bridge.rb
127
- - lib/rfmt/prism_node_extractor.rb
128
123
  - lib/rfmt/version.rb
129
124
  - lib/ruby_lsp/rfmt/addon.rb
130
125
  - lib/ruby_lsp/rfmt/formatter_runner.rb
@@ -1,529 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'prism'
4
- require 'json'
5
- require_relative 'prism_node_extractor'
6
-
7
- module Rfmt
8
- # PrismBridge provides the Ruby-side integration with the Prism parser
9
- # It parses Ruby source code and converts the AST to a JSON format
10
- # that can be consumed by the Rust formatter
11
- class PrismBridge
12
- extend PrismNodeExtractor
13
-
14
- class ParseError < StandardError; end
15
-
16
- # Parse Ruby source code and return serialized AST
17
- # @param source [String] Ruby source code to parse
18
- # @return [String] JSON-serialized AST with comments
19
- # @raise [ParseError] if parsing fails
20
- def self.parse(source)
21
- result = Prism.parse(source)
22
-
23
- handle_parse_errors(result) if result.failure?
24
-
25
- serialize_ast_with_comments(result)
26
- end
27
-
28
- # Parse Ruby source code from a file
29
- # @param file_path [String] Path to Ruby file
30
- # @return [String] JSON-serialized AST
31
- # @raise [ParseError] if parsing fails
32
- # @raise [Errno::ENOENT] if file doesn't exist
33
- def self.parse_file(file_path)
34
- source = File.read(file_path)
35
- parse(source)
36
- rescue Errno::ENOENT
37
- raise ParseError, "File not found: #{file_path}"
38
- end
39
-
40
- # Handle parsing errors from Prism
41
- def self.handle_parse_errors(result)
42
- errors = result.errors.map do |error|
43
- {
44
- line: error.location.start_line,
45
- column: error.location.start_column,
46
- message: error.message
47
- }
48
- end
49
-
50
- error_messages = errors.map do |err|
51
- "#{err[:line]}:#{err[:column]}: #{err[:message]}"
52
- end.join("\n")
53
-
54
- raise ParseError, "Parse errors:\n#{error_messages}"
55
- end
56
-
57
- # Serialize the Prism AST to JSON
58
- def self.serialize_ast(node)
59
- JSON.generate(convert_node(node), max_nesting: false)
60
- end
61
-
62
- # Serialize the Prism AST with comments to JSON
63
- def self.serialize_ast_with_comments(result)
64
- comments = result.comments.map do |comment|
65
- loc = comment.location
66
- end_line = loc.end_line
67
- # Prism's `=begin ... =end` (EmbDocComment) location ends at
68
- # `<=end_line>:0`, i.e. the column-0 start of the line AFTER the
69
- # `=end` terminator. Reporting that larger end_line makes the
70
- # comment appear to overlap with the next statement, so the
71
- # comment gets misattributed to a deeper node (e.g. the first
72
- # expression inside the next method body) and ends up emitted in
73
- # the wrong place. Snap it back to the terminator line.
74
- end_line = loc.end_line - 1 if loc.end_column.zero? && loc.end_line > loc.start_line
75
-
76
- {
77
- comment_type: comment.class.name.split('::').last.downcase.gsub('comment', ''),
78
- location: {
79
- start_line: loc.start_line,
80
- start_column: loc.start_column,
81
- end_line: end_line,
82
- end_column: loc.end_column,
83
- start_offset: loc.start_offset,
84
- end_offset: loc.end_offset
85
- },
86
- text: loc.slice,
87
- position: 'leading' # Default position, will be refined by Rust
88
- }
89
- end
90
-
91
- JSON.generate({
92
- ast: convert_node(result.value),
93
- comments: comments
94
- }, max_nesting: false)
95
- end
96
-
97
- # Convert a Prism node to our internal representation
98
- def self.convert_node(node)
99
- return nil if node.nil?
100
-
101
- {
102
- node_type: node_type_name(node),
103
- location: extract_location(node),
104
- children: extract_children(node),
105
- metadata: extract_metadata(node),
106
- comments: extract_comments(node),
107
- formatting: extract_formatting(node)
108
- }
109
- end
110
-
111
- # Get the node type name from Prism node
112
- def self.node_type_name(node)
113
- # Prism node class names are like "Prism::ProgramNode"
114
- # We want just "program_node" in snake_case
115
- node.class.name.split('::').last.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
116
- .gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
117
- end
118
-
119
- # Extract location information from node
120
- def self.extract_location(node)
121
- loc = node.location
122
-
123
- # For heredoc nodes, the location only covers the opening tag (<<~CSV)
124
- # We need to find the maximum end_offset including closing_loc
125
- end_offset = loc.end_offset
126
- end_line = loc.end_line
127
- end_column = loc.end_column
128
-
129
- # Check this node's closing_loc
130
- if node.respond_to?(:closing_loc) && node.closing_loc
131
- closing = node.closing_loc
132
- if closing.end_offset > end_offset
133
- end_offset = closing.end_offset
134
- end_line = heredoc_terminator_line(closing)
135
- end_column = closing.end_column
136
- end
137
- end
138
-
139
- # Recursively check all descendant nodes for heredoc closing_loc
140
- # Issue #74: handled direct children (e.g., LocalVariableWriteNode -> StringNode)
141
- # Issue #86: handles deeper nesting (e.g., CallNode -> ArgumentsNode -> StringNode)
142
- max_closing = find_max_closing_loc_recursive(node)
143
- if max_closing && max_closing[:end_offset] > end_offset
144
- end_offset = max_closing[:end_offset]
145
- end_line = max_closing[:end_line]
146
- end_column = max_closing[:end_column]
147
- end
148
-
149
- {
150
- start_line: loc.start_line,
151
- start_column: loc.start_column,
152
- end_line: end_line,
153
- end_column: end_column,
154
- start_offset: loc.start_offset,
155
- end_offset: end_offset
156
- }
157
- end
158
-
159
- # Prism reports a heredoc's `closing_loc` as `<terminator_line>:0..(terminator_line+1):0`,
160
- # i.e. its `end_line` is the LINE AFTER the terminator. Using that as the
161
- # node's `end_line` makes blank-line preservation fail: a real blank line
162
- # right after the terminator looks identical (diff == 1) to two adjacent
163
- # statements with no separator. Use the terminator's own line instead.
164
- def self.heredoc_terminator_line(closing_loc)
165
- if closing_loc.end_column.zero? && closing_loc.end_line > closing_loc.start_line
166
- closing_loc.start_line
167
- else
168
- closing_loc.end_line
169
- end
170
- end
171
-
172
- # Recursively find the maximum closing_loc among all descendant nodes
173
- # Returns nil if no closing_loc found, otherwise { end_offset:, end_line:, end_column: }
174
- def self.find_max_closing_loc_recursive(node, depth: 0)
175
- return nil if depth > 10
176
-
177
- max_closing = nil
178
-
179
- node.child_nodes.compact.each do |child|
180
- if child.respond_to?(:closing_loc) && child.closing_loc
181
- closing = child.closing_loc
182
- if max_closing.nil? || closing.end_offset > max_closing[:end_offset]
183
- max_closing = {
184
- end_offset: closing.end_offset,
185
- end_line: heredoc_terminator_line(closing),
186
- end_column: closing.end_column
187
- }
188
- end
189
- end
190
-
191
- child_max = find_max_closing_loc_recursive(child, depth: depth + 1)
192
- max_closing = child_max if child_max && (max_closing.nil? || child_max[:end_offset] > max_closing[:end_offset])
193
- end
194
-
195
- max_closing
196
- end
197
-
198
- # Extract child nodes
199
- def self.extract_children(node)
200
- children = []
201
-
202
- begin
203
- # Different node types have different child accessors
204
- children = case node
205
- when Prism::ProgramNode
206
- node.statements ? node.statements.body : []
207
- when Prism::StatementsNode
208
- node.body || []
209
- when Prism::ClassNode
210
- [
211
- node.constant_path,
212
- node.superclass,
213
- node.body
214
- ].compact
215
- when Prism::ModuleNode
216
- [
217
- node.constant_path,
218
- node.body
219
- ].compact
220
- when Prism::DefNode
221
- params = if node.parameters
222
- node.parameters.child_nodes.compact
223
- else
224
- []
225
- end
226
- params + [node.body].compact
227
- when Prism::CallNode
228
- result = []
229
- result << node.receiver if node.receiver
230
- result.concat(node.arguments.child_nodes.compact) if node.arguments
231
- result << node.block if node.block
232
- result
233
- when Prism::IfNode, Prism::UnlessNode
234
- [
235
- node.predicate,
236
- node.statements,
237
- node.consequent
238
- ].compact
239
- when Prism::ElseNode
240
- [node.statements].compact
241
- when Prism::ArrayNode
242
- node.elements || []
243
- when Prism::HashNode
244
- node.elements || []
245
- when Prism::BlockNode
246
- params = if node.parameters
247
- node.parameters.child_nodes.compact
248
- else
249
- []
250
- end
251
- params + [node.body].compact
252
- when Prism::BeginNode
253
- [
254
- node.statements,
255
- node.rescue_clause,
256
- node.else_clause,
257
- node.ensure_clause
258
- ].compact
259
- when Prism::EnsureNode
260
- [node.statements].compact
261
- when Prism::LambdaNode
262
- params = if node.parameters
263
- node.parameters.child_nodes.compact
264
- else
265
- []
266
- end
267
- params + [node.body].compact
268
- when Prism::RescueNode
269
- result = []
270
- result.concat(node.exceptions) if node.exceptions
271
- result << node.reference if node.reference
272
- result << node.statements if node.statements
273
- result << node.subsequent if node.subsequent
274
- result
275
- when Prism::SymbolNode, Prism::LocalVariableReadNode, Prism::InstanceVariableReadNode
276
- []
277
- when Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode
278
- [node.value].compact
279
- when Prism::ReturnNode
280
- node.arguments ? node.arguments.child_nodes.compact : []
281
- when Prism::OrNode
282
- [node.left, node.right].compact
283
- when Prism::AssocNode
284
- [node.key, node.value].compact
285
- when Prism::KeywordHashNode
286
- node.elements || []
287
- when Prism::InterpolatedStringNode
288
- node.parts || []
289
- when Prism::EmbeddedStatementsNode
290
- [node.statements].compact
291
- when Prism::CaseNode
292
- [node.predicate, *node.conditions, node.else_clause].compact
293
- when Prism::WhenNode
294
- [*node.conditions, node.statements].compact
295
- when Prism::WhileNode, Prism::UntilNode
296
- [node.predicate, node.statements].compact
297
- when Prism::ForNode
298
- [node.index, node.collection, node.statements].compact
299
- when Prism::BreakNode, Prism::NextNode
300
- node.arguments ? node.arguments.child_nodes.compact : []
301
- when Prism::RedoNode, Prism::RetryNode
302
- []
303
- when Prism::YieldNode
304
- node.arguments ? node.arguments.child_nodes.compact : []
305
- when Prism::SuperNode
306
- result = []
307
- result.concat(node.arguments.child_nodes.compact) if node.arguments
308
- result << node.block if node.block
309
- result
310
- when Prism::ForwardingSuperNode
311
- node.block ? [node.block] : []
312
- when Prism::RescueModifierNode
313
- [node.expression, node.rescue_expression].compact
314
- when Prism::RangeNode
315
- [node.left, node.right].compact
316
- when Prism::RegularExpressionNode
317
- []
318
- when Prism::SplatNode
319
- [node.expression].compact
320
- when Prism::AndNode
321
- [node.left, node.right].compact
322
- when Prism::InterpolatedRegularExpressionNode, Prism::InterpolatedSymbolNode,
323
- Prism::InterpolatedXStringNode
324
- node.parts || []
325
- when Prism::XStringNode
326
- []
327
- when Prism::ClassVariableReadNode, Prism::GlobalVariableReadNode, Prism::SelfNode
328
- []
329
- when Prism::ClassVariableWriteNode, Prism::GlobalVariableWriteNode
330
- [node.value].compact
331
- when Prism::ClassVariableOrWriteNode, Prism::ClassVariableAndWriteNode,
332
- Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableAndWriteNode,
333
- Prism::LocalVariableOrWriteNode, Prism::LocalVariableAndWriteNode,
334
- Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableAndWriteNode,
335
- Prism::ConstantOrWriteNode, Prism::ConstantAndWriteNode
336
- [node.value].compact
337
- when Prism::ClassVariableOperatorWriteNode, Prism::GlobalVariableOperatorWriteNode,
338
- Prism::LocalVariableOperatorWriteNode, Prism::InstanceVariableOperatorWriteNode,
339
- Prism::ConstantOperatorWriteNode
340
- [node.value].compact
341
- when Prism::ConstantPathOrWriteNode, Prism::ConstantPathAndWriteNode,
342
- Prism::ConstantPathOperatorWriteNode
343
- [node.target, node.value].compact
344
- when Prism::ConstantPathWriteNode
345
- [node.target, node.value].compact
346
- when Prism::CaseMatchNode
347
- [node.predicate, *node.conditions, node.else_clause].compact
348
- when Prism::InNode
349
- [node.pattern, node.statements].compact
350
- when Prism::MatchPredicateNode, Prism::MatchRequiredNode
351
- [node.value, node.pattern].compact
352
- when Prism::ParenthesesNode
353
- [node.body].compact
354
- when Prism::DefinedNode
355
- [node.value].compact
356
- when Prism::SingletonClassNode
357
- [node.expression, node.body].compact
358
- when Prism::AliasMethodNode
359
- [node.new_name, node.old_name].compact
360
- when Prism::AliasGlobalVariableNode
361
- [node.new_name, node.old_name].compact
362
- when Prism::UndefNode
363
- node.names || []
364
- when Prism::AssocSplatNode
365
- [node.value].compact
366
- when Prism::BlockArgumentNode
367
- [node.expression].compact
368
- when Prism::MultiWriteNode
369
- [*node.lefts, node.rest, *node.rights, node.value].compact
370
- when Prism::MultiTargetNode
371
- [*node.lefts, node.rest, *node.rights].compact
372
- when Prism::SourceFileNode, Prism::SourceLineNode, Prism::SourceEncodingNode
373
- []
374
- when Prism::PreExecutionNode, Prism::PostExecutionNode
375
- [node.statements].compact
376
- # Numeric literals
377
- when Prism::RationalNode, Prism::ImaginaryNode
378
- [node.numeric].compact
379
- # String interpolation
380
- when Prism::EmbeddedVariableNode
381
- [node.variable].compact
382
- # Pattern matching patterns
383
- when Prism::ArrayPatternNode
384
- [*node.requireds, node.rest, *node.posts].compact
385
- when Prism::HashPatternNode
386
- [*node.elements, node.rest].compact
387
- when Prism::FindPatternNode
388
- [node.left, *node.requireds, node.right].compact
389
- when Prism::CapturePatternNode
390
- [node.value, node.target].compact
391
- when Prism::AlternationPatternNode
392
- [node.left, node.right].compact
393
- when Prism::PinnedExpressionNode
394
- [node.expression].compact
395
- when Prism::PinnedVariableNode
396
- [node.variable].compact
397
- # Forwarding and special parameters
398
- when Prism::ForwardingArgumentsNode, Prism::ForwardingParameterNode,
399
- Prism::NoKeywordsParameterNode
400
- []
401
- # References
402
- when Prism::BackReferenceReadNode, Prism::NumberedReferenceReadNode
403
- []
404
- # Call/Index compound assignment
405
- when Prism::CallAndWriteNode, Prism::CallOrWriteNode, Prism::CallOperatorWriteNode
406
- [node.receiver, node.value].compact
407
- when Prism::IndexAndWriteNode, Prism::IndexOrWriteNode, Prism::IndexOperatorWriteNode
408
- [node.receiver, node.arguments, node.value].compact
409
- # Match
410
- when Prism::MatchWriteNode
411
- [node.call, *node.targets].compact
412
- when Prism::MatchLastLineNode, Prism::InterpolatedMatchLastLineNode
413
- []
414
- # Other
415
- when Prism::FlipFlopNode
416
- [node.left, node.right].compact
417
- when Prism::ImplicitNode
418
- [node.value].compact
419
- when Prism::ImplicitRestNode
420
- []
421
- else
422
- # For unknown types, try to get child nodes if they exist
423
- []
424
- end
425
- rescue StandardError => e
426
- # Log warning in debug mode but continue processing
427
- warn "Warning: Failed to extract children from #{node.class}: #{e.message}" if $DEBUG
428
- children = []
429
- end
430
-
431
- children.compact.map { |child| convert_node(child) }
432
- end
433
-
434
- # Extract metadata specific to node type
435
- def self.extract_metadata(node)
436
- metadata = {}
437
-
438
- case node
439
- when Prism::ClassNode
440
- if (name = extract_class_or_module_name(node))
441
- metadata['name'] = name
442
- end
443
- if (superclass = extract_superclass_name(node))
444
- metadata['superclass'] = superclass
445
- end
446
- when Prism::ModuleNode
447
- if (name = extract_class_or_module_name(node))
448
- metadata['name'] = name
449
- end
450
- when Prism::DefNode
451
- # Prefer `name_loc.slice` over `name.to_s` so unary operator suffixes
452
- # (`def !@`, `def +@`, `def -@`) survive the round-trip. Prism
453
- # normalizes `name` to the symbol with the `@` stripped for `!@`,
454
- # which would otherwise rewrite `def !@` to `def !` silently.
455
- name = if node.respond_to?(:name_loc) && node.name_loc
456
- node.name_loc.slice
457
- else
458
- extract_node_name(node)
459
- end
460
- metadata['name'] = name if name
461
- metadata['parameters_count'] = extract_parameter_count(node).to_s
462
- # Extract parameters text directly from source
463
- if node.parameters
464
- metadata['parameters_text'] = node.parameters.location.slice
465
- metadata['has_parens'] = (!node.lparen_loc.nil?).to_s
466
- end
467
- # Check if this is a class method (def self.method_name)
468
- if node.respond_to?(:receiver) && node.receiver
469
- receiver = node.receiver
470
- if receiver.is_a?(Prism::SelfNode)
471
- metadata['receiver'] = 'self'
472
- elsif receiver.respond_to?(:slice)
473
- metadata['receiver'] = receiver.slice
474
- end
475
- end
476
- when Prism::CallNode
477
- if (name = extract_node_name(node))
478
- metadata['name'] = name
479
- end
480
- if (message = extract_message_name(node))
481
- metadata['message'] = message
482
- end
483
- when Prism::StringNode
484
- if (content = extract_string_content(node))
485
- metadata['content'] = content
486
- end
487
- when Prism::IntegerNode
488
- if (value = extract_literal_value(node))
489
- metadata['value'] = value
490
- end
491
- when Prism::FloatNode
492
- if (value = extract_literal_value(node))
493
- metadata['value'] = value
494
- end
495
- when Prism::SymbolNode
496
- if (value = extract_literal_value(node))
497
- metadata['value'] = value
498
- end
499
- when Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode
500
- metadata['name'] = node.name.to_s
501
- when Prism::IfNode, Prism::UnlessNode
502
- # Detect ternary operator: if_keyword_loc is nil for ternary
503
- metadata['is_ternary'] = node.if_keyword_loc.nil?.to_s if node.respond_to?(:if_keyword_loc)
504
- end
505
-
506
- metadata
507
- end
508
-
509
- # Extract comments associated with the node
510
- def self.extract_comments(_node)
511
- # Prism attaches comments to the parse result, not individual nodes
512
- # For Phase 1, we'll return empty array and implement in Phase 2
513
- []
514
- end
515
-
516
- # Extract formatting information
517
- def self.extract_formatting(node)
518
- loc = node.location
519
- {
520
- indent_level: 0, # Will be calculated during formatting
521
- needs_blank_line_before: false,
522
- needs_blank_line_after: false,
523
- preserve_newlines: false,
524
- multiline: loc.start_line != loc.end_line,
525
- original_formatting: nil # Can store original text if needed
526
- }
527
- end
528
- end
529
- end