/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.engine.internal.html.angular;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.dart.engine.AnalysisEngine;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.Expression;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.context.AnalysisContext;
import com.google.dart.engine.context.AnalysisException;
import com.google.dart.engine.element.ClassElement;
import com.google.dart.engine.element.CompilationUnitElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.ExternalHtmlScriptElement;
import com.google.dart.engine.element.FunctionElement;
import com.google.dart.engine.element.HtmlScriptElement;
import com.google.dart.engine.element.ImportElement;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.element.LocalVariableElement;
import com.google.dart.engine.element.ToolkitObjectElement;
import com.google.dart.engine.element.angular.AngularComponentElement;
import com.google.dart.engine.element.angular.AngularControllerElement;
import com.google.dart.engine.element.angular.AngularDecoratorElement;
import com.google.dart.engine.element.angular.AngularElement;
import com.google.dart.engine.element.angular.AngularFormatterElement;
import com.google.dart.engine.element.angular.AngularHasTemplateElement;
import com.google.dart.engine.element.angular.AngularScopePropertyElement;
import com.google.dart.engine.error.AnalysisError;
import com.google.dart.engine.error.AnalysisErrorListener;
import com.google.dart.engine.error.AngularCode;
import com.google.dart.engine.error.ErrorCode;
import com.google.dart.engine.error.StaticTypeWarningCode;
import com.google.dart.engine.error.StaticWarningCode;
import com.google.dart.engine.html.ast.HtmlUnit;
import com.google.dart.engine.html.ast.XmlAttributeNode;
import com.google.dart.engine.html.ast.XmlExpression;
import com.google.dart.engine.html.ast.XmlTagNode;
import com.google.dart.engine.html.ast.visitor.RecursiveXmlVisitor;
import com.google.dart.engine.html.parser.HtmlParser;
import com.google.dart.engine.internal.context.InternalAnalysisContext;
import com.google.dart.engine.internal.element.CompilationUnitElementImpl;
import com.google.dart.engine.internal.element.FunctionElementImpl;
import com.google.dart.engine.internal.element.HtmlElementImpl;
import com.google.dart.engine.internal.element.ImportElementImpl;
import com.google.dart.engine.internal.element.LibraryElementImpl;
import com.google.dart.engine.internal.element.LocalVariableElementImpl;
import com.google.dart.engine.internal.element.angular.AngularApplication;
import com.google.dart.engine.internal.element.angular.AngularComponentElementImpl;
import com.google.dart.engine.internal.element.angular.AngularElementImpl;
import com.google.dart.engine.internal.element.angular.AngularViewElementImpl;
import com.google.dart.engine.internal.resolver.InheritanceManager;
import com.google.dart.engine.internal.resolver.ResolverVisitor;
import com.google.dart.engine.internal.resolver.TypeProvider;
import com.google.dart.engine.internal.scope.Scope;
import com.google.dart.engine.parser.Parser;
import com.google.dart.engine.scanner.StringToken;
import com.google.dart.engine.scanner.Token;
import com.google.dart.engine.scanner.TokenType;
import com.google.dart.engine.source.Source;
import com.google.dart.engine.type.InterfaceType;
import com.google.dart.engine.type.Type;
import com.google.dart.engine.utilities.general.StringUtilities;
import com.google.dart.engine.utilities.source.LineInfo;
import static com.google.dart.engine.internal.html.angular.AngularMoustacheXmlExpression.CLOSING_DELIMITER_CHAR;
import static com.google.dart.engine.internal.html.angular.AngularMoustacheXmlExpression.CLOSING_DELIMITER_LENGTH;
import static com.google.dart.engine.internal.html.angular.AngularMoustacheXmlExpression.OPENING_DELIMITER_CHAR;
import static com.google.dart.engine.internal.html.angular.AngularMoustacheXmlExpression.OPENING_DELIMITER_LENGTH;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* Instances of the class {@link AngularHtmlUnitResolver} resolve Angular specific expressions.
*/
public class AngularHtmlUnitResolver extends RecursiveXmlVisitor<Void> {
private static class FilteringAnalysisErrorListener implements AnalysisErrorListener {
private final AnalysisErrorListener listener;
public FilteringAnalysisErrorListener(AnalysisErrorListener listener) {
this.listener = listener;
}
@Override
public void onError(AnalysisError error) {
ErrorCode errorCode = error.getErrorCode();
if (errorCode == StaticWarningCode.UNDEFINED_GETTER
|| errorCode == StaticWarningCode.UNDEFINED_IDENTIFIER
|| errorCode == StaticTypeWarningCode.UNDEFINED_GETTER) {
return;
}
listener.onError(error);
}
}
private static class FoundAppError extends Error {
}
private static final String NG_APP = "ng-app";
/**
* Checks if given {@link Element} is an artificial local variable and returns corresponding
* {@link AngularElement}, or {@code null} otherwise.
*/
public static AngularElement getAngularElement(Element element) {
// may be artificial local variable, replace with AngularElement
if (element instanceof LocalVariableElement) {
LocalVariableElement local = (LocalVariableElement) element;
ToolkitObjectElement[] toolkitObjects = local.getToolkitObjects();
if (toolkitObjects.length == 1 && toolkitObjects[0] instanceof AngularElement) {
return (AngularElement) toolkitObjects[0];
}
}
// not a special Element
return null;
}
/**
* @return {@code true} if the given {@link HtmlUnit} has <code>ng-app</code> annotation.
*/
public static boolean hasAngularAnnotation(HtmlUnit htmlUnit) {
try {
htmlUnit.accept(new RecursiveXmlVisitor<Void>() {
@Override
public Void visitXmlTagNode(XmlTagNode node) {
if (node.getAttribute(NG_APP) != null) {
throw new FoundAppError();
}
return super.visitXmlTagNode(node);
}
});
} catch (FoundAppError e) {
return true;
}
return false;
}
static SimpleIdentifier createIdentifier(String name, int offset) {
StringToken token = createStringToken(name, offset);
return new SimpleIdentifier(token);
}
/**
* Adds {@link AngularElement} declared by the given top-level {@link Element}.
*
* @param angularElements the list to fill with top-level {@link AngularElement}s
* @param classElement the {@link ClassElement} to get {@link AngularElement}s from
*/
private static void addAngularElementsFromClass(Set<AngularElement> angularElements,
ClassElement classElement) {
for (ToolkitObjectElement toolkitObject : classElement.getToolkitObjects()) {
if (toolkitObject instanceof AngularElement) {
angularElements.add((AngularElement) toolkitObject);
}
}
}
/**
* Returns the array of all top-level Angular elements that could be used in this library.
*
* @param libraryElement the {@link LibraryElement} to analyze
* @return the array of all top-level Angular elements that could be used in this library
*/
private static void addAngularElementsFromLibrary(Set<AngularElement> angularElements,
LibraryElement library, Set<LibraryElement> visited) {
if (library == null) {
return;
}
if (!visited.add(library)) {
return;
}
// add Angular elements from current library
for (CompilationUnitElement unit : library.getUnits()) {
Collections.addAll(angularElements, unit.getAngularViews());
for (ClassElement type : unit.getTypes()) {
addAngularElementsFromClass(angularElements, type);
}
}
// handle imports
for (ImportElement importElement : library.getImports()) {
LibraryElement importedLibrary = importElement.getImportedLibrary();
addAngularElementsFromLibrary(angularElements, importedLibrary, visited);
}
}
// TODO(scheglov) rename to: createIdentifierToken
private static StringToken createStringToken(String name, int offset) {
return new StringToken(TokenType.IDENTIFIER, name, offset);
}
/**
* Returns the array of all top-level Angular elements that could be used in this library.
*
* @param libraryElement the {@link LibraryElement} to analyze
* @return the array of all top-level Angular elements that could be used in this library
*/
private static AngularElement[] getAngularElements(Set<LibraryElement> libraries,
LibraryElement libraryElement) {
Set<AngularElement> angularElements = Sets.newHashSet();
addAngularElementsFromLibrary(angularElements, libraryElement, libraries);
return angularElements.toArray(new AngularElement[angularElements.size()]);
}
/**
* Returns the external Dart {@link CompilationUnit} referenced by the given {@link HtmlUnit}.
*/
private static CompilationUnit getDartUnit(AnalysisContext context, HtmlUnit unit)
throws AnalysisException {
for (HtmlScriptElement script : unit.getElement().getScripts()) {
if (script instanceof ExternalHtmlScriptElement) {
Source scriptSource = ((ExternalHtmlScriptElement) script).getScriptSource();
if (scriptSource != null) {
return context.resolveCompilationUnit(scriptSource, scriptSource);
}
}
}
return null;
}
private static Set<Source> getLibrarySources(Set<LibraryElement> libraries) {
Set<Source> sources = Sets.newHashSet();
for (LibraryElement library : libraries) {
sources.add(library.getSource());
}
return sources;
}
private final InternalAnalysisContext context;
private final TypeProvider typeProvider;
private final FilteringAnalysisErrorListener errorListener;
private final Source source;
private final LineInfo lineInfo;
private final HtmlUnit unit;
private AngularElement[] angularElements;
private final List<NgProcessor> processors = Lists.newArrayList();
private LibraryElementImpl libraryElement;
private CompilationUnitElementImpl unitElement;
private FunctionElementImpl functionElement;
private ResolverVisitor resolver;
private boolean isAngular = false;
private List<LocalVariableElementImpl> definedVariables = Lists.newArrayList();
private Set<LibraryElement> injectedLibraries = Sets.newHashSet();
private Scope topNameScope;
private Scope nameScope;
public AngularHtmlUnitResolver(InternalAnalysisContext context,
AnalysisErrorListener errorListener, Source source, LineInfo lineInfo, HtmlUnit unit)
throws AnalysisException {
this.context = context;
this.typeProvider = context.getTypeProvider();
this.errorListener = new FilteringAnalysisErrorListener(errorListener);
this.source = source;
this.lineInfo = lineInfo;
this.unit = unit;
}
/**
* The {@link AngularApplication} for the Web application with this entry point, may be
* {@code null} if not an entry point.
*/
public AngularApplication calculateAngularApplication() throws AnalysisException {
// check if Angular at all
if (!hasAngularAnnotation(unit)) {
return null;
}
// prepare resolved Dart unit
CompilationUnit dartUnit = getDartUnit(context, unit);
if (dartUnit == null) {
return null;
}
// prepare accessible Angular elements
LibraryElement libraryElement = dartUnit.getElement().getLibrary();
Set<LibraryElement> libraries = Sets.newHashSet();
AngularElement[] angularElements = getAngularElements(libraries, libraryElement);
// resolve AngularComponentElement template URIs
// TODO(scheglov) resolve to HtmlElement to allow F3 ?
Set<Source> angularElementsSources = Sets.newHashSet();
for (AngularElement angularElement : angularElements) {
if (angularElement instanceof AngularHasTemplateElement) {
AngularHasTemplateElement hasTemplate = (AngularHasTemplateElement) angularElement;
angularElementsSources.add(angularElement.getSource());
String templateUri = hasTemplate.getTemplateUri();
if (templateUri == null) {
continue;
}
try {
Source templateSource = context.getSourceFactory().forUri(
source.resolveRelativeUri(new URI(templateUri)));
if (!context.exists(templateSource)) {
templateSource = context.getSourceFactory().resolveUri(source, "package:" + templateUri);
if (!context.exists(templateSource)) {
errorListener.onError(new AnalysisError(
angularElement.getSource(),
hasTemplate.getTemplateUriOffset(),
templateUri.length(),
AngularCode.URI_DOES_NOT_EXIST,
templateUri));
continue;
}
}
if (!AnalysisEngine.isHtmlFileName(templateUri)) {
continue;
}
if (hasTemplate instanceof AngularComponentElementImpl) {
((AngularComponentElementImpl) hasTemplate).setTemplateSource(templateSource);
}
if (hasTemplate instanceof AngularViewElementImpl) {
((AngularViewElementImpl) hasTemplate).setTemplateSource(templateSource);
}
} catch (URISyntaxException exception) {
errorListener.onError(new AnalysisError(
angularElement.getSource(),
hasTemplate.getTemplateUriOffset(),
templateUri.length(),
AngularCode.INVALID_URI,
templateUri));
}
}
}
// create AngularApplication
AngularApplication application = new AngularApplication(
source,
getLibrarySources(libraries),
angularElements,
angularElementsSources.toArray(new Source[angularElementsSources.size()]));
// set AngularApplication for each AngularElement
for (AngularElement angularElement : angularElements) {
((AngularElementImpl) angularElement).setApplication(application);
}
// done
return application;
}
/**
* Resolves {@link #source} as an {@link AngularComponentElement} template file.
*
* @param application the Angular application we are resolving for
* @param component the {@link AngularComponentElement} to resolve template for, not {@code null}
*/
public void resolveComponentTemplate(AngularApplication application,
AngularComponentElement component) throws AnalysisException {
isAngular = true;
resolveInternal(application.getElements(), component);
}
/**
* Resolves {@link #source} as an Angular application entry point.
*/
public void resolveEntryPoint(AngularApplication application) throws AnalysisException {
resolveInternal(application.getElements(), null);
}
@Override
public Void visitXmlAttributeNode(XmlAttributeNode node) {
parseEmbeddedExpressionsInAttribute(node);
resolveExpressions(node.getExpressions());
return super.visitXmlAttributeNode(node);
}
@Override
public Void visitXmlTagNode(XmlTagNode node) {
boolean wasAngular = isAngular;
try {
// new Angular context
if (node.getAttribute(NG_APP) != null) {
isAngular = true;
visitModelDirectives(node);
}
// not Angular
if (!isAngular) {
return super.visitXmlTagNode(node);
}
// process node in separate name scope
pushNameScope();
try {
parseEmbeddedExpressionsInTag(node);
// apply processors
for (NgProcessor processor : processors) {
if (processor.canApply(node)) {
processor.apply(this, node);
}
}
// resolve expressions
resolveExpressions(node.getExpressions());
// process children
return super.visitXmlTagNode(node);
} finally {
popNameScope();
}
} finally {
isAngular = wasAngular;
}
}
/**
* Creates new {@link LocalVariableElementImpl} with given type and identifier.
*
* @param type the {@link Type} of the variable
* @param identifier the identifier to create variable for
* @return the new {@link LocalVariableElementImpl}
*/
LocalVariableElementImpl createLocalVariableFromIdentifier(Type type, SimpleIdentifier identifier) {
LocalVariableElementImpl variable = new LocalVariableElementImpl(identifier);
definedVariables.add(variable);
variable.setType(type);
return variable;
}
/**
* Creates new {@link LocalVariableElementImpl} with given name and type.
*
* @param type the {@link Type} of the variable
* @param name the name of the variable
* @return the new {@link LocalVariableElementImpl}
*/
LocalVariableElementImpl createLocalVariableWithName(Type type, String name) {
SimpleIdentifier identifier = createIdentifier(name, 0);
return createLocalVariableFromIdentifier(type, identifier);
}
/**
* Declares the given {@link LocalVariableElementImpl} in the {@link #topNameScope}.
*/
void defineTopVariable(LocalVariableElementImpl variable) {
recordDefinedVariable(variable);
topNameScope.define(variable);
recordTypeLibraryInjected(variable);
}
/**
* Declares the given {@link LocalVariableElementImpl} in the current {@link #nameScope}.
*/
void defineVariable(LocalVariableElementImpl variable) {
recordDefinedVariable(variable);
nameScope.define(variable);
recordTypeLibraryInjected(variable);
}
/**
* @return the {@link AngularElement} with the given name, maybe {@code null}.
*/
AngularElement findAngularElement(String name) {
for (AngularElement element : angularElements) {
if (name.equals(element.getName())) {
return element;
}
}
return null;
}
/**
* @return the {@link TypeProvider} of the {@link AnalysisContext}.
*/
TypeProvider getTypeProvider() {
return typeProvider;
}
/**
* Parses given {@link String} as an {@link AngularExpression} at the given offset.
*/
AngularExpression parseAngularExpression(String contents, int startIndex, int endIndex, int offset) {
Token token = scanDart(contents, startIndex, endIndex, offset);
return parseAngularExpressionInToken(token);
}
AngularExpression parseAngularExpressionInToken(Token token) {
List<Token> tokens = splitAtBar(token);
Expression mainExpression = parseDartExpressionInToken(tokens.get(0));
// parse formatters
List<AngularFormatterNode> formatters = Lists.newArrayList();
for (int i = 1; i < tokens.size(); i++) {
Token formatterToken = tokens.get(i);
Token barToken = formatterToken;
formatterToken = formatterToken.getNext();
// parse name
Expression nameExpression = parseDartExpressionInToken(formatterToken);
if (!(nameExpression instanceof SimpleIdentifier)) {
reportErrorForNode(AngularCode.INVALID_FORMATTER_NAME, nameExpression);
continue;
}
SimpleIdentifier name = (SimpleIdentifier) nameExpression;
formatterToken = name.getEndToken().getNext();
// parse arguments
List<AngularFormatterArgument> arguments = Lists.newArrayList();
while (formatterToken.getType() != TokenType.EOF) {
// skip ":"
Token colonToken = formatterToken;
if (colonToken.getType() == TokenType.COLON) {
formatterToken = formatterToken.getNext();
} else {
reportErrorForToken(AngularCode.MISSING_FORMATTER_COLON, colonToken);
}
// parse argument
Expression argument = parseDartExpressionInToken(formatterToken);
arguments.add(new AngularFormatterArgument(colonToken, argument));
// next token
formatterToken = argument.getEndToken().getNext();
}
formatters.add(new AngularFormatterNode(barToken, name, arguments));
}
// done
return new AngularExpression(mainExpression, formatters);
}
/**
* Parses given {@link String} as an {@link Expression} at the given offset.
*/
Expression parseDartExpression(String contents, int startIndex, int endIndex, int offset) {
Token token = scanDart(contents, startIndex, endIndex, offset);
return parseDartExpressionInToken(token);
}
Expression parseDartExpressionInToken(Token token) {
Parser parser = new Parser(source, errorListener);
return parser.parseExpression(token);
}
void popNameScope() {
nameScope = resolver.popNameScope();
}
void pushNameScope() {
nameScope = resolver.pushNameScope();
}
/**
* Reports given {@link ErrorCode} at the given {@link AstNode}.
*/
void reportErrorForNode(ErrorCode errorCode, AstNode node, Object... arguments) {
reportErrorForOffset(errorCode, node.getOffset(), node.getLength(), arguments);
}
/**
* Reports given {@link ErrorCode} at the given position.
*/
void reportErrorForOffset(ErrorCode errorCode, int offset, int length, Object... arguments) {
errorListener.onError(new AnalysisError(source, offset, length, errorCode, arguments));
}
/**
* Reports given {@link ErrorCode} at the given {@link Token}.
*/
void reportErrorForToken(ErrorCode errorCode, Token token, Object... arguments) {
reportErrorForOffset(errorCode, token.getOffset(), token.getLength(), arguments);
}
void resolveExpression(AngularExpression angularExpression) {
List<Expression> dartExpressions = angularExpression.getExpressions();
for (Expression dartExpression : dartExpressions) {
resolveNode(dartExpression);
}
}
/**
* Resolves given {@link AstNode} using {@link #resolver}.
*/
void resolveNode(AstNode node) {
node.accept(resolver);
}
Token scanDart(String contents, int startIndex, int endIndex, int offset) {
return HtmlParser.scanDartSource(
source,
lineInfo,
contents.substring(startIndex, endIndex),
offset + startIndex,
errorListener);
}
/**
* Puts into {@link #libraryElement} an artificial {@link LibraryElementImpl} for this HTML
* {@link Source}.
*/
private void createLibraryElement() {
// create CompilationUnitElementImpl
String unitName = source.getShortName();
unitElement = new CompilationUnitElementImpl(unitName);
unitElement.setSource(source);
// create LibraryElementImpl
libraryElement = new LibraryElementImpl(context.getContextFor(source), null);
libraryElement.setDefiningCompilationUnit(unitElement);
libraryElement.setAngularHtml(true);
injectedLibraries.add(libraryElement);
// create FunctionElementImpl
functionElement = new FunctionElementImpl(0);
unitElement.setFunctions(new FunctionElement[] {functionElement});
}
/**
* Creates new {@link NgProcessor} for the given {@link AngularElement}, maybe {@code null} if not
* supported.
*/
private NgProcessor createProcessor(AngularElement element) {
if (element instanceof AngularComponentElement) {
AngularComponentElement component = (AngularComponentElement) element;
return new NgComponentElementProcessor(component);
}
if (element instanceof AngularControllerElement) {
AngularControllerElement controller = (AngularControllerElement) element;
return new NgControllerElementProcessor(controller);
}
if (element instanceof AngularDecoratorElement) {
AngularDecoratorElement directive = (AngularDecoratorElement) element;
return new NgDecoratorElementProcessor(directive);
}
return null;
}
/**
* Puts into {@link #resolver} an {@link ResolverVisitor} to resolve {@link Expression}s in
* {@link #source}.
*/
private void createResolver() {
InheritanceManager inheritanceManager = new InheritanceManager(libraryElement);
resolver = new ResolverVisitor(
libraryElement,
source,
typeProvider,
inheritanceManager,
errorListener);
topNameScope = resolver.pushNameScope();
// add Scope variables - no type, no location, just to avoid warnings
{
Type type = typeProvider.getDynamicType();
topNameScope.define(createLocalVariableWithName(type, "$id"));
topNameScope.define(createLocalVariableWithName(type, "$parent"));
topNameScope.define(createLocalVariableWithName(type, "$root"));
}
}
/**
* Defines variable for the given {@link AngularElement} with type of the enclosing
* {@link ClassElement}.
*/
private void defineTopVariable_forClassElement(AngularElement element) {
ClassElement classElement = (ClassElement) element.getEnclosingElement();
InterfaceType type = classElement.getType();
LocalVariableElementImpl variable = createLocalVariableWithName(type, element.getName());
defineTopVariable(variable);
variable.setToolkitObjects(new AngularElement[] {element});
}
/**
* Defines variable for the given {@link AngularScopePropertyElement}.
*/
private void defineTopVariable_forScopeProperty(AngularScopePropertyElement element) {
Type type = element.getType();
LocalVariableElementImpl variable = createLocalVariableWithName(type, element.getName());
defineTopVariable(variable);
variable.setToolkitObjects(new AngularElement[] {element});
}
/**
* Parse the value of the given token for embedded expressions, and add any embedded expressions
* that are found to the given list of expressions.
*
* @param expressions the list to which embedded expressions are to be added
* @param token the token whose value is to be parsed
*/
private void parseEmbeddedExpressions(List<AngularMoustacheXmlExpression> expressions,
com.google.dart.engine.html.scanner.Token token) {
// prepare Token information
String lexeme = token.getLexeme();
int offset = token.getOffset();
// find expressions between {{ and }}
int startIndex = StringUtilities.indexOf2(
lexeme,
0,
OPENING_DELIMITER_CHAR,
OPENING_DELIMITER_CHAR);
while (startIndex >= 0) {
int endIndex = StringUtilities.indexOf2(
lexeme,
startIndex + OPENING_DELIMITER_LENGTH,
CLOSING_DELIMITER_CHAR,
CLOSING_DELIMITER_CHAR);
if (endIndex < 0) {
// TODO(brianwilkerson) Should we report this error or will it be reported by something else?
return;
} else if (startIndex + OPENING_DELIMITER_LENGTH < endIndex) {
startIndex += OPENING_DELIMITER_LENGTH;
AngularExpression expression = parseAngularExpression(lexeme, startIndex, endIndex, offset);
expressions.add(new AngularMoustacheXmlExpression(startIndex, endIndex, expression));
}
startIndex = StringUtilities.indexOf2(
lexeme,
endIndex + CLOSING_DELIMITER_LENGTH,
OPENING_DELIMITER_CHAR,
OPENING_DELIMITER_CHAR);
}
}
private void parseEmbeddedExpressionsInAttribute(XmlAttributeNode node) {
List<AngularMoustacheXmlExpression> expressions = Lists.newArrayList();
parseEmbeddedExpressions(expressions, node.getValueToken());
if (!expressions.isEmpty()) {
node.setExpressions(expressions.toArray(new AngularMoustacheXmlExpression[expressions.size()]));
}
}
private void parseEmbeddedExpressionsInTag(XmlTagNode node) {
List<AngularMoustacheXmlExpression> expressions = Lists.newArrayList();
com.google.dart.engine.html.scanner.Token token = node.getAttributeEnd();
com.google.dart.engine.html.scanner.Token endToken = node.getEndToken();
boolean inChild = false;
while (token != endToken) {
for (XmlTagNode child : node.getTagNodes()) {
if (token == child.getBeginToken()) {
inChild = true;
break;
}
if (token == child.getEndToken()) {
inChild = false;
break;
}
}
if (!inChild && token.getType() == com.google.dart.engine.html.scanner.TokenType.TEXT) {
parseEmbeddedExpressions(expressions, token);
}
token = token.getNext();
}
node.setExpressions(expressions.toArray(new AngularMoustacheXmlExpression[expressions.size()]));
}
private void recordDefinedVariable(LocalVariableElementImpl variable) {
definedVariables.add(variable);
functionElement.setLocalVariables(definedVariables.toArray(new LocalVariableElementImpl[definedVariables.size()]));
}
/**
* When we inject variable, we give access to the library of its type.
*/
private void recordTypeLibraryInjected(LocalVariableElementImpl variable) {
LibraryElement typeLibrary = variable.getType().getElement().getLibrary();
injectedLibraries.add(typeLibrary);
}
private void resolveExpressions(XmlExpression[] expressions) {
for (XmlExpression xmlExpression : expressions) {
if (xmlExpression instanceof AngularXmlExpression) {
AngularXmlExpression angularXmlExpression = (AngularXmlExpression) xmlExpression;
resolveXmlExpression(angularXmlExpression);
}
}
}
/**
* Resolves Angular specific expressions and elements in the {@link #source}.
*
* @param angularElements the {@link AngularElement}s accessible in the component's library, not
* {@code null}
* @param component the {@link AngularComponentElement} to resolve template for, maybe
* {@code null} if not a component template
*/
private void resolveInternal(AngularElement[] angularElements, AngularComponentElement component)
throws AnalysisException {
this.angularElements = angularElements;
// add built-in processors
processors.add(NgModelProcessor.INSTANCE);
processors.add(NgRepeatProcessor.INSTANCE);
// add element's libraries
for (AngularElement angularElement : angularElements) {
injectedLibraries.add(angularElement.getLibrary());
}
// prepare Dart library
createLibraryElement();
((HtmlElementImpl) unit.getElement()).setAngularCompilationUnit(unitElement);
// prepare Dart resolver
createResolver();
// maybe resolving component template
if (component != null) {
defineTopVariable_forClassElement(component);
for (AngularScopePropertyElement scopeProperty : component.getScopeProperties()) {
defineTopVariable_forScopeProperty(scopeProperty);
}
}
// add processors
for (AngularElement angularElement : angularElements) {
NgProcessor processor = createProcessor(angularElement);
if (processor != null) {
processors.add(processor);
}
}
// define formatters
for (AngularElement angularElement : angularElements) {
if (angularElement instanceof AngularFormatterElement) {
defineTopVariable_forClassElement(angularElement);
}
}
// run this HTML visitor
unit.accept(this);
// simulate imports for injects
{
List<ImportElement> imports = Lists.newArrayList();
for (LibraryElement injectedLibrary : injectedLibraries) {
ImportElementImpl importElement = new ImportElementImpl(-1);
importElement.setImportedLibrary(injectedLibrary);
imports.add(importElement);
}
libraryElement.setImports(imports.toArray(new ImportElement[imports.size()]));
}
}
private void resolveXmlExpression(AngularXmlExpression angularXmlExpression) {
AngularExpression angularExpression = angularXmlExpression.getExpression();
resolveExpression(angularExpression);
}
// XXX
private List<Token> splitAtBar(Token token) {
List<Token> tokens = Lists.newArrayList();
tokens.add(token);
while (token.getType() != TokenType.EOF) {
if (token.getType() == TokenType.BAR) {
tokens.add(token);
Token eofToken = new Token(TokenType.EOF, 0);
token.getPrevious().setNext(eofToken);
}
token = token.getNext();
}
return tokens;
}
/**
* The "ng-model" directive is special, it contributes to the top-level name scope. These models
* can be used before actual "ng-model" attribute in HTML. So, we need to define them once we
* found {@link #NG_APP} context.
*/
private void visitModelDirectives(XmlTagNode appNode) {
appNode.accept(new RecursiveXmlVisitor<Void>() {
@Override
public Void visitXmlTagNode(XmlTagNode node) {
NgModelProcessor directive = NgModelProcessor.INSTANCE;
if (directive.canApply(node)) {
directive.applyTopDeclarations(AngularHtmlUnitResolver.this, node);
}
return super.visitXmlTagNode(node);
}
});
}
}