blob: 7cb49d1bae5155fee2bb3678939ea746f6cb765d [file] [log] [blame]
/*
* Copyright 2011 Bas Leijdekkers
*
* 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.siyeh.ig.redundancy;
import com.intellij.analysis.AnalysisScope;
import com.intellij.codeInsight.TestFrameworks;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.reference.*;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseGlobalInspection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ElementOnlyUsedFromTestCodeInspection
extends BaseGlobalInspection {
private static final Key<Boolean> ONLY_USED_FROM_TEST_CODE =
Key.create("ONLY_USED_FROM_TEST_CODE");
@NotNull
@Override
public String getDisplayName() {
return InspectionGadgetsBundle.message(
"element.only.used.from.test.code.display.name");
}
@Override
@Nullable
public RefGraphAnnotator getAnnotator(@NotNull RefManager refManager) {
return new ElementOnlyUsedFromTestCodeAnnotator();
}
@Nullable
@Override
public CommonProblemDescriptor[] checkElement(
@NotNull RefEntity refEntity, @NotNull AnalysisScope scope, @NotNull InspectionManager manager,
@NotNull GlobalInspectionContext globalContext,
@NotNull ProblemDescriptionsProcessor processor) {
if (!isOnlyUsedFromTestCode(refEntity)) {
return null;
}
if (!(refEntity instanceof RefJavaElement)) {
return null;
}
final RefJavaElement javaElement = (RefJavaElement)refEntity;
if (!javaElement.isReferenced()) {
return null;
}
final PsiElement element = javaElement.getElement();
if (element instanceof PsiClass) {
final PsiClass aClass = (PsiClass)element;
final PsiIdentifier identifier = aClass.getNameIdentifier();
if (identifier == null) {
return null;
}
return new CommonProblemDescriptor[]{
manager.createProblemDescriptor(identifier,
InspectionGadgetsBundle.message(
"class.only.used.from.test.code.problem.descriptor"),
true, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false)
};
}
else if (element instanceof PsiMethod) {
final PsiMethod method = (PsiMethod)element;
final PsiIdentifier identifier = method.getNameIdentifier();
if (identifier == null) {
return null;
}
return new CommonProblemDescriptor[]{
manager.createProblemDescriptor(identifier,
InspectionGadgetsBundle.message(
"method.only.used.from.test.code.problem.descriptor"),
true, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false)
};
}
else if (element instanceof PsiField) {
final PsiField field = (PsiField)element;
final PsiIdentifier identifier = field.getNameIdentifier();
return new CommonProblemDescriptor[]{
manager.createProblemDescriptor(identifier,
InspectionGadgetsBundle.message(
"field.only.used.from.test.code.problem.descriptor"),
true, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false)
};
}
return null;
}
private static boolean isInsideTestClass(@NotNull PsiElement e) {
final PsiClass aClass = getTopLevelParentClass(e);
return aClass != null && TestFrameworks.getInstance().isTestClass(aClass);
}
private static boolean isUnderTestSources(PsiElement e) {
final ProjectRootManager rootManager =
ProjectRootManager.getInstance(e.getProject());
final VirtualFile file = e.getContainingFile().getVirtualFile();
return file != null &&
rootManager.getFileIndex().isInTestSourceContent(file);
}
@Nullable
public static PsiClass getTopLevelParentClass(PsiElement e) {
PsiClass result = null;
PsiElement parent = e.getParent();
while (parent != null && !(parent instanceof PsiFile)) {
if (parent instanceof PsiClass) {
result = (PsiClass)parent;
}
parent = parent.getParent();
}
return result;
}
private static boolean isOnlyUsedFromTestCode(RefEntity refElement) {
final Boolean usedFromTestCode =
refElement.getUserData(ONLY_USED_FROM_TEST_CODE);
return usedFromTestCode != null && usedFromTestCode.booleanValue();
}
private static class ElementOnlyUsedFromTestCodeAnnotator
extends RefGraphAnnotator {
@Override
public void onMarkReferenced(RefElement refWhat, RefElement refFrom,
boolean referencedFromClassInitializer) {
if (!(refWhat instanceof RefMethod) &&
!(refWhat instanceof RefField) &&
!(refWhat instanceof RefClass)) {
return;
}
if (referencedFromClassInitializer ||
refFrom instanceof RefImplicitConstructor) {
return;
}
final PsiElement whatElement = refWhat.getElement();
if (isInsideTestClass(whatElement) ||
isUnderTestSources(whatElement)) {
// test code itself is allowed to only be used from test code
return;
}
if (refFrom instanceof RefMethod && refWhat instanceof RefClass) {
final RefMethod method = (RefMethod)refFrom;
if (method.isConstructor() &&
method.getOwnerClass() == refWhat) {
// don't count references to class from its own constructor
return;
}
}
final Boolean onlyUsedFromTestCode =
refWhat.getUserData(ONLY_USED_FROM_TEST_CODE);
if (onlyUsedFromTestCode == null) {
refWhat.putUserData(ONLY_USED_FROM_TEST_CODE, Boolean.TRUE);
}
else if (!onlyUsedFromTestCode.booleanValue()) {
return;
}
final PsiElement fromElement = refFrom.getElement();
if (isInsideTestClass(fromElement) ||
isUnderTestSources(fromElement)) {
return;
}
if (refWhat instanceof RefMethod) {
final RefMethod what = (RefMethod)refWhat;
if (what.isConstructor()) {
final RefClass ownerClass = what.getOwnerClass();
ownerClass.putUserData(ONLY_USED_FROM_TEST_CODE,
Boolean.FALSE);
// do count references to class from its own constructor
// when that constructor is used outside of test code.
}
}
refWhat.putUserData(ONLY_USED_FROM_TEST_CODE, Boolean.FALSE);
}
}
}