@@ -3097,6 +3097,14 @@ def CheckEnd(self, filename, clean_lines, linenum, error):
30973097 )
30983098
30993099
3100+ class _ConstructorInfo (_BlockInfo ):
3101+ """Stores information about a constructor.
3102+ For detecting member initializer lists."""
3103+
3104+ def __init__ (self , linenum : int ):
3105+ _BlockInfo .__init__ (self , linenum , seen_open_brace = False )
3106+
3107+
31003108class _NamespaceInfo (_BlockInfo ):
31013109 """Stores information about a namespace."""
31023110
@@ -3174,6 +3182,22 @@ def CheckEnd(self, filename, clean_lines, linenum, error):
31743182 )
31753183
31763184
3185+ class _WrappedInfo (_BlockInfo ):
3186+ """Stores information about parentheses, initializer lists, etc.
3187+ Not exactly a block but we do need the same signature.
3188+ Needed to avoid namespace indentation false positives,
3189+ though parentheses tracking would slow us down a lot
3190+ and is effectively already done by open_parentheses."""
3191+
3192+ pass
3193+
3194+
3195+ class _MemInitListInfo (_WrappedInfo ):
3196+ """Stores information about member initializer lists."""
3197+
3198+ pass
3199+
3200+
31773201class _PreprocessorInfo :
31783202 """Stores checkpoints of nesting stacks when #if/#else is seen."""
31793203
@@ -3216,6 +3240,9 @@ def __init__(self):
32163240 # We can't use previous_stack_top, a shallow copy whose open_parentheses value is updated.
32173241 self .previous_open_parentheses = 0
32183242
3243+ # The last stack item we popped.
3244+ self .popped_top : _BlockInfo | None = None
3245+
32193246 # Stack of _PreprocessorInfo objects.
32203247 self .pp_stack = []
32213248
@@ -3370,6 +3397,9 @@ def UpdatePreprocessor(self, line):
33703397 # TODO(google): unexpected #endif, issue warning?
33713398 pass
33723399
3400+ def _Pop (self ):
3401+ """Pop the innermost state (top of the stack) and remember the popped item."""
3402+ self .popped_top = self .stack .pop ()
33733403
33743404 def _CountOpenParentheses (self , line : str ):
33753405 # Count parentheses. This is to avoid adding struct arguments to
@@ -3423,6 +3453,22 @@ def _UpdateNamesapce(self, line: str, linenum: int) -> str | None:
34233453 line = line [line .find ("{" ) + 1 :]
34243454 return line
34253455
3456+ def _UpdateConstructor (self , line : str , linenum : int , class_name : str | None = None ):
3457+ """
3458+ Check if the given line is a constructor.
3459+ Args:
3460+ line: Line to check.
3461+ class_name: If line checked is inside of a class block, a str of the class's name;
3462+ otherwise, None.
3463+ """
3464+ if not class_name :
3465+ if not re .match (r"\s*(\w*)\s*::\s*\1\s*\(" , line ):
3466+ return
3467+ elif not re .match (rf"\s*{ re .escape (class_name )} \s*\(" , line ):
3468+ return
3469+
3470+ self .stack .append (_ConstructorInfo (linenum ))
3471+
34263472 # TODO(google): Update() is too long, but we will refactor later.
34273473 def Update (self , filename : str , clean_lines : CleansedLines , linenum : int , error ):
34283474 """Update nesting state with current line.
@@ -3492,36 +3538,56 @@ def Update(self, filename: str, clean_lines: CleansedLines, linenum: int, error)
34923538 if not self .SeenOpenBrace ():
34933539 self .stack [- 1 ].CheckBegin (filename , clean_lines , linenum , error )
34943540
3495- # Update access control if we are inside a class/struct
3541+ # Update access control if we are directly inside a class/struct
34963542 if self .stack and isinstance (self .stack [- 1 ], _ClassInfo ):
3497- classinfo = self .stack [- 1 ]
3498- access_match = re .match (
3499- r"^(.*)\b(public|private|protected|signals)(\s+(?:slots\s*)?)?"
3500- r":(?:[^:]|$)" ,
3501- line ,
3543+ if self .stack [- 1 ].seen_open_brace :
3544+ classinfo : _ClassInfo = self .stack [- 1 ]
3545+ # Update access control
3546+ if access_match := re .match (
3547+ r"^(.*)\b(public|private|protected|signals)(\s+(?:slots\s*)?)?"
3548+ r":([^:].*|$)" ,
3549+ line ,
3550+ ):
3551+ classinfo .access = access_match .group (2 )
3552+
3553+ # Check that access keywords are indented +1 space. Skip this
3554+ # check if the keywords are not preceded by whitespaces.
3555+ indent = access_match .group (1 )
3556+ if len (indent ) != classinfo .class_indent + 1 and re .match (r"^\s*$" , indent ):
3557+ if classinfo .is_struct :
3558+ parent = "struct " + classinfo .name
3559+ else :
3560+ parent = "class " + classinfo .name
3561+ slots = ""
3562+ if access_match .group (3 ):
3563+ slots = access_match .group (3 )
3564+ error (
3565+ filename ,
3566+ linenum ,
3567+ "whitespace/indent" ,
3568+ 3 ,
3569+ f"{ access_match .group (2 )} { slots } :"
3570+ f" should be indented +1 space inside { parent } " ,
3571+ )
3572+
3573+ line = access_match .group (4 )
3574+ else :
3575+ self ._UpdateConstructor (line , linenum , class_name = classinfo .name )
3576+ else : # Not in class
3577+ self ._UpdateConstructor (line , linenum )
3578+
3579+ # If brace not open and we just finished a parenthetical definition,
3580+ # check if we're in a member initializer list following a constructor.
3581+ if (
3582+ self .stack
3583+ and (
3584+ isinstance (self .stack [- 1 ], _ConstructorInfo )
3585+ or isinstance (self .previous_stack_top , _ConstructorInfo )
35023586 )
3503- if access_match :
3504- classinfo .access = access_match .group (2 )
3505-
3506- # Check that access keywords are indented +1 space. Skip this
3507- # check if the keywords are not preceded by whitespaces.
3508- indent = access_match .group (1 )
3509- if len (indent ) != classinfo .class_indent + 1 and re .match (r"^\s*$" , indent ):
3510- if classinfo .is_struct :
3511- parent = "struct " + classinfo .name
3512- else :
3513- parent = "class " + classinfo .name
3514- slots = ""
3515- if access_match .group (3 ):
3516- slots = access_match .group (3 )
3517- error (
3518- filename ,
3519- linenum ,
3520- "whitespace/indent" ,
3521- 3 ,
3522- f"{ access_match .group (2 )} { slots } :"
3523- f" should be indented +1 space inside { parent } " ,
3524- )
3587+ and not self .stack [- 1 ].seen_open_brace
3588+ and re .search (r"[^:]:[^:]" , line )
3589+ ):
3590+ self .stack .append (_MemInitListInfo (linenum , seen_open_brace = False ))
35253591
35263592 # Consume braces or semicolons from what's left of the line
35273593 while True :
@@ -3532,34 +3598,42 @@ def Update(self, filename: str, clean_lines: CleansedLines, linenum: int, error)
35323598
35333599 token = matched .group (1 )
35343600 if token == "{" :
3535- # If namespace or class hasn't seen a opening brace yet, mark
3601+ # If namespace or class hasn't seen an opening brace yet, mark
35363602 # namespace/class head as complete. Push a new block onto the
35373603 # stack otherwise.
35383604 if not self .SeenOpenBrace ():
3605+ # End of initializer list wrap if present
3606+ if isinstance (self .stack [- 1 ], _MemInitListInfo ):
3607+ self ._Pop ()
35393608 self .stack [- 1 ].seen_open_brace = True
35403609 elif re .match (r'^extern\s*"[^"]*"\s*\{' , line ):
35413610 self .stack .append (_ExternCInfo (linenum ))
35423611 else :
35433612 self .stack .append (_BlockInfo (linenum , True ))
35443613 if _MATCH_ASM .match (line ):
35453614 self .stack [- 1 ].inline_asm = _BLOCK_ASM
3546-
3547- elif token in {";" , ")" }:
3615+ elif token == ";" :
35483616 # If we haven't seen an opening brace yet, but we already saw
35493617 # a semicolon, this is probably a forward declaration. Pop
35503618 # the stack for these.
3551- #
3619+ if not self .SeenOpenBrace ():
3620+ self ._Pop ()
3621+ elif token == ")" :
35523622 # Similarly, if we haven't seen an opening brace yet, but we
35533623 # already saw a closing parenthesis, then these are probably
35543624 # function arguments with extra "class" or "struct" keywords.
35553625 # Also pop these stack for these.
3556- if not self .SeenOpenBrace ():
3557- self .stack .pop ()
3626+ if (
3627+ self .stack
3628+ and not self .stack [- 1 ].seen_open_brace
3629+ and isinstance (self .stack [- 1 ], _ClassInfo )
3630+ ):
3631+ self ._Pop ()
35583632 else : # token == '}'
35593633 # Perform end of block checks and pop the stack.
35603634 if self .stack :
35613635 self .stack [- 1 ].CheckEnd (filename , clean_lines , linenum , error )
3562- self .stack . pop ()
3636+ self ._Pop ()
35633637 line = matched .group (2 )
35643638
35653639 def InnermostClass (self ):
@@ -7097,11 +7171,11 @@ def CheckRedundantOverrideOrFinal(filename, clean_lines, linenum, error):
70977171
70987172# Returns true if we are at a new block, and it is directly
70997173# inside of a namespace.
7100- def IsBlockInNameSpace (nesting_state , is_forward_declaration ):
7174+ def IsBlockInNameSpace (nesting_state : NestingState , is_forward_declaration : bool ): # noqa: FBT001
71017175 """Checks that the new block is directly in a namespace.
71027176
71037177 Args:
7104- nesting_state: The _NestingState object that contains info about our state.
7178+ nesting_state: The NestingState object that contains info about our state.
71057179 is_forward_declaration: If the class is a forward declared class.
71067180 Returns:
71077181 Whether or not the new block is directly in a namespace.
@@ -7117,7 +7191,13 @@ def IsBlockInNameSpace(nesting_state, is_forward_declaration):
71177191 if (
71187192 len (nesting_state .stack ) > 1
71197193 and isinstance (nesting_state .previous_stack_top , _NamespaceInfo )
7120- and isinstance (nesting_state .stack [- 2 ], _NamespaceInfo )
7194+ and (
7195+ isinstance (nesting_state .stack [- 2 ], _NamespaceInfo )
7196+ or len (nesting_state .stack ) > 2 # Accommodate for WrappedInfo
7197+ and issubclass (type (nesting_state .stack [- 1 ]), _WrappedInfo )
7198+ and not nesting_state .stack [- 2 ].seen_open_brace
7199+ and isinstance (nesting_state .stack [- 3 ], _NamespaceInfo )
7200+ )
71217201 ):
71227202 return True
71237203 return False
@@ -7141,6 +7221,10 @@ def ShouldCheckNamespaceIndentation(
71417221 only works for classes and namespaces inside of a namespace.
71427222 """
71437223
7224+ # Required by all checks involving nesting_state
7225+ if not nesting_state .stack :
7226+ return False
7227+
71447228 is_forward_declaration = IsForwardClassDeclaration (raw_lines_no_comments , linenum )
71457229
71467230 if not (is_namespace_indent_item or is_forward_declaration ):
@@ -7154,6 +7238,20 @@ def ShouldCheckNamespaceIndentation(
71547238 if nesting_state .previous_stack_top and nesting_state .previous_open_parentheses > 0 :
71557239 return False
71567240
7241+ # Skip if we are extra-indenting a member initializer list.
7242+ if (
7243+ isinstance (nesting_state .previous_stack_top , _ConstructorInfo ) # F/N (A::A() : _a(0) {/{})
7244+ and (
7245+ isinstance (nesting_state .stack [- 1 ], _MemInitListInfo )
7246+ or isinstance (nesting_state .popped_top , _MemInitListInfo )
7247+ )
7248+ ) or ( # popping constructor after MemInitList on the same line (: _a(a) {})
7249+ isinstance (nesting_state .previous_stack_top , _ConstructorInfo )
7250+ and isinstance (nesting_state .popped_top , _ConstructorInfo )
7251+ and re .search (r"[^:]:[^:]" , raw_lines_no_comments [linenum ])
7252+ ):
7253+ return False
7254+
71577255 return IsBlockInNameSpace (nesting_state , is_forward_declaration )
71587256
71597257
0 commit comments