Skip to content
This repository was archived by the owner on Nov 3, 2023. It is now read-only.

Commit d630a13

Browse files
committed
Initial commit for future import parsing.
1 parent 23cf188 commit d630a13

File tree

4 files changed

+121
-6
lines changed

4 files changed

+121
-6
lines changed

pep257.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ def __eq__(self, other):
100100
return other and vars(self) == vars(other)
101101

102102
def __repr__(self):
103-
kwargs = ', '.join('{}={!r}'.format(field, getattr(self, field))
103+
kwargs = ', '.join('{0}={1!r}'.format(field, getattr(self, field))
104104
for field in self._fields)
105-
return '{}({})'.format(self.__class__.__name__, kwargs)
105+
return '{0}({1})'.format(self.__class__.__name__, kwargs)
106106

107107

108108
class Definition(Value):
@@ -131,7 +131,7 @@ def __str__(self):
131131
class Module(Definition):
132132

133133
_fields = ('name', '_source', 'start', 'end', 'decorators', 'docstring',
134-
'children', 'parent', '_all')
134+
'children', 'parent', '_all', 'unicode_literals')
135135
is_public = True
136136
_nest = staticmethod(lambda s: {'def': Function, 'class': Class}[s])
137137
module = property(lambda self: self)
@@ -251,6 +251,8 @@ def __call__(self, filelike, filename):
251251
self.stream = TokenStream(StringIO(src))
252252
self.filename = filename
253253
self.all = None
254+
# TODO: what about Python 3.x?
255+
self.unicode_literals = False
254256
self._accumulated_decorators = []
255257
return self.parse_module()
256258

@@ -349,6 +351,8 @@ def parse_definitions(self, class_, all=False):
349351
elif self.current.kind == tk.DEDENT:
350352
self.consume(tk.DEDENT)
351353
return
354+
elif self.current.value == 'from':
355+
self.parse_from_import_statement()
352356
else:
353357
self.stream.move()
354358

@@ -410,6 +414,7 @@ def parse_module(self):
410414
[], docstring, children, None, self.all)
411415
for child in module.children:
412416
child.parent = module
417+
module.unicode_literals = self.unicode_literals
413418
log.debug("finished parsing module.")
414419
return module
415420

@@ -460,6 +465,24 @@ def parse_definition(self, class_):
460465
self.current.value)
461466
return definition
462467

468+
def parse_from_import_statement(self):
469+
"""Parse a 'from x import y' statement.
470+
471+
The purpose is to find __future__ statements.
472+
473+
"""
474+
log.debug('parsing from/import statement.')
475+
assert self.current.value == 'from', self.current.value
476+
self.stream.move()
477+
if self.current.value != '__future__':
478+
return
479+
self.stream.move()
480+
assert self.current.value == 'import', self.current.value
481+
self.stream.move()
482+
# TODO: lookout for parenthesis, comments, line breaks, etc.
483+
if self.current.value == 'unicode_literals':
484+
self.unicode_literals = True
485+
463486

464487
class Error(object):
465488

@@ -1123,6 +1146,9 @@ def check_unicode_docstring(self, definition, docstring):
11231146
For Unicode docstrings, use u"""Unicode triple-quoted strings""".
11241147
11251148
'''
1149+
if definition.module.unicode_literals:
1150+
return
1151+
11261152
# Just check that docstring is unicode, check_triple_double_quotes
11271153
# ensures the correct quotes.
11281154
if docstring and sys.version_info[0] <= 2:

test.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,4 +286,11 @@ def a_following_valid_function(x):
286286
287287
"""
288288

289+
290+
def outer_function():
291+
"""Do something."""
292+
def inner_function():
293+
"""Do inner something."""
294+
return 0
295+
289296
expect('test.py', 'D100: Missing docstring in public module')

test_definitions.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,19 @@ def nested_3(self):
3535
# Inconvenient comment.
3636
'a', 'b' 'c',]
3737
'''
38+
source_unicode_literals = '''
39+
from __future__ import unicode_literals
40+
'''
3841

3942

4043
def test_parser():
4144
dunder_all = ('a', 'bc')
45+
unicode_literals = False
4246
module = parse(StringIO(source), 'file.py')
4347
assert len(list(module)) == 8
4448
assert Module('file.py', _, 1, len(source.split('\n')),
45-
_, '"""Module."""', _, _, dunder_all) == module
49+
_, '"""Module."""', _, _, dunder_all, unicode_literals) == \
50+
module
4651

4752
function, class_ = module.children
4853
assert Function('function', _, _, _, _, '"Function."', _,
@@ -70,12 +75,17 @@ def test_parser():
7075

7176
module = parse(StringIO(source_alt), 'file_alt.py')
7277
assert Module('file_alt.py', _, 1, len(source_alt.split('\n')),
73-
_, None, _, _, dunder_all) == module
78+
_, None, _, _, dunder_all, unicode_literals) == module
7479

7580
module = parse(StringIO(source_alt_nl_at_bracket), 'file_alt_nl.py')
7681
assert Module('file_alt_nl.py', _, 1,
7782
len(source_alt_nl_at_bracket.split('\n')), _, None, _, _,
78-
dunder_all) == module
83+
dunder_all, unicode_literals) == module
84+
85+
module = parse(StringIO(source_unicode_literals), 'file_ucl.py')
86+
assert Module('file_ucl.py', _, 1,
87+
_, _, None, _, _,
88+
_, True) == module
7989

8090

8191
def _test_module():

test_pep257.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,3 +297,75 @@ def test_illegal_convention():
297297
assert code == 2
298298
assert "Illegal convention 'illegal_conv'." in err
299299
assert 'Possible conventions: pep257' in err
300+
301+
302+
def test_unicode_literals():
303+
304+
if sys.version_info[0] >= 3:
305+
return # ur"" is a syntax error in python 3.x
306+
307+
# This is all to avoid a syntax error for python 3.2
308+
from codecs import unicode_escape_decode
309+
310+
def u(x):
311+
return unicode_escape_decode(x)[0]
312+
313+
# Check that D302 is reported with unicode docstring
314+
with Pep257Env() as env:
315+
with env.open('example.py', 'wt') as example:
316+
example.write(textwrap.dedent(u('''\
317+
# -*- coding: utf-8 -*-
318+
"""This is a modue."""
319+
320+
def foo():
321+
"""Check unicode: \u2611."""
322+
''').encode('utf-8')))
323+
_, err, code = env.invoke_pep257()
324+
assert code == 1
325+
assert 'D302' in err
326+
327+
# Check that D302 is not reported when import unicode_literals
328+
with Pep257Env() as env:
329+
with env.open('example.py', 'wt') as example:
330+
example.write(textwrap.dedent(u('''\
331+
# -*- coding: utf-8 -*-
332+
"""This is a module."""
333+
334+
from __future__ import unicode_literals
335+
336+
def foo():
337+
"""Check unicode: \u2611."""
338+
''').encode('utf-8')))
339+
_, err, code = env.invoke_pep257()
340+
assert code == 0, err
341+
342+
with Pep257Env() as env:
343+
with env.open('example.py', 'wt') as example:
344+
example.write(textwrap.dedent(u('''\
345+
# -*- coding: utf-8 -*-
346+
"""This is a module."""
347+
348+
from __future__ import (nested_scopes as ns,
349+
unicode_literals)
350+
351+
def foo():
352+
"""Check unicode: \u2611."""
353+
''').encode('utf-8')))
354+
_, err, code = env.invoke_pep257()
355+
assert code == 0, err
356+
357+
with Pep257Env() as env:
358+
with env.open('example.py', 'wt') as example:
359+
example.write(textwrap.dedent(u('''\
360+
# -*- coding: utf-8 -*-
361+
"""This is a module."""
362+
363+
from __future__ import \
364+
nested_scopes, \
365+
unicode_literals as ul
366+
367+
def foo():
368+
"""Check unicode: \u2611."""
369+
''').encode('utf-8')))
370+
_, err, code = env.invoke_pep257()
371+
assert code == 0, err

0 commit comments

Comments
 (0)