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 {ExternalExpr, ExternalReference} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AliasGenerator, FileToModuleHost, Reference} from '../../imports';
|
||||
import {TypeScriptReflectionHost} from '../../reflection';
|
||||
import {ClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {ExportScope} from '../src/api';
|
||||
import {MetadataDtsModuleScopeResolver} from '../src/dependency';
|
||||
@ -42,7 +42,7 @@ export declare type PipeMeta<A, B> = never;
|
||||
*/
|
||||
function makeTestEnv(
|
||||
modules: {[module: string]: string}, aliasGenerator: AliasGenerator | null = null): {
|
||||
refs: {[name: string]: Reference<ts.ClassDeclaration>},
|
||||
refs: {[name: string]: Reference<ClassDeclaration>},
|
||||
resolver: MetadataDtsModuleScopeResolver,
|
||||
} {
|
||||
// Map the modules object to an array of files for `makeProgram`.
|
||||
@ -123,7 +123,7 @@ describe('MetadataDtsModuleScopeResolver', () => {
|
||||
export declare class Dir {
|
||||
static ngDirectiveDef: DirectiveMeta<Dir, '[dir]', never, never, never, never>;
|
||||
}
|
||||
|
||||
|
||||
export declare class ModuleA {
|
||||
static ngModuleDef: ModuleMeta<ModuleA, [typeof Dir], never, [typeof Dir]>;
|
||||
}
|
||||
@ -270,13 +270,13 @@ describe('MetadataDtsModuleScopeResolver', () => {
|
||||
});
|
||||
});
|
||||
|
||||
function scopeToRefs(scope: ExportScope): Reference<ts.ClassDeclaration>[] {
|
||||
function scopeToRefs(scope: ExportScope): Reference<ClassDeclaration>[] {
|
||||
const directives = scope.exported.directives.map(dir => dir.ref);
|
||||
const pipes = scope.exported.pipes.map(pipe => pipe.ref as Reference<ts.ClassDeclaration>);
|
||||
const pipes = scope.exported.pipes.map(pipe => pipe.ref);
|
||||
return [...directives, ...pipes].sort((a, b) => a.debugName !.localeCompare(b.debugName !));
|
||||
}
|
||||
|
||||
function getAlias(ref: Reference<ts.ClassDeclaration>): ExternalReference|null {
|
||||
function getAlias(ref: Reference<ClassDeclaration>): ExternalReference|null {
|
||||
if (ref.alias === null) {
|
||||
return null;
|
||||
} else {
|
||||
|
@ -9,16 +9,17 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Reference, ReferenceEmitter} from '../../imports';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {ScopeData, ScopeDirective, ScopePipe} from '../src/api';
|
||||
import {DtsModuleScopeResolver} from '../src/dependency';
|
||||
import {LocalModuleScopeRegistry} from '../src/local';
|
||||
|
||||
function registerFakeRefs(registry: LocalModuleScopeRegistry):
|
||||
{[name: string]: Reference<ts.ClassDeclaration>} {
|
||||
const get = (target: {}, name: string): Reference<ts.ClassDeclaration> => {
|
||||
{[name: string]: Reference<ClassDeclaration>} {
|
||||
const get = (target: {}, name: string): Reference<ClassDeclaration> => {
|
||||
const sf = ts.createSourceFile(
|
||||
name + '.ts', `export class ${name} {}`, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
||||
const clazz = sf.statements[0] as ts.ClassDeclaration;
|
||||
const clazz = sf.statements[0] as unknown as ClassDeclaration;
|
||||
const ref = new Reference(clazz);
|
||||
if (name.startsWith('Dir') || name.startsWith('Cmp')) {
|
||||
registry.registerDirective(fakeDirective(ref));
|
||||
@ -136,7 +137,7 @@ describe('LocalModuleScopeRegistry', () => {
|
||||
});
|
||||
});
|
||||
|
||||
function fakeDirective(ref: Reference<ts.ClassDeclaration>): ScopeDirective {
|
||||
function fakeDirective(ref: Reference<ClassDeclaration>): ScopeDirective {
|
||||
const name = ref.debugName !;
|
||||
return {
|
||||
ref,
|
||||
@ -152,16 +153,16 @@ function fakeDirective(ref: Reference<ts.ClassDeclaration>): ScopeDirective {
|
||||
};
|
||||
}
|
||||
|
||||
function fakePipe(ref: Reference<ts.ClassDeclaration>): ScopePipe {
|
||||
function fakePipe(ref: Reference<ClassDeclaration>): ScopePipe {
|
||||
const name = ref.debugName !;
|
||||
return {ref, name};
|
||||
}
|
||||
|
||||
class MockDtsModuleScopeResolver implements DtsModuleScopeResolver {
|
||||
resolve(ref: Reference<ts.ClassDeclaration>): null { return null; }
|
||||
resolve(ref: Reference<ClassDeclaration>): null { return null; }
|
||||
}
|
||||
|
||||
function scopeToRefs(scopeData: ScopeData): Reference<ts.Declaration>[] {
|
||||
function scopeToRefs(scopeData: ScopeData): Reference<ClassDeclaration>[] {
|
||||
const directives = scopeData.directives.map(dir => dir.ref);
|
||||
const pipes = scopeData.pipes.map(pipe => pipe.ref);
|
||||
return [...directives, ...pipes].sort((a, b) => a.debugName !.localeCompare(b.debugName !));
|
||||
|
Reference in New Issue
Block a user