[clang-tools-extra] [llvm] [clang-tidy] Remove 'clang-analyzer-*' checks from default checks. (PR #157306)

Baranov Victor via llvm-commits llvm-commits at lists.llvm.org
Sun Sep 21 13:58:26 PDT 2025


https://github.com/vbvictor updated https://github.com/llvm/llvm-project/pull/157306

>From 4ab549990f3dc3f59a6346b32687e5ecbd495586 Mon Sep 17 00:00:00 2001
From: Victor Baranov <bar.victor.2002 at gmail.com>
Date: Sat, 6 Sep 2025 22:54:31 +0300
Subject: [PATCH 1/2] [clang-tidy] Remove 'clang-analyzer-*' checks from
 default checks

---
 clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp | 3 +--
 clang-tools-extra/docs/ReleaseNotes.rst             | 2 ++
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
index bef3b938b5afd..ed9b195f0dbde 100644
--- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
+++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
@@ -102,8 +102,7 @@ Configuration files:
 )");
 
 const char DefaultChecks[] = // Enable these checks by default:
-    "clang-diagnostic-*,"    //   * compiler diagnostics
-    "clang-analyzer-*";      //   * Static Analyzer checks
+    "clang-diagnostic-*";    //   * compiler diagnostics
 
 static cl::opt<std::string> Checks("checks", desc(R"(
 Comma-separated list of globs with optional '-'
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index e1b6daf75457d..d97efe667f98e 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -139,6 +139,8 @@ Improvements to clang-tidy
   scripts by adding the `-hide-progress` option to suppress progress and
   informational messages.
 
+- Removed `clang-analyzer-*` check from default checks in :program:`clang-tidy`.
+
 New checks
 ^^^^^^^^^^
 

>From 54d4521e617091a520cf749cc714309891b7b582 Mon Sep 17 00:00:00 2001
From: Victor Baranov <bar.victor.2002 at gmail.com>
Date: Sun, 21 Sep 2025 23:58:12 +0300
Subject: [PATCH 2/2] WIP

---
 .clang-tidy                                   |  38 --
 .../clang-tidy/add_check_alias.py             | 426 ++++++++++++++++++
 .../clang-tidy/tool/ClangTidyMain.cpp         |  80 ++++
 .../config-with-exclusion                     |   3 +
 .../config-with-inclusion                     |   3 +
 .../config-with-wildcard                      |   3 +
 .../config-without-analyzer                   |   3 +
 .../clang-analyzer-soft-deprecation.cpp       |  35 ++
 clang-tools-extra/test_here.cpp               |   1 +
 test_warning.cpp                              |   4 +
 10 files changed, 558 insertions(+), 38 deletions(-)
 delete mode 100644 .clang-tidy
 create mode 100755 clang-tools-extra/clang-tidy/add_check_alias.py
 create mode 100644 clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-with-exclusion
 create mode 100644 clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-with-inclusion
 create mode 100644 clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-with-wildcard
 create mode 100644 clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-without-analyzer
 create mode 100644 clang-tools-extra/test/clang-tidy/infrastructure/clang-analyzer-soft-deprecation.cpp
 create mode 100644 clang-tools-extra/test_here.cpp
 create mode 100644 test_warning.cpp

diff --git a/.clang-tidy b/.clang-tidy
deleted file mode 100644
index 06bb0f18e9d2e..0000000000000
--- a/.clang-tidy
+++ /dev/null
@@ -1,38 +0,0 @@
-Checks: >
-  -*,
-  clang-diagnostic-*,
-  llvm-*,
-  misc-*,
-  -misc-const-correctness,
-  -misc-include-cleaner,
-  -misc-no-recursion,
-  -misc-non-private-member-variables-in-classes,
-  -misc-unused-parameters,
-  -misc-use-anonymous-namespace,
-  readability-identifier-naming
-
-CheckOptions:
-  - key:             readability-identifier-naming.ClassCase
-    value:           CamelCase
-  - key:             readability-identifier-naming.EnumCase
-    value:           CamelCase
-  - key:             readability-identifier-naming.FunctionCase
-    value:           camelBack
-  # Exclude from scanning as this is an exported symbol used for fuzzing
-  # throughout the code base.
-  - key:             readability-identifier-naming.FunctionIgnoredRegexp
-    value:           "LLVMFuzzerTestOneInput"
-  - key:             readability-identifier-naming.MemberCase
-    value:           CamelCase
-  - key:             readability-identifier-naming.ParameterCase
-    value:           CamelCase
-  - key:             readability-identifier-naming.UnionCase
-    value:           CamelCase
-  - key:             readability-identifier-naming.VariableCase
-    value:           CamelCase
-  - key:             readability-identifier-naming.IgnoreMainLikeFunctions
-    value:           1
-  - key:             readability-redundant-member-init.IgnoreBaseInCopyConstructors
-    value:           1
-  - key:             modernize-use-default-member-init.UseAssignment
-    value:           1
diff --git a/clang-tools-extra/clang-tidy/add_check_alias.py b/clang-tools-extra/clang-tidy/add_check_alias.py
new file mode 100755
index 0000000000000..44d4b956f3686
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/add_check_alias.py
@@ -0,0 +1,426 @@
+#!/usr/bin/env python3
+#
+# ===- add_check_alias.py - clang-tidy check alias generator ----*- python -*--===#
+#
+# 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
+#
+# ===-----------------------------------------------------------------------===#
+
+import argparse
+import io
+import os
+import re
+import sys
+
+# FIXME Python 3.9: Replace typing.Optional with builtins.
+from typing import Optional, Dict
+
+
+def get_camel_name(check_name: str) -> str:
+    return "".join(map(lambda elem: elem.capitalize(), check_name.split("-")))
+
+
+def get_camel_check_name(check_name: str) -> str:
+    return get_camel_name(check_name) + "Check"
+
+
+def get_module_filename(module_path: str, module: str) -> str:
+    modulecpp = list(
+        filter(
+            lambda p: p.lower() == module.lower() + "tidymodule.cpp",
+            os.listdir(module_path),
+        )
+    )[0]
+    return os.path.join(module_path, modulecpp)
+
+
+def add_alias_to_module(
+    module_path: str,
+    alias_module: str,
+    alias_name: str,
+    target_module: str,
+    target_name: str,
+    target_check_class: str,
+    options: Dict[str, str],
+) -> None:
+    """Add the alias registration to the module file."""
+    filename = get_module_filename(module_path, alias_module)
+    with io.open(filename, "r", encoding="utf8") as f:
+        lines = f.readlines()
+
+    alias_check_name = alias_module + "-" + alias_name
+    target_include = f"../{target_module}/{target_check_class}.h"
+
+    print("Updating %s..." % filename)
+    with io.open(filename, "w", encoding="utf8", newline="\n") as f:
+        header_added = False
+        header_found = False
+        check_added = False
+        options_added = False
+
+        alias_registration = (
+            f"    CheckFactories.registerCheck<{target_module}::{target_check_class}>(\n"
+            f'        "{alias_check_name}");\n'
+        )
+
+        lines_iter = iter(lines)
+        try:
+            while True:
+                line = next(lines_iter)
+
+                # Add include for the target check
+                if not header_added:
+                    match = re.search('#include "(.*)"', line)
+                    if match:
+                        header_found = True
+                        if match.group(1) > target_include:
+                            header_added = True
+                            f.write('#include "' + target_include + '"\n')
+                    elif header_found:
+                        header_added = True
+                        f.write('#include "' + target_include + '"\n')
+
+                # Add check registration
+                if not check_added:
+                    if line.strip() == "}":
+                        check_added = True
+                        f.write(alias_registration)
+                    else:
+                        match = re.search(
+                            r'registerCheck<(.*)> *\( *(?:"([^"]*)")?', line
+                        )
+                        prev_line = None
+                        if match:
+                            current_check_name = match.group(2)
+                            if current_check_name is None:
+                                # If we didn't find the check name on this line, look on the
+                                # next one.
+                                prev_line = line
+                                line = next(lines_iter)
+                                match = re.search(' *"([^"]*)"', line)
+                                if match:
+                                    current_check_name = match.group(1)
+                            assert current_check_name
+                            if current_check_name > alias_check_name:
+                                check_added = True
+                                f.write(alias_registration)
+                            if prev_line:
+                                f.write(prev_line)
+
+                # Add options if they exist and we're in the getModuleOptions method
+                if options and not options_added and "getModuleOptions" in line:
+                    # Look for the return statement to add options before it
+                    while True:
+                        f.write(line)
+                        line = next(lines_iter)
+                        if "return Options;" in line:
+                            # Add the options before the return statement
+                            for option_key, option_value in options.items():
+                                f.write(
+                                    f'    Options.CheckOptions["{alias_check_name}.{option_key}"] = "{option_value}";\n'
+                                )
+                            options_added = True
+                            break
+                        elif line.strip() == "}":
+                            # If we hit the end of the function without finding return, add the options before the closing brace
+                            for option_key, option_value in options.items():
+                                f.write(
+                                    f'    Options.CheckOptions["{alias_check_name}.{option_key}"] = "{option_value}";\n'
+                                )
+                            options_added = True
+                            break
+
+                f.write(line)
+        except StopIteration:
+            pass
+
+
+def add_release_notes(
+    clang_tidy_path: str,
+    alias_name: str,
+    target_name: str,
+    alias_module: str,
+    target_module: str,
+) -> None:
+    """Add a release notes entry for the new alias."""
+    filename = os.path.normpath(
+        os.path.join(clang_tidy_path, "../docs/ReleaseNotes.rst")
+    )
+    with io.open(filename, "r", encoding="utf8") as f:
+        lines = f.readlines()
+
+    alias_check_name = alias_module + "-" + alias_name
+    target_check_name = target_module + "-" + target_name
+
+    lineMatcher = re.compile("New check aliases")
+    nextSectionMatcher = re.compile("Changes in existing checks")
+    checkMatcher = re.compile("- New alias :doc:`(.*)")
+
+    print("Updating %s..." % filename)
+    with io.open(filename, "w", encoding="utf8", newline="\n") as f:
+        note_added = False
+        header_found = False
+        add_note_here = False
+
+        for line in lines:
+            if not note_added:
+                match = lineMatcher.match(line)
+                match_next = nextSectionMatcher.match(line)
+                match_check = checkMatcher.match(line)
+                if match_check:
+                    last_check = match_check.group(1)
+                    if last_check > alias_check_name:
+                        add_note_here = True
+
+                if match_next:
+                    add_note_here = True
+
+                if match:
+                    header_found = True
+                    f.write(line)
+                    continue
+
+                if line.startswith("^^^^"):
+                    f.write(line)
+                    continue
+
+                if header_found and add_note_here:
+                    if not line.startswith("^^^^"):
+                        f.write(
+                            f"""- New alias :doc:`{alias_check_name}
+  <clang-tidy/checks/{alias_module}/{alias_name}>` to
+  :doc:`{target_check_name}
+  <clang-tidy/checks/{target_module}/{target_name}>` was added.
+
+"""
+                        )
+                        note_added = True
+
+            f.write(line)
+
+
+def write_alias_docs(
+    clang_tidy_path: str,
+    alias_name: str,
+    target_name: str,
+    alias_module: str,
+    target_module: str,
+) -> None:
+    """Create the documentation file for the alias."""
+    alias_check_name = alias_module + "-" + alias_name
+    target_check_name = target_module + "-" + target_name
+
+    docs_dir = os.path.normpath(
+        os.path.join(clang_tidy_path, "../docs/clang-tidy/checks", alias_module)
+    )
+
+    # Create the module directory if it doesn't exist
+    os.makedirs(docs_dir, exist_ok=True)
+
+    filename = os.path.join(docs_dir, alias_name + ".rst")
+    print("Creating %s..." % filename)
+
+    with io.open(filename, "w", encoding="utf8", newline="\n") as f:
+        f.write(
+            f""".. title:: clang-tidy - {alias_check_name}
+.. meta::
+   :http-equiv=refresh: 5;URL=../{target_module}/{target_name}.html
+
+{alias_check_name}
+{"=" * len(alias_check_name)}
+
+The {alias_check_name} check is an alias, please see
+`{target_check_name} <../{target_module}/{target_name}.html>`_
+for more information.
+
+"""
+        )
+
+
+def update_target_docs(
+    clang_tidy_path: str,
+    alias_name: str,
+    target_name: str,
+    alias_module: str,
+    target_module: str,
+    options: Dict[str, str],
+) -> None:
+    """Update the target check documentation to mention the alias."""
+    filename = os.path.normpath(
+        os.path.join(
+            clang_tidy_path,
+            "../docs/clang-tidy/checks",
+            target_module,
+            target_name + ".rst",
+        )
+    )
+
+    if not os.path.exists(filename):
+        print(f"Warning: Target documentation file {filename} not found")
+        return
+
+    alias_check_name = alias_module + "-" + alias_name
+
+    with io.open(filename, "r", encoding="utf8") as f:
+        content = f.read()
+
+    # Check if alias section already exists
+    if f"{alias_module.upper()} alias" in content:
+        print(f"Alias section already exists in {filename}")
+        return
+
+    # Add alias section at the end
+    alias_section = f"""
+
+{alias_module.upper()} alias
+{"-" * (len(alias_module) + 6)}
+
+There is an alias of this check called {alias_check_name}."""
+
+    if options:
+        alias_section += f"""
+In that version the options"""
+        option_names = list(options.keys())
+        if len(option_names) == 1:
+            alias_section += f" :option:`{option_names[0]}` is"
+        else:
+            alias_section += " " + ", ".join(
+                f":option:`{name}`" for name in option_names[:-1]
+            )
+            alias_section += f" and :option:`{option_names[-1]}` are"
+        alias_section += f" set to different defaults."
+
+    alias_section += "\n"
+
+    print("Updating %s..." % filename)
+    with io.open(filename, "w", encoding="utf8", newline="\n") as f:
+        f.write(content + alias_section)
+
+
+def update_checks_list(clang_tidy_path: str) -> None:
+    """Update the main checks list documentation."""
+    add_new_check_script = os.path.join(clang_tidy_path, "add_new_check.py")
+    os.system(f"python3 {add_new_check_script} --update-docs")
+
+
+def parse_options(options_str: Optional[str]) -> Dict[str, str]:
+    """Parse the options string into a dictionary."""
+    if not options_str:
+        return {}
+
+    options = {}
+    for option in options_str.split(","):
+        if "=" in option:
+            key, value = option.split("=", 1)
+            options[key.strip()] = value.strip()
+        else:
+            print(f"Warning: Invalid option format '{option}', expected 'key=value'")
+
+    return options
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(description="Add a new clang-tidy check alias.")
+    parser.add_argument(
+        "alias_name", help="Name of the new alias (e.g., 'llvm-else-after-return')"
+    )
+    parser.add_argument(
+        "target_name",
+        help="Name of the target check to alias (e.g., 'readability-else-after-return')",
+    )
+    parser.add_argument(
+        "--options",
+        help="Comma-separated list of option overrides in the form key=value (e.g., 'WarnOnUnfixable=0,RefactorConditionVariables=0')",
+        default=None,
+    )
+    parser.add_argument(
+        "--description",
+        help="Description for the alias (used in release notes)",
+        default="",
+    )
+    parser.add_argument(
+        "--check-class-name",
+        help="Name of the check class (e.g., 'StrToNumCheck'). If not provided, will be inferred from target check name.",
+        default=None,
+    )
+
+    args = parser.parse_args()
+
+    # Parse the check names
+    alias_parts = args.alias_name.split("-", 1)
+    target_parts = args.target_name.split("-", 1)
+
+    if len(alias_parts) != 2 or len(target_parts) != 2:
+        print("Error: Check names must be in format 'module-checkname'")
+        return 1
+
+    alias_module, alias_name = alias_parts
+    target_module, target_name = target_parts
+
+    # Parse options
+    options = parse_options(args.options)
+
+    clang_tidy_path = os.path.dirname(sys.argv[0])
+    alias_module_path = os.path.join(clang_tidy_path, alias_module)
+
+    if not os.path.isdir(alias_module_path):
+        print(f"Error: Module directory '{alias_module_path}' does not exist")
+        return 1
+
+    # Determine the target check class name
+    if args.check_class_name:
+        target_check_class = args.check_class_name
+    else:
+        target_check_class = get_camel_check_name(target_name)
+
+    # Check if target check exists
+    target_module_path = os.path.join(clang_tidy_path, target_module)
+    target_header = os.path.join(target_module_path, target_check_class + ".h")
+
+    if not os.path.isfile(target_header):
+        print(f"Error: Target check header '{target_header}' does not exist")
+        print(
+            f"  (Looking for class '{target_check_class}' in module '{target_module}')"
+        )
+        if not args.check_class_name:
+            print(
+                f"  You may need to specify --check-class-name if the class name doesn't match the check name"
+            )
+        return 1
+
+    print(f"Adding alias '{args.alias_name}' -> '{args.target_name}'...")
+
+    # Add alias to the module
+    add_alias_to_module(
+        alias_module_path,
+        alias_module,
+        alias_name,
+        target_module,
+        target_name,
+        target_check_class,
+        options,
+    )
+
+    # Create alias documentation
+    write_alias_docs(
+        clang_tidy_path, alias_name, target_name, alias_module, target_module
+    )
+
+    # Note: Skipping target documentation update as requested
+
+    # Add release notes
+    add_release_notes(
+        clang_tidy_path, alias_name, target_name, alias_module, target_module
+    )
+
+    # Update the main documentation list
+    update_checks_list(clang_tidy_path)
+
+    print("Done! Alias has been created successfully.")
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
index ed9b195f0dbde..661b7f08a5360 100644
--- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
+++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
@@ -632,6 +632,86 @@ int clangTidyMain(int argc, const char **argv) {
   std::vector<std::string> EnabledChecks =
       getCheckNames(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers);
 
+  // Check for soft deprecation warning for clang-analyzer-* checks
+  if (!Quiet) {
+    bool HasAnalyzerChecks = llvm::any_of(EnabledChecks, [](const std::string &Check) {
+      return StringRef(Check).starts_with("clang-analyzer-");
+    });
+
+    if (!HasAnalyzerChecks) {
+      // Check if user explicitly mentioned clang-analyzer-* in their configuration
+      bool ExplicitlyMentioned = false;
+      std::vector<clang::tidy::ClangTidyOptionsProvider::OptionsSource>
+          RawOptions = OptionsProvider->getRawOptions(FilePath);
+      
+      for (const auto &[Opts, Source] : RawOptions) {
+        if (Opts.Checks) {
+          GlobList CheckGlobs(*Opts.Checks);
+          for (const auto &Item : CheckGlobs.getItems()) {
+            if (Item.Text.contains("clang-analyzer-")) {
+              ExplicitlyMentioned = true;
+              break;
+            }
+          }
+          if (ExplicitlyMentioned) break;
+        }
+      }
+
+      // Only show warning if user is using default checks or wildcard patterns
+      // that would have included clang-analyzer-* in the past  
+      bool ShouldWarn = false;
+      if (!ExplicitlyMentioned && !EnabledChecks.empty()) {
+        // Show warning if using defaults (no -checks specified and no config file checks)
+        if (Checks.getNumOccurrences() == 0) {
+          bool HasConfigChecks = false;
+          for (const auto &[Opts, Source] : RawOptions) {
+            if (Opts.Checks && !Opts.Checks->empty()) {
+              HasConfigChecks = true;
+              break;
+            }
+          }
+          if (!HasConfigChecks) {
+            ShouldWarn = true;
+          }
+        }
+        // Or if using broad wildcard patterns
+        else {
+          // Check command line checks
+          if (Checks.getNumOccurrences() > 0) {
+            StringRef CheckStr = Checks;
+            if ((CheckStr == "*" || CheckStr.starts_with("*,") || 
+                 CheckStr.contains(",*,") || CheckStr.ends_with(",*")) && 
+                !CheckStr.contains("clang-analyzer-")) {
+              ShouldWarn = true;
+            }
+          }
+          // Check config file checks if no command line checks
+          if (!ShouldWarn) {
+            for (const auto &[Opts, Source] : RawOptions) {
+              if (Opts.Checks) {
+                StringRef CheckStr = *Opts.Checks;
+                if ((CheckStr == "*" || CheckStr.starts_with("*,") || 
+                     CheckStr.contains(",*,") || CheckStr.ends_with(",*")) && 
+                    !CheckStr.contains("clang-analyzer-")) {
+                  ShouldWarn = true;
+                  break;
+                }
+              }
+            }
+          }
+        }
+      }
+
+      if (ShouldWarn) {
+        llvm::WithColor::warning(llvm::errs())
+            << "clang-analyzer-* checks are no longer enabled by default.\n"
+            << "If you want to enable clang-analyzer-* checks, use "
+            << "-checks='*,clang-analyzer-*' or add 'clang-analyzer-*' to "
+            << "your .clang-tidy configuration file.\n";
+      }
+    }
+  }
+
   if (ExplainConfig) {
     // FIXME: Show other ClangTidyOptions' fields, like ExtraArg.
     std::vector<clang::tidy::ClangTidyOptionsProvider::OptionsSource>
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-with-exclusion b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-with-exclusion
new file mode 100644
index 0000000000000..a2084db3da707
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-with-exclusion
@@ -0,0 +1,3 @@
+---
+Checks: '*,-clang-analyzer-*'
+---
\ No newline at end of file
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-with-inclusion b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-with-inclusion
new file mode 100644
index 0000000000000..1f38e120b7f43
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-with-inclusion
@@ -0,0 +1,3 @@
+---
+Checks: '*,clang-analyzer-*'
+---
\ No newline at end of file
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-with-wildcard b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-with-wildcard
new file mode 100644
index 0000000000000..cbea8802814ce
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-with-wildcard
@@ -0,0 +1,3 @@
+---
+Checks: '*'
+---
\ No newline at end of file
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-without-analyzer b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-without-analyzer
new file mode 100644
index 0000000000000..7678af6337c2f
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/clang-analyzer-soft-deprecation/config-without-analyzer
@@ -0,0 +1,3 @@
+---
+Checks: 'readability-*,bugprone-*'
+---
\ No newline at end of file
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/clang-analyzer-soft-deprecation.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/clang-analyzer-soft-deprecation.cpp
new file mode 100644
index 0000000000000..1a2a7a65f6a35
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/clang-analyzer-soft-deprecation.cpp
@@ -0,0 +1,35 @@
+// This test verifies the soft deprecation warning for clang-analyzer-* checks
+// which are no longer enabled by default.
+
+// Test that warning appears with wildcard patterns that would have included clang-analyzer-*
+// RUN: clang-tidy -checks='*' %s -- 2>&1 | FileCheck %s --check-prefix=CHECK-WILDCARD-WARNING
+// RUN: clang-tidy -checks='*,readability-*' %s -- 2>&1 | FileCheck %s --check-prefix=CHECK-WILDCARD-WARNING
+// RUN: clang-tidy -config-file=%S/Inputs/clang-analyzer-soft-deprecation/config-with-wildcard %s -- 2>&1 | FileCheck %s --check-prefix=CHECK-WILDCARD-WARNING
+
+// Test that warning does not appear when clang-analyzer-* is explicitly mentioned
+// RUN: clang-tidy -checks=-clang-analyzer-* %s -- 2>&1 | FileCheck %s --check-prefix=CHECK-EXPLICIT-EXCLUSION --allow-empty
+// RUN: clang-tidy -checks=clang-analyzer-* %s -- 2>&1 | FileCheck %s --check-prefix=CHECK-EXPLICIT-INCLUSION --allow-empty
+// RUN: clang-tidy -checks='*,clang-analyzer-*' %s -- 2>&1 | FileCheck %s --check-prefix=CHECK-EXPLICIT-INCLUSION --allow-empty
+// RUN: clang-tidy -checks='*,-clang-analyzer-*' %s -- 2>&1 | FileCheck %s --check-prefix=CHECK-EXPLICIT-EXCLUSION --allow-empty
+// RUN: clang-tidy -config-file=%S/Inputs/clang-analyzer-soft-deprecation/config-with-exclusion %s -- 2>&1 | FileCheck %s --check-prefix=CHECK-CONFIG-EXCLUSION --allow-empty
+// RUN: clang-tidy -config-file=%S/Inputs/clang-analyzer-soft-deprecation/config-with-inclusion %s -- 2>&1 | FileCheck %s --check-prefix=CHECK-CONFIG-INCLUSION --allow-empty
+
+// Test that warning does not appear in quiet mode
+// RUN: clang-tidy --quiet -checks='*' %s -- 2>&1 | FileCheck %s --check-prefix=CHECK-QUIET --allow-empty
+
+// Test that warning does not appear with specific checks (not wildcards)
+// RUN: clang-tidy -checks='readability-magic-numbers' %s -- 2>&1 | FileCheck %s --check-prefix=CHECK-SPECIFIC-CHECKS --allow-empty
+
+// CHECK-WILDCARD-WARNING: warning: clang-analyzer-* checks are no longer enabled by default.
+// CHECK-WILDCARD-WARNING-NEXT: If you want to enable clang-analyzer-* checks, use -checks='*,clang-analyzer-*' or add 'clang-analyzer-*' to your .clang-tidy configuration file.
+
+// CHECK-EXPLICIT-EXCLUSION-NOT: clang-analyzer-* checks are no longer enabled by default
+// CHECK-EXPLICIT-INCLUSION-NOT: clang-analyzer-* checks are no longer enabled by default
+// CHECK-CONFIG-EXCLUSION-NOT: clang-analyzer-* checks are no longer enabled by default
+// CHECK-CONFIG-INCLUSION-NOT: clang-analyzer-* checks are no longer enabled by default
+// CHECK-QUIET-NOT: clang-analyzer-* checks are no longer enabled by default
+// CHECK-SPECIFIC-CHECKS-NOT: clang-analyzer-* checks are no longer enabled by default
+
+int main() {
+    return 0;
+}
\ No newline at end of file
diff --git a/clang-tools-extra/test_here.cpp b/clang-tools-extra/test_here.cpp
new file mode 100644
index 0000000000000..76e8197013aab
--- /dev/null
+++ b/clang-tools-extra/test_here.cpp
@@ -0,0 +1 @@
+int main() { return 0; }
diff --git a/test_warning.cpp b/test_warning.cpp
new file mode 100644
index 0000000000000..2846e71306b39
--- /dev/null
+++ b/test_warning.cpp
@@ -0,0 +1,4 @@
+// Simple test file for testing clang-tidy soft deprecation warning
+int main() {
+    return 0;
+}
\ No newline at end of file



More information about the llvm-commits mailing list