Skip to content

Releases: LemonAppDev/konsist

v0.17.3

08 Dec 19:44
8a31e2b
Compare
Choose a tag to compare

Complete List Of Changes

💡 API Improvements

v0.17.2

04 Dec 22:52
89662ce
Compare
Choose a tag to compare

Complete List Of Changes

🐛 API Bug Fixes

v0.17.1

02 Dec 16:36
8cdaa81
Compare
Choose a tag to compare

This release is focused on bug fixes and minor improvements.

Konsist 0.17.0 has accidentally changed layer dependency behaviour from "may depend on" to "has to depend on".

We have reverted this change and added a strict parameter to dependsOn method:

  • strict = false (default) - may depend on layer
  • strict = true - have to depend on layer
// Optional dependency - Feature layer may depend on Domain layer
featureLayer.dependsOn(domainLayer) // strict = false by default

// Required dependency - Feature layer must depend on Domain layer
featureLayer.dependsOn(domainLayer, strict = true)

Complete List Of Changes

🐛 API Bug Fixes

💡 API Improvements

🏗️ Maintenance

  • Block Kotlin updates prior to version 2.1.0 (no Kotlin-related changes in this release) by @igorwojda

v0.17.0

25 Nov 10:01
6d879e4
Compare
Choose a tag to compare

First of all we have an exciting news - Konsist is the winner of the Kotlin Foundation Grants Program 2024 🎉.

We have been hearing you feedback, so this update bring many important improvements requested by the community.

This release bring bugs fixes and many API improvements as well as some groundwork for upcoming 1.0.0-Beta1 release.
Overall Kosnist API is quite stable now , so we don’t expect any more major API changes.

Big thanks goes to @chrisbanes for the contributions.

Key changes

Improvement for Parent References (⚠️Breaking Change)

We enhanced parent references in the API to now support both examining the parent type declaration (enabling Konsist checks for annotations, modifiers, and other declaration properties) and analyzing actual parent usage details (such as inspecting generic type arguments) through separate access methods.

This is best demonstrated with the example:

Konsist
    .classes
    .withName("StringContainer")
    .assertTrue {
        it.parent.sourceDeclaration.hasModifier(KoModifier.PUBLIC) // previously it.parent.hasModifier
    }

This change allows to verify parent generic type arguments (parent use site).

Konsist
    .classes
    .withName("StringContainer")
    .assertTrue {
        it.typeArguments.flattern().contains { it.name == "String" }
    }

Added Support for Generic Type Arguments

This release bring top requested feature - support for generic types. Generic type parameters and type arguments can be now verified with Konsist.

Check Verifying Generics documentation page.

Source Declaration (⚠️Breaking Change)

API around source declarations has been unified. From property declaration, function declaration, function return type, parent
declaration, etc. it's possible to can access sourceDeclaration (declaration site) to get the actual type and verify it.

In this example we are checking if type of current property is a class declaration with internal modifier:

// Snippet
internal class Engine

val current: Engine? = null // testing this in below test

// Konsist test
Konsist
   .scopeFromProject()
   .properties
   .assertTrue {
      it.type?.sourceDeclaration?.asClassDeclaration()?.hasInnerModifier // true
   }

Note that explicit casting (asXDeclaration) has to be used to access specific properties of the declaration.

Due to updated API, some methods have been deprecated. For example, asClassDeclaration has been "moved to sourceDeclaration`:

Konsist
   .scopeFromProject()
   .functions()
   .returnTypes
   .assertTrue {
      it.asClassDeclaration() // Deprecated
         ?.hasPrivateModifier
   }
Konsist
   .scopeFromProject()
   .functions()
   .returnTypes
   .assertTrue {
      it.sourceDeclaration()
      ?.asClassDeclaration()
      ?.hasPrivateModifier
}

Improved Kotlin Backwards Compatibility

Konsist has been updated to ensure backward compatibility with Kotlin 1.8. This enhancement enables developers to integrate Konsist
into projects that haven't yet upgraded to the latest Kotlin version. By supporting Kotlin 1.8, Konsist can now be utilized in a wider
range of codebases, including those maintaining older Kotlin versions for various reasons. From now on Konsist should be compatible with
at least 3 most recent releases of Kotlin (does not take path releases into consideration). We will also slow down with updating minor
version of Kotlin to boost compatibility even more.

Language Reference Link

The Konsist Language reference was added.

API Improvements

Added isExtension:

// Snippet
fun String.sampleFunction() {}

// Konsist test
    Konsist
        .scopeFromProject()
        .functions()
        .assertTrue {
            it.isExtension shouldBeEqualTo true // true
        }

... and more.

Updated Architecture Assertions

  • Reimplemented assertArchitecture to fix few bugs.
  • Error handling has been improved to cover more edge cases for invalid architecture check configurations.
  • We have added new methods and improved validation and messaging around errors:
'Domain' layer does not depends on 'Presentation' layer failed. Files that depend on 'Presentation' layer:
	└──file /Users/igorwojda/domain/MyUseCase.kt
		├── import presentation.MyView.kt
		└── import presentation.MyUiState.kt

A new include() method was added to include layers in architecture verification, without defining a dependency.

private val domain = Layer("Domain",  "com.domain..")
private val presentation = Layer("Presentation", "com..presentation..")

Konsist
    .scopeFromProject()
    scope.assertArchitecture {
        presentation.include() // Include layer without defining a dependency
        domain.doesOnNothing()
    }
}

What's Next

We will now focus on adding baseline and processing community feedback.

Complete List Of Changes

⚠️ Breaking API Changes

🐛 API Bug Fixes

💡 API Improvements

💡 Improvements

Read more

v0.16.1

10 Sep 10:20
d9a4fcb
Compare
Choose a tag to compare

What's Changed

Hot fix release.

Complete list of changes

⚠️ Breaking API Changes

🐛 API Bug Fixes

  • Fix crash while parsing generated Kotlin files #1334

Full Changelog: v0.16.0...v0.16.1

v0.16.0

30 Aug 15:15
696b677
Compare
Choose a tag to compare

What's Changed

This release contains new improvement and API updates. Two new features are providers for all common combinations of interface, object and class and doesNotDependOn method for architecture checks.

Thanks you for your feedback 🙏

1. Add providers for all common combinations of interface, object and class

Adding suport for writing tests for classes, interfaces and objects in one test.

Konsist now provides a set of methods such as:

  • classesAndInterfacesAndObjects()
  • classesAndInterfaces()
  • classesAndObjects()
  • interfacesAndObjects()

These methods can be used in two ways:

  • Case 1: After creating the initial scope.
  • Case 2: When filtering chosen declarations from other declarations.

For example:

// Case 1
scope
	.classesAndInterfacesAndObjects(includeNested = true, includeLocal = false)
	.assertTrue { it.hasNameEndingWith("Suffix") }
	
scope
	.classesAndObjects()
	.assertTrue { it.hasDataModifier }

// Case 2
scope
	.classes()
	.classesAndInterfaces(includeNested = true, includeLocal = false)
	.assertTrue { it.hasNameEndingWith("Suffix") }
	
scope
	.classes()
	.classesAndObjects()
	.assertTrue { it.hasDataModifier }

In addition to these methods, Konsist offers various other functions that allow you to count all selected declarations, check whether they meet a specific predicate, or determine if any declarations match the selected criteria. Examples include:

  • numClassesAndInterfacesAndObjects()
  • countClassesAndInterfacesAndObjects { it.hasNameEndingWith("Suffix") }
  • hasClassesOrInterfacesOrObjects()
  • hasClassOrInterfaceOrObject { it.hasNameEndingWith("Suffix") }

The examples above demonstrate methods for classes, interfaces, and objects, but Konsist provides similar methods for all combinations of classes, interfaces, and objects (either individually, in pairs, or all together).

Additionally, Konsist includes a set of withX/withoutX extension functions, enabling you to write tests like:

scope
	.classes()
	.withClassOrInterfaceOrObjectNamed("SampleName")
	.assertTrue { ... }

2. doesNotDependOn method

Added doesNotDependOn method to architectural checks.

Eg. It is possible to write such tests for this architecture

image

    private val scope = Konsist.scopeFromProject()

    private val adapter = Layer("Adapter", "com.samplepackage.adapter..")
    private val common = Layer("Common", "com.samplepackage.common..")
    private val domain = Layer("Domain", "com.samplepackage.domain..")
    private val port = Layer("Port", "com.samplepackage.port..")
    
    @Test
    fun `Domain layer not depend on Adapter and Port layers and Adapter layer not depend on Domain layer`() {
       	 scope.assertArchitecture { 
       	 	domain.doesNotDependOn(adapter, port)
            adapter.doesNotDependOn(domain)
       	 }
    }

Complete list of changes

⚠️ Breaking API Changes

🐛 API Bug Fixes

💡 API Improvements

📕 Documentation

🏗️ Maintanance

Full Changelog: v0.15.1...v0.16.0

v0.15.1

02 Apr 13:07
603d19e
Compare
Choose a tag to compare

What's Changed

Restore layer validation

Full Changelog: v0.15.0...v0.15.1

v0.15.0

01 Apr 10:50
c333eb5
Compare
Choose a tag to compare

What's Changed

1. Use Kotlin compiler kotlin-compiler-embeddable dependency

This release updates Kotlin compiler dependency to kotlin-compiler-embeddable reducing dependency conflicts and simplifying dependency management.

2. All all APIs accepting varargs now accept Kotlin collections (List and Set, etc.):

//  Existing API
Konsist
	.scopeFromFiles(path1, path2, path3)
    ...

//  Improved API (old API stil works)
val paths = setOf(path1, path2, path3) // listOf()
Konsist
	.scopeFromFiles(paths)
	...

⚠️ In next release bunch of deprecated methods will be removed, so make sure to bring you tests up to date.

💡 Improvements

Full Changelog: v0.14.0...v0.15.0

v0.14.0

11 Mar 14:05
Compare
Choose a tag to compare

What's Changed

⚠️ We are changing the deprecation strategy. Initially we wanted to deprecated API in 1.0.0, but this will be a big change, so instead we decided to follow more granular deprecation strategy to facilitate future upgrades. API with deprecation target for 1.0.0 has beed updated to 0.16.0 (#902). From now on deprecated methods will be available for 2 minor versions (until 1.0.0 release). After 1.0.0 release Konsist will follow semantic versioning scheme meaning breaking changes will be introduced only when major version of the Konsist changes. Deprecated methods (with initial removal targeted for 1.0.0) will be removed in next release (deprecation target has bee updated). Please update your by removing Deprecated Konsist API to facilitate future migration.

Thanks you for your feedback 🙏

New Contributors

1. Added declaration references

Konsist understands the code's structure better. This new feature, highly requested by the community, provides link between declarations, giving you more control over code quality checks. Konsist now works with declarations directly, allowing you to precisely verify type properties, inheritance relationships, and more e.g.

All parent interfaces have actual modifier:

Konsist
    .scopeFromProject()
    .classes()
    .parentInterfaces()
    .assertTrue {
        it.hasActualModifier() // Can access other properties of the interface 
    }

All function parameters are interfaces:

Konsist
    .scopeFromPackage("com.app.worker..")
    .functions()
    .parameters
    .types
    .assertTrue {
        it.isInterface // Can 
    }

All classes have test classes with Test annotation and a name containing its name:

Konsist
   .scopeFromProject()
   .classes()
   .assertTrue {
       it.testClasses().all { testClass -> 
           testClass.hasAnnotationOf<Test>() && testClass.hasNameContaining(it.name)
       }
   }

All interfaces have children (child classes and child interfaces) resided in ..somepackage.. package:

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

See Declaration References docs.

2. Retrieve indirect parents

The indirectParents parameter has been added to parent retrieval methods (parents(), hasParentClass(), hasAllParentInterfacesOf etc.). This parameter specifies whether or not to include parents such as parent of the parent. By default, indirectParents is false e.g.

// Class hierarchy
ClassAClassBClassC

For above inheritance hierarchy is possible to retrieve direct parents of ClassC (ClassB) as well as all parents present in the codebase (ClassB and ClassC).

Konsist
	.scopeFromProject()
	.classes()
	.first { it.name == "ClassC" }
	.parents() // returns direct parents listOf(ClassB)

Konsist
	.scopeFromProject()
	.classes()
	.first { it.name == "SampleClass" }
	.parents(indirectParents = true) // returns listOf(ClassB, ClassA)

3. Improved assertArchitecture methods

Following other assert methods, we added the testName and additionalMessage arguments to the assertArchitecture methods.
You can now manually set the test name that will be displayed in the test failure message and used to suppress tests. This is useful for Kotest and dynamic tests.

scope
	.assertArchitecture(
		testName = "sample name",
		additionalMessage = "sample message"
	) {
		// some dependencies
	}

4. Added support for variables

Now Konsist will allow to access and verify variables located inside functions, init blocks, getters, setters and enum constants.

Konsist
	.scopeFromProject()
	.functions()
	.assertTrue { 
		it.hasVariable { variable -> 
			variable.name == "sampleVariable" 
			} 
		} 

5. Ability to check tacit type

Now Konsist can check whether the properties or variables have a tacit type. Tacit type means that the declaration has an explicitly or implicitly specified type.

val sut: Foo = someCollection.first() // hasTacitTypeOf(SampleClass::class) == true
val sut  = Foo("some text") // hasTacitTypeOf(SampleClass::class) == true
val sut = someCollection.first() // hasTacitTypeOf(SampleClass::class) == false

One scenario where tacit type is useful is verification of sut (system under test / class under test) existence:

        Konsist
            .scopeFromTest()
            .classes()
            .assertTrue {
                // Get type name from test class e.g. FooTest -> Foo
                val type = it.name.removeSuffix("Test")
                
                val sut = it
                    .properties()
                    .firstOrNull { property -> property.name == "sut" }

                    sut != null && sut.hasTacitType(type)
            }

6. Ability to check sourceType and bareSourceType

Now Konsist provides sourceType and bareSourceType properties for better type analysis:

val car: MyClass // sourceType == "MyClass".
val car: MyClass<String> // sourceType == "MyClass<String>"

val car: MyClass // bareSourceType == "MyClass".
val car: MyClass? // bareSourceType == "MyClass".
val car: MyClass<String> // bareSourceType == "MyClass"
val car: MyClass<String?>? // bareSourceType == "MyClass"
val car: com.app.MyClass // bareSourceType == "MyClass"

One scenario where bareSourceType is useful is naming verification of property with List type:

Konsist
    .scopeFromProject()
    .properties()
    .types
    .withBareSourceTypeOf(List::class)
    .assertTrue {
        it.hasNameEndingWith("s") || it.hasNameEndingWith("es")
    }     

7. Ability to check whether a property is read-only

Now Konsist provides isReadOnly property that checks whether a property is specified with a val modifier:

val foo = Foo("some text") // isReadOnly == true
var foo = Foo("some text") // isReadOnly == false

Complete list of changes

⚠️ Breaking API Changes

🐛 Bug Fixes

💡 Improvements

Read more

v0.13.0

09 Oct 11:24
0e3811a
Compare
Choose a tag to compare

What's Changed

This release focuses on improving KoTest support and improve assertions (Konsist docs).

image

From this point, Konsit will have a first-class KoTest support meaning that every following release will be developed with KoTest support in mind. At the moment we have addressed known issues and improved the API. If you think something is still missing just let us know on Slack. We would like to add more KoTest snippets to our documentation, but first, we want to see how you are using Konsist with KoTest (let us know).

On top of that, we have introduced, new assertions, multiple improvements and bug fixes (thanks to your feedback🙏).

Some of our efforts happen in the background. For instance, we improve documentation are enhance the CI setup and release process to facilitate a more frequent release schedule. Additionally, we aim to make the Konsist backlog and roadmap public to boost transparency and community involvement.

Big claps go towards @JonathanSarco for his open-source contributions to the Konsist codebase 👏.

1. Improved Assertions

Konsist
  .scopeFromProject()
  .classes()
  .assertTrue { ... } //  previously assert { ... }

Konsist
  .scopeFromProject()
  .classes()
  .assertFalse { ... } //  previously assertNot { ... }

We have relaxed the empty list behavior. Konsist test will pass on an empty list to match the behavior of methods in Kotlin collection processing:

Konsist
  .scopeFromProject()
  .classes()
  .filter { it.name == "non existing class name" } 
  .assertTrue { ... } // no crash

We have also added a strict parameter to restore old behavior:

Konsist
  .scopeFromProject()
  .classes()
  .filter { it.name == "non existing class name" } 
  .assertTrue(strict = true) { ... } // crash

The assertTrue and assertFalse can be called on a single declaration:

Konsist
  .scopeFromProject()
  .classes()
  .first()
  .assertTrue { ... }

Konsist
  .scopeFromProject()
  .classes()
  .first()
  .assertFalse { ... }

We have added new assertions. Now you can verify if certain queries result in empty or non-empty lists with assertEmpty() and assertNotEmpty():

Konsist
  .scopeFromPackage("..data..")
  .classes()
  .assertEmpty()

Konsist
  .scopeFromPackage("..data..")
  .classes()
  .assertNotEmpty()

You can now check whether a specific query is invoked on a null or non-null declaration.

Konsist
  .scopeFromProject()
  .classes()
  .first()
  .assertNull()

Konsist
  .scopeFromProject()
  .classes()
  .first()
  .assertNotNull()

The assertArchitecture assertion on the list of files (previously it worked only on KoScope):

// Works as before
Konsist
  .scopeFromProject()
  .assertArchitecture { ... } 

// Now files can be further processed before asserting architecture
Konsist
  .scopeFromProject()
  .files
  .filter { ... }
  .assertArchitecture { ... } 

The additionalMessage param allows to provision of additional messages that will be displayed with the failing test. This may be a more detailed description of the problem or a hint on how to fix the issue:

Konist
    .scopeFromProject() 
    .classes()
    .assertFalse(additionalMessage = "Do X to fix the issue") { ... }

2. Improved KoTest Support

We've allowed assertions for individual declaration (previously list of declarations was required).

Konsist
   .scopeFromProject()
   .classes()
   .first()
   .assertTrue { }  // now possible

Due to Kotlin/JVM limitation, the test name has to be passed manually for each KoTest test. We have added a dedicated testName argument (assertTrue and assertFalse methods). The test name is used in two ways - as a test name displayed in the failing test crash and as a suppressName used for suppressing tests. While this solution may not be perfect (unlike JUnit tests which don't require this), it's still effective and works well. We are open to making it better, but ATM we are stuck. The upcoming The K2 Compiler may provide a more convenient alternative.

With the new testName parameter the KoTest test can be now suppressed:

Konsist
 .scopeFromProject()
 .classes()
 .assert(testName = "custom suppress name") { ... }

// Suppression by custom name passed as an argument
@Suppress("custom supress name")
class Car {

}

// Suppression by custom name passed as argument also works with `konsist.` prefix
// This prefix does not have to be explicitly specified for the `testName` argument
@Suppress("konsist.custom supress name")
class Car {

}

We have added multiple KoTest starter projects. Each project has Konsist and KoTest preconfigured configured and a single KoTest test:

  • konsist-starter-android-gradle-groovy-kotest
  • konsist-starter-android-gradle-kotlin-kotest
  • konsist-starter-kmp-gradle-kotlin-kotest
  • konsist-starter-spring-gradle-groovy-kotest
  • konsist-starter-spring-gradle-kotlin-kotest

We have also updated snippets to include a few KoTest examples (help us with adding a few more). The entire Konsist documentation was updated to take KoTest into consideration.

3. Added support for getters & setters

Now Konsist will allow to access and verify setters and getters. For example, now it is possible to verify if a given property has a getter (or setter):

 Konsist
    .scopeFromProject()
    .properties()
    .assertTrue { it.hasGetter && it.hasSetter} 
 Konsist
    .scopeFromProject()
    .properties()
    .getters
    .assertTrue { it.hasBlockBody } 

4. Ability to check block body and expression body

With declarations such as functions, getters, and setters you can now they the type of the body (block or expression):

Konsist
   .scopeFromProject()
   .functions()
   .assertTrue { it.hasExpressionBody} 
 Konsist
    .scopeFromProject()
    .properties()
    .getters
    .assertTrue { it.hasBlockBody } 

5. Ability to check return value

You can check if functions have a return value:

Konsist
   .scopeFromProject()
   .functions()
   .assertTrue { it.hasReturnValue }

You can inspect return value further

Konsist
            .scopeFromProject()
            .functions()
            .assertTrue { it.returnType?.text == "Unit" || it.returnType?.text == null} 

6. Ability to inspect property value

You can check if properties have a value:

Konsist
    .scopeFromProject()
    .properties()
    .assertTrue { it.hasValue() }

Assert if property has a given value:

Konsist
    .scopeFromProject()
    .properties()
    .assertTrue { it.value.startsWith("prefix") }

Filter properties with given value:

Konsist
    .scopeFromProject()
    .properties()
    .withValue { it.startsWith(“prefix”) }
    .assertTrue { ... }

7. Added withName/withoutName with predicate parameter

Now it is possible to call withName and withoutName with the specified predicate:

Konsist
    .scopeFromProject()
    .properties()
    .withName { it.startsWith(“prefix”) || it.endsWith(“suffix”) }
    .assertTrue { ... }

8. Improved hasX methods

We have deprecated all containsX methods and instead added some methods with has prefixes (like hasX, hasAllX etc.):

Konsist
    .scopeFromProject()
    .interfaces()
    .assertTrue { it.hasClasses() }
Konsist
    .scopeFromProject()
    .classes()
    .assertTrue { it.hasProperty { prop -> prop.hasValue() } }
Konsist
    .scopeFromProject()
    .classes()
    .assertTrue { it.hasAllProperties { prop -> prop.hasValue() } }

9. Improved Scope Creation

We are continuing our exploration of JGit and other APIs to enhance various development workflows e.g. a way to run Konsist Tests only on files modified in a given PR. We have added a new way of creating the scope:

// Create scope from paths
Konsist.scopeFromFiles("Repository.kt", "path\UseCase.kt")

// Create scope from set of paths
val paths = setOf("Repository.kt", "path\UseCase.kt")
Konsist.scopeFromFiles(paths)

What’s Next?

We are hearing the community feedback. The top 1 requested feature is the declaration references. We want to enable a way to retrieve parents (of a given class/interface/companion object) as Konsist declarations, rather than string names. You will be able to verify the properties of the parent in the Konsist test. This API is not finalized yet, but we are aiming to expose parents property containing a list of Konsist declarations:

 Konsist
    .scopeFromProject()
    .classes()
    .assertTrue { it.parents.any { parent -> parent.hasAnnotationOf<Repository> } 

We also want to take a look at architecture assertions. Exact changes are quite a vague ATM, but we have a few community-driven threads to process and rethink our approach. We will consider adding “optional/empty layers” and add a few other tweaks to the Konsist API.

Thank you for your engagement using Konsist 🙏 (if you got here you must be really engaged Konsist community member congratulations 🥳). If you are missing something let us know.

Complete list of ...

Read more