|
4 | 4 | use crate::lints::{
|
5 | 5 | BadOptAccessDiag, DefaultHashTypesDiag, DiagOutOfImpl, LintPassByHand, NonExistentDocKeyword,
|
6 | 6 | QueryInstability, TyQualified, TykindDiag, TykindKind, UntranslatableDiag,
|
| 7 | + UntranslatableDiagnosticTrivial, |
7 | 8 | };
|
8 | 9 | use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
|
9 | 10 | use rustc_ast as ast;
|
@@ -366,7 +367,15 @@ declare_tool_lint! {
|
366 | 367 | report_in_external_macro: true
|
367 | 368 | }
|
368 | 369 |
|
369 |
| -declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL ]); |
| 370 | +declare_tool_lint! { |
| 371 | + /// The `untranslatable_diagnostic_trivial` lint detects diagnostics created using only static strings. |
| 372 | + pub rustc::UNTRANSLATABLE_DIAGNOSTIC_TRIVIAL, |
| 373 | + Deny, |
| 374 | + "prevent creation of diagnostics which cannot be translated, which use only static strings", |
| 375 | + report_in_external_macro: true |
| 376 | +} |
| 377 | + |
| 378 | +declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL, UNTRANSLATABLE_DIAGNOSTIC_TRIVIAL ]); |
370 | 379 |
|
371 | 380 | impl LateLintPass<'_> for Diagnostics {
|
372 | 381 | fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
@@ -423,6 +432,75 @@ impl LateLintPass<'_> for Diagnostics {
|
423 | 432 | }
|
424 | 433 | }
|
425 | 434 |
|
| 435 | +impl EarlyLintPass for Diagnostics { |
| 436 | + #[allow(unused_must_use)] |
| 437 | + fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) { |
| 438 | + // Looking for a straight chain of method calls from 'struct_span_err' to 'emit'. |
| 439 | + let ast::StmtKind::Semi(expr) = &stmt.kind else { |
| 440 | + return; |
| 441 | + }; |
| 442 | + let ast::ExprKind::MethodCall(meth) = &expr.kind else { |
| 443 | + return; |
| 444 | + }; |
| 445 | + if meth.seg.ident.name != sym::emit || !meth.args.is_empty() { |
| 446 | + return; |
| 447 | + } |
| 448 | + let mut segments = vec![]; |
| 449 | + let mut cur = &meth.receiver; |
| 450 | + let fake = &[].into(); |
| 451 | + loop { |
| 452 | + match &cur.kind { |
| 453 | + ast::ExprKind::Call(func, args) => { |
| 454 | + if let ast::ExprKind::Path(_, path) = &func.kind { |
| 455 | + segments.push((path.segments.last().unwrap().ident.name, args)) |
| 456 | + } |
| 457 | + break; |
| 458 | + } |
| 459 | + ast::ExprKind::MethodCall(method) => { |
| 460 | + segments.push((method.seg.ident.name, &method.args)); |
| 461 | + cur = &method.receiver; |
| 462 | + } |
| 463 | + ast::ExprKind::MacCall(mac) => { |
| 464 | + segments.push((mac.path.segments.last().unwrap().ident.name, fake)); |
| 465 | + break; |
| 466 | + } |
| 467 | + _ => { |
| 468 | + break; |
| 469 | + } |
| 470 | + } |
| 471 | + } |
| 472 | + segments.reverse(); |
| 473 | + if segments.is_empty() { |
| 474 | + return; |
| 475 | + } |
| 476 | + if segments[0].0.as_str() != "struct_span_err" { |
| 477 | + return; |
| 478 | + } |
| 479 | + if !segments.iter().all(|(name, args)| { |
| 480 | + let arg = match name.as_str() { |
| 481 | + "struct_span_err" | "span_note" | "span_label" | "span_help" => &args[1], |
| 482 | + "note" | "help" => &args[0], |
| 483 | + _ => { |
| 484 | + return false; |
| 485 | + } |
| 486 | + }; |
| 487 | + if let ast::ExprKind::Lit(lit) = arg.kind |
| 488 | + && let ast::token::LitKind::Str = lit.kind { |
| 489 | + true |
| 490 | + } else { |
| 491 | + false |
| 492 | + } |
| 493 | + }) { |
| 494 | + return; |
| 495 | + } |
| 496 | + cx.emit_spanned_lint( |
| 497 | + UNTRANSLATABLE_DIAGNOSTIC_TRIVIAL, |
| 498 | + stmt.span, |
| 499 | + UntranslatableDiagnosticTrivial, |
| 500 | + ); |
| 501 | + } |
| 502 | +} |
| 503 | + |
426 | 504 | declare_tool_lint! {
|
427 | 505 | /// The `bad_opt_access` lint detects accessing options by field instead of
|
428 | 506 | /// the wrapper function.
|
|
0 commit comments