Skip to content

Commit 7c7c95e

Browse files
authored
Merge pull request #739 from hhatto/avoid-lib2to3
Avoid lib2to3 (second challenge)
2 parents b0f2793 + bc21480 commit 7c7c95e

5 files changed

Lines changed: 107 additions & 156 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ coverage.xml
1818
dist
1919
htmlcov
2020
pep8.py
21+
test/suite/out/*.py.err

autopep8.py

Lines changed: 81 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ class documentation for more information.
106106
DOCSTRING_START_REGEX = re.compile(r'^u?r?(?P<kind>["\']{3})')
107107
ENABLE_REGEX = re.compile(r'# *(fmt|autopep8): *on')
108108
DISABLE_REGEX = re.compile(r'# *(fmt|autopep8): *off')
109+
ENCODING_MAGIC_COMMENT = re.compile(
110+
r'^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)'
111+
)
112+
COMPARE_TYPE_REGEX = re.compile(
113+
r'([=!]=)\s+type(?:\s*\(\s*([^)]*[^ )])\s*\))'
114+
r'|\btype(?:\s*\(\s*([^)]*[^ )])\s*\))\s+([=!]=)'
115+
)
116+
TYPE_REGEX = re.compile(r'(type\s*\(\s*[^)]*?[^\s)]\s*\))')
109117

110118
EXIT_CODE_OK = 0
111119
EXIT_CODE_ERROR = 1
@@ -129,25 +137,6 @@ class documentation for more information.
129137
# to be enabled, disable both of them
130138
CONFLICTING_CODES = ('W503', 'W504')
131139

132-
# W602 is handled separately due to the need to avoid "with_traceback".
133-
CODE_TO_2TO3 = {
134-
'E231': ['ws_comma'],
135-
'E721': ['idioms'],
136-
'W690': ['apply',
137-
'except',
138-
'exitfunc',
139-
'numliterals',
140-
'operator',
141-
'paren',
142-
'reduce',
143-
'renames',
144-
'standarderror',
145-
'sys_exc',
146-
'throw',
147-
'tuple_params',
148-
'xreadlines']}
149-
150-
151140
if sys.platform == 'win32': # pragma: no cover
152141
DEFAULT_CONFIG = os.path.expanduser(r'~\.pycodestyle')
153142
else:
@@ -175,16 +164,31 @@ def open_with_encoding(filename, mode='r', encoding=None, limit_byte_check=-1):
175164
newline='') # Preserve line endings
176165

177166

167+
def _detect_encoding_from_file(filename: str):
168+
try:
169+
with open(filename) as input_file:
170+
for idx, line in enumerate(input_file):
171+
if idx == 0 and line[0] == '\ufeff':
172+
return "utf-8-sig"
173+
if idx >= 2:
174+
break
175+
match = ENCODING_MAGIC_COMMENT.search(line)
176+
if match:
177+
return match.groups()[0]
178+
except Exception:
179+
pass
180+
# Python3's default encoding
181+
return 'utf-8'
182+
183+
178184
def detect_encoding(filename, limit_byte_check=-1):
179185
"""Return file encoding."""
186+
encoding = _detect_encoding_from_file(filename)
187+
if encoding == "utf-8-sig":
188+
return encoding
180189
try:
181-
with open(filename, 'rb') as input_file:
182-
from lib2to3.pgen2 import tokenize as lib2to3_tokenize
183-
encoding = lib2to3_tokenize.detect_encoding(input_file.readline)[0]
184-
185190
with open_with_encoding(filename, encoding=encoding) as test_file:
186191
test_file.read(limit_byte_check)
187-
188192
return encoding
189193
except (LookupError, SyntaxError, UnicodeDecodeError):
190194
return 'latin-1'
@@ -449,7 +453,7 @@ class FixPEP8(object):
449453
- e502
450454
- e701,e702,e703,e704
451455
- e711,e712,e713,e714
452-
- e722
456+
- e721,e722
453457
- e731
454458
- w291
455459
- w503,504
@@ -1262,6 +1266,58 @@ def fix_e714(self, result):
12621266
new_target[:pos_start], 'is not', new_target[pos_end:])
12631267
self.source[line_index] = new_target
12641268

1269+
def fix_e721(self, result):
1270+
"""fix comparison type"""
1271+
(line_index, _, target) = get_index_offset_contents(result,
1272+
self.source)
1273+
match = COMPARE_TYPE_REGEX.search(target)
1274+
if match:
1275+
# NOTE: match objects
1276+
# * type(a) == type(b) -> (None, None, 'a', '==')
1277+
# * str == type(b) -> ('==', 'b', None, None)
1278+
# * type("") != type(b) -> (None, None, '""', '!=')
1279+
start = match.start()
1280+
end = match.end()
1281+
_prefix = ""
1282+
_suffix = ""
1283+
first_match_type_obj = match.groups()[1]
1284+
if first_match_type_obj is None:
1285+
_target_obj = match.groups()[2]
1286+
else:
1287+
_target_obj = match.groups()[1]
1288+
_suffix = target[end:]
1289+
1290+
isinstance_stmt = " isinstance"
1291+
is_not_condition = (
1292+
match.groups()[0] == "!=" or match.groups()[3] == "!="
1293+
)
1294+
if is_not_condition:
1295+
isinstance_stmt = " not isinstance"
1296+
1297+
_type_comp = f"{_target_obj}, {target[:start]}"
1298+
1299+
_prefix_tmp = target[:start].split()
1300+
if len(_prefix_tmp) >= 1:
1301+
_type_comp = f"{_target_obj}, {target[:start]}"
1302+
if first_match_type_obj is not None:
1303+
_prefix = " ".join(_prefix_tmp[:-1])
1304+
_type_comp = f"{_target_obj}, {_prefix_tmp[-1]}"
1305+
else:
1306+
_prefix = " ".join(_prefix_tmp)
1307+
1308+
_suffix_tmp = target[end:]
1309+
_suffix_type_match = TYPE_REGEX.search(_suffix_tmp)
1310+
if len(_suffix_tmp.split()) >= 1 and _suffix_type_match:
1311+
if _suffix_type_match:
1312+
type_match_end = _suffix_type_match.end()
1313+
_suffix = _suffix_tmp[type_match_end:]
1314+
if _suffix_type_match:
1315+
cmp_b = _suffix_type_match.groups()[0]
1316+
_type_comp = f"{_target_obj}, {cmp_b}"
1317+
1318+
fix_line = f"{_prefix}{isinstance_stmt}({_type_comp}){_suffix}"
1319+
self.source[line_index] = fix_line
1320+
12651321
def fix_e722(self, result):
12661322
"""fix bare except"""
12671323
(line_index, _, target) = get_index_offset_contents(result,
@@ -1717,69 +1773,6 @@ def split_and_strip_non_empty_lines(text):
17171773
return [line.strip() for line in text.splitlines() if line.strip()]
17181774

17191775

1720-
def refactor(source, fixer_names, ignore=None, filename=''):
1721-
"""Return refactored code using lib2to3.
1722-
1723-
Skip if ignore string is produced in the refactored code.
1724-
1725-
"""
1726-
not_found_end_of_file_newline = source and source.rstrip("\r\n") == source
1727-
if not_found_end_of_file_newline:
1728-
input_source = source + "\n"
1729-
else:
1730-
input_source = source
1731-
1732-
from lib2to3 import pgen2
1733-
try:
1734-
new_text = refactor_with_2to3(input_source,
1735-
fixer_names=fixer_names,
1736-
filename=filename)
1737-
except (pgen2.parse.ParseError,
1738-
SyntaxError,
1739-
UnicodeDecodeError,
1740-
UnicodeEncodeError):
1741-
return source
1742-
1743-
if ignore:
1744-
if ignore in new_text and ignore not in source:
1745-
return source
1746-
1747-
if not_found_end_of_file_newline:
1748-
return new_text.rstrip("\r\n")
1749-
1750-
return new_text
1751-
1752-
1753-
def code_to_2to3(select, ignore, where='', verbose=False):
1754-
fixes = set()
1755-
for code, fix in CODE_TO_2TO3.items():
1756-
if code_match(code, select=select, ignore=ignore):
1757-
if verbose:
1758-
print('---> Applying {} fix for {}'.format(where,
1759-
code.upper()),
1760-
file=sys.stderr)
1761-
fixes |= set(fix)
1762-
return fixes
1763-
1764-
1765-
def fix_2to3(source,
1766-
aggressive=True, select=None, ignore=None, filename='',
1767-
where='global', verbose=False):
1768-
"""Fix various deprecated code (via lib2to3)."""
1769-
if not aggressive:
1770-
return source
1771-
1772-
select = select or []
1773-
ignore = ignore or []
1774-
1775-
return refactor(source,
1776-
code_to_2to3(select=select,
1777-
ignore=ignore,
1778-
where=where,
1779-
verbose=verbose),
1780-
filename=filename)
1781-
1782-
17831776
def find_newline(source):
17841777
"""Return type of newline used in source.
17851778
@@ -3175,24 +3168,6 @@ def _leading_space_count(line):
31753168
return i
31763169

31773170

3178-
def refactor_with_2to3(source_text, fixer_names, filename=''):
3179-
"""Use lib2to3 to refactor the source.
3180-
3181-
Return the refactored source code.
3182-
3183-
"""
3184-
from lib2to3.refactor import RefactoringTool
3185-
fixers = ['lib2to3.fixes.fix_' + name for name in fixer_names]
3186-
tool = RefactoringTool(fixer_names=fixers, explicit=fixers)
3187-
3188-
from lib2to3.pgen2 import tokenize as lib2to3_tokenize
3189-
try:
3190-
# The name parameter is necessary particularly for the "import" fixer.
3191-
return str(tool.refactor_string(source_text, name=filename))
3192-
except lib2to3_tokenize.TokenError:
3193-
return source_text
3194-
3195-
31963171
def check_syntax(code):
31973172
"""Return True if syntax is okay."""
31983173
try:
@@ -3685,14 +3660,6 @@ def apply_global_fixes(source, options, where='global', filename='',
36853660
source = function(source,
36863661
aggressive=options.aggressive)
36873662

3688-
source = fix_2to3(source,
3689-
aggressive=options.aggressive,
3690-
select=options.select,
3691-
ignore=options.ignore,
3692-
filename=filename,
3693-
where=where,
3694-
verbose=options.verbose)
3695-
36963663
return source
36973664

36983665

@@ -4127,10 +4094,6 @@ def supported_fixes():
41274094
yield (code.upper() + (4 - len(code)) * ' ',
41284095
re.sub(r'\s+', ' ', docstring_summary(function.__doc__)))
41294096

4130-
for code in sorted(CODE_TO_2TO3):
4131-
yield (code.upper() + (4 - len(code)) * ' ',
4132-
re.sub(r'\s+', ' ', docstring_summary(fix_2to3.__doc__)))
4133-
41344097

41354098
def docstring_summary(docstring):
41364099
"""Return summary of docstring."""

test/suite/E72.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
assert type(res) == type((0))
2727
#: E721
2828
assert type(res) != type((1, ))
29-
#: E721
29+
#: Okay
3030
assert type(res) is type((1, ))
31-
#: E721
31+
#: Okay
3232
assert type(res) is not type((1, ))
3333
#: E211 E721
3434
assert type(res) == type ([2, ])

test/suite/out/E72.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#: E721
1313
import types
1414

15-
if not isinstance(res, types.ListType):
15+
if type(res) is not types.ListType:
1616
pass
1717
#: E721
1818
assert isinstance(res, type(False)) or isinstance(res, type(None))
@@ -26,10 +26,10 @@
2626
assert isinstance(res, type((0)))
2727
#: E721
2828
assert not isinstance(res, type((1, )))
29-
#: E721
30-
assert isinstance(res, type((1, )))
31-
#: E721
32-
assert not isinstance(res, type((1, )))
29+
#: Okay
30+
assert type(res) is type((1, ))
31+
#: Okay
32+
assert type(res) is not type((1, ))
3333
#: E211 E721
3434
assert isinstance(res, type([2, ]))
3535
#: E201 E201 E202 E721

test/test_autopep8.py

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -384,37 +384,6 @@ def test_split_at_offsets_with_out_of_order(self):
384384
self.assertEqual(['12', '3', '4'],
385385
autopep8.split_at_offsets('1234', [3, 2]))
386386

387-
def test_fix_2to3(self):
388-
self.assertEqual(
389-
'try: pass\nexcept ValueError as e: pass\n',
390-
autopep8.fix_2to3('try: pass\nexcept ValueError, e: pass\n'))
391-
392-
self.assertEqual(
393-
'while True: pass\n',
394-
autopep8.fix_2to3('while 1: pass\n'))
395-
396-
self.assertEqual(
397-
"""\
398-
import sys
399-
sys.maxsize
400-
""",
401-
autopep8.fix_2to3("""\
402-
import sys
403-
sys.maxint
404-
"""))
405-
406-
def test_fix_2to3_subset(self):
407-
line = 'type(res) == type(42)\n'
408-
fixed = 'isinstance(res, type(42))\n'
409-
410-
self.assertEqual(fixed, autopep8.fix_2to3(line))
411-
self.assertEqual(fixed, autopep8.fix_2to3(line, select=['E721']))
412-
self.assertEqual(fixed, autopep8.fix_2to3(line, select=['E7']))
413-
414-
self.assertEqual(line, autopep8.fix_2to3(line, select=['W']))
415-
self.assertEqual(line, autopep8.fix_2to3(line, select=['E999']))
416-
self.assertEqual(line, autopep8.fix_2to3(line, ignore=['E721']))
417-
418387
def test_is_python_file(self):
419388
self.assertTrue(autopep8.is_python_file(
420389
os.path.join(ROOT_DIR, 'autopep8.py')))
@@ -4406,6 +4375,24 @@ def test_e721_in_conditional(self):
44064375
with autopep8_context(line, options=['--aggressive']) as result:
44074376
self.assertEqual(fixed, result)
44084377

4378+
def test_e721_in_conditional_pat2(self):
4379+
line = "if type(res) == type(42):\n pass\n"
4380+
fixed = "if isinstance(res, type(42)):\n pass\n"
4381+
with autopep8_context(line, options=['--aggressive']) as result:
4382+
self.assertEqual(fixed, result)
4383+
4384+
def test_e721_in_not_conditional(self):
4385+
line = "if type(res) != type(''):\n pass\n"
4386+
fixed = "if not isinstance(res, type('')):\n pass\n"
4387+
with autopep8_context(line, options=['--aggressive']) as result:
4388+
self.assertEqual(fixed, result)
4389+
4390+
def test_e721_in_not_conditional_pat2(self):
4391+
line = "if type(a) != type(b) or type(a) == type(ccc):\n pass\n"
4392+
fixed = "if not isinstance(a, type(b)) or isinstance(a, type(ccc)):\n pass\n"
4393+
with autopep8_context(line, options=['--aggressive']) as result:
4394+
self.assertEqual(fixed, result)
4395+
44094396
def test_e722(self):
44104397
line = "try:\n print(a)\nexcept:\n pass\n"
44114398
fixed = "try:\n print(a)\nexcept BaseException:\n pass\n"

0 commit comments

Comments
 (0)