Skip to content

Commit

Permalink
fix(compiler-cli): show the correct message for the error LOCAL_COMPI…
Browse files Browse the repository at this point in the history
…LATION_UNRESOLVED_CONST when an unresolved symbol used for @Component.styles (#54230)

Currently the correct error message is shown only if @Component.styles is an array with some unresolved element. This change supports the new case of string type for the @Component.styles field.

PR Close #54230
  • Loading branch information
pmvald authored and thePunderWoman committed Feb 6, 2024
1 parent 6c8b094 commit 5d63324
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 14 deletions.
Expand Up @@ -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);
}
}
38 changes: 27 additions & 11 deletions packages/compiler-cli/src/ngtsc/annotations/directive/src/shared.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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`);
}
Expand Down Expand Up @@ -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');
}
}

Expand Down
66 changes: 65 additions & 1 deletion packages/compiler-cli/test/ngtsc/local_compilation_spec.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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', () => {
Expand Down

0 comments on commit 5d63324

Please sign in to comment.