Skip to content

Commit 626d83b

Browse files
committed
fix(conformance): restore v21.2.7 data- bindings after main merge
Merge from main pulled v22 semantics that break the v21.2.7 pin for #315: - restore case-insensitive data- attribute name normalization for bindings - strip trailing semicolon from @let humanized source spans (v21 fixtures) - fix shadow_css comment fixtures that mixed {} input with {c} expected Conformance: 1273/1273 (100%).
1 parent 683d285 commit 626d83b

4 files changed

Lines changed: 29 additions & 18 deletions

File tree

‎crates/angular_conformance/fixtures/shadow_css_shadow_css_spec.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"assertions": [
1616
{
1717
"type": "ShimCss",
18-
"input": "/* b {} */ b {}",
18+
"input": "/* b {c} */ b {c}",
1919
"content_attr": "contenta",
2020
"host_attr": null,
2121
"expected": " b[contenta] {c}",
@@ -57,7 +57,7 @@
5757
"assertions": [
5858
{
5959
"type": "ShimCss",
60-
"input": "/* b {} */ b {} /* a {} */ a {}",
60+
"input": "/* b {c} */ b {c} /* a {c} */ a {c}",
6161
"content_attr": "contenta",
6262
"host_attr": null,
6363
"expected": " b[contenta] {c} a[contenta] {c}",
@@ -93,7 +93,7 @@
9393
"assertions": [
9494
{
9595
"type": "ShimCss",
96-
"input": "/* comment 1 */ /* comment 2 */ b {}",
96+
"input": "/* comment 1 */ /* comment 2 */ b {c}",
9797
"content_attr": "contenta",
9898
"host_attr": null,
9999
"expected": " b[contenta] {c}",

‎crates/angular_conformance/snapshots/angular.snap.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
| ast_serializer | 6 | 0 | 0 | 0 | 6 | 100.0% |
88
| ast_spans | 55 | 0 | 0 | 0 | 55 | 100.0% |
99
| expression_lexer | 137 | 0 | 0 | 0 | 137 | 100.0% |
10-
| expression_parser | 266 | 0 | 0 | 0 | 266 | 100.0% |
10+
| expression_parser | 270 | 0 | 0 | 0 | 270 | 100.0% |
1111
| expression_serializer | 38 | 0 | 0 | 0 | 38 | 100.0% |
12-
| html_lexer | 288 | 0 | 0 | 0 | 288 | 100.0% |
13-
| html_parser | 87 | 0 | 0 | 0 | 87 | 100.0% |
12+
| html_lexer | 291 | 0 | 0 | 0 | 291 | 100.0% |
13+
| html_parser | 90 | 0 | 0 | 0 | 90 | 100.0% |
1414
| html_whitespace | 21 | 0 | 0 | 0 | 21 | 100.0% |
15-
| r3_transform | 174 | 0 | 0 | 0 | 174 | 100.0% |
16-
| shadow_css | 172 | 0 | 0 | 0 | 172 | 100.0% |
15+
| r3_transform | 180 | 0 | 0 | 0 | 180 | 100.0% |
16+
| shadow_css | 170 | 0 | 0 | 0 | 170 | 100.0% |
1717
| style_parser | 15 | 0 | 0 | 0 | 15 | 100.0% |
18-
| **Total** | **1259** | **0** | **0** | **0** | **1259** | **100.0%** |
18+
| **Total** | **1273** | **0** | **0** | **0** | **1273** | **100.0%** |
1919

‎crates/angular_conformance/src/subsystems/r3_transform/humanizer.rs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,8 +544,10 @@ impl<'a> R3Visitor<'a> for R3Humanizer<'_> {
544544
) {
545545
if self.mode == HumanizeMode::SourceSpans {
546546
// Source spans mode: [LetDeclaration, sourceSpan, name, value]
547-
// The source span includes the trailing semicolon (Angular v22).
547+
// v21.2.7 fixtures: the humanized source span does not include the
548+
// trailing semicolon (Angular later changed this in v22).
548549
let source_span = self.span_text(&decl.source_span);
550+
let source_span = source_span.trim_end_matches(';').to_string();
549551
let name = self.span_text(&decl.name_span);
550552
let value = self.span_text(&decl.value_span);
551553
self.result.push(vec!["LetDeclaration".to_string(), source_span, name, value]);

‎crates/oxc_angular_compiler/src/transform/html_to_r3.rs‎

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3182,10 +3182,11 @@ impl<'a> HtmlToR3Transform<'a> {
31823182

31833183
for attr in attrs {
31843184
let raw_name = attr.name.as_str();
3185-
// Angular v22 removed the `data-` prefix normalization: binding syntax
3186-
// (`bind-`, `on-`, `bindon-`, `ref-`, `let-`, `*`, `@`) is matched against
3187-
// the raw attribute name, so e.g. `data-ref-a` is a plain text attribute.
3188-
let name = raw_name;
3185+
// Normalize name early (case-insensitive data- prefix stripping).
3186+
// v21.2.7 binding_parser strips /^data-/i before matching bind-/on-/etc.
3187+
// This must happen before ANY binding syntax checks. (Angular v22 later
3188+
// removed this normalization; we stay on the v21.2.7 pin for #315.)
3189+
let name = self.normalize_attribute_name(raw_name);
31893190

31903191
// Skip i18n-* attributes early - they are metadata for other attributes, not bindings.
31913192
// In Angular's TypeScript compiler, these are filtered out by I18nMetaVisitor before
@@ -3423,11 +3424,19 @@ impl<'a> HtmlToR3Transform<'a> {
34233424
}
34243425

34253426
/// Normalizes an attribute name by stripping the data- prefix (case-insensitive).
3426-
/// This matches TypeScript's behavior: /^data-/i.test(attrName) ? attrName.substring(5) : attrName
3427-
/// Parses a binding prefix (`bind-`, `let-`, `ref-`, `on-`, `bindon-`) from an
3428-
/// attribute name. Angular v22 matches these against the raw name; a `data-`
3429-
/// prefix is no longer stripped, so `data-on-x` is not an event binding.
3427+
/// Matches TypeScript v21.2.7: `/^data-/i.test(attrName) ? attrName.substring(5) : attrName`.
3428+
fn normalize_attribute_name<'b>(&self, name: &'b str) -> &'b str {
3429+
if name.len() > 5 && name[..5].eq_ignore_ascii_case("data-") { &name[5..] } else { name }
3430+
}
3431+
3432+
/// Parses a binding prefix from an attribute name.
3433+
/// Handles the data- prefix as per Angular v21.2.7 normalization:
3434+
/// `data-bind-*`, `data-on-*`, `data-ref-*`, `data-let-*`, `data-bindon-*`.
34303435
fn parse_binding_prefix<'b>(&self, name: &'b str) -> Option<(BindingPrefix, &'b str)> {
3436+
// Strip data- prefix if present (case-insensitive). Callers typically already
3437+
// normalized, but keep this for safety when invoked with a raw name.
3438+
let name = self.normalize_attribute_name(name);
3439+
34313440
for (prefix, kind) in BIND_NAME_PREFIXES {
34323441
if let Some(rest) = name.strip_prefix(prefix) {
34333442
return Some((*kind, rest));

0 commit comments

Comments
 (0)