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
This commit is contained in:
parent
8c0a92bb45
commit
87e9cd643b
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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(),
|
||||
},
|
||||
|
@ -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}.`);
|
||||
|
|
Loading…
Reference in New Issue