Skip to content

Commit 2eef70d

Browse files
authored
Allow specifying _third_party_headers_pattern (#388)
1 parent c2b1ce6 commit 2eef70d

2 files changed

Lines changed: 83 additions & 11 deletions

File tree

cpplint.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
[--filter=-x,+y,...]
6969
[--counting=total|toplevel|detailed] [--root=subdir]
7070
[--repository=path]
71-
[--linelength=digits] [--headers=x,y,...]
71+
[--linelength=digits] [--headers=x,y,...] [--third_party_headers=pattern]
7272
[--recursive]
7373
[--exclude=path]
7474
[--extensions=hpp,cpp,...]
@@ -240,6 +240,8 @@
240240
The header extensions that cpplint will treat as .h in checks. Values are
241241
automatically added to --extensions list.
242242
(by default, only files with extensions %s will be assumed to be headers)
243+
third_party_headers=pattern
244+
Regex for identifying third-party headers to exclude from include checks.
243245
244246
Examples:
245247
--headers=%s
@@ -256,6 +258,7 @@
256258
linelength=80
257259
root=subdir
258260
headers=x,y,...
261+
third_party_headers=pattern
259262
260263
"set noparent" option prevents cpplint from traversing directory tree
261264
upwards looking for more .cfg files in parent directories. This option
@@ -812,14 +815,6 @@
812815
r")$"
813816
)
814817

815-
816-
# These headers are excluded from [build/include] and [build/include_order]
817-
# checks:
818-
# - Anything not following google file name conventions (containing an
819-
# uppercase character, such as Python.h or nsStringAPI.h, for example).
820-
# - Lua headers.
821-
_THIRD_PARTY_HEADERS_PATTERN = re.compile(r"^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$")
822-
823818
# Pattern for matching FileInfo.BaseName() against test file name
824819
_test_suffixes = ["_test", "_regtest", "_unittest"]
825820
_TEST_FILE_SUFFIX = "(" + "|".join(_test_suffixes) + r")$"
@@ -981,6 +976,16 @@
981976
# This is set by --headers flag.
982977
_hpp_headers: set[str] = set()
983978

979+
# These headers are excluded from [build/include_subdir], [build/include_order], and
980+
# [build/include_alpha]
981+
# The default checks are following
982+
# - Anything not following google file name conventions (containing an
983+
# uppercase character, such as Python.h or nsStringAPI.h, for example).
984+
# - Lua headers.
985+
# Default pattern for third-party headers (uppercase .h or Lua headers).
986+
_THIRD_PARTY_HEADERS_DEFAULT = r"^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$"
987+
_third_party_headers_pattern = re.compile(_THIRD_PARTY_HEADERS_DEFAULT)
988+
984989

985990
class ErrorSuppressions:
986991
"""Class to track all error suppressions for cpplint"""
@@ -1072,6 +1077,15 @@ def ProcessIncludeOrderOption(val):
10721077
PrintUsage("Invalid includeorder value %s. Expected default|standardcfirst")
10731078

10741079

1080+
def ProcessThirdPartyHeadersOption(val):
1081+
"""Sets the regex pattern for third-party headers."""
1082+
global _third_party_headers_pattern
1083+
try:
1084+
_third_party_headers_pattern = re.compile(val)
1085+
except re.error:
1086+
PrintUsage(f"Invalid third_party_headers pattern: {val}")
1087+
1088+
10751089
def IsHeaderExtension(file_extension):
10761090
return file_extension in GetHeaderExtensions()
10771091

@@ -5744,7 +5758,7 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error):
57445758
if (
57455759
match
57465760
and IsHeaderExtension(match.group(2))
5747-
and not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1))
5761+
and not _third_party_headers_pattern.match(match.group(1))
57485762
):
57495763
error(
57505764
filename,
@@ -5797,7 +5811,7 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error):
57975811
third_src_header = True
57985812
break
57995813

5800-
if third_src_header or not _THIRD_PARTY_HEADERS_PATTERN.match(include):
5814+
if third_src_header or not _third_party_headers_pattern.match(include):
58015815
include_state.include_list[-1].append((include, linenum))
58025816

58035817
# We want to ensure that headers appear in the right order:
@@ -7527,6 +7541,8 @@ def ProcessConfigOverrides(filename):
75277541
_root = os.path.join(os.path.dirname(cfg_file), val)
75287542
elif name == "headers":
75297543
ProcessHppHeadersOption(val)
7544+
elif name == "third_party_headers":
7545+
ProcessThirdPartyHeadersOption(val)
75307546
elif name == "includeorder":
75317547
ProcessIncludeOrderOption(val)
75327548
else:
@@ -7711,6 +7727,7 @@ def ParseArguments(args):
77117727
"exclude=",
77127728
"recursive",
77137729
"headers=",
7730+
"third_party_headers=",
77147731
"includeorder=",
77157732
"config=",
77167733
"quiet",
@@ -7770,6 +7787,8 @@ def ParseArguments(args):
77707787
ProcessExtensionsOption(val)
77717788
elif opt == "--headers":
77727789
ProcessHppHeadersOption(val)
7790+
elif opt == "--third_party_headers":
7791+
ProcessThirdPartyHeadersOption(val)
77737792
elif opt == "--recursive":
77747793
recursive = True
77757794
elif opt == "--includeorder":

cpplint_clitest.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import subprocess
3838
import sys
3939
import tempfile
40+
import textwrap
4041

4142
import pytest
4243
from testfixtures import compare # type: ignore[import-untyped]
@@ -224,5 +225,57 @@ def test_codelite_sample(self):
224225
self.check_all_in_folder("./samples/codelite-sample", 1)
225226

226227

228+
# Tests for third_party_headers option
229+
def test_third_party_headers_default(tmp_path):
230+
# By default, headers with uppercase letters are treated as third-party and not flagged
231+
cpp = tmp_path / "test.cpp"
232+
cpp.write_text(
233+
textwrap.dedent("""
234+
// Copyright 2025 cpplint
235+
#include "Foo.h"
236+
int main() { return 0; }
237+
""")
238+
)
239+
status, out, err = run_shell_command(BASE_CMD, f"{cpp.name}", cwd=str(tmp_path))
240+
assert status == 0, f"stdout\n{out.decode('utf-8')}\nstderr\n{err.decode('utf-8')}"
241+
# No include_subdir warning
242+
assert b"build/include_subdir" not in err
243+
244+
245+
def test_third_party_headers_override(tmp_path):
246+
# Override third_party_headers so Foo.h is not recognized as third-party
247+
cpp = tmp_path / "test.cpp"
248+
cpp.write_text(
249+
textwrap.dedent("""
250+
// Copyright 2025 cpplint
251+
#include "Foo.h"
252+
""")
253+
)
254+
# Use a pattern that matches nothing
255+
flag = "--third_party_headers=^Bar.h$"
256+
status, out, err = run_shell_command(BASE_CMD, f"{flag} {cpp.name}", cwd=str(tmp_path))
257+
# Expect a warning about include_subdir
258+
assert status == 1, f"stdout\n{out.decode('utf-8')}\nstderr\n{err.decode('utf-8')}"
259+
assert b"build/include_subdir" in err
260+
261+
262+
def test_third_party_headers_config(tmp_path):
263+
# Override third_party_headers via config file so Foo.h is not recognized as third-party
264+
cpp = tmp_path / "test.cpp"
265+
cpp.write_text(
266+
textwrap.dedent("""
267+
// Copyright 2025 cpplint
268+
#include "Foo.h"
269+
""")
270+
)
271+
# Write configuration file to override third_party_headers
272+
config = tmp_path / "CPPLINT.cfg"
273+
config.write_text("third_party_headers=^Bar.h$\n")
274+
status, out, err = run_shell_command(BASE_CMD, f"{cpp.name}", cwd=str(tmp_path))
275+
# Expect a warning about include_subdir due to override
276+
assert status == 1, f"stdout\n{out.decode('utf-8')}\nstderr\n{err.decode('utf-8')}"
277+
assert b"build/include_subdir" in err
278+
279+
227280
if __name__ == "__main__":
228281
pytest.main([__file__])

0 commit comments

Comments
 (0)