package com.intellij.lang.javascript.flex; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.roots.*; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.newvfs.ManagingFS; import com.intellij.psi.*; import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference; import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceSet; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; /** * @author Maxim.Mossienko * @since 08.04.2009 */ public class ReferenceSupport { enum RelativeToWhat { Absolute, CurrentFile, ProjectRoot, SourceRoot, Other } public static PsiReference[] getFileRefs(final PsiElement elt, final PsiElement valueNode, final int offset, final LookupOptions lookupOptions) { String str = StringUtil.stripQuotesAroundValue(valueNode.getText()); return getFileRefs(elt, offset, str, lookupOptions); } public static PsiReference[] getFileRefs(@NotNull PsiElement elt, final int offset, String str, final LookupOptions lookupOptions) { if (lookupOptions.IGNORE_TEXT_AFTER_HASH) { int hashIndex = str.indexOf('#'); if (hashIndex != -1) str = str.substring(0, hashIndex); } final RelativeToWhat relativeToWhat = relativeToWhat(str, elt, lookupOptions); final boolean startsWithSlash = str.startsWith("/"); final FileReferenceSet base = new FileReferenceSet(str, elt, offset, null, SystemInfo.isFileSystemCaseSensitive) { @Override public boolean isAbsolutePathReference() { return relativeToWhat == RelativeToWhat.Absolute; } @Override public boolean couldBeConvertedTo(final boolean relative) { return ((relative && lookupOptions.RELATIVE_TO_FILE) || (!relative && lookupOptions.ABSOLUTE)) && super.couldBeConvertedTo(relative); } @Override public FileReference createFileReference(final TextRange range, final int index, final String text) { return new JSFlexFileReference(this, range, index, text, relativeToWhat); } @NotNull @Override public Collection<PsiFileSystemItem> computeDefaultContexts() { PsiFile psiFile = getContainingFile(); if (psiFile == null) return Collections.emptyList(); PsiElement context = psiFile.getContext(); if (context instanceof PsiLanguageInjectionHost) { psiFile = context.getContainingFile(); } psiFile = psiFile.getOriginalFile(); final List<VirtualFile> dirs = new ArrayList<>(); // paths relative to file should not start with slash if (lookupOptions.RELATIVE_TO_FILE && !startsWithSlash) { appendFileLocation(dirs, psiFile); } if ((lookupOptions.RELATIVE_TO_SOURCE_ROOTS_START_WITH_SLASH && startsWithSlash) || (lookupOptions.RELATIVE_TO_SOURCE_ROOTS_START_WITHOUT_SLASH && !startsWithSlash)) { appendSourceRoots(dirs, psiFile); } if (lookupOptions.ABSOLUTE) { appendFileSystemRoots(dirs); } if (lookupOptions.RELATIVE_TO_PROJECT_BASE_DIR) { dirs.add(psiFile.getProject().getBaseDir()); } if (lookupOptions.IN_ROOTS_OF_MODULE_DEPENDENCIES) { appendRootsOfModuleDependencies(dirs, ModuleUtilCore.findModuleForPsiElement(psiFile)); } return toFileSystemItems(dirs); } }; return base.getAllReferences(); } // prevent certain tests from failing VirtualDirectoryImpl.assertAccessInTests() check public static boolean ALLOW_ABSOLUTE_REFERENCES_IN_TESTS = true; private static RelativeToWhat relativeToWhat(final String path, final PsiElement psiElement, final LookupOptions lookupOptions) { if (lookupOptions.ABSOLUTE && (ALLOW_ABSOLUTE_REFERENCES_IN_TESTS || !ApplicationManager.getApplication().isUnitTestMode())) { if (SystemInfo.isWindows) { if (path.length() > 2 && path.charAt(1) == ':' && Character.isLetter(path.charAt(0))) { return RelativeToWhat.Absolute; } } else if (path.startsWith("/") && LocalFileSystem.getInstance().findFileByPath(path) != null) { return RelativeToWhat.Absolute; } } if (lookupOptions.RELATIVE_TO_FILE) { PsiFile psiFile = psiElement.getContainingFile(); final PsiElement context = psiFile.getContext(); if (context != null) psiFile = context.getContainingFile(); final VirtualFile vFile = psiFile.getVirtualFile(); if (vFile != null && VfsUtilCore.findRelativeFile(path, vFile) != null) { return RelativeToWhat.CurrentFile; } } if (lookupOptions.RELATIVE_TO_PROJECT_BASE_DIR && VfsUtilCore.findRelativeFile(path, psiElement.getProject().getBaseDir()) != null) { return RelativeToWhat.ProjectRoot; } if ((lookupOptions.RELATIVE_TO_SOURCE_ROOTS_START_WITH_SLASH && path.startsWith("/")) || lookupOptions.RELATIVE_TO_SOURCE_ROOTS_START_WITHOUT_SLASH && !path.startsWith("/")) { final Module module = ModuleUtilCore.findModuleForPsiElement(psiElement); if (module != null) { for (final VirtualFile sourceRoot : ModuleRootManager.getInstance(module).getSourceRoots()) { if (VfsUtilCore.findRelativeFile(path, sourceRoot) != null) { return RelativeToWhat.SourceRoot; } } } } // consider unresolved paths as relative to file if possible, it is needed for correct refactoring return lookupOptions.RELATIVE_TO_FILE ? RelativeToWhat.CurrentFile : RelativeToWhat.Other; } private static void appendFileLocation(final List<VirtualFile> dirs, final PsiFile psiFile) { final VirtualFile file = psiFile.getVirtualFile(); if (file != null) { dirs.add(file.getParent()); } } private static void appendSourceRoots(final Collection<VirtualFile> dirs, final PsiFile psiFile) { final VirtualFile file = psiFile.getVirtualFile(); if (file != null && ProjectRootManager.getInstance(psiFile.getProject()).getFileIndex().getSourceRootForFile(file) != null) { appendSourceRoots(dirs, ModuleUtilCore.findModuleForPsiElement(psiFile)); } } private static void appendSourceRoots(final Collection<VirtualFile> dirs, final Module module) { if (module != null) { ContainerUtil.addAll(dirs, ModuleRootManager.getInstance(module).getSourceRoots()); } } private static void appendFileSystemRoots(final Collection<VirtualFile> dirs) { ContainerUtil.addAll(dirs, ManagingFS.getInstance().getLocalRoots()); } private static void appendRootsOfModuleDependencies(final List<VirtualFile> dirs, final Module module) { if (module != null) { final OrderEntry[] orderEntries = ModuleRootManager.getInstance(module).getOrderEntries(); for (final OrderEntry orderEntry : orderEntries) { if (orderEntry instanceof LibraryOrderEntry || orderEntry instanceof JdkOrderEntry) { final VirtualFile[] files = orderEntry.getFiles(OrderRootType.CLASSES); for (final VirtualFile file : files) { if ("swc".equalsIgnoreCase(file.getExtension())) { dirs.add(file); } } } else if (orderEntry instanceof ModuleOrderEntry) { appendSourceRoots(dirs, ((ModuleOrderEntry)orderEntry).getModule()); } } } } public static class LookupOptions { // default is absolute or relative to current file public static final LookupOptions SCRIPT_SOURCE = new LookupOptions(false, true, true, false, false, false, false); public static final LookupOptions STYLE_SOURCE = new LookupOptions(false, true, true, true, true, false, true); public static final LookupOptions XML_AND_MODEL_SOURCE = new LookupOptions(false, true, true, true, false, false, false); public static final LookupOptions EMBEDDED_ASSET = new LookupOptions(true, true, true, true, false, false, true); public static final LookupOptions NON_EMBEDDED_ASSET = new LookupOptions(false, true, false, false, true, false, false); public static final LookupOptions FLEX_COMPILER_CONFIG_PATH_ELEMENT = new LookupOptions(false, true, true, false, false, true, false); public final boolean IGNORE_TEXT_AFTER_HASH; public final boolean ABSOLUTE; public final boolean RELATIVE_TO_FILE; public final boolean RELATIVE_TO_SOURCE_ROOTS_START_WITH_SLASH; public final boolean RELATIVE_TO_SOURCE_ROOTS_START_WITHOUT_SLASH; public final boolean RELATIVE_TO_PROJECT_BASE_DIR; // better name would be RELATIVE_TO_COMPILER_START_DIR but FlexUtils.getFlexCompilerStartDirectory() is not accessible from this class public final boolean IN_ROOTS_OF_MODULE_DEPENDENCIES; public LookupOptions(final boolean ignoreTextAfterHash, final boolean absolute, final boolean relativeToFile, final boolean relativeToSourceRootsStartWithSlash, final boolean relativeToSourceRootsStartWithoutSlash, final boolean relativeToProjectBaseDir, final boolean inRootsOfModuleDependencies) { this.IGNORE_TEXT_AFTER_HASH = ignoreTextAfterHash; this.ABSOLUTE = absolute; this.RELATIVE_TO_FILE = relativeToFile; this.RELATIVE_TO_SOURCE_ROOTS_START_WITH_SLASH = relativeToSourceRootsStartWithSlash; this.RELATIVE_TO_SOURCE_ROOTS_START_WITHOUT_SLASH = relativeToSourceRootsStartWithoutSlash; this.RELATIVE_TO_PROJECT_BASE_DIR = relativeToProjectBaseDir; this.IN_ROOTS_OF_MODULE_DEPENDENCIES = inRootsOfModuleDependencies; } } }