feat(compiler): add dependency info and ng-content selectors to metadata (#35695)
This commit augments the `FactoryDef` declaration of Angular decorated classes to contain information about the parameter decorators used in the constructor. If no constructor is present, or none of the parameters have any Angular decorators, then this will be represented using the `null` type. Otherwise, a tuple type is used where the entry at index `i` corresponds with parameter `i`. Each tuple entry can be one of two types: 1. If the associated parameter does not have any Angular decorators, the tuple entry will be the `null` type. 2. Otherwise, a type literal is used that may declare at least one of the following properties: - "attribute": if `@Attribute` is present. The injected attribute's name is used as string literal type, or the `unknown` type if the attribute name is not a string literal. - "self": if `@Self` is present, always of type `true`. - "skipSelf": if `@SkipSelf` is present, always of type `true`. - "host": if `@Host` is present, always of type `true`. - "optional": if `@Optional` is present, always of type `true`. A property is only present if the corresponding decorator is used. Note that the `@Inject` decorator is currently not included, as it's non-trivial to properly convert the token's value expression to a type that is valid in a declaration file. Additionally, the `ComponentDefWithMeta` declaration that is created for Angular components has been extended to include all selectors on `ng-content` elements within the component's template. This additional metadata is useful for tooling such as the Angular Language Service, as it provides the ability to offer suggestions for directives/components defined in libraries. At the moment, such tooling extracts the necessary information from the _metadata.json_ manifest file as generated by ngc, however this metadata representation is being replaced by the information emitted into the declaration files. Resolves FW-1870 PR Close #35695
This commit is contained in:
@ -296,11 +296,12 @@ function convertR3DependencyMetadata(facade: R3DependencyMetadataFacade): R3Depe
|
||||
}
|
||||
return {
|
||||
token: tokenExpr,
|
||||
attribute: null,
|
||||
resolved: facade.resolved,
|
||||
host: facade.host,
|
||||
optional: facade.optional,
|
||||
self: facade.self,
|
||||
skipSelf: facade.skipSelf
|
||||
skipSelf: facade.skipSelf,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -141,6 +141,13 @@ export interface R3DependencyMetadata {
|
||||
*/
|
||||
token: o.Expression;
|
||||
|
||||
/**
|
||||
* If an @Attribute decorator is present, this is the literal type of the attribute name, or
|
||||
* the unknown type if no literal type is available (e.g. the attribute name is an expression).
|
||||
* Will be null otherwise.
|
||||
*/
|
||||
attribute: o.Expression|null;
|
||||
|
||||
/**
|
||||
* An enum indicating whether this dependency has special meaning to Angular and needs to be
|
||||
* injected specially.
|
||||
@ -180,6 +187,7 @@ export interface R3FactoryFn {
|
||||
export function compileFactoryFunction(meta: R3FactoryMetadata): R3FactoryFn {
|
||||
const t = o.variable('t');
|
||||
const statements: o.Statement[] = [];
|
||||
let ctorDepsType: o.Type = o.NONE_TYPE;
|
||||
|
||||
// The type to instantiate via constructor invocation. If there is no delegated factory, meaning
|
||||
// this type is always created by constructor invocation, then this is the type-to-create
|
||||
@ -197,6 +205,8 @@ export function compileFactoryFunction(meta: R3FactoryMetadata): R3FactoryFn {
|
||||
ctorExpr = new o.InstantiateExpr(
|
||||
typeForCtor,
|
||||
injectDependencies(meta.deps, meta.injectFn, meta.target === R3FactoryTarget.Pipe));
|
||||
|
||||
ctorDepsType = createCtorDepsType(meta.deps);
|
||||
}
|
||||
} else {
|
||||
const baseFactory = o.variable(`ɵ${meta.name}_BaseFactory`);
|
||||
@ -269,8 +279,9 @@ export function compileFactoryFunction(meta: R3FactoryMetadata): R3FactoryFn {
|
||||
[new o.FnParam('t', o.DYNAMIC_TYPE)], body, o.INFERRED_TYPE, undefined,
|
||||
`${meta.name}_Factory`),
|
||||
statements,
|
||||
type: o.expressionType(
|
||||
o.importExpr(R3.FactoryDef, [typeWithParameters(meta.type.type, meta.typeArgumentCount)]))
|
||||
type: o.expressionType(o.importExpr(
|
||||
R3.FactoryDef,
|
||||
[typeWithParameters(meta.type.type, meta.typeArgumentCount), ctorDepsType]))
|
||||
};
|
||||
}
|
||||
|
||||
@ -319,6 +330,49 @@ function compileInjectDependency(
|
||||
}
|
||||
}
|
||||
|
||||
function createCtorDepsType(deps: R3DependencyMetadata[]): o.Type {
|
||||
let hasTypes = false;
|
||||
const attributeTypes = deps.map(dep => {
|
||||
const type = createCtorDepType(dep);
|
||||
if (type !== null) {
|
||||
hasTypes = true;
|
||||
return type;
|
||||
} else {
|
||||
return o.literal(null);
|
||||
}
|
||||
});
|
||||
|
||||
if (hasTypes) {
|
||||
return o.expressionType(o.literalArr(attributeTypes));
|
||||
} else {
|
||||
return o.NONE_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
function createCtorDepType(dep: R3DependencyMetadata): o.LiteralMapExpr|null {
|
||||
const entries: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||
|
||||
if (dep.resolved === R3ResolvedDependencyType.Attribute) {
|
||||
if (dep.attribute !== null) {
|
||||
entries.push({key: 'attribute', value: dep.attribute, quoted: false});
|
||||
}
|
||||
}
|
||||
if (dep.optional) {
|
||||
entries.push({key: 'optional', value: o.literal(true), quoted: false});
|
||||
}
|
||||
if (dep.host) {
|
||||
entries.push({key: 'host', value: o.literal(true), quoted: false});
|
||||
}
|
||||
if (dep.self) {
|
||||
entries.push({key: 'self', value: o.literal(true), quoted: false});
|
||||
}
|
||||
if (dep.skipSelf) {
|
||||
entries.push({key: 'skipSelf', value: o.literal(true), quoted: false});
|
||||
}
|
||||
|
||||
return entries.length > 0 ? o.literalMap(entries) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function useful for extracting `R3DependencyMetadata` from a Render2
|
||||
* `CompileTypeMetadata` instance.
|
||||
@ -348,7 +402,7 @@ export function dependenciesFromGlobalMetadata(
|
||||
// Construct the dependency.
|
||||
deps.push({
|
||||
token,
|
||||
resolved,
|
||||
attribute: null, resolved,
|
||||
host: !!dependency.isHost,
|
||||
optional: !!dependency.isOptional,
|
||||
self: !!dependency.isSelf,
|
||||
|
@ -52,6 +52,7 @@ export interface Render3ParseResult {
|
||||
errors: ParseError[];
|
||||
styles: string[];
|
||||
styleUrls: string[];
|
||||
ngContentSelectors: string[];
|
||||
}
|
||||
|
||||
export function htmlAstToRender3Ast(
|
||||
@ -73,6 +74,7 @@ export function htmlAstToRender3Ast(
|
||||
errors: allErrors,
|
||||
styleUrls: transformer.styleUrls,
|
||||
styles: transformer.styles,
|
||||
ngContentSelectors: transformer.ngContentSelectors,
|
||||
};
|
||||
}
|
||||
|
||||
@ -80,6 +82,7 @@ class HtmlAstToIvyAst implements html.Visitor {
|
||||
errors: ParseError[] = [];
|
||||
styles: string[] = [];
|
||||
styleUrls: string[] = [];
|
||||
ngContentSelectors: string[] = [];
|
||||
private inI18nBlock: boolean = false;
|
||||
|
||||
constructor(private bindingParser: BindingParser) {}
|
||||
@ -189,6 +192,8 @@ class HtmlAstToIvyAst implements html.Visitor {
|
||||
const selector = preparsedElement.selectAttr;
|
||||
const attrs: t.TextAttribute[] = element.attrs.map(attr => this.visitAttribute(attr));
|
||||
parsedElement = new t.Content(selector, attrs, element.sourceSpan, element.i18n);
|
||||
|
||||
this.ngContentSelectors.push(selector);
|
||||
} else if (isTemplateElement) {
|
||||
// `<ng-template>`
|
||||
const attrs = this.extractAttributes(element.name, parsedProperties, i18nAttrsMeta);
|
||||
|
@ -129,6 +129,12 @@ export interface R3ComponentMetadata extends R3DirectiveMetadata {
|
||||
* Parsed nodes of the template.
|
||||
*/
|
||||
nodes: t.Node[];
|
||||
|
||||
/**
|
||||
* Any ng-content selectors extracted from the template. Contains `null` when an ng-content
|
||||
* element without selector is present.
|
||||
*/
|
||||
ngContentSelectors: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -22,7 +22,7 @@ import {CONTENT_ATTR, HOST_ATTR} from '../../style_compiler';
|
||||
import {BindingParser} from '../../template_parser/binding_parser';
|
||||
import {OutputContext, error} from '../../util';
|
||||
import {BoundEvent} from '../r3_ast';
|
||||
import {R3FactoryTarget, compileFactoryFunction} from '../r3_factory';
|
||||
import {R3DependencyMetadata, R3FactoryTarget, R3ResolvedDependencyType, compileFactoryFunction} from '../r3_factory';
|
||||
import {Identifiers as R3} from '../r3_identifiers';
|
||||
import {Render3ParseResult} from '../r3_template_transform';
|
||||
import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, typeWithParameters} from '../util';
|
||||
@ -124,7 +124,9 @@ export function compileDirectiveFromMetadata(
|
||||
addFeatures(definitionMap, meta);
|
||||
const expression = o.importExpr(R3.defineDirective).callFn([definitionMap.toLiteralMap()]);
|
||||
|
||||
const type = createTypeForDef(meta, R3.DirectiveDefWithMeta);
|
||||
const typeParams = createDirectiveTypeParams(meta);
|
||||
const type = o.expressionType(o.importExpr(R3.DirectiveDefWithMeta, typeParams));
|
||||
|
||||
return {expression, type};
|
||||
}
|
||||
|
||||
@ -252,7 +254,11 @@ export function compileComponentFromMetadata(
|
||||
}
|
||||
|
||||
const expression = o.importExpr(R3.defineComponent).callFn([definitionMap.toLiteralMap()]);
|
||||
const type = createTypeForDef(meta, R3.ComponentDefWithMeta);
|
||||
|
||||
|
||||
const typeParams = createDirectiveTypeParams(meta);
|
||||
typeParams.push(stringArrayAsType(meta.template.ngContentSelectors));
|
||||
const type = o.expressionType(o.importExpr(R3.ComponentDefWithMeta, typeParams));
|
||||
|
||||
return {expression, type};
|
||||
}
|
||||
@ -311,7 +317,7 @@ export function compileComponentFromRender2(
|
||||
const meta: R3ComponentMetadata = {
|
||||
...directiveMetadataFromGlobalMetadata(component, outputCtx, reflector),
|
||||
selector: component.selector,
|
||||
template: {nodes: render3Ast.nodes},
|
||||
template: {nodes: render3Ast.nodes, ngContentSelectors: render3Ast.ngContentSelectors},
|
||||
directives: [],
|
||||
pipes: typeMapToExpressionMap(pipeTypeByName, outputCtx),
|
||||
viewQueries: queriesFromGlobalMetadata(component.viewQueries, outputCtx),
|
||||
@ -470,24 +476,24 @@ function stringMapAsType(map: {[key: string]: string | string[]}): o.Type {
|
||||
return o.expressionType(o.literalMap(mapValues));
|
||||
}
|
||||
|
||||
function stringArrayAsType(arr: string[]): o.Type {
|
||||
function stringArrayAsType(arr: ReadonlyArray<string|null>): o.Type {
|
||||
return arr.length > 0 ? o.expressionType(o.literalArr(arr.map(value => o.literal(value)))) :
|
||||
o.NONE_TYPE;
|
||||
}
|
||||
|
||||
function createTypeForDef(meta: R3DirectiveMetadata, typeBase: o.ExternalReference): o.Type {
|
||||
function createDirectiveTypeParams(meta: R3DirectiveMetadata): o.Type[] {
|
||||
// On the type side, remove newlines from the selector as it will need to fit into a TypeScript
|
||||
// string literal, which must be on one line.
|
||||
const selectorForType = meta.selector !== null ? meta.selector.replace(/\n/g, '') : null;
|
||||
|
||||
return o.expressionType(o.importExpr(typeBase, [
|
||||
return [
|
||||
typeWithParameters(meta.type.type, meta.typeArgumentCount),
|
||||
selectorForType !== null ? stringAsType(selectorForType) : o.NONE_TYPE,
|
||||
meta.exportAs !== null ? stringArrayAsType(meta.exportAs) : o.NONE_TYPE,
|
||||
stringMapAsType(meta.inputs),
|
||||
stringMapAsType(meta.outputs),
|
||||
stringArrayAsType(meta.queries.map(q => q.propertyName)),
|
||||
]));
|
||||
];
|
||||
}
|
||||
|
||||
// Define and update any view queries
|
||||
|
@ -1983,8 +1983,13 @@ export interface ParseTemplateOptions {
|
||||
* @param options options to modify how the template is parsed
|
||||
*/
|
||||
export function parseTemplate(
|
||||
template: string, templateUrl: string, options: ParseTemplateOptions = {}):
|
||||
{errors?: ParseError[], nodes: t.Node[], styleUrls: string[], styles: string[]} {
|
||||
template: string, templateUrl: string, options: ParseTemplateOptions = {}): {
|
||||
errors?: ParseError[],
|
||||
nodes: t.Node[],
|
||||
styleUrls: string[],
|
||||
styles: string[],
|
||||
ngContentSelectors: string[]
|
||||
} {
|
||||
const {interpolationConfig, preserveWhitespaces, enableI18nLegacyMessageIdFormat} = options;
|
||||
const bindingParser = makeBindingParser(interpolationConfig);
|
||||
const htmlParser = new HtmlParser();
|
||||
@ -1993,7 +1998,13 @@ export function parseTemplate(
|
||||
{leadingTriviaChars: LEADING_TRIVIA_CHARS, ...options, tokenizeExpansionForms: true});
|
||||
|
||||
if (parseResult.errors && parseResult.errors.length > 0) {
|
||||
return {errors: parseResult.errors, nodes: [], styleUrls: [], styles: []};
|
||||
return {
|
||||
errors: parseResult.errors,
|
||||
nodes: [],
|
||||
styleUrls: [],
|
||||
styles: [],
|
||||
ngContentSelectors: []
|
||||
};
|
||||
}
|
||||
|
||||
let rootNodes: html.Node[] = parseResult.rootNodes;
|
||||
@ -2020,12 +2031,13 @@ export function parseTemplate(
|
||||
}
|
||||
}
|
||||
|
||||
const {nodes, errors, styleUrls, styles} = htmlAstToRender3Ast(rootNodes, bindingParser);
|
||||
const {nodes, errors, styleUrls, styles, ngContentSelectors} =
|
||||
htmlAstToRender3Ast(rootNodes, bindingParser);
|
||||
if (errors && errors.length > 0) {
|
||||
return {errors, nodes: [], styleUrls: [], styles: []};
|
||||
return {errors, nodes: [], styleUrls: [], styles: [], ngContentSelectors: []};
|
||||
}
|
||||
|
||||
return {nodes, styleUrls, styles};
|
||||
return {nodes, styleUrls, styles, ngContentSelectors};
|
||||
}
|
||||
|
||||
const elementRegistry = new DomElementSchemaRegistry();
|
||||
|
Reference in New Issue
Block a user