| use clippy_utils::diagnostics::span_lint_and_then; |
| use clippy_utils::expr_or_init; |
| use clippy_utils::source::snippet_opt; |
| use clippy_utils::ty::is_type_diagnostic_item; |
| use rustc_ast::ast::LitKind; |
| use rustc_errors::Applicability; |
| use rustc_hir::{Expr, ExprKind}; |
| use rustc_lint::LateContext; |
| use rustc_span::symbol::sym; |
| use rustc_span::Span; |
| |
| use super::JOIN_ABSOLUTE_PATHS; |
| |
| pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, join_arg: &'tcx Expr<'tcx>, expr_span: Span) { |
| let ty = cx.typeck_results().expr_ty(recv).peel_refs(); |
| if (is_type_diagnostic_item(cx, ty, sym::Path) || is_type_diagnostic_item(cx, ty, sym::PathBuf)) |
| && let ExprKind::Lit(spanned) = expr_or_init(cx, join_arg).kind |
| && let LitKind::Str(symbol, _) = spanned.node |
| && let sym_str = symbol.as_str() |
| && sym_str.starts_with(['/', '\\']) |
| { |
| span_lint_and_then( |
| cx, |
| JOIN_ABSOLUTE_PATHS, |
| join_arg.span, |
| "argument to `Path::join` starts with a path separator", |
| |diag| { |
| let arg_str = snippet_opt(cx, spanned.span).unwrap_or_else(|| "..".to_string()); |
| |
| let no_separator = if sym_str.starts_with('/') { |
| arg_str.replacen('/', "", 1) |
| } else { |
| arg_str.replacen('\\', "", 1) |
| }; |
| |
| diag.note("joining a path starting with separator will replace the path instead") |
| .span_suggestion( |
| spanned.span, |
| "if this is unintentional, try removing the starting separator", |
| no_separator, |
| Applicability::Unspecified, |
| ) |
| .span_suggestion( |
| expr_span, |
| "if this is intentional, consider using `Path::new`", |
| format!("PathBuf::from({arg_str})"), |
| Applicability::Unspecified, |
| ); |
| }, |
| ); |
| } |
| } |