Skip to content

Commit 399c894

Browse files
Brooooooklynclaude
andcommitted
Angular compiler alignment: 98.4% material-angular match rate (615/625)
Fixes applied: - Track function detection for implicit `this` calls in @for loops - Variable ordering / context inlining prevention (don't inline nextContext when intervening ViewContextRead fences exist) - Constant pool function ordering (function declarations before arrow functions) - Formatting improvements (double quotes, comma spacing, blank lines between declarations) Results: - material-angular: 610/625 (97.6%) → 615/625 (98.4%) - bitwarden-clients: 651/651 (100%) maintained 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 2612af0 commit 399c894

File tree

9 files changed

+1065
-780
lines changed

9 files changed

+1065
-780
lines changed

crates/oxc_angular_compiler/src/output/emitter.rs

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -388,9 +388,7 @@ impl JsEmitter {
388388
/// Emit multiple statements to a string.
389389
pub fn emit_statements<'a>(&self, stmts: &[OutputStatement<'a>]) -> String {
390390
let mut ctx = EmitterContext::new();
391-
for stmt in stmts {
392-
self.visit_statement(stmt, &mut ctx);
393-
}
391+
self.visit_all_statements(stmts, &mut ctx);
394392
ctx.to_source()
395393
}
396394

@@ -420,9 +418,7 @@ impl JsEmitter {
420418
generated_file: Option<&str>,
421419
) -> (String, Option<oxc_sourcemap::SourceMap>) {
422420
let mut ctx = EmitterContext::with_source_file(source_file);
423-
for stmt in stmts {
424-
self.visit_statement(stmt, &mut ctx);
425-
}
421+
self.visit_all_statements(stmts, &mut ctx);
426422
ctx.to_source_with_map(generated_file)
427423
}
428424

@@ -440,6 +436,32 @@ impl JsEmitter {
440436
}
441437
}
442438

439+
/// Visit all statements, adding blank lines between top-level const/function declarations.
440+
///
441+
/// This matches Angular's output formatting which adds a blank line between:
442+
/// - Consecutive const declarations
443+
/// - Consecutive function declarations
444+
/// - Between const and function declarations
445+
fn visit_all_statements<'a>(&self, stmts: &[OutputStatement<'a>], ctx: &mut EmitterContext) {
446+
for (i, stmt) in stmts.iter().enumerate() {
447+
// Add blank line between top-level declarations (const/function)
448+
if i > 0 {
449+
let prev_stmt = &stmts[i - 1];
450+
let needs_blank_line = matches!(
451+
(prev_stmt, stmt),
452+
(OutputStatement::DeclareVar(_), OutputStatement::DeclareVar(_))
453+
| (OutputStatement::DeclareVar(_), OutputStatement::DeclareFunction(_))
454+
| (OutputStatement::DeclareFunction(_), OutputStatement::DeclareFunction(_))
455+
| (OutputStatement::DeclareFunction(_), OutputStatement::DeclareVar(_))
456+
);
457+
if needs_blank_line {
458+
ctx.newline();
459+
}
460+
}
461+
self.visit_statement(stmt, ctx);
462+
}
463+
}
464+
443465
fn visit_declare_var_stmt(&self, stmt: &DeclareVarStmt<'_>, ctx: &mut EmitterContext) {
444466
// Print leading comment if present
445467
// See: packages/compiler/src/output/abstract_emitter.ts:218-235
@@ -657,7 +679,7 @@ impl JsEmitter {
657679
}
658680
OutputExpression::Comma(e) => {
659681
ctx.print("(");
660-
self.visit_all_expressions(&e.parts, ctx, ",");
682+
self.visit_all_expressions(&e.parts, ctx, ", ");
661683
ctx.print(")");
662684
}
663685
OutputExpression::Function(e) => {
@@ -679,7 +701,7 @@ impl JsEmitter {
679701
OutputExpression::ArrowFunction(e) => {
680702
ctx.print_with_span("(", source_span);
681703
self.visit_params(&e.params, ctx);
682-
ctx.print(") =>");
704+
ctx.print(") => ");
683705
match &e.body {
684706
ArrowFunctionBody::Expression(body_expr) => {
685707
// Check if the body is an object literal (needs parens)
@@ -717,14 +739,14 @@ impl JsEmitter {
717739
}
718740
// Map the function call to its source location
719741
ctx.print_with_span("(", source_span);
720-
self.visit_all_expressions(&e.args, ctx, ",");
742+
self.visit_all_expressions(&e.args, ctx, ", ");
721743
ctx.print(")");
722744
}
723745
OutputExpression::Instantiate(e) => {
724746
ctx.print_with_span("new ", source_span);
725747
self.visit_expression(&e.class_expr, ctx);
726748
ctx.print("(");
727-
self.visit_all_expressions(&e.args, ctx, ",");
749+
self.visit_all_expressions(&e.args, ctx, ", ");
728750
ctx.print(")");
729751
}
730752
OutputExpression::DynamicImport(e) => {
@@ -802,7 +824,7 @@ impl JsEmitter {
802824

803825
fn visit_literal_array<'a>(&self, entries: &[OutputExpression<'a>], ctx: &mut EmitterContext) {
804826
ctx.print("[");
805-
self.visit_all_expressions(entries, ctx, ",");
827+
self.visit_all_expressions(entries, ctx, ", ");
806828
ctx.print("]");
807829
}
808830

@@ -818,33 +840,37 @@ impl JsEmitter {
818840
entries: &[super::ast::LiteralMapEntry<'a>],
819841
ctx: &mut EmitterContext,
820842
) {
821-
ctx.print("{");
843+
if entries.is_empty() {
844+
ctx.print("{}");
845+
return;
846+
}
847+
ctx.print("{ ");
822848
let mut incremented_indent = false;
823849
for (i, entry) in entries.iter().enumerate() {
824850
if i > 0 {
825851
// Check line length and break if needed
826852
if ctx.line_length() > LINE_LENGTH_LIMIT {
827-
ctx.println(",");
853+
ctx.println(", ");
828854
if !incremented_indent {
829855
// Continuation lines are marked with double indent
830856
ctx.inc_indent();
831857
ctx.inc_indent();
832858
incremented_indent = true;
833859
}
834860
} else {
835-
ctx.print(",");
861+
ctx.print(", ");
836862
}
837863
}
838864
let key = escape_identifier(&entry.key, self.escape_dollar_in_strings, entry.quoted);
839865
ctx.print(&key);
840-
ctx.print(":");
866+
ctx.print(": ");
841867
self.visit_expression(&entry.value, ctx);
842868
}
843869
if incremented_indent {
844870
ctx.dec_indent();
845871
ctx.dec_indent();
846872
}
847-
ctx.print("}");
873+
ctx.print(" }");
848874
}
849875

850876
fn visit_template_literal(
@@ -950,7 +976,7 @@ impl JsEmitter {
950976
fn visit_params(&self, params: &[FnParam<'_>], ctx: &mut EmitterContext) {
951977
for (i, param) in params.iter().enumerate() {
952978
if i > 0 {
953-
ctx.print(",");
979+
ctx.print(", ");
954980
}
955981
ctx.print(&param.name);
956982
}
@@ -1047,18 +1073,18 @@ fn unary_operator_to_str(op: UnaryOperator) -> &'static str {
10471073
/// Escape a string for JavaScript output.
10481074
fn escape_string(input: &str, escape_dollar: bool) -> String {
10491075
let mut result = String::with_capacity(input.len() + 2);
1050-
result.push('\'');
1076+
result.push('"');
10511077
for c in input.chars() {
10521078
match c {
1053-
'\'' => result.push_str("\\'"),
1079+
'"' => result.push_str("\\\""),
10541080
'\\' => result.push_str("\\\\"),
10551081
'\n' => result.push_str("\\n"),
10561082
'\r' => result.push_str("\\r"),
10571083
'$' if escape_dollar => result.push_str("\\$"),
10581084
_ => result.push(c),
10591085
}
10601086
}
1061-
result.push('\'');
1087+
result.push('"');
10621088
result
10631089
}
10641090

@@ -1136,7 +1162,7 @@ mod tests {
11361162
LiteralExpr { value: LiteralValue::String(Atom::from("hello")), source_span: None },
11371163
&alloc,
11381164
));
1139-
assert_eq!(emitter.emit_expression(&expr), "'hello'");
1165+
assert_eq!(emitter.emit_expression(&expr), "\"hello\"");
11401166
}
11411167

11421168
#[test]
@@ -1152,11 +1178,12 @@ mod tests {
11521178

11531179
#[test]
11541180
fn test_escape_string() {
1155-
assert_eq!(escape_string("hello", false), "'hello'");
1156-
assert_eq!(escape_string("it's", false), "'it\\'s'");
1157-
assert_eq!(escape_string("line\nbreak", false), "'line\\nbreak'");
1158-
assert_eq!(escape_string("$var", true), "'\\$var'");
1159-
assert_eq!(escape_string("$var", false), "'$var'");
1181+
assert_eq!(escape_string("hello", false), "\"hello\"");
1182+
assert_eq!(escape_string("it's", false), "\"it's\"");
1183+
assert_eq!(escape_string("say \"hi\"", false), "\"say \\\"hi\\\"\"");
1184+
assert_eq!(escape_string("line\nbreak", false), "\"line\\nbreak\"");
1185+
assert_eq!(escape_string("$var", true), "\"\\$var\"");
1186+
assert_eq!(escape_string("$var", false), "\"$var\"");
11601187
}
11611188

11621189
// ========================================================================

0 commit comments

Comments
 (0)