package com.intellij.lang.javascript.flex; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupItem; import com.intellij.javascript.flex.FlexPredefinedTagNames; import com.intellij.javascript.flex.mxml.FlexCommonTypeNames; import com.intellij.javascript.flex.mxml.MxmlJSClass; import com.intellij.javascript.flex.resolve.ActionScriptClassResolver; import com.intellij.lang.javascript.JSTokenTypes; import com.intellij.lang.javascript.JavaScriptSupportLoader; import com.intellij.lang.javascript.completion.JSCompletionContributor; import com.intellij.lang.javascript.completion.JSLookupPriority; import com.intellij.lang.javascript.completion.JSLookupUtilImpl; import com.intellij.lang.javascript.completion.JSSmartCompletionContributor; import com.intellij.lang.javascript.dialects.JSDialectSpecificHandlersFactory; import com.intellij.lang.javascript.index.JSPackageIndex; import com.intellij.lang.javascript.index.JSPackageIndexInfo; import com.intellij.lang.javascript.index.JavaScriptIndex; import com.intellij.lang.javascript.psi.*; import com.intellij.lang.javascript.psi.ecmal4.JSAttribute; import com.intellij.lang.javascript.psi.ecmal4.JSAttributeList; import com.intellij.lang.javascript.psi.ecmal4.JSAttributeNameValuePair; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.lang.javascript.psi.resolve.*; import com.intellij.lang.javascript.psi.types.JSAnyType; import com.intellij.lang.javascript.psi.types.JSContext; import com.intellij.lang.javascript.psi.types.JSGenericTypeImpl; import com.intellij.lang.javascript.psi.types.JSTypeSourceFactory; import com.intellij.lang.javascript.search.JSClassSearch; import com.intellij.lang.javascript.types.TypeFromUsageDetector; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.ResolveState; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiUtilBase; import com.intellij.psi.xml.XmlDocument; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.util.Query; import gnu.trove.THashMap; import gnu.trove.THashSet; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import static com.intellij.lang.javascript.psi.types.JSNamedType.createType; /** * @author yole */ public class ActionScriptSmartCompletionContributor extends JSSmartCompletionContributor { @Nullable @Override public List<?> getSmartCompletionVariants(final @NotNull JSReferenceExpression location) { final PsiElement parent = location.getParent(); List<Object> variants = new ArrayList<>(); if (parent instanceof JSArgumentList && ((JSArgumentList)parent).getArguments()[0] == location && location.getQualifier() == null ) { final JSExpression calledExpr = ((JSCallExpression)parent.getParent()).getMethodExpression(); if (calledExpr instanceof JSReferenceExpression) { final JSReferenceExpression expression = (JSReferenceExpression)calledExpr; @NonNls final String s = expression.getReferencedName(); if (ActionScriptResolveUtil.ADD_EVENT_LISTENER_METHOD.equals(s) || ActionScriptResolveUtil.REMOVE_EVENT_LISTENER_METHOD.equals(s) || "willTrigger".equals(s) || "hasEventListener".equals(s) ) { final MyEventSubclassesProcessor subclassesProcessor = new MyEventSubclassesProcessor(location, variants); subclassesProcessor.findAcceptableVariants(expression); return variants; } } } JSType expectedClassType = JSTypeUtils.getValuableType(findClassType(parent)); if (expectedClassType != null) { JSClass clazz = expectedClassType.resolveClass(); if (clazz != null && !JSGenericTypeImpl.isGenericActionScriptVectorType(expectedClassType)) { final Set<String> processedCandidateNames = new THashSet<>(50); Query<JSClass> query; if (clazz.isInterface()) { query = JSClassSearch.searchInterfaceImplementations(clazz, true, location.getResolveScope()); } else { final String name = clazz.getName(); if (name != null) { LookupElement lookupItem = JSLookupUtilImpl .createPrioritizedLookupItem(clazz, name, JSLookupPriority.MATCHED_TYPE_PRIORITY, false, true); variants.add(lookupItem); } processedCandidateNames.add(clazz.getQualifiedName()); query = JSClassSearch.searchClassInheritors(clazz, true, location.getResolveScope()); } addAllClassesFromQuery(variants, query, parent, processedCandidateNames); if (clazz.isInterface()) { IElementType opSign; if (parent instanceof JSBinaryExpression && ((opSign = ((JSBinaryExpression)parent).getOperationSign()) == JSTokenTypes.AS_KEYWORD || opSign == JSTokenTypes.IS_KEYWORD )) { addAllClassesFromQuery(variants, JSClassSearch.searchClassInheritors(clazz, true, location.getResolveScope()), parent, processedCandidateNames); } } final JSCompletionContributor contributor = JSCompletionContributor.getInstance(); if (!contributor.isDoingSmartCodeCompleteAction()) { contributor.setAlreadyUsedClassesSet(processedCandidateNames); } } else { String typeText = expectedClassType.getTypeText(); if (!(expectedClassType instanceof JSAnyType)) { variants.add(JSLookupUtilImpl.createPrioritizedLookupItem( clazz, ImportUtils.importAndShortenReference(typeText, parent, false, true).first + "()", JSLookupPriority.SMART_PRIORITY, true, true )); } JSResolveUtil.GenericSignature signature = JSResolveUtil.extractGenericSignature(typeText); if (signature != null) { variants.add(JSLookupUtilImpl.createPrioritizedLookupItem( createType(JSCommonTypeNames.ARRAY_CLASS_NAME, JSTypeSourceFactory.createTypeSource(parent), JSContext.INSTANCE).resolveClass(), "<" + ImportUtils.importAndShortenReference(signature.genericType, parent, false, true).first + ">" + "[]", JSLookupPriority.SMART_PRIORITY, true, true )); } } return variants.isEmpty() ? Collections.emptyList() : variants; } else if (location.getQualifier() == null) { if (JSResolveUtil.isExprInStrictTypeContext(location)) { if (parent instanceof JSVariable || parent instanceof JSFunction) { JSType type = TypeFromUsageDetector.detectTypeFromUsage(parent, parent.getContainingFile()); if (type == null && parent instanceof JSVariable) { PsiElement parent2 = parent.getParent(); PsiElement grandParent = parent2 instanceof JSVarStatement ? parent2.getParent() : null; if (grandParent instanceof JSForInStatement && ((JSForInStatement)grandParent).isForEach() && parent2 == ((JSForInStatement)grandParent).getDeclarationStatement() ) { JSExpression expression = ((JSForInStatement)grandParent).getCollectionExpression(); if (expression != null) { JSType expressionType = JSResolveUtil.getExpressionJSType(expression); if (expressionType != null) { final JSType componentType = JSTypeUtils.getIndexableComponentType(expressionType); if (componentType != null) { type = componentType; } } } } } final String qualifiedNameMatchingType = type != null ? JSTypeUtils.getQualifiedNameMatchingType(type, false) : null; if (qualifiedNameMatchingType != null) { String qName = JSDialectSpecificHandlersFactory.forElement(location).getImportHandler() .resolveTypeName(qualifiedNameMatchingType, location) .getQualifiedName(); variants.add(JSLookupUtilImpl.createPrioritizedLookupItem( JSDialectSpecificHandlersFactory.forElement(location).getClassResolver().findClassByQName(qName, location), ImportUtils.importAndShortenReference(qName, parent, false, true).first, JSLookupPriority.SMART_PRIORITY, true, true )); } } } else { variants = addVariantsForUnqualifiedReference(location); } } return variants.isEmpty() ? null : variants; } @Override protected void processClasses(PsiElement parentInOriginalTree, final SinkResolveProcessor<?> processor) { final Project project = parentInOriginalTree.getProject(); final GlobalSearchScope resolveScope = JSResolveUtil.getResolveScope(parentInOriginalTree); final LinkedHashSet<String> qualifiedNames = new LinkedHashSet<>(); JSPackageIndex.processElementsInScopeRecursive("", new JSPackageIndex.PackageQualifiedElementsProcessor() { @Override public boolean process(String qualifiedName, JSPackageIndexInfo.Kind kind, boolean isPublic) { if (kind != JSPackageIndexInfo.Kind.FUNCTION && kind != JSPackageIndexInfo.Kind.VARIABLE) return true; qualifiedNames.add(qualifiedName); return true; } }, resolveScope, project); for (String qualifiedName : qualifiedNames) { PsiElement element = JSDialectSpecificHandlersFactory.forLanguage(JavaScriptSupportLoader.ECMA_SCRIPT_L4).getClassResolver() .findClassByQName(qualifiedName, resolveScope); if (element != null && !processor.execute(element, ResolveState.initial())) { return; } } } private static void addAllClassesFromQuery(List<Object> variants, Query<JSClass> query, PsiElement place, Set<String> processedCandidateNames) { Collection<JSClass> all = query.findAll(); String packageName = place != null ? JSResolveUtil.getPackageNameFromPlace(place) : ""; for (JSClass result : all) { if (ActionScriptResolveUtil.hasExcludeClassMetadata(result)) continue; if (!JSResolveUtil.isAccessibleFromCurrentPackage(result, packageName, place)) continue; if (!processedCandidateNames.add(result.getQualifiedName())) continue; variants.add(JSLookupUtilImpl.createPrioritizedLookupItem(result, result.getName(), JSLookupPriority.SMART_PRIORITY, false, true)); } } @Nullable public static JSClass findClassOfQualifier(JSReferenceExpression expression) { JSExpression qualifier = expression.getQualifier(); JSClass clazzToProcess = null; if (qualifier != null) { qualifier = PsiUtilBase.getOriginalElement(qualifier, qualifier.getClass()); clazzToProcess = qualifier != null ? JSResolveUtil.findClassOfQualifier(qualifier, qualifier.getContainingFile()) : null; } if (clazzToProcess == null) { clazzToProcess = JSResolveUtil.getClassOfContext(expression); } return clazzToProcess; } public static Map<String, String> getEventsMap(JSClass clazzToProcess) { if (clazzToProcess == null) return Collections.emptyMap(); final Map<String, String> eventsMap = new THashMap<>(); class EventsDataCollector extends ResolveProcessor implements ActionScriptResolveUtil.MetaDataProcessor { public EventsDataCollector() { super(null); setToProcessHierarchy(true); setToProcessMembers(false); setTypeContext(true); setLocalResolve(true); } @Override public boolean process(@NotNull final JSAttribute jsAttribute) { if ("Event".equals(jsAttribute.getName())) { final JSAttributeNameValuePair eventAttr = jsAttribute.getValueByName("name"); JSAttributeNameValuePair typeAttr = jsAttribute.getValueByName("type"); if (eventAttr != null && typeAttr != null) { final String simpleValue = eventAttr.getSimpleValue(); if (simpleValue != null) { eventsMap.put(simpleValue, typeAttr.getSimpleValue()); } } } return true; } @Override public boolean handleOtherElement(final PsiElement el, final PsiElement context, final Ref<PsiElement> continuePassElement) { return true; } @Override public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) { if (element instanceof JSClass) { ActionScriptResolveUtil.processMetaAttributesForClass(element, this, true); } return true; } } final EventsDataCollector eventsDataCollector = new EventsDataCollector(); if (clazzToProcess instanceof XmlBackedJSClassImpl) { XmlFile file = (XmlFile)clazzToProcess.getParent().getContainingFile(); if (file != null && JavaScriptSupportLoader.isFlexMxmFile(file)) { final XmlDocument xmlDocument = file.getDocument(); final XmlTag rootTag = xmlDocument == null ? null : xmlDocument.getRootTag(); final XmlTag[] tags = rootTag == null ? XmlTag.EMPTY : MxmlJSClass.findLanguageSubTags(rootTag, FlexPredefinedTagNames.METADATA); JSResolveUtil.JSInjectedFilesVisitor injectedFilesVisitor = new JSResolveUtil.JSInjectedFilesVisitor() { @Override protected void process(JSFile file) { for (PsiElement element : file.getChildren()) { if (element instanceof JSAttributeList) { ActionScriptResolveUtil.processAttributeList(eventsDataCollector, null, (JSAttributeList)element, true, true); } } } }; for (XmlTag tag : tags) { JSResolveUtil.processInjectedFileForTag(tag, injectedFilesVisitor); } } } clazzToProcess.processDeclarations(eventsDataCollector, ResolveState.initial(), clazzToProcess, clazzToProcess); return eventsMap; } @Override protected int processContextClass(@NotNull JSReferenceExpression location, JSType expectedType, PsiElement parent, List<Object> variants, int qualifiedStaticVariantsStart, SinkResolveProcessor<?> processor, JSClass ourClass) { JSClass clazz = expectedType.resolveClass(); if (clazz != null && !clazz.isEquivalentTo(ourClass)) { qualifiedStaticVariantsStart = processor.getResultSink().getResultCount(); processStaticsOf(clazz, processor, ourClass); } if (ourClass != null && clazz != null && JSInheritanceUtil.isParentClass(ourClass, clazz, false) && !JSResolveUtil.calculateStaticFromContext(location) && JSCompletionContributor.getInstance().isDoingSmartCodeCompleteAction() ) { variants.add(JSLookupUtilImpl.createPrioritizedLookupItem( null, "this", JSLookupPriority.SMART_PRIORITY, true, true )); } if (parent instanceof JSArgumentList) { JSParameterItem param = JSResolveUtil.findParameterForUsedArgument(location, (JSArgumentList)parent); if (param instanceof JSParameter) { PsiElement element = JSResolveUtil.findParent(((JSParameter)param).getParent().getParent()); if (element instanceof JSClass && !element.isEquivalentTo(ourClass) && !element.isEquivalentTo(clazz)) { processStaticsOf((JSClass)element, processor, ourClass); } } } return qualifiedStaticVariantsStart; } private static class MyEventSubclassesProcessor extends ResolveProcessor { private final JavaScriptIndex index; private final PsiElement myExpr; private final List<Object> myVariants; private final ResolveState state = new ResolveState(); private Map<String, String> myEventsMap = new THashMap<>(); public MyEventSubclassesProcessor(final PsiElement expr, final List<Object> variants) { super(null); myExpr = expr; myVariants = variants; index = JavaScriptIndex.getInstance(myExpr.getProject()); setToProcessHierarchy(true); } public boolean process(final JSClass clazz) { clazz.processDeclarations(this, state, clazz, clazz); return true; } @Override public boolean execute(@NotNull final PsiElement element, @NotNull final ResolveState state) { if (element instanceof JSVariable) { final JSVariable variable = (JSVariable)element; final JSAttributeList attributeList = variable.getAttributeList(); if (attributeList != null && attributeList.getAccessType() == JSAttributeList.AccessType.PUBLIC && attributeList.hasModifier(JSAttributeList.ModifierType.STATIC) && "String".equals(variable.getTypeString()) ) { final String s = variable.getLiteralOrReferenceInitializerText(); if (s != null && StringUtil.startsWith(s, "\"") && StringUtil.endsWith(s, "\"")) { String key = StringUtil.stripQuotesAroundValue(s); String event = myEventsMap.get(key); if (event == null) return true; PsiElement parent = JSResolveUtil.findParent(element); if (!(parent instanceof JSClass) || !event.equals(((JSClass)parent).getQualifiedName())) return true; String name = variable.getName(); LookupElement lookupItem = JSLookupUtilImpl.createPrioritizedLookupItem( variable, ((JSClass)parent).getName() + "." + name, JSLookupPriority.SMART_PRIORITY, false, true ); ((LookupItem)lookupItem).addLookupStrings(name); myVariants.add(lookupItem); } } } return true; } public void findAcceptableVariants(JSReferenceExpression expression) { JSClass clazzToProcess = findClassOfQualifier(expression); if (clazzToProcess == null) return; myEventsMap = getEventsMap(clazzToProcess); final PsiElement eventClass1 = ActionScriptClassResolver .findClassByQName(FlexCommonTypeNames.FLASH_EVENT_FQN, index, ModuleUtilCore.findModuleForPsiElement(expression)); if ((eventClass1 instanceof JSClass)) { setToProcessMembers(true); setTypeContext(false); final Set<String> visited = new THashSet<>(); for (JSClass cls : JSClassSearch.searchClassInheritors((JSClass)eventClass1, true, expression.getResolveScope()).findAll()) { if (!visited.add(cls.getQualifiedName())) continue; process(cls); } } final PsiElement eventClass2 = ActionScriptClassResolver .findClassByQName(FlexCommonTypeNames.STARLING_EVENT_FQN, index, ModuleUtilCore.findModuleForPsiElement(expression)); if ((eventClass2 instanceof JSClass)) { setToProcessMembers(true); setTypeContext(false); final Set<String> visited = new THashSet<>(); for (JSClass cls : JSClassSearch.searchClassInheritors((JSClass)eventClass2, true, expression.getResolveScope()).findAll()) { if (!visited.add(cls.getQualifiedName())) continue; process(cls); } } } } private static void processStaticsOf(JSClass parameterClass, ResolveProcessor processor, @Nullable JSClass contextClass) { processor.configureClassScope(contextClass); boolean savedProcessStatics = processor.getAccessibilityProcessingHandler().isProcessStatics(); try { processor.getAccessibilityProcessingHandler().setProcessStatics(true); processor.setTypeName(parameterClass.getQualifiedName()); parameterClass.processDeclarations(processor, ResolveState.initial(), parameterClass, parameterClass); } finally { processor.getAccessibilityProcessingHandler().setProcessStatics(savedProcessStatics); } } }