2016-06-23 09:47:54 -07:00
/**
* @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
*/
2016-07-21 13:56:58 -07:00
import { Inject , Injectable , OpaqueToken , Optional , SchemaMetadata , SecurityContext } from '@angular/core' ;
2016-08-02 15:53:34 -07:00
import { Console , MAX_INTERPOLATION_VALUES } from '../../core_private' ;
import { CompileDirectiveMetadata , CompilePipeMetadata , CompileTokenMetadata , removeIdentifierDuplicates } from '../compile_metadata' ;
import { AST , ASTWithSource , BindingPipe , EmptyExpr , Interpolation , ParserError , RecursiveAstVisitor , TemplateBinding } from '../expression_parser/ast' ;
2016-07-21 11:41:25 -07:00
import { Parser } from '../expression_parser/parser' ;
2016-08-02 15:53:34 -07:00
import { ListWrapper , SetWrapper , StringMapWrapper } from '../facade/collection' ;
import { BaseException } from '../facade/exceptions' ;
import { isBlank , isPresent } from '../facade/lang' ;
2016-08-08 18:11:35 +02:00
import { Identifiers , identifierToken } from '../identifiers' ;
2016-08-01 12:19:09 -07:00
import * as html from '../ml_parser/ast' ;
import { HtmlParser , ParseTreeResult } from '../ml_parser/html_parser' ;
import { expandNodes } from '../ml_parser/icu_ast_expander' ;
import { InterpolationConfig } from '../ml_parser/interpolation_config' ;
import { mergeNsAndName , splitNsName } from '../ml_parser/tags' ;
2016-08-02 15:53:34 -07:00
import { ParseError , ParseErrorLevel , ParseSourceSpan } from '../parse_util' ;
2016-07-21 11:41:25 -07:00
import { ProviderElementContext , ProviderViewContext } from '../provider_analyzer' ;
2016-08-02 15:53:34 -07:00
import { ElementSchemaRegistry } from '../schema/element_schema_registry' ;
import { CssSelector , SelectorMatcher } from '../selector' ;
import { isStyleUrlResolvable } from '../style_url_resolver' ;
import { splitAtColon } from '../util' ;
import { AttrAst , BoundDirectivePropertyAst , BoundElementPropertyAst , BoundEventAst , BoundTextAst , DirectiveAst , ElementAst , EmbeddedTemplateAst , NgContentAst , PropertyBindingType , ReferenceAst , TemplateAst , TemplateAstVisitor , TextAst , VariableAst , templateVisitAll } from './template_ast' ;
import { PreparsedElementType , preparseElement } from './template_preparser' ;
2016-01-06 14:13:44 -08:00
2016-08-08 18:11:35 +02:00
2015-08-25 15:36:02 -07:00
// Group 1 = "bind-"
2016-04-25 19:52:24 -07:00
// Group 2 = "var-"
// Group 3 = "let-"
// Group 4 = "ref-/#"
// Group 5 = "on-"
// Group 6 = "bindon-"
2016-05-25 12:46:22 -07:00
// Group 7 = "animate-/@"
// Group 8 = the identifier after "bind-", "var-/#", or "on-"
// Group 9 = identifier inside [()]
// Group 10 = identifier inside []
// Group 11 = identifier inside ()
2016-07-08 10:05:27 -07:00
const BIND_NAME_REGEXP =
2016-08-05 09:50:49 -07:00
/^(?:(?:(?:(bind-)|(var-)|(let-)|(ref-|#)|(on-)|(bindon-)|(animate-|@))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/ ;
2015-08-25 15:36:02 -07:00
const TEMPLATE_ELEMENT = 'template' ;
const TEMPLATE_ATTR = 'template' ;
const TEMPLATE_ATTR_PREFIX = '*' ;
const CLASS_ATTR = 'class' ;
2016-07-08 10:05:27 -07:00
const PROPERTY_PARTS_SEPARATOR = '.' ;
2015-08-27 16:29:02 -07:00
const ATTRIBUTE_PREFIX = 'attr' ;
const CLASS_PREFIX = 'class' ;
const STYLE_PREFIX = 'style' ;
2016-07-08 10:05:27 -07:00
const TEXT_CSS_SELECTOR = CssSelector . parse ( '*' )[ 0 ];
2015-09-11 13:37:05 -07:00
2015-12-03 15:49:09 -08:00
/**
* Provides an array of {@link TemplateAstVisitor}s which will be used to transform
* parsed templates before compilation is invoked, allowing custom expression syntax
* and other advanced transformations.
*
* This is currently an internal-only feature and not meant for general use.
*/
2016-07-30 19:18:14 -07:00
export const TEMPLATE_TRANSFORMS : any = new OpaqueToken ( 'TemplateTransforms' );
2015-11-19 10:51:16 -08:00
2015-10-07 09:34:21 -07:00
export class TemplateParseError extends ParseError {
2016-04-25 19:52:24 -07:00
constructor ( message : string , span : ParseSourceSpan , level : ParseErrorLevel ) {
super ( span , message , level );
}
2015-10-07 09:34:21 -07:00
}
2016-03-31 12:14:08 -07:00
export class TemplateParseResult {
constructor ( public templateAst? : TemplateAst [], public errors? : ParseError []) {}
}
2015-09-14 15:59:09 -07:00
@Injectable ()
2015-08-25 15:36:02 -07:00
export class TemplateParser {
2016-06-08 16:38:52 -07:00
constructor (
private _exprParser : Parser , private _schemaRegistry : ElementSchemaRegistry ,
private _htmlParser : HtmlParser , private _console : Console ,
@Optional () @Inject ( TEMPLATE_TRANSFORMS ) public transforms : TemplateAstVisitor []) {}
2015-08-25 15:36:02 -07:00
2016-06-08 16:38:52 -07:00
parse (
component : CompileDirectiveMetadata , template : string , directives : CompileDirectiveMetadata [],
2016-07-25 03:02:57 -07:00
pipes : CompilePipeMetadata [], schemas : SchemaMetadata [], templateUrl : string ) : TemplateAst [] {
const result = this . tryParse ( component , template , directives , pipes , schemas , templateUrl );
2016-07-08 10:05:27 -07:00
const warnings = result . errors . filter ( error => error . level === ParseErrorLevel . WARNING );
const errors = result . errors . filter ( error => error . level === ParseErrorLevel . FATAL );
2016-04-25 19:52:24 -07:00
if ( warnings . length > 0 ) {
this . _console . warn ( `Template parse warnings: \ n ${ warnings . join ( '\n' ) } ` );
}
if ( errors . length > 0 ) {
2016-07-08 10:05:27 -07:00
const errorString = errors . join ( '\n' );
2016-03-31 12:14:08 -07:00
throw new BaseException ( `Template parse errors: \ n ${ errorString } ` );
}
2016-06-22 17:25:42 -07:00
2016-03-31 12:14:08 -07:00
return result . templateAst ;
}
2016-06-08 16:38:52 -07:00
tryParse (
component : CompileDirectiveMetadata , template : string , directives : CompileDirectiveMetadata [],
2016-07-25 03:02:57 -07:00
pipes : CompilePipeMetadata [], schemas : SchemaMetadata [],
templateUrl : string ) : TemplateParseResult {
2016-06-22 17:25:42 -07:00
let interpolationConfig : any ;
if ( component . template ) {
interpolationConfig = InterpolationConfig . fromArray ( component . template . interpolation );
}
2016-06-30 14:59:23 -07:00
let htmlAstWithErrors =
this . _htmlParser . parse ( template , templateUrl , true , interpolationConfig );
2016-07-08 10:05:27 -07:00
const errors : ParseError [] = htmlAstWithErrors . errors ;
2016-06-22 17:25:42 -07:00
let result : TemplateAst [];
2016-06-30 14:59:23 -07:00
if ( errors . length == 0 ) {
// Transform ICU messages to angular directives
const expandedHtmlAst = expandNodes ( htmlAstWithErrors . rootNodes );
errors . push (... expandedHtmlAst . errors );
2016-07-21 13:56:58 -07:00
htmlAstWithErrors = new ParseTreeResult ( expandedHtmlAst . nodes , errors );
2016-06-30 14:59:23 -07:00
}
2016-01-06 14:13:44 -08:00
if ( htmlAstWithErrors . rootNodes . length > 0 ) {
2016-07-18 03:50:31 -07:00
const uniqDirectives = removeIdentifierDuplicates ( directives );
const uniqPipes = removeIdentifierDuplicates ( pipes );
2016-07-08 10:05:27 -07:00
const providerViewContext =
2016-06-08 16:38:52 -07:00
new ProviderViewContext ( component , htmlAstWithErrors . rootNodes [ 0 ]. sourceSpan );
2016-07-08 10:05:27 -07:00
const parseVisitor = new TemplateParseVisitor (
2016-07-25 03:02:57 -07:00
providerViewContext , uniqDirectives , uniqPipes , schemas , this . _exprParser ,
this . _schemaRegistry );
2016-01-06 14:13:44 -08:00
2016-07-21 13:56:58 -07:00
result = html . visitAll ( parseVisitor , htmlAstWithErrors . rootNodes , EMPTY_ELEMENT_CONTEXT );
2016-07-08 10:05:27 -07:00
errors . push (... parseVisitor . errors , ... providerViewContext . errors );
2016-01-06 14:13:44 -08:00
} else {
result = [];
}
2016-05-26 23:04:17 +03:00
this . _assertNoReferenceDuplicationOnTemplate ( result , errors );
2015-10-07 09:34:21 -07:00
if ( errors . length > 0 ) {
2016-03-31 12:14:08 -07:00
return new TemplateParseResult ( result , errors );
2015-08-27 16:29:02 -07:00
}
2016-06-30 14:59:23 -07:00
2015-11-19 10:51:16 -08:00
if ( isPresent ( this . transforms )) {
this . transforms . forEach (
( transform : TemplateAstVisitor ) => { result = templateVisitAll ( transform , result ); });
}
2016-06-30 14:59:23 -07:00
2016-04-25 19:52:24 -07:00
return new TemplateParseResult ( result , errors );
2015-08-25 15:36:02 -07:00
}
2016-05-26 23:04:17 +03:00
2016-05-27 09:16:46 -07:00
/** @internal */
2016-06-22 17:25:42 -07:00
_assertNoReferenceDuplicationOnTemplate ( result : TemplateAst [], errors : TemplateParseError []) :
void {
const existingReferences : string [] = [];
result . filter ( element => !! (< any > element ). references )
. forEach ( element => (< any > element ). references . forEach (( reference : ReferenceAst ) => {
2016-06-08 16:38:52 -07:00
const name = reference . name ;
if ( existingReferences . indexOf ( name ) < 0 ) {
existingReferences . push ( name );
} else {
const error = new TemplateParseError (
`Reference "# ${ name } " is defined several times` , reference . sourceSpan ,
ParseErrorLevel . FATAL );
errors . push ( error );
}
}));
2016-05-26 23:04:17 +03:00
}
2015-08-25 15:36:02 -07:00
}
2016-07-21 13:56:58 -07:00
class TemplateParseVisitor implements html . Visitor {
2015-08-25 15:36:02 -07:00
selectorMatcher : SelectorMatcher ;
2015-10-07 09:34:21 -07:00
errors : TemplateParseError [] = [];
2015-09-29 11:11:06 -07:00
directivesIndex = new Map < CompileDirectiveMetadata , number >();
2015-10-07 17:15:12 -07:00
ngContentCount : number = 0 ;
2015-12-02 10:35:51 -08:00
pipesByName : Map < string , CompilePipeMetadata >;
2016-06-20 09:52:41 -07:00
private _interpolationConfig : InterpolationConfig ;
2015-10-07 17:15:12 -07:00
2016-06-08 16:38:52 -07:00
constructor (
public providerViewContext : ProviderViewContext , directives : CompileDirectiveMetadata [],
2016-07-25 03:02:57 -07:00
pipes : CompilePipeMetadata [], private _schemas : SchemaMetadata [], private _exprParser : Parser ,
2016-06-08 16:38:52 -07:00
private _schemaRegistry : ElementSchemaRegistry ) {
2015-08-25 15:36:02 -07:00
this . selectorMatcher = new SelectorMatcher ();
2016-06-22 17:25:42 -07:00
2016-06-20 09:52:41 -07:00
const tempMeta = providerViewContext . component . template ;
2016-06-22 17:25:42 -07:00
2016-06-20 09:52:41 -07:00
if ( isPresent ( tempMeta ) && isPresent ( tempMeta . interpolation )) {
this . _interpolationConfig = {
start : tempMeta.interpolation [ 0 ],
end : tempMeta.interpolation [ 1 ]
};
}
2016-06-22 17:25:42 -07:00
2016-06-08 16:38:52 -07:00
ListWrapper . forEachWithIndex (
directives , ( directive : CompileDirectiveMetadata , index : number ) => {
2016-07-08 10:05:27 -07:00
const selector = CssSelector . parse ( directive . selector );
2016-06-08 16:38:52 -07:00
this . selectorMatcher . addSelectables ( selector , directive );
this . directivesIndex . set ( directive , index );
});
2016-06-22 17:25:42 -07:00
2015-12-02 10:35:51 -08:00
this . pipesByName = new Map < string , CompilePipeMetadata >();
pipes . forEach ( pipe => this . pipesByName . set ( pipe . name , pipe ));
2015-08-25 15:36:02 -07:00
}
2016-06-08 16:38:52 -07:00
private _reportError (
message : string , sourceSpan : ParseSourceSpan ,
level : ParseErrorLevel = ParseErrorLevel . FATAL ) {
2016-04-25 19:52:24 -07:00
this . errors . push ( new TemplateParseError ( message , sourceSpan , level ));
2015-10-07 09:34:21 -07:00
}
2015-08-27 16:29:02 -07:00
2016-07-06 14:06:47 -07:00
private _reportParserErors ( errors : ParserError [], sourceSpan : ParseSourceSpan ) {
2016-08-01 11:24:49 -07:00
for ( const error of errors ) {
2016-07-06 14:06:47 -07:00
this . _reportError ( error . message , sourceSpan );
}
}
2015-10-07 09:34:21 -07:00
private _parseInterpolation ( value : string , sourceSpan : ParseSourceSpan ) : ASTWithSource {
2016-07-08 10:05:27 -07:00
const sourceInfo = sourceSpan . start . toString ();
2015-08-27 16:29:02 -07:00
try {
2016-07-08 10:05:27 -07:00
const ast = this . _exprParser . parseInterpolation ( value , sourceInfo , this . _interpolationConfig );
2016-07-06 14:06:47 -07:00
if ( ast ) this . _reportParserErors ( ast . errors , sourceSpan );
2015-12-02 10:35:51 -08:00
this . _checkPipes ( ast , sourceSpan );
2016-01-06 14:13:44 -08:00
if ( isPresent ( ast ) &&
(< Interpolation > ast . ast ). expressions . length > MAX_INTERPOLATION_VALUES ) {
throw new BaseException (
`Only support at most ${ MAX_INTERPOLATION_VALUES } interpolation values!` );
}
2015-12-02 10:35:51 -08:00
return ast ;
2015-08-27 16:29:02 -07:00
} catch ( e ) {
2015-10-07 09:34:21 -07:00
this . _reportError ( ` ${ e } ` , sourceSpan );
2015-08-27 16:29:02 -07:00
return this . _exprParser . wrapLiteralPrimitive ( 'ERROR' , sourceInfo );
}
}
2015-10-07 09:34:21 -07:00
private _parseAction ( value : string , sourceSpan : ParseSourceSpan ) : ASTWithSource {
2016-07-08 10:05:27 -07:00
const sourceInfo = sourceSpan . start . toString ();
2016-08-01 11:24:49 -07:00
2015-08-27 16:29:02 -07:00
try {
2016-07-08 10:05:27 -07:00
const ast = this . _exprParser . parseAction ( value , sourceInfo , this . _interpolationConfig );
2016-08-01 11:24:49 -07:00
if ( ast ) {
this . _reportParserErors ( ast . errors , sourceSpan );
}
if ( ! ast || ast . ast instanceof EmptyExpr ) {
this . _reportError ( `Empty expressions are not allowed` , sourceSpan );
return this . _exprParser . wrapLiteralPrimitive ( 'ERROR' , sourceInfo );
}
2015-12-02 10:35:51 -08:00
this . _checkPipes ( ast , sourceSpan );
return ast ;
2015-08-27 16:29:02 -07:00
} catch ( e ) {
2015-10-07 09:34:21 -07:00
this . _reportError ( ` ${ e } ` , sourceSpan );
2015-08-27 16:29:02 -07:00
return this . _exprParser . wrapLiteralPrimitive ( 'ERROR' , sourceInfo );
}
}
2015-10-07 09:34:21 -07:00
private _parseBinding ( value : string , sourceSpan : ParseSourceSpan ) : ASTWithSource {
2016-07-08 10:05:27 -07:00
const sourceInfo = sourceSpan . start . toString ();
2015-08-27 16:29:02 -07:00
try {
2016-07-08 10:05:27 -07:00
const ast = this . _exprParser . parseBinding ( value , sourceInfo , this . _interpolationConfig );
2016-07-06 14:06:47 -07:00
if ( ast ) this . _reportParserErors ( ast . errors , sourceSpan );
2015-12-02 10:35:51 -08:00
this . _checkPipes ( ast , sourceSpan );
return ast ;
2015-08-27 16:29:02 -07:00
} catch ( e ) {
2015-10-07 09:34:21 -07:00
this . _reportError ( ` ${ e } ` , sourceSpan );
2015-08-27 16:29:02 -07:00
return this . _exprParser . wrapLiteralPrimitive ( 'ERROR' , sourceInfo );
}
}
2015-10-07 09:34:21 -07:00
private _parseTemplateBindings ( value : string , sourceSpan : ParseSourceSpan ) : TemplateBinding [] {
2016-07-08 10:05:27 -07:00
const sourceInfo = sourceSpan . start . toString ();
2015-08-27 16:29:02 -07:00
try {
2016-07-08 10:05:27 -07:00
const bindingsResult = this . _exprParser . parseTemplateBindings ( value , sourceInfo );
2016-07-06 14:06:47 -07:00
this . _reportParserErors ( bindingsResult . errors , sourceSpan );
2016-04-25 19:52:24 -07:00
bindingsResult . templateBindings . forEach (( binding ) => {
2015-12-02 10:35:51 -08:00
if ( isPresent ( binding . expression )) {
this . _checkPipes ( binding . expression , sourceSpan );
}
});
2016-04-25 19:52:24 -07:00
bindingsResult . warnings . forEach (
( warning ) => { this . _reportError ( warning , sourceSpan , ParseErrorLevel . WARNING ); });
return bindingsResult . templateBindings ;
2015-08-27 16:29:02 -07:00
} catch ( e ) {
2015-10-07 09:34:21 -07:00
this . _reportError ( ` ${ e } ` , sourceSpan );
2015-08-27 16:29:02 -07:00
return [];
}
}
2015-12-02 10:35:51 -08:00
private _checkPipes ( ast : ASTWithSource , sourceSpan : ParseSourceSpan ) {
if ( isPresent ( ast )) {
2016-07-08 10:05:27 -07:00
const collector = new PipeCollector ();
2015-12-02 10:35:51 -08:00
ast . visit ( collector );
collector . pipes . forEach (( pipeName ) => {
if ( ! this . pipesByName . has ( pipeName )) {
this . _reportError ( `The pipe ' ${ pipeName } ' could not be found` , sourceSpan );
}
});
}
}
2016-07-21 13:56:58 -07:00
visitExpansion ( expansion : html.Expansion , context : any ) : any { return null ; }
2016-04-13 16:01:25 -07:00
2016-07-21 13:56:58 -07:00
visitExpansionCase ( expansionCase : html.ExpansionCase , context : any ) : any { return null ; }
2016-04-13 16:01:25 -07:00
2016-07-21 13:56:58 -07:00
visitText ( text : html.Text , parent : ElementContext ) : any {
2016-07-08 10:05:27 -07:00
const ngContentIndex = parent . findNgContentIndex ( TEXT_CSS_SELECTOR );
2016-07-21 13:56:58 -07:00
const expr = this . _parseInterpolation ( text . value , text . sourceSpan );
2015-08-25 15:36:02 -07:00
if ( isPresent ( expr )) {
2016-07-21 13:56:58 -07:00
return new BoundTextAst ( expr , ngContentIndex , text . sourceSpan );
2015-08-25 15:36:02 -07:00
} else {
2016-07-21 13:56:58 -07:00
return new TextAst ( text . value , ngContentIndex , text . sourceSpan );
2015-08-25 15:36:02 -07:00
}
}
2016-07-21 13:56:58 -07:00
visitAttribute ( attribute : html.Attribute , contex : any ) : any {
return new AttrAst ( attribute . name , attribute . value , attribute . sourceSpan );
2015-09-11 13:37:05 -07:00
}
2015-08-25 15:36:02 -07:00
2016-07-21 13:56:58 -07:00
visitComment ( comment : html.Comment , context : any ) : any { return null ; }
2016-03-06 20:21:20 -08:00
2016-07-21 13:56:58 -07:00
visitElement ( element : html.Element , parent : ElementContext ) : any {
2016-07-08 10:05:27 -07:00
const nodeName = element . name ;
const preparsedElement = preparseElement ( element );
2015-09-18 10:33:23 -07:00
if ( preparsedElement . type === PreparsedElementType . SCRIPT ||
2015-10-14 09:39:40 -07:00
preparsedElement . type === PreparsedElementType . STYLE ) {
2015-09-18 10:33:23 -07:00
// Skipping <script> for security reasons
2015-10-14 09:39:40 -07:00
// Skipping <style> as we already processed them
// in the StyleCompiler
return null ;
}
if ( preparsedElement . type === PreparsedElementType . STYLESHEET &&
isStyleUrlResolvable ( preparsedElement . hrefAttr )) {
// Skipping stylesheets with either relative urls or package scheme as we already processed
2015-10-07 09:34:21 -07:00
// them in the StyleCompiler
2015-09-18 10:33:23 -07:00
return null ;
}
2016-07-08 10:05:27 -07:00
const matchableAttrs : string [][] = [];
const elementOrDirectiveProps : BoundElementOrDirectiveProperty [] = [];
const elementOrDirectiveRefs : ElementOrDirectiveRef [] = [];
const elementVars : VariableAst [] = [];
const animationProps : BoundElementPropertyAst [] = [];
const events : BoundEventAst [] = [];
2015-08-25 15:36:02 -07:00
2016-07-08 10:05:27 -07:00
const templateElementOrDirectiveProps : BoundElementOrDirectiveProperty [] = [];
const templateMatchableAttrs : string [][] = [];
const templateElementVars : VariableAst [] = [];
2016-04-25 19:52:24 -07:00
2016-07-08 10:05:27 -07:00
let hasInlineTemplates = false ;
const attrs : AttrAst [] = [];
const lcElName = splitNsName ( nodeName . toLowerCase ())[ 1 ];
const isTemplateElement = lcElName == TEMPLATE_ELEMENT ;
2015-10-07 09:34:21 -07:00
2015-08-25 15:36:02 -07:00
element . attrs . forEach ( attr => {
2016-07-08 10:05:27 -07:00
const hasBinding = this . _parseAttr (
2016-06-08 16:38:52 -07:00
isTemplateElement , attr , matchableAttrs , elementOrDirectiveProps , animationProps , events ,
elementOrDirectiveRefs , elementVars );
2016-07-08 10:05:27 -07:00
const hasTemplateBinding = this . _parseInlineTemplateBinding (
2016-04-25 19:52:24 -07:00
attr , templateMatchableAttrs , templateElementOrDirectiveProps , templateElementVars );
2016-06-22 17:43:27 +02:00
if ( hasTemplateBinding && hasInlineTemplates ) {
this . _reportError (
`Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with *` ,
attr . sourceSpan );
}
2015-08-25 15:36:02 -07:00
if ( ! hasBinding && ! hasTemplateBinding ) {
// don't include the bindings as attributes as well in the AST
2016-07-21 13:56:58 -07:00
attrs . push ( this . visitAttribute ( attr , null ));
2015-12-15 09:39:07 -08:00
matchableAttrs . push ([ attr . name , attr . value ]);
2015-08-25 15:36:02 -07:00
}
if ( hasTemplateBinding ) {
hasInlineTemplates = true ;
}
});
2015-10-07 09:34:21 -07:00
2016-07-08 10:05:27 -07:00
const elementCssSelector = createElementCssSelector ( nodeName , matchableAttrs );
const directiveMetas = this . _parseDirectives ( this . selectorMatcher , elementCssSelector );
const references : ReferenceAst [] = [];
const directiveAsts = this . _createDirectiveAsts (
2016-06-08 16:38:52 -07:00
isTemplateElement , element . name , directiveMetas , elementOrDirectiveProps ,
elementOrDirectiveRefs , element . sourceSpan , references );
2016-07-08 10:05:27 -07:00
const elementProps : BoundElementPropertyAst [] =
2016-06-08 16:38:52 -07:00
this . _createElementPropertyAsts ( element . name , elementOrDirectiveProps , directiveAsts )
. concat ( animationProps );
2016-07-08 10:05:27 -07:00
const isViewRoot = parent . isTemplateElement || hasInlineTemplates ;
const providerContext = new ProviderElementContext (
2016-06-08 16:38:52 -07:00
this . providerViewContext , parent . providerContext , isViewRoot , directiveAsts , attrs ,
references , element . sourceSpan );
2016-07-21 13:56:58 -07:00
const children = html . visitAll (
2016-01-06 14:13:44 -08:00
preparsedElement . nonBindable ? NON_BINDABLE_VISITOR : this , element . children ,
2016-06-08 16:38:52 -07:00
ElementContext . create (
isTemplateElement , directiveAsts ,
isTemplateElement ? parent.providerContext : providerContext ));
2016-01-06 14:13:44 -08:00
providerContext . afterElement ();
2016-03-23 14:15:05 -07:00
// Override the actual selector when the `ngProjectAs` attribute is provided
2016-07-08 10:05:27 -07:00
const projectionSelector = isPresent ( preparsedElement . projectAs ) ?
2016-06-08 16:38:52 -07:00
CssSelector . parse ( preparsedElement . projectAs )[ 0 ] :
elementCssSelector ;
2016-07-08 10:05:27 -07:00
const ngContentIndex = parent . findNgContentIndex ( projectionSelector );
let parsedElement : TemplateAst ;
2016-03-23 14:15:05 -07:00
2015-09-18 10:33:23 -07:00
if ( preparsedElement . type === PreparsedElementType . NG_CONTENT ) {
2015-12-03 14:20:00 -08:00
if ( isPresent ( element . children ) && element . children . length > 0 ) {
this . _reportError (
`<ng-content> element cannot have content. <ng-content> must be immediately followed by </ng-content>` ,
element . sourceSpan );
}
2016-03-23 14:15:05 -07:00
parsedElement = new NgContentAst (
this . ngContentCount ++ , hasInlineTemplates ? null : ngContentIndex , element . sourceSpan );
2015-09-18 10:33:23 -07:00
} else if ( isTemplateElement ) {
2016-01-06 14:13:44 -08:00
this . _assertAllEventsPublishedByDirectives ( directiveAsts , events );
2016-06-08 16:38:52 -07:00
this . _assertNoComponentsNorElementBindingsOnTemplate (
directiveAsts , elementProps , element . sourceSpan );
2016-03-23 14:15:05 -07:00
2016-04-18 13:24:42 -07:00
parsedElement = new EmbeddedTemplateAst (
2016-04-25 19:52:24 -07:00
attrs , events , references , elementVars , providerContext . transformedDirectiveAsts ,
2016-04-18 13:24:42 -07:00
providerContext . transformProviders , providerContext . transformedHasViewContainer , children ,
hasInlineTemplates ? null : ngContentIndex , element . sourceSpan );
2015-08-25 15:36:02 -07:00
} else {
2016-01-06 14:13:44 -08:00
this . _assertOnlyOneComponent ( directiveAsts , element . sourceSpan );
2016-07-08 10:05:27 -07:00
const ngContentIndex =
2016-01-06 14:13:44 -08:00
hasInlineTemplates ? null : parent . findNgContentIndex ( projectionSelector );
parsedElement = new ElementAst (
2016-04-25 19:52:24 -07:00
nodeName , attrs , elementProps , events , references ,
2016-04-18 13:24:42 -07:00
providerContext . transformedDirectiveAsts , providerContext . transformProviders ,
providerContext . transformedHasViewContainer , children ,
2016-01-06 14:13:44 -08:00
hasInlineTemplates ? null : ngContentIndex , element . sourceSpan );
2015-08-25 15:36:02 -07:00
}
if ( hasInlineTemplates ) {
2016-07-08 10:05:27 -07:00
const templateCssSelector =
createElementCssSelector ( TEMPLATE_ELEMENT , templateMatchableAttrs );
const templateDirectiveMetas =
this . _parseDirectives ( this . selectorMatcher , templateCssSelector );
const templateDirectiveAsts = this . _createDirectiveAsts (
2016-06-08 16:38:52 -07:00
true , element . name , templateDirectiveMetas , templateElementOrDirectiveProps , [],
element . sourceSpan , []);
2016-07-08 10:05:27 -07:00
const templateElementProps : BoundElementPropertyAst [] = this . _createElementPropertyAsts (
2016-01-06 14:13:44 -08:00
element . name , templateElementOrDirectiveProps , templateDirectiveAsts );
this . _assertNoComponentsNorElementBindingsOnTemplate (
templateDirectiveAsts , templateElementProps , element . sourceSpan );
2016-07-08 10:05:27 -07:00
const templateProviderContext = new ProviderElementContext (
2016-01-06 14:13:44 -08:00
this . providerViewContext , parent . providerContext , parent . isTemplateElement ,
2016-04-25 19:52:24 -07:00
templateDirectiveAsts , [], [], element . sourceSpan );
2016-01-06 14:13:44 -08:00
templateProviderContext . afterElement ();
2016-03-23 14:15:05 -07:00
2016-06-08 16:38:52 -07:00
parsedElement = new EmbeddedTemplateAst (
[], [], [], templateElementVars , templateProviderContext . transformedDirectiveAsts ,
templateProviderContext . transformProviders ,
templateProviderContext . transformedHasViewContainer , [ parsedElement ], ngContentIndex ,
element . sourceSpan );
2015-08-25 15:36:02 -07:00
}
return parsedElement ;
}
2016-06-08 16:38:52 -07:00
private _parseInlineTemplateBinding (
2016-07-21 13:56:58 -07:00
attr : html.Attribute , targetMatchableAttrs : string [][],
2016-06-08 16:38:52 -07:00
targetProps : BoundElementOrDirectiveProperty [], targetVars : VariableAst []) : boolean {
2016-07-08 10:05:27 -07:00
let templateBindingsSource : string = null ;
2016-07-08 10:09:49 -07:00
if ( this . _normalizeAttributeName ( attr . name ) == TEMPLATE_ATTR ) {
2015-08-25 15:36:02 -07:00
templateBindingsSource = attr . value ;
2015-10-31 13:04:26 -07:00
} else if ( attr . name . startsWith ( TEMPLATE_ATTR_PREFIX )) {
2016-07-08 10:05:27 -07:00
const key = attr . name . substring ( TEMPLATE_ATTR_PREFIX . length ); // remove the star
2016-06-07 09:24:57 -07:00
templateBindingsSource = ( attr . value . length == 0 ) ? key : key + ' ' + attr . value ;
2015-08-25 15:36:02 -07:00
}
if ( isPresent ( templateBindingsSource )) {
2016-07-08 10:05:27 -07:00
const bindings = this . _parseTemplateBindings ( templateBindingsSource , attr . sourceSpan );
for ( let i = 0 ; i < bindings . length ; i ++ ) {
const binding = bindings [ i ];
2015-08-25 15:36:02 -07:00
if ( binding . keyIsVar ) {
2015-11-23 16:02:19 -08:00
targetVars . push ( new VariableAst ( binding . key , binding . name , attr . sourceSpan ));
2015-08-25 15:36:02 -07:00
} else if ( isPresent ( binding . expression )) {
2016-06-08 16:38:52 -07:00
this . _parsePropertyAst (
binding . key , binding . expression , attr . sourceSpan , targetMatchableAttrs , targetProps );
2015-08-25 15:36:02 -07:00
} else {
2015-11-23 16:02:19 -08:00
targetMatchableAttrs . push ([ binding . key , '' ]);
this . _parseLiteralAttr ( binding . key , null , attr . sourceSpan , targetProps );
2015-08-25 15:36:02 -07:00
}
}
return true ;
}
return false ;
}
2016-06-08 16:38:52 -07:00
private _parseAttr (
2016-07-21 13:56:58 -07:00
isTemplateElement : boolean , attr : html.Attribute , targetMatchableAttrs : string [][],
2016-06-08 16:38:52 -07:00
targetProps : BoundElementOrDirectiveProperty [],
targetAnimationProps : BoundElementPropertyAst [], targetEvents : BoundEventAst [],
targetRefs : ElementOrDirectiveRef [], targetVars : VariableAst []) : boolean {
2016-07-08 10:05:27 -07:00
const attrName = this . _normalizeAttributeName ( attr . name );
const attrValue = attr . value ;
2016-08-05 09:50:49 -07:00
const bindParts = attrName . match ( BIND_NAME_REGEXP );
2016-07-08 10:05:27 -07:00
let hasBinding = false ;
2016-08-05 09:50:49 -07:00
if ( bindParts !== null ) {
2015-08-25 15:36:02 -07:00
hasBinding = true ;
if ( isPresent ( bindParts [ 1 ])) { // match: bind-prop
2016-07-07 12:13:52 -07:00
this . _parsePropertyOrAnimation (
bindParts [ 8 ], attrValue , attr . sourceSpan , targetMatchableAttrs , targetProps ,
targetAnimationProps );
2015-08-25 15:36:02 -07:00
2016-04-25 19:52:24 -07:00
} else if ( isPresent ( bindParts [ 2 ])) { // match: var-name / var-name="iden"
2016-07-08 10:05:27 -07:00
const identifier = bindParts [ 8 ];
2016-04-25 19:52:24 -07:00
if ( isTemplateElement ) {
2016-06-08 16:38:52 -07:00
this . _reportError (
`"var-" on <template> elements is deprecated. Use "let-" instead!` , attr . sourceSpan ,
ParseErrorLevel . WARNING );
2016-04-25 19:52:24 -07:00
this . _parseVariable ( identifier , attrValue , attr . sourceSpan , targetVars );
} else {
2016-06-08 16:38:52 -07:00
this . _reportError (
`"var-" on non <template> elements is deprecated. Use "ref-" instead!` ,
attr . sourceSpan , ParseErrorLevel . WARNING );
2016-04-25 19:52:24 -07:00
this . _parseReference ( identifier , attrValue , attr . sourceSpan , targetRefs );
}
} else if ( isPresent ( bindParts [ 3 ])) { // match: let-name
if ( isTemplateElement ) {
2016-07-08 10:05:27 -07:00
const identifier = bindParts [ 8 ];
2016-04-25 19:52:24 -07:00
this . _parseVariable ( identifier , attrValue , attr . sourceSpan , targetVars );
} else {
this . _reportError ( `"let-" is only supported on template elements.` , attr . sourceSpan );
}
} else if ( isPresent ( bindParts [ 4 ])) { // match: ref- / #iden
2016-07-08 10:05:27 -07:00
const identifier = bindParts [ 8 ];
2016-04-25 19:52:24 -07:00
this . _parseReference ( identifier , attrValue , attr . sourceSpan , targetRefs );
} else if ( isPresent ( bindParts [ 5 ])) { // match: on-event
2016-06-08 16:38:52 -07:00
this . _parseEvent (
bindParts [ 8 ], attrValue , attr . sourceSpan , targetMatchableAttrs , targetEvents );
2016-04-25 19:52:24 -07:00
} else if ( isPresent ( bindParts [ 6 ])) { // match: bindon-prop
2016-07-07 12:13:52 -07:00
this . _parsePropertyOrAnimation (
bindParts [ 8 ], attrValue , attr . sourceSpan , targetMatchableAttrs , targetProps ,
targetAnimationProps );
2016-06-08 16:38:52 -07:00
this . _parseAssignmentEvent (
bindParts [ 8 ], attrValue , attr . sourceSpan , targetMatchableAttrs , targetEvents );
2016-04-25 19:52:24 -07:00
2016-05-25 12:46:22 -07:00
} else if ( isPresent ( bindParts [ 7 ])) { // match: animate-name
2016-07-07 12:13:52 -07:00
if ( attrName [ 0 ] == '@' && isPresent ( attrValue ) && attrValue . length > 0 ) {
this . _reportError (
2016-08-08 17:08:53 -07:00
`Assigning animation triggers via @prop="exp" attributes with an expression is deprecated. Use property bindings (e.g. [@prop]="exp") instead!` ,
2016-07-07 12:13:52 -07:00
attr . sourceSpan , ParseErrorLevel . WARNING );
}
2016-06-08 16:38:52 -07:00
this . _parseAnimation (
bindParts [ 8 ], attrValue , attr . sourceSpan , targetMatchableAttrs , targetAnimationProps );
2016-05-25 12:46:22 -07:00
} else if ( isPresent ( bindParts [ 9 ])) { // match: [(expr)]
2016-07-07 12:13:52 -07:00
this . _parsePropertyOrAnimation (
bindParts [ 9 ], attrValue , attr . sourceSpan , targetMatchableAttrs , targetProps ,
targetAnimationProps );
2016-06-08 16:38:52 -07:00
this . _parseAssignmentEvent (
bindParts [ 9 ], attrValue , attr . sourceSpan , targetMatchableAttrs , targetEvents );
2016-04-25 19:52:24 -07:00
2016-05-25 12:46:22 -07:00
} else if ( isPresent ( bindParts [ 10 ])) { // match: [expr]
2016-07-07 12:13:52 -07:00
this . _parsePropertyOrAnimation (
bindParts [ 10 ], attrValue , attr . sourceSpan , targetMatchableAttrs , targetProps ,
targetAnimationProps );
2016-05-25 12:46:22 -07:00
} else if ( isPresent ( bindParts [ 11 ])) { // match: (event)
2016-06-08 16:38:52 -07:00
this . _parseEvent (
bindParts [ 11 ], attrValue , attr . sourceSpan , targetMatchableAttrs , targetEvents );
2015-08-25 15:36:02 -07:00
}
} else {
2016-06-08 16:38:52 -07:00
hasBinding = this . _parsePropertyInterpolation (
attrName , attrValue , attr . sourceSpan , targetMatchableAttrs , targetProps );
2015-08-27 16:29:02 -07:00
}
if ( ! hasBinding ) {
2015-10-07 09:34:21 -07:00
this . _parseLiteralAttr ( attrName , attrValue , attr . sourceSpan , targetProps );
2015-08-25 15:36:02 -07:00
}
return hasBinding ;
}
private _normalizeAttributeName ( attrName : string ) : string {
2015-11-10 15:56:25 -08:00
return attrName . toLowerCase (). startsWith ( 'data-' ) ? attrName . substring ( 5 ) : attrName ;
2015-08-25 15:36:02 -07:00
}
2016-06-08 16:38:52 -07:00
private _parseVariable (
identifier : string , value : string , sourceSpan : ParseSourceSpan , targetVars : VariableAst []) {
2015-11-23 16:02:19 -08:00
if ( identifier . indexOf ( '-' ) > - 1 ) {
this . _reportError ( `"-" is not allowed in variable names` , sourceSpan );
}
2016-05-26 23:04:17 +03:00
2015-11-23 16:02:19 -08:00
targetVars . push ( new VariableAst ( identifier , value , sourceSpan ));
2015-08-25 15:36:02 -07:00
}
2016-06-08 16:38:52 -07:00
private _parseReference (
identifier : string , value : string , sourceSpan : ParseSourceSpan ,
targetRefs : ElementOrDirectiveRef []) {
2016-04-25 19:52:24 -07:00
if ( identifier . indexOf ( '-' ) > - 1 ) {
this . _reportError ( `"-" is not allowed in reference names` , sourceSpan );
}
2016-05-26 23:04:17 +03:00
2016-04-25 19:52:24 -07:00
targetRefs . push ( new ElementOrDirectiveRef ( identifier , value , sourceSpan ));
}
2016-07-07 12:13:52 -07:00
private _parsePropertyOrAnimation (
2016-06-08 16:38:52 -07:00
name : string , expression : string , sourceSpan : ParseSourceSpan ,
2016-07-07 12:13:52 -07:00
targetMatchableAttrs : string [][], targetProps : BoundElementOrDirectiveProperty [],
targetAnimationProps : BoundElementPropertyAst []) {
if ( name [ 0 ] == '@' ) {
this . _parseAnimation (
name . substr ( 1 ), expression , sourceSpan , targetMatchableAttrs , targetAnimationProps );
} else {
this . _parsePropertyAst (
name , this . _parseBinding ( expression , sourceSpan ), sourceSpan , targetMatchableAttrs ,
targetProps );
}
2015-08-25 15:36:02 -07:00
}
2016-06-08 16:38:52 -07:00
private _parseAnimation (
name : string , expression : string , sourceSpan : ParseSourceSpan ,
targetMatchableAttrs : string [][], targetAnimationProps : BoundElementPropertyAst []) {
2016-06-21 13:56:25 -07:00
// This will occur when a @trigger is not paired with an expression.
// For animations it is valid to not have an expression since */void
// states will be applied by angular when the element is attached/detached
if ( ! isPresent ( expression ) || expression . length == 0 ) {
expression = 'null' ;
}
2016-07-08 10:05:27 -07:00
const ast = this . _parseBinding ( expression , sourceSpan );
2016-05-25 12:46:22 -07:00
targetMatchableAttrs . push ([ name , ast . source ]);
2016-06-08 16:38:52 -07:00
targetAnimationProps . push ( new BoundElementPropertyAst (
name , PropertyBindingType . Animation , SecurityContext . NONE , ast , null , sourceSpan ));
2016-05-25 12:46:22 -07:00
}
2016-06-08 16:38:52 -07:00
private _parsePropertyInterpolation (
name : string , value : string , sourceSpan : ParseSourceSpan , targetMatchableAttrs : string [][],
targetProps : BoundElementOrDirectiveProperty []) : boolean {
2016-07-08 10:05:27 -07:00
const expr = this . _parseInterpolation ( value , sourceSpan );
2015-08-25 15:36:02 -07:00
if ( isPresent ( expr )) {
2015-10-07 09:34:21 -07:00
this . _parsePropertyAst ( name , expr , sourceSpan , targetMatchableAttrs , targetProps );
2015-08-25 15:36:02 -07:00
return true ;
}
return false ;
}
2016-06-08 16:38:52 -07:00
private _parsePropertyAst (
name : string , ast : ASTWithSource , sourceSpan : ParseSourceSpan ,
targetMatchableAttrs : string [][], targetProps : BoundElementOrDirectiveProperty []) {
2015-08-27 16:29:02 -07:00
targetMatchableAttrs . push ([ name , ast . source ]);
2015-10-07 09:34:21 -07:00
targetProps . push ( new BoundElementOrDirectiveProperty ( name , ast , false , sourceSpan ));
2015-08-25 15:36:02 -07:00
}
2016-06-08 16:38:52 -07:00
private _parseAssignmentEvent (
name : string , expression : string , sourceSpan : ParseSourceSpan ,
targetMatchableAttrs : string [][], targetEvents : BoundEventAst []) {
this . _parseEvent (
` ${ name } Change` , ` ${ expression } =$event` , sourceSpan , targetMatchableAttrs , targetEvents );
2015-08-25 15:36:02 -07:00
}
2016-06-08 16:38:52 -07:00
private _parseEvent (
name : string , expression : string , sourceSpan : ParseSourceSpan ,
targetMatchableAttrs : string [][], targetEvents : BoundEventAst []) {
2015-08-27 16:29:02 -07:00
// long format: 'target: eventName'
2016-07-08 10:05:27 -07:00
const parts = splitAtColon ( name , [ null , name ]);
const target = parts [ 0 ];
const eventName = parts [ 1 ];
const ast = this . _parseAction ( expression , sourceSpan );
2016-02-03 11:35:42 -08:00
targetMatchableAttrs . push ([ name , ast . source ]);
targetEvents . push ( new BoundEventAst ( eventName , target , ast , sourceSpan ));
2015-08-25 15:36:02 -07:00
// Don't detect directives for event names for now,
// so don't add the event name to the matchableAttrs
}
2016-06-08 16:38:52 -07:00
private _parseLiteralAttr (
name : string , value : string , sourceSpan : ParseSourceSpan ,
targetProps : BoundElementOrDirectiveProperty []) {
2015-08-27 16:29:02 -07:00
targetProps . push ( new BoundElementOrDirectiveProperty (
2015-11-23 16:02:19 -08:00
name , this . _exprParser . wrapLiteralPrimitive ( value , '' ), true , sourceSpan ));
2015-08-27 16:29:02 -07:00
}
2016-06-08 16:38:52 -07:00
private _parseDirectives ( selectorMatcher : SelectorMatcher , elementCssSelector : CssSelector ) :
CompileDirectiveMetadata [] {
2015-08-25 15:36:02 -07:00
// Need to sort the directives so that we get consistent results throughout,
// as selectorMatcher uses Maps inside.
2016-04-25 19:52:24 -07:00
// Also dedupe directives as they might match more than one time!
2016-07-08 10:05:27 -07:00
const directives = ListWrapper . createFixedSize ( this . directivesIndex . size );
2016-04-25 19:52:24 -07:00
selectorMatcher . match ( elementCssSelector , ( selector , directive ) => {
directives [ this . directivesIndex . get ( directive )] = directive ;
});
return directives . filter ( dir => isPresent ( dir ));
2015-08-25 15:36:02 -07:00
}
2015-08-27 16:29:02 -07:00
2016-06-08 16:38:52 -07:00
private _createDirectiveAsts (
isTemplateElement : boolean , elementName : string , directives : CompileDirectiveMetadata [],
props : BoundElementOrDirectiveProperty [], elementOrDirectiveRefs : ElementOrDirectiveRef [],
2016-07-28 02:27:07 -07:00
elementSourceSpan : ParseSourceSpan , targetReferences : ReferenceAst []) : DirectiveAst [] {
2016-07-08 10:05:27 -07:00
const matchedReferences = new Set < string >();
let component : CompileDirectiveMetadata = null ;
const directiveAsts = directives . map (( directive : CompileDirectiveMetadata ) => {
2016-07-28 02:27:07 -07:00
const sourceSpan = new ParseSourceSpan (
elementSourceSpan . start , elementSourceSpan . end , `Directive ${ directive . type . name } ` );
2016-04-25 19:52:24 -07:00
if ( directive . isComponent ) {
component = directive ;
}
2016-07-08 10:05:27 -07:00
const hostProperties : BoundElementPropertyAst [] = [];
const hostEvents : BoundEventAst [] = [];
const directiveProperties : BoundDirectivePropertyAst [] = [];
2016-06-08 16:38:52 -07:00
this . _createDirectiveHostPropertyAsts (
elementName , directive . hostProperties , sourceSpan , hostProperties );
2015-10-07 09:34:21 -07:00
this . _createDirectiveHostEventAsts ( directive . hostListeners , sourceSpan , hostEvents );
2015-09-30 20:59:23 -07:00
this . _createDirectivePropertyAsts ( directive . inputs , props , directiveProperties );
2016-04-25 19:52:24 -07:00
elementOrDirectiveRefs . forEach (( elOrDirRef ) => {
if (( elOrDirRef . value . length === 0 && directive . isComponent ) ||
( directive . exportAs == elOrDirRef . value )) {
2016-06-08 16:38:52 -07:00
targetReferences . push ( new ReferenceAst (
elOrDirRef . name , identifierToken ( directive . type ), elOrDirRef . sourceSpan ));
2016-04-25 19:52:24 -07:00
matchedReferences . add ( elOrDirRef . name );
2015-09-18 10:33:23 -07:00
}
});
2016-06-08 16:38:52 -07:00
return new DirectiveAst (
directive , directiveProperties , hostProperties , hostEvents , sourceSpan );
2015-08-27 16:29:02 -07:00
});
2016-04-25 19:52:24 -07:00
elementOrDirectiveRefs . forEach (( elOrDirRef ) => {
if ( elOrDirRef . value . length > 0 ) {
if ( ! SetWrapper . has ( matchedReferences , elOrDirRef . name )) {
2016-06-08 16:38:52 -07:00
this . _reportError (
`There is no directive with "exportAs" set to " ${ elOrDirRef . value } "` ,
elOrDirRef . sourceSpan );
2016-07-08 10:05:27 -07:00
}
2016-04-25 19:52:24 -07:00
} else if ( isBlank ( component )) {
2016-07-08 10:05:27 -07:00
let refToken : CompileTokenMetadata = null ;
2016-04-25 19:52:24 -07:00
if ( isTemplateElement ) {
refToken = identifierToken ( Identifiers . TemplateRef );
}
targetReferences . push ( new ReferenceAst ( elOrDirRef . name , refToken , elOrDirRef . sourceSpan ));
2015-09-18 10:33:23 -07:00
}
2016-04-29 16:04:08 -07:00
}); // fix syntax highlighting issue: `
2015-09-18 10:33:23 -07:00
return directiveAsts ;
2015-08-27 16:29:02 -07:00
}
2016-06-08 16:38:52 -07:00
private _createDirectiveHostPropertyAsts (
elementName : string , hostProps : {[ key : string ] : string }, sourceSpan : ParseSourceSpan ,
targetPropertyAsts : BoundElementPropertyAst []) {
2015-08-27 16:29:02 -07:00
if ( isPresent ( hostProps )) {
2016-02-19 11:49:31 -08:00
StringMapWrapper . forEach ( hostProps , ( expression : string , propName : string ) => {
2016-07-08 10:05:27 -07:00
const exprAst = this . _parseBinding ( expression , sourceSpan );
2015-08-27 16:29:02 -07:00
targetPropertyAsts . push (
2015-10-07 09:34:21 -07:00
this . _createElementPropertyAst ( elementName , propName , exprAst , sourceSpan ));
2015-08-27 16:29:02 -07:00
});
}
}
2016-06-08 16:38:52 -07:00
private _createDirectiveHostEventAsts (
hostListeners : {[ key : string ] : string }, sourceSpan : ParseSourceSpan ,
targetEventAsts : BoundEventAst []) {
2015-08-27 16:29:02 -07:00
if ( isPresent ( hostListeners )) {
2016-02-19 11:49:31 -08:00
StringMapWrapper . forEach ( hostListeners , ( expression : string , propName : string ) => {
2015-10-07 09:34:21 -07:00
this . _parseEvent ( propName , expression , sourceSpan , [], targetEventAsts );
2015-08-27 16:29:02 -07:00
});
}
}
2016-06-08 16:38:52 -07:00
private _createDirectivePropertyAsts (
directiveProperties : {[ key : string ] : string }, boundProps : BoundElementOrDirectiveProperty [],
targetBoundDirectiveProps : BoundDirectivePropertyAst []) {
2015-08-27 16:29:02 -07:00
if ( isPresent ( directiveProperties )) {
2016-07-08 10:05:27 -07:00
const boundPropsByName = new Map < string , BoundElementOrDirectiveProperty >();
2015-09-18 10:33:23 -07:00
boundProps . forEach ( boundProp => {
2016-07-08 10:05:27 -07:00
const prevValue = boundPropsByName . get ( boundProp . name );
2015-09-18 10:33:23 -07:00
if ( isBlank ( prevValue ) || prevValue . isLiteral ) {
2015-11-23 16:02:19 -08:00
// give [a]="b" a higher precedence than a="b" on the same element
boundPropsByName . set ( boundProp . name , boundProp );
2015-09-18 10:33:23 -07:00
}
});
2015-08-27 16:29:02 -07:00
2015-09-18 10:33:23 -07:00
StringMapWrapper . forEach ( directiveProperties , ( elProp : string , dirProp : string ) => {
2016-07-08 10:05:27 -07:00
const boundProp = boundPropsByName . get ( elProp );
2015-08-27 16:29:02 -07:00
// Bindings are optional, so this binding only needs to be set up if an expression is given.
if ( isPresent ( boundProp )) {
targetBoundDirectiveProps . push ( new BoundDirectivePropertyAst (
2015-10-07 09:34:21 -07:00
dirProp , boundProp . name , boundProp . expression , boundProp . sourceSpan ));
2015-08-27 16:29:02 -07:00
}
});
}
}
2016-06-08 16:38:52 -07:00
private _createElementPropertyAsts (
elementName : string , props : BoundElementOrDirectiveProperty [],
directives : DirectiveAst []) : BoundElementPropertyAst [] {
2016-07-08 10:05:27 -07:00
const boundElementProps : BoundElementPropertyAst [] = [];
const boundDirectivePropsIndex = new Map < string , BoundDirectivePropertyAst >();
2015-08-27 16:29:02 -07:00
directives . forEach (( directive : DirectiveAst ) => {
2015-09-30 20:59:23 -07:00
directive . inputs . forEach (( prop : BoundDirectivePropertyAst ) => {
2015-08-27 16:29:02 -07:00
boundDirectivePropsIndex . set ( prop . templateName , prop );
});
});
props . forEach (( prop : BoundElementOrDirectiveProperty ) => {
if ( ! prop . isLiteral && isBlank ( boundDirectivePropsIndex . get ( prop . name ))) {
2016-06-08 16:38:52 -07:00
boundElementProps . push ( this . _createElementPropertyAst (
elementName , prop . name , prop . expression , prop . sourceSpan ));
2015-08-27 16:29:02 -07:00
}
});
return boundElementProps ;
}
2016-06-08 16:38:52 -07:00
private _createElementPropertyAst (
elementName : string , name : string , ast : AST ,
sourceSpan : ParseSourceSpan ) : BoundElementPropertyAst {
2016-07-08 10:05:27 -07:00
let unit : string = null ;
let bindingType : PropertyBindingType ;
let boundPropertyName : string ;
const parts = name . split ( PROPERTY_PARTS_SEPARATOR );
2016-04-29 16:04:08 -07:00
let securityContext : SecurityContext ;
2015-08-27 16:29:02 -07:00
if ( parts . length === 1 ) {
2016-07-08 17:11:12 -07:00
var partValue = parts [ 0 ];
if ( partValue [ 0 ] == '@' ) {
boundPropertyName = partValue . substr ( 1 );
bindingType = PropertyBindingType . Animation ;
securityContext = SecurityContext . NONE ;
2016-08-08 17:08:53 -07:00
// DEPRECATED: remove this if statement post RC5
if ( boundPropertyName [ 0 ] == '@' ) {
this . _reportError (
`Assigning animation triggers within host data as attributes such as "@prop": "exp" is deprecated. Use host bindings (e.g. "[@prop]": "exp") instead!` ,
sourceSpan , ParseErrorLevel . WARNING );
boundPropertyName = boundPropertyName . substr ( 1 );
}
2016-07-08 17:11:12 -07:00
} else {
boundPropertyName = this . _schemaRegistry . getMappedPropName ( partValue );
securityContext = this . _schemaRegistry . securityContext ( elementName , boundPropertyName );
bindingType = PropertyBindingType . Property ;
2016-07-25 03:02:57 -07:00
if ( ! this . _schemaRegistry . hasProperty ( elementName , boundPropertyName , this . _schemas )) {
let errorMsg =
2016-07-29 20:16:38 -07:00
`Can't bind to ' ${ boundPropertyName } ' since it isn't a known property of ' ${ elementName } '.` ;
2016-07-25 03:02:57 -07:00
if ( elementName . indexOf ( '-' ) !== - 1 ) {
errorMsg +=
2016-07-29 20:16:38 -07:00
` \ n1. If ' ${ elementName } ' is an Angular component and it has ' ${ boundPropertyName } ' input, then verify that it is part of this module.` +
` \ n2. If ' ${ elementName } ' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schema' of this component to suppress this message. \ n` ;
2016-07-25 03:02:57 -07:00
}
this . _reportError ( errorMsg , sourceSpan );
2016-07-08 17:11:12 -07:00
}
2015-08-27 16:29:02 -07:00
}
} else {
2015-11-23 16:02:19 -08:00
if ( parts [ 0 ] == ATTRIBUTE_PREFIX ) {
boundPropertyName = parts [ 1 ];
2016-04-29 16:04:08 -07:00
if ( boundPropertyName . toLowerCase (). startsWith ( 'on' )) {
this . _reportError (
`Binding to event attribute ' ${ boundPropertyName } ' is disallowed ` +
`for security reasons, please use ( ${ boundPropertyName . slice ( 2 ) } )=...` ,
sourceSpan );
}
// NB: For security purposes, use the mapped property name, not the attribute name.
securityContext = this . _schemaRegistry . securityContext (
elementName , this . _schemaRegistry . getMappedPropName ( boundPropertyName ));
2016-01-08 12:01:29 -08:00
let nsSeparatorIdx = boundPropertyName . indexOf ( ':' );
if ( nsSeparatorIdx > - 1 ) {
let ns = boundPropertyName . substring ( 0 , nsSeparatorIdx );
let name = boundPropertyName . substring ( nsSeparatorIdx + 1 );
boundPropertyName = mergeNsAndName ( ns , name );
}
2016-04-29 16:04:08 -07:00
2015-11-10 15:56:25 -08:00
bindingType = PropertyBindingType . Attribute ;
2015-11-23 16:02:19 -08:00
} else if ( parts [ 0 ] == CLASS_PREFIX ) {
2015-11-10 15:56:25 -08:00
boundPropertyName = parts [ 1 ];
bindingType = PropertyBindingType . Class ;
2016-04-29 16:04:08 -07:00
securityContext = SecurityContext . NONE ;
2015-11-23 16:02:19 -08:00
} else if ( parts [ 0 ] == STYLE_PREFIX ) {
2015-11-10 15:56:25 -08:00
unit = parts . length > 2 ? parts [ 2 ] : null ;
2015-11-23 16:02:19 -08:00
boundPropertyName = parts [ 1 ];
2015-11-10 15:56:25 -08:00
bindingType = PropertyBindingType . Style ;
2016-04-29 16:04:08 -07:00
securityContext = SecurityContext . STYLE ;
2015-11-10 15:56:25 -08:00
} else {
2015-11-23 16:02:19 -08:00
this . _reportError ( `Invalid property name ' ${ name } '` , sourceSpan );
2015-11-10 15:56:25 -08:00
bindingType = null ;
2016-04-29 16:04:08 -07:00
securityContext = null ;
2015-11-10 15:56:25 -08:00
}
2015-08-27 16:29:02 -07:00
}
2015-11-10 15:56:25 -08:00
2016-06-08 16:38:52 -07:00
return new BoundElementPropertyAst (
boundPropertyName , bindingType , securityContext , ast , unit , sourceSpan );
2015-08-27 16:29:02 -07:00
}
private _findComponentDirectiveNames ( directives : DirectiveAst []) : string [] {
2016-07-08 10:05:27 -07:00
const componentTypeNames : string [] = [];
2015-08-27 16:29:02 -07:00
directives . forEach ( directive => {
2016-07-08 10:05:27 -07:00
const typeName = directive . directive . type . name ;
2015-08-27 16:29:02 -07:00
if ( directive . directive . isComponent ) {
componentTypeNames . push ( typeName );
}
});
return componentTypeNames ;
}
2015-10-07 09:34:21 -07:00
private _assertOnlyOneComponent ( directives : DirectiveAst [], sourceSpan : ParseSourceSpan ) {
2016-07-08 10:05:27 -07:00
const componentTypeNames = this . _findComponentDirectiveNames ( directives );
2015-08-27 16:29:02 -07:00
if ( componentTypeNames . length > 1 ) {
2015-10-07 09:34:21 -07:00
this . _reportError ( `More than one component: ${ componentTypeNames . join ( ',' ) } ` , sourceSpan );
2015-08-27 16:29:02 -07:00
}
}
2016-06-08 16:38:52 -07:00
private _assertNoComponentsNorElementBindingsOnTemplate (
directives : DirectiveAst [], elementProps : BoundElementPropertyAst [],
sourceSpan : ParseSourceSpan ) {
2016-07-08 10:05:27 -07:00
const componentTypeNames : string [] = this . _findComponentDirectiveNames ( directives );
2015-08-27 16:29:02 -07:00
if ( componentTypeNames . length > 0 ) {
2016-06-08 16:38:52 -07:00
this . _reportError (
`Components on an embedded template: ${ componentTypeNames . join ( ',' ) } ` , sourceSpan );
2015-08-27 16:29:02 -07:00
}
elementProps . forEach ( prop => {
this . _reportError (
2016-06-23 17:51:39 +02:00
`Property binding ${ prop . name } not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "directives" section.` ,
2015-10-07 09:34:21 -07:00
sourceSpan );
2015-08-27 16:29:02 -07:00
});
2015-10-13 17:43:15 -07:00
}
2016-06-08 16:38:52 -07:00
private _assertAllEventsPublishedByDirectives (
directives : DirectiveAst [], events : BoundEventAst []) {
2016-07-08 10:05:27 -07:00
const allDirectiveEvents = new Set < string >();
2015-10-13 17:43:15 -07:00
directives . forEach ( directive => {
2016-06-22 17:43:27 +02:00
StringMapWrapper . forEach ( directive . directive . outputs , ( eventName : string ) => {
allDirectiveEvents . add ( eventName );
});
2015-10-13 17:43:15 -07:00
});
2015-08-27 16:29:02 -07:00
events . forEach ( event => {
2015-10-13 17:43:15 -07:00
if ( isPresent ( event . target ) || ! SetWrapper . has ( allDirectiveEvents , event . name )) {
this . _reportError (
2016-06-23 17:51:39 +02:00
`Event binding ${ event . fullName } not emitted by any directive on an embedded template. Make sure that the event name is spelled correctly and all directives are listed in the "directives" section.` ,
2015-10-07 09:34:21 -07:00
event . sourceSpan );
2015-10-13 17:43:15 -07:00
}
2015-08-27 16:29:02 -07:00
});
}
}
2016-07-21 13:56:58 -07:00
class NonBindableVisitor implements html . Visitor {
visitElement ( ast : html.Element , parent : ElementContext ) : ElementAst {
2016-07-08 10:05:27 -07:00
const preparsedElement = preparseElement ( ast );
2015-09-18 10:33:23 -07:00
if ( preparsedElement . type === PreparsedElementType . SCRIPT ||
preparsedElement . type === PreparsedElementType . STYLE ||
preparsedElement . type === PreparsedElementType . STYLESHEET ) {
// Skipping <script> for security reasons
// Skipping <style> and stylesheets as we already processed them
// in the StyleCompiler
return null ;
}
2016-07-08 10:05:27 -07:00
const attrNameAndValues = ast . attrs . map ( attrAst => [ attrAst . name , attrAst . value ]);
const selector = createElementCssSelector ( ast . name , attrNameAndValues );
const ngContentIndex = parent . findNgContentIndex ( selector );
2016-07-21 13:56:58 -07:00
const children = html . visitAll ( this , ast . children , EMPTY_ELEMENT_CONTEXT );
2016-06-08 16:38:52 -07:00
return new ElementAst (
2016-07-21 13:56:58 -07:00
ast . name , html . visitAll ( this , ast . attrs ), [], [], [], [], [], false , children ,
2016-06-08 16:38:52 -07:00
ngContentIndex , ast . sourceSpan );
2015-09-18 10:33:23 -07:00
}
2016-07-21 13:56:58 -07:00
visitComment ( comment : html.Comment , context : any ) : any { return null ; }
visitAttribute ( attribute : html.Attribute , context : any ) : AttrAst {
return new AttrAst ( attribute . name , attribute . value , attribute . sourceSpan );
2015-09-18 10:33:23 -07:00
}
2016-07-21 13:56:58 -07:00
visitText ( text : html.Text , parent : ElementContext ) : TextAst {
2016-07-08 10:05:27 -07:00
const ngContentIndex = parent . findNgContentIndex ( TEXT_CSS_SELECTOR );
2016-07-21 13:56:58 -07:00
return new TextAst ( text . value , ngContentIndex , text . sourceSpan );
2015-09-18 10:33:23 -07:00
}
2016-07-21 13:56:58 -07:00
visitExpansion ( expansion : html.Expansion , context : any ) : any { return expansion ; }
visitExpansionCase ( expansionCase : html.ExpansionCase , context : any ) : any { return expansionCase ; }
2015-09-18 10:33:23 -07:00
}
2015-08-27 16:29:02 -07:00
class BoundElementOrDirectiveProperty {
2016-06-08 16:38:52 -07:00
constructor (
public name : string , public expression : AST , public isLiteral : boolean ,
public sourceSpan : ParseSourceSpan ) {}
2015-08-27 16:29:02 -07:00
}
2016-04-25 19:52:24 -07:00
class ElementOrDirectiveRef {
constructor ( public name : string , public value : string , public sourceSpan : ParseSourceSpan ) {}
}
2015-08-25 15:36:02 -07:00
export function splitClasses ( classAttrValue : string ) : string [] {
2016-07-21 13:56:58 -07:00
return classAttrValue . trim (). split ( /\s+/g );
2015-08-25 15:36:02 -07:00
}
2015-08-27 16:29:02 -07:00
2016-01-06 14:13:44 -08:00
class ElementContext {
2016-06-08 16:38:52 -07:00
static create (
isTemplateElement : boolean , directives : DirectiveAst [],
providerContext : ProviderElementContext ) : ElementContext {
2016-07-08 10:05:27 -07:00
const matcher = new SelectorMatcher ();
let wildcardNgContentIndex : number = null ;
const component = directives . find ( directive => directive . directive . isComponent );
2016-04-29 17:07:28 -07:00
if ( isPresent ( component )) {
2016-07-08 10:05:27 -07:00
const ngContentSelectors = component . directive . template . ngContentSelectors ;
for ( let i = 0 ; i < ngContentSelectors . length ; i ++ ) {
const selector = ngContentSelectors [ i ];
2016-07-21 13:56:58 -07:00
if ( selector === '*' ) {
2016-01-06 14:13:44 -08:00
wildcardNgContentIndex = i ;
} else {
matcher . addSelectables ( CssSelector . parse ( ngContentSelectors [ i ]), i );
}
2015-09-11 13:37:05 -07:00
}
}
2016-01-06 14:13:44 -08:00
return new ElementContext ( isTemplateElement , matcher , wildcardNgContentIndex , providerContext );
2015-09-11 13:37:05 -07:00
}
2016-06-08 16:38:52 -07:00
constructor (
public isTemplateElement : boolean , private _ngContentIndexMatcher : SelectorMatcher ,
private _wildcardNgContentIndex : number , public providerContext : ProviderElementContext ) {}
2015-09-11 13:37:05 -07:00
findNgContentIndex ( selector : CssSelector ) : number {
2016-07-08 10:05:27 -07:00
const ngContentIndices : number [] = [];
2016-01-06 14:13:44 -08:00
this . _ngContentIndexMatcher . match (
2015-09-11 13:37:05 -07:00
selector , ( selector , ngContentIndex ) => { ngContentIndices . push ( ngContentIndex ); });
ListWrapper . sort ( ngContentIndices );
2016-01-06 14:13:44 -08:00
if ( isPresent ( this . _wildcardNgContentIndex )) {
ngContentIndices . push ( this . _wildcardNgContentIndex );
2015-10-29 16:23:13 -07:00
}
2015-09-11 13:37:05 -07:00
return ngContentIndices . length > 0 ? ngContentIndices [ 0 ] : null ;
}
}
2015-09-18 10:33:23 -07:00
function createElementCssSelector ( elementName : string , matchableAttrs : string [][]) : CssSelector {
2016-07-08 10:05:27 -07:00
const cssSelector = new CssSelector ();
2015-12-09 09:32:15 -08:00
let elNameNoNs = splitNsName ( elementName )[ 1 ];
cssSelector . setElement ( elNameNoNs );
2015-09-18 10:33:23 -07:00
2016-07-08 10:05:27 -07:00
for ( let i = 0 ; i < matchableAttrs . length ; i ++ ) {
2015-12-09 09:32:15 -08:00
let attrName = matchableAttrs [ i ][ 0 ];
let attrNameNoNs = splitNsName ( attrName )[ 1 ];
let attrValue = matchableAttrs [ i ][ 1 ];
cssSelector . addAttribute ( attrNameNoNs , attrValue );
if ( attrName . toLowerCase () == CLASS_ATTR ) {
2016-07-08 10:05:27 -07:00
const classes = splitClasses ( attrValue );
2015-09-18 10:33:23 -07:00
classes . forEach ( className => cssSelector . addClassName ( className ));
}
}
return cssSelector ;
}
2016-07-08 10:05:27 -07:00
const EMPTY_ELEMENT_CONTEXT = new ElementContext ( true , new SelectorMatcher (), null , null );
const NON_BINDABLE_VISITOR = new NonBindableVisitor ();
2015-12-02 10:35:51 -08:00
export class PipeCollector extends RecursiveAstVisitor {
pipes : Set < string > = new Set < string >();
2016-01-06 14:13:44 -08:00
visitPipe ( ast : BindingPipe , context : any ) : any {
2015-12-02 10:35:51 -08:00
this . pipes . add ( ast . name );
ast . exp . visit ( this );
2016-01-06 14:13:44 -08:00
this . visitAll ( ast . args , context );
2015-12-02 10:35:51 -08:00
return null ;
}
}