Repository: cpplint/cpplint
Pull request #355: All TODOs in Python code should have a username
Author: login: cclauss
Created: 2025-03-30T08:51:19Z | Merged: 2025-03-31T18:41:01Z

% `ruff rule TD002 ` # https://docs.astral.sh/ruff/rules/missing-todo-author


Diff:
diff --git a/cpplint.py b/cpplint.py
index 26707968..9a032f80 100755
--- a/cpplint.py
+++ b/cpplint.py
@@ -2004,7 +2004,7 @@ def CleanseRawStrings(raw_lines):
 
         lines_without_raw_strings.append(line)
 
-    # TODO(unknown): if delimiter is not None here, we might want to
+    # TODO(google): if delimiter is not None here, we might want to
     # emit a warning for unterminated string.
     return lines_without_raw_strings
 
@@ -2276,7 +2276,7 @@ def CloseExpression(clean_lines, linenum, pos):
     If lines[linenum][pos] points to a '(' or '{' or '[' or '<', finds the
     linenum/pos that correspond to the closing of the expression.
 
-    TODO(unknown): cpplint spends a fair bit of time matching parentheses.
+    TODO(google): cpplint spends a fair bit of time matching parentheses.
     Ideally we would want to index all opening and closing parentheses once
     and have CloseExpression be just a simple lookup, but due to preprocessor
     tricks, this is not so easy.
@@ -3115,7 +3115,7 @@ def CheckEnd(self, filename, clean_lines, linenum, error):
         # lines.  However, do apply checks if there is already an end of
         # namespace comment and it's incorrect.
         #
-        # TODO(unknown): We always want to check end of namespace comments
+        # TODO(google): We always want to check end of namespace comments
         # if a namespace is large, but sometimes we also want to apply the
         # check if a short namespace contained nontrivial things (something
         # other than forward declarations).  There is currently no logic on
@@ -3353,7 +3353,7 @@ def UpdatePreprocessor(self, line):
                 # Restore the stack to how it was before the #if
                 self.stack = copy.deepcopy(self.pp_stack[-1].stack_before_if)
             else:
-                # TODO(unknown): unexpected #else, issue warning?
+                # TODO(google): unexpected #else, issue warning?
                 pass
         elif re.match(r"^\s*#\s*endif\b", line):
             # End of #if or #else blocks.
@@ -3368,10 +3368,10 @@ def UpdatePreprocessor(self, line):
                 # Drop the corresponding #if
                 self.pp_stack.pop()
             else:
-                # TODO(unknown): unexpected #endif, issue warning?
+                # TODO(google): unexpected #endif, issue warning?
                 pass
 
-    # TODO(unknown): Update() is too long, but we will refactor later.
+    # TODO(google): Update() is too long, but we will refactor later.
     def Update(self, filename, clean_lines, linenum, error):
         """Update nesting state with current line.
 
@@ -3667,7 +3667,7 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, nesting_state,
         )
 
     if re.search(r"^\s*const\s*string\s*&\s*\w+\s*;", line):
-        # TODO(unknown): Could it be expanded safely to arbitrary references,
+        # TODO(google): Could it be expanded safely to arbitrary references,
         # without triggering too many false positives? The first
         # attempt triggered 5 warnings for mostly benign code in the regtest, hence
         # the restriction.
@@ -3845,7 +3845,7 @@ def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error):
             and not re.search(r"\w\s+\((\w+::)*\*\w+\)\(", fncall)
             and not re.search(r"\bcase\s+\(", fncall)
         ):
-            # TODO(unknown): Space after an operator function seem to be a common
+            # TODO(google): Space after an operator function seem to be a common
             # error, silence those for now by restricting them to highest verbosity.
             if re.search(r"\boperator_*\b", line):
                 error(
@@ -4096,7 +4096,7 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
         elided = clean_lines.elided
         prev_line = elided[linenum - 1]
         prevbrace = prev_line.rfind("{")
-        # TODO(unknown): Don't complain if line before blank line, and line after,
+        # TODO(google): Don't complain if line before blank line, and line after,
         #                both start with alnums and are indented the same amount.
         #                This ignores whitespace at the start of a namespace block
         #                because those are not usually indented.
@@ -4230,7 +4230,7 @@ def CheckOperatorSpacing(filename, clean_lines, linenum, error):
 
     # It's ok not to have spaces around binary operators like + - * /, but if
     # there's too little whitespace, we get concerned.  It's hard to tell,
-    # though, so we punt on this one for now.  TODO.
+    # though, so we punt on this one for now.  TODO(google).
 
     # You should always have whitespace around binary operators.
     #
@@ -4245,7 +4245,7 @@ def CheckOperatorSpacing(filename, clean_lines, linenum, error):
     # many false positives due to RValue references.
     match = re.search(r"[^<>=!\s](==|!=|<=|>=|\|\|)[^<>=!\s,;\)]", line)
     if match:
-        # TODO: support alternate operators
+        # TODO(google): support alternate operators
         error(
             filename, linenum, "whitespace/operators", 3, f"Missing spaces around {match.group(1)}"
         )
@@ -4392,7 +4392,7 @@ def CheckCommaSpacing(filename, clean_lines, linenum, error):
 
     # You should always have a space after a semicolon
     # except for few corner cases
-    # TODO(unknown): clarify if 'if (1) { return 1;}' is requires one more
+    # TODO(google): clarify if 'if (1) { return 1;}' is requires one more
     # space after ;
     if re.search(r";[^\s};\\)/]", line):
         error(filename, linenum, "whitespace/semicolon", 3, "Missing space after ;")
@@ -4767,7 +4767,7 @@ def CheckBraces(filename, clean_lines, linenum, error):
             " should be on a separate line",
         )
 
-    # TODO: Err on if...else and do...while statements without braces;
+    # TODO(aaronliu0130): Err on if...else and do...while statements without braces;
     # style guide has changed since the below comment was written
 
     # Check single-line if/else bodies. The style guide says 'curly braces are not
@@ -5288,7 +5288,7 @@ def CheckAltTokens(filename, clean_lines, linenum, error):
     # it provides a way to workaround this warning for people who use
     # multi-line comments in preprocessor macros.
     #
-    # TODO(unknown): remove this once cpplint has better support for
+    # TODO(google): remove this once cpplint has better support for
     # multi-line comments.
     if line.find("/*") >= 0 or line.find("*/") >= 0:
         return
@@ -5740,7 +5740,7 @@ def _GetTextInside(text, start_pattern):
       The extracted text.
       None if either the opening string or ending punctuation could not be found.
     """
-    # TODO(unknown): Audit cpplint.py to see what places could be profitably
+    # TODO(google): Audit cpplint.py to see what places could be profitably
     # rewritten to use _GetTextInside (and use inferior regexp matching today).
 
     # Give opening punctuation to get the matching close-punctuation.
@@ -5853,10 +5853,10 @@ def CheckLanguage(
     CheckPrintf(filename, clean_lines, linenum, error)
 
     if IsHeaderExtension(file_extension):
-        # TODO(unknown): check that 1-arg constructors are explicit.
+        # TODO(google): check that 1-arg constructors are explicit.
         #                How to tell it's a constructor?
         #                (handled in CheckForNonStandardConstructs for now)
-        # TODO(unknown): check that classes declare or disable copy/assign
+        # TODO(google): check that classes declare or disable copy/assign
         #                (level 1 error)
         pass
 
@@ -5879,7 +5879,7 @@ def CheckLanguage(
             )
 
     # Check if some verboten operator overloading is going on
-    # TODO(unknown): catch out-of-line unary operator&:
+    # TODO(google): catch out-of-line unary operator&:
     #   class X {};
     #   int operator&(const X& x) { return 42; }  // unary operator&
     # The trick is it's hard to tell apart from binary operator&:
@@ -5907,7 +5907,7 @@ def CheckLanguage(
     # Check for potential format string bugs like printf(foo).
     # We constrain the pattern not to pick things like DocidForPrintf(foo).
     # Not perfect but it can catch printf(foo.c_str()) and printf(foo->c_str())
-    # TODO(unknown): Catch the following case. Need to change the calling
+    # TODO(google): Catch the following case. Need to change the calling
     # convention of the whole function to process multiple line to handle it.
     #   printf(
     #       boy_this_is_a_really_long_variable_that_cannot_fit_on_the_prev_line);
@@ -6047,8 +6047,8 @@ def CheckGlobalStatic(filename, clean_lines, linenum, error):
     # This is dangerous because the C++ language does not guarantee that
     # globals with constructors are initialized before the first access, and
     # also because globals can be destroyed when some threads are still running.
-    # TODO(unknown): Generalize this to also find static unique_ptr instances.
-    # TODO(unknown): File bugs for clang-tidy to find these.
+    # TODO(google): Generalize this to also find static unique_ptr instances.
+    # TODO(google): File bugs for clang-tidy to find these.
     match = re.match(
         r"((?:|static +)(?:|const +))(?::*std::)?string( +const)? +"
         r"([a-zA-Z0-9_:]+)\b(.*)",
@@ -6303,7 +6303,7 @@ def CheckForNonConstReference(filename, clean_lines, linenum, nesting_state, err
     #   inside declarators: reference parameter
     # We will exclude the first two cases by checking that we are not inside a
     # function body, including one that was just introduced by a trailing '{'.
-    # TODO(unknown): Doesn't account for 'catch(Exception& e)' [rare].
+    # TODO(google): Doesn't account for 'catch(Exception& e)' [rare].
     if nesting_state.previous_stack_top and not (
         isinstance(nesting_state.previous_stack_top, (_ClassInfo, _NamespaceInfo))
     ):
@@ -7010,7 +7010,7 @@ def CheckRedundantVirtual(filename, clean_lines, linenum, error):
 
     # Look for the next opening parenthesis.  This is the start of the
     # parameter list (possibly on the next line shortly after virtual).
-    # TODO(unknown): doesn't work if there are virtual functions with
+    # TODO(google): doesn't work if there are virtual functions with
     # decltype() or other things that use parentheses, but csearch suggests
     # that this is rare.
     end_col = -1
@@ -7457,7 +7457,7 @@ def ProcessFile(filename, vlevel, extra_check_functions=None):
                 lf_lines.append(linenum + 1)
 
     except OSError:
-        # TODO: Maybe make this have an exit code of 2 after all is done
+        # TODO(aaronliu0130): Maybe make this have an exit code of 2 after all is done
         _cpplint_state.PrintError(f"Skipping input '{filename}': Can't open for reading\n")
         _RestoreFilters()
         return
diff --git a/cpplint_clitest.py b/cpplint_clitest.py
index 1ada03eb..26947129 100755
--- a/cpplint_clitest.py
+++ b/cpplint_clitest.py
@@ -63,7 +63,7 @@ def run_shell_command(cmd: str, args: str, cwd: str = ".") -> tuple[int, bytes,
 
     # Make output system-agnostic, aka support Windows
     if os.sep == "\\":
-        # TODO: Support scenario with multiple input names
+        # TODO(aaronliu0130): Support scenario with multiple input names
         # We currently only support the last arguments as the input name
         # to prevent accidentally replacing sed tests.
         # Fixing would likely need coding an internal "replace slashes" option for cpplint itself.
diff --git a/pyproject.toml b/pyproject.toml
index a701d4ca..f028050b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -105,6 +105,7 @@ lint.select = [
   "SLOT",  # flake8-slots
   "T10",   # flake8-debugger
   "TC",    # flake8-type-checking
+  "TD",    # flake8-todos
   "TID",   # flake8-tidy-imports
   "TRY",   # tryceratops
   "UP",    # pyupgrade
@@ -122,13 +123,13 @@ lint.select = [
   # "S",   # flake8-bandit
   # "SLF", # flake8-self
   # "T20", # flake8-print
-  # "TD",  # flake8-todos
 ]
 lint.ignore = [
   "FBT003", # flake8-boolean-trap
   "FIX002", # flake8-fixme
   "ISC003", # flake8-implicit-str-concat
   "PIE790", # Unnecessary `pass` statement
+  "TD003",  # flake8-todos
 ]
 lint.per-file-ignores."cpplint.py" = [
   "ICN001",


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This pull request updates the configuration to enable flake8-todos support and revises TODO comments in the Python code to include a username.  
- Adds flake8-todos ("TD") to the lint.select list in pyproject.toml.  
- Introduces a new ignore rule ("TD003") in pyproject.toml and updates inline TODO comments in cpplint_clitest.py and cpplint.py to include a username placeholder.

### Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

| File               | Description                                                      |
| ------------------ | ---------------------------------------------------------------- |
| pyproject.toml     | Enables flake8-todos and adjusts ignore settings for todo-checking. |
| cpplint_clitest.py | Updates a TODO comment to include a username placeholder.        |
| cpplint.py         | Updates multiple TODO comments to include a username placeholder.  |


<details>
<summary>Comments suppressed due to low confidence (4)</summary>

**cpplint_clitest.py:66**
* Replace 'unknown' with a valid username to meet the requirement that all TODO comments include the responsible individual's username.
```
        # TODO(unknown): Support scenario with multiple input names
```
**cpplint.py:4248**
* Replace 'unknown' with a valid username in the TODO comment so that code ownership is clear.
```
        # TODO(unknown): support alternate operators
```
**cpplint.py:4770**
* Replace 'unknown' with a valid username to properly attribute the TODO comment.
```
    # TODO(unknown): Err on if...else and do...while statements without braces;
```
**cpplint.py:7460**
* Replace 'unknown' with a valid username to comply with the convention for TODO comments.
```
        # TODO(unknown): Maybe make this have an exit code of 2 after all is done
```
</details>


state: COMMENTED
author_association: NONE
submitted_at: 2025-03-30T14:39:34Z
user: login: cclauss
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-30T14:42:23Z
user: login: aaronliu0130
body: What is the point of making these changes if they just going to silence/workaround it by making it all `unknown`? How does this provide a point of reference for the best context? To do this, we should correctly blame all the TODOs. (BTW, since we check all the flake8 rules we check with ruff too, I think we can go ahead with removing flake8 from pre-commit.)

(psssst: I hate to sound like a broken record, but I'd really appreciate a review for #353.)

state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-31T17:10:24Z
user: login: aaronliu0130
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-31T18:39:27Z

Review comments:
diff_hunk: @@ -122,13 +123,13 @@ lint.select = [
   # "S",   # flake8-bandit
   # "SLF", # flake8-self
   # "T20", # flake8-print
-  # "TD",  # flake8-todos
 ]
 lint.ignore = [
   "FBT003", # flake8-boolean-trap
   "FIX002", # flake8-fixme
   "ISC003", # flake8-implicit-str-concat
   "PIE790", # Unnecessary `pass` statement
+  "TD003",  # flake8-todos
path: pyproject.toml
user: login: Copilot
body: The ignore rule for 'TD003' contradicts the PR objective of enforcing a username in all TODO comments. Consider removing 'TD003' from the ignore list to allow the rule to function as intended.
```suggestion

```
created_at: 2025-03-30T14:39:34Z
author_association: NONE
diff_hunk: @@ -122,13 +123,13 @@ lint.select = [
   # "S",   # flake8-bandit
   # "SLF", # flake8-self
   # "T20", # flake8-print
-  # "TD",  # flake8-todos
 ]
 lint.ignore = [
   "FBT003", # flake8-boolean-trap
   "FIX002", # flake8-fixme
   "ISC003", # flake8-implicit-str-concat
   "PIE790", # Unnecessary `pass` statement
+  "TD003",  # flake8-todos
path: pyproject.toml
user: login: cclauss
body: TO003 (https://docs.astral.sh/ruff/rules/missing-todo-link) is about a link to an issue, not about username.
created_at: 2025-03-30T14:42:23Z
author_association: MEMBER

PR comments:
user: login: aaronliu0130
created_at: 2025-03-31T17:20:11Z
body: Also, what do you think of enabling CodeRabbit? It's like the Copilot review you just requested and free for public repos, plus it replies to and learns from the replies you give its suggestions.
author_association: MEMBER
user: login: cclauss
created_at: 2025-03-31T17:22:53Z
body: Never heard of CodeRabbit but will take a look.
author_association: MEMBER
user: login: cclauss
created_at: 2025-03-31T17:27:59Z
body: This is a rule that both cpplint and ruff have this rule for good reasons.  I believe that we should implement it for future TODOs.  I cannot properly assign old TODOs (without a lot of git blame) but adding this rule now will help us (in the code review process) ensure that new TODOs have assignees.
author_association: MEMBER
user: login: aaronliu0130
created_at: 2025-03-31T18:39:11Z
body: I don't see how the original commit would've incentivized that instead of just making TODOs say "TODO(unknown)" instead. Anyways, this issue is moot now as I've just taken a few minutes to blame the TODOs; it's much easier if you use the blame feature in an IDE instead of manually running git commands.
author_association: MEMBER