21 Commits

Author SHA1 Message Date
Dave Halter
4306e8b34b Change the release date for 0.6.1 2020-02-03 21:46:25 +01:00
Dave Halter
2ce3898690 Prepare the next release 0.6.1 2020-02-03 18:40:05 +01:00
Dave Halter
16f257356e Make end_pos public for syntax issues 2020-02-03 18:36:47 +01:00
Dave Halter
c864ca60d1 Bump version to 0.6.0 2020-01-26 20:01:38 +01:00
Dave Halter
a47b5433d4 Make sure iter_funcdefs includes async functions with decorators, fixes #98 2020-01-26 20:00:56 +01:00
Dave Halter
6982cf8321 Add a bit to the changelog 2020-01-26 19:47:46 +01:00
Dave Halter
844ca3d35a del_stmt is now considered a name definition 2020-01-26 19:42:12 +01:00
Dave Halter
9abe5d1e55 Forgot to increase the pickle version 2020-01-20 01:28:06 +01:00
Jarry Shaw
84874aace3 Revision on fstring issues (#100)
* f-string expression part cannot include a backslash
 * failing example `f"{'\n'}"` for tests
2020-01-09 21:49:34 +01:00
Jarry Shaw
55531ab65b Revision on assignment errors (#97)
* Revision on assignment expression errors

 * added rule for __debug__ (should be a keyword)
 * reviewed error messages
 * added new failing samples

* Adjustment upon Dave's review

 * rewind several changes in assignment errors
 * patched is_definition: command not found for assignment expressions
 * patched Python 2 inconsistent error messages in test_python_errors.py: command not found
2020-01-08 23:07:37 +01:00
Dave Halter
31c059fc30 Add a Changelog note about dropping 2.6/3.3 2020-01-06 00:05:11 +01:00
Dave Halter
cfef1d74e7 Fix a Python 2.7 issue 2020-01-06 00:02:26 +01:00
Dave Halter
9ee7409d8a Get rid of Python 3.3 artifacts 2020-01-05 23:59:38 +01:00
Dave Halter
4090c80401 Remove Python 2.6 grammar 2020-01-05 23:55:03 +01:00
Dave Halter
95f353a15f Merge branch 'rm-2.6' of https://github.com/hugovk/parso 2020-01-05 23:50:20 +01:00
Dave Halter
2b0b093276 Make sure to limit the amount of cached files parso stores, fixes davidhalter/jedi#1340 2020-01-05 23:44:51 +01:00
Tim Gates
29b57d93bd Fix simple typo: utitilies -> utilities
Closes #94
2019-12-17 10:00:28 +01:00
Hugo
d3383b6c41 Fix string/tuple concatenation 2019-08-08 16:49:42 +03:00
Hugo
9da4df20d1 Add python_requires to help pip 2019-08-08 14:57:13 +03:00
Hugo
0341f69691 Drop support for EOL Python 3.3 2019-08-08 14:57:13 +03:00
Hugo
f6bdba65c0 Drop support for EOL Python 2.6 2019-08-08 14:56:27 +03:00
26 changed files with 206 additions and 261 deletions

View File

@@ -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.

View File

@@ -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)
++++++++++++++++++ ++++++++++++++++++

View File

@@ -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')

View File

@@ -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):

View File

@@ -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

View File

@@ -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)

View File

@@ -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):

View File

@@ -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

View File

@@ -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"

View File

@@ -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]

View File

@@ -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

View File

@@ -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,

View File

@@ -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]+'

View File

@@ -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__ = ()

View File

@@ -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])

View File

@@ -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)

View File

@@ -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',

View File

@@ -281,10 +281,10 @@ 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):
# 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') FAILING_EXAMPLES.append('[a, 1] += 3')
if sys.version_info[:2] == (3, 5): if sys.version_info[:2] == (3, 5):
@@ -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)',
] ]

View File

@@ -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()

View File

@@ -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:

View File

@@ -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)

View File

@@ -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

View File

@@ -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']

View File

@@ -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

View File

@@ -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():

View File

@@ -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