Skip to content

Added new lint "path_join_correction" #10663

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

Closed
wants to merge 16 commits into from
Closed

Conversation

ofeeg
Copy link
Contributor

@ofeeg ofeeg commented Apr 17, 2023

Thank you for making Clippy better!

We're collecting our changelog from pull request descriptions.
If your PR only includes internal changes, you can just write
changelog: none. Otherwise, please write a short comment
explaining your change.

It's also helpful for us that the lint name is put within backticks (` `),
and then encapsulated by square brackets ([]), for example:

changelog: [`lint_name`]: your change

If your PR fixes an issue, you can add fixes #issue_number into this
PR description. This way the issue will be automatically closed when
your PR is merged.

If you added a new lint, here's a checklist for things that will be
checked during review or continuous integration.

  • Followed lint naming conventions
  • Added passing UI tests (including committed .stderr file)
  • cargo test passes locally
  • Executed cargo dev update_lints
  • Added lint documentation
  • Run cargo dev fmt

Note that you can skip the above if you are just opening a WIP PR in
order to get feedback.

Delete this line and everything above before opening your PR.


Please write a short comment explaining your change (or "none" for internal only changes)

changelog: Added lint resolving issue #10655

@rustbot
Copy link
Collaborator

rustbot commented Apr 17, 2023

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @xFrednet (or someone else) soon.

Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (S-waiting-on-review and S-waiting-on-author) stays updated, invoking these commands when appropriate:

  • @rustbot author: the review is finished, PR author should check the comments and take action accordingly
  • @rustbot review: the author is ready for a review, this PR will be queued again in the reviewer's queue

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Apr 17, 2023
Copy link
Member

@xFrednet xFrednet left a comment

Choose a reason for hiding this comment

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

Welcome to Clippy 👋. This taught me something new about the behavior of Path::join(). I think this is an excellent lint, which should be warn-by-default. You already have a good start and added the lint at the right location. I've left comments, how to improve the implementation.

If you require any further assistance, you're very welcome to reach out :)


Also, small side note: The commits are not linked to your user account, probably because they use a different email. For as that is totally fine, and we can merge it with that, but I wanted to make you aware in case that is unintentional :)

@@ -3655,6 +3678,7 @@ impl Methods {
if let Some(("collect", _, _, span, _)) = method_call(recv) {
unnecessary_join::check(cx, expr, recv, join_arg, span);
}
else {path_join_correction::check(cx, expr, join_arg, span);}
Copy link
Member

Choose a reason for hiding this comment

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

Clippy uses spaces for indention, sadly rustfmt, doesn't work with if-let chains which are used in this function. Could you ensure that everything uses spaces here? :)

Suggested change
else {path_join_correction::check(cx, expr, join_arg, span);}
else {path_join_correction::check(cx, expr, join_arg, span);}

Comment on lines 3221 to 3234
/// ### What it does
/// Checks for initial '/' in an argument to .join on a path.
/// ### Why is this bad?
/// .join() calls on paths already attach the '/'.
/// ### Example
/// ```rust
/// // example code where clippy issues a warning
/// let path = std::path::Path::new("/bin");
/// path.join("/sh");
/// ```
/// Use instead:
/// ```rust
/// // path.join("sh");
/// ```
Copy link
Member

Choose a reason for hiding this comment

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

Some smaller formatting adjustments :)

    /// Checks for initial `'/'` in an argument to `.join()` on a `Path`.
    ///
    /// ### Why is this bad?
    /// `.join()` comments starting with a separator, can replace the entire path.
    /// If this is intentional, prefer creating a new `Path` instead.
    ///
    /// See [`Path::join()`](https://doc.rust-lang.org/std/path/struct.Path.html#method.join)
    /// 
    /// ### Example
    /// ```rust
    /// let path = std::path::Path::new("/bin");
    /// let res = path.join("/sh");
    /// assert_eq!(res, PathBuf::from("/sh"));
    /// ```
    ///
    /// Use instead:
    /// ```rust
    /// let path = std::path::Path::new("/bin");
    ///
    /// // If this was unintentional, remove the leading separator
    /// let extend = path.join("sh");
    /// assert_eq!(extend, PathBuf::from("/bin/sh"));
    ///
    /// // If this was intentional, create a new path instead
    /// let new = Path::new("/sh")
    /// assert_eq!(new PathBuf::from("/sh"));
    /// ```

Edit: I've just checked the documentation for Path::join and it turns out that .join("/xyz") actually behaves differently than I would first expect, as it actually replaces the path. I'd say that we should actually make this lint warn by default, in the suspicious category. The disadvantage is, that we have to construct two suggestions, one for the case that they intended this behavior and one if they didn't. I'll explain more below.


pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, join_arg: &'tcx Expr<'tcx>, span: Span) {
let applicability = Applicability::MachineApplicable;
if_chain!(
Copy link
Member

Choose a reason for hiding this comment

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

This is sadly another case, where rustfmt doesn't work correctly. Could you try fixing the indention here? :)

if_chain!(
if let ExprKind::Lit(spanned) = &join_arg.kind;
if let LitKind::Str(symbol, _) = spanned.node;
if symbol.as_str().starts_with('/');
Copy link
Member

Choose a reason for hiding this comment

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

Here we could check for both separators '/' and '\\'

/// // path.join("sh");
/// ```
#[clippy::version = "1.70.0"]
pub PATH_JOIN_CORRECTION,
Copy link
Member

Choose a reason for hiding this comment

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

I would suggest a different name, as #![warn(path_join_correction)] doesn't really explain what the lint does. Maybe extending_path_with_separator would be better, even if it's longer :). That name is also generic enough to include PathBuf.push(). We don't need to include that in this iteration, but that would be a nice addition for a follow-up.

Copy link
Member

Choose a reason for hiding this comment

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

I personally think the original lint name (join_absolute_path) is the perfect fit for this lint. It lints when you use the join method with an absolute path. It's quite literal and illustrative.

pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, join_arg: &'tcx Expr<'tcx>, span: Span) {
let applicability = Applicability::MachineApplicable;
if_chain!(
if let ExprKind::Lit(spanned) = &join_arg.kind;
Copy link
Member

Choose a reason for hiding this comment

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

Here, it should be checked, if the join() call is actually done on a Path. We have some documentation how this can be done: Checking for a specific type

span.with_hi(expr.span.hi()),
r#"argument in join called on path contains a starting '/'"#,
"try removing first '/'",
"join(\"your/path/here\")".to_owned(),
Copy link
Member

Choose a reason for hiding this comment

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

It's perfect that you tried to include a suggestion. These greatly improve lint messages and usability. The string given in the suggestion should ideally be something the user can copy and directly past into their code, which is not yet the case for this one.

In Clippy/rustc suggestions are usually created by requesting code snippets and concatenating/modifying them in a compatible way.

For this lint, the user can have two different intentions, which makes it a bit harder. I'd suggest using span_lint_and_then to create two suggestions. Something like this (Everything inside <> is still a todo):

span_lint_and_then(
    cx,
    <LINT>,
    <span>,
    "`Path::join()` starts with a path separator",
    |diag| {
        diag.note("expanding the path with a leading separator, will replace the path");

        // The applicability has to be unspecified, as the intention of the user is not clear
        diag.span_suggestion_verbose(
            <str-lit-span>,
            "if the path should be extended, try removing the separator",
            <str-lit-without-separator>,
            Applicability::Unspecified,
        );

        diag.span_suggestion_verbose(
            <expr-span>,
            "if the path should be replaced, try creating a new path instead",
            format!("Path::new(<str-lit-snippet>)"),
            Applicability::Unspecified,
        );
    }
);

To get specific snippets, you can take a look at snippet_with_applicability

Comment on lines 6 to 9
// should be linted
let path = std::path::Path::new("/bin");
path.join("/sh");
println!("{}", path.display());
Copy link
Member

Choose a reason for hiding this comment

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

Could you add some more tests, with PathBuf, windows path separators \\ and raw strings like r#"/xyz"# to see if the suggestion is correct? 🙃

Copy link
Contributor Author

@ofeeg ofeeg Apr 24, 2023

Choose a reason for hiding this comment

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

Hi! Sorry I have not looked at my computer for a couple of days and the app I uses on my phone to interact with github did not notify me of any of these replies. I've read them all now and will start on implementing each of your requests one at a time.

I was thinking at the time of submitting this about implementing some of these suggestions but I was admittedly pretty tired that day.

EDIT: Also, thank you for letting me know about the email address clashing. I use magit in emacs and forgot to set a valid email address. 🤣

@xFrednet
Copy link
Member

Hi! Sorry I have not looked at my computer for a couple of days and the app I uses on my phone to interact with github did not notify me of any of these replies. I've read them all now and will start on implementing each of your requests one at a time.

No problem at all, my studies are currently quite time-consuming. Having some time between reviews helps me as well. Just let me know when it's ready for the next review or when you need some assistance!

EDIT: Also, thank you for letting me know about the email address clashing. I use magit in emacs and forgot to set a valid email address.

No problem! I also mention it, in case, the email/name is supposed to be private. I once commited something under my real name, before I had it linked to my GH and luckily caught it before it landed on master :)

@xFrednet xFrednet added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels May 2, 2023
@bors
Copy link
Contributor

bors commented May 12, 2023

☔ The latest upstream changes (presumably #10769) made this pull request unmergeable. Please resolve the merge conflicts.

@xFrednet
Copy link
Member

xFrednet commented Jul 5, 2023

Oh, I totally missed the PR update. Sorry, I'll give you a review soon.

@blyxyas Do you maybe want to give this review a go if you have the time? :)

Copy link
Member

@blyxyas blyxyas left a comment

Choose a reason for hiding this comment

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

Very good for a first contribution! Thanks for joining the project! ❤️

/// Checks for initial `'/'` in an argument to `.join()` on a `Path`.
///
/// ### Why is this bad?
/// `.join()` comments starting with a separator, can replace the entire path.
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
/// `.join()` comments starting with a separator, can replace the entire path.
/// `.join()` comments starting with a separator (`/` or `\\`) can replace the entire path.

@@ -3194,6 +3195,41 @@ declare_clippy_lint! {
"calling `drain` in order to `clear` a container"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for initial `'/'` in an argument to `.join()` on a `Path`.
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
/// Checks for initial `'/'` in an argument to `.join()` on a `Path`.
/// Checks for initial `/` or `\\` in an argument to `.join()` on a `Path`.

/// // path.join("sh");
/// ```
#[clippy::version = "1.70.0"]
pub PATH_JOIN_CORRECTION,
Copy link
Member

Choose a reason for hiding this comment

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

I personally think the original lint name (join_absolute_path) is the perfect fit for this lint. It lints when you use the join method with an absolute path. It's quite literal and illustrative.

let ty = cx.typeck_results().expr_ty(expr);
if_chain!(
if is_type_diagnostic_item(cx, ty, Path);
let applicability = Applicability::MachineApplicable;
Copy link
Member

Choose a reason for hiding this comment

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

No need to bind the applicability to a variable, don't worry :)
(Personal taste here, it's still fine)

Copy link
Member

Choose a reason for hiding this comment

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

Could you also commit the .stderr version and .fixed of this files? You can get those by using cargo dev bless after a cargo test <blah blah>.

Copy link
Member

@blyxyas blyxyas left a comment

Choose a reason for hiding this comment

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

Headers now use //@

@@ -0,0 +1,26 @@
// run-rustfix
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
// run-rustfix
//@run-rustfix

@blyxyas
Copy link
Member

blyxyas commented Jul 15, 2023

It seems like blessing isn't working... Could you describe your workflow?

@ofeeg
Copy link
Contributor Author

ofeeg commented Jul 15, 2023

It seems like blessing isn't working... Could you describe your workflow?

Not sure. Eventually I noticed that .fixed and .stderr files weren't being created. I use emacs and with this I just grep'd the old name and replaced all instances of the old name with a new name using sed. I wonder if I should just refork clippy and copy all the work I've done to the new fork.

@blyxyas
Copy link
Member

blyxyas commented Jul 15, 2023

Just a cargo test and then a cargo dev bless should fix it 🤔

@ofeeg
Copy link
Contributor Author

ofeeg commented Jul 15, 2023

Just a cargo test and then a cargo dev bless should fix it thinking

I did that and those files just don't get created. Also, this still fails. Unsure what possibly happened, but it's been a while since I last updated this and for all I can remember most of what I did was just keep doing cargo commands until the thing worked. I probably bugged it out at some point.

@blyxyas
Copy link
Member

blyxyas commented Jul 15, 2023

I'll make a PR against your fork in a few hours

@blyxyas
Copy link
Member

blyxyas commented Jul 15, 2023

Just before my PR, could you create a new branch named "path_join_correct" and change this PR to point to that new branch (copy-pasting these changes)? Making a PR from the master branch will have some problems.

@ofeeg
Copy link
Contributor Author

ofeeg commented Jul 15, 2023

Just before my PR, could you create a new branch named "path_join_correct" and change this PR to point to that new branch (copy-pasting these changes)? Making a PR from the master branch will have some problems.

Do I have to make a new PR to change the source branch? Github doesn't seem to just let me change ofeeg:master to ofeeg:path_join_correct.

EDIT: I made the branch and pasted the supposed changes.

@blyxyas
Copy link
Member

blyxyas commented Jul 15, 2023

No, you dont need make a new PR. I'm on mobile currently, but I'm pretty sure it's possible as I've done it for a few PRs

@ofeeg
Copy link
Contributor Author

ofeeg commented Jul 15, 2023

No, you dont need make a new PR. I'm on mobile currently, but I'm pretty sure it's possible as I've done it for a few PRs

All the sources I'm reading are telling me I have to make a new PR and close this one in order to merge from a different branch. If you know how to do it let me know; otherwise I will make a new PR and reference this one. I'll remember to not try to merge from my forks master in the future to prevent this.

@xFrednet
Copy link
Member

As far as I know, there is no way to change the base branch of a PR. You can only force push another branch to the source branch of a PR, that would override the changes on the branch. But this PR is linked to ofeeg:master. I'm sorry. You can create a new PR or just continue development on your master branch, both options are totally file for me. :) @blyxyas Do you have a preference in this regard?

@blyxyas
Copy link
Member

blyxyas commented Jul 16, 2023

I have a preference for creating a new branch. Having clean git history is kinda very important when working in Rust (my first Pr had 110 commits, 75 comments of me fighting with Git, deleting the whole fork and remaking everything 😢)

From personal experience, it isn't something fun :(

Yeah, I'm not currently with a pc at range, but although I'm pretty sure it's possible, creating a new PR is also fine.

Make sure to use r? xFrednet in the PR description :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants