| use clippy_utils::diagnostics::span_lint_and_then; | |
| use clippy_utils::res::MaybeDef as _; | |
| use clippy_utils::source::snippet; | |
| use clippy_utils::{expr_or_init, sym}; | |
| use rustc_ast::ast::{LitKind, StrStyle}; | |
| use rustc_errors::Applicability; | |
| use rustc_hir::{Expr, ExprKind}; | |
| use rustc_lint::LateContext; | |
| 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 matches!(ty.opt_diag_name(cx), Some(sym::Path | sym::PathBuf)) | |
| && let ExprKind::Lit(spanned) = expr_or_init(cx, join_arg).kind | |
| && let LitKind::Str(symbol, style) = 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(cx, spanned.span, ".."); | |
| let no_separator = if sym_str.starts_with('/') { | |
| arg_str.replacen('/', "", 1) | |
| } else if let StrStyle::Raw(_) = style { | |
| 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, | |
| ); | |
| }, | |
| ); | |
| } | |
| } |