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:
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);
|
||||
|
|
|
@ -7,10 +7,9 @@
|
|||
*/
|
||||
|
||||
import {R3BaseRefMetaData, compileBaseDefFromMetadata} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {PartialEvaluator} from '../../partial_evaluator';
|
||||
import {ClassMember, Decorator, ReflectionHost} from '../../reflection';
|
||||
import {ClassDeclaration, ClassMember, Decorator, ReflectionHost} from '../../reflection';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
|
||||
|
||||
import {isAngularDecorator} from './util';
|
||||
|
@ -33,7 +32,7 @@ export class BaseDefDecoratorHandler implements
|
|||
|
||||
readonly precedence = HandlerPrecedence.WEAK;
|
||||
|
||||
detect(node: ts.ClassDeclaration, decorators: Decorator[]|null):
|
||||
detect(node: ClassDeclaration, decorators: Decorator[]|null):
|
||||
DetectResult<R3BaseRefDecoratorDetection>|undefined {
|
||||
if (containsNgTopLevelDecorator(decorators, this.isCore)) {
|
||||
// If the class is already decorated by @Component or @Directive let that
|
||||
|
@ -70,7 +69,7 @@ export class BaseDefDecoratorHandler implements
|
|||
}
|
||||
}
|
||||
|
||||
analyze(node: ts.ClassDeclaration, metadata: R3BaseRefDecoratorDetection):
|
||||
analyze(node: ClassDeclaration, metadata: R3BaseRefDecoratorDetection):
|
||||
AnalysisOutput<R3BaseRefMetaData> {
|
||||
const analysis: R3BaseRefMetaData = {};
|
||||
if (metadata.inputs) {
|
||||
|
@ -114,7 +113,7 @@ export class BaseDefDecoratorHandler implements
|
|||
return {analysis};
|
||||
}
|
||||
|
||||
compile(node: ts.Declaration, analysis: R3BaseRefMetaData): CompileResult[]|CompileResult {
|
||||
compile(node: ClassDeclaration, analysis: R3BaseRefMetaData): CompileResult[]|CompileResult {
|
||||
const {expression, type} = compileBaseDefFromMetadata(analysis);
|
||||
|
||||
return {
|
||||
|
|
|
@ -14,7 +14,7 @@ import {CycleAnalyzer} from '../../cycles';
|
|||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {DefaultImportRecorder, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
||||
import {Decorator, ReflectionHost, filterToMembersWithDecorator, reflectObjectLiteral} from '../../reflection';
|
||||
import {ClassDeclaration, Decorator, ReflectionHost, filterToMembersWithDecorator, reflectObjectLiteral} from '../../reflection';
|
||||
import {LocalModuleScopeRegistry, ScopeDirective, extractDirectiveGuards} from '../../scope';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
|
||||
import {TypeCheckContext} from '../../typecheck';
|
||||
|
@ -60,7 +60,7 @@ export class ComponentDecoratorHandler implements
|
|||
|
||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||
|
||||
detect(node: ts.Declaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined {
|
||||
detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined {
|
||||
if (!decorators) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ export class ComponentDecoratorHandler implements
|
|||
}
|
||||
}
|
||||
|
||||
preanalyze(node: ts.ClassDeclaration, decorator: Decorator): Promise<void>|undefined {
|
||||
preanalyze(node: ClassDeclaration, decorator: Decorator): Promise<void>|undefined {
|
||||
// In preanalyze, resource URLs associated with the component are asynchronously preloaded via
|
||||
// the resourceLoader. This is the only time async operations are allowed for a component.
|
||||
// These resources are:
|
||||
|
@ -127,7 +127,7 @@ export class ComponentDecoratorHandler implements
|
|||
}
|
||||
}
|
||||
|
||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<ComponentHandlerData> {
|
||||
analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput<ComponentHandlerData> {
|
||||
const containingFile = node.getSourceFile().fileName;
|
||||
const meta = this._resolveLiteral(decorator);
|
||||
this.literalCache.delete(decorator);
|
||||
|
@ -213,7 +213,7 @@ export class ComponentDecoratorHandler implements
|
|||
const ref = new Reference(node);
|
||||
this.scopeRegistry.registerDirective({
|
||||
ref,
|
||||
name: node.name !.text,
|
||||
name: node.name.text,
|
||||
selector: metadata.selector,
|
||||
exportAs: metadata.exportAs,
|
||||
inputs: metadata.inputs,
|
||||
|
@ -297,7 +297,7 @@ export class ComponentDecoratorHandler implements
|
|||
return output;
|
||||
}
|
||||
|
||||
typeCheck(ctx: TypeCheckContext, node: ts.Declaration, meta: ComponentHandlerData): void {
|
||||
typeCheck(ctx: TypeCheckContext, node: ClassDeclaration, meta: ComponentHandlerData): void {
|
||||
if (!ts.isClassDeclaration(node)) {
|
||||
return;
|
||||
}
|
||||
|
@ -312,7 +312,7 @@ export class ComponentDecoratorHandler implements
|
|||
}
|
||||
}
|
||||
|
||||
resolve(node: ts.ClassDeclaration, analysis: ComponentHandlerData): ResolveResult {
|
||||
resolve(node: ClassDeclaration, analysis: ComponentHandlerData): ResolveResult {
|
||||
const context = node.getSourceFile();
|
||||
// Check whether this component was registered with an NgModule. If so, it should be compiled
|
||||
// under that module's compilation scope.
|
||||
|
@ -345,9 +345,9 @@ export class ComponentDecoratorHandler implements
|
|||
if (!cycleDetected) {
|
||||
const wrapDirectivesAndPipesInClosure =
|
||||
directives.some(
|
||||
dir => isExpressionForwardReference(dir.expression, node.name !, context)) ||
|
||||
dir => isExpressionForwardReference(dir.expression, node.name, context)) ||
|
||||
Array.from(pipes.values())
|
||||
.some(pipe => isExpressionForwardReference(pipe, node.name !, context));
|
||||
.some(pipe => isExpressionForwardReference(pipe, node.name, context));
|
||||
metadata.directives = directives;
|
||||
metadata.pipes = pipes;
|
||||
metadata.wrapDirectivesAndPipesInClosure = wrapDirectivesAndPipesInClosure;
|
||||
|
@ -374,7 +374,7 @@ export class ComponentDecoratorHandler implements
|
|||
return {};
|
||||
}
|
||||
|
||||
compile(node: ts.ClassDeclaration, analysis: ComponentHandlerData, pool: ConstantPool):
|
||||
compile(node: ClassDeclaration, analysis: ComponentHandlerData, pool: ConstantPool):
|
||||
CompileResult {
|
||||
const res = compileComponentFromMetadata(analysis.meta, pool, makeBindingParser());
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import * as ts from 'typescript';
|
|||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {DefaultImportRecorder, Reference} from '../../imports';
|
||||
import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
||||
import {ClassMember, ClassMemberKind, Decorator, ReflectionHost, filterToMembersWithDecorator, reflectObjectLiteral} from '../../reflection';
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, ReflectionHost, filterToMembersWithDecorator, reflectObjectLiteral} from '../../reflection';
|
||||
import {LocalModuleScopeRegistry} from '../../scope/src/local';
|
||||
import {extractDirectiveGuards} from '../../scope/src/util';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
|
||||
|
@ -35,7 +35,7 @@ export class DirectiveDecoratorHandler implements
|
|||
|
||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||
|
||||
detect(node: ts.Declaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined {
|
||||
detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined {
|
||||
if (!decorators) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ export class DirectiveDecoratorHandler implements
|
|||
}
|
||||
}
|
||||
|
||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<DirectiveHandlerData> {
|
||||
analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput<DirectiveHandlerData> {
|
||||
const directiveResult = extractDirectiveMetadata(
|
||||
node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore);
|
||||
const analysis = directiveResult && directiveResult.metadata;
|
||||
|
@ -61,7 +61,7 @@ export class DirectiveDecoratorHandler implements
|
|||
const ref = new Reference(node);
|
||||
this.scopeRegistry.registerDirective({
|
||||
ref,
|
||||
name: node.name !.text,
|
||||
name: node.name.text,
|
||||
selector: analysis.selector,
|
||||
exportAs: analysis.exportAs,
|
||||
inputs: analysis.inputs,
|
||||
|
@ -84,7 +84,7 @@ export class DirectiveDecoratorHandler implements
|
|||
};
|
||||
}
|
||||
|
||||
compile(node: ts.ClassDeclaration, analysis: DirectiveHandlerData, pool: ConstantPool):
|
||||
compile(node: ClassDeclaration, analysis: DirectiveHandlerData, pool: ConstantPool):
|
||||
CompileResult {
|
||||
const res = compileDirectiveFromMetadata(analysis.meta, pool, makeBindingParser());
|
||||
const statements = res.statements;
|
||||
|
@ -104,7 +104,7 @@ export class DirectiveDecoratorHandler implements
|
|||
* Helper function to extract metadata from a `Directive` or `Component`.
|
||||
*/
|
||||
export function extractDirectiveMetadata(
|
||||
clazz: ts.ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
|
||||
clazz: ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
|
||||
evaluator: PartialEvaluator, defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
||||
defaultSelector: string | null = null): {
|
||||
decorator: Map<string, ts.Expression>,
|
||||
|
@ -189,7 +189,7 @@ export function extractDirectiveMetadata(
|
|||
selector = resolved === '' ? defaultSelector : resolved;
|
||||
}
|
||||
if (!selector) {
|
||||
throw new Error(`Directive ${clazz.name !.text} has no selector, please add it!`);
|
||||
throw new Error(`Directive ${clazz.name.text} has no selector, please add it!`);
|
||||
}
|
||||
|
||||
const host = extractHostBindings(directive, decoratedElements, evaluator, coreModule);
|
||||
|
@ -217,14 +217,14 @@ export function extractDirectiveMetadata(
|
|||
// Detect if the component inherits from another class
|
||||
const usesInheritance = reflector.hasBaseClass(clazz);
|
||||
const metadata: R3DirectiveMetadata = {
|
||||
name: clazz.name !.text,
|
||||
name: clazz.name.text,
|
||||
deps: getValidConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore), host,
|
||||
lifecycle: {
|
||||
usesOnChanges,
|
||||
},
|
||||
inputs: {...inputsFromMeta, ...inputsFromFields},
|
||||
outputs: {...outputsFromMeta, ...outputsFromFields}, queries, viewQueries, selector,
|
||||
type: new WrappedNodeExpr(clazz.name !),
|
||||
type: new WrappedNodeExpr(clazz.name),
|
||||
typeArgumentCount: reflector.getGenericArityOfClass(clazz) || 0,
|
||||
typeSourceSpan: null !, usesInheritance, exportAs, providers
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ import * as ts from 'typescript';
|
|||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {DefaultImportRecorder} from '../../imports';
|
||||
import {Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
|
||||
|
||||
import {generateSetClassMetadataCall} from './metadata';
|
||||
|
@ -33,7 +33,7 @@ export class InjectableDecoratorHandler implements
|
|||
|
||||
readonly precedence = HandlerPrecedence.SHARED;
|
||||
|
||||
detect(node: ts.Declaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined {
|
||||
detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined {
|
||||
if (!decorators) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ export class InjectableDecoratorHandler implements
|
|||
}
|
||||
}
|
||||
|
||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<InjectableHandlerData> {
|
||||
analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput<InjectableHandlerData> {
|
||||
return {
|
||||
analysis: {
|
||||
meta: extractInjectableMetadata(
|
||||
|
@ -60,7 +60,7 @@ export class InjectableDecoratorHandler implements
|
|||
};
|
||||
}
|
||||
|
||||
compile(node: ts.ClassDeclaration, analysis: InjectableHandlerData): CompileResult {
|
||||
compile(node: ClassDeclaration, analysis: InjectableHandlerData): CompileResult {
|
||||
const res = compileIvyInjectable(analysis.meta);
|
||||
const statements = res.statements;
|
||||
if (analysis.metadataStmt !== null) {
|
||||
|
@ -81,13 +81,9 @@ export class InjectableDecoratorHandler implements
|
|||
* A `null` return value indicates this is @Injectable has invalid data.
|
||||
*/
|
||||
function extractInjectableMetadata(
|
||||
clazz: ts.ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
|
||||
clazz: ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
|
||||
defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
||||
strictCtorDeps: boolean): R3InjectableMetadata {
|
||||
if (clazz.name === undefined) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.DECORATOR_ON_ANONYMOUS_CLASS, decorator.node, `@Injectable on anonymous class`);
|
||||
}
|
||||
const name = clazz.name.text;
|
||||
const type = new WrappedNodeExpr(clazz.name);
|
||||
const typeArgumentCount = reflector.getGenericArityOfClass(clazz) || 0;
|
||||
|
|
|
@ -25,7 +25,7 @@ import {valueReferenceToExpression} from './util';
|
|||
export function generateSetClassMetadataCall(
|
||||
clazz: ts.Declaration, reflection: ReflectionHost, defaultImportRecorder: DefaultImportRecorder,
|
||||
isCore: boolean): Statement|null {
|
||||
if (!reflection.isClass(clazz) || clazz.name === undefined || !ts.isIdentifier(clazz.name)) {
|
||||
if (!reflection.isClass(clazz)) {
|
||||
return null;
|
||||
}
|
||||
const id = ts.updateIdentifier(clazz.name);
|
||||
|
|
|
@ -12,7 +12,7 @@ import * as ts from 'typescript';
|
|||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
|
||||
import {Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
|
||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
|
||||
import {NgModuleRouteAnalyzer} from '../../routing';
|
||||
import {LocalModuleScopeRegistry} from '../../scope';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
|
||||
|
@ -26,7 +26,7 @@ export interface NgModuleAnalysis {
|
|||
ngModuleDef: R3NgModuleMetadata;
|
||||
ngInjectorDef: R3InjectorMetadata;
|
||||
metadataStmt: Statement|null;
|
||||
declarations: Reference<ts.Declaration>[];
|
||||
declarations: Reference<ClassDeclaration>[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,7 +44,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
|
||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||
|
||||
detect(node: ts.Declaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined {
|
||||
detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined {
|
||||
if (!decorators) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -59,8 +59,8 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
}
|
||||
}
|
||||
|
||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<NgModuleAnalysis> {
|
||||
const name = node.name !.text;
|
||||
analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput<NgModuleAnalysis> {
|
||||
const name = node.name.text;
|
||||
if (decorator.args === null || decorator.args.length > 1) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.DECORATOR_ARITY_WRONG, decorator.node,
|
||||
|
@ -90,20 +90,20 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
]);
|
||||
|
||||
// Extract the module declarations, imports, and exports.
|
||||
let declarationRefs: Reference<ts.Declaration>[] = [];
|
||||
let declarationRefs: Reference<ClassDeclaration>[] = [];
|
||||
if (ngModule.has('declarations')) {
|
||||
const expr = ngModule.get('declarations') !;
|
||||
const declarationMeta = this.evaluator.evaluate(expr, forwardRefResolver);
|
||||
declarationRefs = this.resolveTypeList(expr, declarationMeta, name, 'declarations');
|
||||
}
|
||||
let importRefs: Reference<ts.Declaration>[] = [];
|
||||
let importRefs: Reference<ClassDeclaration>[] = [];
|
||||
let rawImports: ts.Expression|null = null;
|
||||
if (ngModule.has('imports')) {
|
||||
rawImports = ngModule.get('imports') !;
|
||||
const importsMeta = this.evaluator.evaluate(rawImports, moduleResolvers);
|
||||
importRefs = this.resolveTypeList(rawImports, importsMeta, name, 'imports');
|
||||
}
|
||||
let exportRefs: Reference<ts.Declaration>[] = [];
|
||||
let exportRefs: Reference<ClassDeclaration>[] = [];
|
||||
let rawExports: ts.Expression|null = null;
|
||||
if (ngModule.has('exports')) {
|
||||
rawExports = ngModule.get('exports') !;
|
||||
|
@ -111,7 +111,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
exportRefs = this.resolveTypeList(rawExports, exportsMeta, name, 'exports');
|
||||
this.referencesRegistry.add(node, ...exportRefs);
|
||||
}
|
||||
let bootstrapRefs: Reference<ts.Declaration>[] = [];
|
||||
let bootstrapRefs: Reference<ClassDeclaration>[] = [];
|
||||
if (ngModule.has('bootstrap')) {
|
||||
const expr = ngModule.get('bootstrap') !;
|
||||
const bootstrapMeta = this.evaluator.evaluate(expr, forwardRefResolver);
|
||||
|
@ -146,7 +146,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
exports.some(isForwardReference);
|
||||
|
||||
const ngModuleDef: R3NgModuleMetadata = {
|
||||
type: new WrappedNodeExpr(node.name !),
|
||||
type: new WrappedNodeExpr(node.name),
|
||||
bootstrap,
|
||||
declarations,
|
||||
exports,
|
||||
|
@ -176,7 +176,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
|
||||
const ngInjectorDef: R3InjectorMetadata = {
|
||||
name,
|
||||
type: new WrappedNodeExpr(node.name !),
|
||||
type: new WrappedNodeExpr(node.name),
|
||||
deps: getValidConstructorDependencies(
|
||||
node, this.reflector, this.defaultImportRecorder, this.isCore),
|
||||
providers,
|
||||
|
@ -191,11 +191,11 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
metadataStmt: generateSetClassMetadataCall(
|
||||
node, this.reflector, this.defaultImportRecorder, this.isCore),
|
||||
},
|
||||
factorySymbolName: node.name !== undefined ? node.name.text : undefined,
|
||||
factorySymbolName: node.name.text,
|
||||
};
|
||||
}
|
||||
|
||||
resolve(node: ts.Declaration, analysis: NgModuleAnalysis): ResolveResult {
|
||||
resolve(node: ClassDeclaration, analysis: NgModuleAnalysis): ResolveResult {
|
||||
const scope = this.scopeRegistry.getScopeOfModule(node);
|
||||
const diagnostics = this.scopeRegistry.getDiagnosticsOfModule(node) || undefined;
|
||||
if (scope === null || scope.reexports === null) {
|
||||
|
@ -208,7 +208,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
}
|
||||
}
|
||||
|
||||
compile(node: ts.ClassDeclaration, analysis: NgModuleAnalysis): CompileResult[] {
|
||||
compile(node: ClassDeclaration, analysis: NgModuleAnalysis): CompileResult[] {
|
||||
const ngInjectorDef = compileInjector(analysis.ngInjectorDef);
|
||||
const ngModuleDef = compileNgModule(analysis.ngModuleDef);
|
||||
const ngModuleStatements = ngModuleDef.additionalStatements;
|
||||
|
@ -218,8 +218,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
const context = getSourceFile(node);
|
||||
for (const decl of analysis.declarations) {
|
||||
if (this.scopeRegistry.getRequiresRemoteScope(decl.node)) {
|
||||
const scope =
|
||||
this.scopeRegistry.getScopeOfModule(ts.getOriginalNode(node) as ts.Declaration);
|
||||
const scope = this.scopeRegistry.getScopeOfModule(ts.getOriginalNode(node) as typeof node);
|
||||
if (scope === null) {
|
||||
continue;
|
||||
}
|
||||
|
@ -340,13 +339,19 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
return null;
|
||||
}
|
||||
|
||||
// Verify that a `ts.Declaration` reference is a `ClassDeclaration` reference.
|
||||
private isClassDeclarationReference(ref: Reference<ts.Declaration>):
|
||||
ref is Reference<ClassDeclaration> {
|
||||
return this.reflector.isClass(ref.node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a list of `Reference`s from a resolved metadata value.
|
||||
*/
|
||||
private resolveTypeList(
|
||||
expr: ts.Node, resolvedList: ResolvedValue, className: string,
|
||||
arrayName: string): Reference<ts.Declaration>[] {
|
||||
const refList: Reference<ts.Declaration>[] = [];
|
||||
arrayName: string): Reference<ClassDeclaration>[] {
|
||||
const refList: Reference<ClassDeclaration>[] = [];
|
||||
if (!Array.isArray(resolvedList)) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.VALUE_HAS_WRONG_TYPE, expr,
|
||||
|
@ -364,7 +369,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
// Recurse into nested arrays.
|
||||
refList.push(...this.resolveTypeList(expr, entry, className, arrayName));
|
||||
} else if (isDeclarationReference(entry)) {
|
||||
if (!this.reflector.isClass(entry.node)) {
|
||||
if (!this.isClassDeclarationReference(entry)) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.VALUE_HAS_WRONG_TYPE, entry.node,
|
||||
`Value at position ${idx} in the NgModule.${arrayName}s of ${className} is not a class`);
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {LiteralExpr, R3PipeMetadata, Statement, WrappedNodeExpr, compilePipeFromMetadata} from '@angular/compiler';
|
||||
import {R3PipeMetadata, Statement, WrappedNodeExpr, compilePipeFromMetadata} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {DefaultImportRecorder, Reference} from '../../imports';
|
||||
import {PartialEvaluator} from '../../partial_evaluator';
|
||||
import {Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||
import {LocalModuleScopeRegistry} from '../../scope/src/local';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
|
||||
|
||||
|
@ -32,7 +32,7 @@ export class PipeDecoratorHandler implements DecoratorHandler<PipeHandlerData, D
|
|||
|
||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||
|
||||
detect(node: ts.Declaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined {
|
||||
detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined {
|
||||
if (!decorators) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -47,11 +47,7 @@ export class PipeDecoratorHandler implements DecoratorHandler<PipeHandlerData, D
|
|||
}
|
||||
}
|
||||
|
||||
analyze(clazz: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<PipeHandlerData> {
|
||||
if (clazz.name === undefined) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.DECORATOR_ON_ANONYMOUS_CLASS, clazz, `@Pipes must have names`);
|
||||
}
|
||||
analyze(clazz: ClassDeclaration, decorator: Decorator): AnalysisOutput<PipeHandlerData> {
|
||||
const name = clazz.name.text;
|
||||
const type = new WrappedNodeExpr(clazz.name);
|
||||
if (decorator.args === null) {
|
||||
|
@ -109,7 +105,7 @@ export class PipeDecoratorHandler implements DecoratorHandler<PipeHandlerData, D
|
|||
};
|
||||
}
|
||||
|
||||
compile(node: ts.ClassDeclaration, analysis: PipeHandlerData): CompileResult {
|
||||
compile(node: ClassDeclaration, analysis: PipeHandlerData): CompileResult {
|
||||
const res = compilePipeFromMetadata(analysis.meta);
|
||||
const statements = res.statements;
|
||||
if (analysis.metadataStmt !== null) {
|
||||
|
|
|
@ -12,7 +12,7 @@ import * as ts from 'typescript';
|
|||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {DefaultImportRecorder, ImportMode, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {ForeignFunctionResolver} from '../../partial_evaluator';
|
||||
import {ClassMemberKind, CtorParameter, Decorator, Import, ReflectionHost, TypeValueReference} from '../../reflection';
|
||||
import {ClassDeclaration, CtorParameter, Decorator, Import, ReflectionHost, TypeValueReference} from '../../reflection';
|
||||
|
||||
export enum ConstructorDepErrorKind {
|
||||
NO_SUITABLE_TOKEN,
|
||||
|
@ -33,7 +33,7 @@ export interface ConstructorDepError {
|
|||
}
|
||||
|
||||
export function getConstructorDependencies(
|
||||
clazz: ts.ClassDeclaration, reflector: ReflectionHost,
|
||||
clazz: ClassDeclaration, reflector: ReflectionHost,
|
||||
defaultImportRecorder: DefaultImportRecorder, isCore: boolean): ConstructorDeps|null {
|
||||
const deps: R3DependencyMetadata[] = [];
|
||||
const errors: ConstructorDepError[] = [];
|
||||
|
@ -128,14 +128,14 @@ export function valueReferenceToExpression(
|
|||
}
|
||||
|
||||
export function getValidConstructorDependencies(
|
||||
clazz: ts.ClassDeclaration, reflector: ReflectionHost,
|
||||
clazz: ClassDeclaration, reflector: ReflectionHost,
|
||||
defaultImportRecorder: DefaultImportRecorder, isCore: boolean): R3DependencyMetadata[]|null {
|
||||
return validateConstructorDependencies(
|
||||
clazz, getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore));
|
||||
}
|
||||
|
||||
export function validateConstructorDependencies(
|
||||
clazz: ts.ClassDeclaration, deps: ConstructorDeps | null): R3DependencyMetadata[]|null {
|
||||
clazz: ClassDeclaration, deps: ConstructorDeps | null): R3DependencyMetadata[]|null {
|
||||
if (deps === null) {
|
||||
return null;
|
||||
} else if (deps.deps !== null) {
|
||||
|
|
|
@ -15,6 +15,7 @@ import {PartialEvaluator} from '../../partial_evaluator';
|
|||
import {TypeScriptReflectionHost} from '../../reflection';
|
||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {isNamedClassDeclaration} from '../../util/src/typescript';
|
||||
import {ResourceLoader} from '../src/api';
|
||||
import {ComponentDecoratorHandler} from '../src/component';
|
||||
|
||||
|
@ -56,7 +57,7 @@ describe('ComponentDecoratorHandler', () => {
|
|||
const handler = new ComponentDecoratorHandler(
|
||||
reflectionHost, evaluator, scopeRegistry, false, new NoopResourceLoader(), [''], false,
|
||||
true, moduleResolver, cycleAnalyzer, refEmitter, NOOP_DEFAULT_IMPORT_RECORDER);
|
||||
const TestCmp = getDeclaration(program, 'entry.ts', 'TestCmp', ts.isClassDeclaration);
|
||||
const TestCmp = getDeclaration(program, 'entry.ts', 'TestCmp', isNamedClassDeclaration);
|
||||
const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp));
|
||||
if (detected === undefined) {
|
||||
return fail('Failed to recognize @Component');
|
||||
|
|
|
@ -13,6 +13,7 @@ import {PartialEvaluator} from '../../partial_evaluator';
|
|||
import {TypeScriptReflectionHost} from '../../reflection';
|
||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {isNamedClassDeclaration} from '../../util/src/typescript';
|
||||
import {DirectiveDecoratorHandler} from '../src/directive';
|
||||
|
||||
|
||||
|
@ -47,7 +48,7 @@ describe('DirectiveDecoratorHandler', () => {
|
|||
reflectionHost, evaluator, scopeRegistry, NOOP_DEFAULT_IMPORT_RECORDER, false);
|
||||
|
||||
const analyzeDirective = (dirName: string) => {
|
||||
const DirNode = getDeclaration(program, 'entry.ts', dirName, ts.isClassDeclaration);
|
||||
const DirNode = getDeclaration(program, 'entry.ts', dirName, isNamedClassDeclaration);
|
||||
|
||||
const detected = handler.detect(DirNode, reflectionHost.getDecoratorsOfDeclaration(DirNode));
|
||||
if (detected === undefined) {
|
||||
|
|
|
@ -15,6 +15,7 @@ import {PartialEvaluator} from '../../partial_evaluator';
|
|||
import {TypeScriptReflectionHost} from '../../reflection';
|
||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {isNamedClassDeclaration} from '../../util/src/typescript';
|
||||
import {NgModuleDecoratorHandler} from '../src/ng_module';
|
||||
import {NoopReferencesRegistry} from '../src/references_registry';
|
||||
|
||||
|
@ -63,7 +64,7 @@ describe('NgModuleDecoratorHandler', () => {
|
|||
const handler = new NgModuleDecoratorHandler(
|
||||
reflectionHost, evaluator, scopeRegistry, referencesRegistry, false, null, refEmitter,
|
||||
NOOP_DEFAULT_IMPORT_RECORDER);
|
||||
const TestModule = getDeclaration(program, 'entry.ts', 'TestModule', ts.isClassDeclaration);
|
||||
const TestModule = getDeclaration(program, 'entry.ts', 'TestModule', isNamedClassDeclaration);
|
||||
const detected =
|
||||
handler.detect(TestModule, reflectionHost.getDecoratorsOfDeclaration(TestModule));
|
||||
if (detected === undefined) {
|
||||
|
|
|
@ -11,6 +11,7 @@ ts_library(
|
|||
"//packages:types",
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/path",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
"@npm//@types/node",
|
||||
"@npm//typescript",
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import {Expression, ExternalExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {FileToModuleHost, ReferenceEmitStrategy} from './emitter';
|
||||
import {ImportMode, Reference} from './references';
|
||||
|
||||
|
@ -18,20 +19,16 @@ const CHARS_TO_ESCAPE = /[^a-zA-Z0-9/_]/g;
|
|||
export class AliasGenerator {
|
||||
constructor(private fileToModuleHost: FileToModuleHost) {}
|
||||
|
||||
aliasSymbolName(decl: ts.Declaration, context: ts.SourceFile): string {
|
||||
if (!ts.isClassDeclaration(decl)) {
|
||||
throw new Error(`Attempt to write an alias to something which isn't a class`);
|
||||
}
|
||||
|
||||
aliasSymbolName(decl: ClassDeclaration, context: ts.SourceFile): string {
|
||||
// The declared module is used to get the name of the alias.
|
||||
const declModule =
|
||||
this.fileToModuleHost.fileNameToModuleName(decl.getSourceFile().fileName, context.fileName);
|
||||
|
||||
const replaced = declModule.replace(CHARS_TO_ESCAPE, '_').replace(/\//g, '$');
|
||||
return 'ɵng$' + replaced + '$$' + decl.name !.text;
|
||||
return 'ɵng$' + replaced + '$$' + decl.name.text;
|
||||
}
|
||||
|
||||
aliasTo(decl: ts.Declaration, via: ts.SourceFile): Expression {
|
||||
aliasTo(decl: ClassDeclaration, via: ts.SourceFile): Expression {
|
||||
const name = this.aliasSymbolName(decl, via);
|
||||
// viaModule is the module it'll actually be imported from.
|
||||
const moduleName = this.fileToModuleHost.fileNameToModuleName(via.fileName, via.fileName);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -6,9 +6,8 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Reference} from '../../imports';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {TypeCheckableDirectiveMeta} from '../../typecheck';
|
||||
|
||||
/**
|
||||
|
@ -51,6 +50,6 @@ export interface ScopeDirective extends TypeCheckableDirectiveMeta {
|
|||
* Metadata for a given pipe within an NgModule's scope.
|
||||
*/
|
||||
export interface ScopePipe {
|
||||
ref: Reference<ts.Declaration>;
|
||||
ref: Reference<ClassDeclaration>;
|
||||
name: string;
|
||||
}
|
||||
|
|
|
@ -9,13 +9,13 @@
|
|||
import * as ts from 'typescript';
|
||||
|
||||
import {AliasGenerator, Reference} from '../../imports';
|
||||
import {ReflectionHost} from '../../reflection';
|
||||
import {ClassDeclaration, ReflectionHost} from '../../reflection';
|
||||
|
||||
import {ExportScope, ScopeDirective, ScopePipe} from './api';
|
||||
import {extractDirectiveGuards, extractReferencesFromType, readStringArrayType, readStringMapType, readStringType} from './util';
|
||||
|
||||
export interface DtsModuleScopeResolver {
|
||||
resolve(ref: Reference<ts.ClassDeclaration>): ExportScope|null;
|
||||
resolve(ref: Reference<ClassDeclaration>): ExportScope|null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,7 +29,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
|
|||
/**
|
||||
* Cache which holds fully resolved scopes for NgModule classes from .d.ts files.
|
||||
*/
|
||||
private cache = new Map<ts.ClassDeclaration, ExportScope|null>();
|
||||
private cache = new Map<ClassDeclaration, ExportScope|null>();
|
||||
|
||||
constructor(
|
||||
private checker: ts.TypeChecker, private reflector: ReflectionHost,
|
||||
|
@ -42,7 +42,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
|
|||
* This operation relies on a `Reference` instead of a direct TypeScrpt node as the `Reference`s
|
||||
* produced depend on how the original NgModule was imported.
|
||||
*/
|
||||
resolve(ref: Reference<ts.ClassDeclaration>): ExportScope|null {
|
||||
resolve(ref: Reference<ClassDeclaration>): ExportScope|null {
|
||||
const clazz = ref.node;
|
||||
const sourceFile = clazz.getSourceFile();
|
||||
if (!sourceFile.isDeclarationFile) {
|
||||
|
@ -64,7 +64,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
|
|||
return null;
|
||||
}
|
||||
|
||||
const declarations = new Set<ts.Declaration>();
|
||||
const declarations = new Set<ClassDeclaration>();
|
||||
for (const declRef of meta.declarations) {
|
||||
declarations.add(declRef.node);
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
|
|||
/**
|
||||
* Read directive (or component) metadata from a referenced class in a .d.ts file.
|
||||
*/
|
||||
private readScopeDirectiveFromClassWithDef(ref: Reference<ts.ClassDeclaration>): ScopeDirective
|
||||
private readScopeDirectiveFromClassWithDef(ref: Reference<ClassDeclaration>): ScopeDirective
|
||||
|null {
|
||||
const clazz = ref.node;
|
||||
const def = this.reflector.getMembersOfClass(clazz).find(
|
||||
|
@ -193,7 +193,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
|
|||
|
||||
return {
|
||||
ref,
|
||||
name: clazz.name !.text,
|
||||
name: clazz.name.text,
|
||||
isComponent: def.name === 'ngComponentDef', selector,
|
||||
exportAs: readStringArrayType(def.type.typeArguments[2]),
|
||||
inputs: readStringMapType(def.type.typeArguments[3]),
|
||||
|
@ -206,7 +206,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
|
|||
/**
|
||||
* Read pipe metadata from a referenced class in a .d.ts file.
|
||||
*/
|
||||
private readScopePipeFromClassWithDef(ref: Reference<ts.ClassDeclaration>): ScopePipe|null {
|
||||
private readScopePipeFromClassWithDef(ref: Reference<ClassDeclaration>): ScopePipe|null {
|
||||
const def = this.reflector.getMembersOfClass(ref.node).find(
|
||||
field => field.isStatic && field.name === 'ngPipeDef');
|
||||
if (def === undefined) {
|
||||
|
@ -248,7 +248,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
|
|||
* Raw metadata read from the .d.ts info of an ngModuleDef field on a compiled NgModule class.
|
||||
*/
|
||||
interface RawDependencyMetadata {
|
||||
declarations: Reference<ts.ClassDeclaration>[];
|
||||
imports: Reference<ts.ClassDeclaration>[];
|
||||
exports: Reference<ts.ClassDeclaration>[];
|
||||
declarations: Reference<ClassDeclaration>[];
|
||||
imports: Reference<ClassDeclaration>[];
|
||||
exports: Reference<ClassDeclaration>[];
|
||||
}
|
||||
|
|
|
@ -11,15 +11,16 @@ import * as ts from 'typescript';
|
|||
|
||||
import {ErrorCode, makeDiagnostic} from '../../diagnostics';
|
||||
import {AliasGenerator, Reexport, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {identifierOfNode, nodeNameForError} from '../../util/src/typescript';
|
||||
|
||||
import {ExportScope, ScopeData, ScopeDirective, ScopePipe} from './api';
|
||||
import {DtsModuleScopeResolver} from './dependency';
|
||||
|
||||
export interface LocalNgModuleData {
|
||||
declarations: Reference<ts.Declaration>[];
|
||||
imports: Reference<ts.Declaration>[];
|
||||
exports: Reference<ts.Declaration>[];
|
||||
declarations: Reference<ClassDeclaration>[];
|
||||
imports: Reference<ClassDeclaration>[];
|
||||
exports: Reference<ClassDeclaration>[];
|
||||
}
|
||||
|
||||
export interface LocalModuleScope extends ExportScope {
|
||||
|
@ -57,17 +58,17 @@ export class LocalModuleScopeRegistry {
|
|||
/**
|
||||
* Metadata for each local NgModule registered.
|
||||
*/
|
||||
private ngModuleData = new Map<ts.Declaration, LocalNgModuleData>();
|
||||
private ngModuleData = new Map<ClassDeclaration, LocalNgModuleData>();
|
||||
|
||||
/**
|
||||
* Metadata for each local directive registered.
|
||||
*/
|
||||
private directiveData = new Map<ts.Declaration, ScopeDirective>();
|
||||
private directiveData = new Map<ClassDeclaration, ScopeDirective>();
|
||||
|
||||
/**
|
||||
* Metadata for each local pipe registered.
|
||||
*/
|
||||
private pipeData = new Map<ts.Declaration, ScopePipe>();
|
||||
private pipeData = new Map<ClassDeclaration, ScopePipe>();
|
||||
|
||||
/**
|
||||
* A map of components from the current compilation unit to the NgModule which declared them.
|
||||
|
@ -76,7 +77,7 @@ export class LocalModuleScopeRegistry {
|
|||
* contain directives. This doesn't cause any problems but isn't useful as there is no concept of
|
||||
* a directive's compilation scope.
|
||||
*/
|
||||
private declarationToModule = new Map<ts.Declaration, ts.Declaration>();
|
||||
private declarationToModule = new Map<ClassDeclaration, ClassDeclaration>();
|
||||
|
||||
/**
|
||||
* A cache of calculated `LocalModuleScope`s for each NgModule declared in the current program.
|
||||
|
@ -84,7 +85,7 @@ export class LocalModuleScopeRegistry {
|
|||
* A value of `undefined` indicates the scope was invalid and produced errors (therefore,
|
||||
* diagnostics should exist in the `scopeErrors` map).
|
||||
*/
|
||||
private cache = new Map<ts.Declaration, LocalModuleScope|undefined>();
|
||||
private cache = new Map<ClassDeclaration, LocalModuleScope|undefined>();
|
||||
|
||||
/**
|
||||
* Tracks whether a given component requires "remote scoping".
|
||||
|
@ -94,12 +95,12 @@ export class LocalModuleScopeRegistry {
|
|||
* around cyclic import issues). This is not used in calculation of `LocalModuleScope`s, but is
|
||||
* tracked here for convenience.
|
||||
*/
|
||||
private remoteScoping = new Set<ts.Declaration>();
|
||||
private remoteScoping = new Set<ClassDeclaration>();
|
||||
|
||||
/**
|
||||
* Tracks errors accumulated in the processing of scopes for each module declaration.
|
||||
*/
|
||||
private scopeErrors = new Map<ts.Declaration, ts.Diagnostic[]>();
|
||||
private scopeErrors = new Map<ClassDeclaration, ts.Diagnostic[]>();
|
||||
|
||||
constructor(
|
||||
private dependencyScopeReader: DtsModuleScopeResolver, private refEmitter: ReferenceEmitter,
|
||||
|
@ -108,7 +109,7 @@ export class LocalModuleScopeRegistry {
|
|||
/**
|
||||
* Add an NgModule's data to the registry.
|
||||
*/
|
||||
registerNgModule(clazz: ts.Declaration, data: LocalNgModuleData): void {
|
||||
registerNgModule(clazz: ClassDeclaration, data: LocalNgModuleData): void {
|
||||
this.assertCollecting();
|
||||
this.ngModuleData.set(clazz, data);
|
||||
for (const decl of data.declarations) {
|
||||
|
@ -126,7 +127,7 @@ export class LocalModuleScopeRegistry {
|
|||
this.pipeData.set(pipe.ref.node, pipe);
|
||||
}
|
||||
|
||||
getScopeForComponent(clazz: ts.ClassDeclaration): LocalModuleScope|null {
|
||||
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null {
|
||||
if (!this.declarationToModule.has(clazz)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -141,7 +142,7 @@ export class LocalModuleScopeRegistry {
|
|||
* `LocalModuleScope` for the given NgModule if one can be produced, and `null` if no scope is
|
||||
* available or the scope contains errors.
|
||||
*/
|
||||
getScopeOfModule(clazz: ts.Declaration): LocalModuleScope|null {
|
||||
getScopeOfModule(clazz: ClassDeclaration): LocalModuleScope|null {
|
||||
const scope = this.getScopeOfModuleInternal(clazz);
|
||||
// Translate undefined -> null.
|
||||
return scope !== undefined ? scope : null;
|
||||
|
@ -151,7 +152,7 @@ export class LocalModuleScopeRegistry {
|
|||
* Retrieves any `ts.Diagnostic`s produced during the calculation of the `LocalModuleScope` for
|
||||
* the given NgModule, or `null` if no errors were present.
|
||||
*/
|
||||
getDiagnosticsOfModule(clazz: ts.Declaration): ts.Diagnostic[]|null {
|
||||
getDiagnosticsOfModule(clazz: ClassDeclaration): ts.Diagnostic[]|null {
|
||||
// Required to ensure the errors are populated for the given class. If it has been processed
|
||||
// before, this will be a no-op due to the scope cache.
|
||||
this.getScopeOfModule(clazz);
|
||||
|
@ -167,7 +168,7 @@ export class LocalModuleScopeRegistry {
|
|||
* Implementation of `getScopeOfModule` which differentiates between no scope being available
|
||||
* (returns `null`) and a scope being produced with errors (returns `undefined`).
|
||||
*/
|
||||
private getScopeOfModuleInternal(clazz: ts.Declaration): LocalModuleScope|null|undefined {
|
||||
private getScopeOfModuleInternal(clazz: ClassDeclaration): LocalModuleScope|null|undefined {
|
||||
// Seal the registry to protect the integrity of the `LocalModuleScope` cache.
|
||||
this.sealed = true;
|
||||
|
||||
|
@ -218,8 +219,7 @@ export class LocalModuleScopeRegistry {
|
|||
for (const decl of ngModule.declarations) {
|
||||
if (this.directiveData.has(decl.node)) {
|
||||
const directive = this.directiveData.get(decl.node) !;
|
||||
compilationDirectives.set(
|
||||
decl.node, {...directive, ref: decl as Reference<ts.ClassDeclaration>});
|
||||
compilationDirectives.set(decl.node, {...directive, ref: decl});
|
||||
} else if (this.pipeData.has(decl.node)) {
|
||||
const pipe = this.pipeData.get(decl.node) !;
|
||||
compilationPipes.set(decl.node, {...pipe, ref: decl});
|
||||
|
@ -303,7 +303,7 @@ export class LocalModuleScopeRegistry {
|
|||
let reexports: Reexport[]|null = null;
|
||||
if (this.aliasGenerator !== null) {
|
||||
reexports = [];
|
||||
const addReexport = (ref: Reference<ts.Declaration>) => {
|
||||
const addReexport = (ref: Reference<ClassDeclaration>) => {
|
||||
if (!declared.has(ref.node) && ref.node.getSourceFile() !== sourceFile) {
|
||||
const exportName = this.aliasGenerator !.aliasSymbolName(ref.node, sourceFile);
|
||||
if (ref.alias && ref.alias instanceof ExternalExpr) {
|
||||
|
@ -362,12 +362,14 @@ export class LocalModuleScopeRegistry {
|
|||
/**
|
||||
* Check whether a component requires remote scoping.
|
||||
*/
|
||||
getRequiresRemoteScope(node: ts.Declaration): boolean { return this.remoteScoping.has(node); }
|
||||
getRequiresRemoteScope(node: ClassDeclaration): boolean { return this.remoteScoping.has(node); }
|
||||
|
||||
/**
|
||||
* Set a component as requiring remote scoping.
|
||||
*/
|
||||
setComponentAsRequiringRemoteScoping(node: ts.Declaration): void { this.remoteScoping.add(node); }
|
||||
setComponentAsRequiringRemoteScoping(node: ClassDeclaration): void {
|
||||
this.remoteScoping.add(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the `ExportScope` of a given `Reference` to an NgModule.
|
||||
|
@ -380,8 +382,8 @@ export class LocalModuleScopeRegistry {
|
|||
* array parameter.
|
||||
*/
|
||||
private getExportedScope(
|
||||
ref: Reference<ts.Declaration>, diagnostics: ts.Diagnostic[], ownerForErrors: ts.Declaration,
|
||||
type: 'import'|'export'): ExportScope|null|undefined {
|
||||
ref: Reference<ClassDeclaration>, diagnostics: ts.Diagnostic[],
|
||||
ownerForErrors: ts.Declaration, type: 'import'|'export'): ExportScope|null|undefined {
|
||||
if (ref.node.getSourceFile().isDeclarationFile) {
|
||||
// The NgModule is declared in a .d.ts file. Resolve it with the `DependencyScopeReader`.
|
||||
if (!ts.isClassDeclaration(ref.node)) {
|
||||
|
@ -394,7 +396,7 @@ export class LocalModuleScopeRegistry {
|
|||
`Appears in the NgModule.${type}s of ${nodeNameForError(ownerForErrors)}, but could not be resolved to an NgModule`));
|
||||
return undefined;
|
||||
}
|
||||
return this.dependencyScopeReader.resolve(ref as Reference<ts.ClassDeclaration>);
|
||||
return this.dependencyScopeReader.resolve(ref);
|
||||
} else {
|
||||
// The NgModule is declared locally in the current program. Resolve it from the registry.
|
||||
return this.getScopeOfModuleInternal(ref.node);
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
import * as ts from 'typescript';
|
||||
|
||||
import {Reference} from '../../imports';
|
||||
import {ClassMemberKind, ReflectionHost, reflectTypeEntityToDeclaration} from '../../reflection';
|
||||
import {nodeDebugInfo} from '../../util/src/typescript';
|
||||
import {ClassDeclaration, ClassMemberKind, ReflectionHost, reflectTypeEntityToDeclaration} from '../../reflection';
|
||||
import {isNamedClassDeclaration, nodeDebugInfo} from '../../util/src/typescript';
|
||||
|
||||
export function extractReferencesFromType(
|
||||
checker: ts.TypeChecker, def: ts.TypeNode, ngModuleImportedFrom: string | null,
|
||||
resolutionContext: string): Reference<ts.ClassDeclaration>[] {
|
||||
resolutionContext: string): Reference<ClassDeclaration>[] {
|
||||
if (!ts.isTupleTypeNode(def)) {
|
||||
return [];
|
||||
}
|
||||
|
@ -24,8 +24,8 @@ export function extractReferencesFromType(
|
|||
}
|
||||
const type = element.exprName;
|
||||
const {node, from} = reflectTypeEntityToDeclaration(type, checker);
|
||||
if (!ts.isClassDeclaration(node)) {
|
||||
throw new Error(`Expected ClassDeclaration: ${nodeDebugInfo(node)}`);
|
||||
if (!isNamedClassDeclaration(node)) {
|
||||
throw new Error(`Expected named ClassDeclaration: ${nodeDebugInfo(node)}`);
|
||||
}
|
||||
const specifier = (from !== null && !from.startsWith('.') ? from : ngModuleImportedFrom);
|
||||
if (specifier !== null) {
|
||||
|
|
|
@ -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 !));
|
||||
|
|
|
@ -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) !;
|
||||
|
|
|
@ -9,6 +9,7 @@ ts_library(
|
|||
"//packages:types",
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/imports",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/translator",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
"@npm//typescript",
|
||||
|
|
|
@ -7,16 +7,16 @@
|
|||
*/
|
||||
|
||||
import {BoundTarget, DirectiveMeta} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Reference} from '../../imports';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
|
||||
/**
|
||||
* Extension of `DirectiveMeta` that includes additional information required to type-check the
|
||||
* usage of a particular directive.
|
||||
*/
|
||||
export interface TypeCheckableDirectiveMeta extends DirectiveMeta {
|
||||
ref: Reference<ts.ClassDeclaration>;
|
||||
ref: Reference<ClassDeclaration>;
|
||||
queries: string[];
|
||||
ngTemplateGuards: string[];
|
||||
hasNgTemplateContextGuard: boolean;
|
||||
|
|
|
@ -10,6 +10,7 @@ import {BoundTarget} from '@angular/compiler';
|
|||
import * as ts from 'typescript';
|
||||
|
||||
import {NoopImportRewriter, ReferenceEmitter} from '../../imports';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {ImportManager} from '../../translator';
|
||||
|
||||
import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta, TypeCtorMetadata} from './api';
|
||||
|
@ -42,16 +43,12 @@ export class TypeCheckContext {
|
|||
* @param template AST nodes of the template being recorded.
|
||||
* @param matcher `SelectorMatcher` which tracks directives that are in scope for this template.
|
||||
*/
|
||||
addTemplate(node: ts.ClassDeclaration, boundTarget: BoundTarget<TypeCheckableDirectiveMeta>):
|
||||
void {
|
||||
// Only write TCBs for named classes.
|
||||
if (node.name === undefined) {
|
||||
throw new Error(`Assertion: class must be named`);
|
||||
}
|
||||
|
||||
addTemplate(
|
||||
node: ClassDeclaration<ts.ClassDeclaration>,
|
||||
boundTarget: BoundTarget<TypeCheckableDirectiveMeta>): void {
|
||||
// Get all of the directives used in the template and record type constructors for all of them.
|
||||
boundTarget.getUsedDirectives().forEach(dir => {
|
||||
const dirNode = dir.ref.node;
|
||||
const dirNode = dir.ref.node as ClassDeclaration<ts.ClassDeclaration>;
|
||||
// Add a type constructor operation for the directive.
|
||||
this.addTypeCtor(dirNode.getSourceFile(), dirNode, {
|
||||
fnName: 'ngTypeCtor',
|
||||
|
@ -77,7 +74,9 @@ export class TypeCheckContext {
|
|||
/**
|
||||
* Record a type constructor for the given `node` with the given `ctorMetadata`.
|
||||
*/
|
||||
addTypeCtor(sf: ts.SourceFile, node: ts.ClassDeclaration, ctorMeta: TypeCtorMetadata): void {
|
||||
addTypeCtor(
|
||||
sf: ts.SourceFile, node: ClassDeclaration<ts.ClassDeclaration>,
|
||||
ctorMeta: TypeCtorMetadata): void {
|
||||
// Lazily construct the operation map.
|
||||
if (!this.opMap.has(sf)) {
|
||||
this.opMap.set(sf, []);
|
||||
|
@ -136,7 +135,8 @@ export class TypeCheckContext {
|
|||
}
|
||||
|
||||
private addTypeCheckBlock(
|
||||
sf: ts.SourceFile, node: ts.ClassDeclaration, tcbMeta: TypeCheckBlockMetadata): void {
|
||||
sf: ts.SourceFile, node: ClassDeclaration<ts.ClassDeclaration>,
|
||||
tcbMeta: TypeCheckBlockMetadata): void {
|
||||
if (!this.opMap.has(sf)) {
|
||||
this.opMap.set(sf, []);
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ interface Op {
|
|||
/**
|
||||
* The node in the file which will have code generated for it.
|
||||
*/
|
||||
readonly node: ts.ClassDeclaration;
|
||||
readonly node: ClassDeclaration<ts.ClassDeclaration>;
|
||||
|
||||
/**
|
||||
* Index into the source text where the code generated by the operation should be inserted.
|
||||
|
@ -170,7 +170,9 @@ interface Op {
|
|||
* A type check block operation which produces type check code for a particular component.
|
||||
*/
|
||||
class TcbOp implements Op {
|
||||
constructor(readonly node: ts.ClassDeclaration, readonly meta: TypeCheckBlockMetadata) {}
|
||||
constructor(
|
||||
readonly node: ClassDeclaration<ts.ClassDeclaration>, readonly meta: TypeCheckBlockMetadata) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Type check blocks are inserted immediately after the end of the component class.
|
||||
|
@ -188,7 +190,8 @@ class TcbOp implements Op {
|
|||
* A type constructor operation which produces type constructor code for a particular directive.
|
||||
*/
|
||||
class TypeCtorOp implements Op {
|
||||
constructor(readonly node: ts.ClassDeclaration, readonly meta: TypeCtorMetadata) {}
|
||||
constructor(
|
||||
readonly node: ClassDeclaration<ts.ClassDeclaration>, readonly meta: TypeCtorMetadata) {}
|
||||
|
||||
/**
|
||||
* Type constructor operations are inserted immediately before the end of the directive class.
|
||||
|
|
|
@ -10,6 +10,7 @@ import {AST, BindingType, BoundTarget, ImplicitReceiver, PropertyRead, TmplAstBo
|
|||
import * as ts from 'typescript';
|
||||
|
||||
import {NOOP_DEFAULT_IMPORT_RECORDER, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {ImportManager, translateExpression} from '../../translator';
|
||||
|
||||
import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta} from './api';
|
||||
|
@ -29,8 +30,8 @@ import {astToTypescript} from './expression';
|
|||
* @param importManager an `ImportManager` for the file into which the TCB will be written.
|
||||
*/
|
||||
export function generateTypeCheckBlock(
|
||||
node: ts.ClassDeclaration, meta: TypeCheckBlockMetadata, importManager: ImportManager,
|
||||
refEmitter: ReferenceEmitter): ts.FunctionDeclaration {
|
||||
node: ClassDeclaration<ts.ClassDeclaration>, meta: TypeCheckBlockMetadata,
|
||||
importManager: ImportManager, refEmitter: ReferenceEmitter): ts.FunctionDeclaration {
|
||||
const tcb = new Context(meta.boundTarget, node.getSourceFile(), importManager, refEmitter);
|
||||
const scope = new Scope(tcb);
|
||||
tcbProcessNodes(meta.boundTarget.target.template !, tcb, scope);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {TypeCtorMetadata} from './api';
|
||||
|
||||
/**
|
||||
|
@ -29,19 +30,20 @@ import {TypeCtorMetadata} from './api';
|
|||
*
|
||||
* NgForOf.ngTypeCtor(init: {ngForOf: ['foo', 'bar']}); // Infers a type of NgForOf<string>.
|
||||
*
|
||||
* @param node the `ts.ClassDeclaration` for which a type constructor will be generated.
|
||||
* @param node the `ClassDeclaration<ts.ClassDeclaration>` for which a type constructor will be
|
||||
* generated.
|
||||
* @param meta additional metadata required to generate the type constructor.
|
||||
* @returns a `ts.MethodDeclaration` for the type constructor.
|
||||
*/
|
||||
export function generateTypeCtor(
|
||||
node: ts.ClassDeclaration, meta: TypeCtorMetadata): ts.MethodDeclaration {
|
||||
node: ClassDeclaration<ts.ClassDeclaration>, meta: TypeCtorMetadata): ts.MethodDeclaration {
|
||||
// Build rawType, a `ts.TypeNode` of the class with its generic parameters passed through from
|
||||
// the definition without any type bounds. For example, if the class is
|
||||
// `FooDirective<T extends Bar>`, its rawType would be `FooDirective<T>`.
|
||||
const rawTypeArgs = node.typeParameters !== undefined ?
|
||||
node.typeParameters.map(param => ts.createTypeReferenceNode(param.name, undefined)) :
|
||||
undefined;
|
||||
const rawType: ts.TypeNode = ts.createTypeReferenceNode(node.name !, rawTypeArgs);
|
||||
const rawType: ts.TypeNode = ts.createTypeReferenceNode(node.name, rawTypeArgs);
|
||||
|
||||
// initType is the type of 'init', the single argument to the type constructor method.
|
||||
// If the Directive has any inputs, outputs, or queries, its initType will be:
|
||||
|
|
|
@ -11,7 +11,7 @@ import * as ts from 'typescript';
|
|||
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ReferenceEmitter} from '../../imports';
|
||||
import {LogicalFileSystem} from '../../path';
|
||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {getRootDirs} from '../../util/src/typescript';
|
||||
import {getRootDirs, isNamedClassDeclaration} from '../../util/src/typescript';
|
||||
import {TypeCheckContext} from '../src/context';
|
||||
import {TypeCheckProgramHost} from '../src/host';
|
||||
|
||||
|
@ -47,7 +47,7 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||
new LogicalProjectStrategy(checker, logicalFs),
|
||||
]);
|
||||
const ctx = new TypeCheckContext(emitter);
|
||||
const TestClass = getDeclaration(program, 'main.ts', 'TestClass', ts.isClassDeclaration);
|
||||
const TestClass = getDeclaration(program, 'main.ts', 'TestClass', isNamedClassDeclaration);
|
||||
ctx.addTypeCtor(program.getSourceFile('main.ts') !, TestClass, {
|
||||
fnName: 'ngTypeCtor',
|
||||
body: true,
|
||||
|
|
|
@ -60,6 +60,11 @@ export function isDeclaration(node: ts.Node): node is ts.Declaration {
|
|||
ts.isFunctionDeclaration(node) || ts.isVariableDeclaration(node);
|
||||
}
|
||||
|
||||
export function isNamedClassDeclaration(node: ts.Node): node is ts.ClassDeclaration&
|
||||
{name: ts.Identifier} {
|
||||
return ts.isClassDeclaration(node) && (node.name !== undefined);
|
||||
}
|
||||
|
||||
export function isExported(node: ts.Declaration): boolean {
|
||||
let topLevel: ts.Node = node;
|
||||
if (ts.isVariableDeclaration(node) && ts.isVariableDeclarationList(node.parent)) {
|
||||
|
|
Loading…
Reference in New Issue