diff --git a/packages/compiler-cli/src/ngtsc/annotations/common/src/diagnostics.ts b/packages/compiler-cli/src/ngtsc/annotations/common/src/diagnostics.ts index f023c0ee0378a..0b64d9ddd099b 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/common/src/diagnostics.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/common/src/diagnostics.ts @@ -410,13 +410,16 @@ function getInheritedUndecoratedCtorDiagnostic( * if the compilation mode is local and the value is not resolved due to being imported * from external files. This is a common scenario for errors in local compilation mode, * and so this helper can be used to quickly generate the relevant errors. + * + * @param nodeToHighlight Node to be highlighted in teh error message. + * Will default to value.node if not provided. */ -export function assertLocalCompilationUnresolvedConst(compilationMode: CompilationMode, value: ResolvedValue, nodeToHighlight: ts.Node, errorMessage: string): void { +export function assertLocalCompilationUnresolvedConst(compilationMode: CompilationMode, value: ResolvedValue, nodeToHighlight: ts.Node|null, errorMessage: string): void { if (compilationMode === CompilationMode.LOCAL && value instanceof DynamicValue && value.isFromUnknownIdentifier()) { throw new FatalDiagnosticError( ErrorCode.LOCAL_COMPILATION_UNRESOLVED_CONST, - nodeToHighlight, + nodeToHighlight ?? value.node, errorMessage); } } 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 f98d1be705bfe..7938ede795462 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/directive/src/shared.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/directive/src/shared.ts @@ -15,7 +15,7 @@ import {ClassPropertyMapping, DecoratorInputTransform, HostDirectiveMeta, InputM import {DynamicValue, EnumValue, PartialEvaluator, ResolvedValue, traceDynamicValue} from '../../../partial_evaluator'; import {AmbientImport, ClassDeclaration, ClassMember, ClassMemberKind, Decorator, filterToMembersWithDecorator, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral} from '../../../reflection'; import {CompilationMode} from '../../../transform'; -import {createSourceSpan, createValueHasWrongTypeError, forwardRefResolver, getAngularDecorators, getConstructorDependencies, isAngularDecorator, ReferencesRegistry, toR3Reference, tryUnwrapForwardRef, unwrapConstructorDependencies, unwrapExpression, validateConstructorDependencies, wrapFunctionExpressionsInParens, wrapTypeReference} from '../../common'; +import {createSourceSpan, createValueHasWrongTypeError, forwardRefResolver, getAngularDecorators, getConstructorDependencies, isAngularDecorator, ReferencesRegistry, toR3Reference, tryUnwrapForwardRef, unwrapConstructorDependencies, unwrapExpression, validateConstructorDependencies, assertLocalCompilationUnresolvedConst, wrapFunctionExpressionsInParens, wrapTypeReference} from '../../common'; import {tryParseSignalInputMapping} from './input_function'; import {tryParseInitializerBasedOutput} from './output_function'; @@ -124,6 +124,12 @@ export function extractDirectiveMetadata( if (directive.has('selector')) { const expr = directive.get('selector')!; const resolved = evaluator.evaluate(expr); + assertLocalCompilationUnresolvedConst(compilationMode, resolved, null, + 'Unresolved identifier found for @Component.selector field! Did you ' + + 'import this identifier from a file outside of the compilation unit? ' + + 'This is not allowed when Angular compiler runs in local mode. Possible ' + + 'solutions: 1) Move the declarations into a file within the compilation ' + + 'unit, 2) Inline the selector'); if (typeof resolved !== 'string') { throw createValueHasWrongTypeError(expr, resolved, `selector must be a string`); } @@ -493,16 +499,26 @@ export function parseDirectiveStyles( const evaluated = evaluator.evaluate(expression); const value = typeof evaluated === 'string' ? [evaluated] : evaluated; - // The identifier used for @Component.styles cannot be resolved in local compilation mode. An error specific to this situation is generated. - if (compilationMode === CompilationMode.LOCAL && Array.isArray(value)) { - for (const entry of value) { - if (entry instanceof DynamicValue && entry.isFromUnknownIdentifier()) { - - throw new FatalDiagnosticError( - ErrorCode.LOCAL_COMPILATION_UNRESOLVED_CONST, - entry.node, - 'Unresolved identifier found for @Component.styles field! Did you import this identifier from a file outside of the compilation unit? This is not allowed when Angular compiler runs in local mode. Possible solutions: 1) Move the declarations into a file within the compilation unit, 2) Inline the styles, 3) Move the styles into separate files and include it using @Component.styleUrls'); - } + // Check if the identifier used for @Component.styles cannot be resolved in local compilation mode. if the case, an error specific to this situation is generated. + if (compilationMode === CompilationMode.LOCAL) { + let unresolvedNode: ts.Node|null = null; + if (Array.isArray(value)) { + const entry = value.find(e => e instanceof DynamicValue && e.isFromUnknownIdentifier()) as DynamicValue|undefined; + unresolvedNode = entry?.node ?? null; + } else if (value instanceof DynamicValue && value.isFromUnknownIdentifier()) { + unresolvedNode = value.node; + } + + if (unresolvedNode !== null) { + throw new FatalDiagnosticError( + ErrorCode.LOCAL_COMPILATION_UNRESOLVED_CONST, + unresolvedNode, + 'Unresolved identifier found for @Component.styles field! Did you import ' + + 'this identifier from a file outside of the compilation unit? This is ' + + 'not allowed when Angular compiler runs in local mode. Possible ' + + 'solutions: 1) Move the declarations into a file within the compilation ' + + 'unit, 2) Inline the styles, 3) Move the styles into separate files and ' + + 'include it using @Component.styleUrls'); } } diff --git a/packages/compiler-cli/test/ngtsc/local_compilation_spec.ts b/packages/compiler-cli/test/ngtsc/local_compilation_spec.ts index 82dbf6ad14666..c229b92e4e6f5 100644 --- a/packages/compiler-cli/test/ngtsc/local_compilation_spec.ts +++ b/packages/compiler-cli/test/ngtsc/local_compilation_spec.ts @@ -1222,7 +1222,7 @@ runInEachFileSystem( 'Unresolved identifier found for @Component.template field! Did you import this identifier from a file outside of the compilation unit? This is not allowed when Angular compiler runs in local mode. Possible solutions: 1) Move the declaration into a file within the compilation unit, 2) Inline the template, 3) Move the template into a separate .html file and include it using @Component.templateUrl'); }); - it('should show correct error message when using an external symbol for component styles', + it('should show correct error message when using an external symbol for component styles array', () => { env.write('test.ts', ` import {Component} from '@angular/core'; @@ -1255,6 +1255,70 @@ runInEachFileSystem( expect(text).toEqual( 'Unresolved identifier found for @Component.styles field! Did you import this identifier from a file outside of the compilation unit? This is not allowed when Angular compiler runs in local mode. Possible solutions: 1) Move the declarations into a file within the compilation unit, 2) Inline the styles, 3) Move the styles into separate files and include it using @Component.styleUrls'); }); + + it('should show correct error message when using an external symbol for component styles', + () => { + env.write('test.ts', ` + import {Component} from '@angular/core'; + import {ExternalString} from './some-where'; + + @Component({ + styles: ExternalString, + template: '', + + }) + export class Main { + } + `); + + const errors = env.driveDiagnostics(); + + expect(errors.length).toBe(1); + + const {code, messageText, relatedInformation, length} = errors[0]; + + expect(code).toBe( + ngErrorCode(ErrorCode.LOCAL_COMPILATION_UNRESOLVED_CONST)); + expect(length).toBe(14), + expect(relatedInformation).toBeUndefined(); + + const text = ts.flattenDiagnosticMessageText(messageText, '\n'); + + expect(text).toEqual( + 'Unresolved identifier found for @Component.styles field! Did you import this identifier from a file outside of the compilation unit? This is not allowed when Angular compiler runs in local mode. Possible solutions: 1) Move the declarations into a file within the compilation unit, 2) Inline the styles, 3) Move the styles into separate files and include it using @Component.styleUrls'); + }); + + it('should show correct error message when using an external symbol for component selector', + () => { + env.write('test.ts', ` + import {Component} from '@angular/core'; + import {ExternalString} from './some-where'; + + @Component({ + selector: ExternalString, + template: '', + + }) + export class Main { + } + `); + + const errors = env.driveDiagnostics(); + + expect(errors.length).toBe(1); + + const {code, messageText, relatedInformation, length} = errors[0]; + + expect(code).toBe( + ngErrorCode(ErrorCode.LOCAL_COMPILATION_UNRESOLVED_CONST)); + expect(length).toBe(14), + expect(relatedInformation).toBeUndefined(); + + const text = ts.flattenDiagnosticMessageText(messageText, '\n'); + + expect(text).toEqual( + 'Unresolved identifier found for @Component.selector field! Did you import this identifier from a file outside of the compilation unit? This is not allowed when Angular compiler runs in local mode. Possible solutions: 1) Move the declarations into a file within the compilation unit, 2) Inline the selector'); + }); }); describe('ng module bootstrap def', () => {