Skip to content
Merged
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
7 changes: 6 additions & 1 deletion crates/oxc_angular_compiler/src/parser/html/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::ast::html::{
InterpolatedToken, InterpolatedTokenType,
};
use crate::parser::expression::BindingParser;
use crate::transform::control_flow::is_else_if_pattern;
use crate::util::{ParseError, ParseLocation, ParseSourceFile, ParseSourceSpan};

use super::entities::decode_entities_in_string;
Expand Down Expand Up @@ -1501,7 +1502,11 @@ impl<'a> HtmlParser<'a> {
let block_type = match name.as_str() {
"if" => BlockType::If,
"else" => BlockType::Else,
"else if" => BlockType::ElseIf,
// Match Angular's ELSE_IF_PATTERN: /^else[^\S\r\n]+if/
// Any block name starting with "else " followed by "if" (e.g. "else if",
// "else ifx") is classified as ElseIf, matching Angular's regex-based
// connected-block detection.
_ if is_else_if_pattern(&name) => BlockType::ElseIf,
"for" => BlockType::For,
"empty" => BlockType::Empty,
"switch" => BlockType::Switch,
Expand Down
8 changes: 6 additions & 2 deletions crates/oxc_angular_compiler/src/transform/control_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ fn parse_as_alias(s: &str) -> Option<&str> {
Some(after_as.trim_start())
}

/// Check if string matches `^else\s+if` pattern.
fn is_else_if_pattern(s: &str) -> bool {
/// Check if string matches Angular's `ELSE_IF_PATTERN`: `/^else[^\S\r\n]+if/`.
///
/// Any name starting with "else" followed by at least one whitespace character
/// and then "if" is treated as an else-if block. This means names like
/// "else ifx" also match, which is intentional to mirror Angular's behavior.
pub fn is_else_if_pattern(s: &str) -> bool {
if !s.starts_with("else") {
return false;
}
Expand Down
15 changes: 15 additions & 0 deletions crates/oxc_angular_compiler/tests/r3_template_transform_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,21 @@ mod if_blocks {
assert_eq!(result[1], h!["IfBlockBranch", "cond1"]);
assert_eq!(result[3], h!["IfBlockBranch", "cond2"]);
}

/// Angular uses the regex `/^else[^\S\r\n]+if/` to detect "else if" blocks,
/// which means block names like "else ifx" still match as connected else-if
/// branches. Our parser must replicate this behavior to avoid emitting two
/// independent conditionals instead of a single chained one.
#[test]
fn should_treat_else_if_prefix_as_connected_block() {
// "else ifx" should still be chained as a connected else-if branch,
// matching Angular's regex-based ELSE_IF_PATTERN: /^else[^\S\r\n]+if/
let result = humanize_ignore_errors("@if (cond1) { a } @else ifx (cond2) { b }");
// Should produce a single IfBlock with two branches, not two independent blocks
assert_eq!(result[0], h!["IfBlock"]);
assert_eq!(result[1], h!["IfBlockBranch", "cond1"]);
assert_eq!(result[3], h!["IfBlockBranch", "cond2"]);
}
}

// ============================================================================
Expand Down
Loading