diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -37,6 +37,7 @@
   UseNoexceptCheck.cpp
   UseNullptrCheck.cpp
   UseOverrideCheck.cpp
+  UseStdPrintCheck.cpp
   UseTrailingReturnTypeCheck.cpp
   UseTransparentFunctorsCheck.cpp
   UseUncaughtExceptionsCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -38,6 +38,7 @@
 #include "UseNoexceptCheck.h"
 #include "UseNullptrCheck.h"
 #include "UseOverrideCheck.h"
+#include "UseStdPrintCheck.h"
 #include "UseTrailingReturnTypeCheck.h"
 #include "UseTransparentFunctorsCheck.h"
 #include "UseUncaughtExceptionsCheck.h"
@@ -64,6 +65,7 @@
     CheckFactories.registerCheck<MakeSharedCheck>("modernize-make-shared");
     CheckFactories.registerCheck<MakeUniqueCheck>("modernize-make-unique");
     CheckFactories.registerCheck<PassByValueCheck>("modernize-pass-by-value");
+    CheckFactories.registerCheck<UseStdPrintCheck>("modernize-use-std-print");
     CheckFactories.registerCheck<RawStringLiteralCheck>(
         "modernize-raw-string-literal");
     CheckFactories.registerCheck<RedundantVoidArgCheck>(
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h
new file mode 100644
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h
@@ -0,0 +1,46 @@
+//===--- UseStdPrintCheck.h - clang-tidy-------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USESTDPRINTCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USESTDPRINTCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "../utils/IncludeInserter.h"
+
+namespace clang::tidy::modernize {
+/// Convert calls to printf-like functions to std::print and std::println
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html
+class UseStdPrintCheck : public ClangTidyCheck {
+public:
+  UseStdPrintCheck(StringRef Name, ClangTidyContext *Context);
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    if (ReplacementPrintFunction == "std::print" ||
+        ReplacementPrintlnFunction == "std::println")
+      return LangOpts.CPlusPlus23;
+    return LangOpts.CPlusPlus;
+  }
+  void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+                           Preprocessor *ModuleExpanderPP) override;
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+  bool StrictMode;
+  std::vector<StringRef> PrintfLikeFunctions;
+  std::vector<StringRef> FprintfLikeFunctions;
+  StringRef ReplacementPrintFunction;
+  StringRef ReplacementPrintlnFunction;
+  utils::IncludeInserter IncludeInserter;
+  std::optional<StringRef> MaybeHeaderToInclude;
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USESTDPRINTCHECK_H
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
new file mode 100644
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
@@ -0,0 +1,113 @@
+//===--- UseStdPrintCheck.cpp - clang-tidy-----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UseStdPrintCheck.h"
+#include "../utils/FormatStringConverter.h"
+#include "../utils/Matchers.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/FixIt.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+UseStdPrintCheck::UseStdPrintCheck(StringRef Name, ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context),
+      StrictMode(Options.getLocalOrGlobal("StrictMode", false)),
+      PrintfLikeFunctions(utils::options::parseStringList(
+          Options.get("PrintfLikeFunctions", ""))),
+      FprintfLikeFunctions(utils::options::parseStringList(
+          Options.get("FprintfLikeFunctions", ""))),
+      ReplacementPrintFunction(Options.get("PrintFunction", "std::print")),
+      ReplacementPrintlnFunction(
+          Options.get("PrintlnFunction", "std::println")),
+      IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
+                                               utils::IncludeSorter::IS_LLVM),
+                      areDiagsSelfContained()),
+      MaybeHeaderToInclude(Options.get("PrintHeader")) {
+
+  if (PrintfLikeFunctions.empty() && FprintfLikeFunctions.empty()) {
+    PrintfLikeFunctions.push_back("::printf");
+    PrintfLikeFunctions.push_back("absl::PrintF");
+    FprintfLikeFunctions.push_back("::fprintf");
+    FprintfLikeFunctions.push_back("absl::FPrintF");
+  }
+
+  if (!MaybeHeaderToInclude && (ReplacementPrintFunction == "std::print" ||
+                                ReplacementPrintlnFunction == "std::println"))
+    MaybeHeaderToInclude = "<print>";
+}
+
+void UseStdPrintCheck::registerPPCallbacks(const SourceManager &SM,
+                                           Preprocessor *PP,
+                                           Preprocessor *ModuleExpanderPP) {
+  IncludeInserter.registerPreprocessor(PP);
+}
+
+void UseStdPrintCheck::registerMatchers(MatchFinder *Finder) {
+  Finder->addMatcher(
+      traverse(TK_IgnoreUnlessSpelledInSource,
+               callExpr(callee(functionDecl(matchers::matchesAnyListedName(
+                                                PrintfLikeFunctions))
+                                   .bind("func_decl")),
+                        hasArgument(0, stringLiteral()))
+                   .bind("printf")),
+      this);
+
+  Finder->addMatcher(
+      traverse(TK_IgnoreUnlessSpelledInSource,
+               callExpr(callee(functionDecl(matchers::matchesAnyListedName(
+                                                FprintfLikeFunctions))
+                                   .bind("func_decl")),
+                        hasArgument(1, stringLiteral()))
+                   .bind("fprintf")),
+      this);
+}
+
+void UseStdPrintCheck::check(const MatchFinder::MatchResult &Result) {
+  unsigned FormatArgOffset = 0;
+  const auto *OldFunction = Result.Nodes.getNodeAs<FunctionDecl>("func_decl");
+  const auto *Printf = Result.Nodes.getNodeAs<CallExpr>("printf");
+  if (!Printf) {
+    Printf = Result.Nodes.getNodeAs<CallExpr>("fprintf");
+    FormatArgOffset = 1;
+  }
+
+  utils::FormatStringConverter Converter(
+      Result.Context, Printf, FormatArgOffset, StrictMode, getLangOpts());
+  const Expr *PrintfCall = Printf->getCallee();
+  const StringRef ReplacementFunction = Converter.usePrintNewlineFunction()
+                                            ? ReplacementPrintlnFunction
+                                            : ReplacementPrintFunction;
+  if (Converter.canApply()) {
+    DiagnosticBuilder Diag =
+        diag(PrintfCall->getBeginLoc(), "use '%0' instead of %1")
+        << ReplacementFunction << OldFunction->getIdentifier();
+
+    Diag << FixItHint::CreateReplacement(
+        CharSourceRange::getTokenRange(PrintfCall->getBeginLoc(),
+                                       PrintfCall->getEndLoc()),
+        ReplacementFunction);
+    Converter.applyFixes(Diag, *Result.SourceManager);
+
+    if (MaybeHeaderToInclude)
+      Diag << IncludeInserter.createIncludeInsertion(
+          Result.Context->getSourceManager().getFileID(
+              PrintfCall->getBeginLoc()),
+          *MaybeHeaderToInclude);
+  } else
+    DiagnosticBuilder Diag = diag(PrintfCall->getBeginLoc(),
+                                  "unable to use '%0' instead of %1 because %2")
+                             << ReplacementFunction
+                             << OldFunction->getIdentifier()
+                             << Converter.conversionNotPossibleReason();
+}
+
+} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt
--- a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt
@@ -11,6 +11,7 @@
   ExceptionSpecAnalyzer.cpp
   ExprSequence.cpp
   FileExtensionsUtils.cpp
+  FormatStringConverter.cpp
   FixItHintUtils.cpp
   HeaderGuard.cpp
   IncludeInserter.cpp
diff --git a/clang-tools-extra/clang-tidy/utils/FormatStringConverter.h b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.h
new file mode 100644
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.h
@@ -0,0 +1,78 @@
+//===--- FormatStringConverter.h - clang-tidy--------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Declaration of the FormatStringConverter class which is used to convert
+/// printf format strings to C++ std::formatter format strings.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FORMATSTRINGCONVERTER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FORMATSTRINGCONVERTER_H
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/FormatString.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/Optional.h"
+#include <string>
+
+namespace clang::tidy::utils {
+
+/// Convert a printf-style format string to a std::formatter-style one, and
+/// prepare any casts that are required to wrap the arguments to retain printf
+/// compatibility. This class is expecting to work on the already-cooked format
+/// string (i.e. all the escapes have been converted) so we have to convert them
+/// back. This means that we might not convert them back using the same form.
+class FormatStringConverter
+    : public clang::analyze_format_string::FormatStringHandler {
+public:
+  FormatStringConverter(ASTContext *Context, const CallExpr *Call,
+                        unsigned FormatArgOffset, bool StrictMode,
+                        const LangOptions &LO);
+
+  bool canApply() const { return ConversionNotPossibleReason.empty(); }
+  const std::string &conversionNotPossibleReason() const {
+    return ConversionNotPossibleReason;
+  }
+  void applyFixes(DiagnosticBuilder &Diag, SourceManager &SM);
+  bool usePrintNewlineFunction() const { return UsePrintNewlineFunction; }
+
+private:
+  ASTContext *Context;
+  const bool StrictMode;
+  const Expr *const *Args;
+  const unsigned NumArgs;
+  unsigned ArgsOffset;
+  const LangOptions &LangOpts;
+  std::string ConversionNotPossibleReason;
+  bool FormatStringNeededRewriting = false;
+  bool UsePrintNewlineFunction = false;
+  size_t PrintfFormatStringPos = 0U;
+  StringRef PrintfFormatString;
+
+  const StringLiteral *FormatExpr;
+  std::string StandardFormatString;
+
+  /// Casts to be used to wrap arguments to retain printf compatibility.
+  std::vector<std::tuple<const Expr *, std::string>> ArgFixes;
+  std::vector<clang::ast_matchers::BoundNodes> ArgCStrRemovals;
+
+  bool HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier &FS,
+                             const char *startSpecifier, unsigned specifierLen,
+                             const TargetInfo &Target) override;
+  void appendFormatText(StringRef Text);
+  void finalizeFormatText();
+  bool conversionNotPossible(std::string Reason) {
+    ConversionNotPossibleReason = std::move(Reason);
+    return false;
+  }
+};
+
+} // namespace clang::tidy::utils
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FORMATSTRINGCONVERTER_H
diff --git a/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp
new file mode 100644
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp
@@ -0,0 +1,635 @@
+//===--- FormatStringConverter.cpp - clang-tidy----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of the FormatStringConverter class which is used to convert
+/// printf format strings to C++ std::formatter format strings.
+///
+//===----------------------------------------------------------------------===//
+
+#include "FormatStringConverter.h"
+#include "../utils/FixItHintUtils.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Basic/LangOptions.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/FixIt.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/Debug.h"
+
+using namespace clang::ast_matchers;
+
+namespace {
+/// Is the passed type the actual "char" type, whether that be signed or
+/// unsigned, rather than explicit signed char or unsigned char types.
+bool isRealCharType(const clang::QualType &Ty) {
+  using namespace clang;
+  const Type *DesugaredType = Ty->getUnqualifiedDesugaredType();
+  if (const auto *BT = llvm::dyn_cast<BuiltinType>(DesugaredType)) {
+    const bool result = (BT->getKind() == BuiltinType::Char_U ||
+                         BT->getKind() == BuiltinType::Char_S);
+    return result;
+  } else {
+    return false;
+  }
+}
+
+/// If possible, return the text name of the signed type that corresponds to the
+/// passed integer type. If the passed type is already signed then its name is
+/// just returned. Only supports BuiltinTypes.
+std::optional<std::string>
+getCorrespondingSignedTypeName(const clang::QualType &QT) {
+  using namespace clang;
+  const auto UQT = QT.getUnqualifiedType();
+  if (const auto *BT = llvm::dyn_cast<BuiltinType>(UQT)) {
+    switch (BT->getKind()) {
+    case BuiltinType::UChar:
+    case BuiltinType::Char_U:
+    case BuiltinType::SChar:
+    case BuiltinType::Char_S:
+      return "signed char";
+    case BuiltinType::UShort:
+    case BuiltinType::Short:
+      return "short";
+    case BuiltinType::UInt:
+    case BuiltinType::Int:
+      return "int";
+    case BuiltinType::ULong:
+    case BuiltinType::Long:
+      return "long";
+    case BuiltinType::ULongLong:
+    case BuiltinType::LongLong:
+      return "long long";
+    default:
+      llvm::dbgs() << "Unknown corresponding signed type for BuiltinType '"
+                   << QT.getAsString() << "'\n";
+      return std::nullopt;
+    }
+  } else {
+    // Deal with fixed-width integer types from <cstdint>. Use std:: prefix only
+    // if the argument type does.
+    const std::string TypeName = UQT.getAsString();
+    StringRef SimplifiedTypeName{TypeName};
+    const bool InStd = SimplifiedTypeName.consume_front("std::");
+    const StringRef Prefix = InStd ? "std::" : "";
+
+    if (SimplifiedTypeName.starts_with("uint") &&
+        SimplifiedTypeName.ends_with("_t"))
+      return (Twine(Prefix) + SimplifiedTypeName.drop_front()).str();
+
+    if (SimplifiedTypeName == "size_t")
+      return (Twine(Prefix) + "ssize_t").str();
+
+    llvm::dbgs() << "Unknown corresponding signed type for non-BuiltinType '"
+                 << UQT.getAsString() << "'\n";
+    return std::nullopt;
+  }
+}
+
+/// If possible, return the text name of the unsigned type that corresponds to
+/// the passed integer type. If the passed type is already unsigned then its
+/// name is just returned. Only supports BuiltinTypes.
+std::optional<std::string>
+getCorrespondingUnsignedTypeName(const clang::QualType &QT) {
+  using namespace clang;
+  const auto UQT = QT.getUnqualifiedType();
+  if (const auto *BT = llvm::dyn_cast<BuiltinType>(UQT)) {
+    switch (BT->getKind()) {
+    case BuiltinType::SChar:
+    case BuiltinType::Char_S:
+    case BuiltinType::UChar:
+    case BuiltinType::Char_U:
+      return "unsigned char";
+    case BuiltinType::Short:
+    case BuiltinType::UShort:
+      return "unsigned short";
+    case BuiltinType::Int:
+    case BuiltinType::UInt:
+      return "unsigned int";
+    case BuiltinType::Long:
+    case BuiltinType::ULong:
+      return "unsigned long";
+    case BuiltinType::LongLong:
+    case BuiltinType::ULongLong:
+      return "unsigned long long";
+    default:
+      llvm::dbgs() << "Unknown corresponding unsigned type for BuiltinType '"
+                   << UQT.getAsString() << "'\n";
+      return std::nullopt;
+    }
+  } else {
+    // Deal with fixed-width integer types from <cstdint>. Use std:: prefix only
+    // if the argument type does.
+    const std::string TypeName = UQT.getAsString();
+    StringRef SimplifiedTypeName{TypeName};
+    const bool InStd = SimplifiedTypeName.consume_front("std::");
+    const StringRef Prefix = InStd ? "std::" : "";
+
+    if (SimplifiedTypeName.starts_with("int") &&
+        SimplifiedTypeName.ends_with("_t"))
+      return (Twine(Prefix) + "u" + SimplifiedTypeName).str();
+
+    if (SimplifiedTypeName == "ssize_t")
+      return (Twine(Prefix) + "size_t").str();
+    if (SimplifiedTypeName == "ptrdiff_t")
+      return (Twine(Prefix) + "size_t").str();
+
+    llvm::dbgs() << "Unknown corresponding unsigned type for non-BuiltinType '"
+                 << UQT.getAsString() << "'\n";
+    return std::nullopt;
+  }
+}
+} // namespace
+
+namespace clang::ast_matchers {
+AST_MATCHER(QualType, isRealChar) { return isRealCharType(Node); }
+} // namespace clang::ast_matchers
+
+namespace clang::tidy::utils {
+
+FormatStringConverter::FormatStringConverter(ASTContext *ContextIn,
+                                             const CallExpr *Call,
+                                             unsigned FormatArgOffset,
+                                             bool StrictMode,
+                                             const LangOptions &LO)
+    : Context(ContextIn), StrictMode(StrictMode), Args(Call->getArgs()),
+      NumArgs(Call->getNumArgs()), ArgsOffset(FormatArgOffset + 1),
+      LangOpts(LO) {
+  assert(ArgsOffset <= NumArgs);
+  FormatExpr = llvm::dyn_cast<StringLiteral>(
+      Args[FormatArgOffset]->IgnoreImplicitAsWritten());
+  assert(FormatExpr);
+  PrintfFormatString = FormatExpr->getString();
+
+  // Assume that the output will be approximately the same size as the input,
+  // but perhaps with a few escapes expanded.
+  StandardFormatString.reserve(PrintfFormatString.size() + 8);
+  StandardFormatString.push_back('\"');
+
+  const bool IsFreeBsdkPrintf = false;
+
+  using clang::analyze_format_string::ParsePrintfString;
+  ParsePrintfString(*this, PrintfFormatString.data(),
+                    PrintfFormatString.data() + PrintfFormatString.size(),
+                    LangOpts, Context->getTargetInfo(), IsFreeBsdkPrintf);
+  finalizeFormatText();
+}
+
+/// Called for each format specifier by ParsePrintfString.
+bool FormatStringConverter::HandlePrintfSpecifier(
+    const analyze_printf::PrintfSpecifier &FS, const char *StartSpecifier,
+    unsigned SpecifierLen, const TargetInfo &Target) {
+  using namespace analyze_printf;
+
+  const size_t StartSpecifierPos = StartSpecifier - PrintfFormatString.data();
+  assert(StartSpecifierPos + SpecifierLen <= PrintfFormatString.size());
+
+  // Everything before the specifier needs copying verbatim
+  assert(StartSpecifierPos >= PrintfFormatStringPos);
+
+  appendFormatText(StringRef(PrintfFormatString.begin() + PrintfFormatStringPos,
+                             StartSpecifierPos - PrintfFormatStringPos));
+
+  using analyze_format_string::ConversionSpecifier;
+  const ConversionSpecifier Spec = FS.getConversionSpecifier();
+
+  if (Spec.getKind() == ConversionSpecifier::PercentArg)
+    StandardFormatString.push_back('%');
+  else if (Spec.getKind() == ConversionSpecifier::Kind::nArg) {
+    // std::print doesn't do the equivalent of %n
+    return conversionNotPossible("'%n' is not supported in format string");
+  } else if (Spec.getKind() == ConversionSpecifier::Kind::PrintErrno) {
+    // std::print doesn't support %m. In theory we could insert a
+    // strerror(errno) parameter (assuming that libc has a thread-safe
+    // implementation, which glibc does), but that would require keeping track
+    // of the input and output parameter indices for position arguments too.
+    return conversionNotPossible("'%m' is not supported in format string");
+  } else {
+    StandardFormatString.push_back('{');
+
+    if (FS.usesPositionalArg()) {
+      // std::format argument identifiers are zero-based, whereas printf ones
+      // are one based.
+      assert(FS.getPositionalArgIndex() > 0U);
+      StandardFormatString.append(llvm::utostr(FS.getPositionalArgIndex() - 1));
+    }
+
+    // std::format format argument:
+    // [[fill]align][sign]["#"]["0"][width]["."precision][type]
+    std::string FormatSpec;
+
+    // printf doesn't support specifying the fill character - it's always a
+    // space, so we never need to generate one.
+
+    // Convert alignment
+    {
+      // We only care about alignment if a field width is specified
+      if (FS.getFieldWidth().getHowSpecified() !=
+          OptionalAmount::NotSpecified) {
+        const ConversionSpecifier Spec = FS.getConversionSpecifier();
+        if (Spec.getKind() == ConversionSpecifier::sArg) {
+          // Strings are left-aligned by default with std::format, so we only
+          // need to emit an alignment if this one needs to be right aligned.
+          if (!FS.isLeftJustified())
+            FormatSpec.push_back('>');
+        } else {
+          // Numbers are right-aligned by default with std::format, so we only
+          // need to emit an alignment if this one needs to be left aligned.
+          if (FS.isLeftJustified())
+            FormatSpec.push_back('<');
+        }
+      }
+    }
+
+    // Convert sign
+    {
+      const ConversionSpecifier Spec = FS.getConversionSpecifier();
+      // Ignore on something that isn't numeric. For printf it's would be a
+      // compile-time warning but ignored at runtime, but for std::format it
+      // ought to be a compile-time error.
+      if (Spec.isAnyIntArg() || Spec.isDoubleArg()) {
+        // + is preferred to ' '
+        if (FS.hasPlusPrefix())
+          FormatSpec.push_back('+');
+        else if (FS.hasSpacePrefix())
+          FormatSpec.push_back(' ');
+      }
+    }
+
+    // Convert alternative form
+    if (FS.hasAlternativeForm()) {
+      switch (Spec.getKind()) {
+      case ConversionSpecifier::Kind::aArg:
+      case ConversionSpecifier::Kind::AArg:
+      case ConversionSpecifier::Kind::eArg:
+      case ConversionSpecifier::Kind::EArg:
+      case ConversionSpecifier::Kind::fArg:
+      case ConversionSpecifier::Kind::FArg:
+      case ConversionSpecifier::Kind::gArg:
+      case ConversionSpecifier::Kind::GArg:
+      case ConversionSpecifier::Kind::xArg:
+      case ConversionSpecifier::Kind::XArg:
+      case ConversionSpecifier::Kind::oArg:
+        FormatSpec.push_back('#');
+        break;
+      default:
+        // Alternative forms don't exist for other argument kinds
+        break;
+      }
+    }
+
+    // Convert leading zero
+    if (FS.hasLeadingZeros())
+      FormatSpec.push_back('0');
+
+    // Convert field width
+    {
+      const OptionalAmount FieldWidth = FS.getFieldWidth();
+      switch (FieldWidth.getHowSpecified()) {
+      case OptionalAmount::NotSpecified:
+        break;
+      case OptionalAmount::Constant:
+        FormatSpec.append(llvm::utostr(FieldWidth.getConstantAmount()));
+        break;
+      case OptionalAmount::Arg:
+        FormatSpec.push_back('{');
+        if (FieldWidth.usesPositionalArg()) {
+          // std::format argument identifiers are zero-based, whereas printf
+          // ones are one based.
+          assert(FieldWidth.getPositionalArgIndex() > 0U);
+          FormatSpec.append(
+              llvm::utostr(FieldWidth.getPositionalArgIndex() - 1));
+        }
+        FormatSpec.push_back('}');
+        break;
+      case OptionalAmount::Invalid:
+        break;
+      }
+    }
+
+    // Convert precision
+    {
+      const OptionalAmount FieldPrecision = FS.getPrecision();
+      switch (FieldPrecision.getHowSpecified()) {
+      case OptionalAmount::NotSpecified:
+        break;
+      case OptionalAmount::Constant:
+        FormatSpec.push_back('.');
+        FormatSpec.append(llvm::utostr(FieldPrecision.getConstantAmount()));
+        break;
+      case OptionalAmount::Arg:
+        FormatSpec.push_back('.');
+        FormatSpec.push_back('{');
+        if (FieldPrecision.usesPositionalArg()) {
+          // std::format argument identifiers are zero-based, whereas printf
+          // ones are one based.
+          assert(FieldPrecision.getPositionalArgIndex() > 0U);
+          FormatSpec.append(
+              llvm::utostr(FieldPrecision.getPositionalArgIndex() - 1));
+        }
+        FormatSpec.push_back('}');
+        break;
+      case OptionalAmount::Invalid:
+        break;
+      }
+    }
+
+    // Convert type
+    {
+      const unsigned ArgIndex = FS.getArgIndex() + ArgsOffset;
+      if (ArgIndex >= NumArgs) {
+        // Argument index out of range. Give up.
+        return conversionNotPossible(
+            (Twine("argument index ") + Twine(ArgIndex) + " is out of range")
+                .str());
+      }
+
+      // If we've got this far, then the specifier must have an associated
+      // argument
+      assert(FS.consumesDataArgument());
+
+      const Expr *Arg = Args[ArgIndex]->IgnoreImplicitAsWritten();
+      using analyze_format_string::ConversionSpecifier;
+      const ConversionSpecifier Spec = FS.getConversionSpecifier();
+      switch (Spec.getKind()) {
+      case ConversionSpecifier::Kind::sArg: {
+        // If the argument is the result of a call to std::string::c_str() or
+        // data() with a return type of char then we can remove that call and
+        // pass the std::string directly. We don't want to do so if the return
+        // type is not a char pointer (though it's unlikely that such code would
+        // compile without warnings anyway.) See RedundantStringCStrCheck.
+        const auto StringDecl = type(hasUnqualifiedDesugaredType(recordType(
+            hasDeclaration(cxxRecordDecl(hasName("::std::basic_string"))))));
+        const auto StringExpr = expr(anyOf(
+            hasType(StringDecl), hasType(qualType(pointsTo(StringDecl)))));
+
+        const auto StringCStrCallExpr =
+            cxxMemberCallExpr(on(StringExpr.bind("arg")),
+                              callee(memberExpr().bind("member")),
+                              callee(cxxMethodDecl(
+                                  hasAnyName("c_str", "data"),
+                                  returns(pointerType(pointee(isRealChar()))))))
+                .bind("call");
+
+        auto CStrMatches = match(StringCStrCallExpr, *Arg, *Context);
+        if (CStrMatches.size() == 1)
+          ArgCStrRemovals.push_back(CStrMatches.front());
+        else if (Arg->getType()->isPointerType()) {
+          const QualType Pointee = Arg->getType()->getPointeeType();
+          // printf is happy to print signed char and unsigned char strings, but
+          // std::format only likes char strings.
+          if (Pointee->isCharType() && !isRealCharType(Pointee))
+            ArgFixes.emplace_back(Arg, "reinterpret_cast<const char *>(");
+        }
+        // Strings never need to have their type specified.
+        break;
+      }
+      case ConversionSpecifier::Kind::cArg:
+        // The type must be "c" to get a character unless the type is exactly
+        // char (whether that be signed or unsigned for the target.)
+        if (!isRealCharType(Arg->getType()))
+          FormatSpec.push_back('c');
+        break;
+      case ConversionSpecifier::Kind::dArg:
+      case ConversionSpecifier::Kind::iArg:
+        if (Arg->getType()->isBooleanType()) {
+          // std::format will print bool as either "true" or "false" by default,
+          // but printf prints them as "0" or "1". Ber compatible with printf by
+          // requesting decimal output.
+          FormatSpec.push_back('d');
+        } else if (Arg->getType()->isEnumeralType()) {
+          // std::format will try to find a specialization to print the enum
+          // (and probably fail), whereas printf would have just expected it to
+          // be passed as its underlying type. However, printf will have forced
+          // the signedness based on the format string, so we need to do the
+          // same.
+          if (const auto *ET = Arg->getType()->getAs<EnumType>()) {
+            if (const std::optional<std::string> MaybeSignedTypeName =
+                    getCorrespondingSignedTypeName(
+                        ET->getDecl()->getIntegerType()))
+              ArgFixes.emplace_back(
+                  Arg,
+                  (Twine("static_cast<") + *MaybeSignedTypeName + ">(").str());
+            else
+              return conversionNotPossible((Twine("argument ") +
+                                            Twine(ArgIndex) +
+                                            " has unexpected enum type")
+                                               .str());
+          }
+        } else if (Arg->getType()->isUnsignedIntegerType() && StrictMode) {
+          // printf will happily print an unsigned type as signed if told to.
+          // Even -Wformat doesn't warn for this. std::format will format as
+          // unsigned unless we cast it.
+          if (const std::optional<std::string> MaybeSignedTypeName =
+                  getCorrespondingSignedTypeName(Arg->getType()))
+            ArgFixes.emplace_back(
+                Arg,
+                (Twine("static_cast<") + *MaybeSignedTypeName + ">(").str());
+          else
+            return conversionNotPossible(
+                (Twine("argument ") + Twine(ArgIndex) +
+                 " cannot be cast to signed integer type to match format"
+                 " specifier and StrictMode is enabled")
+                    .str());
+        } else if (isRealCharType(Arg->getType()) ||
+                   !Arg->getType()->isIntegerType()) {
+          // Only specify integer if the argument is of a different type
+          FormatSpec.push_back('d');
+        }
+        break;
+      case ConversionSpecifier::Kind::uArg:
+        if (Arg->getType()->isEnumeralType()) {
+          // std::format will try to find a specialization to print the enum
+          // (and probably fail), whereas printf would have just expected it to
+          // be passed as its underlying type. However, printf will have forced
+          // the signedness based on the format string, so we need to do the
+          // same.
+          if (const auto *ET = Arg->getType()->getAs<EnumType>()) {
+            if (const std::optional<std::string> MaybeUnsignedTypeName =
+                    getCorrespondingUnsignedTypeName(
+                        ET->getDecl()->getIntegerType()))
+              ArgFixes.emplace_back(
+                  Arg, (Twine("static_cast<") + *MaybeUnsignedTypeName + ">(")
+                           .str());
+            else
+              return conversionNotPossible((Twine("argument ") +
+                                            Twine(ArgIndex) +
+                                            " has unexpected enum type")
+                                               .str());
+          }
+        } else if (Arg->getType()->isSignedIntegerType() && StrictMode) {
+          // printf will happily print an signed type as unsigned if told to.
+          // Even -Wformat doesn't warn for this. std::format will format as
+          // signed unless we cast it.
+          if (const std::optional<std::string> MaybeUnsignedTypeName =
+                  getCorrespondingUnsignedTypeName(Arg->getType()))
+            ArgFixes.emplace_back(
+                Arg,
+                (Twine("static_cast<") + *MaybeUnsignedTypeName + ">(").str());
+          else
+            return conversionNotPossible(
+                (Twine("argument ") + Twine(ArgIndex) +
+                 " cannot be cast to unsigned integer type to match format"
+                 " specifier and StrictMode is enabled")
+                    .str());
+        } else if (isRealCharType(Arg->getType()) ||
+                   Arg->getType()->isBooleanType() ||
+                   !Arg->getType()->isIntegerType()) {
+          // Only specify integer if the argument is of a different type
+          FormatSpec.push_back('d');
+        }
+        break;
+      case ConversionSpecifier::Kind::pArg:
+        // std::format knows how to format void pointers and nullptrs
+        if (!Arg->getType()->isNullPtrType() &&
+            !Arg->getType()->isVoidPointerType())
+          ArgFixes.emplace_back(Arg, "static_cast<const void *>(");
+        break;
+      case ConversionSpecifier::Kind::xArg:
+        FormatSpec.push_back('x');
+        break;
+      case ConversionSpecifier::Kind::XArg:
+        FormatSpec.push_back('X');
+        break;
+      case ConversionSpecifier::Kind::oArg:
+        FormatSpec.push_back('o');
+        break;
+      case ConversionSpecifier::Kind::aArg:
+        FormatSpec.push_back('a');
+        break;
+      case ConversionSpecifier::Kind::AArg:
+        FormatSpec.push_back('A');
+        break;
+      case ConversionSpecifier::Kind::eArg:
+        FormatSpec.push_back('e');
+        break;
+      case ConversionSpecifier::Kind::EArg:
+        FormatSpec.push_back('E');
+        break;
+      case ConversionSpecifier::Kind::fArg:
+        FormatSpec.push_back('f');
+        break;
+      case ConversionSpecifier::Kind::FArg:
+        FormatSpec.push_back('F');
+        break;
+      case ConversionSpecifier::Kind::gArg:
+        FormatSpec.push_back('g');
+        break;
+      case ConversionSpecifier::Kind::GArg:
+        FormatSpec.push_back('G');
+        break;
+      default:
+        // Something we don't understand
+        return conversionNotPossible((Twine("argument ") + Twine(ArgIndex) +
+                                      " has an unknown format specifier")
+                                         .str());
+      }
+    }
+
+    if (!FormatSpec.empty()) {
+      StandardFormatString.push_back(':');
+      StandardFormatString.append(FormatSpec);
+    }
+
+    StandardFormatString.push_back('}');
+  }
+
+  // Skip over specifier
+  PrintfFormatStringPos = StartSpecifierPos + SpecifierLen;
+  assert(PrintfFormatStringPos <= PrintfFormatString.size());
+
+  FormatStringNeededRewriting = true;
+  return true;
+}
+
+/// Called at the very end just before applying fixes to capture the last part
+/// of the format string.
+void FormatStringConverter::finalizeFormatText() {
+  appendFormatText(
+      StringRef(PrintfFormatString.begin() + PrintfFormatStringPos,
+                PrintfFormatString.size() - PrintfFormatStringPos));
+  PrintfFormatStringPos = PrintfFormatString.size();
+
+  if (StringRef(StandardFormatString).ends_with("\\n") &&
+      !StringRef(StandardFormatString).ends_with("\\\\n")) {
+    UsePrintNewlineFunction = true;
+    FormatStringNeededRewriting = true;
+    StandardFormatString.erase(StandardFormatString.end() - 2,
+                               StandardFormatString.end());
+  }
+
+  StandardFormatString.push_back('\"');
+}
+
+/// Append literal parts of the format text, reinstating escapes as required.
+void FormatStringConverter::appendFormatText(const StringRef Text) {
+  for (const char Ch : Text) {
+    if (Ch == '\a')
+      StandardFormatString += "\\a";
+    else if (Ch == '\b')
+      StandardFormatString += "\\b";
+    else if (Ch == '\f')
+      StandardFormatString += "\\f";
+    else if (Ch == '\n')
+      StandardFormatString += "\\n";
+    else if (Ch == '\r')
+      StandardFormatString += "\\r";
+    else if (Ch == '\t')
+      StandardFormatString += "\\t";
+    else if (Ch == '\v')
+      StandardFormatString += "\\v";
+    else if (Ch == '\"')
+      StandardFormatString += "\\\"";
+    else if (Ch == '\\')
+      StandardFormatString += "\\\\";
+    else if (Ch == '{') {
+      StandardFormatString += "{{";
+      FormatStringNeededRewriting = true;
+    } else if (Ch == '}') {
+      StandardFormatString += "}}";
+      FormatStringNeededRewriting = true;
+    } else if (Ch < 32) {
+      StandardFormatString += "\\x";
+      StandardFormatString += llvm::hexdigit(Ch >> 4, true);
+      StandardFormatString += llvm::hexdigit(Ch & 0xf, true);
+    } else
+      StandardFormatString += Ch;
+  }
+}
+
+/// Called by the check when it is ready to apply the fixes.
+void FormatStringConverter::applyFixes(DiagnosticBuilder &Diag,
+                                       SourceManager &SM) {
+  if (FormatStringNeededRewriting) {
+    Diag << FixItHint::CreateReplacement(
+        CharSourceRange::getTokenRange(FormatExpr->getBeginLoc(),
+                                       FormatExpr->getEndLoc()),
+        StandardFormatString);
+  }
+
+  for (const auto &[Arg, Replacement] : ArgFixes) {
+    SourceLocation AfterOtherSide =
+        Lexer::findNextToken(Arg->getEndLoc(), SM, LangOpts)->getLocation();
+
+    Diag << FixItHint::CreateInsertion(Arg->getBeginLoc(), Replacement)
+         << FixItHint::CreateInsertion(AfterOtherSide, ")");
+  }
+
+  for (const auto &Match : ArgCStrRemovals) {
+    const auto *Call = Match.getNodeAs<CallExpr>("call");
+    const auto *Arg = Match.getNodeAs<Expr>("arg");
+    const auto *Member = Match.getNodeAs<MemberExpr>("member");
+    const bool Arrow = Member->isArrow();
+    const std::string ArgText =
+        Arrow ? utils::fixit::formatDereference(*Arg, *Context)
+              : tooling::fixit::getText(*Arg, *Context).str();
+    if (!ArgText.empty())
+      Diag << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText);
+  }
+}
+} // namespace clang::tidy::utils
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -165,6 +165,16 @@
   Converts standard library type traits of the form ``traits<...>::type`` and
   ``traits<...>::value`` into ``traits_t<...>`` and ``traits_v<...>`` respectively.
 
+- New :doc: `modernize-use-std-print
+  <clang-tidy/checks/modernize/use-std-print>` check.
+
+  Converts calls to ``printf``, ``fprintf``, ``absl::PrintF``,
+  ``absl::FPrintf`` or other functions via configuration options, to
+  equivalent calls to C++23's ``std::print`` and ``std::println``, or other
+  functions via a configuration option, modifying the format string
+  appropriately and removing now-unnecessary calls to
+  ``std::string::c_str()`` and ``std::string::data()``.
+
 - New :doc:`performance-avoid-endl
   <clang-tidy/checks/performance/avoid-endl>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -299,6 +299,7 @@
    `modernize-use-noexcept <modernize/use-noexcept.html>`_, "Yes"
    `modernize-use-nullptr <modernize/use-nullptr.html>`_, "Yes"
    `modernize-use-override <modernize/use-override.html>`_, "Yes"
+   `modernize-use-std-print <modernize/use-std-print.html>`_, "Yes"
    `modernize-use-trailing-return-type <modernize/use-trailing-return-type.html>`_, "Yes"
    `modernize-use-transparent-functors <modernize/use-transparent-functors.html>`_, "Yes"
    `modernize-use-uncaught-exceptions <modernize/use-uncaught-exceptions.html>`_, "Yes"
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst
new file mode 100644
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst
@@ -0,0 +1,144 @@
+.. title:: clang-tidy - modernize-use-std-print
+
+modernize-use-std-print
+=======================
+
+Converts calls to ``printf``, ``fprintf``, ``absl::PrintF`` and
+``absl::FPrintf`` to equivalent calls to C++23's ``std::print`` or
+``std::println`` as appropriate, modifying the format string appropriately.
+The replaced and replacement functions can be customised by configuration
+options. Each argument that is the result of a call to ``std::string::c_str()`` and
+``std::string::data()`` will have that now-unnecessary call removed in a
+similar manner to the readability-redundant-string-cstr check.
+
+In other words, it turns lines like::
+
+.. code-block:: c++
+
+  fprintf(stderr, "The %s is %3d\n", description.c_str(), value);
+
+into::
+
+.. code-block:: c++
+
+  std::println(stderr, "The {} is {:3}", description, value);
+
+It doesn't do a bad job, but it's not perfect. In particular:
+
+- It assumes that the format string is correct for the arguments. If you
+  get any warnings when compiling with ``-Wformat`` then misbehaviour is
+  possible.
+
+- At the point that the check runs, the AST contains a single
+  ``StringLiteral`` for the format string and any macro expansion, token
+  pasting, adjacent string literal concatenation and escaping has been
+  handled. Although it's possible for the check to automatically put the
+  escapes back, they may not be exactly as they were written (e.g.
+  ``"\x0a"`` will become ``"\n"`` and ``"ab" "cd"`` will become
+  ``"abcd"``.) This is helpful since it means that the PRIx macros from
+  ``<inttypes.h>`` are removed correctly.
+
+- It supports field widths, precision, positional arguments, leading zeros,
+  leading ``+``, alignment and alternative forms.
+
+- Use of any unsupported flags or specifiers will cause the entire
+  statement to be left alone and a warning to be emitted. Particular
+  unsupported features are:
+
+  - The ``%'`` flag for thousands separators.
+
+  - The glibc extension ``%m``.
+
+If conversion would be incomplete or unsafe then the entire invocation will
+be left unchanged.
+
+If the call is deemed suitable for conversion then:
+
+- ``printf``, ``fprintf``, ``absl::PrintF``, ``absl::FPrintF`` and any
+  functions specified by the ``PrintfLikeFunctions`` option or
+  ``FprintfLikeFunctions`` are replaced with the function specified by the
+  ``PrintlnFunction`` option if the format string ends with ``\n`` or
+  ``PrintFunction`` otherwise.
+- the format string is rewritten to use the ``std::formatter`` language and
+  a ``\n`` is removed from the end.
+- any arguments that corresponded to ``%p`` specifiers that
+  ``std::formatter`` wouldn't accept are wrapped in a ``static_cast``
+  to ``const void *``.
+- any arguments that corresponded to ``%s`` specifiers where the argument
+  is of ``signed char`` or ``unsigned char`` type are wrapped in a
+  ``reinterpret_cast<const char *>``.
+- any arguments where the format string and the parameter differ in
+  signedness will be wrapped in an approprate ``static_cast`` if StrictMode
+  is enabled.
+- any arguments that end in a call to ``std::string::c_str()`` or
+  ``std::string_data()`` will have that call removed.
+
+Options
+-------
+
+.. option:: StrictMode
+
+   When true, the check will add casts when printing signed or unsigned
+   integer types (including fixed-width integer types from ``<cstdint>``,
+   ``ptrdiff_t``, ``size_t`` and ``ssize_t``) as the opposite signedness to
+   ensure that the output matches that of ``printf``. For example:
+
+  .. code-block:: c++
+
+    int i = -42;
+    unsigned int u = 0xffffffff;
+    printf("%d %u\n", i, u);
+
+  would be turned into::
+
+  .. code-block:: c++
+
+    std::print("{} {}\n", static_cast<unsigned int>(i), static_cast<int>(u));
+
+  to ensure that the output will continue to be the unsigned representation
+  of -42 and the signed representation of 0xffffffff (often 4294967254
+  and -1 respectively.) When false (which is the default), these casts
+  will not be added which may cause a change in the output.
+
+.. option:: PrintfLikeFunctions
+
+   A semicolon-separated list of (fully qualified) extra function names to
+   replace, with the requirement that the first parameter contains the
+   printf-style format string and the arguments to be formatted follow
+   immediately afterwards. If neither this option nor
+   ``FprintfLikeFunctions`` are set then the default value for this option
+   is ``printf; absl::PrintF``, otherwise it is empty.
+
+
+.. option:: FprintfLikeFunctions
+
+   A semicolon-separated list of (fully qualified) extra function names to
+   replace, with the requirement that the first parameter is retained, the
+   second parameter contains the printf-style format string and the
+   arguments to be formatted follow immediately afterwards. If neither this
+   option nor ``PrintfLikeFunctions`` are set then the default value for
+   this option is ``fprintf; absl::FPrintF``, otherwise it is empty.
+
+.. option:: PrintFunction
+
+   The function that will be used to replace ``printf``, ``fprintf`` etc.
+   during conversion rather than the default ``std::print`` when the
+   originalformat string does not end with ``\n``. It is expected that the
+   function provides an interface that is compatible with ``std::print``. A
+   suitable candidate would be ``fmt::print``.
+
+.. option:: PrintlnFunction
+
+   The function that will be used to replace ``printf``, ``fprintf`` etc.
+   during conversion rather than the default ``std::println`` when the
+   original format string ends with ``\n``. It is expected that the
+   function provides an interface that is compatible with ``std::println``.
+   A suitable candidate would be ``fmt::println``.
+
+.. option:: PrintHeader
+
+   The header that must be included for the declaration of
+   ``PrintFunction`` so that a ``#include`` directive can be added if
+   required. If ``PrintFunction`` is ``std::print`` then this option will
+   default to ``<print>``, otherwise this option will default to nothing
+   and no ``#include`` directive will be added.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstddef
copy from clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h
copy to clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstddef
--- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstddef
@@ -1,4 +1,4 @@
-//===--- string.h - Stub header for tests------ -----------------*- C++ -*-===//
+//===--- cstddef - Stub header for tests -------------------------*- C++ -*-===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -6,11 +6,14 @@
 //
 //===----------------------------------------------------------------------===//
 
-#ifndef _STRING_H_
-#define _STRING_H_
+#ifndef _CSTDDEF_
+#define _CSTDDEF_
 
-typedef __typeof__(sizeof(0)) size_t;
+#include "stddef.h"
 
-void *memcpy(void *dest, const void *src, size_t n);
+namespace std {
+  using ::ptrdiff_t;
+  using ::size_t;
+}
 
-#endif // _STRING_H_
+#endif // _CSTDDEF_
diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdint b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdint
new file mode 100644
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdint
@@ -0,0 +1,40 @@
+//===--- cstdint - Stub header for tests ------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _CSTDINT_
+#define _CSTDINT_
+
+typedef __INTMAX_TYPE__  intmax_t;
+typedef __UINTMAX_TYPE__ uintmax_t;
+
+namespace std {
+  using ::intmax_t;
+  using ::uintmax_t;
+}
+
+#define DECLARE_INTTYPE(N) \
+  typedef __INT ## N ## _TYPE__ int ## N ## _t; \
+  typedef int ## N ## _t int_least ## N ## _t; \
+  typedef int ## N ## _t int_fast ## N ## _t; \
+  typedef __UINT ## N ## _TYPE__ uint ## N ## _t; \
+  typedef uint ## N ## _t uint_least ## N ## _t; \
+  typedef uint ## N ## _t uint_fast ## N ## _t; \
+  namespace std { \
+    using ::int ## N ## _t; \
+    using ::int_least ## N ## _t; \
+    using ::int_fast ## N ## _t; \
+    using ::uint ## N ## _t; \
+    using ::uint_least ## N ## _t; \
+    using ::uint_fast ## N ## _t; \
+ } // std
+
+// For reasons unknown, these aren't coming in from <stdint.h>
+DECLARE_INTTYPE(8)
+DECLARE_INTTYPE(64)
+
+#endif // _CSTDINT__
diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio
copy from clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h
copy to clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio
--- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio
@@ -1,4 +1,4 @@
-//===--- string.h - Stub header for tests------ -----------------*- C++ -*-===//
+//===--- cstdio - Stub header for tests -------------------------*- C++ -*-===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -6,11 +6,16 @@
 //
 //===----------------------------------------------------------------------===//
 
-#ifndef _STRING_H_
-#define _STRING_H_
+#ifndef _STDIO_
+#define _STDIO_
 
-typedef __typeof__(sizeof(0)) size_t;
+#include <stdio.h>
 
-void *memcpy(void *dest, const void *src, size_t n);
+namespace std {
+  using ::FILE;
+  using ::printf;
+  using ::fprintf;
+}
+
+#endif // _STDIO_
 
-#endif // _STRING_H_
diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h
--- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h
@@ -12,7 +12,13 @@
 // A header intended to contain C standard input and output library
 // declarations.
 
+typedef struct structFILE {} FILE;
+extern FILE *stderr;
+
 int printf(const char *, ...);
+int fprintf(FILE *, const char *, ...);
+
+#define NULL (0)
 
 #endif // _STDIO_H_
 
diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h
--- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h
@@ -9,7 +9,7 @@
 #ifndef _STRING_H_
 #define _STRING_H_
 
-typedef __typeof__(sizeof(0)) size_t;
+#include "stddef.h"
 
 void *memcpy(void *dest, const void *src, size_t n);
 
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp
new file mode 100644
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp
@@ -0,0 +1,48 @@
+// RUN: %check_clang_tidy -std=c++23 %s modernize-use-std-print %t -- -- -isystem %clang_tidy_headers
+
+#include <cstdio>
+#include <string.h>
+
+namespace absl
+{
+// Use const char * for the format since the real type is hard to mock up.
+template <typename... Args>
+int PrintF(const char *format, const Args&... args);
+
+template <typename... Args>
+int FPrintF(FILE* output, const char *format, const Args&... args);
+}
+
+void printf_simple() {
+  absl::PrintF("Hello %s %d", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'PrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {} {}", "world", 42);
+}
+
+void printf_newline() {
+  absl::PrintF("Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'PrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {} {}", "world", 42);
+
+  using namespace absl;
+  PrintF("Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'PrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {} {}", "world", 42);
+}
+
+void fprintf_simple(FILE *fp) {
+  absl::FPrintF(fp, "Hello %s %d", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'FPrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(fp, "Hello {} {}", "world", 42);
+}
+
+void fprintf_newline(FILE *fp) {
+  absl::FPrintF(fp, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'FPrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(fp, "Hello {} {}", "world", 42);
+
+  using namespace absl;
+  FPrintF(fp, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'FPrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(fp, "Hello {} {}", "world", 42);
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp
new file mode 100644
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp
@@ -0,0 +1,75 @@
+// RUN: %check_clang_tidy -std=c++23 %s modernize-use-std-print %t -- \
+// RUN:   -config="{CheckOptions: \
+// RUN:             [ \
+// RUN:              { \
+// RUN:               key: modernize-use-std-print.PrintfLikeFunctions, \
+// RUN:               value: '::myprintf; mynamespace::myprintf2' \
+// RUN:              }, \
+// RUN:              { \
+// RUN:               key: modernize-use-std-print.FprintfLikeFunctions, \
+// RUN:               value: '::myfprintf; mynamespace::myfprintf2' \
+// RUN:              } \
+// RUN:             ] \
+// RUN:            }" \
+// RUN:   -- -isystem %clang_tidy_headers
+
+#include <cstdio>
+#include <string.h>
+
+int myprintf(const char *, ...);
+int myfprintf(FILE *fp, const char *, ...);
+
+namespace mynamespace {
+int myprintf2(const char *, ...);
+int myfprintf2(FILE *fp, const char *, ...);
+}
+
+void printf_simple() {
+  myprintf("Hello %s %d", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'myprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {} {}", "world", 42);
+}
+
+void printf_newline() {
+  myprintf("Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {} {}", "world", 42);
+
+  mynamespace::myprintf2("Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myprintf2' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {} {}", "world", 42);
+
+  using mynamespace::myprintf2;
+  myprintf2("Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myprintf2' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {} {}", "world", 42);
+
+  // When using custom options leave printf alone
+  printf("Hello %s %d\n", "world", 42);
+}
+
+void fprintf_simple(FILE *fp)
+{
+  myfprintf(stderr, "Hello %s %d", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'myfprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(stderr, "Hello {} {}", "world", 42);
+}
+
+void fprintf_newline(FILE *fp)
+{
+  myfprintf(stderr, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myfprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(stderr, "Hello {} {}", "world", 42);
+
+  mynamespace::myfprintf2(stderr, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myfprintf2' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(stderr, "Hello {} {}", "world", 42);
+
+  using mynamespace::myfprintf2;
+  myfprintf2(stderr, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myfprintf2' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(stderr, "Hello {} {}", "world", 42);
+
+  // When using custom options leave fprintf alone
+  fprintf(stderr, "Hello %s %d\n", "world", 42);
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-fmt.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-fmt.cpp
new file mode 100644
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-fmt.cpp
@@ -0,0 +1,46 @@
+// RUN: %check_clang_tidy %s modernize-use-std-print %t -- \
+// RUN:   -config="{CheckOptions: \
+// RUN:             [ \
+// RUN:              { \
+// RUN:               key: modernize-use-std-print.PrintFunction, \
+// RUN:               value: 'fmt::print' \
+// RUN:              }, \
+// RUN:              { \
+// RUN:               key: modernize-use-std-print.PrintlnFunction, \
+// RUN:               value: 'fmt::println' \
+// RUN:              }, \
+// RUN:              { \
+// RUN:               key: modernize-use-std-print.PrintHeader, \
+// RUN:               value: '<fmt/core.h>' \
+// RUN:              } \
+// RUN:             ] \
+// RUN:            }" \
+// RUN:   -- -isystem %clang_tidy_headers
+
+#include <cstdio>
+// CHECK-FIXES: #include <fmt/core.h>
+#include <string.h>
+
+void printf_simple() {
+  printf("Hello %s %d", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'fmt::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: fmt::print("Hello {} {}", "world", 42);
+}
+
+void printf_newline() {
+  printf("Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'fmt::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: fmt::println("Hello {} {}", "world", 42);
+}
+
+void fprintf_simple(FILE *fp) {
+  fprintf(fp, "Hello %s %d", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'fmt::print' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: fmt::print(fp, "Hello {} {}", "world", 42);
+}
+
+void fprintf_newline(FILE *fp) {
+  fprintf(fp, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'fmt::println' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: fmt::println(fp, "Hello {} {}", "world", 42);
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp
new file mode 100644
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp
@@ -0,0 +1,1255 @@
+// RUN: %check_clang_tidy -check-suffixes=,STRICT \
+// RUN:   -std=c++23 %s modernize-use-std-print %t -- \
+// RUN:   -config="{CheckOptions: [{key: StrictMode, value: true}]}" \
+// RUN:   -- -isystem %clang_tidy_headers
+// RUN: %check_clang_tidy -check-suffixes=,NOTSTRICT \
+// RUN:   -std=c++23 %s modernize-use-std-print %t -- \
+// RUN:   -config="{CheckOptions: [{key: StrictMode, value: false}]}" \
+// RUN:   -- -isystem %clang_tidy_headers
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+// CHECK-FIXES: #include <print>
+#include <inttypes.h>
+#include <string.h>
+#include <string>
+
+void printf_simple() {
+  printf("Hello");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello");
+}
+
+void printf_newline() {
+  printf("Hello\n");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello");
+
+  printf("Split" "\n");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Split");
+
+  printf("Double\n\n");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Double\n");
+}
+
+void printf_deceptive_newline() {
+  printf("Hello\\n");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello\\n");
+
+  printf("Hello\x0a");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello");
+}
+
+void fprintf_simple() {
+  fprintf(stderr, "Hello");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(stderr, "Hello");
+}
+
+void std_printf_simple() {
+  std::printf("std::Hello");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("std::Hello");
+}
+
+void printf_escape() {
+  printf("before \t");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("before \t");
+
+  printf("\n after");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("\n after");
+
+  printf("before \a after");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("before \a after");
+
+  printf("Bell\a%dBackspace\bFF%s\fNewline\nCR\rTab\tVT\vEscape\x1b\x07%d", 42, "string", 99);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Bell\a{}Backspace\bFF{}\fNewline\nCR\rTab\tVT\vEscape\x1b\a{}", 42, "string", 99);
+
+  printf("not special \x1b\x01\x7f");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("not special \x1b\x01\x7f");
+}
+
+void printf_percent() {
+  printf("before %%");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("before %");
+
+  printf("%% after");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("% after");
+
+  printf("before %% after");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("before % after");
+
+  printf("Hello %% and another %%");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello % and another %");
+
+  printf("Not a string %%s");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Not a string %s");
+}
+
+void printf_curlies() {
+  printf("%d {}", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{} {{[{][{]}}}}", 42);
+
+  printf("{}");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{{[{][{]}}}}");
+}
+
+void printf_unsupported_format_specifiers() {
+  int pos;
+  printf("%d %n %d\n", 42, &pos, 72);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: unable to use 'std::println' instead of 'printf' because '%n' is not supported in format string [modernize-use-std-print]
+
+  printf("Error %m\n");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: unable to use 'std::println' instead of 'printf' because '%m' is not supported in format string [modernize-use-std-print]
+}
+
+void printf_not_string_literal(const char *fmt) {
+  // We can't convert the format string if it's not a literal
+  printf(fmt, 42);
+}
+
+void printf_inttypes_ugliness() {
+  // The one advantage of the checker seeing the token pasted version of the
+  // format string is that we automatically cope with the horrendously-ugly
+  // inttypes.h macros!
+  int64_t u64 = 42;
+  uintmax_t umax = 4242;
+  printf("uint64:%" PRId64 " uintmax:%" PRIuMAX "\n", u64, umax);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("uint64:{} uintmax:{}", u64, umax);
+}
+
+void printf_raw_string() {
+  // This one doesn't require the format string to be changed, so it stays intact
+  printf(R"(First\Second)");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(R"(First\Second)");
+
+  // This one does require the format string to be changed, so unfortunately it
+  // gets reformatted as a normal string.
+  printf(R"(First %d\Second)", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("First {}\\Second", 42);
+}
+
+void printf_integer_d() {
+  const bool b = true;
+  // The "d" type is necessary here for compatibility with printf since
+  // std::print will print booleans as "true" or "false".
+  printf("Integer %d from bool\n", b);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {:d} from bool", b);
+
+  // The "d" type is necessary here for compatibility with printf since
+  // std::print will print booleans as "true" or "false".
+  printf("Integer %i from bool\n", b);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {:d} from bool", b);
+
+  // The 'd' is always necessary since otherwise the parameter will be formatted as a character
+  const char c = 'A';
+  printf("Integer %d from char\n", c);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {:d} from char", c);
+
+  printf("Integer %i from char\n", 'A');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {:d} from char", 'A');
+
+  const signed char sc = 'A';
+  printf("Integer %hhd from signed char\n", sc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from signed char", sc);
+
+  printf("Integer %hhi from signed char\n", sc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from signed char", sc);
+
+  const unsigned char uc = 'A';
+  printf("Integer %hhd from unsigned char\n", uc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned char", uc);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned char", static_cast<signed char>(uc));
+
+  printf("Integer %hhi from unsigned char\n", uc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned char", uc);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned char", static_cast<signed char>(uc));
+
+  const int8_t i8 = 42;
+  printf("Integer %" PRIi8 " from int8_t\n", i8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from int8_t", i8);
+
+  const int_fast8_t if8 = 42;
+  printf("Integer %" PRIiFAST8 " from int_fast8_t\n", if8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from int_fast8_t", if8);
+
+  const int_least8_t il8 = 42;
+  printf("Integer %" PRIiFAST8 " from int_least8_t\n", il8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from int_least8_t", il8);
+
+  const uint8_t u8 = 42U;
+  const std::uint8_t su8 = u8;
+  printf("Integers %" PRIi8 " and %" PRId8 " from uint8_t\n", u8, su8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integers {} and {} from uint8_t", u8, su8);
+  // CHECK-FIXES-STRICT: std::println("Integers {} and {} from uint8_t", static_cast<int8_t>(u8), static_cast<std::int8_t>(su8));
+
+  const uint_fast8_t uf8 = 42U;
+  printf("Integer %" PRIiFAST8 " from uint_fast8_t\n", uf8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from uint_fast8_t", uf8);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from uint_fast8_t", static_cast<int_fast8_t>(uf8));
+
+  const uint_least8_t ul8 = 42U;
+  printf("Integer %" PRIiLEAST8 " from uint_least8_t\n", ul8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from uint_least8_t", ul8);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from uint_least8_t", static_cast<int_least8_t>(ul8));
+
+  const short s = 42;
+  printf("Integer %hd from short\n", s);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from short", s);
+
+  printf("Integer %hi from short\n", s);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from short", s);
+
+  const unsigned short us = 42U;
+  printf("Integer %hd from unsigned short\n", us);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned short", us);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned short", static_cast<short>(us));
+
+  printf("Integer %hi from unsigned short\n", us);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned short", us);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned short", static_cast<short>(us));
+
+  const int i = 42;
+  printf("Integer %d from integer\n", i);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from integer", i);
+
+  printf("Integer %i from integer\n", i);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from integer", i);
+
+  const unsigned int ui = 42U;
+  printf("Integer %d from unsigned integer\n", ui);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned integer", ui);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned integer", static_cast<int>(ui));
+
+  printf("Integer %i from unsigned integer\n", ui);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned integer", ui);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned integer", static_cast<int>(ui));
+
+  const long l = 42L;
+  printf("Integer %ld from long\n", l);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from long", l);
+
+  printf("Integer %li from long\n", l);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from long", l);
+
+  const unsigned long ul = 42UL;
+  printf("Integer %ld from unsigned long\n", ul);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned long", ul);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned long", static_cast<long>(ul));
+
+  printf("Integer %li from unsigned long\n", ul);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned long", ul);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned long", static_cast<long>(ul));
+
+  const long long ll = 42LL;
+  printf("Integer %lld from long long\n", ll);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from long long", ll);
+
+  printf("Integer %lli from long long\n", ll);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from long long", ll);
+
+  const unsigned long long ull = 42ULL;
+  printf("Integer %lld from unsigned long long\n", ull);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned long long", ull);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned long long", static_cast<long long>(ull));
+
+  printf("Integer %lli from unsigned long long\n", ull);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned long long", ull);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned long long", static_cast<long long>(ull));
+
+  const intmax_t im = 42;
+  const std::intmax_t sim = im;
+  printf("Integers %jd and %jd from intmax_t\n", im, sim);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integers {} and {} from intmax_t", im, sim);
+
+  printf("Integers %ji and %ji from intmax_t\n", im, sim);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integers {} and {} from intmax_t", im, sim);
+
+  const uintmax_t uim = 42;
+  const std::uintmax_t suim = uim;
+  printf("Integers %jd and %jd from uintmax_t\n", uim, suim);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integers {} and {} from uintmax_t", uim, suim);
+  // CHECK-FIXES-STRICT: std::println("Integers {} and {} from uintmax_t", static_cast<intmax_t>(uim), static_cast<std::intmax_t>(suim));
+
+  printf("Integer %ji from intmax_t\n", uim);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from intmax_t", uim);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from intmax_t", static_cast<intmax_t>(uim));
+
+  const int ai[] = { 0, 1, 2, 3};
+  const ptrdiff_t pd = &ai[3] - &ai[0];
+  const std::ptrdiff_t spd = pd;
+  printf("Integers %td and %td from ptrdiff_t\n", pd, spd);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integers {} and {} from ptrdiff_t", pd, spd);
+
+  printf("Integers %ti and %ti from ptrdiff_t\n", pd, spd);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integers {} and {} from ptrdiff_t", pd, spd);
+
+  const size_t z = 42UL;
+  const std::size_t sz = z;
+  printf("Integers %zd and %zd from size_t\n", z, sz);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integers {} and {} from size_t", z, sz);
+  // CHECK-FIXES-STRICT: std::println("Integers {} and {} from size_t", static_cast<ssize_t>(z), static_cast<std::ssize_t>(sz));
+}
+
+void printf_integer_u()
+{
+  const bool b = true;
+  // The "d" type is necessary here for compatibility with printf since
+  // std::print will print booleans as "true" or "false".
+  printf("Unsigned integer %u from bool\n", b);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {:d} from bool", b);
+
+  const char c = 'A';
+  printf("Unsigned integer %hhu from char\n", c);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {:d} from char", c);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from char", static_cast<unsigned char>(c));
+
+  const signed char sc = 'A';
+  printf("Unsigned integer %hhu from signed char\n", sc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from signed char", sc);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from signed char", static_cast<unsigned char>(sc));
+
+  printf("Unsigned integer %u from signed char\n", sc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from signed char", sc);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from signed char", static_cast<unsigned char>(sc));
+
+  const unsigned char uc = 'A';
+  printf("Unsigned integer %hhu from unsigned char\n", uc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from unsigned char", uc);
+
+  printf("Unsigned integer %u from unsigned char\n", uc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from unsigned char", uc);
+
+  const int8_t i8 = 42;
+  printf("Unsigned integer %" PRIu8 " from int8_t\n", i8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from int8_t", i8);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from int8_t", static_cast<uint8_t>(i8));
+
+  const int_fast8_t if8 = 42;
+  printf("Unsigned integer %" PRIuFAST8 " from int_fast8_t\n", if8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from int_fast8_t", if8);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from int_fast8_t", static_cast<uint_fast8_t>(if8));
+
+  const int_least8_t il8 = 42;
+  printf("Unsigned integer %" PRIuFAST8 " from int_least8_t\n", il8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from int_least8_t", il8);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from int_least8_t", static_cast<uint_least8_t>(il8));
+
+  const uint8_t u8 = 42U;
+  printf("Unsigned integer %" PRIu8 " from uint8_t\n", u8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from uint8_t", u8);
+
+  const uint_fast8_t uf8 = 42U;
+  printf("Unsigned integer %" PRIuFAST8 " from uint_fast8_t\n", uf8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from uint_fast8_t", uf8);
+
+  const uint_least8_t ul8 = 42U;
+  printf("Unsigned integer %" PRIuLEAST8 " from uint_least8_t\n", ul8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from uint_least8_t", ul8);
+
+  const short s = 42;
+  printf("Unsigned integer %hu from short\n", s);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from short", s);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from short", static_cast<unsigned short>(s));
+
+  const unsigned short us = 42U;
+  printf("Unsigned integer %hu from unsigned short\n", us);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from unsigned short", us);
+
+  const int i = 42;
+  printf("Unsigned integer %u from signed integer\n", i);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from signed integer", i);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from signed integer", static_cast<unsigned int>(i));
+
+  const unsigned int ui = 42U;
+  printf("Unsigned integer %u from unsigned integer\n", ui);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from unsigned integer", ui);
+
+  const long l = 42L;
+  printf("Unsigned integer %u from signed long\n", l);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from signed long", l);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from signed long", static_cast<unsigned long>(l));
+
+  const unsigned long ul = 42UL;
+  printf("Unsigned integer %lu from unsigned long\n", ul);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from unsigned long", ul);
+
+  const long long ll = 42LL;
+  printf("Unsigned integer %llu from long long\n", ll);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from long long", ll);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from long long", static_cast<unsigned long long>(ll));
+
+  const unsigned long long ull = 42ULL;
+  printf("Unsigned integer %llu from unsigned long long\n", ull);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from unsigned long long", ull);
+
+  const intmax_t im = 42;
+  const std::intmax_t sim = im;
+  printf("Unsigned integers %ju and %ju from intmax_t\n", im, sim);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integers {} and {} from intmax_t", im, sim);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integers {} and {} from intmax_t", static_cast<uintmax_t>(im), static_cast<std::uintmax_t>(sim));
+
+  const uintmax_t uim = 42U;
+  const std::uintmax_t suim = uim;
+  printf("Unsigned integers %ju and %ju from uintmax_t\n", uim, suim);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integers {} and {} from uintmax_t", uim, suim);
+
+  const int ai[] = { 0, 1, 2, 3};
+  const ptrdiff_t pd = &ai[3] - &ai[0];
+  const std::ptrdiff_t spd = pd;
+  printf("Unsigned integers %tu and %tu from ptrdiff_t\n", pd, spd);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integers {} and {} from ptrdiff_t", pd, spd);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integers {} and {} from ptrdiff_t", static_cast<size_t>(pd), static_cast<std::size_t>(spd));
+
+  const size_t z = 42U;
+  const std::size_t sz = z;
+  printf("Unsigned integers %zu and %zu from size_t\n", z, sz);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integers {} and {} from size_t", z, sz);
+}
+
+// This checks that we get the argument offset right with the extra FILE * argument
+void fprintf_integer() {
+  fprintf(stderr, "Integer %d from integer\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(stderr, "Integer {} from integer", 42);
+
+  fprintf(stderr, "Integer %i from integer\n", 65);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(stderr, "Integer {} from integer", 65);
+
+  fprintf(stderr, "Integer %i from char\n", 'A');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(stderr, "Integer {:d} from char", 'A');
+
+  fprintf(stderr, "Integer %d from char\n", 'A');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(stderr, "Integer {:d} from char", 'A');
+}
+
+void printf_char() {
+  const char c = 'A';
+  printf("Char %c from char\n", c);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {} from char", c);
+
+  const signed char sc = 'A';
+  printf("Char %c from signed char\n", sc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {:c} from signed char", sc);
+
+  const unsigned char uc = 'A';
+  printf("Char %c from unsigned char\n", uc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {:c} from unsigned char", uc);
+
+  const int i = 65;
+  printf("Char %c from integer\n", i);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {:c} from integer", i);
+
+  const unsigned int ui = 65;
+  printf("Char %c from unsigned integer\n", ui);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {:c} from unsigned integer", ui);
+
+  const unsigned long long ull = 65;
+  printf("Char %c from unsigned long long\n", ull);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {:c} from unsigned long long", ull);
+}
+
+void printf_bases() {
+  printf("Hex %lx\n", 42L);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hex {:x}", 42L);
+
+  printf("HEX %X\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("HEX {:X}", 42);
+
+  printf("Oct %lo\n", 42L);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Oct {:o}", 42L);
+}
+
+void printf_alternative_forms() {
+  printf("Hex %#lx\n", 42L);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hex {:#x}", 42L);
+
+  printf("HEX %#X\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("HEX {:#X}", 42);
+
+  printf("Oct %#lo\n", 42L);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Oct {:#o}", 42L);
+
+  printf("Double %#f %#F\n", -42.0, -42.0);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Double {:#f} {:#F}", -42.0, -42.0);
+
+  printf("Double %#g %#G\n", -42.0, -42.0);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Double {:#g} {:#G}", -42.0, -42.0);
+
+  printf("Double %#e %#E\n", -42.0, -42.0);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Double {:#e} {:#E}", -42.0, -42.0);
+
+  printf("Double %#a %#A\n", -42.0, -42.0);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Double {:#a} {:#A}", -42.0, -42.0);
+
+  // Characters don't have an alternate form
+  printf("Char %#c\n", 'A');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {}", 'A');
+
+  // Strings don't have an alternate form
+  printf("Char %#c\n", 'A');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {}", 'A');
+}
+
+void printf_string() {
+  printf("Hello %s after\n", "Goodbye");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {} after", "Goodbye");
+
+  // std::print can't print signed char strings.
+  const signed char *sstring = reinterpret_cast<const signed char *>("ustring");
+  printf("signed char string %s\n", sstring);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("signed char string {}", reinterpret_cast<const char *>(sstring));
+
+  // std::print can't print unsigned char strings.
+  const unsigned char *ustring = reinterpret_cast<const unsigned char *>("ustring");
+  printf("unsigned char string %s\n", ustring);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("unsigned char string {}", reinterpret_cast<const char *>(ustring));
+}
+
+void printf_float() {
+  // If the type is not specified then either f or e will be used depending on
+  // whichever is shorter. This means that it is necessary to be specific to
+  // maintain compatibility with printf.
+
+  // TODO: Should we force a cast here, since printf will promote to double
+  // automatically, but std::format will not, which could result in different
+  // output?
+
+  const float f = 42.0F;
+  printf("Hello %f after\n", f);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:f} after", f);
+
+  printf("Hello %g after\n", f);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:g} after", f);
+
+  printf("Hello %e after\n", f);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:e} after", f);
+}
+
+void printf_double() {
+  // If the type is not specified then either f or e will be used depending on
+  // whichever is shorter. This means that it is necessary to be specific to
+  // maintain compatibility with printf.
+
+  const double d = 42.0;
+  printf("Hello %f after\n", d);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:f} after", d);
+
+  printf("Hello %g after\n", d);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:g} after", d);
+
+  printf("Hello %e after\n", d);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:e} after", d);
+}
+
+void printf_long_double() {
+  // If the type is not specified then either f or e will be used depending on
+  // whichever is shorter. This means that it is necessary to be specific to
+  // maintain compatibility with printf.
+
+  const long double ld = 42.0L;
+  printf("Hello %Lf after\n", ld);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:f} after", ld);
+
+  printf("Hello %g after\n", ld);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:g} after", ld);
+
+  printf("Hello %e after\n", ld);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:e} after", ld);
+}
+
+void printf_pointer() {
+  int i;
+  double j;
+  printf("Int* %p %s %p\n", &i, "Double*", &j);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Int* {} {} {}", static_cast<const void *>(&i), "Double*", static_cast<const void *>(&j));
+
+  printf("%p\n", nullptr);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", nullptr);
+
+  const auto np = nullptr;
+  printf("%p\n", np);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", np);
+
+  // NULL isn't a pointer, so std::print needs some help.
+  printf("%p\n", NULL);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", static_cast<const void *>(NULL));
+
+  printf("%p\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", static_cast<const void *>(42));
+
+  // If we already have a void pointer then no cast is required.
+  printf("%p\n", reinterpret_cast<const void *>(44));
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", reinterpret_cast<const void *>(44));
+
+  const void *p;
+  printf("%p\n", p);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", p);
+
+  // But a pointer to a pointer to void does need a cast
+  const void **pp;
+  printf("%p\n", pp);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", static_cast<const void *>(pp));
+
+  printf("%p\n", printf_pointer);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", static_cast<const void *>(printf_pointer));
+}
+
+class AClass
+{
+  int member;
+
+  void printf_this_pointer()
+  {
+    printf("%p\n", this);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("{}", static_cast<const void *>(this));
+  }
+
+  void printf_pointer_to_member_function()
+  {
+    printf("%p\n", &AClass::printf_pointer_to_member_function);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("{}", static_cast<const void *>(&AClass::printf_pointer_to_member_function));
+  }
+
+  void printf_pointer_to_member_variable()
+  {
+    printf("%p\n", &AClass::member);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("{}", static_cast<const void *>(&AClass::member));
+  }
+};
+
+void printf_positional_arg() {
+  printf("%1$d", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{0}", 42);
+
+  printf("before %1$d", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("before {0}", 42);
+
+  printf("%1$d after", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{0} after", 42);
+
+  printf("before %1$d after", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("before {0} after", 42);
+
+  printf("before %2$d between %1$s after", "string", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("before {1} between {0} after", "string", 42);
+}
+
+// printf always defaults to right justification,, no matter what the type is of
+// the argument. std::format uses left justification by default for strings, and
+// right justification for numbers.
+void printf_right_justified() {
+  printf("Right-justified integer %4d after\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Right-justified integer {:4} after", 42);
+
+  printf("Right-justified double %4f\n", 227.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Right-justified double {:4f}", 227.2);
+
+  printf("Right-justified double %4g\n", 227.4);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Right-justified double {:4g}", 227.4);
+
+  printf("Right-justified integer with field width argument %*d after\n", 5, 424242);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Right-justified integer with field width argument {:{}} after", 5, 424242);
+
+  printf("Right-justified integer with field width argument %2$*1$d after\n", 5, 424242);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Right-justified integer with field width argument {1:{0}} after", 5, 424242);
+
+  printf("Right-justified string %20s\n", "Hello");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Right-justified string {:>20}", "Hello");
+
+  printf("Right-justified string with field width argument %2$*1$s after\n", 20, "wibble");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Right-justified string with field width argument {1:>{0}} after", 20, "wibble");
+}
+
+// printf always requires - for left justification, no matter what the type is
+// of the argument. std::format uses left justification by default for strings,
+// and right justification for numbers.
+void printf_left_justified() {
+  printf("Left-justified integer %-4d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified integer {:<4}", 42);
+
+  printf("Left-justified integer %--4d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified integer {:<4}", 42);
+
+  printf("Left-justified double %-4f\n", 227.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified double {:<4f}", 227.2);
+
+  printf("Left-justified double %-4g\n", 227.4);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified double {:<4g}", 227.4);
+
+  printf("Left-justified integer with field width argument %-*d after\n", 5, 424242);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified integer with field width argument {:<{}} after", 5, 424242);
+
+  printf("Left-justified integer with field width argument %2$-*1$d after\n", 5, 424242);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified integer with field width argument {1:<{0}} after", 5, 424242);
+
+  printf("Left-justified string %-20s\n", "Hello");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified string {:20}", "Hello");
+
+  printf("Left-justified string with field width argument %2$-*1$s after\n", 5, "wibble");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified string with field width argument {1:{0}} after", 5, "wibble");
+}
+
+void printf_precision() {
+  printf("Hello %.3f\n", 3.14159);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:.3f}", 3.14159);
+
+  printf("Hello %10.3f\n", 3.14159);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:10.3f}", 3.14159);
+
+  printf("Hello %.*f after\n", 10, 3.14159265358979323846);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:.{}f} after", 10, 3.14159265358979323846);
+
+  printf("Hello %10.*f after\n", 3, 3.14159265358979323846);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:10.{}f} after", 3, 3.14159265358979323846);
+
+  printf("Hello %*.*f after\n", 10, 4, 3.14159265358979323846);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:{}.{}f} after", 10, 4, 3.14159265358979323846);
+
+  printf("Hello %1$.*2$f after\n", 3.14159265358979323846, 4);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {0:.{1}f} after", 3.14159265358979323846, 4);
+
+  // Precision is ignored, but maintained on non-numeric arguments
+  printf("Hello %.5s\n", "Goodbye");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:.5}", "Goodbye");
+
+  printf("Hello %.5c\n", 'G');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:.5}", 'G');
+}
+
+void printf_field_width_and_precision() {
+  printf("Hello %1$*2$.*3$f after\n", 3.14159265358979323846, 4, 2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {0:{1}.{2}f} after", 3.14159265358979323846, 4, 2);
+}
+
+void printf_alternative_form() {
+  printf("Wibble %#x\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Wibble {:#x}", 42);
+
+  printf("Wibble %#20x\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Wibble {:#20x}", 42);
+
+  printf("Wibble %#020x\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Wibble {:#020x}", 42);
+
+  printf("Wibble %#-20x\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Wibble {:<#20x}", 42);
+}
+
+void printf_leading_plus() {
+  printf("Positive integer %+d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Positive integer {:+}", 42);
+
+  printf("Positive double %+f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Positive double {:+f}", 42.2);
+
+  printf("Positive double %+g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Positive double {:+g}", 42.2);
+
+  // Ignore leading plus on strings to avoid potential runtime exception where
+  // printf would have just ignored it.
+  printf("Positive string %+s\n", "string");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Positive string {}", "string");
+}
+
+void printf_leading_space() {
+  printf("Spaced integer % d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Spaced integer {: }", 42);
+
+  printf("Spaced integer %- d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Spaced integer {: }", 42);
+
+  printf("Spaced double % f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Spaced double {: f}", 42.2);
+
+  printf("Spaced double % g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Spaced double {: g}", 42.2);
+}
+
+void printf_leading_zero() {
+  printf("Leading zero integer %03d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero integer {:03}", 42);
+
+  printf("Leading minus and zero integer %-03d minus ignored\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading minus and zero integer {:<03} minus ignored", 42);
+
+  printf("Leading zero unsigned integer %03u\n", 42U);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero unsigned integer {:03}", 42U);
+
+  printf("Leading zero double %03f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero double {:03f}", 42.2);
+
+  printf("Leading zero double %03g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero double {:03g}", 42.2);
+}
+
+void printf_leading_plus_and_space() {
+  // printf prefers plus to space. {fmt} will throw if both are present.
+  printf("Spaced integer % +d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Spaced integer {:+}", 42);
+
+  printf("Spaced double %+ f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Spaced double {:+f}", 42.2);
+
+  printf("Spaced double % +g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Spaced double {:+g}", 42.2);
+}
+
+void printf_leading_zero_and_plus() {
+  printf("Leading zero integer %+03d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero integer {:+03}", 42);
+
+  printf("Leading zero double %0+3f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero double {:+03f}", 42.2);
+
+  printf("Leading zero double %0+3g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero double {:+03g}", 42.2);
+}
+
+void printf_leading_zero_and_space() {
+  printf("Leading zero and space integer %0 3d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero and space integer {: 03}", 42);
+
+  printf("Leading zero and space double %0 3f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero and space double {: 03f}", 42.2);
+
+  printf("Leading zero and space double %0 3g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero and space double {: 03g}", 42.2);
+}
+
+// add signed plained enum too
+enum PlainEnum { red };
+enum SignedPlainEnum { black = -42 };
+enum BoolEnum : unsigned int { yellow };
+enum CharEnum : char { purple };
+enum SCharEnum : signed char  { aquamarine };
+enum UCharEnum : unsigned char  { pink };
+enum ShortEnum : short { beige };
+enum UShortEnum : unsigned short { grey };
+enum IntEnum : int { green };
+enum UIntEnum : unsigned int { blue };
+enum LongEnum : long { magenta };
+enum ULongEnum : unsigned long { cyan };
+enum LongLongEnum : long long { taupe };
+enum ULongLongEnum : unsigned long long { brown };
+
+void printf_enum_d() {
+  PlainEnum plain_enum;
+  printf("%d", plain_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<int>(plain_enum));
+
+  SignedPlainEnum splain_enum;
+  printf("%d", splain_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<int>(splain_enum));
+
+  BoolEnum bool_enum;
+  printf("%d", bool_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<int>(bool_enum));
+
+  CharEnum char_enum;
+  printf("%d", char_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<signed char>(char_enum));
+
+  SCharEnum schar_enum;
+  printf("%d", schar_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<signed char>(schar_enum));
+
+  UCharEnum uchar_enum;
+  printf("%d", uchar_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<signed char>(uchar_enum));
+
+  ShortEnum short_enum;
+  printf("%d", short_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<short>(short_enum));
+
+  UShortEnum ushort_enum;
+  printf("%d", ushort_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<short>(ushort_enum));
+
+  IntEnum int_enum;
+  printf("%d", int_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<int>(int_enum));
+
+  UIntEnum uint_enum;
+  printf("%d", uint_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<int>(uint_enum));
+
+  LongEnum long_enum;
+  printf("%d", long_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<long>(long_enum));
+
+  ULongEnum ulong_enum;
+  printf("%d", ulong_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<long>(ulong_enum));
+
+  LongLongEnum longlong_enum;
+  printf("%d", longlong_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<long long>(longlong_enum));
+
+  ULongLongEnum ulonglong_enum;
+  printf("%d", ulonglong_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<long long>(ulonglong_enum));
+}
+
+void printf_enum_u() {
+  PlainEnum plain_enum;
+  printf("%u", plain_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned int>(plain_enum));
+
+  SignedPlainEnum splain_enum;
+  printf("%u", splain_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned int>(splain_enum));
+
+  BoolEnum bool_enum;
+  printf("%u", bool_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned int>(bool_enum));
+
+  CharEnum char_enum;
+  printf("%u", char_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned char>(char_enum));
+
+  SCharEnum schar_enum;
+  printf("%u", schar_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned char>(schar_enum));
+
+  UCharEnum uchar_enum;
+  printf("%u", uchar_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned char>(uchar_enum));
+
+  ShortEnum short_enum;
+  printf("%u", short_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned short>(short_enum));
+
+  UShortEnum ushort_enum;
+  printf("%u", ushort_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned short>(ushort_enum));
+
+  IntEnum int_enum;
+  printf("%u", int_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned int>(int_enum));
+
+  UIntEnum uint_enum;
+  printf("%u", uint_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned int>(uint_enum));
+
+  LongEnum long_enum;
+  printf("%u", long_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned long>(long_enum));
+
+  ULongEnum ulong_enum;
+  printf("%u", ulong_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned long>(ulong_enum));
+
+  LongLongEnum longlong_enum;
+  printf("%u", longlong_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned long long>(longlong_enum));
+
+  ULongLongEnum ulonglong_enum;
+  printf("%u", ulonglong_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned long long>(ulonglong_enum));
+}
+
+void printf_string_function(const char *(*callback)()) {
+  printf("printf string from callback %s", callback());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("printf string from callback {}", callback());
+}
+
+template <typename CharType>
+struct X
+{
+  const CharType *str() const;
+};
+
+void printf_string_member_function(const X<char> &x, const X<const char> &cx) {
+  printf("printf string from member function %s", x.str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("printf string from member function {}", x.str());
+
+  printf("printf string from member function on const %s", cx.str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("printf string from member function on const {}", cx.str());
+}
+
+void printf_string_cstr(const std::string &s1, const std::string &s2) {
+  printf("printf string one c_str %s", s1.c_str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("printf string one c_str {}", s1);
+
+  printf("printf string two c_str %s %s\n", s1.c_str(), s2.data());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("printf string two c_str {} {}", s1, s2);
+}
+
+void printf_not_char_string_cstr(const std::wstring &ws1) {
+  // This test is to check that we only remove
+  // std::basic_string<CharType>::c_str()/data() when CharType is char. I've
+  // been unable to come up with a genuine situation where someone would have
+  // actually successfully called those methods when this isn't the case without
+  // -Wformat warning, but it seems sensible to restrict removal regardless.
+  printf("printf bogus wstring c_str %s", ws1.c_str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("printf bogus wstring c_str {}", ws1.c_str());
+}
+
+void fprintf_string_cstr(const std::string &s1) {
+  fprintf(stderr, "fprintf string c_str %s", s1.c_str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(stderr, "fprintf string c_str {}", s1);
+}
+
+void printf_string_pointer_cstr(const std::string *s1, const std::string *s2) {
+  printf("printf string pointer one c_str %s", s1->c_str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("printf string pointer one c_str {}", *s1);
+
+  printf("printf string pointer two c_str %s %s\n", s1->c_str(), s2->data());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("printf string pointer two c_str {} {}", *s1, *s2);
+}
+
+void fprintf_string_pointer_cstr(const std::string *s1) {
+  fprintf(stderr, "fprintf string pointer c_str %s", s1->c_str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(stderr, "fprintf string pointer c_str {}", *s1);
+}
+
+template <typename T>
+struct iterator {
+  T *operator->();
+  T &operator*();
+};
+
+void printf_iterator_cstr(iterator<std::string> i1, iterator<std::string> i2)
+{
+  printf("printf iterator c_str %s %s\n", i1->c_str(), i2->data());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("printf iterator c_str {} {}", *i1, *i2);
+}
+
+// Something that isn't std::string, so the calls to c_str() and data() must not
+// be removed even though the printf call will be replaced.
+struct S
+{
+  const char *c_str() const;
+  const char *data() const;
+};
+
+void p(S s1, S *s2)
+{
+  printf("Not std::string %s %s", s1.c_str(), s2->c_str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Not std::string {} {}", s1.c_str(), s2->c_str());
+
+  printf("Not std::string %s %s", s1.data(), s2->data());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Not std::string {} {}", s1.data(), s2->data());
+}