From 87e9cd643bf9743d68406dfd11268715426a277b Mon Sep 17 00:00:00 2001 From: JoostK Date: Fri, 30 Oct 2020 23:50:02 +0100 Subject: [PATCH] feat(compiler-cli): implement partial directive declaration linking (#39518) This commit implements the logic to compile a partial declaration of a directive into its full AOT compilation output. PR Close #39518 --- .../linker/src/file_linker/file_linker.ts | 2 +- .../partial_component_linker_1.ts | 3 +- .../partial_directive_linker_1.ts | 142 +++++++++++++++++- .../partial_linkers/partial_linker.ts | 2 +- .../partial_linker_selector.ts | 6 +- 5 files changed, 143 insertions(+), 12 deletions(-) diff --git a/packages/compiler-cli/linker/src/file_linker/file_linker.ts b/packages/compiler-cli/linker/src/file_linker/file_linker.ts index 2b541fa372..936ef1ac59 100644 --- a/packages/compiler-cli/linker/src/file_linker/file_linker.ts +++ b/packages/compiler-cli/linker/src/file_linker/file_linker.ts @@ -18,7 +18,7 @@ export const NO_STATEMENTS: Readonly = [] as const; * This class is responsible for linking all the partial declarations found in a single file. */ export class FileLinker { - private linkerSelector = new PartialLinkerSelector(); + private linkerSelector = new PartialLinkerSelector(); private emitScopes = new Map>(); constructor( diff --git a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts index 1c7bcde242..b32dda466d 100644 --- a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts +++ b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts @@ -15,8 +15,7 @@ import {PartialLinker} from './partial_linker'; /** * A `PartialLinker` that is designed to process `$ngDeclareComponent()` call expressions. */ -export class PartialComponentLinkerVersion1 implements - PartialLinker { +export class PartialComponentLinkerVersion1 implements PartialLinker { linkPartialDeclaration( sourceUrl: string, code: string, constantPool: ConstantPool, metaObj: AstObject): o.Expression { diff --git a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_directive_linker_1.ts b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_directive_linker_1.ts index bd3e8b80a9..bcd80f86cf 100644 --- a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_directive_linker_1.ts +++ b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_directive_linker_1.ts @@ -5,21 +5,153 @@ * 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 {ConstantPool} from '@angular/compiler'; +import {compileDirectiveFromMetadata, ConstantPool, makeBindingParser, ParseLocation, ParseSourceFile, ParseSourceSpan, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata, R3Reference} from '@angular/compiler'; import * as o from '@angular/compiler/src/output/output_ast'; -import {AstObject} from '../../ast/ast_value'; +import {Range} from '../../ast/ast_host'; +import {AstObject, AstValue} from '../../ast/ast_value'; +import {FatalLinkerError} from '../../fatal_linker_error'; import {PartialLinker} from './partial_linker'; /** * A `PartialLinker` that is designed to process `$ngDeclareDirective()` call expressions. */ -export class PartialDirectiveLinkerVersion1 implements - PartialLinker { +export class PartialDirectiveLinkerVersion1 implements PartialLinker { linkPartialDeclaration( sourceUrl: string, code: string, constantPool: ConstantPool, metaObj: AstObject): o.Expression { - throw new Error('Not implemented.'); + const meta = toR3DirectiveMeta(metaObj, code, sourceUrl); + const def = compileDirectiveFromMetadata(meta, constantPool, makeBindingParser()); + return def.expression; } } + +/** + * Derives the `R3DirectiveMetadata` structure from the AST object. + */ +export function toR3DirectiveMeta( + metaObj: AstObject, code: string, sourceUrl: string): R3DirectiveMetadata { + const typeExpr = metaObj.getValue('type'); + const typeName = typeExpr.getSymbolName(); + if (typeName === null) { + throw new FatalLinkerError( + typeExpr.expression, 'Unsupported type, its name could not be determined'); + } + + return { + typeSourceSpan: createSourceSpan(typeExpr.getRange(), code, sourceUrl), + type: wrapReference(typeExpr.getOpaque()), + typeArgumentCount: 0, + internalType: metaObj.getOpaque('type'), + deps: null, + host: toHostMetadata(metaObj), + inputs: metaObj.has('inputs') ? metaObj.getObject('inputs').toLiteral(toInputMapping) : {}, + outputs: metaObj.has('outputs') ? + metaObj.getObject('outputs').toLiteral(value => value.getString()) : + {}, + queries: metaObj.has('queries') ? + metaObj.getArray('queries').map(entry => toQueryMetadata(entry.getObject())) : + [], + viewQueries: metaObj.has('viewQueries') ? + metaObj.getArray('viewQueries').map(entry => toQueryMetadata(entry.getObject())) : + [], + providers: metaObj.has('providers') ? metaObj.getOpaque('providers') : null, + fullInheritance: false, + selector: metaObj.has('selector') ? metaObj.getString('selector') : null, + exportAs: metaObj.has('exportAs') ? + metaObj.getArray('exportAs').map(entry => entry.getString()) : + null, + lifecycle: { + usesOnChanges: metaObj.has('usesOnChanges') ? metaObj.getBoolean('usesOnChanges') : false, + }, + name: typeName, + usesInheritance: metaObj.has('usesInheritance') ? metaObj.getBoolean('usesInheritance') : false, + }; +} + +/** + * Decodes the AST value for a single input to its representation as used in the metadata. + */ +function toInputMapping(value: AstValue): string|[string, string] { + if (value.isString()) { + return value.getString(); + } + + const values = value.getArray().map(innerValue => innerValue.getString()); + if (values.length !== 2) { + throw new FatalLinkerError( + value.expression, + 'Unsupported input, expected a string or an array containing exactly two strings'); + } + return values as [string, string]; +} + +/** + * Extracts the host metadata configuration from the AST metadata object. + */ +function toHostMetadata(metaObj: AstObject): R3HostMetadata { + if (!metaObj.has('host')) { + return { + attributes: {}, + listeners: {}, + properties: {}, + specialAttributes: {}, + }; + } + + const host = metaObj.getObject('host'); + + const specialAttributes: R3HostMetadata['specialAttributes'] = {}; + if (host.has('styleAttribute')) { + specialAttributes.styleAttr = host.getString('styleAttribute'); + } + if (host.has('classAttribute')) { + specialAttributes.classAttr = host.getString('classAttribute'); + } + + return { + attributes: host.has('attributes') ? + host.getObject('attributes').toLiteral(value => value.getOpaque()) : + {}, + listeners: host.has('listeners') ? + host.getObject('listeners').toLiteral(value => value.getString()) : + {}, + properties: host.has('properties') ? + host.getObject('properties').toLiteral(value => value.getString()) : + {}, + specialAttributes, + }; +} + +/** + * Extracts the metadata for a single query from an AST object. + */ +function toQueryMetadata(obj: AstObject): R3QueryMetadata { + let predicate: R3QueryMetadata['predicate']; + const predicateExpr = obj.getValue('predicate'); + if (predicateExpr.isArray()) { + predicate = predicateExpr.getArray().map(entry => entry.getString()); + } else { + predicate = predicateExpr.getOpaque(); + } + return { + propertyName: obj.getString('propertyName'), + first: obj.getBoolean('first'), + predicate, + descendants: obj.getBoolean('descendants'), + read: obj.has('read') ? obj.getOpaque('read') : null, + static: obj.getBoolean('static'), + }; +} + +function wrapReference(wrapped: o.WrappedNodeExpr): R3Reference { + return {value: wrapped, type: wrapped}; +} + +export function createSourceSpan(range: Range, code: string, sourceUrl: string): ParseSourceSpan { + const sourceFile = new ParseSourceFile(code, sourceUrl); + const startLocation = + new ParseLocation(sourceFile, range.startPos, range.startLine, range.startCol); + return new ParseSourceSpan(startLocation, startLocation.moveBy(range.endPos - range.startPos)); +} diff --git a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker.ts b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker.ts index cc5031f165..e9d0119793 100644 --- a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker.ts +++ b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker.ts @@ -12,7 +12,7 @@ import {AstObject} from '../../ast/ast_value'; /** * An interface for classes that can link partial declarations into full definitions. */ -export interface PartialLinker { +export interface PartialLinker { /** * Link the partial declaration `metaObj` information to generate a full definition expression. */ diff --git a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker_selector.ts b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker_selector.ts index ba6ffe6aa2..f2490ba17b 100644 --- a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker_selector.ts +++ b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker_selector.ts @@ -9,8 +9,8 @@ import {PartialComponentLinkerVersion1} from './partial_component_linker_1'; import {PartialDirectiveLinkerVersion1} from './partial_directive_linker_1'; import {PartialLinker} from './partial_linker'; -export class PartialLinkerSelector { - private linkers: Record>> = { +export class PartialLinkerSelector { + private linkers: Record>> = { '$ngDeclareDirective': { 1: new PartialDirectiveLinkerVersion1(), }, @@ -30,7 +30,7 @@ export class PartialLinkerSelector { * Returns the `PartialLinker` that can handle functions with the given name and version. * Throws an error if there is none. */ - getLinker(functionName: string, version: number): PartialLinker { + getLinker(functionName: string, version: number): PartialLinker { const versions = this.linkers[functionName]; if (versions === undefined) { throw new Error(`Unknown partial declaration function ${functionName}.`);