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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -0
- data/Cargo.lock +266 -92
- data/README.md +22 -18
- data/ext/rfmt/Cargo.toml +4 -1
- data/ext/rfmt/src/doc/builders.rs +528 -0
- data/ext/rfmt/src/doc/mod.rs +220 -0
- data/ext/rfmt/src/doc/printer.rs +684 -0
- data/ext/rfmt/src/format/context.rs +448 -0
- data/ext/rfmt/src/format/formatter.rs +226 -0
- data/ext/rfmt/src/format/mod.rs +35 -0
- data/ext/rfmt/src/format/registry.rs +195 -0
- data/ext/rfmt/src/format/rule.rs +555 -0
- data/ext/rfmt/src/format/rules/begin.rs +295 -0
- data/ext/rfmt/src/format/rules/body_end.rs +109 -0
- data/ext/rfmt/src/format/rules/call.rs +409 -0
- data/ext/rfmt/src/format/rules/case.rs +359 -0
- data/ext/rfmt/src/format/rules/class.rs +160 -0
- data/ext/rfmt/src/format/rules/def.rs +216 -0
- data/ext/rfmt/src/format/rules/fallback.rs +116 -0
- data/ext/rfmt/src/format/rules/if_unless.rs +407 -0
- data/ext/rfmt/src/format/rules/loops.rs +325 -0
- data/ext/rfmt/src/format/rules/mod.rs +31 -0
- data/ext/rfmt/src/format/rules/module.rs +150 -0
- data/ext/rfmt/src/format/rules/singleton_class.rs +202 -0
- data/ext/rfmt/src/format/rules/statements.rs +122 -0
- data/ext/rfmt/src/format/rules/variable_write.rs +296 -0
- data/ext/rfmt/src/lib.rs +8 -5
- data/ext/rfmt/src/parser/prism_adapter.rs +157 -2
- data/lib/rfmt/version.rb +1 -1
- data/lib/ruby_lsp/rfmt/formatter_runner.rb +2 -0
- metadata +23 -2
- data/ext/rfmt/src/emitter/mod.rs +0 -1760
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
//! StatementsRule - Handles StatementsNode (body of class/module/def)
|
|
2
|
+
//!
|
|
3
|
+
//! StatementsNode contains a sequence of statements that form the body
|
|
4
|
+
//! of a construct. This rule formats each statement with proper spacing.
|
|
5
|
+
|
|
6
|
+
use crate::ast::Node;
|
|
7
|
+
use crate::doc::Doc;
|
|
8
|
+
use crate::error::Result;
|
|
9
|
+
use crate::format::context::FormatContext;
|
|
10
|
+
use crate::format::registry::RuleRegistry;
|
|
11
|
+
use crate::format::rule::{format_statements, FormatRule};
|
|
12
|
+
|
|
13
|
+
/// Rule for formatting StatementsNode.
|
|
14
|
+
///
|
|
15
|
+
/// This rule delegates to the shared `format_statements` helper function
|
|
16
|
+
/// which properly handles the sequence of child statements.
|
|
17
|
+
pub struct StatementsRule;
|
|
18
|
+
|
|
19
|
+
impl FormatRule for StatementsRule {
|
|
20
|
+
fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
|
|
21
|
+
format_statements(node, ctx, registry)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#[cfg(test)]
|
|
26
|
+
mod tests {
|
|
27
|
+
use super::*;
|
|
28
|
+
use crate::ast::{FormattingInfo, Location, NodeType};
|
|
29
|
+
use crate::config::Config;
|
|
30
|
+
use crate::doc::Printer;
|
|
31
|
+
use std::collections::HashMap;
|
|
32
|
+
|
|
33
|
+
fn make_statements_node(children: Vec<Node>, start_line: usize, end_line: usize) -> Node {
|
|
34
|
+
Node {
|
|
35
|
+
node_type: NodeType::StatementsNode,
|
|
36
|
+
location: Location::new(start_line, 0, end_line, 0, 0, 100),
|
|
37
|
+
children,
|
|
38
|
+
metadata: HashMap::new(),
|
|
39
|
+
comments: Vec::new(),
|
|
40
|
+
formatting: FormattingInfo::default(),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn make_call_node(source: &str, line: usize, start_offset: usize) -> Node {
|
|
45
|
+
Node {
|
|
46
|
+
node_type: NodeType::CallNode,
|
|
47
|
+
location: Location::new(
|
|
48
|
+
line,
|
|
49
|
+
0,
|
|
50
|
+
line,
|
|
51
|
+
source.len(),
|
|
52
|
+
start_offset,
|
|
53
|
+
start_offset + source.len(),
|
|
54
|
+
),
|
|
55
|
+
children: Vec::new(),
|
|
56
|
+
metadata: HashMap::new(),
|
|
57
|
+
comments: Vec::new(),
|
|
58
|
+
formatting: FormattingInfo::default(),
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#[test]
|
|
63
|
+
fn test_statements_single_statement() {
|
|
64
|
+
let config = Config::default();
|
|
65
|
+
let source = "puts 'hello'";
|
|
66
|
+
let mut ctx = FormatContext::new(&config, source);
|
|
67
|
+
let registry = RuleRegistry::default_registry();
|
|
68
|
+
|
|
69
|
+
let call = make_call_node("puts 'hello'", 1, 0);
|
|
70
|
+
let stmt = make_statements_node(vec![call], 1, 1);
|
|
71
|
+
|
|
72
|
+
ctx.collect_comments(&stmt);
|
|
73
|
+
|
|
74
|
+
let rule = StatementsRule;
|
|
75
|
+
let doc = rule.format(&stmt, &mut ctx, ®istry).unwrap();
|
|
76
|
+
|
|
77
|
+
let mut printer = Printer::new(&config);
|
|
78
|
+
let result = printer.print(&doc);
|
|
79
|
+
|
|
80
|
+
assert_eq!(result, "puts 'hello'\n");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#[test]
|
|
84
|
+
fn test_statements_multiple_statements() {
|
|
85
|
+
let config = Config::default();
|
|
86
|
+
let source = "a = 1\nb = 2";
|
|
87
|
+
let mut ctx = FormatContext::new(&config, source);
|
|
88
|
+
let registry = RuleRegistry::default_registry();
|
|
89
|
+
|
|
90
|
+
let call1 = make_call_node("a = 1", 1, 0);
|
|
91
|
+
let call2 = make_call_node("b = 2", 2, 6);
|
|
92
|
+
let stmt = make_statements_node(vec![call1, call2], 1, 2);
|
|
93
|
+
|
|
94
|
+
ctx.collect_comments(&stmt);
|
|
95
|
+
|
|
96
|
+
let rule = StatementsRule;
|
|
97
|
+
let doc = rule.format(&stmt, &mut ctx, ®istry).unwrap();
|
|
98
|
+
|
|
99
|
+
let mut printer = Printer::new(&config);
|
|
100
|
+
let result = printer.print(&doc);
|
|
101
|
+
|
|
102
|
+
assert!(result.contains("a = 1"));
|
|
103
|
+
assert!(result.contains("b = 2"));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#[test]
|
|
107
|
+
fn test_statements_empty() {
|
|
108
|
+
let config = Config::default();
|
|
109
|
+
let source = "";
|
|
110
|
+
let mut ctx = FormatContext::new(&config, source);
|
|
111
|
+
let registry = RuleRegistry::default_registry();
|
|
112
|
+
|
|
113
|
+
let stmt = make_statements_node(vec![], 1, 1);
|
|
114
|
+
|
|
115
|
+
ctx.collect_comments(&stmt);
|
|
116
|
+
|
|
117
|
+
let rule = StatementsRule;
|
|
118
|
+
let doc = rule.format(&stmt, &mut ctx, ®istry).unwrap();
|
|
119
|
+
|
|
120
|
+
assert!(matches!(doc, Doc::Empty));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
//! VariableWriteRule - Formats Ruby variable assignment expressions
|
|
2
|
+
//!
|
|
3
|
+
//! Handles variable assignments including:
|
|
4
|
+
//! - Local variable writes: `x = value`
|
|
5
|
+
//! - Instance variable writes: `@x = value`
|
|
6
|
+
//! - Block value assignments: `x = if true then 1 else 2 end`
|
|
7
|
+
//! - Multiline method chain assignments: `x = foo.bar.baz`
|
|
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_child, format_leading_comments, format_trailing_comment, reformat_chain_lines,
|
|
16
|
+
FormatRule,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/// Rule for formatting local variable write expressions.
|
|
20
|
+
pub struct LocalVariableWriteRule;
|
|
21
|
+
|
|
22
|
+
impl FormatRule for LocalVariableWriteRule {
|
|
23
|
+
fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
|
|
24
|
+
format_variable_write(node, ctx, registry)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Rule for formatting instance variable write expressions.
|
|
29
|
+
pub struct InstanceVariableWriteRule;
|
|
30
|
+
|
|
31
|
+
impl FormatRule for InstanceVariableWriteRule {
|
|
32
|
+
fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
|
|
33
|
+
format_variable_write(node, ctx, registry)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Shared implementation for variable write formatting.
|
|
38
|
+
///
|
|
39
|
+
/// Handles three cases:
|
|
40
|
+
/// 1. Block value (if/unless/case/begin/while/until/for): formats on new line with indent
|
|
41
|
+
/// 2. Multiline method chain: reformats with indented chain style
|
|
42
|
+
/// 3. Simple value: inline assignment
|
|
43
|
+
fn format_variable_write(
|
|
44
|
+
node: &Node,
|
|
45
|
+
ctx: &mut FormatContext,
|
|
46
|
+
registry: &RuleRegistry,
|
|
47
|
+
) -> Result<Doc> {
|
|
48
|
+
let mut docs: Vec<Doc> = Vec::with_capacity(5);
|
|
49
|
+
|
|
50
|
+
let start_line = node.location.start_line;
|
|
51
|
+
let end_line = node.location.end_line;
|
|
52
|
+
|
|
53
|
+
// 1. Leading comments
|
|
54
|
+
let leading = format_leading_comments(ctx, start_line);
|
|
55
|
+
if !leading.is_empty() {
|
|
56
|
+
docs.push(leading);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 2. Get variable name from metadata
|
|
60
|
+
let name = node.metadata.get("name").map(|s| s.as_str()).unwrap_or("_");
|
|
61
|
+
|
|
62
|
+
// 3. Get value node (first child)
|
|
63
|
+
let value = match node.children.first() {
|
|
64
|
+
Some(v) => v,
|
|
65
|
+
None => {
|
|
66
|
+
// No value: fallback to source extraction
|
|
67
|
+
if let Some(source_text) = ctx.extract_source(node) {
|
|
68
|
+
docs.push(text(source_text.to_string()));
|
|
69
|
+
}
|
|
70
|
+
let trailing = format_trailing_comment(ctx, end_line);
|
|
71
|
+
if !trailing.is_empty() {
|
|
72
|
+
docs.push(trailing);
|
|
73
|
+
}
|
|
74
|
+
return Ok(concat(docs));
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// 4. Check if value is a block construct
|
|
79
|
+
let is_block_value = matches!(
|
|
80
|
+
value.node_type,
|
|
81
|
+
NodeType::IfNode
|
|
82
|
+
| NodeType::UnlessNode
|
|
83
|
+
| NodeType::CaseNode
|
|
84
|
+
| NodeType::CaseMatchNode
|
|
85
|
+
| NodeType::BeginNode
|
|
86
|
+
| NodeType::WhileNode
|
|
87
|
+
| NodeType::UntilNode
|
|
88
|
+
| NodeType::ForNode
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if is_block_value {
|
|
92
|
+
// Block value: format on new line with indent
|
|
93
|
+
// x =
|
|
94
|
+
// if true
|
|
95
|
+
// 1
|
|
96
|
+
// else
|
|
97
|
+
// 2
|
|
98
|
+
// end
|
|
99
|
+
docs.push(text(format!("{} =", name)));
|
|
100
|
+
docs.push(indent(concat(vec![
|
|
101
|
+
hardline(),
|
|
102
|
+
format_child(value, ctx, registry)?,
|
|
103
|
+
])));
|
|
104
|
+
} else {
|
|
105
|
+
// Check for multiline method chain
|
|
106
|
+
let is_multiline_call = matches!(value.node_type, NodeType::CallNode)
|
|
107
|
+
&& value.location.start_line != value.location.end_line;
|
|
108
|
+
|
|
109
|
+
docs.push(text(format!("{} = ", name)));
|
|
110
|
+
|
|
111
|
+
if is_multiline_call {
|
|
112
|
+
// Multiline call: reformat chain with indented style
|
|
113
|
+
if let Some(source_text) = ctx.extract_source(value) {
|
|
114
|
+
let reformatted =
|
|
115
|
+
reformat_chain_lines(source_text, ctx.config().formatting.indent_width);
|
|
116
|
+
docs.push(text(reformatted.trim_start().to_string()));
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
// Simple value: extract from source trimmed
|
|
120
|
+
if let Some(source_text) = ctx.extract_source(value) {
|
|
121
|
+
docs.push(text(source_text.trim().to_string()));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 5. Trailing comment
|
|
127
|
+
let trailing = format_trailing_comment(ctx, end_line);
|
|
128
|
+
if !trailing.is_empty() {
|
|
129
|
+
docs.push(trailing);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
Ok(concat(docs))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[cfg(test)]
|
|
136
|
+
mod tests {
|
|
137
|
+
use super::*;
|
|
138
|
+
use crate::ast::{FormattingInfo, Location};
|
|
139
|
+
use crate::config::Config;
|
|
140
|
+
use crate::doc::Printer;
|
|
141
|
+
use std::collections::HashMap;
|
|
142
|
+
|
|
143
|
+
fn make_local_var_write_node(
|
|
144
|
+
name: &str,
|
|
145
|
+
children: Vec<Node>,
|
|
146
|
+
start_line: usize,
|
|
147
|
+
end_line: usize,
|
|
148
|
+
start_offset: usize,
|
|
149
|
+
end_offset: usize,
|
|
150
|
+
) -> Node {
|
|
151
|
+
let mut metadata = HashMap::new();
|
|
152
|
+
metadata.insert("name".to_string(), name.to_string());
|
|
153
|
+
|
|
154
|
+
Node {
|
|
155
|
+
node_type: NodeType::LocalVariableWriteNode,
|
|
156
|
+
location: Location::new(start_line, 0, end_line, 10, start_offset, end_offset),
|
|
157
|
+
children,
|
|
158
|
+
metadata,
|
|
159
|
+
comments: Vec::new(),
|
|
160
|
+
formatting: FormattingInfo::default(),
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fn make_instance_var_write_node(
|
|
165
|
+
name: &str,
|
|
166
|
+
children: Vec<Node>,
|
|
167
|
+
start_line: usize,
|
|
168
|
+
end_line: usize,
|
|
169
|
+
start_offset: usize,
|
|
170
|
+
end_offset: usize,
|
|
171
|
+
) -> Node {
|
|
172
|
+
let mut metadata = HashMap::new();
|
|
173
|
+
metadata.insert("name".to_string(), name.to_string());
|
|
174
|
+
|
|
175
|
+
Node {
|
|
176
|
+
node_type: NodeType::InstanceVariableWriteNode,
|
|
177
|
+
location: Location::new(start_line, 0, end_line, 10, start_offset, end_offset),
|
|
178
|
+
children,
|
|
179
|
+
metadata,
|
|
180
|
+
comments: Vec::new(),
|
|
181
|
+
formatting: FormattingInfo::default(),
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
fn make_integer_node(line: usize, start_offset: usize, end_offset: usize) -> Node {
|
|
186
|
+
Node {
|
|
187
|
+
node_type: NodeType::IntegerNode,
|
|
188
|
+
location: Location::new(line, 0, line, 1, start_offset, end_offset),
|
|
189
|
+
children: Vec::new(),
|
|
190
|
+
metadata: HashMap::new(),
|
|
191
|
+
comments: Vec::new(),
|
|
192
|
+
formatting: FormattingInfo::default(),
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[test]
|
|
197
|
+
fn test_simple_local_var_assignment() {
|
|
198
|
+
let config = Config::default();
|
|
199
|
+
let source = "x = 1";
|
|
200
|
+
let mut ctx = FormatContext::new(&config, source);
|
|
201
|
+
let registry = RuleRegistry::default_registry();
|
|
202
|
+
|
|
203
|
+
let value_node = make_integer_node(1, 4, 5);
|
|
204
|
+
let node = make_local_var_write_node("x", vec![value_node], 1, 1, 0, 5);
|
|
205
|
+
ctx.collect_comments(&node);
|
|
206
|
+
|
|
207
|
+
let rule = LocalVariableWriteRule;
|
|
208
|
+
let doc = rule.format(&node, &mut ctx, ®istry).unwrap();
|
|
209
|
+
|
|
210
|
+
let mut printer = Printer::new(&config);
|
|
211
|
+
let result = printer.print(&doc);
|
|
212
|
+
|
|
213
|
+
assert_eq!(result.trim(), "x = 1");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#[test]
|
|
217
|
+
fn test_simple_instance_var_assignment() {
|
|
218
|
+
let config = Config::default();
|
|
219
|
+
let source = "@value = 42";
|
|
220
|
+
let mut ctx = FormatContext::new(&config, source);
|
|
221
|
+
let registry = RuleRegistry::default_registry();
|
|
222
|
+
|
|
223
|
+
let value_node = make_integer_node(1, 9, 11);
|
|
224
|
+
let node = make_instance_var_write_node("@value", vec![value_node], 1, 1, 0, 11);
|
|
225
|
+
ctx.collect_comments(&node);
|
|
226
|
+
|
|
227
|
+
let rule = InstanceVariableWriteRule;
|
|
228
|
+
let doc = rule.format(&node, &mut ctx, ®istry).unwrap();
|
|
229
|
+
|
|
230
|
+
let mut printer = Printer::new(&config);
|
|
231
|
+
let result = printer.print(&doc);
|
|
232
|
+
|
|
233
|
+
assert_eq!(result.trim(), "@value = 42");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
#[test]
|
|
237
|
+
fn test_block_value_assignment() {
|
|
238
|
+
let config = Config::default();
|
|
239
|
+
let source = "x = if true\n 1\nelse\n 2\nend";
|
|
240
|
+
let mut ctx = FormatContext::new(&config, source);
|
|
241
|
+
let registry = RuleRegistry::default_registry();
|
|
242
|
+
|
|
243
|
+
let if_node = Node {
|
|
244
|
+
node_type: NodeType::IfNode,
|
|
245
|
+
location: Location::new(1, 4, 5, 3, 4, 28),
|
|
246
|
+
children: Vec::new(),
|
|
247
|
+
metadata: HashMap::new(),
|
|
248
|
+
comments: Vec::new(),
|
|
249
|
+
formatting: FormattingInfo::default(),
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
let node = make_local_var_write_node("x", vec![if_node], 1, 5, 0, 28);
|
|
253
|
+
ctx.collect_comments(&node);
|
|
254
|
+
|
|
255
|
+
let rule = LocalVariableWriteRule;
|
|
256
|
+
let doc = rule.format(&node, &mut ctx, ®istry).unwrap();
|
|
257
|
+
|
|
258
|
+
let mut printer = Printer::new(&config);
|
|
259
|
+
let result = printer.print(&doc);
|
|
260
|
+
|
|
261
|
+
// Should format with value on new line
|
|
262
|
+
assert!(result.contains("x ="));
|
|
263
|
+
assert!(result.contains("if"));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
#[test]
|
|
267
|
+
fn test_multiline_chain_assignment() {
|
|
268
|
+
let config = Config::default();
|
|
269
|
+
let source = "x = foo\n .bar\n .baz";
|
|
270
|
+
let mut ctx = FormatContext::new(&config, source);
|
|
271
|
+
let registry = RuleRegistry::default_registry();
|
|
272
|
+
|
|
273
|
+
let call_node = Node {
|
|
274
|
+
node_type: NodeType::CallNode,
|
|
275
|
+
location: Location::new(1, 4, 3, 6, 4, 21),
|
|
276
|
+
children: Vec::new(),
|
|
277
|
+
metadata: HashMap::new(),
|
|
278
|
+
comments: Vec::new(),
|
|
279
|
+
formatting: FormattingInfo::default(),
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
let node = make_local_var_write_node("x", vec![call_node], 1, 3, 0, 21);
|
|
283
|
+
ctx.collect_comments(&node);
|
|
284
|
+
|
|
285
|
+
let rule = LocalVariableWriteRule;
|
|
286
|
+
let doc = rule.format(&node, &mut ctx, ®istry).unwrap();
|
|
287
|
+
|
|
288
|
+
let mut printer = Printer::new(&config);
|
|
289
|
+
let result = printer.print(&doc);
|
|
290
|
+
|
|
291
|
+
// Should reformat chain with indented style
|
|
292
|
+
assert!(result.contains("x = foo"));
|
|
293
|
+
assert!(result.contains(".bar"));
|
|
294
|
+
assert!(result.contains(".baz"));
|
|
295
|
+
}
|
|
296
|
+
}
|
data/ext/rfmt/src/lib.rs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
mod ast;
|
|
2
|
-
mod config;
|
|
3
|
-
mod
|
|
2
|
+
pub mod config;
|
|
3
|
+
pub mod doc;
|
|
4
4
|
mod error;
|
|
5
|
+
pub mod format;
|
|
5
6
|
mod logging;
|
|
6
7
|
mod parser;
|
|
7
8
|
mod policy;
|
|
@@ -9,7 +10,7 @@ mod policy;
|
|
|
9
10
|
use policy::SecurityPolicy;
|
|
10
11
|
|
|
11
12
|
use config::Config;
|
|
12
|
-
use
|
|
13
|
+
use format::Formatter;
|
|
13
14
|
use magnus::{function, prelude::*, Error, Ruby};
|
|
14
15
|
use parser::{PrismAdapter, RubyParser};
|
|
15
16
|
|
|
@@ -24,9 +25,11 @@ fn format_ruby_code(ruby: &Ruby, source: String, json: String) -> Result<String,
|
|
|
24
25
|
let ast = parser.parse(&json).map_err(|e| e.to_magnus_error(ruby))?;
|
|
25
26
|
|
|
26
27
|
let config = Config::discover().map_err(|e| e.to_magnus_error(ruby))?;
|
|
27
|
-
let
|
|
28
|
+
let formatter = Formatter::new(config);
|
|
28
29
|
|
|
29
|
-
let formatted =
|
|
30
|
+
let formatted = formatter
|
|
31
|
+
.format(&source, &ast)
|
|
32
|
+
.map_err(|e| e.to_magnus_error(ruby))?;
|
|
30
33
|
|
|
31
34
|
Ok(formatted)
|
|
32
35
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
use crate::ast::{Comment, CommentPosition, CommentType, FormattingInfo, Location, Node, NodeType};
|
|
2
2
|
use crate::error::{Result, RfmtError};
|
|
3
3
|
use crate::parser::RubyParser;
|
|
4
|
-
use serde::{Deserialize, Serialize};
|
|
4
|
+
use serde::{Deserialize, Deserializer, Serialize};
|
|
5
5
|
use std::collections::HashMap;
|
|
6
|
+
use std::fmt;
|
|
6
7
|
|
|
7
8
|
/// Prism parser adapter
|
|
8
9
|
/// This integrates with Ruby Prism parser via Magnus FFI
|
|
@@ -144,7 +145,13 @@ pub struct PrismLocation {
|
|
|
144
145
|
pub struct PrismComment {
|
|
145
146
|
pub text: String,
|
|
146
147
|
pub location: PrismLocation,
|
|
147
|
-
|
|
148
|
+
/// PrismBridge sends `comment_type`; older payloads may use `type`.
|
|
149
|
+
#[serde(
|
|
150
|
+
default,
|
|
151
|
+
rename = "comment_type",
|
|
152
|
+
alias = "type",
|
|
153
|
+
deserialize_with = "deserialize_prism_comment_type"
|
|
154
|
+
)]
|
|
148
155
|
pub comment_type: PrismCommentType,
|
|
149
156
|
#[serde(default)]
|
|
150
157
|
pub position: PrismCommentPosition,
|
|
@@ -158,6 +165,39 @@ pub enum PrismCommentType {
|
|
|
158
165
|
Block,
|
|
159
166
|
}
|
|
160
167
|
|
|
168
|
+
/// Maps Ruby Prism comment class names (after `PrismBridge` normalization) to our enum.
|
|
169
|
+
/// Unknown values default to `Line` so parsing never fails on new Prism comment kinds.
|
|
170
|
+
fn deserialize_prism_comment_type<'de, D>(
|
|
171
|
+
deserializer: D,
|
|
172
|
+
) -> std::result::Result<PrismCommentType, D::Error>
|
|
173
|
+
where
|
|
174
|
+
D: Deserializer<'de>,
|
|
175
|
+
{
|
|
176
|
+
use serde::de::{self, Visitor};
|
|
177
|
+
|
|
178
|
+
struct CommentTypeVisitor;
|
|
179
|
+
|
|
180
|
+
impl Visitor<'_> for CommentTypeVisitor {
|
|
181
|
+
type Value = PrismCommentType;
|
|
182
|
+
|
|
183
|
+
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
184
|
+
f.write_str("a Prism comment type string")
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fn visit_str<E: de::Error>(self, v: &str) -> std::result::Result<Self::Value, E> {
|
|
188
|
+
Ok(match v {
|
|
189
|
+
// LineComment -> "line", InlineComment -> "inline"
|
|
190
|
+
"line" | "inline" => PrismCommentType::Line,
|
|
191
|
+
// EmbDocComment (=begin/=end) -> "embdoc"; keep "block" for compatibility
|
|
192
|
+
"block" | "embdoc" => PrismCommentType::Block,
|
|
193
|
+
_ => PrismCommentType::Line,
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
deserializer.deserialize_str(CommentTypeVisitor)
|
|
199
|
+
}
|
|
200
|
+
|
|
161
201
|
impl From<PrismCommentType> for CommentType {
|
|
162
202
|
fn from(t: PrismCommentType) -> Self {
|
|
163
203
|
match t {
|
|
@@ -199,6 +239,7 @@ pub struct PrismFormattingInfo {
|
|
|
199
239
|
#[cfg(test)]
|
|
200
240
|
mod tests {
|
|
201
241
|
use super::*;
|
|
242
|
+
use crate::ast::CommentType;
|
|
202
243
|
|
|
203
244
|
#[test]
|
|
204
245
|
fn test_parse_simple_program() {
|
|
@@ -426,4 +467,118 @@ mod tests {
|
|
|
426
467
|
assert!(node.is_unknown());
|
|
427
468
|
assert_eq!(node.unknown_type(), Some("totally_unknown_node"));
|
|
428
469
|
}
|
|
470
|
+
|
|
471
|
+
/// Ruby PrismBridge uses `comment_type` (not `type`) and values like `embdoc` / `inline`.
|
|
472
|
+
#[test]
|
|
473
|
+
fn test_parse_wrapper_with_prism_bridge_comment_fields() {
|
|
474
|
+
let adapter = PrismAdapter::new();
|
|
475
|
+
let json = r##"{
|
|
476
|
+
"ast": {
|
|
477
|
+
"node_type": "program_node",
|
|
478
|
+
"location": {
|
|
479
|
+
"start_line": 1,
|
|
480
|
+
"start_column": 0,
|
|
481
|
+
"end_line": 2,
|
|
482
|
+
"end_column": 0,
|
|
483
|
+
"start_offset": 0,
|
|
484
|
+
"end_offset": 20
|
|
485
|
+
},
|
|
486
|
+
"children": [],
|
|
487
|
+
"metadata": {},
|
|
488
|
+
"comments": [],
|
|
489
|
+
"formatting": {
|
|
490
|
+
"indent_level": 0,
|
|
491
|
+
"needs_blank_line_before": false,
|
|
492
|
+
"needs_blank_line_after": false,
|
|
493
|
+
"preserve_newlines": false,
|
|
494
|
+
"multiline": false,
|
|
495
|
+
"original_formatting": null
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
"comments": [
|
|
499
|
+
{
|
|
500
|
+
"text": "=begin\nnote\n=end",
|
|
501
|
+
"comment_type": "embdoc",
|
|
502
|
+
"location": {
|
|
503
|
+
"start_line": 1,
|
|
504
|
+
"start_column": 0,
|
|
505
|
+
"end_line": 3,
|
|
506
|
+
"end_column": 4,
|
|
507
|
+
"start_offset": 0,
|
|
508
|
+
"end_offset": 16
|
|
509
|
+
},
|
|
510
|
+
"position": "leading"
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
"text": "# inline style",
|
|
514
|
+
"comment_type": "inline",
|
|
515
|
+
"location": {
|
|
516
|
+
"start_line": 4,
|
|
517
|
+
"start_column": 0,
|
|
518
|
+
"end_line": 4,
|
|
519
|
+
"end_column": 14,
|
|
520
|
+
"start_offset": 17,
|
|
521
|
+
"end_offset": 31
|
|
522
|
+
},
|
|
523
|
+
"position": "leading"
|
|
524
|
+
}
|
|
525
|
+
]
|
|
526
|
+
}"##;
|
|
527
|
+
|
|
528
|
+
let result = adapter.parse(json);
|
|
529
|
+
assert!(result.is_ok(), "{:?}", result.err());
|
|
530
|
+
let node = result.unwrap();
|
|
531
|
+
assert_eq!(node.comments.len(), 2);
|
|
532
|
+
assert_eq!(node.comments[0].comment_type, CommentType::Block);
|
|
533
|
+
assert_eq!(node.comments[1].comment_type, CommentType::Line);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/// Legacy JSON that used the `type` key for comment_kind still parses.
|
|
537
|
+
#[test]
|
|
538
|
+
fn test_parse_wrapper_comment_type_alias_type_key() {
|
|
539
|
+
let adapter = PrismAdapter::new();
|
|
540
|
+
let json = r##"{
|
|
541
|
+
"ast": {
|
|
542
|
+
"node_type": "program_node",
|
|
543
|
+
"location": {
|
|
544
|
+
"start_line": 1,
|
|
545
|
+
"start_column": 0,
|
|
546
|
+
"end_line": 1,
|
|
547
|
+
"end_column": 0,
|
|
548
|
+
"start_offset": 0,
|
|
549
|
+
"end_offset": 1
|
|
550
|
+
},
|
|
551
|
+
"children": [],
|
|
552
|
+
"metadata": {},
|
|
553
|
+
"comments": [],
|
|
554
|
+
"formatting": {
|
|
555
|
+
"indent_level": 0,
|
|
556
|
+
"needs_blank_line_before": false,
|
|
557
|
+
"needs_blank_line_after": false,
|
|
558
|
+
"preserve_newlines": false,
|
|
559
|
+
"multiline": false,
|
|
560
|
+
"original_formatting": null
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
"comments": [
|
|
564
|
+
{
|
|
565
|
+
"text": "# hi",
|
|
566
|
+
"type": "line",
|
|
567
|
+
"location": {
|
|
568
|
+
"start_line": 1,
|
|
569
|
+
"start_column": 0,
|
|
570
|
+
"end_line": 1,
|
|
571
|
+
"end_column": 4,
|
|
572
|
+
"start_offset": 0,
|
|
573
|
+
"end_offset": 4
|
|
574
|
+
},
|
|
575
|
+
"position": "leading"
|
|
576
|
+
}
|
|
577
|
+
]
|
|
578
|
+
}"##;
|
|
579
|
+
|
|
580
|
+
let node = adapter.parse(json).unwrap();
|
|
581
|
+
assert_eq!(node.comments.len(), 1);
|
|
582
|
+
assert_eq!(node.comments[0].comment_type, CommentType::Line);
|
|
583
|
+
}
|
|
429
584
|
}
|
data/lib/rfmt/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rfmt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- fujitani sora
|
|
@@ -84,8 +84,29 @@ files:
|
|
|
84
84
|
- ext/rfmt/extconf.rb
|
|
85
85
|
- ext/rfmt/src/ast/mod.rs
|
|
86
86
|
- ext/rfmt/src/config/mod.rs
|
|
87
|
-
- ext/rfmt/src/
|
|
87
|
+
- ext/rfmt/src/doc/builders.rs
|
|
88
|
+
- ext/rfmt/src/doc/mod.rs
|
|
89
|
+
- ext/rfmt/src/doc/printer.rs
|
|
88
90
|
- ext/rfmt/src/error/mod.rs
|
|
91
|
+
- ext/rfmt/src/format/context.rs
|
|
92
|
+
- ext/rfmt/src/format/formatter.rs
|
|
93
|
+
- ext/rfmt/src/format/mod.rs
|
|
94
|
+
- ext/rfmt/src/format/registry.rs
|
|
95
|
+
- ext/rfmt/src/format/rule.rs
|
|
96
|
+
- ext/rfmt/src/format/rules/begin.rs
|
|
97
|
+
- ext/rfmt/src/format/rules/body_end.rs
|
|
98
|
+
- ext/rfmt/src/format/rules/call.rs
|
|
99
|
+
- ext/rfmt/src/format/rules/case.rs
|
|
100
|
+
- ext/rfmt/src/format/rules/class.rs
|
|
101
|
+
- ext/rfmt/src/format/rules/def.rs
|
|
102
|
+
- ext/rfmt/src/format/rules/fallback.rs
|
|
103
|
+
- ext/rfmt/src/format/rules/if_unless.rs
|
|
104
|
+
- ext/rfmt/src/format/rules/loops.rs
|
|
105
|
+
- ext/rfmt/src/format/rules/mod.rs
|
|
106
|
+
- ext/rfmt/src/format/rules/module.rs
|
|
107
|
+
- ext/rfmt/src/format/rules/singleton_class.rs
|
|
108
|
+
- ext/rfmt/src/format/rules/statements.rs
|
|
109
|
+
- ext/rfmt/src/format/rules/variable_write.rs
|
|
89
110
|
- ext/rfmt/src/lib.rs
|
|
90
111
|
- ext/rfmt/src/logging/logger.rs
|
|
91
112
|
- ext/rfmt/src/logging/mod.rs
|