| use hir::{db::ExpandDatabase, HirFileIdExt, InFile}; |
| use ide_db::source_change::SourceChange; |
| use syntax::{ |
| ast::{self, HasArgList}, |
| AstNode, TextRange, |
| }; |
| use text_edit::TextEdit; |
| |
| use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext}; |
| |
| // Diagnostic: replace-filter-map-next-with-find-map |
| // |
| // This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`. |
| pub(crate) fn replace_filter_map_next_with_find_map( |
| ctx: &DiagnosticsContext<'_>, |
| d: &hir::ReplaceFilterMapNextWithFindMap, |
| ) -> Diagnostic { |
| Diagnostic::new_with_syntax_node_ptr( |
| ctx, |
| DiagnosticCode::Clippy("filter_map_next"), |
| "replace filter_map(..).next() with find_map(..)", |
| InFile::new(d.file, d.next_expr.into()), |
| ) |
| .with_fixes(fixes(ctx, d)) |
| } |
| |
| fn fixes( |
| ctx: &DiagnosticsContext<'_>, |
| d: &hir::ReplaceFilterMapNextWithFindMap, |
| ) -> Option<Vec<Assist>> { |
| let root = ctx.sema.db.parse_or_expand(d.file); |
| let next_expr = d.next_expr.to_node(&root); |
| let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; |
| |
| let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?; |
| let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range(); |
| let filter_map_args = filter_map_call.arg_list()?; |
| |
| let range_to_replace = |
| TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end()); |
| let replacement = format!("find_map{}", filter_map_args.syntax().text()); |
| let trigger_range = next_expr.syntax().text_range(); |
| |
| let edit = TextEdit::replace(range_to_replace, replacement); |
| |
| let source_change = SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit); |
| |
| Some(vec![fix( |
| "replace_with_find_map", |
| "Replace filter_map(..).next() with find_map()", |
| source_change, |
| trigger_range, |
| )]) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::{ |
| tests::{check_diagnostics_with_config, check_fix}, |
| DiagnosticsConfig, |
| }; |
| |
| #[track_caller] |
| pub(crate) fn check_diagnostics(ra_fixture: &str) { |
| let mut config = DiagnosticsConfig::test_sample(); |
| config.disabled.insert("inactive-code".to_string()); |
| config.disabled.insert("E0599".to_string()); |
| check_diagnostics_with_config(config, ra_fixture) |
| } |
| |
| #[test] |
| fn replace_filter_map_next_with_find_map2() { |
| check_diagnostics( |
| r#" |
| //- minicore: iterators |
| fn foo() { |
| let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next(); |
| } //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: replace filter_map(..).next() with find_map(..) |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn replace_filter_map_next_with_find_map_no_diagnostic_without_next() { |
| check_diagnostics( |
| r#" |
| //- minicore: iterators |
| fn foo() { |
| let m = core::iter::repeat(()) |
| .filter_map(|()| Some(92)) |
| .count(); |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn replace_filter_map_next_with_find_map_no_diagnostic_with_intervening_methods() { |
| check_diagnostics( |
| r#" |
| //- minicore: iterators |
| fn foo() { |
| let m = core::iter::repeat(()) |
| .filter_map(|()| Some(92)) |
| .map(|x| x + 2) |
| .next(); |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn replace_filter_map_next_with_find_map_no_diagnostic_if_not_in_chain() { |
| check_diagnostics( |
| r#" |
| //- minicore: iterators |
| fn foo() { |
| let mut m = core::iter::repeat(()) |
| .filter_map(|()| Some(92)); |
| let _n = m.next(); |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn replace_with_find_map() { |
| check_fix( |
| r#" |
| //- minicore: iterators |
| fn foo() { |
| let m = core::iter::repeat(()).$0filter_map(|()| Some(92)).next(); |
| } |
| "#, |
| r#" |
| fn foo() { |
| let m = core::iter::repeat(()).find_map(|()| Some(92)); |
| } |
| "#, |
| ) |
| } |
| |
| #[test] |
| fn respect_lint_attributes_for_clippy_equivalent() { |
| check_diagnostics( |
| r#" |
| //- minicore: iterators |
| |
| fn foo() { |
| #[allow(clippy::filter_map_next)] |
| let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next(); |
| } |
| |
| #[deny(clippy::filter_map_next)] |
| fn foo() { |
| let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next(); |
| } //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 error: replace filter_map(..).next() with find_map(..) |
| |
| fn foo() { |
| let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next(); |
| } //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: replace filter_map(..).next() with find_map(..) |
| |
| #[warn(clippy::filter_map_next)] |
| fn foo() { |
| let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next(); |
| } //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 warn: replace filter_map(..).next() with find_map(..) |
| |
| "#, |
| ); |
| } |
| } |