diff --git a/crates/oxc_linter/src/rules/eslint/curly.rs b/crates/oxc_linter/src/rules/eslint/curly.rs index 70a57ccdd87ae..1370d1165bd03 100644 --- a/crates/oxc_linter/src/rules/eslint/curly.rs +++ b/crates/oxc_linter/src/rules/eslint/curly.rs @@ -356,12 +356,6 @@ fn get_if_branches_from_statement<'a>( branches } -fn get_node_by_statement<'a>(statement: &'a Statement, ctx: &'a LintContext) -> &'a AstNode<'a> { - let span = statement.span(); - - ctx.nodes().iter().find(|n| n.span() == span).expect("Failed to get node by statement") -} - fn get_if_else_keyword(is_else: bool) -> &'static str { if is_else { "else" } else { "if" } } @@ -451,9 +445,8 @@ fn report_if_needed<'a>( } #[expect(clippy::cast_possible_truncation)] -fn is_collapsed_one_liner(node: &Statement, ctx: &LintContext) -> bool { - let node = get_node_by_statement(node, ctx); - let span = node.span(); +fn is_collapsed_one_liner(stmt: &Statement, ctx: &LintContext) -> bool { + let span = stmt.span(); let node_string = ctx.source_range(span); let trimmed = node_string.trim_end_matches(|c: char| c.is_whitespace() || c == ';'); @@ -462,28 +455,18 @@ fn is_collapsed_one_liner(node: &Statement, ctx: &LintContext) -> bool { Err(_) => return false, // length too large for u32 }; - let before_node_span = get_token_before(node, ctx).map_or_else( - || { - let parent = ctx.nodes().parent_node(node.id()); - - if parent.span().start < span.start { - Span::empty(parent.span().start) - } else { - Span::empty(0) - } - }, - oxc_span::GetSpan::span, - ); + let src = ctx.source_text(); + let stmt_start = span.start as usize; - let Some(next_char_offset) = get_next_char_offset(before_node_span, ctx) else { - return true; - }; + let before_stmt = &src[..stmt_start]; + let prev_token_end = + before_stmt.bytes().rposition(|b| !b.is_ascii_whitespace()).map_or(0, |pos| pos + 1); let end_offset = span.end.saturating_sub((node_string.len() as u32).saturating_sub(trimmed_len)); - let text = ctx.source_range(Span::new(next_char_offset, end_offset)); + let text_to_check = &src[prev_token_end..end_offset as usize]; - !text.contains('\n') + !text_to_check.contains('\n') } fn is_one_liner(node: &Statement, ctx: &LintContext) -> bool { @@ -497,12 +480,6 @@ fn is_one_liner(node: &Statement, ctx: &LintContext) -> bool { !trimmed.contains('\n') } -fn get_token_before<'a>(node: &AstNode, ctx: &'a LintContext) -> Option<&'a AstNode<'a>> { - let span_start = node.span().start; - - ctx.nodes().iter().filter(|n| n.span().end < span_start).max_by_key(|n| n.span().end) -} - pub fn are_braces_necessary(node: &Statement, ctx: &LintContext) -> bool { let Statement::BlockStatement(block) = node else { return false; @@ -970,6 +947,20 @@ fn test() { };", Some(serde_json::json!(["multi"])), ), + ("if (foo) /* comment */ bar()", Some(serde_json::json!(["multi-line"]))), + ("while (foo) /* comment */ bar()", Some(serde_json::json!(["multi-line"]))), + ("for (;;) /* comment */ bar()", Some(serde_json::json!(["multi-line"]))), + ("if (foo) /* 日本語コメント */ bar()", Some(serde_json::json!(["multi-line"]))), + ("if (foo) /* émojis: 🎉🚀 */ bar()", Some(serde_json::json!(["multi-line"]))), + ("if (foo) /* special: @#$%^&*() */ bar()", Some(serde_json::json!(["multi-line"]))), + ("if (foo)\t\tbar()", Some(serde_json::json!(["multi-line"]))), + ("if (foo) \t bar()", Some(serde_json::json!(["multi-line"]))), + ("if (foo) /* { */ bar()", Some(serde_json::json!(["multi-line"]))), + ("if (foo) /* } */ bar()", Some(serde_json::json!(["multi-line"]))), + ("if (foo) /* { } */ bar()", Some(serde_json::json!(["multi-line"]))), + ("if (foo) /* {{ nested }} */ bar()", Some(serde_json::json!(["multi-line"]))), + ("while (foo) /* [brackets] */ bar()", Some(serde_json::json!(["multi-line"]))), + ("for (;;) /* (parens) { braces } */ bar()", Some(serde_json::json!(["multi-line"]))), ]; let fail = vec![