From de770c58f528427774b0c0637c9ca1897ec1a355 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Tue, 15 Apr 2025 20:02:44 -0400 Subject: [PATCH 1/3] fix(whitespace/indent_namespace): Specifier & multiline false positives for constructors Fix false positive for when a function specifier (e.g. `inline`, `protected`) is provided Fix false positive for when constructor ends on the same line a MemInitList does, but on a different line from said list's start Fix ownership assignment of open parentheses Use search instead of match for some cases Fix typo & misc refactoring --- cpplint.py | 105 ++++++++++++++++++++++++++++++++------------ cpplint_unittest.py | 22 +++++++--- 2 files changed, 95 insertions(+), 32 deletions(-) diff --git a/cpplint.py b/cpplint.py index 7abd2ba..c9c0585 100755 --- a/cpplint.py +++ b/cpplint.py @@ -3195,7 +3195,17 @@ class _WrappedInfo(_BlockInfo): class _MemInitListInfo(_WrappedInfo): """Stores information about member initializer lists.""" - pass + def __init__(self, linenum): + _WrappedInfo.__init__(self, linenum, False) + # For `: a(b)`, a is the identifier + self.expecting_identifier = True + # Whether we are expecting the value that follows the identifier + self.expecting_braces = False + # Whether we are currently within braces + self.in_braces = False + # Only used when within braces + self.is_curly = False + self.open_brace_count = 0 class _PreprocessorInfo: @@ -3341,6 +3351,20 @@ def InTemplateArgumentList(self, clean_lines, linenum, pos): pos = end_pos return False + def _BequeathParensAndAppend(self, appendee: _BlockInfo, parens_changed: int): + """Pass down number of open parentheses and append to the stack. + + Args: + appendee: The object to append to the stack. + parens_changed: The number of parentheses opened, as computed by _ChangeParentheses(). + """ + # Compensate for adding parentheses to the previous top block before this update + if self.stack: + self.stack[-1].open_parentheses -= parens_changed + appendee.open_parentheses += parens_changed + + self.stack.append(appendee) + def UpdatePreprocessor(self, line): """Update preprocessor stack. @@ -3401,9 +3425,15 @@ def _Pop(self): """Pop the innermost state (top of the stack) and remember the popped item.""" self.popped_top = self.stack.pop() - def _CountOpenParentheses(self, line: str): - # Count parentheses. This is to avoid adding struct arguments to - # the nesting stack. + def _ChangeParentheses(self, line: str) -> int: + """ + Count parentheses. This is to avoid adding struct arguments to the nesting stack. + Args: + line: The line to count parentheses on. + + Returns: + The number of parentheses opened on this line. + """ if self.stack: inner_block = self.stack[-1] depth_change = line.count("(") - line.count(")") @@ -3426,7 +3456,10 @@ def _CountOpenParentheses(self, line: str): # Exit assembly block inner_block.inline_asm = _END_ASM - def _UpdateNamesapce(self, line: str, linenum: int) -> str | None: + return depth_change + return 0 + + def _ConsumeNamespace(self, line: str, linenum: int) -> str | None: """ Match start of namespace, append to stack, and consume line Args: @@ -3453,21 +3486,29 @@ def _UpdateNamesapce(self, line: str, linenum: int) -> str | None: line = line[line.find("{") + 1 :] return line - def _UpdateConstructor(self, line: str, linenum: int, class_name: str | None = None): + def _UpdateConstructor( + self, line: str, linenum: int, parens_changed: int, class_name: str | None = None + ) -> bool: """ Check if the given line is a constructor. Args: line: Line to check. class_name: If line checked is inside of a class block, a str of the class's name; otherwise, None. + parens_changed: The number of parentheses opened on this line. + + Returns: + Whether the given line was a constructor. """ + prefix = r"(?:^|public|private|protected|friend|inline|explicit|constexpr)\s*" if not class_name: - if not re.match(r"\s*(\w*)\s*::\s*\1\s*\(", line): - return - elif not re.match(rf"\s*{re.escape(class_name)}\s*\(", line): - return + if not re.search(prefix + r"(\w*)\s*::\s*\1\s*\(", line): + return False + elif not re.search(prefix + rf"{re.escape(class_name)}\s*\(", line): + return False - self.stack.append(_ConstructorInfo(linenum)) + self._BequeathParensAndAppend(_ConstructorInfo(linenum)) + return True # TODO(google): Update() is too long, but we will refactor later. def Update(self, filename: str, clean_lines: CleansedLines, linenum: int, error): @@ -3495,12 +3536,12 @@ def Update(self, filename: str, clean_lines: CleansedLines, linenum: int, error) # Update pp_stack self.UpdatePreprocessor(line) - self._CountOpenParentheses(line) + parens_changed = self._ChangeParentheses(line) # Consume namespace declaration at the beginning of the line. Do # this in a loop so that we catch same line declarations like this: # namespace proto2 { namespace bridge { class MessageSet; } } - while (new_line := self._UpdateNamesapce(line, linenum)) is not None: # could be empty str + while (new_line := self._ConsumeNamespace(line, linenum)) is not None: # could be empty str line = new_line # Look for a class declaration in whatever is left of the line @@ -3573,8 +3614,11 @@ def Update(self, filename: str, clean_lines: CleansedLines, linenum: int, error) line = access_match.group(4) else: self._UpdateConstructor(line, linenum, class_name=classinfo.name) + self._UpdateConstructor( + line, linenum, parens_changed, class_name=classinfo.name + ) else: # Not in class - self._UpdateConstructor(line, linenum) + self._UpdateConstructor(line, linenum, parens_changed) # If brace not open and we just finished a parenthetical definition, # check if we're in a member initializer list following a constructor. @@ -3587,16 +3631,16 @@ def Update(self, filename: str, clean_lines: CleansedLines, linenum: int, error) and not self.stack[-1].seen_open_brace and re.search(r"[^:]:[^:]", line) ): - self.stack.append(_MemInitListInfo(linenum, seen_open_brace=False)) + self._BequeathParensAndAppend(_MemInitListInfo(linenum), parens_changed) # Consume braces or semicolons from what's left of the line while True: # Match first brace, semicolon, or closed parenthesis. - matched = re.match(r"^[^{;)}]*([{;)}])(.*)$", line) - if not matched: + searched = re.search(r"([{;)}])", line) + if not searched: break - token = matched.group(1) + token = searched.group(1) if token == "{": # If namespace or class hasn't seen an opening brace yet, mark # namespace/class head as complete. Push a new block onto the @@ -3634,7 +3678,7 @@ def Update(self, filename: str, clean_lines: CleansedLines, linenum: int, error) if self.stack: self.stack[-1].CheckEnd(filename, clean_lines, linenum, error) self._Pop() - line = matched.group(2) + line = line[searched.end(0) :] def InnermostClass(self): """Get class info on the top of the stack. @@ -7240,15 +7284,22 @@ def ShouldCheckNamespaceIndentation( # Skip if we are extra-indenting a member initializer list. if ( - isinstance(nesting_state.previous_stack_top, _ConstructorInfo) # F/N (A::A() : _a(0) {/{}) - and ( - isinstance(nesting_state.stack[-1], _MemInitListInfo) - or isinstance(nesting_state.popped_top, _MemInitListInfo) + ( + isinstance(nesting_state.previous_stack_top, _ConstructorInfo) # F/N (A::A() : _a(0){) + and ( + isinstance(nesting_state.stack[-1], _MemInitListInfo) + or isinstance(nesting_state.popped_top, _MemInitListInfo) + ) + ) + or ( # empty constructor in multiline list + isinstance(nesting_state.previous_stack_top, _MemInitListInfo) + and isinstance(nesting_state.popped_top, _ConstructorInfo) + ) + or ( # popping constructor after MemInitList on the same line (: _a(a) {}) + isinstance(nesting_state.previous_stack_top, _ConstructorInfo) + and isinstance(nesting_state.popped_top, _ConstructorInfo) + and re.search(r"[^:]:[^:]", raw_lines_no_comments[linenum]) ) - ) or ( # popping constructor after MemInitList on the same line (: _a(a) {}) - isinstance(nesting_state.previous_stack_top, _ConstructorInfo) - and isinstance(nesting_state.popped_top, _ConstructorInfo) - and re.search(r"[^:]:[^:]", raw_lines_no_comments[linenum]) ): return False diff --git a/cpplint_unittest.py b/cpplint_unittest.py index 730babd..9b32ae5 100755 --- a/cpplint_unittest.py +++ b/cpplint_unittest.py @@ -327,20 +327,20 @@ def testNamespaceIndentationMemberInitializerList(self): lines = [ "namespace Opossum {", "", - "Acme::Acme(const std::shared_ptr left,", - " const std::shared_ptr nigh)", + "protected Acme::Acme(const std::shared_ptr left,", + " const std::shared_ptr nigh)", " : _left(left), _behind(nigh) {}", "", "} // namespace Opossum", ] assert self.GetNamespaceResults(lines) == "" - # Multiline member initializer List + # Multiline member initializer list; constructor on different line from list lines = [ "namespace Rosenfield {", "class Crush : public Habitual {", " public:", - " Crush() : _a(1),", + " explicit Crush() : _a(1),", " _b(2)", " {}", "};", @@ -348,6 +348,18 @@ def testNamespaceIndentationMemberInitializerList(self): ] assert self.GetNamespaceResults(lines) == "" + # Inline + multiline, ends on the same line as a list's end + lines = [ + "namespace Humpety {", + "Dumpety::Dumpety(const int a)", + " : number(a),", + " looooooong_attr1(0),", + " looooooong_attr2(0),", + " looooooong_attr3(0),", + " looooooong_attr4(0) {}", + ] + assert self.GetNamespaceResults(lines) == "" + # Same line as constructor declaration lines = [ "namespace Boucher {", @@ -363,7 +375,7 @@ def testNamespaceIndentationMemberInitializerList(self): lines = [ "namespace Store {", "", - " Color::Color() : my_name_is('b')", + " Color::Color() : my_name_is('b'),", " this_is_true(true) {", ] assert ( From 21f2d05175537764f0a7898c1f34520fe55a0ad5 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 17 Apr 2025 14:41:17 -0400 Subject: [PATCH 2/3] fix(whitespace/indent_namespace): Curly braces in MemInitList Fixes `: a({0}), b{"c", d}` etc. F/Ps Consumes and updates state of MemInitList so we get to the curly braces before _ConsumeEnd() does Splits ending token searches/checks into `_ConsumeEnd()` Requires starting index to initialize `_WrapInfo`s Renames _WrappedInfo to _WrapInfo since Wrapped is not a noun --- cpplint.py | 189 +++++++++++++++++++++++++++++++------------- cpplint_unittest.py | 11 +++ 2 files changed, 144 insertions(+), 56 deletions(-) diff --git a/cpplint.py b/cpplint.py index c9c0585..c1a5f65 100755 --- a/cpplint.py +++ b/cpplint.py @@ -3182,21 +3182,24 @@ def CheckEnd(self, filename, clean_lines, linenum, error): ) -class _WrappedInfo(_BlockInfo): +class _WrapInfo(_BlockInfo): """Stores information about parentheses, initializer lists, etc. Not exactly a block but we do need the same signature. Needed to avoid namespace indentation false positives, though parentheses tracking would slow us down a lot and is effectively already done by open_parentheses.""" - pass + def __init__(self, linenum: int, index: int): + _BlockInfo.__init__(self, linenum, seen_open_brace=False) + # The index on lines[starting_linenum] where the wrap started + self.starting_index = index -class _MemInitListInfo(_WrappedInfo): +class _MemInitListInfo(_WrapInfo): """Stores information about member initializer lists.""" - def __init__(self, linenum): - _WrappedInfo.__init__(self, linenum, False) + def __init__(self, linenum: int, index: int): + _WrapInfo.__init__(self, linenum, index) # For `: a(b)`, a is the identifier self.expecting_identifier = True # Whether we are expecting the value that follows the identifier @@ -3507,9 +3510,117 @@ def _UpdateConstructor( elif not re.search(prefix + rf"{re.escape(class_name)}\s*\(", line): return False - self._BequeathParensAndAppend(_ConstructorInfo(linenum)) + self._BequeathParensAndAppend(_ConstructorInfo(linenum), parens_changed) return True + def _ConsumeMemInitList(self, line: str, item: _MemInitListInfo) -> str | None: + """ + Consume a line with a member initializer list and update its state. + Args: + line: Line to check and consume + item: Stack item for the checked line to read from and update + + Returns: + The consumed line if we may continue consuming; None otherwise. + """ + if line == "": + return None # see last line of `if item.in_braces` + if item.expecting_identifier: + if searched := re.search(r"(\w+)", line): + item.expecting_identifier = False + line = line[searched.end(0) :] + item.expecting_braces = True + else: + return None + if item.expecting_braces: + if searched := re.match(r"\s*([({])", line): + item.in_braces = True + item.open_brace_count = 1 + item.is_curly = searched.group(1) == "{" + line = line[searched.end(0) :] + item.expecting_braces = False + else: + return None + if item.in_braces: + lbrace, rbrace = ("{", "}") if item.is_curly else ("(", ")") + if found := line.rfind(rbrace) + 1: # the index after that of the rbrace + item.open_brace_count -= line[:found].count(rbrace) - line[:found].count(lbrace) + line = line[found:] + if item.open_brace_count == 0: + item.in_braces = False + if item.in_braces: + # Nothing meaningful for _ConsumeEnd() if we're still within the MemInitList + # Plus the mentioned function would pick up the {} + return "" + if not (item.expecting_identifier or item.expecting_braces or item.in_braces): + if found := line.find(",") + 1: + item.expecting_identifier = True + line = line[found:] + else: + return None + return line + + def _ConsumeEnd( + self, line: str, filename: str, clean_lines: CleansedLines, linenum: int, error + ) -> str | None: + """ + Consume braces or semicolons from what's left of the line. + Checks that should only be run after all other Update checks have been. + Args: + line: Line to check and consume + filename: Name of the current file + clean_lines: CleansedLines instance containing the file + linenum: Number of the line to check + error: Function to call with any errors found + + Returns: + The consumed line if any checks were successful; None otherwise. + """ + # Match first brace, semicolon, or closed parenthesis. + searched = re.search(r"([{;)}])", line) + if not searched: + return None + + token = searched.group(1) + if token == "{": + # If namespace or class hasn't seen an opening brace yet, mark + # namespace/class head as complete. Push a new block onto the + # stack otherwise. + if not self.SeenOpenBrace(): + # End of initializer list wrap if present + if isinstance(self.stack[-1], _MemInitListInfo): + self._Pop() + self.stack[-1].seen_open_brace = True + elif re.match(r'^extern\s*"[^"]*"\s*\{', line): + self.stack.append(_ExternCInfo(linenum)) + else: + self.stack.append(_BlockInfo(linenum, True)) + if _MATCH_ASM.match(line): + self.stack[-1].inline_asm = _BLOCK_ASM + elif token == ";": + # If we haven't seen an opening brace yet, but we already saw + # a semicolon, this is probably a forward declaration. Pop + # the stack for these. + if not self.SeenOpenBrace(): + self._Pop() + elif token == ")": + # Similarly, if we haven't seen an opening brace yet, but we + # already saw a closing parenthesis, then these are probably + # function arguments with extra "class" or "struct" keywords. + # Also pop these stack for these. + if ( + self.stack + and not self.stack[-1].seen_open_brace + and isinstance(self.stack[-1], _ClassInfo) + ): + self._Pop() + else: # token == '}' + # Perform end of block checks and pop the stack. + if self.stack: + self.stack[-1].CheckEnd(filename, clean_lines, linenum, error) + self._Pop() + return line[searched.end(0) :] + # TODO(google): Update() is too long, but we will refactor later. def Update(self, filename: str, clean_lines: CleansedLines, linenum: int, error): """Update nesting state with current line. @@ -3541,7 +3652,7 @@ def Update(self, filename: str, clean_lines: CleansedLines, linenum: int, error) # Consume namespace declaration at the beginning of the line. Do # this in a loop so that we catch same line declarations like this: # namespace proto2 { namespace bridge { class MessageSet; } } - while (new_line := self._ConsumeNamespace(line, linenum)) is not None: # could be empty str + while (new_line := self._ConsumeNamespace(line, linenum)) is not None: line = new_line # Look for a class declaration in whatever is left of the line @@ -3613,7 +3724,6 @@ def Update(self, filename: str, clean_lines: CleansedLines, linenum: int, error) line = access_match.group(4) else: - self._UpdateConstructor(line, linenum, class_name=classinfo.name) self._UpdateConstructor( line, linenum, parens_changed, class_name=classinfo.name ) @@ -3629,56 +3739,23 @@ def Update(self, filename: str, clean_lines: CleansedLines, linenum: int, error) or isinstance(self.previous_stack_top, _ConstructorInfo) ) and not self.stack[-1].seen_open_brace - and re.search(r"[^:]:[^:]", line) + and (searched := re.search(r"[^:]:[^:]", line)) ): - self._BequeathParensAndAppend(_MemInitListInfo(linenum), parens_changed) + self._BequeathParensAndAppend( + _MemInitListInfo(linenum, index=searched.start(0) + 1), parens_changed + ) + line = line[searched.end(0) :] # Consume everything b4 the MemInitList, incl. the ':' - # Consume braces or semicolons from what's left of the line - while True: - # Match first brace, semicolon, or closed parenthesis. - searched = re.search(r"([{;)}])", line) - if not searched: - break + # Consume contents of MemInitList if we're in one + if self.stack and isinstance(self.stack[-1], _MemInitListInfo): + while (new_line := self._ConsumeMemInitList(line, self.stack[-1])) is not None: + line = new_line - token = searched.group(1) - if token == "{": - # If namespace or class hasn't seen an opening brace yet, mark - # namespace/class head as complete. Push a new block onto the - # stack otherwise. - if not self.SeenOpenBrace(): - # End of initializer list wrap if present - if isinstance(self.stack[-1], _MemInitListInfo): - self._Pop() - self.stack[-1].seen_open_brace = True - elif re.match(r'^extern\s*"[^"]*"\s*\{', line): - self.stack.append(_ExternCInfo(linenum)) - else: - self.stack.append(_BlockInfo(linenum, True)) - if _MATCH_ASM.match(line): - self.stack[-1].inline_asm = _BLOCK_ASM - elif token == ";": - # If we haven't seen an opening brace yet, but we already saw - # a semicolon, this is probably a forward declaration. Pop - # the stack for these. - if not self.SeenOpenBrace(): - self._Pop() - elif token == ")": - # Similarly, if we haven't seen an opening brace yet, but we - # already saw a closing parenthesis, then these are probably - # function arguments with extra "class" or "struct" keywords. - # Also pop these stack for these. - if ( - self.stack - and not self.stack[-1].seen_open_brace - and isinstance(self.stack[-1], _ClassInfo) - ): - self._Pop() - else: # token == '}' - # Perform end of block checks and pop the stack. - if self.stack: - self.stack[-1].CheckEnd(filename, clean_lines, linenum, error) - self._Pop() - line = line[searched.end(0) :] + # Consume braces or semicolons from what's left of the line + while ( + new_line := self._ConsumeEnd(line, filename, clean_lines, linenum, error) + ) is not None: + line = new_line def InnermostClass(self): """Get class info on the top of the stack. @@ -7238,7 +7315,7 @@ def IsBlockInNameSpace(nesting_state: NestingState, is_forward_declaration: bool and ( isinstance(nesting_state.stack[-2], _NamespaceInfo) or len(nesting_state.stack) > 2 # Accommodate for WrappedInfo - and issubclass(type(nesting_state.stack[-1]), _WrappedInfo) + and issubclass(type(nesting_state.stack[-1]), _WrapInfo) and not nesting_state.stack[-2].seen_open_brace and isinstance(nesting_state.stack[-3], _NamespaceInfo) ) diff --git a/cpplint_unittest.py b/cpplint_unittest.py index 9b32ae5..3e67caa 100755 --- a/cpplint_unittest.py +++ b/cpplint_unittest.py @@ -360,6 +360,17 @@ def testNamespaceIndentationMemberInitializerList(self): ] assert self.GetNamespaceResults(lines) == "" + # Uniform initialization + lines = [ + "namespace Unifor {", + "class MIA {", + ' private MIA() : cali({"nia"}),', + " parme({3}),", + ' carne{"val", ipsmo}', + " gat(o) {}", + ] + assert self.GetNamespaceResults(lines) == "" + # Same line as constructor declaration lines = [ "namespace Boucher {", From 032ab5fd59ab522969d49ce417dd4b7ddbb00f3c Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Wed, 23 Apr 2025 17:48:12 -0400 Subject: [PATCH 3/3] fix(whitespace/indent_namespace): Brace parsing direction and unreturned consumption Fix the direction in which we find braces Fix bug where consumption doesn't happen due to return None without returning the consumed line Refactored consumption functions to have the loop themselves Change tests to account for that --- cpplint.py | 112 ++++++++++++++++++++++---------------------- cpplint_unittest.py | 2 +- 2 files changed, 58 insertions(+), 56 deletions(-) diff --git a/cpplint.py b/cpplint.py index c1a5f65..9e087cc 100755 --- a/cpplint.py +++ b/cpplint.py @@ -3462,7 +3462,7 @@ def _ChangeParentheses(self, line: str) -> int: return depth_change return 0 - def _ConsumeNamespace(self, line: str, linenum: int) -> str | None: + def _ConsumeNamespace(self, line: str, linenum: int) -> str: """ Match start of namespace, append to stack, and consume line Args: @@ -3470,23 +3470,24 @@ def _ConsumeNamespace(self, line: str, linenum: int) -> str | None: linenum: Line number of the line to check Returns: - The consumed line if namespace matched; None otherwise + Consumed line """ - # Match start of namespace. The "\b\s*" below catches namespace - # declarations even if it weren't followed by a whitespace, this - # is so that we don't confuse our namespace checker. The - # missing spaces will be flagged by CheckSpacing. - namespace_decl_match = re.match(r"^\s*namespace\b\s*([:\w]+)?(.*)$", line) - if not namespace_decl_match: - return None + while True: + # Match start of namespace. The "\b\s*" below catches namespace + # declarations even if it weren't followed by a whitespace, this + # is so that we don't confuse our namespace checker. The + # missing spaces will be flagged by CheckSpacing. + namespace_decl_match = re.match(r"^\s*namespace\b\s*([:\w]+)?(.*)$", line) + if not namespace_decl_match: + break - new_namespace = _NamespaceInfo(namespace_decl_match.group(1), linenum) - self.stack.append(new_namespace) + new_namespace = _NamespaceInfo(namespace_decl_match.group(1), linenum) + self.stack.append(new_namespace) - line = namespace_decl_match.group(2) - if line.find("{") != -1: - new_namespace.seen_open_brace = True - line = line[line.find("{") + 1 :] + line = namespace_decl_match.group(2) + if line.find("{") != -1: + new_namespace.seen_open_brace = True + line = line[line.find("{") + 1 :] return line def _UpdateConstructor( @@ -3513,7 +3514,7 @@ def _UpdateConstructor( self._BequeathParensAndAppend(_ConstructorInfo(linenum), parens_changed) return True - def _ConsumeMemInitList(self, line: str, item: _MemInitListInfo) -> str | None: + def _ConsumeMemInitList(self, line: str, item: _MemInitListInfo) -> str: """ Consume a line with a member initializer list and update its state. Args: @@ -3521,43 +3522,46 @@ def _ConsumeMemInitList(self, line: str, item: _MemInitListInfo) -> str | None: item: Stack item for the checked line to read from and update Returns: - The consumed line if we may continue consuming; None otherwise. + Processed line """ - if line == "": - return None # see last line of `if item.in_braces` - if item.expecting_identifier: - if searched := re.search(r"(\w+)", line): - item.expecting_identifier = False - line = line[searched.end(0) :] - item.expecting_braces = True - else: - return None - if item.expecting_braces: - if searched := re.match(r"\s*([({])", line): - item.in_braces = True - item.open_brace_count = 1 - item.is_curly = searched.group(1) == "{" - line = line[searched.end(0) :] - item.expecting_braces = False - else: - return None - if item.in_braces: - lbrace, rbrace = ("{", "}") if item.is_curly else ("(", ")") - if found := line.rfind(rbrace) + 1: # the index after that of the rbrace - item.open_brace_count -= line[:found].count(rbrace) - line[:found].count(lbrace) - line = line[found:] - if item.open_brace_count == 0: - item.in_braces = False + while True: + if line == "": + break # see last line of `if item.in_braces` + if item.expecting_identifier: + if searched := re.search(r"(\w+)", line): + item.expecting_identifier = False + line = line[searched.end(0) :] + item.expecting_braces = True + else: + break + if item.expecting_braces: + if searched := re.match(r"\s*([({])", line): + item.in_braces = True + item.open_brace_count = 1 + item.is_curly = searched.group(1) == "{" + line = line[searched.end(0) :] + item.expecting_braces = False + else: + break if item.in_braces: - # Nothing meaningful for _ConsumeEnd() if we're still within the MemInitList - # Plus the mentioned function would pick up the {} - return "" - if not (item.expecting_identifier or item.expecting_braces or item.in_braces): - if found := line.find(",") + 1: - item.expecting_identifier = True - line = line[found:] - else: - return None + lbrace, rbrace = ("{", "}") if item.is_curly else ("(", ")") + # Matching braces + while found := line.find(rbrace) + 1: # the index after that of the rbrace + item.open_brace_count -= line[:found].count(rbrace) - line[:found].count(lbrace) + line = line[found:] + if item.open_brace_count == 0: + item.in_braces = False + break + if item.in_braces: + # Nothing meaningful for _ConsumeEnd() if we're still within the MemInitList + # Plus the mentioned function would pick up the }{ + return "" + if not (item.expecting_identifier or item.expecting_braces or item.in_braces): + if found := line.find(",") + 1: + item.expecting_identifier = True + line = line[found:] + else: + break return line def _ConsumeEnd( @@ -3652,8 +3656,7 @@ def Update(self, filename: str, clean_lines: CleansedLines, linenum: int, error) # Consume namespace declaration at the beginning of the line. Do # this in a loop so that we catch same line declarations like this: # namespace proto2 { namespace bridge { class MessageSet; } } - while (new_line := self._ConsumeNamespace(line, linenum)) is not None: - line = new_line + line = self._ConsumeNamespace(line, linenum) # Look for a class declaration in whatever is left of the line # after parsing namespaces. The regexp accounts for decorated classes @@ -3748,8 +3751,7 @@ def Update(self, filename: str, clean_lines: CleansedLines, linenum: int, error) # Consume contents of MemInitList if we're in one if self.stack and isinstance(self.stack[-1], _MemInitListInfo): - while (new_line := self._ConsumeMemInitList(line, self.stack[-1])) is not None: - line = new_line + line = self._ConsumeMemInitList(line, self.stack[-1]) # Consume braces or semicolons from what's left of the line while ( diff --git a/cpplint_unittest.py b/cpplint_unittest.py index 3e67caa..9398070 100755 --- a/cpplint_unittest.py +++ b/cpplint_unittest.py @@ -329,7 +329,7 @@ def testNamespaceIndentationMemberInitializerList(self): "", "protected Acme::Acme(const std::shared_ptr left,", " const std::shared_ptr nigh)", - " : _left(left), _behind(nigh) {}", + " : _left(left), _behind{nigh} {}", "", "} // namespace Opossum", ]