isthmus-cli 0.4.0 → 0.5.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 +31 -9
- package/README.md +36 -16
- package/dist/cli/atomic-write.d.ts +5 -2
- package/dist/cli/atomic-write.js +10 -3
- package/dist/cli/atomic-write.js.map +1 -1
- package/dist/cli/check-command.d.ts +2 -25
- package/dist/cli/check-command.js +27 -179
- package/dist/cli/check-command.js.map +1 -1
- package/dist/cli/command-support.d.ts +48 -0
- package/dist/cli/command-support.js +144 -0
- package/dist/cli/command-support.js.map +1 -0
- package/dist/cli/diff-command.d.ts +1 -1
- package/dist/cli/diff-command.js +1 -1
- package/dist/cli/diff-command.js.map +1 -1
- package/dist/cli/graph-command.d.ts +1 -1
- package/dist/cli/graph-command.js +15 -14
- package/dist/cli/graph-command.js.map +1 -1
- package/dist/cli/main.js +20 -13
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/parse-arguments.d.ts +18 -0
- package/dist/cli/parse-arguments.js +48 -0
- package/dist/cli/parse-arguments.js.map +1 -0
- package/dist/cli/query-command.d.ts +1 -1
- package/dist/cli/query-command.js +30 -10
- package/dist/cli/query-command.js.map +1 -1
- package/dist/cli/retentions-command.d.ts +1 -1
- package/dist/cli/retentions-command.js +11 -10
- package/dist/cli/retentions-command.js.map +1 -1
- package/dist/exchange/parse.js +12 -3
- package/dist/exchange/parse.js.map +1 -1
- package/dist/join/join.js +6 -1
- package/dist/join/join.js.map +1 -1
- package/dist/report/diff.d.ts +40 -33
- package/dist/report/diff.js +4 -1
- package/dist/report/diff.js.map +1 -1
- package/dist/report/query.js +9 -2
- package/dist/report/query.js.map +1 -1
- package/dist/report/retentions.d.ts +7 -0
- package/dist/report/retentions.js +20 -5
- package/dist/report/retentions.js.map +1 -1
- package/dist/report/sarif.d.ts +9 -2
- package/dist/report/sarif.js +4 -10
- package/dist/report/sarif.js.map +1 -1
- package/package.json +1 -1
package/dist/report/diff.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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"]}
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/report/diff.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAM/C,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEvG,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AA2CtD,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,uEAAuE;cACrE,qCAAqC,OAAO,CAAC,aAAa,gBAAgB;cAC1E,GAAG,MAAM,CAAC,MAAM,4CAA4C;cAC5D,GAAG,OAAO,CAAC,aAAa,iBAAiB,KAAK,CAAC,MAAM,aAAa,CACrE,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,gBAAgB,CAAC,CAAC;IAC1F,MAAM,cAAc,GAAG,UAAU,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACxF,OAAO;QACL,MAAM,EAAE,cAAuB;QAC/B,OAAO,EAAE,CAAU;QACnB,OAAO,EAAE;YACP,YAAY,EAAE,YAAY,CAAC,MAAM;YACjC,cAAc,EAAE,cAAc,CAAC,MAAM;YACrC,gBAAgB,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM;YACvF,kBAAkB,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM;YAC3F,cAAc,EAAE,cAAc,CAAC,MAAM;SACtC;QACD,YAAY;QACZ,cAAc;QACd,gBAAgB;QAChB,cAAc;QACd,WAAW,EAAE;YACX,MAAM,EAAE,SAAS,CAAC,WAAW;YAC7B,KAAK,EAAE,SAAS,CAAC,WAAW;YAC5B,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,WAAW,EAAE,SAAS,CAAC,WAAW,EAAE,aAAa,CAAC;YAC9E,OAAO,EAAE,UAAU,CAAC,SAAS,CAAC,WAAW,EAAE,SAAS,CAAC,WAAW,EAAE,aAAa,CAAC;SACjF;QACD,SAAS,EAAE,EAAE,MAAM,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAAE;KAChF,CAAC;AACJ,CAAC;AAED,qDAAqD;AACrD,SAAS,iBAAiB,CAAC,MAAsC,EAAE,KAAqC;IACtG,MAAM,GAAG,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC;IAClC,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC;QACnD,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,CAAC;YAC1E,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;QAC/C,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;QACtF,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC;YACrE,CAAC,GAAG,CAAC,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,yBAAyB,CACjC,0EAA0E;cACxE,0EAA0E,CAC7E,CAAC;IACJ,CAAC;AACH,CAAC;AAED,8CAA8C;AAC9C,SAAS,iBAAiB,CAAC,IAAoC;IAC7D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AAC/F,CAAC;AAED,yCAAyC;AACzC,SAAS,gBAAgB,CAAC,IAAoC;IAC5D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,GAAG,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;SAC9F,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED,gDAAgD;AAChD,SAAS,UAAU,CAAC,IAA2F;IAC7G,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED,SAAS,aAAa,CAAC,IAAyL;IAC9M,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,IAAI,IAAI,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC;AAC3H,CAAC;AAED,uCAAuC;AACvC,SAAS,UAAU,CAAI,IAAkB,EAAE,KAAmB,EAAE,GAAwB;IACtF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACzC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;SACnD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC","sourcesContent":["import type { BridgeFactsDocument, BridgeTarget } from '../exchange/parse.ts';\nimport { compareStrings } from '../compare.ts';\nimport type {\n BridgeJoinResult,\n JoinLimitation,\n MatchedMethod,\n} from '../join/join.ts';\nimport { BridgeJoinValidationError, isBridgeJoinDeferred, joinBridgeDocuments } from '../join/join.ts';\nimport type { CheckIssue } from './check-report.ts';\nimport { baselineEntryKey } from './baseline.ts';\nimport { createCheckReport } from './check-report.ts';\n\n/** 두 스냅샷의 생산 도구 버전 근거다. */\nexport interface DiffProducerVersion {\n readonly platform: string;\n readonly name: string;\n readonly version: string;\n readonly generatedAt: string;\n}\n\n/**\n * 동일 프로젝트의 전후 관찰을 비교한 isthmus-diff 문서다.\n *\n * added/removed는 키 집합 차이라 소스 줄 이동은 차이가 아니다. introduced와\n * resolved는 논리 이슈 키 기준이라 코드가 바뀌면(-unverified → 본 판정)\n * 해결·추가 한 쌍으로 보인다 — 심각도 상승은 새 관찰이기 때문이다.\n */\nexport interface BridgeDiffDocument {\n readonly format: 'isthmus-diff';\n readonly version: 1;\n readonly summary: {\n readonly addedMethods: number;\n readonly removedMethods: number;\n readonly introducedErrors: number;\n readonly introducedWarnings: number;\n readonly resolvedIssues: number;\n };\n readonly addedMethods: readonly MatchedMethod[];\n readonly removedMethods: readonly MatchedMethod[];\n readonly introducedIssues: readonly CheckIssue[];\n readonly resolvedIssues: readonly CheckIssue[];\n readonly limitations: {\n readonly before: readonly JoinLimitation[];\n readonly after: readonly JoinLimitation[];\n readonly added: readonly JoinLimitation[];\n readonly removed: readonly JoinLimitation[];\n };\n readonly producers: {\n readonly before: readonly DiffProducerVersion[];\n readonly after: readonly DiffProducerVersion[];\n };\n}\n\n/** 동일 프로젝트의 관찰 결과를 비교하며 삭제 안전성이나 rename을 추측하지 않는다. */\nexport function createBridgeDiff(\n before: readonly BridgeFactsDocument[],\n after: readonly BridgeFactsDocument[],\n): BridgeDiffDocument {\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 '\n + `retry. The before inputs observed ${oldJoin.observedFacts} facts across `\n + `${before.length} documents, and the after inputs observed `\n + `${newJoin.observedFacts} facts across ${after.length} documents.`,\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/query.js
CHANGED
|
@@ -94,8 +94,15 @@ function makeQueryResult(target, name, kind, usedBy, dependsOn) {
|
|
|
94
94
|
dependsOn,
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* qualifiedName 구분자(:와 #)와 이스케이프 문자(%)를 가역적인 퍼센트 표기로
|
|
99
|
+
* 바꾼다. 세 문자를 모두 이스케이프하므로 첫 `:`와 `#` 기준으로 나눠 되돌릴 수
|
|
100
|
+
* 있다 — 채널·메서드 이름에 `:`가 들어도 소비자의 분해가 모호해지지 않는다.
|
|
101
|
+
*/
|
|
98
102
|
function encodeSubjectComponent(value) {
|
|
99
|
-
return value
|
|
103
|
+
return value
|
|
104
|
+
.replaceAll('%', '%25')
|
|
105
|
+
.replaceAll('#', '%23')
|
|
106
|
+
.replaceAll(':', '%3A');
|
|
100
107
|
}
|
|
101
108
|
//# sourceMappingURL=query.js.map
|
package/dist/report/query.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/report/query.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/report/query.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BridgeTarget } from '../exchange/parse.ts';\nimport { compareStrings } from '../compare.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/** query가 식별한 채널 또는 메서드 키다. */\nexport interface BridgeQuerySubject {\n readonly name: string;\n readonly qualifiedName: string;\n readonly kind: 'channel' | 'method';\n}\n\n/** 한 브리지 키에서 본 호출 측과 수신 측 증거다. */\nexport interface BridgeQueryResult {\n readonly subject: BridgeQuerySubject;\n readonly usedBy: readonly BridgeEndpoint[];\n readonly dependsOn: readonly BridgeEndpoint[];\n}\n\n/** cartograph query와 같은 상태 외피를 쓰는 브리지 질의 문서다. */\nexport interface BridgeQueryDocument {\n readonly status: 'found' | 'ambiguous' | 'notFound';\n readonly requested: string;\n readonly level: 'bridge';\n readonly limitations: readonly JoinLimitation[];\n readonly result?: BridgeQueryResult;\n readonly candidates?: ReadonlyArray<{\n readonly qualifiedName: string;\n }>;\n}\n\n/** query 문서를 결정적인 JSON 문자열로 인코딩한다. */\nexport function encodeBridgeQuery(document: BridgeQueryDocument): string {\n return encodeSortedJson(document);\n}\n\n/** 조인된 브리지에서 채널 또는 메서드 문자열을 질의한다. */\nexport function createBridgeQuery(\n joined: BridgeJoinResult,\n requested: string,\n): BridgeQueryDocument {\n if (isBridgeJoinDeferred(joined)) {\n throw new Error('Cannot query a deferred bridge join.');\n }\n const results = [...channelResults(joined), ...methodResults(joined)];\n const exact = results.filter(\n ({ subject }) => subject.qualifiedName === requested,\n );\n if (exact.length > 1) return ambiguousQuery(joined, requested, exact);\n if (exact[0] !== undefined) return foundQuery(joined, requested, exact[0]);\n\n const named = results.filter(({ subject }) => subject.name === requested);\n if (named.length > 1) return ambiguousQuery(joined, requested, named);\n if (named[0] !== undefined) return foundQuery(joined, requested, named[0]);\n return {\n status: 'notFound',\n requested,\n level: 'bridge',\n limitations: joined.limitations,\n };\n}\n\n/** 유일하게 식별된 논리 키와 증거를 found 문서로 감싼다. */\nfunction foundQuery(\n joined: BridgeJoinResult,\n requested: string,\n result: BridgeQueryResult,\n): BridgeQueryDocument {\n return {\n status: 'found',\n requested,\n level: 'bridge',\n limitations: joined.limitations,\n result,\n };\n}\n\n/** 여러 논리 키가 같은 요청과 맞을 때 선택 가능한 정규화 이름을 돌려준다. */\nfunction ambiguousQuery(\n joined: BridgeJoinResult,\n requested: string,\n results: readonly BridgeQueryResult[],\n): BridgeQueryDocument {\n return {\n status: 'ambiguous',\n requested,\n level: 'bridge',\n limitations: joined.limitations,\n candidates: results\n .map(({ subject }) => ({ qualifiedName: subject.qualifiedName }))\n .sort((left, right) => compareStrings(left.qualifiedName, right.qualifiedName)),\n };\n}\n\n/** 요청 문자열과 정확히 같은 논리 메서드 결과를 만든다. */\nfunction methodResults(\n joined: BridgeJoinResult,\n): BridgeQueryResult[] {\n const matched = joined.matchedMethods\n .map(({ target, channel, method, invocations, handlers }) =>\n makeMethodResult(target, channel, method, invocations, handlers),\n );\n const unhandled = joined.unhandledInvocations\n .map(({ target, channel, method, invocations }) =>\n makeMethodResult(target, channel, method, invocations, []),\n );\n const handlers = joined.handlersWithoutInvocations\n .map(({ target, channel, method, handlers }) =>\n makeMethodResult(target, channel, method, [], handlers),\n );\n return [...matched, ...unhandled, ...handlers];\n}\n\n/** 메서드 키와 양쪽 증거를 query result로 바꾼다. */\nfunction makeMethodResult(\n target: BridgeTarget,\n channel: string,\n method: string,\n usedBy: readonly BridgeEndpoint[],\n dependsOn: readonly BridgeEndpoint[],\n): BridgeQueryResult {\n return {\n subject: {\n name: method,\n qualifiedName:\n `${target}:${encodeSubjectComponent(channel)}#${encodeSubjectComponent(method)}`,\n kind: 'method',\n },\n usedBy,\n dependsOn,\n };\n}\n\n/** 요청 문자열과 정확히 같은 논리 채널 결과를 만든다. */\nfunction channelResults(\n joined: BridgeJoinResult,\n): BridgeQueryResult[] {\n const matched = joined.matchedChannels\n .map(({ target, channel, creations, registrations }) =>\n makeQueryResult(target, channel, 'channel', creations, registrations),\n );\n const unregistered = joined.unregisteredChannelCreations\n .map(({ target, channel, creations }) =>\n makeQueryResult(target, channel, 'channel', creations, []),\n );\n const registrations = joined.registrationsWithoutCreations\n .map(({ target, channel, registrations }) =>\n makeQueryResult(target, channel, 'channel', [], registrations),\n );\n return [...matched, ...unregistered, ...registrations];\n}\n\n/** 조인 키와 양쪽 증거를 query result 골격으로 바꾼다. */\nfunction makeQueryResult(\n target: BridgeTarget,\n name: string,\n kind: 'channel' | 'method',\n usedBy: readonly BridgeEndpoint[],\n dependsOn: readonly BridgeEndpoint[],\n): BridgeQueryResult {\n return {\n subject: {\n name,\n qualifiedName: `${target}:${encodeSubjectComponent(name)}`,\n kind,\n },\n usedBy,\n dependsOn,\n };\n}\n\n/**\n * qualifiedName 구분자(:와 #)와 이스케이프 문자(%)를 가역적인 퍼센트 표기로\n * 바꾼다. 세 문자를 모두 이스케이프하므로 첫 `:`와 `#` 기준으로 나눠 되돌릴 수\n * 있다 — 채널·메서드 이름에 `:`가 들어도 소비자의 분해가 모호해지지 않는다.\n */\nfunction encodeSubjectComponent(value: string): string {\n return value\n .replaceAll('%', '%25')\n .replaceAll('#', '%23')\n .replaceAll(':', '%3A');\n}\n"]}
|
|
@@ -44,6 +44,13 @@ export interface CartographRetentionsDocument {
|
|
|
44
44
|
}
|
|
45
45
|
/** 근거 하나가 실을 수 있는 호출 위치 상한이다. */
|
|
46
46
|
export declare const MAX_RETENTION_CALLERS = 100;
|
|
47
|
+
/**
|
|
48
|
+
* 문서 전체가 실을 수 있는 호출 위치 총상한이다.
|
|
49
|
+
*
|
|
50
|
+
* 호출자 증거는 (심볼 × 채널 × 메서드)마다 다시 실리므로 건당 상한만으로는 출력이
|
|
51
|
+
* 입력보다 커질 수 있다. 베이스라인의 항목 상한과 같은 자원 거버넌스다.
|
|
52
|
+
*/
|
|
53
|
+
export declare const MAX_RETENTION_CALLER_ENTRIES = 1000000;
|
|
47
54
|
/** 불완전한 조인으로 보존 결정을 만들 수 없음을 나타낸다. */
|
|
48
55
|
export declare class RetentionValidationError extends Error {
|
|
49
56
|
constructor(message: string);
|
|
@@ -2,6 +2,13 @@ import { isBridgeJoinDeferred, } from "../join/join.js";
|
|
|
2
2
|
import { encodeSortedJson } from "./sorted-json.js";
|
|
3
3
|
/** 근거 하나가 실을 수 있는 호출 위치 상한이다. */
|
|
4
4
|
export const MAX_RETENTION_CALLERS = 100;
|
|
5
|
+
/**
|
|
6
|
+
* 문서 전체가 실을 수 있는 호출 위치 총상한이다.
|
|
7
|
+
*
|
|
8
|
+
* 호출자 증거는 (심볼 × 채널 × 메서드)마다 다시 실리므로 건당 상한만으로는 출력이
|
|
9
|
+
* 입력보다 커질 수 있다. 베이스라인의 항목 상한과 같은 자원 거버넌스다.
|
|
10
|
+
*/
|
|
11
|
+
export const MAX_RETENTION_CALLER_ENTRIES = 1_000_000;
|
|
5
12
|
/** 불완전한 조인으로 보존 결정을 만들 수 없음을 나타낸다. */
|
|
6
13
|
export class RetentionValidationError extends Error {
|
|
7
14
|
constructor(message) {
|
|
@@ -83,11 +90,16 @@ function rejectUnresolvedSwiftHandlers(joined) {
|
|
|
83
90
|
function collectCartographRetentions(joined) {
|
|
84
91
|
const retentions = [];
|
|
85
92
|
const seen = new Set();
|
|
93
|
+
let callerEntries = 0;
|
|
86
94
|
for (const method of joined.matchedMethods) {
|
|
87
95
|
if (method.invocations.length === 0)
|
|
88
96
|
continue;
|
|
89
|
-
// 결정적 순서(플랫폼·경로·줄·열)의 첫 호출이 대표 증거다.
|
|
90
|
-
|
|
97
|
+
// 결정적 순서(플랫폼·경로·줄·열)의 첫 호출이 대표 증거다. 상한 밖 호출까지
|
|
98
|
+
// 객체로 만들지 않도록 먼저 자른다.
|
|
99
|
+
const callers = method.invocations
|
|
100
|
+
.slice(0, MAX_RETENTION_CALLERS)
|
|
101
|
+
.map(toRetentionCaller);
|
|
102
|
+
const callersOmitted = method.invocations.length - callers.length;
|
|
91
103
|
const representative = callers[0];
|
|
92
104
|
if (representative === undefined)
|
|
93
105
|
continue;
|
|
@@ -101,8 +113,11 @@ function collectCartographRetentions(joined) {
|
|
|
101
113
|
if (seen.has(retentionKey))
|
|
102
114
|
continue;
|
|
103
115
|
seen.add(retentionKey);
|
|
104
|
-
|
|
105
|
-
|
|
116
|
+
callerEntries += callers.length;
|
|
117
|
+
if (callerEntries > MAX_RETENTION_CALLER_ENTRIES) {
|
|
118
|
+
throw new RetentionValidationError(`Cannot produce retention evidence with more than `
|
|
119
|
+
+ `${MAX_RETENTION_CALLER_ENTRIES} caller entries; narrow the join inputs.`);
|
|
120
|
+
}
|
|
106
121
|
retentions.push({
|
|
107
122
|
symbol: handler.symbol,
|
|
108
123
|
reason: 'bridge',
|
|
@@ -110,7 +125,7 @@ function collectCartographRetentions(joined) {
|
|
|
110
125
|
channel: method.channel,
|
|
111
126
|
method: method.method,
|
|
112
127
|
caller: representative,
|
|
113
|
-
...(
|
|
128
|
+
...(callers.length > 1 ? { callers } : {}),
|
|
114
129
|
...(callersOmitted > 0 ? { callersOmitted } : {}),
|
|
115
130
|
},
|
|
116
131
|
});
|
|
@@ -1 +1 @@
|
|
|
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 /** 대표 호출 위치다. 결정적 순서의 첫 호출이며 옛 소비자가 읽는 필드다. */\n readonly caller: RetentionCaller;\n /**\n * 이 보존의 전체 호출 위치(대표 포함)다. 결정적 순서를 유지하고 호출이 둘 이상일\n * 때만 실는다. 옛 소비자는 이 필드를 모르고 대표 `caller`만 읽는다.\n */\n readonly callers?: readonly RetentionCaller[];\n /** 상한 때문에 `callers`에 실지 못한 호출 수다. 0이면 실지 않는다. */\n readonly callersOmitted?: number;\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 const MAX_RETENTION_CALLERS = 100;\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 if (method.invocations.length === 0) continue;\n // 결정적 순서(플랫폼·경로·줄·열)의 첫 호출이 대표 증거다.\n const callers = method.invocations.map(toRetentionCaller);\n const representative = callers[0];\n if (representative === 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 const capped = callers.slice(0, MAX_RETENTION_CALLERS);\n const callersOmitted = callers.length - capped.length;\n retentions.push({\n symbol: handler.symbol,\n reason: 'bridge',\n evidence: {\n channel: method.channel,\n method: method.method,\n caller: representative,\n ...(capped.length > 1 ? { callers: capped } : {}),\n ...(callersOmitted > 0 ? { callersOmitted } : {}),\n },\n });\n }\n }\n return retentions;\n}\n\n/** 증거 끝점을 external-retentions의 호출자 형태로 바꾼다. */\nfunction toRetentionCaller(endpoint: {\n readonly platform: BridgePlatform;\n readonly location: { readonly path: string; readonly line: number };\n}): RetentionCaller {\n return {\n platform: endpoint.platform,\n path: endpoint.location.path,\n line: endpoint.location.line,\n };\n}\n"]}
|
|
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 /** 대표 호출 위치다. 결정적 순서의 첫 호출이며 옛 소비자가 읽는 필드다. */\n readonly caller: RetentionCaller;\n /**\n * 이 보존의 전체 호출 위치(대표 포함)다. 결정적 순서를 유지하고 호출이 둘 이상일\n * 때만 실는다. 옛 소비자는 이 필드를 모르고 대표 `caller`만 읽는다.\n */\n readonly callers?: readonly RetentionCaller[];\n /** 상한 때문에 `callers`에 실지 못한 호출 수다. 0이면 실지 않는다. */\n readonly callersOmitted?: number;\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 const MAX_RETENTION_CALLERS = 100;\n\n/**\n * 문서 전체가 실을 수 있는 호출 위치 총상한이다.\n *\n * 호출자 증거는 (심볼 × 채널 × 메서드)마다 다시 실리므로 건당 상한만으로는 출력이\n * 입력보다 커질 수 있다. 베이스라인의 항목 상한과 같은 자원 거버넌스다.\n */\nexport const MAX_RETENTION_CALLER_ENTRIES = 1_000_000;\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 let callerEntries = 0;\n for (const method of joined.matchedMethods) {\n if (method.invocations.length === 0) continue;\n // 결정적 순서(플랫폼·경로·줄·열)의 첫 호출이 대표 증거다. 상한 밖 호출까지\n // 객체로 만들지 않도록 먼저 자른다.\n const callers = method.invocations\n .slice(0, MAX_RETENTION_CALLERS)\n .map(toRetentionCaller);\n const callersOmitted = method.invocations.length - callers.length;\n const representative = callers[0];\n if (representative === 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 callerEntries += callers.length;\n if (callerEntries > MAX_RETENTION_CALLER_ENTRIES) {\n throw new RetentionValidationError(\n `Cannot produce retention evidence with more than `\n + `${MAX_RETENTION_CALLER_ENTRIES} caller entries; narrow the join inputs.`,\n );\n }\n retentions.push({\n symbol: handler.symbol,\n reason: 'bridge',\n evidence: {\n channel: method.channel,\n method: method.method,\n caller: representative,\n ...(callers.length > 1 ? { callers } : {}),\n ...(callersOmitted > 0 ? { callersOmitted } : {}),\n },\n });\n }\n }\n return retentions;\n}\n\n/** 증거 끝점을 external-retentions의 호출자 형태로 바꾼다. */\nfunction toRetentionCaller(endpoint: {\n readonly platform: BridgePlatform;\n readonly location: { readonly path: string; readonly line: number };\n}): RetentionCaller {\n return {\n platform: endpoint.platform,\n path: endpoint.location.path,\n line: endpoint.location.line,\n };\n}\n"]}
|
package/dist/report/sarif.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import type { CheckIssueCode, CheckReport } from './check-report.ts';
|
|
2
|
+
/** 논리 이슈 키를 안정 지문으로 바꾸는 주입 경계다. 해싱은 cli 계층이 담당한다. */
|
|
3
|
+
export type IssueFingerprint = (issue: {
|
|
4
|
+
readonly code: string;
|
|
5
|
+
readonly target: string;
|
|
6
|
+
readonly channel: string;
|
|
7
|
+
readonly method?: string;
|
|
8
|
+
}) => string;
|
|
2
9
|
/** GitHub code scanning이 받아들이는 SARIF 2.1.0 스키마 식별자다. */
|
|
3
10
|
export declare const sarifSchema = "https://json.schemastore.org/sarif-2.1.0.json";
|
|
4
11
|
/** 도구 소개 문서의 안정적인 위치다. */
|
|
@@ -63,12 +70,12 @@ export interface SarifLog {
|
|
|
63
70
|
}>;
|
|
64
71
|
}
|
|
65
72
|
/** check 보고서를 SARIF 2.1.0 로그로 바꾼다. */
|
|
66
|
-
export declare function createSarifLog(report: CheckReport, toolVersion
|
|
73
|
+
export declare function createSarifLog(report: CheckReport, toolVersion: string | undefined, issueFingerprint: IssueFingerprint): SarifLog;
|
|
67
74
|
/** SARIF 로그를 결정적인 JSON 문자열로 인코딩한다. */
|
|
68
75
|
export declare function encodeSarifLog(log: SarifLog): string;
|
|
69
76
|
/**
|
|
70
77
|
* 경로를 URI 참조로 만든다. 구분자 `/`는 보존하고 각 세그먼트를 RFC 3986으로
|
|
71
|
-
* 인코딩해 공백·`#`·비ASCII 파일명이 GitHub 업로드를
|
|
78
|
+
* 인코딩해 공백·`#`·비ASCII 파일명이 GitHub 업로드를 깨지지 않게 한다.
|
|
72
79
|
*/
|
|
73
80
|
export declare function sarifUri(path: string): string;
|
|
74
81
|
export {};
|
package/dist/report/sarif.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { baselineEntryKey } from "./baseline.js";
|
|
3
1
|
import { checkIssueCodes } from "./check-report.js";
|
|
4
2
|
import { encodeSortedJson } from "./sorted-json.js";
|
|
5
3
|
/** GitHub code scanning이 받아들이는 SARIF 2.1.0 스키마 식별자다. */
|
|
@@ -16,7 +14,7 @@ const ruleDescriptions = {
|
|
|
16
14
|
'handler-without-invocation': 'A receiver-side bridge method handler has no matching caller-side invocation.',
|
|
17
15
|
};
|
|
18
16
|
/** check 보고서를 SARIF 2.1.0 로그로 바꾼다. */
|
|
19
|
-
export function createSarifLog(report, toolVersion) {
|
|
17
|
+
export function createSarifLog(report, toolVersion, issueFingerprint) {
|
|
20
18
|
const rules = [...checkIssueCodes].sort().map((id) => ({
|
|
21
19
|
id,
|
|
22
20
|
shortDescription: { text: ruleDescriptions[id] },
|
|
@@ -34,7 +32,7 @@ export function createSarifLog(report, toolVersion) {
|
|
|
34
32
|
rules,
|
|
35
33
|
},
|
|
36
34
|
},
|
|
37
|
-
results: report.issues.map((issue) => sarifResult(issue, ruleIndex)),
|
|
35
|
+
results: report.issues.map((issue) => sarifResult(issue, ruleIndex, issueFingerprint)),
|
|
38
36
|
}],
|
|
39
37
|
};
|
|
40
38
|
}
|
|
@@ -43,7 +41,7 @@ export function encodeSarifLog(log) {
|
|
|
43
41
|
return encodeSortedJson(log);
|
|
44
42
|
}
|
|
45
43
|
/** 논리 이슈 하나를 규칙 참조와 물리 근거를 갖춘 결과로 바꾼다. */
|
|
46
|
-
function sarifResult(issue, ruleIndex) {
|
|
44
|
+
function sarifResult(issue, ruleIndex, issueFingerprint) {
|
|
47
45
|
const [primary, ...related] = issue.evidence;
|
|
48
46
|
if (primary === undefined) {
|
|
49
47
|
throw new Error('Cannot create a SARIF result without evidence.');
|
|
@@ -93,13 +91,9 @@ function physicalLocation(endpoint) {
|
|
|
93
91
|
}
|
|
94
92
|
/**
|
|
95
93
|
* 경로를 URI 참조로 만든다. 구분자 `/`는 보존하고 각 세그먼트를 RFC 3986으로
|
|
96
|
-
* 인코딩해 공백·`#`·비ASCII 파일명이 GitHub 업로드를
|
|
94
|
+
* 인코딩해 공백·`#`·비ASCII 파일명이 GitHub 업로드를 깨지지 않게 한다.
|
|
97
95
|
*/
|
|
98
96
|
export function sarifUri(path) {
|
|
99
97
|
return path.split('/').map(encodeURIComponent).join('/');
|
|
100
98
|
}
|
|
101
|
-
/** 베이스라인과 같은 논리 키의 안정 지문으로 소스 줄 이동에도 중복을 막는다. */
|
|
102
|
-
function issueFingerprint(issue) {
|
|
103
|
-
return createHash('sha256').update(baselineEntryKey(issue)).digest('hex');
|
|
104
|
-
}
|
|
105
99
|
//# sourceMappingURL=sarif.js.map
|
package/dist/report/sarif.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sarif.js","sourceRoot":"","sources":["../../src/report/sarif.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\n\nimport { baselineEntryKey } from './baseline.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/** GitHub code scanning이 받아들이는 SARIF 2.1.0 스키마 식별자다. */\nexport const sarifSchema = 'https://json.schemastore.org/sarif-2.1.0.json';\n\n/** 도구 소개 문서의 안정적인 위치다. */\nexport const sarifInformationUri = 'https://github.com/ictechgy/isthmus';\n\n/** SARIF 물리 위치다. 경로는 프로젝트 루트 기준 상대 경로를 그대로 쓴다. */\ninterface SarifPhysicalLocation {\n readonly artifactLocation: { readonly uri: string };\n readonly region: { readonly startLine: number; readonly startColumn: number };\n}\n\n/** 결과 하나가 위치(규칙·수준·메시지·근거)를 다 담는 형태다. */\ninterface SarifResult {\n readonly ruleId: CheckIssueCode;\n readonly ruleIndex: number;\n readonly level: 'error' | 'warning';\n readonly message: { readonly text: string };\n readonly locations: ReadonlyArray<{\n readonly physicalLocation: SarifPhysicalLocation;\n }>;\n readonly relatedLocations?: ReadonlyArray<{\n readonly id: number;\n readonly physicalLocation: SarifPhysicalLocation;\n }>;\n readonly suppressions?: ReadonlyArray<{\n readonly kind: 'external';\n readonly status: 'accepted';\n }>;\n readonly properties: {\n readonly target: string;\n readonly channel: string;\n readonly method?: string;\n };\n readonly partialFingerprints: { readonly isthmusIssueV1: string };\n}\n\n/** check가 보고하는 진단 종류의 SARIF 규칙 문구다. */\nconst ruleDescriptions: Record<CheckIssueCode, string> = {\n 'unhandled-invocation':\n 'A caller-side bridge method invocation has no matching handler on any receiver-side document.',\n 'unhandled-invocation-unverified':\n 'A caller-side bridge method invocation has no matching handler, and a receiver-side analysis gap may be hiding it.',\n 'unregistered-channel-creation':\n 'A caller-side bridge channel creation has no matching registration on any receiver-side document.',\n 'unregistered-channel-creation-unverified':\n 'A caller-side bridge channel creation has no matching registration, and a receiver-side analysis gap may be hiding it.',\n 'registration-without-creation':\n 'A receiver-side channel registration has no matching caller-side channel creation.',\n 'handler-without-invocation':\n 'A receiver-side bridge method handler has no matching caller-side invocation.',\n};\n\n/** SARIF 2.1.0 로그 문서다. 결정적 정렬로 인코딩한다. */\nexport interface SarifLog {\n readonly $schema: string;\n readonly version: '2.1.0';\n readonly runs: ReadonlyArray<{\n readonly tool: {\n readonly driver: {\n readonly name: 'isthmus';\n readonly informationUri: string;\n readonly version?: string;\n readonly rules: ReadonlyArray<{\n readonly id: CheckIssueCode;\n readonly shortDescription: { readonly text: string };\n }>;\n };\n };\n readonly results: readonly SarifResult[];\n }>;\n}\n\n/** check 보고서를 SARIF 2.1.0 로그로 바꾼다. */\nexport function createSarifLog(\n report: CheckReport,\n toolVersion?: string,\n): SarifLog {\n const rules = [...checkIssueCodes].sort().map((id) => ({\n id,\n shortDescription: { text: ruleDescriptions[id] },\n }));\n const ruleIndex = new Map(rules.map(({ id }, index) => [id, index]));\n return {\n $schema: sarifSchema,\n version: '2.1.0',\n runs: [{\n tool: {\n driver: {\n name: 'isthmus',\n informationUri: sarifInformationUri,\n ...(toolVersion ? { version: toolVersion } : {}),\n rules,\n },\n },\n results: report.issues.map((issue) => sarifResult(issue, ruleIndex)),\n }],\n };\n}\n\n/** SARIF 로그를 결정적인 JSON 문자열로 인코딩한다. */\nexport function encodeSarifLog(log: SarifLog): string {\n return encodeSortedJson(log);\n}\n\n/** 논리 이슈 하나를 규칙 참조와 물리 근거를 갖춘 결과로 바꾼다. */\nfunction sarifResult(\n issue: CheckIssue,\n ruleIndex: Map<string, number>,\n): SarifResult {\n const [primary, ...related] = issue.evidence;\n if (primary === undefined) {\n throw new Error('Cannot create a SARIF result without evidence.');\n }\n const index = ruleIndex.get(issue.code);\n if (index === undefined) {\n throw new Error('Cannot create a SARIF result for an unknown rule.');\n }\n const subject = issue.method === undefined\n ? `${issue.code} on channel '${issue.channel}'`\n : `${issue.code} on channel '${issue.channel}' for method '${issue.method}'`;\n return {\n ruleId: issue.code,\n ruleIndex: index,\n level: issue.severity,\n message: { text: subject },\n locations: [{ physicalLocation: physicalLocation(primary) }],\n ...(related.length === 0 ? {} : {\n relatedLocations: related.map((endpoint, order) => ({\n id: order + 1,\n physicalLocation: physicalLocation(endpoint),\n })),\n }),\n ...(issue.suppressed === true\n ? { suppressions: [{ kind: 'external' as const, status: 'accepted' as const }] }\n : {}),\n properties: {\n target: issue.target,\n channel: issue.channel,\n ...(issue.method === undefined ? {} : { method: issue.method }),\n },\n partialFingerprints: {\n isthmusIssueV1: issueFingerprint(issue),\n },\n };\n}\n\n/** 증거 끝점을 SARIF 물리 위치로 바꾼다. 줄·열은 계약과 같은 1 기반이다. */\nfunction physicalLocation(endpoint: {\n readonly location: { readonly path: string; readonly line: number; readonly column: number };\n}): SarifPhysicalLocation {\n const { line, column } = endpoint.location;\n if (line < 1 || column < 1) {\n throw new Error('Cannot create a SARIF region before the first line or column.');\n }\n return {\n artifactLocation: { uri: sarifUri(endpoint.location.path) },\n region: { startLine: line, startColumn: column },\n };\n}\n\n/**\n * 경로를 URI 참조로 만든다. 구분자 `/`는 보존하고 각 세그먼트를 RFC 3986으로\n * 인코딩해 공백·`#`·비ASCII 파일명이 GitHub 업로드를 깨지 않게 한다.\n */\nexport function sarifUri(path: string): string {\n return path.split('/').map(encodeURIComponent).join('/');\n}\n\n/** 베이스라인과 같은 논리 키의 안정 지문으로 소스 줄 이동에도 중복을 막는다. */\nfunction issueFingerprint(issue: {\n readonly code: string;\n readonly target: string;\n readonly channel: string;\n readonly method?: string;\n}): string {\n return createHash('sha256').update(baselineEntryKey(issue)).digest('hex');\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sarif.js","sourceRoot":"","sources":["../../src/report/sarif.ts"],"names":[],"mappings":"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","sourcesContent":["import 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/** 논리 이슈 키를 안정 지문으로 바꾸는 주입 경계다. 해싱은 cli 계층이 담당한다. */\nexport type IssueFingerprint = (issue: {\n readonly code: string;\n readonly target: string;\n readonly channel: string;\n readonly method?: string;\n}) => string;\n\n/** GitHub code scanning이 받아들이는 SARIF 2.1.0 스키마 식별자다. */\nexport const sarifSchema = 'https://json.schemastore.org/sarif-2.1.0.json';\n\n/** 도구 소개 문서의 안정적인 위치다. */\nexport const sarifInformationUri = 'https://github.com/ictechgy/isthmus';\n\n/** SARIF 물리 위치다. 경로는 프로젝트 루트 기준 상대 경로를 그대로 쓴다. */\ninterface SarifPhysicalLocation {\n readonly artifactLocation: { readonly uri: string };\n readonly region: { readonly startLine: number; readonly startColumn: number };\n}\n\n/** 결과 하나가 위치(규칙·수준·메시지·근거)를 다 담는 형태다. */\ninterface SarifResult {\n readonly ruleId: CheckIssueCode;\n readonly ruleIndex: number;\n readonly level: 'error' | 'warning';\n readonly message: { readonly text: string };\n readonly locations: ReadonlyArray<{\n readonly physicalLocation: SarifPhysicalLocation;\n }>;\n readonly relatedLocations?: ReadonlyArray<{\n readonly id: number;\n readonly physicalLocation: SarifPhysicalLocation;\n }>;\n readonly suppressions?: ReadonlyArray<{\n readonly kind: 'external';\n readonly status: 'accepted';\n }>;\n readonly properties: {\n readonly target: string;\n readonly channel: string;\n readonly method?: string;\n };\n readonly partialFingerprints: { readonly isthmusIssueV1: string };\n}\n\n/** check가 보고하는 진단 종류의 SARIF 규칙 문구다. */\nconst ruleDescriptions: Record<CheckIssueCode, string> = {\n 'unhandled-invocation':\n 'A caller-side bridge method invocation has no matching handler on any receiver-side document.',\n 'unhandled-invocation-unverified':\n 'A caller-side bridge method invocation has no matching handler, and a receiver-side analysis gap may be hiding it.',\n 'unregistered-channel-creation':\n 'A caller-side bridge channel creation has no matching registration on any receiver-side document.',\n 'unregistered-channel-creation-unverified':\n 'A caller-side bridge channel creation has no matching registration, and a receiver-side analysis gap may be hiding it.',\n 'registration-without-creation':\n 'A receiver-side channel registration has no matching caller-side channel creation.',\n 'handler-without-invocation':\n 'A receiver-side bridge method handler has no matching caller-side invocation.',\n};\n\n/** SARIF 2.1.0 로그 문서다. 결정적 정렬로 인코딩한다. */\nexport interface SarifLog {\n readonly $schema: string;\n readonly version: '2.1.0';\n readonly runs: ReadonlyArray<{\n readonly tool: {\n readonly driver: {\n readonly name: 'isthmus';\n readonly informationUri: string;\n readonly version?: string;\n readonly rules: ReadonlyArray<{\n readonly id: CheckIssueCode;\n readonly shortDescription: { readonly text: string };\n }>;\n };\n };\n readonly results: readonly SarifResult[];\n }>;\n}\n\n/** check 보고서를 SARIF 2.1.0 로그로 바꾼다. */\nexport function createSarifLog(\n report: CheckReport,\n toolVersion: string | undefined,\n issueFingerprint: IssueFingerprint,\n): SarifLog {\n const rules = [...checkIssueCodes].sort().map((id) => ({\n id,\n shortDescription: { text: ruleDescriptions[id] },\n }));\n const ruleIndex = new Map(rules.map(({ id }, index) => [id, index]));\n return {\n $schema: sarifSchema,\n version: '2.1.0',\n runs: [{\n tool: {\n driver: {\n name: 'isthmus',\n informationUri: sarifInformationUri,\n ...(toolVersion ? { version: toolVersion } : {}),\n rules,\n },\n },\n results: report.issues.map((issue) =>\n sarifResult(issue, ruleIndex, issueFingerprint)\n ),\n }],\n };\n}\n\n/** SARIF 로그를 결정적인 JSON 문자열로 인코딩한다. */\nexport function encodeSarifLog(log: SarifLog): string {\n return encodeSortedJson(log);\n}\n\n/** 논리 이슈 하나를 규칙 참조와 물리 근거를 갖춘 결과로 바꾼다. */\nfunction sarifResult(\n issue: CheckIssue,\n ruleIndex: Map<string, number>,\n issueFingerprint: IssueFingerprint,\n): SarifResult {\n const [primary, ...related] = issue.evidence;\n if (primary === undefined) {\n throw new Error('Cannot create a SARIF result without evidence.');\n }\n const index = ruleIndex.get(issue.code);\n if (index === undefined) {\n throw new Error('Cannot create a SARIF result for an unknown rule.');\n }\n const subject = issue.method === undefined\n ? `${issue.code} on channel '${issue.channel}'`\n : `${issue.code} on channel '${issue.channel}' for method '${issue.method}'`;\n return {\n ruleId: issue.code,\n ruleIndex: index,\n level: issue.severity,\n message: { text: subject },\n locations: [{ physicalLocation: physicalLocation(primary) }],\n ...(related.length === 0 ? {} : {\n relatedLocations: related.map((endpoint, order) => ({\n id: order + 1,\n physicalLocation: physicalLocation(endpoint),\n })),\n }),\n ...(issue.suppressed === true\n ? { suppressions: [{ kind: 'external' as const, status: 'accepted' as const }] }\n : {}),\n properties: {\n target: issue.target,\n channel: issue.channel,\n ...(issue.method === undefined ? {} : { method: issue.method }),\n },\n partialFingerprints: {\n isthmusIssueV1: issueFingerprint(issue),\n },\n };\n}\n\n/** 증거 끝점을 SARIF 물리 위치로 바꾼다. 줄·열은 계약과 같은 1 기반이다. */\nfunction physicalLocation(endpoint: {\n readonly location: { readonly path: string; readonly line: number; readonly column: number };\n}): SarifPhysicalLocation {\n const { line, column } = endpoint.location;\n if (line < 1 || column < 1) {\n throw new Error('Cannot create a SARIF region before the first line or column.');\n }\n return {\n artifactLocation: { uri: sarifUri(endpoint.location.path) },\n region: { startLine: line, startColumn: column },\n };\n}\n\n/**\n * 경로를 URI 참조로 만든다. 구분자 `/`는 보존하고 각 세그먼트를 RFC 3986으로\n * 인코딩해 공백·`#`·비ASCII 파일명이 GitHub 업로드를 깨지지 않게 한다.\n */\nexport function sarifUri(path: string): string {\n return path.split('/').map(encodeURIComponent).join('/');\n}\n"]}
|