package org.angularjs.codeInsight.attributes;
import com.intellij.lang.javascript.DialectDetector;
import com.intellij.lang.javascript.index.JSSymbolUtil;
import com.intellij.lang.javascript.psi.*;
import com.intellij.lang.javascript.psi.ecma6.ES6Decorator;
import com.intellij.lang.javascript.psi.ecmal4.JSAttributeList;
import com.intellij.lang.javascript.psi.ecmal4.JSAttributeListOwner;
import com.intellij.lang.javascript.psi.ecmal4.JSClass;
import com.intellij.lang.javascript.psi.stubs.JSElementIndexingData;
import com.intellij.lang.javascript.psi.stubs.JSImplicitElement;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.meta.PsiPresentableMetaData;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.stubs.StubIndex;
import com.intellij.psi.stubs.StubIndexKey;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlElement;
import com.intellij.util.ArrayUtil;
import com.intellij.util.NullableFunction;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xml.XmlAttributeDescriptor;
import com.intellij.xml.impl.BasicXmlAttributeDescriptor;
import com.intellij.xml.impl.XmlAttributeDescriptorEx;
import icons.AngularJSIcons;
import org.angularjs.index.AngularDecoratorsIndex;
import org.angularjs.index.AngularIndexUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author Dennis.Ushakov
*/
public class AngularAttributeDescriptor extends BasicXmlAttributeDescriptor implements XmlAttributeDescriptorEx, PsiPresentableMetaData {
protected final Project myProject;
protected final PsiElement myElement;
private final String myAttributeName;
private final StubIndexKey<String, JSImplicitElementProvider> myIndex;
/**
* NativeScript compatibility
* @deprecated to be removed in 2017.3
*/
public AngularAttributeDescriptor(final Project project,
String attributeName,
final StubIndexKey<String, JSImplicitElementProvider> index) {
this(project, attributeName, index, null);
}
public AngularAttributeDescriptor(final Project project,
String attributeName,
final StubIndexKey<String, JSImplicitElementProvider> index,
PsiElement element) {
myProject = project;
myAttributeName = attributeName;
myIndex = index;
myElement = element;
}
public static XmlAttributeDescriptor[] getFieldBasedDescriptors(JSImplicitElement declaration,
String decorator,
NullableFunction<Pair<PsiElement, String>, XmlAttributeDescriptor> factory) {
final JSClass clazz = PsiTreeUtil.getContextOfType(declaration, JSClass.class);
if (clazz != null) {
JSField[] fields = clazz.getFields();
final List<XmlAttributeDescriptor> result = new ArrayList<>(fields.length);
for (JSField field : fields) {
String decoratedName = getDecoratedName(field, decorator);
if (decoratedName == null) continue;
ContainerUtil.addIfNotNull(result, factory.fun(Pair.create(field, decoratedName)));
}
for (JSFunction function : clazz.getFunctions()) {
String decoratedName = getDecoratedName(function, decorator);
if (decoratedName == null) continue;
ContainerUtil.addIfNotNull(result, factory.fun(Pair.create(function, decoratedName)));
}
return result.toArray(new XmlAttributeDescriptor[result.size()]);
}
if (!DialectDetector.isTypeScript(declaration)) {
return getCompiledFieldBasedDescriptors(declaration, decorator, factory);
}
return EMPTY;
}
@NotNull
private static XmlAttributeDescriptor[] getCompiledFieldBasedDescriptors(JSImplicitElement declaration,
String decorator,
NullableFunction<Pair<PsiElement, String>, XmlAttributeDescriptor> factory) {
Project project = declaration.getProject();
Collection<String> keys = StubIndex.getInstance().getAllKeys(AngularDecoratorsIndex.KEY, project);
GlobalSearchScope scope = GlobalSearchScope.fileScope(declaration.getContainingFile());
JSVariable context = PsiTreeUtil.getContextOfType(declaration, JSVariable.class);
if (context == null) return EMPTY;
final List<XmlAttributeDescriptor> result = new ArrayList<>();
for (String key : keys) {
StubIndex.getInstance().processElements(AngularDecoratorsIndex.KEY, key, project, scope, JSImplicitElementProvider.class, (provider) -> {
JSElementIndexingData data = provider.getIndexingData();
Collection<JSImplicitElement> elements = data != null ? data.getImplicitElements() : null;
if (elements != null) {
for (JSImplicitElement element : elements) {
if (key.equals(element.getName())) {
String type = element.getTypeString();
if (type != null && type.startsWith(decorator + ";") &&
context.isEquivalentTo(PsiTreeUtil.getContextOfType(element, JSVariable.class))) {
ContainerUtil.addIfNotNull(result, factory.fun(Pair.create(element, element.getName())));
}
}
}
}
return true;
});
}
return result.toArray(new XmlAttributeDescriptor[result.size()]);
}
private static String getDecoratedName(JSAttributeListOwner field, String name) {
final JSAttributeList list = field.getAttributeList();
if (list != null) {
for (PsiElement candidate : list.getChildren()) {
if (candidate instanceof ES6Decorator) {
final PsiElement child = candidate.getLastChild();
if (child instanceof JSCallExpression) {
final JSExpression expression = ((JSCallExpression)child).getMethodExpression();
if (expression instanceof JSReferenceExpression &&
JSSymbolUtil.isAccurateReferenceExpressionName((JSReferenceExpression)expression, name)) {
JSExpression[] arguments = ((JSCallExpression)child).getArguments();
if (arguments.length > 0 && arguments[0] instanceof JSLiteralExpression) {
Object value = ((JSLiteralExpression)arguments[0]).getValue();
if (value instanceof String) return (String)value;
}
return field.getName();
}
}
}
}
}
return null;
}
@NotNull
public static XmlAttributeDescriptor[] getFieldBasedDescriptors(JSImplicitElement declaration) {
return ArrayUtil.mergeArrays(AngularBindingDescriptor.getBindingDescriptors(declaration),
AngularEventHandlerDescriptor.getEventHandlerDescriptors(declaration));
}
@Override
public String getName() {
return myAttributeName;
}
@Override
public void init(PsiElement element) {}
@NotNull
@Override
public Object[] getDependences() {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
@Override
public boolean isRequired() {
return false;
}
@Override
public boolean hasIdType() {
return false;
}
@Override
public boolean hasIdRefType() {
return false;
}
@Override
public boolean isEnumerated() {
return myIndex != null;
}
@Override
public boolean isFixed() {
return false;
}
@Override
public String getDefaultValue() {
return null;
}
@Override
public String[] getEnumeratedValues() {
if (myProject == null || myIndex == null) return ArrayUtil.EMPTY_STRING_ARRAY;
return ArrayUtil.toStringArray(AngularIndexUtil.getAllKeys(myIndex, myProject));
}
@Override
protected PsiElement getEnumeratedValueDeclaration(XmlElement xmlElement, String value) {
if (myIndex != null) {
return AngularIndexUtil.resolve(xmlElement.getProject(), myIndex, value);
}
return xmlElement;
}
@Override
public PsiElement getDeclaration() {
return myElement;
}
@Nullable
@Override
public String handleTargetRename(@NotNull @NonNls String newTargetName) {
return newTargetName;
}
@Override
public String getTypeName() {
return null;
}
@Nullable
@Override
public Icon getIcon() {
return AngularJSIcons.Angular2;
}
}