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