示例#1
0
def test_equals(source):
    evaluator = Evaluator(load_grammar())
    node = Parser(load_grammar(), source, 'eval_input').get_parsed_node()
    results = evaluator.eval_element(node)
    assert len(results) == 1
    first = results.pop()
    assert isinstance(first, CompiledObject) and first.obj is True
示例#2
0
def test_equals(source):
    evaluator = Evaluator(load_grammar())
    node = Parser(load_grammar(), source, 'eval_input').get_parsed_node()
    results = evaluator.eval_element(node)
    assert len(results) == 1
    first = results.pop()
    assert isinstance(first, CompiledObject) and first.obj is True
示例#3
0
def test_simple():
    e = Evaluator()
    bltn = compiled.CompiledObject(builtins)
    obj = compiled.CompiledObject('_str_', bltn)
    upper = e.find_types(obj, 'upper')
    assert len(upper) == 1
    objs = list(e.execute(upper[0]))
    assert len(objs) == 1
    assert objs[0].obj is str
示例#4
0
文件: test_compiled.py 项目: ABob/vim
def test_simple():
    e = Evaluator(load_grammar())
    bltn = compiled.CompiledObject(builtins)
    obj = compiled.CompiledObject('_str_', bltn)
    upper = e.find_types(obj, 'upper')
    assert len(upper) == 1
    objs = list(e.execute(upper[0]))
    assert len(objs) == 1
    assert isinstance(objs[0], representation.Instance)
示例#5
0
def test_simple():
    e = Evaluator(load_grammar())
    bltn = compiled.CompiledObject(builtins)
    obj = compiled.CompiledObject('_str_', bltn)
    upper = e.find_types(obj, 'upper')
    assert len(upper) == 1
    objs = list(e.execute(upper[0]))
    assert len(objs) == 1
    assert isinstance(objs[0], representation.Instance)
示例#6
0
def test_simple():
    e = Evaluator()
    bltn = compiled.CompiledObject(builtins)
    obj = compiled.CompiledObject('_str_', bltn)
    upper = e.find_types(obj, 'upper')
    assert len(upper) == 1
    objs = list(e.execute(upper[0]))
    assert len(objs) == 1
    assert objs[0].obj is str
示例#7
0
    def __init__(self,
                 source=None,
                 line=None,
                 column=None,
                 path=None,
                 encoding='utf-8',
                 sys_path=None,
                 environment=None):
        self._orig_path = path
        # An empty path (also empty string) should always result in no path.
        self.path = os.path.abspath(path) if path else None

        if source is None:
            # TODO add a better warning than the traceback!
            with open(path, 'rb') as f:
                source = f.read()

        # Load the Python grammar of the current interpreter.
        self._grammar = parso.load_grammar()

        if sys_path is not None and not is_py3:
            sys_path = list(map(force_unicode, sys_path))

        # Load the Python grammar of the current interpreter.
        project = get_default_project(self.path or os.getcwd())
        # TODO deprecate and remove sys_path from the Script API.
        if sys_path is not None:
            project._sys_path = sys_path
        self._evaluator = Evaluator(project,
                                    environment=environment,
                                    script_path=self.path)
        self._project = project
        debug.speed('init')
        self._module_node, source = self._evaluator.parse_and_get_code(
            code=source,
            path=self.path,
            cache=
            False,  # No disk cache, because the current script often changes.
            diff_cache=True,
            cache_path=settings.cache_directory)
        debug.speed('parsed')
        self._code_lines = parso.split_lines(source)
        line = max(len(self._code_lines), 1) if line is None else line
        if not (0 < line <= len(self._code_lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(self._code_lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column
        self._path = path

        cache.clear_time_caches()
        debug.reset_time()
示例#8
0
    def __init__(self,
                 source=None,
                 line=None,
                 column=None,
                 path=None,
                 encoding='utf-8',
                 source_path=None,
                 source_encoding=None,
                 sys_path=None):
        if source_path is not None:
            warnings.warn(
                "Deprecated since version 0.7. Use path instead of source_path.",
                DeprecationWarning,
                stacklevel=2)
            path = source_path
        if source_encoding is not None:
            warnings.warn(
                "Deprecated since version 0.8. Use encoding instead of source_encoding.",
                DeprecationWarning,
                stacklevel=2)
            encoding = source_encoding

        self._orig_path = path
        # An empty path (also empty string) should always result in no path.
        self.path = os.path.abspath(path) if path else None

        if source is None:
            # TODO add a better warning than the traceback!
            with open(path, 'rb') as f:
                source = f.read()

        self._source = common.source_to_unicode(source, encoding)
        self._code_lines = common.splitlines(self._source)
        line = max(len(self._code_lines), 1) if line is None else line
        if not (0 < line <= len(self._code_lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(self._code_lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column
        self._path = path

        cache.clear_time_caches()
        debug.reset_time()
        self._grammar = load_grammar(version='%s.%s' % sys.version_info[:2])
        if sys_path is None:
            venv = os.getenv('VIRTUAL_ENV')
            if venv:
                sys_path = list(get_venv_path(venv))
        self._evaluator = Evaluator(self._grammar, sys_path=sys_path)
        debug.speed('init')
示例#9
0
    def __init__(self,
                 source=None,
                 line=None,
                 column=None,
                 path=None,
                 encoding='utf-8',
                 source_path=None,
                 source_encoding=None,
                 sys_path=None):
        if source_path is not None:
            warnings.warn("Use path instead of source_path.",
                          DeprecationWarning)
            path = source_path
        if source_encoding is not None:
            warnings.warn("Use encoding instead of source_encoding.",
                          DeprecationWarning)
            encoding = source_encoding

        self._orig_path = path
        self.path = None if path is None else os.path.abspath(path)

        if source is None:
            with open(path) as f:
                source = f.read()

        self.source = common.source_to_unicode(source, encoding)
        lines = common.splitlines(self.source)
        line = max(len(lines), 1) if line is None else line
        if not (0 < line <= len(lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column

        cache.clear_time_caches()
        debug.reset_time()
        self._grammar = load_grammar('grammar%s.%s' % sys.version_info[:2])
        self._user_context = UserContext(self.source, self._pos)
        self._parser = UserContextParser(self._grammar, self.source, path,
                                         self._pos, self._user_context,
                                         self._parsed_callback)
        if sys_path is None:
            venv = os.getenv('VIRTUAL_ENV')
            if venv:
                sys_path = list(get_venv_path(venv))
        self._evaluator = Evaluator(self._grammar, sys_path=sys_path)
        debug.speed('init')
示例#10
0
def test_keyword_full_name_should_be_none():
    """issue #94"""
    # Using `from jedi.keywords import Keyword` here does NOT work
    # in Python 3.  This is due to the import hack jedi using.
    Keyword = classes.keywords.Keyword
    d = classes.Definition(Evaluator(), Keyword('(', (0, 0)))
    assert d.full_name is None
示例#11
0
def test_fake_loading():
    assert isinstance(compiled.create(Evaluator(), next), Function)

    string = compiled.builtin.get_subscope_by_name('str')
    from_name = compiled._create_from_name(compiled.builtin, string,
                                           '__init__')
    assert isinstance(from_name, Function)
示例#12
0
    def __init__(self, source=None, line=None, column=None, path=None,
                 encoding='utf-8', sys_path=None, environment=None):
        self._orig_path = path
        # An empty path (also empty string) should always result in no path.
        self.path = os.path.abspath(path) if path else None

        if source is None:
            # TODO add a better warning than the traceback!
            with open(path, 'rb') as f:
                source = f.read()

        # Load the Python grammar of the current interpreter.
        self._grammar = parso.load_grammar()

        if sys_path is not None and not is_py3:
            sys_path = list(map(force_unicode, sys_path))

        # Load the Python grammar of the current interpreter.
        project = get_default_project(
            os.path.dirname(self.path)if path else os.getcwd()
        )
        # TODO deprecate and remove sys_path from the Script API.
        if sys_path is not None:
            project._sys_path = sys_path
        self._evaluator = Evaluator(
            project, environment=environment, script_path=self.path
        )
        debug.speed('init')
        self._module_node, source = self._evaluator.parse_and_get_code(
            code=source,
            path=self.path,
            encoding=encoding,
            cache=False,  # No disk cache, because the current script often changes.
            diff_cache=settings.fast_parser,
            cache_path=settings.cache_directory,
        )
        debug.speed('parsed')
        self._code_lines = parso.split_lines(source, keepends=True)
        self._code = source
        line = max(len(self._code_lines), 1) if line is None else line
        if not (0 < line <= len(self._code_lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_string = self._code_lines[line - 1]
        line_len = len(line_string)
        if line_string.endswith('\r\n'):
            line_len -= 1
        if line_string.endswith('\n'):
            line_len -= 1

        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter (%d) is not in a valid range '
                             '(0-%d) for line %d (%r).' % (
                                 column, line_len, line, line_string))
        self._pos = line, column
        self._path = path

        cache.clear_time_caches()
        debug.reset_time()
示例#13
0
    def __init__(self, source=None, line=None, column=None, path=None,
                 encoding='utf-8', sys_path=None):
        self._orig_path = path
        # An empty path (also empty string) should always result in no path.
        self.path = os.path.abspath(path) if path else None

        if source is None:
            # TODO add a better warning than the traceback!
            with open(path, 'rb') as f:
                source = f.read()

        # TODO do we really want that?
        self._source = python_bytes_to_unicode(source, encoding, errors='replace')
        self._code_lines = split_lines(self._source)
        line = max(len(self._code_lines), 1) if line is None else line
        if not (0 < line <= len(self._code_lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(self._code_lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column
        self._path = path

        cache.clear_time_caches()
        debug.reset_time()

        # Load the Python grammar of the current interpreter.
        self._grammar = parso.load_grammar()
        project = Project(sys_path=sys_path)
        self._evaluator = Evaluator(self._grammar, project)
        project.add_script_path(self.path)
        debug.speed('init')
示例#14
0
    def __init__(self, source=None, line=None, column=None, path=None,
                 encoding='utf-8', source_path=None):
        if source_path is not None:
            warnings.warn("Use path instead of source_path.", DeprecationWarning)
            path = source_path
        self._source_path = path
        self.path = None if path is None else os.path.abspath(path)

        if source is None:
            with open(path) as f:
                source = f.read()

        lines = source.splitlines() or ['']
        if source and source[-1] == '\n':
            lines.append('')
        line = max(len(lines), 1) if line is None else line
        if not (0 < line <= len(lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column

        cache.clear_caches()
        debug.reset_time()
        self.source = common.source_to_unicode(source, encoding)
        self._user_context = UserContext(self.source, self._pos)
        self._parser = UserContextParser(self.source, path, self._pos, self._user_context)
        self._evaluator = Evaluator()
        debug.speed('init')
示例#15
0
    def __init__(self,
                 source=None,
                 line=None,
                 column=None,
                 path=None,
                 encoding='utf-8',
                 source_path=None,
                 source_encoding=None):
        if source_path is not None:
            warnings.warn("Use path instead of source_path.",
                          DeprecationWarning)
            path = source_path
        if source_encoding is not None:
            warnings.warn("Use encoding instead of source_encoding.",
                          DeprecationWarning)
            encoding = source_encoding

        self._orig_path = path
        self.path = None if path is None else os.path.abspath(path)

        if source is None:
            with open(path) as f:
                source = f.read()

        lines = source.splitlines() or ['']
        if source and source[-1] == '\n':
            lines.append('')
        line = max(len(lines), 1) if line is None else line
        if not (0 < line <= len(lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column

        cache.clear_caches()
        debug.reset_time()
        self.source = common.source_to_unicode(source, encoding)
        self._user_context = UserContext(self.source, self._pos)
        self._parser = UserContextParser(self.source, path, self._pos,
                                         self._user_context)
        self._evaluator = Evaluator()
        debug.speed('init')
示例#16
0
def test_path_from_invalid_sys_path_assignment():
    SRC = dedent(u("""
        import sys
        sys.path = 'invalid'"""))
    grammar = load_grammar()
    p = ParserWithRecovery(grammar, SRC)
    paths = _check_module(Evaluator(grammar), p.module)
    assert len(paths) > 0
    assert 'invalid' not in paths
示例#17
0
def test_sys_path_with_modifications():
    SRC = dedent(u("""
        import os
    """))
    grammar = load_grammar()
    p = ParserWithRecovery(grammar, SRC)
    p.module.path = os.path.abspath(os.path.join(os.curdir, 'module_name.py'))
    paths = sys_path_with_modifications(Evaluator(grammar), p.module)
    assert '/tmp/.buildout/eggs/important_package.egg' in paths
示例#18
0
文件: __init__.py 项目: Shieh/jedi
    def __init__(self,
                 source=None,
                 line=None,
                 column=None,
                 path=None,
                 encoding='utf-8',
                 sys_path=None):
        self._orig_path = path
        # An empty path (also empty string) should always result in no path.
        self.path = os.path.abspath(path) if path else None

        if source is None:
            # TODO add a better warning than the traceback!
            with open(path, 'rb') as f:
                source = f.read()

        # Load the Python grammar of the current interpreter.
        self._grammar = parso.load_grammar()
        project = Project(sys_path=sys_path)
        self._evaluator = Evaluator(self._grammar, project)
        self._module_node, source = self._evaluator.parse_and_get_code(
            code=source,
            path=self.path,
            cache=
            False,  # No disk cache, because the current script often changes.
            diff_cache=True,
            cache_path=settings.cache_directory)
        self._code_lines = split_lines(source)
        line = max(len(self._code_lines), 1) if line is None else line
        if not (0 < line <= len(self._code_lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(self._code_lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column
        self._path = path

        cache.clear_time_caches()
        debug.reset_time()

        project.add_script_path(self.path)
        debug.speed('init')
    def __init__(self,
                 source=None,
                 line=None,
                 column=None,
                 path=None,
                 encoding='utf-8',
                 sys_path=None):
        self._orig_path = path
        # An empty path (also empty string) should always result in no path.
        self.path = os.path.abspath(path) if path else None

        if source is None:
            # TODO add a better warning than the traceback!
            with open(path, 'rb') as f:
                source = f.read()

        # TODO do we really want that?
        self._source = python_bytes_to_unicode(source,
                                               encoding,
                                               errors='replace')
        self._code_lines = split_lines(self._source)
        line = max(len(self._code_lines), 1) if line is None else line
        if not (0 < line <= len(self._code_lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(self._code_lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column
        self._path = path

        cache.clear_time_caches()
        debug.reset_time()

        # Load the Python grammar of the current interpreter.
        self._grammar = parso.load_grammar()
        if sys_path is None:
            venv = os.getenv('VIRTUAL_ENV')
            if venv:
                sys_path = list(get_venv_path(venv))
        self._evaluator = Evaluator(self._grammar, sys_path=sys_path)
        debug.speed('init')
示例#20
0
def test_sys_path_with_modifications():
    code = dedent("""
        import os
    """)

    path = os.path.abspath(os.path.join(os.curdir, 'module_name.py'))
    grammar = load_grammar()
    module_node = parse(code, path=path)
    module_context = ModuleContext(Evaluator(grammar), module_node, path=path)
    paths = sys_path_with_modifications(module_context.evaluator,
                                        module_context)
    assert '/tmp/.buildout/eggs/important_package.egg' in paths
示例#21
0
    def __init__(self,
                 source=None,
                 line=None,
                 column=None,
                 path=None,
                 encoding='utf-8',
                 source_path=None,
                 source_encoding=None,
                 sys_path=None):
        if source_path is not None:
            warnings.warn("Use path instead of source_path.",
                          DeprecationWarning)
            path = source_path
        if source_encoding is not None:
            warnings.warn("Use encoding instead of source_encoding.",
                          DeprecationWarning)
            encoding = source_encoding

        self._orig_path = path
        # An empty path (also empty string) should always result in no path.
        self.path = os.path.abspath(path) if path else None

        if source is None:
            # TODO add a better warning than the traceback!
            try:
                with open(path) as f:
                    source = f.read()
            except UnicodeDecodeError:
                with open(path, encoding=encoding) as f:
                    source = f.read()

        self._source = common.source_to_unicode(source, encoding)
        self._code_lines = common.splitlines(self._source)
        line = max(len(self._code_lines), 1) if line is None else line
        if not (0 < line <= len(self._code_lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(self._code_lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column
        self._path = path

        cache.clear_time_caches()
        debug.reset_time()
        self._grammar = load_grammar(version='%s.%s' % sys.version_info[:2])
        if sys_path is None:
            venv = os.getenv('VIRTUAL_ENV')
            if venv:
                sys_path = list(get_venv_path(venv))
        self._evaluator = Evaluator(self._grammar, sys_path=sys_path)
        debug.speed('init')
示例#22
0
def test_append_on_non_sys_path():
    SRC = dedent(
        u("""
        class Dummy(object):
            path = []

        d = Dummy()
        d.path.append('foo')"""))
    grammar = load_grammar()
    p = ParserWithRecovery(grammar, SRC)
    paths = _check_module(Evaluator(grammar), p.module)
    assert len(paths) > 0
    assert 'foo' not in paths
示例#23
0
文件: __init__.py 项目: syntonym/jedi
    def _get_evaluator(self, function, evaluator_id):
        from jedi.evaluate import Evaluator

        try:
            evaluator = self._evaluators[evaluator_id]
        except KeyError:
            from jedi.api.environment import InterpreterEnvironment
            evaluator = Evaluator(
                # The project is not actually needed. Nothing should need to
                # access it.
                project=None,
                environment=InterpreterEnvironment())
            self._evaluators[evaluator_id] = evaluator
        return evaluator
示例#24
0
def defined_names(source, path=None, encoding='utf-8'):
    """
    Get all definitions in `source` sorted by its position.

    This functions can be used for listing functions, classes and
    data defined in a file.  This can be useful if you want to list
    them in "sidebar".  Each element in the returned list also has
    `defined_names` method which can be used to get sub-definitions
    (e.g., methods in class).

    :rtype: list of classes.Definition
    """
    parser = Parser(
        common.source_to_unicode(source, encoding),
        module_path=path,
    )
    return classes.defined_names(Evaluator(), parser.module)
示例#25
0
    def __init__(self, source=None, line=None, column=None, path=None,
                 encoding='utf-8', source_path=None, source_encoding=None,
                 sys_path=None):
        if source_path is not None:
            warnings.warn("Use path instead of source_path.", DeprecationWarning)
            path = source_path
        if source_encoding is not None:
            warnings.warn("Use encoding instead of source_encoding.", DeprecationWarning)
            encoding = source_encoding

        self._orig_path = path
        self.path = None if path is None else os.path.abspath(path)

        if source is None:
            with open(path) as f:
                source = f.read()

        self.source = common.source_to_unicode(source, encoding)
        lines = common.splitlines(self.source)
        line = max(len(lines), 1) if line is None else line
        if not (0 < line <= len(lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column

        cache.clear_time_caches()
        debug.reset_time()
        self._grammar = load_grammar('grammar%s.%s' % sys.version_info[:2])
        self._user_context = UserContext(self.source, self._pos)
        self._parser = UserContextParser(self._grammar, self.source, path,
                                         self._pos, self._user_context,
                                         self._parsed_callback)
        if sys_path is None:
            venv = os.getenv('VIRTUAL_ENV')
            if venv:
                sys_path = list(get_venv_path(venv))
        self._evaluator = Evaluator(self._grammar, sys_path=sys_path)
        debug.speed('init')
示例#26
0
def test_path_from_sys_path_assignment():
    SRC = dedent(
        u("""
        #!/usr/bin/python

        import sys
        sys.path[0:0] = [
          '/usr/lib/python3.4/site-packages',
          '/home/test/.buildout/eggs/important_package.egg'
          ]

        path[0:0] = [1]

        import important_package

        if __name__ == '__main__':
            sys.exit(important_package.main())"""))
    grammar = load_grammar()
    p = ParserWithRecovery(grammar, SRC)
    paths = _check_module(Evaluator(grammar), p.module)
    assert 1 not in paths
    assert '/home/test/.buildout/eggs/important_package.egg' in paths
示例#27
0
文件: __init__.py 项目: beyang/jedi
    def __init__(self, source=None, line=None, column=None, path=None,
                 encoding='utf-8', source_path=None, source_encoding=None,
                 resolve_variables_to_types=True):
        if source_path is not None:
            warnings.warn("Use path instead of source_path.", DeprecationWarning)
            path = source_path
        if source_encoding is not None:
            warnings.warn("Use encoding instead of source_encoding.", DeprecationWarning)
            encoding = source_encoding

        self._orig_path = path
        self.path = None if path is None else os.path.abspath(path)

        if source is None:
            with open(path) as f:
                source = f.read()

        self.source = common.source_to_unicode(source, encoding)
        lines = common.splitlines(self.source)
        line = max(len(lines), 1) if line is None else line
        if not (0 < line <= len(lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column

        if not cache.never_clear_cache:
            cache.clear_caches()

        debug.reset_time()
        self._user_context = UserContext(self.source, self._pos)
        self._parser = UserContextParser(self.source, path, self._pos, self._user_context)
        self._evaluator = Evaluator(resolve_variables_to_types=resolve_variables_to_types)
        debug.speed('init')
示例#28
0
class Script(object):
    """
    A Script is the base for completions, goto or whatever you want to do with
    |jedi|.

    You can either use the ``source`` parameter or ``path`` to read a file.
    Usually you're going to want to use both of them (in an editor).

    :param source: The source code of the current file, separated by newlines.
    :type source: str
    :param line: The line to perform actions on (starting with 1).
    :type line: int
    :param col: The column of the cursor (starting with 0).
    :type col: int
    :param path: The path of the file in the file system, or ``''`` if
        it hasn't been saved yet.
    :type path: str or None
    :param encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    :param source_encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    """
    def __init__(self,
                 source=None,
                 line=None,
                 column=None,
                 path=None,
                 encoding='utf-8',
                 source_path=None,
                 source_encoding=None):
        if source_path is not None:
            warnings.warn("Use path instead of source_path.",
                          DeprecationWarning)
            path = source_path
        if source_encoding is not None:
            warnings.warn("Use encoding instead of source_encoding.",
                          DeprecationWarning)
            encoding = source_encoding

        self._orig_path = path
        self.path = None if path is None else os.path.abspath(path)

        if source is None:
            with open(path) as f:
                source = f.read()

        lines = source.splitlines() or ['']
        if source and source[-1] == '\n':
            lines.append('')
        line = max(len(lines), 1) if line is None else line
        if not (0 < line <= len(lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column

        cache.clear_caches()
        debug.reset_time()
        self.source = common.source_to_unicode(source, encoding)
        self._user_context = UserContext(self.source, self._pos)
        self._parser = UserContextParser(self.source, path, self._pos,
                                         self._user_context)
        self._evaluator = Evaluator()
        debug.speed('init')

    @property
    def source_path(self):
        """
        .. deprecated:: 0.7.0
           Use :attr:`.path` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use path instead of source_path.", DeprecationWarning)
        return self.path

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, repr(self._orig_path))

    def completions(self):
        """
        Return :class:`classes.Completion` objects. Those objects contain
        information about the completions, more than just names.

        :return: Completion objects, sorted by name and __ comes last.
        :rtype: list of :class:`classes.Completion`
        """
        def get_completions(user_stmt, bs):
            if isinstance(user_stmt, pr.Import):
                context = self._user_context.get_context()
                next(context)  # skip the path
                if next(context) == 'from':
                    # completion is just "import" if before stands from ..
                    return ((k, bs) for k in keywords.keyword_names('import'))
            return self._simple_complete(path, like)

        def completion_possible(path):
            """
            The completion logic is kind of complicated, because we strip the
            last word part. To ignore certain strange patterns with dots, just
            use regex.
            """
            if re.match('\d+\.\.$|\.{4}$', path):
                return True  # check Ellipsis and float literal `1.`

            return not re.search(r'^\.|^\d\.$|\.\.$', path)

        debug.speed('completions start')
        path = self._user_context.get_path_until_cursor()
        if not completion_possible(path):
            return []
        path, dot, like = helpers.completion_parts(path)

        user_stmt = self._parser.user_stmt_with_whitespace()
        b = compiled.builtin
        completions = get_completions(user_stmt, b)

        if not dot:
            # add named params
            for call_sig in self.call_signatures():
                # allow protected access, because it's a public API.
                module = call_sig._definition.get_parent_until()
                # Compiled modules typically don't allow keyword arguments.
                if not isinstance(module, compiled.CompiledObject):
                    for p in call_sig.params:
                        # Allow access on _definition here, because it's a
                        # public API and we don't want to make the internal
                        # Name object public.
                        if p._definition.stars == 0:  # no *args/**kwargs
                            completions.append((p._definition.get_name(), p))

            if not path and not isinstance(user_stmt, pr.Import):
                # add keywords
                completions += ((k, b)
                                for k in keywords.keyword_names(all=True))

        needs_dot = not dot and path

        comps = []
        comp_dct = {}
        for c, s in set(completions):
            n = str(c.names[-1])
            if settings.case_insensitive_completion \
                    and n.lower().startswith(like.lower()) \
                    or n.startswith(like):
                if not filter_private_variable(
                        s, user_stmt or self._parser.user_scope(), n):
                    new = classes.Completion(self._evaluator, c, needs_dot,
                                             len(like), s)
                    k = (new.name, new.complete)  # key
                    if k in comp_dct and settings.no_completion_duplicates:
                        comp_dct[k]._same_name_completions.append(new)
                    else:
                        comp_dct[k] = new
                        comps.append(new)

        debug.speed('completions end')

        return sorted(
            comps,
            key=lambda x:
            (x.name.startswith('__'), x.name.startswith('_'), x.name.lower()))

    def _simple_complete(self, path, like):
        try:
            scopes = list(self._prepare_goto(path, True))
        except NotFoundError:
            scopes = []
            scope_generator = get_names_of_scope(self._evaluator,
                                                 self._parser.user_scope(),
                                                 self._pos)
            completions = []
            for scope, name_list in scope_generator:
                for c in name_list:
                    completions.append((c, scope))
        else:
            completions = []
            debug.dbg('possible completion scopes: %s', scopes)
            for s in scopes:
                if s.isinstance(er.Function):
                    names = s.get_magic_function_names()
                else:
                    if isinstance(s, imports.ImportWrapper):
                        under = like + self._user_context.get_path_after_cursor(
                        )
                        if under == 'import':
                            current_line = self._user_context.get_position_line(
                            )
                            if not current_line.endswith('import import'):
                                continue
                        a = s.import_stmt.alias
                        if a and a.start_pos <= self._pos <= a.end_pos:
                            continue
                        names = s.get_defined_names(on_import_stmt=True)
                    else:
                        names = s.get_defined_names()

                for c in names:
                    completions.append((c, s))
        return completions

    def _prepare_goto(self, goto_path, is_completion=False):
        """
        Base for completions/goto. Basically it returns the resolved scopes
        under cursor.
        """
        debug.dbg('start: %s in %s', goto_path, self._parser.user_scope())

        user_stmt = self._parser.user_stmt_with_whitespace()
        if not user_stmt and len(goto_path.split('\n')) > 1:
            # If the user_stmt is not defined and the goto_path is multi line,
            # something's strange. Most probably the backwards tokenizer
            # matched to much.
            return []

        if isinstance(user_stmt, pr.Import):
            scopes = [
                helpers.get_on_import_stmt(self._evaluator, self._user_context,
                                           user_stmt, is_completion)[0]
            ]
        else:
            # just parse one statement, take it and evaluate it
            eval_stmt = self._get_under_cursor_stmt(goto_path)

            if not is_completion:
                # goto_definition returns definitions of its statements if the
                # cursor is on the assignee. By changing the start_pos of our
                # "pseud" statement, the Jedi evaluator can find the assignees.
                if user_stmt is not None:
                    eval_stmt.start_pos = user_stmt.end_pos
            scopes = self._evaluator.eval_statement(eval_stmt)
        return scopes

    def _get_under_cursor_stmt(self, cursor_txt):
        tokenizer = source_tokens(cursor_txt, line_offset=self._pos[0] - 1)
        r = Parser(cursor_txt, no_docstr=True, tokenizer=tokenizer)
        try:
            # Take the last statement available.
            stmt = r.module.statements[-1]
        except IndexError:
            raise NotFoundError()
        if not isinstance(stmt, pr.Statement):
            raise NotFoundError()

        user_stmt = self._parser.user_stmt()
        if user_stmt is None:
            # Set the start_pos to a pseudo position, that doesn't exist but works
            # perfectly well (for both completions in docstrings and statements).
            stmt.start_pos = self._pos
        else:
            stmt.start_pos = user_stmt.start_pos
        stmt.parent = self._parser.user_scope()
        return stmt

    def complete(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.completions` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use completions instead.", DeprecationWarning)
        return self.completions()

    def goto(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.goto_assignments` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use goto_assignments instead.", DeprecationWarning)
        return self.goto_assignments()

    def definition(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.goto_definitions` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use goto_definitions instead.", DeprecationWarning)
        return self.goto_definitions()

    def get_definition(self):
        """
        .. deprecated:: 0.5.0
           Use :attr:`.goto_definitions` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use goto_definitions instead.", DeprecationWarning)
        return self.goto_definitions()

    def related_names(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.usages` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use usages instead.", DeprecationWarning)
        return self.usages()

    def get_in_function_call(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.call_signatures` instead.
        .. todo:: Remove!
        """
        return self.function_definition()

    def function_definition(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.call_signatures` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use line instead.", DeprecationWarning)
        sig = self.call_signatures()
        return sig[0] if sig else None

    def goto_definitions(self):
        """
        Return the definitions of a the path under the cursor.  goto function!
        This follows complicated paths and returns the end, not the first
        definition. The big difference between :meth:`goto_assignments` and
        :meth:`goto_definitions` is that :meth:`goto_assignments` doesn't
        follow imports and statements. Multiple objects may be returned,
        because Python itself is a dynamic language, which means depending on
        an option you can have two different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        def resolve_import_paths(scopes):
            for s in scopes.copy():
                if isinstance(s, imports.ImportWrapper):
                    scopes.remove(s)
                    scopes.update(resolve_import_paths(set(s.follow())))
            return scopes

        user_stmt = self._parser.user_stmt_with_whitespace()
        goto_path = self._user_context.get_path_under_cursor()
        context = self._user_context.get_context()
        definitions = set()
        if next(context) in ('class', 'def'):
            definitions = set([self._parser.user_scope()])
        else:
            # Fetch definition of callee, if there's no path otherwise.
            if not goto_path:
                (call, _) = search_call_signatures(user_stmt, self._pos)
                if call is not None:
                    while call.next is not None:
                        call = call.next
                    # reset cursor position:
                    (row, col) = call.name.end_pos
                    pos = (row, max(col - 1, 0))
                    self._user_context = UserContext(self.source, pos)
                    # then try to find the path again
                    goto_path = self._user_context.get_path_under_cursor()

        if not definitions:
            if goto_path:
                definitions = set(self._prepare_goto(goto_path))

        definitions = resolve_import_paths(definitions)
        d = set([
            classes.Definition(self._evaluator, s) for s in definitions
            if s is not imports.ImportWrapper.GlobalNamespace
        ])
        return helpers.sorted_definitions(d)

    def goto_assignments(self):
        """
        Return the first definition found. Imports and statements aren't
        followed. Multiple objects may be returned, because Python itself is a
        dynamic language, which means depending on an option you can have two
        different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        results, _ = self._goto()
        d = [
            classes.Definition(self._evaluator, d) for d in set(results)
            if d is not imports.ImportWrapper.GlobalNamespace
        ]
        return helpers.sorted_definitions(d)

    def _goto(self, add_import_name=False):
        """
        Used for goto_assignments and usages.

        :param add_import_name: Add the the name (if import) to the result.
        """
        def follow_inexistent_imports(defs):
            """ Imports can be generated, e.g. following
            `multiprocessing.dummy` generates an import dummy in the
            multiprocessing module. The Import doesn't exist -> follow.
            """
            definitions = set(defs)
            for d in defs:
                if isinstance(d.parent, pr.Import) \
                        and d.start_pos == (0, 0):
                    i = imports.ImportWrapper(self._evaluator,
                                              d.parent).follow(is_goto=True)
                    definitions.remove(d)
                    definitions |= follow_inexistent_imports(i)
            return definitions

        goto_path = self._user_context.get_path_under_cursor()
        context = self._user_context.get_context()
        user_stmt = self._parser.user_stmt()
        if next(context) in ('class', 'def'):
            user_scope = self._parser.user_scope()
            definitions = set([user_scope.name])
            search_name = unicode(user_scope.name)
        elif isinstance(user_stmt, pr.Import):
            s, name_part = helpers.get_on_import_stmt(self._evaluator,
                                                      self._user_context,
                                                      user_stmt)
            try:
                definitions = [s.follow(is_goto=True)[0]]
            except IndexError:
                definitions = []
            search_name = unicode(name_part)

            if add_import_name:
                import_name = user_stmt.get_defined_names()
                # imports have only one name
                if not user_stmt.star \
                        and unicode(name_part) == unicode(import_name[0].names[-1]):
                    definitions.append(import_name[0])
        else:
            stmt = self._get_under_cursor_stmt(goto_path)

            def test_lhs():
                """
                Special rule for goto, left hand side of the statement returns
                itself, if the name is ``foo``, but not ``foo.bar``.
                """
                if isinstance(user_stmt, pr.Statement):
                    for name in user_stmt.get_defined_names():
                        if name.start_pos <= self._pos <= name.end_pos \
                                and len(name.names) == 1:
                            return name, unicode(name.names[-1])
                return None, None

            lhs, search_name = test_lhs()
            if lhs is None:
                expression_list = stmt.expression_list()
                if len(expression_list) == 0:
                    return [], ''
                # Only the first command is important, the rest should basically not
                # happen except in broken code (e.g. docstrings that aren't code).
                call = expression_list[0]
                if isinstance(call, pr.Call):
                    call_path = list(call.generate_call_path())
                else:
                    call_path = [call]

                defs, search_name_part = self._evaluator.goto(stmt, call_path)
                search_name = unicode(search_name_part)
                definitions = follow_inexistent_imports(defs)
            else:
                definitions = [lhs]
            if isinstance(user_stmt, pr.Statement):
                c = user_stmt.expression_list()
                if c and not isinstance(c[0], (str, unicode)) \
                        and c[0].start_pos > self._pos \
                        and not re.search(r'\.\w+$', goto_path):
                    # The cursor must be after the start, otherwise the
                    # statement is just an assignee.
                    definitions = [user_stmt]
        return definitions, search_name

    def usages(self, additional_module_paths=()):
        """
        Return :class:`classes.Definition` objects, which contain all
        names that point to the definition of the name under the cursor. This
        is very useful for refactoring (renaming), or to show all usages of a
        variable.

        .. todo:: Implement additional_module_paths

        :rtype: list of :class:`classes.Definition`
        """
        temp, settings.dynamic_flow_information = \
            settings.dynamic_flow_information, False
        user_stmt = self._parser.user_stmt()
        definitions, search_name = self._goto(add_import_name=True)
        if isinstance(user_stmt, pr.Statement):
            c = user_stmt.expression_list()[0]
            if not isinstance(c, unicode) and self._pos < c.start_pos:
                # the search_name might be before `=`
                definitions = [
                    v for v in user_stmt.get_defined_names()
                    if unicode(v.names[-1]) == search_name
                ]
        if not isinstance(user_stmt, pr.Import):
            # import case is looked at with add_import_name option
            definitions = usages.usages_add_import_modules(
                self._evaluator, definitions, search_name)

        module = set([d.get_parent_until() for d in definitions])
        module.add(self._parser.module())
        names = usages.usages(self._evaluator, definitions, search_name,
                              module)

        for d in set(definitions):
            try:
                name_part = d.names[-1]
            except AttributeError:
                names.append(classes.Definition(self._evaluator, d))
            else:
                names.append(classes.Definition(self._evaluator, name_part))

        settings.dynamic_flow_information = temp
        return helpers.sorted_definitions(set(names))

    def call_signatures(self):
        """
        Return the function object of the call you're currently in.

        E.g. if the cursor is here::

            abs(# <-- cursor is here

        This would return the ``abs`` function. On the other hand::

            abs()# <-- cursor is here

        This would return ``None``.

        :rtype: list of :class:`classes.CallSignature`
        """
        user_stmt = self._parser.user_stmt_with_whitespace()
        call, index = search_call_signatures(user_stmt, self._pos)
        if call is None:
            return []

        stmt_el = call
        while isinstance(stmt_el.parent, pr.StatementElement):
            # Go to parent literal/variable until not possible anymore. This
            # makes it possible to return the whole expression.
            stmt_el = stmt_el.parent
        # We can reset the execution since it's a new object
        # (fast_parent_copy).
        execution_arr, call.execution = call.execution, None

        with common.scale_speed_settings(settings.scale_call_signatures):
            _callable = lambda: self._evaluator.eval_call(stmt_el)
            origins = cache.cache_call_signatures(_callable, self.source,
                                                  self._pos, user_stmt)
        debug.speed('func_call followed')

        key_name = None
        try:
            detail = execution_arr[index].assignment_details[0]
        except IndexError:
            pass
        else:
            try:
                key_name = unicode(detail[0][0].name)
            except (IndexError, AttributeError):
                pass
        return [
            classes.CallSignature(self._evaluator, o, call, index, key_name)
            for o in origins if o.is_callable()
        ]
示例#29
0
def check_module_test(code):
    grammar = load_grammar()
    p = ParserWithRecovery(grammar, code)
    module_context = ModuleContext(Evaluator(grammar), p.module)
    return _check_module(module_context)
示例#30
0
class Script(object):
    """
    A Script is the base for completions, goto or whatever you want to do with
    |jedi|.

    You can either use the ``source`` parameter or ``path`` to read a file.
    Usually you're going to want to use both of them (in an editor).

    The script might be analyzed in a different ``sys.path`` than |jedi|:

    - if `sys_path` parameter is not ``None``, it will be used as ``sys.path``
      for the script;

    - if `sys_path` parameter is ``None`` and ``VIRTUAL_ENV`` environment
      variable is defined, ``sys.path`` for the specified environment will be
      guessed (see :func:`jedi.evaluate.sys_path.get_venv_path`) and used for
      the script;

    - otherwise ``sys.path`` will match that of |jedi|.

    :param source: The source code of the current file, separated by newlines.
    :type source: str
    :param line: The line to perform actions on (starting with 1).
    :type line: int
    :param column: The column of the cursor (starting with 0).
    :type column: int
    :param path: The path of the file in the file system, or ``''`` if
        it hasn't been saved yet.
    :type path: str or None
    :param encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    :param sys_path: ``sys.path`` to use during analysis of the script
    :type sys_path: list
    :param environment: TODO
    :type environment: Environment
    """
    def __init__(self, source=None, line=None, column=None, path=None,
                 encoding='utf-8', sys_path=None, environment=None):
        self._orig_path = path
        # An empty path (also empty string) should always result in no path.
        self.path = os.path.abspath(path) if path else None

        if source is None:
            # TODO add a better warning than the traceback!
            with open(path, 'rb') as f:
                source = f.read()

        # Load the Python grammar of the current interpreter.
        self._grammar = parso.load_grammar()

        if sys_path is not None and not is_py3:
            sys_path = list(map(force_unicode, sys_path))

        # Load the Python grammar of the current interpreter.
        project = get_default_project(
            os.path.dirname(self.path)if path else os.getcwd()
        )
        # TODO deprecate and remove sys_path from the Script API.
        if sys_path is not None:
            project._sys_path = sys_path
        self._evaluator = Evaluator(
            project, environment=environment, script_path=self.path
        )
        debug.speed('init')
        self._module_node, source = self._evaluator.parse_and_get_code(
            code=source,
            path=self.path,
            encoding=encoding,
            cache=False,  # No disk cache, because the current script often changes.
            diff_cache=settings.fast_parser,
            cache_path=settings.cache_directory,
        )
        debug.speed('parsed')
        self._code_lines = parso.split_lines(source, keepends=True)
        self._code = source
        line = max(len(self._code_lines), 1) if line is None else line
        if not (0 < line <= len(self._code_lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_string = self._code_lines[line - 1]
        line_len = len(line_string)
        if line_string.endswith('\r\n'):
            line_len -= 1
        if line_string.endswith('\n'):
            line_len -= 1

        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter (%d) is not in a valid range '
                             '(0-%d) for line %d (%r).' % (
                                 column, line_len, line, line_string))
        self._pos = line, column
        self._path = path

        cache.clear_time_caches()
        debug.reset_time()

    def _get_module(self):
        name = '__main__'
        if self.path is not None:
            import_names = transform_path_to_dotted(self._evaluator.get_sys_path(), self.path)
            if import_names is not None:
                name = '.'.join(import_names)

        module = ModuleContext(
            self._evaluator, self._module_node, self.path,
            code_lines=self._code_lines
        )
        imports.add_module_to_cache(self._evaluator, name, module)
        return module

    def __repr__(self):
        return '<%s: %s %r>' % (
            self.__class__.__name__,
            repr(self._orig_path),
            self._evaluator.environment,
        )

    def completions(self):
        """
        Return :class:`classes.Completion` objects. Those objects contain
        information about the completions, more than just names.

        :return: Completion objects, sorted by name and __ comes last.
        :rtype: list of :class:`classes.Completion`
        """
        debug.speed('completions start')
        completion = Completion(
            self._evaluator, self._get_module(), self._code_lines,
            self._pos, self.call_signatures
        )
        completions = completion.completions()

        def iter_import_completions():
            for c in completions:
                tree_name = c._name.tree_name
                if tree_name is None:
                    continue
                definition = tree_name.get_definition()
                if definition is not None \
                        and definition.type in ('import_name', 'import_from'):
                    yield c

        if len(list(iter_import_completions())) > 10:
            # For now disable completions if there's a lot of imports that
            # might potentially be resolved. This is the case for tensorflow
            # and has been fixed for it. This is obviously temporary until we
            # have a better solution.
            self._evaluator.infer_enabled = False

        debug.speed('completions end')
        return completions

    def goto_definitions(self):
        """
        Return the definitions of a the path under the cursor.  goto function!
        This follows complicated paths and returns the end, not the first
        definition. The big difference between :meth:`goto_assignments` and
        :meth:`goto_definitions` is that :meth:`goto_assignments` doesn't
        follow imports and statements. Multiple objects may be returned,
        because Python itself is a dynamic language, which means depending on
        an option you can have two different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        leaf = self._module_node.get_name_of_position(self._pos)
        if leaf is None:
            leaf = self._module_node.get_leaf_for_position(self._pos)
            if leaf is None:
                return []

        context = self._evaluator.create_context(self._get_module(), leaf)
        definitions = helpers.evaluate_goto_definition(self._evaluator, context, leaf)

        names = [s.name for s in definitions]
        defs = [classes.Definition(self._evaluator, name) for name in names]
        # The additional set here allows the definitions to become unique in an
        # API sense. In the internals we want to separate more things than in
        # the API.
        return helpers.sorted_definitions(set(defs))

    def goto_assignments(self, follow_imports=False, follow_builtin_imports=False):
        """
        Return the first definition found, while optionally following imports.
        Multiple objects may be returned, because Python itself is a
        dynamic language, which means depending on an option you can have two
        different versions of a function.

        :param follow_imports: The goto call will follow imports.
        :param follow_builtin_imports: If follow_imports is True will decide if
            it follow builtin imports.
        :rtype: list of :class:`classes.Definition`
        """
        def filter_follow_imports(names, check):
            for name in names:
                if check(name):
                    new_names = list(filter_follow_imports(name.goto(), check))
                    found_builtin = False
                    if follow_builtin_imports:
                        for new_name in new_names:
                            if new_name.start_pos is None:
                                found_builtin = True

                    if found_builtin and not isinstance(name, imports.SubModuleName):
                        yield name
                    else:
                        for new_name in new_names:
                            yield new_name
                else:
                    yield name

        tree_name = self._module_node.get_name_of_position(self._pos)
        if tree_name is None:
            return []
        context = self._evaluator.create_context(self._get_module(), tree_name)
        names = list(self._evaluator.goto(context, tree_name))

        if follow_imports:
            def check(name):
                return name.is_import()
        else:
            def check(name):
                return isinstance(name, imports.SubModuleName)

        names = filter_follow_imports(names, check)

        defs = [classes.Definition(self._evaluator, d) for d in set(names)]
        return helpers.sorted_definitions(defs)

    def usages(self, additional_module_paths=(), **kwargs):
        """
        Return :class:`classes.Definition` objects, which contain all
        names that point to the definition of the name under the cursor. This
        is very useful for refactoring (renaming), or to show all usages of a
        variable.

        .. todo:: Implement additional_module_paths

        :param additional_module_paths: Deprecated, never ever worked.
        :param include_builtins: Default True, checks if a usage is a builtin
            (e.g. ``sys``) and in that case does not return it.
        :rtype: list of :class:`classes.Definition`
        """
        if additional_module_paths:
            warnings.warn(
                "Deprecated since version 0.12.0. This never even worked, just ignore it.",
                DeprecationWarning,
                stacklevel=2
            )

        def _usages(include_builtins=True):
            tree_name = self._module_node.get_name_of_position(self._pos)
            if tree_name is None:
                # Must be syntax
                return []

            names = usages.usages(self._get_module(), tree_name)

            definitions = [classes.Definition(self._evaluator, n) for n in names]
            if not include_builtins:
                definitions = [d for d in definitions if not d.in_builtin_module()]
            return helpers.sorted_definitions(definitions)
        return _usages(**kwargs)

    def call_signatures(self):
        """
        Return the function object of the call you're currently in.

        E.g. if the cursor is here::

            abs(# <-- cursor is here

        This would return the ``abs`` function. On the other hand::

            abs()# <-- cursor is here

        This would return an empty list..

        :rtype: list of :class:`classes.CallSignature`
        """
        call_signature_details = \
            helpers.get_call_signature_details(self._module_node, self._pos)
        if call_signature_details is None:
            return []

        context = self._evaluator.create_context(
            self._get_module(),
            call_signature_details.bracket_leaf
        )
        definitions = helpers.cache_call_signatures(
            self._evaluator,
            context,
            call_signature_details.bracket_leaf,
            self._code_lines,
            self._pos
        )
        debug.speed('func_call followed')

        return [classes.CallSignature(self._evaluator, d.name,
                                      call_signature_details.bracket_leaf.start_pos,
                                      call_signature_details.call_index,
                                      call_signature_details.keyword_name_str)
                for d in definitions if hasattr(d, 'py__call__')]

    def _analysis(self):
        self._evaluator.is_analysis = True
        self._evaluator.analysis_modules = [self._module_node]
        module = self._get_module()
        try:
            for node in get_executable_nodes(self._module_node):
                context = module.create_context(node)
                if node.type in ('funcdef', 'classdef'):
                    # Resolve the decorators.
                    tree_name_to_contexts(self._evaluator, context, node.children[1])
                elif isinstance(node, tree.Import):
                    import_names = set(node.get_defined_names())
                    if node.is_nested():
                        import_names |= set(path[-1] for path in node.get_paths())
                    for n in import_names:
                        imports.infer_import(context, n)
                elif node.type == 'expr_stmt':
                    types = context.eval_node(node)
                    for testlist in node.children[:-1:2]:
                        # Iterate tuples.
                        unpack_tuple_to_dict(context, types, testlist)
                else:
                    if node.type == 'name':
                        defs = self._evaluator.goto_definitions(context, node)
                    else:
                        defs = evaluate_call_of_leaf(context, node)
                    try_iter_content(defs)
                self._evaluator.reset_recursion_limitations()

            ana = [a for a in self._evaluator.analysis if self.path == a.path]
            return sorted(set(ana), key=lambda x: x.line)
        finally:
            self._evaluator.is_analysis = False
示例#31
0
def check_module_test(code):
    grammar = load_grammar()
    module_context = ModuleContext(Evaluator(grammar), parse(code), path=None)
    return _check_module(module_context)
示例#32
0
class Script(object):
    """
    A Script is the base for completions, goto or whatever you want to do with
    |jedi|.

    You can either use the ``source`` parameter or ``path`` to read a file.
    Usually you're going to want to use both of them (in an editor).

    :param source: The source code of the current file, separated by newlines.
    :type source: str
    :param line: The line to perform actions on (starting with 1).
    :type line: int
    :param col: The column of the cursor (starting with 0).
    :type col: int
    :param path: The path of the file in the file system, or ``''`` if
        it hasn't been saved yet.
    :type path: str or None
    :param encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    :param source_encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    """
    def __init__(self, source=None, line=None, column=None, path=None,
                 encoding='utf-8', source_path=None, source_encoding=None):
        if source_path is not None:
            warnings.warn("Use path instead of source_path.", DeprecationWarning)
            path = source_path
        if source_encoding is not None:
            warnings.warn("Use encoding instead of source_encoding.", DeprecationWarning)
            encoding = source_encoding

        self._orig_path = path
        self.path = None if path is None else os.path.abspath(path)

        if source is None:
            with open(path) as f:
                source = f.read()

        self.source = common.source_to_unicode(source, encoding)
        lines = common.splitlines(self.source)
        line = max(len(lines), 1) if line is None else line
        if not (0 < line <= len(lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column

        cache.clear_time_caches()
        debug.reset_time()
        self._user_context = UserContext(self.source, self._pos)
        self._parser = UserContextParser(self.source, path, self._pos, self._user_context)
        self._evaluator = Evaluator()
        debug.speed('init')

    @property
    def source_path(self):
        """
        .. deprecated:: 0.7.0
           Use :attr:`.path` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use path instead of source_path.", DeprecationWarning)
        return self.path

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, repr(self._orig_path))

    def completions(self):
        """
        Return :class:`classes.Completion` objects. Those objects contain
        information about the completions, more than just names.

        :return: Completion objects, sorted by name and __ comes last.
        :rtype: list of :class:`classes.Completion`
        """
        def get_completions(user_stmt, bs):
            if isinstance(user_stmt, pr.Import):
                context = self._user_context.get_context()
                next(context)  # skip the path
                if next(context) == 'from':
                    # completion is just "import" if before stands from ..
                    return ((k, bs) for k in keywords.keyword_names('import'))
            return self._simple_complete(path, like)

        def completion_possible(path):
            """
            The completion logic is kind of complicated, because we strip the
            last word part. To ignore certain strange patterns with dots, just
            use regex.
            """
            if re.match('\d+\.\.$|\.{4}$', path):
                return True  # check Ellipsis and float literal `1.`

            return not re.search(r'^\.|^\d\.$|\.\.$', path)

        debug.speed('completions start')
        path = self._user_context.get_path_until_cursor()
        if not completion_possible(path):
            return []
        path, dot, like = helpers.completion_parts(path)

        user_stmt = self._parser.user_stmt_with_whitespace()
        b = compiled.builtin
        completions = get_completions(user_stmt, b)

        if not dot:
            # add named params
            for call_sig in self.call_signatures():
                # Allow protected access, because it's a public API.
                module = call_sig._name.get_parent_until()
                # Compiled modules typically don't allow keyword arguments.
                if not isinstance(module, compiled.CompiledObject):
                    for p in call_sig.params:
                        # Allow access on _definition here, because it's a
                        # public API and we don't want to make the internal
                        # Name object public.
                        if p._definition.stars == 0:  # no *args/**kwargs
                            completions.append((p._name, p._name))

            if not path and not isinstance(user_stmt, pr.Import):
                # add keywords
                completions += ((k, b) for k in keywords.keyword_names(all=True))

        needs_dot = not dot and path

        comps = []
        comp_dct = {}
        for c, s in set(completions):
            n = str(c)
            if settings.case_insensitive_completion \
                    and n.lower().startswith(like.lower()) \
                    or n.startswith(like):
                if not filter_private_variable(s, user_stmt or self._parser.user_scope(), n):
                    if isinstance(c.parent, (pr.Function, pr.Class)):
                        # TODO I think this is a hack. It should be an
                        #   er.Function/er.Class before that.
                        c = er.wrap(self._evaluator, c.parent).name
                    new = classes.Completion(self._evaluator, c, needs_dot, len(like), s)
                    k = (new.name, new.complete)  # key
                    if k in comp_dct and settings.no_completion_duplicates:
                        comp_dct[k]._same_name_completions.append(new)
                    else:
                        comp_dct[k] = new
                        comps.append(new)

        debug.speed('completions end')

        return sorted(comps, key=lambda x: (x.name.startswith('__'),
                                            x.name.startswith('_'),
                                            x.name.lower()))

    def _simple_complete(self, path, like):
        try:
            scopes = list(self._prepare_goto(path, True))
        except NotFoundError:
            scopes = []
            scope_names_generator = get_names_of_scope(self._evaluator,
                                                       self._parser.user_scope(),
                                                       self._pos)
            completions = []
            for scope, name_list in scope_names_generator:
                for c in name_list:
                    completions.append((c, scope))
        else:
            completions = []
            debug.dbg('possible completion scopes: %s', scopes)
            for s in scopes:
                if s.isinstance(er.Function):
                    names = s.get_magic_function_names()
                elif isinstance(s, imports.ImportWrapper):
                    under = like + self._user_context.get_path_after_cursor()
                    if under == 'import':
                        current_line = self._user_context.get_position_line()
                        if not current_line.endswith('import import'):
                            continue
                    a = s.import_stmt.alias
                    if a and a.start_pos <= self._pos <= a.end_pos:
                        continue
                    names = s.get_defined_names(on_import_stmt=True)
                else:
                    names = []
                    for _, new_names in s.scope_names_generator():
                        names += new_names

                for c in names:
                    completions.append((c, s))
        return completions

    def _prepare_goto(self, goto_path, is_completion=False):
        """
        Base for completions/goto. Basically it returns the resolved scopes
        under cursor.
        """
        debug.dbg('start: %s in %s', goto_path, self._parser.user_scope())

        user_stmt = self._parser.user_stmt_with_whitespace()
        if not user_stmt and len(goto_path.split('\n')) > 1:
            # If the user_stmt is not defined and the goto_path is multi line,
            # something's strange. Most probably the backwards tokenizer
            # matched to much.
            return []

        if isinstance(user_stmt, pr.Import):
            scopes = [helpers.get_on_import_stmt(self._evaluator, self._user_context,
                                                 user_stmt, is_completion)[0]]
        else:
            # just parse one statement, take it and evaluate it
            eval_stmt = self._get_under_cursor_stmt(goto_path)

            if not is_completion:
                # goto_definition returns definitions of its statements if the
                # cursor is on the assignee. By changing the start_pos of our
                # "pseudo" statement, the Jedi evaluator can find the assignees.
                if user_stmt is not None:
                    eval_stmt.start_pos = user_stmt.end_pos
            scopes = self._evaluator.eval_statement(eval_stmt)

        return scopes

    def _get_under_cursor_stmt(self, cursor_txt):
        tokenizer = source_tokens(cursor_txt, line_offset=self._pos[0] - 1)
        r = Parser(cursor_txt, no_docstr=True, tokenizer=tokenizer)
        try:
            # Take the last statement available.
            stmt = r.module.statements[-1]
        except IndexError:
            raise NotFoundError()
        if isinstance(stmt, pr.KeywordStatement):
            stmt = stmt.stmt
        if not isinstance(stmt, pr.ExprStmt):
            raise NotFoundError()

        user_stmt = self._parser.user_stmt()
        if user_stmt is None:
            # Set the start_pos to a pseudo position, that doesn't exist but works
            # perfectly well (for both completions in docstrings and statements).
            stmt.start_pos = self._pos
        else:
            stmt.start_pos = user_stmt.start_pos
        stmt.parent = self._parser.user_scope()
        return stmt

    def complete(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.completions` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use completions instead.", DeprecationWarning)
        return self.completions()

    def goto(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.goto_assignments` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use goto_assignments instead.", DeprecationWarning)
        return self.goto_assignments()

    def definition(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.goto_definitions` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use goto_definitions instead.", DeprecationWarning)
        return self.goto_definitions()

    def get_definition(self):
        """
        .. deprecated:: 0.5.0
           Use :attr:`.goto_definitions` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use goto_definitions instead.", DeprecationWarning)
        return self.goto_definitions()

    def related_names(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.usages` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use usages instead.", DeprecationWarning)
        return self.usages()

    def get_in_function_call(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.call_signatures` instead.
        .. todo:: Remove!
        """
        return self.function_definition()

    def function_definition(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.call_signatures` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use call_signatures instead.", DeprecationWarning)
        sig = self.call_signatures()
        return sig[0] if sig else None

    def goto_definitions(self):
        """
        Return the definitions of a the path under the cursor.  goto function!
        This follows complicated paths and returns the end, not the first
        definition. The big difference between :meth:`goto_assignments` and
        :meth:`goto_definitions` is that :meth:`goto_assignments` doesn't
        follow imports and statements. Multiple objects may be returned,
        because Python itself is a dynamic language, which means depending on
        an option you can have two different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        def resolve_import_paths(scopes):
            for s in scopes.copy():
                if isinstance(s, imports.ImportWrapper):
                    scopes.remove(s)
                    scopes.update(resolve_import_paths(set(s.follow())))
            return scopes

        user_stmt = self._parser.user_stmt_with_whitespace()
        goto_path = self._user_context.get_path_under_cursor()
        context = self._user_context.get_context()
        definitions = set()
        if next(context) in ('class', 'def'):
            definitions = set([er.wrap(self._evaluator, self._parser.user_scope())])
        else:
            # Fetch definition of callee, if there's no path otherwise.
            if not goto_path:
                call, _, _ = search_call_signatures(user_stmt, self._pos)
                if call is not None:
                    definitions = set(self._evaluator.eval_call(call))

        if not definitions:
            if goto_path:
                definitions = set(self._prepare_goto(goto_path))

        definitions = resolve_import_paths(definitions)
        names = [s.name for s in definitions
                 if s is not imports.ImportWrapper.GlobalNamespace]
        defs = [classes.Definition(self._evaluator, name) for name in names]
        return helpers.sorted_definitions(set(defs))

    def goto_assignments(self):
        """
        Return the first definition found. Imports and statements aren't
        followed. Multiple objects may be returned, because Python itself is a
        dynamic language, which means depending on an option you can have two
        different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        results = self._goto()
        d = [classes.Definition(self._evaluator, d) for d in set(results)
             if d is not imports.ImportWrapper.GlobalNamespace]
        return helpers.sorted_definitions(d)

    def _goto(self, add_import_name=False):
        """
        Used for goto_assignments and usages.

        :param add_import_name: Add the the name (if import) to the result.
        """
        def follow_inexistent_imports(defs):
            """ Imports can be generated, e.g. following
            `multiprocessing.dummy` generates an import dummy in the
            multiprocessing module. The Import doesn't exist -> follow.
            """
            definitions = set(defs)
            for d in defs:
                if isinstance(d.parent, pr.Import) \
                        and d.start_pos == (0, 0):
                    i = imports.ImportWrapper(self._evaluator, d.parent).follow(is_goto=True)
                    definitions.remove(d)
                    definitions |= follow_inexistent_imports(i)
            return definitions

        goto_path = self._user_context.get_path_under_cursor()
        context = self._user_context.get_context()
        user_stmt = self._parser.user_stmt()

        stmt = self._get_under_cursor_stmt(goto_path)
        expression_list = stmt.expression_list()
        if len(expression_list) == 0:
            return []
        # The reverse tokenizer only generates parses call.
        assert len(expression_list) == 1
        call = expression_list[0]
        if isinstance(call, pr.Call):
            call_path = list(call.generate_call_path())
        else:
            # goto_assignments on Operator returns nothing.
            return []

        if next(context) in ('class', 'def'):
            # The cursor is on a class/function name.
            user_scope = self._parser.user_scope()
            definitions = set([user_scope.name])
        elif isinstance(user_stmt, pr.Import):
            s, name_part = helpers.get_on_import_stmt(self._evaluator,
                                                      self._user_context, user_stmt)
            try:
                definitions = [s.follow(is_goto=True)[0]]
            except IndexError:
                definitions = []

            if add_import_name:
                import_name = user_stmt.get_defined_names()
                # imports have only one name
                np = import_name[0]
                if not user_stmt.star and unicode(name_part) == unicode(np):
                    definitions.append(np)
        else:
            # The Evaluator.goto function checks for definitions, but since we
            # use a reverse tokenizer, we have new name_part objects, so we
            # have to check the user_stmt here for positions.
            if isinstance(user_stmt, pr.ExprStmt):
                for name in user_stmt.get_defined_names():
                    if name.start_pos <= self._pos <= name.end_pos \
                            and (not isinstance(name.parent, pr.Call)
                                 or name.parent.next is None):
                        return [name]

            defs = self._evaluator.goto(stmt, call_path)
            definitions = follow_inexistent_imports(defs)
        return definitions

    def usages(self, additional_module_paths=()):
        """
        Return :class:`classes.Definition` objects, which contain all
        names that point to the definition of the name under the cursor. This
        is very useful for refactoring (renaming), or to show all usages of a
        variable.

        .. todo:: Implement additional_module_paths

        :rtype: list of :class:`classes.Definition`
        """
        temp, settings.dynamic_flow_information = \
            settings.dynamic_flow_information, False
        try:
            user_stmt = self._parser.user_stmt()
            definitions = self._goto(add_import_name=True)
            if not definitions:
                # Without a definition for a name we cannot find references.
                return []

            if not isinstance(user_stmt, pr.Import):
                # import case is looked at with add_import_name option
                definitions = usages.usages_add_import_modules(self._evaluator,
                                                               definitions)

            module = set([d.get_parent_until() for d in definitions])
            module.add(self._parser.module())
            names = usages.usages(self._evaluator, definitions, module)

            for d in set(definitions):
                try:
                    name_part = d.names[-1]
                except AttributeError:
                    names.append(classes.Definition(self._evaluator, d))
                else:
                    names.append(classes.Definition(self._evaluator, name_part))
        finally:
            settings.dynamic_flow_information = temp

        return helpers.sorted_definitions(set(names))

    def call_signatures(self):
        """
        Return the function object of the call you're currently in.

        E.g. if the cursor is here::

            abs(# <-- cursor is here

        This would return the ``abs`` function. On the other hand::

            abs()# <-- cursor is here

        This would return ``None``.

        :rtype: list of :class:`classes.CallSignature`
        """
        user_stmt = self._parser.user_stmt_with_whitespace()
        call, execution_arr, index = search_call_signatures(user_stmt, self._pos)
        if call is None:
            return []

        with common.scale_speed_settings(settings.scale_call_signatures):
            origins = cache.cache_call_signatures(self._evaluator, call, self.source,
                                                  self._pos, user_stmt)
        debug.speed('func_call followed')

        key_name = None
        try:
            detail = execution_arr[index].assignment_details[0]
        except IndexError:
            pass
        else:
            try:
                key_name = unicode(detail[0][0].name)
            except (IndexError, AttributeError):
                pass
        return [classes.CallSignature(self._evaluator, o.name, call, index, key_name)
                for o in origins if hasattr(o, 'py__call__')]

    def _analysis(self):
        #statements = set(chain(*self._parser.module().used_names.values()))
        stmts, imps = analysis.get_module_statements(self._parser.module())
        # Sort the statements so that the results are reproducible.
        for i in imps:
            iw = imports.ImportWrapper(self._evaluator, i,
                                       nested_resolve=True).follow()
            if i.is_nested() and any(not isinstance(i, pr.Module) for i in iw):
                analysis.add(self._evaluator, 'import-error', i.namespace_names[-1])
        for stmt in sorted(stmts, key=lambda obj: obj.start_pos):
            if not (isinstance(stmt.parent, pr.ForFlow)
                    and stmt.parent.set_stmt == stmt):
                self._evaluator.eval_statement(stmt)

        ana = [a for a in self._evaluator.analysis if self.path == a.path]
        return sorted(set(ana), key=lambda x: x.line)
示例#33
0
class Script(object):
    """
    A Script is the base for completions, goto or whatever you want to do with
    |jedi|.

    You can either use the ``source`` parameter or ``path`` to read a file.
    Usually you're going to want to use both of them (in an editor).

    The script might be analyzed in a different ``sys.path`` than |jedi|:

    - if `sys_path` parameter is not ``None``, it will be used as ``sys.path``
      for the script;

    - if `sys_path` parameter is ``None`` and ``VIRTUAL_ENV`` environment
      variable is defined, ``sys.path`` for the specified environment will be
      guessed (see :func:`jedi.evaluate.sys_path.get_venv_path`) and used for
      the script;

    - otherwise ``sys.path`` will match that of |jedi|.

    :param source: The source code of the current file, separated by newlines.
    :type source: str
    :param line: The line to perform actions on (starting with 1).
    :type line: int
    :param column: The column of the cursor (starting with 0).
    :type column: int
    :param path: The path of the file in the file system, or ``''`` if
        it hasn't been saved yet.
    :type path: str or None
    :param encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    :param source_encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    :param sys_path: ``sys.path`` to use during analysis of the script
    :type sys_path: list

    """
    def __init__(self, source=None, line=None, column=None, path=None,
                 encoding='utf-8', sys_path=None):
        self._orig_path = path
        # An empty path (also empty string) should always result in no path.
        self.path = os.path.abspath(path) if path else None

        if source is None:
            # TODO add a better warning than the traceback!
            with open(path, 'rb') as f:
                source = f.read()

        # TODO do we really want that?
        self._source = python_bytes_to_unicode(source, encoding, errors='replace')
        self._code_lines = split_lines(self._source)
        line = max(len(self._code_lines), 1) if line is None else line
        if not (0 < line <= len(self._code_lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(self._code_lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column
        self._path = path

        cache.clear_time_caches()
        debug.reset_time()

        # Load the Python grammar of the current interpreter.
        self._grammar = parso.load_grammar()
        project = Project(sys_path=sys_path)
        self._evaluator = Evaluator(self._grammar, project)
        project.add_script_path(self.path)
        debug.speed('init')

    @cache.memoize_method
    def _get_module_node(self):
        return self._grammar.parse(
            code=self._source,
            path=self.path,
            cache=False,  # No disk cache, because the current script often changes.
            diff_cache=True,
            cache_path=settings.cache_directory
        )

    @cache.memoize_method
    def _get_module(self):
        module = ModuleContext(
            self._evaluator,
            self._get_module_node(),
            self.path
        )
        if self.path is not None:
            name = dotted_path_in_sys_path(self._evaluator.project.sys_path, self.path)
            if name is not None:
                imports.add_module(self._evaluator, name, module)
        return module

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, repr(self._orig_path))

    def completions(self):
        """
        Return :class:`classes.Completion` objects. Those objects contain
        information about the completions, more than just names.

        :return: Completion objects, sorted by name and __ comes last.
        :rtype: list of :class:`classes.Completion`
        """
        debug.speed('completions start')
        completion = Completion(
            self._evaluator, self._get_module(), self._code_lines,
            self._pos, self.call_signatures
        )
        completions = completion.completions()
        debug.speed('completions end')
        return completions

    def goto_definitions(self):
        """
        Return the definitions of a the path under the cursor.  goto function!
        This follows complicated paths and returns the end, not the first
        definition. The big difference between :meth:`goto_assignments` and
        :meth:`goto_definitions` is that :meth:`goto_assignments` doesn't
        follow imports and statements. Multiple objects may be returned,
        because Python itself is a dynamic language, which means depending on
        an option you can have two different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        module_node = self._get_module_node()
        leaf = module_node.get_name_of_position(self._pos)
        if leaf is None:
            leaf = module_node.get_leaf_for_position(self._pos)
            if leaf is None:
                return []

        context = self._evaluator.create_context(self._get_module(), leaf)
        definitions = helpers.evaluate_goto_definition(self._evaluator, context, leaf)

        names = [s.name for s in definitions]
        defs = [classes.Definition(self._evaluator, name) for name in names]
        # The additional set here allows the definitions to become unique in an
        # API sense. In the internals we want to separate more things than in
        # the API.
        return helpers.sorted_definitions(set(defs))

    def goto_assignments(self, follow_imports=False):
        """
        Return the first definition found, while optionally following imports.
        Multiple objects may be returned, because Python itself is a
        dynamic language, which means depending on an option you can have two
        different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        def filter_follow_imports(names, check):
            for name in names:
                if check(name):
                    for result in filter_follow_imports(name.goto(), check):
                        yield result
                else:
                    yield name

        tree_name = self._get_module_node().get_name_of_position(self._pos)
        if tree_name is None:
            return []
        context = self._evaluator.create_context(self._get_module(), tree_name)
        names = list(self._evaluator.goto(context, tree_name))

        if follow_imports:
            def check(name):
                if isinstance(name, ModuleName):
                    return False
                return name.api_type == 'module'
        else:
            def check(name):
                return isinstance(name, imports.SubModuleName)

        names = filter_follow_imports(names, check)

        defs = [classes.Definition(self._evaluator, d) for d in set(names)]
        return helpers.sorted_definitions(defs)

    def usages(self, additional_module_paths=()):
        """
        Return :class:`classes.Definition` objects, which contain all
        names that point to the definition of the name under the cursor. This
        is very useful for refactoring (renaming), or to show all usages of a
        variable.

        .. todo:: Implement additional_module_paths

        :rtype: list of :class:`classes.Definition`
        """
        tree_name = self._get_module_node().get_name_of_position(self._pos)
        if tree_name is None:
            # Must be syntax
            return []

        names = usages.usages(self._get_module(), tree_name)

        definitions = [classes.Definition(self._evaluator, n) for n in names]
        return helpers.sorted_definitions(definitions)

    def call_signatures(self):
        """
        Return the function object of the call you're currently in.

        E.g. if the cursor is here::

            abs(# <-- cursor is here

        This would return the ``abs`` function. On the other hand::

            abs()# <-- cursor is here

        This would return an empty list..

        :rtype: list of :class:`classes.CallSignature`
        """
        call_signature_details = \
            helpers.get_call_signature_details(self._get_module_node(), self._pos)
        if call_signature_details is None:
            return []

        context = self._evaluator.create_context(
            self._get_module(),
            call_signature_details.bracket_leaf
        )
        definitions = helpers.cache_call_signatures(
            self._evaluator,
            context,
            call_signature_details.bracket_leaf,
            self._code_lines,
            self._pos
        )
        debug.speed('func_call followed')

        return [classes.CallSignature(self._evaluator, d.name,
                                      call_signature_details.bracket_leaf.start_pos,
                                      call_signature_details.call_index,
                                      call_signature_details.keyword_name_str)
                for d in definitions if hasattr(d, 'py__call__')]

    def _analysis(self):
        self._evaluator.is_analysis = True
        module_node = self._get_module_node()
        self._evaluator.analysis_modules = [module_node]
        try:
            for node in get_executable_nodes(module_node):
                context = self._get_module().create_context(node)
                if node.type in ('funcdef', 'classdef'):
                    # Resolve the decorators.
                    tree_name_to_contexts(self._evaluator, context, node.children[1])
                elif isinstance(node, tree.Import):
                    import_names = set(node.get_defined_names())
                    if node.is_nested():
                        import_names |= set(path[-1] for path in node.get_paths())
                    for n in import_names:
                        imports.infer_import(context, n)
                elif node.type == 'expr_stmt':
                    types = context.eval_node(node)
                    for testlist in node.children[:-1:2]:
                        # Iterate tuples.
                        unpack_tuple_to_dict(context, types, testlist)
                else:
                    if node.type == 'name':
                        defs = self._evaluator.goto_definitions(context, node)
                    else:
                        defs = evaluate_call_of_leaf(context, node)
                    try_iter_content(defs)
                self._evaluator.reset_recursion_limitations()

            ana = [a for a in self._evaluator.analysis if self.path == a.path]
            return sorted(set(ana), key=lambda x: x.line)
        finally:
            self._evaluator.is_analysis = False
示例#34
0
def _evaluator():
    return Evaluator(load_grammar())
示例#35
0
class Script(object):
    """
    A Script is the base for completions, goto or whatever you want to do with
    |jedi|.

    You can either use the ``source`` parameter or ``path`` to read a file.
    Usually you're going to want to use both of them (in an editor).

    :param source: The source code of the current file, separated by newlines.
    :type source: str
    :param line: The line to perform actions on (starting with 1).
    :type line: int
    :param col: The column of the cursor (starting with 0).
    :type col: int
    :param path: The path of the file in the file system, or ``''`` if
        it hasn't been saved yet.
    :type path: str or None
    :param encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    """
    def __init__(self, source=None, line=None, column=None, path=None,
                 encoding='utf-8', source_path=None):
        if source_path is not None:
            warnings.warn("Use path instead of source_path.", DeprecationWarning)
            path = source_path
        self._source_path = path
        self.path = None if path is None else os.path.abspath(path)

        if source is None:
            with open(path) as f:
                source = f.read()

        lines = source.splitlines() or ['']
        if source and source[-1] == '\n':
            lines.append('')
        line = max(len(lines), 1) if line is None else line
        if not (0 < line <= len(lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column

        cache.clear_caches()
        debug.reset_time()
        self.source = common.source_to_unicode(source, encoding)
        self._user_context = UserContext(self.source, self._pos)
        self._parser = UserContextParser(self.source, path, self._pos, self._user_context)
        self._evaluator = Evaluator()
        debug.speed('init')

    @property
    def source_path(self):
        """
        .. deprecated:: 0.7.0
           Use :attr:`.path` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use path instead of source_path.", DeprecationWarning)
        return self.path

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, repr(self._source_path))

    def completions(self):
        """
        Return :class:`classes.Completion` objects. Those objects contain
        information about the completions, more than just names.

        :return: Completion objects, sorted by name and __ comes last.
        :rtype: list of :class:`classes.Completion`
        """
        def get_completions(user_stmt, bs):
            if isinstance(user_stmt, pr.Import):
                context = self._user_context.get_context()
                next(context)  # skip the path
                if next(context) == 'from':
                    # completion is just "import" if before stands from ..
                    return ((k, bs) for k in keywords.keyword_names('import'))
            return self._simple_complete(path, like)

        debug.speed('completions start')
        path = self._user_context.get_path_until_cursor()
        if re.search('^\.|\.\.$', path):
            return []
        path, dot, like = helpers.completion_parts(path)

        user_stmt = self._parser.user_stmt_with_whitespace()
        b = compiled.builtin
        completions = get_completions(user_stmt, b)

        if not dot:
            # add named params
            for call_sig in self.call_signatures():
                # allow protected access, because it's a public API.
                module = call_sig._definition.get_parent_until()
                # Compiled modules typically don't allow keyword arguments.
                if not isinstance(module, compiled.CompiledObject):
                    for p in call_sig.params:
                        # Allow access on _definition here, because it's a
                        # public API and we don't want to make the internal
                        # Name object public.
                        completions.append((p._definition.get_name(), p))

            if not path and not isinstance(user_stmt, pr.Import):
                # add keywords
                completions += ((k, b) for k in keywords.keyword_names(all=True))

        needs_dot = not dot and path

        comps = []
        comp_dct = {}
        for c, s in set(completions):
            n = str(c.names[-1])
            if settings.case_insensitive_completion \
                    and n.lower().startswith(like.lower()) \
                    or n.startswith(like):
                if not filter_private_variable(s, user_stmt or self._parser.user_scope(), n):
                    new = classes.Completion(self._evaluator, c, needs_dot, len(like), s)
                    k = (new.name, new.complete)  # key
                    if k in comp_dct and settings.no_completion_duplicates:
                        comp_dct[k]._same_name_completions.append(new)
                    else:
                        comp_dct[k] = new
                        comps.append(new)

        debug.speed('completions end')

        return sorted(comps, key=lambda x: (x.name.startswith('__'),
                                            x.name.startswith('_'),
                                            x.name.lower()))

    def _simple_complete(self, path, like):
        try:
            scopes = list(self._prepare_goto(path, True))
        except NotFoundError:
            scopes = []
            scope_generator = get_names_of_scope(self._evaluator,
                                                 self._parser.user_scope(),
                                                 self._pos)
            completions = []
            for scope, name_list in scope_generator:
                for c in name_list:
                    completions.append((c, scope))
        else:
            completions = []
            debug.dbg('possible completion scopes: %s', scopes)
            for s in scopes:
                if s.isinstance(er.Function):
                    names = s.get_magic_function_names()
                else:
                    if isinstance(s, imports.ImportPath):
                        under = like + self._user_context.get_path_after_cursor()
                        if under == 'import':
                            current_line = self._user_context.get_position_line()
                            if not current_line.endswith('import import'):
                                continue
                        a = s.import_stmt.alias
                        if a and a.start_pos <= self._pos <= a.end_pos:
                            continue
                        names = s.get_defined_names(on_import_stmt=True)
                    else:
                        names = s.get_defined_names()

                for c in names:
                    completions.append((c, s))
        return completions

    def _prepare_goto(self, goto_path, is_completion=False):
        """
        Base for completions/goto. Basically it returns the resolved scopes
        under cursor.
        """
        debug.dbg('start: %s in %s', goto_path, self._parser.user_scope())

        user_stmt = self._parser.user_stmt_with_whitespace()
        if not user_stmt and len(goto_path.split('\n')) > 1:
            # If the user_stmt is not defined and the goto_path is multi line,
            # something's strange. Most probably the backwards tokenizer
            # matched to much.
            return []

        if isinstance(user_stmt, pr.Import):
            scopes = [helpers.get_on_import_stmt(self._evaluator, self._user_context,
                                                 user_stmt, is_completion)[0]]
        else:
            # just parse one statement, take it and evaluate it
            stmt = self._get_under_cursor_stmt(goto_path)
            scopes = self._evaluator.eval_statement(stmt)
        return scopes

    def _get_under_cursor_stmt(self, cursor_txt):
        tokenizer = source_tokens(cursor_txt, self._pos[0] - 1)
        r = Parser(cursor_txt, no_docstr=True, tokenizer=tokenizer)
        try:
            # Take the last statement available.
            stmt = r.module.statements[-1]
        except IndexError:
            raise NotFoundError()
        # Set the start_pos to a pseudo position, that doesn't exist but works
        # perfectly well (for both completions in docstrings and statements).
        stmt.start_pos = self._pos
        stmt.parent = self._parser.user_scope()
        return stmt

    def complete(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.completions` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use completions instead.", DeprecationWarning)
        return self.completions()

    def goto(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.goto_assignments` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use goto_assignments instead.", DeprecationWarning)
        return self.goto_assignments()

    def definition(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.goto_definitions` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use goto_definitions instead.", DeprecationWarning)
        return self.goto_definitions()

    def get_definition(self):
        """
        .. deprecated:: 0.5.0
           Use :attr:`.goto_definitions` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use goto_definitions instead.", DeprecationWarning)
        return self.goto_definitions()

    def related_names(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.usages` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use usages instead.", DeprecationWarning)
        return self.usages()

    def get_in_function_call(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.call_signatures` instead.
        .. todo:: Remove!
        """
        return self.function_definition()

    def function_definition(self):
        """
        .. deprecated:: 0.6.0
           Use :attr:`.call_signatures` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use line instead.", DeprecationWarning)
        sig = self.call_signatures()
        return sig[0] if sig else None

    def goto_definitions(self):
        """
        Return the definitions of a the path under the cursor.  goto function!
        This follows complicated paths and returns the end, not the first
        definition. The big difference between :meth:`goto_assignments` and
        :meth:`goto_definitions` is that :meth:`goto_assignments` doesn't
        follow imports and statements. Multiple objects may be returned,
        because Python itself is a dynamic language, which means depending on
        an option you can have two different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        def resolve_import_paths(scopes):
            for s in scopes.copy():
                if isinstance(s, imports.ImportPath):
                    scopes.remove(s)
                    scopes.update(resolve_import_paths(set(s.follow())))
            return scopes

        user_stmt = self._parser.user_stmt_with_whitespace()
        goto_path = self._user_context.get_path_under_cursor()
        context = self._user_context.get_context()
        definitions = set()
        if next(context) in ('class', 'def'):
            definitions = set([self._parser.user_scope()])
        else:
            # Fetch definition of callee, if there's no path otherwise.
            if not goto_path:
                (call, _) = helpers.func_call_and_param_index(user_stmt, self._pos)
                if call is not None:
                    while call.next is not None:
                        call = call.next
                    # reset cursor position:
                    (row, col) = call.name.end_pos
                    pos = (row, max(col - 1, 0))
                    self._user_context = UserContext(self.source, pos)
                    # then try to find the path again
                    goto_path = self._user_context.get_path_under_cursor()

        if not definitions:
            if goto_path:
                definitions = set(self._prepare_goto(goto_path))

        definitions = resolve_import_paths(definitions)
        d = set([classes.Definition(self._evaluator, s) for s in definitions
                 if s is not imports.ImportPath.GlobalNamespace])
        return helpers.sorted_definitions(d)

    def goto_assignments(self):
        """
        Return the first definition found. Imports and statements aren't
        followed. Multiple objects may be returned, because Python itself is a
        dynamic language, which means depending on an option you can have two
        different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        results, _ = self._goto()
        d = [classes.Definition(self._evaluator, d) for d in set(results)
             if d is not imports.ImportPath.GlobalNamespace]
        return helpers.sorted_definitions(d)

    def _goto(self, add_import_name=False):
        """
        Used for goto_assignments and usages.

        :param add_import_name: Add the the name (if import) to the result.
        """
        def follow_inexistent_imports(defs):
            """ Imports can be generated, e.g. following
            `multiprocessing.dummy` generates an import dummy in the
            multiprocessing module. The Import doesn't exist -> follow.
            """
            definitions = set(defs)
            for d in defs:
                if isinstance(d.parent, pr.Import) \
                        and d.start_pos == (0, 0):
                    i = imports.ImportPath(self._evaluator, d.parent).follow(is_goto=True)
                    definitions.remove(d)
                    definitions |= follow_inexistent_imports(i)
            return definitions

        goto_path = self._user_context.get_path_under_cursor()
        context = self._user_context.get_context()
        user_stmt = self._parser.user_stmt()
        if next(context) in ('class', 'def'):
            user_scope = self._parser.user_scope()
            definitions = set([user_scope.name])
            search_name = unicode(user_scope.name)
        elif isinstance(user_stmt, pr.Import):
            s, name_part = helpers.get_on_import_stmt(self._evaluator,
                                                   self._user_context, user_stmt)
            try:
                definitions = [s.follow(is_goto=True)[0]]
            except IndexError:
                definitions = []
            search_name = unicode(name_part)

            if add_import_name:
                import_name = user_stmt.get_defined_names()
                # imports have only one name
                if not user_stmt.star \
                        and name_part == import_name[0].names[-1]:
                    definitions.append(import_name[0])
        else:
            stmt = self._get_under_cursor_stmt(goto_path)
            defs, search_name = self._evaluator.goto(stmt)
            definitions = follow_inexistent_imports(defs)
            if isinstance(user_stmt, pr.Statement):
                c = user_stmt.expression_list()
                if c and not isinstance(c[0], (str, unicode)) \
                        and c[0].start_pos > self._pos \
                        and not re.search(r'\.\w+$', goto_path):
                    # The cursor must be after the start, otherwise the
                    # statement is just an assignee.
                    definitions = [user_stmt]
        return definitions, search_name

    def usages(self, additional_module_paths=()):
        """
        Return :class:`classes.Usage` objects, which contain all
        names that point to the definition of the name under the cursor. This
        is very useful for refactoring (renaming), or to show all usages of a
        variable.

        .. todo:: Implement additional_module_paths

        :rtype: list of :class:`classes.Usage`
        """
        temp, settings.dynamic_flow_information = \
            settings.dynamic_flow_information, False
        user_stmt = self._parser.user_stmt()
        definitions, search_name = self._goto(add_import_name=True)
        if isinstance(user_stmt, pr.Statement):
            c = user_stmt.expression_list()[0]
            if not isinstance(c, unicode) and self._pos < c.start_pos:
                # the search_name might be before `=`
                definitions = [v for v in user_stmt.get_set_vars()
                               if unicode(v.names[-1]) == search_name]
        if not isinstance(user_stmt, pr.Import):
            # import case is looked at with add_import_name option
            definitions = usages.usages_add_import_modules(self._evaluator, definitions, search_name)

        module = set([d.get_parent_until() for d in definitions])
        module.add(self._parser.module())
        names = usages.usages(self._evaluator, definitions, search_name, module)

        for d in set(definitions):
            if isinstance(d, pr.Module):
                names.append(usages.Usage(self._evaluator, d, d))
            elif isinstance(d, er.Instance):
                # Instances can be ignored, because they have been created by
                # ``__getattr__``.
                pass
            else:
                names.append(usages.Usage(self._evaluator, d.names[-1], d))

        settings.dynamic_flow_information = temp
        return helpers.sorted_definitions(set(names))

    def call_signatures(self):
        """
        Return the function object of the call you're currently in.

        E.g. if the cursor is here::

            abs(# <-- cursor is here

        This would return the ``abs`` function. On the other hand::

            abs()# <-- cursor is here

        This would return ``None``.

        :rtype: list of :class:`classes.CallSignature`
        """

        user_stmt = self._parser.user_stmt_with_whitespace()
        call, index = helpers.func_call_and_param_index(user_stmt, self._pos)
        if call is None:
            return []

        with common.scale_speed_settings(settings.scale_call_signatures):
            _callable = lambda: self._evaluator.eval_call(call)
            origins = cache.cache_call_signatures(_callable, user_stmt)
        debug.speed('func_call followed')

        return [classes.CallSignature(self._evaluator, o, call, index) for o in origins
                if o.isinstance(er.Function, er.Instance, er.Class)
                or isinstance(o, compiled.CompiledObject) and o.type() != 'module']
示例#36
0
 def paths(src):
     grammar = load_grammar()
     stmt = ParserWithRecovery(grammar, unicode(src)).module.statements[0]
     return set(sys_path._paths_from_assignment(Evaluator(grammar), stmt))
class Script(object):
    """
    A Script is the base for completions, goto or whatever you want to do with
    |jedi|.

    You can either use the ``source`` parameter or ``path`` to read a file.
    Usually you're going to want to use both of them (in an editor).

    The script might be analyzed in a different ``sys.path`` than |jedi|:

    - if `sys_path` parameter is not ``None``, it will be used as ``sys.path``
      for the script;

    - if `sys_path` parameter is ``None`` and ``VIRTUAL_ENV`` environment
      variable is defined, ``sys.path`` for the specified environment will be
      guessed (see :func:`jedi.evaluate.sys_path.get_venv_path`) and used for
      the script;

    - otherwise ``sys.path`` will match that of |jedi|.

    :param source: The source code of the current file, separated by newlines.
    :type source: str
    :param line: The line to perform actions on (starting with 1).
    :type line: int
    :param column: The column of the cursor (starting with 0).
    :type column: int
    :param path: The path of the file in the file system, or ``''`` if
        it hasn't been saved yet.
    :type path: str or None
    :param encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    :param source_encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    :param sys_path: ``sys.path`` to use during analysis of the script
    :type sys_path: list

    """
    def __init__(self,
                 source=None,
                 line=None,
                 column=None,
                 path=None,
                 encoding='utf-8',
                 sys_path=None):
        self._orig_path = path
        # An empty path (also empty string) should always result in no path.
        self.path = os.path.abspath(path) if path else None

        if source is None:
            # TODO add a better warning than the traceback!
            with open(path, 'rb') as f:
                source = f.read()

        # TODO do we really want that?
        self._source = python_bytes_to_unicode(source,
                                               encoding,
                                               errors='replace')
        self._code_lines = split_lines(self._source)
        line = max(len(self._code_lines), 1) if line is None else line
        if not (0 < line <= len(self._code_lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(self._code_lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column
        self._path = path

        cache.clear_time_caches()
        debug.reset_time()

        # Load the Python grammar of the current interpreter.
        self._grammar = parso.load_grammar()
        if sys_path is None:
            venv = os.getenv('VIRTUAL_ENV')
            if venv:
                sys_path = list(get_venv_path(venv))
        self._evaluator = Evaluator(self._grammar, sys_path=sys_path)
        debug.speed('init')

    @cache.memoize_method
    def _get_module_node(self):
        return self._grammar.parse(
            code=self._source,
            path=self.path,
            cache=
            False,  # No disk cache, because the current script often changes.
            diff_cache=True,
            cache_path=settings.cache_directory)

    @cache.memoize_method
    def _get_module(self):
        module = er.ModuleContext(self._evaluator, self._get_module_node(),
                                  self.path)
        if self.path is not None:
            name = dotted_path_in_sys_path(self._evaluator.sys_path, self.path)
            if name is not None:
                imports.add_module(self._evaluator, name, module)
        return module

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, repr(self._orig_path))

    def completions(self):
        """
        Return :class:`classes.Completion` objects. Those objects contain
        information about the completions, more than just names.

        :return: Completion objects, sorted by name and __ comes last.
        :rtype: list of :class:`classes.Completion`
        """
        debug.speed('completions start')
        completion = Completion(self._evaluator, self._get_module(),
                                self._code_lines, self._pos,
                                self.call_signatures)
        completions = completion.completions()
        debug.speed('completions end')
        return completions

    def goto_definitions(self):
        """
        Return the definitions of a the path under the cursor.  goto function!
        This follows complicated paths and returns the end, not the first
        definition. The big difference between :meth:`goto_assignments` and
        :meth:`goto_definitions` is that :meth:`goto_assignments` doesn't
        follow imports and statements. Multiple objects may be returned,
        because Python itself is a dynamic language, which means depending on
        an option you can have two different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        module_node = self._get_module_node()
        leaf = module_node.get_name_of_position(self._pos)
        if leaf is None:
            leaf = module_node.get_leaf_for_position(self._pos)
            if leaf is None:
                return []

        context = self._evaluator.create_context(self._get_module(), leaf)
        definitions = helpers.evaluate_goto_definition(self._evaluator,
                                                       context, leaf)

        names = [s.name for s in definitions]
        defs = [classes.Definition(self._evaluator, name) for name in names]
        # The additional set here allows the definitions to become unique in an
        # API sense. In the internals we want to separate more things than in
        # the API.
        return helpers.sorted_definitions(set(defs))

    def goto_assignments(self, follow_imports=False):
        """
        Return the first definition found, while optionally following imports.
        Multiple objects may be returned, because Python itself is a
        dynamic language, which means depending on an option you can have two
        different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        def filter_follow_imports(names, check):
            for name in names:
                if check(name):
                    for result in filter_follow_imports(name.goto(), check):
                        yield result
                else:
                    yield name

        names = self._goto()
        if follow_imports:

            def check(name):
                if isinstance(name, er.ModuleName):
                    return False
                return name.api_type == 'module'
        else:

            def check(name):
                return isinstance(name, imports.SubModuleName)

        names = filter_follow_imports(names, check)

        defs = [classes.Definition(self._evaluator, d) for d in set(names)]
        return helpers.sorted_definitions(defs)

    def _goto(self):
        """
        Used for goto_assignments and usages.
        """
        name = self._get_module_node().get_name_of_position(self._pos)
        if name is None:
            return []
        context = self._evaluator.create_context(self._get_module(), name)
        return list(self._evaluator.goto(context, name))

    def usages(self, additional_module_paths=()):
        """
        Return :class:`classes.Definition` objects, which contain all
        names that point to the definition of the name under the cursor. This
        is very useful for refactoring (renaming), or to show all usages of a
        variable.

        .. todo:: Implement additional_module_paths

        :rtype: list of :class:`classes.Definition`
        """
        temp, settings.dynamic_flow_information = \
            settings.dynamic_flow_information, False
        try:
            module_node = self._get_module_node()
            user_stmt = get_statement_of_position(module_node, self._pos)
            definition_names = self._goto()
            if not definition_names and isinstance(user_stmt, tree.Import):
                # For not defined imports (goto doesn't find something, we take
                # the name as a definition. This is enough, because every name
                # points to it.
                name = user_stmt.get_name_of_position(self._pos)
                if name is None:
                    # Must be syntax
                    return []
                definition_names = [
                    TreeNameDefinition(self._get_module(), name)
                ]

            if not definition_names:
                # Without a definition for a name we cannot find references.
                return []

            definition_names = usages.resolve_potential_imports(
                self._evaluator, definition_names)

            modules = set([d.get_root_context() for d in definition_names])
            modules.add(self._get_module())
            definitions = usages.usages(self._evaluator, definition_names,
                                        modules)
        finally:
            settings.dynamic_flow_information = temp

        return helpers.sorted_definitions(set(definitions))

    def call_signatures(self):
        """
        Return the function object of the call you're currently in.

        E.g. if the cursor is here::

            abs(# <-- cursor is here

        This would return the ``abs`` function. On the other hand::

            abs()# <-- cursor is here

        This would return an empty list..

        :rtype: list of :class:`classes.CallSignature`
        """
        call_signature_details = \
            helpers.get_call_signature_details(self._get_module_node(), self._pos)
        if call_signature_details is None:
            return []

        context = self._evaluator.create_context(
            self._get_module(), call_signature_details.bracket_leaf)
        definitions = helpers.cache_call_signatures(
            self._evaluator, context, call_signature_details.bracket_leaf,
            self._code_lines, self._pos)
        debug.speed('func_call followed')

        return [
            classes.CallSignature(
                self._evaluator, d.name,
                call_signature_details.bracket_leaf.start_pos,
                call_signature_details.call_index,
                call_signature_details.keyword_name_str) for d in definitions
            if hasattr(d, 'py__call__')
        ]

    def _analysis(self):
        self._evaluator.is_analysis = True
        module_node = self._get_module_node()
        self._evaluator.analysis_modules = [module_node]
        try:
            for node in get_executable_nodes(module_node):
                context = self._get_module().create_context(node)
                if node.type in ('funcdef', 'classdef'):
                    # TODO This is stupid, should be private
                    from jedi.evaluate.finder import _name_to_types
                    # Resolve the decorators.
                    _name_to_types(self._evaluator, context, node.children[1])
                elif isinstance(node, tree.Import):
                    import_names = set(node.get_defined_names())
                    if node.is_nested():
                        import_names |= set(path[-1]
                                            for path in node.get_paths())
                    for n in import_names:
                        imports.infer_import(context, n)
                elif node.type == 'expr_stmt':
                    types = context.eval_node(node)
                    for testlist in node.children[:-1:2]:
                        # Iterate tuples.
                        unpack_tuple_to_dict(context, types, testlist)
                else:
                    if node.type == 'name':
                        defs = self._evaluator.goto_definitions(context, node)
                    else:
                        defs = evaluate_call_of_leaf(context, node)
                    try_iter_content(defs)
                self._evaluator.reset_recursion_limitations()

            ana = [a for a in self._evaluator.analysis if self.path == a.path]
            return sorted(set(ana), key=lambda x: x.line)
        finally:
            self._evaluator.is_analysis = False
示例#38
0
class Script(object):
    """
    A Script is the base for completions, goto or whatever you want to do with
    |jedi|.

    You can either use the ``source`` parameter or ``path`` to read a file.
    Usually you're going to want to use both of them (in an editor).

    :param source: The source code of the current file, separated by newlines.
    :type source: str
    :param line: The line to perform actions on (starting with 1).
    :type line: int
    :param col: The column of the cursor (starting with 0).
    :type col: int
    :param path: The path of the file in the file system, or ``''`` if
        it hasn't been saved yet.
    :type path: str or None
    :param encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    :param source_encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    """
    def __init__(self,
                 source=None,
                 line=None,
                 column=None,
                 path=None,
                 encoding='utf-8',
                 source_path=None,
                 source_encoding=None):
        if source_path is not None:
            warnings.warn("Use path instead of source_path.",
                          DeprecationWarning)
            path = source_path
        if source_encoding is not None:
            warnings.warn("Use encoding instead of source_encoding.",
                          DeprecationWarning)
            encoding = source_encoding

        self._orig_path = path
        self.path = None if path is None else os.path.abspath(path)

        if source is None:
            with open(path) as f:
                source = f.read()

        self.source = common.source_to_unicode(source, encoding)
        lines = common.splitlines(self.source)
        line = max(len(lines), 1) if line is None else line
        if not (0 < line <= len(lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column

        cache.clear_time_caches()
        debug.reset_time()
        self._grammar = load_grammar('grammar%s.%s' % sys.version_info[:2])
        self._user_context = UserContext(self.source, self._pos)
        self._parser = UserContextParser(self._grammar, self.source, path,
                                         self._pos, self._user_context,
                                         self._parsed_callback)
        self._evaluator = Evaluator(self._grammar)
        debug.speed('init')

    def _parsed_callback(self, parser):
        module = self._evaluator.wrap(parser.module)
        imports.add_module(self._evaluator, unicode(module.name), module)

    @property
    def source_path(self):
        """
        .. deprecated:: 0.7.0
           Use :attr:`.path` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use path instead of source_path.", DeprecationWarning)
        return self.path

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, repr(self._orig_path))

    def completions(self):
        """
        Return :class:`classes.Completion` objects. Those objects contain
        information about the completions, more than just names.

        :return: Completion objects, sorted by name and __ comes last.
        :rtype: list of :class:`classes.Completion`
        """
        def get_completions(user_stmt, bs):
            # TODO this closure is ugly. it also doesn't work with
            # simple_complete (used for Interpreter), somehow redo.
            module = self._evaluator.wrap(self._parser.module())
            names, level, only_modules, unfinished_dotted = \
                helpers.check_error_statements(module, self._pos)
            completion_names = []
            if names is not None:
                imp_names = tuple(
                    str(n) for n in names if n.end_pos < self._pos)
                i = imports.Importer(self._evaluator, imp_names, module, level)
                completion_names = i.completion_names(self._evaluator,
                                                      only_modules)

            # TODO this paragraph is necessary, but not sure it works.
            context = self._user_context.get_context()
            if not next(context).startswith('.'):  # skip the path
                if next(context) == 'from':
                    # completion is just "import" if before stands from ..
                    if unfinished_dotted:
                        return completion_names
                    else:
                        return set([keywords.keyword('import').name])

            if isinstance(user_stmt, tree.Import):
                module = self._parser.module()
                completion_names += imports.completion_names(
                    self._evaluator, user_stmt, self._pos)
                return completion_names

            if names is None and not isinstance(user_stmt, tree.Import):
                if not path and not dot:
                    # add keywords
                    completion_names += keywords.completion_names(
                        self._evaluator, user_stmt, self._pos, module)
                    # TODO delete? We should search for valid parser
                    # transformations.
                completion_names += self._simple_complete(path, dot, like)
            return completion_names

        debug.speed('completions start')
        path = self._user_context.get_path_until_cursor()
        # Dots following an int are not the start of a completion but a float
        # literal.
        if re.search(r'^\d\.$', path):
            return []
        path, dot, like = helpers.completion_parts(path)

        user_stmt = self._parser.user_stmt_with_whitespace()

        b = compiled.builtin
        completion_names = get_completions(user_stmt, b)

        if not dot:
            # add named params
            for call_sig in self.call_signatures():
                # Allow protected access, because it's a public API.
                module = call_sig._name.get_parent_until()
                # Compiled modules typically don't allow keyword arguments.
                if not isinstance(module, compiled.CompiledObject):
                    for p in call_sig.params:
                        # Allow access on _definition here, because it's a
                        # public API and we don't want to make the internal
                        # Name object public.
                        if p._definition.stars == 0:  # no *args/**kwargs
                            completion_names.append(p._name)

        needs_dot = not dot and path

        comps = []
        comp_dct = {}
        for c in set(completion_names):
            n = str(c)
            if settings.case_insensitive_completion \
                    and n.lower().startswith(like.lower()) \
                    or n.startswith(like):
                if isinstance(c.parent, (tree.Function, tree.Class)):
                    # TODO I think this is a hack. It should be an
                    #   er.Function/er.Class before that.
                    c = self._evaluator.wrap(c.parent).name
                new = classes.Completion(self._evaluator, c, needs_dot,
                                         len(like))
                k = (new.name, new.complete)  # key
                if k in comp_dct and settings.no_completion_duplicates:
                    comp_dct[k]._same_name_completions.append(new)
                else:
                    comp_dct[k] = new
                    comps.append(new)

        debug.speed('completions end')

        return sorted(
            comps,
            key=lambda x:
            (x.name.startswith('__'), x.name.startswith('_'), x.name.lower()))

    def _simple_complete(self, path, dot, like):
        if not path and not dot:
            scope = self._parser.user_scope()
            if not scope.is_scope():  # Might be a flow (if/while/etc).
                scope = scope.get_parent_scope()
            names_dicts = global_names_dict_generator(
                self._evaluator, self._evaluator.wrap(scope), self._pos)
            completion_names = []
            for names_dict, pos in names_dicts:
                names = list(chain.from_iterable(names_dict.values()))
                if not names:
                    continue
                completion_names += filter_definition_names(
                    names, self._parser.user_stmt(), pos)
        elif self._get_under_cursor_stmt(path) is None:
            return []
        else:
            scopes = list(self._prepare_goto(path, True))
            completion_names = []
            debug.dbg('possible completion scopes: %s', scopes)
            for s in scopes:
                names = []
                for names_dict in s.names_dicts(search_global=False):
                    names += chain.from_iterable(names_dict.values())

                completion_names += filter_definition_names(
                    names, self._parser.user_stmt())
        return completion_names

    def _prepare_goto(self, goto_path, is_completion=False):
        """
        Base for completions/goto. Basically it returns the resolved scopes
        under cursor.
        """
        debug.dbg('start: %s in %s', goto_path, self._parser.user_scope())

        user_stmt = self._parser.user_stmt_with_whitespace()
        if not user_stmt and len(goto_path.split('\n')) > 1:
            # If the user_stmt is not defined and the goto_path is multi line,
            # something's strange. Most probably the backwards tokenizer
            # matched to much.
            return []

        if isinstance(user_stmt, tree.Import):
            i, _ = helpers.get_on_import_stmt(self._evaluator,
                                              self._user_context, user_stmt,
                                              is_completion)
            if i is None:
                return []
            scopes = [i]
        else:
            # just parse one statement, take it and evaluate it
            eval_stmt = self._get_under_cursor_stmt(goto_path)
            if eval_stmt is None:
                return []

            module = self._evaluator.wrap(self._parser.module())
            names, level, _, _ = helpers.check_error_statements(
                module, self._pos)
            if names:
                names = [str(n) for n in names]
                i = imports.Importer(self._evaluator, names, module, level)
                return i.follow()

            scopes = self._evaluator.eval_element(eval_stmt)

        return scopes

    @memoize_default()
    def _get_under_cursor_stmt(self, cursor_txt, start_pos=None):
        tokenizer = source_tokens(cursor_txt)
        r = Parser(self._grammar, cursor_txt, tokenizer=tokenizer)
        try:
            # Take the last statement available that is not an endmarker.
            # And because it's a simple_stmt, we need to get the first child.
            stmt = r.module.children[-2].children[0]
        except (AttributeError, IndexError):
            return None

        user_stmt = self._parser.user_stmt()
        if user_stmt is None:
            # Set the start_pos to a pseudo position, that doesn't exist but
            # works perfectly well (for both completions in docstrings and
            # statements).
            pos = start_pos or self._pos
        else:
            pos = user_stmt.start_pos

        stmt.move(pos[0] - 1, pos[1])  # Moving the offset.
        stmt.parent = self._parser.user_scope()
        return stmt

    def goto_definitions(self):
        """
        Return the definitions of a the path under the cursor.  goto function!
        This follows complicated paths and returns the end, not the first
        definition. The big difference between :meth:`goto_assignments` and
        :meth:`goto_definitions` is that :meth:`goto_assignments` doesn't
        follow imports and statements. Multiple objects may be returned,
        because Python itself is a dynamic language, which means depending on
        an option you can have two different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        def resolve_import_paths(scopes):
            for s in scopes.copy():
                if isinstance(s, imports.ImportWrapper):
                    scopes.remove(s)
                    scopes.update(resolve_import_paths(set(s.follow())))
            return scopes

        goto_path = self._user_context.get_path_under_cursor()
        context = self._user_context.get_context()
        definitions = set()
        if next(context) in ('class', 'def'):
            definitions = set(
                [self._evaluator.wrap(self._parser.user_scope())])
        else:
            # Fetch definition of callee, if there's no path otherwise.
            if not goto_path:
                definitions = set(signature._definition
                                  for signature in self.call_signatures())

        if re.match('\w[\w\d_]*$', goto_path) and not definitions:
            user_stmt = self._parser.user_stmt()
            if user_stmt is not None and user_stmt.type == 'expr_stmt':
                for name in user_stmt.get_defined_names():
                    if name.start_pos <= self._pos <= name.end_pos:
                        # TODO scaning for a name and then using it should be
                        # the default.
                        definitions = set(
                            self._evaluator.goto_definition(name))

        if not definitions and goto_path:
            definitions = set(self._prepare_goto(goto_path))

        definitions = resolve_import_paths(definitions)
        names = [s.name for s in definitions]
        defs = [classes.Definition(self._evaluator, name) for name in names]
        return helpers.sorted_definitions(set(defs))

    def goto_assignments(self):
        """
        Return the first definition found. Imports and statements aren't
        followed. Multiple objects may be returned, because Python itself is a
        dynamic language, which means depending on an option you can have two
        different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        results = self._goto()
        d = [classes.Definition(self._evaluator, d) for d in set(results)]
        return helpers.sorted_definitions(d)

    def _goto(self, add_import_name=False):
        """
        Used for goto_assignments and usages.

        :param add_import_name: Add the the name (if import) to the result.
        """
        def follow_inexistent_imports(defs):
            """ Imports can be generated, e.g. following
            `multiprocessing.dummy` generates an import dummy in the
            multiprocessing module. The Import doesn't exist -> follow.
            """
            definitions = set(defs)
            for d in defs:
                if isinstance(d.parent, tree.Import) \
                        and d.start_pos == (0, 0):
                    i = imports.ImportWrapper(self._evaluator,
                                              d.parent).follow(is_goto=True)
                    definitions.remove(d)
                    definitions |= follow_inexistent_imports(i)
            return definitions

        goto_path = self._user_context.get_path_under_cursor()
        context = self._user_context.get_context()
        user_stmt = self._parser.user_stmt()
        user_scope = self._parser.user_scope()

        stmt = self._get_under_cursor_stmt(goto_path)
        if stmt is None:
            return []

        if user_scope is None:
            last_name = None
        else:
            # Try to use the parser if possible.
            last_name = user_scope.name_for_position(self._pos)

        if last_name is None:
            last_name = stmt
            while not isinstance(last_name, tree.Name):
                try:
                    last_name = last_name.children[-1]
                except AttributeError:
                    # Doesn't have a name in it.
                    return []

        if next(context) in ('class', 'def'):
            # The cursor is on a class/function name.
            user_scope = self._parser.user_scope()
            definitions = set([user_scope.name])
        elif isinstance(user_stmt, tree.Import):
            s, name = helpers.get_on_import_stmt(self._evaluator,
                                                 self._user_context, user_stmt)

            definitions = self._evaluator.goto(name)
        else:
            # The Evaluator.goto function checks for definitions, but since we
            # use a reverse tokenizer, we have new name_part objects, so we
            # have to check the user_stmt here for positions.
            if isinstance(user_stmt, tree.ExprStmt) \
                    and isinstance(last_name.parent, tree.ExprStmt):
                for name in user_stmt.get_defined_names():
                    if name.start_pos <= self._pos <= name.end_pos:
                        return [name]

            defs = self._evaluator.goto(last_name)
            definitions = follow_inexistent_imports(defs)
        return definitions

    def usages(self, additional_module_paths=()):
        """
        Return :class:`classes.Definition` objects, which contain all
        names that point to the definition of the name under the cursor. This
        is very useful for refactoring (renaming), or to show all usages of a
        variable.

        .. todo:: Implement additional_module_paths

        :rtype: list of :class:`classes.Definition`
        """
        temp, settings.dynamic_flow_information = \
            settings.dynamic_flow_information, False
        try:
            user_stmt = self._parser.user_stmt()
            definitions = self._goto(add_import_name=True)
            if not definitions and isinstance(user_stmt, tree.Import):
                # For not defined imports (goto doesn't find something, we take
                # the name as a definition. This is enough, because every name
                # points to it.
                name = user_stmt.name_for_position(self._pos)
                if name is None:
                    # Must be syntax
                    return []
                definitions = [name]

            if not definitions:
                # Without a definition for a name we cannot find references.
                return []

            if not isinstance(user_stmt, tree.Import):
                # import case is looked at with add_import_name option
                definitions = usages.usages_add_import_modules(
                    self._evaluator, definitions)

            module = set([d.get_parent_until() for d in definitions])
            module.add(self._parser.module())
            names = usages.usages(self._evaluator, definitions, module)

            for d in set(definitions):
                names.append(classes.Definition(self._evaluator, d))
        finally:
            settings.dynamic_flow_information = temp

        return helpers.sorted_definitions(set(names))

    def call_signatures(self):
        """
        Return the function object of the call you're currently in.

        E.g. if the cursor is here::

            abs(# <-- cursor is here

        This would return the ``abs`` function. On the other hand::

            abs()# <-- cursor is here

        This would return ``None``.

        :rtype: list of :class:`classes.CallSignature`
        """
        call_txt, call_index, key_name, start_pos = self._user_context.call_signature(
        )
        if call_txt is None:
            return []

        stmt = self._get_under_cursor_stmt(call_txt, start_pos)
        if stmt is None:
            return []

        with common.scale_speed_settings(settings.scale_call_signatures):
            origins = cache.cache_call_signatures(self._evaluator, stmt,
                                                  self.source, self._pos)
        debug.speed('func_call followed')

        return [
            classes.CallSignature(self._evaluator, o.name, stmt, call_index,
                                  key_name) for o in origins
            if hasattr(o, 'py__call__')
        ]

    def _analysis(self):
        def check_types(types):
            for typ in types:
                try:
                    f = typ.iter_content
                except AttributeError:
                    pass
                else:
                    check_types(f())

        #statements = set(chain(*self._parser.module().used_names.values()))
        nodes, imp_names, decorated_funcs = \
            analysis.get_module_statements(self._parser.module())
        # Sort the statements so that the results are reproducible.
        for n in imp_names:
            imports.ImportWrapper(self._evaluator, n).follow()
        for node in sorted(nodes, key=lambda obj: obj.start_pos):
            check_types(self._evaluator.eval_element(node))

        for dec_func in decorated_funcs:
            er.Function(self._evaluator, dec_func).get_decorated_func()

        ana = [a for a in self._evaluator.analysis if self.path == a.path]
        return sorted(set(ana), key=lambda x: x.line)
示例#39
0
class Script(object):
    """
    A Script is the base for completions, goto or whatever you want to do with
    |jedi|.

    You can either use the ``source`` parameter or ``path`` to read a file.
    Usually you're going to want to use both of them (in an editor).

    The script might be analyzed in a different ``sys.path`` than |jedi|:

    - if `sys_path` parameter is not ``None``, it will be used as ``sys.path``
      for the script;

    - if `sys_path` parameter is ``None`` and ``VIRTUAL_ENV`` environment
      variable is defined, ``sys.path`` for the specified environment will be
      guessed (see :func:`jedi.evaluate.sys_path.get_venv_path`) and used for
      the script;

    - otherwise ``sys.path`` will match that of |jedi|.

    :param source: The source code of the current file, separated by newlines.
    :type source: str
    :param line: The line to perform actions on (starting with 1).
    :type line: int
    :param column: The column of the cursor (starting with 0).
    :type column: int
    :param path: The path of the file in the file system, or ``''`` if
        it hasn't been saved yet.
    :type path: str or None
    :param encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    :param source_encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    :param sys_path: ``sys.path`` to use during analysis of the script
    :type sys_path: list

    """
    def __init__(self, source=None, line=None, column=None, path=None,
                 encoding='utf-8', source_path=None, source_encoding=None,
                 sys_path=None):
        if source_path is not None:
            warnings.warn("Use path instead of source_path.", DeprecationWarning)
            path = source_path
        if source_encoding is not None:
            warnings.warn("Use encoding instead of source_encoding.", DeprecationWarning)
            encoding = source_encoding

        self._orig_path = path
        # An empty path (also empty string) should always result in no path.
        self.path = os.path.abspath(path) if path else None

        if source is None:
            # TODO add a better warning than the traceback!
            with open(path, 'rb') as f:
                source = f.read()

        self._source = common.source_to_unicode(source, encoding)
        self._code_lines = common.splitlines(self._source)
        line = max(len(self._code_lines), 1) if line is None else line
        if not (0 < line <= len(self._code_lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(self._code_lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column
        self._path = path

        cache.clear_time_caches()
        debug.reset_time()
        self._grammar = load_grammar(version='%s.%s' % sys.version_info[:2])
        if sys_path is None:
            venv = os.getenv('VIRTUAL_ENV')
            if venv:
                sys_path = list(get_venv_path(venv))
        self._evaluator = Evaluator(self._grammar, sys_path=sys_path)
        debug.speed('init')

    def _get_module(self):
        cache.invalidate_star_import_cache(self._path)
        parser = FastParser(self._grammar, self._source, self.path)
        save_parser(self.path, parser, pickling=False)

        module = self._evaluator.wrap(parser.module)
        imports.add_module(self._evaluator, unicode(module.name), module)
        return parser.module

    @property
    def source_path(self):
        """
        .. deprecated:: 0.7.0
           Use :attr:`.path` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use path instead of source_path.", DeprecationWarning)
        return self.path

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, repr(self._orig_path))

    def completions(self):
        """
        Return :class:`classes.Completion` objects. Those objects contain
        information about the completions, more than just names.

        :return: Completion objects, sorted by name and __ comes last.
        :rtype: list of :class:`classes.Completion`
        """
        debug.speed('completions start')
        completion = Completion(
            self._evaluator, self._get_module(), self._code_lines,
            self._pos, self.call_signatures
        )
        completions = completion.completions()
        debug.speed('completions end')
        return completions

    def goto_definitions(self):
        """
        Return the definitions of a the path under the cursor.  goto function!
        This follows complicated paths and returns the end, not the first
        definition. The big difference between :meth:`goto_assignments` and
        :meth:`goto_definitions` is that :meth:`goto_assignments` doesn't
        follow imports and statements. Multiple objects may be returned,
        because Python itself is a dynamic language, which means depending on
        an option you can have two different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        leaf = self._get_module().name_for_position(self._pos)
        if leaf is None:
            leaf = self._get_module().get_leaf_for_position(self._pos)
            if leaf is None:
                return []
        definitions = helpers.evaluate_goto_definition(self._evaluator, leaf)

        names = [s.name for s in definitions]
        defs = [classes.Definition(self._evaluator, name) for name in names]
        # The additional set here allows the definitions to become unique in an
        # API sense. In the internals we want to separate more things than in
        # the API.
        return helpers.sorted_definitions(set(defs))

    def goto_assignments(self, follow_imports=False):
        """
        Return the first definition found, while optionally following imports.
        Multiple objects may be returned, because Python itself is a
        dynamic language, which means depending on an option you can have two
        different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        def filter_follow_imports(names):
            for name in names:
                definition = name.get_definition()
                if definition.type in ('import_name', 'import_from'):
                    imp = imports.ImportWrapper(self._evaluator, name)
                    for name in filter_follow_imports(imp.follow(is_goto=True)):
                        yield name
                else:
                    yield name

        names = self._goto()
        if follow_imports:
            names = filter_follow_imports(names)

        defs = [classes.Definition(self._evaluator, d) for d in set(names)]
        return helpers.sorted_definitions(defs)

    def _goto(self):
        """
        Used for goto_assignments and usages.
        """
        name = self._get_module().name_for_position(self._pos)
        if name is None:
            return []
        return list(self._evaluator.goto(name))

    def usages(self, additional_module_paths=()):
        """
        Return :class:`classes.Definition` objects, which contain all
        names that point to the definition of the name under the cursor. This
        is very useful for refactoring (renaming), or to show all usages of a
        variable.

        .. todo:: Implement additional_module_paths

        :rtype: list of :class:`classes.Definition`
        """
        temp, settings.dynamic_flow_information = \
            settings.dynamic_flow_information, False
        try:
            user_stmt = self._get_module().get_statement_for_position(self._pos)
            definitions = self._goto()
            if not definitions and isinstance(user_stmt, tree.Import):
                # For not defined imports (goto doesn't find something, we take
                # the name as a definition. This is enough, because every name
                # points to it.
                name = user_stmt.name_for_position(self._pos)
                if name is None:
                    # Must be syntax
                    return []
                definitions = [name]

            if not definitions:
                # Without a definition for a name we cannot find references.
                return []

            if not isinstance(user_stmt, tree.Import):
                # import case is looked at with add_import_name option
                definitions = usages.usages_add_import_modules(self._evaluator,
                                                               definitions)

            module = set([d.get_parent_until() for d in definitions])
            module.add(self._get_module())
            names = usages.usages(self._evaluator, definitions, module)

            for d in set(definitions):
                names.append(classes.Definition(self._evaluator, d))
        finally:
            settings.dynamic_flow_information = temp

        return helpers.sorted_definitions(set(names))

    def call_signatures(self):
        """
        Return the function object of the call you're currently in.

        E.g. if the cursor is here::

            abs(# <-- cursor is here

        This would return the ``abs`` function. On the other hand::

            abs()# <-- cursor is here

        This would return an empty list..

        :rtype: list of :class:`classes.CallSignature`
        """
        call_signature_details = \
            helpers.get_call_signature_details(self._get_module(), self._pos)
        if call_signature_details is None:
            return []

        with common.scale_speed_settings(settings.scale_call_signatures):
            definitions = helpers.cache_call_signatures(
                self._evaluator,
                call_signature_details.bracket_leaf,
                self._code_lines,
                self._pos
            )
        debug.speed('func_call followed')

        return [classes.CallSignature(self._evaluator, d.name,
                                      call_signature_details.bracket_leaf.start_pos,
                                      call_signature_details.call_index,
                                      call_signature_details.keyword_name_str)
                for d in definitions if hasattr(d, 'py__call__')]

    def _analysis(self):
        self._evaluator.is_analysis = True
        self._evaluator.analysis_modules = [self._get_module()]
        try:
            for node in self._get_module().nodes_to_execute():
                if node.type in ('funcdef', 'classdef'):
                    if node.type == 'classdef':
                        continue
                        raise NotImplementedError
                    er.Function(self._evaluator, node).get_decorated_func()
                elif isinstance(node, tree.Import):
                    import_names = set(node.get_defined_names())
                    if node.is_nested():
                        import_names |= set(path[-1] for path in node.paths())
                    for n in import_names:
                        imports.ImportWrapper(self._evaluator, n).follow()
                elif node.type == 'expr_stmt':
                    types = self._evaluator.eval_element(node)
                    for testlist in node.children[:-1:2]:
                        # Iterate tuples.
                        unpack_tuple_to_dict(self._evaluator, types, testlist)
                else:
                    try_iter_content(self._evaluator.goto_definitions(node))
                self._evaluator.reset_recursion_limitations()

            ana = [a for a in self._evaluator.analysis if self.path == a.path]
            return sorted(set(ana), key=lambda x: x.line)
        finally:
            self._evaluator.is_analysis = False
示例#40
0
class Script(object):
    """
    A Script is the base for completions, goto or whatever you want to do with
    |jedi|.

    You can either use the ``source`` parameter or ``path`` to read a file.
    Usually you're going to want to use both of them (in an editor).

    The script might be analyzed in a different ``sys.path`` than |jedi|:

    - if `sys_path` parameter is not ``None``, it will be used as ``sys.path``
      for the script;

    - if `sys_path` parameter is ``None`` and ``VIRTUAL_ENV`` environment
      variable is defined, ``sys.path`` for the specified environment will be
      guessed (see :func:`jedi.evaluate.sys_path.get_venv_path`) and used for
      the script;

    - otherwise ``sys.path`` will match that of |jedi|.

    :param source: The source code of the current file, separated by newlines.
    :type source: str
    :param line: The line to perform actions on (starting with 1).
    :type line: int
    :param column: The column of the cursor (starting with 0).
    :type column: int
    :param path: The path of the file in the file system, or ``''`` if
        it hasn't been saved yet.
    :type path: str or None
    :param encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    :param sys_path: ``sys.path`` to use during analysis of the script
    :type sys_path: list
    :param environment: TODO
    :type sys_path: Environment
    """
    def __init__(self, source=None, line=None, column=None, path=None,
                 encoding='utf-8', sys_path=None, environment=None):
        self._orig_path = path
        # An empty path (also empty string) should always result in no path.
        self.path = os.path.abspath(path) if path else None

        if source is None:
            # TODO add a better warning than the traceback!
            with open(path, 'rb') as f:
                source = f.read()

        # Load the Python grammar of the current interpreter.
        self._grammar = parso.load_grammar()

        if sys_path is not None and not is_py3:
            sys_path = list(map(force_unicode, sys_path))

        # Load the Python grammar of the current interpreter.
        project = get_default_project(
            os.path.dirname(self.path)if path else os.getcwd()
        )
        # TODO deprecate and remove sys_path from the Script API.
        if sys_path is not None:
            project._sys_path = sys_path
        self._evaluator = Evaluator(
            project, environment=environment, script_path=self.path
        )
        self._project = project
        debug.speed('init')
        self._module_node, source = self._evaluator.parse_and_get_code(
            code=source,
            path=self.path,
            encoding=encoding,
            cache=False,  # No disk cache, because the current script often changes.
            diff_cache=settings.fast_parser,
            cache_path=settings.cache_directory,
        )
        debug.speed('parsed')
        self._code_lines = parso.split_lines(source, keepends=True)
        self._code = source
        line = max(len(self._code_lines), 1) if line is None else line
        if not (0 < line <= len(self._code_lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_string = self._code_lines[line - 1]
        line_len = len(line_string)
        if line_string.endswith('\r\n'):
            line_len -= 1
        if line_string.endswith('\n'):
            line_len -= 1

        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter (%d) is not in a valid range '
                             '(0-%d) for line %d (%r).' % (
                                 column, line_len, line, line_string))
        self._pos = line, column
        self._path = path

        cache.clear_time_caches()
        debug.reset_time()

    def _get_module(self):
        name = '__main__'
        if self.path is not None:
            import_names = dotted_path_in_sys_path(self._evaluator.get_sys_path(), self.path)
            if import_names is not None:
                name = '.'.join(import_names)

        module = ModuleContext(
            self._evaluator, self._module_node, self.path,
            code_lines=self._code_lines
        )
        imports.add_module_to_cache(self._evaluator, name, module)
        return module

    def __repr__(self):
        return '<%s: %s %r>' % (
            self.__class__.__name__,
            repr(self._orig_path),
            self._evaluator.environment,
        )

    def completions(self):
        """
        Return :class:`classes.Completion` objects. Those objects contain
        information about the completions, more than just names.

        :return: Completion objects, sorted by name and __ comes last.
        :rtype: list of :class:`classes.Completion`
        """
        debug.speed('completions start')
        completion = Completion(
            self._evaluator, self._get_module(), self._code_lines,
            self._pos, self.call_signatures
        )
        completions = completion.completions()

        def iter_import_completions():
            for c in completions:
                tree_name = c._name.tree_name
                if tree_name is None:
                    continue
                definition = tree_name.get_definition()
                if definition is not None \
                        and definition.type in ('import_name', 'import_from'):
                    yield c

        if len(list(iter_import_completions())) > 10:
            # For now disable completions if there's a lot of imports that
            # might potentially be resolved. This is the case for tensorflow
            # and has been fixed for it. This is obviously temporary until we
            # have a better solution.
            self._evaluator.infer_enabled = False

        debug.speed('completions end')
        return completions

    def goto_definitions(self):
        """
        Return the definitions of a the path under the cursor.  goto function!
        This follows complicated paths and returns the end, not the first
        definition. The big difference between :meth:`goto_assignments` and
        :meth:`goto_definitions` is that :meth:`goto_assignments` doesn't
        follow imports and statements. Multiple objects may be returned,
        because Python itself is a dynamic language, which means depending on
        an option you can have two different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        leaf = self._module_node.get_name_of_position(self._pos)
        if leaf is None:
            leaf = self._module_node.get_leaf_for_position(self._pos)
            if leaf is None:
                return []

        context = self._evaluator.create_context(self._get_module(), leaf)
        definitions = helpers.evaluate_goto_definition(self._evaluator, context, leaf)

        names = [s.name for s in definitions]
        defs = [classes.Definition(self._evaluator, name) for name in names]
        # The additional set here allows the definitions to become unique in an
        # API sense. In the internals we want to separate more things than in
        # the API.
        return helpers.sorted_definitions(set(defs))

    def goto_assignments(self, follow_imports=False, follow_builtin_imports=False):
        """
        Return the first definition found, while optionally following imports.
        Multiple objects may be returned, because Python itself is a
        dynamic language, which means depending on an option you can have two
        different versions of a function.

        :param follow_imports: The goto call will follow imports.
        :param follow_builtin_imports: If follow_imports is True will decide if
            it follow builtin imports.
        :rtype: list of :class:`classes.Definition`
        """
        def filter_follow_imports(names, check):
            for name in names:
                if check(name):
                    new_names = list(filter_follow_imports(name.goto(), check))
                    found_builtin = False
                    if follow_builtin_imports:
                        for new_name in new_names:
                            if new_name.start_pos is None:
                                found_builtin = True

                    if found_builtin and not isinstance(name, imports.SubModuleName):
                        yield name
                    else:
                        for new_name in new_names:
                            yield new_name
                else:
                    yield name

        tree_name = self._module_node.get_name_of_position(self._pos)
        if tree_name is None:
            return []
        context = self._evaluator.create_context(self._get_module(), tree_name)
        names = list(self._evaluator.goto(context, tree_name))

        if follow_imports:
            def check(name):
                return name.is_import()
        else:
            def check(name):
                return isinstance(name, imports.SubModuleName)

        names = filter_follow_imports(names, check)

        defs = [classes.Definition(self._evaluator, d) for d in set(names)]
        return helpers.sorted_definitions(defs)

    def usages(self, additional_module_paths=(), **kwargs):
        """
        Return :class:`classes.Definition` objects, which contain all
        names that point to the definition of the name under the cursor. This
        is very useful for refactoring (renaming), or to show all usages of a
        variable.

        .. todo:: Implement additional_module_paths

        :param additional_module_paths: Deprecated, never ever worked.
        :param include_builtins: Default True, checks if a usage is a builtin
            (e.g. ``sys``) and in that case does not return it.
        :rtype: list of :class:`classes.Definition`
        """
        if additional_module_paths:
            warnings.warn(
                "Deprecated since version 0.12.0. This never even worked, just ignore it.",
                DeprecationWarning,
                stacklevel=2
            )

        def _usages(include_builtins=True):
            tree_name = self._module_node.get_name_of_position(self._pos)
            if tree_name is None:
                # Must be syntax
                return []

            names = usages.usages(self._get_module(), tree_name)

            definitions = [classes.Definition(self._evaluator, n) for n in names]
            if not include_builtins:
                definitions = [d for d in definitions if not d.in_builtin_module()]
            return helpers.sorted_definitions(definitions)
        return _usages(**kwargs)

    def usages_in_module(self, additional_module_paths=(), **kwargs):
        """
        Return :class:`classes.Definition` objects, which contain all
        names that point to the definition of the name under the cursor. This
        is very useful for refactoring (renaming), or to show all usages of a
        variable.

        .. todo:: Implement additional_module_paths

        :param additional_module_paths: Deprecated, never ever worked.
        :param include_builtins: Default True, checks if a usage is a builtin
            (e.g. ``sys``) and in that case does not return it.
        :rtype: list of :class:`classes.Definition`
        """
        if additional_module_paths:
            warnings.warn(
                "Deprecated since version 0.12.0. This never even worked, just ignore it.",
                DeprecationWarning,
                stacklevel=2
            )

        def _usages_in_module(include_builtins=True):
            tree_name = self._module_node.get_name_of_position(self._pos)
            if tree_name is None:
                # Must be syntax
                return []
            names = usages.usages_in_module(self._get_module(), tree_name)
            definitions = [classes.Definition(self._evaluator, n) for n in names]
            if not include_builtins:
                definitions = [d for d in definitions if not d.in_builtin_module()]
            return helpers.sorted_definitions(definitions)
        return _usages_in_module(**kwargs)

    def call_signatures(self):
        """
        Return the function object of the call you're currently in.

        E.g. if the cursor is here::

            abs(# <-- cursor is here

        This would return the ``abs`` function. On the other hand::

            abs()# <-- cursor is here

        This would return an empty list..

        :rtype: list of :class:`classes.CallSignature`
        """
        call_signature_details = \
            helpers.get_call_signature_details(self._module_node, self._pos)
        if call_signature_details is None:
            return []

        context = self._evaluator.create_context(
            self._get_module(),
            call_signature_details.bracket_leaf
        )
        definitions = helpers.cache_call_signatures(
            self._evaluator,
            context,
            call_signature_details.bracket_leaf,
            self._code_lines,
            self._pos
        )
        debug.speed('func_call followed')

        return [classes.CallSignature(self._evaluator, d.name,
                                      call_signature_details.bracket_leaf.start_pos,
                                      call_signature_details.call_index,
                                      call_signature_details.keyword_name_str)
                for d in definitions if hasattr(d, 'py__call__')]

    def _analysis(self):
        self._evaluator.is_analysis = True
        self._evaluator.analysis_modules = [self._module_node]
        module = self._get_module()
        try:
            for node in get_executable_nodes(self._module_node):
                context = module.create_context(node)
                if node.type in ('funcdef', 'classdef'):
                    # Resolve the decorators.
                    tree_name_to_contexts(self._evaluator, context, node.children[1])
                elif isinstance(node, tree.Import):
                    import_names = set(node.get_defined_names())
                    if node.is_nested():
                        import_names |= set(path[-1] for path in node.get_paths())
                    for n in import_names:
                        imports.infer_import(context, n)
                elif node.type == 'expr_stmt':
                    types = context.eval_node(node)
                    for testlist in node.children[:-1:2]:
                        # Iterate tuples.
                        unpack_tuple_to_dict(context, types, testlist)
                else:
                    if node.type == 'name':
                        defs = self._evaluator.goto_definitions(context, node)
                    else:
                        defs = evaluate_call_of_leaf(context, node)
                    try_iter_content(defs)
                self._evaluator.reset_recursion_limitations()

            ana = [a for a in self._evaluator.analysis if self.path == a.path]
            return sorted(set(ana), key=lambda x: x.line)
        finally:
            self._evaluator.is_analysis = False
示例#41
0
文件: __init__.py 项目: ABob/vim
class Script(object):
    """
    A Script is the base for completions, goto or whatever you want to do with
    |jedi|.

    You can either use the ``source`` parameter or ``path`` to read a file.
    Usually you're going to want to use both of them (in an editor).

    :param source: The source code of the current file, separated by newlines.
    :type source: str
    :param line: The line to perform actions on (starting with 1).
    :type line: int
    :param col: The column of the cursor (starting with 0).
    :type col: int
    :param path: The path of the file in the file system, or ``''`` if
        it hasn't been saved yet.
    :type path: str or None
    :param encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    :param source_encoding: The encoding of ``source``, if it is not a
        ``unicode`` object (default ``'utf-8'``).
    :type encoding: str
    """
    def __init__(self, source=None, line=None, column=None, path=None,
                 encoding='utf-8', source_path=None, source_encoding=None):
        if source_path is not None:
            warnings.warn("Use path instead of source_path.", DeprecationWarning)
            path = source_path
        if source_encoding is not None:
            warnings.warn("Use encoding instead of source_encoding.", DeprecationWarning)
            encoding = source_encoding

        self._orig_path = path
        self.path = None if path is None else os.path.abspath(path)

        if source is None:
            with open(path) as f:
                source = f.read()

        self.source = common.source_to_unicode(source, encoding)
        lines = common.splitlines(self.source)
        line = max(len(lines), 1) if line is None else line
        if not (0 < line <= len(lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_len = len(lines[line - 1])
        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter is not in a valid range.')
        self._pos = line, column

        cache.clear_time_caches()
        debug.reset_time()
        self._grammar = load_grammar('grammar%s.%s' % sys.version_info[:2])
        self._user_context = UserContext(self.source, self._pos)
        self._parser = UserContextParser(self._grammar, self.source, path,
                                         self._pos, self._user_context,
                                         self._parsed_callback)
        self._evaluator = Evaluator(self._grammar)
        debug.speed('init')

    def _parsed_callback(self, parser):
        module = self._evaluator.wrap(parser.module)
        imports.add_module(self._evaluator, unicode(module.name), module)

    @property
    def source_path(self):
        """
        .. deprecated:: 0.7.0
           Use :attr:`.path` instead.
        .. todo:: Remove!
        """
        warnings.warn("Use path instead of source_path.", DeprecationWarning)
        return self.path

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, repr(self._orig_path))

    def completions(self):
        """
        Return :class:`classes.Completion` objects. Those objects contain
        information about the completions, more than just names.

        :return: Completion objects, sorted by name and __ comes last.
        :rtype: list of :class:`classes.Completion`
        """
        def get_completions(user_stmt, bs):
            # TODO this closure is ugly. it also doesn't work with
            # simple_complete (used for Interpreter), somehow redo.
            module = self._evaluator.wrap(self._parser.module())
            names, level, only_modules, unfinished_dotted = \
                helpers.check_error_statements(module, self._pos)
            completion_names = []
            if names is not None:
                imp_names = tuple(str(n) for n in names if n.end_pos < self._pos)
                i = imports.Importer(self._evaluator, imp_names, module, level)
                completion_names = i.completion_names(self._evaluator, only_modules)

            # TODO this paragraph is necessary, but not sure it works.
            context = self._user_context.get_context()
            if not next(context).startswith('.'):  # skip the path
                if next(context) == 'from':
                    # completion is just "import" if before stands from ..
                    if unfinished_dotted:
                        return completion_names
                    else:
                        return set([keywords.keyword('import').name])

            if isinstance(user_stmt, tree.Import):
                module = self._parser.module()
                completion_names += imports.completion_names(self._evaluator,
                                                             user_stmt, self._pos)
                return completion_names

            if names is None and not isinstance(user_stmt, tree.Import):
                if not path and not dot:
                    # add keywords
                    completion_names += keywords.completion_names(
                        self._evaluator,
                        user_stmt,
                        self._pos,
                        module)
                    # TODO delete? We should search for valid parser
                    # transformations.
                completion_names += self._simple_complete(path, dot, like)
            return completion_names

        debug.speed('completions start')
        path = self._user_context.get_path_until_cursor()
        # Dots following an int are not the start of a completion but a float
        # literal.
        if re.search(r'^\d\.$', path):
            return []
        path, dot, like = helpers.completion_parts(path)

        user_stmt = self._parser.user_stmt_with_whitespace()

        b = compiled.builtin
        completion_names = get_completions(user_stmt, b)

        if not dot:
            # add named params
            for call_sig in self.call_signatures():
                # Allow protected access, because it's a public API.
                module = call_sig._name.get_parent_until()
                # Compiled modules typically don't allow keyword arguments.
                if not isinstance(module, compiled.CompiledObject):
                    for p in call_sig.params:
                        # Allow access on _definition here, because it's a
                        # public API and we don't want to make the internal
                        # Name object public.
                        if p._definition.stars == 0:  # no *args/**kwargs
                            completion_names.append(p._name)

        needs_dot = not dot and path

        comps = []
        comp_dct = {}
        for c in set(completion_names):
            n = str(c)
            if settings.case_insensitive_completion \
                    and n.lower().startswith(like.lower()) \
                    or n.startswith(like):
                if isinstance(c.parent, (tree.Function, tree.Class)):
                    # TODO I think this is a hack. It should be an
                    #   er.Function/er.Class before that.
                    c = self._evaluator.wrap(c.parent).name
                new = classes.Completion(self._evaluator, c, needs_dot, len(like))
                k = (new.name, new.complete)  # key
                if k in comp_dct and settings.no_completion_duplicates:
                    comp_dct[k]._same_name_completions.append(new)
                else:
                    comp_dct[k] = new
                    comps.append(new)

        debug.speed('completions end')

        return sorted(comps, key=lambda x: (x.name.startswith('__'),
                                            x.name.startswith('_'),
                                            x.name.lower()))

    def _simple_complete(self, path, dot, like):
        if not path and not dot:
            scope = self._parser.user_scope()
            if not scope.is_scope():  # Might be a flow (if/while/etc).
                scope = scope.get_parent_scope()
            names_dicts = global_names_dict_generator(
                self._evaluator,
                self._evaluator.wrap(scope),
                self._pos
            )
            completion_names = []
            for names_dict, pos in names_dicts:
                names = list(chain.from_iterable(names_dict.values()))
                if not names:
                    continue
                completion_names += filter_definition_names(names, self._parser.user_stmt(), pos)
        elif self._get_under_cursor_stmt(path) is None:
            return []
        else:
            scopes = list(self._prepare_goto(path, True))
            completion_names = []
            debug.dbg('possible completion scopes: %s', scopes)
            for s in scopes:
                names = []
                for names_dict in s.names_dicts(search_global=False):
                    names += chain.from_iterable(names_dict.values())

                completion_names += filter_definition_names(names, self._parser.user_stmt())
        return completion_names

    def _prepare_goto(self, goto_path, is_completion=False):
        """
        Base for completions/goto. Basically it returns the resolved scopes
        under cursor.
        """
        debug.dbg('start: %s in %s', goto_path, self._parser.user_scope())

        user_stmt = self._parser.user_stmt_with_whitespace()
        if not user_stmt and len(goto_path.split('\n')) > 1:
            # If the user_stmt is not defined and the goto_path is multi line,
            # something's strange. Most probably the backwards tokenizer
            # matched to much.
            return []

        if isinstance(user_stmt, tree.Import):
            i, _ = helpers.get_on_import_stmt(self._evaluator, self._user_context,
                                              user_stmt, is_completion)
            if i is None:
                return []
            scopes = [i]
        else:
            # just parse one statement, take it and evaluate it
            eval_stmt = self._get_under_cursor_stmt(goto_path)
            if eval_stmt is None:
                return []

            module = self._evaluator.wrap(self._parser.module())
            names, level, _, _ = helpers.check_error_statements(module, self._pos)
            if names:
                names = [str(n) for n in names]
                i = imports.Importer(self._evaluator, names, module, level)
                return i.follow()

            scopes = self._evaluator.eval_element(eval_stmt)

        return scopes

    @memoize_default()
    def _get_under_cursor_stmt(self, cursor_txt, start_pos=None):
        tokenizer = source_tokens(cursor_txt)
        r = Parser(self._grammar, cursor_txt, tokenizer=tokenizer)
        try:
            # Take the last statement available that is not an endmarker.
            # And because it's a simple_stmt, we need to get the first child.
            stmt = r.module.children[-2].children[0]
        except (AttributeError, IndexError):
            return None

        user_stmt = self._parser.user_stmt()
        if user_stmt is None:
            # Set the start_pos to a pseudo position, that doesn't exist but
            # works perfectly well (for both completions in docstrings and
            # statements).
            pos = start_pos or self._pos
        else:
            pos = user_stmt.start_pos

        stmt.move(pos[0] - 1, pos[1])  # Moving the offset.
        stmt.parent = self._parser.user_scope()
        return stmt

    def goto_definitions(self):
        """
        Return the definitions of a the path under the cursor.  goto function!
        This follows complicated paths and returns the end, not the first
        definition. The big difference between :meth:`goto_assignments` and
        :meth:`goto_definitions` is that :meth:`goto_assignments` doesn't
        follow imports and statements. Multiple objects may be returned,
        because Python itself is a dynamic language, which means depending on
        an option you can have two different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        def resolve_import_paths(scopes):
            for s in scopes.copy():
                if isinstance(s, imports.ImportWrapper):
                    scopes.remove(s)
                    scopes.update(resolve_import_paths(set(s.follow())))
            return scopes

        goto_path = self._user_context.get_path_under_cursor()
        context = self._user_context.get_context()
        definitions = set()
        if next(context) in ('class', 'def'):
            definitions = set([self._evaluator.wrap(self._parser.user_scope())])
        else:
            # Fetch definition of callee, if there's no path otherwise.
            if not goto_path:
                definitions = set(signature._definition
                                  for signature in self.call_signatures())

        if re.match('\w[\w\d_]*$', goto_path) and not definitions:
            user_stmt = self._parser.user_stmt()
            if user_stmt is not None and user_stmt.type == 'expr_stmt':
                for name in user_stmt.get_defined_names():
                    if name.start_pos <= self._pos <= name.end_pos:
                        # TODO scaning for a name and then using it should be
                        # the default.
                        definitions = set(self._evaluator.goto_definition(name))

        if not definitions and goto_path:
            definitions = set(self._prepare_goto(goto_path))

        definitions = resolve_import_paths(definitions)
        names = [s.name for s in definitions]
        defs = [classes.Definition(self._evaluator, name) for name in names]
        return helpers.sorted_definitions(set(defs))

    def goto_assignments(self):
        """
        Return the first definition found. Imports and statements aren't
        followed. Multiple objects may be returned, because Python itself is a
        dynamic language, which means depending on an option you can have two
        different versions of a function.

        :rtype: list of :class:`classes.Definition`
        """
        results = self._goto()
        d = [classes.Definition(self._evaluator, d) for d in set(results)]
        return helpers.sorted_definitions(d)

    def _goto(self, add_import_name=False):
        """
        Used for goto_assignments and usages.

        :param add_import_name: Add the the name (if import) to the result.
        """
        def follow_inexistent_imports(defs):
            """ Imports can be generated, e.g. following
            `multiprocessing.dummy` generates an import dummy in the
            multiprocessing module. The Import doesn't exist -> follow.
            """
            definitions = set(defs)
            for d in defs:
                if isinstance(d.parent, tree.Import) \
                        and d.start_pos == (0, 0):
                    i = imports.ImportWrapper(self._evaluator, d.parent).follow(is_goto=True)
                    definitions.remove(d)
                    definitions |= follow_inexistent_imports(i)
            return definitions

        goto_path = self._user_context.get_path_under_cursor()
        context = self._user_context.get_context()
        user_stmt = self._parser.user_stmt()
        user_scope = self._parser.user_scope()

        stmt = self._get_under_cursor_stmt(goto_path)
        if stmt is None:
            return []

        if user_scope is None:
            last_name = None
        else:
            # Try to use the parser if possible.
            last_name = user_scope.name_for_position(self._pos)

        if last_name is None:
            last_name = stmt
            while not isinstance(last_name, tree.Name):
                try:
                    last_name = last_name.children[-1]
                except AttributeError:
                    # Doesn't have a name in it.
                    return []

        if next(context) in ('class', 'def'):
            # The cursor is on a class/function name.
            user_scope = self._parser.user_scope()
            definitions = set([user_scope.name])
        elif isinstance(user_stmt, tree.Import):
            s, name = helpers.get_on_import_stmt(self._evaluator,
                                                 self._user_context, user_stmt)

            definitions = self._evaluator.goto(name)
        else:
            # The Evaluator.goto function checks for definitions, but since we
            # use a reverse tokenizer, we have new name_part objects, so we
            # have to check the user_stmt here for positions.
            if isinstance(user_stmt, tree.ExprStmt) \
                    and isinstance(last_name.parent, tree.ExprStmt):
                for name in user_stmt.get_defined_names():
                    if name.start_pos <= self._pos <= name.end_pos:
                        return [name]

            defs = self._evaluator.goto(last_name)
            definitions = follow_inexistent_imports(defs)
        return definitions

    def usages(self, additional_module_paths=()):
        """
        Return :class:`classes.Definition` objects, which contain all
        names that point to the definition of the name under the cursor. This
        is very useful for refactoring (renaming), or to show all usages of a
        variable.

        .. todo:: Implement additional_module_paths

        :rtype: list of :class:`classes.Definition`
        """
        temp, settings.dynamic_flow_information = \
            settings.dynamic_flow_information, False
        try:
            user_stmt = self._parser.user_stmt()
            definitions = self._goto(add_import_name=True)
            if not definitions and isinstance(user_stmt, tree.Import):
                # For not defined imports (goto doesn't find something, we take
                # the name as a definition. This is enough, because every name
                # points to it.
                name = user_stmt.name_for_position(self._pos)
                if name is None:
                    # Must be syntax
                    return []
                definitions = [name]

            if not definitions:
                # Without a definition for a name we cannot find references.
                return []

            if not isinstance(user_stmt, tree.Import):
                # import case is looked at with add_import_name option
                definitions = usages.usages_add_import_modules(self._evaluator,
                                                               definitions)

            module = set([d.get_parent_until() for d in definitions])
            module.add(self._parser.module())
            names = usages.usages(self._evaluator, definitions, module)

            for d in set(definitions):
                names.append(classes.Definition(self._evaluator, d))
        finally:
            settings.dynamic_flow_information = temp

        return helpers.sorted_definitions(set(names))

    def call_signatures(self):
        """
        Return the function object of the call you're currently in.

        E.g. if the cursor is here::

            abs(# <-- cursor is here

        This would return the ``abs`` function. On the other hand::

            abs()# <-- cursor is here

        This would return ``None``.

        :rtype: list of :class:`classes.CallSignature`
        """
        call_txt, call_index, key_name, start_pos = self._user_context.call_signature()
        if call_txt is None:
            return []

        stmt = self._get_under_cursor_stmt(call_txt, start_pos)
        if stmt is None:
            return []

        with common.scale_speed_settings(settings.scale_call_signatures):
            origins = cache.cache_call_signatures(self._evaluator, stmt,
                                                  self.source, self._pos)
        debug.speed('func_call followed')

        return [classes.CallSignature(self._evaluator, o.name, stmt, call_index, key_name)
                for o in origins if hasattr(o, 'py__call__')]

    def _analysis(self):
        def check_types(types):
            for typ in types:
                try:
                    f = typ.iter_content
                except AttributeError:
                    pass
                else:
                    check_types(f())

        #statements = set(chain(*self._parser.module().used_names.values()))
        nodes, imp_names, decorated_funcs = \
            analysis.get_module_statements(self._parser.module())
        # Sort the statements so that the results are reproducible.
        for n in imp_names:
            imports.ImportWrapper(self._evaluator, n).follow()
        for node in sorted(nodes, key=lambda obj: obj.start_pos):
            check_types(self._evaluator.eval_element(node))

        for dec_func in decorated_funcs:
            er.Function(self._evaluator, dec_func).get_decorated_func()

        ana = [a for a in self._evaluator.analysis if self.path == a.path]
        return sorted(set(ana), key=lambda x: x.line)
示例#42
0
def test_fake_docstr():
    assert compiled.create(Evaluator(), next).raw_doc == next.__doc__
示例#43
0
def _evaluator():
    return Evaluator(parso.load_grammar())
示例#44
0
    def __init__(self,
                 source=None,
                 line=None,
                 column=None,
                 path=None,
                 encoding='utf-8',
                 sys_path=None,
                 environment=None):
        self._orig_path = path
        # An empty path (also empty string) should always result in no path.
        self.path = os.path.abspath(path) if path else None

        if source is None:
            # TODO add a better warning than the traceback!
            with open(path, 'rb') as f:
                source = f.read()

        # Load the Python grammar of the current interpreter.
        self._grammar = parso.load_grammar()

        if sys_path is not None and not is_py3:
            sys_path = list(map(force_unicode, sys_path))

        # Load the Python grammar of the current interpreter.
        project = get_default_project(
            # iOS: getcwd() creates problems. We force it to ~/Documents
            #            os.path.dirname(self.path)if path else os.getcwd()
            os.path.dirname(self.path) if path else os.path.
            join(os.getenv("HOME"), 'Library/lib/python3.7/'))
        # TODO deprecate and remove sys_path from the Script API.
        if sys_path is not None:
            project._sys_path = sys_path
        self._evaluator = Evaluator(project,
                                    environment=environment,
                                    script_path=self.path)
        self._project = project
        debug.speed('init')
        self._module_node, source = self._evaluator.parse_and_get_code(
            code=source,
            path=self.path,
            encoding=encoding,
            cache=
            False,  # No disk cache, because the current script often changes.
            diff_cache=settings.fast_parser,
            cache_path=settings.cache_directory,
        )
        debug.speed('parsed')
        self._code_lines = parso.split_lines(source, keepends=True)
        self._code = source
        line = max(len(self._code_lines), 1) if line is None else line
        if not (0 < line <= len(self._code_lines)):
            raise ValueError('`line` parameter is not in a valid range.')

        line_string = self._code_lines[line - 1]
        line_len = len(line_string)
        if line_string.endswith('\r\n'):
            line_len -= 1
        if line_string.endswith('\n'):
            line_len -= 1

        column = line_len if column is None else column
        if not (0 <= column <= line_len):
            raise ValueError('`column` parameter (%d) is not in a valid range '
                             '(0-%d) for line %d (%r).' %
                             (column, line_len, line, line_string))
        self._pos = line, column
        self._path = path

        cache.clear_time_caches()
        debug.reset_time()