Skip to content

Commit

Permalink
feat(compiler-cli): implement partial directive declaration linking (#…
Browse files Browse the repository at this point in the history
…39518)

This commit implements the logic to compile a partial declaration of a
directive into its full AOT compilation output.

PR Close #39518
  • Loading branch information
JoostK authored and josephperrott committed Nov 4, 2020
1 parent 8c0a92b commit 87e9cd6
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const NO_STATEMENTS: Readonly<any[]> = [] as const;
* This class is responsible for linking all the partial declarations found in a single file.
*/
export class FileLinker<TConstantScope, TStatement, TExpression> {
private linkerSelector = new PartialLinkerSelector<TStatement, TExpression>();
private linkerSelector = new PartialLinkerSelector<TExpression>();
private emitScopes = new Map<TConstantScope, EmitScope<TStatement, TExpression>>();

constructor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import {PartialLinker} from './partial_linker';
/**
* A `PartialLinker` that is designed to process `$ngDeclareComponent()` call expressions.
*/
export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
PartialLinker<TStatement, TExpression> {
export class PartialComponentLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
linkPartialDeclaration(
sourceUrl: string, code: string, constantPool: ConstantPool,
metaObj: AstObject<TExpression>): o.Expression {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TStatement, TExpression> implements
PartialLinker<TStatement, TExpression> {
export class PartialDirectiveLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
linkPartialDeclaration(
sourceUrl: string, code: string, constantPool: ConstantPool,
metaObj: AstObject<TExpression>): 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<TExpression>(
metaObj: AstObject<TExpression>, 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<TExpression>(value: AstValue<TExpression>): 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<TExpression>(metaObj: AstObject<TExpression>): 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<TExpression>(obj: AstObject<TExpression>): 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<TExpression>(wrapped: o.WrappedNodeExpr<TExpression>): 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));
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<TStatement, TExpression> {
export interface PartialLinker<TExpression> {
/**
* Link the partial declaration `metaObj` information to generate a full definition expression.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TStatement, TExpression> {
private linkers: Record<string, Record<number, PartialLinker<TStatement, TExpression>>> = {
export class PartialLinkerSelector<TExpression> {
private linkers: Record<string, Record<number, PartialLinker<TExpression>>> = {
'$ngDeclareDirective': {
1: new PartialDirectiveLinkerVersion1(),
},
Expand All @@ -30,7 +30,7 @@ export class PartialLinkerSelector<TStatement, TExpression> {
* 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<TStatement, TExpression> {
getLinker(functionName: string, version: number): PartialLinker<TExpression> {
const versions = this.linkers[functionName];
if (versions === undefined) {
throw new Error(`Unknown partial declaration function ${functionName}.`);
Expand Down

0 comments on commit 87e9cd6

Please sign in to comment.