Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions pydoctor/epydoc2stan.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,11 @@ class ParamType:
stan: Tag
origin: FieldOrigin

def _report_field_has_no_meaning_on_module(obj: model.Documentable, field: Field) -> None:
if isinstance(obj, model.Module):
msg = f"Field '{field.tag}' has no meaning on module"
field.report(msg)

class FieldHandler:

def __init__(self, obj: model.Documentable):
Expand Down Expand Up @@ -310,20 +315,23 @@ def _report_unexpected_argument(field:Field) -> None:
field.report('Unexpected argument in %s field' % (field.tag,))

def handle_return(self, field: Field) -> None:
_report_field_has_no_meaning_on_module(self.obj, field)
self._report_unexpected_argument(field)
if not self.return_desc:
self.return_desc = ReturnDesc()
self.return_desc.body = field.format()
handle_returns = handle_return

def handle_yield(self, field: Field) -> None:
_report_field_has_no_meaning_on_module(self.obj, field)
self._report_unexpected_argument(field)
if not self.yields_desc:
self.yields_desc = FieldDesc()
self.yields_desc.body = field.format()
handle_yields = handle_yield

def handle_returntype(self, field: Field) -> None:
_report_field_has_no_meaning_on_module(self.obj, field)
self._report_unexpected_argument(field)
if not self.return_desc:
self.return_desc = ReturnDesc()
Expand All @@ -332,6 +340,7 @@ def handle_returntype(self, field: Field) -> None:
handle_rtype = handle_returntype

def handle_yieldtype(self, field: Field) -> None:
_report_field_has_no_meaning_on_module(self.obj, field)
self._report_unexpected_argument(field)
if not self.yields_desc:
self.yields_desc = FieldDesc()
Expand Down Expand Up @@ -415,6 +424,7 @@ def handle_type(self, field: Field) -> None:
self.types[name] = ParamType(field.format(), origin=FieldOrigin.FROM_DOCSTRING)

def handle_param(self, field: Field) -> None:
_report_field_has_no_meaning_on_module(self.obj, field)
name = self._handle_param_name(field)
if name is not None:
if any(desc.name == name for desc in self.parameter_descs):
Expand All @@ -426,6 +436,7 @@ def handle_param(self, field: Field) -> None:
handle_arg = handle_param

def handle_keyword(self, field: Field) -> None:
_report_field_has_no_meaning_on_module(self.obj, field)
name = self._handle_param_name(field)
if name is not None:
# TODO: How should this be matched to the type annotation?
Expand Down Expand Up @@ -921,6 +932,11 @@ def extract_fields(obj: model.CanContainImportsDocumentable) -> None:

for field in parsed_doc.fields:
tag = field.tag()
# ivar and cvar fields on modules don't make sense: warn but still
# allow them to be processed so their documentation is rendered.
if tag in ('ivar', 'cvar'):
_report_field_has_no_meaning_on_module(obj, Field.from_epydoc(field, obj))

if tag in ['ivar', 'cvar', 'var', 'type']:
arg = field.arg()
if arg is None:
Expand Down
130 changes: 128 additions & 2 deletions pydoctor/test/test_epydoc2stan.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from pydoctor.stanutils import flatten, flatten_text
from pydoctor.epydoc.markup.epytext import ParsedEpytextDocstring
from pydoctor.sphinx import SphinxInventory
from pydoctor.test.test_astbuilder import fromText, unwrap
from pydoctor.test.test_astbuilder import fromText, unwrap, type2html
from pydoctor.test import CapSys, NotFoundLinker
from pydoctor.templatewriter.search import stem_identifier
from pydoctor.templatewriter.pages import format_signature, format_class_signature
Expand Down Expand Up @@ -904,7 +904,8 @@ def test_missing_field_name(capsys: CapSys) -> None:
''', modname='test')
epydoc2stan.format_docstring(mod)
captured = capsys.readouterr().out
assert captured == "test:5: Missing field name in @ivar\n" \
assert captured == "test:5: Field 'ivar' has no meaning on module\n" \
"test:5: Missing field name in @ivar\n" \
"test:6: Missing field name in @type\n"


Expand All @@ -921,6 +922,131 @@ def test_unknown_field_name(capsys: CapSys) -> None:
assert captured == "test:5: Unknown field 'zap'\n"


def test_param_on_module_warns(capsys: CapSys) -> None:
mod = fromText('''
"""
Module docstring.

@param x: This makes no sense on a module.
"""
''', modname='test')
stan = epydoc2stan.format_docstring(mod)
captured = capsys.readouterr().out
assert captured == (
"test:5: Field 'param' has no meaning on module\n"
"test:5: Documented parameter \"x\" does not exist\n"
)
html = flatten_text(stan)
assert 'This makes no sense on a module.' in html

def test_return_on_module_warns(capsys: CapSys) -> None:
mod = fromText('''
"""
Module docstring.

@return: Nothing to return here.
"""
''', modname='test')
stan = epydoc2stan.format_docstring(mod)
captured = capsys.readouterr().out
assert captured == "test:5: Field 'return' has no meaning on module\n"
html = flatten_text(stan)
assert 'Nothing to return here.' in html

def test_rtype_on_module_warns(capsys: CapSys) -> None:
mod = fromText('''
"""
Module docstring.

@rtype: int
"""
''', modname='test')
stan = epydoc2stan.format_docstring(mod)
captured = capsys.readouterr().out
assert captured == "test:5: Field 'rtype' has no meaning on module\n"
html = flatten_text(stan)
assert 'int' in html

def test_yields_on_module_warns(capsys: CapSys) -> None:
mod = fromText('''
"""
Module docstring.

@yields: each
"""
''', modname='test')
stan = epydoc2stan.format_docstring(mod)
captured = capsys.readouterr().out
assert captured == "test:5: Field 'yields' has no meaning on module\n"
html = flatten_text(stan)
assert 'each' in html

def test_ytype_on_module_warns(capsys: CapSys) -> None:
mod = fromText('''
"""
Module docstring.

@ytype: str
"""
''', modname='test')
stan = epydoc2stan.format_docstring(mod)
captured = capsys.readouterr().out
assert captured == "test:5: Field 'ytype' has no meaning on module\n"
html = flatten_text(stan)
assert 'str' in html

def test_keyword_on_module_warns(capsys: CapSys) -> None:
mod = fromText('''
"""
Module docstring.

@keyword k: something
"""
''', modname='test')
stan = epydoc2stan.format_docstring(mod)
captured = capsys.readouterr().out
assert captured == "test:5: Field 'keyword' has no meaning on module\n"
html = flatten_text(stan)
assert 'something' in html

def test_ivar_in_module_docstring_creates_attribute(capsys: CapSys) -> None:
mod = fromText('''
"""
Module docstring.

@ivar foo: module-level instance var
@type foo: string
"""

foo = 1
''', modname='test')
epydoc2stan.format_docstring(mod)
captured = capsys.readouterr().out
assert captured == "test:5: Field 'ivar' has no meaning on module\n"
a = mod.resolveName('foo')
assert isinstance(a, model.Attribute)
assert unwrap(a.parsed_docstring) == "module-level instance var"
assert type2html(a) == 'string'

def test_cvar_in_module_docstring_creates_attribute(capsys: CapSys) -> None:
mod = fromText('''
"""
Module docstring.

@cvar bar: module-level class var
@type bar: int
"""

bar = 2
''', modname='test')
epydoc2stan.format_docstring(mod)
captured = capsys.readouterr().out
assert captured == "test:5: Field 'cvar' has no meaning on module\n"
a = mod.resolveName('bar')
assert isinstance(a, model.Attribute)
assert unwrap(a.parsed_docstring) == "module-level class var"
assert str(unwrap(a.parsed_type)) == 'int'

def test_inline_field_type(capsys: CapSys) -> None:
"""The C{type} field in a variable docstring updates the C{parsed_type}
of the Attribute it documents.
Expand Down
Loading