mirror of
https://github.com/davidhalter/parso.git
synced 2025-12-08 21:54:54 +08:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4306e8b34b | ||
|
|
2ce3898690 | ||
|
|
16f257356e | ||
|
|
c864ca60d1 | ||
|
|
a47b5433d4 | ||
|
|
6982cf8321 | ||
|
|
844ca3d35a | ||
|
|
9abe5d1e55 | ||
|
|
84874aace3 | ||
|
|
55531ab65b | ||
|
|
31c059fc30 | ||
|
|
cfef1d74e7 | ||
|
|
9ee7409d8a | ||
|
|
4090c80401 | ||
|
|
95f353a15f | ||
|
|
2b0b093276 | ||
|
|
29b57d93bd | ||
|
|
d3383b6c41 | ||
|
|
9da4df20d1 | ||
|
|
0341f69691 | ||
|
|
f6bdba65c0 |
@@ -49,6 +49,7 @@ Mathias Rav (@Mortal) <rav@cs.au.dk>
|
|||||||
Daniel Fiterman (@dfit99) <fitermandaniel2@gmail.com>
|
Daniel Fiterman (@dfit99) <fitermandaniel2@gmail.com>
|
||||||
Simon Ruggier (@sruggier)
|
Simon Ruggier (@sruggier)
|
||||||
Élie Gouzien (@ElieGouzien)
|
Élie Gouzien (@ElieGouzien)
|
||||||
|
Tim Gates (@timgates42) <tim.gates@iress.com>
|
||||||
|
|
||||||
|
|
||||||
Note: (@user) means a github user name.
|
Note: (@user) means a github user name.
|
||||||
|
|||||||
@@ -3,6 +3,20 @@
|
|||||||
Changelog
|
Changelog
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
0.6.1 (2020-02-03)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
- Add ``parso.normalizer.Issue.end_pos`` to make it possible to know where an
|
||||||
|
issue ends
|
||||||
|
|
||||||
|
0.6.0 (2020-01-26)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
- Dropped Python 2.6/Python 3.3 support
|
||||||
|
- del_stmt names are now considered as a definition
|
||||||
|
(for ``name.is_definition()``)
|
||||||
|
- Bugfixes
|
||||||
|
|
||||||
0.5.2 (2019-12-15)
|
0.5.2 (2019-12-15)
|
||||||
++++++++++++++++++
|
++++++++++++++++++
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ from parso.utils import parse_version_string
|
|||||||
|
|
||||||
collect_ignore = ["setup.py"]
|
collect_ignore = ["setup.py"]
|
||||||
|
|
||||||
VERSIONS_2 = '2.6', '2.7'
|
VERSIONS_2 = '2.7',
|
||||||
VERSIONS_3 = '3.3', '3.4', '3.5', '3.6', '3.7', '3.8'
|
VERSIONS_3 = '3.4', '3.5', '3.6', '3.7', '3.8'
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ from parso.grammar import Grammar, load_grammar
|
|||||||
from parso.utils import split_lines, python_bytes_to_unicode
|
from parso.utils import split_lines, python_bytes_to_unicode
|
||||||
|
|
||||||
|
|
||||||
__version__ = '0.5.2'
|
__version__ = '0.6.1'
|
||||||
|
|
||||||
|
|
||||||
def parse(code=None, **kwargs):
|
def parse(code=None, **kwargs):
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
"""
|
"""
|
||||||
To ensure compatibility from Python ``2.6`` - ``3.3``, a module has been
|
To ensure compatibility from Python ``2.7`` - ``3.3``, a module has been
|
||||||
created. Clearly there is huge need to use conforming syntax.
|
created. Clearly there is huge need to use conforming syntax.
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
# Cannot use sys.version.major and minor names, because in Python 2.6 it's not
|
|
||||||
# a namedtuple.
|
|
||||||
py_version = int(str(sys.version_info[0]) + str(sys.version_info[1]))
|
|
||||||
|
|
||||||
# unicode function
|
# unicode function
|
||||||
try:
|
try:
|
||||||
unicode = unicode
|
unicode = unicode
|
||||||
@@ -39,7 +35,7 @@ def u(string):
|
|||||||
have to cast back to a unicode (and we know that we always deal with valid
|
have to cast back to a unicode (and we know that we always deal with valid
|
||||||
unicode, because we check that in the beginning).
|
unicode, because we check that in the beginning).
|
||||||
"""
|
"""
|
||||||
if py_version >= 30:
|
if sys.version_info.major >= 3:
|
||||||
return str(string)
|
return str(string)
|
||||||
|
|
||||||
if not isinstance(string, unicode):
|
if not isinstance(string, unicode):
|
||||||
@@ -48,8 +44,10 @@ def u(string):
|
|||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Python 2.7
|
||||||
FileNotFoundError = FileNotFoundError
|
FileNotFoundError = FileNotFoundError
|
||||||
except NameError:
|
except NameError:
|
||||||
|
# Python 3.3+
|
||||||
FileNotFoundError = IOError
|
FileNotFoundError = IOError
|
||||||
|
|
||||||
|
|
||||||
@@ -65,39 +63,7 @@ def utf8_repr(func):
|
|||||||
else:
|
else:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
if py_version >= 30:
|
if sys.version_info.major >= 3:
|
||||||
return func
|
return func
|
||||||
else:
|
else:
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from functools import total_ordering
|
|
||||||
except ImportError:
|
|
||||||
# Python 2.6
|
|
||||||
def total_ordering(cls):
|
|
||||||
"""Class decorator that fills in missing ordering methods"""
|
|
||||||
convert = {
|
|
||||||
'__lt__': [('__gt__', lambda self, other: not (self < other or self == other)),
|
|
||||||
('__le__', lambda self, other: self < other or self == other),
|
|
||||||
('__ge__', lambda self, other: not self < other)],
|
|
||||||
'__le__': [('__ge__', lambda self, other: not self <= other or self == other),
|
|
||||||
('__lt__', lambda self, other: self <= other and not self == other),
|
|
||||||
('__gt__', lambda self, other: not self <= other)],
|
|
||||||
'__gt__': [('__lt__', lambda self, other: not (self > other or self == other)),
|
|
||||||
('__ge__', lambda self, other: self > other or self == other),
|
|
||||||
('__le__', lambda self, other: not self > other)],
|
|
||||||
'__ge__': [('__le__', lambda self, other: (not self >= other) or self == other),
|
|
||||||
('__gt__', lambda self, other: self >= other and not self == other),
|
|
||||||
('__lt__', lambda self, other: not self >= other)]
|
|
||||||
}
|
|
||||||
roots = set(dir(cls)) & set(convert)
|
|
||||||
if not roots:
|
|
||||||
raise ValueError('must define at least one ordering operation: < > <= >=')
|
|
||||||
root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__
|
|
||||||
for opname, opfunc in convert[root]:
|
|
||||||
if opname not in roots:
|
|
||||||
opfunc.__name__ = opname
|
|
||||||
opfunc.__doc__ = getattr(int, opname).__doc__
|
|
||||||
setattr(cls, opname, opfunc)
|
|
||||||
return cls
|
|
||||||
|
|||||||
@@ -17,8 +17,23 @@ from parso._compatibility import FileNotFoundError
|
|||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_CACHED_FILE_MINIMUM_SURVIVAL = 60 * 10 # 10 minutes
|
||||||
|
"""
|
||||||
|
Cached files should survive at least a few minutes.
|
||||||
|
"""
|
||||||
|
_CACHED_SIZE_TRIGGER = 600
|
||||||
|
"""
|
||||||
|
This setting limits the amount of cached files. It's basically a way to start
|
||||||
|
garbage collection.
|
||||||
|
|
||||||
_PICKLE_VERSION = 32
|
The reasoning for this limit being as big as it is, is the following:
|
||||||
|
|
||||||
|
Numpy, Pandas, Matplotlib and Tensorflow together use about 500 files. This
|
||||||
|
makes Jedi use ~500mb of memory. Since we might want a bit more than those few
|
||||||
|
libraries, we just increase it a bit.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_PICKLE_VERSION = 33
|
||||||
"""
|
"""
|
||||||
Version number (integer) for file system cache.
|
Version number (integer) for file system cache.
|
||||||
|
|
||||||
@@ -40,7 +55,7 @@ _VERSION_TAG = '%s-%s%s-%s' % (
|
|||||||
"""
|
"""
|
||||||
Short name for distinguish Python implementations and versions.
|
Short name for distinguish Python implementations and versions.
|
||||||
|
|
||||||
It's like `sys.implementation.cache_tag` but for Python < 3.3
|
It's like `sys.implementation.cache_tag` but for Python2
|
||||||
we generate something similar. See:
|
we generate something similar. See:
|
||||||
http://docs.python.org/3/library/sys.html#sys.implementation
|
http://docs.python.org/3/library/sys.html#sys.implementation
|
||||||
"""
|
"""
|
||||||
@@ -76,6 +91,7 @@ class _NodeCacheItem(object):
|
|||||||
if change_time is None:
|
if change_time is None:
|
||||||
change_time = time.time()
|
change_time = time.time()
|
||||||
self.change_time = change_time
|
self.change_time = change_time
|
||||||
|
self.last_used = change_time
|
||||||
|
|
||||||
|
|
||||||
def load_module(hashed_grammar, file_io, cache_path=None):
|
def load_module(hashed_grammar, file_io, cache_path=None):
|
||||||
@@ -89,6 +105,7 @@ def load_module(hashed_grammar, file_io, cache_path=None):
|
|||||||
try:
|
try:
|
||||||
module_cache_item = parser_cache[hashed_grammar][file_io.path]
|
module_cache_item = parser_cache[hashed_grammar][file_io.path]
|
||||||
if p_time <= module_cache_item.change_time:
|
if p_time <= module_cache_item.change_time:
|
||||||
|
module_cache_item.last_used = time.time()
|
||||||
return module_cache_item.node
|
return module_cache_item.node
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return _load_from_file_system(
|
return _load_from_file_system(
|
||||||
@@ -122,11 +139,27 @@ def _load_from_file_system(hashed_grammar, path, p_time, cache_path=None):
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
parser_cache.setdefault(hashed_grammar, {})[path] = module_cache_item
|
_set_cache_item(hashed_grammar, path, module_cache_item)
|
||||||
LOG.debug('pickle loaded: %s', path)
|
LOG.debug('pickle loaded: %s', path)
|
||||||
return module_cache_item.node
|
return module_cache_item.node
|
||||||
|
|
||||||
|
|
||||||
|
def _set_cache_item(hashed_grammar, path, module_cache_item):
|
||||||
|
if sum(len(v) for v in parser_cache.values()) >= _CACHED_SIZE_TRIGGER:
|
||||||
|
# Garbage collection of old cache files.
|
||||||
|
# We are basically throwing everything away that hasn't been accessed
|
||||||
|
# in 10 minutes.
|
||||||
|
cutoff_time = time.time() - _CACHED_FILE_MINIMUM_SURVIVAL
|
||||||
|
for key, path_to_item_map in parser_cache.items():
|
||||||
|
parser_cache[key] = {
|
||||||
|
path: node_item
|
||||||
|
for path, node_item in path_to_item_map.items()
|
||||||
|
if node_item.last_used > cutoff_time
|
||||||
|
}
|
||||||
|
|
||||||
|
parser_cache.setdefault(hashed_grammar, {})[path] = module_cache_item
|
||||||
|
|
||||||
|
|
||||||
def save_module(hashed_grammar, file_io, module, lines, pickling=True, cache_path=None):
|
def save_module(hashed_grammar, file_io, module, lines, pickling=True, cache_path=None):
|
||||||
path = file_io.path
|
path = file_io.path
|
||||||
try:
|
try:
|
||||||
@@ -136,7 +169,7 @@ def save_module(hashed_grammar, file_io, module, lines, pickling=True, cache_pat
|
|||||||
pickling = False
|
pickling = False
|
||||||
|
|
||||||
item = _NodeCacheItem(module, lines, p_time)
|
item = _NodeCacheItem(module, lines, p_time)
|
||||||
parser_cache.setdefault(hashed_grammar, {})[path] = item
|
_set_cache_item(hashed_grammar, path, item)
|
||||||
if pickling and path is not None:
|
if pickling and path is not None:
|
||||||
_save_to_file_system(hashed_grammar, path, item, cache_path=cache_path)
|
_save_to_file_system(hashed_grammar, path, item, cache_path=cache_path)
|
||||||
|
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ def load_grammar(**kwargs):
|
|||||||
Loads a :py:class:`parso.Grammar`. The default version is the current Python
|
Loads a :py:class:`parso.Grammar`. The default version is the current Python
|
||||||
version.
|
version.
|
||||||
|
|
||||||
:param str version: A python version string, e.g. ``version='3.3'``.
|
:param str version: A python version string, e.g. ``version='3.8'``.
|
||||||
:param str path: A path to a grammar file
|
:param str path: A path to a grammar file
|
||||||
"""
|
"""
|
||||||
def load_grammar(language='python', version=None, path=None):
|
def load_grammar(language='python', version=None, path=None):
|
||||||
|
|||||||
@@ -119,7 +119,6 @@ class NormalizerConfig(object):
|
|||||||
|
|
||||||
class Issue(object):
|
class Issue(object):
|
||||||
def __init__(self, node, code, message):
|
def __init__(self, node, code, message):
|
||||||
self._node = node
|
|
||||||
self.code = code
|
self.code = code
|
||||||
"""
|
"""
|
||||||
An integer code that stands for the type of error.
|
An integer code that stands for the type of error.
|
||||||
@@ -133,6 +132,7 @@ class Issue(object):
|
|||||||
The start position position of the error as a tuple (line, column). As
|
The start position position of the error as a tuple (line, column). As
|
||||||
always in |parso| the first line is 1 and the first column 0.
|
always in |parso| the first line is 1 and the first column 0.
|
||||||
"""
|
"""
|
||||||
|
self.end_pos = node.end_pos
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.start_pos == other.start_pos and self.code == other.code
|
return self.start_pos == other.start_pos and self.code == other.code
|
||||||
|
|||||||
@@ -176,8 +176,7 @@ class _Context(object):
|
|||||||
self._analyze_names(self._global_names, 'global')
|
self._analyze_names(self._global_names, 'global')
|
||||||
self._analyze_names(self._nonlocal_names, 'nonlocal')
|
self._analyze_names(self._nonlocal_names, 'nonlocal')
|
||||||
|
|
||||||
# Python2.6 doesn't have dict comprehensions.
|
global_name_strs = {n.value: n for n in self._global_names}
|
||||||
global_name_strs = dict((n.value, n) for n in self._global_names)
|
|
||||||
for nonlocal_name in self._nonlocal_names:
|
for nonlocal_name in self._nonlocal_names:
|
||||||
try:
|
try:
|
||||||
global_name = global_name_strs[nonlocal_name.value]
|
global_name = global_name_strs[nonlocal_name.value]
|
||||||
@@ -864,6 +863,7 @@ class _TryStmtRule(SyntaxRule):
|
|||||||
@ErrorFinder.register_rule(type='fstring')
|
@ErrorFinder.register_rule(type='fstring')
|
||||||
class _FStringRule(SyntaxRule):
|
class _FStringRule(SyntaxRule):
|
||||||
_fstring_grammar = None
|
_fstring_grammar = None
|
||||||
|
message_expr = "f-string expression part cannot include a backslash"
|
||||||
message_nested = "f-string: expressions nested too deeply"
|
message_nested = "f-string: expressions nested too deeply"
|
||||||
message_conversion = "f-string: invalid conversion character: expected 's', 'r', or 'a'"
|
message_conversion = "f-string: invalid conversion character: expected 's', 'r', or 'a'"
|
||||||
|
|
||||||
@@ -874,6 +874,10 @@ class _FStringRule(SyntaxRule):
|
|||||||
if depth >= 2:
|
if depth >= 2:
|
||||||
self.add_issue(fstring_expr, message=self.message_nested)
|
self.add_issue(fstring_expr, message=self.message_nested)
|
||||||
|
|
||||||
|
expr = fstring_expr.children[1]
|
||||||
|
if '\\' in expr.get_code():
|
||||||
|
self.add_issue(expr, message=self.message_expr)
|
||||||
|
|
||||||
conversion = fstring_expr.children[2]
|
conversion = fstring_expr.children[2]
|
||||||
if conversion.type == 'fstring_conversion':
|
if conversion.type == 'fstring_conversion':
|
||||||
name = conversion.children[1]
|
name = conversion.children[1]
|
||||||
@@ -915,6 +919,14 @@ class _CheckAssignmentRule(SyntaxRule):
|
|||||||
if second.type == 'yield_expr':
|
if second.type == 'yield_expr':
|
||||||
error = 'yield expression'
|
error = 'yield expression'
|
||||||
elif second.type == 'testlist_comp':
|
elif second.type == 'testlist_comp':
|
||||||
|
# ([a, b] := [1, 2])
|
||||||
|
# ((a, b) := [1, 2])
|
||||||
|
if is_namedexpr:
|
||||||
|
if first == '(':
|
||||||
|
error = 'tuple'
|
||||||
|
elif first == '[':
|
||||||
|
error = 'list'
|
||||||
|
|
||||||
# This is not a comprehension, they were handled
|
# This is not a comprehension, they were handled
|
||||||
# further above.
|
# further above.
|
||||||
for child in second.children[::2]:
|
for child in second.children[::2]:
|
||||||
@@ -964,6 +976,8 @@ class _CheckAssignmentRule(SyntaxRule):
|
|||||||
|
|
||||||
if error is not None:
|
if error is not None:
|
||||||
if is_namedexpr:
|
if is_namedexpr:
|
||||||
|
# c.f. CPython bpo-39176, should be changed in next release
|
||||||
|
# message = 'cannot use assignment expressions with %s' % error
|
||||||
message = 'cannot use named assignment with %s' % error
|
message = 'cannot use named assignment with %s' % error
|
||||||
else:
|
else:
|
||||||
cannot = "can't" if self._normalizer.version < (3, 8) else "cannot"
|
cannot = "can't" if self._normalizer.version < (3, 8) else "cannot"
|
||||||
|
|||||||
@@ -1,159 +0,0 @@
|
|||||||
# Grammar for Python
|
|
||||||
|
|
||||||
# Note: Changing the grammar specified in this file will most likely
|
|
||||||
# require corresponding changes in the parser module
|
|
||||||
# (../Modules/parsermodule.c). If you can't make the changes to
|
|
||||||
# that module yourself, please co-ordinate the required changes
|
|
||||||
# with someone who can; ask around on python-dev for help. Fred
|
|
||||||
# Drake <fdrake@acm.org> will probably be listening there.
|
|
||||||
|
|
||||||
# NOTE WELL: You should also follow all the steps listed in PEP 306,
|
|
||||||
# "How to Change Python's Grammar"
|
|
||||||
|
|
||||||
# Commands for Kees Blom's railroad program
|
|
||||||
#diagram:token NAME
|
|
||||||
#diagram:token NUMBER
|
|
||||||
#diagram:token STRING
|
|
||||||
#diagram:token NEWLINE
|
|
||||||
#diagram:token ENDMARKER
|
|
||||||
#diagram:token INDENT
|
|
||||||
#diagram:output\input python.bla
|
|
||||||
#diagram:token DEDENT
|
|
||||||
#diagram:output\textwidth 20.04cm\oddsidemargin 0.0cm\evensidemargin 0.0cm
|
|
||||||
#diagram:rules
|
|
||||||
|
|
||||||
# Start symbols for the grammar:
|
|
||||||
# single_input is a single interactive statement;
|
|
||||||
# file_input is a module or sequence of commands read from an input file;
|
|
||||||
# eval_input is the input for the eval() and input() functions.
|
|
||||||
# NB: compound_stmt in single_input is followed by extra NEWLINE!
|
|
||||||
single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
|
|
||||||
file_input: (NEWLINE | stmt)* ENDMARKER
|
|
||||||
eval_input: testlist NEWLINE* ENDMARKER
|
|
||||||
|
|
||||||
decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
|
|
||||||
decorators: decorator+
|
|
||||||
decorated: decorators (classdef | funcdef)
|
|
||||||
funcdef: 'def' NAME parameters ':' suite
|
|
||||||
parameters: '(' [varargslist] ')'
|
|
||||||
varargslist: ((fpdef ['=' test] ',')*
|
|
||||||
('*' NAME [',' '**' NAME] | '**' NAME) |
|
|
||||||
fpdef ['=' test] (',' fpdef ['=' test])* [','])
|
|
||||||
fpdef: NAME | '(' fplist ')'
|
|
||||||
fplist: fpdef (',' fpdef)* [',']
|
|
||||||
|
|
||||||
stmt: simple_stmt | compound_stmt
|
|
||||||
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
|
|
||||||
small_stmt: (expr_stmt | print_stmt | del_stmt | pass_stmt | flow_stmt |
|
|
||||||
import_stmt | global_stmt | exec_stmt | assert_stmt)
|
|
||||||
expr_stmt: testlist (augassign (yield_expr|testlist) |
|
|
||||||
('=' (yield_expr|testlist))*)
|
|
||||||
augassign: ('+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' |
|
|
||||||
'<<=' | '>>=' | '**=' | '//=')
|
|
||||||
# For normal assignments, additional restrictions enforced by the interpreter
|
|
||||||
print_stmt: 'print' ( [ test (',' test)* [','] ] |
|
|
||||||
'>>' test [ (',' test)+ [','] ] )
|
|
||||||
del_stmt: 'del' exprlist
|
|
||||||
pass_stmt: 'pass'
|
|
||||||
flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt
|
|
||||||
break_stmt: 'break'
|
|
||||||
continue_stmt: 'continue'
|
|
||||||
return_stmt: 'return' [testlist]
|
|
||||||
yield_stmt: yield_expr
|
|
||||||
raise_stmt: 'raise' [test [',' test [',' test]]]
|
|
||||||
import_stmt: import_name | import_from
|
|
||||||
import_name: 'import' dotted_as_names
|
|
||||||
import_from: ('from' ('.'* dotted_name | '.'+)
|
|
||||||
'import' ('*' | '(' import_as_names ')' | import_as_names))
|
|
||||||
import_as_name: NAME ['as' NAME]
|
|
||||||
dotted_as_name: dotted_name ['as' NAME]
|
|
||||||
import_as_names: import_as_name (',' import_as_name)* [',']
|
|
||||||
dotted_as_names: dotted_as_name (',' dotted_as_name)*
|
|
||||||
dotted_name: NAME ('.' NAME)*
|
|
||||||
global_stmt: 'global' NAME (',' NAME)*
|
|
||||||
exec_stmt: 'exec' expr ['in' test [',' test]]
|
|
||||||
assert_stmt: 'assert' test [',' test]
|
|
||||||
|
|
||||||
compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated
|
|
||||||
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
|
|
||||||
while_stmt: 'while' test ':' suite ['else' ':' suite]
|
|
||||||
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
|
|
||||||
try_stmt: ('try' ':' suite
|
|
||||||
((except_clause ':' suite)+
|
|
||||||
['else' ':' suite]
|
|
||||||
['finally' ':' suite] |
|
|
||||||
'finally' ':' suite))
|
|
||||||
with_stmt: 'with' with_item ':' suite
|
|
||||||
# Dave: Python2.6 actually defines a little bit of a different label called
|
|
||||||
# 'with_var'. However in 2.7+ this is the default. Apply it for
|
|
||||||
# consistency reasons.
|
|
||||||
with_item: test ['as' expr]
|
|
||||||
# NB compile.c makes sure that the default except clause is last
|
|
||||||
except_clause: 'except' [test [('as' | ',') test]]
|
|
||||||
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
|
|
||||||
|
|
||||||
# Backward compatibility cruft to support:
|
|
||||||
# [ x for x in lambda: True, lambda: False if x() ]
|
|
||||||
# even while also allowing:
|
|
||||||
# lambda x: 5 if x else 2
|
|
||||||
# (But not a mix of the two)
|
|
||||||
testlist_safe: old_test [(',' old_test)+ [',']]
|
|
||||||
old_test: or_test | old_lambdef
|
|
||||||
old_lambdef: 'lambda' [varargslist] ':' old_test
|
|
||||||
|
|
||||||
test: or_test ['if' or_test 'else' test] | lambdef
|
|
||||||
or_test: and_test ('or' and_test)*
|
|
||||||
and_test: not_test ('and' not_test)*
|
|
||||||
not_test: 'not' not_test | comparison
|
|
||||||
comparison: expr (comp_op expr)*
|
|
||||||
comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not'
|
|
||||||
expr: xor_expr ('|' xor_expr)*
|
|
||||||
xor_expr: and_expr ('^' and_expr)*
|
|
||||||
and_expr: shift_expr ('&' shift_expr)*
|
|
||||||
shift_expr: arith_expr (('<<'|'>>') arith_expr)*
|
|
||||||
arith_expr: term (('+'|'-') term)*
|
|
||||||
term: factor (('*'|'/'|'%'|'//') factor)*
|
|
||||||
factor: ('+'|'-'|'~') factor | power
|
|
||||||
power: atom trailer* ['**' factor]
|
|
||||||
atom: ('(' [yield_expr|testlist_comp] ')' |
|
|
||||||
'[' [listmaker] ']' |
|
|
||||||
'{' [dictorsetmaker] '}' |
|
|
||||||
'`' testlist1 '`' |
|
|
||||||
NAME | NUMBER | strings)
|
|
||||||
strings: STRING+
|
|
||||||
listmaker: test ( list_for | (',' test)* [','] )
|
|
||||||
# Dave: Renamed testlist_gexpr to testlist_comp, because in 2.7+ this is the
|
|
||||||
# default. It's more consistent like this.
|
|
||||||
testlist_comp: test ( gen_for | (',' test)* [','] )
|
|
||||||
lambdef: 'lambda' [varargslist] ':' test
|
|
||||||
trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
|
|
||||||
subscriptlist: subscript (',' subscript)* [',']
|
|
||||||
subscript: '.' '.' '.' | test | [test] ':' [test] [sliceop]
|
|
||||||
sliceop: ':' [test]
|
|
||||||
exprlist: expr (',' expr)* [',']
|
|
||||||
testlist: test (',' test)* [',']
|
|
||||||
# Dave: Rename from dictmaker to dictorsetmaker, because this is more
|
|
||||||
# consistent with the following grammars.
|
|
||||||
dictorsetmaker: test ':' test (',' test ':' test)* [',']
|
|
||||||
|
|
||||||
classdef: 'class' NAME ['(' [testlist] ')'] ':' suite
|
|
||||||
|
|
||||||
arglist: (argument ',')* (argument [',']
|
|
||||||
|'*' test (',' argument)* [',' '**' test]
|
|
||||||
|'**' test)
|
|
||||||
argument: test [gen_for] | test '=' test # Really [keyword '='] test
|
|
||||||
|
|
||||||
list_iter: list_for | list_if
|
|
||||||
list_for: 'for' exprlist 'in' testlist_safe [list_iter]
|
|
||||||
list_if: 'if' old_test [list_iter]
|
|
||||||
|
|
||||||
gen_iter: gen_for | gen_if
|
|
||||||
gen_for: 'for' exprlist 'in' or_test [gen_iter]
|
|
||||||
gen_if: 'if' old_test [gen_iter]
|
|
||||||
|
|
||||||
testlist1: test (',' test)*
|
|
||||||
|
|
||||||
# not used in grammar, but may appear in "node" passed from Parser to Compiler
|
|
||||||
encoding_decl: NAME
|
|
||||||
|
|
||||||
yield_expr: 'yield' [testlist]
|
|
||||||
@@ -172,5 +172,5 @@ A list of syntax/indentation errors I've encountered in CPython.
|
|||||||
Version specific:
|
Version specific:
|
||||||
Python 3.5:
|
Python 3.5:
|
||||||
'yield' inside async function
|
'yield' inside async function
|
||||||
Python 3.3/3.4:
|
Python 3.4:
|
||||||
can use starred expression only as assignment target
|
can use starred expression only as assignment target
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ class Parser(BaseParser):
|
|||||||
# avoid extreme amounts of work around the subtle difference of 2/3
|
# avoid extreme amounts of work around the subtle difference of 2/3
|
||||||
# grammar in list comoprehensions.
|
# grammar in list comoprehensions.
|
||||||
'list_for': tree.SyncCompFor,
|
'list_for': tree.SyncCompFor,
|
||||||
# Same here. This just exists in Python 2.6.
|
|
||||||
'gen_for': tree.SyncCompFor,
|
|
||||||
'decorator': tree.Decorator,
|
'decorator': tree.Decorator,
|
||||||
'lambdef': tree.Lambda,
|
'lambdef': tree.Lambda,
|
||||||
'old_lambdef': tree.Lambda,
|
'old_lambdef': tree.Lambda,
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import itertools as _itertools
|
|||||||
from codecs import BOM_UTF8
|
from codecs import BOM_UTF8
|
||||||
|
|
||||||
from parso.python.token import PythonTokenTypes
|
from parso.python.token import PythonTokenTypes
|
||||||
from parso._compatibility import py_version
|
|
||||||
from parso.utils import split_lines
|
from parso.utils import split_lines
|
||||||
|
|
||||||
|
|
||||||
@@ -50,7 +49,7 @@ BOM_UTF8_STRING = BOM_UTF8.decode('utf-8')
|
|||||||
|
|
||||||
_token_collection_cache = {}
|
_token_collection_cache = {}
|
||||||
|
|
||||||
if py_version >= 30:
|
if sys.version_info.major >= 3:
|
||||||
# Python 3 has str.isidentifier() to check if a char is a valid identifier
|
# Python 3 has str.isidentifier() to check if a char is a valid identifier
|
||||||
is_identifier = str.isidentifier
|
is_identifier = str.isidentifier
|
||||||
else:
|
else:
|
||||||
@@ -86,7 +85,7 @@ def _all_string_prefixes(version_info, include_fstring=False, only_fstring=False
|
|||||||
# and don't contain any permuations (include 'fr', but not
|
# and don't contain any permuations (include 'fr', but not
|
||||||
# 'rf'). The various permutations will be generated.
|
# 'rf'). The various permutations will be generated.
|
||||||
valid_string_prefixes = ['b', 'r', 'u']
|
valid_string_prefixes = ['b', 'r', 'u']
|
||||||
if version_info >= (3, 0):
|
if version_info.major >= 3:
|
||||||
valid_string_prefixes.append('br')
|
valid_string_prefixes.append('br')
|
||||||
|
|
||||||
result = set([''])
|
result = set([''])
|
||||||
@@ -106,7 +105,7 @@ def _all_string_prefixes(version_info, include_fstring=False, only_fstring=False
|
|||||||
# create a list with upper and lower versions of each
|
# create a list with upper and lower versions of each
|
||||||
# character
|
# character
|
||||||
result.update(different_case_versions(t))
|
result.update(different_case_versions(t))
|
||||||
if version_info <= (2, 7):
|
if version_info.major == 2:
|
||||||
# In Python 2 the order cannot just be random.
|
# In Python 2 the order cannot just be random.
|
||||||
result.update(different_case_versions('ur'))
|
result.update(different_case_versions('ur'))
|
||||||
result.update(different_case_versions('br'))
|
result.update(different_case_versions('br'))
|
||||||
@@ -164,7 +163,7 @@ def _create_token_collection(version_info):
|
|||||||
else:
|
else:
|
||||||
Hexnumber = r'0[xX][0-9a-fA-F]+'
|
Hexnumber = r'0[xX][0-9a-fA-F]+'
|
||||||
Binnumber = r'0[bB][01]+'
|
Binnumber = r'0[bB][01]+'
|
||||||
if version_info >= (3, 0):
|
if version_info.major >= 3:
|
||||||
Octnumber = r'0[oO][0-7]+'
|
Octnumber = r'0[oO][0-7]+'
|
||||||
else:
|
else:
|
||||||
Octnumber = '0[oO]?[0-7]+'
|
Octnumber = '0[oO]?[0-7]+'
|
||||||
|
|||||||
@@ -57,10 +57,14 @@ from parso.utils import split_lines
|
|||||||
_FLOW_CONTAINERS = set(['if_stmt', 'while_stmt', 'for_stmt', 'try_stmt',
|
_FLOW_CONTAINERS = set(['if_stmt', 'while_stmt', 'for_stmt', 'try_stmt',
|
||||||
'with_stmt', 'async_stmt', 'suite'])
|
'with_stmt', 'async_stmt', 'suite'])
|
||||||
_RETURN_STMT_CONTAINERS = set(['suite', 'simple_stmt']) | _FLOW_CONTAINERS
|
_RETURN_STMT_CONTAINERS = set(['suite', 'simple_stmt']) | _FLOW_CONTAINERS
|
||||||
_FUNC_CONTAINERS = set(['suite', 'simple_stmt', 'decorated']) | _FLOW_CONTAINERS
|
|
||||||
|
_FUNC_CONTAINERS = set(
|
||||||
|
['suite', 'simple_stmt', 'decorated', 'async_funcdef']
|
||||||
|
) | _FLOW_CONTAINERS
|
||||||
|
|
||||||
_GET_DEFINITION_TYPES = set([
|
_GET_DEFINITION_TYPES = set([
|
||||||
'expr_stmt', 'sync_comp_for', 'with_stmt', 'for_stmt', 'import_name',
|
'expr_stmt', 'sync_comp_for', 'with_stmt', 'for_stmt', 'import_name',
|
||||||
'import_from', 'param'
|
'import_from', 'param', 'del_stmt',
|
||||||
])
|
])
|
||||||
_IMPORTS = set(['import_name', 'import_from'])
|
_IMPORTS = set(['import_name', 'import_from'])
|
||||||
|
|
||||||
@@ -95,7 +99,7 @@ class DocstringMixin(object):
|
|||||||
|
|
||||||
class PythonMixin(object):
|
class PythonMixin(object):
|
||||||
"""
|
"""
|
||||||
Some Python specific utitilies.
|
Some Python specific utilities.
|
||||||
"""
|
"""
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
@@ -233,6 +237,8 @@ class Name(_LeafWithoutNewlines):
|
|||||||
while node is not None:
|
while node is not None:
|
||||||
if node.type == 'suite':
|
if node.type == 'suite':
|
||||||
return None
|
return None
|
||||||
|
if node.type == 'namedexpr_test':
|
||||||
|
return node.children[0]
|
||||||
if node.type in _GET_DEFINITION_TYPES:
|
if node.type in _GET_DEFINITION_TYPES:
|
||||||
if self in node.get_defined_names(include_setitem):
|
if self in node.get_defined_names(include_setitem):
|
||||||
return node
|
return node
|
||||||
@@ -993,6 +999,14 @@ class KeywordStatement(PythonBaseNode):
|
|||||||
def keyword(self):
|
def keyword(self):
|
||||||
return self.children[0].value
|
return self.children[0].value
|
||||||
|
|
||||||
|
def get_defined_names(self, include_setitem=False):
|
||||||
|
keyword = self.keyword
|
||||||
|
if keyword == 'del':
|
||||||
|
return _defined_names(self.children[1], include_setitem)
|
||||||
|
if keyword in ('global', 'nonlocal'):
|
||||||
|
return self.children[1::2]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class AssertStmt(KeywordStatement):
|
class AssertStmt(KeywordStatement):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import sys
|
||||||
from abc import abstractmethod, abstractproperty
|
from abc import abstractmethod, abstractproperty
|
||||||
|
|
||||||
from parso._compatibility import utf8_repr, encoding, py_version
|
from parso._compatibility import utf8_repr, encoding
|
||||||
from parso.utils import split_lines
|
from parso.utils import split_lines
|
||||||
|
|
||||||
|
|
||||||
@@ -321,7 +322,7 @@ class BaseNode(NodeOrLeaf):
|
|||||||
@utf8_repr
|
@utf8_repr
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
code = self.get_code().replace('\n', ' ').replace('\r', ' ').strip()
|
code = self.get_code().replace('\n', ' ').replace('\r', ' ').strip()
|
||||||
if not py_version >= 30:
|
if not sys.version_info.major >= 3:
|
||||||
code = code.encode(encoding, 'replace')
|
code = code.encode(encoding, 'replace')
|
||||||
return "<%s: %s@%s,%s>" % \
|
return "<%s: %s@%s,%s>" % \
|
||||||
(type(self).__name__, code, self.start_pos[0], self.start_pos[1])
|
(type(self).__name__, code, self.start_pos[0], self.start_pos[1])
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ from collections import namedtuple
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from ast import literal_eval
|
from ast import literal_eval
|
||||||
|
from functools import total_ordering
|
||||||
|
|
||||||
from parso._compatibility import unicode, total_ordering
|
from parso._compatibility import unicode
|
||||||
|
|
||||||
# The following is a list in Python that are line breaks in str.splitlines, but
|
# The following is a list in Python that are line breaks in str.splitlines, but
|
||||||
# not in Python. In Python only \r (Carriage Return, 0xD) and \n (Line Feed,
|
# not in Python. In Python only \r (Carriage Return, 0xD) and \n (Line Feed,
|
||||||
@@ -122,7 +123,7 @@ def _parse_version(version):
|
|||||||
match = re.match(r'(\d+)(?:\.(\d)(?:\.\d+)?)?$', version)
|
match = re.match(r'(\d+)(?:\.(\d)(?:\.\d+)?)?$', version)
|
||||||
if match is None:
|
if match is None:
|
||||||
raise ValueError('The given version is not in the right format. '
|
raise ValueError('The given version is not in the right format. '
|
||||||
'Use something like "3.2" or "3".')
|
'Use something like "3.8" or "3".')
|
||||||
|
|
||||||
major = int(match.group(1))
|
major = int(match.group(1))
|
||||||
minor = match.group(2)
|
minor = match.group(2)
|
||||||
@@ -163,13 +164,13 @@ class PythonVersionInfo(namedtuple('Version', 'major, minor')):
|
|||||||
|
|
||||||
def parse_version_string(version=None):
|
def parse_version_string(version=None):
|
||||||
"""
|
"""
|
||||||
Checks for a valid version number (e.g. `3.2` or `2.7.1` or `3`) and
|
Checks for a valid version number (e.g. `3.8` or `2.7.1` or `3`) and
|
||||||
returns a corresponding version info that is always two characters long in
|
returns a corresponding version info that is always two characters long in
|
||||||
decimal.
|
decimal.
|
||||||
"""
|
"""
|
||||||
if version is None:
|
if version is None:
|
||||||
version = '%s.%s' % sys.version_info[:2]
|
version = '%s.%s' % sys.version_info[:2]
|
||||||
if not isinstance(version, (unicode, str)):
|
if not isinstance(version, (unicode, str)):
|
||||||
raise TypeError("version must be a string like 3.2.")
|
raise TypeError('version must be a string like "3.8"')
|
||||||
|
|
||||||
return _parse_version(version)
|
return _parse_version(version)
|
||||||
|
|||||||
3
setup.py
3
setup.py
@@ -27,6 +27,7 @@ setup(name='parso',
|
|||||||
packages=find_packages(exclude=['test']),
|
packages=find_packages(exclude=['test']),
|
||||||
package_data={'parso': ['python/grammar*.txt']},
|
package_data={'parso': ['python/grammar*.txt']},
|
||||||
platforms=['any'],
|
platforms=['any'],
|
||||||
|
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 4 - Beta',
|
'Development Status :: 4 - Beta',
|
||||||
'Environment :: Plugins',
|
'Environment :: Plugins',
|
||||||
@@ -34,10 +35,8 @@ setup(name='parso',
|
|||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python :: 2',
|
'Programming Language :: Python :: 2',
|
||||||
'Programming Language :: Python :: 2.6',
|
|
||||||
'Programming Language :: Python :: 2.7',
|
'Programming Language :: Python :: 2.7',
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3',
|
||||||
'Programming Language :: Python :: 3.3',
|
|
||||||
'Programming Language :: Python :: 3.4',
|
'Programming Language :: Python :: 3.4',
|
||||||
'Programming Language :: Python :: 3.5',
|
'Programming Language :: Python :: 3.5',
|
||||||
'Programming Language :: Python :: 3.6',
|
'Programming Language :: Python :: 3.6',
|
||||||
|
|||||||
@@ -281,11 +281,11 @@ if sys.version_info >= (3, 6):
|
|||||||
# Same as above, but for f-strings.
|
# Same as above, but for f-strings.
|
||||||
'f"s" b""',
|
'f"s" b""',
|
||||||
'b"s" f""',
|
'b"s" f""',
|
||||||
|
|
||||||
|
# f-string expression part cannot include a backslash
|
||||||
|
r'''f"{'\n'}"''',
|
||||||
]
|
]
|
||||||
if sys.version_info >= (2, 7):
|
FAILING_EXAMPLES.append('[a, 1] += 3')
|
||||||
# This is something that raises a different error in 2.6 than in the other
|
|
||||||
# versions. Just skip it for 2.6.
|
|
||||||
FAILING_EXAMPLES.append('[a, 1] += 3')
|
|
||||||
|
|
||||||
if sys.version_info[:2] == (3, 5):
|
if sys.version_info[:2] == (3, 5):
|
||||||
# yields are not allowed in 3.5 async functions. Therefore test them
|
# yields are not allowed in 3.5 async functions. Therefore test them
|
||||||
@@ -350,4 +350,14 @@ if sys.version_info[:2] >= (3, 8):
|
|||||||
# Not in that issue
|
# Not in that issue
|
||||||
'(await a := x)',
|
'(await a := x)',
|
||||||
'((await a) := x)',
|
'((await a) := x)',
|
||||||
|
# new discoveries
|
||||||
|
'((a, b) := (1, 2))',
|
||||||
|
'([a, b] := [1, 2])',
|
||||||
|
'({a, b} := {1, 2})',
|
||||||
|
'({a: b} := {1: 2})',
|
||||||
|
'(a + b := 1)',
|
||||||
|
'(True := 1)',
|
||||||
|
'(False := 1)',
|
||||||
|
'(None := 1)',
|
||||||
|
'(__debug__ := 1)',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ Test all things related to the ``jedi.cache`` module.
|
|||||||
from os import unlink
|
from os import unlink
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import time
|
||||||
|
|
||||||
from parso.cache import _NodeCacheItem, save_module, load_module, \
|
from parso.cache import _NodeCacheItem, save_module, load_module, \
|
||||||
_get_hashed_path, parser_cache, _load_from_file_system, _save_to_file_system
|
_get_hashed_path, parser_cache, _load_from_file_system, _save_to_file_system
|
||||||
from parso import load_grammar
|
from parso import load_grammar
|
||||||
from parso import cache
|
from parso import cache
|
||||||
from parso import file_io
|
from parso import file_io
|
||||||
|
from parso import parse
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
@@ -87,3 +89,53 @@ def test_modulepickling_simulate_deleted_cache(tmpdir):
|
|||||||
|
|
||||||
cached2 = load_module(grammar._hashed, io)
|
cached2 = load_module(grammar._hashed, io)
|
||||||
assert cached2 is None
|
assert cached2 is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_cache_limit():
|
||||||
|
def cache_size():
|
||||||
|
return sum(len(v) for v in parser_cache.values())
|
||||||
|
|
||||||
|
try:
|
||||||
|
parser_cache.clear()
|
||||||
|
future_node_cache_item = _NodeCacheItem('bla', [], change_time=time.time() + 10e6)
|
||||||
|
old_node_cache_item = _NodeCacheItem('bla', [], change_time=time.time() - 10e4)
|
||||||
|
parser_cache['some_hash_old'] = {
|
||||||
|
'/path/%s' % i: old_node_cache_item for i in range(300)
|
||||||
|
}
|
||||||
|
parser_cache['some_hash_new'] = {
|
||||||
|
'/path/%s' % i: future_node_cache_item for i in range(300)
|
||||||
|
}
|
||||||
|
assert cache_size() == 600
|
||||||
|
parse('somecode', cache=True, path='/path/somepath')
|
||||||
|
assert cache_size() == 301
|
||||||
|
finally:
|
||||||
|
parser_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
|
class _FixedTimeFileIO(file_io.KnownContentFileIO):
|
||||||
|
def __init__(self, path, content, last_modified):
|
||||||
|
super(_FixedTimeFileIO, self).__init__(path, content)
|
||||||
|
self._last_modified = last_modified
|
||||||
|
|
||||||
|
def get_last_modified(self):
|
||||||
|
return self._last_modified
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('diff_cache', [False, True])
|
||||||
|
@pytest.mark.parametrize('use_file_io', [False, True])
|
||||||
|
def test_cache_last_used_update(diff_cache, use_file_io):
|
||||||
|
p = '/path/last-used'
|
||||||
|
parser_cache.clear() # Clear, because then it's easier to find stuff.
|
||||||
|
parse('somecode', cache=True, path=p)
|
||||||
|
node_cache_item = next(iter(parser_cache.values()))[p]
|
||||||
|
now = time.time()
|
||||||
|
assert node_cache_item.last_used < now
|
||||||
|
|
||||||
|
if use_file_io:
|
||||||
|
f = _FixedTimeFileIO(p, 'code', node_cache_item.last_used - 10)
|
||||||
|
parse(file_io=f, cache=True, diff_cache=diff_cache)
|
||||||
|
else:
|
||||||
|
parse('somecode2', cache=True, path=p, diff_cache=diff_cache)
|
||||||
|
|
||||||
|
node_cache_item = next(iter(parser_cache.values()))[p]
|
||||||
|
assert now < node_cache_item.last_used < time.time()
|
||||||
|
|||||||
@@ -989,7 +989,6 @@ def test_random_unicode_characters(differ):
|
|||||||
differ.parse(' a( # xx\ndef', parsers=2, expect_error_leaves=True)
|
differ.parse(' a( # xx\ndef', parsers=2, expect_error_leaves=True)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.version_info < (2, 7), reason="No set literals in Python 2.6")
|
|
||||||
def test_dedent_end_positions(differ):
|
def test_dedent_end_positions(differ):
|
||||||
code1 = dedent('''\
|
code1 = dedent('''\
|
||||||
if 1:
|
if 1:
|
||||||
|
|||||||
@@ -28,4 +28,4 @@ def test_invalid_grammar_version(string):
|
|||||||
|
|
||||||
def test_grammar_int_version():
|
def test_grammar_int_version():
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
load_grammar(version=3.2)
|
load_grammar(version=3.8)
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ tests of pydocstyle.
|
|||||||
|
|
||||||
import difflib
|
import difflib
|
||||||
import re
|
import re
|
||||||
|
from functools import total_ordering
|
||||||
|
|
||||||
import parso
|
import parso
|
||||||
from parso._compatibility import total_ordering
|
|
||||||
from parso.utils import python_bytes_to_unicode
|
from parso.utils import python_bytes_to_unicode
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ def test_yields(each_version):
|
|||||||
|
|
||||||
|
|
||||||
def test_yield_from():
|
def test_yield_from():
|
||||||
y, = get_yield_exprs('def x(): (yield from 1)', '3.3')
|
y, = get_yield_exprs('def x(): (yield from 1)', '3.8')
|
||||||
assert y.type == 'yield_expr'
|
assert y.type == 'yield_expr'
|
||||||
|
|
||||||
|
|
||||||
@@ -222,3 +222,19 @@ def test_is_definition(code, name_index, is_definition, include_setitem):
|
|||||||
name = name.get_next_leaf()
|
name = name.get_next_leaf()
|
||||||
|
|
||||||
assert name.is_definition(include_setitem=include_setitem) == is_definition
|
assert name.is_definition(include_setitem=include_setitem) == is_definition
|
||||||
|
|
||||||
|
|
||||||
|
def test_iter_funcdefs():
|
||||||
|
code = dedent('''
|
||||||
|
def normal(): ...
|
||||||
|
async def asyn(): ...
|
||||||
|
@dec
|
||||||
|
def dec_normal(): ...
|
||||||
|
@dec1
|
||||||
|
@dec2
|
||||||
|
async def dec_async(): ...
|
||||||
|
def broken
|
||||||
|
''')
|
||||||
|
module = parse(code, version='3.8')
|
||||||
|
func_names = [f.name.value for f in module.iter_funcdefs()]
|
||||||
|
assert func_names == ['normal', 'asyn', 'dec_normal', 'dec_async']
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ def test_python_exception_matches(code):
|
|||||||
error, = errors
|
error, = errors
|
||||||
actual = error.message
|
actual = error.message
|
||||||
assert actual in wanted
|
assert actual in wanted
|
||||||
# Somehow in Python3.3 the SyntaxError().lineno is sometimes None
|
# Somehow in Python2.7 the SyntaxError().lineno is sometimes None
|
||||||
assert line_nr is None or line_nr == error.start_pos[0]
|
assert line_nr is None or line_nr == error.start_pos[0]
|
||||||
|
|
||||||
|
|
||||||
@@ -118,22 +118,12 @@ def _get_actual_exception(code):
|
|||||||
assert False, "The piece of code should raise an exception."
|
assert False, "The piece of code should raise an exception."
|
||||||
|
|
||||||
# SyntaxError
|
# SyntaxError
|
||||||
# Python 2.6 has a bit different error messages here, so skip it.
|
|
||||||
if sys.version_info[:2] == (2, 6) and wanted == 'SyntaxError: unexpected EOF while parsing':
|
|
||||||
wanted = 'SyntaxError: invalid syntax'
|
|
||||||
|
|
||||||
if wanted == 'SyntaxError: non-keyword arg after keyword arg':
|
if wanted == 'SyntaxError: non-keyword arg after keyword arg':
|
||||||
# The python 3.5+ way, a bit nicer.
|
# The python 3.5+ way, a bit nicer.
|
||||||
wanted = 'SyntaxError: positional argument follows keyword argument'
|
wanted = 'SyntaxError: positional argument follows keyword argument'
|
||||||
elif wanted == 'SyntaxError: assignment to keyword':
|
elif wanted == 'SyntaxError: assignment to keyword':
|
||||||
return [wanted, "SyntaxError: can't assign to keyword",
|
return [wanted, "SyntaxError: can't assign to keyword",
|
||||||
'SyntaxError: cannot assign to __debug__'], line_nr
|
'SyntaxError: cannot assign to __debug__'], line_nr
|
||||||
elif wanted == 'SyntaxError: assignment to None':
|
|
||||||
# Python 2.6 does has a slightly different error.
|
|
||||||
wanted = 'SyntaxError: cannot assign to None'
|
|
||||||
elif wanted == 'SyntaxError: can not assign to __debug__':
|
|
||||||
# Python 2.6 does has a slightly different error.
|
|
||||||
wanted = 'SyntaxError: cannot assign to __debug__'
|
|
||||||
elif wanted == 'SyntaxError: can use starred expression only as assignment target':
|
elif wanted == 'SyntaxError: can use starred expression only as assignment target':
|
||||||
# Python 3.4/3.4 have a bit of a different warning than 3.5/3.6 in
|
# Python 3.4/3.4 have a bit of a different warning than 3.5/3.6 in
|
||||||
# certain places. But in others this error makes sense.
|
# certain places. But in others this error makes sense.
|
||||||
@@ -331,4 +321,3 @@ def test_invalid_fstrings(code, message):
|
|||||||
def test_trailing_comma(code):
|
def test_trailing_comma(code):
|
||||||
errors = _get_error_list(code)
|
errors = _get_error_list(code)
|
||||||
assert not errors
|
assert not errors
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import sys
|
|||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import sys
|
||||||
|
|
||||||
from parso._compatibility import py_version
|
|
||||||
from parso.utils import split_lines, parse_version_string
|
from parso.utils import split_lines, parse_version_string
|
||||||
from parso.python.token import PythonTokenTypes
|
from parso.python.token import PythonTokenTypes
|
||||||
from parso.python import tokenize
|
from parso.python import tokenize
|
||||||
@@ -137,7 +137,7 @@ def test_identifier_contains_unicode():
|
|||||||
''')
|
''')
|
||||||
token_list = _get_token_list(fundef)
|
token_list = _get_token_list(fundef)
|
||||||
unicode_token = token_list[1]
|
unicode_token = token_list[1]
|
||||||
if py_version >= 30:
|
if sys.version_info.major >= 3:
|
||||||
assert unicode_token[0] == NAME
|
assert unicode_token[0] == NAME
|
||||||
else:
|
else:
|
||||||
# Unicode tokens in Python 2 seem to be identified as operators.
|
# Unicode tokens in Python 2 seem to be identified as operators.
|
||||||
@@ -185,19 +185,19 @@ def test_ur_literals():
|
|||||||
assert typ == NAME
|
assert typ == NAME
|
||||||
|
|
||||||
check('u""')
|
check('u""')
|
||||||
check('ur""', is_literal=not py_version >= 30)
|
check('ur""', is_literal=not sys.version_info.major >= 3)
|
||||||
check('Ur""', is_literal=not py_version >= 30)
|
check('Ur""', is_literal=not sys.version_info.major >= 3)
|
||||||
check('UR""', is_literal=not py_version >= 30)
|
check('UR""', is_literal=not sys.version_info.major >= 3)
|
||||||
check('bR""')
|
check('bR""')
|
||||||
# Starting with Python 3.3 this ordering is also possible.
|
# Starting with Python 3.3 this ordering is also possible.
|
||||||
if py_version >= 33:
|
if sys.version_info.major >= 3:
|
||||||
check('Rb""')
|
check('Rb""')
|
||||||
|
|
||||||
# Starting with Python 3.6 format strings where introduced.
|
# Starting with Python 3.6 format strings where introduced.
|
||||||
check('fr""', is_literal=py_version >= 36)
|
check('fr""', is_literal=sys.version_info >= (3, 6))
|
||||||
check('rF""', is_literal=py_version >= 36)
|
check('rF""', is_literal=sys.version_info >= (3, 6))
|
||||||
check('f""', is_literal=py_version >= 36)
|
check('f""', is_literal=sys.version_info >= (3, 6))
|
||||||
check('F""', is_literal=py_version >= 36)
|
check('F""', is_literal=sys.version_info >= (3, 6))
|
||||||
|
|
||||||
|
|
||||||
def test_error_literal():
|
def test_error_literal():
|
||||||
|
|||||||
4
tox.ini
4
tox.ini
@@ -1,11 +1,9 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist = {py26,py27,py33,py34,py35,py36,py37,py38}
|
envlist = {py27,py34,py35,py36,py37,py38}
|
||||||
[testenv]
|
[testenv]
|
||||||
extras = testing
|
extras = testing
|
||||||
deps =
|
deps =
|
||||||
py26,py33: pytest>=3.0.7,<3.3
|
|
||||||
py27,py34: pytest<3.3
|
py27,py34: pytest<3.3
|
||||||
py26,py33: setuptools<37
|
|
||||||
coverage: coverage
|
coverage: coverage
|
||||||
setenv =
|
setenv =
|
||||||
# https://github.com/tomchristie/django-rest-framework/issues/1957
|
# https://github.com/tomchristie/django-rest-framework/issues/1957
|
||||||
|
|||||||
Reference in New Issue
Block a user