package org.angularjs.codeInsight.refs;
import com.intellij.codeInsight.completion.CompletionUtil;
import com.intellij.lang.javascript.JSTokenTypes;
import com.intellij.lang.javascript.psi.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.patterns.PsiElementPattern;
import com.intellij.psi.*;
import com.intellij.psi.filters.ElementFilter;
import com.intellij.psi.filters.position.FilterPattern;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ProcessingContext;
import org.angularjs.index.AngularIndexUtil;
import org.angularjs.index.AngularJSIndexingHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Dennis.Ushakov
*/
public class AngularJSReferencesContributor extends PsiReferenceContributor {
private static final PsiElementPattern.Capture<JSLiteralExpression> TEMPLATE_PATTERN = literalInProperty("templateUrl");
private static final PsiElementPattern.Capture<JSLiteralExpression> CONTROLLER_PATTERN = literalInProperty("controller");
public static final PsiElementPattern.Capture<PsiElement> UI_VIEW_PATTERN = uiViewPattern();
public static final PsiElementPattern.Capture<XmlAttributeValue> UI_VIEW_REF = xmlAttributePattern("ui-sref");
public static final PsiElementPattern.Capture<XmlAttributeValue> NG_APP_REF = xmlAttributePattern("ng-app");
public static final PsiElementPattern.Capture<JSLiteralExpression> MODULE_PATTERN = modulePattern();
public static final PsiElementPattern.Capture<JSLiteralExpression> MODULE_DEPENDENCY_PATTERN = moduleDependencyPattern();
private static final PsiElementPattern.Capture<JSLiteralExpression> NG_INCLUDE_PATTERN =
PlatformPatterns.psiElement(JSLiteralExpression.class).and(new FilterPattern(new ElementFilter() {
@Override
public boolean isAcceptable(Object element, @Nullable PsiElement context) {
if (element instanceof JSLiteralExpression) {
final JSLiteralExpression literal = (JSLiteralExpression)element;
if (literal.isQuotedLiteral()) {
final PsiElement original = CompletionUtil.getOriginalOrSelf(literal);
final PsiLanguageInjectionHost host = InjectedLanguageUtil.findInjectionHost(original);
if (host instanceof XmlAttributeValue) {
final PsiElement parent = host.getParent();
return parent instanceof XmlAttribute && "ng-include".equals(((XmlAttribute)parent).getName());
}
}
}
return false;
}
@Override
public boolean isClassAcceptable(Class hintClass) {
return true;
}
}));
private static final PsiElementPattern.Capture<JSLiteralExpression> STYLE_PATTERN = PlatformPatterns.psiElement(JSLiteralExpression.class).and(new FilterPattern(new ElementFilter() {
@Override
public boolean isAcceptable(Object element, @Nullable PsiElement context) {
if (element instanceof JSLiteralExpression) {
final JSLiteralExpression literal = (JSLiteralExpression)element;
if (literal.isQuotedLiteral()) {
if ((literal.getParent() instanceof JSArrayLiteralExpression)) {
final JSProperty property = ObjectUtils.tryCast(literal.getParent().getParent(), JSProperty.class);
if (property != null && "styleUrls".equals((property).getName())) {
return AngularIndexUtil.hasAngularJS(literal.getProject());
}
}
}
}
return false;
}
@Override
public boolean isClassAcceptable(Class hintClass) {
return true;
}
}));
public static final PsiElementPattern.Capture<JSParameter> DI_PATTERN = PlatformPatterns.psiElement(JSParameter.class).and(new FilterPattern(new ElementFilter() {
@Override
public boolean isAcceptable(Object element, @Nullable PsiElement context) {
return AngularJSIndexingHandler.isInjectable(context);
}
@Override
public boolean isClassAcceptable(Class hintClass) {
return true;
}
}));
@Override
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
final AngularJSTemplateReferencesProvider templateProvider = new AngularJSTemplateReferencesProvider();
registrar.registerReferenceProvider(TEMPLATE_PATTERN, templateProvider);
registrar.registerReferenceProvider(NG_INCLUDE_PATTERN, templateProvider);
registrar.registerReferenceProvider(STYLE_PATTERN, new PsiReferenceProvider() {
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
return new AngularJSTemplateReferencesProvider.Angular2SoftFileReferenceSet(element).getAllReferences();
}
});
registrar.registerReferenceProvider(CONTROLLER_PATTERN, new AngularJSControllerReferencesProvider());
registrar.registerReferenceProvider(DI_PATTERN, new AngularJSDIReferencesProvider());
registrar.registerReferenceProvider(UI_VIEW_PATTERN, new AngularJSUiRouterViewReferencesProvider());
registrar.registerReferenceProvider(UI_VIEW_REF, new AngularJSUiRouterStatesReferencesProvider());
registrar.registerReferenceProvider(NG_APP_REF, new AngularJSNgAppReferencesProvider());
registrar.registerReferenceProvider(MODULE_PATTERN, new AngularJSModuleReferencesProvider());
}
private static PsiElementPattern.Capture<JSLiteralExpression> modulePattern() {
return PlatformPatterns.psiElement(JSLiteralExpression.class).and(new FilterPattern(new ElementFilter() {
@Override
public boolean isAcceptable(Object element, @Nullable PsiElement context) {
if (element instanceof JSLiteralExpression) {
final PsiElement parent = ((PsiElement)element).getParent();
if (parent instanceof JSArgumentList && parent.getParent() instanceof JSCallExpression
&& ((JSArgumentList)parent).getArguments().length == 1) {
if (PsiTreeUtil.isAncestor(((JSArgumentList)parent).getArguments()[0], (PsiElement)element, false)) {
final JSExpression methodExpression = ((JSCallExpression)parent.getParent()).getMethodExpression();
if (looksLikeAngularModuleReference(methodExpression)) {
return true;
}
}
}
}
return false;
}
@Override
public boolean isClassAcceptable(Class hintClass) {
return true;
}
}));
}
private static PsiElementPattern.Capture<JSLiteralExpression> moduleDependencyPattern() {
return PlatformPatterns.psiElement(JSLiteralExpression.class).and(new FilterPattern(new ElementFilter() {
@Override
public boolean isAcceptable(Object element, @Nullable PsiElement context) {
if (element instanceof JSLiteralExpression) {
PsiElement parent = ((PsiElement)element).getParent();
if (!(parent instanceof JSArrayLiteralExpression)) return false;
parent = parent.getParent();
if (parent instanceof JSArgumentList && parent.getParent() instanceof JSCallExpression
&& ((JSArgumentList)parent).getArguments().length > 1) {
if (PsiTreeUtil.isAncestor(((JSArgumentList)parent).getArguments()[1], (PsiElement)element, false) &&
((JSArgumentList)parent).getArguments()[1] instanceof JSArrayLiteralExpression) {
final JSExpression methodExpression = ((JSCallExpression)parent.getParent()).getMethodExpression();
if (looksLikeAngularModuleReference(methodExpression)) return true;
}
}
}
return false;
}
@Override
public boolean isClassAcceptable(Class hintClass) {
return true;
}
}));
}
static boolean looksLikeAngularModuleReference(JSExpression methodExpression) {
if (methodExpression instanceof JSReferenceExpression && ((JSReferenceExpression)methodExpression).getQualifier() != null &&
AngularJSIndexingHandler.MODULE.equals(((JSReferenceExpression)methodExpression).getReferenceName())) {
return true;
}
return false;
}
private static PsiElementPattern.Capture<JSLiteralExpression> literalInProperty(final String propertyName) {
return PlatformPatterns.psiElement(JSLiteralExpression.class).and(new FilterPattern(new ElementFilter() {
@Override
public boolean isAcceptable(Object element, @Nullable PsiElement context) {
if (element instanceof JSLiteralExpression) {
final JSLiteralExpression literal = (JSLiteralExpression)element;
if (literal.isQuotedLiteral()) {
final PsiElement parent = literal.getParent();
if (parent instanceof JSProperty && propertyName.equals(((JSProperty)parent).getName())) {
return AngularIndexUtil.hasAngularJS(literal.getProject());
}
}
}
return false;
}
@Override
public boolean isClassAcceptable(Class hintClass) {
return true;
}
}));
}
private static PsiElementPattern.Capture<PsiElement> uiViewPattern() {
return PlatformPatterns.psiElement(PsiElement.class).and(new FilterPattern(new ElementFilter() {
@Override
public boolean isAcceptable(Object element, @Nullable PsiElement context) {
if (!(element instanceof PsiElement)) return false;
if (element instanceof JSLiteralExpression ||
element instanceof LeafPsiElement && ((LeafPsiElement)element).getNode().getElementType() == JSTokenTypes.STRING_LITERAL) {
if (!(((PsiElement) element).getParent() instanceof JSProperty)) return false;
// started typing property, variant
PsiElement current = moveUpChain((PsiElement) element,
JSLiteralExpression.class,
JSReferenceExpression.class,
JSProperty.class);
if (!(current instanceof JSProperty) || !acceptablePropertyValue((JSProperty)current)) return false;
current = current.getParent();
if (current != null && checkParentViewsObject(current)) return AngularIndexUtil.hasAngularJS(current.getProject());
}
return false;
}
private boolean acceptablePropertyValue(JSProperty element) {
return element.getNameIdentifier() != null && StringUtil.isQuotedString(element.getNameIdentifier().getText()) &&
(element.getValue() instanceof JSObjectLiteralExpression ||
element.getValue() instanceof JSReferenceExpression || element.getValue() == null);
}
private PsiElement moveUpChain(@Nullable final PsiElement element, @NotNull final Class<? extends PsiElement>... clazz) {
PsiElement current = element;
for (Class<? extends PsiElement> aClass : clazz) {
current = current != null && aClass.isInstance(current.getParent()) ? current.getParent() : current;
}
return current;
}
private boolean checkParentViewsObject(final PsiElement mustBeObject) {
if (mustBeObject instanceof JSObjectLiteralExpression) {
final PsiElement viewsProperty = mustBeObject.getParent();
if (viewsProperty instanceof JSProperty && "views".equals(((JSProperty)viewsProperty).getName())) {
// by now will not go further todo other cases
return true;
}
}
return false;
}
@Override
public boolean isClassAcceptable(Class hintClass) {
return true;
}
}));
}
private static PsiElementPattern.Capture<XmlAttributeValue> xmlAttributePattern(@NotNull final String attributeName) {
return PlatformPatterns.psiElement(XmlAttributeValue.class).and(new FilterPattern(new ElementFilter() {
@Override
public boolean isAcceptable(Object element, @Nullable PsiElement context) {
final XmlAttributeValue attributeValue = (XmlAttributeValue)element;
final PsiElement parent = attributeValue.getParent();
if (parent instanceof XmlAttribute && attributeName.equals(((XmlAttribute)parent).getName())) {
return AngularIndexUtil.hasAngularJS(attributeValue.getProject());
}
return false;
}
@Override
public boolean isClassAcceptable(Class hintClass) {
return true;
}
}));
}
}