We no longer support Python 2 so we can drop support for linting code
for Python 2 support.
This gets rid of the check for from __future__, enabling us to delete
those imports.
Alphare |
hg-reviewers |
We no longer support Python 2 so we can drop support for linting code
for Python 2 support.
This gets rid of the check for from __future__, enabling us to delete
those imports.
Automatic diff as part of commit; lint not applicable. |
Automatic diff as part of commit; unit tests not applicable. |
Path | Packages | |||
---|---|---|---|---|
M | contrib/check-py3-compat.py (49 lines) |
import ast | import ast | ||||
import importlib | import importlib | ||||
import os | import os | ||||
import sys | import sys | ||||
import traceback | import traceback | ||||
import warnings | import warnings | ||||
def check_compat_py2(f): | |||||
"""Check Python 3 compatibility for a file with Python 2""" | |||||
with open(f, 'rb') as fh: | |||||
content = fh.read() | |||||
root = ast.parse(content) | |||||
# Ignore empty files. | |||||
if not root.body: | |||||
return | |||||
futures = set() | |||||
haveprint = False | |||||
for node in ast.walk(root): | |||||
if isinstance(node, ast.ImportFrom): | |||||
if node.module == '__future__': | |||||
futures |= {n.name for n in node.names} | |||||
elif isinstance(node, ast.Print): | |||||
haveprint = True | |||||
if 'absolute_import' not in futures: | |||||
print('%s not using absolute_import' % f) | |||||
if haveprint and 'print_function' not in futures: | |||||
print('%s requires print_function' % f) | |||||
def check_compat_py3(f): | def check_compat_py3(f): | ||||
"""Check Python 3 compatibility of a file with Python 3.""" | """Check Python 3 compatibility of a file with Python 3.""" | ||||
with open(f, 'rb') as fh: | with open(f, 'rb') as fh: | ||||
content = fh.read() | content = fh.read() | ||||
try: | try: | ||||
ast.parse(content, filename=f) | ast.parse(content, filename=f) | ||||
except SyntaxError as e: | except SyntaxError as e: | ||||
else: | else: | ||||
print( | print( | ||||
'%s: error importing module: <%s> %s (line %d)' | '%s: error importing module: <%s> %s (line %d)' | ||||
% (f, type(e).__name__, e, frame.lineno) | % (f, type(e).__name__, e, frame.lineno) | ||||
) | ) | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
if sys.version_info[0] == 2: | |||||
fn = check_compat_py2 | |||||
else: | |||||
# check_compat_py3 will import every filename we specify as long as it | # check_compat_py3 will import every filename we specify as long as it | ||||
# starts with one of a few prefixes. It does this by converting | # starts with one of a few prefixes. It does this by converting | ||||
# specified filenames like 'mercurial/foo.py' to 'mercurial.foo' and | # specified filenames like 'mercurial/foo.py' to 'mercurial.foo' and | ||||
# importing that. When running standalone (not as part of a test), this | # importing that. When running standalone (not as part of a test), this | ||||
# means we actually import the installed versions, not the files we just | # means we actually import the installed versions, not the files we just | ||||
# specified. When running as test-check-py3-compat.t, we technically | # specified. When running as test-check-py3-compat.t, we technically | ||||
# would import the correct paths, but it's cleaner to have both cases | # would import the correct paths, but it's cleaner to have both cases | ||||
# use the same import logic. | # use the same import logic. | ||||
sys.path.insert(0, '.') | sys.path.insert(0, '.') | ||||
fn = check_compat_py3 | |||||
for f in sys.argv[1:]: | for f in sys.argv[1:]: | ||||
with warnings.catch_warnings(record=True) as warns: | with warnings.catch_warnings(record=True) as warns: | ||||
fn(f) | check_compat_py3(f) | ||||
for w in warns: | for w in warns: | ||||
print( | print( | ||||
warnings.formatwarning( | warnings.formatwarning( | ||||
w.message, w.category, w.filename, w.lineno | w.message, w.category, w.filename, w.lineno | ||||
).rstrip() | ).rstrip() | ||||
) | ) | ||||
sys.exit(0) | sys.exit(0) |