google-docstring-parser 0.0.11__tar.gz → 0.0.12__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. google_docstring_parser-0.0.12/.gitignore +176 -0
  2. google_docstring_parser-0.0.12/.pre-commit-hooks.yaml +14 -0
  3. {google_docstring_parser-0.0.11 → google_docstring_parser-0.0.12}/PKG-INFO +43 -37
  4. {google_docstring_parser-0.0.11 → google_docstring_parser-0.0.12}/README.md +33 -25
  5. {google_docstring_parser-0.0.11 → google_docstring_parser-0.0.12}/google_docstring_parser/__init__.py +1 -0
  6. {google_docstring_parser-0.0.11 → google_docstring_parser-0.0.12}/google_docstring_parser/google_docstring_parser.py +80 -36
  7. {google_docstring_parser-0.0.11 → google_docstring_parser-0.0.12}/google_docstring_parser/type_validation.py +2 -2
  8. {google_docstring_parser-0.0.11 → google_docstring_parser-0.0.12}/pyproject.toml +31 -42
  9. google_docstring_parser-0.0.12/requirements-dev.txt +1 -0
  10. google_docstring_parser-0.0.12/tests/__init__.py +0 -0
  11. google_docstring_parser-0.0.12/tests/test_brackets_validation.py +184 -0
  12. google_docstring_parser-0.0.12/tests/test_docstring_checker/__init__.py +0 -0
  13. google_docstring_parser-0.0.12/tests/test_docstring_checker/test_check_docstrings.py +1032 -0
  14. google_docstring_parser-0.0.12/tests/test_docstring_checker/test_docstring_validation.py +278 -0
  15. google_docstring_parser-0.0.12/tests/test_docstring_checker/test_example_files.py +118 -0
  16. google_docstring_parser-0.0.12/tests/test_docstring_checker/test_malformed_docstrings.py +131 -0
  17. google_docstring_parser-0.0.12/tests/test_docstring_checker/test_valid_docstrings.py +75 -0
  18. google_docstring_parser-0.0.12/tests/test_multiline_returns.py +131 -0
  19. google_docstring_parser-0.0.12/tests/test_parser.py +248 -0
  20. google_docstring_parser-0.0.12/tests/test_references.py +403 -0
  21. google_docstring_parser-0.0.12/tests/test_type_validation.py +229 -0
  22. google_docstring_parser-0.0.12/tests/test_type_validation_edge_cases.py +175 -0
  23. google_docstring_parser-0.0.12/tools/README.md +138 -0
  24. {google_docstring_parser-0.0.11 → google_docstring_parser-0.0.12}/tools/__init__.py +1 -0
  25. {google_docstring_parser-0.0.11 → google_docstring_parser-0.0.12}/tools/check_docstrings.py +42 -34
  26. google_docstring_parser-0.0.11/google_docstring_parser.egg-info/PKG-INFO +0 -192
  27. google_docstring_parser-0.0.11/google_docstring_parser.egg-info/SOURCES.txt +0 -14
  28. google_docstring_parser-0.0.11/google_docstring_parser.egg-info/dependency_links.txt +0 -1
  29. google_docstring_parser-0.0.11/google_docstring_parser.egg-info/requires.txt +0 -5
  30. google_docstring_parser-0.0.11/google_docstring_parser.egg-info/top_level.txt +0 -2
  31. google_docstring_parser-0.0.11/setup.cfg +0 -4
  32. {google_docstring_parser-0.0.11 → google_docstring_parser-0.0.12}/LICENSE +0 -0
  33. {google_docstring_parser-0.0.11 → google_docstring_parser-0.0.12}/google_docstring_parser/py.typed +0 -0
@@ -0,0 +1,176 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # Ruff stuff:
171
+ .ruff_cache/
172
+
173
+ # PyPI configuration file
174
+ .pypirc
175
+
176
+ uv.lock
@@ -0,0 +1,14 @@
1
+ - id: check-google-docstrings
2
+ name: Check Google-style docstrings can be parsed
3
+ description: >
4
+ Checks that Google-style docstrings in specified folders can be parsed with google_docstring_parser.
5
+ IMPORTANT: You MUST configure paths in pyproject.toml under [tool.docstring_checker] section,
6
+ otherwise no files will be checked. For example:
7
+ [tool.docstring_checker]
8
+ paths = ["your_package", "your_module"]
9
+ entry: python -m tools.check_docstrings
10
+ language: python
11
+ types: [python]
12
+ pass_filenames: false
13
+ require_serial: true
14
+ args: [] # Default empty args
@@ -1,36 +1,34 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: google-docstring-parser
3
- Version: 0.0.11
3
+ Version: 0.0.12
4
4
  Summary: A lightweight, efficient parser for Google-style Python docstrings that converts them into structured dictionaries.
5
5
  Author: Vladimir Iglovikov
6
6
  Maintainer: Vladimir Iglovikov
7
- License: Custom License - See LICENSE file for details
7
+ License-Expression: LicenseRef-Proprietary
8
+ License-File: LICENSE
8
9
  Keywords: docstring,documentation,google-style,parser,python,static-analysis,type-hints
9
10
  Classifier: Development Status :: 5 - Production/Stable
10
11
  Classifier: Intended Audience :: Developers
11
12
  Classifier: Intended Audience :: Science/Research
12
- Classifier: License :: Other/Proprietary License
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python
15
15
  Classifier: Programming Language :: Python :: 3 :: Only
16
- Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Programming Language :: Python :: 3.13
20
19
  Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
21
21
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
22
  Classifier: Topic :: Software Development :: Documentation
23
23
  Classifier: Topic :: Software Development :: Libraries
24
24
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
25
  Classifier: Typing :: Typed
26
- Requires-Python: >=3.10
27
- Description-Content-Type: text/markdown
28
- License-File: LICENSE
29
- Requires-Dist: docstring-parser>=0.16
26
+ Requires-Python: >=3.11
27
+ Requires-Dist: docstring-parser>=0.18
30
28
  Provides-Extra: dev
31
- Requires-Dist: pre-commit>=3.5; extra == "dev"
32
- Requires-Dist: pytest>=8.3.3; extra == "dev"
33
- Dynamic: license-file
29
+ Requires-Dist: pre-commit>=4.7; extra == 'dev'
30
+ Requires-Dist: pytest>=9.2; extra == 'dev'
31
+ Description-Content-Type: text/markdown
34
32
 
35
33
  # Google Docstring Parser
36
34
 
@@ -40,6 +38,8 @@ Dynamic: license-file
40
38
 
41
39
  A Python package for parsing Google-style docstrings into structured dictionaries.
42
40
 
41
+ Requires Python 3.11 or newer.
42
+
43
43
  ## License Information
44
44
 
45
45
  This package is available under a custom license:
@@ -59,7 +59,7 @@ pip install google-docstring-parser
59
59
  ```python
60
60
  from google_docstring_parser import parse_google_docstring
61
61
 
62
- docstring = '''Apply elastic deformation to images, masks, bounding boxes, and keypoints.
62
+ docstring = """Apply elastic deformation to images, masks, bounding boxes, and keypoints.
63
63
 
64
64
  This transformation introduces random elastic distortions to the input data. It's particularly
65
65
  useful for data augmentation in training deep learning models, especially for tasks like
@@ -81,7 +81,7 @@ References:
81
81
  - Implementation details: https://example.com/elastic-transform
82
82
  Returns:
83
83
  dict[str, Any]: Some info here
84
- '''
84
+ """
85
85
 
86
86
  parsed = parse_google_docstring(docstring)
87
87
  print(parsed)
@@ -90,35 +90,28 @@ print(parsed)
90
90
  Output:
91
91
  ```python
92
92
  {
93
- 'Description': 'Apply elastic deformation to images, masks, bounding boxes, and keypoints.\n\nThis transformation introduces random elastic distortions to the input data. It\'s particularly\nuseful for data augmentation in training deep learning models, especially for tasks like\nimage segmentation or object detection where you want to maintain the relative positions of\nfeatures while introducing realistic deformations.',
94
- 'Args': [
93
+ "Description": "Apply elastic deformation to images, masks, bounding boxes, and keypoints.\n\nThis transformation introduces random elastic distortions to the input data. It's particularly\nuseful for data augmentation in training deep learning models, especially for tasks like\nimage segmentation or object detection where you want to maintain the relative positions of\nfeatures while introducing realistic deformations.",
94
+ "Args": [
95
95
  {
96
- 'name': 'alpha',
97
- 'type': 'float',
98
- 'description': 'Scaling factor for the random displacement fields. Higher values result in\nmore pronounced distortions. Default: 1.0'
96
+ "name": "alpha",
97
+ "type": "float",
98
+ "description": "Scaling factor for the random displacement fields. Higher values result in\nmore pronounced distortions. Default: 1.0",
99
99
  },
100
100
  {
101
- 'name': 'sigma',
102
- 'type': 'float',
103
- 'description': 'Standard deviation of the Gaussian filter used to smooth the displacement\nfields. Higher values result in smoother, more global distortions. Default: 50.0'
104
- }
101
+ "name": "sigma",
102
+ "type": "float",
103
+ "description": "Standard deviation of the Gaussian filter used to smooth the displacement\nfields. Higher values result in smoother, more global distortions. Default: 50.0",
104
+ },
105
105
  ],
106
- 'Example': '>>> import albumentations as A\n>>> transform = A.ElasticTransform(alpha=1, sigma=50, p=0.5)',
107
- 'References': [
106
+ "Example": ">>> import albumentations as A\n>>> transform = A.ElasticTransform(alpha=1, sigma=50, p=0.5)",
107
+ "References": [
108
108
  {
109
- 'description': 'Original paper',
110
- 'source': 'Simard, P. Y., et al. "Best practices for convolutional neural networks applied to visual document analysis." ICDAR 2003'
109
+ "description": "Original paper",
110
+ "source": 'Simard, P. Y., et al. "Best practices for convolutional neural networks applied to visual document analysis." ICDAR 2003',
111
111
  },
112
- {
113
- 'description': 'Implementation details',
114
- 'source': 'https://example.com/elastic-transform'
115
- }
112
+ {"description": "Implementation details", "source": "https://example.com/elastic-transform"},
116
113
  ],
117
- 'Returns':
118
- {
119
- "type": "dict[str, Any]",
120
- "description": "Some info here"
121
- }
114
+ "Returns": {"type": "dict[str, Any]", "description": "Some info here"},
122
115
  }
123
116
  ```
124
117
 
@@ -130,6 +123,20 @@ Output:
130
123
  - Handles multi-line descriptions and indentation properly
131
124
  - Properly parses and validates References sections with special handling for URLs
132
125
 
126
+ ### Wrapped return annotations
127
+
128
+ Long return types can span several lines. The parser accepts wrapped unions, generics, and parenthesized types:
129
+
130
+ ```text
131
+ Returns:
132
+ dict[
133
+ str,
134
+ list[int | str],
135
+ ]: Results grouped by name.
136
+ ```
137
+
138
+ The parsed type is `dict[str, list[int | str]]`. Quoted colons and brackets inside `Literal` values are preserved, and wrapped prose remains untyped. The checker compares type expressions while preserving spaces inside literal values.
139
+
133
140
  ### References
134
141
 
135
142
  The parser can handle reference sections in Google-style docstrings. References can be formatted in two ways:
@@ -170,7 +177,6 @@ To use this hook in another project, add the following to your `.pre-commit-conf
170
177
  rev: v0.0.1 # Use the latest version
171
178
  hooks:
172
179
  - id: check-google-docstrings
173
- additional_dependencies: ["tomli>=2.0.0"] # Required for pyproject.toml configuration
174
180
  ```
175
181
 
176
182
  ### Configuration
@@ -6,6 +6,8 @@
6
6
 
7
7
  A Python package for parsing Google-style docstrings into structured dictionaries.
8
8
 
9
+ Requires Python 3.11 or newer.
10
+
9
11
  ## License Information
10
12
 
11
13
  This package is available under a custom license:
@@ -25,7 +27,7 @@ pip install google-docstring-parser
25
27
  ```python
26
28
  from google_docstring_parser import parse_google_docstring
27
29
 
28
- docstring = '''Apply elastic deformation to images, masks, bounding boxes, and keypoints.
30
+ docstring = """Apply elastic deformation to images, masks, bounding boxes, and keypoints.
29
31
 
30
32
  This transformation introduces random elastic distortions to the input data. It's particularly
31
33
  useful for data augmentation in training deep learning models, especially for tasks like
@@ -47,7 +49,7 @@ References:
47
49
  - Implementation details: https://example.com/elastic-transform
48
50
  Returns:
49
51
  dict[str, Any]: Some info here
50
- '''
52
+ """
51
53
 
52
54
  parsed = parse_google_docstring(docstring)
53
55
  print(parsed)
@@ -56,35 +58,28 @@ print(parsed)
56
58
  Output:
57
59
  ```python
58
60
  {
59
- 'Description': 'Apply elastic deformation to images, masks, bounding boxes, and keypoints.\n\nThis transformation introduces random elastic distortions to the input data. It\'s particularly\nuseful for data augmentation in training deep learning models, especially for tasks like\nimage segmentation or object detection where you want to maintain the relative positions of\nfeatures while introducing realistic deformations.',
60
- 'Args': [
61
+ "Description": "Apply elastic deformation to images, masks, bounding boxes, and keypoints.\n\nThis transformation introduces random elastic distortions to the input data. It's particularly\nuseful for data augmentation in training deep learning models, especially for tasks like\nimage segmentation or object detection where you want to maintain the relative positions of\nfeatures while introducing realistic deformations.",
62
+ "Args": [
61
63
  {
62
- 'name': 'alpha',
63
- 'type': 'float',
64
- 'description': 'Scaling factor for the random displacement fields. Higher values result in\nmore pronounced distortions. Default: 1.0'
64
+ "name": "alpha",
65
+ "type": "float",
66
+ "description": "Scaling factor for the random displacement fields. Higher values result in\nmore pronounced distortions. Default: 1.0",
65
67
  },
66
68
  {
67
- 'name': 'sigma',
68
- 'type': 'float',
69
- 'description': 'Standard deviation of the Gaussian filter used to smooth the displacement\nfields. Higher values result in smoother, more global distortions. Default: 50.0'
70
- }
69
+ "name": "sigma",
70
+ "type": "float",
71
+ "description": "Standard deviation of the Gaussian filter used to smooth the displacement\nfields. Higher values result in smoother, more global distortions. Default: 50.0",
72
+ },
71
73
  ],
72
- 'Example': '>>> import albumentations as A\n>>> transform = A.ElasticTransform(alpha=1, sigma=50, p=0.5)',
73
- 'References': [
74
+ "Example": ">>> import albumentations as A\n>>> transform = A.ElasticTransform(alpha=1, sigma=50, p=0.5)",
75
+ "References": [
74
76
  {
75
- 'description': 'Original paper',
76
- 'source': 'Simard, P. Y., et al. "Best practices for convolutional neural networks applied to visual document analysis." ICDAR 2003'
77
+ "description": "Original paper",
78
+ "source": 'Simard, P. Y., et al. "Best practices for convolutional neural networks applied to visual document analysis." ICDAR 2003',
77
79
  },
78
- {
79
- 'description': 'Implementation details',
80
- 'source': 'https://example.com/elastic-transform'
81
- }
80
+ {"description": "Implementation details", "source": "https://example.com/elastic-transform"},
82
81
  ],
83
- 'Returns':
84
- {
85
- "type": "dict[str, Any]",
86
- "description": "Some info here"
87
- }
82
+ "Returns": {"type": "dict[str, Any]", "description": "Some info here"},
88
83
  }
89
84
  ```
90
85
 
@@ -96,6 +91,20 @@ Output:
96
91
  - Handles multi-line descriptions and indentation properly
97
92
  - Properly parses and validates References sections with special handling for URLs
98
93
 
94
+ ### Wrapped return annotations
95
+
96
+ Long return types can span several lines. The parser accepts wrapped unions, generics, and parenthesized types:
97
+
98
+ ```text
99
+ Returns:
100
+ dict[
101
+ str,
102
+ list[int | str],
103
+ ]: Results grouped by name.
104
+ ```
105
+
106
+ The parsed type is `dict[str, list[int | str]]`. Quoted colons and brackets inside `Literal` values are preserved, and wrapped prose remains untyped. The checker compares type expressions while preserving spaces inside literal values.
107
+
99
108
  ### References
100
109
 
101
110
  The parser can handle reference sections in Google-style docstrings. References can be formatted in two ways:
@@ -136,7 +145,6 @@ To use this hook in another project, add the following to your `.pre-commit-conf
136
145
  rev: v0.0.1 # Use the latest version
137
146
  hooks:
138
147
  - id: check-google-docstrings
139
- additional_dependencies: ["tomli>=2.0.0"] # Required for pyproject.toml configuration
140
148
  ```
141
149
 
142
150
  ### Configuration
@@ -1,3 +1,4 @@
1
+ # Copyright (c) 2025 Vladimir Iglovikov
1
2
  """Google Docstring Parser package.
2
3
 
3
4
  A lightweight, efficient parser for Google-style Python docstrings that converts them into structured dictionaries.
@@ -18,7 +18,10 @@ This module provides functions to parse Google-style docstrings into structured
18
18
 
19
19
  from __future__ import annotations
20
20
 
21
+ import ast
22
+ import io
21
23
  import re
24
+ import tokenize
22
25
  from typing import TYPE_CHECKING, Any
23
26
 
24
27
  from docstring_parser import parse
@@ -438,42 +441,86 @@ def _process_args_with_validation(
438
441
  result["Args"] = args
439
442
 
440
443
 
441
- def _parse_returns_section(sections: dict[str, str], *, validate_types: bool) -> dict[str, str] | str:
442
- """Process the Returns section of a docstring into type and description.
444
+ def _return_annotation_tokens(text: str) -> tuple[list[tuple[int, str]], tuple[int, int] | None]:
445
+ """Find the return separator outside quoted literals and nested brackets.
443
446
 
444
447
  Args:
445
- sections (dict[str, str]): The sections dictionary
446
- validate_types (bool): Whether to validate type annotations
448
+ text (str): Returns section contents.
447
449
 
448
450
  Returns:
449
- dict[str, str] | str: Either:
450
- - A dictionary with 'type' and 'description' keys
451
- - The string 'None' if the section only contains 'None'
452
- - An empty dict if no return information is found
451
+ tuple[list[tuple[int, str]], tuple[int, int] | None]: Prefix tokens and zero-based separator position.
453
452
  """
454
- if (
455
- "Returns" not in sections
456
- or not (returns_lines := sections["Returns"].split("\n"))
457
- or not (return_match := re.match(r"^(?:([^:]+):\s*)?(.*)$", returns_lines[0].strip()))
458
- ):
459
- return {}
453
+ prefix: list[tuple[int, str]] = []
454
+ depth = 0
455
+ ignored = {tokenize.NL, tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT, tokenize.ENDMARKER, tokenize.COMMENT}
456
+ # An implicit group permits wrapping between any two annotation tokens, regardless of indentation.
457
+ tokens = tokenize.generate_tokens(io.StringIO(f"(\n{text}").readline)
458
+ next(tokens)
459
+ try:
460
+ for token in tokens:
461
+ if token.type == tokenize.NL and depth == 0 and not token.line.strip():
462
+ break
463
+ if token.type == tokenize.OP:
464
+ if token.string in "([{":
465
+ depth += 1
466
+ elif token.string in ")]}":
467
+ depth -= 1
468
+ elif token.string == ":" and depth == 0:
469
+ return prefix, (token.start[0] - 2, token.start[1])
470
+ if token.type not in ignored:
471
+ prefix.append((token.type, token.string))
472
+ except (tokenize.TokenError, IndentationError):
473
+ return prefix, None
474
+ return prefix, None
475
+
476
+
477
+ def _is_return_type(expression: ast.expr) -> bool:
478
+ """Distinguish Python type expressions from wrapped numeric or prose content.
460
479
 
461
- return_type = return_match[1]
462
- return_desc = return_match[2].strip()
480
+ Args:
481
+ expression (ast.expr): Parsed candidate return annotation.
463
482
 
464
- # Special case: Returns section just contains "None"
465
- if not return_type and return_desc == "None":
466
- return "None"
483
+ Returns:
484
+ bool: Whether the candidate has the shape of a type expression.
485
+ """
486
+ match expression:
487
+ case ast.Name() | ast.Attribute() | ast.Subscript() | ast.Constant(value=str() | None):
488
+ return True
489
+ case ast.BinOp(left=left, op=ast.BitOr(), right=right):
490
+ return _is_return_type(left) and _is_return_type(right)
491
+ case _:
492
+ return False
467
493
 
468
- # Validate type if present
469
- if return_type and validate_types:
470
- validate_type_annotation(return_type)
471
494
 
472
- # Check for nested types
473
- if "[" in return_type and "]" in return_type:
474
- check_text_for_bare_collections(return_type)
495
+ def _split_return_header(text: str) -> tuple[str | None, str]:
496
+ """Parse a wrapped type expression while preserving untyped prose.
475
497
 
476
- return {"type": return_type, "description": return_desc.rstrip()}
498
+ Args:
499
+ text (str): Returns section contents.
500
+
501
+ Returns:
502
+ tuple[str | None, str]: Return annotation and its first description line.
503
+ """
504
+ lines = text.split("\n")
505
+ first_line = lines[0].strip()
506
+ prefix, separator = _return_annotation_tokens(text)
507
+ if separator is None:
508
+ return None, first_line
509
+ row, column = separator
510
+ if row == 0:
511
+ annotation = first_line[:column].strip()
512
+ if not annotation:
513
+ return None, first_line
514
+ return annotation, first_line[column + 1 :].strip()
515
+ candidate = tokenize.untokenize(prefix)
516
+ try:
517
+ expression = ast.parse(candidate.strip(), mode="eval").body
518
+ if not _is_return_type(expression):
519
+ return None, first_line
520
+ annotation = ast.unparse(expression)
521
+ except (SyntaxError, ValueError, RecursionError):
522
+ return None, first_line
523
+ return annotation, lines[row][column + 1 :].strip()
477
524
 
478
525
 
479
526
  def _process_returns_with_validation(
@@ -493,16 +540,13 @@ def _process_returns_with_validation(
493
540
  if "Returns" not in sections:
494
541
  return
495
542
 
496
- try:
497
- returns = _parse_returns_section(sections, validate_types=validate_types)
498
- if isinstance(returns, dict) and returns.get("type") and validate_types:
499
- _validate_type_with_error_handling(returns["type"], result, collect_errors)
500
- result["Returns"] = returns
501
- except InvalidTypeAnnotationError as e:
502
- if collect_errors:
503
- result["errors"].append(str(e))
504
- else:
505
- raise
543
+ return_type, return_desc = _split_return_header(sections["Returns"])
544
+ if return_type and validate_types:
545
+ _validate_type_with_error_handling(return_type, result, collect_errors)
546
+ if not return_type and return_desc == "None":
547
+ result["Returns"] = "None"
548
+ else:
549
+ result["Returns"] = {"type": return_type, "description": return_desc}
506
550
 
507
551
 
508
552
  def _process_references_section(sections: dict[str, str], result: dict[str, Any]) -> None:
@@ -112,7 +112,7 @@ class InvalidTypeAnnotationError(ValueError):
112
112
  super().__init__(message)
113
113
 
114
114
 
115
- class BracketValidationError(ValueError):
115
+ class BracketValidationError(InvalidTypeAnnotationError):
116
116
  """Error raised when brackets in a type annotation are not balanced or mismatched.
117
117
 
118
118
  Contains specific error types for different bracket validation issues.
@@ -315,7 +315,7 @@ def replace_string_literals(match: Match[AnyStr], literals: list[str]) -> str:
315
315
  """
316
316
  placeholder = str(match.group(0))
317
317
  if placeholder.startswith("STR_LITERAL_"):
318
- idx = int(placeholder.split("_")[-1])
318
+ idx = int(placeholder.rsplit("_", maxsplit=1)[-1])
319
319
  if idx < len(literals):
320
320
  return literals[idx]
321
321
  return placeholder