blob: d35fc906ee8c516c7ee3ce39aa00a758326b2a1c [file] [log] [blame]
/*
* Copyright 2016 The Bazel Authors. All rights reserved.
*
* 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.google.idea.blaze.base.lang.buildfile.search;
import com.google.common.collect.Lists;
import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile.BlazeFileType;
import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
import com.google.idea.blaze.base.lang.buildfile.psi.NamedBuildElement;
import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
import com.google.idea.blaze.base.lang.buildfile.references.LabelUtils;
import com.google.idea.blaze.base.model.primitives.Label;
import com.intellij.openapi.application.QueryExecutorBase;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.UsageSearchContext;
import com.intellij.psi.search.searches.ReferencesSearch.SearchParameters;
import com.intellij.util.Processor;
import java.util.List;
import javax.annotation.Nullable;
/** String search for references in BUILD files */
public class BuildReferenceSearcher extends QueryExecutorBase<PsiReference, SearchParameters> {
public BuildReferenceSearcher() {
super(true);
}
@Override
public void processQuery(SearchParameters params, Processor<PsiReference> consumer) {
PsiElement element = params.getElementToSearch();
if (element instanceof NamedBuildElement) {
String fnName = ((NamedBuildElement) element).getName();
if (fnName != null) {
searchForString(params, element, fnName);
}
return;
}
PsiFile file = ResolveUtil.asFileSearch(element);
if (file != null) {
processFileReferences(params, file);
return;
}
if (!(element instanceof FuncallExpression)) {
return;
}
FuncallExpression funcall = (FuncallExpression) element;
PsiFile localFile = element.getContainingFile();
if (localFile == null) {
return;
}
Label label = funcall.resolveBuildLabel();
if (label == null) {
searchForExternalWorkspace(params, localFile, funcall);
return;
}
List<String> stringsToSearch = LabelUtils.getAllValidLabelStrings(label, true);
for (String string : stringsToSearch) {
if (LabelUtils.isAbsolute(string)) {
searchForString(params, element, string);
} else {
// only a valid reference from local package -- restrict the search scope accordingly
SearchScope scope = limitScopeToFile(params.getScopeDeterminedByUser(), localFile);
if (scope != null) {
searchForString(params, scope, element, string);
}
}
}
}
/** Find all references to the given file within BUILD files. */
private void processFileReferences(SearchParameters params, PsiFile file) {
if (file instanceof BuildFile) {
BuildFile buildFile = (BuildFile) file;
processBuildFileReferences(params, buildFile);
if (buildFile.getBlazeFileType() == BlazeFileType.BuildPackage) {
return;
}
// for skylark extensions, we also check for package-local references, below
}
BlazePackage blazePackage = BlazePackage.getContainingPackage(file);
PsiDirectory directory = blazePackage != null ? blazePackage.getContainingDirectory() : null;
if (directory == null) {
return;
}
Label label = LabelUtils.createLabelForFile(blazePackage, PsiUtils.getFilePath(file));
if (label == null) {
return;
}
// files can only be directly referenced in the containing blaze package
List<String> stringsToSearch = LabelUtils.getAllValidLabelStrings(label, true);
SearchScope scope =
params.getScopeDeterminedByUser().intersectWith(blazePackage.getSearchScope(true));
for (String string : stringsToSearch) {
searchForString(params, scope, file, string);
}
}
/** Find references to both the file itself, and build targets defined in the file. */
private void processBuildFileReferences(SearchParameters params, BuildFile file) {
Label label = file.getBuildLabel();
if (label == null) {
return;
}
String labelString = label.toString();
List<String> stringsToSearch = Lists.newArrayList();
if (file.getBlazeFileType() == BlazeFileType.BuildPackage) {
// remove ':__pkg__' component of label
stringsToSearch.add(labelString.split(":", 2)[0]);
} else {
stringsToSearch.add(labelString);
stringsToSearch.add(labelString.replace(':', '/')); // deprecated load/subinclude format
}
for (String string : stringsToSearch) {
searchForString(params, file, string);
}
}
/**
* Search for package-local references.<br>
* Returns null if the resulting scope is empty
*/
@Nullable
private static SearchScope limitScopeToFile(SearchScope scope, PsiFile file) {
if (scope instanceof LocalSearchScope) {
return ((LocalSearchScope) scope).isInScope(file.getVirtualFile())
? new LocalSearchScope(file)
: null;
}
return scope.intersectWith(new LocalSearchScope(file));
}
private static void searchForString(SearchParameters params, PsiElement element, String string) {
searchForString(params, params.getScopeDeterminedByUser(), element, string);
}
private static void searchForString(
SearchParameters params, SearchScope scope, PsiElement element, String string) {
if (scope instanceof GlobalSearchScope) {
scope =
GlobalSearchScope.getScopeRestrictedByFileTypes(
(GlobalSearchScope) scope, BuildFileType.INSTANCE);
}
params.getOptimizer().searchWord(string, scope, UsageSearchContext.IN_STRINGS, true, element);
}
private static void searchForExternalWorkspace(
SearchParameters params, PsiFile file, FuncallExpression funcall) {
if (!isBlazeWorkspaceFile(file)) {
return;
}
String name = funcall.getNameArgumentValue();
if (name != null) {
searchForString(params, funcall, "@" + name);
}
}
/** Is the file a blaze WORKSPACE file */
private static boolean isBlazeWorkspaceFile(PsiFile file) {
return file instanceof BuildFile
&& ((BuildFile) file).getBlazeFileType() == BlazeFileType.Workspace;
}
}