2018-04-24 11:34:11 -07:00
|
|
|
/**
|
|
|
|
|
* @license
|
2020-05-19 12:08:49 -07:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2018-04-24 11:34:11 -07:00
|
|
|
*
|
|
|
|
|
* 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 {StaticSymbol} from '../../aot/static_symbol';
|
2018-07-27 09:56:35 -07:00
|
|
|
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileQueryMetadata, CompileTokenMetadata, identifierName, sanitizeIdentifier} from '../../compile_metadata';
|
2018-04-24 11:34:11 -07:00
|
|
|
import {CompileReflector} from '../../compile_reflector';
|
2018-12-19 15:03:47 -08:00
|
|
|
import {BindingForm, convertPropertyBinding} from '../../compiler_util/expression_converter';
|
2018-04-24 11:34:11 -07:00
|
|
|
import {ConstantPool, DefinitionKind} from '../../constant_pool';
|
|
|
|
|
import * as core from '../../core';
|
2019-10-03 21:54:49 +02:00
|
|
|
import {AST, ParsedEvent, ParsedEventType, ParsedProperty} from '../../expression_parser/ast';
|
2018-11-29 16:21:16 -08:00
|
|
|
import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config';
|
2018-04-24 11:34:11 -07:00
|
|
|
import * as o from '../../output/output_ast';
|
2019-10-03 21:54:49 +02:00
|
|
|
import {ParseError, ParseSourceSpan} from '../../parse_util';
|
2018-04-24 11:34:11 -07:00
|
|
|
import {CssSelector, SelectorMatcher} from '../../selector';
|
2018-07-31 11:14:06 -07:00
|
|
|
import {ShadowCss} from '../../shadow_css';
|
|
|
|
|
import {CONTENT_ATTR, HOST_ATTR} from '../../style_compiler';
|
2018-04-24 11:34:11 -07:00
|
|
|
import {BindingParser} from '../../template_parser/binding_parser';
|
2020-04-08 10:14:18 -07:00
|
|
|
import {error, OutputContext} from '../../util';
|
2018-12-19 15:03:47 -08:00
|
|
|
import {BoundEvent} from '../r3_ast';
|
2020-11-16 18:04:49 +01:00
|
|
|
import {compileFactoryFunction, R3FactoryTarget} from '../r3_factory';
|
2018-04-27 14:39:07 -07:00
|
|
|
import {Identifiers as R3} from '../r3_identifiers';
|
|
|
|
|
import {Render3ParseResult} from '../r3_template_transform';
|
2018-12-19 15:03:47 -08:00
|
|
|
import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, typeWithParameters} from '../util';
|
2018-07-13 14:49:01 -07:00
|
|
|
|
2019-04-27 09:33:10 +02:00
|
|
|
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata} from './api';
|
2019-12-14 19:48:24 -08:00
|
|
|
import {MIN_STYLING_BINDING_SLOTS_REQUIRED, StylingBuilder, StylingInstructionCall} from './styling_builder';
|
2020-04-08 10:14:18 -07:00
|
|
|
import {BindingScope, makeBindingParser, prepareEventListenerParameters, renderFlagCheckIfStmt, resolveSanitizationFn, TemplateDefinitionBuilder, ValueConverter} from './template';
|
|
|
|
|
import {asLiteral, chainedInstruction, conditionallyCreateMapObjectLiteral, CONTEXT_NAME, DefinitionMap, getQueryPredicate, RENDER_FLAGS, TEMPORARY_NAME, temporaryAllocator} from './util';
|
2018-04-24 11:34:11 -07:00
|
|
|
|
2018-07-31 11:14:06 -07:00
|
|
|
const EMPTY_ARRAY: any[] = [];
|
|
|
|
|
|
2018-11-06 19:05:06 -08:00
|
|
|
// This regex matches any binding names that contain the "attr." prefix, e.g. "attr.required"
|
|
|
|
|
// If there is a match, the first matching group will contain the attribute name to bind.
|
|
|
|
|
const ATTR_REGEX = /attr\.([^\]]+)/;
|
|
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
function baseDirectiveFields(
|
|
|
|
|
meta: R3DirectiveMetadata, constantPool: ConstantPool,
|
2019-08-12 09:26:20 +03:00
|
|
|
bindingParser: BindingParser): DefinitionMap {
|
2018-04-24 11:34:11 -07:00
|
|
|
const definitionMap = new DefinitionMap();
|
2019-10-27 10:59:23 +01:00
|
|
|
const selectors = core.parseSelectorToR3Selector(meta.selector);
|
2018-04-24 11:34:11 -07:00
|
|
|
|
|
|
|
|
// e.g. `type: MyDirective`
|
refactor(ivy): split `type` into `type`, `internalType` and `adjacentType` (#33533)
When compiling an Angular decorator (e.g. Directive), @angular/compiler
generates an 'expression' to be added as a static definition field
on the class, a 'type' which will be added for that field to the .d.ts
file, and a statement adjacent to the class that calls `setClassMetadata()`.
Previously, the same WrappedNodeExpr of the class' ts.Identifier was used
within each of this situations.
In the ngtsc case, this is proper. In the ngcc case, if the class being
compiled is within an ES5 IIFE, the outer name of the class may have
changed. Thus, the class has both an inner and outer name. The outer name
should continue to be used elsewhere in the compiler and in 'type'.
The 'expression' will live within the IIFE, the `internalType` should be used.
The adjacent statement will also live within the IIFE, the `adjacentType` should be used.
This commit introduces `ReflectionHost.getInternalNameOfClass()` and
`ReflectionHost.getAdjacentNameOfClass()`, which the compiler can use to
query for the correct name to use.
PR Close #33533
2019-11-01 16:55:09 +00:00
|
|
|
definitionMap.set('type', meta.internalType);
|
2018-04-24 11:34:11 -07:00
|
|
|
|
|
|
|
|
// e.g. `selectors: [['', 'someDir', '']]`
|
2019-10-27 10:59:23 +01:00
|
|
|
if (selectors.length > 0) {
|
|
|
|
|
definitionMap.set('selectors', asLiteral(selectors));
|
|
|
|
|
}
|
2018-04-24 11:34:11 -07:00
|
|
|
|
2019-02-02 11:20:33 -08:00
|
|
|
if (meta.queries.length > 0) {
|
|
|
|
|
// e.g. `contentQueries: (rf, ctx, dirIndex) => { ... }
|
2019-04-21 17:37:15 +02:00
|
|
|
definitionMap.set(
|
|
|
|
|
'contentQueries', createContentQueriesFunction(meta.queries, constantPool, meta.name));
|
2019-02-02 11:20:33 -08:00
|
|
|
}
|
2018-07-18 09:42:42 +02:00
|
|
|
|
2019-03-13 19:30:38 +01:00
|
|
|
if (meta.viewQueries.length) {
|
2019-04-21 17:37:15 +02:00
|
|
|
definitionMap.set(
|
|
|
|
|
'viewQuery', createViewQueriesFunction(meta.viewQueries, constantPool, meta.name));
|
2019-03-13 19:30:38 +01:00
|
|
|
}
|
|
|
|
|
|
2020-01-25 12:38:42 +01:00
|
|
|
// e.g. `hostBindings: (rf, ctx) => { ... }
|
2018-08-22 16:11:25 -07:00
|
|
|
definitionMap.set(
|
2020-04-08 10:14:18 -07:00
|
|
|
'hostBindings',
|
|
|
|
|
createHostBindingsFunction(
|
|
|
|
|
meta.host, meta.typeSourceSpan, bindingParser, constantPool, meta.selector || '',
|
|
|
|
|
meta.name, definitionMap));
|
2018-04-24 11:34:11 -07:00
|
|
|
|
|
|
|
|
// e.g 'inputs: {a: 'a'}`
|
2018-12-17 13:17:42 -08:00
|
|
|
definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs, true));
|
2018-04-24 11:34:11 -07:00
|
|
|
|
|
|
|
|
// e.g 'outputs: {a: 'a'}`
|
|
|
|
|
definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs));
|
|
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
if (meta.exportAs !== null) {
|
2019-01-10 22:24:32 +01:00
|
|
|
definitionMap.set('exportAs', o.literalArr(meta.exportAs.map(e => o.literal(e))));
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-12 09:26:20 +03:00
|
|
|
return definitionMap;
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add features to the definition map.
|
|
|
|
|
*/
|
2020-04-08 10:14:18 -07:00
|
|
|
function addFeatures(definitionMap: DefinitionMap, meta: R3DirectiveMetadata|R3ComponentMetadata) {
|
2020-02-21 11:34:39 -08:00
|
|
|
// e.g. `features: [NgOnChangesFeature]`
|
2018-05-21 08:15:19 -07:00
|
|
|
const features: o.Expression[] = [];
|
2018-06-18 08:05:06 -07:00
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
const providers = meta.providers;
|
|
|
|
|
const viewProviders = (meta as R3ComponentMetadata).viewProviders;
|
|
|
|
|
if (providers || viewProviders) {
|
|
|
|
|
const args = [providers || new o.LiteralArrayExpr([])];
|
|
|
|
|
if (viewProviders) {
|
|
|
|
|
args.push(viewProviders);
|
|
|
|
|
}
|
|
|
|
|
features.push(o.importExpr(R3.ProvidersFeature).callFn(args));
|
|
|
|
|
}
|
2018-08-03 12:20:27 -07:00
|
|
|
|
2018-06-18 08:05:06 -07:00
|
|
|
if (meta.usesInheritance) {
|
|
|
|
|
features.push(o.importExpr(R3.InheritDefinitionFeature));
|
|
|
|
|
}
|
feat(ngcc): add a migration for undecorated child classes (#33362)
In Angular View Engine, there are two kinds of decorator inheritance:
1) both the parent and child classes have decorators
This case is supported by InheritDefinitionFeature, which merges some fields
of the definitions (such as the inputs or queries).
2) only the parent class has a decorator
If the child class is missing a decorator, the compiler effectively behaves
as if the parent class' decorator is applied to the child class as well.
This is the "undecorated child" scenario, and this commit adds a migration
to ngcc to support this pattern in Ivy.
This migration has 2 phases. First, the NgModules of the application are
scanned for classes in 'declarations' which are missing decorators, but
whose base classes do have decorators. These classes are the undecorated
children. This scan is performed recursively, so even if a declared class
has a base class that itself inherits a decorator, this case is handled.
Next, a synthetic decorator (either @Component or @Directive) is created
on the child class. This decorator copies some critical information such
as 'selector' and 'exportAs', as well as supports any decorated fields
(@Input, etc). A flag is passed to the decorator compiler which causes a
special feature `CopyDefinitionFeature` to be included on the compiled
definition. This feature copies at runtime the remaining aspects of the
parent definition which `InheritDefinitionFeature` does not handle,
completing the "full" inheritance of the child class' decorator from its
parent class.
PR Close #33362
2019-10-23 12:00:49 -07:00
|
|
|
if (meta.fullInheritance) {
|
|
|
|
|
features.push(o.importExpr(R3.CopyDefinitionFeature));
|
|
|
|
|
}
|
2019-01-14 17:39:21 -08:00
|
|
|
if (meta.lifecycle.usesOnChanges) {
|
2020-02-21 11:34:39 -08:00
|
|
|
features.push(o.importExpr(R3.NgOnChangesFeature));
|
2019-01-14 17:39:21 -08:00
|
|
|
}
|
2018-05-21 08:15:19 -07:00
|
|
|
if (features.length) {
|
|
|
|
|
definitionMap.set('features', o.literalArr(features));
|
|
|
|
|
}
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Compile a directive for the render3 runtime as defined by the `R3DirectiveMetadata`.
|
|
|
|
|
*/
|
2018-05-21 08:15:19 -07:00
|
|
|
export function compileDirectiveFromMetadata(
|
2018-04-24 11:34:11 -07:00
|
|
|
meta: R3DirectiveMetadata, constantPool: ConstantPool,
|
|
|
|
|
bindingParser: BindingParser): R3DirectiveDef {
|
2019-08-12 09:26:20 +03:00
|
|
|
const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser);
|
2018-10-18 09:23:18 +02:00
|
|
|
addFeatures(definitionMap, meta);
|
2018-04-24 11:34:11 -07:00
|
|
|
const expression = o.importExpr(R3.defineDirective).callFn([definitionMap.toLiteralMap()]);
|
2020-11-16 18:04:49 +01:00
|
|
|
const type = createDirectiveType(meta);
|
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
2020-02-26 22:05:44 +01:00
|
|
|
|
2019-08-12 09:26:20 +03:00
|
|
|
return {expression, type};
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Compile a component for the render3 runtime as defined by the `R3ComponentMetadata`.
|
|
|
|
|
*/
|
2018-05-21 08:15:19 -07:00
|
|
|
export function compileComponentFromMetadata(
|
2018-04-24 11:34:11 -07:00
|
|
|
meta: R3ComponentMetadata, constantPool: ConstantPool,
|
|
|
|
|
bindingParser: BindingParser): R3ComponentDef {
|
2019-08-12 09:26:20 +03:00
|
|
|
const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser);
|
2018-10-18 09:23:18 +02:00
|
|
|
addFeatures(definitionMap, meta);
|
2018-04-24 11:34:11 -07:00
|
|
|
|
|
|
|
|
const selector = meta.selector && CssSelector.parse(meta.selector);
|
|
|
|
|
const firstSelector = selector && selector[0];
|
|
|
|
|
|
|
|
|
|
// e.g. `attr: ["class", ".my.app"]`
|
|
|
|
|
// This is optional an only included if the first selector of a component specifies attributes.
|
|
|
|
|
if (firstSelector) {
|
|
|
|
|
const selectorAttributes = firstSelector.getAttrs();
|
|
|
|
|
if (selectorAttributes.length) {
|
|
|
|
|
definitionMap.set(
|
2020-04-08 10:14:18 -07:00
|
|
|
'attrs',
|
|
|
|
|
constantPool.getConstLiteral(
|
|
|
|
|
o.literalArr(selectorAttributes.map(
|
|
|
|
|
value => value != null ? o.literal(value) : o.literal(undefined))),
|
|
|
|
|
/* forceShared */ true));
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate the CSS matcher that recognize directive
|
|
|
|
|
let directiveMatcher: SelectorMatcher|null = null;
|
|
|
|
|
|
2018-11-20 17:20:16 +01:00
|
|
|
if (meta.directives.length > 0) {
|
2018-04-24 11:34:11 -07:00
|
|
|
const matcher = new SelectorMatcher();
|
2020-11-16 18:24:37 +01:00
|
|
|
for (const {selector, type} of meta.directives) {
|
|
|
|
|
matcher.addSelectables(CssSelector.parse(selector), type);
|
2018-11-20 17:20:16 +01:00
|
|
|
}
|
2018-04-24 11:34:11 -07:00
|
|
|
directiveMatcher = matcher;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// e.g. `template: function MyComponent_Template(_ctx, _cm) {...}`
|
|
|
|
|
const templateTypeName = meta.name;
|
|
|
|
|
const templateName = templateTypeName ? `${templateTypeName}_Template` : null;
|
|
|
|
|
|
|
|
|
|
const directivesUsed = new Set<o.Expression>();
|
|
|
|
|
const pipesUsed = new Set<o.Expression>();
|
2018-12-20 09:49:24 +01:00
|
|
|
const changeDetection = meta.changeDetection;
|
2018-04-24 11:34:11 -07:00
|
|
|
|
|
|
|
|
const template = meta.template;
|
2018-08-16 18:53:21 -07:00
|
|
|
const templateBuilder = new TemplateDefinitionBuilder(
|
2020-04-01 10:02:26 +01:00
|
|
|
constantPool, BindingScope.createRootScope(), 0, templateTypeName, null, null, templateName,
|
2019-01-18 18:02:32 -08:00
|
|
|
directiveMatcher, directivesUsed, meta.pipes, pipesUsed, R3.namespaceHTML,
|
2018-11-16 09:57:23 -08:00
|
|
|
meta.relativeContextFilePath, meta.i18nUseExternalIds);
|
2018-08-16 18:53:21 -07:00
|
|
|
|
2018-11-30 15:01:37 -08:00
|
|
|
const templateFunctionExpression = templateBuilder.buildTemplateFunction(template.nodes, []);
|
2018-08-16 18:53:21 -07:00
|
|
|
|
2018-12-22 16:02:34 +00:00
|
|
|
// We need to provide this so that dynamically generated components know what
|
|
|
|
|
// projected content blocks to pass through to the component when it is instantiated.
|
|
|
|
|
const ngContentSelectors = templateBuilder.getNgContentSelectors();
|
|
|
|
|
if (ngContentSelectors) {
|
|
|
|
|
definitionMap.set('ngContentSelectors', ngContentSelectors);
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-23 20:08:51 +02:00
|
|
|
// e.g. `decls: 2`
|
|
|
|
|
definitionMap.set('decls', o.literal(templateBuilder.getConstCount()));
|
2018-08-18 11:14:50 -07:00
|
|
|
|
|
|
|
|
// e.g. `vars: 2`
|
|
|
|
|
definitionMap.set('vars', o.literal(templateBuilder.getVarCount()));
|
2018-04-24 11:34:11 -07:00
|
|
|
|
2020-08-10 17:25:51 -07:00
|
|
|
// Generate `consts` section of ComponentDef:
|
|
|
|
|
// - either as an array:
|
|
|
|
|
// `consts: [['one', 'two'], ['three', 'four']]`
|
|
|
|
|
// - or as a factory function in case additional statements are present (to support i18n):
|
|
|
|
|
// `consts: function() { var i18n_0; if (ngI18nClosureMode) {...} else {...} return [i18n_0]; }`
|
|
|
|
|
const {constExpressions, prepareStatements} = templateBuilder.getConsts();
|
|
|
|
|
if (constExpressions.length > 0) {
|
|
|
|
|
let constsExpr: o.LiteralArrayExpr|o.FunctionExpr = o.literalArr(constExpressions);
|
|
|
|
|
// Prepare statements are present - turn `consts` into a function.
|
|
|
|
|
if (prepareStatements.length > 0) {
|
|
|
|
|
constsExpr = o.fn([], [...prepareStatements, new o.ReturnStatement(constsExpr)]);
|
|
|
|
|
}
|
|
|
|
|
definitionMap.set('consts', constsExpr);
|
2019-09-23 20:08:51 +02:00
|
|
|
}
|
|
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
definitionMap.set('template', templateFunctionExpression);
|
|
|
|
|
|
|
|
|
|
// e.g. `directives: [MyDirective]`
|
|
|
|
|
if (directivesUsed.size) {
|
2018-08-06 14:49:35 +02:00
|
|
|
let directivesExpr: o.Expression = o.literalArr(Array.from(directivesUsed));
|
2018-10-25 11:13:14 -07:00
|
|
|
if (meta.wrapDirectivesAndPipesInClosure) {
|
2018-08-06 14:49:35 +02:00
|
|
|
directivesExpr = o.fn([], [new o.ReturnStatement(directivesExpr)]);
|
|
|
|
|
}
|
|
|
|
|
definitionMap.set('directives', directivesExpr);
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// e.g. `pipes: [MyPipe]`
|
|
|
|
|
if (pipesUsed.size) {
|
2018-10-25 11:13:14 -07:00
|
|
|
let pipesExpr: o.Expression = o.literalArr(Array.from(pipesUsed));
|
|
|
|
|
if (meta.wrapDirectivesAndPipesInClosure) {
|
|
|
|
|
pipesExpr = o.fn([], [new o.ReturnStatement(pipesExpr)]);
|
|
|
|
|
}
|
|
|
|
|
definitionMap.set('pipes', pipesExpr);
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
|
2018-11-19 18:48:14 +01:00
|
|
|
if (meta.encapsulation === null) {
|
|
|
|
|
meta.encapsulation = core.ViewEncapsulation.Emulated;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-31 11:14:06 -07:00
|
|
|
// e.g. `styles: [str1, str2]`
|
|
|
|
|
if (meta.styles && meta.styles.length) {
|
|
|
|
|
const styleValues = meta.encapsulation == core.ViewEncapsulation.Emulated ?
|
|
|
|
|
compileStyles(meta.styles, CONTENT_ATTR, HOST_ATTR) :
|
|
|
|
|
meta.styles;
|
2020-07-23 18:51:55 -07:00
|
|
|
const strings = styleValues.map(str => constantPool.getConstLiteral(o.literal(str)));
|
2018-07-31 11:14:06 -07:00
|
|
|
definitionMap.set('styles', o.literalArr(strings));
|
2018-11-19 18:48:14 +01:00
|
|
|
} else if (meta.encapsulation === core.ViewEncapsulation.Emulated) {
|
|
|
|
|
// If there is no style, don't generate css selectors on elements
|
|
|
|
|
meta.encapsulation = core.ViewEncapsulation.None;
|
2018-07-31 11:14:06 -07:00
|
|
|
}
|
|
|
|
|
|
2018-11-15 16:00:55 +01:00
|
|
|
// Only set view encapsulation if it's not the default value
|
2018-11-19 18:48:14 +01:00
|
|
|
if (meta.encapsulation !== core.ViewEncapsulation.Emulated) {
|
2018-11-15 16:00:55 +01:00
|
|
|
definitionMap.set('encapsulation', o.literal(meta.encapsulation));
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-05 15:56:36 -08:00
|
|
|
// e.g. `animation: [trigger('123', [])]`
|
2018-10-09 10:45:27 -07:00
|
|
|
if (meta.animations !== null) {
|
2018-10-16 11:09:04 -07:00
|
|
|
definitionMap.set(
|
2018-12-05 15:56:36 -08:00
|
|
|
'data', o.literalMap([{key: 'animation', value: meta.animations, quoted: false}]));
|
2018-09-05 15:23:59 -07:00
|
|
|
}
|
|
|
|
|
|
2018-12-20 09:49:24 +01:00
|
|
|
// Only set the change detection flag if it's defined and it's not the default.
|
|
|
|
|
if (changeDetection != null && changeDetection !== core.ChangeDetectionStrategy.Default) {
|
|
|
|
|
definitionMap.set('changeDetection', o.literal(changeDetection));
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
const expression = o.importExpr(R3.defineComponent).callFn([definitionMap.toLiteralMap()]);
|
2020-11-16 18:04:49 +01:00
|
|
|
const type = createComponentType(meta);
|
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
2020-02-26 22:05:44 +01:00
|
|
|
|
2020-11-16 18:04:49 +01:00
|
|
|
return {expression, type};
|
|
|
|
|
}
|
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
2020-02-26 22:05:44 +01:00
|
|
|
|
2020-11-16 18:04:49 +01:00
|
|
|
/**
|
|
|
|
|
* Creates the type specification from the component meta. This type is inserted into .d.ts files
|
|
|
|
|
* to be consumed by upstream compilations.
|
|
|
|
|
*/
|
|
|
|
|
export function createComponentType(meta: R3ComponentMetadata): o.Type {
|
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
2020-02-26 22:05:44 +01:00
|
|
|
const typeParams = createDirectiveTypeParams(meta);
|
|
|
|
|
typeParams.push(stringArrayAsType(meta.template.ngContentSelectors));
|
2020-11-16 18:04:49 +01:00
|
|
|
return o.expressionType(o.importExpr(R3.ComponentDefWithMeta, typeParams));
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A wrapper around `compileDirective` which depends on render2 global analysis data as its input
|
|
|
|
|
* instead of the `R3DirectiveMetadata`.
|
|
|
|
|
*
|
|
|
|
|
* `R3DirectiveMetadata` is computed from `CompileDirectiveMetadata` and other statically reflected
|
|
|
|
|
* information.
|
|
|
|
|
*/
|
|
|
|
|
export function compileDirectiveFromRender2(
|
|
|
|
|
outputCtx: OutputContext, directive: CompileDirectiveMetadata, reflector: CompileReflector,
|
|
|
|
|
bindingParser: BindingParser) {
|
2020-04-08 10:14:18 -07:00
|
|
|
const name = identifierName(directive.type)!;
|
2018-04-24 11:34:11 -07:00
|
|
|
name || error(`Cannot resolver the name of ${directive.type}`);
|
|
|
|
|
|
|
|
|
|
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive);
|
|
|
|
|
|
|
|
|
|
const meta = directiveMetadataFromGlobalMetadata(directive, outputCtx, reflector);
|
2018-05-21 08:15:19 -07:00
|
|
|
const res = compileDirectiveFromMetadata(meta, outputCtx.constantPool, bindingParser);
|
2019-10-03 21:54:49 +02:00
|
|
|
const factoryRes = compileFactoryFunction(
|
|
|
|
|
{...meta, injectFn: R3.directiveInject, target: R3FactoryTarget.Directive});
|
2019-08-12 09:26:20 +03:00
|
|
|
const ngFactoryDefStatement = new o.ClassStmt(
|
|
|
|
|
name, null,
|
2019-10-11 14:18:45 -07:00
|
|
|
[new o.ClassField('ɵfac', o.INFERRED_TYPE, [o.StmtModifier.Static], factoryRes.factory)], [],
|
|
|
|
|
new o.ClassMethod(null, [], []), []);
|
2019-08-12 09:26:20 +03:00
|
|
|
const directiveDefStatement = new o.ClassStmt(
|
2018-04-24 11:34:11 -07:00
|
|
|
name, null,
|
|
|
|
|
[new o.ClassField(definitionField, o.INFERRED_TYPE, [o.StmtModifier.Static], res.expression)],
|
2019-08-12 09:26:20 +03:00
|
|
|
[], new o.ClassMethod(null, [], []), []);
|
|
|
|
|
|
|
|
|
|
// Create the partial class to be merged with the actual class.
|
|
|
|
|
outputCtx.statements.push(ngFactoryDefStatement, directiveDefStatement);
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A wrapper around `compileComponent` which depends on render2 global analysis data as its input
|
|
|
|
|
* instead of the `R3DirectiveMetadata`.
|
|
|
|
|
*
|
|
|
|
|
* `R3ComponentMetadata` is computed from `CompileDirectiveMetadata` and other statically reflected
|
|
|
|
|
* information.
|
|
|
|
|
*/
|
|
|
|
|
export function compileComponentFromRender2(
|
2018-04-27 14:39:07 -07:00
|
|
|
outputCtx: OutputContext, component: CompileDirectiveMetadata, render3Ast: Render3ParseResult,
|
|
|
|
|
reflector: CompileReflector, bindingParser: BindingParser, directiveTypeBySel: Map<string, any>,
|
2018-04-24 11:34:11 -07:00
|
|
|
pipeTypeByName: Map<string, any>) {
|
2020-04-08 10:14:18 -07:00
|
|
|
const name = identifierName(component.type)!;
|
2018-04-24 11:34:11 -07:00
|
|
|
name || error(`Cannot resolver the name of ${component.type}`);
|
|
|
|
|
|
|
|
|
|
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Component);
|
|
|
|
|
|
|
|
|
|
const summary = component.toSummary();
|
|
|
|
|
|
|
|
|
|
// Compute the R3ComponentMetadata from the CompileDirectiveMetadata
|
|
|
|
|
const meta: R3ComponentMetadata = {
|
|
|
|
|
...directiveMetadataFromGlobalMetadata(component, outputCtx, reflector),
|
|
|
|
|
selector: component.selector,
|
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
2020-02-26 22:05:44 +01:00
|
|
|
template: {nodes: render3Ast.nodes, ngContentSelectors: render3Ast.ngContentSelectors},
|
2018-11-20 17:20:16 +01:00
|
|
|
directives: [],
|
2018-04-24 11:34:11 -07:00
|
|
|
pipes: typeMapToExpressionMap(pipeTypeByName, outputCtx),
|
|
|
|
|
viewQueries: queriesFromGlobalMetadata(component.viewQueries, outputCtx),
|
2018-10-25 11:13:14 -07:00
|
|
|
wrapDirectivesAndPipesInClosure: false,
|
2018-07-31 11:14:06 -07:00
|
|
|
styles: (summary.template && summary.template.styles) || EMPTY_ARRAY,
|
|
|
|
|
encapsulation:
|
2018-09-05 15:23:59 -07:00
|
|
|
(summary.template && summary.template.encapsulation) || core.ViewEncapsulation.Emulated,
|
2018-11-29 16:21:16 -08:00
|
|
|
interpolation: DEFAULT_INTERPOLATION_CONFIG,
|
2018-10-09 10:45:27 -07:00
|
|
|
animations: null,
|
2018-10-18 09:23:18 +02:00
|
|
|
viewProviders:
|
2018-11-16 09:57:23 -08:00
|
|
|
component.viewProviders.length > 0 ? new o.WrappedNodeExpr(component.viewProviders) : null,
|
|
|
|
|
relativeContextFilePath: '',
|
|
|
|
|
i18nUseExternalIds: true,
|
2018-04-24 11:34:11 -07:00
|
|
|
};
|
2018-05-21 08:15:19 -07:00
|
|
|
const res = compileComponentFromMetadata(meta, outputCtx.constantPool, bindingParser);
|
2019-10-03 21:54:49 +02:00
|
|
|
const factoryRes = compileFactoryFunction(
|
|
|
|
|
{...meta, injectFn: R3.directiveInject, target: R3FactoryTarget.Directive});
|
2019-08-12 09:26:20 +03:00
|
|
|
const ngFactoryDefStatement = new o.ClassStmt(
|
|
|
|
|
name, null,
|
2019-10-11 14:18:45 -07:00
|
|
|
[new o.ClassField('ɵfac', o.INFERRED_TYPE, [o.StmtModifier.Static], factoryRes.factory)], [],
|
|
|
|
|
new o.ClassMethod(null, [], []), []);
|
2019-08-12 09:26:20 +03:00
|
|
|
const componentDefStatement = new o.ClassStmt(
|
2018-04-24 11:34:11 -07:00
|
|
|
name, null,
|
|
|
|
|
[new o.ClassField(definitionField, o.INFERRED_TYPE, [o.StmtModifier.Static], res.expression)],
|
2019-08-12 09:26:20 +03:00
|
|
|
[], new o.ClassMethod(null, [], []), []);
|
|
|
|
|
|
|
|
|
|
// Create the partial class to be merged with the actual class.
|
|
|
|
|
outputCtx.statements.push(ngFactoryDefStatement, componentDefStatement);
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Compute `R3DirectiveMetadata` given `CompileDirectiveMetadata` and a `CompileReflector`.
|
|
|
|
|
*/
|
|
|
|
|
function directiveMetadataFromGlobalMetadata(
|
|
|
|
|
directive: CompileDirectiveMetadata, outputCtx: OutputContext,
|
|
|
|
|
reflector: CompileReflector): R3DirectiveMetadata {
|
2019-02-27 16:54:37 -08:00
|
|
|
// The global-analysis based Ivy mode in ngc is no longer utilized/supported.
|
|
|
|
|
throw new Error('unsupported');
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert `CompileQueryMetadata` into `R3QueryMetadata`.
|
|
|
|
|
*/
|
|
|
|
|
function queriesFromGlobalMetadata(
|
|
|
|
|
queries: CompileQueryMetadata[], outputCtx: OutputContext): R3QueryMetadata[] {
|
|
|
|
|
return queries.map(query => {
|
|
|
|
|
let read: o.Expression|null = null;
|
|
|
|
|
if (query.read && query.read.identifier) {
|
|
|
|
|
read = outputCtx.importExpr(query.read.identifier.reference);
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
propertyName: query.propertyName,
|
|
|
|
|
first: query.first,
|
|
|
|
|
predicate: selectorsFromGlobalMetadata(query.selectors, outputCtx),
|
2020-04-08 10:14:18 -07:00
|
|
|
descendants: query.descendants,
|
|
|
|
|
read,
|
2019-02-18 17:33:59 -08:00
|
|
|
static: !!query.static
|
2018-04-24 11:34:11 -07:00
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert `CompileTokenMetadata` for query selectors into either an expression for a predicate
|
|
|
|
|
* type, or a list of string predicates.
|
|
|
|
|
*/
|
|
|
|
|
function selectorsFromGlobalMetadata(
|
|
|
|
|
selectors: CompileTokenMetadata[], outputCtx: OutputContext): o.Expression|string[] {
|
|
|
|
|
if (selectors.length > 1 || (selectors.length == 1 && selectors[0].value)) {
|
|
|
|
|
const selectorStrings = selectors.map(value => value.value as string);
|
|
|
|
|
selectorStrings.some(value => !value) &&
|
|
|
|
|
error('Found a type among the string selectors expected');
|
|
|
|
|
return outputCtx.constantPool.getConstLiteral(
|
|
|
|
|
o.literalArr(selectorStrings.map(value => o.literal(value))));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (selectors.length == 1) {
|
|
|
|
|
const first = selectors[0];
|
|
|
|
|
if (first.identifier) {
|
|
|
|
|
return outputCtx.importExpr(first.identifier.reference);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
error('Unexpected query form');
|
|
|
|
|
return o.NULL_EXPR;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 18:02:32 -08:00
|
|
|
function prepareQueryParams(query: R3QueryMetadata, constantPool: ConstantPool): o.Expression[] {
|
2019-07-20 12:32:29 +02:00
|
|
|
const parameters = [getQueryPredicate(query, constantPool), o.literal(query.descendants)];
|
|
|
|
|
if (query.read) {
|
|
|
|
|
parameters.push(query.read);
|
|
|
|
|
}
|
2019-01-18 18:02:32 -08:00
|
|
|
return parameters;
|
|
|
|
|
}
|
2018-07-24 11:56:35 +02:00
|
|
|
|
2019-02-27 16:54:37 -08:00
|
|
|
function convertAttributesToExpressions(attributes: {[name: string]: o.Expression}):
|
|
|
|
|
o.Expression[] {
|
2018-04-24 11:34:11 -07:00
|
|
|
const values: o.Expression[] = [];
|
|
|
|
|
for (let key of Object.getOwnPropertyNames(attributes)) {
|
|
|
|
|
const value = attributes[key];
|
2019-02-27 16:54:37 -08:00
|
|
|
values.push(o.literal(key), value);
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
2019-01-11 14:03:37 -08:00
|
|
|
return values;
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
|
2019-02-02 11:20:33 -08:00
|
|
|
// Define and update any content queries
|
2018-07-18 09:42:42 +02:00
|
|
|
function createContentQueriesFunction(
|
2019-04-21 17:37:15 +02:00
|
|
|
queries: R3QueryMetadata[], constantPool: ConstantPool, name?: string): o.Expression {
|
2019-02-02 11:20:33 -08:00
|
|
|
const createStatements: o.Statement[] = [];
|
|
|
|
|
const updateStatements: o.Statement[] = [];
|
|
|
|
|
const tempAllocator = temporaryAllocator(updateStatements, TEMPORARY_NAME);
|
|
|
|
|
|
2019-04-21 17:37:15 +02:00
|
|
|
for (const query of queries) {
|
2019-02-18 18:18:56 -08:00
|
|
|
const queryInstruction = query.static ? R3.staticContentQuery : R3.contentQuery;
|
|
|
|
|
|
2019-07-20 12:32:29 +02:00
|
|
|
// creation, e.g. r3.contentQuery(dirIndex, somePredicate, true, null);
|
|
|
|
|
createStatements.push(
|
|
|
|
|
o.importExpr(queryInstruction)
|
|
|
|
|
.callFn([o.variable('dirIndex'), ...prepareQueryParams(query, constantPool) as any])
|
|
|
|
|
.toStmt());
|
2019-02-02 11:20:33 -08:00
|
|
|
|
2019-08-12 11:27:18 +03:00
|
|
|
// update, e.g. (r3.queryRefresh(tmp = r3.loadQuery()) && (ctx.someDir = tmp));
|
2019-02-02 11:20:33 -08:00
|
|
|
const temporary = tempAllocator();
|
2019-08-12 11:27:18 +03:00
|
|
|
const getQueryList = o.importExpr(R3.loadQuery).callFn([]);
|
2019-02-02 11:20:33 -08:00
|
|
|
const refresh = o.importExpr(R3.queryRefresh).callFn([temporary.set(getQueryList)]);
|
|
|
|
|
const updateDirective = o.variable(CONTEXT_NAME)
|
|
|
|
|
.prop(query.propertyName)
|
|
|
|
|
.set(query.first ? temporary.prop('first') : temporary);
|
|
|
|
|
updateStatements.push(refresh.and(updateDirective).toStmt());
|
2018-07-18 09:42:42 +02:00
|
|
|
}
|
|
|
|
|
|
2019-04-21 17:37:15 +02:00
|
|
|
const contentQueriesFnName = name ? `${name}_ContentQueries` : null;
|
2019-02-02 11:20:33 -08:00
|
|
|
return o.fn(
|
|
|
|
|
[
|
|
|
|
|
new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(CONTEXT_NAME, null),
|
|
|
|
|
new o.FnParam('dirIndex', null)
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
renderFlagCheckIfStmt(core.RenderFlags.Create, createStatements),
|
|
|
|
|
renderFlagCheckIfStmt(core.RenderFlags.Update, updateStatements)
|
|
|
|
|
],
|
|
|
|
|
o.INFERRED_TYPE, null, contentQueriesFnName);
|
2018-07-18 09:42:42 +02:00
|
|
|
}
|
|
|
|
|
|
2018-09-21 12:12:06 -07:00
|
|
|
function stringAsType(str: string): o.Type {
|
|
|
|
|
return o.expressionType(o.literal(str));
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-08 10:14:18 -07:00
|
|
|
function stringMapAsType(map: {[key: string]: string|string[]}): o.Type {
|
2018-10-25 23:05:15 -07:00
|
|
|
const mapValues = Object.keys(map).map(key => {
|
|
|
|
|
const value = Array.isArray(map[key]) ? map[key][0] : map[key];
|
|
|
|
|
return {
|
|
|
|
|
key,
|
|
|
|
|
value: o.literal(value),
|
|
|
|
|
quoted: true,
|
|
|
|
|
};
|
|
|
|
|
});
|
2018-09-21 12:12:06 -07:00
|
|
|
return o.expressionType(o.literalMap(mapValues));
|
|
|
|
|
}
|
|
|
|
|
|
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
2020-02-26 22:05:44 +01:00
|
|
|
function stringArrayAsType(arr: ReadonlyArray<string|null>): o.Type {
|
2018-09-21 12:12:06 -07:00
|
|
|
return arr.length > 0 ? o.expressionType(o.literalArr(arr.map(value => o.literal(value)))) :
|
|
|
|
|
o.NONE_TYPE;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-30 23:45:15 +01:00
|
|
|
export function createDirectiveTypeParams(meta: R3DirectiveMetadata): o.Type[] {
|
2018-09-21 12:12:06 -07:00
|
|
|
// 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.
|
fix(ivy): support abstract directives in template type checking (#33131)
Recently it was made possible to have a directive without selector,
which are referred to as abstract directives. Such directives should not
be registered in an NgModule, but can still contain decorators for
inputs, outputs, queries, etc. The information from these decorators and
the `@Directive()` decorator itself needs to be registered with the
central `MetadataRegistry` so that other areas of the compiler can
request information about a given directive, an example of which is the
template type checker that needs to know about the inputs and outputs of
directives.
Prior to this change, however, abstract directives would only register
themselves with the `MetadataRegistry` as being an abstract directive,
without all of its other metadata like inputs and outputs. This meant
that the template type checker was unable to resolve the inputs and
outputs of these abstract directives, therefore failing to check them
correctly. The typical error would be that some property does not exist
on a DOM element, whereas said property should have been bound to the
abstract directive's input.
This commit fixes the problem by always registering the metadata of a
directive or component with the `MetadataRegistry`. Tests have been
added to ensure abstract directives are handled correctly in the
template type checker, together with tests to verify the form of
abstract directives in declaration files.
Fixes #30080
PR Close #33131
2019-10-13 17:00:13 +02:00
|
|
|
const selectorForType = meta.selector !== null ? meta.selector.replace(/\n/g, '') : null;
|
2018-09-21 12:12:06 -07:00
|
|
|
|
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
2020-02-26 22:05:44 +01:00
|
|
|
return [
|
2019-12-18 14:03:05 +00:00
|
|
|
typeWithParameters(meta.type.type, meta.typeArgumentCount),
|
fix(ivy): support abstract directives in template type checking (#33131)
Recently it was made possible to have a directive without selector,
which are referred to as abstract directives. Such directives should not
be registered in an NgModule, but can still contain decorators for
inputs, outputs, queries, etc. The information from these decorators and
the `@Directive()` decorator itself needs to be registered with the
central `MetadataRegistry` so that other areas of the compiler can
request information about a given directive, an example of which is the
template type checker that needs to know about the inputs and outputs of
directives.
Prior to this change, however, abstract directives would only register
themselves with the `MetadataRegistry` as being an abstract directive,
without all of its other metadata like inputs and outputs. This meant
that the template type checker was unable to resolve the inputs and
outputs of these abstract directives, therefore failing to check them
correctly. The typical error would be that some property does not exist
on a DOM element, whereas said property should have been bound to the
abstract directive's input.
This commit fixes the problem by always registering the metadata of a
directive or component with the `MetadataRegistry`. Tests have been
added to ensure abstract directives are handled correctly in the
template type checker, together with tests to verify the form of
abstract directives in declaration files.
Fixes #30080
PR Close #33131
2019-10-13 17:00:13 +02:00
|
|
|
selectorForType !== null ? stringAsType(selectorForType) : o.NONE_TYPE,
|
2019-01-08 16:30:57 -08:00
|
|
|
meta.exportAs !== null ? stringArrayAsType(meta.exportAs) : o.NONE_TYPE,
|
2018-09-21 12:12:06 -07:00
|
|
|
stringMapAsType(meta.inputs),
|
|
|
|
|
stringMapAsType(meta.outputs),
|
|
|
|
|
stringArrayAsType(meta.queries.map(q => q.propertyName)),
|
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
2020-02-26 22:05:44 +01:00
|
|
|
];
|
2018-09-21 12:12:06 -07:00
|
|
|
}
|
|
|
|
|
|
2020-11-16 18:04:49 +01:00
|
|
|
/**
|
|
|
|
|
* Creates the type specification from the directive meta. This type is inserted into .d.ts files
|
|
|
|
|
* to be consumed by upstream compilations.
|
|
|
|
|
*/
|
|
|
|
|
export function createDirectiveType(meta: R3DirectiveMetadata): o.Type {
|
|
|
|
|
const typeParams = createDirectiveTypeParams(meta);
|
|
|
|
|
return o.expressionType(o.importExpr(R3.DirectiveDefWithMeta, typeParams));
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-24 11:56:35 +02:00
|
|
|
// Define and update any view queries
|
|
|
|
|
function createViewQueriesFunction(
|
2019-04-21 17:37:15 +02:00
|
|
|
viewQueries: R3QueryMetadata[], constantPool: ConstantPool, name?: string): o.Expression {
|
2018-07-24 11:56:35 +02:00
|
|
|
const createStatements: o.Statement[] = [];
|
|
|
|
|
const updateStatements: o.Statement[] = [];
|
|
|
|
|
const tempAllocator = temporaryAllocator(updateStatements, TEMPORARY_NAME);
|
|
|
|
|
|
2019-04-21 17:37:15 +02:00
|
|
|
viewQueries.forEach((query: R3QueryMetadata) => {
|
2019-02-18 17:33:59 -08:00
|
|
|
const queryInstruction = query.static ? R3.staticViewQuery : R3.viewQuery;
|
|
|
|
|
|
2019-01-18 18:02:32 -08:00
|
|
|
// creation, e.g. r3.viewQuery(somePredicate, true);
|
|
|
|
|
const queryDefinition =
|
2019-02-18 17:33:59 -08:00
|
|
|
o.importExpr(queryInstruction).callFn(prepareQueryParams(query, constantPool));
|
2018-07-24 11:56:35 +02:00
|
|
|
createStatements.push(queryDefinition.toStmt());
|
|
|
|
|
|
2019-08-12 11:27:18 +03:00
|
|
|
// update, e.g. (r3.queryRefresh(tmp = r3.loadQuery()) && (ctx.someDir = tmp));
|
2018-07-24 11:56:35 +02:00
|
|
|
const temporary = tempAllocator();
|
2019-08-12 11:27:18 +03:00
|
|
|
const getQueryList = o.importExpr(R3.loadQuery).callFn([]);
|
2018-07-24 11:56:35 +02:00
|
|
|
const refresh = o.importExpr(R3.queryRefresh).callFn([temporary.set(getQueryList)]);
|
|
|
|
|
const updateDirective = o.variable(CONTEXT_NAME)
|
|
|
|
|
.prop(query.propertyName)
|
|
|
|
|
.set(query.first ? temporary.prop('first') : temporary);
|
|
|
|
|
updateStatements.push(refresh.and(updateDirective).toStmt());
|
2019-01-18 18:02:32 -08:00
|
|
|
});
|
2018-07-24 11:56:35 +02:00
|
|
|
|
2019-04-21 17:37:15 +02:00
|
|
|
const viewQueryFnName = name ? `${name}_Query` : null;
|
2018-07-24 11:56:35 +02:00
|
|
|
return o.fn(
|
|
|
|
|
[new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(CONTEXT_NAME, null)],
|
|
|
|
|
[
|
|
|
|
|
renderFlagCheckIfStmt(core.RenderFlags.Create, createStatements),
|
|
|
|
|
renderFlagCheckIfStmt(core.RenderFlags.Update, updateStatements)
|
|
|
|
|
],
|
|
|
|
|
o.INFERRED_TYPE, null, viewQueryFnName);
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
// Return a host binding function or null if one is not necessary.
|
|
|
|
|
function createHostBindingsFunction(
|
2019-04-27 09:33:10 +02:00
|
|
|
hostBindingsMetadata: R3HostMetadata, typeSourceSpan: ParseSourceSpan,
|
2020-01-08 11:32:33 -08:00
|
|
|
bindingParser: BindingParser, constantPool: ConstantPool, selector: string, name: string,
|
|
|
|
|
definitionMap: DefinitionMap): o.Expression|null {
|
2019-04-27 09:33:10 +02:00
|
|
|
const bindingContext = o.variable(CONTEXT_NAME);
|
2020-01-25 12:38:42 +01:00
|
|
|
const styleBuilder = new StylingBuilder(bindingContext);
|
2019-04-27 09:33:10 +02:00
|
|
|
|
|
|
|
|
const {styleAttr, classAttr} = hostBindingsMetadata.specialAttributes;
|
|
|
|
|
if (styleAttr !== undefined) {
|
|
|
|
|
styleBuilder.registerStyleAttr(styleAttr);
|
|
|
|
|
}
|
|
|
|
|
if (classAttr !== undefined) {
|
|
|
|
|
styleBuilder.registerClassAttr(classAttr);
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-20 15:20:19 -08:00
|
|
|
const createStatements: o.Statement[] = [];
|
|
|
|
|
const updateStatements: o.Statement[] = [];
|
2018-04-24 11:34:11 -07:00
|
|
|
|
2019-04-27 09:33:10 +02:00
|
|
|
const hostBindingSourceSpan = typeSourceSpan;
|
|
|
|
|
const directiveSummary = metadataAsSummary(hostBindingsMetadata);
|
2018-04-24 11:34:11 -07:00
|
|
|
|
2019-12-14 19:48:24 -08:00
|
|
|
// Calculate host event bindings
|
|
|
|
|
const eventBindings =
|
|
|
|
|
bindingParser.createDirectiveHostEventAsts(directiveSummary, hostBindingSourceSpan);
|
|
|
|
|
if (eventBindings && eventBindings.length) {
|
|
|
|
|
const listeners = createHostListeners(eventBindings, name);
|
|
|
|
|
createStatements.push(...listeners);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate the host property bindings
|
|
|
|
|
const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan);
|
|
|
|
|
const allOtherBindings: ParsedProperty[] = [];
|
|
|
|
|
|
|
|
|
|
// We need to calculate the total amount of binding slots required by
|
|
|
|
|
// all the instructions together before any value conversions happen.
|
|
|
|
|
// Value conversions may require additional slots for interpolation and
|
|
|
|
|
// bindings with pipes. These calculates happen after this block.
|
|
|
|
|
let totalHostVarsCount = 0;
|
|
|
|
|
bindings && bindings.forEach((binding: ParsedProperty) => {
|
2020-09-17 13:01:32 -07:00
|
|
|
const stylingInputWasSet = styleBuilder.registerInputBasedOnName(
|
|
|
|
|
binding.name, binding.expression, hostBindingSourceSpan);
|
2019-12-14 19:48:24 -08:00
|
|
|
if (stylingInputWasSet) {
|
|
|
|
|
totalHostVarsCount += MIN_STYLING_BINDING_SLOTS_REQUIRED;
|
|
|
|
|
} else {
|
|
|
|
|
allOtherBindings.push(binding);
|
|
|
|
|
totalHostVarsCount++;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2019-01-25 12:17:50 -08:00
|
|
|
let valueConverter: ValueConverter;
|
|
|
|
|
const getValueConverter = () => {
|
|
|
|
|
if (!valueConverter) {
|
|
|
|
|
const hostVarsCountFn = (numSlots: number): number => {
|
|
|
|
|
const originalVarsCount = totalHostVarsCount;
|
|
|
|
|
totalHostVarsCount += numSlots;
|
|
|
|
|
return originalVarsCount;
|
|
|
|
|
};
|
|
|
|
|
valueConverter = new ValueConverter(
|
|
|
|
|
constantPool,
|
|
|
|
|
() => error('Unexpected node'), // new nodes are illegal here
|
|
|
|
|
hostVarsCountFn,
|
|
|
|
|
() => error('Unexpected pipe')); // pipes are illegal here
|
|
|
|
|
}
|
|
|
|
|
return valueConverter;
|
|
|
|
|
};
|
|
|
|
|
|
2019-06-27 20:23:15 +02:00
|
|
|
const propertyBindings: o.Expression[][] = [];
|
|
|
|
|
const attributeBindings: o.Expression[][] = [];
|
|
|
|
|
const syntheticHostBindings: o.Expression[][] = [];
|
2019-12-14 19:48:24 -08:00
|
|
|
allOtherBindings.forEach((binding: ParsedProperty) => {
|
|
|
|
|
// resolve literal arrays and literal objects
|
|
|
|
|
const value = binding.expression.visit(getValueConverter());
|
|
|
|
|
const bindingExpr = bindingFn(bindingContext, value);
|
|
|
|
|
|
|
|
|
|
const {bindingName, instruction, isAttribute} = getBindingNameAndInstruction(binding);
|
|
|
|
|
|
|
|
|
|
const securityContexts =
|
|
|
|
|
bindingParser.calcPossibleSecurityContexts(selector, bindingName, isAttribute)
|
|
|
|
|
.filter(context => context !== core.SecurityContext.NONE);
|
|
|
|
|
|
|
|
|
|
let sanitizerFn: o.ExternalExpr|null = null;
|
|
|
|
|
if (securityContexts.length) {
|
|
|
|
|
if (securityContexts.length === 2 &&
|
|
|
|
|
securityContexts.indexOf(core.SecurityContext.URL) > -1 &&
|
|
|
|
|
securityContexts.indexOf(core.SecurityContext.RESOURCE_URL) > -1) {
|
|
|
|
|
// Special case for some URL attributes (such as "src" and "href") that may be a part
|
|
|
|
|
// of different security contexts. In this case we use special santitization function and
|
|
|
|
|
// select the actual sanitizer at runtime based on a tag name that is provided while
|
|
|
|
|
// invoking sanitization function.
|
|
|
|
|
sanitizerFn = o.importExpr(R3.sanitizeUrlOrResourceUrl);
|
|
|
|
|
} else {
|
|
|
|
|
sanitizerFn = resolveSanitizationFn(securityContexts[0], isAttribute);
|
2019-01-25 12:17:50 -08:00
|
|
|
}
|
2019-12-14 19:48:24 -08:00
|
|
|
}
|
|
|
|
|
const instructionParams = [o.literal(bindingName), bindingExpr.currValExpr];
|
|
|
|
|
if (sanitizerFn) {
|
|
|
|
|
instructionParams.push(sanitizerFn);
|
|
|
|
|
}
|
2018-11-16 15:22:12 +01:00
|
|
|
|
2019-12-14 19:48:24 -08:00
|
|
|
updateStatements.push(...bindingExpr.stmts);
|
2019-06-27 20:23:15 +02:00
|
|
|
|
2019-12-14 19:48:24 -08:00
|
|
|
if (instruction === R3.hostProperty) {
|
|
|
|
|
propertyBindings.push(instructionParams);
|
|
|
|
|
} else if (instruction === R3.attribute) {
|
|
|
|
|
attributeBindings.push(instructionParams);
|
2020-05-15 15:33:00 -07:00
|
|
|
} else if (instruction === R3.syntheticHostProperty) {
|
2019-12-14 19:48:24 -08:00
|
|
|
syntheticHostBindings.push(instructionParams);
|
|
|
|
|
} else {
|
|
|
|
|
updateStatements.push(o.importExpr(instruction).callFn(instructionParams).toStmt());
|
2019-01-11 14:03:37 -08:00
|
|
|
}
|
2019-01-25 12:17:50 -08:00
|
|
|
});
|
2018-11-16 15:22:12 +01:00
|
|
|
|
2019-06-27 20:23:15 +02:00
|
|
|
if (propertyBindings.length > 0) {
|
2019-07-14 11:11:10 +02:00
|
|
|
updateStatements.push(chainedInstruction(R3.hostProperty, propertyBindings).toStmt());
|
2019-06-27 20:23:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (attributeBindings.length > 0) {
|
|
|
|
|
updateStatements.push(chainedInstruction(R3.attribute, attributeBindings).toStmt());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (syntheticHostBindings.length > 0) {
|
|
|
|
|
updateStatements.push(
|
2020-05-15 15:33:00 -07:00
|
|
|
chainedInstruction(R3.syntheticHostProperty, syntheticHostBindings).toStmt());
|
2019-06-27 20:23:15 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-25 12:17:50 -08:00
|
|
|
// since we're dealing with directives/components and both have hostBinding
|
|
|
|
|
// functions, we need to generate a special hostAttrs instruction that deals
|
|
|
|
|
// with both the assignment of styling as well as static attributes to the host
|
|
|
|
|
// element. The instruction below will instruct all initial styling (styling
|
|
|
|
|
// that is inside of a host binding within a directive/component) to be attached
|
|
|
|
|
// to the host element alongside any of the provided host attributes that were
|
|
|
|
|
// collected earlier.
|
2019-04-27 09:33:10 +02:00
|
|
|
const hostAttrs = convertAttributesToExpressions(hostBindingsMetadata.attributes);
|
2020-01-08 11:32:33 -08:00
|
|
|
styleBuilder.assignHostAttrs(hostAttrs, definitionMap);
|
2018-12-13 15:51:47 -08:00
|
|
|
|
2019-01-25 12:17:50 -08:00
|
|
|
if (styleBuilder.hasBindings) {
|
|
|
|
|
// finally each binding that was registered in the statement above will need to be added to
|
|
|
|
|
// the update block of a component/directive templateFn/hostBindingsFn so that the bindings
|
|
|
|
|
// are evaluated and updated for the element.
|
|
|
|
|
styleBuilder.buildUpdateLevelInstructions(getValueConverter()).forEach(instruction => {
|
2019-11-18 20:13:23 +01:00
|
|
|
if (instruction.calls.length > 0) {
|
|
|
|
|
const calls: o.Expression[][] = [];
|
|
|
|
|
|
|
|
|
|
instruction.calls.forEach(call => {
|
|
|
|
|
// we subtract a value of `1` here because the binding slot was already allocated
|
|
|
|
|
// at the top of this method when all the input bindings were counted.
|
2019-12-14 19:48:24 -08:00
|
|
|
totalHostVarsCount +=
|
|
|
|
|
Math.max(call.allocateBindingSlots - MIN_STYLING_BINDING_SLOTS_REQUIRED, 0);
|
2019-11-18 20:13:23 +01:00
|
|
|
calls.push(convertStylingCall(call, bindingContext, bindingFn));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
updateStatements.push(chainedInstruction(instruction.reference, calls).toStmt());
|
|
|
|
|
}
|
2019-01-25 12:17:50 -08:00
|
|
|
});
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
|
2018-11-27 12:05:26 -08:00
|
|
|
if (totalHostVarsCount) {
|
2020-01-08 11:32:33 -08:00
|
|
|
definitionMap.set('hostVars', o.literal(totalHostVarsCount));
|
2018-11-27 12:05:26 -08:00
|
|
|
}
|
|
|
|
|
|
2018-11-20 15:20:19 -08:00
|
|
|
if (createStatements.length > 0 || updateStatements.length > 0) {
|
2019-04-27 09:33:10 +02:00
|
|
|
const hostBindingsFnName = name ? `${name}_HostBindings` : null;
|
2018-11-20 15:20:19 -08:00
|
|
|
const statements: o.Statement[] = [];
|
|
|
|
|
if (createStatements.length > 0) {
|
|
|
|
|
statements.push(renderFlagCheckIfStmt(core.RenderFlags.Create, createStatements));
|
|
|
|
|
}
|
|
|
|
|
if (updateStatements.length > 0) {
|
|
|
|
|
statements.push(renderFlagCheckIfStmt(core.RenderFlags.Update, updateStatements));
|
|
|
|
|
}
|
2018-04-24 11:34:11 -07:00
|
|
|
return o.fn(
|
2020-01-25 12:38:42 +01:00
|
|
|
[new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(CONTEXT_NAME, null)], statements,
|
|
|
|
|
o.INFERRED_TYPE, null, hostBindingsFnName);
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-25 12:17:50 -08:00
|
|
|
function bindingFn(implicit: any, value: AST) {
|
|
|
|
|
return convertPropertyBinding(
|
fix(compiler): evaluate safe navigation expressions in correct binding order (#37911)
When using the safe navigation operator in a binding expression, a temporary
variable may be used for storing the result of a side-effectful call.
For example, the following template uses a pipe and a safe property access:
```html
<app-person-view [enabled]="enabled" [firstName]="(person$ | async)?.name"></app-person-view>
```
The result of the pipe evaluation is stored in a temporary to be able to check
whether it is present. The temporary variable needs to be declared in a separate
statement and this would also cause the full expression itself to be pulled out
into a separate statement. This would compile into the following
pseudo-code instructions:
```js
var temp = null;
var firstName = (temp = pipe('async', ctx.person$)) == null ? null : temp.name;
property('enabled', ctx.enabled)('firstName', firstName);
```
Notice that the pipe evaluation happens before evaluating the `enabled` binding,
such that the runtime's internal binding index would correspond with `enabled`,
not `firstName`. This introduces a problem when the pipe uses `WrappedValue` to
force a change to be detected, as the runtime would then mark the binding slot
corresponding with `enabled` as dirty, instead of `firstName`. This results
in the `enabled` binding to be updated, triggering setters and affecting how
`OnChanges` is called.
In the pseudo-code above, the intermediate `firstName` variable is not strictly
necessary---it only improved readability a bit---and emitting it inline with
the binding itself avoids the out-of-order execution of the pipe:
```js
var temp = null;
property('enabled', ctx.enabled)
('firstName', (temp = pipe('async', ctx.person$)) == null ? null : temp.name);
```
This commit introduces a new `BindingForm` that results in the above code to be
generated and adds compiler and acceptance tests to verify the proper behavior.
Fixes #37194
PR Close #37911
2020-07-03 19:35:44 +02:00
|
|
|
null, implicit, value, 'b', BindingForm.Expression, () => error('Unexpected interpolation'));
|
2019-01-25 12:17:50 -08:00
|
|
|
}
|
|
|
|
|
|
2019-11-18 20:13:23 +01:00
|
|
|
function convertStylingCall(
|
|
|
|
|
call: StylingInstructionCall, bindingContext: any, bindingFn: Function) {
|
|
|
|
|
return call.params(value => bindingFn(bindingContext, value).currValExpr);
|
2018-11-16 15:22:12 +01:00
|
|
|
}
|
|
|
|
|
|
2019-01-03 18:24:21 -08:00
|
|
|
function getBindingNameAndInstruction(binding: ParsedProperty):
|
2019-01-03 10:04:06 -08:00
|
|
|
{bindingName: string, instruction: o.ExternalReference, isAttribute: boolean} {
|
2019-01-03 18:24:21 -08:00
|
|
|
let bindingName = binding.name;
|
2020-04-08 10:14:18 -07:00
|
|
|
let instruction!: o.ExternalReference;
|
2018-11-06 19:05:06 -08:00
|
|
|
|
|
|
|
|
// Check to see if this is an attr binding or a property binding
|
|
|
|
|
const attrMatches = bindingName.match(ATTR_REGEX);
|
|
|
|
|
if (attrMatches) {
|
|
|
|
|
bindingName = attrMatches[1];
|
2019-05-15 20:21:24 -07:00
|
|
|
instruction = R3.attribute;
|
2018-11-06 19:05:06 -08:00
|
|
|
} else {
|
2019-01-03 18:24:21 -08:00
|
|
|
if (binding.isAnimation) {
|
|
|
|
|
bindingName = prepareSyntheticPropertyName(bindingName);
|
|
|
|
|
// host bindings that have a synthetic property (e.g. @foo) should always be rendered
|
|
|
|
|
// in the context of the component and not the parent. Therefore there is a special
|
|
|
|
|
// compatibility instruction available for this purpose.
|
2020-05-15 15:33:00 -07:00
|
|
|
instruction = R3.syntheticHostProperty;
|
2019-01-03 18:24:21 -08:00
|
|
|
} else {
|
2019-07-14 11:11:10 +02:00
|
|
|
instruction = R3.hostProperty;
|
2019-01-03 18:24:21 -08:00
|
|
|
}
|
2018-11-06 19:05:06 -08:00
|
|
|
}
|
|
|
|
|
|
2019-01-03 10:04:06 -08:00
|
|
|
return {bindingName, instruction, isAttribute: !!attrMatches};
|
2018-11-06 19:05:06 -08:00
|
|
|
}
|
|
|
|
|
|
2019-06-06 20:01:51 +02:00
|
|
|
function createHostListeners(eventBindings: ParsedEvent[], name?: string): o.Statement[] {
|
2019-11-12 02:15:24 +09:00
|
|
|
const listeners: o.Expression[][] = [];
|
|
|
|
|
const syntheticListeners: o.Expression[][] = [];
|
|
|
|
|
const instructions: o.Statement[] = [];
|
|
|
|
|
|
|
|
|
|
eventBindings.forEach(binding => {
|
2019-01-03 18:24:21 -08:00
|
|
|
let bindingName = binding.name && sanitizeIdentifier(binding.name);
|
2018-12-19 15:03:47 -08:00
|
|
|
const bindingFnName = binding.type === ParsedEventType.Animation ?
|
|
|
|
|
prepareSyntheticListenerFunctionName(bindingName, binding.targetOrPhase) :
|
|
|
|
|
bindingName;
|
2019-04-27 09:33:10 +02:00
|
|
|
const handlerName = name && bindingName ? `${name}_${bindingFnName}_HostBindingHandler` : null;
|
2019-06-06 20:01:51 +02:00
|
|
|
const params = prepareEventListenerParameters(BoundEvent.fromParsedEvent(binding), handlerName);
|
2019-11-12 02:15:24 +09:00
|
|
|
|
|
|
|
|
if (binding.type == ParsedEventType.Animation) {
|
|
|
|
|
syntheticListeners.push(params);
|
|
|
|
|
} else {
|
|
|
|
|
listeners.push(params);
|
|
|
|
|
}
|
2018-10-16 10:28:23 -07:00
|
|
|
});
|
2019-11-12 02:15:24 +09:00
|
|
|
|
|
|
|
|
if (syntheticListeners.length > 0) {
|
2020-05-15 15:33:00 -07:00
|
|
|
instructions.push(chainedInstruction(R3.syntheticHostListener, syntheticListeners).toStmt());
|
2019-11-12 02:15:24 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (listeners.length > 0) {
|
|
|
|
|
instructions.push(chainedInstruction(R3.listener, listeners).toStmt());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return instructions;
|
2018-10-16 10:28:23 -07:00
|
|
|
}
|
|
|
|
|
|
2019-04-27 09:33:10 +02:00
|
|
|
function metadataAsSummary(meta: R3HostMetadata): CompileDirectiveSummary {
|
2018-04-24 11:34:11 -07:00
|
|
|
// clang-format off
|
|
|
|
|
return {
|
2019-02-27 16:54:37 -08:00
|
|
|
// This is used by the BindingParser, which only deals with listeners and properties. There's no
|
|
|
|
|
// need to pass attributes to it.
|
|
|
|
|
hostAttributes: {},
|
2019-04-27 09:33:10 +02:00
|
|
|
hostListeners: meta.listeners,
|
|
|
|
|
hostProperties: meta.properties,
|
2018-04-24 11:34:11 -07:00
|
|
|
} as CompileDirectiveSummary;
|
|
|
|
|
// clang-format on
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function typeMapToExpressionMap(
|
|
|
|
|
map: Map<string, StaticSymbol>, outputCtx: OutputContext): Map<string, o.Expression> {
|
|
|
|
|
// Convert each map entry into another entry where the value is an expression importing the type.
|
|
|
|
|
const entries = Array.from(map).map(
|
|
|
|
|
([key, type]): [string, o.Expression] => [key, outputCtx.importExpr(type)]);
|
|
|
|
|
return new Map(entries);
|
2018-06-08 09:00:01 -07:00
|
|
|
}
|
2018-06-12 16:58:09 -07:00
|
|
|
|
2019-01-03 18:24:21 -08:00
|
|
|
const HOST_REG_EXP = /^(?:\[([^\]]+)\])|(?:\(([^\)]+)\))$/;
|
2018-06-12 16:58:09 -07:00
|
|
|
// Represents the groups in the above regex.
|
|
|
|
|
const enum HostBindingGroup {
|
2019-01-03 18:24:21 -08:00
|
|
|
// group 1: "prop" from "[prop]", or "attr.role" from "[attr.role]", or @anim from [@anim]
|
2018-11-06 19:05:06 -08:00
|
|
|
Binding = 1,
|
2018-06-12 16:58:09 -07:00
|
|
|
|
|
|
|
|
// group 2: "event" from "(event)"
|
|
|
|
|
Event = 2,
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-24 17:25:46 -08:00
|
|
|
// Defines Host Bindings structure that contains attributes, listeners, and properties,
|
|
|
|
|
// parsed from the `host` object defined for a Type.
|
|
|
|
|
export interface ParsedHostBindings {
|
2019-02-27 16:54:37 -08:00
|
|
|
attributes: {[key: string]: o.Expression};
|
2019-01-24 17:25:46 -08:00
|
|
|
listeners: {[key: string]: string};
|
|
|
|
|
properties: {[key: string]: string};
|
2019-02-27 16:54:37 -08:00
|
|
|
specialAttributes: {styleAttr?: string; classAttr?: string;};
|
2019-01-24 17:25:46 -08:00
|
|
|
}
|
|
|
|
|
|
2020-04-08 10:14:18 -07:00
|
|
|
export function parseHostBindings(host: {[key: string]: string|o.Expression}): ParsedHostBindings {
|
2019-02-27 16:54:37 -08:00
|
|
|
const attributes: {[key: string]: o.Expression} = {};
|
2018-06-12 16:58:09 -07:00
|
|
|
const listeners: {[key: string]: string} = {};
|
|
|
|
|
const properties: {[key: string]: string} = {};
|
2019-02-27 16:54:37 -08:00
|
|
|
const specialAttributes: {styleAttr?: string; classAttr?: string;} = {};
|
2018-06-12 16:58:09 -07:00
|
|
|
|
2019-02-27 16:54:37 -08:00
|
|
|
for (const key of Object.keys(host)) {
|
2018-06-12 16:58:09 -07:00
|
|
|
const value = host[key];
|
|
|
|
|
const matches = key.match(HOST_REG_EXP);
|
2019-02-27 16:54:37 -08:00
|
|
|
|
2018-06-12 16:58:09 -07:00
|
|
|
if (matches === null) {
|
2019-02-27 16:54:37 -08:00
|
|
|
switch (key) {
|
|
|
|
|
case 'class':
|
|
|
|
|
if (typeof value !== 'string') {
|
|
|
|
|
// TODO(alxhub): make this a diagnostic.
|
|
|
|
|
throw new Error(`Class binding must be string`);
|
|
|
|
|
}
|
|
|
|
|
specialAttributes.classAttr = value;
|
|
|
|
|
break;
|
|
|
|
|
case 'style':
|
|
|
|
|
if (typeof value !== 'string') {
|
|
|
|
|
// TODO(alxhub): make this a diagnostic.
|
|
|
|
|
throw new Error(`Style binding must be string`);
|
|
|
|
|
}
|
|
|
|
|
specialAttributes.styleAttr = value;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
|
attributes[key] = o.literal(value);
|
|
|
|
|
} else {
|
|
|
|
|
attributes[key] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-11-06 19:05:06 -08:00
|
|
|
} else if (matches[HostBindingGroup.Binding] != null) {
|
2019-02-27 16:54:37 -08:00
|
|
|
if (typeof value !== 'string') {
|
|
|
|
|
// TODO(alxhub): make this a diagnostic.
|
|
|
|
|
throw new Error(`Property binding must be string`);
|
|
|
|
|
}
|
2019-01-03 18:24:21 -08:00
|
|
|
// synthetic properties (the ones that have a `@` as a prefix)
|
|
|
|
|
// are still treated the same as regular properties. Therefore
|
|
|
|
|
// there is no point in storing them in a separate map.
|
2018-11-06 19:05:06 -08:00
|
|
|
properties[matches[HostBindingGroup.Binding]] = value;
|
2018-06-12 16:58:09 -07:00
|
|
|
} else if (matches[HostBindingGroup.Event] != null) {
|
2019-02-27 16:54:37 -08:00
|
|
|
if (typeof value !== 'string') {
|
|
|
|
|
// TODO(alxhub): make this a diagnostic.
|
|
|
|
|
throw new Error(`Event binding must be string`);
|
|
|
|
|
}
|
2018-06-12 16:58:09 -07:00
|
|
|
listeners[matches[HostBindingGroup.Event]] = value;
|
|
|
|
|
}
|
2019-02-27 16:54:37 -08:00
|
|
|
}
|
2018-06-12 16:58:09 -07:00
|
|
|
|
2019-02-27 16:54:37 -08:00
|
|
|
return {attributes, listeners, properties, specialAttributes};
|
2018-06-13 10:33:04 -07:00
|
|
|
}
|
2018-07-31 11:14:06 -07:00
|
|
|
|
2019-01-24 17:25:46 -08:00
|
|
|
/**
|
|
|
|
|
* Verifies host bindings and returns the list of errors (if any). Empty array indicates that a
|
|
|
|
|
* given set of host bindings has no errors.
|
|
|
|
|
*
|
|
|
|
|
* @param bindings set of host bindings to verify.
|
|
|
|
|
* @param sourceSpan source span where host bindings were defined.
|
|
|
|
|
* @returns array of errors associated with a given set of host bindings.
|
|
|
|
|
*/
|
|
|
|
|
export function verifyHostBindings(
|
|
|
|
|
bindings: ParsedHostBindings, sourceSpan: ParseSourceSpan): ParseError[] {
|
2019-04-27 09:33:10 +02:00
|
|
|
const summary = metadataAsSummary(bindings);
|
2019-01-24 17:25:46 -08:00
|
|
|
// TODO: abstract out host bindings verification logic and use it instead of
|
|
|
|
|
// creating events and properties ASTs to detect errors (FW-996)
|
|
|
|
|
const bindingParser = makeBindingParser();
|
|
|
|
|
bindingParser.createDirectiveHostEventAsts(summary, sourceSpan);
|
|
|
|
|
bindingParser.createBoundHostProperties(summary, sourceSpan);
|
|
|
|
|
return bindingParser.errors;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-31 11:14:06 -07:00
|
|
|
function compileStyles(styles: string[], selector: string, hostSelector: string): string[] {
|
|
|
|
|
const shadowCss = new ShadowCss();
|
2020-04-08 10:14:18 -07:00
|
|
|
return styles.map(style => {
|
|
|
|
|
return shadowCss!.shimCssText(style, selector, hostSelector);
|
|
|
|
|
});
|
2018-07-31 11:14:06 -07:00
|
|
|
}
|