unused declaration make a query for external non java usages in queryExternalUsagesRequests()

This commit is contained in:
Dmitry Batkovich
2018-09-24 16:46:18 +03:00
parent 912a10399d
commit d28aac1be5
18 changed files with 188 additions and 130 deletions
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* 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.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection;
@@ -86,6 +72,9 @@ public abstract class GlobalJavaInspectionContext implements GlobalInspectionCon
*/
public abstract void enqueueMethodUsagesProcessor(RefMethod refMethod, UsagesProcessor p);
public abstract void enqueueQualifiedNameOccurrencesProcessor(RefClass refClass, Runnable p);
public abstract EntryPointsManager getEntryPointsManager(RefManager manager);
@NotNull
@@ -35,7 +35,7 @@ public class UnreferencedFilter extends RefUnreachableFilter {
if (!(refElement instanceof RefMethod || refElement instanceof RefClass || refElement instanceof RefField)) return 0;
if (!((GlobalInspectionContextBase)myContext).isToCheckMember(refElement, myTool)) return 0;
if (refElement instanceof RefField) {
if (refElement instanceof RefField && !isExternallyReferenced(refElement)) {
RefField refField = (RefField) refElement;
if (refField.isUsedForReading() && !refField.isUsedForWriting()) return 1;
if (refField.isUsedForWriting() && !refField.isUsedForReading()) return 1;
@@ -44,4 +44,8 @@ public class UnreferencedFilter extends RefUnreachableFilter {
if (refElement instanceof RefClass && ((RefClass)refElement).isAnonymous()) return 0;
return -1;
}
protected static boolean isExternallyReferenced(RefElement element) {
return element.getInReferences().stream().anyMatch(reference -> reference instanceof RefFile);
}
}
@@ -13,23 +13,17 @@ import com.intellij.codeInspection.ex.JobDescriptor;
import com.intellij.codeInspection.reference.*;
import com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspectionBase;
import com.intellij.codeInspection.util.RefFilter;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.ExtensionPoint;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiClassImplUtil;
import com.intellij.psi.search.DelegatingGlobalSearchScope;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiNonJavaFileReferenceProcessor;
import com.intellij.psi.search.PsiSearchHelper;
import com.intellij.psi.util.PsiMethodUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jdom.Element;
@@ -283,60 +277,6 @@ public class UnusedDeclarationInspectionBase extends GlobalInspectionTool {
}
});
if (isAddNonJavaUsedEnabled()) {
checkForReachableRefs(globalContext);
final StrictUnreferencedFilter strictUnreferencedFilter = new StrictUnreferencedFilter(this, globalContext);
ProgressManager.getInstance().runProcess(new Runnable() {
@Override
public void run() {
final RefManager refManager = globalContext.getRefManager();
final PsiSearchHelper helper = PsiSearchHelper.getInstance(refManager.getProject());
refManager.iterate(new RefJavaVisitor() {
@Override
public void visitElement(@NotNull final RefEntity refEntity) {
if (refEntity instanceof RefClass && strictUnreferencedFilter.accepts((RefClass)refEntity)) {
findExternalClassReferences((RefClass)refEntity);
}
else if (refEntity instanceof RefMethod) {
RefMethod refMethod = (RefMethod)refEntity;
if (refMethod.isConstructor() && strictUnreferencedFilter.accepts(refMethod)) {
findExternalClassReferences(refMethod.getOwnerClass());
}
}
}
private void findExternalClassReferences(final RefClass refElement) {
final UClass psiClass = refElement.getUastElement();
String qualifiedName = psiClass != null ? psiClass.getQualifiedName() : null;
if (qualifiedName != null) {
final GlobalSearchScope projectScope = GlobalSearchScope.projectScope(globalContext.getProject());
final PsiNonJavaFileReferenceProcessor processor = (file, startOffset, endOffset) -> {
getEntryPointsManager(globalContext).addEntryPoint(refElement, false);
return false;
};
final DelegatingGlobalSearchScope globalSearchScope = new DelegatingGlobalSearchScope(projectScope) {
@Override
public boolean contains(@NotNull VirtualFile file) {
return file.getFileType() != JavaFileType.INSTANCE && super.contains(file);
}
};
helper.processUsagesInNonJavaFiles(qualifiedName, processor, globalSearchScope);
//references from java-like are already in graph or
//they would be checked during GlobalJavaInspectionContextImpl.performPostRunActivities
for (RefElement element : refElement.getInReferences()) {
if (!(element instanceof RefJavaElement)) {
getEntryPointsManager(globalContext).addEntryPoint(refElement, false);
}
}
}
}
});
}
}, null);
}
myProcessedSuspicious = new HashSet<>();
myPhase = 1;
}
@@ -453,7 +393,8 @@ public class UnusedDeclarationInspectionBase extends GlobalInspectionTool {
@NotNull GlobalInspectionContext globalContext,
@NotNull ProblemDescriptionsProcessor problemDescriptionsProcessor) {
checkForReachableRefs(globalContext);
final RefFilter filter = myPhase == 1 ? new StrictUnreferencedFilter(this, globalContext) :
final boolean firstPhase = myPhase == 1;
final RefFilter filter = firstPhase ? new StrictUnreferencedFilter(this, globalContext) :
new RefUnreachableFilter(this, globalContext);
LOG.assertTrue(myProcessedSuspicious != null, "phase: " + myPhase);
@@ -486,7 +427,14 @@ public class UnusedDeclarationInspectionBase extends GlobalInspectionTool {
public void visitMethod(@NotNull final RefMethod refMethod) {
myProcessedSuspicious.add(refMethod);
if (refMethod instanceof RefImplicitConstructor) {
visitClass(refMethod.getOwnerClass());
RefClass ownerClass = refMethod.getOwnerClass();
LOG.assertTrue(ownerClass != null);
visitClass(ownerClass);
}
else if (refMethod.isConstructor()) {
RefClass ownerClass = refMethod.getOwnerClass();
LOG.assertTrue(ownerClass != null);
queryQualifiedNameUsages(ownerClass);
}
else {
UMethod uMethod = (UMethod)refMethod.getUastElement();
@@ -515,9 +463,31 @@ public class UnusedDeclarationInspectionBase extends GlobalInspectionTool {
getEntryPointsManager(globalContext).addEntryPoint(refClass, false);
return false;
});
queryQualifiedNameUsages(refClass);
requestAdded[0] = true;
}
}
public void queryQualifiedNameUsages(@NotNull RefClass refClass) {
if (firstPhase && isAddNonJavaUsedEnabled()) {
globalContext.getExtension(GlobalJavaInspectionContext.CONTEXT).enqueueQualifiedNameOccurrencesProcessor(refClass, () -> {
EntryPointsManager entryPointsManager = getEntryPointsManager(globalContext);
entryPointsManager.addEntryPoint(refClass, false);
for (RefMethod constructor : refClass.getConstructors()) {
entryPointsManager.addEntryPoint(constructor, false);
}
});
//references from java-like are already in graph or
//they would be checked during GlobalJavaInspectionContextImpl.performPostRunActivities
for (RefElement element : refClass.getInReferences()) {
if (!(element instanceof RefJavaElement)) {
getEntryPointsManager(globalContext).addEntryPoint(refElement, false);
}
}
}
}
});
}
}
@@ -5,7 +5,6 @@ package com.intellij.codeInspection.reference;
import com.intellij.codeInsight.TestFrameworks;
import com.intellij.lang.Language;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.lang.jvm.JvmClass;
import com.intellij.lang.jvm.JvmField;
import com.intellij.lang.jvm.JvmMetaLanguage;
import com.intellij.lang.jvm.JvmModifier;
@@ -274,9 +273,9 @@ public class RefClassImpl extends RefJavaElementImpl implements RefClass {
@NotNull
@Override
public String getQualifiedName() {
final JvmClass jvmClass = getUastElement();
if (jvmClass == null) return super.getQualifiedName();
final String qName = jvmClass.getQualifiedName();
final UClass uClass = getUastElement();
if (uClass == null) return super.getQualifiedName();
final String qName = uClass.getQualifiedName();
if (qName == null) return super.getQualifiedName();
return qName;
}
@@ -34,7 +34,6 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.uast.*;
import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor;
import javax.swing.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -444,6 +443,11 @@ public class RefJavaManagerImpl extends RefJavaManager {
.filter(Objects::nonNull);
}
@Override
public void markExternalReferencesProcessed(@NotNull RefElement file) {
getEntryPointsManager().addEntryPoint(file, false);
}
@Override
public EntryPointsManager getEntryPointsManager() {
EntryPointsManager entryPointsManager = myEntryPointsManager;
@@ -1,4 +1,4 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection.ex;
@@ -15,6 +15,7 @@ import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
@@ -43,10 +44,8 @@ import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UMethod;
import org.jetbrains.uast.UastContextKt;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
public class GlobalJavaInspectionContextImpl extends GlobalJavaInspectionContext {
private static final Logger LOG = Logger.getInstance(GlobalJavaInspectionContextImpl.class);
@@ -56,6 +55,7 @@ public class GlobalJavaInspectionContextImpl extends GlobalJavaInspectionContext
private Map<SmartPsiElementPointer, List<UsagesProcessor>> myMethodUsagesRequests;
private Map<SmartPsiElementPointer, List<UsagesProcessor>> myFieldUsagesRequests;
private Map<SmartPsiElementPointer, List<UsagesProcessor>> myClassUsagesRequests;
private Map<SmartPsiElementPointer, List<Runnable>> myQNameUsagesRequests;
@Override
@@ -89,6 +89,12 @@ public class GlobalJavaInspectionContextImpl extends GlobalJavaInspectionContext
enqueueRequestImpl(refMethod, myMethodUsagesRequests, p);
}
@Override
public void enqueueQualifiedNameOccurrencesProcessor(RefClass refClass, Runnable c) {
if (myQNameUsagesRequests == null) myQNameUsagesRequests = new THashMap<>();
enqueueRequestImpl(refClass, myQNameUsagesRequests, c);
}
@Override
public EntryPointsManager getEntryPointsManager(final RefManager manager) {
return manager.getExtension(RefJavaManager.MANAGER).getEntryPointsManager();
@@ -162,7 +168,7 @@ public class GlobalJavaInspectionContextImpl extends GlobalJavaInspectionContext
return anyModuleUsesProjectSdk && !anyModuleAcceptsSdk;
}
private static <T extends Processor> void enqueueRequestImpl(RefElement refElement, Map<SmartPsiElementPointer, List<T>> requestMap, T processor) {
private static <T> void enqueueRequestImpl(RefElement refElement, Map<SmartPsiElementPointer, List<T>> requestMap, T processor) {
List<T> requests = requestMap.computeIfAbsent(refElement.getPointer(), __ -> new ArrayList<>());
requests.add(processor);
}
@@ -289,6 +295,41 @@ public class GlobalJavaInspectionContextImpl extends GlobalJavaInspectionContext
myMethodUsagesRequests = null;
}
if (myQNameUsagesRequests != null) {
PsiSearchHelper helper = PsiSearchHelper.getInstance(refManager.getProject());
RefJavaManager javaManager = refManager.getExtension(RefJavaManager.MANAGER);
List<SmartPsiElementPointer> sortedIDs = getSortedIDs(myQNameUsagesRequests);
for (SmartPsiElementPointer id : sortedIDs) {
final UClass uClass = ReadAction.compute(() -> UastContextKt.toUElement(dereferenceInReadAction(id), UClass.class));
String qualifiedName = uClass != null ? uClass.getQualifiedName() : null;
if (qualifiedName != null) {
List<Runnable> callbacks = myQNameUsagesRequests.get(id);
final GlobalSearchScope projectScope = GlobalSearchScope.projectScope(context.getProject());
final PsiNonJavaFileReferenceProcessor processor = (file, startOffset, endOffset) -> {
for (Runnable callback : callbacks) {
callback.run();
}
return false;
};
final DelegatingGlobalSearchScope globalSearchScope = new DelegatingGlobalSearchScope(projectScope) {
Set<FileType> fileTypes = javaManager.getLanguages().stream().map(l -> l.getAssociatedFileType()).collect(Collectors.toSet());
boolean ignoreExpectedXml = ApplicationManager.getApplication().isUnitTestMode();
@Override
public boolean contains(@NotNull VirtualFile file) {
return !fileTypes.contains(file.getFileType()) && (!ignoreExpectedXml || !file.getName().equals("expected.xml")) && super.contains(file);
}
};
helper.processUsagesInNonJavaFiles(qualifiedName, processor, globalSearchScope);
}
}
myQNameUsagesRequests = null;
}
}
private static String getClassPresentableName(@NotNull UClass uClass) {
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<problems>
</problems>
@@ -0,0 +1,5 @@
class MyGroovyUsage {
static void "to use java"(Util util) {
util.foo
}
}
@@ -0,0 +1,3 @@
class Util {
String foo;
}
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<problems>
</problems>
@@ -0,0 +1,5 @@
class MyGroovyUsage {
static void "to use java"() {
Util.FOO
}
}
@@ -0,0 +1,3 @@
class Util {
public final static String FOO = "foo";
}
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<problems>
</problems>
@@ -0,0 +1,5 @@
class MyGroovyUsage {
static void "to use java"() {
Util.foo()
}
}
@@ -0,0 +1,3 @@
class Util {
static void foo() { }
}
@@ -207,6 +207,18 @@ public class UnusedDeclarationTest extends AbstractUnusedDeclarationTest {
doTest();
}
public void testStaticMethodReferenceFromGroovy() {
doTest();
}
public void testStaticFieldReferenceFromExternalGroovy() {
doTest();
}
public void testFieldReferenceFromExternalGroovy() {
doTest();
}
public void testStaticImport() {
doTest();
}
@@ -1,4 +1,4 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection.lang;
@@ -79,4 +79,8 @@ public interface RefManagerExtension<T> {
default Stream<? extends PsiElement> extractExternalFileImplicitReferences(@NotNull PsiFile psiFile) {
return Stream.empty();
}
default void markExternalReferencesProcessed(@NotNull RefElement file) {
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection.reference;
@@ -467,50 +467,49 @@ public class RefManagerImpl extends RefManager {
else if (processExternalElements) {
PsiFile file = element.getContainingFile();
if (file != null) {
boolean referencesProcessed = false;
for (RefManagerExtension<?> managerExtension : myExtensions.values()) {
if (managerExtension.shouldProcessExternalFile(file)) {
RefElement refFile = getReference(file);
LOG.assertTrue(refFile != null, file);
if (!referencesProcessed) {
referencesProcessed = true;
for (PsiReference reference : element.getReferences()) {
PsiElement resolve = reference.resolve();
if (resolve != null) {
fireNodeMarkedReferenced(resolve, file);
RefElement refWhat = getReference(resolve);
if (refWhat == null) {
PsiFile targetContainingFile = resolve.getContainingFile();
//no logic to distinguish different elements in the file anyway
if (file == targetContainingFile) continue;
refWhat = getReference(targetContainingFile);
}
RefManagerExtension externalFileManagerExtension = myExtensions.values().stream().filter(ex -> ex.shouldProcessExternalFile(file)).findFirst().orElse(null);
if (externalFileManagerExtension == null) {
if (element instanceof PsiFile) {
VirtualFile virtualFile = PsiUtilCore.getVirtualFile(element);
if (virtualFile instanceof VirtualFileWithId) {
registerUnprocessed((VirtualFileWithId)virtualFile);
}
}
} else {
RefElement refFile = getReference(file);
LOG.assertTrue(refFile != null, file);
for (PsiReference reference : element.getReferences()) {
PsiElement resolve = reference.resolve();
if (resolve != null) {
fireNodeMarkedReferenced(resolve, file);
RefElement refWhat = getReference(resolve);
if (refWhat == null) {
PsiFile targetContainingFile = resolve.getContainingFile();
//no logic to distinguish different elements in the file anyway
if (file == targetContainingFile) continue;
refWhat = getReference(targetContainingFile);
}
if (refWhat != null) {
((RefElementImpl)refWhat).addInReference(refFile);
((RefElementImpl)refFile).addOutReference(refWhat);
}
}
if (refWhat != null) {
((RefElementImpl)refWhat).addInReference(refFile);
((RefElementImpl)refFile).addOutReference(refWhat);
}
}
Stream<? extends PsiElement> implicitRefs = managerExtension.extractExternalFileImplicitReferences(file);
implicitRefs.forEach(e -> {
RefElement superClassReference = getReference(e);
if (superClassReference != null) {
//in case of implicit inheritance, e.g. GroovyObject
//= no explicit reference is provided, dependency on groovy library could be treated as redundant though it is not
//inReference is not important in this case
((RefElementImpl)refFile).addOutReference(superClassReference);
}
});
}
}
if (!referencesProcessed && element instanceof PsiFile) {
VirtualFile virtualFile = PsiUtilCore.getVirtualFile(element);
if (virtualFile instanceof VirtualFileWithId) {
registerUnprocessed((VirtualFileWithId)virtualFile);
Stream<? extends PsiElement> implicitRefs = externalFileManagerExtension.extractExternalFileImplicitReferences(file);
implicitRefs.forEach(e -> {
RefElement superClassReference = getReference(e);
if (superClassReference != null) {
//in case of implicit inheritance, e.g. GroovyObject
//= no explicit reference is provided, dependency on groovy library could be treated as redundant though it is not
//inReference is not important in this case
((RefElementImpl)refFile).addOutReference(superClassReference);
}
});
if (element instanceof PsiFile) {
externalFileManagerExtension.markExternalReferencesProcessed(refFile);
}
}
}