Merge topic 'cmake-diagnostics-doc'

95f7f84609 Help: Fix CMD_INSTALL_ABSOLUTE_DESTINATION documentation
a882ec84ec cmDocumentation: Add diagnostics
97d13c3053 Help: Improve diagnostics documentation
3cccce8ca2 Help: Add developer documentation for new diagnostics
ffe4c2a8ff Help: Fix developer documentation

Acked-by: Kitware Robot <kwrobot@kitware.com>
Acked-by: Robert Maynard <robertjmaynard@gmail.com>
Merge-request: !11996
diff --git a/Help/command/install.rst b/Help/command/install.rst
index 0d6ccb6..e69d266 100644
--- a/Help/command/install.rst
+++ b/Help/command/install.rst
@@ -61,9 +61,8 @@
   but not recommended.
 
   .. versionadded:: 4.4
-    The :ref:`CMD_INSTALL_ABSOLUTE_DESTINATION <CMD_INSTALL_ABSOLUTE_DESTINATION>`
-    diagnostic can be enabled to warn or error out when an absolute destination
-    is provided.
+    The :diagnostic:`CMD_INSTALL_ABSOLUTE_DESTINATION` diagnostic can be
+    enabled to warn or error out when an absolute destination is provided.
 
   When a relative path is given, it is interpreted relative to the value
   of the :variable:`CMAKE_INSTALL_PREFIX` variable.
diff --git a/Help/dev/README.rst b/Help/dev/README.rst
index 84766a6..050ec07 100644
--- a/Help/dev/README.rst
+++ b/Help/dev/README.rst
@@ -41,12 +41,14 @@
 * The `CMake Testing Guide`_.
 * The `CMake Experimental Features Guide`_.
 * The `CMake Debugging Guide`_.
+* The `CMake Diagnostics Guide`_.
 
 .. _`CMake Source Code Guide`: source.rst
 .. _`CMake Documentation Guide`: documentation.rst
 .. _`CMake Testing Guide`: testing.rst
 .. _`CMake Experimental Features Guide`: experimental.rst
 .. _`CMake Debugging Guide`: debug.rst
+.. _`CMake Diagnostics Guide`: diagnostics.rst
 
 Maintainer Documentation
 ========================
diff --git a/Help/dev/diagnostics.rst b/Help/dev/diagnostics.rst
new file mode 100644
index 0000000..394f4d6
--- /dev/null
+++ b/Help/dev/diagnostics.rst
@@ -0,0 +1,157 @@
+CMake Diagnostics Guide
+***********************
+
+The following is a guide on how CMake should issue diagnostics.
+See documentation on `CMake Development`_ for more information.
+
+.. _`CMake Development`: README.rst
+
+TL;DR Porting Guide (from CMake < 4.4)
+======================================
+
+I want to write ``IssueMessage(MessageType::AUTHOR_WARNING, ...``:
+  Write ``IssueDiagnostic(cmDiagnostics::CMD_AUTHOR, ...`` instead.
+
+I want to write ``IssueMessage(MessageType::DEPRECATION_WARNING, ...``:
+  Write ``IssueDiagnostic(cmDiagnostics::CMD_DEPRECATED, ...`` instead.
+
+I want to write ``IssueMessage(MessageType::AUTHOR_ERROR, ...``:
+  No, you don't.  Under the old system, there isn't a meaningful difference
+  between ``AUTHOR_WARNING`` and ``AUTHOR_ERROR``; at least, not as arguments
+  to ``IssueMessage``.
+
+I want to write ``IssueMessage(MessageType::DEPRECATION_ERROR, ...``:
+  See ``AUTHOR_ERROR``.
+
+History
+=======
+
+Prior to CMake 4.4, CMake did not have a formal system for issuing diagnostics.
+The messaging system had message types for author diagnostics and deprecation
+diagnostics which conflated category and severity.  Worse, although these types
+*purported* to allow the caller to specify severity, this information is not
+actually honored.  Other diagnostics existed which did not use these types.
+
+More importantly, the mechanisms for managing severity differed depending on
+the diagnostic.  Variables to control a diagnostic might be documented,
+undocumented, or non-existent.  Spelling of command-line options to control
+diagnostics was inconsistent, as was the ability to make diagnostics fatal.
+Adding new categories required significant effort, as the logic to handle a
+diagnostic was bespoke at every layer.
+
+CMake Diagnostic Framework
+==========================
+
+CMake 4.4 introduced a structured framework for issuing and managing
+diagnostics.  The ``cmDiagnostics.h`` header enumerates all available
+diagnostic categories.  All code to manage diagnostics leverages either this
+table or helper code generated from the same, such that it is the *only*
+non-documentation location that needs to be updated to add a new diagnostic
+category.  The framework is also responsible for determining the severity of a
+diagnostic, based on the combination of category defaults and user preferences,
+and for presenting the user with a consistent set of control mechanisms.
+
+Numeric identifiers associated with diagnostic categories are only used
+internally and are never persisted. This means that numbers can be reassigned
+at any time. This also permits ensuring that numbers are consecutive, which
+allows iterating over all categories using a simple ``for`` loop.  (Note that
+such loops will typically start from ``1``, not ``0``, as ``0`` is reserved
+for the 'NONE' category which is used primarily as a virtual parent.)  Where
+necessary, macros provided by ``cmDiagnostics.h`` can generate code for each
+diagnostic category.
+
+Refer to ``Help/manual/cmake-diagnostics.7.rst`` for user-facing documentation.
+
+Issuing Diagnostics
+===================
+
+Diagnostics are the CMake equivalent of compiler warnings, with categorization
+providing both additional information and additional response control to users.
+
+Diagnostics are issued via the ``IssueDiagnostic`` method.  This method is
+provided on various objects, with ``cmMakefile`` being the most prominent, and
+typically exists alongside an ``IssueMessage`` method.  The latter is not
+deprecated, and should still be used to issue messages outside of the
+diagnostic categories, such as hard errors or informational messages.
+
+The historic ``{AUTHOR,DEPRECATION}_{WARNING,ERROR}`` message types, however,
+have been removed.  These are replaced by ``IssueDiagnostic`` and the
+``CMD_{AUTHOR,DEPRECATED}`` diagnostic categories.  Because the methods have
+similar signatures, porting in many cases can be accomplished by replacing the
+method name and first argument (the former message type, newly the diagnostic
+category).  For example, where prior versions of CMake wrote:
+
+.. code:: c++
+
+  mf->IssueMessage(MessageType::AUTHOR_WARNING, ...);
+
+CMake 4.4 and later should write:
+
+.. code:: c++
+
+  mf->IssueDiagnostic(cmDiagnostics::CMD_AUTHOR, ...);
+
+(Note that the ``"cmDiagnostics.h"`` header may need to be newly included.)
+
+As previously mentioned, the purported severity was ignored by ``cmMessenger``,
+which instead used its own internal state to decide whether a message should
+be a warning or an error.  This logic overrode the severity component of the
+message type as passed into the method.  As a result, although the new method
+does not accept a severity, this is actually consistent with the historic
+behavior and does not represent any loss of functionality.
+
+.. note::
+
+  When issuing diagnostics targeted at build-system authors, please consult the
+  complete list of available diagnostics, as a more specific category may be
+  available.  In some cases, it may even make sense to add a new category.
+
+Adding Diagnostic Categories
+============================
+
+Adding new diagnostic categories involves four steps.
+
+1.  The new category must be added to ``cmDiagnostics.h``.  The diagnostics
+    table looks like this:
+
+    .. code:: c++
+
+      #define CM_FOR_EACH_DIAGNOSTIC_TABLE(ACTION, SELECT)      \
+        SELECT(ACTION, Warn, CMD_NONE, CMD_AUTHOR, 12)          \
+        SELECT(ACTION, Warn, CMD_AUTHOR, CMD_DEPRECATED, 1)     \
+        ...
+
+    Add a new entry.  All entries must begin with ``SELECT(ACTION,``.  The
+    second argument is the *default* state of the diagnostic and should
+    typically be ``Warn``, but may be ``Ignore`` if the diagnostic should be
+    opt-in.  Defaulting to ``SendError`` would be exceptional, and defaulting
+    to ``FatalError`` is not recommended under any circumstances.  The third
+    argument is the *parent* of the diagnostic (see below).  The fourth is the
+    actual name of the diagnostic.  The fifth and final argument is the CMake
+    preset version corresponding to the introduction of the argument, which
+    should be the latest preset version as used by the version of CMake under
+    development.
+
+    .. note::
+
+      In the event that the development version has not already introduced a
+      new preset version, adding a new diagnostic will additionally require
+      introducing a new preset version.
+
+    Diagnostics are hierarchical and **must** be listed in order of a
+    depth-first search.  (If you imagine the order of items in a typical tree
+    view which has been fully expanded, that is depth-first order.)  Each
+    diagnostic has a direct parent.  Recursive alterations to a diagnostic will
+    also affect all descendant diagnostics.
+
+2.  A documentation page for the new diagnostic must be created under
+    ``Help/diagnostic``, and a link to the same added to the Diagnostics
+    manual, ``Help/manual/cmake-diagnostics.7.rst``.
+
+3.  The new presets fields must be mentioned in the CMake presets version
+    history, found at the bottom of ``Help/manual/cmake-presets.7.rst``.  Note
+    that the documentation of the fields themselves is generated.
+
+4.  The ``Utilities/Scripts/regenerate-presets.py`` script must be run to
+    generate the updated presets schema and documentation.  Don't forget to
+    review and commit the changes made by this script.
diff --git a/Help/dev/experimental.rst b/Help/dev/experimental.rst
index 30d53ff..61b77c0 100644
--- a/Help/dev/experimental.rst
+++ b/Help/dev/experimental.rst
@@ -37,7 +37,7 @@
 
 * The package name associated with specific targets may be specified
   using the ``CMAKE_EXPORT_FIND_PACKAGE_NAME`` variable and/or
-``EXPORT_FIND_PACKAGE_NAME`` target property.
+  ``EXPORT_FIND_PACKAGE_NAME`` target property.
 
 Export |CPS| Package Information for ``install(EXPORT)``
 ========================================================
@@ -102,7 +102,7 @@
 * Targets with the property set to a true value will have their C++ build
   information exported to the build database.
 
-Software Bill Of Materials |SBOM|
+Software Bill Of Materials (SBOM)
 =================================
 
 In order to activate support for the :command:`install(SBOM)` command,
diff --git a/Help/dev/source.rst b/Help/dev/source.rst
index ec4e12c..046be1e 100644
--- a/Help/dev/source.rst
+++ b/Help/dev/source.rst
@@ -287,10 +287,10 @@
 * ``<cmext/type_traits>``:
 
   * ``cm::is_pair``:
-   Checks if a type is a std::pair<> type.
+    Checks if a type is a std::pair<> type.
 
   * ``cm::is_tuple``:
-   Checks if a type is a std::tuple<> type.
+    Checks if a type is a std::tuple<> type.
 
   * ``cm::is_container``:
     Checks if a type is a container type.
diff --git a/Help/diagnostic/CMD_AUTHOR.rst b/Help/diagnostic/CMD_AUTHOR.rst
new file mode 100644
index 0000000..c309f31
--- /dev/null
+++ b/Help/diagnostic/CMD_AUTHOR.rst
@@ -0,0 +1,16 @@
+CMD_AUTHOR
+----------
+
+.. diagnostic::
+  :default: warn
+
+  Warn about a build system's incorrect use of CMake, or of a CMake interface
+  provided by a dependency.  This is the category triggered by
+  :command:`message(AUTHOR_WARNING)`.  It is also the ancestor of many other
+  diagnostic categories.
+
+  The most important aspect of this category is that it represents issues with
+  a project's build system which typically require alteration to the same.
+  This is to say that users simply trying to build a project obtained elsewhere
+  will typically not be interested in these warnings, except to perhaps report
+  them to the project's developer(s).
diff --git a/Help/diagnostic/CMD_DEPRECATED.rst b/Help/diagnostic/CMD_DEPRECATED.rst
new file mode 100644
index 0000000..dfb23d5
--- /dev/null
+++ b/Help/diagnostic/CMD_DEPRECATED.rst
@@ -0,0 +1,9 @@
+CMD_DEPRECATED
+--------------
+
+.. diagnostic::
+  :default: warn
+  :parent: CMD_AUTHOR
+
+  Warn about use of a deprecated function or package.  This is the category
+  triggered by :command:`message(DEPRECATION)`.
diff --git a/Help/diagnostic/CMD_INSTALL_ABSOLUTE_DESTINATION.rst b/Help/diagnostic/CMD_INSTALL_ABSOLUTE_DESTINATION.rst
new file mode 100644
index 0000000..b7c6e08
--- /dev/null
+++ b/Help/diagnostic/CMD_INSTALL_ABSOLUTE_DESTINATION.rst
@@ -0,0 +1,10 @@
+CMD_INSTALL_ABSOLUTE_DESTINATION
+--------------------------------
+
+.. diagnostic::
+  :default: ignore
+  :parent: CMD_AUTHOR
+
+  Warn when an :command:`install` command specifies an absolute ``DESTINATION``
+  path.  Absolute destinations are typically undesirable because they prevent
+  the installation prefix from being overridden at install time.
diff --git a/Help/diagnostic/CMD_UNINITIALIZED.rst b/Help/diagnostic/CMD_UNINITIALIZED.rst
new file mode 100644
index 0000000..8c2edc8
--- /dev/null
+++ b/Help/diagnostic/CMD_UNINITIALIZED.rst
@@ -0,0 +1,7 @@
+CMD_UNINITIALIZED
+-----------------
+
+.. diagnostic::
+  :default: ignore
+
+  Warn if an uninitialized variable is dereferenced.
diff --git a/Help/diagnostic/CMD_UNUSED_CLI.rst b/Help/diagnostic/CMD_UNUSED_CLI.rst
new file mode 100644
index 0000000..718598e
--- /dev/null
+++ b/Help/diagnostic/CMD_UNUSED_CLI.rst
@@ -0,0 +1,12 @@
+CMD_UNUSED_CLI
+--------------
+
+.. diagnostic::
+  :default: ignore
+
+  Warn about variables that are declared on the command line, but not used.
+
+  This diagnostic is issued outside of the configuration / generation phases.
+  Accordingly, while the action of this warning category can be queried as
+  usual, the :command:`cmake_diagnostic` command is unable to affect the action
+  of this diagnostic.
diff --git a/Help/manual/cmake-diagnostics.7.rst b/Help/manual/cmake-diagnostics.7.rst
index 5612299..de2b705 100644
--- a/Help/manual/cmake-diagnostics.7.rst
+++ b/Help/manual/cmake-diagnostics.7.rst
@@ -32,71 +32,62 @@
 * Issues that may impact the ability of the project
   to be built with newer versions of CMake.
 
-Diagnostic Actions
-------------------
+Controlling Diagnostics
+=======================
 
-The action taken when a particular diagnostic is triggered depends on the
-diagnostic category.  Most categories will warn by default.  The
-:command:`cmake_diagnostic` command and :option:`-W <cmake -W>` options can be
-used to control what action occurs when a diagnostic of a particular category
-is triggered.  The possible actions are described in the documentation of the
-same.
+Each diagnostic category has an associated action to be taken when that
+diagnostic is triggered.  Most categories will warn by default.  The available
+actions are described in the :command:`cmake_diagnostic` command documentation.
+
+CMake maintains a diagnostic state stack that is similar to the policy state.
+The initial state of the stack is determined by four factors, which are, in
+order of precedence:
+
+* The default action associated with the diagnostic.
+
+* The action associated with the diagnostic stored in the CMake variable cache,
+  which is used to persist the initial state between CMake runs.
+
+* The :preset:`configurePresets.warnings` and :preset:`configurePresets.errors`
+  fields of :manual:`CMake Presets <cmake-presets(7)>`.
+
+* The :option:`-W[no-][error=] <cmake -W>` command line arguments.
+
+.. note::
+
+  Because command line arguments operate both recursively and in the order
+  specified, some combinations of diagnostic arguments may result in later
+  arguments completely overwriting the action of earlier arguments.  For
+  example, ``-Wno-child -Wparent`` will result in the ``child`` warning being
+  enabled, because ``-Wparent`` promotes both ``parent`` and ``child`` to at
+  least ``WARN`` severity.  CMake presets are evaluated in order from most
+  ancestral to least ancestral.
+
+During script execution, the :command:`cmake_diagnostic` command can be used to
+query or alter the state, or to perform limited stack manipulations.
+
+When a diagnostic is issued at configure time (or during script execution, when
+CMake is running in script mode), the current diagnostic state controls the
+action.  Diagnostics issued at generate time, or outside of the configuration
+and generation phases must make use of recorded state information. While CMake
+strives to preserve this information in a way that matches the recorded state
+to the state as of the CMake command which ultimately causes a diagnostic to be
+issued, CMake may sometimes fall back to the state when processing of a
+subdirectory completed, or even the root state.  This may limit the ability of
+the :command:`cmake_diagnostic` command to control such diagnostics, especially
+if called from a function or included file.  This is especially the case for
+diagnostics that are not directly coupled to a CMake command.
 
 Diagnostic Categories
 =====================
 
-The following categories are defined.
+The following categories are defined:
 
-``CMD_AUTHOR`` (``-Wauthor``)
------------------------------
+.. toctree::
+   :maxdepth: 1
 
-:Default: Warn
-
-Warn about a build system's incorrect use of CMake, or of a CMake interface
-provided by a dependency.  This is the category triggered by
-:command:`message(AUTHOR_WARNING)`.  It is also the ancestor of many other
-diagnostic categories.
-
-The most important aspect of this category is that it represents issues with
-a project's build system which typically require alteration to the same.  This
-is to say that users simply trying to build a project obtained elsewhere will
-typically not be interested in these warnings, except to perhaps report them
-to the project's developer(s).
-
-``CMD_DEPRECATED`` (``-Wdeprecated``)
--------------------------------------
-
-:Default: Warn
-:Parent: ``CMD_AUTHOR``
-
-Warn about use of a deprecated function or package.  This is the category
-triggered by :command:`message(DEPRECATION)`.
-
-``CMD_UNINITIALIZED`` (``-Wuninitialized``)
--------------------------------------------
-
-:Default: Ignore
-
-Warn if an uninitialized variable is dereferenced.
-
-``CMD_UNUSED_CLI`` (``-Wunused-cli``)
--------------------------------------
-
-:Default: Warn
-
-Warn about variables that are declared on the command line, but not used.
-
-Although the action of this warning category can be queried as usual, changes
-made using the :command:`cmake_diagnostic` command have no effect.
-
-.. _CMD_INSTALL_ABSOLUTE_DESTINATION:
-
-``CMD_INSTALL_ABSOLUTE_DESTINATION`` (``-Winstall-absolute-destination``)
--------------------------------------------------------------------------
-
-:Default: Ignore
-
-Warn when an :command:`install` command specifies an absolute
-``DESTINATION`` path.  Absolute destinations are typically undesirable
-because they prevent the installation prefix from being overridden at
-install time.
+   /diagnostic/CMD_AUTHOR
+   /diagnostic/CMD_DEPRECATED
+   /diagnostic/CMD_INSTALL_ABSOLUTE_DESTINATION
+   /diagnostic/CMD_UNINITIALIZED
+   /diagnostic/CMD_UNUSED_CLI
diff --git a/Help/release/dev/install-absolute-dest-configure-time.rst b/Help/release/dev/install-absolute-dest-configure-time.rst
index 25a9c0a..580a46c 100644
--- a/Help/release/dev/install-absolute-dest-configure-time.rst
+++ b/Help/release/dev/install-absolute-dest-configure-time.rst
@@ -3,8 +3,10 @@
 
 * CMake gained the ability to diagnose :command:`install` commands that
   specify an absolute ``DESTINATION`` path via the
-  :ref:`CMD_INSTALL_ABSOLUTE_DESTINATION <CMD_INSTALL_ABSOLUTE_DESTINATION>`
-  diagnostic category. This diagnostic may be controlled with the
-  :option:`-Winstall-absolute-destination <cmake -W>` command-line option, the
-  ``installAbsoluteDestination`` field in a :manual:`cmake-presets(7)`
-  ``warnings`` object, or the :command:`cmake_diagnostic` command.
+  :diagnostic:`CMD_INSTALL_ABSOLUTE_DESTINATION` diagnostic category.
+  This diagnostic may be controlled with the
+  :option:`-Winstall-absolute-destination <cmake -W>` command-line option,
+  the ``installAbsoluteDestination`` field in a
+  :manual:`CMake Presets <cmake-presets(7)>`
+  :preset:`configurePresets.warnings` object,
+  or the :command:`cmake_diagnostic` command.
diff --git a/Help/variable/CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION.rst b/Help/variable/CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION.rst
index 84a27e5..5ee7bf3 100644
--- a/Help/variable/CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION.rst
+++ b/Help/variable/CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION.rst
@@ -11,8 +11,7 @@
 
 .. versionadded:: 4.4
 
-  The :ref:`CMD_INSTALL_ABSOLUTE_DESTINATION <CMD_INSTALL_ABSOLUTE_DESTINATION>`
-  diagnostic can be used to to generate errors for absolute install destinations
-  at generate time.
+  The :diagnostic:`CMD_INSTALL_ABSOLUTE_DESTINATION` diagnostic can be used to
+  generate errors for absolute install destinations at generate time.
 
 See also :variable:`CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION`.
diff --git a/Help/variable/CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION.rst b/Help/variable/CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION.rst
index fac680e..ac40b3d 100644
--- a/Help/variable/CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION.rst
+++ b/Help/variable/CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION.rst
@@ -10,8 +10,7 @@
 
 .. versionadded:: 4.4
 
-  The :ref:`CMD_INSTALL_ABSOLUTE_DESTINATION <CMD_INSTALL_ABSOLUTE_DESTINATION>`
-  diagnostic can be used to to generate warnings for absolute install destinations
-  at generate time.
+  The :diagnostic:`CMD_INSTALL_ABSOLUTE_DESTINATION` diagnostic can be used to
+  generate warnings for absolute install destinations at generate time.
 
 See also :variable:`CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION`.
diff --git a/Source/cmDocumentation.cxx b/Source/cmDocumentation.cxx
index 66e3730..0cc2b64 100644
--- a/Source/cmDocumentation.cxx
+++ b/Source/cmDocumentation.cxx
@@ -26,7 +26,7 @@
 #include "cmVersion.h"
 
 namespace {
-cmDocumentationEntry const cmDocumentationStandardOptions[21] = {
+cmDocumentationEntry const cmDocumentationStandardOptions[] = {
   { "-h,-H,--help,-help,-usage,/?", "Print usage information and exit." },
   { "--version[=json-v1],-version[=json-v1],/V[=json-v1],/version[=json-v1] "
     "[<file>]",
@@ -39,6 +39,12 @@
   { "--help-command-list [<file>]",
     "List commands with help available and exit." },
   { "--help-commands [<file>]", "Print cmake-commands manual and exit." },
+  { "--help-diagnostic <diag> [<file>]",
+    "Print help for one diagnostic and exit." },
+  { "--help-diagnostic-list [<file>]",
+    "List diagnostics with help available and exit." },
+  { "--help-diagnostics [<file>]",
+    "Print cmake-diagnostics manual and exit." },
   { "--help-module <mod> [<file>]", "Print help for one module and exit." },
   { "--help-module-list [<file>]",
     "List modules with help available and exit." },
@@ -164,6 +170,8 @@
       return this->PrintHelpOneManual(os);
     case cmDocumentation::OneCommand:
       return this->PrintHelpOneCommand(os);
+    case cmDocumentation::OneDiagnostic:
+      return this->PrintHelpOneDiagnostic(os);
     case cmDocumentation::OneModule:
       return this->PrintHelpOneModule(os);
     case cmDocumentation::OnePolicy:
@@ -176,6 +184,8 @@
       return this->PrintHelpListManuals(os);
     case cmDocumentation::ListCommands:
       return this->PrintHelpListCommands(os);
+    case cmDocumentation::ListDiagnostics:
+      return this->PrintHelpListDiagnostics(os);
     case cmDocumentation::ListModules:
       return this->PrintHelpListModules(os);
     case cmDocumentation::ListProperties:
@@ -377,6 +387,11 @@
       cmSystemTools::Message(
         "Warning: --help-compatcommands no longer supported");
       return true;
+    } else if (strcmp(argv[i], "--help-diagnostics") == 0) {
+      help.HelpType = cmDocumentation::OneManual;
+      help.Argument = "cmake-diagnostics.7";
+      i += int(get_opt_argument(i + 1, help.Filename));
+      this->WarnFormFromFilename(help, result);
     } else if (strcmp(argv[i], "--help-full") == 0) {
       help.HelpType = cmDocumentation::Full;
       i += int(get_opt_argument(i + 1, help.Filename));
@@ -393,6 +408,12 @@
       i += int(get_opt_argument(i + 1, help.Filename));
       help.Argument = cmSystemTools::LowerCase(help.Argument);
       this->WarnFormFromFilename(help, result);
+    } else if (strcmp(argv[i], "--help-diagnostic") == 0) {
+      help.HelpType = cmDocumentation::OneDiagnostic;
+      i += int(get_opt_argument(i + 1, help.Argument));
+      i += int(get_opt_argument(i + 1, help.Filename));
+      help.Argument = cmSystemTools::UpperCase(help.Argument);
+      this->WarnFormFromFilename(help, result);
     } else if (strcmp(argv[i], "--help-module") == 0) {
       help.HelpType = cmDocumentation::OneModule;
       i += int(get_opt_argument(i + 1, help.Argument));
@@ -421,6 +442,9 @@
     } else if (strcmp(argv[i], "--help-command-list") == 0) {
       help.HelpType = cmDocumentation::ListCommands;
       i += int(get_opt_argument(i + 1, help.Filename));
+    } else if (strcmp(argv[i], "--help-diagnostic-list") == 0) {
+      help.HelpType = cmDocumentation::ListDiagnostics;
+      i += int(get_opt_argument(i + 1, help.Filename));
     } else if (strcmp(argv[i], "--help-module-list") == 0) {
       help.HelpType = cmDocumentation::ListModules;
       i += int(get_opt_argument(i + 1, help.Filename));
@@ -622,6 +646,25 @@
   return true;
 }
 
+bool cmDocumentation::PrintHelpOneDiagnostic(std::ostream& os)
+{
+  std::string dname = cmSystemTools::UpperCase(this->CurrentArgument);
+  if (this->PrintFiles(os, cmStrCat("diagnostic/", dname))) {
+    return true;
+  }
+  // Argument was not a command.  Complain.
+  os << "Argument \"" << this->CurrentArgument
+     << "\" to --help-command is not a CMake diagnostic.  "
+        "Use --help-diagnostic-list to see all diagnostics.\n";
+  return false;
+}
+
+bool cmDocumentation::PrintHelpListDiagnostics(std::ostream& os)
+{
+  this->PrintNames(os, "diagnostic/*");
+  return true;
+}
+
 bool cmDocumentation::PrintHelpOneModule(std::ostream& os)
 {
   std::string mname = this->CurrentArgument;
diff --git a/Source/cmDocumentation.h b/Source/cmDocumentation.h
index dd0fadb..e23cee9 100644
--- a/Source/cmDocumentation.h
+++ b/Source/cmDocumentation.h
@@ -30,6 +30,7 @@
     Full,
     ListManuals,
     ListCommands,
+    ListDiagnostics,
     ListModules,
     ListProperties,
     ListVariables,
@@ -38,6 +39,7 @@
     OneArbitrary,
     OneManual,
     OneCommand,
+    OneDiagnostic,
     OneModule,
     OneProperty,
     OneVariable,
@@ -124,12 +126,14 @@
   bool PrintHelpOneArbitrary(std::ostream& os);
   bool PrintHelpOneManual(std::ostream& os);
   bool PrintHelpOneCommand(std::ostream& os);
+  bool PrintHelpOneDiagnostic(std::ostream& os);
   bool PrintHelpOneModule(std::ostream& os);
   bool PrintHelpOnePolicy(std::ostream& os);
   bool PrintHelpOneProperty(std::ostream& os);
   bool PrintHelpOneVariable(std::ostream& os);
   bool PrintHelpListManuals(std::ostream& os);
   bool PrintHelpListCommands(std::ostream& os);
+  bool PrintHelpListDiagnostics(std::ostream& os);
   bool PrintHelpListModules(std::ostream& os);
   bool PrintHelpListProperties(std::ostream& os);
   bool PrintHelpListVariables(std::ostream& os);
diff --git a/Source/cmRST.cxx b/Source/cmRST.cxx
index e490370..0d3f3bc 100644
--- a/Source/cmRST.cxx
+++ b/Source/cmRST.cxx
@@ -20,7 +20,7 @@
   : OS(os)
   , DocRoot(std::move(docroot))
   , CMakeDirective("^.. (cmake:)?("
-                   "command|envvar|genex|signature|variable"
+                   "command|diagnostic|envvar|genex|signature|variable"
                    ")::")
   , CMakeModuleDirective("^.. cmake-module::[ \t]+([^ \t\n]+)$")
   , ParsedLiteralDirective("^.. parsed-literal::[ \t]*(.*)$")
@@ -34,7 +34,7 @@
   , ModuleRST(R"(^#\[(=*)\[\.rst:$)")
   , CMakeRole("(:cmake)?:("
               "cref|"
-              "command|cpack_gen|generator|genex|"
+              "command|cpack_gen|diagnostic|generator|genex|"
               "variable|envvar|module|policy|"
               "prop_cache|prop_dir|prop_gbl|prop_inst|prop_sf|"
               "prop_test|prop_tgt|"
diff --git a/Utilities/Sphinx/cmake.py b/Utilities/Sphinx/cmake.py
index 39b2d24..27049c7 100644
--- a/Utilities/Sphinx/cmake.py
+++ b/Utilities/Sphinx/cmake.py
@@ -234,6 +234,7 @@
 _cmake_index_objs = {
     'command':    _cmake_index_entry('command'),
     'cpack_gen':  _cmake_index_entry('cpack generator'),
+    'diagnostic': _cmake_index_entry('diagnostic'),
     'envvar':     _cmake_index_entry('envvar'),
     'generator':  _cmake_index_entry('generator'),
     'genex':      _cmake_index_entry('genex'),
@@ -523,6 +524,79 @@
         return super().run()
 
 
+class CMakeDiagnosticObject(CMakeObject):
+    object_type = 'diagnostic'
+    required_arguments = 0
+    optional_arguments = 0
+
+    DEFAULT_CHOICES = {'ignore', 'warn', 'error'}
+
+    def default_option(argument):
+        return directives.choice(
+            argument, CMakeDiagnosticObject.DEFAULT_CHOICES)
+
+    option_spec = {
+        'default': default_option,
+        'parent': directives.unchanged,
+    }
+
+    def __init__(self, *args, **kwargs):
+        self.targetname = None
+        super().__init__(*args, **kwargs)
+
+    def _preset_name(self) -> str:
+        sep = False
+        out = ''
+        for c in self.targetname[4:]:
+            if c == '_':
+                sep = True
+            elif sep:
+                out += c
+                sep = False
+            else:
+                out += c.lower()
+        return out
+
+    def _build_field(self, name: str, content: str | list[Node]) -> Node:
+        if type(content) is not list:
+            content = self.parse_text_to_nodes(content)
+
+        name_node = nodes.field_name(text=name)
+        body_node = nodes.field_body('', *content)
+        return nodes.field('', name_node, body_node)
+
+    def _build_cli(self) -> list[Node]:
+        cname = self.targetname[4:].lower().replace('_', '-')
+        ctext = f'-W[no-][error=]{cname}'
+        return self.parse_text_to_nodes(f':option:`{ctext} <cmake -W>`')
+
+    def _build_preset_refs(self) -> list[Node]:
+        p = self._preset_name()
+        w = f':preset:`warnings.{p} <configurePresets.warnings.{p}>`'
+        e = f':preset:`errors.{p} <configurePresets.errors.{p}>`'
+        return self.parse_text_to_nodes(f'{w}, {e}')
+
+    def run(self) -> list[Node]:
+        self.domain, self.objtype = self.name.split(':', 1)
+        doc = self.state.document
+        self.targetname = doc.next_node(nodes.title).astext()
+
+        default = self.options['default'].capitalize()
+        parent = self.options.get('parent')
+
+        headers = nodes.field_list()
+        headers += self._build_field('Command Line', self._build_cli())
+        headers += self._build_field('Presets', self._build_preset_refs())
+        headers += self._build_field('Default', default)
+
+        if parent:
+            parentRef = self.parse_text_to_nodes(f':diagnostic:`{parent}`')
+            headers += self._build_field('Parent', parentRef)
+
+        content = self.parse_content_to_nodes()
+        return [headers] + content
+
+
 class CMakeReferenceRole:
     # See sphinx.util.nodes.explicit_title_re; \x00 escapes '<'.
     _re = re.compile(r'^(.+?)(\s*)(?<!\x00)<(.*?)>$', re.DOTALL)
@@ -680,6 +754,7 @@
     object_types = {
         'command':    ObjType('command',    'command'),
         'cpack_gen':  ObjType('cpack_gen',  'cpack_gen'),
+        'diagnostic': ObjType('diagnostic', 'diagnostic'),
         'envvar':     ObjType('envvar',     'envvar'),
         'generator':  ObjType('generator',  'generator'),
         'genex':      ObjType('genex',      'genex'),
@@ -700,6 +775,7 @@
     directives = {
         'command':    CMakeObject,
         'envvar':     CMakeObject,
+        'diagnostic': CMakeDiagnosticObject,
         'genex':      CMakeGenexObject,
         'signature':  CMakeSignatureObject,
         'variable':   CMakeObject,
@@ -710,6 +786,7 @@
         'cref':       CMakeCRefRole(),
         'command':    CMakeXRefRole(fix_parens=True, lowercase=True),
         'cpack_gen':  CMakeXRefRole(),
+        'diagnostic': CMakeXRefRole(),
         'envvar':     CMakeXRefRole(),
         'generator':  CMakeXRefRole(),
         'genex':      CMakeXRefRole(),