Skip to content

Commit 8ec9501

Browse files
committed
WIP
1 parent 23afd6b commit 8ec9501

15 files changed

Lines changed: 586 additions & 730 deletions

src/docstub-stubs/_analysis.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ from typing import Any, ClassVar
1414
import libcst as cst
1515
import libcst.matchers as cstm
1616

17+
from ._report import Stats
1718
from ._utils import accumulate_qualname, module_name_from_path, pyfile_checksum
1819

1920
logger: logging.Logger
@@ -83,6 +84,7 @@ class TypeMatcher:
8384
types: dict[str, PyImport] | None = ...,
8485
type_prefixes: dict[str, PyImport] | None = ...,
8586
type_nicknames: dict[str, str] | None = ...,
87+
stats: Stats | None = ...,
8688
) -> None: ...
8789
def _resolve_nickname(self, name: str) -> str: ...
8890
def match(self, search: str) -> tuple[str | None, PyImport | None]: ...

src/docstub-stubs/_app_generate_stubs.pyi

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ from collections import Counter
66
from collections.abc import Iterable, Sequence
77
from contextlib import contextmanager
88
from pathlib import Path
9-
from typing import Literal
109

1110
from ._analysis import PyImport, TypeCollector, TypeMatcher, common_known_types
1211
from ._cache import CACHE_DIR_NAME, FileCache
@@ -18,9 +17,8 @@ from ._path_utils import (
1817
walk_source_and_targets,
1918
walk_source_package,
2019
)
21-
from ._report import setup_logging
20+
from ._report import Stats, setup_logging
2221
from ._stubs import Py2StubTransformer, try_format_stub
23-
from ._utils import update_with_add_values
2422
from ._version import __version__
2523

2624
logger: logging.Logger

src/docstub-stubs/_cli.pyi

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import logging
44
import sys
55
from collections.abc import Callable, Sequence
66
from pathlib import Path
7-
from typing import Literal
87

98
import click
109
from _typeshed import Incomplete

src/docstub-stubs/_docstrings.pyi

Lines changed: 10 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,22 @@ import traceback
55
from collections.abc import Generator, Iterable
66
from dataclasses import dataclass, field
77
from functools import cached_property
8-
from pathlib import Path
9-
from typing import Any, ClassVar
108

119
import click
1210
import lark
1311
import lark.visitors
1412
import numpydoc.docscrape as npds
1513

1614
from ._analysis import PyImport, TypeMatcher
17-
from ._report import ContextReporter
18-
from ._utils import DocstubError, escape_qualname
15+
from ._doctype import BlacklistedQualname, Expression, Token, TokenKind, parse_doctype
16+
from ._report import ContextReporter, Stats
17+
from ._utils import escape_qualname
1918

2019
logger: logging.Logger
2120

22-
here: Path
23-
grammar_path: Path
24-
25-
with grammar_path.open() as file:
26-
_grammar: str
27-
28-
_lark: lark.Lark
29-
21+
def update_qualnames(
22+
expr: Expression, *, _parents: tuple[Expression, ...] = ...
23+
) -> Generator[tuple[tuple[Expression, ...], Token], str]: ...
3024
def _find_one_token(tree: lark.Tree, *, name: str) -> lark.Token: ...
3125
@dataclass(frozen=True, slots=True, kw_only=True)
3226
class Annotation:
@@ -54,51 +48,23 @@ class Annotation:
5448

5549
FallbackAnnotation: Annotation
5650

57-
class QualnameIsKeyword(DocstubError):
58-
pass
59-
60-
class DoctypeTransformer(lark.visitors.Transformer):
61-
matcher: TypeMatcher
62-
stats: dict[str, Any]
63-
64-
blacklisted_qualnames: ClassVar[frozenset[str]]
65-
66-
def __init__(
67-
self, *, matcher: TypeMatcher | None = ..., **kwargs: dict[Any, Any]
68-
) -> None: ...
69-
def doctype_to_annotation(
70-
self, doctype: str, *, reporter: ContextReporter | None = ...
71-
) -> tuple[Annotation, list[tuple[str, int, int]]]: ...
72-
def qualname(self, tree: lark.Tree) -> lark.Token: ...
73-
def rst_role(self, tree: lark.Tree) -> lark.Token: ...
74-
def union(self, tree: lark.Tree) -> str: ...
75-
def subscription(self, tree: lark.Tree) -> str: ...
76-
def natlang_literal(self, tree: lark.Tree) -> str: ...
77-
def natlang_container(self, tree: lark.Tree) -> str: ...
78-
def natlang_array(self, tree: lark.Tree) -> str: ...
79-
def array_name(self, tree: lark.Tree) -> lark.Token: ...
80-
def shape(self, tree: lark.Tree) -> lark.visitors._DiscardType: ...
81-
def optional_info(self, tree: lark.Tree) -> lark.visitors._DiscardType: ...
82-
def __default__(
83-
self, data: lark.Token, children: list[lark.Token], meta: lark.tree.Meta
84-
) -> lark.Token | list[lark.Token]: ...
85-
def _match_import(self, qualname: str, *, meta: lark.tree.Meta) -> str: ...
86-
8751
def _uncombine_numpydoc_params(
8852
params: list[npds.Parameter],
8953
) -> Generator[npds.Parameter]: ...
54+
def _red_partial_underline(doctype: str, *, start: int, stop: int) -> str: ...
9055

9156
class DocstringAnnotations:
9257
docstring: str
93-
transformer: DoctypeTransformer
58+
matcher: TypeMatcher
9459
reporter: ContextReporter
9560

9661
def __init__(
9762
self,
9863
docstring: str,
9964
*,
100-
transformer: DoctypeTransformer,
65+
matcher: TypeMatcher,
10166
reporter: ContextReporter | None = ...,
67+
stats: Stats | None = ...,
10268
) -> None: ...
10369
def _doctype_to_annotation(
10470
self, doctype: str, ds_line: int = ...

src/docstub-stubs/_report.pyi

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
import dataclasses
44
import logging
5+
from collections.abc import Hashable, Iterator, Mapping, Sequence
56
from pathlib import Path
67
from textwrap import indent
7-
from typing import Any, ClassVar, Literal, Self, TextIO
8+
from typing import Any, ClassVar, Self, TextIO
89

910
import click
11+
from pre_commit.envcontext import UNSET
1012

1113
from ._cli_help import should_strip_ansi
1214

@@ -79,3 +81,22 @@ class LogCounter(logging.NullHandler):
7981
def setup_logging(
8082
*, verbosity: Literal[-2, -1, 0, 1, 2, 3], group_errors: bool
8183
) -> tuple[ReportHandler, LogCounter]: ...
84+
def update_with_add_values(
85+
*mappings: Mapping[Hashable, int | Sequence], out: dict | None = ...
86+
) -> dict: ...
87+
88+
class Stats(Mapping):
89+
class _UNSET:
90+
pass
91+
92+
def __init__(self, stats: dict[str, list[Any] | str] | None = ...) -> None: ...
93+
def __getitem__(self, key: str) -> list[Any] | int: ...
94+
def __iter__(self) -> Iterator: ...
95+
def __len__(self) -> int: ...
96+
def inc_counter(self, key: str, *, inc: int = ...) -> None: ...
97+
def append_to_list(self, key: str, value: Any) -> None: ...
98+
@classmethod
99+
def merge(cls, *stats: Self) -> Self: ...
100+
def __repr__(self) -> str: ...
101+
def pop(self, key: str, *, default: Any = ...) -> list[Any] | int: ...
102+
def pop_all(self) -> dict[str, list[Any] | int]: ...

src/docstub-stubs/_stubs.pyi

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,10 @@ import libcst.matchers as cstm
1212
from _typeshed import Incomplete
1313

1414
from ._analysis import PyImport, TypeMatcher
15-
from ._docstrings import (
16-
Annotation,
17-
DocstringAnnotations,
18-
DoctypeTransformer,
19-
FallbackAnnotation,
20-
)
21-
from ._report import ContextReporter
22-
from ._utils import module_name_from_path, update_with_add_values
15+
from ._docstrings import Annotation, DocstringAnnotations, FallbackAnnotation
16+
from ._doctype import DoctypeTransformer
17+
from ._report import ContextReporter, Stats
18+
from ._utils import module_name_from_path
2319

2420
logger: logging.Logger
2521

@@ -73,9 +69,6 @@ class Py2StubTransformer(cst.CSTTransformer):
7369
@property
7470
def is_inside_function_def(self) -> bool: ...
7571
def python_to_stub(self, source: str, *, module_path: Path | None = ...) -> str: ...
76-
def collect_stats(
77-
self, *, reset_after: bool = ...
78-
) -> dict[str, int | list[str]]: ...
7972
def visit_ClassDef(self, node: cst.ClassDef) -> Literal[True]: ...
8073
def leave_ClassDef(
8174
self, original_node: cst.ClassDef, updated_node: cst.ClassDef

src/docstub-stubs/_utils.pyi

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import itertools
44
import re
5-
from collections.abc import Callable, Hashable, Mapping, Sequence
5+
from collections.abc import Callable
66
from functools import lru_cache, wraps
77
from pathlib import Path
88
from zlib import crc32
@@ -12,9 +12,6 @@ def escape_qualname(name: str) -> str: ...
1212
def _resolve_path_before_caching(func: Callable) -> Callable: ...
1313
def module_name_from_path(path: Path) -> str: ...
1414
def pyfile_checksum(path: Path) -> str: ...
15-
def update_with_add_values(
16-
*mappings: Mapping[Hashable, int | Sequence], out: dict | None = ...
17-
) -> dict: ...
1815

1916
class DocstubError(Exception):
2017
pass

src/docstub/_analysis.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import libcst.matchers as cstm
1414

1515
from ._utils import accumulate_qualname, module_name_from_path, pyfile_checksum
16+
from ._report import Stats
1617

1718
logger: logging.Logger = logging.getLogger(__name__)
1819

@@ -492,22 +493,21 @@ def __init__(
492493
types=None,
493494
type_prefixes=None,
494495
type_nicknames=None,
496+
stats=None,
495497
):
496498
"""
497499
Parameters
498500
----------
499501
types : dict[str, PyImport]
500502
type_prefixes : dict[str, PyImport]
501503
type_nicknames : dict[str, str]
504+
stats : ~.Stats, optional
502505
"""
503506
self.types = common_known_types() | (types or {})
504507
self.type_prefixes = type_prefixes or {}
505508
self.type_nicknames = type_nicknames or {}
506509

507-
self.stats = {
508-
"matched_type_names": 0,
509-
"unknown_type_names": [],
510-
}
510+
self.stats = stats or Stats()
511511

512512
self.current_file = None
513513

@@ -623,8 +623,8 @@ def match(self, search):
623623
type_name = type_name[type_name.find(py_import.target) :]
624624

625625
if type_name is not None:
626-
self.stats["matched_type_names"] += 1
626+
self.stats.inc_counter("matched_type_names")
627627
else:
628-
self.stats["unknown_type_names"].append(search)
628+
self.stats.append_to_list("unknown_type_names", search)
629629

630630
return type_name, py_import

src/docstub/_app_generate_stubs.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,8 @@
2424
walk_source_and_targets,
2525
walk_source_package,
2626
)
27-
from ._report import setup_logging
27+
from ._report import setup_logging, Stats
2828
from ._stubs import Py2StubTransformer, try_format_stub
29-
from ._utils import update_with_add_values
3029
from ._version import __version__
3130

3231
logger: logging.Logger = logging.getLogger(__name__)
@@ -234,7 +233,9 @@ def _generate_single_stub(task):
234233
logger.info("Wrote %s", stub_path)
235234
fo.write(stub_content)
236235

237-
stats = stub_transformer.collect_stats()
236+
stats = Stats.merge(
237+
stub_transformer.stats.pop_all(), stub_transformer.matcher.stats.pop_all()
238+
)
238239

239240
return stats
240241

@@ -350,7 +351,7 @@ def generate_stubs(
350351
stats_per_task = executor.map(
351352
_generate_single_stub, task_args, chunksize=chunk_size
352353
)
353-
stats = update_with_add_values(*stats_per_task)
354+
stats = Stats.merge(*stats_per_task)
354355

355356
py_typed_out = out_dir / "py.typed"
356357
if not py_typed_out.exists():
@@ -368,24 +369,26 @@ def generate_stubs(
368369
total_warnings = error_counter.warning_count
369370
total_errors = error_counter.error_count
370371

371-
logger.info("Recognized type names: %i", stats["matched_type_names"])
372-
logger.info("Transformed doctypes: %i", stats["transformed_doctypes"])
372+
logger.info("Recognized type names: %i", stats.pop("matched_type_names", default=0))
373+
logger.info("Transformed doctypes: %i", stats.pop("transformed_doctypes", default=0))
373374
if total_warnings:
374375
logger.warning("Warnings: %i", total_warnings)
375-
if stats["doctype_syntax_errors"]:
376+
if "doctype_syntax_errors" in stats:
376377
assert total_errors
377-
logger.warning("Syntax errors: %i", stats["doctype_syntax_errors"])
378-
if stats["unknown_type_names"]:
378+
logger.warning("Syntax errors: %i", stats.pop("doctype_syntax_errors"))
379+
if "unknown_type_names" in stats:
379380
assert total_errors
380381
logger.warning(
381382
"Unknown type names: %i (locations: %i)",
382383
len(set(stats["unknown_type_names"])),
383384
len(stats["unknown_type_names"]),
384-
extra={"details": _format_unknown_names(stats["unknown_type_names"])},
385+
extra={"details": _format_unknown_names(stats.pop("unknown_type_names"))},
385386
)
386387
if total_errors:
387388
logger.error("Total errors: %i", total_errors)
388389

390+
assert len(stats) == 0
391+
389392
total_fails = total_errors
390393
if fail_on_warning:
391394
total_fails += total_warnings

0 commit comments

Comments
 (0)