rfmt 1.5.3 → 1.6.1
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 +51 -0
- data/Cargo.lock +1 -1
- 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 +721 -0
- data/ext/rfmt/src/format/context.rs +448 -0
- data/ext/rfmt/src/format/formatter.rs +250 -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 +726 -0
- data/ext/rfmt/src/format/rules/begin.rs +434 -0
- data/ext/rfmt/src/format/rules/body_end.rs +233 -0
- data/ext/rfmt/src/format/rules/call.rs +448 -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 +130 -0
- data/ext/rfmt/src/format/rules/if_unless.rs +454 -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 +314 -0
- data/ext/rfmt/src/lib.rs +8 -5
- data/ext/rfmt/src/parser/prism_adapter.rs +157 -2
- data/lib/rfmt/prism_bridge.rb +43 -12
- 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 -1844
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
//! CallRule - Formats Ruby method calls
|
|
2
|
+
//!
|
|
3
|
+
//! Handles:
|
|
4
|
+
//! - Simple calls: `foo.bar`
|
|
5
|
+
//! - Calls with blocks: `foo.bar do ... end` or `foo.bar { ... }`
|
|
6
|
+
//! - Method chains: `foo.bar.baz`
|
|
7
|
+
|
|
8
|
+
use std::borrow::Cow;
|
|
9
|
+
|
|
10
|
+
use crate::ast::{Node, NodeType};
|
|
11
|
+
use crate::doc::{align, concat, hardline, indent, text, Doc};
|
|
12
|
+
use crate::error::Result;
|
|
13
|
+
use crate::format::context::FormatContext;
|
|
14
|
+
use crate::format::registry::RuleRegistry;
|
|
15
|
+
use crate::format::rule::{
|
|
16
|
+
format_child, format_leading_comments, format_statements, format_trailing_comment,
|
|
17
|
+
line_leading_indent, mark_comments_in_range_emitted, reformat_chain_lines,
|
|
18
|
+
strip_one_trailing_newline, FormatRule,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/// Rule for formatting method calls.
|
|
22
|
+
pub struct CallRule;
|
|
23
|
+
|
|
24
|
+
/// Rule for formatting blocks (do...end and {...}).
|
|
25
|
+
pub struct BlockRule;
|
|
26
|
+
|
|
27
|
+
/// Rule for formatting lambdas.
|
|
28
|
+
pub struct LambdaRule;
|
|
29
|
+
|
|
30
|
+
/// Block style for Ruby blocks
|
|
31
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
32
|
+
enum BlockStyle {
|
|
33
|
+
DoEnd, // do ... end
|
|
34
|
+
Braces, // { ... }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
impl FormatRule for CallRule {
|
|
38
|
+
fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
|
|
39
|
+
format_call(node, ctx, registry)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
impl FormatRule for BlockRule {
|
|
44
|
+
fn format(&self, node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
|
|
45
|
+
// Detect block style and format accordingly
|
|
46
|
+
let block_style = detect_block_style(node, ctx);
|
|
47
|
+
match block_style {
|
|
48
|
+
BlockStyle::DoEnd => format_do_end_block(node, ctx, registry),
|
|
49
|
+
BlockStyle::Braces => format_brace_block(node, ctx, registry),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
impl FormatRule for LambdaRule {
|
|
55
|
+
fn format(
|
|
56
|
+
&self,
|
|
57
|
+
node: &Node,
|
|
58
|
+
ctx: &mut FormatContext,
|
|
59
|
+
_registry: &RuleRegistry,
|
|
60
|
+
) -> Result<Doc> {
|
|
61
|
+
// Lambda syntax is complex (-> vs lambda, {} vs do-end)
|
|
62
|
+
// Use source extraction to preserve original style
|
|
63
|
+
let mut docs: Vec<Doc> = Vec::with_capacity(3);
|
|
64
|
+
|
|
65
|
+
// Leading comments
|
|
66
|
+
let leading = format_leading_comments(ctx, node.location.start_line);
|
|
67
|
+
if !leading.is_empty() {
|
|
68
|
+
docs.push(leading);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Extract source
|
|
72
|
+
if let Some(source_text) = ctx.extract_source(node) {
|
|
73
|
+
docs.push(text(source_text));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Mark internal comments as emitted
|
|
77
|
+
mark_comments_in_range_emitted(ctx, node.location.start_line, node.location.end_line);
|
|
78
|
+
|
|
79
|
+
// Trailing comment
|
|
80
|
+
let trailing = format_trailing_comment(ctx, node.location.end_line);
|
|
81
|
+
if !trailing.is_empty() {
|
|
82
|
+
docs.push(trailing);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Ok(concat(docs))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Formats method call
|
|
90
|
+
fn format_call(node: &Node, ctx: &mut FormatContext, registry: &RuleRegistry) -> Result<Doc> {
|
|
91
|
+
// Leading comments
|
|
92
|
+
let mut docs: Vec<Doc> = Vec::with_capacity(4);
|
|
93
|
+
let leading = format_leading_comments(ctx, node.location.start_line);
|
|
94
|
+
if !leading.is_empty() {
|
|
95
|
+
docs.push(leading);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Check if this call has a block (last child is BlockNode)
|
|
99
|
+
let has_block = node
|
|
100
|
+
.children
|
|
101
|
+
.last()
|
|
102
|
+
.map(|c| matches!(c.node_type, NodeType::BlockNode))
|
|
103
|
+
.unwrap_or(false);
|
|
104
|
+
|
|
105
|
+
if !has_block {
|
|
106
|
+
// Simple call - use source extraction with chain reformatting.
|
|
107
|
+
//
|
|
108
|
+
// A CallNode that carries a heredoc argument (e.g.
|
|
109
|
+
// `query(<<~SQL)\n …\nSQL`) reports its end_offset past the
|
|
110
|
+
// heredoc terminator's trailing newline. Leaving that newline in
|
|
111
|
+
// the emitted `Doc::Text` combines with the later `hardline + end`
|
|
112
|
+
// or inter-statement hardline to produce a spurious blank line.
|
|
113
|
+
// Strip at most one trailing newline so this doesn't happen; using
|
|
114
|
+
// the full `trim_end` here would instead eat a blank separator line
|
|
115
|
+
// that legitimately belongs between statements.
|
|
116
|
+
if let Some(source_text) = ctx.extract_source(node) {
|
|
117
|
+
let base_indent = line_leading_indent(ctx.source(), node.location.start_offset);
|
|
118
|
+
let reformatted = reformat_chain_lines(
|
|
119
|
+
source_text,
|
|
120
|
+
base_indent,
|
|
121
|
+
ctx.config().formatting.indent_width,
|
|
122
|
+
);
|
|
123
|
+
let trimmed = strip_one_trailing_newline(&reformatted);
|
|
124
|
+
docs.push(text(trimmed.to_string()));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Mark comments in this range as emitted (they're in source extraction)
|
|
128
|
+
mark_comments_in_range_emitted(ctx, node.location.start_line, node.location.end_line);
|
|
129
|
+
|
|
130
|
+
// Trailing comment
|
|
131
|
+
let trailing = format_trailing_comment(ctx, node.location.end_line);
|
|
132
|
+
if !trailing.is_empty() {
|
|
133
|
+
docs.push(trailing);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return Ok(concat(docs));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Has block - need to handle specially
|
|
140
|
+
let block_node = node.children.last().unwrap();
|
|
141
|
+
let block_style = detect_block_style(block_node, ctx);
|
|
142
|
+
|
|
143
|
+
// Emit the call part (receiver.method(args)) from source with chain
|
|
144
|
+
// reformatting. Track whether reformatting actually fired so the block
|
|
145
|
+
// body can be re-aligned to match the chain's new depth.
|
|
146
|
+
let call_end_offset = block_node.location.start_offset;
|
|
147
|
+
let chain_reformatted = if let Some(call_text) = ctx
|
|
148
|
+
.source()
|
|
149
|
+
.get(node.location.start_offset..call_end_offset)
|
|
150
|
+
{
|
|
151
|
+
let base_indent = line_leading_indent(ctx.source(), node.location.start_offset);
|
|
152
|
+
let reformatted = reformat_chain_lines(
|
|
153
|
+
call_text.trim_end(),
|
|
154
|
+
base_indent,
|
|
155
|
+
ctx.config().formatting.indent_width,
|
|
156
|
+
);
|
|
157
|
+
let changed = matches!(reformatted, Cow::Owned(_));
|
|
158
|
+
docs.push(text(reformatted));
|
|
159
|
+
changed
|
|
160
|
+
} else {
|
|
161
|
+
false
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Mark comments in the call part (before block) as emitted
|
|
165
|
+
// This includes trailing comments that are part of the extracted source
|
|
166
|
+
mark_comments_in_range_emitted(
|
|
167
|
+
ctx,
|
|
168
|
+
node.location.start_line,
|
|
169
|
+
block_node.location.start_line,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Format the block. When the receiver's chain was re-indented, the
|
|
173
|
+
// `do`-line ends up one level below `base_indent` instead of at
|
|
174
|
+
// `base_indent` itself, so the default `indent(body)` wrap inside the
|
|
175
|
+
// block formatter now places the body *at* the chain depth rather than
|
|
176
|
+
// one level below it (and the `end` keyword floats up to `base_indent`).
|
|
177
|
+
// Push both down with `Align` so the `do…end` body is indented relative
|
|
178
|
+
// to the chain's last line, matching what a human would write.
|
|
179
|
+
let block_doc = match block_style {
|
|
180
|
+
BlockStyle::DoEnd => format_do_end_block(block_node, ctx, registry)?,
|
|
181
|
+
BlockStyle::Braces => format_brace_block(block_node, ctx, registry)?,
|
|
182
|
+
};
|
|
183
|
+
if chain_reformatted {
|
|
184
|
+
docs.push(align(ctx.config().formatting.indent_width, block_doc));
|
|
185
|
+
} else {
|
|
186
|
+
docs.push(block_doc);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
Ok(concat(docs))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Detect whether block uses do...end or { } style
|
|
193
|
+
fn detect_block_style(block_node: &Node, ctx: &FormatContext) -> BlockStyle {
|
|
194
|
+
if let Some(first_char) = ctx
|
|
195
|
+
.source()
|
|
196
|
+
.get(block_node.location.start_offset..block_node.location.start_offset + 1)
|
|
197
|
+
{
|
|
198
|
+
if first_char == "{" {
|
|
199
|
+
return BlockStyle::Braces;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
BlockStyle::DoEnd
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/// Formats do...end style block
|
|
206
|
+
fn format_do_end_block(
|
|
207
|
+
block_node: &Node,
|
|
208
|
+
ctx: &mut FormatContext,
|
|
209
|
+
registry: &RuleRegistry,
|
|
210
|
+
) -> Result<Doc> {
|
|
211
|
+
let mut docs: Vec<Doc> = Vec::with_capacity(8);
|
|
212
|
+
|
|
213
|
+
docs.push(text(" do"));
|
|
214
|
+
|
|
215
|
+
// Emit block parameters if present (|x, y|)
|
|
216
|
+
if let Some(params) = extract_block_parameters(block_node, ctx) {
|
|
217
|
+
docs.push(text(" "));
|
|
218
|
+
docs.push(text(params));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Trailing comment on same line as do |...|
|
|
222
|
+
let trailing = format_trailing_comment(ctx, block_node.location.start_line);
|
|
223
|
+
if !trailing.is_empty() {
|
|
224
|
+
docs.push(trailing);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Find and emit the body (StatementsNode or BeginNode among children)
|
|
228
|
+
for child in &block_node.children {
|
|
229
|
+
match &child.node_type {
|
|
230
|
+
NodeType::StatementsNode => {
|
|
231
|
+
let body_doc = format_statements(child, ctx, registry)?;
|
|
232
|
+
docs.push(indent(concat(vec![hardline(), body_doc])));
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
NodeType::BeginNode => {
|
|
236
|
+
// Block with rescue/else/ensure needs the clause keywords at
|
|
237
|
+
// the block opener's indent level, not at the body indent.
|
|
238
|
+
if super::begin::is_implicit_begin_with_clauses(child, ctx) {
|
|
239
|
+
docs.push(super::begin::format_implicit_begin_body(
|
|
240
|
+
child, ctx, registry,
|
|
241
|
+
)?);
|
|
242
|
+
} else {
|
|
243
|
+
let body_doc = format_child(child, ctx, registry)?;
|
|
244
|
+
docs.push(indent(concat(vec![hardline(), body_doc])));
|
|
245
|
+
}
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
_ => {
|
|
249
|
+
// Skip parameter nodes
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Emit 'end'
|
|
255
|
+
docs.push(hardline());
|
|
256
|
+
docs.push(text("end"));
|
|
257
|
+
|
|
258
|
+
// Trailing comment on end line
|
|
259
|
+
let end_trailing = format_trailing_comment(ctx, block_node.location.end_line);
|
|
260
|
+
if !end_trailing.is_empty() {
|
|
261
|
+
docs.push(end_trailing);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
Ok(concat(docs))
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/// Formats { } style block
|
|
268
|
+
fn format_brace_block(
|
|
269
|
+
block_node: &Node,
|
|
270
|
+
ctx: &mut FormatContext,
|
|
271
|
+
registry: &RuleRegistry,
|
|
272
|
+
) -> Result<Doc> {
|
|
273
|
+
let is_multiline = block_node.location.start_line != block_node.location.end_line;
|
|
274
|
+
|
|
275
|
+
if is_multiline {
|
|
276
|
+
format_multiline_brace_block(block_node, ctx, registry)
|
|
277
|
+
} else {
|
|
278
|
+
format_inline_brace_block(block_node, ctx)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/// Formats multiline brace block
|
|
283
|
+
fn format_multiline_brace_block(
|
|
284
|
+
block_node: &Node,
|
|
285
|
+
ctx: &mut FormatContext,
|
|
286
|
+
registry: &RuleRegistry,
|
|
287
|
+
) -> Result<Doc> {
|
|
288
|
+
let mut docs: Vec<Doc> = Vec::with_capacity(8);
|
|
289
|
+
|
|
290
|
+
docs.push(text(" {"));
|
|
291
|
+
|
|
292
|
+
// Emit block parameters if present
|
|
293
|
+
if let Some(params) = extract_block_parameters(block_node, ctx) {
|
|
294
|
+
docs.push(text(" "));
|
|
295
|
+
docs.push(text(params));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Emit body
|
|
299
|
+
for child in &block_node.children {
|
|
300
|
+
if matches!(child.node_type, NodeType::StatementsNode) {
|
|
301
|
+
let body_doc = format_statements(child, ctx, registry)?;
|
|
302
|
+
docs.push(indent(concat(vec![hardline(), body_doc])));
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
docs.push(hardline());
|
|
308
|
+
docs.push(text("}"));
|
|
309
|
+
|
|
310
|
+
// Trailing comment
|
|
311
|
+
let trailing = format_trailing_comment(ctx, block_node.location.end_line);
|
|
312
|
+
if !trailing.is_empty() {
|
|
313
|
+
docs.push(trailing);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
Ok(concat(docs))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/// Formats inline brace block
|
|
320
|
+
fn format_inline_brace_block(block_node: &Node, ctx: &mut FormatContext) -> Result<Doc> {
|
|
321
|
+
let mut docs: Vec<Doc> = Vec::with_capacity(3);
|
|
322
|
+
|
|
323
|
+
docs.push(text(" "));
|
|
324
|
+
|
|
325
|
+
// Extract from source to preserve spacing
|
|
326
|
+
if let Some(source_text) = ctx.extract_source(block_node) {
|
|
327
|
+
docs.push(text(source_text));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Mark internal comments as emitted
|
|
331
|
+
mark_comments_in_range_emitted(
|
|
332
|
+
ctx,
|
|
333
|
+
block_node.location.start_line,
|
|
334
|
+
block_node.location.end_line,
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
// Trailing comment
|
|
338
|
+
let trailing = format_trailing_comment(ctx, block_node.location.end_line);
|
|
339
|
+
if !trailing.is_empty() {
|
|
340
|
+
docs.push(trailing);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
Ok(concat(docs))
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/// Extract block parameters (|x, y|) from block node
|
|
347
|
+
fn extract_block_parameters(block_node: &Node, ctx: &FormatContext) -> Option<String> {
|
|
348
|
+
let source = ctx.source();
|
|
349
|
+
if source.is_empty() {
|
|
350
|
+
return None;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
let block_source =
|
|
354
|
+
source.get(block_node.location.start_offset..block_node.location.end_offset)?;
|
|
355
|
+
|
|
356
|
+
// Only look at the first line of the block for parameters
|
|
357
|
+
let first_line = block_source.lines().next()?;
|
|
358
|
+
|
|
359
|
+
// Find |...| pattern in the first line only
|
|
360
|
+
let pipe_start = first_line.find('|')?;
|
|
361
|
+
let rest = &first_line[pipe_start + 1..];
|
|
362
|
+
let pipe_end = rest.find('|')?;
|
|
363
|
+
|
|
364
|
+
Some(first_line[pipe_start..=pipe_start + 1 + pipe_end].to_string())
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
#[cfg(test)]
|
|
368
|
+
mod tests {
|
|
369
|
+
use super::*;
|
|
370
|
+
use crate::ast::{FormattingInfo, Location};
|
|
371
|
+
use crate::config::Config;
|
|
372
|
+
use crate::doc::Printer;
|
|
373
|
+
use std::collections::HashMap;
|
|
374
|
+
|
|
375
|
+
fn make_call_node(
|
|
376
|
+
children: Vec<Node>,
|
|
377
|
+
start_offset: usize,
|
|
378
|
+
end_offset: usize,
|
|
379
|
+
start_line: usize,
|
|
380
|
+
end_line: usize,
|
|
381
|
+
) -> Node {
|
|
382
|
+
Node {
|
|
383
|
+
node_type: NodeType::CallNode,
|
|
384
|
+
location: Location::new(start_line, 0, end_line, 0, start_offset, end_offset),
|
|
385
|
+
children,
|
|
386
|
+
metadata: HashMap::new(),
|
|
387
|
+
comments: Vec::new(),
|
|
388
|
+
formatting: FormattingInfo::default(),
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
#[test]
|
|
393
|
+
fn test_simple_call() {
|
|
394
|
+
let config = Config::default();
|
|
395
|
+
let source = "puts 'hello'";
|
|
396
|
+
let mut ctx = FormatContext::new(&config, source);
|
|
397
|
+
let registry = RuleRegistry::default_registry();
|
|
398
|
+
|
|
399
|
+
let node = make_call_node(Vec::new(), 0, 12, 1, 1);
|
|
400
|
+
ctx.collect_comments(&node);
|
|
401
|
+
|
|
402
|
+
let rule = CallRule;
|
|
403
|
+
let doc = rule.format(&node, &mut ctx, ®istry).unwrap();
|
|
404
|
+
|
|
405
|
+
let mut printer = Printer::new(&config);
|
|
406
|
+
let result = printer.print(&doc);
|
|
407
|
+
|
|
408
|
+
assert_eq!(result.trim(), "puts 'hello'");
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
#[test]
|
|
412
|
+
fn test_call_with_do_block() {
|
|
413
|
+
let config = Config::default();
|
|
414
|
+
let source = "items.each do |item|\n puts item\nend";
|
|
415
|
+
let mut ctx = FormatContext::new(&config, source);
|
|
416
|
+
let registry = RuleRegistry::default_registry();
|
|
417
|
+
|
|
418
|
+
let block_body = Node {
|
|
419
|
+
node_type: NodeType::StatementsNode,
|
|
420
|
+
location: Location::new(2, 2, 2, 11, 23, 32),
|
|
421
|
+
children: Vec::new(),
|
|
422
|
+
metadata: HashMap::new(),
|
|
423
|
+
comments: Vec::new(),
|
|
424
|
+
formatting: FormattingInfo::default(),
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
let block = Node {
|
|
428
|
+
node_type: NodeType::BlockNode,
|
|
429
|
+
location: Location::new(1, 11, 3, 3, 11, 36),
|
|
430
|
+
children: vec![block_body],
|
|
431
|
+
metadata: HashMap::new(),
|
|
432
|
+
comments: Vec::new(),
|
|
433
|
+
formatting: FormattingInfo::default(),
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
let node = make_call_node(vec![block], 0, 36, 1, 3);
|
|
437
|
+
ctx.collect_comments(&node);
|
|
438
|
+
|
|
439
|
+
let rule = CallRule;
|
|
440
|
+
let doc = rule.format(&node, &mut ctx, ®istry).unwrap();
|
|
441
|
+
|
|
442
|
+
let mut printer = Printer::new(&config);
|
|
443
|
+
let result = printer.print(&doc);
|
|
444
|
+
|
|
445
|
+
assert!(result.contains("items.each do"));
|
|
446
|
+
assert!(result.contains("end"));
|
|
447
|
+
}
|
|
448
|
+
}
|