Skip to content

v1.0.0-beta.1

Latest

Choose a tag to compare

@igorwojda igorwojda released this 04 Nov 06:51
464bbed

This is Konsist’s biggest release to date. It brings major performance improvements, additional context for AI, official support for nested modules, better Windows support, and a long list of fixes for edge cases.

⚠️ This is the final release before 1.0.0, so we’ve taken a more liberal approach to breaking changes than usual. After 1.0.0, the API should remain stable. Thats being said I’ve aimed to keep the key APIs unchanged while fixing as many bugs as possible. Be sure to check the Migration guide.

Some of your Konsist tests may fail after upgrading. In most cases, that doesn’t mean Konsist is broken: many APIs returned incorrect results in edge cases, such as wrong fully qualified names, missing library parents, type checks based on text, or files silently skipped by architecture checks. These issues are now fixed, so the APIs are stricter and some return values have changed. If you think you’ve found a regression, please report it.

Table of Contents

Community Feedback & What's Next

This beta is the API freeze candidate for 1.0.0. Please upgrade, run your Konsist tests and tell me what broke. Report issues and regressions in the #konsist channel or Github Discussions. Your feedback decides what lands in 1.0.0.

Key changes

Performance

Konsist 1.0.0-beta.1 vs 0.18.1 on ktor (same Kotlin files scanned by both versions):

Metric 0.18.1 1.0.0-beta.1 Change
Smallest -Xmx for a typical test suite 160 MB 128 MB −20%
Heap retained after the suite 155.5 MB 110.8 MB −29%
Suite wall time after warm-up (median) 0.56 s 0.49 s −13%
sourceDeclaration of all parameter and return types 37.4 s 0.24 s 150× faster
sourceDeclaration of all property types 15.8 s 0.08 s 190× faster
sourceDeclaration of all imports 12.4 s 0.03 s 400× faster
children() 1.57 s 0.09 s 17× faster
children(indirectChildren = true) 2.45 s <0.01 s >200× faster

Projects with Kotlin files outside the module src directory (test data, scripts) gain more, because 1.0.0-beta.1 no longer scans these files in project scopes. On ktor as-is, the smallest -Xmx drops from 224 MB to 128 MB for the suite and from 256 MB to 32 MB for module scopes with type resolution.

On the Kotlin repository (15,177 Kotlin files, same files scanned by both versions):

Metric 0.18.1 1.0.0-beta.1 Change
Smallest -Xmx for a typical test suite 1,792 MB 1,280 MB −29%
Suite run 4 times in one JVM, total (first run includes parsing and type resolution) 150–156 s 31 s 5× faster
Suite wall time after warm-up (median) 6.5–6.7 s 5.4 s −18%

On the Kotlin repository as-is (67,000+ Kotlin files, most of them test data outside src), a single run of the same suite drops from 23.6 minutes to 21 seconds: 1.0.0-beta.1 skips the test data files and resolves types much faster.

Konsist tests now run faster and need less memory, so CI is faster and large projects are less likely to hit OutOfMemoryError:

  • Less memory: the directory scope memory leak is fixed (every scopeFromDirectory call kept its files in memory), and parsed files that are no longer used are released when memory runs low.
  • Parse only what is needed: scopeFromProduction, scopeFromTest and scopeFromPackage filter files before parsing, and type resolution in a module or directory scope parses only the files it needs instead of the whole project.
  • Faster queries: parents(indirectParents = true) for a chain of 20 classes went from 8.5 s to <1 ms, children() of ~3,000 declarations from 211 s to 0.14 s, and resolving all 15,301 ktor imports from >60 s to 0.04 s.
  • Faster project scan: directories are scanned in parallel (−64% scan time on the Kotlin repository).

Measured with konsist-memory-benchmark.

Nested Modules

Nested modules are now officially supported. Module names accept the .. wildcard (any number of module directories, including none), the same way package names do:

// project modules: :feature, :feature:payment, :feature:payment:api, :feature:auth:data
Konsist.scopeFromModule(":feature..")    // feature + all nested modules
Konsist.scopeFromModule("..data")        // every module named "data"
Konsist.scopeFromModule("feature..data") // "data" modules nested anywhere under feature
Konsist.scopeFromModule("..")            // all modules

Konsist.scopeFromProject().classes().withModule(":feature..")

Gradle-style module paths are supported too. All of these mean the same module:

Konsist.scopeFromModule(":feature:auth") // preferred
Konsist.scopeFromModule("feature:auth")
Konsist.scopeFromModule("feature/auth")
Konsist.scopeFromModule("feature\\auth")
// before: Gradle-style paths returned an empty scope
// after:  files of the feature/auth module

This works everywhere a module name is accepted: scopeFromModule(s), the moduleName parameter of scopeFromProject / scopeFromPackage / scopeFromProduction / scopeFromTest, residesInModule, withModule and withoutModule. A module that does not exist still returns an empty scope.

Module and source set detection was fixed for many project layouts: files outside the src directory, modules located in a directory named src, test fixture projects located in resources or testData, buildSrc, and subfolders with their own Gradle wrapper. See Behaviour changes.

Improved Compatibility

Konsist targets JVM 11 again and publishes kotlin-stdlib 2.0.21, so it works with more projects:

0.18.1 1.0.0-beta.1
Java (JVM) 17+ 11+
Kotlin (Android projects with AGP 8) 2.3+ 1.9+

See the Compatibility page for details.

When another Kotlin compiler is on the test classpath (e.g. kotlin-compiler, Android Lint), the parse error now explains the conflict instead of throwing a cryptic ClassCastException:

KoInternalException: Failed to parse Kotlin file: /project/src/test/kotlin/ReproTest.kt
Multiple Kotlin compilers found on the classpath:
 - ~/.gradle/caches/.../kotlin-compiler-2.4.21.jar
 - ~/.gradle/caches/.../kotlin-compiler-embeddable-2.4.21.jar
Konsist requires only 'kotlin-compiler-embeddable'.

Windows Support

  • Every API that takes a path accepts both / and \ on every OS. Path outputs use the OS separator.
  • moduleName always uses / (e.g. feature/data), so moduleName == "feature/data" works on every OS.
  • File links in failure messages are clickable in IntelliJ on Windows (file:///C:/project/...).
  • Files saved with a UTF-8 BOM or with \r line endings are parsed correctly (before, their package was null or location threw).

Context Parameters

Kotlin context parameters can now be verified on functions and properties:

// Snippet
interface UserRepository {
    context(logger: Logger)
    fun getUser(id: String): User
}

// Konsist test
Konsist
    .scopeFromProject()
    .interfaces()
    .withNameEndingWith("Repository")
    .functions()
    .assertTrue { it.hasContextParameter { parameter -> parameter.hasTypeOf(Logger::class) } }

Context receivers (context(Logger)) are not supported: they were an experimental feature replaced by context parameters, and Kotlin 2.4 no longer compiles them.

Failure Messages That Explain The Rule

Failure messages can now explain what is wrong and how to fix it. This helps developers, and it helps AI agents fix violations without reading the test.

addFailureReason adds a reason for each failing declaration:

Konsist
    .scopeFromProject()
    .classes()
    .withModifier(KoModifier.DATA)
    .assertTrue { clazz ->
        val ctorParams = clazz.primaryConstructor?.parameters?.map { it.name }.orEmpty().toSet()
        val missing = clazz.properties().map { it.name } - ctorParams

        if (missing.isNotEmpty()) addFailureReason("Properties outside constructor: ${missing.joinToString()}")

        missing.isEmpty()
    }
Assert 'data classes have all properties in constructor' was violated (2 times). Invalid declarations:
├── Class User file:///.../User.kt:3:1
│   └── Properties outside constructor: createdAt, cache
└── Class Order file:///.../Order.kt:5:1
    └── Properties outside constructor: total

KoRule describes the rule itself providing more context for AI to fix it. All assertions and assertArchitecture accept it:

Konsist
    .scopeFromProject()
    .classes()
    .withNameEndingWith("ViewModel")
    .assertTrue(
        rule = KoRule(
            title = "ViewModels must extend BaseViewModel.",
            rationale = "Lifecycle handling lives in BaseViewModel.",
            howToFix = "Change the parent class to BaseViewModel.",
            goodExample = "class LoginViewModel : BaseViewModel()",
            badExample = "class LoginViewModel : ViewModel()",
        ),
    ) { it.hasParentClassWithName("BaseViewModel") }
Assert 'myTest' was violated (1 time).
RULE: ViewModels must extend BaseViewModel.
WHY: Lifecycle handling lives in BaseViewModel.
HOW TO FIX: Change the parent class to BaseViewModel.
GOOD EXAMPLE:
class LoginViewModel : BaseViewModel()
BAD EXAMPLE:
class LoginViewModel : ViewModel()
Invalid declarations:
└── Class LoginViewModel …/LoginViewModel.kt:3:1

Architecture Assertions

⚠️ Breaking change. assertArchitecture is stricter: before, some files and dependencies were silently not checked.

Every file in the scope must now reside in exactly one layer. Files outside every layer fail by default:

All files are required to reside in a layer, but these files reside in no layer:
├── File .../NoPackageClass.kt
└── File .../util/UtilClass.kt

Declare the missing packages as layers, narrow the scope, or restore the old behaviour:

scope.assertArchitecture(allowFilesOutsideLayers = true) {
    domain.dependsOnNothing()
    data.dependsOn(domain)
}

Other changes:

  • Layers that share files (e.g. com.app.. and com.app.domain..) fail with Layers overlap. Dependencies between overlapping layers were never checked.
  • A layer with dependsOn(...) fails again when it depends on a declared layer that is not on its list (regression from 0.17.0).
  • New Layer(..., optional = true): a layer without files is allowed (e.g. a feature that has no presentation package yet).
  • rootPackage without trailing .. covers a single package, so a root app package no longer overlaps the layers below it:
val app = Layer("App", "com.example")             // com.example only
val domain = Layer("Domain", "com.example.domain..")
  • Failure output lists only the imports that break the rule, not every import of the file.
  • Equal Layer instances count as one layer, cycles not including the first layer report Circular dependency detected instead of an internal error, and imports are matched by package (class names no longer match layer packages).

Kotest And JUnit 5 Dynamic Tests

Register the new extensions to get correct test names in failure messages and working @Suppress("konsist.<test name>"), without passing testName to every assertion:

// Kotest 6
class ProjectConfig : AbstractProjectConfig() {
    override val extensions = listOf(KoKotestExtension)
}

// JUnit 5 dynamic tests
@ExtendWith(KoJUnitExtension::class)
class UseCaseKonsistTest {
    @TestFactory
    fun `use case test`() = useCases.map { useCase ->
        dynamicTest("${useCase.name} should have test") {
            useCase.assertTrue { it.hasTestClasses() } // message: 'MyUseCase should have test'
        }
    }
}

@Suppress is now parsed from the annotation syntax tree, so @Suppress(names = ["konsist.x"]), @Suppress(*arrayOf(...)) and @file:kotlin.Suppress(...) work. Suppression also works for packages, annotations, KDocs and type arguments.

New APIs

  • Explicit backing fields:
// Snippet
class SearchViewModel : ViewModel() {
    val state: StateFlow<Int>
        field = MutableStateFlow(0)
}

// Konsist test
Konsist
    .scopeFromProject()
    .classes()
    .withParentClassOf(ViewModel::class)
    .properties()
    .withTypeOf(StateFlow::class)
    .assertTrue { it.hasExplicitBackingField }
  • Built-in delegates: hasLazyDelegate(mode), hasObservableDelegate, hasVetoableDelegate, hasNotNullDelegate, hasPropertyReferenceDelegate and matching with* / without* list extensions:
Konsist
    .scopeFromProject()
    .properties()
    .assertFalse { it.hasLazyDelegate(LazyThreadSafetyMode.NONE) }
  • isImmutableType and hasImmutableTypeArgument. isMutableType now resolves aliases and knows mutable types like ArrayList, arrays and java.util.*. Classes with val properties of mutable types count as mutable:
Konsist
    .scopeFromProject()
    .classes()
    .withNameEndingWith("UiState")
    .properties()
    .assertTrue { it.type?.isImmutableType == true }
  • body on functions, getters and setters (block body without braces, or the expression)
  • hasTestClass { ... } and friends for functions (e.g. @Composable functions)
  • files.withName("MyClass.kt"): file name with extension matches

Notable Bug Fixes

  • Parents from libraries and the Kotlin standard library are now classes and interfaces: withParentClassOf(ComponentActivity::class) returns [MainActivity] instead of [], and parents(indirectParents = true) continues through library parents (MyException : IllegalStateException() has parent Throwable)
  • KClass checks match type aliases (RuntimeException, ArrayList, HashMap, MutableList), import aliases and generic type arguments (hasReturnTypeOf<List<String>>() no longer matches List<Int>)
  • Fully qualified names are resolved correctly: project types before Kotlin default imports (com.app.Result instead of kotlin.Result), types nested in superclasses, nested annotations used without qualifier, no more suffix matching (Test resolved to UnitTest)
  • hasTacitType uses the constructor call instead of text search (val x = FooBar() no longer matches Bar)
  • type of extension properties returns the declared type instead of the receiver type
  • isGeneric, isNullable and isFunctionType use the syntax tree instead of text (List<Item2> is generic, List<(Int) -> Unit> is not a function type)
  • hasMatchingPackage / hasMatchingPath compare whole directories (com.app no longer matches com/application)
  • KDoc text and description keep all content (e.g. john@example.com no longer cuts the description)
  • scopeFromDirectory skips build, hidden and node_modules directories, += / -= refresh cached scope values, scopes contain each file once, and a cancelled project scan (e.g. JUnit @Timeout) no longer breaks later scopes
  • assertNotNull / assertNotEmpty no longer fail for suppressed declarations, and only @Suppress annotations are skipped during assertions

Migration guide: 1.0.0-beta.1 breaking changes

All APIs deprecated in 0.18.x were either removed or are no longer deprecated, so 1.0.0-beta.1 has no deprecated APIs. Removed APIs fail to compile, so the compiler shows every place to update.

Removed resideIn* methods

The resideIn* / resideOutside* provider methods have been removed. Use the residesIn* / residesOutside* methods instead. Only the name changed; behaviour is the same. residesIn* is new in this release, so there is no deprecation step: builds fail until call sites are renamed.

Removed Use instead
resideInPackage(name) residesInPackage(name)
resideOutsidePackage(name) residesOutsidePackage(name)
resideInPath(path, absolutePath) residesInPath(path, absolutePath)
resideInModule(name) residesInModule(name)
resideInSourceSet(sourceSetName) residesInSourceSet(sourceSetName)
// Before:
Konsist.scopeFromProject().classes().assertTrue { it.resideInPackage("..usecase..") }
// After:
Konsist.scopeFromProject().classes().assertTrue { it.residesInPackage("..usecase..") }

Modifier and generic providers

Removed Use instead
KoValModifierProvider, hasValModifier KoIsValProvider, isVal
withValModifier() / withoutValModifier() withVal() / withoutVal()
KoVarModifierProvider, hasVarModifier KoIsVarProvider, isVar
withVarModifier() / withoutVarModifier() withVar() / withoutVar()
KoIsGenericTypeProvider, isGenericType KoIsGenericProvider, isGeneric
withGenericType() / withoutGenericType() withGeneric() / withoutGeneric()
KoSourceAndAliasTypeProvider, isAlias KoSourceTypeProvider, isImportAlias

External type declarations

Removed Use instead
isExternalType isExternal
asExternalTypeDeclaration() asExternalDeclaration()
hasExternalTypeDeclaration { } / hasExternalTypeDeclarationOf(kClass) hasExternalDeclaration { } / hasExternalDeclarationOf(kClass)
withExternalTypeDeclaration { } / withoutExternalTypeDeclaration { } withExternalDeclaration { } / withoutExternalDeclaration { }
withExternalTypeDeclarationOf(...) / withoutExternalTypeDeclarationOf(...) withExternalDeclarationOf(...) / withoutExternalDeclarationOf(...)

Note: the old hasExternalTypeDeclaration(predicate) ignored predicate, so results may change after migrating.

Function type parameters

Removed Use instead
parameterTypes (also on List<KoFunctionTypeDeclarationProvider>) parameters
numParameterTypes numParameters
countParameterTypes { } countParameters { }
hasParameterType { } hasParameter { }
hasAllParameterTypes { } hasAllParameters { }
withParameterType / withoutParameterType withParameter / withoutParameter
withAllParameterTypes / withoutAllParameterTypes withAllParameters / withoutAllParameters
withParameterTypes / withoutParameterTypes withParameters / withoutParameters

Enum constant local declarations and variables

KoEnumConstantDeclaration is no longer a KoLocalClassProvider, KoLocalFunctionProvider, KoLocalDeclarationProvider or KoVariableProvider. Use the class, function, declaration and property providers added in 0.18.0 (the same applies to the List<KoEnumConstantDeclaration> extensions):

Removed Use instead
localClasses, numLocalClasses, countLocalClasses { } classes(), numClasses(), countClasses { }
hasLocalClasses(), hasLocalClass { }, hasAllLocalClasses { } hasClasses(), hasClass { }, hasAllClasses { }
hasLocalClassWithName, hasLocalClassesWithAllNames hasClassWithName, hasClassesWithAllNames
localFunctions, numLocalFunctions, countLocalFunctions { } functions(), numFunctions(), countFunctions { }
hasLocalFunctions(), hasLocalFunction { }, hasAllLocalFunctions { } hasFunctions(), hasFunction { }, hasAllFunctions { }
hasLocalFunctionWithName, hasLocalFunctionsWithAllNames hasFunctionWithName, hasFunctionsWithAllNames
localDeclarations, numLocalDeclarations, countLocalDeclarations { } declarations(), numDeclarations(), countDeclarations { }
hasLocalDeclarations(), hasLocalDeclaration { }, hasAllLocalDeclarations { } hasDeclarations(), hasDeclaration { }, hasAllDeclarations { }
variables, numVariables, countVariables { } properties(), numProperties(), countProperties { }
hasVariables(), hasVariable { }, hasAllVariables { } hasProperties(), hasProperty { }, hasAllProperties { }
hasVariableWithName, hasVariablesWithAllNames hasPropertyWithName, hasPropertiesWithAllNames
withLocalClass* / withoutLocalClass* list extensions withClass* / withoutClass*
withLocalFunction* / withoutLocalFunction* list extensions withFunction* / withoutFunction*
withLocalDeclaration* / withoutLocalDeclaration* list extensions withDeclaration* / withoutDeclaration*

No longer deprecated

These APIs were marked "Will be removed in version 0.19.0". They stay, and the deprecation warnings are gone:

  • KoTypeDeclaration type checks and casts: isClass, isInterface, isObject, isKotlinType, isExternal, asClassDeclaration(), hasClassDeclaration { }, hasClassDeclarationOf(kClass) and the other is* / as* / has*Declaration* members
  • List<KoTypeDeclaration> extensions: classDeclarations(), withClassDeclaration { }, withClassDeclarationOf(...) and the other *Declarations() / with*Declaration* / without*Declaration* extensions
  • List<KoDeclarationCastProvider> properties: classDeclarations, objectDeclarations, interfaceDeclarations, typeAliasDeclarations, importAliasDeclarations, kotlinTypeDeclarations, externalTypeDeclarations

Signature changes

Change Migration
List<KoCompanionObjectProvider>.companionObjects property removed (it returned different results than companionObjects()) companionObjects(includeNested = false) for the old result, companionObjects() to include nested companion objects
typeArguments is non-null (List<KoTypeArgumentDeclaration>) Replace typeArguments?. / typeArguments.orEmpty() with typeArguments
Function type parameters is non-null (List<KoParameterDeclaration>) Replace parameters?. with parameters
The assertTrue / assertFalse lambda is now KoAssertScope.(E) -> Boolean? No change for most code. Inside the lambda, this refers to KoAssertScope
New rule, allowFilesOutsideLayers and Layer.optional parameters Source compatible. Libraries compiled against 0.18.x must be recompiled (NoSuchMethodError otherwise)

Behaviour changes

These APIs return different, more correct results. Tests that relied on the old results may fail:

  • ‼️ assertArchitecture fails for files outside every layer (opt out with allowFilesOutsideLayers = true), for overlapping layers, and for dependencies on declared layers missing from dependsOn(...)
  • ‼️ Project scopes (scopeFromProject, scopeFromModule, scopeFromProduction, scopeFromTest, ...) contain only files located in the module src directory. Directory and file scopes still return other files
  • ‼️ moduleName of files outside src is their directory (samples/A.kt → samples, root files → root), source sets are computed from the module directory, and projects located in resources or other source set directories belong to the containing module
  • ‼️ A folder with gradlew but without settings.gradle(.kts) is no longer a project root
  • ‼️ Parents from libraries and the Kotlin standard library are included in parentClass, parentClasses(), parentInterfaces() and parents(indirectParents = true)
  • ‼️ KClass checks compare fully qualified names (type aliases, import aliases and generic type arguments are taken into account), hasTacitType uses the constructor call, extension property type is the declared type
  • ‼️ isGeneric, isNullable, isFunctionType, isMutableType and isImmutableType return different results for some types; KoTypeParameterDeclaration.name returns only the name (T instead of out T : Number)
  • ‼️ Type argument filters with empty input match only declarations that have type arguments
  • ‼️ scopeFromDirectory skips build, hidden and node_modules directories
  • ‼️ property.value is null for properties with explicit backing fields (use explicitBackingField?.value)

Complete List Of Changes

⚠️ Breaking API Changes

  • Remove deprecated APIs before 1.0.0 (see Migration guide)
    • Change deprecation removal version from 1.0.0-beta.1 to 1.0.0-rc.1 by @igorwojda in #2022
    • Remove deprecated KoValModifierProvider by @igorwojda in #2067
    • Remove deprecated KoIsGenericTypeProvider by @igorwojda in #2068
    • Remove deprecated isExternalType API by @igorwojda in #2069
    • Remove deprecated KoFunctionTypeDeclarationProvider.hasParameterType by @igorwojda in #2070
    • Remove deprecated List.parameterTypes by @igorwojda in #2071
    • Remove deprecated KoVarModifierProvider by @igorwojda in #2072
    • Remove deprecated KoSourceAndAliasTypeProvider by @igorwojda in #2073
    • Remove deprecated KoFunctionTypeDeclarationProvider parameterTypes APIs by @igorwojda in #2074
    • Remove deprecated List parameter type filters by @igorwojda in #2075
    • Remove deprecated List local class extensions by @igorwojda in #2077
    • Remove deprecated List local declaration extensions by @igorwojda in #2078
    • Remove deprecated List local function extensions by @igorwojda in #2079
    • Remove deprecated local and variable providers from KoEnumConstantDeclaration by @igorwojda in #2080
    • Remove deprecated local and variable provider implementations from KoEnumConstantDeclarationCore by @igorwojda in #2081
    • Remove List.companionObjects property in favor of companionObjects() by @igorwojda in #2108
  • Rename resideIn* methods to residesIn*
    • Add residesIn* methods and deprecate resideIn* methods by @igorwojda in #1985
    • Remove resideIn* methods deprecated for removal in 1.0.0-rc1 by @igorwojda in #2088
  • Stricter assertArchitecture
    • Fail dependsOn when layer depends on undeclared layer by @igorwojda in #1953
    • Fail architecture assertion when a file belongs to more than one layer by @igorwojda in #1988
    • Require every file to reside in exactly one layer in assertArchitecture by @igorwojda in #1989
  • Make type arguments non-null and align empty-input filters by @igorwojda in #2101
  • Use syntax tree instead of text in type providers by @igorwojda in #2094

🐛 API Bug Fixes

  • Type resolution
    • Fix wrong fully qualified names from suffix matching and external cache by @igorwojda in #1968
    • Resolve project types before Kotlin default imports by @igorwojda in #1978
    • Resolve types nested in superclasses by @igorwojda in #1981
    • Include library and Kotlin parents in parent classes and interfaces by @igorwojda in #1983
    • Include library and Kotlin parents of library parents in indirect parents by @igorwojda in #1986
    • Match Kotlin type aliases in KClass type checks by @igorwojda in #1963
    • Compare generic type arguments in reified type checks by @igorwojda in #1964
    • Match KClass by fully qualified name and resolve import aliases by @igorwojda in #2106
    • Fix withRepresentedTypeOf(KClass) for parameters and context parameters by @igorwojda in #2105
    • Match source type KClass filters by fully qualified name by @igorwojda in #2058
    • Compare fully qualified names of Kotlin annotations in hasAnnotationOf by @igorwojda in #1994
    • Resolve nested annotations used without qualifier by @igorwojda in #2107
    • Fix name, annotations and source declaration of annotated nullable types by @igorwojda in #2112
    • Infer tacit type from constructor call instead of text search by @igorwojda in #1982
    • Fix KoPropertyDeclaration.type for extension properties by @igorwojda in #2002
  • Mutable and immutable types
    • Resolve aliases and known mutable types in isMutableType by @igorwojda in #1967
    • Treat classes with val properties of mutable types as mutable by @igorwojda in #1997
    • Report only known immutable external types as immutable by @igorwojda in #1998
    • Treat Any and open types as not immutable by @igorwojda in #2000
  • Architecture assertions
    • Treat equal Layer instances as the same layer by @igorwojda in #1956
    • KON-684 List only violating imports in architecture failure output by @igorwojda in #1957
    • Fix circular dependency detection for cycles not including first layer by @igorwojda in #2092
    • Match imported declaration package against architecture layers by @igorwojda in #2093
  • Suppression
  • Paths and scopes
    • Use OS separator in hasMatchingPackage so it works on Windows by @igorwojda in #1928
    • Escape project root path in scope regex so folders with brackets work by @igorwojda in #1930
    • Ignore leading and trailing slashes in module and source set scope names by @igorwojda in #1936
    • Treat top-level module named like root directory as regular module by @igorwojda in #1940
    • Match path patterns that start with a separator by @igorwojda in #1948
    • Support project root at drive root or filesystem root by @igorwojda in #1949
    • Skip subfolders with own Gradle wrapper when resolving project root by @igorwojda in #2018
    • Refresh cached scope values after plusAssign and minusAssign by @igorwojda in #2027
    • Skip build, hidden and node_modules directories in directory scopes by @igorwojda in #2098
    • Retry project scan after cancelled or failed scan by @igorwojda in #2099
    • Deduplicate scope files and normalize scope input paths by @igorwojda in #2100
    • Fix hasMatchingPackage and hasMatchingPath matching part of the path by @igorwojda in #2102
  • Parsing and KDoc
    • Strip UTF-8 BOM before parsing Kotlin files by @igorwojda in #1942
    • Normalize CR and CRLF line endings before parsing Kotlin files by @igorwojda in #2104
    • Fix KDoc text and description losing content by @igorwojda in #2096
    • Skip KDoc tags without value instead of throwing ClassCastException by @igorwojda in #2056
    • Ignore nested class properties in hasValidKDocPropertyTags by @igorwojda in #2057
  • Pass indirectChildren through in hasChildOf and hasAllChildrenOf vararg overloads by @igorwojda in #2055
  • Fix duplicated local declarations in function scope by @igorwojda in #2110

💡 API Improvements

  • Nested modules support
    • Support Gradle-style paths in module scopes and throw for invalid module names by @igorwojda in #1941
    • Return empty scope instead of throwing for invalid module names by @igorwojda in #1952
    • Support .. wildcard in module names by @igorwojda in #2034
    • Use file directory as module name for files outside src directory by @igorwojda in #2030
    • Include files outside src directory in module scopes by @igorwojda in #2033
    • Detect modules located in directory named src by @igorwojda in #2040
    • Use closest module directory as module of files outside src directory by @igorwojda in #2041
    • Use closest module directory as module of files outside source root by @igorwojda in #2042
    • Use module directory to compute source set and filter source set scopes by @igorwojda in #2043
    • Treat resources directory as source root by @igorwojda in #2044
    • Ignore files outside src directory in project scopes by @igorwojda in #2048
    • Treat projects located in source set directories as part of the containing module by @igorwojda in #2051
    • Ignore build directories of modules located in src directory by @igorwojda in #2052
    • Detect buildSrc by directory name relative to project root by @igorwojda in #2032
    • Reject module names with more than two consecutive dots by @igorwojda in #2046
    • Add nested module scope tests to declaration tester by @igorwojda in #1934
    • Add tests for slash-trimmed module names in resideInModule and withModule by @igorwojda in #1946
    • Add tests for nested modules by @igorwojda in #1972
    • Test sibling nested modules with the same name in module scope by @igorwojda in #2007
    • Test parent module with own sources and nested module by @igorwojda in #2031
    • Add end-to-end test for module of files outside src directory by @igorwojda in #2045
  • Explain assertion failures
  • Add context parameters support
  • Kotest and JUnit 5 dynamic test names
  • Add built-in delegate checks
    • Add built-in delegate checks to KoDelegateProvider by @igorwojda in #1962
    • Add built-in delegate filters to KoDelegateProvider list extensions by @igorwojda in #2039
  • Add isImmutableType and hasImmutableTypeArgument
  • Keep type cast APIs (deprecation removed)
    • Remove deprecation from sourceDeclaration-replaced APIs by @igorwojda in #2087
    • Remove 0.19.0 deprecation from declaration cast list extensions by @igorwojda in #2089
  • Allow optional layers without files in architecture assertion by @igorwojda in #1987
  • Support single package layers by @igorwojda in #2111
  • Add explicit backing field support by @igorwojda in #2038
  • Add body property to KoBodyProvider by @igorwojda in #1995
  • Add test class lookup for functions by @igorwojda in #1984
  • Allow file name with extension in hasName and withName by @igorwojda in #1954

💡 Improvements

  • Performance and memory
    • Reduce memory usage and fix directory scope memory leak by @igorwojda in #1960
    • Speed up type resolution, parents and children, and release unused parsed files by @igorwojda in #1975
    • Scan project directories in parallel when collecting Kotlin files by @igorwojda in #2023
    • Check file name before file system when scanning for Kotlin files by @igorwojda in #2024
    • Filter scopeFromProduction and scopeFromTest by source set before parsing by @igorwojda in #2028
    • Filter scopeFromPackage files by package before parsing by @igorwojda in #2029
    • Cache module name matchers by @igorwojda in #2047
  • Windows support
    • Normalize moduleName separators to / on Windows by @igorwojda in #1926
    • Use clickable file:/// links and / separators in output paths on Windows by @igorwojda in #1927
    • Accept / and \ in path inputs and use OS separators in path outputs by @igorwojda in #1935
  • Compatibility
    • Publish Konsist for JVM 11 again by @igorwojda in #2017
    • Publish kotlin-stdlib 2.0.21 to support older consumer Kotlin versions by @igorwojda in #2019
  • Explain Kotlin compiler classpath conflicts in parse errors by @igorwojda in #1966

📕 Documentation

🏗️ Maintenance

Full Changelog: v0.18.1...v1.0.0-beta.1