From c21e08d9111b38bcce4005514ce0e12867474292 Mon Sep 17 00:00:00 2001 From: Pavel Dolgov Date: Thu, 3 Nov 2016 14:34:27 +0300 Subject: [PATCH] Java: Created the inspection "Non-accessible type is exposed" for Java 9 modules (IDEA-162768) --- ...va9NonAccessibleTypeExposedInspection.java | 142 ++++++++++++++++++ .../Java9NonAccessibleTypeExposed.html | 7 + resources/src/META-INF/IdeaPlugin.xml | 4 + 3 files changed, 153 insertions(+) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/java19modules/Java9NonAccessibleTypeExposedInspection.java create mode 100644 resources-en/src/inspectionDescriptions/Java9NonAccessibleTypeExposed.html diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/java19modules/Java9NonAccessibleTypeExposedInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/java19modules/Java9NonAccessibleTypeExposedInspection.java new file mode 100644 index 000000000000..d89687f166e9 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/java19modules/Java9NonAccessibleTypeExposedInspection.java @@ -0,0 +1,142 @@ +/* + * 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.daemon.impl.analysis.JavaModuleGraphUtil; +import com.intellij.codeInspection.BaseJavaLocalInspectionTool; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.roots.ModuleFileIndex; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.containers.ContainerUtil; +import gnu.trove.THashSet; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +/** + * @author Pavel.Dolgov + */ +public class Java9NonAccessibleTypeExposedInspection 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)) { + PsiJavaModule psiModule = JavaModuleGraphUtil.findDescriptorByElement(file); + if (psiModule != null) { + VirtualFile vFile = file.getVirtualFile(); + if (vFile != null) { + Module module = ProjectFileIndex.SERVICE.getInstance(holder.getProject()).getModuleForFile(vFile); + if (module != null) { + Set exportedPackageNames = new THashSet<>(ContainerUtil.mapNotNull(psiModule.getExports(), p -> p.getPackageName())); + if (exportedPackageNames.contains(javaFile.getPackageName())) { + return new NonAccessibleTypeExposedVisitor(holder, module, exportedPackageNames); + } + } + } + } + } + } + return PsiElementVisitor.EMPTY_VISITOR; + } + + private static class NonAccessibleTypeExposedVisitor extends JavaElementVisitor { + private final ProblemsHolder myHolder; + private final ModuleFileIndex myModuleFileIndex; + private final Set myExportedPackageNames; + + public NonAccessibleTypeExposedVisitor(ProblemsHolder holder, Module module, Set exportedPackageNames) { + myHolder = holder; + myModuleFileIndex = ModuleRootManager.getInstance(module).getFileIndex(); + myExportedPackageNames = exportedPackageNames; + } + + @Override + public void visitMethod(PsiMethod method) { + super.visitMethod(method); + if (isModulePublicApi(method)) { + if (!method.isConstructor()) { + checkType(method.getReturnType(), method.getReturnTypeElement()); + } + for (PsiParameter parameter : method.getParameterList().getParameters()) { + checkType(parameter.getType(), parameter.getTypeElement()); + } + } + } + + @Override + public void visitField(PsiField field) { + super.visitField(field); + if (isModulePublicApi(field)) { + checkType(field.getType(), field.getTypeElement()); + } + } + + private void checkType(@Nullable PsiType type, @Nullable PsiTypeElement typeElement) { + if (typeElement != null) { + PsiClass psiClass = PsiUtil.resolveClassInType(type); + if (psiClass != null && isInModuleSource(psiClass) && !isModulePublicApi(psiClass)) { + myHolder.registerProblem(typeElement, "The class is not exported from the module"); + } + } + } + + @Contract("null -> false") + private boolean isModulePublicApi(@Nullable PsiMember member) { + if (member != null && isModulePublicApiMember(member)) { + PsiElement parent = member.getParent(); + if (parent instanceof PsiClass) { + return isModulePublicApi((PsiClass)parent); + } + if (parent instanceof PsiJavaFile) { + String packageName = ((PsiJavaFile)parent).getPackageName(); + return myExportedPackageNames.contains(packageName); + } + } + return false; + } + + private static boolean isModulePublicApiMember(@NotNull PsiMember member) { + if (member.hasModifierProperty(PsiModifier.PUBLIC) || member.hasModifierProperty(PsiModifier.PROTECTED)) { + return true; + } + PsiClass containingClass = member.getContainingClass(); + return containingClass != null && containingClass.isInterface(); + } + + private boolean isInModuleSource(@NotNull PsiClass psiClass) { + PsiFile psiFile = psiClass.getContainingFile(); + if (psiFile != null) { + VirtualFile vFile = psiFile.getVirtualFile(); + if (vFile != null) { + return myModuleFileIndex.isInSourceContent(vFile); + } + } + return false; + } + } +} diff --git a/resources-en/src/inspectionDescriptions/Java9NonAccessibleTypeExposed.html b/resources-en/src/inspectionDescriptions/Java9NonAccessibleTypeExposed.html new file mode 100644 index 000000000000..dd8f5008a01c --- /dev/null +++ b/resources-en/src/inspectionDescriptions/Java9NonAccessibleTypeExposed.html @@ -0,0 +1,7 @@ + + +A module in Java 9 may hide some of its classes by not exporting their packages.

+If the public API of a class in an exported package references a class from non-exported package, +such API isn't useful outside of the module. + + \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index f9aad09c86df..3d3c489b84d4 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -868,6 +868,10 @@ groupKey="group.names.language.level.specific.issues.and.migration.aids" enabledByDefault="true" level="WARNING" implementationClass="com.intellij.codeInspection.OptionalIsPresentInspection" displayName="Replace Optional.isPresent() checks with functional-style expressions"/> + com.intellij.codeInsight.intention.impl.SplitIfAction