Coverage for sources / vibelinter / rules / implementations / vbl101.py: 60%

63 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-12-01 02:35 +0000

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

4#============================================================================# 

5# # 

6# Licensed under the Apache License, Version 2.0 (the "License"); # 

7# you may not use this file except in compliance with the License. # 

8# You may obtain a copy of the License at # 

9# # 

10# http://www.apache.org/licenses/LICENSE-2.0 # 

11# # 

12# Unless required by applicable law or agreed to in writing, software # 

13# distributed under the License is distributed on an "AS IS" BASIS, # 

14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

15# See the License for the specific language governing permissions and # 

16# limitations under the License. # 

17# # 

18#============================================================================# 

19 

20 

21 

22''' VBL101: Detect blank lines between statements in function bodies. 

23 

24 

25 

26 Category: Readability 

27 Subcategory: Compactness 

28 

29 This rule detects blank lines between statements within function or 

30 method bodies and suggests their elimination to improve vertical 

31 compactness per the project coding standards. Blank lines inside 

32 string literals are allowed. 

33''' 

34 

35 

36from . import __ 

37 

38 

39class VBL101( __.BaseRule ): 

40 ''' Detects blank lines between statements in function bodies. ''' 

41 

42 @property 

43 def rule_id( self ) -> str: 

44 return 'VBL101' 

45 

46 def __init__( 

47 self, 

48 filename: str, 

49 wrapper: __.libcst.metadata.MetadataWrapper, 

50 source_lines: tuple[ str, ... ], 

51 ) -> None: 

52 super( ).__init__( filename, wrapper, source_lines ) 

53 # Collection: store function definitions and their line ranges 

54 self._function_ranges: list[ 

55 tuple[ int, int, __.libcst.FunctionDef ] ] = [ ] 

56 # Collection: store triple-quoted string literal line ranges 

57 self._string_ranges: list[ tuple[ int, int ] ] = [ ] 

58 

59 def visit_FunctionDef( self, node: __.libcst.FunctionDef ) -> bool: 

60 ''' Collects function definitions for later analysis. ''' 

61 # Get the position of the function 

62 try: 

63 position = self.wrapper.resolve( 

64 __.libcst.metadata.PositionProvider )[ node ] 

65 start_line = position.start.line 

66 end_line = position.end.line 

67 self._function_ranges.append( ( start_line, end_line, node ) ) 

68 except KeyError: 

69 # Position not available, skip this function 

70 pass 

71 return True # Continue visiting children 

72 

73 def visit_SimpleString( self, node: __.libcst.SimpleString ) -> bool: 

74 ''' Collects triple-quoted string literal ranges. ''' 

75 # Only track triple-quoted strings (docstrings and multiline strings) 

76 if node.quote in ( '"""', "'''" ): 

77 try: 

78 position = self.wrapper.resolve( 

79 __.libcst.metadata.PositionProvider )[ node ] 

80 start_line = position.start.line 

81 end_line = position.end.line 

82 self._string_ranges.append( ( start_line, end_line ) ) 

83 except KeyError: 

84 # Position not available, skip this string 

85 pass 

86 return True # Continue visiting children 

87 

88 def visit_ConcatenatedString( 

89 self, node: __.libcst.ConcatenatedString 

90 ) -> bool: 

91 ''' Collects concatenated string literal ranges. ''' 

92 # Check if any part is a triple-quoted string 

93 has_triple_quote = False 

94 for part in ( node.left, node.right ): 

95 if isinstance( part, __.libcst.SimpleString ): 

96 if part.quote in ( '"""', "'''" ): 

97 has_triple_quote = True 

98 break 

99 elif ( 

100 isinstance( part, __.libcst.FormattedString ) 

101 and part.start in ( '"""', "'''" ) 

102 ): 

103 # f-strings can also be triple-quoted 

104 has_triple_quote = True 

105 break 

106 if has_triple_quote: 

107 try: 

108 position = self.wrapper.resolve( 

109 __.libcst.metadata.PositionProvider )[ node ] 

110 start_line = position.start.line 

111 end_line = position.end.line 

112 self._string_ranges.append( ( start_line, end_line ) ) 

113 except KeyError: 

114 # Position not available, skip this string 

115 pass 

116 return True # Continue visiting children 

117 

118 def _analyze_collections( self ) -> None: 

119 ''' Analyzes collected functions for blank lines between statements. 

120 Blank lines inside string literals are allowed. 

121 ''' 

122 for start_line, end_line, _func_node in self._function_ranges: 

123 # Get function body start (after the def line) 

124 body_start = start_line + 1 

125 for line_num in range( body_start, end_line + 1 ): 

126 if line_num - 1 >= len( self.source_lines ): break 126 ↛ 122line 126 didn't jump to line 122 because the break on line 126 wasn't executed

127 line = self.source_lines[ line_num - 1 ] 

128 stripped = line.strip( ) 

129 # Report violation for blank lines between statements 

130 # Skip blank lines inside string literals 

131 if not stripped and not self._is_in_string( line_num ): 

132 self._report_blank_line( line_num ) 

133 

134 def _is_in_string( self, line_num: int ) -> bool: 

135 ''' Checks if line is inside a triple-quoted string literal. ''' 

136 return any( 

137 start <= line_num <= end 

138 for start, end in self._string_ranges ) 

139 

140 def _report_blank_line( self, line_num: int ) -> None: 

141 ''' Reports a violation for a blank line in function body. ''' 

142 from .. import violations as _violations 

143 violation = _violations.Violation( 

144 rule_id = self.rule_id, 

145 filename = self.filename, 

146 line = line_num, 

147 column = 1, 

148 message = "Blank line in function body.", 

149 severity = 'warning' ) 

150 self._violations.append( violation ) 

151 

152 

153# Self-register this rule 

154__.RULE_DESCRIPTORS[ 'VBL101' ] = __.RuleDescriptor( 

155 vbl_code = 'VBL101', 

156 descriptive_name = 'blank-line-elimination', 

157 description = 'Detects blank lines within function bodies.', 

158 category = 'readability', 

159 subcategory = 'compactness', 

160 rule_class = VBL101, 

161)