Skip to content

Commit 246b519

Browse files
committed
feat: add new lint seek_from_current
addresses #7886 added `seek_from_current` complexity lint. it checks use of `Seek#seek` with `SeekFrom::Current(0)` and suggests `Seek#stream_position` method
1 parent 967f172 commit 246b519

File tree

12 files changed

+173
-0
lines changed

12 files changed

+173
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4198,6 +4198,7 @@ Released 2018-09-13
41984198
[`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push
41994199
[`same_name_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_name_method
42004200
[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
4201+
[`seek_from_current`]: https://rust-lang.github.io/rust-clippy/master/index.html#seek_from_current
42014202
[`self_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_assignment
42024203
[`self_named_constructors`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_constructors
42034204
[`self_named_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_module_files

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
211211
LintId::of(methods::REPEAT_ONCE),
212212
LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
213213
LintId::of(methods::SEARCH_IS_SOME),
214+
LintId::of(methods::SEEK_FROM_CURRENT),
214215
LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),
215216
LintId::of(methods::SINGLE_CHAR_ADD_STR),
216217
LintId::of(methods::SINGLE_CHAR_PATTERN),

clippy_lints/src/lib.register_complexity.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
5858
LintId::of(methods::RANGE_ZIP_WITH_LEN),
5959
LintId::of(methods::REPEAT_ONCE),
6060
LintId::of(methods::SEARCH_IS_SOME),
61+
LintId::of(methods::SEEK_FROM_CURRENT),
6162
LintId::of(methods::SKIP_WHILE_NEXT),
6263
LintId::of(methods::UNNECESSARY_FILTER_MAP),
6364
LintId::of(methods::UNNECESSARY_FIND_MAP),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ store.register_lints(&[
362362
methods::REPEAT_ONCE,
363363
methods::RESULT_MAP_OR_INTO_OPTION,
364364
methods::SEARCH_IS_SOME,
365+
methods::SEEK_FROM_CURRENT,
365366
methods::SHOULD_IMPLEMENT_TRAIT,
366367
methods::SINGLE_CHAR_ADD_STR,
367368
methods::SINGLE_CHAR_PATTERN,

clippy_lints/src/methods/mod.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ mod path_buf_push_overwrite;
6969
mod range_zip_with_len;
7070
mod repeat_once;
7171
mod search_is_some;
72+
mod seek_from_current;
7273
mod single_char_add_str;
7374
mod single_char_insert_string;
7475
mod single_char_pattern;
@@ -3066,6 +3067,49 @@ declare_clippy_lint! {
30663067
"iterating on map using `iter` when `keys` or `values` would do"
30673068
}
30683069

3070+
declare_clippy_lint! {
3071+
/// ### What it does
3072+
///
3073+
/// Checks an argument of `seek` method of `Seek` trait
3074+
/// and if it start seek from `SeekFrom::Current(0)`, suggests `stream_position` instead.
3075+
///
3076+
/// ### Why is this bad?
3077+
///
3078+
/// Readability. Use dedicated method.
3079+
///
3080+
/// ### Example
3081+
///
3082+
/// ```rust
3083+
/// use std::fs::File;
3084+
/// use std::io::{self, Write, Seek, SeekFrom};
3085+
///
3086+
/// fn main() -> io::Result<()> {
3087+
/// let mut f = File::create("foo.txt")?;
3088+
/// f.write_all(b"Hello")?;
3089+
/// eprintln!("Written {} bytes", f.seek(SeekFrom::Current(0))?);
3090+
///
3091+
/// Ok(())
3092+
/// }
3093+
/// ```
3094+
/// Use instead:
3095+
/// ```rust
3096+
/// use std::fs::File;
3097+
/// use std::io::{self, Write, Seek, SeekFrom};
3098+
///
3099+
/// fn main() -> io::Result<()> {
3100+
/// let mut f = File::create("foo.txt")?;
3101+
/// f.write_all(b"Hello")?;
3102+
/// eprintln!("Written {} bytes", f.stream_position()?);
3103+
///
3104+
/// Ok(())
3105+
/// }
3106+
/// ```
3107+
#[clippy::version = "1.65.0"]
3108+
pub SEEK_FROM_CURRENT,
3109+
complexity,
3110+
"use dedicated method for seek from current position"
3111+
}
3112+
30693113
pub struct Methods {
30703114
avoid_breaking_exported_api: bool,
30713115
msrv: Option<RustcVersion>,
@@ -3190,6 +3234,7 @@ impl_lint_pass!(Methods => [
31903234
VEC_RESIZE_TO_ZERO,
31913235
VERBOSE_FILE_READS,
31923236
ITER_KV_MAP,
3237+
SEEK_FROM_CURRENT,
31933238
]);
31943239

31953240
/// Extracts a method call name, args, and `Span` of the method name.
@@ -3604,6 +3649,9 @@ impl Methods {
36043649
("resize", [count_arg, default_arg]) => {
36053650
vec_resize_to_zero::check(cx, expr, count_arg, default_arg, span);
36063651
},
3652+
("seek", [arg]) => {
3653+
seek_from_current::check(cx, expr, recv, arg);
3654+
},
36073655
("sort", []) => {
36083656
stable_sort_primitive::check(cx, expr, recv);
36093657
},
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use rustc_ast::ast::{LitIntType, LitKind};
2+
use rustc_errors::Applicability;
3+
use rustc_hir::{Expr, ExprKind};
4+
use rustc_lint::LateContext;
5+
6+
use clippy_utils::{
7+
diagnostics::span_lint_and_sugg, get_trait_def_id, match_def_path, paths, source::snippet_with_applicability,
8+
ty::implements_trait,
9+
};
10+
11+
use super::SEEK_FROM_CURRENT;
12+
13+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) {
14+
let ty = cx.typeck_results().expr_ty(recv);
15+
16+
if let Some(def_id) = get_trait_def_id(cx, &paths::STD_IO_SEEK) {
17+
if implements_trait(cx, ty, def_id, &[]) && arg_is_seek_from_current(cx, arg) {
18+
let mut applicability = Applicability::MachineApplicable;
19+
let snip = snippet_with_applicability(cx, recv.span, "..", &mut applicability);
20+
21+
span_lint_and_sugg(
22+
cx,
23+
SEEK_FROM_CURRENT,
24+
expr.span,
25+
"using `SeekFrom::Current` to start from current position",
26+
"replace with",
27+
format!("{snip}.stream_position()"),
28+
applicability,
29+
);
30+
}
31+
}
32+
}
33+
34+
fn arg_is_seek_from_current<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
35+
if let ExprKind::Call(f, args) = expr.kind &&
36+
let ExprKind::Path(ref path) = f.kind &&
37+
let Some(def_id) = cx.qpath_res(path, f.hir_id).opt_def_id() &&
38+
match_def_path(cx, def_id, &paths::STD_IO_SEEK_FROM_CURRENT) {
39+
// check if argument of `SeekFrom::Current` is `0`
40+
if args.len() == 1 &&
41+
let ExprKind::Lit(ref lit) = args[0].kind &&
42+
let LitKind::Int(0, LitIntType::Unsuffixed) = lit.node {
43+
return true
44+
}
45+
}
46+
47+
false
48+
}

clippy_utils/src/paths.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
115115
pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
116116
pub const CONVERT_IDENTITY: [&str; 3] = ["core", "convert", "identity"];
117117
pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"];
118+
pub const STD_IO_SEEK: [&str; 3] = ["std", "io", "Seek"];
119+
pub const STD_IO_SEEK_FROM_CURRENT: [&str; 4] = ["std", "io", "SeekFrom", "Current"];
118120
pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_str"];
119121
pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"];
120122
pub const STRING_NEW: [&str; 4] = ["alloc", "string", "String", "new"];

src/docs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ docs! {
449449
"same_item_push",
450450
"same_name_method",
451451
"search_is_some",
452+
"seek_from_current",
452453
"self_assignment",
453454
"self_named_constructors",
454455
"self_named_module_files",

src/docs/seek_from_current.txt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
### What it does
2+
3+
Checks an argument of `seek` method of `Seek` trait
4+
and if it start seek from `SeekFrom::Current(0)`, suggests `stream_position` instead.
5+
6+
### Why is this bad?
7+
8+
Readability. Use dedicated method.
9+
10+
### Example
11+
12+
```
13+
use std::fs::File;
14+
use std::io::{self, Write, Seek, SeekFrom};
15+
16+
fn main() -> io::Result<()> {
17+
let mut f = File::create("foo.txt")?;
18+
f.write_all(b"Hello")?;
19+
eprintln!("Written {} bytes", f.seek(SeekFrom::Current(0))?);
20+
21+
Ok(())
22+
}
23+
```
24+
Use instead:
25+
```
26+
use std::fs::File;
27+
use std::io::{self, Write, Seek, SeekFrom};
28+
29+
fn main() -> io::Result<()> {
30+
let mut f = File::create("foo.txt")?;
31+
f.write_all(b"Hello")?;
32+
eprintln!("Written {} bytes", f.stream_position()?);
33+
34+
Ok(())
35+
}
36+
```

tests/ui/seek_from_current.fixed

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// run-rustfix
2+
#![warn(clippy::seek_from_current)]
3+
use std::fs::File;
4+
use std::io::{self, Seek, SeekFrom, Write};
5+
6+
fn main() -> io::Result<()> {
7+
let mut f = File::create("foo.txt")?;
8+
f.write_all(b"Hi!")?;
9+
f.stream_position()?;
10+
f.seek(SeekFrom::Current(1))?;
11+
Ok(())
12+
}

tests/ui/seek_from_current.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// run-rustfix
2+
#![warn(clippy::seek_from_current)]
3+
use std::fs::File;
4+
use std::io::{self, Seek, SeekFrom, Write};
5+
6+
fn main() -> io::Result<()> {
7+
let mut f = File::create("foo.txt")?;
8+
f.write_all(b"Hi!")?;
9+
f.seek(SeekFrom::Current(0))?;
10+
f.seek(SeekFrom::Current(1))?;
11+
Ok(())
12+
}

tests/ui/seek_from_current.stderr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: using `SeekFrom::Current` to start from current position
2+
--> $DIR/seek_from_current.rs:9:5
3+
|
4+
LL | f.seek(SeekFrom::Current(0))?;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `f.stream_position()`
6+
|
7+
= note: `-D clippy::seek-from-current` implied by `-D warnings`
8+
9+
error: aborting due to previous error
10+

0 commit comments

Comments
 (0)