Skip to content

Commit 4bcb75e

Browse files
feat: add enhanced useBraces modes, structural indentation, and nextControlFlowPosition fixes
This fork adds several enhancements to formatting control and fixes bugs in control flow positioning: New Features: - Enhanced useBraces modes: whenFormattedMultiLine and onlyNeeded for finer control over brace usage - Structural indentation mode for conditional expressions (ternary operators) that avoids deep nesting - forceParentheses option to control when wrapping parentheses are added around expressions - Helper functions (single_non_empty_stmt, push_always_true_condition) to optimize iterator usage Bug Fixes: - Fixed NextLineExceptAfterBrace hanging with try-catch-finally statements - Fixed NextLineExceptAfterBrace for do-while statements to properly track block braces - Fixed useBraces:whenNeeded to keep braces for function and variable declarations (strict mode requirement) - Fixed multiple-statement blocks incorrectly returning false for brace requirements Configuration Changes: - Version bumped to 0.95.11+sf.2 - Made plugin URLs configurable via Cargo.toml metadata - Uses version ranges for dependencies instead of exact pinning Test Coverage: - Added comprehensive test coverage for empty statements, dangling else, and control flow positioning - Added regression tests for empty blocks and NextLineExceptAfterBrace behavior - 671 tests passing Performance Improvements: - Eliminated Vec allocations in get_force_braces and brace-checking helpers - Optimized to only create conditions when NextLineExceptAfterBrace is actually used - Extracted common iterator patterns to reduce code duplication
1 parent cd4469b commit 4bcb75e

33 files changed

Lines changed: 2221 additions & 83 deletions

File tree

Cargo.lock

Lines changed: 60 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
[package]
22
name = "dprint-plugin-typescript"
3-
version = "0.95.11"
3+
version = "0.95.11+sf.2"
44
authors = ["David Sherret <dsherret@gmail.com>"]
55
edition = "2021"
6-
homepage = "https://github.com/dprint/dprint-plugin-typescript"
6+
homepage = "https://github.com/secforge/dprint-plugin-typescript"
77
keywords = ["formatting", "formatter", "typescript", "javascript"]
88
license = "MIT"
9-
repository = "https://github.com/dprint/dprint-plugin-typescript"
9+
repository = "https://github.com/secforge/dprint-plugin-typescript"
1010
description = "TypeScript and JavaScript code formatter."
1111

12+
[package.metadata]
13+
dprint_update_url = "https://github.com/secforge/dprint-plugin-typescript/releases/latest/download/latest.json"
14+
dprint_schema_url_template = "https://github.com/secforge/dprint-plugin-typescript/releases/download/{VERSION}/schema.json"
15+
1216
[lib]
1317
crate-type = ["lib", "cdylib"]
1418

@@ -29,6 +33,9 @@ name = "specs"
2933
path = "tests/spec_test.rs"
3034
harness = false
3135

36+
[build-dependencies]
37+
toml = "0.9"
38+
3239
[dependencies]
3340
anyhow = "1.0.64"
3441
capacity_builder = "0.5.0"
@@ -47,4 +54,3 @@ malva = "0.11.2"
4754
markup_fmt = "0.19.0"
4855
pretty_assertions = "1.3.0"
4956
serde_json = { version = "1.0" }
50-

build.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
fn main() {
2+
// Read Cargo.toml and extract custom metadata
3+
let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap();
4+
let cargo_toml = std::fs::read_to_string(format!("{}/Cargo.toml", manifest))
5+
.expect("Failed to read Cargo.toml");
6+
7+
// Parse TOML to get metadata
8+
let toml: toml::Table = cargo_toml.parse().expect("Failed to parse Cargo.toml");
9+
10+
if let Some(package) = toml.get("package") {
11+
if let Some(metadata) = package.get("metadata") {
12+
if let Some(update_url) = metadata.get("dprint_update_url") {
13+
if let Some(url_str) = update_url.as_str() {
14+
println!("cargo:rustc-env=DPRINT_UPDATE_URL={}", url_str);
15+
}
16+
}
17+
if let Some(schema_url_template) = metadata.get("dprint_schema_url_template") {
18+
if let Some(url_str) = schema_url_template.as_str() {
19+
println!("cargo:rustc-env=DPRINT_SCHEMA_URL_TEMPLATE={}", url_str);
20+
}
21+
}
22+
}
23+
}
24+
}

deployment/schema.json

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "http://json-schema.org/draft-07/schema#",
3-
"$id": "https://plugins.dprint.dev/dprint/dprint-plugin-typescript/0.0.0/schema.json",
3+
"$id": "https://github.com/secforge/dprint-plugin-typescript/releases/download/0.95.11+sf.2/schema.json",
44
"type": "object",
55
"definitions": {
66
"useTabs": {
@@ -151,6 +151,24 @@
151151
}, {
152152
"const": "preferNone",
153153
"description": "Forces no braces when the header is one line and body is one line. Otherwise forces braces."
154+
}, {
155+
"const": "whenNeeded",
156+
"description": "Uses braces when needed for clarity or safety, avoiding them when the statement is simple and unambiguous."
157+
}, {
158+
"const": "whenFormattedMultiLine",
159+
"description": "Uses braces when the formatted code spans multiple lines, removing them for single-line statements."
160+
}]
161+
},
162+
"ternaryIndentStyle": {
163+
"description": "How ternary conditional expressions should be indented.",
164+
"type": "string",
165+
"default": "align",
166+
"oneOf": [{
167+
"const": "align",
168+
"description": "Align the ? and : operators under the condition."
169+
}, {
170+
"const": "structural",
171+
"description": "Use structural indentation for the ternary expression."
154172
}]
155173
},
156174
"bracePosition": {
@@ -199,6 +217,9 @@
199217
}, {
200218
"const": "nextLine",
201219
"description": "Forces the next control flow to be on the next line."
220+
}, {
221+
"const": "nextLineExceptAfterBrace",
222+
"description": "Forces the next control flow to be on the next line, except when preceded by a brace."
202223
}]
203224
},
204225
"trailingCommas": {
@@ -1181,6 +1202,14 @@
11811202
"conditionalExpression.operatorPosition": {
11821203
"$ref": "#/definitions/operatorPosition"
11831204
},
1205+
"conditionalExpression.indentStyle": {
1206+
"$ref": "#/definitions/ternaryIndentStyle"
1207+
},
1208+
"expressionStatement.forceParentheses": {
1209+
"description": "Whether to force parentheses around expression statements.",
1210+
"type": "boolean",
1211+
"default": true
1212+
},
11841213
"conditionalType.operatorPosition": {
11851214
"$ref": "#/definitions/operatorPosition"
11861215
},

src/configuration/builder.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,10 @@ impl ConfigurationBuilder {
822822
self.insert("conditionalExpression.operatorPosition", value.to_string().into())
823823
}
824824

825+
pub fn conditional_expression_indent_style(&mut self, value: TernaryIndentStyle) -> &mut Self {
826+
self.insert("conditionalExpression.indentStyle", value.to_string().into())
827+
}
828+
825829
pub fn conditional_type_operator_position(&mut self, value: OperatorPosition) -> &mut Self {
826830
self.insert("conditionalType.operatorPosition", value.to_string().into())
827831
}
@@ -1077,6 +1081,10 @@ impl ConfigurationBuilder {
10771081
self.insert("whileStatement.spaceAround", value.into())
10781082
}
10791083

1084+
pub fn expression_statement_force_parentheses(&mut self, value: bool) -> &mut Self {
1085+
self.insert("expressionStatement.forceParentheses", value.into())
1086+
}
1087+
10801088
#[cfg(test)]
10811089
pub(super) fn get_inner_config(&self) -> ConfigKeyMap {
10821090
self.config.clone()

src/configuration/resolve_config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ pub fn resolve_config(config: ConfigKeyMap, global_config: &GlobalConfiguration)
202202
/* operator position */
203203
binary_expression_operator_position: get_value(&mut config, "binaryExpression.operatorPosition", operator_position, &mut diagnostics),
204204
conditional_expression_operator_position: get_value(&mut config, "conditionalExpression.operatorPosition", operator_position, &mut diagnostics),
205+
conditional_expression_indent_style: get_value(&mut config, "conditionalExpression.indentStyle", TernaryIndentStyle::Align, &mut diagnostics),
205206
conditional_type_operator_position: get_value(&mut config, "conditionalType.operatorPosition", operator_position, &mut diagnostics),
206207
/* single body position */
207208
if_statement_single_body_position: get_value(&mut config, "ifStatement.singleBodyPosition", single_body_position, &mut diagnostics),
@@ -333,6 +334,7 @@ pub fn resolve_config(config: ConfigKeyMap, global_config: &GlobalConfiguration)
333334
switch_statement_space_around: get_value(&mut config, "switchStatement.spaceAround", space_around, &mut diagnostics),
334335
tuple_type_space_around: get_value(&mut config, "tupleType.spaceAround", space_around, &mut diagnostics),
335336
while_statement_space_around: get_value(&mut config, "whileStatement.spaceAround", space_around, &mut diagnostics),
337+
expression_statement_force_parentheses: get_value(&mut config, "expressionStatement.forceParentheses", true, &mut diagnostics),
336338
};
337339

338340
diagnostics.extend(get_unknown_property_diagnostics(config));

src/configuration/types.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,11 @@ pub enum NextControlFlowPosition {
119119
SameLine,
120120
/// Forces the next control flow to be on the next line.
121121
NextLine,
122+
/// Forces the next control flow to be on the next line except when it appears after a closing brace.
123+
NextLineExceptAfterBrace,
122124
}
123125

124-
generate_str_to_from![NextControlFlowPosition, [Maintain, "maintain"], [SameLine, "sameLine"], [NextLine, "nextLine"]];
126+
generate_str_to_from![NextControlFlowPosition, [Maintain, "maintain"], [SameLine, "sameLine"], [NextLine, "nextLine"], [NextLineExceptAfterBrace, "nextLineExceptAfterBrace"]];
125127

126128
/// Where to place the operator for expressions that span multiple lines.
127129
#[derive(Debug, Clone, PartialEq, Copy, Serialize, Deserialize)]
@@ -137,6 +139,19 @@ pub enum OperatorPosition {
137139

138140
generate_str_to_from![OperatorPosition, [Maintain, "maintain"], [SameLine, "sameLine"], [NextLine, "nextLine"]];
139141

142+
/// How to indent ternary expression branches.
143+
#[derive(Clone, PartialEq, Copy, Serialize, Deserialize)]
144+
#[serde(rename_all = "camelCase")]
145+
pub enum TernaryIndentStyle {
146+
/// Align all ternary branches at the same indentation level (original behavior).
147+
Align,
148+
/// Add structural indentation for nested ternary branches.
149+
Structural,
150+
}
151+
152+
generate_str_to_from![TernaryIndentStyle, [Align, "align"], [Structural, "structural"]];
153+
154+
140155
/// Where to place a node that could be on the same line or next line.
141156
#[derive(Clone, PartialEq, Copy, Serialize, Deserialize)]
142157
#[serde(rename_all = "camelCase")]
@@ -163,14 +178,20 @@ pub enum UseBraces {
163178
Always,
164179
/// Forces no braces when the header is one line and body is one line. Otherwise forces braces.
165180
PreferNone,
181+
/// Only uses braces when syntactically required (empty blocks, declarations, multiple statements).
182+
WhenNeeded,
183+
/// Uses braces when the formatted body spans multiple lines.
184+
WhenFormattedMultiLine,
166185
}
167186

168187
generate_str_to_from![
169188
UseBraces,
170189
[Maintain, "maintain"],
171190
[WhenNotSingleLine, "whenNotSingleLine"],
172191
[Always, "always"],
173-
[PreferNone, "preferNone"]
192+
[PreferNone, "preferNone"],
193+
[WhenNeeded, "whenNeeded"],
194+
[WhenFormattedMultiLine, "whenFormattedMultiLine"]
174195
];
175196

176197
/// Whether to use parentheses around a single parameter in an arrow function.
@@ -466,6 +487,8 @@ pub struct Configuration {
466487
pub binary_expression_operator_position: OperatorPosition,
467488
#[serde(rename = "conditionalExpression.operatorPosition")]
468489
pub conditional_expression_operator_position: OperatorPosition,
490+
#[serde(rename = "conditionalExpression.indentStyle")]
491+
pub conditional_expression_indent_style: TernaryIndentStyle,
469492
#[serde(rename = "conditionalType.operatorPosition")]
470493
pub conditional_type_operator_position: OperatorPosition,
471494
/* single body position */
@@ -662,4 +685,7 @@ pub struct Configuration {
662685
pub tuple_type_space_around: bool,
663686
#[serde(rename = "whileStatement.spaceAround")]
664687
pub while_statement_space_around: bool,
688+
/* expression parentheses */
689+
#[serde(rename = "expressionStatement.forceParentheses")]
690+
pub expression_statement_force_parentheses: bool,
665691
}

0 commit comments

Comments
 (0)