Auto merge of #9295 - Guilherme-Vasconcelos:manual-empty-string-creation, r=dswij

Add `manual_empty_string_creations` lint

Closes #2972

- [x] Followed [lint naming conventions][lint_naming]
- [x] Added passing UI tests (including committed `.stderr` file)
- [x] `cargo test` passes locally
- [x] Executed `cargo dev update_lints`
- [x] Added lint documentation
- [x] Run `cargo dev fmt`

changelog: [`manual_empty_string_creations`]: Add lint for empty String not being created with `String::new()`
This commit is contained in:
bors 2022-08-19 11:19:06 +00:00
commit 868dba9f65
32 changed files with 371 additions and 38 deletions

View file

@ -14,31 +14,31 @@ fn is_rust_file(filename: &str) -> bool {
fn main() {
// std::string::String and &str should trigger the lint failure with .ext12
let _ = String::from("").ends_with(".ext12");
let _ = String::new().ends_with(".ext12");
let _ = "str".ends_with(".ext12");
// The test struct should not trigger the lint failure with .ext12
TestStruct {}.ends_with(".ext12");
// std::string::String and &str should trigger the lint failure with .EXT12
let _ = String::from("").ends_with(".EXT12");
let _ = String::new().ends_with(".EXT12");
let _ = "str".ends_with(".EXT12");
// The test struct should not trigger the lint failure with .EXT12
TestStruct {}.ends_with(".EXT12");
// Should not trigger the lint failure with .eXT12
let _ = String::from("").ends_with(".eXT12");
let _ = String::new().ends_with(".eXT12");
let _ = "str".ends_with(".eXT12");
TestStruct {}.ends_with(".eXT12");
// Should not trigger the lint failure with .EXT123 (too long)
let _ = String::from("").ends_with(".EXT123");
let _ = String::new().ends_with(".EXT123");
let _ = "str".ends_with(".EXT123");
TestStruct {}.ends_with(".EXT123");
// Shouldn't fail if it doesn't start with a dot
let _ = String::from("").ends_with("a.ext");
let _ = String::new().ends_with("a.ext");
let _ = "str".ends_with("a.extA");
TestStruct {}.ends_with("a.ext");
}

View file

@ -8,10 +8,10 @@ LL | filename.ends_with(".rs")
= help: consider using a case-insensitive comparison instead
error: case-sensitive file extension comparison
--> $DIR/case_sensitive_file_extension_comparisons.rs:17:30
--> $DIR/case_sensitive_file_extension_comparisons.rs:17:27
|
LL | let _ = String::from("").ends_with(".ext12");
| ^^^^^^^^^^^^^^^^^^^
LL | let _ = String::new().ends_with(".ext12");
| ^^^^^^^^^^^^^^^^^^^
|
= help: consider using a case-insensitive comparison instead
@ -24,10 +24,10 @@ LL | let _ = "str".ends_with(".ext12");
= help: consider using a case-insensitive comparison instead
error: case-sensitive file extension comparison
--> $DIR/case_sensitive_file_extension_comparisons.rs:24:30
--> $DIR/case_sensitive_file_extension_comparisons.rs:24:27
|
LL | let _ = String::from("").ends_with(".EXT12");
| ^^^^^^^^^^^^^^^^^^^
LL | let _ = String::new().ends_with(".EXT12");
| ^^^^^^^^^^^^^^^^^^^
|
= help: consider using a case-insensitive comparison instead

View file

@ -33,7 +33,7 @@ fn main() {
format!("foo {}", "bar");
format!("{} bar", "foo");
let arg: String = "".to_owned();
let arg = String::new();
arg.to_string();
format!("{:?}", arg); // Don't warn about debug.
format!("{:8}", arg);

View file

@ -35,7 +35,7 @@ fn main() {
format!("foo {}", "bar");
format!("{} bar", "foo");
let arg: String = "".to_owned();
let arg = String::new();
format!("{}", arg);
format!("{:?}", arg); // Don't warn about debug.
format!("{:8}", arg);

View file

@ -68,7 +68,7 @@ fn main() {
&x;
x;
let mut a = A("".into());
let mut a = A(String::new());
let b = a << 0; // no error: non-integer
1 * Meter; // no error: non-integer

View file

@ -68,7 +68,7 @@ fn main() {
&x >> 0;
x >> &0;
let mut a = A("".into());
let mut a = A(String::new());
let b = a << 0; // no error: non-integer
1 * Meter; // no error: non-integer

View file

@ -0,0 +1,63 @@
// run-rustfix
#![warn(clippy::manual_empty_string_creations)]
macro_rules! create_strings_from_macro {
// When inside a macro, nothing should warn to prevent false positives.
($some_str:expr) => {
let _: String = $some_str.into();
let _ = $some_str.to_string();
};
}
fn main() {
// Method calls
let _ = String::new();
let _ = "no warning".to_string();
let _ = String::new();
let _ = "no warning".to_owned();
let _: String = String::new();
let _: String = "no warning".into();
let _: SomeOtherStruct = "no warning".into();
let _: SomeOtherStruct = "".into(); // No warning too. We are not converting into String.
// Calls
let _ = String::new();
let _ = String::new();
let _ = String::from("no warning");
let _ = SomeOtherStruct::from("no warning");
let _ = SomeOtherStruct::from(""); // Again: no warning.
let _ = String::new();
let _ = String::try_from("no warning").unwrap();
let _ = String::try_from("no warning").expect("this should not warn");
let _ = SomeOtherStruct::try_from("no warning").unwrap();
let _ = SomeOtherStruct::try_from("").unwrap(); // Again: no warning.
let _: String = String::new();
let _: String = From::from("no warning");
let _: SomeOtherStruct = From::from("no warning");
let _: SomeOtherStruct = From::from(""); // Again: no warning.
let _: String = String::new();
let _: String = TryFrom::try_from("no warning").unwrap();
let _: String = TryFrom::try_from("no warning").expect("this should not warn");
let _: String = String::new();
let _: SomeOtherStruct = TryFrom::try_from("no_warning").unwrap();
let _: SomeOtherStruct = TryFrom::try_from("").unwrap(); // Again: no warning.
// Macros (never warn)
create_strings_from_macro!("");
create_strings_from_macro!("Hey");
}
struct SomeOtherStruct {}
impl From<&str> for SomeOtherStruct {
fn from(_value: &str) -> Self {
Self {}
}
}

View file

@ -0,0 +1,63 @@
// run-rustfix
#![warn(clippy::manual_empty_string_creations)]
macro_rules! create_strings_from_macro {
// When inside a macro, nothing should warn to prevent false positives.
($some_str:expr) => {
let _: String = $some_str.into();
let _ = $some_str.to_string();
};
}
fn main() {
// Method calls
let _ = "".to_string();
let _ = "no warning".to_string();
let _ = "".to_owned();
let _ = "no warning".to_owned();
let _: String = "".into();
let _: String = "no warning".into();
let _: SomeOtherStruct = "no warning".into();
let _: SomeOtherStruct = "".into(); // No warning too. We are not converting into String.
// Calls
let _ = String::from("");
let _ = <String>::from("");
let _ = String::from("no warning");
let _ = SomeOtherStruct::from("no warning");
let _ = SomeOtherStruct::from(""); // Again: no warning.
let _ = String::try_from("").unwrap();
let _ = String::try_from("no warning").unwrap();
let _ = String::try_from("no warning").expect("this should not warn");
let _ = SomeOtherStruct::try_from("no warning").unwrap();
let _ = SomeOtherStruct::try_from("").unwrap(); // Again: no warning.
let _: String = From::from("");
let _: String = From::from("no warning");
let _: SomeOtherStruct = From::from("no warning");
let _: SomeOtherStruct = From::from(""); // Again: no warning.
let _: String = TryFrom::try_from("").unwrap();
let _: String = TryFrom::try_from("no warning").unwrap();
let _: String = TryFrom::try_from("no warning").expect("this should not warn");
let _: String = TryFrom::try_from("").expect("this should warn");
let _: SomeOtherStruct = TryFrom::try_from("no_warning").unwrap();
let _: SomeOtherStruct = TryFrom::try_from("").unwrap(); // Again: no warning.
// Macros (never warn)
create_strings_from_macro!("");
create_strings_from_macro!("Hey");
}
struct SomeOtherStruct {}
impl From<&str> for SomeOtherStruct {
fn from(_value: &str) -> Self {
Self {}
}
}

View file

@ -0,0 +1,58 @@
error: empty String is being created manually
--> $DIR/manual_empty_string_creations.rs:15:13
|
LL | let _ = "".to_string();
| ^^^^^^^^^^^^^^ help: consider using: `String::new()`
|
= note: `-D clippy::manual-empty-string-creations` implied by `-D warnings`
error: empty String is being created manually
--> $DIR/manual_empty_string_creations.rs:18:13
|
LL | let _ = "".to_owned();
| ^^^^^^^^^^^^^ help: consider using: `String::new()`
error: empty String is being created manually
--> $DIR/manual_empty_string_creations.rs:21:21
|
LL | let _: String = "".into();
| ^^^^^^^^^ help: consider using: `String::new()`
error: empty String is being created manually
--> $DIR/manual_empty_string_creations.rs:28:13
|
LL | let _ = String::from("");
| ^^^^^^^^^^^^^^^^ help: consider using: `String::new()`
error: empty String is being created manually
--> $DIR/manual_empty_string_creations.rs:29:13
|
LL | let _ = <String>::from("");
| ^^^^^^^^^^^^^^^^^^ help: consider using: `String::new()`
error: empty String is being created manually
--> $DIR/manual_empty_string_creations.rs:34:13
|
LL | let _ = String::try_from("").unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `String::new()`
error: empty String is being created manually
--> $DIR/manual_empty_string_creations.rs:40:21
|
LL | let _: String = From::from("");
| ^^^^^^^^^^^^^^ help: consider using: `String::new()`
error: empty String is being created manually
--> $DIR/manual_empty_string_creations.rs:45:21
|
LL | let _: String = TryFrom::try_from("").unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `String::new()`
error: empty String is being created manually
--> $DIR/manual_empty_string_creations.rs:48:21
|
LL | let _: String = TryFrom::try_from("").expect("this should warn");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `String::new()`
error: aborting due to 9 previous errors

View file

@ -90,8 +90,8 @@ fn or_fun_call() {
let mut btree_vec = BTreeMap::<u64, Vec<i32>>::new();
btree_vec.entry(42).or_insert(vec![]);
let stringy = Some(String::from(""));
let _ = stringy.unwrap_or_else(|| "".to_owned());
let stringy = Some(String::new());
let _ = stringy.unwrap_or_default();
let opt = Some(1);
let hello = "Hello";

View file

@ -90,8 +90,8 @@ fn or_fun_call() {
let mut btree_vec = BTreeMap::<u64, Vec<i32>>::new();
btree_vec.entry(42).or_insert(vec![]);
let stringy = Some(String::from(""));
let _ = stringy.unwrap_or("".to_owned());
let stringy = Some(String::new());
let _ = stringy.unwrap_or(String::new());
let opt = Some(1);
let hello = "Hello";

View file

@ -66,11 +66,11 @@ error: use of `unwrap_or` followed by a function call
LL | without_default.unwrap_or(Foo::new());
| ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(Foo::new)`
error: use of `unwrap_or` followed by a function call
error: use of `unwrap_or` followed by a call to `new`
--> $DIR/or_fun_call.rs:94:21
|
LL | let _ = stringy.unwrap_or("".to_owned());
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| "".to_owned())`
LL | let _ = stringy.unwrap_or(String::new());
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()`
error: use of `unwrap_or` followed by a function call
--> $DIR/or_fun_call.rs:102:21

View file

@ -7,13 +7,13 @@ extern crate macro_rules;
#[allow(clippy::string_add_assign, unused)]
fn main() {
// ignores assignment distinction
let mut x = "".to_owned();
let mut x = String::new();
for _ in 1..3 {
x = x + ".";
}
let y = "".to_owned();
let y = String::new();
let z = y + "...";
assert_eq!(&x, &z);

View file

@ -4,13 +4,13 @@
#[warn(clippy::string_add_assign)]
fn main() {
// ignores assignment distinction
let mut x = "".to_owned();
let mut x = String::new();
for _ in 1..3 {
x += ".";
}
let y = "".to_owned();
let y = String::new();
let z = y + "...";
assert_eq!(&x, &z);

View file

@ -4,13 +4,13 @@
#[warn(clippy::string_add_assign)]
fn main() {
// ignores assignment distinction
let mut x = "".to_owned();
let mut x = String::new();
for _ in 1..3 {
x = x + ".";
}
let y = "".to_owned();
let y = String::new();
let z = y + "...";
assert_eq!(&x, &z);

View file

@ -12,6 +12,7 @@ fn main() {
ref_str_argument("");
// should be linted
#[allow(clippy::manual_empty_string_creations)]
ref_str_argument("");
// should not be linted

View file

@ -12,6 +12,7 @@ fn main() {
ref_str_argument(&String::new());
// should be linted
#[allow(clippy::manual_empty_string_creations)]
ref_str_argument(&String::from(""));
// should not be linted

View file

@ -7,7 +7,7 @@ LL | ref_str_argument(&String::new());
= note: `-D clippy::unnecessary-owned-empty-strings` implied by `-D warnings`
error: usage of `&String::from("")` for a function expecting a `&str` argument
--> $DIR/unnecessary_owned_empty_strings.rs:15:22
--> $DIR/unnecessary_owned_empty_strings.rs:16:22
|
LL | ref_str_argument(&String::from(""));
| ^^^^^^^^^^^^^^^^^ help: try: `""`

View file

@ -29,10 +29,10 @@ fn main() {
let _ = String::try_from("foo".to_string()).unwrap();
let _ = String::try_from(format!("A: {:04}", 123)).unwrap();
let _: String = format!("Hello {}", "world").try_into().unwrap();
let _: String = "".to_owned().try_into().unwrap();
let _: String = String::new().try_into().unwrap();
let _: String = match String::from("_").try_into() {
Ok(a) => a,
Err(_) => "".into(),
Err(_) => String::new(),
};
// FIXME this is a false negative
#[allow(clippy::cmp_owned)]

View file

@ -62,7 +62,7 @@ LL | let _: String = format!("Hello {}", "world").try_into().unwrap();
error: useless conversion to the same type: `std::string::String`
--> $DIR/useless_conversion_try.rs:32:21
|
LL | let _: String = "".to_owned().try_into().unwrap();
LL | let _: String = String::new().try_into().unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: consider removing `.try_into()`