diff --git a/deployment/schema.json b/deployment/schema.json index 50c4f059..883d26ff 100644 --- a/deployment/schema.json +++ b/deployment/schema.json @@ -231,6 +231,21 @@ "description": "Forces the operator to be on the next line." }] }, + "paddingLineBetweenStatements": { + "description": "If true, adds blank lines between statements. If false, maintains existing spacing.", + "type": "boolean", + "default": false, + "oneOf": [ + { + "const": true, + "description": "Adds blank lines between statements for better readability." + }, + { + "const": false, + "description": "Maintains existing spacing between statements." + } + ] + }, "preferHanging": { "description": "Set to prefer hanging indentation when exceeding the line width instead of making code split up on multiple lines.", "type": "boolean", diff --git a/src/configuration/builder.rs b/src/configuration/builder.rs index aeaaa4d3..605dfe78 100644 --- a/src/configuration/builder.rs +++ b/src/configuration/builder.rs @@ -87,6 +87,14 @@ impl ConfigurationBuilder { self.insert("useTabs", value.into()) } + /// Set the padding line between statements. + /// + /// When true, adds blank lines between statements. When false, maintains existing spacing. + /// Default: `false` + pub fn padding_line_between_statements(&mut self, value: bool) -> &mut Self { + self.insert("paddingLineBetweenStatements", value.into()) + } + /// The number of columns for an indent. /// /// Default: `4` diff --git a/src/configuration/resolve_config.rs b/src/configuration/resolve_config.rs index 486b2365..b764422f 100644 --- a/src/configuration/resolve_config.rs +++ b/src/configuration/resolve_config.rs @@ -185,6 +185,8 @@ pub fn resolve_config(config: ConfigKeyMap, global_config: &GlobalConfiguration) while_statement_prefer_hanging: get_value(&mut config, "whileStatement.preferHanging", prefer_hanging, &mut diagnostics), /* member spacing */ enum_declaration_member_spacing: get_value(&mut config, "enumDeclaration.memberSpacing", MemberSpacing::Maintain, &mut diagnostics), + /* padding line between statements */ + padding_line_between_statements: get_value(&mut config, "paddingLineBetweenStatements", false, &mut diagnostics), /* next control flow position */ if_statement_next_control_flow_position: get_value(&mut config, "ifStatement.nextControlFlowPosition", next_control_flow_position, &mut diagnostics), try_statement_next_control_flow_position: get_value( diff --git a/src/configuration/types.rs b/src/configuration/types.rs index 1e67d7b7..1e1b46a0 100644 --- a/src/configuration/types.rs +++ b/src/configuration/types.rs @@ -454,6 +454,9 @@ pub struct Configuration { /* member spacing */ #[serde(rename = "enumDeclaration.memberSpacing")] pub enum_declaration_member_spacing: MemberSpacing, + /* padding line between statements */ + #[serde(rename = "paddingLineBetweenStatements")] + pub padding_line_between_statements: bool, /* next control flow position */ #[serde(rename = "ifStatement.nextControlFlowPosition")] pub if_statement_next_control_flow_position: NextControlFlowPosition, diff --git a/src/generation/generate.rs b/src/generation/generate.rs index f978a04b..bf50b072 100644 --- a/src/generation/generate.rs +++ b/src/generation/generate.rs @@ -4382,7 +4382,7 @@ fn gen_program<'a, 'b>(node: ProgramInfo<'a, 'b>, context: &mut Context<'a>) -> } } - items.extend(gen_statements(node.range, node.statements, context)); + items.extend(gen_statements(node.range, node.statements, context, true)); items } @@ -4653,7 +4653,7 @@ fn accessibility_to_str(accessibility: Accessibility) -> &'static str { fn gen_block_stmt<'a>(node: &BlockStmt<'a>, context: &mut Context<'a>) -> PrintItems { gen_block( - |stmts, context| gen_statements(node.get_inner_range(context), stmts, context), + |stmts, context| gen_statements(node.get_inner_range(context), stmts, context, false), GenBlockOptions { range: Some(node.range()), children: node.stmts.iter().map(|x| x.into()).collect(), @@ -5360,6 +5360,7 @@ fn gen_switch_case<'a>(node: &SwitchCase<'a>, context: &mut Context<'a>) -> Prin SourceRange::new(colon_token.end(), node.end()), node.cons.iter().map(|node| node.into()).collect(), context, + false, ))); } } @@ -7237,7 +7238,7 @@ where items } -fn gen_statements<'a>(inner_range: SourceRange, stmts: Vec>, context: &mut Context<'a>) -> PrintItems { +fn gen_statements<'a>(inner_range: SourceRange, stmts: Vec>, context: &mut Context<'a>, _is_top_level: bool) -> PrintItems { let stmt_groups = get_stmt_groups(stmts, context); let mut items = PrintItems::new(); let mut last_node: Option = None; @@ -7264,13 +7265,71 @@ fn gen_statements<'a>(inner_range: SourceRange, stmts: Vec>, context: & Some(sorter) => Some(get_sorted_indexes(stmt_group.nodes.iter().map(|n| Some(*n)), sorter, context)), None => None, }; + + // Clone nodes to avoid borrow checker issues + let nodes_clone = stmt_group.nodes.clone(); + for (i, node) in stmt_group.nodes.into_iter().enumerate() { let is_empty_stmt = node.is::(); if !is_empty_stmt { let mut separator_items = PrintItems::new(); if let Some(last_node) = &last_node { separator_items.push_signal(Signal::NewLine); - if node_helpers::has_separating_blank_line(&last_node, &node, context.program) { + // Check if we should add padding lines between statements based on ESLint rule + let should_add_padding = context.config.padding_line_between_statements; + if should_add_padding { + if i > 0 { + // Check if we should add blank line based on the previous statement type + let prev_node = &nodes_clone[i - 1]; + let current_node = &nodes_clone[i]; + + // Check if previous is a variable declaration + let is_prev_var = prev_node.is::(); + + // Check if current is a variable declaration + let is_current_var = current_node.is::(); + + // Check if previous is a control flow statement + let is_prev_control_flow = prev_node.is::() + || prev_node.is::() + || prev_node.is::() + || prev_node.is::() + || prev_node.is::() + || prev_node.is::() + || prev_node.is::() + || prev_node.is::(); + + // Check if previous is a function declaration + let is_prev_function = prev_node.is::(); + + // Check if previous is a class declaration + let is_prev_class = prev_node.is::(); + + // Check if previous is a block statement + let is_prev_block = prev_node.is::(); + + // Always add blank line when previous is one of the specified types + // EXCEPT when both are variable declarations (no blank line between consecutive vars) + let rule_wants_blank_line = if is_prev_var && is_current_var { + false // No blank line between consecutive variable declarations + } else if is_prev_var || is_prev_control_flow || is_prev_function || is_prev_class || is_prev_block { + true // Always add blank line after var/control-flow/function/class/block + } else { + false // No blank line for other cases + }; + + // Never remove existing blank lines—preserve what's in the source + let had_blank_line_in_source = node_helpers::has_separating_blank_line(&last_node, &node, context.program); + if rule_wants_blank_line || had_blank_line_in_source { + separator_items.push_signal(Signal::NewLine); + } + } else { + // First statement in group—only preserve existing blank lines + if node_helpers::has_separating_blank_line(&last_node, &node, context.program) { + separator_items.push_signal(Signal::NewLine); + } + } + } else if node_helpers::has_separating_blank_line(&last_node, &node, context.program) { separator_items.push_signal(Signal::NewLine); } generated_line_separators.insert(i, separator_items); @@ -8810,7 +8869,7 @@ fn gen_conditional_brace_body<'a>(opts: GenConditionalBraceBodyOptions<'a>, cont context, )); } else { - items.extend(gen_statements(inner_range, body_node.stmts.iter().map(|x| x.into()).collect(), context)); + items.extend(gen_statements(inner_range, body_node.stmts.iter().map(|x| x.into()).collect(), context, false)); } items })); diff --git a/tests/specs/general/Padding_Line_Between_Statements_False.txt b/tests/specs/general/Padding_Line_Between_Statements_False.txt new file mode 100644 index 00000000..9daa2b96 --- /dev/null +++ b/tests/specs/general/Padding_Line_Between_Statements_False.txt @@ -0,0 +1,40 @@ +~~ paddingLineBetweenStatements: false ~~ +== should maintain existing spacing between statements == +const a = 1; +const b = 2; + +function test() { + const too = 0; + if (a > 0) { + const foo = 0; + const bar = 1; + console.log(a); + } + if (b > 0) { + const foo = 0; + const bar = 1; + console.log(b); + } else { + console.log(too); + } +} + +[expect] +const a = 1; +const b = 2; + +function test() { + const too = 0; + if (a > 0) { + const foo = 0; + const bar = 1; + console.log(a); + } + if (b > 0) { + const foo = 0; + const bar = 1; + console.log(b); + } else { + console.log(too); + } +} diff --git a/tests/specs/general/Padding_Line_Between_Statements_True.txt b/tests/specs/general/Padding_Line_Between_Statements_True.txt new file mode 100644 index 00000000..725bcbf5 --- /dev/null +++ b/tests/specs/general/Padding_Line_Between_Statements_True.txt @@ -0,0 +1,61 @@ +~~ paddingLineBetweenStatements: true ~~ +== should add blank lines between statements == +const a = 1; +const b = 2; + +function test() { + const too = 0; + if (a > 0) { + const foo = 0; + const bar = 1; + console.log(a); + } + if (b > 0) { + const foo = 0; + const bar = 1; + console.log(b); + } else { + console.log(too); + } + test2(); + test2(); + + console.log("hello world"); + // Get the header from the first row of the first file +} + +function test2() { +} + +[expect] +const a = 1; +const b = 2; + +function test() { + const too = 0; + + if (a > 0) { + const foo = 0; + const bar = 1; + + console.log(a); + } + + if (b > 0) { + const foo = 0; + const bar = 1; + + console.log(b); + } else { + console.log(too); + } + + test2(); + test2(); + + console.log("hello world"); + // Get the header from the first row of the first file +} + +function test2() { +}