Skip to content

Commit ebf51c6

Browse files
committed
fix(docs): enforce Google docstring style for Returns/Raises sections
- Enhanced check_google_docstring_inline_descriptions.py to validate Returns, Raises, and Yields sections - Updated pre-commit-config.yaml to apply validator to all optimizer files - Updated copilot-instructions.md with official Google Python Style Guide references - Updated docs-completion.prompt.md with critical formatting requirements - Enhanced scripts/README.md with comprehensive validator documentation - Fixes #110: Prevents line breaks in parameter descriptions across all docstring sections - Discovered in PR #109: All multi-objective optimizers have Returns section formatting issues
1 parent a1b538a commit ebf51c6

File tree

5 files changed

+229
-28
lines changed

5 files changed

+229
-28
lines changed

.github/copilot-instructions.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,26 @@ uv run python -c "from opt.abstract_optimizer import AbstractOptimizer; from opt
196196
- Pre-commit hooks are configured but run `ruff` manually to be sure
197197
198198
### Docstring Formatting Guidelines (CRITICAL)
199-
**All optimizer docstrings must follow these strict formatting rules to pass Ruff linting:**
199+
**All optimizer docstrings must follow [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods) and pass Ruff linting:**
200200
201+
- **Follow Google docstring conventions**:
202+
- See [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
203+
- See [PEP 257 - Docstring Conventions](https://peps.python.org/pep-0257/)
204+
- See [Sphinx Napoleon - Google Style](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html)
201205
- **Accurate indentation**: Use consistent 4-space indentation for all docstring content
202-
- **No line breaks in Args/Attributes**: Keep parameter descriptions on single lines
206+
- **CRITICAL - No line breaks in Args/Attributes**: Parameter descriptions MUST start on same line as parameter name
203207
- ✅ Correct: `n_bats (int): Number of bats in the population. Recommended: 10-50 bats.`
204-
- ❌ Wrong: Multi-line parameter descriptions that break across lines
208+
- ❌ Wrong: Parameter name on one line, description on next line (common in agent mode)
209+
- ❌ Wrong Example:
210+
```python
211+
func (Callable[[ndarray], float]):
212+
Objective function to minimize. Must accept numpy array.
213+
```
214+
- ✅ Correct Example:
215+
```python
216+
func (Callable[[ndarray], float]): Objective function to minimize. Must accept numpy array.
217+
```
218+
- If description is too long, continue on next line with proper indentation (aligned with first line of description)
205219
- **Use LaTeX for mathematical symbols** (Ruff RUF002 compliance):
206220
- ❌ NEVER use unicode: `×` (multiplication sign), `α` (alpha), `β` (beta), etc.
207221
- ✅ ALWAYS use LaTeX: `$\times$`, `$\alpha$`, `$\beta$`
@@ -212,7 +226,7 @@ uv run python -c "from opt.abstract_optimizer import AbstractOptimizer; from opt
212226
- Complexity: `O(n $\times$ m)` NOT `O(n × m)`
213227
- Greek letters: `$\alpha$`, `$\beta$`, `$\gamma$` NOT `α`, `β`, `γ`
214228
- Budget expressions: `dim $\times$ 10000` NOT `dim×10000`
215-
- **Pre-commit validation**: Run `pre-commit run -a` before committing to catch RUF002 errors
229+
- **Pre-commit validation**: Run `pre-commit run -a` before committing to catch RUF002 and formatting errors
216230
217231
### Creating New Optimizers
218232
- Inherit from `AbstractOptimizer` class in `opt/abstract_optimizer.py`

.github/prompts/docs-completion.prompt.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,45 @@ Stack: VitePress v1.6.4, Vue 3, ECharts, Python 3.10+
2828
- Modify optimizer algorithm logic
2929
- Restructure existing 117 algorithm pages
3030

31+
## Docstring Formatting Requirements (CRITICAL)
32+
33+
**All docstrings must follow [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods).**
34+
35+
### Parameter Description Format
36+
37+
**CRITICAL**: Parameter descriptions MUST start on the **same line** as the parameter name. This is the official Google style.
38+
39+
**❌ WRONG** (line break after parameter name - common in agent mode):
40+
```python
41+
func (Callable[[ndarray], float]):
42+
Objective function to minimize. Must accept numpy array and return scalar.
43+
BBOB functions available in `opt.benchmark.functions`.
44+
```
45+
46+
**✅ CORRECT** (description on same line):
47+
```python
48+
func (Callable[[ndarray], float]): Objective function to minimize. Must accept numpy array and return scalar. BBOB functions available in `opt.benchmark.functions`.
49+
```
50+
51+
**✅ CORRECT** (multi-line with proper indentation):
52+
```python
53+
parameter2 (str): This is a longer definition. I need to include so much
54+
information that it needs a second line. Notice the indentation.
55+
```
56+
57+
### Key Rules
58+
59+
1. **No line breaks between parameter name and description**
60+
2. **Consistent 4-space indentation**
61+
3. **LaTeX for mathematical symbols**: `$\times$` not `×`, `$\alpha$` not `α`
62+
4. **Run validation**: `pre-commit run -a` before committing
63+
64+
### References
65+
66+
- [Google Python Style Guide - Functions and Methods](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods)
67+
- [PEP 257 - Docstring Conventions](https://peps.python.org/pep-0257/)
68+
- [Sphinx Napoleon - Google Style](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html)
69+
3170
---
3271

3372
## Workflow

.pre-commit-config.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ repos:
3535
entry: python scripts/check_google_docstring_inline_descriptions.py
3636
language: system
3737
pass_filenames: true
38-
files: ^opt/(abstract_optimizer\.py|multi_objective/abstract_multi_objective\.py)$
38+
files: ^opt/(abstract_optimizer\.py|multi_objective/abstract_multi_objective\.py|(classical|constrained|evolutionary|gradient_based|metaheuristic|multi_objective|physics_inspired|probabilistic|social_inspired|swarm_intelligence)/.*\.py)$
39+
exclude: ^opt/(benchmark|test|__pycache__|visualization)/.*
3940
# Custom optimizer validator
4041
- id: validate-optimizer-docs
4142
name: Validate optimizer COCO/BBOB compliance

scripts/README.md

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
# Batch Docstring Update Script
1+
# Scripts Documentation
22

3-
## Overview
3+
This directory contains validation and generation scripts for the useful-optimizer library.
44

5-
The `batch_update_docstrings.py` script automates the generation of COCO/BBOB-compliant docstring templates for all optimizer classes in the useful-optimizer library. This eliminates the error-prone and time-consuming manual process of updating 117+ optimizer docstrings.
5+
## Available Scripts
6+
7+
### 1. `batch_update_docstrings.py` - Docstring Template Generator
8+
9+
Automates the generation of COCO/BBOB-compliant docstring templates for all optimizer classes.
610

711
## Features
812

@@ -96,7 +100,10 @@ uv run python scripts/batch_update_docstrings.py --help
96100

97101
```
98102
scripts/
99-
└── batch_update_docstrings.py # Main script
103+
├── batch_update_docstrings.py # Main docstring template generator
104+
├── check_google_docstring_inline_descriptions.py # Enforce Google-style inline parameter descriptions
105+
├── validate_optimizer_docs.py # Validate COCO/BBOB compliance
106+
└── generate_docs.py # VitePress documentation generator
100107
opt/
101108
├── classical/ # 9 optimizers
102109
├── constrained/ # 5 optimizers
@@ -213,6 +220,101 @@ When modifying the script:
213220
## Related Documentation
214221

215222
- [COCO/BBOB Template](../.github/prompts/optimizer-docs-template.prompt.md): Complete docstring template guide
223+
- [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods): Official docstring formatting guidelines
224+
- [Copilot Instructions](../.github/copilot-instructions.md): Comprehensive project development guidelines
225+
226+
---
227+
228+
## 2. `check_google_docstring_inline_descriptions.py` - Google Style Validator
229+
230+
Enforces Google-style docstring formatting by ensuring parameter descriptions start on the same line as the parameter name.
231+
232+
### Purpose
233+
234+
This script prevents the common mistake of adding line breaks between parameter names and their descriptions, which violates the official Google Python Style Guide.
235+
236+
### What It Checks
237+
238+
**✅ CORRECT** (parameter description on same line):
239+
```python
240+
Args:
241+
func (Callable[[ndarray], float]): Objective function to minimize.
242+
lower_bound (float): Lower bound of search space.
243+
```
244+
245+
**❌ WRONG** (line break after parameter name):
246+
```python
247+
Args:
248+
func (Callable[[ndarray], float]):
249+
Objective function to minimize.
250+
lower_bound (float):
251+
Lower bound of search space.
252+
```
253+
254+
### Usage
255+
256+
The script is automatically run by pre-commit hooks on all optimizer files:
257+
258+
```bash
259+
# Run manually on specific files
260+
python scripts/check_google_docstring_inline_descriptions.py opt/swarm_intelligence/particle_swarm.py
261+
262+
# Run via pre-commit (recommended)
263+
pre-commit run google-docstring-inline-summaries --all-files
264+
```
265+
266+
### Pre-Commit Integration
267+
268+
Configured in `.pre-commit-config.yaml` to run on:
269+
- `opt/abstract_optimizer.py`
270+
- `opt/multi_objective/abstract_multi_objective.py`
271+
- All optimizer files in: `classical/`, `constrained/`, `evolutionary/`, `gradient_based/`, `metaheuristic/`, `multi_objective/`, `physics_inspired/`, `probabilistic/`, `social_inspired/`, `swarm_intelligence/`
272+
273+
Excludes: `benchmark/`, `test/`, `__pycache__/`, `visualization/`
274+
275+
### Error Messages
276+
277+
```
278+
opt/swarm_intelligence/particle_swarm.py:45: Docstring entry missing inline summary after type: func (Callable[[ndarray], float]):
279+
```
280+
281+
### How to Fix
282+
283+
Move the description to the same line as the parameter:
284+
285+
```python
286+
# Before (WRONG)
287+
func (Callable[[ndarray], float]):
288+
Objective function to minimize.
289+
290+
# After (CORRECT)
291+
func (Callable[[ndarray], float]): Objective function to minimize.
292+
```
293+
294+
For long descriptions, continue on the next line with proper indentation:
295+
296+
```python
297+
parameter2 (str): This is a longer definition. I need to include so much
298+
information that it needs a second line. Notice the indentation.
299+
```
300+
301+
### References
302+
303+
- **Google Python Style Guide**: https://google.github.io/styleguide/pyguide.html#383-functions-and-methods
304+
- **PEP 257**: https://peps.python.org/pep-0257/
305+
- **Sphinx Napoleon**: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html
306+
307+
---
308+
309+
## 3. `validate_optimizer_docs.py` - COCO/BBOB Compliance Validator
310+
311+
Validates optimizer docstrings for COCO/BBOB scientific benchmarking compliance (see issue [#110](https://github.com/Anselmoo/useful-optimizer/issues/110)).
312+
313+
---
314+
315+
## 4. `generate_docs.py` - VitePress Documentation Generator
316+
317+
Generates VitePress documentation pages from optimizer source code.
216318
- [COCO Platform](https://coco-platform.org/): Official COCO/BBOB documentation
217319
- [BBOB Test Suite](https://coco-platform.org/testsuites/bbob/overview.html): Benchmark suite overview
218320

scripts/check_google_docstring_inline_descriptions.py

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"""Check Google-style docstring entries have inline summaries.
22
33
This enforces a small style rule used in the abstract base classes:
4-
Within "Args:" and "Attributes:" sections, the item line should contain the
5-
short description on the same line as the type.
4+
Within "Args:", "Attributes:", "Returns:", and "Raises:" sections, the item line
5+
should contain the short description on the same line as the type.
66
77
Example (required):
88
history (dict[str, list]): Dictionary containing optimization history.
@@ -11,6 +11,15 @@
1111
history (dict[str, list]):
1212
Dictionary containing optimization history.
1313
14+
Example (Returns/Raises - required):
15+
Returns:
16+
float: The computed value.
17+
18+
Example (Returns/Raises - rejected):
19+
Returns:
20+
float:
21+
The computed value.
22+
1423
This script is intentionally scoped via pre-commit to only a small set of files
1524
to avoid mass changes across the repository.
1625
"""
@@ -23,7 +32,14 @@
2332
from pathlib import Path
2433

2534

26-
_SECTION_HEADERS = {"args:", "arguments:", "attributes:"}
35+
_SECTION_HEADERS = {
36+
"args:",
37+
"arguments:",
38+
"attributes:",
39+
"returns:",
40+
"raises:",
41+
"yields:",
42+
}
2743

2844
_NEXT_SECTION_HEADER_RE = re.compile(
2945
r"^\s{4,}[A-Z][A-Za-z0-9_\- ]*:\s*$" # e.g. "Returns:", "Notes:", "Methods:"
@@ -39,49 +55,78 @@
3955
# (no inline summary) -> error
4056
_ITEM_MISSING_INLINE_SUMMARY_RE = re.compile(r"^\s{8,}[A-Za-z_]\w*\s*\([^)]*\):\s*$")
4157

58+
# For Returns/Raises sections - Match lines like:
59+
# type:
60+
# Description... (WRONG - should be on same line)
61+
# More indented entries (12+ spaces) indicate type declarations
62+
_RETURNS_TYPE_MISSING_INLINE_RE = re.compile(r"^\s{12,}[\w\[\],\s|]+:\s*$")
63+
64+
# For Returns/Raises sections - Match lines like:
65+
# type: Description... (CORRECT)
66+
_RETURNS_TYPE_WITH_INLINE_RE = re.compile(r"^\s{12,}[\w\[\],\s|]+:\s*\S")
67+
4268

4369
def _check_file(path: Path) -> list[str]:
4470
errors: list[str] = []
4571
lines = path.read_text(encoding="utf-8").splitlines()
4672

4773
in_target_section = False
74+
current_section = None
4875

4976
for idx, line in enumerate(lines, start=1):
5077
stripped = line.strip().lower()
5178

5279
if stripped in _SECTION_HEADERS:
5380
in_target_section = True
81+
current_section = stripped.rstrip(":")
5482
continue
5583

5684
if in_target_section:
5785
# End the section when we hit a blank line or a new section header.
5886
if stripped == "" or _NEXT_SECTION_HEADER_RE.match(line):
5987
in_target_section = False
88+
current_section = None
6089
continue
6190

6291
# Ignore bullet lists inside sections.
6392
if line.lstrip().startswith("-"):
6493
continue
6594

66-
if _ITEM_MISSING_INLINE_SUMMARY_RE.match(line):
67-
errors.append(
68-
f"{path}:{idx}: Docstring entry missing inline summary after type: {line.strip()}"
69-
)
70-
continue
71-
72-
# If it looks like an item, ensure it has an inline summary.
73-
if (
74-
"(" in line
75-
and ")" in line
76-
and ":" in line
77-
and line.startswith(" " * 8)
78-
and not _ITEM_WITH_INLINE_SUMMARY_RE.match(line)
79-
):
80-
name_type_prefix = re.match(r"^\s{8,}[A-Za-z_]\w*\s*\([^)]*\):", line)
81-
if name_type_prefix and line.strip().endswith(":"):
95+
# For Args/Attributes sections (parameter-style)
96+
if current_section in ("args", "arguments", "attributes"):
97+
if _ITEM_MISSING_INLINE_SUMMARY_RE.match(line):
8298
errors.append(
8399
f"{path}:{idx}: Docstring entry missing inline summary after type: {line.strip()}"
84100
)
101+
continue
102+
103+
# If it looks like an item, ensure it has an inline summary.
104+
if (
105+
"(" in line
106+
and ")" in line
107+
and ":" in line
108+
and line.startswith(" " * 8)
109+
and not _ITEM_WITH_INLINE_SUMMARY_RE.match(line)
110+
):
111+
name_type_prefix = re.match(
112+
r"^\s{8,}[A-Za-z_]\w*\s*\([^)]*\):", line
113+
)
114+
if name_type_prefix and line.strip().endswith(":"):
115+
errors.append(
116+
f"{path}:{idx}: Docstring entry missing inline summary after type: {line.strip()}"
117+
)
118+
119+
# For Returns/Raises/Yields sections (type-style)
120+
elif current_section in ("returns", "raises", "yields"):
121+
# Check for type declarations missing inline summaries
122+
# Lines with just "type:" and nothing after should have description on same line
123+
# Avoid false positives on continuation lines (those starting with -)
124+
if _RETURNS_TYPE_MISSING_INLINE_RE.match(
125+
line
126+
) and not line.lstrip().startswith("-"):
127+
errors.append(
128+
f"{path}:{idx}: {current_section.capitalize()} entry missing inline summary after type: {line.strip()}"
129+
)
85130

86131
return errors
87132

0 commit comments

Comments
 (0)