1 | //===--- UseStdFormatCheck.cpp - clang-tidy -------------------------------===// |
2 | // |
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 |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
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 | |
16 | using namespace clang::ast_matchers; |
17 | |
18 | namespace clang::tidy::modernize { |
19 | |
20 | namespace { |
21 | AST_MATCHER(StringLiteral, isOrdinary) { return Node.isOrdinary(); } |
22 | } // namespace |
23 | |
24 | UseStdFormatCheck::UseStdFormatCheck(StringRef Name, ClangTidyContext *Context) |
25 | : ClangTidyCheck(Name, Context), |
26 | StrictMode(Options.getLocalOrGlobal(LocalName: "StrictMode" , Default: false)), |
27 | StrFormatLikeFunctions(utils::options::parseStringList( |
28 | Option: Options.get(LocalName: "StrFormatLikeFunctions" , Default: "" ))), |
29 | ReplacementFormatFunction( |
30 | Options.get(LocalName: "ReplacementFormatFunction" , Default: "std::format" )), |
31 | IncludeInserter(Options.getLocalOrGlobal(LocalName: "IncludeStyle" , |
32 | Default: utils::IncludeSorter::IS_LLVM), |
33 | areDiagsSelfContained()), |
34 | MaybeHeaderToInclude(Options.get(LocalName: "FormatHeader" )) { |
35 | if (StrFormatLikeFunctions.empty()) |
36 | StrFormatLikeFunctions.push_back(x: "absl::StrFormat" ); |
37 | |
38 | if (!MaybeHeaderToInclude && ReplacementFormatFunction == "std::format" ) |
39 | MaybeHeaderToInclude = "<format>" ; |
40 | } |
41 | |
42 | void UseStdFormatCheck::registerPPCallbacks(const SourceManager &SM, |
43 | Preprocessor *PP, |
44 | Preprocessor *ModuleExpanderPP) { |
45 | IncludeInserter.registerPreprocessor(PP); |
46 | this->PP = PP; |
47 | } |
48 | |
49 | void UseStdFormatCheck::registerMatchers(MatchFinder *Finder) { |
50 | Finder->addMatcher( |
51 | NodeMatch: callExpr(argumentCountAtLeast(N: 1), |
52 | hasArgument(N: 0, InnerMatcher: stringLiteral(isOrdinary())), |
53 | callee(InnerMatcher: functionDecl(matchers::matchesAnyListedName( |
54 | NameList: StrFormatLikeFunctions)) |
55 | .bind(ID: "func_decl" ))) |
56 | .bind(ID: "strformat" ), |
57 | Action: this); |
58 | } |
59 | |
60 | void UseStdFormatCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
61 | using utils::options::serializeStringList; |
62 | Options.store(Options&: Opts, LocalName: "StrictMode" , Value: StrictMode); |
63 | Options.store(Options&: Opts, LocalName: "StrFormatLikeFunctions" , |
64 | Value: serializeStringList(Strings: StrFormatLikeFunctions)); |
65 | Options.store(Options&: Opts, LocalName: "ReplacementFormatFunction" , Value: ReplacementFormatFunction); |
66 | Options.store(Options&: Opts, LocalName: "IncludeStyle" , Value: IncludeInserter.getStyle()); |
67 | if (MaybeHeaderToInclude) |
68 | Options.store(Options&: Opts, LocalName: "FormatHeader" , Value: *MaybeHeaderToInclude); |
69 | } |
70 | |
71 | void UseStdFormatCheck::check(const MatchFinder::MatchResult &Result) { |
72 | const unsigned FormatArgOffset = 0; |
73 | const auto *OldFunction = Result.Nodes.getNodeAs<FunctionDecl>(ID: "func_decl" ); |
74 | const auto *StrFormat = Result.Nodes.getNodeAs<CallExpr>(ID: "strformat" ); |
75 | |
76 | utils::FormatStringConverter::Configuration ConverterConfig; |
77 | ConverterConfig.StrictMode = StrictMode; |
78 | utils::FormatStringConverter Converter( |
79 | Result.Context, StrFormat, FormatArgOffset, ConverterConfig, |
80 | getLangOpts(), *Result.SourceManager, *PP); |
81 | const Expr *StrFormatCall = StrFormat->getCallee(); |
82 | if (!Converter.canApply()) { |
83 | diag(Loc: StrFormat->getBeginLoc(), |
84 | Description: "unable to use '%0' instead of %1 because %2" ) |
85 | << StrFormatCall->getSourceRange() << ReplacementFormatFunction |
86 | << OldFunction->getIdentifier() |
87 | << Converter.conversionNotPossibleReason(); |
88 | return; |
89 | } |
90 | |
91 | DiagnosticBuilder Diag = |
92 | diag(StrFormatCall->getBeginLoc(), "use '%0' instead of %1" ) |
93 | << ReplacementFormatFunction << OldFunction->getIdentifier(); |
94 | Diag << FixItHint::CreateReplacement( |
95 | CharSourceRange::getTokenRange(StrFormatCall->getExprLoc(), |
96 | StrFormatCall->getEndLoc()), |
97 | ReplacementFormatFunction); |
98 | Converter.applyFixes(Diag, SM&: *Result.SourceManager); |
99 | |
100 | if (MaybeHeaderToInclude) |
101 | Diag << IncludeInserter.createIncludeInsertion( |
102 | FileID: Result.Context->getSourceManager().getFileID( |
103 | StrFormatCall->getBeginLoc()), |
104 | Header: *MaybeHeaderToInclude); |
105 | } |
106 | |
107 | } // namespace clang::tidy::modernize |
108 | |