From d4cb8d971d5b8c6f1798e2c431f4dadda9455903 Mon Sep 17 00:00:00 2001 From: yangdanny97 Date: Thu, 2 Jan 2025 21:37:15 -0500 Subject: [PATCH 1/6] variadic callable --- docs/guide/builtins.rst | 20 ++- src/fixit/rules/variadic_callable_syntax.py | 134 ++++++++++++++++++++ 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 src/fixit/rules/variadic_callable_syntax.py diff --git a/docs/guide/builtins.rst b/docs/guide/builtins.rst index 6f2b1484..a6c47b65 100644 --- a/docs/guide/builtins.rst +++ b/docs/guide/builtins.rst @@ -43,6 +43,7 @@ Built-in Rules - :class:`UseClsInClassmethod` - :class:`UseFstring` - :class:`UseTypesFromTyping` +- :class:`VariadicCallableSyntax` .. class:: AvoidOrInExcept @@ -1380,4 +1381,21 @@ Built-in Rules from fixit import LintRule class CatsRuleDogsDroolRule(LintRule): ... - \ No newline at end of file + +.. class:: VariadicCallableSyntax + + Callable types with arbitrary parameters are written as `Callable[..., T]`, not `Callable[[...], T]` + + .. attribute:: AUTOFIX + :type: Yes + + .. attribute:: VALID + + .. code:: python + from typing import Callable + x: Callable[..., int] + .. attribute:: INVALID + + .. code:: python + from typing import Callable + x: Callable[[...], int] diff --git a/src/fixit/rules/variadic_callable_syntax.py b/src/fixit/rules/variadic_callable_syntax.py new file mode 100644 index 00000000..e167c04f --- /dev/null +++ b/src/fixit/rules/variadic_callable_syntax.py @@ -0,0 +1,134 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import libcst as cst +import libcst.matchers as m +from libcst.metadata import QualifiedName, QualifiedNameProvider, QualifiedNameSource + +from fixit import Invalid, LintRule, Valid + + +class VariadicCallableSyntax(LintRule): + """ + Callable types with arbitrary parameters are written as `Callable[..., T]`, not `Callable[[...], T]` + """ + + METADATA_DEPENDENCIES = (QualifiedNameProvider,) + VALID = [ + Valid( + """ + from typing import Callable + x: Callable[[int], int] + """ + ), + Valid( + """ + from typing import Callable + x: Callable[[int, int, ...], int] + """ + ), + Valid( + """ + from typing import Callable + x: Callable + """ + ), + Valid( + """ + from typing import Callable as C + x: C[..., int] = ... + """ + ), + Valid( + """ + from typing import Callable + def foo(bar: Optional[Callable[..., int]]) -> Callable[..., int]: + ... + """ + ), + Valid( + """ + import typing as t + x: t.Callable[..., int] = ... + """ + ), + Valid( + """ + from typing import Callable + x: Callable[..., int] = ... + """ + ), + ] + INVALID = [ + Invalid( + """ + from typing import Callable + x: Callable[[...], int] = ... + """, + expected_replacement=""" + from typing import Callable + x: Callable[..., int] = ... + """, + ), + Invalid( + """ + import typing as t + x: t.Callable[[...], int] = ... + """, + expected_replacement=""" + import typing as t + x: t.Callable[..., int] = ... + """, + ), + Invalid( + """ + from typing import Callable as C + x: C[[...], int] = ... + """, + expected_replacement=""" + from typing import Callable as C + x: C[..., int] = ... + """, + ), + Invalid( + """ + from typing import Callable + def foo(bar: Optional[Callable[[...], int]]) -> Callable[[...], int]: + ... + """, + expected_replacement=""" + from typing import Callable + def foo(bar: Optional[Callable[..., int]]) -> Callable[..., int]: + ... + """, + ), + ] + + def __init__(self) -> None: + super().__init__() + + def visit_Subscript(self, node: cst.Subscript) -> None: + if not QualifiedNameProvider.has_name( + self, + node, + QualifiedName(name="typing.Callable", source=QualifiedNameSource.IMPORT), + ): + return + node_matches = len(node.slice) == 2 and m.matches( + node.slice[0], + m.SubscriptElement( + slice=m.Index(value=m.List(elements=[m.Element(m.Ellipsis())])) + ), + ) + if not node_matches: + return + slices = list(node.slice) + slices[0] = cst.SubscriptElement(cst.Index(cst.Ellipsis())) + new_node = node.with_changes(slice=slices) + self.report( + node, + "Callable types with arbitrary parameters are written as `Callable[..., T]`, not `Callable[[...], T]`", + replacement=node.deep_replace(node, new_node), + ) From b3039e2c29ce985d10490a04fb774bcbb4c40cc3 Mon Sep 17 00:00:00 2001 From: yangdanny97 Date: Thu, 2 Jan 2025 22:07:19 -0500 Subject: [PATCH 2/6] move docs --- docs/guide/builtins.rst | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/guide/builtins.rst b/docs/guide/builtins.rst index a6c47b65..8dc95f4f 100644 --- a/docs/guide/builtins.rst +++ b/docs/guide/builtins.rst @@ -1158,6 +1158,24 @@ Built-in Rules def function(list: list[str]) -> None: pass +.. class:: VariadicCallableSyntax + + Callable types with arbitrary parameters are written as `Callable[..., T]`, not `Callable[[...], T]` + + .. attribute:: AUTOFIX + :type: Yes + + .. attribute:: VALID + + .. code:: python + from typing import Callable + x: Callable[..., int] + .. attribute:: INVALID + + .. code:: python + from typing import Callable + x: Callable[[...], int] + ``fixit.rules.extra`` ^^^^^^^^^^^^^^^^^^^^^ @@ -1381,21 +1399,3 @@ Built-in Rules from fixit import LintRule class CatsRuleDogsDroolRule(LintRule): ... - -.. class:: VariadicCallableSyntax - - Callable types with arbitrary parameters are written as `Callable[..., T]`, not `Callable[[...], T]` - - .. attribute:: AUTOFIX - :type: Yes - - .. attribute:: VALID - - .. code:: python - from typing import Callable - x: Callable[..., int] - .. attribute:: INVALID - - .. code:: python - from typing import Callable - x: Callable[[...], int] From 4348cbf14212ed23deb2a3869a62fdff1ffb6f54 Mon Sep 17 00:00:00 2001 From: yangdanny97 Date: Thu, 2 Jan 2025 22:07:38 -0500 Subject: [PATCH 3/6] spacing --- docs/guide/builtins.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/guide/builtins.rst b/docs/guide/builtins.rst index 8dc95f4f..d016ec29 100644 --- a/docs/guide/builtins.rst +++ b/docs/guide/builtins.rst @@ -1170,6 +1170,7 @@ Built-in Rules .. code:: python from typing import Callable x: Callable[..., int] + .. attribute:: INVALID .. code:: python From 0f68f9b9239c0f02a83880faeaf6804e2f6e5f14 Mon Sep 17 00:00:00 2001 From: yangdanny97 Date: Thu, 2 Jan 2025 22:55:24 -0500 Subject: [PATCH 4/6] fix docs --- docs/guide/builtins.rst | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/docs/guide/builtins.rst b/docs/guide/builtins.rst index d016ec29..0ee8236a 100644 --- a/docs/guide/builtins.rst +++ b/docs/guide/builtins.rst @@ -1157,7 +1157,6 @@ Built-in Rules def function(list: list[str]) -> None: pass - .. class:: VariadicCallableSyntax Callable types with arbitrary parameters are written as `Callable[..., T]`, not `Callable[[...], T]` @@ -1165,17 +1164,38 @@ Built-in Rules .. attribute:: AUTOFIX :type: Yes + .. attribute:: VALID .. code:: python + + from typing import Callable + x: Callable[[int], int] + .. code:: python + from typing import Callable - x: Callable[..., int] + x: Callable[[int, int, ...], int] .. attribute:: INVALID .. code:: python + + from typing import Callable + x: Callable[[...], int] = ... + + # suggested fix from typing import Callable - x: Callable[[...], int] + x: Callable[..., int] = ... + + .. code:: python + + import typing as t + x: t.Callable[[...], int] = ... + + # suggested fix + import typing as t + x: t.Callable[..., int] = ... + ``fixit.rules.extra`` ^^^^^^^^^^^^^^^^^^^^^ From 37b19dfdfef88fbcef0e3d65ee5f46e5229a4ae2 Mon Sep 17 00:00:00 2001 From: yangdanny97 Date: Fri, 3 Jan 2025 12:27:19 -0500 Subject: [PATCH 5/6] spacing --- docs/guide/builtins.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/guide/builtins.rst b/docs/guide/builtins.rst index 0ee8236a..ab1b9e58 100644 --- a/docs/guide/builtins.rst +++ b/docs/guide/builtins.rst @@ -1420,3 +1420,4 @@ Built-in Rules from fixit import LintRule class CatsRuleDogsDroolRule(LintRule): ... + From accc81cfe633bf7737a6a235548dcd61b09d2907 Mon Sep 17 00:00:00 2001 From: yangdanny97 Date: Fri, 3 Jan 2025 14:06:40 -0500 Subject: [PATCH 6/6] spacing --- docs/guide/builtins.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/builtins.rst b/docs/guide/builtins.rst index ab1b9e58..ab70482a 100644 --- a/docs/guide/builtins.rst +++ b/docs/guide/builtins.rst @@ -1420,4 +1420,4 @@ Built-in Rules from fixit import LintRule class CatsRuleDogsDroolRule(LintRule): ... - + \ No newline at end of file