refactor(ivy): correctly type class declarations in ngtsc/ngcc (#29209)

Previously, several `ngtsc` and `ngcc` APIs dealing with class
declaration nodes used inconsistent types. For example, some methods of
the `DecoratorHandler` interface expected a `ts.Declaration` argument,
but actual `DecoratorHandler` implementations specified a stricter
`ts.ClassDeclaration` type.

As a result, the stricter methods would operate under the incorrect
assumption that their arguments were of type `ts.ClassDeclaration`,
while the actual arguments might be of different types (e.g. `ngcc`
would call them with `ts.FunctionDeclaration` or
`ts.VariableDeclaration` arguments, when compiling ES5 code).

Additionally, since we need those class declarations to be referenced in
other parts of the program, `ngtsc`/`ngcc` had to either repeatedly
check for `ts.isIdentifier(node.name)` or assume there was a `name`
identifier and use `node.name!`. While this assumption happens to be
true in the current implementation, working around type-checking is
error-prone (e.g. the assumption might stop being true in the future).

This commit fixes this by introducing a new type to be used for such
class declarations (`ts.Declaration & {name: ts.Identifier}`) and using
it consistently throughput the code.

PR Close #29209
This commit is contained in:
George Kalpakas
2019-03-20 12:10:57 +02:00
committed by Miško Hevery
parent 2d859a8c3a
commit bb6a3632f6
36 changed files with 229 additions and 194 deletions

View File

@ -42,6 +42,29 @@ export interface Decorator {
args: ts.Expression[]|null;
}
/**
* The `ts.Declaration` of a "class".
*
* Classes are represented differently in different code formats:
* - In TS code, they are typically defined using the `class` keyword.
* - In ES2015 code, they are usually defined using the `class` keyword, but they can also be
* variable declarations, which are initialized to a class expression (e.g.
* `let Foo = Foo1 = class Foo {}`).
* - In ES5 code, they are typically defined as variable declarations being assigned the return
* value of an IIFE. The actual "class" is implemented as a constructor function inside the IIFE,
* but the outer variable declaration represents the "class" to the rest of the program.
*
* For `ReflectionHost` purposes, a class declaration should always have a `name` identifier,
* because we need to be able to reference it in other parts of the program.
*/
export type ClassDeclaration<T extends ts.Declaration = ts.Declaration> = T & {name: ts.Identifier};
/**
* The symbol corresponding to a "class" declaration. I.e. a `ts.Symbol` whose `valueDeclaration` is
* a `ClassDeclaration`.
*/
export type ClassSymbol = ts.Symbol & {valueDeclaration: ClassDeclaration};
/**
* An enumeration of possible kinds of class members.
*/
@ -452,7 +475,7 @@ export interface ReflectionHost {
/**
* Check whether the given node actually represents a class.
*/
isClass(node: ts.Node): node is ts.NamedDeclaration;
isClass(node: ts.Node): node is ClassDeclaration;
/**
* Determines whether the given declaration has a base class.

View File

@ -8,7 +8,7 @@
import * as ts from 'typescript';
import {ClassMember, ClassMemberKind, CtorParameter, Declaration, Decorator, FunctionDefinition, Import, ReflectionHost, TypeValueReference} from './host';
import {ClassDeclaration, ClassMember, ClassMemberKind, CtorParameter, Declaration, Decorator, FunctionDefinition, Import, ReflectionHost} from './host';
import {typeToValue} from './type_to_value';
/**
@ -133,9 +133,10 @@ export class TypeScriptReflectionHost implements ReflectionHost {
return map;
}
isClass(node: ts.Node): node is ts.NamedDeclaration {
isClass(node: ts.Node): node is ClassDeclaration {
// In TypeScript code, classes are ts.ClassDeclarations.
return ts.isClassDeclaration(node);
// (`name` can be undefined in unnamed default exports: `default export class { ... }`)
return ts.isClassDeclaration(node) && (node.name !== undefined) && ts.isIdentifier(node.name);
}
hasBaseClass(node: ts.Declaration): boolean {