package com.intellij.javascript.flex.resolve;
import com.intellij.javascript.flex.mxml.MxmlJSClassProvider;
import com.intellij.lang.ASTNode;
import com.intellij.lang.javascript.JavaScriptSupportLoader;
import com.intellij.lang.javascript.flex.ImportUtils;
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.JSFunction;
import com.intellij.lang.javascript.psi.JSReferenceExpression;
import com.intellij.lang.javascript.psi.ecmal4.*;
import com.intellij.lang.javascript.psi.impl.JSChangeUtil;
import com.intellij.lang.javascript.psi.impl.JSPsiImplUtils;
import com.intellij.lang.javascript.psi.resolve.JSImportHandlingUtil;
import com.intellij.lang.javascript.psi.resolve.JSResolveUtil;
import com.intellij.lang.javascript.psi.resolve.ResolveProcessor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.roots.impl.DirectoryIndex;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.css.CssString;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.Processor;
import com.intellij.util.Query;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* @author yole
*/
public class FlexResolveHelper implements JSResolveHelper {
@Nullable
public PsiElement findClassByQName(final String link, final Project project, final String className, final GlobalSearchScope scope) {
final Ref<JSClass> result = new Ref<>();
final String expectedPackage = link.equals(className) ? "" : link.substring(0, link.length() - className.length() - 1);
final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
final PsiManager manager = PsiManager.getInstance(project);
final Processor<VirtualFile> processor = file -> {
VirtualFile rootForFile = projectFileIndex.getSourceRootForFile(file);
if (rootForFile == null) return true;
if (expectedPackage.equals(VfsUtilCore.getRelativePath(file.getParent(), rootForFile, '.'))) {
PsiFile psiFile = manager.findFile(file);
final JSClass clazz = psiFile instanceof XmlFile ? XmlBackedJSClassFactory.getXmlBackedClass((XmlFile)psiFile):null;
if (clazz != null) {
result.set(clazz);
return false;
}
}
return true;
};
Collection<VirtualFile> files =
FilenameIndex.getVirtualFilesByName(project, className + JavaScriptSupportLoader.MXML_FILE_EXTENSION_DOT, scope);
ContainerUtil.process(files, processor);
if (result.isNull()) {
files = FilenameIndex.getVirtualFilesByName(project, className + JavaScriptSupportLoader.FXG_FILE_EXTENSION_DOT, scope);
ContainerUtil.process(files, processor);
}
return result.get();
}
public boolean importClass(final PsiScopeProcessor processor, final PsiNamedElement file) {
if (file instanceof JSFunction) return true; // there is no need to process package stuff at function level
if (file instanceof XmlBackedJSClassImpl) {
if (!processInlineComponentsInScope((XmlBackedJSClassImpl)file,
inlineComponent -> processor.execute(inlineComponent, ResolveState.initial()))) {
return false;
}
}
final String packageQualifierText = JSResolveUtil.findPackageStatementQualifier(file);
final Project project = file.getProject();
GlobalSearchScope scope = JSResolveUtil.getResolveScope(file);
final MxmlAndFxgFilesProcessor filesProcessor = new MxmlAndFxgFilesProcessor() {
final PsiManager manager = PsiManager.getInstance(project);
public void addDependency(final PsiDirectory directory) {
}
public boolean processFile(final VirtualFile file, final VirtualFile root) {
final PsiFile xmlFile = manager.findFile(file);
if (!(xmlFile instanceof XmlFile)) return true;
return processor.execute(XmlBackedJSClassFactory.getXmlBackedClass((XmlFile)xmlFile), ResolveState.initial());
}
};
PsiFile containingFile = file.getContainingFile();
boolean completion = containingFile.getOriginalFile() != containingFile;
if (completion) {
return processAllMxmlAndFxgFiles(scope, project, filesProcessor, null);
} else {
if (packageQualifierText != null && packageQualifierText.length() > 0) {
if (!processMxmlAndFxgFilesInPackage(scope, project, packageQualifierText, filesProcessor)) return false;
}
return processMxmlAndFxgFilesInPackage(scope, project, "", filesProcessor);
}
}
public boolean processPackage(String packageQualifierText, String resolvedName, Processor<VirtualFile> processor, GlobalSearchScope globalSearchScope,
Project project) {
for(VirtualFile vfile: DirectoryIndex.getInstance(project).getDirectoriesByPackageName(packageQualifierText, globalSearchScope.isSearchInLibraries())) {
if (!globalSearchScope.contains(vfile)) continue;
if (vfile.getFileSystem() instanceof JarFileSystem) {
VirtualFile fileForJar = JarFileSystem.getInstance().getVirtualFileForJar(vfile);
if (fileForJar != null &&
!("swc".equalsIgnoreCase(fileForJar.getExtension()) || "ane".equalsIgnoreCase(fileForJar.getExtension()))) {
continue;
}
}
if (resolvedName != null) {
VirtualFile child = vfile.findChild(resolvedName);
if (child == null) {
child = vfile.findChild(resolvedName + JavaScriptSupportLoader.MXML_FILE_EXTENSION_DOT);
if (child == null) child = vfile.findChild(resolvedName + JavaScriptSupportLoader.FXG_FILE_EXTENSION_DOT);
}
if (child != null) if (!processor.process(child)) return false;
} else {
ProjectFileIndex index = ProjectRootManager.getInstance(project).getFileIndex();
for(VirtualFile child:vfile.getChildren()) {
if (!index.isExcluded(child) && !processor.process(child)) return false;
}
}
}
return true;
}
public boolean isAdequatePlaceForImport(final PsiElement place) {
return place instanceof CssString;
}
@Override
public boolean resolveTypeNameUsingImports(final ResolveProcessor resolveProcessor, PsiNamedElement parent) {
if (parent instanceof XmlBackedJSClassImpl) {
return processInlineComponentsInScope((XmlBackedJSClassImpl)parent,
inlineComponent -> resolveProcessor.execute(inlineComponent, ResolveState.initial()));
}
return true;
}
@Override
public long getResolveResultTimestamp(PsiElement candidate) {
return SwcCatalogXmlUtil.getTimestampFromCatalogXml(candidate);
}
@Override
public JSReferenceExpression bindReferenceToElement(JSReferenceExpression ref,
String qName,
String newName, boolean justMakeQualified, PsiNamedElement element) {
PsiFile file;
if (qName != null &&
(element instanceof XmlBackedJSClass ||
(element instanceof XmlFile && JavaScriptSupportLoader.isFlexMxmFile((PsiFile)element)) ||
(file = element.getContainingFile()) == null ||
file.getLanguage().isKindOf(JavaScriptSupportLoader.ECMA_SCRIPT_L4))) {
boolean qualify;
boolean doImport;
if (justMakeQualified ||
ref.getParent() instanceof JSImportStatement ||
element instanceof PsiDirectoryContainer ||
(ref.getParent() instanceof JSReferenceListMember && ref.getContainingFile().getContext() instanceof XmlAttributeValue)) {
qualify = true;
doImport = false;
}
else {
doImport = JSImportHandlingUtil.evaluateImportStatus(newName, ref) == JSImportHandlingUtil.ImportStatus.ABSENT &&
JSImportHandlingUtil.evaluateImportStatus(ref.getReferencedName(), ref) == JSImportHandlingUtil.ImportStatus.ABSENT;
JSQualifiedNamedElement qualifiedElement = null;
if (element instanceof JSQualifiedNamedElement) {
qualifiedElement = (JSQualifiedNamedElement)element;
}
else if (element instanceof JSFile) {
qualifiedElement = JSPsiImplUtils.findQualifiedElement((JSFile)element);
}
else if (element instanceof XmlFile) {
qualifiedElement = XmlBackedJSClassFactory.getXmlBackedClass(((XmlFile)element));
}
assert qualifiedElement != null:qualifiedElement.getClass();
// at this moment package declaration is out of date so element has it's original qName
qualify = JSResolveUtil.shortReferenceIsAmbiguousOrUnequal(newName, ref, qualifiedElement.getQualifiedName(), null);
}
if (qualify) {
ASTNode newChild = JSChangeUtil.createExpressionFromText(ref.getProject(), qName);
ref.getParent().getNode().replaceChild(ref.getNode(), newChild);
ref = (JSReferenceExpression)newChild.getPsi();
}
if (doImport && qName.indexOf('.') != -1 && !StringUtil.getPackageName(qName).equals(JSResolveUtil.getPackageNameFromPlace(ref))) {
final SmartPsiElementPointer<JSReferenceExpression> refPointer =
SmartPointerManager.getInstance(ref.getProject()).createSmartPsiElementPointer(ref);
ImportUtils.doImport(ref, qName, false);
ref = refPointer.getElement();
}
}
return ref;
}
@Override
public boolean isStrictTypeContext(PsiElement element) {
return true;
}
public static boolean processAllMxmlAndFxgFiles(final GlobalSearchScope scope,
Project project,
final MxmlAndFxgFilesProcessor processor,
final String nameHint) {
final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
for (final VirtualFile root : ProjectRootManager.getInstance(project).getContentSourceRoots()) {
final boolean b = projectFileIndex.iterateContentUnderDirectory(root, fileOrDir -> {
if (scope.contains(fileOrDir) &&
JavaScriptSupportLoader.isMxmlOrFxgFile(fileOrDir) &&
(nameHint == null || nameHint.equals(fileOrDir.getNameWithoutExtension()))) {
if (!processor.processFile(fileOrDir, root)) return false;
}
return true;
});
if (!b) return false;
}
return true;
}
private static boolean processMxmlAndFxgFilesInPackage(final GlobalSearchScope scope, Project project, final String packageName, MxmlAndFxgFilesProcessor processor) {
Query<VirtualFile> packageFiles = DirectoryIndex.getInstance(project).getDirectoriesByPackageName(packageName, scope.isSearchInLibraries());
final PsiManager manager = PsiManager.getInstance(project);
for (VirtualFile packageFile : packageFiles) {
if (!scope.contains(packageFile)) continue;
PsiDirectory dir = manager.findDirectory(packageFile);
if (dir == null) continue;
processor.addDependency(dir);
for (PsiFile file : dir.getFiles()) {
if (JavaScriptSupportLoader.isMxmlOrFxgFile(file)) {
if (!processor.processFile(file.getVirtualFile(), null)) return false;
}
}
}
return true;
}
public interface MxmlAndFxgFilesProcessor {
void addDependency(PsiDirectory directory);
boolean processFile(VirtualFile file, final VirtualFile root);
}
public static boolean mxmlPackageExists(String packageName, Project project, GlobalSearchScope scope) {
return !processMxmlAndFxgFilesInPackage(scope, project, packageName, new MxmlAndFxgFilesProcessor() {
@Override
public void addDependency(final PsiDirectory directory) {
}
@Override
public boolean processFile(final VirtualFile file, final VirtualFile root) {
return false;
}
});
}
private static boolean processInlineComponentsInScope(XmlBackedJSClassImpl context, Processor<XmlBackedJSClass> processor) {
XmlTag rootTag = ((XmlFile)context.getContainingFile()).getDocument().getRootTag();
boolean recursive =
context.getParent().getParentTag() != null && XmlBackedJSClassImpl.isComponentTag(context.getParent().getParentTag());
Collection<XmlBackedJSClass> components = MxmlJSClassProvider.getChildInlineComponents(rootTag, recursive);
return ContainerUtil.process(components, processor);
}
}