0
# flake8-simplify
1
2
A flake8 plugin that helps simplify Python code by detecting patterns that can be improved for readability and maintainability. It provides over 40 different rules that analyze Python AST and suggest code simplifications, including combining isinstance calls, using context managers, replacing nested if statements, utilizing built-in functions, and modernizing Python constructs.
3
4
## Package Information
5
6
- **Package Name**: flake8-simplify
7
- **Language**: Python
8
- **Installation**: `pip install flake8-simplify`
9
- **Python Version**: 3.6.1+
10
- **Dependencies**: `astor>=0.1`, `flake8>=3.7`, `importlib-metadata>=0.9` (Python < 3.8)
11
12
## Core Imports
13
14
The plugin automatically registers with flake8 through entry points (registered as "SIM") and doesn't require explicit imports in typical usage scenarios. Users simply install the package and run flake8.
15
16
For direct programmatic access to the plugin class (advanced usage only):
17
18
```python
19
from flake8_simplify import Plugin
20
```
21
22
However, the standard workflow requires no imports - the plugin integrates automatically via the entry point defined in pyproject.toml:
23
24
```toml
25
[project.entry-points]
26
"flake8.extension" = {SIM = "flake8_simplify:Plugin"}
27
```
28
29
## Basic Usage
30
31
flake8-simplify integrates seamlessly with flake8. After installation, it automatically analyzes your code when running flake8:
32
33
```bash
34
# Install the plugin
35
pip install flake8-simplify
36
37
# Run flake8 on your code
38
flake8 your_file.py
39
40
# Or run on entire directory
41
flake8 .
42
```
43
44
Example output:
45
```
46
./foo.py:10:12: SIM101 Multiple isinstance-calls which can be merged into a single call for variable 'value'
47
./bar.py:25:8: SIM108 Use ternary operator instead of if-else-assignment
48
./baz.py:42:1: SIM115 Use context handler for opening files
49
```
50
51
## Architecture
52
53
The plugin follows flake8's standard plugin architecture:
54
55
- **Plugin**: Main entry point that flake8 discovers and instantiates
56
- **Visitor**: AST visitor that traverses Python code and applies all rules
57
- **Rule Functions**: Individual functions implementing specific simplification checks
58
- **Utility Classes**: Extended AST node classes with metadata for rule analysis
59
60
Each rule function analyzes specific AST node types and returns violations as tuples containing line number, column offset, and error message. This design allows the plugin to efficiently check multiple simplification patterns in a single AST traversal.
61
62
## Capabilities
63
64
### Core Plugin API
65
66
The main plugin interface for flake8 integration, providing the entry point for code analysis and rule execution.
67
68
```python { .api }
69
class Plugin:
70
"""Main flake8 plugin class."""
71
name = __name__ # Class attribute: Plugin identifier (__name__ resolves to "flake8_simplify")
72
version = importlib_metadata.version(__name__) # Class attribute: Plugin version from package metadata
73
74
def __init__(self, tree: ast.AST):
75
"""
76
Initialize plugin with AST tree to analyze.
77
78
Parameters:
79
- tree: ast.AST - Python AST tree to analyze
80
"""
81
82
def run(self) -> Generator[Tuple[int, int, str, Type[Any]], None, None]:
83
"""
84
Execute analysis and yield violations.
85
86
Returns:
87
Generator yielding tuples of (line, column, message, plugin_type)
88
"""
89
90
class Visitor(ast.NodeVisitor):
91
"""AST visitor implementing all simplification rules."""
92
errors: List[Tuple[int, int, str]] # Collected violations
93
94
def __init__(self):
95
"""Initialize visitor with empty error list."""
96
97
# Visit methods for each AST node type
98
def visit_Assign(self, node: ast.Assign) -> Any: ...
99
def visit_Call(self, node: ast.Call) -> Any: ...
100
def visit_With(self, node: ast.With) -> Any: ...
101
def visit_Expr(self, node: ast.Expr) -> None: ...
102
def visit_BoolOp(self, node: ast.BoolOp) -> None: ...
103
def visit_If(self, node: ast.If) -> None: ...
104
def visit_For(self, node: ast.For) -> None: ...
105
def visit_Subscript(self, node: ast.Subscript) -> None: ...
106
def visit_Try(self, node: ast.Try) -> None: ...
107
def visit_UnaryOp(self, node_v: ast.UnaryOp) -> None: ...
108
def visit_IfExp(self, node: ast.IfExp) -> None: ...
109
def visit_Compare(self, node: ast.Compare) -> None: ...
110
def visit_ClassDef(self, node: ast.ClassDef) -> None: ...
111
112
def add_meta(root: ast.AST, level: int = 0) -> None:
113
"""
114
Add parent and sibling metadata to AST nodes.
115
116
Adds parent, previous_sibling, and next_sibling attributes to all AST nodes
117
to enable rule analysis that requires knowledge of node relationships.
118
119
Parameters:
120
- root: ast.AST - Root AST node to process
121
- level: int - Recursion depth (default: 0)
122
"""
123
```
124
125
### Assignment Rules
126
127
Rules for detecting and simplifying variable assignment patterns.
128
129
```python { .api }
130
def get_sim904(node: ast.Assign) -> List[Tuple[int, int, str]]:
131
"""
132
SIM904: Dict assignment that can be simplified.
133
134
Detects: a = {}; a['key'] = 'value'
135
Suggests: a = {'key': 'value'}
136
137
Parameters:
138
- node: ast.Assign - Assignment node to analyze
139
140
Returns:
141
List of (line, column, message) tuples
142
"""
143
144
def get_sim909(node: Assign) -> List[Tuple[int, int, str]]:
145
"""
146
SIM909: Remove reflexive assignments.
147
148
Detects: foo = foo
149
Suggests: Remove redundant assignment
150
151
Parameters:
152
- node: Assign - Extended assignment node with metadata
153
154
Returns:
155
List of (line, column, message) tuples
156
"""
157
```
158
159
### Boolean Operation Rules
160
161
Rules for simplifying boolean expressions and logical operations.
162
163
```python { .api }
164
def get_sim101(node: ast.BoolOp) -> List[Tuple[int, int, str]]:
165
"""
166
SIM101: Merge multiple isinstance calls.
167
168
Detects: isinstance(a, int) or isinstance(a, float)
169
Suggests: isinstance(a, (int, float))
170
171
Parameters:
172
- node: ast.BoolOp - Boolean operation node
173
174
Returns:
175
List of (line, column, message) tuples
176
"""
177
178
def get_sim109(node: ast.BoolOp) -> List[Tuple[int, int, str]]:
179
"""
180
SIM109: Use tuple for multiple equality checks.
181
182
Detects: value == a or value == b or value == c
183
Suggests: value in (a, b, c)
184
185
Parameters:
186
- node: ast.BoolOp - Boolean operation node
187
188
Returns:
189
List of (line, column, message) tuples
190
"""
191
192
def get_sim220(node: ast.BoolOp) -> List[Tuple[int, int, str]]:
193
"""
194
SIM220: Replace contradictory expressions with False.
195
196
Detects: a and not a
197
Suggests: False
198
199
Parameters:
200
- node: ast.BoolOp - Boolean operation node
201
202
Returns:
203
List of (line, column, message) tuples
204
"""
205
206
def get_sim221(node: ast.BoolOp) -> List[Tuple[int, int, str]]:
207
"""
208
SIM221: Replace tautological expressions with True.
209
210
Detects: a or not a
211
Suggests: True
212
213
Parameters:
214
- node: ast.BoolOp - Boolean operation node
215
216
Returns:
217
List of (line, column, message) tuples
218
"""
219
220
def get_sim222(node: ast.BoolOp) -> List[Tuple[int, int, str]]:
221
"""
222
SIM222: Simplify OR with True.
223
224
Detects: ... or True
225
Suggests: True
226
227
Parameters:
228
- node: ast.BoolOp - Boolean operation node
229
230
Returns:
231
List of (line, column, message) tuples
232
"""
233
234
def get_sim223(node: ast.BoolOp) -> List[Tuple[int, int, str]]:
235
"""
236
SIM223: Simplify AND with False.
237
238
Detects: ... and False
239
Suggests: False
240
241
Parameters:
242
- node: ast.BoolOp - Boolean operation node
243
244
Returns:
245
List of (line, column, message) tuples
246
"""
247
```
248
249
### Function Call Rules
250
251
Rules for optimizing function calls and method invocations.
252
253
```python { .api }
254
def get_sim115(node: Call) -> List[Tuple[int, int, str]]:
255
"""
256
SIM115: Use context manager for file operations.
257
258
Detects: f = open('file.txt'); ...
259
Suggests: with open('file.txt') as f: ...
260
261
Parameters:
262
- node: Call - Extended call node with metadata
263
264
Returns:
265
List of (line, column, message) tuples
266
"""
267
268
def get_sim901(node: ast.Call) -> List[Tuple[int, int, str]]:
269
"""
270
SIM901: Remove unnecessary bool() wrapper.
271
272
Detects: bool(a == b)
273
Suggests: a == b
274
275
Parameters:
276
- node: ast.Call - Function call node
277
278
Returns:
279
List of (line, column, message) tuples
280
"""
281
282
def get_sim905(node: ast.Call) -> List[Tuple[int, int, str]]:
283
"""
284
SIM905: Use list literal instead of string split.
285
286
Detects: "a,b,c".split(",")
287
Suggests: ["a", "b", "c"]
288
289
Parameters:
290
- node: ast.Call - Function call node
291
292
Returns:
293
List of (line, column, message) tuples
294
"""
295
296
def get_sim906(node: ast.Call) -> List[Tuple[int, int, str]]:
297
"""
298
SIM906: Merge nested os.path.join calls.
299
300
Detects: os.path.join(os.path.join(a, b), c)
301
Suggests: os.path.join(a, b, c)
302
303
Parameters:
304
- node: ast.Call - Function call node
305
306
Returns:
307
List of (line, column, message) tuples
308
"""
309
310
def get_sim910(node: Call) -> List[Tuple[int, int, str]]:
311
"""
312
SIM910: Remove explicit None default from dict.get.
313
314
Detects: dict.get(key, None)
315
Suggests: dict.get(key)
316
317
Parameters:
318
- node: Call - Extended call node with metadata
319
320
Returns:
321
List of (line, column, message) tuples
322
"""
323
324
def get_sim911(node: ast.Call) -> List[Tuple[int, int, str]]:
325
"""
326
SIM911: Use dict.items() instead of zip(keys(), values()).
327
328
Detects: zip(dict.keys(), dict.values())
329
Suggests: dict.items()
330
331
Parameters:
332
- node: ast.Call - Function call node
333
334
Returns:
335
List of (line, column, message) tuples
336
"""
337
```
338
339
### Control Flow Rules
340
341
Rules for simplifying if statements, loops, and conditional expressions.
342
343
```python { .api }
344
def get_sim102(node: ast.If) -> List[Tuple[int, int, str]]:
345
"""
346
SIM102: Combine nested if statements.
347
348
Detects: if a: if b: ...
349
Suggests: if a and b: ...
350
351
Parameters:
352
- node: ast.If - If statement node
353
354
Returns:
355
List of (line, column, message) tuples
356
"""
357
358
def get_sim103(node: ast.If) -> List[Tuple[int, int, str]]:
359
"""
360
SIM103: Return boolean condition directly.
361
362
Detects: if condition: return True else: return False
363
Suggests: return condition
364
365
Parameters:
366
- node: ast.If - If statement node
367
368
Returns:
369
List of (line, column, message) tuples
370
"""
371
372
def get_sim108(node: If) -> List[Tuple[int, int, str]]:
373
"""
374
SIM108: Use ternary operator for assignments.
375
376
Detects: if condition: x = a else: x = b
377
Suggests: x = a if condition else b
378
379
Parameters:
380
- node: If - Extended if node with metadata
381
382
Returns:
383
List of (line, column, message) tuples
384
"""
385
386
def get_sim114(node: ast.If) -> List[Tuple[int, int, str]]:
387
"""
388
SIM114: Combine if-elif with same body using OR.
389
390
Detects: if a: body elif b: body
391
Suggests: if a or b: body
392
393
Parameters:
394
- node: ast.If - If statement node
395
396
Returns:
397
List of (line, column, message) tuples
398
"""
399
400
def get_sim116(node: ast.If) -> List[Tuple[int, int, str]]:
401
"""
402
SIM116: Use dictionary instead of if-elif equality checks.
403
404
Detects: Long if-elif chains with equality comparisons
405
Suggests: Dictionary lookup pattern
406
407
Parameters:
408
- node: ast.If - If statement node
409
410
Returns:
411
List of (line, column, message) tuples
412
"""
413
414
def get_sim401(node: ast.If) -> List[Tuple[int, int, str]]:
415
"""
416
SIM401: Use dict.get() with default instead of if-else.
417
418
Detects: if key in dict: x = dict[key] else: x = default
419
Suggests: x = dict.get(key, default)
420
421
Parameters:
422
- node: ast.If - If statement node
423
424
Returns:
425
List of (line, column, message) tuples
426
"""
427
428
def get_sim908(node: ast.If) -> List[Tuple[int, int, str]]:
429
"""
430
SIM908: Use dict.get() for key existence checks.
431
432
Detects: if key in dict: dict[key]
433
Suggests: dict.get(key)
434
435
Parameters:
436
- node: ast.If - If statement node
437
438
Returns:
439
List of (line, column, message) tuples
440
"""
441
```
442
443
### Loop Rules
444
445
Rules for optimizing for loops and replacing them with more Pythonic constructs.
446
447
```python { .api }
448
def get_sim104(node: ast.For) -> List[Tuple[int, int, str]]:
449
"""
450
SIM104: Use yield from for generator delegation.
451
452
Detects: for item in iterable: yield item
453
Suggests: yield from iterable
454
455
Parameters:
456
- node: ast.For - For loop node
457
458
Returns:
459
List of (line, column, message) tuples
460
"""
461
462
def get_sim110_sim111(node: ast.For) -> List[Tuple[int, int, str]]:
463
"""
464
SIM110/SIM111: Use any() or all() for loop patterns.
465
466
SIM110 - Detects: for x in iterable: if condition: return True
467
SIM110 - Suggests: return any(condition for x in iterable)
468
469
SIM111 - Detects: for x in iterable: if condition: return False
470
SIM111 - Suggests: return all(not condition for x in iterable)
471
472
Parameters:
473
- node: ast.For - For loop node
474
475
Returns:
476
List of (line, column, message) tuples
477
"""
478
479
def get_sim113(node: For) -> List[Tuple[int, int, str]]:
480
"""
481
SIM113: Use enumerate instead of manual counter.
482
483
Detects: i = 0; for item in iterable: ...; i += 1
484
Suggests: for i, item in enumerate(iterable): ...
485
486
Parameters:
487
- node: For - Extended for node with metadata
488
489
Returns:
490
List of (line, column, message) tuples
491
"""
492
```
493
494
### Comparison and Unary Operation Rules
495
496
Rules for simplifying comparison operations and unary expressions.
497
498
```python { .api }
499
def get_sim118(node: ast.Compare) -> List[Tuple[int, int, str]]:
500
"""
501
SIM118: Use 'key in dict' instead of 'key in dict.keys()'.
502
503
Detects: key in dict.keys()
504
Suggests: key in dict
505
506
Parameters:
507
- node: ast.Compare - Comparison node
508
509
Returns:
510
List of (line, column, message) tuples
511
"""
512
513
def get_sim300(node: ast.Compare) -> List[Tuple[int, int, str]]:
514
"""
515
SIM300: Use natural comparison order (avoid Yoda conditions).
516
517
Detects: 42 == age
518
Suggests: age == 42
519
520
Parameters:
521
- node: ast.Compare - Comparison node
522
523
Returns:
524
List of (line, column, message) tuples
525
"""
526
527
def get_sim201(node: UnaryOp) -> List[Tuple[int, int, str]]:
528
"""
529
SIM201: Use '!=' instead of 'not =='.
530
531
Detects: not (a == b)
532
Suggests: a != b
533
534
Parameters:
535
- node: UnaryOp - Extended unary operation node
536
537
Returns:
538
List of (line, column, message) tuples
539
"""
540
541
def get_sim202(node: UnaryOp) -> List[Tuple[int, int, str]]:
542
"""
543
SIM202: Use '==' instead of 'not !='.
544
545
Detects: not (a != b)
546
Suggests: a == b
547
548
Parameters:
549
- node: UnaryOp - Extended unary operation node
550
551
Returns:
552
List of (line, column, message) tuples
553
"""
554
555
def get_sim203(node: UnaryOp) -> List[Tuple[int, int, str]]:
556
"""
557
SIM203: Use 'not in' instead of 'not (in)'.
558
559
Detects: not (a in b)
560
Suggests: a not in b
561
562
Parameters:
563
- node: UnaryOp - Extended unary operation node
564
565
Returns:
566
List of (line, column, message) tuples
567
"""
568
569
def get_sim208(node: UnaryOp) -> List[Tuple[int, int, str]]:
570
"""
571
SIM208: Remove double negation.
572
573
Detects: not (not a)
574
Suggests: a
575
576
Parameters:
577
- node: UnaryOp - Extended unary operation node
578
579
Returns:
580
List of (line, column, message) tuples
581
"""
582
```
583
584
### Ternary Expression Rules
585
586
Rules for simplifying conditional expressions (ternary operators).
587
588
```python { .api }
589
def get_sim210(node: ast.IfExp) -> List[Tuple[int, int, str]]:
590
"""
591
SIM210: Use bool() instead of True if condition else False.
592
593
Detects: True if condition else False
594
Suggests: bool(condition)
595
596
Parameters:
597
- node: ast.IfExp - Ternary expression node
598
599
Returns:
600
List of (line, column, message) tuples
601
"""
602
603
def get_sim211(node: ast.IfExp) -> List[Tuple[int, int, str]]:
604
"""
605
SIM211: Use 'not condition' instead of False if condition else True.
606
607
Detects: False if condition else True
608
Suggests: not condition
609
610
Parameters:
611
- node: ast.IfExp - Ternary expression node
612
613
Returns:
614
List of (line, column, message) tuples
615
"""
616
617
def get_sim212(node: ast.IfExp) -> List[Tuple[int, int, str]]:
618
"""
619
SIM212: Use natural order for ternary expressions.
620
621
Detects: b if not a else a
622
Suggests: a if a else b
623
624
Parameters:
625
- node: ast.IfExp - Ternary expression node
626
627
Returns:
628
List of (line, column, message) tuples
629
"""
630
```
631
632
### Exception Handling and Context Manager Rules
633
634
Rules for improving exception handling and resource management patterns.
635
636
```python { .api }
637
def get_sim105(node: ast.Try) -> List[Tuple[int, int, str]]:
638
"""
639
SIM105: Use contextlib.suppress for try-except-pass.
640
641
Detects: try: risky_operation() except SomeException: pass
642
Suggests: from contextlib import suppress; with suppress(SomeException): risky_operation()
643
644
Parameters:
645
- node: ast.Try - Try statement node
646
647
Returns:
648
List of (line, column, message) tuples
649
"""
650
651
def get_sim107(node: ast.Try) -> List[Tuple[int, int, str]]:
652
"""
653
SIM107: Avoid return in try/except and finally.
654
655
Detects: return statements in both try/except and finally blocks
656
Suggests: Restructure control flow to avoid confusing returns
657
658
Parameters:
659
- node: ast.Try - Try statement node
660
661
Returns:
662
List of (line, column, message) tuples
663
"""
664
665
def get_sim117(node: ast.With) -> List[Tuple[int, int, str]]:
666
"""
667
SIM117: Merge nested with statements.
668
669
Detects: with a: with b: ...
670
Suggests: with a, b: ...
671
672
Parameters:
673
- node: ast.With - With statement node
674
675
Returns:
676
List of (line, column, message) tuples
677
"""
678
```
679
680
### Class and Type Rules
681
682
Rules for modernizing class definitions and type annotations.
683
684
```python { .api }
685
def get_sim120(node: ast.ClassDef) -> List[Tuple[int, int, str]]:
686
"""
687
SIM120: Use modern class definition syntax.
688
689
Detects: class FooBar(object):
690
Suggests: class FooBar:
691
692
Parameters:
693
- node: ast.ClassDef - Class definition node
694
695
Returns:
696
List of (line, column, message) tuples
697
"""
698
699
def get_sim907(node: ast.Subscript) -> List[Tuple[int, int, str]]:
700
"""
701
SIM907: Use Optional[Type] instead of Union[Type, None].
702
703
Detects: Union[str, None]
704
Suggests: Optional[str]
705
706
Parameters:
707
- node: ast.Subscript - Subscript node (for type annotations)
708
709
Returns:
710
List of (line, column, message) tuples
711
"""
712
```
713
714
### Environment and Expression Rules
715
716
Rules for improving environment variable usage and expression patterns.
717
718
```python { .api }
719
def get_sim112(node: ast.Expr) -> List[Tuple[int, int, str]]:
720
"""
721
SIM112: Use CAPITAL environment variable names.
722
723
Detects: os.environ['path']
724
Suggests: os.environ['PATH']
725
726
Parameters:
727
- node: ast.Expr - Expression node
728
729
Returns:
730
List of (line, column, message) tuples
731
"""
732
```
733
734
## Utility Classes and Functions
735
736
### Extended AST Node Classes
737
738
Enhanced AST node classes that include parent and sibling relationships for rule analysis.
739
740
```python { .api }
741
class UnaryOp(ast.UnaryOp):
742
"""Extended UnaryOp with parent tracking."""
743
parent: ast.Expr # Parent node reference
744
745
def __init__(self, orig: ast.UnaryOp):
746
"""Copy constructor from original UnaryOp node."""
747
748
class Call(ast.Call):
749
"""Extended Call with parent tracking."""
750
parent: ast.Expr # Parent node reference
751
752
def __init__(self, orig: ast.Call):
753
"""Copy constructor from original Call node."""
754
755
class If(ast.If):
756
"""Extended If with parent tracking."""
757
parent: ast.Expr # Parent node reference
758
759
def __init__(self, orig: ast.If):
760
"""Copy constructor from original If node."""
761
762
class For(ast.For):
763
"""Extended For with parent/sibling tracking."""
764
parent: ast.AST # Parent node reference
765
previous_sibling: ast.AST # Previous sibling node
766
767
def __init__(self, orig: ast.For):
768
"""Copy constructor from original For node."""
769
770
class Assign(ast.Assign):
771
"""Extended Assign with parent/sibling tracking."""
772
parent: ast.AST # Parent node reference
773
previous_sibling: ast.AST # Previous sibling node
774
orig: ast.Assign # Original node reference
775
776
def __init__(self, orig: ast.Assign):
777
"""Copy constructor from original Assign node."""
778
```
779
780
### Code Analysis Utilities
781
782
Helper functions for analyzing and transforming AST nodes.
783
784
```python { .api }
785
def to_source(node: Union[None, ast.expr, ast.Expr, ast.withitem, ast.slice, ast.Assign]) -> str:
786
"""
787
Convert AST node to source code string.
788
789
Parameters:
790
- node: AST node or None
791
792
Returns:
793
Source code string representation
794
"""
795
796
def strip_parenthesis(string: str) -> str:
797
"""
798
Remove surrounding parentheses from string.
799
800
Parameters:
801
- string: String to process
802
803
Returns:
804
String without outer parentheses
805
"""
806
807
def strip_triple_quotes(string: str) -> str:
808
"""
809
Convert triple quotes to double quotes.
810
811
Parameters:
812
- string: String with potential triple quotes
813
814
Returns:
815
String with normalized quotes
816
"""
817
818
def use_double_quotes(string: str) -> str:
819
"""
820
Convert single quotes to double quotes.
821
822
Parameters:
823
- string: String with potential single quotes
824
825
Returns:
826
String with double quotes
827
"""
828
829
def is_body_same(body1: List[ast.stmt], body2: List[ast.stmt]) -> bool:
830
"""
831
Check if two statement lists are equivalent.
832
833
Parameters:
834
- body1: First list of AST statements
835
- body2: Second list of AST statements
836
837
Returns:
838
True if equivalent, False otherwise
839
"""
840
841
def is_stmt_equal(a: ast.stmt, b: ast.stmt) -> bool:
842
"""
843
Deep comparison of AST statements.
844
845
Parameters:
846
- a: First AST statement
847
- b: Second AST statement
848
849
Returns:
850
True if equal, False otherwise
851
"""
852
853
def get_if_body_pairs(node: ast.If) -> List[Tuple[ast.expr, List[ast.stmt]]]:
854
"""
855
Extract condition-body pairs from if-elif chain.
856
857
Parameters:
858
- node: If statement node
859
860
Returns:
861
List of (condition, body) tuples
862
"""
863
864
def is_constant_increase(expr: ast.AugAssign) -> bool:
865
"""
866
Check if augmented assignment increments by constant 1.
867
868
Parameters:
869
- expr: Augmented assignment node
870
871
Returns:
872
True if increments by 1, False otherwise
873
"""
874
875
def is_exception_check(node: ast.If) -> bool:
876
"""
877
Check if if-statement is an exception check pattern.
878
879
Parameters:
880
- node: If statement node
881
882
Returns:
883
True if raises exception, False otherwise
884
"""
885
886
def is_same_expression(a: ast.expr, b: ast.expr) -> bool:
887
"""
888
Check if two expressions are identical.
889
890
Parameters:
891
- a: First expression node
892
- b: Second expression node
893
894
Returns:
895
True if same, False otherwise
896
"""
897
898
def expression_uses_variable(expr: ast.expr, var: str) -> bool:
899
"""
900
Check if expression references a variable.
901
902
Parameters:
903
- expr: Expression node
904
- var: Variable name to check
905
906
Returns:
907
True if variable is used, False otherwise
908
"""
909
910
def body_contains_continue(stmts: List[ast.stmt]) -> bool:
911
"""
912
Check if statement list contains continue statements.
913
914
Parameters:
915
- stmts: List of statements
916
917
Returns:
918
True if contains continue, False otherwise
919
"""
920
921
def _get_duplicated_isinstance_call_by_node(node: ast.BoolOp) -> List[str]:
922
"""
923
Get a list of isinstance arguments which could be shortened (used by SIM101).
924
925
Analyzes boolean operations to find isinstance calls that can be combined.
926
For example: isinstance(a, int) or isinstance(a, float) can become isinstance(a, (int, float))
927
928
Parameters:
929
- node: ast.BoolOp - Boolean operation node containing isinstance calls
930
931
Returns:
932
List of variable names that have multiple isinstance calls
933
"""
934
```
935
936
## Constants
937
938
Python version compatibility constants for different AST node types. These constants handle differences between Python versions in AST node representation.
939
940
```python { .api }
941
# ast.Constant in Python 3.8+, ast.NameConstant in Python 3.6-3.7
942
if hasattr(ast, 'NameConstant'):
943
BOOL_CONST_TYPES = (ast.Constant, ast.NameConstant)
944
AST_CONST_TYPES = (ast.Constant, ast.NameConstant, ast.Str, ast.Num)
945
STR_TYPES = (ast.Constant, ast.Str)
946
else:
947
BOOL_CONST_TYPES = (ast.Constant,)
948
AST_CONST_TYPES = (ast.Constant,)
949
STR_TYPES = (ast.Constant,)
950
```
951
952
## Types
953
954
```python { .api }
955
from typing import Any, Generator, List, Tuple, Type, Union, DefaultDict
956
from collections import defaultdict
957
import ast
958
import itertools
959
import astor
960
import importlib_metadata # For Python < 3.8 compatibility
961
962
# Type aliases for rule function return values
963
RuleViolation = Tuple[int, int, str] # (line, column, message)
964
RuleResult = List[RuleViolation]
965
966
# Extended AST node types with metadata
967
ExtendedASTNode = Union[UnaryOp, Call, If, For, Assign]
968
```