diff --git a/src/compile/ir/tasks/mod.rs b/src/compile/ir/tasks/mod.rs index eaa7355f..f5c96b99 100644 --- a/src/compile/ir/tasks/mod.rs +++ b/src/compile/ir/tasks/mod.rs @@ -59,6 +59,7 @@ pub mod publish_build_artifacts; pub mod publish_code_coverage_results; pub mod publish_pipeline_artifact; pub mod publish_test_results; +pub mod sonar_qube_analyze; pub mod sonar_qube_publish; pub mod twine_authenticate; pub mod universal_packages; diff --git a/src/compile/ir/tasks/parse.rs b/src/compile/ir/tasks/parse.rs index ec475a3a..511669b3 100644 --- a/src/compile/ir/tasks/parse.rs +++ b/src/compile/ir/tasks/parse.rs @@ -37,8 +37,8 @@ use super::{ helm_installer, java_tool_installer, manual_validation, maven, maven_authenticate, node_tool, npm, npm_authenticate, nuget_authenticate, nuget_command, pip_authenticate, powershell, publish_build_artifacts, publish_code_coverage_results, publish_pipeline_artifact, - publish_test_results, python_script, sonar_qube_publish, twine_authenticate, universal_packages, - use_dotnet, use_node, use_python_version, use_ruby_version, vs_build, vstest, + publish_test_results, python_script, sonar_qube_analyze, sonar_qube_publish, twine_authenticate, + universal_packages, use_dotnet, use_node, use_python_version, use_ruby_version, vs_build, vstest, }; /// Registry mapping an ADO task id (`"CopyFiles@2"`) to a validator that checks @@ -93,6 +93,7 @@ const VALIDATORS: &[(&str, fn(Value) -> Result<(), String>)] = &[ validate_by_deserialize::, ), ("PublishTestResults@2", validate_by_deserialize::), + ("SonarQubeAnalyze@8", validate_by_deserialize::), ("SonarQubePublish@8", validate_by_deserialize::), ("TwineAuthenticate@1", validate_by_deserialize::), ("UseDotNet@2", validate_by_deserialize::), @@ -714,6 +715,29 @@ mod tests { assert!(err.contains("SonarQubePublish@8"), "got: {err}"); } + #[test] + fn roundtrip_sonar_qube_analyze() { + assert_roundtrips(sonar_qube_analyze::SonarQubeAnalyze::new().into_step()); + assert_roundtrips( + sonar_qube_analyze::SonarQubeAnalyze::new() + .jdk_version(sonar_qube_analyze::JdkVersion::JavaHome21X64) + .into_step(), + ); + } + + #[test] + fn sonar_qube_analyze_unknown_input_warns() { + let step = yaml( + r#" + task: SonarQubeAnalyze@8 + inputs: + bogusInput: nope + "#, + ); + let err = validate_task_step(&step).expect("recognized").unwrap_err(); + assert!(err.contains("SonarQubeAnalyze@8"), "got: {err}"); + } + #[test] fn registry_has_no_duplicate_task_ids() { let mut ids: Vec<&str> = VALIDATORS.iter().map(|(id, _)| *id).collect(); diff --git a/src/compile/ir/tasks/sonar_qube_analyze.rs b/src/compile/ir/tasks/sonar_qube_analyze.rs new file mode 100644 index 00000000..ff64a875 --- /dev/null +++ b/src/compile/ir/tasks/sonar_qube_analyze.rs @@ -0,0 +1,174 @@ +//! Typed builder for `SonarQubeAnalyze@8`. +//! +//! ADO task reference: +//! + +use crate::compile::ir::step::TaskStep; +use serde::Deserialize; + +/// JDK version source used by the SonarQube scanner during analysis. +/// +/// Maps to the `jdkversion` input of `SonarQubeAnalyze@8`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +pub enum JdkVersion { + /// Use the value of the `JAVA_HOME` environment variable. + #[serde(rename = "JAVA_HOME")] + JavaHome, + /// Use the built-in `JAVA_HOME_17_X64` path available on hosted agents + /// (ADO server default). + #[serde(rename = "JAVA_HOME_17_X64")] + JavaHome17X64, + /// Use the built-in `JAVA_HOME_21_X64` path available on hosted agents. + #[serde(rename = "JAVA_HOME_21_X64")] + JavaHome21X64, +} + +impl JdkVersion { + /// Returns the exact ADO token for this value. + pub fn as_ado_str(self) -> &'static str { + match self { + Self::JavaHome => "JAVA_HOME", + Self::JavaHome17X64 => "JAVA_HOME_17_X64", + Self::JavaHome21X64 => "JAVA_HOME_21_X64", + } + } +} + +/// Builder for a [`TaskStep`] invoking `SonarQubeAnalyze@8`. +/// +/// Runs the SonarQube scanner analysis and uploads results to the SonarQube +/// server. This is the second step in the three-task SonarQube workflow: +/// `SonarQubePrepare@8` → `SonarQubeAnalyze@8` → `SonarQubePublish@8`. +/// +/// All inputs are optional; calling `into_step()` with no inputs set uses +/// the ADO server-side default JDK version (`JAVA_HOME_17_X64`). +/// +/// ADO task reference: +/// +#[derive(Debug, Clone, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct SonarQubeAnalyze { + #[serde(rename = "jdkversion", default)] + jdk_version: Option, + #[serde(skip)] + display_name: Option, +} + +impl SonarQubeAnalyze { + /// Create a new builder; all inputs are optional. + pub fn new() -> Self { + Self { + jdk_version: None, + display_name: None, + } + } + + /// `jdkversion` — JDK version source for the analysis. + /// + /// Defaults to [`JdkVersion::JavaHome17X64`] on the ADO server side when + /// not set. Only emit this input to override the default. + pub fn jdk_version(mut self, version: JdkVersion) -> Self { + self.jdk_version = Some(version); + self + } + + /// Override the default `displayName` (`"Run Code Analysis"`). + pub fn with_display_name(mut self, value: impl Into) -> Self { + self.display_name = Some(value.into()); + self + } + + /// Lower into a [`TaskStep`]. + pub fn into_step(self) -> TaskStep { + let mut t = TaskStep::new( + "SonarQubeAnalyze@8", + self.display_name + .unwrap_or_else(|| "Run Code Analysis".into()), + ); + if let Some(v) = self.jdk_version { + t = t.with_input("jdkversion", v.as_ado_str()); + } + t + } +} + +impl Default for SonarQubeAnalyze { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sets_task_identifier() { + let t = SonarQubeAnalyze::new().into_step(); + assert_eq!(t.task, "SonarQubeAnalyze@8"); + } + + #[test] + fn default_display_name() { + let t = SonarQubeAnalyze::new().into_step(); + assert_eq!(t.display_name, "Run Code Analysis"); + } + + #[test] + fn no_inputs_by_default() { + let t = SonarQubeAnalyze::new().into_step(); + assert!( + t.inputs.is_empty(), + "expected no inputs when none are set; got {:?}", + t.inputs + ); + } + + #[test] + fn jdk_version_java_home() { + let t = SonarQubeAnalyze::new() + .jdk_version(JdkVersion::JavaHome) + .into_step(); + assert_eq!( + t.inputs.get("jdkversion").map(String::as_str), + Some("JAVA_HOME") + ); + } + + #[test] + fn jdk_version_java_home_17() { + let t = SonarQubeAnalyze::new() + .jdk_version(JdkVersion::JavaHome17X64) + .into_step(); + assert_eq!( + t.inputs.get("jdkversion").map(String::as_str), + Some("JAVA_HOME_17_X64") + ); + } + + #[test] + fn jdk_version_java_home_21() { + let t = SonarQubeAnalyze::new() + .jdk_version(JdkVersion::JavaHome21X64) + .into_step(); + assert_eq!( + t.inputs.get("jdkversion").map(String::as_str), + Some("JAVA_HOME_21_X64") + ); + } + + #[test] + fn display_name_override() { + let t = SonarQubeAnalyze::new() + .with_display_name("SonarQube Analysis") + .into_step(); + assert_eq!(t.display_name, "SonarQube Analysis"); + } + + #[test] + fn default_impl_matches_new() { + let t = SonarQubeAnalyze::default().into_step(); + assert_eq!(t.task, "SonarQubeAnalyze@8"); + assert!(t.inputs.is_empty()); + } +}