test(ivy): add html to ivy ast transformer tests (#23546)
PR Close #23546
This commit is contained in:
parent
c5ca5c0d9f
commit
46674d5fac
|
@ -705,7 +705,7 @@ export class ParsedVariable {
|
||||||
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum BoundElementBindingType {
|
export const enum BindingType {
|
||||||
// A regular binding to a property (e.g. `[property]="expression"`).
|
// A regular binding to a property (e.g. `[property]="expression"`).
|
||||||
Property,
|
Property,
|
||||||
// A binding to an element attribute (e.g. `[attr.name]="expression"`).
|
// A binding to an element attribute (e.g. `[attr.name]="expression"`).
|
||||||
|
@ -720,7 +720,6 @@ export const enum BoundElementBindingType {
|
||||||
|
|
||||||
export class BoundElementProperty {
|
export class BoundElementProperty {
|
||||||
constructor(
|
constructor(
|
||||||
public name: string, public type: BoundElementBindingType,
|
public name: string, public type: BindingType, public securityContext: SecurityContext,
|
||||||
public securityContext: SecurityContext, public value: AST, public unit: string|null,
|
public value: AST, public unit: string|null, public sourceSpan: ParseSourceSpan) {}
|
||||||
public sourceSpan: ParseSourceSpan) {}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {SecurityContext} from '../core';
|
import {SecurityContext} from '../core';
|
||||||
import {AST, BoundElementBindingType, BoundElementProperty, ParsedEvent, ParsedEventType} from '../expression_parser/ast';
|
import {AST, BindingType, BoundElementProperty, ParsedEvent, ParsedEventType} from '../expression_parser/ast';
|
||||||
import {ParseSourceSpan} from '../parse_util';
|
import {ParseSourceSpan} from '../parse_util';
|
||||||
|
|
||||||
export interface Node {
|
export interface Node {
|
||||||
|
@ -29,14 +29,13 @@ export class TextAttribute implements Node {
|
||||||
constructor(
|
constructor(
|
||||||
public name: string, public value: string, public sourceSpan: ParseSourceSpan,
|
public name: string, public value: string, public sourceSpan: ParseSourceSpan,
|
||||||
public valueSpan?: ParseSourceSpan) {}
|
public valueSpan?: ParseSourceSpan) {}
|
||||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitAttribute(this); }
|
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitTextAttribute(this); }
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BoundAttribute implements Node {
|
export class BoundAttribute implements Node {
|
||||||
constructor(
|
constructor(
|
||||||
public name: string, public type: BoundElementBindingType,
|
public name: string, public type: BindingType, public securityContext: SecurityContext,
|
||||||
public securityContext: SecurityContext, public value: AST, public unit: string|null,
|
public value: AST, public unit: string|null, public sourceSpan: ParseSourceSpan) {}
|
||||||
public sourceSpan: ParseSourceSpan) {}
|
|
||||||
|
|
||||||
static fromBoundElementProperty(prop: BoundElementProperty) {
|
static fromBoundElementProperty(prop: BoundElementProperty) {
|
||||||
return new BoundAttribute(
|
return new BoundAttribute(
|
||||||
|
@ -106,7 +105,7 @@ export interface Visitor<Result = any> {
|
||||||
visitContent(content: Content): Result;
|
visitContent(content: Content): Result;
|
||||||
visitVariable(variable: Variable): Result;
|
visitVariable(variable: Variable): Result;
|
||||||
visitReference(reference: Reference): Result;
|
visitReference(reference: Reference): Result;
|
||||||
visitAttribute(attribute: TextAttribute): Result;
|
visitTextAttribute(attribute: TextAttribute): Result;
|
||||||
visitBoundAttribute(attribute: BoundAttribute): Result;
|
visitBoundAttribute(attribute: BoundAttribute): Result;
|
||||||
visitBoundEvent(attribute: BoundEvent): Result;
|
visitBoundEvent(attribute: BoundEvent): Result;
|
||||||
visitText(text: Text): Result;
|
visitText(text: Text): Result;
|
||||||
|
@ -119,7 +118,7 @@ export class NullVisitor implements Visitor<void> {
|
||||||
visitContent(content: Content): void {}
|
visitContent(content: Content): void {}
|
||||||
visitVariable(variable: Variable): void {}
|
visitVariable(variable: Variable): void {}
|
||||||
visitReference(reference: Reference): void {}
|
visitReference(reference: Reference): void {}
|
||||||
visitAttribute(attribute: TextAttribute): void {}
|
visitTextAttribute(attribute: TextAttribute): void {}
|
||||||
visitBoundAttribute(attribute: BoundAttribute): void {}
|
visitBoundAttribute(attribute: BoundAttribute): void {}
|
||||||
visitBoundEvent(attribute: BoundEvent): void {}
|
visitBoundEvent(attribute: BoundEvent): void {}
|
||||||
visitText(text: Text): void {}
|
visitText(text: Text): void {}
|
||||||
|
@ -141,7 +140,7 @@ export class RecursiveVisitor implements Visitor<void> {
|
||||||
visitContent(content: Content): void {}
|
visitContent(content: Content): void {}
|
||||||
visitVariable(variable: Variable): void {}
|
visitVariable(variable: Variable): void {}
|
||||||
visitReference(reference: Reference): void {}
|
visitReference(reference: Reference): void {}
|
||||||
visitAttribute(attribute: TextAttribute): void {}
|
visitTextAttribute(attribute: TextAttribute): void {}
|
||||||
visitBoundAttribute(attribute: BoundAttribute): void {}
|
visitBoundAttribute(attribute: BoundAttribute): void {}
|
||||||
visitBoundEvent(attribute: BoundEvent): void {}
|
visitBoundEvent(attribute: BoundEvent): void {}
|
||||||
visitText(text: Text): void {}
|
visitText(text: Text): void {}
|
||||||
|
@ -185,7 +184,7 @@ export class TransformVisitor implements Visitor<Node> {
|
||||||
|
|
||||||
visitVariable(variable: Variable): Node { return variable; }
|
visitVariable(variable: Variable): Node { return variable; }
|
||||||
visitReference(reference: Reference): Node { return reference; }
|
visitReference(reference: Reference): Node { return reference; }
|
||||||
visitAttribute(attribute: TextAttribute): Node { return attribute; }
|
visitTextAttribute(attribute: TextAttribute): Node { return attribute; }
|
||||||
visitBoundAttribute(attribute: BoundAttribute): Node { return attribute; }
|
visitBoundAttribute(attribute: BoundAttribute): Node { return attribute; }
|
||||||
visitBoundEvent(attribute: BoundEvent): Node { return attribute; }
|
visitBoundEvent(attribute: BoundEvent): Node { return attribute; }
|
||||||
visitText(text: Text): Node { return text; }
|
visitText(text: Text): Node { return text; }
|
||||||
|
|
|
@ -111,9 +111,12 @@ export class HtmlToTemplateTransform implements html.Visitor {
|
||||||
|
|
||||||
inlineTemplateSourceSpan = attribute.valueSpan || attribute.sourceSpan;
|
inlineTemplateSourceSpan = attribute.valueSpan || attribute.sourceSpan;
|
||||||
|
|
||||||
|
const parsedVariables: ParsedVariable[] = [];
|
||||||
this.bindingParser.parseInlineTemplateBinding(
|
this.bindingParser.parseInlineTemplateBinding(
|
||||||
templateKey, templateValue, attribute.sourceSpan, templateMatchableAttributes,
|
templateKey, templateValue, attribute.sourceSpan, templateMatchableAttributes,
|
||||||
templateParsedProperties, templateVariables);
|
templateParsedProperties, parsedVariables);
|
||||||
|
templateVariables.push(
|
||||||
|
...parsedVariables.map(v => new t.Variable(v.name, v.value, v.sourceSpan)));
|
||||||
} else {
|
} else {
|
||||||
// Check for variables, events, property bindings, interpolation
|
// Check for variables, events, property bindings, interpolation
|
||||||
hasBinding = this.parseAttribute(
|
hasBinding = this.parseAttribute(
|
||||||
|
@ -272,7 +275,6 @@ export class HtmlToTemplateTransform implements html.Visitor {
|
||||||
return hasBinding;
|
return hasBinding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private parseVariable(
|
private parseVariable(
|
||||||
identifier: string, value: string, sourceSpan: ParseSourceSpan, variables: t.Variable[]) {
|
identifier: string, value: string, sourceSpan: ParseSourceSpan, variables: t.Variable[]) {
|
||||||
if (identifier.indexOf('-') > -1) {
|
if (identifier.indexOf('-') > -1) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {CompileReflector} from '../../compile_reflector';
|
||||||
import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter';
|
import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter';
|
||||||
import {ConstantPool} from '../../constant_pool';
|
import {ConstantPool} from '../../constant_pool';
|
||||||
import * as core from '../../core';
|
import * as core from '../../core';
|
||||||
import {AST, AstMemoryEfficientTransformer, BindingPipe, BoundElementBindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast';
|
import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast';
|
||||||
import * as o from '../../output/output_ast';
|
import * as o from '../../output/output_ast';
|
||||||
import {ParseSourceSpan} from '../../parse_util';
|
import {ParseSourceSpan} from '../../parse_util';
|
||||||
import {CssSelector, SelectorMatcher} from '../../selector';
|
import {CssSelector, SelectorMatcher} from '../../selector';
|
||||||
|
@ -23,10 +23,10 @@ import {R3QueryMetadata} from './api';
|
||||||
import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, getQueryPredicate, invalid, mapToExpression, noop, temporaryAllocator, trimTrailingNulls, unsupported} from './util';
|
import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, getQueryPredicate, invalid, mapToExpression, noop, temporaryAllocator, trimTrailingNulls, unsupported} from './util';
|
||||||
|
|
||||||
const BINDING_INSTRUCTION_MAP: {[type: number]: o.ExternalReference} = {
|
const BINDING_INSTRUCTION_MAP: {[type: number]: o.ExternalReference} = {
|
||||||
[BoundElementBindingType.Property]: R3.elementProperty,
|
[BindingType.Property]: R3.elementProperty,
|
||||||
[BoundElementBindingType.Attribute]: R3.elementAttribute,
|
[BindingType.Attribute]: R3.elementAttribute,
|
||||||
[BoundElementBindingType.Class]: R3.elementClassNamed,
|
[BindingType.Class]: R3.elementClassNamed,
|
||||||
[BoundElementBindingType.Style]: R3.elementStyleNamed,
|
[BindingType.Style]: R3.elementStyleNamed,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver {
|
export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver {
|
||||||
|
@ -331,7 +331,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
|
|
||||||
// Generate element input bindings
|
// Generate element input bindings
|
||||||
element.inputs.forEach((input: t.BoundAttribute) => {
|
element.inputs.forEach((input: t.BoundAttribute) => {
|
||||||
if (input.type === BoundElementBindingType.Animation) {
|
if (input.type === BindingType.Animation) {
|
||||||
this._unsupported('animations');
|
this._unsupported('animations');
|
||||||
}
|
}
|
||||||
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
|
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
|
||||||
|
@ -431,7 +431,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
// These should be handled in the template or element directly.
|
// These should be handled in the template or element directly.
|
||||||
readonly visitReference = invalid;
|
readonly visitReference = invalid;
|
||||||
readonly visitVariable = invalid;
|
readonly visitVariable = invalid;
|
||||||
readonly visitAttribute = invalid;
|
readonly visitTextAttribute = invalid;
|
||||||
readonly visitBoundAttribute = invalid;
|
readonly visitBoundAttribute = invalid;
|
||||||
readonly visitBoundEvent = invalid;
|
readonly visitBoundEvent = invalid;
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,9 @@ import * as t from '../r3_ast';
|
||||||
|
|
||||||
import {R3QueryMetadata} from './api';
|
import {R3QueryMetadata} from './api';
|
||||||
|
|
||||||
|
|
||||||
/** Name of the temporary to use during data binding */
|
/** Name of the temporary to use during data binding */
|
||||||
export const TEMPORARY_NAME = '_t';
|
export const TEMPORARY_NAME = '_t';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** Name of the context parameter passed into a template function */
|
/** Name of the context parameter passed into a template function */
|
||||||
export const CONTEXT_NAME = 'ctx';
|
export const CONTEXT_NAME = 'ctx';
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
|
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
|
||||||
import {SecurityContext} from '../core';
|
import {SecurityContext} from '../core';
|
||||||
import {ASTWithSource, BindingPipe, BoundElementBindingType, BoundElementProperty, EmptyExpr, ParsedEvent, ParsedEventType, ParsedProperty, ParsedPropertyType, ParsedVariable, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
import {ASTWithSource, BindingPipe, BindingType, BoundElementProperty, EmptyExpr, ParsedEvent, ParsedEventType, ParsedProperty, ParsedPropertyType, ParsedVariable, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
||||||
import {Parser} from '../expression_parser/parser';
|
import {Parser} from '../expression_parser/parser';
|
||||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||||
import {mergeNsAndName} from '../ml_parser/tags';
|
import {mergeNsAndName} from '../ml_parser/tags';
|
||||||
|
@ -239,12 +239,12 @@ export class BindingParser {
|
||||||
BoundElementProperty {
|
BoundElementProperty {
|
||||||
if (boundProp.isAnimation) {
|
if (boundProp.isAnimation) {
|
||||||
return new BoundElementProperty(
|
return new BoundElementProperty(
|
||||||
boundProp.name, BoundElementBindingType.Animation, SecurityContext.NONE,
|
boundProp.name, BindingType.Animation, SecurityContext.NONE, boundProp.expression, null,
|
||||||
boundProp.expression, null, boundProp.sourceSpan);
|
boundProp.sourceSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
let unit: string|null = null;
|
let unit: string|null = null;
|
||||||
let bindingType: BoundElementBindingType = undefined !;
|
let bindingType: BindingType = undefined !;
|
||||||
let boundPropertyName: string|null = null;
|
let boundPropertyName: string|null = null;
|
||||||
const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
|
const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
|
||||||
let securityContexts: SecurityContext[] = undefined !;
|
let securityContexts: SecurityContext[] = undefined !;
|
||||||
|
@ -264,15 +264,15 @@ export class BindingParser {
|
||||||
boundPropertyName = mergeNsAndName(ns, name);
|
boundPropertyName = mergeNsAndName(ns, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingType = BoundElementBindingType.Attribute;
|
bindingType = BindingType.Attribute;
|
||||||
} else if (parts[0] == CLASS_PREFIX) {
|
} else if (parts[0] == CLASS_PREFIX) {
|
||||||
boundPropertyName = parts[1];
|
boundPropertyName = parts[1];
|
||||||
bindingType = BoundElementBindingType.Class;
|
bindingType = BindingType.Class;
|
||||||
securityContexts = [SecurityContext.NONE];
|
securityContexts = [SecurityContext.NONE];
|
||||||
} else if (parts[0] == STYLE_PREFIX) {
|
} else if (parts[0] == STYLE_PREFIX) {
|
||||||
unit = parts.length > 2 ? parts[2] : null;
|
unit = parts.length > 2 ? parts[2] : null;
|
||||||
boundPropertyName = parts[1];
|
boundPropertyName = parts[1];
|
||||||
bindingType = BoundElementBindingType.Style;
|
bindingType = BindingType.Style;
|
||||||
securityContexts = [SecurityContext.STYLE];
|
securityContexts = [SecurityContext.STYLE];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,7 +282,7 @@ export class BindingParser {
|
||||||
boundPropertyName = this._schemaRegistry.getMappedPropName(boundProp.name);
|
boundPropertyName = this._schemaRegistry.getMappedPropName(boundProp.name);
|
||||||
securityContexts = calcPossibleSecurityContexts(
|
securityContexts = calcPossibleSecurityContexts(
|
||||||
this._schemaRegistry, elementSelector, boundPropertyName, false);
|
this._schemaRegistry, elementSelector, boundPropertyName, false);
|
||||||
bindingType = BoundElementBindingType.Property;
|
bindingType = BindingType.Property;
|
||||||
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, false);
|
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import {AstPath} from '../ast_path';
|
import {AstPath} from '../ast_path';
|
||||||
import {CompileDirectiveSummary, CompileProviderMetadata, CompileTokenMetadata} from '../compile_metadata';
|
import {CompileDirectiveSummary, CompileProviderMetadata, CompileTokenMetadata} from '../compile_metadata';
|
||||||
import {SecurityContext} from '../core';
|
import {SecurityContext} from '../core';
|
||||||
import {AST, BoundElementBindingType, BoundElementProperty, ParsedEvent, ParsedEventType, ParsedVariable} from '../expression_parser/ast';
|
import {AST, BindingType, BoundElementProperty, ParsedEvent, ParsedEventType, ParsedVariable} from '../expression_parser/ast';
|
||||||
import {LifecycleHooks} from '../lifecycle_reflector';
|
import {LifecycleHooks} from '../lifecycle_reflector';
|
||||||
import {ParseSourceSpan} from '../parse_util';
|
import {ParseSourceSpan} from '../parse_util';
|
||||||
|
|
||||||
|
@ -72,11 +72,11 @@ export enum PropertyBindingType {
|
||||||
}
|
}
|
||||||
|
|
||||||
const BoundPropertyMapping = {
|
const BoundPropertyMapping = {
|
||||||
[BoundElementBindingType.Animation]: PropertyBindingType.Animation,
|
[BindingType.Animation]: PropertyBindingType.Animation,
|
||||||
[BoundElementBindingType.Attribute]: PropertyBindingType.Attribute,
|
[BindingType.Attribute]: PropertyBindingType.Attribute,
|
||||||
[BoundElementBindingType.Class]: PropertyBindingType.Class,
|
[BindingType.Class]: PropertyBindingType.Class,
|
||||||
[BoundElementBindingType.Property]: PropertyBindingType.Property,
|
[BindingType.Property]: PropertyBindingType.Property,
|
||||||
[BoundElementBindingType.Style]: PropertyBindingType.Style,
|
[BindingType.Style]: PropertyBindingType.Style,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,7 +5,6 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
|
||||||
NODE_ONLY = [
|
NODE_ONLY = [
|
||||||
"**/*_node_only_spec.ts",
|
"**/*_node_only_spec.ts",
|
||||||
"aot/**/*.ts",
|
"aot/**/*.ts",
|
||||||
"render3/**/*.ts",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
UTILS = [
|
UTILS = [
|
||||||
|
@ -37,6 +36,7 @@ ts_library(
|
||||||
"//packages:types",
|
"//packages:types",
|
||||||
"//packages/common",
|
"//packages/common",
|
||||||
"//packages/compiler",
|
"//packages/compiler",
|
||||||
|
"//packages/compiler/test/expression_parser/utils",
|
||||||
"//packages/compiler/testing",
|
"//packages/compiler/testing",
|
||||||
"//packages/core",
|
"//packages/core",
|
||||||
"//packages/core/testing",
|
"//packages/core/testing",
|
||||||
|
@ -58,6 +58,7 @@ ts_library(
|
||||||
":test_utils",
|
":test_utils",
|
||||||
"//packages/compiler",
|
"//packages/compiler",
|
||||||
"//packages/compiler-cli",
|
"//packages/compiler-cli",
|
||||||
|
"//packages/compiler/test/expression_parser/utils",
|
||||||
"//packages/compiler/testing",
|
"//packages/compiler/testing",
|
||||||
"//packages/core",
|
"//packages/core",
|
||||||
],
|
],
|
||||||
|
|
|
@ -12,8 +12,8 @@ import {Parser, SplitInterpolation, TemplateBindingParseResult} from '@angular/c
|
||||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||||
|
|
||||||
|
|
||||||
import {unparse} from './unparser';
|
import {unparse} from './utils/unparser';
|
||||||
import {validate} from './validator';
|
import {validate} from './utils/validator';
|
||||||
|
|
||||||
describe('parser', () => {
|
describe('parser', () => {
|
||||||
describe('parseAction', () => {
|
describe('parseAction', () => {
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
load("//tools:defaults.bzl", "ts_library")
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "utils",
|
||||||
|
testonly = 1,
|
||||||
|
srcs = glob(
|
||||||
|
["*.ts"],
|
||||||
|
),
|
||||||
|
visibility = [
|
||||||
|
"//packages/compiler/test:__subpackages__",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//packages/compiler",
|
||||||
|
],
|
||||||
|
)
|
|
@ -6,8 +6,8 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AST, AstVisitor, Binary, BindingPipe, Chain, Conditional, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, NonNullAssert, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead} from '../../src/expression_parser/ast';
|
import {AST, AstVisitor, Binary, BindingPipe, Chain, Conditional, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, NonNullAssert, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead} from '../../../src/expression_parser/ast';
|
||||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../src/ml_parser/interpolation_config';
|
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../src/ml_parser/interpolation_config';
|
||||||
|
|
||||||
class Unparser implements AstVisitor {
|
class Unparser implements AstVisitor {
|
||||||
private static _quoteRegExp = /"/g;
|
private static _quoteRegExp = /"/g;
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AST, Binary, BindingPipe, Chain, Conditional, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, PrefixNot, PropertyRead, PropertyWrite, Quote, RecursiveAstVisitor, SafeMethodCall, SafePropertyRead} from '../../src/expression_parser/ast';
|
import {AST, Binary, BindingPipe, Chain, Conditional, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, PrefixNot, PropertyRead, PropertyWrite, Quote, RecursiveAstVisitor, SafeMethodCall, SafePropertyRead} from '../../../src/expression_parser/ast';
|
||||||
|
|
||||||
import {unparse} from './unparser';
|
import {unparse} from './unparser';
|
||||||
|
|
|
@ -11,6 +11,8 @@ ts_library(
|
||||||
"//packages:types",
|
"//packages:types",
|
||||||
"//packages/compiler",
|
"//packages/compiler",
|
||||||
"//packages/compiler/test:test_utils",
|
"//packages/compiler/test:test_utils",
|
||||||
|
"//packages/compiler/test/expression_parser/utils",
|
||||||
|
"//packages/compiler/testing",
|
||||||
"//packages/core",
|
"//packages/core",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,486 @@
|
||||||
|
/**
|
||||||
|
* @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 {BindingType} from '../../src/expression_parser/ast';
|
||||||
|
import {Lexer} from '../../src/expression_parser/lexer';
|
||||||
|
import {Parser} from '../../src/expression_parser/parser';
|
||||||
|
import {visitAll} from '../../src/ml_parser/ast';
|
||||||
|
import {HtmlParser} from '../../src/ml_parser/html_parser';
|
||||||
|
import {DEFAULT_INTERPOLATION_CONFIG} from '../../src/ml_parser/interpolation_config';
|
||||||
|
import {ParseError} from '../../src/parse_util';
|
||||||
|
import * as t from '../../src/render3/r3_ast';
|
||||||
|
import {HtmlToTemplateTransform} from '../../src/render3/r3_template_transform';
|
||||||
|
import {BindingParser} from '../../src/template_parser/binding_parser';
|
||||||
|
import {MockSchemaRegistry} from '../../testing';
|
||||||
|
import {unparse} from '../expression_parser/utils/unparser';
|
||||||
|
|
||||||
|
// Parse an html string to IVY specific info
|
||||||
|
function parse(html: string) {
|
||||||
|
const htmlParser = new HtmlParser();
|
||||||
|
|
||||||
|
const parseResult = htmlParser.parse(html, 'path:://to/template', true);
|
||||||
|
|
||||||
|
if (parseResult.errors.length > 0) {
|
||||||
|
const msg = parseResult.errors.map(e => e.toString()).join('\n');
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const htmlNodes = parseResult.rootNodes;
|
||||||
|
const expressionErrors: ParseError[] = [];
|
||||||
|
const expressionParser = new Parser(new Lexer());
|
||||||
|
const schemaRegistry = new MockSchemaRegistry(
|
||||||
|
{'invalidProp': false}, {'mappedAttr': 'mappedProp'}, {'unknown': false, 'un-known': false},
|
||||||
|
['onEvent'], ['onEvent']);
|
||||||
|
const bindingParser = new BindingParser(
|
||||||
|
expressionParser, DEFAULT_INTERPOLATION_CONFIG, schemaRegistry, null, expressionErrors);
|
||||||
|
const r3Transform = new HtmlToTemplateTransform(bindingParser);
|
||||||
|
|
||||||
|
const r3Nodes = visitAll(r3Transform, htmlNodes);
|
||||||
|
|
||||||
|
if (r3Transform.errors) {
|
||||||
|
const msg = r3Transform.errors.map(e => e.toString()).join('\n');
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
nodes: r3Nodes,
|
||||||
|
hasNgContent: r3Transform.hasNgContent,
|
||||||
|
ngContentSelectors: r3Transform.ngContentSelectors,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform an IVY AST to a flat list of nodes to ease testing
|
||||||
|
class R3AstHumanizer implements t.Visitor<void> {
|
||||||
|
result: any[] = [];
|
||||||
|
|
||||||
|
visitElement(element: t.Element) {
|
||||||
|
this.result.push(['Element', element.name]);
|
||||||
|
this.visitAll([
|
||||||
|
element.attributes,
|
||||||
|
element.inputs,
|
||||||
|
element.outputs,
|
||||||
|
element.references,
|
||||||
|
element.children,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitTemplate(template: t.Template) {
|
||||||
|
this.result.push(['Template']);
|
||||||
|
this.visitAll([
|
||||||
|
template.attributes,
|
||||||
|
template.inputs,
|
||||||
|
template.references,
|
||||||
|
template.variables,
|
||||||
|
template.children,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitContent(content: t.Content) {
|
||||||
|
this.result.push(['Content', content.selectorIndex]);
|
||||||
|
t.visitAll(this, content.attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitVariable(variable: t.Variable) {
|
||||||
|
this.result.push(['Variable', variable.name, variable.value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitReference(reference: t.Reference) {
|
||||||
|
this.result.push(['Reference', reference.name, reference.value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitTextAttribute(attribute: t.TextAttribute) {
|
||||||
|
this.result.push(['TextAttribute', attribute.name, attribute.value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitBoundAttribute(attribute: t.BoundAttribute) {
|
||||||
|
this.result.push([
|
||||||
|
'BoundAttribute',
|
||||||
|
attribute.type,
|
||||||
|
attribute.name,
|
||||||
|
unparse(attribute.value),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitBoundEvent(event: t.BoundEvent) {
|
||||||
|
this.result.push([
|
||||||
|
'BoundEvent',
|
||||||
|
event.name,
|
||||||
|
event.target,
|
||||||
|
unparse(event.handler),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitText(text: t.Text) { this.result.push(['Text', text.value]); }
|
||||||
|
|
||||||
|
visitBoundText(text: t.BoundText) { this.result.push(['BoundText', unparse(text.value)]); }
|
||||||
|
|
||||||
|
private visitAll(nodes: t.Node[][]) { nodes.forEach(node => t.visitAll(this, node)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectFromHtml(html: string) {
|
||||||
|
const res = parse(html);
|
||||||
|
return expectFromR3Nodes(res.nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectFromR3Nodes(nodes: t.Node[]) {
|
||||||
|
const humanizer = new R3AstHumanizer();
|
||||||
|
t.visitAll(humanizer, nodes);
|
||||||
|
return expect(humanizer.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('R3 template transform', () => {
|
||||||
|
describe('Nodes without binding', () => {
|
||||||
|
it('should parse text nodes', () => {
|
||||||
|
expectFromHtml('a').toEqual([
|
||||||
|
['Text', 'a'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse elements with attributes', () => {
|
||||||
|
expectFromHtml('<div a=b></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['TextAttribute', 'a', 'b'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse ngContent', () => {
|
||||||
|
const res = parse('<ng-content select="a"></ng-content>');
|
||||||
|
expect(res.hasNgContent).toEqual(true);
|
||||||
|
expect(res.ngContentSelectors).toEqual(['a']);
|
||||||
|
expectFromR3Nodes(res.nodes).toEqual([
|
||||||
|
['Content', 1],
|
||||||
|
['TextAttribute', 'select', 'a'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse ngContent when it contains WS only', () => {
|
||||||
|
expectFromHtml('<ng-content select="a"> \n </ng-content>').toEqual([
|
||||||
|
['Content', 1],
|
||||||
|
['TextAttribute', 'select', 'a'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse ngContent regardless the namespace', () => {
|
||||||
|
expectFromHtml('<svg><ng-content select="a"></ng-content></svg>').toEqual([
|
||||||
|
['Element', ':svg:svg'],
|
||||||
|
['Content', 1],
|
||||||
|
['TextAttribute', 'select', 'a'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Bound text nodes', () => {
|
||||||
|
it('should parse bound text nodes', () => {
|
||||||
|
expectFromHtml('{{a}}').toEqual([
|
||||||
|
['BoundText', '{{ a }}'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Bound attributes', () => {
|
||||||
|
it('should parse mixed case bound properties', () => {
|
||||||
|
expectFromHtml('<div [someProp]="v"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundAttribute', BindingType.Property, 'someProp', 'v'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse bound properties via bind- ', () => {
|
||||||
|
expectFromHtml('<div bind-prop="v"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundAttribute', BindingType.Property, 'prop', 'v'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse bound properties via {{...}}', () => {
|
||||||
|
expectFromHtml('<div prop="{{v}}"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundAttribute', BindingType.Property, 'prop', '{{ v }}'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse dash case bound properties', () => {
|
||||||
|
expectFromHtml('<div [some-prop]="v"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundAttribute', BindingType.Property, 'some-prop', 'v'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse dotted name bound properties', () => {
|
||||||
|
expectFromHtml('<div [d.ot]="v"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundAttribute', BindingType.Property, 'd.ot', 'v'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should normalize property names via the element schema', () => {
|
||||||
|
expectFromHtml('<div [mappedAttr]="v"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundAttribute', BindingType.Property, 'mappedProp', 'v'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse mixed case bound attributes', () => {
|
||||||
|
expectFromHtml('<div [attr.someAttr]="v"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundAttribute', BindingType.Attribute, 'someAttr', 'v'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse and dash case bound classes', () => {
|
||||||
|
expectFromHtml('<div [class.some-class]="v"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundAttribute', BindingType.Class, 'some-class', 'v'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse mixed case bound classes', () => {
|
||||||
|
expectFromHtml('<div [class.someClass]="v"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundAttribute', BindingType.Class, 'someClass', 'v'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse mixed case bound styles', () => {
|
||||||
|
expectFromHtml('<div [style.someStyle]="v"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundAttribute', BindingType.Style, 'someStyle', 'v'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('templates', () => {
|
||||||
|
it('should support * directives', () => {
|
||||||
|
expectFromHtml('<div *ngIf></div>').toEqual([
|
||||||
|
['Template'],
|
||||||
|
['TextAttribute', 'ngIf', ''],
|
||||||
|
['Element', 'div'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support <ng-template>', () => {
|
||||||
|
expectFromHtml('<ng-template></ng-template>').toEqual([
|
||||||
|
['Template'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support <ng-template> regardless the namespace', () => {
|
||||||
|
expectFromHtml('<svg><ng-template></ng-template></svg>').toEqual([
|
||||||
|
['Element', ':svg:svg'],
|
||||||
|
['Template'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support reference via #...', () => {
|
||||||
|
expectFromHtml('<ng-template #a></ng-template>').toEqual([
|
||||||
|
['Template'],
|
||||||
|
['Reference', 'a', ''],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support reference via ref-...', () => {
|
||||||
|
expectFromHtml('<ng-template ref-a></ng-template>').toEqual([
|
||||||
|
['Template'],
|
||||||
|
['Reference', 'a', ''],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse variables via let-...', () => {
|
||||||
|
expectFromHtml('<ng-template let-a="b"></ng-template>').toEqual([
|
||||||
|
['Template'],
|
||||||
|
['Variable', 'a', 'b'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('inline templates', () => {
|
||||||
|
it('should parse variables via let ...', () => {
|
||||||
|
expectFromHtml('<div *ngIf="let a=b"></div>').toEqual([
|
||||||
|
['Template'],
|
||||||
|
['TextAttribute', 'ngIf', ''],
|
||||||
|
['Variable', 'a', 'b'],
|
||||||
|
['Element', 'div'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse variables via as ...', () => {
|
||||||
|
expectFromHtml('<div *ngIf="expr as local"></div>').toEqual([
|
||||||
|
['Template'],
|
||||||
|
['TextAttribute', 'ngIf', 'expr '],
|
||||||
|
['BoundAttribute', BindingType.Property, 'ngIf', 'expr'],
|
||||||
|
['Variable', 'local', 'ngIf'],
|
||||||
|
['Element', 'div'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('events', () => {
|
||||||
|
it('should parse bound events with a target', () => {
|
||||||
|
expectFromHtml('<div (window:event)="v"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundEvent', 'event', 'window', 'v'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse event names case sensitive', () => {
|
||||||
|
expectFromHtml('<div (some-event)="v"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundEvent', 'some-event', null, 'v'],
|
||||||
|
]);
|
||||||
|
expectFromHtml('<div (someEvent)="v"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundEvent', 'someEvent', null, 'v'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse bound events via on-', () => {
|
||||||
|
expectFromHtml('<div on-event="v"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundEvent', 'event', null, 'v'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse bound events and properties via [(...)]', () => {
|
||||||
|
expectFromHtml('<div [(prop)]="v"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundAttribute', BindingType.Property, 'prop', 'v'],
|
||||||
|
['BoundEvent', 'propChange', null, 'v = $event'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse bound events and properties via bindon-', () => {
|
||||||
|
expectFromHtml('<div bindon-prop="v"></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['BoundAttribute', BindingType.Property, 'prop', 'v'],
|
||||||
|
['BoundEvent', 'propChange', null, 'v = $event'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO(vicb): Should Error
|
||||||
|
xit('should report an error on empty expression', () => {
|
||||||
|
expect(() => parse('<div (event)="">')).toThrowError(/Empty expressions are not allowed/);
|
||||||
|
|
||||||
|
expect(() => parse('<div (event)=" ">')).toThrowError(/Empty expressions are not allowed/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('references', () => {
|
||||||
|
it('should parse references via #...', () => {
|
||||||
|
expectFromHtml('<div #a></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['Reference', 'a', ''],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse references via ref-', () => {
|
||||||
|
expectFromHtml('<div ref-a></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['Reference', 'a', ''],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse camel case references', () => {
|
||||||
|
expectFromHtml('<div #someA></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['Reference', 'someA', ''],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO(vicb): Add ng-content test
|
||||||
|
describe('ng-content', () => {});
|
||||||
|
|
||||||
|
describe('Ignored elements', () => {
|
||||||
|
it('should ignore <script> elements', () => {
|
||||||
|
expectFromHtml('<script></script>a').toEqual([
|
||||||
|
['Text', 'a'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore <style> elements', () => {
|
||||||
|
expectFromHtml('<style></style>a').toEqual([
|
||||||
|
['Text', 'a'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('<link rel="stylesheet">', () => {
|
||||||
|
it('should keep <link rel="stylesheet"> elements if they have an absolute url', () => {
|
||||||
|
expectFromHtml('<link rel="stylesheet" href="http://someurl">').toEqual([
|
||||||
|
['Element', 'link'],
|
||||||
|
['TextAttribute', 'rel', 'stylesheet'],
|
||||||
|
['TextAttribute', 'href', 'http://someurl'],
|
||||||
|
]);
|
||||||
|
expectFromHtml('<link REL="stylesheet" href="http://someurl">').toEqual([
|
||||||
|
['Element', 'link'],
|
||||||
|
['TextAttribute', 'REL', 'stylesheet'],
|
||||||
|
['TextAttribute', 'href', 'http://someurl'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep <link rel="stylesheet"> elements if they have no uri', () => {
|
||||||
|
expectFromHtml('<link rel="stylesheet">').toEqual([
|
||||||
|
['Element', 'link'],
|
||||||
|
['TextAttribute', 'rel', 'stylesheet'],
|
||||||
|
]);
|
||||||
|
expectFromHtml('<link REL="stylesheet">').toEqual([
|
||||||
|
['Element', 'link'],
|
||||||
|
['TextAttribute', 'REL', 'stylesheet'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore <link rel="stylesheet"> elements if they have a relative uri', () => {
|
||||||
|
expectFromHtml('<link rel="stylesheet" href="./other.css">').toEqual([]);
|
||||||
|
expectFromHtml('<link REL="stylesheet" HREF="./other.css">').toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ngNonBindable', () => {
|
||||||
|
it('should ignore bindings on children of elements with ngNonBindable', () => {
|
||||||
|
expectFromHtml('<div ngNonBindable>{{b}}</div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['TextAttribute', 'ngNonBindable', ''],
|
||||||
|
['Text', '{{b}}'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep nested children of elements with ngNonBindable', () => {
|
||||||
|
expectFromHtml('<div ngNonBindable><span>{{b}}</span></div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['TextAttribute', 'ngNonBindable', ''],
|
||||||
|
['Element', 'span'],
|
||||||
|
['Text', '{{b}}'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore <script> elements inside of elements with ngNonBindable', () => {
|
||||||
|
expectFromHtml('<div ngNonBindable><script></script>a</div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['TextAttribute', 'ngNonBindable', ''],
|
||||||
|
['Text', 'a'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore <style> elements inside of elements with ngNonBindable', () => {
|
||||||
|
expectFromHtml('<div ngNonBindable><style></style>a</div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['TextAttribute', 'ngNonBindable', ''],
|
||||||
|
['Text', 'a'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore <link rel="stylesheet"> elements inside of elements with ngNonBindable',
|
||||||
|
() => {
|
||||||
|
expectFromHtml('<div ngNonBindable><link rel="stylesheet">a</div>').toEqual([
|
||||||
|
['Element', 'div'],
|
||||||
|
['TextAttribute', 'ngNonBindable', ''],
|
||||||
|
['Text', 'a'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -21,7 +21,7 @@ import {Identifiers, createTokenForExternalReference, createTokenForReference} f
|
||||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../src/ml_parser/interpolation_config';
|
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../src/ml_parser/interpolation_config';
|
||||||
import {noUndefined} from '../../src/util';
|
import {noUndefined} from '../../src/util';
|
||||||
import {MockSchemaRegistry} from '../../testing';
|
import {MockSchemaRegistry} from '../../testing';
|
||||||
import {unparse} from '../expression_parser/unparser';
|
import {unparse} from '../expression_parser/utils/unparser';
|
||||||
import {TEST_COMPILER_PROVIDERS} from '../test_bindings';
|
import {TEST_COMPILER_PROVIDERS} from '../test_bindings';
|
||||||
|
|
||||||
const someModuleUrl = 'package:someModule';
|
const someModuleUrl = 'package:someModule';
|
||||||
|
@ -139,12 +139,12 @@ class TemplateHumanizer implements TemplateAstVisitor {
|
||||||
|
|
||||||
visitNgContent(ast: NgContentAst, context: any): any {
|
visitNgContent(ast: NgContentAst, context: any): any {
|
||||||
const res = [NgContentAst];
|
const res = [NgContentAst];
|
||||||
this.result.push(this._appendContext(ast, res));
|
this.result.push(this._appendSourceSpan(ast, res));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||||||
const res = [EmbeddedTemplateAst];
|
const res = [EmbeddedTemplateAst];
|
||||||
this.result.push(this._appendContext(ast, res));
|
this.result.push(this._appendSourceSpan(ast, res));
|
||||||
templateVisitAll(this, ast.attrs);
|
templateVisitAll(this, ast.attrs);
|
||||||
templateVisitAll(this, ast.outputs);
|
templateVisitAll(this, ast.outputs);
|
||||||
templateVisitAll(this, ast.references);
|
templateVisitAll(this, ast.references);
|
||||||
|
@ -155,7 +155,7 @@ class TemplateHumanizer implements TemplateAstVisitor {
|
||||||
}
|
}
|
||||||
visitElement(ast: ElementAst, context: any): any {
|
visitElement(ast: ElementAst, context: any): any {
|
||||||
const res = [ElementAst, ast.name];
|
const res = [ElementAst, ast.name];
|
||||||
this.result.push(this._appendContext(ast, res));
|
this.result.push(this._appendSourceSpan(ast, res));
|
||||||
templateVisitAll(this, ast.attrs);
|
templateVisitAll(this, ast.attrs);
|
||||||
templateVisitAll(this, ast.inputs);
|
templateVisitAll(this, ast.inputs);
|
||||||
templateVisitAll(this, ast.outputs);
|
templateVisitAll(this, ast.outputs);
|
||||||
|
@ -166,18 +166,18 @@ class TemplateHumanizer implements TemplateAstVisitor {
|
||||||
}
|
}
|
||||||
visitReference(ast: ReferenceAst, context: any): any {
|
visitReference(ast: ReferenceAst, context: any): any {
|
||||||
const res = [ReferenceAst, ast.name, ast.value];
|
const res = [ReferenceAst, ast.name, ast.value];
|
||||||
this.result.push(this._appendContext(ast, res));
|
this.result.push(this._appendSourceSpan(ast, res));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
visitVariable(ast: VariableAst, context: any): any {
|
visitVariable(ast: VariableAst, context: any): any {
|
||||||
const res = [VariableAst, ast.name, ast.value];
|
const res = [VariableAst, ast.name, ast.value];
|
||||||
this.result.push(this._appendContext(ast, res));
|
this.result.push(this._appendSourceSpan(ast, res));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
visitEvent(ast: BoundEventAst, context: any): any {
|
visitEvent(ast: BoundEventAst, context: any): any {
|
||||||
const res =
|
const res =
|
||||||
[BoundEventAst, ast.name, ast.target, unparse(ast.handler, this.interpolationConfig)];
|
[BoundEventAst, ast.name, ast.target, unparse(ast.handler, this.interpolationConfig)];
|
||||||
this.result.push(this._appendContext(ast, res));
|
this.result.push(this._appendSourceSpan(ast, res));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any {
|
visitElementProperty(ast: BoundElementPropertyAst, context: any): any {
|
||||||
|
@ -185,27 +185,27 @@ class TemplateHumanizer implements TemplateAstVisitor {
|
||||||
BoundElementPropertyAst, ast.type, ast.name, unparse(ast.value, this.interpolationConfig),
|
BoundElementPropertyAst, ast.type, ast.name, unparse(ast.value, this.interpolationConfig),
|
||||||
ast.unit
|
ast.unit
|
||||||
];
|
];
|
||||||
this.result.push(this._appendContext(ast, res));
|
this.result.push(this._appendSourceSpan(ast, res));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
visitAttr(ast: AttrAst, context: any): any {
|
visitAttr(ast: AttrAst, context: any): any {
|
||||||
const res = [AttrAst, ast.name, ast.value];
|
const res = [AttrAst, ast.name, ast.value];
|
||||||
this.result.push(this._appendContext(ast, res));
|
this.result.push(this._appendSourceSpan(ast, res));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
visitBoundText(ast: BoundTextAst, context: any): any {
|
visitBoundText(ast: BoundTextAst, context: any): any {
|
||||||
const res = [BoundTextAst, unparse(ast.value, this.interpolationConfig)];
|
const res = [BoundTextAst, unparse(ast.value, this.interpolationConfig)];
|
||||||
this.result.push(this._appendContext(ast, res));
|
this.result.push(this._appendSourceSpan(ast, res));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
visitText(ast: TextAst, context: any): any {
|
visitText(ast: TextAst, context: any): any {
|
||||||
const res = [TextAst, ast.value];
|
const res = [TextAst, ast.value];
|
||||||
this.result.push(this._appendContext(ast, res));
|
this.result.push(this._appendSourceSpan(ast, res));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
visitDirective(ast: DirectiveAst, context: any): any {
|
visitDirective(ast: DirectiveAst, context: any): any {
|
||||||
const res = [DirectiveAst, ast.directive];
|
const res = [DirectiveAst, ast.directive];
|
||||||
this.result.push(this._appendContext(ast, res));
|
this.result.push(this._appendSourceSpan(ast, res));
|
||||||
templateVisitAll(this, ast.inputs);
|
templateVisitAll(this, ast.inputs);
|
||||||
templateVisitAll(this, ast.hostProperties);
|
templateVisitAll(this, ast.hostProperties);
|
||||||
templateVisitAll(this, ast.hostEvents);
|
templateVisitAll(this, ast.hostEvents);
|
||||||
|
@ -215,11 +215,11 @@ class TemplateHumanizer implements TemplateAstVisitor {
|
||||||
const res = [
|
const res = [
|
||||||
BoundDirectivePropertyAst, ast.directiveName, unparse(ast.value, this.interpolationConfig)
|
BoundDirectivePropertyAst, ast.directiveName, unparse(ast.value, this.interpolationConfig)
|
||||||
];
|
];
|
||||||
this.result.push(this._appendContext(ast, res));
|
this.result.push(this._appendSourceSpan(ast, res));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _appendContext(ast: TemplateAst, input: any[]): any[] {
|
private _appendSourceSpan(ast: TemplateAst, input: any[]): any[] {
|
||||||
if (!this.includeSourceSpan) return input;
|
if (!this.includeSourceSpan) return input;
|
||||||
input.push(ast.sourceSpan !.toString());
|
input.push(ast.sourceSpan !.toString());
|
||||||
return input;
|
return input;
|
||||||
|
@ -1965,14 +1965,13 @@ Property binding a not used by any directive on an embedded template. Make sure
|
||||||
|
|
||||||
describe('<link rel="stylesheet">', () => {
|
describe('<link rel="stylesheet">', () => {
|
||||||
|
|
||||||
it('should keep <link rel="stylesheet"> elements if they have an absolute non package: url',
|
it('should keep <link rel="stylesheet"> elements if they have an absolute url', () => {
|
||||||
() => {
|
expect(humanizeTplAst(parse('<link rel="stylesheet" href="http://someurl">a', [])))
|
||||||
expect(humanizeTplAst(parse('<link rel="stylesheet" href="http://someurl">a', [])))
|
.toEqual([
|
||||||
.toEqual([
|
[ElementAst, 'link'], [AttrAst, 'rel', 'stylesheet'],
|
||||||
[ElementAst, 'link'], [AttrAst, 'rel', 'stylesheet'],
|
[AttrAst, 'href', 'http://someurl'], [TextAst, 'a']
|
||||||
[AttrAst, 'href', 'http://someurl'], [TextAst, 'a']
|
]);
|
||||||
]);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('should keep <link rel="stylesheet"> elements if they have no uri', () => {
|
it('should keep <link rel="stylesheet"> elements if they have no uri', () => {
|
||||||
expect(humanizeTplAst(parse('<link rel="stylesheet">a', [
|
expect(humanizeTplAst(parse('<link rel="stylesheet">a', [
|
||||||
|
|
Loading…
Reference in New Issue