ci: SDL description 커버리지 게이트 추가 (docs:check) - #281
Conversation
SDL에 설명 없는 필드를 추가해도 아무도 모른다. yarn validate에 설명 검사가 없어서 CI가 그대로 통과했고, 실제로 도메인별 편차가 크게 벌어졌다 (seller-order 0% ↔ user-search 100%). dto:check가 SDL↔DTO 동기화를 도구로 강제하듯 문서 커버리지도 게이트로 받친다. 구성: - scripts/sdl-description-coverage.ts — 순수 집계 로직. 파일 시스템·CLI와 분리해 spec에서 SDL 문자열만으로 검증 가능하게 했다 - scripts/check-sdl-description-coverage.ts — CLI. --report(임계치 검사 없이 현재 수치만) / --warning(종료코드 0) 지원 - jest.scripts.config.js — package.json의 jest는 rootDir이 src라 scripts/ 아래 spec을 수집하지 못한다. 앱 커버리지 임계치에 빌드 도구를 섞지 않으려고 실행만 분리했다 - validate 체인과 pr-check.yml check job에 docs:check·test:scripts 편입. check는 이미 필수 status check라 ruleset 변경은 불필요하다 단순 유무 집계를 쓰지 않은 이유 두 가지: 1. 플레이스홀더 판정. seller SDL에 """SellerOrderSummary 타입"""처럼 이름만 되풀이하는 설명이 70건 있다. 파서는 description이 있으므로 "문서화됨"으로 세지만 전달되는 정보는 0이다. 이걸 미기재로 세지 않으면 임계치가 거짓 안전을 준다. 실제로 이 판정을 넣자 enum 선언이 78.3% → 52.2%, input 타입 선언이 63.9% → 18.1%로 내려갔다. 2. 자명 필드 제외. id·createdAt·*Id를 분모에서 뺀다. 전부 채우게 하면 "상품 ID" 같은 무의미한 설명만 늘어난다. order-checkout.graphql이 이미 비자명 필드에만 근거를 적고 productId·quantity는 비워 뒀는데, 단순 집계는 그 판단에 벌점을 준다. 제외는 분모에서 빼는 것일 뿐 작성을 막지 않는다. 임계치는 2026-09-10 실측치로 고정해 회귀만 차단한다. 후속 PR에서 커버리지를 올린 뒤 임계치도 함께 상향한다. 테스트 14건: 플레이스홀더 판정 4 / 자명 필드 제외 2 / 요소별 집계 6 / 임계치 비교 2(실측치를 그대로 임계치로 박아도 부동소수 오차로 자기 자신에게 걸리지 않는지 포함).
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🩺 NestJS Doctor — 90/100 (Excellent)진단 306건 (error 0).
architecture / security 상위 항목
|
🧹 knip — dead-code 리포트전체 리포트
|
Coverage report
Test suite run success1869 tests passing in 225 suites. Report generated by 🧪jest coverage report action from b207cf9 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 33b79d09ed
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| inputType: 18, | ||
| inputField: 21, | ||
| outputType: 49, | ||
| outputField: 30, |
There was a problem hiding this comment.
Use exact baseline percentages for regression thresholds
The configured threshold is rounded down from the stated baseline: outputField currently has 185/603 coverage (30.68%), but a threshold of 30 permits undocumented additions without failing the gate. For example, adding 13 undocumented output fields leaves 185/616 = 30.03%, so docs:check still passes despite a clear regression; several other integer thresholds have the same slack. Preserve the actual baseline percentage, which findViolations already supports, if this check is intended to block every coverage regression.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
반영: 임계치를 정수 %에서 실측 분수(documented/total 스냅샷)로 교체.
지적대로 내림이 여유분을 만들었다. outputField 185/603=30.68%에 임계 30을 걸면 미기재 13건 추가(185/616=30.03%)가 통과한다 — "회귀만 차단"이라는 운용 취지와 어긋난다.
BASELINE을 {documented,total} 쌍으로 두고 임계 %를 거기서 계산하도록 바꿨다. 이제 185/603이 그대로 기준이라 185/616은 미달로 걸린다. findViolations의 부동소수 방어(1e-9)는 이미 있었고 spec에도 "실측치를 그대로 임계치로 박아도 자기 자신에게 걸리지 않는지" 케이스가 있어 그대로 쓴다.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
정수로 내림한 임계치가 그만큼의 여유분을 만들어, "회귀만 차단"이라는 운용
취지가 실제로는 성립하지 않았다. 출력 필드 185/603(30.68%)에 임계 30%를 걸면
미기재 13건을 더 넣어도 185/616 = 30.03%라 게이트를 그냥 통과한다. 다른 정수
임계치도 같은 여유를 갖고 있었다.
BASELINE을 {documented, total} 스냅샷으로 두고 임계 %를 거기서 계산한다.
findViolations는 이미 부동소수 비교(1e-9 여유)를 하고 있어 그대로 쓴다.
검증: root.graphql에 설명 없는 출력 필드를 1건만 추가해도
출력 type 필드 30.6% < 30.7%로 걸린다 (이전 기준이면 13건까지 통과).
리포트 출력도 `(기준 185/603 = 30.7%)` 형태로 바꿔 분수를 그대로 노출한다 —
임계치 갱신 시 --report 결과를 그대로 옮겨 적을 수 있다.
테스트 1건 추가(총 15건): 미기재 1건 추가가 분수 기준선에 걸리는지.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2d85869005
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| */ | ||
| const BASELINE: Record<Category, { documented: number; total: number }> = { | ||
| rootField: { documented: 130, total: 130 }, | ||
| rootArgScalar: { documented: 0, total: 6 }, |
There was a problem hiding this comment.
Make the zero-coverage baseline enforce additions
Because rootArgScalar starts at 0/6, its computed threshold is 0%, which can never be violated. Adding an undocumented non-ID scalar argument such as search(keyword: String!) changes the result to 0/7 and still passes docs:check, so this entire category does not provide the promised regression gate; enforce the baseline missing count or otherwise special-case zero-percent baselines.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
반영: 비율 하나로는 0% 기준선을 못 막는다는 지적이 맞다. 0/6 → 0/7도 0% ≥ 0%라 통과한다.
기준선을 {documented, total} 스냅샷으로 두고 있으니 미기재 건수(total-documented)도 같이 고정한다. 게이트를 두 조건으로 바꿨다.
- 미기재 건수 ≤ 기준선 미기재 건수 (0% 카테고리를 포함해 신규 미기재 추가를 차단)
- 커버리지 비율 ≥ 기준선 비율 (설명 있던 필드가 사라지는 회귀를 차단)
건수만으로는 "설명된 필드를 지워 비율이 떨어지는" 경우를 놓치고, 비율만으로는 이 지적의 0% 경우를 놓쳐서 둘 다 건다.
| const EXEMPT_FIELD_NAMES = new Set([ | ||
| 'id', | ||
| 'createdAt', | ||
| 'updatedAt', | ||
| 'deletedAt', |
There was a problem hiding this comment.
Include
quantity in the trivial-field exemption
The adjacent policy comment explicitly uses the undescribed quantity in order-checkout.graphql as an example that should not be penalized, but this set and the *Id pattern leave isExemptFieldName('quantity') false. Existing and newly added quantity fields therefore enter the denominator and can fail the coverage gate despite being declared exempt by the metric's stated policy.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
부분 반영: 주석과 구현이 어긋난 건 맞다. 다만 quantity를 제외 목록에 넣는 쪽이 아니라 주석에서 예시를 바꾸는 쪽으로 고친다.
quantity는 자명하지 않다고 본다 — 단위(개수인지 인분인지)나 상한이 필요한 경우가 있어 일률적으로 빼면 오히려 설명이 필요한 자리를 가린다. order-checkout에서 지금 비어 있는 건 그 맥락에서 자명했기 때문이지 제외 규칙의 근거는 아니었다.
주석 예시를 실제 제외 대상인 productId로 교체했다. quantity는 분모에 그대로 남는다(기준선에 이미 포함돼 있어 게이트가 새로 깨지지는 않는다).
비율 하나로는 기준선이 0%인 카테고리를 영영 막지 못했다. 루트 스칼라 인자는
0/6에서 시작하므로 임계가 0%가 되고, 설명 없는 인자를 하나 더 넣어 0/7이 돼도
0% >= 0%라 통과한다. 카테고리 전체가 게이트 역할을 못 하고 있었다.
기준선 {documented, total} 스냅샷에서 미기재 건수(total-documented)도 함께
고정하고, 두 조건을 건다.
- 미기재 건수 > 기준선 건수 → reason 'count'
- 커버리지 비율 < 기준선 비율 → reason 'ratio'
건수만으로는 설명이 있던 필드를 지워 비율이 떨어지는 회귀를 놓치고, 비율만으로는
위 0% 경우를 놓쳐서 둘 다 필요하다. 위반 출력에 어느 조건에 걸렸는지 찍는다.
검증: root.graphql의 no-op 필드에 설명 없는 스칼라 인자를 하나 추가하면
"루트 인자(스칼라·ID): 미기재 7건 > 기준선 6건"으로 걸린다. 이전 구현에서는
통과하던 케이스다.
함께: 제외 목록 주석이 quantity를 예시로 들면서 정작 구현은 제외하지 않아
서로 어긋났다. quantity는 단위·상한 설명이 필요할 수 있어 제외 목록에 넣지 않는
쪽이 맞다고 보고, 주석 예시를 실제 제외 대상인 productId로 바꿨다. 제외 목록을
넓히면 설명이 필요한 자리를 가린다는 점도 주석에 남겼다.
테스트 17건(+2): ratio/count 위반 구분, 0% 기준선 카테고리 차단.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 35902c8d7d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (actualMissing > baselineMissing) { | ||
| violations.push({ ...base, reason: 'count' }); | ||
| continue; | ||
| } | ||
| if (actual + 1e-9 < threshold) { |
There was a problem hiding this comment.
Track newly undocumented elements individually
Track the baseline set of missing elements, not only its aggregate count and ratio. For example, renaming one currently undocumented output field leaves both actualMissing and the coverage ratio unchanged, while documenting one old field and adding one undocumented field leaves the missing count unchanged and improves the ratio. Both changes therefore pass these conditions even though the newly introduced API element has no description, bypassing the gate's stated purpose.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
미반영(시점 유예): 지적한 우회 경로는 실재한다. 기존 미기재 1건을 설명하면서 새 미기재 1건을 추가하면 건수는 그대로, 비율은 오히려 올라 통과한다.
지금 element 단위 baseline(미기재 800여 개 목록 파일)을 넣지 않는 이유는 후속 PR과의 충돌 비용이다. 이 작업은 PR 6개로 SDL 설명을 순차 보강하는 중이고, 각 PR이 수백 건을 문서화한다. 목록 파일을 두면 매 PR이 같은 파일을 크게 바꿔 리베이스마다 충돌하고 리뷰 노이즈가 커진다.
대신 보강이 끝나는 시점에 이 구멍이 자동으로 닫힌다. 기준선 미기재 건수가 0에 수렴하면 count 조건이 곧 element 단위 강제가 된다(미기재 0건 기준선에서는 새 미기재 1건도 즉시 위반). 잔여가 남는 카테고리가 있으면 그때 명시 목록으로 고정하겠다.
| const SCALAR_TYPE_NAMES = new Set([ | ||
| 'String', | ||
| 'Int', | ||
| 'Float', | ||
| 'Boolean', | ||
| 'ID', | ||
| 'DateTime', | ||
| ]); |
There was a problem hiding this comment.
Derive custom scalar names from the SDL
Include scalar definitions discovered in the parsed SDL rather than relying on this fixed allowlist. If a supported custom scalar such as scalar URL is added and then used by an undocumented root argument like search(url: URL!), namedTypeOf(arg) is absent from this set and line 186 skips the argument completely, so docs:check passes without enforcing its description unless the contributor happens to update this unrelated list too.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
반영: 하드코딩 allowlist가 맞지 않다는 지적이 맞다. scalar URL을 추가하면 그 타입 인자가 통째로 집계에서 빠진다.
allowlist를 SDL에서 파생하는 대신 판정 자체를 뒤집었다. "스칼라 목록에 있으면 대상"이 아니라 "SDL에 선언된 input 객체 타입이 아니면 대상"으로 바꿨다. 커스텀 스칼라는 물론 enum 타입 인자도 자동으로 포함된다 — enum 인자도 설명을 적을 자리가 인자뿐이라 원래 대상이어야 했다.
이 변경으로 분모가 6 → 8로 늘어(enum 인자 2건 편입) 기준선도 다시 뽑았다.
There was a problem hiding this comment.
정정: 앞선 답글에서 "분모가 6 → 8로 늘어(enum 인자 2건 편입)"라고 적었는데 사실이 아니다. 확인해 보니 현재 스키마의 비-input 루트 인자는 ID 36건·String 6건뿐이고 enum 타입 인자는 없다. 기준선은 0/6 그대로다.
반영 내용 자체는 유효하다 — 판정을 "SDL에 선언된 input 객체가 아니면 대상"으로 뒤집어, 앞으로 커스텀 스칼라나 enum 타입 인자가 추가돼도 allowlist를 손대지 않고 자동 포함된다. 카테고리 라벨도 "루트 인자(스칼라·ID)"에서 "루트 인자(비 input)"로 바꿨다.
스칼라 타입명을 하드코딩한 allowlist가 확장에 취약했다. `scalar URL`을 추가하고 `search(url: URL!)`처럼 쓰면 그 인자가 집계에서 통째로 빠져, 설명 없이도 docs:check가 통과한다. 목록을 같이 고쳐야 한다는 걸 아는 사람만 게이트를 유지할 수 있는 구조였다. allowlist를 SDL에서 파생하는 대신 판정을 뒤집었다. "스칼라 목록에 있으면 대상"이 아니라 "SDL에 선언된 input 객체 타입이 아니면 대상"으로 본다. input 객체 인자는 설명을 input 타입 쪽에 두면 되지만 그 외에는 인자가 유일한 자리이므로, 커스텀 스칼라도 enum 타입 인자도 자동으로 포함된다. 집계는 파일별 개별 파싱에서 1패스(input 타입명 수집) → 2패스(집계) 구조로 바꿨다. input 타입이 다른 파일에 선언돼 있어도 인식해야 하기 때문이다. 현재 스키마의 비-input 루트 인자는 ID 36건·String 6건이라 기준선(0/6)은 그대로다. 카테고리 라벨만 "루트 인자(스칼라·ID)" → "루트 인자(비 input)"로 정정했다. 테스트 18건(+1): 커스텀 스칼라·enum 인자가 대상에 들어가는지.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2630344fdc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return; | ||
| } | ||
|
|
||
| if (def.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION) { |
There was a problem hiding this comment.
Count input fields declared in extensions
Handle INPUT_OBJECT_TYPE_EXTENSION here as well as definitions. A valid addition such as extend input ExistingInput { newOption: String } currently skips this branch entirely, so the new field is absent from both inputField.total and missing; consequently docs:check passes even when that newly introduced API element has no description.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
반영: extend input이 통째로 빠지는 게 맞다. 같은 구멍이 enum·interface 확장에도 있어서 함께 막았다.
- INPUT_OBJECT_TYPE_EXTENSION → inputField 집계 (선언 설명은 확장에 없으므로 inputType은 정의에서만)
- ENUM_TYPE_EXTENSION → enumValue 집계
- INTERFACE_TYPE_EXTENSION → outputField 집계
object 확장은 이미 처리하고 있었다(루트 타입은 rootField, 그 외는 outputField).
현재 레포는 extend type Query/Mutation만 쓰지만, 게이트가 "쓰지 않는 문법이라 괜찮다"에 기대면 안 되는 자리다.
`extend input ExistingInput { newOption: String }`처럼 확장으로 필드를 추가하면
어느 분기에도 걸리지 않아 total에도 missing에도 잡히지 않았다. 설명 없는 신규
요소가 게이트를 그대로 통과한다.
같은 구멍이 enum·interface 확장에도 있어 함께 막았다.
- INPUT_OBJECT_TYPE_EXTENSION → inputField
- ENUM_TYPE_EXTENSION → enumValue
- INTERFACE_TYPE_EXTENSION → outputField
- input 객체 이름 수집(루트 인자 판정용)도 확장을 포함
타입 선언 설명은 확장에 붙일 수 없으므로 inputType·enumType·outputType은 정의에서만
센다. object 확장은 이미 처리하고 있었다(루트 타입은 rootField, 그 외는 outputField).
현재 레포는 extend type Query/Mutation만 쓴다. 다만 게이트가 "지금 안 쓰는
문법이라 괜찮다"에 기대면 그 문법을 처음 쓰는 사람이 조용히 빠져나간다.
테스트 19건(+1): extend input/enum/type으로 추가한 요소가 집계에 잡히는지.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 21eb82e31b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if ( | ||
| def.kind === Kind.INTERFACE_TYPE_DEFINITION || | ||
| def.kind === Kind.INTERFACE_TYPE_EXTENSION | ||
| ) { |
There was a problem hiding this comment.
Count union declarations in output coverage
When a contributor adds an undocumented output union such as union SearchResult = Product | Store, collectDefinition has no UNION_TYPE_DEFINITION branch, so the declaration is omitted from every category and docs:check can pass without documenting it. Treat union definitions as output-type declarations so newly exposed union APIs affect the gate.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
반영. 다만 이 지적을 계기로 대응 방식을 바꿨다.
지금까지 지적받은 자리를 하나씩 막아 왔는데, 라운드마다 새 구멍이 나온 건 내가 게이트를 "현재 스키마에서 통과하는지"로만 검증했기 때문이다. 통과는 오탐이 없다는 뜻이지 막아야 할 걸 막는다는 증거가 아니다.
그래서 설명을 붙일 수 있는 SDL 자리를 전수로 나열하고 각 자리에 설명 없는 요소를 넣어 게이트가 잡는지 직접 확인했다. 18자리 중 4자리가 비어 있었다.
- union 선언 (이 지적)
- scalar 선언
- 비루트 object/interface 필드의 인자
- object 확장 필드의 인자
union·scalar는 필드가 없어 선언 설명이 유일한 문서이므로 outputType으로 집계한다. 필드 인자는 루트든 아니든 규칙이 같아서 recordFieldArgs로 합치고 카테고리 라벨도 "루트 인자" → "필드 인자"로 고쳤다.
이 전수 표를 spec에 it.each로 고정했다(18자리). 새 자리가 생기면 줄을 추가하게 된다 — 같은 유형의 지적이 반복되지 않게 하는 게 목적이다. 테스트 19 → 37건.
지금까지 지적을 하나씩 반응적으로 막아 왔는데, 라운드마다 새 구멍이 나온 건 게이트를 "현재 스키마에서 통과하는지"로만 검증했기 때문이다. 통과는 오탐이 없다는 뜻이지 막아야 할 것을 막는다는 증거가 아니다. 설명을 붙일 수 있는 SDL 자리를 전수로 나열하고 각 자리에 설명 없는 요소를 넣어 게이트가 실제로 잡는지 확인했다. 18자리 중 4자리가 비어 있었다. - union 선언 (Codex 지적) - scalar 선언 - 비루트 object/interface 필드의 인자 - object 확장 필드의 인자 union·scalar는 필드가 없어 선언 설명이 유일한 문서이므로 outputType으로 센다. 필드 인자는 루트든 아니든 "인자 외에 설명을 적을 자리가 없다"는 근거가 같아 recordFieldArgs로 합쳤고, 카테고리 라벨을 "루트 인자" → "필드 인자"로 고쳤다. 기준선은 scalar DateTime 편입으로 출력 type 선언만 66/133 → 67/134로 조정. 현재 스키마에 union·비루트 필드 인자는 없어 나머지는 그대로다. 검증: root.graphql에 설명 없는 union·scalar·필드 인자를 넣으면 각각 "미기재 70건 > 기준선 67건", "미기재 7건 > 기준선 6건"으로 걸린다. 전수 표를 spec에 it.each로 고정했다(18자리). 새 자리가 생기면 여기에 줄을 추가하게 되므로 같은 유형이 반복되지 않는다. 테스트 19 → 37건.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 71ccb398e3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if ( | ||
| def.kind === Kind.ENUM_TYPE_DEFINITION || | ||
| def.kind === Kind.ENUM_TYPE_EXTENSION |
There was a problem hiding this comment.
Include directive definitions in the coverage scan
When a contributor adds a valid custom directive such as directive @auth(reason: String) on FIELD_DEFINITION, the parser produces a DIRECTIVE_DEFINITION, but collectDefinition has no branch for it, so neither the directive nor its description-capable arguments enters any coverage total or missing list. Consequently yarn docs:check remains green for an undocumented SDL element despite the exhaustive-site test; count directive declarations and their arguments, or explicitly codify their exclusion.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
반영. 그리고 이 지적이 앞선 대응이 아직 부족했다는 걸 보여준다.
직전 커밋에서 "설명 가능한 자리를 전수로 나열했다"고 했지만 그 표는 내가 손으로 적은 목록이라 directive가 빠졌다. 손으로 적는 한 또 빠진다.
그래서 열거를 그만두고 구조를 바꿨다. definition kind를 처리 대상/의도적 제외로 명시하고, 둘 중 어디에도 없는 kind를 만나면 예외를 던진다. 새 SDL 구문이 들어오면 조용히 통과하는 게 아니라 docs:check가 그 자리에서 실패하고 "이 kind를 어떻게 셀지 정하라"고 알린다.
- DIRECTIVE_DEFINITION → 선언은 outputType, 인자는 필드 인자와 같은 규칙으로 집계
- SCHEMA_DEFINITION/EXTENSION은 명시적 제외(FE가 소비하는 API 요소가 아님)
- 나머지 미지의 kind → 예외
spec에 directive 자리와 "미처리 kind는 예외를 던진다" 케이스를 추가했다.
directive 선언과 그 인자가 어느 분기에도 걸리지 않아 집계에서 통째로 빠졌다. `directive @auth(reason: String) on FIELD_DEFINITION`을 추가해도 docs:check는 초록불이다. 문제는 이 누락 자체보다, 직전 커밋에서 "설명 가능한 자리를 전수로 나열했다"고 하면서 그 표를 손으로 적었다는 데 있다. 손으로 적는 한 또 빠진다 — 실제로 빠졌다. 그래서 열거를 그만두고 구조로 막는다. definition kind를 처리 대상과 의도적 제외(IGNORED_KINDS)로 나누고, 둘 중 어디에도 없는 kind를 만나면 예외를 던진다. 새 SDL 구문이 들어오면 조용히 통과하는 대신 docs:check가 그 자리에서 실패하며 "집계 대상이면 collectDefinition에, 아니면 IGNORED_KINDS에 근거와 함께 추가하라"고 알린다. - DIRECTIVE_DEFINITION → 선언은 outputType, 인자는 필드 인자와 같은 규칙 - SCHEMA_DEFINITION/EXTENSION, 실행 문서는 명시적 제외(FE가 소비하는 요소가 아님) - 그 외 미지의 kind → 예외 현재 스키마에 directive 선언이 없어 기준선은 변하지 않는다. 테스트 37 → 41건. 미지 kind 예외 케이스는 collectDefinition을 직접 호출해 실제 경로를 타게 했고, 가드를 지우면 그 테스트만 실패하는 것까지 확인했다.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: efe24c07b8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if ( | ||
| def.kind === Kind.UNION_TYPE_DEFINITION || | ||
| def.kind === Kind.SCALAR_TYPE_DEFINITION | ||
| ) { |
There was a problem hiding this comment.
Handle valid scalar and union extensions
When an SDL adds a valid extend scalar DateTime @specifiedBy(...) or extend union SearchResult = OtherResult, parsing produces SCALAR_TYPE_EXTENSION or UNION_TYPE_EXTENSION. This branch handles only their definition kinds, so both extensions fall through to the unhandled-kind exception and make yarn docs:check fail even though GraphQL extensions have no description slot to measure. Explicitly accept these kinds, likely as intentional exclusions.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
반영. 내가 넣은 가드가 만든 오탐이 맞다 — extend scalar / extend union은 설명 슬롯이 없는데 예외로 터진다.
두 kind를 IGNORED_KINDS에 넣되, 또 손으로 분류하면 같은 일이 반복되므로 분류 누락 자체를 테스트로 막았다. graphql 패키지의 Kind에서 이름이 DEFINITION/EXTENSION으로 끝나는 값을 전부 뽑아, 각 kind가 "집계됨" 또는 "의도적 제외" 중 하나로 분류돼 있는지 확인한다. 어느 쪽도 아니면 실패한다.
목록을 내가 적는 게 아니라 라이브러리에서 파생하므로, graphql 버전이 올라가며 새 kind가 생겨도 그 테스트가 먼저 잡는다. directive를 놓치고 이번에 extension을 놓친 게 둘 다 손으로 적은 목록 때문이었다.
직전 커밋의 미처리 kind 가드가 오탐을 냈다. `extend scalar DateTime @specifiedBy(...)` 같은 유효한 SDL이 SCALAR_TYPE_EXTENSION으로 파싱되는데 분류돼 있지 않아 예외로 터진다. 확장에는 설명 슬롯이 없으므로 집계 대상이 아니라 의도적 제외가 맞다. 문제는 이 누락 자체가 아니라 원인이 같다는 것이다. directive를 놓친 것도, 이번 확장 2종을 놓친 것도 전부 분류 목록을 손으로 적었기 때문이다. 그래서 목록을 graphql 패키지의 Kind에서 파생하는 테스트를 넣었다. 이름이 Definition/Extension으로 끝나는 kind를 전부 뽑아, 각각이 "집계됨" 또는 "의도적 제외" 중 하나로 분류돼 있는지 확인한다. 어느 쪽도 아니면 실패한다. 이 테스트가 곧바로 DirectiveExtension을 찾아냈다 — 리뷰에서도 내 목록에서도 빠져 있던 kind다. 결국 IGNORED_KINDS에 3종을 넣었다 (SCALAR_TYPE_EXTENSION · UNION_TYPE_EXTENSION · DIRECTIVE_EXTENSION). 처음 작성한 이 테스트는 Kind 값이 snake_case인 줄 알고 필터를 걸어 아무것도 검사하지 않는 공허한 통과 상태였다. 제외 목록에서 항목을 빼도 테스트가 실패하지 않는 걸 보고 발견했다. 지금은 3종을 빼면 정확히 그 3개를 짚어 실패한다.
이슈 #250. SDL 설명 커버리지를
yarn validate와 CIcheckjob에 편입한다.배경
SDL에 설명 없는 필드를 추가해도 CI가 그대로 통과했다. 그 결과 도메인별 편차가 크게 벌어져 있다 — seller-order 0% ↔ user-search 100%.
구성
scripts/sdl-description-coverage.tsscripts/check-sdl-description-coverage.ts--report(수치만) /--warning(종료코드 0)scripts/sdl-description-coverage.spec.tsjest.scripts.config.jspackage.json의 jest는rootDir이src라scripts/spec을 수집하지 못한다validate체인과pr-check.yml의checkjob에docs:check·test:scripts를 넣었다.check는 이미 필수 status check라terraform/main.tfruleset 변경은 불필요하다.단순 유무 집계를 쓰지 않은 이유
1. 플레이스홀더 판정 — seller SDL에
"""SellerOrderSummary 타입"""처럼 이름만 되풀이하는 설명이 70건 있다. 파서는description이 있으므로 "문서화됨"으로 세지만 정보량은 0이다. 이 판정을 넣자 실측치가 내려갔다:2. 자명 필드 제외 —
id·createdAt·*Id를 분모에서 뺀다. 전부 채우게 하면 "상품 ID" 같은 설명만 수백 개 늘어난다.order-checkout.graphql이 이미 비자명 필드(idempotencyKey·pickupAt)에만 근거를 적고productId·quantity는 비워 뒀는데, 단순 집계는 그 판단에 오히려 벌점을 준다. 제외는 분모에서 빼는 것일 뿐 작성을 막지 않는다.현재 수치 (임계치 = 실측치, 회귀만 차단)
후속 PR에서 커버리지를 올린 뒤 임계치도 함께 상향한다.
테스트
14건 — 플레이스홀더 판정 4 / 자명 필드 제외 2 / 요소별 집계 6 / 임계치 비교 2.
마지막 항목에 "실측치를 그대로 임계치로 박아도 부동소수 오차로 자기 자신에게 걸리지 않는지"를 포함했다 — 임계치를 달성치로 고정하는 운용이라 필요하다.