|
28 | 28 | ast.GeneratorExp, |
29 | 29 | ) |
30 | 30 | FUNCTION_NODES = (ast.AsyncFunctionDef, ast.FunctionDef, ast.Lambda) |
| 31 | +B908_pytest_functions = {"raises", "warns"} |
| 32 | +B908_unittest_methods = { |
| 33 | + "assertRaises", |
| 34 | + "assertRaisesRegex", |
| 35 | + "assertRaisesRegexp", |
| 36 | + "assertWarns", |
| 37 | + "assertWarnsRegex", |
| 38 | +} |
31 | 39 |
|
32 | 40 | Context = namedtuple("Context", ["node", "stack"]) |
33 | 41 |
|
@@ -504,6 +512,7 @@ def visit_Raise(self, node): |
504 | 512 | def visit_With(self, node): |
505 | 513 | self.check_for_b017(node) |
506 | 514 | self.check_for_b022(node) |
| 515 | + self.check_for_b908(node) |
507 | 516 | self.generic_visit(node) |
508 | 517 |
|
509 | 518 | def visit_JoinedStr(self, node): |
@@ -1104,6 +1113,39 @@ def check_for_b022(self, node): |
1104 | 1113 | ): |
1105 | 1114 | self.errors.append(B022(node.lineno, node.col_offset)) |
1106 | 1115 |
|
| 1116 | + @staticmethod |
| 1117 | + def _is_assertRaises_like(node: ast.withitem) -> bool: |
| 1118 | + if not ( |
| 1119 | + isinstance(node, ast.withitem) |
| 1120 | + and isinstance(node.context_expr, ast.Call) |
| 1121 | + and isinstance(node.context_expr.func, (ast.Attribute, ast.Name)) |
| 1122 | + ): |
| 1123 | + return False |
| 1124 | + if isinstance(node.context_expr.func, ast.Name): |
| 1125 | + # "with raises" |
| 1126 | + return node.context_expr.func.id in B908_pytest_functions |
| 1127 | + elif isinstance(node.context_expr.func, ast.Attribute) and isinstance( |
| 1128 | + node.context_expr.func.value, ast.Name |
| 1129 | + ): |
| 1130 | + return ( |
| 1131 | + # "with pytest.raises" |
| 1132 | + node.context_expr.func.value.id == "pytest" |
| 1133 | + and node.context_expr.func.attr in B908_pytest_functions |
| 1134 | + ) or ( |
| 1135 | + # "with self.assertRaises" |
| 1136 | + node.context_expr.func.value.id == "self" |
| 1137 | + and node.context_expr.func.attr in B908_unittest_methods |
| 1138 | + ) |
| 1139 | + else: |
| 1140 | + return False |
| 1141 | + |
| 1142 | + def check_for_b908(self, node: ast.With): |
| 1143 | + if len(node.body) < 2: |
| 1144 | + return |
| 1145 | + for node_item in node.items: |
| 1146 | + if self._is_assertRaises_like(node_item): |
| 1147 | + self.errors.append(B908(node.lineno, node.col_offset)) |
| 1148 | + |
1107 | 1149 | def check_for_b025(self, node): |
1108 | 1150 | seen = [] |
1109 | 1151 | for handler in node.handlers: |
@@ -1759,7 +1801,12 @@ def visit_Lambda(self, node): |
1759 | 1801 | " flag." |
1760 | 1802 | ) |
1761 | 1803 | ) |
1762 | | - |
| 1804 | +B908 = Error( |
| 1805 | + message=( |
| 1806 | + "B908 assertRaises-type context should not contains more than one top-level" |
| 1807 | + " statement." |
| 1808 | + ) |
| 1809 | +) |
1763 | 1810 | B950 = Error(message="B950 line too long ({} > {} characters)") |
1764 | 1811 |
|
1765 | | -disabled_by_default = ["B901", "B902", "B903", "B904", "B905", "B906", "B950"] |
| 1812 | +disabled_by_default = ["B901", "B902", "B903", "B904", "B905", "B906", "B908", "B950"] |
0 commit comments