Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ecb73e6
Enhance test cases with additional ParamSpec example
vrslev Jul 7, 2025
94f2d4a
Rename test_finder.py to test_find_imports.py and update import state…
vrslev Jul 7, 2025
bcd5a35
Create global variable test cases and update transform test
vrslev Jul 7, 2025
6072f3f
Refactor test case parsing and assertion for global variable transfor…
vrslev Jul 7, 2025
2db59c8
Rename global_vars.md to global_vars_enabled.md and update test case …
vrslev Jul 7, 2025
aece3d5
Simplify global variable tests and update fixtures
vrslev Jul 7, 2025
d205229
Update test cases for global vars with ignore flag
vrslev Jul 7, 2025
4369129
Organize test utilities into conftest.py and streamline imports
vrslev Jul 7, 2025
d11cf23
Introduce fixture and parameterize ignore_global_vars in tests
vrslev Jul 7, 2025
76ee71d
Enhance Ruff linting by including FBT001 in ignored rules
vrslev Jul 7, 2025
d2c3116
Reorganize fixtures and add test case parsing function
vrslev Jul 7, 2025
7d7b394
Introduce new test cases and update existing test logic for function …
vrslev Jul 7, 2025
937a9b9
Expand test cases and update assertions for better coverage
vrslev Jul 7, 2025
814859a
Simplify import statements by removing unnecessary typing prefix
vrslev Jul 7, 2025
ba238e7
Simplify and update test cases for ignoring comments and global varia…
vrslev Jul 7, 2025
c53f031
Merge import test cases into transform imports test file
vrslev Jul 7, 2025
9b6cee3
Update Justfile to upgrade dependencies and consolidate test files in…
vrslev Jul 7, 2025
7ce5e54
Simplify and update markdown test files and remove unused script
vrslev Jul 7, 2025
a123a3d
Add README.md to explain markdown test format and usage
vrslev Jul 7, 2025
334548a
Ensure UTF-8 encoding when reading test files and add test for unused…
vrslev Jul 7, 2025
500ab38
Remove noqa comments from function signatures
vrslev Jul 7, 2025
76f6b6f
Update markdown headers to use triple hashes for consistency
vrslev Jul 7, 2025
cd1c980
Organize tests and documentation files, update path in conftest.py
vrslev Jul 7, 2025
4327249
Change section headers from H1 to H3 in multiple documents
vrslev Jul 7, 2025
86c507d
Enhance documentation by adding test descriptions and clarifications
vrslev Jul 7, 2025
10978f2
Enhance README by updating references to type checker and adding docu…
vrslev Jul 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ run *args:
uv run auto-typing-final {{ args }}

install:
uv lock
uv lock --upgrade
uv sync

lint:
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ Auto-fixer for Python code that adds `typing.Final` annotation to variable assig

Basically, this, but handles different operations (like usage of `nonlocal`, augmented assignments: `+=`, etc) as well.

- Keeps mypy happy.
- Keeps type checker happy.
- Adds global import if it's not imported yet (`import typing`/`from typing import Final`).
- Inspects one file at a time.
- Is careful with global variables: adds Final only for uppercase variables, ignores variable that are referenced in `global` statement inside functions in current file, and avoids removing Final when it already was set.
- Is careful with global variables: adds Final only for uppercase variables, ignores variable that are referenced in `global` statement inside functions in current file, and avoids removing Final when it already was set ([docs](docs/global_vars_enabled.md)).
- Ignores class variables: it is common to use `typing.ClassVar` instead of `typing.Final`.

## How To Use
Expand Down Expand Up @@ -59,7 +59,7 @@ auto-typing-final . --ignore-global-vars

### Ignore comment

You can ignore variables by adding `# auto-typing-final: ignore` comment to the line.
You can ignore variables by adding `# auto-typing-final: ignore` comment to the line ([docs](docs/ignore_comment.md)).

### VS Code Extension

Expand Down
2 changes: 1 addition & 1 deletion auto_typing_final/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from auto_typing_final.transform import IMPORT_STYLES_TO_IMPORT_CONFIGS, ImportConfig, ImportStyle, make_replacements


def transform_file_content(source: str, import_config: ImportConfig, ignore_global_vars: bool) -> str: # noqa: FBT001
def transform_file_content(source: str, import_config: ImportConfig, ignore_global_vars: bool) -> str:
root: Final = SgRoot(source, "python").root()
result: Final = make_replacements(root, import_config, ignore_global_vars)
new_text: Final = root.commit_edits(
Expand Down
4 changes: 2 additions & 2 deletions auto_typing_final/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def _should_skip_global_variable(definition: Definition) -> bool:
)


def _make_operation_from_definitions_of_one_name(nodes: list[SgNode], ignore_global_vars: bool) -> Operation | None: # noqa: FBT001
def _make_operation_from_definitions_of_one_name(nodes: list[SgNode], ignore_global_vars: bool) -> Operation | None:
value_definitions: Final[list[Definition]] = []
has_node_inside_loop = False
has_global_scope_definition = False
Expand Down Expand Up @@ -215,7 +215,7 @@ class MakeReplacementsResult:
import_text: str | None


def make_replacements(root: SgNode, import_config: ImportConfig, ignore_global_vars: bool) -> MakeReplacementsResult: # noqa: FBT001
def make_replacements(root: SgNode, import_config: ImportConfig, ignore_global_vars: bool) -> MakeReplacementsResult:
replacements: Final = []
has_added_final = False
imports_result: Final = find_imports_of_identifier_in_scope(root, module_name="typing", identifier_name="Final")
Expand Down
9 changes: 9 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Human-readable tests.

How to interpret them:
- `# insert` indicates where Final should be added
- `# remove` indicates where Final should be removed

Tests will assert that lines are changed according to this comments. If unexpected fix applied, tests will fail. This means that these tests are documentation as well!

Most headings are AI-generated.
210 changes: 210 additions & 0 deletions docs/function_vars.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
Tests for variables inside functions.

### Variable Declaration with Type Annotation
```python
def foo():
a: int
```

### Variable Assignment with Inline Comment
```python
def foo():
a = 1 # insert
```

### Variable Declaration with Final Type Annotation
```python
def foo():
a: Final = 1
```

### Variable Declaration with Type Annotation and Assignment
```python
def foo():
a: int = 1 # insert
```

### Annotated Variable with Custom Metadata
```python
def foo():
a: typing.Annotated[int, 'hello'] = 1 # insert
```

### List Type Annotation and Assignment
```python
def foo():
a: list[int] = 1 # insert
```

### Variable Assignment with Previous Declaration
```python
def foo():
b = 1
a = 2 # insert
b = 3
```

### Variable Assignment with Reassignment of Another Variable
```python
def foo():
b = 1
b = 2
a = 3 # insert
```

### Variable Assignment with Later Reassignment
```python
def foo():
a = 1 # insert
b = 2
b = 3
```

### Reassignment of a Variable with Previous Assignment
```python
def foo():
a = 1
a = 2
b: int
```

### Variable Declaration After Assignment
```python
def foo():
a = 1
a: int
```

### Variable Declaration Before Assignment
```python
def foo():
a: int
a = 1
```

### Final Variable Declaration Before Assignment
```python
def foo():
a: Final
a = 1
```

### Redundant Type Annotation with Final
```python
def foo():
a: int
a: int = 1
```

### Simultaneous Variable Assignment
```python
def foo():
a, b = 1, 2
```

### Tuple Assignment with Parentheses
```python
def foo():
(a, b) = 1, 2
```

### Tuple Assignment from Function Return
```python
def foo():
(a, b) = t()
```

### List Assignment from Function Return
```python
def foo():
[a, b] = t()
```

### Single Element List Assignment
```python
def foo():
[a] = t()
```

### Multiple Variable Assignment in a Single Line
```python
def foo():
a = b = 1
```

### Chained Multiple Variable Assignment
```python
def foo():
a = b = c = 1
```

### Variable Assignment with Walrus Operator
```python
def foo():
a = (b := 1) # insert
```

### Reassignment with Final Annotation
```python
def foo():
a = 1
a: Final[int] = 2 # remove
```

### Reassignment with Final Type Declaration
```python
def foo():
a = 1
a: Final = 2 # remove
```

### Reassignment with Final Type Declaration (No Space)
```python
def foo():
a = 1
a: Final=2 # remove
```

### Simple Variable Reassignment
```python
def foo():
a = 1
a =2
```

### Type Annotation Followed by Final Reassignment
```python
def foo():
a: int = 1
a: Final[int] = 2 # remove
```

### Final Reassignment After Int Type Annotation
```python
def foo():
a: int = 1
a: Final = 2 # remove
```

### Multiple Reassignments with Final and Type Annotation
```python
def foo():
a: Final = 1 # remove
a: Final = 2 # remove
a = 3
a: int = 4
```

### Final and Assignment in a Single Line
```python
def foo():
a: Final = b = 1
```

### Variable Assignment and Final Annotation
```python
def foo():
a = 1 # insert
b = 2
b: Final[int] = 3 # remove
```
115 changes: 115 additions & 0 deletions docs/global_vars_enabled.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
Test for global variables for case when running **without** `ignore-global-vars`.

### Constants and Variable Assignments
```python
MY_CONSTANT = 42 # insert
MY_OTHER_CONSTANT = "hello" # insert
```

### Debug Flag Assignment
```python
DEBUG = True # insert
```

### Global vs. Local Variable Scope
```python
MY_CONSTANT = 42 # insert
global_var = "hello"

def foo():
local_var = 1 # insert
```

### Constants with Type Hints
```python
MY_CONSTANT: typing.Final = 42
MY_OTHER_CONSTANT = "hello" # insert
```

### Constants with Explicit Typing
```python
MY_CONSTANT: int = 42 # insert
MY_OTHER_CONSTANT = "hello" # insert
```

### Constant Shadowing in Function Scope
```python
MY_CONSTANT = 42 # insert

def foo():
MY_CONSTANT = 1 # insert
local_var = 2 # insert
```

### Union Types for Constants
```python
FRUIT = Apple | Banana # insert
```

### Code Examples with Header Improvements

### Unchanged Global Assignment
```python
global_var = 42
```

### Simple Assignment
```python
myVar = "hello"
```

### Constant Assignment
```python
A = 42
```

### Constants with Global Statement

```python
MY_CONSTANT = 42

def foo():
global MY_CONSTANT
MY_CONSTANT = 1
```

### Constants with Global Declaration
```python
from foo import MY_CONSTANT
MY_CONSTANT = 42
```

### Final Assignment for Constants
```python
a: typing.Final = 1
```

### TypeVar Declaration for Generics
```python
_T = typing.TypeVar("_T")
```

### Final TypeVar Declaration
```python
_T: typing.Final = typing.TypeVar("_T")
```

### ParamSpec Declaration for Function Generics
```python
_P = typing.ParamSpec("_P")
```

### Final ParamSpec Declaration
```python
_P: typing.Final = typing.ParamSpec("_P")
```

### Union Types for Variables
```python
Fruit = Apple | Banana
```

### String Assignment for Constants
```python
A = "ParamSpec"
```
Loading