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,325 @@
1
+ //! Loop rules - Formats Ruby while/until/for loops
2
+ //!
3
+ //! Handles:
4
+ //! - while loops: `while cond ... end`
5
+ //! - until loops: `until cond ... end`
6
+ //! - for loops: `for x in collection ... end`
7
+ //! - Postfix forms: `expr while/until cond`
8
+
9
+ use crate::ast::{Node, NodeType};
10
+ use crate::doc::{concat, hardline, indent, text, Doc};
11
+ use crate::error::Result;
12
+ use crate::format::context::FormatContext;
13
+ use crate::format::registry::RuleRegistry;
14
+ use crate::format::rule::{
15
+ format_leading_comments, format_statements, format_trailing_comment, FormatRule,
16
+ };
17
+
18
+ /// Rule for formatting while loops.
19
+ pub struct WhileRule;
20
+
21
+ /// Rule for formatting until loops.
22
+ pub struct UntilRule;
23
+
24
+ /// Rule for formatting for loops.
25
+ pub struct ForRule;
26
+
27
+ impl FormatRule for WhileRule {
28
+ fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
29
+ format_while_until(node, ctx, registry, "while")
30
+ }
31
+ }
32
+
33
+ impl FormatRule for UntilRule {
34
+ fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
35
+ format_while_until(node, ctx, registry, "until")
36
+ }
37
+ }
38
+
39
+ impl FormatRule for ForRule {
40
+ fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
41
+ format_for(node, ctx, registry)
42
+ }
43
+ }
44
+
45
+ /// Formats while/until loop
46
+ fn format_while_until(
47
+ node: &Node,
48
+ ctx: &mut FormatContext,
49
+ registry: &RuleRegistry,
50
+ keyword: &str,
51
+ ) -> Result<Doc> {
52
+ // Check if this is a postfix while/until (modifier form)
53
+ // In postfix form: "statement while/until condition"
54
+ let is_postfix = if node.children.len() >= 2 {
55
+ let predicate = &node.children[0];
56
+ let body = &node.children[1];
57
+ body.location.start_offset < predicate.location.start_offset
58
+ } else {
59
+ false
60
+ };
61
+
62
+ if is_postfix {
63
+ return format_postfix_while_until(node, ctx, keyword);
64
+ }
65
+
66
+ // Normal while/until with do...end
67
+ format_normal_while_until(node, ctx, registry, keyword)
68
+ }
69
+
70
+ /// Formats postfix while/until
71
+ fn format_postfix_while_until(node: &Node, ctx: &mut FormatContext, keyword: &str) -> Result<Doc> {
72
+ let mut docs: Vec<Doc> = Vec::with_capacity(6);
73
+
74
+ // Leading comments
75
+ let leading = format_leading_comments(ctx, node.location.start_line);
76
+ if !leading.is_empty() {
77
+ docs.push(leading);
78
+ }
79
+
80
+ // Extract from source for postfix form
81
+ if let Some(source_text) = ctx.extract_source(node) {
82
+ docs.push(text(source_text));
83
+ }
84
+
85
+ // Trailing comment
86
+ let trailing = format_trailing_comment(ctx, node.location.end_line);
87
+ if !trailing.is_empty() {
88
+ docs.push(trailing);
89
+ }
90
+
91
+ let _ = keyword; // Used for potential future formatting
92
+
93
+ Ok(concat(docs))
94
+ }
95
+
96
+ /// Formats normal while/until with do...end
97
+ fn format_normal_while_until(
98
+ node: &Node,
99
+ ctx: &mut FormatContext,
100
+ registry: &RuleRegistry,
101
+ keyword: &str,
102
+ ) -> Result<Doc> {
103
+ let mut docs: Vec<Doc> = Vec::with_capacity(8);
104
+
105
+ // Leading comments
106
+ let leading = format_leading_comments(ctx, node.location.start_line);
107
+ if !leading.is_empty() {
108
+ docs.push(leading);
109
+ }
110
+
111
+ docs.push(text(keyword));
112
+ docs.push(text(" "));
113
+
114
+ // Emit predicate (condition) - first child
115
+ if let Some(predicate) = node.children.first() {
116
+ if let Some(source_text) = ctx.extract_source(predicate) {
117
+ docs.push(text(source_text));
118
+ }
119
+ }
120
+
121
+ // Trailing comment on same line as while/until
122
+ let trailing = format_trailing_comment(ctx, node.location.start_line);
123
+ if !trailing.is_empty() {
124
+ docs.push(trailing);
125
+ }
126
+
127
+ // Emit body - second child (StatementsNode)
128
+ if let Some(body) = node.children.get(1) {
129
+ if matches!(body.node_type, NodeType::StatementsNode) {
130
+ let body_doc = format_statements(body, ctx, registry)?;
131
+ docs.push(indent(concat(vec![hardline(), body_doc])));
132
+ }
133
+ }
134
+
135
+ docs.push(hardline());
136
+ docs.push(text("end"));
137
+
138
+ // Trailing comment on end line
139
+ let end_trailing = format_trailing_comment(ctx, node.location.end_line);
140
+ if !end_trailing.is_empty() {
141
+ docs.push(end_trailing);
142
+ }
143
+
144
+ Ok(concat(docs))
145
+ }
146
+
147
+ /// Formats for loop
148
+ fn format_for(node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
149
+ let mut docs: Vec<Doc> = Vec::with_capacity(10);
150
+
151
+ // Leading comments
152
+ let leading = format_leading_comments(ctx, node.location.start_line);
153
+ if !leading.is_empty() {
154
+ docs.push(leading);
155
+ }
156
+
157
+ docs.push(text("for "));
158
+
159
+ // node.children: [index, collection, statements]
160
+ // index: LocalVariableTargetNode or MultiTargetNode
161
+ // collection: expression
162
+ // statements: StatementsNode
163
+
164
+ // Emit index variable - first child
165
+ if let Some(index) = node.children.first() {
166
+ if let Some(source_text) = ctx.extract_source(index) {
167
+ docs.push(text(source_text));
168
+ }
169
+ }
170
+
171
+ docs.push(text(" in "));
172
+
173
+ // Emit collection - second child
174
+ if let Some(collection) = node.children.get(1) {
175
+ if let Some(source_text) = ctx.extract_source(collection) {
176
+ docs.push(text(source_text));
177
+ }
178
+ }
179
+
180
+ // Emit body - third child (StatementsNode)
181
+ if let Some(body) = node.children.get(2) {
182
+ if matches!(body.node_type, NodeType::StatementsNode) {
183
+ let body_doc = format_statements(body, ctx, registry)?;
184
+ docs.push(indent(concat(vec![hardline(), body_doc])));
185
+ }
186
+ }
187
+
188
+ docs.push(hardline());
189
+ docs.push(text("end"));
190
+
191
+ // Trailing comment on end line
192
+ let trailing = format_trailing_comment(ctx, node.location.end_line);
193
+ if !trailing.is_empty() {
194
+ docs.push(trailing);
195
+ }
196
+
197
+ Ok(concat(docs))
198
+ }
199
+
200
+ #[cfg(test)]
201
+ mod tests {
202
+ use super::*;
203
+ use crate::ast::{FormattingInfo, Location};
204
+ use crate::config::Config;
205
+ use crate::doc::Printer;
206
+ use std::collections::HashMap;
207
+
208
+ fn make_while_node(
209
+ children: Vec<Node>,
210
+ start_line: usize,
211
+ end_line: usize,
212
+ start_offset: usize,
213
+ end_offset: usize,
214
+ ) -> Node {
215
+ Node {
216
+ node_type: NodeType::WhileNode,
217
+ location: Location::new(start_line, 0, end_line, 3, start_offset, end_offset),
218
+ children,
219
+ metadata: HashMap::new(),
220
+ comments: Vec::new(),
221
+ formatting: FormattingInfo::default(),
222
+ }
223
+ }
224
+
225
+ fn make_for_node(children: Vec<Node>, start_line: usize, end_line: usize) -> Node {
226
+ Node {
227
+ node_type: NodeType::ForNode,
228
+ location: Location::new(start_line, 0, end_line, 3, 0, 50),
229
+ children,
230
+ metadata: HashMap::new(),
231
+ comments: Vec::new(),
232
+ formatting: FormattingInfo::default(),
233
+ }
234
+ }
235
+
236
+ #[test]
237
+ fn test_simple_while() {
238
+ let config = Config::default();
239
+ let source = "while true\n puts 'loop'\nend";
240
+ let mut ctx = FormatContext::new(&config, source);
241
+ let registry = RuleRegistry::default_registry();
242
+
243
+ // predicate: "true" at offset 6-10
244
+ let predicate = Node {
245
+ node_type: NodeType::TrueNode,
246
+ location: Location::new(1, 6, 1, 10, 6, 10),
247
+ children: Vec::new(),
248
+ metadata: HashMap::new(),
249
+ comments: Vec::new(),
250
+ formatting: FormattingInfo::default(),
251
+ };
252
+
253
+ // body statements at offset 13-25
254
+ let body = Node {
255
+ node_type: NodeType::StatementsNode,
256
+ location: Location::new(2, 2, 2, 14, 13, 25),
257
+ children: Vec::new(),
258
+ metadata: HashMap::new(),
259
+ comments: Vec::new(),
260
+ formatting: FormattingInfo::default(),
261
+ };
262
+
263
+ let node = make_while_node(vec![predicate, body], 1, 3, 0, 29);
264
+ ctx.collect_comments(&node);
265
+
266
+ let rule = WhileRule;
267
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
268
+
269
+ let mut printer = Printer::new(&config);
270
+ let result = printer.print(&doc);
271
+
272
+ assert!(result.contains("while true"));
273
+ assert!(result.contains("end"));
274
+ }
275
+
276
+ #[test]
277
+ fn test_simple_for() {
278
+ let config = Config::default();
279
+ let source = "for x in items\n puts x\nend";
280
+ let mut ctx = FormatContext::new(&config, source);
281
+ let registry = RuleRegistry::default_registry();
282
+
283
+ // index: "x" at offset 4-5
284
+ let index = Node {
285
+ node_type: NodeType::LocalVariableReadNode,
286
+ location: Location::new(1, 4, 1, 5, 4, 5),
287
+ children: Vec::new(),
288
+ metadata: HashMap::new(),
289
+ comments: Vec::new(),
290
+ formatting: FormattingInfo::default(),
291
+ };
292
+
293
+ // collection: "items" at offset 9-14
294
+ let collection = Node {
295
+ node_type: NodeType::LocalVariableReadNode,
296
+ location: Location::new(1, 9, 1, 14, 9, 14),
297
+ children: Vec::new(),
298
+ metadata: HashMap::new(),
299
+ comments: Vec::new(),
300
+ formatting: FormattingInfo::default(),
301
+ };
302
+
303
+ // body
304
+ let body = Node {
305
+ node_type: NodeType::StatementsNode,
306
+ location: Location::new(2, 2, 2, 8, 17, 23),
307
+ children: Vec::new(),
308
+ metadata: HashMap::new(),
309
+ comments: Vec::new(),
310
+ formatting: FormattingInfo::default(),
311
+ };
312
+
313
+ let node = make_for_node(vec![index, collection, body], 1, 3);
314
+ ctx.collect_comments(&node);
315
+
316
+ let rule = ForRule;
317
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
318
+
319
+ let mut printer = Printer::new(&config);
320
+ let result = printer.print(&doc);
321
+
322
+ assert!(result.contains("for x in items"));
323
+ assert!(result.contains("end"));
324
+ }
325
+ }
@@ -0,0 +1,31 @@
1
+ //! Formatting rules for different AST node types
2
+ //!
3
+ //! This module contains the FormatRule implementations for each supported
4
+ //! node type. Each rule is responsible for converting its node type to Doc IR.
5
+
6
+ mod begin;
7
+ mod body_end;
8
+ mod call;
9
+ mod case;
10
+ mod class;
11
+ mod def;
12
+ mod fallback;
13
+ mod if_unless;
14
+ mod loops;
15
+ mod module;
16
+ mod singleton_class;
17
+ mod statements;
18
+ mod variable_write;
19
+
20
+ pub use begin::{BeginRule, EnsureRule, RescueRule};
21
+ pub use call::{BlockRule, CallRule, LambdaRule};
22
+ pub use case::{CaseMatchRule, CaseRule, InRule, WhenRule};
23
+ pub use class::ClassRule;
24
+ pub use def::DefRule;
25
+ pub use fallback::FallbackRule;
26
+ pub use if_unless::{IfRule, UnlessRule};
27
+ pub use loops::{ForRule, UntilRule, WhileRule};
28
+ pub use module::ModuleRule;
29
+ pub use singleton_class::SingletonClassRule;
30
+ pub use statements::StatementsRule;
31
+ pub use variable_write::{InstanceVariableWriteRule, LocalVariableWriteRule};
@@ -0,0 +1,150 @@
1
+ //! ModuleRule - Formats Ruby module definitions
2
+ //!
3
+ //! Handles module definitions including:
4
+ //! - Simple modules: `module Foo`
5
+ //! - Nested modules: `module Foo::Bar`
6
+ //! - Module 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 module definitions.
19
+ pub struct ModuleRule;
20
+
21
+ impl FormatRule for ModuleRule {
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: "module",
28
+ node,
29
+ header_builder: Box::new(build_module_header),
30
+ skip_same_line_children: false,
31
+ },
32
+ )
33
+ }
34
+ }
35
+
36
+ /// Builds the header portion for a module definition.
37
+ ///
38
+ /// Returns: `ModuleName`
39
+ fn build_module_header(node: &Node) -> Vec<Doc> {
40
+ let mut parts: Vec<Doc> = Vec::with_capacity(1);
41
+
42
+ // Get module name from metadata
43
+ if let Some(name) = node.metadata.get("name") {
44
+ parts.push(text(name));
45
+ }
46
+
47
+ parts
48
+ }
49
+
50
+ #[cfg(test)]
51
+ mod tests {
52
+ use super::*;
53
+ use crate::ast::{FormattingInfo, Location, NodeType};
54
+ use crate::config::Config;
55
+ use crate::doc::Printer;
56
+ use std::collections::HashMap;
57
+
58
+ fn make_module_node(
59
+ name: &str,
60
+ children: Vec<Node>,
61
+ start_line: usize,
62
+ end_line: usize,
63
+ ) -> Node {
64
+ let mut metadata = HashMap::new();
65
+ metadata.insert("name".to_string(), name.to_string());
66
+
67
+ Node {
68
+ node_type: NodeType::ModuleNode,
69
+ location: Location::new(start_line, 0, end_line, 3, 0, 50),
70
+ children,
71
+ metadata,
72
+ comments: Vec::new(),
73
+ formatting: FormattingInfo::default(),
74
+ }
75
+ }
76
+
77
+ #[test]
78
+ fn test_simple_module() {
79
+ let config = Config::default();
80
+ let source = "module Foo\nend";
81
+ let mut ctx = FormatContext::new(&config, source);
82
+ let registry = RuleRegistry::default_registry();
83
+
84
+ let node = make_module_node("Foo", Vec::new(), 1, 2);
85
+ ctx.collect_comments(&node);
86
+
87
+ let rule = ModuleRule;
88
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
89
+
90
+ let mut printer = Printer::new(&config);
91
+ let result = printer.print(&doc);
92
+
93
+ assert_eq!(result.trim(), "module Foo\nend");
94
+ }
95
+
96
+ #[test]
97
+ fn test_nested_module_name() {
98
+ let config = Config::default();
99
+ let source = "module Foo::Bar\nend";
100
+ let mut ctx = FormatContext::new(&config, source);
101
+ let registry = RuleRegistry::default_registry();
102
+
103
+ let node = make_module_node("Foo::Bar", Vec::new(), 1, 2);
104
+ ctx.collect_comments(&node);
105
+
106
+ let rule = ModuleRule;
107
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
108
+
109
+ let mut printer = Printer::new(&config);
110
+ let result = printer.print(&doc);
111
+
112
+ assert_eq!(result.trim(), "module Foo::Bar\nend");
113
+ }
114
+
115
+ #[test]
116
+ fn test_module_with_body() {
117
+ let config = Config::default();
118
+ let source = "module Foo\n def bar\n end\nend";
119
+ let mut ctx = FormatContext::new(&config, source);
120
+ let registry = RuleRegistry::default_registry();
121
+
122
+ // Create a method node as child
123
+ let method_node = Node {
124
+ node_type: NodeType::DefNode,
125
+ location: Location::new(2, 2, 3, 5, 13, 25),
126
+ children: Vec::new(),
127
+ metadata: {
128
+ let mut m = HashMap::new();
129
+ m.insert("name".to_string(), "bar".to_string());
130
+ m
131
+ },
132
+ comments: Vec::new(),
133
+ formatting: FormattingInfo::default(),
134
+ };
135
+
136
+ let node = make_module_node("Foo", vec![method_node], 1, 4);
137
+ ctx.collect_comments(&node);
138
+
139
+ let rule = ModuleRule;
140
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
141
+
142
+ let mut printer = Printer::new(&config);
143
+ let result = printer.print(&doc);
144
+
145
+ // Should have the module, body, and end
146
+ assert!(result.contains("module Foo"));
147
+ assert!(result.contains("def bar"));
148
+ assert!(result.contains("end"));
149
+ }
150
+ }
@@ -0,0 +1,202 @@
1
+ //! SingletonClassRule - Formats Ruby singleton class definitions
2
+ //!
3
+ //! Handles singleton class definitions:
4
+ //! - `class << self ... end`
5
+ //! - `class << object ... end`
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_child, format_comments_before_end, format_leading_comments, format_statements,
14
+ format_trailing_comment, FormatRule,
15
+ };
16
+
17
+ /// Rule for formatting singleton class definitions.
18
+ ///
19
+ /// Handles `class << self` and `class << object` patterns.
20
+ pub struct SingletonClassRule;
21
+
22
+ impl FormatRule for SingletonClassRule {
23
+ fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
24
+ let mut docs: Vec<Doc> = Vec::with_capacity(8);
25
+
26
+ let start_line = node.location.start_line;
27
+ let end_line = node.location.end_line;
28
+
29
+ // 1. Leading comments before definition
30
+ let leading = format_leading_comments(ctx, start_line);
31
+ if !leading.is_empty() {
32
+ docs.push(leading);
33
+ }
34
+
35
+ // 2. Build header: "class << "
36
+ docs.push(text("class << "));
37
+
38
+ // 3. First child is the expression (self or an object)
39
+ if let Some(expression) = node.children.first() {
40
+ if let Some(expr_text) = ctx.extract_source(expression) {
41
+ docs.push(text(expr_text.to_string()));
42
+ }
43
+ }
44
+
45
+ // 4. Trailing comment on definition line
46
+ let trailing = format_trailing_comment(ctx, start_line);
47
+ if !trailing.is_empty() {
48
+ docs.push(trailing);
49
+ }
50
+
51
+ // 5. Body (children), skipping the first child (expression)
52
+ let mut body_docs: Vec<Doc> = Vec::new();
53
+ let mut has_body_content = false;
54
+
55
+ for (i, child) in node.children.iter().enumerate() {
56
+ // Skip the first child (expression: self or object)
57
+ if i == 0 {
58
+ continue;
59
+ }
60
+
61
+ if matches!(child.node_type, NodeType::StatementsNode) {
62
+ has_body_content = true;
63
+ body_docs.push(hardline());
64
+ body_docs.push(format_statements(child, ctx, registry)?);
65
+ } else if !is_structural_node_for_singleton(child) {
66
+ has_body_content = true;
67
+ body_docs.push(hardline());
68
+ body_docs.push(format_child(child, ctx, registry)?);
69
+ }
70
+ }
71
+
72
+ if has_body_content {
73
+ docs.push(indent(concat(body_docs)));
74
+ }
75
+
76
+ // 6. Comments before end
77
+ let comments_before_end = format_comments_before_end(ctx, start_line, end_line);
78
+ if !comments_before_end.is_empty() {
79
+ docs.push(indent(comments_before_end));
80
+ }
81
+
82
+ // 7. Add newline before end
83
+ docs.push(hardline());
84
+
85
+ // 8. End keyword
86
+ docs.push(text("end"));
87
+
88
+ // 9. Trailing comment on end line
89
+ let end_trailing = format_trailing_comment(ctx, end_line);
90
+ if !end_trailing.is_empty() {
91
+ docs.push(end_trailing);
92
+ }
93
+
94
+ Ok(concat(docs))
95
+ }
96
+ }
97
+
98
+ /// Check if a node is structural (should be skipped in body).
99
+ fn is_structural_node_for_singleton(node: &Node) -> bool {
100
+ matches!(
101
+ node.node_type,
102
+ NodeType::ConstantReadNode
103
+ | NodeType::ConstantWriteNode
104
+ | NodeType::ConstantPathNode
105
+ | NodeType::SelfNode
106
+ )
107
+ }
108
+
109
+ #[cfg(test)]
110
+ mod tests {
111
+ use super::*;
112
+ use crate::ast::{FormattingInfo, Location};
113
+ use crate::config::Config;
114
+ use crate::doc::Printer;
115
+ use std::collections::HashMap;
116
+
117
+ fn make_singleton_class_node(children: Vec<Node>, start_line: usize, end_line: usize) -> Node {
118
+ Node {
119
+ node_type: NodeType::SingletonClassNode,
120
+ location: Location::new(start_line, 0, end_line, 3, 0, 50),
121
+ children,
122
+ metadata: HashMap::new(),
123
+ comments: Vec::new(),
124
+ formatting: FormattingInfo::default(),
125
+ }
126
+ }
127
+
128
+ fn make_self_node(line: usize, start_offset: usize) -> Node {
129
+ Node {
130
+ node_type: NodeType::SelfNode,
131
+ location: Location::new(line, 9, line, 13, start_offset, start_offset + 4),
132
+ children: Vec::new(),
133
+ metadata: HashMap::new(),
134
+ comments: Vec::new(),
135
+ formatting: FormattingInfo::default(),
136
+ }
137
+ }
138
+
139
+ #[test]
140
+ fn test_simple_singleton_class() {
141
+ let config = Config::default();
142
+ let source = "class << self\nend";
143
+ let mut ctx = FormatContext::new(&config, source);
144
+ let registry = RuleRegistry::default_registry();
145
+
146
+ let self_node = make_self_node(1, 9);
147
+ let node = make_singleton_class_node(vec![self_node], 1, 2);
148
+ ctx.collect_comments(&node);
149
+
150
+ let rule = SingletonClassRule;
151
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
152
+
153
+ let mut printer = Printer::new(&config);
154
+ let result = printer.print(&doc);
155
+
156
+ assert_eq!(result.trim(), "class << self\nend");
157
+ }
158
+
159
+ #[test]
160
+ fn test_singleton_class_with_body() {
161
+ let config = Config::default();
162
+ let source = "class << self\n def foo\n end\nend";
163
+ let mut ctx = FormatContext::new(&config, source);
164
+ let registry = RuleRegistry::default_registry();
165
+
166
+ let self_node = make_self_node(1, 9);
167
+ let method_node = Node {
168
+ node_type: NodeType::DefNode,
169
+ location: Location::new(2, 2, 3, 5, 16, 28),
170
+ children: Vec::new(),
171
+ metadata: {
172
+ let mut m = HashMap::new();
173
+ m.insert("name".to_string(), "foo".to_string());
174
+ m
175
+ },
176
+ comments: Vec::new(),
177
+ formatting: FormattingInfo::default(),
178
+ };
179
+
180
+ let statements_node = Node {
181
+ node_type: NodeType::StatementsNode,
182
+ location: Location::new(2, 2, 3, 5, 16, 28),
183
+ children: vec![method_node],
184
+ metadata: HashMap::new(),
185
+ comments: Vec::new(),
186
+ formatting: FormattingInfo::default(),
187
+ };
188
+
189
+ let node = make_singleton_class_node(vec![self_node, statements_node], 1, 4);
190
+ ctx.collect_comments(&node);
191
+
192
+ let rule = SingletonClassRule;
193
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
194
+
195
+ let mut printer = Printer::new(&config);
196
+ let result = printer.print(&doc);
197
+
198
+ assert!(result.contains("class << self"));
199
+ assert!(result.contains("def foo"));
200
+ assert!(result.contains("end"));
201
+ }
202
+ }