import re import os import sys import argparse FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'] parser = argparse.ArgumentParser(description='Detects invalid dependencies of a module') parser.add_argument('-r', '--root', required=True, help='Path to the directory of the whole project') parser.add_argument('-m', '--module', required=True, help='Path to the directory of the target module') def get_path_from_import_line(import_line): matches = re.findall('[\'"](.+?)[\'"]', import_line) if len(matches) == 0: return None path = matches[0] if not path.startswith('.'): return None return path def get_absolute_path_from_import_path(filepath, import_path): dir_path = os.path.dirname(filepath) absolute_import_path = os.path.join(dir_path, import_path) return os.path.normpath(absolute_import_path) def get_imports(filepath): if os.path.splitext(filepath)[1] not in FILE_EXTENSIONS: return [] imports = [] with open(filepath, 'r') as fin: for i, code_line in enumerate(fin.readlines()): import_matches = re.findall('} from [\'"].+?[\'"];', code_line) if len(import_matches) == 0: continue import_relative_path = get_path_from_import_line(import_matches[0]) if import_relative_path is not None: imports.append({ 'line_number': i + 1, 'path': get_absolute_path_from_import_path(filepath, import_relative_path) }) return imports def is_importing_from_module_subdir(module_path, import_path): return import_path.startswith(module_path) def is_importing_from_module(module_path, import_path): return import_path == module_path def main(root_path, module_path): check_passed = True # Check that all external imports only import from the module's index file for dir_path, _, files in os.walk(root_path): if os.path.normpath(dir_path).startswith(module_path): continue for file in files: filepath = os.path.join(dir_path, file) imports = get_imports(filepath) errors = [] for import_item in imports: if is_importing_from_module_subdir(module_path, import_item['path']) and not is_importing_from_module(module_path, import_item['path']): errors.append(import_item) if len(errors) > 0: print(f'Incorrect imports from module subdir in {filepath}:') for import_item in errors: print(f" Line {import_item['line_number']}: '{import_item['path']}' should be '{module_path}'") check_passed = False print() # Check that there are files in the module that import something from outside the module for dir_path, _, files in os.walk(module_path): break for file in files: filepath = os.path.join(dir_path, file) imports = get_imports(filepath) errors = [] for import_item in imports: if not is_importing_from_module_subdir(module_path, import_item['path']): errors.append(import_item) if len(errors) > 0: print(f'Invalid external imports from module file {filepath}:') for import_item in errors: print(f" Line {import_item['line_number']}: '{import_item['path']}'") check_passed = False print() if not check_passed: sys.exit('Script failed: Not all imports are correct.') print('All imports are correct.') if __name__ == '__main__': args = parser.parse_args() main( os.path.normpath(args.root), os.path.normpath(args.module) )