rfmt 1.0.0 → 1.2.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 +37 -0
- data/Cargo.lock +1 -1
- data/README.md +30 -2
- data/ext/rfmt/Cargo.toml +1 -1
- data/ext/rfmt/src/ast/mod.rs +188 -2
- data/ext/rfmt/src/emitter/mod.rs +367 -14
- data/lib/rfmt/prism_bridge.rb +121 -0
- data/lib/rfmt/prism_node_extractor.rb +14 -5
- data/lib/rfmt/rfmt.bundle +0 -0
- data/lib/rfmt/version.rb +1 -1
- data/lib/rfmt.rb +1 -1
- metadata +4 -9
- data/ext/rfmt/spec/config_spec.rb +0 -39
- data/ext/rfmt/spec/spec_helper.rb +0 -16
- data/lib/rfmt/rfmt.so +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 21876b488a79df7b3a136bfc563ea91312bc86569205b4349b5ccc0371338071
|
|
4
|
+
data.tar.gz: 7896e3c94fe83d0de8a3a4bebd9d10e0f277f92bfd069384ff05c4cb524d57b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e50633a81f18a263109deac74be48dbb067f39a1139d166d2e6d5d02c271f7dba232ad5512d8a63d7a571c23dc246ee8d41c82be2e477493c89ed92b7949dd19
|
|
7
|
+
data.tar.gz: eae8574579a5b30cef07472ef90ce854162282c84bb379c507796ee2984f5f6ee2004ec82a9e8100c04b5e047b0e8a4894564ada404f789e2180d7302f5842fc
|
data/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,47 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.2.0] - 2026-01-04
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Loop node types support (`for`, `while`, `until`)
|
|
7
|
+
- Case/When statement support
|
|
8
|
+
- Ensure and Lambda node support
|
|
9
|
+
- Begin/End block handling for explicit `begin...end` blocks
|
|
10
|
+
- High-priority node types support
|
|
11
|
+
- Medium-priority node types support
|
|
12
|
+
- Prism supported node viewer task
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Consolidated and simplified test suite
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Exclude `.DS_Store` from Git tracking (@topi0247)
|
|
19
|
+
- Repository URL changed from `fujitanisora` to `fs0414` (@topi0247)
|
|
20
|
+
- End line space handling
|
|
21
|
+
- Comment location fix
|
|
22
|
+
- End expression indent fix
|
|
23
|
+
- Begin formatting fix
|
|
24
|
+
|
|
25
|
+
## [1.1.0] - 2025-12-12
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- Editor integration (Ruby LSP support)
|
|
29
|
+
- Required/Optional keyword parameter node type support
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- Migration file superclass corruption (ActiveRecord::Migration[8.1] etc.)
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- Removed unused scripts and test files (reduced Ruby code by ~38%)
|
|
36
|
+
|
|
3
37
|
## [1.0.0] - 2025-12-11
|
|
4
38
|
|
|
5
39
|
### Breaking Changes
|
|
6
40
|
- First stable release (v1.0.0)
|
|
7
41
|
|
|
42
|
+
### Added
|
|
43
|
+
- Neovim integration: format-on-save support with autocmd configuration
|
|
44
|
+
|
|
8
45
|
### Changed
|
|
9
46
|
- Set JSON as default output format
|
|
10
47
|
- Updated Japanese documentation
|
data/Cargo.lock
CHANGED
data/README.md
CHANGED
|
@@ -10,6 +10,7 @@ A Ruby code formatter written in Rust
|
|
|
10
10
|
[Installation](#installation) •
|
|
11
11
|
[Usage](#usage) •
|
|
12
12
|
[Features](#features) •
|
|
13
|
+
[Editor Integration](#editor-integration) •
|
|
13
14
|
[Documentation](#documentation) •
|
|
14
15
|
[Contributing](#contributing)
|
|
15
16
|
|
|
@@ -101,7 +102,7 @@ bundle install
|
|
|
101
102
|
### From Source
|
|
102
103
|
|
|
103
104
|
```bash
|
|
104
|
-
git clone https://github.com/
|
|
105
|
+
git clone https://github.com/fs0414/rfmt.git
|
|
105
106
|
cd rfmt
|
|
106
107
|
bundle install
|
|
107
108
|
bundle exec rake compile
|
|
@@ -273,6 +274,33 @@ class User < ApplicationRecord
|
|
|
273
274
|
end
|
|
274
275
|
```
|
|
275
276
|
|
|
277
|
+
## Editor Integration
|
|
278
|
+
|
|
279
|
+
### Neovim
|
|
280
|
+
|
|
281
|
+
Format Ruby files on save using autocmd:
|
|
282
|
+
|
|
283
|
+
```lua
|
|
284
|
+
-- ~/.config/nvim/init.lua
|
|
285
|
+
|
|
286
|
+
vim.api.nvim_create_autocmd("BufWritePre", {
|
|
287
|
+
pattern = { "*.rb", "*.rake", "Gemfile", "Rakefile" },
|
|
288
|
+
callback = function()
|
|
289
|
+
local filepath = vim.fn.expand("%:p")
|
|
290
|
+
local result = vim.fn.system({ "rfmt", filepath })
|
|
291
|
+
if vim.v.shell_error == 0 then
|
|
292
|
+
vim.cmd("edit!")
|
|
293
|
+
end
|
|
294
|
+
end,
|
|
295
|
+
})
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Coming Soon
|
|
299
|
+
|
|
300
|
+
- **VS Code** - Extension in development
|
|
301
|
+
- **RubyMine** - Plugin in development
|
|
302
|
+
- **Zed** - Extension in development
|
|
303
|
+
|
|
276
304
|
## Development
|
|
277
305
|
|
|
278
306
|
### Setup
|
|
@@ -351,7 +379,7 @@ Everyone interacting in the rfmt project's codebases, issue trackers, chat rooms
|
|
|
351
379
|
## Support
|
|
352
380
|
|
|
353
381
|
- 📖 [Documentation](docs/)
|
|
354
|
-
- 🐛 [Issues](https://github.com/
|
|
382
|
+
- 🐛 [Issues](https://github.com/fs0414/rfmt/issues)
|
|
355
383
|
- 📧 Email: fujitanisora0414@gmail.com
|
|
356
384
|
|
|
357
385
|
## Acknowledgments
|
data/ext/rfmt/Cargo.toml
CHANGED
data/ext/rfmt/src/ast/mod.rs
CHANGED
|
@@ -43,6 +43,10 @@ pub enum NodeType {
|
|
|
43
43
|
ElseNode,
|
|
44
44
|
UnlessNode,
|
|
45
45
|
|
|
46
|
+
// Exception handling
|
|
47
|
+
BeginNode,
|
|
48
|
+
RescueNode,
|
|
49
|
+
|
|
46
50
|
// Literals
|
|
47
51
|
StringNode,
|
|
48
52
|
IntegerNode,
|
|
@@ -52,10 +56,115 @@ pub enum NodeType {
|
|
|
52
56
|
TrueNode,
|
|
53
57
|
FalseNode,
|
|
54
58
|
NilNode,
|
|
59
|
+
SymbolNode,
|
|
55
60
|
|
|
56
61
|
// Blocks
|
|
57
62
|
BlockNode,
|
|
58
63
|
|
|
64
|
+
// Case/When
|
|
65
|
+
CaseNode,
|
|
66
|
+
WhenNode,
|
|
67
|
+
|
|
68
|
+
// HashNode
|
|
69
|
+
AssocNode,
|
|
70
|
+
KeywordHashNode,
|
|
71
|
+
|
|
72
|
+
// Variables
|
|
73
|
+
LocalVariableReadNode,
|
|
74
|
+
LocalVariableWriteNode,
|
|
75
|
+
InstanceVariableReadNode,
|
|
76
|
+
InstanceVariableWriteNode,
|
|
77
|
+
|
|
78
|
+
// Lambda
|
|
79
|
+
LambdaNode,
|
|
80
|
+
|
|
81
|
+
// Control flow
|
|
82
|
+
ReturnNode,
|
|
83
|
+
EnsureNode,
|
|
84
|
+
|
|
85
|
+
// Strings
|
|
86
|
+
InterpolatedStringNode,
|
|
87
|
+
EmbeddedStatementsNode,
|
|
88
|
+
|
|
89
|
+
// Logical
|
|
90
|
+
OrNode,
|
|
91
|
+
AndNode,
|
|
92
|
+
NotNode,
|
|
93
|
+
|
|
94
|
+
// Loop constructs
|
|
95
|
+
WhileNode,
|
|
96
|
+
UntilNode,
|
|
97
|
+
ForNode,
|
|
98
|
+
|
|
99
|
+
// Control flow
|
|
100
|
+
BreakNode,
|
|
101
|
+
NextNode,
|
|
102
|
+
RedoNode,
|
|
103
|
+
RetryNode,
|
|
104
|
+
YieldNode,
|
|
105
|
+
SuperNode,
|
|
106
|
+
ForwardingSuperNode,
|
|
107
|
+
RescueModifierNode,
|
|
108
|
+
|
|
109
|
+
// Ranges
|
|
110
|
+
RangeNode,
|
|
111
|
+
|
|
112
|
+
// Other literals
|
|
113
|
+
RegularExpressionNode,
|
|
114
|
+
SplatNode,
|
|
115
|
+
InterpolatedRegularExpressionNode,
|
|
116
|
+
InterpolatedSymbolNode,
|
|
117
|
+
XStringNode,
|
|
118
|
+
InterpolatedXStringNode,
|
|
119
|
+
|
|
120
|
+
// Class variables
|
|
121
|
+
ClassVariableReadNode,
|
|
122
|
+
ClassVariableWriteNode,
|
|
123
|
+
ClassVariableOrWriteNode,
|
|
124
|
+
ClassVariableAndWriteNode,
|
|
125
|
+
ClassVariableOperatorWriteNode,
|
|
126
|
+
|
|
127
|
+
// Global variables
|
|
128
|
+
GlobalVariableReadNode,
|
|
129
|
+
GlobalVariableWriteNode,
|
|
130
|
+
GlobalVariableOrWriteNode,
|
|
131
|
+
GlobalVariableAndWriteNode,
|
|
132
|
+
GlobalVariableOperatorWriteNode,
|
|
133
|
+
|
|
134
|
+
// Compound assignment
|
|
135
|
+
LocalVariableOrWriteNode,
|
|
136
|
+
LocalVariableAndWriteNode,
|
|
137
|
+
LocalVariableOperatorWriteNode,
|
|
138
|
+
InstanceVariableOrWriteNode,
|
|
139
|
+
InstanceVariableAndWriteNode,
|
|
140
|
+
InstanceVariableOperatorWriteNode,
|
|
141
|
+
ConstantOrWriteNode,
|
|
142
|
+
ConstantAndWriteNode,
|
|
143
|
+
ConstantOperatorWriteNode,
|
|
144
|
+
ConstantPathOrWriteNode,
|
|
145
|
+
ConstantPathAndWriteNode,
|
|
146
|
+
ConstantPathOperatorWriteNode,
|
|
147
|
+
ConstantPathWriteNode,
|
|
148
|
+
|
|
149
|
+
// Pattern matching
|
|
150
|
+
CaseMatchNode,
|
|
151
|
+
InNode,
|
|
152
|
+
MatchPredicateNode,
|
|
153
|
+
MatchRequiredNode,
|
|
154
|
+
|
|
155
|
+
// Other common nodes
|
|
156
|
+
SelfNode,
|
|
157
|
+
ParenthesesNode,
|
|
158
|
+
DefinedNode,
|
|
159
|
+
SingletonClassNode,
|
|
160
|
+
AliasMethodNode,
|
|
161
|
+
AliasGlobalVariableNode,
|
|
162
|
+
UndefNode,
|
|
163
|
+
AssocSplatNode,
|
|
164
|
+
BlockArgumentNode,
|
|
165
|
+
MultiWriteNode,
|
|
166
|
+
MultiTargetNode,
|
|
167
|
+
|
|
59
168
|
// Constants (structural nodes, part of definitions)
|
|
60
169
|
ConstantReadNode,
|
|
61
170
|
ConstantWriteNode,
|
|
@@ -66,6 +175,8 @@ pub enum NodeType {
|
|
|
66
175
|
OptionalParameterNode,
|
|
67
176
|
RestParameterNode,
|
|
68
177
|
KeywordParameterNode,
|
|
178
|
+
RequiredKeywordParameterNode,
|
|
179
|
+
OptionalKeywordParameterNode,
|
|
69
180
|
KeywordRestParameterNode,
|
|
70
181
|
BlockParameterNode,
|
|
71
182
|
|
|
@@ -73,8 +184,6 @@ pub enum NodeType {
|
|
|
73
184
|
}
|
|
74
185
|
|
|
75
186
|
impl NodeType {
|
|
76
|
-
/// Parse node type from Prism type string
|
|
77
|
-
/// Returns Unknown variant for unsupported node types
|
|
78
187
|
pub fn from_str(s: &str) -> Self {
|
|
79
188
|
match s {
|
|
80
189
|
"program_node" => Self::ProgramNode,
|
|
@@ -86,6 +195,8 @@ impl NodeType {
|
|
|
86
195
|
"if_node" => Self::IfNode,
|
|
87
196
|
"else_node" => Self::ElseNode,
|
|
88
197
|
"unless_node" => Self::UnlessNode,
|
|
198
|
+
"begin_node" => Self::BeginNode,
|
|
199
|
+
"rescue_node" => Self::RescueNode,
|
|
89
200
|
"string_node" => Self::StringNode,
|
|
90
201
|
"integer_node" => Self::IntegerNode,
|
|
91
202
|
"float_node" => Self::FloatNode,
|
|
@@ -102,8 +213,83 @@ impl NodeType {
|
|
|
102
213
|
"optional_parameter_node" => Self::OptionalParameterNode,
|
|
103
214
|
"rest_parameter_node" => Self::RestParameterNode,
|
|
104
215
|
"keyword_parameter_node" => Self::KeywordParameterNode,
|
|
216
|
+
"required_keyword_parameter_node" => Self::RequiredKeywordParameterNode,
|
|
217
|
+
"optional_keyword_parameter_node" => Self::OptionalKeywordParameterNode,
|
|
105
218
|
"keyword_rest_parameter_node" => Self::KeywordRestParameterNode,
|
|
106
219
|
"block_parameter_node" => Self::BlockParameterNode,
|
|
220
|
+
"symbol_node" => Self::SymbolNode,
|
|
221
|
+
"case_node" => Self::CaseNode,
|
|
222
|
+
"when_node" => Self::WhenNode,
|
|
223
|
+
"assoc_node" => Self::AssocNode,
|
|
224
|
+
"keyword_hash_node" => Self::KeywordHashNode,
|
|
225
|
+
"local_variable_read_node" => Self::LocalVariableReadNode,
|
|
226
|
+
"local_variable_write_node" => Self::LocalVariableWriteNode,
|
|
227
|
+
"instance_variable_read_node" => Self::InstanceVariableReadNode,
|
|
228
|
+
"instance_variable_write_node" => Self::InstanceVariableWriteNode,
|
|
229
|
+
"lambda_node" => Self::LambdaNode,
|
|
230
|
+
"return_node" => Self::ReturnNode,
|
|
231
|
+
"ensure_node" => Self::EnsureNode,
|
|
232
|
+
"interpolated_string_node" => Self::InterpolatedStringNode,
|
|
233
|
+
"embedded_statements_node" => Self::EmbeddedStatementsNode,
|
|
234
|
+
"or_node" => Self::OrNode,
|
|
235
|
+
"and_node" => Self::AndNode,
|
|
236
|
+
"not_node" => Self::NotNode,
|
|
237
|
+
"while_node" => Self::WhileNode,
|
|
238
|
+
"until_node" => Self::UntilNode,
|
|
239
|
+
"for_node" => Self::ForNode,
|
|
240
|
+
"break_node" => Self::BreakNode,
|
|
241
|
+
"next_node" => Self::NextNode,
|
|
242
|
+
"redo_node" => Self::RedoNode,
|
|
243
|
+
"retry_node" => Self::RetryNode,
|
|
244
|
+
"yield_node" => Self::YieldNode,
|
|
245
|
+
"super_node" => Self::SuperNode,
|
|
246
|
+
"forwarding_super_node" => Self::ForwardingSuperNode,
|
|
247
|
+
"rescue_modifier_node" => Self::RescueModifierNode,
|
|
248
|
+
"range_node" => Self::RangeNode,
|
|
249
|
+
"regular_expression_node" => Self::RegularExpressionNode,
|
|
250
|
+
"splat_node" => Self::SplatNode,
|
|
251
|
+
"interpolated_regular_expression_node" => Self::InterpolatedRegularExpressionNode,
|
|
252
|
+
"interpolated_symbol_node" => Self::InterpolatedSymbolNode,
|
|
253
|
+
"x_string_node" => Self::XStringNode,
|
|
254
|
+
"interpolated_x_string_node" => Self::InterpolatedXStringNode,
|
|
255
|
+
"class_variable_read_node" => Self::ClassVariableReadNode,
|
|
256
|
+
"class_variable_write_node" => Self::ClassVariableWriteNode,
|
|
257
|
+
"class_variable_or_write_node" => Self::ClassVariableOrWriteNode,
|
|
258
|
+
"class_variable_and_write_node" => Self::ClassVariableAndWriteNode,
|
|
259
|
+
"class_variable_operator_write_node" => Self::ClassVariableOperatorWriteNode,
|
|
260
|
+
"global_variable_read_node" => Self::GlobalVariableReadNode,
|
|
261
|
+
"global_variable_write_node" => Self::GlobalVariableWriteNode,
|
|
262
|
+
"global_variable_or_write_node" => Self::GlobalVariableOrWriteNode,
|
|
263
|
+
"global_variable_and_write_node" => Self::GlobalVariableAndWriteNode,
|
|
264
|
+
"global_variable_operator_write_node" => Self::GlobalVariableOperatorWriteNode,
|
|
265
|
+
"local_variable_or_write_node" => Self::LocalVariableOrWriteNode,
|
|
266
|
+
"local_variable_and_write_node" => Self::LocalVariableAndWriteNode,
|
|
267
|
+
"local_variable_operator_write_node" => Self::LocalVariableOperatorWriteNode,
|
|
268
|
+
"instance_variable_or_write_node" => Self::InstanceVariableOrWriteNode,
|
|
269
|
+
"instance_variable_and_write_node" => Self::InstanceVariableAndWriteNode,
|
|
270
|
+
"instance_variable_operator_write_node" => Self::InstanceVariableOperatorWriteNode,
|
|
271
|
+
"constant_or_write_node" => Self::ConstantOrWriteNode,
|
|
272
|
+
"constant_and_write_node" => Self::ConstantAndWriteNode,
|
|
273
|
+
"constant_operator_write_node" => Self::ConstantOperatorWriteNode,
|
|
274
|
+
"constant_path_or_write_node" => Self::ConstantPathOrWriteNode,
|
|
275
|
+
"constant_path_and_write_node" => Self::ConstantPathAndWriteNode,
|
|
276
|
+
"constant_path_operator_write_node" => Self::ConstantPathOperatorWriteNode,
|
|
277
|
+
"constant_path_write_node" => Self::ConstantPathWriteNode,
|
|
278
|
+
"case_match_node" => Self::CaseMatchNode,
|
|
279
|
+
"in_node" => Self::InNode,
|
|
280
|
+
"match_predicate_node" => Self::MatchPredicateNode,
|
|
281
|
+
"match_required_node" => Self::MatchRequiredNode,
|
|
282
|
+
"self_node" => Self::SelfNode,
|
|
283
|
+
"parentheses_node" => Self::ParenthesesNode,
|
|
284
|
+
"defined_node" => Self::DefinedNode,
|
|
285
|
+
"singleton_class_node" => Self::SingletonClassNode,
|
|
286
|
+
"alias_method_node" => Self::AliasMethodNode,
|
|
287
|
+
"alias_global_variable_node" => Self::AliasGlobalVariableNode,
|
|
288
|
+
"undef_node" => Self::UndefNode,
|
|
289
|
+
"assoc_splat_node" => Self::AssocSplatNode,
|
|
290
|
+
"block_argument_node" => Self::BlockArgumentNode,
|
|
291
|
+
"multi_write_node" => Self::MultiWriteNode,
|
|
292
|
+
"multi_target_node" => Self::MultiTargetNode,
|
|
107
293
|
_ => Self::Unknown(s.to_string()),
|
|
108
294
|
}
|
|
109
295
|
}
|
data/ext/rfmt/src/emitter/mod.rs
CHANGED
|
@@ -46,10 +46,15 @@ impl Emitter {
|
|
|
46
46
|
self.buffer.clear();
|
|
47
47
|
self.emitted_comment_indices.clear();
|
|
48
48
|
|
|
49
|
-
// Collect all comments from the AST
|
|
50
49
|
self.collect_comments(ast);
|
|
51
50
|
|
|
52
51
|
self.emit_node(ast, 0)?;
|
|
52
|
+
|
|
53
|
+
// Ensure file ends with a newline
|
|
54
|
+
if !self.buffer.ends_with('\n') {
|
|
55
|
+
self.buffer.push('\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
53
58
|
Ok(self.buffer.clone())
|
|
54
59
|
}
|
|
55
60
|
|
|
@@ -68,22 +73,26 @@ impl Emitter {
|
|
|
68
73
|
IndentStyle::Tabs => "\t".repeat(indent_level),
|
|
69
74
|
};
|
|
70
75
|
|
|
71
|
-
let mut
|
|
76
|
+
let mut comments_to_emit = Vec::new();
|
|
72
77
|
for (idx, comment) in self.all_comments.iter().enumerate() {
|
|
73
78
|
if self.emitted_comment_indices.contains(&idx) {
|
|
74
79
|
continue;
|
|
75
80
|
}
|
|
76
81
|
|
|
77
|
-
// Collect comments that end before this line
|
|
78
82
|
if comment.location.end_line < line {
|
|
79
|
-
|
|
83
|
+
comments_to_emit.push((idx, comment.text.clone(), comment.location.end_line));
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
for (idx, text) in
|
|
87
|
+
let comments_count = comments_to_emit.len();
|
|
88
|
+
for (i, (idx, text, comment_end_line)) in comments_to_emit.into_iter().enumerate() {
|
|
85
89
|
writeln!(self.buffer, "{}{}", indent_str, text)?;
|
|
86
90
|
self.emitted_comment_indices.push(idx);
|
|
91
|
+
|
|
92
|
+
// Only add blank line after the LAST comment if there was a gap in the original
|
|
93
|
+
if i == comments_count - 1 && line > comment_end_line + 1 {
|
|
94
|
+
self.buffer.push('\n');
|
|
95
|
+
}
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
Ok(())
|
|
@@ -123,6 +132,15 @@ impl Emitter {
|
|
|
123
132
|
NodeType::IfNode => self.emit_if_unless(node, indent_level, false, "if")?,
|
|
124
133
|
NodeType::UnlessNode => self.emit_if_unless(node, indent_level, false, "unless")?,
|
|
125
134
|
NodeType::CallNode => self.emit_call(node, indent_level)?,
|
|
135
|
+
NodeType::BeginNode => self.emit_begin(node, indent_level)?,
|
|
136
|
+
NodeType::RescueNode => self.emit_rescue(node, indent_level)?,
|
|
137
|
+
NodeType::EnsureNode => self.emit_ensure(node, indent_level)?,
|
|
138
|
+
NodeType::LambdaNode => self.emit_lambda(node, indent_level)?,
|
|
139
|
+
NodeType::CaseNode => self.emit_case(node, indent_level)?,
|
|
140
|
+
NodeType::WhenNode => self.emit_when(node, indent_level)?,
|
|
141
|
+
NodeType::WhileNode => self.emit_while_until(node, indent_level, "while")?,
|
|
142
|
+
NodeType::UntilNode => self.emit_while_until(node, indent_level, "until")?,
|
|
143
|
+
NodeType::ForNode => self.emit_for(node, indent_level)?,
|
|
126
144
|
_ => self.emit_generic(node, indent_level)?,
|
|
127
145
|
}
|
|
128
146
|
Ok(())
|
|
@@ -154,14 +172,28 @@ impl Emitter {
|
|
|
154
172
|
for (i, child) in node.children.iter().enumerate() {
|
|
155
173
|
self.emit_node(child, indent_level)?;
|
|
156
174
|
|
|
157
|
-
// Add newlines between statements, normalizing to max 1 blank line
|
|
158
175
|
if i < node.children.len() - 1 {
|
|
159
176
|
let current_end_line = child.location.end_line;
|
|
160
|
-
let
|
|
161
|
-
let
|
|
177
|
+
let next_child = &node.children[i + 1];
|
|
178
|
+
let next_start_line = next_child.location.start_line;
|
|
179
|
+
|
|
180
|
+
// Find the first comment between current and next node (if any)
|
|
181
|
+
let first_comment_line = self
|
|
182
|
+
.all_comments
|
|
183
|
+
.iter()
|
|
184
|
+
.filter(|c| {
|
|
185
|
+
c.location.start_line > current_end_line
|
|
186
|
+
&& c.location.end_line < next_start_line
|
|
187
|
+
})
|
|
188
|
+
.map(|c| c.location.start_line)
|
|
189
|
+
.min();
|
|
190
|
+
|
|
191
|
+
// Calculate line diff based on whether there's a comment
|
|
192
|
+
let effective_next_line = first_comment_line.unwrap_or(next_start_line);
|
|
193
|
+
let line_diff = effective_next_line.saturating_sub(current_end_line);
|
|
162
194
|
|
|
163
|
-
// Add 1 newline if consecutive, 2 newlines (1 blank line) if there was a gap
|
|
164
195
|
let newlines = if line_diff > 1 { 2 } else { 1 };
|
|
196
|
+
|
|
165
197
|
for _ in 0..newlines {
|
|
166
198
|
self.buffer.push('\n');
|
|
167
199
|
}
|
|
@@ -314,6 +346,217 @@ impl Emitter {
|
|
|
314
346
|
Ok(())
|
|
315
347
|
}
|
|
316
348
|
|
|
349
|
+
/// Emit begin node
|
|
350
|
+
/// BeginNode can be either:
|
|
351
|
+
/// 1. Explicit begin...end block (source starts with "begin")
|
|
352
|
+
/// 2. Implicit begin wrapping method body with rescue/ensure
|
|
353
|
+
fn emit_begin(&mut self, node: &Node, indent_level: usize) -> Result<()> {
|
|
354
|
+
// Check if this is an explicit begin block by looking at source
|
|
355
|
+
let is_explicit_begin = if !self.source.is_empty() {
|
|
356
|
+
self.source
|
|
357
|
+
.get(node.location.start_offset..)
|
|
358
|
+
.map(|s| s.trim_start().starts_with("begin"))
|
|
359
|
+
.unwrap_or(false)
|
|
360
|
+
} else {
|
|
361
|
+
false
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
if is_explicit_begin {
|
|
365
|
+
self.emit_comments_before(node.location.start_line, indent_level)?;
|
|
366
|
+
self.emit_indent(indent_level)?;
|
|
367
|
+
writeln!(self.buffer, "begin")?;
|
|
368
|
+
|
|
369
|
+
for child in &node.children {
|
|
370
|
+
self.emit_node(child, indent_level + 1)?;
|
|
371
|
+
self.buffer.push('\n');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
self.emit_indent(indent_level)?;
|
|
375
|
+
write!(self.buffer, "end")?;
|
|
376
|
+
} else {
|
|
377
|
+
// Implicit begin - emit children directly
|
|
378
|
+
for (i, child) in node.children.iter().enumerate() {
|
|
379
|
+
if i > 0 {
|
|
380
|
+
self.buffer.push('\n');
|
|
381
|
+
}
|
|
382
|
+
self.emit_node(child, indent_level)?;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
Ok(())
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/// Emit rescue node
|
|
389
|
+
fn emit_rescue(&mut self, node: &Node, indent_level: usize) -> Result<()> {
|
|
390
|
+
// Rescue node structure:
|
|
391
|
+
// - First children are exception class references (ConstantReadNode)
|
|
392
|
+
// - Then exception variable (LocalVariableTargetNode)
|
|
393
|
+
// - Last child is StatementsNode with the rescue body
|
|
394
|
+
|
|
395
|
+
// Dedent by 1 level since rescue is at the same level as method body
|
|
396
|
+
let rescue_indent = indent_level.saturating_sub(1);
|
|
397
|
+
self.emit_indent(rescue_indent)?;
|
|
398
|
+
write!(self.buffer, "rescue")?;
|
|
399
|
+
|
|
400
|
+
// Extract exception classes and variable from source
|
|
401
|
+
if !self.source.is_empty() && node.location.end_offset <= self.source.len() {
|
|
402
|
+
if let Some(source_text) = self
|
|
403
|
+
.source
|
|
404
|
+
.get(node.location.start_offset..node.location.end_offset)
|
|
405
|
+
{
|
|
406
|
+
// Get the rescue line to extract exception class and variable
|
|
407
|
+
if let Some(rescue_line) = source_text.lines().next() {
|
|
408
|
+
// Remove "rescue" prefix and get the rest (exception class => var)
|
|
409
|
+
let after_rescue = rescue_line.trim_start_matches("rescue").trim();
|
|
410
|
+
if !after_rescue.is_empty() {
|
|
411
|
+
write!(self.buffer, " {}", after_rescue)?;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
self.buffer.push('\n');
|
|
418
|
+
|
|
419
|
+
// Emit rescue body (last child is typically StatementsNode)
|
|
420
|
+
if let Some(body) = node.children.last() {
|
|
421
|
+
if matches!(body.node_type, NodeType::StatementsNode) {
|
|
422
|
+
self.emit_node(body, indent_level)?;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
Ok(())
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/// Emit ensure node
|
|
430
|
+
fn emit_ensure(&mut self, node: &Node, indent_level: usize) -> Result<()> {
|
|
431
|
+
// ensure keyword should be at same level as begin/rescue
|
|
432
|
+
let ensure_indent = indent_level.saturating_sub(1);
|
|
433
|
+
|
|
434
|
+
self.emit_comments_before(node.location.start_line, ensure_indent)?;
|
|
435
|
+
self.emit_indent(ensure_indent)?;
|
|
436
|
+
writeln!(self.buffer, "ensure")?;
|
|
437
|
+
|
|
438
|
+
// Emit ensure body statements
|
|
439
|
+
for child in &node.children {
|
|
440
|
+
match &child.node_type {
|
|
441
|
+
NodeType::StatementsNode => {
|
|
442
|
+
self.emit_statements(child, indent_level)?;
|
|
443
|
+
}
|
|
444
|
+
_ => {
|
|
445
|
+
self.emit_node(child, indent_level)?;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
Ok(())
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/// Emit lambda node
|
|
454
|
+
fn emit_lambda(&mut self, node: &Node, indent_level: usize) -> Result<()> {
|
|
455
|
+
self.emit_comments_before(node.location.start_line, indent_level)?;
|
|
456
|
+
|
|
457
|
+
// Lambda syntax is complex (-> vs lambda, {} vs do-end)
|
|
458
|
+
// Use source extraction to preserve original style
|
|
459
|
+
self.emit_generic_without_comments(node, indent_level)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/// Emit case node
|
|
463
|
+
fn emit_case(&mut self, node: &Node, indent_level: usize) -> Result<()> {
|
|
464
|
+
self.emit_comments_before(node.location.start_line, indent_level)?;
|
|
465
|
+
self.emit_indent(indent_level)?;
|
|
466
|
+
|
|
467
|
+
// Write "case" keyword
|
|
468
|
+
write!(self.buffer, "case")?;
|
|
469
|
+
|
|
470
|
+
// Find predicate (first child that isn't WhenNode or ElseNode)
|
|
471
|
+
let mut when_start_idx = 0;
|
|
472
|
+
if let Some(first_child) = node.children.first() {
|
|
473
|
+
if !matches!(
|
|
474
|
+
first_child.node_type,
|
|
475
|
+
NodeType::WhenNode | NodeType::ElseNode
|
|
476
|
+
) {
|
|
477
|
+
// This is the predicate - extract from source
|
|
478
|
+
let start = first_child.location.start_offset;
|
|
479
|
+
let end = first_child.location.end_offset;
|
|
480
|
+
if let Some(text) = self.source.get(start..end) {
|
|
481
|
+
write!(self.buffer, " {}", text)?;
|
|
482
|
+
}
|
|
483
|
+
when_start_idx = 1;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
self.buffer.push('\n');
|
|
488
|
+
|
|
489
|
+
// Emit when clauses and else
|
|
490
|
+
for child in node.children.iter().skip(when_start_idx) {
|
|
491
|
+
match &child.node_type {
|
|
492
|
+
NodeType::WhenNode => {
|
|
493
|
+
self.emit_when(child, indent_level)?;
|
|
494
|
+
self.buffer.push('\n');
|
|
495
|
+
}
|
|
496
|
+
NodeType::ElseNode => {
|
|
497
|
+
self.emit_indent(indent_level)?;
|
|
498
|
+
writeln!(self.buffer, "else")?;
|
|
499
|
+
// Emit else body
|
|
500
|
+
for else_child in &child.children {
|
|
501
|
+
if matches!(else_child.node_type, NodeType::StatementsNode) {
|
|
502
|
+
self.emit_statements(else_child, indent_level + 1)?;
|
|
503
|
+
} else {
|
|
504
|
+
self.emit_node(else_child, indent_level + 1)?;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
self.buffer.push('\n');
|
|
508
|
+
}
|
|
509
|
+
_ => {}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Emit "end" keyword
|
|
514
|
+
self.emit_indent(indent_level)?;
|
|
515
|
+
write!(self.buffer, "end")?;
|
|
516
|
+
|
|
517
|
+
Ok(())
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/// Emit when node
|
|
521
|
+
fn emit_when(&mut self, node: &Node, indent_level: usize) -> Result<()> {
|
|
522
|
+
self.emit_comments_before(node.location.start_line, indent_level)?;
|
|
523
|
+
self.emit_indent(indent_level)?;
|
|
524
|
+
|
|
525
|
+
write!(self.buffer, "when ")?;
|
|
526
|
+
|
|
527
|
+
// Collect conditions (all children except StatementsNode)
|
|
528
|
+
let conditions: Vec<_> = node
|
|
529
|
+
.children
|
|
530
|
+
.iter()
|
|
531
|
+
.filter(|c| !matches!(c.node_type, NodeType::StatementsNode))
|
|
532
|
+
.collect();
|
|
533
|
+
|
|
534
|
+
// Emit conditions with comma separator
|
|
535
|
+
for (i, cond) in conditions.iter().enumerate() {
|
|
536
|
+
let start = cond.location.start_offset;
|
|
537
|
+
let end = cond.location.end_offset;
|
|
538
|
+
if let Some(text) = self.source.get(start..end) {
|
|
539
|
+
write!(self.buffer, "{}", text)?;
|
|
540
|
+
}
|
|
541
|
+
if i < conditions.len() - 1 {
|
|
542
|
+
write!(self.buffer, ", ")?;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
self.buffer.push('\n');
|
|
547
|
+
|
|
548
|
+
// Emit statements body
|
|
549
|
+
if let Some(statements) = node
|
|
550
|
+
.children
|
|
551
|
+
.iter()
|
|
552
|
+
.find(|c| matches!(c.node_type, NodeType::StatementsNode))
|
|
553
|
+
{
|
|
554
|
+
self.emit_statements(statements, indent_level + 1)?;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
Ok(())
|
|
558
|
+
}
|
|
559
|
+
|
|
317
560
|
/// Emit if/unless/elsif/else node
|
|
318
561
|
/// is_elsif: true if this is an elsif clause (don't emit 'end')
|
|
319
562
|
/// keyword: "if" or "unless"
|
|
@@ -616,22 +859,29 @@ impl Emitter {
|
|
|
616
859
|
|
|
617
860
|
/// Emit generic node by extracting from source
|
|
618
861
|
fn emit_generic(&mut self, node: &Node, indent_level: usize) -> Result<()> {
|
|
619
|
-
// Emit any comments before this node
|
|
620
862
|
self.emit_comments_before(node.location.start_line, indent_level)?;
|
|
621
863
|
|
|
622
864
|
if !self.source.is_empty() {
|
|
623
865
|
let start = node.location.start_offset;
|
|
624
866
|
let end = node.location.end_offset;
|
|
625
867
|
|
|
626
|
-
// Clone text first to avoid borrow conflict
|
|
627
868
|
let text_owned = self.source.get(start..end).map(|s| s.to_string());
|
|
628
869
|
|
|
629
870
|
if let Some(text) = text_owned {
|
|
630
|
-
// Add indentation before the extracted text
|
|
631
871
|
self.emit_indent(indent_level)?;
|
|
632
872
|
write!(self.buffer, "{}", text)?;
|
|
633
873
|
|
|
634
|
-
//
|
|
874
|
+
// Mark comments within this node's range as emitted
|
|
875
|
+
// (they are included in the source extraction)
|
|
876
|
+
for (idx, comment) in self.all_comments.iter().enumerate() {
|
|
877
|
+
if !self.emitted_comment_indices.contains(&idx)
|
|
878
|
+
&& comment.location.start_line >= node.location.start_line
|
|
879
|
+
&& comment.location.end_line <= node.location.end_line
|
|
880
|
+
{
|
|
881
|
+
self.emitted_comment_indices.push(idx);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
635
885
|
self.emit_trailing_comments(node.location.end_line)?;
|
|
636
886
|
}
|
|
637
887
|
}
|
|
@@ -649,6 +899,107 @@ impl Emitter {
|
|
|
649
899
|
Ok(())
|
|
650
900
|
}
|
|
651
901
|
|
|
902
|
+
/// Emit while/until loop
|
|
903
|
+
fn emit_while_until(&mut self, node: &Node, indent_level: usize, keyword: &str) -> Result<()> {
|
|
904
|
+
// Check if this is a postfix while/until (modifier form)
|
|
905
|
+
// In postfix form: "statement while/until condition"
|
|
906
|
+
// Check if body starts before predicate in source
|
|
907
|
+
let is_postfix = if node.children.len() >= 2 {
|
|
908
|
+
let predicate = &node.children[0];
|
|
909
|
+
let body = &node.children[1];
|
|
910
|
+
body.location.start_offset < predicate.location.start_offset
|
|
911
|
+
} else {
|
|
912
|
+
false
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
if is_postfix {
|
|
916
|
+
// Postfix form: extract from source as-is
|
|
917
|
+
return self.emit_generic(node, indent_level);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Normal while/until with do...end
|
|
921
|
+
self.emit_comments_before(node.location.start_line, indent_level)?;
|
|
922
|
+
self.emit_indent(indent_level)?;
|
|
923
|
+
write!(self.buffer, "{} ", keyword)?;
|
|
924
|
+
|
|
925
|
+
// Emit predicate (condition) - first child
|
|
926
|
+
if let Some(predicate) = node.children.first() {
|
|
927
|
+
if !self.source.is_empty() {
|
|
928
|
+
let start = predicate.location.start_offset;
|
|
929
|
+
let end = predicate.location.end_offset;
|
|
930
|
+
if let Some(text) = self.source.get(start..end) {
|
|
931
|
+
write!(self.buffer, "{}", text)?;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
self.buffer.push('\n');
|
|
937
|
+
|
|
938
|
+
// Emit body - second child (StatementsNode)
|
|
939
|
+
if let Some(body) = node.children.get(1) {
|
|
940
|
+
if matches!(body.node_type, NodeType::StatementsNode) {
|
|
941
|
+
self.emit_statements(body, indent_level + 1)?;
|
|
942
|
+
self.buffer.push('\n');
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
self.emit_indent(indent_level)?;
|
|
947
|
+
write!(self.buffer, "end")?;
|
|
948
|
+
|
|
949
|
+
Ok(())
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/// Emit for loop
|
|
953
|
+
fn emit_for(&mut self, node: &Node, indent_level: usize) -> Result<()> {
|
|
954
|
+
self.emit_comments_before(node.location.start_line, indent_level)?;
|
|
955
|
+
self.emit_indent(indent_level)?;
|
|
956
|
+
write!(self.buffer, "for ")?;
|
|
957
|
+
|
|
958
|
+
// node.children: [index, collection, statements]
|
|
959
|
+
// index: LocalVariableTargetNode or MultiTargetNode
|
|
960
|
+
// collection: expression
|
|
961
|
+
// statements: StatementsNode
|
|
962
|
+
|
|
963
|
+
// Emit index variable - first child
|
|
964
|
+
if let Some(index) = node.children.first() {
|
|
965
|
+
if !self.source.is_empty() {
|
|
966
|
+
let start = index.location.start_offset;
|
|
967
|
+
let end = index.location.end_offset;
|
|
968
|
+
if let Some(text) = self.source.get(start..end) {
|
|
969
|
+
write!(self.buffer, "{}", text)?;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
write!(self.buffer, " in ")?;
|
|
975
|
+
|
|
976
|
+
// Emit collection - second child
|
|
977
|
+
if let Some(collection) = node.children.get(1) {
|
|
978
|
+
if !self.source.is_empty() {
|
|
979
|
+
let start = collection.location.start_offset;
|
|
980
|
+
let end = collection.location.end_offset;
|
|
981
|
+
if let Some(text) = self.source.get(start..end) {
|
|
982
|
+
write!(self.buffer, "{}", text)?;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
self.buffer.push('\n');
|
|
988
|
+
|
|
989
|
+
// Emit body - third child (StatementsNode)
|
|
990
|
+
if let Some(body) = node.children.get(2) {
|
|
991
|
+
if matches!(body.node_type, NodeType::StatementsNode) {
|
|
992
|
+
self.emit_statements(body, indent_level + 1)?;
|
|
993
|
+
self.buffer.push('\n');
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
self.emit_indent(indent_level)?;
|
|
998
|
+
write!(self.buffer, "end")?;
|
|
999
|
+
|
|
1000
|
+
Ok(())
|
|
1001
|
+
}
|
|
1002
|
+
|
|
652
1003
|
/// Check if node is structural (part of definition syntax, not body)
|
|
653
1004
|
fn is_structural_node(&self, node_type: &NodeType) -> bool {
|
|
654
1005
|
matches!(
|
|
@@ -660,6 +1011,8 @@ impl Emitter {
|
|
|
660
1011
|
| NodeType::OptionalParameterNode
|
|
661
1012
|
| NodeType::RestParameterNode
|
|
662
1013
|
| NodeType::KeywordParameterNode
|
|
1014
|
+
| NodeType::RequiredKeywordParameterNode
|
|
1015
|
+
| NodeType::OptionalKeywordParameterNode
|
|
663
1016
|
| NodeType::KeywordRestParameterNode
|
|
664
1017
|
| NodeType::BlockParameterNode
|
|
665
1018
|
)
|
data/lib/rfmt/prism_bridge.rb
CHANGED
|
@@ -172,6 +172,127 @@ module Rfmt
|
|
|
172
172
|
[]
|
|
173
173
|
end
|
|
174
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
|
|
175
296
|
else
|
|
176
297
|
# For unknown types, try to get child nodes if they exist
|
|
177
298
|
[]
|
|
@@ -27,16 +27,25 @@ module Rfmt
|
|
|
27
27
|
when Prism::ConstantReadNode
|
|
28
28
|
sc.name.to_s
|
|
29
29
|
when Prism::ConstantPathNode
|
|
30
|
-
# Try full_name first, fall back to
|
|
30
|
+
# Try full_name first, fall back to slice for original source
|
|
31
31
|
if sc.respond_to?(:full_name)
|
|
32
32
|
sc.full_name.to_s
|
|
33
|
-
elsif sc.respond_to?(:
|
|
34
|
-
sc.
|
|
33
|
+
elsif sc.respond_to?(:slice)
|
|
34
|
+
sc.slice
|
|
35
35
|
else
|
|
36
|
-
sc.
|
|
36
|
+
sc.location.slice
|
|
37
37
|
end
|
|
38
|
+
when Prism::CallNode
|
|
39
|
+
# Handle cases like ActiveRecord::Migration[8.1]
|
|
40
|
+
# Use slice to get the original source text
|
|
41
|
+
sc.slice
|
|
38
42
|
else
|
|
39
|
-
|
|
43
|
+
# Fallback: try to get original source text
|
|
44
|
+
if sc.respond_to?(:slice)
|
|
45
|
+
sc.slice
|
|
46
|
+
else
|
|
47
|
+
sc.location.slice
|
|
48
|
+
end
|
|
40
49
|
end
|
|
41
50
|
end
|
|
42
51
|
|
|
Binary file
|
data/lib/rfmt/version.rb
CHANGED
data/lib/rfmt.rb
CHANGED
|
@@ -61,7 +61,7 @@ module Rfmt
|
|
|
61
61
|
DEFAULT_CONFIG = <<~YAML
|
|
62
62
|
# rfmt Configuration File
|
|
63
63
|
# This file controls how rfmt formats your Ruby code.
|
|
64
|
-
# See https://github.com/
|
|
64
|
+
# See https://github.com/fs0414/rfmt for full documentation.
|
|
65
65
|
|
|
66
66
|
version: "1.0"
|
|
67
67
|
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rfmt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- fujitani sora
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: rb_sys
|
|
@@ -41,8 +40,6 @@ files:
|
|
|
41
40
|
- exe/rfmt
|
|
42
41
|
- ext/rfmt/Cargo.toml
|
|
43
42
|
- ext/rfmt/extconf.rb
|
|
44
|
-
- ext/rfmt/spec/config_spec.rb
|
|
45
|
-
- ext/rfmt/spec/spec_helper.rb
|
|
46
43
|
- ext/rfmt/src/ast/mod.rs
|
|
47
44
|
- ext/rfmt/src/config/mod.rs
|
|
48
45
|
- ext/rfmt/src/emitter/mod.rs
|
|
@@ -60,7 +57,7 @@ files:
|
|
|
60
57
|
- lib/rfmt/configuration.rb
|
|
61
58
|
- lib/rfmt/prism_bridge.rb
|
|
62
59
|
- lib/rfmt/prism_node_extractor.rb
|
|
63
|
-
- lib/rfmt/rfmt.
|
|
60
|
+
- lib/rfmt/rfmt.bundle
|
|
64
61
|
- lib/rfmt/version.rb
|
|
65
62
|
- lib/ruby_lsp/rfmt/addon.rb
|
|
66
63
|
- lib/ruby_lsp/rfmt/formatter_runner.rb
|
|
@@ -73,7 +70,6 @@ metadata:
|
|
|
73
70
|
source_code_uri: https://github.com/fs0414/rfmt
|
|
74
71
|
changelog_uri: https://github.com/fs0414/rfmt/releases
|
|
75
72
|
ruby_lsp_addon: 'true'
|
|
76
|
-
post_install_message:
|
|
77
73
|
rdoc_options: []
|
|
78
74
|
require_paths:
|
|
79
75
|
- lib
|
|
@@ -88,8 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
88
84
|
- !ruby/object:Gem::Version
|
|
89
85
|
version: 3.0.0
|
|
90
86
|
requirements: []
|
|
91
|
-
rubygems_version: 3.
|
|
92
|
-
signing_key:
|
|
87
|
+
rubygems_version: 3.7.2
|
|
93
88
|
specification_version: 4
|
|
94
89
|
summary: Ruby Formatter impl Rust lang.
|
|
95
90
|
test_files: []
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe 'Configuration' do
|
|
6
|
-
describe 'configuration system integration' do
|
|
7
|
-
it 'can load YAML configuration' do
|
|
8
|
-
require 'tempfile'
|
|
9
|
-
require 'yaml'
|
|
10
|
-
|
|
11
|
-
config = {
|
|
12
|
-
'version' => '1.0',
|
|
13
|
-
'formatting' => {
|
|
14
|
-
'line_length' => 120,
|
|
15
|
-
'indent_width' => 4,
|
|
16
|
-
'indent_style' => 'tabs',
|
|
17
|
-
'quote_style' => 'single'
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
Tempfile.create(['test_config', '.yml']) do |file|
|
|
22
|
-
file.write(YAML.dump(config))
|
|
23
|
-
file.flush
|
|
24
|
-
|
|
25
|
-
loaded_config = YAML.load_file(file.path)
|
|
26
|
-
expect(loaded_config['formatting']['line_length']).to eq(120)
|
|
27
|
-
expect(loaded_config['formatting']['indent_width']).to eq(4)
|
|
28
|
-
expect(loaded_config['formatting']['indent_style']).to eq('tabs')
|
|
29
|
-
expect(loaded_config['formatting']['quote_style']).to eq('single')
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
it 'validates configuration values' do
|
|
34
|
-
# Configuration validation is tested in Rust tests
|
|
35
|
-
# 11 Rust tests verify all validation logic
|
|
36
|
-
expect(true).to be true
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'bundler/setup'
|
|
4
|
-
require 'rfmt'
|
|
5
|
-
|
|
6
|
-
RSpec.configure do |config|
|
|
7
|
-
config.expect_with :rspec do |expectations|
|
|
8
|
-
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
config.mock_with :rspec do |mocks|
|
|
12
|
-
mocks.verify_partial_doubles = true
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
config.shared_context_metadata_behavior = :apply_to_host_groups
|
|
16
|
-
end
|
data/lib/rfmt/rfmt.so
DELETED
|
Binary file
|