package com.intellij.javascript.flex.resolve; import com.intellij.lang.javascript.JavaScriptSupportLoader; import com.intellij.lang.javascript.dialects.JSDialectSpecificHandlersFactory; import com.intellij.lang.javascript.flex.JSResolveHelper; import com.intellij.lang.javascript.flex.XmlBackedJSClassImpl; import com.intellij.lang.javascript.psi.JSFile; import com.intellij.lang.javascript.psi.JSNamedElement; import com.intellij.lang.javascript.psi.JSReferenceExpression; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.lang.javascript.psi.ecmal4.JSPackageStatement; import com.intellij.lang.javascript.psi.ecmal4.JSQualifiedNamedElement; import com.intellij.lang.javascript.psi.ecmal4.XmlBackedJSClassFactory; import com.intellij.lang.javascript.psi.resolve.*; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.UserDataCache; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiNamedElement; import com.intellij.psi.ResolveResult; import com.intellij.psi.ResolveState; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.util.CachedValue; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.psi.xml.XmlElement; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.util.containers.ContainerUtil; import com.intellij.xml.XmlElementDescriptor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Map; /** * @author Konstantin.Ulitin */ public class ActionScriptImportHandler extends JSImportHandler { private static ActionScriptImportHandler INSTANCE = new ActionScriptImportHandler(); protected ActionScriptImportHandler() {} @SuppressWarnings("MethodOverridesStaticMethodOfSuperclass") public static JSImportHandler getInstance() { return INSTANCE; } public static final Key<CachedValue<Map<String, JSImportedElementResolveResult>>> ourImportResolveCache = Key.create("js.import.resolve"); public static final UserDataCache<CachedValue<Map<String, JSImportedElementResolveResult>>,PsiElement, Object> myImportResolveCache = new UserDataCache<CachedValue<Map<String, JSImportedElementResolveResult>>, PsiElement, Object>() { protected CachedValue<Map<String, JSImportedElementResolveResult>> compute(final PsiElement psiElement, final Object p) { return CachedValuesManager .getManager(psiElement.getProject()).createCachedValue(() -> new CachedValueProvider.Result<Map<String, JSImportedElementResolveResult>>( ContainerUtil.newConcurrentMap(), PsiModificationTracker.MODIFICATION_COUNT), false); } }; @NotNull @Override public JSTypeResolveResult resolveName(@NotNull String type, @NotNull PsiElement context) { final JSImportedElementResolveResult result = _resolveTypeName(type, context); String resolvedType = result != null ? result.qualifiedName : type; return new JSTypeResolveResult(resolvedType != null ? resolvedType : type, null); } // TODO _str should be JSReferenceExpression for caching! @Nullable private JSImportedElementResolveResult _resolveTypeName(@Nullable final String _name, @NotNull PsiElement context) { String name = _name; if (name == null) return null; JSResolveUtil.GenericSignature genericSignature = JSResolveUtil.extractGenericSignature(name); if (genericSignature != null) { name = genericSignature.elementType; } final Ref<JSImportedElementResolveResult> resultRef = new Ref<>(); final String name1 = name; JSResolveUtil.walkOverStructure(context, context1 -> { JSImportedElementResolveResult resolved = null; if (context1 instanceof XmlBackedJSClassImpl) { // reference list in mxml XmlTag rootTag = ((XmlBackedJSClassImpl)context1).getParent(); if (rootTag != null && name1.equals(rootTag.getLocalName())) { final XmlElementDescriptor descriptor = rootTag.getDescriptor(); PsiElement element = descriptor != null ? descriptor.getDeclaration():null; if (element instanceof XmlFile) { element = XmlBackedJSClassFactory.getXmlBackedClass((XmlFile)element); } final String s = element instanceof JSClass ? ((JSClass)element).getQualifiedName() : rootTag.getLocalName(); resolved = new JSImportedElementResolveResult(s); } else { resolved = resolveTypeNameUsingImports(name1, context1); } } else if (context1 instanceof JSQualifiedNamedElement) { if (context1 instanceof JSClass && name1.equals(context1.getName())) { resolved = new JSImportedElementResolveResult(((JSQualifiedNamedElement)context1).getQualifiedName()); } else { resolved = resolveTypeNameUsingImports(name1, context1); if (resolved == null && context1.getParent() instanceof JSFile) { final String qName = ((JSQualifiedNamedElement)context1).getQualifiedName(); final String packageName = qName == null ? "" : context1 instanceof JSPackageStatement ? qName + "." : qName.substring( 0, qName.lastIndexOf('.') + 1); if (packageName.length() != 0) { final PsiElement byQName = JSClassResolver.findClassFromNamespace(packageName + name1, context1); if (byQName instanceof JSQualifiedNamedElement) { resolved = new JSImportedElementResolveResult(((JSQualifiedNamedElement)byQName).getQualifiedName()); } } } } } else { resolved = resolveTypeNameUsingImports(name1, context1); PsiElement contextOfContext; if (resolved == null && context1 instanceof JSFile && (contextOfContext = context1.getContext()) != null) { XmlBackedJSClassImpl clazz = contextOfContext instanceof XmlElement ? (XmlBackedJSClassImpl)XmlBackedJSClassImpl.getContainingComponent((XmlElement)contextOfContext) : null; if (clazz != null) { SinkResolveProcessor r = new SinkResolveProcessor(name1, new ResolveResultSink(null, name1)); r.setForceImportsForPlace(true); boolean b = clazz.doImportFromScripts(r, clazz); if(!b) { PsiElement resultFromProcessor = r.getResult(); JSQualifiedNamedElement clazzFromComponent = resultFromProcessor instanceof JSQualifiedNamedElement ? (JSQualifiedNamedElement)resultFromProcessor : null; if (clazzFromComponent != null) { resolved = new JSImportedElementResolveResult(clazzFromComponent.getQualifiedName(), clazz, null); } } } } } if (resolved != null) { resultRef.set(resolved); return false; } if (context1 instanceof JSPackageStatement) return false; return true; }); JSImportedElementResolveResult result = resultRef.get(); if (genericSignature != null && result != null) { // TODO: more than one type parameter StringBuilder genericSignatureBuffer = new StringBuilder(); genericSignatureBuffer.append(".<"); genericSignatureBuffer.append(resolveTypeName(genericSignature.genericType, context).getQualifiedName()); genericSignatureBuffer.append(">"); result = result.appendSignature(genericSignatureBuffer.toString()); } return result; } private static JSNamedElement resolveTypeNameInTheSamePackage(@NotNull final String str, final PsiElement context) { JSNamedElement fileLocalElement = JSResolveUtil.findFileLocalElement(str, context); if (fileLocalElement != null) return fileLocalElement; final String packageQualifierText = JSResolveUtil.findPackageStatementQualifier(context); PsiElement byQName; if (packageQualifierText != null) { byQName = JSClassResolver.findClassFromNamespace(packageQualifierText + "." + str, context); if (byQName instanceof JSQualifiedNamedElement) { return (JSQualifiedNamedElement)byQName; } } byQName = JSDialectSpecificHandlersFactory.forElement(context).getClassResolver().findClassByQName(str, context); if (byQName instanceof JSQualifiedNamedElement && JSResolveUtil.acceptableSymbol((JSQualifiedNamedElement)byQName, JSResolveUtil.GlobalSymbolsAcceptanceState.WHATEVER, false, context)) { return (JSQualifiedNamedElement)byQName; } return null; } @Nullable @Override public JSImportedElementResolveResult resolveTypeNameUsingImports(@NotNull JSReferenceExpression expr) { if (expr.getQualifier() != null) return null; if (JSResolveUtil.getElementThatShouldBeQualified(expr, null) != null) return null; if (expr.getReferencedName() == null) return null; return _resolveTypeName(expr.getText(), expr); } private static @Nullable JSImportedElementResolveResult resolveTypeNameUsingImports(final @NotNull String referencedName, PsiNamedElement parent) { final Map<String, JSImportedElementResolveResult> map = myImportResolveCache.get(ourImportResolveCache, parent, null).getValue(); JSImportedElementResolveResult result = map.get(referencedName); if (result == null) { SinkResolveProcessor resolveProcessor = new SinkResolveProcessor(referencedName, new ResolveResultSink(null, referencedName)); INSTANCE.resolveTypeNameUsingImportsInner(resolveProcessor, parent); final ResolveResult[] resolveResults = resolveProcessor.getResultsAsResolveResults(); assert resolveResults.length < 2; if (resolveResults.length == 1 && resolveResults[0] instanceof JSResolveResult) { JSResolveResult resolveResult = (JSResolveResult)resolveResults[0]; final PsiElement element = resolveResult.getElement(); String typeName = ((JSQualifiedNamedElement)element).getQualifiedName(); result = new JSImportedElementResolveResult(typeName, element, resolveResult.getActionScriptImport()); } map.put(referencedName, result != null ? result: JSImportedElementResolveResult.EMPTY_RESULT); } return result != JSImportedElementResolveResult.EMPTY_RESULT ? result:null; } private boolean resolveTypeNameUsingImportsInner(final ResolveProcessor resolveProcessor, final PsiNamedElement parent) { final PsiElement element = JSResolveUtil.getClassReferenceForXmlFromContext(parent); if (!JSImportHandlingUtil.ourPsiScopedImportSet.tryResolveImportedClass(parent, resolveProcessor)) return false; if (parent instanceof JSPackageStatement) { final JSNamedElement jsClass = resolveTypeNameInTheSamePackage(resolveProcessor.getName(), parent); return jsClass == null || resolveProcessor.execute(jsClass, ResolveState.initial()); } else if (parent instanceof JSFile && parent.getLanguage().isKindOf(JavaScriptSupportLoader.ECMA_SCRIPT_L4)) { if (element instanceof XmlBackedJSClassImpl) { //if(!((XmlBackedJSClassImpl)element).doImportFromScripts(resolveProcessor, parent)) return false; REMOVE? JSNamedElement jsClass = resolveTypeNameInTheSamePackage(resolveProcessor.getName(), element); if (jsClass == null) { final JSClass parentClass = (JSClass)element; final JSClass[] classes = parentClass.getSuperClasses(); if (classes != null && classes.length > 0 && resolveProcessor.getName().equals(classes[0].getName())) { jsClass = classes[0]; } } if (jsClass != null && !resolveProcessor.execute(jsClass, ResolveState.initial())) return false; } else { JSNamedElement jsClass = resolveTypeNameInTheSamePackage(resolveProcessor.getName(), element); return jsClass == null || resolveProcessor.execute(jsClass, ResolveState.initial()); } } else if (parent instanceof XmlBackedJSClassImpl) { JSNamedElement jsClass = resolveTypeNameInTheSamePackage(resolveProcessor.getName(), parent); if (jsClass != null && !resolveProcessor.execute(jsClass, ResolveState.initial())) return false; } for (JSResolveHelper helper : Extensions.getExtensions(JSResolveHelper.EP_NAME)) { if (!helper.resolveTypeNameUsingImports(resolveProcessor, parent)) { return false; } } return true; } public boolean importClass(final PsiScopeProcessor processor, final PsiNamedElement parent) { final ResolveProcessor resolveProcessor = (ResolveProcessor)processor; if(resolveProcessor.isLocalResolve() || resolveProcessor.needPackages()) return true; final String s = resolveProcessor.getName(); if (s != null) { if (resolveProcessor.needsAllVariants()) { return resolveTypeNameUsingImportsInner(resolveProcessor, parent); } final JSImportedElementResolveResult expression = resolveTypeNameUsingImports(s, parent); return !dispatchResult(expression, processor); } else { return importClassViaHelper(processor, parent); } } private static boolean dispatchResult(JSImportedElementResolveResult expression, PsiScopeProcessor processor) { if (expression != null) { final PsiElement element = expression.resolvedElement; if (element != null) { ResolveState state = ResolveState.initial(); if (expression.importStatement != null) state = state.put(ResolveProcessor.IMPORT_KEY, expression.importStatement); return !processor.execute(element, state); } } return false; } public static boolean importClassViaHelper(final PsiScopeProcessor processor, final PsiNamedElement file) { for(JSResolveHelper helper: Extensions.getExtensions(JSResolveHelper.EP_NAME)) { if (!helper.importClass(processor, file)) return false; } return true; } }