mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Java: Created an inspection (with a quick fix) that checks if a package is exported to the same module where it's declared. Tests added. (IDEA-164129)
This commit is contained in:
+4
-5
@@ -43,7 +43,10 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -291,10 +294,6 @@ public class ModuleHighlightUtil {
|
||||
String message = JavaErrorMessages.message("module.duplicate.export", refText);
|
||||
results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(refElement).description(message).create());
|
||||
}
|
||||
else if (target == container) {
|
||||
String message = JavaErrorMessages.message("module.self.export");
|
||||
results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(refElement).description(message).create());
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2000-2016 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.
|
||||
*/
|
||||
package com.intellij.codeInspection.java19modules;
|
||||
|
||||
import com.intellij.codeInsight.FileModificationService;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Pavel.Dolgov
|
||||
*/
|
||||
public class Java9ModuleExportsPackageToItselfInspection extends BaseJavaLocalInspectionTool {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
||||
PsiFile file = holder.getFile();
|
||||
if (file instanceof PsiJavaFile) {
|
||||
PsiJavaFile javaFile = (PsiJavaFile)file;
|
||||
if (javaFile.getLanguageLevel().isAtLeast(LanguageLevel.JDK_1_9) && javaFile.getModuleDeclaration() != null) {
|
||||
return new ExportedToSelfVisitor(holder);
|
||||
}
|
||||
}
|
||||
return PsiElementVisitor.EMPTY_VISITOR;
|
||||
}
|
||||
|
||||
private static class ExportedToSelfVisitor extends JavaElementVisitor {
|
||||
private final ProblemsHolder myHolder;
|
||||
|
||||
public ExportedToSelfVisitor(ProblemsHolder holder) { myHolder = holder; }
|
||||
|
||||
@Override
|
||||
public void visitExportsStatement(PsiExportsStatement statement) {
|
||||
super.visitExportsStatement(statement);
|
||||
PsiJavaModule javaModule = PsiTreeUtil.getParentOfType(statement, PsiJavaModule.class);
|
||||
if (javaModule != null) {
|
||||
String moduleName = javaModule.getModuleName();
|
||||
List<PsiJavaModuleReferenceElement> referenceElements = ContainerUtil.newArrayList(statement.getModuleReferences());
|
||||
for (PsiJavaModuleReferenceElement referenceElement : referenceElements) {
|
||||
if (moduleName.equals(referenceElement.getReferenceText())) {
|
||||
String message = InspectionsBundle.message(referenceElements.size() == 1
|
||||
? "inspection.module.exports.package.to.itself.only.message"
|
||||
: "inspection.module.exports.package.to.itself.message");
|
||||
myHolder.registerProblem(referenceElement, message,
|
||||
new DeleteExportsToModuleFix(referenceElement));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class DeleteExportsToModuleFix implements LocalQuickFix {
|
||||
private final String myModuleName;
|
||||
|
||||
public DeleteExportsToModuleFix(PsiJavaModuleReferenceElement reference) {
|
||||
myModuleName = reference.getReferenceText();
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return InspectionsBundle.message("exports.to.itself.delete.module.fix.name", myModuleName);
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return InspectionsBundle.message("exports.to.itself.delete.module.fix.family.name");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PsiElement psiElement = descriptor.getPsiElement();
|
||||
if (!FileModificationService.getInstance().prepareFileForWrite(psiElement.getContainingFile())) return;
|
||||
|
||||
PsiElement comma = findNearestComma(psiElement);
|
||||
if (comma != null) {
|
||||
comma.delete();
|
||||
}
|
||||
else {
|
||||
PsiKeyword keyword = PsiTreeUtil.getPrevSiblingOfType(psiElement, PsiKeyword.class);
|
||||
if (keyword != null && keyword.getTokenType() == JavaTokenType.TO_KEYWORD) {
|
||||
keyword.delete();
|
||||
}
|
||||
}
|
||||
psiElement.delete();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiElement findNearestComma(PsiElement psiElement) {
|
||||
for (PsiElement next = psiElement.getNextSibling(); next != null; next = next.getNextSibling()) {
|
||||
if (next instanceof PsiJavaToken && ((PsiJavaToken)next).getTokenType() == JavaTokenType.COMMA) {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
for (PsiElement prev = psiElement.getPrevSibling(); prev != null; prev = prev.getPrevSibling()) {
|
||||
if (prev instanceof PsiJavaToken && ((PsiJavaToken)prev).getTokenType() == JavaTokenType.COMMA) {
|
||||
return prev;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -405,7 +405,6 @@ module.not.on.path=Module is not in dependencies: {0}
|
||||
module.cyclic.dependence=Cyclic dependence: {0}
|
||||
package.not.found=Package not found: {0}
|
||||
package.is.empty=Package is empty: {0}
|
||||
module.self.export=Exports to itself
|
||||
module.service.enum=The service definition is an enum: {0}
|
||||
module.service.subtype=The service implementation type must be a subtype of the service interface type
|
||||
module.service.abstract=The service implementation is an abstract class: {0}
|
||||
|
||||
@@ -86,7 +86,6 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() {
|
||||
exports pkg.<error descr="Cannot resolve symbol 'missing'">missing</error>;
|
||||
exports <error descr="Package is empty: pkg.empty">pkg.empty</error>;
|
||||
exports pkg.main to <error descr="Module not found: M.missing">M.missing</error>, M2, <error descr="Duplicate export: M2">M2</error>;
|
||||
exports pkg.other to <warning descr="Exports to itself">M</warning>;
|
||||
}""".trimIndent())
|
||||
}
|
||||
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2000-2016 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.
|
||||
*/
|
||||
package com.intellij.codeInsight.daemon.quickFix
|
||||
|
||||
import com.intellij.codeInspection.InspectionsBundle
|
||||
import com.intellij.codeInspection.java19modules.Java9ModuleExportsPackageToItselfInspection
|
||||
import com.intellij.testFramework.fixtures.LightJava9ModulesCodeInsightFixtureTestCase
|
||||
import com.intellij.testFramework.fixtures.MultiModuleJava9ProjectDescriptor
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.jetbrains.annotations.NonNls
|
||||
|
||||
/**
|
||||
* @author Pavel.Dolgov
|
||||
*/
|
||||
class Java9DeleteExportsToModuleFixTest : LightJava9ModulesCodeInsightFixtureTestCase() {
|
||||
val message = InspectionsBundle.message("exports.to.itself.delete.module.fix.name", "M")!!
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
myFixture.enableInspections(Java9ModuleExportsPackageToItselfInspection())
|
||||
|
||||
addFile("module-info.java", "module M2 { }", MultiModuleJava9ProjectDescriptor.ModuleDescriptor.M2)
|
||||
addFile("module-info.java", "module M4 { }", MultiModuleJava9ProjectDescriptor.ModuleDescriptor.M4)
|
||||
addFile("pkg/main/C.java", "package pkg.main; public class C {}")
|
||||
}
|
||||
|
||||
fun testOnlySelfModule() {
|
||||
doFix("module M { exports pkg.main to <caret>M; }",
|
||||
"module M { exports pkg.main; }")
|
||||
}
|
||||
|
||||
fun testOnlySelfModuleWithComments() {
|
||||
doFix("module M { exports pkg.main to /*a*/ <caret>M /*b*/; }",
|
||||
"module M { exports pkg.main /*a*/ /*b*/; }")
|
||||
}
|
||||
|
||||
fun testSelfModuleInList() {
|
||||
doFix("module M { exports pkg.main to M2, <caret>M , M4; }",
|
||||
"module M { exports pkg.main to M2, M4; }")
|
||||
}
|
||||
|
||||
fun testSelfModuleInListWithComments() {
|
||||
doFix("module M { exports pkg.main to M2, /*a*/ <caret>M /*b*/,/*c*/ M4; }",
|
||||
"module M { exports pkg.main to M2, /*a*/ /*b*//*c*/ M4; }")
|
||||
}
|
||||
|
||||
private fun doFix(textBefore: String, @Language("JAVA") @NonNls textAfter: String) {
|
||||
myFixture.configureByText("module-info.java", textBefore)
|
||||
|
||||
val action = myFixture.findSingleIntention(message)
|
||||
assertNotNull(action)
|
||||
myFixture.launchAction(action)
|
||||
|
||||
myFixture.checkHighlighting() // no warning
|
||||
myFixture.checkResult("module-info.java", textAfter, false)
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2000-2016 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.
|
||||
*/
|
||||
package com.intellij.codeInspection
|
||||
|
||||
import com.intellij.codeInspection.java19modules.Java9ModuleExportsPackageToItselfInspection
|
||||
import com.intellij.testFramework.fixtures.LightJava9ModulesCodeInsightFixtureTestCase
|
||||
import com.intellij.testFramework.fixtures.MultiModuleJava9ProjectDescriptor
|
||||
|
||||
/**
|
||||
* @author Pavel.Dolgov
|
||||
*/
|
||||
class Java9ModuleExportsPackageToItselfTest : LightJava9ModulesCodeInsightFixtureTestCase() {
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
myFixture.enableInspections(Java9ModuleExportsPackageToItselfInspection())
|
||||
|
||||
addFile("module-info.java", "module M2 { }", MultiModuleJava9ProjectDescriptor.ModuleDescriptor.M2)
|
||||
addFile("module-info.java", "module M4 { }", MultiModuleJava9ProjectDescriptor.ModuleDescriptor.M4)
|
||||
addFile("pkg/main/C.java", "package pkg.main; public class C {}")
|
||||
}
|
||||
|
||||
fun testNoSelfModule() {
|
||||
highlight("module M { exports pkg.main to M2, M4; }")
|
||||
}
|
||||
|
||||
fun testOnlySelfModule() {
|
||||
val message = InspectionsBundle.message("inspection.module.exports.package.to.itself.only.message")
|
||||
highlight("module M { exports pkg.main to <warning descr=\"$message\">M</warning>; }")
|
||||
}
|
||||
|
||||
fun testSelfModuleInList() {
|
||||
val message = InspectionsBundle.message("inspection.module.exports.package.to.itself.message")
|
||||
highlight("module M { exports pkg.main to M2, <warning descr=\"$message\">M</warning>, M4; }")
|
||||
}
|
||||
|
||||
private fun highlight(text: String) = highlight("module-info.java", text)
|
||||
|
||||
private fun highlight(path: String, text: String) {
|
||||
myFixture.configureFromExistingVirtualFile(addFile(path, text))
|
||||
myFixture.checkHighlighting()
|
||||
}
|
||||
}
|
||||
@@ -744,3 +744,8 @@ nullable.stuff.inspection.navigate.null.argument.usages.view.name=''null'' argum
|
||||
inspection.excessive.lambda.message=Excessive lambda usage
|
||||
inspection.excessive.lambda.fix.family.name=Replace lambda with constant
|
||||
inspection.excessive.lambda.fix.name=Use ''{0}'' method without lambda
|
||||
|
||||
inspection.module.exports.package.to.itself.message=Module exports package to itself
|
||||
inspection.module.exports.package.to.itself.only.message=Module exports package only to itself
|
||||
exports.to.itself.delete.module.fix.name=Delete reference to module ''{0}''
|
||||
exports.to.itself.delete.module.fix.family.name=Delete reference to module
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
The inspection detects a situation where a package is exported to the same Java 9 module where it's defined.
|
||||
<br>Example:
|
||||
<code>module <b>B</b> { exports org.example to A, <b>B</b>, C; }</code>
|
||||
</body>
|
||||
</html>
|
||||
@@ -879,6 +879,10 @@
|
||||
groupBundle="messages.InspectionsBundle" groupKey="group.names.visibility.issues"
|
||||
enabledByDefault="true" level="WARNING" displayName="Non-accessible type is exposed"
|
||||
implementationClass="com.intellij.codeInspection.java19modules.Java9NonAccessibleTypeExposedInspection"/>
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="Java9ModuleExportsPackageToItself"
|
||||
groupBundle="messages.InspectionsBundle" groupKey="group.names.visibility.issues"
|
||||
enabledByDefault="true" level="WARNING" displayName="Package exported to its own module"
|
||||
implementationClass="com.intellij.codeInspection.java19modules.Java9ModuleExportsPackageToItselfInspection"/>
|
||||
|
||||
<intentionAction>
|
||||
<className>com.intellij.codeInsight.intention.impl.SplitIfAction</className>
|
||||
|
||||
Reference in New Issue
Block a user