fix(compiler): narrow the span reported for invalid pipes

fixes #13326
closes #13411
This commit is contained in:
Chuck Jazdzewski 2016-12-12 15:59:12 -08:00 committed by Victor Berchet
parent 3a64ad895a
commit 2b90cd532f
5 changed files with 54 additions and 6 deletions

View File

@ -344,7 +344,7 @@ export class _ParseAST {
while (this.optionalCharacter(chars.$COLON)) { while (this.optionalCharacter(chars.$COLON)) {
args.push(this.parseExpression()); args.push(this.parseExpression());
} }
result = new BindingPipe(this.span(result.span.start - this.offset), result, name, args); result = new BindingPipe(this.span(result.span.start), result, name, args);
} while (this.optionalOperator('|')); } while (this.optionalOperator('|'));
} }

View File

@ -5,6 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import * as chars from './chars';
import {isPresent} from './facade/lang'; import {isPresent} from './facade/lang';
export class ParseLocation { export class ParseLocation {
@ -15,6 +16,38 @@ export class ParseLocation {
toString(): string { toString(): string {
return isPresent(this.offset) ? `${this.file.url}@${this.line}:${this.col}` : this.file.url; return isPresent(this.offset) ? `${this.file.url}@${this.line}:${this.col}` : this.file.url;
} }
moveBy(delta: number): ParseLocation {
const source = this.file.content;
const len = source.length;
let offset = this.offset;
let line = this.line;
let col = this.col;
while (offset > 0 && delta < 0) {
offset--;
delta++;
const ch = source.charCodeAt(offset);
if (ch == chars.$LF) {
line--;
const priorLine = source.substr(0, offset - 1).lastIndexOf(String.fromCharCode(chars.$LF));
col = priorLine > 0 ? offset - priorLine : offset;
} else {
col--;
}
}
while (offset < len && delta > 0) {
const ch = source.charCodeAt(offset);
offset++;
delta--;
if (ch == chars.$LF) {
line++;
col = 0;
} else {
col++;
}
}
return new ParseLocation(this.file, offset, line, col);
}
} }
export class ParseSourceFile { export class ParseSourceFile {

View File

@ -377,9 +377,12 @@ export class BindingParser {
if (isPresent(ast)) { if (isPresent(ast)) {
const collector = new PipeCollector(); const collector = new PipeCollector();
ast.visit(collector); ast.visit(collector);
collector.pipes.forEach((pipeName) => { collector.pipes.forEach((ast, pipeName) => {
if (!this.pipesByName.has(pipeName)) { if (!this.pipesByName.has(pipeName)) {
this._reportError(`The pipe '${pipeName}' could not be found`, sourceSpan); this._reportError(
`The pipe '${pipeName}' could not be found`,
new ParseSourceSpan(
sourceSpan.start.moveBy(ast.span.start), sourceSpan.start.moveBy(ast.span.end)));
} }
}); });
} }
@ -402,9 +405,9 @@ export class BindingParser {
} }
export class PipeCollector extends RecursiveAstVisitor { export class PipeCollector extends RecursiveAstVisitor {
pipes = new Set<string>(); pipes = new Map<string, BindingPipe>();
visitPipe(ast: BindingPipe, context: any): any { visitPipe(ast: BindingPipe, context: any): any {
this.pipes.add(ast.name); this.pipes.set(ast.name, ast);
ast.exp.visit(this); ast.exp.visit(this);
this.visitAll(ast.args, context); this.visitAll(ast.args, context);
return null; return null;

View File

@ -1839,7 +1839,7 @@ Property binding a not used by any directive on an embedded template. Make sure
it('should report pipes as error that have not been defined as dependencies', () => { it('should report pipes as error that have not been defined as dependencies', () => {
expect(() => parse('{{a | test}}', [])).toThrowError(`Template parse errors: expect(() => parse('{{a | test}}', [])).toThrowError(`Template parse errors:
The pipe 'test' could not be found ("[ERROR ->]{{a | test}}"): TestComp@0:0`); The pipe 'test' could not be found ("{{[ERROR ->]a | test}}"): TestComp@0:2`);
}); });
}); });

View File

@ -151,6 +151,18 @@ describe('diagnostics', () => {
}); });
}); });
// Issue #13326
it('should report a narrow span for invalid pipes', () => {
const code =
` @Component({template: '<p> Using an invalid pipe {{data | dat}} </p>'}) export class MyComponent { data = 'some data'; }`;
addCode(code, fileName => {
const diagnostic =
ngService.getDiagnostics(fileName).filter(d => d.message.indexOf('pipe') > 0)[0];
expect(diagnostic).not.toBeUndefined();
expect(diagnostic.span.end - diagnostic.span.start).toBeLessThan(11);
});
});
function addCode(code: string, cb: (fileName: string, content?: string) => void) { function addCode(code: string, cb: (fileName: string, content?: string) => void) {
const fileName = '/app/app.component.ts'; const fileName = '/app/app.component.ts';
const originalContent = mockHost.getFileContent(fileName); const originalContent = mockHost.getFileContent(fileName);