A plugin for flake8 finding likely bugs and design problems in your program.
npx @tessl/cli install tessl/pypi-flake8-bugbear@24.12.00
# flake8-bugbear
1
2
A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle. This plugin implements over 70 opinionated linting rules that catch subtle bugs, design problems, and code quality issues that standard tools miss.
3
4
## Package Information
5
6
- **Package Name**: flake8-bugbear
7
- **Language**: Python
8
- **Installation**: `pip install flake8-bugbear`
9
10
## Core Imports
11
12
The plugin automatically integrates with flake8 once installed. For programmatic usage:
13
14
```python
15
from bugbear import BugBearChecker
16
```
17
18
## Basic Usage
19
20
### As a flake8 plugin (standard usage)
21
22
Once installed, flake8-bugbear automatically runs as part of flake8:
23
24
```bash
25
# Install the plugin
26
pip install flake8-bugbear
27
28
# Run flake8 - bugbear rules are automatically included
29
flake8 mycode.py
30
31
# Check that the plugin is loaded
32
flake8 --version
33
```
34
35
### Programmatic usage
36
37
```python
38
import ast
39
from bugbear import BugBearChecker
40
41
# Parse some Python code
42
code = '''
43
def example():
44
try:
45
risky_operation()
46
except: # B001: bare except
47
pass
48
'''
49
50
tree = ast.parse(code)
51
lines = code.splitlines()
52
53
# Create and run the checker
54
checker = BugBearChecker(tree=tree, filename="example.py", lines=lines)
55
errors = list(checker.run())
56
57
for error in errors:
58
print(f"{error.lineno}:{error.col} {error.message}")
59
```
60
61
## Architecture
62
63
flake8-bugbear follows flake8's plugin architecture:
64
65
- **BugBearChecker**: Main plugin class implementing flake8's checker interface
66
- **BugBearVisitor**: AST visitor that traverses code and detects rule violations
67
- **Error definitions**: 70+ predefined error types with specific codes (B001-B950)
68
- **Configuration options**: Support for extending rules and customizing behavior
69
70
The plugin registers with flake8 via entry point: `flake8.extension = {B = "bugbear:BugBearChecker"}`
71
72
## Configuration Options
73
74
flake8-bugbear supports the following configuration options:
75
76
```ini
77
[flake8]
78
# Extend the list of immutable function calls for B008/B039
79
extend-immutable-calls = frozenset,tuple
80
81
# Customize classmethod decorators for B902
82
classmethod-decorators = classmethod,my_classmethod_decorator
83
```
84
85
## Capabilities
86
87
### Main Plugin Interface
88
89
The BugBearChecker class provides the primary interface for flake8 integration and programmatic usage.
90
91
```python { .api }
92
class BugBearChecker:
93
"""
94
Main checker class implementing flake8 plugin interface.
95
96
Attributes:
97
name (str): Plugin name "flake8-bugbear"
98
version (str): Plugin version
99
tree (ast.AST): AST tree to analyze
100
filename (str): Name of file being analyzed
101
lines (List[str]): Source code lines
102
max_line_length (int): Maximum line length setting
103
options (object): Configuration options
104
"""
105
106
def run(self):
107
"""
108
Main entry point that yields error instances.
109
110
Yields:
111
Error objects with line number, column, and message
112
"""
113
114
def load_file(self) -> None:
115
"""Load file content into tree and lines attributes."""
116
117
def gen_line_based_checks(self):
118
"""
119
Generator for line-based lint checks.
120
121
Yields:
122
Error objects for line-based violations
123
"""
124
125
@classmethod
126
def adapt_error(cls, e):
127
"""
128
Adapt extended error namedtuple to be compatible with flake8.
129
130
Args:
131
e: Error namedtuple instance
132
133
Returns:
134
Adapted error tuple
135
"""
136
137
@staticmethod
138
def add_options(optmanager) -> None:
139
"""
140
Register configuration options with flake8.
141
142
Args:
143
optmanager: flake8 option manager instance
144
"""
145
146
def should_warn(self, code: str) -> bool:
147
"""
148
Check if warning code should be reported.
149
150
Args:
151
code: Error code (e.g., "B001")
152
153
Returns:
154
True if warning should be reported
155
"""
156
```
157
158
### Error Detection Rules
159
160
flake8-bugbear implements over 70 specific error detection rules. Each rule has a unique code (B001-B950) and targets specific code patterns that are likely bugs or poor design choices.
161
162
#### Logic and Exception Handling Errors (B001-B030)
163
164
```python { .api }
165
# B001: Bare except clauses
166
try:
167
risky_operation()
168
except: # Error: Use "except Exception:" instead
169
pass
170
171
# B002: Unary prefix increment
172
++n # Error: Python doesn't support this, use n += 1
173
174
# B003: os.environ assignment
175
os.environ = {} # Error: Use os.environ.clear() instead
176
177
# B004: hasattr() with __call__
178
hasattr(obj, '__call__') # Error: Use callable(obj) instead
179
180
# B005: strip() with multi-character strings
181
text.strip('abc') # Error: Misleading, use replace() or regex
182
183
# B006: Mutable default arguments
184
def func(items=[]): # Error: Use items=None, then items = items or []
185
pass
186
187
# B007: Unused loop variables
188
for i in range(10): # Error: If unused, name it _i
189
print("hello")
190
191
# B008: Function calls in argument defaults
192
def func(timestamp=time.time()): # Error: Called once at definition
193
pass
194
195
# B009-B010: getattr/setattr with known attributes
196
getattr(obj, 'attr') # Error: Use obj.attr directly
197
setattr(obj, 'attr', val) # Error: Use obj.attr = val
198
199
# B011: assert False
200
assert False # Error: Use raise AssertionError() instead
201
202
# B012: Control flow in finally blocks
203
try:
204
operation()
205
finally:
206
return value # Error: Will silence exceptions
207
208
# B013-B014: Exception handling issues
209
except (ValueError,): # B013: Redundant tuple
210
except (ValueError, ValueError): # B014: Duplicate exception types
211
```
212
213
#### Language Feature Misuse (B015-B030)
214
215
```python { .api }
216
# B015: Redundant comparisons
217
if x == True: # Error: Use if x: instead
218
219
# B016: Raising in finally
220
try:
221
operation()
222
finally:
223
raise Exception() # Error: Will override original exception
224
225
# B017: pytest.raises without match
226
with pytest.raises(ValueError): # Error: Add match= parameter
227
risky_operation()
228
229
# B018: Useless expressions
230
x + 1 # Error: Statement has no effect
231
232
# B019: lru_cache without parameters
233
@lru_cache # Error: Use @lru_cache() with parentheses
234
235
# B020: Loop variable usage outside loop
236
for i in range(10):
237
items.append(i)
238
print(i) # Error: i is not guaranteed to be defined
239
240
# B021: f-string in format()
241
"{}".format(f"{x}") # Error: Use f-string directly
242
243
# B022: Useless contextlib.suppress
244
with contextlib.suppress(): # Error: No exception types specified
245
operation()
246
247
# B023: Function definition scope issues
248
for i in range(3):
249
def func():
250
return i # Error: Will always return final loop value
251
252
# B024: Abstract methods without @abstractmethod
253
class Base:
254
def method(self):
255
raise NotImplementedError() # Error: Use @abstractmethod
256
257
# B025: Duplicate except handlers
258
try:
259
operation()
260
except ValueError:
261
handle_error()
262
except ValueError: # Error: Duplicate handler
263
handle_error()
264
265
# B026: Star-arg unpacking after keyword argument
266
def func(a, b, **kwargs, *args): # Error: *args after **kwargs is discouraged
267
pass
268
269
# B027: Empty abstract method without decorator
270
class Base:
271
def method(self): # Error: Empty method should use @abstractmethod
272
pass
273
274
# B028: warnings.warn without stacklevel
275
import warnings
276
warnings.warn("message") # Error: Should specify stacklevel=2
277
278
# B029: Empty except tuple
279
try:
280
operation()
281
except (): # Error: Empty tuple catches nothing
282
pass
283
```
284
285
#### Advanced Pattern Detection (B030-B041)
286
287
```python { .api }
288
# B030: Except handler names
289
except ValueError as e: # OK
290
except ValueError as ValueError: # Error: Shadowing exception class
291
292
# B031: itertools.islice with generators
293
list(itertools.islice(generator, 10)) # Error: Consider using itertools.takewhile
294
295
# B032: Possible unintentional type annotations
296
def func():
297
x: int # Error: Missing assignment, use x: int = 0
298
299
# B033: Duplicate set elements
300
{1, 2, 1} # Error: Duplicate element
301
302
# B034: re.sub without flags
303
re.sub(r'pattern', 'replacement', text) # Error: Consider adding flags
304
305
# B035: Static key in dict comprehension
306
{key: value for item in items} # Error: key doesn't depend on item
307
308
# B036: Found f-string with incorrect prefix
309
rf"regex {pattern}" # Error: Use fr"regex {pattern}" instead
310
311
# B037: __exit__ return value
312
def __exit__(self, exc_type, exc_val, exc_tb):
313
return True # Error: Usually should return None
314
315
# B039: contextlib.suppress with BaseException
316
with contextlib.suppress(BaseException): # Error: Too broad
317
operation()
318
319
# B040: Exception caught without being used
320
try:
321
operation()
322
except ValueError as e: # Error: e is never used
323
log("Error occurred")
324
325
# B041: Repeated key-value pairs in dict literals
326
{"key": 1, "key": 2} # Error: Duplicate key
327
```
328
329
#### Class and Method Design (B901-B911)
330
331
```python { .api }
332
# B901: return in generator
333
def generator():
334
yield 1
335
return 2 # Error: Use return without value
336
337
# B902: Invalid first argument names
338
class Example:
339
def method(bad_self): # Error: Should be 'self'
340
pass
341
342
@classmethod
343
def classmethod(bad_cls): # Error: Should be 'cls'
344
pass
345
346
# B903: Data class without slots
347
@dataclass
348
class Example: # Error: Consider @dataclass(slots=True) for performance
349
value: int
350
351
# B904: re-raise without from
352
try:
353
operation()
354
except ValueError:
355
raise RuntimeError("Failed") # Error: Use "raise ... from e"
356
357
# B905: zip() without strict parameter
358
zip(list1, list2) # Error: Use zip(list1, list2, strict=True)
359
360
# B906: visit_ methods without isinstance check
361
def visit_Name(self, node): # Error: Add isinstance(node, ast.Name) check
362
pass
363
364
# B907: JSON loads without secure defaults
365
json.loads(data) # Error: Consider security implications
366
367
# B908: Context manager protocols
368
with context_manager() as ctx:
369
pass # Various context manager usage patterns
370
371
# B909: Mutation during iteration
372
for item in items:
373
items.remove(item) # Error: Modifying collection during iteration
374
375
# B910: Counter() instead of defaultdict(int)
376
defaultdict(int) # Error: Use Counter() for better memory efficiency
377
378
# B911: itertools.batched() without explicit strict parameter
379
itertools.batched(data, 3) # Error: Add strict=True parameter
380
```
381
382
#### Line Length and Formatting (B950)
383
384
```python { .api }
385
# B950: Line too long
386
very_long_line = "This line exceeds the configured maximum length and will trigger B950" # Error if > max_line_length
387
```
388
389
### Version Information
390
391
Access to package version information:
392
393
```python { .api }
394
__version__: str # Package version string (e.g., "24.12.12")
395
```
396
397
### AST Visitor Implementation
398
399
The BugBearVisitor class handles the actual AST traversal and error detection:
400
401
```python { .api }
402
class BugBearVisitor:
403
"""
404
AST visitor that traverses code and detects rule violations.
405
406
Attributes:
407
filename (str): Name of file being analyzed
408
lines (List[str]): Source code lines
409
b008_b039_extend_immutable_calls (set): Extended immutable calls configuration
410
b902_classmethod_decorators (set): Classmethod decorators configuration
411
node_window (list): Recent AST nodes for context
412
errors (list): Collected error instances
413
contexts (list): Current context stack
414
b040_caught_exception: Exception context for B040 rule
415
"""
416
417
def visit(self, node):
418
"""Visit an AST node and apply bugbear rules."""
419
420
def generic_visit(self, node):
421
"""Generic visit implementation for unhandled node types."""
422
```
423
424
### Utility Functions
425
426
Public utility functions for AST analysis:
427
428
```python { .api }
429
def compose_call_path(node):
430
"""
431
Compose call path from AST call node.
432
433
Args:
434
node: AST node (Call, Attribute, or Name)
435
436
Yields:
437
str: Components of the call path
438
439
Example:
440
For `foo.bar.baz()`, yields: "foo", "bar", "baz"
441
"""
442
443
def is_name(node: ast.expr, name: str) -> bool:
444
"""
445
Check if AST node represents a specific name.
446
447
Args:
448
node: AST expression node to check
449
name: Name to match (supports dotted names like "typing.Generator")
450
451
Returns:
452
bool: True if node matches the given name
453
454
Example:
455
is_name(node, "typing.Generator") matches typing.Generator references
456
"""
457
```
458
459
## Error Message Format
460
461
All errors follow a consistent format:
462
463
```
464
{filename}:{line}:{column} {code} {message}
465
```
466
467
Examples:
468
```
469
example.py:5:8 B001 Do not use bare `except:`, it also catches unexpected events
470
example.py:12:4 B006 Do not use mutable data structures for argument defaults
471
example.py:20:1 B950 line too long (95 > 79 characters)
472
```
473
474
## Integration Examples
475
476
### With pre-commit
477
478
```yaml
479
repos:
480
- repo: https://github.com/PyCQA/flake8
481
rev: 6.0.0
482
hooks:
483
- id: flake8
484
additional_dependencies: [flake8-bugbear]
485
```
486
487
### With tox
488
489
```ini
490
[testenv:lint]
491
deps =
492
flake8
493
flake8-bugbear
494
commands = flake8 src tests
495
```
496
497
### With GitHub Actions
498
499
```yaml
500
- name: Run flake8
501
run: |
502
pip install flake8 flake8-bugbear
503
flake8 .
504
```