From 8dea1260d34caaa93384784b5203f17e3e1a2bf0 Mon Sep 17 00:00:00 2001 From: Niklas Rosenstein Date: Mon, 18 Jul 2022 19:39:59 +0200 Subject: [PATCH] fix: fix parsing trailing comma after keyword remainder argument in function definition (before it would accidentally consider the comma as a positional argument) --- docspec-python/.changelog/_unreleased.toml | 5 +++ docspec-python/src/docspec_python/parser.py | 2 + docspec-python/test/test_parser.py | 43 ++++++++++++++++++++- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 docspec-python/.changelog/_unreleased.toml diff --git a/docspec-python/.changelog/_unreleased.toml b/docspec-python/.changelog/_unreleased.toml new file mode 100644 index 0000000..1516b5f --- /dev/null +++ b/docspec-python/.changelog/_unreleased.toml @@ -0,0 +1,5 @@ +[[entries]] +id = "9698224d-eca1-41c5-9cf5-c04d34a607fe" +type = "fix" +description = "fix parsing trailing comma after keyword remainder argument in function definition (before it would accidentally consider the comma as a positional argument)" +author = "@NiklasRosenstein" diff --git a/docspec-python/src/docspec_python/parser.py b/docspec-python/src/docspec_python/parser.py index 158446b..839e9eb 100644 --- a/docspec-python/src/docspec_python/parser.py +++ b/docspec-python/src/docspec_python/parser.py @@ -361,6 +361,8 @@ def parse_parameters(self, parameters): elif node and node.type == token.DOUBLESTAR: node = index.next() result.append(self.parse_argument(node, Argument.Type.KEYWORD_REMAINDER, index)) + argtype = Argument.Type.KEYWORD_ONLY + index.advance() else: result.append(self.parse_argument(node, argtype, index)) diff --git a/docspec-python/test/test_parser.py b/docspec-python/test/test_parser.py index 4a72bd8..01ed949 100644 --- a/docspec-python/test/test_parser.py +++ b/docspec-python/test/test_parser.py @@ -33,14 +33,14 @@ loc = Location('', 0, None) -def mkfunc(name: str, docstring: Optional[str], lineno: int, args: List[Argument], modifiers: Optional[List[str]] = None) -> Function: +def mkfunc(name: str, docstring: Optional[str], lineno: int, args: List[Argument], modifiers: Optional[List[str]] = None, return_type: Optional[str] = None) -> Function: return Function( name=name, location=loc, docstring=Docstring(Location(get_callsite().code_name, lineno), docstring) if docstring else None, modifiers=modifiers, args=args, - return_type=None, + return_type=return_type, decorations=[], ) @@ -433,3 +433,42 @@ def test_format_arglist(): Argument(loc, 'tqdm_kwargs', Argument.Type.KEYWORD_REMAINDER, None, None, None), ], ['async']) assert format_arglist(func.args, True) == 'cls, *fs, loop=None, timeout=None, total=None, **tqdm_kwargs' + + + +@docspec_test() +def test_funcdef_with_trailing_comma(): + """ + def build_docker_image( + name: str = "buildDocker", + default: bool = False, + dockerfile: str = "docker/release.Dockerfile", + project: Project | None = None, + auth: dict[str, tuple[str, str]] | None = None, + secrets: dict[str, str] | None = None, + image_qualifier: str | None = None, + platforms: list[str] | None = None, + **kwargs: Any, + ) -> Task: + pass + """ + + return [ + mkfunc( + "build_docker_image", + None, + 0, + [ + Argument(loc, "name", Argument.Type.POSITIONAL, None, "str", '"buildDocker"'), + Argument(loc, "default", Argument.Type.POSITIONAL, None, "bool", 'False'), + Argument(loc, "dockerfile", Argument.Type.POSITIONAL, None, "str", '"docker/release.Dockerfile"'), + Argument(loc, "project", Argument.Type.POSITIONAL, None, "Project | None", 'None'), + Argument(loc, "auth", Argument.Type.POSITIONAL, None, "dict[str, tuple[str, str]] | None", 'None'), + Argument(loc, "secrets", Argument.Type.POSITIONAL, None, "dict[str, str] | None", 'None'), + Argument(loc, "image_qualifier", Argument.Type.POSITIONAL, None, "str | None", 'None'), + Argument(loc, "platforms", Argument.Type.POSITIONAL, None, "list[str] | None", 'None'), + Argument(loc, "kwargs", Argument.Type.KEYWORD_REMAINDER, None, "Any", None), + ], + return_type="Task" + ), + ]