rfmt 0.2.3 → 0.3.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 +4 -4
- data/CHANGELOG.md +25 -0
- data/Cargo.lock +1 -1
- data/README.md +81 -156
- data/ext/rfmt/Cargo.toml +1 -1
- data/ext/rfmt/src/ast/mod.rs +2 -0
- data/ext/rfmt/src/config/mod.rs +2 -2
- data/ext/rfmt/src/emitter/mod.rs +118 -0
- data/ext/rfmt/src/lib.rs +3 -0
- data/ext/rfmt/src/logging/logger.rs +5 -1
- data/lib/rfmt/prism_bridge.rb +2 -0
- data/lib/rfmt/rfmt.so +0 -0
- data/lib/rfmt/version.rb +1 -1
- data/lib/rfmt.rb +4 -4
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1344ebd75598608c54dc975f8afecc17be05152039542ac1c1e80007086530fc
|
|
4
|
+
data.tar.gz: b5a29adb45e5163fdfdbbea2526d4ac72bdbca410ee86e888ecb224f3b98c922
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 71db0a1b94b8a2288a5fae7e3a09ec41fac6e834abd9760fd45b4ac7dfef71baac7ada32582a6018b74fa22a6d0711f529b8372c491179dfbe66cc9c56c287aa
|
|
7
|
+
data.tar.gz: c7dc265d4ba68a15c653715bd26d8c087be6d361a7bb3170bd59057c0b9a65cfbf2093c02ff8528dcffd8a2b4ca445faef2fdc4fa4c5035016360d64ad297b9e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2025-11-25
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- **BREAKING**: Default configuration file name changed from `rfmt.yml` to `.rfmt.yml`
|
|
7
|
+
- `rfmt init` now creates `.rfmt.yml` instead of `rfmt.yml`
|
|
8
|
+
- Configuration file search order updated: `.rfmt.yml` > `.rfmt.yaml` > `rfmt.yml` > `rfmt.yaml`
|
|
9
|
+
- This follows Ruby community conventions for hidden configuration files
|
|
10
|
+
- Backward compatibility maintained: `rfmt.yml` is still supported
|
|
11
|
+
- Updated README.md to use `.rfmt.yml` in all examples and documentation
|
|
12
|
+
- Updated benchmark data with latest accurate measurements (3,241 lines, more realistic performance ratios)
|
|
13
|
+
- Removed exaggerated performance claims from README and documentation
|
|
14
|
+
- Simplified feature descriptions in README (removed Error Handling and Logging from Features section)
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
- Updated user guides (English and Japanese) to reflect `.rfmt.yml` as default
|
|
18
|
+
- Updated version information in documentation to 0.2.4 → 0.3.0
|
|
19
|
+
- Updated benchmark documentation with accurate data from latest measurements
|
|
20
|
+
- Added configuration verification examples in examples/ directory
|
|
21
|
+
- Improved Ruby API examples with clearer input/output distinction
|
|
22
|
+
|
|
23
|
+
## [0.2.4] - 2025-11-25
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- Fixed if-else expression formatting
|
|
27
|
+
|
|
3
28
|
## [0.2.3] - 2025-01-25
|
|
4
29
|
|
|
5
30
|
### Added
|
data/Cargo.lock
CHANGED
data/README.md
CHANGED
|
@@ -22,21 +22,16 @@ A Ruby code formatter written in Rust
|
|
|
22
22
|
|
|
23
23
|
**rfmt** is a Ruby code formatter that enforces consistent style across your codebase. Key characteristics:
|
|
24
24
|
|
|
25
|
-
- **Rust implementation**: Provides faster execution than Ruby-based tools
|
|
26
25
|
- **Opinionated**: Minimal configuration with consistent output
|
|
27
26
|
- **Idempotent**: Running multiple times produces identical results
|
|
28
27
|
- **Comment preservation**: Maintains existing comment placement
|
|
29
|
-
- **
|
|
28
|
+
- **Rust implementation**: Core formatter implemented in Rust
|
|
30
29
|
|
|
31
30
|
## Features
|
|
32
31
|
|
|
33
32
|
### Performance
|
|
34
33
|
|
|
35
|
-
Built with Rust for improved execution speed
|
|
36
|
-
|
|
37
|
-
- Benchmark shows 7-58x faster than RuboCop depending on project size (see Performance Benchmarks section)
|
|
38
|
-
- Processes 168 files/second in tested Rails project
|
|
39
|
-
- Supports parallel processing
|
|
34
|
+
Built with Rust for improved execution speed. See Performance Benchmarks section for details.
|
|
40
35
|
|
|
41
36
|
### Consistent Style
|
|
42
37
|
|
|
@@ -47,54 +42,26 @@ Enforces code style rules:
|
|
|
47
42
|
- Quote style standardization
|
|
48
43
|
- Method definition formatting
|
|
49
44
|
|
|
50
|
-
### Error Handling
|
|
51
|
-
|
|
52
|
-
Provides structured error messages:
|
|
53
|
-
|
|
54
|
-
- Error codes (E001-E999) for categorization
|
|
55
|
-
- Code snippets showing error locations
|
|
56
|
-
- Help URLs linking to documentation
|
|
57
|
-
- Recovery strategies for handling errors
|
|
58
|
-
|
|
59
|
-
### Logging
|
|
60
|
-
|
|
61
|
-
Built-in logging system:
|
|
62
|
-
|
|
63
|
-
- 5 log levels (ERROR, WARN, INFO, DEBUG, TRACE)
|
|
64
|
-
- Structured output format
|
|
65
|
-
- Performance metrics and timing data
|
|
66
|
-
- Debug context information
|
|
67
|
-
|
|
68
45
|
## Performance Benchmarks
|
|
69
46
|
|
|
70
|
-
Performance comparison with RuboCop on a Rails project (111 files, 3,
|
|
47
|
+
Performance comparison with RuboCop on a Rails project (111 files, 3,241 lines):
|
|
71
48
|
|
|
72
|
-
|
|
49
|
+
| Test Type | Files | rfmt | RuboCop | Ratio |
|
|
50
|
+
|-----------|-------|------|---------|-------|
|
|
51
|
+
| Single File | 1 | 191ms | 1.38s | 7.2x |
|
|
52
|
+
| Directory | 14 | 176ms | 1.68s | 9.6x |
|
|
53
|
+
| Full Project (check) | 111 | 172ms | 4.36s | 25.4x |
|
|
73
54
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
| **Directory (14 files)** | 168ms | 1.67s | **10.0x faster** |
|
|
78
|
-
| **Full Project (111 files)** | 173ms | 10.09s | **58.5x faster** |
|
|
79
|
-
| **Check Mode (CI/CD)** | 172ms | 1.55s | **9.0x faster** |
|
|
55
|
+
**Observations:**
|
|
56
|
+
- rfmt execution time remains constant (172-191ms) regardless of file count
|
|
57
|
+
- Low variance across runs (standard deviation: 8-23ms)
|
|
80
58
|
|
|
81
|
-
|
|
59
|
+
**Test Environment:**
|
|
60
|
+
- CPU: Apple Silicon (arm64)
|
|
61
|
+
- Ruby: 3.4.5
|
|
62
|
+
- rfmt: 0.2.4, RuboCop: 1.81.7
|
|
82
63
|
|
|
83
|
-
|
|
84
|
-
- Scaling: 58x faster on full project (111 files)
|
|
85
|
-
- CI/CD: Check time reduced from 10.09s to 0.173s (98% reduction)
|
|
86
|
-
- Variance: Low standard deviation across runs
|
|
87
|
-
|
|
88
|
-
### Throughput Comparison
|
|
89
|
-
|
|
90
|
-
| Directory | rfmt | RuboCop | Difference |
|
|
91
|
-
|-----------|------|---------|------------|
|
|
92
|
-
| app/models (14 files) | 83.5 files/s | 8.4 files/s | **10x throughput** |
|
|
93
|
-
| test/ (30 files) | 168.1 files/s | 18.1 files/s | **9.3x throughput** |
|
|
94
|
-
|
|
95
|
-
*Benchmark environment: Apple Silicon (arm64), macOS Darwin 23.6.0, Ruby 3.4.5*
|
|
96
|
-
|
|
97
|
-
See [detailed benchmark report](docspriv/benchmark_report.md) for full data.
|
|
64
|
+
See [detailed benchmark report](docs/benchmark.md) for complete data.
|
|
98
65
|
|
|
99
66
|
## Installation
|
|
100
67
|
|
|
@@ -132,6 +99,48 @@ bundle exec rake compile
|
|
|
132
99
|
|
|
133
100
|
## Usage
|
|
134
101
|
|
|
102
|
+
### Initialize Configuration
|
|
103
|
+
|
|
104
|
+
First, create a configuration file with default settings:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
rfmt init
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This creates a `.rfmt.yml` file with default settings:
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
version: "1.0"
|
|
114
|
+
|
|
115
|
+
formatting:
|
|
116
|
+
line_length: 100 # Maximum line length (40-500)
|
|
117
|
+
indent_width: 2 # Spaces/tabs per indent (1-8)
|
|
118
|
+
indent_style: "spaces" # "spaces" or "tabs"
|
|
119
|
+
quote_style: "double" # "double", "single", or "consistent"
|
|
120
|
+
|
|
121
|
+
include:
|
|
122
|
+
- "**/*.rb"
|
|
123
|
+
- "**/*.rake"
|
|
124
|
+
- "**/Rakefile"
|
|
125
|
+
- "**/Gemfile"
|
|
126
|
+
|
|
127
|
+
exclude:
|
|
128
|
+
- "vendor/**/*"
|
|
129
|
+
- "tmp/**/*"
|
|
130
|
+
- "node_modules/**/*"
|
|
131
|
+
- "db/schema.rb"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Options:**
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Specify custom path
|
|
138
|
+
rfmt init --path config/.rfmt.yml
|
|
139
|
+
|
|
140
|
+
# Overwrite existing configuration
|
|
141
|
+
rfmt init --force
|
|
142
|
+
```
|
|
143
|
+
|
|
135
144
|
### Command Line
|
|
136
145
|
|
|
137
146
|
Format a single file:
|
|
@@ -152,16 +161,24 @@ Check if files need formatting (CI/CD):
|
|
|
152
161
|
rfmt check .
|
|
153
162
|
```
|
|
154
163
|
|
|
164
|
+
Show diff without modifying files:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
rfmt format lib/user.rb --diff
|
|
168
|
+
```
|
|
169
|
+
|
|
155
170
|
### Ruby API
|
|
156
171
|
|
|
172
|
+
**Input (unformatted code):**
|
|
173
|
+
|
|
157
174
|
```ruby
|
|
158
175
|
require 'rfmt'
|
|
159
176
|
|
|
160
177
|
source = <<~RUBY
|
|
161
178
|
class User
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
179
|
+
def initialize(name)
|
|
180
|
+
@name=name
|
|
181
|
+
end
|
|
165
182
|
end
|
|
166
183
|
RUBY
|
|
167
184
|
|
|
@@ -169,67 +186,25 @@ formatted = Rfmt.format(source)
|
|
|
169
186
|
puts formatted
|
|
170
187
|
```
|
|
171
188
|
|
|
172
|
-
**Output:**
|
|
189
|
+
**Output (formatted code):**
|
|
173
190
|
|
|
174
191
|
```ruby
|
|
175
192
|
class User
|
|
176
193
|
def initialize(name)
|
|
177
|
-
@name
|
|
194
|
+
@name=name
|
|
178
195
|
end
|
|
179
196
|
end
|
|
180
197
|
```
|
|
181
198
|
|
|
182
199
|
### Configuration
|
|
183
200
|
|
|
184
|
-
#### Initializing Configuration
|
|
185
|
-
|
|
186
|
-
Create a configuration file with default settings:
|
|
187
|
-
|
|
188
|
-
```bash
|
|
189
|
-
rfmt init
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
This creates a `rfmt.yml` file with default settings:
|
|
193
|
-
|
|
194
|
-
```yaml
|
|
195
|
-
version: "1.0"
|
|
196
|
-
|
|
197
|
-
formatting:
|
|
198
|
-
line_length: 100 # Maximum line length (40-500)
|
|
199
|
-
indent_width: 2 # Spaces/tabs per indent (1-8)
|
|
200
|
-
indent_style: "spaces" # "spaces" or "tabs"
|
|
201
|
-
quote_style: "double" # "double", "single", or "consistent"
|
|
202
|
-
|
|
203
|
-
include:
|
|
204
|
-
- "**/*.rb"
|
|
205
|
-
- "**/*.rake"
|
|
206
|
-
- "**/Rakefile"
|
|
207
|
-
- "**/Gemfile"
|
|
208
|
-
|
|
209
|
-
exclude:
|
|
210
|
-
- "vendor/**/*"
|
|
211
|
-
- "tmp/**/*"
|
|
212
|
-
- "node_modules/**/*"
|
|
213
|
-
- "db/schema.rb"
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
**Options:**
|
|
217
|
-
|
|
218
|
-
```bash
|
|
219
|
-
# Specify custom path
|
|
220
|
-
rfmt init --path config/rfmt.yml
|
|
221
|
-
|
|
222
|
-
# Overwrite existing configuration
|
|
223
|
-
rfmt init --force
|
|
224
|
-
```
|
|
225
|
-
|
|
226
201
|
#### Configuration File Discovery
|
|
227
202
|
|
|
228
203
|
rfmt automatically searches for configuration files in this order:
|
|
229
204
|
|
|
230
|
-
1. Current directory (
|
|
205
|
+
1. Current directory (`.rfmt.yml`, `.rfmt.yaml`, `rfmt.yml`, or `rfmt.yaml`)
|
|
231
206
|
2. Parent directories (up to root)
|
|
232
|
-
3. User home directory (
|
|
207
|
+
3. User home directory (`.rfmt.yml`, `.rfmt.yaml`, `rfmt.yml`, or `rfmt.yaml`)
|
|
233
208
|
4. Default settings (if no file found)
|
|
234
209
|
|
|
235
210
|
#### Ruby API for Configuration
|
|
@@ -238,11 +213,11 @@ rfmt automatically searches for configuration files in this order:
|
|
|
238
213
|
require 'rfmt'
|
|
239
214
|
|
|
240
215
|
# Generate configuration file
|
|
241
|
-
Rfmt::Config.init('rfmt.yml', force: false)
|
|
216
|
+
Rfmt::Config.init('.rfmt.yml', force: false)
|
|
242
217
|
|
|
243
218
|
# Find configuration file
|
|
244
219
|
config_path = Rfmt::Config.find
|
|
245
|
-
# => "/Users/username/project
|
|
220
|
+
# => "/Users/username/project/.rfmt.yml"
|
|
246
221
|
|
|
247
222
|
# Check if configuration exists
|
|
248
223
|
Rfmt::Config.exists?
|
|
@@ -253,59 +228,6 @@ config = Rfmt::Config.load
|
|
|
253
228
|
# => {"version"=>"1.0", "formatting"=>{"line_length"=>100, ...}, ...}
|
|
254
229
|
```
|
|
255
230
|
|
|
256
|
-
## Error Handling
|
|
257
|
-
|
|
258
|
-
rfmt provides structured error messages:
|
|
259
|
-
|
|
260
|
-
```
|
|
261
|
-
[Rfmt::ParseError] Parse error in app/models/user.rb:15:10
|
|
262
|
-
Expected closing 'end' for class definition
|
|
263
|
-
|
|
264
|
-
Code:
|
|
265
|
-
13 | class User < ApplicationRecord
|
|
266
|
-
14 | def initialize(name)
|
|
267
|
-
15 | @name = name
|
|
268
|
-
| ^
|
|
269
|
-
16 | # Missing 'end' for method
|
|
270
|
-
17 | end
|
|
271
|
-
|
|
272
|
-
Help: https://rfmt.dev/errors/E001
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### Error Codes
|
|
276
|
-
|
|
277
|
-
| Code | Type | Description |
|
|
278
|
-
|------|------|-------------|
|
|
279
|
-
| E001 | ParseError | Ruby syntax error |
|
|
280
|
-
| E002 | ConfigError | Invalid configuration |
|
|
281
|
-
| E003 | IoError | File read/write error |
|
|
282
|
-
| E004 | FormattingError | Formatting process error |
|
|
283
|
-
| E005 | RuleError | Rule application failed |
|
|
284
|
-
| E006 | UnsupportedFeature | Feature not yet supported |
|
|
285
|
-
| E007 | PrismError | Parser integration error |
|
|
286
|
-
| E008 | FormatError | General formatting error |
|
|
287
|
-
| E999 | InternalError | Internal bug (please report!) |
|
|
288
|
-
|
|
289
|
-
See the [Error Reference](docs/error_reference.md) for detailed information.
|
|
290
|
-
|
|
291
|
-
## Logging
|
|
292
|
-
|
|
293
|
-
rfmt includes a logging system:
|
|
294
|
-
|
|
295
|
-
```ruby
|
|
296
|
-
# Logs are automatically output during initialization
|
|
297
|
-
require 'rfmt'
|
|
298
|
-
# [INFO] rfmt - Initializing rfmt Rust extension
|
|
299
|
-
# [INFO] rfmt - rfmt Rust extension initialized successfully
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
Log levels:
|
|
303
|
-
- **ERROR**: Critical errors
|
|
304
|
-
- **WARN**: Warnings
|
|
305
|
-
- **INFO**: General information (default)
|
|
306
|
-
- **DEBUG**: Debug information
|
|
307
|
-
- **TRACE**: Detailed trace information
|
|
308
|
-
|
|
309
231
|
## Examples
|
|
310
232
|
|
|
311
233
|
### Before Formatting
|
|
@@ -335,7 +257,11 @@ end
|
|
|
335
257
|
|
|
336
258
|
## Documentation
|
|
337
259
|
|
|
338
|
-
Documentation is available in the [docs](docs/) directory
|
|
260
|
+
Documentation is available in the [docs](docs/) directory:
|
|
261
|
+
|
|
262
|
+
- [User Guide](docs/user_guide.md) - Comprehensive usage guide
|
|
263
|
+
- [Error Reference](docs/error_reference.md) - Error codes and troubleshooting
|
|
264
|
+
- [Contributing Guide](CONTRIBUTING.md) - How to contribute
|
|
339
265
|
|
|
340
266
|
## Contributing
|
|
341
267
|
|
|
@@ -348,12 +274,11 @@ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) f
|
|
|
348
274
|
| Feature | rfmt | RuboCop |
|
|
349
275
|
|---------|------|---------|
|
|
350
276
|
| **Primary Purpose** | Code formatting | Linting + formatting |
|
|
351
|
-
| **Speed** | 58x faster (tested benchmark) | Baseline |
|
|
352
277
|
| **Configuration** | Minimal | Extensive |
|
|
353
278
|
| **Code Quality Checks** | No | Yes |
|
|
354
279
|
| **Bug Detection** | No | Yes |
|
|
355
280
|
|
|
356
|
-
**Note**: rfmt focuses on formatting
|
|
281
|
+
**Note**: rfmt focuses on code formatting, while RuboCop provides additional code quality analysis. They can be used together.
|
|
357
282
|
|
|
358
283
|
## License
|
|
359
284
|
|
data/ext/rfmt/Cargo.toml
CHANGED
data/ext/rfmt/src/ast/mod.rs
CHANGED
|
@@ -40,6 +40,7 @@ pub enum NodeType {
|
|
|
40
40
|
// Expressions
|
|
41
41
|
CallNode,
|
|
42
42
|
IfNode,
|
|
43
|
+
ElseNode,
|
|
43
44
|
UnlessNode,
|
|
44
45
|
|
|
45
46
|
// Literals
|
|
@@ -83,6 +84,7 @@ impl NodeType {
|
|
|
83
84
|
"def_node" => Self::DefNode,
|
|
84
85
|
"call_node" => Self::CallNode,
|
|
85
86
|
"if_node" => Self::IfNode,
|
|
87
|
+
"else_node" => Self::ElseNode,
|
|
86
88
|
"unless_node" => Self::UnlessNode,
|
|
87
89
|
"string_node" => Self::StringNode,
|
|
88
90
|
"integer_node" => Self::IntegerNode,
|
data/ext/rfmt/src/config/mod.rs
CHANGED
|
@@ -110,7 +110,7 @@ impl Config {
|
|
|
110
110
|
for filename in &config_files {
|
|
111
111
|
let config_path = current_dir.join(filename);
|
|
112
112
|
if config_path.exists() {
|
|
113
|
-
log::
|
|
113
|
+
log::info!("Found config file: {:?}", config_path);
|
|
114
114
|
return Self::load_file(&config_path);
|
|
115
115
|
}
|
|
116
116
|
}
|
|
@@ -133,7 +133,7 @@ impl Config {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
log::
|
|
136
|
+
log::info!("No config file found, using defaults");
|
|
137
137
|
Ok(Config::default())
|
|
138
138
|
}
|
|
139
139
|
|
data/ext/rfmt/src/emitter/mod.rs
CHANGED
|
@@ -113,6 +113,8 @@ impl Emitter {
|
|
|
113
113
|
NodeType::ClassNode => self.emit_class(node, indent_level)?,
|
|
114
114
|
NodeType::ModuleNode => self.emit_module(node, indent_level)?,
|
|
115
115
|
NodeType::DefNode => self.emit_method(node, indent_level)?,
|
|
116
|
+
NodeType::IfNode => self.emit_if_unless(node, indent_level, false, "if")?,
|
|
117
|
+
NodeType::UnlessNode => self.emit_if_unless(node, indent_level, false, "unless")?,
|
|
116
118
|
_ => self.emit_generic(node, indent_level)?,
|
|
117
119
|
}
|
|
118
120
|
Ok(())
|
|
@@ -304,6 +306,122 @@ impl Emitter {
|
|
|
304
306
|
Ok(())
|
|
305
307
|
}
|
|
306
308
|
|
|
309
|
+
/// Emit if/unless/elsif/else node
|
|
310
|
+
/// is_elsif: true if this is an elsif clause (don't emit 'end')
|
|
311
|
+
/// keyword: "if" or "unless"
|
|
312
|
+
fn emit_if_unless(&mut self, node: &Node, indent_level: usize, is_elsif: bool, keyword: &str) -> Result<()> {
|
|
313
|
+
// Check if this is a postfix if (modifier form)
|
|
314
|
+
// In postfix if, the statements come before the if keyword in source
|
|
315
|
+
let is_postfix = if let (Some(predicate), Some(statements)) =
|
|
316
|
+
(node.children.first(), node.children.get(1)) {
|
|
317
|
+
statements.location.start_offset < predicate.location.start_offset
|
|
318
|
+
} else {
|
|
319
|
+
false
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Postfix if/unless: "statement if/unless condition"
|
|
323
|
+
if is_postfix && !is_elsif {
|
|
324
|
+
self.emit_comments_before(node.location.start_line, indent_level)?;
|
|
325
|
+
self.emit_indent(indent_level)?;
|
|
326
|
+
|
|
327
|
+
// Emit statement
|
|
328
|
+
if let Some(statements) = node.children.get(1) {
|
|
329
|
+
if matches!(statements.node_type, NodeType::StatementsNode) {
|
|
330
|
+
// Extract the statement text (without extra indentation)
|
|
331
|
+
if !self.source.is_empty() {
|
|
332
|
+
let start = statements.location.start_offset;
|
|
333
|
+
let end = statements.location.end_offset;
|
|
334
|
+
if let Some(text) = self.source.get(start..end) {
|
|
335
|
+
write!(self.buffer, "{}", text.trim())?;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
write!(self.buffer, " {} ", keyword)?;
|
|
342
|
+
|
|
343
|
+
// Emit condition
|
|
344
|
+
if let Some(predicate) = node.children.first() {
|
|
345
|
+
if !self.source.is_empty() {
|
|
346
|
+
let start = predicate.location.start_offset;
|
|
347
|
+
let end = predicate.location.end_offset;
|
|
348
|
+
if let Some(text) = self.source.get(start..end) {
|
|
349
|
+
write!(self.buffer, "{}", text)?;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return Ok(());
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Normal if/unless/elsif
|
|
358
|
+
if !is_elsif {
|
|
359
|
+
self.emit_comments_before(node.location.start_line, indent_level)?;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Emit 'if'/'unless' or 'elsif' keyword
|
|
363
|
+
self.emit_indent(indent_level)?;
|
|
364
|
+
if is_elsif {
|
|
365
|
+
write!(self.buffer, "elsif ")?;
|
|
366
|
+
} else {
|
|
367
|
+
write!(self.buffer, "{} ", keyword)?;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Emit predicate (condition) - first child
|
|
371
|
+
if let Some(predicate) = node.children.first() {
|
|
372
|
+
// Extract predicate from source
|
|
373
|
+
if !self.source.is_empty() {
|
|
374
|
+
let start = predicate.location.start_offset;
|
|
375
|
+
let end = predicate.location.end_offset;
|
|
376
|
+
if let Some(text) = self.source.get(start..end) {
|
|
377
|
+
write!(self.buffer, "{}", text)?;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
self.buffer.push('\n');
|
|
383
|
+
|
|
384
|
+
// Emit then clause (second child is StatementsNode)
|
|
385
|
+
if let Some(statements) = node.children.get(1) {
|
|
386
|
+
if matches!(statements.node_type, NodeType::StatementsNode) {
|
|
387
|
+
self.emit_statements(statements, indent_level + 1)?;
|
|
388
|
+
self.buffer.push('\n');
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Check for elsif/else (third child)
|
|
393
|
+
if let Some(consequent) = node.children.get(2) {
|
|
394
|
+
match &consequent.node_type {
|
|
395
|
+
NodeType::IfNode => {
|
|
396
|
+
// This is an elsif clause (only valid for if, not unless)
|
|
397
|
+
self.emit_if_unless(consequent, indent_level, true, "if")?;
|
|
398
|
+
}
|
|
399
|
+
NodeType::ElseNode => {
|
|
400
|
+
// This is an else clause
|
|
401
|
+
self.emit_indent(indent_level)?;
|
|
402
|
+
write!(self.buffer, "else\n")?;
|
|
403
|
+
|
|
404
|
+
// Emit else body (first child of ElseNode)
|
|
405
|
+
if let Some(else_statements) = consequent.children.first() {
|
|
406
|
+
if matches!(else_statements.node_type, NodeType::StatementsNode) {
|
|
407
|
+
self.emit_statements(else_statements, indent_level + 1)?;
|
|
408
|
+
self.buffer.push('\n');
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
_ => {}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Only emit 'end' for the outermost if (not for elsif)
|
|
417
|
+
if !is_elsif {
|
|
418
|
+
self.emit_indent(indent_level)?;
|
|
419
|
+
write!(self.buffer, "end")?;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
Ok(())
|
|
423
|
+
}
|
|
424
|
+
|
|
307
425
|
/// Emit generic node by extracting from source
|
|
308
426
|
fn emit_generic(&mut self, node: &Node, indent_level: usize) -> Result<()> {
|
|
309
427
|
// Emit any comments before this node
|
data/ext/rfmt/src/lib.rs
CHANGED
|
@@ -14,6 +14,7 @@ use magnus::{define_module, function, prelude::*, Error, Ruby};
|
|
|
14
14
|
use parser::{PrismAdapter, RubyParser};
|
|
15
15
|
|
|
16
16
|
fn format_ruby_code(ruby: &Ruby, source: String, json: String) -> Result<String, Error> {
|
|
17
|
+
log::info!("format_ruby_code called");
|
|
17
18
|
let policy = SecurityPolicy::default();
|
|
18
19
|
|
|
19
20
|
policy
|
|
@@ -26,7 +27,9 @@ fn format_ruby_code(ruby: &Ruby, source: String, json: String) -> Result<String,
|
|
|
26
27
|
let ast = parser.parse(&json).map_err(|e| e.to_magnus_error(ruby))?;
|
|
27
28
|
|
|
28
29
|
// Load configuration from file or use defaults
|
|
30
|
+
log::info!("Attempting to discover config file...");
|
|
29
31
|
let config = Config::discover().map_err(|e| e.to_magnus_error(ruby))?;
|
|
32
|
+
log::info!("Config loaded successfully, line_length: {}", config.formatting.line_length);
|
|
30
33
|
let mut emitter = Emitter::with_source(config, source);
|
|
31
34
|
|
|
32
35
|
let formatted = emitter.emit(&ast).map_err(|e| e.to_magnus_error(ruby))?;
|
|
@@ -22,7 +22,11 @@ impl RfmtLogger {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
pub fn init() {
|
|
25
|
-
let
|
|
25
|
+
let level = std::env::var("RFMT_LOG")
|
|
26
|
+
.ok()
|
|
27
|
+
.and_then(|s| s.parse().ok())
|
|
28
|
+
.unwrap_or(LevelFilter::Info);
|
|
29
|
+
let logger = Self::new(level);
|
|
26
30
|
log::set_boxed_logger(Box::new(logger)).expect("Failed to initialize logger");
|
|
27
31
|
log::set_max_level(LevelFilter::Trace);
|
|
28
32
|
}
|
data/lib/rfmt/prism_bridge.rb
CHANGED
data/lib/rfmt/rfmt.so
CHANGED
|
Binary file
|
data/lib/rfmt/version.rb
CHANGED
data/lib/rfmt.rb
CHANGED
|
@@ -95,10 +95,10 @@ module Rfmt
|
|
|
95
95
|
YAML
|
|
96
96
|
|
|
97
97
|
# Generate a default configuration file
|
|
98
|
-
# @param path [String] Path where to create the config file (default: rfmt.yml)
|
|
98
|
+
# @param path [String] Path where to create the config file (default: .rfmt.yml)
|
|
99
99
|
# @param force [Boolean] Overwrite existing file if true
|
|
100
100
|
# @return [Boolean] true if file was created, false if already exists
|
|
101
|
-
def self.init(path = 'rfmt.yml', force: false)
|
|
101
|
+
def self.init(path = '.rfmt.yml', force: false)
|
|
102
102
|
if File.exist?(path) && !force
|
|
103
103
|
warn "Configuration file already exists: #{path}"
|
|
104
104
|
warn 'Use force: true to overwrite'
|
|
@@ -116,7 +116,7 @@ module Rfmt
|
|
|
116
116
|
current_dir = Dir.pwd
|
|
117
117
|
|
|
118
118
|
loop do
|
|
119
|
-
['rfmt.yml', 'rfmt.yaml', '
|
|
119
|
+
['.rfmt.yml', '.rfmt.yaml', 'rfmt.yml', 'rfmt.yaml'].each do |filename|
|
|
120
120
|
config_path = File.join(current_dir, filename)
|
|
121
121
|
return config_path if File.exist?(config_path)
|
|
122
122
|
end
|
|
@@ -134,7 +134,7 @@ module Rfmt
|
|
|
134
134
|
nil
|
|
135
135
|
end
|
|
136
136
|
if home_dir
|
|
137
|
-
['rfmt.yml', 'rfmt.yaml', '
|
|
137
|
+
['.rfmt.yml', '.rfmt.yaml', 'rfmt.yml', 'rfmt.yaml'].each do |filename|
|
|
138
138
|
config_path = File.join(home_dir, filename)
|
|
139
139
|
return config_path if File.exist?(config_path)
|
|
140
140
|
end
|
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: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- fujitani sora
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-11-
|
|
11
|
+
date: 2025-11-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|