/* * Copyright 2012 Ronnie Kolehmainen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.cssxfire.resolve; import com.github.cssxfire.CssUtils; import com.intellij.openapi.util.Key; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiErrorElement; import com.intellij.psi.PsiFile; import com.intellij.psi.ResolveState; import com.intellij.psi.css.CssImport; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.SearchScope; import com.intellij.psi.search.TextOccurenceProcessor; import com.intellij.psi.search.UsageSearchContext; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.HashSet; /** * <p> * Resolving of a variable (or mixin) is performed in the following manner: * <ol> * <li>Process all declarations in current file.</li> * <li>Process all files imported by current file, and their imports until there are no more imports.</li> * <li>Search for files that are importing current file and repeat from step 1 (with <i>current</i> file being the <i>importing file</i>).</li> * </ol> * As soon as the declaration is found the processing is stopped. * </p> * <p> * Created by IntelliJ IDEA. * User: Ronnie * </p> */ public class CssResolveUtils { private static final Key<Collection<String>> PROCESSED_PATHS = new Key<Collection<String>>("PROCESSED_PATHS"); /** * Checks if the PSI tree in given root contains a PsiErrorElement. * @param root the PSI tree to check * @return <tt>true</tt> if there is at least one error element in the given tree */ public static boolean containsErrors(@Nullable PsiElement root) { if (root == null) { return false; } return PsiTreeUtil.findChildOfType(root, PsiErrorElement.class) != null; } @Nullable public static PsiElement resolveVariable(@NotNull PsiElement base, @NotNull String name) { CssResolveProcessor processor = CssPluginsFacade.getVariableProcessor(base, name); if (processor.executeInScope(base)) { processFile(base.getContainingFile(), processor, ResolveState.initial().put(PROCESSED_PATHS, new HashSet<String>())); } return processor.getResult(); } @Nullable public static PsiElement resolveMixin(@NotNull PsiElement base, @NotNull String name) { CssResolveProcessor processor = CssPluginsFacade.getMixinProcessor(base, name); if (processor.executeInScope(base)) { processFile(base.getContainingFile(), processor, ResolveState.initial().put(PROCESSED_PATHS, new HashSet<String>())); } return processor.getResult(); } private static boolean processFile(final PsiFile file, final CssResolveProcessor processor, final ResolveState state) { if (!pushPath(file, state)) { // Already visited, skip return true; } // Process declarations in file if (!PsiTreeUtil.processElements(file, processor)) { return false; } // Process imports in file CssImport cssImport; while ((cssImport = processor.popImport()) != null) { PsiFile[] imports = cssImport.resolve(); for (PsiFile resolvedImport : imports) { if (!processFile(resolvedImport, processor, state)) { return false; } } } // Recurse on files importing this file CssUtils.getPsiSearchHelper(file.getProject()).processElementsWithWord(new TextOccurenceProcessor() { public boolean execute(PsiElement element, int offsetInElement) { if (element.getParent().getParent() instanceof CssImport) { CssImport cssImport = (CssImport) element.getParent().getParent(); String[] uris = cssImport.getUriStrings(); for (String uri : uris) { if (uri != null && uri.endsWith(file.getName())) { PsiFile[] imports = cssImport.resolve(); for (PsiFile importedFile : imports) { if (file == importedFile) { if (!processFile(cssImport.getContainingFile(), processor, state)) { return false; } } } } } } return true; } }, getResolveSearchScope(file), file.getName(), (short) (UsageSearchContext.IN_CODE | UsageSearchContext.IN_STRINGS), true); return true; } @NotNull private static SearchScope getResolveSearchScope(@NotNull PsiFile file) { return GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.projectScope(file.getProject()), file.getFileType()); } private static final Object LOCK = new Object(); private static boolean pushPath(@NotNull PsiFile file, @NotNull ResolveState state) { synchronized (LOCK) { if (!CssUtils.isDynamicCssLanguage(file)) { return false; } Collection<String> paths = state.get(PROCESSED_PATHS); if (paths == null) { return false; } VirtualFile virtualFile = file.getVirtualFile(); if (virtualFile != null && paths.add(virtualFile.getPath())) { return true; } return false; } } }