refactor(compiler): parse bindings "by hand" rather than via regex (#39375)
To support recovery of malformed binding property names like `([a)`, `[a`, or `()`, the binding parser needs to be more permissive w.r.t. the kinds of bindings it can detect. This is difficult to do maintainably with a regex, but is trivial with a "hand-rolled" string parser. This commit refactors render3's binding attribute parsing to use this method for multi-delimited bindings (namely via the `()`, `[]`, and `[()]`) syntax, making the way recovery of malformed bindings in a future patch. Note that we can keep using a regex for prefix-only binding syntax (e.g. `bind-`, `ref-`) because validation of the binding is complete once we have matched the prefix, and the only thing left to do is check that the binding identifier is non-empty, which is trivial. Part of #38596 PR Close #39375
This commit is contained in:
parent
c83b2ad87f
commit
3241d922fc
|
@ -15,13 +15,11 @@ import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
|
|||
import {isStyleUrlResolvable} from '../style_url_resolver';
|
||||
import {BindingParser} from '../template_parser/binding_parser';
|
||||
import {PreparsedElementType, preparseElement} from '../template_parser/template_preparser';
|
||||
import {syntaxError} from '../util';
|
||||
|
||||
import * as t from './r3_ast';
|
||||
import {I18N_ICU_VAR_PREFIX, isI18nRootNode} from './view/i18n/util';
|
||||
|
||||
const BIND_NAME_REGEXP =
|
||||
/^(?:(?:(?:(bind-)|(let-)|(ref-|#)|(on-)|(bindon-)|(@))(.*))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/;
|
||||
const BIND_NAME_REGEXP = /^(?:(bind-)|(let-)|(ref-|#)|(on-)|(bindon-)|(@))(.*)$/;
|
||||
|
||||
// Group 1 = "bind-"
|
||||
const KW_BIND_IDX = 1;
|
||||
|
@ -37,12 +35,12 @@ const KW_BINDON_IDX = 5;
|
|||
const KW_AT_IDX = 6;
|
||||
// Group 7 = the identifier after "bind-", "let-", "ref-/#", "on-", "bindon-" or "@"
|
||||
const IDENT_KW_IDX = 7;
|
||||
// Group 8 = identifier inside [()]
|
||||
const IDENT_BANANA_BOX_IDX = 8;
|
||||
// Group 9 = identifier inside []
|
||||
const IDENT_PROPERTY_IDX = 9;
|
||||
// Group 10 = identifier inside ()
|
||||
const IDENT_EVENT_IDX = 10;
|
||||
|
||||
const BINDING_DELIMS = {
|
||||
BANANA_BOX: {start: '[(', end: ')]'},
|
||||
PROPERTY: {start: '[', end: ']'},
|
||||
EVENT: {start: '(', end: ')'},
|
||||
};
|
||||
|
||||
const TEMPLATE_ATTR_PREFIX = '*';
|
||||
|
||||
|
@ -337,10 +335,8 @@ class HtmlAstToIvyAst implements html.Visitor {
|
|||
}
|
||||
|
||||
const bindParts = name.match(BIND_NAME_REGEXP);
|
||||
let hasBinding = false;
|
||||
|
||||
if (bindParts) {
|
||||
hasBinding = true;
|
||||
if (bindParts[KW_BIND_IDX] != null) {
|
||||
const identifier = bindParts[IDENT_KW_IDX];
|
||||
const keySpan = createKeySpan(srcSpan, bindParts[KW_BIND_IDX], identifier);
|
||||
|
@ -380,36 +376,54 @@ class HtmlAstToIvyAst implements html.Visitor {
|
|||
this.bindingParser.parseLiteralAttr(
|
||||
name, value, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes,
|
||||
parsedProperties, keySpan);
|
||||
|
||||
} else if (bindParts[IDENT_BANANA_BOX_IDX]) {
|
||||
const keySpan = createKeySpan(srcSpan, '[(', bindParts[IDENT_BANANA_BOX_IDX]);
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_BANANA_BOX_IDX], value, false, srcSpan, absoluteOffset,
|
||||
attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
|
||||
this.parseAssignmentEvent(
|
||||
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, attribute.valueSpan,
|
||||
matchableAttributes, boundEvents);
|
||||
|
||||
} else if (bindParts[IDENT_PROPERTY_IDX]) {
|
||||
const keySpan = createKeySpan(srcSpan, '[', bindParts[IDENT_PROPERTY_IDX]);
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_PROPERTY_IDX], value, false, srcSpan, absoluteOffset,
|
||||
attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
|
||||
|
||||
} else if (bindParts[IDENT_EVENT_IDX]) {
|
||||
const events: ParsedEvent[] = [];
|
||||
this.bindingParser.parseEvent(
|
||||
bindParts[IDENT_EVENT_IDX], value, srcSpan, attribute.valueSpan || srcSpan,
|
||||
matchableAttributes, events);
|
||||
addEvents(events, boundEvents);
|
||||
}
|
||||
} else {
|
||||
const keySpan = createKeySpan(srcSpan, '' /* prefix */, name);
|
||||
hasBinding = this.bindingParser.parsePropertyInterpolation(
|
||||
name, value, srcSpan, attribute.valueSpan, matchableAttributes, parsedProperties,
|
||||
keySpan);
|
||||
return true;
|
||||
}
|
||||
|
||||
// We didn't see a kw-prefixed property binding, but we have not yet checked
|
||||
// for the []/()/[()] syntax.
|
||||
let delims: {start: string, end: string}|null = null;
|
||||
if (name.startsWith(BINDING_DELIMS.BANANA_BOX.start)) {
|
||||
delims = BINDING_DELIMS.BANANA_BOX;
|
||||
} else if (name.startsWith(BINDING_DELIMS.PROPERTY.start)) {
|
||||
delims = BINDING_DELIMS.PROPERTY;
|
||||
} else if (name.startsWith(BINDING_DELIMS.EVENT.start)) {
|
||||
delims = BINDING_DELIMS.EVENT;
|
||||
}
|
||||
if (delims !== null &&
|
||||
// NOTE: older versions of the parser would match a start/end delimited
|
||||
// binding iff the property name was terminated by the ending delimiter
|
||||
// and the identifier in the binding was non-empty.
|
||||
// TODO(ayazhafiz): update this to handle malformed bindings.
|
||||
name.endsWith(delims.end) && name.length > delims.start.length + delims.end.length) {
|
||||
const identifier = name.substring(delims.start.length, name.length - delims.end.length);
|
||||
if (delims.start === BINDING_DELIMS.BANANA_BOX.start) {
|
||||
const keySpan = createKeySpan(srcSpan, delims.start, identifier);
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
identifier, value, false, srcSpan, absoluteOffset, attribute.valueSpan,
|
||||
matchableAttributes, parsedProperties, keySpan);
|
||||
this.parseAssignmentEvent(
|
||||
identifier, value, srcSpan, attribute.valueSpan, matchableAttributes, boundEvents);
|
||||
} else if (delims.start === BINDING_DELIMS.PROPERTY.start) {
|
||||
const keySpan = createKeySpan(srcSpan, delims.start, identifier);
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
identifier, value, false, srcSpan, absoluteOffset, attribute.valueSpan,
|
||||
matchableAttributes, parsedProperties, keySpan);
|
||||
} else {
|
||||
const events: ParsedEvent[] = [];
|
||||
this.bindingParser.parseEvent(
|
||||
identifier, value, srcSpan, attribute.valueSpan || srcSpan, matchableAttributes,
|
||||
events);
|
||||
addEvents(events, boundEvents);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// No explicit binding found.
|
||||
const keySpan = createKeySpan(srcSpan, '' /* prefix */, name);
|
||||
const hasBinding = this.bindingParser.parsePropertyInterpolation(
|
||||
name, value, srcSpan, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
|
||||
return hasBinding;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue