rfmt 1.3.0-x86_64-darwin

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.
@@ -0,0 +1,390 @@
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))
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
+ {
66
+ comment_type: comment.class.name.split('::').last.downcase.gsub('comment', ''),
67
+ location: {
68
+ start_line: comment.location.start_line,
69
+ start_column: comment.location.start_column,
70
+ end_line: comment.location.end_line,
71
+ end_column: comment.location.end_column,
72
+ start_offset: comment.location.start_offset,
73
+ end_offset: comment.location.end_offset
74
+ },
75
+ text: comment.location.slice,
76
+ position: 'leading' # Default position, will be refined by Rust
77
+ }
78
+ end
79
+
80
+ JSON.generate({
81
+ ast: convert_node(result.value),
82
+ comments: comments
83
+ })
84
+ end
85
+
86
+ # Convert a Prism node to our internal representation
87
+ def self.convert_node(node)
88
+ return nil if node.nil?
89
+
90
+ {
91
+ node_type: node_type_name(node),
92
+ location: extract_location(node),
93
+ children: extract_children(node),
94
+ metadata: extract_metadata(node),
95
+ comments: extract_comments(node),
96
+ formatting: extract_formatting(node)
97
+ }
98
+ end
99
+
100
+ # Get the node type name from Prism node
101
+ def self.node_type_name(node)
102
+ # Prism node class names are like "Prism::ProgramNode"
103
+ # We want just "program_node" in snake_case
104
+ node.class.name.split('::').last.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
105
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
106
+ end
107
+
108
+ # Extract location information from node
109
+ def self.extract_location(node)
110
+ loc = node.location
111
+ {
112
+ start_line: loc.start_line,
113
+ start_column: loc.start_column,
114
+ end_line: loc.end_line,
115
+ end_column: loc.end_column,
116
+ start_offset: loc.start_offset,
117
+ end_offset: loc.end_offset
118
+ }
119
+ end
120
+
121
+ # Extract child nodes
122
+ def self.extract_children(node)
123
+ children = []
124
+
125
+ begin
126
+ # Different node types have different child accessors
127
+ children = case node
128
+ when Prism::ProgramNode
129
+ node.statements ? node.statements.body : []
130
+ when Prism::StatementsNode
131
+ node.body || []
132
+ when Prism::ClassNode
133
+ [
134
+ node.constant_path,
135
+ node.superclass,
136
+ node.body
137
+ ].compact
138
+ when Prism::ModuleNode
139
+ [
140
+ node.constant_path,
141
+ node.body
142
+ ].compact
143
+ when Prism::DefNode
144
+ params = if node.parameters
145
+ node.parameters.child_nodes.compact
146
+ else
147
+ []
148
+ end
149
+ params + [node.body].compact
150
+ when Prism::CallNode
151
+ result = []
152
+ result << node.receiver if node.receiver
153
+ result.concat(node.arguments.child_nodes.compact) if node.arguments
154
+ result << node.block if node.block
155
+ result
156
+ when Prism::IfNode, Prism::UnlessNode
157
+ [
158
+ node.predicate,
159
+ node.statements,
160
+ node.consequent
161
+ ].compact
162
+ when Prism::ElseNode
163
+ [node.statements].compact
164
+ when Prism::ArrayNode
165
+ node.elements || []
166
+ when Prism::HashNode
167
+ node.elements || []
168
+ when Prism::BlockNode
169
+ params = if node.parameters
170
+ node.parameters.child_nodes.compact
171
+ else
172
+ []
173
+ end
174
+ params + [node.body].compact
175
+ when Prism::BeginNode
176
+ [
177
+ node.statements,
178
+ node.rescue_clause,
179
+ node.ensure_clause
180
+ ].compact
181
+ when Prism::EnsureNode
182
+ [node.statements].compact
183
+ when Prism::LambdaNode
184
+ params = if node.parameters
185
+ node.parameters.child_nodes.compact
186
+ else
187
+ []
188
+ end
189
+ params + [node.body].compact
190
+ when Prism::RescueNode
191
+ result = []
192
+ result.concat(node.exceptions) if node.exceptions
193
+ result << node.reference if node.reference
194
+ result << node.statements if node.statements
195
+ result << node.subsequent if node.subsequent
196
+ result
197
+ when Prism::SymbolNode, Prism::LocalVariableReadNode, Prism::InstanceVariableReadNode
198
+ []
199
+ when Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode
200
+ [node.value].compact
201
+ when Prism::ReturnNode
202
+ node.arguments ? node.arguments.child_nodes.compact : []
203
+ when Prism::OrNode
204
+ [node.left, node.right].compact
205
+ when Prism::AssocNode
206
+ [node.key, node.value].compact
207
+ when Prism::KeywordHashNode
208
+ node.elements || []
209
+ when Prism::InterpolatedStringNode
210
+ node.parts || []
211
+ when Prism::EmbeddedStatementsNode
212
+ [node.statements].compact
213
+ when Prism::CaseNode
214
+ [node.predicate, *node.conditions, node.else_clause].compact
215
+ when Prism::WhenNode
216
+ [*node.conditions, node.statements].compact
217
+ when Prism::WhileNode, Prism::UntilNode
218
+ [node.predicate, node.statements].compact
219
+ when Prism::ForNode
220
+ [node.index, node.collection, node.statements].compact
221
+ when Prism::BreakNode, Prism::NextNode
222
+ node.arguments ? node.arguments.child_nodes.compact : []
223
+ when Prism::RedoNode, Prism::RetryNode
224
+ []
225
+ when Prism::YieldNode
226
+ node.arguments ? node.arguments.child_nodes.compact : []
227
+ when Prism::SuperNode
228
+ result = []
229
+ result.concat(node.arguments.child_nodes.compact) if node.arguments
230
+ result << node.block if node.block
231
+ result
232
+ when Prism::ForwardingSuperNode
233
+ node.block ? [node.block] : []
234
+ when Prism::RescueModifierNode
235
+ [node.expression, node.rescue_expression].compact
236
+ when Prism::RangeNode
237
+ [node.left, node.right].compact
238
+ when Prism::RegularExpressionNode
239
+ []
240
+ when Prism::SplatNode
241
+ [node.expression].compact
242
+ when Prism::AndNode
243
+ [node.left, node.right].compact
244
+ when Prism::NotNode
245
+ [node.expression].compact
246
+ when Prism::InterpolatedRegularExpressionNode, Prism::InterpolatedSymbolNode,
247
+ Prism::InterpolatedXStringNode
248
+ node.parts || []
249
+ when Prism::XStringNode
250
+ []
251
+ when Prism::ClassVariableReadNode, Prism::GlobalVariableReadNode, Prism::SelfNode
252
+ []
253
+ when Prism::ClassVariableWriteNode, Prism::GlobalVariableWriteNode
254
+ [node.value].compact
255
+ when Prism::ClassVariableOrWriteNode, Prism::ClassVariableAndWriteNode,
256
+ Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableAndWriteNode,
257
+ Prism::LocalVariableOrWriteNode, Prism::LocalVariableAndWriteNode,
258
+ Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableAndWriteNode,
259
+ Prism::ConstantOrWriteNode, Prism::ConstantAndWriteNode
260
+ [node.value].compact
261
+ when Prism::ClassVariableOperatorWriteNode, Prism::GlobalVariableOperatorWriteNode,
262
+ Prism::LocalVariableOperatorWriteNode, Prism::InstanceVariableOperatorWriteNode,
263
+ Prism::ConstantOperatorWriteNode
264
+ [node.value].compact
265
+ when Prism::ConstantPathOrWriteNode, Prism::ConstantPathAndWriteNode,
266
+ Prism::ConstantPathOperatorWriteNode
267
+ [node.target, node.value].compact
268
+ when Prism::ConstantPathWriteNode
269
+ [node.target, node.value].compact
270
+ when Prism::CaseMatchNode
271
+ [node.predicate, *node.conditions, node.else_clause].compact
272
+ when Prism::InNode
273
+ [node.pattern, node.statements].compact
274
+ when Prism::MatchPredicateNode, Prism::MatchRequiredNode
275
+ [node.value, node.pattern].compact
276
+ when Prism::ParenthesesNode
277
+ [node.body].compact
278
+ when Prism::DefinedNode
279
+ [node.value].compact
280
+ when Prism::SingletonClassNode
281
+ [node.expression, node.body].compact
282
+ when Prism::AliasMethodNode
283
+ [node.new_name, node.old_name].compact
284
+ when Prism::AliasGlobalVariableNode
285
+ [node.new_name, node.old_name].compact
286
+ when Prism::UndefNode
287
+ node.names || []
288
+ when Prism::AssocSplatNode
289
+ [node.value].compact
290
+ when Prism::BlockArgumentNode
291
+ [node.expression].compact
292
+ when Prism::MultiWriteNode
293
+ [*node.lefts, node.rest, *node.rights, node.value].compact
294
+ when Prism::MultiTargetNode
295
+ [*node.lefts, node.rest, *node.rights].compact
296
+ else
297
+ # For unknown types, try to get child nodes if they exist
298
+ []
299
+ end
300
+ rescue StandardError => e
301
+ # Log warning in debug mode but continue processing
302
+ warn "Warning: Failed to extract children from #{node.class}: #{e.message}" if $DEBUG
303
+ children = []
304
+ end
305
+
306
+ children.compact.map { |child| convert_node(child) }
307
+ end
308
+
309
+ # Extract metadata specific to node type
310
+ def self.extract_metadata(node)
311
+ metadata = {}
312
+
313
+ case node
314
+ when Prism::ClassNode
315
+ if (name = extract_class_or_module_name(node))
316
+ metadata['name'] = name
317
+ end
318
+ if (superclass = extract_superclass_name(node))
319
+ metadata['superclass'] = superclass
320
+ end
321
+ when Prism::ModuleNode
322
+ if (name = extract_class_or_module_name(node))
323
+ metadata['name'] = name
324
+ end
325
+ when Prism::DefNode
326
+ if (name = extract_node_name(node))
327
+ metadata['name'] = name
328
+ end
329
+ metadata['parameters_count'] = extract_parameter_count(node).to_s
330
+ # Check if this is a class method (def self.method_name)
331
+ if node.respond_to?(:receiver) && node.receiver
332
+ receiver = node.receiver
333
+ if receiver.is_a?(Prism::SelfNode)
334
+ metadata['receiver'] = 'self'
335
+ elsif receiver.respond_to?(:slice)
336
+ metadata['receiver'] = receiver.slice
337
+ end
338
+ end
339
+ when Prism::CallNode
340
+ if (name = extract_node_name(node))
341
+ metadata['name'] = name
342
+ end
343
+ if (message = extract_message_name(node))
344
+ metadata['message'] = message
345
+ end
346
+ when Prism::StringNode
347
+ if (content = extract_string_content(node))
348
+ metadata['content'] = content
349
+ end
350
+ when Prism::IntegerNode
351
+ if (value = extract_literal_value(node))
352
+ metadata['value'] = value
353
+ end
354
+ when Prism::FloatNode
355
+ if (value = extract_literal_value(node))
356
+ metadata['value'] = value
357
+ end
358
+ when Prism::SymbolNode
359
+ if (value = extract_literal_value(node))
360
+ metadata['value'] = value
361
+ end
362
+ when Prism::IfNode, Prism::UnlessNode
363
+ # Detect ternary operator: if_keyword_loc is nil for ternary
364
+ metadata['is_ternary'] = node.if_keyword_loc.nil?.to_s if node.respond_to?(:if_keyword_loc)
365
+ end
366
+
367
+ metadata
368
+ end
369
+
370
+ # Extract comments associated with the node
371
+ def self.extract_comments(_node)
372
+ # Prism attaches comments to the parse result, not individual nodes
373
+ # For Phase 1, we'll return empty array and implement in Phase 2
374
+ []
375
+ end
376
+
377
+ # Extract formatting information
378
+ def self.extract_formatting(node)
379
+ loc = node.location
380
+ {
381
+ indent_level: 0, # Will be calculated during formatting
382
+ needs_blank_line_before: false,
383
+ needs_blank_line_after: false,
384
+ preserve_newlines: false,
385
+ multiline: loc.start_line != loc.end_line,
386
+ original_formatting: nil # Can store original text if needed
387
+ }
388
+ end
389
+ end
390
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rfmt
4
+ # PrismNodeExtractor provides safe methods to extract information from Prism nodes
5
+ # This module encapsulates the logic for accessing Prism node properties,
6
+ # making the code resilient to Prism API changes
7
+ module PrismNodeExtractor
8
+ # Extract the name from a node
9
+ # @param node [Prism::Node] The node to extract name from
10
+ # @return [String, nil] The node name or nil if not available
11
+ def extract_node_name(node)
12
+ return nil unless node.respond_to?(:name)
13
+
14
+ node.name.to_s
15
+ end
16
+
17
+ # Extract full name from class or module node (handles namespaced names like Foo::Bar::Baz)
18
+ # @param node [Prism::ClassNode, Prism::ModuleNode] The class or module node
19
+ # @return [String, nil] The full name or nil if not available
20
+ def extract_class_or_module_name(node)
21
+ return nil unless node.respond_to?(:constant_path)
22
+
23
+ cp = node.constant_path
24
+ return node.name.to_s if cp.nil?
25
+
26
+ case cp
27
+ when Prism::ConstantReadNode
28
+ cp.name.to_s
29
+ when Prism::ConstantPathNode
30
+ if cp.respond_to?(:full_name)
31
+ cp.full_name.to_s
32
+ elsif cp.respond_to?(:slice)
33
+ cp.slice
34
+ else
35
+ cp.location.slice
36
+ end
37
+ else
38
+ node.name.to_s
39
+ end
40
+ end
41
+
42
+ # Extract superclass name from a class node
43
+ # @param class_node [Prism::ClassNode] The class node
44
+ # @return [String, nil] The superclass name or nil if not available
45
+ def extract_superclass_name(class_node)
46
+ return nil unless class_node.respond_to?(:superclass)
47
+
48
+ sc = class_node.superclass
49
+ return nil if sc.nil?
50
+
51
+ case sc
52
+ when Prism::ConstantReadNode
53
+ sc.name.to_s
54
+ when Prism::ConstantPathNode
55
+ # Try full_name first, fall back to slice for original source
56
+ if sc.respond_to?(:full_name)
57
+ sc.full_name.to_s
58
+ elsif sc.respond_to?(:slice)
59
+ sc.slice
60
+ else
61
+ sc.location.slice
62
+ end
63
+ when Prism::CallNode
64
+ # Handle cases like ActiveRecord::Migration[8.1]
65
+ # Use slice to get the original source text
66
+ sc.slice
67
+ else
68
+ # Fallback: try to get original source text
69
+ if sc.respond_to?(:slice)
70
+ sc.slice
71
+ else
72
+ sc.location.slice
73
+ end
74
+ end
75
+ end
76
+
77
+ # Extract parameter count from a method definition node
78
+ # @param def_node [Prism::DefNode] The method definition node
79
+ # @return [Integer] The number of parameters (0 if none)
80
+ def extract_parameter_count(def_node)
81
+ return 0 unless def_node.respond_to?(:parameters)
82
+ return 0 if def_node.parameters.nil?
83
+ return 0 unless def_node.parameters.respond_to?(:child_nodes)
84
+
85
+ def_node.parameters.child_nodes.compact.length
86
+ end
87
+
88
+ # Extract message name from a call node
89
+ # @param call_node [Prism::CallNode] The call node
90
+ # @return [String, nil] The message name or nil if not available
91
+ def extract_message_name(call_node)
92
+ return nil unless call_node.respond_to?(:message)
93
+
94
+ call_node.message.to_s
95
+ end
96
+
97
+ # Extract content from a string node
98
+ # @param string_node [Prism::StringNode] The string node
99
+ # @return [String, nil] The string content or nil if not available
100
+ def extract_string_content(string_node)
101
+ return nil unless string_node.respond_to?(:content)
102
+
103
+ string_node.content
104
+ end
105
+
106
+ # Extract value from a literal node (Integer, Float, Symbol)
107
+ # @param node [Prism::Node] The literal node
108
+ # @return [String, nil] The value as string or nil if not available
109
+ def extract_literal_value(node)
110
+ return nil unless node.respond_to?(:value)
111
+
112
+ node.value.to_s
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rfmt
4
+ VERSION = '1.3.0'
5
+ end
data/lib/rfmt.rb ADDED
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rfmt/version'
4
+ require_relative 'rfmt/rfmt'
5
+ require_relative 'rfmt/prism_bridge'
6
+
7
+ module Rfmt
8
+ class Error < StandardError; end
9
+ # Errors from Rust side
10
+ class RfmtError < Error; end
11
+ # AST validation errors
12
+ class ValidationError < RfmtError; end
13
+
14
+ # Format Ruby source code
15
+ # @param source [String] Ruby source code to format
16
+ # @return [String] Formatted Ruby code
17
+ def self.format(source)
18
+ # Step 1: Parse with Prism (Ruby side)
19
+ prism_json = PrismBridge.parse(source)
20
+
21
+ # Step 2: Format in Rust
22
+ # Pass both source and AST to enable source extraction fallback
23
+ format_code(source, prism_json)
24
+ rescue PrismBridge::ParseError => e
25
+ # Re-raise with more context
26
+ raise Error, "Failed to parse Ruby code: #{e.message}"
27
+ rescue RfmtError
28
+ # Rust side errors are re-raised as-is to preserve error details
29
+ raise
30
+ rescue StandardError => e
31
+ raise Error, "Unexpected error during formatting: #{e.class}: #{e.message}"
32
+ end
33
+
34
+ # Format a Ruby file
35
+ # @param path [String] Path to Ruby file
36
+ # @return [String] Formatted Ruby code
37
+ def self.format_file(path)
38
+ source = File.read(path)
39
+ format(source)
40
+ rescue Errno::ENOENT
41
+ raise Error, "File not found: #{path}"
42
+ end
43
+
44
+ # Get version information
45
+ # @return [String] Version string including Ruby and Rust versions
46
+ def self.version_info
47
+ "Ruby: #{VERSION}, Rust: #{rust_version}"
48
+ end
49
+
50
+ # Parse Ruby code to AST (for debugging)
51
+ # @param source [String] Ruby source code
52
+ # @return [String] AST representation
53
+ def self.parse(source)
54
+ prism_json = PrismBridge.parse(source)
55
+ parse_to_json(prism_json)
56
+ end
57
+
58
+ # Configuration management
59
+ module Config
60
+ # Default configuration template
61
+ DEFAULT_CONFIG = <<~YAML
62
+ # rfmt Configuration File
63
+ # This file controls how rfmt formats your Ruby code.
64
+ # See https://github.com/fs0414/rfmt for full documentation.
65
+
66
+ version: "1.0"
67
+
68
+ # Formatting options
69
+ formatting:
70
+ # Maximum line length before wrapping (40-500)
71
+ line_length: 100
72
+
73
+ # Number of spaces or tabs per indentation level (1-8)
74
+ indent_width: 2
75
+
76
+ # Use "spaces" or "tabs" for indentation
77
+ indent_style: "spaces"
78
+
79
+ # Quote style for strings: "double", "single", or "consistent"
80
+ quote_style: "double"
81
+
82
+ # Files to include in formatting (glob patterns)
83
+ include:
84
+ - "**/*.rb"
85
+ - "**/*.rake"
86
+ - "**/Rakefile"
87
+ - "**/Gemfile"
88
+
89
+ # Files to exclude from formatting (glob patterns)
90
+ exclude:
91
+ - "vendor/**/*"
92
+ - "tmp/**/*"
93
+ - "node_modules/**/*"
94
+ - "db/schema.rb"
95
+ YAML
96
+
97
+ # Generate a default configuration file
98
+ # @param path [String] Path where to create the config file (default: .rfmt.yml)
99
+ # @param force [Boolean] Overwrite existing file if true
100
+ # @return [Boolean] true if file was created, false if already exists
101
+ def self.init(path = '.rfmt.yml', force: false)
102
+ if File.exist?(path) && !force
103
+ warn "Configuration file already exists: #{path}"
104
+ warn 'Use force: true to overwrite'
105
+ return false
106
+ end
107
+
108
+ File.write(path, DEFAULT_CONFIG)
109
+ puts "Created rfmt configuration file: #{path}"
110
+ true
111
+ end
112
+
113
+ # Find configuration file in current or parent directories
114
+ # @return [String, nil] Path to config file or nil if not found
115
+ def self.find
116
+ current_dir = Dir.pwd
117
+
118
+ loop do
119
+ ['.rfmt.yml', '.rfmt.yaml', 'rfmt.yml', 'rfmt.yaml'].each do |filename|
120
+ config_path = File.join(current_dir, filename)
121
+ return config_path if File.exist?(config_path)
122
+ end
123
+
124
+ parent = File.dirname(current_dir)
125
+ break if parent == current_dir # Reached root
126
+
127
+ current_dir = parent
128
+ end
129
+
130
+ # Check user home directory
131
+ home_dir = begin
132
+ Dir.home
133
+ rescue StandardError
134
+ nil
135
+ end
136
+ if home_dir
137
+ ['.rfmt.yml', '.rfmt.yaml', 'rfmt.yml', 'rfmt.yaml'].each do |filename|
138
+ config_path = File.join(home_dir, filename)
139
+ return config_path if File.exist?(config_path)
140
+ end
141
+ end
142
+
143
+ nil
144
+ end
145
+
146
+ # Check if configuration file exists
147
+ # @return [Boolean] true if config file exists
148
+ def self.exists?
149
+ !find.nil?
150
+ end
151
+
152
+ # Load and validate configuration file
153
+ # @param path [String, nil] Path to config file (default: auto-detect)
154
+ # @return [Hash] Loaded configuration
155
+ def self.load(path = nil)
156
+ require 'yaml'
157
+
158
+ config_path = path || find
159
+
160
+ unless config_path
161
+ warn 'No configuration file found, using defaults'
162
+ return {}
163
+ end
164
+
165
+ YAML.load_file(config_path)
166
+ rescue Errno::ENOENT
167
+ raise Error, "Configuration file not found: #{config_path}"
168
+ rescue Psych::SyntaxError => e
169
+ raise Error, "Invalid YAML in configuration file: #{e.message}"
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_lsp/addon'
4
+ require_relative 'formatter_runner'
5
+
6
+ module RubyLsp
7
+ module Rfmt
8
+ class Addon < ::RubyLsp::Addon
9
+ def name
10
+ 'rfmt'
11
+ end
12
+
13
+ def activate(global_state, _message_queue)
14
+ global_state.register_formatter('rfmt', FormatterRunner.new)
15
+ end
16
+
17
+ def deactivate; end
18
+ end
19
+ end
20
+ end