Skip to content

Commit

Permalink
fix(compiler-cli): ensure that a declaration is available in type-to-…
Browse files Browse the repository at this point in the history
…value conversion (#38684)

The type-to-value conversion could previously crash if a symbol was
resolved that does not have any declarations, e.g. because it's imported
from a missing module. This would typically result in a semantic
TypeScript diagnostic and halt further compilation, therefore not
reaching the type-to-value conversion logic. In Bazel however, it turns
out that Angular semantic diagnostics are requested even if there are
semantic TypeScript errors in the program, so it would then reach the
type-to-value conversation and crash.

This commit fixes the unsafe access and adds a test that ignores the
TypeScript semantic error, effectively replicating the situation as
experienced under Bazel.

Fixes #38670

PR Close #38684
  • Loading branch information
JoostK authored and atscott committed Sep 8, 2020
1 parent b4eb016 commit 56d5ff2
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 4 deletions.
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];
}
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

0 comments on commit 56d5ff2

Please sign in to comment.