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

Issue:
nextControlFlowPosition doesn't apply to do/while
**Describe the bug**

dprint-plugin-typescript version: 0.74.0

With `nextControlFlowPosition=nextLine`, `while` is left on the same line as the brace.

**Input Code**

```ts
do {
    console.log("hello")
} while (1 === 1);
```

**Expected Output**

```ts
do {
    console.log("hello")
}
while (1 === 1);
```

**Actual Output**

```ts
do {
    console.log("hello");
} while (1 === 1);
```

[dprint playground](https://dprint.dev/playground/#code/JYMwBAFAjGC89igSjAbwLACgw7AYwHsA7AZwIBsBTAOnIIHMIAiAFwCcBXSppLAXzCVyJSmiy58xMlVoNmIAIbDuvTHyxYAJgTHZchUhRp1GTABZC6PfmADuZ4FUgx4sREgDcQA/config/N4WAUABFEEQJYDsAmBTBAXA6nJ6AWMAXBACwA040sANoitrgcQIwAM7Fk0MCKA7gBk6AaURIisAIYBXdAHsYnKjGkBnFABVJAI1USAZpOrql3dQFs4AYTnU5CPcRhG+kgJ57TUGAEdpc9BQAZXQ3ahQJGAAHACcUfRQYgBE5aW1wxUpuNRQAIRjJAGMUR1g+PDQAOQCgxABzcKFeTK5vbQLigAU5VTh0OHtI1UlzFCaUAFUEcNVVAAlJBDr6luVepfDcuSQ3bt7+wadh0fHV7l4AD3QbDBjbADE7Pj2+gYRI80lEdC-3r1h0AU4LQljZzJ9SjB7NQ3ABZaTUfqnf7OGJ3Pj3aQIQoHBAAOhynUkcQwFXUkNi8US1WaKMK9iQr3sRgAohdKbM3niQShOok2RzeocIIZjCgUfosTi3gK4pz7HjVFEiigAIL6QIxTHY3HCFBuPhyGLiYiA6TirLeSkJGILJYrYiikyW2ByKKJSTyGIvXEfX4-RBnbzmbYIlCKo3oACS5iikaSKEK1GJnrekM+31+QdgIaQYYjMXQAvjieTBVx6f9WZRKHZJaTKdxBfQlRGKCQxcLlczgZRcFj9bLqYVqkjrdGSBjca7fp7fxdMHpDjNOKNGjc7sVyuK6s1lX4eoNRpNEDNFtasCXqhX6FqdQQnukcS3Kt31IP+sNxokZ-AAF9wCAA/language/typescript)

Additionally, `maintain` doesn't work if the keyword is already on the next line:

**Input Code**

```ts
do {
    console.log("hello")
}
while (1 === 1);
```

**Expected Output**

```ts
do {
    console.log("hello")
}
while (1 === 1);
```

**Actual Output**

```ts
do {
    console.log("hello");
} while (1 === 1);
```


Pull request description:
feat: add `doWhileStatement.nextControlFlowPosition`
Closes #429

Patch:
diff --git a/deployment/schema.json b/deployment/schema.json
index 13c65bd..4eeaf08 100644
--- a/deployment/schema.json
+++ b/deployment/schema.json
@@ -1049,6 +1049,9 @@
     "tryStatement.nextControlFlowPosition": {
       "$ref": "#/definitions/nextControlFlowPosition"
     },
+    "doWhileStatement.nextControlFlowPosition": {
+      "$ref": "#/definitions/nextControlFlowPosition"
+    },
     "arguments.trailingCommas": {
       "$ref": "#/definitions/trailingCommas"
     },
diff --git a/src/configuration/builder.rs b/src/configuration/builder.rs
index 95a2df9..0fa156e 100644
--- a/src/configuration/builder.rs
+++ b/src/configuration/builder.rs
@@ -749,6 +749,10 @@ impl ConfigurationBuilder {
     self.insert("tryStatement.nextControlFlowPosition", value.to_string().into())
   }
 
+  pub fn do_while_statement_next_control_flow_position(&mut self, value: NextControlFlowPosition) -> &mut Self {
+    self.insert("doWhileStatement.nextControlFlowPosition", value.to_string().into())
+  }
+
   /* operator position */
 
   pub fn binary_expression_operator_position(&mut self, value: OperatorPosition) -> &mut Self {
@@ -1112,6 +1116,7 @@ mod tests {
       /* next control flow position */
       .if_statement_next_control_flow_position(NextControlFlowPosition::SameLine)
       .try_statement_next_control_flow_position(NextControlFlowPosition::SameLine)
+      .do_while_statement_next_control_flow_position(NextControlFlowPosition::SameLine)
       /* operator position */
       .binary_expression_operator_position(OperatorPosition::SameLine)
       .conditional_expression_operator_position(OperatorPosition::SameLine)
@@ -1214,7 +1219,7 @@ mod tests {
       .while_statement_space_around(true);
 
     let inner_config = config.get_inner_config();
-    assert_eq!(inner_config.len(), 171);
+    assert_eq!(inner_config.len(), 172);
     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 07e1063..147a7e9 100644
--- a/src/configuration/resolve_config.rs
+++ b/src/configuration/resolve_config.rs
@@ -162,6 +162,12 @@ pub fn resolve_config(config: ConfigKeyMap, global_config: &GlobalConfiguration)
       next_control_flow_position,
       &mut diagnostics,
     ),
+    do_while_statement_next_control_flow_position: get_value(
+      &mut config,
+      "doWhileStatement.nextControlFlowPosition",
+      next_control_flow_position,
+      &mut diagnostics,
+    ),
     /* 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),
diff --git a/src/configuration/types.rs b/src/configuration/types.rs
index 951c0ea..923de25 100644
--- a/src/configuration/types.rs
+++ b/src/configuration/types.rs
@@ -409,6 +409,8 @@ pub struct Configuration {
   pub if_statement_next_control_flow_position: NextControlFlowPosition,
   #[serde(rename = "tryStatement.nextControlFlowPosition")]
   pub try_statement_next_control_flow_position: NextControlFlowPosition,
+  #[serde(rename = "doWhileStatement.nextControlFlowPosition")]
+  pub do_while_statement_next_control_flow_position: NextControlFlowPosition,
   /* operator position */
   #[serde(rename = "binaryExpression.operatorPosition")]
   pub binary_expression_operator_position: OperatorPosition,
diff --git a/src/generation/generate.rs b/src/generation/generate.rs
index 627f7d3..48063f9 100644
--- a/src/generation/generate.rs
+++ b/src/generation/generate.rs
@@ -4287,7 +4287,15 @@ fn gen_do_while_stmt<'a>(node: &'a DoWhileStmt, context: &mut Context<'a>) -> Pr
     context,
   ));
   items.extend(gen_node(node.body.into(), context));
-  items.push_str(" while");
+  items.extend(gen_control_flow_separator(
+    context.config.do_while_statement_next_control_flow_position,
+    &node.body.range(),
+    "while",
+    None,
+    None,
+    context,
+  ));
+  items.push_str("while");
   if context.config.do_while_statement_space_after_while_keyword {
     items.push_str(" ");
   }
@@ -4738,7 +4746,7 @@ fn gen_if_stmt<'a>(node: &'a IfStmt, context: &mut Context<'a>) -> PrintItems {
       context.config.if_statement_next_control_flow_position,
       &cons_range,
       "else",
-      if_stmt_start_ln,
+      Some(if_stmt_start_ln),
       Some(result.close_brace_condition_ref),
       context,
     ));
@@ -4995,7 +5003,7 @@ fn gen_try_stmt<'a>(node: &'a TryStmt, context: &mut Context<'a>) -> PrintItems
       next_control_flow_position,
       &last_block_range,
       "catch",
-      last_block_start_ln,
+      Some(last_block_start_ln),
       None,
       context,
     ));
@@ -5011,7 +5019,7 @@ fn gen_try_stmt<'a>(node: &'a TryStmt, context: &mut Context<'a>) -> PrintItems
       next_control_flow_position,
       &last_block_range,
       "finally",
-      last_block_start_ln,
+      Some(last_block_start_ln),
       None,
       context,
     ));
@@ -7892,7 +7900,7 @@ fn gen_control_flow_separator(
   next_control_flow_position: NextControlFlowPosition,
   previous_node_block: &SourceRange,
   token_text: &str,
-  previous_start_ln: LineNumber,
+  previous_start_ln: Option<LineNumber>,
   previous_close_brace_condition_ref: Option<ConditionReference>,
   context: &mut Context,
 ) -> PrintItems {
@@ -7903,8 +7911,10 @@ fn gen_control_flow_separator(
         "newLineOrSpace",
         Rc::new(move |condition_context| {
           // newline if on the same line as the previous
-          if condition_helpers::is_on_same_line(condition_context, previous_start_ln)? {
-            return Some(true);
+          if let Some(previous_start_ln) = previous_start_ln {
+            if condition_helpers::is_on_same_line(condition_context, previous_start_ln)? {
+              return Some(true);
+            }
           }
 
           // newline if the previous did not have a close brace


Test patch:
diff --git a/tests/specs/statements/doWhileStatement/DoWhileStatement_NextControlFlowPosition_Maintain.txt b/tests/specs/statements/doWhileStatement/DoWhileStatement_NextControlFlowPosition_Maintain.txt
new file mode 100644
index 0000000..46656ee
--- /dev/null
+++ b/tests/specs/statements/doWhileStatement/DoWhileStatement_NextControlFlowPosition_Maintain.txt
@@ -0,0 +1,22 @@
+~~ doWhileStatement.nextControlFlowPosition: maintain, lineWidth: 40 ~~
+== should maintain the position when on same line ==
+do {
+    console.log("hello")
+} while (1 === 1);
+
+[expect]
+do {
+    console.log("hello");
+} while (1 === 1);
+
+== should maintain the position when else if on next line ==
+do {
+    console.log("hello");
+}
+while (1 === 1);
+
+[expect]
+do {
+    console.log("hello");
+}
+while (1 === 1);
diff --git a/tests/specs/statements/doWhileStatement/DoWhileStatement_NextControlFlowPosition_NextLine.txt b/tests/specs/statements/doWhileStatement/DoWhileStatement_NextControlFlowPosition_NextLine.txt
new file mode 100644
index 0000000..b30ba9f
--- /dev/null
+++ b/tests/specs/statements/doWhileStatement/DoWhileStatement_NextControlFlowPosition_NextLine.txt
@@ -0,0 +1,39 @@
+~~ doWhileStatement.nextControlFlowPosition: nextLine, lineWidth: 40 ~~
+== should use the next line for the new control flow position ==
+do {
+    console.log("hello")
+} while (1 === 1);
+
+[expect]
+do {
+    console.log("hello");
+}
+while (1 === 1);
+
+== should stay on next line ==
+do {
+    console.log("hello")
+}
+while (1 === 1);
+
+[expect]
+do {
+    console.log("hello");
+}
+while (1 === 1);
+
+== should keep comments before while ==
+do {
+    console.log("hello")
+}
+// good enough for now
+while (1 === 1);
+
+[expect]
+do {
+    console.log("hello");
+}
+while (
+    // good enough for now
+    1 === 1
+);
diff --git a/tests/specs/statements/doWhileStatement/DoWhileStatement_NextControlFlowPosition_SameLine.txt b/tests/specs/statements/doWhileStatement/DoWhileStatement_NextControlFlowPosition_SameLine.txt
new file mode 100644
index 0000000..7b9fccc
--- /dev/null
+++ b/tests/specs/statements/doWhileStatement/DoWhileStatement_NextControlFlowPosition_SameLine.txt
@@ -0,0 +1,11 @@
+~~ doWhileStatement.nextControlFlowPosition: sameLine, lineWidth: 40 ~~
+== should use the same line for the new control flow position ==
+do {
+    console.log("hello")
+}
+while (1 === 1);
+
+[expect]
+do {
+    console.log("hello");
+} while (1 === 1);