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.
|
* This class is responsible for linking all the partial declarations found in a single file.
|
||||||
*/
|
*/
|
||||||
export class FileLinker<TConstantScope, TStatement, TExpression> {
|
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>>();
|
private emitScopes = new Map<TConstantScope, EmitScope<TStatement, TExpression>>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -15,8 +15,7 @@ import {PartialLinker} from './partial_linker';
|
|||||||
/**
|
/**
|
||||||
* A `PartialLinker` that is designed to process `$ngDeclareComponent()` call expressions.
|
* A `PartialLinker` that is designed to process `$ngDeclareComponent()` call expressions.
|
||||||
*/
|
*/
|
||||||
export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
|
export class PartialComponentLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
|
||||||
PartialLinker<TStatement, TExpression> {
|
|
||||||
linkPartialDeclaration(
|
linkPartialDeclaration(
|
||||||
sourceUrl: string, code: string, constantPool: ConstantPool,
|
sourceUrl: string, code: string, constantPool: ConstantPool,
|
||||||
metaObj: AstObject<TExpression>): o.Expression {
|
metaObj: AstObject<TExpression>): o.Expression {
|
||||||
|
@ -5,21 +5,153 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* 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 * 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';
|
import {PartialLinker} from './partial_linker';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A `PartialLinker` that is designed to process `$ngDeclareDirective()` call expressions.
|
* A `PartialLinker` that is designed to process `$ngDeclareDirective()` call expressions.
|
||||||
*/
|
*/
|
||||||
export class PartialDirectiveLinkerVersion1<TStatement, TExpression> implements
|
export class PartialDirectiveLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
|
||||||
PartialLinker<TStatement, TExpression> {
|
|
||||||
linkPartialDeclaration(
|
linkPartialDeclaration(
|
||||||
sourceUrl: string, code: string, constantPool: ConstantPool,
|
sourceUrl: string, code: string, constantPool: ConstantPool,
|
||||||
metaObj: AstObject<TExpression>): o.Expression {
|
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.
|
* 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.
|
* 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 {PartialDirectiveLinkerVersion1} from './partial_directive_linker_1';
|
||||||
import {PartialLinker} from './partial_linker';
|
import {PartialLinker} from './partial_linker';
|
||||||
|
|
||||||
export class PartialLinkerSelector<TStatement, TExpression> {
|
export class PartialLinkerSelector<TExpression> {
|
||||||
private linkers: Record<string, Record<number, PartialLinker<TStatement, TExpression>>> = {
|
private linkers: Record<string, Record<number, PartialLinker<TExpression>>> = {
|
||||||
'$ngDeclareDirective': {
|
'$ngDeclareDirective': {
|
||||||
1: new PartialDirectiveLinkerVersion1(),
|
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.
|
* Returns the `PartialLinker` that can handle functions with the given name and version.
|
||||||
* Throws an error if there is none.
|
* 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];
|
const versions = this.linkers[functionName];
|
||||||
if (versions === undefined) {
|
if (versions === undefined) {
|
||||||
throw new Error(`Unknown partial declaration function ${functionName}.`);
|
throw new Error(`Unknown partial declaration function ${functionName}.`);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user