30 @type ast.Node |
32 @type ast.Node |
31 @yield call path components |
33 @yield call path components |
32 @ytype str |
34 @ytype str |
33 """ |
35 """ |
34 if isinstance(node, ast.Attribute): |
36 if isinstance(node, ast.Attribute): |
35 for v in composeCallPath(node.value): |
37 yield from composeCallPath(node.value) |
36 yield v |
|
37 yield node.attr |
38 yield node.attr |
38 elif isinstance(node, ast.Name): |
39 elif isinstance(node, ast.Name): |
39 yield node.id |
40 yield node.id |
40 |
41 |
41 |
42 |
42 class MiscellaneousChecker(object): |
43 class MiscellaneousChecker: |
43 """ |
44 """ |
44 Class implementing a checker for miscellaneous checks. |
45 Class implementing a checker for miscellaneous checks. |
45 """ |
46 """ |
46 Codes = [ |
47 Codes = [ |
47 ## Coding line |
48 ## Coding line |
122 "__name__", |
120 "__name__", |
123 "__doc__", |
121 "__doc__", |
124 "credits", |
122 "credits", |
125 ] |
123 ] |
126 |
124 |
127 def __init__(self, source, filename, select, ignore, expected, repeat, |
125 def __init__(self, source, filename, tree, select, ignore, expected, |
128 args): |
126 repeat, args): |
129 """ |
127 """ |
130 Constructor |
128 Constructor |
131 |
129 |
132 @param source source code to be checked |
130 @param source source code to be checked |
133 @type list of str |
131 @type list of str |
134 @param filename name of the source file |
132 @param filename name of the source file |
135 @type str |
133 @type str |
|
134 @param tree AST tree of the source code |
|
135 @type ast.Module |
136 @param select list of selected codes |
136 @param select list of selected codes |
137 @type list of str |
137 @type list of str |
138 @param ignore list of codes to be ignored |
138 @param ignore list of codes to be ignored |
139 @type list of str |
139 @type list of str |
140 @param expected list of expected codes |
140 @param expected list of expected codes |
148 self.__ignore = ('',) if select else tuple(ignore) |
148 self.__ignore = ('',) if select else tuple(ignore) |
149 self.__expected = expected[:] |
149 self.__expected = expected[:] |
150 self.__repeat = repeat |
150 self.__repeat = repeat |
151 self.__filename = filename |
151 self.__filename = filename |
152 self.__source = source[:] |
152 self.__source = source[:] |
|
153 self.__tree = copy.deepcopy(tree) |
153 self.__args = args |
154 self.__args = args |
154 |
155 |
155 self.__pep3101FormatRegex = re.compile( |
156 self.__pep3101FormatRegex = re.compile( |
156 r'^(?:[^\'"]*[\'"][^\'"]*[\'"])*\s*%|^\s*%') |
157 r'^(?:[^\'"]*[\'"][^\'"]*[\'"])*\s*%|^\s*%') |
157 |
158 |
265 "code": code, |
266 "code": code, |
266 "args": args, |
267 "args": args, |
267 } |
268 } |
268 ) |
269 ) |
269 |
270 |
270 def __reportInvalidSyntax(self): |
|
271 """ |
|
272 Private method to report a syntax error. |
|
273 """ |
|
274 exc_type, exc = sys.exc_info()[:2] |
|
275 if len(exc.args) > 1: |
|
276 offset = exc.args[1] |
|
277 if len(offset) > 2: |
|
278 offset = offset[1:3] |
|
279 else: |
|
280 offset = (1, 0) |
|
281 self.__error(offset[0] - 1, offset[1] or 0, |
|
282 'M901', exc_type.__name__, exc.args[0]) |
|
283 |
|
284 def __generateTree(self): |
|
285 """ |
|
286 Private method to generate an AST for our source. |
|
287 |
|
288 @return generated AST |
|
289 @rtype ast.AST |
|
290 """ |
|
291 return ast.parse("".join(self.__source), self.__filename) |
|
292 |
|
293 def run(self): |
271 def run(self): |
294 """ |
272 """ |
295 Public method to check the given source against miscellaneous |
273 Public method to check the given source against miscellaneous |
296 conditions. |
274 conditions. |
297 """ |
275 """ |
299 # don't do anything, if essential data is missing |
277 # don't do anything, if essential data is missing |
300 return |
278 return |
301 |
279 |
302 if not self.__checkers: |
280 if not self.__checkers: |
303 # don't do anything, if no codes were selected |
281 # don't do anything, if no codes were selected |
304 return |
|
305 |
|
306 try: |
|
307 self.__tree = self.__generateTree() |
|
308 except (SyntaxError, TypeError): |
|
309 self.__reportInvalidSyntax() |
|
310 return |
282 return |
311 |
283 |
312 for check in self.__checkers: |
284 for check in self.__checkers: |
313 check() |
285 check() |
314 |
286 |
478 break |
450 break |
479 |
451 |
480 if isinstance(node, ast.Module) or not hasCode: |
452 if isinstance(node, ast.Module) or not hasCode: |
481 return |
453 return |
482 |
454 |
483 if not (imports >= expectedImports): |
455 if imports < expectedImports: |
484 if imports: |
456 if imports: |
485 self.__error(node.lineno - 1, node.col_offset, "M701", |
457 self.__error(node.lineno - 1, node.col_offset, "M701", |
486 ", ".join(expectedImports), ", ".join(imports)) |
458 ", ".join(expectedImports), ", ".join(imports)) |
487 else: |
459 else: |
488 self.__error(node.lineno - 1, node.col_offset, "M702", |
460 self.__error(node.lineno - 1, node.col_offset, "M702", |
570 if hasStarArgs: |
542 if hasStarArgs: |
571 numArgs -= 1 |
543 numArgs -= 1 |
572 |
544 |
573 # if starargs or kwargs is not None, it can't count the |
545 # if starargs or kwargs is not None, it can't count the |
574 # parameters but at least check if the args are used |
546 # parameters but at least check if the args are used |
575 if hasKwArgs: |
547 if hasKwArgs and not names: |
576 if not names: |
548 # No names but kwargs |
577 # No names but kwargs |
549 self.__error(call.lineno - 1, call.col_offset, "M623") |
578 self.__error(call.lineno - 1, call.col_offset, "M623") |
550 if hasStarArgs and not numbers: |
579 if hasStarArgs: |
551 # No numbers but args |
580 if not numbers: |
552 self.__error(call.lineno - 1, call.col_offset, "M624") |
581 # No numbers but args |
|
582 self.__error(call.lineno - 1, call.col_offset, "M624") |
|
583 |
553 |
584 if not hasKwArgs and not hasStarArgs: |
554 if not hasKwArgs and not hasStarArgs: |
585 # can actually verify numbers and names |
555 # can actually verify numbers and names |
586 for number in sorted(numbers): |
556 for number in sorted(numbers): |
587 if number >= numArgs: |
557 if number >= numArgs: |
640 def __checkBuiltins(self): |
610 def __checkBuiltins(self): |
641 """ |
611 """ |
642 Private method to check, if built-ins are shadowed. |
612 Private method to check, if built-ins are shadowed. |
643 """ |
613 """ |
644 functionDefs = [ast.FunctionDef] |
614 functionDefs = [ast.FunctionDef] |
645 try: |
615 with contextlib.suppress(AttributeError): |
646 functionDefs.append(ast.AsyncFunctionDef) |
616 functionDefs.append(ast.AsyncFunctionDef) |
647 except AttributeError: |
|
648 pass |
|
649 |
617 |
650 ignoreBuiltinAssignments = self.__args.get( |
618 ignoreBuiltinAssignments = self.__args.get( |
651 "BuiltinsChecker", |
619 "BuiltinsChecker", |
652 MiscellaneousCheckerDefaultArgs["BuiltinsChecker"]) |
620 MiscellaneousCheckerDefaultArgs["BuiltinsChecker"]) |
653 |
621 |
828 immutableCalls = ( |
796 immutableCalls = ( |
829 "tuple", |
797 "tuple", |
830 "frozenset", |
798 "frozenset", |
831 ) |
799 ) |
832 functionDefs = [ast.FunctionDef] |
800 functionDefs = [ast.FunctionDef] |
833 try: |
801 with contextlib.suppress(AttributeError): |
834 functionDefs.append(ast.AsyncFunctionDef) |
802 functionDefs.append(ast.AsyncFunctionDef) |
835 except AttributeError: |
|
836 pass |
|
837 |
803 |
838 for node in ast.walk(self.__tree): |
804 for node in ast.walk(self.__tree): |
839 if any(isinstance(node, functionDef) |
805 if any(isinstance(node, functionDef) |
840 for functionDef in functionDefs): |
806 for functionDef in functionDefs): |
841 defaults = node.args.defaults[:] |
807 defaults = node.args.defaults[:] |
842 try: |
808 with contextlib.suppress(AttributeError): |
843 defaults += node.args.kw_defaults[:] |
809 defaults += node.args.kw_defaults[:] |
844 except AttributeError: |
|
845 pass |
|
846 for default in defaults: |
810 for default in defaults: |
847 if any(isinstance(default, mutableType) |
811 if any(isinstance(default, mutableType) |
848 for mutableType in mutableTypes): |
812 for mutableType in mutableTypes): |
849 typeName = type(default).__name__ |
813 typeName = type(default).__name__ |
850 if isinstance(default, ast.Call): |
814 if isinstance(default, ast.Call): |
941 """ |
905 """ |
942 Private method to check use of naive datetime functions. |
906 Private method to check use of naive datetime functions. |
943 """ |
907 """ |
944 # step 1: generate an augmented node tree containing parent info |
908 # step 1: generate an augmented node tree containing parent info |
945 # for each child node |
909 # for each child node |
946 tree = self.__generateTree() |
910 tree = copy.deepcopy(self.__tree) |
947 for node in ast.walk(tree): |
911 for node in ast.walk(tree): |
948 for childNode in ast.iter_child_nodes(node): |
912 for childNode in ast.iter_child_nodes(node): |
949 childNode._dtCheckerParent = node |
913 childNode._dtCheckerParent = node |
950 |
914 |
951 # step 2: perform checks and report issues |
915 # step 2: perform checks and report issues |
1023 """ |
987 """ |
1024 if sys.version_info >= (3, 8, 0): |
988 if sys.version_info >= (3, 8, 0): |
1025 if AstUtilities.isBaseString(node): |
989 if AstUtilities.isBaseString(node): |
1026 self.__addNode(node) |
990 self.__addNode(node) |
1027 else: |
991 else: |
1028 super(TextVisitor, self).generic_visit(node) |
992 super().generic_visit(node) |
1029 else: |
993 else: |
1030 super(TextVisitor, self).generic_visit(node) |
994 super().generic_visit(node) |
1031 |
995 |
1032 def __visitDefinition(self, node): |
996 def __visitDefinition(self, node): |
1033 """ |
997 """ |
1034 Private method handling class and function definitions. |
998 Private method handling class and function definitions. |
1035 |
999 |
1121 node.func.value.id == 'str' and |
1085 node.func.value.id == 'str' and |
1122 node.args and |
1086 node.args and |
1123 AstUtilities.isBaseString(node.args[0]) |
1087 AstUtilities.isBaseString(node.args[0]) |
1124 ): |
1088 ): |
1125 self.calls[node.args[0]] = (node, True) |
1089 self.calls[node.args[0]] = (node, True) |
1126 super(TextVisitor, self).generic_visit(node) |
1090 super().generic_visit(node) |
1127 |
1091 |
1128 |
1092 |
1129 class LoggingVisitor(ast.NodeVisitor): |
1093 class LoggingVisitor(ast.NodeVisitor): |
1130 """ |
1094 """ |
1131 Class implementing a node visitor to check logging statements. |
1095 Class implementing a node visitor to check logging statements. |
1190 @param node reference to the node to be processed |
1154 @param node reference to the node to be processed |
1191 @type ast.Call |
1155 @type ast.Call |
1192 @return logging level |
1156 @return logging level |
1193 @rtype str or None |
1157 @rtype str or None |
1194 """ |
1158 """ |
1195 try: |
1159 with contextlib.suppress(AttributeError): |
1196 if node.func.value.id == "warnings": |
1160 if node.func.value.id == "warnings": |
1197 return None |
1161 return None |
1198 |
1162 |
1199 if node.func.attr in LoggingVisitor.LoggingLevels: |
1163 if node.func.attr in LoggingVisitor.LoggingLevels: |
1200 return node.func.attr |
1164 return node.func.attr |
1201 except AttributeError: |
|
1202 pass |
|
1203 |
1165 |
1204 return None |
1166 return None |
1205 |
1167 |
1206 def __isFormatCall(self, node): |
1168 def __isFormatCall(self, node): |
1207 """ |
1169 """ |
1226 |
1188 |
1227 @param node reference to the node to be processed |
1189 @param node reference to the node to be processed |
1228 @type ast.Call |
1190 @type ast.Call |
1229 """ |
1191 """ |
1230 # we are in a logging statement |
1192 # we are in a logging statement |
1231 if self.__withinLoggingStatement(): |
1193 if ( |
1232 if self.__withinLoggingArgument() and self.__isFormatCall(node): |
1194 self.__withinLoggingStatement() and |
1233 self.violations.append((node, "M651")) |
1195 self.__withinLoggingArgument() and |
1234 super(LoggingVisitor, self).generic_visit(node) |
1196 self.__isFormatCall(node) |
1235 return |
1197 ): |
|
1198 self.violations.append((node, "M651")) |
|
1199 super().generic_visit(node) |
|
1200 return |
1236 |
1201 |
1237 loggingLevel = self.__detectLoggingLevel(node) |
1202 loggingLevel = self.__detectLoggingLevel(node) |
1238 |
1203 |
1239 if loggingLevel and self.__currentLoggingLevel is None: |
1204 if loggingLevel and self.__currentLoggingLevel is None: |
1240 self.__currentLoggingLevel = loggingLevel |
1205 self.__currentLoggingLevel = loggingLevel |
1241 |
1206 |
1242 # we are in some other statement |
1207 # we are in some other statement |
1243 if loggingLevel is None: |
1208 if loggingLevel is None: |
1244 super(LoggingVisitor, self).generic_visit(node) |
1209 super().generic_visit(node) |
1245 return |
1210 return |
1246 |
1211 |
1247 # we are entering a new logging statement |
1212 # we are entering a new logging statement |
1248 self.__currentLoggingCall = node |
1213 self.__currentLoggingCall = node |
1249 |
1214 |
1258 isinstance(child, ast.keyword) and |
1223 isinstance(child, ast.keyword) and |
1259 child.arg == "extra" |
1224 child.arg == "extra" |
1260 ): |
1225 ): |
1261 self.__currentExtraKeyword = child |
1226 self.__currentExtraKeyword = child |
1262 |
1227 |
1263 super(LoggingVisitor, self).visit(child) |
1228 super().visit(child) |
1264 |
1229 |
1265 self.__currentLoggingArgument = None |
1230 self.__currentLoggingArgument = None |
1266 self.__currentExtraKeyword = None |
1231 self.__currentExtraKeyword = None |
1267 |
1232 |
1268 self.__currentLoggingCall = None |
1233 self.__currentLoggingCall = None |
1283 |
1248 |
1284 # handle string concat |
1249 # handle string concat |
1285 if isinstance(node.op, ast.Add): |
1250 if isinstance(node.op, ast.Add): |
1286 self.violations.append((node, "M653")) |
1251 self.violations.append((node, "M653")) |
1287 |
1252 |
1288 super(LoggingVisitor, self).generic_visit(node) |
1253 super().generic_visit(node) |
1289 |
1254 |
1290 def visit_JoinedStr(self, node): |
1255 def visit_JoinedStr(self, node): |
1291 """ |
1256 """ |
1292 Public method to handle f-string arguments. |
1257 Public method to handle f-string arguments. |
1293 |
1258 |
1294 @param node reference to the node to be processed |
1259 @param node reference to the node to be processed |
1295 @type ast.JoinedStr |
1260 @type ast.JoinedStr |
1296 """ |
1261 """ |
1297 if self.__withinLoggingStatement(): |
1262 if ( |
1298 if any(isinstance(i, ast.FormattedValue) for i in node.values): |
1263 self.__withinLoggingStatement() and |
1299 if self.__withinLoggingArgument(): |
1264 any(isinstance(i, ast.FormattedValue) for i in node.values) and |
1300 self.violations.append((node, "M654")) |
1265 self.__withinLoggingArgument() |
1301 |
1266 ): |
1302 super(LoggingVisitor, self).generic_visit(node) |
1267 self.violations.append((node, "M654")) |
|
1268 |
|
1269 super().generic_visit(node) |
1303 |
1270 |
1304 |
1271 |
1305 class BugBearVisitor(ast.NodeVisitor): |
1272 class BugBearVisitor(ast.NodeVisitor): |
1306 """ |
1273 """ |
1307 Class implementing a node visitor to check for various topics. |
1274 Class implementing a node visitor to check for various topics. |
1332 """ |
1299 """ |
1333 self.__nodeStack.append(node) |
1300 self.__nodeStack.append(node) |
1334 self.__nodeWindow.append(node) |
1301 self.__nodeWindow.append(node) |
1335 self.__nodeWindow = self.__nodeWindow[-BugBearVisitor.NodeWindowSize:] |
1302 self.__nodeWindow = self.__nodeWindow[-BugBearVisitor.NodeWindowSize:] |
1336 |
1303 |
1337 super(BugBearVisitor, self).visit(node) |
1304 super().visit(node) |
1338 |
1305 |
1339 self.__nodeStack.pop() |
1306 self.__nodeStack.pop() |
1340 |
1307 |
1341 def visit_UAdd(self, node): |
1308 def visit_UAdd(self, node): |
1342 """ |
1309 """ |
1405 node.func.id == "setattr" and |
1372 node.func.id == "setattr" and |
1406 len(node.args) == 3 and |
1373 len(node.args) == 3 and |
1407 AstUtilities.isString(node.args[1]) |
1374 AstUtilities.isString(node.args[1]) |
1408 ): |
1375 ): |
1409 self.violations.append((node, "M513")) |
1376 self.violations.append((node, "M513")) |
1410 except (AttributeError, IndexError): |
|
1411 pass |
|
1412 |
1377 |
1413 self.generic_visit(node) |
1378 self.generic_visit(node) |
1414 |
1379 |
1415 def visit_Attribute(self, node): |
1380 def visit_Attribute(self, node): |
1416 """ |
1381 """ |
1450 |
1415 |
1451 elif len(node.targets) == 1: |
1416 elif len(node.targets) == 1: |
1452 target = node.targets[0] |
1417 target = node.targets[0] |
1453 if ( |
1418 if ( |
1454 isinstance(target, ast.Attribute) and |
1419 isinstance(target, ast.Attribute) and |
1455 isinstance(target.value, ast.Name) |
1420 isinstance(target.value, ast.Name) and |
|
1421 (target.value.id, target.attr) == ('os', 'environ') |
1456 ): |
1422 ): |
1457 if (target.value.id, target.attr) == ('os', 'environ'): |
1423 self.violations.append((node, "M506")) |
1458 self.violations.append((node, "M506")) |
|
1459 |
1424 |
1460 self.generic_visit(node) |
1425 self.generic_visit(node) |
1461 |
1426 |
1462 def visit_For(self, node): |
1427 def visit_For(self, node): |
1463 """ |
1428 """ |
2124 |
2089 |
2125 elif node.func.attr == 'strptime': |
2090 elif node.func.attr == 'strptime': |
2126 # datetime.strptime(...).replace(tzinfo=UTC) |
2091 # datetime.strptime(...).replace(tzinfo=UTC) |
2127 parent = getattr(node, '_dtCheckerParent', None) |
2092 parent = getattr(node, '_dtCheckerParent', None) |
2128 pparent = getattr(parent, '_dtCheckerParent', None) |
2093 pparent = getattr(parent, '_dtCheckerParent', None) |
2129 if not (isinstance(parent, ast.Attribute) and |
2094 if ( |
2130 parent.attr == 'replace'): |
2095 not (isinstance(parent, ast.Attribute) and |
2131 isCase1 = False |
2096 parent.attr == 'replace') or |
2132 elif not isinstance(pparent, ast.Call): |
2097 not isinstance(pparent, ast.Call) |
|
2098 ): |
2133 isCase1 = False |
2099 isCase1 = False |
2134 else: |
2100 else: |
2135 tzinfoKeyword = self.__getFromKeywords(pparent.keywords, |
2101 tzinfoKeyword = self.__getFromKeywords(pparent.keywords, |
2136 'tzinfo') |
2102 'tzinfo') |
2137 isCase1 = ( |
2103 isCase1 = ( |
2217 @return flag indicating a match |
2183 @return flag indicating a match |
2218 @rtype bool |
2184 @rtype bool |
2219 """ |
2185 """ |
2220 match = False |
2186 match = False |
2221 if ( |
2187 if ( |
2222 isinstance(node, ast.Attribute) and |
2188 (isinstance(node, ast.Attribute) and |
2223 isinstance(node.value, ast.Name) and |
2189 isinstance(node.value, ast.Name) and |
2224 node.value.id == "sys" and |
2190 node.value.id == "sys" and |
2225 node.attr == attr |
2191 node.attr == attr) or |
2226 ): |
2192 (isinstance(node, ast.Name) and |
2227 match = True |
2193 node.id == attr and |
2228 elif ( |
2194 self.__fromImports.get(node.id) == "sys") |
2229 isinstance(node, ast.Name) and |
|
2230 node.id == attr and |
|
2231 self.__fromImports.get(node.id) == "sys" |
|
2232 ): |
2195 ): |
2233 match = True |
2196 match = True |
2234 |
2197 |
2235 return match |
2198 return match |
2236 |
2199 |