frindlymap/tool/check_modules.py

68 lines
3.5 KiB
Python

#!/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(),
"groups": {"core"},
"work": set(),
"circles": set(),
"history": set(),
"events": set(),
"app": {"core", "platform", "storage", "sharing", "sync", "discovery", "inference", "layout", "visibility", "kinship", "events", "groups", "work", "circles", "history"},
"ui": {"app", "core", "platform", "storage", "discovery", "inference", "layout", "visibility", "kinship", "events", "groups", "work", "circles", "history"},
}
# 모듈 입구가 둘 이상인 경우: 입구 파일이 모듈 이름.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", "flutter_contacts", "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)