ktrs_cli/ktlint/
gradle.rs1use std::fs::File;
16use std::io::{BufWriter, Write};
17use std::sync::atomic::Ordering;
18
19use ktrs_lint::{AutocorrectDecision, Code};
20
21use ktrs_lint::editorconfig::{
22 CODE_STYLE_PROPERTY, END_OF_LINE_PROPERTY, INDENT_SIZE_PROPERTY, INDENT_STYLE_PROPERTY, INSERT_FINAL_NEWLINE_PROPERTY,
23 MAX_LINE_LENGTH_PROPERTY, PropertyRef, RuleExecution, create_rule_execution_editor_config_property,
24 create_rule_set_execution_editor_config_property,
25};
26use ktrs_lint::rule_provider::RuleV2Provider;
27
28use crate::ktlint::baseline::does_not_contain;
29use crate::ktlint::command_line::Exit;
30use crate::ktlint::process::Processor;
31use crate::ktlint::reporter::{KtlintCliError, ReporterV2, Status};
32
33pub const EVENTS_OPTION: &str = "--ktrs-gradle-events";
34pub const RELATIVE_TO_OPTION: &str = "--ktrs-relative-to";
35pub const EDITOR_CONFIG_OVERRIDE_OPTION: &str = "--ktrs-editorconfig-override";
36
37const EVENTS_HEADER: &str = "# ktrs-gradle-events 1";
39
40pub struct GradleEventsReporter {
45 out: BufWriter<File>,
46}
47
48impl GradleEventsReporter {
49 pub fn new(file: File) -> GradleEventsReporter {
50 GradleEventsReporter { out: BufWriter::new(file) }
51 }
52}
53
54impl ReporterV2 for GradleEventsReporter {
55 fn before_all(&mut self) {
56 let _ = writeln!(self.out, "{EVENTS_HEADER}");
57 }
58
59 fn before(&mut self, file: &str) {
60 let _ = writeln!(self.out, "file\t{file}");
61 }
62
63 fn on_lint_error(&mut self, file: &str, e: &KtlintCliError) {
64 let status = match e.status {
65 Status::BaselineIgnored => "BASELINE_IGNORED",
66 Status::LintCanNotBeAutocorrected => "LINT_CAN_NOT_BE_AUTOCORRECTED",
67 Status::LintCanBeAutocorrected => "LINT_CAN_BE_AUTOCORRECTED",
68 Status::FormatIsAutocorrected => "FORMAT_IS_AUTOCORRECTED",
69 Status::KotlinParseException => "KOTLIN_PARSE_EXCEPTION",
70 Status::KtlintRuleEngineException => "KTLINT_RULE_ENGINE_EXCEPTION",
71 };
72 let detail = e.detail.replace('\\', "\\\\").replace('\t', "\\t").replace('\n', "\\n").replace('\r', "\\r");
73 let _ = writeln!(self.out, "error\t{file}\t{}\t{}\t{}\t{status}\t{}\t{detail}", e.line, e.col, e.rule_id, e.corrected);
74 }
75
76 fn after_all(&mut self) {
77 let _ = self.out.flush();
78 }
79}
80
81impl Processor<'_> {
82 pub(crate) fn gradle_format(&self, code: &Code, baseline_lint_errors: &[KtlintCliError]) -> Result<Vec<KtlintCliError>, Exit> {
86 let mut ktlint_cli_errors: Vec<KtlintCliError> = Vec::new();
87 let result = self.engine.format_reporting(code, &mut |_| AutocorrectDecision::AllowAutocorrect, &mut |e, corrected| {
88 let status = if e.can_be_auto_corrected { Status::LintCanBeAutocorrected } else { Status::LintCanNotBeAutocorrected };
89 let mut ktlint_cli_error = KtlintCliError::new(e.line, e.col, e.rule_id.value(), &e.detail, status);
90 ktlint_cli_error.corrected = corrected;
91 if does_not_contain(baseline_lint_errors, &ktlint_cli_error) {
92 if !corrected {
93 self.contains_unfixed_lint_errors.store(true, Ordering::SeqCst);
94 }
95 ktlint_cli_errors.push(ktlint_cli_error);
96 }
97 });
98 match result {
99 Ok(formatted_file_content) => {
100 if code.content != formatted_file_content
101 && let Some(path) = &code.file_path
102 && let Err(e) = std::fs::write(path, &formatted_file_content)
103 {
104 return Err(Exit::Crash(format!("java.io.FileNotFoundException: {} ({e})", path.display())));
105 }
106 }
107 Err(e) => {
108 ktlint_cli_errors.push(self.to_ktlint_cli_error(&e, code)?);
109 self.contains_unfixed_lint_errors.store(true, Ordering::SeqCst);
110 }
111 }
112 Ok(ktlint_cli_errors)
113 }
114}
115
116pub fn editor_config_override(entries: &[String], providers: &[RuleV2Provider]) -> Result<Vec<(PropertyRef, Option<String>)>, String> {
119 let core: [PropertyRef; 6] = [
120 (&*CODE_STYLE_PROPERTY).into(),
121 (&*END_OF_LINE_PROPERTY).into(),
122 (&*INDENT_STYLE_PROPERTY).into(),
123 (&*INDENT_SIZE_PROPERTY).into(),
124 (&*INSERT_FINAL_NEWLINE_PROPERTY).into(),
125 (&*MAX_LINE_LENGTH_PROPERTY).into(),
126 ];
127 let known: Vec<&PropertyRef> = providers.iter().flat_map(|p| p.uses_editor_config_properties()).chain(&core).collect();
128 entries
129 .iter()
130 .map(|entry| {
131 let (name, value) = entry.split_once('=').ok_or_else(|| format!("{EDITOR_CONFIG_OVERRIDE_OPTION} needs name=value, got '{entry}'"))?;
132 let property = match known.iter().find(|p| p.name() == name) {
133 Some(property) => (*property).clone(),
134 None => rule_execution_property(name).ok_or_else(|| not_found(name, &known))?,
135 };
136 Ok((property, Some(value.to_owned())))
137 })
138 .collect()
139}
140
141fn rule_execution_property(name: &str) -> Option<PropertyRef> {
143 let id = name.strip_prefix("ktlint_")?.replace('_', ":");
144 let valid = |part: &str| !part.is_empty() && part.chars().all(|c| c.is_ascii_alphanumeric() || c == '-');
145 match id.split_once(':') {
146 Some((rule_set, rule)) if valid(rule_set) && valid(rule) => {
147 Some(create_rule_execution_editor_config_property(&id, RuleExecution::Enabled).into())
148 }
149 None if valid(&id) => Some(create_rule_set_execution_editor_config_property(&id, RuleExecution::Enabled).into()),
150 _ => None,
151 }
152}
153
154fn not_found(name: &str, known: &[&PropertyRef]) -> String {
155 let mut names: Vec<&str> = known.iter().map(|p| p.name()).collect();
156 names.sort_unstable();
157 names.dedup();
158 let list: String = names.iter().map(|n| format!("\n\t- {n}")).collect();
159 format!(
160 "EditorConfigPropertyNotFoundException: Property with name '{name}' is not found in any of given rules. Available \
161 properties:{list}\nNext to properties above, the properties to enable or disable ktlint rules are allowed as well."
162 )
163}
164
165pub fn native_separators(route: &str) -> String {
168 if cfg!(windows) { route.replace('/', "\\") } else { route.to_owned() }
169}
170
171pub fn hand_off_token(option: &str, value: &str) -> Option<String> {
173 (option == EVENTS_OPTION).then(|| format!("--reporter=json,output={value}"))
174}