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