rfmt 1.5.2 → 1.6.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.
@@ -0,0 +1,359 @@
1
+ //! CaseRule, WhenRule, CaseMatchRule, InRule - Formats Ruby case expressions
2
+ //!
3
+ //! Handles:
4
+ //! - case/when: `case x when ... end`
5
+ //! - case/in: `case x in ... end` (pattern matching)
6
+
7
+ use crate::ast::{Node, NodeType};
8
+ use crate::doc::{concat, hardline, indent, text, Doc};
9
+ use crate::error::Result;
10
+ use crate::format::context::FormatContext;
11
+ use crate::format::registry::RuleRegistry;
12
+ use crate::format::rule::{
13
+ format_leading_comments, format_statements, format_trailing_comment, FormatRule,
14
+ };
15
+
16
+ /// Rule for formatting case expressions.
17
+ pub struct CaseRule;
18
+
19
+ /// Rule for formatting when clauses.
20
+ pub struct WhenRule;
21
+
22
+ /// Rule for formatting case match expressions (Ruby 3.0+ pattern matching).
23
+ pub struct CaseMatchRule;
24
+
25
+ /// Rule for formatting in clauses (pattern matching).
26
+ pub struct InRule;
27
+
28
+ impl FormatRule for CaseRule {
29
+ fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
30
+ format_case(node, ctx, registry)
31
+ }
32
+ }
33
+
34
+ impl FormatRule for WhenRule {
35
+ fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
36
+ format_when(node, ctx, registry)
37
+ }
38
+ }
39
+
40
+ impl FormatRule for CaseMatchRule {
41
+ fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
42
+ format_case_match(node, ctx, registry)
43
+ }
44
+ }
45
+
46
+ impl FormatRule for InRule {
47
+ fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
48
+ format_in(node, ctx, registry)
49
+ }
50
+ }
51
+
52
+ /// Formats case expression
53
+ fn format_case(node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
54
+ let mut docs: Vec<Doc> = Vec::with_capacity(8);
55
+
56
+ // Leading comments
57
+ let leading = format_leading_comments(ctx, node.location.start_line);
58
+ if !leading.is_empty() {
59
+ docs.push(leading);
60
+ }
61
+
62
+ // "case" keyword
63
+ docs.push(text("case"));
64
+
65
+ // Find predicate (first child that isn't WhenNode or ElseNode)
66
+ let mut when_start_idx = 0;
67
+ if let Some(first_child) = node.children.first() {
68
+ if !matches!(
69
+ first_child.node_type,
70
+ NodeType::WhenNode | NodeType::ElseNode
71
+ ) {
72
+ // This is the predicate
73
+ if let Some(source_text) = ctx.extract_source(first_child) {
74
+ docs.push(text(" "));
75
+ docs.push(text(source_text));
76
+ }
77
+ when_start_idx = 1;
78
+ }
79
+ }
80
+
81
+ // Emit when clauses and else
82
+ for child in node.children.iter().skip(when_start_idx) {
83
+ match &child.node_type {
84
+ NodeType::WhenNode => {
85
+ docs.push(hardline());
86
+ let when_doc = format_when(child, ctx, registry)?;
87
+ docs.push(when_doc);
88
+ }
89
+ NodeType::ElseNode => {
90
+ docs.push(hardline());
91
+ docs.push(text("else"));
92
+
93
+ // Emit else body
94
+ for else_child in &child.children {
95
+ if matches!(else_child.node_type, NodeType::StatementsNode) {
96
+ let body_doc = format_statements(else_child, ctx, registry)?;
97
+ docs.push(indent(concat(vec![hardline(), body_doc])));
98
+ }
99
+ }
100
+ }
101
+ _ => {}
102
+ }
103
+ }
104
+
105
+ // End keyword
106
+ docs.push(hardline());
107
+ docs.push(text("end"));
108
+
109
+ // Trailing comment on end line
110
+ let trailing = format_trailing_comment(ctx, node.location.end_line);
111
+ if !trailing.is_empty() {
112
+ docs.push(trailing);
113
+ }
114
+
115
+ Ok(concat(docs))
116
+ }
117
+
118
+ /// Formats when clause
119
+ fn format_when(node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
120
+ let mut docs: Vec<Doc> = Vec::with_capacity(6);
121
+
122
+ // Leading comments
123
+ let leading = format_leading_comments(ctx, node.location.start_line);
124
+ if !leading.is_empty() {
125
+ docs.push(leading);
126
+ }
127
+
128
+ docs.push(text("when "));
129
+
130
+ // Collect conditions (all children except StatementsNode)
131
+ let conditions: Vec<_> = node
132
+ .children
133
+ .iter()
134
+ .filter(|c| !matches!(c.node_type, NodeType::StatementsNode))
135
+ .collect();
136
+
137
+ // Emit conditions with comma separator
138
+ for (i, cond) in conditions.iter().enumerate() {
139
+ if let Some(source_text) = ctx.extract_source(cond) {
140
+ docs.push(text(source_text));
141
+ if i < conditions.len() - 1 {
142
+ docs.push(text(", "));
143
+ }
144
+ }
145
+ }
146
+
147
+ let statements = node
148
+ .children
149
+ .iter()
150
+ .find(|c| matches!(c.node_type, NodeType::StatementsNode));
151
+
152
+ let is_single_line = node.location.start_line == node.location.end_line;
153
+
154
+ if is_single_line {
155
+ // Inline style: when X then Y
156
+ if let Some(statements) = statements {
157
+ if let Some(source_text) = ctx.extract_source(statements) {
158
+ docs.push(text(" then "));
159
+ docs.push(text(source_text));
160
+ }
161
+ }
162
+ } else {
163
+ // Multi-line style: when X\n Y
164
+ if let Some(statements) = statements {
165
+ let body_doc = format_statements(statements, ctx, registry)?;
166
+ docs.push(indent(concat(vec![hardline(), body_doc])));
167
+ }
168
+ }
169
+
170
+ Ok(concat(docs))
171
+ }
172
+
173
+ /// Formats case match expression (Ruby 3.0+ pattern matching)
174
+ fn format_case_match(node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
175
+ let mut docs: Vec<Doc> = Vec::with_capacity(8);
176
+
177
+ // Leading comments
178
+ let leading = format_leading_comments(ctx, node.location.start_line);
179
+ if !leading.is_empty() {
180
+ docs.push(leading);
181
+ }
182
+
183
+ // "case" keyword
184
+ docs.push(text("case"));
185
+
186
+ // Find predicate (first child that isn't InNode or ElseNode)
187
+ let mut in_start_idx = 0;
188
+ if let Some(first_child) = node.children.first() {
189
+ if !matches!(first_child.node_type, NodeType::InNode | NodeType::ElseNode) {
190
+ // This is the predicate
191
+ if let Some(source_text) = ctx.extract_source(first_child) {
192
+ docs.push(text(" "));
193
+ docs.push(text(source_text));
194
+ }
195
+ in_start_idx = 1;
196
+ }
197
+ }
198
+
199
+ // Emit in clauses and else
200
+ for child in node.children.iter().skip(in_start_idx) {
201
+ match &child.node_type {
202
+ NodeType::InNode => {
203
+ docs.push(hardline());
204
+ let in_doc = format_in(child, ctx, registry)?;
205
+ docs.push(in_doc);
206
+ }
207
+ NodeType::ElseNode => {
208
+ docs.push(hardline());
209
+ docs.push(text("else"));
210
+
211
+ // Emit else body
212
+ for else_child in &child.children {
213
+ if matches!(else_child.node_type, NodeType::StatementsNode) {
214
+ let body_doc = format_statements(else_child, ctx, registry)?;
215
+ docs.push(indent(concat(vec![hardline(), body_doc])));
216
+ }
217
+ }
218
+ }
219
+ _ => {}
220
+ }
221
+ }
222
+
223
+ // End keyword
224
+ docs.push(hardline());
225
+ docs.push(text("end"));
226
+
227
+ // Trailing comment on end line
228
+ let trailing = format_trailing_comment(ctx, node.location.end_line);
229
+ if !trailing.is_empty() {
230
+ docs.push(trailing);
231
+ }
232
+
233
+ Ok(concat(docs))
234
+ }
235
+
236
+ /// Formats in clause (pattern matching)
237
+ fn format_in(node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
238
+ let mut docs: Vec<Doc> = Vec::with_capacity(6);
239
+
240
+ // Leading comments
241
+ let leading = format_leading_comments(ctx, node.location.start_line);
242
+ if !leading.is_empty() {
243
+ docs.push(leading);
244
+ }
245
+
246
+ docs.push(text("in "));
247
+
248
+ // First child is the pattern
249
+ if let Some(pattern) = node.children.first() {
250
+ if let Some(source_text) = ctx.extract_source(pattern) {
251
+ docs.push(text(source_text));
252
+ }
253
+ }
254
+
255
+ let is_single_line = node.location.start_line == node.location.end_line;
256
+
257
+ if is_single_line {
258
+ // Inline style: in X then Y
259
+ if let Some(statements) = node.children.get(1) {
260
+ if let Some(source_text) = ctx.extract_source(statements) {
261
+ docs.push(text(" then "));
262
+ docs.push(text(source_text));
263
+ }
264
+ }
265
+ } else {
266
+ // Multi-line style: in X\n Y
267
+ if let Some(statements) = node.children.get(1) {
268
+ if matches!(statements.node_type, NodeType::StatementsNode) {
269
+ let body_doc = format_statements(statements, ctx, registry)?;
270
+ docs.push(indent(concat(vec![hardline(), body_doc])));
271
+ }
272
+ }
273
+ }
274
+
275
+ Ok(concat(docs))
276
+ }
277
+
278
+ #[cfg(test)]
279
+ mod tests {
280
+ use super::*;
281
+ use crate::ast::{FormattingInfo, Location};
282
+ use crate::config::Config;
283
+ use crate::doc::Printer;
284
+ use std::collections::HashMap;
285
+
286
+ fn make_case_node(children: Vec<Node>, start_line: usize, end_line: usize) -> Node {
287
+ Node {
288
+ node_type: NodeType::CaseNode,
289
+ location: Location::new(start_line, 0, end_line, 3, 0, 50),
290
+ children,
291
+ metadata: HashMap::new(),
292
+ comments: Vec::new(),
293
+ formatting: FormattingInfo::default(),
294
+ }
295
+ }
296
+
297
+ fn make_when_node(children: Vec<Node>, start_line: usize, end_line: usize) -> Node {
298
+ Node {
299
+ node_type: NodeType::WhenNode,
300
+ location: Location::new(start_line, 0, end_line, 0, 0, 30),
301
+ children,
302
+ metadata: HashMap::new(),
303
+ comments: Vec::new(),
304
+ formatting: FormattingInfo::default(),
305
+ }
306
+ }
307
+
308
+ #[test]
309
+ fn test_simple_case() {
310
+ let config = Config::default();
311
+ let source = "case x\nwhen 1\n puts 'one'\nend";
312
+ let mut ctx = FormatContext::new(&config, source);
313
+ let registry = RuleRegistry::default_registry();
314
+
315
+ // predicate node
316
+ let predicate = Node {
317
+ node_type: NodeType::CallNode,
318
+ location: Location::new(1, 5, 1, 6, 5, 6),
319
+ children: Vec::new(),
320
+ metadata: HashMap::new(),
321
+ comments: Vec::new(),
322
+ formatting: FormattingInfo::default(),
323
+ };
324
+
325
+ // when node
326
+ let when_cond = Node {
327
+ node_type: NodeType::IntegerNode,
328
+ location: Location::new(2, 5, 2, 6, 12, 13),
329
+ children: Vec::new(),
330
+ metadata: HashMap::new(),
331
+ comments: Vec::new(),
332
+ formatting: FormattingInfo::default(),
333
+ };
334
+
335
+ let when_body = Node {
336
+ node_type: NodeType::StatementsNode,
337
+ location: Location::new(3, 2, 3, 13, 16, 27),
338
+ children: Vec::new(),
339
+ metadata: HashMap::new(),
340
+ comments: Vec::new(),
341
+ formatting: FormattingInfo::default(),
342
+ };
343
+
344
+ let when_node = make_when_node(vec![when_cond, when_body], 2, 3);
345
+
346
+ let node = make_case_node(vec![predicate, when_node], 1, 4);
347
+ ctx.collect_comments(&node);
348
+
349
+ let rule = CaseRule;
350
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
351
+
352
+ let mut printer = Printer::new(&config);
353
+ let result = printer.print(&doc);
354
+
355
+ assert!(result.contains("case x"));
356
+ assert!(result.contains("when 1"));
357
+ assert!(result.contains("end"));
358
+ }
359
+ }
@@ -0,0 +1,160 @@
1
+ //! ClassRule - Formats Ruby class definitions
2
+ //!
3
+ //! Handles class definitions including:
4
+ //! - Simple classes: `class Foo`
5
+ //! - Classes with inheritance: `class Foo < Bar`
6
+ //! - Class bodies with methods and other declarations
7
+ //! - Leading and trailing comments
8
+
9
+ use crate::ast::Node;
10
+ use crate::doc::{text, Doc};
11
+ use crate::error::Result;
12
+ use crate::format::context::FormatContext;
13
+ use crate::format::registry::RuleRegistry;
14
+ use crate::format::rule::FormatRule;
15
+
16
+ use super::body_end::{format_body_end, BodyEndConfig};
17
+
18
+ /// Rule for formatting class definitions.
19
+ pub struct ClassRule;
20
+
21
+ impl FormatRule for ClassRule {
22
+ fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
23
+ format_body_end(
24
+ ctx,
25
+ registry,
26
+ BodyEndConfig {
27
+ keyword: "class",
28
+ node,
29
+ header_builder: Box::new(build_class_header),
30
+ skip_same_line_children: true,
31
+ },
32
+ )
33
+ }
34
+ }
35
+
36
+ /// Builds the header portion for a class definition.
37
+ ///
38
+ /// Returns: `ClassName` or `ClassName < Superclass`
39
+ fn build_class_header(node: &Node) -> Vec<Doc> {
40
+ let mut parts: Vec<Doc> = Vec::with_capacity(4);
41
+
42
+ // Get class name from metadata
43
+ if let Some(name) = node.metadata.get("name") {
44
+ parts.push(text(name));
45
+ }
46
+
47
+ // Get superclass from metadata if present
48
+ if let Some(superclass) = node.metadata.get("superclass") {
49
+ parts.push(text(" < "));
50
+ parts.push(text(superclass));
51
+ }
52
+
53
+ parts
54
+ }
55
+
56
+ #[cfg(test)]
57
+ mod tests {
58
+ use super::*;
59
+ use crate::ast::{FormattingInfo, Location, NodeType};
60
+ use crate::config::Config;
61
+ use crate::doc::Printer;
62
+ use std::collections::HashMap;
63
+
64
+ fn make_class_node(
65
+ name: &str,
66
+ superclass: Option<&str>,
67
+ children: Vec<Node>,
68
+ start_line: usize,
69
+ end_line: usize,
70
+ ) -> Node {
71
+ let mut metadata = HashMap::new();
72
+ metadata.insert("name".to_string(), name.to_string());
73
+ if let Some(sc) = superclass {
74
+ metadata.insert("superclass".to_string(), sc.to_string());
75
+ }
76
+
77
+ Node {
78
+ node_type: NodeType::ClassNode,
79
+ location: Location::new(start_line, 0, end_line, 3, 0, 50),
80
+ children,
81
+ metadata,
82
+ comments: Vec::new(),
83
+ formatting: FormattingInfo::default(),
84
+ }
85
+ }
86
+
87
+ #[test]
88
+ fn test_simple_class() {
89
+ let config = Config::default();
90
+ let source = "class Foo\nend";
91
+ let mut ctx = FormatContext::new(&config, source);
92
+ let registry = RuleRegistry::default_registry();
93
+
94
+ let node = make_class_node("Foo", None, Vec::new(), 1, 2);
95
+ ctx.collect_comments(&node);
96
+
97
+ let rule = ClassRule;
98
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
99
+
100
+ let mut printer = Printer::new(&config);
101
+ let result = printer.print(&doc);
102
+
103
+ assert_eq!(result.trim(), "class Foo\nend");
104
+ }
105
+
106
+ #[test]
107
+ fn test_class_with_inheritance() {
108
+ let config = Config::default();
109
+ let source = "class Foo < Bar\nend";
110
+ let mut ctx = FormatContext::new(&config, source);
111
+ let registry = RuleRegistry::default_registry();
112
+
113
+ let node = make_class_node("Foo", Some("Bar"), Vec::new(), 1, 2);
114
+ ctx.collect_comments(&node);
115
+
116
+ let rule = ClassRule;
117
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
118
+
119
+ let mut printer = Printer::new(&config);
120
+ let result = printer.print(&doc);
121
+
122
+ assert_eq!(result.trim(), "class Foo < Bar\nend");
123
+ }
124
+
125
+ #[test]
126
+ fn test_class_with_body() {
127
+ let config = Config::default();
128
+ let source = "class Foo\n def bar\n end\nend";
129
+ let mut ctx = FormatContext::new(&config, source);
130
+ let registry = RuleRegistry::default_registry();
131
+
132
+ // Create a method node as child
133
+ let method_node = Node {
134
+ node_type: NodeType::DefNode,
135
+ location: Location::new(2, 2, 3, 5, 12, 24),
136
+ children: Vec::new(),
137
+ metadata: {
138
+ let mut m = HashMap::new();
139
+ m.insert("name".to_string(), "bar".to_string());
140
+ m
141
+ },
142
+ comments: Vec::new(),
143
+ formatting: FormattingInfo::default(),
144
+ };
145
+
146
+ let node = make_class_node("Foo", None, vec![method_node], 1, 4);
147
+ ctx.collect_comments(&node);
148
+
149
+ let rule = ClassRule;
150
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
151
+
152
+ let mut printer = Printer::new(&config);
153
+ let result = printer.print(&doc);
154
+
155
+ // Should have the class, body, and end
156
+ assert!(result.contains("class Foo"));
157
+ assert!(result.contains("def bar"));
158
+ assert!(result.contains("end"));
159
+ }
160
+ }