From 1fdab339743d5aa5214f4a8cb4af57b6b4536158 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 3 Apr 2023 19:59:14 +0200 Subject: [PATCH] feat: parse docstrings in the `epydoc` format (#82) Closes #16. Closes #17. ### Summary of Changes * Parse docstrings in the `epydoc` format * Add a flag to the CLI to choose the docstring format (supports the settings `plaintext`, `numpy` and `epydoc` at the moment) * Refactor the parser for `Numpydoc` to use the `docstring_parser` --- .github/linters/.ruff.toml | 1 - poetry.lock | 215 +--------------- pyproject.toml | 2 +- src/library_analyzer/cli/_cli.py | 20 +- src/library_analyzer/cli/_run_all.py | 4 +- src/library_analyzer/cli/_run_api.py | 4 +- .../processing/api/_ast_visitor.py | 12 +- .../processing/api/_get_api.py | 12 +- .../processing/api/_get_parameter_list.py | 8 +- .../api/docstring_parsing/__init__.py | 17 ++ .../_abstract_documentation_parser.py | 2 +- .../_create_docstring_parser.py | 20 ++ .../api/docstring_parsing/_docstring_style.py | 22 ++ .../api/docstring_parsing/_epydoc_parser.py | 81 ++++++ .../api/docstring_parsing/_helpers.py | 32 +++ .../_numpydoc_parser.py | 73 ++---- .../_plaintext_docstring_parser.py} | 10 +- .../api/documentation_parsing/__init__.py | 13 - .../_get_full_docstring.py | 15 -- .../processing/api/model/_api.py | 12 +- .../processing/api/model/_documentation.py | 2 - .../__init__.py | 0 .../docstring_parsing/test_epydoc_parser.py | 241 ++++++++++++++++++ .../test_get_full_docstring.py | 2 +- .../test_numpydoc_parser.py | 12 +- .../test_plaintext_docstring_parser.py} | 30 +-- .../processing/api/model/test_api.py | 3 +- .../api/model/test_documentation.py | 4 +- .../processing/api/test_get_parameter_list.py | 8 +- .../processing/migration/model/test_differ.py | 6 +- .../model/test_inheritance_differ.py | 10 +- .../migration/model/test_mapping.py | 14 +- .../migration/model/test_strict_differ.py | 6 +- .../migration/model/test_unchanged_differ.py | 6 +- .../annotations/called_after_migration.py | 54 ++-- .../annotations/description_migration.py | 18 +- .../migration/annotations/expert_migration.py | 16 +- .../migration/annotations/group_migration.py | 28 +- tests/migration/annotations/move_migration.py | 20 +- .../migration/annotations/remove_migration.py | 18 +- .../migration/annotations/rename_migration.py | 2 +- tests/migration/annotations/todo_migration.py | 2 +- tests/migration/test_migration.py | 8 +- 43 files changed, 628 insertions(+), 457 deletions(-) create mode 100644 src/library_analyzer/processing/api/docstring_parsing/__init__.py rename src/library_analyzer/processing/api/{documentation_parsing => docstring_parsing}/_abstract_documentation_parser.py (95%) create mode 100644 src/library_analyzer/processing/api/docstring_parsing/_create_docstring_parser.py create mode 100644 src/library_analyzer/processing/api/docstring_parsing/_docstring_style.py create mode 100644 src/library_analyzer/processing/api/docstring_parsing/_epydoc_parser.py create mode 100644 src/library_analyzer/processing/api/docstring_parsing/_helpers.py rename src/library_analyzer/processing/api/{documentation_parsing => docstring_parsing}/_numpydoc_parser.py (60%) rename src/library_analyzer/processing/api/{documentation_parsing/_default_documentation_parser.py => docstring_parsing/_plaintext_docstring_parser.py} (74%) delete mode 100644 src/library_analyzer/processing/api/documentation_parsing/__init__.py delete mode 100644 src/library_analyzer/processing/api/documentation_parsing/_get_full_docstring.py rename tests/library_analyzer/processing/api/{documentation_parsing => docstring_parsing}/__init__.py (100%) create mode 100644 tests/library_analyzer/processing/api/docstring_parsing/test_epydoc_parser.py rename tests/library_analyzer/processing/api/{documentation_parsing => docstring_parsing}/test_get_full_docstring.py (94%) rename tests/library_analyzer/processing/api/{documentation_parsing => docstring_parsing}/test_numpydoc_parser.py (93%) rename tests/library_analyzer/processing/api/{documentation_parsing/test_default_documentation_parser.py => docstring_parsing/test_plaintext_docstring_parser.py} (73%) diff --git a/.github/linters/.ruff.toml b/.github/linters/.ruff.toml index d0c4c012..ec84ebfc 100644 --- a/.github/linters/.ruff.toml +++ b/.github/linters/.ruff.toml @@ -37,7 +37,6 @@ select = [ "INT", "ARG", "PTH", - "ERA", "PGH", "PL", "TRY", diff --git a/poetry.lock b/poetry.lock index ea411cce..5504ee8c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,16 +1,4 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. - -[[package]] -name = "alabaster" -version = "0.7.13" -description = "A configurable sidebar-enabled Sphinx theme" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, - {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, -] +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "anyio" @@ -175,18 +163,6 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope.interface"] tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] -[[package]] -name = "babel" -version = "2.12.1" -description = "Internationalization utilities" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, - {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, -] - [[package]] name = "backcall" version = "0.2.0" @@ -731,15 +707,15 @@ files = [ ] [[package]] -name = "docutils" -version = "0.19" -description = "Docutils -- Python Documentation Utilities" +name = "docstring-parser" +version = "0.15" +description = "Parse Python docstrings in reST, Google and Numpydoc format" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6,<4.0" files = [ - {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, - {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, + {file = "docstring_parser-0.15-py3-none-any.whl", hash = "sha256:d1679b86250d269d06a99670924d6bce45adc00b08069dae8c47d98e89b667a9"}, + {file = "docstring_parser-0.15.tar.gz", hash = "sha256:48ddc093e8b1865899956fcc03b03e66bb7240c310fac5af81814580c55bf682"}, ] [[package]] @@ -847,18 +823,6 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -[[package]] -name = "imagesize" -version = "1.4.1" -description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] - [[package]] name = "iniconfig" version = "2.0.0" @@ -2124,25 +2088,6 @@ files = [ {file = "numpy-1.24.2.tar.gz", hash = "sha256:003a9f530e880cb2cd177cba1af7220b9aa42def9c4afc2a2fc3ee6be7eb2b22"}, ] -[[package]] -name = "numpydoc" -version = "1.5.0" -description = "Sphinx extension to support docstrings in Numpy format" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "numpydoc-1.5.0-py3-none-any.whl", hash = "sha256:c997759fb6fc32662801cece76491eedbc0ec619b514932ffd2b270ae89c07f9"}, - {file = "numpydoc-1.5.0.tar.gz", hash = "sha256:b0db7b75a32367a0e25c23b397842c65e344a1206524d16c8069f0a1c91b5f4c"}, -] - -[package.dependencies] -Jinja2 = ">=2.10" -sphinx = ">=4.2" - -[package.extras] -testing = ["matplotlib", "pytest", "pytest-cov"] - [[package]] name = "packaging" version = "23.0" @@ -2472,7 +2417,7 @@ email = ["email-validator (>=1.0.3)"] name = "pygments" version = "2.14.0" description = "Pygments is a syntax highlighting package written in Python." -category = "main" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3214,18 +3159,6 @@ files = [ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] -[[package]] -name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] - [[package]] name = "soupsieve" version = "2.4" @@ -3349,136 +3282,6 @@ files = [ {file = "spacy_loggers-1.0.4-py3-none-any.whl", hash = "sha256:e050bf2e63208b2f096b777e494971c962ad7c1dc997641c8f95c622550044ae"}, ] -[[package]] -name = "sphinx" -version = "6.1.3" -description = "Python documentation generator" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "Sphinx-6.1.3.tar.gz", hash = "sha256:0dac3b698538ffef41716cf97ba26c1c7788dba73ce6f150c1ff5b4720786dd2"}, - {file = "sphinx-6.1.3-py3-none-any.whl", hash = "sha256:807d1cb3d6be87eb78a381c3e70ebd8d346b9a25f3753e9947e866b2786865fc"}, -] - -[package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=2.9" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18,<0.20" -imagesize = ">=1.3" -Jinja2 = ">=3.0" -packaging = ">=21.0" -Pygments = ">=2.13" -requests = ">=2.25.0" -snowballstemmer = ">=2.0" -sphinxcontrib-applehelp = "*" -sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" -sphinxcontrib-jsmath = "*" -sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython", "html5lib", "pytest (>=4.6)"] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "1.0.4" -description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, - {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "1.0.2" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "main" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.0.1" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, - {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["html5lib", "pytest"] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "main" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] - -[package.extras] -test = ["flake8", "mypy", "pytest"] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "1.0.3" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "main" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "main" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - [[package]] name = "srsly" version = "2.4.6" @@ -4012,4 +3815,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10,<3.12" -content-hash = "48a40db306ef188cee412f3d5d35ea0bc81a4d762b6d5767609830c7f96688b1" +content-hash = "0deb71d5a186f72860c466ec53aa3d909d25984b6f3b9a26678807e0775d45de" diff --git a/pyproject.toml b/pyproject.toml index a690e529..672dcb2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,9 +20,9 @@ python = "^3.10,<3.12" astroid = "^2.14.2" black = "^23.1.0" levenshtein = "^0.20.9" -numpydoc = "^1.5" scipy = "^1.10.1" spacy = "^3.5.1" +docstring-parser = "^0.15" [tool.poetry.group.dev.dependencies] pytest = "^7.2.1" diff --git a/src/library_analyzer/cli/_cli.py b/src/library_analyzer/cli/_cli.py index 0cbc162e..06a66f41 100644 --- a/src/library_analyzer/cli/_cli.py +++ b/src/library_analyzer/cli/_cli.py @@ -10,6 +10,7 @@ from library_analyzer.cli._run_api import _run_api_command from library_analyzer.cli._run_migrate import _run_migrate_command from library_analyzer.cli._run_usages import _run_usages_command +from library_analyzer.processing.api.docstring_parsing import DocstringStyle _API_COMMAND = "api" _USAGES_COMMAND = "usages" @@ -24,7 +25,7 @@ def cli() -> None: logging.basicConfig(level=logging.INFO) if args.command == _API_COMMAND: - _run_api_command(args.package, args.src, args.out) + _run_api_command(args.package, args.src, args.out, args.docstyle) elif args.command == _USAGES_COMMAND: _run_usages_command(args.package, args.client, args.out, args.processes, args.batchsize) elif args.command == _ANNOTATIONS_COMMAND: @@ -35,6 +36,7 @@ def cli() -> None: args.src, args.client, args.out, + args.docstyle, args.processes, args.batchsize, ) @@ -76,6 +78,14 @@ def _add_api_subparser(subparsers: _SubParsersAction) -> None: default=None, ) api_parser.add_argument("-o", "--out", help="Output directory.", type=Path, required=True) + api_parser.add_argument( + "--docstyle", + help="The docstring style.", + type=DocstringStyle.from_string, + choices=list(DocstringStyle), + required=False, + default=DocstringStyle.PLAINTEXT.name, + ) def _add_usages_subparser(subparsers: _SubParsersAction) -> None: @@ -159,6 +169,14 @@ def _add_all_subparser(subparsers: _SubParsersAction) -> None: required=True, ) all_parser.add_argument("-o", "--out", help="Output directory.", type=Path, required=True) + all_parser.add_argument( + "--docstyle", + help="The docstring style.", + type=DocstringStyle.from_string, + choices=list(DocstringStyle), + required=False, + default=DocstringStyle.PLAINTEXT.name, + ) all_parser.add_argument( "--processes", help="How many processes should be spawned during processing.", diff --git a/src/library_analyzer/cli/_run_all.py b/src/library_analyzer/cli/_run_all.py index dd4eae70..626b1c3d 100644 --- a/src/library_analyzer/cli/_run_all.py +++ b/src/library_analyzer/cli/_run_all.py @@ -7,6 +7,7 @@ from library_analyzer.cli._run_api import _run_api_command from library_analyzer.cli._run_usages import _run_usages_command from library_analyzer.cli._shared_constants import _API_KEY, _USAGES_KEY +from library_analyzer.processing.api.docstring_parsing import DocstringStyle def _run_all_command( @@ -14,12 +15,13 @@ def _run_all_command( src_dir_path: Path, client_dir_path: Path, out_dir_path: Path, + docstring_style: DocstringStyle, n_processes: int, batch_size: int, ) -> None: out_file_annotations = out_dir_path.joinpath("annotations.json") results = _run_in_parallel( - partial(_run_api_command, package, src_dir_path, out_dir_path), + partial(_run_api_command, package, src_dir_path, out_dir_path, docstring_style), partial( _run_usages_command, package, diff --git a/src/library_analyzer/cli/_run_api.py b/src/library_analyzer/cli/_run_api.py index d0eb6a21..8d81a850 100644 --- a/src/library_analyzer/cli/_run_api.py +++ b/src/library_analyzer/cli/_run_api.py @@ -1,6 +1,7 @@ from pathlib import Path from library_analyzer.processing.api import get_api +from library_analyzer.processing.api.docstring_parsing import DocstringStyle from library_analyzer.processing.dependencies import get_dependencies from ._read_and_write_file import _write_api_dependency_file, _write_api_file @@ -11,9 +12,10 @@ def _run_api_command( package: str, src_dir_path: Path, out_dir_path: Path, + docstring_style: DocstringStyle, result_dict: dict | None = None, ) -> None: - api = get_api(package, src_dir_path) + api = get_api(package, src_dir_path, docstring_style) api_dependencies = get_dependencies(api) api_file_path = _write_api_file(api, out_dir_path) diff --git a/src/library_analyzer/processing/api/_ast_visitor.py b/src/library_analyzer/processing/api/_ast_visitor.py index 3beac7de..99ec02b2 100644 --- a/src/library_analyzer/processing/api/_ast_visitor.py +++ b/src/library_analyzer/processing/api/_ast_visitor.py @@ -19,7 +19,7 @@ from ._file_filters import _is_init_file from ._get_instance_attributes import get_instance_attributes from ._get_parameter_list import get_parameter_list -from .documentation_parsing import AbstractDocumentationParser +from .docstring_parsing import AbstractDocstringParser def trim_code(code: str | None, from_line_no: int, to_line_no: int, encoding: str) -> str: @@ -32,8 +32,8 @@ def trim_code(code: str | None, from_line_no: int, to_line_no: int, encoding: st class _AstVisitor: - def __init__(self, documentation_parser: AbstractDocumentationParser, api: API) -> None: - self.documentation_parser: AbstractDocumentationParser = documentation_parser + def __init__(self, docstring_parser: AbstractDocstringParser, api: API) -> None: + self.docstring_parser: AbstractDocstringParser = docstring_parser self.reexported: dict[str, list[str]] = {} self.api: API = api self.__declaration_stack: list[Module | Class | Function] = [] @@ -150,7 +150,7 @@ def enter_classdef(self, class_node: astroid.ClassDef) -> None: superclasses=class_node.basenames, is_public=self.is_public(class_node.name, qname), reexported_by=self.reexported.get(qname, []), - documentation=self.documentation_parser.get_class_documentation(class_node), + documentation=self.docstring_parser.get_class_documentation(class_node), code=code, instance_attributes=instance_attributes, ) @@ -188,7 +188,7 @@ def enter_functiondef(self, function_node: astroid.FunctionDef) -> None: qname=qname, decorators=decorator_names, parameters=get_parameter_list( - self.documentation_parser, + self.docstring_parser, function_node, function_id, qname, @@ -197,7 +197,7 @@ def enter_functiondef(self, function_node: astroid.FunctionDef) -> None: results=[], # TODO: results is_public=is_public, reexported_by=self.reexported.get(qname, []), - documentation=self.documentation_parser.get_function_documentation(function_node), + documentation=self.docstring_parser.get_function_documentation(function_node), code=code, ) self.__declaration_stack.append(function) diff --git a/src/library_analyzer/processing/api/_get_api.py b/src/library_analyzer/processing/api/_get_api.py index 1d409baa..762e9d05 100644 --- a/src/library_analyzer/processing/api/_get_api.py +++ b/src/library_analyzer/processing/api/_get_api.py @@ -14,10 +14,14 @@ package_files, package_root, ) -from .documentation_parsing import NumpyDocParser +from .docstring_parsing import DocstringStyle, create_docstring_parser -def get_api(package_name: str, root: Path | None = None) -> API: +def get_api( + package_name: str, + root: Path | None = None, + docstring_style: DocstringStyle = DocstringStyle.PLAINTEXT, +) -> API: if root is None: root = package_root(package_name) dist = distribution(package_name) or "" @@ -25,8 +29,8 @@ def get_api(package_name: str, root: Path | None = None) -> API: files = package_files(root) api = API(dist, package_name, dist_version) - documentation_parser = NumpyDocParser() - callable_visitor = _AstVisitor(documentation_parser, api) + docstring_parser = create_docstring_parser(docstring_style) + callable_visitor = _AstVisitor(docstring_parser, api) walker = ASTWalker(callable_visitor) for file in files: diff --git a/src/library_analyzer/processing/api/_get_parameter_list.py b/src/library_analyzer/processing/api/_get_parameter_list.py index b038167c..9e908d74 100644 --- a/src/library_analyzer/processing/api/_get_parameter_list.py +++ b/src/library_analyzer/processing/api/_get_parameter_list.py @@ -1,13 +1,13 @@ import astroid -from library_analyzer.processing.api.documentation_parsing import ( - AbstractDocumentationParser, +from library_analyzer.processing.api.docstring_parsing import ( + AbstractDocstringParser, ) from library_analyzer.processing.api.model import Parameter, ParameterAssignment def get_parameter_list( - documentation_parser: AbstractDocumentationParser, + docstring_parser: AbstractDocstringParser, function_node: astroid.FunctionDef, function_id: str, function_qname: str, @@ -27,7 +27,7 @@ def get_parameter_list( default_value=_get_stringified_default_value(function_node, parameter_name), assigned_by=parameter_assigned_by, is_public=function_is_public, - documentation=documentation_parser.get_parameter_documentation( + documentation=docstring_parser.get_parameter_documentation( function_node, parameter_name, parameter_assigned_by, diff --git a/src/library_analyzer/processing/api/docstring_parsing/__init__.py b/src/library_analyzer/processing/api/docstring_parsing/__init__.py new file mode 100644 index 00000000..af8c8a42 --- /dev/null +++ b/src/library_analyzer/processing/api/docstring_parsing/__init__.py @@ -0,0 +1,17 @@ +"""Parsing docstrings into a common format.""" + +from ._abstract_documentation_parser import AbstractDocstringParser +from ._create_docstring_parser import create_docstring_parser +from ._docstring_style import DocstringStyle +from ._epydoc_parser import EpydocParser +from ._numpydoc_parser import NumpyDocParser +from ._plaintext_docstring_parser import PlaintextDocstringParser + +__all__ = [ + "AbstractDocstringParser", + "create_docstring_parser", + "DocstringStyle", + "EpydocParser", + "NumpyDocParser", + "PlaintextDocstringParser", +] diff --git a/src/library_analyzer/processing/api/documentation_parsing/_abstract_documentation_parser.py b/src/library_analyzer/processing/api/docstring_parsing/_abstract_documentation_parser.py similarity index 95% rename from src/library_analyzer/processing/api/documentation_parsing/_abstract_documentation_parser.py rename to src/library_analyzer/processing/api/docstring_parsing/_abstract_documentation_parser.py index 4cdd2fc6..5e9fbc4e 100644 --- a/src/library_analyzer/processing/api/documentation_parsing/_abstract_documentation_parser.py +++ b/src/library_analyzer/processing/api/docstring_parsing/_abstract_documentation_parser.py @@ -14,7 +14,7 @@ ) -class AbstractDocumentationParser(ABC): +class AbstractDocstringParser(ABC): @abstractmethod def get_class_documentation(self, class_node: astroid.ClassDef) -> ClassDocumentation: pass diff --git a/src/library_analyzer/processing/api/docstring_parsing/_create_docstring_parser.py b/src/library_analyzer/processing/api/docstring_parsing/_create_docstring_parser.py new file mode 100644 index 00000000..c2d49741 --- /dev/null +++ b/src/library_analyzer/processing/api/docstring_parsing/_create_docstring_parser.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ._docstring_style import DocstringStyle +from ._epydoc_parser import EpydocParser +from ._numpydoc_parser import NumpyDocParser +from ._plaintext_docstring_parser import PlaintextDocstringParser + +if TYPE_CHECKING: + from ._abstract_documentation_parser import AbstractDocstringParser + + +def create_docstring_parser(style: DocstringStyle) -> AbstractDocstringParser: + if style == DocstringStyle.NUMPY: + return NumpyDocParser() + if style == DocstringStyle.EPYDOC: + return EpydocParser() + else: # TODO: cover other cases + return PlaintextDocstringParser() diff --git a/src/library_analyzer/processing/api/docstring_parsing/_docstring_style.py b/src/library_analyzer/processing/api/docstring_parsing/_docstring_style.py new file mode 100644 index 00000000..6edb137d --- /dev/null +++ b/src/library_analyzer/processing/api/docstring_parsing/_docstring_style.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from enum import Enum + + +class DocstringStyle(Enum): + # AUTO = "auto", + PLAINTEXT = ("plaintext",) + # REST = "reST", + NUMPY = ("numpy",) + # GOOGLE = "google", + EPYDOC = "epydoc" + + def __str__(self) -> str: + return self.name + + @staticmethod + def from_string(key: str) -> DocstringStyle: + try: + return DocstringStyle[key.upper()] + except KeyError as err: + raise ValueError(f"Unknown docstring style: {key}") from err diff --git a/src/library_analyzer/processing/api/docstring_parsing/_epydoc_parser.py b/src/library_analyzer/processing/api/docstring_parsing/_epydoc_parser.py new file mode 100644 index 00000000..8865aa17 --- /dev/null +++ b/src/library_analyzer/processing/api/docstring_parsing/_epydoc_parser.py @@ -0,0 +1,81 @@ +import astroid +from docstring_parser import Docstring, DocstringParam, DocstringStyle +from docstring_parser import parse as parse_docstring + +from library_analyzer.processing.api.model import ( + ClassDocumentation, + FunctionDocumentation, + ParameterAssignment, + ParameterDocumentation, +) + +from ._abstract_documentation_parser import AbstractDocstringParser +from ._helpers import get_description, get_full_docstring + + +class EpydocParser(AbstractDocstringParser): + """ + Parses documentation in the Epydoc format. See http://epydoc.sourceforge.net/epytext.html for more information. + + This class is not thread-safe. Each thread should create its own instance. + """ + + def __init__(self) -> None: + self.__cached_function_node: astroid.FunctionDef | None = None + self.__cached_docstring: DocstringParam | None = None + + def get_class_documentation(self, class_node: astroid.ClassDef) -> ClassDocumentation: + docstring = get_full_docstring(class_node) + docstring_obj = parse_docstring(docstring, style=DocstringStyle.EPYDOC) + + return ClassDocumentation(description=get_description(docstring_obj)) + + def get_function_documentation(self, function_node: astroid.FunctionDef) -> FunctionDocumentation: + docstring = get_full_docstring(function_node) + + return FunctionDocumentation( + description=get_description(self.__get_cached_function_numpydoc_string(function_node, docstring)), + ) + + def get_parameter_documentation( + self, + function_node: astroid.FunctionDef, + parameter_name: str, + parameter_assigned_by: ParameterAssignment, # noqa: ARG002 + ) -> ParameterDocumentation: + # For constructors (__init__ functions) the parameters are described on the class + if function_node.name == "__init__" and isinstance(function_node.parent, astroid.ClassDef): + docstring = get_full_docstring(function_node.parent) + else: + docstring = get_full_docstring(function_node) + + # Find matching parameter docstrings + function_numpydoc = self.__get_cached_function_numpydoc_string(function_node, docstring) + all_parameters_numpydoc: list[DocstringParam] = function_numpydoc.params + matching_parameters_numpydoc = [it for it in all_parameters_numpydoc if it.arg_name == parameter_name] + + if len(matching_parameters_numpydoc) == 0: + return ParameterDocumentation(type="", default_value="", description="") + + last_parameter_docstring_obj = matching_parameters_numpydoc[-1] + return ParameterDocumentation( + type=last_parameter_docstring_obj.type_name or "", + default_value=last_parameter_docstring_obj.default or "", + description=last_parameter_docstring_obj.description, + ) + + def __get_cached_function_numpydoc_string(self, function_node: astroid.FunctionDef, docstring: str) -> Docstring: + """ + Return the NumpyDocString for the given function node. + + It is only recomputed when the function node differs from the previous one that was passed to this function. + This avoids reparsing the docstring for the function itself and all of its parameters. + + On Lars's system this caused a significant performance improvement: Previously, 8.382s were spent inside the + function get_parameter_documentation when parsing sklearn. Afterwards, it was only 2.113s. + """ + if self.__cached_function_node is not function_node: + self.__cached_function_node = function_node + self.__cached_docstring = parse_docstring(docstring, style=DocstringStyle.EPYDOC) + + return self.__cached_docstring diff --git a/src/library_analyzer/processing/api/docstring_parsing/_helpers.py b/src/library_analyzer/processing/api/docstring_parsing/_helpers.py new file mode 100644 index 00000000..4803bb9d --- /dev/null +++ b/src/library_analyzer/processing/api/docstring_parsing/_helpers.py @@ -0,0 +1,32 @@ +import inspect + +import astroid +from docstring_parser import Docstring + + +def get_full_docstring(declaration: astroid.ClassDef | astroid.FunctionDef) -> str: + """ + Return the full docstring of the given declaration. + + If no docstring is available, an empty string is returned. Indentation is cleaned up. + """ + doc_node = declaration.doc_node + if doc_node is None: + return "" + return inspect.cleandoc(doc_node.value) + + +def get_description(docstring_obj: Docstring) -> str: + """ + Return the concatenated short and long description of the given docstring object. + + If these parts are blank, an empty string is returned. + """ + summary: str = docstring_obj.short_description or "" + extended_summary: str = docstring_obj.long_description or "" + + result = "" + result += summary.rstrip() + result += "\n\n" + result += extended_summary.rstrip() + return result.strip() diff --git a/src/library_analyzer/processing/api/documentation_parsing/_numpydoc_parser.py b/src/library_analyzer/processing/api/docstring_parsing/_numpydoc_parser.py similarity index 60% rename from src/library_analyzer/processing/api/documentation_parsing/_numpydoc_parser.py rename to src/library_analyzer/processing/api/docstring_parsing/_numpydoc_parser.py index 6057e789..1a4ccdc2 100644 --- a/src/library_analyzer/processing/api/documentation_parsing/_numpydoc_parser.py +++ b/src/library_analyzer/processing/api/docstring_parsing/_numpydoc_parser.py @@ -1,8 +1,8 @@ import re import astroid -import numpydoc.docscrape -from numpydoc.docscrape import NumpyDocString +from docstring_parser import Docstring, DocstringParam, DocstringStyle +from docstring_parser import parse as parse_docstring from library_analyzer.processing.api.model import ( ClassDocumentation, @@ -11,13 +11,13 @@ ParameterDocumentation, ) -from ._abstract_documentation_parser import AbstractDocumentationParser -from ._get_full_docstring import get_full_docstring +from ._abstract_documentation_parser import AbstractDocstringParser +from ._helpers import get_description, get_full_docstring -class NumpyDocParser(AbstractDocumentationParser): +class NumpyDocParser(AbstractDocstringParser): """ - Parses documentation in the NumpyDoc format. + Parse documentation in the NumpyDoc format. Notes ----- @@ -30,23 +30,19 @@ class NumpyDocParser(AbstractDocumentationParser): def __init__(self) -> None: self.__cached_function_node: astroid.FunctionDef | None = None - self.__cached_numpydoc_string: NumpyDocString | None = None + self.__cached_docstring: Docstring | None = None def get_class_documentation(self, class_node: astroid.ClassDef) -> ClassDocumentation: docstring = get_full_docstring(class_node) + docstring_obj = parse_docstring(docstring, style=DocstringStyle.NUMPYDOC) - return ClassDocumentation( - description=_get_description(NumpyDocString(docstring)), - full_docstring=docstring, - ) + return ClassDocumentation(description=get_description(docstring_obj)) def get_function_documentation(self, function_node: astroid.FunctionDef) -> FunctionDocumentation: docstring = get_full_docstring(function_node) + docstring_obj = self.__get_cached_function_numpydoc_string(function_node, docstring) - return FunctionDocumentation( - description=_get_description(self.__get_cached_function_numpydoc_string(function_node, docstring)), - full_docstring=docstring, - ) + return FunctionDocumentation(description=get_description(docstring_obj)) def get_parameter_documentation( self, @@ -62,7 +58,7 @@ def get_parameter_documentation( # Find matching parameter docstrings function_numpydoc = self.__get_cached_function_numpydoc_string(function_node, docstring) - all_parameters_numpydoc: list[numpydoc.docscrape.Parameter] = function_numpydoc.get("Parameters", []) + all_parameters_numpydoc: list[DocstringParam] = function_numpydoc.params matching_parameters_numpydoc = [ it for it in all_parameters_numpydoc @@ -77,51 +73,36 @@ def get_parameter_documentation( return ParameterDocumentation( type=type_, default_value=default_value, - description="\n".join([line.rstrip() for line in last_parameter_numpydoc.desc]), + description=last_parameter_numpydoc.description, ) def __get_cached_function_numpydoc_string( self, function_node: astroid.FunctionDef, docstring: str, - ) -> NumpyDocString: + ) -> Docstring: """ Return the NumpyDocString for the given function node. It is only recomputed when the function node differs from the previous one that was passed to this function. - This avoids reparsing the docstring for the function itself and all of its parameters. On Lars's system this - caused a significant performance improvement: Previously, 8.382s were spent inside the function - `get_parameter_documentation` when parsing sklearn. Afterwards, it was only 2.113s. + This avoids reparsing the docstring for the function itself and all of its parameters. + + On Lars's system this caused a significant performance improvement: Previously, 8.382s were spent inside the + function `get_parameter_documentation` when parsing sklearn. Afterwards, it was only 2.113s. """ if self.__cached_function_node is not function_node: self.__cached_function_node = function_node - self.__cached_numpydoc_string = NumpyDocString(docstring) - - return self.__cached_numpydoc_string - - -def _get_description(numpydoc_string: NumpyDocString) -> str: - """ - Return the concatenated summary and extended summary parts of the given docstring. - - If these parts are blank, an empty string is returned. - """ - summary: list[str] = numpydoc_string.get("Summary", []) - extended_summary: list[str] = numpydoc_string.get("Extended Summary", []) + self.__cached_docstring = parse_docstring(docstring, style=DocstringStyle.NUMPYDOC) - result = "" - result += "\n".join([line.rstrip() for line in summary]) - result += "\n\n" - result += "\n".join([line.rstrip() for line in extended_summary]) - return result.strip() + return self.__cached_docstring def _is_matching_parameter_numpydoc( - parameter_numpydoc: numpydoc.docscrape.Parameter, + parameter_docstring_obj: DocstringParam, parameter_name: str, parameter_assigned_by: ParameterAssignment, ) -> bool: - """Return whether the given NumpyDoc applied to the parameter with the given name.""" + """Return whether the given docstring object applies to the parameter with the given name.""" if parameter_assigned_by == ParameterAssignment.POSITIONAL_VARARG: lookup_name = f"*{parameter_name}" elif parameter_assigned_by == ParameterAssignment.NAMED_VARARG: @@ -131,17 +112,17 @@ def _is_matching_parameter_numpydoc( # Numpydoc allows multiple parameters to be documented at once. See # https://numpydoc.readthedocs.io/en/latest/format.html#parameters for more information. - return any(name.strip() == lookup_name for name in parameter_numpydoc.name.split(",")) + return any(name.strip() == lookup_name for name in parameter_docstring_obj.arg_name.split(",")) def _get_type_and_default_value( - parameter_numpydoc: numpydoc.docscrape.Parameter, + parameter_docstring_obj: DocstringParam, ) -> tuple[str, str]: """Return the type and default value for the given NumpyDoc.""" - type_ = parameter_numpydoc.type - parts = re.split(r",\s*optional|,\s*default\s*[:=]?", type_) + type_name = parameter_docstring_obj.type_name or "" + parts = re.split(r",\s*optional|,\s*default\s*[:=]?", type_name) if len(parts) != 2: - return type_.strip(), "" + return type_name.strip(), parameter_docstring_obj.default or "" return parts[0].strip(), parts[1].strip() diff --git a/src/library_analyzer/processing/api/documentation_parsing/_default_documentation_parser.py b/src/library_analyzer/processing/api/docstring_parsing/_plaintext_docstring_parser.py similarity index 74% rename from src/library_analyzer/processing/api/documentation_parsing/_default_documentation_parser.py rename to src/library_analyzer/processing/api/docstring_parsing/_plaintext_docstring_parser.py index b8e30d40..fdbece72 100644 --- a/src/library_analyzer/processing/api/documentation_parsing/_default_documentation_parser.py +++ b/src/library_analyzer/processing/api/docstring_parsing/_plaintext_docstring_parser.py @@ -7,21 +7,21 @@ ParameterDocumentation, ) -from ._abstract_documentation_parser import AbstractDocumentationParser -from ._get_full_docstring import get_full_docstring +from ._abstract_documentation_parser import AbstractDocstringParser +from ._helpers import get_full_docstring -class DefaultDocumentationParser(AbstractDocumentationParser): +class PlaintextDocstringParser(AbstractDocstringParser): """Parses documentation in any format. Should not be used if there is another parser for the specific format.""" def get_class_documentation(self, class_node: astroid.ClassDef) -> ClassDocumentation: return ClassDocumentation( - full_docstring=get_full_docstring(class_node), + description=get_full_docstring(class_node), ) def get_function_documentation(self, function_node: astroid.FunctionDef) -> FunctionDocumentation: return FunctionDocumentation( - full_docstring=get_full_docstring(function_node), + description=get_full_docstring(function_node), ) def get_parameter_documentation( diff --git a/src/library_analyzer/processing/api/documentation_parsing/__init__.py b/src/library_analyzer/processing/api/documentation_parsing/__init__.py deleted file mode 100644 index fc1c5edf..00000000 --- a/src/library_analyzer/processing/api/documentation_parsing/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Parsing docstrings into a common format.""" - -from ._abstract_documentation_parser import AbstractDocumentationParser -from ._default_documentation_parser import DefaultDocumentationParser -from ._get_full_docstring import get_full_docstring -from ._numpydoc_parser import NumpyDocParser - -__all__ = [ - "AbstractDocumentationParser", - "DefaultDocumentationParser", - "NumpyDocParser", - "get_full_docstring", -] diff --git a/src/library_analyzer/processing/api/documentation_parsing/_get_full_docstring.py b/src/library_analyzer/processing/api/documentation_parsing/_get_full_docstring.py deleted file mode 100644 index 5db4f557..00000000 --- a/src/library_analyzer/processing/api/documentation_parsing/_get_full_docstring.py +++ /dev/null @@ -1,15 +0,0 @@ -import inspect - -import astroid - - -def get_full_docstring(declaration: astroid.ClassDef | astroid.FunctionDef) -> str: - """ - Return the full docstring of the given declaration. - - Indentation is cleaned up. If no docstring is available, an empty string is returned. - """ - doc_node = declaration.doc_node - if doc_node is None: - return "" - return inspect.cleandoc(doc_node.value) diff --git a/src/library_analyzer/processing/api/model/_api.py b/src/library_analyzer/processing/api/model/_api.py index 70aa19e5..763b7ce3 100644 --- a/src/library_analyzer/processing/api/model/_api.py +++ b/src/library_analyzer/processing/api/model/_api.py @@ -234,10 +234,7 @@ def from_json(json: Any) -> Class: json.get("superclasses", []), json.get("is_public", True), json.get("reexported_by", []), - ClassDocumentation( - description=json.get("description", ""), - full_docstring=json.get("docstring", ""), - ), + ClassDocumentation(description=json.get("description", "")), json.get("code", ""), [ Attribute.from_json(instance_attribute, json["id"]) @@ -271,7 +268,6 @@ def to_json(self) -> Any: "is_public": self.is_public, "reexported_by": self.reexported_by, "description": self.documentation.description, - "docstring": self.documentation.full_docstring, "code": self.code, "instance_attributes": [attribute.to_json() for attribute in self.instance_attributes], } @@ -360,10 +356,7 @@ def from_json(json: Any) -> Function: [Result.from_json(result_json) for result_json in json.get("results", [])], json.get("is_public", True), json.get("reexported_by", []), - FunctionDocumentation( - description=json.get("description", ""), - full_docstring=json.get("docstring", ""), - ), + FunctionDocumentation(description=json.get("description", "")), json.get("code", ""), ) @@ -382,7 +375,6 @@ def to_json(self) -> Any: "is_public": self.is_public, "reexported_by": self.reexported_by, "description": self.documentation.description, - "docstring": self.documentation.full_docstring, "code": self.code, } diff --git a/src/library_analyzer/processing/api/model/_documentation.py b/src/library_analyzer/processing/api/model/_documentation.py index ccbae18d..7909b4f3 100644 --- a/src/library_analyzer/processing/api/model/_documentation.py +++ b/src/library_analyzer/processing/api/model/_documentation.py @@ -7,7 +7,6 @@ @dataclass(frozen=True) class ClassDocumentation: description: str = "" - full_docstring: str = "" @staticmethod def from_dict(d: dict) -> ClassDocumentation: @@ -20,7 +19,6 @@ def to_dict(self) -> dict: @dataclass(frozen=True) class FunctionDocumentation: description: str = "" - full_docstring: str = "" @staticmethod def from_dict(d: dict) -> FunctionDocumentation: diff --git a/tests/library_analyzer/processing/api/documentation_parsing/__init__.py b/tests/library_analyzer/processing/api/docstring_parsing/__init__.py similarity index 100% rename from tests/library_analyzer/processing/api/documentation_parsing/__init__.py rename to tests/library_analyzer/processing/api/docstring_parsing/__init__.py diff --git a/tests/library_analyzer/processing/api/docstring_parsing/test_epydoc_parser.py b/tests/library_analyzer/processing/api/docstring_parsing/test_epydoc_parser.py new file mode 100644 index 00000000..1a1bf848 --- /dev/null +++ b/tests/library_analyzer/processing/api/docstring_parsing/test_epydoc_parser.py @@ -0,0 +1,241 @@ +import astroid +import pytest +from library_analyzer.processing.api.docstring_parsing import EpydocParser +from library_analyzer.processing.api.model import ( + ClassDocumentation, + FunctionDocumentation, + ParameterAssignment, + ParameterDocumentation, +) + + +@pytest.fixture() +def epydoc_parser() -> EpydocParser: + return EpydocParser() + + +class_with_documentation = ''' +class C: + """ + Lorem ipsum. Code:: + + pass + + Dolor sit amet. + """ +''' + +class_without_documentation = """ +class C: + pass +""" + + +@pytest.mark.parametrize( + ("python_code", "expected_class_documentation"), + [ + ( + class_with_documentation, + ClassDocumentation( + description="Lorem ipsum. Code::\n\npass\n\nDolor sit amet.", + ), + ), + ( + class_without_documentation, + ClassDocumentation(description=""), + ), + ], + ids=[ + "class with documentation", + "class without documentation", + ], +) +def test_get_class_documentation( + epydoc_parser: EpydocParser, + python_code: str, + expected_class_documentation: ClassDocumentation, +) -> None: + node = astroid.extract_node(python_code) + + assert isinstance(node, astroid.ClassDef) + assert epydoc_parser.get_class_documentation(node) == expected_class_documentation + + +# language=python +function_with_documentation = ''' +def f(): + """ + Lorem ipsum. Code:: + + pass + + Dolor sit amet. + """ + + pass +''' + +# language=python +function_without_documentation = """ +def f(): + pass +""" + + +@pytest.mark.parametrize( + ("python_code", "expected_function_documentation"), + [ + ( + function_with_documentation, + FunctionDocumentation(description="Lorem ipsum. Code::\n\npass\n\nDolor sit amet."), + ), + ( + function_without_documentation, + FunctionDocumentation(description=""), + ), + ], + ids=[ + "function with documentation", + "function without documentation", + ], +) +def test_get_function_documentation( + epydoc_parser: EpydocParser, + python_code: str, + expected_function_documentation: FunctionDocumentation, +) -> None: + node = astroid.extract_node(python_code) + + assert isinstance(node, astroid.FunctionDef) + assert epydoc_parser.get_function_documentation(node) == expected_function_documentation + + +# language=python +class_with_parameters = ''' +# noinspection PyUnresolvedReferences,PyIncorrectDocstring +class C: + """ + Lorem ipsum. + + Dolor sit amet. + + @param p: foo defaults to 1 + @type p: int + """ + + def __init__(self): + pass +''' + +# language=python +function_with_parameters = ''' +# noinspection PyUnresolvedReferences,PyIncorrectDocstring +def f(): + """ + Lorem ipsum. + + Dolor sit amet. + + Parameters + ---------- + @param no_type_no_default: no type and no default + @param type_no_default: type but no default + @type type_no_default: int + @param with_default: foo that defaults to 2 + @type with_default: int + """ + + pass +''' + + +@pytest.mark.parametrize( + ("python_code", "parameter_name", "parameter_assigned_by", "expected_parameter_documentation"), + [ + ( + class_with_parameters, + "p", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocumentation( + type="int", + default_value="1", + description="foo defaults to 1", + ), + ), + ( + class_with_parameters, + "missing", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocumentation( + type="", + default_value="", + description="", + ), + ), + ( + function_with_parameters, + "no_type_no_default", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocumentation( + type="", + default_value="", + description="no type and no default", + ), + ), + ( + function_with_parameters, + "type_no_default", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocumentation( + type="int", + default_value="", + description="type but no default", + ), + ), + ( + function_with_parameters, + "with_default", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocumentation( + type="int", + default_value="2", + description="foo that defaults to 2", + ), + ), + ( + function_with_parameters, + "missing", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocumentation(type="", default_value="", description=""), + ), + ], + ids=[ + "existing class parameter", + "missing class parameter", + "function parameter with no type and no default", + "function parameter with type and no default", + "function parameter with default", + "missing function parameter", + ], +) +def test_get_parameter_documentation( + epydoc_parser: EpydocParser, + python_code: str, + parameter_name: str, + parameter_assigned_by: ParameterAssignment, + expected_parameter_documentation: ParameterDocumentation, +) -> None: + node = astroid.extract_node(python_code) + assert isinstance(node, astroid.ClassDef | astroid.FunctionDef) + + # Find the constructor + if isinstance(node, astroid.ClassDef): + for method in node.mymethods(): + if method.name == "__init__": + node = method + + assert isinstance(node, astroid.FunctionDef) + assert ( + epydoc_parser.get_parameter_documentation(node, parameter_name, parameter_assigned_by) + == expected_parameter_documentation + ) diff --git a/tests/library_analyzer/processing/api/documentation_parsing/test_get_full_docstring.py b/tests/library_analyzer/processing/api/docstring_parsing/test_get_full_docstring.py similarity index 94% rename from tests/library_analyzer/processing/api/documentation_parsing/test_get_full_docstring.py rename to tests/library_analyzer/processing/api/docstring_parsing/test_get_full_docstring.py index ff232c5b..b9e16801 100644 --- a/tests/library_analyzer/processing/api/documentation_parsing/test_get_full_docstring.py +++ b/tests/library_analyzer/processing/api/docstring_parsing/test_get_full_docstring.py @@ -1,6 +1,6 @@ import astroid import pytest -from library_analyzer.processing.api.documentation_parsing import get_full_docstring +from library_analyzer.processing.api.docstring_parsing._helpers import get_full_docstring class_with_multi_line_documentation = ''' class C: diff --git a/tests/library_analyzer/processing/api/documentation_parsing/test_numpydoc_parser.py b/tests/library_analyzer/processing/api/docstring_parsing/test_numpydoc_parser.py similarity index 93% rename from tests/library_analyzer/processing/api/documentation_parsing/test_numpydoc_parser.py rename to tests/library_analyzer/processing/api/docstring_parsing/test_numpydoc_parser.py index 7b0fc52a..64c246b7 100644 --- a/tests/library_analyzer/processing/api/documentation_parsing/test_numpydoc_parser.py +++ b/tests/library_analyzer/processing/api/docstring_parsing/test_numpydoc_parser.py @@ -1,6 +1,6 @@ import astroid import pytest -from library_analyzer.processing.api.documentation_parsing import NumpyDocParser +from library_analyzer.processing.api.docstring_parsing import NumpyDocParser from library_analyzer.processing.api.model import ( ClassDocumentation, FunctionDocumentation, @@ -37,13 +37,12 @@ class C: ( class_with_documentation, ClassDocumentation( - description="Lorem ipsum. Code::\n\n pass\n\nDolor sit amet.", - full_docstring="Lorem ipsum. Code::\n\n pass\n\nDolor sit amet.", + description="Lorem ipsum. Code::\n\npass\n\nDolor sit amet.", ), ), ( class_without_documentation, - ClassDocumentation(description="", full_docstring=""), + ClassDocumentation(description=""), ), ], ids=[ @@ -87,13 +86,12 @@ def f(): ( function_with_documentation, FunctionDocumentation( - description="Lorem ipsum. Code::\n\n pass\n\nDolor sit amet.", - full_docstring="Lorem ipsum. Code::\n\n pass\n\nDolor sit amet.", + description="Lorem ipsum. Code::\n\npass\n\nDolor sit amet.", ), ), ( function_without_documentation, - FunctionDocumentation(description="", full_docstring=""), + FunctionDocumentation(description=""), ), ], ids=[ diff --git a/tests/library_analyzer/processing/api/documentation_parsing/test_default_documentation_parser.py b/tests/library_analyzer/processing/api/docstring_parsing/test_plaintext_docstring_parser.py similarity index 73% rename from tests/library_analyzer/processing/api/documentation_parsing/test_default_documentation_parser.py rename to tests/library_analyzer/processing/api/docstring_parsing/test_plaintext_docstring_parser.py index c38a07a1..10fc15de 100644 --- a/tests/library_analyzer/processing/api/documentation_parsing/test_default_documentation_parser.py +++ b/tests/library_analyzer/processing/api/docstring_parsing/test_plaintext_docstring_parser.py @@ -1,7 +1,7 @@ import astroid import pytest -from library_analyzer.processing.api.documentation_parsing import ( - DefaultDocumentationParser, +from library_analyzer.processing.api.docstring_parsing import ( + PlaintextDocstringParser, ) from library_analyzer.processing.api.model import ( ClassDocumentation, @@ -12,8 +12,8 @@ @pytest.fixture() -def default_documentation_parser() -> DefaultDocumentationParser: - return DefaultDocumentationParser() +def plaintext_docstring_parser() -> PlaintextDocstringParser: + return PlaintextDocstringParser() class_with_documentation = ''' @@ -40,13 +40,12 @@ class C: ( class_with_documentation, ClassDocumentation( - description="", - full_docstring="Lorem ipsum.\n\nDolor sit amet.", + description="Lorem ipsum.\n\nDolor sit amet.", ), ), ( class_without_documentation, - ClassDocumentation(description="", full_docstring=""), + ClassDocumentation(description=""), ), ], ids=[ @@ -55,14 +54,14 @@ class C: ], ) def test_get_class_documentation( - default_documentation_parser: DefaultDocumentationParser, + plaintext_docstring_parser: PlaintextDocstringParser, python_code: str, expected_class_documentation: ClassDocumentation, ) -> None: node = astroid.extract_node(python_code) assert isinstance(node, astroid.ClassDef) - assert default_documentation_parser.get_class_documentation(node) == expected_class_documentation + assert plaintext_docstring_parser.get_class_documentation(node) == expected_class_documentation function_with_documentation = ''' @@ -88,13 +87,12 @@ def f(p: int): ( function_with_documentation, FunctionDocumentation( - description="", - full_docstring="Lorem ipsum.\n\nDolor sit amet.", + description="Lorem ipsum.\n\nDolor sit amet.", ), ), ( function_without_documentation, - FunctionDocumentation(description="", full_docstring=""), + FunctionDocumentation(description=""), ), ], ids=[ @@ -103,14 +101,14 @@ def f(p: int): ], ) def test_get_function_documentation( - default_documentation_parser: DefaultDocumentationParser, + plaintext_docstring_parser: PlaintextDocstringParser, python_code: str, expected_function_documentation: FunctionDocumentation, ) -> None: node = astroid.extract_node(python_code) assert isinstance(node, astroid.FunctionDef) - assert default_documentation_parser.get_function_documentation(node) == expected_function_documentation + assert plaintext_docstring_parser.get_function_documentation(node) == expected_function_documentation @pytest.mark.parametrize( @@ -141,7 +139,7 @@ def test_get_function_documentation( ], ) def test_get_parameter_documentation( - default_documentation_parser: DefaultDocumentationParser, + plaintext_docstring_parser: PlaintextDocstringParser, python_code: str, parameter_name: str, expected_parameter_documentation: ParameterDocumentation, @@ -149,7 +147,7 @@ def test_get_parameter_documentation( node = astroid.extract_node(python_code) assert isinstance(node, astroid.FunctionDef) assert ( - default_documentation_parser.get_parameter_documentation( + plaintext_docstring_parser.get_parameter_documentation( node, parameter_name, ParameterAssignment.POSITION_OR_NAME, diff --git a/tests/library_analyzer/processing/api/model/test_api.py b/tests/library_analyzer/processing/api/model/test_api.py index a7b7808d..dceaa8cb 100644 --- a/tests/library_analyzer/processing/api/model/test_api.py +++ b/tests/library_analyzer/processing/api/model/test_api.py @@ -217,7 +217,6 @@ def test_cut_documentation_from_code(code: str, expected_code: str) -> None: reexported_by=[], documentation=ClassDocumentation( "this documentation string cannot be used", - " because indentation was removed", ), code=code, instance_attributes=[], @@ -231,7 +230,7 @@ def test_cut_documentation_from_code(code: str, expected_code: str) -> None: results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code=code, ) assert api_element.get_formatted_code(cut_documentation=True) == expected_code + "\n" diff --git a/tests/library_analyzer/processing/api/model/test_documentation.py b/tests/library_analyzer/processing/api/model/test_documentation.py index 12b5c596..19f8b9bf 100644 --- a/tests/library_analyzer/processing/api/model/test_documentation.py +++ b/tests/library_analyzer/processing/api/model/test_documentation.py @@ -10,7 +10,7 @@ "class_documentation", [ ClassDocumentation(), - ClassDocumentation(description="foo", full_docstring="foo bar"), + ClassDocumentation(description="foo"), ], ) def test_dict_conversion_for_class_documentation( @@ -23,7 +23,7 @@ def test_dict_conversion_for_class_documentation( "function_documentation", [ FunctionDocumentation(), - FunctionDocumentation(description="foo", full_docstring="foo bar"), + FunctionDocumentation(description="foo"), ], ) def test_dict_conversion_for_function_documentation( diff --git a/tests/library_analyzer/processing/api/test_get_parameter_list.py b/tests/library_analyzer/processing/api/test_get_parameter_list.py index 0d58fcac..e77b2716 100644 --- a/tests/library_analyzer/processing/api/test_get_parameter_list.py +++ b/tests/library_analyzer/processing/api/test_get_parameter_list.py @@ -1,8 +1,8 @@ import astroid import pytest from library_analyzer.processing.api import get_parameter_list -from library_analyzer.processing.api.documentation_parsing import ( - DefaultDocumentationParser, +from library_analyzer.processing.api.docstring_parsing import ( + PlaintextDocstringParser, ) from library_analyzer.processing.api.model import ( Parameter, @@ -119,7 +119,7 @@ def test_get_parameter_list_on_global_functions(python_code: str, expected_param actual_parameter_list = [ it.to_json() for it in get_parameter_list( - documentation_parser=DefaultDocumentationParser(), + docstring_parser=PlaintextDocstringParser(), function_node=node, function_id="f", function_qname="f", @@ -257,7 +257,7 @@ def test_get_parameter_list_on_method(python_code: str, expected_parameter_list: actual_parameter_list = [ it.to_json() for it in get_parameter_list( - documentation_parser=DefaultDocumentationParser(), + docstring_parser=PlaintextDocstringParser(), function_node=node, function_id="C/f", function_qname="C.f", diff --git a/tests/library_analyzer/processing/migration/model/test_differ.py b/tests/library_analyzer/processing/migration/model/test_differ.py index 2537c946..f4ce1c93 100644 --- a/tests/library_analyzer/processing/migration/model/test_differ.py +++ b/tests/library_analyzer/processing/migration/model/test_differ.py @@ -69,7 +69,7 @@ class Test: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a test", "This is a test"), + documentation=ClassDocumentation("This is a test"), code=code_a, instance_attributes=[], ) @@ -87,7 +87,7 @@ class newTest: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a new test", "This is a new test"), + documentation=ClassDocumentation("This is a new test"), code=code_b, instance_attributes=[], ) @@ -130,7 +130,6 @@ def test(test_parameter: str): reexported_by=[], documentation=FunctionDocumentation( "This test function is a proof of work", - "This test function is a proof of work", ), code=code_a, ) @@ -166,7 +165,6 @@ def test_method(test_parameter: str): reexported_by=[], documentation=FunctionDocumentation( "This test function is a proof of concept.", - "This test function is a proof of concept.", ), code=code_b, ) diff --git a/tests/library_analyzer/processing/migration/model/test_inheritance_differ.py b/tests/library_analyzer/processing/migration/model/test_inheritance_differ.py index 51cd6632..a85b2b93 100644 --- a/tests/library_analyzer/processing/migration/model/test_inheritance_differ.py +++ b/tests/library_analyzer/processing/migration/model/test_inheritance_differ.py @@ -42,7 +42,7 @@ class SuperTest: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a test", "This is a test"), + documentation=ClassDocumentation("This is a test"), code=code_a, instance_attributes=[attribute_super], ) @@ -53,7 +53,7 @@ class SuperTest: superclasses=["SuperTest"], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a test", "This is a test"), + documentation=ClassDocumentation("This is a test"), code="", instance_attributes=[], ) @@ -87,7 +87,6 @@ def test_function_super(test_parameter: str): reexported_by=[], documentation=FunctionDocumentation( "This is a test function", - "This is a test function", ), code=code_function_a, ) @@ -121,7 +120,7 @@ class SubTest: superclasses=["SuperTest"], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a test", "This is a test"), + documentation=ClassDocumentation("This is a test"), code=code_a, instance_attributes=[attribute_sub], ) @@ -132,7 +131,7 @@ class SubTest: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a test", "This is a test"), + documentation=ClassDocumentation("This is a test"), code="", instance_attributes=[], ) @@ -166,7 +165,6 @@ def test_function_sub(test_parameter: str): reexported_by=[], documentation=FunctionDocumentation( "This test function is only for testing", - "This test function is only for testing", ), code=code_function_a, ) diff --git a/tests/library_analyzer/processing/migration/model/test_mapping.py b/tests/library_analyzer/processing/migration/model/test_mapping.py index 7d1ace7d..0f4932a5 100644 --- a/tests/library_analyzer/processing/migration/model/test_mapping.py +++ b/tests/library_analyzer/processing/migration/model/test_mapping.py @@ -21,7 +21,7 @@ def test_one_to_one_mapping() -> None: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a test", "This is a test"), + documentation=ClassDocumentation("This is a test"), code="", instance_attributes=[], ) @@ -81,7 +81,7 @@ def test_many_to_many_mapping() -> None: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a test", "This is a test"), + documentation=ClassDocumentation("This is a test"), code="", instance_attributes=[], ) @@ -110,7 +110,7 @@ def test_too_different_mapping() -> None: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a test", "This is a test"), + documentation=ClassDocumentation("This is a test"), code="", instance_attributes=[], ) @@ -123,7 +123,7 @@ def test_too_different_mapping() -> None: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("not similar to the other class", "not similar to the other class"), + documentation=ClassDocumentation("not similar to the other class"), code=cleandoc( """ @@ -165,7 +165,7 @@ def create_apis() -> tuple[API, API, Class, Class, Class]: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a test", "This is a test"), + documentation=ClassDocumentation("This is a test"), code="", instance_attributes=[], ) @@ -178,7 +178,7 @@ def create_apis() -> tuple[API, API, Class, Class, Class]: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a test", "This is a test"), + documentation=ClassDocumentation("This is a test"), code="", instance_attributes=[], ) @@ -189,7 +189,7 @@ def create_apis() -> tuple[API, API, Class, Class, Class]: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a test", "This is a test"), + documentation=ClassDocumentation("This is a test"), code="", instance_attributes=[], ) diff --git a/tests/library_analyzer/processing/migration/model/test_strict_differ.py b/tests/library_analyzer/processing/migration/model/test_strict_differ.py index e61cc3be..5e24fb6f 100644 --- a/tests/library_analyzer/processing/migration/model/test_strict_differ.py +++ b/tests/library_analyzer/processing/migration/model/test_strict_differ.py @@ -45,7 +45,7 @@ class Test: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a test", "This is a test"), + documentation=ClassDocumentation("This is a test"), code=code_a, instance_attributes=[attribute_a], ) @@ -64,7 +64,7 @@ class newTest: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a new test", "This is a new test"), + documentation=ClassDocumentation("This is a new test"), code=code_b, instance_attributes=[attribute_b], ) @@ -101,7 +101,6 @@ def test(test_parameter: str): reexported_by=[], documentation=FunctionDocumentation( "This test function is a for testing", - "This test function is a for testing", ), code=code_function_a, ) @@ -139,7 +138,6 @@ def test_method(test_parameter: str): reexported_by=[], documentation=FunctionDocumentation( "This test function is a test", - "This test function is a test", ), code=code_b, ) diff --git a/tests/library_analyzer/processing/migration/model/test_unchanged_differ.py b/tests/library_analyzer/processing/migration/model/test_unchanged_differ.py index f24abf28..8f9d527a 100644 --- a/tests/library_analyzer/processing/migration/model/test_unchanged_differ.py +++ b/tests/library_analyzer/processing/migration/model/test_unchanged_differ.py @@ -35,7 +35,7 @@ class Test: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a test", "This is a test"), + documentation=ClassDocumentation("This is a test"), code=code_a, instance_attributes=[attribute_a], ) @@ -54,7 +54,7 @@ class newTest: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("This is a new test", "This is a new test"), + documentation=ClassDocumentation("This is a new test"), code=code_b, instance_attributes=[attribute_b], ) @@ -91,7 +91,6 @@ def test(test_parameter: str): reexported_by=[], documentation=FunctionDocumentation( "This test function is a for testing", - "This test function is a for testing", ), code=code_function_a, ) @@ -129,7 +128,6 @@ def test_method(test_parameter: str): reexported_by=[], documentation=FunctionDocumentation( "This test function is a test", - "This test function is a test", ), code=code_b, ) diff --git a/tests/migration/annotations/called_after_migration.py b/tests/migration/annotations/called_after_migration.py index 27795664..5b0032e4 100644 --- a/tests/migration/annotations/called_after_migration.py +++ b/tests/migration/annotations/called_after_migration.py @@ -32,7 +32,7 @@ def migrate_called_after_annotation_data_one_to_one_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv1_before = Function( @@ -43,7 +43,7 @@ def migrate_called_after_annotation_data_one_to_one_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_after = Function( @@ -54,7 +54,7 @@ def migrate_called_after_annotation_data_one_to_one_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_before = Function( @@ -65,7 +65,7 @@ def migrate_called_after_annotation_data_one_to_one_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) mapping_after = OneToOneMapping(1.0, functionv1_after, functionv2_after) @@ -104,7 +104,7 @@ def migrate_called_after_annotation_data_one_to_many_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv1_before = Function( @@ -115,7 +115,7 @@ def migrate_called_after_annotation_data_one_to_many_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_after_a = Function( @@ -126,7 +126,7 @@ def migrate_called_after_annotation_data_one_to_many_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_after_b = Function( @@ -137,7 +137,7 @@ def migrate_called_after_annotation_data_one_to_many_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_before = Function( @@ -148,7 +148,7 @@ def migrate_called_after_annotation_data_one_to_many_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) mapping_after = OneToManyMapping(1.0, functionv1_after, [functionv2_after_a, functionv2_after_b]) @@ -199,7 +199,7 @@ def migrate_called_after_annotation_data_one_to_one_mapping__no_mapping_found() results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_after = Function( @@ -210,7 +210,7 @@ def migrate_called_after_annotation_data_one_to_one_mapping__no_mapping_found() results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) mapping_after = OneToOneMapping(1.0, functionv1_after, functionv2_after) @@ -248,7 +248,7 @@ def migrate_called_after_annotation_data_one_to_one_mapping__before_splits() -> results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv1_before = Function( @@ -259,7 +259,7 @@ def migrate_called_after_annotation_data_one_to_one_mapping__before_splits() -> results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_after = Function( @@ -270,7 +270,7 @@ def migrate_called_after_annotation_data_one_to_one_mapping__before_splits() -> results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_before_a = Function( @@ -281,7 +281,7 @@ def migrate_called_after_annotation_data_one_to_one_mapping__before_splits() -> results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_before_b = Function( @@ -292,7 +292,7 @@ def migrate_called_after_annotation_data_one_to_one_mapping__before_splits() -> results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) mapping_after = OneToOneMapping(1.0, functionv1_after, functionv2_after) @@ -335,7 +335,7 @@ def migrate_called_after_annotation_data_one_to_many_mapping__two_classes() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv1_before = Function( @@ -346,7 +346,7 @@ def migrate_called_after_annotation_data_one_to_many_mapping__two_classes() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_after_a = Function( @@ -357,7 +357,7 @@ def migrate_called_after_annotation_data_one_to_many_mapping__two_classes() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_after_b = Function( @@ -368,7 +368,7 @@ def migrate_called_after_annotation_data_one_to_many_mapping__two_classes() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_before_a = Function( @@ -379,7 +379,7 @@ def migrate_called_after_annotation_data_one_to_many_mapping__two_classes() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_before_b = Function( @@ -390,7 +390,7 @@ def migrate_called_after_annotation_data_one_to_many_mapping__two_classes() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) mapping_after = OneToManyMapping(1.0, functionv1_after, [functionv2_after_a, functionv2_after_b]) @@ -441,7 +441,7 @@ def migrate_called_after_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv1_after_2 = Function( @@ -452,7 +452,7 @@ def migrate_called_after_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv1_before = Function( @@ -463,7 +463,7 @@ def migrate_called_after_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_after = Function( @@ -474,7 +474,7 @@ def migrate_called_after_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_before = Function( @@ -485,7 +485,7 @@ def migrate_called_after_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) mapping_after = ManyToOneMapping(1.0, [functionv1_after, functionv1_after_2], functionv2_after) diff --git a/tests/migration/annotations/description_migration.py b/tests/migration/annotations/description_migration.py index efc5fa50..334f9985 100644 --- a/tests/migration/annotations/description_migration.py +++ b/tests/migration/annotations/description_migration.py @@ -40,7 +40,7 @@ def migrate_description_annotation_data_one_to_one_mapping__function() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -52,7 +52,7 @@ def migrate_description_annotation_data_one_to_one_mapping__function() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -91,7 +91,7 @@ def migrate_description_annotation_data_one_to_many_mapping__class() -> ( superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("", ""), + documentation=ClassDocumentation(""), code="class DescriptionTestClass:\n pass", instance_attributes=[], ) @@ -102,7 +102,7 @@ def migrate_description_annotation_data_one_to_many_mapping__class() -> ( superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("", ""), + documentation=ClassDocumentation(""), code="class NewDescriptionTestClass:\n pass", instance_attributes=[], ) @@ -113,7 +113,7 @@ def migrate_description_annotation_data_one_to_many_mapping__class() -> ( superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("", ""), + documentation=ClassDocumentation(""), code="class NewDescriptionTestClass2:\n pass", instance_attributes=[], ) @@ -125,7 +125,7 @@ def migrate_description_annotation_data_one_to_many_mapping__class() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -229,7 +229,7 @@ def migrate_description_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv1_2 = Function( @@ -240,7 +240,7 @@ def migrate_description_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -252,7 +252,7 @@ def migrate_description_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) diff --git a/tests/migration/annotations/expert_migration.py b/tests/migration/annotations/expert_migration.py index f118c8d3..87e3a15e 100644 --- a/tests/migration/annotations/expert_migration.py +++ b/tests/migration/annotations/expert_migration.py @@ -40,7 +40,7 @@ def migrate_expert_annotation_data__function() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -52,7 +52,7 @@ def migrate_expert_annotation_data__function() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -89,7 +89,7 @@ def migrate_expert_annotation_data__class() -> ( superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("", ""), + documentation=ClassDocumentation(""), code="class ExpertTestClass:\n pass", instance_attributes=[], ) @@ -100,7 +100,7 @@ def migrate_expert_annotation_data__class() -> ( superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("", ""), + documentation=ClassDocumentation(""), code="class NewExpertTestClass:\n pass", instance_attributes=[], ) @@ -112,7 +112,7 @@ def migrate_expert_annotation_data__class() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -201,7 +201,7 @@ def migrate_expert_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv1_2 = Function( @@ -212,7 +212,7 @@ def migrate_expert_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -224,7 +224,7 @@ def migrate_expert_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) diff --git a/tests/migration/annotations/group_migration.py b/tests/migration/annotations/group_migration.py index b2efb9fc..e7b0d5ba 100644 --- a/tests/migration/annotations/group_migration.py +++ b/tests/migration/annotations/group_migration.py @@ -68,7 +68,7 @@ def migrate_group_annotation_data_one_to_many_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -107,7 +107,7 @@ def migrate_group_annotation_data_one_to_many_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -137,7 +137,7 @@ def migrate_group_annotation_data_one_to_many_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -167,7 +167,7 @@ def migrate_group_annotation_data_one_to_many_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) parameterv2_4_b = Parameter( @@ -187,7 +187,7 @@ def migrate_group_annotation_data_one_to_many_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv2_5 = Function( @@ -198,7 +198,7 @@ def migrate_group_annotation_data_one_to_many_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) classv2_6 = Class( @@ -208,7 +208,7 @@ def migrate_group_annotation_data_one_to_many_mapping() -> ( superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("", ""), + documentation=ClassDocumentation(""), code="class NewClass:\n pass", instance_attributes=[], ) @@ -328,7 +328,7 @@ def migrate_group_annotation_data_one_to_one_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) parameterv1_a = Parameter( @@ -358,7 +358,7 @@ def migrate_group_annotation_data_one_to_one_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) parameterv2_a = Parameter( @@ -423,7 +423,7 @@ def migrate_group_annotation_data_one_to_one_mapping__one_mapping_for_parameters results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) parameterv1_a = Parameter( @@ -453,7 +453,7 @@ def migrate_group_annotation_data_one_to_one_mapping__one_mapping_for_parameters results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) parameterv2_a = Parameter( @@ -571,7 +571,7 @@ def migrate_group_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv1_2 = Function( @@ -582,7 +582,7 @@ def migrate_group_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -621,7 +621,7 @@ def migrate_group_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) diff --git a/tests/migration/annotations/move_migration.py b/tests/migration/annotations/move_migration.py index 5c6b1a8b..792d20c8 100644 --- a/tests/migration/annotations/move_migration.py +++ b/tests/migration/annotations/move_migration.py @@ -37,7 +37,7 @@ def migrate_move_annotation_data_one_to_one_mapping__global_function() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -49,7 +49,7 @@ def migrate_move_annotation_data_one_to_one_mapping__global_function() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -88,7 +88,7 @@ def migrate_move_annotation_data_one_to_one_mapping__class() -> ( superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("", ""), + documentation=ClassDocumentation(""), code="class MoveTestClass:\n pass", instance_attributes=[], ) @@ -99,7 +99,7 @@ def migrate_move_annotation_data_one_to_one_mapping__class() -> ( superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("", ""), + documentation=ClassDocumentation(""), code="class NewMoveTestClass:\n pass", instance_attributes=[], ) @@ -140,7 +140,7 @@ def migrate_move_annotation_data_one_to_many_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -152,7 +152,7 @@ def migrate_move_annotation_data_one_to_many_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -164,7 +164,7 @@ def migrate_move_annotation_data_one_to_many_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -212,7 +212,7 @@ def migrate_move_annotation_data_one_to_one_mapping_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv1_2 = Function( @@ -223,7 +223,7 @@ def migrate_move_annotation_data_one_to_one_mapping_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -235,7 +235,7 @@ def migrate_move_annotation_data_one_to_one_mapping_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) diff --git a/tests/migration/annotations/remove_migration.py b/tests/migration/annotations/remove_migration.py index c7da6a8b..1074a422 100644 --- a/tests/migration/annotations/remove_migration.py +++ b/tests/migration/annotations/remove_migration.py @@ -37,7 +37,7 @@ def migrate_remove_annotation_data_one_to_one_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -49,7 +49,7 @@ def migrate_remove_annotation_data_one_to_one_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -86,7 +86,7 @@ def migrate_remove_annotation_data_one_to_many_mapping() -> ( superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("", ""), + documentation=ClassDocumentation(""), code="class RemoveTestClass:\n pass", instance_attributes=[], ) @@ -97,7 +97,7 @@ def migrate_remove_annotation_data_one_to_many_mapping() -> ( superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("", ""), + documentation=ClassDocumentation(""), code="class NewRemoveTestClass:\n pass", instance_attributes=[], ) @@ -108,7 +108,7 @@ def migrate_remove_annotation_data_one_to_many_mapping() -> ( superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("", ""), + documentation=ClassDocumentation(""), code="class NewRemoveTestClass2:\n pass", instance_attributes=[], ) @@ -120,7 +120,7 @@ def migrate_remove_annotation_data_one_to_many_mapping() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -173,7 +173,7 @@ def migrate_remove_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) functionv1_2 = Function( @@ -184,7 +184,7 @@ def migrate_remove_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) @@ -196,7 +196,7 @@ def migrate_remove_annotation_data_duplicated() -> ( results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) diff --git a/tests/migration/annotations/rename_migration.py b/tests/migration/annotations/rename_migration.py index 1a2f22b8..b6f2e57b 100644 --- a/tests/migration/annotations/rename_migration.py +++ b/tests/migration/annotations/rename_migration.py @@ -109,7 +109,7 @@ def migrate_rename_annotation_data_one_to_many_mapping() -> ( superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("", ""), + documentation=ClassDocumentation(""), code="class NewClass:\n pass", instance_attributes=[], ) diff --git a/tests/migration/annotations/todo_migration.py b/tests/migration/annotations/todo_migration.py index efa495fa..d90c7294 100644 --- a/tests/migration/annotations/todo_migration.py +++ b/tests/migration/annotations/todo_migration.py @@ -168,7 +168,7 @@ def migrate_todo_annotation_data_many_to_many_mapping() -> tuple[Mapping, Abstra superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("", ""), + documentation=ClassDocumentation(""), code="class TestTodoClass:\n pass", instance_attributes=[], ) diff --git a/tests/migration/test_migration.py b/tests/migration/test_migration.py index 0bd15aa9..26da26da 100644 --- a/tests/migration/test_migration.py +++ b/tests/migration/test_migration.py @@ -314,7 +314,7 @@ def test_handle_duplicates() -> None: superclasses=[], is_public=True, reexported_by=[], - documentation=ClassDocumentation("", ""), + documentation=ClassDocumentation(""), code="", instance_attributes=[], ) @@ -395,7 +395,7 @@ def test_was_moved() -> None: results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ) assert _was_moved(function, function, move_annotation) is False @@ -410,7 +410,7 @@ def test_was_moved() -> None: results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ), move_annotation, @@ -428,7 +428,7 @@ def test_was_moved() -> None: results=[], is_public=True, reexported_by=[], - documentation=FunctionDocumentation("", ""), + documentation=FunctionDocumentation(""), code="", ), move_annotation,