Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add initial lint level config #784

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
298 changes: 163 additions & 135 deletions src/check_release.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ use anyhow::Context;
use clap::crate_version;
use itertools::Itertools;
use rayon::prelude::*;
use trustfall::TransparentValue;
use trustfall::{FieldValue, TransparentValue};
use trustfall_rustdoc::{VersionedCrate, VersionedIndexedCrate, VersionedRustdocAdapter};

use crate::{
query::{ActualSemverUpdate, RequiredSemverUpdate, SemverQuery},
query::{ActualSemverUpdate, LintLevel, QueryOverrideList, RequiredSemverUpdate, SemverQuery},
CrateReport, GlobalConfig, ReleaseType,
};

Expand Down Expand Up @@ -64,13 +64,106 @@ fn classify_semver_version_change(
}
}

fn print_lint_failure(
config: &mut GlobalConfig,
semver_query: &SemverQuery,
results: Vec<BTreeMap<Arc<str>, FieldValue>>,
) -> anyhow::Result<()> {
if let Some(ref_link) = semver_query.reference_link.as_deref() {
config.log_info(|config| {
writeln!(config.stdout(), "{}Description:{}\n{}\n{:>12} {}\n{:>12} https://github.com/obi1kenobi/cargo-semver-checks/tree/v{}/src/lints/{}.ron\n",
Style::new().bold(), Reset,
&semver_query.error_message,
"ref:",
ref_link,
"impl:",
crate_version!(),
semver_query.id,
)?;
Ok(())
})?;
} else {
config.log_info(|config| {
writeln!(
config.stdout(),
"{}Description:{}\n{}\n{:>12} https://github.com/obi1kenobi/cargo-semver-checks/tree/v{}/src/lints/{}.ron",
Style::new().bold(),
Reset,
&semver_query.error_message,
"impl:",
crate_version!(),
semver_query.id,
)?;
Ok(())
})?;
}

config.log_info(|config| {
writeln!(
config.stdout(),
"{}Failed in:{}",
Style::new().bold(),
Reset
)?;
Ok(())
})?;

for semver_violation_result in results {
let pretty_result: BTreeMap<Arc<str>, TransparentValue> = semver_violation_result
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect();

if let Some(template) = semver_query.per_result_error_template.as_deref() {
let message = config
.handlebars()
.render_template(template, &pretty_result)
.context("Error instantiating semver query template.")
.expect("could not materialize template");
config.log_info(|config| {
writeln!(config.stdout(), " {}", message)?;
Ok(())
})?;

config.log_extra_verbose(|config| {
let serde_pretty =
serde_json::to_string_pretty(&pretty_result).expect("serde failed");
let indented_serde = serde_pretty
.split('\n')
.map(|line| format!(" {line}"))
.join("\n");
writeln!(
config.stdout(),
"\tlint rule output values:\n{}",
indented_serde
)?;
Ok(())
})?;
} else {
config.log_info(|config| {
writeln!(
config.stdout(),
"{}\n",
serde_json::to_string_pretty(&pretty_result)?
)?;
Ok(())
})?;
}
}

Ok(())
}

pub(super) fn run_check_release(
config: &mut GlobalConfig,
crate_name: &str,
current_crate: VersionedCrate,
baseline_crate: VersionedCrate,
release_type: Option<ReleaseType>,
overrides: QueryOverrideList,
) -> anyhow::Result<CrateReport> {
let queries = SemverQuery::all_queries();

let current_version = current_crate.crate_version();
let baseline_version = baseline_crate.crate_version();

Expand Down Expand Up @@ -100,9 +193,10 @@ pub(super) fn run_check_release(
let previous = VersionedIndexedCrate::new(&baseline_crate);
let adapter = VersionedRustdocAdapter::new(&current, Some(&previous))?;

let (queries_to_run, queries_to_skip): (Vec<_>, _) = SemverQuery::all_queries()
.into_values()
.partition(|query| !version_change.supports_requirement(query.required_update));
let (queries_to_run, queries_to_skip): (Vec<_>, _) = queries.values().partition(|query| {
!version_change.supports_requirement(overrides.effective_required_update(query))
&& overrides.effective_lint_level(query) >= LintLevel::Warn
});
let skipped_queries = queries_to_skip.len();

config.shell_status(
Expand Down Expand Up @@ -155,11 +249,12 @@ pub(super) fn run_check_release(
})
.collect::<anyhow::Result<Vec<_>>>()?;

let mut results_with_errors = vec![];
let mut error_results = vec![];
let mut warning_results = vec![];
for (semver_query, time_to_decide, results) in all_results {
config
.log_verbose(|config| {
let category = match semver_query.required_update {
let category = match overrides.effective_required_update(semver_query) {
RequiredSemverUpdate::Major => "major",
RequiredSemverUpdate::Minor => "minor",
};
Expand Down Expand Up @@ -194,166 +289,99 @@ pub(super) fn run_check_release(
})
.expect("print failed");
if !results.is_empty() {
results_with_errors.push((semver_query, results));
if overrides.effective_lint_level(semver_query) == LintLevel::Deny {
error_results.push((semver_query, results));
} else {
warning_results.push((semver_query, results));
}
}
}

if !results_with_errors.is_empty() {
let failed_checks = error_results.len() + warning_results.len();
if failed_checks > 0 {
config
.shell_print(
"Checked",
format_args!(
"[{:>8.3}s] {} checks; {} passed, {} failed, {} unnecessary",
queries_start_instant.elapsed().as_secs_f32(),
queries_to_run.len(),
queries_to_run.len() - results_with_errors.len(),
results_with_errors.len(),
queries_to_run.len() - failed_checks,
failed_checks,
skipped_queries,
),
Color::Ansi(AnsiColor::Red),
true,
)
.expect("print failed");

let mut required_versions = vec![];

for (semver_query, results) in results_with_errors {
required_versions.push(semver_query.required_update);
config
.log_info(|config| {
writeln!(
config.stdout(),
"\n--- failure {}: {} ---\n",
&semver_query.id,
&semver_query.human_readable_name
)?;
Ok(())
})
.expect("print failed");

if let Some(ref_link) = semver_query.reference_link.as_deref() {
config.log_info(|config| {
writeln!(config.stdout(), "{}Description:{}\n{}\n{:>12} {}\n{:>12} https://github.com/obi1kenobi/cargo-semver-checks/tree/v{}/src/lints/{}.ron\n",
Style::new().bold(), Reset,
&semver_query.error_message,
"ref:",
ref_link,
"impl:",
crate_version!(),
semver_query.id,
)?;
Ok(())
})
.expect("print failed");
} else {
config.log_info(|config| {
writeln!(
config.stdout(),
"{}Description:{}\n{}\n{:>12} https://github.com/obi1kenobi/cargo-semver-checks/tree/v{}/src/lints/{}.ron",
Style::new().bold(),
Reset,
&semver_query.error_message,
"impl:",
crate_version!(),
semver_query.id,
)?;
Ok(())
})
.expect("print failed");
}
let mut warnings = BTreeMap::new();
let mut errors = BTreeMap::new();
// print errors before warnings like clippy does
for (semver_query, results) in error_results {
errors
.entry(overrides.effective_required_update(semver_query))
.and_modify(|e| *e += 1)
.or_insert(1);

config.shell_error(format!(
"{}: {}",
&semver_query.id, &semver_query.human_readable_name
))?;
print_lint_failure(config, semver_query, results)?;
}

config
.log_info(|config| {
writeln!(
config.stdout(),
"{}Failed in:{}",
Style::new().bold(),
Reset
)?;
Ok(())
})
.expect("print failed");
for (semver_query, results) in warning_results {
warnings
.entry(overrides.effective_required_update(semver_query))
.and_modify(|e| *e += 1)
.or_insert(1);

config.shell_warn(format!(
"{}: {}",
&semver_query.id, &semver_query.human_readable_name
))?;
print_lint_failure(config, semver_query, results)?;
}

for semver_violation_result in results {
let pretty_result: BTreeMap<Arc<str>, TransparentValue> = semver_violation_result
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect();

if let Some(template) = semver_query.per_result_error_template.as_deref() {
let message = config
.handlebars()
.render_template(template, &pretty_result)
.context("Error instantiating semver query template.")
.expect("could not materialize template");
config
.log_info(|config| {
writeln!(config.stdout(), " {}", message)?;
Ok(())
})
.expect("print failed");

config
.log_extra_verbose(|config| {
let serde_pretty =
serde_json::to_string_pretty(&pretty_result).expect("serde failed");
let indented_serde = serde_pretty
.split('\n')
.map(|line| format!(" {line}"))
.join("\n");
writeln!(
config.stdout(),
"\tlint rule output values:\n{}",
indented_serde
)?;
Ok(())
})
.expect("print failed");
} else {
config
.log_info(|config| {
writeln!(
config.stdout(),
"{}\n",
serde_json::to_string_pretty(&pretty_result)?
)?;
Ok(())
})
.expect("print failed");
let required_bump = errors.last_key_value().map(|x| *x.0);
let suggested_bump = warnings.last_key_value().map(|x| *x.0);

if let Some(suggested_bump) = suggested_bump {
config.shell_warn(format!(
"generated {} major- and {} minor-version warnings",
warnings.get(&RequiredSemverUpdate::Major).unwrap_or(&0),
warnings.get(&RequiredSemverUpdate::Minor).unwrap_or(&0),
))?;
match required_bump {
// no need to warn about warnings' version bump if it is the
// same or less extreme than the required version bump
Some(x) if x >= suggested_bump => {}
_ => {
config.shell_warn(format!(
"warnings suggest a new {} version",
suggested_bump.as_str()
))?;
}
}
}

let required_bump = if required_versions.contains(&RequiredSemverUpdate::Major) {
RequiredSemverUpdate::Major
} else if required_versions.contains(&RequiredSemverUpdate::Minor) {
RequiredSemverUpdate::Minor
} else {
unreachable!("{:?}", required_versions)
};

config
.shell_print(
if let Some(required_bump) = required_bump {
config.shell_print(
"Summary",
format_args!(
"semver requires new {} version: {} major and {} minor checks failed",
required_bump.as_str(),
required_versions
.iter()
.filter(|x| *x == &RequiredSemverUpdate::Major)
.count(),
required_versions
.iter()
.filter(|x| *x == &RequiredSemverUpdate::Minor)
.count(),
errors.get(&RequiredSemverUpdate::Major).unwrap_or(&0),
errors.get(&RequiredSemverUpdate::Minor).unwrap_or(&0),
),
Color::Ansi(AnsiColor::Red),
true,
)
.expect("print failed");
)?;
}

Ok(CrateReport {
required_bump: Some(required_bump.into()),
required_bump: required_bump.map(Into::into),
detected_bump: version_change,
})
} else {
Expand Down
4 changes: 4 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ impl GlobalConfig {
self.shell_print("warning", message, Color::Ansi(AnsiColor::Yellow), false)
}

pub fn shell_error(&mut self, message: impl std::fmt::Display) -> anyhow::Result<()> {
self.shell_print("error", message, Color::Ansi(AnsiColor::Red), false)
}

/// Gets the color-supporting `stdout` that the crate will use.
///
/// See [`GlobalConfig::set_stdout`] and [`GlobalConfig::set_out_color_choice`] to
Expand Down
Loading
Loading