From 4ffa73651c9fbfce1cc147e9f255106c12748576 Mon Sep 17 00:00:00 2001 From: cexbrayat Date: Tue, 30 Apr 2024 16:02:41 +0200 Subject: [PATCH] fix(compiler-cli): interpolatedSignalNotInvoked diagnostic for class, style, attribute and animation bindings (#55969) This extends the diagnostic to catch issues like: ```html
``` PR Close #55969 --- .../interpolated_signal_not_invoked/index.ts | 13 ++- .../interpolated_signal_not_invoked_spec.ts | 100 ++++++++++-------- 2 files changed, 65 insertions(+), 48 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/typecheck/extended/checks/interpolated_signal_not_invoked/index.ts b/packages/compiler-cli/src/ngtsc/typecheck/extended/checks/interpolated_signal_not_invoked/index.ts index c1323d5be40..395f94a1e6e 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/extended/checks/interpolated_signal_not_invoked/index.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/extended/checks/interpolated_signal_not_invoked/index.ts @@ -55,9 +55,18 @@ class InterpolatedSignalCheck extends TemplateCheckWithVisitor { expect(getSourceCodeForDiagnostic(diags[0])).toBe(`myNestedSignal`); }); - it("should produce a warning when signal isn't invoked on dom property binding", () => { - const fileName = absoluteFrom('/main.ts'); - const {program, templateTypeChecker} = setup([ - { - fileName, - templates: { - 'TestCmp': `
`, - }, - source: ` + [ + ['dom property', 'id'], + ['class', 'class.green'], + ['style', 'style.width'], + ['attribute', 'attr.role'], + ['animation', '@triggerName'], + ].forEach(([name, binding]) => { + it(`should produce a warning when signal isn't invoked on ${name} binding`, () => { + const fileName = absoluteFrom('/main.ts'); + const {program, templateTypeChecker} = setup([ + { + fileName, + templates: { + 'TestCmp': `
`, + }, + source: ` import {signal} from '@angular/core'; export class TestCmp { mySignal = signal(0); }`, - }, - ]); - const sf = getSourceFileOrError(program, fileName); - const component = getClass(sf, 'TestCmp'); - const extendedTemplateChecker = new ExtendedTemplateCheckerImpl( - templateTypeChecker, - program.getTypeChecker(), - [interpolatedSignalFactory], - {} /* options */, - ); - const diags = extendedTemplateChecker.getDiagnosticsForComponent(component); - expect(diags.length).toBe(1); - expect(diags[0].category).toBe(ts.DiagnosticCategory.Warning); - expect(diags[0].code).toBe(ngErrorCode(ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED)); - expect(getSourceCodeForDiagnostic(diags[0])).toBe(`mySignal`); - }); - - it('should not produce a warning when signal is invoked on dom property binding', () => { - const fileName = absoluteFrom('/main.ts'); - const {program, templateTypeChecker} = setup([ - { - fileName, - templates: { - 'TestCmp': `
`, }, - source: ` + ]); + const sf = getSourceFileOrError(program, fileName); + const component = getClass(sf, 'TestCmp'); + const extendedTemplateChecker = new ExtendedTemplateCheckerImpl( + templateTypeChecker, + program.getTypeChecker(), + [interpolatedSignalFactory], + {} /* options */, + ); + const diags = extendedTemplateChecker.getDiagnosticsForComponent(component); + expect(diags.length).toBe(1); + expect(diags[0].category).toBe(ts.DiagnosticCategory.Warning); + expect(diags[0].code).toBe(ngErrorCode(ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED)); + expect(getSourceCodeForDiagnostic(diags[0])).toBe(`mySignal`); + }); + + it(`should not produce a warning when signal is invoked on ${name} binding`, () => { + const fileName = absoluteFrom('/main.ts'); + const {program, templateTypeChecker} = setup([ + { + fileName, + templates: { + 'TestCmp': `
`, + }, + source: ` import {signal} from '@angular/core'; export class TestCmp { mySignal = signal(0); }`, - }, - ]); - const sf = getSourceFileOrError(program, fileName); - const component = getClass(sf, 'TestCmp'); - const extendedTemplateChecker = new ExtendedTemplateCheckerImpl( - templateTypeChecker, - program.getTypeChecker(), - [interpolatedSignalFactory], - {} /* options */, - ); - const diags = extendedTemplateChecker.getDiagnosticsForComponent(component); - expect(diags.length).toBe(0); + }, + ]); + const sf = getSourceFileOrError(program, fileName); + const component = getClass(sf, 'TestCmp'); + const extendedTemplateChecker = new ExtendedTemplateCheckerImpl( + templateTypeChecker, + program.getTypeChecker(), + [interpolatedSignalFactory], + {} /* options */, + ); + const diags = extendedTemplateChecker.getDiagnosticsForComponent(component); + expect(diags.length).toBe(0); + }); }); it('should not produce a warning with other Signal type', () => {