refactor(compiler): add `span` to `TemplateBinding` (#12792)
fix(compiler): corrected error location for implicit templates expressions
This commit is contained in:
parent
44a142fc02
commit
c3c0e2e2a2
|
@ -204,7 +204,7 @@ export class ASTWithSource extends AST {
|
|||
|
||||
export class TemplateBinding {
|
||||
constructor(
|
||||
public key: string, public keyIsVar: boolean, public name: string,
|
||||
public span: ParseSpan, public key: string, public keyIsVar: boolean, public name: string,
|
||||
public expression: ASTWithSource) {}
|
||||
}
|
||||
|
||||
|
|
|
@ -102,8 +102,17 @@ export class Parser {
|
|||
return new Quote(new ParseSpan(0, input.length), prefix, uninterpretedExpression, location);
|
||||
}
|
||||
|
||||
parseTemplateBindings(input: string, location: any): TemplateBindingParseResult {
|
||||
var tokens = this._lexer.tokenize(input);
|
||||
parseTemplateBindings(prefixToken: string, input: string, location: any):
|
||||
TemplateBindingParseResult {
|
||||
const tokens = this._lexer.tokenize(input);
|
||||
if (prefixToken) {
|
||||
// Prefix the tokens with the tokens from prefixToken but have them take no space (0 index).
|
||||
const prefixTokens = this._lexer.tokenize(prefixToken).map(t => {
|
||||
t.index = 0;
|
||||
return t;
|
||||
});
|
||||
tokens.unshift(...prefixTokens);
|
||||
}
|
||||
return new _ParseAST(input, location, tokens, input.length, false, this.errors, 0)
|
||||
.parseTemplateBindings();
|
||||
}
|
||||
|
@ -161,6 +170,8 @@ export class Parser {
|
|||
'Blank expressions are not allowed in interpolated strings', input,
|
||||
`at column ${this._findInterpolationErrorColumn(parts, i, interpolationConfig)} in`,
|
||||
location);
|
||||
expressions.push('$implict');
|
||||
offsets.push(offset);
|
||||
}
|
||||
}
|
||||
return new SplitInterpolation(strings, expressions, offsets);
|
||||
|
@ -676,6 +687,7 @@ export class _ParseAST {
|
|||
let prefix: string = null;
|
||||
let warnings: string[] = [];
|
||||
while (this.index < this.tokens.length) {
|
||||
const start = this.inputIndex;
|
||||
const keyIsVar: boolean = this.peekKeywordLet();
|
||||
if (keyIsVar) {
|
||||
this.advance();
|
||||
|
@ -700,10 +712,10 @@ export class _ParseAST {
|
|||
} else if (this.next !== EOF && !this.peekKeywordLet()) {
|
||||
const start = this.inputIndex;
|
||||
const ast = this.parsePipe();
|
||||
const source = this.input.substring(start, this.inputIndex);
|
||||
const source = this.input.substring(start - this.offset, this.inputIndex - this.offset);
|
||||
expression = new ASTWithSource(ast, source, this.location, this.errors);
|
||||
}
|
||||
bindings.push(new TemplateBinding(key, keyIsVar, name, expression));
|
||||
bindings.push(new TemplateBinding(this.span(start), key, keyIsVar, name, expression));
|
||||
if (!this.optionalCharacter(chars.$SEMICOLON)) {
|
||||
this.optionalCharacter(chars.$COMMA);
|
||||
}
|
||||
|
|
|
@ -112,9 +112,9 @@ export class BindingParser {
|
|||
}
|
||||
|
||||
parseInlineTemplateBinding(
|
||||
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
|
||||
targetProps: BoundProperty[], targetVars: VariableAst[]) {
|
||||
const bindings = this._parseTemplateBindings(value, sourceSpan);
|
||||
name: string, prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) {
|
||||
const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan);
|
||||
for (let i = 0; i < bindings.length; i++) {
|
||||
const binding = bindings[i];
|
||||
if (binding.keyIsVar) {
|
||||
|
@ -129,11 +129,12 @@ export class BindingParser {
|
|||
}
|
||||
}
|
||||
|
||||
private _parseTemplateBindings(value: string, sourceSpan: ParseSourceSpan): TemplateBinding[] {
|
||||
private _parseTemplateBindings(prefixToken: string, value: string, sourceSpan: ParseSourceSpan):
|
||||
TemplateBinding[] {
|
||||
const sourceInfo = sourceSpan.start.toString();
|
||||
|
||||
try {
|
||||
const bindingsResult = this._exprParser.parseTemplateBindings(value, sourceInfo);
|
||||
const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo);
|
||||
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
||||
bindingsResult.templateBindings.forEach((binding) => {
|
||||
if (isPresent(binding.expression)) {
|
||||
|
|
|
@ -271,12 +271,13 @@ class TemplateParseVisitor implements html.Visitor {
|
|||
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
|
||||
elementOrDirectiveRefs, elementVars);
|
||||
|
||||
let templateBindingsSource: string;
|
||||
let templateBindingsSource: string|undefined = undefined;
|
||||
let prefixToken: string|undefined = undefined;
|
||||
if (this._normalizeAttributeName(attr.name) == TEMPLATE_ATTR) {
|
||||
templateBindingsSource = attr.value;
|
||||
} else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
||||
const key = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star
|
||||
templateBindingsSource = (attr.value.length == 0) ? key : key + ' ' + attr.value;
|
||||
templateBindingsSource = attr.value;
|
||||
prefixToken = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star
|
||||
}
|
||||
const hasTemplateBinding = isPresent(templateBindingsSource);
|
||||
if (hasTemplateBinding) {
|
||||
|
@ -287,7 +288,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||
}
|
||||
hasInlineTemplates = true;
|
||||
this._bindingParser.parseInlineTemplateBinding(
|
||||
attr.name, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs,
|
||||
attr.name, prefixToken, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs,
|
||||
templateElementOrDirectiveProps, templateElementVars);
|
||||
}
|
||||
|
||||
|
|
|
@ -28,11 +28,12 @@ export function main() {
|
|||
}
|
||||
|
||||
function parseTemplateBindingsResult(
|
||||
text: string, location: any = null): TemplateBindingParseResult {
|
||||
return createParser().parseTemplateBindings(text, location);
|
||||
text: string, location: any = null, prefix?: string): TemplateBindingParseResult {
|
||||
return createParser().parseTemplateBindings(prefix, text, location);
|
||||
}
|
||||
function parseTemplateBindings(text: string, location: any = null): TemplateBinding[] {
|
||||
return parseTemplateBindingsResult(text, location).templateBindings;
|
||||
function parseTemplateBindings(
|
||||
text: string, location: any = null, prefix?: string): TemplateBinding[] {
|
||||
return parseTemplateBindingsResult(text, location, prefix).templateBindings;
|
||||
}
|
||||
|
||||
function parseInterpolation(text: string, location: any = null): ASTWithSource {
|
||||
|
@ -327,6 +328,11 @@ export function main() {
|
|||
});
|
||||
}
|
||||
|
||||
function keySpans(source: string, templateBindings: TemplateBinding[]) {
|
||||
return templateBindings.map(
|
||||
binding => source.substring(binding.span.start, binding.span.end));
|
||||
}
|
||||
|
||||
function exprSources(templateBindings: any[]) {
|
||||
return templateBindings.map(
|
||||
binding => isPresent(binding.expression) ? binding.expression.source : null);
|
||||
|
@ -429,6 +435,30 @@ export function main() {
|
|||
var ast = bindings[0].expression.ast;
|
||||
expect(ast).toBeAnInstanceOf(BindingPipe);
|
||||
});
|
||||
|
||||
describe('spans', () => {
|
||||
it('should should support let', () => {
|
||||
const source = 'let i';
|
||||
expect(keySpans(source, parseTemplateBindings(source))).toEqual(['let i']);
|
||||
});
|
||||
|
||||
it('should support multiple lets', () => {
|
||||
const source = 'let item; let i=index; let e=even;';
|
||||
expect(keySpans(source, parseTemplateBindings(source))).toEqual([
|
||||
'let item', 'let i=index', 'let e=even'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should support a prefix', () => {
|
||||
var source = 'let person of people';
|
||||
var prefix = 'ngFor';
|
||||
var bindings = parseTemplateBindings(source, null, prefix);
|
||||
expect(keyValues(bindings)).toEqual([
|
||||
'ngFor', 'let person=$implicit', 'ngForOf=people in null'
|
||||
]);
|
||||
expect(keySpans(source, bindings)).toEqual(['', 'let person ', 'of people']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseInterpolation', () => {
|
||||
|
|
|
@ -1192,7 +1192,7 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
|
|||
|
||||
it('should report an error on variables declared with #', () => {
|
||||
expect(() => humanizeTplAst(parse('<div *ngIf="#a=b">', [])))
|
||||
.toThrowError(/Parser Error: Unexpected token # at column 6/);
|
||||
.toThrowError(/Parser Error: Unexpected token # at column 1/);
|
||||
});
|
||||
|
||||
it('should parse variables via let ...', () => {
|
||||
|
|
Loading…
Reference in New Issue