mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java] module 'provides' statement highlighting
This commit is contained in:
+10
@@ -1619,6 +1619,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
if (!myHolder.hasErrorResults()) myHolder.add(ModuleHighlightUtil.checkFileName(module, myFile));
|
||||
if (!myHolder.hasErrorResults()) myHolder.add(ModuleHighlightUtil.checkFileDuplicates(module, myFile));
|
||||
if (!myHolder.hasErrorResults()) myHolder.addAll(ModuleHighlightUtil.checkDuplicateStatements(module));
|
||||
if (!myHolder.hasErrorResults()) myHolder.addAll(ModuleHighlightUtil.checkUnusedServices(module));
|
||||
if (!myHolder.hasErrorResults()) myHolder.add(ModuleHighlightUtil.checkFileLocation(module, myFile));
|
||||
}
|
||||
|
||||
@@ -1651,6 +1652,15 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitProvidesStatement(PsiProvidesStatement statement) {
|
||||
super.visitProvidesStatement(statement);
|
||||
if (PsiUtil.isLanguageLevel9OrHigher(myFile)) {
|
||||
PsiJavaCodeReferenceElement intRef = statement.getInterfaceReference(), implRef = statement.getImplementationReference();
|
||||
if (!myHolder.hasErrorResults()) myHolder.add(ModuleHighlightUtil.checkServiceImplementation(implRef, intRef));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private HighlightInfo checkFeature(@NotNull PsiElement element, @NotNull Feature feature) {
|
||||
return HighlightUtil.checkFeature(element, feature, myLanguageLevel, myFile);
|
||||
|
||||
+84
-3
@@ -31,22 +31,29 @@ import com.intellij.openapi.module.impl.scopes.ModulesScope;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectFileIndex;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.FilenameIndex;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.JBIterable;
|
||||
import com.intellij.util.graph.Graph;
|
||||
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;
|
||||
|
||||
import static com.intellij.openapi.util.Pair.pair;
|
||||
import static com.intellij.psi.PsiJavaModule.MODULE_INFO_FILE;
|
||||
import static com.intellij.psi.SyntaxTraverser.psiTraverser;
|
||||
|
||||
@@ -101,6 +108,46 @@ public class ModuleHighlightUtil {
|
||||
st -> Optional.ofNullable(st.getClassReference()).map(ModuleHighlightUtil::refText),
|
||||
"module.duplicate.uses", results);
|
||||
|
||||
checkDuplicateRefs(
|
||||
psiTraverser().children(module).filter(PsiProvidesStatement.class),
|
||||
st -> Optional.of(pair(st.getInterfaceReference(), st.getImplementationReference()))
|
||||
.map(p -> p.first != null && p.second != null ? refText(p.first) + " / " + refText(p.second) : null),
|
||||
"module.duplicate.provides", results);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static List<HighlightInfo> checkUnusedServices(@NotNull PsiJavaModule module) {
|
||||
List<HighlightInfo> results = ContainerUtil.newSmartList();
|
||||
|
||||
Set<String> exports = ContainerUtil.newTroveSet(), uses = ContainerUtil.newTroveSet();
|
||||
for (PsiElement child : psiTraverser().children(module)) {
|
||||
if (child instanceof PsiExportsStatement) {
|
||||
PsiJavaCodeReferenceElement ref = ((PsiExportsStatement)child).getPackageReference();
|
||||
if (ref != null) exports.add(refText(ref));
|
||||
}
|
||||
else if (child instanceof PsiUsesStatement) {
|
||||
PsiJavaCodeReferenceElement ref = ((PsiUsesStatement)child).getClassReference();
|
||||
if (ref != null) uses.add(refText(ref));
|
||||
}
|
||||
}
|
||||
|
||||
Module host = ModuleUtilCore.findModuleForPsiElement(module);
|
||||
for (PsiProvidesStatement statement : psiTraverser().children(module).filter(PsiProvidesStatement.class)) {
|
||||
PsiJavaCodeReferenceElement ref = statement.getInterfaceReference();
|
||||
if (ref != null) {
|
||||
PsiElement target = ref.resolve();
|
||||
if (target instanceof PsiClass && ModuleUtilCore.findModuleForPsiElement(target) == host) {
|
||||
String className = refText(ref), packageName = StringUtil.getPackageName(className);
|
||||
if (!exports.contains(packageName) && !uses.contains(className)) {
|
||||
String message = JavaErrorMessages.message("module.service.unused");
|
||||
results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(range(ref)).description(message).create());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -108,7 +155,7 @@ public class ModuleHighlightUtil {
|
||||
Function<T, Optional<String>> ref,
|
||||
@PropertyKey(resourceBundle = JavaErrorMessages.BUNDLE) String key,
|
||||
List<HighlightInfo> results) {
|
||||
Set<String> filter = ContainerUtil.newHashSet();
|
||||
Set<String> filter = ContainerUtil.newTroveSet();
|
||||
for (T statement : statements) {
|
||||
String refText = ref.apply(statement).orElse(null);
|
||||
if (refText != null && !filter.add(refText)) {
|
||||
@@ -199,7 +246,7 @@ public class ModuleHighlightUtil {
|
||||
static List<HighlightInfo> checkExportTargets(@NotNull PsiExportsStatement statement, @NotNull PsiJavaModule container) {
|
||||
List<HighlightInfo> results = ContainerUtil.newSmartList();
|
||||
|
||||
Set<String> targets = ContainerUtil.newHashSet();
|
||||
Set<String> targets = ContainerUtil.newTroveSet();
|
||||
for (PsiJavaModuleReferenceElement refElement : psiTraverser().children(statement).filter(PsiJavaModuleReferenceElement.class)) {
|
||||
String refText = refElement.getReferenceText();
|
||||
PsiPolyVariantReference ref = refElement.getReference();
|
||||
@@ -235,6 +282,40 @@ public class ModuleHighlightUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static HighlightInfo checkServiceImplementation(@Nullable PsiJavaCodeReferenceElement implRef,
|
||||
@Nullable PsiJavaCodeReferenceElement intRef) {
|
||||
if (implRef != null && intRef != null) {
|
||||
PsiElement implTarget = implRef.resolve(), intTarget = intRef.resolve();
|
||||
if (implTarget instanceof PsiClass && intTarget instanceof PsiClass) {
|
||||
PsiClass implClass = (PsiClass)implTarget;
|
||||
if (!InheritanceUtil.isInheritorOrSelf(implClass, (PsiClass)intTarget, true)) {
|
||||
String message = JavaErrorMessages.message("module.service.subtype");
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(implRef)).description(message).create();
|
||||
}
|
||||
if (implClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
|
||||
String message = JavaErrorMessages.message("module.service.abstract", implClass.getName());
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(implRef)).description(message).create();
|
||||
}
|
||||
|
||||
PsiMethod[] constructors = implClass.getConstructors();
|
||||
if (constructors.length > 0) {
|
||||
PsiMethod constructor = JBIterable.of(constructors).find(c -> c.getParameterList().getParametersCount() == 0);
|
||||
if (constructor == null) {
|
||||
String message = JavaErrorMessages.message("module.service.no.ctor", implClass.getName());
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(implRef)).description(message).create();
|
||||
}
|
||||
if (!constructor.hasModifierProperty(PsiModifier.PUBLIC)) {
|
||||
String message = JavaErrorMessages.message("module.service.hidden.ctor", implClass.getName());
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(implRef)).description(message).create();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static QuickFixFactory factory() {
|
||||
return QuickFixFactory.getInstance();
|
||||
}
|
||||
|
||||
@@ -15,10 +15,14 @@
|
||||
*/
|
||||
package com.intellij.psi;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a {@code provides} statement of a Java module declaration.
|
||||
*
|
||||
* @since 2016.3
|
||||
*/
|
||||
public interface PsiProvidesStatement extends PsiElement {
|
||||
@Nullable PsiJavaCodeReferenceElement getInterfaceReference();
|
||||
@Nullable PsiJavaCodeReferenceElement getImplementationReference();
|
||||
}
|
||||
+1
-1
@@ -175,7 +175,7 @@ public class PsiJavaCodeReferenceElementImpl extends CompositePsiElement impleme
|
||||
PsiJavaCodeReferenceCodeFragment fragment = (PsiJavaCodeReferenceCodeFragment)treeParent.getPsi();
|
||||
return fragment.isClassesAccepted() ? CLASS_FQ_OR_PACKAGE_NAME_KIND : PACKAGE_NAME_KIND;
|
||||
}
|
||||
if (i == JavaElementType.USES_STATEMENT) {
|
||||
if (i == JavaElementType.USES_STATEMENT || i == JavaElementType.PROVIDES_STATEMENT) {
|
||||
return CLASS_FQ_NAME_KIND;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,18 +15,39 @@
|
||||
*/
|
||||
package com.intellij.psi.impl.source;
|
||||
|
||||
import com.intellij.psi.JavaElementVisitor;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
import com.intellij.psi.PsiProvidesStatement;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.tree.CompositePsiElement;
|
||||
import com.intellij.psi.impl.source.tree.JavaElementType;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class PsiProvidesStatementImpl extends CompositePsiElement implements PsiProvidesStatement {
|
||||
public PsiProvidesStatementImpl() {
|
||||
super(JavaElementType.PROVIDES_STATEMENT);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiJavaCodeReferenceElement getInterfaceReference() {
|
||||
for (PsiElement child = getFirstChild(); child != null; child = child.getNextSibling()) {
|
||||
if (child instanceof PsiJavaCodeReferenceElement) return (PsiJavaCodeReferenceElement)child;
|
||||
if (PsiUtil.isJavaToken(child, JavaTokenType.WITH_KEYWORD)) break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiJavaCodeReferenceElement getImplementationReference() {
|
||||
boolean afterWith = false;
|
||||
for (PsiElement child = getFirstChild(); child != null; child = child.getNextSibling()) {
|
||||
if (afterWith && child instanceof PsiJavaCodeReferenceElement) return (PsiJavaCodeReferenceElement)child;
|
||||
if (PsiUtil.isJavaToken(child, JavaTokenType.WITH_KEYWORD)) afterWith = true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(@NotNull PsiElementVisitor visitor) {
|
||||
if (visitor instanceof JavaElementVisitor) {
|
||||
|
||||
@@ -396,6 +396,7 @@ module.file.duplicate='module-info.java' already exists in the module
|
||||
module.duplicate.requires=Duplicate requires: {0}
|
||||
module.duplicate.export=Duplicate export: {0}
|
||||
module.duplicate.uses=Duplicate uses: {0}
|
||||
module.duplicate.provides=Duplicate provides: {0}
|
||||
module.file.wrong.location=Module declaration should be located in a module's source root
|
||||
module.open.duplicate.text=Go to duplicate
|
||||
module.not.found=Module not found: {0}
|
||||
@@ -404,6 +405,11 @@ 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}
|
||||
module.service.no.ctor=The service implementation does not have a default constructor: {0}
|
||||
module.service.hidden.ctor=The default constructor of the service implementation is not public: {0}
|
||||
module.service.unused=Service interface provided but not exported or used
|
||||
|
||||
feature.generics=Generics
|
||||
feature.annotations=Annotations
|
||||
|
||||
@@ -44,6 +44,7 @@ class ModuleHighlightingTest : LightCodeInsightFixtureTestCase() {
|
||||
|
||||
fun testDuplicateStatements() {
|
||||
addFile("pkg/main/C.java", "package pkg.main;\npublic class C { }")
|
||||
addFile("pkg/main/Impl.java", "package pkg.main;\npublic class Impl extends C { }")
|
||||
doTest("""
|
||||
module M {
|
||||
requires M2;
|
||||
@@ -52,6 +53,17 @@ class ModuleHighlightingTest : LightCodeInsightFixtureTestCase() {
|
||||
<error descr="Duplicate export: pkg.main">exports pkg. main;</error>
|
||||
uses pkg.main.C;
|
||||
<error descr="Duplicate uses: pkg.main.C">uses pkg. main . /*...*/ C;</error>
|
||||
provides pkg .main .C with pkg.main.Impl;
|
||||
<error descr="Duplicate provides: pkg.main.C / pkg.main.Impl">provides pkg.main.C with pkg. main. Impl;</error>
|
||||
}""".trimIndent(), true)
|
||||
}
|
||||
|
||||
fun testUnusedStatements() {
|
||||
addFile("pkg/main/C.java", "package pkg.main;\npublic class C { }")
|
||||
addFile("pkg/main/Impl.java", "package pkg.main;\npublic class Impl extends C { }")
|
||||
doTest("""
|
||||
module M {
|
||||
provides pkg.main.<warning descr="Service interface provided but not exported or used">C</warning> with pkg.main.Impl;
|
||||
}""".trimIndent(), true)
|
||||
}
|
||||
|
||||
@@ -89,6 +101,23 @@ class ModuleHighlightingTest : LightCodeInsightFixtureTestCase() {
|
||||
}""".trimIndent())
|
||||
}
|
||||
|
||||
fun testProvides() {
|
||||
addFile("pkg/main/C.java", "package pkg.main;\npublic interface C { }")
|
||||
addFile("pkg/main/Impl1.java", "package pkg.main;\nclass Impl1 { }")
|
||||
addFile("pkg/main/Impl2.java", "package pkg.main;\npublic class Impl2 { }")
|
||||
addFile("pkg/main/Impl3.java", "package pkg.main;\npublic abstract class Impl3 implements C { }")
|
||||
addFile("pkg/main/Impl4.java", "package pkg.main;\npublic class Impl4 implements C {\n public Impl4(String s) { }\n}")
|
||||
addFile("pkg/main/Impl5.java", "package pkg.main;\npublic class Impl5 implements C {\n protected Impl5() { }\n}")
|
||||
doTest("""
|
||||
module M {
|
||||
provides pkg.main.C with pkg.main.<error descr="'pkg.main.Impl1' is not public in 'pkg.main'. Cannot be accessed from outside package">Impl1</error>;
|
||||
provides pkg.main.C with pkg.main.<error descr="The service implementation type must be a subtype of the service interface type">Impl2</error>;
|
||||
provides pkg.main.C with pkg.main.<error descr="The service implementation is an abstract class: Impl3">Impl3</error>;
|
||||
provides pkg.main.C with pkg.main.<error descr="The service implementation does not have a default constructor: Impl4">Impl4</error>;
|
||||
provides pkg.main.C with pkg.main.<error descr="The default constructor of the service implementation is not public: Impl5">Impl5</error>;
|
||||
}""".trimIndent())
|
||||
}
|
||||
|
||||
//<editor-fold desc="Helpers.">
|
||||
private fun addFile(path: String, text: String) = VfsTestUtil.createFile(LightPlatformTestCase.getSourceRoot(), path, text)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user