Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .agents/coding-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
- **Generic parameters** over explicit variable types (`val list = mutableList<Dependency>()`)
- **Java interop annotations** only when needed (`@file:JvmName`, `@JvmStatic`)
- **Kotlin DSL** for Gradle files
- **Kotlin Protobuf DSL** (`myMessage { field = value }`) over Java builder chains

### ❌ Avoid
- Mutable data structures
- Java-style verbosity (builders with setters)
- Java Protobuf builders in Kotlin code (`newBuilder()`, `toBuilder()`) unless interop requires them
- Redundant null checks (`?.let` misuse)
- Using `!!` unless clearly justified
- Type names in variable names (`userObject`, `itemList`)
Expand Down
5 changes: 5 additions & 0 deletions .agents/documentation-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
- When using TODO comments, follow the format on the [dedicated page][todo-comments].
- File and directory names should be formatted as code.

## Protobuf file headers
- In `.proto` files, a multi-paragraph documentation header must end with a
trailing empty comment line (`//`).
- Single-paragraph headers do not require the trailing empty comment line.

## Avoid widows, runts, orphans, or rivers

Agents should **AVOID** text flow patters illustrated
Expand Down
1 change: 1 addition & 0 deletions .agents/skills/kotlin-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ live in `.agents/`:
nullability, and idiomatic refactors require surrounding context.
3. Check against `.agents/coding-guidelines.md`:
- Kotlin idioms (extension functions, `when`, smart casts, data/sealed classes).
- Kotlin Protobuf DSL (`message { ... }`) preferred over Java builders (`newBuilder()`, `toBuilder()`) in Kotlin.
- Immutability by default.
- No `!!` without justification.
- No type names in variable names.
Expand Down
4 changes: 4 additions & 0 deletions .agents/skills/review-docs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ The authoritative standards live in `.agents/`:
`git diff <base>...HEAD` depending on what the user describes. Restrict
to files matching:
- `**/*.kt`, `**/*.kts`, `**/*.java` (for KDoc/Javadoc inside sources)
- `**/*.proto` (for file-level documentation headers)
- `**/*.md` (Markdown docs)
Do **not** review the full repo — only what changed.
Filter out config-distributed files (see `AGENTS.md § Code review` for the
Expand Down Expand Up @@ -68,6 +69,9 @@ The authoritative standards live in `.agents/`:
`// TODO: …` without owner/issue reference is a Should-fix.
- **File and directory names rendered as code.** Within KDoc/Javadoc prose,
`path/to/file.kt` and `module-name` must use backticks.
- **Multi-paragraph Protobuf headers end with an empty comment line.** In
`.proto` files, if the file-level documentation header has more than one
paragraph, it must end with a trailing empty comment line (`//`).

### B. Markdown docs

Expand Down
7 changes: 5 additions & 2 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ Additional guidelines are in `.agents/` — see `.agents/_TOC.md` for the index

## Do not review

If the current repository is `config`, review these files normally: they are
authoritative there. In other repositories, the following files are managed by
Never review `gradlew` or `gradlew.bat` in any repository, including `config`.
These files are provided by Gradle and are not edited manually.

If the current repository is `config`, review its files normally unless noted
above: they are authoritative there. In other repositories, the following files are managed by
the `config` submodule and must be reviewed in the `config` repository, not
here. In those consumer repositories, skip them without comment:

Expand Down
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,17 @@ Ruthlessly iterate until mistakes stop repeating.

## Code review

Never review `gradlew` or `gradlew.bat` in any repository, including `config`.
These files are provided by Gradle and are not edited manually.

When reviewing a pull request or diff in a consumer repository, skip any
file that the `config` module distributes. Those files belong in a review
of the `config` repo, not the consumer repo — reviewing them there adds
noise without value.

Do **not** apply this skip rule when reviewing the `config` repository
itself. In `config`, these files are source files owned by the current
repo and must be reviewed normally.
repo and must be reviewed normally, except `gradlew` and `gradlew.bat`.

In consumer repositories, skip without comment any path matching:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ object CoreJvmCompiler {
/**
* The version used in the build classpath.
*/
const val dogfoodingVersion = "2.0.0-SNAPSHOT.066"
const val dogfoodingVersion = "2.0.0-SNAPSHOT.067"

/**
* The version to be used for integration tests.
*/
const val version = "2.0.0-SNAPSHOT.066"
const val version = "2.0.0-SNAPSHOT.067"

/**
* The ID of the Gradle plugin.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ object Validation {
/**
* The version of the Validation library artifacts.
*/
const val version = "2.0.0-SNAPSHOT.443"
const val version = "2.0.0-SNAPSHOT.444"

/**
* The last version of Validation compatible with ProtoData.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025, TeamDev. All rights reserved.
* Copyright 2026, TeamDev. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -35,8 +35,8 @@ import org.gradle.api.file.FileTree
import org.gradle.api.tasks.SourceSetOutput

/**
* Serves to distinguish the `.java` and `.class` files built on top of the Protobuf definitions
* from the human-created production code.
* Serves to distinguish the generated `.java` and `.kt` files (and the `.class` files
* compiled from them) from the human-created production code.
*
* Works on top of the passed [source][srcDirs] and [output][outputDirs] directories, by analyzing
* the source file names and finding the corresponding compiler output.
Expand Down Expand Up @@ -70,26 +70,15 @@ internal class CodebaseFilter(
return humanProducedTree
}

private fun generatedClassNames(): List<String> {
val generatedSourceFiles = generatedOnly(srcDirs)
val generatedNames = mutableListOf<String>()
generatedSourceFiles
private fun generatedClassNames(): List<String> =
generatedOnly(srcDirs)
.filter { it.exists() && it.isDirectory }
.forEach { folder ->
folder.walk()
.flatMap { root ->
root.walk()
.filter { !it.isDirectory }
.forEach { file ->
file.parseName(
File::asJavaClassName,
File::asGrpcClassName,
File::asSpineClassName
)?.let { clsName ->
generatedNames.add(clsName)
}
}
.flatMap { it.classNamesIn(root) }
.toList()
}
return generatedNames
}

private fun log(message: String) {
project.logger.info(message)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025, TeamDev. All rights reserved.
* Copyright 2026, TeamDev. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -36,6 +36,11 @@ internal enum class FileExtension(val value: String) {
*/
JAVA_SOURCE(".java"),

/**
* Extension of a Kotlin source file.
*/
KOTLIN_SOURCE(".kt"),

/**
* Extension of a Java compiled file.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025, TeamDev. All rights reserved.
* Copyright 2026, TeamDev. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,32 +20,42 @@
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF TE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package io.spine.gradle.report.coverage

import io.spine.gradle.report.coverage.FileExtension.COMPILED_CLASS
import io.spine.gradle.report.coverage.FileExtension.JAVA_SOURCE
import io.spine.gradle.report.coverage.FileExtension.KOTLIN_SOURCE
import io.spine.gradle.report.coverage.PathMarker.ANONYMOUS_CLASS
import io.spine.gradle.report.coverage.PathMarker.GENERATED
import io.spine.gradle.report.coverage.PathMarker.GRPC_SRC_FOLDER
import io.spine.gradle.report.coverage.PathMarker.JAVA_OUTPUT_FOLDER
import io.spine.gradle.report.coverage.PathMarker.JAVA_SRC_FOLDER
import io.spine.gradle.report.coverage.PathMarker.SPINE_JAVA_SRC_FOLDER
import io.spine.gradle.report.coverage.PathMarker.MAIN_OUTPUT_FOLDER
import java.io.File

/**
* This file contains extension methods and properties for `java.io.File`.
*/

/**
* The two-part extension used by `protoc-gen-kotlin` for proto-file-scoped Kotlin
* helpers (e.g., `FooProtoKt.proto.kt`).
*/
private const val PROTO_KOTLIN_SUFFIX = ".proto.kt"

/**
* Suffix that the Kotlin compiler appends to the file name when generating the
* synthetic file class for top-level declarations.
*/
private const val KOTLIN_FILE_CLASS_SUFFIX = "Kt"

/**
* Parses the name of a class from the absolute path of this file.
*
* Treats the fragment between the [precedingMarker] and [extension] as the value to look for.
* In case the fragment is located and it contains `/` symbols, they are treated
* as Java package delimiters and are replaced by `.` symbols before returning the value.
* as package delimiters and are replaced by `.` symbols before returning the value.
*
* If the absolute path of this file has either no [precedingMarker] or no [extension],
* returns `null`.
Expand All @@ -69,58 +79,59 @@ internal fun File.parseClassName(
}

/**
* Attempts to parse the file name with either of the specified [parsers],
* in their respective order.
*
* Returns the first non-`null` parsed value.
* Attempts to parse the fully-qualified class name from the absolute path of this file,
* treating it as a path to a compiled `.class` file produced by either `javac` or `kotlinc`.
*
* If none of the parsers returns non-`null` value, returns `null`.
*/
internal fun File.parseName(vararg parsers: (file: File) -> String?): String? {
for (parser in parsers) {
val className = parser.invoke(this)
if (className != null) {
return className
}
}
return null
}

/**
* Attempts to parse the Java fully-qualified class name from the absolute path of this file,
* treating it as a path to a human-produced `.java` file.
*/
internal fun File.asJavaClassName(): String? =
this.parseClassName(JAVA_SRC_FOLDER, JAVA_SOURCE)

/**
* Attempts to parse the Java fully-qualified class name from the absolute path of this file,
* treating it as a path to a compiled `.class` file.
*
* If the `.class` file corresponds to the anonymous class, only the name of the parent
* class is returned.
* If the `.class` file corresponds to the anonymous or nested class, only the name of the
* top-level enclosing class is returned.
*/
internal fun File.asJavaCompiledClassName(): String? {
var className = this.parseClassName(JAVA_OUTPUT_FOLDER, COMPILED_CLASS)
var className = this.parseClassName(MAIN_OUTPUT_FOLDER, COMPILED_CLASS)
if (className != null && className.contains(ANONYMOUS_CLASS.infix)) {
className = className.split(ANONYMOUS_CLASS.infix)[0]
}
return className
}

/**
* Attempts to parse the Java fully-qualified class name from the absolute path of this file,
* treating it as a path to a gRPC-generated `.java` file.
* Returns the fully-qualified names of compiled JVM classes that originate from this
* source file, assuming [sourceRoot] is the source-set root under which the file was
* discovered.
*
* The shape of the returned list depends on the source file extension:
*
* - `.java` — a single FQN derived from the path relative to [sourceRoot].
* - `.kt` — two FQNs: the declared file/class name, and the same name with `Kt`
* appended, which is the synthetic file class that Kotlin emits for top-level
* declarations.
* - `.proto.kt` — the two-part extension is stripped first; otherwise behaves
* like `.kt`. This is the convention used by `protoc-gen-kotlin` for files
* holding proto-file-scoped helpers.
* - Any other extension — an empty list.
*
* Returns an empty list if this file is not located under [sourceRoot].
*/
internal fun File.asGrpcClassName(): String? =
this.parseClassName(GRPC_SRC_FOLDER, JAVA_SOURCE)
internal fun File.classNamesIn(sourceRoot: File): List<String> {
if (!this.startsWith(sourceRoot)) {
return emptyList()
}
val relative = this.toRelativeString(sourceRoot)
return when {
relative.endsWith(PROTO_KOTLIN_SUFFIX) -> {
val base = relative.removeSuffix(PROTO_KOTLIN_SUFFIX).toFqn()
listOf(base, base + KOTLIN_FILE_CLASS_SUFFIX)
}
relative.endsWith(KOTLIN_SOURCE.value) -> {
val base = relative.removeSuffix(KOTLIN_SOURCE.value).toFqn()
listOf(base, base + KOTLIN_FILE_CLASS_SUFFIX)
}
relative.endsWith(JAVA_SOURCE.value) ->
listOf(relative.removeSuffix(JAVA_SOURCE.value).toFqn())
else -> emptyList()
}
}

/**
* Attempts to parse the Java fully-qualified class name from the absolute path of this file,
* treating it as a path to a Spine-generated `.java` file.
*/
internal fun File.asSpineClassName(): String? =
this.parseClassName(SPINE_JAVA_SRC_FOLDER, JAVA_SOURCE)
private fun String.toFqn(): String = this.replace(File.separatorChar, '.')

/**
* Tells whether this file is a part of the generated sources, and not produced by a human.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import java.io.File
import java.util.*
import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.SourceSetContainer
Expand Down Expand Up @@ -150,13 +151,13 @@ class JacocoConfig(
copyReports: TaskProvider<Copy>
): TaskProvider<JacocoReport> {
val allSourceSets = Projects(projects).sourceSets()
val mainJavaSrcDirs = allSourceSets.mainJavaSrcDirs()
val mainSrcDirs = allSourceSets.mainSrcDirs()
val humanProducedSourceFolders =
FileFilter.producedByHuman(mainJavaSrcDirs)
FileFilter.producedByHuman(mainSrcDirs)

val filter = CodebaseFilter(
rootProject,
mainJavaSrcDirs,
mainSrcDirs,
allSourceSets.mainOutputs()
)
val humanProducedCompiledFiles = filter.humanProducedCompiledFiles()
Expand Down Expand Up @@ -228,12 +229,26 @@ private class SourceSets(
) {

/**
* Returns all Java source folders corresponding to the `main` source set type.
* Returns the union of Java and Kotlin source folders corresponding to the `main`
* source set across all underlying [SourceSetContainer]s.
*
* Kotlin source directories are registered as a separate [SourceDirectorySet]
* extension on the source set, not exposed via [allJava][org.gradle.api.tasks.SourceSet.getAllJava].
* They are surfaced explicitly here so that generated Kotlin code (for example,
* the output of `protoc-gen-kotlin`) is visible to the coverage filter alongside
* the Java sources.
*/
fun mainJavaSrcDirs(): Set<File> {
fun mainSrcDirs(): Set<File> {
return sourceSets
.asSequence()
.flatMap { it["main"].allJava.srcDirs }
.flatMap { container ->
val main = container["main"]
val javaDirs = main.allJava.srcDirs
val kotlinDirs = (main.extensions.findByName("kotlin") as? SourceDirectorySet)
?.srcDirs
?: emptySet()
javaDirs + kotlinDirs
}
.toSet()
}

Expand Down
Loading
Loading