Repository navigation
fix(compiler): don't report parse error for interpolation inside string in property binding #40267
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
63c1d8f
c6ae50b
dc8d6c4
ce32bdb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,6 @@ | |
|
|
||
| import * as chars from '../chars'; | ||
| import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config'; | ||
| import {escapeRegExp} from '../util'; | ||
|
|
||
| import {AbsoluteSourceSpan, AST, AstVisitor, ASTWithSource, Binary, BindingPipe, Chain, Conditional, EmptyExpr, ExpressionBinding, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralMapKey, LiteralPrimitive, MethodCall, NonNullAssert, ParserError, ParseSpan, PrefixNot, PropertyRead, PropertyWrite, Quote, RecursiveAstVisitor, SafeMethodCall, SafePropertyRead, TemplateBinding, TemplateBindingIdentifier, ThisReceiver, Unary, VariableBinding} from './ast'; | ||
| import {EOF, isIdentifier, isQuote, Lexer, Token, TokenType} from './lexer'; | ||
|
|
@@ -30,20 +29,6 @@ export class TemplateBindingParseResult { | |
| public errors: ParserError[]) {} | ||
| } | ||
|
|
||
| const defaultInterpolateRegExp = _createInterpolateRegExp(DEFAULT_INTERPOLATION_CONFIG); | ||
| function _getInterpolateRegExp(config: InterpolationConfig): RegExp { | ||
| if (config === DEFAULT_INTERPOLATION_CONFIG) { | ||
| return defaultInterpolateRegExp; | ||
| } else { | ||
| return _createInterpolateRegExp(config); | ||
| } | ||
| } | ||
|
|
||
| function _createInterpolateRegExp(config: InterpolationConfig): RegExp { | ||
| const pattern = escapeRegExp(config.start) + '([\\s\\S]*?)' + escapeRegExp(config.end); | ||
| return new RegExp(pattern, 'g'); | ||
| } | ||
|
|
||
| export class Parser { | ||
| private errors: ParserError[] = []; | ||
|
|
||
|
|
@@ -247,7 +232,7 @@ export class Parser { | |
| // parse from starting {{ to ending }} while ignoring content inside quotes. | ||
| const fullStart = i; | ||
| const exprStart = fullStart + interpStart.length; | ||
| const exprEnd = this._getExpressiondEndIndex(input, interpEnd, exprStart); | ||
| const exprEnd = this._getInterpolationEndIndex(input, interpEnd, exprStart); | ||
| if (exprEnd === -1) { | ||
| // Could not find the end of the interpolation; do not parse an expression. | ||
| // Instead we should extend the content on the last raw string. | ||
|
|
@@ -315,59 +300,76 @@ export class Parser { | |
| return null; | ||
| } | ||
|
|
||
| private _checkNoInterpolation( | ||
| input: string, location: string, interpolationConfig: InterpolationConfig): void { | ||
| const regexp = _getInterpolateRegExp(interpolationConfig); | ||
| const parts = input.split(regexp); | ||
| if (parts.length > 1) { | ||
| private _checkNoInterpolation(input: string, location: string, {start, end}: InterpolationConfig): | ||
| void { | ||
| let startIndex = -1; | ||
| let endIndex = -1; | ||
|
|
||
| this._forEachUnquotedChar(input, 0, charIndex => { | ||
| if (startIndex === -1) { | ||
| startIndex = input.indexOf(start, charIndex); | ||
| return false; | ||
| } else { | ||
| endIndex = this._getInterpolationEndIndex(input, end, charIndex); | ||
| return endIndex > -1; | ||
| } | ||
| }); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that this is a bit inefficient, since it will continue to run through each unquoted character even Also I am concerned that the following string would find a false positive: since the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this would also be clearer if you created a new function private _getInterpolationStartIndex(input: string, expressionStart: string, start: number): number {
let result = -1;
this._forEachUnquotedChar(input, start, charIndex => {
if (input.startsWith(expressionStart, charIndex)) {
result = charIndex;
return true;
} else {
return false;
}
});
return result;Then you could do: const startIndex = this._getInterpolationStartIndex(input, start, 0);
if (startIndex === -1) {
return;
}
const endIndex = this._getInterpolationEndIndex(input, end, startIndex+start.length);
if (endIndex === -1) {
return;
}
this._reportError(...);
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't want to add a |
||
|
|
||
| if (startIndex > -1 && endIndex > -1) { | ||
| this._reportError( | ||
| `Got interpolation (${interpolationConfig.start}${ | ||
| interpolationConfig.end}) where expression was expected`, | ||
| input, | ||
| `at column ${this._findInterpolationErrorColumn(parts, 1, interpolationConfig)} in`, | ||
| location); | ||
| `Got interpolation (${start}${end}) where expression was expected`, input, | ||
| `at column ${startIndex} in`, location); | ||
| } | ||
| } | ||
|
|
||
| private _findInterpolationErrorColumn( | ||
| parts: string[], partInErrIdx: number, interpolationConfig: InterpolationConfig): number { | ||
| let errLocation = ''; | ||
| for (let j = 0; j < partInErrIdx; j++) { | ||
| errLocation += j % 2 === 0 ? | ||
| parts[j] : | ||
| `${interpolationConfig.start}${parts[j]}${interpolationConfig.end}`; | ||
| } | ||
| /** | ||
| * Finds the index of the end of an interpolation expression | ||
| * while ignoring comments and quoted content. | ||
| */ | ||
| private _getInterpolationEndIndex(input: string, expressionEnd: string, start: number): number { | ||
| let result = -1; | ||
|
|
||
| this._forEachUnquotedChar(input, start, charIndex => { | ||
| if (input.startsWith(expressionEnd, charIndex)) { | ||
| result = charIndex; | ||
| return true; | ||
| } | ||
| // Nothing else in the expression matters after we've | ||
| // hit a comment so look directly for the end token. | ||
| if (input.startsWith('//', charIndex)) { | ||
| result = input.indexOf(expressionEnd, charIndex); | ||
| return true; | ||
| } | ||
| return false; | ||
| }); | ||
|
|
||
| return errLocation.length; | ||
| return result; | ||
| } | ||
|
|
||
| /** | ||
| * Finds the index of the end of an interpolation expression | ||
| * while ignoring comments and quoted content. | ||
| * Invokes a callback function for each character of a string that is outside | ||
| * of a quote. | ||
| * @param input String to loop through. | ||
| * @param start Index within the string at which to start. | ||
| * @param callback Callback to be invoked for each index. If the callback | ||
| * returns `true`, the loop will be broken off. | ||
| */ | ||
| private _getExpressiondEndIndex(input: string, expressionEnd: string, start: number): number { | ||
| private _forEachUnquotedChar(input: string, start: number, callback: (index: number) => boolean): | ||
|
crisbeto marked this conversation as resolved.
Resolved
|
||
| void { | ||
| let currentQuote: string|null = null; | ||
| let escapeCount = 0; | ||
| for (let i = start; i < input.length; i++) { | ||
| const char = input[i]; | ||
| // Skip the characters inside quotes. Note that we only care about the | ||
| // outer-most quotes matching up and we need to account for escape characters. | ||
| // Skip the characters inside quotes. Note that we only care about the outer-most | ||
| // quotes matching up and we need to account for escape characters. | ||
| if (isQuote(input.charCodeAt(i)) && (currentQuote === null || currentQuote === char) && | ||
| escapeCount % 2 === 0) { | ||
| currentQuote = currentQuote === null ? char : null; | ||
| } else if (currentQuote === null) { | ||
| if (input.startsWith(expressionEnd, i)) { | ||
| return i; | ||
| } | ||
| // Nothing else in the expression matters after we've | ||
| // hit a comment so look directly for the end token. | ||
| if (input.startsWith('//', i)) { | ||
| return input.indexOf(expressionEnd, i); | ||
| } | ||
| } else if (currentQuote === null && callback(i)) { | ||
| break; | ||
| } | ||
| escapeCount = char === '\\' ? escapeCount + 1 : 0; | ||
| } | ||
| return -1; | ||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.