2016-10-21 18:41:14 +00:00
|
|
|
/**
|
|
|
|
|
* @license
|
2020-05-19 19:08:49 +00:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2016-10-21 18:41:14 +00:00
|
|
|
*
|
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
2024-09-20 15:23:15 +00:00
|
|
|
* found in the LICENSE file at https://angular.dev/license
|
2016-10-21 18:41:14 +00:00
|
|
|
*/
|
|
|
|
|
|
2017-08-16 16:00:03 +00:00
|
|
|
import {SecurityContext} from '../core';
|
2024-04-18 16:14:19 +00:00
|
|
|
import {
|
|
|
|
|
AbsoluteSourceSpan,
|
|
|
|
|
AST,
|
|
|
|
|
ASTWithSource,
|
|
|
|
|
Binary,
|
|
|
|
|
BindingPipe,
|
|
|
|
|
BindingType,
|
|
|
|
|
BoundElementProperty,
|
|
|
|
|
Conditional,
|
|
|
|
|
EmptyExpr,
|
|
|
|
|
KeyedRead,
|
|
|
|
|
NonNullAssert,
|
|
|
|
|
ParsedEvent,
|
|
|
|
|
ParsedEventType,
|
|
|
|
|
ParsedProperty,
|
|
|
|
|
ParsedPropertyType,
|
|
|
|
|
ParsedVariable,
|
|
|
|
|
ParserError,
|
|
|
|
|
PrefixNot,
|
|
|
|
|
PropertyRead,
|
|
|
|
|
RecursiveAstVisitor,
|
|
|
|
|
TemplateBinding,
|
|
|
|
|
VariableBinding,
|
|
|
|
|
} from '../expression_parser/ast';
|
2016-10-21 18:41:14 +00:00
|
|
|
import {Parser} from '../expression_parser/parser';
|
2023-11-16 11:52:23 +00:00
|
|
|
import {InterpolationConfig} from '../ml_parser/defaults';
|
2016-10-21 18:41:14 +00:00
|
|
|
import {mergeNsAndName} from '../ml_parser/tags';
|
2022-01-21 22:41:03 +00:00
|
|
|
import {InterpolatedAttributeToken, InterpolatedTextToken} from '../ml_parser/tokens';
|
2023-11-16 11:52:23 +00:00
|
|
|
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
|
2016-10-21 18:41:14 +00:00
|
|
|
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
2016-10-24 16:58:52 +00:00
|
|
|
import {CssSelector} from '../selector';
|
2016-10-21 18:41:14 +00:00
|
|
|
import {splitAtColon, splitAtPeriod} from '../util';
|
|
|
|
|
|
|
|
|
|
const PROPERTY_PARTS_SEPARATOR = '.';
|
|
|
|
|
const ATTRIBUTE_PREFIX = 'attr';
|
|
|
|
|
const CLASS_PREFIX = 'class';
|
|
|
|
|
const STYLE_PREFIX = 'style';
|
2020-03-11 23:46:08 +00:00
|
|
|
const TEMPLATE_ATTR_PREFIX = '*';
|
2016-10-21 18:41:14 +00:00
|
|
|
const ANIMATE_PROP_PREFIX = 'animate-';
|
|
|
|
|
|
2021-11-22 22:35:55 +00:00
|
|
|
export interface HostProperties {
|
|
|
|
|
[key: string]: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface HostListeners {
|
|
|
|
|
[key: string]: string;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-21 18:41:14 +00:00
|
|
|
/**
|
|
|
|
|
* Parses bindings in templates and in the directive host area.
|
|
|
|
|
*/
|
|
|
|
|
export class BindingParser {
|
|
|
|
|
constructor(
|
2024-04-18 16:14:19 +00:00
|
|
|
private _exprParser: Parser,
|
|
|
|
|
private _interpolationConfig: InterpolationConfig,
|
|
|
|
|
private _schemaRegistry: ElementSchemaRegistry,
|
|
|
|
|
public errors: ParseError[],
|
|
|
|
|
) {}
|
2016-10-21 18:41:14 +00:00
|
|
|
|
2020-04-08 17:14:18 +00:00
|
|
|
get interpolationConfig(): InterpolationConfig {
|
|
|
|
|
return this._interpolationConfig;
|
|
|
|
|
}
|
2018-11-30 00:21:16 +00:00
|
|
|
|
2024-04-18 16:14:19 +00:00
|
|
|
createBoundHostProperties(
|
|
|
|
|
properties: HostProperties,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
): ParsedProperty[] | null {
|
2021-11-22 22:35:55 +00:00
|
|
|
const boundProps: ParsedProperty[] = [];
|
|
|
|
|
for (const propName of Object.keys(properties)) {
|
|
|
|
|
const expression = properties[propName];
|
|
|
|
|
if (typeof expression === 'string') {
|
|
|
|
|
this.parsePropertyBinding(
|
2024-04-18 16:14:19 +00:00
|
|
|
propName,
|
|
|
|
|
expression,
|
|
|
|
|
true,
|
|
|
|
|
false,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
sourceSpan.start.offset,
|
|
|
|
|
undefined,
|
|
|
|
|
[],
|
|
|
|
|
// Use the `sourceSpan` for `keySpan`. This isn't really accurate, but neither is the
|
|
|
|
|
// sourceSpan, as it represents the sourceSpan of the host itself rather than the
|
|
|
|
|
// source of the host binding (which doesn't exist in the template). Regardless,
|
|
|
|
|
// neither of these values are used in Ivy but are only here to satisfy the function
|
|
|
|
|
// signature. This should likely be refactored in the future so that `sourceSpan`
|
|
|
|
|
// isn't being used inaccurately.
|
|
|
|
|
boundProps,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
);
|
2021-11-22 22:35:55 +00:00
|
|
|
} else {
|
|
|
|
|
this._reportError(
|
2024-04-18 16:14:19 +00:00
|
|
|
`Value of the host property binding "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
);
|
2021-11-22 22:35:55 +00:00
|
|
|
}
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
2021-11-22 22:35:55 +00:00
|
|
|
return boundProps;
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-18 16:14:19 +00:00
|
|
|
createDirectiveHostEventAsts(
|
|
|
|
|
hostListeners: HostListeners,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
): ParsedEvent[] | null {
|
2021-11-22 22:35:55 +00:00
|
|
|
const targetEvents: ParsedEvent[] = [];
|
|
|
|
|
for (const propName of Object.keys(hostListeners)) {
|
|
|
|
|
const expression = hostListeners[propName];
|
|
|
|
|
if (typeof expression === 'string') {
|
|
|
|
|
// Use the `sourceSpan` for `keySpan` and `handlerSpan`. This isn't really accurate, but
|
|
|
|
|
// neither is the `sourceSpan`, as it represents the `sourceSpan` of the host itself
|
|
|
|
|
// rather than the source of the host binding (which doesn't exist in the template).
|
|
|
|
|
// Regardless, neither of these values are used in Ivy but are only here to satisfy the
|
|
|
|
|
// function signature. This should likely be refactored in the future so that `sourceSpan`
|
|
|
|
|
// isn't being used inaccurately.
|
2020-06-28 16:38:38 +00:00
|
|
|
this.parseEvent(
|
2024-04-18 16:14:19 +00:00
|
|
|
propName,
|
|
|
|
|
expression,
|
|
|
|
|
/* isAssignmentEvent */ false,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
[],
|
|
|
|
|
targetEvents,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
);
|
2021-11-22 22:35:55 +00:00
|
|
|
} else {
|
|
|
|
|
this._reportError(
|
2024-04-18 16:14:19 +00:00
|
|
|
`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
);
|
2021-11-22 22:35:55 +00:00
|
|
|
}
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
2021-11-22 22:35:55 +00:00
|
|
|
return targetEvents;
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
|
2022-01-21 22:41:03 +00:00
|
|
|
parseInterpolation(
|
2024-04-18 16:14:19 +00:00
|
|
|
value: string,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
interpolatedTokens: InterpolatedAttributeToken[] | InterpolatedTextToken[] | null,
|
|
|
|
|
): ASTWithSource {
|
2016-10-21 18:41:14 +00:00
|
|
|
const sourceInfo = sourceSpan.start.toString();
|
2021-01-21 20:48:42 +00:00
|
|
|
const absoluteOffset = sourceSpan.fullStart.offset;
|
2016-10-21 18:41:14 +00:00
|
|
|
|
|
|
|
|
try {
|
2019-07-16 19:18:32 +00:00
|
|
|
const ast = this._exprParser.parseInterpolation(
|
2024-04-18 16:14:19 +00:00
|
|
|
value,
|
|
|
|
|
sourceInfo,
|
|
|
|
|
absoluteOffset,
|
|
|
|
|
interpolatedTokens,
|
|
|
|
|
this._interpolationConfig,
|
|
|
|
|
)!;
|
2016-10-21 18:41:14 +00:00
|
|
|
if (ast) this._reportExpressionParserErrors(ast.errors, sourceSpan);
|
|
|
|
|
return ast;
|
|
|
|
|
} catch (e) {
|
2016-10-24 18:11:31 +00:00
|
|
|
this._reportError(`${e}`, sourceSpan);
|
2021-01-21 20:48:42 +00:00
|
|
|
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-30 22:17:21 +00:00
|
|
|
/**
|
|
|
|
|
* Similar to `parseInterpolation`, but treats the provided string as a single expression
|
|
|
|
|
* element that would normally appear within the interpolation prefix and suffix (`{{` and `}}`).
|
|
|
|
|
* This is used for parsing the switch expression in ICUs.
|
|
|
|
|
*/
|
|
|
|
|
parseInterpolationExpression(expression: string, sourceSpan: ParseSourceSpan): ASTWithSource {
|
|
|
|
|
const sourceInfo = sourceSpan.start.toString();
|
2021-01-21 20:48:42 +00:00
|
|
|
const absoluteOffset = sourceSpan.start.offset;
|
2020-09-30 22:17:21 +00:00
|
|
|
|
|
|
|
|
try {
|
2024-04-18 16:14:19 +00:00
|
|
|
const ast = this._exprParser.parseInterpolationExpression(
|
|
|
|
|
expression,
|
|
|
|
|
sourceInfo,
|
|
|
|
|
absoluteOffset,
|
|
|
|
|
);
|
2020-09-30 22:17:21 +00:00
|
|
|
if (ast) this._reportExpressionParserErrors(ast.errors, sourceSpan);
|
|
|
|
|
return ast;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this._reportError(`${e}`, sourceSpan);
|
2021-01-21 20:48:42 +00:00
|
|
|
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
|
2020-09-30 22:17:21 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-23 22:51:42 +00:00
|
|
|
/**
|
2020-03-05 23:38:25 +00:00
|
|
|
* Parses the bindings in a microsyntax expression, and converts them to
|
|
|
|
|
* `ParsedProperty` or `ParsedVariable`.
|
|
|
|
|
*
|
2019-07-23 22:51:42 +00:00
|
|
|
* @param tplKey template binding name
|
|
|
|
|
* @param tplValue template binding value
|
|
|
|
|
* @param sourceSpan span of template binding relative to entire the template
|
|
|
|
|
* @param absoluteValueOffset start of the tplValue relative to the entire template
|
|
|
|
|
* @param targetMatchableAttrs potential attributes to match in the template
|
|
|
|
|
* @param targetProps target property bindings in the template
|
|
|
|
|
* @param targetVars target variables in the template
|
|
|
|
|
*/
|
2016-10-21 18:41:14 +00:00
|
|
|
parseInlineTemplateBinding(
|
2024-04-18 16:14:19 +00:00
|
|
|
tplKey: string,
|
|
|
|
|
tplValue: string,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
absoluteValueOffset: number,
|
|
|
|
|
targetMatchableAttrs: string[][],
|
|
|
|
|
targetProps: ParsedProperty[],
|
|
|
|
|
targetVars: ParsedVariable[],
|
|
|
|
|
isIvyAst: boolean,
|
|
|
|
|
) {
|
2020-03-11 23:46:08 +00:00
|
|
|
const absoluteKeyOffset = sourceSpan.start.offset + TEMPLATE_ATTR_PREFIX.length;
|
2020-03-05 23:38:25 +00:00
|
|
|
const bindings = this._parseTemplateBindings(
|
2024-04-18 16:14:19 +00:00
|
|
|
tplKey,
|
|
|
|
|
tplValue,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
absoluteKeyOffset,
|
|
|
|
|
absoluteValueOffset,
|
|
|
|
|
);
|
2018-04-20 00:23:27 +00:00
|
|
|
|
2020-03-11 23:46:08 +00:00
|
|
|
for (const binding of bindings) {
|
|
|
|
|
// sourceSpan is for the entire HTML attribute. bindingSpan is for a particular
|
|
|
|
|
// binding within the microsyntax expression so it's more narrow than sourceSpan.
|
|
|
|
|
const bindingSpan = moveParseSourceSpan(sourceSpan, binding.sourceSpan);
|
2020-03-05 23:38:25 +00:00
|
|
|
const key = binding.key.source;
|
2020-03-11 23:46:08 +00:00
|
|
|
const keySpan = moveParseSourceSpan(sourceSpan, binding.key.span);
|
2020-03-05 23:38:25 +00:00
|
|
|
if (binding instanceof VariableBinding) {
|
|
|
|
|
const value = binding.value ? binding.value.source : '$implicit';
|
2024-04-18 16:14:19 +00:00
|
|
|
const valueSpan = binding.value
|
|
|
|
|
? moveParseSourceSpan(sourceSpan, binding.value.span)
|
|
|
|
|
: undefined;
|
2020-03-11 23:46:08 +00:00
|
|
|
targetVars.push(new ParsedVariable(key, value, bindingSpan, keySpan, valueSpan));
|
2020-03-05 23:05:30 +00:00
|
|
|
} else if (binding.value) {
|
2020-09-29 04:23:34 +00:00
|
|
|
const srcSpan = isIvyAst ? bindingSpan : sourceSpan;
|
2020-03-17 21:17:53 +00:00
|
|
|
const valueSpan = moveParseSourceSpan(sourceSpan, binding.value.ast.sourceSpan);
|
2016-10-21 18:41:14 +00:00
|
|
|
this._parsePropertyAst(
|
2024-04-18 16:14:19 +00:00
|
|
|
key,
|
|
|
|
|
binding.value,
|
|
|
|
|
false,
|
|
|
|
|
srcSpan,
|
|
|
|
|
keySpan,
|
|
|
|
|
valueSpan,
|
|
|
|
|
targetMatchableAttrs,
|
|
|
|
|
targetProps,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
} else {
|
2020-09-09 18:22:50 +00:00
|
|
|
targetMatchableAttrs.push([key, '' /* value */]);
|
|
|
|
|
// Since this is a literal attribute with no RHS, source span should be
|
|
|
|
|
// just the key span.
|
2019-07-16 19:18:32 +00:00
|
|
|
this.parseLiteralAttr(
|
2024-04-18 16:14:19 +00:00
|
|
|
key,
|
|
|
|
|
null /* value */,
|
|
|
|
|
keySpan,
|
|
|
|
|
absoluteValueOffset,
|
|
|
|
|
undefined /* valueSpan */,
|
|
|
|
|
targetMatchableAttrs,
|
|
|
|
|
targetProps,
|
|
|
|
|
keySpan,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-23 22:51:42 +00:00
|
|
|
/**
|
2020-03-05 23:38:25 +00:00
|
|
|
* Parses the bindings in a microsyntax expression, e.g.
|
|
|
|
|
* ```
|
2019-07-23 22:51:42 +00:00
|
|
|
* <tag *tplKey="let value1 = prop; let value2 = localVar">
|
2020-03-05 23:38:25 +00:00
|
|
|
* ```
|
|
|
|
|
*
|
2019-07-23 22:51:42 +00:00
|
|
|
* @param tplKey template binding name
|
|
|
|
|
* @param tplValue template binding value
|
|
|
|
|
* @param sourceSpan span of template binding relative to entire the template
|
2020-03-05 23:38:25 +00:00
|
|
|
* @param absoluteKeyOffset start of the `tplKey`
|
|
|
|
|
* @param absoluteValueOffset start of the `tplValue`
|
2019-07-23 22:51:42 +00:00
|
|
|
*/
|
2019-10-15 22:02:56 +00:00
|
|
|
private _parseTemplateBindings(
|
2024-04-18 16:14:19 +00:00
|
|
|
tplKey: string,
|
|
|
|
|
tplValue: string,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
absoluteKeyOffset: number,
|
|
|
|
|
absoluteValueOffset: number,
|
|
|
|
|
): TemplateBinding[] {
|
2016-10-21 18:41:14 +00:00
|
|
|
const sourceInfo = sourceSpan.start.toString();
|
|
|
|
|
|
|
|
|
|
try {
|
2020-03-05 23:38:25 +00:00
|
|
|
const bindingsResult = this._exprParser.parseTemplateBindings(
|
2024-04-18 16:14:19 +00:00
|
|
|
tplKey,
|
|
|
|
|
tplValue,
|
|
|
|
|
sourceInfo,
|
|
|
|
|
absoluteKeyOffset,
|
|
|
|
|
absoluteValueOffset,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
2020-04-08 17:14:18 +00:00
|
|
|
bindingsResult.warnings.forEach((warning) => {
|
|
|
|
|
this._reportError(warning, sourceSpan, ParseErrorLevel.WARNING);
|
|
|
|
|
});
|
2016-10-21 18:41:14 +00:00
|
|
|
return bindingsResult.templateBindings;
|
|
|
|
|
} catch (e) {
|
2016-10-24 18:11:31 +00:00
|
|
|
this._reportError(`${e}`, sourceSpan);
|
2016-10-21 18:41:14 +00:00
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parseLiteralAttr(
|
2024-04-18 16:14:19 +00:00
|
|
|
name: string,
|
|
|
|
|
value: string | null,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
absoluteOffset: number,
|
|
|
|
|
valueSpan: ParseSourceSpan | undefined,
|
|
|
|
|
targetMatchableAttrs: string[][],
|
|
|
|
|
targetProps: ParsedProperty[],
|
|
|
|
|
keySpan: ParseSourceSpan,
|
|
|
|
|
) {
|
2018-04-20 18:28:34 +00:00
|
|
|
if (isAnimationLabel(name)) {
|
2016-10-21 18:41:14 +00:00
|
|
|
name = name.substring(1);
|
2021-01-07 09:48:55 +00:00
|
|
|
if (keySpan !== undefined) {
|
|
|
|
|
keySpan = moveParseSourceSpan(
|
2024-04-18 16:14:19 +00:00
|
|
|
keySpan,
|
|
|
|
|
new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset),
|
|
|
|
|
);
|
2021-01-07 09:48:55 +00:00
|
|
|
}
|
2016-10-24 18:11:31 +00:00
|
|
|
if (value) {
|
|
|
|
|
this._reportError(
|
2024-04-18 16:14:19 +00:00
|
|
|
`Assigning animation triggers via @prop="exp" attributes with an expression is invalid.` +
|
|
|
|
|
` Use property bindings (e.g. [@prop]="exp") or use an attribute without a value (e.g. @prop) instead.`,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
ParseErrorLevel.ERROR,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
2019-07-16 19:18:32 +00:00
|
|
|
this._parseAnimation(
|
2024-04-18 16:14:19 +00:00
|
|
|
name,
|
|
|
|
|
value,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
absoluteOffset,
|
|
|
|
|
keySpan,
|
|
|
|
|
valueSpan,
|
|
|
|
|
targetMatchableAttrs,
|
|
|
|
|
targetProps,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
} else {
|
2024-04-18 16:14:19 +00:00
|
|
|
targetProps.push(
|
|
|
|
|
new ParsedProperty(
|
|
|
|
|
name,
|
|
|
|
|
this._exprParser.wrapLiteralPrimitive(value, '', absoluteOffset),
|
|
|
|
|
ParsedPropertyType.LITERAL_ATTR,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
keySpan,
|
|
|
|
|
valueSpan,
|
|
|
|
|
),
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parsePropertyBinding(
|
2024-04-18 16:14:19 +00:00
|
|
|
name: string,
|
|
|
|
|
expression: string,
|
|
|
|
|
isHost: boolean,
|
|
|
|
|
isPartOfAssignmentBinding: boolean,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
absoluteOffset: number,
|
|
|
|
|
valueSpan: ParseSourceSpan | undefined,
|
|
|
|
|
targetMatchableAttrs: string[][],
|
|
|
|
|
targetProps: ParsedProperty[],
|
|
|
|
|
keySpan: ParseSourceSpan,
|
|
|
|
|
) {
|
2019-12-29 00:02:09 +00:00
|
|
|
if (name.length === 0) {
|
|
|
|
|
this._reportError(`Property name is missing in binding`, sourceSpan);
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-21 18:41:14 +00:00
|
|
|
let isAnimationProp = false;
|
|
|
|
|
if (name.startsWith(ANIMATE_PROP_PREFIX)) {
|
|
|
|
|
isAnimationProp = true;
|
|
|
|
|
name = name.substring(ANIMATE_PROP_PREFIX.length);
|
2021-01-07 09:48:55 +00:00
|
|
|
if (keySpan !== undefined) {
|
|
|
|
|
keySpan = moveParseSourceSpan(
|
2024-04-18 16:14:19 +00:00
|
|
|
keySpan,
|
|
|
|
|
new AbsoluteSourceSpan(
|
|
|
|
|
keySpan.start.offset + ANIMATE_PROP_PREFIX.length,
|
|
|
|
|
keySpan.end.offset,
|
|
|
|
|
),
|
|
|
|
|
);
|
2021-01-07 09:48:55 +00:00
|
|
|
}
|
2018-04-20 18:28:34 +00:00
|
|
|
} else if (isAnimationLabel(name)) {
|
2016-10-21 18:41:14 +00:00
|
|
|
isAnimationProp = true;
|
|
|
|
|
name = name.substring(1);
|
2021-01-07 09:48:55 +00:00
|
|
|
if (keySpan !== undefined) {
|
|
|
|
|
keySpan = moveParseSourceSpan(
|
2024-04-18 16:14:19 +00:00
|
|
|
keySpan,
|
|
|
|
|
new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset),
|
|
|
|
|
);
|
2021-01-07 09:48:55 +00:00
|
|
|
}
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isAnimationProp) {
|
2019-07-16 19:18:32 +00:00
|
|
|
this._parseAnimation(
|
2024-04-18 16:14:19 +00:00
|
|
|
name,
|
|
|
|
|
expression,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
absoluteOffset,
|
|
|
|
|
keySpan,
|
|
|
|
|
valueSpan,
|
|
|
|
|
targetMatchableAttrs,
|
|
|
|
|
targetProps,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
} else {
|
|
|
|
|
this._parsePropertyAst(
|
2024-04-18 16:14:19 +00:00
|
|
|
name,
|
|
|
|
|
this.parseBinding(expression, isHost, valueSpan || sourceSpan, absoluteOffset),
|
|
|
|
|
isPartOfAssignmentBinding,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
keySpan,
|
|
|
|
|
valueSpan,
|
|
|
|
|
targetMatchableAttrs,
|
|
|
|
|
targetProps,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parsePropertyInterpolation(
|
2024-04-18 16:14:19 +00:00
|
|
|
name: string,
|
|
|
|
|
value: string,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
valueSpan: ParseSourceSpan | undefined,
|
|
|
|
|
targetMatchableAttrs: string[][],
|
|
|
|
|
targetProps: ParsedProperty[],
|
|
|
|
|
keySpan: ParseSourceSpan,
|
|
|
|
|
interpolatedTokens: InterpolatedAttributeToken[] | InterpolatedTextToken[] | null,
|
|
|
|
|
): boolean {
|
2022-01-21 22:41:03 +00:00
|
|
|
const expr = this.parseInterpolation(value, valueSpan || sourceSpan, interpolatedTokens);
|
2017-01-04 21:59:43 +00:00
|
|
|
if (expr) {
|
2020-09-17 20:01:32 +00:00
|
|
|
this._parsePropertyAst(
|
2024-04-18 16:14:19 +00:00
|
|
|
name,
|
|
|
|
|
expr,
|
|
|
|
|
false,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
keySpan,
|
|
|
|
|
valueSpan,
|
|
|
|
|
targetMatchableAttrs,
|
|
|
|
|
targetProps,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _parsePropertyAst(
|
2024-04-18 16:14:19 +00:00
|
|
|
name: string,
|
|
|
|
|
ast: ASTWithSource,
|
|
|
|
|
isPartOfAssignmentBinding: boolean,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
keySpan: ParseSourceSpan,
|
|
|
|
|
valueSpan: ParseSourceSpan | undefined,
|
|
|
|
|
targetMatchableAttrs: string[][],
|
|
|
|
|
targetProps: ParsedProperty[],
|
|
|
|
|
) {
|
2020-04-08 17:14:18 +00:00
|
|
|
targetMatchableAttrs.push([name, ast.source!]);
|
2024-04-18 16:14:19 +00:00
|
|
|
targetProps.push(
|
|
|
|
|
new ParsedProperty(
|
|
|
|
|
name,
|
|
|
|
|
ast,
|
2024-01-25 09:08:34 +00:00
|
|
|
isPartOfAssignmentBinding ? ParsedPropertyType.TWO_WAY : ParsedPropertyType.DEFAULT,
|
2024-04-18 16:14:19 +00:00
|
|
|
sourceSpan,
|
|
|
|
|
keySpan,
|
|
|
|
|
valueSpan,
|
|
|
|
|
),
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _parseAnimation(
|
2024-04-18 16:14:19 +00:00
|
|
|
name: string,
|
|
|
|
|
expression: string | null,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
absoluteOffset: number,
|
|
|
|
|
keySpan: ParseSourceSpan,
|
|
|
|
|
valueSpan: ParseSourceSpan | undefined,
|
|
|
|
|
targetMatchableAttrs: string[][],
|
|
|
|
|
targetProps: ParsedProperty[],
|
|
|
|
|
) {
|
2019-12-29 00:02:09 +00:00
|
|
|
if (name.length === 0) {
|
|
|
|
|
this._reportError('Animation trigger is missing', sourceSpan);
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-21 18:41:14 +00:00
|
|
|
// This will occur when a @trigger is not paired with an expression.
|
|
|
|
|
// For animations it is valid to not have an expression since */void
|
|
|
|
|
// states will be applied by angular when the element is attached/detached
|
2023-07-15 08:27:23 +00:00
|
|
|
const ast = this.parseBinding(
|
2024-04-18 16:14:19 +00:00
|
|
|
expression || 'undefined',
|
|
|
|
|
false,
|
|
|
|
|
valueSpan || sourceSpan,
|
|
|
|
|
absoluteOffset,
|
|
|
|
|
);
|
2020-04-08 17:14:18 +00:00
|
|
|
targetMatchableAttrs.push([name, ast.source!]);
|
2024-04-18 16:14:19 +00:00
|
|
|
targetProps.push(
|
|
|
|
|
new ParsedProperty(name, ast, ParsedPropertyType.ANIMATION, sourceSpan, keySpan, valueSpan),
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-15 08:27:23 +00:00
|
|
|
parseBinding(
|
2024-04-18 16:14:19 +00:00
|
|
|
value: string,
|
|
|
|
|
isHostBinding: boolean,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
absoluteOffset: number,
|
|
|
|
|
): ASTWithSource {
|
|
|
|
|
const sourceInfo = ((sourceSpan && sourceSpan.start) || '(unknown)').toString();
|
2016-10-21 18:41:14 +00:00
|
|
|
|
|
|
|
|
try {
|
2024-04-18 16:14:19 +00:00
|
|
|
const ast = isHostBinding
|
|
|
|
|
? this._exprParser.parseSimpleBinding(
|
|
|
|
|
value,
|
|
|
|
|
sourceInfo,
|
|
|
|
|
absoluteOffset,
|
|
|
|
|
this._interpolationConfig,
|
|
|
|
|
)
|
|
|
|
|
: this._exprParser.parseBinding(
|
|
|
|
|
value,
|
|
|
|
|
sourceInfo,
|
|
|
|
|
absoluteOffset,
|
|
|
|
|
this._interpolationConfig,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
if (ast) this._reportExpressionParserErrors(ast.errors, sourceSpan);
|
|
|
|
|
return ast;
|
|
|
|
|
} catch (e) {
|
2016-10-24 18:11:31 +00:00
|
|
|
this._reportError(`${e}`, sourceSpan);
|
2019-07-16 19:18:32 +00:00
|
|
|
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-10 21:34:39 +00:00
|
|
|
createBoundElementProperty(
|
2024-04-18 16:14:19 +00:00
|
|
|
elementSelector: string,
|
|
|
|
|
boundProp: ParsedProperty,
|
|
|
|
|
skipValidation: boolean = false,
|
|
|
|
|
mapPropertyName: boolean = true,
|
|
|
|
|
): BoundElementProperty {
|
2016-10-21 18:41:14 +00:00
|
|
|
if (boundProp.isAnimation) {
|
2018-04-20 18:28:34 +00:00
|
|
|
return new BoundElementProperty(
|
2024-04-18 16:14:19 +00:00
|
|
|
boundProp.name,
|
|
|
|
|
BindingType.Animation,
|
|
|
|
|
SecurityContext.NONE,
|
|
|
|
|
boundProp.expression,
|
|
|
|
|
null,
|
|
|
|
|
boundProp.sourceSpan,
|
|
|
|
|
boundProp.keySpan,
|
|
|
|
|
boundProp.valueSpan,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-18 16:14:19 +00:00
|
|
|
let unit: string | null = null;
|
2020-04-08 17:14:18 +00:00
|
|
|
let bindingType: BindingType = undefined!;
|
2024-04-18 16:14:19 +00:00
|
|
|
let boundPropertyName: string | null = null;
|
2016-10-21 18:41:14 +00:00
|
|
|
const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
|
2020-04-08 17:14:18 +00:00
|
|
|
let securityContexts: SecurityContext[] = undefined!;
|
2016-10-21 18:41:14 +00:00
|
|
|
|
2019-01-10 21:34:39 +00:00
|
|
|
// Check for special cases (prefix style, attr, class)
|
2016-12-09 22:42:13 +00:00
|
|
|
if (parts.length > 1) {
|
2016-10-21 18:41:14 +00:00
|
|
|
if (parts[0] == ATTRIBUTE_PREFIX) {
|
2019-08-22 02:45:43 +00:00
|
|
|
boundPropertyName = parts.slice(1).join(PROPERTY_PARTS_SEPARATOR);
|
2019-01-10 21:34:39 +00:00
|
|
|
if (!skipValidation) {
|
|
|
|
|
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, true);
|
|
|
|
|
}
|
2016-10-24 16:58:52 +00:00
|
|
|
securityContexts = calcPossibleSecurityContexts(
|
2024-04-18 16:14:19 +00:00
|
|
|
this._schemaRegistry,
|
|
|
|
|
elementSelector,
|
|
|
|
|
boundPropertyName,
|
|
|
|
|
true,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
|
|
|
|
|
const nsSeparatorIdx = boundPropertyName.indexOf(':');
|
|
|
|
|
if (nsSeparatorIdx > -1) {
|
|
|
|
|
const ns = boundPropertyName.substring(0, nsSeparatorIdx);
|
|
|
|
|
const name = boundPropertyName.substring(nsSeparatorIdx + 1);
|
|
|
|
|
boundPropertyName = mergeNsAndName(ns, name);
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-24 21:22:55 +00:00
|
|
|
bindingType = BindingType.Attribute;
|
2016-10-21 18:41:14 +00:00
|
|
|
} else if (parts[0] == CLASS_PREFIX) {
|
|
|
|
|
boundPropertyName = parts[1];
|
2018-04-24 21:22:55 +00:00
|
|
|
bindingType = BindingType.Class;
|
2016-10-24 16:58:52 +00:00
|
|
|
securityContexts = [SecurityContext.NONE];
|
2016-10-21 18:41:14 +00:00
|
|
|
} else if (parts[0] == STYLE_PREFIX) {
|
|
|
|
|
unit = parts.length > 2 ? parts[2] : null;
|
|
|
|
|
boundPropertyName = parts[1];
|
2018-04-24 21:22:55 +00:00
|
|
|
bindingType = BindingType.Style;
|
2016-10-24 16:58:52 +00:00
|
|
|
securityContexts = [SecurityContext.STYLE];
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
2016-12-09 22:42:13 +00:00
|
|
|
|
|
|
|
|
// If not a special case, use the full property name
|
|
|
|
|
if (boundPropertyName === null) {
|
2019-02-15 20:55:07 +00:00
|
|
|
const mappedPropName = this._schemaRegistry.getMappedPropName(boundProp.name);
|
|
|
|
|
boundPropertyName = mapPropertyName ? mappedPropName : boundProp.name;
|
2016-12-09 22:42:13 +00:00
|
|
|
securityContexts = calcPossibleSecurityContexts(
|
2024-04-18 16:14:19 +00:00
|
|
|
this._schemaRegistry,
|
|
|
|
|
elementSelector,
|
|
|
|
|
mappedPropName,
|
|
|
|
|
false,
|
|
|
|
|
);
|
2024-01-25 09:08:34 +00:00
|
|
|
bindingType =
|
2024-04-18 16:14:19 +00:00
|
|
|
boundProp.type === ParsedPropertyType.TWO_WAY ? BindingType.TwoWay : BindingType.Property;
|
2019-01-10 21:34:39 +00:00
|
|
|
if (!skipValidation) {
|
2019-02-15 20:55:07 +00:00
|
|
|
this._validatePropertyOrAttributeName(mappedPropName, boundProp.sourceSpan, false);
|
2019-01-10 21:34:39 +00:00
|
|
|
}
|
2016-12-09 22:42:13 +00:00
|
|
|
}
|
|
|
|
|
|
2018-04-20 18:28:34 +00:00
|
|
|
return new BoundElementProperty(
|
2024-04-18 16:14:19 +00:00
|
|
|
boundPropertyName,
|
|
|
|
|
bindingType,
|
|
|
|
|
securityContexts[0],
|
|
|
|
|
boundProp.expression,
|
|
|
|
|
unit,
|
|
|
|
|
boundProp.sourceSpan,
|
|
|
|
|
boundProp.keySpan,
|
|
|
|
|
boundProp.valueSpan,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
|
2020-11-09 17:16:19 +00:00
|
|
|
// TODO: keySpan should be required but was made optional to avoid changing VE parser.
|
2016-10-21 18:41:14 +00:00
|
|
|
parseEvent(
|
2024-04-18 16:14:19 +00:00
|
|
|
name: string,
|
|
|
|
|
expression: string,
|
|
|
|
|
isAssignmentEvent: boolean,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
handlerSpan: ParseSourceSpan,
|
|
|
|
|
targetMatchableAttrs: string[][],
|
|
|
|
|
targetEvents: ParsedEvent[],
|
|
|
|
|
keySpan: ParseSourceSpan,
|
|
|
|
|
) {
|
2019-12-29 00:02:09 +00:00
|
|
|
if (name.length === 0) {
|
|
|
|
|
this._reportError(`Event name is missing in binding`, sourceSpan);
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-20 18:28:34 +00:00
|
|
|
if (isAnimationLabel(name)) {
|
2022-03-20 10:23:47 +00:00
|
|
|
name = name.slice(1);
|
2021-01-07 09:48:55 +00:00
|
|
|
if (keySpan !== undefined) {
|
|
|
|
|
keySpan = moveParseSourceSpan(
|
2024-04-18 16:14:19 +00:00
|
|
|
keySpan,
|
|
|
|
|
new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset),
|
|
|
|
|
);
|
2021-01-07 09:48:55 +00:00
|
|
|
}
|
2024-01-30 08:11:30 +00:00
|
|
|
this._parseAnimationEvent(name, expression, sourceSpan, handlerSpan, targetEvents, keySpan);
|
2016-10-21 18:41:14 +00:00
|
|
|
} else {
|
2019-02-08 22:10:20 +00:00
|
|
|
this._parseRegularEvent(
|
2024-04-18 16:14:19 +00:00
|
|
|
name,
|
|
|
|
|
expression,
|
|
|
|
|
isAssignmentEvent,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
handlerSpan,
|
|
|
|
|
targetMatchableAttrs,
|
|
|
|
|
targetEvents,
|
|
|
|
|
keySpan,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-18 16:14:19 +00:00
|
|
|
calcPossibleSecurityContexts(
|
|
|
|
|
selector: string,
|
|
|
|
|
propName: string,
|
|
|
|
|
isAttribute: boolean,
|
|
|
|
|
): SecurityContext[] {
|
2019-01-03 18:04:06 +00:00
|
|
|
const prop = this._schemaRegistry.getMappedPropName(propName);
|
|
|
|
|
return calcPossibleSecurityContexts(this._schemaRegistry, selector, prop, isAttribute);
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-21 18:41:14 +00:00
|
|
|
private _parseAnimationEvent(
|
2024-04-18 16:14:19 +00:00
|
|
|
name: string,
|
|
|
|
|
expression: string,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
handlerSpan: ParseSourceSpan,
|
|
|
|
|
targetEvents: ParsedEvent[],
|
|
|
|
|
keySpan: ParseSourceSpan,
|
|
|
|
|
) {
|
2016-10-21 18:41:14 +00:00
|
|
|
const matches = splitAtPeriod(name, [name, '']);
|
|
|
|
|
const eventName = matches[0];
|
|
|
|
|
const phase = matches[1].toLowerCase();
|
2024-01-30 08:11:30 +00:00
|
|
|
const ast = this._parseAction(expression, handlerSpan);
|
2024-04-18 16:14:19 +00:00
|
|
|
targetEvents.push(
|
|
|
|
|
new ParsedEvent(
|
|
|
|
|
eventName,
|
|
|
|
|
phase,
|
|
|
|
|
ParsedEventType.Animation,
|
|
|
|
|
ast,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
handlerSpan,
|
|
|
|
|
keySpan,
|
|
|
|
|
),
|
|
|
|
|
);
|
2020-12-02 03:14:07 +00:00
|
|
|
|
|
|
|
|
if (eventName.length === 0) {
|
|
|
|
|
this._reportError(`Animation event name is missing in binding`, sourceSpan);
|
|
|
|
|
}
|
2016-10-21 18:41:14 +00:00
|
|
|
if (phase) {
|
2020-12-02 03:14:07 +00:00
|
|
|
if (phase !== 'start' && phase !== 'done') {
|
|
|
|
|
this._reportError(
|
2024-04-18 16:14:19 +00:00
|
|
|
`The provided animation output phase value "${phase}" for "@${eventName}" is not supported (use start or done)`,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
2016-10-24 18:11:31 +00:00
|
|
|
this._reportError(
|
2024-04-18 16:14:19 +00:00
|
|
|
`The animation trigger output event (@${eventName}) is missing its phase value name (start or done are currently supported)`,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-20 18:28:34 +00:00
|
|
|
private _parseRegularEvent(
|
2024-04-18 16:14:19 +00:00
|
|
|
name: string,
|
|
|
|
|
expression: string,
|
|
|
|
|
isAssignmentEvent: boolean,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
handlerSpan: ParseSourceSpan,
|
|
|
|
|
targetMatchableAttrs: string[][],
|
|
|
|
|
targetEvents: ParsedEvent[],
|
|
|
|
|
keySpan: ParseSourceSpan,
|
|
|
|
|
): void {
|
2016-10-21 18:41:14 +00:00
|
|
|
// long format: 'target: eventName'
|
2020-04-08 17:14:18 +00:00
|
|
|
const [target, eventName] = splitAtColon(name, [null!, name]);
|
2024-01-30 08:11:30 +00:00
|
|
|
const prevErrorCount = this.errors.length;
|
|
|
|
|
const ast = this._parseAction(expression, handlerSpan);
|
|
|
|
|
const isValid = this.errors.length === prevErrorCount;
|
2020-04-08 17:14:18 +00:00
|
|
|
targetMatchableAttrs.push([name!, ast.source!]);
|
2024-01-30 08:11:30 +00:00
|
|
|
|
|
|
|
|
// Don't try to validate assignment events if there were other
|
|
|
|
|
// parsing errors to avoid adding more noise to the error logs.
|
|
|
|
|
if (isAssignmentEvent && isValid && !this._isAllowedAssignmentEvent(ast)) {
|
|
|
|
|
this._reportError('Unsupported expression in a two-way binding', sourceSpan);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-18 16:14:19 +00:00
|
|
|
targetEvents.push(
|
|
|
|
|
new ParsedEvent(
|
|
|
|
|
eventName,
|
|
|
|
|
target,
|
|
|
|
|
isAssignmentEvent ? ParsedEventType.TwoWay : ParsedEventType.Regular,
|
|
|
|
|
ast,
|
|
|
|
|
sourceSpan,
|
|
|
|
|
handlerSpan,
|
|
|
|
|
keySpan,
|
|
|
|
|
),
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
// Don't detect directives for event names for now,
|
|
|
|
|
// so don't add the event name to the matchableAttrs
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-30 08:11:30 +00:00
|
|
|
private _parseAction(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
|
2024-04-18 16:14:19 +00:00
|
|
|
const sourceInfo = ((sourceSpan && sourceSpan.start) || '(unknown').toString();
|
|
|
|
|
const absoluteOffset = sourceSpan && sourceSpan.start ? sourceSpan.start.offset : 0;
|
2016-10-21 18:41:14 +00:00
|
|
|
|
|
|
|
|
try {
|
2019-07-16 19:18:32 +00:00
|
|
|
const ast = this._exprParser.parseAction(
|
2024-04-18 16:14:19 +00:00
|
|
|
value,
|
|
|
|
|
sourceInfo,
|
|
|
|
|
absoluteOffset,
|
|
|
|
|
this._interpolationConfig,
|
|
|
|
|
);
|
2016-10-21 18:41:14 +00:00
|
|
|
if (ast) {
|
|
|
|
|
this._reportExpressionParserErrors(ast.errors, sourceSpan);
|
|
|
|
|
}
|
|
|
|
|
if (!ast || ast.ast instanceof EmptyExpr) {
|
2016-10-24 18:11:31 +00:00
|
|
|
this._reportError(`Empty expressions are not allowed`, sourceSpan);
|
2019-07-16 19:18:32 +00:00
|
|
|
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
return ast;
|
|
|
|
|
} catch (e) {
|
2016-10-24 18:11:31 +00:00
|
|
|
this._reportError(`${e}`, sourceSpan);
|
2019-07-16 19:18:32 +00:00
|
|
|
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-24 18:11:31 +00:00
|
|
|
private _reportError(
|
2024-04-18 16:14:19 +00:00
|
|
|
message: string,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
level: ParseErrorLevel = ParseErrorLevel.ERROR,
|
2024-11-25 10:54:54 +00:00
|
|
|
relatedError?: ParserError,
|
2024-04-18 16:14:19 +00:00
|
|
|
) {
|
2024-11-25 10:54:54 +00:00
|
|
|
this.errors.push(new ParseError(sourceSpan, message, level, relatedError));
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _reportExpressionParserErrors(errors: ParserError[], sourceSpan: ParseSourceSpan) {
|
|
|
|
|
for (const error of errors) {
|
2024-11-25 10:54:54 +00:00
|
|
|
this._reportError(error.message, sourceSpan, undefined, error);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param propName the name of the property / attribute
|
|
|
|
|
* @param sourceSpan
|
|
|
|
|
* @param isAttr true when binding to an attribute
|
|
|
|
|
*/
|
|
|
|
|
private _validatePropertyOrAttributeName(
|
2024-04-18 16:14:19 +00:00
|
|
|
propName: string,
|
|
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
isAttr: boolean,
|
|
|
|
|
): void {
|
|
|
|
|
const report = isAttr
|
|
|
|
|
? this._schemaRegistry.validateAttribute(propName)
|
|
|
|
|
: this._schemaRegistry.validateProperty(propName);
|
2016-10-21 18:41:14 +00:00
|
|
|
if (report.error) {
|
2020-04-08 17:14:18 +00:00
|
|
|
this._reportError(report.msg!, sourceSpan, ParseErrorLevel.ERROR);
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
2024-01-30 08:11:30 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns whether a parsed AST is allowed to be used within the event side of a two-way binding.
|
|
|
|
|
* @param ast Parsed AST to be checked.
|
|
|
|
|
*/
|
|
|
|
|
private _isAllowedAssignmentEvent(ast: AST): boolean {
|
|
|
|
|
if (ast instanceof ASTWithSource) {
|
|
|
|
|
return this._isAllowedAssignmentEvent(ast.ast);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ast instanceof NonNullAssert) {
|
|
|
|
|
return this._isAllowedAssignmentEvent(ast.expression);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-30 19:32:44 +00:00
|
|
|
if (ast instanceof PropertyRead || ast instanceof KeyedRead) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-01 08:11:28 +00:00
|
|
|
return false;
|
2024-01-30 08:11:30 +00:00
|
|
|
}
|
2016-10-21 18:41:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class PipeCollector extends RecursiveAstVisitor {
|
2016-12-12 23:59:12 +00:00
|
|
|
pipes = new Map<string, BindingPipe>();
|
2021-06-07 15:10:51 +00:00
|
|
|
override visitPipe(ast: BindingPipe, context: any): any {
|
2016-12-12 23:59:12 +00:00
|
|
|
this.pipes.set(ast.name, ast);
|
2016-10-21 18:41:14 +00:00
|
|
|
ast.exp.visit(this);
|
|
|
|
|
this.visitAll(ast.args, context);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-20 18:28:34 +00:00
|
|
|
function isAnimationLabel(name: string): boolean {
|
2016-10-21 18:41:14 +00:00
|
|
|
return name[0] == '@';
|
|
|
|
|
}
|
2016-10-24 16:58:52 +00:00
|
|
|
|
|
|
|
|
export function calcPossibleSecurityContexts(
|
2024-04-18 16:14:19 +00:00
|
|
|
registry: ElementSchemaRegistry,
|
|
|
|
|
selector: string,
|
|
|
|
|
propName: string,
|
|
|
|
|
isAttribute: boolean,
|
|
|
|
|
): SecurityContext[] {
|
2016-10-24 16:58:52 +00:00
|
|
|
const ctxs: SecurityContext[] = [];
|
|
|
|
|
CssSelector.parse(selector).forEach((selector) => {
|
|
|
|
|
const elementNames = selector.element ? [selector.element] : registry.allKnownElementNames();
|
2024-04-18 16:14:19 +00:00
|
|
|
const notElementNames = new Set(
|
|
|
|
|
selector.notSelectors
|
|
|
|
|
.filter((selector) => selector.isElementSelector())
|
|
|
|
|
.map((selector) => selector.element),
|
|
|
|
|
);
|
|
|
|
|
const possibleElementNames = elementNames.filter(
|
|
|
|
|
(elementName) => !notElementNames.has(elementName),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
ctxs.push(
|
|
|
|
|
...possibleElementNames.map((elementName) =>
|
|
|
|
|
registry.securityContext(elementName, propName, isAttribute),
|
|
|
|
|
),
|
|
|
|
|
);
|
2016-10-24 16:58:52 +00:00
|
|
|
});
|
|
|
|
|
return ctxs.length === 0 ? [SecurityContext.NONE] : Array.from(new Set(ctxs)).sort();
|
2019-02-15 20:55:07 +00:00
|
|
|
}
|
2020-03-11 23:46:08 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Compute a new ParseSourceSpan based off an original `sourceSpan` by using
|
|
|
|
|
* absolute offsets from the specified `absoluteSpan`.
|
|
|
|
|
*
|
|
|
|
|
* @param sourceSpan original source span
|
|
|
|
|
* @param absoluteSpan absolute source span to move to
|
|
|
|
|
*/
|
|
|
|
|
function moveParseSourceSpan(
|
2024-04-18 16:14:19 +00:00
|
|
|
sourceSpan: ParseSourceSpan,
|
|
|
|
|
absoluteSpan: AbsoluteSourceSpan,
|
|
|
|
|
): ParseSourceSpan {
|
2020-03-11 23:46:08 +00:00
|
|
|
// The difference of two absolute offsets provide the relative offset
|
|
|
|
|
const startDiff = absoluteSpan.start - sourceSpan.start.offset;
|
|
|
|
|
const endDiff = absoluteSpan.end - sourceSpan.end.offset;
|
2020-10-28 21:35:06 +00:00
|
|
|
return new ParseSourceSpan(
|
2024-04-18 16:14:19 +00:00
|
|
|
sourceSpan.start.moveBy(startDiff),
|
|
|
|
|
sourceSpan.end.moveBy(endDiff),
|
|
|
|
|
sourceSpan.fullStart.moveBy(startDiff),
|
|
|
|
|
sourceSpan.details,
|
|
|
|
|
);
|
2020-03-11 23:46:08 +00:00
|
|
|
}
|