diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 60fa1d80..2be3dc85 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,8 +6,8 @@ Changelog 0.8.1 (2014-07-23) +++++++++++++++++++ -* Bugfix release, the last release forgot to include files that improve - autocompletion for builtin libraries. Fixed. +- Bugfix release, the last release forgot to include files that improve + autocompletion for builtin libraries. Fixed. 0.8.0 (2014-05-05) +++++++++++++++++++ @@ -16,26 +16,27 @@ Changelog drastically. Loading times are down as well (it takes basically as long as an import). - REPL completion is starting to become usable. -- Various small API changes. Generally this released focuses on stability and +- Various small API changes. Generally this release focuses on stability and refactoring of internal APIs. -- Introducing operator precedence, which makes calculating correct Array indices - and ``__getattr__`` strings possible. +- Introducing operator precedence, which makes calculating correct Array + indices and ``__getattr__`` strings possible. 0.7.0 (2013-08-09) ++++++++++++++++++ -- Switched from LGPL to MIT license -- Added an Interpreter class to the API to make autocompletion in REPL possible. -- Added autocompletion support for namespace packages -- Add sith.py, a new random testing method +- Switched from LGPL to MIT license. +- Added an Interpreter class to the API to make autocompletion in REPL + possible. +- Added autocompletion support for namespace packages. +- Add sith.py, a new random testing method. 0.6.0 (2013-05-14) ++++++++++++++++++ -- Much faster parser with builtin part caching -- A test suite, thanks @tkf +- Much faster parser with builtin part caching. +- A test suite, thanks @tkf. 0.5 versions (2012) +++++++++++++++++++ -- Initial development +- Initial development. diff --git a/docs/docs/features.rst b/docs/docs/features.rst index 321cbf71..f921e13a 100644 --- a/docs/docs/features.rst +++ b/docs/docs/features.rst @@ -119,10 +119,11 @@ http://sphinx-doc.org/domains.html#info-field-lists :: - def myfunction(node): + def myfunction(node, foo): """Do something with a ``node``. :type node: ProgramNode + :param str foo: foo parameter description """ node.| # complete here diff --git a/jedi/evaluate/docstrings.py b/jedi/evaluate/docstrings.py index 9a789a5d..02f83ba1 100644 --- a/jedi/evaluate/docstrings.py +++ b/jedi/evaluate/docstrings.py @@ -14,6 +14,7 @@ As an addition to parameter searching, this module also provides return annotations. """ +from ast import literal_eval import re from itertools import chain from textwrap import dedent @@ -24,6 +25,7 @@ from jedi.common import indent_block DOCSTRING_PARAM_PATTERNS = [ r'\s*:type\s+%s:\s*([^\n]+)', # Sphinx + r'\s*:param\s+(\w+)\s+%s:[^\n]+', # Sphinx param with type r'\s*@type\s+%s:\s*([^\n]+)', # Epydoc ] @@ -35,26 +37,44 @@ DOCSTRING_RETURN_PATTERNS = [ REST_ROLE_PATTERN = re.compile(r':[^`]+:`([^`]+)`') -@memoize_default(None, evaluator_is_first_arg=True) -def follow_param(evaluator, param): - func = param.parent_function - param_str = _search_param_in_docstr(func.raw_doc, str(param.get_name())) - return _evaluate_for_statement_string(evaluator, param_str, param.get_parent_until()) +try: + from numpydoc.docscrape import NumpyDocString +except ImportError: + def _search_param_in_numpydocstr(docstr, param_str): + return [] +else: + def _search_param_in_numpydocstr(docstr, param_str): + """Search `docstr` (in numpydoc format) for type(-s) of `param_str`.""" + params = NumpyDocString(docstr)._parsed_data['Parameters'] + for p_name, p_type, p_descr in params: + if p_name == param_str: + m = re.match('([^,]+(,[^,]+)*?)(,[ ]*optional)?$', p_type) + if m: + p_type = m.group(1) + + if p_type.startswith('{'): + types = set(type(x).__name__ for x in literal_eval(p_type)) + return list(types) + else: + return [p_type] + return [] def _search_param_in_docstr(docstr, param_str): """ - Search `docstr` for a type of `param_str`. + Search `docstr` for type(-s) of `param_str`. >>> _search_param_in_docstr(':type param: int', 'param') - 'int' + ['int'] >>> _search_param_in_docstr('@type param: int', 'param') - 'int' + ['int'] >>> _search_param_in_docstr( ... ':type param: :class:`threading.Thread`', 'param') - 'threading.Thread' - >>> _search_param_in_docstr('no document', 'param') is None - True + ['threading.Thread'] + >>> bool(_search_param_in_docstr('no document', 'param')) + False + >>> _search_param_in_docstr(':param int param: some description', 'param') + ['int'] """ # look at #40 to see definitions of those params @@ -63,9 +83,10 @@ def _search_param_in_docstr(docstr, param_str): for pattern in patterns: match = pattern.search(docstr) if match: - return _strip_rst_role(match.group(1)) + return [_strip_rst_role(match.group(1))] - return None + return (_search_param_in_numpydocstr(docstr, param_str) or + []) def _strip_rst_role(type_str): @@ -126,6 +147,17 @@ def _evaluate_for_statement_string(evaluator, string, module): return list(chain.from_iterable(it)) or definitions +@memoize_default(None, evaluator_is_first_arg=True) +def follow_param(evaluator, param): + func = param.parent_function + + return [p + for param_str in _search_param_in_docstr(func.raw_doc, + str(param.get_name())) + for p in _evaluate_for_statement_string(evaluator, param_str, + param.get_parent_until())] + + @memoize_default(None, evaluator_is_first_arg=True) def find_return_types(evaluator, func): def search_return_in_docstr(code): diff --git a/test/completion/docstring.py b/test/completion/docstring.py index 1d7bff7f..f80fea31 100644 --- a/test/completion/docstring.py +++ b/test/completion/docstring.py @@ -3,13 +3,14 @@ # ----------------- # sphinx style # ----------------- -def f(a, b, c, d): +def f(a, b, c, d, x): """ asdfasdf :param a: blablabla :type a: str :type b: (str, int) :type c: threading.Thread :type d: :class:`threading.Thread` + :param str x: blablabla :rtype: dict """ #? str() @@ -22,23 +23,28 @@ def f(a, b, c, d): c.join #? ['join'] d.join + #? ['lower'] + x.lower #? dict() f() # wrong declarations -def f(a, b): +def f(a, b, x): """ :param a: Forgot type declaration :type a: :param b: Just something :type b: `` - :rtype: + :param x: Just something without type + :rtype: """ #? a #? b + #? + x #? f() diff --git a/test/test_evaluate/test_docstring.py b/test/test_evaluate/test_docstring.py index daa2d331..341e2c6d 100644 --- a/test/test_evaluate/test_docstring.py +++ b/test/test_evaluate/test_docstring.py @@ -6,6 +6,13 @@ from textwrap import dedent import jedi from ..helpers import unittest +try: + import numpydoc +except ImportError: + numpydoc_unavailable = True +else: + numpydoc_unavailable = False + class TestDocstring(unittest.TestCase): def test_function_doc(self): @@ -50,6 +57,16 @@ class TestDocstring(unittest.TestCase): names = [c.name for c in jedi.Script(s).completions()] assert 'start' in names + def test_docstrings_param_type(self): + s = """ + def func(arg): + ''' + :param str arg: some description + ''' + arg.""" + names = [c.name for c in jedi.Script(s).completions()] + assert 'join' in names + def test_docstrings_type_str(self): s = """ def func(arg): @@ -87,3 +104,49 @@ class TestDocstring(unittest.TestCase): assert 'a' in names assert '__init__' in names assert 'mro' not in names # Exists only for types. + + @unittest.skipIf(numpydoc_unavailable, 'numpydoc module is unavailable') + def test_numpydoc_docstring(self): + s = dedent(''' + def foobar(x, y): + """ + Parameters + ---------- + x : int + y : str + """ + y.''') + names = [c.name for c in jedi.Script(s).completions()] + assert 'isupper' in names + assert 'capitalize' in names + + @unittest.skipIf(numpydoc_unavailable, 'numpydoc module is unavailable') + def test_numpydoc_docstring_set_of_values(self): + s = dedent(''' + def foobar(x, y): + """ + Parameters + ---------- + x : {'foo', 'bar', 100500}, optional + """ + x.''') + names = [c.name for c in jedi.Script(s).completions()] + assert 'isupper' in names + assert 'capitalize' in names + assert 'numerator' in names + + @unittest.skipIf(numpydoc_unavailable, 'numpydoc module is unavailable') + def test_numpydoc_alternative_types(self): + s = dedent(''' + def foobar(x, y): + """ + Parameters + ---------- + x : int or str or list + """ + x.''') + names = [c.name for c in jedi.Script(s).completions()] + assert 'isupper' in names + assert 'capitalize' in names + assert 'numerator' in names + assert 'append' in names