diff --git a/packages/compiler-cli/src/ngtsc/annotations/directive/src/shared.ts b/packages/compiler-cli/src/ngtsc/annotations/directive/src/shared.ts index 0457cb91715..8e32da25204 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/directive/src/shared.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/directive/src/shared.ts @@ -2097,29 +2097,7 @@ function extractHostDirectives( `In ${compilationModeName} mode, host directive cannot be an expression. Use an identifier instead`, ); } - - if (emitDeclarationOnly) { - if (ts.isIdentifier(hostReference.node)) { - const importInfo = reflector.getImportOfIdentifier(hostReference.node); - if (importInfo) { - directive = new ExternalReference(importInfo.from, importInfo.name); - } else { - throw new FatalDiagnosticError( - ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION, - hostReference.node, - `In experimental declaration-only emission mode, host directive cannot use indirect external indentifiers. Use a direct external identifier instead`, - ); - } - } else { - throw new FatalDiagnosticError( - ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION, - hostReference.node, - `In experimental declaration-only emission mode, host directive cannot be an expression. Use an identifier instead`, - ); - } - } else { - directive = new WrappedNodeExpr(hostReference.node); - } + directive = new WrappedNodeExpr(hostReference.node); } else if (hostReference instanceof Reference) { directive = hostReference as Reference; nameForErrors = (fieldName: string) => diff --git a/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts b/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts index 0c601a93695..f82961fee95 100644 --- a/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts +++ b/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts @@ -252,6 +252,16 @@ class TypeTranslatorVisitor implements o.ExpressionVisitor, o.TypeVisitor { const node: ts.Node = ast.node; if (ts.isEntityName(node)) { return ts.factory.createTypeReferenceNode(this.routeEntityNameThroughImportManager(node)); + } else if (ts.isPropertyAccessExpression(node)) { + const entityName = expressionToEntityName(node); + if (entityName !== null) { + return ts.factory.createTypeReferenceNode( + this.routeEntityNameThroughImportManager(entityName), + ); + } + throw new Error( + `Unsupported PropertyAccessExpression in TypeTranslatorVisitor: ${node.getText()} in ${node.getSourceFile()?.fileName}`, + ); } else if (ts.isTypeNode(node)) { // The wrapped type node may reference identifiers from another source file (e.g. when the // NgModule isolated-declarations transform synthesizes `ReturnType`). @@ -423,3 +433,14 @@ function replaceLeftmostEntityName(name: ts.EntityName, newLeftmost: ts.EntityNa name.right, ); } + +function expressionToEntityName(expr: ts.Expression): ts.EntityName | null { + if (ts.isIdentifier(expr)) { + return ts.factory.createIdentifier(expr.text); + } + if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.name)) { + const left = expressionToEntityName(expr.expression); + return left === null ? null : ts.factory.createQualifiedName(left, expr.name); + } + return null; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_directives/host_directives/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_directives/host_directives/TEST_CASES.json index 12e7fbcf1e4..2523850d94b 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_directives/host_directives/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_directives/host_directives/TEST_CASES.json @@ -3,9 +3,7 @@ "cases": [ { "description": "should create a basic hostDirectives definition", - "inputFiles": [ - "basic_host_directives.ts" - ], + "inputFiles": ["basic_host_directives.ts"], "expectations": [ { "failureMessage": "Incorrect definition", @@ -16,13 +14,17 @@ } ] } + ], + "compilationModeFilter": [ + "full compile", + "linked compile", + "local compile", + "declaration-only emit" ] }, { "description": "should create hostDirective definitions for a chain of host directives", - "inputFiles": [ - "chained_host_directives.ts" - ], + "inputFiles": ["chained_host_directives.ts"], "expectations": [ { "failureMessage": "Incorrect definition", @@ -33,13 +35,17 @@ } ] } + ], + "compilationModeFilter": [ + "full compile", + "linked compile", + "local compile", + "declaration-only emit" ] }, { "description": "should handle a forwardRef used in hostDirectives", - "inputFiles": [ - "forward_ref_host_directives.ts" - ], + "inputFiles": ["forward_ref_host_directives.ts"], "expectations": [ { "failureMessage": "Incorrect definition", @@ -50,13 +56,17 @@ } ] } + ], + "compilationModeFilter": [ + "full compile", + "linked compile", + "local compile", + "declaration-only emit" ] }, { "description": "should handle the `inputs` and `outputs` options in host directives", - "inputFiles": [ - "host_directives_with_inputs_outputs.ts" - ], + "inputFiles": ["host_directives_with_inputs_outputs.ts"], "expectations": [ { "failureMessage": "Incorrect definition", @@ -67,13 +77,17 @@ } ] } + ], + "compilationModeFilter": [ + "full compile", + "linked compile", + "local compile", + "declaration-only emit" ] }, { "description": "should handle aliases to aliased `inputs` and `outputs` of a host directive", - "inputFiles": [ - "host_directives_with_host_aliases.ts" - ], + "inputFiles": ["host_directives_with_host_aliases.ts"], "expectations": [ { "failureMessage": "Incorrect definition", @@ -84,6 +98,12 @@ } ] } + ], + "compilationModeFilter": [ + "full compile", + "linked compile", + "local compile", + "declaration-only emit" ] } ] diff --git a/packages/compiler-cli/test/ngtsc/declaration_only_emission_spec.ts b/packages/compiler-cli/test/ngtsc/declaration_only_emission_spec.ts index c658172b328..36048f4a8b1 100644 --- a/packages/compiler-cli/test/ngtsc/declaration_only_emission_spec.ts +++ b/packages/compiler-cli/test/ngtsc/declaration_only_emission_spec.ts @@ -445,7 +445,59 @@ runInEachFileSystem(() => { ); }); - it('should show correct error message when using an indirect external reference in a simple host directive on a component', () => { + it('should emit type declarations containing external reference via namespace import in host directive on a component', () => { + env.write( + 'test.ts', + ` + import {Component} from '@angular/core'; + import * as n from './dir'; + + @Component({ + template: '', + selector: 'host-comp', + hostDirectives: [n.Dir], + }) + export class HostComp {} + `, + ); + + env.driveMain(); + const dtsContent = env.getContents('test.d.ts'); + + expect(dtsContent).toContain( + 'static ɵcmp: i0.ɵɵComponentDeclaration;', + ); + }); + + it('should emit type declarations containing external reference with inputs and outputs in host directive on a component', () => { + env.write( + 'test.ts', + ` + import {Component} from '@angular/core'; + import {Dir} from './dir'; + + @Component({ + template: '', + selector: 'host-comp', + hostDirectives: [{ + directive: Dir, + inputs: ['a: b'], + outputs: ['c: d'], + }], + }) + export class HostComp {} + `, + ); + + env.driveMain(); + const dtsContent = env.getContents('test.d.ts'); + + expect(dtsContent).toContain( + 'static ɵcmp: i0.ɵɵComponentDeclaration;', + ); + }); + + it('should emit type declarations when using an indirect external reference in a simple host directive on a component', () => { env.write( 'test.ts', ` @@ -463,16 +515,15 @@ runInEachFileSystem(() => { `, ); - const errors = env.driveDiagnostics(); + env.driveMain(); + const dtsContent = env.getContents('test.d.ts'); - expect(errors.length).toBe(1); - expect(errors[0].code).toBe(ngErrorCode(ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION)); - expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toBe( - 'In experimental declaration-only emission mode, host directive cannot use indirect external indentifiers. Use a direct external identifier instead', + expect(dtsContent).toContain( + 'static ɵcmp: i0.ɵɵComponentDeclaration;', ); }); - it('should show correct error message when using an indirect external reference in host directive object on a component', () => { + it('should emit type declarations when using an indirect external reference in host directive object on a component', () => { env.write( 'test.ts', ` @@ -492,16 +543,15 @@ runInEachFileSystem(() => { `, ); - const errors = env.driveDiagnostics(); + env.driveMain(); + const dtsContent = env.getContents('test.d.ts'); - expect(errors.length).toBe(1); - expect(errors[0].code).toBe(ngErrorCode(ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION)); - expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toBe( - 'In experimental declaration-only emission mode, host directive cannot use indirect external indentifiers. Use a direct external identifier instead', + expect(dtsContent).toContain( + 'static ɵcmp: i0.ɵɵComponentDeclaration;', ); }); - it('should show correct error message when using an indirect external reference in a simple host directive on a directive', () => { + it('should emit type declarations when using an indirect external reference in a simple host directive on a directive', () => { env.write( 'test.ts', ` @@ -518,16 +568,15 @@ runInEachFileSystem(() => { `, ); - const errors = env.driveDiagnostics(); + env.driveMain(); + const dtsContent = env.getContents('test.d.ts'); - expect(errors.length).toBe(1); - expect(errors[0].code).toBe(ngErrorCode(ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION)); - expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toBe( - 'In experimental declaration-only emission mode, host directive cannot use indirect external indentifiers. Use a direct external identifier instead', + expect(dtsContent).toContain( + 'static ɵdir: i0.ɵɵDirectiveDeclaration;', ); }); - it('should show correct error message when using an indirect external reference in host directive object on a directive', () => { + it('should emit type declarations when using an indirect external reference in host directive object on a directive', () => { env.write( 'test.ts', ` @@ -546,16 +595,15 @@ runInEachFileSystem(() => { `, ); - const errors = env.driveDiagnostics(); + env.driveMain(); + const dtsContent = env.getContents('test.d.ts'); - expect(errors.length).toBe(1); - expect(errors[0].code).toBe(ngErrorCode(ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION)); - expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toBe( - 'In experimental declaration-only emission mode, host directive cannot use indirect external indentifiers. Use a direct external identifier instead', + expect(dtsContent).toContain( + 'static ɵdir: i0.ɵɵDirectiveDeclaration;', ); }); - it('should show correct error message when using a property access expression resolving to an indirect external reference in a simple host directive on a component', () => { + it('should emit type declarations when using a property access expression resolving to an indirect external reference in a simple host directive on a component', () => { env.write( 'test.ts', ` @@ -575,16 +623,15 @@ runInEachFileSystem(() => { `, ); - const errors = env.driveDiagnostics(); + env.driveMain(); + const dtsContent = env.getContents('test.d.ts'); - expect(errors.length).toBe(1); - expect(errors[0].code).toBe(ngErrorCode(ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION)); - expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toBe( - 'In experimental declaration-only emission mode, host directive cannot be an expression. Use an identifier instead', + expect(dtsContent).toContain( + 'static ɵcmp: i0.ɵɵComponentDeclaration;', ); }); - it('should show correct error message when using a property access expression resolving to an indirect external reference in host directive object on a component', () => { + it('should emit type declarations when using a property access expression resolving to an indirect external reference in host directive object on a component', () => { env.write( 'test.ts', ` @@ -606,16 +653,15 @@ runInEachFileSystem(() => { `, ); - const errors = env.driveDiagnostics(); + env.driveMain(); + const dtsContent = env.getContents('test.d.ts'); - expect(errors.length).toBe(1); - expect(errors[0].code).toBe(ngErrorCode(ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION)); - expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toBe( - 'In experimental declaration-only emission mode, host directive cannot be an expression. Use an identifier instead', + expect(dtsContent).toContain( + 'static ɵcmp: i0.ɵɵComponentDeclaration;', ); }); - it('should show correct error message when using a property access expression resolving to an indirect external reference in a simple host directive on a directive', () => { + it('should emit type declarations when using a property access expression resolving to an indirect external reference in a simple host directive on a directive', () => { env.write( 'test.ts', ` @@ -634,16 +680,15 @@ runInEachFileSystem(() => { `, ); - const errors = env.driveDiagnostics(); + env.driveMain(); + const dtsContent = env.getContents('test.d.ts'); - expect(errors.length).toBe(1); - expect(errors[0].code).toBe(ngErrorCode(ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION)); - expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toBe( - 'In experimental declaration-only emission mode, host directive cannot be an expression. Use an identifier instead', + expect(dtsContent).toContain( + 'static ɵdir: i0.ɵɵDirectiveDeclaration;', ); }); - it('should show correct error message when using a property access expression resolving to an indirect external reference in host directive object on a directive', () => { + it('should emit type declarations when using a property access expression resolving to an indirect external reference in host directive object on a directive', () => { env.write( 'test.ts', ` @@ -664,12 +709,11 @@ runInEachFileSystem(() => { `, ); - const errors = env.driveDiagnostics(); + env.driveMain(); + const dtsContent = env.getContents('test.d.ts'); - expect(errors.length).toBe(1); - expect(errors[0].code).toBe(ngErrorCode(ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION)); - expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toBe( - 'In experimental declaration-only emission mode, host directive cannot be an expression. Use an identifier instead', + expect(dtsContent).toContain( + 'static ɵdir: i0.ɵɵDirectiveDeclaration;', ); });