[clang-tools-extra] [llvm] New check `misc-header-guard` (PR #177315)
Thorsten Klein via cfe-commits
cfe-commits at lists.llvm.org
Thu Feb 5 02:20:48 PST 2026
https://github.com/thorsten-klein updated https://github.com/llvm/llvm-project/pull/177315
>From f1ab2bb24101b9b02b70675ed95339c67e1afa0a Mon Sep 17 00:00:00 2001
From: Thorsten Klein <thorsten.klein at bshg.com>
Date: Thu, 5 Feb 2026 10:28:01 +0100
Subject: [PATCH 1/2] [clang-tidy] Add a new check `misc-header-guard`
Find and fix header guards
It respects option `misc-header-guard.HeaderDirs`, which contains a list
of one or more header directory names.
---
.../clang-tidy/misc/CMakeLists.txt | 1 +
.../clang-tidy/misc/HeaderGuardCheck.cpp | 104 ++++++++
.../clang-tidy/misc/HeaderGuardCheck.h | 46 ++++
.../clang-tidy/misc/MiscTidyModule.cpp | 2 +
clang-tools-extra/docs/ReleaseNotes.rst | 5 +
.../clang-tidy/checks/misc/header-guard.rst | 92 +++++++
.../clang-tidy/checkers/misc/header-guard.cpp | 251 ++++++++++++++++++
.../misc/header-guard/include/correct.hpp | 7 +
.../misc/header-guard/include/missing.hpp | 3 +
.../header-guard/include/other/correct.hpp | 7 +
.../header-guard/include/other/missing.hpp | 3 +
.../misc/header-guard/include/other/wrong.hpp | 7 +
.../misc/header-guard/include/pragma-once.hpp | 10 +
.../misc/header-guard/include/wrong.hpp | 7 +
14 files changed, 545 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/misc/HeaderGuardCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/misc/HeaderGuardCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/misc/header-guard.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/header-guard.cpp
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/correct.hpp
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/missing.hpp
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/other/correct.hpp
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/other/missing.hpp
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/other/wrong.hpp
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/pragma-once.hpp
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/wrong.hpp
diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index e34b0cf687be3..f8cb11bd3b8c1 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -23,6 +23,7 @@ add_clang_library(clangTidyMiscModule STATIC
CoroutineHostileRAIICheck.cpp
DefinitionsInHeadersCheck.cpp
ConfusableIdentifierCheck.cpp
+ HeaderGuardCheck.cpp
HeaderIncludeCycleCheck.cpp
IncludeCleanerCheck.cpp
MiscTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/HeaderGuardCheck.cpp b/clang-tools-extra/clang-tidy/misc/HeaderGuardCheck.cpp
new file mode 100644
index 0000000000000..5778e30eb65a4
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/HeaderGuardCheck.cpp
@@ -0,0 +1,104 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "HeaderGuardCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Lex/PPCallbacks.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/Support/Path.h"
+
+namespace clang::tidy::misc {
+
+HeaderGuardCheck::HeaderGuardCheck(StringRef Name, ClangTidyContext *Context)
+ : clang::tidy::utils::HeaderGuardCheck(Name, Context),
+ AllowPragmaOnce(Options.get("AllowPragmaOnce", false)),
+ HeaderDirs(utils::options::parseStringList(
+ Options.get("HeaderDirs", "include"))),
+ EndifComment(Options.get("EndifComment", false)),
+ Prefix(Options.get("Prefix", "")) {}
+
+std::string HeaderGuardCheck::getHeaderGuard(StringRef Filename,
+ StringRef OldGuard) {
+ std::string AbsPath = tooling::getAbsolutePath(Filename);
+
+ // When running under Windows, need to convert the path separators from
+ // `\` to `/`.
+ AbsPath = llvm::sys::path::convert_to_slash(AbsPath);
+
+ // consider all directories from HeaderDirs option. Stop at first found.
+ for (const StringRef HeaderDir : HeaderDirs) {
+ const size_t PosHeaderDir = AbsPath.rfind("/" + HeaderDir.str() + "/");
+ if (PosHeaderDir != StringRef::npos) {
+ // We don't want the header dir in our guards, i.e. _INCLUDE_
+ AbsPath = AbsPath.substr(PosHeaderDir + HeaderDir.size() + 2);
+ break; // stop at first found
+ }
+ }
+
+ std::string Guard = AbsPath;
+ llvm::replace(Guard, '/', '_');
+ llvm::replace(Guard, '.', '_');
+ llvm::replace(Guard, '-', '_');
+ Guard = Prefix.str() + Guard;
+
+ return StringRef(Guard).upper();
+}
+
+bool HeaderGuardCheck::shouldSuggestEndifComment(StringRef Filename) {
+ return EndifComment;
+}
+
+bool HeaderGuardCheck::shouldSuggestToAddHeaderGuard(StringRef Filename) {
+ if (HasPragmaOnce && AllowPragmaOnce)
+ return false;
+ return utils::HeaderGuardCheck::shouldSuggestToAddHeaderGuard(Filename);
+}
+
+void HeaderGuardCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "AllowPragmaOnce", AllowPragmaOnce);
+ Options.store(Opts, "EndifComment", EndifComment);
+ Options.store(Opts, "HeaderDirs",
+ utils::options::serializeStringList(HeaderDirs));
+ Options.store(Opts, "Prefix", Prefix);
+}
+
+class HeaderGuardCallbacks : public PPCallbacks {
+public:
+ HeaderGuardCallbacks(HeaderGuardCheck *Check, const SourceManager &SM)
+ : Check(Check), SM(SM) {}
+ void PragmaDirective(SourceLocation Loc,
+ PragmaIntroducerKind Introducer) override {
+ auto Str = StringRef(SM.getCharacterData(Loc));
+ if (!Str.consume_front("#"))
+ return;
+ Str = Str.trim();
+ if (!Str.consume_front("pragma"))
+ return;
+ Str = Str.trim();
+ if (Str.starts_with("once")) {
+ Check->HasPragmaOnce = true;
+ if (!Check->AllowPragmaOnce)
+ Check->diag(Loc, "use include guards instead of 'pragma once'");
+ }
+ }
+
+private:
+ HeaderGuardCheck *Check;
+ const SourceManager &SM;
+};
+
+void HeaderGuardCheck::registerPPCallbacks(const SourceManager &SM,
+ Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) {
+ utils::HeaderGuardCheck::registerPPCallbacks(SM, PP, ModuleExpanderPP);
+ PP->addPPCallbacks(std::make_unique<HeaderGuardCallbacks>(this, SM));
+}
+
+} // namespace clang::tidy::misc
diff --git a/clang-tools-extra/clang-tidy/misc/HeaderGuardCheck.h b/clang-tools-extra/clang-tidy/misc/HeaderGuardCheck.h
new file mode 100644
index 0000000000000..426c0a88af571
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/HeaderGuardCheck.h
@@ -0,0 +1,46 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_MISC_HEADERGUARDCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_HEADERGUARDCHECK_H
+
+#include "../utils/HeaderGuard.h"
+
+namespace clang::tidy::misc {
+
+/// Finds and fixes header guards.
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/misc/header-guard.html
+class HeaderGuardCheck : public utils::HeaderGuardCheck {
+public:
+ HeaderGuardCheck(StringRef Name, ClangTidyContext *Context);
+
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus || LangOpts.C99;
+ }
+
+ void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) override;
+
+ bool shouldSuggestEndifComment(StringRef Filename) override;
+ bool shouldSuggestToAddHeaderGuard(StringRef Filename) override;
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+ std::string getHeaderGuard(StringRef Filename, StringRef OldGuard) override;
+
+ const bool AllowPragmaOnce;
+ bool HasPragmaOnce = false;
+
+private:
+ const std::vector<StringRef> HeaderDirs;
+ const bool EndifComment;
+ const StringRef Prefix;
+};
+
+} // namespace clang::tidy::misc
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_HEADERGUARDCHECK_H
diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index f8550b30b9789..8523031ef820e 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -13,6 +13,7 @@
#include "ConstCorrectnessCheck.h"
#include "CoroutineHostileRAIICheck.h"
#include "DefinitionsInHeadersCheck.h"
+#include "HeaderGuardCheck.h"
#include "HeaderIncludeCycleCheck.h"
#include "IncludeCleanerCheck.h"
#include "MisleadingBidirectionalCheck.h"
@@ -53,6 +54,7 @@ class MiscModule : public ClangTidyModule {
"misc-coroutine-hostile-raii");
CheckFactories.registerCheck<DefinitionsInHeadersCheck>(
"misc-definitions-in-headers");
+ CheckFactories.registerCheck<HeaderGuardCheck>("misc-header-guard");
CheckFactories.registerCheck<HeaderIncludeCycleCheck>(
"misc-header-include-cycle");
CheckFactories.registerCheck<IncludeCleanerCheck>("misc-include-cleaner");
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 0ad69f5fdc5aa..4874d36f9b125 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -134,6 +134,11 @@ New checks
Checks for presence or absence of trailing commas in enum definitions and
initializer lists.
+- New :doc:`misc-header-guard <clang-tidy/checks/misc/header-guard>` check.
+
+ Finds and fixes header guards.
+
+
New check aliases
^^^^^^^^^^^^^^^^^
diff --git a/clang-tools-extra/docs/clang-tidy/checks/misc/header-guard.rst b/clang-tools-extra/docs/clang-tidy/checks/misc/header-guard.rst
new file mode 100644
index 0000000000000..9c908a2727dd6
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/misc/header-guard.rst
@@ -0,0 +1,92 @@
+.. title:: clang-tidy - llvm-header-guard
+
+misc-header-guard
+=================
+
+Finds and fixes header guards that do not conform to the configured style
+options from this check.
+
+All following examples consider header file
+``/path/to/include/component/header.hpp``
+
+By default, the check ensures following header guard:
+
+.. code-block:: c++
+
+ #ifndef COMPONENT_HEADER_HPP
+ #define COMPONENT_HEADER_HPP
+ ...
+ # endif
+
+Options
+-------
+
+.. option:: HeaderDirs
+
+ A semicolon-separated list of one or more header directory names. Header
+ directories may contain `/` as path separator. The list is searched for the
+ first matching string. The header guard will start from this path
+ component. Default value is `include`.
+
+ E.g. :option:`HeaderDirs` is set to one of the following values:
+
+ - `component`
+ - `include/component`
+ - `component;include`
+
+ It results in the same following header guard:
+
+ .. code-block:: c++
+
+ #ifndef HEADER_HPP
+ #define HEADER_HPP
+ ...
+ # endif
+
+ .. warning::
+
+ The .:option:`HeaderDirs` list is searched until first directory name
+ matches the header file path. E.g. if `HeaderDirs` is set to
+ `include;component`, the check will result in default behavior (since
+ `include` is found first).
+
+.. option:: Prefix
+
+ A string specifying an optional prefix that is applied to each header guard.
+ Default is an empty string.
+
+ E.g. :option:`Prefix` is set to `MY_OWN_PREFIX_`:
+
+ .. code-block:: c++
+
+ #ifndef MY_OWN_PREFIX_COMPONENT_HEADER_HPP
+ #define MY_OWN_PREFIX_COMPONENT_HEADER_HPP
+ ...
+ # endif
+
+.. option:: EndifComment
+
+ A boolean that controls whether the endif namespace comment is suggested.
+ Default value is `false`.
+
+ E.g. :option:`EndifComment` is set to `true`:
+
+ .. code-block:: c++
+
+ #ifndef COMPONENT_HEADER_HPP
+ #define COMPONENT_HEADER_HPP
+ ...
+ # endif // COMPONENT_HEADER_HPP
+
+.. option:: AllowPragmaOnce
+
+ A boolean that controls whether ``#pragma once`` directive is allowed.
+ Default value is `false`.
+
+ E.g. with option :option:`AllowPragmaOnce` set to `true`, ``#pragma once``
+ is allowed as header guard:
+
+ .. code-block:: c++
+
+ #pragma once
+ ...
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard.cpp
new file mode 100644
index 0000000000000..b1d39a632ed4f
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard.cpp
@@ -0,0 +1,251 @@
+#include "header-guard/include/correct.hpp"
+#include "header-guard/include/missing.hpp"
+#include "header-guard/include/wrong.hpp"
+
+#include "header-guard/include/other/correct.hpp"
+#include "header-guard/include/other/missing.hpp"
+#include "header-guard/include/other/wrong.hpp"
+
+// ---------------------------------------
+// TEST 1: Use no config options (default)
+// ---------------------------------------
+// RUN: %check_clang_tidy %s misc-header-guard %t -export-fixes=%t.1.yaml --header-filter=.* -- -I%S > %t.1.msg 2>&1
+// RUN: FileCheck -input-file=%t.1.msg -check-prefix=CHECK-MESSAGES1 %s
+// RUN: FileCheck -input-file=%t.1.yaml -check-prefix=CHECK-YAML1 %s
+
+// CHECK-MESSAGES1: missing.hpp:1:1: warning: header is missing header guard [misc-header-guard]
+// CHECK-MESSAGES1: missing.hpp:1:1: warning: header is missing header guard [misc-header-guard]
+// CHECK-MESSAGES1: wrong.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+// CHECK-MESSAGES1: wrong.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+
+// CHECK-YAML1: Message: header is missing header guard
+// CHECK-YAML1: FilePath: '{{.*header-guard.include.}}missing.hpp'
+// CHECK-YAML1: ReplacementText: "#ifndef MISSING_HPP\n#define MISSING_HPP\n\n"
+// CHECK-YAML1: ReplacementText: "\n#endif\n"
+
+// CHECK-YAML1: Message: header is missing header guard
+// CHECK-YAML1: FilePath: '{{.*header-guard.include.other.}}missing.hpp'
+// CHECK-YAML1: ReplacementText: "#ifndef OTHER_MISSING_HPP\n#define OTHER_MISSING_HPP\n\n"
+// CHECK-YAML1: ReplacementText: "\n#endif\n"
+
+// CHECK-YAML1: Message: header guard does not follow preferred style
+// CHECK-YAML1: FilePath: '{{.*header-guard.include.other.}}wrong.hpp'
+// CHECK-YAML1: ReplacementText: OTHER_WRONG_HPP
+
+// CHECK-YAML1: Message: header guard does not follow preferred style
+// CHECK-YAML1: FilePath: '{{.*header-guard.include.}}wrong.hpp'
+// CHECK-YAML1: ReplacementText: WRONG_HPP
+
+
+// ---------------------------------------
+// TEST 2: Set option HeaderDirs=other
+// ---------------------------------------
+// RUN: %check_clang_tidy %s misc-header-guard %t -export-fixes=%t.2.yaml --header-filter=.* \
+// RUN: --config='{CheckOptions: { \
+// RUN: misc-header-guard.HeaderDirs: other, \
+// RUN: }}' -- -I%S > %t.2.msg 2>&1
+// RUN: FileCheck -input-file=%t.2.msg -check-prefix=CHECK-MESSAGES2 %s
+// RUN: FileCheck -input-file=%t.2.yaml -check-prefix=CHECK-YAML2 %s
+
+// CHECK-MESSAGES2: correct.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+// CHECK-MESSAGES2: correct.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+// CHECK-MESSAGES2: missing.hpp:1:1: warning: header is missing header guard [misc-header-guard]
+// CHECK-MESSAGES2: missing.hpp:1:1: warning: header is missing header guard [misc-header-guard]
+// CHECK-MESSAGES2: wrong.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+// CHECK-MESSAGES2: wrong.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+
+// CHECK-YAML2: Message: header guard does not follow preferred style
+// CHECK-YAML2: FilePath: '{{.*header-guard.include.}}correct.hpp'
+// CHECK-YAML2: ReplacementText: {{.*}}CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_MISC_HEADER_GUARD_INCLUDE_CORRECT_HPP
+
+// CHECK-YAML2: Message: header is missing header guard
+// CHECK-YAML2: FilePath: '{{.*header-guard.include.}}missing.hpp'
+// CHECK-YAML2: ReplacementText: "#ifndef {{.*}}CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_MISC_HEADER_GUARD_INCLUDE_MISSING_HPP\n#define {{.*}}CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_MISC_HEADER_GUARD_INCLUDE_MISSING_HPP\n\n"
+// CHECK-YAML2: ReplacementText: "\n#endif\n"
+
+// CHECK-YAML2: Message: header guard does not follow preferred style
+// CHECK-YAML2: FilePath: '{{.*header-guard.include.other.}}correct.hpp'
+// CHECK-YAML2: ReplacementText: CORRECT_HPP
+
+// CHECK-YAML2: Message: header is missing header guard
+// CHECK-YAML2: FilePath: '{{.*header-guard.include.other.}}missing.hpp'
+// CHECK-YAML2: ReplacementText: "#ifndef MISSING_HPP\n#define MISSING_HPP\n\n"
+// CHECK-YAML2: ReplacementText: "\n#endif\n"
+
+// CHECK-YAML2: Message: header guard does not follow preferred style
+// CHECK-YAML2: FilePath: '{{.*header-guard.include.other.}}wrong.hpp'
+// CHECK-YAML2: ReplacementText: WRONG_HPP
+
+// CHECK-YAML2: Message: header guard does not follow preferred style
+// CHECK-YAML2: FilePath: '{{.*header-guard.include.}}wrong.hpp'
+// CHECK-YAML2: ReplacementText: {{.*}}CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_MISC_HEADER_GUARD_INCLUDE_WRONG_HPP
+
+
+// ---------------------------------------
+// TEST 3: Set option HeaderDirs=other;include
+// ---------------------------------------
+// RUN: %check_clang_tidy %s misc-header-guard %t -export-fixes=%t.3.yaml --header-filter=.* \
+// RUN: --config='{CheckOptions: { \
+// RUN: misc-header-guard.HeaderDirs: other;include, \
+// RUN: }}' -- -I%S > %t.3.msg 2>&1
+// RUN: FileCheck -input-file=%t.3.msg -check-prefix=CHECK-MESSAGES3 %s
+// RUN: FileCheck -input-file=%t.3.yaml -check-prefix=CHECK-YAML3 %s
+
+// CHECK-MESSAGES3: correct.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+// CHECK-MESSAGES3: correct.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+// CHECK-MESSAGES3: missing.hpp:1:1: warning: header is missing header guard [misc-header-guard]
+// CHECK-MESSAGES3: missing.hpp:1:1: warning: header is missing header guard [misc-header-guard]
+// CHECK-MESSAGES3: wrong.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+// CHECK-MESSAGES3: wrong.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+
+// CHECK-YAML3: Message: header is missing header guard
+// CHECK-YAML3: FilePath: '{{.*header-guard.include.}}missing.hpp'
+// CHECK-YAML3: ReplacementText: "#ifndef MISSING_HPP\n#define MISSING_HPP\n\n"
+// CHECK-YAML3: ReplacementText: "\n#endif\n"
+
+// CHECK-YAML3: Message: header guard does not follow preferred style
+// CHECK-YAML3: FilePath: '{{.*header-guard.include.other.}}correct.hpp'
+// CHECK-YAML3: ReplacementText: CORRECT_HPP
+
+// CHECK-YAML3: Message: header is missing header guard
+// CHECK-YAML3: FilePath: '{{.*header-guard.include.other.}}missing.hpp'
+// CHECK-YAML3: ReplacementText: "#ifndef MISSING_HPP\n#define MISSING_HPP\n\n"
+// CHECK-YAML3: ReplacementText: "\n#endif\n"
+
+// CHECK-YAML3: Message: header guard does not follow preferred style
+// CHECK-YAML3: FilePath: '{{.*header-guard.include.other.}}wrong.hpp'
+// CHECK-YAML3: ReplacementText: WRONG_HPP
+
+// CHECK-YAML3: Message: header guard does not follow preferred style
+// CHECK-YAML3: FilePath: '{{.*header-guard.include.}}wrong.hpp'
+// CHECK-YAML3: ReplacementText: WRONG_HPP
+
+
+// -------------------------------------------------------------------
+// TEST 4: Set option HeaderDirs=other;include and Prefix=SOME_PREFIX_
+// -------------------------------------------------------------------
+// RUN: %check_clang_tidy %s misc-header-guard %t -export-fixes=%t.4.yaml --header-filter=.* \
+// RUN: --config='{CheckOptions: { \
+// RUN: misc-header-guard.Prefix: SOME_PREFIX_, \
+// RUN: }}' -- -I%S > %t.4.msg 2>&1
+// RUN: FileCheck -input-file=%t.4.msg -check-prefix=CHECK-MESSAGES4 %s
+// RUN: FileCheck -input-file=%t.4.yaml -check-prefix=CHECK-YAML4 %s
+
+// CHECK-MESSAGES4: correct.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+// CHECK-MESSAGES4: correct.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+// CHECK-MESSAGES4: missing.hpp:1:1: warning: header is missing header guard [misc-header-guard]
+// CHECK-MESSAGES4: missing.hpp:1:1: warning: header is missing header guard [misc-header-guard]
+// CHECK-MESSAGES4: wrong.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+// CHECK-MESSAGES4: wrong.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+
+// CHECK-YAML4: Message: header guard does not follow preferred style
+// CHECK-YAML4: FilePath: '{{.*header-guard.include.}}correct.hpp'
+// CHECK-YAML4: ReplacementText: SOME_PREFIX_CORRECT_HPP
+
+// CHECK-YAML4: Message: header is missing header guard
+// CHECK-YAML4: FilePath: '{{.*header-guard.include.}}missing.hpp'
+// CHECK-YAML4: ReplacementText: "#ifndef SOME_PREFIX_MISSING_HPP\n#define SOME_PREFIX_MISSING_HPP\n\n"
+// CHECK-YAML4: ReplacementText: "\n#endif\n"
+
+// CHECK-YAML4: Message: header guard does not follow preferred style
+// CHECK-YAML4: FilePath: '{{.*header-guard.include.other.}}correct.hpp'
+// CHECK-YAML4: ReplacementText: SOME_PREFIX_OTHER_CORRECT_HPP
+
+// CHECK-YAML4: Message: header is missing header guard
+// CHECK-YAML4: FilePath: '{{.*header-guard.include.other.}}missing.hpp'
+// CHECK-YAML4: ReplacementText: "#ifndef SOME_PREFIX_OTHER_MISSING_HPP\n#define SOME_PREFIX_OTHER_MISSING_HPP\n\n"
+// CHECK-YAML4: ReplacementText: "\n#endif\n"
+
+// CHECK-YAML4: Message: header guard does not follow preferred style
+// CHECK-YAML4: FilePath: '{{.*header-guard.include.other.}}wrong.hpp'
+// CHECK-YAML4: ReplacementText: SOME_PREFIX_OTHER_WRONG_HPP
+
+// CHECK-YAML4: Message: header guard does not follow preferred style
+// CHECK-YAML4: FilePath: '{{.*header-guard.include.}}wrong.hpp'
+// CHECK-YAML4: ReplacementText: SOME_PREFIX_WRONG_HPP
+
+
+
+// -------------------------------------------------------------------
+// TEST 5: Set option EndifComment=true
+// -------------------------------------------------------------------
+// RUN: %check_clang_tidy %s misc-header-guard %t -export-fixes=%t.5.yaml --header-filter=.* \
+// RUN: --config='{CheckOptions: { \
+// RUN: misc-header-guard.EndifComment: true, \
+// RUN: }}' -- -I%S > %t.5.msg 2>&1
+// RUN: FileCheck -input-file=%t.5.msg -check-prefix=CHECK-MESSAGES5 %s
+// RUN: FileCheck -input-file=%t.5.yaml -check-prefix=CHECK-YAML5 %s
+
+// CHECK-MESSAGES5: correct.hpp:3:2: warning: #endif for a header guard should reference the guard macro in a comment [misc-header-guard]
+// CHECK-MESSAGES5: missing.hpp:1:1: warning: header is missing header guard [misc-header-guard]
+// CHECK-MESSAGES5: other{{.}}missing.hpp:1:1: warning: header is missing header guard [misc-header-guard]
+// CHECK-MESSAGES5: other{{.}}wrong.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+// CHECK-MESSAGES5: wrong.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+
+// CHECK-YAML5: Message: '#endif for a header guard should reference the guard macro in a comment'
+// CHECK-YAML5: FilePath: '{{.*header-guard.include.}}correct.hpp'
+// CHECK-YAML5: ReplacementText: 'endif // CORRECT_HPP'
+
+// CHECK-YAML5: Message: header is missing header guard
+// CHECK-YAML5: FilePath: '{{.*header-guard.include.}}missing.hpp'
+// CHECK-YAML5: ReplacementText: "#ifndef MISSING_HPP\n#define MISSING_HPP\n\n"
+// CHECK-YAML5: ReplacementText: "\n#endif // MISSING_HPP\n"
+
+// CHECK-YAML5: Message: '#endif for a header guard should reference the guard macro in a comment'
+// CHECK-YAML5: FilePath: '{{.*header-guard.include.other.}}correct.hpp'
+// CHECK-YAML5: ReplacementText: 'endif // OTHER_CORRECT_HPP'
+
+// CHECK-YAML5: Message: header is missing header guard
+// CHECK-YAML5: FilePath: '{{.*header-guard.include.other.}}missing.hpp'
+// CHECK-YAML5: ReplacementText: "#ifndef OTHER_MISSING_HPP\n#define OTHER_MISSING_HPP\n\n"
+// CHECK-YAML5: ReplacementText: "\n#endif // OTHER_MISSING_HPP\n"
+
+// CHECK-YAML5: Message: header guard does not follow preferred style
+// CHECK-YAML5: FilePath: '{{.*header-guard.include.other.}}wrong.hpp'
+// CHECK-YAML5: ReplacementText: OTHER_WRONG_HPP
+
+// CHECK-YAML5: Message: header guard does not follow preferred style
+// CHECK-YAML5: FilePath: '{{.*header-guard.include.}}wrong.hpp'
+// CHECK-YAML5: ReplacementText: WRONG_HPP
+
+// -------------------------------------------------------------------
+// TEST 6: Set option HeaderDirs=path/to/non-matching-dir
+// -------------------------------------------------------------------
+// RUN: %check_clang_tidy %s misc-header-guard %t -export-fixes=%t.6.yaml --header-filter=.* \
+// RUN: --config='{CheckOptions: { \
+// RUN: misc-header-guard.HeaderDirs: path/to/non-matching-dir, \
+// RUN: }}' -- -I%S > %t.6.msg 2>&1
+// RUN: FileCheck -input-file=%t.6.msg -check-prefix=CHECK-MESSAGES6 %s
+// RUN: FileCheck -input-file=%t.6.yaml -check-prefix=CHECK-YAML6 %s
+
+// CHECK-MESSAGES6: correct.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+// CHECK-MESSAGES6: correct.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+// CHECK-MESSAGES6: missing.hpp:1:1: warning: header is missing header guard [misc-header-guard]
+// CHECK-MESSAGES6: missing.hpp:1:1: warning: header is missing header guard [misc-header-guard]
+// CHECK-MESSAGES6: wrong.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+// CHECK-MESSAGES6: wrong.hpp:1:9: warning: header guard does not follow preferred style [misc-header-guard]
+
+// CHECK-YAML6: Message: header guard does not follow preferred style
+// CHECK-YAML6: FilePath: '{{.*header-guard.include.}}correct.hpp'
+// CHECK-YAML6: ReplacementText: {{.*}}_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_MISC_HEADER_GUARD_INCLUDE_CORRECT_HPP
+
+// CHECK-YAML6: Message: header is missing header guard
+// CHECK-YAML6: FilePath: '{{.*header-guard.include.}}missing.hpp'
+// CHECK-YAML6: ReplacementText: "#ifndef {{.*}}_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_MISC_HEADER_GUARD_INCLUDE_MISSING_HPP
+
+// CHECK-YAML6: Message: header guard does not follow preferred style
+// CHECK-YAML6: FilePath: '{{.*header-guard.include.other.}}correct.hpp'
+// CHECK-YAML6: ReplacementText: {{.*}}_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_MISC_HEADER_GUARD_INCLUDE_OTHER_CORRECT_HPP
+
+// CHECK-YAML6: Message: header is missing header guard
+// CHECK-YAML6: FilePath: '{{.*header-guard.include.other.}}missing.hpp'
+// CHECK-YAML6: ReplacementText: "#ifndef {{.*}}_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_MISC_HEADER_GUARD_INCLUDE_OTHER_MISSING_HPP
+
+// CHECK-YAML6: Message: header guard does not follow preferred style
+// CHECK-YAML6: FilePath: '{{.*header-guard.include.other.}}wrong.hpp'
+// CHECK-YAML6: ReplacementText: {{.*}}_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_MISC_HEADER_GUARD_INCLUDE_OTHER_WRONG_HPP
+
+// CHECK-YAML6: Message: header guard does not follow preferred style
+// CHECK-YAML6: FilePath: '{{.*header-guard.include.}}wrong.hpp'
+// CHECK-YAML6: ReplacementText: {{.*}}_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_MISC_HEADER_GUARD_INCLUDE_WRONG_HPP
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/correct.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/correct.hpp
new file mode 100644
index 0000000000000..7eeead99748e9
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/correct.hpp
@@ -0,0 +1,7 @@
+#ifndef CORRECT_HPP
+#define CORRECT_HPP
+#endif
+
+// RUN: %check_clang_tidy %s misc-header-guard correct -export-fixes=%t.yaml > %t.msg 2>&1
+// RUN: FileCheck -input-file=%t.msg -check-prefix=CHECK-MSG %s
+// CHECK-MSG: warning: code/includes outside of area guarded by header guard; consider moving it [misc-header-guard]
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/missing.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/missing.hpp
new file mode 100644
index 0000000000000..4a5a3a495a2b1
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/missing.hpp
@@ -0,0 +1,3 @@
+// RUN: %check_clang_tidy %s misc-header-guard missing -export-fixes=%t.yaml > %t.msg 2>&1
+// RUN: FileCheck -input-file=%t.msg -check-prefix=CHECK-MSG %s
+// CHECK-MSG: :1:1: warning: header is missing header guard [misc-header-guard]
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/other/correct.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/other/correct.hpp
new file mode 100644
index 0000000000000..97ea9db05cd41
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/other/correct.hpp
@@ -0,0 +1,7 @@
+#ifndef OTHER_CORRECT_HPP
+#define OTHER_CORRECT_HPP
+#endif
+
+// RUN: %check_clang_tidy %s misc-header-guard correct -export-fixes=%t.yaml > %t.msg 2>&1
+// RUN: FileCheck -input-file=%t.msg -check-prefix=CHECK-MSG %s
+// CHECK-MSG: warning: code/includes outside of area guarded by header guard; consider moving it [misc-header-guard]
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/other/missing.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/other/missing.hpp
new file mode 100644
index 0000000000000..4a5a3a495a2b1
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/other/missing.hpp
@@ -0,0 +1,3 @@
+// RUN: %check_clang_tidy %s misc-header-guard missing -export-fixes=%t.yaml > %t.msg 2>&1
+// RUN: FileCheck -input-file=%t.msg -check-prefix=CHECK-MSG %s
+// CHECK-MSG: :1:1: warning: header is missing header guard [misc-header-guard]
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/other/wrong.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/other/wrong.hpp
new file mode 100644
index 0000000000000..a9afba832a10a
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/other/wrong.hpp
@@ -0,0 +1,7 @@
+#ifndef SOME_WRONG_HEADER_GUARD_HPP
+#define SOME_WRONG_HEADER_GUARD_HPP
+#endif
+
+// RUN: %check_clang_tidy %s misc-header-guard wrong -export-fixes=%t.yaml > %t.msg 2>&1
+// RUN: FileCheck -input-file=%t.msg -check-prefix=CHECK-MSG %s
+// CHECK-MSG: warning: header guard does not follow preferred style [misc-header-guard]
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/pragma-once.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/pragma-once.hpp
new file mode 100644
index 0000000000000..be4a9341966e0
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/pragma-once.hpp
@@ -0,0 +1,10 @@
+#pragma once
+
+// RUN: %check_clang_tidy %s misc-header-guard pragma-once -export-fixes=%t.1.yaml > %t.1.msg 2>&1
+// RUN: FileCheck -input-file=%t.1.msg -check-prefix=CHECK-MSG1 %s
+// CHECK-MSG1: pragma-once.hpp:1:1: warning: use include guards instead of 'pragma once' [misc-header-guard]
+
+// RUN: %check_clang_tidy %s misc-header-guard pragma-once \
+// RUN: --config='{CheckOptions: { \
+// RUN: misc-header-guard.AllowPragmaOnce: true, \
+// RUN: }}' > %t.2.msg 2>&1
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/wrong.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/wrong.hpp
new file mode 100644
index 0000000000000..e5ca81da6a680
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/header-guard/include/wrong.hpp
@@ -0,0 +1,7 @@
+#ifndef HERE_IS_SOMETHING_WRONG_HPP
+#define HERE_IS_SOMETHING_WRONG_HPP
+#endif
+
+// RUN: %check_clang_tidy %s misc-header-guard wrong -export-fixes=%t.yaml > %t.msg 2>&1
+// RUN: FileCheck -input-file=%t.msg -check-prefix=CHECK-MSG %s
+// CHECK-MSG: warning: header guard does not follow preferred style [misc-header-guard]
>From ec8462d2e563e2e77600a9c887721b78ecd2e75b Mon Sep 17 00:00:00 2001
From: Thorsten Klein <thorsten.klein at bshg.com>
Date: Thu, 22 Jan 2026 08:15:20 +0100
Subject: [PATCH 2/2] Disable clang-tidy misc-header-guard
This does not apply well to LLVM which intentionally have own
llvm-header-guard check.
---
.clang-tidy | 1 +
1 file changed, 1 insertion(+)
diff --git a/.clang-tidy b/.clang-tidy
index 2cda1b81de808..bb404c291cd13 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -4,6 +4,7 @@ Checks: >
clang-diagnostic-*,
llvm-*,
misc-*,
+ -misc-header-guard,
-misc-const-correctness,
-misc-include-cleaner,
-misc-no-recursion,
More information about the cfe-commits
mailing list