Skip to content

Commit 435c162

Browse files
authored
Model inline conditionals as breakables (#778)
* [prism] Model inline conditionals as breakables * fmt * clippy * Remove dead code * Add fixture * More dead code after rebase * Add testcase
1 parent 5441514 commit 435c162

File tree

7 files changed

+367
-81
lines changed

7 files changed

+367
-81
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
some_really_long_method_name_that_takes_up_space! if another_really_long_condition_that_makes_this_line_exceed_one_hundred_twenty_chars
2+
3+
some_really_long_method_name_that_takes_up_space! unless another_really_long_condition_that_makes_this_line_exceed_one_hundred_twenty
4+
5+
short_method_call if a_condition_that_fits_within_the_line_length_limit_of_one_hundred_twenty
6+
7+
module X
8+
class Y
9+
module More
10+
def test
11+
# comments make a difference?
12+
amount = amount_from_api ||
13+
(Opus::MonetaryFlows::Private::API::BeesItems::ParamTransformer.validation_amount(line_items) if !line_items.nil?)
14+
end
15+
end
16+
end
17+
end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
if another_really_long_condition_that_makes_this_line_exceed_one_hundred_twenty_chars
2+
some_really_long_method_name_that_takes_up_space!
3+
end
4+
5+
unless another_really_long_condition_that_makes_this_line_exceed_one_hundred_twenty
6+
some_really_long_method_name_that_takes_up_space!
7+
end
8+
9+
short_method_call if a_condition_that_fits_within_the_line_length_limit_of_one_hundred_twenty
10+
11+
module X
12+
class Y
13+
module More
14+
def test
15+
# comments make a difference?
16+
amount = amount_from_api ||
17+
(
18+
if !line_items.nil?
19+
Opus::MonetaryFlows::Private::API::BeesItems::ParamTransformer.validation_amount(line_items)
20+
end
21+
)
22+
end
23+
end
24+
end
25+
end

librubyfmt/src/format_prism.rs

Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3757,47 +3757,29 @@ fn format_conditional_node<'src>(
37573757
// For while/until, always use the inline format for modifiers, since
37583758
// some transformations are unsafe.
37593759
// For if/unless, check if we should convert to block form.
3760-
let should_convert_to_block = match conditional {
3761-
Conditional::While(_) | Conditional::Until(_) => false,
3760+
match conditional {
3761+
Conditional::While(_) | Conditional::Until(_) => {
3762+
// Always use inline format for while/until modifiers
3763+
format_inline_conditional(
3764+
ps,
3765+
conditional.predicate(),
3766+
conditional.statements(),
3767+
conditional_keyword,
3768+
);
3769+
}
37623770
Conditional::If(_) | Conditional::Unless(_) => {
3763-
let predicate = conditional.predicate();
3764-
let statements = conditional.statements();
3765-
let predicate_start_line =
3766-
ps.get_line_number_for_offset(predicate.location().start_offset());
3767-
let predicate_end_line =
3768-
ps.get_line_number_for_offset(predicate.location().end_offset());
3769-
if predicate_start_line != predicate_end_line {
3770-
true
3771-
} else {
3772-
// Check if it renders multiline due to length
3773-
ps.will_render_as_multiline(|next_ps| {
3774-
format_inline_conditional(
3775-
next_ps,
3776-
predicate,
3777-
statements,
3778-
conditional_keyword,
3779-
)
3780-
})
3781-
}
3771+
ps.conditional_layout_of(
3772+
conditional_keyword,
3773+
|ps| format_node(ps, conditional.predicate()),
3774+
|ps| {
3775+
if let Some(statements) = conditional.statements()
3776+
&& let Some(first_statement) = statements.body().iter().next()
3777+
{
3778+
format_node(ps, first_statement);
3779+
}
3780+
},
3781+
);
37823782
}
3783-
};
3784-
3785-
if should_convert_to_block {
3786-
format_conditional_block_form(
3787-
ps,
3788-
conditional_keyword,
3789-
conditional.predicate(),
3790-
conditional.statements(),
3791-
conditional.subsequent_or_else(),
3792-
requires_end_keyword,
3793-
);
3794-
} else {
3795-
format_inline_conditional(
3796-
ps,
3797-
conditional.predicate(),
3798-
conditional.statements(),
3799-
conditional_keyword,
3800-
);
38013783
}
38023784
} else {
38033785
format_conditional_block_form(

librubyfmt/src/line_tokens.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::heredoc_string::{HeredocKind, HeredocString};
2-
use crate::render_targets::{BreakableCallChainEntry, BreakableEntry};
2+
use crate::render_targets::{BreakableCallChainEntry, BreakableEntry, ConditionalLayoutEntry};
33
use crate::types::ColNumber;
44
use crate::util::get_indent;
55
use std::borrow::Cow;
@@ -229,6 +229,9 @@ impl<'src> From<ConcreteLineTokenAndTargets<'src>> for AbstractLineToken<'src> {
229229
ConcreteLineTokenAndTargets::BreakableCallChainEntry(bcce) => {
230230
AbstractLineToken::BreakableCallChainEntry(bcce)
231231
}
232+
ConcreteLineTokenAndTargets::ConditionalLayoutEntry(cle) => {
233+
AbstractLineToken::ConditionalLayoutEntry(cle)
234+
}
232235
}
233236
}
234237
}
@@ -238,6 +241,7 @@ pub enum ConcreteLineTokenAndTargets<'src> {
238241
ConcreteLineToken(ConcreteLineToken<'src>),
239242
BreakableEntry(BreakableEntry<'src>),
240243
BreakableCallChainEntry(BreakableCallChainEntry<'src>),
244+
ConditionalLayoutEntry(ConditionalLayoutEntry<'src>),
241245
}
242246

243247
impl<'src> ConcreteLineTokenAndTargets<'src> {
@@ -265,6 +269,7 @@ pub enum AbstractLineToken<'src> {
265269
SoftIndent { depth: u32 },
266270
BreakableEntry(BreakableEntry<'src>),
267271
BreakableCallChainEntry(BreakableCallChainEntry<'src>),
272+
ConditionalLayoutEntry(ConditionalLayoutEntry<'src>),
268273
}
269274

270275
impl<'src> AbstractLineToken<'src> {
@@ -292,6 +297,9 @@ impl<'src> AbstractLineToken<'src> {
292297
Self::BreakableCallChainEntry(bcce) => {
293298
out.push(ConcreteLineTokenAndTargets::BreakableCallChainEntry(bcce));
294299
}
300+
Self::ConditionalLayoutEntry(cle) => {
301+
out.push(ConcreteLineTokenAndTargets::ConditionalLayoutEntry(cle))
302+
}
295303
}
296304
}
297305

@@ -319,6 +327,9 @@ impl<'src> AbstractLineToken<'src> {
319327
Self::BreakableCallChainEntry(bcce) => {
320328
out.push(ConcreteLineTokenAndTargets::BreakableCallChainEntry(bcce));
321329
}
330+
Self::ConditionalLayoutEntry(cle) => {
331+
out.push(ConcreteLineTokenAndTargets::ConditionalLayoutEntry(cle))
332+
}
322333
}
323334
}
324335

@@ -390,6 +401,7 @@ impl<'src> AbstractLineToken<'src> {
390401
Self::ConcreteLineToken(clt) => clt.len(),
391402
Self::BreakableEntry(be) => be.single_line_len(),
392403
Self::BreakableCallChainEntry(bcce) => bcce.single_line_len(),
404+
Self::ConditionalLayoutEntry(cle) => cle.inline_single_line_len(),
393405
}
394406
}
395407
}

librubyfmt/src/parser_state.rs

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ use crate::delimiters::BreakableDelims;
33
use crate::file_comments::FileComments;
44
use crate::heredoc_string::{HeredocKind, HeredocSegment, HeredocString};
55
use crate::line_tokens::*;
6-
use crate::render_queue_writer::{MAX_LINE_LENGTH, RenderQueueWriter};
7-
use crate::render_targets::{BaseQueue, Breakable, BreakableCallChainEntry, BreakableEntry};
6+
use crate::render_queue_writer::RenderQueueWriter;
7+
use crate::render_targets::{
8+
BaseQueue, Breakable, BreakableCallChainEntry, BreakableEntry, ConditionalLayoutEntry,
9+
};
810
use crate::types::{ColNumber, LineNumber, SourceOffset};
911
use log::debug;
1012
use std::borrow::Cow;
11-
use std::io::{self, Cursor, Write};
13+
use std::io::{self, Write};
1214
use std::str;
1315

1416
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -176,19 +178,6 @@ impl<'src> ParserState<'src> {
176178
}
177179
}
178180

179-
pub(crate) fn will_render_as_multiline<F>(&mut self, f: F) -> bool
180-
where
181-
F: FnOnce(&mut ParserState<'src>),
182-
{
183-
let mut next_ps = ParserState::new_with_indent_from(self);
184-
// Ignore commments when determining line length
185-
next_ps.with_suppress_comments(true, f);
186-
let data = next_ps.render_to_buffer();
187-
188-
let s = str::from_utf8(&data).expect("string is utf8");
189-
s.trim().contains('\n') || s.len() > MAX_LINE_LENGTH
190-
}
191-
192181
pub(crate) fn has_comment_in_offset_span(
193182
&self,
194183
start_offset: SourceOffset,
@@ -322,15 +311,6 @@ impl<'src> ParserState<'src> {
322311
));
323312
}
324313

325-
pub(crate) fn with_suppress_comments<F>(&mut self, suppress: bool, f: F)
326-
where
327-
F: FnOnce(&mut ParserState<'src>),
328-
{
329-
self.suppress_comments_stack.push(suppress);
330-
f(self);
331-
self.suppress_comments_stack.pop();
332-
}
333-
334314
pub(crate) fn new_block<F>(&mut self, f: F)
335315
where
336316
F: FnOnce(&mut ParserState<'src>),
@@ -828,12 +808,6 @@ impl<'src> ParserState<'src> {
828808
}
829809
}
830810

831-
pub(crate) fn new_with_indent_from(ps: &ParserState<'src>) -> Self {
832-
let mut next_ps = ParserState::new_with_reset_indentation(ps);
833-
next_ps.indent_depth = ps.indent_depth;
834-
next_ps
835-
}
836-
837811
// Creates a copy of the parser state *with the indent_depth reset*.
838812
// This is used for heredocs, where we explicitly want to ignore current indentation.
839813
pub(crate) fn new_with_reset_indentation(ps: &ParserState<'src>) -> Self {
@@ -844,13 +818,6 @@ impl<'src> ParserState<'src> {
844818
next_ps
845819
}
846820

847-
pub(crate) fn render_to_buffer(self) -> Vec<u8> {
848-
let mut bufio = Cursor::new(Vec::new());
849-
self.write(&mut bufio).expect("in memory io cannot fail");
850-
bufio.set_position(0);
851-
bufio.into_inner()
852-
}
853-
854821
/// Convert the render queue to heredoc segments. This separates content into
855822
/// Normal segments (which should receive squiggly indentation) and Raw segments
856823
/// (content from nested non-squiggly heredocs that should not be indented).
@@ -968,6 +935,51 @@ impl<'src> ParserState<'src> {
968935
None => self.render_queue.push(Self::dangerously_convert(t)),
969936
}
970937
}
938+
939+
/// Format a conditional modifier expression (e.g., `x if y` or `x unless y`).
940+
pub(crate) fn conditional_layout_of<FP, FS>(
941+
&mut self,
942+
keyword: &'static str,
943+
format_predicate: FP,
944+
format_statement: FS,
945+
) where
946+
FP: FnOnce(&mut ParserState<'src>),
947+
FS: FnOnce(&mut ParserState<'src>),
948+
{
949+
// Save and clear any pending comments to prevent them from being
950+
// inserted into the conditional layout
951+
let saved_comments = self.comments_to_insert.take();
952+
953+
let entry = ConditionalLayoutEntry::new(keyword, self.current_spaces());
954+
self.breakable_entry_stack
955+
.push(Breakable::InlineConditional(entry));
956+
957+
self.with_start_of_line(false, |ps| {
958+
ps.new_block(format_predicate);
959+
});
960+
961+
self.breakable_entry_stack
962+
.last_mut()
963+
.expect("just pushed InlineConditional")
964+
.as_conditional_layout_mut()
965+
.expect("just pushed InlineConditional")
966+
.switch_to_statement();
967+
968+
self.with_start_of_line(false, |ps| {
969+
ps.new_block(format_statement);
970+
});
971+
972+
let cle = self
973+
.breakable_entry_stack
974+
.pop()
975+
.expect("just pushed InlineConditional")
976+
.into_conditional_layout()
977+
.expect("InlineConditional always returns Some from into_conditional_layout");
978+
979+
self.comments_to_insert = saved_comments;
980+
981+
self.push_target(ConcreteLineTokenAndTargets::ConditionalLayoutEntry(cle));
982+
}
971983
}
972984

973985
pub fn line_difference_requires_newline(to_line: LineNumber, from_line: LineNumber) -> bool {

librubyfmt/src/render_queue_writer.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use crate::heredoc_string::HeredocKind;
22
use crate::intermediary::{BlanklineReason, Intermediary};
33
use crate::line_tokens::*;
44
use crate::render_targets::{
5-
AbstractTokenTarget, BreakableCallChainEntry, BreakableEntry, ConvertType,
5+
AbstractTokenTarget, BreakableCallChainEntry, BreakableEntry, ConditionalLayoutEntry,
6+
ConvertType,
67
};
78
use crate::util::get_indent;
89
#[cfg(debug_assertions)]
@@ -126,6 +127,9 @@ impl<'src> RenderQueueWriter<'src> {
126127
ConcreteLineTokenAndTargets::BreakableCallChainEntry(bcce) => {
127128
Self::format_breakable_call_chain_entry(accum, bcce)
128129
}
130+
ConcreteLineTokenAndTargets::ConditionalLayoutEntry(cle) => {
131+
Self::format_conditional_layout_entry(accum, cle)
132+
}
129133
ConcreteLineTokenAndTargets::ConcreteLineToken(x) => match x {
130134
BeginCallChainIndent => accum.additional_indent += 1,
131135
EndCallChainIndent => accum.additional_indent -= 1,
@@ -245,6 +249,17 @@ impl<'src> RenderQueueWriter<'src> {
245249
}
246250
}
247251

252+
fn format_conditional_layout_entry(
253+
accum: &mut Intermediary<'src>,
254+
cle: ConditionalLayoutEntry<'src>,
255+
) {
256+
if cle.should_use_block_form(accum.current_line_length()) {
257+
Self::render_as(accum, cle.into_block_tokens());
258+
} else {
259+
Self::render_as(accum, cle.into_inline_tokens());
260+
}
261+
}
262+
248263
fn renders_over_max_line_length(
249264
accum: &Intermediary<'src>,
250265
breakable: &dyn AbstractTokenTarget<'src>,

0 commit comments

Comments
 (0)