UNPKG

angular2

Version:

Angular 2 - a web framework for modern web apps

637 lines (636 loc) 31.9 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; import { ListWrapper, StringMapWrapper, SetWrapper } from 'angular2/src/facade/collection'; import { RegExpWrapper, isPresent, StringWrapper, isBlank } from 'angular2/src/facade/lang'; import { Injectable, Inject, OpaqueToken, Optional } from 'angular2/core'; import { CONST_EXPR } from 'angular2/src/facade/lang'; import { BaseException } from 'angular2/src/facade/exceptions'; import { Parser } from 'angular2/src/core/change_detection/change_detection'; import { HtmlParser } from './html_parser'; import { splitNsName, mergeNsAndName } from './html_tags'; import { ParseError } from './parse_util'; import { RecursiveAstVisitor } from 'angular2/src/core/change_detection/parser/ast'; import { ElementAst, BoundElementPropertyAst, BoundEventAst, VariableAst, templateVisitAll, TextAst, BoundTextAst, EmbeddedTemplateAst, AttrAst, NgContentAst, PropertyBindingType, DirectiveAst, BoundDirectivePropertyAst } from './template_ast'; import { CssSelector, SelectorMatcher } from 'angular2/src/compiler/selector'; import { ElementSchemaRegistry } from 'angular2/src/compiler/schema/element_schema_registry'; import { preparseElement, PreparsedElementType } from './template_preparser'; import { isStyleUrlResolvable } from './style_url_resolver'; import { htmlVisitAll } from './html_ast'; import { splitAtColon } from './util'; // Group 1 = "bind-" // Group 2 = "var-" or "#" // Group 3 = "on-" // Group 4 = "bindon-" // Group 5 = the identifier after "bind-", "var-/#", or "on-" // Group 6 = identifier inside [()] // Group 7 = identifier inside [] // Group 8 = identifier inside () var BIND_NAME_REGEXP = /^(?:(?:(?:(bind-)|(var-|#)|(on-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g; const TEMPLATE_ELEMENT = 'template'; const TEMPLATE_ATTR = 'template'; const TEMPLATE_ATTR_PREFIX = '*'; const CLASS_ATTR = 'class'; var PROPERTY_PARTS_SEPARATOR = '.'; const ATTRIBUTE_PREFIX = 'attr'; const CLASS_PREFIX = 'class'; const STYLE_PREFIX = 'style'; var TEXT_CSS_SELECTOR = CssSelector.parse('*')[0]; /** * 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. */ export const TEMPLATE_TRANSFORMS = CONST_EXPR(new OpaqueToken('TemplateTransforms')); export class TemplateParseError extends ParseError { constructor(message, location) { super(location, message); } } export let TemplateParser = class { constructor(_exprParser, _schemaRegistry, _htmlParser, transforms) { this._exprParser = _exprParser; this._schemaRegistry = _schemaRegistry; this._htmlParser = _htmlParser; this.transforms = transforms; } parse(template, directives, pipes, templateUrl) { var parseVisitor = new TemplateParseVisitor(directives, pipes, this._exprParser, this._schemaRegistry); var htmlAstWithErrors = this._htmlParser.parse(template, templateUrl); var result = htmlVisitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_COMPONENT); var errors = htmlAstWithErrors.errors.concat(parseVisitor.errors); if (errors.length > 0) { var errorString = errors.join('\n'); throw new BaseException(`Template parse errors:\n${errorString}`); } if (isPresent(this.transforms)) { this.transforms.forEach((transform) => { result = templateVisitAll(transform, result); }); } return result; } }; TemplateParser = __decorate([ Injectable(), __param(3, Optional()), __param(3, Inject(TEMPLATE_TRANSFORMS)), __metadata('design:paramtypes', [Parser, ElementSchemaRegistry, HtmlParser, Array]) ], TemplateParser); class TemplateParseVisitor { constructor(directives, pipes, _exprParser, _schemaRegistry) { this._exprParser = _exprParser; this._schemaRegistry = _schemaRegistry; this.errors = []; this.directivesIndex = new Map(); this.ngContentCount = 0; this.selectorMatcher = new SelectorMatcher(); ListWrapper.forEachWithIndex(directives, (directive, index) => { var selector = CssSelector.parse(directive.selector); this.selectorMatcher.addSelectables(selector, directive); this.directivesIndex.set(directive, index); }); this.pipesByName = new Map(); pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe)); } _reportError(message, sourceSpan) { this.errors.push(new TemplateParseError(message, sourceSpan.start)); } _parseInterpolation(value, sourceSpan) { var sourceInfo = sourceSpan.start.toString(); try { var ast = this._exprParser.parseInterpolation(value, sourceInfo); this._checkPipes(ast, sourceSpan); return ast; } catch (e) { this._reportError(`${e}`, sourceSpan); return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo); } } _parseAction(value, sourceSpan) { var sourceInfo = sourceSpan.start.toString(); try { var ast = this._exprParser.parseAction(value, sourceInfo); this._checkPipes(ast, sourceSpan); return ast; } catch (e) { this._reportError(`${e}`, sourceSpan); return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo); } } _parseBinding(value, sourceSpan) { var sourceInfo = sourceSpan.start.toString(); try { var ast = this._exprParser.parseBinding(value, sourceInfo); this._checkPipes(ast, sourceSpan); return ast; } catch (e) { this._reportError(`${e}`, sourceSpan); return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo); } } _parseTemplateBindings(value, sourceSpan) { var sourceInfo = sourceSpan.start.toString(); try { var bindings = this._exprParser.parseTemplateBindings(value, sourceInfo); bindings.forEach((binding) => { if (isPresent(binding.expression)) { this._checkPipes(binding.expression, sourceSpan); } }); return bindings; } catch (e) { this._reportError(`${e}`, sourceSpan); return []; } } _checkPipes(ast, sourceSpan) { if (isPresent(ast)) { var collector = new PipeCollector(); ast.visit(collector); collector.pipes.forEach((pipeName) => { if (!this.pipesByName.has(pipeName)) { this._reportError(`The pipe '${pipeName}' could not be found`, sourceSpan); } }); } } visitText(ast, component) { var ngContentIndex = component.findNgContentIndex(TEXT_CSS_SELECTOR); var expr = this._parseInterpolation(ast.value, ast.sourceSpan); if (isPresent(expr)) { return new BoundTextAst(expr, ngContentIndex, ast.sourceSpan); } else { return new TextAst(ast.value, ngContentIndex, ast.sourceSpan); } } visitAttr(ast, contex) { return new AttrAst(ast.name, ast.value, ast.sourceSpan); } visitElement(element, component) { var nodeName = element.name; var preparsedElement = preparseElement(element); if (preparsedElement.type === PreparsedElementType.SCRIPT || preparsedElement.type === PreparsedElementType.STYLE) { // Skipping <script> for security reasons // 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 // them in the StyleCompiler return null; } var matchableAttrs = []; var elementOrDirectiveProps = []; var vars = []; var events = []; var templateElementOrDirectiveProps = []; var templateVars = []; var templateMatchableAttrs = []; var hasInlineTemplates = false; var attrs = []; element.attrs.forEach(attr => { var hasBinding = this._parseAttr(attr, matchableAttrs, elementOrDirectiveProps, events, vars); var hasTemplateBinding = this._parseInlineTemplateBinding(attr, templateMatchableAttrs, templateElementOrDirectiveProps, templateVars); if (!hasBinding && !hasTemplateBinding) { // don't include the bindings as attributes as well in the AST attrs.push(this.visitAttr(attr, null)); matchableAttrs.push([attr.name, attr.value]); } if (hasTemplateBinding) { hasInlineTemplates = true; } }); var lcElName = splitNsName(nodeName.toLowerCase())[1]; var isTemplateElement = lcElName == TEMPLATE_ELEMENT; var elementCssSelector = createElementCssSelector(nodeName, matchableAttrs); var directives = this._createDirectiveAsts(element.name, this._parseDirectives(this.selectorMatcher, elementCssSelector), elementOrDirectiveProps, isTemplateElement ? [] : vars, element.sourceSpan); var elementProps = this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directives); var children = htmlVisitAll(preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children, Component.create(directives)); var elementNgContentIndex = hasInlineTemplates ? null : component.findNgContentIndex(elementCssSelector); var parsedElement; if (preparsedElement.type === PreparsedElementType.NG_CONTENT) { 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); } parsedElement = new NgContentAst(this.ngContentCount++, elementNgContentIndex, element.sourceSpan); } else if (isTemplateElement) { this._assertAllEventsPublishedByDirectives(directives, events); this._assertNoComponentsNorElementBindingsOnTemplate(directives, elementProps, element.sourceSpan); parsedElement = new EmbeddedTemplateAst(attrs, events, vars, directives, children, elementNgContentIndex, element.sourceSpan); } else { this._assertOnlyOneComponent(directives, element.sourceSpan); var elementExportAsVars = vars.filter(varAst => varAst.value.length === 0); parsedElement = new ElementAst(nodeName, attrs, elementProps, events, elementExportAsVars, directives, children, elementNgContentIndex, element.sourceSpan); } if (hasInlineTemplates) { var templateCssSelector = createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs); var templateDirectives = this._createDirectiveAsts(element.name, this._parseDirectives(this.selectorMatcher, templateCssSelector), templateElementOrDirectiveProps, [], element.sourceSpan); var templateElementProps = this._createElementPropertyAsts(element.name, templateElementOrDirectiveProps, templateDirectives); this._assertNoComponentsNorElementBindingsOnTemplate(templateDirectives, templateElementProps, element.sourceSpan); parsedElement = new EmbeddedTemplateAst([], [], templateVars, templateDirectives, [parsedElement], component.findNgContentIndex(templateCssSelector), element.sourceSpan); } return parsedElement; } _parseInlineTemplateBinding(attr, targetMatchableAttrs, targetProps, targetVars) { var templateBindingsSource = null; if (attr.name == TEMPLATE_ATTR) { templateBindingsSource = attr.value; } else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) { var key = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star templateBindingsSource = (attr.value.length == 0) ? key : key + ' ' + attr.value; } if (isPresent(templateBindingsSource)) { var bindings = this._parseTemplateBindings(templateBindingsSource, attr.sourceSpan); for (var i = 0; i < bindings.length; i++) { var binding = bindings[i]; if (binding.keyIsVar) { targetVars.push(new VariableAst(binding.key, binding.name, attr.sourceSpan)); targetMatchableAttrs.push([binding.key, binding.name]); } else if (isPresent(binding.expression)) { this._parsePropertyAst(binding.key, binding.expression, attr.sourceSpan, targetMatchableAttrs, targetProps); } else { targetMatchableAttrs.push([binding.key, '']); this._parseLiteralAttr(binding.key, null, attr.sourceSpan, targetProps); } } return true; } return false; } _parseAttr(attr, targetMatchableAttrs, targetProps, targetEvents, targetVars) { var attrName = this._normalizeAttributeName(attr.name); var attrValue = attr.value; var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName); var hasBinding = false; if (isPresent(bindParts)) { hasBinding = true; if (isPresent(bindParts[1])) { this._parseProperty(bindParts[5], attrValue, attr.sourceSpan, targetMatchableAttrs, targetProps); } else if (isPresent(bindParts[2])) { var identifier = bindParts[5]; this._parseVariable(identifier, attrValue, attr.sourceSpan, targetVars); } else if (isPresent(bindParts[3])) { this._parseEvent(bindParts[5], attrValue, attr.sourceSpan, targetMatchableAttrs, targetEvents); } else if (isPresent(bindParts[4])) { this._parseProperty(bindParts[5], attrValue, attr.sourceSpan, targetMatchableAttrs, targetProps); this._parseAssignmentEvent(bindParts[5], attrValue, attr.sourceSpan, targetMatchableAttrs, targetEvents); } else if (isPresent(bindParts[6])) { this._parseProperty(bindParts[6], attrValue, attr.sourceSpan, targetMatchableAttrs, targetProps); this._parseAssignmentEvent(bindParts[6], attrValue, attr.sourceSpan, targetMatchableAttrs, targetEvents); } else if (isPresent(bindParts[7])) { this._parseProperty(bindParts[7], attrValue, attr.sourceSpan, targetMatchableAttrs, targetProps); } else if (isPresent(bindParts[8])) { this._parseEvent(bindParts[8], attrValue, attr.sourceSpan, targetMatchableAttrs, targetEvents); } } else { hasBinding = this._parsePropertyInterpolation(attrName, attrValue, attr.sourceSpan, targetMatchableAttrs, targetProps); } if (!hasBinding) { this._parseLiteralAttr(attrName, attrValue, attr.sourceSpan, targetProps); } return hasBinding; } _normalizeAttributeName(attrName) { return attrName.toLowerCase().startsWith('data-') ? attrName.substring(5) : attrName; } _parseVariable(identifier, value, sourceSpan, targetVars) { if (identifier.indexOf('-') > -1) { this._reportError(`"-" is not allowed in variable names`, sourceSpan); } targetVars.push(new VariableAst(identifier, value, sourceSpan)); } _parseProperty(name, expression, sourceSpan, targetMatchableAttrs, targetProps) { this._parsePropertyAst(name, this._parseBinding(expression, sourceSpan), sourceSpan, targetMatchableAttrs, targetProps); } _parsePropertyInterpolation(name, value, sourceSpan, targetMatchableAttrs, targetProps) { var expr = this._parseInterpolation(value, sourceSpan); if (isPresent(expr)) { this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps); return true; } return false; } _parsePropertyAst(name, ast, sourceSpan, targetMatchableAttrs, targetProps) { targetMatchableAttrs.push([name, ast.source]); targetProps.push(new BoundElementOrDirectiveProperty(name, ast, false, sourceSpan)); } _parseAssignmentEvent(name, expression, sourceSpan, targetMatchableAttrs, targetEvents) { this._parseEvent(`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, targetEvents); } _parseEvent(name, expression, sourceSpan, targetMatchableAttrs, targetEvents) { // long format: 'target: eventName' var parts = splitAtColon(name, [null, name]); var target = parts[0]; var eventName = parts[1]; var ast = this._parseAction(expression, sourceSpan); targetMatchableAttrs.push([name, ast.source]); targetEvents.push(new BoundEventAst(eventName, target, ast, sourceSpan)); // Don't detect directives for event names for now, // so don't add the event name to the matchableAttrs } _parseLiteralAttr(name, value, sourceSpan, targetProps) { targetProps.push(new BoundElementOrDirectiveProperty(name, this._exprParser.wrapLiteralPrimitive(value, ''), true, sourceSpan)); } _parseDirectives(selectorMatcher, elementCssSelector) { var directives = []; selectorMatcher.match(elementCssSelector, (selector, directive) => { directives.push(directive); }); // Need to sort the directives so that we get consistent results throughout, // as selectorMatcher uses Maps inside. // Also need to make components the first directive in the array ListWrapper.sort(directives, (dir1, dir2) => { var dir1Comp = dir1.isComponent; var dir2Comp = dir2.isComponent; if (dir1Comp && !dir2Comp) { return -1; } else if (!dir1Comp && dir2Comp) { return 1; } else { return this.directivesIndex.get(dir1) - this.directivesIndex.get(dir2); } }); return directives; } _createDirectiveAsts(elementName, directives, props, possibleExportAsVars, sourceSpan) { var matchedVariables = new Set(); var directiveAsts = directives.map((directive) => { var hostProperties = []; var hostEvents = []; var directiveProperties = []; this._createDirectiveHostPropertyAsts(elementName, directive.hostProperties, sourceSpan, hostProperties); this._createDirectiveHostEventAsts(directive.hostListeners, sourceSpan, hostEvents); this._createDirectivePropertyAsts(directive.inputs, props, directiveProperties); var exportAsVars = []; possibleExportAsVars.forEach((varAst) => { if ((varAst.value.length === 0 && directive.isComponent) || (directive.exportAs == varAst.value)) { exportAsVars.push(varAst); matchedVariables.add(varAst.name); } }); return new DirectiveAst(directive, directiveProperties, hostProperties, hostEvents, exportAsVars, sourceSpan); }); possibleExportAsVars.forEach((varAst) => { if (varAst.value.length > 0 && !SetWrapper.has(matchedVariables, varAst.name)) { this._reportError(`There is no directive with "exportAs" set to "${varAst.value}"`, varAst.sourceSpan); } }); return directiveAsts; } _createDirectiveHostPropertyAsts(elementName, hostProps, sourceSpan, targetPropertyAsts) { if (isPresent(hostProps)) { StringMapWrapper.forEach(hostProps, (expression, propName) => { var exprAst = this._parseBinding(expression, sourceSpan); targetPropertyAsts.push(this._createElementPropertyAst(elementName, propName, exprAst, sourceSpan)); }); } } _createDirectiveHostEventAsts(hostListeners, sourceSpan, targetEventAsts) { if (isPresent(hostListeners)) { StringMapWrapper.forEach(hostListeners, (expression, propName) => { this._parseEvent(propName, expression, sourceSpan, [], targetEventAsts); }); } } _createDirectivePropertyAsts(directiveProperties, boundProps, targetBoundDirectiveProps) { if (isPresent(directiveProperties)) { var boundPropsByName = new Map(); boundProps.forEach(boundProp => { var prevValue = boundPropsByName.get(boundProp.name); if (isBlank(prevValue) || prevValue.isLiteral) { // give [a]="b" a higher precedence than a="b" on the same element boundPropsByName.set(boundProp.name, boundProp); } }); StringMapWrapper.forEach(directiveProperties, (elProp, dirProp) => { var boundProp = boundPropsByName.get(elProp); // Bindings are optional, so this binding only needs to be set up if an expression is given. if (isPresent(boundProp)) { targetBoundDirectiveProps.push(new BoundDirectivePropertyAst(dirProp, boundProp.name, boundProp.expression, boundProp.sourceSpan)); } }); } } _createElementPropertyAsts(elementName, props, directives) { var boundElementProps = []; var boundDirectivePropsIndex = new Map(); directives.forEach((directive) => { directive.inputs.forEach((prop) => { boundDirectivePropsIndex.set(prop.templateName, prop); }); }); props.forEach((prop) => { if (!prop.isLiteral && isBlank(boundDirectivePropsIndex.get(prop.name))) { boundElementProps.push(this._createElementPropertyAst(elementName, prop.name, prop.expression, prop.sourceSpan)); } }); return boundElementProps; } _createElementPropertyAst(elementName, name, ast, sourceSpan) { var unit = null; var bindingType; var boundPropertyName; var parts = name.split(PROPERTY_PARTS_SEPARATOR); if (parts.length === 1) { boundPropertyName = this._schemaRegistry.getMappedPropName(parts[0]); bindingType = PropertyBindingType.Property; if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName)) { this._reportError(`Can't bind to '${boundPropertyName}' since it isn't a known native property`, sourceSpan); } } else { if (parts[0] == ATTRIBUTE_PREFIX) { boundPropertyName = parts[1]; let nsSeparatorIdx = boundPropertyName.indexOf(':'); if (nsSeparatorIdx > -1) { let ns = boundPropertyName.substring(0, nsSeparatorIdx); let name = boundPropertyName.substring(nsSeparatorIdx + 1); boundPropertyName = mergeNsAndName(ns, name); } bindingType = PropertyBindingType.Attribute; } else if (parts[0] == CLASS_PREFIX) { boundPropertyName = parts[1]; bindingType = PropertyBindingType.Class; } else if (parts[0] == STYLE_PREFIX) { unit = parts.length > 2 ? parts[2] : null; boundPropertyName = parts[1]; bindingType = PropertyBindingType.Style; } else { this._reportError(`Invalid property name '${name}'`, sourceSpan); bindingType = null; } } return new BoundElementPropertyAst(boundPropertyName, bindingType, ast, unit, sourceSpan); } _findComponentDirectiveNames(directives) { var componentTypeNames = []; directives.forEach(directive => { var typeName = directive.directive.type.name; if (directive.directive.isComponent) { componentTypeNames.push(typeName); } }); return componentTypeNames; } _assertOnlyOneComponent(directives, sourceSpan) { var componentTypeNames = this._findComponentDirectiveNames(directives); if (componentTypeNames.length > 1) { this._reportError(`More than one component: ${componentTypeNames.join(',')}`, sourceSpan); } } _assertNoComponentsNorElementBindingsOnTemplate(directives, elementProps, sourceSpan) { var componentTypeNames = this._findComponentDirectiveNames(directives); if (componentTypeNames.length > 0) { this._reportError(`Components on an embedded template: ${componentTypeNames.join(',')}`, sourceSpan); } elementProps.forEach(prop => { this._reportError(`Property binding ${prop.name} not used by any directive on an embedded template`, sourceSpan); }); } _assertAllEventsPublishedByDirectives(directives, events) { var allDirectiveEvents = new Set(); directives.forEach(directive => { StringMapWrapper.forEach(directive.directive.outputs, (eventName, _) => { allDirectiveEvents.add(eventName); }); }); events.forEach(event => { if (isPresent(event.target) || !SetWrapper.has(allDirectiveEvents, event.name)) { this._reportError(`Event binding ${event.fullName} not emitted by any directive on an embedded template`, event.sourceSpan); } }); } } class NonBindableVisitor { visitElement(ast, component) { var preparsedElement = preparseElement(ast); 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; } var attrNameAndValues = ast.attrs.map(attrAst => [attrAst.name, attrAst.value]); var selector = createElementCssSelector(ast.name, attrNameAndValues); var ngContentIndex = component.findNgContentIndex(selector); var children = htmlVisitAll(this, ast.children, EMPTY_COMPONENT); return new ElementAst(ast.name, htmlVisitAll(this, ast.attrs), [], [], [], [], children, ngContentIndex, ast.sourceSpan); } visitAttr(ast, context) { return new AttrAst(ast.name, ast.value, ast.sourceSpan); } visitText(ast, component) { var ngContentIndex = component.findNgContentIndex(TEXT_CSS_SELECTOR); return new TextAst(ast.value, ngContentIndex, ast.sourceSpan); } } class BoundElementOrDirectiveProperty { constructor(name, expression, isLiteral, sourceSpan) { this.name = name; this.expression = expression; this.isLiteral = isLiteral; this.sourceSpan = sourceSpan; } } export function splitClasses(classAttrValue) { return StringWrapper.split(classAttrValue.trim(), /\s+/g); } class Component { constructor(ngContentIndexMatcher, wildcardNgContentIndex) { this.ngContentIndexMatcher = ngContentIndexMatcher; this.wildcardNgContentIndex = wildcardNgContentIndex; } static create(directives) { if (directives.length === 0 || !directives[0].directive.isComponent) { return EMPTY_COMPONENT; } var matcher = new SelectorMatcher(); var ngContentSelectors = directives[0].directive.template.ngContentSelectors; var wildcardNgContentIndex = null; for (var i = 0; i < ngContentSelectors.length; i++) { var selector = ngContentSelectors[i]; if (StringWrapper.equals(selector, '*')) { wildcardNgContentIndex = i; } else { matcher.addSelectables(CssSelector.parse(ngContentSelectors[i]), i); } } return new Component(matcher, wildcardNgContentIndex); } findNgContentIndex(selector) { var ngContentIndices = []; this.ngContentIndexMatcher.match(selector, (selector, ngContentIndex) => { ngContentIndices.push(ngContentIndex); }); ListWrapper.sort(ngContentIndices); if (isPresent(this.wildcardNgContentIndex)) { ngContentIndices.push(this.wildcardNgContentIndex); } return ngContentIndices.length > 0 ? ngContentIndices[0] : null; } } function createElementCssSelector(elementName, matchableAttrs) { var cssSelector = new CssSelector(); let elNameNoNs = splitNsName(elementName)[1]; cssSelector.setElement(elNameNoNs); for (var i = 0; i < matchableAttrs.length; i++) { let attrName = matchableAttrs[i][0]; let attrNameNoNs = splitNsName(attrName)[1]; let attrValue = matchableAttrs[i][1]; cssSelector.addAttribute(attrNameNoNs, attrValue); if (attrName.toLowerCase() == CLASS_ATTR) { var classes = splitClasses(attrValue); classes.forEach(className => cssSelector.addClassName(className)); } } return cssSelector; } var EMPTY_COMPONENT = new Component(new SelectorMatcher(), null); var NON_BINDABLE_VISITOR = new NonBindableVisitor(); export class PipeCollector extends RecursiveAstVisitor { constructor(...args) { super(...args); this.pipes = new Set(); } visitPipe(ast) { this.pipes.add(ast.name); ast.exp.visit(this); this.visitAll(ast.args); return null; } }