Skip to content

Commit 071df9d

Browse files
committed
Added strict mode options to CLI and Gradle, and diagnostic declarations
1 parent 3bab585 commit 071df9d

File tree

12 files changed

+373
-10
lines changed

12 files changed

+373
-10
lines changed

compiler-plugin/compiler-plugin-cli/src/main/latest/kotlinx/rpc/codegen/RpcCompilerPlugin.kt

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package kotlinx.rpc.codegen
66

77
import kotlinx.rpc.codegen.extension.RpcIrExtension
88
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
9-
import org.jetbrains.kotlin.compiler.plugin.CliOption
9+
import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption
1010
import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
1111
import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
1212
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
@@ -15,9 +15,25 @@ import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter
1515

1616
@OptIn(ExperimentalCompilerApi::class)
1717
class RpcCommandLineProcessor : CommandLineProcessor {
18-
override val pluginId = "kotlinx.rpc.compiler-plugin"
19-
20-
override val pluginOptions = emptyList<CliOption>()
18+
override val pluginId = "kotlinx-rpc"
19+
20+
override val pluginOptions = listOf(
21+
StrictModeCliOptions.STATE_FLOW,
22+
StrictModeCliOptions.SHARED_FLOW,
23+
StrictModeCliOptions.NESTED_FLOW,
24+
StrictModeCliOptions.STREAM_SCOPED_FUNCTIONS,
25+
StrictModeCliOptions.SUSPENDING_SERVER_STREAMING,
26+
StrictModeCliOptions.NOT_TOP_LEVEL_SERVER_FLOW,
27+
StrictModeCliOptions.FIELDS,
28+
)
29+
30+
override fun processOption(
31+
option: AbstractCliOption,
32+
value: String,
33+
configuration: CompilerConfiguration,
34+
) {
35+
option.processAsStrictModeOption(value, configuration)
36+
}
2137
}
2238

2339
@OptIn(ExperimentalCompilerApi::class)

compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcCheckers.kt renamed to compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcAdditionalCheckers.kt

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package kotlinx.rpc.codegen
77
import kotlinx.rpc.codegen.checkers.FirCheckedAnnotationHelper
88
import kotlinx.rpc.codegen.checkers.FirRpcDeclarationCheckers
99
import kotlinx.rpc.codegen.checkers.FirRpcExpressionCheckers
10+
import kotlinx.rpc.codegen.checkers.diagnostics.FirRpcStrictModeDiagnostics
1011
import org.jetbrains.kotlin.fir.FirSession
1112
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers
1213
import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers
@@ -16,19 +17,29 @@ import org.jetbrains.kotlin.fir.caches.firCachesFactory
1617
import org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar
1718
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
1819

19-
class FirRpcCheckers(session: FirSession, serializationIsPresent: Boolean) : FirAdditionalCheckersExtension(session) {
20+
class FirRpcAdditionalCheckers(
21+
session: FirSession,
22+
serializationIsPresent: Boolean,
23+
modes: StrictModeAggregator,
24+
) : FirAdditionalCheckersExtension(session) {
2025
override fun FirDeclarationPredicateRegistrar.registerPredicates() {
2126
register(FirRpcPredicates.rpc)
2227
register(FirRpcPredicates.checkedAnnotationMeta)
2328
}
2429

25-
private val ctx = FirCheckersContext(session, serializationIsPresent)
30+
private val ctx = FirCheckersContext(session, serializationIsPresent, modes)
2631

2732
override val declarationCheckers: DeclarationCheckers = FirRpcDeclarationCheckers(ctx)
2833
override val expressionCheckers: ExpressionCheckers = FirRpcExpressionCheckers(ctx)
2934
}
3035

31-
class FirCheckersContext(private val session: FirSession, val serializationIsPresent: Boolean) {
36+
class FirCheckersContext(
37+
private val session: FirSession,
38+
val serializationIsPresent: Boolean,
39+
modes: StrictModeAggregator,
40+
) {
41+
val strictModeDiagnostics = FirRpcStrictModeDiagnostics(modes)
42+
3243
val typeParametersCache = session.firCachesFactory.createCache { typeParameter: FirTypeParameterSymbol ->
3344
FirCheckedAnnotationHelper.checkedAnnotations(session, typeParameter)
3445
}

compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcExtensionRegistrar.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.fir.extensions.FirSupertypeGenerationExtension.Facto
1616
class FirRpcExtensionRegistrar(private val configuration: CompilerConfiguration) : FirExtensionRegistrar() {
1717
override fun ExtensionRegistrarContext.configurePlugin() {
1818
val logger = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
19+
val modes = configuration.strictModeAggregator()
1920

2021
val serializationIsPresent = try {
2122
Class.forName("org.jetbrains.kotlinx.serialization.compiler.fir.SerializationFirResolveExtension")
@@ -29,7 +30,7 @@ class FirRpcExtensionRegistrar(private val configuration: CompilerConfiguration)
2930
false
3031
}
3132

32-
+CFactory { FirRpcCheckers(it, serializationIsPresent) }
33+
+CFactory { FirRpcAdditionalCheckers(it, serializationIsPresent, modes) }
3334

3435
+SFactory { FirRpcSupertypeGenerator(it, logger) }
3536
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.rpc.codegen
6+
7+
import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption
8+
import org.jetbrains.kotlin.compiler.plugin.CliOption
9+
import org.jetbrains.kotlin.config.CompilerConfiguration
10+
import org.jetbrains.kotlin.config.CompilerConfigurationKey
11+
12+
enum class StrictMode {
13+
NONE, WARNING, ERROR;
14+
}
15+
16+
data class StrictModeAggregator(
17+
val stateFlow: StrictMode,
18+
val sharedFlow: StrictMode,
19+
val nestedFlow: StrictMode,
20+
val streamScopedFunctions: StrictMode,
21+
val suspendingServerStreaming: StrictMode,
22+
val notTopLevelServerFlow: StrictMode,
23+
val fields: StrictMode,
24+
)
25+
26+
object StrictModeConfigurationKeys {
27+
val STATE_FLOW = CompilerConfigurationKey.create<StrictMode>("state flow rpc mode")
28+
val SHARED_FLOW = CompilerConfigurationKey.create<StrictMode>("shared flow rpc mode")
29+
val NESTED_FLOW = CompilerConfigurationKey.create<StrictMode>("nested flow rpc mode")
30+
val STREAM_SCOPED_FUNCTIONS = CompilerConfigurationKey.create<StrictMode>("stream scoped rpc mode")
31+
val SUSPENDING_SERVER_STREAMING = CompilerConfigurationKey.create<StrictMode>(
32+
"suspending server streaming rpc mode"
33+
)
34+
val NOT_TOP_LEVEL_SERVER_FLOW = CompilerConfigurationKey.create<StrictMode>("not top level server flow rpc mode")
35+
val FIELDS = CompilerConfigurationKey.create<StrictMode>("fields rpc mode")
36+
}
37+
38+
fun CompilerConfiguration.strictModeAggregator(): StrictModeAggregator {
39+
return StrictModeAggregator(
40+
stateFlow = get(StrictModeConfigurationKeys.STATE_FLOW, StrictMode.WARNING),
41+
sharedFlow = get(StrictModeConfigurationKeys.SHARED_FLOW, StrictMode.WARNING),
42+
nestedFlow = get(StrictModeConfigurationKeys.NESTED_FLOW, StrictMode.WARNING),
43+
streamScopedFunctions = get(StrictModeConfigurationKeys.STREAM_SCOPED_FUNCTIONS, StrictMode.WARNING),
44+
suspendingServerStreaming = get(StrictModeConfigurationKeys.SUSPENDING_SERVER_STREAMING, StrictMode.WARNING),
45+
notTopLevelServerFlow = get(StrictModeConfigurationKeys.NOT_TOP_LEVEL_SERVER_FLOW, StrictMode.WARNING),
46+
fields = get(StrictModeConfigurationKeys.FIELDS, StrictMode.WARNING),
47+
)
48+
}
49+
50+
object StrictModeCliOptions {
51+
val STATE_FLOW = CliOption(
52+
optionName = "strict-stateFlow",
53+
valueDescription = VALUE_DESCRIPTION,
54+
description = description("StateFlow"),
55+
required = false,
56+
allowMultipleOccurrences = false,
57+
)
58+
59+
val SHARED_FLOW = CliOption(
60+
optionName = "strict-sharedFlow",
61+
valueDescription = VALUE_DESCRIPTION,
62+
description = description("SharedFlow"),
63+
required = false,
64+
allowMultipleOccurrences = false,
65+
)
66+
67+
val NESTED_FLOW = CliOption(
68+
optionName = "strict-nested-flow",
69+
valueDescription = VALUE_DESCRIPTION,
70+
description = description("Nested flows"),
71+
required = false,
72+
allowMultipleOccurrences = false,
73+
)
74+
75+
val STREAM_SCOPED_FUNCTIONS = CliOption(
76+
optionName = "strict-stream-scope",
77+
valueDescription = VALUE_DESCRIPTION,
78+
description = description("Stream Scopes"),
79+
required = false,
80+
allowMultipleOccurrences = false,
81+
)
82+
83+
val SUSPENDING_SERVER_STREAMING = CliOption(
84+
optionName = "strict-suspending-server-streaming",
85+
valueDescription = VALUE_DESCRIPTION,
86+
description = description("suspending server streaming methods"),
87+
required = false,
88+
allowMultipleOccurrences = false,
89+
)
90+
91+
val NOT_TOP_LEVEL_SERVER_FLOW = CliOption(
92+
optionName = "strict-not-top-level-server-flow",
93+
valueDescription = VALUE_DESCRIPTION,
94+
description = description("not top-level server streaming declarations"),
95+
required = false,
96+
allowMultipleOccurrences = false,
97+
)
98+
99+
val FIELDS = CliOption(
100+
optionName = "strict-fields",
101+
valueDescription = VALUE_DESCRIPTION,
102+
description = description("fields"),
103+
required = false,
104+
allowMultipleOccurrences = false,
105+
)
106+
107+
const val VALUE_DESCRIPTION = "none, warning or error"
108+
109+
fun description(entity: String): String {
110+
return "Diagnostic level for $entity in @Rpc services."
111+
}
112+
113+
val configurationMapper = mapOf(
114+
STATE_FLOW to StrictModeConfigurationKeys.STATE_FLOW,
115+
SHARED_FLOW to StrictModeConfigurationKeys.SHARED_FLOW,
116+
NESTED_FLOW to StrictModeConfigurationKeys.NESTED_FLOW,
117+
STREAM_SCOPED_FUNCTIONS to StrictModeConfigurationKeys.STREAM_SCOPED_FUNCTIONS,
118+
SUSPENDING_SERVER_STREAMING to StrictModeConfigurationKeys.SUSPENDING_SERVER_STREAMING,
119+
NOT_TOP_LEVEL_SERVER_FLOW to StrictModeConfigurationKeys.NOT_TOP_LEVEL_SERVER_FLOW,
120+
FIELDS to StrictModeConfigurationKeys.FIELDS,
121+
)
122+
}
123+
124+
fun AbstractCliOption.processAsStrictModeOption(value: String, configuration: CompilerConfiguration): Boolean {
125+
StrictModeCliOptions.configurationMapper[this]?.let { key ->
126+
value.toStrictMode()?.let { mode ->
127+
configuration.put(key, mode)
128+
return true
129+
}
130+
}
131+
132+
return false
133+
}
134+
135+
private fun String.toStrictMode(): StrictMode? {
136+
return when (lowercase()) {
137+
"none" -> StrictMode.NONE
138+
"warning" -> StrictMode.WARNING
139+
"error" -> StrictMode.ERROR
140+
else -> null
141+
}
142+
}
File renamed without changes.

compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/diagnostics/FirRpcDiagnostics.kt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,22 @@
44

55
package kotlinx.rpc.codegen.checkers.diagnostics
66

7+
import kotlinx.rpc.codegen.StrictMode
8+
import kotlinx.rpc.codegen.StrictModeAggregator
9+
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
10+
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory0
11+
import org.jetbrains.kotlin.diagnostics.Severity
12+
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies
713
import org.jetbrains.kotlin.diagnostics.error0
814
import org.jetbrains.kotlin.diagnostics.error1
915
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
1016
import org.jetbrains.kotlin.diagnostics.warning0
1117
import org.jetbrains.kotlin.fir.types.ConeKotlinType
1218
import org.jetbrains.kotlin.psi.KtAnnotationEntry
19+
import org.jetbrains.kotlin.utils.DummyDelegate
20+
import kotlin.properties.ReadOnlyProperty
21+
import kotlin.reflect.KClass
22+
import kotlin.reflect.KProperty
1323

1424
object FirRpcDiagnostics {
1525
val MISSING_RPC_ANNOTATION by error0<KtAnnotationEntry>()
@@ -21,3 +31,48 @@ object FirRpcDiagnostics {
2131
RootDiagnosticRendererFactory.registerFactory(RpcDiagnosticRendererFactory)
2232
}
2333
}
34+
35+
@Suppress("PropertyName", "detekt.VariableNaming")
36+
class FirRpcStrictModeDiagnostics(val modes: StrictModeAggregator) {
37+
val STATE_FLOW_IN_RPC_SERVICE by modded0<PsiElement>(modes.stateFlow)
38+
val SHARED_FLOW_IN_RPC_SERVICE by modded0<PsiElement>(modes.sharedFlow)
39+
val NESTED_STREAMING_IN_RPC_SERVICE by modded0<PsiElement>(modes.nestedFlow)
40+
val STREAM_SCOPE_ENTITY_IN_RPC by modded0<PsiElement>(modes.streamScopedFunctions)
41+
val SUSPENDING_SERVER_STREAMING_IN_RPC_SERVICE by modded0<PsiElement>(modes.suspendingServerStreaming)
42+
val NON_TOP_LEVEL_SERVER_STREAMING_IN_RPC_SERVICE by modded0<PsiElement>(modes.notTopLevelServerFlow)
43+
val FIELD_IN_RPC_SERVICE by modded0<PsiElement>(modes.fields)
44+
45+
init {
46+
RootDiagnosticRendererFactory.registerFactory(RpcStrictModeDiagnosticRendererFactory(this))
47+
}
48+
}
49+
50+
private inline fun <reified T> modded0(mode: StrictMode): DiagnosticFactory0DelegateProviderOnNull {
51+
return DiagnosticFactory0DelegateProviderOnNull(mode, T::class)
52+
}
53+
54+
class DiagnosticFactory0DelegateProviderOnNull(
55+
private val mode: StrictMode,
56+
private val psiType: KClass<*>,
57+
) {
58+
operator fun provideDelegate(
59+
@Suppress("unused")
60+
thisRef: Any?,
61+
prop: KProperty<*>,
62+
): ReadOnlyProperty<Any?, KtDiagnosticFactory0?> {
63+
val severity = when (mode) {
64+
StrictMode.ERROR -> Severity.ERROR
65+
StrictMode.WARNING -> Severity.WARNING
66+
StrictMode.NONE -> null
67+
} ?: return DummyDelegate(null)
68+
69+
return DummyDelegate(
70+
KtDiagnosticFactory0(
71+
name = prop.name,
72+
severity = severity,
73+
defaultPositioningStrategy = SourceElementPositioningStrategies.DEFAULT,
74+
psiType = psiType,
75+
),
76+
)
77+
}
78+
}

compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/diagnostics/RpcDiagnosticRendererFactory.kt

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package kotlinx.rpc.codegen.checkers.diagnostics
66

7+
import kotlinx.rpc.codegen.StrictMode
8+
import kotlinx.rpc.codegen.StrictModeAggregator
79
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
810
import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory
911
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers
@@ -37,3 +39,68 @@ object RpcDiagnosticRendererFactory : BaseDiagnosticRendererFactory() {
3739
)
3840
}
3941
}
42+
43+
class RpcStrictModeDiagnosticRendererFactory(
44+
private val diagnostics: FirRpcStrictModeDiagnostics,
45+
) : BaseDiagnosticRendererFactory() {
46+
override val MAP = KtDiagnosticFactoryToRendererMap("Rpc").apply {
47+
diagnostics.STATE_FLOW_IN_RPC_SERVICE?.let {
48+
put(
49+
factory = it,
50+
message = message("StateFlow") { stateFlow },
51+
)
52+
}
53+
54+
diagnostics.SHARED_FLOW_IN_RPC_SERVICE?.let {
55+
put(
56+
factory = it,
57+
message = message("SharedFlow") { sharedFlow },
58+
)
59+
}
60+
61+
diagnostics.NESTED_STREAMING_IN_RPC_SERVICE?.let {
62+
put(
63+
factory = it,
64+
message = message("Nested streaming") { nestedFlow },
65+
)
66+
}
67+
68+
diagnostics.STREAM_SCOPE_ENTITY_IN_RPC?.let {
69+
put(
70+
factory = it,
71+
message = message("Stream Scope usage") { streamScopedFunctions },
72+
)
73+
}
74+
75+
diagnostics.SUSPENDING_SERVER_STREAMING_IN_RPC_SERVICE?.let {
76+
put(
77+
factory = it,
78+
message = message("Suspend function declaration with server streaming") { suspendingServerStreaming },
79+
)
80+
}
81+
82+
diagnostics.NON_TOP_LEVEL_SERVER_STREAMING_IN_RPC_SERVICE?.let {
83+
put(
84+
factory = it,
85+
message = message("Not top-level server-side streaming") { sharedFlow },
86+
)
87+
}
88+
89+
diagnostics.FIELD_IN_RPC_SERVICE?.let {
90+
put(
91+
factory = it,
92+
message = message("Field declaration") { fields },
93+
)
94+
}
95+
}
96+
97+
private fun message(entityName: String, selector: StrictModeAggregator.() -> StrictMode): String {
98+
val actionWord = when (diagnostics.modes.selector()) {
99+
StrictMode.NONE -> ""
100+
StrictMode.WARNING -> "deprecated"
101+
StrictMode.ERROR -> "prohibited"
102+
}
103+
104+
return "$entityName is $actionWord in @Rpc services in strict mode."
105+
}
106+
}

0 commit comments

Comments
 (0)