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
@ -10,7 +10,7 @@ import {ConstantPool, Expression, Statement, Type} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Reexport} from '../../imports';
|
||||
import {Decorator} from '../../reflection';
|
||||
import {ClassDeclaration, Decorator} from '../../reflection';
|
||||
import {TypeCheckContext} from '../../typecheck';
|
||||
|
||||
export enum HandlerPrecedence {
|
||||
@ -58,7 +58,7 @@ export interface DecoratorHandler<A, M> {
|
||||
* Scan a set of reflected decorators and determine if this handler is responsible for compilation
|
||||
* of one of them.
|
||||
*/
|
||||
detect(node: ts.Declaration, decorators: Decorator[]|null): DetectResult<M>|undefined;
|
||||
detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<M>|undefined;
|
||||
|
||||
|
||||
/**
|
||||
@ -67,14 +67,14 @@ export interface DecoratorHandler<A, M> {
|
||||
* `preAnalyze` is optional and is not guaranteed to be called through all compilation flows. It
|
||||
* will only be called if asynchronicity is supported in the CompilerHost.
|
||||
*/
|
||||
preanalyze?(node: ts.Declaration, metadata: M): Promise<void>|undefined;
|
||||
preanalyze?(node: ClassDeclaration, metadata: M): Promise<void>|undefined;
|
||||
|
||||
/**
|
||||
* Perform analysis on the decorator/class combination, producing instructions for compilation
|
||||
* if successful, or an array of diagnostic messages if the analysis fails or the decorator
|
||||
* isn't valid.
|
||||
*/
|
||||
analyze(node: ts.Declaration, metadata: M): AnalysisOutput<A>;
|
||||
analyze(node: ClassDeclaration, metadata: M): AnalysisOutput<A>;
|
||||
|
||||
/**
|
||||
* Perform resolution on the given decorator along with the result of analysis.
|
||||
@ -83,15 +83,15 @@ export interface DecoratorHandler<A, M> {
|
||||
* `DecoratorHandler` a chance to leverage information from the whole compilation unit to enhance
|
||||
* the `analysis` before the emit phase.
|
||||
*/
|
||||
resolve?(node: ts.Declaration, analysis: A): ResolveResult;
|
||||
resolve?(node: ClassDeclaration, analysis: A): ResolveResult;
|
||||
|
||||
typeCheck?(ctx: TypeCheckContext, node: ts.Declaration, metadata: A): void;
|
||||
typeCheck?(ctx: TypeCheckContext, node: ClassDeclaration, metadata: A): void;
|
||||
|
||||
/**
|
||||
* Generate a description of the field which should be added to the class, including any
|
||||
* initialization code to be generated.
|
||||
*/
|
||||
compile(node: ts.Declaration, analysis: A, constantPool: ConstantPool): CompileResult
|
||||
compile(node: ClassDeclaration, analysis: A, constantPool: ConstantPool): CompileResult
|
||||
|CompileResult[];
|
||||
}
|
||||
|
||||
|
@ -6,14 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ConstantPool, ExternalExpr} from '@angular/compiler';
|
||||
import {ConstantPool} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {ImportRewriter} from '../../imports';
|
||||
import {ReflectionHost, reflectNameOfDeclaration} from '../../reflection';
|
||||
import {ClassDeclaration, ReflectionHost, reflectNameOfDeclaration} from '../../reflection';
|
||||
import {TypeCheckContext} from '../../typecheck';
|
||||
import {getSourceFile} from '../../util/src/typescript';
|
||||
import {getSourceFile, isNamedClassDeclaration} from '../../util/src/typescript';
|
||||
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from './api';
|
||||
import {DtsFileTransformer} from './declaration';
|
||||
@ -48,7 +48,7 @@ export class IvyCompilation {
|
||||
* Tracks classes which have been analyzed and found to have an Ivy decorator, and the
|
||||
* information recorded about them for later compilation.
|
||||
*/
|
||||
private ivyClasses = new Map<ts.Declaration, IvyClass>();
|
||||
private ivyClasses = new Map<ClassDeclaration, IvyClass>();
|
||||
|
||||
/**
|
||||
* Tracks factory information which needs to be generated.
|
||||
@ -84,7 +84,7 @@ export class IvyCompilation {
|
||||
|
||||
analyzeAsync(sf: ts.SourceFile): Promise<void>|undefined { return this.analyze(sf, true); }
|
||||
|
||||
private detectHandlersForClass(node: ts.Declaration): IvyClass|null {
|
||||
private detectHandlersForClass(node: ClassDeclaration): IvyClass|null {
|
||||
// The first step is to reflect the decorators.
|
||||
const classDecorators = this.reflector.getDecoratorsOfDeclaration(node);
|
||||
let ivyClass: IvyClass|null = null;
|
||||
@ -169,7 +169,7 @@ export class IvyCompilation {
|
||||
private analyze(sf: ts.SourceFile, preanalyze: boolean): Promise<void>|undefined {
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
const analyzeClass = (node: ts.Declaration): void => {
|
||||
const analyzeClass = (node: ClassDeclaration): void => {
|
||||
const ivyClass = this.detectHandlersForClass(node);
|
||||
|
||||
// If the class has no Ivy behavior (or had errors), skip it.
|
||||
@ -227,7 +227,7 @@ export class IvyCompilation {
|
||||
|
||||
const visit = (node: ts.Node): void => {
|
||||
// Process nodes recursively, and look for class declarations with decorators.
|
||||
if (ts.isClassDeclaration(node)) {
|
||||
if (isNamedClassDeclaration(node)) {
|
||||
analyzeClass(node);
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
@ -291,8 +291,8 @@ export class IvyCompilation {
|
||||
*/
|
||||
compileIvyFieldFor(node: ts.Declaration, constantPool: ConstantPool): CompileResult[]|undefined {
|
||||
// Look to see whether the original node was analyzed. If not, there's nothing to do.
|
||||
const original = ts.getOriginalNode(node) as ts.Declaration;
|
||||
if (!this.ivyClasses.has(original)) {
|
||||
const original = ts.getOriginalNode(node) as typeof node;
|
||||
if (!isNamedClassDeclaration(original) || !this.ivyClasses.has(original)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -305,7 +305,8 @@ export class IvyCompilation {
|
||||
continue;
|
||||
}
|
||||
|
||||
const compileMatchRes = match.handler.compile(node, match.analyzed.analysis, constantPool);
|
||||
const compileMatchRes =
|
||||
match.handler.compile(node as ClassDeclaration, match.analyzed.analysis, constantPool);
|
||||
if (!Array.isArray(compileMatchRes)) {
|
||||
res.push(compileMatchRes);
|
||||
} else {
|
||||
@ -327,9 +328,9 @@ export class IvyCompilation {
|
||||
* Lookup the `ts.Decorator` which triggered transformation of a particular class declaration.
|
||||
*/
|
||||
ivyDecoratorsFor(node: ts.Declaration): ts.Decorator[] {
|
||||
const original = ts.getOriginalNode(node) as ts.Declaration;
|
||||
const original = ts.getOriginalNode(node) as typeof node;
|
||||
|
||||
if (!this.ivyClasses.has(original)) {
|
||||
if (!isNamedClassDeclaration(original) || !this.ivyClasses.has(original)) {
|
||||
return EMPTY_ARRAY;
|
||||
}
|
||||
const ivyClass = this.ivyClasses.get(original) !;
|
||||
|
Reference in New Issue
Block a user