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,216 @@
1
+ //! DefRule - Formats Ruby method definitions
2
+ //!
3
+ //! Handles method definitions including:
4
+ //! - Instance methods: `def foo`
5
+ //! - Class methods: `def self.foo`
6
+ //! - Methods with parameters: `def foo(x, y)` or `def foo x, y`
7
+ //! - Method bodies
8
+ //! - Leading and trailing comments
9
+
10
+ use crate::ast::Node;
11
+ use crate::doc::{text, Doc};
12
+ use crate::error::Result;
13
+ use crate::format::context::FormatContext;
14
+ use crate::format::registry::RuleRegistry;
15
+ use crate::format::rule::FormatRule;
16
+
17
+ use super::body_end::{format_body_end, BodyEndConfig};
18
+
19
+ /// Rule for formatting method definitions.
20
+ pub struct DefRule;
21
+
22
+ impl FormatRule for DefRule {
23
+ fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
24
+ format_body_end(
25
+ ctx,
26
+ registry,
27
+ BodyEndConfig {
28
+ keyword: "def",
29
+ node,
30
+ header_builder: Box::new(build_def_header),
31
+ skip_same_line_children: false,
32
+ },
33
+ )
34
+ }
35
+ }
36
+
37
+ /// Builds the header portion for a method definition.
38
+ ///
39
+ /// Returns: `[receiver.]name[(params)]`
40
+ fn build_def_header(node: &Node) -> Vec<Doc> {
41
+ let mut parts: Vec<Doc> = Vec::with_capacity(6);
42
+
43
+ // Get receiver if class method (def self.method_name)
44
+ if let Some(receiver) = node.metadata.get("receiver") {
45
+ parts.push(text(receiver));
46
+ parts.push(text("."));
47
+ }
48
+
49
+ // Get method name from metadata
50
+ if let Some(name) = node.metadata.get("name") {
51
+ parts.push(text(name));
52
+ }
53
+
54
+ // Get parameters from metadata
55
+ if let Some(params_text) = node.metadata.get("parameters_text") {
56
+ let has_parens = node.metadata.get("has_parens").is_some_and(|v| v == "true");
57
+
58
+ if has_parens {
59
+ parts.push(text("("));
60
+ parts.push(text(params_text));
61
+ parts.push(text(")"));
62
+ } else {
63
+ parts.push(text(" "));
64
+ parts.push(text(params_text));
65
+ }
66
+ }
67
+
68
+ parts
69
+ }
70
+
71
+ #[cfg(test)]
72
+ mod tests {
73
+ use super::*;
74
+ use crate::ast::{FormattingInfo, Location, NodeType};
75
+ use crate::config::Config;
76
+ use crate::doc::Printer;
77
+ use std::collections::HashMap;
78
+
79
+ fn make_def_node(
80
+ name: &str,
81
+ receiver: Option<&str>,
82
+ params: Option<(&str, bool)>,
83
+ children: Vec<Node>,
84
+ start_line: usize,
85
+ end_line: usize,
86
+ ) -> Node {
87
+ let mut metadata = HashMap::new();
88
+ metadata.insert("name".to_string(), name.to_string());
89
+
90
+ if let Some(r) = receiver {
91
+ metadata.insert("receiver".to_string(), r.to_string());
92
+ }
93
+
94
+ if let Some((p, has_parens)) = params {
95
+ metadata.insert("parameters_text".to_string(), p.to_string());
96
+ metadata.insert("has_parens".to_string(), has_parens.to_string());
97
+ }
98
+
99
+ Node {
100
+ node_type: NodeType::DefNode,
101
+ location: Location::new(start_line, 0, end_line, 3, 0, 50),
102
+ children,
103
+ metadata,
104
+ comments: Vec::new(),
105
+ formatting: FormattingInfo::default(),
106
+ }
107
+ }
108
+
109
+ #[test]
110
+ fn test_simple_def() {
111
+ let config = Config::default();
112
+ let source = "def foo\nend";
113
+ let mut ctx = FormatContext::new(&config, source);
114
+ let registry = RuleRegistry::default_registry();
115
+
116
+ let node = make_def_node("foo", None, None, Vec::new(), 1, 2);
117
+ ctx.collect_comments(&node);
118
+
119
+ let rule = DefRule;
120
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
121
+
122
+ let mut printer = Printer::new(&config);
123
+ let result = printer.print(&doc);
124
+
125
+ assert_eq!(result.trim(), "def foo\nend");
126
+ }
127
+
128
+ #[test]
129
+ fn test_def_with_parens_params() {
130
+ let config = Config::default();
131
+ let source = "def foo(x, y)\nend";
132
+ let mut ctx = FormatContext::new(&config, source);
133
+ let registry = RuleRegistry::default_registry();
134
+
135
+ let node = make_def_node("foo", None, Some(("x, y", true)), Vec::new(), 1, 2);
136
+ ctx.collect_comments(&node);
137
+
138
+ let rule = DefRule;
139
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
140
+
141
+ let mut printer = Printer::new(&config);
142
+ let result = printer.print(&doc);
143
+
144
+ assert_eq!(result.trim(), "def foo(x, y)\nend");
145
+ }
146
+
147
+ #[test]
148
+ fn test_def_without_parens_params() {
149
+ let config = Config::default();
150
+ let source = "def foo x, y\nend";
151
+ let mut ctx = FormatContext::new(&config, source);
152
+ let registry = RuleRegistry::default_registry();
153
+
154
+ let node = make_def_node("foo", None, Some(("x, y", false)), Vec::new(), 1, 2);
155
+ ctx.collect_comments(&node);
156
+
157
+ let rule = DefRule;
158
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
159
+
160
+ let mut printer = Printer::new(&config);
161
+ let result = printer.print(&doc);
162
+
163
+ assert_eq!(result.trim(), "def foo x, y\nend");
164
+ }
165
+
166
+ #[test]
167
+ fn test_class_method() {
168
+ let config = Config::default();
169
+ let source = "def self.foo\nend";
170
+ let mut ctx = FormatContext::new(&config, source);
171
+ let registry = RuleRegistry::default_registry();
172
+
173
+ let node = make_def_node("foo", Some("self"), None, Vec::new(), 1, 2);
174
+ ctx.collect_comments(&node);
175
+
176
+ let rule = DefRule;
177
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
178
+
179
+ let mut printer = Printer::new(&config);
180
+ let result = printer.print(&doc);
181
+
182
+ assert_eq!(result.trim(), "def self.foo\nend");
183
+ }
184
+
185
+ #[test]
186
+ fn test_def_with_body() {
187
+ let config = Config::default();
188
+ let source = "def foo\n puts 'hello'\nend";
189
+ let mut ctx = FormatContext::new(&config, source);
190
+ let registry = RuleRegistry::default_registry();
191
+
192
+ // Create a body node
193
+ let body_node = Node {
194
+ node_type: NodeType::CallNode,
195
+ location: Location::new(2, 2, 2, 15, 10, 23),
196
+ children: Vec::new(),
197
+ metadata: HashMap::new(),
198
+ comments: Vec::new(),
199
+ formatting: FormattingInfo::default(),
200
+ };
201
+
202
+ let node = make_def_node("foo", None, None, vec![body_node], 1, 3);
203
+ ctx.collect_comments(&node);
204
+
205
+ let rule = DefRule;
206
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
207
+
208
+ let mut printer = Printer::new(&config);
209
+ let result = printer.print(&doc);
210
+
211
+ // Should have the method, body, and end
212
+ assert!(result.contains("def foo"));
213
+ assert!(result.contains("puts 'hello'"));
214
+ assert!(result.contains("end"));
215
+ }
216
+ }
@@ -0,0 +1,116 @@
1
+ //! FallbackRule - Handles nodes without specific rules
2
+ //!
3
+ //! The fallback rule extracts source text directly, similar to the
4
+ //! existing Emitter's emit_generic function. This provides a safety
5
+ //! net for node types that haven't been implemented yet.
6
+
7
+ use crate::ast::Node;
8
+ use crate::doc::{concat, 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_trailing_comment, mark_comments_in_range_emitted,
14
+ reformat_chain_lines, FormatRule,
15
+ };
16
+
17
+ /// Fallback rule that extracts source text directly.
18
+ ///
19
+ /// This rule is used when no specific rule is registered for a node type.
20
+ /// It extracts the original source text and preserves it as-is, handling
21
+ /// leading and trailing comments.
22
+ pub struct FallbackRule;
23
+
24
+ impl FormatRule for FallbackRule {
25
+ fn format(
26
+ &self,
27
+ node: &Node,
28
+ ctx: &mut FormatContext,
29
+ _registry: &RuleRegistry,
30
+ ) -> Result<Doc> {
31
+ let mut docs: Vec<Doc> = Vec::with_capacity(3);
32
+
33
+ // Add leading comments
34
+ let leading = format_leading_comments(ctx, node.location.start_line);
35
+ if !leading.is_empty() {
36
+ docs.push(leading);
37
+ }
38
+
39
+ // Extract source text with chain reformatting
40
+ if let Some(source_text) = ctx.extract_source(node) {
41
+ let reformatted =
42
+ reformat_chain_lines(source_text, ctx.config().formatting.indent_width);
43
+ docs.push(text(reformatted));
44
+
45
+ // Mark any comments within this node's range as emitted
46
+ // (they are included in the source extraction)
47
+ mark_comments_in_range_emitted(ctx, node.location.start_line, node.location.end_line);
48
+ }
49
+
50
+ // Add trailing comment
51
+ let trailing = format_trailing_comment(ctx, node.location.end_line);
52
+ if !trailing.is_empty() {
53
+ docs.push(trailing);
54
+ }
55
+
56
+ Ok(concat(docs))
57
+ }
58
+ }
59
+
60
+ #[cfg(test)]
61
+ mod tests {
62
+ use super::*;
63
+ use crate::ast::{FormattingInfo, Location, NodeType};
64
+ use crate::config::Config;
65
+ use std::collections::HashMap;
66
+
67
+ #[test]
68
+ fn test_fallback_extracts_source() {
69
+ let config = Config::default();
70
+ let source = "puts 'hello'";
71
+ let mut ctx = FormatContext::new(&config, source);
72
+ let registry = RuleRegistry::default_registry();
73
+
74
+ let node = Node {
75
+ node_type: NodeType::CallNode,
76
+ location: Location::new(1, 0, 1, 12, 0, 12),
77
+ children: Vec::new(),
78
+ metadata: HashMap::new(),
79
+ comments: Vec::new(),
80
+ formatting: FormattingInfo::default(),
81
+ };
82
+
83
+ ctx.collect_comments(&node);
84
+
85
+ let rule = FallbackRule;
86
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
87
+
88
+ // The doc should contain the source text
89
+ assert!(matches!(doc, Doc::Text(s) if s == "puts 'hello'"));
90
+ }
91
+
92
+ #[test]
93
+ fn test_fallback_handles_empty_source() {
94
+ let config = Config::default();
95
+ let source = "";
96
+ let mut ctx = FormatContext::new(&config, source);
97
+ let registry = RuleRegistry::default_registry();
98
+
99
+ let node = Node {
100
+ node_type: NodeType::CallNode,
101
+ location: Location::new(1, 0, 1, 12, 0, 12),
102
+ children: Vec::new(),
103
+ metadata: HashMap::new(),
104
+ comments: Vec::new(),
105
+ formatting: FormattingInfo::default(),
106
+ };
107
+
108
+ ctx.collect_comments(&node);
109
+
110
+ let rule = FallbackRule;
111
+ let doc = rule.format(&node, &mut ctx, &registry).unwrap();
112
+
113
+ // Should handle gracefully
114
+ assert!(matches!(doc, Doc::Empty));
115
+ }
116
+ }