05. 언어 지원 전략
현재 구현 상태 (v0.46.0 / metrics v14.5.0): 6개 정식 지원 언어(TS/JS, Python, Go, Rust, Java, C#)와 C/C++ 제한 지원은 모두
internal/lang의 경량 렉서 프론트엔드(퍼지 파싱, lizard 방식) 또는 표준 Go AST 경로로 동작한다. C/C++은 전처리기 미전개 한계 때문에 confidence는 medium 중심이며, SIZE-02/03, CPLX-01~05, ARCH-01/02/03(include 그래프), DUP-01/02, SEC-01, HYG-01/03/04/05/06/08, RES-01(malloc/free·new/delete)을 지원한다. Rust ARCH-01/03은mod.rsfacade/re-export 관례를 반영해 부모/자식 모듈 계층 엣지를 제외하고 sibling/cross-branch 의존만 순환·결합 후보로 본다. Go 파일은 추가로 표준 라이브러리go/parser파스 실패를 HYG-07로 기록하고--strict에서 exit 3을 반환한다. 렉서가 보지 못하는 타입 내부 구조(클래스 필드·메서드·상속)가 필요한 지표(ARCH-07 LCOM4, SIZE-04 god type, ARCH-08 DIT)는 추가로 tree-sitter WASM(wasm32-wasi + 순수 Go wazero,CGO_ENABLED=0유지) 프론트엔드를 사용한다 — §3 참조. v13 점수는 아래 §4.1의 카테고리×언어 eligible production CLOC만 분모로 쓴다.
1. 지원 언어 (확정)
정식 지원 6개 언어: TypeScript/JavaScript, Python, Go, Rust, Java, C#. 추가로 C/C++은 제한 지원(§5)한다. 6개 언어는 전체 메트릭 적용이 목표이고, C/C++은 빌드 없는 정적 스캔에서 신뢰 가능한 범위만 적용한다.
| 단계 | 언어 | 파서 | 비고 |
|---|---|---|---|
| v0.1 (구현 완료) | TypeScript / JavaScript (+JSX/TSX) | 내부 렉서 (tree-sitter 이연) | AI 산출물 최다 언어 |
| v0.1 (구현 완료) | Python | 내부 렉서 (tree-sitter 이연) | 〃 |
| v0.1 (구현 완료) | Go | go/ast (stdlib) | 자체 도구 언어, 무료로 고정밀. 수직 슬라이스 기준 언어 |
| v0.4 (구현 완료) | Java, C# | 내부 렉서 (tree-sitter 이연) | 기업 환경 수요 |
| v0.4 (구현 완료) | Rust | 내부 렉서 (tree-sitter 이연) | 소유권 모델로 RES 일부가 불필요 — 대신 Rust 고유 메트릭(§2.4) |
| v0.7 (구현 완료) | C / C++ | 내부 렉서 | 전처리기/매크로 한계로 일부 메트릭 제한 — §5 |
언어 미지원 파일도 텍스트 수준 메트릭은 동작한다: SIZE-01(라인 카운트, 휴리스틱 주석 인식), SEC-01(시크릿 — 정규식/엔트로피는 언어 무관), HYG-03(TODO). 따라서 어떤 프로젝트든 부분 진단은 가능하다. 다만 v13 점수에서는 명시적 지원표 밖 finding을 unsupported_language로 보존·제외해, 부분 진단을 완전한 언어 커버리지처럼 점수화하지 않는다. other production CLOC는 언어 독립 SIZE만 eligible이다.
2. 언어별 Spec 작성 가이드
새 언어 추가 = internal/lang/<lang> 프론트엔드 1개 + 엔진 blank import + 픽스처 테스트. 목표 작업량: 언어당 2~3일.
2.1 매핑 테이블에 반드시 정의할 것
| 항목 | 예시 (Python) | 예시 (TypeScript) |
|---|---|---|
| 함수 추출 | def, lambda 근사 | function/method/arrow 추출 |
| 분기 토큰(CCN) | if, elif, for, while, except, and, or | if, for, while, case, catch, &&, ||, ?: |
| 중첩 추적 | 들여쓰기/블록 토큰 | {} 블록과 제어문 |
| 주석/문자열 처리 | #, triple quote | //, /* */, string/template literal |
| import 추출 | import, from ... import | import, require(), re-export |
| 한계 문서화 | 동적 import, decorator 영향 | JSX/TS 타입 구문, regex/template literal 휴리스틱 |
2.2 import → 내부 모듈 해석 (빌드 없이)
ARCH 메트릭의 정밀도를 좌우하는 부분. 언어별 해석 규칙:
| 언어 | 모듈 단위 | 내부/외부 판별 | 해석 규칙 |
|---|---|---|---|
| Go | 패키지(디렉터리) | go.mod의 module path 접두 일치 | import path → 디렉터리 직매핑 |
| TS/JS | 파일 | 상대 경로(./, ../) 또는 tsconfig.json/package.json의 paths/alias | 확장자 보완(.ts, .tsx, /index.ts), alias는 tsconfig paths 파싱 |
| Python | 파일/패키지 | 프로젝트 루트 기준 최상위 패키지명 일치 | dotted path → 디렉터리/파일, __init__.py 인식, 상대 import 처리 |
| Java | 패키지 | 소스 트리 내 동일 package 선언 존재 여부 | package 선언으로 역인덱스 구축 |
| C# | 네임스페이스 | 소스 트리 내 동일 namespace 선언 존재 여부 | using → namespace 역인덱스. .csproj/.sln으로 프로젝트 경계 인식 |
| Rust | 모듈(mod) | crate 내부 경로(crate::, self::, super::) | use 선언 → mod 트리/파일 매핑. Cargo.toml workspace로 crate 간 의존 인식 |
| C/C++ | 파일 | #include "..."(따옴표)만 내부 후보 | include 경로 휴리스틱(루트, 같은 디렉터리, include/) |
해석 실패한 import는 외부로 간주(감점 없음). 오탐(가짜 순환)을 만들지 않는 방향으로 보수적으로.
순환 검출 단위는 언어 관례를 따른다: Go/Java/Python은 패키지 수준, TS/JS는 파일 수준(파일=모듈), C/C++은 헤더 포함 그래프.
2.3 SEC/RES 룰의 언어별 인스턴스
각 룰은 "언어 무관 정의(CWE) + 언어별 패턴"으로 분리한다. 예: SEC-03 TLS 검증 비활성화
| 언어 | 패턴 |
|---|---|
| Go | tls.Config{InsecureSkipVerify: true} |
| Python | verify=False (requests 계열 호출 인자), ssl._create_unverified_context |
| JS/TS | rejectUnauthorized: false, NODE_TLS_REJECT_UNAUTHORIZED=0 |
| Java | TrustManager no-op 구현, setHostnameVerifier(ALLOW_ALL) |
| C# | ServerCertificateCustomValidationCallback가 항상 true 반환, ServicePointManager.ServerCertificateValidationCallback 무조건 통과 |
| Rust | reqwest danger_accept_invalid_certs(true), native-tls danger_accept_invalid_hostnames |
현재 룰은 Go 코드와 경량 lexer 패턴으로 구현한다. 후속 플러그인 모델에서는 YAML 메타데이터 또는 tree-sitter 쿼리를 사용할 수 있다.
2.4 언어 고유 메트릭 보강
언어별로 "그 언어에서 위험 신호인 것"을 추가 룰로 둔다(메트릭 ID는 기존 카테고리에 귀속):
| 언어 | 보강 룰 | 귀속 |
|---|---|---|
| Rust | unsafe 블록 밀도, unwrap()/expect() 남용(라이브러리 코드), mem::forget/Box::leak, #[allow(...)] 남발 | RES/HYG |
| C# | IDisposable을 using 없이 사용, async void(이벤트 핸들러 외), .Result/.Wait() 블로킹(데드락 위험) | RES/HYG |
| Java | try-with-resources 미사용 Closeable, 빈 InterruptedException catch, raw type 사용 | RES/HYG |
| Go | 고루틴 누수 패턴(취소 없는 for-select), context 미전파 | RES |
| Python | mutable default argument, bare except: | HYG |
| TS/JS | any 밀도(TS), floating promise(await 누락 휴리스틱) | HYG |
3. tree-sitter WASM 통합 (구현됨)
기본(렉서) 프론트엔드 외에, 타입 내부 구조가 필요한 지표는 tree-sitter를 사용한다(v8.2.0~). 문법을 wasm32-wasi로 컴파일해 순수 Go wazero 런타임으로 구동하므로 CGO_ENABLED=0 정적 빌드가 유지된다(emscripten·CGo go-tree-sitter 불필요). 현재 ARCH-07(LCOM4 응집도, Go+TS), SIZE-04(God Type, Go=go/ast / TS·Java·Python·C#·C++·Rust=tree-sitter), ARCH-08(DIT, TS·Java·C#·C++), 그리고 SEC-07/08의 함수 내 taint 분석(TS/JS 소스→싱크 데이터플로우)이 이 경로를 쓴다. Rust는 메서드가 struct 본문이 아니라 별도 impl 블록에 있어 inherent impl을 타입명으로 합산하는 전용 추출기를 쓴다(trait impl 제외, 상속 없어 DIT N/A). 문법 *.wasm은 gzip 압축해 go:embed(첫 컴파일 시 1회 gunzip). 설계 메모:
- 빌드 파이프라인: 문법 C 소스(
parser.c+scanner.c)와 작은 래퍼(wrap_generic.c)를 wasi-sdk clang으로wasm32-wasi타깃 컴파일(emscripten 미사용) → gzip 압축 →go:embed.grammars/build.sh가 언어 목록을 돌며 재생성한다. - 런타임: wazero(순수 Go, CGo 없음)로 인스턴스화. WASI command 모듈이라 파일당 1회 인스턴스화(컴파일은 grammar별 1회 캐시).
- 노드 추출: 래퍼가 named 노드를
id/parent/start/end/field/typeTSV로 stdout에 출력 → Go에서 트리 재구성. 언어별 추출(클래스/메서드/필드/상속)은 Go에서 처리. - 에러 회복: tree-sitter는 문법 오류가 있어도 ERROR 노드를 포함한 트리를 반환 → 부분 추출 가능.
- 검증: 픽스처의 노드 종류 단위 테스트(
TestGodClasses_*)로 문법 업그레이드 시 매핑 호환성 확인.
폴백 계획
wazero 경유 성능이 목표(100k LOC/30s)에 못 미치면: (a) 토큰 수준 메트릭(SIZE, DUP, SEC-01)은 자체 경량 렉서로 분리, (b) 빌드 태그로 CGo 버전을 옵션 제공(기본은 순수 Go 유지).
4. 언어별 메트릭 적용 매트릭스
| 메트릭 | Go | TS/JS | Python | Java | C# | Rust | C/C++(제한) | 미지원 언어 |
|---|---|---|---|---|---|---|---|---|
| SIZE-01 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓(텍스트) |
| SIZE-02/03 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
| SIZE-04 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | △ | ✗ |
| CPLX-01/02/03 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓(match 분기 포함) | △(매크로 영향) | ✗ |
| CPLX-04/05 | ✓ | △ | △ | △ | △ | △ | △ | ✗ |
| ARCH-01/02/03 | ✓(패키지/파일) | △(파일/심볼) | △(패키지/심볼) | △(패키지/심볼) | △(네임스페이스/심볼) | △(mod/crate/심볼) | △(include/심볼) | ✗ |
| DUP-01/02 | ✓ | △ | △ | △ | △ | △ | △ | △(라인 해시) |
| SEC-01 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓(정규식) |
| SEC-02~05 | ✓ | ✓ | ✓ | ✓ | ✓ | △(싱크 적음) | △ | ✗ |
| RES-01 | ✓(defer) | ✗ | ✓(with) | ✗ | ✗ | △(RAII 예외: forget/leak/unsafe) | ✓(malloc/free·new/delete) | ✗ |
| HYG-01 | ✓(err 무시) | ✓(빈 catch) | ✓(except: pass) | ✓ | ✓(빈 catch) | ✓(unwrap 남용, let _ =) | △ | ✗ |
| HYG-02/03/06 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | △ | △(03만) |
| HYG-04/05 | ✓(API doc 포함) | △ | △ | △ | △ | △ | △ | ✗ |
| HYG-08(주석코드) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | △ | ✗ |
| HYG-09(미구현 스텁) | ✓(panic/errors) | ✓(throw) | △(문구형만) | ✓ | ✓ | ✓(todo!) | ✗ | ✗ |
| HYG-10(디버그 잔재) | ✗(전용 호출 없음) | ✓(console.debug 등) | ✓(breakpoint/pdb) | ✓(printStackTrace) | ✓(Debugger.Break) | ✓(dbg!) | ✗ | ✗ |
| HYG-11(명명 비일관) | ✗(대소문자 관례) | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ |
| HYG-12(검사기 억제) | ✗(억제 관용구 없음) | ✓(@ts-ignore 등) | ✓(bare type:ignore) | ✓(@SuppressWarnings("all")) | ✓(pragma/nullable) | ✗(타입 억제 불가) | ✗ | ✗ |
✓ 완전 지원, △ 부분/휴리스틱, ✗ 미적용. 이 표는 finding 생성의 세부 커버리지이고, 아래 표는 점수 분모의 보수적 eligibility 계약이다.
4.1 점수 분모용 카테고리×언어 매트릭스 (v13.0.0)
세부 룰별 부분 지원 정도는 위 표가 원본이고, 점수 엔진은 보수적인 카테고리 단위 지원표를 사용한다.
| 카테고리 | eligible production 언어 | 미지원 CLOC 처리 |
|---|---|---|
| ARCH | Go, TS/JS, Python, Java, C#, Rust, C/C++ | 분모 제외, finding은 unsupported_language |
| CPLX | 위와 같음 | 〃 |
| SEC | 위와 같음 | 〃 |
| DUP | 위와 같음 | 〃 |
| SIZE | 위 언어 + other 코드형 텍스트 | 사실상 모든 분석 가능한 production 텍스트 |
| RES | Go, Python, Rust, C/C++ | TS/JS·Java·C# 등은 RES 분모 제외 |
| HYG | Go, TS/JS, Python, Java, C#, Rust, C/C++ | 분모 제외, finding은 unsupported_language |
coverage(category) = eligible production CLOC / all production CLOC. eligible CLOC가 0이면 그 카테고리는 not_assessed이며, 전 카테고리가 미측정이면 Grade N/A다. test/documentation/configuration/generated는 언어와 무관하게 모든 카테고리 점수 분모·debt에서 제외한다.
5. C/C++ 제한 지원의 범위와 이유
C/C++은 전처리기(#define, #ifdef, 매크로) 전개 없이는 구문 정보가 불완전하다는 본질적 문제가 있다. JAM은 빌드를 하지 않으므로(컴파일 플래그·include 경로를 모름) 전개 전 소스를 경량 lexer로 분석한다. 그 결과:
- 신뢰 가능: SIZE(라인 기반), DUP-01(토큰 해시 — 매크로 호출도 토큰으로 일관 처리), SEC-01(정규식/엔트로피), RES-01의 malloc/free·new/delete 함수-로컬 짝 검사.
- 부분 동작: CPLX — 매크로 안에 분기가 숨으면 과소 측정,
#ifdef분기 양쪽이 모두 파싱되면 과대 측정 가능. confidence를 medium 이하로 기록한다. - 휴리스틱: ARCH — include 그래프는 헤더 검색 경로를 모르는 상태의 근사. 가짜 순환을 피하기 위해 따옴표 include + 경로 일치 확인된 것만 에지로 인정.
- 리포트 표기: C/C++ 비중이 높은 프로젝트는 리포트 머리말에 "C/C++은 제한 지원 — 전처리기 미전개로 일부 메트릭의 confidence가 낮습니다"를 고지한다.
.h분류(v10.1.0):.h는 기본 C지만, 내용에 C++ 마커(namespace/template/class/std::/접근지정자)가 있으면 cpp로 분류해 헤더 전용 C++ 라이브러리(예: fmt)의 클래스가 타입 내부 지표(god type/DIT/LCOM)에 잡히게 한다..hpp/.hh/.hxx는 항상 cpp. 순수 C 헤더는 마커가 없어 C로 남는다. (별개로, minified/번들·벤더 JS는-min.js/.min.js·>2KB 단일 라인·/*!라이선스 배너(비압축 bootstrap.js류)로 감지해 generated로 제외 — 벤더 라이브러리를 소스로 분석하지 않는다.)
템플릿 메타프로그래밍이 무거운 현대 C++ 코드베이스는 정밀 분석이 컴파일러 기반 도구(clang-tidy 등)의 영역임을 10-coverage-and-limitations.md와 함께 명시한다.