xref: /aosp_15_r20/external/pytorch/tools/linter/adapters/nativefunctions_linter.py (revision da0073e96a02ea20f0ac840b70461e3646d07c45)
1#!/usr/bin/env python3
2"""
3Verify that it is possible to round-trip native_functions.yaml via ruamel under some
4configuration.  Keeping native_functions.yaml consistent in this way allows us to
5run codemods on the file using ruamel without introducing line noise.  Note that we don't
6want to normalize the YAML file, as that would to lots of spurious lint failures.  Anything
7that ruamel understands how to roundtrip, e.g., whitespace and comments, is OK!
8
9ruamel is a bit picky about inconsistent indentation, so you will have to indent your
10file properly.  Also, if you are working on changing the syntax of native_functions.yaml,
11you may find that you want to use some format that is not what ruamel prefers.  If so,
12it is OK to modify this script (instead of reformatting native_functions.yaml)--the point
13is simply to make sure that there is *some* configuration of ruamel that can round trip
14the YAML, not to be prescriptive about it.
15"""
16
17from __future__ import annotations
18
19import argparse
20import json
21import sys
22from enum import Enum
23from io import StringIO
24from typing import NamedTuple
25
26import ruamel.yaml  # type: ignore[import]
27
28
29class LintSeverity(str, Enum):
30    ERROR = "error"
31    WARNING = "warning"
32    ADVICE = "advice"
33    DISABLED = "disabled"
34
35
36class LintMessage(NamedTuple):
37    path: str | None
38    line: int | None
39    char: int | None
40    code: str
41    severity: LintSeverity
42    name: str
43    original: str | None
44    replacement: str | None
45    description: str | None
46
47
48if __name__ == "__main__":
49    parser = argparse.ArgumentParser(
50        description="native functions linter",
51        fromfile_prefix_chars="@",
52    )
53    parser.add_argument(
54        "--native-functions-yml",
55        required=True,
56        help="location of native_functions.yaml",
57    )
58
59    args = parser.parse_args()
60
61    with open(args.native_functions_yml) as f:
62        contents = f.read()
63
64    yaml = ruamel.yaml.YAML()  # type: ignore[attr-defined]
65    yaml.preserve_quotes = True  # type: ignore[assignment]
66    yaml.width = 1000  # type: ignore[assignment]
67    yaml.boolean_representation = ["False", "True"]  # type: ignore[attr-defined]
68    try:
69        r = yaml.load(contents)
70    except Exception as err:
71        msg = LintMessage(
72            path=None,
73            line=None,
74            char=None,
75            code="NATIVEFUNCTIONS",
76            severity=LintSeverity.ERROR,
77            name="YAML load failure",
78            original=None,
79            replacement=None,
80            description=f"Failed due to {err.__class__.__name__}:\n{err}",
81        )
82
83        print(json.dumps(msg._asdict()), flush=True)
84        sys.exit(0)
85
86    # Cuz ruamel's author intentionally didn't include conversion to string
87    # https://stackoverflow.com/questions/47614862/best-way-to-use-ruamel-yaml-to-dump-to-string-not-to-stream
88    string_stream = StringIO()
89    yaml.dump(r, string_stream)
90    new_contents = string_stream.getvalue()
91    string_stream.close()
92
93    if contents != new_contents:
94        msg = LintMessage(
95            path=args.native_functions_yml,
96            line=None,
97            char=None,
98            code="NATIVEFUNCTIONS",
99            severity=LintSeverity.ERROR,
100            name="roundtrip inconsistency",
101            original=contents,
102            replacement=new_contents,
103            description=(
104                "YAML roundtrip failed; run `lintrunner --take NATIVEFUNCTIONS -a` to apply the suggested changes. "
105                "If you think this is in error, please see tools/linter/adapters/nativefunctions_linter.py"
106            ),
107        )
108
109        print(json.dumps(msg._asdict()), flush=True)
110