Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(compiler-cli): ensure that a declaration is available in type-to-value conversion #38684

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/compiler-cli/src/ngtsc/annotations/src/util.ts
Expand Up @@ -215,8 +215,10 @@ function createUnsuitableInjectionTokenError(
makeRelatedInformation(
reason.typeNode,
'This type does not have a value, so it cannot be used as injection token.'),
makeRelatedInformation(reason.decl, 'The type is declared here.'),
];
if (reason.decl !== null) {
hints.push(makeRelatedInformation(reason.decl, 'The type is declared here.'));
}
break;
case ValueUnavailableKind.TYPE_ONLY_IMPORT:
chainMessage =
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-cli/src/ngtsc/reflection/src/host.ts
Expand Up @@ -336,7 +336,7 @@ export interface UnsupportedType {
export interface NoValueDeclaration {
kind: ValueUnavailableKind.NO_VALUE_DECLARATION;
typeNode: ts.TypeNode;
decl: ts.Declaration;
decl: ts.Declaration|null;
}

export interface TypeOnlyImport {
Expand Down
Expand Up @@ -38,7 +38,11 @@ export function typeToValue(
// has a value declaration associated with it. Note that const enums are an exception,
// because while they do have a value declaration, they don't exist at runtime.
if (decl.valueDeclaration === undefined || decl.flags & ts.SymbolFlags.ConstEnum) {
return noValueDeclaration(typeNode, decl.declarations[0]);
let typeOnlyDecl: ts.Declaration|null = null;
if (decl.declarations !== undefined && decl.declarations.length > 0) {
typeOnlyDecl = decl.declarations[0];
}
petebacondarwin marked this conversation as resolved.
Show resolved Hide resolved
return noValueDeclaration(typeNode, typeOnlyDecl);
}

// The type points to a valid value declaration. Rewrite the TypeReference into an
Expand Down Expand Up @@ -140,7 +144,7 @@ function unsupportedType(typeNode: ts.TypeNode): UnavailableTypeValueReference {
}

function noValueDeclaration(
typeNode: ts.TypeNode, decl: ts.Declaration): UnavailableTypeValueReference {
typeNode: ts.TypeNode, decl: ts.Declaration|null): UnavailableTypeValueReference {
return {
kind: TypeValueReferenceKind.UNAVAILABLE,
reason: {kind: ValueUnavailableKind.NO_VALUE_DECLARATION, typeNode, decl},
Expand Down
29 changes: 29 additions & 0 deletions packages/compiler-cli/test/ngtsc/ngtsc_spec.ts
Expand Up @@ -2214,6 +2214,35 @@ runInEachFileSystem(os => {
expect(diags[0].relatedInformation![1].messageText).toBe('The type is declared here.');
});

it('should report an error when using a missing type as injection token', () => {
// This test replicates the situation where a symbol does not have any declarations at
// all, e.g. because it's imported from a missing module. This would result in a
// semantic TypeScript diagnostic which we ignore in this test to verify that ngtsc's
// analysis is able to operate in this situation.
env.tsconfig({strictInjectionParameters: true});
env.write(`test.ts`, `
import {Injectable} from '@angular/core';
// @ts-expect-error
import {Interface} from 'missing';

@Injectable()
export class MyService {
constructor(param: Interface) {}
}
`);

const diags = env.driveDiagnostics();
expect(diags.length).toBe(1);
expect(ts.flattenDiagnosticMessageText(diags[0].messageText, '\n'))
.toBe(
`No suitable injection token for parameter 'param' of ` +
`class 'MyService'.\n` +
` Consider using the @Inject decorator to specify an injection token.`);
expect(diags[0].relatedInformation!.length).toBe(1);
expect(diags[0].relatedInformation![0].messageText)
.toBe('This type does not have a value, so it cannot be used as injection token.');
});

it('should report an error when no type is present', () => {
env.tsconfig({strictInjectionParameters: true, noImplicitAny: false});
env.write(`test.ts`, `
Expand Down