-
-
Notifications
You must be signed in to change notification settings - Fork 92
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
Handle lint level in check_release #805
Conversation
- if we ever want an --ignore-warnings (or --ignore-less-than LEVEL) flag, we can change the hardcoded `LintLevel::Warn` to something else - should add a test to verify correct behavior
- will be useful for tests without new API - the 12 chars is kind of arbitrary but 18 chars looked too wide
- so we can call `.max()` on the `Vec` and get an `Option<update>` - it needs to be an `Option` now because there may be nothing (e.g., only warnings fail)
- follow clippy's behavior of printing individual errors before warnings, but printing warning summary before error summary - always print number of warning checks that fail, if there are any, but only print suggested version bump if it is greater than required version bump. - refactor a little to use `max` instead of `contains` when calculating the required/suggested bump
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.
Designing CLI output is really hard: there are lots of competing objectives and it isn't always clear what the answer should be.
I'd recommend stepping away from the Rust code, and just taking some time to look at and design the printout of running the tool in various cases: when it had warnings, when it didn't have warnings, when some config was overridden (and where from!), etc.
Consider what the user would probably want to know at each stage, what additional info they might know in --verbose
mode, and what info isn't useful and can be omitted.
Consider that they are often reading the output on a terminal that's only ~75 chars wide, where wrapping text can often look really ugly unless it's specifically meant to be a paragraph and spaced accordingly.
After you have some designs you are happy with, let's review just the mock printouts for each case — no implementation yet! That will make it easier for us to make small improvements in an iterative fashion, without forcing you to constantly adjust the implementation.
In the meantime, if you want to merge the parts of this PR that don't affect the CLI output, you could split them off into a separate PR that we could merge. But that might not be worth the effort given it's just a dozen lines or so — your call.
src/check_release.rs
Outdated
queries_to_run.len() - results_with_errors.len(), | ||
results_with_errors.len(), | ||
queries_to_run.len() - results_with_errors.len() - results_with_warnings.len(), | ||
results_with_errors.len() + results_with_warnings.len(), |
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.
Might we want to separate out "failed" from "had warnings" here? Saying something failed but then exiting cleanly feels strange...
src/check_release.rs
Outdated
if results.is_empty() { | ||
writeln!( | ||
config.stderr(), | ||
"{}{:>12}{} [{:8.3}s] {:^18} {}", | ||
"{}{:>12}{} [{:8.3}s] {:^18} {:^12} {}", |
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.
I think this results in output that is too wide. Of the ~75 chars we have to play with, we've already spent almost 50 before we even print the lint name. That's too much.
Perhaps we can combine the category and lint level fields into one table column somehow?
src/check_release.rs
Outdated
|
||
config | ||
.shell_print( | ||
print_failed_lint(config, semver_query, results)?; |
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 function might be a good candidate to rename, given its new role in both warnings and failures.
src/check_release.rs
Outdated
config.shell_warn(format_args!( | ||
"warning checks suggest new {} version", | ||
suggested_bump.as_str() | ||
))?; |
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 produces text that's like warning: warning checks ...
It feels like it could use another polishing pass.
src/check_release.rs
Outdated
} | ||
|
||
config.shell_warn(format_args!( | ||
"{} major and {} minor warning checks failed", |
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.
It's strange to say "warning checks failed."
Perhaps "produced warnings" or "triggered warnings" or something to that effect?
))?; | ||
} | ||
|
||
if let Some(required_bump) = required_bump { |
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.
It's strange that by this point, we've already printed out summary information about warnings, and now we print a line titled "Summary" with more information.
So long as they are in the same PR, it usually wouldn't be particularly more helpful to me since the GitHub workflow shows a squashed diff by default. It might sometimes be more useful if I need to dig into the changes for a particular commit, but honestly if I have to dig that deep, it probably either should have been its own PR instead or there's some sort of code quality issue that shouldn't have been there in the first place. I certainly don't mind it and if you like it, you're welcome to keep using it. But if it's extra work for you, then it's probably not worth it.
Yes, it would be great to have more consistency across the entire CLI experience, and I welcome the help. The The top thing I'd worry about is a half-completed attempt to change from nextest-style to clippy-style output, because that'd spend a bunch of our energy while leaving us in the middle of nowhere. And I'd certainly love the help if you'd like to think through, formalize, write down, and apply new rules for which things go on stderr vs stdout :) |
Some thoughts:
Playing around with both nextest and clippy, both tools put everything I tested (except for --help messages) in stderr. cargo-semver-checks writes most things to stderr, but when a check fails, it prints the lint name/description/info/span to stdout. Is there a reason we print just this to stdout, or would it make sense to move this to stderr?
Here's one
Thoughts on this? The summary section is a little tricky (esp. heading names) to handle all of the different cases. |
Co-authored-by: Predrag Gruevski <[email protected]>
- show PASS/FAIL/WARN in verbose mode - print `--- failure ---` or `--- warning ---` on triggered lint - update summary - add new test for when only warnings are produced
New formatting - still don't think the summary is perfect, lmk if you have suggestions. Just warnings, no errors
Both warnings and errors
|
src/check_release.rs
Outdated
config | ||
.shell_print( | ||
"Checked", | ||
format_args!( | ||
"[{:>8.3}s] {} checks; {} passed, {} failed, {} unnecessary", | ||
"[{:>8.3}s] {} checks; {} passed, {} failed, {} warnings, {} skipped", |
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.
{} warnings
seems a bit inconsistent here, because this is describing how many lints generated at least one warning, not how many warnings were generated.
warned
looks more consistent but sounds a bit weird too.
What do you think about tweaking the whole thing like:
"[{:>8.3}s] {} checks; {} passed, {} failed, {} warnings, {} skipped", | |
"[{:>8.3}s] {} checks; {} pass, {} fail, {} warn, {} skip", |
I'm on the fence about it personally, but leaning toward it.
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.
I like it because it saves characters, but it does feel a little weird not being in past tense. It is definitely still clear and understandable, though, so I'm definitely open to changing it to this.
src/check_release.rs
Outdated
@@ -396,6 +373,41 @@ pub(super) fn run_check_release( | |||
Color::Ansi(AnsiColor::Red), | |||
true, | |||
)?; | |||
} else { |
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.
If errors or warnings got printed, let's make sure we leave an empty line between the last of those and the Summary
line to improve readability. Otherwise the summary looks visually attached to the most recently printed error or warning block.
src/check_release.rs
Outdated
@@ -407,7 +419,7 @@ pub(super) fn run_check_release( | |||
.shell_print( | |||
"Checked", | |||
format_args!( | |||
"[{:>8.3}s] {} checks; {} passed, {} unnecessary", | |||
"[{:>8.3}s] {} checks; {} passed, {} skipped", |
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.
If we end up making the passed -> pass / skipped -> skip
change I suggested above, let's make sure we apply the corresponding change here as well for consistency.
"[{:>8.3}s] {} checks; {} passed, {} skipped", | |
"[{:>8.3}s] {} checks; {} pass, {} skip", |
Honestly, this looks very good to me for a first pass. We can always polish everything more and we'll keep doing it in the future. No reason to make perfect be the enemy of good enough, let's keep moving forward. |
- change failed -> fail, etc. in `Checked` summary - add a blank line before writing summary when fails/warns were printed - add summary line when all checks pass
@@ -356,6 +356,7 @@ pub(super) fn run_check_release( | |||
let suggested_bump = suggested_versions.iter().max().copied(); | |||
|
|||
if let Some(required_bump) = required_bump { | |||
writeln!(config.stderr())?; |
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.
Are you sure config.shell_print()
goes to stderr, not stdout? We want the newline in the same stream.
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.
It does.. Almost everything writes to stderr, except the --- failure/warning ---
blocks. Does this newline belong to the Summary
block or would it make more sense to write an extra newline after every --- failure/warning ---
block?
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.
I'm open to it either way, whatever is cleaner to do.
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.
I think it makes more sense to put it with the summary block, as the --- failure ---
blocks already separate themselves with newlines, so we would be adding two newlines in between each block.
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.
Sounds good to me!
@@ -374,6 +375,7 @@ pub(super) fn run_check_release( | |||
true, | |||
)?; | |||
} else { | |||
writeln!(config.stderr())?; |
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 blank line appears to be printed if there were no errors, whether or not any warnings were produced. If no warnings were printed, I'm not sure we want a blank line there.
To me, conceptually, the blank line is the last line of each warning or error block, not a prefix to the summary line. So if there were no error or warning blocks (--- failure ...
) then I probably wouldn't include a blank line here.
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 is inside check_release.rs:299
: if produced_errors || produced_warnings
, so it must be true that if no errors are produced, some warnings are produced, although this isn't expressed well in the code. Would it make sense to change the else
to an else if warnings_produced {...} else { unreachable!() }
to be more expressive?
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.
Yes, please! That would definitely have been more clear, and it's safer against future refactoring too.
Co-authored-by: Predrag Gruevski <[email protected]>
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.
I think this looks really good, very nicely done!
I have a couple of minor nitpicks and suggestions for reducing future maintainability cost, but they don't have to block this PR. I trust you to address them in a subsequent PR, so I'll merge this and we can move forward.
(true, _) => ("PASS", AnsiColor::Green), | ||
(false, LintLevel::Deny) => ("FAIL", AnsiColor::Red), | ||
(false, LintLevel::Warn) => ("WARN", AnsiColor::Yellow), | ||
(false, LintLevel::Allow) => unreachable!(), |
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.
Having seen and debugged a decent number of cases where an unreachable!()
without any error message was hit, I've started to feel that it's worth spending an extra 10s to add more context to the crash (so it becomes more obvious and easily reproducible) than to spend many hours trying to reproduce a finicky error case triggered by unknown conditions.
Usually, in a situation where an unreachable statement got hit, the first change we'll make is add more context to the error so we understand what's going on. Adding that context up front doesn't meaningfully slow down development, but it makes for a much less frustrating debugging experience.
(false, LintLevel::Allow) => unreachable!(), | |
(false, LintLevel::Allow) => { | |
unreachable!( | |
"lint was unexpectedly executed at LintLevel::Allow: {semver_query:?}" | |
) | |
} |
match overrides.effective_lint_level(semver_query) { | ||
LintLevel::Deny => results_with_errors.push((semver_query, results)), | ||
LintLevel::Warn => results_with_warnings.push((semver_query, results)), | ||
_ => (), |
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 is probably unreachable too, right?
Am I correct in thinking that the invariant is "LintLevel::Allow
lints are skipped entirely, because there's no point in running them"?
true, | ||
)?; | ||
} else { | ||
unreachable!(); |
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.
If there's a good description of what appears to have gone wrong for us to hit this, it would be great to put it here.
Even if right now it may seem obvious to you and me, we're still 200+ lines into a very complex function, and we might not think it's so obvious in 6 months.
trying something new with smaller, annotated commits, lmk if it is helpful or not.
This PR handles lint level in
check_release
, especially differentiating warnings from errors. It now skips running any checks marked asallow
, prints errors and warnings differently, and the summary now reflects the required version bump (from error/deny checks) and the suggested version bump (from warning checks, if it is higher than the required bump).example command output
Commits:
In addition to adding more tests, one other thing to work on is increasing the consistency in formatted text, if we want it to be similar to tools like clippy, for instance. I tried to make it internally consistent between warnings/errors, but the program has certain behaviors such as printing some lint failure info to stdout and some to stderr that we could fix by following what linters like clippy do, if we want that.