rfmt 1.7.0 → 2.0.0.beta1
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 +4 -4
- data/CHANGELOG.md +25 -0
- data/Cargo.lock +90 -1225
- data/Cargo.toml +6 -0
- data/README.md +32 -28
- data/ext/rfmt/Cargo.toml +9 -28
- data/ext/rfmt/src/ast/mod.rs +2 -0
- data/ext/rfmt/src/config/mod.rs +267 -30
- data/ext/rfmt/src/error/mod.rs +14 -3
- data/ext/rfmt/src/format/formatter.rs +22 -11
- data/ext/rfmt/src/format/registry.rs +8 -0
- data/ext/rfmt/src/format/rule.rs +208 -136
- data/ext/rfmt/src/format/rules/call.rs +14 -22
- data/ext/rfmt/src/format/rules/fallback.rs +4 -11
- data/ext/rfmt/src/format/rules/if_unless.rs +25 -3
- data/ext/rfmt/src/format/rules/loops.rs +3 -1
- data/ext/rfmt/src/format/rules/variable_write.rs +16 -9
- data/ext/rfmt/src/lib.rs +45 -12
- data/ext/rfmt/src/parser/mod.rs +2 -0
- data/ext/rfmt/src/parser/native_adapter.rs +2735 -0
- data/ext/rfmt/src/parser/prism_adapter.rs +5 -0
- data/ext/rfmt/src/validation.rs +69 -0
- data/ext/rfmt/tests/fixtures/parity/comments_mixed.rb +9 -0
- data/ext/rfmt/tests/fixtures/parity/constructs.rb +50 -0
- data/ext/rfmt/tests/fixtures/parity/embdoc.rb +12 -0
- data/ext/rfmt/tests/fixtures/parity/heredoc_assign.rb +15 -0
- data/ext/rfmt/tests/fixtures/parity/heredoc_call_args.rb +17 -0
- data/ext/rfmt/tests/fixtures/parity/metadata_classes.rb +30 -0
- data/ext/rfmt/tests/fixtures/parity/metadata_conditionals.rb +14 -0
- data/ext/rfmt/tests/fixtures/parity/metadata_defs.rb +33 -0
- data/ext/rfmt/tests/fixtures/parity/multibyte.rb +14 -0
- data/ext/rfmt/tests/fixtures/parity/numeric.rb +6 -0
- data/ext/rfmt/tests/fixtures/parity/plain.rb +31 -0
- data/ext/rfmt/tests/native_parity.rs +245 -0
- data/ext/rfmt/tests/ruby_prism_smoke.rs +66 -0
- data/lib/rfmt/cli.rb +37 -7
- data/lib/rfmt/configuration.rb +2 -20
- data/lib/rfmt/version.rb +1 -1
- data/lib/rfmt.rb +47 -19
- metadata +17 -18
- data/lib/rfmt/prism_bridge.rb +0 -529
- data/lib/rfmt/prism_node_extractor.rb +0 -115
data/lib/rfmt/cli.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'fileutils'
|
|
3
4
|
require 'thor'
|
|
4
5
|
|
|
5
6
|
# Check for verbose flag before loading rfmt to set debug mode early
|
|
@@ -44,11 +45,15 @@ module Rfmt
|
|
|
44
45
|
|
|
45
46
|
# Command Line Interface for rfmt
|
|
46
47
|
class CLI < Thor
|
|
48
|
+
def self.exit_on_failure?
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
|
|
47
52
|
# Constants
|
|
48
53
|
PROGRESS_THRESHOLD = 20 # Show progress for file counts >= this
|
|
49
54
|
PROGRESS_INTERVAL = 10 # Update progress every N files
|
|
50
55
|
|
|
51
|
-
class_option :config, type: :string, desc: 'Path to configuration file'
|
|
56
|
+
class_option :config, type: :string, desc: 'Path to configuration file (formatting options and file selection)'
|
|
52
57
|
class_option :verbose, type: :boolean, desc: 'Verbose output'
|
|
53
58
|
|
|
54
59
|
default_command :format
|
|
@@ -120,11 +125,11 @@ module Rfmt
|
|
|
120
125
|
say "Rust extension: #{Rfmt.rust_version}"
|
|
121
126
|
end
|
|
122
127
|
|
|
123
|
-
desc 'config', 'Show
|
|
128
|
+
desc 'config', 'Show the effective configuration the formatter will use'
|
|
124
129
|
def config_cmd
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
say Rfmt.resolved_config(config_path: options[:config])
|
|
131
|
+
rescue Rfmt::Error => e
|
|
132
|
+
raise Thor::Error, e.message
|
|
128
133
|
end
|
|
129
134
|
|
|
130
135
|
desc 'cache SUBCOMMAND', 'Manage cache'
|
|
@@ -183,12 +188,24 @@ module Rfmt
|
|
|
183
188
|
|
|
184
189
|
def load_config
|
|
185
190
|
if options[:config]
|
|
191
|
+
validate_explicit_config!(options[:config])
|
|
186
192
|
Configuration.new(file: options[:config])
|
|
187
193
|
else
|
|
188
194
|
Configuration.discover
|
|
189
195
|
end
|
|
190
196
|
end
|
|
191
197
|
|
|
198
|
+
# Fail fast once instead of repeating the same load error per file
|
|
199
|
+
def validate_explicit_config!(path)
|
|
200
|
+
raise Thor::Error, "Configuration file not found: #{path}" unless File.exist?(path)
|
|
201
|
+
|
|
202
|
+
begin
|
|
203
|
+
Rfmt.resolved_config(config_path: path)
|
|
204
|
+
rescue Rfmt::Error => e
|
|
205
|
+
raise Thor::Error, e.message
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
192
209
|
def initialize_cache_if_enabled
|
|
193
210
|
return nil unless options[:cache]
|
|
194
211
|
|
|
@@ -255,7 +272,7 @@ module Rfmt
|
|
|
255
272
|
start_time = Time.now
|
|
256
273
|
source = File.read(file)
|
|
257
274
|
|
|
258
|
-
formatted = Rfmt.format(source)
|
|
275
|
+
formatted = Rfmt.format(source, config_path: options[:config])
|
|
259
276
|
changed = source != formatted
|
|
260
277
|
|
|
261
278
|
{
|
|
@@ -325,11 +342,24 @@ module Rfmt
|
|
|
325
342
|
end
|
|
326
343
|
|
|
327
344
|
def write_formatted_file(result, cache)
|
|
328
|
-
|
|
345
|
+
atomic_write(result[:file], result[:formatted])
|
|
329
346
|
say "✓ Formatted #{result[:file]}", :green unless options[:quiet]
|
|
330
347
|
cache&.mark_formatted(result[:file])
|
|
331
348
|
end
|
|
332
349
|
|
|
350
|
+
# Temp file must live in the same directory: rename across filesystems is not atomic.
|
|
351
|
+
def atomic_write(path, content)
|
|
352
|
+
# rename would replace a symlink itself; write to its target instead
|
|
353
|
+
path = File.realpath(path) if File.symlink?(path)
|
|
354
|
+
tmp_path = "#{path}.rfmt-#{Process.pid}.tmp"
|
|
355
|
+
mode = File.stat(path).mode
|
|
356
|
+
File.write(tmp_path, content)
|
|
357
|
+
File.chmod(mode, tmp_path)
|
|
358
|
+
File.rename(tmp_path, path)
|
|
359
|
+
ensure
|
|
360
|
+
FileUtils.rm_f(tmp_path)
|
|
361
|
+
end
|
|
362
|
+
|
|
333
363
|
def display_summary(stats, total_files)
|
|
334
364
|
@last_stats = stats # Store for verbose details
|
|
335
365
|
unchanged_count = total_files - stats[:changed] - stats[:errors]
|
data/lib/rfmt/configuration.rb
CHANGED
|
@@ -3,17 +3,13 @@
|
|
|
3
3
|
require 'yaml'
|
|
4
4
|
|
|
5
5
|
module Rfmt
|
|
6
|
-
#
|
|
6
|
+
# File selection and cache concerns only: formatter settings (indent_width
|
|
7
|
+
# etc.) live in the Rust Config, reached via Rfmt.format(config_path:).
|
|
7
8
|
class Configuration
|
|
8
9
|
class ConfigError < StandardError; end
|
|
9
10
|
|
|
10
11
|
DEFAULT_CONFIG = {
|
|
11
12
|
'version' => '1.0',
|
|
12
|
-
'formatting' => {
|
|
13
|
-
'line_length' => 100,
|
|
14
|
-
'indent_width' => 2,
|
|
15
|
-
'indent_style' => 'spaces'
|
|
16
|
-
},
|
|
17
13
|
'include' => ['**/*.rb'],
|
|
18
14
|
'exclude' => ['vendor/**/*', 'tmp/**/*', 'node_modules/**/*']
|
|
19
15
|
}.freeze
|
|
@@ -43,11 +39,6 @@ module Rfmt
|
|
|
43
39
|
(included_files - excluded_files).select { |f| File.file?(f) }
|
|
44
40
|
end
|
|
45
41
|
|
|
46
|
-
# Get formatting configuration
|
|
47
|
-
def formatting_config
|
|
48
|
-
@config['formatting']
|
|
49
|
-
end
|
|
50
|
-
|
|
51
42
|
private
|
|
52
43
|
|
|
53
44
|
def load_configuration(options)
|
|
@@ -64,18 +55,9 @@ module Rfmt
|
|
|
64
55
|
options.delete('file')
|
|
65
56
|
config = deep_merge(config, options) unless options.empty?
|
|
66
57
|
|
|
67
|
-
validate_config!(config)
|
|
68
58
|
config
|
|
69
59
|
end
|
|
70
60
|
|
|
71
|
-
def validate_config!(config)
|
|
72
|
-
line_length = config.dig('formatting', 'line_length')
|
|
73
|
-
raise ConfigError, 'line_length must be positive' if line_length && line_length <= 0
|
|
74
|
-
|
|
75
|
-
indent_width = config.dig('formatting', 'indent_width')
|
|
76
|
-
raise ConfigError, 'indent_width must be positive' if indent_width && indent_width <= 0
|
|
77
|
-
end
|
|
78
|
-
|
|
79
61
|
def deep_merge(hash1, hash2)
|
|
80
62
|
hash1.merge(hash2) do |_key, old_val, new_val|
|
|
81
63
|
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
|
data/lib/rfmt/version.rb
CHANGED
data/lib/rfmt.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'rfmt/version'
|
|
4
4
|
require_relative 'rfmt/native_extension_loader'
|
|
5
|
-
require_relative 'rfmt/prism_bridge'
|
|
6
5
|
|
|
7
6
|
# Load native extension with version-aware loader
|
|
8
7
|
Rfmt::NativeExtensionLoader.load_extension
|
|
@@ -14,25 +13,43 @@ module Rfmt
|
|
|
14
13
|
# AST validation errors
|
|
15
14
|
class ValidationError < RfmtError; end
|
|
16
15
|
|
|
16
|
+
# Rust reports errors as plain StandardError with a [Rfmt::<kind>] prefix;
|
|
17
|
+
# these two kinds map onto the public exception classes.
|
|
18
|
+
NATIVE_PARSE_ERROR_PREFIX = '[Rfmt::ParseError] '
|
|
19
|
+
NATIVE_VALIDATION_ERROR_PREFIX = '[Rfmt::ValidationError] '
|
|
20
|
+
NATIVE_CONFIG_ERROR_PREFIX = '[Rfmt::ConfigError] '
|
|
21
|
+
private_constant :NATIVE_PARSE_ERROR_PREFIX, :NATIVE_VALIDATION_ERROR_PREFIX,
|
|
22
|
+
:NATIVE_CONFIG_ERROR_PREFIX
|
|
23
|
+
|
|
17
24
|
# Format Ruby source code
|
|
25
|
+
# Parsing, config resolution, and output validation all happen natively in Rust
|
|
18
26
|
# @param source [String] Ruby source code to format
|
|
27
|
+
# @param config_path [String, nil] Explicit config file path; nil discovers
|
|
28
|
+
# rfmt.yml/.rfmt.yml from the current directory upward (cached per process)
|
|
19
29
|
# @return [String] Formatted Ruby code
|
|
20
|
-
def self.format(source)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
format_code(source, prism_json)
|
|
27
|
-
rescue PrismBridge::ParseError => e
|
|
28
|
-
# Re-raise with more context
|
|
29
|
-
raise Error, "Failed to parse Ruby code: #{e.message}"
|
|
30
|
-
rescue RfmtError
|
|
31
|
-
# Rust side errors are re-raised as-is to preserve error details
|
|
32
|
-
raise
|
|
30
|
+
def self.format(source, config_path: nil)
|
|
31
|
+
if config_path
|
|
32
|
+
format_code_with_config(source, config_path.to_s)
|
|
33
|
+
else
|
|
34
|
+
format_code(source)
|
|
35
|
+
end
|
|
33
36
|
rescue StandardError => e
|
|
34
|
-
raise
|
|
37
|
+
raise wrap_native_error(e)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.wrap_native_error(error)
|
|
41
|
+
message = error.message
|
|
42
|
+
if message.start_with?(NATIVE_PARSE_ERROR_PREFIX)
|
|
43
|
+
Error.new("Failed to parse Ruby code: #{message.delete_prefix(NATIVE_PARSE_ERROR_PREFIX)}")
|
|
44
|
+
elsif message.start_with?(NATIVE_VALIDATION_ERROR_PREFIX)
|
|
45
|
+
ValidationError.new(message.delete_prefix(NATIVE_VALIDATION_ERROR_PREFIX))
|
|
46
|
+
elsif message.start_with?(NATIVE_CONFIG_ERROR_PREFIX)
|
|
47
|
+
Error.new("Configuration error: #{message.delete_prefix(NATIVE_CONFIG_ERROR_PREFIX)}")
|
|
48
|
+
else
|
|
49
|
+
Error.new("Unexpected error during formatting: #{error.class}: #{message}")
|
|
50
|
+
end
|
|
35
51
|
end
|
|
52
|
+
private_class_method :wrap_native_error
|
|
36
53
|
|
|
37
54
|
# Format a Ruby file
|
|
38
55
|
# @param path [String] Path to Ruby file
|
|
@@ -44,6 +61,15 @@ module Rfmt
|
|
|
44
61
|
raise Error, "File not found: #{path}"
|
|
45
62
|
end
|
|
46
63
|
|
|
64
|
+
# Effective configuration as the Rust formatter resolves it
|
|
65
|
+
# @param config_path [String, nil] Explicit config file path; nil discovers
|
|
66
|
+
# @return [String] YAML dump of the resolved configuration
|
|
67
|
+
def self.resolved_config(config_path: nil)
|
|
68
|
+
resolved_config_yaml(config_path&.to_s)
|
|
69
|
+
rescue StandardError => e
|
|
70
|
+
raise wrap_native_error(e)
|
|
71
|
+
end
|
|
72
|
+
|
|
47
73
|
# Get version information
|
|
48
74
|
# @return [String] Version string including Ruby and Rust versions
|
|
49
75
|
def self.version_info
|
|
@@ -54,8 +80,9 @@ module Rfmt
|
|
|
54
80
|
# @param source [String] Ruby source code
|
|
55
81
|
# @return [String] AST representation
|
|
56
82
|
def self.parse(source)
|
|
57
|
-
|
|
58
|
-
|
|
83
|
+
parse_to_json(source)
|
|
84
|
+
rescue StandardError => e
|
|
85
|
+
raise wrap_native_error(e)
|
|
59
86
|
end
|
|
60
87
|
|
|
61
88
|
# Configuration management
|
|
@@ -114,7 +141,8 @@ module Rfmt
|
|
|
114
141
|
current_dir = Dir.pwd
|
|
115
142
|
|
|
116
143
|
loop do
|
|
117
|
-
|
|
144
|
+
# Same search order as the Rust side (config/mod.rs CONFIG_FILE_NAMES)
|
|
145
|
+
['rfmt.yml', 'rfmt.yaml', '.rfmt.yml', '.rfmt.yaml'].each do |filename|
|
|
118
146
|
config_path = File.join(current_dir, filename)
|
|
119
147
|
return config_path if File.exist?(config_path)
|
|
120
148
|
end
|
|
@@ -132,7 +160,7 @@ module Rfmt
|
|
|
132
160
|
nil
|
|
133
161
|
end
|
|
134
162
|
if home_dir
|
|
135
|
-
['
|
|
163
|
+
['rfmt.yml', 'rfmt.yaml', '.rfmt.yml', '.rfmt.yaml'].each do |filename|
|
|
136
164
|
config_path = File.join(home_dir, filename)
|
|
137
165
|
return config_path if File.exist?(config_path)
|
|
138
166
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rfmt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0.beta1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- fujitani sora
|
|
@@ -65,20 +65,6 @@ dependencies:
|
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '1.24'
|
|
68
|
-
- !ruby/object:Gem::Dependency
|
|
69
|
-
name: prism
|
|
70
|
-
requirement: !ruby/object:Gem::Requirement
|
|
71
|
-
requirements:
|
|
72
|
-
- - "~>"
|
|
73
|
-
- !ruby/object:Gem::Version
|
|
74
|
-
version: '1.6'
|
|
75
|
-
type: :runtime
|
|
76
|
-
prerelease: false
|
|
77
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
-
requirements:
|
|
79
|
-
- - "~>"
|
|
80
|
-
- !ruby/object:Gem::Version
|
|
81
|
-
version: '1.6'
|
|
82
68
|
- !ruby/object:Gem::Dependency
|
|
83
69
|
name: rb_sys
|
|
84
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -156,9 +142,24 @@ files:
|
|
|
156
142
|
- ext/rfmt/src/logging/logger.rs
|
|
157
143
|
- ext/rfmt/src/logging/mod.rs
|
|
158
144
|
- ext/rfmt/src/parser/mod.rs
|
|
145
|
+
- ext/rfmt/src/parser/native_adapter.rs
|
|
159
146
|
- ext/rfmt/src/parser/prism_adapter.rs
|
|
160
147
|
- ext/rfmt/src/policy/mod.rs
|
|
161
148
|
- ext/rfmt/src/policy/validation.rs
|
|
149
|
+
- ext/rfmt/src/validation.rs
|
|
150
|
+
- ext/rfmt/tests/fixtures/parity/comments_mixed.rb
|
|
151
|
+
- ext/rfmt/tests/fixtures/parity/constructs.rb
|
|
152
|
+
- ext/rfmt/tests/fixtures/parity/embdoc.rb
|
|
153
|
+
- ext/rfmt/tests/fixtures/parity/heredoc_assign.rb
|
|
154
|
+
- ext/rfmt/tests/fixtures/parity/heredoc_call_args.rb
|
|
155
|
+
- ext/rfmt/tests/fixtures/parity/metadata_classes.rb
|
|
156
|
+
- ext/rfmt/tests/fixtures/parity/metadata_conditionals.rb
|
|
157
|
+
- ext/rfmt/tests/fixtures/parity/metadata_defs.rb
|
|
158
|
+
- ext/rfmt/tests/fixtures/parity/multibyte.rb
|
|
159
|
+
- ext/rfmt/tests/fixtures/parity/numeric.rb
|
|
160
|
+
- ext/rfmt/tests/fixtures/parity/plain.rb
|
|
161
|
+
- ext/rfmt/tests/native_parity.rs
|
|
162
|
+
- ext/rfmt/tests/ruby_prism_smoke.rs
|
|
162
163
|
- lib/rfmt.rb
|
|
163
164
|
- lib/rfmt/cache.rb
|
|
164
165
|
- lib/rfmt/cli.rb
|
|
@@ -171,8 +172,6 @@ files:
|
|
|
171
172
|
- lib/rfmt/lsp/uri.rb
|
|
172
173
|
- lib/rfmt/lsp/workspace.rb
|
|
173
174
|
- lib/rfmt/native_extension_loader.rb
|
|
174
|
-
- lib/rfmt/prism_bridge.rb
|
|
175
|
-
- lib/rfmt/prism_node_extractor.rb
|
|
176
175
|
- lib/rfmt/version.rb
|
|
177
176
|
- lib/ruby_lsp/rfmt/addon.rb
|
|
178
177
|
- lib/ruby_lsp/rfmt/formatter_runner.rb
|
|
@@ -192,7 +191,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
192
191
|
requirements:
|
|
193
192
|
- - ">="
|
|
194
193
|
- !ruby/object:Gem::Version
|
|
195
|
-
version: 3.
|
|
194
|
+
version: 3.3.0
|
|
196
195
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
197
196
|
requirements:
|
|
198
197
|
- - ">="
|