forked from VimPlug/jedi
668 lines
27 KiB
Python
668 lines
27 KiB
Python
"""
|
|
The ``Parser`` tries to convert the available Python code in an easy to read
|
|
format, something like an abstract syntax tree. The classes who represent this
|
|
tree, are sitting in the :mod:`parsing_representation` module.
|
|
|
|
The Python module ``tokenize`` is a very important part in the ``Parser``,
|
|
because it splits the code into different words (tokens). Sometimes it looks a
|
|
bit messy. Sorry for that! You might ask now: "Why didn't you use the ``ast``
|
|
module for this? Well, ``ast`` does a very good job understanding proper Python
|
|
code, but fails to work as soon as there's a single line of broken code.
|
|
|
|
There's one important optimization that needs to be known: Statements are not
|
|
being parsed completely. ``Statement`` is just a representation of the tokens
|
|
within the statement. This lowers memory usage and cpu time and reduces the
|
|
complexity of the ``Parser`` (there's another parser sitting inside
|
|
``Statement``, which produces ``Array`` and ``Call``).
|
|
"""
|
|
from __future__ import with_statement
|
|
|
|
import tokenize
|
|
import keyword
|
|
|
|
from jedi._compatibility import next, StringIO
|
|
from jedi import debug
|
|
from jedi import common
|
|
from jedi import parsing_representation as pr
|
|
|
|
|
|
class ParserError(Exception):
|
|
pass
|
|
|
|
|
|
class Parser(object):
|
|
"""
|
|
This class is used to parse a Python file, it then divides them into a
|
|
class structure of different scopes.
|
|
|
|
:param source: The codebase for the parser.
|
|
:type source: str
|
|
:param module_path: The path of the module in the file system, may be None.
|
|
:type module_path: str
|
|
:param user_position: The line/column, the user is currently on.
|
|
:type user_position: tuple(int, int)
|
|
:param no_docstr: If True, a string at the beginning is not a docstr.
|
|
:param stop_on_scope: Stop if a scope appears -> for fast_parser
|
|
:param top_module: Use this module as a parent instead of `self.module`.
|
|
"""
|
|
def __init__(self, source, module_path=None, user_position=None,
|
|
no_docstr=False, offset=(0, 0), stop_on_scope=None,
|
|
top_module=None):
|
|
self.user_position = user_position
|
|
self.user_scope = None
|
|
self.user_stmt = None
|
|
self.no_docstr = no_docstr
|
|
|
|
# initialize global Scope
|
|
self.module = pr.SubModule(module_path, (offset[0] + 1, offset[1]),
|
|
top_module)
|
|
self.scope = self.module
|
|
self.current = (None, None)
|
|
self.start_pos = 1, 0
|
|
self.end_pos = 1, 0
|
|
|
|
source = source + '\n' # end with \n, because the parser needs it
|
|
buf = StringIO(source)
|
|
self._gen = common.NoErrorTokenizer(buf.readline, offset,
|
|
stop_on_scope)
|
|
self.top_module = top_module or self.module
|
|
try:
|
|
self._parse()
|
|
except (common.MultiLevelStopIteration, StopIteration):
|
|
# StopIteration needs to be added as well, because python 2 has a
|
|
# strange way of handling StopIterations.
|
|
# sometimes StopIteration isn't catched. Just ignore it.
|
|
pass
|
|
|
|
# clean up unused decorators
|
|
for d in self._decorators:
|
|
# set a parent for unused decorators, avoid NullPointerException
|
|
# because of `self.module.used_names`.
|
|
d.parent = self.module
|
|
|
|
self.start_pos = self.module.start_pos
|
|
self.module.end_pos = self.end_pos
|
|
del self._gen
|
|
|
|
def __repr__(self):
|
|
return "<%s: %s>" % (type(self).__name__, self.module)
|
|
|
|
def _check_user_stmt(self, simple):
|
|
# this is not user checking, just update the used_names
|
|
for tok_name in self.module.temp_used_names:
|
|
try:
|
|
self.module.used_names[tok_name].add(simple)
|
|
except KeyError:
|
|
self.module.used_names[tok_name] = set([simple])
|
|
self.module.temp_used_names = []
|
|
|
|
if not self.user_position:
|
|
return
|
|
# the position is right
|
|
if simple.start_pos <= self.user_position <= simple.end_pos:
|
|
if self.user_stmt is not None:
|
|
# if there is already a user position (another import, because
|
|
# imports are splitted) the names are checked.
|
|
for n in simple.get_set_vars():
|
|
if n.start_pos < self.user_position <= n.end_pos:
|
|
self.user_stmt = simple
|
|
else:
|
|
self.user_stmt = simple
|
|
|
|
def _parse_dot_name(self, pre_used_token=None):
|
|
"""
|
|
The dot name parser parses a name, variable or function and returns
|
|
their names.
|
|
|
|
:return: Tuple of Name, token_type, nexttoken.
|
|
:rtype: tuple(Name, int, str)
|
|
"""
|
|
def append(el):
|
|
names.append(el)
|
|
self.module.temp_used_names.append(el[0])
|
|
|
|
names = []
|
|
if pre_used_token is None:
|
|
token_type, tok = self.next()
|
|
if token_type != tokenize.NAME and tok != '*':
|
|
return [], token_type, tok
|
|
else:
|
|
token_type, tok = pre_used_token
|
|
|
|
if token_type != tokenize.NAME and tok != '*':
|
|
# token maybe a name or star
|
|
return None, token_type, tok
|
|
|
|
append((tok, self.start_pos))
|
|
first_pos = self.start_pos
|
|
while True:
|
|
token_type, tok = self.next()
|
|
if tok != '.':
|
|
break
|
|
token_type, tok = self.next()
|
|
if token_type != tokenize.NAME:
|
|
break
|
|
append((tok, self.start_pos))
|
|
|
|
n = pr.Name(self.module, names, first_pos, self.end_pos) if names \
|
|
else None
|
|
return n, token_type, tok
|
|
|
|
def _parse_import_list(self):
|
|
"""
|
|
The parser for the imports. Unlike the class and function parse
|
|
function, this returns no Import class, but rather an import list,
|
|
which is then added later on.
|
|
The reason, why this is not done in the same class lies in the nature
|
|
of imports. There are two ways to write them:
|
|
|
|
- from ... import ...
|
|
- import ...
|
|
|
|
To distinguish, this has to be processed after the parser.
|
|
|
|
:return: List of imports.
|
|
:rtype: list
|
|
"""
|
|
imports = []
|
|
brackets = False
|
|
continue_kw = [",", ";", "\n", ')'] \
|
|
+ list(set(keyword.kwlist) - set(['as']))
|
|
while True:
|
|
defunct = False
|
|
token_type, tok = self.next()
|
|
if token_type == tokenize.ENDMARKER:
|
|
break
|
|
if brackets and tok == '\n':
|
|
self.next()
|
|
if tok == '(': # python allows only one `(` in the statement.
|
|
brackets = True
|
|
self.next()
|
|
i, token_type, tok = self._parse_dot_name(self.current)
|
|
if not i:
|
|
defunct = True
|
|
name2 = None
|
|
if tok == 'as':
|
|
name2, token_type, tok = self._parse_dot_name()
|
|
imports.append((i, name2, defunct))
|
|
while tok not in continue_kw:
|
|
token_type, tok = self.next()
|
|
if not (tok == "," or brackets and tok == '\n'):
|
|
break
|
|
return imports
|
|
|
|
def _parse_parentheses(self):
|
|
"""
|
|
Functions and Classes have params (which means for classes
|
|
super-classes). They are parsed here and returned as Statements.
|
|
|
|
:return: List of Statements
|
|
:rtype: list
|
|
"""
|
|
names = []
|
|
tok = None
|
|
pos = 0
|
|
breaks = [',', ':']
|
|
while tok not in [')', ':']:
|
|
param, tok = self._parse_statement(added_breaks=breaks,
|
|
stmt_class=pr.Param)
|
|
if param and tok == ':':
|
|
# parse annotations
|
|
annotation, tok = self._parse_statement(added_breaks=breaks)
|
|
if annotation:
|
|
param.add_annotation(annotation)
|
|
|
|
# params without vars are usually syntax errors.
|
|
if param and (param.set_vars or param.used_vars):
|
|
param.position_nr = pos
|
|
names.append(param)
|
|
pos += 1
|
|
|
|
return names
|
|
|
|
def _parse_function(self):
|
|
"""
|
|
The parser for a text functions. Process the tokens, which follow a
|
|
function definition.
|
|
|
|
:return: Return a Scope representation of the tokens.
|
|
:rtype: Function
|
|
"""
|
|
first_pos = self.start_pos
|
|
token_type, fname = self.next()
|
|
if token_type != tokenize.NAME:
|
|
return None
|
|
|
|
fname = pr.Name(self.module, [(fname, self.start_pos)], self.start_pos,
|
|
self.end_pos)
|
|
|
|
token_type, open = self.next()
|
|
if open != '(':
|
|
return None
|
|
params = self._parse_parentheses()
|
|
|
|
token_type, colon = self.next()
|
|
annotation = None
|
|
if colon in ['-', '->']:
|
|
# parse annotations
|
|
if colon == '-':
|
|
# The Python 2 tokenizer doesn't understand this
|
|
token_type, colon = self.next()
|
|
if colon != '>':
|
|
return None
|
|
annotation, colon = self._parse_statement(added_breaks=[':'])
|
|
|
|
if colon != ':':
|
|
return None
|
|
|
|
# because of 2 line func param definitions
|
|
scope = pr.Function(self.module, fname, params, first_pos, annotation)
|
|
if self.user_scope and scope != self.user_scope \
|
|
and self.user_position > first_pos:
|
|
self.user_scope = scope
|
|
return scope
|
|
|
|
def _parse_class(self):
|
|
"""
|
|
The parser for a text class. Process the tokens, which follow a
|
|
class definition.
|
|
|
|
:return: Return a Scope representation of the tokens.
|
|
:rtype: Class
|
|
"""
|
|
first_pos = self.start_pos
|
|
token_type, cname = self.next()
|
|
if token_type != tokenize.NAME:
|
|
debug.warning("class: syntax err, token is not a name@%s (%s: %s)"
|
|
% (self.start_pos[0], tokenize.tok_name[token_type], cname))
|
|
return None
|
|
|
|
cname = pr.Name(self.module, [(cname, self.start_pos)], self.start_pos,
|
|
self.end_pos)
|
|
|
|
super = []
|
|
token_type, _next = self.next()
|
|
if _next == '(':
|
|
super = self._parse_parentheses()
|
|
token_type, _next = self.next()
|
|
|
|
if _next != ':':
|
|
debug.warning("class syntax: %s@%s" % (cname, self.start_pos[0]))
|
|
return None
|
|
|
|
# because of 2 line class initializations
|
|
scope = pr.Class(self.module, cname, super, first_pos)
|
|
if self.user_scope and scope != self.user_scope \
|
|
and self.user_position > first_pos:
|
|
self.user_scope = scope
|
|
return scope
|
|
|
|
def _parse_statement(self, pre_used_token=None, added_breaks=None,
|
|
stmt_class=pr.Statement):
|
|
"""
|
|
Parses statements like::
|
|
|
|
a = test(b)
|
|
a += 3 - 2 or b
|
|
|
|
and so on. One line at a time.
|
|
|
|
:param pre_used_token: The pre parsed token.
|
|
:type pre_used_token: set
|
|
:return: Statement + last parsed token.
|
|
:rtype: (Statement, str)
|
|
"""
|
|
set_vars = []
|
|
used_vars = []
|
|
level = 0 # The level of parentheses
|
|
|
|
if pre_used_token:
|
|
token_type, tok = pre_used_token
|
|
else:
|
|
token_type, tok = self.next()
|
|
|
|
while token_type == tokenize.COMMENT:
|
|
# remove newline and comment
|
|
self.next()
|
|
token_type, tok = self.next()
|
|
|
|
first_pos = self.start_pos
|
|
opening_brackets = ['{', '(', '[']
|
|
closing_brackets = ['}', ')', ']']
|
|
|
|
# the difference between "break" and "always break" is that the latter
|
|
# will even break in parentheses. This is true for typical flow
|
|
# commands like def and class and the imports, which will never be used
|
|
# in a statement.
|
|
breaks = set(['\n', ':', ')'])
|
|
always_break = [';', 'import', 'from', 'class', 'def', 'try', 'except',
|
|
'finally', 'while', 'return', 'yield']
|
|
not_first_break = ['del', 'raise']
|
|
if added_breaks:
|
|
breaks |= set(added_breaks)
|
|
|
|
tok_list = []
|
|
while not (tok in always_break
|
|
or tok in not_first_break and not tok_list
|
|
or tok in breaks and level <= 0):
|
|
try:
|
|
#print 'parse_stmt', tok, tokenize.tok_name[token_type]
|
|
tok_list.append(self.current + (self.start_pos,))
|
|
if tok == 'as':
|
|
token_type, tok = self.next()
|
|
if token_type == tokenize.NAME:
|
|
n, token_type, tok = self._parse_dot_name(self.current)
|
|
if n:
|
|
set_vars.append(n)
|
|
tok_list.append(n)
|
|
continue
|
|
elif tok in ['lambda', 'for', 'in']:
|
|
# don't parse these keywords, parse later in stmt.
|
|
if tok == 'lambda':
|
|
breaks.discard(':')
|
|
elif token_type == tokenize.NAME:
|
|
n, token_type, tok = self._parse_dot_name(self.current)
|
|
# removed last entry, because we add Name
|
|
tok_list.pop()
|
|
if n:
|
|
tok_list.append(n)
|
|
used_vars.append(n)
|
|
continue
|
|
elif tok.endswith('=') and tok not in ['>=', '<=', '==', '!=']:
|
|
# there has been an assignement -> change vars
|
|
if level == 0:
|
|
set_vars += used_vars
|
|
used_vars = []
|
|
elif tok in opening_brackets:
|
|
level += 1
|
|
elif tok in closing_brackets:
|
|
level -= 1
|
|
|
|
token_type, tok = self.next()
|
|
except (StopIteration, common.MultiLevelStopIteration):
|
|
# comes from tokenizer
|
|
break
|
|
|
|
if not tok_list:
|
|
return None, tok
|
|
#print 'new_stat', set_vars, used_vars
|
|
if self.freshscope and not self.no_docstr and len(tok_list) == 1 \
|
|
and self.last_token[0] == tokenize.STRING:
|
|
self.scope.add_docstr(self.last_token[1])
|
|
return None, tok
|
|
else:
|
|
stmt = stmt_class(self.module, set_vars, used_vars, tok_list,
|
|
first_pos, self.end_pos)
|
|
|
|
self._check_user_stmt(stmt)
|
|
|
|
# Attribute docstring (PEP 257) support
|
|
with common.ignored(IndexError, AttributeError):
|
|
# If string literal is being parsed
|
|
first_tok = stmt.token_list[0]
|
|
if (not stmt.set_vars and
|
|
not stmt.used_vars and
|
|
len(stmt.token_list) == 1 and
|
|
first_tok[0] == tokenize.STRING):
|
|
# ... then set it as a docstring
|
|
self.scope.statements[-1].add_docstr(first_tok[1])
|
|
|
|
if tok in always_break + not_first_break:
|
|
self._gen.push_last_back()
|
|
return stmt, tok
|
|
|
|
def next(self):
|
|
return self.__next__()
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def __next__(self):
|
|
""" Generate the next tokenize pattern. """
|
|
try:
|
|
typ, tok, self.start_pos, self.end_pos, \
|
|
self.parserline = next(self._gen)
|
|
except (StopIteration, common.MultiLevelStopIteration):
|
|
# on finish, set end_pos correctly
|
|
s = self.scope
|
|
while s is not None:
|
|
s.end_pos = self.end_pos
|
|
s = s.parent
|
|
raise
|
|
|
|
if self.user_position and (self.start_pos[0] == self.user_position[0]
|
|
or self.user_scope is None
|
|
and self.start_pos[0] >= self.user_position[0]):
|
|
debug.dbg('user scope found [%s] = %s' % \
|
|
(self.parserline.replace('\n', ''), repr(self.scope)))
|
|
self.user_scope = self.scope
|
|
self.last_token = self.current
|
|
self.current = (typ, tok)
|
|
return self.current
|
|
|
|
def _parse(self):
|
|
"""
|
|
The main part of the program. It analyzes the given code-text and
|
|
returns a tree-like scope. For a more detailed description, see the
|
|
class description.
|
|
|
|
:param text: The code which should be parsed.
|
|
:param type: str
|
|
|
|
:raises: IndentationError
|
|
"""
|
|
extended_flow = ['else', 'elif', 'except', 'finally']
|
|
statement_toks = ['{', '[', '(', '`']
|
|
|
|
self._decorators = []
|
|
self.freshscope = True
|
|
self.iterator = iter(self)
|
|
# This iterator stuff is not intentional. It grew historically.
|
|
for token_type, tok in self.iterator:
|
|
self.module.temp_used_names = []
|
|
#debug.dbg('main: tok=[%s] type=[%s] indent=[%s]'\
|
|
# % (tok, tokenize.tok_name[token_type], start_position[0]))
|
|
|
|
while token_type == tokenize.DEDENT and self.scope != self.module:
|
|
token_type, tok = self.next()
|
|
if self.start_pos[1] <= self.scope.start_pos[1]:
|
|
self.scope.end_pos = self.start_pos
|
|
self.scope = self.scope.parent
|
|
if isinstance(self.scope, pr.Module) \
|
|
and not isinstance(self.scope, pr.SubModule):
|
|
self.scope = self.module
|
|
|
|
# check again for unindented stuff. this is true for syntax
|
|
# errors. only check for names, because thats relevant here. If
|
|
# some docstrings are not indented, I don't care.
|
|
while self.start_pos[1] <= self.scope.start_pos[1] \
|
|
and (token_type == tokenize.NAME or tok in ['(', '['])\
|
|
and self.scope != self.module:
|
|
self.scope.end_pos = self.start_pos
|
|
self.scope = self.scope.parent
|
|
if isinstance(self.scope, pr.Module) \
|
|
and not isinstance(self.scope, pr.SubModule):
|
|
self.scope = self.module
|
|
|
|
use_as_parent_scope = self.top_module if isinstance(self.scope,
|
|
pr.SubModule) else self.scope
|
|
first_pos = self.start_pos
|
|
if tok == 'def':
|
|
func = self._parse_function()
|
|
if func is None:
|
|
debug.warning("function: syntax error@%s" %
|
|
self.start_pos[0])
|
|
continue
|
|
self.freshscope = True
|
|
self.scope = self.scope.add_scope(func, self._decorators)
|
|
self._decorators = []
|
|
elif tok == 'class':
|
|
cls = self._parse_class()
|
|
if cls is None:
|
|
debug.warning("class: syntax error@%s" % self.start_pos[0])
|
|
continue
|
|
self.freshscope = True
|
|
self.scope = self.scope.add_scope(cls, self._decorators)
|
|
self._decorators = []
|
|
# import stuff
|
|
elif tok == 'import':
|
|
imports = self._parse_import_list()
|
|
for m, alias, defunct in imports:
|
|
i = pr.Import(self.module, first_pos, self.end_pos, m,
|
|
alias, defunct=defunct)
|
|
self._check_user_stmt(i)
|
|
self.scope.add_import(i)
|
|
if not imports:
|
|
i = pr.Import(self.module, first_pos, self.end_pos, None,
|
|
defunct=True)
|
|
self._check_user_stmt(i)
|
|
self.freshscope = False
|
|
elif tok == 'from':
|
|
defunct = False
|
|
# take care for relative imports
|
|
relative_count = 0
|
|
while 1:
|
|
token_type, tok = self.next()
|
|
if tok != '.':
|
|
break
|
|
relative_count += 1
|
|
# the from import
|
|
mod, token_type, tok = self._parse_dot_name(self.current)
|
|
if str(mod) == 'import' and relative_count:
|
|
self._gen.push_last_back()
|
|
tok = 'import'
|
|
mod = None
|
|
if not mod and not relative_count or tok != "import":
|
|
debug.warning("from: syntax error@%s" % self.start_pos[0])
|
|
defunct = True
|
|
if tok != 'import':
|
|
self._gen.push_last_back()
|
|
names = self._parse_import_list()
|
|
for name, alias, defunct2 in names:
|
|
star = name is not None and name.names[0] == '*'
|
|
if star:
|
|
name = None
|
|
i = pr.Import(self.module, first_pos, self.end_pos, name,
|
|
alias, mod, star, relative_count,
|
|
defunct=defunct or defunct2)
|
|
self._check_user_stmt(i)
|
|
self.scope.add_import(i)
|
|
self.freshscope = False
|
|
#loops
|
|
elif tok == 'for':
|
|
set_stmt, tok = self._parse_statement(added_breaks=['in'])
|
|
if tok == 'in':
|
|
statement, tok = self._parse_statement()
|
|
if tok == ':':
|
|
s = [] if statement is None else [statement]
|
|
f = pr.ForFlow(self.module, s, first_pos, set_stmt)
|
|
self.scope = self.scope.add_statement(f)
|
|
else:
|
|
debug.warning('syntax err, for flow started @%s',
|
|
self.start_pos[0])
|
|
if statement is not None:
|
|
statement.parent = use_as_parent_scope
|
|
if set_stmt is not None:
|
|
set_stmt.parent = use_as_parent_scope
|
|
else:
|
|
debug.warning('syntax err, for flow incomplete @%s',
|
|
self.start_pos[0])
|
|
if set_stmt is not None:
|
|
set_stmt.parent = use_as_parent_scope
|
|
|
|
elif tok in ['if', 'while', 'try', 'with'] + extended_flow:
|
|
added_breaks = []
|
|
command = tok
|
|
if command in ['except', 'with']:
|
|
added_breaks.append(',')
|
|
# multiple inputs because of with
|
|
inputs = []
|
|
first = True
|
|
while first or command == 'with' \
|
|
and tok not in [':', '\n']:
|
|
statement, tok = \
|
|
self._parse_statement(added_breaks=added_breaks)
|
|
if command == 'except' and tok in added_breaks:
|
|
# the except statement defines a var
|
|
# this is only true for python 2
|
|
n, token_type, tok = self._parse_dot_name()
|
|
if n:
|
|
statement.set_vars.append(n)
|
|
if statement:
|
|
inputs.append(statement)
|
|
first = False
|
|
|
|
if tok == ':':
|
|
f = pr.Flow(self.module, command, inputs, first_pos)
|
|
if command in extended_flow:
|
|
# the last statement has to be another part of
|
|
# the flow statement, because a dedent releases the
|
|
# main scope, so just take the last statement.
|
|
try:
|
|
s = self.scope.statements[-1].set_next(f)
|
|
except (AttributeError, IndexError):
|
|
# If set_next doesn't exist, just add it.
|
|
s = self.scope.add_statement(f)
|
|
else:
|
|
s = self.scope.add_statement(f)
|
|
self.scope = s
|
|
else:
|
|
for i in inputs:
|
|
i.parent = use_as_parent_scope
|
|
debug.warning('syntax err, flow started @%s',
|
|
self.start_pos[0])
|
|
# returns
|
|
elif tok in ['return', 'yield']:
|
|
s = self.start_pos
|
|
self.freshscope = False
|
|
# add returns to the scope
|
|
func = self.scope.get_parent_until(pr.Function)
|
|
if tok == 'yield':
|
|
func.is_generator = True
|
|
|
|
stmt, tok = self._parse_statement()
|
|
if stmt is not None:
|
|
stmt.parent = use_as_parent_scope
|
|
try:
|
|
func.returns.append(stmt)
|
|
# start_pos is the one of the return statement
|
|
stmt.start_pos = s
|
|
except AttributeError:
|
|
debug.warning('return in non-function')
|
|
# globals
|
|
elif tok == 'global':
|
|
stmt, tok = self._parse_statement(self.current)
|
|
if stmt:
|
|
self.scope.add_statement(stmt)
|
|
for name in stmt.used_vars:
|
|
# add the global to the top, because there it is
|
|
# important.
|
|
self.module.add_global(name)
|
|
# decorator
|
|
elif tok == '@':
|
|
stmt, tok = self._parse_statement()
|
|
self._decorators.append(stmt)
|
|
elif tok == 'pass':
|
|
continue
|
|
elif tok == 'assert':
|
|
stmt, tok = self._parse_statement()
|
|
stmt.parent = use_as_parent_scope
|
|
self.scope.asserts.append(stmt)
|
|
# default
|
|
elif token_type in [tokenize.NAME, tokenize.STRING,
|
|
tokenize.NUMBER] \
|
|
or tok in statement_toks:
|
|
# this is the main part - a name can be a function or a
|
|
# normal var, which can follow anything. but this is done
|
|
# by the statement parser.
|
|
stmt, tok = self._parse_statement(self.current)
|
|
if stmt:
|
|
self.scope.add_statement(stmt)
|
|
self.freshscope = False
|
|
else:
|
|
if token_type not in [tokenize.COMMENT, tokenize.INDENT,
|
|
tokenize.NEWLINE, tokenize.NL,
|
|
tokenize.ENDMARKER]:
|
|
debug.warning('token not classified', tok, token_type,
|
|
self.start_pos[0])
|