diff --git a/CHANGES b/CHANGES index 5aac456ef9d..95f889d8101 100644 --- a/CHANGES +++ b/CHANGES @@ -90,6 +90,7 @@ Features added * HTML buildre uses experimental HTML5 writer if ``html_experimental_html5_builder`` is True and docutils 0.13 and newer is installed. * LaTeX macros to customize space before and after tables in PDF output (refs #3504) +* #3348: Show decorators in literalinclude and viewcode directives Bugs fixed ---------- diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index 48c62ecf75f..182c0e7588e 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -296,6 +296,7 @@ def find_tags(self): namespace = [] # type: List[unicode] stack = [] # type: List[Tuple[unicode, unicode, unicode, int]] indent = 0 + decopos = None defline = False expect_indent = False emptylines = 0 @@ -319,8 +320,12 @@ def tokeniter(ignore = (token.COMMENT,)): name = next(tokeniter)[1] # type: ignore namespace.append(name) fullname = '.'.join(namespace) - stack.append((tok, fullname, spos[0], indent)) + stack.append((tok, fullname, decopos or spos[0], indent)) defline = True + decopos = None + elif type == token.OP and tok == '@': + if decopos is None: + decopos = spos[0] elif type == token.INDENT: expect_indent = False indent += 1 diff --git a/tests/roots/test-directive-code/py-decorators.inc b/tests/roots/test-directive-code/py-decorators.inc new file mode 100644 index 00000000000..b1864ab8d4f --- /dev/null +++ b/tests/roots/test-directive-code/py-decorators.inc @@ -0,0 +1,16 @@ +# Literally included file using Python highlighting +# -*- coding: utf-8 -*- + +@class_decorator +@other_decorator() +class TheClass(object): + + @method_decorator + @other_decorator() + def the_method(): + pass + +@function_decorator +@other_decorator() +def the_function(): + pass diff --git a/tests/roots/test-directive-code/py-decorators.rst b/tests/roots/test-directive-code/py-decorators.rst new file mode 100644 index 00000000000..31417f5908c --- /dev/null +++ b/tests/roots/test-directive-code/py-decorators.rst @@ -0,0 +1,17 @@ +py-decorators +============= + +Various decorators +------------------ + +.. literalinclude:: py-decorators.inc + :name: literal_include_pydecorators_1 + :pyobject: TheClass + +.. literalinclude:: py-decorators.inc + :name: literal_include_pydecorators_2 + :pyobject: TheClass.the_method + +.. literalinclude:: py-decorators.inc + :name: literal_include_pydecorators_3 + :pyobject: the_function diff --git a/tests/roots/test-ext-viewcode/spam/mod1.py b/tests/roots/test-ext-viewcode/spam/mod1.py index 7133fc8295c..94fceff7a0c 100644 --- a/tests/roots/test-ext-viewcode/spam/mod1.py +++ b/tests/roots/test-ext-viewcode/spam/mod1.py @@ -2,6 +2,10 @@ mod1 """ +def decorator(f): + return f + +@decorator def func1(a, b): """ this is func1 @@ -9,6 +13,7 @@ def func1(a, b): return a, b +@decorator class Class1(object): """ this is Class1 diff --git a/tests/roots/test-ext-viewcode/spam/mod2.py b/tests/roots/test-ext-viewcode/spam/mod2.py index 79834b66554..5953c94fe29 100644 --- a/tests/roots/test-ext-viewcode/spam/mod2.py +++ b/tests/roots/test-ext-viewcode/spam/mod2.py @@ -2,6 +2,10 @@ mod2 """ +def decorator(f): + return f + +@decorator def func2(a, b): """ this is func2 @@ -9,6 +13,7 @@ def func2(a, b): return a, b +@decorator class Class2(object): """ this is Class2 diff --git a/tests/test_directive_code.py b/tests/test_directive_code.py index f2ca9f040ab..3ace7fb5f75 100644 --- a/tests/test_directive_code.py +++ b/tests/test_directive_code.py @@ -454,3 +454,45 @@ def test_literalinclude_classes(app, status, warning): assert len(literalinclude) > 0 assert 'bar baz' == literalinclude[0].get('classes') assert 'literal_include' == literalinclude[0].get('names') + + +@pytest.mark.sphinx('xml', testroot='directive-code') +def test_literalinclude_pydecorators(app, status, warning): + app.builder.build(['py-decorators']) + et = etree_parse(app.outdir / 'py-decorators.xml') + secs = et.findall('./section/section') + + literal_include = secs[0].findall('literal_block') + assert len(literal_include) == 3 + + actual = literal_include[0].text + expect = ( + '@class_decorator\n' + '@other_decorator()\n' + 'class TheClass(object):\n' + '\n' + ' @method_decorator\n' + ' @other_decorator()\n' + ' def the_method():\n' + ' pass\n' + ) + assert actual == expect + + actual = literal_include[1].text + expect = ( + ' @method_decorator\n' + ' @other_decorator()\n' + ' def the_method():\n' + ' pass\n' + ) + assert actual == expect + + actual = literal_include[2].text + expect = ( + '@function_decorator\n' + '@other_decorator()\n' + 'def the_function():\n' + ' pass\n' + ) + assert actual == expect + diff --git a/tests/test_ext_viewcode.py b/tests/test_ext_viewcode.py index e4763119ab5..3f810ef86aa 100644 --- a/tests/test_ext_viewcode.py +++ b/tests/test_ext_viewcode.py @@ -30,6 +30,7 @@ def test_viewcode(app, status, warning): assert result.count('href="_modules/spam/mod2.html#func2"') == 2 assert result.count('href="_modules/spam/mod1.html#Class1"') == 2 assert result.count('href="_modules/spam/mod2.html#Class2"') == 2 + assert result.count('@decorator') == 1 @pytest.mark.sphinx(testroot='ext-viewcode', tags=['test_linkcode'])