rfmt 0.1.0 → 0.2.2
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 +35 -0
- data/Cargo.lock +1748 -133
- data/README.md +360 -20
- data/exe/rfmt +15 -0
- data/ext/rfmt/Cargo.toml +46 -1
- data/ext/rfmt/extconf.rb +5 -5
- data/ext/rfmt/spec/config_spec.rb +39 -0
- data/ext/rfmt/spec/spec_helper.rb +16 -0
- data/ext/rfmt/src/ast/mod.rs +335 -0
- data/ext/rfmt/src/config/mod.rs +403 -0
- data/ext/rfmt/src/emitter/mod.rs +363 -0
- data/ext/rfmt/src/error/mod.rs +48 -0
- data/ext/rfmt/src/lib.rs +59 -36
- data/ext/rfmt/src/logging/logger.rs +128 -0
- data/ext/rfmt/src/logging/mod.rs +3 -0
- data/ext/rfmt/src/parser/mod.rs +9 -0
- data/ext/rfmt/src/parser/prism_adapter.rs +407 -0
- data/ext/rfmt/src/policy/mod.rs +36 -0
- data/ext/rfmt/src/policy/validation.rs +18 -0
- data/lib/rfmt/cache.rb +120 -0
- data/lib/rfmt/cli.rb +280 -0
- data/lib/rfmt/configuration.rb +95 -0
- data/lib/rfmt/prism_bridge.rb +255 -0
- data/lib/rfmt/prism_node_extractor.rb +81 -0
- data/lib/rfmt/rfmt.so +0 -0
- data/lib/rfmt/version.rb +1 -1
- data/lib/rfmt.rb +156 -5
- metadata +29 -7
- data/lib/rfmt/rfmt.bundle +0 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
use serde::{Deserialize, Serialize};
|
|
2
|
+
use std::collections::HashMap;
|
|
3
|
+
|
|
4
|
+
/// Internal AST representation
|
|
5
|
+
/// This structure is designed to work seamlessly with Prism parser output
|
|
6
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
7
|
+
pub struct Node {
|
|
8
|
+
pub node_type: NodeType,
|
|
9
|
+
pub location: Location,
|
|
10
|
+
pub children: Vec<Node>,
|
|
11
|
+
pub metadata: HashMap<String, String>,
|
|
12
|
+
pub comments: Vec<Comment>,
|
|
13
|
+
pub formatting: FormattingInfo,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// Location information for a node in the source code
|
|
17
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
18
|
+
pub struct Location {
|
|
19
|
+
pub start_line: usize,
|
|
20
|
+
pub start_column: usize,
|
|
21
|
+
pub end_line: usize,
|
|
22
|
+
pub end_column: usize,
|
|
23
|
+
pub start_offset: usize,
|
|
24
|
+
pub end_offset: usize,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Node types supported by rfmt
|
|
28
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
29
|
+
#[serde(rename_all = "snake_case")]
|
|
30
|
+
pub enum NodeType {
|
|
31
|
+
// Program & Statements
|
|
32
|
+
ProgramNode,
|
|
33
|
+
StatementsNode,
|
|
34
|
+
|
|
35
|
+
// Definitions
|
|
36
|
+
ClassNode,
|
|
37
|
+
ModuleNode,
|
|
38
|
+
DefNode,
|
|
39
|
+
|
|
40
|
+
// Expressions
|
|
41
|
+
CallNode,
|
|
42
|
+
IfNode,
|
|
43
|
+
UnlessNode,
|
|
44
|
+
|
|
45
|
+
// Literals
|
|
46
|
+
StringNode,
|
|
47
|
+
IntegerNode,
|
|
48
|
+
FloatNode,
|
|
49
|
+
ArrayNode,
|
|
50
|
+
HashNode,
|
|
51
|
+
TrueNode,
|
|
52
|
+
FalseNode,
|
|
53
|
+
NilNode,
|
|
54
|
+
|
|
55
|
+
// Blocks
|
|
56
|
+
BlockNode,
|
|
57
|
+
|
|
58
|
+
// Constants (structural nodes, part of definitions)
|
|
59
|
+
ConstantReadNode,
|
|
60
|
+
ConstantWriteNode,
|
|
61
|
+
ConstantPathNode,
|
|
62
|
+
|
|
63
|
+
// Parameters (structural nodes, part of method definitions)
|
|
64
|
+
RequiredParameterNode,
|
|
65
|
+
OptionalParameterNode,
|
|
66
|
+
RestParameterNode,
|
|
67
|
+
KeywordParameterNode,
|
|
68
|
+
KeywordRestParameterNode,
|
|
69
|
+
BlockParameterNode,
|
|
70
|
+
|
|
71
|
+
Unknown(String),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
impl NodeType {
|
|
75
|
+
/// Parse node type from Prism type string
|
|
76
|
+
/// Returns Unknown variant for unsupported node types
|
|
77
|
+
pub fn from_str(s: &str) -> Self {
|
|
78
|
+
match s {
|
|
79
|
+
"program_node" => Self::ProgramNode,
|
|
80
|
+
"statements_node" => Self::StatementsNode,
|
|
81
|
+
"class_node" => Self::ClassNode,
|
|
82
|
+
"module_node" => Self::ModuleNode,
|
|
83
|
+
"def_node" => Self::DefNode,
|
|
84
|
+
"call_node" => Self::CallNode,
|
|
85
|
+
"if_node" => Self::IfNode,
|
|
86
|
+
"unless_node" => Self::UnlessNode,
|
|
87
|
+
"string_node" => Self::StringNode,
|
|
88
|
+
"integer_node" => Self::IntegerNode,
|
|
89
|
+
"float_node" => Self::FloatNode,
|
|
90
|
+
"array_node" => Self::ArrayNode,
|
|
91
|
+
"hash_node" => Self::HashNode,
|
|
92
|
+
"true_node" => Self::TrueNode,
|
|
93
|
+
"false_node" => Self::FalseNode,
|
|
94
|
+
"nil_node" => Self::NilNode,
|
|
95
|
+
"block_node" => Self::BlockNode,
|
|
96
|
+
"constant_read_node" => Self::ConstantReadNode,
|
|
97
|
+
"constant_write_node" => Self::ConstantWriteNode,
|
|
98
|
+
"constant_path_node" => Self::ConstantPathNode,
|
|
99
|
+
"required_parameter_node" => Self::RequiredParameterNode,
|
|
100
|
+
"optional_parameter_node" => Self::OptionalParameterNode,
|
|
101
|
+
"rest_parameter_node" => Self::RestParameterNode,
|
|
102
|
+
"keyword_parameter_node" => Self::KeywordParameterNode,
|
|
103
|
+
"keyword_rest_parameter_node" => Self::KeywordRestParameterNode,
|
|
104
|
+
"block_parameter_node" => Self::BlockParameterNode,
|
|
105
|
+
_ => Self::Unknown(s.to_string()),
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Check if this node type is a definition (class, module, or method)
|
|
110
|
+
#[cfg(test)]
|
|
111
|
+
pub fn is_definition(&self) -> bool {
|
|
112
|
+
matches!(
|
|
113
|
+
self,
|
|
114
|
+
NodeType::ClassNode | NodeType::ModuleNode | NodeType::DefNode
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Comment attached to a node
|
|
120
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
121
|
+
pub struct Comment {
|
|
122
|
+
pub text: String,
|
|
123
|
+
pub location: Location,
|
|
124
|
+
pub comment_type: CommentType,
|
|
125
|
+
pub position: CommentPosition,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
129
|
+
pub enum CommentType {
|
|
130
|
+
Line, // # comment
|
|
131
|
+
Block, // =begin...=end
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
135
|
+
pub enum CommentPosition {
|
|
136
|
+
Leading, // Comment before the node
|
|
137
|
+
Trailing, // Comment after the node (same line)
|
|
138
|
+
Inner, // Comment inside the node
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Formatting information attached to a node
|
|
142
|
+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
143
|
+
pub struct FormattingInfo {
|
|
144
|
+
pub indent_level: usize,
|
|
145
|
+
pub needs_blank_line_before: bool,
|
|
146
|
+
pub needs_blank_line_after: bool,
|
|
147
|
+
pub preserve_newlines: bool,
|
|
148
|
+
pub multiline: bool,
|
|
149
|
+
pub original_formatting: Option<String>,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
impl Node {
|
|
153
|
+
/// Create a new node with the given type and location
|
|
154
|
+
#[cfg(test)]
|
|
155
|
+
pub fn new(node_type: NodeType, location: Location) -> Self {
|
|
156
|
+
Self {
|
|
157
|
+
node_type,
|
|
158
|
+
location,
|
|
159
|
+
children: Vec::new(),
|
|
160
|
+
metadata: HashMap::new(),
|
|
161
|
+
comments: Vec::new(),
|
|
162
|
+
formatting: FormattingInfo::default(),
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// Add children to the node
|
|
167
|
+
#[cfg(test)]
|
|
168
|
+
pub fn with_children(mut self, children: Vec<Node>) -> Self {
|
|
169
|
+
self.children = children;
|
|
170
|
+
self
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// Add metadata to the node
|
|
174
|
+
#[cfg(test)]
|
|
175
|
+
pub fn with_metadata(mut self, metadata: HashMap<String, String>) -> Self {
|
|
176
|
+
self.metadata = metadata;
|
|
177
|
+
self
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/// Add comments to the node
|
|
181
|
+
#[cfg(test)]
|
|
182
|
+
pub fn with_comments(mut self, comments: Vec<Comment>) -> Self {
|
|
183
|
+
self.comments = comments;
|
|
184
|
+
self
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/// Check if the node spans multiple lines
|
|
188
|
+
#[cfg(test)]
|
|
189
|
+
pub fn is_multiline(&self) -> bool {
|
|
190
|
+
self.location.start_line != self.location.end_line
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// Get the number of lines this node spans
|
|
194
|
+
#[cfg(test)]
|
|
195
|
+
pub fn line_count(&self) -> usize {
|
|
196
|
+
self.location.end_line - self.location.start_line + 1
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// Check if this is an unknown node type
|
|
200
|
+
#[cfg(test)]
|
|
201
|
+
pub fn is_unknown(&self) -> bool {
|
|
202
|
+
matches!(self.node_type, NodeType::Unknown(_))
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/// Get the unknown node type name if this is an unknown node
|
|
206
|
+
#[cfg(test)]
|
|
207
|
+
pub fn unknown_type(&self) -> Option<&str> {
|
|
208
|
+
match &self.node_type {
|
|
209
|
+
NodeType::Unknown(name) => Some(name.as_str()),
|
|
210
|
+
_ => None,
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
impl Location {
|
|
216
|
+
/// Create a new location
|
|
217
|
+
pub fn new(
|
|
218
|
+
start_line: usize,
|
|
219
|
+
start_column: usize,
|
|
220
|
+
end_line: usize,
|
|
221
|
+
end_column: usize,
|
|
222
|
+
start_offset: usize,
|
|
223
|
+
end_offset: usize,
|
|
224
|
+
) -> Self {
|
|
225
|
+
Self {
|
|
226
|
+
start_line,
|
|
227
|
+
start_column,
|
|
228
|
+
end_line,
|
|
229
|
+
end_column,
|
|
230
|
+
start_offset,
|
|
231
|
+
end_offset,
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/// Create a zero location (for testing)
|
|
236
|
+
#[cfg(test)]
|
|
237
|
+
pub fn zero() -> Self {
|
|
238
|
+
Self {
|
|
239
|
+
start_line: 0,
|
|
240
|
+
start_column: 0,
|
|
241
|
+
end_line: 0,
|
|
242
|
+
end_column: 0,
|
|
243
|
+
start_offset: 0,
|
|
244
|
+
end_offset: 0,
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
#[cfg(test)]
|
|
250
|
+
mod tests {
|
|
251
|
+
use super::*;
|
|
252
|
+
|
|
253
|
+
#[test]
|
|
254
|
+
fn test_node_creation() {
|
|
255
|
+
let node = Node::new(NodeType::ProgramNode, Location::zero());
|
|
256
|
+
assert_eq!(node.node_type, NodeType::ProgramNode);
|
|
257
|
+
assert_eq!(node.children.len(), 0);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
#[test]
|
|
261
|
+
fn test_node_with_children() {
|
|
262
|
+
let child = Node::new(NodeType::ClassNode, Location::zero());
|
|
263
|
+
let node = Node::new(NodeType::ProgramNode, Location::zero()).with_children(vec![child]);
|
|
264
|
+
|
|
265
|
+
assert_eq!(node.children.len(), 1);
|
|
266
|
+
assert_eq!(node.children[0].node_type, NodeType::ClassNode);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
#[test]
|
|
270
|
+
fn test_node_type_from_str() {
|
|
271
|
+
assert_eq!(NodeType::from_str("program_node"), NodeType::ProgramNode);
|
|
272
|
+
assert_eq!(NodeType::from_str("class_node"), NodeType::ClassNode);
|
|
273
|
+
assert_eq!(NodeType::from_str("def_node"), NodeType::DefNode);
|
|
274
|
+
|
|
275
|
+
// Unknown type should return Unknown variant
|
|
276
|
+
match NodeType::from_str("unknown_node") {
|
|
277
|
+
NodeType::Unknown(s) => assert_eq!(s, "unknown_node"),
|
|
278
|
+
_ => panic!("Expected Unknown variant"),
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
#[test]
|
|
283
|
+
fn test_node_type_is_definition() {
|
|
284
|
+
assert!(NodeType::ClassNode.is_definition());
|
|
285
|
+
assert!(NodeType::ModuleNode.is_definition());
|
|
286
|
+
assert!(NodeType::DefNode.is_definition());
|
|
287
|
+
assert!(!NodeType::CallNode.is_definition());
|
|
288
|
+
assert!(!NodeType::IntegerNode.is_definition());
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
#[test]
|
|
292
|
+
fn test_location_zero() {
|
|
293
|
+
let loc = Location::zero();
|
|
294
|
+
assert_eq!(loc.start_line, 0);
|
|
295
|
+
assert_eq!(loc.start_offset, 0);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
#[test]
|
|
299
|
+
fn test_node_is_multiline() {
|
|
300
|
+
let single_line = Node::new(NodeType::CallNode, Location::new(1, 0, 1, 10, 0, 10));
|
|
301
|
+
assert!(!single_line.is_multiline());
|
|
302
|
+
|
|
303
|
+
let multi_line = Node::new(NodeType::ClassNode, Location::new(1, 0, 5, 3, 0, 50));
|
|
304
|
+
assert!(multi_line.is_multiline());
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
#[test]
|
|
308
|
+
fn test_node_line_count() {
|
|
309
|
+
let node = Node::new(NodeType::DefNode, Location::new(10, 0, 15, 3, 100, 200));
|
|
310
|
+
assert_eq!(node.line_count(), 6);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
#[test]
|
|
314
|
+
fn test_node_is_unknown() {
|
|
315
|
+
let known_node = Node::new(NodeType::ClassNode, Location::zero());
|
|
316
|
+
assert!(!known_node.is_unknown());
|
|
317
|
+
assert_eq!(known_node.unknown_type(), None);
|
|
318
|
+
|
|
319
|
+
let unknown_node = Node::new(
|
|
320
|
+
NodeType::Unknown("custom_node".to_string()),
|
|
321
|
+
Location::zero(),
|
|
322
|
+
);
|
|
323
|
+
assert!(unknown_node.is_unknown());
|
|
324
|
+
assert_eq!(unknown_node.unknown_type(), Some("custom_node"));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#[test]
|
|
328
|
+
fn test_node_type_from_str_returns_unknown_for_unsupported() {
|
|
329
|
+
let node_type = NodeType::from_str("unsupported_node_type");
|
|
330
|
+
match node_type {
|
|
331
|
+
NodeType::Unknown(name) => assert_eq!(name, "unsupported_node_type"),
|
|
332
|
+
_ => panic!("Expected Unknown variant"),
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|