Remove the f-string rules and replace them with new ones

This commit is contained in:
Dave Halter
2018-04-06 20:29:22 +02:00
parent 0d96b12566
commit afb71dc762
2 changed files with 16 additions and 92 deletions

View File

@@ -846,23 +846,12 @@ class _TryStmtRule(SyntaxRule):
@ErrorFinder.register_rule(type='fstring')
class _FStringRule(SyntaxRule):
_fstring_grammar = None
message_empty = "f-string: empty expression not allowed" # f'{}'
message_single_closing = "f-string: single '}' is not allowed" # f'}'
message_nested = "f-string: expressions nested too deeply"
message_backslash = "f-string expression part cannot include a backslash" # f'{"\"}' or f'{"\\"}'
message_comment = "f-string expression part cannot include '#'" # f'{#}'
message_unterminated_string = "f-string: unterminated string" # f'{"}'
message_conversion = "f-string: invalid conversion character: expected 's', 'r', or 'a'"
message_incomplete = "f-string: expecting '}'" # f'{'
message_syntax = "invalid syntax"
@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 _check_format_spec(self, format_spec, depth):
self._check_fstring_contents(format_spec.children[1:], depth)
def _check_string_part(self, fstring_string):
index = -1
@@ -877,95 +866,30 @@ class _FStringRule(SyntaxRule):
else:
self.add_issue(fstring_string, message=self.message_single_closing)
def _check_fstring_expr(self, fstring_expr):
def _check_fstring_expr(self, fstring_expr, depth):
if depth >= 2:
self.add_issue(fstring_expr, message=self.message_nested)
conversion = fstring_expr.children[2]
if conversion.type == 'fstring_conversion':
name = conversion.children[1]
if name.value not in ('s', 'r', 'a'):
self.add_issue(name, message=self.message_conversion)
format_spec = fstring_expr.children[-2]
if format_spec.type == 'fstring_format_spec':
self._check_format_spec(format_spec, depth + 1)
def is_issue(self, fstring):
for fstring_content in fstring.children[1:-1]:
self._check_fstring_contents(fstring.children[1:-1])
def _check_fstring_contents(self, children, depth=0):
for fstring_content in children:
if fstring_content.type == 'fstring_string':
self._check_string_part(fstring_content)
else:
assert fstring_content.type == 'fstring_expr'
self._check_fstring_expr(fstring_content)
return
print(fstring)
if 'f' not in fstring.string_prefix.lower():
return
parsed = self._load_grammar().parse_leaf(fstring)
for child in parsed.children:
if child.type == 'expression':
self._check_expression(child)
elif child.type == 'error_node':
next_ = child.get_next_leaf()
if next_.type == 'error_leaf' and next_.original_type == 'unterminated_string':
self.add_issue(next_, message=self.message_unterminated_string)
# At this point nothing more is comming except the error
# leaf that we've already checked here.
break
self.add_issue(child, message=self.message_incomplete)
elif child.type == 'error_leaf':
self.add_issue(child, message=self.message_single_closing)
def _check_python_expr(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
if re.match('\s*$', value) is not None:
self.add_issue(python_expr, message=self.message_empty)
return
# This is now nested parsing. We parsed the fstring and now
# we're parsing Python again.
try:
# CPython has a bit of a special ways to parse Python code within
# f-strings. It wraps the code in brackets to make sure that
# whitespace doesn't make problems (indentation/newlines).
# Just use that algorithm as well here and adapt start positions.
start_pos = python_expr.start_pos
start_pos = start_pos[0], start_pos[1] - 1
eval_input = self._normalizer.grammar._parse(
'(%s)' % value,
start_symbol='eval_input',
start_pos=start_pos,
error_recovery=False
)
except ParserSyntaxError as e:
self.add_issue(e.error_leaf, message=self.message_syntax)
return
issues = self._normalizer.grammar.iter_errors(eval_input)
self._normalizer.issues += issues
def _check_format_spec(self, format_spec):
for expression in format_spec.children[1:]:
nested_format_spec = expression.children[-2]
if nested_format_spec.type == 'format_spec':
if len(nested_format_spec.children) > 1:
self.add_issue(
nested_format_spec.children[1],
message=self.message_nested
)
self._check_expression(expression)
def _check_expression(self, expression):
for c in expression.children:
if c.type == 'python_expr':
self._check_python_expr(c)
elif c.type == 'conversion':
if c.value not in ('s', 'r', 'a'):
self.add_issue(c, message=self.message_conversion)
elif c.type == 'format_spec':
self._check_format_spec(c)
self._check_fstring_expr(fstring_content, depth)
class _CheckAssignmentRule(SyntaxRule):

View File

@@ -265,7 +265,7 @@ class FStringNode(object):
return len(self.quote) == 3
def is_in_expr(self):
return self.parentheses_count and not self.in_format_spec
return self.parentheses_count
def _check_fstring_ending(fstring_stack, token, from_start=False):