Files
rio/scripts/check_docs.py
2024-04-14 16:22:40 +02:00

144 lines
4.3 KiB
Python

"""
Helper script for running checks on documentation, such as looking for missing
docstrings.
"""
import inspect
import sys
from pathlib import Path
from typing import * # type: ignore
# Some rio modules optionally depend on libraries and evaling their type
# annotations can fail if they're not installed. Import them explicitly here to produce more obvious error messages
from revel import * # type: ignore
# Make sure `rio_docs` can be imported
sys.path.append(str(Path(__file__).absolute().parent.parent))
import rio_docs
import website
import rio
def check_function(
docs: rio_docs.FunctionDocs,
owning_cls: type | None,
) -> None:
qualname = (
f"{owning_cls.__name__}.{docs.name}" if owning_cls is not None else docs.name
)
# __init__ methods for components need no documentation, since the
# class' documentation already fills that role
if (
docs.name == "__init__"
and owning_cls is not None
and issubclass(owning_cls, rio.Component)
): # type: ignore
return
# Run checks
if docs.summary is None and docs.name != "__init__":
warning(f"Docstring for `{qualname}` is missing a short description")
if docs.details is None and docs.name != "__init__":
warning(f"Docstring for `{qualname}` is missing a long description")
if docs.return_type is None:
warning(f"`{qualname}` is missing a return type hint")
# Chain to parameters
for param in docs.parameters:
if param.name == "self":
continue
if param.type is None:
warning(f"`{qualname}` is missing a type hint for parameter `{param.name}`")
if param.description is None:
warning(
f"Docstring for `{qualname}` is missing a description for parameter `{param.name}`"
)
def check_class(cls: type, docs: rio_docs.ClassDocs) -> None:
# Run checks
if docs.summary is None:
warning(f"Docstring for `{docs.name}` is missing a short description")
if docs.details is None:
warning(f"Docstring for `{docs.name}` is missing a long description")
for attr in docs.attributes:
if attr.description is None:
warning(f"Docstring for `{docs.name}.{attr.name}` is missing a description")
for func_docs in docs.functions:
check_function(func_docs, cls)
def main() -> None:
print_chapter("Saving you hours of debugging")
print(
"Note: If you get an error about an undefined value (usually a "
"type), you're most likely missing a `from typing import *` in one "
"of rio's files."
)
print()
print(
"An easy way to find out is to run this script in a debugger and "
"have it stop on exceptions. Go up one scope in the stack and "
"display the value of `globalns`."
)
# Find all items that should be documented
print_chapter("Looking for items needing documentation")
target_items: list[type | Callable[..., Any]] = list(
rio_docs.custom.find_items_needing_documentation()
)
print(f"Found {len(target_items)} items")
# Make sure they're all properly documented
print_chapter("Making you depressed")
for item in target_items:
# Classes / Components
if inspect.isclass(item):
# Fetch the docs
docs = rio_docs.ClassDocs.parse(item)
# Post-process them as needed
if isinstance(item, rio.Component):
rio_docs.custom.postprocess_component_docs(docs)
else:
rio_docs.custom.postprocess_class_docs(docs)
check_class(item, docs)
else:
assert inspect.isfunction(item), item
docs = rio_docs.FunctionDocs.parse(item)
check_function(docs, None)
# Make sure all items are displayed Rio's documentation
visited_item_names: set[str] = set()
for entry in website.structure.DOCUMENTATION_STRUCTURE_LINEAR:
section_name, section_url, builder = entry
if not isinstance(builder, website.article_models.ArticleBuilder):
continue
visited_item_names.add(builder.component_class.__name__)
for item in target_items:
if item.__name__ not in visited_item_names:
warning(f"Item `{item.__name__}` is not displayed in the documentation")
if __name__ == "__main__":
main()