use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn, MacroCall};
-use clippy_utils::source::snippet_opt;
+use clippy_utils::source::{expand_past_previous_comma, snippet_opt};
+use clippy_utils::{is_in_cfg_test, is_in_test_function};
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, HirIdMap, Impl, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::{declare_tool_lint, impl_lint_pass};
-use rustc_span::{sym, BytePos, Span};
+use rustc_span::{sym, BytePos};
declare_clippy_lint! {
/// ### What it does
#[derive(Default)]
pub struct Write {
in_debug_impl: bool,
+ allow_print_in_tests: bool,
+}
+
+impl Write {
+ pub fn new(allow_print_in_tests: bool) -> Self {
+ Self {
+ allow_print_in_tests,
+ ..Default::default()
+ }
+ }
}
impl_lint_pass!(Write => [
.as_ref()
.map_or(false, |crate_name| crate_name == "build_script_build");
+ let allowed_in_tests = self.allow_print_in_tests
+ && (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id));
match diag_name {
- sym::print_macro | sym::println_macro => {
+ sym::print_macro | sym::println_macro if !allowed_in_tests => {
if !is_build_script {
span_lint(cx, PRINT_STDOUT, macro_call.span, &format!("use of `{name}!`"));
}
},
- sym::eprint_macro | sym::eprintln_macro => {
+ sym::eprint_macro | sym::eprintln_macro if !allowed_in_tests => {
span_lint(cx, PRINT_STDERR, macro_call.span, &format!("use of `{name}!`"));
},
sym::write_macro | sym::writeln_macro => {},
// print!("\n"), write!(f, "\n")
diag.multipart_suggestion(
- &format!("use `{name}ln!` instead"),
+ format!("use `{name}ln!` instead"),
vec![(name_span, format!("{name}ln")), (format_string_span, String::new())],
Applicability::MachineApplicable,
);
let newline_span = format_string_span.with_lo(hi - BytePos(3)).with_hi(hi - BytePos(1));
diag.multipart_suggestion(
- &format!("use `{name}ln!` instead"),
+ format!("use `{name}ln!` instead"),
vec![(name_span, format!("{name}ln")), (newline_span, String::new())],
Applicability::MachineApplicable,
);
value.span,
"literal with an empty format string",
|diag| {
- if let Some(replacement) = replacement {
+ if let Some(replacement) = replacement
// `format!("{}", "a")`, `format!("{named}", named = "b")
// ~~~~~ ~~~~~~~~~~~~~
- let value_span = expand_past_previous_comma(cx, value.span);
-
+ && let Some(value_span) = format_args.value_with_prev_comma_span(value.hir_id)
+ {
let replacement = replacement.replace('{', "{{").replace('}', "}}");
diag.multipart_suggestion(
"try this",
if err { Err(UnescapeErr::Lint) } else { Ok(unescaped) }
}
-
-// Expand from `writeln!(o, "")` to `writeln!(o, "")`
-// ^^ ^^^^
-fn expand_past_previous_comma(cx: &LateContext<'_>, span: Span) -> Span {
- let extended = cx.sess().source_map().span_extend_to_prev_char(span, ',', true);
- extended.with_lo(extended.lo() - BytePos(1))
-}