Skip to content

Commit e0e5614

Browse files
committed
Fix crash when using f-strings
Closes PyCQA#640
1 parent 8e2bc99 commit e0e5614

File tree

2 files changed

+31
-13
lines changed

2 files changed

+31
-13
lines changed

src/pydocstyle/checker.py

+21-12
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@
3535

3636
__all__ = ('check',)
3737

38+
FSTRING_REGEX = re(r'^([rR]?)[fF]')
39+
40+
41+
def eval_docstring(docstring):
42+
"""Safely evaluate docstring."""
43+
if FSTRING_REGEX.match(str(docstring)):
44+
return ""
45+
return ast.literal_eval(docstring)
46+
3847

3948
def check_for(kind, terminal=False):
4049
def decorator(f):
@@ -241,7 +250,7 @@ def check_docstring_empty(self, definition, docstring):
241250
NOTE: This used to report as D10X errors.
242251
243252
"""
244-
if docstring and is_blank(ast.literal_eval(docstring)):
253+
if docstring and is_blank(eval_docstring(docstring)):
245254
return violations.D419()
246255

247256
@check_for(Definition)
@@ -253,7 +262,7 @@ def check_one_liners(self, definition, docstring):
253262
254263
"""
255264
if docstring:
256-
lines = ast.literal_eval(docstring).split('\n')
265+
lines = eval_docstring(docstring).split('\n')
257266
if len(lines) > 1:
258267
non_empty_lines = sum(1 for l in lines if not is_blank(l))
259268
if non_empty_lines == 1:
@@ -329,7 +338,7 @@ def check_blank_after_summary(self, definition, docstring):
329338
330339
"""
331340
if docstring:
332-
lines = ast.literal_eval(docstring).strip().split('\n')
341+
lines = eval_docstring(docstring).strip().split('\n')
333342
if len(lines) > 1:
334343
post_summary_blanks = list(map(is_blank, lines[1:]))
335344
blanks_count = sum(takewhile(bool, post_summary_blanks))
@@ -382,7 +391,7 @@ def check_newline_after_last_paragraph(self, definition, docstring):
382391
if docstring:
383392
lines = [
384393
l
385-
for l in ast.literal_eval(docstring).split('\n')
394+
for l in eval_docstring(docstring).split('\n')
386395
if not is_blank(l)
387396
]
388397
if len(lines) > 1:
@@ -393,7 +402,7 @@ def check_newline_after_last_paragraph(self, definition, docstring):
393402
def check_surrounding_whitespaces(self, definition, docstring):
394403
"""D210: No whitespaces allowed surrounding docstring text."""
395404
if docstring:
396-
lines = ast.literal_eval(docstring).split('\n')
405+
lines = eval_docstring(docstring).split('\n')
397406
if (
398407
lines[0].startswith(' ')
399408
or len(lines) == 1
@@ -421,7 +430,7 @@ def check_multi_line_summary_start(self, definition, docstring):
421430
"ur'''",
422431
]
423432

424-
lines = ast.literal_eval(docstring).split('\n')
433+
lines = eval_docstring(docstring).split('\n')
425434
if len(lines) > 1:
426435
first = docstring.split("\n")[0].strip().lower()
427436
if first in start_triple:
@@ -443,7 +452,7 @@ def check_triple_double_quotes(self, definition, docstring):
443452
444453
'''
445454
if docstring:
446-
if '"""' in ast.literal_eval(docstring):
455+
if '"""' in eval_docstring(docstring):
447456
# Allow ''' quotes if docstring contains """, because
448457
# otherwise """ quotes could not be expressed inside
449458
# docstring. Not in PEP 257.
@@ -487,7 +496,7 @@ def _check_ends_with(docstring, chars, violation):
487496
488497
"""
489498
if docstring:
490-
summary_line = ast.literal_eval(docstring).strip().split('\n')[0]
499+
summary_line = eval_docstring(docstring).strip().split('\n')[0]
491500
if not summary_line.endswith(chars):
492501
return violation(summary_line[-1])
493502

@@ -526,7 +535,7 @@ def check_imperative_mood(self, function, docstring): # def context
526535
and not function.is_test
527536
and not function.is_property(self.property_decorators)
528537
):
529-
stripped = ast.literal_eval(docstring).strip()
538+
stripped = eval_docstring(docstring).strip()
530539
if stripped:
531540
first_word = strip_non_alphanumeric(stripped.split()[0])
532541
check_word = first_word.lower()
@@ -552,7 +561,7 @@ def check_no_signature(self, function, docstring): # def context
552561
553562
"""
554563
if docstring:
555-
first_line = ast.literal_eval(docstring).strip().split('\n')[0]
564+
first_line = eval_docstring(docstring).strip().split('\n')[0]
556565
if function.name + '(' in first_line.replace(' ', ''):
557566
return violations.D402()
558567

@@ -564,7 +573,7 @@ def check_capitalized(self, function, docstring):
564573
565574
"""
566575
if docstring:
567-
first_word = ast.literal_eval(docstring).split()[0]
576+
first_word = eval_docstring(docstring).split()[0]
568577
if first_word == first_word.upper():
569578
return
570579
for char in first_word:
@@ -596,7 +605,7 @@ def check_starts_with_this(self, function, docstring):
596605
if not docstring:
597606
return
598607

599-
stripped = ast.literal_eval(docstring).strip()
608+
stripped = eval_docstring(docstring).strip()
600609
if not stripped:
601610
return
602611

src/tests/test_integration.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -1584,4 +1584,13 @@ def test_match_considers_basenames_for_path_args(env):
15841584
# env.invoke calls pydocstyle with full path to test_a.py
15851585
out, _, code = env.invoke(target='test_a.py')
15861586
assert '' == out
1587-
assert code == 0
1587+
assert code == 0
1588+
1589+
def test_fstring(env):
1590+
"""Test that f-strings do not cause a crash."""
1591+
env.write_config(select='D')
1592+
with env.open("test.py", 'wt') as fobj:
1593+
fobj.write('''f"bar {123}"''')
1594+
_, err, code = env.invoke(args="-v")
1595+
assert code == 1
1596+
assert "ValueError" not in err

0 commit comments

Comments
 (0)