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 coding style tool #681

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open

Add coding style tool #681

wants to merge 10 commits into from

Conversation

h3ndrk
Copy link
Member

@h3ndrk h3ndrk commented Jan 14, 2024

Introduced Changes

This PR adds the "checky" tool that iterates over all Rust files in our repository and checks them for the following coding style rules:

  • mod/use rules
    • mod must appear before use
    • use must be in the following order: std, extern crates, crate
    • all use items of the same category must be ordered: pub, crate, pub(...), ``
    • all these major and minor ordering groups must be separated with empty lines
  • empty line rules
    • enum, fn, impl, struct, and trait must be separated by empty lines
    • fn within impl must be separated by empty lines

ToDo / Known Issues

/dev/null

Ideas for Next Iterations (Not This PR)

  • Be that restrictive with empty lines in mod/use rules?
  • Add --fix support
  • Integrate in pepsi clippy e.g. by moving the code in an extra crate and use it from Pepsi
  • Add this tool to our CI
  • Add rules for: Add WrapErr, do not capitalize

How to Test

Run cargo run --manifest-path=tools/checky/Cargo.toml from the root directory of the repository.

@schmidma
Copy link
Member

for sorting granularity of use statements: https://rust-lang.github.io/rustfmt/?version=v1.7.0&search=#group_imports

regarding order of mod and use statements, the rust style guide differs to your proposal: https://doc.rust-lang.org/beta/style-guide/items.html#items

Put imports before module declarations.

for blank lines between items: https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#blank_lines_lower_bound

@oleflb
Copy link
Contributor

oleflb commented Jan 17, 2024

We talked about this in the devmeeting and decided we don't want to maintain another tool since rustfmt can take care of the things you listed. If you want, you can create a correct rustfmt.toml file that enforces these behaviors.

@h3ndrk
Copy link
Member Author

h3ndrk commented Jan 17, 2024

You are proposing to use rustfmt instead. These features are not available in stable rustfmt.

for sorting granularity of use statements: https://rust-lang.github.io/rustfmt/?version=v1.7.0&search=#group_imports

This feature is sadly unstable. It also does not implement "all use items of the same category must be ordered: pub, crate, pub(...), ...".

regarding order of mod and use statements, the rust style guide differs to your proposal: https://doc.rust-lang.org/beta/style-guide/items.html#items

We can change that to comply with the style guide, that is no problem. For me consistency is the important thing. This style guide seems to be Beta, so not official?

for blank lines between items: https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#blank_lines_lower_bound

This feature is sadly also unstable. Hopefully we don't get the empty lines in the beginning of functions?

We talked about this in the devmeeting and decided we don't want to maintain another tool since rustfmt can take care of the things you listed. If you want, you can create a correct rustfmt.toml file that enforces these behaviors.

Oh, that was a fast decision. I hoped to argue for my solution but I was late to the meeting due to work as I mentioned earlier.

So do I conclude correctly that we would be fine to require our developers to use an unstable toolchain to use the unstable features? Until now, this was a no-go? 🤔

@oleflb
Copy link
Contributor

oleflb commented Feb 14, 2024

After todays discussion I'm more open to this PR, since I thought previously this PR would introduce another external tool which I'd not have been a fan of. Since @h3ndrk understandably wants formatting rules for this use cases which are not covered by rustfmt yet, I think we can use this tool until the respective checks are stable in rustfmt. Despite that I would like to hear the opinion @schmidma, since he originally also was not a fan of this extra tool.

Copy link
Member

@schmidma schmidma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left many ideas for improvement. Feel free to discuss ;)

tools/checky/src/checks/mod.rs Outdated Show resolved Hide resolved
tools/checky/src/main.rs Outdated Show resolved Hide resolved
tools/checky/src/main.rs Outdated Show resolved Hide resolved
tools/checky/src/main.rs Outdated Show resolved Hide resolved
tools/checky/src/main.rs Outdated Show resolved Hide resolved
tools/checky/src/checks/empty_lines.rs Outdated Show resolved Hide resolved
tools/checky/src/checks/mod_use_order.rs Outdated Show resolved Hide resolved
tools/checky/src/checks/mod_use_order.rs Outdated Show resolved Hide resolved
tools/checky/src/checks/mod_use_order.rs Outdated Show resolved Hide resolved
tools/checky/src/checks/mod_use_order.rs Outdated Show resolved Hide resolved
@schmidma schmidma self-assigned this Apr 5, 2024
@schmidma
Copy link
Member

schmidma commented May 5, 2024

This branch has conflicts ;)

@h3ndrk h3ndrk force-pushed the checky branch 3 times, most recently from 12bfb08 to 7fda14a Compare May 9, 2024 17:15
@h3ndrk
Copy link
Member Author

h3ndrk commented May 9, 2024

@schmidma Please have a look again. The tool is largely rewritten to comply with our coding standards. It also features much better error reports thanks to a third-party crate.

The mod_use_order check is currently disabled because we should discuss the order that we want and this needs to be implemented then.

@schmidma
Copy link
Member

This branch has conflicts and is also not passing the CI 😉

Copy link
Member

@schmidma schmidma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some remarks and suggestions

Comment on lines 13 to 18
#[derive(Parser, Debug)]
struct Arguments {
paths: Vec<PathBuf>,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide docstrings (///) such that clap automatically generates proper help pages

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


#[derive(Parser, Debug)]
struct Arguments {
paths: Vec<PathBuf>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you somehow tell clap that the user should provide at least one path? currently, the tool also successfully completes when no paths are given

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

Comment on lines +30 to +39
if !entry
.path()
.extension()
.map(|extension| extension == "rs")
.unwrap_or_default()
{
continue;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I provide a single non-rust file, I'd expect this tool to fail. Currently it silently runs to completion.

e.g.

cargo run --manifest-path tools/checky/Cargo.toml -- Cargo.toml

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a suggestion how to implement that without adding a lot of complexity in this file?

let file = RustFile::try_parse(entry.path())
.wrap_err_with(|| format!("failed to parse {}", entry.path().display()))?;

for report in check(&file) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for report in check(&file) {
let reports = check(&file);
for report in reports {

Just because this is where the magic happens...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

pub fn check(file: &RustFile) -> Vec<Report> {
let mut reports = Vec::new();
reports.extend(empty_lines::check(file));
// reports.extend(mod_use_order::check(file));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it disabled? If it is dead code, we should remove it for now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to first decide on the way how we want to order that. I forgot what we decided in some Dev-Meeting. So maybe you need to decide it again. Then we can enable the check and fix all wrong mod-use-orders.


pub fn check(file: &RustFile) -> Vec<Report> {
let mut reports = Vec::new();
reports.extend(empty_lines::check(file));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion for the check API, as some check may want to have configuration parameters:

pub trait Check {
  fn check(&self) -> impl IntoIterator<Item = Report>;
}

// [...]

let empty_lines = EmptyLines::new(/*possible setup here, read from toml etc./*);
reports.extend(empty_lines.check(file));

or maybe even additionally

pub trait Check {
  fn check(&self, context: &mut ReportContext) -> impl IntoIterator<Item = Report>;
}

which hides the allocation in Vec<Report> or even directly prints them. This way the check only calls something like context.report(...)

What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried quickly but this requires to think a bit about lifetimes etc. which is too much for me in this PR. Either someone takes this over or we could do this in a next iteration.

file.read_to_string(&mut buffer).wrap_err_with(|| {
format!("failed to read file {} to string", path.as_ref().display())
})?;
let path = path.as_ref().to_path_buf();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect this to be cloned at the end of the function and not here, and then reborrowed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the .to_path_buf() at the end. Did you mean this? 🤔

) -> Option<Report<'a>> {
let line_before = before.span().end().line;
let line_after = after.span().start().line;
if line_before + 1 == line_after {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if line_before + 1 == line_after {
let something_with_subsequent = line_before + 1 == line_after;
if something_with_subsequent {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Named the documentation variable is_subsequent.

Comment on lines 62 to 67
let Some(line_before) = file.source.line(line_before - 1) else {
panic!("line {line_before} does not exist");
};
let Some(line_after) = file.source.line(line_after - 1) else {
panic!("line {line_after} does not exist");
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are .expect() calls, are they?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now the format string is created regardless if there was a None but maybe that is fine. .expect() only takes &str.

Comment on lines 83 to 85
} else {
None
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactor for early returns

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored

@schmidma
Copy link
Member

Do we want to revive this? Or do you want to pass this to someone else @h3ndrk ?

@h3ndrk
Copy link
Member Author

h3ndrk commented Jul 31, 2024

Feel free to take this over. I fixed some things but the mod-use-order check still needs some work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tools:CI tools:Tooling Related to pepsi et.al.
Projects
Status: Open
Development

Successfully merging this pull request may close these issues.

4 participants