#!/usr/bin/env python3 """모듈 규칙 검사: (1) 파일 700줄 이하 (400줄 넘으면 경고) (2) 모듈 간 import 방향.""" import os, re, sys, pathlib MAX, WARN = 700, 400 # 허용 방향: 왼쪽 모듈은 오른쪽 모듈만 import 할 수 있다. ALLOW = { "core": set(), "platform": set(), "platform_android": {"core", "platform", "storage"}, "storage": {"platform"}, "keys": {"core", "platform"}, "sharing": {"storage"}, "sync": {"core", "storage"}, "transport": {"core"}, "discovery": {"platform"}, "inference": {"core"}, "layout": {"core"}, "visibility": {"core"}, "kinship": set(), "events": set(), "app": {"core", "platform", "storage", "sharing", "sync", "discovery", "inference", "layout", "visibility", "kinship", "events"}, "ui": {"app", "core", "platform", "storage", "discovery", "inference", "layout", "visibility", "kinship", "events"}, } # 모듈 입구가 둘 이상인 경우: 입구 파일이 모듈 이름.dart 외에 허용되는 것들. EXTRA_BARRELS = {"platform": {"platform_ui.dart"}, "storage": {"storage_sqlite.dart"}} # 모듈 폴더 밖(main.dart, composition/)은 조립 코드라서 어느 모듈 입구든 쓸 수 있다. # 플랫폼마다 다른 플러그인은 platform_android 에서만 쓴다 (나중에 iOS 등을 붙이기 쉽게). PLATFORM_ONLY = ("flutter_secure_storage", "sensors_plus", "nsd", "mobile_scanner", "sqflite_sqlcipher", "geolocator", "call_log", "url_launcher", "flutter_local_notifications", "timezone") root = pathlib.Path(__file__).resolve().parent.parent / "lib" bad = 0 for f in sorted(root.rglob("*.dart")): n = len(f.read_text(encoding="utf-8").splitlines()) rel = f.relative_to(root) if n > MAX: print(f"오류 {rel}: {n}줄 (최대 {MAX})"); bad += 1 elif n > WARN: print(f"경고 {rel}: {n}줄 ({WARN}줄 넘음)") parts = rel.parts in_platform = len(parts) >= 2 and parts[1].startswith("platform_") for m in re.finditer(r"import\s+'package:(\w+)/", f.read_text(encoding="utf-8")): if m.group(1) in PLATFORM_ONLY and not in_platform: print(f"오류 {rel}: {m.group(1)} 는 platform_* 모듈에서만 import"); bad += 1 if len(parts) >= 3 and parts[0] == "modules": me = parts[1] for m in re.finditer(r"import\s+'([^']+)'", f.read_text(encoding="utf-8")): p = m.group(1) if p.startswith("package:friendly_map/"): target = pathlib.PurePosixPath(p[len("package:friendly_map/"):]) elif p.startswith("."): target = pathlib.PurePosixPath( os.path.normpath(os.path.join(os.path.dirname(rel.as_posix()), p)).replace(os.sep, "/")) else: continue tp = target.parts if len(tp) >= 3 and tp[0] == "modules" and tp[1] != me: other = tp[1] if other not in ALLOW.get(me, set()): print(f"오류 {rel}: {me} 모듈은 {other} 모듈을 쓸 수 없음"); bad += 1 elif target.as_posix() not in {f"modules/{other}/{other}.dart", *(f"modules/{other}/{x}" for x in EXTRA_BARRELS.get(other, ()))}: print(f"오류 {rel}: {other} 모듈은 {other}.dart 로만 import"); bad += 1 print("통과" if not bad else f"규칙 위반 {bad}건") sys.exit(1 if bad else 0)