Skip to content

Commit 82daefc

Browse files
authored
Merge pull request #62 from skydoves/feature/plugin-project-scope
Change configuration scopes from to application to project
2 parents 355e4e1 + f2fd4b6 commit 82daefc

File tree

9 files changed

+162
-22
lines changed

9 files changed

+162
-22
lines changed

compose-stability-analyzer-idea/api/compose-stability-analyzer-idea.api

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,24 @@ public final class com/skydoves/compose/stability/idea/quickfix/AddTraceRecompos
7979
public fun isAvailable (Lcom/intellij/openapi/project/Project;Lcom/intellij/openapi/editor/Editor;Lcom/intellij/psi/PsiElement;)Z
8080
}
8181

82-
public final class com/skydoves/compose/stability/idea/settings/StabilitySettingsConfigurable : com/intellij/openapi/options/BoundConfigurable {
82+
public final class com/skydoves/compose/stability/idea/settings/StabilityProjectSettingsState : com/intellij/openapi/components/PersistentStateComponent {
83+
public static final field Companion Lcom/skydoves/compose/stability/idea/settings/StabilityProjectSettingsState$Companion;
8384
public fun <init> ()V
85+
public final fun getCustomStableTypesAsRegex ()Ljava/util/List;
86+
public final fun getStabilityConfigurationPath ()Ljava/lang/String;
87+
public fun getState ()Lcom/skydoves/compose/stability/idea/settings/StabilityProjectSettingsState;
88+
public synthetic fun getState ()Ljava/lang/Object;
89+
public fun loadState (Lcom/skydoves/compose/stability/idea/settings/StabilityProjectSettingsState;)V
90+
public synthetic fun loadState (Ljava/lang/Object;)V
91+
public final fun setStabilityConfigurationPath (Ljava/lang/String;)V
92+
}
93+
94+
public final class com/skydoves/compose/stability/idea/settings/StabilityProjectSettingsState$Companion {
95+
public final fun getInstance (Lcom/intellij/openapi/project/Project;)Lcom/skydoves/compose/stability/idea/settings/StabilityProjectSettingsState;
96+
}
97+
98+
public final class com/skydoves/compose/stability/idea/settings/StabilitySettingsConfigurable : com/intellij/openapi/options/BoundConfigurable {
99+
public fun <init> (Lcom/intellij/openapi/project/Project;)V
84100
public fun apply ()V
85101
public fun createPanel ()Lcom/intellij/openapi/ui/DialogPanel;
86102
public fun isModified ()Z

compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/StabilityAnalyzer.kt

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717

1818
package com.skydoves.compose.stability.idea
1919

20+
import com.intellij.openapi.project.Project
2021
import com.skydoves.compose.stability.idea.k2.StabilityAnalyzerK2
22+
import com.skydoves.compose.stability.idea.settings.StabilityProjectSettingsState
2123
import com.skydoves.compose.stability.idea.settings.StabilitySettingsState
2224
import com.skydoves.compose.stability.runtime.ComposableStabilityInfo
2325
import com.skydoves.compose.stability.runtime.ParameterStability
@@ -72,9 +74,19 @@ internal object StabilityAnalyzer {
7274

7375
/**
7476
* Get custom stable type patterns from configuration file.
77+
* Uses project-level settings first, falls back to global settings.
7578
*/
76-
private val customStablePatterns: List<Regex>
77-
get() = settings.getCustomStableTypesAsRegex()
79+
private fun getCustomStablePatterns(project: Project?): List<Regex> {
80+
if (project != null) {
81+
val projectPatterns = StabilityProjectSettingsState.getInstance(
82+
project,
83+
).getCustomStableTypesAsRegex()
84+
if (projectPatterns.isNotEmpty()) {
85+
return projectPatterns
86+
}
87+
}
88+
return settings.getCustomStableTypesAsRegex()
89+
}
7890

7991
/**
8092
* Check if a fully qualified type name should be ignored based on user settings.
@@ -88,9 +100,9 @@ internal object StabilityAnalyzer {
88100
/**
89101
* Check if a fully qualified type name should be considered stable based on custom configuration.
90102
*/
91-
private fun isCustomStableType(fqName: String?): Boolean {
103+
private fun isCustomStableType(fqName: String?, project: Project? = null): Boolean {
92104
if (fqName == null) return false
93-
return customStablePatterns.any { pattern -> pattern.matches(fqName) }
105+
return getCustomStablePatterns(project).any { pattern -> pattern.matches(fqName) }
94106
}
95107

96108
/**
@@ -303,7 +315,7 @@ internal object StabilityAnalyzer {
303315
ParameterStability.STABLE,
304316
"Ignored by user settings: $fqName",
305317
)
306-
} else if (isCustomStableType(fqName)) {
318+
} else if (isCustomStableType(fqName, param.project)) {
307319
StabilityResult(
308320
ParameterStability.STABLE,
309321
"Custom stable type from configuration: $fqName",
@@ -414,7 +426,7 @@ internal object StabilityAnalyzer {
414426
}
415427

416428
// Check if type is custom stable
417-
if (isCustomStableType(fqName)) {
429+
if (isCustomStableType(fqName, typeRef.project)) {
418430
return StabilityResult(
419431
ParameterStability.STABLE,
420432
"Custom stable type from configuration: $fqName",

compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/k2/KtStabilityInferencer.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
*/
1616
package com.skydoves.compose.stability.idea.k2
1717

18+
import com.intellij.openapi.project.Project
1819
import com.skydoves.compose.stability.idea.StabilityAnalysisConstants
1920
import com.skydoves.compose.stability.idea.StabilityConstants
21+
import com.skydoves.compose.stability.idea.settings.StabilityProjectSettingsState
2022
import com.skydoves.compose.stability.idea.settings.StabilitySettingsState
2123
import org.jetbrains.kotlin.analysis.api.KaSession
2224
import org.jetbrains.kotlin.analysis.api.symbols.KaClassKind
@@ -55,7 +57,7 @@ import org.jetbrains.kotlin.analysis.api.types.KaTypeNullability
5557
* 20. Regular classes - property analysis (returns STABLE/UNSTABLE if definitive)
5658
* 21. @StabilityInferred (RUNTIME - only for uncertain cases)
5759
*/
58-
internal class KtStabilityInferencer {
60+
internal class KtStabilityInferencer(private val project: Project? = null) {
5961

6062
private val settings: StabilitySettingsState
6163
get() = StabilitySettingsState.getInstance()
@@ -690,11 +692,19 @@ internal class KtStabilityInferencer {
690692

691693
/**
692694
* Check if type is custom stable based on user configuration.
695+
* Uses project-level settings first, falls back to global settings.
693696
*/
694697
private fun isCustomStableType(fqName: String?): Boolean {
695698
if (fqName == null) return false
696699

697-
val customPatterns = settings.getCustomStableTypesAsRegex()
700+
val customPatterns = if (project != null) {
701+
val projectPatterns = StabilityProjectSettingsState.getInstance(
702+
project,
703+
).getCustomStableTypesAsRegex()
704+
if (projectPatterns.isNotEmpty()) projectPatterns else settings.getCustomStableTypesAsRegex()
705+
} else {
706+
settings.getCustomStableTypesAsRegex()
707+
}
698708
return customPatterns.any { pattern -> pattern.matches(fqName) }
699709
}
700710

compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/k2/StabilityAnalyzerK2.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ internal object StabilityAnalyzerK2 {
7272
private fun analyzeWithK2Session(function: KtNamedFunction): ComposableStabilityInfo {
7373
// Get function symbol
7474
val functionSymbol = function.symbol
75-
val inferencer = KtStabilityInferencer()
75+
val inferencer = KtStabilityInferencer(function.project)
7676

7777
// Analyze value parameters
7878
val parameters = functionSymbol.valueParameters.map { param ->
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Designed and developed by 2025 skydoves (Jaewoong Eum)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.skydoves.compose.stability.idea.settings
17+
18+
import com.intellij.openapi.components.PersistentStateComponent
19+
import com.intellij.openapi.components.Service
20+
import com.intellij.openapi.components.State
21+
import com.intellij.openapi.components.Storage
22+
import com.intellij.openapi.components.StoragePathMacros
23+
import com.intellij.openapi.project.Project
24+
import com.intellij.util.xmlb.XmlSerializerUtil
25+
26+
/**
27+
* Project-level settings for Compose Stability Analyzer plugin.
28+
* These settings are stored per-project in .idea/composeStabilityProject.xml
29+
*/
30+
@Service(Service.Level.PROJECT)
31+
@State(
32+
name = "ComposeStabilityProjectSettings",
33+
storages = [Storage(StoragePathMacros.WORKSPACE_FILE)],
34+
)
35+
public class StabilityProjectSettingsState :
36+
PersistentStateComponent<StabilityProjectSettingsState> {
37+
38+
/**
39+
* Path to external stability configuration file for this project.
40+
* This file can define custom stable types and ignored patterns.
41+
*/
42+
public var stabilityConfigurationPath: String = ""
43+
44+
public override fun getState(): StabilityProjectSettingsState = this
45+
46+
public override fun loadState(state: StabilityProjectSettingsState) {
47+
XmlSerializerUtil.copyBean(state, this)
48+
}
49+
50+
/**
51+
* Get custom stable type patterns from configuration file.
52+
*/
53+
public fun getCustomStableTypesAsRegex(): List<Regex> {
54+
if (stabilityConfigurationPath.isEmpty()) {
55+
return emptyList()
56+
}
57+
58+
return try {
59+
val file = java.io.File(stabilityConfigurationPath)
60+
if (!file.exists() || !file.isFile) {
61+
return emptyList()
62+
}
63+
64+
val patterns = mutableListOf<String>()
65+
file.readLines().forEach { line ->
66+
val trimmed = line.trim()
67+
if (trimmed.isNotEmpty() && !trimmed.startsWith("#")) {
68+
patterns.add(trimmed)
69+
}
70+
}
71+
72+
patterns.mapNotNull { pattern ->
73+
try {
74+
pattern
75+
.replace(".", "\\.")
76+
.replace("*", ".*")
77+
.toRegex()
78+
} catch (e: Exception) {
79+
null
80+
}
81+
}
82+
} catch (e: Exception) {
83+
emptyList()
84+
}
85+
}
86+
87+
public companion object {
88+
public fun getInstance(project: Project): StabilityProjectSettingsState {
89+
return project.getService(StabilityProjectSettingsState::class.java)
90+
}
91+
}
92+
}

compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/settings/StabilitySettingsConfigurable.kt

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ package com.skydoves.compose.stability.idea.settings
1818
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
1919
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
2020
import com.intellij.openapi.options.BoundConfigurable
21-
import com.intellij.openapi.project.ProjectManager
21+
import com.intellij.openapi.project.Project
2222
import com.intellij.openapi.ui.DialogPanel
2323
import com.intellij.ui.ColorPanel
2424
import com.intellij.ui.dsl.builder.AlignX
@@ -32,9 +32,13 @@ import java.awt.Color
3232
* Settings configurable for Compose Stability Analyzer plugin.
3333
* Appears in Settings → Tools → Compose Stability Analyzer
3434
*/
35-
public class StabilitySettingsConfigurable : BoundConfigurable("Compose Stability Analyzer") {
35+
public class StabilitySettingsConfigurable(
36+
private val project: Project,
37+
) : BoundConfigurable("Compose Stability Analyzer") {
3638

3739
private val settings: StabilitySettingsState = StabilitySettingsState.getInstance()
40+
private val projectSettings: StabilityProjectSettingsState =
41+
StabilityProjectSettingsState.getInstance(project)
3842

3943
// Gutter color panels
4044
private lateinit var stableGutterColorPanel: ColorPanel
@@ -149,23 +153,24 @@ public class StabilitySettingsConfigurable : BoundConfigurable("Compose Stabilit
149153
}
150154
}
151155

152-
group("External Configuration") {
156+
group("Project Configuration") {
153157
row {
154-
label("Stability configuration file:")
158+
label("Stability configuration file (per-project):")
155159
}
156160

157161
row {
158162
textFieldWithBrowseButton(
159163
fileChooserDescriptor = FileChooserDescriptorFactory.createSingleFileDescriptor(),
160164
fileChosen = { file -> file.path },
161165
)
162-
.bindText(settings::stabilityConfigurationPath)
166+
.bindText(projectSettings::stabilityConfigurationPath)
163167
.align(AlignX.FILL)
164168
.comment(
165169
"""
166-
Path to a file containing custom stable type patterns.
170+
Path to a file containing custom stable type patterns for this project.
167171
Each line should be a package/type pattern (supports wildcards).
168172
Types matching these patterns will be treated as stable.
173+
This setting is stored per-project.
169174
170175
Example file content:
171176
# Custom stable types
@@ -266,9 +271,7 @@ public class StabilitySettingsConfigurable : BoundConfigurable("Compose Stabilit
266271
settings.runtimeHintColorRGB = color.rgb
267272
}
268273

269-
// Restart code analysis in all open projects to apply new settings
270-
ProjectManager.getInstance().openProjects.forEach { project ->
271-
DaemonCodeAnalyzer.getInstance(project).restart()
272-
}
274+
// Restart code analysis in the current project to apply new settings
275+
DaemonCodeAnalyzer.getInstance(project).restart()
273276
}
274277
}

compose-stability-analyzer-idea/src/main/resources/META-INF/plugin.xml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,15 @@
3939
<applicationService
4040
serviceImplementation="com.skydoves.compose.stability.idea.settings.StabilitySettingsState"/>
4141

42-
<applicationConfigurable
42+
<projectService
43+
serviceImplementation="com.skydoves.compose.stability.idea.settings.StabilityProjectSettingsState"/>
44+
45+
<projectConfigurable
4346
parentId="tools"
4447
instance="com.skydoves.compose.stability.idea.settings.StabilitySettingsConfigurable"
4548
id="com.skydoves.compose.stability.idea.settings.StabilitySettingsConfigurable"
46-
displayName="Compose Stability Analyzer"/>
49+
displayName="Compose Stability Analyzer"
50+
nonDefaultProject="true"/>
4751

4852
<!-- Documentation provider for hover tooltips -->
4953
<lang.documentationProvider

stability-compiler/api/stability-compiler.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ public final class com/skydoves/compose/stability/compiler/StabilityAnalyzerIrGe
114114

115115
public final class com/skydoves/compose/stability/compiler/StabilityAnalyzerPluginRegistrar : org/jetbrains/kotlin/compiler/plugin/CompilerPluginRegistrar {
116116
public fun <init> ()V
117+
public final fun getPluginId ()Ljava/lang/String;
117118
public fun getSupportsK2 ()Z
118119
public fun registerExtensions (Lorg/jetbrains/kotlin/compiler/plugin/CompilerPluginRegistrar$ExtensionStorage;Lorg/jetbrains/kotlin/config/CompilerConfiguration;)V
119120
}

stability-compiler/src/main/kotlin/com/skydoves/compose/stability/compiler/StabilityAnalyzerPluginRegistrar.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public class StabilityAnalyzerPluginRegistrar : CompilerPluginRegistrar() {
2626

2727
override val supportsK2: Boolean = true
2828

29+
public val pluginId: String = StabilityAnalyzerCommandLineProcessor.PLUGIN_ID
30+
2931
override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {
3032
val enabled = configuration.get(StabilityAnalyzerConfigurationKeys.KEY_ENABLED, true)
3133

0 commit comments

Comments
 (0)