inspection: fix bugs in "Suspicious package-private access" inspection (IDEA-200047)

Properly report references to protected constructors accessed not via subclass, and fix duplicating warning in Kotlin files. Also add tests for Kotlin.
This commit is contained in:
nik
2019-02-21 13:00:17 +03:00
parent f2f078d2a3
commit e189f7b1dc
19 changed files with 432 additions and 116 deletions
@@ -0,0 +1,16 @@
package xxx;
public class InnerClasses {
static class PackagePrivateInnerClass {
}
static class PackagePrivateInnerClassWithConstructor {
PackagePrivateInnerClassWithConstructor() {
}
}
public static class ClassWithPackagePrivateConstructor {
ClassWithPackagePrivateConstructor() {
}
}
}
@@ -0,0 +1,4 @@
package xxx;
class PackagePrivateClass {
}
@@ -0,0 +1,9 @@
package xxx;
public class ProtectedConstructors {
protected ProtectedConstructors() {
}
protected ProtectedConstructors(int i) {
}
}
@@ -0,0 +1,12 @@
package xxx;
public class ProtectedMembers {
protected void method() {
}
static protected void staticMethod() {
}
protected static class StaticInner {
}
}
@@ -0,0 +1,16 @@
// 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 xxx;
public class PublicClass {
public String publicField;
String packagePrivateField;
static public String PUBLIC_STATIC_FIELD;
static String PACKAGE_PRIVATE_STATIC_FIELD;
public void publicMethod() {}
void packagePrivateMethod() {}
PublicClass() {}
public PublicClass(int i) {}
PublicClass(boolean b) {}
}
@@ -0,0 +1,5 @@
// 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 xxx;
public class PublicClassWithDefaultConstructor {
}
@@ -0,0 +1,7 @@
package xxx;
public class StaticMembers {
static String IMPORTED_FIELD = "";
static void importedMethod() {
}
}
@@ -0,0 +1,41 @@
package xxx
import xxx.StaticMembers.*
/**
* @see PackagePrivateClass
* @see PublicClass.packagePrivateField
*/
@Suppress("UNUSED_VARIABLE")
class AccessingPackagePrivateMembers {
private val property = <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass()</warning>
fun main() {
<warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass()</warning>
var variable: <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning>
val aClass: PublicClass = PublicClass(1);
val aClass2: PublicClassWithDefaultConstructor = PublicClassWithDefaultConstructor();
<warning descr="Constructor PublicClass.PublicClass() is package-private, but declared in a different module 'dep'">PublicClass()</warning>
<warning descr="Constructor PublicClass.PublicClass(boolean) is package-private, but declared in a different module 'dep'">PublicClass(true)</warning>
System.out.println(aClass.publicField)
System.out.println(aClass.<warning descr="Field PublicClass.packagePrivateField is package-private, but declared in a different module 'dep'">packagePrivateField</warning>)
System.out.println(PublicClass.PUBLIC_STATIC_FIELD)
System.out.println(PublicClass.<warning descr="Field PublicClass.PACKAGE_PRIVATE_STATIC_FIELD is package-private, but declared in a different module 'dep'">PACKAGE_PRIVATE_STATIC_FIELD</warning>)
aClass.publicMethod()
aClass.<warning descr="Method PublicClass.packagePrivateMethod() is package-private, but declared in a different module 'dep'">packagePrivateMethod()</warning>
System.out.println(<warning descr="Field StaticMembers.IMPORTED_FIELD is package-private, but declared in a different module 'dep'">IMPORTED_FIELD</warning>)
<warning descr="Method StaticMembers.importedMethod() is package-private, but declared in a different module 'dep'">importedMethod</warning>()
InnerClasses.<warning descr="Class xxx.InnerClasses.PackagePrivateInnerClass is package-private, but declared in a different module 'dep'">PackagePrivateInnerClass()</warning>
InnerClasses.<warning descr="Constructor PackagePrivateInnerClassWithConstructor.PackagePrivateInnerClassWithConstructor() is package-private, but declared in a different module 'dep'">PackagePrivateInnerClassWithConstructor()</warning>
InnerClasses.<warning descr="Constructor ClassWithPackagePrivateConstructor.ClassWithPackagePrivateConstructor() is package-private, but declared in a different module 'dep'">ClassWithPackagePrivateConstructor()</warning>
}
companion object {
private val staticProperty = <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass()</warning>
}
}
@@ -0,0 +1,36 @@
package xxx
class AccessingProtectedMembersNotFromSubclass {
fun foo() {
val aClass: ProtectedMembers = ProtectedMembers()
aClass.<warning descr="Method ProtectedMembers.method() is protected and used not through a subclass here, but declared in a different module 'dep'">method()</warning>
ProtectedMembers.<warning descr="Method ProtectedMembers.staticMethod() is protected and used not through a subclass here, but declared in a different module 'dep'">staticMethod()</warning>
<warning descr="Constructor ProtectedConstructors.ProtectedConstructors() is protected and used not through a subclass here, but declared in a different module 'dep'">ProtectedConstructors()</warning>
<warning descr="Constructor ProtectedConstructors.ProtectedConstructors(int) is protected and used not through a subclass here, but declared in a different module 'dep'">ProtectedConstructors(1)</warning>
}
}
@Suppress("UNUSED_VARIABLE")
class AccessingProtectedMembersFromSubclass : ProtectedMembers() {
fun foo() {
method()
staticMethod()
ProtectedMembers.staticMethod()
val aClass = ProtectedMembers()
aClass.<warning descr="Method ProtectedMembers.method() is protected and used not through a subclass here, but declared in a different module 'dep'">method()</warning>
val myInstance = AccessingProtectedMembersFromSubclass()
myInstance.method()
var inner1: ProtectedMembers.StaticInner
var inner2: StaticInner
}
private class StaticInnerImpl1 : ProtectedMembers.StaticInner()
private class StaticInnerImpl2 : StaticInner()
}
class AccessingDefaultProtectedConstructorFromSubclass : ProtectedConstructors()
class AccessingProtectedConstructorFromSubclass : ProtectedConstructors(1)
@@ -0,0 +1,18 @@
package com.intellij.codeInspection.tests.kotlin
import com.intellij.jvm.analysis.JvmAnalysisKtTestsUtil
import com.intellij.testFramework.TestDataPath
import com.siyeh.ig.dependency.SuspiciousPackagePrivateAccessInspectionTestCase
@TestDataPath("/testData/codeInspection/suspiciousPackagePrivateAccess")
class KtSuspiciousPackagePrivateAccessInspectionTest : SuspiciousPackagePrivateAccessInspectionTestCase("kt") {
fun testAccessingPackagePrivateMembers() {
doTestWithDependency()
}
fun testAccessingProtectedMembers() {
doTestWithDependency()
}
override fun getBasePath() = "${JvmAnalysisKtTestsUtil.TEST_DATA_PROJECT_RELATIVE_BASE_PATH}/codeInspection/suspiciousPackagePrivateAccess"
}
@@ -15,6 +15,7 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.refactoring.util.RefactoringUIUtil;
import com.intellij.uast.UastVisitorAdapter;
import com.intellij.ui.ContextHelpLabel;
@@ -65,20 +66,49 @@ public class SuspiciousPackagePrivateAccessInspection extends AbstractBaseUastLo
}
PsiElement resolved = node.resolve();
if (resolved instanceof PsiMember) {
checkAccess(node.getSelector(), (PsiMember)resolved, receiver);
checkAccess(node.getSelector(), (PsiMember)resolved, getAccessObjectType(receiver));
}
return true;
}
@Override
public boolean visitSimpleNameReferenceExpression(@NotNull USimpleNameReferenceExpression node) {
PsiElement resolved = node.resolve();
if (resolved instanceof PsiMember) {
checkAccess(node, (PsiMember)resolved, null);
UElement uastParent = node.getUastParent();
//we should skip 'checkAccess' here if node is part of UQualifiedReferenceExpression or UCallExpression node,
// otherwise the same problem will be reported twice
if (!isSelectorOfQualifiedReference(node)
&& !(uastParent instanceof UCallExpression && isMethodReferenceOfCallExpression(node, (UCallExpression)uastParent)
&& (((UCallExpression)uastParent).getKind() == UastCallKind.CONSTRUCTOR_CALL || isSelectorOfQualifiedReference((UExpression)uastParent)))) {
PsiElement resolved = node.resolve();
if (resolved instanceof PsiMember) {
checkAccess(node, (PsiMember)resolved, null);
}
}
return true;
}
private boolean isSelectorOfQualifiedReference(@Nullable UExpression expression) {
if (expression == null) return false;
UElement parent = expression.getUastParent();
return parent instanceof UQualifiedReferenceExpression
&& referToSameSourceElement(expression, ((UQualifiedReferenceExpression)parent).getSelector());
}
private boolean isMethodReferenceOfCallExpression(@NotNull USimpleNameReferenceExpression expression, @NotNull UCallExpression parent) {
UElement methodIdentifier = parent.getMethodIdentifier();
UReferenceExpression classReference = parent.getClassReference();
if (methodIdentifier == null && classReference != null) {
methodIdentifier = classReference.getReferenceNameElement();
}
return referToSameSourceElement(expression.getReferenceNameElement(), methodIdentifier);
}
private boolean referToSameSourceElement(@Nullable UElement element1, @Nullable UElement element2) {
if (element1 == null || element2 == null) return false;
PsiElement sourcePsi1 = element1.getSourcePsi();
return sourcePsi1 != null && sourcePsi1.equals(element2.getSourcePsi());
}
@Override
public boolean visitCallableReferenceExpression(@NotNull UCallableReferenceExpression node) {
PsiElement resolve = node.resolve();
@@ -86,18 +116,50 @@ public class SuspiciousPackagePrivateAccessInspection extends AbstractBaseUastLo
PsiMember member = (PsiMember)resolve;
UElement sourceNode = getReferenceNameElement(node);
if (sourceNode != null) {
checkAccess(sourceNode, member, node.getQualifierExpression());
checkAccess(sourceNode, member, getAccessObjectType(node.getQualifierExpression()));
}
}
return true;
}
private void checkAccess(@NotNull UElement sourceNode, @NotNull PsiMember target, @Nullable UExpression receiver) {
@Override
public boolean visitTypeReferenceExpression(@NotNull UTypeReferenceExpression node) {
//in Kotlin implementation of UAST USimpleNameReferenceExpression::resolve returns null for reference to type in local variable declaration,
// so we need to have this case specifically
if (!(node.getSourcePsi() instanceof PsiTypeElement)) {
PsiClass resolved = PsiTypesUtil.getPsiClass(node.getType());
if (resolved != null) {
checkAccess(node, resolved, null);
}
}
return true;
}
@Override
public boolean visitCallExpression(@NotNull UCallExpression node) {
//regular method calls are handled by visitSimpleNameReferenceExpression or visitQualifiedReferenceExpression, but we need to handle
// constructor calls in a special way because they may refer to classes
if (!isSelectorOfQualifiedReference(node) && node.getKind() == UastCallKind.CONSTRUCTOR_CALL) {
PsiMethod resolved = node.resolve();
if (resolved != null) {
checkAccess(node, resolved, null);
}
else {
UReferenceExpression classReference = node.getClassReference();
PsiElement resolvedClass = classReference != null ? classReference.resolve() : null;
if (resolvedClass instanceof PsiClass) {
checkAccess(node, (PsiClass)resolvedClass, null);
}
}
}
return true;
}
private void checkAccess(@NotNull UElement sourceNode, @NotNull PsiMember target, @Nullable PsiClass accessObjectType) {
if (target.hasModifier(JvmModifier.PACKAGE_LOCAL)) {
checkPackageLocalAccess(sourceNode, target, "package-private");
}
else if (target.hasModifier(JvmModifier.PROTECTED) && receiver != null
&& !(receiver instanceof UThisExpression) && !(receiver instanceof USuperExpression) && !canAccessProtectedMember(receiver, sourceNode, target)) {
else if (target.hasModifier(JvmModifier.PROTECTED) && !canAccessProtectedMember(sourceNode, target, accessObjectType)) {
checkPackageLocalAccess(sourceNode, target, "protected and used not through a subclass here");
}
}
@@ -132,22 +194,26 @@ public class SuspiciousPackagePrivateAccessInspection extends AbstractBaseUastLo
return node;
}
private static boolean canAccessProtectedMember(UExpression receiver, UElement sourceNode, PsiMember member) {
PsiClass memberClass = member.getContainingClass();
if (memberClass == null) return false;
@Nullable
private static PsiClass getAccessObjectType(@Nullable UExpression receiver) {
if (receiver == null || receiver instanceof UThisExpression || receiver instanceof USuperExpression) {
return null;
}
PsiClass accessObjectType;
PsiType type = receiver.getExpressionType();
if (type != null) {
if (!(type instanceof PsiClassType)) return false;
accessObjectType = ((PsiClassType)type).resolve();
if (accessObjectType == null) return false;
if (!(type instanceof PsiClassType)) return null;
return ((PsiClassType)type).resolve();
}
else {
PsiElement element = ((UReferenceExpression)receiver).resolve();
if (!(element instanceof PsiClass)) return false;
accessObjectType = (PsiClass)element;
return element instanceof PsiClass ? (PsiClass)element : null;
}
}
private static boolean canAccessProtectedMember(UElement sourceNode, PsiMember member, PsiClass accessObjectType) {
PsiClass memberClass = member.getContainingClass();
if (memberClass == null) return false;
PsiElement sourcePsi = sourceNode.getSourcePsi();
UClass sourceClass = UastUtils.findContaining(sourcePsi, UClass.class);
@@ -0,0 +1,16 @@
package xxx;
public class InnerClasses {
static class PackagePrivateInnerClass {
}
static class PackagePrivateInnerClassWithConstructor {
PackagePrivateInnerClassWithConstructor() {
}
}
public static class ClassWithPackagePrivateConstructor {
ClassWithPackagePrivateConstructor() {
}
}
}
@@ -0,0 +1,9 @@
package xxx;
public class ProtectedConstructors {
protected ProtectedConstructors() {
}
protected ProtectedConstructors(int i) {
}
}
@@ -9,4 +9,8 @@ public class PublicClass {
public void publicMethod() {}
void packagePrivateMethod() {}
PublicClass() {}
public PublicClass(int i) {}
PublicClass(boolean b) {}
}
@@ -0,0 +1,5 @@
// 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 xxx;
public class PublicClassWithDefaultConstructor {
}
@@ -7,19 +7,23 @@ import static xxx.StaticMembers.*;
* @see PublicClass#packagePrivateField
*/
public class AccessingPackagePrivateMembers {
Object field = new <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning>();
static Object staticField = <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">new PackagePrivateClass()</warning>;
Object field = <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">new PackagePrivateClass()</warning>;
{
new <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning>();
<warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">new PackagePrivateClass()</warning>;
}
static {
new <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning>();
<warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">new PackagePrivateClass()</warning>;
}
public void main() {
new <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning>();
<warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">new PackagePrivateClass()</warning>;
<warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning> variable;
PublicClass aClass = new PublicClass();
PublicClass aClass = new PublicClass(1);
PublicClassWithDefaultConstructor aClass2 = new PublicClassWithDefaultConstructor();
<warning descr="Constructor PublicClass.PublicClass() is package-private, but declared in a different module 'dep'">new PublicClass()</warning>;
<warning descr="Constructor PublicClass.PublicClass(boolean) is package-private, but declared in a different module 'dep'">new PublicClass(true)</warning>;
System.out.println(aClass.publicField);
System.out.println(aClass.<warning descr="Field PublicClass.packagePrivateField is package-private, but declared in a different module 'dep'">packagePrivateField</warning>);
@@ -32,5 +36,9 @@ public class AccessingPackagePrivateMembers {
System.out.println(<warning descr="Field StaticMembers.IMPORTED_FIELD is package-private, but declared in a different module 'dep'">IMPORTED_FIELD</warning>);
<warning descr="Method StaticMembers.importedMethod() is package-private, but declared in a different module 'dep'">importedMethod</warning>();
<warning descr="Class xxx.InnerClasses.PackagePrivateInnerClass is package-private, but declared in a different module 'dep'">new InnerClasses.<warning descr="Class xxx.InnerClasses.PackagePrivateInnerClass is package-private, but declared in a different module 'dep'">PackagePrivateInnerClass</warning>()</warning>;
<warning descr="Constructor PackagePrivateInnerClassWithConstructor.PackagePrivateInnerClassWithConstructor() is package-private, but declared in a different module 'dep'">new InnerClasses.<warning descr="Class xxx.InnerClasses.PackagePrivateInnerClassWithConstructor is package-private, but declared in a different module 'dep'">PackagePrivateInnerClassWithConstructor</warning>()</warning>;
<warning descr="Constructor ClassWithPackagePrivateConstructor.ClassWithPackagePrivateConstructor() is package-private, but declared in a different module 'dep'">new InnerClasses.ClassWithPackagePrivateConstructor()</warning>;
}
}
@@ -5,6 +5,10 @@ class AccessingProtectedMembersNotFromSubclass {
ProtectedMembers aClass = new ProtectedMembers();
aClass.<warning descr="Method ProtectedMembers.method() is protected and used not through a subclass here, but declared in a different module 'dep'">method</warning>();
ProtectedMembers.<warning descr="Method ProtectedMembers.staticMethod() is protected and used not through a subclass here, but declared in a different module 'dep'">staticMethod</warning>();
<warning descr="Constructor ProtectedConstructors.ProtectedConstructors() is protected and used not through a subclass here, but declared in a different module 'dep'">new ProtectedConstructors()</warning>;
<warning descr="Constructor ProtectedConstructors.ProtectedConstructors(int) is protected and used not through a subclass here, but declared in a different module 'dep'">new ProtectedConstructors(1)</warning>;
new ProtectedConstructors() {};
new ProtectedConstructors(1) {};
}
}
@@ -21,6 +25,13 @@ class AccessingProtectedMembersFromSubclass extends ProtectedMembers {
ProtectedMembers.StaticInner inner1;
StaticInner inner2;
new Runnable() {
public void run() {
method();
staticMethod();
}
};
}
public static class StaticInnerImpl1 extends ProtectedMembers.StaticInner {
@@ -28,4 +39,26 @@ class AccessingProtectedMembersFromSubclass extends ProtectedMembers {
public static class StaticInnerImpl2 extends StaticInner {
}
public class OwnInner {
void bar() {
method();
staticMethod();
}
}
public static class OwnStaticInner {
void bar() {
staticMethod();
}
}
}
class AccessingDefaultProtectedConstructorFromSubclass extends ProtectedConstructors {
}
class AccessingProtectedConstructorFromSubclass extends ProtectedConstructors {
AccessingProtectedConstructorFromSubclass() {
super(1);
}
}
@@ -1,27 +1,8 @@
// 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.siyeh.ig.dependency;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.LanguageLevelModuleExtension;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.testFramework.LightProjectDescriptor;
import com.siyeh.ig.LightInspectionTestCase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaSourceRootType;
import java.io.File;
import java.io.IOException;
public class SuspiciousPackagePrivateAccessInspectionTest extends LightInspectionTestCase {
private final ProjectWithDepModuleDescriptor myProjectDescriptor = new ProjectWithDepModuleDescriptor(LanguageLevel.HIGHEST);
public class SuspiciousPackagePrivateAccessInspectionTest extends SuspiciousPackagePrivateAccessInspectionTestCase {
public SuspiciousPackagePrivateAccessInspectionTest() {super("java");}
public void testAccessingPackagePrivateMembers() {
doTestWithDependency();
@@ -30,77 +11,4 @@ public class SuspiciousPackagePrivateAccessInspectionTest extends LightInspectio
public void testAccessingProtectedMembers() {
doTestWithDependency();
}
@Override
protected void setUp() throws Exception {
super.setUp();
myFixture.copyDirectoryToProject("dep", ProjectWithDepModuleDescriptor.getDepModuleSourceRoot());
}
@Override
protected void tearDown() throws Exception {
try {
myProjectDescriptor.cleanUpSources();
}
catch (Throwable e) {
addSuppressedException(e);
}
finally {
super.tearDown();
}
}
private void doTestWithDependency() {
myFixture.configureByFile("src/" + getTestName(false) + ".java");
myFixture.testHighlighting(true, false, false);
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return myProjectDescriptor;
}
@Nullable
@Override
protected InspectionProfileEntry getInspection() {
return new SuspiciousPackagePrivateAccessInspection();
}
private static class ProjectWithDepModuleDescriptor extends ProjectDescriptor {
private static final String DEP_MODULE_SOURCE_ROOT = "dep-module-src";
private VirtualFile mySourceRoot;
ProjectWithDepModuleDescriptor(@NotNull LanguageLevel languageLevel) {
super(languageLevel);
}
@Override
public void setUpProject(@NotNull Project project, @NotNull SetupHandler handler) throws Exception {
super.setUpProject(project, handler);
WriteAction.run(() -> {
Module mainModule = ModuleManager.getInstance(project).findModuleByName(TEST_MODULE_NAME);
File depModuleDir = FileUtil.createTempDirectory("dep-module-", null);
Module depModule = createModule(project, depModuleDir + "/dep.iml");
ModuleRootModificationUtil.updateModel(depModule, model -> {
model.getModuleExtension(LanguageLevelModuleExtension.class).setLanguageLevel(myLanguageLevel);
model.setSdk(getSdk());
mySourceRoot = createSourceRoot(depModule, DEP_MODULE_SOURCE_ROOT);
model.addContentEntry(mySourceRoot).addSourceFolder(mySourceRoot, JavaSourceRootType.SOURCE);
});
ModuleRootModificationUtil.addDependency(mainModule, depModule);
});
}
public void cleanUpSources() throws IOException {
if (mySourceRoot != null) {
WriteAction.run(() -> mySourceRoot.delete(this));
}
}
@NotNull
private static String getDepModuleSourceRoot() {
return "../" + DEP_MODULE_SOURCE_ROOT;
}
}
}
@@ -0,0 +1,103 @@
// Copyright 2000-2019 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.siyeh.ig.dependency;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.LanguageLevelModuleExtension;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.testFramework.LightProjectDescriptor;
import com.siyeh.ig.LightInspectionTestCase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaSourceRootType;
import java.io.File;
import java.io.IOException;
public class SuspiciousPackagePrivateAccessInspectionTestCase extends LightInspectionTestCase {
private final ProjectWithDepModuleDescriptor myProjectDescriptor = new ProjectWithDepModuleDescriptor(LanguageLevel.HIGHEST);
private final String myExtension;
public SuspiciousPackagePrivateAccessInspectionTestCase(String extension) {
myExtension = extension;
}
@Override
protected void setUp() throws Exception {
super.setUp();
myFixture.copyDirectoryToProject("dep", ProjectWithDepModuleDescriptor.getDepModuleSourceRoot());
}
@Override
protected void tearDown() throws Exception {
try {
myProjectDescriptor.cleanUpSources();
}
catch (Throwable e) {
addSuppressedException(e);
}
finally {
super.tearDown();
}
}
protected void doTestWithDependency() {
myFixture.configureByFile("src/" + getTestName(false) + "." + myExtension);
myFixture.testHighlighting(true, false, false);
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return myProjectDescriptor;
}
@Nullable
@Override
protected InspectionProfileEntry getInspection() {
return new SuspiciousPackagePrivateAccessInspection();
}
private static class ProjectWithDepModuleDescriptor extends ProjectDescriptor {
private static final String DEP_MODULE_SOURCE_ROOT = "dep-module-src";
private VirtualFile mySourceRoot;
ProjectWithDepModuleDescriptor(@NotNull LanguageLevel languageLevel) {
super(languageLevel);
}
@Override
public void setUpProject(@NotNull Project project, @NotNull SetupHandler handler) throws Exception {
super.setUpProject(project, handler);
WriteAction.run(() -> {
Module mainModule = ModuleManager.getInstance(project).findModuleByName(TEST_MODULE_NAME);
File depModuleDir = FileUtil.createTempDirectory("dep-module-", null);
Module depModule = createModule(project, depModuleDir + "/dep.iml");
ModuleRootModificationUtil.updateModel(depModule, model -> {
model.getModuleExtension(LanguageLevelModuleExtension.class).setLanguageLevel(myLanguageLevel);
model.setSdk(getSdk());
mySourceRoot = createSourceRoot(depModule, DEP_MODULE_SOURCE_ROOT);
model.addContentEntry(mySourceRoot).addSourceFolder(mySourceRoot, JavaSourceRootType.SOURCE);
});
ModuleRootModificationUtil.addDependency(mainModule, depModule);
});
}
public void cleanUpSources() throws IOException {
if (mySourceRoot != null) {
WriteAction.run(() -> mySourceRoot.delete(this));
}
}
@NotNull
private static String getDepModuleSourceRoot() {
return "../" + DEP_MODULE_SOURCE_ROOT;
}
}
}