test(compiler): add expression absolute span tests for `TemplateAst`s (#33253)

Previously, we had tested that expressions parsed in a Render3 AST
had correctly-defined absolute spans (spans relative to the entire
template, not the local expression). Sometimes we use Template ASTs
rather than Render3 ASTs, and it's desirable to test for correct
expression spans in the template parser as well.

Adding these tests resolved one bug, similar to the one fixed in
fd4fed14d8, where expressions in the value
of a template attribute were not given an absolute span corresponding to
the start of the attribute name rather than the start of the attribute
value.

The diff on this commit is large, partially because it involves some
structural changes of the template parser testing layout. In particular,
the following is done:

1. Move `createMeta*`-like functions from `template_parser_spec.ts` to
   be exported from a new test utility file.
2. Create an `ExpressionSourceHumanizer`, similar to the one created in
   b04488d692, to allow convenient testing
   of expressions' locations.
3. Create `template_parser_absolute_span_spec.ts`, testing the spans of
   expressions parsed by the template parser. This is very similar to
   the `r3_ast_absolute_span_spec`.

PR Close #33253
This commit is contained in:
ayazhafiz 2019-10-18 09:51:20 -05:00 committed by Andrew Kushnir
parent 2805af900f
commit 3d11355fec
5 changed files with 589 additions and 92 deletions

View File

@ -305,9 +305,10 @@ class TemplateParseVisitor implements html.Visitor {
}
hasInlineTemplates = true;
const parsedVariables: ParsedVariable[] = [];
const absoluteOffset = (attr.valueSpan || attr.sourceSpan).start.offset;
this._bindingParser.parseInlineTemplateBinding(
templateKey !, templateValue !, attr.sourceSpan, attr.sourceSpan.start.offset,
templateMatchableAttrs, templateElementOrDirectiveProps, parsedVariables);
templateKey !, templateValue !, attr.sourceSpan, absoluteOffset, templateMatchableAttrs,
templateElementOrDirectiveProps, parsedVariables);
templateElementVars.push(...parsedVariables.map(v => t.VariableAst.fromParsedVariable(v)));
}

View File

@ -0,0 +1,365 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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
*/
import {AbsoluteSourceSpan, CompileDirectiveSummary, CompilePipeMetadata, CompilePipeSummary, SchemaMetadata} from '@angular/compiler';
import {TemplateAst} from '@angular/compiler/src/template_parser/template_ast';
import {TemplateParser} from '@angular/compiler/src/template_parser/template_parser';
import {inject} from '@angular/core/testing';
import {humanizeExpressionSource} from './util/expression';
import {compileDirectiveMetadataCreate, compileTemplateMetadata, createTypeMeta} from './util/metadata';
describe('expression AST absolute source spans', () => {
const fakeTemplate = compileTemplateMetadata({animations: []});
const fakeComponent = compileDirectiveMetadataCreate({
isHost: false,
selector: 'app-fake',
template: fakeTemplate,
type: createTypeMeta({reference: {filePath: 'fake-path', name: 'FakeComponent'}}),
isComponent: true
});
const ngIf = compileDirectiveMetadataCreate({
selector: '[ngIf]',
template: fakeTemplate,
type: createTypeMeta({reference: {filePath: 'fake-path', name: 'NgIf'}}),
inputs: ['ngIf']
}).toSummary();
let parse: (
template: string, directives?: CompileDirectiveSummary[], pipes?: CompilePipeSummary[],
schemas?: SchemaMetadata[], preserveWhitespaces?: boolean) => TemplateAst[];
beforeEach(inject([TemplateParser], (parser: TemplateParser) => {
parse =
(template: string, directives: CompileDirectiveSummary[] = [],
pipes: CompilePipeSummary[] | null = null, schemas: SchemaMetadata[] = [],
preserveWhitespaces = true): TemplateAst[] => {
if (pipes === null) {
pipes = [];
}
return parser
.parse(
fakeComponent, template, directives, pipes, schemas, 'TestComponent',
preserveWhitespaces)
.template;
};
}));
it('should provide absolute offsets of an expression in a bound text', () => {
expect(humanizeExpressionSource(parse('<div>{{foo}}</div>'))).toContain([
'{{ foo }}', new AbsoluteSourceSpan(5, 12)
]);
});
it('should provide absolute offsets of an expression in a bound event', () => {
expect(humanizeExpressionSource(parse('<div (click)="foo();bar();"></div>'))).toContain([
'foo(); bar();', new AbsoluteSourceSpan(14, 26)
]);
expect(humanizeExpressionSource(parse('<div on-click="foo();bar();"></div>'))).toContain([
'foo(); bar();', new AbsoluteSourceSpan(15, 27)
]);
});
it('should provide absolute offsets of an expression in a bound attribute', () => {
expect(humanizeExpressionSource(parse('<input [disabled]="condition ? true : false" />')))
.toContain(['condition ? true : false', new AbsoluteSourceSpan(19, 43)]);
expect(humanizeExpressionSource(parse('<input bind-disabled="condition ? true : false" />')))
.toContain(['condition ? true : false', new AbsoluteSourceSpan(22, 46)]);
});
it('should provide absolute offsets of an expression in a template attribute', () => {
const ngTemplate =
compileDirectiveMetadataCreate({
selector: 'ng-template',
type: createTypeMeta({reference: {filePath: 'fake-path', name: 'OnTemplate'}})
}).toSummary();
expect(humanizeExpressionSource(parse('<div *ngIf="value"></div>', [ngIf, ngTemplate])))
.toContain(['value', new AbsoluteSourceSpan(12, 17)]);
});
describe('binary expression', () => {
it('should provide absolute offsets of a binary expression', () => {
expect(humanizeExpressionSource(parse('<div>{{1 + 2}}<div>'))).toContain([
'1 + 2', new AbsoluteSourceSpan(7, 12)
]);
});
it('should provide absolute offsets of expressions in a binary expression', () => {
expect(humanizeExpressionSource(parse('<div>{{1 + 2}}<div>')))
.toEqual(jasmine.arrayContaining([
// TODO(ayazhafiz): The expression parser includes an extra whitespace on a expressions
// with trailing whitespace in a binary expression. Look into fixing this.
['1', new AbsoluteSourceSpan(7, 9)],
['2', new AbsoluteSourceSpan(11, 12)],
]));
});
});
describe('conditional', () => {
it('should provide absolute offsets of a conditional', () => {
expect(humanizeExpressionSource(parse('<div>{{bool ? 1 : 0}}<div>'))).toContain([
'bool ? 1 : 0', new AbsoluteSourceSpan(7, 19)
]);
});
it('should provide absolute offsets of expressions in a conditional', () => {
expect(humanizeExpressionSource(parse('<div>{{bool ? 1 : 0}}<div>')))
.toEqual(jasmine.arrayContaining([
// TODO(ayazhafiz): The expression parser includes an extra whitespace on a expressions
// with trailing whitespace in a conditional expression. Look into fixing this.
['bool', new AbsoluteSourceSpan(7, 12)],
['1', new AbsoluteSourceSpan(14, 16)],
['0', new AbsoluteSourceSpan(18, 19)],
]));
});
});
describe('chain', () => {
it('should provide absolute offsets of a chain', () => {
expect(humanizeExpressionSource(parse('<div (click)="a(); b();"><div>'))).toContain([
'a(); b();', new AbsoluteSourceSpan(14, 23)
]);
});
it('should provide absolute offsets of expressions in a chain', () => {
expect(humanizeExpressionSource(parse('<div (click)="a(); b();"><div>')))
.toEqual(jasmine.arrayContaining([
['a()', new AbsoluteSourceSpan(14, 17)],
['b()', new AbsoluteSourceSpan(19, 22)],
]));
});
});
describe('function call', () => {
it('should provide absolute offsets of a function call', () => {
expect(humanizeExpressionSource(parse('<div>{{fn()()}}<div>'))).toContain([
'fn()()', new AbsoluteSourceSpan(7, 13)
]);
});
it('should provide absolute offsets of expressions in a function call', () => {
expect(humanizeExpressionSource(parse('<div>{{fn()(param)}}<div>'))).toContain([
'param', new AbsoluteSourceSpan(12, 17)
]);
});
});
it('should provide absolute offsets of an implicit receiver', () => {
expect(humanizeExpressionSource(parse('<div>{{a.b}}<div>'))).toContain([
'', new AbsoluteSourceSpan(7, 7)
]);
});
describe('interpolation', () => {
it('should provide absolute offsets of an interpolation', () => {
expect(humanizeExpressionSource(parse('<div>{{1 + foo.length}}<div>'))).toContain([
'{{ 1 + foo.length }}', new AbsoluteSourceSpan(5, 23)
]);
});
it('should provide absolute offsets of expressions in an interpolation', () => {
expect(humanizeExpressionSource(parse('<div>{{1 + 2}}<div>')))
.toEqual(jasmine.arrayContaining([
// TODO(ayazhafiz): The expression parser includes an extra whitespace on a expressions
// with trailing whitespace in a conditional expression. Look into fixing this.
['1', new AbsoluteSourceSpan(7, 9)],
['2', new AbsoluteSourceSpan(11, 12)],
]));
});
});
describe('keyed read', () => {
it('should provide absolute offsets of a keyed read', () => {
expect(humanizeExpressionSource(parse('<div>{{obj[key]}}<div>'))).toContain([
'obj[key]', new AbsoluteSourceSpan(7, 15)
]);
});
it('should provide absolute offsets of expressions in a keyed read', () => {
expect(humanizeExpressionSource(parse('<div>{{obj[key]}}<div>'))).toContain([
'key', new AbsoluteSourceSpan(11, 14)
]);
});
});
describe('keyed write', () => {
it('should provide absolute offsets of a keyed write', () => {
expect(humanizeExpressionSource(parse('<div>{{obj[key] = 0}}<div>'))).toContain([
'obj[key] = 0', new AbsoluteSourceSpan(7, 19)
]);
});
it('should provide absolute offsets of expressions in a keyed write', () => {
expect(humanizeExpressionSource(parse('<div>{{obj[key] = 0}}<div>')))
.toEqual(jasmine.arrayContaining([
['key', new AbsoluteSourceSpan(11, 14)],
['0', new AbsoluteSourceSpan(18, 19)],
]));
});
});
it('should provide absolute offsets of a literal primitive', () => {
expect(humanizeExpressionSource(parse('<div>{{100}}<div>'))).toContain([
'100', new AbsoluteSourceSpan(7, 10)
]);
});
describe('literal array', () => {
it('should provide absolute offsets of a literal array', () => {
expect(humanizeExpressionSource(parse('<div>{{[0, 1, 2]}}<div>'))).toContain([
'[0, 1, 2]', new AbsoluteSourceSpan(7, 16)
]);
});
it('should provide absolute offsets of expressions in a literal array', () => {
expect(humanizeExpressionSource(parse('<div>{{[0, 1, 2]}}<div>')))
.toEqual(jasmine.arrayContaining([
['0', new AbsoluteSourceSpan(8, 9)],
['1', new AbsoluteSourceSpan(11, 12)],
['2', new AbsoluteSourceSpan(14, 15)],
]));
});
});
describe('literal map', () => {
it('should provide absolute offsets of a literal map', () => {
expect(humanizeExpressionSource(parse('<div>{{ {a: 0} }}<div>'))).toContain([
// TODO(ayazhafiz): The expression parser includes an extra whitespace on a expressions
// with trailing whitespace in a literal map. Look into fixing this.
'{a: 0}', new AbsoluteSourceSpan(8, 15)
]);
});
it('should provide absolute offsets of expressions in a literal map', () => {
expect(humanizeExpressionSource(parse('<div>{{ {a: 0} }}<div>')))
.toEqual(jasmine.arrayContaining([
['0', new AbsoluteSourceSpan(12, 13)],
]));
});
});
describe('method call', () => {
it('should provide absolute offsets of a method call', () => {
expect(humanizeExpressionSource(parse('<div>{{method()}}</div>'))).toContain([
'method()', new AbsoluteSourceSpan(7, 15)
]);
});
it('should provide absolute offsets of expressions in a method call', () => {
expect(humanizeExpressionSource(parse('<div>{{method(param)}}<div>'))).toContain([
'param', new AbsoluteSourceSpan(14, 19)
]);
});
});
describe('non-null assert', () => {
it('should provide absolute offsets of a non-null assert', () => {
expect(humanizeExpressionSource(parse('<div>{{prop!}}</div>'))).toContain([
'prop!', new AbsoluteSourceSpan(7, 12)
]);
});
it('should provide absolute offsets of expressions in a non-null assert', () => {
expect(humanizeExpressionSource(parse('<div>{{prop!}}<div>'))).toContain([
'prop', new AbsoluteSourceSpan(7, 11)
]);
});
});
describe('pipe', () => {
const testPipe = new CompilePipeMetadata({
name: 'test',
type: createTypeMeta({reference: {filePath: 'fake-path', name: 'TestPipe'}}),
pure: false
}).toSummary();
it('should provide absolute offsets of a pipe', () => {
expect(humanizeExpressionSource(parse('<div>{{prop | test}}<div>', [], [testPipe])))
.toContain(['(prop | test)', new AbsoluteSourceSpan(7, 18)]);
});
it('should provide absolute offsets expressions in a pipe', () => {
expect(humanizeExpressionSource(parse('<div>{{prop | test}}<div>', [], [testPipe])))
.toContain([
// TODO(ayazhafiz): The expression parser includes an extra whitespace on a expressions
// with trailing whitespace in a pipe. Look into fixing this.
'prop', new AbsoluteSourceSpan(7, 12)
]);
});
});
it('should provide absolute offsets of a property read', () => {
expect(humanizeExpressionSource(parse('<div>{{prop}}</div>'))).toContain([
'prop', new AbsoluteSourceSpan(7, 11)
]);
});
describe('property write', () => {
it('should provide absolute offsets of a property write', () => {
expect(humanizeExpressionSource(parse('<div (click)="prop = 0"></div>'))).toContain([
'prop = 0', new AbsoluteSourceSpan(14, 22)
]);
});
it('should provide absolute offsets of expressions in a property write', () => {
expect(humanizeExpressionSource(parse('<div (click)="prop = 0"></div>'))).toContain([
'0', new AbsoluteSourceSpan(21, 22)
]);
});
});
describe('"not" prefix', () => {
it('should provide absolute offsets of a "not" prefix', () => {
expect(humanizeExpressionSource(parse('<div>{{!prop}}</div>'))).toContain([
'!prop', new AbsoluteSourceSpan(7, 12)
]);
});
it('should provide absolute offsets of expressions in a "not" prefix', () => {
expect(humanizeExpressionSource(parse('<div>{{!prop}}<div>'))).toContain([
'prop', new AbsoluteSourceSpan(8, 12)
]);
});
});
describe('safe method call', () => {
it('should provide absolute offsets of a safe method call', () => {
expect(humanizeExpressionSource(parse('<div>{{prop?.safe()}}<div>'))).toContain([
'prop?.safe()', new AbsoluteSourceSpan(7, 19)
]);
});
it('should provide absolute offsets of expressions in safe method call', () => {
expect(humanizeExpressionSource(parse('<div>{{prop?.safe()}}<div>'))).toContain([
'prop', new AbsoluteSourceSpan(7, 11)
]);
});
});
describe('safe property read', () => {
it('should provide absolute offsets of a safe property read', () => {
expect(humanizeExpressionSource(parse('<div>{{prop?.safe}}<div>'))).toContain([
'prop?.safe', new AbsoluteSourceSpan(7, 17)
]);
});
it('should provide absolute offsets of expressions in safe property read', () => {
expect(humanizeExpressionSource(parse('<div>{{prop?.safe}}<div>'))).toContain([
'prop', new AbsoluteSourceSpan(7, 11)
]);
});
});
it('should provide absolute offsets of a quote', () => {
expect(humanizeExpressionSource(parse('<div [class.some-class]="a:b"></div>'))).toContain([
'a:b', new AbsoluteSourceSpan(25, 28)
]);
});
});

View File

@ -5,24 +5,24 @@
* 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
*/
import {CompileQueryMetadata, CompilerConfig, ProxyClass, StaticSymbol, preserveWhitespacesDefault} from '@angular/compiler';
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileTemplateMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenReference} from '@angular/compiler/src/compile_metadata';
import {preserveWhitespacesDefault} from '@angular/compiler';
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileTemplateMetadata, CompileTokenMetadata, tokenReference} from '@angular/compiler/src/compile_metadata';
import {DomElementSchemaRegistry} from '@angular/compiler/src/schema/dom_element_schema_registry';
import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAstType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '@angular/compiler/src/template_parser/template_ast';
import {TemplateParser, splitClasses} from '@angular/compiler/src/template_parser/template_parser';
import {ChangeDetectionStrategy, ComponentFactory, RendererType2, SchemaMetadata, SecurityContext, ViewEncapsulation} from '@angular/core';
import {SchemaMetadata, SecurityContext} from '@angular/core';
import {Console} from '@angular/core/src/console';
import {TestBed, inject} from '@angular/core/testing';
import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector';
import {CompileEntryComponentMetadata, CompileStylesheetMetadata} from '../../src/compile_metadata';
import {Identifiers, createTokenForExternalReference, createTokenForReference} from '../../src/identifiers';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../src/ml_parser/interpolation_config';
import {newArray, noUndefined} from '../../src/util';
import {newArray} from '../../src/util';
import {MockSchemaRegistry} from '../../testing';
import {unparse} from '../expression_parser/utils/unparser';
import {TEST_COMPILER_PROVIDERS} from '../test_bindings';
import {compileDirectiveMetadataCreate, compileTemplateMetadata, createTypeMeta} from './util/metadata';
const someModuleUrl = 'package:someModule';
@ -33,88 +33,6 @@ const MOCK_SCHEMA_REGISTRY = [{
['onEvent'], ['onEvent']),
}];
function createTypeMeta({reference, diDeps}: {reference: any, diDeps?: any[]}):
CompileTypeMetadata {
return {reference: reference, diDeps: diDeps || [], lifecycleHooks: []};
}
function compileDirectiveMetadataCreate(
{isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host,
providers, viewProviders, queries, guards, viewQueries, entryComponents, template,
componentViewType, rendererType}: {
isHost?: boolean,
type?: CompileTypeMetadata,
isComponent?: boolean,
selector?: string | null,
exportAs?: string | null,
changeDetection?: ChangeDetectionStrategy | null,
inputs?: string[],
outputs?: string[],
host?: {[key: string]: string},
providers?: CompileProviderMetadata[] | null,
viewProviders?: CompileProviderMetadata[] | null,
queries?: CompileQueryMetadata[] | null,
guards?: {[key: string]: any},
viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileEntryComponentMetadata[],
template?: CompileTemplateMetadata,
componentViewType?: StaticSymbol | ProxyClass | null,
rendererType?: StaticSymbol | RendererType2 | null,
}) {
return CompileDirectiveMetadata.create({
isHost: !!isHost,
type: noUndefined(type) !,
isComponent: !!isComponent,
selector: noUndefined(selector),
exportAs: noUndefined(exportAs),
changeDetection: null,
inputs: inputs || [],
outputs: outputs || [],
host: host || {},
providers: providers || [],
viewProviders: viewProviders || [],
queries: queries || [],
guards: guards || {},
viewQueries: viewQueries || [],
entryComponents: entryComponents || [],
template: noUndefined(template) !,
componentViewType: noUndefined(componentViewType),
rendererType: noUndefined(rendererType),
componentFactory: null,
});
}
function compileTemplateMetadata({encapsulation, template, templateUrl, styles, styleUrls,
externalStylesheets, animations, ngContentSelectors,
interpolation, isInline, preserveWhitespaces}: {
encapsulation?: ViewEncapsulation | null,
template?: string | null,
templateUrl?: string | null,
styles?: string[],
styleUrls?: string[],
externalStylesheets?: CompileStylesheetMetadata[],
ngContentSelectors?: string[],
animations?: any[],
interpolation?: [string, string] | null,
isInline?: boolean,
preserveWhitespaces?: boolean | null,
}): CompileTemplateMetadata {
return new CompileTemplateMetadata({
encapsulation: noUndefined(encapsulation),
template: noUndefined(template),
templateUrl: noUndefined(templateUrl),
htmlAst: null,
styles: styles || [],
styleUrls: styleUrls || [],
externalStylesheets: externalStylesheets || [],
animations: animations || [],
ngContentSelectors: ngContentSelectors || [],
interpolation: noUndefined(interpolation),
isInline: !!isInline,
preserveWhitespaces: preserveWhitespacesDefault(noUndefined(preserveWhitespaces)),
});
}
function humanizeTplAst(
templateAsts: TemplateAst[], interpolationConfig?: InterpolationConfig): any[] {
@ -1136,9 +1054,10 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
}
function createDir(
selector: string, {providers = null, viewProviders = null, deps = [], queries = []}: {
providers?: CompileProviderMetadata[] | null,
viewProviders?: CompileProviderMetadata[] | null,
selector: string,
{providers = undefined, viewProviders = undefined, deps = [], queries = []}: {
providers?: CompileProviderMetadata[] | undefined,
viewProviders?: CompileProviderMetadata[] | undefined,
deps?: string[],
queries?: string[]
} = {}): CompileDirectiveSummary {

View File

@ -0,0 +1,150 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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
*/
import {AbsoluteSourceSpan} from '@angular/compiler';
import * as e from '../../../src/expression_parser/ast';
import * as t from '../../../src/template_parser/template_ast';
import {unparse} from '../../expression_parser/utils/unparser';
type HumanizedExpressionSource = [string, AbsoluteSourceSpan];
class ExpressionSourceHumanizer extends e.RecursiveAstVisitor implements t.TemplateAstVisitor {
result: HumanizedExpressionSource[] = [];
private recordAst(ast: e.AST) { this.result.push([unparse(ast), ast.sourceSpan]); }
visitASTWithSource(ast: e.ASTWithSource) {
this.recordAst(ast);
this.visitAll([ast.ast], null);
}
visitBinary(ast: e.Binary) {
this.recordAst(ast);
super.visitBinary(ast, null);
}
visitChain(ast: e.Chain) {
this.recordAst(ast);
super.visitChain(ast, null);
}
visitConditional(ast: e.Conditional) {
this.recordAst(ast);
super.visitConditional(ast, null);
}
visitFunctionCall(ast: e.FunctionCall) {
this.recordAst(ast);
super.visitFunctionCall(ast, null);
}
visitImplicitReceiver(ast: e.ImplicitReceiver) {
this.recordAst(ast);
super.visitImplicitReceiver(ast, null);
}
visitInterpolation(ast: e.Interpolation) {
this.recordAst(ast);
super.visitInterpolation(ast, null);
}
visitKeyedRead(ast: e.KeyedRead) {
this.recordAst(ast);
super.visitKeyedRead(ast, null);
}
visitKeyedWrite(ast: e.KeyedWrite) {
this.recordAst(ast);
super.visitKeyedWrite(ast, null);
}
visitLiteralPrimitive(ast: e.LiteralPrimitive) {
this.recordAst(ast);
super.visitLiteralPrimitive(ast, null);
}
visitLiteralArray(ast: e.LiteralArray) {
this.recordAst(ast);
super.visitLiteralArray(ast, null);
}
visitLiteralMap(ast: e.LiteralMap) {
this.recordAst(ast);
super.visitLiteralMap(ast, null);
}
visitMethodCall(ast: e.MethodCall) {
this.recordAst(ast);
super.visitMethodCall(ast, null);
}
visitNonNullAssert(ast: e.NonNullAssert) {
this.recordAst(ast);
super.visitNonNullAssert(ast, null);
}
visitPipe(ast: e.BindingPipe) {
this.recordAst(ast);
super.visitPipe(ast, null);
}
visitPrefixNot(ast: e.PrefixNot) {
this.recordAst(ast);
super.visitPrefixNot(ast, null);
}
visitPropertyRead(ast: e.PropertyRead) {
this.recordAst(ast);
super.visitPropertyRead(ast, null);
}
visitPropertyWrite(ast: e.PropertyWrite) {
this.recordAst(ast);
super.visitPropertyWrite(ast, null);
}
visitSafeMethodCall(ast: e.SafeMethodCall) {
this.recordAst(ast);
super.visitSafeMethodCall(ast, null);
}
visitSafePropertyRead(ast: e.SafePropertyRead) {
this.recordAst(ast);
super.visitSafePropertyRead(ast, null);
}
visitQuote(ast: e.Quote) {
this.recordAst(ast);
super.visitQuote(ast, null);
}
visitNgContent(ast: t.NgContentAst) {}
visitEmbeddedTemplate(ast: t.EmbeddedTemplateAst) {
t.templateVisitAll(this, ast.attrs);
t.templateVisitAll(this, ast.children);
t.templateVisitAll(this, ast.directives);
t.templateVisitAll(this, ast.outputs);
t.templateVisitAll(this, ast.providers);
t.templateVisitAll(this, ast.references);
t.templateVisitAll(this, ast.variables);
}
visitElement(ast: t.ElementAst) {
t.templateVisitAll(this, ast.attrs);
t.templateVisitAll(this, ast.children);
t.templateVisitAll(this, ast.directives);
t.templateVisitAll(this, ast.inputs);
t.templateVisitAll(this, ast.outputs);
t.templateVisitAll(this, ast.providers);
t.templateVisitAll(this, ast.references);
}
visitReference(ast: t.ReferenceAst) {}
visitVariable(ast: t.VariableAst) {}
visitEvent(ast: t.BoundEventAst) { ast.handler.visit(this); }
visitElementProperty(ast: t.BoundElementPropertyAst) { ast.value.visit(this); }
visitAttr(ast: t.AttrAst) {}
visitBoundText(ast: t.BoundTextAst) { ast.value.visit(this); }
visitText(ast: t.TextAst) {}
visitDirective(ast: t.DirectiveAst) {
t.templateVisitAll(this, ast.hostEvents);
t.templateVisitAll(this, ast.hostProperties);
t.templateVisitAll(this, ast.inputs);
}
visitDirectiveProperty(ast: t.BoundDirectivePropertyAst) { ast.value.visit(this); }
}
/**
* Humanizes expression AST source spans in a template by returning an array of tuples
* [unparsed AST, AST source span]
* for each expression in the template.
* @param templateAsts template AST to humanize
*/
export function humanizeExpressionSource(templateAsts: t.TemplateAst[]):
HumanizedExpressionSource[] {
const humanizer = new ExpressionSourceHumanizer();
t.templateVisitAll(humanizer, templateAsts);
return humanizer.result;
}

View File

@ -0,0 +1,62 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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
*/
import {CompileDirectiveMetadata, CompileEntryComponentMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata, ProxyClass, StaticSymbol, preserveWhitespacesDefault} from '@angular/compiler';
import {ChangeDetectionStrategy, RendererType2, ViewEncapsulation} from '@angular/core';
import {noUndefined} from '../../../src/util';
export function createTypeMeta({reference, diDeps}: {reference: any, diDeps?: any[]}):
CompileTypeMetadata {
return {reference: reference, diDeps: diDeps || [], lifecycleHooks: []};
}
export function compileDirectiveMetadataCreate(
{isHost, type, isComponent, selector, exportAs, inputs, outputs, host, providers, viewProviders,
queries, guards, viewQueries, entryComponents, template, componentViewType,
rendererType}: Partial<Parameters<typeof CompileDirectiveMetadata.create>[0]>) {
return CompileDirectiveMetadata.create({
isHost: !!isHost,
type: noUndefined(type) !,
isComponent: !!isComponent,
selector: noUndefined(selector),
exportAs: noUndefined(exportAs),
changeDetection: null,
inputs: inputs || [],
outputs: outputs || [],
host: host || {},
providers: providers || [],
viewProviders: viewProviders || [],
queries: queries || [],
guards: guards || {},
viewQueries: viewQueries || [],
entryComponents: entryComponents || [],
template: noUndefined(template) !,
componentViewType: noUndefined(componentViewType),
rendererType: noUndefined(rendererType),
componentFactory: null,
});
}
export function compileTemplateMetadata(
{encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations,
ngContentSelectors, interpolation, isInline,
preserveWhitespaces}: Partial<CompileTemplateMetadata>): CompileTemplateMetadata {
return new CompileTemplateMetadata({
encapsulation: noUndefined(encapsulation),
template: noUndefined(template),
templateUrl: noUndefined(templateUrl),
htmlAst: null,
styles: styles || [],
styleUrls: styleUrls || [],
externalStylesheets: externalStylesheets || [],
animations: animations || [],
ngContentSelectors: ngContentSelectors || [],
interpolation: noUndefined(interpolation),
isInline: !!isInline,
preserveWhitespaces: preserveWhitespacesDefault(noUndefined(preserveWhitespaces)),
});
}