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 @@ -7,6 +7,7 @@ TBA

* Fixed a whitespace/newline false positive for control conditions containing lambdas. (#410)
* We now error on relative include paths (``./``, ``../``). (#432)
* For C files, build/include_what_you_use now suggests the C header (e.g. ``<stdio.h>``) instead of its C++ counterpart (e.g. ``<cstdio>``). (#399)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* This makes ``#include "./foo.h"`` produce two separate errors: that foo.cpp should include foo.h and that relative paths are not allowed.

2.0.2 (2025-04-08)
Expand Down
61 changes: 55 additions & 6 deletions cpplint.py
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,45 @@
r"vim?:\s*.*(\s*|:)filetype=c(\s*|:|$))"
)

# Maps the C++ C-library headers to their C equivalents so that, for C files,
# build/include_what_you_use suggests the C header (e.g. <stdio.h> rather than
# <cstdio>). See https://github.com/cpplint/cpplint/issues/399.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# <cstdio>). See https://github.com/cpplint/cpplint/issues/399.
# <cstdio>).

_C_LIBRARY_CPP_HEADERS = {
"<cassert>": "<assert.h>",
"<cctype>": "<ctype.h>",
"<cerrno>": "<errno.h>",
"<cfenv>": "<fenv.h>",
"<cfloat>": "<float.h>",
"<cinttypes>": "<inttypes.h>",
"<climits>": "<limits.h>",
"<clocale>": "<locale.h>",
"<cmath>": "<math.h>",
"<csetjmp>": "<setjmp.h>",
"<csignal>": "<signal.h>",
"<cstdarg>": "<stdarg.h>",
"<cstddef>": "<stddef.h>",
"<cstdint>": "<stdint.h>",
"<cstdio>": "<stdio.h>",
"<cstdlib>": "<stdlib.h>",
"<cstring>": "<string.h>",
"<ctime>": "<time.h>",
"<cuchar>": "<uchar.h>",
"<cwchar>": "<wchar.h>",
"<cwctype>": "<wctype.h>",
}


def _IsCFile(filename: str, lines: list[str]) -> bool:
"""Whether the file is a C file: a .c/.cu extension or a LINT_C_FILE marker.

Single source of truth shared by the C-specific error suppression and the
C-vs-C++ header suggestion, so the two never drift. See #399.
"""
return filename.lower().endswith((".c", ".cu")) or any(
_SEARCH_C_FILE.search(line) for line in lines
)


# Match string that indicates we're working on a Linux Kernel file.
_SEARCH_KERNEL_FILE = re.compile(r"\b(?:LINT_KERNEL_FILE)")

Expand Down Expand Up @@ -1206,10 +1245,10 @@ def ProcessGlobalSuppressions(filename: str, lines: list[str]) -> None:
last element being empty if the file is terminated with a newline.
filename: str, the name of the input file.
"""
if _IsCFile(filename, lines):
for category in _DEFAULT_C_SUPPRESSED_CATEGORIES:
_error_suppressions.AddGlobalSuppression(category)
for line in lines:
if _SEARCH_C_FILE.search(line) or filename.lower().endswith((".c", ".cu")):
for category in _DEFAULT_C_SUPPRESSED_CATEGORIES:
_error_suppressions.AddGlobalSuppression(category)
if _SEARCH_KERNEL_FILE.search(line):
for category in _DEFAULT_KERNEL_SUPPRESSED_CATEGORIES:
_error_suppressions.AddGlobalSuppression(category)
Expand Down Expand Up @@ -7119,7 +7158,9 @@ def FilesBelongToSameModule(filename_cc, filename_h):
return files_belong_to_same_module, common_path


def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, io=codecs):
def CheckForIncludeWhatYouUse(
filename, clean_lines, include_state, error, io=codecs, is_c_file=False
):
"""Reports for missing stl includes.

This function will output warnings to make sure you are including the headers
Expand Down Expand Up @@ -7183,12 +7224,19 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, io=co
if header_stripped not in include_dict and not (
header_stripped[0] == "c" and (header_stripped[1:] + ".h") in include_dict
):
# For C files, suggest the C header (e.g. <stdio.h>) rather than
# the C++ one (e.g. <cstdio>). See #399.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# the C++ one (e.g. <cstdio>). See #399.
# the C++ one (e.g. <cstdio>).

suggested = (
_C_LIBRARY_CPP_HEADERS[header]
if is_c_file and header in _C_LIBRARY_CPP_HEADERS
else header
)
error(
filename,
required[header][0],
"build/include_what_you_use",
4,
"Add #include " + header + " for " + template,
"Add #include " + suggested + " for " + template,
)


Expand Down Expand Up @@ -7578,7 +7626,8 @@ def ProcessFileData(filename, file_extension, lines, error, extra_check_function
"NONLINT block never ended",
)

CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error)
is_c_file = _IsCFile(filename, clean_lines.raw_lines)
CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, is_c_file=is_c_file)

# Check that the .cc file has included its header if it exists.
if _IsSourceExtension(file_extension):
Expand Down
32 changes: 28 additions & 4 deletions cpplint_unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ def PerformIncludeWhatYouUse(self, code, filename="foo.h", io=codecs):
error_collector = ErrorCollector(self.assertTrue)
include_state = cpplint._IncludeState()
nesting_state = cpplint.NestingState()
lines = code.split("\n")
raw_lines = code.split("\n")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the point of making a new variable here?

lines = raw_lines
Comment on lines +226 to +227

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep raw_lines independent from lines.

lines = raw_lines aliases the same list. RemoveMultiLineComments() then mutates raw_lines before _IsCFile() reads it. This helper cannot reproduce detection of a LINT_C_FILE marker in a multi-line comment.

Proposed fix
 raw_lines = code.split("\n")
-lines = raw_lines
+lines = raw_lines[:]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
raw_lines = code.split("\n")
lines = raw_lines
raw_lines = code.split("\n")
lines = raw_lines[:]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpplint_unittest.py` around lines 226 - 227, Update the setup in the affected
test helper so lines is an independent copy of raw_lines rather than an alias.
Preserve raw_lines unchanged for _IsCFile() after RemoveMultiLineComments()
mutates lines, ensuring LINT_C_FILE markers inside multi-line comments are
detected.

cpplint.RemoveMultiLineComments(filename, lines, error_collector)
lines = cpplint.CleansedLines(lines)
for i in range(lines.NumLines()):
Expand All @@ -235,7 +236,10 @@ def PerformIncludeWhatYouUse(self, code, filename="foo.h", io=codecs):
# have language problems.

# Second, look for missing includes.
cpplint.CheckForIncludeWhatYouUse(filename, lines, include_state, error_collector, io)
is_c_file = cpplint._IsCFile(filename, raw_lines)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means we still run this twice for each file, which was exactly what we were trying to avoid with the move to state.

cpplint.CheckForIncludeWhatYouUse(
filename, lines, include_state, error_collector, io, is_c_file=is_c_file
)
return error_collector.Results()

# Perform lint and make sure one of the errors is what we want
Expand All @@ -259,8 +263,8 @@ def TestMultiLineLintRE(self, code, expected_message_re):
def TestLanguageRulesCheck(self, file_name, code, expected_message):
assert expected_message == self.PerformLanguageRulesCheck(file_name, code)

def TestIncludeWhatYouUse(self, code, expected_message):
assert expected_message == self.PerformIncludeWhatYouUse(code)
def TestIncludeWhatYouUse(self, code, expected_message, filename="foo.h"):
assert expected_message == self.PerformIncludeWhatYouUse(code, filename=filename)

def TestBlankLinesCheck(self, lines, start_errors, end_errors):
for extension in ["c", "cc", "cpp", "cxx", "c++", "cu"]:
Expand Down Expand Up @@ -1290,6 +1294,26 @@ def testIncludeWhatYouUse(self):
printf("hello world");""",
"",
) # Avoid false positives w/ c-style include
# C files should be told to include the C header (e.g. <stdio.h>) rather
# than its C++ counterpart (e.g. <cstdio>). See #399.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# than its C++ counterpart (e.g. <cstdio>). See #399.
# than the C++ counterpart (e.g. <cstdio>).

self.TestIncludeWhatYouUse(
'printf("hello world");',
"Add #include <stdio.h> for printf [build/include_what_you_use] [4]",
filename="foo.c",
)
# C++ files are unaffected.
self.TestIncludeWhatYouUse(
'printf("hello world");',
"Add #include <cstdio> for printf [build/include_what_you_use] [4]",
filename="foo.cpp",
)
# The "C header already included" behaviour is preserved for C files.
self.TestIncludeWhatYouUse(
"""#include <stdio.h>
printf("hello world");""",
"",
filename="foo.c",
)
Comment thread
aaronliu0130 marked this conversation as resolved.
Comment on lines +1310 to +1316

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already tested

self.TestIncludeWhatYouUse(
"void a(const string &foobar);",
"Add #include <string> for string [build/include_what_you_use] [4]",
Expand Down