mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Problems view: store file state(changes and snapshot) and inlays separately
GitOrigin-RevId: 5f4ba1f78fa99016c2ba65869c1c598c5a1c6875
This commit is contained in:
committed by
intellij-monorepo-bot
parent
04afc69c01
commit
37158871a3
@@ -0,0 +1,144 @@
|
||||
// Copyright 2000-2020 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.intellij.codeInsight.daemon.problems
|
||||
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.psi.*
|
||||
|
||||
internal typealias Snapshot = Map<SmartPsiElementPointer<PsiMember>, ScopedMember>
|
||||
|
||||
internal data class FileState(val snapshot: Snapshot, val changes: Map<PsiMember, ScopedMember?>)
|
||||
|
||||
internal class FileStateUpdater(private val prevSnapshot: Snapshot?) : JavaElementVisitor() {
|
||||
|
||||
private val snapshot = mutableMapOf<SmartPsiElementPointer<PsiMember>, ScopedMember>()
|
||||
private val changes = mutableMapOf<PsiMember, ScopedMember?>()
|
||||
|
||||
override fun visitEnumConstant(psiEnumConstant: PsiEnumConstant) = visitMember(psiEnumConstant)
|
||||
|
||||
override fun visitClass(psiClass: PsiClass) = visitMember(psiClass)
|
||||
|
||||
override fun visitField(psiField: PsiField) = visitMember(psiField)
|
||||
|
||||
override fun visitMethod(psiMethod: PsiMethod) = visitMember(psiMethod)
|
||||
|
||||
private fun visitMember(psiMember: PsiMember) {
|
||||
val member = ScopedMember.create(psiMember) ?: return
|
||||
val pointer = SmartPointerManager.createPointer(psiMember)
|
||||
snapshot[pointer] = member
|
||||
if (prevSnapshot == null) return
|
||||
val prevMember = prevSnapshot[pointer]
|
||||
if (prevMember != null && !member.hasChanged(prevMember)) return
|
||||
changes[psiMember] = prevMember
|
||||
collectRelatedChanges(psiMember, member, prevMember, changes)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val FILE_STATE_KEY = Key.create<PrivateFileState>("ProjectProblemFileStateKey")
|
||||
|
||||
@JvmStatic
|
||||
@JvmName("getState")
|
||||
internal fun getState(psiFile: PsiFile): FileState? {
|
||||
val storedState = psiFile.getUserData(FILE_STATE_KEY)?.toFileState()
|
||||
if (storedState != null || DumbService.isDumb(psiFile.project)) return storedState
|
||||
val updater = FileStateUpdater(null)
|
||||
publicApi(psiFile).forEach { it.accept(updater) }
|
||||
val snapshot = updater.snapshot
|
||||
return FileState(snapshot, emptyMap())
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmName("findState")
|
||||
internal fun findState(psiFile: PsiFile, prevSnapshot: Snapshot): FileState {
|
||||
val updater = FileStateUpdater(prevSnapshot)
|
||||
publicApi(psiFile).forEach { it.accept(updater) }
|
||||
val snapshot = updater.snapshot
|
||||
val changes = updater.changes
|
||||
for ((memberPointer, prevMember) in prevSnapshot) {
|
||||
if (memberPointer in snapshot) continue
|
||||
val psiMember = memberPointer.element ?: continue
|
||||
val member = ScopedMember.create(psiMember) ?: continue
|
||||
changes[psiMember] = prevMember
|
||||
collectRelatedChanges(psiMember, member, prevMember, changes)
|
||||
}
|
||||
return FileState(snapshot, changes)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmName("setPreviousState")
|
||||
internal fun setPreviousState(psiFile: PsiFile) {
|
||||
val (snapshot, changes) = psiFile.getUserData(FILE_STATE_KEY) ?: return
|
||||
val oldSnapshot = snapshot.toMutableMap()
|
||||
changes.forEach { (memberPointer, prevMember) ->
|
||||
if (memberPointer.element == null) return@forEach
|
||||
if (prevMember == null) oldSnapshot.remove(memberPointer)
|
||||
else oldSnapshot[memberPointer] = prevMember
|
||||
}
|
||||
psiFile.putUserData(FILE_STATE_KEY, PrivateFileState(oldSnapshot, emptyMap()))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmName("updateState")
|
||||
internal fun updateState(psiFile: PsiFile, fileState: FileState) {
|
||||
psiFile.putUserData(FILE_STATE_KEY, PrivateFileState.create(fileState.snapshot, fileState.changes))
|
||||
}
|
||||
|
||||
private fun collectRelatedChanges(
|
||||
psiMember: PsiMember,
|
||||
member: ScopedMember,
|
||||
prevMember: ScopedMember?,
|
||||
changes: MutableMap<PsiMember, ScopedMember?>
|
||||
) {
|
||||
when (psiMember) {
|
||||
is PsiMethod -> {
|
||||
val containingClass = psiMember.containingClass ?: return
|
||||
// anonymous classes and lambdas creation might be broken, need to check class usages
|
||||
changes.putIfAbsent(containingClass, null)
|
||||
}
|
||||
is PsiClass -> {
|
||||
val prevClass = prevMember?.member as? Member.Class ?: return
|
||||
val curClass = member.member as? Member.Class ?: return
|
||||
when {
|
||||
prevClass.isInterface != psiMember.isInterface -> {
|
||||
// members usages might be broken, need to check them all
|
||||
publicApi(psiMember).forEach { changes.putIfAbsent(it, null) }
|
||||
}
|
||||
prevClass.extendsList != curClass.extendsList || prevClass.implementsList != curClass.implementsList -> {
|
||||
// maybe some parent members were referenced instead of current class overrides
|
||||
publicApi(psiMember).filter { it is PsiMethod && it.isOverride() }.forEach { changes.putIfAbsent(it, null) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class PrivateFileState(
|
||||
val snapshot: Snapshot,
|
||||
val changePointers: Map<SmartPsiElementPointer<PsiMember>, ScopedMember?>
|
||||
) {
|
||||
fun toFileState(): FileState {
|
||||
val changes: Map<PsiMember, ScopedMember?> = changePointers.asSequence()
|
||||
.mapNotNull { (memberPointer, prevMember) -> memberPointer.element?.let { it to prevMember } }
|
||||
.toMap()
|
||||
return FileState(snapshot, changes)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(snapshot: Snapshot, changes: Map<PsiMember, ScopedMember?>): PrivateFileState {
|
||||
val changePointers: Map<SmartPsiElementPointer<PsiMember>, ScopedMember?> = changes.entries.asSequence()
|
||||
.map { (psiMember, prevMember) -> SmartPointerManager.createPointer(psiMember) to prevMember }
|
||||
.toMap()
|
||||
return PrivateFileState(snapshot, changePointers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun publicApi(psiElement: PsiElement) = MemberCollector.collectMembers(psiElement) { !it.hasModifier(PsiModifier.PRIVATE) }
|
||||
|
||||
private fun PsiMethod.isOverride() = hasAnnotation(CommonClassNames.JAVA_LANG_OVERRIDE)
|
||||
|
||||
private fun PsiMember.hasModifier(modifier: String) = modifierList?.hasModifierProperty(modifier) ?: false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -98,7 +98,7 @@ internal sealed class Member(open val name: String, open val modifiers: Set<Stri
|
||||
|
||||
internal data class Method(override val name: String,
|
||||
override val modifiers: Set<String>,
|
||||
val returnType: String,
|
||||
val returnType: String?,
|
||||
val paramTypes: List<String>) : Member(name, modifiers) {
|
||||
|
||||
override fun hasChanged(other: Member): Boolean {
|
||||
@@ -110,7 +110,8 @@ internal sealed class Member(open val name: String, open val modifiers: Set<Stri
|
||||
|
||||
companion object {
|
||||
internal fun create(psiMethod: PsiMethod): Method? {
|
||||
val returnType = psiMethod.returnType?.canonicalText ?: return null
|
||||
val returnType = psiMethod.returnType?.canonicalText
|
||||
if (returnType == null && !psiMethod.isConstructor) return null
|
||||
val name = psiMethod.name
|
||||
val modifiers = extractModifiers(psiMethod.modifierList)
|
||||
val paramTypes = psiMethod.parameterList.parameters.map { it.type.canonicalText }
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
// Copyright 2000-2020 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.intellij.codeInsight.daemon.problems
|
||||
|
||||
import com.intellij.lang.java.JavaLanguage
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
|
||||
internal typealias Snapshot = Map<SmartPsiElementPointer<PsiMember>, ScopedMember>
|
||||
|
||||
internal data class ChangeSet(val newSnapshot: Snapshot, val changes: Map<PsiMember, ScopedMember?>)
|
||||
|
||||
internal class SnapshotUpdater(project: Project, private val prevSnapshot: Snapshot) : JavaElementVisitor() {
|
||||
|
||||
private val pointerManager = SmartPointerManager.getInstance(project)
|
||||
|
||||
private val snapshot = mutableMapOf<SmartPsiElementPointer<PsiMember>, ScopedMember>()
|
||||
private val changes = mutableMapOf<PsiMember, ScopedMember?>()
|
||||
|
||||
override fun visitEnumConstant(psiEnumConstant: PsiEnumConstant) {
|
||||
val member = ScopedMember.create(psiEnumConstant) ?: return
|
||||
val (psiMember, prevMember) = visitMember(member, psiEnumConstant) ?: return
|
||||
changes[psiMember] = prevMember
|
||||
}
|
||||
|
||||
override fun visitClass(psiClass: PsiClass) {
|
||||
val member = ScopedMember.create(psiClass) ?: return
|
||||
val (psiMember, prevMember) = visitMember(member, psiClass) ?: return
|
||||
changes[psiMember] = prevMember
|
||||
collectRelatedChanges(psiClass, member, prevMember, changes)
|
||||
}
|
||||
|
||||
override fun visitField(psiField: PsiField) {
|
||||
val member = ScopedMember.create(psiField) ?: return
|
||||
val (psiMember, prevMember) = visitMember(member, psiField) ?: return
|
||||
changes[psiMember] = prevMember
|
||||
}
|
||||
|
||||
override fun visitMethod(psiMethod: PsiMethod) {
|
||||
val member = ScopedMember.create(psiMethod) ?: return
|
||||
val (psiMember, prevMember) = visitMember(member, psiMethod) ?: return
|
||||
changes[psiMember] = prevMember
|
||||
collectRelatedChanges(psiMethod, member, prevMember, changes)
|
||||
}
|
||||
|
||||
private fun visitMember(member: ScopedMember, psiMember: PsiMember): Pair<PsiMember, ScopedMember?>? {
|
||||
val pointer = pointerManager.createSmartPsiElementPointer(psiMember)
|
||||
snapshot[pointer] = member
|
||||
val prevMember = prevSnapshot[pointer]
|
||||
if (prevMember != null && !member.hasChanged(prevMember)) return null
|
||||
return psiMember to prevMember
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val OLD_CONTENT_KEY = Key.create<CharSequence>("OLD_CONTENT_KEY")
|
||||
private val SNAPSHOT_KEY = Key.create<Snapshot>("SNAPSHOT_KEY")
|
||||
|
||||
@JvmStatic
|
||||
@JvmName("storeContent")
|
||||
internal fun storeContent(psiFile: PsiFile, content: CharSequence) {
|
||||
if (psiFile.getUserData(SNAPSHOT_KEY) != null || psiFile.getUserData(OLD_CONTENT_KEY) != null) return
|
||||
psiFile.putUserData(OLD_CONTENT_KEY, content)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmName("updateSnapshot")
|
||||
internal fun updateSnapshot(psiFile: PsiClassOwner, snapshot: Snapshot) {
|
||||
psiFile.putUserData(OLD_CONTENT_KEY, null)
|
||||
psiFile.putUserData(SNAPSHOT_KEY, snapshot)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmName("collectChanges")
|
||||
internal fun collectChanges(psiFile: PsiClassOwner): ChangeSet? {
|
||||
val snapshot = psiFile.getUserData(SNAPSHOT_KEY)
|
||||
if (snapshot != null) return update(psiFile, snapshot)
|
||||
val content = psiFile.getUserData(OLD_CONTENT_KEY)
|
||||
if (content != null) return update(psiFile, content)
|
||||
return null
|
||||
}
|
||||
|
||||
private fun update(psiFile: PsiClassOwner, prevSnapshot: Snapshot): ChangeSet {
|
||||
val updater = SnapshotUpdater(psiFile.project, prevSnapshot)
|
||||
publicApi(psiFile).forEach { it.accept(updater) }
|
||||
val snapshot = updater.snapshot
|
||||
val changes = updater.changes
|
||||
for ((memberPointer, prevMember) in prevSnapshot) {
|
||||
if (memberPointer in snapshot) continue
|
||||
val psiMember = memberPointer.element ?: continue
|
||||
val member = ScopedMember.create(psiMember) ?: continue
|
||||
changes[psiMember] = prevMember
|
||||
collectRelatedChanges(psiMember, member, prevMember, changes)
|
||||
}
|
||||
return ChangeSet(snapshot, changes)
|
||||
}
|
||||
|
||||
private fun update(psiFile: PsiClassOwner, oldContent: CharSequence): ChangeSet? {
|
||||
val content = psiFile.viewProvider.contents
|
||||
if (content == oldContent) return null
|
||||
val project = psiFile.project
|
||||
val module = ModuleUtilCore.findModuleForFile(psiFile) ?: return null
|
||||
val scope = module.moduleScope
|
||||
val oldFile = parseFile(project, oldContent) ?: return null
|
||||
val publicMembers = constructMembers(psiFile) { !it.hasModifier(PsiModifier.PRIVATE) }
|
||||
val oldPsiMembers = publicApi(oldFile)
|
||||
val prevSnapshot: MutableMap<SmartPsiElementPointer<PsiMember>, ScopedMember> = mutableMapOf()
|
||||
val removedMembers = constructSnapshot(project, scope, publicMembers, oldPsiMembers, prevSnapshot)
|
||||
if (removedMembers.size == 1) {
|
||||
val removedMember = removedMembers[0]
|
||||
val replacement = findReplacement(removedMember, publicMembers, psiFile)
|
||||
if (replacement != null) prevSnapshot[SmartPointerManager.createPointer(replacement)] = removedMember
|
||||
}
|
||||
return update(psiFile, prevSnapshot)
|
||||
}
|
||||
|
||||
private fun constructSnapshot(project: Project,
|
||||
scope: GlobalSearchScope,
|
||||
publicMembers: MutableMap<ScopedMember, PsiMember>,
|
||||
oldPsiMembers: List<PsiMember>,
|
||||
snapshot: MutableMap<SmartPsiElementPointer<PsiMember>, ScopedMember>): List<ScopedMember> {
|
||||
val pointerManager = SmartPointerManager.getInstance(project)
|
||||
val removedMembers = mutableListOf<ScopedMember>()
|
||||
for (oldPsiMember in oldPsiMembers) {
|
||||
val member = ScopedMember.create(oldPsiMember, scope) ?: continue
|
||||
val psiMember = publicMembers.remove(member)
|
||||
if (psiMember == null) {
|
||||
removedMembers.add(member)
|
||||
continue
|
||||
}
|
||||
val memberPointer = pointerManager.createSmartPsiElementPointer(psiMember)
|
||||
snapshot[memberPointer] = member
|
||||
}
|
||||
return removedMembers
|
||||
}
|
||||
|
||||
private fun findReplacement(removedMember: ScopedMember,
|
||||
publicMembers: MutableMap<ScopedMember,
|
||||
PsiMember>, psiFile: PsiClassOwner): PsiMember? {
|
||||
if (publicMembers.size == 1) return publicMembers.entries.first().value
|
||||
val removedPrivate = removedMember.asPrivate()
|
||||
val privateMembers = constructMembers(psiFile) { it.hasModifier(PsiModifier.PRIVATE) }
|
||||
return privateMembers[removedPrivate]
|
||||
}
|
||||
|
||||
private fun parseFile(project: Project, content: CharSequence) =
|
||||
PsiFileFactory.getInstance(project).createFileFromText(JavaLanguage.INSTANCE, content) as? PsiClassOwner
|
||||
|
||||
private fun constructMembers(psiFile: PsiClassOwner, filter: (PsiMember) -> Boolean): MutableMap<ScopedMember, PsiMember> {
|
||||
val psiMembers = MemberCollector.collectMembers(psiFile, filter)
|
||||
val members = mutableMapOf<ScopedMember, PsiMember>()
|
||||
for (psiMember in psiMembers) {
|
||||
val member = ScopedMember.create(psiMember) ?: continue
|
||||
members[member] = psiMember
|
||||
}
|
||||
return members
|
||||
}
|
||||
|
||||
private fun collectRelatedChanges(psiMember: PsiMember,
|
||||
curMember: ScopedMember,
|
||||
prevMember: ScopedMember?,
|
||||
changes: MutableMap<PsiMember, ScopedMember?>) {
|
||||
when (psiMember) {
|
||||
is PsiMethod -> {
|
||||
val containingClass = psiMember.containingClass ?: return
|
||||
// anonymous classes and lambdas creation might be broken, need to check class usages
|
||||
changes.putIfAbsent(containingClass, null)
|
||||
}
|
||||
is PsiClass -> {
|
||||
val prevClass = prevMember?.member as? Member.Class ?: return
|
||||
val curClass = curMember.member as? Member.Class ?: return
|
||||
when {
|
||||
prevClass.isInterface != psiMember.isInterface -> {
|
||||
// members usages might be broken, need to check them all
|
||||
publicApi(psiMember).forEach { changes.putIfAbsent(it, null) }
|
||||
}
|
||||
prevClass.extendsList != curClass.extendsList || prevClass.implementsList != curClass.implementsList -> {
|
||||
// maybe some parent members were referenced instead of current class overrides
|
||||
publicApi(psiMember).filter { it is PsiMethod && it.isOverride() }.forEach { changes.putIfAbsent(it, null) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun publicApi(psiElement: PsiElement) = MemberCollector.collectMembers(psiElement) { !it.hasModifier(PsiModifier.PRIVATE) }
|
||||
|
||||
private fun PsiMethod.isOverride() = hasAnnotation(CommonClassNames.JAVA_LANG_OVERRIDE)
|
||||
|
||||
private fun PsiMember.hasModifier(modifier: String) = modifierList?.hasModifierProperty(modifier) ?: false
|
||||
}
|
||||
|
||||
}
|
||||
+7
-70
@@ -1,35 +1,22 @@
|
||||
// Copyright 2000-2020 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.intellij.codeInsight.daemon.problems.pass;
|
||||
|
||||
import com.intellij.codeInsight.daemon.problems.SnapshotUpdater;
|
||||
import com.intellij.codeInsight.daemon.problems.FileStateUpdater;
|
||||
import com.intellij.injected.editor.VirtualFileWindow;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.event.BulkAwareDocumentListener;
|
||||
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectManager;
|
||||
import com.intellij.openapi.project.ProjectManagerListener;
|
||||
import com.intellij.openapi.startup.StartupActivity;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiJavaFile;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.intellij.util.ObjectUtils.tryCast;
|
||||
|
||||
class ProjectProblemFileSelectionListener implements FileEditorManagerListener, BulkAwareDocumentListener, ProjectManagerListener {
|
||||
|
||||
private static final Key<Boolean> PROJECT_PROBLEM_FILE_CHANGE_LISTENER_KEY = Key.create("ProjectProblemFileChangeListenerKey");
|
||||
class ProjectProblemFileSelectionListener implements FileEditorManagerListener {
|
||||
|
||||
private final Project myProject;
|
||||
|
||||
@@ -40,60 +27,12 @@ class ProjectProblemFileSelectionListener implements FileEditorManagerListener,
|
||||
@Override
|
||||
public void selectionChanged(@NotNull FileEditorManagerEvent event) {
|
||||
VirtualFile oldFile = event.getOldFile();
|
||||
if (oldFile != null) removeListener(oldFile);
|
||||
VirtualFile newFile = event.getNewFile();
|
||||
if (newFile == null) return;
|
||||
PsiJavaFile psiFile = tryCast(PsiManager.getInstance(myProject).findFile(newFile), PsiJavaFile.class);
|
||||
if (psiFile == null) return;
|
||||
ProjectProblemPassUtils.removeOldInlays(psiFile);
|
||||
addListener(newFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void projectClosingBeforeSave(@NotNull Project project) {
|
||||
Arrays.stream(FileEditorManager.getInstance(project).getSelectedFiles()).forEach(vf -> removeListener(vf));
|
||||
}
|
||||
|
||||
private void addListener(VirtualFile virtualFile) {
|
||||
if (virtualFile instanceof VirtualFileWindow || !virtualFile.isValid()) return;
|
||||
PsiJavaFile psiFile = tryCast(PsiManager.getInstance(myProject).findFile(virtualFile), PsiJavaFile.class);
|
||||
if (psiFile == null) return;
|
||||
Document document = getDocument(virtualFile);
|
||||
if (document == null) return;
|
||||
if (Boolean.TRUE.equals(document.getUserData(PROJECT_PROBLEM_FILE_CHANGE_LISTENER_KEY))) return;
|
||||
document.addDocumentListener(this);
|
||||
document.putUserData(PROJECT_PROBLEM_FILE_CHANGE_LISTENER_KEY, true);
|
||||
}
|
||||
|
||||
private void removeListener(VirtualFile virtualFile) {
|
||||
if (virtualFile == null || virtualFile instanceof VirtualFileWindow || !virtualFile.isValid()) return;
|
||||
if (!(PsiManager.getInstance(myProject).findFile(virtualFile) instanceof PsiJavaFile)) return;
|
||||
Document document = getDocument(virtualFile);
|
||||
if (document == null) return;
|
||||
if (!Boolean.TRUE.equals(document.getUserData(PROJECT_PROBLEM_FILE_CHANGE_LISTENER_KEY))) return;
|
||||
document.removeDocumentListener(this);
|
||||
document.putUserData(PROJECT_PROBLEM_FILE_CHANGE_LISTENER_KEY, null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Document getDocument(@NotNull VirtualFile virtualFile) {
|
||||
FileDocumentManager documentManager = FileDocumentManager.getInstance();
|
||||
Document document = documentManager.getDocument(virtualFile);
|
||||
if (document == null) return null;
|
||||
return document;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDocumentChange(@NotNull DocumentEvent event) {
|
||||
Document document = event.getDocument();
|
||||
FileDocumentManager documentManager = FileDocumentManager.getInstance();
|
||||
VirtualFile virtualFile = documentManager.getFile(document);
|
||||
if (virtualFile == null) return;
|
||||
CharSequence content = document.getImmutableCharSequence();
|
||||
PsiJavaFile psiFile = tryCast(PsiManager.getInstance(myProject).findFile(virtualFile), PsiJavaFile.class);
|
||||
if (psiFile == null) return;
|
||||
SnapshotUpdater.storeContent(psiFile, content);
|
||||
removeListener(virtualFile);
|
||||
if (oldFile == null || oldFile instanceof VirtualFileWindow || !oldFile.isValid() || oldFile.equals(newFile)) return;
|
||||
PsiJavaFile psiJavaFile = tryCast(PsiManager.getInstance(myProject).findFile(oldFile), PsiJavaFile.class);
|
||||
if (psiJavaFile == null) return;
|
||||
ProjectProblemPassUtils.removeInlays(psiJavaFile);
|
||||
FileStateUpdater.setPreviousState(psiJavaFile);
|
||||
}
|
||||
|
||||
public static class MyStartupActivity implements StartupActivity {
|
||||
@@ -101,9 +40,7 @@ class ProjectProblemFileSelectionListener implements FileEditorManagerListener,
|
||||
public void runActivity(@NotNull Project project) {
|
||||
if (!Registry.is("project.problems.view") && !ApplicationManager.getApplication().isUnitTestMode()) return;
|
||||
ProjectProblemFileSelectionListener listener = new ProjectProblemFileSelectionListener(project);
|
||||
Arrays.stream(FileEditorManager.getInstance(project).getSelectedFiles()).forEach(vf -> listener.addListener(vf));
|
||||
project.getMessageBus().connect().subscribe(FILE_EDITOR_MANAGER, listener);
|
||||
ProjectManager.getInstance().addProjectManagerListener(project, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+63
-78
@@ -2,26 +2,33 @@
|
||||
package com.intellij.codeInsight.daemon.problems.pass;
|
||||
|
||||
import com.intellij.codeHighlighting.TextEditorHighlightingPass;
|
||||
import com.intellij.codeInsight.daemon.problems.ChangeSet;
|
||||
import com.intellij.codeInsight.daemon.problems.FileState;
|
||||
import com.intellij.codeInsight.daemon.problems.FileStateUpdater;
|
||||
import com.intellij.codeInsight.daemon.problems.ProblemCollector;
|
||||
import com.intellij.codeInsight.daemon.problems.ScopedMember;
|
||||
import com.intellij.codeInsight.hints.BlockInlayRenderer;
|
||||
import com.intellij.codeInsight.hints.presentation.InlayPresentation;
|
||||
import com.intellij.codeInsight.hints.presentation.PresentationFactory;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.editor.*;
|
||||
import com.intellij.codeInspection.SmartHashMap;
|
||||
import com.intellij.openapi.editor.BlockInlayPriority;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.Inlay;
|
||||
import com.intellij.openapi.editor.impl.EditorImpl;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiJavaFile;
|
||||
import com.intellij.psi.PsiMember;
|
||||
import com.intellij.psi.SmartPsiElementPointer;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.intellij.codeInsight.daemon.problems.SnapshotUpdater.collectChanges;
|
||||
import static com.intellij.codeInsight.daemon.problems.SnapshotUpdater.updateSnapshot;
|
||||
import static com.intellij.codeInsight.daemon.problems.pass.ProjectProblemPassUtils.*;
|
||||
|
||||
public class ProjectProblemPass extends TextEditorHighlightingPass {
|
||||
@@ -29,9 +36,7 @@ public class ProjectProblemPass extends TextEditorHighlightingPass {
|
||||
private final Editor myEditor;
|
||||
private final PsiJavaFile myFile;
|
||||
|
||||
private final SmartPointerManager myPointerManager = SmartPointerManager.getInstance(myProject);
|
||||
|
||||
private Map<Change, List<SmartPsiElementPointer<PsiElement>>> myProblems = null;
|
||||
private Map<PsiMember, Problem> myProblems = null;
|
||||
private Map<SmartPsiElementPointer<PsiMember>, ScopedMember> mySnapshot = null;
|
||||
|
||||
ProjectProblemPass(@NotNull Project project, @NotNull Editor editor, @NotNull PsiJavaFile file) {
|
||||
@@ -42,88 +47,68 @@ public class ProjectProblemPass extends TextEditorHighlightingPass {
|
||||
|
||||
@Override
|
||||
public void doCollectInformation(@NotNull ProgressIndicator progress) {
|
||||
ChangeSet changeSet = collectChanges(myFile);
|
||||
if (changeSet == null) return;
|
||||
Map<PsiMember, ScopedMember> changes = mergeWithOldChanges(myFile, changeSet.getChanges());
|
||||
myProblems = ReadAction.nonBlocking(() -> collectProblems(changes)).executeSynchronously();
|
||||
mySnapshot = changeSet.getNewSnapshot();
|
||||
}
|
||||
|
||||
private @Nullable Map<Change, List<SmartPsiElementPointer<PsiElement>>> collectProblems(@NotNull Map<PsiMember, ScopedMember> changes) {
|
||||
if (changes.isEmpty()) return Collections.emptyMap();
|
||||
Map<Change, List<SmartPsiElementPointer<PsiElement>>> problems = new HashMap<>();
|
||||
for (Map.Entry<PsiMember, ScopedMember> entry : changes.entrySet()) {
|
||||
if (myProject.isDisposed()) return null;
|
||||
PsiMember curMember = entry.getKey();
|
||||
ScopedMember prevMember = entry.getValue();
|
||||
Set<PsiElement> memberProblems = ProblemCollector.collect(prevMember, curMember);
|
||||
if (memberProblems == null) memberProblems = Collections.emptySet();
|
||||
SmartPsiElementPointer<PsiMember> curMemberPointer = myPointerManager.createSmartPsiElementPointer(curMember);
|
||||
Change change = new Change(curMemberPointer, prevMember);
|
||||
problems.put(change, ContainerUtil.map(memberProblems, p -> myPointerManager.createSmartPsiElementPointer(p)));
|
||||
}
|
||||
return problems;
|
||||
FileState prevState = FileStateUpdater.getState(myFile);
|
||||
if (prevState == null) return;
|
||||
FileState curState = FileStateUpdater.findState(myFile, prevState.getSnapshot());
|
||||
myProblems = collectProblems(curState.getChanges(), prevState.getChanges());
|
||||
mySnapshot = curState.getSnapshot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doApplyInformationToEditor() {
|
||||
Document document = myDocument;
|
||||
if (document == null) return;
|
||||
Map<SmartPsiElementPointer<PsiMember>, ScopedMember> snapshot = mySnapshot;
|
||||
if (snapshot == null) return;
|
||||
Map<Change, List<SmartPsiElementPointer<PsiElement>>> problems = myProblems;
|
||||
Map<PsiMember, Problem> problems = myProblems;
|
||||
if (problems == null) return;
|
||||
InlayModel inlayModel = myEditor.getInlayModel();
|
||||
|
||||
PresentationFactory factory = new PresentationFactory((EditorImpl)myEditor);
|
||||
Map<SmartPsiElementPointer<PsiMember>, ReportedChange> reportedChanges = new HashMap<>();
|
||||
problems.forEach((change, changeProblems) -> {
|
||||
ScopedMember prevMember = change.prevMember;
|
||||
SmartPsiElementPointer<PsiMember> memberPointer = change.curMemberPointer;
|
||||
PsiMember member = memberPointer.getElement();
|
||||
if (member == null) return;
|
||||
reportedChanges.computeIfAbsent(memberPointer, (k) -> {
|
||||
if (changeProblems.isEmpty()) return new ReportedChange(prevMember, null);
|
||||
int offset = getMemberOffset(member);
|
||||
InlayPresentation presentation = getPresentation(myProject, myEditor, document, factory, offset, member, changeProblems);
|
||||
BlockInlayRenderer renderer = createBlockRenderer(presentation);
|
||||
Inlay<?> newInlay = inlayModel.addBlockElement(offset, true, true, BlockInlayPriority.PROBLEMS, renderer);
|
||||
return new ReportedChange(prevMember, newInlay);
|
||||
});
|
||||
Map<PsiMember, Inlay<?>> inlays = getInlays(myFile);
|
||||
Map<PsiMember, ScopedMember> changes = new SmartHashMap<>();
|
||||
problems.forEach((curMember, problem) -> {
|
||||
ScopedMember prevMember = problem.prevMember;
|
||||
Set<PsiElement> brokenUsages = problem.brokenUsages;
|
||||
changes.put(curMember, prevMember);
|
||||
if (brokenUsages != null) addInlay(factory, curMember, brokenUsages, inlays);
|
||||
});
|
||||
reportChanges(myFile, reportedChanges);
|
||||
updateSnapshot(myFile, snapshot);
|
||||
updateInlays(myFile, inlays);
|
||||
|
||||
FileState fileState = new FileState(snapshot, changes);
|
||||
FileStateUpdater.updateState(myFile, fileState);
|
||||
}
|
||||
|
||||
private static class Change {
|
||||
private void addInlay(PresentationFactory factory,
|
||||
PsiMember psiMember,
|
||||
@NotNull Set<PsiElement> brokenUsages,
|
||||
@NotNull Map<PsiMember, Inlay<?>> inlays) {
|
||||
Inlay<?> oldInlay = inlays.remove(psiMember);
|
||||
if (oldInlay != null) Disposer.dispose(oldInlay);
|
||||
if (brokenUsages.isEmpty()) return;
|
||||
int offset = getMemberOffset(psiMember);
|
||||
InlayPresentation presentation = getPresentation(myProject, myEditor, myEditor.getDocument(), factory, offset, psiMember, brokenUsages);
|
||||
BlockInlayRenderer renderer = createBlockRenderer(presentation);
|
||||
Inlay<?> newInlay = myEditor.getInlayModel().addBlockElement(offset, true, true, BlockInlayPriority.PROBLEMS, renderer);
|
||||
inlays.put(psiMember, newInlay);
|
||||
}
|
||||
|
||||
private final SmartPsiElementPointer<PsiMember> curMemberPointer;
|
||||
private static @NotNull Map<PsiMember, Problem> collectProblems(@NotNull Map<PsiMember, ScopedMember> curChanges,
|
||||
@NotNull Map<PsiMember, ScopedMember> oldChanges) {
|
||||
Map<PsiMember, Problem> problems = ContainerUtil.map2Map(oldChanges.entrySet(),
|
||||
e -> Pair.create(e.getKey(), new Problem(e.getValue(), null)));
|
||||
curChanges.forEach((curMember, prevMember) -> {
|
||||
Set<PsiElement> changeProblems = ProblemCollector.collect(prevMember, curMember);
|
||||
if (changeProblems == null) changeProblems = Collections.emptySet();
|
||||
problems.put(curMember, new Problem(prevMember, changeProblems));
|
||||
});
|
||||
return problems;
|
||||
}
|
||||
|
||||
private static class Problem {
|
||||
private final ScopedMember prevMember;
|
||||
private final Set<PsiElement> brokenUsages;
|
||||
|
||||
private Change(SmartPsiElementPointer<PsiMember> curMemberPointer, ScopedMember prevMember) {
|
||||
this.curMemberPointer = curMemberPointer;
|
||||
private Problem(ScopedMember prevMember, Set<PsiElement> brokenUsages) {
|
||||
this.prevMember = prevMember;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Change change = (Change)o;
|
||||
return Objects.equals(curMemberPointer, change.curMemberPointer) &&
|
||||
Objects.equals(prevMember, change.prevMember);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(curMemberPointer, prevMember);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Change{" +
|
||||
"curMemberPointer=" + curMemberPointer +
|
||||
", prevMember=" + prevMember +
|
||||
'}';
|
||||
this.brokenUsages = brokenUsages;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-81
@@ -2,7 +2,6 @@
|
||||
package com.intellij.codeInsight.daemon.problems.pass;
|
||||
|
||||
import com.intellij.codeInsight.daemon.JavaErrorBundle;
|
||||
import com.intellij.codeInsight.daemon.problems.ScopedMember;
|
||||
import com.intellij.codeInsight.hints.BlockConstrainedPresentation;
|
||||
import com.intellij.codeInsight.hints.BlockConstraints;
|
||||
import com.intellij.codeInsight.hints.BlockInlayRenderer;
|
||||
@@ -10,6 +9,7 @@ import com.intellij.codeInsight.hints.presentation.InlayPresentation;
|
||||
import com.intellij.codeInsight.hints.presentation.PresentationFactory;
|
||||
import com.intellij.codeInsight.hints.presentation.RecursivelyUpdatingRootPresentation;
|
||||
import com.intellij.codeInsight.hints.presentation.SpacePresentation;
|
||||
import com.intellij.codeInspection.SmartHashMap;
|
||||
import com.intellij.find.FindUtil;
|
||||
import com.intellij.openapi.editor.BlockInlayPriority;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
@@ -19,19 +19,21 @@ import com.intellij.openapi.editor.ex.util.EditorUtil;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.pom.Navigatable;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.javadoc.PsiDocComment;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class ProjectProblemPassUtils {
|
||||
|
||||
private static final Key<Map<SmartPsiElementPointer<PsiMember>, ReportedChange>> REPORTED_CHANGES = Key.create("REPORTED_CHANGES");
|
||||
private static final Key<Boolean> FILE_OPENED_KEY = Key.create("FILE_OPENED");
|
||||
private static final Key<Map<SmartPsiElementPointer<PsiMember>, Inlay<?>>> PROBLEM_INLAY_HINTS = Key.create("ProjectProblemInlayHintsKey");
|
||||
|
||||
static @NotNull InlayPresentation getPresentation(@NotNull Project project,
|
||||
@NotNull Editor editor,
|
||||
@@ -39,21 +41,20 @@ public class ProjectProblemPassUtils {
|
||||
@NotNull PresentationFactory factory,
|
||||
int offset,
|
||||
@NotNull PsiElement element,
|
||||
@NotNull List<SmartPsiElementPointer<PsiElement>> problems) {
|
||||
@NotNull Set<PsiElement> brokenUsages) {
|
||||
int line = document.getLineNumber(offset);
|
||||
int column = offset - document.getLineStartOffset(line);
|
||||
int columnWidth = EditorUtil.getPlainSpaceWidth(editor);
|
||||
SpacePresentation spacePresentation = new SpacePresentation(column * columnWidth, 0);
|
||||
InlayPresentation textPresentation = factory.smallText(JavaErrorBundle.message("project.problems.broken.usages", problems.size()));
|
||||
InlayPresentation textPresentation = factory.smallText(JavaErrorBundle.message("project.problems.broken.usages", brokenUsages.size()));
|
||||
InlayPresentation presentation = factory.seq(spacePresentation, textPresentation);
|
||||
return factory.referenceOnHover(presentation, (e, p) -> {
|
||||
List<PsiElement> elements = ContainerUtil.mapNotNull(problems, pointer -> pointer.getElement());
|
||||
if (elements.size() == 1) {
|
||||
PsiElement problem = elements.get(0);
|
||||
if (problem instanceof Navigatable) ((Navigatable)problem).navigate(true);
|
||||
if (brokenUsages.size() == 1) {
|
||||
PsiElement usage = brokenUsages.iterator().next();
|
||||
if (usage instanceof Navigatable) ((Navigatable)usage).navigate(true);
|
||||
}
|
||||
else {
|
||||
FindUtil.showInUsageView(element, elements.toArray(PsiElement.EMPTY_ARRAY),
|
||||
FindUtil.showInUsageView(element, brokenUsages.toArray(PsiElement.EMPTY_ARRAY),
|
||||
JavaErrorBundle.message("project.problems.title"), project);
|
||||
}
|
||||
});
|
||||
@@ -74,79 +75,26 @@ public class ProjectProblemPassUtils {
|
||||
.getTextRange().getStartOffset();
|
||||
}
|
||||
|
||||
static void removeOldInlays(@NotNull PsiFile psiFile) {
|
||||
Map<SmartPsiElementPointer<PsiMember>, ReportedChange> changes = getReportedChanges(psiFile);
|
||||
changes.values().forEach(c -> {
|
||||
Inlay<?> inlay = c.myInlay;
|
||||
if (inlay != null) Disposer.dispose(inlay);
|
||||
public static @NotNull Map<PsiMember, Inlay<?>> getInlays(@NotNull PsiFile psiFile) {
|
||||
Map<SmartPsiElementPointer<PsiMember>, Inlay<?>> oldInlays = psiFile.getUserData(PROBLEM_INLAY_HINTS);
|
||||
Map<PsiMember, Inlay<?>> inlays = new SmartHashMap<>();
|
||||
if (oldInlays == null) return inlays;
|
||||
oldInlays.forEach((pointer, inlay) -> {
|
||||
PsiMember member = pointer.getElement();
|
||||
if (member == null) Disposer.dispose(inlay);
|
||||
else inlays.put(member, inlay);
|
||||
});
|
||||
psiFile.putUserData(FILE_OPENED_KEY, true);
|
||||
return inlays;
|
||||
}
|
||||
|
||||
static @NotNull Map<PsiMember, ScopedMember> mergeWithOldChanges(@NotNull PsiFile psiFile,
|
||||
@NotNull Map<PsiMember, ScopedMember> newChanges) {
|
||||
Map<SmartPsiElementPointer<PsiMember>, ReportedChange> oldChanges = Boolean.TRUE.equals(psiFile.getUserData(FILE_OPENED_KEY)) ?
|
||||
getReportedChanges(psiFile) : Collections.emptyMap();
|
||||
if (oldChanges.isEmpty()) return newChanges;
|
||||
Map<PsiMember, ScopedMember> changes = new HashMap<>(newChanges);
|
||||
oldChanges.forEach((memberPointer, reportedChange) -> {
|
||||
PsiMember psiMember = memberPointer.getElement();
|
||||
if (psiMember != null) changes.putIfAbsent(psiMember, reportedChange.getPrevMember());
|
||||
});
|
||||
return changes;
|
||||
static void updateInlays(@NotNull PsiFile psiFile, @NotNull Map<PsiMember, Inlay<?>> inlays) {
|
||||
Map<SmartPsiElementPointer<PsiMember>, Inlay<?>> newInlays =
|
||||
ContainerUtil.map2Map(inlays.entrySet(), e -> Pair.create(SmartPointerManager.createPointer(e.getKey()), e.getValue()));
|
||||
psiFile.putUserData(PROBLEM_INLAY_HINTS, newInlays);
|
||||
}
|
||||
|
||||
static void reportChanges(@NotNull PsiFile psiFile, @NotNull Map<SmartPsiElementPointer<PsiMember>, ReportedChange> newChanges) {
|
||||
Map<SmartPsiElementPointer<PsiMember>, ReportedChange> reportedChanges = new HashMap<>(newChanges);
|
||||
Map<SmartPsiElementPointer<PsiMember>, ReportedChange> oldChanges = getReportedChanges(psiFile);
|
||||
oldChanges.forEach((memberPointer, oldChange) -> {
|
||||
PsiMember member = memberPointer.getElement();
|
||||
if (member == null) {
|
||||
Inlay<?> inlay = oldChange.myInlay;
|
||||
if (inlay != null) Disposer.dispose(inlay);
|
||||
return;
|
||||
}
|
||||
ReportedChange newChange = reportedChanges.putIfAbsent(memberPointer, oldChange);
|
||||
// we have new change for this element
|
||||
if (newChange != null && newChange != oldChange) {
|
||||
Inlay<?> oldInlay = oldChange.myInlay;
|
||||
if (oldInlay != null) Disposer.dispose(oldInlay);
|
||||
if (newChange.myInlay == null) reportedChanges.remove(memberPointer);
|
||||
}
|
||||
});
|
||||
psiFile.putUserData(REPORTED_CHANGES, reportedChanges);
|
||||
psiFile.putUserData(FILE_OPENED_KEY, false);
|
||||
}
|
||||
|
||||
public static @NotNull Map<SmartPsiElementPointer<PsiMember>, ReportedChange> getReportedChanges(@NotNull PsiFile psiFile) {
|
||||
Map<SmartPsiElementPointer<PsiMember>, ReportedChange> changes = psiFile.getUserData(REPORTED_CHANGES);
|
||||
return changes == null ? Collections.emptyMap() : changes;
|
||||
}
|
||||
|
||||
public static class ReportedChange {
|
||||
|
||||
private final ScopedMember myPrevMember;
|
||||
private final Inlay<?> myInlay;
|
||||
|
||||
ReportedChange(@Nullable ScopedMember prevMember, @Nullable Inlay<?> inlay) {
|
||||
myPrevMember = prevMember;
|
||||
myInlay = inlay;
|
||||
}
|
||||
|
||||
public Inlay<?> getInlay() {
|
||||
return myInlay;
|
||||
}
|
||||
|
||||
public ScopedMember getPrevMember() {
|
||||
return myPrevMember;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ReportedChange{" +
|
||||
"myPrevMember=" + myPrevMember +
|
||||
", myInlay=" + myInlay +
|
||||
'}';
|
||||
}
|
||||
static void removeInlays(@NotNull PsiFile psiFile) {
|
||||
getInlays(psiFile).values().forEach(inlay -> Disposer.dispose(inlay));
|
||||
psiFile.putUserData(PROBLEM_INLAY_HINTS, null);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -183,11 +183,12 @@ internal class FieldProblemsTest : ProjectProblemsViewTest() {
|
||||
val factory = JavaPsiFacade.getElementFactory(project)
|
||||
WriteCommandAction.runWriteCommandAction(project) {
|
||||
val psiField = factory.createFieldFromText("static final String field = \"foo\";", null)
|
||||
targetClass.add(psiField) as PsiField
|
||||
targetClass.add(psiField)
|
||||
}
|
||||
myFixture.checkHighlighting()
|
||||
|
||||
assertFalse(hasReportedProblems<PsiStatement>(targetClass, outsideRefClass))
|
||||
myFixture.checkHighlighting()
|
||||
assertTrue(hasReportedProblems<PsiAssignmentExpression>(targetClass, packageRefClass))
|
||||
|
||||
changeField(targetClass) { field -> field.modifierList?.setModifierProperty(PsiModifier.PUBLIC, true) }
|
||||
|
||||
+3
-4
@@ -2,12 +2,12 @@
|
||||
package com.intellij.java.codeInsight.daemon.problems
|
||||
|
||||
import com.intellij.codeInsight.daemon.problems.pass.ProjectProblemPassUtils
|
||||
import com.intellij.codeInsight.daemon.problems.pass.ProjectProblemPassUtils.ReportedChange
|
||||
import com.intellij.codeInsight.hints.BlockInlayRenderer
|
||||
import com.intellij.codeInsight.hints.presentation.DynamicDelegatePresentation
|
||||
import com.intellij.codeInsight.hints.presentation.OnClickPresentation
|
||||
import com.intellij.codeInsight.hints.presentation.OnHoverPresentation
|
||||
import com.intellij.codeInsight.hints.presentation.RecursivelyUpdatingRootPresentation
|
||||
import com.intellij.openapi.editor.Inlay
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.psi.*
|
||||
@@ -30,7 +30,7 @@ internal abstract class ProjectProblemsViewTest : LightJavaCodeInsightFixtureTes
|
||||
}
|
||||
|
||||
protected fun getProblems(psiFile: PsiFile): List<PsiElement> {
|
||||
val reportedChanges: Map<SmartPsiElementPointer<PsiMember>, ReportedChange> = ProjectProblemPassUtils.getReportedChanges(psiFile)
|
||||
val reportedChanges: MutableMap<PsiMember, Inlay<*>> = ProjectProblemPassUtils.getInlays(psiFile)
|
||||
val targetFile = psiFile.virtualFile
|
||||
val problems: MutableList<PsiElement> = mutableListOf()
|
||||
|
||||
@@ -38,8 +38,7 @@ internal abstract class ProjectProblemsViewTest : LightJavaCodeInsightFixtureTes
|
||||
val editorManager = FileEditorManager.getInstance(project)
|
||||
val clickEvent = MouseEvent(JPanel(), 0, 0, 0, 0, 0, 0, true, MouseEvent.BUTTON1)
|
||||
val point = Point(0, 0)
|
||||
for (reportedChange in reportedChanges.values) {
|
||||
val inlay = reportedChange.inlay ?: continue
|
||||
for (inlay in reportedChanges.values) {
|
||||
val renderer = inlay.renderer as BlockInlayRenderer
|
||||
val presentation = renderer.getConstrainedPresentations()[0]
|
||||
val rootPresentation = presentation.root as RecursivelyUpdatingRootPresentation
|
||||
|
||||
+6
-10
@@ -4,8 +4,7 @@ package com.intellij.java.propertyBased
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
||||
import com.intellij.codeInsight.daemon.problems.MemberCollector
|
||||
import com.intellij.codeInsight.daemon.problems.MemberUsageCollector
|
||||
import com.intellij.codeInsight.daemon.problems.pass.ProjectProblemPassUtils.ReportedChange
|
||||
import com.intellij.codeInsight.daemon.problems.pass.ProjectProblemPassUtils.getReportedChanges
|
||||
import com.intellij.codeInsight.daemon.problems.pass.ProjectProblemPassUtils
|
||||
import com.intellij.codeInsight.hints.BlockInlayRenderer
|
||||
import com.intellij.codeInsight.hints.presentation.DynamicDelegatePresentation
|
||||
import com.intellij.codeInsight.hints.presentation.OnClickPresentation
|
||||
@@ -95,14 +94,12 @@ class ProjectProblemsViewPropertyTest : BaseUnivocityTest() {
|
||||
for (changedFile in changedFiles) {
|
||||
val psiFile = psiManager.findFile(changedFile)!!
|
||||
rehighlight(psiFile)
|
||||
val reportedChanges = getReportedChanges(psiFile)
|
||||
for ((pointer, reportedChange) in reportedChanges) {
|
||||
val inlay = reportedChange.inlay
|
||||
val reportedChanges = ProjectProblemPassUtils.getInlays(psiFile)
|
||||
for ((member, inlay) in reportedChanges) {
|
||||
if (inlay != null) {
|
||||
TestCase.fail("Problems are still reported even after the fix. " +
|
||||
"File: ${changedFile.name}, " +
|
||||
"Member: ${JavaDocUtil.getReferenceText(myProject, pointer.element)}, " +
|
||||
"Previous member: ${reportedChange.prevMember}, " +
|
||||
"Member: ${JavaDocUtil.getReferenceText(myProject, member)}, " +
|
||||
"Reported problems: ${extractProblems(changedFile, inlay)}")
|
||||
}
|
||||
}
|
||||
@@ -281,11 +278,10 @@ class ProjectProblemsViewPropertyTest : BaseUnivocityTest() {
|
||||
}
|
||||
|
||||
private fun getFilesReportedByProblemSearch(psiFile: PsiFile): Set<VirtualFile> {
|
||||
val reportedChanges: Map<SmartPsiElementPointer<PsiMember>, ReportedChange> = getReportedChanges(psiFile)
|
||||
val reportedChanges: Map<PsiMember, Inlay<*>> = ProjectProblemPassUtils.getInlays(psiFile)
|
||||
val virtualFile = psiFile.virtualFile
|
||||
val filesWithProblems = mutableSetOf<VirtualFile>()
|
||||
for (change in reportedChanges.values) {
|
||||
val inlay = change.inlay ?: continue
|
||||
for (inlay in reportedChanges.values) {
|
||||
clickOnInlay(inlay)
|
||||
val openedFile = FileEditorManager.getInstance(myProject).selectedEditor!!.file!!
|
||||
if (openedFile != virtualFile) {
|
||||
|
||||
Reference in New Issue
Block a user