Skip to content

Commit 6a1dd04

Browse files
committed
WIP
1 parent c27e52b commit 6a1dd04

9 files changed

Lines changed: 83 additions & 69 deletions

File tree

src/docstub-stubs/_docstrings.pyi

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import click
1010
import lark
1111
import lark.visitors
1212
import numpydoc.docscrape as npds
13-
from _typeshed import Incomplete as Expr
1413

1514
from ._analysis import PyImport, TypeMatcher
1615
from ._doctype import BlacklistedQualname, Expr, Term, TermKind, parse_doctype

src/docstub-stubs/_doctype.pyi

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ from collections.abc import Generator, Iterable, Sequence
77
from dataclasses import dataclass
88
from pathlib import Path
99
from textwrap import indent
10-
from typing import Any, Final, Literal, Self
10+
from typing import Any, Final, Self
1111

1212
import lark
1313
import lark.visitors
1414
from _typeshed import Incomplete
15-
from _typeshed import Incomplete as Expression
1615

16+
from ._report import ContextReporter
1717
from ._utils import DocstubError
1818

1919
logger: Final
@@ -55,7 +55,7 @@ class Expr:
5555
@property
5656
def names(self) -> list[Term]: ...
5757
@property
58-
def sub_expressions(self) -> list[Expression] | Literal[1]: ...
58+
def sub_expressions(self) -> list[Self]: ...
5959
def __iter__(self) -> Generator[Expr | Term]: ...
6060
def format_tree(self) -> str: ...
6161
def print_tree(self) -> None: ...
@@ -69,9 +69,9 @@ class BlacklistedQualname(DocstubError):
6969
pass
7070

7171
class DoctypeTransformer(lark.visitors.Transformer):
72+
def __init__(self, *, reporter: ContextReporter | None = ...) -> None: ...
7273
def start(self, tree: lark.Tree) -> Expr: ...
7374
def qualname(self, tree: lark.Tree) -> Term: ...
74-
def qualname(self, tree: lark.Tree) -> Term: ...
7575
def rst_role(self, tree: lark.Tree) -> Expr: ...
7676
def ELLIPSES(self, token: lark.Token) -> Term: ...
7777
def union(self, tree: lark.Tree) -> Expr: ...
@@ -90,6 +90,4 @@ class DoctypeTransformer(lark.visitors.Transformer):
9090
def extra_info(self, tree: lark.Tree) -> lark.visitors._DiscardType: ...
9191
def _format_subscription(self, sequence: Sequence[str], *, rule: str) -> Expr: ...
9292

93-
_transformer: Final
94-
95-
def parse_doctype(doctype: str) -> Expr: ...
93+
def parse_doctype(doctype: str, *, reporter: ContextReporter | None = ...) -> Expr: ...

src/docstub-stubs/_report.pyi

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,43 @@ class ContextReporter:
3535
short: str,
3636
*args: Any,
3737
log_level: int,
38-
details: str | None = ...,
38+
details: str | tuple[Any, ...] | None = ...,
3939
**log_kw: Any
4040
) -> None: ...
4141
def debug(
42-
self, short: str, *args: Any, details: str | None = ..., **log_kw: Any
42+
self,
43+
short: str,
44+
*args: Any,
45+
details: str | tuple[Any, ...] | None = ...,
46+
**log_kw: Any
4347
) -> None: ...
4448
def info(
45-
self, short: str, *args: Any, details: str | None = ..., **log_kw: Any
49+
self,
50+
short: str,
51+
*args: Any,
52+
details: str | tuple[Any, ...] | None = ...,
53+
**log_kw: Any
4654
) -> None: ...
4755
def warn(
48-
self, short: str, *args: Any, details: str | None = ..., **log_kw: Any
56+
self,
57+
short: str,
58+
*args: Any,
59+
details: str | tuple[Any, ...] | None = ...,
60+
**log_kw: Any
4961
) -> None: ...
5062
def error(
51-
self, short: str, *args: Any, details: str | None = ..., **log_kw: Any
63+
self,
64+
short: str,
65+
*args: Any,
66+
details: str | tuple[Any, ...] | None = ...,
67+
**log_kw: Any
5268
) -> None: ...
5369
def critical(
54-
self, short: str, *args: Any, details: str | None = ..., **log_kw: Any
70+
self,
71+
short: str,
72+
*args: Any,
73+
details: str | tuple[Any, ...] | None = ...,
74+
**log_kw: Any
5575
) -> None: ...
5676
def __post_init__(self) -> None: ...
5777
@staticmethod

src/docstub/_docstrings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def _update_qualnames(expr, *, _parents=()):
2929
3030
Parameters
3131
----------
32-
expr : Expr
32+
expr : ~.Expr
3333
_parents : tuple of (~._doctype.Expr, ...)
3434
3535
Yields
@@ -266,7 +266,7 @@ def doctype_to_annotation(doctype, *, matcher=None, reporter=None, stats=None):
266266
stats = Stats() if stats is None else stats
267267

268268
try:
269-
expression = parse_doctype(doctype)
269+
expression = parse_doctype(doctype, reporter=reporter)
270270
stats.inc_counter("transformed_doctypes")
271271
reporter.debug(
272272
"Transformed doctype", details=(" %s\n-> %s", doctype, expression)

src/docstub/_doctype.py

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import lark
1313
import lark.visitors
1414

15+
from ._report import ContextReporter
1516
from ._utils import DocstubError
1617

1718
logger: Final = logging.getLogger(__name__)
@@ -152,7 +153,7 @@ def sub_expressions(self):
152153
153154
Returns
154155
-------
155-
names : list of Expr or {1}
156+
names : list of Self
156157
"""
157158
cls = type(self)
158159
for child in self.children:
@@ -188,7 +189,7 @@ def format_tree(self):
188189

189190
def print_tree(self):
190191
"""Print full hierarchy as a tree."""
191-
print(self.format_tree())
192+
print(self.format_tree()) # noqa: T201
192193

193194
def __repr__(self) -> str:
194195
return f"<{type(self).__name__}: '{self.as_code()}' rule='{self.rule}'>"
@@ -209,40 +210,25 @@ class BlacklistedQualname(DocstubError):
209210

210211
@lark.visitors.v_args(tree=True)
211212
class DoctypeTransformer(lark.visitors.Transformer):
212-
def start(self, tree):
213+
def __init__(self, *, reporter=None):
213214
"""
214215
Parameters
215216
----------
216-
tree : lark.Tree
217-
218-
Returns
219-
-------
220-
out : Expr
217+
reporter : ~.ContextReporter
221218
"""
222-
return Expr(rule="start", children=tree.children)
219+
self.reporter = reporter or ContextReporter(logger=logger)
223220

224-
def qualname(self, tree):
221+
def start(self, tree):
225222
"""
226223
Parameters
227224
----------
228225
tree : lark.Tree
229226
230227
Returns
231228
-------
232-
out : Term
229+
out : Expr
233230
"""
234-
children = tree.children
235-
_qualname = ".".join(children)
236-
237-
if _qualname in BLACKLISTED_QUALNAMES:
238-
raise BlacklistedQualname(_qualname)
239-
240-
_qualname = Term(
241-
_qualname,
242-
kind=TermKind.NAME,
243-
pos=(tree.meta.start_pos, tree.meta.end_pos),
244-
)
245-
return _qualname
231+
return Expr(rule="start", children=tree.children)
246232

247233
def qualname(self, tree):
248234
"""
@@ -381,11 +367,11 @@ def natlang_literal(self, tree):
381367
out = self._format_subscription(items, rule="natlang_literal")
382368

383369
if len(tree.children) == 1:
384-
logger.warning(
385-
"natural language literal with one item `%s`, "
386-
"consider using `%s` to improve readability",
370+
details = ("Consider using `%s` to improve readability", "".join(out))
371+
self.reporter.warn(
372+
"Natural language literal with one item: `{%s}`",
387373
tree.children[0],
388-
"".join(out),
374+
details=details,
389375
)
390376
return out
391377

@@ -524,16 +510,14 @@ def _format_subscription(self, sequence, *, rule):
524510
return expr
525511

526512

527-
_transformer: Final = DoctypeTransformer()
528-
529-
530-
def parse_doctype(doctype):
513+
def parse_doctype(doctype, *, reporter=None):
531514
"""Turn a type description in a docstring into a type annotation.
532515
533516
Parameters
534517
----------
535518
doctype : str
536519
The doctype to parse.
520+
reporter : ~.ContextReporter, optional
537521
538522
Returns
539523
-------
@@ -553,5 +537,6 @@ def parse_doctype(doctype):
553537
<Expr: 'ndarray[float | int]' rule='start'>
554538
"""
555539
tree = _lark.parse(doctype)
556-
expression = _transformer.transform(tree=tree)
540+
transformer = DoctypeTransformer(reporter=reporter)
541+
expression = transformer.transform(tree=tree)
557542
return expression

src/docstub/_report.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def report(self, short, *args, log_level, details=None, **log_kw):
9595
Optional formatting arguments for `short`.
9696
log_level : int
9797
The logging level.
98-
details : str, optional
98+
details : str or tuple of (Any, ...), optional
9999
An optional multiline report with more details.
100100
**log_kw : Any
101101
"""
@@ -118,7 +118,7 @@ def debug(self, short, *args, details=None, **log_kw):
118118
A short summarizing report that shouldn't wrap over multiple lines.
119119
*args : Any
120120
Optional formatting arguments for `short`.
121-
details : str, optional
121+
details : str or tuple of (Any, ...), optional
122122
An optional multiline report with more details.
123123
**log_kw : Any
124124
"""
@@ -135,7 +135,7 @@ def info(self, short, *args, details=None, **log_kw):
135135
A short summarizing report that shouldn't wrap over multiple lines.
136136
*args : Any
137137
Optional formatting arguments for `short`.
138-
details : str, optional
138+
details : str or tuple of (Any, ...), optional
139139
An optional multiline report with more details.
140140
**log_kw : Any
141141
"""
@@ -152,7 +152,7 @@ def warn(self, short, *args, details=None, **log_kw):
152152
A short summarizing report that shouldn't wrap over multiple lines.
153153
*args : Any
154154
Optional formatting arguments for `short`.
155-
details : str, optional
155+
details : str or tuple of (Any, ...), optional
156156
An optional multiline report with more details.
157157
**log_kw : Any
158158
"""
@@ -169,7 +169,7 @@ def error(self, short, *args, details=None, **log_kw):
169169
A short summarizing report that shouldn't wrap over multiple lines.
170170
*args : Any
171171
Optional formatting arguments for `short`.
172-
details : str, optional
172+
details : str or tuple of (Any, ...), optional
173173
An optional multiline report with more details.
174174
**log_kw : Any
175175
"""
@@ -186,7 +186,7 @@ def critical(self, short, *args, details=None, **log_kw):
186186
A short summarizing report that shouldn't wrap over multiple lines.
187187
*args : Any
188188
Optional formatting arguments for `short`.
189-
details : str, optional
189+
details : str or tuple of (Any, ...), optional
190190
An optional multiline report with more details.
191191
**log_kw : Any
192192
"""

src/docstub/doctype.lark

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ literal.1: qualname "[" literal_item ("," literal_item)* "]"
7878

7979
// An single item in a literal expression (or `optional`). We must also allow
8080
// for qualified names, since a "class" or enum can be used as a literal too.
81-
literal_item: ELLIPSES | STRING | SIGNED_NUMBER | qualname
81+
literal_item: STRING | SIGNED_NUMBER | qualname
8282

8383

8484
// Natural language forms of the subscription expression for containers.

tests/test_docstrings.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
from docstub._analysis import PyImport
66
from docstub._docstrings import (
77
Annotation,
8-
doctype_to_annotation,
98
DocstringAnnotations,
9+
doctype_to_annotation,
1010
)
1111

1212

@@ -37,34 +37,36 @@ def test_unexpected_value(self):
3737

3838

3939
class Test_doctype_to_annotation:
40-
41-
def test_unknown_name(self):
40+
def test_unknown_name(self, caplog):
4241
# Simple unknown name is aliased to typing.Any
4342
annotation = doctype_to_annotation("a")
4443
assert annotation.value == "a"
4544
assert annotation.imports == {
4645
PyImport(import_="Incomplete", from_="_typeshed", as_="a")
4746
}
48-
assert unknown_names == [("a", 0, 1)]
47+
assert caplog.messages == ["Unknown name in doctype: 'a'"]
4948

50-
def test_unknown_qualname(self):
49+
def test_unknown_qualname(self, caplog):
5150
# Unknown qualified name is escaped and aliased to typing.Any as well
5251
annotation = doctype_to_annotation("a.b")
5352
assert annotation.value == "a_b"
5453
assert annotation.imports == {
5554
PyImport(import_="Incomplete", from_="_typeshed", as_="a_b")
5655
}
57-
assert unknown_names == [("a.b", 0, 3)]
56+
assert caplog.messages == ["Unknown name in doctype: 'a.b'"]
5857

59-
def test_multiple_unknown_names(self):
58+
def test_multiple_unknown_names(self, caplog):
6059
# Multiple names are aliased to typing.Any
6160
annotation = doctype_to_annotation("a.b of c")
6261
assert annotation.value == "a_b[c]"
6362
assert annotation.imports == {
6463
PyImport(import_="Incomplete", from_="_typeshed", as_="a_b"),
6564
PyImport(import_="Incomplete", from_="_typeshed", as_="c"),
6665
}
67-
assert unknown_names == [("a.b", 0, 3), ("c", 7, 8)]
66+
assert sorted(caplog.messages) == [
67+
"Unknown name in doctype: 'a.b'",
68+
"Unknown name in doctype: 'c'",
69+
]
6870

6971

7072
class Test_DocstringAnnotations:
@@ -279,4 +281,4 @@ def test_combined_numpydoc_params(self):
279281
assert annotations.parameters["c"].value == "bool"
280282

281283
assert "d" not in annotations.parameters
282-
assert "e" not in annotations.parameters
284+
assert "e" not in annotations.parameters

0 commit comments

Comments
 (0)