Declaration References

Declaration reference represents a link between codebase declarations. Konsist allows to precisely verify properties of linked type. This can be type used in function or property declaration or child/parent class or interface. For example

1. Verify if all types of function parameters are interfaces:

Konsist
    .scopeFromProject()
    .functions()
    .parameters
    .types
    .assertTrue {
        it.isInterface
    }

2. Access properties of parents (parent classes and child interfaces). Below snippet checks if parent class has internal modifier:

fun `all parrent interfaces are internal`() {
    Konsist
        .scopeFromProject()
        .classes()
        .parentInterfaces()
        .assertTrue {
            it.hasInternalModifier()
        }
}

3. Access properties of children (child classes and child interfaces). Below snippet checks if all interfaces have children that resided in ..somepackage.. package:

Konsist
    .scopeFromProject()
    .interfaces()
    .assertTrue {
        it.hasAllChildren(indirectChildren = true) { child -> 
            child.resideInPackage("..somepackage..") 
        }
    }

Type Representation

Kotlin types can defined in multiple ways. Consider foo property with Foo type:

val foo: Foo

The Foo type can be defined by:

  • class

  • interface

  • object

  • type alias

  • import alias

  • kotlin types (Kotlin basic type or Kotlin collections type)

  • function type

  • external library (type defined outside project codebase) represents declaration which is not defined in the project

The Foo type can be represented by one of KoDeclarationX classes:

SorceDeclaration

KoClassDeclaration

KoInterfaceDeclaration

KoObjectDeclaration

KoTypeAliasDeclaration

KoImportAliasDeclaration

KoKotlinTypeDeclaration

KoFunctionDeclaration

KoExternalDeclaration

Each of these types possesses a largely distinct set of characteristics; for instance, classes and interfaces can include annotations, whereas import aliases cannot.

To access properties the specific declaration type, the deceleration cast to more specific type is required (from generic KoTypeDeclaration). Example below assumes that Foo is represented by the Foo class:

Konsist
    .scopeFromProject()
    .properties()    
    .types
    .assertTrue { koTypeDeclaration ->
        val koClass = koTypeDeclaration as KoClassDeclaration

        koClass.hasAllAnnotations {
            it.representsTypeOf<String>()
        }
    }

To facilitate testing Konsist API provides set of dedicated casting extensions. The above code can be simplified:

Konsist
    .scopeFromProject()
    .properties()
    .types
    .assertTrue { koTypeDeclaration ->
        koTypeDeclaration.asSourceClass.hasAllAnnotations {
            it.representsTypeOf<String>()
        }
    }

Here is the list of all casting extensions:

SorceDeclarationCast ExtensionType Check Extension

KoClassDeclaration

asClassDeclaration

isClass

KoInterfaceDeclaration

asObjectDeclaration

isObject

KoObjectDeclaration

asInterfaceDeclaration

isInterface

KoTypeAliasDeclaration

asTypeAliasDeclaration

isTypeAlias

KoImportAliasDeclaration

asImportAliasDeclaration

isImportAlias

KoKotlinTypeDeclaration

asKotlinTypeDeclaration

isKotlinType

KoFunctionDeclaration

asFunctionTypeDeclaration

isFunctionType

KoExternalDeclaration

asExternalTypeDeclaration

isExternalType

Type Represented By Class

Source code:

internal class Foo

Usage:

val foo: Foo? = null

Konsist test:

scope
    .properties()
    .types
    .assertTrue {
        it.asClassDeclaration?.hasInternalModifier
    }

Type Represented By Interface

Source code:

internal interface Foo

Usage:

val foo: Foo? = null

Konsist test:

scope
    .properties()
    .types
    .assertTrue {
        it.asInterfaceDeclaration?.hasInternalModifier
    }

Type Represented By Object

This scenario is uncommon, but still possible.

Source code:

internal object Foo

Usage:

val foo: Foo? = null

Konsist test:

scope
    .properties()
    .types
    .assertTrue {
        it.asObjectDeclaration?.hasInternalModifier
    }

Type Represented By Type Apias

Source code:

internal object Foo

Usage:

typealias MyFoo = Foo
val foo: MyFoo? = null

Konsist test:

scope
    .properties()
    .types
    .assertTrue {
        it
            .sourceTypeAlias
            .type
            .sourceInterface
            .hasInternalModifier
    }

Type Represented By Import Alias

Source code:

internal object Foo

Usage:

import com.app.Foo as MyFoo

val foo: MyFoo? = null

Konsist test:

scope
    .properties()
    .types
    .assertTrue {
        it
            .asTypeAliasDeclaration
            .type
            .asInterfaceDeclaration
            .hasInternalModifier
    }

Type Represented By Kotlin Type

Source code:

// Kotlin internal source code for String

Usage:

val foo: String? = null

Konsist test:

scope
    .properties()
    .types
    .assertTrue {
        it.asKotlinTypeDeclaration.name == "String"
    }

Type Represented Function Type

Source code:

// Kotlin internal source code

Usage:

val foo: () -> Unit? = null

Konsist test:

scope
    .properties()
    .types
    .assertTrue {
        it
            .sourceFunctionType
            .parameterTypes
            .isEmpty()
    }

Type Represented By External Type

External type represents the type is define outside of the project codebase, usually by external library. Konsist is not able to parse this type, so type information is limited (Konsist is not able to parse the compiled file).

For Example:

class MyViewModel: ViewModel

The Android ViewModel class is provided by androidx.lifecycle:lifecycle-viewmodel-ktx dependency, so Konsist has limited information.

Last updated