Skip to content

Commit

Permalink
fix(compiler): give ASTWithSource its own visit method (#31347)
Browse files Browse the repository at this point in the history
ASTWithSource contains more information that AST and should have its own
visit method, if desired. This implements that.

PR Close #31347
  • Loading branch information
ayazhafiz authored and jasonaden committed Jul 8, 2019
1 parent 50c4ec6 commit 6aaca21
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 79 deletions.
159 changes: 81 additions & 78 deletions packages/compiler-cli/src/ngtsc/indexer/test/template_spec.ts
Expand Up @@ -7,6 +7,7 @@
*/

import {AbsoluteSourceSpan, IdentifierKind} from '..';
import {runInEachFileSystem} from '../../file_system/testing';
import {getTemplateIdentifiers} from '../src/template';
import * as util from './util';

Expand All @@ -17,112 +18,114 @@ function bind(template: string) {
});
}

describe('getTemplateIdentifiers', () => {
it('should generate nothing in HTML-only template', () => {
const refs = getTemplateIdentifiers(bind('<div></div>'));
runInEachFileSystem(() => {
describe('getTemplateIdentifiers', () => {
it('should generate nothing in HTML-only template', () => {
const refs = getTemplateIdentifiers(bind('<div></div>'));

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),
expect(refs.size).toBe(0);
});
});

it('should ignore identifiers defined in the template', () => {
const template = `
<input #model />
{{model.valid}}
`;
const refs = getTemplateIdentifiers(bind(template));
it('should ignore comments', () => {
const refs = getTemplateIdentifiers(bind('<!-- {{comment}} -->'));

const refArr = Array.from(refs);
const modelId = refArr.find(ref => ref.name === 'model');
expect(modelId).toBeUndefined();
});
expect(refs.size).toBe(0);
});

describe('generates identifiers for PropertyReads', () => {
it('should discover component properties', () => {
const template = '{{foo}}';
it('should handle arbitrary whitespace', () => {
const template = '<div>\n\n {{foo}}</div>';
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),
span: new AbsoluteSourceSpan(12, 15),
});
});

it('should discover nested properties', () => {
const template = '<div><span>{{foo}}</span></div>';
it('should ignore identifiers defined in the template', () => {
const template = `
<input #model />
{{model.valid}}
`;
const refs = getTemplateIdentifiers(bind(template));

const refArr = Array.from(refs);
expect(refArr).toEqual(jasmine.arrayContaining([{
name: 'foo',
kind: IdentifierKind.Property,
span: new AbsoluteSourceSpan(13, 16),
}]));
const modelId = refArr.find(ref => ref.name === 'model');
expect(modelId).toBeUndefined();
});

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);
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),
});
});

const [ref] = Array.from(refs);
expect(ref.name).toBe('foo');
});
});
it('should discover nested properties', () => {
const template = '<div><span>{{foo}}</span></div>';
const refs = getTemplateIdentifiers(bind(template));

describe('generates identifiers for MethodCalls', () => {
it('should discover component method calls', () => {
const template = '{{foo()}}';
const refs = getTemplateIdentifiers(bind(template));
expect(refs.size).toBe(1);
const refArr = Array.from(refs);
expect(refArr).toEqual(jasmine.arrayContaining([{
name: 'foo',
kind: IdentifierKind.Property,
span: new AbsoluteSourceSpan(13, 16),
}]));
});

const [ref] = Array.from(refs);
expect(ref).toEqual({
name: 'foo',
kind: IdentifierKind.Method,
span: new AbsoluteSourceSpan(2, 5),
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', () => {
const template = '<div><span>{{foo()}}</span></div>';
const refs = getTemplateIdentifiers(bind(template));
describe('generates identifiers for MethodCalls', () => {
it('should discover component method calls', () => {
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.Method,
span: new AbsoluteSourceSpan(2, 5),
});
});

const refArr = Array.from(refs);
expect(refArr).toEqual(jasmine.arrayContaining([{
name: 'foo',
kind: IdentifierKind.Method,
span: new AbsoluteSourceSpan(13, 16),
}]));
});
it('should discover nested properties', () => {
const template = '<div><span>{{foo()}}</span></div>';
const refs = getTemplateIdentifiers(bind(template));

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 refArr = Array.from(refs);
expect(refArr).toEqual(jasmine.arrayContaining([{
name: 'foo',
kind: IdentifierKind.Method,
span: new AbsoluteSourceSpan(13, 16),
}]));
});

const [ref] = Array.from(refs);
expect(ref.name).toBe('foo');
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');
});
});
});
});
8 changes: 7 additions & 1 deletion packages/compiler/src/expression_parser/ast.ts
Expand Up @@ -209,7 +209,12 @@ export class ASTWithSource extends AST {
public errors: ParserError[]) {
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}`; }
}

Expand Down Expand Up @@ -240,6 +245,7 @@ export interface AstVisitor {
visitQuote(ast: Quote, context: any): any;
visitSafeMethodCall(ast: SafeMethodCall, context: any): any;
visitSafePropertyRead(ast: SafePropertyRead, context: any): any;
visitASTWithSource?(ast: ASTWithSource, context: any): any;
visit?(ast: AST, context?: any): any;
}

Expand Down

0 comments on commit 6aaca21

Please sign in to comment.