Skip to content

Commit

Permalink
refactor(compiler-cli): ctor dependencies in local compilation mode
Browse files Browse the repository at this point in the history
Ctor deps are added to the fctory function for all the angular classes: NgModule, Component, Pipe, Directive and Injectable
  • Loading branch information
pmvald committed Jul 26, 2023
1 parent 005121b commit 692cb6f
Show file tree
Hide file tree
Showing 14 changed files with 357 additions and 36 deletions.
44 changes: 38 additions & 6 deletions packages/compiler-cli/src/ngtsc/annotations/common/src/di.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Expression, LiteralExpr, R3DependencyMetadata, WrappedNodeExpr} from '@angular/compiler';
import {Expression, LiteralExpr, R3DependencyMetadata, ReadPropExpr, WrappedNodeExpr} from '@angular/compiler';
import ts from 'typescript';

import {ErrorCode, FatalDiagnosticError, makeRelatedInformation} from '../../../diagnostics';
import {ClassDeclaration, CtorParameter, ReflectionHost, TypeValueReferenceKind, UnavailableValue, ValueUnavailableKind,} from '../../../reflection';
import {CompilationMode} from '../../../transform';

import {isAngularCore, valueReferenceToExpression} from './util';

Expand All @@ -28,7 +29,8 @@ export interface ConstructorDepError {
}

export function getConstructorDependencies(
clazz: ClassDeclaration, reflector: ReflectionHost, isCore: boolean): ConstructorDeps|null {
clazz: ClassDeclaration, reflector: ReflectionHost, isCore: boolean,
compilationMode: CompilationMode): ConstructorDeps|null {
const deps: R3DependencyMetadata[] = [];
const errors: ConstructorDepError[] = [];
let ctorParams = reflector.getConstructorParameters(clazz);
Expand All @@ -40,7 +42,37 @@ export function getConstructorDependencies(
}
}
ctorParams.forEach((param, idx) => {
let token = valueReferenceToExpression(param.typeValueReference);
let token: Expression|null = null;

if (compilationMode === CompilationMode.LOCAL &&
param.typeValueReference.kind === TypeValueReferenceKind.UNAVAILABLE &&
param.typeValueReference.reason.kind !== ValueUnavailableKind.MISSING_TYPE) {
// The case of local compilation where injection token cannot be resolved because it is
// "probably" imported from another file

const typeNode = param.typeValueReference.reason.typeNode;

if (ts.isTypeReferenceNode(typeNode)) {
if (ts.isIdentifier(typeNode.typeName)) {
token = new WrappedNodeExpr(typeNode.typeName);
} else if (ts.isQualifiedName(typeNode.typeName)) {
const receiver = new WrappedNodeExpr(typeNode.typeName.left);

// Here we manually create the token out of the typeName without caring about its
// references for better TS tracking. This is because in this code path the typeNode is
// imported from another file and since we are in local compilation mode (=single file
// mode) the reference of this node (or its typeName node) cannot be resolved. So all we
// can do is just to create a new expression.
token = new ReadPropExpr(receiver, typeNode.typeName.right.getText());
} else {
throw new Error('Impossible state!');
}
}
} else {
// In all other cases resolve the injection token
token = valueReferenceToExpression(param.typeValueReference);
}

let attributeNameType: Expression|null = null;
let optional = false, self = false, skipSelf = false, host = false;

Expand Down Expand Up @@ -123,10 +155,10 @@ export function unwrapConstructorDependencies(deps: ConstructorDeps|null): R3Dep
}

export function getValidConstructorDependencies(
clazz: ClassDeclaration, reflector: ReflectionHost, isCore: boolean): R3DependencyMetadata[]|
null {
clazz: ClassDeclaration, reflector: ReflectionHost, isCore: boolean,
compilationMode: CompilationMode): R3DependencyMetadata[]|null {
return validateConstructorDependencies(
clazz, getConstructorDependencies(clazz, reflector, isCore));
clazz, getConstructorDependencies(clazz, reflector, isCore, compilationMode));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {R3DependencyMetadata} from '@angular/compiler';

import {hasInjectableFields} from '../../../metadata';
import {ClassDeclaration, ReflectionHost} from '../../../reflection';
import {CompilationMode} from '../../../transform';

import {getConstructorDependencies, unwrapConstructorDependencies} from './di';

Expand Down Expand Up @@ -42,7 +43,8 @@ export class InjectableClassRegistry {
return null;
}

const ctorDeps = getConstructorDependencies(declaration, this.host, this.isCore);
const ctorDeps =
getConstructorDependencies(declaration, this.host, this.isCore, CompilationMode.FULL);
const meta: InjectableMeta = {
ctorDeps: unwrapConstructorDependencies(ctorDeps),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export class ComponentDecoratorHandler implements
// on it.
const directiveResult = extractDirectiveMetadata(
node, decorator, this.reflector, this.evaluator, this.refEmitter, this.referencesRegistry,
this.isCore, this.annotateForClosureCompiler,
this.isCore, this.annotateForClosureCompiler, this.compilationMode,
this.elementSchemaRegistry.getDefaultComponentElementName());
if (directiveResult === undefined) {
// `extractDirectiveMetadata` returns undefined when the @Directive has `jit: true`. In this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ 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, HandlerPrecedence, ResolveResult} from '../../../transform';
import {AnalysisOutput, CompilationMode, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../../transform';
import {compileDeclareFactory, compileInputTransformFields, compileNgFactoryDefField, compileResults, extractClassMetadata, findAngularDecorator, getDirectiveDiagnostics, getProviderDiagnostics, getUndecoratedClassWithAngularFeaturesDiagnostic, InjectableClassRegistry, isAngularDecorator, readBaseClass, ReferencesRegistry, resolveProvidersRequiringFactory, toFactoryMetadata, validateHostDirectives} from '../../common';

import {extractDirectiveMetadata} from './shared';
Expand Down Expand Up @@ -49,14 +49,22 @@ export interface DirectiveHandlerData {
export class DirectiveDecoratorHandler implements
DecoratorHandler<Decorator|null, DirectiveHandlerData, DirectiveSymbol, unknown> {
constructor(
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
private metaReader: MetadataReader, private injectableRegistry: InjectableClassRegistry,
private refEmitter: ReferenceEmitter, private referencesRegistry: ReferencesRegistry,
private isCore: boolean, private strictCtorDeps: boolean,
private reflector: ReflectionHost,
private evaluator: PartialEvaluator,
private metaRegistry: MetadataRegistry,
private scopeRegistry: LocalModuleScopeRegistry,
private metaReader: MetadataReader,
private injectableRegistry: InjectableClassRegistry,
private refEmitter: ReferenceEmitter,
private referencesRegistry: ReferencesRegistry,
private isCore: boolean,
private strictCtorDeps: boolean,
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
private annotateForClosureCompiler: boolean, private perf: PerfRecorder,
private includeClassMetadata: boolean) {}
private annotateForClosureCompiler: boolean,
private perf: PerfRecorder,
private includeClassMetadata: boolean,
private readonly compilationMode: CompilationMode,
) {}

readonly precedence = HandlerPrecedence.PRIMARY;
readonly name = 'DirectiveDecoratorHandler';
Expand Down Expand Up @@ -95,7 +103,7 @@ export class DirectiveDecoratorHandler implements

const directiveResult = extractDirectiveMetadata(
node, decorator, this.reflector, this.evaluator, this.refEmitter, this.referencesRegistry,
this.isCore, this.annotateForClosureCompiler);
this.isCore, this.annotateForClosureCompiler, this.compilationMode);
if (directiveResult === undefined) {
return {};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {assertSuccessfulReferenceEmit, ImportFlags, Reference, ReferenceEmitter}
import {ClassPropertyMapping, HostDirectiveMeta, InputMapping, InputTransform} from '../../../metadata';
import {DynamicValue, EnumValue, PartialEvaluator, ResolvedValue} from '../../../partial_evaluator';
import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, filterToMembersWithDecorator, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral} from '../../../reflection';
import {CompilationMode} from '../../../transform';
import {createSourceSpan, createValueHasWrongTypeError, forwardRefResolver, getConstructorDependencies, ReferencesRegistry, toR3Reference, tryUnwrapForwardRef, unwrapConstructorDependencies, unwrapExpression, validateConstructorDependencies, wrapFunctionExpressionsInParens, wrapTypeReference,} from '../../common';

const EMPTY_OBJECT: {[key: string]: string} = {};
Expand All @@ -34,7 +35,7 @@ export function extractDirectiveMetadata(
clazz: ClassDeclaration, decorator: Readonly<Decorator|null>, reflector: ReflectionHost,
evaluator: PartialEvaluator, refEmitter: ReferenceEmitter,
referencesRegistry: ReferencesRegistry, isCore: boolean, annotateForClosureCompiler: boolean,
defaultSelector: string|null = null): {
compilationMode: CompilationMode, defaultSelector: string|null = null): {
decorator: Map<string, ts.Expression>,
metadata: R3DirectiveMetadata,
inputs: ClassPropertyMapping<InputMapping>,
Expand Down Expand Up @@ -155,7 +156,7 @@ export function extractDirectiveMetadata(
exportAs = resolved.split(',').map(part => part.trim());
}

const rawCtorDeps = getConstructorDependencies(clazz, reflector, isCore);
const rawCtorDeps = getConstructorDependencies(clazz, reflector, isCore, compilationMode);

// Non-abstract directives (those with a selector) require valid constructor dependencies, whereas
// abstract directives are allowed to have invalid dependencies, given that a subclass may call
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/scope",
"//packages/compiler-cli/src/ngtsc/testing",
"//packages/compiler-cli/src/ngtsc/transform",
"@npm//typescript",
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {NOOP_PERF_RECORDER} from '../../../perf';
import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../scope';
import {getDeclaration, makeProgram} from '../../../testing';
import {CompilationMode} from '../../../transform';
import {InjectableClassRegistry, NoopReferencesRegistry} from '../../common';
import {DirectiveDecoratorHandler} from '../index';

Expand Down Expand Up @@ -177,7 +178,8 @@ runInEachFileSystem(() => {
/*isCore*/ false,
/*strictCtorDeps*/ false,
/*semanticDepGraphUpdater*/ null,
/*annotateForClosureCompiler*/ false, NOOP_PERF_RECORDER, /*includeClassMetadata*/ true);
/*annotateForClosureCompiler*/ false, NOOP_PERF_RECORDER, /*includeClassMetadata*/ true,
/*compilationMode */ CompilationMode.FULL);

const DirNode = getDeclaration(program, _('/entry.ts'), dirName, isNamedClassDeclaration);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,8 @@ export class NgModuleDecoratorHandler implements
name,
type,
typeArgumentCount: 0,
deps: getValidConstructorDependencies(node, this.reflector, this.isCore),
deps:
getValidConstructorDependencies(node, this.reflector, this.isCore, this.compilationMode),
target: FactoryTarget.NgModule,
};

Expand Down
18 changes: 10 additions & 8 deletions packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {PartialEvaluator} from '../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult,} from '../../transform';
import {AnalysisOutput, CompilationMode, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult,} from '../../transform';
import {checkInheritanceOfInjectable, compileDeclareFactory, CompileFactoryFn, compileNgFactoryDefField, extractClassMetadata, findAngularDecorator, getConstructorDependencies, getValidConstructorDependencies, isAngularCore, toFactoryMetadata, tryUnwrapForwardRef, unwrapConstructorDependencies, validateConstructorDependencies, wrapTypeReference,} from '../common';

export interface InjectableHandlerData {
Expand All @@ -33,7 +33,7 @@ export class InjectableDecoratorHandler implements
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
private isCore: boolean, private strictCtorDeps: boolean,
private injectableRegistry: InjectableClassRegistry, private perf: PerfRecorder,
private includeClassMetadata: boolean,
private includeClassMetadata: boolean, private readonly compilationMode: CompilationMode,
/**
* What to do if the injectable already contains a ɵprov property.
*
Expand Down Expand Up @@ -72,7 +72,8 @@ export class InjectableDecoratorHandler implements
analysis: {
meta,
ctorDeps: extractInjectableCtorDeps(
node, meta, decorator, this.reflector, this.isCore, this.strictCtorDeps),
node, meta, decorator, this.reflector, this.isCore, this.strictCtorDeps,
this.compilationMode),
classMetadata: this.includeClassMetadata ?
extractClassMetadata(node, this.reflector, this.isCore) :
null,
Expand Down Expand Up @@ -252,7 +253,8 @@ function getProviderExpression(

function extractInjectableCtorDeps(
clazz: ClassDeclaration, meta: R3InjectableMetadata, decorator: Decorator,
reflector: ReflectionHost, isCore: boolean, strictCtorDeps: boolean) {
reflector: ReflectionHost, isCore: boolean, strictCtorDeps: boolean,
compilationMode: CompilationMode) {
if (decorator.args === null) {
throw new FatalDiagnosticError(
ErrorCode.DECORATOR_NOT_CALLED, decorator.node, '@Injectable must be called');
Expand All @@ -270,15 +272,15 @@ function extractInjectableCtorDeps(
// constructor signature does not work for DI then a factory definition (ɵfac) that throws is
// generated.
if (strictCtorDeps && !isAbstractClassDeclaration(clazz)) {
ctorDeps = getValidConstructorDependencies(clazz, reflector, isCore);
ctorDeps = getValidConstructorDependencies(clazz, reflector, isCore, compilationMode);
} else {
ctorDeps =
unwrapConstructorDependencies(getConstructorDependencies(clazz, reflector, isCore));
ctorDeps = unwrapConstructorDependencies(
getConstructorDependencies(clazz, reflector, isCore, compilationMode));
}

return ctorDeps;
} else if (decorator.args.length === 1) {
const rawCtorDeps = getConstructorDependencies(clazz, reflector, isCore);
const rawCtorDeps = getConstructorDependencies(clazz, reflector, isCore, compilationMode);

if (strictCtorDeps && !isAbstractClassDeclaration(clazz) && requiresValidCtor(meta)) {
// Since use* was not provided for a concrete class, validate the deps according to
Expand Down
8 changes: 5 additions & 3 deletions packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ 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 {AnalysisOutput, CompilationMode, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult,} from '../../transform';
import {compileDeclareFactory, compileNgFactoryDefField, compileResults, createValueHasWrongTypeError, extractClassMetadata, findAngularDecorator, getValidConstructorDependencies, InjectableClassRegistry, makeDuplicateDeclarationError, toFactoryMetadata, unwrapExpression, wrapTypeReference,} from '../common';

export interface PipeHandlerData {
Expand Down Expand Up @@ -54,7 +54,8 @@ export class PipeDecoratorHandler implements
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
private injectableRegistry: InjectableClassRegistry, private isCore: boolean,
private perf: PerfRecorder, private includeClassMetadata: boolean) {}
private perf: PerfRecorder, private includeClassMetadata: boolean,
private readonly compilationMode: CompilationMode) {}

readonly precedence = HandlerPrecedence.PRIMARY;
readonly name = 'PipeDecoratorHandler';
Expand Down Expand Up @@ -134,7 +135,8 @@ export class PipeDecoratorHandler implements
type,
typeArgumentCount: this.reflector.getGenericArityOfClass(clazz) || 0,
pipeName,
deps: getValidConstructorDependencies(clazz, this.reflector, this.isCore),
deps: getValidConstructorDependencies(
clazz, this.reflector, this.isCore, this.compilationMode),
pure,
isStandalone,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/scope",
"//packages/compiler-cli/src/ngtsc/testing",
"//packages/compiler-cli/src/ngtsc/transform",
"//packages/compiler-cli/src/ngtsc/translator",
"@npm//typescript",
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {PartialEvaluator} from '../../partial_evaluator';
import {NOOP_PERF_RECORDER} from '../../perf';
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {getDeclaration, makeProgram} from '../../testing';
import {CompilationMode} from '../../transform';
import {InjectableDecoratorHandler} from '../src/injectable';

runInEachFileSystem(() => {
Expand Down Expand Up @@ -73,7 +74,7 @@ function setupHandler(errorOnDuplicateProv: boolean) {
const handler = new InjectableDecoratorHandler(
reflectionHost, evaluator, /* isCore */ false,
/* strictCtorDeps */ false, injectableRegistry, NOOP_PERF_RECORDER, true,
errorOnDuplicateProv);
/*compilationMode */ CompilationMode.FULL, errorOnDuplicateProv);
const TestClass = getDeclaration(program, ENTRY_FILE, 'TestClass', isNamedClassDeclaration);
const ɵprov = reflectionHost.getMembersOfClass(TestClass).find(member => member.name === 'ɵprov');
if (ɵprov === undefined) {
Expand Down
6 changes: 3 additions & 3 deletions packages/compiler-cli/src/ngtsc/core/src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1080,17 +1080,17 @@ export class NgCompiler {
injectableRegistry, refEmitter, referencesRegistry, isCore, strictCtorDeps, semanticDepGraphUpdater,
this.closureCompilerEnabled,
this.delegatingPerfRecorder,
supportTestBed,
supportTestBed, compilationMode,
) as Readonly<DecoratorHandler<unknown, unknown, SemanticSymbol | null,unknown>>,
// clang-format on
// Pipe handler must be before injectable handler in list so pipe factories are printed
// before injectable factories (so injectable factories can delegate to them)
new PipeDecoratorHandler(
reflector, evaluator, metaRegistry, ngModuleScopeRegistry, injectableRegistry, isCore,
this.delegatingPerfRecorder, supportTestBed),
this.delegatingPerfRecorder, supportTestBed, compilationMode),
new InjectableDecoratorHandler(
reflector, evaluator, isCore, strictCtorDeps, injectableRegistry,
this.delegatingPerfRecorder, supportTestBed),
this.delegatingPerfRecorder, supportTestBed, compilationMode),
new NgModuleDecoratorHandler(
reflector, evaluator, metaReader, metaRegistry, ngModuleScopeRegistry, referencesRegistry,
exportedProviderStatusResolver, semanticDepGraphUpdater, isCore, refEmitter,
Expand Down
Loading

0 comments on commit 692cb6f

Please sign in to comment.