Skip to content
48 changes: 36 additions & 12 deletions deployment/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -318,18 +318,6 @@
"description": "Maintains the line breaks as written by the programmer."
}]
},
"conditionalExpression.linePerExpression": {
"description": "Whether to force a line per expression when spanning multiple lines.",
"type": "boolean",
"default": true,
"oneOf": [{
"const": true,
"description": "Formats with each part on a new line."
}, {
"const": false,
"description": "Maintains the line breaks as written by the programmer."
}]
},
"memberExpression.linePerExpression": {
"description": "Whether to force a line per expression when spanning multiple lines.",
"type": "boolean",
Expand Down Expand Up @@ -793,6 +781,42 @@
"binaryExpression.linePerExpression": {
"$ref": "#/definitions/binaryExpression.linePerExpression"
},
"conditionalExpression.linePerExpression": {
"description": "Whether to force a line per expression when spanning multiple lines.",
"type": "boolean",
"default": true,
"oneOf": [{
"const": true,
"description": "Formats with each part on a new line."
}, {
"const": false,
"description": "Maintains the line breaks as written by the programmer."
}]
},
"conditionalExpression.useNestedIndentation": {
"description": "Whether to use nested indentation within conditional expressions.",
"type": "boolean",
"default": true,
"oneOf": [{
"const": true,
"description": "Uses nested indentation in the true and false expressions."
}, {
"const": false,
"description": "Don't use nested indentation."
}]
},
"conditionalType.useNestedIndentation": {
"description": "Whether to use nested indentation within conditional types.",
"type": "boolean",
"default": true,
"oneOf": [{
"const": true,
"description": "Uses nested indentation in the true and false expressions."
}, {
"const": false,
"description": "Don't use nested indentation."
}]
},
"jsx.bracketPosition": {
"$ref": "#/definitions/jsx.bracketPosition"
},
Expand Down
15 changes: 14 additions & 1 deletion src/configuration/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,16 @@ impl ConfigurationBuilder {
self.insert("whileStatement.preferHanging", value.into())
}

/* situational indentation */

pub fn conditional_expression_use_nested_indentation(&mut self, value: bool) -> &mut Self {
self.insert("conditionalExpression.useNestedIndentation", value.into())
}

pub fn conditional_type_use_nested_indentation(&mut self, value: bool) -> &mut Self {
self.insert("conditionalType.useNestedIndentation", value.into())
}

/* force single line */

pub fn export_declaration_force_single_line(&mut self, value: bool) -> &mut Self {
Expand Down Expand Up @@ -1138,6 +1148,9 @@ mod tests {
.union_and_intersection_type_prefer_hanging(true)
.variable_statement_prefer_hanging(true)
.while_statement_prefer_hanging(true)
/* situational indentation */
.conditional_expression_use_nested_indentation(false)
.conditional_type_use_nested_indentation(false)
/* member spacing */
.enum_declaration_member_spacing(MemberSpacing::Maintain)
/* next control flow position */
Expand Down Expand Up @@ -1246,7 +1259,7 @@ mod tests {
.while_statement_space_around(true);

let inner_config = config.get_inner_config();
assert_eq!(inner_config.len(), 175);
assert_eq!(inner_config.len(), 177);
let diagnostics = resolve_config(inner_config, &resolve_global_config(ConfigKeyMap::new(), &Default::default()).config).diagnostics;
assert_eq!(diagnostics.len(), 0);
}
Expand Down
3 changes: 3 additions & 0 deletions src/configuration/resolve_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ pub fn resolve_config(config: ConfigKeyMap, global_config: &GlobalConfiguration)
union_and_intersection_type_prefer_hanging: get_value(&mut config, "unionAndIntersectionType.preferHanging", prefer_hanging, &mut diagnostics),
variable_statement_prefer_hanging: get_value(&mut config, "variableStatement.preferHanging", prefer_hanging, &mut diagnostics),
while_statement_prefer_hanging: get_value(&mut config, "whileStatement.preferHanging", prefer_hanging, &mut diagnostics),
/* situational indentation */
conditional_expression_use_nested_indentation: get_value(&mut config, "conditionalExpression.useNestedIndentation", false, &mut diagnostics),
conditional_type_use_nested_indentation: get_value(&mut config, "conditionalType.useNestedIndentation", false, &mut diagnostics),
/* member spacing */
enum_declaration_member_spacing: get_value(&mut config, "enumDeclaration.memberSpacing", MemberSpacing::Maintain, &mut diagnostics),
/* next control flow position */
Expand Down
5 changes: 5 additions & 0 deletions src/configuration/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,11 @@ pub struct Configuration {
pub variable_statement_prefer_hanging: bool,
#[serde(rename = "whileStatement.preferHanging")]
pub while_statement_prefer_hanging: bool,
/* situational indentation */
#[serde(rename = "conditionalExpression.useNestedIndentation")]
pub conditional_expression_use_nested_indentation: bool,
#[serde(rename = "conditionalType.useNestedIndentation")]
pub conditional_type_use_nested_indentation: bool,
/* member spacing */
#[serde(rename = "enumDeclaration.memberSpacing")]
pub enum_declaration_member_spacing: MemberSpacing,
Expand Down
28 changes: 20 additions & 8 deletions src/generation/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2180,6 +2180,9 @@ fn gen_conditional_expr<'a>(node: &'a CondExpr, context: &mut Context<'a>) -> Pr
force_test_cons_newline = true;
force_cons_alt_newline = true;
}
let is_parent_conditional_expr = node.parent().kind() == NodeKind::CondExpr;
let is_nested_conditional = is_parent_conditional_expr || node.cons.kind() == NodeKind::CondExpr || node.alt.kind() == NodeKind::CondExpr;
let use_new_lines_for_nested_conditional = context.config.conditional_expression_use_nested_indentation && is_nested_conditional;

let (question_position, colon_position) = get_operator_position(node, question_token, colon_token, context);
let top_most_data = get_top_most_data(node, context);
Expand Down Expand Up @@ -2210,7 +2213,7 @@ fn gen_conditional_expr<'a>(node: &'a CondExpr, context: &mut Context<'a>) -> Pr
items.push_anchor(LineNumberAnchor::new(end_ln));
items.push_anchor(LineNumberAnchor::new(before_alternate_ln));

let multi_line_reevaluation = if force_test_cons_newline {
let multi_line_reevaluation = if force_test_cons_newline || use_new_lines_for_nested_conditional {
items.push_signal(Signal::NewLine);
None
} else if line_per_expression {
Expand Down Expand Up @@ -2252,7 +2255,7 @@ fn gen_conditional_expr<'a>(node: &'a CondExpr, context: &mut Context<'a>) -> Pr

items.extend(colon_comment_items.previous_lines);

if force_cons_alt_newline {
if force_cons_alt_newline || use_new_lines_for_nested_conditional {
items.push_signal(Signal::NewLine);
} else if line_per_expression {
items.push_condition(conditions::new_line_if_multiple_lines_space_or_new_line_otherwise(
Expand Down Expand Up @@ -2282,7 +2285,7 @@ fn gen_conditional_expr<'a>(node: &'a CondExpr, context: &mut Context<'a>) -> Pr
items
};

if top_most_data.is_top_most {
if use_new_lines_for_nested_conditional {
items.push_condition(conditions::indent_if_start_of_line(cons_and_alt_items));
} else {
items.push_condition(indent_if_sol_and_same_indent_as_top_most(cons_and_alt_items, top_most_data.il));
Expand Down Expand Up @@ -5212,10 +5215,13 @@ fn gen_array_type<'a>(node: &'a TsArrayType, context: &mut Context<'a>) -> Print
}

fn gen_conditional_type<'a>(node: &'a TsConditionalType, context: &mut Context<'a>) -> PrintItems {
let use_new_lines =
!context.config.conditional_type_prefer_single_line && node_helpers::get_use_new_lines_for_nodes(&node.true_type, &node.false_type, context.program);
let top_most_data = get_top_most_data(node, context);
let is_parent_conditional_type = node.parent().kind() == NodeKind::TsConditionalType;
let is_nested_conditional =
is_parent_conditional_type || node.true_type.kind() == NodeKind::TsConditionalType || node.false_type.kind() == NodeKind::TsConditionalType;
let use_new_lines_for_nested_conditional = context.config.conditional_type_use_nested_indentation && is_nested_conditional;
let force_new_lines_for_false_type =
!context.config.conditional_type_prefer_single_line && node_helpers::get_use_new_lines_for_nodes(&node.true_type, &node.false_type, context.program);
let mut items = PrintItems::new();
let before_false_ln = LineNumber::new("beforeFalse");
let question_token = context.token_finder.get_first_operator_after(&node.extends_type, "?").unwrap();
Expand Down Expand Up @@ -5243,7 +5249,9 @@ fn gen_conditional_type<'a>(node: &'a TsConditionalType, context: &mut Context<'
items
})));

if question_comment_items.previous_lines.is_empty() {
if use_new_lines_for_nested_conditional {
items.push_signal(Signal::NewLine);
} else if question_comment_items.previous_lines.is_empty() {
items.push_signal(Signal::SpaceOrNewLine);
} else {
items.extend(question_comment_items.previous_lines);
Expand Down Expand Up @@ -5275,7 +5283,7 @@ fn gen_conditional_type<'a>(node: &'a TsConditionalType, context: &mut Context<'
items.extend(colon_comment_items.previous_lines);

// false type
if use_new_lines {
if force_new_lines_for_false_type || use_new_lines_for_nested_conditional {
items.push_signal(Signal::NewLine);
} else {
items.push_condition(conditions::new_line_if_multiple_lines_space_or_new_line_otherwise(
Expand Down Expand Up @@ -5303,7 +5311,11 @@ fn gen_conditional_type<'a>(node: &'a TsConditionalType, context: &mut Context<'
items
};

if is_parent_conditional_type {
// Unless `use_nested_indentation` is enabled, we keep the indentation the same. e.g.:
// type A<T> = T extends string ? 'string'
// : T extends number ? 'number'
// : T extends boolean ? 'boolean';
if !use_new_lines_for_nested_conditional && is_parent_conditional_type {
items.extend(false_type_generated);
} else {
items.push_condition(conditions::indent_if_start_of_line(false_type_generated));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
~~ lineWidth: 80, conditionalExpression.useNestedIndentation: true ~~
== should handle nested indentation on a basic ternary / conditional expression ==
const value = is_prod
? do1()
: is_laptop
? do2()
: do3();

[expect]
const value = is_prod
? do1()
: is_laptop
? do2()
: do3();
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
~~ lineWidth: 80, conditionalType.useNestedIndentation: true ~~
== should handle nested indentation with extends and mapped types ==
export type R<S> = S extends Record<string, T<any, any>> ? { [K in keyof S]: ReturnType<S[K][1]> } : S extends T<infer _T, any>
? _T extends Array<infer I>
? I
: _T : S extends SZ<infer _T, any> ? _T : S extends DZ<infer _T>
? _T : never;

[expect]
export type R<S> = S extends Record<string, T<any, any>>
? { [K in keyof S]: ReturnType<S[K][1]> }
: S extends T<infer _T, any>
? _T extends Array<infer I>
? I
: _T
: S extends SZ<infer _T, any>
? _T
: S extends DZ<infer _T>
? _T
: never;