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:

committed by
Miško Hevery

parent
2d859a8c3a
commit
bb6a3632f6
@ -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.
|
||||
|
@ -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 {
|
||||
|
Reference in New Issue
Block a user