feat(ivy): throw compilation error when providing undecorated classes (#34460)
Adds a compilation error if the consumer tries to pass in an undecorated class into the `providers` of an `NgModule`, or the `providers`/`viewProviders` arrays of a `Directive`/`Component`. PR Close #34460
This commit is contained in:
parent
6057c7a373
commit
dcc8ff4ce7
@ -7,12 +7,13 @@
|
|||||||
*/
|
*/
|
||||||
import {ConstantPool} from '@angular/compiler';
|
import {ConstantPool} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ReferencesRegistry, ResourceLoader} from '../../../src/ngtsc/annotations';
|
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ReferencesRegistry, ResourceLoader} from '../../../src/ngtsc/annotations';
|
||||||
import {CycleAnalyzer, ImportGraph} from '../../../src/ngtsc/cycles';
|
import {CycleAnalyzer, ImportGraph} from '../../../src/ngtsc/cycles';
|
||||||
import {isFatalDiagnosticError} from '../../../src/ngtsc/diagnostics';
|
import {isFatalDiagnosticError} from '../../../src/ngtsc/diagnostics';
|
||||||
import {FileSystem, LogicalFileSystem, absoluteFrom, dirname, resolve} from '../../../src/ngtsc/file_system';
|
import {FileSystem, LogicalFileSystem, absoluteFrom, dirname, resolve} from '../../../src/ngtsc/file_system';
|
||||||
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, PrivateExportAliasingHost, Reexport, ReferenceEmitter} from '../../../src/ngtsc/imports';
|
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, PrivateExportAliasingHost, Reexport, ReferenceEmitter} from '../../../src/ngtsc/imports';
|
||||||
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, LocalMetadataRegistry} from '../../../src/ngtsc/metadata';
|
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../../src/ngtsc/metadata';
|
||||||
import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
|
import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
|
||||||
import {ClassDeclaration} from '../../../src/ngtsc/reflection';
|
import {ClassDeclaration} from '../../../src/ngtsc/reflection';
|
||||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../src/ngtsc/scope';
|
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../src/ngtsc/scope';
|
||||||
@ -30,6 +31,7 @@ import {AnalyzedClass, AnalyzedFile, CompiledClass, CompiledFile, DecorationAnal
|
|||||||
import {NOOP_DEPENDENCY_TRACKER, analyzeDecorators, isWithinPackage} from './util';
|
import {NOOP_DEPENDENCY_TRACKER, analyzeDecorators, isWithinPackage} from './util';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple class that resolves and loads files directly from the filesystem.
|
* Simple class that resolves and loads files directly from the filesystem.
|
||||||
*/
|
*/
|
||||||
@ -85,6 +87,7 @@ export class DecorationAnalyzer {
|
|||||||
new PartialEvaluator(this.reflectionHost, this.typeChecker, /* dependencyTracker */ null);
|
new PartialEvaluator(this.reflectionHost, this.typeChecker, /* dependencyTracker */ null);
|
||||||
importGraph = new ImportGraph(this.moduleResolver);
|
importGraph = new ImportGraph(this.moduleResolver);
|
||||||
cycleAnalyzer = new CycleAnalyzer(this.importGraph);
|
cycleAnalyzer = new CycleAnalyzer(this.importGraph);
|
||||||
|
injectableRegistry = new InjectableClassRegistry(this.reflectionHost);
|
||||||
handlers: DecoratorHandler<unknown, unknown, unknown>[] = [
|
handlers: DecoratorHandler<unknown, unknown, unknown>[] = [
|
||||||
new ComponentDecoratorHandler(
|
new ComponentDecoratorHandler(
|
||||||
this.reflectionHost, this.evaluator, this.fullRegistry, this.fullMetaReader,
|
this.reflectionHost, this.evaluator, this.fullRegistry, this.fullMetaReader,
|
||||||
@ -92,28 +95,28 @@ export class DecorationAnalyzer {
|
|||||||
/* defaultPreserveWhitespaces */ false,
|
/* defaultPreserveWhitespaces */ false,
|
||||||
/* i18nUseExternalIds */ true, this.bundle.enableI18nLegacyMessageIdFormat,
|
/* i18nUseExternalIds */ true, this.bundle.enableI18nLegacyMessageIdFormat,
|
||||||
this.moduleResolver, this.cycleAnalyzer, this.refEmitter, NOOP_DEFAULT_IMPORT_RECORDER,
|
this.moduleResolver, this.cycleAnalyzer, this.refEmitter, NOOP_DEFAULT_IMPORT_RECORDER,
|
||||||
NOOP_DEPENDENCY_TRACKER, /* annotateForClosureCompiler */ false),
|
NOOP_DEPENDENCY_TRACKER, this.injectableRegistry, /* annotateForClosureCompiler */ false),
|
||||||
// See the note in ngtsc about why this cast is needed.
|
// See the note in ngtsc about why this cast is needed.
|
||||||
// clang-format off
|
// clang-format off
|
||||||
new DirectiveDecoratorHandler(
|
new DirectiveDecoratorHandler(
|
||||||
this.reflectionHost, this.evaluator, this.fullRegistry, this.scopeRegistry,
|
this.reflectionHost, this.evaluator, this.fullRegistry, this.scopeRegistry,
|
||||||
NOOP_DEFAULT_IMPORT_RECORDER, this.isCore,
|
NOOP_DEFAULT_IMPORT_RECORDER, this.injectableRegistry, this.isCore,
|
||||||
/* annotateForClosureCompiler */ false) as DecoratorHandler<unknown, unknown, unknown>,
|
/* annotateForClosureCompiler */ false) as DecoratorHandler<unknown, unknown, unknown>,
|
||||||
// clang-format on
|
// clang-format on
|
||||||
// Pipe handler must be before injectable handler in list so pipe factories are printed
|
// Pipe handler must be before injectable handler in list so pipe factories are printed
|
||||||
// before injectable factories (so injectable factories can delegate to them)
|
// before injectable factories (so injectable factories can delegate to them)
|
||||||
new PipeDecoratorHandler(
|
new PipeDecoratorHandler(
|
||||||
this.reflectionHost, this.evaluator, this.metaRegistry, this.scopeRegistry,
|
this.reflectionHost, this.evaluator, this.metaRegistry, this.scopeRegistry,
|
||||||
NOOP_DEFAULT_IMPORT_RECORDER, this.isCore),
|
NOOP_DEFAULT_IMPORT_RECORDER, this.injectableRegistry, this.isCore),
|
||||||
new InjectableDecoratorHandler(
|
new InjectableDecoratorHandler(
|
||||||
this.reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, this.isCore,
|
this.reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, this.isCore,
|
||||||
/* strictCtorDeps */ false, /* errorOnDuplicateProv */ false),
|
/* strictCtorDeps */ false, this.injectableRegistry, /* errorOnDuplicateProv */ false),
|
||||||
new NgModuleDecoratorHandler(
|
new NgModuleDecoratorHandler(
|
||||||
this.reflectionHost, this.evaluator, this.fullMetaReader, this.fullRegistry,
|
this.reflectionHost, this.evaluator, this.fullMetaReader, this.fullRegistry,
|
||||||
this.scopeRegistry, this.referencesRegistry, this.isCore, /* routeAnalyzer */ null,
|
this.scopeRegistry, this.referencesRegistry, this.isCore, /* routeAnalyzer */ null,
|
||||||
this.refEmitter,
|
this.refEmitter,
|
||||||
/* factoryTracker */ null, NOOP_DEFAULT_IMPORT_RECORDER,
|
/* factoryTracker */ null, NOOP_DEFAULT_IMPORT_RECORDER,
|
||||||
/* annotateForClosureCompiler */ false),
|
/* annotateForClosureCompiler */ false, this.injectableRegistry),
|
||||||
];
|
];
|
||||||
migrations: Migration[] = [
|
migrations: Migration[] = [
|
||||||
new UndecoratedParentMigration(),
|
new UndecoratedParentMigration(),
|
||||||
|
@ -15,7 +15,7 @@ import {absoluteFrom, relative} from '../../file_system';
|
|||||||
import {DefaultImportRecorder, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
|
import {DefaultImportRecorder, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
|
||||||
import {DependencyTracker} from '../../incremental/api';
|
import {DependencyTracker} from '../../incremental/api';
|
||||||
import {IndexingContext} from '../../indexer';
|
import {IndexingContext} from '../../indexer';
|
||||||
import {DirectiveMeta, MetadataReader, MetadataRegistry, extractDirectiveGuards} from '../../metadata';
|
import {DirectiveMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, extractDirectiveGuards} from '../../metadata';
|
||||||
import {flattenInheritedDirectiveMetadata} from '../../metadata/src/inheritance';
|
import {flattenInheritedDirectiveMetadata} from '../../metadata/src/inheritance';
|
||||||
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
||||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||||
@ -26,10 +26,11 @@ import {tsSourceMapBug29300Fixed} from '../../util/src/ts_source_map_bug_29300';
|
|||||||
import {SubsetOfKeys} from '../../util/src/typescript';
|
import {SubsetOfKeys} from '../../util/src/typescript';
|
||||||
|
|
||||||
import {ResourceLoader} from './api';
|
import {ResourceLoader} from './api';
|
||||||
|
import {getProviderDiagnostics} from './diagnostics';
|
||||||
import {extractDirectiveMetadata, parseFieldArrayValue} from './directive';
|
import {extractDirectiveMetadata, parseFieldArrayValue} from './directive';
|
||||||
import {compileNgFactoryDefField} from './factory';
|
import {compileNgFactoryDefField} from './factory';
|
||||||
import {generateSetClassMetadataCall} from './metadata';
|
import {generateSetClassMetadataCall} from './metadata';
|
||||||
import {findAngularDecorator, isAngularCoreReference, isExpressionForwardReference, makeDuplicateDeclarationError, readBaseClass, unwrapExpression, wrapFunctionExpressionsInParens} from './util';
|
import {findAngularDecorator, isAngularCoreReference, isExpressionForwardReference, makeDuplicateDeclarationError, readBaseClass, resolveProvidersRequiringFactory, unwrapExpression, wrapFunctionExpressionsInParens} from './util';
|
||||||
|
|
||||||
const EMPTY_MAP = new Map<string, Expression>();
|
const EMPTY_MAP = new Map<string, Expression>();
|
||||||
const EMPTY_ARRAY: any[] = [];
|
const EMPTY_ARRAY: any[] = [];
|
||||||
@ -53,6 +54,18 @@ export interface ComponentAnalysisData {
|
|||||||
guards: ReturnType<typeof extractDirectiveGuards>;
|
guards: ReturnType<typeof extractDirectiveGuards>;
|
||||||
template: ParsedTemplateWithSource;
|
template: ParsedTemplateWithSource;
|
||||||
metadataStmt: Statement|null;
|
metadataStmt: Statement|null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Providers extracted from the `providers` field of the component annotation which will require
|
||||||
|
* an Angular factory definition at runtime.
|
||||||
|
*/
|
||||||
|
providersRequiringFactory: Set<Reference<ClassDeclaration>>|null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Providers extracted from the `viewProviders` field of the component annotation which will
|
||||||
|
* require an Angular factory definition at runtime.
|
||||||
|
*/
|
||||||
|
viewProvidersRequiringFactory: Set<Reference<ClassDeclaration>>|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ComponentResolutionData = Pick<R3ComponentMetadata, ComponentMetadataResolvedFields>;
|
export type ComponentResolutionData = Pick<R3ComponentMetadata, ComponentMetadataResolvedFields>;
|
||||||
@ -71,7 +84,9 @@ export class ComponentDecoratorHandler implements
|
|||||||
private enableI18nLegacyMessageIdFormat: boolean, private moduleResolver: ModuleResolver,
|
private enableI18nLegacyMessageIdFormat: boolean, private moduleResolver: ModuleResolver,
|
||||||
private cycleAnalyzer: CycleAnalyzer, private refEmitter: ReferenceEmitter,
|
private cycleAnalyzer: CycleAnalyzer, private refEmitter: ReferenceEmitter,
|
||||||
private defaultImportRecorder: DefaultImportRecorder,
|
private defaultImportRecorder: DefaultImportRecorder,
|
||||||
private depTracker: DependencyTracker|null, private annotateForClosureCompiler: boolean) {}
|
private depTracker: DependencyTracker|null,
|
||||||
|
private injectableRegistry: InjectableClassRegistry,
|
||||||
|
private annotateForClosureCompiler: boolean) {}
|
||||||
|
|
||||||
private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
|
private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
|
||||||
private elementSchemaRegistry = new DomElementSchemaRegistry();
|
private elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||||
@ -186,12 +201,27 @@ export class ComponentDecoratorHandler implements
|
|||||||
}
|
}
|
||||||
}, undefined) !;
|
}, undefined) !;
|
||||||
|
|
||||||
const viewProviders: Expression|null = component.has('viewProviders') ?
|
|
||||||
new WrappedNodeExpr(
|
// Note that we could technically combine the `viewProvidersRequiringFactory` and
|
||||||
this.annotateForClosureCompiler ?
|
// `providersRequiringFactory` into a single set, but we keep the separate so that
|
||||||
wrapFunctionExpressionsInParens(component.get('viewProviders') !) :
|
// we can distinguish where an error is coming from when logging the diagnostics in `resolve`.
|
||||||
component.get('viewProviders') !) :
|
let viewProvidersRequiringFactory: Set<Reference<ClassDeclaration>>|null = null;
|
||||||
null;
|
let providersRequiringFactory: Set<Reference<ClassDeclaration>>|null = null;
|
||||||
|
let wrappedViewProviders: Expression|null = null;
|
||||||
|
|
||||||
|
if (component.has('viewProviders')) {
|
||||||
|
const viewProviders = component.get('viewProviders') !;
|
||||||
|
viewProvidersRequiringFactory =
|
||||||
|
resolveProvidersRequiringFactory(viewProviders, this.reflector, this.evaluator);
|
||||||
|
wrappedViewProviders = new WrappedNodeExpr(
|
||||||
|
this.annotateForClosureCompiler ? wrapFunctionExpressionsInParens(viewProviders) :
|
||||||
|
viewProviders);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.has('providers')) {
|
||||||
|
providersRequiringFactory = resolveProvidersRequiringFactory(
|
||||||
|
component.get('providers') !, this.reflector, this.evaluator);
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the template.
|
// Parse the template.
|
||||||
// If a preanalyze phase was executed, the template may already exist in parsed form, so check
|
// If a preanalyze phase was executed, the template may already exist in parsed form, so check
|
||||||
@ -290,7 +320,7 @@ export class ComponentDecoratorHandler implements
|
|||||||
// These will be replaced during the compilation step, after all `NgModule`s have been
|
// These will be replaced during the compilation step, after all `NgModule`s have been
|
||||||
// analyzed and the full compilation scope for the component can be realized.
|
// analyzed and the full compilation scope for the component can be realized.
|
||||||
animations,
|
animations,
|
||||||
viewProviders,
|
viewProviders: wrappedViewProviders,
|
||||||
i18nUseExternalIds: this.i18nUseExternalIds, relativeContextFilePath,
|
i18nUseExternalIds: this.i18nUseExternalIds, relativeContextFilePath,
|
||||||
},
|
},
|
||||||
guards: extractDirectiveGuards(node, this.reflector),
|
guards: extractDirectiveGuards(node, this.reflector),
|
||||||
@ -298,6 +328,8 @@ export class ComponentDecoratorHandler implements
|
|||||||
node, this.reflector, this.defaultImportRecorder, this.isCore,
|
node, this.reflector, this.defaultImportRecorder, this.isCore,
|
||||||
this.annotateForClosureCompiler),
|
this.annotateForClosureCompiler),
|
||||||
template,
|
template,
|
||||||
|
providersRequiringFactory,
|
||||||
|
viewProvidersRequiringFactory,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (changeDetection !== null) {
|
if (changeDetection !== null) {
|
||||||
@ -321,6 +353,8 @@ export class ComponentDecoratorHandler implements
|
|||||||
isComponent: true,
|
isComponent: true,
|
||||||
baseClass: analysis.baseClass, ...analysis.guards,
|
baseClass: analysis.baseClass, ...analysis.guards,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.injectableRegistry.registerInjectable(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
index(
|
index(
|
||||||
@ -395,14 +429,6 @@ export class ComponentDecoratorHandler implements
|
|||||||
|
|
||||||
resolve(node: ClassDeclaration, analysis: Readonly<ComponentAnalysisData>):
|
resolve(node: ClassDeclaration, analysis: Readonly<ComponentAnalysisData>):
|
||||||
ResolveResult<ComponentResolutionData> {
|
ResolveResult<ComponentResolutionData> {
|
||||||
const duplicateDeclData = this.scopeRegistry.getDuplicateDeclarations(node);
|
|
||||||
if (duplicateDeclData !== null) {
|
|
||||||
// This component was declared twice (or more).
|
|
||||||
return {
|
|
||||||
diagnostics: [makeDuplicateDeclarationError(node, duplicateDeclData, 'Component')],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const context = node.getSourceFile();
|
const context = node.getSourceFile();
|
||||||
// Check whether this component was registered with an NgModule. If so, it should be compiled
|
// Check whether this component was registered with an NgModule. If so, it should be compiled
|
||||||
// under that module's compilation scope.
|
// under that module's compilation scope.
|
||||||
@ -505,6 +531,34 @@ export class ComponentDecoratorHandler implements
|
|||||||
this.scopeRegistry.setComponentAsRequiringRemoteScoping(node);
|
this.scopeRegistry.setComponentAsRequiringRemoteScoping(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const diagnostics: ts.Diagnostic[] = [];
|
||||||
|
|
||||||
|
if (analysis.providersRequiringFactory !== null &&
|
||||||
|
analysis.meta.providers instanceof WrappedNodeExpr) {
|
||||||
|
const providerDiagnostics = getProviderDiagnostics(
|
||||||
|
analysis.providersRequiringFactory, analysis.meta.providers !.node,
|
||||||
|
this.injectableRegistry);
|
||||||
|
diagnostics.push(...providerDiagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (analysis.viewProvidersRequiringFactory !== null &&
|
||||||
|
analysis.meta.viewProviders instanceof WrappedNodeExpr) {
|
||||||
|
const viewProviderDiagnostics = getProviderDiagnostics(
|
||||||
|
analysis.viewProvidersRequiringFactory, analysis.meta.viewProviders !.node,
|
||||||
|
this.injectableRegistry);
|
||||||
|
diagnostics.push(...viewProviderDiagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
const duplicateDeclData = this.scopeRegistry.getDuplicateDeclarations(node);
|
||||||
|
if (duplicateDeclData !== null) {
|
||||||
|
diagnostics.push(makeDuplicateDeclarationError(node, duplicateDeclData, 'Component'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diagnostics.length > 0) {
|
||||||
|
return {diagnostics};
|
||||||
|
}
|
||||||
|
|
||||||
return {data};
|
return {data};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. 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 * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ErrorCode, makeDiagnostic} from '../../diagnostics';
|
||||||
|
import {Reference} from '../../imports';
|
||||||
|
import {InjectableClassRegistry} from '../../metadata';
|
||||||
|
import {ClassDeclaration} from '../../reflection';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the diagnostics for a set of provider classes.
|
||||||
|
* @param providerClasses Classes that should be checked.
|
||||||
|
* @param providersDeclaration Node that declares the providers array.
|
||||||
|
* @param registry Registry that keeps track of the registered injectable classes.
|
||||||
|
*/
|
||||||
|
export function getProviderDiagnostics(
|
||||||
|
providerClasses: Set<Reference<ClassDeclaration>>, providersDeclaration: ts.Expression,
|
||||||
|
registry: InjectableClassRegistry): ts.Diagnostic[] {
|
||||||
|
const diagnostics: ts.Diagnostic[] = [];
|
||||||
|
|
||||||
|
for (const provider of providerClasses) {
|
||||||
|
if (registry.isInjectable(provider.node)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextNode = provider.getOriginForDiagnostics(providersDeclaration);
|
||||||
|
diagnostics.push(makeDiagnostic(
|
||||||
|
ErrorCode.UNDECORATED_PROVIDER, contextNode,
|
||||||
|
`The class '${provider.node.name.text}' cannot be created via dependency injection, as it does not have an Angular decorator. This will result in an error at runtime.
|
||||||
|
|
||||||
|
Either add the @Injectable() decorator to '${provider.node.name.text}', or configure a different provider (such as a provider with 'useFactory').
|
||||||
|
`,
|
||||||
|
[{node: provider.node, messageText: `'${provider.node.name.text}' is declared here.`}]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return diagnostics;
|
||||||
|
}
|
@ -11,16 +11,17 @@ import * as ts from 'typescript';
|
|||||||
|
|
||||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
import {DefaultImportRecorder, Reference} from '../../imports';
|
import {DefaultImportRecorder, Reference} from '../../imports';
|
||||||
import {MetadataRegistry} from '../../metadata';
|
import {InjectableClassRegistry, MetadataRegistry} from '../../metadata';
|
||||||
import {extractDirectiveGuards} from '../../metadata/src/util';
|
import {extractDirectiveGuards} from '../../metadata/src/util';
|
||||||
import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
||||||
import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, ReflectionHost, filterToMembersWithDecorator, reflectObjectLiteral} from '../../reflection';
|
import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, ReflectionHost, filterToMembersWithDecorator, reflectObjectLiteral} from '../../reflection';
|
||||||
import {LocalModuleScopeRegistry} from '../../scope';
|
import {LocalModuleScopeRegistry} from '../../scope';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
|
||||||
|
|
||||||
|
import {getProviderDiagnostics} from './diagnostics';
|
||||||
import {compileNgFactoryDefField} from './factory';
|
import {compileNgFactoryDefField} from './factory';
|
||||||
import {generateSetClassMetadataCall} from './metadata';
|
import {generateSetClassMetadataCall} from './metadata';
|
||||||
import {findAngularDecorator, getConstructorDependencies, isAngularDecorator, makeDuplicateDeclarationError, readBaseClass, unwrapConstructorDependencies, unwrapExpression, unwrapForwardRef, validateConstructorDependencies, wrapFunctionExpressionsInParens, wrapTypeReference} from './util';
|
import {findAngularDecorator, getConstructorDependencies, isAngularDecorator, makeDuplicateDeclarationError, readBaseClass, resolveProvidersRequiringFactory, unwrapConstructorDependencies, unwrapExpression, unwrapForwardRef, validateConstructorDependencies, wrapFunctionExpressionsInParens, wrapTypeReference} from './util';
|
||||||
|
|
||||||
const EMPTY_OBJECT: {[key: string]: string} = {};
|
const EMPTY_OBJECT: {[key: string]: string} = {};
|
||||||
const FIELD_DECORATORS = [
|
const FIELD_DECORATORS = [
|
||||||
@ -37,13 +38,16 @@ export interface DirectiveHandlerData {
|
|||||||
guards: ReturnType<typeof extractDirectiveGuards>;
|
guards: ReturnType<typeof extractDirectiveGuards>;
|
||||||
meta: R3DirectiveMetadata;
|
meta: R3DirectiveMetadata;
|
||||||
metadataStmt: Statement|null;
|
metadataStmt: Statement|null;
|
||||||
|
providersRequiringFactory: Set<Reference<ClassDeclaration>>|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DirectiveDecoratorHandler implements
|
export class DirectiveDecoratorHandler implements
|
||||||
DecoratorHandler<Decorator|null, DirectiveHandlerData, unknown> {
|
DecoratorHandler<Decorator|null, DirectiveHandlerData, unknown> {
|
||||||
constructor(
|
constructor(
|
||||||
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
|
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
|
||||||
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
|
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
|
||||||
private defaultImportRecorder: DefaultImportRecorder, private isCore: boolean,
|
private defaultImportRecorder: DefaultImportRecorder,
|
||||||
|
private injectableRegistry: InjectableClassRegistry, private isCore: boolean,
|
||||||
private annotateForClosureCompiler: boolean) {}
|
private annotateForClosureCompiler: boolean) {}
|
||||||
|
|
||||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||||
@ -88,6 +92,12 @@ export class DirectiveDecoratorHandler implements
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let providersRequiringFactory: Set<Reference<ClassDeclaration>>|null = null;
|
||||||
|
if (directiveResult !== undefined && directiveResult.decorator.has('providers')) {
|
||||||
|
providersRequiringFactory = resolveProvidersRequiringFactory(
|
||||||
|
directiveResult.decorator.get('providers') !, this.reflector, this.evaluator);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
analysis: {
|
analysis: {
|
||||||
meta: analysis,
|
meta: analysis,
|
||||||
@ -95,7 +105,7 @@ export class DirectiveDecoratorHandler implements
|
|||||||
node, this.reflector, this.defaultImportRecorder, this.isCore,
|
node, this.reflector, this.defaultImportRecorder, this.isCore,
|
||||||
this.annotateForClosureCompiler),
|
this.annotateForClosureCompiler),
|
||||||
baseClass: readBaseClass(node, this.reflector, this.evaluator),
|
baseClass: readBaseClass(node, this.reflector, this.evaluator),
|
||||||
guards: extractDirectiveGuards(node, this.reflector),
|
guards: extractDirectiveGuards(node, this.reflector), providersRequiringFactory
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -115,18 +125,28 @@ export class DirectiveDecoratorHandler implements
|
|||||||
isComponent: false,
|
isComponent: false,
|
||||||
baseClass: analysis.baseClass, ...analysis.guards,
|
baseClass: analysis.baseClass, ...analysis.guards,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.injectableRegistry.registerInjectable(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(node: ClassDeclaration): ResolveResult<unknown> {
|
resolve(node: ClassDeclaration, analysis: DirectiveHandlerData): ResolveResult<unknown> {
|
||||||
|
const diagnostics: ts.Diagnostic[] = [];
|
||||||
|
|
||||||
|
if (analysis.providersRequiringFactory !== null &&
|
||||||
|
analysis.meta.providers instanceof WrappedNodeExpr) {
|
||||||
|
const providerDiagnostics = getProviderDiagnostics(
|
||||||
|
analysis.providersRequiringFactory, analysis.meta.providers !.node,
|
||||||
|
this.injectableRegistry);
|
||||||
|
diagnostics.push(...providerDiagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
const duplicateDeclData = this.scopeRegistry.getDuplicateDeclarations(node);
|
const duplicateDeclData = this.scopeRegistry.getDuplicateDeclarations(node);
|
||||||
if (duplicateDeclData !== null) {
|
if (duplicateDeclData !== null) {
|
||||||
// This directive was declared twice (or more).
|
// This directive was declared twice (or more).
|
||||||
return {
|
diagnostics.push(makeDuplicateDeclarationError(node, duplicateDeclData, 'Directive'));
|
||||||
diagnostics: [makeDuplicateDeclarationError(node, duplicateDeclData, 'Directive')],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {diagnostics: diagnostics.length > 0 ? diagnostics : undefined};
|
||||||
}
|
}
|
||||||
|
|
||||||
compile(
|
compile(
|
||||||
@ -160,10 +180,8 @@ export function extractDirectiveMetadata(
|
|||||||
clazz: ClassDeclaration, decorator: Readonly<Decorator|null>, reflector: ReflectionHost,
|
clazz: ClassDeclaration, decorator: Readonly<Decorator|null>, reflector: ReflectionHost,
|
||||||
evaluator: PartialEvaluator, defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
evaluator: PartialEvaluator, defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
||||||
flags: HandlerFlags, annotateForClosureCompiler: boolean,
|
flags: HandlerFlags, annotateForClosureCompiler: boolean,
|
||||||
defaultSelector: string | null = null): {
|
defaultSelector: string | null =
|
||||||
decorator: Map<string, ts.Expression>,
|
null): {decorator: Map<string, ts.Expression>, metadata: R3DirectiveMetadata}|undefined {
|
||||||
metadata: R3DirectiveMetadata,
|
|
||||||
}|undefined {
|
|
||||||
let directive: Map<string, ts.Expression>;
|
let directive: Map<string, ts.Expression>;
|
||||||
if (decorator === null || decorator.args === null || decorator.args.length === 0) {
|
if (decorator === null || decorator.args === null || decorator.args.length === 0) {
|
||||||
directive = new Map<string, ts.Expression>();
|
directive = new Map<string, ts.Expression>();
|
||||||
@ -390,7 +408,6 @@ export function extractQueriesFromDecorator(
|
|||||||
view: R3QueryMetadata[],
|
view: R3QueryMetadata[],
|
||||||
} {
|
} {
|
||||||
const content: R3QueryMetadata[] = [], view: R3QueryMetadata[] = [];
|
const content: R3QueryMetadata[] = [], view: R3QueryMetadata[] = [];
|
||||||
const expr = unwrapExpression(queryData);
|
|
||||||
if (!ts.isObjectLiteralExpression(queryData)) {
|
if (!ts.isObjectLiteralExpression(queryData)) {
|
||||||
throw new Error(`queries metadata must be an object literal`);
|
throw new Error(`queries metadata must be an object literal`);
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import * as ts from 'typescript';
|
|||||||
|
|
||||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
import {DefaultImportRecorder} from '../../imports';
|
import {DefaultImportRecorder} from '../../imports';
|
||||||
|
import {InjectableClassRegistry} from '../../metadata';
|
||||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ export class InjectableDecoratorHandler implements
|
|||||||
constructor(
|
constructor(
|
||||||
private reflector: ReflectionHost, private defaultImportRecorder: DefaultImportRecorder,
|
private reflector: ReflectionHost, private defaultImportRecorder: DefaultImportRecorder,
|
||||||
private isCore: boolean, private strictCtorDeps: boolean,
|
private isCore: boolean, private strictCtorDeps: boolean,
|
||||||
|
private injectableRegistry: InjectableClassRegistry,
|
||||||
/**
|
/**
|
||||||
* What to do if the injectable already contains a ɵprov property.
|
* What to do if the injectable already contains a ɵprov property.
|
||||||
*
|
*
|
||||||
@ -80,6 +82,8 @@ export class InjectableDecoratorHandler implements
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
register(node: ClassDeclaration): void { this.injectableRegistry.registerInjectable(node); }
|
||||||
|
|
||||||
compile(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>): CompileResult[] {
|
compile(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>): CompileResult[] {
|
||||||
const res = compileIvyInjectable(analysis.meta);
|
const res = compileIvyInjectable(analysis.meta);
|
||||||
const statements = res.statements;
|
const statements = res.statements;
|
||||||
|
@ -11,8 +11,8 @@ import * as ts from 'typescript';
|
|||||||
|
|
||||||
import {ErrorCode, FatalDiagnosticError, makeDiagnostic} from '../../diagnostics';
|
import {ErrorCode, FatalDiagnosticError, makeDiagnostic} from '../../diagnostics';
|
||||||
import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports';
|
import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports';
|
||||||
import {MetadataReader, MetadataRegistry} from '../../metadata';
|
import {InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
|
||||||
import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
|
import {PartialEvaluator, ResolvedValue, ResolvedValueArray} from '../../partial_evaluator';
|
||||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
|
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
|
||||||
import {NgModuleRouteAnalyzer} from '../../routing';
|
import {NgModuleRouteAnalyzer} from '../../routing';
|
||||||
import {LocalModuleScopeRegistry, ScopeData} from '../../scope';
|
import {LocalModuleScopeRegistry, ScopeData} from '../../scope';
|
||||||
@ -20,9 +20,10 @@ import {FactoryTracker} from '../../shims';
|
|||||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
|
||||||
import {getSourceFile} from '../../util/src/typescript';
|
import {getSourceFile} from '../../util/src/typescript';
|
||||||
|
|
||||||
|
import {getProviderDiagnostics} from './diagnostics';
|
||||||
import {generateSetClassMetadataCall} from './metadata';
|
import {generateSetClassMetadataCall} from './metadata';
|
||||||
import {ReferencesRegistry} from './references_registry';
|
import {ReferencesRegistry} from './references_registry';
|
||||||
import {combineResolvers, findAngularDecorator, forwardRefResolver, getValidConstructorDependencies, isExpressionForwardReference, toR3Reference, unwrapExpression, wrapFunctionExpressionsInParens, wrapTypeReference} from './util';
|
import {combineResolvers, findAngularDecorator, forwardRefResolver, getValidConstructorDependencies, isExpressionForwardReference, resolveProvidersRequiringFactory, toR3Reference, unwrapExpression, wrapFunctionExpressionsInParens, wrapTypeReference} from './util';
|
||||||
|
|
||||||
export interface NgModuleAnalysis {
|
export interface NgModuleAnalysis {
|
||||||
mod: R3NgModuleMetadata;
|
mod: R3NgModuleMetadata;
|
||||||
@ -35,6 +36,8 @@ export interface NgModuleAnalysis {
|
|||||||
exports: Reference<ClassDeclaration>[];
|
exports: Reference<ClassDeclaration>[];
|
||||||
id: Expression|null;
|
id: Expression|null;
|
||||||
factorySymbolName: string;
|
factorySymbolName: string;
|
||||||
|
providersRequiringFactory: Set<Reference<ClassDeclaration>>|null;
|
||||||
|
providers: ts.Expression|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NgModuleResolution { injectorImports: Expression[]; }
|
export interface NgModuleResolution { injectorImports: Expression[]; }
|
||||||
@ -54,7 +57,8 @@ export class NgModuleDecoratorHandler implements
|
|||||||
private routeAnalyzer: NgModuleRouteAnalyzer|null, private refEmitter: ReferenceEmitter,
|
private routeAnalyzer: NgModuleRouteAnalyzer|null, private refEmitter: ReferenceEmitter,
|
||||||
private factoryTracker: FactoryTracker|null,
|
private factoryTracker: FactoryTracker|null,
|
||||||
private defaultImportRecorder: DefaultImportRecorder,
|
private defaultImportRecorder: DefaultImportRecorder,
|
||||||
private annotateForClosureCompiler: boolean, private localeId?: string) {}
|
private annotateForClosureCompiler: boolean,
|
||||||
|
private injectableRegistry: InjectableClassRegistry, private localeId?: string) {}
|
||||||
|
|
||||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||||
readonly name = NgModuleDecoratorHandler.name;
|
readonly name = NgModuleDecoratorHandler.name;
|
||||||
@ -240,7 +244,7 @@ export class NgModuleDecoratorHandler implements
|
|||||||
};
|
};
|
||||||
|
|
||||||
const rawProviders = ngModule.has('providers') ? ngModule.get('providers') ! : null;
|
const rawProviders = ngModule.has('providers') ? ngModule.get('providers') ! : null;
|
||||||
const providers = rawProviders !== null ?
|
const wrapperProviders = rawProviders !== null ?
|
||||||
new WrappedNodeExpr(
|
new WrappedNodeExpr(
|
||||||
this.annotateForClosureCompiler ? wrapFunctionExpressionsInParens(rawProviders) :
|
this.annotateForClosureCompiler ? wrapFunctionExpressionsInParens(rawProviders) :
|
||||||
rawProviders) :
|
rawProviders) :
|
||||||
@ -264,7 +268,7 @@ export class NgModuleDecoratorHandler implements
|
|||||||
internalType,
|
internalType,
|
||||||
deps: getValidConstructorDependencies(
|
deps: getValidConstructorDependencies(
|
||||||
node, this.reflector, this.defaultImportRecorder, this.isCore),
|
node, this.reflector, this.defaultImportRecorder, this.isCore),
|
||||||
providers,
|
providers: wrapperProviders,
|
||||||
imports: injectorImports,
|
imports: injectorImports,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -277,6 +281,10 @@ export class NgModuleDecoratorHandler implements
|
|||||||
declarations: declarationRefs, rawDeclarations,
|
declarations: declarationRefs, rawDeclarations,
|
||||||
imports: importRefs,
|
imports: importRefs,
|
||||||
exports: exportRefs,
|
exports: exportRefs,
|
||||||
|
providers: rawProviders,
|
||||||
|
providersRequiringFactory: rawProviders ?
|
||||||
|
resolveProvidersRequiringFactory(rawProviders, this.reflector, this.evaluator) :
|
||||||
|
null,
|
||||||
metadataStmt: generateSetClassMetadataCall(
|
metadataStmt: generateSetClassMetadataCall(
|
||||||
node, this.reflector, this.defaultImportRecorder, this.isCore,
|
node, this.reflector, this.defaultImportRecorder, this.isCore,
|
||||||
this.annotateForClosureCompiler),
|
this.annotateForClosureCompiler),
|
||||||
@ -301,6 +309,8 @@ export class NgModuleDecoratorHandler implements
|
|||||||
if (this.factoryTracker !== null) {
|
if (this.factoryTracker !== null) {
|
||||||
this.factoryTracker.track(node.getSourceFile(), analysis.factorySymbolName);
|
this.factoryTracker.track(node.getSourceFile(), analysis.factorySymbolName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.injectableRegistry.registerInjectable(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(node: ClassDeclaration, analysis: Readonly<NgModuleAnalysis>):
|
resolve(node: ClassDeclaration, analysis: Readonly<NgModuleAnalysis>):
|
||||||
@ -313,6 +323,12 @@ export class NgModuleDecoratorHandler implements
|
|||||||
diagnostics.push(...scopeDiagnostics);
|
diagnostics.push(...scopeDiagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (analysis.providersRequiringFactory !== null) {
|
||||||
|
const providerDiagnostics = getProviderDiagnostics(
|
||||||
|
analysis.providersRequiringFactory, analysis.providers !, this.injectableRegistry);
|
||||||
|
diagnostics.push(...providerDiagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
const data: NgModuleResolution = {
|
const data: NgModuleResolution = {
|
||||||
injectorImports: [],
|
injectorImports: [],
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,7 @@ import * as ts from 'typescript';
|
|||||||
|
|
||||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
import {DefaultImportRecorder, Reference} from '../../imports';
|
import {DefaultImportRecorder, Reference} from '../../imports';
|
||||||
import {MetadataRegistry} from '../../metadata';
|
import {InjectableClassRegistry, MetadataRegistry} from '../../metadata';
|
||||||
import {PartialEvaluator} from '../../partial_evaluator';
|
import {PartialEvaluator} from '../../partial_evaluator';
|
||||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||||
import {LocalModuleScopeRegistry} from '../../scope';
|
import {LocalModuleScopeRegistry} from '../../scope';
|
||||||
@ -30,7 +30,8 @@ export class PipeDecoratorHandler implements DecoratorHandler<Decorator, PipeHan
|
|||||||
constructor(
|
constructor(
|
||||||
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
|
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
|
||||||
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
|
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
|
||||||
private defaultImportRecorder: DefaultImportRecorder, private isCore: boolean) {}
|
private defaultImportRecorder: DefaultImportRecorder,
|
||||||
|
private injectableRegistry: InjectableClassRegistry, private isCore: boolean) {}
|
||||||
|
|
||||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||||
readonly name = PipeDecoratorHandler.name;
|
readonly name = PipeDecoratorHandler.name;
|
||||||
@ -115,6 +116,8 @@ export class PipeDecoratorHandler implements DecoratorHandler<Decorator, PipeHan
|
|||||||
register(node: ClassDeclaration, analysis: Readonly<PipeHandlerData>): void {
|
register(node: ClassDeclaration, analysis: Readonly<PipeHandlerData>): void {
|
||||||
const ref = new Reference(node);
|
const ref = new Reference(node);
|
||||||
this.metaRegistry.registerPipeMetadata({ref, name: analysis.meta.pipeName});
|
this.metaRegistry.registerPipeMetadata({ref, name: analysis.meta.pipeName});
|
||||||
|
|
||||||
|
this.injectableRegistry.registerInjectable(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(node: ClassDeclaration): ResolveResult<unknown> {
|
resolve(node: ClassDeclaration): ResolveResult<unknown> {
|
||||||
|
@ -408,6 +408,50 @@ export function makeDuplicateDeclarationError(
|
|||||||
`The ${kind} '${node.name.text}' is declared by more than one NgModule.`, context);
|
`The ${kind} '${node.name.text}' is declared by more than one NgModule.`, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the given `rawProviders` into `ClassDeclarations` and returns
|
||||||
|
* a set containing those that are known to require a factory definition.
|
||||||
|
* @param rawProviders Expression that declared the providers array in the source.
|
||||||
|
*/
|
||||||
|
export function resolveProvidersRequiringFactory(
|
||||||
|
rawProviders: ts.Expression, reflector: ReflectionHost,
|
||||||
|
evaluator: PartialEvaluator): Set<Reference<ClassDeclaration>> {
|
||||||
|
const providers = new Set<Reference<ClassDeclaration>>();
|
||||||
|
const resolvedProviders = evaluator.evaluate(rawProviders);
|
||||||
|
|
||||||
|
if (!Array.isArray(resolvedProviders)) {
|
||||||
|
return providers;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedProviders.forEach(function processProviders(provider) {
|
||||||
|
let tokenClass: Reference|null = null;
|
||||||
|
|
||||||
|
if (Array.isArray(provider)) {
|
||||||
|
// If we ran into an array, recurse into it until we've resolve all the classes.
|
||||||
|
provider.forEach(processProviders);
|
||||||
|
} else if (provider instanceof Reference) {
|
||||||
|
tokenClass = provider;
|
||||||
|
} else if (provider instanceof Map && provider.has('useClass') && !provider.has('deps')) {
|
||||||
|
const useExisting = provider.get('useClass') !;
|
||||||
|
if (useExisting instanceof Reference) {
|
||||||
|
tokenClass = useExisting;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenClass !== null && reflector.isClass(tokenClass.node)) {
|
||||||
|
const constructorParameters = reflector.getConstructorParameters(tokenClass.node);
|
||||||
|
|
||||||
|
// Note that we only want to capture providers with a non-trivial constructor,
|
||||||
|
// because they're the ones that might be using DI and need to be decorated.
|
||||||
|
if (constructorParameters !== null && constructorParameters.length > 0) {
|
||||||
|
providers.add(tokenClass as Reference<ClassDeclaration>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return providers;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an R3Reference for a class.
|
* Create an R3Reference for a class.
|
||||||
*
|
*
|
||||||
|
@ -10,7 +10,7 @@ import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
|||||||
import {absoluteFrom} from '../../file_system';
|
import {absoluteFrom} from '../../file_system';
|
||||||
import {runInEachFileSystem} from '../../file_system/testing';
|
import {runInEachFileSystem} from '../../file_system/testing';
|
||||||
import {ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
import {ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
||||||
import {CompoundMetadataReader, DtsMetadataReader, LocalMetadataRegistry} from '../../metadata';
|
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata';
|
||||||
import {PartialEvaluator} from '../../partial_evaluator';
|
import {PartialEvaluator} from '../../partial_evaluator';
|
||||||
import {TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
|
import {TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
|
||||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
||||||
@ -59,13 +59,14 @@ runInEachFileSystem(() => {
|
|||||||
new ReferenceEmitter([]), null);
|
new ReferenceEmitter([]), null);
|
||||||
const metaReader = new CompoundMetadataReader([metaRegistry, dtsReader]);
|
const metaReader = new CompoundMetadataReader([metaRegistry, dtsReader]);
|
||||||
const refEmitter = new ReferenceEmitter([]);
|
const refEmitter = new ReferenceEmitter([]);
|
||||||
|
const injectableRegistry = new InjectableClassRegistry(reflectionHost);
|
||||||
|
|
||||||
const handler = new ComponentDecoratorHandler(
|
const handler = new ComponentDecoratorHandler(
|
||||||
reflectionHost, evaluator, metaRegistry, metaReader, scopeRegistry, scopeRegistry,
|
reflectionHost, evaluator, metaRegistry, metaReader, scopeRegistry, scopeRegistry,
|
||||||
/* isCore */ false, new NoopResourceLoader(), /* rootDirs */[''],
|
/* isCore */ false, new NoopResourceLoader(), /* rootDirs */[''],
|
||||||
/* defaultPreserveWhitespaces */ false, /* i18nUseExternalIds */ true,
|
/* defaultPreserveWhitespaces */ false, /* i18nUseExternalIds */ true,
|
||||||
/* enableI18nLegacyMessageIdFormat */ false, moduleResolver, cycleAnalyzer, refEmitter,
|
/* enableI18nLegacyMessageIdFormat */ false, moduleResolver, cycleAnalyzer, refEmitter,
|
||||||
NOOP_DEFAULT_IMPORT_RECORDER, /* depTracker */ null,
|
NOOP_DEFAULT_IMPORT_RECORDER, /* depTracker */ null, injectableRegistry,
|
||||||
/* annotateForClosureCompiler */ false);
|
/* annotateForClosureCompiler */ false);
|
||||||
const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration);
|
const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration);
|
||||||
const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp));
|
const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp));
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
import {absoluteFrom} from '../../file_system';
|
import {absoluteFrom} from '../../file_system';
|
||||||
import {runInEachFileSystem} from '../../file_system/testing';
|
import {runInEachFileSystem} from '../../file_system/testing';
|
||||||
import {NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
import {NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
||||||
import {DtsMetadataReader, LocalMetadataRegistry} from '../../metadata';
|
import {DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata';
|
||||||
import {PartialEvaluator} from '../../partial_evaluator';
|
import {PartialEvaluator} from '../../partial_evaluator';
|
||||||
import {ClassDeclaration, TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
|
import {ClassDeclaration, TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
|
||||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
||||||
@ -48,8 +48,10 @@ runInEachFileSystem(() => {
|
|||||||
const scopeRegistry = new LocalModuleScopeRegistry(
|
const scopeRegistry = new LocalModuleScopeRegistry(
|
||||||
metaReader, new MetadataDtsModuleScopeResolver(dtsReader, null), new ReferenceEmitter([]),
|
metaReader, new MetadataDtsModuleScopeResolver(dtsReader, null), new ReferenceEmitter([]),
|
||||||
null);
|
null);
|
||||||
|
const injectableRegistry = new InjectableClassRegistry(reflectionHost);
|
||||||
const handler = new DirectiveDecoratorHandler(
|
const handler = new DirectiveDecoratorHandler(
|
||||||
reflectionHost, evaluator, scopeRegistry, scopeRegistry, NOOP_DEFAULT_IMPORT_RECORDER,
|
reflectionHost, evaluator, scopeRegistry, scopeRegistry, NOOP_DEFAULT_IMPORT_RECORDER,
|
||||||
|
injectableRegistry,
|
||||||
/* isCore */ false, /* annotateForClosureCompiler */ false);
|
/* isCore */ false, /* annotateForClosureCompiler */ false);
|
||||||
|
|
||||||
const analyzeDirective = (dirName: string) => {
|
const analyzeDirective = (dirName: string) => {
|
||||||
|
@ -9,6 +9,7 @@ import {ErrorCode, FatalDiagnosticError, ngErrorCode} from '../../diagnostics';
|
|||||||
import {absoluteFrom} from '../../file_system';
|
import {absoluteFrom} from '../../file_system';
|
||||||
import {runInEachFileSystem} from '../../file_system/testing';
|
import {runInEachFileSystem} from '../../file_system/testing';
|
||||||
import {NOOP_DEFAULT_IMPORT_RECORDER} from '../../imports';
|
import {NOOP_DEFAULT_IMPORT_RECORDER} from '../../imports';
|
||||||
|
import {InjectableClassRegistry} from '../../metadata';
|
||||||
import {TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
|
import {TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
|
||||||
import {getDeclaration, makeProgram} from '../../testing';
|
import {getDeclaration, makeProgram} from '../../testing';
|
||||||
import {InjectableDecoratorHandler} from '../src/injectable';
|
import {InjectableDecoratorHandler} from '../src/injectable';
|
||||||
@ -67,9 +68,10 @@ function setupHandler(errorOnDuplicateProv: boolean) {
|
|||||||
]);
|
]);
|
||||||
const checker = program.getTypeChecker();
|
const checker = program.getTypeChecker();
|
||||||
const reflectionHost = new TypeScriptReflectionHost(checker);
|
const reflectionHost = new TypeScriptReflectionHost(checker);
|
||||||
|
const injectableRegistry = new InjectableClassRegistry(reflectionHost);
|
||||||
const handler = new InjectableDecoratorHandler(
|
const handler = new InjectableDecoratorHandler(
|
||||||
reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, /* isCore */ false,
|
reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, /* isCore */ false,
|
||||||
/* strictCtorDeps */ false, errorOnDuplicateProv);
|
/* strictCtorDeps */ false, injectableRegistry, errorOnDuplicateProv);
|
||||||
const TestClass = getDeclaration(program, ENTRY_FILE, 'TestClass', isNamedClassDeclaration);
|
const TestClass = getDeclaration(program, ENTRY_FILE, 'TestClass', isNamedClassDeclaration);
|
||||||
const ɵprov = reflectionHost.getMembersOfClass(TestClass).find(member => member.name === 'ɵprov');
|
const ɵprov = reflectionHost.getMembersOfClass(TestClass).find(member => member.name === 'ɵprov');
|
||||||
if (ɵprov === undefined) {
|
if (ɵprov === undefined) {
|
||||||
|
@ -12,7 +12,7 @@ import * as ts from 'typescript';
|
|||||||
import {absoluteFrom} from '../../file_system';
|
import {absoluteFrom} from '../../file_system';
|
||||||
import {runInEachFileSystem} from '../../file_system/testing';
|
import {runInEachFileSystem} from '../../file_system/testing';
|
||||||
import {LocalIdentifierStrategy, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
import {LocalIdentifierStrategy, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
||||||
import {CompoundMetadataReader, DtsMetadataReader, LocalMetadataRegistry} from '../../metadata';
|
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata';
|
||||||
import {PartialEvaluator} from '../../partial_evaluator';
|
import {PartialEvaluator} from '../../partial_evaluator';
|
||||||
import {TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
|
import {TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
|
||||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
||||||
@ -66,11 +66,12 @@ runInEachFileSystem(() => {
|
|||||||
metaRegistry, new MetadataDtsModuleScopeResolver(dtsReader, null),
|
metaRegistry, new MetadataDtsModuleScopeResolver(dtsReader, null),
|
||||||
new ReferenceEmitter([]), null);
|
new ReferenceEmitter([]), null);
|
||||||
const refEmitter = new ReferenceEmitter([new LocalIdentifierStrategy()]);
|
const refEmitter = new ReferenceEmitter([new LocalIdentifierStrategy()]);
|
||||||
|
const injectableRegistry = new InjectableClassRegistry(reflectionHost);
|
||||||
|
|
||||||
const handler = new NgModuleDecoratorHandler(
|
const handler = new NgModuleDecoratorHandler(
|
||||||
reflectionHost, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry,
|
reflectionHost, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry,
|
||||||
/* isCore */ false, /* routeAnalyzer */ null, refEmitter, /* factoryTracker */ null,
|
/* isCore */ false, /* routeAnalyzer */ null, refEmitter, /* factoryTracker */ null,
|
||||||
NOOP_DEFAULT_IMPORT_RECORDER, /* annotateForClosureCompiler */ false);
|
NOOP_DEFAULT_IMPORT_RECORDER, /* annotateForClosureCompiler */ false, injectableRegistry);
|
||||||
const TestModule =
|
const TestModule =
|
||||||
getDeclaration(program, _('/entry.ts'), 'TestModule', isNamedClassDeclaration);
|
getDeclaration(program, _('/entry.ts'), 'TestModule', isNamedClassDeclaration);
|
||||||
const detected =
|
const detected =
|
||||||
|
@ -26,6 +26,9 @@ export enum ErrorCode {
|
|||||||
PARAM_MISSING_TOKEN = 2003,
|
PARAM_MISSING_TOKEN = 2003,
|
||||||
DIRECTIVE_MISSING_SELECTOR = 2004,
|
DIRECTIVE_MISSING_SELECTOR = 2004,
|
||||||
|
|
||||||
|
/** Raised when an undecorated class is passed in as a provider to a module or a directive. */
|
||||||
|
UNDECORATED_PROVIDER = 2005,
|
||||||
|
|
||||||
SYMBOL_NOT_EXPORTED = 3001,
|
SYMBOL_NOT_EXPORTED = 3001,
|
||||||
SYMBOL_EXPORTED_UNDER_DIFFERENT_NAME = 3002,
|
SYMBOL_EXPORTED_UNDER_DIFFERENT_NAME = 3002,
|
||||||
|
|
||||||
|
@ -8,5 +8,5 @@
|
|||||||
|
|
||||||
export * from './src/api';
|
export * from './src/api';
|
||||||
export {DtsMetadataReader} from './src/dts';
|
export {DtsMetadataReader} from './src/dts';
|
||||||
export {CompoundMetadataRegistry, LocalMetadataRegistry} from './src/registry';
|
export {CompoundMetadataRegistry, LocalMetadataRegistry, InjectableClassRegistry} from './src/registry';
|
||||||
export {extractDirectiveGuards, CompoundMetadataReader} from './src/util';
|
export {extractDirectiveGuards, CompoundMetadataReader} from './src/util';
|
||||||
|
@ -7,9 +7,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Reference} from '../../imports';
|
import {Reference} from '../../imports';
|
||||||
import {ClassDeclaration} from '../../reflection';
|
import {ClassDeclaration, ReflectionHost} from '../../reflection';
|
||||||
|
|
||||||
import {DirectiveMeta, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from './api';
|
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
|
* A registry of directive, pipe, and module metadata for types defined in the current compilation
|
||||||
@ -59,3 +60,22 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -174,3 +174,10 @@ function afterUnderscore(str: string): string {
|
|||||||
}
|
}
|
||||||
return str.substr(pos + 1);
|
return str.substr(pos + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns whether a class declaration has the necessary class fields to make it injectable. */
|
||||||
|
export function hasInjectableFields(clazz: ClassDeclaration, host: ReflectionHost): boolean {
|
||||||
|
const members = host.getMembersOfClass(clazz);
|
||||||
|
return members.some(
|
||||||
|
({isStatic, name}) => isStatic && (name === 'ɵprov' || name === 'ɵfac' || name === 'ɵinj'));
|
||||||
|
}
|
||||||
|
@ -23,6 +23,7 @@ import {IncrementalDriver} from './incremental';
|
|||||||
import {IndexedComponent, IndexingContext} from './indexer';
|
import {IndexedComponent, IndexingContext} from './indexer';
|
||||||
import {generateAnalysis} from './indexer/src/transform';
|
import {generateAnalysis} from './indexer/src/transform';
|
||||||
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, LocalMetadataRegistry, MetadataReader} from './metadata';
|
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, LocalMetadataRegistry, MetadataReader} from './metadata';
|
||||||
|
import {InjectableClassRegistry} from './metadata/src/registry';
|
||||||
import {ModuleWithProvidersScanner} from './modulewithproviders';
|
import {ModuleWithProvidersScanner} from './modulewithproviders';
|
||||||
import {PartialEvaluator} from './partial_evaluator';
|
import {PartialEvaluator} from './partial_evaluator';
|
||||||
import {NOOP_PERF_RECORDER, PerfRecorder, PerfTracker} from './perf';
|
import {NOOP_PERF_RECORDER, PerfRecorder, PerfTracker} from './perf';
|
||||||
@ -629,6 +630,7 @@ export class NgtscProgram implements api.Program {
|
|||||||
localMetaReader, depScopeReader, this.refEmitter, this.aliasingHost);
|
localMetaReader, depScopeReader, this.refEmitter, this.aliasingHost);
|
||||||
const scopeReader: ComponentScopeReader = this.scopeRegistry;
|
const scopeReader: ComponentScopeReader = this.scopeRegistry;
|
||||||
const metaRegistry = new CompoundMetadataRegistry([localMetaRegistry, this.scopeRegistry]);
|
const metaRegistry = new CompoundMetadataRegistry([localMetaRegistry, this.scopeRegistry]);
|
||||||
|
const injectableRegistry = new InjectableClassRegistry(this.reflector);
|
||||||
|
|
||||||
this.metaReader = new CompoundMetadataReader([localMetaReader, dtsReader]);
|
this.metaReader = new CompoundMetadataReader([localMetaReader, dtsReader]);
|
||||||
|
|
||||||
@ -658,26 +660,27 @@ export class NgtscProgram implements api.Program {
|
|||||||
this.options.preserveWhitespaces || false, this.options.i18nUseExternalIds !== false,
|
this.options.preserveWhitespaces || false, this.options.i18nUseExternalIds !== false,
|
||||||
this.options.enableI18nLegacyMessageIdFormat !== false, this.moduleResolver,
|
this.options.enableI18nLegacyMessageIdFormat !== false, this.moduleResolver,
|
||||||
this.cycleAnalyzer, this.refEmitter, this.defaultImportTracker,
|
this.cycleAnalyzer, this.refEmitter, this.defaultImportTracker,
|
||||||
this.incrementalDriver.depGraph, this.closureCompilerEnabled),
|
this.incrementalDriver.depGraph, injectableRegistry, this.closureCompilerEnabled),
|
||||||
// TODO(alxhub): understand why the cast here is necessary (something to do with `null` not
|
// TODO(alxhub): understand why the cast here is necessary (something to do with `null` not
|
||||||
// being assignable to `unknown` when wrapped in `Readonly`).
|
// being assignable to `unknown` when wrapped in `Readonly`).
|
||||||
// clang-format off
|
// clang-format off
|
||||||
new DirectiveDecoratorHandler(
|
new DirectiveDecoratorHandler(
|
||||||
this.reflector, evaluator, metaRegistry, this.scopeRegistry, this.defaultImportTracker,
|
this.reflector, evaluator, metaRegistry, this.scopeRegistry, this.defaultImportTracker, injectableRegistry,
|
||||||
this.isCore, this.closureCompilerEnabled) as Readonly<DecoratorHandler<unknown, unknown, unknown>>,
|
this.isCore, this.closureCompilerEnabled) as Readonly<DecoratorHandler<unknown, unknown, unknown>>,
|
||||||
// clang-format on
|
// clang-format on
|
||||||
// Pipe handler must be before injectable handler in list so pipe factories are printed
|
// Pipe handler must be before injectable handler in list so pipe factories are printed
|
||||||
// before injectable factories (so injectable factories can delegate to them)
|
// before injectable factories (so injectable factories can delegate to them)
|
||||||
new PipeDecoratorHandler(
|
new PipeDecoratorHandler(
|
||||||
this.reflector, evaluator, metaRegistry, this.scopeRegistry, this.defaultImportTracker,
|
this.reflector, evaluator, metaRegistry, this.scopeRegistry, this.defaultImportTracker,
|
||||||
this.isCore),
|
injectableRegistry, this.isCore),
|
||||||
new InjectableDecoratorHandler(
|
new InjectableDecoratorHandler(
|
||||||
this.reflector, this.defaultImportTracker, this.isCore,
|
this.reflector, this.defaultImportTracker, this.isCore,
|
||||||
this.options.strictInjectionParameters || false),
|
this.options.strictInjectionParameters || false, injectableRegistry),
|
||||||
new NgModuleDecoratorHandler(
|
new NgModuleDecoratorHandler(
|
||||||
this.reflector, evaluator, this.metaReader, metaRegistry, this.scopeRegistry,
|
this.reflector, evaluator, this.metaReader, metaRegistry, this.scopeRegistry,
|
||||||
referencesRegistry, this.isCore, this.routeAnalyzer, this.refEmitter, this.factoryTracker,
|
referencesRegistry, this.isCore, this.routeAnalyzer, this.refEmitter, this.factoryTracker,
|
||||||
this.defaultImportTracker, this.closureCompilerEnabled, this.options.i18nInLocale),
|
this.defaultImportTracker, this.closureCompilerEnabled, injectableRegistry,
|
||||||
|
this.options.i18nInLocale),
|
||||||
];
|
];
|
||||||
|
|
||||||
return new TraitCompiler(
|
return new TraitCompiler(
|
||||||
|
@ -363,7 +363,7 @@ export class TraitCompiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.diagnostics !== undefined) {
|
if (result.diagnostics !== undefined && result.diagnostics.length > 0) {
|
||||||
trait = trait.toErrored(result.diagnostics);
|
trait = trait.toErrored(result.diagnostics);
|
||||||
} else {
|
} else {
|
||||||
if (result.data !== undefined) {
|
if (result.data !== undefined) {
|
||||||
|
@ -89,3 +89,5 @@ export class EventEmitter<T> {
|
|||||||
export interface QueryList<T>/* implements Iterable<T> */ { [Symbol.iterator]: () => Iterator<T>; }
|
export interface QueryList<T>/* implements Iterable<T> */ { [Symbol.iterator]: () => Iterator<T>; }
|
||||||
|
|
||||||
export type NgIterable<T> = Array<T>| Iterable<T>;
|
export type NgIterable<T> = Array<T>| Iterable<T>;
|
||||||
|
|
||||||
|
export class NgZone {}
|
||||||
|
@ -5222,6 +5222,334 @@ export const Foo = Foo__PRE_R3__;
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('undecorated providers', () => {
|
||||||
|
it('should error when an undecorated class, with a non-trivial constructor, is provided directly in a module',
|
||||||
|
() => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule, NgZone} from '@angular/core';
|
||||||
|
|
||||||
|
class NotAService {
|
||||||
|
constructor(ngZone: NgZone) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [NotAService]
|
||||||
|
})
|
||||||
|
export class SomeModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(1);
|
||||||
|
expect(diags[0].messageText).toContain('cannot be created via dependency injection');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error when an undecorated class is provided via useClass', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule, Injectable, NgZone} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({providedIn: 'root'})
|
||||||
|
class Service {}
|
||||||
|
|
||||||
|
class NotAService {
|
||||||
|
constructor(ngZone: NgZone) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [{provide: Service, useClass: NotAService}]
|
||||||
|
})
|
||||||
|
export class SomeModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(1);
|
||||||
|
expect(diags[0].messageText).toContain('cannot be created via dependency injection');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error when an undecorated class is provided via useClass with deps', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule, Injectable, NgZone} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({providedIn: 'root'})
|
||||||
|
class Service {}
|
||||||
|
|
||||||
|
class NotAService {
|
||||||
|
constructor(ngZone: NgZone) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [{provide: Service, useClass: NotAService, deps: [NgZone]}]
|
||||||
|
})
|
||||||
|
export class SomeModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error when an undecorated class is provided via an array', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule, Injectable, NgZone} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({providedIn: 'root'})
|
||||||
|
class Service {}
|
||||||
|
|
||||||
|
class NotAService {
|
||||||
|
constructor(ngZone: NgZone) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [Service, [NotAService]]
|
||||||
|
})
|
||||||
|
export class SomeModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(1);
|
||||||
|
expect(diags[0].messageText).toContain('cannot be created via dependency injection');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error when an undecorated class is provided to a directive', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule, Directive, NgZone} from '@angular/core';
|
||||||
|
|
||||||
|
class NotAService {
|
||||||
|
constructor(ngZone: NgZone) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[some-dir]',
|
||||||
|
providers: [NotAService]
|
||||||
|
})
|
||||||
|
class SomeDirective {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [SomeDirective]
|
||||||
|
})
|
||||||
|
export class SomeModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(1);
|
||||||
|
expect(diags[0].messageText).toContain('cannot be created via dependency injection');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error when an undecorated class is provided to a component', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule, Component, NgZone} from '@angular/core';
|
||||||
|
|
||||||
|
class NotAService {
|
||||||
|
constructor(ngZone: NgZone) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'some-comp',
|
||||||
|
template: '',
|
||||||
|
providers: [NotAService]
|
||||||
|
})
|
||||||
|
class SomeComponent {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [SomeComponent]
|
||||||
|
})
|
||||||
|
export class SomeModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(1);
|
||||||
|
expect(diags[0].messageText).toContain('cannot be created via dependency injection');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error when an undecorated class is provided to a component via viewProviders',
|
||||||
|
() => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule, Component, NgZone} from '@angular/core';
|
||||||
|
|
||||||
|
class NotAService {
|
||||||
|
constructor(ngZone: NgZone) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'some-comp',
|
||||||
|
template: '',
|
||||||
|
viewProviders: [NotAService]
|
||||||
|
})
|
||||||
|
class SomeComponent {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [SomeComponent]
|
||||||
|
})
|
||||||
|
export class SomeModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(1);
|
||||||
|
expect(diags[0].messageText).toContain('cannot be created via dependency injection');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error when a class with a factory is provided', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule, Pipe} from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'some-pipe'
|
||||||
|
})
|
||||||
|
class SomePipe {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [SomePipe],
|
||||||
|
providers: [SomePipe]
|
||||||
|
})
|
||||||
|
export class SomeModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error when an NgModule is provided', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {Injectable, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class Service {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
})
|
||||||
|
class SomeModule {
|
||||||
|
constructor(dep: Service) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [SomeModule],
|
||||||
|
})
|
||||||
|
export class Module {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error when an undecorated class from a declaration file is provided', () => {
|
||||||
|
env.write('node_modules/@angular/core/testing/index.d.ts', `
|
||||||
|
export declare class Testability {
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {Testability} from '@angular/core/testing';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [Testability]
|
||||||
|
})
|
||||||
|
export class SomeModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error when an undecorated class without a constructor from a declaration file is provided via useClass',
|
||||||
|
() => {
|
||||||
|
env.write('node_modules/@angular/core/testing/index.d.ts', `
|
||||||
|
export declare class Testability {
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule, Injectable} from '@angular/core';
|
||||||
|
import {Testability} from '@angular/core/testing';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class TestingService {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [{provide: TestingService, useClass: Testability}]
|
||||||
|
})
|
||||||
|
export class SomeModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error if the undecorated class does not have a constructor or the constructor is blank',
|
||||||
|
() => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule, NgZone} from '@angular/core';
|
||||||
|
|
||||||
|
class NoConstructorService {
|
||||||
|
}
|
||||||
|
|
||||||
|
class BlankConstructorService {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [NoConstructorService, BlankConstructorService]
|
||||||
|
})
|
||||||
|
export class SomeModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error when an undecorated class with a non-trivial constructor in a declaration file is provided via useClass',
|
||||||
|
() => {
|
||||||
|
env.write('node_modules/@angular/core/testing/index.d.ts', `
|
||||||
|
export declare class NgZone {}
|
||||||
|
|
||||||
|
export declare class Testability {
|
||||||
|
constructor(ngZone: NgZone) {}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule, Injectable} from '@angular/core';
|
||||||
|
import {Testability} from '@angular/core/testing';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class TestingService {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [{provide: TestingService, useClass: Testability}]
|
||||||
|
})
|
||||||
|
export class SomeModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(1);
|
||||||
|
expect(diags[0].messageText).toContain('cannot be created via dependency injection');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error when an class with a factory definition and a non-trivial constructor in a declaration file is provided via useClass',
|
||||||
|
() => {
|
||||||
|
env.write('node_modules/@angular/core/testing/index.d.ts', `
|
||||||
|
import * as i0 from '@angular/core';
|
||||||
|
|
||||||
|
export declare class NgZone {}
|
||||||
|
|
||||||
|
export declare class Testability {
|
||||||
|
static ɵfac: i0.ɵɵFactoryDef<Testability>;
|
||||||
|
constructor(ngZone: NgZone) {}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule, Injectable} from '@angular/core';
|
||||||
|
import {Testability} from '@angular/core/testing';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class TestingService {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [{provide: TestingService, useClass: Testability}]
|
||||||
|
})
|
||||||
|
export class SomeModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function expectTokenAtPosition<T extends ts.Node>(
|
function expectTokenAtPosition<T extends ts.Node>(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user