Repository: dprint/dprint-plugin-typescript (rust)

Issue:
operatorPosition=maintain not respected in conditional type
**Describe the bug**

dprint-plugin-typescript version: main

**Input Code**

```ts
export function getIterator<I extends readonly any[] | ReadonlySet<any> | ReadonlyESMap<any, any> | undefined>(iterable: I): Iterator<
    I extends ReadonlyESMap<infer K, infer V> ? [K, V] :
    I extends ReadonlySet<infer T> ? T :
    I extends readonly (infer T)[] ? T :
    I extends undefined ? undefined :
    never>;
```

**Expected Output**

```ts
export function getIterator<I extends readonly any[] | ReadonlySet<any> | ReadonlyESMap<any, any> | undefined>(iterable: I): Iterator<
    I extends ReadonlyESMap<infer K, infer V> ? [K, V] :
    I extends ReadonlySet<infer T> ? T :
    I extends readonly (infer T)[] ? T :
    I extends undefined ? undefined :
    never
>;
```

(or something)

**Actual Output**

```ts
export function getIterator<I extends readonly any[] | ReadonlySet<any> | ReadonlyESMap<any, any> | undefined>(iterable: I): Iterator<
    I extends ReadonlyESMap<infer K, infer V> ? [K, V]
        : I extends ReadonlySet<infer T> ? T
        : I extends readonly (infer T)[] ? T
        : I extends undefined ? undefined
        : never
>;
```

Note how the operators are on the right now.

From: https://github.com/microsoft/TypeScript/compare/main...jakebailey:TypeScript:dprint#diff-8eee8edc4cdf82aaa3b0c0ba3c8c74fa80ac6d5214540a460c49fb2ee1031b25


Pull request:
feat: conditionalExpression.operatorPosition
Closes #413

Patch:
diff --git a/deployment/schema.json b/deployment/schema.json
index 59ae2bf..b85de9f 100644
--- a/deployment/schema.json
+++ b/deployment/schema.json
@@ -1091,6 +1091,9 @@
     "conditionalExpression.operatorPosition": {
       "$ref": "#/definitions/operatorPosition"
     },
+    "conditionalType.operatorPosition": {
+      "$ref": "#/definitions/operatorPosition"
+    },
     "arguments.preferHanging": {
       "$ref": "#/definitions/preferHanging"
     },
diff --git a/src/configuration/builder.rs b/src/configuration/builder.rs
index b57bfce..95a2df9 100644
--- a/src/configuration/builder.rs
+++ b/src/configuration/builder.rs
@@ -52,6 +52,8 @@ impl ConfigurationBuilder {
       .indent_width(2)
       .next_control_flow_position(NextControlFlowPosition::SameLine)
       .binary_expression_operator_position(OperatorPosition::SameLine)
+      .conditional_expression_operator_position(OperatorPosition::NextLine)
+      .conditional_type_operator_position(OperatorPosition::NextLine)
       .brace_position(BracePosition::SameLine)
       .comment_line_force_space_after_slashes(false)
       .construct_signature_space_after_new_keyword(true)
@@ -757,6 +759,10 @@ impl ConfigurationBuilder {
     self.insert("conditionalExpression.operatorPosition", value.to_string().into())
   }
 
+  pub fn conditional_type_operator_position(&mut self, value: OperatorPosition) -> &mut Self {
+    self.insert("conditionalType.operatorPosition", value.to_string().into())
+  }
+
   /* single body position */
 
   pub fn if_statement_single_body_position(&mut self, value: SingleBodyPosition) -> &mut Self {
@@ -1109,6 +1115,7 @@ mod tests {
       /* operator position */
       .binary_expression_operator_position(OperatorPosition::SameLine)
       .conditional_expression_operator_position(OperatorPosition::SameLine)
+      .conditional_type_operator_position(OperatorPosition::SameLine)
       /* single body position */
       .if_statement_single_body_position(SingleBodyPosition::SameLine)
       .for_statement_single_body_position(SingleBodyPosition::SameLine)
@@ -1207,7 +1214,7 @@ mod tests {
       .while_statement_space_around(true);
 
     let inner_config = config.get_inner_config();
-    assert_eq!(inner_config.len(), 170);
+    assert_eq!(inner_config.len(), 171);
     let diagnostics = resolve_config(inner_config, &resolve_global_config(ConfigKeyMap::new(), &Default::default()).config).diagnostics;
     assert_eq!(diagnostics.len(), 0);
   }
diff --git a/src/configuration/resolve_config.rs b/src/configuration/resolve_config.rs
index df46d47..07e1063 100644
--- a/src/configuration/resolve_config.rs
+++ b/src/configuration/resolve_config.rs
@@ -165,6 +165,7 @@ pub fn resolve_config(config: ConfigKeyMap, global_config: &GlobalConfiguration)
     /* operator position */
     binary_expression_operator_position: get_value(&mut config, "binaryExpression.operatorPosition", operator_position, &mut diagnostics),
     conditional_expression_operator_position: get_value(&mut config, "conditionalExpression.operatorPosition", operator_position, &mut diagnostics),
+    conditional_type_operator_position: get_value(&mut config, "conditionalType.operatorPosition", operator_position, &mut diagnostics),
     /* single body position */
     if_statement_single_body_position: get_value(&mut config, "ifStatement.singleBodyPosition", single_body_position, &mut diagnostics),
     for_statement_single_body_position: get_value(&mut config, "forStatement.singleBodyPosition", single_body_position, &mut diagnostics),
diff --git a/src/configuration/types.rs b/src/configuration/types.rs
index cf1a6d5..951c0ea 100644
--- a/src/configuration/types.rs
+++ b/src/configuration/types.rs
@@ -96,7 +96,7 @@ pub enum NextControlFlowPosition {
 generate_str_to_from![NextControlFlowPosition, [Maintain, "maintain"], [SameLine, "sameLine"], [NextLine, "nextLine"]];
 
 /// Where to place the operator for expressions that span multiple lines.
-#[derive(Clone, PartialEq, Copy, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Copy, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub enum OperatorPosition {
   /// Maintains the operator being on the next line or the same line.
@@ -414,6 +414,8 @@ pub struct Configuration {
   pub binary_expression_operator_position: OperatorPosition,
   #[serde(rename = "conditionalExpression.operatorPosition")]
   pub conditional_expression_operator_position: OperatorPosition,
+  #[serde(rename = "conditionalType.operatorPosition")]
+  pub conditional_type_operator_position: OperatorPosition,
   /* single body position */
   #[serde(rename = "ifStatement.singleBodyPosition")]
   pub if_statement_single_body_position: SingleBodyPosition,
diff --git a/src/generation/generate.rs b/src/generation/generate.rs
index 3e168ab..81679fa 100644
--- a/src/generation/generate.rs
+++ b/src/generation/generate.rs
@@ -2168,8 +2168,8 @@ fn gen_conditional_expr<'a>(node: &'a CondExpr, context: &mut Context<'a>) -> Pr
   let top_most_data = get_top_most_data(node, context);
   let before_alternate_ln = LineNumber::new("beforeAlternate");
   let end_ln = LineNumber::new("endConditionalExpression");
-  let question_comment_items = gen_token_comments(question_token, context, top_most_data.il);
-  let colon_comment_items = gen_token_comments(colon_token, context, top_most_data.il);
+  let question_comment_items = gen_cond_token_comments(question_token, context, top_most_data.il);
+  let colon_comment_items = gen_cond_token_comments(colon_token, context, top_most_data.il);
   let mut items = PrintItems::new();
 
   if top_most_data.is_top_most {
@@ -2273,78 +2273,6 @@ fn gen_conditional_expr<'a>(node: &'a CondExpr, context: &mut Context<'a>) -> Pr
 
   return items;
 
-  struct TokenComments {
-    previous_lines: PrintItems,
-    leading_line: PrintItems,
-    trailing_line: PrintItems,
-  }
-
-  fn gen_token_comments(token: &TokenAndSpan, context: &mut Context, top_most_il: IndentLevel) -> TokenComments {
-    let token_line = token.end_line_fast(context.program);
-    let previous_token = token.previous_token_fast(context.program).unwrap();
-    let next_token = token.next_token_fast(context.program).unwrap();
-    let previous_token_line = previous_token.end_line_fast(context.program);
-    let next_token_line = next_token.start_line_fast(context.program);
-    if token_line > previous_token_line {
-      TokenComments {
-        previous_lines: {
-          let leading_comments = token.leading_comments_fast(context.program).filter(|c| {
-            let comment_line = c.start_line_fast(context.program);
-            comment_line > previous_token_line && comment_line < next_token_line
-          });
-          let trailing_comments = token
-            .trailing_comments_fast(context.program)
-            .filter(|c| c.start_line_fast(context.program) < next_token_line);
-          let items = gen_comments_as_statements(leading_comments.chain(trailing_comments), None, context);
-          if items.is_empty() {
-            items
-          } else {
-            let mut new_items = PrintItems::new();
-            new_items.push_signal(Signal::NewLine);
-            new_items.push_condition(indent_if_sol_and_same_indent_as_top_most(items, top_most_il));
-            new_items
-          }
-        },
-        leading_line: if token_line < next_token_line {
-          gen_leading_comments_same_line(&token.range(), context)
-        } else {
-          PrintItems::new()
-        },
-        trailing_line: PrintItems::new(),
-      }
-    } else if token_line < next_token_line {
-      TokenComments {
-        previous_lines: PrintItems::new(),
-        leading_line: PrintItems::new(),
-        trailing_line: gen_trailing_comments_same_line(&token.range(), context),
-      }
-    } else {
-      // do nothing
-      TokenComments {
-        previous_lines: PrintItems::new(),
-        leading_line: PrintItems::new(),
-        trailing_line: PrintItems::new(),
-      }
-    }
-  }
-
-  fn indent_if_sol_and_same_indent_as_top_most(items: PrintItems, top_most_il: IndentLevel) -> Condition {
-    let items = items.into_rc_path();
-    if_true_or(
-      "indentIfSameIndentationAsTopMostAndStartOfLine",
-      Rc::new(move |context| {
-        if context.writer_info.is_start_of_line() {
-          let top_most_il = context.resolved_indent_level(top_most_il)?;
-          Some(context.writer_info.indent_level == top_most_il)
-        } else {
-          Some(false)
-        }
-      }),
-      with_indent(items.into()),
-      items.into(),
-    )
-  }
-
   struct TopMostData {
     ln: LineNumber,
     il: IndentLevel,
@@ -2369,7 +2297,7 @@ fn gen_conditional_expr<'a>(node: &'a CondExpr, context: &mut Context<'a>) -> Pr
     }
 
     let is_top_most = top_most_node.range() == node.range();
-    let (top_most_ln, top_most_il) = get_or_set_top_most_ln(top_most_node.start(), is_top_most, context);
+    let (top_most_ln, top_most_il) = get_or_set_top_most(top_most_node.start(), is_top_most, context);
 
     return TopMostData {
       is_top_most,
@@ -2377,7 +2305,7 @@ fn gen_conditional_expr<'a>(node: &'a CondExpr, context: &mut Context<'a>) -> Pr
       il: top_most_il,
     };
 
-    fn get_or_set_top_most_ln(top_most_expr_start: SourcePos, is_top_most: bool, context: &mut Context) -> (LineNumber, IndentLevel) {
+    fn get_or_set_top_most(top_most_expr_start: SourcePos, is_top_most: bool, context: &mut Context) -> (LineNumber, IndentLevel) {
       if is_top_most {
         let ln = LineNumber::new("conditionalExprStart");
         let il = IndentLevel::new("conditionalExprStart");
@@ -2393,13 +2321,18 @@ fn gen_conditional_expr<'a>(node: &'a CondExpr, context: &mut Context<'a>) -> Pr
     }
   }
 
-  fn get_operator_position(node: &CondExpr, operator_token: &TokenAndSpan, context: &mut Context) -> OperatorPosition {
+  fn get_operator_position(node: &CondExpr, question_token: &TokenAndSpan, context: &mut Context) -> OperatorPosition {
     match context.config.conditional_expression_operator_position {
       OperatorPosition::NextLine => OperatorPosition::NextLine,
       OperatorPosition::SameLine => OperatorPosition::SameLine,
       OperatorPosition::Maintain => {
-        if node.test.end_line_fast(context.program) == operator_token.start_line_fast(context.program) {
-          OperatorPosition::SameLine
+        if node.test.end_line_fast(context.program) == question_token.start_line_fast(context.program) {
+          if node.start_line_fast(context.program) == node.end_line_fast(context.program) {
+            // prefer the dprint default when going from one to multiple lines
+            OperatorPosition::NextLine
+          } else {
+            OperatorPosition::SameLine
+          }
         } else {
           OperatorPosition::NextLine
         }
@@ -2408,6 +2341,79 @@ fn gen_conditional_expr<'a>(node: &'a CondExpr, context: &mut Context<'a>) -> Pr
   }
 }
 
+struct ConditionalTokenComments {
+  previous_lines: PrintItems,
+  leading_line: PrintItems,
+  trailing_line: PrintItems,
+}
+
+/// Generates the comments used for tokens in conditional expressions and types.
+fn gen_cond_token_comments(token: &TokenAndSpan, context: &mut Context, top_most_il: IndentLevel) -> ConditionalTokenComments {
+  let token_line = token.end_line_fast(context.program);
+  let previous_token = token.previous_token_fast(context.program).unwrap();
+  let next_token = token.next_token_fast(context.program).unwrap();
+  let previous_token_line = previous_token.end_line_fast(context.program);
+  let next_token_line = next_token.start_line_fast(context.program);
+  if token_line > previous_token_line {
+    ConditionalTokenComments {
+      previous_lines: {
+        let leading_comments = token.leading_comments_fast(context.program).filter(|c| {
+          let comment_line = c.start_line_fast(context.program);
+          comment_line > previous_token_line && comment_line < next_token_line
+        });
+        let trailing_comments = token
+          .trailing_comments_fast(context.program)
+          .filter(|c| c.start_line_fast(context.program) < next_token_line);
+        let items = gen_comments_as_statements(leading_comments.chain(trailing_comments), None, context);
+        if items.is_empty() {
+          items
+        } else {
+          let mut new_items = PrintItems::new();
+          new_items.push_signal(Signal::NewLine);
+          new_items.push_condition(indent_if_sol_and_same_indent_as_top_most(items, top_most_il));
+          new_items
+        }
+      },
+      leading_line: if token_line == next_token_line {
+        gen_leading_comments_same_line(&token.range(), context)
+      } else {
+        PrintItems::new()
+      },
+      trailing_line: PrintItems::new(),
+    }
+  } else if token_line < next_token_line {
+    ConditionalTokenComments {
+      previous_lines: PrintItems::new(),
+      leading_line: PrintItems::new(),
+      trailing_line: gen_trailing_comments_same_line(&token.range(), context),
+    }
+  } else {
+    // do nothing
+    ConditionalTokenComments {
+      previous_lines: PrintItems::new(),
+      leading_line: PrintItems::new(),
+      trailing_line: PrintItems::new(),
+    }
+  }
+}
+
+fn indent_if_sol_and_same_indent_as_top_most(items: PrintItems, indent_level: IndentLevel) -> Condition {
+  let items = items.into_rc_path();
+  if_true_or(
+    "indentIfSameIndentationLevelAndStartOfLine",
+    Rc::new(move |context| {
+      if context.writer_info.is_start_of_line() {
+        let top_most_il = context.resolved_indent_level(indent_level)?;
+        Some(context.writer_info.indent_level == top_most_il)
+      } else {
+        Some(false)
+      }
+    }),
+    with_indent(items.into()),
+    items.into(),
+  )
+}
+
 fn gen_expr_or_spread<'a>(node: &'a ExprOrSpread, context: &mut Context<'a>) -> PrintItems {
   let mut items = PrintItems::new();
   if node.spread().is_some() {
@@ -5156,6 +5162,11 @@ fn gen_conditional_type<'a>(node: &'a TsConditionalType, context: &mut Context<'
   let is_parent_conditional_type = node.parent().kind() == NodeKind::TsConditionalType;
   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();
+  let colon_token = context.token_finder.get_first_operator_after(&node.true_type, ":").unwrap();
+  let operator_position = get_operator_position(node, colon_token, context);
+  let question_comment_items = gen_cond_token_comments(question_token, context, top_most_data.il);
+  let colon_comment_items = gen_cond_token_comments(colon_token, context, top_most_data.il);
 
   // main area
   items.extend(ir_helpers::new_line_group(gen_node(node.check_type.into(), context)));
@@ -5163,31 +5174,37 @@ fn gen_conditional_type<'a>(node: &'a TsConditionalType, context: &mut Context<'
   items.push_signal(Signal::SpaceOrNewLine);
 
   if top_most_data.is_top_most {
-    items.push_info(top_most_data.top_most_ln);
+    items.push_info(top_most_data.ln);
+    items.push_info(top_most_data.il);
   }
 
-  items.push_condition(conditions::indent_if_start_of_line(ir_helpers::new_line_group(gen_node(
-    node.extends_type.into(),
-    context,
-  ))));
-
-  // add any preceeding comments of the question token
-  let question_token_start = context.token_finder.get_next_token_pos_after(&node.extends_type);
-  items.extend({
-    let comment_items = gen_leading_comments_on_previous_lines(&question_token_start, context);
-    if comment_items.is_empty() {
-      Signal::SpaceOrNewLine.into()
-    } else {
-      surround_with_new_lines(with_indent(comment_items))
+  items.push_condition(conditions::indent_if_start_of_line(ir_helpers::new_line_group({
+    let mut items = gen_node(node.extends_type.into(), context);
+    if operator_position == OperatorPosition::SameLine {
+      items.push_str(" ?");
     }
-  });
+    items.extend(question_comment_items.trailing_line);
+    items
+  })));
+
+  if question_comment_items.previous_lines.is_empty() {
+    items.push_signal(Signal::SpaceOrNewLine);
+  } else {
+    items.extend(question_comment_items.previous_lines);
+  }
 
   items.push_condition({
     let inner_items = {
       let mut items = PrintItems::new();
-      items.extend(gen_leading_comments_same_line(&question_token_start, context));
-      items.push_str("? ");
+      items.extend(question_comment_items.leading_line);
+      if operator_position == OperatorPosition::NextLine {
+        items.push_str("? ");
+      }
       items.extend(ir_helpers::new_line_group(gen_node(node.true_type.into(), context)));
+      if operator_position == OperatorPosition::SameLine {
+        items.push_str(" :");
+      }
+      items.extend(colon_comment_items.trailing_line);
       items
     }
     .into_rc_path();
@@ -5199,20 +5216,21 @@ fn gen_conditional_type<'a>(node: &'a TsConditionalType, context: &mut Context<'
     )
   });
 
+  items.extend(colon_comment_items.previous_lines);
+
   // false type
   if use_new_lines {
     items.push_signal(Signal::NewLine);
   } else {
     items.push_condition(conditions::new_line_if_multiple_lines_space_or_new_line_otherwise(
-      top_most_data.top_most_ln,
+      top_most_data.ln,
       Some(before_false_ln),
     ));
   }
 
   // add any preceeding comments of the colon token
-  let colon_token_start = context.token_finder.get_next_token_pos_after(&node.true_type);
   {
-    let comment_items = gen_leading_comments_on_previous_lines(&colon_token_start, context);
+    let comment_items = gen_leading_comments_on_previous_lines(&colon_token.range(), context);
     if !comment_items.is_empty() {
       items.extend(with_indent(comment_items));
     }
@@ -5221,8 +5239,10 @@ fn gen_conditional_type<'a>(node: &'a TsConditionalType, context: &mut Context<'
   let false_type_generated = {
     let mut items = PrintItems::new();
     items.push_info(before_false_ln);
-    items.extend(gen_leading_comments_same_line(&colon_token_start, context));
-    items.push_str(": ");
+    items.extend(colon_comment_items.leading_line);
+    if operator_position == OperatorPosition::NextLine {
+      items.push_str(": ");
+    }
     items.extend(ir_helpers::new_line_group(gen_node(node.false_type.into(), context)));
     items
   };
@@ -5236,7 +5256,8 @@ fn gen_conditional_type<'a>(node: &'a TsConditionalType, context: &mut Context<'
   return items;
 
   struct TopMostData {
-    top_most_ln: LineNumber,
+    ln: LineNumber,
+    il: IndentLevel,
     is_top_most: bool,
   }
 
@@ -5259,17 +5280,41 @@ fn gen_conditional_type<'a>(node: &'a TsConditionalType, context: &mut Context<'
     }
 
     let is_top_most = top_most_node.range() == node.range();
-    let top_most_ln = get_or_set_top_most_ln(top_most_node.start(), is_top_most, context);
+    let (ln, il) = get_or_set_top_most_ln(top_most_node.start(), is_top_most, context);
 
-    return TopMostData { is_top_most, top_most_ln };
+    return TopMostData { is_top_most, ln, il };
 
-    fn get_or_set_top_most_ln(top_most_expr_start: SourcePos, is_top_most: bool, context: &mut Context) -> LineNumber {
+    fn get_or_set_top_most_ln(top_most_expr_start: SourcePos, is_top_most: bool, context: &mut Context) -> (LineNumber, IndentLevel) {
       if is_top_most {
         let ln = LineNumber::new("conditionalTypeStart");
+        let il = IndentLevel::new("conditionalTypeStart");
         context.store_ln_for_node(&top_most_expr_start, ln);
-        ln
+        context.store_il_for_node(&top_most_expr_start, il);
+        (ln, il)
       } else {
-        context.get_ln_for_node(&top_most_expr_start).unwrap()
+        (
+          context.get_ln_for_node(&top_most_expr_start).unwrap(),
+          context.get_il_for_node(&top_most_expr_start).unwrap(),
+        )
+      }
+    }
+  }
+
+  fn get_operator_position(node: &TsConditionalType, colon_token: &TokenAndSpan, context: &mut Context) -> OperatorPosition {
+    match context.config.conditional_type_operator_position {
+      OperatorPosition::NextLine => OperatorPosition::NextLine,
+      OperatorPosition::SameLine => OperatorPosition::SameLine,
+      OperatorPosition::Maintain => {
+        if node.true_type.end_line_fast(context.program) == colon_token.start_line_fast(context.program) {
+          if node.start_line_fast(context.program) == node.end_line_fast(context.program) {
+            // prefer the dprint default when going from one to multiple lines
+            OperatorPosition::NextLine
+          } else {
+            OperatorPosition::SameLine
+          }
+        } else {
+          OperatorPosition::NextLine
+        }
       }
     }
   }
@@ -6010,7 +6055,7 @@ fn get_leading_comments_on_same_line<'a>(node: &dyn SourceRanged, context: &mut
   } else {
     let node_start_line = node.start_line_fast(context.program);
     leading_comments
-      .filter(|c| c.kind == CommentKind::Block || c.start_line_fast(context.program) == node_start_line)
+      .filter(|c| c.start_line_fast(context.program) == node_start_line)
       .collect::<Vec<_>>()
   }
 }


Test patch:
diff --git a/tests/specs/expressions/ConditionalExpression/ConditionalExpression_All.txt b/tests/specs/expressions/ConditionalExpression/ConditionalExpression_All.txt
index c81f673..ffae91d 100644
--- a/tests/specs/expressions/ConditionalExpression/ConditionalExpression_All.txt
+++ b/tests/specs/expressions/ConditionalExpression/ConditionalExpression_All.txt
@@ -110,3 +110,13 @@ test ? testingThis : outABittt;
 test
     ? testingThis
     : outABittt;
+
+== should handle comments ==
+/* 0 */ testingtesting /* 1 */
+    /* 2 */ ? /* 3 */ asdfasdf /* 4 */
+    /* 5 */ : /* 6 */ asdfasdfasdf /* 7 */; /* 8 */
+
+[expect]
+/* 0 */ testingtesting /* 1 */
+    /* 2 */ ? /* 3 */ asdfasdf /* 4 */
+    /* 5 */ : /* 6 */ asdfasdfasdf /* 7 */; /* 8 */
diff --git a/tests/specs/expressions/ConditionalExpression/ConditionalExpression_OperatorPosition_Maintain.txt b/tests/specs/expressions/ConditionalExpression/ConditionalExpression_OperatorPosition_Maintain.txt
index 9274bdb..531ad7f 100644
--- a/tests/specs/expressions/ConditionalExpression/ConditionalExpression_OperatorPosition_Maintain.txt
+++ b/tests/specs/expressions/ConditionalExpression/ConditionalExpression_OperatorPosition_Maintain.txt
@@ -1,10 +1,11 @@
-~~ conditionalExpression.operatorPosition: maintain, conditionalExpression.preferSingleLine: false ~~
+~~ conditionalExpression.operatorPosition: maintain, conditionalExpression.preferSingleLine: false, lineWidth: 40 ~~
 == should format ==
 test? 1: 2;
 test
     ? 1: 2;
 test ? 1
 : 2;
+test? testingthis: testingthismoretesttest;
 
 [expect]
 test ? 1 : 2;
@@ -14,3 +15,6 @@ test
 test ?
     1 :
     2;
+test
+    ? testingthis
+    : testingthismoretesttest;
diff --git a/tests/specs/issues/issue0413.txt b/tests/specs/issues/issue0413.txt
new file mode 100644
index 0000000..73cc86b
--- /dev/null
+++ b/tests/specs/issues/issue0413.txt
@@ -0,0 +1,17 @@
+~~ operatorPosition: maintain ~~
+== should format maintaining the operator position ==
+export function getIterator(iterable: I): Iterator<
+    I extends ReadonlyESMap<infer K, infer V> ? [K, V] :
+    I extends ReadonlySet<infer T> ? T :
+    I extends readonly (infer T)[] ? T :
+    I extends undefined ? undefined :
+    never>;
+
+[expect]
+export function getIterator(iterable: I): Iterator<
+    I extends ReadonlyESMap<infer K, infer V> ? [K, V] :
+        I extends ReadonlySet<infer T> ? T :
+        I extends readonly (infer T)[] ? T :
+        I extends undefined ? undefined :
+        never
+>;
diff --git a/tests/specs/types/ConditionalType/ConditionalType_All.txt b/tests/specs/types/ConditionalType/ConditionalType_All.txt
index 953761b..04bcb9e 100644
--- a/tests/specs/types/ConditionalType/ConditionalType_All.txt
+++ b/tests/specs/types/ConditionalType/ConditionalType_All.txt
@@ -109,3 +109,13 @@ type T = U extends true
     ? false
     /* test */
     : true;
+
+== should handle comments ==
+type T = /* 0 */ U extends true /* 1 */
+    /* 2 */ ? /* 3 */ asdfasdf /* 4 */
+    /* 5 */ : /* 6 */ asdfasdfasdf /* 7 */; /* 8 */
+
+[expect]
+type T = /* 0 */ U extends true /* 1 */
+    /* 2 */ ? /* 3 */ asdfasdf /* 4 */
+    /* 5 */ : /* 6 */ asdfasdfasdf /* 7 */; /* 8 */
diff --git a/tests/specs/types/ConditionalType/ConditionalType_OperatorPosition_Maintain.txt b/tests/specs/types/ConditionalType/ConditionalType_OperatorPosition_Maintain.txt
new file mode 100644
index 0000000..f658bcc
--- /dev/null
+++ b/tests/specs/types/ConditionalType/ConditionalType_OperatorPosition_Maintain.txt
@@ -0,0 +1,15 @@
+~~ conditionalType.operatorPosition: maintain, lineWidth: 60 ~~
+== should maintain the operator position ==
+type T = test extends string ? testing : testingtestingtesting;
+type T = A extends B ? /* 0 */ testing /* 1 */
+  /* 2 */ : /* 3 */ asdfasdf;
+type T = A extends B ? /* 0 */ testing /* 1 */ : /* 2 */
+  /* 3 */ asdfasdf;
+
+[expect]
+type T = test extends string ? testing
+    : testingtestingtesting;
+type T = A extends B ? /* 0 */ testing /* 1 */
+    /* 2 */ : /* 3 */ asdfasdf;
+type T = A extends B ? /* 0 */ testing /* 1 */ : /* 2 */
+    /* 3 */ asdfasdf;
diff --git a/tests/specs/types/ConditionalType/ConditionalType_OperatorPosition_NextLine.txt b/tests/specs/types/ConditionalType/ConditionalType_OperatorPosition_NextLine.txt
new file mode 100644
index 0000000..a4f1b02
--- /dev/null
+++ b/tests/specs/types/ConditionalType/ConditionalType_OperatorPosition_NextLine.txt
@@ -0,0 +1,15 @@
+~~ conditionalType.operatorPosition: nextLine, lineWidth: 60 ~~
+== should maintain the operator position ==
+type T = test extends string ? testing : testingtestingtesting;
+type T = A extends B ? /* 0 */ testing /* 1 */
+  /* 2 */ : /* 3 */ asdfasdf;
+type T = A extends B ? /* 0 */ testing /* 1 */ : /* 2 */
+  /* 3 */ asdfasdf;
+
+[expect]
+type T = test extends string ? testing
+    : testingtestingtesting;
+type T = A extends B ? /* 0 */ testing /* 1 */
+    /* 2 */ : /* 3 */ asdfasdf;
+type T = A extends B ? /* 0 */ testing /* 1 */ /* 2 */
+    : /* 3 */ asdfasdf;
diff --git a/tests/specs/types/ConditionalType/ConditionalType_OperatorPosition_SameLine.txt b/tests/specs/types/ConditionalType/ConditionalType_OperatorPosition_SameLine.txt
new file mode 100644
index 0000000..7eabc25
--- /dev/null
+++ b/tests/specs/types/ConditionalType/ConditionalType_OperatorPosition_SameLine.txt
@@ -0,0 +1,15 @@
+~~ conditionalType.operatorPosition: sameLine, lineWidth: 60 ~~
+== should maintain the operator position ==
+type T = test extends string ? testing : testingtestingtesting;
+type T = A extends B ? /* 0 */ testing /* 1 */
+  /* 2 */ : /* 3 */ asdfasdf;
+type T = A extends B ? /* 0 */ testing /* 1 */ : /* 2 */
+  /* 3 */ asdfasdf;
+
+[expect]
+type T = test extends string ? testing :
+    testingtestingtesting;
+type T = A extends B ? /* 0 */ testing /* 1 */ :
+    /* 2 */ /* 3 */ asdfasdf;
+type T = A extends B ? /* 0 */ testing /* 1 */ : /* 2 */
+    /* 3 */ asdfasdf;