diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 39f1c904e..d7b2d5ae6 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -30,6 +30,7 @@ from jedi.api.keywords import KeywordName from jedi.api.environment import InterpreterEnvironment from jedi.api.project import get_default_project, Project +from jedi.api.errors import parso_to_jedi_errors from jedi.inference import InferenceState from jedi.inference import imports from jedi.inference.references import find_references @@ -504,6 +505,9 @@ def get_names(self, **kwargs): """ return self._names(**kwargs) # Python 2... + def get_syntax_errors(self): + return parso_to_jedi_errors(self._grammar, self._module_node) + def _names(self, all_scopes=False, definitions=True, references=False): def def_ref_filter(_def): is_def = _def._name.tree_name.is_definition() diff --git a/jedi/api/errors.py b/jedi/api/errors.py new file mode 100644 index 000000000..e86f92127 --- /dev/null +++ b/jedi/api/errors.py @@ -0,0 +1,36 @@ +""" +This file is about errors in Python files and not about exception handling in +Jedi. +""" + + +def parso_to_jedi_errors(grammar, module_node): + return [SyntaxError(e) for e in grammar.iter_errors(module_node)] + + +class SyntaxError(object): + def __init__(self, parso_error): + self._parso_error = parso_error + + @property + def line(self): + return self._parso_error.start_pos[0] + + @property + def column(self): + return self._parso_error.start_pos[1] + + @property + def until_line(self): + return self._parso_error.end_pos[0] + + @property + def until_column(self): + return self._parso_error.end_pos[1] + + def __repr__(self): + return '<%s from=%s to=%s>' % ( + self.__class__.__name__, + self._parso_error.start_pos, + self._parso_error.end_pos, + ) diff --git a/test/test_api/test_syntax_errors.py b/test/test_api/test_syntax_errors.py new file mode 100644 index 000000000..01e99688b --- /dev/null +++ b/test/test_api/test_syntax_errors.py @@ -0,0 +1,54 @@ +""" +These tests test Jedi's Parso usage. Basically there's not a lot of tests here, +because we're just checking if the API works. Bugfixes should be done in parso, +mostly. +""" + +from textwrap import dedent + +import pytest + + +@pytest.mark.parametrize( + 'code, line, column, until_line, until_column', [ + ('?\n', 1, 0, 1, 1), + ('x %% y', 1, 3, 1, 4), + ('"""\n\n', 1, 0, 3, 0), + ('(1, 2\n', 2, 0, 2, 0), + ('foo(1, 2\ndef x(): pass', 2, 0, 2, 3), + ] +) +def test_simple_syntax_errors(Script, code, line, column, until_line, until_column): + e, = Script(code).get_syntax_errors() + assert e.line == line + assert e.column == column + assert e.until_line == until_line + assert e.until_column == until_column + + +@pytest.mark.parametrize( + 'code', [ + 'x % y', + 'def x(x): pass', + 'def x(x):\n pass', + ] +) +def test_no_syntax_errors(Script, code): + assert not Script(code).get_syntax_errors() + + +def test_multi_syntax_error(Script): + code = dedent('''\ + def x(): + 1 + def y() + 1 + 1 + 1 *** 3 + ''') + x, y, power = Script(code).get_syntax_errors() + assert x.line == 2 + assert x.column == 0 + assert y.line == 3 + assert y.column == 7 + assert power.line == 5 + assert power.column == 4