[AA] KT-58257 Improve modification event API and documentation
- Module state modification events now have a modification kind, which
allows adding additional kinds of modification in the future (e.g.
separating module property and content root updates, if the workspace
model ever supports it).
- Splitting off modification kinds was also a good opportunity to
better document when module state modification occurs.
- Changed the documentation of modification events to be (1) less
reliant on the IDE implementation and (2) more detailed in the
intended effect of each event.
- Removed the notion of "stable" modules again and replaced it with
"libraries". Even though an SDK is technically not a library, the
term "library modules" should be more friendly to API consumers.
This commit is contained in:
committed by
Space Team
parent
d2b05b8f4f
commit
03c8654fe3
+6
-2
@@ -9,9 +9,13 @@ import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
||||
|
||||
public fun interface KotlinGlobalModuleStateModificationListener {
|
||||
/**
|
||||
* [onModification] is invoked in a write action before or after global modification of the module state of all [KtModule]s.
|
||||
* [onModification] is invoked in a write action before or after global module state modification.
|
||||
*
|
||||
* This event is published after SDK removal and to invalidate caches during/between tests.
|
||||
* The module structure, source code, and binary content of all [KtModule]s in the project should be considered modified when this event
|
||||
* is received. This includes source files being moved or removed, binary content being added, removed, or changed, and modules possibly
|
||||
* being removed. Thus, all caches related to module structure, source code, and binaries should be invalidated.
|
||||
*
|
||||
* @see KotlinTopics
|
||||
*/
|
||||
public fun onModification()
|
||||
}
|
||||
|
||||
+9
-3
@@ -9,10 +9,16 @@ import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
||||
|
||||
public fun interface KotlinGlobalSourceModuleStateModificationListener {
|
||||
/**
|
||||
* [onModification] is invoked in a write action before or after global modification of the module state of all source [KtModule]s,
|
||||
* excluding [stable][org.jetbrains.kotlin.analysis.project.structure.isStableModule] modules.
|
||||
* [onModification] is invoked in a write action before or after global source module state modification.
|
||||
*
|
||||
* This event is published to invalidate caches during/between tests.
|
||||
* The module structure and source code of all source [KtModule]s in the project should be considered modified when this event is
|
||||
* received. This includes source files being moved or removed, and source modules possibly being removed. Thus, all caches related to
|
||||
* source module structure and source code should be invalidated.
|
||||
*
|
||||
* Library modules (including library sources) do not need to be considered modified, so any caches related to library modules and their
|
||||
* contents may be kept.
|
||||
*
|
||||
* @see KotlinTopics
|
||||
*/
|
||||
public fun onModification()
|
||||
}
|
||||
|
||||
+8
-3
@@ -9,10 +9,15 @@ import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
||||
|
||||
public fun interface KotlinGlobalSourceOutOfBlockModificationListener {
|
||||
/**
|
||||
* [onModification] is invoked in a write action before or after global out-of-block modification of all source [KtModule]s, excluding
|
||||
* [stable][org.jetbrains.kotlin.analysis.project.structure.isStableModule] modules.
|
||||
* [onModification] is invoked in a write action before or after global out-of-block modification of all sources.
|
||||
*
|
||||
* This event is published on global PSI changes.
|
||||
* The source code of all source [KtModule]s in the project should be considered modified when this event is received. This includes
|
||||
* source files being moved or removed. Thus, all caches related to source code and source files should be invalidated.
|
||||
*
|
||||
* Library modules (including library sources) do not need to be considered modified, so any caches related to library modules and their
|
||||
* contents may be kept.
|
||||
*
|
||||
* @see KotlinTopics
|
||||
*/
|
||||
public fun onModification()
|
||||
}
|
||||
|
||||
+2
@@ -17,6 +17,8 @@ public fun interface KotlinModuleOutOfBlockModificationListener {
|
||||
*
|
||||
* This event may be published for any and all source code changes, not just out-of-block modifications, to simplify the implementation
|
||||
* of modification detection.
|
||||
*
|
||||
* @see KotlinTopics
|
||||
*/
|
||||
public fun onModification(module: KtModule)
|
||||
}
|
||||
|
||||
+24
-3
@@ -9,8 +9,29 @@ import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
||||
|
||||
public fun interface KotlinModuleStateModificationListener {
|
||||
/**
|
||||
* [onModification] is invoked in a write action *before* the [module] is moved, removed, or its structure is changed. [isRemoval]
|
||||
* signals if the event is a removal.
|
||||
* [onModification] is invoked in a write action *before* the [module] is updated or removed (see [modificationKind] for specifics).
|
||||
*
|
||||
* @see KotlinTopics
|
||||
*/
|
||||
public fun onModification(module: KtModule, isRemoval: Boolean)
|
||||
public fun onModification(module: KtModule, modificationKind: KotlinModuleStateModificationKind)
|
||||
}
|
||||
|
||||
public enum class KotlinModuleStateModificationKind {
|
||||
/**
|
||||
* The [KtModule]'s properties or references to other modules are being changed.
|
||||
*
|
||||
* #### Examples
|
||||
*
|
||||
* - The name of the module is being changed.
|
||||
* - The module's content roots are being changed, such as adding another source folder to a source module.
|
||||
* - If module A depends on module B and module B is being removed, in addition to the removal event for module B, module A also
|
||||
* receives an update event.
|
||||
*/
|
||||
UPDATE,
|
||||
|
||||
/**
|
||||
* The [KtModule] is being removed. Because this event is published before the removal, the [KtModule] can still be accessed to clear
|
||||
* caches. It should be removed from any caches managed by the subscriber to avoid stale or broken keys/values.
|
||||
*/
|
||||
REMOVAL,
|
||||
}
|
||||
|
||||
+14
-5
@@ -20,15 +20,24 @@ import org.jetbrains.kotlin.analysis.providers.analysisMessageBus
|
||||
* - [KotlinGlobalSourceModuleStateModificationListener]
|
||||
* - [KotlinGlobalSourceOutOfBlockModificationListener]
|
||||
*
|
||||
* Modification events may be published before or after a modification, so subscribers should not assume that the modification has or hasn't
|
||||
* happened yet. The reason for this design decision is that the underlying events (such as PSI tree changes or workspace model events) are
|
||||
* sometimes published before and sometimes after a change, or even both. Modification events published before the modification should
|
||||
* however be published close to the modification.
|
||||
*
|
||||
* Care needs to be taken with the lack of interplay between different types of topics: Publishing a global modification event, for example,
|
||||
* does not imply the corresponding module-level event. Similarly, publishing a module state modification event does not imply out-of-block
|
||||
* modification.
|
||||
*
|
||||
* Global modification events are published when it's not feasible or desired to publish events for a single module, or a limited set of
|
||||
* modules. For example, a change in the environment such as removing an SDK might affect all modules, so a global event is more
|
||||
* appropriate.
|
||||
*
|
||||
* #### Timing Guarantees
|
||||
*
|
||||
* Most modification events may be published before or after a modification, so subscribers should not assume that the modification has or
|
||||
* hasn't happened yet. The reason for this design decision is that some of the underlying events (such as PSI tree changes) may be
|
||||
* published before and after a change, or even both. Modification events published before the modification should however be published
|
||||
* close to the modification.
|
||||
*
|
||||
* Only [module state modification events][KotlinModuleStateModificationListener] guarantee that the event is published before the module is
|
||||
* affected. This allows subscribers to access the module's properties and dependencies to invalidate or update caches.
|
||||
*
|
||||
* #### Implementation Notes
|
||||
*
|
||||
* Analysis API implementations need to take care of publishing to these topics via the [analysisMessageBus]. In general, if your tool works
|
||||
|
||||
+6
-5
@@ -91,19 +91,20 @@ class LLFirSessionCache(private val project: Project) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all sessions after global invalidation. If [includeStableModules] is `false`, sessions of stable modules will not be removed.
|
||||
* Removes all sessions after global invalidation. If [includeLibraryModules] is `false`, sessions of library modules will not be
|
||||
* removed.
|
||||
*
|
||||
* [removeAllSessions] must be called in a write action.
|
||||
*/
|
||||
fun removeAllSessions(includeStableModules: Boolean) {
|
||||
fun removeAllSessions(includeLibraryModules: Boolean) {
|
||||
ApplicationManager.getApplication().assertWriteAccessAllowed()
|
||||
|
||||
if (includeStableModules) {
|
||||
if (includeLibraryModules) {
|
||||
removeAllSessionsFrom(sourceCache)
|
||||
removeAllSessionsFrom(binaryCache)
|
||||
} else {
|
||||
// `binaryCache` can only contain binary and thus stable modules, so we only need to remove sessions from `sourceCache`.
|
||||
removeAllMatchingSessionsFrom(sourceCache) { !it.isStableModule }
|
||||
// `binaryCache` can only contain library modules, so we only need to remove sessions from `sourceCache`.
|
||||
removeAllMatchingSessionsFrom(sourceCache) { it !is KtBinaryModule && it !is KtLibrarySourceModule }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-9
@@ -41,15 +41,15 @@ class LLFirSessionInvalidationService(private val project: Project) : Disposable
|
||||
)
|
||||
busConnection.subscribe(
|
||||
KotlinTopics.GLOBAL_MODULE_STATE_MODIFICATION,
|
||||
KotlinGlobalModuleStateModificationListener { invalidateAll(includeStableModules = true) }
|
||||
KotlinGlobalModuleStateModificationListener { invalidateAll(includeLibraryModules = true) }
|
||||
)
|
||||
busConnection.subscribe(
|
||||
KotlinTopics.GLOBAL_SOURCE_MODULE_STATE_MODIFICATION,
|
||||
KotlinGlobalSourceModuleStateModificationListener { invalidateAll(includeStableModules = false) },
|
||||
KotlinGlobalSourceModuleStateModificationListener { invalidateAll(includeLibraryModules = false) },
|
||||
)
|
||||
busConnection.subscribe(
|
||||
KotlinTopics.GLOBAL_SOURCE_OUT_OF_BLOCK_MODIFICATION,
|
||||
KotlinGlobalSourceOutOfBlockModificationListener { invalidateAll(includeStableModules = false) },
|
||||
KotlinGlobalSourceOutOfBlockModificationListener { invalidateAll(includeLibraryModules = false) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -81,21 +81,21 @@ class LLFirSessionInvalidationService(private val project: Project) : Disposable
|
||||
}
|
||||
}
|
||||
|
||||
private fun invalidateAll(includeStableModules: Boolean) {
|
||||
private fun invalidateAll(includeLibraryModules: Boolean) {
|
||||
ApplicationManager.getApplication().assertWriteAccessAllowed()
|
||||
|
||||
// When anchor modules are configured and `includeStableModules` is `false`, we get a situation where the anchor module session will
|
||||
// be invalidated (because it is a source session), while its library dependents won't be invalidated (because they are sessions of
|
||||
// stable modules). But such library sessions also need to be invalidated because they depend on the anchor module.
|
||||
// When anchor modules are configured and `includeLibraryModules` is `false`, we get a situation where the anchor module session
|
||||
// will be invalidated (because it is a source session), while its library dependents won't be invalidated. But such library
|
||||
// sessions also need to be invalidated because they depend on the anchor module.
|
||||
//
|
||||
// Invalidating anchor modules before all source sessions has the advantage that `invalidate`'s session existence check will work,
|
||||
// so we do not have to invalidate dependent sessions if the anchor module does not exist in the first place.
|
||||
if (!includeStableModules) {
|
||||
if (!includeLibraryModules) {
|
||||
val anchorModules = KotlinAnchorModuleProvider.getInstance(project)?.getAllAnchorModules()
|
||||
anchorModules?.forEach(::invalidate)
|
||||
}
|
||||
|
||||
LLFirSessionCache.getInstance(project).removeAllSessions(includeStableModules)
|
||||
LLFirSessionCache.getInstance(project).removeAllSessions(includeLibraryModules)
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
|
||||
-8
@@ -64,11 +64,3 @@ public inline fun <reified M : KtModule> KtModule.allDirectDependenciesOfType():
|
||||
*/
|
||||
public fun computeTransitiveDependsOnDependencies(directDependsOnDependencies: List<KtModule>): List<KtModule> =
|
||||
topologicalSort(directDependsOnDependencies) { this.directDependsOnDependencies }
|
||||
|
||||
/**
|
||||
* A stable [KtModule] does not contain conventionally *editable* sources and cannot cause or be affected by out-of-block modification.
|
||||
*
|
||||
* Stable modules may still be subject to modification (e.g. when a JAR is added to a library), but in such cases, module state modification
|
||||
* events will be raised.
|
||||
*/
|
||||
public val KtModule.isStableModule: Boolean get() = this is KtBinaryModule || this is KtLibrarySourceModule
|
||||
|
||||
Reference in New Issue
Block a user