Add some first f-string issues.

This commit is contained in:
Dave Halter
2017-08-25 22:09:58 +02:00
parent 609ab1ffa9
commit ede8a2139f
6 changed files with 62 additions and 5 deletions

View File

@@ -220,7 +220,7 @@ class PythonFStringGrammar(Grammar):
) )
return p.parse(tokens=tokens) return p.parse(tokens=tokens)
def parse_leaf(leaf, error_recovery=True): def parse_leaf(self, leaf, error_recovery=True):
code = leaf._get_payload() code = leaf._get_payload()
return self.parse(code, error_recovery=True, start_pos=leaf.start_pos) return self.parse(code, error_recovery=True, start_pos=leaf.start_pos)

View File

@@ -13,7 +13,7 @@ class _NormalizerMeta(type):
class Normalizer(use_metaclass(_NormalizerMeta)): class Normalizer(use_metaclass(_NormalizerMeta)):
def __init__(self, grammar, config): def __init__(self, grammar, config):
self._grammar = grammar self.grammar = grammar
self._config = config self._config = config
self.issues = [] self.issues = []

View File

@@ -254,7 +254,7 @@ class ErrorFinder(Normalizer):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ErrorFinder, self).__init__(*args, **kwargs) super(ErrorFinder, self).__init__(*args, **kwargs)
self._error_dict = {} self._error_dict = {}
self.version = self._grammar.version_info self.version = self.grammar.version_info
def initialize(self, node): def initialize(self, node):
def create_context(node): def create_context(node):
@@ -836,6 +836,54 @@ class _TryStmtRule(SyntaxRule):
self.add_issue(default_except, message=self.message) self.add_issue(default_except, message=self.message)
@ErrorFinder.register_rule(type='string')
class _FStringRule(SyntaxRule):
_fstring_grammar = None
message_empty = "f-string: empty expression not allowed" # f'{}'
"f-string: single '}' is not allowed" # f'}'
"f-string: expressions nested too deeply" # f'{1:{5:{3}}}'
message_backslash = "f-string expression part cannot include a backslash" # f'{"\"}' or f'{"\\"}'
message_comment = "f-string expression part cannot include '#'" # f'{#}'
"f-string: unterminated string" # f'{"}'
"f-string: mismatched '(', '{', or '['"
"f-string: invalid conversion character: expected 's', 'r', or 'a'" # f'{1!b}'
"f-string: unexpected end of string" # Doesn't really happen?!
"f-string: expecting '}'" # f'{'
@classmethod
def _load_grammar(cls):
import parso
if cls._fstring_grammar is None:
cls._fstring_grammar = parso.load_grammar(language='python-f-string')
return cls._fstring_grammar
def is_issue(self, fstring):
if 'f' not in fstring.string_prefix.lower():
return
parsed = self._load_grammar().parse_leaf(fstring)
for child in parsed.children:
type = child.type
if type == 'expression':
self._check_expression(child.children[1])
def _check_expression(self, python_expr):
value = python_expr.value
if '\\' in value:
self.add_issue(python_expr, message=self.message_backslash)
return
if '#' in value:
self.add_issue(python_expr, message=self.message_comment)
return
# This is now nested parsing. We parsed the fstring and now
# we're parsing Python again.
module = self._normalizer.grammar.parse(value)
parsed_expr = module.children[0]
if parsed_expr.type == 'endmarker':
self.add_issue(python_expr, message=self.message_empty)
class _CheckAssignmentRule(SyntaxRule): class _CheckAssignmentRule(SyntaxRule):
def _check_assignment(self, node, is_deletion=False): def _check_assignment(self, node, is_deletion=False):
error = None error = None

View File

@@ -186,5 +186,5 @@ class Parser(parser.BaseParser):
def convert_leaf(self, pgen_grammar, type, value, prefix, start_pos): def convert_leaf(self, pgen_grammar, type, value, prefix, start_pos):
# TODO this is so ugly. # TODO this is so ugly.
leaf_type = TokenNamespace.token_map[type] leaf_type = TokenNamespace.token_map[type].lower()
return TypedLeaf(leaf_type, value, start_pos, prefix) return TypedLeaf(leaf_type, value, start_pos, prefix)

View File

@@ -203,7 +203,10 @@ class Leaf(NodeOrLeaf):
@utf8_repr @utf8_repr
def __repr__(self): def __repr__(self):
return "<%s: %s>" % (type(self).__name__, self.value) value = self.value
if not value:
value = self.type
return "<%s: %s>" % (type(self).__name__, value)
class TypedLeaf(Leaf): class TypedLeaf(Leaf):

View File

@@ -138,6 +138,12 @@ FAILING_EXAMPLES = [
'def x():\n 1\n 2', 'def x():\n 1\n 2',
'if 1:\nfoo', 'if 1:\nfoo',
'if 1: blubb\nif 1:\npass\nTrue and False', 'if 1: blubb\nif 1:\npass\nTrue and False',
# f-strings
'f"{}"',
'f"{\\}"',
#'f"{\'\\\'}"',
'f"{#}"',
] ]
GLOBAL_NONLOCAL_ERROR = [ GLOBAL_NONLOCAL_ERROR = [