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
@ -6,8 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import {Decorator} from '../../../src/ngtsc/reflection';
|
||||
import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection';
|
||||
|
||||
/**
|
||||
* A simple container that holds the details of a decorated class that has been
|
||||
@ -22,5 +21,5 @@ export class DecoratedClass {
|
||||
* @param decorators The collection of decorators that have been found on this class.
|
||||
*/
|
||||
constructor(
|
||||
public name: string, public declaration: ts.Declaration, public decorators: Decorator[], ) {}
|
||||
public name: string, public declaration: ClassDeclaration, public decorators: Decorator[]) {}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassMember, ClassMemberKind, CtorParameter, Decorator, Import, TypeScriptReflectionHost, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||
import {ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Decorator, Import, TypeScriptReflectionHost, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||
import {BundleProgram} from '../packages/bundle_program';
|
||||
import {findAll, getNameText, isDefined} from '../utils';
|
||||
|
||||
@ -199,15 +199,15 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
* @param node the node whose symbol we are finding.
|
||||
* @returns the symbol for the node or `undefined` if it is not a "class" or has no symbol.
|
||||
*/
|
||||
getClassSymbol(declaration: ts.Node): ts.Symbol|undefined {
|
||||
getClassSymbol(declaration: ts.Node): ClassSymbol|undefined {
|
||||
if (ts.isClassDeclaration(declaration)) {
|
||||
return declaration.name && this.checker.getSymbolAtLocation(declaration.name);
|
||||
return declaration.name && this.checker.getSymbolAtLocation(declaration.name) as ClassSymbol;
|
||||
}
|
||||
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
||||
declaration = declaration.initializer;
|
||||
}
|
||||
if (ts.isClassExpression(declaration)) {
|
||||
return declaration.name && this.checker.getSymbolAtLocation(declaration.name);
|
||||
return declaration.name && this.checker.getSymbolAtLocation(declaration.name) as ClassSymbol;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@ -405,7 +405,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
}
|
||||
}
|
||||
|
||||
protected getDecoratedClassFromSymbol(symbol: ts.Symbol|undefined): DecoratedClass|null {
|
||||
protected getDecoratedClassFromSymbol(symbol: ClassSymbol|undefined): DecoratedClass|null {
|
||||
if (symbol) {
|
||||
const decorators = this.getDecoratorsOfSymbol(symbol);
|
||||
if (decorators && decorators.length) {
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassMember, ClassMemberKind, Declaration, Decorator, FunctionDefinition, Parameter, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, Declaration, Decorator, FunctionDefinition, Parameter, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||
import {getNameText, hasNameIdentifier} from '../utils';
|
||||
|
||||
import {Esm2015ReflectionHost, ParamInfo, getPropertyValueFromSymbol, isAssignmentStatement} from './esm2015_host';
|
||||
@ -36,7 +36,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||
/**
|
||||
* Check whether the given node actually represents a class.
|
||||
*/
|
||||
isClass(node: ts.Node): node is ts.NamedDeclaration {
|
||||
isClass(node: ts.Node): node is ClassDeclaration {
|
||||
return super.isClass(node) || !!this.getClassSymbol(node);
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||
* expression inside the IIFE.
|
||||
* @returns the symbol for the node or `undefined` if it is not a "class" or has no symbol.
|
||||
*/
|
||||
getClassSymbol(node: ts.Node): ts.Symbol|undefined {
|
||||
getClassSymbol(node: ts.Node): ClassSymbol|undefined {
|
||||
const symbol = super.getClassSymbol(node);
|
||||
if (symbol) return symbol;
|
||||
|
||||
@ -85,7 +85,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||
const innerClassIdentifier = getReturnIdentifier(iifeBody);
|
||||
if (!innerClassIdentifier) return undefined;
|
||||
|
||||
return this.checker.getSymbolAtLocation(innerClassIdentifier);
|
||||
return this.checker.getSymbolAtLocation(innerClassIdentifier) as ClassSymbol;
|
||||
}
|
||||
|
||||
const outerClassNode = getClassDeclarationFromInnerFunctionDeclaration(node);
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as ts from 'typescript';
|
||||
import {ReflectionHost} from '../../../src/ngtsc/reflection';
|
||||
import {ClassSymbol, ReflectionHost} from '../../../src/ngtsc/reflection';
|
||||
import {DecoratedClass} from './decorated_class';
|
||||
|
||||
export const PRE_R3_MARKER = '__PRE_R3__';
|
||||
@ -52,7 +52,7 @@ export interface NgccReflectionHost extends ReflectionHost {
|
||||
* @returns the symbol for the declaration or `undefined` if it is not
|
||||
* a "class" or has no symbol.
|
||||
*/
|
||||
getClassSymbol(node: ts.Node): ts.Symbol|undefined;
|
||||
getClassSymbol(node: ts.Node): ClassSymbol|undefined;
|
||||
|
||||
/**
|
||||
* Search the given module for variable declarations in which the initializer
|
||||
|
@ -481,7 +481,7 @@ export function renderConstantPool(
|
||||
export function renderDefinitions(
|
||||
sourceFile: ts.SourceFile, compiledClass: CompiledClass, imports: ImportManager): string {
|
||||
const printer = ts.createPrinter();
|
||||
const name = (compiledClass.declaration as ts.NamedDeclaration).name !;
|
||||
const name = compiledClass.declaration.name;
|
||||
const translate = (stmt: Statement) =>
|
||||
translateStatement(stmt, imports, NOOP_DEFAULT_IMPORT_RECORDER);
|
||||
const definitions =
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassMemberKind, Import} from '../../../src/ngtsc/reflection';
|
||||
import {ClassDeclaration, ClassMemberKind, ClassSymbol, Import} from '../../../src/ngtsc/reflection';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
|
||||
import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils';
|
||||
@ -1551,7 +1551,7 @@ describe('Esm5ReflectionHost', () => {
|
||||
|
||||
it('should return the class symbol returned by the superclass (if any)', () => {
|
||||
const mockNode = {} as ts.Node;
|
||||
const mockSymbol = {} as ts.Symbol;
|
||||
const mockSymbol = {} as ClassSymbol;
|
||||
superGetClassSymbolSpy.and.returnValue(mockSymbol);
|
||||
|
||||
const host = new Esm5ReflectionHost(false, {} as any);
|
||||
@ -1590,7 +1590,7 @@ describe('Esm5ReflectionHost', () => {
|
||||
const innerNode = (((outerNode.initializer as ts.ParenthesizedExpression)
|
||||
.expression as ts.CallExpression)
|
||||
.expression as ts.FunctionExpression)
|
||||
.body.statements.find(ts.isFunctionDeclaration) !;
|
||||
.body.statements.find(ts.isFunctionDeclaration) as ClassDeclaration;
|
||||
|
||||
expect(host.getClassSymbol(innerNode)).toBe(host.getClassSymbol(outerNode));
|
||||
expect(host.getClassSymbol(innerNode) !.valueDeclaration).toBe(innerNode);
|
||||
|
Reference in New Issue
Block a user