Skip to content

Commit e40d378

Browse files
crisbetoatscott
authored andcommitted
fix(compiler): handle nested brackets in host object bindings
Fixes that we were parsing bindings in the `host` object with a regex that didn't account for nested brackets which may come up with something like Tailwind. Fixes #68039. (cherry picked from commit 2ce0e98)
1 parent d715c36 commit e40d378

3 files changed

Lines changed: 24 additions & 35 deletions

File tree

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/GOLDEN_PARTIAL.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ import * as i0 from "@angular/core";
308308
export class MyComponent {
309309
expr = true;
310310
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
311-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: true, selector: "ng-component", host: { properties: { "class.text-primary/80": "expr", "class.data-active:text-green-300/80": "expr", "class.data-[size='large'": "expr" } }, ngImport: i0, template: ``, isInline: true });
311+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: true, selector: "ng-component", host: { properties: { "class.text-primary/80": "expr", "class.data-active:text-green-300/80": "expr", "class.data-[size='large']:p-8": "expr" } }, ngImport: i0, template: ``, isInline: true });
312312
}
313313
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
314314
type: Component,

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/host_class_binding_special_chars.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ $r3$.ɵɵdefineComponent({
33
hostVars: 6,
44
hostBindings: function MyComponent_HostBindings(rf, ctx) {
55
if (rf & 2) {
6-
$r3$.ɵɵclassProp("text-primary/80", ctx.expr)("data-active:text-green-300/80", ctx.expr)("data-[size='large'", ctx.expr);
6+
$r3$.ɵɵclassProp("text-primary/80", ctx.expr)("data-active:text-green-300/80", ctx.expr)("data-[size='large']:p-8", ctx.expr);
77
}
88
},
99

packages/compiler/src/render3/view/compiler.ts

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -537,38 +537,42 @@ function createHostBindingsFunction(
537537
return emitHostBindingFunction(hostJob);
538538
}
539539

540-
const HOST_REG_EXP = /^(?:\[([^\]]+)\])|(?:\(([^\)]+)\))$/;
541-
// Represents the groups in the above regex.
542-
const enum HostBindingGroup {
543-
// group 1: "prop" from "[prop]", or "attr.role" from "[attr.role]", or @anim from [@anim]
544-
Binding = 1,
545-
546-
// group 2: "event" from "(event)"
547-
Event = 2,
548-
}
549-
550540
// Defines Host Bindings structure that contains attributes, listeners, and properties,
551541
// parsed from the `host` object defined for a Type.
552542
export interface ParsedHostBindings {
553-
attributes: {[key: string]: o.Expression};
554-
listeners: {[key: string]: string};
555-
properties: {[key: string]: string};
543+
attributes: Record<string, o.Expression>;
544+
listeners: Record<string, string>;
545+
properties: Record<string, string>;
556546
specialAttributes: {styleAttr?: string; classAttr?: string};
557547
}
558548

559549
export function parseHostBindings(host: {
560550
[key: string]: string | o.Expression;
561551
}): ParsedHostBindings {
562-
const attributes: {[key: string]: o.Expression} = {};
563-
const listeners: {[key: string]: string} = {};
564-
const properties: {[key: string]: string} = {};
552+
const attributes: Record<string, o.Expression> = {};
553+
const listeners: Record<string, string> = {};
554+
const properties: Record<string, string> = {};
565555
const specialAttributes: {styleAttr?: string; classAttr?: string} = {};
566556

567557
for (const key of Object.keys(host)) {
568558
const value = host[key];
569-
const matches = key.match(HOST_REG_EXP);
570559

571-
if (matches === null) {
560+
if (key.startsWith('(') && key.endsWith(')')) {
561+
if (typeof value !== 'string') {
562+
// TODO(alxhub): make this a diagnostic.
563+
throw new Error(`Event binding must be string`);
564+
}
565+
listeners[key.slice(1, -1)] = value;
566+
} else if (key.startsWith('[') && key.endsWith(']')) {
567+
if (typeof value !== 'string') {
568+
// TODO(alxhub): make this a diagnostic.
569+
throw new Error(`Property binding must be string`);
570+
}
571+
// synthetic properties (the ones that have a `@` as a prefix)
572+
// are still treated the same as regular properties. Therefore
573+
// there is no point in storing them in a separate map.
574+
properties[key.slice(1, -1)] = value;
575+
} else {
572576
switch (key) {
573577
case 'class':
574578
if (typeof value !== 'string') {
@@ -591,21 +595,6 @@ export function parseHostBindings(host: {
591595
attributes[key] = value;
592596
}
593597
}
594-
} else if (matches[HostBindingGroup.Binding] != null) {
595-
if (typeof value !== 'string') {
596-
// TODO(alxhub): make this a diagnostic.
597-
throw new Error(`Property binding must be string`);
598-
}
599-
// synthetic properties (the ones that have a `@` as a prefix)
600-
// are still treated the same as regular properties. Therefore
601-
// there is no point in storing them in a separate map.
602-
properties[matches[HostBindingGroup.Binding]] = value;
603-
} else if (matches[HostBindingGroup.Event] != null) {
604-
if (typeof value !== 'string') {
605-
// TODO(alxhub): make this a diagnostic.
606-
throw new Error(`Event binding must be string`);
607-
}
608-
listeners[matches[HostBindingGroup.Event]] = value;
609598
}
610599
}
611600

0 commit comments

Comments
 (0)