Skip to content

conditionals: add support for nested indentation #450

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
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;