#!/usr/bin/env python # # Copyright 2021 Espressif Systems (Shanghai) CO LTD # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import subprocess from sys import exit try: from typing import List except ImportError: # Only used for type annotations pass IGNORE_LIST_MYPY = 'tools/ci/mypy_ignore_list.txt' def types_valid_global_rules(file_name, ignorelisted): # type: (str, bool) -> bool """ Run Mypy check with global rules on the given file, return TRUE if Mypy check passes """ output = subprocess.DEVNULL if ignorelisted else None mypy_exit_code = subprocess.call('mypy {}'.format(file_name), shell=True, stdout=output) return not bool(mypy_exit_code) def types_valid_ignored_rules(file_name): # type: (str) -> bool """ Run Mypy check with rules for ignore list on the given file, return TRUE if Mypy check passes """ mypy_exit_code = subprocess.call('mypy {} --allow-untyped-defs'.format(file_name), shell=True) return not bool(mypy_exit_code) def check_files(files): # type: (List[str]) -> List[str] """ Check files for type annotatins: - new python file -> run Mypy check with global rules - existed file on ignore list -> run Mypy check with Global rules: global check OK -> remove from ignore list (updates on the file system) global check FAILS -> run Mypy check with Ignored rules Global rules: Enforce type annotations for all functions in file Ignored rules: Do not enforce untyped functions, check only already typed """ type_issues = [] with open(IGNORE_LIST_MYPY, 'r') as f: ignore_list = [item.strip() for item in f.readlines()] updated_ignore_list = ignore_list.copy() for file_name in files: if file_name in ignore_list: if types_valid_global_rules(file_name, ignorelisted=True): updated_ignore_list.remove(file_name) print('\33[93m\n File {} removed from ignore list - run commit again! \n\33[0m'.format(file_name)) continue if types_valid_ignored_rules(file_name): continue else: type_issues.append(file_name) else: if not types_valid_global_rules(file_name, ignorelisted=False): type_issues.append(file_name) if updated_ignore_list != ignore_list: with open(IGNORE_LIST_MYPY, 'w') as f: for item in updated_ignore_list: f.write('{}\n'.format(item)) return type_issues def main(): # type: () -> None parser = argparse.ArgumentParser() parser.add_argument('filenames', nargs='*', help='Filenames to check.') args = parser.parse_args() type_issues = check_files(args.filenames) if type_issues: print('mypy check failed for:') for file_name in type_issues: print('\t', file_name) exit(1) if __name__ == '__main__': main()