diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/diagnostics.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/diagnostics.ts index d2514af66823..139b84791154 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/diagnostics.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/diagnostics.ts @@ -12,6 +12,15 @@ import {makeTemplateDiagnostic} from '../diagnostics'; import {getSourceMapping, TypeCheckSourceResolver} from './tcb_util'; +/** + * This function will the check the source text of the TCB instead of doing a more expensive AST traversal (via getTokenAtPosition) + */ +function isAccessOnThis(text: string, start: number): boolean { + return ( + text.substring(start - 5, start) === 'this.' || text.substring(start - 7, start) === '(this).' + ); +} + /** * Determines if the diagnostic should be reported. Some diagnostics are produced because of the * way TCBs are generated; those diagnostics should not be reported as type check errors of the @@ -27,7 +36,15 @@ export function shouldReportDiagnostic(diagnostic: ts.Diagnostic): boolean { return false; } else if (code === 7006 /* Parameter '$event' implicitly has an 'any' type. */) { return false; + } else if (code === 2341 /* Property 'X' is private and only accessible within class */) { + if (diagnostic.file !== undefined && diagnostic.start !== undefined) { + // Here we're discarding private property reads error to allow them to be used in template expressions + if (isAccessOnThis(diagnostic.file.text, diagnostic.start)) { + return false; + } + } } + return true; } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/diagnostics_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/diagnostics_spec.ts index bc7bf7b4f862..ee578d54d8c7 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/diagnostics_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/diagnostics_spec.ts @@ -748,7 +748,7 @@ class TestComponent { expect(messages).toEqual([]); }); - it('disallows access to private members', () => { + it('allow access to private members', () => { const messages = diagnose( ``, ` @@ -758,9 +758,24 @@ class TestComponent { }`, ); + expect(messages).toEqual([]); + }); + + it('disallows access of a nested private member', () => { + const messages = diagnose( + `
{{ prop.a }}
`, + ` + export class Model { + private a = 1; + } + + export class TestComponent { + private prop = new Model(); + } + `, + ); expect(messages).toEqual([ - `TestComponent.html(1, 18): Property 'doFoo' is private and only accessible within class 'TestComponent'.`, - `TestComponent.html(1, 30): Property 'message' is private and only accessible within class 'TestComponent'.`, + `TestComponent.html(1, 14): Property 'a' is private and only accessible within class 'Model'.`, ]); }); }); diff --git a/packages/compiler-cli/test/ngtsc/host_bindings_type_check_spec.ts b/packages/compiler-cli/test/ngtsc/host_bindings_type_check_spec.ts index 127492bc81ac..83e85b82b564 100644 --- a/packages/compiler-cli/test/ngtsc/host_bindings_type_check_spec.ts +++ b/packages/compiler-cli/test/ngtsc/host_bindings_type_check_spec.ts @@ -6,10 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ +import ts from 'typescript'; import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing'; import {loadStandardTestFiles} from '../../src/ngtsc/testing'; import {NgtscTestEnvironment} from './env'; -import ts from 'typescript'; const testFiles = loadStandardTestFiles(); @@ -603,7 +603,7 @@ runInEachFileSystem(() => { expect(getDiagnosticSourceCode(diags[0])).toBe('two'); }); - it('should report host decorators on private members', () => { + it('should not report host decorators on private members', () => { env.write( 'test.ts', ` @@ -618,15 +618,7 @@ runInEachFileSystem(() => { ); const diags = env.driveDiagnostics(); - expect(diags.length).toBe(2); - expect(diags[0].messageText).toBe( - `Property 'id' is private and only accessible within class 'Comp'.`, - ); - expect(diags[1].messageText).toBe( - `Property 'handleClick' is private and only accessible within class 'Comp'.`, - ); - expect(getDiagnosticSourceCode(diags[0])).toBe('id'); - expect(getDiagnosticSourceCode(diags[1])).toBe('handleClick'); + expect(diags.length).toBe(0); }); it('should report diagnostic on the entire expression of property binding if node contains escaped string', () => {