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
===

* Added a ``--output=github`` format that emits GitHub Actions warning annotations. (#419)
* 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
42 changes: 35 additions & 7 deletions cpplint.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
__VERSION__ = "2.0.3-dev0"

_USAGE = """
Syntax: cpplint.py [--verbose=#] [--output=emacs|eclipse|vs7|junit|sed|gsed]
Syntax: cpplint.py [--verbose=#] [--output=emacs|eclipse|vs7|github|junit|sed|gsed]
[--filter=-x,+y,...]
[--counting=total|toplevel|detailed] [--root=subdir]
[--repository=path]
Expand Down Expand Up @@ -103,11 +103,12 @@

Flags:

output=emacs|eclipse|vs7|junit|sed|gsed
output=emacs|eclipse|vs7|github|junit|sed|gsed
By default, the output is formatted to ease emacs parsing. Visual Studio
compatible output (vs7) may also be used. Further support exists for
eclipse (eclipse), and JUnit (junit). XML parsers such as those used
in Jenkins and Bamboo may also be used.
eclipse (eclipse), GitHub Actions annotations (github), and JUnit (junit).
XML parsers such as those used in Jenkins and Bamboo may also be used.
The github format writes workflow-command warning annotations to stdout.
The sed format outputs sed commands that should fix some of the errors.
Note that this requires gnu sed. If that is installed as gsed on your
system (common e.g. on macOS with homebrew) you can use the gsed output
Expand Down Expand Up @@ -372,7 +373,7 @@
]

# keywords to use with --outputs which generate stdout for machine processing
_MACHINE_OUTPUTS = ["junit", "sed", "gsed"]
_MACHINE_OUTPUTS = ["github", "junit", "sed", "gsed"]

# These error categories are no longer enforced by cpplint, but for backwards-
# compatibility they may still appear in NOLINT comments.
Expand Down Expand Up @@ -1437,6 +1438,7 @@ def __init__(self):
# "eclipse" - format that eclipse can parse
# "vs7" - format that Microsoft Visual Studio 7 can parse
# "junit" - format that Jenkins, Bamboo, etc can parse
# "github" - GitHub Actions workflow-command annotations
# "sed" - returns a gnu sed command to fix the problem
# "gsed" - like sed, but names the command gsed, e.g. for macOS homebrew users
self.output_format = "emacs"
Expand Down Expand Up @@ -1878,6 +1880,16 @@ def _ShouldPrintError(category, confidence, filename, linenum):
return not is_filtered


def _EscapeGithubCommandData(value):
"""Escapes workflow-command message data for GitHub Actions."""
return str(value).replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")


def _EscapeGithubCommandProperty(value):
"""Escapes a workflow-command property value for GitHub Actions."""
return _EscapeGithubCommandData(value).replace(":", "%3A").replace(",", "%2C")


def Error(filename, linenum, category, confidence, message):
"""Logs the fact we've found a lint error.

Expand Down Expand Up @@ -1910,6 +1922,13 @@ def Error(filename, linenum, category, confidence, message):
sys.stderr.write(
f"{filename}:{linenum}: warning: {message} [{category}] [{confidence}]\n"
)
elif _cpplint_state.output_format == "github":
properties = f"file={_EscapeGithubCommandProperty(filename)}"
if linenum > 0:
properties += f",line={linenum}"
properties += ",title=cpplint"
data = _EscapeGithubCommandData(f"{message} [{category}] [{confidence}]")
sys.stdout.write(f"::warning {properties}::{data}\n")
elif _cpplint_state.output_format == "junit":
_cpplint_state.AddJUnitFailure(filename, linenum, message, category, confidence)
elif _cpplint_state.output_format in ["sed", "gsed"]:
Expand Down Expand Up @@ -7831,9 +7850,18 @@ def ParseArguments(args):
if opt == "--version":
PrintVersion()
elif opt == "--output":
if val not in ("emacs", "vs7", "eclipse", "junit", "sed", "gsed"):
if val not in (
"emacs",
"vs7",
"eclipse",
"github",
"junit",
"sed",
"gsed",
):
PrintUsage(
"The only allowed output formats are emacs, vs7, eclipse sed, gsed and junit."
"The only allowed output formats are emacs, vs7, eclipse, github, "
"sed, gsed and junit."
)
output_format = val
elif opt == "--quiet":
Expand Down
15 changes: 15 additions & 0 deletions cpplint_clitest.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,5 +271,20 @@ def test_third_party_headers_config(tmp_path):
assert b"build/include_subdir" in err


def test_github_output(tmp_path):
cpp = tmp_path / "test.cpp"
cpp.write_text("// Copyright 2026 cpplint\nint main() {\n return 0; \n}\n")

status, out, err = run_shell_command(BASE_CMD, "--output=github test.cpp", cwd=str(tmp_path))

assert status == 1
assert err == b""
assert out == (
b"::warning file=test.cpp,line=3,title=cpplint::"
b"Line ends in whitespace. Consider deleting these extra spaces. "
b"[whitespace/end_of_line] [4]\n"
)


if __name__ == "__main__":
pytest.main([__file__])
5 changes: 5 additions & 0 deletions cpplint_unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7414,5 +7414,10 @@ def run_around_tests(pytestconfig: pytest.Config):
ErrorCollector(None).VerifyAllCategoriesAreSeen()


def test_github_command_escaping():
assert cpplint._EscapeGithubCommandData("100%\r\n") == "100%25%0D%0A"
assert cpplint._EscapeGithubCommandProperty("a,b:c") == "a%2Cb%3Ac"


if __name__ == "__main__":
pytest.main([__file__])