108 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//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 16using namespace clang::ast_matchers;17 18namespace clang::tidy::modernize {19 20namespace {21AST_MATCHER(StringLiteral, isOrdinary) { return Node.isOrdinary(); }22} // namespace23 24UseStdFormatCheck::UseStdFormatCheck(StringRef Name, ClangTidyContext *Context)25 : ClangTidyCheck(Name, Context),26 StrictMode(Options.get("StrictMode", false)),27 StrFormatLikeFunctions(utils::options::parseStringList(28 Options.get("StrFormatLikeFunctions", ""))),29 ReplacementFormatFunction(30 Options.get("ReplacementFormatFunction", "std::format")),31 IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",32 utils::IncludeSorter::IS_LLVM),33 areDiagsSelfContained()),34 MaybeHeaderToInclude(Options.get("FormatHeader")) {35 if (StrFormatLikeFunctions.empty())36 StrFormatLikeFunctions.emplace_back("absl::StrFormat");37 38 if (!MaybeHeaderToInclude && ReplacementFormatFunction == "std::format")39 MaybeHeaderToInclude = "<format>";40}41 42void UseStdFormatCheck::registerPPCallbacks(const SourceManager &SM,43 Preprocessor *PP,44 Preprocessor *ModuleExpanderPP) {45 IncludeInserter.registerPreprocessor(PP);46 this->PP = PP;47}48 49void UseStdFormatCheck::registerMatchers(MatchFinder *Finder) {50 Finder->addMatcher(51 callExpr(argumentCountAtLeast(1),52 hasArgument(0, stringLiteral(isOrdinary())),53 callee(functionDecl(matchers::matchesAnyListedName(54 StrFormatLikeFunctions))55 .bind("func_decl")))56 .bind("strformat"),57 this);58}59 60void UseStdFormatCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {61 using utils::options::serializeStringList;62 Options.store(Opts, "StrictMode", StrictMode);63 Options.store(Opts, "StrFormatLikeFunctions",64 serializeStringList(StrFormatLikeFunctions));65 Options.store(Opts, "ReplacementFormatFunction", ReplacementFormatFunction);66 Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());67 if (MaybeHeaderToInclude)68 Options.store(Opts, "FormatHeader", *MaybeHeaderToInclude);69}70 71void UseStdFormatCheck::check(const MatchFinder::MatchResult &Result) {72 const unsigned FormatArgOffset = 0;73 const auto *OldFunction = Result.Nodes.getNodeAs<FunctionDecl>("func_decl");74 const auto *StrFormat = Result.Nodes.getNodeAs<CallExpr>("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(StrFormat->getBeginLoc(),84 "unable to use '%0' instead of %1 because %2")85 << StrFormatCall->getSourceRange() << ReplacementFormatFunction86 << 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, *Result.SourceManager);99 100 if (MaybeHeaderToInclude)101 Diag << IncludeInserter.createIncludeInsertion(102 Result.Context->getSourceManager().getFileID(103 StrFormatCall->getBeginLoc()),104 *MaybeHeaderToInclude);105}106 107} // namespace clang::tidy::modernize108