-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
(feat) Add type annotations checker #10749
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
qequ
wants to merge
16
commits into
pylint-dev:main
Choose a base branch
from
qequ:feature/require-typeannotation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+921
β6
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
dec6452
Add type annotations checker
qequ 967978e
Add tests
qequ 9ec24bb
Add docs
qequ 75252e6
pylint
qequ 98a3b52
fix id's clash
qequ a4e8361
update docs
qequ 74eb013
corrections
qequ 1ce8b62
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 249aace
fix doc issue
qequ 01c0431
Merge branch 'feature/require-typeannotation' of github.com:qequ/pyliβ¦
qequ c4e156a
fix tests
qequ 54e81fe
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 34606a8
fix tests
qequ 4802c54
Merge branch 'feature/require-typeannotation' of github.com:qequ/pyliβ¦
qequ 7df9b81
coverage
qequ ffc43d6
fix
qequ File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| def greet(name) -> str: # [missing-param-type-annotation] | ||
| return f"Hello, {name}!" | ||
|
|
||
|
|
||
| def add(x, y) -> int: # [missing-param-type-annotation, missing-param-type-annotation] | ||
| return x + y | ||
|
|
||
|
|
||
| def process( # [missing-param-type-annotation, missing-param-type-annotation] | ||
| *args, **kwargs | ||
| ) -> dict: | ||
| return combine(args, kwargs) |
20 changes: 20 additions & 0 deletions
20
doc/data/messages/m/missing-param-type-annotation/details.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| Type annotations improve code readability and enable better static analysis. This check | ||
| ensures that all function and method parameters have type annotations, making the expected | ||
| types clear and allowing type checkers like mypy to verify correct usage. | ||
|
|
||
| This check is opt-in (disabled by default) to maintain backward compatibility. Enable it | ||
| with ``--enable=missing-param-type-annotation``. | ||
|
|
||
| The check automatically skips: | ||
|
|
||
| - ``self`` and ``cls`` parameters in methods | ||
| - Parameters in abstract methods (``@abstractmethod``, ``@abstractproperty``) | ||
| - Parameters in overload stub definitions (``@typing.overload``) | ||
|
|
||
| All parameter types are checked, including: | ||
|
|
||
| - Regular positional parameters | ||
| - Positional-only parameters (before ``/``) | ||
| - Keyword-only parameters (after ``*``) | ||
| - Variadic positional parameters (``*args``) | ||
| - Variadic keyword parameters (``**kwargs``) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| def greet(name: str) -> str: | ||
| return f"Hello, {name}!" | ||
|
|
||
|
|
||
| def add(x: int, y: int) -> int: | ||
| return x + y | ||
|
|
||
|
|
||
| def process(*args: str, **kwargs: bool) -> dict: | ||
| return combine(args, kwargs) | ||
|
|
||
|
|
||
| class Calculator: | ||
| def compute(self, x: int, y: int) -> int: # self doesn't need annotation | ||
| return x + y |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| def calculate_sum(numbers: list[int]): # [missing-return-type-annotation] | ||
| return sum(numbers) | ||
|
|
||
|
|
||
| async def fetch_data(url: str): # [missing-return-type-annotation] | ||
| return await get(url) |
13 changes: 13 additions & 0 deletions
13
doc/data/messages/m/missing-return-type-annotation/details.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| Type annotations improve code readability and enable better static analysis. This check | ||
| ensures that all functions and methods have return type annotations, making the code's | ||
| intent clearer and allowing type checkers like mypy to verify correctness. | ||
|
|
||
| This check is opt-in (disabled by default) to maintain backward compatibility. Enable it | ||
| with ``--enable=missing-return-type-annotation``. | ||
|
|
||
| The check automatically skips: | ||
|
|
||
| - ``__init__`` methods (which implicitly return None) | ||
| - Abstract methods (``@abstractmethod``, ``@abstractproperty``) | ||
| - Properties and their setters/deleters | ||
| - Overload stub definitions (``@typing.overload``) | ||
11 changes: 11 additions & 0 deletions
11
doc/data/messages/m/missing-return-type-annotation/good.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| def calculate_sum(numbers: list[int]) -> int: | ||
| return sum(numbers) | ||
|
|
||
|
|
||
| async def fetch_data(url: str) -> dict: | ||
| return await get(url) | ||
|
|
||
|
|
||
| class Calculator: | ||
| def __init__(self, initial: int): # __init__ doesn't need return type | ||
| self.value = initial |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| Add ``missing-return-type-annotation`` and ``missing-param-type-annotation`` checks to enforce type annotation presence in functions and methods. | ||
|
|
||
| These new convention-level checks help teams enforce type annotation standards. Both checks are opt-in (disabled by default) and can be enabled independently for granular control. The checks intelligently skip ``self``/``cls`` parameters, ``__init__`` methods (return type only), and methods decorated with ``@abstractmethod``, ``@property``, or ``@typing.overload``. | ||
|
|
||
| Closes #3853 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html | ||
| # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE | ||
| # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt | ||
|
|
||
| """Checker for type annotations in function definitions.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import TYPE_CHECKING | ||
|
|
||
| from astroid import nodes | ||
|
|
||
| from pylint import checkers | ||
| from pylint.checkers import utils | ||
|
|
||
| if TYPE_CHECKING: | ||
| from pylint.lint import PyLinter | ||
|
|
||
|
|
||
| class TypeAnnotationChecker(checkers.BaseChecker): | ||
| """Checker for enforcing type annotations on functions and methods. | ||
|
|
||
| This checker verifies that functions and methods have appropriate | ||
| type annotations for return values and parameters. | ||
| """ | ||
|
|
||
| name = "type-annotation" | ||
| msgs = { | ||
| "C3801": ( | ||
| "Missing return type annotation for function %r", | ||
| "missing-return-type-annotation", | ||
| "Used when a function or method does not have a return type annotation. " | ||
| "Type annotations improve code readability and help with static type checking.", | ||
| {"default_enabled": False}, | ||
| ), | ||
| "C3802": ( | ||
| "Missing type annotation for parameter %r in function %r", | ||
| "missing-param-type-annotation", | ||
| "Used when a function or method parameter does not have a type annotation. " | ||
| "Type annotations improve code readability and help with static type checking.", | ||
| {"default_enabled": False}, | ||
| ), | ||
| } | ||
|
|
||
| @utils.only_required_for_messages( | ||
| "missing-return-type-annotation", "missing-param-type-annotation" | ||
| ) | ||
| def visit_functiondef(self, node: nodes.FunctionDef) -> None: | ||
| """Check for missing type annotations in regular functions.""" | ||
| self._check_return_type_annotation(node) | ||
| self._check_param_type_annotations(node) | ||
|
|
||
| visit_asyncfunctiondef = visit_functiondef | ||
|
|
||
| def _check_return_type_annotation( | ||
| self, node: nodes.FunctionDef | nodes.AsyncFunctionDef | ||
| ) -> None: | ||
| """Check if a function has a return type annotation. | ||
|
|
||
| Args: | ||
| node: The function definition node to check | ||
| """ | ||
| if node.returns is not None: | ||
| return | ||
|
|
||
| if node.type_comment_returns: | ||
| return | ||
|
|
||
| if node.name == "__init__": | ||
| return | ||
|
|
||
| if utils.decorated_with(node, ["abc.abstractmethod", "abc.abstractproperty"]): | ||
| return | ||
|
|
||
| if utils.decorated_with( | ||
| node, ["typing.overload", "typing_extensions.overload"] | ||
| ): | ||
| return | ||
|
|
||
| if utils.decorated_with(node, ["property", "builtins.property"]): | ||
| return | ||
|
|
||
| if utils.is_property_setter_or_deleter(node): | ||
| return | ||
|
|
||
| self.add_message("missing-return-type-annotation", node=node, args=(node.name,)) | ||
|
|
||
| def _check_param_type_annotations( | ||
| self, node: nodes.FunctionDef | nodes.AsyncFunctionDef | ||
| ) -> None: | ||
| """Check if function parameters have type annotations. | ||
|
|
||
| Args: | ||
| node: The function definition node to check | ||
| """ | ||
| if utils.decorated_with(node, ["abc.abstractmethod", "abc.abstractproperty"]): | ||
| return | ||
|
|
||
| if utils.decorated_with( | ||
| node, ["typing.overload", "typing_extensions.overload"] | ||
| ): | ||
| return | ||
|
|
||
| if utils.is_property_setter_or_deleter(node): | ||
| return | ||
|
|
||
| arguments = node.args | ||
|
|
||
| # Check positional-only args | ||
| if arguments.posonlyargs: | ||
| annotations = arguments.posonlyargs_annotations or [] | ||
| for idx, arg in enumerate(arguments.posonlyargs): | ||
| if arg.name in {"self", "cls"}: | ||
| continue | ||
| if idx >= len(annotations) or annotations[idx] is None: | ||
| self.add_message( | ||
| "missing-param-type-annotation", | ||
| node=node, | ||
| args=(arg.name, node.name), | ||
| ) | ||
|
|
||
| # Check regular args (skip self/cls for methods) | ||
| if arguments.args: | ||
| annotations = arguments.annotations or [] | ||
| start_idx = 0 | ||
| if ( | ||
| arguments.args | ||
| and arguments.args[0].name in {"self", "cls"} | ||
| and isinstance(node.parent, nodes.ClassDef) | ||
| ): | ||
| start_idx = 1 | ||
|
|
||
| for idx, arg in enumerate(arguments.args[start_idx:], start=start_idx): | ||
| if idx >= len(annotations) or annotations[idx] is None: | ||
| self.add_message( | ||
| "missing-param-type-annotation", | ||
| node=node, | ||
| args=(arg.name, node.name), | ||
| ) | ||
|
|
||
| # Check *args | ||
| if arguments.vararg and not arguments.varargannotation: | ||
| self.add_message( | ||
| "missing-param-type-annotation", | ||
| node=node, | ||
| args=(arguments.vararg, node.name), | ||
| ) | ||
|
|
||
| # Check keyword-only args | ||
| if arguments.kwonlyargs: | ||
| annotations = arguments.kwonlyargs_annotations or [] | ||
| for idx, arg in enumerate(arguments.kwonlyargs): | ||
| if idx >= len(annotations) or annotations[idx] is None: | ||
| self.add_message( | ||
| "missing-param-type-annotation", | ||
| node=node, | ||
| args=(arg.name, node.name), | ||
| ) | ||
|
|
||
| # Check **kwargs | ||
| if arguments.kwarg and not arguments.kwargannotation: | ||
| self.add_message( | ||
| "missing-param-type-annotation", | ||
| node=node, | ||
| args=(arguments.kwarg, node.name), | ||
| ) | ||
|
|
||
|
|
||
| def register(linter: PyLinter) -> None: | ||
| """Register the checker with the linter.""" | ||
| linter.register_checker(TypeAnnotationChecker(linter)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can also add function and methods starting with
test_for return-type ? I always found adding-> Nonein all tests to be rather pointless. But then mypy would disagree and we probably want to agree with mypy. (Btw just gave me an idea of a checker to check that function starting withtest_should not return anything).