|
|
|
@ -6,7 +6,7 @@
|
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import {Expression, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, R3ResolvedDependencyType, Statement, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler';
|
|
|
|
|
import {Expression, Identifiers, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, R3ResolvedDependencyType, Statement, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler';
|
|
|
|
|
import * as ts from 'typescript';
|
|
|
|
|
|
|
|
|
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
|
|
|
@ -14,12 +14,15 @@ import {DefaultImportRecorder} from '../../imports';
|
|
|
|
|
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
|
|
|
|
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
|
|
|
|
|
|
|
|
|
|
import {compileNgFactoryDefField} from './factory';
|
|
|
|
|
import {generateSetClassMetadataCall} from './metadata';
|
|
|
|
|
import {findAngularDecorator, getConstructorDependencies, getValidConstructorDependencies, validateConstructorDependencies} from './util';
|
|
|
|
|
import {findAngularDecorator, getConstructorDependencies, getValidConstructorDependencies, isAngularCore, unwrapForwardRef, validateConstructorDependencies} from './util';
|
|
|
|
|
|
|
|
|
|
export interface InjectableHandlerData {
|
|
|
|
|
meta: R3InjectableMetadata;
|
|
|
|
|
metadataStmt: Statement|null;
|
|
|
|
|
ctorDeps: R3DependencyMetadata[]|'invalid'|null;
|
|
|
|
|
needsFactory: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -49,41 +52,65 @@ export class InjectableDecoratorHandler implements
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput<InjectableHandlerData> {
|
|
|
|
|
const meta = extractInjectableMetadata(node, decorator, this.reflector);
|
|
|
|
|
const decorators = this.reflector.getDecoratorsOfDeclaration(node);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
analysis: {
|
|
|
|
|
meta: extractInjectableMetadata(
|
|
|
|
|
node, decorator, this.reflector, this.defaultImportRecorder, this.isCore,
|
|
|
|
|
meta,
|
|
|
|
|
ctorDeps: extractInjectableCtorDeps(
|
|
|
|
|
node, meta, decorator, this.reflector, this.defaultImportRecorder, this.isCore,
|
|
|
|
|
this.strictCtorDeps),
|
|
|
|
|
metadataStmt: generateSetClassMetadataCall(
|
|
|
|
|
node, this.reflector, this.defaultImportRecorder, this.isCore),
|
|
|
|
|
// Avoid generating multiple factories if a class has
|
|
|
|
|
// more Angular decorators, apart from Injectable.
|
|
|
|
|
needsFactory: !decorators ||
|
|
|
|
|
decorators.every(current => !isAngularCore(current) || current.name === 'Injectable')
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compile(node: ClassDeclaration, analysis: InjectableHandlerData): CompileResult {
|
|
|
|
|
compile(node: ClassDeclaration, analysis: InjectableHandlerData): CompileResult[] {
|
|
|
|
|
const res = compileIvyInjectable(analysis.meta);
|
|
|
|
|
const statements = res.statements;
|
|
|
|
|
if (analysis.metadataStmt !== null) {
|
|
|
|
|
statements.push(analysis.metadataStmt);
|
|
|
|
|
const results: CompileResult[] = [];
|
|
|
|
|
|
|
|
|
|
if (analysis.needsFactory) {
|
|
|
|
|
const meta = analysis.meta;
|
|
|
|
|
const factoryRes = compileNgFactoryDefField({
|
|
|
|
|
name: meta.name,
|
|
|
|
|
type: meta.type,
|
|
|
|
|
typeArgumentCount: meta.typeArgumentCount,
|
|
|
|
|
deps: analysis.ctorDeps,
|
|
|
|
|
injectFn: Identifiers.inject
|
|
|
|
|
});
|
|
|
|
|
if (analysis.metadataStmt !== null) {
|
|
|
|
|
factoryRes.statements.push(analysis.metadataStmt);
|
|
|
|
|
}
|
|
|
|
|
results.push(factoryRes);
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
|
|
results.push({
|
|
|
|
|
name: 'ngInjectableDef',
|
|
|
|
|
initializer: res.expression, statements,
|
|
|
|
|
type: res.type,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return results;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Read metadata from the `@Injectable` decorator and produce the `IvyInjectableMetadata`, the input
|
|
|
|
|
* Read metadata from the `@Injectable` decorator and produce the `IvyInjectableMetadata`, the
|
|
|
|
|
* input
|
|
|
|
|
* metadata needed to run `compileIvyInjectable`.
|
|
|
|
|
*
|
|
|
|
|
* A `null` return value indicates this is @Injectable has invalid data.
|
|
|
|
|
*/
|
|
|
|
|
function extractInjectableMetadata(
|
|
|
|
|
clazz: ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
|
|
|
|
|
defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
|
|
|
|
strictCtorDeps: boolean): R3InjectableMetadata {
|
|
|
|
|
clazz: ClassDeclaration, decorator: Decorator,
|
|
|
|
|
reflector: ReflectionHost): R3InjectableMetadata {
|
|
|
|
|
const name = clazz.name.text;
|
|
|
|
|
const type = new WrappedNodeExpr(clazz.name);
|
|
|
|
|
const typeArgumentCount = reflector.getGenericArityOfClass(clazz) || 0;
|
|
|
|
@ -92,53 +119,13 @@ function extractInjectableMetadata(
|
|
|
|
|
ErrorCode.DECORATOR_NOT_CALLED, decorator.node, '@Injectable must be called');
|
|
|
|
|
}
|
|
|
|
|
if (decorator.args.length === 0) {
|
|
|
|
|
// Ideally, using @Injectable() would have the same effect as using @Injectable({...}), and be
|
|
|
|
|
// subject to the same validation. However, existing Angular code abuses @Injectable, applying
|
|
|
|
|
// it to things like abstract classes with constructors that were never meant for use with
|
|
|
|
|
// Angular's DI.
|
|
|
|
|
//
|
|
|
|
|
// To deal with this, @Injectable() without an argument is more lenient, and if the constructor
|
|
|
|
|
// signature does not work for DI then an ngInjectableDef that throws.
|
|
|
|
|
let ctorDeps: R3DependencyMetadata[]|'invalid'|null = null;
|
|
|
|
|
if (strictCtorDeps) {
|
|
|
|
|
ctorDeps = getValidConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
|
|
|
|
} else {
|
|
|
|
|
const possibleCtorDeps =
|
|
|
|
|
getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
|
|
|
|
if (possibleCtorDeps !== null) {
|
|
|
|
|
if (possibleCtorDeps.deps !== null) {
|
|
|
|
|
// This use of @Injectable has valid constructor dependencies.
|
|
|
|
|
ctorDeps = possibleCtorDeps.deps;
|
|
|
|
|
} else {
|
|
|
|
|
// This use of @Injectable is technically invalid. Generate a factory function which
|
|
|
|
|
// throws
|
|
|
|
|
// an error.
|
|
|
|
|
// TODO(alxhub): log warnings for the bad use of @Injectable.
|
|
|
|
|
ctorDeps = 'invalid';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
name,
|
|
|
|
|
type,
|
|
|
|
|
typeArgumentCount,
|
|
|
|
|
providedIn: new LiteralExpr(null), ctorDeps,
|
|
|
|
|
providedIn: new LiteralExpr(null),
|
|
|
|
|
};
|
|
|
|
|
} else if (decorator.args.length === 1) {
|
|
|
|
|
const rawCtorDeps = getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
|
|
|
|
let ctorDeps: R3DependencyMetadata[]|'invalid'|null = null;
|
|
|
|
|
|
|
|
|
|
// rawCtorDeps will be null if the class has no constructor.
|
|
|
|
|
if (rawCtorDeps !== null) {
|
|
|
|
|
if (rawCtorDeps.deps !== null) {
|
|
|
|
|
// A constructor existed and had valid dependencies.
|
|
|
|
|
ctorDeps = rawCtorDeps.deps;
|
|
|
|
|
} else {
|
|
|
|
|
// A constructor existed but had invalid dependencies.
|
|
|
|
|
ctorDeps = 'invalid';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const metaNode = decorator.args[0];
|
|
|
|
|
// Firstly make sure the decorator argument is an inline literal - if not, it's illegal to
|
|
|
|
|
// transport references from one location to another. This is the problem that lowering
|
|
|
|
@ -170,27 +157,25 @@ function extractInjectableMetadata(
|
|
|
|
|
name,
|
|
|
|
|
type,
|
|
|
|
|
typeArgumentCount,
|
|
|
|
|
ctorDeps,
|
|
|
|
|
providedIn,
|
|
|
|
|
useValue: new WrappedNodeExpr(meta.get('useValue') !),
|
|
|
|
|
useValue: new WrappedNodeExpr(unwrapForwardRef(meta.get('useValue') !, reflector)),
|
|
|
|
|
};
|
|
|
|
|
} else if (meta.has('useExisting')) {
|
|
|
|
|
return {
|
|
|
|
|
name,
|
|
|
|
|
type,
|
|
|
|
|
typeArgumentCount,
|
|
|
|
|
ctorDeps,
|
|
|
|
|
providedIn,
|
|
|
|
|
useExisting: new WrappedNodeExpr(meta.get('useExisting') !),
|
|
|
|
|
useExisting: new WrappedNodeExpr(unwrapForwardRef(meta.get('useExisting') !, reflector)),
|
|
|
|
|
};
|
|
|
|
|
} else if (meta.has('useClass')) {
|
|
|
|
|
return {
|
|
|
|
|
name,
|
|
|
|
|
type,
|
|
|
|
|
typeArgumentCount,
|
|
|
|
|
ctorDeps,
|
|
|
|
|
providedIn,
|
|
|
|
|
useClass: new WrappedNodeExpr(meta.get('useClass') !), userDeps,
|
|
|
|
|
useClass: new WrappedNodeExpr(unwrapForwardRef(meta.get('useClass') !, reflector)),
|
|
|
|
|
userDeps,
|
|
|
|
|
};
|
|
|
|
|
} else if (meta.has('useFactory')) {
|
|
|
|
|
// useFactory is special - the 'deps' property must be analyzed.
|
|
|
|
@ -200,14 +185,10 @@ function extractInjectableMetadata(
|
|
|
|
|
type,
|
|
|
|
|
typeArgumentCount,
|
|
|
|
|
providedIn,
|
|
|
|
|
useFactory: factory, ctorDeps, userDeps,
|
|
|
|
|
useFactory: factory, userDeps,
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
if (strictCtorDeps) {
|
|
|
|
|
// Since use* was not provided, validate the deps according to strictCtorDeps.
|
|
|
|
|
validateConstructorDependencies(clazz, rawCtorDeps);
|
|
|
|
|
}
|
|
|
|
|
return {name, type, typeArgumentCount, providedIn, ctorDeps};
|
|
|
|
|
return {name, type, typeArgumentCount, providedIn};
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
throw new FatalDiagnosticError(
|
|
|
|
@ -215,7 +196,69 @@ function extractInjectableMetadata(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function extractInjectableCtorDeps(
|
|
|
|
|
clazz: ClassDeclaration, meta: R3InjectableMetadata, decorator: Decorator,
|
|
|
|
|
reflector: ReflectionHost, defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
|
|
|
|
strictCtorDeps: boolean) {
|
|
|
|
|
if (decorator.args === null) {
|
|
|
|
|
throw new FatalDiagnosticError(
|
|
|
|
|
ErrorCode.DECORATOR_NOT_CALLED, decorator.node, '@Injectable must be called');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let ctorDeps: R3DependencyMetadata[]|'invalid'|null = null;
|
|
|
|
|
|
|
|
|
|
if (decorator.args.length === 0) {
|
|
|
|
|
// Ideally, using @Injectable() would have the same effect as using @Injectable({...}), and be
|
|
|
|
|
// subject to the same validation. However, existing Angular code abuses @Injectable, applying
|
|
|
|
|
// it to things like abstract classes with constructors that were never meant for use with
|
|
|
|
|
// Angular's DI.
|
|
|
|
|
//
|
|
|
|
|
// To deal with this, @Injectable() without an argument is more lenient, and if the
|
|
|
|
|
// constructor
|
|
|
|
|
// signature does not work for DI then an ngInjectableDef that throws.
|
|
|
|
|
if (strictCtorDeps) {
|
|
|
|
|
ctorDeps = getValidConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
|
|
|
|
} else {
|
|
|
|
|
const possibleCtorDeps =
|
|
|
|
|
getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
|
|
|
|
if (possibleCtorDeps !== null) {
|
|
|
|
|
if (possibleCtorDeps.deps !== null) {
|
|
|
|
|
// This use of @Injectable has valid constructor dependencies.
|
|
|
|
|
ctorDeps = possibleCtorDeps.deps;
|
|
|
|
|
} else {
|
|
|
|
|
// This use of @Injectable is technically invalid. Generate a factory function which
|
|
|
|
|
// throws
|
|
|
|
|
// an error.
|
|
|
|
|
// TODO(alxhub): log warnings for the bad use of @Injectable.
|
|
|
|
|
ctorDeps = 'invalid';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ctorDeps;
|
|
|
|
|
} else if (decorator.args.length === 1) {
|
|
|
|
|
const rawCtorDeps = getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
|
|
|
|
|
|
|
|
|
// rawCtorDeps will be null if the class has no constructor.
|
|
|
|
|
if (rawCtorDeps !== null) {
|
|
|
|
|
if (rawCtorDeps.deps !== null) {
|
|
|
|
|
// A constructor existed and had valid dependencies.
|
|
|
|
|
ctorDeps = rawCtorDeps.deps;
|
|
|
|
|
} else {
|
|
|
|
|
// A constructor existed but had invalid dependencies.
|
|
|
|
|
ctorDeps = 'invalid';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strictCtorDeps && !meta.useValue && !meta.useExisting && !meta.useClass &&
|
|
|
|
|
!meta.useFactory) {
|
|
|
|
|
// Since use* was not provided, validate the deps according to strictCtorDeps.
|
|
|
|
|
validateConstructorDependencies(clazz, rawCtorDeps);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ctorDeps;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getDep(dep: ts.Expression, reflector: ReflectionHost): R3DependencyMetadata {
|
|
|
|
|
const meta: R3DependencyMetadata = {
|
|
|
|
|