Revert "feat(compiler-cli): exclude abstract classes from strictInjectionParameters requirement (#44615)" (#45862)

This reverts commit 9cf14ff03d.

PR Close #45862
This commit is contained in:
Dylan Hunn
2022-05-03 16:46:46 -07:00
parent fa755b2a54
commit a2d5358c9b
23 changed files with 114 additions and 632 deletions
@@ -10,13 +10,12 @@ import ts from 'typescript';
import {ParsedConfiguration} from '../../..';
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ReferencesRegistry, ResourceLoader} from '../../../src/ngtsc/annotations';
import {InjectableClassRegistry} from '../../../src/ngtsc/annotations/common';
import {CycleAnalyzer, CycleHandlingStrategy, ImportGraph} from '../../../src/ngtsc/cycles';
import {isFatalDiagnosticError} from '../../../src/ngtsc/diagnostics';
import {absoluteFromSourceFile, LogicalFileSystem, ReadonlyFileSystem} from '../../../src/ngtsc/file_system';
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, PrivateExportAliasingHost, Reexport, ReferenceEmitter} from '../../../src/ngtsc/imports';
import {SemanticSymbol} from '../../../src/ngtsc/incremental/semantic_graph';
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, LocalMetadataRegistry, ResourceRegistry} from '../../../src/ngtsc/metadata';
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, ResourceRegistry} from '../../../src/ngtsc/metadata';
import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
import {NOOP_PERF_RECORDER} from '../../../src/ngtsc/perf';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver, TypeCheckScopeRegistry} from '../../../src/ngtsc/scope';
@@ -98,15 +97,14 @@ export class DecorationAnalyzer {
new PartialEvaluator(this.reflectionHost, this.typeChecker, /* dependencyTracker */ null);
importGraph = new ImportGraph(this.typeChecker, NOOP_PERF_RECORDER);
cycleAnalyzer = new CycleAnalyzer(this.importGraph);
injectableRegistry = new InjectableClassRegistry(this.reflectionHost, this.isCore);
injectableRegistry = new InjectableClassRegistry(this.reflectionHost);
typeCheckScopeRegistry = new TypeCheckScopeRegistry(this.scopeRegistry, this.fullMetaReader);
handlers: DecoratorHandler<unknown, unknown, SemanticSymbol|null, unknown>[] = [
new ComponentDecoratorHandler(
this.reflectionHost, this.evaluator, this.fullRegistry, this.fullMetaReader,
this.scopeRegistry, this.dtsModuleScopeResolver, this.scopeRegistry,
this.typeCheckScopeRegistry, new ResourceRegistry(), this.isCore,
/* strictCtorDeps */ false, this.resourceManager, this.rootDirs,
!!this.compilerOptions.preserveWhitespaces,
this.typeCheckScopeRegistry, new ResourceRegistry(), this.isCore, this.resourceManager,
this.rootDirs, !!this.compilerOptions.preserveWhitespaces,
/* i18nUseExternalIds */ true, this.bundle.enableI18nLegacyMessageIdFormat,
/* usePoisonedData */ false,
/* i18nNormalizeLineEndingsInICUs */ false, this.moduleResolver, this.cycleAnalyzer,
@@ -119,7 +117,7 @@ export class DecorationAnalyzer {
// clang-format off
new DirectiveDecoratorHandler(
this.reflectionHost, this.evaluator, this.fullRegistry, this.scopeRegistry,
this.fullMetaReader, this.injectableRegistry, this.isCore, /* strictCtorDeps */ false,
this.fullMetaReader, this.injectableRegistry, this.isCore,
/* semanticDepGraphUpdater */ null,
!!this.compilerOptions.annotateForClosureCompiler,
// In ngcc we want to compile undecorated classes with Angular features. As of
@@ -136,7 +134,7 @@ export class DecorationAnalyzer {
this.reflectionHost, this.evaluator, this.metaRegistry, this.scopeRegistry,
this.injectableRegistry, this.isCore, NOOP_PERF_RECORDER),
new InjectableDecoratorHandler(
this.reflectionHost, this.evaluator, this.isCore,
this.reflectionHost, this.isCore,
/* strictCtorDeps */ false, this.injectableRegistry, NOOP_PERF_RECORDER,
/* errorOnDuplicateProv */ false),
new NgModuleDecoratorHandler(
@@ -11,7 +11,6 @@ export * from './src/di';
export * from './src/diagnostics';
export * from './src/evaluation';
export * from './src/factory';
export * from './src/injectable_registry';
export * from './src/metadata';
export * from './src/references_registry';
export * from './src/schema';
@@ -10,13 +10,13 @@ import ts from 'typescript';
import {ErrorCode, FatalDiagnosticError, makeDiagnostic, makeRelatedInformation} from '../../../diagnostics';
import {Reference} from '../../../imports';
import {InjectableClassRegistry, MetadataReader} from '../../../metadata';
import {describeResolvedType, DynamicValue, PartialEvaluator, ResolvedValue, traceDynamicValue} from '../../../partial_evaluator';
import {ClassDeclaration, ReflectionHost} from '../../../reflection';
import {DeclarationData, LocalModuleScopeRegistry} from '../../../scope';
import {identifierOfNode} from '../../../util/src/typescript';
import {InjectableClassRegistry} from './injectable_registry';
import {isAbstractClassDeclaration, readBaseClass} from './util';
import {readBaseClass} from './util';
/**
@@ -102,10 +102,7 @@ export function getProviderDiagnostics(
const diagnostics: ts.Diagnostic[] = [];
for (const provider of providerClasses) {
const injectableMeta = registry.getInjectableMeta(provider.node);
if (injectableMeta !== null) {
// The provided type is recognized as injectable, so we don't report a diagnostic for this
// provider.
if (registry.isInjectable(provider.node)) {
continue;
}
@@ -127,9 +124,9 @@ Either add the @Injectable() decorator to '${
}
export function getDirectiveDiagnostics(
node: ClassDeclaration, injectableRegistry: InjectableClassRegistry,
evaluator: PartialEvaluator, reflector: ReflectionHost, scopeRegistry: LocalModuleScopeRegistry,
strictInjectionParameters: boolean, kind: 'Directive'|'Component'): ts.Diagnostic[]|null {
node: ClassDeclaration, reader: MetadataReader, evaluator: PartialEvaluator,
reflector: ReflectionHost, scopeRegistry: LocalModuleScopeRegistry,
kind: string): ts.Diagnostic[]|null {
let diagnostics: ts.Diagnostic[]|null = [];
const addDiagnostics = (more: ts.Diagnostic|ts.Diagnostic[]|null) => {
@@ -150,8 +147,7 @@ export function getDirectiveDiagnostics(
addDiagnostics(makeDuplicateDeclarationError(node, duplicateDeclarations, kind));
}
addDiagnostics(checkInheritanceOfInjectable(
node, injectableRegistry, reflector, evaluator, strictInjectionParameters, kind));
addDiagnostics(checkInheritanceOfDirective(node, reader, reflector, evaluator));
return diagnostics;
}
@@ -163,42 +159,9 @@ export function getUndecoratedClassWithAngularFeaturesDiagnostic(node: ClassDecl
`Angular decorator.`);
}
export function checkInheritanceOfInjectable(
node: ClassDeclaration, injectableRegistry: InjectableClassRegistry, reflector: ReflectionHost,
evaluator: PartialEvaluator, strictInjectionParameters: boolean,
kind: 'Directive'|'Component'|'Pipe'|'Injectable'): ts.Diagnostic|null {
const classWithCtor = findInheritedCtor(node, injectableRegistry, reflector, evaluator);
if (classWithCtor === null || classWithCtor.isCtorValid) {
// The class does not inherit a constructor, or the inherited constructor is compatible
// with DI; no need to report a diagnostic.
return null;
}
if (!classWithCtor.isDecorated) {
// The inherited constructor exists in a class that does not have an Angular decorator.
// This is an error, as there won't be a factory definition available for DI to invoke
// the constructor.
return getInheritedUndecoratedCtorDiagnostic(node, classWithCtor.ref, kind);
}
if (!strictInjectionParameters || isAbstractClassDeclaration(node)) {
// An invalid constructor is only reported as error under `strictInjectionParameters` and
// only for concrete classes; follow the same exclusions for derived types.
return null;
}
return getInheritedInvalidCtorDiagnostic(node, classWithCtor.ref, kind);
}
interface ClassWithCtor {
ref: Reference<ClassDeclaration>;
isCtorValid: boolean;
isDecorated: boolean;
}
export function findInheritedCtor(
node: ClassDeclaration, injectableRegistry: InjectableClassRegistry, reflector: ReflectionHost,
evaluator: PartialEvaluator): ClassWithCtor|null {
export function checkInheritanceOfDirective(
node: ClassDeclaration, reader: MetadataReader, reflector: ReflectionHost,
evaluator: PartialEvaluator): ts.Diagnostic|null {
if (!reflector.isClass(node) || reflector.getConstructorParameters(node) !== null) {
// We should skip nodes that aren't classes. If a constructor exists, then no base class
// definition is required on the runtime side - it's legal to inherit from any class.
@@ -215,64 +178,44 @@ export function findInheritedCtor(
return null;
}
const injectableMeta = injectableRegistry.getInjectableMeta(baseClass.node);
if (injectableMeta !== null) {
if (injectableMeta.ctorDeps !== null) {
// The class has an Angular decorator with a constructor.
return {
ref: baseClass,
isCtorValid: injectableMeta.ctorDeps !== 'invalid',
isDecorated: true,
};
}
} else {
const baseClassConstructorParams = reflector.getConstructorParameters(baseClass.node);
if (baseClassConstructorParams !== null) {
// The class is not decorated, but it does have constructor. An undecorated class is only
// allowed to have a constructor without parameters, otherwise it is invalid.
return {
ref: baseClass,
isCtorValid: baseClassConstructorParams.length === 0,
isDecorated: false,
};
}
// We can skip the base class if it has metadata.
const baseClassMeta = reader.getDirectiveMetadata(baseClass);
if (baseClassMeta !== null) {
return null;
}
// If the base class has a blank constructor we can skip it since it can't be using DI.
const baseClassConstructorParams = reflector.getConstructorParameters(baseClass.node);
const newParentClass = readBaseClass(baseClass.node, reflector, evaluator);
if (baseClassConstructorParams !== null && baseClassConstructorParams.length > 0) {
// This class has a non-trivial constructor, that's an error!
return getInheritedUndecoratedCtorDiagnostic(node, baseClass, reader);
} else if (baseClassConstructorParams !== null || newParentClass === null) {
// This class has a trivial constructor, or no constructor + is the
// top of the inheritance chain, so it's okay.
return null;
}
// Go up the chain and continue
baseClass = readBaseClass(baseClass.node, reflector, evaluator);
baseClass = newParentClass;
}
return null;
}
function getInheritedInvalidCtorDiagnostic(
node: ClassDeclaration, baseClass: Reference,
kind: 'Directive'|'Component'|'Pipe'|'Injectable') {
const baseClassName = baseClass.debugName;
return makeDiagnostic(
ErrorCode.INJECTABLE_INHERITS_INVALID_CONSTRUCTOR, node.name,
`The ${kind.toLowerCase()} ${node.name.text} inherits its constructor from ${
baseClassName}, ` +
`but the latter has a constructor parameter that is not compatible with dependency injection. ` +
`Either add an explicit constructor to ${node.name.text} or change ${
baseClassName}'s constructor to ` +
`use parameters that are valid for DI.`);
}
function getInheritedUndecoratedCtorDiagnostic(
node: ClassDeclaration, baseClass: Reference,
kind: 'Directive'|'Component'|'Pipe'|'Injectable') {
node: ClassDeclaration, baseClass: Reference, reader: MetadataReader) {
const subclassMeta = reader.getDirectiveMetadata(new Reference(node))!;
const dirOrComp = subclassMeta.isComponent ? 'Component' : 'Directive';
const baseClassName = baseClass.debugName;
const baseNeedsDecorator =
kind === 'Component' || kind === 'Directive' ? 'Directive' : 'Injectable';
return makeDiagnostic(
ErrorCode.DIRECTIVE_INHERITS_UNDECORATED_CTOR, node.name,
`The ${kind.toLowerCase()} ${node.name.text} inherits its constructor from ${
`The ${dirOrComp.toLowerCase()} ${node.name.text} inherits its constructor from ${
baseClassName}, ` +
`but the latter does not have an Angular decorator of its own. Dependency injection will not be able to ` +
`resolve the parameters of ${baseClassName}'s constructor. Either add a @${
baseNeedsDecorator} decorator ` +
`resolve the parameters of ${
baseClassName}'s constructor. Either add a @Directive decorator ` +
`to ${baseClassName}, or add an explicit constructor to ${node.name.text}.`);
}
@@ -1,50 +0,0 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {R3DependencyMetadata} from '@angular/compiler';
import {hasInjectableFields} from '../../../metadata';
import {ClassDeclaration, ReflectionHost} from '../../../reflection';
import {getConstructorDependencies, unwrapConstructorDependencies} from './di';
export interface InjectableMeta {
ctorDeps: R3DependencyMetadata[]|'invalid'|null;
}
/**
* Registry that keeps track of classes that can be constructed via dependency injection (e.g.
* injectables, directives, pipes).
*/
export class InjectableClassRegistry {
private classes = new Map<ClassDeclaration, InjectableMeta>();
constructor(private host: ReflectionHost, private isCore: boolean) {}
registerInjectable(declaration: ClassDeclaration, meta: InjectableMeta): void {
this.classes.set(declaration, meta);
}
getInjectableMeta(declaration: ClassDeclaration): InjectableMeta|null {
// Figure out whether the class is injectable based on the registered classes, otherwise
// fall back to looking at its members since we might not have been able to register the class
// if it was compiled in another compilation unit.
if (this.classes.has(declaration)) {
return this.classes.get(declaration)!;
}
if (!hasInjectableFields(declaration, this.host)) {
return null;
}
const ctorDeps = getConstructorDependencies(declaration, this.host, this.isCore);
return {
ctorDeps: unwrapConstructorDependencies(ctorDeps),
};
}
}
@@ -380,8 +380,3 @@ export function resolveImportedFile(
// Figure out what file is being imported.
return moduleResolver.resolveModule(expr.value.moduleName!, origin.fileName);
}
export function isAbstractClassDeclaration(clazz: ClassDeclaration): boolean {
return clazz.modifiers !== undefined &&
clazz.modifiers.some(mod => mod.kind === ts.SyntaxKind.AbstractKeyword);
}
@@ -16,7 +16,7 @@ import {assertSuccessfulReferenceEmit, ImportedFile, ModuleResolver, Reference,
import {DependencyTracker} from '../../../incremental/api';
import {extractSemanticTypeParameters, SemanticDepGraphUpdater} from '../../../incremental/semantic_graph';
import {IndexingContext} from '../../../indexer';
import {DirectiveMeta, extractDirectiveTypeCheckMeta, MetadataReader, MetadataRegistry, MetaKind, PipeMeta, ResourceRegistry} from '../../../metadata';
import {DirectiveMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, MetaKind, PipeMeta, ResourceRegistry} from '../../../metadata';
import {PartialEvaluator} from '../../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../../perf';
import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost, reflectObjectLiteral} from '../../../reflection';
@@ -26,7 +26,7 @@ import {TypeCheckContext} from '../../../typecheck/api';
import {ExtendedTemplateChecker} from '../../../typecheck/extended/api';
import {getSourceFile} from '../../../util/src/typescript';
import {Xi18nContext} from '../../../xi18n';
import {compileDeclareFactory, compileNgFactoryDefField, compileResults, extractClassMetadata, extractSchemas, findAngularDecorator, getDirectiveDiagnostics, getProviderDiagnostics, InjectableClassRegistry, isExpressionForwardReference, readBaseClass, resolveEnumValue, resolveImportedFile, resolveLiteral, resolveProvidersRequiringFactory, ResourceLoader, toFactoryMetadata, wrapFunctionExpressionsInParens} from '../../common';
import {compileDeclareFactory, compileNgFactoryDefField, compileResults, extractClassMetadata, extractSchemas, findAngularDecorator, getDirectiveDiagnostics, getProviderDiagnostics, isExpressionForwardReference, readBaseClass, resolveEnumValue, resolveImportedFile, resolveLiteral, resolveProvidersRequiringFactory, ResourceLoader, toFactoryMetadata, wrapFunctionExpressionsInParens} from '../../common';
import {extractDirectiveMetadata, parseFieldArrayValue} from '../../directive';
import {NgModuleSymbol} from '../../ng_module';
@@ -51,13 +51,12 @@ export class ComponentDecoratorHandler implements
private scopeRegistry: LocalModuleScopeRegistry,
private typeCheckScopeRegistry: TypeCheckScopeRegistry,
private resourceRegistry: ResourceRegistry, private isCore: boolean,
private strictCtorDeps: boolean, private resourceLoader: ResourceLoader,
private rootDirs: ReadonlyArray<string>, private defaultPreserveWhitespaces: boolean,
private i18nUseExternalIds: boolean, private enableI18nLegacyMessageIdFormat: boolean,
private usePoisonedData: boolean, private i18nNormalizeLineEndingsInICUs: boolean,
private moduleResolver: ModuleResolver, private cycleAnalyzer: CycleAnalyzer,
private cycleHandlingStrategy: CycleHandlingStrategy, private refEmitter: ReferenceEmitter,
private depTracker: DependencyTracker|null,
private resourceLoader: ResourceLoader, private rootDirs: ReadonlyArray<string>,
private defaultPreserveWhitespaces: boolean, private i18nUseExternalIds: boolean,
private enableI18nLegacyMessageIdFormat: boolean, private usePoisonedData: boolean,
private i18nNormalizeLineEndingsInICUs: boolean, private moduleResolver: ModuleResolver,
private cycleAnalyzer: CycleAnalyzer, private cycleHandlingStrategy: CycleHandlingStrategy,
private refEmitter: ReferenceEmitter, private depTracker: DependencyTracker|null,
private injectableRegistry: InjectableClassRegistry,
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
private annotateForClosureCompiler: boolean, private perf: PerfRecorder) {}
@@ -483,9 +482,7 @@ export class ComponentDecoratorHandler implements
});
this.resourceRegistry.registerResources(analysis.resources, node);
this.injectableRegistry.registerInjectable(node, {
ctorDeps: analysis.meta.deps,
});
this.injectableRegistry.registerInjectable(node);
}
index(
@@ -822,8 +819,7 @@ export class ComponentDecoratorHandler implements
}
const directiveDiagnostics = getDirectiveDiagnostics(
node, this.injectableRegistry, this.evaluator, this.reflector, this.scopeRegistry,
this.strictCtorDeps, 'Component');
node, this.metaReader, this.evaluator, this.reflector, this.scopeRegistry, 'Component');
if (directiveDiagnostics !== null) {
diagnostics.push(...directiveDiagnostics);
}
@@ -14,13 +14,13 @@ import {ErrorCode, FatalDiagnosticError} from '../../../diagnostics';
import {absoluteFrom} from '../../../file_system';
import {runInEachFileSystem} from '../../../file_system/testing';
import {ModuleResolver, Reference, ReferenceEmitter} from '../../../imports';
import {CompoundMetadataReader, DtsMetadataReader, LocalMetadataRegistry, ResourceRegistry} from '../../../metadata';
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, ResourceRegistry} from '../../../metadata';
import {PartialEvaluator} from '../../../partial_evaluator';
import {NOOP_PERF_RECORDER} from '../../../perf';
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver, TypeCheckScopeRegistry} from '../../../scope';
import {getDeclaration, makeProgram} from '../../../testing';
import {InjectableClassRegistry, ResourceLoader, ResourceLoaderContext} from '../../common';
import {ResourceLoader, ResourceLoaderContext} from '../../common';
import {ComponentDecoratorHandler} from '../src/handler';
export class StubResourceLoader implements ResourceLoader {
@@ -55,7 +55,7 @@ function setup(program: ts.Program, options: ts.CompilerOptions, host: ts.Compil
const scopeRegistry = new LocalModuleScopeRegistry(
metaRegistry, metaReader, dtsResolver, new ReferenceEmitter([]), null);
const refEmitter = new ReferenceEmitter([]);
const injectableRegistry = new InjectableClassRegistry(reflectionHost, /* isCore */ false);
const injectableRegistry = new InjectableClassRegistry(reflectionHost);
const resourceRegistry = new ResourceRegistry();
const typeCheckScopeRegistry = new TypeCheckScopeRegistry(scopeRegistry, metaReader);
const resourceLoader = new StubResourceLoader();
@@ -71,7 +71,6 @@ function setup(program: ts.Program, options: ts.CompilerOptions, host: ts.Compil
typeCheckScopeRegistry,
resourceRegistry,
/* isCore */ false,
/* strictCtorDeps */ false,
resourceLoader,
/* rootDirs */['/'],
/* defaultPreserveWhitespaces */ false,
@@ -11,13 +11,13 @@ import ts from 'typescript';
import {Reference} from '../../../imports';
import {extractSemanticTypeParameters, SemanticDepGraphUpdater} from '../../../incremental/semantic_graph';
import {ClassPropertyMapping, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, MetadataReader, MetadataRegistry, MetaKind} from '../../../metadata';
import {ClassPropertyMapping, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, MetaKind} from '../../../metadata';
import {PartialEvaluator} from '../../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../../perf';
import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, ReflectionHost} from '../../../reflection';
import {LocalModuleScopeRegistry} from '../../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../../transform';
import {compileDeclareFactory, compileNgFactoryDefField, compileResults, extractClassMetadata, findAngularDecorator, getDirectiveDiagnostics, getProviderDiagnostics, getUndecoratedClassWithAngularFeaturesDiagnostic, InjectableClassRegistry, isAngularDecorator, readBaseClass, resolveProvidersRequiringFactory, toFactoryMetadata,} from '../../common';
import {compileDeclareFactory, compileNgFactoryDefField, compileResults, extractClassMetadata, findAngularDecorator, getDirectiveDiagnostics, getProviderDiagnostics, getUndecoratedClassWithAngularFeaturesDiagnostic, isAngularDecorator, readBaseClass, resolveProvidersRequiringFactory, toFactoryMetadata} from '../../common';
import {extractDirectiveMetadata} from './shared';
import {DirectiveSymbol} from './symbol';
@@ -49,8 +49,7 @@ export class DirectiveDecoratorHandler implements
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
private metaReader: MetadataReader, private injectableRegistry: InjectableClassRegistry,
private isCore: boolean, private strictCtorDeps: boolean,
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
private isCore: boolean, private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
private annotateForClosureCompiler: boolean,
private compileUndecoratedClassesWithAngularFeatures: boolean, private perf: PerfRecorder) {}
@@ -152,9 +151,7 @@ export class DirectiveDecoratorHandler implements
schemas: null,
});
this.injectableRegistry.registerInjectable(node, {
ctorDeps: analysis.meta.deps,
});
this.injectableRegistry.registerInjectable(node);
}
resolve(node: ClassDeclaration, analysis: DirectiveHandlerData, symbol: DirectiveSymbol):
@@ -173,8 +170,7 @@ export class DirectiveDecoratorHandler implements
}
const directiveDiagnostics = getDirectiveDiagnostics(
node, this.injectableRegistry, this.evaluator, this.reflector, this.scopeRegistry,
this.strictCtorDeps, 'Directive');
node, this.metaReader, this.evaluator, this.reflector, this.scopeRegistry, 'Directive');
if (directiveDiagnostics !== null) {
diagnostics.push(...directiveDiagnostics);
}
@@ -11,7 +11,6 @@ ts_library(
deps = [
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/annotations/common",
"//packages/compiler-cli/src/ngtsc/annotations/directive",
"//packages/compiler-cli/src/ngtsc/file_system",
"//packages/compiler-cli/src/ngtsc/file_system/testing",
@@ -11,13 +11,12 @@ import ts from 'typescript';
import {absoluteFrom} from '../../../file_system';
import {runInEachFileSystem} from '../../../file_system/testing';
import {ReferenceEmitter} from '../../../imports';
import {CompoundMetadataReader, DtsMetadataReader, LocalMetadataRegistry} from '../../../metadata';
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../../metadata';
import {PartialEvaluator} from '../../../partial_evaluator';
import {NOOP_PERF_RECORDER} from '../../../perf';
import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../scope';
import {getDeclaration, makeProgram} from '../../../testing';
import {InjectableClassRegistry} from '../../common';
import {DirectiveDecoratorHandler} from '../index';
runInEachFileSystem(() => {
@@ -168,11 +167,10 @@ runInEachFileSystem(() => {
const scopeRegistry = new LocalModuleScopeRegistry(
metaReader, new CompoundMetadataReader([metaReader, dtsReader]),
new MetadataDtsModuleScopeResolver(dtsReader, null), new ReferenceEmitter([]), null);
const injectableRegistry = new InjectableClassRegistry(reflectionHost, /* isCore */ false);
const injectableRegistry = new InjectableClassRegistry(reflectionHost);
const handler = new DirectiveDecoratorHandler(
reflectionHost, evaluator, scopeRegistry, scopeRegistry, metaReader, injectableRegistry,
/*isCore*/ false,
/*strictCtorDeps*/ false,
/*semanticDepGraphUpdater*/ null,
/*annotateForClosureCompiler*/ false,
/*detectUndecoratedClassesWithAngularFeatures*/ false, NOOP_PERF_RECORDER);
@@ -12,7 +12,7 @@ import ts from 'typescript';
import {ErrorCode, FatalDiagnosticError, makeDiagnostic, makeRelatedInformation} from '../../../diagnostics';
import {assertSuccessfulReferenceEmit, Reference, ReferenceEmitter} from '../../../imports';
import {isArrayEqual, isReferenceEqual, isSymbolEqual, SemanticReference, SemanticSymbol} from '../../../incremental/semantic_graph';
import {MetadataReader, MetadataRegistry, MetaKind} from '../../../metadata';
import {InjectableClassRegistry, MetadataReader, MetadataRegistry, MetaKind} from '../../../metadata';
import {DynamicValue, PartialEvaluator, ResolvedValue, SyntheticValue} from '../../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../../perf';
import {ClassDeclaration, Decorator, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../../reflection';
@@ -21,7 +21,7 @@ import {getDiagnosticNode} from '../../../scope/src/util';
import {FactoryTracker} from '../../../shims/api';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../../transform';
import {getSourceFile} from '../../../util/src/typescript';
import {combineResolvers, compileDeclareFactory, compileNgFactoryDefField, createValueHasWrongTypeError, extractClassMetadata, extractSchemas, findAngularDecorator, forwardRefResolver, getProviderDiagnostics, getValidConstructorDependencies, InjectableClassRegistry, isExpressionForwardReference, ReferencesRegistry, resolveProvidersRequiringFactory, toR3Reference, unwrapExpression, wrapFunctionExpressionsInParens, wrapTypeReference} from '../../common';
import {combineResolvers, compileDeclareFactory, compileNgFactoryDefField, createValueHasWrongTypeError, extractClassMetadata, extractSchemas, findAngularDecorator, forwardRefResolver, getProviderDiagnostics, getValidConstructorDependencies, isExpressionForwardReference, ReferencesRegistry, resolveProvidersRequiringFactory, toR3Reference, unwrapExpression, wrapFunctionExpressionsInParens, wrapTypeReference} from '../../common';
export interface NgModuleAnalysis {
mod: R3NgModuleMetadata;
@@ -424,9 +424,7 @@ export class NgModuleDecoratorHandler implements
});
}
this.injectableRegistry.registerInjectable(node, {
ctorDeps: analysis.fac.deps,
});
this.injectableRegistry.registerInjectable(node);
}
resolve(node: ClassDeclaration, analysis: Readonly<NgModuleAnalysis>):
@@ -12,13 +12,13 @@ import ts from 'typescript';
import {absoluteFrom} from '../../../file_system';
import {runInEachFileSystem} from '../../../file_system/testing';
import {LocalIdentifierStrategy, ReferenceEmitter} from '../../../imports';
import {CompoundMetadataReader, DtsMetadataReader, LocalMetadataRegistry} from '../../../metadata';
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../../metadata';
import {PartialEvaluator} from '../../../partial_evaluator';
import {NOOP_PERF_RECORDER} from '../../../perf';
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../scope';
import {getDeclaration, makeProgram} from '../../../testing';
import {InjectableClassRegistry, NoopReferencesRegistry} from '../../common';
import {NoopReferencesRegistry} from '../../common';
import {NgModuleDecoratorHandler} from '../src/handler';
runInEachFileSystem(() => {
@@ -67,7 +67,7 @@ runInEachFileSystem(() => {
metaRegistry, metaReader, new MetadataDtsModuleScopeResolver(dtsReader, null),
new ReferenceEmitter([]), null);
const refEmitter = new ReferenceEmitter([new LocalIdentifierStrategy()]);
const injectableRegistry = new InjectableClassRegistry(reflectionHost, /* isCore */ false);
const injectableRegistry = new InjectableClassRegistry(reflectionHost);
const handler = new NgModuleDecoratorHandler(
reflectionHost, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry,
@@ -9,13 +9,12 @@
import {compileClassMetadata, CompileClassMetadataFn, compileDeclareClassMetadata, compileDeclareInjectableFromMetadata, compileInjectable, createMayBeForwardRefExpression, FactoryTarget, ForwardRefHandling, LiteralExpr, MaybeForwardRefExpression, R3ClassMetadata, R3CompiledExpression, R3DependencyMetadata, R3InjectableMetadata, WrappedNodeExpr} from '@angular/compiler';
import ts from 'typescript';
import {InjectableClassRegistry, isAbstractClassDeclaration} from '../../annotations/common';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {PartialEvaluator} from '../../partial_evaluator';
import {InjectableClassRegistry} from '../../metadata';
import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
import {checkInheritanceOfInjectable, compileDeclareFactory, CompileFactoryFn, compileNgFactoryDefField, extractClassMetadata, findAngularDecorator, getConstructorDependencies, getValidConstructorDependencies, isAngularCore, toFactoryMetadata, tryUnwrapForwardRef, unwrapConstructorDependencies, validateConstructorDependencies, wrapTypeReference,} from '../common';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
import {compileDeclareFactory, CompileFactoryFn, compileNgFactoryDefField, extractClassMetadata, findAngularDecorator, getConstructorDependencies, getValidConstructorDependencies, isAngularCore, toFactoryMetadata, tryUnwrapForwardRef, unwrapConstructorDependencies, validateConstructorDependencies, wrapTypeReference} from '../common';
export interface InjectableHandlerData {
meta: R3InjectableMetadata;
@@ -30,8 +29,7 @@ export interface InjectableHandlerData {
export class InjectableDecoratorHandler implements
DecoratorHandler<Decorator, InjectableHandlerData, null, unknown> {
constructor(
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
private isCore: boolean, private strictCtorDeps: boolean,
private reflector: ReflectionHost, private isCore: boolean, private strictCtorDeps: boolean,
private injectableRegistry: InjectableClassRegistry, private perf: PerfRecorder,
/**
* What to do if the injectable already contains a ɵprov property.
@@ -85,26 +83,8 @@ export class InjectableDecoratorHandler implements
return null;
}
register(node: ClassDeclaration, analysis: InjectableHandlerData): void {
this.injectableRegistry.registerInjectable(node, {
ctorDeps: analysis.ctorDeps,
});
}
resolve(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>, symbol: null):
ResolveResult<unknown> {
if (requiresValidCtor(analysis.meta)) {
const diagnostic = checkInheritanceOfInjectable(
node, this.injectableRegistry, this.reflector, this.evaluator, this.strictCtorDeps,
'Injectable');
if (diagnostic !== null) {
return {
diagnostics: [diagnostic],
};
}
}
return {};
register(node: ClassDeclaration): void {
this.injectableRegistry.registerInjectable(node);
}
compileFull(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>): CompileResult[] {
@@ -264,7 +244,7 @@ function extractInjectableCtorDeps(
// To deal with this, @Injectable() without an argument is more lenient, and if the
// constructor signature does not work for DI then a factory definition (ɵfac) that throws is
// generated.
if (strictCtorDeps && !isAbstractClassDeclaration(clazz)) {
if (strictCtorDeps) {
ctorDeps = getValidConstructorDependencies(clazz, reflector, isCore);
} else {
ctorDeps =
@@ -275,9 +255,9 @@ function extractInjectableCtorDeps(
} else if (decorator.args.length === 1) {
const rawCtorDeps = getConstructorDependencies(clazz, reflector, isCore);
if (strictCtorDeps && !isAbstractClassDeclaration(clazz) && requiresValidCtor(meta)) {
// Since use* was not provided for a concrete class, validate the deps according to
// strictCtorDeps.
if (strictCtorDeps && meta.useValue === undefined && meta.useExisting === undefined &&
meta.useClass === undefined && meta.useFactory === undefined) {
// Since use* was not provided, validate the deps according to strictCtorDeps.
ctorDeps = validateConstructorDependencies(clazz, rawCtorDeps);
} else {
ctorDeps = unwrapConstructorDependencies(rawCtorDeps);
@@ -287,11 +267,6 @@ function extractInjectableCtorDeps(
return ctorDeps;
}
function requiresValidCtor(meta: R3InjectableMetadata): boolean {
return meta.useValue === undefined && meta.useExisting === undefined &&
meta.useClass === undefined && meta.useFactory === undefined;
}
function getDep(dep: ts.Expression, reflector: ReflectionHost): R3DependencyMetadata {
const meta: R3DependencyMetadata = {
token: new WrappedNodeExpr(dep),
@@ -12,13 +12,13 @@ import ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {Reference} from '../../imports';
import {SemanticSymbol} from '../../incremental/semantic_graph';
import {MetadataRegistry, MetaKind} from '../../metadata';
import {InjectableClassRegistry, MetadataRegistry, MetaKind} from '../../metadata';
import {PartialEvaluator} from '../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {LocalModuleScopeRegistry} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
import {compileDeclareFactory, compileNgFactoryDefField, compileResults, createValueHasWrongTypeError, extractClassMetadata, findAngularDecorator, getValidConstructorDependencies, InjectableClassRegistry, makeDuplicateDeclarationError, toFactoryMetadata, unwrapExpression, wrapTypeReference,} from '../common';
import {compileDeclareFactory, compileNgFactoryDefField, compileResults, createValueHasWrongTypeError, extractClassMetadata, findAngularDecorator, getValidConstructorDependencies, makeDuplicateDeclarationError, toFactoryMetadata, unwrapExpression, wrapTypeReference} from '../common';
export interface PipeHandlerData {
meta: R3PipeMetadata;
@@ -161,9 +161,7 @@ export class PipeDecoratorHandler implements
isStandalone: analysis.meta.isStandalone,
});
this.injectableRegistry.registerInjectable(node, {
ctorDeps: analysis.meta.deps,
});
this.injectableRegistry.registerInjectable(node);
}
resolve(node: ClassDeclaration): ResolveResult<unknown> {
@@ -12,7 +12,6 @@ ts_library(
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/annotations",
"//packages/compiler-cli/src/ngtsc/annotations/common",
"//packages/compiler-cli/src/ngtsc/cycles",
"//packages/compiler-cli/src/ngtsc/diagnostics",
"//packages/compiler-cli/src/ngtsc/file_system",
@@ -5,11 +5,10 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {InjectableClassRegistry} from '../../annotations/common';
import {ErrorCode, FatalDiagnosticError, ngErrorCode} from '../../diagnostics';
import {absoluteFrom} from '../../file_system';
import {runInEachFileSystem} from '../../file_system/testing';
import {PartialEvaluator} from '../../partial_evaluator';
import {InjectableClassRegistry} from '../../metadata';
import {NOOP_PERF_RECORDER} from '../../perf';
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {getDeclaration, makeProgram} from '../../testing';
@@ -68,10 +67,9 @@ function setupHandler(errorOnDuplicateProv: boolean) {
]);
const checker = program.getTypeChecker();
const reflectionHost = new TypeScriptReflectionHost(checker);
const injectableRegistry = new InjectableClassRegistry(reflectionHost, /* isCore */ false);
const evaluator = new PartialEvaluator(reflectionHost, checker, null);
const injectableRegistry = new InjectableClassRegistry(reflectionHost);
const handler = new InjectableDecoratorHandler(
reflectionHost, evaluator, /* isCore */ false,
reflectionHost, /* isCore */ false,
/* strictCtorDeps */ false, injectableRegistry, NOOP_PERF_RECORDER, errorOnDuplicateProv);
const TestClass = getDeclaration(program, ENTRY_FILE, 'TestClass', isNamedClassDeclaration);
const ɵprov = reflectionHost.getMembersOfClass(TestClass).find(member => member.name === 'ɵprov');
@@ -13,7 +13,6 @@ ts_library(
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/annotations",
"//packages/compiler-cli/src/ngtsc/annotations/common",
"//packages/compiler-cli/src/ngtsc/cycles",
"//packages/compiler-cli/src/ngtsc/diagnostics",
"//packages/compiler-cli/src/ngtsc/entry_point",
@@ -9,7 +9,6 @@
import ts from 'typescript';
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, NoopReferencesRegistry, PipeDecoratorHandler, ReferencesRegistry} from '../../annotations';
import {InjectableClassRegistry} from '../../annotations/common';
import {CycleAnalyzer, CycleHandlingStrategy, ImportGraph} from '../../cycles';
import {COMPILER_ERRORS_WITH_GUIDES, ERROR_DETAILS_PAGE_BASE_URL, ErrorCode, ngErrorCode} from '../../diagnostics';
import {checkForPrivateExports, ReferenceGraph} from '../../entry_point';
@@ -18,7 +17,7 @@ import {AbsoluteModuleStrategy, AliasingHost, AliasStrategy, DefaultImportTracke
import {IncrementalBuildStrategy, IncrementalCompilation, IncrementalState} from '../../incremental';
import {SemanticSymbol} from '../../incremental/semantic_graph';
import {generateAnalysis, IndexedComponent, IndexingContext} from '../../indexer';
import {ComponentResources, CompoundMetadataReader, CompoundMetadataRegistry, DirectiveMeta, DtsMetadataReader, LocalMetadataRegistry, MetadataReader, PipeMeta, ResourceRegistry} from '../../metadata';
import {ComponentResources, CompoundMetadataReader, CompoundMetadataRegistry, DirectiveMeta, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, MetadataReader, PipeMeta, ResourceRegistry} from '../../metadata';
import {PartialEvaluator} from '../../partial_evaluator';
import {ActivePerfRecorder, DelegatingPerfRecorder, PerfCheckpoint, PerfEvent, PerfPhase} from '../../perf';
import {FileUpdate, ProgramDriver, UpdateMode} from '../../program_driver';
@@ -959,8 +958,6 @@ export class NgCompiler {
aliasingHost = new UnifiedModulesAliasingHost(this.adapter.unifiedModulesHost);
}
const isCore = isAngularCorePackage(this.inputProgram);
const evaluator =
new PartialEvaluator(reflector, checker, this.incrementalCompilation.depGraph);
const dtsReader = new DtsMetadataReader(checker, reflector);
@@ -976,7 +973,7 @@ export class NgCompiler {
new CompoundComponentScopeReader([ngModuleScopeRegistry, standaloneScopeReader]);
const semanticDepGraphUpdater = this.incrementalCompilation.semanticDepGraphUpdater;
const metaRegistry = new CompoundMetadataRegistry([localMetaRegistry, ngModuleScopeRegistry]);
const injectableRegistry = new InjectableClassRegistry(reflector, isCore);
const injectableRegistry = new InjectableClassRegistry(reflector);
const typeCheckScopeRegistry = new TypeCheckScopeRegistry(scopeReader, metaReader);
@@ -995,6 +992,8 @@ export class NgCompiler {
const dtsTransforms = new DtsTransformRegistry();
const isCore = isAngularCorePackage(this.inputProgram);
const resourceRegistry = new ResourceRegistry();
// Note: If this compilation builds `@angular/core`, we always build in full compilation
@@ -1011,13 +1010,11 @@ export class NgCompiler {
CycleHandlingStrategy.UseRemoteScoping :
CycleHandlingStrategy.Error;
const strictCtorDeps = this.options.strictInjectionParameters || false;
// Set up the IvyCompilation, which manages state for the Ivy transformer.
const handlers: DecoratorHandler<unknown, unknown, SemanticSymbol|null, unknown>[] = [
new ComponentDecoratorHandler(
reflector, evaluator, metaRegistry, metaReader, scopeReader, depScopeReader,
ngModuleScopeRegistry, typeCheckScopeRegistry, resourceRegistry, isCore, strictCtorDeps,
ngModuleScopeRegistry, typeCheckScopeRegistry, resourceRegistry, isCore,
this.resourceManager, this.adapter.rootDirs, this.options.preserveWhitespaces || false,
this.options.i18nUseExternalIds !== false,
this.options.enableI18nLegacyMessageIdFormat !== false, this.usePoisonedData,
@@ -1031,7 +1028,7 @@ export class NgCompiler {
// clang-format off
new DirectiveDecoratorHandler(
reflector, evaluator, metaRegistry, ngModuleScopeRegistry, metaReader,
injectableRegistry, isCore, strictCtorDeps, semanticDepGraphUpdater,
injectableRegistry, isCore, semanticDepGraphUpdater,
this.closureCompilerEnabled, /** compileUndecoratedClassesWithAngularFeatures */ false,
this.delegatingPerfRecorder,
) as Readonly<DecoratorHandler<unknown, unknown, SemanticSymbol | null,unknown>>,
@@ -1042,7 +1039,7 @@ export class NgCompiler {
reflector, evaluator, metaRegistry, ngModuleScopeRegistry, injectableRegistry, isCore,
this.delegatingPerfRecorder),
new InjectableDecoratorHandler(
reflector, evaluator, isCore, strictCtorDeps, injectableRegistry,
reflector, isCore, this.options.strictInjectionParameters || false, injectableRegistry,
this.delegatingPerfRecorder),
new NgModuleDecoratorHandler(
reflector, evaluator, metaReader, metaRegistry, ngModuleScopeRegistry, referencesRegistry,
@@ -72,12 +72,6 @@ export enum ErrorCode {
*/
COMPONENT_UNKNOWN_IMPORT = 2012,
/**
* Raised when a type with Angular decorator inherits its constructor from a base class
* which has a constructor that is incompatible with Angular DI.
*/
INJECTABLE_INHERITS_INVALID_CONSTRUCTOR = 2013,
SYMBOL_NOT_EXPORTED = 3001,
SYMBOL_EXPORTED_UNDER_DIFFERENT_NAME = 3002,
/**
@@ -9,7 +9,7 @@
export * from './src/api';
export {DtsMetadataReader} from './src/dts';
export {flattenInheritedDirectiveMetadata} from './src/inheritance';
export {CompoundMetadataRegistry, LocalMetadataRegistry} from './src/registry';
export {CompoundMetadataRegistry, LocalMetadataRegistry, InjectableClassRegistry} from './src/registry';
export {ResourceRegistry, Resource, ComponentResources, isExternalResource, ExternalResource} from './src/resource_registry';
export {extractDirectiveTypeCheckMeta, hasInjectableFields, CompoundMetadataReader} from './src/util';
export {extractDirectiveTypeCheckMeta, CompoundMetadataReader} from './src/util';
export {BindingPropertyName, ClassPropertyMapping, ClassPropertyName, InputOrOutput} from './src/property_mapping';
@@ -7,9 +7,10 @@
*/
import {Reference} from '../../imports';
import {ClassDeclaration} from '../../reflection';
import {ClassDeclaration, ReflectionHost} from '../../reflection';
import {DirectiveMeta, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from './api';
import {hasInjectableFields} from './util';
/**
* A registry of directive, pipe, and module metadata for types defined in the current compilation
@@ -65,3 +66,24 @@ export class CompoundMetadataRegistry implements MetadataRegistry {
}
}
}
/**
* Registry that keeps track of classes that can be constructed via dependency injection (e.g.
* injectables, directives, pipes).
*/
export class InjectableClassRegistry {
private classes = new Set<ClassDeclaration>();
constructor(private host: ReflectionHost) {}
registerInjectable(declaration: ClassDeclaration): void {
this.classes.add(declaration);
}
isInjectable(declaration: ClassDeclaration): boolean {
// Figure out whether the class is injectable based on the registered classes, otherwise
// fall back to looking at its members since we might not have been able register the class
// if it was compiled already.
return this.classes.has(declaration) || hasInjectableFields(declaration, this.host);
}
}
@@ -2672,234 +2672,6 @@ function allTests(os: string) {
expect(jsContents)
.toMatch(/function Test_Factory\(t\) { i0\.ɵɵinvalidFactory\(\)/ms);
});
it('should not give a compile-time error if an invalid @Injectable without providedIn is an abstract class',
() => {
env.tsconfig({strictInjectionParameters: true});
env.write('test.ts', `
import {Injectable} from '@angular/core';
@Injectable()
export abstract class Test {
constructor(private notInjectable: string) {}
}
`);
env.driveMain();
const jsContents = env.getContents('test.js');
expect(jsContents)
.toMatch(/function Test_Factory\(t\) { i0\.ɵɵinvalidFactory\(\)/ms);
});
it('should not give a compile-time error if an invalid @Injectable with providedIn is an abstract class',
() => {
env.tsconfig({strictInjectionParameters: true});
env.write('test.ts', `
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root',
})
export abstract class Test {
constructor(private notInjectable: string) {}
}
`);
env.driveMain();
const jsContents = env.getContents('test.js');
expect(jsContents)
.toMatch(/function Test_Factory\(t\) { i0\.ɵɵinvalidFactory\(\)/ms);
});
it('should give a compile-time error when a derived Directive inherits an invalid constructor', () => {
env.tsconfig({strictInjectionParameters: true});
env.write('test.ts', `
import {Directive} from '@angular/core';
@Directive()
export abstract class ParentDir {
constructor(private notInjectable: string) {}
}
@Directive()
export abstract class AbstractMiddleDir extends ParentDir {}
@Directive()
export class ConcreteMiddleDir extends AbstractMiddleDir {}
@Directive()
export class ConcreteDirWithoutCtor extends ConcreteMiddleDir {}
@Directive()
export class ConcreteDirWithCtor extends ConcreteMiddleDir {
constructor() {
super('correct');
}
}
@Directive()
export class ConcreteDerivedDirWithoutCtor extends ConcreteDirWithCtor {}
`);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(2);
expect(diags[0].code)
.toBe(ngErrorCode(ErrorCode.INJECTABLE_INHERITS_INVALID_CONSTRUCTOR));
expect(diags[0].messageText)
.toEqual(
`The directive ConcreteMiddleDir inherits its constructor from ParentDir, but the latter has ` +
`a constructor parameter that is not compatible with dependency injection. Either add an explicit ` +
`constructor to ConcreteMiddleDir or change ParentDir's constructor to use parameters that are ` +
`valid for DI.`);
expect(getDiagnosticSourceCode(diags[0])).toBe('ConcreteMiddleDir');
expect(diags[1].code)
.toBe(ngErrorCode(ErrorCode.INJECTABLE_INHERITS_INVALID_CONSTRUCTOR));
expect(diags[1].messageText)
.toEqual(
`The directive ConcreteDirWithoutCtor inherits its constructor from ParentDir, but the latter ` +
`has a constructor parameter that is not compatible with dependency injection. Either add an ` +
`explicit constructor to ConcreteDirWithoutCtor or change ParentDir's constructor to use ` +
`parameters that are valid for DI.`);
expect(getDiagnosticSourceCode(diags[1])).toBe('ConcreteDirWithoutCtor');
});
it('should give a compile-time error when a derived Injectable inherits an invalid constructor', () => {
env.tsconfig({strictInjectionParameters: true});
env.write('test.ts', `
import {Injectable} from '@angular/core';
@Injectable()
export abstract class ParentService {
constructor(private notInjectable: string) {}
}
@Injectable()
export abstract class AbstractMiddleService extends ParentService {}
@Injectable()
export class ConcreteMiddleService extends AbstractMiddleService {}
@Injectable()
export class ConcreteServiceWithoutCtor extends ConcreteMiddleService {}
@Injectable()
export class ConcreteServiceWithCtor extends ConcreteMiddleService {
constructor() {
super('correct');
}
}
@Injectable()
export class ConcreteDerivedServiceWithoutCtor extends ConcreteServiceWithCtor {}
@Injectable({ providedIn: 'root' })
export class ProvidedInRootService extends ParentService {}
@Injectable({ providedIn: 'root', useFactory: () => null })
export class UseFactoryService extends ParentService {}
@Injectable({ providedIn: 'root', useValue: null })
export class UseValueService extends ParentService {}
@Injectable({ providedIn: 'root', useClass: ConcreteServiceWithCtor })
export class UseClassService extends ParentService {}
@Injectable({ providedIn: 'root', useExisting: ConcreteServiceWithCtor })
export class UseExistingService extends ParentService {}
`);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(3);
expect(diags[0].code)
.toBe(ngErrorCode(ErrorCode.INJECTABLE_INHERITS_INVALID_CONSTRUCTOR));
expect(diags[0].messageText)
.toEqual(
`The injectable ConcreteMiddleService inherits its constructor from ParentService, but the ` +
`latter has a constructor parameter that is not compatible with dependency injection. Either add ` +
`an explicit constructor to ConcreteMiddleService or change ParentService's constructor to use ` +
`parameters that are valid for DI.`);
expect(getDiagnosticSourceCode(diags[0])).toBe('ConcreteMiddleService');
expect(diags[1].code)
.toBe(ngErrorCode(ErrorCode.INJECTABLE_INHERITS_INVALID_CONSTRUCTOR));
expect(diags[1].messageText)
.toEqual(
`The injectable ConcreteServiceWithoutCtor inherits its constructor from ParentService, but the ` +
`latter has a constructor parameter that is not compatible with dependency injection. Either add ` +
`an explicit constructor to ConcreteServiceWithoutCtor or change ParentService's constructor to ` +
`use parameters that are valid for DI.`);
expect(getDiagnosticSourceCode(diags[1])).toBe('ConcreteServiceWithoutCtor');
expect(diags[2].code)
.toBe(ngErrorCode(ErrorCode.INJECTABLE_INHERITS_INVALID_CONSTRUCTOR));
expect(diags[2].messageText)
.toEqual(
`The injectable ProvidedInRootService inherits its constructor from ParentService, but the ` +
`latter has a constructor parameter that is not compatible with dependency injection. Either add ` +
`an explicit constructor to ProvidedInRootService or change ParentService's constructor to use ` +
`parameters that are valid for DI.`);
expect(getDiagnosticSourceCode(diags[2])).toBe('ProvidedInRootService');
});
it('should give a compile-time error when a derived Directive inherits from a non-decorated class', () => {
env.tsconfig({strictInjectionParameters: true});
env.write('test.ts', `
import {Directive} from '@angular/core';
export abstract class ParentClass {
constructor(private notInjectable: string) {}
}
@Directive()
export abstract class AbstractMiddleDir extends ParentClass {}
@Directive()
export class ConcreteMiddleDir extends AbstractMiddleDir {}
@Directive()
export class ConcreteDirWithoutCtor extends ConcreteMiddleDir {}
@Directive()
export class ConcreteDirWithCtor extends ConcreteMiddleDir {
constructor() {
super('correct');
}
}
@Directive()
export class ConcreteDerivedDirWithoutCtor extends ConcreteDirWithCtor {}
`);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(3);
expect(diags[0].code).toBe(ngErrorCode(ErrorCode.DIRECTIVE_INHERITS_UNDECORATED_CTOR));
expect(diags[0].messageText)
.toEqual(
`The directive AbstractMiddleDir inherits its constructor from ParentClass, but the latter ` +
`does not have an Angular decorator of its own. Dependency injection will not be able to resolve ` +
`the parameters of ParentClass's constructor. Either add a @Directive decorator to ParentClass, ` +
`or add an explicit constructor to AbstractMiddleDir.`);
expect(getDiagnosticSourceCode(diags[0])).toBe('AbstractMiddleDir');
expect(diags[1].code).toBe(ngErrorCode(ErrorCode.DIRECTIVE_INHERITS_UNDECORATED_CTOR));
expect(diags[1].messageText)
.toEqual(
`The directive ConcreteMiddleDir inherits its constructor from ParentClass, but the latter ` +
`does not have an Angular decorator of its own. Dependency injection will not be able to resolve ` +
`the parameters of ParentClass's constructor. Either add a @Directive decorator to ParentClass, or ` +
`add an explicit constructor to ConcreteMiddleDir.`);
expect(getDiagnosticSourceCode(diags[1])).toBe('ConcreteMiddleDir');
expect(diags[2].code).toBe(ngErrorCode(ErrorCode.DIRECTIVE_INHERITS_UNDECORATED_CTOR));
expect(diags[2].messageText)
.toEqual(
`The directive ConcreteDirWithoutCtor inherits its constructor from ParentClass, but the latter ` +
`does not have an Angular decorator of its own. Dependency injection will not be able to resolve ` +
`the parameters of ParentClass's constructor. Either add a @Directive decorator to ParentClass, ` +
`or add an explicit constructor to ConcreteDirWithoutCtor.`);
expect(getDiagnosticSourceCode(diags[2])).toBe('ConcreteDirWithoutCtor');
});
});
describe('with strictInjectionParameters = false', () => {
@@ -2936,148 +2708,6 @@ function allTests(os: string) {
expect(jsContents)
.toContain('Test.ɵfac = function Test_Factory(t) { i0.ɵɵinvalidFactory()');
});
it('should compile when a derived Directive inherits an invalid constructor', () => {
env.tsconfig({strictInjectionParameters: false});
env.write('test.ts', `
import {Directive} from '@angular/core';
@Directive()
export abstract class ParentDir {
constructor(private notInjectable: string) {}
}
@Directive()
export abstract class AbstractMiddleDir extends ParentDir {}
@Directive()
export class ConcreteMiddleDir extends AbstractMiddleDir {}
@Directive()
export class ConcreteDirWithoutCtor extends ConcreteMiddleDir {}
@Directive()
export class ConcreteDirWithCtor extends ConcreteMiddleDir {
constructor() {
super('correct');
}
}
@Directive()
export class ConcreteDerivedDirWithoutCtor extends ConcreteDirWithCtor {}
`);
env.driveMain();
});
it('should compile when a derived Injectable inherits an invalid constructor', () => {
env.tsconfig({strictInjectionParameters: false});
env.write('test.ts', `
import {Injectable} from '@angular/core';
@Injectable()
export abstract class ParentService {
constructor(private notInjectable: string) {}
}
@Injectable()
export abstract class AbstractMiddleService extends ParentService {}
@Injectable()
export class ConcreteMiddleService extends AbstractMiddleService {}
@Injectable()
export class ConcreteServiceWithoutCtor extends ConcreteMiddleService {}
@Injectable()
export class ConcreteServiceWithCtor extends ConcreteMiddleService {
constructor() {
super('correct');
}
}
@Injectable()
export class ConcreteDerivedServiceWithoutCtor extends ConcreteServiceWithCtor {}
@Injectable({ providedIn: 'root' })
export class ProvidedInRootService extends ParentService {}
@Injectable({ providedIn: 'root', useFactory: () => null })
export class UseFactoryService extends ParentService {}
@Injectable({ providedIn: 'root', useValue: null })
export class UseValueService extends ParentService {}
@Injectable({ providedIn: 'root', useClass: ConcreteServiceWithCtor })
export class UseClassService extends ParentService {}
@Injectable({ providedIn: 'root', useExisting: ConcreteServiceWithCtor })
export class UseExistingService extends ParentService {}
`);
env.driveMain();
});
it('should give a compile-time error when a derived Directive inherits from a non-decorated class', () => {
// Errors for undecorated base classes should always be reported, even under
// `strictInjectionParameters`.
env.tsconfig({strictInjectionParameters: false});
env.write('test.ts', `
import {Directive} from '@angular/core';
export abstract class ParentClass {
constructor(private notInjectable: string) {}
}
@Directive()
export abstract class AbstractMiddleDir extends ParentClass {}
@Directive()
export class ConcreteMiddleDir extends AbstractMiddleDir {}
@Directive()
export class ConcreteDirWithoutCtor extends ConcreteMiddleDir {}
@Directive()
export class ConcreteDirWithCtor extends ConcreteMiddleDir {
constructor() {
super('correct');
}
}
@Directive()
export class ConcreteDerivedDirWithoutCtor extends ConcreteDirWithCtor {}
`);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(3);
expect(diags[0].code).toBe(ngErrorCode(ErrorCode.DIRECTIVE_INHERITS_UNDECORATED_CTOR));
expect(diags[0].messageText)
.toEqual(
`The directive AbstractMiddleDir inherits its constructor from ParentClass, but the latter ` +
`does not have an Angular decorator of its own. Dependency injection will not be able to resolve ` +
`the parameters of ParentClass's constructor. Either add a @Directive decorator to ParentClass, ` +
`or add an explicit constructor to AbstractMiddleDir.`);
expect(getDiagnosticSourceCode(diags[0])).toBe('AbstractMiddleDir');
expect(diags[1].code).toBe(ngErrorCode(ErrorCode.DIRECTIVE_INHERITS_UNDECORATED_CTOR));
expect(diags[1].messageText)
.toEqual(
`The directive ConcreteMiddleDir inherits its constructor from ParentClass, but the latter ` +
`does not have an Angular decorator of its own. Dependency injection will not be able to resolve ` +
`the parameters of ParentClass's constructor. Either add a @Directive decorator to ParentClass, ` +
`or add an explicit constructor to ConcreteMiddleDir.`);
expect(getDiagnosticSourceCode(diags[1])).toBe('ConcreteMiddleDir');
expect(diags[2].code).toBe(ngErrorCode(ErrorCode.DIRECTIVE_INHERITS_UNDECORATED_CTOR));
expect(diags[2].messageText)
.toEqual(
`The directive ConcreteDirWithoutCtor inherits its constructor from ParentClass, but the latter ` +
`does not have an Angular decorator of its own. Dependency injection will not be able to resolve ` +
`the parameters of ParentClass's constructor. Either add a @Directive decorator to ParentClass, ` +
`or add an explicit constructor to ConcreteDirWithoutCtor.`);
expect(getDiagnosticSourceCode(diags[2])).toBe('ConcreteDirWithoutCtor');
});
});
});