isthmus-cli 0.1.7 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +322 -0
- package/README.md +254 -162
- package/Skills/isthmus/SKILL.md +7 -0
- package/dist/cli/check-command.d.ts +5 -1
- package/dist/cli/check-command.js +170 -10
- package/dist/cli/check-command.js.map +1 -1
- package/dist/cli/main.js +19 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/retentions-command.d.ts +2 -3
- package/dist/cli/retentions-command.js.map +1 -1
- package/dist/exchange/parse.d.ts +20 -0
- package/dist/exchange/parse.js +51 -8
- package/dist/exchange/parse.js.map +1 -1
- package/dist/join/join.d.ts +5 -1
- package/dist/join/join.js +26 -10
- package/dist/join/join.js.map +1 -1
- package/dist/report/baseline.d.ts +61 -0
- package/dist/report/baseline.js +170 -0
- package/dist/report/baseline.js.map +1 -0
- package/dist/report/check-report.d.ts +15 -1
- package/dist/report/check-report.js +49 -13
- package/dist/report/check-report.js.map +1 -1
- package/dist/report/diff.js +4 -6
- package/dist/report/diff.js.map +1 -1
- package/dist/report/graph.d.ts +2 -1
- package/dist/report/graph.js +8 -12
- package/dist/report/graph.js.map +1 -1
- package/dist/report/retentions.d.ts +1 -0
- package/dist/report/retentions.js +18 -2
- package/dist/report/retentions.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.js","sourceRoot":"","sources":["../../src/report/baseline.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,oBAAoB,GAErB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAM/C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAwBpD,oCAAoC;AACpC,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE3C,qCAAqC;AACrC,MAAM,OAAO,6BAA8B,SAAQ,KAAK;IACtD,oCAAoC;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,+BAA+B,CAAC;IAC9C,CAAC;CACF;AAED,uDAAuD;AACvD,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC;QAAE,IAAI,CAAC,iCAAiC,CAAC,CAAC;IAClE,IAAI,KAAK,CAAC,MAAM,KAAK,kBAAkB,EAAE,CAAC;QACxC,IAAI,CAAC,qCAAqC,CAAC,CAAC;IAC9C,CAAC;IACD,6DAA6D;IAC7D,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;QACxB,IAAI,CAAC,2DAA2D,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;QAC1C,IAAI,CAAC,gCAAgC,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;QAAE,IAAI,CAAC,2BAA2B,CAAC,CAAC;IACrE,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,oBAAoB,EAAE,CAAC;QAChD,IAAI,CAAC,sBAAsB,oBAAoB,cAAc,CAAC,CAAC;IACjE,CAAC;IACD,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QACrD,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC3C,MAAM,GAAG,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC7B,OAAO;QACL,MAAM,EAAE,kBAAkB;QAC1B,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,OAAO;KACR,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAA6B,EAC7B,WAAmB;IAEnB,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAkB,KAAK,CAAC,MAAM,KAAK,SAAS;YACrD,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE;YACpE,CAAC,CAAC;gBACE,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,MAAM,EAAE,KAAK,CAAC,MAAM;aACrB,CAAC;QACN,MAAM,GAAG,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,oBAAoB,EAAE,CAAC;QAC1C,IAAI,CACF,+BAA+B,oBAAoB,eAAe;cAChE,yBAAyB,CAC5B,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC7B,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;AAC1E,CAAC;AAED,sCAAsC;AACtC,MAAM,UAAU,sBAAsB,CAAC,QAA0B;IAC/D,OAAO,gBAAgB,CAAC,QAAQ,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAmB,EACnB,OAAiC;IAEjC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACzC,SAAS,CAAC,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,UAAU,EAAE,IAAa,EAAE;QACzC,CAAC,CAAC,KAAK,CACV,CAAC;IACF,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAC9B,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,KAAK,IAAI,CACrC,CAAC,MAAM,CAAC;IACT,MAAM,oBAAoB,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,MAAM,CAChD,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAC7B,CAAC,MAAM,CAAC;IACT,OAAO;QACL,GAAG,MAAM;QACT,OAAO,EAAE;YACP,GAAG,MAAM,CAAC,OAAO;YACjB,MAAM,EAAE,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC;YAC1C,QAAQ,EAAE,iBAAiB,CAAC,MAAM,EAAE,SAAS,CAAC;YAC9C,UAAU;YACV,oBAAoB;SACrB;QACD,MAAM;KACP,CAAC;AACJ,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,gBAAgB,CAAC,KAKhC;IACC,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,KAAK,CAAC,IAAI;QACV,KAAK,CAAC,MAAM;QACZ,KAAK,CAAC,OAAO;QACb,KAAK,CAAC,MAAM,IAAI,IAAI;KACrB,CAAC,CAAC;AACL,CAAC;AAED,mCAAmC;AACnC,SAAS,iBAAiB,CACxB,MAA6B,EAC7B,QAA6B;IAE7B,OAAO,MAAM,CAAC,MAAM,CAClB,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,KAAK,IAAI,CACpE,CAAC,MAAM,CAAC;AACX,CAAC;AAED,wCAAwC;AACxC,SAAS,cAAc,CAAC,KAAc,EAAE,KAAa;IACnD,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,CAAC,2BAA2B,KAAK,yBAAyB,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACzE,IAAI,CAAC,iDAAiD,KAAK,GAAG,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QAClC,IAAI,CAAC,wDAAwD,KAAK,GAAG,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,8CAA8C,KAAK,GAAG,CAAC,CAAC;IAC/D,CAAC;IACD,IACE,KAAK,CAAC,MAAM,KAAK,SAAS;QAC1B,CAAC,oBAAoB,CAAC,KAAK,CAAC,MAAM,CAAC,EACnC,CAAC;QACD,IAAI,CAAC,6CAA6C,KAAK,GAAG,CAAC,CAAC;IAC9D,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAsB,CAAC;IAC1C,OAAO,KAAK,CAAC,MAAM,KAAK,SAAS;QAC/B,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE;QACxD,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;AACnF,CAAC;AAED,+CAA+C;AAC/C,SAAS,cAAc,CAAC,IAAmB,EAAE,KAAoB;IAC/D,OAAO,CACL,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC;QACrC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC;QACzC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC;QAC3C,cAAc,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CACtD,CAAC;AACJ,CAAC;AAED,gCAAgC;AAChC,SAAS,IAAI,CAAC,OAAe;IAC3B,MAAM,IAAI,6BAA6B,CAAC,OAAO,CAAC,CAAC;AACnD,CAAC;AAED,2DAA2D;AAE3D,yBAAyB;AACzB,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS,eAAe,CAAC,CAAC","sourcesContent":["import {\n isBridgeTarget,\n isBridgeTimestamp,\n isJsonObject,\n isSafeNonEmptyString,\n type BridgeTarget,\n} from '../exchange/parse.ts';\nimport { compareStrings } from '../compare.ts';\nimport type {\n CheckIssue,\n CheckIssueCode,\n CheckReport,\n} from './check-report.ts';\nimport { checkIssueCodes } from './check-report.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/**\n * 베이스라인이 억제하는 논리 이슈 하나의 식별자다.\n *\n * 위치가 아니라 논리 키(code+target+channel+method)다. 소스 줄 이동이나\n * 증거 위치 변화는 억제를 깨지 않고, 새 채널·메서드 불일치는 새 키라\n * 억제되지 않는다. diff가 논리 연결을 비교하는 키와 같은 철학이다.\n */\nexport interface BaselineEntry {\n readonly code: CheckIssueCode;\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method?: string;\n}\n\n/** check 실행 사이에서 인정된 이슈를 보존하는 isthmus 소유 문서다. */\nexport interface BaselineDocument {\n readonly format: 'isthmus-baseline';\n readonly version: 1;\n readonly generatedAt: string;\n readonly entries: readonly BaselineEntry[];\n}\n\n/** 한 베이스라인 문서가 담을 수 있는 최대 항목 수다. */\nexport const MAX_BASELINE_ENTRIES = 10_000;\n\n/** 외부 베이스라인 문서가 v1 계약을 어겼음을 나타낸다. */\nexport class BridgeBaselineValidationError extends Error {\n /** 입력 내용을 노출하지 않는 안전한 메시지를 보존한다. */\n constructor(message: string) {\n super(message);\n this.name = 'BridgeBaselineValidationError';\n }\n}\n\n/** 신뢰하지 않는 JSON 값을 검증된 isthmus-baseline v1 문서로 바꾼다. */\nexport function parseBaselineDocument(input: unknown): BaselineDocument {\n if (!isJsonObject(input)) fail('Baseline must be a JSON object.');\n if (input.format !== 'isthmus-baseline') {\n fail('Expected format \"isthmus-baseline\".');\n }\n // 숫자 비교라 `1.0`도 통과한다. bridge-facts의 version 검증과 같은 의도적 관용이다.\n if (input.version !== 1) {\n fail('Unsupported isthmus-baseline version; expected version 1.');\n }\n if (!isBridgeTimestamp(input.generatedAt)) {\n fail('Invalid generatedAt timestamp.');\n }\n if (!Array.isArray(input.entries)) fail('Entries must be an array.');\n if (input.entries.length > MAX_BASELINE_ENTRIES) {\n fail(`Entries exceed the ${MAX_BASELINE_ENTRIES} item limit.`);\n }\n const entries: BaselineEntry[] = [];\n const seen = new Set<string>();\n for (const [index, value] of input.entries.entries()) {\n const entry = normalizeEntry(value, index);\n const key = baselineEntryKey(entry);\n if (seen.has(key)) continue;\n seen.add(key);\n entries.push(entry);\n }\n entries.sort(compareEntries);\n return {\n format: 'isthmus-baseline',\n version: 1,\n generatedAt: input.generatedAt,\n entries,\n };\n}\n\n/**\n * 현재 보고서의 이슈 전체를 새 베이스라인 문서로 만든다.\n *\n * 읽기 상한과 같은 상한을 쓰기에도 적용한다. 상한 없이 쓰면 도구가\n * 스스로 소비할 수 없는 산출물을 만들고, 그 파일은 다음 `--baseline`\n * 실행에서 거부된다.\n */\nexport function createBaselineDocument(\n issues: readonly CheckIssue[],\n generatedAt: string,\n): BaselineDocument {\n const entries: BaselineEntry[] = [];\n const seen = new Set<string>();\n for (const issue of issues) {\n const entry: BaselineEntry = issue.method === undefined\n ? { code: issue.code, target: issue.target, channel: issue.channel }\n : {\n code: issue.code,\n target: issue.target,\n channel: issue.channel,\n method: issue.method,\n };\n const key = baselineEntryKey(entry);\n if (seen.has(key)) continue;\n seen.add(key);\n entries.push(entry);\n }\n if (entries.length > MAX_BASELINE_ENTRIES) {\n fail(\n `Baseline entries exceed the ${MAX_BASELINE_ENTRIES} item limit; `\n + 'narrow the join inputs.',\n );\n }\n entries.sort(compareEntries);\n return { format: 'isthmus-baseline', version: 1, generatedAt, entries };\n}\n\n/** 베이스라인 문서를 결정적인 JSON 문자열로 인코딩한다. */\nexport function encodeBaselineDocument(document: BaselineDocument): string {\n return encodeSortedJson(document);\n}\n\n/**\n * 베이스라인 항목과 맞는 이슈를 표시한 보고서와 남은 항목 수를 만든다.\n *\n * 억제된 이슈는 목록에서 지우지 않는다. 사실·증거·심각도는 보존되고\n * 요약의 error·warning과 `--strict` 판단에서만 빠진다. 어떤 항목도 맞지\n * 않는 이슈를 만들지 않으며, 맞지 않는 항목은 오래된 것으로 센다 —\n * 해결된 이슈가 베이스라인에 남으면 다음 악화를 가릴 수 있기 때문이다.\n *\n * 판정은 전부 논리 키 교집합이라 이미 억제 표시가 있는 보고서에 다시\n * 적용해도 결과가 같다(멱등). suppressed 표시가 아닌 키로 세어야 stale과\n * suppressed 계수가 이중 적용에 흔들리지 않는다.\n */\nexport function applyBaseline(\n report: CheckReport,\n entries: readonly BaselineEntry[],\n): CheckReport {\n const entryKeys = new Set(entries.map(baselineEntryKey));\n const issueKeys = new Set(report.issues.map(baselineEntryKey));\n const issues = report.issues.map((issue) =>\n entryKeys.has(baselineEntryKey(issue))\n ? { ...issue, suppressed: true as const }\n : issue,\n );\n const suppressed = issues.filter(\n (issue) => issue.suppressed === true,\n ).length;\n const staleBaselineEntries = [...entryKeys].filter(\n (key) => !issueKeys.has(key),\n ).length;\n return {\n ...report,\n summary: {\n ...report.summary,\n errors: countUnsuppressed(issues, 'error'),\n warnings: countUnsuppressed(issues, 'warning'),\n suppressed,\n staleBaselineEntries,\n },\n issues,\n };\n}\n\n/** 논리 이슈 식별자를 충돌 없는 JSON 문자열 키로 만든다. */\nexport function baselineEntryKey(issue: {\n readonly code: string;\n readonly target: string;\n readonly channel: string;\n readonly method?: string;\n}): string {\n return JSON.stringify([\n issue.code,\n issue.target,\n issue.channel,\n issue.method ?? null,\n ]);\n}\n\n/** 억제되지 않은 이슈 중 주어진 심각도의 수를 센다. */\nfunction countUnsuppressed(\n issues: readonly CheckIssue[],\n severity: 'error' | 'warning',\n): number {\n return issues.filter(\n (issue) => issue.severity === severity && issue.suppressed !== true,\n ).length;\n}\n\n/** 항목 하나의 종류·키 필드를 검증하고 계약 필드만 복사한다. */\nfunction normalizeEntry(value: unknown, index: number): BaselineEntry {\n if (!isJsonObject(value)) {\n fail(`Baseline entry at index ${index} must be a JSON object.`);\n }\n if (typeof value.code !== 'string' || !checkIssueCodeSet.has(value.code)) {\n fail(`Unknown issue code in baseline entry at index ${index}.`);\n }\n if (!isBridgeTarget(value.target)) {\n fail(`Unsupported bridge target in baseline entry at index ${index}.`);\n }\n if (!isSafeNonEmptyString(value.channel)) {\n fail(`Invalid channel in baseline entry at index ${index}.`);\n }\n if (\n value.method !== undefined &&\n !isSafeNonEmptyString(value.method)\n ) {\n fail(`Invalid method in baseline entry at index ${index}.`);\n }\n const code = value.code as CheckIssueCode;\n return value.method === undefined\n ? { code, target: value.target, channel: value.channel }\n : { code, target: value.target, channel: value.channel, method: value.method };\n}\n\n/** 항목을 code·target·channel·method 순으로 고정한다. */\nfunction compareEntries(left: BaselineEntry, right: BaselineEntry): number {\n return (\n compareStrings(left.code, right.code) ||\n compareStrings(left.target, right.target) ||\n compareStrings(left.channel, right.channel) ||\n compareStrings(left.method ?? '', right.method ?? '')\n );\n}\n\n/** 입력 값을 포함하지 않는 검증 오류를 던진다. */\nfunction fail(message: string): never {\n throw new BridgeBaselineValidationError(message);\n}\n\n/** 배열과 null을 제외한 JSON 객체 확인은 exchange/parse의 검증을 재사용한다. */\n\n/** 검증에 쓰는 진단 코드 집합이다. */\nconst checkIssueCodeSet = new Set<string>(checkIssueCodes);\n"]}
|
|
@@ -6,6 +6,10 @@ export interface CheckSummary {
|
|
|
6
6
|
readonly warnings: number;
|
|
7
7
|
readonly matchedChannels: number;
|
|
8
8
|
readonly matchedMethods: number;
|
|
9
|
+
/** 베이스라인이 적용된 실행에서만 실리는 억제된 이슈 수다. */
|
|
10
|
+
readonly suppressed?: number;
|
|
11
|
+
/** 베이스라인이 적용된 실행에서만 실리는, 현재 이슈와 맞지 않는 항목 수다. */
|
|
12
|
+
readonly staleBaselineEntries?: number;
|
|
9
13
|
}
|
|
10
14
|
/**
|
|
11
15
|
* check가 보고하는 안정적인 진단 종류다.
|
|
@@ -13,7 +17,9 @@ export interface CheckSummary {
|
|
|
13
17
|
* `-unverified` 종류는 수신 측이 스스로 분석 공백을 신고해, 핸들러가 없는 것인지
|
|
14
18
|
* 보지 못한 것인지 구분할 수 없는 경우다. 사실과 증거는 같지만 판정이 아니다.
|
|
15
19
|
*/
|
|
16
|
-
export
|
|
20
|
+
export declare const checkIssueCodes: readonly ["unhandled-invocation", "unhandled-invocation-unverified", "unregistered-channel-creation", "unregistered-channel-creation-unverified", "registration-without-creation", "handler-without-invocation"];
|
|
21
|
+
/** check가 보고하는 안정적인 진단 종류다. */
|
|
22
|
+
export type CheckIssueCode = (typeof checkIssueCodes)[number];
|
|
17
23
|
/** 삭제 판정 없이 경계 불일치 사실과 증거만 전달한다. */
|
|
18
24
|
export interface CheckIssue {
|
|
19
25
|
readonly severity: 'error' | 'warning';
|
|
@@ -22,6 +28,14 @@ export interface CheckIssue {
|
|
|
22
28
|
readonly channel: string;
|
|
23
29
|
readonly method?: string;
|
|
24
30
|
readonly evidence: readonly BridgeEndpoint[];
|
|
31
|
+
/**
|
|
32
|
+
* 베이스라인이 이 이슈를 인정된 상태로 억제했다는 표시다.
|
|
33
|
+
*
|
|
34
|
+
* 사실·증거·심각도는 그대로 보존하고, 요약의 error·warning 계산과
|
|
35
|
+
* `--strict`에서만 빼는다. 억제 자체를 지우면 베이스라인이 무엇을
|
|
36
|
+
* 삼켰는지 보고서에서 사라진다.
|
|
37
|
+
*/
|
|
38
|
+
readonly suppressed?: true;
|
|
25
39
|
}
|
|
26
40
|
/** 에이전트와 CI가 소비할 check 문서다. */
|
|
27
41
|
export interface CheckReport {
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { isReceiverPlatform } from "../exchange/parse.js";
|
|
2
2
|
import { isBridgeJoinDeferred } from "../join/join.js";
|
|
3
3
|
import { encodeSortedJson } from "./sorted-json.js";
|
|
4
|
+
/**
|
|
5
|
+
* check가 보고하는 안정적인 진단 종류다.
|
|
6
|
+
*
|
|
7
|
+
* `-unverified` 종류는 수신 측이 스스로 분석 공백을 신고해, 핸들러가 없는 것인지
|
|
8
|
+
* 보지 못한 것인지 구분할 수 없는 경우다. 사실과 증거는 같지만 판정이 아니다.
|
|
9
|
+
*/
|
|
10
|
+
export const checkIssueCodes = [
|
|
11
|
+
'unhandled-invocation',
|
|
12
|
+
'unhandled-invocation-unverified',
|
|
13
|
+
'unregistered-channel-creation',
|
|
14
|
+
'unregistered-channel-creation-unverified',
|
|
15
|
+
'registration-without-creation',
|
|
16
|
+
'handler-without-invocation',
|
|
17
|
+
];
|
|
4
18
|
/** check 문서를 결정적인 JSON 문자열로 인코딩한다. */
|
|
5
19
|
export function encodeCheckReport(report) {
|
|
6
20
|
return encodeSortedJson(report);
|
|
@@ -13,8 +27,8 @@ export function createCheckReport(joined) {
|
|
|
13
27
|
const gaps = receiverCoverageGaps(joined.limitations);
|
|
14
28
|
const issues = [
|
|
15
29
|
...joined.unhandledInvocations.map((item) => ({
|
|
16
|
-
severity: gaps.hidesHandlers(item.target) ? 'warning' : 'error',
|
|
17
|
-
code: gaps.hidesHandlers(item.target)
|
|
30
|
+
severity: gaps.hidesHandlers(item.target, item.channel) ? 'warning' : 'error',
|
|
31
|
+
code: gaps.hidesHandlers(item.target, item.channel)
|
|
18
32
|
? 'unhandled-invocation-unverified'
|
|
19
33
|
: 'unhandled-invocation',
|
|
20
34
|
target: item.target,
|
|
@@ -23,8 +37,8 @@ export function createCheckReport(joined) {
|
|
|
23
37
|
evidence: item.invocations,
|
|
24
38
|
})),
|
|
25
39
|
...joined.unregisteredChannelCreations.map((item) => ({
|
|
26
|
-
severity: gaps.hidesRegistrations(item.target) ? 'warning' : 'error',
|
|
27
|
-
code: gaps.hidesRegistrations(item.target)
|
|
40
|
+
severity: gaps.hidesRegistrations(item.target, item.channel) ? 'warning' : 'error',
|
|
41
|
+
code: gaps.hidesRegistrations(item.target, item.channel)
|
|
28
42
|
? 'unregistered-channel-creation-unverified'
|
|
29
43
|
: 'unregistered-channel-creation',
|
|
30
44
|
target: item.target,
|
|
@@ -89,21 +103,43 @@ function receiverCoverageGaps(limitations) {
|
|
|
89
103
|
const existing = memoized.get(target);
|
|
90
104
|
if (existing !== undefined)
|
|
91
105
|
return existing;
|
|
92
|
-
const relevant = receiverLimitations.filter(({ target: gapTarget }) => gapTarget === null || gapTarget === target);
|
|
93
106
|
const gaps = {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
startsWithAny(isthmusHandlerGapPrefixes)(message))),
|
|
97
|
-
registrations: relevant.some(({ tool, message }) => startsWithAny(sourceCoverageGapPrefixes)(message) ||
|
|
98
|
-
(tool === 'isthmus' &&
|
|
99
|
-
startsWithAny(isthmusRegistrationGapPrefixes)(message))),
|
|
107
|
+
allHandlers: false, allRegistrations: false,
|
|
108
|
+
handlerChannels: new Set(), registrationChannels: new Set(),
|
|
100
109
|
};
|
|
110
|
+
for (const { target: gapTarget, tool, message, channels, origin } of receiverLimitations) {
|
|
111
|
+
if (gapTarget !== null && gapTarget !== target)
|
|
112
|
+
continue;
|
|
113
|
+
const handlers = startsWithAny(producerHandlerGapPrefixes)(message) ||
|
|
114
|
+
(origin === 'consumer' && tool === 'isthmus' && startsWithAny(isthmusHandlerGapPrefixes)(message));
|
|
115
|
+
const registrations = startsWithAny(sourceCoverageGapPrefixes)(message) ||
|
|
116
|
+
(origin === 'consumer' && tool === 'isthmus' && startsWithAny(isthmusRegistrationGapPrefixes)(message));
|
|
117
|
+
// 하나라도 범위가 불명확한 공백이 있으면 같은 target의 좁은 범위로 덮지 않는다.
|
|
118
|
+
if (channels === undefined) {
|
|
119
|
+
gaps.allHandlers ||= handlers;
|
|
120
|
+
gaps.allRegistrations ||= registrations;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
for (const channel of channels) {
|
|
124
|
+
if (handlers)
|
|
125
|
+
gaps.handlerChannels.add(channel);
|
|
126
|
+
if (registrations)
|
|
127
|
+
gaps.registrationChannels.add(channel);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
101
131
|
memoized.set(target, gaps);
|
|
102
132
|
return gaps;
|
|
103
133
|
};
|
|
104
134
|
return {
|
|
105
|
-
hidesHandlers: (target) =>
|
|
106
|
-
|
|
135
|
+
hidesHandlers: (target, channel) => {
|
|
136
|
+
const gaps = gapsFor(target);
|
|
137
|
+
return gaps.allHandlers || gaps.handlerChannels.has(channel);
|
|
138
|
+
},
|
|
139
|
+
hidesRegistrations: (target, channel) => {
|
|
140
|
+
const gaps = gapsFor(target);
|
|
141
|
+
return gaps.allRegistrations || gaps.registrationChannels.has(channel);
|
|
142
|
+
},
|
|
107
143
|
};
|
|
108
144
|
}
|
|
109
145
|
/** 주어진 접두사 중 하나로 시작하는지 검사하는 술어를 만든다. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-report.js","sourceRoot":"","sources":["../../src/report/check-report.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BridgeTarget } from '../exchange/parse.ts';\nimport { isReceiverPlatform } from '../exchange/parse.ts';\nimport type {\n BridgeEndpoint,\n BridgeJoinResult,\n JoinLimitation,\n} from '../join/join.ts';\nimport { isBridgeJoinDeferred } from '../join/join.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** check 결과 개수를 빠르게 판단할 요약이다. */\nexport interface CheckSummary {\n readonly errors: number;\n readonly warnings: number;\n readonly matchedChannels: number;\n readonly matchedMethods: number;\n}\n\n/**\n * check가 보고하는 안정적인 진단 종류다.\n *\n * `-unverified` 종류는 수신 측이 스스로 분석 공백을 신고해, 핸들러가 없는 것인지\n * 보지 못한 것인지 구분할 수 없는 경우다. 사실과 증거는 같지만 판정이 아니다.\n */\nexport type CheckIssueCode =\n | 'unhandled-invocation'\n | 'unhandled-invocation-unverified'\n | 'unregistered-channel-creation'\n | 'unregistered-channel-creation-unverified'\n | 'registration-without-creation'\n | 'handler-without-invocation';\n\n/** 삭제 판정 없이 경계 불일치 사실과 증거만 전달한다. */\nexport interface CheckIssue {\n readonly severity: 'error' | 'warning';\n readonly code: CheckIssueCode;\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method?: string;\n readonly evidence: readonly BridgeEndpoint[];\n}\n\n/** 에이전트와 CI가 소비할 check 문서다. */\nexport interface CheckReport {\n readonly format: 'isthmus-check';\n readonly version: 1;\n readonly summary: CheckSummary;\n readonly issues: readonly CheckIssue[];\n readonly limitations: readonly JoinLimitation[];\n}\n\n/** check 문서를 결정적인 JSON 문자열로 인코딩한다. */\nexport function encodeCheckReport(report: CheckReport): string {\n return encodeSortedJson(report);\n}\n\n/** 조인 결과를 정책 심각도가 포함된 check 문서로 바꾼다. */\nexport function createCheckReport(joined: BridgeJoinResult): CheckReport {\n if (isBridgeJoinDeferred(joined)) {\n throw new Error('Cannot create a check report from a deferred bridge join.');\n }\n const gaps = receiverCoverageGaps(joined.limitations);\n const issues: CheckIssue[] = [\n ...joined.unhandledInvocations.map<CheckIssue>((item) => ({\n severity: gaps.hidesHandlers(item.target) ? 'warning' : 'error',\n code: gaps.hidesHandlers(item.target)\n ? 'unhandled-invocation-unverified'\n : 'unhandled-invocation',\n target: item.target,\n channel: item.channel,\n method: item.method,\n evidence: item.invocations,\n })),\n ...joined.unregisteredChannelCreations.map<CheckIssue>((item) => ({\n severity: gaps.hidesRegistrations(item.target) ? 'warning' : 'error',\n code: gaps.hidesRegistrations(item.target)\n ? 'unregistered-channel-creation-unverified'\n : 'unregistered-channel-creation',\n target: item.target,\n channel: item.channel,\n evidence: item.creations,\n })),\n ...joined.registrationsWithoutCreations.map<CheckIssue>((item) => ({\n severity: 'warning',\n code: 'registration-without-creation',\n target: item.target,\n channel: item.channel,\n evidence: item.registrations,\n })),\n ...joined.handlersWithoutInvocations.map<CheckIssue>((item) => ({\n severity: 'warning',\n code: 'handler-without-invocation',\n target: item.target,\n channel: item.channel,\n method: item.method,\n evidence: item.handlers,\n })),\n ];\n return {\n format: 'isthmus-check',\n version: 1,\n summary: {\n errors: issues.filter(({ severity }) => severity === 'error').length,\n warnings: issues.filter(({ severity }) => severity === 'warning').length,\n matchedChannels: joined.matchedChannels.length,\n matchedMethods: joined.matchedMethods.length,\n },\n issues,\n limitations: joined.limitations,\n };\n}\n\n/** 수신 측이 스스로 알린 분석 공백이 target별로 무엇을 가리는지 나타낸다. */\ninterface ReceiverCoverageGaps {\n hidesHandlers(target: BridgeTarget): boolean;\n hidesRegistrations(target: BridgeTarget): boolean;\n}\n\n/**\n * 수신 측이 핸들러나 등록을 놓쳤을 수 있다고 스스로 알렸는지 target별로 확인한다.\n *\n * 이때 \"핸들러 없는 호출\"은 경계 불일치가 아니라 판정 불가다. Objective-C로 쓰인\n * Flutter 핸들러처럼 수신 측 분석에 아예 나타나지 않는 코드가 실제로 있어서,\n * error로 단정하면 이 도구가 없애려던 오탐을 이 도구가 만든다.\n *\n * 공백의 종류는 구분한다. 이름이 리터럴이 아닌 채널 등록 하나가 무관한 메서드\n * 진단까지 무르게 하면 안 된다. 호출 측 한계는 네이티브 코드를 가리지 않으므로\n * 수신 측 플랫폼의 한계만 본다.\n *\n * 완화 단위는 진단의 target이다. 사실은 target별로만 조인되므로, 다른 target의\n * 수신 문서가 신고한 공백은 현재 target의 핸들러를 가릴 수 없다. target이 없는\n * (사실이 없는) 수신 문서는 어느 target을 분석했는지 특정할 수 없어 모든 target에\n * 적용한다. 같은 target에 귀속된 수신 문서가 사실과 함께 존재해도 마찬가지다.\n * 수신 문서 여러 개가 소스 트리를 나누어 가졌을 수 있으므로, 귀속 없는 문서가\n * 본 소스가 해당 target의 핸들러를 가릴 가능성을 배제할 수 없기 때문이다.\n *\n * `unjoined-` 접두사는 isthmus가 직접 세어 자신을 출처로 밝힌 한계만 인정한다.\n * 생산자 문자열은 신뢰의 근거가 아니므로, 같은 접두사를 차용한 생산자 신고는\n * 완화 근거가 되지 못한다.\n */\nfunction receiverCoverageGaps(\n limitations: readonly JoinLimitation[],\n): ReceiverCoverageGaps {\n const receiverLimitations = limitations.filter(({ platform }) =>\n isReceiverPlatform(platform),\n );\n const memoized = new Map<\n BridgeTarget,\n { handlers: boolean; registrations: boolean }\n >();\n const gapsFor = (target: BridgeTarget) => {\n const existing = memoized.get(target);\n if (existing !== undefined) return existing;\n const relevant = receiverLimitations.filter(\n ({ target: gapTarget }) => gapTarget === null || gapTarget === target,\n );\n const gaps = {\n handlers: relevant.some(\n ({ tool, message }) =>\n startsWithAny(producerHandlerGapPrefixes)(message) ||\n (tool === 'isthmus' &&\n startsWithAny(isthmusHandlerGapPrefixes)(message)),\n ),\n registrations: relevant.some(\n ({ tool, message }) =>\n startsWithAny(sourceCoverageGapPrefixes)(message) ||\n (tool === 'isthmus' &&\n startsWithAny(isthmusRegistrationGapPrefixes)(message)),\n ),\n };\n memoized.set(target, gaps);\n return gaps;\n };\n return {\n hidesHandlers: (target) => gapsFor(target).handlers,\n hidesRegistrations: (target) => gapsFor(target).registrations,\n };\n}\n\n/** 주어진 접두사 중 하나로 시작하는지 검사하는 술어를 만든다. */\nfunction startsWithAny(\n prefixes: readonly string[],\n): (message: string) => boolean {\n return (message) => prefixes.some((prefix) => message.startsWith(prefix));\n}\n\n/**\n * 수신 측 소스 자체가 분석되지 않아 등록과 핸들러를 모두 가리는 한계다.\n *\n * 알려진 접두사만 인정한다. 모르는 한계를 공백으로 넓게 해석하면 진짜 불일치가\n * 경고로 묻힌다.\n */\nconst sourceCoverageGapPrefixes = [\n 'objective-c-sources:',\n 'shadowed-flutter-method-channel:',\n];\n\n/** 생산자가 신고하는, 핸들러 본문만 가리는 한계다. */\nconst handlerBodyCoverageGapPrefixes = ['opaque-handler-bodies:'];\n\n/** 생산자 신고 중 핸들러를 가릴 수 있는 한계 전체다. */\nconst producerHandlerGapPrefixes = [\n ...sourceCoverageGapPrefixes,\n ...handlerBodyCoverageGapPrefixes,\n];\n\n/**\n * isthmus가 직접 센 한계 중 핸들러를 가리는 접두사다.\n *\n * 생산자의 신고 개수에 의존하지 않으므로 `tool`이 `isthmus`인 항목만 인정한다.\n */\nconst isthmusHandlerGapPrefixes = [\n 'unjoined-dynamic-methods:',\n 'unjoined-unattributed-handlers:',\n];\n\n/** isthmus가 직접 센 한계 중 채널 등록을 가리는 접두사다. */\nconst isthmusRegistrationGapPrefixes = ['unjoined-dynamic-channels:'];\n"]}
|
|
1
|
+
{"version":3,"file":"check-report.js","sourceRoot":"","sources":["../../src/report/check-report.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BridgeTarget } from '../exchange/parse.ts';\nimport { isReceiverPlatform } from '../exchange/parse.ts';\nimport type {\n BridgeEndpoint,\n BridgeJoinResult,\n JoinLimitation,\n} from '../join/join.ts';\nimport { isBridgeJoinDeferred } from '../join/join.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** check 결과 개수를 빠르게 판단할 요약이다. */\nexport interface CheckSummary {\n readonly errors: number;\n readonly warnings: number;\n readonly matchedChannels: number;\n readonly matchedMethods: number;\n /** 베이스라인이 적용된 실행에서만 실리는 억제된 이슈 수다. */\n readonly suppressed?: number;\n /** 베이스라인이 적용된 실행에서만 실리는, 현재 이슈와 맞지 않는 항목 수다. */\n readonly staleBaselineEntries?: number;\n}\n\n/**\n * check가 보고하는 안정적인 진단 종류다.\n *\n * `-unverified` 종류는 수신 측이 스스로 분석 공백을 신고해, 핸들러가 없는 것인지\n * 보지 못한 것인지 구분할 수 없는 경우다. 사실과 증거는 같지만 판정이 아니다.\n */\nexport const checkIssueCodes = [\n 'unhandled-invocation',\n 'unhandled-invocation-unverified',\n 'unregistered-channel-creation',\n 'unregistered-channel-creation-unverified',\n 'registration-without-creation',\n 'handler-without-invocation',\n] as const;\n\n/** check가 보고하는 안정적인 진단 종류다. */\nexport type CheckIssueCode = (typeof checkIssueCodes)[number];\n\n/** 삭제 판정 없이 경계 불일치 사실과 증거만 전달한다. */\nexport interface CheckIssue {\n readonly severity: 'error' | 'warning';\n readonly code: CheckIssueCode;\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method?: string;\n readonly evidence: readonly BridgeEndpoint[];\n /**\n * 베이스라인이 이 이슈를 인정된 상태로 억제했다는 표시다.\n *\n * 사실·증거·심각도는 그대로 보존하고, 요약의 error·warning 계산과\n * `--strict`에서만 빼는다. 억제 자체를 지우면 베이스라인이 무엇을\n * 삼켰는지 보고서에서 사라진다.\n */\n readonly suppressed?: true;\n}\n\n/** 에이전트와 CI가 소비할 check 문서다. */\nexport interface CheckReport {\n readonly format: 'isthmus-check';\n readonly version: 1;\n readonly summary: CheckSummary;\n readonly issues: readonly CheckIssue[];\n readonly limitations: readonly JoinLimitation[];\n}\n\n/** check 문서를 결정적인 JSON 문자열로 인코딩한다. */\nexport function encodeCheckReport(report: CheckReport): string {\n return encodeSortedJson(report);\n}\n\n/** 조인 결과를 정책 심각도가 포함된 check 문서로 바꾼다. */\nexport function createCheckReport(joined: BridgeJoinResult): CheckReport {\n if (isBridgeJoinDeferred(joined)) {\n throw new Error('Cannot create a check report from a deferred bridge join.');\n }\n const gaps = receiverCoverageGaps(joined.limitations);\n const issues: CheckIssue[] = [\n ...joined.unhandledInvocations.map<CheckIssue>((item) => ({\n severity: gaps.hidesHandlers(item.target, item.channel) ? 'warning' : 'error',\n code: gaps.hidesHandlers(item.target, item.channel)\n ? 'unhandled-invocation-unverified'\n : 'unhandled-invocation',\n target: item.target,\n channel: item.channel,\n method: item.method,\n evidence: item.invocations,\n })),\n ...joined.unregisteredChannelCreations.map<CheckIssue>((item) => ({\n severity: gaps.hidesRegistrations(item.target, item.channel) ? 'warning' : 'error',\n code: gaps.hidesRegistrations(item.target, item.channel)\n ? 'unregistered-channel-creation-unverified'\n : 'unregistered-channel-creation',\n target: item.target,\n channel: item.channel,\n evidence: item.creations,\n })),\n ...joined.registrationsWithoutCreations.map<CheckIssue>((item) => ({\n severity: 'warning',\n code: 'registration-without-creation',\n target: item.target,\n channel: item.channel,\n evidence: item.registrations,\n })),\n ...joined.handlersWithoutInvocations.map<CheckIssue>((item) => ({\n severity: 'warning',\n code: 'handler-without-invocation',\n target: item.target,\n channel: item.channel,\n method: item.method,\n evidence: item.handlers,\n })),\n ];\n return {\n format: 'isthmus-check',\n version: 1,\n summary: {\n errors: issues.filter(({ severity }) => severity === 'error').length,\n warnings: issues.filter(({ severity }) => severity === 'warning').length,\n matchedChannels: joined.matchedChannels.length,\n matchedMethods: joined.matchedMethods.length,\n },\n issues,\n limitations: joined.limitations,\n };\n}\n\n/** 수신 측이 스스로 알린 분석 공백이 target별로 무엇을 가리는지 나타낸다. */\ninterface ReceiverCoverageGaps {\n hidesHandlers(target: BridgeTarget, channel: string): boolean;\n hidesRegistrations(target: BridgeTarget, channel: string): boolean;\n}\n\n/**\n * 수신 측이 핸들러나 등록을 놓쳤을 수 있다고 스스로 알렸는지 target별로 확인한다.\n *\n * 이때 \"핸들러 없는 호출\"은 경계 불일치가 아니라 판정 불가다. Objective-C로 쓰인\n * Flutter 핸들러처럼 수신 측 분석에 아예 나타나지 않는 코드가 실제로 있어서,\n * error로 단정하면 이 도구가 없애려던 오탐을 이 도구가 만든다.\n *\n * 공백의 종류는 구분한다. 이름이 리터럴이 아닌 채널 등록 하나가 무관한 메서드\n * 진단까지 무르게 하면 안 된다. 호출 측 한계는 네이티브 코드를 가리지 않으므로\n * 수신 측 플랫폼의 한계만 본다.\n *\n * 완화 단위는 진단의 target이다. 사실은 target별로만 조인되므로, 다른 target의\n * 수신 문서가 신고한 공백은 현재 target의 핸들러를 가릴 수 없다. target이 없는\n * (사실이 없는) 수신 문서는 어느 target을 분석했는지 특정할 수 없어 모든 target에\n * 적용한다. 같은 target에 귀속된 수신 문서가 사실과 함께 존재해도 마찬가지다.\n * 수신 문서 여러 개가 소스 트리를 나누어 가졌을 수 있으므로, 귀속 없는 문서가\n * 본 소스가 해당 target의 핸들러를 가릴 가능성을 배제할 수 없기 때문이다.\n *\n * `unjoined-` 접두사는 isthmus가 직접 세어 자신을 출처로 밝힌 한계만 인정한다.\n * 생산자 문자열은 신뢰의 근거가 아니므로, 같은 접두사를 차용한 생산자 신고는\n * 완화 근거가 되지 못한다.\n */\nfunction receiverCoverageGaps(\n limitations: readonly JoinLimitation[],\n): ReceiverCoverageGaps {\n const receiverLimitations = limitations.filter(({ platform }) =>\n isReceiverPlatform(platform),\n );\n const memoized = new Map<BridgeTarget, {\n allHandlers: boolean; allRegistrations: boolean;\n handlerChannels: Set<string>; registrationChannels: Set<string>;\n }>();\n const gapsFor = (target: BridgeTarget) => {\n const existing = memoized.get(target);\n if (existing !== undefined) return existing;\n const gaps = {\n allHandlers: false, allRegistrations: false,\n handlerChannels: new Set<string>(), registrationChannels: new Set<string>(),\n };\n for (const { target: gapTarget, tool, message, channels, origin } of receiverLimitations) {\n if (gapTarget !== null && gapTarget !== target) continue;\n const handlers = startsWithAny(producerHandlerGapPrefixes)(message) ||\n (origin === 'consumer' && tool === 'isthmus' && startsWithAny(isthmusHandlerGapPrefixes)(message));\n const registrations = startsWithAny(sourceCoverageGapPrefixes)(message) ||\n (origin === 'consumer' && tool === 'isthmus' && startsWithAny(isthmusRegistrationGapPrefixes)(message));\n // 하나라도 범위가 불명확한 공백이 있으면 같은 target의 좁은 범위로 덮지 않는다.\n if (channels === undefined) {\n gaps.allHandlers ||= handlers;\n gaps.allRegistrations ||= registrations;\n } else {\n for (const channel of channels) {\n if (handlers) gaps.handlerChannels.add(channel);\n if (registrations) gaps.registrationChannels.add(channel);\n }\n }\n }\n memoized.set(target, gaps);\n return gaps;\n };\n return {\n hidesHandlers: (target, channel) => {\n const gaps = gapsFor(target);\n return gaps.allHandlers || gaps.handlerChannels.has(channel);\n },\n hidesRegistrations: (target, channel) => {\n const gaps = gapsFor(target);\n return gaps.allRegistrations || gaps.registrationChannels.has(channel);\n },\n };\n}\n\n/** 주어진 접두사 중 하나로 시작하는지 검사하는 술어를 만든다. */\nfunction startsWithAny(\n prefixes: readonly string[],\n): (message: string) => boolean {\n return (message) => prefixes.some((prefix) => message.startsWith(prefix));\n}\n\n/**\n * 수신 측 소스 자체가 분석되지 않아 등록과 핸들러를 모두 가리는 한계다.\n *\n * 알려진 접두사만 인정한다. 모르는 한계를 공백으로 넓게 해석하면 진짜 불일치가\n * 경고로 묻힌다.\n */\nconst sourceCoverageGapPrefixes = [\n 'objective-c-sources:',\n 'shadowed-flutter-method-channel:',\n];\n\n/** 생산자가 신고하는, 핸들러 본문만 가리는 한계다. */\nconst handlerBodyCoverageGapPrefixes = ['opaque-handler-bodies:'];\n\n/** 생산자 신고 중 핸들러를 가릴 수 있는 한계 전체다. */\nconst producerHandlerGapPrefixes = [\n ...sourceCoverageGapPrefixes,\n ...handlerBodyCoverageGapPrefixes,\n];\n\n/**\n * isthmus가 직접 센 한계 중 핸들러를 가리는 접두사다.\n *\n * 생산자의 신고 개수에 의존하지 않으므로 `tool`이 `isthmus`인 항목만 인정한다.\n */\nconst isthmusHandlerGapPrefixes = [\n 'unjoined-dynamic-methods:',\n 'unjoined-unattributed-handlers:',\n];\n\n/** isthmus가 직접 센 한계 중 채널 등록을 가리는 접두사다. */\nconst isthmusRegistrationGapPrefixes = ['unjoined-dynamic-channels:'];\n"]}
|
package/dist/report/diff.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { compareStrings } from "../compare.js";
|
|
2
2
|
import { BridgeJoinValidationError, isBridgeJoinDeferred, joinBridgeDocuments } from "../join/join.js";
|
|
3
|
+
import { baselineEntryKey } from "./baseline.js";
|
|
3
4
|
import { createCheckReport } from "./check-report.js";
|
|
4
5
|
/** 동일 프로젝트의 관찰 결과를 비교하며 삭제 안전성이나 rename을 추측하지 않는다. */
|
|
5
6
|
export function createBridgeDiff(before, after) {
|
|
@@ -13,8 +14,8 @@ export function createBridgeDiff(before, after) {
|
|
|
13
14
|
const newReport = createCheckReport(newJoin);
|
|
14
15
|
const addedMethods = difference(newJoin.matchedMethods, oldJoin.matchedMethods, logicalKey);
|
|
15
16
|
const removedMethods = difference(oldJoin.matchedMethods, newJoin.matchedMethods, logicalKey);
|
|
16
|
-
const introducedIssues = difference(newReport.issues, oldReport.issues,
|
|
17
|
-
const resolvedIssues = difference(oldReport.issues, newReport.issues,
|
|
17
|
+
const introducedIssues = difference(newReport.issues, oldReport.issues, baselineEntryKey);
|
|
18
|
+
const resolvedIssues = difference(oldReport.issues, newReport.issues, baselineEntryKey);
|
|
18
19
|
return {
|
|
19
20
|
format: 'isthmus-diff',
|
|
20
21
|
version: 1,
|
|
@@ -64,11 +65,8 @@ function producerVersions(docs) {
|
|
|
64
65
|
function logicalKey(item) {
|
|
65
66
|
return JSON.stringify([item.target, item.channel, item.method ?? null]);
|
|
66
67
|
}
|
|
67
|
-
function issueKey(item) {
|
|
68
|
-
return JSON.stringify([item.code, logicalKey(item)]);
|
|
69
|
-
}
|
|
70
68
|
function limitationKey(item) {
|
|
71
|
-
return JSON.stringify([item.platform, item.target, item.tool, item.message]);
|
|
69
|
+
return JSON.stringify([item.platform, item.target, item.tool, item.message, item.channels ?? null, item.origin ?? null]);
|
|
72
70
|
}
|
|
73
71
|
/** 키 집합 차이를 안정적으로 정렬하고 원래 증거를 보존한다. */
|
|
74
72
|
function difference(left, right, key) {
|
package/dist/report/diff.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/report/diff.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACvG,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,sDAAsD;AACtD,MAAM,UAAU,gBAAgB,CAC9B,MAAsC,EACtC,KAAqC;IAErC,iBAAiB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjC,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,oBAAoB,CAAC,OAAO,CAAC,IAAI,oBAAoB,CAAC,OAAO,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,yBAAyB,CACjC,6EAA6E,CAC9E,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,CAAC,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;IAC5F,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,CAAC,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;IAC9F,MAAM,gBAAgB,GAAG,UAAU,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/report/diff.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BridgeFactsDocument, BridgeTarget } from '../exchange/parse.ts';\nimport { compareStrings } from '../compare.ts';\nimport { BridgeJoinValidationError, isBridgeJoinDeferred, joinBridgeDocuments } from '../join/join.ts';\nimport { baselineEntryKey } from './baseline.ts';\nimport { createCheckReport } from './check-report.ts';\n\n/** 동일 프로젝트의 관찰 결과를 비교하며 삭제 안전성이나 rename을 추측하지 않는다. */\nexport function createBridgeDiff(\n before: readonly BridgeFactsDocument[],\n after: readonly BridgeFactsDocument[],\n) {\n validateSnapshots(before, after);\n const oldJoin = joinBridgeDocuments(before);\n const newJoin = joinBridgeDocuments(after);\n if (isBridgeJoinDeferred(oldJoin) || isBridgeJoinDeferred(newJoin)) {\n throw new BridgeJoinValidationError(\n 'Cannot compare deferred bridge joins; split mixed bridge targets and retry.',\n );\n }\n const oldReport = createCheckReport(oldJoin);\n const newReport = createCheckReport(newJoin);\n const addedMethods = difference(newJoin.matchedMethods, oldJoin.matchedMethods, logicalKey);\n const removedMethods = difference(oldJoin.matchedMethods, newJoin.matchedMethods, logicalKey);\n const introducedIssues = difference(newReport.issues, oldReport.issues, baselineEntryKey);\n const resolvedIssues = difference(oldReport.issues, newReport.issues, baselineEntryKey);\n return {\n format: 'isthmus-diff' as const,\n version: 1 as const,\n summary: {\n addedMethods: addedMethods.length,\n removedMethods: removedMethods.length,\n introducedErrors: introducedIssues.filter((issue) => issue.severity === 'error').length,\n introducedWarnings: introducedIssues.filter((issue) => issue.severity === 'warning').length,\n resolvedIssues: resolvedIssues.length,\n },\n addedMethods,\n removedMethods,\n introducedIssues,\n resolvedIssues,\n limitations: {\n before: oldReport.limitations,\n after: newReport.limitations,\n added: difference(newReport.limitations, oldReport.limitations, limitationKey),\n removed: difference(oldReport.limitations, newReport.limitations, limitationKey),\n },\n producers: { before: producerVersions(before), after: producerVersions(after) },\n };\n}\n\n/** 플랫폼 누락이나 다른 프로젝트를 코드 삭제로 오해하지 않도록 입력 구성을 고정한다. */\nfunction validateSnapshots(before: readonly BridgeFactsDocument[], after: readonly BridgeFactsDocument[]): void {\n const all = [...before, ...after];\n if (new Set(all.map((doc) => doc.project)).size !== 1 ||\n ![before, after].every((docs) => docs.some((doc) => doc.platform === 'dart') &&\n docs.some((doc) => doc.platform === 'swift')) ||\n JSON.stringify(producerInventory(before)) !== JSON.stringify(producerInventory(after)) ||\n all.some((doc) => (doc.platform !== 'dart' && doc.platform !== 'swift') ||\n (doc.target !== null && doc.target !== 'flutter'))) {\n throw new BridgeJoinValidationError(\n 'Diff requires the same project and matching Flutter dart/swift producer '\n + 'inventories in both snapshots; rebuild both snapshots from one checkout.',\n );\n }\n}\n\n/** 버전 변화는 출력하되 플랫폼·도구별 문서 개수 변화는 허용하지 않는다. */\nfunction producerInventory(docs: readonly BridgeFactsDocument[]): string[] {\n return docs.map((doc) => JSON.stringify([doc.platform, doc.tool.name])).sort(compareStrings);\n}\n\n/** 추출기 업그레이드가 관찰 차이의 원인인지 검토할 버전 근거다. */\nfunction producerVersions(docs: readonly BridgeFactsDocument[]) {\n return docs.map((doc) => ({ platform: doc.platform, ...doc.tool, generatedAt: doc.generatedAt }))\n .sort((a, b) => compareStrings(JSON.stringify(a), JSON.stringify(b)));\n}\n\n/** 충돌 없는 논리 키로 비교해 소스 줄 이동을 추가·삭제로 보고하지 않는다. */\nfunction logicalKey(item: { readonly target: BridgeTarget; readonly channel: string; readonly method?: string }): string {\n return JSON.stringify([item.target, item.channel, item.method ?? null]);\n}\n\nfunction limitationKey(item: { readonly platform: string; readonly target: string | null; readonly tool: string; readonly message: string; readonly channels?: readonly string[]; readonly origin?: 'consumer' }): string {\n return JSON.stringify([item.platform, item.target, item.tool, item.message, item.channels ?? null, item.origin ?? null]);\n}\n\n/** 키 집합 차이를 안정적으로 정렬하고 원래 증거를 보존한다. */\nfunction difference<T>(left: readonly T[], right: readonly T[], key: (item: T) => string): T[] {\n const existing = new Set(right.map(key));\n return left.filter((item) => !existing.has(key(item)))\n .sort((a, b) => compareStrings(key(a), key(b)));\n}\n"]}
|
package/dist/report/graph.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BridgeLocation, BridgePlatform, BridgeSymbol, BridgeTarget } from '../exchange/parse.ts';
|
|
1
|
+
import type { BridgeLocation, BridgePlatform, BridgeSymbol, BridgeSourceLanguage, BridgeTarget } from '../exchange/parse.ts';
|
|
2
2
|
import type { BridgeJoinResult, JoinLimitation } from '../join/join.ts';
|
|
3
3
|
/** 소스 위치 하나를 나타내는 경계 그래프 노드다. */
|
|
4
4
|
export interface BridgeGraphNode {
|
|
@@ -6,6 +6,7 @@ export interface BridgeGraphNode {
|
|
|
6
6
|
readonly platform: BridgePlatform;
|
|
7
7
|
readonly location: BridgeLocation;
|
|
8
8
|
readonly symbol?: BridgeSymbol;
|
|
9
|
+
readonly sourceLanguage?: BridgeSourceLanguage;
|
|
9
10
|
}
|
|
10
11
|
/** 호출 측 위치에서 수신 측 위치로 향하는 경계 간선이다. */
|
|
11
12
|
export interface BridgeGraphEdge {
|
package/dist/report/graph.js
CHANGED
|
@@ -33,7 +33,7 @@ function renderMermaid(graph) {
|
|
|
33
33
|
lines.push(` %% limitation: ${limitationComment(limitation)}`);
|
|
34
34
|
}
|
|
35
35
|
for (const node of graph.nodes) {
|
|
36
|
-
const label = `${mermaidText(node.platform)}<br/>${mermaidText(locationLabel(node.location))}`;
|
|
36
|
+
const label = `${mermaidText(node.sourceLanguage ?? node.platform)}<br/>${mermaidText(locationLabel(node.location))}`;
|
|
37
37
|
lines.push(` ${identifiers.get(node.id)}["${label}"]`);
|
|
38
38
|
}
|
|
39
39
|
for (const edge of graph.edges) {
|
|
@@ -61,7 +61,7 @@ function renderDot(graph) {
|
|
|
61
61
|
lines.push(` // limitation: ${limitationComment(limitation)}`);
|
|
62
62
|
}
|
|
63
63
|
for (const node of graph.nodes) {
|
|
64
|
-
const label = `${node.platform}\n${locationLabel(node.location)}`;
|
|
64
|
+
const label = `${node.sourceLanguage ?? node.platform}\n${locationLabel(node.location)}`;
|
|
65
65
|
lines.push(` ${dotString(node.id)} [label=${dotString(label)}];`);
|
|
66
66
|
}
|
|
67
67
|
for (const edge of graph.edges) {
|
|
@@ -74,7 +74,9 @@ function renderDot(graph) {
|
|
|
74
74
|
/** 분석 한계를 텍스트 그래프의 한 줄 주석으로 안전하게 만든다. */
|
|
75
75
|
function limitationComment(limitation) {
|
|
76
76
|
const text = `${limitation.platform}/${limitation.target}/${limitation.tool}: `
|
|
77
|
-
+ limitation.message
|
|
77
|
+
+ limitation.message
|
|
78
|
+
+ (limitation.origin === undefined ? '' : ' [origin: consumer]')
|
|
79
|
+
+ (limitation.channels === undefined ? '' : ` [channels: ${JSON.stringify(limitation.channels)}]`);
|
|
78
80
|
return text.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]+/gu, ' ');
|
|
79
81
|
}
|
|
80
82
|
/** DOT 문자열 리터럴로 안전하게 이스케이프한다. */
|
|
@@ -167,14 +169,7 @@ function addNode(nodes, endpoint) {
|
|
|
167
169
|
const id = endpointId(endpoint);
|
|
168
170
|
const existing = nodes.get(id);
|
|
169
171
|
if (existing === undefined) {
|
|
170
|
-
nodes.set(id, endpoint
|
|
171
|
-
? { id, platform: endpoint.platform, location: endpoint.location }
|
|
172
|
-
: {
|
|
173
|
-
id,
|
|
174
|
-
platform: endpoint.platform,
|
|
175
|
-
location: endpoint.location,
|
|
176
|
-
symbol: endpoint.symbol,
|
|
177
|
-
});
|
|
172
|
+
nodes.set(id, { id, ...endpoint });
|
|
178
173
|
}
|
|
179
174
|
else if (endpoint.symbol !== undefined) {
|
|
180
175
|
const symbol = mergeSymbols(existing.symbol, endpoint.symbol);
|
|
@@ -202,7 +197,8 @@ function mergeSymbols(left, right) {
|
|
|
202
197
|
/** 플랫폼과 소스 위치로 실행 간 안정적인 노드 ID를 만든다. */
|
|
203
198
|
function endpointId(endpoint) {
|
|
204
199
|
const { path, line, column } = endpoint.location;
|
|
205
|
-
|
|
200
|
+
const suffix = endpoint.sourceLanguage === undefined ? '' : `:${endpoint.sourceLanguage}`;
|
|
201
|
+
return `${endpoint.platform}:${path}:${line}:${column}${suffix}`;
|
|
206
202
|
}
|
|
207
203
|
/** 간선을 종류·from·to 순으로 고정한다. */
|
|
208
204
|
function compareEdges(left, right) {
|
package/dist/report/graph.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/report/graph.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n BridgeLocation,\n BridgePlatform,\n BridgeSymbol,\n BridgeTarget,\n} from '../exchange/parse.ts';\nimport type {\n BridgeEndpoint,\n BridgeJoinResult,\n JoinLimitation,\n} from '../join/join.ts';\nimport { isBridgeJoinDeferred } from '../join/join.ts';\nimport { compareStrings } from '../compare.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** 소스 위치 하나를 나타내는 경계 그래프 노드다. */\nexport interface BridgeGraphNode {\n readonly id: string;\n readonly platform: BridgePlatform;\n readonly location: BridgeLocation;\n readonly symbol?: BridgeSymbol;\n}\n\n/** 호출 측 위치에서 수신 측 위치로 향하는 경계 간선이다. */\nexport interface BridgeGraphEdge {\n readonly from: string;\n readonly to: string;\n readonly kind: 'channel' | 'method';\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method?: string;\n}\n\n/** 매치된 경계 간선만 담는 그래프 문서다. */\nexport interface BridgeGraphDocument {\n readonly format: 'isthmus-graph';\n readonly version: 1;\n readonly nodes: readonly BridgeGraphNode[];\n readonly edges: readonly BridgeGraphEdge[];\n readonly limitations: readonly JoinLimitation[];\n}\n\n/** 한 번에 생성할 수 있는 경계 그래프 간선 수다. */\nexport const MAX_GRAPH_EDGES = 100_000;\n\n/** 입력 증거의 곱이 안전한 그래프 크기를 넘었음을 나타낸다. */\nexport class BridgeGraphLimitError extends Error {\n constructor() {\n super(`Bridge graph exceeds the ${MAX_GRAPH_EDGES} edge limit.`);\n this.name = 'BridgeGraphLimitError';\n }\n}\n\n/** 같은 위치 노드가 서로 모순된 심볼을 가졌음을 나타낸다. */\nexport class BridgeGraphValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BridgeGraphValidationError';\n }\n}\n\n/** 경계 그래프를 요청한 출력 형식으로 렌더링한다. */\nexport function renderBridgeGraph(\n graph: BridgeGraphDocument,\n format: 'json' | 'dot' | 'mermaid',\n): string {\n if (format === 'json') return encodeSortedJson(graph);\n if (format === 'dot') return renderDot(graph);\n return renderMermaid(graph);\n}\n\n/** Mermaid flowchart 문법으로 경계 그래프를 렌더링한다. */\nfunction renderMermaid(graph: BridgeGraphDocument): string {\n const identifiers = new Map(\n graph.nodes.map((node, index) => [node.id, `n${index}`]),\n );\n const lines = ['flowchart LR'];\n for (const limitation of graph.limitations) {\n lines.push(` %% limitation: ${limitationComment(limitation)}`);\n }\n for (const node of graph.nodes) {\n const label = `${mermaidText(node.platform)}<br/>${mermaidText(locationLabel(node.location))}`;\n lines.push(` ${identifiers.get(node.id)}[\"${label}\"]`);\n }\n for (const edge of graph.edges) {\n const from = identifiers.get(edge.from);\n const to = identifiers.get(edge.to);\n lines.push(` ${from} -->|\"${mermaidText(edgeLabel(edge))}\"| ${to}`);\n }\n return `${lines.join('\\n')}\\n`;\n}\n\n/** Mermaid 인용 라벨에서 제어 문자를 HTML 엔터티로 바꾼다. */\nfunction mermaidText(value: string): string {\n return value\n .replaceAll('&', '&')\n .replaceAll('\"', '"')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('|', '|')\n .replaceAll('\\r', ' ')\n .replaceAll('\\n', ' ');\n}\n\n/** Graphviz DOT 문법으로 경계 그래프를 렌더링한다. */\nfunction renderDot(graph: BridgeGraphDocument): string {\n const lines = ['digraph isthmus {', ' rankdir=LR;'];\n for (const limitation of graph.limitations) {\n lines.push(` // limitation: ${limitationComment(limitation)}`);\n }\n for (const node of graph.nodes) {\n const label = `${node.platform}\\n${locationLabel(node.location)}`;\n lines.push(` ${dotString(node.id)} [label=${dotString(label)}];`);\n }\n for (const edge of graph.edges) {\n lines.push(\n ` ${dotString(edge.from)} -> ${dotString(edge.to)} `\n + `[label=${dotString(edgeLabel(edge))}];`,\n );\n }\n lines.push('}');\n return `${lines.join('\\n')}\\n`;\n}\n\n/** 분석 한계를 텍스트 그래프의 한 줄 주석으로 안전하게 만든다. */\nfunction limitationComment(limitation: JoinLimitation): string {\n const text =\n `${limitation.platform}/${limitation.target}/${limitation.tool}: `\n + limitation.message;\n return text.replace(/[\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029]+/gu, ' ');\n}\n\n/** DOT 문자열 리터럴로 안전하게 이스케이프한다. */\nfunction dotString(value: string): string {\n return JSON.stringify(value);\n}\n\n/** 위치를 사람이 읽을 수 있는 경로:줄:열로 만든다. */\nfunction locationLabel(location: BridgeLocation): string {\n return `${location.path}:${location.line}:${location.column}`;\n}\n\n/** 간선 종류와 문자열 키를 한 줄 라벨로 만든다. */\nfunction edgeLabel(edge: BridgeGraphEdge): string {\n const key = edge.method === undefined\n ? edge.channel\n : `${edge.channel}#${edge.method}`;\n return `${edge.kind} ${key}`;\n}\n\n/** 조인 결과의 매치만 경계 그래프로 바꾼다. */\nexport function createBridgeGraph(joined: BridgeJoinResult): BridgeGraphDocument {\n if (isBridgeJoinDeferred(joined)) {\n throw new Error('Cannot create a graph from a deferred bridge join.');\n }\n assertBridgeGraphSize(joined);\n const nodes = new Map<string, BridgeGraphNode>();\n const edges: BridgeGraphEdge[] = [];\n addChannelEdges(joined, nodes, edges);\n addMethodEdges(joined, nodes, edges);\n return {\n format: 'isthmus-graph',\n version: 1,\n nodes: [...nodes.values()].sort((left, right) => compareStrings(left.id, right.id)),\n edges: edges.sort(compareEdges),\n limitations: joined.limitations,\n };\n}\n\n/** 모든 Cartesian 간선 수를 할당 전에 계산해 메모리 폭증을 막는다. */\nexport function assertBridgeGraphSize(joined: BridgeJoinResult): void {\n let edgeCount = 0;\n const endpointCounts = [\n ...joined.matchedChannels.map(({ creations, registrations }) =>\n [creations.length, registrations.length] as const,\n ),\n ...joined.matchedMethods.map(({ invocations, handlers }) =>\n [invocations.length, handlers.length] as const,\n ),\n ];\n for (const [fromCount, toCount] of endpointCounts) {\n if (\n toCount > 0 &&\n fromCount > Math.floor((MAX_GRAPH_EDGES - edgeCount) / toCount)\n ) {\n throw new BridgeGraphLimitError();\n }\n edgeCount += fromCount * toCount;\n }\n}\n\n/** 매치된 채널의 생성→등록 간선을 추가한다. */\nfunction addChannelEdges(\n joined: BridgeJoinResult,\n nodes: Map<string, BridgeGraphNode>,\n edges: BridgeGraphEdge[],\n): void {\n for (const match of joined.matchedChannels) {\n for (const creation of match.creations) {\n for (const registration of match.registrations) {\n const from = addNode(nodes, creation);\n const to = addNode(nodes, registration);\n edges.push({\n from,\n to,\n kind: 'channel',\n target: match.target,\n channel: match.channel,\n });\n }\n }\n }\n}\n\n/** 매치된 메서드의 호출→핸들러 간선을 추가한다. */\nfunction addMethodEdges(\n joined: BridgeJoinResult,\n nodes: Map<string, BridgeGraphNode>,\n edges: BridgeGraphEdge[],\n): void {\n for (const match of joined.matchedMethods) {\n for (const invocation of match.invocations) {\n for (const handler of match.handlers) {\n const from = addNode(nodes, invocation);\n const to = addNode(nodes, handler);\n edges.push({\n from,\n to,\n kind: 'method',\n target: match.target,\n channel: match.channel,\n method: match.method,\n });\n }\n }\n }\n}\n\n/** 증거 위치를 그래프 노드로 추가하고 안정 ID를 돌려준다. */\nfunction addNode(\n nodes: Map<string, BridgeGraphNode>,\n endpoint: BridgeEndpoint,\n): string {\n const id = endpointId(endpoint);\n const existing = nodes.get(id);\n if (existing === undefined) {\n nodes.set(\n id,\n endpoint.symbol === undefined\n ? { id, platform: endpoint.platform, location: endpoint.location }\n : {\n id,\n platform: endpoint.platform,\n location: endpoint.location,\n symbol: endpoint.symbol,\n },\n );\n } else if (endpoint.symbol !== undefined) {\n const symbol = mergeSymbols(existing.symbol, endpoint.symbol);\n if (symbol === undefined) {\n throw new BridgeGraphValidationError(\n 'Bridge graph node has conflicting symbols.',\n );\n }\n nodes.set(id, { ...existing, symbol });\n }\n return id;\n}\n\n/** 호환되는 두 심볼을 더 구체적인 선언 식별자로 병합한다. */\nfunction mergeSymbols(\n left: BridgeSymbol | undefined,\n right: BridgeSymbol,\n): BridgeSymbol | undefined {\n if (left === undefined) return right;\n if (left.qualifiedName !== right.qualifiedName) return undefined;\n if (left.usr !== undefined && right.usr !== undefined && left.usr !== right.usr) {\n return undefined;\n }\n const usr = left.usr ?? right.usr;\n return usr === undefined\n ? { qualifiedName: left.qualifiedName }\n : { qualifiedName: left.qualifiedName, usr };\n}\n\n/** 플랫폼과 소스 위치로 실행 간 안정적인 노드 ID를 만든다. */\nfunction endpointId(endpoint: BridgeEndpoint): string {\n const { path, line, column } = endpoint.location;\n return `${endpoint.platform}:${path}:${line}:${column}`;\n}\n\n/** 간선을 종류·from·to 순으로 고정한다. */\nfunction compareEdges(left: BridgeGraphEdge, right: BridgeGraphEdge): number {\n return (\n compareStrings(left.kind, right.kind) ||\n compareStrings(left.from, right.from) ||\n compareStrings(left.to, right.to)\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/report/graph.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n BridgeLocation,\n BridgePlatform,\n BridgeSymbol,\n BridgeSourceLanguage,\n BridgeTarget,\n} from '../exchange/parse.ts';\nimport type {\n BridgeEndpoint,\n BridgeJoinResult,\n JoinLimitation,\n} from '../join/join.ts';\nimport { isBridgeJoinDeferred } from '../join/join.ts';\nimport { compareStrings } from '../compare.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** 소스 위치 하나를 나타내는 경계 그래프 노드다. */\nexport interface BridgeGraphNode {\n readonly id: string;\n readonly platform: BridgePlatform;\n readonly location: BridgeLocation;\n readonly symbol?: BridgeSymbol;\n readonly sourceLanguage?: BridgeSourceLanguage;\n}\n\n/** 호출 측 위치에서 수신 측 위치로 향하는 경계 간선이다. */\nexport interface BridgeGraphEdge {\n readonly from: string;\n readonly to: string;\n readonly kind: 'channel' | 'method';\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method?: string;\n}\n\n/** 매치된 경계 간선만 담는 그래프 문서다. */\nexport interface BridgeGraphDocument {\n readonly format: 'isthmus-graph';\n readonly version: 1;\n readonly nodes: readonly BridgeGraphNode[];\n readonly edges: readonly BridgeGraphEdge[];\n readonly limitations: readonly JoinLimitation[];\n}\n\n/** 한 번에 생성할 수 있는 경계 그래프 간선 수다. */\nexport const MAX_GRAPH_EDGES = 100_000;\n\n/** 입력 증거의 곱이 안전한 그래프 크기를 넘었음을 나타낸다. */\nexport class BridgeGraphLimitError extends Error {\n constructor() {\n super(`Bridge graph exceeds the ${MAX_GRAPH_EDGES} edge limit.`);\n this.name = 'BridgeGraphLimitError';\n }\n}\n\n/** 같은 위치 노드가 서로 모순된 심볼을 가졌음을 나타낸다. */\nexport class BridgeGraphValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BridgeGraphValidationError';\n }\n}\n\n/** 경계 그래프를 요청한 출력 형식으로 렌더링한다. */\nexport function renderBridgeGraph(\n graph: BridgeGraphDocument,\n format: 'json' | 'dot' | 'mermaid',\n): string {\n if (format === 'json') return encodeSortedJson(graph);\n if (format === 'dot') return renderDot(graph);\n return renderMermaid(graph);\n}\n\n/** Mermaid flowchart 문법으로 경계 그래프를 렌더링한다. */\nfunction renderMermaid(graph: BridgeGraphDocument): string {\n const identifiers = new Map(\n graph.nodes.map((node, index) => [node.id, `n${index}`]),\n );\n const lines = ['flowchart LR'];\n for (const limitation of graph.limitations) {\n lines.push(` %% limitation: ${limitationComment(limitation)}`);\n }\n for (const node of graph.nodes) {\n const label = `${mermaidText(node.sourceLanguage ?? node.platform)}<br/>${mermaidText(locationLabel(node.location))}`;\n lines.push(` ${identifiers.get(node.id)}[\"${label}\"]`);\n }\n for (const edge of graph.edges) {\n const from = identifiers.get(edge.from);\n const to = identifiers.get(edge.to);\n lines.push(` ${from} -->|\"${mermaidText(edgeLabel(edge))}\"| ${to}`);\n }\n return `${lines.join('\\n')}\\n`;\n}\n\n/** Mermaid 인용 라벨에서 제어 문자를 HTML 엔터티로 바꾼다. */\nfunction mermaidText(value: string): string {\n return value\n .replaceAll('&', '&')\n .replaceAll('\"', '"')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('|', '|')\n .replaceAll('\\r', ' ')\n .replaceAll('\\n', ' ');\n}\n\n/** Graphviz DOT 문법으로 경계 그래프를 렌더링한다. */\nfunction renderDot(graph: BridgeGraphDocument): string {\n const lines = ['digraph isthmus {', ' rankdir=LR;'];\n for (const limitation of graph.limitations) {\n lines.push(` // limitation: ${limitationComment(limitation)}`);\n }\n for (const node of graph.nodes) {\n const label = `${node.sourceLanguage ?? node.platform}\\n${locationLabel(node.location)}`;\n lines.push(` ${dotString(node.id)} [label=${dotString(label)}];`);\n }\n for (const edge of graph.edges) {\n lines.push(\n ` ${dotString(edge.from)} -> ${dotString(edge.to)} `\n + `[label=${dotString(edgeLabel(edge))}];`,\n );\n }\n lines.push('}');\n return `${lines.join('\\n')}\\n`;\n}\n\n/** 분석 한계를 텍스트 그래프의 한 줄 주석으로 안전하게 만든다. */\nfunction limitationComment(limitation: JoinLimitation): string {\n const text =\n `${limitation.platform}/${limitation.target}/${limitation.tool}: `\n + limitation.message\n + (limitation.origin === undefined ? '' : ' [origin: consumer]')\n + (limitation.channels === undefined ? '' : ` [channels: ${JSON.stringify(limitation.channels)}]`);\n return text.replace(/[\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029]+/gu, ' ');\n}\n\n/** DOT 문자열 리터럴로 안전하게 이스케이프한다. */\nfunction dotString(value: string): string {\n return JSON.stringify(value);\n}\n\n/** 위치를 사람이 읽을 수 있는 경로:줄:열로 만든다. */\nfunction locationLabel(location: BridgeLocation): string {\n return `${location.path}:${location.line}:${location.column}`;\n}\n\n/** 간선 종류와 문자열 키를 한 줄 라벨로 만든다. */\nfunction edgeLabel(edge: BridgeGraphEdge): string {\n const key = edge.method === undefined\n ? edge.channel\n : `${edge.channel}#${edge.method}`;\n return `${edge.kind} ${key}`;\n}\n\n/** 조인 결과의 매치만 경계 그래프로 바꾼다. */\nexport function createBridgeGraph(joined: BridgeJoinResult): BridgeGraphDocument {\n if (isBridgeJoinDeferred(joined)) {\n throw new Error('Cannot create a graph from a deferred bridge join.');\n }\n assertBridgeGraphSize(joined);\n const nodes = new Map<string, BridgeGraphNode>();\n const edges: BridgeGraphEdge[] = [];\n addChannelEdges(joined, nodes, edges);\n addMethodEdges(joined, nodes, edges);\n return {\n format: 'isthmus-graph',\n version: 1,\n nodes: [...nodes.values()].sort((left, right) => compareStrings(left.id, right.id)),\n edges: edges.sort(compareEdges),\n limitations: joined.limitations,\n };\n}\n\n/** 모든 Cartesian 간선 수를 할당 전에 계산해 메모리 폭증을 막는다. */\nexport function assertBridgeGraphSize(joined: BridgeJoinResult): void {\n let edgeCount = 0;\n const endpointCounts = [\n ...joined.matchedChannels.map(({ creations, registrations }) =>\n [creations.length, registrations.length] as const,\n ),\n ...joined.matchedMethods.map(({ invocations, handlers }) =>\n [invocations.length, handlers.length] as const,\n ),\n ];\n for (const [fromCount, toCount] of endpointCounts) {\n if (\n toCount > 0 &&\n fromCount > Math.floor((MAX_GRAPH_EDGES - edgeCount) / toCount)\n ) {\n throw new BridgeGraphLimitError();\n }\n edgeCount += fromCount * toCount;\n }\n}\n\n/** 매치된 채널의 생성→등록 간선을 추가한다. */\nfunction addChannelEdges(\n joined: BridgeJoinResult,\n nodes: Map<string, BridgeGraphNode>,\n edges: BridgeGraphEdge[],\n): void {\n for (const match of joined.matchedChannels) {\n for (const creation of match.creations) {\n for (const registration of match.registrations) {\n const from = addNode(nodes, creation);\n const to = addNode(nodes, registration);\n edges.push({\n from,\n to,\n kind: 'channel',\n target: match.target,\n channel: match.channel,\n });\n }\n }\n }\n}\n\n/** 매치된 메서드의 호출→핸들러 간선을 추가한다. */\nfunction addMethodEdges(\n joined: BridgeJoinResult,\n nodes: Map<string, BridgeGraphNode>,\n edges: BridgeGraphEdge[],\n): void {\n for (const match of joined.matchedMethods) {\n for (const invocation of match.invocations) {\n for (const handler of match.handlers) {\n const from = addNode(nodes, invocation);\n const to = addNode(nodes, handler);\n edges.push({\n from,\n to,\n kind: 'method',\n target: match.target,\n channel: match.channel,\n method: match.method,\n });\n }\n }\n }\n}\n\n/** 증거 위치를 그래프 노드로 추가하고 안정 ID를 돌려준다. */\nfunction addNode(\n nodes: Map<string, BridgeGraphNode>,\n endpoint: BridgeEndpoint,\n): string {\n const id = endpointId(endpoint);\n const existing = nodes.get(id);\n if (existing === undefined) {\n nodes.set(id, { id, ...endpoint });\n } else if (endpoint.symbol !== undefined) {\n const symbol = mergeSymbols(existing.symbol, endpoint.symbol);\n if (symbol === undefined) {\n throw new BridgeGraphValidationError(\n 'Bridge graph node has conflicting symbols.',\n );\n }\n nodes.set(id, { ...existing, symbol });\n }\n return id;\n}\n\n/** 호환되는 두 심볼을 더 구체적인 선언 식별자로 병합한다. */\nfunction mergeSymbols(\n left: BridgeSymbol | undefined,\n right: BridgeSymbol,\n): BridgeSymbol | undefined {\n if (left === undefined) return right;\n if (left.qualifiedName !== right.qualifiedName) return undefined;\n if (left.usr !== undefined && right.usr !== undefined && left.usr !== right.usr) {\n return undefined;\n }\n const usr = left.usr ?? right.usr;\n return usr === undefined\n ? { qualifiedName: left.qualifiedName }\n : { qualifiedName: left.qualifiedName, usr };\n}\n\n/** 플랫폼과 소스 위치로 실행 간 안정적인 노드 ID를 만든다. */\nfunction endpointId(endpoint: BridgeEndpoint): string {\n const { path, line, column } = endpoint.location;\n const suffix = endpoint.sourceLanguage === undefined ? '' : `:${endpoint.sourceLanguage}`;\n return `${endpoint.platform}:${path}:${line}:${column}${suffix}`;\n}\n\n/** 간선을 종류·from·to 순으로 고정한다. */\nfunction compareEdges(left: BridgeGraphEdge, right: BridgeGraphEdge): number {\n return (\n compareStrings(left.kind, right.kind) ||\n compareStrings(left.from, right.from) ||\n compareStrings(left.to, right.to)\n );\n}\n"]}
|
|
@@ -32,6 +32,7 @@ export interface CartographRetentionsDocument {
|
|
|
32
32
|
}>;
|
|
33
33
|
readonly generatedAt: string;
|
|
34
34
|
readonly retentions: readonly ExternalRetention[];
|
|
35
|
+
readonly omittedObjectiveCHandlers?: number;
|
|
35
36
|
}
|
|
36
37
|
/** 불완전한 조인으로 보존 결정을 만들 수 없음을 나타낸다. */
|
|
37
38
|
export declare class RetentionValidationError extends Error {
|
|
@@ -30,16 +30,32 @@ export function createCartographRetentionsDocument(joined, generatedAt, producer
|
|
|
30
30
|
throw new RetentionValidationError('Cannot create retentions from a deferred bridge join.');
|
|
31
31
|
}
|
|
32
32
|
rejectUnresolvedSwiftHandlers(joined);
|
|
33
|
+
const omittedObjectiveCHandlers = countObjectiveCHandlers(joined);
|
|
33
34
|
return {
|
|
34
35
|
format: 'external-retentions',
|
|
35
36
|
version: 0,
|
|
36
37
|
producedBy: { name: 'isthmus', version: producerVersion },
|
|
37
38
|
generatedAt,
|
|
38
39
|
retentions: collectCartographRetentions(joined),
|
|
40
|
+
...(omittedObjectiveCHandlers === 0 ? {} : { omittedObjectiveCHandlers }),
|
|
39
41
|
};
|
|
40
42
|
}
|
|
43
|
+
/** 그래프 밖의 매치도 사라지지 않게 채널·메서드·위치별로 센다. */
|
|
44
|
+
function countObjectiveCHandlers(joined) {
|
|
45
|
+
const keys = new Set();
|
|
46
|
+
for (const method of joined.matchedMethods) {
|
|
47
|
+
for (const handler of method.handlers) {
|
|
48
|
+
if (handler.sourceLanguage !== 'objective-c')
|
|
49
|
+
continue;
|
|
50
|
+
keys.add(JSON.stringify([method.target, method.channel, method.method,
|
|
51
|
+
handler.location.path, handler.location.line, handler.location.column]));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return keys.size;
|
|
55
|
+
}
|
|
41
56
|
/**
|
|
42
57
|
* 심볼이 없어 보존 근거로 바꿀 수 없는 매치 Swift 핸들러를 거부한다.
|
|
58
|
+
* sourceLanguage로 확인된 Objective-C 구현은 Swift 그래프의 보존 대상이 아니다.
|
|
43
59
|
*
|
|
44
60
|
* 교환 계약에서 `symbol`은 선택 필드다. 호출자가 있는데도 근거를 만들지 못한
|
|
45
61
|
* 핸들러를 조용히 빼면 cartograph는 그 핸들러를 계속 미사용으로 보고하고,
|
|
@@ -49,7 +65,7 @@ function rejectUnresolvedSwiftHandlers(joined) {
|
|
|
49
65
|
const unresolved = new Set();
|
|
50
66
|
for (const method of joined.matchedMethods) {
|
|
51
67
|
for (const handler of method.handlers) {
|
|
52
|
-
if (handler.platform !== 'swift' || handler.symbol !== undefined)
|
|
68
|
+
if (handler.platform !== 'swift' || handler.sourceLanguage === 'objective-c' || handler.symbol !== undefined)
|
|
53
69
|
continue;
|
|
54
70
|
const { path, line, column } = handler.location;
|
|
55
71
|
unresolved.add(`${path}\u0000${line}\u0000${column}`);
|
|
@@ -70,7 +86,7 @@ function collectCartographRetentions(joined) {
|
|
|
70
86
|
if (caller === undefined)
|
|
71
87
|
continue;
|
|
72
88
|
for (const handler of method.handlers) {
|
|
73
|
-
if (handler.platform !== 'swift' || handler.symbol === undefined)
|
|
89
|
+
if (handler.platform !== 'swift' || handler.sourceLanguage === 'objective-c' || handler.symbol === undefined)
|
|
74
90
|
continue;
|
|
75
91
|
const symbolKey = handler.symbol.usr === undefined
|
|
76
92
|
? `name:${handler.symbol.qualifiedName}`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retentions.js","sourceRoot":"","sources":["../../src/report/retentions.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,oBAAoB,GAErB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"retentions.js","sourceRoot":"","sources":["../../src/report/retentions.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n BridgeFactsDocument,\n BridgePlatform,\n BridgeSymbol,\n} from '../exchange/parse.ts';\nimport {\n isBridgeJoinDeferred,\n type BridgeJoinResult,\n} from '../join/join.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** cartograph가 보존할 Swift 선언 식별자다. */\nexport interface RetentionSymbol extends BridgeSymbol {\n readonly usr?: string;\n}\n\n/** 언어 경계 너머 호출자의 증거 위치다. */\nexport interface RetentionCaller {\n readonly platform: BridgePlatform;\n readonly path: string;\n readonly line: number;\n}\n\n/** 보존 판단을 설명할 채널·메서드·호출자 근거다. */\nexport interface RetentionEvidence {\n readonly channel: string;\n readonly method: string;\n readonly caller: RetentionCaller;\n}\n\n/** cartograph 외부 보존 근거 하나다. */\nexport interface ExternalRetention {\n readonly symbol: RetentionSymbol;\n readonly reason: 'bridge';\n readonly evidence: RetentionEvidence;\n}\n\n/** cartograph가 읽는 external-retentions 버전 0 문서다. */\nexport interface CartographRetentionsDocument {\n readonly format: 'external-retentions';\n readonly version: 0;\n readonly producedBy: Readonly<{ name: 'isthmus'; version: string }>;\n readonly generatedAt: string;\n readonly retentions: readonly ExternalRetention[];\n readonly omittedObjectiveCHandlers?: number;\n}\n\n/** 불완전한 조인으로 보존 결정을 만들 수 없음을 나타낸다. */\nexport class RetentionValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'RetentionValidationError';\n }\n}\n\n/**\n * cartograph가 소비할 수 있는 수신 측 문서가 입력에 있는지 검증한다.\n *\n * cartograph는 Swift 심볼만 보존한다. Swift 문서가 없는 입력은 조인 자체는\n * 성공하므로, 검증하지 않으면 보존할 근거가 없다는 사실이 빈 목록과 코드 0으로\n * 사라진다. 사실이 없는 Swift 문서도 그 플랫폼을 분석했다는 근거로 인정한다.\n */\nexport function validateCartographRetentionInputs(\n documents: readonly BridgeFactsDocument[],\n): void {\n if (documents.some(({ platform }) => platform === 'swift')) return;\n throw new RetentionValidationError(\n 'Retentions for cartograph require at least one swift bridge facts document; '\n + 'run a swift producer for the receiver side.',\n );\n}\n\n/** cartograph 보존 문서를 결정적인 JSON으로 인코딩한다. */\nexport function encodeCartographRetentionsDocument(\n document: CartographRetentionsDocument,\n): string {\n return encodeSortedJson(document);\n}\n\n/** 매치된 브리지 메서드를 cartograph 보존 근거로 바꾼다. */\nexport function createCartographRetentionsDocument(\n joined: BridgeJoinResult,\n generatedAt: string,\n producerVersion: string,\n): CartographRetentionsDocument {\n if (isBridgeJoinDeferred(joined)) {\n throw new RetentionValidationError(\n 'Cannot create retentions from a deferred bridge join.',\n );\n }\n rejectUnresolvedSwiftHandlers(joined);\n const omittedObjectiveCHandlers = countObjectiveCHandlers(joined);\n return {\n format: 'external-retentions',\n version: 0,\n producedBy: { name: 'isthmus', version: producerVersion },\n generatedAt,\n retentions: collectCartographRetentions(joined),\n ...(omittedObjectiveCHandlers === 0 ? {} : { omittedObjectiveCHandlers }),\n };\n}\n\n/** 그래프 밖의 매치도 사라지지 않게 채널·메서드·위치별로 센다. */\nfunction countObjectiveCHandlers(joined: BridgeJoinResult): number {\n const keys = new Set<string>();\n for (const method of joined.matchedMethods) {\n for (const handler of method.handlers) {\n if (handler.sourceLanguage !== 'objective-c') continue;\n keys.add(JSON.stringify([method.target, method.channel, method.method,\n handler.location.path, handler.location.line, handler.location.column]));\n }\n }\n return keys.size;\n}\n\n/**\n * 심볼이 없어 보존 근거로 바꿀 수 없는 매치 Swift 핸들러를 거부한다.\n * sourceLanguage로 확인된 Objective-C 구현은 Swift 그래프의 보존 대상이 아니다.\n *\n * 교환 계약에서 `symbol`은 선택 필드다. 호출자가 있는데도 근거를 만들지 못한\n * 핸들러를 조용히 빼면 cartograph는 그 핸들러를 계속 미사용으로 보고하고,\n * 소비자는 살아 있는 코드를 지운다. 부분 보존 문서 대신 실패를 돌려준다.\n */\nfunction rejectUnresolvedSwiftHandlers(joined: BridgeJoinResult): void {\n const unresolved = new Set<string>();\n for (const method of joined.matchedMethods) {\n for (const handler of method.handlers) {\n if (handler.platform !== 'swift' || handler.sourceLanguage === 'objective-c' || handler.symbol !== undefined) continue;\n const { path, line, column } = handler.location;\n unresolved.add(`${path}\\u0000${line}\\u0000${column}`);\n }\n }\n if (unresolved.size === 0) return;\n throw new RetentionValidationError(\n `Cannot produce retention evidence for ${unresolved.size} matched swift `\n + 'handlers without a symbol; regenerate the swift document with a producer '\n + 'that attaches handler symbols.',\n );\n}\n\n/** 매치별 Dart 호출자와 Swift 심볼을 cartograph 근거로 결합한다. */\nfunction collectCartographRetentions(\n joined: BridgeJoinResult,\n): ExternalRetention[] {\n const retentions: ExternalRetention[] = [];\n const seen = new Set<string>();\n for (const method of joined.matchedMethods) {\n const caller = method.invocations[0];\n if (caller === undefined) continue;\n for (const handler of method.handlers) {\n if (handler.platform !== 'swift' || handler.sourceLanguage === 'objective-c' || handler.symbol === undefined) continue;\n const symbolKey = handler.symbol.usr === undefined\n ? `name:${handler.symbol.qualifiedName}`\n : `usr:${handler.symbol.usr}`;\n const retentionKey = `${symbolKey}\\u0000${method.channel}\\u0000${method.method}`;\n if (seen.has(retentionKey)) continue;\n seen.add(retentionKey);\n retentions.push({\n symbol: handler.symbol,\n reason: 'bridge',\n evidence: {\n channel: method.channel,\n method: method.method,\n caller: {\n platform: caller.platform,\n path: caller.location.path,\n line: caller.location.line,\n },\n },\n });\n }\n }\n return retentions;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "isthmus-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Join bridge facts across cross-platform application boundaries.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"flutter",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"dist",
|
|
30
30
|
"Skills",
|
|
31
31
|
"LICENSE",
|
|
32
|
-
"README.md"
|
|
32
|
+
"README.md",
|
|
33
|
+
"README.ko.md"
|
|
33
34
|
],
|
|
34
35
|
"publishConfig": {
|
|
35
36
|
"access": "public"
|