package org.angularjs.codeInsight.attributes; import com.intellij.lang.javascript.psi.JSField; import com.intellij.lang.javascript.psi.JSFunction; import com.intellij.lang.javascript.psi.JSType; import com.intellij.lang.javascript.psi.JSTypeUtils; import com.intellij.lang.javascript.psi.stubs.JSImplicitElement; import com.intellij.lang.javascript.psi.types.JSStringLiteralTypeImpl; import com.intellij.lang.javascript.psi.types.JSTypeContext; import com.intellij.lang.javascript.psi.types.JSTypeImpl; import com.intellij.lang.javascript.psi.types.JSTypeSource; import com.intellij.lang.javascript.psi.types.primitives.JSStringType; import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; import com.intellij.util.*; import com.intellij.xml.XmlAttributeDescriptor; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * @author Dennis.Ushakov */ public class AngularBindingDescriptor extends AngularAttributeDescriptor { public static final JSType STRING_TYPE = new JSStringType(true, JSTypeSource.EXPLICITLY_DECLARED, JSTypeContext.INSTANCE); public static final String INPUT = "Input"; public static final NotNullFunction<Pair<PsiElement, String>, XmlAttributeDescriptor> FACTORY = AngularBindingDescriptor::createBinding; public static final NullableFunction<Pair<PsiElement, String>, XmlAttributeDescriptor> FACTORY2 = AngularBindingDescriptor::createOneTimeBinding; public AngularBindingDescriptor(PsiElement element, String attributeName) { super(element.getProject(), attributeName, null, element); } public static XmlAttributeDescriptor[] getBindingDescriptors(JSImplicitElement declaration) { return ArrayUtil.mergeArrays(getFieldBasedDescriptors(declaration, INPUT, FACTORY), getFieldBasedDescriptors(declaration, INPUT, FACTORY2)); } @NotNull private static AngularBindingDescriptor createBinding(Pair<PsiElement, String> dom) { return new AngularBindingDescriptor(dom.first, "[" + dom.second + "]"); } @Nullable private static AngularBindingDescriptor createOneTimeBinding(Pair<PsiElement, String> dom) { PsiElement element = dom.first; if (element instanceof JSImplicitElement) { String type = ((JSImplicitElement)element).getTypeString(); if (type != null && (type.endsWith("String") || type.endsWith("Object"))) { return new AngularBindingDescriptor(element, dom.second); } } final JSType type = expandStringLiteralTypes(element instanceof JSFunction ? ((JSFunction)element).getReturnType() : element instanceof JSField ? ((JSField)element).getType() : null); return type != null && type.isDirectlyAssignableType(STRING_TYPE, null) ? new AngularBindingDescriptor(element, dom.second) : null; } @Contract("null->null") private static JSType expandStringLiteralTypes(@Nullable JSType type) { if (type == null) return null; type = JSTypeUtils.getValuableType(type); ProcessingContext context = new ProcessingContext(); Function<JSType, JSType> expander = new Function<JSType, JSType>() { @Override public JSType fun(@NotNull JSType toApply) { if (toApply instanceof JSStringLiteralTypeImpl) { return STRING_TYPE; } else if (toApply instanceof JSTypeImpl) { JSType typedef = ((JSTypeImpl)toApply).getTypedef(null, context); if (typedef != null && toApply != typedef) { return typedef.transformTypeHierarchy(this); } } return toApply; } }; return type.transformTypeHierarchy(expander); } @Nullable @Override public String handleTargetRename(@NotNull @NonNls String newTargetName) { return "[" + newTargetName + "]"; } }