xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.cpp (revision a199fb1229987d0885a4367e3a439db336069156)
1af79372dSMike Crowe //===--- UseStdFormatCheck.cpp - clang-tidy -------------------------------===//
2af79372dSMike Crowe //
3af79372dSMike Crowe // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4af79372dSMike Crowe // See https://llvm.org/LICENSE.txt for license information.
5af79372dSMike Crowe // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6af79372dSMike Crowe //
7af79372dSMike Crowe //===----------------------------------------------------------------------===//
8af79372dSMike Crowe 
9af79372dSMike Crowe #include "UseStdFormatCheck.h"
10af79372dSMike Crowe #include "../utils/FormatStringConverter.h"
11af79372dSMike Crowe #include "../utils/Matchers.h"
12af79372dSMike Crowe #include "../utils/OptionsUtils.h"
13af79372dSMike Crowe #include "clang/ASTMatchers/ASTMatchFinder.h"
14af79372dSMike Crowe #include "clang/Lex/Lexer.h"
15af79372dSMike Crowe #include "clang/Tooling/FixIt.h"
16af79372dSMike Crowe 
17af79372dSMike Crowe using namespace clang::ast_matchers;
18af79372dSMike Crowe 
19af79372dSMike Crowe namespace clang::tidy::modernize {
20af79372dSMike Crowe 
21af79372dSMike Crowe namespace {
22af79372dSMike Crowe AST_MATCHER(StringLiteral, isOrdinary) { return Node.isOrdinary(); }
23af79372dSMike Crowe } // namespace
24af79372dSMike Crowe 
25af79372dSMike Crowe UseStdFormatCheck::UseStdFormatCheck(StringRef Name, ClangTidyContext *Context)
26af79372dSMike Crowe     : ClangTidyCheck(Name, Context),
27af79372dSMike Crowe       StrictMode(Options.getLocalOrGlobal("StrictMode", false)),
28af79372dSMike Crowe       StrFormatLikeFunctions(utils::options::parseStringList(
29af79372dSMike Crowe           Options.get("StrFormatLikeFunctions", ""))),
30af79372dSMike Crowe       ReplacementFormatFunction(
31af79372dSMike Crowe           Options.get("ReplacementFormatFunction", "std::format")),
32af79372dSMike Crowe       IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
33af79372dSMike Crowe                                                utils::IncludeSorter::IS_LLVM),
34af79372dSMike Crowe                       areDiagsSelfContained()),
35af79372dSMike Crowe       MaybeHeaderToInclude(Options.get("FormatHeader")) {
36af79372dSMike Crowe   if (StrFormatLikeFunctions.empty())
37af79372dSMike Crowe     StrFormatLikeFunctions.push_back("absl::StrFormat");
38af79372dSMike Crowe 
39af79372dSMike Crowe   if (!MaybeHeaderToInclude && ReplacementFormatFunction == "std::format")
40af79372dSMike Crowe     MaybeHeaderToInclude = "<format>";
41af79372dSMike Crowe }
42af79372dSMike Crowe 
43af79372dSMike Crowe void UseStdFormatCheck::registerPPCallbacks(const SourceManager &SM,
44af79372dSMike Crowe                                             Preprocessor *PP,
45af79372dSMike Crowe                                             Preprocessor *ModuleExpanderPP) {
46af79372dSMike Crowe   IncludeInserter.registerPreprocessor(PP);
47*a199fb12SMike Crowe   this->PP = PP;
48af79372dSMike Crowe }
49af79372dSMike Crowe 
50af79372dSMike Crowe void UseStdFormatCheck::registerMatchers(MatchFinder *Finder) {
51af79372dSMike Crowe   Finder->addMatcher(
52666d2242SMike Crowe       callExpr(argumentCountAtLeast(1),
53666d2242SMike Crowe                hasArgument(0, stringLiteral(isOrdinary())),
54bbcb6257SMike Crowe                callee(functionDecl(matchers::matchesAnyListedName(
55666d2242SMike Crowe                                        StrFormatLikeFunctions))
56af79372dSMike Crowe                           .bind("func_decl")))
57af79372dSMike Crowe           .bind("strformat"),
58af79372dSMike Crowe       this);
59af79372dSMike Crowe }
60af79372dSMike Crowe 
61af79372dSMike Crowe void UseStdFormatCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
62af79372dSMike Crowe   using utils::options::serializeStringList;
63af79372dSMike Crowe   Options.store(Opts, "StrictMode", StrictMode);
64af79372dSMike Crowe   Options.store(Opts, "StrFormatLikeFunctions",
65af79372dSMike Crowe                 serializeStringList(StrFormatLikeFunctions));
66af79372dSMike Crowe   Options.store(Opts, "ReplacementFormatFunction", ReplacementFormatFunction);
67af79372dSMike Crowe   Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
68af79372dSMike Crowe   if (MaybeHeaderToInclude)
69af79372dSMike Crowe     Options.store(Opts, "FormatHeader", *MaybeHeaderToInclude);
70af79372dSMike Crowe }
71af79372dSMike Crowe 
72af79372dSMike Crowe void UseStdFormatCheck::check(const MatchFinder::MatchResult &Result) {
73af79372dSMike Crowe   const unsigned FormatArgOffset = 0;
74af79372dSMike Crowe   const auto *OldFunction = Result.Nodes.getNodeAs<FunctionDecl>("func_decl");
75af79372dSMike Crowe   const auto *StrFormat = Result.Nodes.getNodeAs<CallExpr>("strformat");
76af79372dSMike Crowe 
77af79372dSMike Crowe   utils::FormatStringConverter::Configuration ConverterConfig;
78af79372dSMike Crowe   ConverterConfig.StrictMode = StrictMode;
79*a199fb12SMike Crowe   utils::FormatStringConverter Converter(
80*a199fb12SMike Crowe       Result.Context, StrFormat, FormatArgOffset, ConverterConfig,
81*a199fb12SMike Crowe       getLangOpts(), *Result.SourceManager, *PP);
82af79372dSMike Crowe   const Expr *StrFormatCall = StrFormat->getCallee();
83af79372dSMike Crowe   if (!Converter.canApply()) {
84af79372dSMike Crowe     diag(StrFormat->getBeginLoc(),
85af79372dSMike Crowe          "unable to use '%0' instead of %1 because %2")
86af79372dSMike Crowe         << StrFormatCall->getSourceRange() << ReplacementFormatFunction
87af79372dSMike Crowe         << OldFunction->getIdentifier()
88af79372dSMike Crowe         << Converter.conversionNotPossibleReason();
89af79372dSMike Crowe     return;
90af79372dSMike Crowe   }
91af79372dSMike Crowe 
92af79372dSMike Crowe   DiagnosticBuilder Diag =
93af79372dSMike Crowe       diag(StrFormatCall->getBeginLoc(), "use '%0' instead of %1")
94af79372dSMike Crowe       << ReplacementFormatFunction << OldFunction->getIdentifier();
95af79372dSMike Crowe   Diag << FixItHint::CreateReplacement(
96bbcb6257SMike Crowe       CharSourceRange::getTokenRange(StrFormatCall->getExprLoc(),
97bbcb6257SMike Crowe                                      StrFormatCall->getEndLoc()),
98af79372dSMike Crowe       ReplacementFormatFunction);
99af79372dSMike Crowe   Converter.applyFixes(Diag, *Result.SourceManager);
100af79372dSMike Crowe 
101af79372dSMike Crowe   if (MaybeHeaderToInclude)
102af79372dSMike Crowe     Diag << IncludeInserter.createIncludeInsertion(
103af79372dSMike Crowe         Result.Context->getSourceManager().getFileID(
104af79372dSMike Crowe             StrFormatCall->getBeginLoc()),
105af79372dSMike Crowe         *MaybeHeaderToInclude);
106af79372dSMike Crowe }
107af79372dSMike Crowe 
108af79372dSMike Crowe } // namespace clang::tidy::modernize
109