Skip to main content

ktrs_cli/ktlint/
version.rs

1//! Which ktlint CLI the drop-in imitates: `--ktlint-version=<1.8|2.0>` (ktrs only, hidden), else
2//! `ktrs_ktlint_version` of the `.editorconfig` that applies to the working directory, else 2.0. Chosen once
3//! per run and passed to the engine as an `.editorconfig` override, so every file follows it. The engine and
4//! rule switches: research/26-ktlint-18-mode.md.
5
6use std::path::Path;
7
8use ktrs_editorconfig::EnumValue;
9use ktrs_lint::editorconfig::KtlintVersion;
10pub use ktrs_lint::editorconfig::with_ktlint_version;
11use ktrs_lint::engine::editor_config_loader::{EditorConfigLoader, EditorConfigLoaderEc4j};
12use ktrs_lint::rule_provider::property_types;
13use ktrs_lint::rules::standard_rule_providers;
14use ktrs_lint::{EditorConfigDefaults, EditorConfigOverride};
15
16use crate::ktlint::command_line::ExitCode;
17use crate::ktlint::reporter::KTLINT_VERSION;
18
19pub const KTLINT_VERSION_OPTION: &str = "--ktlint-version";
20
21/// The `--ktlint-version` value among `tokens` (before `--`; the last one wins), else the working
22/// directory's `.editorconfig`. `Err` is a Clikt-style usage error message.
23pub fn resolve_ktlint_version(tokens: &[String], working_dir: &Path) -> Result<KtlintVersion, String> {
24    match option_value(tokens)? {
25        Some(value) => KtlintVersion::value_of(value)
26            .ok_or_else(|| format!("invalid value for {KTLINT_VERSION_OPTION}: invalid choice: {value}. (choose from 1.8, 2.0)")),
27        None => Ok(working_dir_ktlint_version(working_dir)),
28    }
29}
30
31fn option_value(tokens: &[String]) -> Result<Option<&str>, String> {
32    let mut value: Option<&str> = None;
33    let mut tokens = tokens.iter().take_while(|t| *t != "--");
34    while let Some(token) = tokens.next() {
35        if let Some(attached) = token.strip_prefix(KTLINT_VERSION_OPTION).and_then(|rest| rest.strip_prefix('=')) {
36            value = Some(attached);
37        } else if token == KTLINT_VERSION_OPTION {
38            value = Some(tokens.next().ok_or_else(|| format!("option {KTLINT_VERSION_OPTION} requires a value"))?);
39        }
40    }
41    Ok(value)
42}
43
44/// Loaded with the engine's property types: the `.editorconfig` cache keeps the first parse of a file. An
45/// unreadable `.editorconfig` counts as unset here; the engine reports it on the first file.
46fn working_dir_ktlint_version(working_dir: &Path) -> KtlintVersion {
47    let property_types = property_types(&standard_rule_providers());
48    EditorConfigLoader::new(EditorConfigLoaderEc4j::new(&property_types), EditorConfigDefaults::empty(), EditorConfigOverride::empty())
49        .load(Some(&working_dir.join(".kt")))
50        .map_or(KtlintVersion::default(), |editor_config| KtlintVersion::of(&editor_config))
51}
52
53/// The release `--version` and the SARIF reporter show.
54pub fn release(version: KtlintVersion) -> &'static str {
55    match version {
56        KtlintVersion::V1_8 => "1.8.0",
57        KtlintVersion::V2_0 => KTLINT_VERSION,
58    }
59}
60
61/// The JVM package prefix in logger and exception class names.
62pub fn package(version: KtlintVersion) -> &'static str {
63    match version {
64        KtlintVersion::V1_8 => "com.pinterest.ktlint",
65        KtlintVersion::V2_0 => "io.github.ktlint.core",
66    }
67}
68
69/// The GitHub repository named in messages and git hooks.
70pub fn repository(version: KtlintVersion) -> &'static str {
71    match version {
72        KtlintVersion::V1_8 => "https://github.com/pinterest/ktlint",
73        KtlintVersion::V2_0 => "https://github.com/ktlint/ktlint",
74    }
75}
76
77/// `exitKtLintProcess`: 1.8 exits 1 for every failure but an unparsable format result (123).
78pub fn exit_value(version: KtlintVersion, code: ExitCode) -> i32 {
79    match (version, code) {
80        (KtlintVersion::V1_8, ExitCode::Ok | ExitCode::ParseExceptionAfterFormat) | (KtlintVersion::V2_0, _) => code as i32,
81        (KtlintVersion::V1_8, _) => 1,
82    }
83}