Skip to content

Commit

Permalink
refactor(compiler-cli): introduce declaration function to declare cla…
Browse files Browse the repository at this point in the history
…ss metadata (#41200)

This commit refactors the generated code for class metadata in partial
compilation mode. Instead of emitting class metadata into a top-level
`ɵsetClassMetadata` call guarded by `ngDevMode` flags, the class
metadata is now declared using a top-level `ɵɵngDeclareClassMetadata`
call.

PR Close #41200
  • Loading branch information
JoostK authored and zarend committed Apr 12, 2021
1 parent 42e3a52 commit c20db69
Show file tree
Hide file tree
Showing 81 changed files with 5,196 additions and 4,319 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {compileClassMetadata, ConstantPool, R3ClassMetadata, R3DeclareClassMetadata, R3PartialDeclaration} from '@angular/compiler';
import * as o from '@angular/compiler/src/output/output_ast';

import {AstObject} from '../../ast/ast_value';

import {PartialLinker} from './partial_linker';

/**
* A `PartialLinker` that is designed to process `ɵɵngDeclareClassMetadata()` call expressions.
*/
export class PartialClassMetadataLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
linkPartialDeclaration(
constantPool: ConstantPool,
metaObj: AstObject<R3PartialDeclaration, TExpression>): o.Expression {
const meta = toR3ClassMetadata(metaObj);
return compileClassMetadata(meta);
}
}

/**
* Derives the `R3ClassMetadata` structure from the AST object.
*/
export function toR3ClassMetadata<TExpression>(
metaObj: AstObject<R3DeclareClassMetadata, TExpression>): R3ClassMetadata {
return {
type: metaObj.getOpaque('type'),
decorators: metaObj.getOpaque('decorators'),
ctorParameters: metaObj.has('ctorParameters') ? metaObj.getOpaque('ctorParameters') : null,
propDecorators: metaObj.has('propDecorators') ? metaObj.getOpaque('propDecorators') : null,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system';
import {createGetSourceFile} from '../get_source_file';
import {LinkerEnvironment} from '../linker_environment';

import {PartialClassMetadataLinkerVersion1} from './partial_class_metadata_linker_1';
import {PartialComponentLinkerVersion1} from './partial_component_linker_1';
import {PartialDirectiveLinkerVersion1} from './partial_directive_linker_1';
import {PartialFactoryLinkerVersion1} from './partial_factory_linker_1';
Expand All @@ -21,15 +22,16 @@ import {PartialNgModuleLinkerVersion1} from './partial_ng_module_linker_1';
import {PartialPipeLinkerVersion1} from './partial_pipe_linker_1';

export const ɵɵngDeclareDirective = 'ɵɵngDeclareDirective';
export const ɵɵngDeclareClassMetadata = 'ɵɵngDeclareClassMetadata';
export const ɵɵngDeclareComponent = 'ɵɵngDeclareComponent';
export const ɵɵngDeclareFactory = 'ɵɵngDeclareFactory';
export const ɵɵngDeclareInjectable = 'ɵɵngDeclareInjectable';
export const ɵɵngDeclareInjector = 'ɵɵngDeclareInjector';
export const ɵɵngDeclareNgModule = 'ɵɵngDeclareNgModule';
export const ɵɵngDeclarePipe = 'ɵɵngDeclarePipe';
export const declarationFunctions = [
ɵɵngDeclareDirective, ɵɵngDeclareComponent, ɵɵngDeclareFactory, ɵɵngDeclareInjectable,
ɵɵngDeclareInjector, ɵɵngDeclareNgModule, ɵɵngDeclarePipe
ɵɵngDeclareDirective, ɵɵngDeclareClassMetadata, ɵɵngDeclareComponent, ɵɵngDeclareFactory,
ɵɵngDeclareInjectable, ɵɵngDeclareInjector, ɵɵngDeclareNgModule, ɵɵngDeclarePipe
];

interface LinkerRange<TExpression> {
Expand Down Expand Up @@ -91,6 +93,7 @@ export class PartialLinkerSelector<TStatement, TExpression> {
environment: LinkerEnvironment<TStatement, TExpression>, sourceUrl: AbsoluteFsPath,
code: string): Map<string, LinkerRange<TExpression>[]> {
const partialDirectiveLinkerVersion1 = new PartialDirectiveLinkerVersion1(sourceUrl, code);
const partialClassMetadataLinkerVersion1 = new PartialClassMetadataLinkerVersion1();
const partialComponentLinkerVersion1 = new PartialComponentLinkerVersion1(
environment, createGetSourceFile(sourceUrl, code, environment.sourceFileLoader), sourceUrl,
code);
Expand All @@ -106,6 +109,10 @@ export class PartialLinkerSelector<TStatement, TExpression> {
{range: '0.0.0-PLACEHOLDER', linker: partialDirectiveLinkerVersion1},
{range: '>=11.1.0-next.1', linker: partialDirectiveLinkerVersion1},
]);
linkers.set(ɵɵngDeclareClassMetadata, [
{range: '0.0.0-PLACEHOLDER', linker: partialClassMetadataLinkerVersion1},
{range: '>=11.1.0-next.1', linker: partialClassMetadataLinkerVersion1},
]);
linkers.set(ɵɵngDeclareComponent, [
{range: '0.0.0-PLACEHOLDER', linker: partialComponentLinkerVersion1},
{range: '>=11.1.0-next.1', linker: partialComponentLinkerVersion1},
Expand Down
18 changes: 12 additions & 6 deletions packages/compiler-cli/src/ngtsc/annotations/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {compileComponentFromMetadata, compileDeclareComponentFromMetadata, ConstantPool, CssSelector, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, FactoryTarget, InterpolationConfig, LexerRange, makeBindingParser, ParsedTemplate, ParseSourceFile, parseTemplate, R3ComponentMetadata, R3FactoryMetadata, R3TargetBinder, R3UsedDirectiveMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr} from '@angular/compiler';
import {compileClassMetadata, compileComponentFromMetadata, compileDeclareClassMetadata, compileDeclareComponentFromMetadata, ConstantPool, CssSelector, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, FactoryTarget, InterpolationConfig, LexerRange, makeBindingParser, ParsedTemplate, ParseSourceFile, parseTemplate, R3ClassMetadata, R3ComponentMetadata, R3FactoryMetadata, R3TargetBinder, R3UsedDirectiveMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr} from '@angular/compiler';
import * as ts from 'typescript';

import {Cycle, CycleAnalyzer, CycleHandlingStrategy} from '../../cycles';
Expand All @@ -30,7 +30,7 @@ import {ResourceLoader} from './api';
import {createValueHasWrongTypeError, getDirectiveDiagnostics, getProviderDiagnostics} from './diagnostics';
import {DirectiveSymbol, extractDirectiveMetadata, parseFieldArrayValue} from './directive';
import {compileDeclareFactory, compileNgFactoryDefField} from './factory';
import {generateSetClassMetadataCall} from './metadata';
import {extractClassMetadata} from './metadata';
import {NgModuleSymbol} from './ng_module';
import {compileResults, findAngularDecorator, isAngularCoreReference, isExpressionForwardReference, readBaseClass, resolveProvidersRequiringFactory, toFactoryMetadata, unwrapExpression, wrapFunctionExpressionsInParens} from './util';

Expand All @@ -55,7 +55,7 @@ export interface ComponentAnalysisData {
baseClass: Reference<ClassDeclaration>|'dynamic'|null;
typeCheckMeta: DirectiveTypeCheckMeta;
template: ParsedTemplateWithSource;
metadataStmt: Statement|null;
classMetadata: R3ClassMetadata|null;

inputs: ClassPropertyMapping;
outputs: ClassPropertyMapping;
Expand Down Expand Up @@ -489,7 +489,7 @@ export class ComponentDecoratorHandler implements
relativeContextFilePath,
},
typeCheckMeta: extractDirectiveTypeCheckMeta(node, inputs, this.reflector),
metadataStmt: generateSetClassMetadataCall(
classMetadata: extractClassMetadata(
node, this.reflector, this.defaultImportRecorder, this.isCore,
this.annotateForClosureCompiler),
template,
Expand Down Expand Up @@ -870,7 +870,10 @@ export class ComponentDecoratorHandler implements
const meta: R3ComponentMetadata = {...analysis.meta, ...resolution};
const fac = compileNgFactoryDefField(toFactoryMetadata(meta, FactoryTarget.Component));
const def = compileComponentFromMetadata(meta, pool, makeBindingParser());
return compileResults(fac, def, analysis.metadataStmt, 'ɵcmp');
const classMetadata = analysis.classMetadata !== null ?
compileClassMetadata(analysis.classMetadata).toStmt() :
null;
return compileResults(fac, def, classMetadata, 'ɵcmp');
}

compilePartial(
Expand All @@ -882,7 +885,10 @@ export class ComponentDecoratorHandler implements
const meta: R3ComponentMetadata = {...analysis.meta, ...resolution};
const fac = compileDeclareFactory(toFactoryMetadata(meta, FactoryTarget.Component));
const def = compileDeclareComponentFromMetadata(meta, analysis.template);
return compileResults(fac, def, analysis.metadataStmt, 'ɵcmp');
const classMetadata = analysis.classMetadata !== null ?
compileDeclareClassMetadata(analysis.classMetadata).toStmt() :
null;
return compileResults(fac, def, classMetadata, 'ɵcmp');
}

private _resolveLiteral(decorator: Decorator): ts.ObjectLiteralExpression {
Expand Down
18 changes: 12 additions & 6 deletions packages/compiler-cli/src/ngtsc/annotations/src/directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, Expression, ExternalExpr, FactoryTarget, getSafePropertyAccessString, makeBindingParser, ParsedHostBindings, ParseError, parseHostBindings, R3DirectiveMetadata, R3FactoryMetadata, R3QueryMetadata, Statement, verifyHostBindings, WrappedNodeExpr} from '@angular/compiler';
import {compileClassMetadata, compileDeclareClassMetadata, compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, Expression, ExternalExpr, FactoryTarget, getSafePropertyAccessString, makeBindingParser, ParsedHostBindings, ParseError, parseHostBindings, R3ClassMetadata, R3DirectiveMetadata, R3FactoryMetadata, R3QueryMetadata, Statement, verifyHostBindings, WrappedNodeExpr} from '@angular/compiler';
import {emitDistinctChangesOnlyDefaultValue} from '@angular/compiler/src/core';
import * as ts from 'typescript';

Expand All @@ -23,7 +23,7 @@ import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFl

import {createValueHasWrongTypeError, getDirectiveDiagnostics, getProviderDiagnostics, getUndecoratedClassWithAngularFeaturesDiagnostic} from './diagnostics';
import {compileDeclareFactory, compileNgFactoryDefField} from './factory';
import {generateSetClassMetadataCall} from './metadata';
import {extractClassMetadata} from './metadata';
import {compileResults, createSourceSpan, findAngularDecorator, getConstructorDependencies, isAngularDecorator, readBaseClass, resolveProvidersRequiringFactory, toFactoryMetadata, tryUnwrapForwardRef, unwrapConstructorDependencies, unwrapExpression, validateConstructorDependencies, wrapFunctionExpressionsInParens, wrapTypeReference} from './util';

const EMPTY_OBJECT: {[key: string]: string} = {};
Expand All @@ -40,7 +40,7 @@ export interface DirectiveHandlerData {
baseClass: Reference<ClassDeclaration>|'dynamic'|null;
typeCheckMeta: DirectiveTypeCheckMeta;
meta: R3DirectiveMetadata;
metadataStmt: Statement|null;
classMetadata: R3ClassMetadata|null;
providersRequiringFactory: Set<Reference<ClassDeclaration>>|null;
inputs: ClassPropertyMapping;
outputs: ClassPropertyMapping;
Expand Down Expand Up @@ -233,7 +233,7 @@ export class DirectiveDecoratorHandler implements
inputs: directiveResult.inputs,
outputs: directiveResult.outputs,
meta: analysis,
metadataStmt: generateSetClassMetadataCall(
classMetadata: extractClassMetadata(
node, this.reflector, this.defaultImportRecorder, this.isCore,
this.annotateForClosureCompiler),
baseClass: readBaseClass(node, this.reflector, this.evaluator),
Expand Down Expand Up @@ -304,15 +304,21 @@ export class DirectiveDecoratorHandler implements
resolution: Readonly<unknown>, pool: ConstantPool): CompileResult[] {
const fac = compileNgFactoryDefField(toFactoryMetadata(analysis.meta, FactoryTarget.Directive));
const def = compileDirectiveFromMetadata(analysis.meta, pool, makeBindingParser());
return compileResults(fac, def, analysis.metadataStmt, 'ɵdir');
const classMetadata = analysis.classMetadata !== null ?
compileClassMetadata(analysis.classMetadata).toStmt() :
null;
return compileResults(fac, def, classMetadata, 'ɵdir');
}

compilePartial(
node: ClassDeclaration, analysis: Readonly<DirectiveHandlerData>,
resolution: Readonly<unknown>): CompileResult[] {
const fac = compileDeclareFactory(toFactoryMetadata(analysis.meta, FactoryTarget.Directive));
const def = compileDeclareDirectiveFromMetadata(analysis.meta);
return compileResults(fac, def, analysis.metadataStmt, 'ɵdir');
const classMetadata = analysis.classMetadata !== null ?
compileDeclareClassMetadata(analysis.classMetadata).toStmt() :
null;
return compileResults(fac, def, classMetadata, 'ɵdir');
}

/**
Expand Down
23 changes: 13 additions & 10 deletions packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {compileDeclareInjectableFromMetadata, compileInjectable, createR3ProviderExpression, Expression, FactoryTarget, LiteralExpr, R3CompiledExpression, R3DependencyMetadata, R3InjectableMetadata, R3ProviderExpression, Statement, WrappedNodeExpr} from '@angular/compiler';
import {compileClassMetadata, CompileClassMetadataFn, compileDeclareClassMetadata, compileDeclareInjectableFromMetadata, compileInjectable, createR3ProviderExpression, Expression, FactoryTarget, LiteralExpr, R3ClassMetadata, R3CompiledExpression, R3DependencyMetadata, R3InjectableMetadata, R3ProviderExpression, Statement, WrappedNodeExpr} from '@angular/compiler';
import * as ts from 'typescript';

import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
Expand All @@ -17,12 +17,12 @@ import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';

import {compileDeclareFactory, CompileFactoryFn, compileNgFactoryDefField} from './factory';
import {generateSetClassMetadataCall} from './metadata';
import {extractClassMetadata} from './metadata';
import {findAngularDecorator, getConstructorDependencies, getValidConstructorDependencies, isAngularCore, toFactoryMetadata, tryUnwrapForwardRef, unwrapConstructorDependencies, validateConstructorDependencies, wrapTypeReference} from './util';

export interface InjectableHandlerData {
meta: R3InjectableMetadata;
metadataStmt: Statement|null;
classMetadata: R3ClassMetadata|null;
ctorDeps: R3DependencyMetadata[]|'invalid'|null;
needsFactory: boolean;
}
Expand Down Expand Up @@ -76,8 +76,8 @@ export class InjectableDecoratorHandler implements
ctorDeps: extractInjectableCtorDeps(
node, meta, decorator, this.reflector, this.defaultImportRecorder, this.isCore,
this.strictCtorDeps),
metadataStmt: generateSetClassMetadataCall(
node, this.reflector, this.defaultImportRecorder, this.isCore),
classMetadata:
extractClassMetadata(node, this.reflector, this.defaultImportRecorder, this.isCore),
// Avoid generating multiple factories if a class has
// more Angular decorators, apart from Injectable.
needsFactory: !decorators ||
Expand All @@ -96,27 +96,30 @@ export class InjectableDecoratorHandler implements

compileFull(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>): CompileResult[] {
return this.compile(
compileNgFactoryDefField, meta => compileInjectable(meta, false), node, analysis);
compileNgFactoryDefField, meta => compileInjectable(meta, false), compileClassMetadata,
node, analysis);
}

compilePartial(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>):
CompileResult[] {
return this.compile(
compileDeclareFactory, compileDeclareInjectableFromMetadata, node, analysis);
compileDeclareFactory, compileDeclareInjectableFromMetadata, compileDeclareClassMetadata,
node, analysis);
}

private compile(
compileFactoryFn: CompileFactoryFn,
compileInjectableFn: (meta: R3InjectableMetadata) => R3CompiledExpression,
node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>): CompileResult[] {
compileClassMetadataFn: CompileClassMetadataFn, node: ClassDeclaration,
analysis: Readonly<InjectableHandlerData>): CompileResult[] {
const results: CompileResult[] = [];

if (analysis.needsFactory) {
const meta = analysis.meta;
const factoryRes = compileFactoryFn(
toFactoryMetadata({...meta, deps: analysis.ctorDeps}, FactoryTarget.Injectable));
if (analysis.metadataStmt !== null) {
factoryRes.statements.push(analysis.metadataStmt);
if (analysis.classMetadata !== null) {
factoryRes.statements.push(compileClassMetadataFn(analysis.classMetadata).toStmt());
}
results.push(factoryRes);
}
Expand Down
34 changes: 13 additions & 21 deletions packages/compiler-cli/src/ngtsc/annotations/src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/

import {devOnlyGuardedExpression, Expression, ExternalExpr, FunctionExpr, Identifiers, InvokeFunctionExpr, LiteralArrayExpr, LiteralExpr, literalMap, NONE_TYPE, ReturnStatement, Statement, WrappedNodeExpr} from '@angular/compiler';
import {Expression, FunctionExpr, LiteralArrayExpr, LiteralExpr, literalMap, R3ClassMetadata, ReturnStatement, WrappedNodeExpr} from '@angular/compiler';
import * as ts from 'typescript';

import {DefaultImportRecorder} from '../../imports';
import {CtorParameter, DeclarationNode, Decorator, ReflectionHost, TypeValueReferenceKind} from '../../reflection';

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


/**
* Given a class declaration, generate a call to `setClassMetadata` with the Angular metadata
* present on the class or its member fields. An ngDevMode guard is used to allow the call to be
Expand All @@ -23,10 +22,10 @@ import {valueReferenceToExpression, wrapFunctionExpressionsInParens} from './uti
* If no such metadata is present, this function returns `null`. Otherwise, the call is returned
* as a `Statement` for inclusion along with the class.
*/
export function generateSetClassMetadataCall(
export function extractClassMetadata(
clazz: DeclarationNode, reflection: ReflectionHost,
defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
annotateForClosureCompiler?: boolean): Statement|null {
annotateForClosureCompiler?: boolean): R3ClassMetadata|null {
if (!reflection.isClass(clazz)) {
return null;
}
Expand All @@ -50,10 +49,10 @@ export function generateSetClassMetadataCall(
if (ngClassDecorators.length === 0) {
return null;
}
const metaDecorators = ts.createArrayLiteral(ngClassDecorators);
const metaDecorators = new WrappedNodeExpr(ts.createArrayLiteral(ngClassDecorators));

// Convert the constructor parameters to metadata, passing null if none are present.
let metaCtorParameters: Expression = new LiteralExpr(null);
let metaCtorParameters: Expression|null = null;
const classCtorParameters = reflection.getConstructorParameters(clazz);
if (classCtorParameters !== null) {
const ctorParameters = classCtorParameters.map(
Expand All @@ -64,7 +63,7 @@ export function generateSetClassMetadataCall(
}

// Do the same for property decorators.
let metaPropDecorators: ts.Expression = ts.createNull();
let metaPropDecorators: Expression|null = null;
const classMembers = reflection.getMembersOfClass(clazz).filter(
member => !member.isStatic && member.decorators !== null && member.decorators.length > 0);
const duplicateDecoratedMemberNames =
Expand All @@ -80,22 +79,15 @@ export function generateSetClassMetadataCall(
const decoratedMembers = classMembers.map(
member => classMemberToMetadata(member.nameNode ?? member.name, member.decorators!, isCore));
if (decoratedMembers.length > 0) {
metaPropDecorators = ts.createObjectLiteral(decoratedMembers);
metaPropDecorators = new WrappedNodeExpr(ts.createObjectLiteral(decoratedMembers));
}

// Generate a pure call to setClassMetadata with the class identifier and its metadata.
const setClassMetadata = new ExternalExpr(Identifiers.setClassMetadata);
const fnCall = new InvokeFunctionExpr(
/* fn */ setClassMetadata,
/* args */
[
new WrappedNodeExpr(id),
new WrappedNodeExpr(metaDecorators),
metaCtorParameters,
new WrappedNodeExpr(metaPropDecorators),
]);
const iife = new FunctionExpr([], [devOnlyGuardedExpression(fnCall).toStmt()]);
return iife.callFn([]).toStmt();
return {
type: new WrappedNodeExpr(id),
decorators: metaDecorators,
ctorParameters: metaCtorParameters,
propDecorators: metaPropDecorators,
};
}

/**
Expand Down

0 comments on commit c20db69

Please sign in to comment.