diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/expression.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/expression.ts index c1b199c28db..4cf410fbf4a 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/expression.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/expression.ts @@ -178,12 +178,18 @@ class TcbExprTranslator implements AstVisitor { let literal = `{ ${properties.join(', ')} }`; - // If strictLiteralTypes is disabled, array literals are cast to `any`. if (!this.config.strictLiteralTypes) { - literal = `(${literal} as any)`; + // If strictLiteralTypes is disabled, array literals are cast to `any`. + literal = `${literal} as any`; } - return new TcbExpr(literal).addParseSpanInfo(ast.sourceSpan); + const expression = new TcbExpr(literal).addParseSpanInfo(ast.sourceSpan); + + // Always parenthesize the literal, because for DOM bindings we may put it + // directly in the function body at which point TS might parse it as a block. + expression.wrapForTypeChecker(); + + return expression; } visitLiteralPrimitive(ast: LiteralPrimitive): TcbExpr { diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/span_comments_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/span_comments_spec.ts index 30cc82f0ddc..e211640e554 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/span_comments_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/span_comments_spec.ts @@ -40,7 +40,7 @@ describe('type check blocks diagnostics', () => { // statement, which would wrap it into parenthesis that clutter the expected output. const TEMPLATE = '{{ m({foo: a, bar: b}) }}'; expect(tcbWithSpans(TEMPLATE)).toContain( - '(this).m /*3,4*/({ "foo" /*6,9*/: ((this).a /*11,12*/) /*11,12*/, "bar" /*14,17*/: ((this).b /*19,20*/) /*19,20*/ } /*5,21*/) /*3,22*/', + '((this).m /*3,4*/(({ "foo" /*6,9*/: ((this).a /*11,12*/) /*11,12*/, "bar" /*14,17*/: ((this).b /*19,20*/) /*19,20*/ } /*5,21*/)) /*3,22*/)', ); }); @@ -49,7 +49,7 @@ describe('type check blocks diagnostics', () => { // statement, which would wrap it into parenthesis that clutter the expected output. const TEMPLATE = '{{ m({a, b}) }}'; expect(tcbWithSpans(TEMPLATE)).toContain( - '((this).m /*3,4*/({ "a" /*6,7*/: ((this).a /*6,7*/) /*6,7*/, "b" /*9,10*/: ((this).b /*9,10*/) /*9,10*/ } /*5,11*/) /*3,12*/)', + '((this).m /*3,4*/(({ "a" /*6,7*/: ((this).a /*6,7*/) /*6,7*/, "b" /*9,10*/: ((this).b /*9,10*/) /*9,10*/ } /*5,11*/)) /*3,12*/)', ); }); diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts index a51d93b7be1..5f72d78490b 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts @@ -31,7 +31,7 @@ describe('type check blocks', () => { it('should generate literal map expressions', () => { const TEMPLATE = '{{ method({foo: a, bar: b}) }}'; - expect(tcb(TEMPLATE)).toContain('(this).method({ "foo": ((this).a), "bar": ((this).b) })'); + expect(tcb(TEMPLATE)).toContain('(this).method(({ "foo": ((this).a), "bar": ((this).b) }))'); }); it('should generate literal array expressions', () => { @@ -39,6 +39,11 @@ describe('type check blocks', () => { expect(tcb(TEMPLATE)).toContain('(this).method([((this).a), ((this).b)])'); }); + it('should parenthesize literal maps bound to DOM properties', () => { + const TEMPLATE = '
'; + expect(tcb(TEMPLATE)).toContain('if (true) { ({ "foo": true, "bar": false }); }'); + }); + it('should handle non-null assertions', () => { const TEMPLATE = `{{a!}}`; expect(tcb(TEMPLATE)).toContain('((((this).a))!)'); @@ -112,8 +117,8 @@ describe('type check blocks', () => { }); it('should handle "in" expressions', () => { - expect(tcb(`{{'bar' in {bar: 'bar'} }}`)).toContain(`(("bar") in ({ "bar": "bar" }))`); - expect(tcb(`{{!('bar' in {bar: 'bar'}) }}`)).toContain(`!((("bar") in ({ "bar": "bar" })))`); + expect(tcb(`{{'bar' in {bar: 'bar'} }}`)).toContain(`(("bar") in (({ "bar": "bar" })))`); + expect(tcb(`{{!('bar' in {bar: 'bar'}) }}`)).toContain(`!((("bar") in (({ "bar": "bar" }))))`); }); it('should handle "instanceof" expressions', () => { @@ -1878,7 +1883,7 @@ describe('type check blocks', () => { `; expect(tcb(TEMPLATE)).toContain( - 'new IntersectionObserver(null!, { "rootMargin": "123px" }); "" + ((this).main()); "" + ((this).placeholder());', + 'new IntersectionObserver(null!, ({ "rootMargin": "123px" })); "" + ((this).main()); "" + ((this).placeholder());', ); }); });