mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-highlighting] Refactor JavaPlatformModuleSystem: separate finding problem, reporting it and creating fixes
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: 0990deef5a6fc379464fe1e8c9ecb46d73a1c13b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
5a96924d32
commit
9559d696ff
@@ -321,6 +321,29 @@ public final class JavaPsiModuleUtil {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param source source module
|
||||
* @param packageName package name in source module
|
||||
* @param target target module
|
||||
* @return true if a source module exports a specified package to the target module, or to everybody (if the target module is null)
|
||||
*/
|
||||
public static boolean exports(@NotNull PsiJavaModule source, @NotNull String packageName, @Nullable PsiJavaModule target) {
|
||||
Map<String, Set<String>> exports = CachedValuesManager.getCachedValue(source, () ->
|
||||
CachedValueProvider.Result.create(exportsMap(source), source.getContainingFile()));
|
||||
Set<String> targets = exports.get(packageName);
|
||||
return targets != null && (targets.isEmpty() || target != null && targets.contains(target.getName()));
|
||||
}
|
||||
|
||||
private static @NotNull Map<String, Set<String>> exportsMap(@NotNull PsiJavaModule source) {
|
||||
Map<String, Set<String>> map = new HashMap<>();
|
||||
for (PsiPackageAccessibilityStatement statement : source.getExports()) {
|
||||
String pkg = statement.getPackageName();
|
||||
List<String> targets = statement.getModuleNames();
|
||||
map.put(pkg, targets.isEmpty() ? Collections.emptySet() : new HashSet<>(targets));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a dependency conflict when a single package is imported from two modules
|
||||
* @param packageName package name
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.codeserver.core
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.roots.JdkOrderEntry
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.pom.java.LanguageLevel
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.light.LightJavaModule
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.util.JavaMultiReleaseUtil
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
import com.intellij.util.indexing.DumbModeAccessType
|
||||
|
||||
/**
|
||||
* Represents the access details between the current module and the target module.
|
||||
*
|
||||
* @property current The current module.
|
||||
* @property target The target module.
|
||||
*/
|
||||
data class JpmsModuleAccessInfo(val current: JpmsModuleInfo.CurrentModuleInfo, val target: JpmsModuleInfo.TargetModuleInfo) {
|
||||
enum class JpmsModuleAccessProblem {
|
||||
FROM_NAMED,
|
||||
FROM_UNNAMED,
|
||||
TO_UNNAMED,
|
||||
PACKAGE_BAD_NAME,
|
||||
BAD_NAME,
|
||||
PACKAGE_NOT_IN_GRAPH,
|
||||
NOT_IN_GRAPH,
|
||||
PACKAGE_DOES_NOT_READ,
|
||||
DOES_NOT_READ,
|
||||
JPS_DEPENDENCY_PROBLEM
|
||||
}
|
||||
|
||||
fun checkAccess(
|
||||
place: PsiFileSystemItem,
|
||||
isAccessible: (JpmsModuleAccessInfo) -> Boolean,
|
||||
): JpmsModuleAccessProblem? {
|
||||
val targetModule = target.module
|
||||
if (targetModule != null) {
|
||||
if (targetModule == current.module) {
|
||||
return null
|
||||
}
|
||||
|
||||
val currentJpsModule = current.jpsModule
|
||||
if (current.module == null) {
|
||||
val origin = targetModule.containingFile?.virtualFile
|
||||
if (origin == null || currentJpsModule == null ||
|
||||
ModuleRootManager.getInstance(currentJpsModule).fileIndex.getOrderEntryForFile(origin) !is JdkOrderEntry
|
||||
) {
|
||||
return null // a target is not on the mandatory module path
|
||||
}
|
||||
|
||||
if (!accessibleFromJdkModules(place, isAccessible) &&
|
||||
!inAddedModules(currentJpsModule, targetModule.name) &&
|
||||
!hasUpgrade(currentJpsModule, targetModule.name, target.packageName, place)) {
|
||||
return JpmsModuleAccessProblem.PACKAGE_NOT_IN_GRAPH
|
||||
}
|
||||
}
|
||||
|
||||
if (targetModule !is LightJavaModule &&
|
||||
!JavaPsiModuleUtil.exports(targetModule, target.packageName, current.module) &&
|
||||
(currentJpsModule == null || !inAddedExports(currentJpsModule, targetModule.name, target.packageName, current.name)) &&
|
||||
(currentJpsModule == null || !isPatchedModule(targetModule.name, currentJpsModule, place))) {
|
||||
return if (current.module == null) JpmsModuleAccessProblem.FROM_UNNAMED else JpmsModuleAccessProblem.FROM_NAMED
|
||||
}
|
||||
|
||||
if (current.module != null &&
|
||||
targetModule.name != PsiJavaModule.JAVA_BASE &&
|
||||
!isAccessible(this) &&
|
||||
!inAddedReads(current.module, targetModule)) {
|
||||
return when {
|
||||
PsiNameHelper.isValidModuleName(targetModule.name, current.module) -> JpmsModuleAccessProblem.PACKAGE_DOES_NOT_READ
|
||||
else -> JpmsModuleAccessProblem.PACKAGE_BAD_NAME
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (current.module != null) {
|
||||
val autoModule = JpmsModuleInfo.TargetModuleInfo(detectAutomaticModule(target), target.packageName)
|
||||
if (autoModule.module == null) {
|
||||
return JpmsModuleAccessProblem.TO_UNNAMED
|
||||
}
|
||||
else if (!isAccessible(JpmsModuleAccessInfo(current, autoModule)) &&
|
||||
!inAddedReads(current.module, null) &&
|
||||
!inSameMultiReleaseModule(current, target)) {
|
||||
return JpmsModuleAccessProblem.TO_UNNAMED
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* @param place place where the target module is accessed
|
||||
* @return access problem, or null if the target module is accessible without any problem
|
||||
*/
|
||||
fun checkModuleAccess(place: PsiElement): JpmsModuleAccessProblem? {
|
||||
val targetModule = target.module
|
||||
if (targetModule != null) {
|
||||
if (targetModule == current.module) {
|
||||
return null
|
||||
}
|
||||
|
||||
val currentJpsModule = current.jpsModule
|
||||
if (current.module == null) {
|
||||
var origin = targetModule.containingFile?.virtualFile
|
||||
if (origin == null && targetModule is LightJavaModule) origin = targetModule.rootVirtualFile
|
||||
if (origin == null || currentJpsModule == null) return null
|
||||
|
||||
if (ModuleRootManager.getInstance(currentJpsModule).fileIndex.getOrderEntryForFile(origin) !is JdkOrderEntry) {
|
||||
val searchScope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(currentJpsModule)
|
||||
if (searchScope.contains(origin)) return null
|
||||
return JpmsModuleAccessProblem.JPS_DEPENDENCY_PROBLEM
|
||||
}
|
||||
|
||||
if (!accessibleFromJdkModules(place, { it.isAccessible() }) &&
|
||||
!inAddedModules(currentJpsModule, targetModule.name)) {
|
||||
return JpmsModuleAccessProblem.NOT_IN_GRAPH
|
||||
}
|
||||
}
|
||||
|
||||
if (current.module != null &&
|
||||
targetModule.name != PsiJavaModule.JAVA_BASE &&
|
||||
!isAccessible() &&
|
||||
!inAddedReads(current.module, targetModule)) {
|
||||
return if (PsiNameHelper.isValidModuleName(targetModule.name, current.module)) JpmsModuleAccessProblem.DOES_NOT_READ
|
||||
else JpmsModuleAccessProblem.BAD_NAME
|
||||
}
|
||||
}
|
||||
else if (current.module != null) {
|
||||
val autoModule = JpmsModuleInfo.TargetModuleInfo(detectAutomaticModule(target), target.packageName)
|
||||
if (autoModule.module != null &&
|
||||
!JpmsModuleAccessInfo(current, autoModule).isAccessible() &&
|
||||
!inAddedReads(current.module, null) &&
|
||||
!inSameMultiReleaseModule(current, target)) {
|
||||
return JpmsModuleAccessProblem.TO_UNNAMED
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun isExported(): Boolean {
|
||||
val targetModule = target.module ?: return false
|
||||
if (!targetModule.isPhysical || JavaPsiModuleUtil.exports(targetModule, target.packageName, current.module)) return true
|
||||
val currentJpsModule = current.jpsModule ?: return false
|
||||
return inAddedExports(currentJpsModule, targetModule.name, target.packageName, current.name)
|
||||
}
|
||||
|
||||
fun isAccessible(): Boolean {
|
||||
val currentModule = current.module ?: return false
|
||||
val targetModule = target.module ?: return false
|
||||
return JavaPsiModuleUtil.reads(currentModule, targetModule)
|
||||
}
|
||||
|
||||
private fun accessibleFromJdkModules(
|
||||
place: PsiElement,
|
||||
isAccessible: (JpmsModuleAccessInfo) -> Boolean,
|
||||
): Boolean {
|
||||
val jpsModule = current.jpsModule ?: return false
|
||||
val targetModule = target.module ?: return false
|
||||
if (targetModule.name == PsiJavaModule.JAVA_BASE) return true
|
||||
|
||||
if (!isJdkModule(jpsModule, targetModule)) return false
|
||||
val languageLevel = PsiUtil.getLanguageLevel(place)
|
||||
// https://bugs.openjdk.org/browse/JDK-8197532
|
||||
val jdkModulePred: (PsiJavaModule) -> Boolean = if (languageLevel >= LanguageLevel.JDK_11) {
|
||||
{ module -> module.exports.any { e -> e.moduleNames.isEmpty() } }
|
||||
}
|
||||
else {
|
||||
val javaSE = DumbModeAccessType.RELIABLE_DATA_ONLY.ignoreDumbMode<PsiJavaModule, Throwable> {
|
||||
JavaPsiFacade.getInstance(place.project).findModule("java.se", jpsModule.moduleWithLibrariesScope)
|
||||
}
|
||||
|
||||
if (javaSE != null) {
|
||||
{ module ->
|
||||
(!module.name.startsWith("java.") && module.exports.any { e -> e.moduleNames.isEmpty() }) ||
|
||||
isAccessible(JpmsModuleAccessInfo(JpmsModuleInfo.CurrentModuleInfo(javaSE, current.name) { jpsModule }, target))
|
||||
}
|
||||
}
|
||||
else {
|
||||
{ _ -> true }
|
||||
}
|
||||
}
|
||||
val noIncubatorPred: (PsiJavaModule) -> Boolean = { module -> !module.doNotResolveByDefault() }
|
||||
return jdkModulePred(targetModule) && noIncubatorPred(targetModule)
|
||||
}
|
||||
|
||||
private fun isJdkModule(jpsModule: Module, psiModule: PsiJavaModule): Boolean {
|
||||
val sdkHomePath = ModuleRootManager.getInstance(jpsModule).getSdk()?.homePath?.replace('\\', '/')
|
||||
val moduleFilePath = psiModule.containingFile?.virtualFile?.path?.replace('\\', '/')
|
||||
if (sdkHomePath != null && moduleFilePath != null) {
|
||||
return moduleFilePath.startsWith("$sdkHomePath!") ||
|
||||
moduleFilePath.startsWith(if (sdkHomePath.last() == '/') sdkHomePath else "$sdkHomePath/")
|
||||
}
|
||||
else {
|
||||
return psiModule.name.startsWith("java.") ||
|
||||
psiModule.name.startsWith("jdk.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inSameMultiReleaseModule(current: JpmsModuleInfo, target: JpmsModuleInfo): Boolean {
|
||||
val placeModule = current.jpsModule ?: return false
|
||||
val targetModule = target.jpsModule ?: return false
|
||||
return JavaMultiReleaseUtil.areMainAndAdditionalMultiReleaseModules(targetModule, placeModule)
|
||||
}
|
||||
|
||||
private fun detectAutomaticModule(current: JpmsModuleInfo): PsiJavaModule? {
|
||||
val module = current.jpsModule ?: return null
|
||||
return JavaPsiFacade.getInstance(module.project)
|
||||
.findModule(LightJavaModule.moduleName(module.name),
|
||||
GlobalSearchScope.moduleScope(module))
|
||||
}
|
||||
|
||||
private fun hasUpgrade(module: Module, targetName: String, packageName: String, place: PsiFileSystemItem): Boolean {
|
||||
if (PsiJavaModule.UPGRADEABLE.contains(targetName)) {
|
||||
val target = JavaPsiFacade.getInstance(module.project).findPackage(packageName)
|
||||
if (target != null) {
|
||||
val useVFile = place.virtualFile
|
||||
if (useVFile != null) {
|
||||
val index = ModuleRootManager.getInstance(module).fileIndex
|
||||
val test = index.isInTestSourceContent(useVFile)
|
||||
val dirs = target.getDirectories(module.getModuleWithDependenciesAndLibrariesScope(test))
|
||||
return dirs.any { index.getOrderEntryForFile(it.virtualFile) !is JdkOrderEntry }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isPatchedModule(targetModuleName: String, module: Module, place: PsiFileSystemItem): Boolean {
|
||||
val virtualFile = place.virtualFile ?: return false
|
||||
val rootForFile = ProjectRootManager.getInstance(place.project).fileIndex.getSourceRootForFile(virtualFile) ?: return false
|
||||
return JavaCompilerConfigurationProxy.isPatchedModuleRoot(targetModuleName, module, rootForFile)
|
||||
}
|
||||
|
||||
private fun inAddedExports(module: Module, targetName: String, packageName: String, useName: String): Boolean {
|
||||
val options = JavaCompilerConfigurationProxy.getAdditionalOptions(module.project, module)
|
||||
if (options.isEmpty()) return false
|
||||
val prefix = "${targetName}/${packageName}="
|
||||
return JavaCompilerConfigurationProxy.optionValues(options, JavaModuleSystem.ADD_EXPORTS_OPTION)
|
||||
.filter { it.startsWith(prefix) }
|
||||
.map { it.substring(prefix.length) }
|
||||
.flatMap { it.splitToSequence(",") }
|
||||
.any { it == useName }
|
||||
}
|
||||
|
||||
private fun inAddedModules(module: Module, moduleName: String): Boolean {
|
||||
val options = JavaCompilerConfigurationProxy.getAdditionalOptions(module.project, module)
|
||||
return JavaCompilerConfigurationProxy.optionValues(options, JavaModuleSystem.ADD_MODULES_OPTION)
|
||||
.flatMap { it.splitToSequence(",") }
|
||||
.any { it == moduleName || it == JavaModuleSystem.ALL_SYSTEM || it == JavaModuleSystem.ALL_MODULE_PATH }
|
||||
}
|
||||
|
||||
private fun inAddedReads(fromJavaModule: PsiJavaModule, toJavaModule: PsiJavaModule?): Boolean {
|
||||
val fromModule = ModuleUtilCore.findModuleForPsiElement(fromJavaModule) ?: return false
|
||||
val options = JavaCompilerConfigurationProxy.getAdditionalOptions(fromModule.project, fromModule)
|
||||
return JavaCompilerConfigurationProxy.optionValues(options, JavaModuleSystem.ADD_READS_OPTION)
|
||||
.flatMap { it.splitToSequence(",") }
|
||||
.any {
|
||||
val (optFromModuleName, optToModuleName) = it.split("=").apply { it.first() to it.last() }
|
||||
fromJavaModule.name == optFromModuleName &&
|
||||
(toJavaModule?.name == optToModuleName || (optToModuleName == JavaModuleSystem.ALL_UNNAMED && isUnnamedModule(toJavaModule)))
|
||||
}
|
||||
}
|
||||
|
||||
private fun isUnnamedModule(module: PsiJavaModule?) = module == null || module is LightJavaModule
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.codeserver.core
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.psi.JavaModuleSystem
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFileSystemItem
|
||||
import com.intellij.psi.PsiJavaModule
|
||||
import com.intellij.psi.impl.light.LightJavaModule
|
||||
|
||||
/**
|
||||
* Represents a JPMS module and the corresponding module in IntelliJ project model
|
||||
*/
|
||||
sealed interface JpmsModuleInfo {
|
||||
val module: PsiJavaModule?
|
||||
val jpsModule: Module?
|
||||
|
||||
/**
|
||||
* Represents the details of a current module.
|
||||
*
|
||||
* Note: "name" is not always possible to get from "module".
|
||||
* For example, "module" can be "java.se", but the name is from the original module.
|
||||
*
|
||||
* @property module The PsiJavaModule instance representing the module.
|
||||
* @property name original module name
|
||||
* @property jpsModule JPS module initialization.
|
||||
*/
|
||||
class CurrentModuleInfo(override val module: PsiJavaModule?, val name: String, jps: () -> Module? = { null }) : JpmsModuleInfo {
|
||||
constructor(use: PsiJavaModule?, element: PsiElement) : this(use, use?.name ?: JavaModuleSystem.ALL_UNNAMED, {
|
||||
ModuleUtilCore.findModuleForPsiElement(element)
|
||||
})
|
||||
|
||||
override val jpsModule: Module? by lazy { jps() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the details of a target module
|
||||
*/
|
||||
class TargetModuleInfo(element: PsiElement?, val packageName: String) : JpmsModuleInfo {
|
||||
override val jpsModule: Module? by lazy {
|
||||
if (element == null) return@lazy null
|
||||
ModuleUtilCore.findModuleForPsiElement(element)
|
||||
}
|
||||
override val module: PsiJavaModule? by lazy {
|
||||
JavaPsiModuleUtil.findDescriptorByElement(element)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return access information when the specified target module is accessed at a given place
|
||||
*/
|
||||
fun accessAt(place: PsiFileSystemItem): JpmsModuleAccessInfo {
|
||||
val useModule = JavaPsiModuleUtil.findDescriptorByElement(place).let { if (it is LightJavaModule) null else it }
|
||||
val current = CurrentModuleInfo(useModule, place)
|
||||
return JpmsModuleAccessInfo(current, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-20
@@ -21,8 +21,6 @@ import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.light.LightJavaModule;
|
||||
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.util.CachedValueProvider.Result;
|
||||
import com.intellij.psi.util.CachedValuesManager;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
@@ -32,7 +30,10 @@ import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.intellij.openapi.roots.DependencyScope.PROVIDED;
|
||||
import static com.intellij.psi.PsiJavaModule.JAVA_BASE;
|
||||
@@ -66,13 +67,6 @@ public final class JavaModuleGraphUtil {
|
||||
return javaModule instanceof LightJavaModule ? null : javaModule;
|
||||
}
|
||||
|
||||
public static boolean exports(@NotNull PsiJavaModule source, @NotNull String packageName, @Nullable PsiJavaModule target) {
|
||||
Map<String, Set<String>> exports = CachedValuesManager.getCachedValue(source, () ->
|
||||
Result.create(exportsMap(source), source.getContainingFile()));
|
||||
Set<String> targets = exports.get(packageName);
|
||||
return targets != null && (targets.isEmpty() || target != null && targets.contains(target.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a specified module is readable from a given context
|
||||
*
|
||||
@@ -193,16 +187,6 @@ public final class JavaModuleGraphUtil {
|
||||
return scope == PROVIDED;
|
||||
}
|
||||
|
||||
private static @NotNull Map<String, Set<String>> exportsMap(@NotNull PsiJavaModule source) {
|
||||
Map<String, Set<String>> map = new HashMap<>();
|
||||
for (PsiPackageAccessibilityStatement statement : source.getExports()) {
|
||||
String pkg = statement.getPackageName();
|
||||
List<String> targets = statement.getModuleNames();
|
||||
map.put(pkg, targets.isEmpty() ? Collections.emptySet() : new HashSet<>(targets));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public static class JavaModuleScope extends GlobalSearchScope {
|
||||
private final @NotNull MultiMap<String, PsiJavaModule> myModules;
|
||||
private final boolean myIncludeLibraries;
|
||||
|
||||
+2
-1
@@ -7,6 +7,7 @@ import com.intellij.codeInspection.*;
|
||||
import com.intellij.codeInspection.reference.*;
|
||||
import com.intellij.concurrency.ConcurrentCollectionFactory;
|
||||
import com.intellij.java.analysis.JavaAnalysisBundle;
|
||||
import com.intellij.java.codeserver.core.JavaPsiModuleUtil;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
|
||||
import com.intellij.openapi.module.LanguageLevelUtil;
|
||||
@@ -173,7 +174,7 @@ public final class Java9RedundantRequiresStatementInspection extends GlobalJavaB
|
||||
.map(PsiPackageAccessibilityStatement::getPackageName)
|
||||
.nonNull()
|
||||
.filter(myImportedPackages::contains)
|
||||
.anyMatch(packageName -> JavaModuleGraphUtil.exports(transitiveModule, packageName, currentModule));
|
||||
.anyMatch(packageName -> JavaPsiModuleUtil.exports(transitiveModule, packageName, currentModule));
|
||||
}
|
||||
|
||||
private void addTransitiveDependencies(@NotNull PsiRequiresStatement statementToDelete) {
|
||||
|
||||
@@ -4,7 +4,6 @@ package com.intellij.psi.impl
|
||||
import com.intellij.codeInsight.JavaModuleSystemEx
|
||||
import com.intellij.codeInsight.JavaModuleSystemEx.ErrorWithFixes
|
||||
import com.intellij.codeInsight.daemon.JavaErrorBundle
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.AddExportsDirectiveFix
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.AddExportsOptionFix
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.AddModulesOptionFix
|
||||
@@ -12,23 +11,14 @@ import com.intellij.codeInsight.daemon.impl.quickfix.AddRequiresDirectiveFix
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.codeInsight.intention.QuickFixFactory
|
||||
import com.intellij.java.JavaBundle
|
||||
import com.intellij.java.codeserver.core.JavaPsiModuleUtil
|
||||
import com.intellij.java.codeserver.core.JavaPsiModuleUtil.findDescriptorByElement
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.roots.JdkOrderEntry
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.java.codeserver.core.JpmsModuleAccessInfo
|
||||
import com.intellij.java.codeserver.core.JpmsModuleAccessInfo.JpmsModuleAccessProblem
|
||||
import com.intellij.java.codeserver.core.JpmsModuleInfo.TargetModuleInfo
|
||||
import com.intellij.openapi.roots.ProjectFileIndex
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.pom.java.JavaFeature
|
||||
import com.intellij.pom.java.LanguageLevel
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.JavaModuleSystem.*
|
||||
import com.intellij.psi.impl.light.LightJavaModule
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.util.JavaMultiReleaseUtil
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
import com.intellij.util.indexing.DumbModeAccessType
|
||||
import org.jetbrains.annotations.Nls
|
||||
|
||||
/**
|
||||
* Checks package accessibility according to JLS 7 "Packages and Modules".
|
||||
@@ -40,46 +30,29 @@ internal class JavaPlatformModuleSystem : JavaModuleSystemEx {
|
||||
override fun getName(): String = JavaBundle.message("java.platform.module.system.name")
|
||||
|
||||
override fun isAccessible(targetPackageName: String, targetFile: PsiFile?, place: PsiElement): Boolean {
|
||||
return getProblem(targetPackageName, targetFile, place, true) { (current, target) -> isExported(current, target) } == null
|
||||
return getProblem(targetPackageName, targetFile, place, true) { it.isExported() } == null
|
||||
}
|
||||
|
||||
override fun checkAccess(targetPackageName: String, targetFile: PsiFile?, place: PsiElement): ErrorWithFixes? {
|
||||
return getProblem(targetPackageName, targetFile, place, false) { (current, target) ->
|
||||
val currentModule = current.module ?: return@getProblem false
|
||||
val targetModule = target.module ?: return@getProblem false
|
||||
return@getProblem JavaPsiModuleUtil.reads(currentModule, targetModule)
|
||||
}
|
||||
return getProblem(targetPackageName, targetFile, place, false) { it.isAccessible() }
|
||||
}
|
||||
|
||||
override fun isAccessible(targetModule: PsiJavaModule, place: PsiElement): Boolean {
|
||||
return getProblem(targetModule, place, true) { (current, target) ->
|
||||
if (current.module == null || target.module == null) return@getProblem false
|
||||
return@getProblem JavaPsiModuleUtil.reads(current.module, target.module!!)
|
||||
} == null
|
||||
val useFile = place.containingFile?.originalFile ?: return true
|
||||
return TargetModuleInfo(targetModule, "").accessAt(useFile).checkModuleAccess(place) == null
|
||||
}
|
||||
|
||||
override fun checkAccess(targetModule: PsiJavaModule, place: PsiElement): ErrorWithFixes? {
|
||||
return getProblem(targetModule, place, false) { (current, target) ->
|
||||
if (current.module == null || target.module == null) return@getProblem false
|
||||
return@getProblem JavaPsiModuleUtil.reads(current.module, target.module!!)
|
||||
}
|
||||
}
|
||||
val useFile = place.containingFile?.originalFile ?: return null
|
||||
val moduleAccess = TargetModuleInfo(targetModule, "").accessAt(useFile)
|
||||
|
||||
private fun isExported(current: CurrentModuleInfo, target: TargetModuleInfo): Boolean {
|
||||
val targetModule = target.module ?: return false
|
||||
if (!targetModule.isPhysical || JavaModuleGraphUtil.exports(targetModule, target.packageName, current.module)) return true
|
||||
val currentJpsModule = current.jpsModule ?: return false
|
||||
return inAddedExports(currentJpsModule, targetModule.name, target.packageName, current.name)
|
||||
}
|
||||
|
||||
private fun getProblem(targetModule: PsiJavaModule, place: PsiElement, quick: Boolean,
|
||||
isAccessible: (ModuleAccessInfo) -> Boolean): ErrorWithFixes? {
|
||||
val target = TargetModuleInfo(targetModule, "")
|
||||
return checkModuleAccess(target, place, quick, isAccessible)
|
||||
val access = moduleAccess.checkModuleAccess(place)
|
||||
return if (access == null) null
|
||||
else moduleAccess.toErrorWithFixes(access, place)
|
||||
}
|
||||
|
||||
private fun getProblem(targetPackageName: String, targetFile: PsiFile?, place: PsiElement, quick: Boolean,
|
||||
isAccessible: (ModuleAccessInfo) -> Boolean): ErrorWithFixes? {
|
||||
isAccessible: (JpmsModuleAccessInfo) -> Boolean): ErrorWithFixes? {
|
||||
val originalTargetFile = targetFile?.originalFile
|
||||
val useFile = place.containingFile?.originalFile ?: return null
|
||||
if (!PsiUtil.isAvailable(JavaFeature.MODULES, useFile)) return null
|
||||
@@ -117,300 +90,69 @@ internal class JavaPlatformModuleSystem : JavaModuleSystemEx {
|
||||
|
||||
private val ERR = ErrorWithFixes("-")
|
||||
|
||||
private fun checkModuleAccess(
|
||||
target: TargetModuleInfo, place: PsiElement, quick: Boolean,
|
||||
isAccessible: (ModuleAccessInfo) -> Boolean,
|
||||
): ErrorWithFixes? {
|
||||
val useFile = place.containingFile?.originalFile ?: return null
|
||||
val useModule = findDescriptorByElement(useFile).let { if (it is LightJavaModule) null else it }
|
||||
val current = CurrentModuleInfo(useModule, place)
|
||||
private fun checkAccess(target: TargetModuleInfo, place: PsiFileSystemItem, quick: Boolean,
|
||||
isAccessible: (JpmsModuleAccessInfo) -> Boolean): ErrorWithFixes? {
|
||||
val moduleAccess = target.accessAt(place)
|
||||
|
||||
val targetModule = target.module
|
||||
if (targetModule != null) {
|
||||
if (targetModule == current.module) {
|
||||
return null
|
||||
}
|
||||
|
||||
val currentJpsModule = current.jpsModule
|
||||
if (current.module == null) {
|
||||
var origin = targetModule.containingFile?.virtualFile
|
||||
if (origin == null && targetModule is LightJavaModule) origin = targetModule.rootVirtualFile
|
||||
if (origin == null || currentJpsModule == null) return null
|
||||
|
||||
if (ModuleRootManager.getInstance(currentJpsModule).fileIndex.getOrderEntryForFile(origin) !is JdkOrderEntry) {
|
||||
val searchScope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(currentJpsModule)
|
||||
if (searchScope.contains(origin)) return null
|
||||
return if (quick) ERR
|
||||
else if (place is PsiJavaModuleReferenceElement) {
|
||||
val reference: PsiJavaModuleReference = place.reference ?: return null
|
||||
val registrar: MutableList<IntentionAction> = ArrayList()
|
||||
QuickFixFactory.getInstance().registerOrderEntryFixes(reference, registrar)
|
||||
ErrorWithFixes("-", registrar)
|
||||
}
|
||||
else null
|
||||
}
|
||||
|
||||
if (!accessibleFromJdkModules(current, target, place, isAccessible) &&
|
||||
!inAddedModules(currentJpsModule, targetModule.name)) {
|
||||
return if (quick) ERR
|
||||
else ErrorWithFixes(JavaErrorBundle.message("module.not.in.graph", targetModule.name),
|
||||
listOf(AddModulesOptionFix(currentJpsModule, targetModule.name).asIntention()))
|
||||
}
|
||||
}
|
||||
|
||||
if (current.module != null &&
|
||||
targetModule.name != PsiJavaModule.JAVA_BASE &&
|
||||
!isAccessible(ModuleAccessInfo(current, target)) &&
|
||||
!inAddedReads(current.module, targetModule)) {
|
||||
return when {
|
||||
quick -> ERR
|
||||
PsiNameHelper.isValidModuleName(targetModule.name, current.module) -> ErrorWithFixes(JavaErrorBundle.message("module.does.not.read", targetModule.name, current.name),
|
||||
listOf(AddRequiresDirectiveFix(current.module, targetModule.name).asIntention()))
|
||||
else -> ErrorWithFixes(JavaErrorBundle.message("module.bad.name", targetModule.name))
|
||||
}
|
||||
}
|
||||
val access = moduleAccess.checkAccess(place, isAccessible)
|
||||
return when {
|
||||
access == null -> null
|
||||
quick -> ERR
|
||||
else -> moduleAccess.toErrorWithFixes(access, place)
|
||||
}
|
||||
else if (current.module != null) {
|
||||
val autoModule = TargetModuleInfo(detectAutomaticModule(target), target.packageName)
|
||||
if (autoModule.module != null &&
|
||||
!isAccessible(ModuleAccessInfo(current, autoModule)) &&
|
||||
!inAddedReads(current.module, null) &&
|
||||
!inSameMultiReleaseModule(current, target)) {
|
||||
return if (quick) ERR else ErrorWithFixes(JavaErrorBundle.message("module.access.to.unnamed", target.packageName, current.name))
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun checkAccess(target: TargetModuleInfo, place: PsiFileSystemItem, quick: Boolean,
|
||||
isAccessible: (ModuleAccessInfo) -> Boolean): ErrorWithFixes? {
|
||||
val useModule = findDescriptorByElement(place).let { if (it is LightJavaModule) null else it }
|
||||
val current = CurrentModuleInfo(useModule, place)
|
||||
fun JpmsModuleAccessInfo.toErrorWithFixes(problem: JpmsModuleAccessProblem, place: PsiElement): ErrorWithFixes {
|
||||
return ErrorWithFixes(getMessage(problem), getFixes(problem, place))
|
||||
}
|
||||
|
||||
private fun JpmsModuleAccessInfo.getMessage(problem: JpmsModuleAccessProblem): @Nls String {
|
||||
val current = current
|
||||
val target = target
|
||||
val targetModule = target.module
|
||||
if (targetModule != null) {
|
||||
if (targetModule == current.module) {
|
||||
return null
|
||||
}
|
||||
return when (problem) {
|
||||
JpmsModuleAccessProblem.FROM_NAMED ->
|
||||
JavaErrorBundle.message("module.access.from.named", target.packageName, targetModule!!.name, current.name)
|
||||
JpmsModuleAccessProblem.FROM_UNNAMED -> JavaErrorBundle.message("module.access.from.unnamed", target.packageName, targetModule!!.name)
|
||||
JpmsModuleAccessProblem.TO_UNNAMED -> JavaErrorBundle.message("module.access.to.unnamed", target.packageName, current.name)
|
||||
JpmsModuleAccessProblem.PACKAGE_BAD_NAME -> JavaErrorBundle.message("module.access.bad.name", target.packageName, targetModule!!.name)
|
||||
JpmsModuleAccessProblem.BAD_NAME -> JavaErrorBundle.message("module.bad.name", targetModule!!.name)
|
||||
JpmsModuleAccessProblem.NOT_IN_GRAPH -> JavaErrorBundle.message("module.not.in.graph", targetModule!!.name)
|
||||
JpmsModuleAccessProblem.PACKAGE_NOT_IN_GRAPH -> JavaErrorBundle.message("module.access.not.in.graph", target.packageName, targetModule!!.name)
|
||||
JpmsModuleAccessProblem.DOES_NOT_READ -> JavaErrorBundle.message("module.does.not.read", targetModule!!.name, current.name)
|
||||
JpmsModuleAccessProblem.PACKAGE_DOES_NOT_READ -> JavaErrorBundle.message("module.access.does.not.read", target.packageName, targetModule!!.name, current.name)
|
||||
JpmsModuleAccessProblem.JPS_DEPENDENCY_PROBLEM -> "-" // TODO: proper name?
|
||||
}
|
||||
}
|
||||
|
||||
val currentJpsModule = current.jpsModule
|
||||
if (current.module == null) {
|
||||
val origin = targetModule.containingFile?.virtualFile
|
||||
if (origin == null || currentJpsModule == null ||
|
||||
ModuleRootManager.getInstance(currentJpsModule).fileIndex.getOrderEntryForFile(origin) !is JdkOrderEntry) {
|
||||
return null // a target is not on the mandatory module path
|
||||
}
|
||||
|
||||
if (!accessibleFromJdkModules(current, target, place, isAccessible) &&
|
||||
!inAddedModules(currentJpsModule, targetModule.name) &&
|
||||
!hasUpgrade(currentJpsModule, targetModule.name, target.packageName, place)) {
|
||||
return if (quick) ERR
|
||||
else ErrorWithFixes(
|
||||
JavaErrorBundle.message("module.access.not.in.graph", target.packageName, targetModule.name),
|
||||
listOf(AddModulesOptionFix(currentJpsModule, targetModule.name).asIntention()))
|
||||
}
|
||||
}
|
||||
|
||||
if (targetModule !is LightJavaModule &&
|
||||
!JavaModuleGraphUtil.exports(targetModule, target.packageName, current.module) &&
|
||||
(currentJpsModule == null || !inAddedExports(currentJpsModule, targetModule.name, target.packageName, current.name)) &&
|
||||
(currentJpsModule == null || !isPatchedModule(targetModule.name, currentJpsModule, place))) {
|
||||
if (quick) return ERR
|
||||
val fixes = when {
|
||||
private fun JpmsModuleAccessInfo.getFixes(
|
||||
problem: JpmsModuleAccessProblem,
|
||||
place: PsiElement
|
||||
): List<IntentionAction> {
|
||||
val currentJpsModule = current.jpsModule
|
||||
val targetModule = target.module
|
||||
return when (problem) {
|
||||
JpmsModuleAccessProblem.FROM_UNNAMED, JpmsModuleAccessProblem.FROM_NAMED -> {
|
||||
when {
|
||||
target.packageName.isEmpty() -> emptyList()
|
||||
targetModule is PsiCompiledElement && currentJpsModule != null ->
|
||||
listOf(AddExportsOptionFix(currentJpsModule, targetModule.name, target.packageName, current.name).asIntention())
|
||||
listOf(AddExportsOptionFix(currentJpsModule, targetModule.name, target.packageName,
|
||||
current.name).asIntention())
|
||||
targetModule !is PsiCompiledElement && current.module != null ->
|
||||
listOf(AddExportsDirectiveFix(targetModule, target.packageName, current.name).asIntention())
|
||||
listOf(AddExportsDirectiveFix(targetModule!!, target.packageName, current.name).asIntention())
|
||||
else -> emptyList()
|
||||
}
|
||||
return when (current.module) {
|
||||
null -> ErrorWithFixes(JavaErrorBundle.message("module.access.from.unnamed", target.packageName, targetModule.name), fixes)
|
||||
else -> ErrorWithFixes(JavaErrorBundle.message("module.access.from.named", target.packageName, targetModule.name, current.name), fixes)
|
||||
}
|
||||
}
|
||||
|
||||
if (current.module != null &&
|
||||
targetModule.name != PsiJavaModule.JAVA_BASE &&
|
||||
!isAccessible(ModuleAccessInfo(current, target)) &&
|
||||
!inAddedReads(current.module, targetModule)) {
|
||||
return when {
|
||||
quick -> ERR
|
||||
PsiNameHelper.isValidModuleName(targetModule.name, current.module) -> ErrorWithFixes(
|
||||
JavaErrorBundle.message("module.access.does.not.read", target.packageName, targetModule.name, current.name),
|
||||
listOf(AddRequiresDirectiveFix(current.module, targetModule.name).asIntention()))
|
||||
else -> ErrorWithFixes(JavaErrorBundle.message("module.access.bad.name", target.packageName, targetModule.name))
|
||||
}
|
||||
JpmsModuleAccessProblem.TO_UNNAMED, JpmsModuleAccessProblem.PACKAGE_BAD_NAME, JpmsModuleAccessProblem.BAD_NAME -> listOf()
|
||||
JpmsModuleAccessProblem.PACKAGE_NOT_IN_GRAPH, JpmsModuleAccessProblem.NOT_IN_GRAPH ->
|
||||
listOf(AddModulesOptionFix(currentJpsModule!!, targetModule!!.name).asIntention())
|
||||
JpmsModuleAccessProblem.PACKAGE_DOES_NOT_READ, JpmsModuleAccessProblem.DOES_NOT_READ ->
|
||||
listOf(AddRequiresDirectiveFix(current.module!!, targetModule!!.name).asIntention())
|
||||
JpmsModuleAccessProblem.JPS_DEPENDENCY_PROBLEM -> {
|
||||
val reference: PsiJavaModuleReference = (place as? PsiJavaModuleReferenceElement)?.reference ?: return listOf()
|
||||
val registrar: MutableList<IntentionAction> = ArrayList()
|
||||
QuickFixFactory.getInstance().registerOrderEntryFixes(reference, registrar)
|
||||
registrar
|
||||
}
|
||||
}
|
||||
else if (current.module != null) {
|
||||
val autoModule = TargetModuleInfo(detectAutomaticModule(target), target.packageName)
|
||||
if (autoModule.module == null) {
|
||||
return if (quick) ERR else ErrorWithFixes(JavaErrorBundle.message("module.access.to.unnamed", target.packageName, current.name))
|
||||
}
|
||||
else if (!isAccessible(ModuleAccessInfo(current, autoModule)) &&
|
||||
!inAddedReads(current.module, null) &&
|
||||
!inSameMultiReleaseModule(current, target)) {
|
||||
return if (quick) ERR else ErrorWithFixes(JavaErrorBundle.message("module.access.to.unnamed", target.packageName, current.name))
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun accessibleFromJdkModules(
|
||||
current: CurrentModuleInfo,
|
||||
target: TargetModuleInfo,
|
||||
place: PsiElement,
|
||||
isAccessible: (ModuleAccessInfo) -> Boolean,
|
||||
): Boolean {
|
||||
val jpsModule = current.jpsModule ?: return false
|
||||
val targetModule = target.module ?: return false
|
||||
if(targetModule.name == PsiJavaModule.JAVA_BASE) return true
|
||||
|
||||
if(!isJdkModule(jpsModule, targetModule)) return false
|
||||
val languageLevel = PsiUtil.getLanguageLevel(place)
|
||||
// https://bugs.openjdk.org/browse/JDK-8197532
|
||||
val jdkModulePred: (PsiJavaModule) -> Boolean = if (languageLevel >= LanguageLevel.JDK_11) {
|
||||
{ module -> module.exports.any { e -> e.moduleNames.isEmpty() } }
|
||||
}
|
||||
else {
|
||||
val javaSE = DumbModeAccessType.RELIABLE_DATA_ONLY.ignoreDumbMode<PsiJavaModule, Throwable> {
|
||||
JavaPsiFacade.getInstance(place.project).findModule("java.se", jpsModule.moduleWithLibrariesScope)
|
||||
}
|
||||
|
||||
if(javaSE != null) {
|
||||
{ module ->
|
||||
(!module.name.startsWith("java.") && module.exports.any { e -> e.moduleNames.isEmpty() }) ||
|
||||
isAccessible(ModuleAccessInfo(CurrentModuleInfo(javaSE, current.name) { jpsModule }, target))
|
||||
}
|
||||
} else {
|
||||
{_ -> true}
|
||||
}
|
||||
}
|
||||
val noIncubatorPred: (PsiJavaModule) -> Boolean = {module -> !module.doNotResolveByDefault()}
|
||||
return jdkModulePred(targetModule) && noIncubatorPred(targetModule)
|
||||
}
|
||||
|
||||
private fun isJdkModule(jpsModule: Module, psiModule: PsiJavaModule): Boolean {
|
||||
val sdkHomePath = ModuleRootManager.getInstance(jpsModule).getSdk()?.homePath?.replace('\\', '/')
|
||||
val moduleFilePath = psiModule.containingFile?.virtualFile?.path?.replace('\\', '/')
|
||||
if (sdkHomePath != null && moduleFilePath != null) {
|
||||
return moduleFilePath.startsWith("$sdkHomePath!") ||
|
||||
moduleFilePath.startsWith(if(sdkHomePath.last() == '/') sdkHomePath else "$sdkHomePath/")
|
||||
} else {
|
||||
return psiModule.name.startsWith("java.") ||
|
||||
psiModule.name.startsWith("jdk.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inSameMultiReleaseModule(current: ModuleInfo, target: ModuleInfo): Boolean {
|
||||
val placeModule = current.jpsModule ?: return false
|
||||
val targetModule = target.jpsModule ?: return false
|
||||
return JavaMultiReleaseUtil.areMainAndAdditionalMultiReleaseModules(targetModule, placeModule)
|
||||
}
|
||||
|
||||
private fun detectAutomaticModule(current: ModuleInfo): PsiJavaModule? {
|
||||
val module = current.jpsModule ?: return null
|
||||
return JavaPsiFacade.getInstance(module.project)
|
||||
.findModule(LightJavaModule.moduleName(module.name),
|
||||
GlobalSearchScope.moduleScope(module))
|
||||
}
|
||||
|
||||
private fun hasUpgrade(module: Module, targetName: String, packageName: String, place: PsiFileSystemItem): Boolean {
|
||||
if (PsiJavaModule.UPGRADEABLE.contains(targetName)) {
|
||||
val target = JavaPsiFacade.getInstance(module.project).findPackage(packageName)
|
||||
if (target != null) {
|
||||
val useVFile = place.virtualFile
|
||||
if (useVFile != null) {
|
||||
val index = ModuleRootManager.getInstance(module).fileIndex
|
||||
val test = index.isInTestSourceContent(useVFile)
|
||||
val dirs = target.getDirectories(module.getModuleWithDependenciesAndLibrariesScope(test))
|
||||
return dirs.any { index.getOrderEntryForFile(it.virtualFile) !is JdkOrderEntry }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isPatchedModule(targetModuleName: String, module: Module, place: PsiFileSystemItem): Boolean {
|
||||
val virtualFile = place.virtualFile ?: return false
|
||||
val rootForFile = ProjectRootManager.getInstance(place.project).fileIndex.getSourceRootForFile(virtualFile) ?: return false
|
||||
return JavaCompilerConfigurationProxy.isPatchedModuleRoot(targetModuleName, module, rootForFile)
|
||||
}
|
||||
|
||||
private fun inAddedExports(module: Module, targetName: String, packageName: String, useName: String): Boolean {
|
||||
val options = JavaCompilerConfigurationProxy.getAdditionalOptions(module.project, module)
|
||||
if (options.isEmpty()) return false
|
||||
val prefix = "${targetName}/${packageName}="
|
||||
return JavaCompilerConfigurationProxy.optionValues(options, ADD_EXPORTS_OPTION)
|
||||
.filter { it.startsWith(prefix) }
|
||||
.map { it.substring(prefix.length) }
|
||||
.flatMap { it.splitToSequence(",") }
|
||||
.any { it == useName }
|
||||
}
|
||||
|
||||
private fun inAddedModules(module: Module, moduleName: String): Boolean {
|
||||
val options = JavaCompilerConfigurationProxy.getAdditionalOptions(module.project, module)
|
||||
return JavaCompilerConfigurationProxy.optionValues(options, ADD_MODULES_OPTION)
|
||||
.flatMap { it.splitToSequence(",") }
|
||||
.any { it == moduleName || it == ALL_SYSTEM || it == ALL_MODULE_PATH }
|
||||
}
|
||||
|
||||
private fun inAddedReads(fromJavaModule: PsiJavaModule, toJavaModule: PsiJavaModule?): Boolean {
|
||||
val fromModule = ModuleUtilCore.findModuleForPsiElement(fromJavaModule) ?: return false
|
||||
val options = JavaCompilerConfigurationProxy.getAdditionalOptions(fromModule.project, fromModule)
|
||||
return JavaCompilerConfigurationProxy.optionValues(options, ADD_READS_OPTION)
|
||||
.flatMap { it.splitToSequence(",") }
|
||||
.any {
|
||||
val (optFromModuleName, optToModuleName) = it.split("=").apply { it.first() to it.last() }
|
||||
fromJavaModule.name == optFromModuleName &&
|
||||
(toJavaModule?.name == optToModuleName || (optToModuleName == ALL_UNNAMED && isUnnamedModule(toJavaModule)))
|
||||
}
|
||||
}
|
||||
|
||||
private fun isUnnamedModule(module: PsiJavaModule?) = module == null || module is LightJavaModule
|
||||
|
||||
/**
|
||||
* Represents the access details between the current module and the target module.
|
||||
*
|
||||
* @property current The current module.
|
||||
* @property target The target module.
|
||||
*/
|
||||
private data class ModuleAccessInfo(val current: CurrentModuleInfo, val target: TargetModuleInfo)
|
||||
|
||||
private interface ModuleInfo {
|
||||
val module: PsiJavaModule?
|
||||
val jpsModule: Module?
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the details of a current module.
|
||||
*
|
||||
* Note: "name" is not always possible to get from "module".
|
||||
* For example, "module" can be "java.se", but the name is from the original module.
|
||||
*
|
||||
* @property module The PsiJavaModule instance representing the module.
|
||||
* @property name original module name
|
||||
* @property jpsModule JPS module initialization.
|
||||
*/
|
||||
private class CurrentModuleInfo(override val module: PsiJavaModule?, val name: String, jps: () -> Module? = { null }) : ModuleInfo {
|
||||
constructor(use: PsiJavaModule?, element: PsiElement) : this(use, use?.name ?: ALL_UNNAMED, {
|
||||
ModuleUtilCore.findModuleForPsiElement(element)
|
||||
})
|
||||
|
||||
override val jpsModule: Module? by lazy { jps() }
|
||||
}
|
||||
|
||||
private class TargetModuleInfo(element: PsiElement?, val packageName: String) : ModuleInfo {
|
||||
override val jpsModule: Module? by lazy {
|
||||
if (element == null) return@lazy null
|
||||
ModuleUtilCore.findModuleForPsiElement(element)
|
||||
}
|
||||
override val module: PsiJavaModule? by lazy {
|
||||
findDescriptorByElement(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -20,5 +20,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.util.jdom" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.core.impl" />
|
||||
<orderEntry type="module" module-name="intellij.jvm.analysis.quickFix" />
|
||||
<orderEntry type="module" module-name="intellij.java.codeserver.core" />
|
||||
</component>
|
||||
</module>
|
||||
+2
-1
@@ -3,6 +3,7 @@ package com.intellij.codeInspection
|
||||
|
||||
import com.intellij.analysis.JvmAnalysisBundle
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil
|
||||
import com.intellij.java.codeserver.core.JavaPsiModuleUtil
|
||||
import com.intellij.packageDependencies.DependenciesBuilder
|
||||
import com.intellij.pom.java.JavaFeature
|
||||
import com.intellij.psi.PsiClass
|
||||
@@ -28,7 +29,7 @@ private class IllegalDependencyOnInternalPackage(private val holder: ProblemsHol
|
||||
val moduleName = javaModule.name
|
||||
if (moduleName.startsWith("java.")) return@analyzeFileDependencies
|
||||
val packageName = dependencyFile.packageName
|
||||
if (JavaModuleGraphUtil.exports(javaModule, packageName, null)) return@analyzeFileDependencies
|
||||
if (JavaPsiModuleUtil.exports(javaModule, packageName, null)) return@analyzeFileDependencies
|
||||
holder.registerProblem(
|
||||
place,
|
||||
JvmAnalysisBundle.message("inspection.message.illegal.dependency.module.doesn.t.export", moduleName, packageName)
|
||||
|
||||
@@ -107,7 +107,7 @@ class IdeJavaModuleResolver(private val project: Project) : JavaModuleResolver {
|
||||
|
||||
// Returns whether or not [source] exports [packageName] to [target]
|
||||
private fun exports(source: PsiJavaModule, packageName: String, target: PsiJavaModule): Boolean =
|
||||
source is LightJavaModule || JavaModuleGraphUtil.exports(source, packageName, target)
|
||||
source is LightJavaModule || JavaPsiModuleUtil.exports(source, packageName, target)
|
||||
|
||||
companion object {
|
||||
private const val MODULE_ANNOTATIONS_CACHE_SIZE = 10000
|
||||
|
||||
Reference in New Issue
Block a user