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 new literal_string_with_formatting_arg lint #13410

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

GuillaumeGomez
Copy link
Member

Fixes #10195.

changelog: Add new literal_string_with_formatting_arg lint

@rustbot
Copy link
Collaborator

rustbot commented Sep 17, 2024

r? @Jarcho

rustbot has assigned @Jarcho.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Sep 17, 2024
@GuillaumeGomez GuillaumeGomez changed the title Literal string with formatting arg Add new literal_string_with_formatting_arg lint Sep 17, 2024
@GuillaumeGomez GuillaumeGomez changed the title Add new literal_string_with_formatting_arg lint Add new literal_string_with_formatting_arg lint Sep 17, 2024
})
.collect::<Vec<_>>();
if spans.len() == 1 {
span_lint(
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 could emit a suggestion for both cases by wrapping the literal into &format!(). Not sure if it's a good idea since it could be wrong in a lot of cases so for now I didn't do it.

@matthiaskrgr
Copy link
Member

Would like to see a test for malformed fmt attemps such as {y:?}} or "{y:?} y:?}" to make sure we don't lint there by accident

@emilk
Copy link

emilk commented Sep 18, 2024

I did a little bit of testing, and it seems to work like it should! ❤️ A few more unit tests would be nice though

@GuillaumeGomez
Copy link
Member Author

I just found a much better idea: using the rustc format parser directly. :3

@GuillaumeGomez GuillaumeGomez force-pushed the literal_string_with_formatting_arg branch 3 times, most recently from d720088 to 2356854 Compare September 18, 2024 21:59
@GuillaumeGomez
Copy link
Member Author

So it now uses the rustc format parser, making things much better overall: no more regex, less false positive, simpler code.

I also added all suggested test cases and some more.

@y21
Copy link
Member

y21 commented Sep 18, 2024

What does the lint do for the empty JSON object {} which is also a valid formatting specifier? Would probably be a good to have as a negative test too.

cx,
LITERAL_STRING_WITH_FORMATTING_ARG,
spans,
"this is a formatting argument but it is not part of a formatting macro",
Copy link

Choose a reason for hiding this comment

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

Maybe a less strong wording would be good, e.g. "this looks like a formatting argument…" ? Just in case of false positives

@GuillaumeGomez GuillaumeGomez force-pushed the literal_string_with_formatting_arg branch from 2356854 to c662397 Compare September 19, 2024 10:11
@GuillaumeGomez
Copy link
Member Author

What does the lint do for the empty JSON object {} which is also a valid formatting specifier? Would probably be a good to have as a negative test too.

It lints (as expected 😄). There was a test for that with other characters but created a new one with just {} just in case.

Maybe a less strong wording would be good, e.g. "this looks like a formatting argument…" ? Just in case of false positives

Good idea! I changed the wording.

@GuillaumeGomez GuillaumeGomez force-pushed the literal_string_with_formatting_arg branch 2 times, most recently from 6a3f320 to c027b98 Compare September 19, 2024 13:55
@y21
Copy link
Member

y21 commented Sep 19, 2024

It lints (as expected 😄).

Realistically speaking, I feel like {} would run into more false positives than true positives if we decide to lint that.

{foo}/{foo:?} etc. all make sense since they can work with format! alone (that is, format!("{foo}") works), but you can't make that argument for just {}. Not only is it missing the format macro, but it's also missing a formatting argument.

If someone writes let foo = "{}";, it seems fairly unlikely to me that they actually meant to use a formatting macro there, because there is nothing to format.

@GuillaumeGomez
Copy link
Member Author

Good point. Should I only lint against non-empty {} like {y}/{:?}/{y:?} and eventually we can add an option to look for everything?

@GuillaumeGomez GuillaumeGomez force-pushed the literal_string_with_formatting_arg branch 3 times, most recently from 2d7c05d to 852f1e3 Compare September 19, 2024 16:05
@GuillaumeGomez
Copy link
Member Author

I removed the check for {} for the time being.

@bors
Copy link
Collaborator

bors commented Sep 22, 2024

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

@GuillaumeGomez GuillaumeGomez force-pushed the literal_string_with_formatting_arg branch from 852f1e3 to d9c9ad4 Compare September 22, 2024 15:10
@GuillaumeGomez
Copy link
Member Author

Fixed merge conflict.

@samueltardieu
Copy link
Contributor

Quite a lot of hits in lintcheck, some crates may have to allow this crate-wise.

@GuillaumeGomez
Copy link
Member Author

Should I move it to another lint level maybe?

@samueltardieu
Copy link
Contributor

On the other hand, the affected crates are the expected ones: clap, cargo, rg, regex, that are crates that manipulate patterns. Those can disable the lint globally.

@GuillaumeGomez
Copy link
Member Author

Then keeping it as is.

Do you see other things that need to be changed @y21?

@GuillaumeGomez
Copy link
Member Author

ping @y21 :)

@GuillaumeGomez
Copy link
Member Author

r? @xFrednet

@rustbot rustbot assigned xFrednet and unassigned Jarcho Oct 29, 2024
@Jarcho
Copy link
Contributor

Jarcho commented Oct 29, 2024

Since this is currently in suspicious the goal should be to have no false-positives (they're acceptable, but should be rare). With that in mind This should only lint strings which would compile when used with format in the first place. Something like:

  • A valid format string (no parse errors).
  • Only inline arguments (or at least one).
  • All named arguments name real locals/globals (or a small edit distance away from one).
  • All arguments actually implement the required traits.

The goal here is a suspicious lint shouldn't be annoying. If all it does is end up linting on things which aren't intended as format strings it gets turned off.

@GuillaumeGomez
Copy link
Member Author

  • All named arguments name real locals/globals (or a small edit distance away from one).
  • All arguments actually implement the required traits.

Sounds acceptable. Going to try to implement these extra requirements.

@bors
Copy link
Collaborator

bors commented Oct 31, 2024

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

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.

Implementation generally looks good to me, but I haven't worked with the Parser before.

Mostly superficial comments and questions.

@@ -1,5 +1,5 @@
#![warn(clippy::print_literal)]
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::uninlined_format_args, clippy::literal_string_with_formatting_arg)]
Copy link
Member

Choose a reason for hiding this comment

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

Why does it have to be allowed in these files? Does it also lint strings in print! macros? 🤔

/// x.expect(&format!("{y:?}"));
/// ```
#[clippy::version = "1.83.0"]
pub LITERAL_STRING_WITH_FORMATTING_ARG,
Copy link
Member

Choose a reason for hiding this comment

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

I guess this should be plural, according to lint naming guidelines

Suggested change
pub LITERAL_STRING_WITH_FORMATTING_ARG,
pub LITERAL_STRINGS_WITH_FORMATTING_ARG,

/// ```
#[clippy::version = "1.83.0"]
pub LITERAL_STRING_WITH_FORMATTING_ARG,
suspicious,
Copy link
Member

Choose a reason for hiding this comment

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

Based on the number of needed allows this feels like the wrong category. Lintcheck also found 76 occurrences, so I don't think this should be warn by default. It feels more like a restriction lint or maybe pedantic but I'd be in favor of restriction

x.expect(r"{y:?} y:?}"); //~ literal_string_with_formatting_arg
x.expect(r##" {y:?} {y:?} "##); //~ literal_string_with_formatting_arg
// Ensure that it doesn't try to go in the middle of a unicode character.
x.expect("———{:?}"); //~ literal_string_with_formatting_arg
Copy link
Member

Choose a reason for hiding this comment

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

I feel like this shouldn't be linted, for the simple reason, that the {:?} can't refer to anything. If someone mixed expect() up to think that it allows formatting, they would probably write expect("{:?}", obj) which would result in an error, since expect only takes one argument.

One option to make this line more accepting would be to restrict linting to {x:?} / {x} where x is available in the current scope 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties
Projects
None yet
Development

Successfully merging this pull request may close these issues.

warn on strings that look like inline format strings but aren't
9 participants