aboutsummaryrefslogtreecommitdiff
path: root/clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.cpp
diff options
context:
space:
mode:
authorMike Crowe <mac@mcrowe.com>2024-05-13 19:42:44 +0100
committerGitHub <noreply@github.com>2024-05-13 20:42:44 +0200
commitaf79372d6349cfba6beff26d54b7ad1b798fc4d5 (patch)
tree5403a5d058495cc3f9e1c601591e62d724c1eb66 /clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.cpp
parent69937982dbdd73172ec06580f6f93616edca8e9e (diff)
downloadllvm-af79372d6349cfba6beff26d54b7ad1b798fc4d5.zip
llvm-af79372d6349cfba6beff26d54b7ad1b798fc4d5.tar.gz
llvm-af79372d6349cfba6beff26d54b7ad1b798fc4d5.tar.bz2
[clang-tidy] Add modernize-use-std-format check (#90397)
Add a new clang-tidy check that converts absl::StrFormat (and similar functions) to std::format (and similar functions.) Split the configuration of FormatStringConverter out to a separate Configuration class so that we don't risk confusion by passing two boolean configuration parameters into the constructor. Add AllowTrailingNewlineRemoval option since we never want to remove trailing newlines in this check.
Diffstat (limited to 'clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.cpp')
-rw-r--r--clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.cpp107
1 files changed, 107 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.cpp
new file mode 100644
index 0000000..6cef21f
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.cpp
@@ -0,0 +1,107 @@
+//===--- UseStdFormatCheck.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
+//
+//===----------------------------------------------------------------------===//
+
+#include "UseStdFormatCheck.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 {
+
+namespace {
+AST_MATCHER(StringLiteral, isOrdinary) { return Node.isOrdinary(); }
+} // namespace
+
+UseStdFormatCheck::UseStdFormatCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ StrictMode(Options.getLocalOrGlobal("StrictMode", false)),
+ StrFormatLikeFunctions(utils::options::parseStringList(
+ Options.get("StrFormatLikeFunctions", ""))),
+ ReplacementFormatFunction(
+ Options.get("ReplacementFormatFunction", "std::format")),
+ IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
+ utils::IncludeSorter::IS_LLVM),
+ areDiagsSelfContained()),
+ MaybeHeaderToInclude(Options.get("FormatHeader")) {
+ if (StrFormatLikeFunctions.empty())
+ StrFormatLikeFunctions.push_back("absl::StrFormat");
+
+ if (!MaybeHeaderToInclude && ReplacementFormatFunction == "std::format")
+ MaybeHeaderToInclude = "<format>";
+}
+
+void UseStdFormatCheck::registerPPCallbacks(const SourceManager &SM,
+ Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) {
+ IncludeInserter.registerPreprocessor(PP);
+}
+
+void UseStdFormatCheck::registerMatchers(MatchFinder *Finder) {
+ Finder->addMatcher(
+ callExpr(argumentCountAtLeast(1),
+ hasArgument(0, stringLiteral(isOrdinary())),
+ callee(functionDecl(unless(cxxMethodDecl()),
+ matchers::matchesAnyListedName(
+ StrFormatLikeFunctions))
+ .bind("func_decl")))
+ .bind("strformat"),
+ this);
+}
+
+void UseStdFormatCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ using utils::options::serializeStringList;
+ Options.store(Opts, "StrictMode", StrictMode);
+ Options.store(Opts, "StrFormatLikeFunctions",
+ serializeStringList(StrFormatLikeFunctions));
+ Options.store(Opts, "ReplacementFormatFunction", ReplacementFormatFunction);
+ Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
+ if (MaybeHeaderToInclude)
+ Options.store(Opts, "FormatHeader", *MaybeHeaderToInclude);
+}
+
+void UseStdFormatCheck::check(const MatchFinder::MatchResult &Result) {
+ const unsigned FormatArgOffset = 0;
+ const auto *OldFunction = Result.Nodes.getNodeAs<FunctionDecl>("func_decl");
+ const auto *StrFormat = Result.Nodes.getNodeAs<CallExpr>("strformat");
+
+ utils::FormatStringConverter::Configuration ConverterConfig;
+ ConverterConfig.StrictMode = StrictMode;
+ utils::FormatStringConverter Converter(Result.Context, StrFormat,
+ FormatArgOffset, ConverterConfig,
+ getLangOpts());
+ const Expr *StrFormatCall = StrFormat->getCallee();
+ if (!Converter.canApply()) {
+ diag(StrFormat->getBeginLoc(),
+ "unable to use '%0' instead of %1 because %2")
+ << StrFormatCall->getSourceRange() << ReplacementFormatFunction
+ << OldFunction->getIdentifier()
+ << Converter.conversionNotPossibleReason();
+ return;
+ }
+
+ DiagnosticBuilder Diag =
+ diag(StrFormatCall->getBeginLoc(), "use '%0' instead of %1")
+ << ReplacementFormatFunction << OldFunction->getIdentifier();
+ Diag << FixItHint::CreateReplacement(
+ CharSourceRange::getTokenRange(StrFormatCall->getSourceRange()),
+ ReplacementFormatFunction);
+ Converter.applyFixes(Diag, *Result.SourceManager);
+
+ if (MaybeHeaderToInclude)
+ Diag << IncludeInserter.createIncludeInsertion(
+ Result.Context->getSourceManager().getFileID(
+ StrFormatCall->getBeginLoc()),
+ *MaybeHeaderToInclude);
+}
+
+} // namespace clang::tidy::modernize