Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Changelog
TBA
===

* ``exclude_files`` patterns now match paths relative to their ``CPPLINT.cfg`` instead of only the next path component. (#254)
* Fixed a whitespace/newline false positive for control conditions containing lambdas. (#410)
* We now error on relative include paths (``./``, ``../``). (#432)
* This makes ``#include "./foo.h"`` produce two separate errors: that foo.cpp should include foo.h and that relative paths are not allowed.
Expand Down
36 changes: 18 additions & 18 deletions cpplint.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,10 @@
through --filter command-line flag.

"exclude_files" allows to specify a regular expression to be matched against
a file name. If the expression matches, the file is skipped and not run
through the linter.
the file path relative to the CPPLINT.cfg directory. Paths use "/" as the
separator on all platforms. If the expression matches, the file is skipped
and not run through the linter. To exclude a directory subtree, use a prefix
pattern such as "^bar/" rather than an anchored directory pattern like "^bar$".

"linelength" allows to specify the allowed line length for the project.

Expand Down Expand Up @@ -7602,12 +7604,14 @@ def ProcessConfigOverrides(filename):
"""

abs_filename = os.path.abspath(filename)
relative_filename = ""
cfg_filters = []
keep_looking = True
while keep_looking:
abs_path, base_name = os.path.split(abs_filename)
if not base_name:
break # Reached the root directory.
relative_filename = f"{base_name}/{relative_filename}" if relative_filename else base_name

cfg_file = os.path.join(abs_path, _config_filename)
abs_filename = abs_path
Expand All @@ -7629,24 +7633,20 @@ def ProcessConfigOverrides(filename):
elif name == "filter":
cfg_filters.append(val)
elif name == "exclude_files":
# When matching exclude_files pattern, use the base_name of
# the current file name or the directory name we are processing.
# Match the target path relative to this configuration file.
# For example, if we are checking for lint errors in /foo/bar/baz.cc
# and we found the .cfg file at /foo/CPPLINT.cfg, then the config
# file's "exclude_files" filter is meant to be checked against "bar"
# and not "baz" nor "bar/baz.cc".
if base_name:
pattern = re.compile(val)
if pattern.match(base_name):
if _cpplint_state.quiet:
# Suppress "Ignoring file" warning when using --quiet.
return False
_cpplint_state.PrintInfo(
f'Ignoring "{filename}": file excluded by "{cfg_file}". '
'File path component "%s" matches '
'pattern "%s"\n' % (base_name, val)
)
# and we found the .cfg file at /foo/CPPLINT.cfg, match against
# "bar/baz.cc". Use forward slashes consistently across platforms.
pattern = re.compile(val)
if pattern.match(relative_filename):
if _cpplint_state.quiet:
# Suppress "Ignoring file" warning when using --quiet.
return False
_cpplint_state.PrintInfo(
f'Ignoring "{filename}": file excluded by "{cfg_file}". '
'File path "%s" matches pattern "%s"\n' % (relative_filename, val)
)
return False
elif name == "linelength":
global _line_length
try:
Expand Down
57 changes: 57 additions & 0 deletions cpplint_unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7286,6 +7286,63 @@ def testInlineAssembly(self):
assert len(self.nesting_state.stack) == 0


class TestConfigOverrides:
@staticmethod
def _write_file(path, content=""):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")

@pytest.mark.parametrize(
("relative_path", "pattern", "should_process"),
[
("baz.cc", "^baz[.]cc$", False),
("bar/baz.cc", "^bar/baz[.]cc$", False),
("bar/qux.cc", "^bar/baz[.]cc$", True),
("bar/baz.cc", "^bar$", True),
("bar/baz.cc", "^bar/", False),
],
)
def test_exclude_files_matches_relative_path(
self, tmp_path, relative_path, pattern, should_process
):
source = tmp_path.joinpath(*relative_path.split("/"))
self._write_file(source)
self._write_file(
tmp_path / "CPPLINT.cfg",
f"exclude_files={pattern}\n",
)

assert cpplint.ProcessConfigOverrides(str(source)) is should_process

def test_parent_exclude_applies_through_nested_config(self, tmp_path):
source = tmp_path / "deps" / "library" / "source.cc"
self._write_file(source)
self._write_file(
tmp_path / "CPPLINT.cfg",
"exclude_files=^deps/library/source[.]cc$\n",
)
self._write_file(
tmp_path / "deps" / "CPPLINT.cfg",
"filter=-legal/copyright\n",
)

assert not cpplint.ProcessConfigOverrides(str(source))

def test_noparent_stops_parent_exclude(self, tmp_path):
source = tmp_path / "deps" / "library" / "source.cc"
self._write_file(source)
self._write_file(
tmp_path / "CPPLINT.cfg",
"exclude_files=^deps/library/source[.]cc$\n",
)
self._write_file(
tmp_path / "deps" / "CPPLINT.cfg",
"set noparent\n",
)

assert cpplint.ProcessConfigOverrides(str(source))


class TestQuiet:
@pytest.fixture(autouse=True)
def setUp(self):
Expand Down