| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| //===--- 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 = "<format>"; | ||
| } | ||
|
|
||
| void UseStdFormatCheck::registerPPCallbacks(const SourceManager &SM, | ||
| Preprocessor *PP, | ||
| Preprocessor *ModuleExpanderPP) { | ||
| IncludeInserter.registerPreprocessor(PP); | ||
| } | ||
|
|
||
| void UseStdFormatCheck::registerMatchers(MatchFinder *Finder) { | ||
| Finder->addMatcher( | ||
| callExpr(argumentCountAtLeast(1), | ||
| hasArgument(0, stringLiteral(isOrdinary())), | ||
| callee(functionDecl(unless(cxxMethodDecl()), | ||
| 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<FunctionDecl>("func_decl"); | ||
| const auto *StrFormat = Result.Nodes.getNodeAs<CallExpr>("strformat"); | ||
|
|
||
| utils::FormatStringConverter::Configuration ConverterConfig; | ||
| ConverterConfig.StrictMode = StrictMode; | ||
| utils::FormatStringConverter Converter(Result.Context, StrFormat, | ||
| FormatArgOffset, ConverterConfig, | ||
| getLangOpts()); | ||
| 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->getSourceRange()), | ||
| ReplacementFormatFunction); | ||
| Converter.applyFixes(Diag, *Result.SourceManager); | ||
|
|
||
| if (MaybeHeaderToInclude) | ||
| Diag << IncludeInserter.createIncludeInsertion( | ||
| Result.Context->getSourceManager().getFileID( | ||
| StrFormatCall->getBeginLoc()), | ||
| *MaybeHeaderToInclude); | ||
| } | ||
|
|
||
| } // namespace clang::tidy::modernize |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| //===--- UseStdFormatCheck.h - clang-tidy -----------------------*- C++ -*-===// | ||
| // | ||
| // 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDFORMATCHECK_H | ||
| #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDFORMATCHECK_H | ||
|
|
||
| #include "../ClangTidyCheck.h" | ||
| #include "../utils/IncludeInserter.h" | ||
|
|
||
| namespace clang::tidy::modernize { | ||
|
|
||
| /// Converts calls to absl::StrFormat, or other functions via configuration | ||
| /// options, to C++20's std::format, or another function via a configuration | ||
| /// option, modifying the format string appropriately and removing | ||
| /// now-unnecessary calls to std::string::c_str() and std::string::data(). | ||
| /// | ||
| /// For the user-facing documentation see: | ||
| /// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-format.html | ||
| class UseStdFormatCheck : public ClangTidyCheck { | ||
| public: | ||
| UseStdFormatCheck(StringRef Name, ClangTidyContext *Context); | ||
| bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { | ||
| if (ReplacementFormatFunction == "std::format") | ||
| return LangOpts.CPlusPlus20; | ||
| return LangOpts.CPlusPlus; | ||
| } | ||
| void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, | ||
| Preprocessor *ModuleExpanderPP) override; | ||
| void storeOptions(ClangTidyOptions::OptionMap &Opts) override; | ||
| void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
| void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
| std::optional<TraversalKind> getCheckTraversalKind() const override { | ||
| return TK_IgnoreUnlessSpelledInSource; | ||
| } | ||
|
|
||
| private: | ||
| bool StrictMode; | ||
| std::vector<StringRef> StrFormatLikeFunctions; | ||
| StringRef ReplacementFormatFunction; | ||
| utils::IncludeInserter IncludeInserter; | ||
| std::optional<StringRef> MaybeHeaderToInclude; | ||
| }; | ||
|
|
||
| } // namespace clang::tidy::modernize | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDFORMATCHECK_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| .. title:: clang-tidy - modernize-use-std-format | ||
|
|
||
| modernize-use-std-format | ||
| ======================== | ||
|
|
||
| Converts calls to ``absl::StrFormat``, or other functions via | ||
| configuration options, to C++20's ``std::format``, or another function | ||
| via a configuration option, modifying the format string appropriately and | ||
| removing now-unnecessary calls to ``std::string::c_str()`` and | ||
| ``std::string::data()``. | ||
|
|
||
| For example, it turns lines like | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| return absl::StrFormat("The %s is %3d", description.c_str(), value); | ||
|
|
||
| into: | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| return std::format("The {} is {:3}", description, value); | ||
|
|
||
| The check uses the same format-string-conversion algorithm as | ||
| `modernize-use-std-print <../modernize/use-std-print.html>`_ and its | ||
| shortcomings are described in the documentation for that check. | ||
|
|
||
| Options | ||
| ------- | ||
|
|
||
| .. option:: StrictMode | ||
|
|
||
| When `true`, the check will add casts when converting from variadic | ||
| functions and printing signed or unsigned integer types (including | ||
| fixed-width integer types from ``<cstdint>``, ``ptrdiff_t``, ``size_t`` | ||
| and ``ssize_t``) as the opposite signedness to ensure that the output | ||
| would matches that of a simple wrapper for ``std::sprintf`` that | ||
| accepted a C-style variable argument list. For example, with | ||
| `StrictMode` enabled, | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| extern std::string strprintf(const char *format, ...); | ||
| int i = -42; | ||
| unsigned int u = 0xffffffff; | ||
| return strprintf("%d %u\n", i, u); | ||
| would be converted to | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| return std::format("{} {}\n", static_cast<unsigned int>(i), static_cast<int>(u)); | ||
|
|
||
| to ensure that the output will continue to be the unsigned representation | ||
| of -42 and the signed representation of 0xffffffff (often 4294967254 | ||
| and -1 respectively). When `false` (which is the default), these casts | ||
| will not be added which may cause a change in the output. Note that this | ||
| option makes no difference for the default value of | ||
| `StrFormatLikeFunctions` since ``absl::StrFormat`` takes a function | ||
| parameter pack and is not a variadic function. | ||
|
|
||
| .. option:: StrFormatLikeFunctions | ||
|
|
||
| A semicolon-separated list of (fully qualified) function names to | ||
| replace, with the requirement that the first parameter contains the | ||
| printf-style format string and the arguments to be formatted follow | ||
| immediately afterwards. The default value for this option is | ||
| `absl::StrFormat`. | ||
|
|
||
| .. option:: ReplacementFormatFunction | ||
|
|
||
| The function that will be used to replace the function set by the | ||
| `StrFormatLikeFunctions` option rather than the default | ||
| `std::format`. It is expected that the function provides an interface | ||
| that is compatible with ``std::format``. A suitable candidate would be | ||
| `fmt::format`. | ||
|
|
||
| .. option:: FormatHeader | ||
|
|
||
| The header that must be included for the declaration of | ||
| `ReplacementFormatFunction` so that a ``#include`` directive can be added if | ||
| required. If `ReplacementFormatFunction` is `std::format` then this option will | ||
| default to ``<format>``, otherwise this option will default to nothing | ||
| and no ``#include`` directive will be added. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| # This file intentionally has no queries |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| f DIRECTORY/runtime_file.script |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| set bind-root false | ||
|
|
||
| l func functionDecl(hasName("bar")) | ||
| m func.bind("f") | ||
| m varDecl().bind("v") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,12 @@ | ||
| // RUN: not clang-query -c foo -c bar %s -- | FileCheck %s | ||
| // RUN: not clang-query -f %S/Inputs/foo.script %s -- | FileCheck %s | ||
| // RUN: not clang-query -f %S/Inputs/nonexistent.script %s -- 2>&1 | FileCheck --check-prefix=CHECK-NONEXISTENT %s | ||
| // RUN: not clang-query -c 'file %S/Inputs/nonexistent.script' %s -- 2>&1 | FileCheck --check-prefix=CHECK-NONEXISTENT-FILEQUERY %s | ||
| // RUN: not clang-query -c foo -f foo %s -- 2>&1 | FileCheck --check-prefix=CHECK-BOTH %s | ||
|
|
||
| // CHECK: unknown command: foo | ||
| // CHECK-NOT: unknown command: bar | ||
|
|
||
| // CHECK-NONEXISTENT: cannot open {{.*}}nonexistent.script | ||
| // CHECK-NONEXISTENT-FILEQUERY: cannot open {{.*}}nonexistent.script | ||
| // CHECK-BOTH: cannot specify both -c and -f |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| // RUN: clang-query -c 'file %S/Inputs/empty.script' %s -- | ||
| // COM: no output expected; nothing to CHECK |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| // RUN: rm -rf %/t | ||
| // RUN: mkdir %/t | ||
| // RUN: cp %/S/Inputs/file.script %/t/file.script | ||
| // RUN: cp %/S/Inputs/runtime_file.script %/t/runtime_file.script | ||
| // Need to embed the correct temp path in the actual JSON-RPC requests. | ||
| // RUN: sed -e "s|DIRECTORY|%/t|" %/t/file.script > %/t/file.script.temp | ||
|
|
||
| // RUN: clang-query -c 'file %/t/file.script.temp' %s -- | FileCheck %s | ||
|
|
||
| // CHECK: file-query.c:11:1: note: "f" binds here | ||
| void bar(void) {} | ||
|
|
||
| // CHECK: file-query.c:14:1: note: "v" binds here | ||
| int baz{1}; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| // RUN: %check_clang_tidy -check-suffixes=,STRICT \ | ||
| // RUN: -std=c++20 %s modernize-use-std-format %t -- \ | ||
| // RUN: -config="{CheckOptions: { \ | ||
| // RUN: modernize-use-std-format.StrictMode: true, \ | ||
| // RUN: modernize-use-std-format.StrFormatLikeFunctions: '::strprintf; mynamespace::strprintf2', \ | ||
| // RUN: modernize-use-std-format.ReplacementFormatFunction: 'fmt::format', \ | ||
| // RUN: modernize-use-std-format.FormatHeader: '<fmt/core.h>' \ | ||
| // RUN: }}" \ | ||
| // RUN: -- -isystem %clang_tidy_headers | ||
| // RUN: %check_clang_tidy -check-suffixes=,NOTSTRICT \ | ||
| // RUN: -std=c++20 %s modernize-use-std-format %t -- \ | ||
| // RUN: -config="{CheckOptions: { \ | ||
| // RUN: modernize-use-std-format.StrFormatLikeFunctions: '::strprintf; mynamespace::strprintf2', \ | ||
| // RUN: modernize-use-std-format.ReplacementFormatFunction: 'fmt::format', \ | ||
| // RUN: modernize-use-std-format.FormatHeader: '<fmt/core.h>' \ | ||
| // RUN: }}" \ | ||
| // RUN: -- -isystem %clang_tidy_headers | ||
|
|
||
| #include <cstdio> | ||
| #include <string> | ||
| // CHECK-FIXES: #include <fmt/core.h> | ||
|
|
||
| std::string strprintf(const char *, ...); | ||
|
|
||
| namespace mynamespace { | ||
| std::string strprintf2(const char *, ...); | ||
| } | ||
|
|
||
| std::string strprintf_test(const std::string &name, double value) { | ||
| return strprintf("'%s'='%f'\n", name.c_str(), value); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:10: warning: use 'fmt::format' instead of 'strprintf' [modernize-use-std-format] | ||
| // CHECK-FIXES: return fmt::format("'{}'='{:f}'\n", name, value); | ||
|
|
||
| return mynamespace::strprintf2("'%s'='%f'\n", name.c_str(), value); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:10: warning: use 'fmt::format' instead of 'strprintf2' [modernize-use-std-format] | ||
| // CHECK-FIXES: return fmt::format("'{}'='{:f}'\n", name, value); | ||
| } | ||
|
|
||
| std::string StrFormat_strict_conversion() { | ||
| const unsigned char uc = 'A'; | ||
| return strprintf("Integer %hhd from unsigned char\n", uc); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:10: warning: use 'fmt::format' instead of 'strprintf' [modernize-use-std-format] | ||
| // CHECK-FIXES-NOTSTRICT: return fmt::format("Integer {} from unsigned char\n", uc); | ||
| // CHECK-FIXES-STRICT: return fmt::format("Integer {} from unsigned char\n", static_cast<signed char>(uc)); | ||
| } | ||
|
|
||
| // Ensure that MatchesAnyListedNameMatcher::NameMatcher::match() can cope with a | ||
| // NamedDecl that has no name when we're trying to match unqualified_strprintf. | ||
| std::string A(const std::string &in) | ||
| { | ||
| return "_" + in; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // RUN: %check_clang_tidy %s modernize-use-std-format %t -- \ | ||
| // RUN: -config="{CheckOptions: { \ | ||
| // RUN: StrictMode: true, \ | ||
| // RUN: modernize-use-std-format.StrFormatLikeFunctions: 'fmt::sprintf', \ | ||
| // RUN: modernize-use-std-format.ReplacementFormatFunction: 'fmt::format', \ | ||
| // RUN: modernize-use-std-format.FormatHeader: '<fmt/core.h>' \ | ||
| // RUN: }}" \ | ||
| // RUN: -- -isystem %clang_tidy_headers | ||
|
|
||
| // CHECK-FIXES: #include <fmt/core.h> | ||
| #include <string> | ||
|
|
||
| namespace fmt | ||
| { | ||
| // Use const char * for the format since the real type is hard to mock up. | ||
| template <typename... Args> | ||
| std::string sprintf(const char *format, const Args&... args); | ||
| } // namespace fmt | ||
|
|
||
| std::string fmt_sprintf_simple() { | ||
| return fmt::sprintf("Hello %s %d", "world", 42); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:10: warning: use 'fmt::format' instead of 'sprintf' [modernize-use-std-format] | ||
| // CHECK-FIXES: fmt::format("Hello {} {}", "world", 42); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| // RUN: %check_clang_tidy \ | ||
| // RUN: -std=c++20 %s modernize-use-std-format %t -- \ | ||
| // RUN: -config="{CheckOptions: {StrictMode: true}}" \ | ||
| // RUN: -- -isystem %clang_tidy_headers | ||
| // RUN: %check_clang_tidy \ | ||
| // RUN: -std=c++20 %s modernize-use-std-format %t -- \ | ||
| // RUN: -config="{CheckOptions: {StrictMode: false}}" \ | ||
| // RUN: -- -isystem %clang_tidy_headers | ||
| #include <string> | ||
| // CHECK-FIXES: #include <format> | ||
|
|
||
| namespace absl | ||
| { | ||
| // Use const char * for the format since the real type is hard to mock up. | ||
| template <typename... Args> | ||
| std::string StrFormat(const char *format, const Args&... args); | ||
| } // namespace absl | ||
|
|
||
| template <typename T> | ||
| struct iterator { | ||
| T *operator->(); | ||
| T &operator*(); | ||
| }; | ||
|
|
||
| std::string StrFormat_simple() { | ||
| return absl::StrFormat("Hello"); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:10: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format] | ||
| // CHECK-FIXES: return std::format("Hello"); | ||
| } | ||
|
|
||
| std::string StrFormat_complex(const char *name, double value) { | ||
| return absl::StrFormat("'%s'='%f'", name, value); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:10: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format] | ||
| // CHECK-FIXES: return std::format("'{}'='{:f}'", name, value); | ||
| } | ||
|
|
||
| std::string StrFormat_integer_conversions() { | ||
| return absl::StrFormat("int:%d int:%d char:%c char:%c", 65, 'A', 66, 'B'); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:10: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format] | ||
| // CHECK-FIXES: return std::format("int:{} int:{:d} char:{:c} char:{}", 65, 'A', 66, 'B'); | ||
| } | ||
|
|
||
| // FormatConverter is capable of removing newlines from the end of the format | ||
| // string. Ensure that isn't incorrectly happening for std::format. | ||
| std::string StrFormat_no_newline_removal() { | ||
| return absl::StrFormat("a line\n"); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:10: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format] | ||
| // CHECK-FIXES: return std::format("a line\n"); | ||
| } | ||
|
|
||
| // FormatConverter is capable of removing newlines from the end of the format | ||
| // string. Ensure that isn't incorrectly happening for std::format. | ||
| std::string StrFormat_cstr_removal(const std::string &s1, const std::string *s2) { | ||
| return absl::StrFormat("%s %s %s %s", s1.c_str(), s1.data(), s2->c_str(), s2->data()); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:10: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format] | ||
| // CHECK-FIXES: return std::format("{} {} {} {}", s1, s1, *s2, *s2); | ||
| } | ||
|
|
||
| std::string StrFormat_strict_conversion() { | ||
| const unsigned char uc = 'A'; | ||
| return absl::StrFormat("Integer %hhd from unsigned char\n", uc); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:10: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format] | ||
| // CHECK-FIXES: return std::format("Integer {} from unsigned char\n", uc); | ||
| } | ||
|
|
||
| std::string StrFormat_field_width_and_precision() { | ||
| auto s1 = absl::StrFormat("width only:%*d width and precision:%*.*f precision only:%.*f", 3, 42, 4, 2, 3.14159265358979323846, 5, 2.718); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format] | ||
| // CHECK-FIXES: std::format("width only:{:{}} width and precision:{:{}.{}f} precision only:{:.{}f}", 42, 3, 3.14159265358979323846, 4, 2, 2.718, 5); | ||
|
|
||
| auto s2 = absl::StrFormat("width and precision positional:%1$*2$.*3$f after", 3.14159265358979323846, 4, 2); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format] | ||
| // CHECK-FIXES: std::format("width and precision positional:{0:{1}.{2}f} after", 3.14159265358979323846, 4, 2); | ||
|
|
||
| const int width = 10, precision = 3; | ||
| const unsigned int ui1 = 42, ui2 = 43, ui3 = 44; | ||
| auto s3 = absl::StrFormat("casts width only:%*d width and precision:%*.*d precision only:%.*d\n", 3, ui1, 4, 2, ui2, 5, ui3); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format] | ||
| // CHECK-FIXES-NOTSTRICT: std::format("casts width only:{:{}} width and precision:{:{}.{}} precision only:{:.{}}", ui1, 3, ui2, 4, 2, ui3, 5); | ||
| // CHECK-FIXES-STRICT: std::format("casts width only:{:{}} width and precision:{:{}.{}} precision only:{:.{}}", static_cast<int>(ui1), 3, static_cast<int>(ui2), 4, 2, static_cast<int>(ui3), 5); | ||
|
|
||
| auto s4 = absl::StrFormat("c_str removal width only:%*s width and precision:%*.*s precision only:%.*s", 3, s1.c_str(), 4, 2, s2.c_str(), 5, s3.c_str()); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format] | ||
| // CHECK-FIXES: std::format("c_str removal width only:{:>{}} width and precision:{:>{}.{}} precision only:{:.{}}", s1, 3, s2, 4, 2, s3, 5); | ||
|
|
||
| const std::string *ps1 = &s1, *ps2 = &s2, *ps3 = &s3; | ||
| auto s5 = absl::StrFormat("c_str() removal pointer width only:%-*s width and precision:%-*.*s precision only:%-.*s", 3, ps1->c_str(), 4, 2, ps2->c_str(), 5, ps3->c_str()); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format] | ||
| // CHECK-FIXES: std::format("c_str() removal pointer width only:{:{}} width and precision:{:{}.{}} precision only:{:.{}}", *ps1, 3, *ps2, 4, 2, *ps3, 5); | ||
|
|
||
| iterator<std::string> is1, is2, is3; | ||
| auto s6 = absl::StrFormat("c_str() removal iterator width only:%-*s width and precision:%-*.*s precision only:%-.*s", 3, is1->c_str(), 4, 2, is2->c_str(), 5, is3->c_str()); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format] | ||
| // CHECK-FIXES: std::format("c_str() removal iterator width only:{:{}} width and precision:{:{}.{}} precision only:{:.{}}", *is1, 3, *is2, 4, 2, *is3, 5); | ||
|
|
||
| return s1 + s2 + s3 + s4 + s5 + s6; | ||
| } | ||
|
|
||
| std::string StrFormat_macros() { | ||
| // The function call is replaced even though it comes from a macro. | ||
| #define FORMAT absl::StrFormat | ||
| auto s1 = FORMAT("Hello %d", 42); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format] | ||
| // CHECK-FIXES: std::format("Hello {}", 42); | ||
|
|
||
| // The format string is replaced even though it comes from a macro, this | ||
| // behaviour is required so that that <inttypes.h> macros are replaced. | ||
| #define FORMAT_STRING "Hello %s" | ||
| auto s2 = absl::StrFormat(FORMAT_STRING, 42); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format] | ||
| // CHECK-FIXES: std::format("Hello {}", 42); | ||
|
|
||
| // Arguments that are macros aren't replaced with their value, even if they are rearranged. | ||
| #define VALUE 3.14159265358979323846 | ||
| #define WIDTH 10 | ||
| #define PRECISION 4 | ||
| auto s3 = absl::StrFormat("Hello %*.*f", WIDTH, PRECISION, VALUE); | ||
| // CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format] | ||
| // CHECK-FIXES: std::format("Hello {:{}.{}f}", VALUE, WIDTH, PRECISION); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| // RUN: %check_clang_tidy -std=c++23 %s readability-else-after-return %t | ||
|
|
||
| // Consteval if is an exception to the rule, we cannot remove the else. | ||
| void f() { | ||
| if (sizeof(int) > 4) { | ||
| return; | ||
| } else { | ||
| return; | ||
| } | ||
| // CHECK-MESSAGES: [[@LINE-3]]:5: warning: do not use 'else' after 'return' | ||
|
|
||
| if consteval { | ||
| return; | ||
| } else { | ||
| return; | ||
| } | ||
| } |