diff --git a/cpplint.py b/cpplint.py index 24897da..ad942ba 100755 --- a/cpplint.py +++ b/cpplint.py @@ -68,7 +68,7 @@ [--filter=-x,+y,...] [--counting=total|toplevel|detailed] [--root=subdir] [--repository=path] - [--linelength=digits] [--headers=x,y,...] + [--linelength=digits] [--headers=x,y,...] [--third_party_headers=pattern] [--recursive] [--exclude=path] [--extensions=hpp,cpp,...] @@ -240,6 +240,8 @@ The header extensions that cpplint will treat as .h in checks. Values are automatically added to --extensions list. (by default, only files with extensions %s will be assumed to be headers) + third_party_headers=pattern + Regex for identifying third-party headers to exclude from include checks. Examples: --headers=%s @@ -256,6 +258,7 @@ linelength=80 root=subdir headers=x,y,... + third_party_headers=pattern "set noparent" option prevents cpplint from traversing directory tree upwards looking for more .cfg files in parent directories. This option @@ -812,14 +815,6 @@ r")$" ) - -# These headers are excluded from [build/include] and [build/include_order] -# checks: -# - Anything not following google file name conventions (containing an -# uppercase character, such as Python.h or nsStringAPI.h, for example). -# - Lua headers. -_THIRD_PARTY_HEADERS_PATTERN = re.compile(r"^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$") - # Pattern for matching FileInfo.BaseName() against test file name _test_suffixes = ["_test", "_regtest", "_unittest"] _TEST_FILE_SUFFIX = "(" + "|".join(_test_suffixes) + r")$" @@ -981,6 +976,16 @@ # This is set by --headers flag. _hpp_headers: set[str] = set() +# These headers are excluded from [build/include_subdir], [build/include_order], and +# [build/include_alpha] +# The default checks are following +# - Anything not following google file name conventions (containing an +# uppercase character, such as Python.h or nsStringAPI.h, for example). +# - Lua headers. +# Default pattern for third-party headers (uppercase .h or Lua headers). +_THIRD_PARTY_HEADERS_DEFAULT = r"^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$" +_third_party_headers_pattern = re.compile(_THIRD_PARTY_HEADERS_DEFAULT) + class ErrorSuppressions: """Class to track all error suppressions for cpplint""" @@ -1072,6 +1077,15 @@ def ProcessIncludeOrderOption(val): PrintUsage("Invalid includeorder value %s. Expected default|standardcfirst") +def ProcessThirdPartyHeadersOption(val): + """Sets the regex pattern for third-party headers.""" + global _third_party_headers_pattern + try: + _third_party_headers_pattern = re.compile(val) + except re.error: + PrintUsage(f"Invalid third_party_headers pattern: {val}") + + def IsHeaderExtension(file_extension): return file_extension in GetHeaderExtensions() @@ -5744,7 +5758,7 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): if ( match and IsHeaderExtension(match.group(2)) - and not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1)) + and not _third_party_headers_pattern.match(match.group(1)) ): error( filename, @@ -5797,7 +5811,7 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): third_src_header = True break - if third_src_header or not _THIRD_PARTY_HEADERS_PATTERN.match(include): + if third_src_header or not _third_party_headers_pattern.match(include): include_state.include_list[-1].append((include, linenum)) # We want to ensure that headers appear in the right order: @@ -7527,6 +7541,8 @@ def ProcessConfigOverrides(filename): _root = os.path.join(os.path.dirname(cfg_file), val) elif name == "headers": ProcessHppHeadersOption(val) + elif name == "third_party_headers": + ProcessThirdPartyHeadersOption(val) elif name == "includeorder": ProcessIncludeOrderOption(val) else: @@ -7711,6 +7727,7 @@ def ParseArguments(args): "exclude=", "recursive", "headers=", + "third_party_headers=", "includeorder=", "config=", "quiet", @@ -7770,6 +7787,8 @@ def ParseArguments(args): ProcessExtensionsOption(val) elif opt == "--headers": ProcessHppHeadersOption(val) + elif opt == "--third_party_headers": + ProcessThirdPartyHeadersOption(val) elif opt == "--recursive": recursive = True elif opt == "--includeorder": diff --git a/cpplint_clitest.py b/cpplint_clitest.py index 7cb2047..165e192 100755 --- a/cpplint_clitest.py +++ b/cpplint_clitest.py @@ -37,6 +37,7 @@ import subprocess import sys import tempfile +import textwrap import pytest from testfixtures import compare # type: ignore[import-untyped] @@ -224,5 +225,57 @@ def test_codelite_sample(self): self.check_all_in_folder("./samples/codelite-sample", 1) +# Tests for third_party_headers option +def test_third_party_headers_default(tmp_path): + # By default, headers with uppercase letters are treated as third-party and not flagged + cpp = tmp_path / "test.cpp" + cpp.write_text( + textwrap.dedent(""" + // Copyright 2025 cpplint + #include "Foo.h" + int main() { return 0; } + """) + ) + status, out, err = run_shell_command(BASE_CMD, f"{cpp.name}", cwd=str(tmp_path)) + assert status == 0, f"stdout\n{out.decode('utf-8')}\nstderr\n{err.decode('utf-8')}" + # No include_subdir warning + assert b"build/include_subdir" not in err + + +def test_third_party_headers_override(tmp_path): + # Override third_party_headers so Foo.h is not recognized as third-party + cpp = tmp_path / "test.cpp" + cpp.write_text( + textwrap.dedent(""" + // Copyright 2025 cpplint + #include "Foo.h" + """) + ) + # Use a pattern that matches nothing + flag = "--third_party_headers=^Bar.h$" + status, out, err = run_shell_command(BASE_CMD, f"{flag} {cpp.name}", cwd=str(tmp_path)) + # Expect a warning about include_subdir + assert status == 1, f"stdout\n{out.decode('utf-8')}\nstderr\n{err.decode('utf-8')}" + assert b"build/include_subdir" in err + + +def test_third_party_headers_config(tmp_path): + # Override third_party_headers via config file so Foo.h is not recognized as third-party + cpp = tmp_path / "test.cpp" + cpp.write_text( + textwrap.dedent(""" + // Copyright 2025 cpplint + #include "Foo.h" + """) + ) + # Write configuration file to override third_party_headers + config = tmp_path / "CPPLINT.cfg" + config.write_text("third_party_headers=^Bar.h$\n") + status, out, err = run_shell_command(BASE_CMD, f"{cpp.name}", cwd=str(tmp_path)) + # Expect a warning about include_subdir due to override + assert status == 1, f"stdout\n{out.decode('utf-8')}\nstderr\n{err.decode('utf-8')}" + assert b"build/include_subdir" in err + + if __name__ == "__main__": pytest.main([__file__])