A lightweight, extensible static analysis tool for the Kotlin programming language, built as a Gradle plugin. This project was developed as part of a diploma thesis at the School of Computing, Union University.
While powerful tools like Detekt offer a comprehensive suite of static analysis rules, they can sometimes have a steep learning curve for teams that need to implement highly specific, domain-centric rules.
Rough Analyzer aims to fill that gap. It provides a simple, transparent, and easily extensible framework that empowers development teams to quickly implement their own coding standards without the overhead of a complex, industrial-grade tool. It serves as both a practical utility and an educational resource for understanding the application of compiler theory concepts, such as Abstract Syntax Tree (AST) traversal.
Rough Analyzer ships with a set of fundamental rules to help improve code quality and reduce technical debt:
- Long Function Rule: Flags functions that exceed a configurable line count.
- Cyclomatic Complexity Rule: Calculates and flags functions with high cyclomatic complexity.
- Magic Number Rule: Detects hardcoded numerical literals ("magic numbers").
- Println Usage Rule: Discourages the use of
printlnandprintin production code.
Here is a sample output when running Rough Analyzer on a real-world Android project. The report is clear, actionable, and helps pinpoint exact locations of code quality issues.
> ./gradlew roughAnalyzer
Analysis finished. Found 329 issues:
/app/src/main/java/com/raf/catalist/cats/quiz/QuizQuestionScreen.kt
Function 'CongratulationsScreen' is too long (118 lines). Maximum allowed is 80. - [long-function]
at /app/src/main/java/com/raf/catalist/cats/quiz/QuizQuestionScreen.kt:384:5
Magic number '150' found. Extract it to a named constant. - [magic-number]
at /app/src/main/java/com/raf/catalist/cats/quiz/QuizQuestionScreen.kt:401:23
/app/src/main/java/com/raf/catalist/users/details/UserDetailsEditScreen.kt
Cyclomatic complexity of function 'UserEdit' is 16 (threshold is 15). - [cyclomatic-complexity]
at /app/src/main/java/com/raf/catalist/users/details/UserDetailsEditScreen.kt:86:5
/app/src/main/java/com/raf/catalist/cats/repository/BreedsRepository.kt
Magic number '10' found. Extract it to a named constant. - [magic-number]
at /app/src/main/java/com/raf/catalist/cats/repository/BreedsRepository.kt:63:59
---
Total issues found: 329
329 warningsAs a Gradle plugin, Rough Analyzer is designed to be integrated directly into your build process.
Before using it in another project, you need to publish the analyzer to your local Maven repository. Clone this repository and run:
./gradlew publishToMavenLocalIn the Kotlin/Gradle project you want to analyze, make the following changes:
-
In your
settings.gradle.ktsfile, addmavenLocal()to thepluginManagementrepositories block. This tells Gradle to look for plugins on your local machine.pluginManagement { repositories { mavenLocal() // Add this line google() mavenCentral() gradlePluginPortal() } } -
In your module's
build.gradle.ktsfile (e.g.,app/build.gradle.ktsfor Android), apply the plugin.plugins { // ... your other plugins id("rs.raf.student.rough-analyzer") version "1.0-SNAPSHOT" }
After syncing your project, you can run the analysis by executing the roughAnalyzer task from your terminal:
./gradlew roughAnalyzerYou can customize Rough Analyzer's behavior in two ways:
By default, Rough Analyzer looks for Kotlin files in src/main/kotlin. This is fine for pure JVM projects, but Android projects typically place Kotlin files in src/main/java.
You can easily configure this in your module's build.gradle.kts:
// In app/build.gradle.kts
plugins {
id("rs.raf.student.rough-analyzer") version "1.0-SNAPSHOT"
// ...
}
// Add this block to point to the correct directory
roughAnalyzerConfig {
sourceDir.set("src/main/java")
}
android {
// ...
}To enable/disable rules and set custom thresholds, create a file named rough-analyzer.yml in the root directory of your project. If this file is not found, default values will be used.
Here is an example configuration tailored for a Jetpack Compose project, which often has longer functions:
# rough-analyzer.yml
longFunction:
active: true
# Compose functions can be longer, so we are more generous
threshold: 80
cyclomaticComplexity:
active: true
# UI logic can sometimes justify higher complexity
threshold: 15
magicNumber:
active: true
printlnUsage:
active: false # Disable this rule for a project still in heavy developmentRough Analyzer's architecture is built on a few key principles:
- Kotlin Compiler API: It uses the
org.jetbrains.kotlin:kotlin-compiler-embeddablelibrary to parse Kotlin source code into a Program Structure Interface (PSI) tree, which is a concrete implementation of an Abstract Syntax Tree (AST). This provides a rich, semantically-aware representation of the code. - Visitor Pattern: Each rule is implemented as a
KtTreeVisitorVoid, which traverses the PSI tree. This pattern cleanly separates the analysis logic (the "operations") from the code structure (the "elements"). - Modularity: The core
AnalyzerEngineis completely decoupled from the rules themselves. It operates on aList<Rule>, making it trivial to add new rules without modifying the engine, adhering to the Open/Closed Principle.