Index: clang-tools-extra/clang-tidy/modernize/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -36,6 +36,7 @@ UseNoexceptCheck.cpp UseNullptrCheck.cpp UseOverrideCheck.cpp + UsePragmaOnceCheck.cpp UseTrailingReturnTypeCheck.cpp UseTransparentFunctorsCheck.cpp UseUncaughtExceptionsCheck.cpp Index: clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp =================================================================== --- clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -37,6 +37,7 @@ #include "UseNoexceptCheck.h" #include "UseNullptrCheck.h" #include "UseOverrideCheck.h" +#include "UsePragmaOnceCheck.h" #include "UseTrailingReturnTypeCheck.h" #include "UseTransparentFunctorsCheck.h" #include "UseUncaughtExceptionsCheck.h" @@ -92,6 +93,8 @@ CheckFactories.registerCheck("modernize-use-noexcept"); CheckFactories.registerCheck("modernize-use-nullptr"); CheckFactories.registerCheck("modernize-use-override"); + CheckFactories.registerCheck( + "modernize-use-pragma-once"); CheckFactories.registerCheck( "modernize-use-trailing-return-type"); CheckFactories.registerCheck( Index: clang-tools-extra/clang-tidy/modernize/UsePragmaOnceCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/modernize/UsePragmaOnceCheck.h @@ -0,0 +1,47 @@ +//===--- UsePragmaOnceCheck.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_USEPRAGMAONCECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEPRAGMAONCECHECK_H + +#include "../utils/HeaderGuardBase.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Check that suggests the use of #pragma once. +/// The check supports these options: +/// - `HeaderFileExtensions`: a semicolon-separated list of filename +/// extensions of header files (The filename extension should not contain +/// "." prefix). ";h;hh;hpp;hxx" by default. +/// +/// For extension-less header files, using an empty string or leaving an +/// empty string between ";" if there are other filename extensions. +class UsePragmaOnceCheck : public utils::HeaderGuardBase { +public: + UsePragmaOnceCheck(StringRef Name, ClangTidyContext *Context) + : HeaderGuardBase(Name, Context) {} + + void onHeaderGuard(Preprocessor *PP, StringRef FileName, const FileEntry *FE, + SourceLocation IfndefHash, SourceLocation Ifndef, + SourceLocation IfndefToken, SourceLocation DefineHash, + const Token &Define, SourceLocation EndIfHash, + SourceLocation EndIf) override; + + void onGuardlessHeader( + Preprocessor *PP, StringRef FileName, const FileEntry *FE, + SourceLocation StartLoc, + const std::vector> + &Macros) override; +}; +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEPRAGMAONCECHECK_H Index: clang-tools-extra/clang-tidy/modernize/UsePragmaOnceCheck.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/modernize/UsePragmaOnceCheck.cpp @@ -0,0 +1,72 @@ +//===--- UsePragmaOnceCheck.cpp - 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 +// +//===----------------------------------------------------------------------===// + +#include "UsePragmaOnceCheck.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang::tidy::modernize { +namespace { +CharSourceRange goToLineEnd(Preprocessor *PP, SourceLocation StartLoc, + SourceLocation EndLoc) { + SourceManager &SM = PP->getSourceManager(); + FileID FID = SM.getFileID(EndLoc); + StringRef BufData = SM.getBufferData(FID); + const char *EndData = BufData.begin() + SM.getFileOffset(EndLoc); + Lexer Lex(EndLoc, PP->getLangOpts(), BufData.begin(), EndData, BufData.end()); + // FIXME: this is a bit hacky to get ReadToEndOfLine work. + Lex.setParsingPreprocessorDirective(true); + Lex.ReadToEndOfLine(); + return CharSourceRange::getCharRange( + StartLoc, SM.getLocForStartOfFile(FID).getLocWithOffset( + Lex.getCurrentBufferOffset())); +} +} // namespace + +void UsePragmaOnceCheck::onHeaderGuard( + Preprocessor *PP, StringRef FileName, const FileEntry *FE, + SourceLocation IfndefHash, SourceLocation Ifndef, + SourceLocation IfndefToken, SourceLocation DefineHash, const Token &Define, + SourceLocation EndIfHash, SourceLocation EndIf) { + std::vector FixIts; + + HeaderSearch &HeaderInfo = PP->getHeaderSearchInfo(); + + HeaderFileInfo &Info = HeaderInfo.getFileInfo(FE); + + CharSourceRange IfndefSrcRange = goToLineEnd(PP, IfndefHash, IfndefToken); + CharSourceRange DefineSrcRange = + goToLineEnd(PP, DefineHash, Define.getLocation()); + CharSourceRange EndifSrcRange = goToLineEnd(PP, EndIfHash, EndIf); + + if (Info.isPragmaOnce) + FixIts.push_back(FixItHint::CreateRemoval(IfndefSrcRange)); + else + FixIts.push_back( + FixItHint::CreateReplacement(IfndefSrcRange, "#pragma once\n")); + + FixIts.push_back(FixItHint::CreateRemoval(DefineSrcRange)); + FixIts.push_back(FixItHint::CreateRemoval(EndifSrcRange)); + + diag(IfndefSrcRange.getBegin(), "use #pragma once") << FixIts; +} + +void UsePragmaOnceCheck::onGuardlessHeader( + Preprocessor *PP, StringRef FileName, const FileEntry *FE, + SourceLocation StartLoc, + const std::vector> + &Macros) { + HeaderSearch &HeaderInfo = PP->getHeaderSearchInfo(); + + HeaderFileInfo &Info = HeaderInfo.getFileInfo(FE); + if (Info.isPragmaOnce) + return; + + diag(StartLoc, "use #pragma once") + << FixItHint::CreateInsertion(StartLoc, "#pragma once\n"); +} +} // namespace clang::tidy::modernize Index: clang-tools-extra/docs/clang-tidy/checks/list.rst =================================================================== --- clang-tools-extra/docs/clang-tidy/checks/list.rst +++ clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -290,6 +290,7 @@ `modernize-use-noexcept `_, "Yes" `modernize-use-nullptr `_, "Yes" `modernize-use-override `_, "Yes" + `modernize-use-pragma-once `_, "Yes" `modernize-use-trailing-return-type `_, "Yes" `modernize-use-transparent-functors `_, "Yes" `modernize-use-uncaught-exceptions `_, "Yes" Index: clang-tools-extra/docs/clang-tidy/checks/modernize/use-pragma-once.rst =================================================================== --- /dev/null +++ clang-tools-extra/docs/clang-tidy/checks/modernize/use-pragma-once.rst @@ -0,0 +1,22 @@ +.. title:: clang-tidy - modernize-use-pragma-once + +modernize-use-pragma-once +========================= + +Adds ``#pragma once`` (a widely-supported compiler extension) to header files +and removes header guards if they are used (``#ifndef`` / ``#define`` / +``#endif``). + +Note that ``#pragma once`` cannot be correct in all possible use cases and has +not been added to the C++ standard. Not all projects will wish to use it. + +Options +------- + +.. option:: HeaderFileExtensions + + A comma-separated list of filename extensions of header files (the filename + extensions should not include "." prefix). Default is "h,hh,hpp,hxx". + For header files without an extension, use an empty string (if there are no + other desired extensions) or leave an empty element in the list. E.g., + "h,hh,hpp,hxx," (note the trailing comma). Index: clang-tools-extra/unittests/clang-tidy/ModernizeModuleTest.cpp =================================================================== --- clang-tools-extra/unittests/clang-tidy/ModernizeModuleTest.cpp +++ clang-tools-extra/unittests/clang-tidy/ModernizeModuleTest.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "ClangTidyTest.h" #include "modernize/IntegralLiteralExpressionMatcher.h" +#include "modernize/UsePragmaOnceCheck.h" #include "clang/Lex/Lexer.h" #include "gtest/gtest.h" @@ -272,6 +273,62 @@ INSTANTIATE_TEST_SUITE_P(IntegralLiteralExpressionMatcherTests, SizeTest, ::testing::ValuesIn(SizeParams)); +namespace { +std::string runPragmaOnceCheck(StringRef Code, const Twine &Filename, + std::optional ExpectedWarning, + std::map PathsToContent = + std::map()) { + std::vector Errors; + std::string Result = test::runCheckOnCode( + Code, &Errors, Filename, std::string("-xc++-header"), ClangTidyOptions{}, + std::move(PathsToContent)); + if (Errors.size() != (size_t)ExpectedWarning.has_value()) + return "invalid error count"; + if (ExpectedWarning && *ExpectedWarning != Errors.back().Message.Message) + return "expected: '" + ExpectedWarning->str() + "', saw: '" + + Errors.back().Message.Message + "'"; + return Result; +} +} // namespace + +TEST(UsePragmaOnceCheckTest, AddPragmaOnce) { + EXPECT_EQ("#pragma once\n" + "\n" + "void headerGuard();\n" + "\n", + runPragmaOnceCheck("#ifndef HEADER_GUARD_H\n" + "#define HEADER_GUARD_H\n" + "\n" + "void headerGuard();\n" + "\n" + "#endif // HEADER_GUARD_H\n", + "header-guard.h", + StringRef("use #pragma once"))); + EXPECT_EQ("#pragma once\n" + "\n" + "void pragmaOnce();\n", + runPragmaOnceCheck("#pragma once\n" + "\n" + "void pragmaOnce();\n", + "pragma-once.h", std::nullopt)); + EXPECT_EQ("#pragma once\n" + "\n" + "void both();\n" + "\n", + runPragmaOnceCheck("#ifndef BOTH_H\n" + "#define BOTH_H\n" + "#pragma once\n" + "\n" + "void both();\n" + "\n" + "#endif // BOTH_H\n", + "both.h", StringRef("use #pragma once"))); + EXPECT_EQ("#pragma once\n" + "void neither();\n", + runPragmaOnceCheck("void neither();\n", "neither.h", + StringRef("use #pragma once"))); +} + } // namespace test } // namespace tidy } // namespace clang