This document is a contribution guideline for the development of Analysis API project. Please, follow it for your changes to be accepted.
val dclSmbl: KtDeclarationSymbolval declarationSymbol: KtDeclarationSymbolanalysis-api module.This includes:
val/var;Do not hesitate to make the KDoc as long and detailed as possible until the information you write will help others to use your API changes in a better way.
Please do not include obvious KDocs, which do not add additional information. Always describe the purpose of the declaration.
Bad (adds no information to the declaration name):
public class KtAnnotationApplication( /** * ClassId of an annotation */ val classId: ClassId )
Good:
public class KtAnnotationApplication( /** * A fully qualified name of an annotation class which is being applied */ val classId: ClassId )
Please, explain (better with examples) how the new functionality you add behaves on corner cases.
Example:
/** * Get type of given expression. * * Return: * - [KtExpression] type if given [KtExpression] is real expression; * - `null` for [KtExpression] inside packages and import declarations; * - `Unit` type for statements; */ public fun KtExpression.getKtType(): KtType?
As you already know, Analysis API is a compiler API used to retrieve compiler-related information for FIR IDE Plugin. It is used in a wide range of features: code completion, debugger, inspections, and all other features which require compiler-related information. So it is important to keep the Analysis API Surface Area concise. Whether you want to introduce a new method or change the behavior of the existing method, consider
It was already mentioned in the general part of the guidelines but Unit Tests are an important thing for public API. So, write Unit Test whenever you add new functionality or modify existing behavior (either fixing bug or adding feature). A good unit test:
If you fixed a bug or added new functionality to an existing feature, consider adding test(s) which cover it.
Kt prefix. E.g, KtSymbol, KtConstantValue.KtLifetimeTokenOwner as supertype for all declarations which contains other KtLifetimeTokenOwner inside (eg, via parameter types, function return types) to ensure that internal KtLifetimeTokenOwner are not exposed via your declaration.KtLifetimeTokenOwner. It means that this declaration has a lifetime. And this declaration has to be checked to ensure that it is not used after its lifetime has come to the end. To ensure that all methods(except hashCode/equals /toString) and properties should be wrapped into withValidityAssertion { .. } check:public class KtCall( private val _symbol: KtSymbol, private val _isInvokeCall: Boolean, ) : KtLifetimeTokenOwner { public val symbol: KtSymbol get() = withValidityAssertion { _symbol } public val isInvokeCall: Boolean get() = withValidityAssertion { _isInvokeCall } public fun isImplicitCall(): Boolean = withValidityAssertion { // IMPL } override fun equals(other: Any?): Boolean { // no withValidityAssertion // IMPL } override fun hashCode(): Int { // no withValidityAssertion // IMPL } override fun toString(): String { // no withValidityAssertion // IMPL } }
The only part of Analysis API which should be exposed is the Analysis API surface area (the API itself). All other declarations should be kept internal or private. To ensure that, analysis-api module has Library Mode enabled. This will enforce that only declarations which are supposed to be exposed are really exposed. There are no guarantees on the non-surface part of analysis API on binary and source compatibility.
Also, the implementation modules should be considered as internal themselves. Please, keep declarations there internal or private too then it is possible. Implementation modules are:
In compiler-related code (and Analysis API is compiler-related 😀), there may be a lot of Kotlin top-level utility functions and properties to work with AST, PsiElements, and others. Such code pollutes public and internal namespaces and introduces a lot of functions with unclear semantics:
Bad:
internal fun render(value: KtAnnotationValue): String = buildString { renderConstantValue(value) } private fun StringBuilder.renderConstantValue(value: KtAnnotationValue) { when (value) { is KtAnnotationApplicationValue -> renderAnnotationConstantValue(value) ... } } private fun StringBuilder.renderConstantAnnotationValue(value: KtConstantAnnotationValue) { append(value.constantValue.renderAsKotlinConstant()) } // A lot of other non-related utility functions in the same file
Good:
internal object KtAnnotationRenderer { fun render(value: KtAnnotationValue): String = buildString { renderConstantValue(value) } private fun StringBuilder.renderConstantValue(value: KtAnnotationValue) { when (value) { is KtAnnotationApplicationValue -> renderAnnotationConstantValue(value) ... } } private fun StringBuilder.renderConstantAnnotationValue(value: KtConstantAnnotationValue) { append(value.constantValue.renderAsKotlinConstant()) } }