fix(compiler): give ASTWithSource its own visit method (#31347)

ASTWithSource contains more information that AST and should have its own
visit method, if desired. This implements that.

PR Close #31347
This commit is contained in:
Ayaz Hafiz 2019-06-28 15:59:21 -07:00 committed by Jason Aden
parent 50c4ec6687
commit 6aaca21c27
2 changed files with 96 additions and 87 deletions

View File

@ -7,6 +7,7 @@
*/ */
import {AbsoluteSourceSpan, IdentifierKind} from '..'; import {AbsoluteSourceSpan, IdentifierKind} from '..';
import {runInEachFileSystem} from '../../file_system/testing';
import {getTemplateIdentifiers} from '../src/template'; import {getTemplateIdentifiers} from '../src/template';
import * as util from './util'; import * as util from './util';
@ -17,112 +18,114 @@ function bind(template: string) {
}); });
} }
describe('getTemplateIdentifiers', () => { runInEachFileSystem(() => {
it('should generate nothing in HTML-only template', () => { describe('getTemplateIdentifiers', () => {
const refs = getTemplateIdentifiers(bind('<div></div>')); it('should generate nothing in HTML-only template', () => {
const refs = getTemplateIdentifiers(bind('<div></div>'));
expect(refs.size).toBe(0); expect(refs.size).toBe(0);
});
it('should ignore comments', () => {
const refs = getTemplateIdentifiers(bind('<!-- {{comment}} -->'));
expect(refs.size).toBe(0);
});
it('should handle arbitrary whitespace', () => {
const template = '<div>\n\n {{foo}}</div>';
const refs = getTemplateIdentifiers(bind(template));
const [ref] = Array.from(refs);
expect(ref).toEqual({
name: 'foo',
kind: IdentifierKind.Property,
span: new AbsoluteSourceSpan(12, 15),
}); });
});
it('should ignore identifiers defined in the template', () => { it('should ignore comments', () => {
const template = ` const refs = getTemplateIdentifiers(bind('<!-- {{comment}} -->'));
expect(refs.size).toBe(0);
});
it('should handle arbitrary whitespace', () => {
const template = '<div>\n\n {{foo}}</div>';
const refs = getTemplateIdentifiers(bind(template));
const [ref] = Array.from(refs);
expect(ref).toEqual({
name: 'foo',
kind: IdentifierKind.Property,
span: new AbsoluteSourceSpan(12, 15),
});
});
it('should ignore identifiers defined in the template', () => {
const template = `
<input #model /> <input #model />
{{model.valid}} {{model.valid}}
`; `;
const refs = getTemplateIdentifiers(bind(template));
const refArr = Array.from(refs);
const modelId = refArr.find(ref => ref.name === 'model');
expect(modelId).toBeUndefined();
});
describe('generates identifiers for PropertyReads', () => {
it('should discover component properties', () => {
const template = '{{foo}}';
const refs = getTemplateIdentifiers(bind(template));
expect(refs.size).toBe(1);
const [ref] = Array.from(refs);
expect(ref).toEqual({
name: 'foo',
kind: IdentifierKind.Property,
span: new AbsoluteSourceSpan(2, 5),
});
});
it('should discover nested properties', () => {
const template = '<div><span>{{foo}}</span></div>';
const refs = getTemplateIdentifiers(bind(template)); const refs = getTemplateIdentifiers(bind(template));
const refArr = Array.from(refs); const refArr = Array.from(refs);
expect(refArr).toEqual(jasmine.arrayContaining([{ const modelId = refArr.find(ref => ref.name === 'model');
name: 'foo', expect(modelId).toBeUndefined();
kind: IdentifierKind.Property,
span: new AbsoluteSourceSpan(13, 16),
}]));
}); });
it('should ignore identifiers that are not implicitly received by the template', () => { describe('generates identifiers for PropertyReads', () => {
const template = '{{foo.bar.baz}}'; it('should discover component properties', () => {
const refs = getTemplateIdentifiers(bind(template)); const template = '{{foo}}';
expect(refs.size).toBe(1); const refs = getTemplateIdentifiers(bind(template));
expect(refs.size).toBe(1);
const [ref] = Array.from(refs); const [ref] = Array.from(refs);
expect(ref.name).toBe('foo'); expect(ref).toEqual({
}); name: 'foo',
}); kind: IdentifierKind.Property,
span: new AbsoluteSourceSpan(2, 5),
});
});
describe('generates identifiers for MethodCalls', () => { it('should discover nested properties', () => {
it('should discover component method calls', () => { const template = '<div><span>{{foo}}</span></div>';
const template = '{{foo()}}'; const refs = getTemplateIdentifiers(bind(template));
const refs = getTemplateIdentifiers(bind(template));
expect(refs.size).toBe(1);
const [ref] = Array.from(refs); const refArr = Array.from(refs);
expect(ref).toEqual({ expect(refArr).toEqual(jasmine.arrayContaining([{
name: 'foo', name: 'foo',
kind: IdentifierKind.Method, kind: IdentifierKind.Property,
span: new AbsoluteSourceSpan(2, 5), span: new AbsoluteSourceSpan(13, 16),
}]));
});
it('should ignore identifiers that are not implicitly received by the template', () => {
const template = '{{foo.bar.baz}}';
const refs = getTemplateIdentifiers(bind(template));
expect(refs.size).toBe(1);
const [ref] = Array.from(refs);
expect(ref.name).toBe('foo');
}); });
}); });
it('should discover nested properties', () => { describe('generates identifiers for MethodCalls', () => {
const template = '<div><span>{{foo()}}</span></div>'; it('should discover component method calls', () => {
const refs = getTemplateIdentifiers(bind(template)); const template = '{{foo()}}';
const refs = getTemplateIdentifiers(bind(template));
expect(refs.size).toBe(1);
const refArr = Array.from(refs); const [ref] = Array.from(refs);
expect(refArr).toEqual(jasmine.arrayContaining([{ expect(ref).toEqual({
name: 'foo', name: 'foo',
kind: IdentifierKind.Method, kind: IdentifierKind.Method,
span: new AbsoluteSourceSpan(13, 16), span: new AbsoluteSourceSpan(2, 5),
}])); });
}); });
it('should ignore identifiers that are not implicitly received by the template', () => { it('should discover nested properties', () => {
const template = '{{foo().bar().baz()}}'; const template = '<div><span>{{foo()}}</span></div>';
const refs = getTemplateIdentifiers(bind(template)); const refs = getTemplateIdentifiers(bind(template));
expect(refs.size).toBe(1);
const [ref] = Array.from(refs); const refArr = Array.from(refs);
expect(ref.name).toBe('foo'); expect(refArr).toEqual(jasmine.arrayContaining([{
name: 'foo',
kind: IdentifierKind.Method,
span: new AbsoluteSourceSpan(13, 16),
}]));
});
it('should ignore identifiers that are not implicitly received by the template', () => {
const template = '{{foo().bar().baz()}}';
const refs = getTemplateIdentifiers(bind(template));
expect(refs.size).toBe(1);
const [ref] = Array.from(refs);
expect(ref.name).toBe('foo');
});
}); });
}); });
}); });

View File

@ -209,7 +209,12 @@ export class ASTWithSource extends AST {
public errors: ParserError[]) { public errors: ParserError[]) {
super(new ParseSpan(0, source == null ? 0 : source.length)); super(new ParseSpan(0, source == null ? 0 : source.length));
} }
visit(visitor: AstVisitor, context: any = null): any { return this.ast.visit(visitor, context); } visit(visitor: AstVisitor, context: any = null): any {
if (visitor.visitASTWithSource) {
return visitor.visitASTWithSource(this, context);
}
return this.ast.visit(visitor, context);
}
toString(): string { return `${this.source} in ${this.location}`; } toString(): string { return `${this.source} in ${this.location}`; }
} }
@ -240,6 +245,7 @@ export interface AstVisitor {
visitQuote(ast: Quote, context: any): any; visitQuote(ast: Quote, context: any): any;
visitSafeMethodCall(ast: SafeMethodCall, context: any): any; visitSafeMethodCall(ast: SafeMethodCall, context: any): any;
visitSafePropertyRead(ast: SafePropertyRead, context: any): any; visitSafePropertyRead(ast: SafePropertyRead, context: any): any;
visitASTWithSource?(ast: ASTWithSource, context: any): any;
visit?(ast: AST, context?: any): any; visit?(ast: AST, context?: any): any;
} }