Skip to content

Commit 8991bc9

Browse files
authored
[fix] Render inline conditionals in source order to fix line handling (#894)
* Fixtures * Render conditionals in source order and fixup comments * Combine indenting/dedenting and operate in-place
1 parent 857fed9 commit 8991bc9

5 files changed

Lines changed: 230 additions & 15 deletions

File tree

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
module Example
2+
class Application < Rails::Application
3+
config.middleware.insert_before(
4+
ActionDispatch::Cookies,
5+
SessionCookieUpgrader
6+
) if Rails.env.staging? || Rails.env.production?
7+
end
8+
end
9+
10+
foo(
11+
a,
12+
b
13+
) if c
14+
15+
foo(
16+
a,
17+
b
18+
) unless c
19+
20+
foo(
21+
a,
22+
b
23+
) if some_method(
24+
x,
25+
y
26+
)
27+
28+
foo(a, b) if some_method(
29+
x,
30+
y
31+
)
32+
33+
# Surrounding blank lines and a leading comment should be preserved when
34+
# the modifier conditional gets converted to block form.
35+
36+
# leading comment
37+
config.middleware.insert_before(
38+
ActionDispatch::Cookies,
39+
SessionCookieUpgrader
40+
) if Rails.env.staging? || Rails.env.production?
41+
42+
# Multiple modifier conditionals separated by blank lines.
43+
foo(
44+
a
45+
) if c
46+
47+
bar(
48+
b
49+
) unless d
50+
51+
# Comments and blank lines inside the statement body are preserved.
52+
foo(
53+
a,
54+
# mid-arg comment
55+
b
56+
) if cond
57+
58+
foo(
59+
a,
60+
61+
b
62+
) if cond
63+
64+
foo(
65+
a,
66+
67+
# comment after blank
68+
b
69+
) if cond
70+
71+
# Trailing comment on the modifier-if line is hoisted as a leading comment.
72+
foo(
73+
a,
74+
b
75+
) if cond # trailing
76+
77+
def m
78+
foo(
79+
a,
80+
b
81+
) if cond # trailing
82+
end
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
module Example
2+
class Application < Rails::Application
3+
if Rails.env.staging? || Rails.env.production?
4+
config.middleware.insert_before(
5+
ActionDispatch::Cookies,
6+
SessionCookieUpgrader
7+
)
8+
end
9+
end
10+
end
11+
12+
if c
13+
foo(
14+
a,
15+
b
16+
)
17+
end
18+
19+
unless c
20+
foo(
21+
a,
22+
b
23+
)
24+
end
25+
26+
if some_method(
27+
x,
28+
y
29+
)
30+
foo(
31+
a,
32+
b
33+
)
34+
end
35+
36+
if some_method(
37+
x,
38+
y
39+
)
40+
foo(a, b)
41+
end
42+
43+
# Surrounding blank lines and a leading comment should be preserved when
44+
# the modifier conditional gets converted to block form.
45+
46+
# leading comment
47+
if Rails.env.staging? || Rails.env.production?
48+
config.middleware.insert_before(
49+
ActionDispatch::Cookies,
50+
SessionCookieUpgrader
51+
)
52+
end
53+
54+
# Multiple modifier conditionals separated by blank lines.
55+
if c
56+
foo(
57+
a
58+
)
59+
end
60+
61+
unless d
62+
bar(
63+
b
64+
)
65+
end
66+
67+
# Comments and blank lines inside the statement body are preserved.
68+
if cond
69+
foo(
70+
a,
71+
# mid-arg comment
72+
b
73+
)
74+
end
75+
76+
if cond
77+
foo(
78+
a,
79+
80+
b
81+
)
82+
end
83+
84+
if cond
85+
foo(
86+
a,
87+
88+
# comment after blank
89+
b
90+
)
91+
end
92+
93+
# Trailing comment on the modifier-if line is hoisted as a leading comment.
94+
# trailing
95+
if cond
96+
foo(
97+
a,
98+
b
99+
)
100+
end
101+
102+
def m
103+
# trailing
104+
if cond
105+
foo(
106+
a,
107+
b
108+
)
109+
end
110+
end

librubyfmt/src/comment_block.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,24 @@ impl CommentBlock {
5454
})
5555
}
5656

57+
/// Set each comment's leading indent to exactly `indent_depth` spaces
5758
pub fn apply_spaces(mut self, indent_depth: ColNumber) -> Self {
58-
if indent_depth == 0 {
59-
return self;
60-
}
61-
62-
let indent = get_indent(indent_depth as usize);
59+
let target = indent_depth as usize;
6360
for comment in &mut self.comments {
6461
// Ignore empty vecs -- these represent blank lines between
6562
// groups of comments
66-
if !comment.is_empty() && !comment.starts_with(b"=begin") {
67-
comment.to_mut().splice(0..0, indent.iter().copied());
63+
if comment.is_empty() || comment.starts_with(b"=begin") {
64+
continue;
65+
}
66+
let current = comment.iter().take_while(|&&b| b == b' ').count();
67+
if current == target {
68+
continue;
69+
}
70+
if current > target {
71+
comment.to_mut().drain(0..(current - target));
72+
} else {
73+
let extra = get_indent(target - current);
74+
comment.to_mut().splice(0..0, extra.iter().copied());
6875
}
6976
}
7077
self

librubyfmt/src/parser_state.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -958,19 +958,21 @@ impl<'src> ParserState<'src> {
958958
self.breakable_entry_stack
959959
.push(Breakable::InlineConditional(entry));
960960

961+
// Format in source order — statement before predicate — so that
962+
// `on_line` calls advance in the order of the source
961963
self.with_start_of_line(false, |ps| {
962-
ps.new_block(format_predicate);
964+
ps.new_block(format_statement);
963965
});
964966

965967
self.breakable_entry_stack
966968
.last_mut()
967969
.expect("just pushed InlineConditional")
968970
.as_conditional_layout_mut()
969971
.expect("just pushed InlineConditional")
970-
.switch_to_statement();
972+
.switch_to_predicate();
971973

972974
self.with_start_of_line(false, |ps| {
973-
ps.new_block(format_statement);
975+
ps.new_block(format_predicate);
974976
});
975977

976978
let cle = self
@@ -980,7 +982,21 @@ impl<'src> ParserState<'src> {
980982
.into_conditional_layout()
981983
.expect("InlineConditional always returns Some from into_conditional_layout");
982984

985+
// Preserve any comments that were extracted during the conditional
986+
// (e.g. a trailing `# comment` on the modifier line) so they aren't
987+
// dropped when restoring the comments that were pending beforehand.
988+
// The accumulated comments may have an inner-scope indent baked in
989+
// (from `apply_spaces` at capture time), so re-apply at the
990+
// conditional's outer indent before merging.
991+
let outer_spaces = self.current_spaces();
992+
let accumulated = self
993+
.comments_to_insert
994+
.take()
995+
.map(|c| c.apply_spaces(outer_spaces));
983996
self.comments_to_insert = saved_comments;
997+
if let Some(acc) = accumulated {
998+
self.comments_to_insert.merge(acc);
999+
}
9841000

9851001
self.push_target(ConcreteLineTokenAndTargets::ConditionalLayoutEntry(cle));
9861002
}

librubyfmt/src/render_targets.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ impl<'src> ConditionalLayoutEntry<'src> {
533533
statement_tokens: Vec::new(),
534534
keyword,
535535
indent_depth,
536-
phase: ConditionalLayoutPhase::Predicate,
536+
phase: ConditionalLayoutPhase::Statement,
537537
}
538538
}
539539

@@ -544,13 +544,13 @@ impl<'src> ConditionalLayoutEntry<'src> {
544544
}
545545
}
546546

547-
pub fn switch_to_statement(&mut self) {
547+
pub fn switch_to_predicate(&mut self) {
548548
debug_assert_eq!(
549549
self.phase,
550-
ConditionalLayoutPhase::Predicate,
551-
"switch_to_statement called when not in Predicate phase"
550+
ConditionalLayoutPhase::Statement,
551+
"switch_to_predicate called when not in Statement phase"
552552
);
553-
self.phase = ConditionalLayoutPhase::Statement;
553+
self.phase = ConditionalLayoutPhase::Predicate;
554554
}
555555

556556
/// Returns the number of tokens in the current phase's token list.

0 commit comments

Comments
 (0)