//===--- 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 = ""; } void UseStdFormatCheck::registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { IncludeInserter.registerPreprocessor(PP); this->PP = PP; } void UseStdFormatCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( callExpr(argumentCountAtLeast(1), hasArgument(0, stringLiteral(isOrdinary())), callee(functionDecl(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("func_decl"); const auto *StrFormat = Result.Nodes.getNodeAs("strformat"); utils::FormatStringConverter::Configuration ConverterConfig; ConverterConfig.StrictMode = StrictMode; utils::FormatStringConverter Converter( Result.Context, StrFormat, FormatArgOffset, ConverterConfig, getLangOpts(), *Result.SourceManager, *PP); 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->getExprLoc(), StrFormatCall->getEndLoc()), ReplacementFormatFunction); Converter.applyFixes(Diag, *Result.SourceManager); if (MaybeHeaderToInclude) Diag << IncludeInserter.createIncludeInsertion( Result.Context->getSourceManager().getFileID( StrFormatCall->getBeginLoc()), *MaybeHeaderToInclude); } } // namespace clang::tidy::modernize