Skip to content

Commit 658fa30

Browse files
authored
feat: conditionalExpression.linePerExpression (#407)
1 parent 50e9333 commit 658fa30

File tree

9 files changed

+299
-72
lines changed

9 files changed

+299
-72
lines changed

deployment/schema.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,18 @@
303303
"description": "Maintains the line breaks as written by the programmer."
304304
}]
305305
},
306+
"conditionalExpression.linePerExpression": {
307+
"description": "Whether to force a line per expression when spanning multiple lines.",
308+
"type": "boolean",
309+
"default": true,
310+
"oneOf": [{
311+
"const": true,
312+
"description": "Formats with each part on a new line."
313+
}, {
314+
"const": false,
315+
"description": "Maintains the line breaks as written by the programmer."
316+
}]
317+
},
306318
"memberExpression.linePerExpression": {
307319
"description": "Whether to force a line per expression when spanning multiple lines.",
308320
"type": "boolean",

src/configuration/builder.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,14 @@ impl ConfigurationBuilder {
456456
self.insert("binaryExpression.linePerExpression", value.into())
457457
}
458458

459+
/// Whether to force a line per expression when spanning multiple lines.
460+
///
461+
/// * `true` - Formats with each part on a new line.
462+
/// * `false` (default) - Maintains the line breaks as written by the programmer.
463+
pub fn conditional_expression_line_per_expression(&mut self, value: bool) -> &mut Self {
464+
self.insert("conditionalExpression.linePerExpression", value.into())
465+
}
466+
459467
/// Whether to force a line per expression when spanning multiple lines.
460468
///
461469
/// * `true` - Formats with each part on a new line.
@@ -1032,6 +1040,7 @@ mod tests {
10321040
/* situational */
10331041
.arrow_function_use_parentheses(UseParentheses::Maintain)
10341042
.binary_expression_line_per_expression(false)
1043+
.conditional_expression_line_per_expression(true)
10351044
.member_expression_line_per_expression(false)
10361045
.type_literal_separator_kind(SemiColonOrComma::Comma)
10371046
.type_literal_separator_kind_single_line(SemiColonOrComma::Comma)
@@ -1198,7 +1207,7 @@ mod tests {
11981207
.while_statement_space_around(true);
11991208

12001209
let inner_config = config.get_inner_config();
1201-
assert_eq!(inner_config.len(), 169);
1210+
assert_eq!(inner_config.len(), 170);
12021211
let diagnostics = resolve_config(inner_config, &resolve_global_config(ConfigKeyMap::new(), &Default::default()).config).diagnostics;
12031212
assert_eq!(diagnostics.len(), 0);
12041213
}

src/configuration/resolve_config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ pub fn resolve_config(config: ConfigKeyMap, global_config: &GlobalConfiguration)
7979
/* situational */
8080
arrow_function_use_parentheses: get_value(&mut config, "arrowFunction.useParentheses", UseParentheses::Maintain, &mut diagnostics),
8181
binary_expression_line_per_expression: get_value(&mut config, "binaryExpression.linePerExpression", false, &mut diagnostics),
82+
conditional_expression_line_per_expression: get_value(&mut config, "conditionalExpression.linePerExpression", true, &mut diagnostics),
8283
jsx_quote_style: get_value(&mut config, "jsx.quoteStyle", quote_style.to_jsx_quote_style(), &mut diagnostics),
8384
jsx_multi_line_parens: get_value(&mut config, "jsx.multiLineParens", JsxMultiLineParens::Prefer, &mut diagnostics),
8485
jsx_force_new_lines_surrounding_content: get_value(&mut config, "jsx.forceNewLinesSurroundingContent", false, &mut diagnostics),

src/configuration/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,8 @@ pub struct Configuration {
281281
pub arrow_function_use_parentheses: UseParentheses,
282282
#[serde(rename = "binaryExpression.linePerExpression")]
283283
pub binary_expression_line_per_expression: bool,
284+
#[serde(rename = "conditionalExpression.linePerExpression")]
285+
pub conditional_expression_line_per_expression: bool,
284286
#[serde(rename = "jsx.quoteStyle")]
285287
pub jsx_quote_style: JsxQuoteStyle,
286288
#[serde(rename = "jsx.multiLineParens")]

src/generation/generate.rs

Lines changed: 135 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2151,94 +2151,111 @@ fn gen_class_expr<'a>(node: &'a ClassExpr, context: &mut Context<'a>) -> PrintIt
21512151
}
21522152

21532153
fn gen_conditional_expr<'a>(node: &'a CondExpr, context: &mut Context<'a>) -> PrintItems {
2154-
let operator_token = context.token_finder.get_first_operator_after(&node.test, "?").unwrap();
2155-
let force_new_lines = !context.config.conditional_expression_prefer_single_line
2156-
&& (node_helpers::get_use_new_lines_for_nodes(&node.test, &node.cons, context.program)
2157-
|| node_helpers::get_use_new_lines_for_nodes(&node.cons, &node.alt, context.program));
2158-
let operator_position = get_operator_position(node, operator_token, context);
2154+
let question_token = context.token_finder.get_first_operator_after(&node.test, "?").unwrap();
2155+
let colon_token = context.token_finder.get_first_operator_after(&node.cons, ":").unwrap();
2156+
let line_per_expression = context.config.conditional_expression_line_per_expression;
2157+
let has_newline_test_cons = node_helpers::get_use_new_lines_for_nodes(&node.test, &node.cons, context.program);
2158+
let has_newline_const_alt = node_helpers::get_use_new_lines_for_nodes(&node.cons, &node.alt, context.program);
2159+
let mut force_test_cons_newline = !context.config.conditional_expression_prefer_single_line && has_newline_test_cons;
2160+
let mut force_cons_alt_newline = !context.config.conditional_expression_prefer_single_line && has_newline_const_alt;
2161+
if line_per_expression && (force_test_cons_newline || force_cons_alt_newline) {
2162+
// for line per expression, if one is true then both should be true
2163+
force_test_cons_newline = true;
2164+
force_cons_alt_newline = true;
2165+
}
2166+
2167+
let operator_position = get_operator_position(node, question_token, context);
21592168
let top_most_data = get_top_most_data(node, context);
21602169
let before_alternate_ln = LineNumber::new("beforeAlternate");
21612170
let end_ln = LineNumber::new("endConditionalExpression");
2171+
let question_comment_items = gen_token_comments(question_token, context, top_most_data.il);
2172+
let colon_comment_items = gen_token_comments(colon_token, context, top_most_data.il);
21622173
let mut items = PrintItems::new();
21632174

21642175
if top_most_data.is_top_most {
2165-
items.push_info(top_most_data.top_most_ln);
2166-
items.push_info(top_most_data.top_most_il);
2176+
items.push_info(top_most_data.ln);
2177+
items.push_info(top_most_data.il);
21672178
}
21682179

2169-
items.extend(ir_helpers::new_line_group(with_queued_indent(gen_node_with_inner_gen(
2170-
node.test.into(),
2171-
context,
2172-
{
2173-
move |mut items, _| {
2174-
if operator_position == OperatorPosition::SameLine {
2175-
items.push_str(" ?");
2176-
}
2177-
items
2178-
}
2179-
},
2180-
))));
2180+
let top_most_il = top_most_data.il;
2181+
2182+
items.extend(ir_helpers::new_line_group(with_queued_indent({
2183+
let mut items = gen_node(node.test.into(), context);
2184+
if operator_position == OperatorPosition::SameLine {
2185+
items.push_str(" ?");
2186+
}
2187+
items.extend(question_comment_items.trailing_line);
2188+
items
2189+
})));
2190+
2191+
items.extend(question_comment_items.previous_lines);
21812192

21822193
items.push_anchor(LineNumberAnchor::new(end_ln));
21832194
items.push_anchor(LineNumberAnchor::new(before_alternate_ln));
21842195

2185-
let multi_line_reevaluation = if force_new_lines {
2196+
let multi_line_reevaluation = if force_test_cons_newline {
21862197
items.push_signal(Signal::NewLine);
21872198
None
2188-
} else {
2189-
let mut condition = conditions::new_line_if_multiple_lines_space_or_new_line_otherwise(top_most_data.top_most_ln, Some(before_alternate_ln));
2199+
} else if line_per_expression {
2200+
let mut condition = conditions::new_line_if_multiple_lines_space_or_new_line_otherwise(top_most_data.ln, Some(before_alternate_ln));
21902201
let reevaluation = condition.create_reevaluation();
21912202
items.push_condition(condition);
21922203
Some(reevaluation)
2204+
} else {
2205+
items.push_signal(Signal::SpaceOrNewLine);
2206+
None
21932207
};
21942208

21952209
let cons_and_alt_items = {
21962210
let mut items = PrintItems::new();
21972211

21982212
// add any preceeding comments of the question token
21992213
items.extend({
2200-
let operator_token_leading_comments = get_leading_comments_on_previous_lines(&operator_token.range(), context);
2214+
let operator_token_leading_comments = get_leading_comments_on_previous_lines(&question_token.range(), context);
22012215
let mut items = gen_comment_collection(operator_token_leading_comments.into_iter(), None, None, context);
22022216
if !items.is_empty() {
22032217
items.push_signal(Signal::NewLine);
22042218
}
22052219
items
22062220
});
22072221

2208-
if operator_position == OperatorPosition::NextLine {
2209-
items.push_str("? ");
2210-
}
2211-
items.extend(ir_helpers::new_line_group(gen_node_with_inner_gen(node.cons.into(), context, {
2212-
move |mut items, _| {
2213-
if operator_position == OperatorPosition::SameLine {
2214-
items.push_str(" :");
2215-
items
2216-
} else {
2217-
conditions::indent_if_start_of_line(items).into()
2218-
}
2222+
items.push_condition({
2223+
let mut items = PrintItems::new();
2224+
items.extend(question_comment_items.leading_line);
2225+
if operator_position == OperatorPosition::NextLine {
2226+
items.push_str("? ");
22192227
}
2220-
})));
2228+
items.extend(gen_node(node.cons.into(), context));
2229+
if operator_position == OperatorPosition::SameLine {
2230+
items.push_str(" :");
2231+
}
2232+
items.extend(colon_comment_items.trailing_line);
2233+
indent_if_sol_and_same_indent_as_top_most(ir_helpers::new_line_group(items), top_most_il)
2234+
});
22212235

2222-
if force_new_lines {
2236+
items.extend(colon_comment_items.previous_lines);
2237+
2238+
if force_cons_alt_newline {
22232239
items.push_signal(Signal::NewLine);
2224-
} else {
2240+
} else if line_per_expression {
22252241
items.push_condition(conditions::new_line_if_multiple_lines_space_or_new_line_otherwise(
2226-
top_most_data.top_most_ln,
2242+
top_most_data.ln,
22272243
Some(before_alternate_ln),
22282244
));
2245+
} else {
2246+
items.push_signal(Signal::SpaceOrNewLine);
22292247
}
22302248

2231-
if operator_position == OperatorPosition::NextLine {
2232-
items.push_str(": ");
2233-
}
2234-
items.push_info(before_alternate_ln);
2235-
items.extend(ir_helpers::new_line_group(gen_node_with_inner_gen(node.alt.into(), context, |items, _| {
2249+
items.push_condition({
2250+
let mut items = PrintItems::new();
2251+
items.extend(colon_comment_items.leading_line);
22362252
if operator_position == OperatorPosition::NextLine {
2237-
conditions::indent_if_start_of_line(items).into()
2238-
} else {
2239-
items
2253+
items.push_str(": ");
22402254
}
2241-
})));
2255+
items.push_info(before_alternate_ln);
2256+
items.extend(gen_node(node.alt.into(), context));
2257+
indent_if_sol_and_same_indent_as_top_most(ir_helpers::new_line_group(items), top_most_il)
2258+
});
22422259
items.push_info(end_ln);
22432260

22442261
if let Some(reevaluation) = multi_line_reevaluation {
@@ -2251,9 +2268,69 @@ fn gen_conditional_expr<'a>(node: &'a CondExpr, context: &mut Context<'a>) -> Pr
22512268
if top_most_data.is_top_most {
22522269
items.push_condition(conditions::indent_if_start_of_line(cons_and_alt_items));
22532270
} else {
2254-
let cons_and_alt_items = cons_and_alt_items.into_rc_path();
2255-
let top_most_il = top_most_data.top_most_il;
2256-
items.push_condition(if_true_or(
2271+
items.push_condition(indent_if_sol_and_same_indent_as_top_most(cons_and_alt_items, top_most_data.il));
2272+
}
2273+
2274+
return items;
2275+
2276+
struct TokenComments {
2277+
previous_lines: PrintItems,
2278+
leading_line: PrintItems,
2279+
trailing_line: PrintItems,
2280+
}
2281+
2282+
fn gen_token_comments(token: &TokenAndSpan, context: &mut Context, top_most_il: IndentLevel) -> TokenComments {
2283+
let token_line = token.end_line_fast(context.program);
2284+
let previous_token = token.previous_token_fast(context.program).unwrap();
2285+
let next_token = token.next_token_fast(context.program).unwrap();
2286+
let previous_token_line = previous_token.end_line_fast(context.program);
2287+
let next_token_line = next_token.start_line_fast(context.program);
2288+
if token_line > previous_token_line {
2289+
TokenComments {
2290+
previous_lines: {
2291+
let leading_comments = token.leading_comments_fast(context.program).filter(|c| {
2292+
let comment_line = c.start_line_fast(context.program);
2293+
comment_line > previous_token_line && comment_line < next_token_line
2294+
});
2295+
let trailing_comments = token
2296+
.trailing_comments_fast(context.program)
2297+
.filter(|c| c.start_line_fast(context.program) < next_token_line);
2298+
let items = gen_comments_as_statements(leading_comments.chain(trailing_comments), None, context);
2299+
if items.is_empty() {
2300+
items
2301+
} else {
2302+
let mut new_items = PrintItems::new();
2303+
new_items.push_signal(Signal::NewLine);
2304+
new_items.push_condition(indent_if_sol_and_same_indent_as_top_most(items, top_most_il));
2305+
new_items
2306+
}
2307+
},
2308+
leading_line: if token_line < next_token_line {
2309+
gen_leading_comments_same_line(&token.range(), context)
2310+
} else {
2311+
PrintItems::new()
2312+
},
2313+
trailing_line: PrintItems::new(),
2314+
}
2315+
} else if token_line < next_token_line {
2316+
TokenComments {
2317+
previous_lines: PrintItems::new(),
2318+
leading_line: PrintItems::new(),
2319+
trailing_line: gen_trailing_comments_same_line(&token.range(), context),
2320+
}
2321+
} else {
2322+
// do nothing
2323+
TokenComments {
2324+
previous_lines: PrintItems::new(),
2325+
leading_line: PrintItems::new(),
2326+
trailing_line: PrintItems::new(),
2327+
}
2328+
}
2329+
}
2330+
2331+
fn indent_if_sol_and_same_indent_as_top_most(items: PrintItems, top_most_il: IndentLevel) -> Condition {
2332+
let items = items.into_rc_path();
2333+
if_true_or(
22572334
"indentIfSameIndentationAsTopMostAndStartOfLine",
22582335
Rc::new(move |context| {
22592336
if context.writer_info.is_start_of_line() {
@@ -2263,16 +2340,14 @@ fn gen_conditional_expr<'a>(node: &'a CondExpr, context: &mut Context<'a>) -> Pr
22632340
Some(false)
22642341
}
22652342
}),
2266-
with_indent(cons_and_alt_items.into()),
2267-
cons_and_alt_items.into(),
2268-
));
2343+
with_indent(items.into()),
2344+
items.into(),
2345+
)
22692346
}
22702347

2271-
return items;
2272-
22732348
struct TopMostData {
2274-
top_most_ln: LineNumber,
2275-
top_most_il: IndentLevel,
2349+
ln: LineNumber,
2350+
il: IndentLevel,
22762351
is_top_most: bool,
22772352
}
22782353

@@ -2298,8 +2373,8 @@ fn gen_conditional_expr<'a>(node: &'a CondExpr, context: &mut Context<'a>) -> Pr
22982373

22992374
return TopMostData {
23002375
is_top_most,
2301-
top_most_ln,
2302-
top_most_il,
2376+
ln: top_most_ln,
2377+
il: top_most_il,
23032378
};
23042379

23052380
fn get_or_set_top_most_ln(top_most_expr_start: SourcePos, is_top_most: bool, context: &mut Context) -> (LineNumber, IndentLevel) {
@@ -5947,7 +6022,7 @@ fn get_trailing_comments_on_same_line<'a>(node: &dyn SourceRanged, context: &mut
59476022
} else {
59486023
let node_start_line = node.start_line_fast(context.program);
59496024
trailing_comments
5950-
.take_while(|c| c.kind == CommentKind::Block || c.start_line_fast(context.program) == node_start_line)
6025+
.take_while(|c| c.start_line_fast(context.program) == node_start_line)
59516026
.collect::<Vec<_>>()
59526027
}
59536028
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
~~ conditionalExpression.linePerExpression: false, lineWidth: 40 ~~
2+
== should move only alt over ==
3+
const t = value < 0 ? true : testing > 0;
4+
5+
[expect]
6+
const t = value < 0 ? true
7+
: testing > 0;
8+
9+
== should move when cons over ==
10+
const t = value < 0 ? asdftestingtestingg : test > 0;
11+
const t = value < 0 ? asdftestingtestingg : testinging > 0;
12+
13+
[expect]
14+
const t = value < 0
15+
? asdftestingtestingg : test > 0;
16+
const t = value < 0
17+
? asdftestingtestingg
18+
: testinging > 0;
19+
20+
== should maintain match like formatting ==
21+
const t = value < 0 ? true
22+
: value === 1 ? "1"
23+
: value === 2 ? "2"
24+
: false;
25+
26+
[expect]
27+
const t = value < 0 ? true
28+
: value === 1 ? "1"
29+
: value === 2 ? "2"
30+
: false;
31+
32+
== should support putting expressions on any line ==
33+
const t = value < 0
34+
? true : value === 1
35+
? "1" : value === 2 ? "2" : false;
36+
37+
[expect]
38+
const t = value < 0
39+
? true : value === 1
40+
? "1" : value === 2 ? "2" : false;

0 commit comments

Comments
 (0)