From c5060079e425bfb60b7db617b598764e54980318 Mon Sep 17 00:00:00 2001 From: Ray Lehtiniemi Date: Fri, 10 Mar 2017 16:28:19 -0700 Subject: [PATCH 1/3] Fix #3348: Show decorators in literalinclude and viewcode directives Signed-off-by: Ray Lehtiniemi --- CHANGES | 1 + sphinx/pycode/__init__.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 14bf4621d..1eb7a13c3 100644 --- a/CHANGES +++ b/CHANGES @@ -80,6 +80,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 48c62ecf7..182c0e758 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -296,6 +296,7 @@ class ModuleAnalyzer(object): 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 @@ class ModuleAnalyzer(object): 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 From 06b6e07b9832be16c5f23e3a224b60cc4d9a020f Mon Sep 17 00:00:00 2001 From: Ray Lehtiniemi Date: Sat, 11 Mar 2017 11:46:03 -0700 Subject: [PATCH 2/3] Add tests Signed-off-by: Ray Lehtiniemi --- .../test-directive-code/py-decorators.inc | 16 +++++++ .../test-directive-code/py-decorators.rst | 17 ++++++++ tests/test_directive_code.py | 42 +++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 tests/roots/test-directive-code/py-decorators.inc create mode 100644 tests/roots/test-directive-code/py-decorators.rst 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 000000000..b1864ab8d --- /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 000000000..31417f590 --- /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/test_directive_code.py b/tests/test_directive_code.py index f2ca9f040..3ace7fb5f 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 + From 560c8ab29d3269eb7f2260256a8d7eb160719c17 Mon Sep 17 00:00:00 2001 From: Ray Lehtiniemi Date: Sat, 11 Mar 2017 14:16:23 -0700 Subject: [PATCH 3/3] Add more decorator tests Signed-off-by: Ray Lehtiniemi --- tests/roots/test-ext-viewcode/spam/mod1.py | 5 +++++ tests/roots/test-ext-viewcode/spam/mod2.py | 5 +++++ tests/test_ext_viewcode.py | 1 + 3 files changed, 11 insertions(+) diff --git a/tests/roots/test-ext-viewcode/spam/mod1.py b/tests/roots/test-ext-viewcode/spam/mod1.py index 7133fc829..94fceff7a 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 79834b665..5953c94fe 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_ext_viewcode.py b/tests/test_ext_viewcode.py index e4763119a..3f810ef86 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'])