summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Source/cmCMakePresetsGraphInternal.h2
-rw-r--r--Source/cmCPluginAPI.h2
-rw-r--r--Source/cmVersionConfig.h.in2
-rw-r--r--Utilities/ClangTidyModule/CMakeLists.txt2
-rw-r--r--Utilities/ClangTidyModule/Module.cxx2
-rw-r--r--Utilities/ClangTidyModule/Tests/CMakeLists.txt1
-rw-r--r--Utilities/ClangTidyModule/Tests/RunClangTidy.cmake54
-rw-r--r--Utilities/ClangTidyModule/Tests/cmake-use-pragma-once-stdout.txt25
-rw-r--r--Utilities/ClangTidyModule/Tests/cmake-use-pragma-once.cxx5
-rw-r--r--Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-both-fixit.h8
-rw-r--r--Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-both.h10
-rw-r--r--Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-include-guards-fixit.h6
-rw-r--r--Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h9
-rw-r--r--Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-neither-fixit.h5
-rw-r--r--Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-neither.h4
-rw-r--r--Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once.h6
-rw-r--r--Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx325
-rw-r--r--Utilities/ClangTidyModule/UsePragmaOnceCheck.h60
18 files changed, 515 insertions, 13 deletions
diff --git a/Source/cmCMakePresetsGraphInternal.h b/Source/cmCMakePresetsGraphInternal.h
index 9e47a69..2726e92 100644
--- a/Source/cmCMakePresetsGraphInternal.h
+++ b/Source/cmCMakePresetsGraphInternal.h
@@ -1,5 +1,7 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
#include <memory>
#include <string>
#include <vector>
diff --git a/Source/cmCPluginAPI.h b/Source/cmCPluginAPI.h
index 19626f0..0d8a366 100644
--- a/Source/cmCPluginAPI.h
+++ b/Source/cmCPluginAPI.h
@@ -8,7 +8,7 @@
loosely into four groups 1) Utility 2) cmMakefile 3) cmSourceFile 4)
cmSystemTools. Within each grouping functions are listed alphabetically */
/*=========================================================================*/
-#ifndef cmCPluginAPI_h
+#ifndef cmCPluginAPI_h /* NOLINT(cmake-use-pragma-once) */
#define cmCPluginAPI_h
#define CMAKE_VERSION_MAJOR 2
diff --git a/Source/cmVersionConfig.h.in b/Source/cmVersionConfig.h.in
index 06251f3..5d52950 100644
--- a/Source/cmVersionConfig.h.in
+++ b/Source/cmVersionConfig.h.in
@@ -1,5 +1,7 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
#define CMake_VERSION_MAJOR @CMake_VERSION_MAJOR@
#define CMake_VERSION_MINOR @CMake_VERSION_MINOR@
#define CMake_VERSION_PATCH @CMake_VERSION_PATCH@
diff --git a/Utilities/ClangTidyModule/CMakeLists.txt b/Utilities/ClangTidyModule/CMakeLists.txt
index c51f43a..6fc54b1 100644
--- a/Utilities/ClangTidyModule/CMakeLists.txt
+++ b/Utilities/ClangTidyModule/CMakeLists.txt
@@ -22,6 +22,8 @@ add_library(cmake-clang-tidy-module MODULE
UseCmstrlenCheck.h
UseCmsysFstreamCheck.cxx
UseCmsysFstreamCheck.h
+ UsePragmaOnceCheck.cxx
+ UsePragmaOnceCheck.h
)
target_include_directories(cmake-clang-tidy-module PRIVATE ${CLANG_INCLUDE_DIRS})
target_link_libraries(cmake-clang-tidy-module PRIVATE clang-tidy)
diff --git a/Utilities/ClangTidyModule/Module.cxx b/Utilities/ClangTidyModule/Module.cxx
index 7ef8e7d..c747cee 100644
--- a/Utilities/ClangTidyModule/Module.cxx
+++ b/Utilities/ClangTidyModule/Module.cxx
@@ -7,6 +7,7 @@
#include "UseBespokeEnumClassCheck.h"
#include "UseCmstrlenCheck.h"
#include "UseCmsysFstreamCheck.h"
+#include "UsePragmaOnceCheck.h"
namespace clang {
namespace tidy {
@@ -23,6 +24,7 @@ public:
"cmake-use-bespoke-enum-class");
CheckFactories.registerCheck<OstringstreamUseCmstrcatCheck>(
"cmake-ostringstream-use-cmstrcat");
+ CheckFactories.registerCheck<UsePragmaOnceCheck>("cmake-use-pragma-once");
}
};
diff --git a/Utilities/ClangTidyModule/Tests/CMakeLists.txt b/Utilities/ClangTidyModule/Tests/CMakeLists.txt
index 5bf0e89..b53d5d2 100644
--- a/Utilities/ClangTidyModule/Tests/CMakeLists.txt
+++ b/Utilities/ClangTidyModule/Tests/CMakeLists.txt
@@ -14,3 +14,4 @@ add_run_clang_tidy_test(cmake-use-cmstrlen)
add_run_clang_tidy_test(cmake-use-cmsys-fstream)
add_run_clang_tidy_test(cmake-use-bespoke-enum-class)
add_run_clang_tidy_test(cmake-ostringstream-use-cmstrcat)
+add_run_clang_tidy_test(cmake-use-pragma-once)
diff --git a/Utilities/ClangTidyModule/Tests/RunClangTidy.cmake b/Utilities/ClangTidyModule/Tests/RunClangTidy.cmake
index 7fd7cdd..98770d7 100644
--- a/Utilities/ClangTidyModule/Tests/RunClangTidy.cmake
+++ b/Utilities/ClangTidyModule/Tests/RunClangTidy.cmake
@@ -13,12 +13,22 @@ endif()
set(source_file "${RunClangTidy_BINARY_DIR}/${CHECK_NAME}.cxx")
configure_file("${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}.cxx" "${source_file}" COPYONLY)
+file(GLOB header_files RELATIVE "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}" "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}/*")
+file(REMOVE_RECURSE "${RunClangTiy_BINARY_DIR}/${CHECK_NAME}")
+foreach(header_file IN LISTS header_files)
+ if(NOT header_file MATCHES "-fixit\\.h\$")
+ file(MAKE_DIRECTORY "${RunClangTidy_BINARY_DIR}/${CHECK_NAME}")
+ configure_file("${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}/${header_file}" "${RunClangTidy_BINARY_DIR}/${CHECK_NAME}/${header_file}" COPYONLY)
+ endif()
+endforeach()
+
set(command
"${CLANG_TIDY_COMMAND}"
"--load=${CLANG_TIDY_MODULE}"
"--checks=-*,${CHECK_NAME}"
"--fix"
"--format-style=file"
+ "--header-filter=/${CHECK_NAME}/"
${config_arg}
"${source_file}"
--
@@ -44,18 +54,38 @@ if(NOT actual_stdout STREQUAL expect_stdout)
string(APPEND RunClangTidy_TEST_FAILED "Expected stdout:\n${expect_stdout_formatted}\nActual stdout:\n${actual_stdout_formatted}\n")
endif()
-if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}-fixit.cxx")
- set(expect_fixit_file "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}-fixit.cxx")
-else()
- set(expect_fixit_file "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}.cxx")
-endif()
-file(READ "${expect_fixit_file}" expect_fixit)
-file(READ "${source_file}" actual_fixit)
-if(NOT expect_fixit STREQUAL actual_fixit)
- string(REPLACE "\n" "\n " expect_fixit_formatted " ${expect_fixit}")
- string(REPLACE "\n" "\n " actual_fixit_formatted " ${actual_fixit}")
- string(APPEND RunClangTidy_TEST_FAILED "Expected fixit:\n${expect_fixit_formatted}\nActual fixit:\n${actual_fixit_formatted}\n")
-endif()
+function(check_fixit expected fallback_expected actual)
+ if(EXISTS "${expected}")
+ set(expect_fixit_file "${expected}")
+ else()
+ set(expect_fixit_file "${fallback_expected}")
+ endif()
+ file(READ "${expect_fixit_file}" expect_fixit)
+ file(READ "${actual}" actual_fixit)
+ if(NOT expect_fixit STREQUAL actual_fixit)
+ string(REPLACE "\n" "\n " expect_fixit_formatted " ${expect_fixit}")
+ string(REPLACE "\n" "\n " actual_fixit_formatted " ${actual_fixit}")
+ string(APPEND RunClangTidy_TEST_FAILED "Expected fixit for ${actual}:\n${expect_fixit_formatted}\nActual fixit:\n${actual_fixit_formatted}\n")
+ set(RunClangTidy_TEST_FAILED "${RunClangTidy_TEST_FAILED}" PARENT_SCOPE)
+ endif()
+endfunction()
+
+check_fixit(
+ "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}-fixit.cxx"
+ "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}.cxx"
+ "${source_file}"
+ )
+
+foreach(header_file IN LISTS header_files)
+ if(NOT header_file MATCHES "-fixit\\.h\$")
+ string(REGEX REPLACE "\\.h\$" "-fixit.h" header_fixit "${header_file}")
+ check_fixit(
+ "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}/${header_fixit}"
+ "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}/${header_file}"
+ "${RunClangTidy_BINARY_DIR}/${CHECK_NAME}/${header_file}"
+ )
+ endif()
+endforeach()
if(RunClangTidy_TEST_FAILED)
string(REPLACE ";" " " command_formatted "${command}")
diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once-stdout.txt b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once-stdout.txt
new file mode 100644
index 0000000..e80e4a4
--- /dev/null
+++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once-stdout.txt
@@ -0,0 +1,25 @@
+cmake-use-pragma-once/cmake-use-pragma-once-both.h:1:1: warning: use #pragma once [cmake-use-pragma-once]
+#ifndef BOTH_H
+^~~~~~~~~~~~~~
+cmake-use-pragma-once/cmake-use-pragma-once-both.h:1:1: note: FIX-IT applied suggested code changes
+cmake-use-pragma-once/cmake-use-pragma-once-both.h:2:1: note: FIX-IT applied suggested code changes
+#define BOTH_H
+^
+cmake-use-pragma-once/cmake-use-pragma-once-both.h:10:1: note: FIX-IT applied suggested code changes
+#endif
+^
+cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h:1:1: warning: use #pragma once [cmake-use-pragma-once]
+#ifndef INCLUDE_GUARDS_H
+^~~~~~~~~~~~~~~~~~~~~~~~
+#pragma once
+cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h:1:1: note: FIX-IT applied suggested code changes
+cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h:2:1: note: FIX-IT applied suggested code changes
+#define INCLUDE_GUARDS_H
+^
+cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h:9:1: note: FIX-IT applied suggested code changes
+#endif
+^
+cmake-use-pragma-once/cmake-use-pragma-once-neither.h:1:1: warning: use #pragma once [cmake-use-pragma-once]
+int neither()
+^
+cmake-use-pragma-once/cmake-use-pragma-once-neither.h:1:1: note: FIX-IT applied suggested code changes
diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once.cxx b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once.cxx
new file mode 100644
index 0000000..a571bc1
--- /dev/null
+++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once.cxx
@@ -0,0 +1,5 @@
+#include "cmake-use-pragma-once/cmake-use-pragma-once.h"
+
+#include "cmake-use-pragma-once/cmake-use-pragma-once-both.h"
+#include "cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h"
+#include "cmake-use-pragma-once/cmake-use-pragma-once-neither.h"
diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-both-fixit.h b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-both-fixit.h
new file mode 100644
index 0000000..73c9720
--- /dev/null
+++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-both-fixit.h
@@ -0,0 +1,8 @@
+
+
+#pragma once
+
+int both()
+{
+ return 0;
+}
diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-both.h b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-both.h
new file mode 100644
index 0000000..fdf3cd3
--- /dev/null
+++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-both.h
@@ -0,0 +1,10 @@
+#ifndef BOTH_H
+#define BOTH_H
+#pragma once
+
+int both()
+{
+ return 0;
+}
+
+#endif
diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-include-guards-fixit.h b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-include-guards-fixit.h
new file mode 100644
index 0000000..36461c2
--- /dev/null
+++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-include-guards-fixit.h
@@ -0,0 +1,6 @@
+#pragma once
+
+int includeGuards()
+{
+ return 0;
+}
diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h
new file mode 100644
index 0000000..687306d
--- /dev/null
+++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h
@@ -0,0 +1,9 @@
+#ifndef INCLUDE_GUARDS_H
+#define INCLUDE_GUARDS_H
+
+int includeGuards()
+{
+ return 0;
+}
+
+#endif
diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-neither-fixit.h b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-neither-fixit.h
new file mode 100644
index 0000000..eb5c6dd
--- /dev/null
+++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-neither-fixit.h
@@ -0,0 +1,5 @@
+#pragma once
+int neither()
+{
+ return 0;
+}
diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-neither.h b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-neither.h
new file mode 100644
index 0000000..c779ca0
--- /dev/null
+++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-neither.h
@@ -0,0 +1,4 @@
+int neither()
+{
+ return 0;
+}
diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once.h b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once.h
new file mode 100644
index 0000000..b0b2ea2
--- /dev/null
+++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once.h
@@ -0,0 +1,6 @@
+#pragma once
+
+int once()
+{
+ return 0;
+}
diff --git a/Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx b/Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx
new file mode 100644
index 0000000..7a42798
--- /dev/null
+++ b/Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx
@@ -0,0 +1,325 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+/* This code was originally taken from part of the Clang-Tidy LLVM project and
+ * modified for use with CMake under the following original license: */
+
+//===--- HeaderGuard.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 "UsePragmaOnceCheck.h"
+
+#include <algorithm>
+#include <cassert>
+
+#include <clang/Frontend/CompilerInstance.h>
+#include <clang/Lex/PPCallbacks.h>
+#include <clang/Lex/Preprocessor.h>
+#include <clang/Tooling/Tooling.h>
+#include <llvm/Support/Path.h>
+
+namespace clang {
+namespace tidy {
+namespace cmake {
+
+/// canonicalize a path by removing ./ and ../ components.
+static std::string cleanPath(StringRef Path)
+{
+ SmallString<256> Result = Path;
+ llvm::sys::path::remove_dots(Result, true);
+ return std::string(Result.str());
+}
+
+namespace {
+// This class is a workaround for the fact that PPCallbacks doesn't give us the
+// location of the hash for an #ifndef, #define, or #endif, so we have to find
+// it ourselves. We can't lex backwards, and attempting to turn on the
+// preprocessor's backtracking functionality wreaks havoc, so we have to
+// instantiate a second lexer and lex all the way from the beginning of the
+// file. Cache the results of this lexing so that we don't have to do it more
+// times than needed.
+//
+// TODO: Upstream a change to LLVM to give us the location of the hash in
+// PPCallbacks so we don't have to do this workaround.
+class DirectiveCache
+{
+public:
+ DirectiveCache(Preprocessor* PP, FileID FID)
+ : PP(PP)
+ , FID(FID)
+ {
+ SourceManager& SM = this->PP->getSourceManager();
+ const FileEntry* Entry = SM.getFileEntryForID(FID);
+ assert(Entry && "Invalid FileID given");
+
+ Lexer MyLexer(FID, SM.getMemoryBufferForFileOrFake(Entry), SM,
+ this->PP->getLangOpts());
+ Token Tok;
+
+ while (!MyLexer.LexFromRawLexer(Tok)) {
+ if (Tok.getKind() == tok::hash) {
+ assert(SM.getFileID(Tok.getLocation()) == this->FID &&
+ "Token FileID does not match passed FileID");
+ if (!this->HashLocs.empty()) {
+ assert(SM.getFileOffset(this->HashLocs.back()) <
+ SM.getFileOffset(Tok.getLocation()) &&
+ "Tokens in file are not in order");
+ }
+
+ this->HashLocs.push_back(Tok.getLocation());
+ }
+ }
+ }
+
+ SourceRange createRangeForIfndef(SourceLocation IfndefMacroTokLoc)
+ {
+ // The #ifndef of an include guard is likely near the beginning of the
+ // file, so search from the front.
+ return SourceRange(this->findPreviousHashFromFront(IfndefMacroTokLoc),
+ IfndefMacroTokLoc);
+ }
+
+ SourceRange createRangeForDefine(SourceLocation DefineMacroTokLoc)
+ {
+ // The #define of an include guard is likely near the beginning of the
+ // file, so search from the front.
+ return SourceRange(this->findPreviousHashFromFront(DefineMacroTokLoc),
+ DefineMacroTokLoc);
+ }
+
+ SourceRange createRangeForEndif(SourceLocation EndifLoc)
+ {
+ // The #endif of an include guard is likely near the end of the file, so
+ // search from the back.
+ return SourceRange(this->findPreviousHashFromBack(EndifLoc), EndifLoc);
+ }
+
+private:
+ Preprocessor* PP;
+ FileID FID;
+ SmallVector<SourceLocation> HashLocs;
+
+ SourceLocation findPreviousHashFromFront(SourceLocation Loc)
+ {
+ SourceManager& SM = this->PP->getSourceManager();
+ Loc = SM.getExpansionLoc(Loc);
+ assert(SM.getFileID(Loc) == this->FID &&
+ "Loc FileID does not match our FileID");
+
+ auto It = std::find_if(
+ this->HashLocs.begin(), this->HashLocs.end(),
+ [&SM, &Loc](const SourceLocation& OtherLoc) -> bool {
+ return SM.getFileOffset(OtherLoc) >= SM.getFileOffset(Loc);
+ });
+ assert(It != this->HashLocs.begin() &&
+ "No hash associated with passed Loc");
+ return *--It;
+ }
+
+ SourceLocation findPreviousHashFromBack(SourceLocation Loc)
+ {
+ SourceManager& SM = this->PP->getSourceManager();
+ Loc = SM.getExpansionLoc(Loc);
+ assert(SM.getFileID(Loc) == this->FID &&
+ "Loc FileID does not match our FileID");
+
+ auto It =
+ std::find_if(this->HashLocs.rbegin(), this->HashLocs.rend(),
+ [&SM, &Loc](const SourceLocation& OtherLoc) -> bool {
+ return SM.getFileOffset(OtherLoc) < SM.getFileOffset(Loc);
+ });
+ assert(It != this->HashLocs.rend() &&
+ "No hash associated with passed Loc");
+ return *It;
+ }
+};
+
+class UsePragmaOncePPCallbacks : public PPCallbacks
+{
+public:
+ UsePragmaOncePPCallbacks(Preprocessor* PP, UsePragmaOnceCheck* Check)
+ : PP(PP)
+ , Check(Check)
+ {
+ }
+
+ void FileChanged(SourceLocation Loc, FileChangeReason Reason,
+ SrcMgr::CharacteristicKind FileType,
+ FileID PrevFID) override
+ {
+ // Record all files we enter. We'll need them to diagnose headers without
+ // guards.
+ SourceManager& SM = this->PP->getSourceManager();
+ if (Reason == EnterFile && FileType == SrcMgr::C_User) {
+ if (const FileEntry* FE = SM.getFileEntryForID(SM.getFileID(Loc))) {
+ std::string FileName = cleanPath(FE->getName());
+ this->Files[FileName] = FE;
+ }
+ }
+ }
+
+ void Ifndef(SourceLocation Loc, const Token& MacroNameTok,
+ const MacroDefinition& MD) override
+ {
+ if (MD) {
+ return;
+ }
+
+ // Record #ifndefs that succeeded. We also need the Location of the Name.
+ this->Ifndefs[MacroNameTok.getIdentifierInfo()] =
+ std::make_pair(Loc, MacroNameTok.getLocation());
+ }
+
+ void MacroDefined(const Token& MacroNameTok,
+ const MacroDirective* MD) override
+ {
+ // Record all defined macros. We store the whole token to get info on the
+ // name later.
+ this->Macros.emplace_back(MacroNameTok, MD->getMacroInfo());
+ }
+
+ void Endif(SourceLocation Loc, SourceLocation IfLoc) override
+ {
+ // Record all #endif and the corresponding #ifs (including #ifndefs).
+ this->EndIfs[IfLoc] = Loc;
+ }
+
+ void EndOfMainFile() override
+ {
+ // Now that we have all this information from the preprocessor, use it!
+ SourceManager& SM = this->PP->getSourceManager();
+
+ for (const auto& MacroEntry : this->Macros) {
+ const MacroInfo* MI = MacroEntry.second;
+
+ // We use clang's header guard detection. This has the advantage of also
+ // emitting a warning for cases where a pseudo header guard is found but
+ // preceded by something blocking the header guard optimization.
+ if (!MI->isUsedForHeaderGuard()) {
+ continue;
+ }
+
+ const FileEntry* FE =
+ SM.getFileEntryForID(SM.getFileID(MI->getDefinitionLoc()));
+ std::string FileName = cleanPath(FE->getName());
+ this->Files.erase(FileName);
+
+ // Look up Locations for this guard.
+ SourceLocation Ifndef =
+ this->Ifndefs[MacroEntry.first.getIdentifierInfo()].second;
+ SourceLocation Define = MacroEntry.first.getLocation();
+ SourceLocation EndIf =
+ this
+ ->EndIfs[this->Ifndefs[MacroEntry.first.getIdentifierInfo()].first];
+
+ StringRef CurHeaderGuard =
+ MacroEntry.first.getIdentifierInfo()->getName();
+ std::vector<FixItHint> FixIts;
+
+ HeaderSearch& HeaderInfo = this->PP->getHeaderSearchInfo();
+
+ HeaderFileInfo& Info = HeaderInfo.getFileInfo(FE);
+
+ DirectiveCache Cache(this->PP, SM.getFileID(MI->getDefinitionLoc()));
+ SourceRange IfndefSrcRange = Cache.createRangeForIfndef(Ifndef);
+ SourceRange DefineSrcRange = Cache.createRangeForDefine(Define);
+ SourceRange EndifSrcRange = Cache.createRangeForEndif(EndIf);
+
+ if (Info.isPragmaOnce) {
+ FixIts.push_back(FixItHint::CreateRemoval(IfndefSrcRange));
+ } else {
+ FixIts.push_back(
+ FixItHint::CreateReplacement(IfndefSrcRange, "#pragma once"));
+ }
+
+ FixIts.push_back(FixItHint::CreateRemoval(DefineSrcRange));
+ FixIts.push_back(FixItHint::CreateRemoval(EndifSrcRange));
+
+ this->Check->diag(IfndefSrcRange.getBegin(), "use #pragma once")
+ << FixIts;
+ }
+
+ // Emit warnings for headers that are missing guards.
+ checkGuardlessHeaders();
+ clearAllState();
+ }
+
+ /// Looks for files that were visited but didn't have a header guard.
+ /// Emits a warning with fixits suggesting adding one.
+ void checkGuardlessHeaders()
+ {
+ // Look for header files that didn't have a header guard. Emit a warning
+ // and fix-its to add the guard.
+ // TODO: Insert the guard after top comments.
+ for (const auto& FE : this->Files) {
+ StringRef FileName = FE.getKey();
+ if (!Check->shouldSuggestToAddPragmaOnce(FileName)) {
+ continue;
+ }
+
+ SourceManager& SM = this->PP->getSourceManager();
+ FileID FID = SM.translateFile(FE.getValue());
+ SourceLocation StartLoc = SM.getLocForStartOfFile(FID);
+ if (StartLoc.isInvalid()) {
+ continue;
+ }
+
+ HeaderSearch& HeaderInfo = this->PP->getHeaderSearchInfo();
+
+ HeaderFileInfo& Info = HeaderInfo.getFileInfo(FE.second);
+ if (Info.isPragmaOnce) {
+ continue;
+ }
+
+ this->Check->diag(StartLoc, "use #pragma once")
+ << FixItHint::CreateInsertion(StartLoc, "#pragma once\n");
+ }
+ }
+
+private:
+ void clearAllState()
+ {
+ this->Macros.clear();
+ this->Files.clear();
+ this->Ifndefs.clear();
+ this->EndIfs.clear();
+ }
+
+ std::vector<std::pair<Token, const MacroInfo*>> Macros;
+ llvm::StringMap<const FileEntry*> Files;
+ std::map<const IdentifierInfo*, std::pair<SourceLocation, SourceLocation>>
+ Ifndefs;
+ std::map<SourceLocation, SourceLocation> EndIfs;
+
+ Preprocessor* PP;
+ UsePragmaOnceCheck* Check;
+};
+} // namespace
+
+void UsePragmaOnceCheck::storeOptions(ClangTidyOptions::OptionMap& Opts)
+{
+ this->Options.store(Opts, "HeaderFileExtensions",
+ RawStringHeaderFileExtensions);
+}
+
+void UsePragmaOnceCheck::registerPPCallbacks(const SourceManager& SM,
+ Preprocessor* PP,
+ Preprocessor* ModuleExpanderPP)
+{
+ PP->addPPCallbacks(std::make_unique<UsePragmaOncePPCallbacks>(PP, this));
+}
+
+bool UsePragmaOnceCheck::shouldSuggestToAddPragmaOnce(StringRef FileName)
+{
+ return utils::isFileExtension(FileName, this->HeaderFileExtensions);
+}
+
+} // namespace cmake
+} // namespace tidy
+} // namespace clang
diff --git a/Utilities/ClangTidyModule/UsePragmaOnceCheck.h b/Utilities/ClangTidyModule/UsePragmaOnceCheck.h
new file mode 100644
index 0000000..08c2099
--- /dev/null
+++ b/Utilities/ClangTidyModule/UsePragmaOnceCheck.h
@@ -0,0 +1,60 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+/* This code was originally taken from part of the Clang-Tidy LLVM project and
+ * modified for use with CMake under the following original license: */
+
+//===--- HeaderGuard.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
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include <clang-tidy/ClangTidyCheck.h>
+#include <clang-tidy/utils/FileExtensionsUtils.h>
+
+namespace clang {
+namespace tidy {
+namespace cmake {
+
+/// Finds and replaces header guards with 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 ClangTidyCheck
+{
+public:
+ UsePragmaOnceCheck(StringRef Name, ClangTidyContext* Context)
+ : ClangTidyCheck(Name, Context)
+ , RawStringHeaderFileExtensions(Options.getLocalOrGlobal(
+ "HeaderFileExtensions", utils::defaultHeaderFileExtensions()))
+ {
+ utils::parseFileExtensions(RawStringHeaderFileExtensions,
+ HeaderFileExtensions,
+ utils::defaultFileExtensionDelimiters());
+ }
+ void storeOptions(ClangTidyOptions::OptionMap& Opts) override;
+ void registerPPCallbacks(const SourceManager& SM, Preprocessor* PP,
+ Preprocessor* ModuleExpanderPP) override;
+
+ /// Returns ``true`` if the check should add pragma once to the file
+ /// if it has none.
+ virtual bool shouldSuggestToAddPragmaOnce(StringRef Filename);
+
+private:
+ std::string RawStringHeaderFileExtensions;
+ utils::FileExtensionsSet HeaderFileExtensions;
+};
+
+} // namespace cmake
+} // namespace tidy
+} // namespace clang