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
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
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)
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()
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')
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')
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
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)
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 __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')
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')
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')
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
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
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')
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
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')
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
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
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)
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
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')
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() ]
def check_module_test(code): grammar = load_grammar() p = ParserWithRecovery(grammar, code) module_context = ModuleContext(Evaluator(grammar), p.module) return _check_module(module_context)
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
def check_module_test(code): grammar = load_grammar() module_context = ModuleContext(Evaluator(grammar), parse(code), path=None) return _check_module(module_context)
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)
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
def _evaluator(): return Evaluator(load_grammar())
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']
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
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)
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
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
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)
def test_fake_docstr(): assert compiled.create(Evaluator(), next).raw_doc == next.__doc__
def _evaluator(): return Evaluator(parso.load_grammar())
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()