konsist
fun String.konsist(scope: FileConstraintRange = FileConstraintRange.Subtree, block: KonsistScope.() -> Unit)(source)
Declares a named constraint over the files the block around it matched, written with Konsist.
this is the name the report prints next to the role, so it is a sentence about the code ("is not exposed outside its feature") rather than an identifier. Where the call is written decides what it covers: directly on a role it covers the union of every layout { } that role declares, inside a directory block it covers that block's subtree and nothing else, or only the files that block declares directly with scope = FileConstraintRange.DirectOnly. That is FileConstraintScope's rule, not this function's — konsist { } is one backend among several and passes scope through unchanged.
The block is stored, not run. It runs when a check that evaluates constraints runs, which means architecture { } still reads no files and parses no Kotlin.
FileConstraintScope is taken as a context parameter rather than as a receiver so that the call reads the same as katachi's own layout vocabulary and needs no import of the scope type. architecture { } and group { } do not implement FileConstraintScope, so writing konsist { } there does not compile: a group holds roles, not files, so there would be no set of files for the block to be about.
Example 1: one rule, on one module
"UseCase" {
layout {
"core/domain" {
"is usable from outside".konsist {
classes().must { it.hasPublicOrDefaultModifier }
}
mainSourceSet / kotlin / "com/example/core/domain" / "useCase" / "*UseCase".ktFile()
}
}
}Example 2: one rule over everywhere the role lives
"UseCase" {
"has an invoke function".konsist {
classes().withNameEndingWith("UseCase")
.must { klass -> klass.hasFunction { it.name == "invoke" } }
}
layout { "core/domain" / "useCase" / "*UseCase".ktFile() }
}Example 3: a rule for one directory that its subdirectories do not inherit
"Util" {
layout {
"util" {
"does not use the KSP API".konsist(scope = FileConstraintRange.DirectOnly) {
files.must { file -> file.imports.none { it.name.startsWith("com.google.devtools.ksp") } }
}
"*".ktFile()
"ksp" { "*".ktFile() } // not covered
}
}
}Parameters
how far below the surrounding directory block it reaches: FileConstraintRange.Subtree (the default) or FileConstraintRange.DirectOnly. See FileConstraintScope.fileConstraint.
what to ask of the covered files. See KonsistScope.
Throws
when the name is blank or holds a line break — it is printed on one line of a report block.
when the role it is written on declares no layout { }, so nothing could ever be covered.
when scope = FileConstraintRange.DirectOnly is written outside a directory block.
when scope = FileConstraintRange.DirectOnly is written in a block that declares no file directly.
fun konsist(scope: FileConstraintRange = FileConstraintRange.Subtree, block: KonsistScope.() -> Unit)(source)
Declares an unnamed constraint over the files the block around it matched, written with Konsist.
Identical to the named form except that the report identifies it by its declaration site alone. Worth it when the block is short enough to read as its own name, and when there is only one constraint in that place; a report that has to say which of three rules failed reads better with a name.
Example 1: the only rule of one directory
"UseCase" {
layout {
"core/domain/useCase" {
konsist { classes().must { it.hasInternalModifier } }
"*UseCase.kt".file()
}
}
}Parameters
how far below the surrounding directory block it reaches, as for the named form.
what to ask of the covered files. See KonsistScope.
Throws
when the role it is written on declares no layout { }, so nothing could ever be covered.
when scope = FileConstraintRange.DirectOnly is written outside a directory block.
when scope = FileConstraintRange.DirectOnly is written in a block that declares no file directly.