Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ techDebtReport {

// Set a base URL for tickets to automatically generate links (Optional)
baseTicketUrl.set("https://jira.myproject.com/tickets/")

// Enable Git metadata like last modified date and author (Optional, default is false)
enableGitMetadata.set(true)
}
```

Expand All @@ -99,6 +102,7 @@ techDebtReport {
- **Consolidated HTML Report**: A clean, easy-to-read summary of all technical debt from all modules in your project.
- **Suppress Support**: Optionally collect and visualize suppressed rules (e.g., `@Suppress("MagicNumber")`) in the report.
- **TODO/FIXME Comments Support**: Optionally collect and visualize `TODO` and `FIXME` comments from your source code.
- **Git Metadata Support**: Optionally collect and visualize Git information (Author and Last Modified date) for each tech debt item.
- **Priority Levels**: Support for `LOW`, `MEDIUM`, and `HIGH` priority levels (and `NONE`).
- **Ticket Linking**: Keep track of related tickets in your issue tracking system. If `baseTicketUrl` is configured,
tickets will automatically become clickable links in the report.
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ junit-platform = "1.10.1"
ksp = "2.3.4"
android-gradle-plugin = "8.13.2"
maven-publish = "0.35.0"
jgit = "7.5.0.202512021534-r"
detekt = "1.23.4"
ktfmt = "0.16.0"

Expand All @@ -23,6 +24,7 @@ kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-p
junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" }
junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junit" }
junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version.ref = "junit-platform" }
jgit = { group = "org.eclipse.jgit", name = "org.eclipse.jgit", version.ref = "jgit" }

[plugins]
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
Expand Down
131 changes: 112 additions & 19 deletions samples/sample-jvm/assets/report.html
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@
.info-value {
font-size: 14px;
color: #333;
min-width: 0;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
}

.ticket {
Expand Down Expand Up @@ -239,8 +243,18 @@ <h2>Annotated Tech Debt</h2>
</div>
<span class="expand-icon"></span></summary>
<div class="card-content">
<div class="info-group"><span class="info-label">Priority</span><span class="info-value">HIGH</span></div>
<div class="info-group"><span class="info-label">Source Set</span><span class="info-value">main</span></div>
<div class="info-group"><span class="info-label">Priority</span>
<div class="info-value">HIGH</div>
</div>
<div class="info-group"><span class="info-label">Source Set</span>
<div class="info-value">main</div>
</div>
<div class="info-group"><span class="info-label">Last Modified</span>
<div class="info-value">2026-01-18 11:39:12</div>
</div>
<div class="info-group"><span class="info-label">Author</span>
<div class="info-value">Igor Escodro</div>
</div>
</div>
</details>
</div>
Expand All @@ -255,9 +269,21 @@ <h2>Annotated Tech Debt</h2>
</div>
<span class="expand-icon"></span></summary>
<div class="card-content">
<div class="info-group"><span class="info-label">Ticket</span><span class="info-value"><a href="https://github.com/igorescodro/tech-debt/issues/26" target="_blank" rel="noopener noreferrer">26</a></span></div>
<div class="info-group"><span class="info-label">Priority</span><span class="info-value">HIGH</span></div>
<div class="info-group"><span class="info-label">Source Set</span><span class="info-value">main</span></div>
<div class="info-group"><span class="info-label">Ticket</span>
<div class="info-value"><a href="https://github.com/igorescodro/tech-debt/issues/26" target="_blank" rel="noopener noreferrer">26</a></div>
</div>
<div class="info-group"><span class="info-label">Priority</span>
<div class="info-value">HIGH</div>
</div>
<div class="info-group"><span class="info-label">Source Set</span>
<div class="info-value">main</div>
</div>
<div class="info-group"><span class="info-label">Last Modified</span>
<div class="info-value">2026-01-31 17:34:25</div>
</div>
<div class="info-group"><span class="info-label">Author</span>
<div class="info-value">Igor Escodro</div>
</div>
</div>
</details>
</div>
Expand All @@ -272,9 +298,15 @@ <h2>Annotated Tech Debt</h2>
</div>
<span class="expand-icon"></span></summary>
<div class="card-content">
<div class="info-group"><span class="info-label">Ticket</span><span class="info-value"><a href="https://github.com/igorescodro/tech-debt/issues/23" target="_blank" rel="noopener noreferrer">23</a></span></div>
<div class="info-group"><span class="info-label">Priority</span><span class="info-value">MEDIUM</span></div>
<div class="info-group"><span class="info-label">Source Set</span><span class="info-value">main</span></div>
<div class="info-group"><span class="info-label">Ticket</span>
<div class="info-value"><a href="https://github.com/igorescodro/tech-debt/issues/23" target="_blank" rel="noopener noreferrer">23</a></div>
</div>
<div class="info-group"><span class="info-label">Priority</span>
<div class="info-value">MEDIUM</div>
</div>
<div class="info-group"><span class="info-label">Source Set</span>
<div class="info-value">main</div>
</div>
</div>
</details>
</div>
Expand All @@ -289,9 +321,21 @@ <h2>Annotated Tech Debt</h2>
</div>
<span class="expand-icon"></span></summary>
<div class="card-content">
<div class="info-group"><span class="info-label">Ticket</span><span class="info-value"><a href="https://github.com/igorescodro/tech-debt/issues/20" target="_blank" rel="noopener noreferrer">20</a></span></div>
<div class="info-group"><span class="info-label">Priority</span><span class="info-value">LOW</span></div>
<div class="info-group"><span class="info-label">Source Set</span><span class="info-value">main</span></div>
<div class="info-group"><span class="info-label">Ticket</span>
<div class="info-value"><a href="https://github.com/igorescodro/tech-debt/issues/20" target="_blank" rel="noopener noreferrer">20</a></div>
</div>
<div class="info-group"><span class="info-label">Priority</span>
<div class="info-value">LOW</div>
</div>
<div class="info-group"><span class="info-label">Source Set</span>
<div class="info-value">main</div>
</div>
<div class="info-group"><span class="info-label">Last Modified</span>
<div class="info-value">2026-01-31 17:34:25</div>
</div>
<div class="info-group"><span class="info-label">Author</span>
<div class="info-value">Igor Escodro</div>
</div>
</div>
</details>
</div>
Expand All @@ -306,8 +350,18 @@ <h2>Annotated Tech Debt</h2>
</div>
<span class="expand-icon"></span></summary>
<div class="card-content">
<div class="info-group"><span class="info-label">Priority</span><span class="info-value">NONE</span></div>
<div class="info-group"><span class="info-label">Source Set</span><span class="info-value">main</span></div>
<div class="info-group"><span class="info-label">Priority</span>
<div class="info-value">NONE</div>
</div>
<div class="info-group"><span class="info-label">Source Set</span>
<div class="info-value">main</div>
</div>
<div class="info-group"><span class="info-label">Last Modified</span>
<div class="info-value">2026-01-10 19:56:05</div>
</div>
<div class="info-group"><span class="info-label">Author</span>
<div class="info-value">Igor Escodro</div>
</div>
</div>
</details>
</div>
Expand All @@ -322,7 +376,15 @@ <h2>Comments</h2>
</div>
<span class="expand-icon"></span></summary>
<div class="card-content">
<div class="info-group"><span class="info-label">Location</span><span class="info-value">src/main/kotlin/com/escodro/sample/Sample.kt:20</span></div>
<div class="info-group"><span class="info-label">Location</span>
<div class="info-value">src/main/kotlin/com/escodro/sample/Sample.kt</div>
</div>
<div class="info-group"><span class="info-label">Last Modified</span>
<div class="info-value">2026-01-31 09:38:49</div>
</div>
<div class="info-group"><span class="info-label">Author</span>
<div class="info-value">Igor Escodro</div>
</div>
</div>
</details>
</div>
Expand All @@ -336,7 +398,15 @@ <h2>Comments</h2>
</div>
<span class="expand-icon"></span></summary>
<div class="card-content">
<div class="info-group"><span class="info-label">Location</span><span class="info-value">src/main/kotlin/com/escodro/sample/Sample.kt:27</span></div>
<div class="info-group"><span class="info-label">Location</span>
<div class="info-value">src/main/kotlin/com/escodro/sample/Sample.kt</div>
</div>
<div class="info-group"><span class="info-label">Last Modified</span>
<div class="info-value">2026-01-31 09:38:49</div>
</div>
<div class="info-group"><span class="info-label">Author</span>
<div class="info-value">Igor Escodro</div>
</div>
</div>
</details>
</div>
Expand All @@ -350,7 +420,15 @@ <h2>Comments</h2>
</div>
<span class="expand-icon"></span></summary>
<div class="card-content">
<div class="info-group"><span class="info-label">Location</span><span class="info-value">src/main/kotlin/com/escodro/sample/Sample.kt:35</span></div>
<div class="info-group"><span class="info-label">Location</span>
<div class="info-value">src/main/kotlin/com/escodro/sample/Sample.kt</div>
</div>
<div class="info-group"><span class="info-label">Last Modified</span>
<div class="info-value">2026-01-31 09:38:49</div>
</div>
<div class="info-group"><span class="info-label">Author</span>
<div class="info-value">Igor Escodro</div>
</div>
</div>
</details>
</div>
Expand All @@ -364,7 +442,15 @@ <h2>Comments</h2>
</div>
<span class="expand-icon"></span></summary>
<div class="card-content">
<div class="info-group"><span class="info-label">Location</span><span class="info-value">src/main/kotlin/com/escodro/sample/Sample.kt:39</span></div>
<div class="info-group"><span class="info-label">Location</span>
<div class="info-value">src/main/kotlin/com/escodro/sample/Sample.kt</div>
</div>
<div class="info-group"><span class="info-label">Last Modified</span>
<div class="info-value">2026-01-31 09:38:49</div>
</div>
<div class="info-group"><span class="info-label">Author</span>
<div class="info-value">Igor Escodro</div>
</div>
</div>
</details>
</div>
Expand All @@ -380,8 +466,15 @@ <h2>Suppressed Rules</h2>
</div>
<span class="expand-icon"></span></summary>
<div class="card-content">
<div class="info-group"><span class="info-label">Source Set</span><span class="info-value">main</span></div>
<div class="info-group"><span class="info-label">Symbol</span><span class="info-value">com.escodro.sample.Sample.suppressedFunction</span></div>
<div class="info-group"><span class="info-label">Source Set</span>
<div class="info-value">main</div>
</div>
<div class="info-group"><span class="info-label">Last Modified</span>
<div class="info-value">2026-01-25 09:17:04</div>
</div>
<div class="info-group"><span class="info-label">Author</span>
<div class="info-value">Igor Escodro</div>
</div>
</div>
</details>
</div>
Expand Down
1 change: 1 addition & 0 deletions samples/sample-jvm/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ techDebtReport {
outputFile.set(layout.projectDirectory.file("assets/report.html"))
collectSuppress.set(true)
collectComments.set(true)
enableGitMetadata.set(true)
baseTicketUrl.set("https://github.com/igorescodro/tech-debt/issues/")
}
1 change: 1 addition & 0 deletions techdebt-gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies {
implementation(libs.kotlin.gradle.plugin)
implementation(libs.ksp.gradle.plugin)
implementation(libs.android.gradle.plugin)
implementation(libs.jgit)

testImplementation(libs.junit.jupiter.api)
testRuntimeOnly(libs.junit.jupiter.engine)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ package com.escodro.techdebt.gradle
import com.escodro.techdebt.gradle.model.TechDebtItem
import com.escodro.techdebt.gradle.parser.CommentParser
import com.escodro.techdebt.gradle.parser.GeneratedTechDebtParser
import com.escodro.techdebt.gradle.parser.GitParser
import com.escodro.techdebt.gradle.report.HtmlReportGenerator
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
Expand All @@ -24,12 +27,18 @@ abstract class GenerateTechDebtReportTask : DefaultTask() {
/** Whether to collect TODO/FIXME comments. Defaults to `false`. */
@get:Input abstract val collectComments: Property<Boolean>

/** Whether to enable Git metadata (e.g. last modified date). Defaults to `false`. */
@get:Input abstract val enableGitMetadata: Property<Boolean>

/**
* Map of project directory to project path. Used to resolve the module name for TODO comments
* without accessing the Project object at execution time.
*/
@get:Input abstract val projectPathByDirectory: MapProperty<String, String>

/** The root directory of the project. Used to resolve Git metadata. */
@get:Internal abstract val rootProjectDirectory: DirectoryProperty

/** The source files to scan for TODO comments. */
@get:InputFiles @get:Optional abstract val sourceFiles: ConfigurableFileCollection

Expand All @@ -43,25 +52,29 @@ abstract class GenerateTechDebtReportTask : DefaultTask() {

private val jsonParser: GeneratedTechDebtParser = GeneratedTechDebtParser()

private val commentParser: CommentParser = CommentParser()

@TaskAction
fun generate() {
val allItems = mutableListOf<TechDebtItem>()
allItems += jsonParser.parse(jsonFiles)

val rootProjectDir = rootProjectDirectory.get().asFile
if (collectComments.get()) {
allItems +=
commentParser.parse(
sourceFiles = sourceFiles,
projectPaths = projectPathByDirectory.get()
)
CommentParser()
.parse(sourceFiles = sourceFiles, projectPaths = projectPathByDirectory.get())
}

val aggregatedItems = aggregateItems(allItems)

val itemsWithMetadata =
if (enableGitMetadata.get()) {
GitParser(rootProjectDir).parse(aggregatedItems)
} else {
aggregatedItems
}

val sortedItems =
aggregatedItems.sortedWith(compareBy({ it.moduleName }, { it.priorityOrder }))
itemsWithMetadata.sortedWith(compareBy({ it.moduleName }, { it.priorityOrder }))

writeReport(sortedItems)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ abstract class TechDebtExtension {
/** Whether to collect TODO/FIXME comments. Defaults to `false`. */
abstract val collectComments: Property<Boolean>

/** Whether to enable Git metadata (e.g. last modified date). Defaults to `false`. */
abstract val enableGitMetadata: Property<Boolean>

/**
* The base URL for the tickets. If set, the ticket property in the HTML report will be a link.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@ class TechDebtPlugin : Plugin<Project> {
project.extensions.create("techDebtReport", TechDebtExtension::class.java)
extension.collectSuppress.convention(false)
extension.collectComments.convention(false)
extension.enableGitMetadata.convention(false)

val reportTask: TaskProvider<GenerateTechDebtReportTask> =
project.tasks.register(
"generateTechDebtReport",
GenerateTechDebtReportTask::class.java
) { task ->
task.collectComments.set(extension.collectComments)
task.enableGitMetadata.set(extension.enableGitMetadata)
task.baseTicketUrl.set(extension.baseTicketUrl)
task.projectPathByDirectory.set(
project.allprojects.associate { it.projectDir.absolutePath to it.path }
)
task.rootProjectDirectory.set(project.rootProject.layout.projectDirectory)
task.outputFile.set(
extension.outputFile.convention(
project.layout.buildDirectory.file(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import kotlinx.serialization.Serializable
* @property priority the priority of the tech debt
* @property sourceSet the source set where the tech debt is located
* @property type the type of the tech debt item
* @property lastModified the last time the tech debt was modified
* @property location the location of the tech debt in the source code
* @property author the author of the tech debt
*/
@Serializable
data class TechDebtItem(
Expand All @@ -21,7 +24,10 @@ data class TechDebtItem(
val ticket: String,
val priority: String,
val sourceSet: String,
val type: TechDebtItemType = TechDebtItemType.TECH_DEBT
val type: TechDebtItemType = TechDebtItemType.TECH_DEBT,
val lastModified: String? = null,
val location: String? = null,
val author: String? = null,
) {
/**
* Returns the priority order for the tech debt item.
Expand Down
Loading