Formatting and linting
kargo fmt formats your Kotlin code with ktlint, and kargo lint analyzes it with detekt. They are Kargo’s cargo fmt and cargo clippy. Kargo downloads both tools from Maven Central and runs them on your JDK, so you don’t install anything and every machine on the team runs the same version.
$ kargo fmt Formatting my-app v0.1.0 (ktlint 1.8.0) Finished ktlint in 1.30s$ kargo lint Linting my-app v0.1.0 (detekt 1.23.8) Finished detekt in 1.81sProjects created by kargo new already pass both.
Which files
Section titled “Which files”Both commands look at every .kt and .kts file in src/, src-<target>/, tests/ and tests-<target>/, for all targets whether or not they are enabled. target/ and other directories are never touched.
Formatting with kargo fmt
Section titled “Formatting with kargo fmt”kargo fmt rewrites files in place. ktlint fixes what it can; if a problem has no automatic fix (some naming rules, for example), ktlint lists it and kargo fmt exits with code 101.
To check without changing anything, as in CI, pass --check:
$ kargo fmt --check Checking formatting of my-app v0.1.0 (ktlint 1.8.0)src/Helper.kt:3:4: Single space expected after the fun keyword (standard:fun-keyword-spacing)src/Helper.kt:3:5: Unnecessary long whitespace (standard:no-multi-spaces)
Summary error count (descending) by rule: standard:fun-keyword-spacing: 1 standard:no-multi-spaces: 1error: formatting check failed; `kargo fmt` fixes what ktlint can correct automatically--check exits with code 101 if ktlint reports anything. Problems are printed to stdout, relative to the project root.
Configuring ktlint
Section titled “Configuring ktlint”ktlint follows the ktlint_official code style by default. Change the style, line length or individual rules in an .editorconfig file at the project root. ktlint finds it on its own:
root = true
[*.{kt,kts}]ktlint_code_style = intellij_ideamax_line_length = 120ktlint_standard_no-wildcard-imports = disabledSee ktlint’s configuration docs for every property.
Skip files, such as generated code, and pin the ktlint version in kargo.toml:
[fmt]exclude = ["src/generated/**"]ktlint = "1.8.0"| Key | Default | Meaning |
|---|---|---|
exclude |
[] |
Globs, relative to the project root, of files ktlint skips |
ktlint |
1.8.0 |
Exact ktlint version |
Formatting from your editor
Section titled “Formatting from your editor”Kargo’s Kotlin server, the default behind kargo lsp, formats the open file with this same ktlint: Format Document in your editor gives what kargo fmt would write, with your .editorconfig and [fmt] settings, even for text you haven’t saved. A file that [fmt] exclude leaves out is left alone. IntelliJ IDEA keeps its own formatter, and --server kotlin-lsp and --server kls format with their own.
Format on save is your editor’s setting; turn it on and the editor asks the server on each save:
| Editor | Setting |
|---|---|
| Neovim | vim.lsp.buf.format({ async = false }) from a BufWritePre autocommand |
| VS Code | "[kotlin]": { "editor.formatOnSave": true } |
| Zed | "languages": { "Kotlin": { "format_on_save": "on" } } |
| Helix | auto-format = true for kotlin in languages.toml |
ktlint is a JVM program, so a format takes about a second. It runs on its own thread: the editor stays responsive, and text you changed in the meantime is not overwritten. The first format downloads ktlint (and a JDK, if [toolchain] asks for one you haven’t installed); kargo fetch --tools downloads ktlint ahead of time. Only whole files are formatted, and .kts scripts are not.
Linting with kargo lint
Section titled “Linting with kargo lint”kargo lint runs detekt’s default rule set: complexity, naming, potential bugs, style and more. It exits with code 101 if detekt reports issues:
$ kargo lint Linting my-app v0.1.0 (detekt 1.23.8)/home/me/my-app/src/Helper.kt:3:5: helper is returning a constant. Prefer declaring a constant instead. [FunctionOnlyReturningConstant]
Analysis failed with 1 weighted issues.error: detekt found issues; fix them, suppress them with `@Suppress`, or record them with `kargo lint --create-baseline`Silence a single finding with @Suppress("FunctionOnlyReturningConstant") on the declaration.
Configuring detekt
Section titled “Configuring detekt”Put a detekt.yml at the project root. Kargo passes it to detekt on top of detekt’s default configuration, so the file only needs the rules you change:
style: MagicNumber: active: false MaxLineLength: maxLineLength: 140complexity: LongMethod: threshold: 80Kargo turns off one rule for you: InvalidPackageDeclaration, because Kargo projects keep sources directly in src/ instead of in directories that mirror the package. Turn it back on in your detekt.yml if you lay out your sources by package.
To use other files or pin the version, add a [lint] section:
[lint]config = ["config/detekt.yml"]baseline = "config/detekt-baseline.xml"detekt = "1.23.8"| Key | Default | Meaning |
|---|---|---|
config |
["detekt.yml"] if that file exists |
detekt configuration files, relative to the project root. Later files override earlier ones |
baseline |
detekt-baseline.xml |
Baseline file, relative to the project root. It is used only if it exists, unless you set this key; then it must exist |
detekt |
1.23.8 |
Exact detekt version |
Adopting detekt in an existing project
Section titled “Adopting detekt in an existing project”A codebase that has never been linted usually has many findings. Record them in a baseline, so kargo lint only fails on new issues:
$ kargo lint --create-baseline Linting my-app v0.1.0 (detekt 1.23.8) Created baseline detekt-baseline.xml Finished detekt in 1.78sCommit detekt-baseline.xml. Later runs of kargo lint ignore the issues it lists. Fix them over time and run --create-baseline again to shrink the file.
Add both commands to your workflow. They fail the job with exit code 101:
- run: kargo fmt --check - run: kargo lintHow it works
Section titled “How it works”ktlint and detekt are downloaded on first use (the ktlint-cli and detekt-cli jars with the all classifier) and cached under ~/.kargo/cache, like the Kotlin compiler. kargo fetch --tools downloads them ahead of time. After that they work with --offline. They run on the JDK from [toolchain], or JAVA_HOME.
Run kargo fmt -v or kargo lint -v to see the exact command line. Kargo’s own detekt settings are written to target/lint/kargo-detekt.yml.