-
Notifications
You must be signed in to change notification settings - Fork 2
Tolerate recorded violations in strict mode (Part B of #43) #45
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
base: main
Are you sure you want to change the base?
Changes from all commits
e198118
7b22b86
22bc710
bfec99d
03ce358
c131362
7ba0136
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,20 @@ pub struct ViolationIdentifier { | |
| pub referencing_pack_name: String, | ||
| pub defining_pack_name: String, | ||
| } | ||
|
|
||
| impl ViolationIdentifier { | ||
| /// `strict` describes how a violation should be treated, not which violation | ||
| /// it is, and `package_todo.yml` has nowhere to record it, so recorded | ||
| /// violations are always rebuilt with `strict: false`. Compare through this | ||
| /// so a violation in a strict pack can still match its recorded entry. | ||
| pub(crate) fn recorded_key(&self) -> Self { | ||
| Self { | ||
| strict: false, | ||
| ..self.clone() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// A violation combines an identifier with display metadata. | ||
| /// | ||
| /// `source_location` is intentionally separate from `ViolationIdentifier` because: | ||
|
|
@@ -124,7 +138,7 @@ impl<'a> CheckAllBuilder<'a> { | |
| .cloned() | ||
| .collect(), | ||
| strict_mode_violations: self | ||
| .build_strict_mode_violations() | ||
| .build_strict_mode_violations(recorded_violations) | ||
| .into_iter() | ||
| .collect(), | ||
| }) | ||
|
|
@@ -142,7 +156,10 @@ impl<'a> CheckAllBuilder<'a> { | |
| self.found_violations | ||
| .violations | ||
| .iter() | ||
| .filter(|v| !recorded_violations.contains(&v.identifier)) | ||
| .filter(|v| { | ||
| !recorded_violations | ||
| .contains(&v.identifier.recorded_key()) | ||
| }) | ||
| .collect() | ||
| }; | ||
| reportable_violations | ||
|
|
@@ -152,11 +169,11 @@ impl<'a> CheckAllBuilder<'a> { | |
| &mut self, | ||
| recorded_violations: &'a HashSet<ViolationIdentifier>, | ||
| ) -> anyhow::Result<Vec<&'a ViolationIdentifier>> { | ||
| let found_violation_identifiers: HashSet<&ViolationIdentifier> = self | ||
| let found_violation_identifiers: HashSet<ViolationIdentifier> = self | ||
| .found_violations | ||
| .violations | ||
| .par_iter() | ||
| .map(|v| &v.identifier) | ||
| .map(|v| v.identifier.recorded_key()) | ||
| .collect(); | ||
| let relative_files = self | ||
| .found_violations | ||
|
|
@@ -196,9 +213,13 @@ impl<'a> CheckAllBuilder<'a> { | |
| Ok(stale_violations) | ||
| } | ||
|
|
||
| /// `found_violation_identifiers` is keyed by [`ViolationIdentifier::recorded_key`]. | ||
| /// `todo_violation_identifier` needs no such normalization: it comes from | ||
| /// `pack_set.all_violations`, which rebuilds every recorded violation with | ||
| /// `strict: false` already, so it is its own recorded key. | ||
| fn is_stale_violation( | ||
| relative_files: &HashSet<&str>, | ||
| found_violation_identifiers: &HashSet<&ViolationIdentifier>, | ||
| found_violation_identifiers: &HashSet<ViolationIdentifier>, | ||
| todo_violation_identifier: &ViolationIdentifier, | ||
| ) -> bool { | ||
| let violation_path_exists = | ||
|
|
@@ -210,11 +231,23 @@ impl<'a> CheckAllBuilder<'a> { | |
| } | ||
| } | ||
|
|
||
| fn build_strict_mode_violations(&self) -> Vec<Violation> { | ||
| /// Strict mode reports violations that are not already recorded in a | ||
| /// `package_todo.yml`, matching packwerk's `unlisted_strict_mode_violations` | ||
| /// (Shopify/packwerk#368). Turning strict on therefore blocks new violations | ||
| /// without also requiring every recorded one to be fixed first. | ||
| fn build_strict_mode_violations( | ||
| &self, | ||
| recorded_violations: &HashSet<ViolationIdentifier>, | ||
| ) -> Vec<Violation> { | ||
| self.found_violations | ||
| .violations | ||
| .iter() | ||
| .filter(|v| v.identifier.strict) | ||
| .filter(|v| { | ||
| self.configuration.ignore_recorded_violations | ||
| || !recorded_violations | ||
| .contains(&v.identifier.recorded_key()) | ||
| }) | ||
| .cloned() | ||
| .collect() | ||
| } | ||
|
|
@@ -302,22 +335,35 @@ pub(crate) fn update(configuration: &Configuration) -> anyhow::Result<()> { | |
| &checkers, | ||
| )?; | ||
|
|
||
| let strict_violations = &violations | ||
| let recorded_violations = &configuration.pack_set.all_violations; | ||
|
|
||
| // Only *unlisted* strict violations make `check` fail, so only those are | ||
| // worth reporting here. Reporting recorded ones too claimed `check` would | ||
| // fail when it succeeds. Same filter as `build_strict_mode_violations`, and | ||
| // as packwerk's `unlisted_strict_mode_violations`. | ||
| let unlisted_strict_violations = &violations | ||
| .iter() | ||
| .filter(|v| v.identifier.strict) | ||
| .filter(|v| !recorded_violations.contains(&v.identifier.recorded_key())) | ||
| .collect::<Vec<&Violation>>(); | ||
| if !strict_violations.is_empty() { | ||
| for violation in strict_violations { | ||
| if !unlisted_strict_violations.is_empty() { | ||
| for violation in unlisted_strict_violations { | ||
| let strict_message = | ||
| build_strict_violation_message(&violation.identifier); | ||
| println!("{}", strict_message); | ||
| } | ||
| println!( | ||
| "{} strict mode violation(s) detected. These violations must be fixed for `check` to succeed.", | ||
| &strict_violations.len() | ||
| &unlisted_strict_violations.len() | ||
| ); | ||
| // TODO: packwerk's `update-todo` exits non-zero here; `update` returns | ||
| // Ok and prints a success line. Pre-existing, separate breaking change. | ||
| } | ||
| package_todo::write_violations_to_disk(configuration, violations); | ||
| package_todo::write_violations_to_disk( | ||
| configuration, | ||
| violations, | ||
| recorded_violations, | ||
| ); | ||
| println!("Successfully updated package_todo.yml files!"); | ||
|
Comment on lines
+362
to
367
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still pre-existing and still not yours to fix. Raising it once more only because this PR edits this block and adds a comment asserting the filter matches packwerk's packwerk treats this state as a failure and pks reports success. A follow-up issue or a one-line note that it is out of scope would settle it either way. |
||
|
|
||
| Ok(()) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer}; | |
| use std::collections::{BTreeMap, HashMap, HashSet}; | ||
| use tracing::debug; | ||
|
|
||
| use super::checker::ViolationIdentifier; | ||
| use super::{pack::Pack, Configuration, Violation}; | ||
|
|
||
| #[derive(PartialEq, Debug, Eq, Deserialize, Serialize, Default, Clone)] | ||
|
|
@@ -133,6 +134,7 @@ pub fn package_todos_for_pack_name( | |
| pub fn write_violations_to_disk( | ||
| configuration: &Configuration, | ||
| violations: HashSet<Violation>, | ||
| recorded_violations: &HashSet<ViolationIdentifier>, | ||
| ) { | ||
| debug!("Starting writing violations to disk"); | ||
| // First we need to group the violations by the responsible pack, which today is always the referencing pack | ||
|
|
@@ -141,7 +143,18 @@ pub fn write_violations_to_disk( | |
| let mut violations_by_responsible_pack: HashMap<String, Vec<Violation>> = | ||
| HashMap::new(); | ||
| for violation in violations { | ||
| if violation.identifier.strict { | ||
| // An *unlisted* strict violation is never recorded, so `update` cannot | ||
| // be used to silence strict mode. An already-recorded one has to be | ||
| // re-written, because `check` now tolerates recorded violations in | ||
| // strict packs and `PackageTodo` is dumped wholesale from these | ||
| // entries — dropping it here would delete the record that made the | ||
| // build green and fail the next `check` with no source change in | ||
| // between. packwerk keeps the entry for the same reason, in | ||
| // `OffenseCollection#add_offense`. | ||
| if violation.identifier.strict | ||
| && !recorded_violations | ||
| .contains(&violation.identifier.recorded_key()) | ||
| { | ||
| continue; | ||
|
Comment on lines
+146
to
158
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is right, and the comment explaining why is welcome. The gap is on the other side of it: nothing pins that Behavior is correct today, I checked. Deleting the recorded reference gets "There were stale violations found" from But the comment says an already-recorded strict violation "has to be re-written", and the natural over-correction to that is to union That is the highest-value test this PR is missing. A |
||
| } | ||
| let referencing_pack_name = | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This retitling is correct, and the evidence is stronger than the PR description claims.
git log main --oneline -- CHANGELOG.mdreturns exactly one commit,2fe98b7, so the entire old## Unreleasedsection originated in a single pre-0.4.0 commit. Nothing shipped is left under Unreleased and nothing unshipped is being labelled as released. Tags do exist,v0.4.0resolves to "Bump version to 0.4.0 for gitignore release (#38)", andCargo.tomlonmainagrees at0.4.0.Agreed on leaving the version bump to a release PR, which matches how #25 and #38 were done.
One thing to flag for whoever cuts 0.5.0, since it makes these headings functional rather than bookkeeping:
release.yml:281builds the GitHub Release body fromannouncement_github_body, which cargo-dist derives by matching aCHANGELOG.mdheading against the version being tagged. So## Unreleasedhas to be retitled to## 0.5.0in the bump PR, or this entry silently will not reach the release notes.