Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions deployment/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 8 additions & 0 deletions src/configuration/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
2 changes: 2 additions & 0 deletions src/configuration/resolve_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
3 changes: 3 additions & 0 deletions src/configuration/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
69 changes: 64 additions & 5 deletions src/generation/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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,
)));
}
}
Expand Down Expand Up @@ -7237,7 +7238,7 @@ where
items
}

fn gen_statements<'a>(inner_range: SourceRange, stmts: Vec<Node<'a>>, context: &mut Context<'a>) -> PrintItems {
fn gen_statements<'a>(inner_range: SourceRange, stmts: Vec<Node<'a>>, 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<SourceRange> = None;
Expand All @@ -7264,13 +7265,71 @@ fn gen_statements<'a>(inner_range: SourceRange, stmts: Vec<Node<'a>>, 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::<EmptyStmt>();
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::<VarDecl>();

// Check if current is a variable declaration
let is_current_var = current_node.is::<VarDecl>();

// Check if previous is a control flow statement
let is_prev_control_flow = prev_node.is::<ForStmt>()
|| prev_node.is::<ForInStmt>()
|| prev_node.is::<ForOfStmt>()
|| prev_node.is::<WhileStmt>()
|| prev_node.is::<DoWhileStmt>()
|| prev_node.is::<SwitchStmt>()
|| prev_node.is::<IfStmt>()
|| prev_node.is::<TryStmt>();

// Check if previous is a function declaration
let is_prev_function = prev_node.is::<FnDecl>();

// Check if previous is a class declaration
let is_prev_class = prev_node.is::<ClassDecl>();

// Check if previous is a block statement
let is_prev_block = prev_node.is::<BlockStmt>();

// 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);
Expand Down Expand Up @@ -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
}));
Expand Down
40 changes: 40 additions & 0 deletions tests/specs/general/Padding_Line_Between_Statements_False.txt
Original file line number Diff line number Diff line change
@@ -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);
}
}
61 changes: 61 additions & 0 deletions tests/specs/general/Padding_Line_Between_Statements_True.txt
Original file line number Diff line number Diff line change
@@ -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() {
}