Skip to content

Commit 7216d28

Browse files
committed
refactor(const_path_join): improve handling of constant expressions in path construction
Enhance the logic for suggesting path construction methods based on the number of constant expressions. Introduce checks to differentiate between single and multiple constants, updating suggestions accordingly. This change improves the accuracy of linting messages for path joins.
1 parent c3a4f88 commit 7216d28

File tree

1 file changed

+63
-11
lines changed
  • examples/restriction/const_path_join/src

1 file changed

+63
-11
lines changed

examples/restriction/const_path_join/src/lib.rs

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ impl<'tcx> LateLintPass<'tcx> for ConstPathJoin {
8080
let all_const_or_literal = components
8181
.iter()
8282
.all(|c| !matches!(c, ComponentType::Other));
83+
84+
// Only consider constants if there are multiple constants
85+
// If there's just one constant with string literals, treat as normal joins
86+
let has_multiple_constants = components
87+
.iter()
88+
.filter(|c| matches!(c, ComponentType::Constant(_)))
89+
.count() > 1;
8390

8491
if all_literals {
8592
// Suggest joining into a single string literal
@@ -111,8 +118,8 @@ impl<'tcx> LateLintPass<'tcx> for ConstPathJoin {
111118
sugg,
112119
Applicability::MachineApplicable,
113120
);
114-
} else if all_const_or_literal {
115-
// Suggest using concat!()
121+
} else if all_const_or_literal && has_multiple_constants {
122+
// Suggest using concat!() only when there are multiple constant expressions
116123
let concat_args = components
117124
.iter()
118125
.enumerate()
@@ -132,20 +139,57 @@ impl<'tcx> LateLintPass<'tcx> for ConstPathJoin {
132139
.collect::<Vec<String>>()
133140
.join(", ");
134141

135-
let sugg = format!("concat!({concat_args})");
136-
137-
let span = match ty_or_partial_span {
138-
TyOrPartialSpan::Ty(_) => expr.span,
139-
// For partial joins, we need a wider span
140-
TyOrPartialSpan::PartialSpan(partial_span) => expr.span.with_lo(partial_span.lo()),
142+
let (span, sugg) = match ty_or_partial_span {
143+
TyOrPartialSpan::Ty(ty) => (
144+
expr.span,
145+
format!(r#"{}::from(concat!({concat_args}))"#, ty.join("::"))
146+
),
147+
TyOrPartialSpan::PartialSpan(partial_span) => {
148+
// We need to replace the entire chain of joins with a single join of the concat
149+
let full_span = expr.span.with_lo(partial_span.lo());
150+
(full_span, format!(r#".join(concat!({concat_args}))"#))
151+
}
141152
};
142153

143154
span_lint_and_sugg(
144155
cx,
145156
CONST_PATH_JOIN,
146157
span,
158+
"path could be constructed with concat!",
159+
"consider using",
160+
sugg,
161+
Applicability::MachineApplicable,
162+
);
163+
} else if all_const_or_literal {
164+
// For a single constant expression with string literals, treat like string literals
165+
let joined_path = components
166+
.iter()
167+
.map(|c| match c {
168+
ComponentType::Literal(s) => s.as_str(),
169+
ComponentType::Constant(_) => "..", // We know it's a path component
170+
_ => unreachable!(), // Already checked all are const or literals
171+
})
172+
.collect::<Vec<&str>>()
173+
.join("/");
174+
175+
let (span, sugg) = match ty_or_partial_span {
176+
TyOrPartialSpan::Ty(ty) => (
177+
expr.span,
178+
format!(r#"{}::from("{joined_path}")"#, ty.join("::"))
179+
),
180+
TyOrPartialSpan::PartialSpan(partial_span) => {
181+
// Use a wider span to replace all the joins
182+
let full_span = expr.span.with_lo(partial_span.lo());
183+
(full_span, format!(r#".join("{joined_path}")"#))
184+
}
185+
};
186+
187+
span_lint_and_sugg(
188+
cx,
189+
CONST_PATH_JOIN,
190+
span,
147191
"path could be constructed from a string literal",
148-
"consider using concat!",
192+
"use",
149193
sugg,
150194
Applicability::MachineApplicable,
151195
);
@@ -242,18 +286,26 @@ fn check_component_type<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) ->
242286
return ComponentType::Literal(s);
243287
}
244288

289+
// Check if it's a concat!() or env!() macro
290+
if let ExprKind::Call(callee, _) = expr.kind {
291+
if is_expr_path_def_path(cx, callee, &["std", "concat"]) ||
292+
is_expr_path_def_path(cx, callee, &["std", "env"]) {
293+
if let Some(snippet) = snippet_opt(cx, expr.span) {
294+
return ComponentType::Constant(snippet);
295+
}
296+
}
297+
}
298+
245299
// Check if it's a constant-evaluatable string expression
246300
if !expr.span.from_expansion()
247301
&& is_const_evaluatable(cx, expr)
248302
&& matches!(cx.typeck_results().expr_ty(expr).kind(), ty::Str)
249303
{
250304
if let Some(snippet) = snippet_opt(cx, expr.span) {
251-
// Consider it a constant component if we can get the snippet
252305
return ComponentType::Constant(snippet);
253306
}
254307
}
255308

256-
// Otherwise, it's neither a literal nor a recognized constant string
257309
ComponentType::Other
258310
}
259311

0 commit comments

Comments
 (0)