summaryrefslogtreecommitdiffstats
path: root/tools/lint/python/check_compat.py
blob: 25a15fcedc36d92964825a96d1e93fbf765455d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env python
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import ast
import json
import sys


def parse_file(f):
    with open(f, "rb") as fh:
        content = fh.read()
    try:
        return ast.parse(content)
    except SyntaxError as e:
        err = {
            "path": f,
            "message": e.msg,
            "lineno": e.lineno,
            "column": e.offset,
            "source": e.text,
            "rule": "is-parseable",
        }
        print(json.dumps(err))


def check_compat_py2(f):
    """Check Python 2 and Python 3 compatibility for a file with Python 2"""
    root = parse_file(f)

    # Ignore empty or un-parseable files.
    if not root or not root.body:
        return

    futures = set()
    haveprint = False
    future_lineno = 1
    may_have_relative_imports = False
    for node in ast.walk(root):
        if isinstance(node, ast.ImportFrom):
            if node.module == "__future__":
                future_lineno = node.lineno
                futures |= set(n.name for n in node.names)
            else:
                may_have_relative_imports = True
        elif isinstance(node, ast.Import):
            may_have_relative_imports = True
        elif isinstance(node, ast.Print):
            haveprint = True

    err = {
        "path": f,
        "lineno": future_lineno,
        "column": 1,
    }

    if "absolute_import" not in futures and may_have_relative_imports:
        err["rule"] = "require absolute_import"
        err["message"] = "Missing from __future__ import absolute_import"
        print(json.dumps(err))

    if haveprint and "print_function" not in futures:
        err["rule"] = "require print_function"
        err["message"] = "Missing from __future__ import print_function"
        print(json.dumps(err))


def check_compat_py3(f):
    """Check Python 3 compatibility of a file with Python 3."""
    parse_file(f)


if __name__ == "__main__":
    if sys.version_info[0] == 2:
        fn = check_compat_py2
    else:
        fn = check_compat_py3

    manifest = sys.argv[1]
    with open(manifest, "r") as fh:
        files = fh.read().splitlines()

    for f in files:
        fn(f)

    sys.exit(0)