diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts index b720bbeca56af..a98633589d8f3 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts @@ -864,7 +864,7 @@ class TcbDomSchemaCheckerOp extends TcbOp { if (binding.type === BindingType.Property) { if (binding.name !== 'style' && binding.name !== 'class') { // A direct binding to a property. - const propertyName = ATTR_TO_PROP[binding.name] || binding.name; + const propertyName = ATTR_TO_PROP.get(binding.name) ?? binding.name; this.tcb.domSchemaChecker.checkProperty( this.tcb.id, this.element, propertyName, binding.sourceSpan, this.tcb.schemas, this.tcb.hostIsStandalone); @@ -880,14 +880,14 @@ class TcbDomSchemaCheckerOp extends TcbOp { * Mapping between attributes names that don't correspond to their element property names. * Note: this mapping has to be kept in sync with the equally named mapping in the runtime. */ -const ATTR_TO_PROP: {[name: string]: string} = { +const ATTR_TO_PROP = new Map(Object.entries({ 'class': 'className', 'for': 'htmlFor', 'formaction': 'formAction', 'innerHtml': 'innerHTML', 'readonly': 'readOnly', 'tabindex': 'tabIndex', -}; +})); /** * A `TcbOp` which generates code to check "unclaimed inputs" - bindings on an element which were @@ -930,7 +930,7 @@ class TcbUnclaimedInputsOp extends TcbOp { elId = this.scope.resolve(this.element); } // A direct binding to a property. - const propertyName = ATTR_TO_PROP[binding.name] || binding.name; + const propertyName = ATTR_TO_PROP.get(binding.name) ?? binding.name; const prop = ts.factory.createElementAccessExpression( elId, ts.factory.createStringLiteral(propertyName)); const stmt = ts.factory.createBinaryExpression( diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 1facc78e6841d..dd429f63cd19e 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -3670,6 +3670,24 @@ function allTests(os: string) { expect(trim(jsContents)).toContain(trim(hostBindingsFn)); }); + // https://github.com/angular/angular/issues/46936 + it('should support bindings with Object builtin names', () => { + env.write('test.ts', ` + import {Component} from '@angular/core'; + + @Component({ + selector: 'test-cmp', + template: '
', + }) + export class TestCmp {} + `); + + const errors = env.driveDiagnostics(); + expect(errors.length).toBe(1); + expect(errors[0].messageText) + .toContain(`Can't bind to 'valueOf' since it isn't a known property of 'div'.`); + }); + it('should handle $any used inside a listener', () => { env.write('test.ts', ` import {Component} from '@angular/core'; diff --git a/packages/compiler/src/schema/dom_element_schema_registry.ts b/packages/compiler/src/schema/dom_element_schema_registry.ts index 14599ae41d749..68def7c973244 100644 --- a/packages/compiler/src/schema/dom_element_schema_registry.ts +++ b/packages/compiler/src/schema/dom_element_schema_registry.ts @@ -7,7 +7,6 @@ */ import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '../core'; - import {isNgContainer, isNgContent} from '../ml_parser/tags'; import {dashCaseToCamelCase} from '../util'; @@ -232,46 +231,46 @@ const SCHEMA: string[] = [ ':svg:cursor^:svg:|', ]; -const _ATTR_TO_PROP: {[name: string]: string} = { +const _ATTR_TO_PROP = new Map(Object.entries({ 'class': 'className', 'for': 'htmlFor', 'formaction': 'formAction', 'innerHtml': 'innerHTML', 'readonly': 'readOnly', 'tabindex': 'tabIndex', -}; +})); // Invert _ATTR_TO_PROP. -const _PROP_TO_ATTR: {[name: string]: string} = - Object.keys(_ATTR_TO_PROP).reduce((inverted, attr) => { - inverted[_ATTR_TO_PROP[attr]] = attr; +const _PROP_TO_ATTR = + Array.from(_ATTR_TO_PROP).reduce((inverted, [propertyName, attributeName]) => { + inverted.set(propertyName, attributeName); return inverted; - }, {} as {[prop: string]: string}); + }, new Map