Index: clang-tools-extra/clang-tidy/readability/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/readability/CMakeLists.txt +++ clang-tools-extra/clang-tidy/readability/CMakeLists.txt @@ -29,6 +29,7 @@ NamedParameterCheck.cpp NamespaceCommentCheck.cpp NonConstParameterCheck.cpp + PragmaOnceHeaderGuardStyle.cpp QualifiedAutoCheck.cpp ReadabilityTidyModule.cpp RedundantAccessSpecifiersCheck.cpp Index: clang-tools-extra/clang-tidy/readability/PragmaOnceHeaderGuardStyle.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/readability/PragmaOnceHeaderGuardStyle.h @@ -0,0 +1,36 @@ +//===--- PragmaOnceHeaderGuardStyle.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_READABILITY_PRAGMAONCEHEADERGUARDSTYLE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_PRAGMAONCEHEADERGUARDSTYLE_H + +#include "../utils/HeaderGuardStyle.h" + +namespace clang::tidy::readability { + +/// Header guard style that suggests the use of #pragma once. +class PragmaOnceHeaderGuardStyle : public utils::HeaderGuardStyle { +public: + PragmaOnceHeaderGuardStyle(HeaderGuardCheck *Check) + : HeaderGuardStyle(Check) {} + + 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 clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_PRAGMAONCEHEADERGUARDSTYLE_H Index: clang-tools-extra/clang-tidy/readability/PragmaOnceHeaderGuardStyle.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/readability/PragmaOnceHeaderGuardStyle.cpp @@ -0,0 +1,76 @@ +//===--- PragmaOnceHeaderGuardStyle.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 "PragmaOnceHeaderGuardStyle.h" +#include "HeaderGuardCheck.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang::tidy::readability { +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 PragmaOnceHeaderGuardStyle::onHeaderGuard( + Preprocessor *PP, StringRef FileName, const FileEntry *FE, + SourceLocation IfndefHash, SourceLocation Ifndef, + SourceLocation IfndefToken, SourceLocation DefineHash, const Token &Define, + SourceLocation EndIfHash, SourceLocation EndIf) { + if (!Ifndef.isValid()) + return; + + 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)); + + Check->diag(IfndefSrcRange.getBegin(), "use #pragma once") << FixIts; +} + +void PragmaOnceHeaderGuardStyle::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; + + Check->diag(StartLoc, "use #pragma once") + << FixItHint::CreateInsertion(StartLoc, "#pragma once\n"); +} +} // namespace clang::tidy::readability Index: clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp =================================================================== --- clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp +++ clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -33,6 +33,7 @@ #include "MisplacedArrayIndexCheck.h" #include "NamedParameterCheck.h" #include "NonConstParameterCheck.h" +#include "PragmaOnceHeaderGuardStyle.h" #include "QualifiedAutoCheck.h" #include "RedundantAccessSpecifiersCheck.h" #include "RedundantControlFlowCheck.h" @@ -145,6 +146,11 @@ CheckFactories.registerCheck( "readability-use-anyofallof"); } + + void addHeaderGuardStyleFactories( + utils::HeaderGuardStyleFactories &StyleFactories) override { + StyleFactories.registerStyle("pragma-once"); + } }; // Register the ReadabilityModule using this statically initialized variable. Index: clang-tools-extra/docs/clang-tidy/checks/readability/header-guard.rst =================================================================== --- clang-tools-extra/docs/clang-tidy/checks/readability/header-guard.rst +++ clang-tools-extra/docs/clang-tidy/checks/readability/header-guard.rst @@ -24,3 +24,8 @@ ``llvm`` Use the LLVM header guard style. + + ``pragma-once`` + + Use ``#pragma once`` instead of macro guards. Note that ``#pragma once`` + is not part of the C++ standard, and may not work correctly in all cases. Index: clang-tools-extra/unittests/clang-tidy/ReadabilityModuleTest.cpp =================================================================== --- clang-tools-extra/unittests/clang-tidy/ReadabilityModuleTest.cpp +++ clang-tools-extra/unittests/clang-tidy/ReadabilityModuleTest.cpp @@ -1,6 +1,8 @@ #include "ClangTidyTest.h" #include "readability/BracesAroundStatementsCheck.h" +#include "readability/HeaderGuardCheck.h" #include "readability/NamespaceCommentCheck.h" +#include "readability/PragmaOnceHeaderGuardStyle.h" #include "readability/SimplifyBooleanExprCheck.h" #include "gtest/gtest.h" @@ -513,6 +515,97 @@ nullptr, "input.cc", {"-Wno-error=return-type"})); } +namespace { +struct UsePragmaOnceCheck : readability::HeaderGuardCheck { + UsePragmaOnceCheck(StringRef Name, ClangTidyContext *Context) + : HeaderGuardCheck(Name, Context) {} + + std::unique_ptr createHeaderGuardStyle() override { + return std::make_unique(this); + } +}; + +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(PragmaOnceHeaderGuardStyleTest, 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 headerGuardValue();\n" + "\n", + runPragmaOnceCheck("#ifndef HEADER_GUARD_VALUE_H\n" + "#define HEADER_GUARD_VALUE_H \\\n" + "1\n" + "\n" + "void headerGuardValue();\n" + "\n" + "#endif\n", + "header-guard-value.h", + StringRef("use #pragma once"))); + EXPECT_EQ("#if !defined(DEFINED_HEADER_GUARD_H)\n" + "#define DEFINED_HEADER_GUARD_H\n" + "\n" + "void definedHeaderGuard();\n" + "\n" + "#endif\n", + runPragmaOnceCheck("#if !defined(DEFINED_HEADER_GUARD_H)\n" + "#define DEFINED_HEADER_GUARD_H\n" + "\n" + "void definedHeaderGuard();\n" + "\n" + "#endif\n", + "defined-header-guard.h", std::nullopt)); + 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