isthmus-cli 0.1.5 → 0.1.7

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.md CHANGED
@@ -22,10 +22,12 @@ isthmus는 조사한 도구들이 언어별로 나눠 보던 이 교차 경계
22
22
 
23
23
  ## 상태
24
24
 
25
- **0.1.5.** bridge-facts 버전 1 파서와 `check`, `query`, `graph`, `diff`,
25
+ **0.1.7.** bridge-facts 버전 1 파서와 `check`, `query`, `graph`, `diff`,
26
26
  cartograph용 외부 보존 근거 왕복을 구현했다. 외부 입력·혼합 target·그래프 크기와
27
27
  Dart/Swift Phase 0 추출 경계를 fail-closed로 강화했고, 조인하지 못한 사실과 근거를
28
- 만들지 못한 보존 대상이 조용히 사라지지 않도록 소비자 쪽에서 다시 센다. 다음 단계는
28
+ 만들지 못한 보존 대상이 조용히 사라지지 않도록 소비자 쪽에서 다시 세고, 수신 측이 신고한
29
+ 분석 공백은 불일치가 아니라 판정 불가로 보고한다. 한계는 신고 문서의 target으로 귀속되어
30
+ 공백 완화가 다른 target의 진단으로 번지지 않는다. 다음 단계는
29
31
  실제 Flutter 앱 도그푸딩과 React Native 지원이다.
30
32
 
31
33
  정식 producer는 cartograph 0.5.3 이상과 dartograph 0.1.1 이상이다. 두 도구의 실제 출력과
@@ -160,17 +162,23 @@ isthmus graph dart-bridges.json swift-bridges.json --format mermaid
160
162
  Objective-C로 쓰인 플러그인에서 cartograph는 `objective-c-sources:`를 신고하고 핸들러 사실을
161
163
  내지 못한다. 이때 "핸들러 없는 호출"을 error로 단정하면 이 도구가 없애려던 오탐을 이 도구가
162
164
  만든다. 사실과 증거는 그대로 보고하되 `--strict`를 실패시키지 않는다. 공백의 종류는 구분해서,
163
- 이름이 리터럴이 아닌 채널 등록은 채널 진단만 낮추고 메서드 진단은 낮추지 않는다. 호출
164
- 한계는 네이티브 코드를 가리지 않으므로 심각도에 영향을 주지 않으며, 알려지지 않은 한계
165
- 문구는 공백으로 해석하지 않는다.
165
+ 이름이 리터럴이 아닌 채널 등록은 채널 진단만 낮추고 메서드 진단은 낮추지 않는다. 완화 단위는
166
+ 진단의 target이다. 사실은 target별로만 조인되므로 다른 target 수신 문서가 신고한 공백은 현재
167
+ target의 진단을 낮추지 않고, 사실이 없는 수신 문서의 공백은 무엇이 가려졌는지 귀속시킬 수
168
+ 없어 모든 target에 적용한다. 호출 측 한계는 네이티브 코드를 가리지 않으므로 심각도에 영향을
169
+ 주지 않으며, 알려지지 않은 한계 문구는 공백으로 해석하지 않는다.
166
170
 
167
171
  모든 이슈는 관찰된 위치를 `evidence`로 제공한다. 동적 이름, 해석하지 못한 receiver나
168
172
  handler 본문, USR 누락, 입력 생성 시각 차이, 혼합 target은 `limitations`에 출처와 함께
169
173
  남긴다. 이 도구는 삭제 가능 여부를 판정하지 않는다.
170
174
 
175
+ isthmus 출력 문서는 버전 1 안에서 필드 추가나 새 이슈 code를 호환 변경으로 다룬다.
176
+ 기존 필드의 의미를 바꾸거나 제거할 때 문서 버전을 올린다.
177
+
171
178
  `limitations`에는 생산자가 신고한 한계와 isthmus가 직접 센 한계가 함께 들어간다.
172
- `tool`이 `isthmus`인 항목은 조인 단계에서 관찰한 것이다. 조인하지 못한 사실은 생산자의
173
- 신고나 그 개수와 무관하게 플랫폼별로 다시 센다.
179
+ 각 항목은 `platform`·`target`·`tool`로 출처와 귀속을 밝히고, `tool`이 `isthmus`인 항목은
180
+ 조인 단계에서 관찰한 것이다. 조인하지 못한 사실은 생산자의 신고나 그 개수와 무관하게
181
+ 플랫폼·target별로 다시 센다.
174
182
 
175
183
  - `unjoined-dynamic-channels`: 이름이 리터럴이 아닌 채널 생성·등록 사실
176
184
  - `unjoined-dynamic-methods`: 이름이 리터럴이 아닌 호출·핸들러 사실
@@ -46,9 +46,17 @@ export interface HandlerWithoutInvocation {
46
46
  readonly method: string;
47
47
  readonly handlers: readonly BridgeEndpoint[];
48
48
  }
49
- /** 생산 문서가 밝힌 분석 한계와 출처다. */
49
+ /**
50
+ * 생산 문서가 밝힌 분석 한계와 출처다.
51
+ *
52
+ * `target`은 이 한계를 신고한 문서의 브리지 메커니즘이다. 사실은 target별로만
53
+ * 조인되므로, 보고서는 이 값으로 한계를 해당 target의 진단에만 귀속시킨다.
54
+ * 사실이 없는 문서의 한계, 선언한 target을 신뢰할 수 없는 mixed-targets 문서의
55
+ * 한계, 교차 입력 한계는 어느 target이 가려졌는지 특정할 수 없어 `null`이다.
56
+ */
50
57
  export interface JoinLimitation {
51
58
  readonly platform: BridgePlatform | 'cross-platform';
59
+ readonly target: BridgeTarget | null;
52
60
  readonly tool: string;
53
61
  readonly message: string;
54
62
  }
package/dist/join/join.js CHANGED
@@ -92,6 +92,16 @@ function validatePlatformComposition(documents) {
92
92
  function hasMixedTargets(document) {
93
93
  return document.limitations.some((message) => /mixed-targets\b/i.test(message));
94
94
  }
95
+ /**
96
+ * 한계를 귀속시킬 target을 결정한다.
97
+ *
98
+ * mixed-targets 문서의 선언 target은 대표값일 뿐 사실별 메커니즘을 복원할 수
99
+ * 없으므로, 그 문서의 한계와 조인 제외 사실 계수는 귀속 없이(null) 남긴다.
100
+ * 귀속을 잃어도 관찰 자체는 보존해야 한다.
101
+ */
102
+ function limitationTarget(document) {
103
+ return hasMixedTargets(document) ? null : document.target;
104
+ }
95
105
  /** 안전하게 조인을 보류하면서 입력 한계만 전달한다. */
96
106
  function emptyJoinResult(limitations) {
97
107
  return {
@@ -109,6 +119,7 @@ function emptyJoinResult(limitations) {
109
119
  function collectLimitations(documents) {
110
120
  const limitations = documents.flatMap((document) => document.limitations.map((message) => ({
111
121
  platform: document.platform,
122
+ target: limitationTarget(document),
112
123
  tool: document.tool.name,
113
124
  message,
114
125
  })));
@@ -133,32 +144,54 @@ function unjoinedFactLimitations(documents) {
133
144
  ...unjoinedLimitations(documents, isUnattributedHandler, 'unjoined-unattributed-handlers', 'method handler facts without a channel'),
134
145
  ];
135
146
  }
136
- /** 조건에 맞는 사실 수를 플랫폼별로 세어 한계 문장으로 바꾼다. */
147
+ /** 조건에 맞는 사실 수를 플랫폼·target별로 세어 한계 문장으로 바꾼다. */
137
148
  function unjoinedLimitations(documents, matchesFact, prefix, subject) {
138
- return [...countFactsByPlatform(documents, matchesFact)].map(([platform, count]) => ({
149
+ return countFactsByPlatformTarget(documents, matchesFact).map(({ platform, target, count }) => ({
139
150
  platform,
151
+ target,
140
152
  tool: 'isthmus',
141
153
  message: `${prefix}: ${count} ${subject} were not joined`,
142
154
  }));
143
155
  }
144
- /** 주어진 조건의 서로 다른 사실 수를 생산 플랫폼별로 모은다. */
145
- function countFactsByPlatform(documents, matchesFact) {
156
+ /**
157
+ * 주어진 조건의 서로 다른 사실 수를 생산 플랫폼·target별로 모은다.
158
+ *
159
+ * 사실은 target별로만 조인되므로 다른 target의 사실을 한 한계로 합산하면
160
+ * 관찰 공백의 귀속이 사라진다. mixed-targets 문서의 사실은 귀속 없이(null)
161
+ * 센다. 사실이 없는 문서는 교환 계약상 target이 null이고 사실도 없으므로
162
+ * 여기에서 자연스럽게 제외된다.
163
+ */
164
+ function countFactsByPlatformTarget(documents, matchesFact) {
146
165
  const distinctFacts = new Map();
147
166
  for (const document of documents) {
167
+ const target = limitationTarget(document);
168
+ // BridgeTarget은 닫힌 어휘라 문자열화된 null과 충돌하지 않는다.
169
+ const groupKey = `${document.platform}\u0000${target}`;
148
170
  for (const fact of document.facts) {
149
171
  if (!matchesFact(fact))
150
172
  continue;
151
- const keys = distinctFacts.get(document.platform) ?? new Set();
152
- distinctFacts.set(document.platform, keys.add(unjoinedFactKey(fact)));
173
+ const group = distinctFacts.get(groupKey) ?? {
174
+ platform: document.platform,
175
+ target,
176
+ keys: new Set(),
177
+ };
178
+ group.keys.add(unjoinedFactKey(fact));
179
+ distinctFacts.set(groupKey, group);
153
180
  }
154
181
  }
155
- return new Map([...distinctFacts].map(([platform, keys]) => [platform, keys.size]));
182
+ return [...distinctFacts.values()].map(({ platform, target, keys }) => ({
183
+ platform,
184
+ target,
185
+ count: keys.size,
186
+ }));
156
187
  }
157
188
  /**
158
- * 증거 dedup과 같게 같은 위치의 중복 사실을 한 번만 세는 키를 만든다.
189
+ * 같은 위치의 중복 사실을 한 번만 세는 키를 만든다.
159
190
  *
160
- * `channel`은 null일 있어 구분자 결합 대신 JSON으로 만든다. 그래야 미귀속
161
- * 핸들러와 이름이 `null`인 채널의 사실이 같은 키로 합쳐지지 않는다.
191
+ * 위치 기준은 증거 dedup과 같지만 symbol은 비교하지 않아, 같은 위치의 서로
192
+ * 다른 symbol은 하나로 센다. `channel`은 null 있어 구분자 결합 대신
193
+ * JSON으로 만든다. 그래야 미귀속 핸들러와 이름이 `null`인 채널의 사실이
194
+ * 같은 키로 합쳐지지 않는다.
162
195
  */
163
196
  function unjoinedFactKey(fact) {
164
197
  const { path, line, column } = fact.location;
@@ -206,16 +239,28 @@ function freshnessLimitation(documents) {
206
239
  const hours = Math.round(difference / millisecondsPerHour);
207
240
  return {
208
241
  platform: 'cross-platform',
242
+ target: null,
209
243
  tool: 'isthmus',
210
244
  message: `input-freshness: bridge documents differ by ${hours} hours`,
211
245
  };
212
246
  }
213
- /** limitation을 플랫폼·도구·문장 순으로 고정한다. */
247
+ /** limitation을 플랫폼·target·도구·문장 순으로 고정한다. */
214
248
  function compareLimitations(left, right) {
215
249
  return (compareStrings(left.platform, right.platform) ||
250
+ compareTargets(left.target, right.target) ||
216
251
  compareStrings(left.tool, right.tool) ||
217
252
  compareStrings(left.message, right.message));
218
253
  }
254
+ /** target을 문자열 순으로 고정하고 귀속이 없는(null) 한계를 뒤에 둔다. */
255
+ function compareTargets(left, right) {
256
+ if (left === right)
257
+ return 0;
258
+ if (left === null)
259
+ return 1;
260
+ if (right === null)
261
+ return -1;
262
+ return compareStrings(left, right);
263
+ }
219
264
  /** 메서드 키별로 호출과 핸들러 증거를 모은다. */
220
265
  function collectMethodGroups(documents) {
221
266
  const groups = new Map();
@@ -1 +1 @@
1
- {"version":3,"file":"join.js","sourceRoot":"","sources":["../../src/join/join.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n BridgeFact,\n BridgeFactsDocument,\n BridgeLocation,\n BridgePlatform,\n BridgeSymbol,\n BridgeTarget,\n} from '../exchange/parse.ts';\nimport { compareStrings } from '../compare.ts';\nimport { isCallerPlatform, isReceiverPlatform } from '../exchange/parse.ts';\n\n/** 한 언어 문서가 제공한 브리지 증거 위치다. */\nexport interface BridgeEndpoint {\n readonly platform: BridgePlatform;\n readonly location: BridgeLocation;\n readonly symbol?: BridgeSymbol;\n}\n\n/** 논리 채널 하나에 모인 양쪽 생성·등록 증거다. */\nexport interface MatchedChannel {\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly creations: readonly BridgeEndpoint[];\n readonly registrations: readonly BridgeEndpoint[];\n}\n\n/** 등록을 찾지 못한 논리 채널과 모든 생성 증거다. */\nexport interface UnregisteredChannelCreation {\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly creations: readonly BridgeEndpoint[];\n}\n\n/** 생성을 찾지 못한 논리 채널과 모든 등록 증거다. */\nexport interface RegistrationWithoutCreation {\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly registrations: readonly BridgeEndpoint[];\n}\n\n/** 논리 메서드 하나에 모인 양쪽 호출·핸들러 증거다. */\nexport interface MatchedMethod {\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method: string;\n readonly invocations: readonly BridgeEndpoint[];\n readonly handlers: readonly BridgeEndpoint[];\n}\n\n/** 핸들러를 찾지 못한 논리 호출과 모든 호출 증거다. */\nexport interface UnhandledInvocation {\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method: string;\n readonly invocations: readonly BridgeEndpoint[];\n}\n\n/** 호출자를 찾지 못한 논리 핸들러와 모든 네이티브 증거다. */\nexport interface HandlerWithoutInvocation {\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method: string;\n readonly handlers: readonly BridgeEndpoint[];\n}\n\n/** 생산 문서가 밝힌 분석 한계와 출처다. */\nexport interface JoinLimitation {\n readonly platform: BridgePlatform | 'cross-platform';\n readonly tool: string;\n readonly message: string;\n}\n\n/** 검증된 교환 문서들의 논리 조인 결과다. */\nexport interface BridgeJoinResult {\n readonly deferred: boolean;\n readonly matchedChannels: readonly MatchedChannel[];\n readonly unregisteredChannelCreations: readonly UnregisteredChannelCreation[];\n readonly registrationsWithoutCreations: readonly RegistrationWithoutCreation[];\n readonly matchedMethods: readonly MatchedMethod[];\n readonly unhandledInvocations: readonly UnhandledInvocation[];\n readonly handlersWithoutInvocations: readonly HandlerWithoutInvocation[];\n readonly limitations: readonly JoinLimitation[];\n}\n\n/** 한 번의 조인에서 허용하는 최대 생산 문서 수다. */\nexport const MAX_DOCUMENTS_PER_JOIN = 256;\n\n/** 함께 조인할 입력 문서 집합이 논리 계약을 위반했음을 나타낸다. */\nexport class BridgeJoinValidationError extends Error {\n /** 입력 경로를 노출하지 않는 안전한 메시지를 보존한다. */\n constructor(message: string) {\n super(message);\n this.name = 'BridgeJoinValidationError';\n }\n}\n\n/** 위치가 아니라 문자열 키로 검증된 교환 문서를 조인한다. */\nexport function joinBridgeDocuments(\n documents: readonly BridgeFactsDocument[],\n): BridgeJoinResult {\n if (documents.length > MAX_DOCUMENTS_PER_JOIN) {\n throw new BridgeJoinValidationError(\n `Bridge join exceeds the ${MAX_DOCUMENTS_PER_JOIN} document limit.`,\n );\n }\n validateProjects(documents);\n validatePlatformComposition(documents);\n const limitations = collectLimitations(documents);\n if (documents.some(hasMixedTargets)) return emptyJoinResult(limitations);\n const groups = collectChannelGroups(documents);\n const matchedChannels = [...groups.values()]\n .filter((group) => group.creations.length > 0 && group.registrations.length > 0)\n .sort(compareChannels);\n const unregisteredChannelCreations = [...groups.values()]\n .filter((group) => group.creations.length > 0 && group.registrations.length === 0)\n .map(({ target, channel, creations }) => ({ target, channel, creations }))\n .sort(compareChannels);\n const registrationsWithoutCreations = [...groups.values()]\n .filter((group) => group.registrations.length > 0 && group.creations.length === 0)\n .map(({ target, channel, registrations }) => ({\n target,\n channel,\n registrations,\n }))\n .sort(compareChannels);\n const methodGroups = collectMethodGroups(documents);\n const matchedMethods = [...methodGroups.values()]\n .filter((group) => group.invocations.length > 0 && group.handlers.length > 0)\n .sort(compareMethodKeys);\n const unhandledInvocations = [...methodGroups.values()]\n .filter((group) => group.invocations.length > 0 && group.handlers.length === 0)\n .map(({ target, channel, method, invocations }) => ({\n target,\n channel,\n method,\n invocations,\n }))\n .sort(compareMethodKeys);\n const handlersWithoutInvocations = [...methodGroups.values()]\n .filter((group) => group.handlers.length > 0 && group.invocations.length === 0)\n .map(({ target, channel, method, handlers }) => ({\n target,\n channel,\n method,\n handlers,\n }))\n .sort(compareMethodKeys);\n return {\n deferred: false,\n matchedChannels,\n unregisteredChannelCreations,\n registrationsWithoutCreations,\n matchedMethods,\n unhandledInvocations,\n handlersWithoutInvocations,\n limitations,\n };\n}\n\n/** 입력 한계 때문에 조인 전체가 보류된 결과인지 확인한다. */\nexport function isBridgeJoinDeferred(joined: BridgeJoinResult): boolean {\n return joined.deferred;\n}\n\n/** 한 번의 조인 입력이 정확히 하나의 project를 기술하는지 검증한다. */\nfunction validateProjects(documents: readonly BridgeFactsDocument[]): void {\n if (new Set(documents.map(({ project }) => project)).size > 1) {\n throw new BridgeJoinValidationError(\n 'Bridge documents must describe the same project; regenerate them from one project root.',\n );\n }\n}\n\n/** 호출 측·수신 측 문서가 모두 있어야 경계 사실로 조인할 수 있다. */\nfunction validatePlatformComposition(\n documents: readonly BridgeFactsDocument[],\n): void {\n const hasCaller = documents.some(({ platform }) => isCallerPlatform(platform));\n const hasReceiver = documents.some(({ platform }) =>\n isReceiverPlatform(platform),\n );\n if (!hasCaller || !hasReceiver) {\n throw new BridgeJoinValidationError(\n 'Bridge documents must include at least one caller platform (dart, js) document '\n + 'and one receiver platform (swift, kotlin) document; run a producer for the missing side.',\n );\n }\n}\n\n/** 사실별 target이 없는 혼합 문서인지 확인한다. */\nfunction hasMixedTargets(document: BridgeFactsDocument): boolean {\n return document.limitations.some((message) =>\n /mixed-targets\\b/i.test(message),\n );\n}\n\n/** 안전하게 조인을 보류하면서 입력 한계만 전달한다. */\nfunction emptyJoinResult(limitations: readonly JoinLimitation[]): BridgeJoinResult {\n return {\n deferred: true,\n matchedChannels: [],\n unregisteredChannelCreations: [],\n registrationsWithoutCreations: [],\n matchedMethods: [],\n unhandledInvocations: [],\n handlersWithoutInvocations: [],\n limitations,\n };\n}\n\n/** 입력 limitation에 생산 플랫폼과 도구 이름을 붙이고 조인하지 못한 사실을 함께 센다. */\nfunction collectLimitations(\n documents: readonly BridgeFactsDocument[],\n): JoinLimitation[] {\n const limitations: JoinLimitation[] = documents.flatMap((document) =>\n document.limitations.map((message) => ({\n platform: document.platform,\n tool: document.tool.name,\n message,\n })),\n );\n limitations.push(...unjoinedFactLimitations(documents));\n const freshness = freshnessLimitation(documents);\n if (freshness !== undefined) limitations.push(freshness);\n return limitations.sort(compareLimitations);\n}\n\n/**\n * 조인에서 제외한 사실을 isthmus 자신의 관찰 한계로 남긴다.\n *\n * 생산자가 같은 사실을 limitation으로 신고하지 않아도, 또는 신고한 개수가 실제와\n * 달라도 관찰 공백이 보고서에서 사라지지 않아야 한다. 교환 계약은 이 사실들을\n * 세도록 요구하지만 문서의 limitation 문자열은 생산자의 자발적인 신고라\n * 소비자가 신뢰의 근거로 삼을 수 없다.\n */\nfunction unjoinedFactLimitations(\n documents: readonly BridgeFactsDocument[],\n): JoinLimitation[] {\n return [\n ...unjoinedLimitations(\n documents,\n isUnjoinedDynamicChannel,\n 'unjoined-dynamic-channels',\n 'channel facts with a non-literal name',\n ),\n ...unjoinedLimitations(\n documents,\n isUnjoinedDynamicMethod,\n 'unjoined-dynamic-methods',\n 'method facts with a non-literal name',\n ),\n ...unjoinedLimitations(\n documents,\n isUnattributedHandler,\n 'unjoined-unattributed-handlers',\n 'method handler facts without a channel',\n ),\n ];\n}\n\n/** 조건에 맞는 사실 수를 플랫폼별로 세어 한계 문장으로 바꾼다. */\nfunction unjoinedLimitations(\n documents: readonly BridgeFactsDocument[],\n matchesFact: (fact: BridgeFact) => boolean,\n prefix: string,\n subject: string,\n): JoinLimitation[] {\n return [...countFactsByPlatform(documents, matchesFact)].map(\n ([platform, count]) => ({\n platform,\n tool: 'isthmus',\n message: `${prefix}: ${count} ${subject} were not joined`,\n }),\n );\n}\n\n/** 주어진 조건의 서로 다른 사실 수를 생산 플랫폼별로 모은다. */\nfunction countFactsByPlatform(\n documents: readonly BridgeFactsDocument[],\n matchesFact: (fact: BridgeFact) => boolean,\n): Map<BridgePlatform, number> {\n const distinctFacts = new Map<BridgePlatform, Set<string>>();\n for (const document of documents) {\n for (const fact of document.facts) {\n if (!matchesFact(fact)) continue;\n const keys = distinctFacts.get(document.platform) ?? new Set<string>();\n distinctFacts.set(document.platform, keys.add(unjoinedFactKey(fact)));\n }\n }\n return new Map([...distinctFacts].map(([platform, keys]) => [platform, keys.size]));\n}\n\n/**\n * 증거 dedup과 같게 같은 위치의 중복 사실을 한 번만 세는 키를 만든다.\n *\n * `channel`은 null일 수 있어 구분자 결합 대신 JSON으로 만든다. 그래야 미귀속\n * 핸들러와 이름이 `null`인 채널의 사실이 같은 키로 합쳐지지 않는다.\n */\nfunction unjoinedFactKey(fact: BridgeFact): string {\n const { path, line, column } = fact.location;\n return JSON.stringify([\n fact.kind,\n fact.channel,\n fact.method ?? null,\n path,\n line,\n column,\n ]);\n}\n\n/** 이름이 리터럴이 아니어서 조인하지 못한 채널 사실인지 확인한다. */\nfunction isUnjoinedDynamicChannel(fact: BridgeFact): boolean {\n return (\n fact.dynamic &&\n (fact.kind === 'channel-create' || fact.kind === 'channel-register')\n );\n}\n\n/** 이름이 리터럴이 아니어서 조인하지 못한 메서드 사실인지 확인한다. */\nfunction isUnjoinedDynamicMethod(fact: BridgeFact): boolean {\n return (\n fact.dynamic &&\n (fact.kind === 'method-invoke' || fact.kind === 'method-handle')\n );\n}\n\n/**\n * 어느 채널에 속하는지 몰라 조인하지 못한 핸들러 사실인지 확인한다.\n *\n * dynamic 사실은 위에서 이미 세므로 여기서 다시 세지 않는다.\n */\nfunction isUnattributedHandler(fact: BridgeFact): boolean {\n return fact.kind === 'method-handle' && fact.channel === null && !fact.dynamic;\n}\n\n/** 생성 시각 차이가 하루를 넘을 때 교차 입력 한계를 만든다. */\nfunction freshnessLimitation(\n documents: readonly BridgeFactsDocument[],\n): JoinLimitation | undefined {\n if (documents.length < 2) return undefined;\n let earliest = Number.POSITIVE_INFINITY;\n let latest = Number.NEGATIVE_INFINITY;\n for (const document of documents) {\n const timestamp = Date.parse(document.generatedAt);\n earliest = Math.min(earliest, timestamp);\n latest = Math.max(latest, timestamp);\n }\n const difference = latest - earliest;\n if (difference <= millisecondsPerDay) return undefined;\n const hours = Math.round(difference / millisecondsPerHour);\n return {\n platform: 'cross-platform',\n tool: 'isthmus',\n message: `input-freshness: bridge documents differ by ${hours} hours`,\n };\n}\n\n/** limitation을 플랫폼·도구·문장 순으로 고정한다. */\nfunction compareLimitations(left: JoinLimitation, right: JoinLimitation): number {\n return (\n compareStrings(left.platform, right.platform) ||\n compareStrings(left.tool, right.tool) ||\n compareStrings(left.message, right.message)\n );\n}\n\n/** 메서드 키별로 호출과 핸들러 증거를 모은다. */\nfunction collectMethodGroups(\n documents: readonly BridgeFactsDocument[],\n): Map<string, MutableMethodGroup> {\n const groups = new Map<string, MutableMethodGroup>();\n for (const document of documents) collectDocumentMethods(document, groups);\n for (const group of groups.values()) {\n sortUniqueEndpoints(group.invocations);\n sortUniqueEndpoints(group.handlers);\n }\n return groups;\n}\n\n/** 증거 위치와 심볼을 정렬하고 같은 증거를 한 번만 남긴다. */\nfunction sortUniqueEndpoints(endpoints: BridgeEndpoint[]): void {\n endpoints.sort(compareEndpoints);\n if (endpoints.length < 2) return;\n let writeIndex = 1;\n for (let readIndex = 1; readIndex < endpoints.length; readIndex++) {\n const previous = endpoints[writeIndex - 1];\n const current = endpoints[readIndex];\n if (\n previous !== undefined &&\n current !== undefined &&\n compareEndpoints(previous, current) !== 0\n ) {\n endpoints[writeIndex] = current;\n writeIndex++;\n }\n }\n endpoints.length = writeIndex;\n}\n\n/** 증거 위치와 선택 심볼을 완전한 결정 순서로 비교한다. */\nfunction compareEndpoints(left: BridgeEndpoint, right: BridgeEndpoint): number {\n const locationOrder =\n compareStrings(left.platform, right.platform) ||\n compareStrings(left.location.path, right.location.path) ||\n left.location.line - right.location.line ||\n left.location.column - right.location.column;\n if (locationOrder !== 0) return locationOrder;\n if (left.symbol === undefined) return right.symbol === undefined ? 0 : 1;\n if (right.symbol === undefined) return -1;\n return (\n compareStrings(left.symbol.qualifiedName, right.symbol.qualifiedName) ||\n compareOptionalStrings(left.symbol.usr, right.symbol.usr)\n );\n}\n\n/** 존재하는 선택 문자열을 없는 값보다 먼저 두고 비교한다. */\nfunction compareOptionalStrings(\n left: string | undefined,\n right: string | undefined,\n): number {\n if (left === undefined) return right === undefined ? 0 : 1;\n if (right === undefined) return -1;\n return compareStrings(left, right);\n}\n\n/** 문서 하나의 정적 메서드 사실을 그룹에 추가한다. */\nfunction collectDocumentMethods(\n document: BridgeFactsDocument,\n groups: Map<string, MutableMethodGroup>,\n): void {\n if (document.target === null) return;\n for (const fact of document.facts) {\n if (!isStaticMethodFact(fact)) continue;\n const key = `${document.target}\\u0000${fact.channel}\\u0000${fact.method}`;\n const group = groups.get(key) ?? createMethodGroup(document.target, fact.channel, fact.method);\n const endpoints = fact.kind === 'method-invoke' ? group.invocations : group.handlers;\n endpoints.push(toEndpoint(document.platform, fact));\n groups.set(key, group);\n }\n}\n\n/** 조인 가능한 정적 메서드 호출·핸들러 사실인지 확인한다. */\nfunction isStaticMethodFact(\n fact: BridgeFactsDocument['facts'][number],\n): fact is BridgeFactsDocument['facts'][number] & {\n channel: string;\n method: string;\n} {\n return (\n (fact.kind === 'method-invoke' || fact.kind === 'method-handle') &&\n !fact.dynamic &&\n fact.channel !== null &&\n fact.method !== undefined\n );\n}\n\n/** 빈 메서드 그룹을 만든다. */\nfunction createMethodGroup(\n target: BridgeTarget,\n channel: string,\n method: string,\n): MutableMethodGroup {\n return { target, channel, method, invocations: [], handlers: [] };\n}\n\n/** 채널 키별로 생성과 등록 증거를 모은다. */\nfunction collectChannelGroups(\n documents: readonly BridgeFactsDocument[],\n): Map<string, MutableChannelGroup> {\n const groups = new Map<string, MutableChannelGroup>();\n for (const document of documents) collectDocumentChannels(document, groups);\n for (const group of groups.values()) {\n sortUniqueEndpoints(group.creations);\n sortUniqueEndpoints(group.registrations);\n }\n return groups;\n}\n\n/** 문서 하나의 정적 채널 사실을 그룹에 추가한다. */\nfunction collectDocumentChannels(\n document: BridgeFactsDocument,\n groups: Map<string, MutableChannelGroup>,\n): void {\n if (document.target === null) return;\n for (const fact of document.facts) {\n if (!isStaticChannelFact(fact)) continue;\n const key = `${document.target}\\u0000${fact.channel}`;\n const group = groups.get(key) ?? createChannelGroup(document.target, fact.channel);\n const endpoints = fact.kind === 'channel-create' ? group.creations : group.registrations;\n endpoints.push(toEndpoint(document.platform, fact));\n groups.set(key, group);\n }\n}\n\n/** 조인 가능한 정적 채널 생성·등록 사실인지 확인한다. */\nfunction isStaticChannelFact(\n fact: BridgeFactsDocument['facts'][number],\n): fact is BridgeFactsDocument['facts'][number] & { channel: string } {\n return (\n (fact.kind === 'channel-create' || fact.kind === 'channel-register') &&\n !fact.dynamic &&\n fact.channel !== null\n );\n}\n\n/** 빈 채널 그룹을 만든다. */\nfunction createChannelGroup(\n target: BridgeTarget,\n channel: string,\n): MutableChannelGroup {\n return { target, channel, creations: [], registrations: [] };\n}\n\n/** 사실을 플랫폼이 포함된 증거 위치로 바꾼다. */\nfunction toEndpoint(\n platform: BridgePlatform,\n fact: BridgeFactsDocument['facts'][number],\n): BridgeEndpoint {\n return fact.symbol === undefined\n ? { platform, location: fact.location }\n : { platform, location: fact.location, symbol: fact.symbol };\n}\n\n/** 채널 결과를 target과 이름 순으로 고정한다. */\nfunction compareChannels(left: ChannelKey, right: ChannelKey): number {\n return (\n compareStrings(left.target, right.target) ||\n compareStrings(left.channel, right.channel)\n );\n}\n\n/** 결정적 정렬에 필요한 논리 채널 키다. */\ntype ChannelKey = Pick<MatchedChannel, 'target' | 'channel'>;\n\n/** 메서드 결과를 target·채널·메서드 순으로 고정한다. */\nfunction compareMethodKeys(left: MethodKey, right: MethodKey): number {\n return (\n compareStrings(left.target, right.target) ||\n compareStrings(left.channel, right.channel) ||\n compareStrings(left.method, right.method)\n );\n}\n\n/** 결정적 정렬에 필요한 논리 메서드 키다. */\ntype MethodKey = Pick<MatchedMethod, 'target' | 'channel' | 'method'>;\n\n/** 조립 중인 채널 증거 그룹이다. */\ninterface MutableChannelGroup extends MatchedChannel {\n readonly creations: BridgeEndpoint[];\n readonly registrations: BridgeEndpoint[];\n}\n\n/** 조립 중인 메서드 증거 그룹이다. */\ninterface MutableMethodGroup extends MatchedMethod {\n readonly invocations: BridgeEndpoint[];\n readonly handlers: BridgeEndpoint[];\n}\n\nconst millisecondsPerHour = 60 * 60 * 1_000;\nconst millisecondsPerDay = 24 * millisecondsPerHour;\n"]}
1
+ {"version":3,"file":"join.js","sourceRoot":"","sources":["../../src/join/join.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n BridgeFact,\n BridgeFactsDocument,\n BridgeLocation,\n BridgePlatform,\n BridgeSymbol,\n BridgeTarget,\n} from '../exchange/parse.ts';\nimport { compareStrings } from '../compare.ts';\nimport { isCallerPlatform, isReceiverPlatform } from '../exchange/parse.ts';\n\n/** 한 언어 문서가 제공한 브리지 증거 위치다. */\nexport interface BridgeEndpoint {\n readonly platform: BridgePlatform;\n readonly location: BridgeLocation;\n readonly symbol?: BridgeSymbol;\n}\n\n/** 논리 채널 하나에 모인 양쪽 생성·등록 증거다. */\nexport interface MatchedChannel {\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly creations: readonly BridgeEndpoint[];\n readonly registrations: readonly BridgeEndpoint[];\n}\n\n/** 등록을 찾지 못한 논리 채널과 모든 생성 증거다. */\nexport interface UnregisteredChannelCreation {\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly creations: readonly BridgeEndpoint[];\n}\n\n/** 생성을 찾지 못한 논리 채널과 모든 등록 증거다. */\nexport interface RegistrationWithoutCreation {\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly registrations: readonly BridgeEndpoint[];\n}\n\n/** 논리 메서드 하나에 모인 양쪽 호출·핸들러 증거다. */\nexport interface MatchedMethod {\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method: string;\n readonly invocations: readonly BridgeEndpoint[];\n readonly handlers: readonly BridgeEndpoint[];\n}\n\n/** 핸들러를 찾지 못한 논리 호출과 모든 호출 증거다. */\nexport interface UnhandledInvocation {\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method: string;\n readonly invocations: readonly BridgeEndpoint[];\n}\n\n/** 호출자를 찾지 못한 논리 핸들러와 모든 네이티브 증거다. */\nexport interface HandlerWithoutInvocation {\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method: string;\n readonly handlers: readonly BridgeEndpoint[];\n}\n\n/**\n * 생산 문서가 밝힌 분석 한계와 출처다.\n *\n * `target`은 이 한계를 신고한 문서의 브리지 메커니즘이다. 사실은 target별로만\n * 조인되므로, 보고서는 이 값으로 한계를 해당 target의 진단에만 귀속시킨다.\n * 사실이 없는 문서의 한계, 선언한 target을 신뢰할 수 없는 mixed-targets 문서의\n * 한계, 교차 입력 한계는 어느 target이 가려졌는지 특정할 수 없어 `null`이다.\n */\nexport interface JoinLimitation {\n readonly platform: BridgePlatform | 'cross-platform';\n readonly target: BridgeTarget | null;\n readonly tool: string;\n readonly message: string;\n}\n\n/** 검증된 교환 문서들의 논리 조인 결과다. */\nexport interface BridgeJoinResult {\n readonly deferred: boolean;\n readonly matchedChannels: readonly MatchedChannel[];\n readonly unregisteredChannelCreations: readonly UnregisteredChannelCreation[];\n readonly registrationsWithoutCreations: readonly RegistrationWithoutCreation[];\n readonly matchedMethods: readonly MatchedMethod[];\n readonly unhandledInvocations: readonly UnhandledInvocation[];\n readonly handlersWithoutInvocations: readonly HandlerWithoutInvocation[];\n readonly limitations: readonly JoinLimitation[];\n}\n\n/** 한 번의 조인에서 허용하는 최대 생산 문서 수다. */\nexport const MAX_DOCUMENTS_PER_JOIN = 256;\n\n/** 함께 조인할 입력 문서 집합이 논리 계약을 위반했음을 나타낸다. */\nexport class BridgeJoinValidationError extends Error {\n /** 입력 경로를 노출하지 않는 안전한 메시지를 보존한다. */\n constructor(message: string) {\n super(message);\n this.name = 'BridgeJoinValidationError';\n }\n}\n\n/** 위치가 아니라 문자열 키로 검증된 교환 문서를 조인한다. */\nexport function joinBridgeDocuments(\n documents: readonly BridgeFactsDocument[],\n): BridgeJoinResult {\n if (documents.length > MAX_DOCUMENTS_PER_JOIN) {\n throw new BridgeJoinValidationError(\n `Bridge join exceeds the ${MAX_DOCUMENTS_PER_JOIN} document limit.`,\n );\n }\n validateProjects(documents);\n validatePlatformComposition(documents);\n const limitations = collectLimitations(documents);\n if (documents.some(hasMixedTargets)) return emptyJoinResult(limitations);\n const groups = collectChannelGroups(documents);\n const matchedChannels = [...groups.values()]\n .filter((group) => group.creations.length > 0 && group.registrations.length > 0)\n .sort(compareChannels);\n const unregisteredChannelCreations = [...groups.values()]\n .filter((group) => group.creations.length > 0 && group.registrations.length === 0)\n .map(({ target, channel, creations }) => ({ target, channel, creations }))\n .sort(compareChannels);\n const registrationsWithoutCreations = [...groups.values()]\n .filter((group) => group.registrations.length > 0 && group.creations.length === 0)\n .map(({ target, channel, registrations }) => ({\n target,\n channel,\n registrations,\n }))\n .sort(compareChannels);\n const methodGroups = collectMethodGroups(documents);\n const matchedMethods = [...methodGroups.values()]\n .filter((group) => group.invocations.length > 0 && group.handlers.length > 0)\n .sort(compareMethodKeys);\n const unhandledInvocations = [...methodGroups.values()]\n .filter((group) => group.invocations.length > 0 && group.handlers.length === 0)\n .map(({ target, channel, method, invocations }) => ({\n target,\n channel,\n method,\n invocations,\n }))\n .sort(compareMethodKeys);\n const handlersWithoutInvocations = [...methodGroups.values()]\n .filter((group) => group.handlers.length > 0 && group.invocations.length === 0)\n .map(({ target, channel, method, handlers }) => ({\n target,\n channel,\n method,\n handlers,\n }))\n .sort(compareMethodKeys);\n return {\n deferred: false,\n matchedChannels,\n unregisteredChannelCreations,\n registrationsWithoutCreations,\n matchedMethods,\n unhandledInvocations,\n handlersWithoutInvocations,\n limitations,\n };\n}\n\n/** 입력 한계 때문에 조인 전체가 보류된 결과인지 확인한다. */\nexport function isBridgeJoinDeferred(joined: BridgeJoinResult): boolean {\n return joined.deferred;\n}\n\n/** 한 번의 조인 입력이 정확히 하나의 project를 기술하는지 검증한다. */\nfunction validateProjects(documents: readonly BridgeFactsDocument[]): void {\n if (new Set(documents.map(({ project }) => project)).size > 1) {\n throw new BridgeJoinValidationError(\n 'Bridge documents must describe the same project; regenerate them from one project root.',\n );\n }\n}\n\n/** 호출 측·수신 측 문서가 모두 있어야 경계 사실로 조인할 수 있다. */\nfunction validatePlatformComposition(\n documents: readonly BridgeFactsDocument[],\n): void {\n const hasCaller = documents.some(({ platform }) => isCallerPlatform(platform));\n const hasReceiver = documents.some(({ platform }) =>\n isReceiverPlatform(platform),\n );\n if (!hasCaller || !hasReceiver) {\n throw new BridgeJoinValidationError(\n 'Bridge documents must include at least one caller platform (dart, js) document '\n + 'and one receiver platform (swift, kotlin) document; run a producer for the missing side.',\n );\n }\n}\n\n/** 사실별 target이 없는 혼합 문서인지 확인한다. */\nfunction hasMixedTargets(document: BridgeFactsDocument): boolean {\n return document.limitations.some((message) =>\n /mixed-targets\\b/i.test(message),\n );\n}\n\n/**\n * 한계를 귀속시킬 target을 결정한다.\n *\n * mixed-targets 문서의 선언 target은 대표값일 뿐 사실별 메커니즘을 복원할 수\n * 없으므로, 그 문서의 한계와 조인 제외 사실 계수는 귀속 없이(null) 남긴다.\n * 귀속을 잃어도 관찰 자체는 보존해야 한다.\n */\nfunction limitationTarget(document: BridgeFactsDocument): BridgeTarget | null {\n return hasMixedTargets(document) ? null : document.target;\n}\n\n/** 안전하게 조인을 보류하면서 입력 한계만 전달한다. */\nfunction emptyJoinResult(limitations: readonly JoinLimitation[]): BridgeJoinResult {\n return {\n deferred: true,\n matchedChannels: [],\n unregisteredChannelCreations: [],\n registrationsWithoutCreations: [],\n matchedMethods: [],\n unhandledInvocations: [],\n handlersWithoutInvocations: [],\n limitations,\n };\n}\n\n/** 입력 limitation에 생산 플랫폼과 도구 이름을 붙이고 조인하지 못한 사실을 함께 센다. */\nfunction collectLimitations(\n documents: readonly BridgeFactsDocument[],\n): JoinLimitation[] {\n const limitations: JoinLimitation[] = documents.flatMap((document) =>\n document.limitations.map((message) => ({\n platform: document.platform,\n target: limitationTarget(document),\n tool: document.tool.name,\n message,\n })),\n );\n limitations.push(...unjoinedFactLimitations(documents));\n const freshness = freshnessLimitation(documents);\n if (freshness !== undefined) limitations.push(freshness);\n return limitations.sort(compareLimitations);\n}\n\n/**\n * 조인에서 제외한 사실을 isthmus 자신의 관찰 한계로 남긴다.\n *\n * 생산자가 같은 사실을 limitation으로 신고하지 않아도, 또는 신고한 개수가 실제와\n * 달라도 관찰 공백이 보고서에서 사라지지 않아야 한다. 교환 계약은 이 사실들을\n * 세도록 요구하지만 문서의 limitation 문자열은 생산자의 자발적인 신고라\n * 소비자가 신뢰의 근거로 삼을 수 없다.\n */\nfunction unjoinedFactLimitations(\n documents: readonly BridgeFactsDocument[],\n): JoinLimitation[] {\n return [\n ...unjoinedLimitations(\n documents,\n isUnjoinedDynamicChannel,\n 'unjoined-dynamic-channels',\n 'channel facts with a non-literal name',\n ),\n ...unjoinedLimitations(\n documents,\n isUnjoinedDynamicMethod,\n 'unjoined-dynamic-methods',\n 'method facts with a non-literal name',\n ),\n ...unjoinedLimitations(\n documents,\n isUnattributedHandler,\n 'unjoined-unattributed-handlers',\n 'method handler facts without a channel',\n ),\n ];\n}\n\n/** 조건에 맞는 사실 수를 플랫폼·target별로 세어 한계 문장으로 바꾼다. */\nfunction unjoinedLimitations(\n documents: readonly BridgeFactsDocument[],\n matchesFact: (fact: BridgeFact) => boolean,\n prefix: string,\n subject: string,\n): JoinLimitation[] {\n return countFactsByPlatformTarget(documents, matchesFact).map(\n ({ platform, target, count }) => ({\n platform,\n target,\n tool: 'isthmus',\n message: `${prefix}: ${count} ${subject} were not joined`,\n }),\n );\n}\n\n/**\n * 주어진 조건의 서로 다른 사실 수를 생산 플랫폼·target별로 모은다.\n *\n * 사실은 target별로만 조인되므로 다른 target의 사실을 한 한계로 합산하면\n * 관찰 공백의 귀속이 사라진다. mixed-targets 문서의 사실은 귀속 없이(null)\n * 센다. 사실이 없는 문서는 교환 계약상 target이 null이고 사실도 없으므로\n * 여기에서 자연스럽게 제외된다.\n */\nfunction countFactsByPlatformTarget(\n documents: readonly BridgeFactsDocument[],\n matchesFact: (fact: BridgeFact) => boolean,\n): Array<{\n platform: BridgePlatform;\n target: BridgeTarget | null;\n count: number;\n}> {\n const distinctFacts = new Map<\n string,\n {\n platform: BridgePlatform;\n target: BridgeTarget | null;\n keys: Set<string>;\n }\n >();\n for (const document of documents) {\n const target = limitationTarget(document);\n // BridgeTarget은 닫힌 어휘라 문자열화된 null과 충돌하지 않는다.\n const groupKey = `${document.platform}\\u0000${target}`;\n for (const fact of document.facts) {\n if (!matchesFact(fact)) continue;\n const group = distinctFacts.get(groupKey) ?? {\n platform: document.platform,\n target,\n keys: new Set<string>(),\n };\n group.keys.add(unjoinedFactKey(fact));\n distinctFacts.set(groupKey, group);\n }\n }\n return [...distinctFacts.values()].map(({ platform, target, keys }) => ({\n platform,\n target,\n count: keys.size,\n }));\n}\n\n/**\n * 같은 위치의 중복 사실을 한 번만 세는 키를 만든다.\n *\n * 위치 기준은 증거 dedup과 같지만 symbol은 비교하지 않아, 같은 위치의 서로\n * 다른 symbol은 하나로 센다. `channel`은 null일 수 있어 구분자 결합 대신\n * JSON으로 만든다. 그래야 미귀속 핸들러와 이름이 `null`인 채널의 사실이\n * 같은 키로 합쳐지지 않는다.\n */\nfunction unjoinedFactKey(fact: BridgeFact): string {\n const { path, line, column } = fact.location;\n return JSON.stringify([\n fact.kind,\n fact.channel,\n fact.method ?? null,\n path,\n line,\n column,\n ]);\n}\n\n/** 이름이 리터럴이 아니어서 조인하지 못한 채널 사실인지 확인한다. */\nfunction isUnjoinedDynamicChannel(fact: BridgeFact): boolean {\n return (\n fact.dynamic &&\n (fact.kind === 'channel-create' || fact.kind === 'channel-register')\n );\n}\n\n/** 이름이 리터럴이 아니어서 조인하지 못한 메서드 사실인지 확인한다. */\nfunction isUnjoinedDynamicMethod(fact: BridgeFact): boolean {\n return (\n fact.dynamic &&\n (fact.kind === 'method-invoke' || fact.kind === 'method-handle')\n );\n}\n\n/**\n * 어느 채널에 속하는지 몰라 조인하지 못한 핸들러 사실인지 확인한다.\n *\n * dynamic 사실은 위에서 이미 세므로 여기서 다시 세지 않는다.\n */\nfunction isUnattributedHandler(fact: BridgeFact): boolean {\n return fact.kind === 'method-handle' && fact.channel === null && !fact.dynamic;\n}\n\n/** 생성 시각 차이가 하루를 넘을 때 교차 입력 한계를 만든다. */\nfunction freshnessLimitation(\n documents: readonly BridgeFactsDocument[],\n): JoinLimitation | undefined {\n if (documents.length < 2) return undefined;\n let earliest = Number.POSITIVE_INFINITY;\n let latest = Number.NEGATIVE_INFINITY;\n for (const document of documents) {\n const timestamp = Date.parse(document.generatedAt);\n earliest = Math.min(earliest, timestamp);\n latest = Math.max(latest, timestamp);\n }\n const difference = latest - earliest;\n if (difference <= millisecondsPerDay) return undefined;\n const hours = Math.round(difference / millisecondsPerHour);\n return {\n platform: 'cross-platform',\n target: null,\n tool: 'isthmus',\n message: `input-freshness: bridge documents differ by ${hours} hours`,\n };\n}\n\n/** limitation을 플랫폼·target·도구·문장 순으로 고정한다. */\nfunction compareLimitations(left: JoinLimitation, right: JoinLimitation): number {\n return (\n compareStrings(left.platform, right.platform) ||\n compareTargets(left.target, right.target) ||\n compareStrings(left.tool, right.tool) ||\n compareStrings(left.message, right.message)\n );\n}\n\n/** target을 문자열 순으로 고정하고 귀속이 없는(null) 한계를 뒤에 둔다. */\nfunction compareTargets(\n left: BridgeTarget | null,\n right: BridgeTarget | null,\n): number {\n if (left === right) return 0;\n if (left === null) return 1;\n if (right === null) return -1;\n return compareStrings(left, right);\n}\n\n/** 메서드 키별로 호출과 핸들러 증거를 모은다. */\nfunction collectMethodGroups(\n documents: readonly BridgeFactsDocument[],\n): Map<string, MutableMethodGroup> {\n const groups = new Map<string, MutableMethodGroup>();\n for (const document of documents) collectDocumentMethods(document, groups);\n for (const group of groups.values()) {\n sortUniqueEndpoints(group.invocations);\n sortUniqueEndpoints(group.handlers);\n }\n return groups;\n}\n\n/** 증거 위치와 심볼을 정렬하고 같은 증거를 한 번만 남긴다. */\nfunction sortUniqueEndpoints(endpoints: BridgeEndpoint[]): void {\n endpoints.sort(compareEndpoints);\n if (endpoints.length < 2) return;\n let writeIndex = 1;\n for (let readIndex = 1; readIndex < endpoints.length; readIndex++) {\n const previous = endpoints[writeIndex - 1];\n const current = endpoints[readIndex];\n if (\n previous !== undefined &&\n current !== undefined &&\n compareEndpoints(previous, current) !== 0\n ) {\n endpoints[writeIndex] = current;\n writeIndex++;\n }\n }\n endpoints.length = writeIndex;\n}\n\n/** 증거 위치와 선택 심볼을 완전한 결정 순서로 비교한다. */\nfunction compareEndpoints(left: BridgeEndpoint, right: BridgeEndpoint): number {\n const locationOrder =\n compareStrings(left.platform, right.platform) ||\n compareStrings(left.location.path, right.location.path) ||\n left.location.line - right.location.line ||\n left.location.column - right.location.column;\n if (locationOrder !== 0) return locationOrder;\n if (left.symbol === undefined) return right.symbol === undefined ? 0 : 1;\n if (right.symbol === undefined) return -1;\n return (\n compareStrings(left.symbol.qualifiedName, right.symbol.qualifiedName) ||\n compareOptionalStrings(left.symbol.usr, right.symbol.usr)\n );\n}\n\n/** 존재하는 선택 문자열을 없는 값보다 먼저 두고 비교한다. */\nfunction compareOptionalStrings(\n left: string | undefined,\n right: string | undefined,\n): number {\n if (left === undefined) return right === undefined ? 0 : 1;\n if (right === undefined) return -1;\n return compareStrings(left, right);\n}\n\n/** 문서 하나의 정적 메서드 사실을 그룹에 추가한다. */\nfunction collectDocumentMethods(\n document: BridgeFactsDocument,\n groups: Map<string, MutableMethodGroup>,\n): void {\n if (document.target === null) return;\n for (const fact of document.facts) {\n if (!isStaticMethodFact(fact)) continue;\n const key = `${document.target}\\u0000${fact.channel}\\u0000${fact.method}`;\n const group = groups.get(key) ?? createMethodGroup(document.target, fact.channel, fact.method);\n const endpoints = fact.kind === 'method-invoke' ? group.invocations : group.handlers;\n endpoints.push(toEndpoint(document.platform, fact));\n groups.set(key, group);\n }\n}\n\n/** 조인 가능한 정적 메서드 호출·핸들러 사실인지 확인한다. */\nfunction isStaticMethodFact(\n fact: BridgeFactsDocument['facts'][number],\n): fact is BridgeFactsDocument['facts'][number] & {\n channel: string;\n method: string;\n} {\n return (\n (fact.kind === 'method-invoke' || fact.kind === 'method-handle') &&\n !fact.dynamic &&\n fact.channel !== null &&\n fact.method !== undefined\n );\n}\n\n/** 빈 메서드 그룹을 만든다. */\nfunction createMethodGroup(\n target: BridgeTarget,\n channel: string,\n method: string,\n): MutableMethodGroup {\n return { target, channel, method, invocations: [], handlers: [] };\n}\n\n/** 채널 키별로 생성과 등록 증거를 모은다. */\nfunction collectChannelGroups(\n documents: readonly BridgeFactsDocument[],\n): Map<string, MutableChannelGroup> {\n const groups = new Map<string, MutableChannelGroup>();\n for (const document of documents) collectDocumentChannels(document, groups);\n for (const group of groups.values()) {\n sortUniqueEndpoints(group.creations);\n sortUniqueEndpoints(group.registrations);\n }\n return groups;\n}\n\n/** 문서 하나의 정적 채널 사실을 그룹에 추가한다. */\nfunction collectDocumentChannels(\n document: BridgeFactsDocument,\n groups: Map<string, MutableChannelGroup>,\n): void {\n if (document.target === null) return;\n for (const fact of document.facts) {\n if (!isStaticChannelFact(fact)) continue;\n const key = `${document.target}\\u0000${fact.channel}`;\n const group = groups.get(key) ?? createChannelGroup(document.target, fact.channel);\n const endpoints = fact.kind === 'channel-create' ? group.creations : group.registrations;\n endpoints.push(toEndpoint(document.platform, fact));\n groups.set(key, group);\n }\n}\n\n/** 조인 가능한 정적 채널 생성·등록 사실인지 확인한다. */\nfunction isStaticChannelFact(\n fact: BridgeFactsDocument['facts'][number],\n): fact is BridgeFactsDocument['facts'][number] & { channel: string } {\n return (\n (fact.kind === 'channel-create' || fact.kind === 'channel-register') &&\n !fact.dynamic &&\n fact.channel !== null\n );\n}\n\n/** 빈 채널 그룹을 만든다. */\nfunction createChannelGroup(\n target: BridgeTarget,\n channel: string,\n): MutableChannelGroup {\n return { target, channel, creations: [], registrations: [] };\n}\n\n/** 사실을 플랫폼이 포함된 증거 위치로 바꾼다. */\nfunction toEndpoint(\n platform: BridgePlatform,\n fact: BridgeFactsDocument['facts'][number],\n): BridgeEndpoint {\n return fact.symbol === undefined\n ? { platform, location: fact.location }\n : { platform, location: fact.location, symbol: fact.symbol };\n}\n\n/** 채널 결과를 target과 이름 순으로 고정한다. */\nfunction compareChannels(left: ChannelKey, right: ChannelKey): number {\n return (\n compareStrings(left.target, right.target) ||\n compareStrings(left.channel, right.channel)\n );\n}\n\n/** 결정적 정렬에 필요한 논리 채널 키다. */\ntype ChannelKey = Pick<MatchedChannel, 'target' | 'channel'>;\n\n/** 메서드 결과를 target·채널·메서드 순으로 고정한다. */\nfunction compareMethodKeys(left: MethodKey, right: MethodKey): number {\n return (\n compareStrings(left.target, right.target) ||\n compareStrings(left.channel, right.channel) ||\n compareStrings(left.method, right.method)\n );\n}\n\n/** 결정적 정렬에 필요한 논리 메서드 키다. */\ntype MethodKey = Pick<MatchedMethod, 'target' | 'channel' | 'method'>;\n\n/** 조립 중인 채널 증거 그룹이다. */\ninterface MutableChannelGroup extends MatchedChannel {\n readonly creations: BridgeEndpoint[];\n readonly registrations: BridgeEndpoint[];\n}\n\n/** 조립 중인 메서드 증거 그룹이다. */\ninterface MutableMethodGroup extends MatchedMethod {\n readonly invocations: BridgeEndpoint[];\n readonly handlers: BridgeEndpoint[];\n}\n\nconst millisecondsPerHour = 60 * 60 * 1_000;\nconst millisecondsPerDay = 24 * millisecondsPerHour;\n"]}
@@ -13,8 +13,8 @@ export function createCheckReport(joined) {
13
13
  const gaps = receiverCoverageGaps(joined.limitations);
14
14
  const issues = [
15
15
  ...joined.unhandledInvocations.map((item) => ({
16
- severity: gaps.hidesHandlers ? 'warning' : 'error',
17
- code: gaps.hidesHandlers
16
+ severity: gaps.hidesHandlers(item.target) ? 'warning' : 'error',
17
+ code: gaps.hidesHandlers(item.target)
18
18
  ? 'unhandled-invocation-unverified'
19
19
  : 'unhandled-invocation',
20
20
  target: item.target,
@@ -23,8 +23,8 @@ export function createCheckReport(joined) {
23
23
  evidence: item.invocations,
24
24
  })),
25
25
  ...joined.unregisteredChannelCreations.map((item) => ({
26
- severity: gaps.hidesRegistrations ? 'warning' : 'error',
27
- code: gaps.hidesRegistrations
26
+ severity: gaps.hidesRegistrations(item.target) ? 'warning' : 'error',
27
+ code: gaps.hidesRegistrations(item.target)
28
28
  ? 'unregistered-channel-creation-unverified'
29
29
  : 'unregistered-channel-creation',
30
30
  target: item.target,
@@ -61,7 +61,7 @@ export function createCheckReport(joined) {
61
61
  };
62
62
  }
63
63
  /**
64
- * 수신 측이 핸들러나 등록을 놓쳤을 수 있다고 스스로 알렸는지 확인한다.
64
+ * 수신 측이 핸들러나 등록을 놓쳤을 수 있다고 스스로 알렸는지 target별로 확인한다.
65
65
  *
66
66
  * 이때 "핸들러 없는 호출"은 경계 불일치가 아니라 판정 불가다. Objective-C로 쓰인
67
67
  * Flutter 핸들러처럼 수신 측 분석에 아예 나타나지 않는 코드가 실제로 있어서,
@@ -70,14 +70,40 @@ export function createCheckReport(joined) {
70
70
  * 공백의 종류는 구분한다. 이름이 리터럴이 아닌 채널 등록 하나가 무관한 메서드
71
71
  * 진단까지 무르게 하면 안 된다. 호출 측 한계는 네이티브 코드를 가리지 않으므로
72
72
  * 수신 측 플랫폼의 한계만 본다.
73
+ *
74
+ * 완화 단위는 진단의 target이다. 사실은 target별로만 조인되므로, 다른 target의
75
+ * 수신 문서가 신고한 공백은 현재 target의 핸들러를 가릴 수 없다. target이 없는
76
+ * (사실이 없는) 수신 문서는 어느 target을 분석했는지 특정할 수 없어 모든 target에
77
+ * 적용한다. 같은 target에 귀속된 수신 문서가 사실과 함께 존재해도 마찬가지다.
78
+ * 수신 문서 여러 개가 소스 트리를 나누어 가졌을 수 있으므로, 귀속 없는 문서가
79
+ * 본 소스가 해당 target의 핸들러를 가릴 가능성을 배제할 수 없기 때문이다.
80
+ *
81
+ * `unjoined-` 접두사는 isthmus가 직접 세어 자신을 출처로 밝힌 한계만 인정한다.
82
+ * 생산자 문자열은 신뢰의 근거가 아니므로, 같은 접두사를 차용한 생산자 신고는
83
+ * 완화 근거가 되지 못한다.
73
84
  */
74
85
  function receiverCoverageGaps(limitations) {
75
- const messages = limitations
76
- .filter(({ platform }) => isReceiverPlatform(platform))
77
- .map(({ message }) => message);
86
+ const receiverLimitations = limitations.filter(({ platform }) => isReceiverPlatform(platform));
87
+ const memoized = new Map();
88
+ const gapsFor = (target) => {
89
+ const existing = memoized.get(target);
90
+ if (existing !== undefined)
91
+ return existing;
92
+ const relevant = receiverLimitations.filter(({ target: gapTarget }) => gapTarget === null || gapTarget === target);
93
+ const gaps = {
94
+ handlers: relevant.some(({ tool, message }) => startsWithAny(producerHandlerGapPrefixes)(message) ||
95
+ (tool === 'isthmus' &&
96
+ startsWithAny(isthmusHandlerGapPrefixes)(message))),
97
+ registrations: relevant.some(({ tool, message }) => startsWithAny(sourceCoverageGapPrefixes)(message) ||
98
+ (tool === 'isthmus' &&
99
+ startsWithAny(isthmusRegistrationGapPrefixes)(message))),
100
+ };
101
+ memoized.set(target, gaps);
102
+ return gaps;
103
+ };
78
104
  return {
79
- hidesHandlers: messages.some(startsWithAny(handlerCoverageGapPrefixes)),
80
- hidesRegistrations: messages.some(startsWithAny(registrationCoverageGapPrefixes)),
105
+ hidesHandlers: (target) => gapsFor(target).handlers,
106
+ hidesRegistrations: (target) => gapsFor(target).registrations,
81
107
  };
82
108
  }
83
109
  /** 주어진 접두사 중 하나로 시작하는지 검사하는 술어를 만든다. */
@@ -94,20 +120,22 @@ const sourceCoverageGapPrefixes = [
94
120
  'objective-c-sources:',
95
121
  'shadowed-flutter-method-channel:',
96
122
  ];
123
+ /** 생산자가 신고하는, 핸들러 본문만 가리는 한계다. */
124
+ const handlerBodyCoverageGapPrefixes = ['opaque-handler-bodies:'];
125
+ /** 생산자 신고 중 핸들러를 가릴 수 있는 한계 전체다. */
126
+ const producerHandlerGapPrefixes = [
127
+ ...sourceCoverageGapPrefixes,
128
+ ...handlerBodyCoverageGapPrefixes,
129
+ ];
97
130
  /**
98
- * 핸들러를 가릴 있는 한계다.
131
+ * isthmus가 직접 한계 중 핸들러를 가리는 접두사다.
99
132
  *
100
- * `unjoined-`는 isthmus가 직접 값이라 생산자의 신고 개수에 의존하지 않는다.
133
+ * 생산자의 신고 개수에 의존하지 않으므로 `tool`이 `isthmus`인 항목만 인정한다.
101
134
  */
102
- const handlerCoverageGapPrefixes = [
103
- ...sourceCoverageGapPrefixes,
104
- 'opaque-handler-bodies:',
135
+ const isthmusHandlerGapPrefixes = [
105
136
  'unjoined-dynamic-methods:',
106
137
  'unjoined-unattributed-handlers:',
107
138
  ];
108
- /** 채널 등록을 가릴 있는 한계다. */
109
- const registrationCoverageGapPrefixes = [
110
- ...sourceCoverageGapPrefixes,
111
- 'unjoined-dynamic-channels:',
112
- ];
139
+ /** isthmus가 직접 센 한계 중 채널 등록을 가리는 접두사다. */
140
+ const isthmusRegistrationGapPrefixes = ['unjoined-dynamic-channels:'];
113
141
  //# sourceMappingURL=check-report.js.map
@@ -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 ? 'warning' : 'error',\n code: gaps.hidesHandlers\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 ? 'warning' : 'error',\n code: gaps.hidesRegistrations\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/** 수신 측이 스스로 알린 분석 공백이 무엇을 가리는지 나눈 결과다. */\ninterface ReceiverCoverageGaps {\n readonly hidesHandlers: boolean;\n readonly hidesRegistrations: boolean;\n}\n\n/**\n * 수신 측이 핸들러나 등록을 놓쳤을 수 있다고 스스로 알렸는지 확인한다.\n *\n * 이때 \"핸들러 없는 호출\"은 경계 불일치가 아니라 판정 불가다. Objective-C로 쓰인\n * Flutter 핸들러처럼 수신 측 분석에 아예 나타나지 않는 코드가 실제로 있어서,\n * error로 단정하면 이 도구가 없애려던 오탐을 이 도구가 만든다.\n *\n * 공백의 종류는 구분한다. 이름이 리터럴이 아닌 채널 등록 하나가 무관한 메서드\n * 진단까지 무르게 하면 안 된다. 호출 측 한계는 네이티브 코드를 가리지 않으므로\n * 수신 측 플랫폼의 한계만 본다.\n */\nfunction receiverCoverageGaps(\n limitations: readonly JoinLimitation[],\n): ReceiverCoverageGaps {\n const messages = limitations\n .filter(({ platform }) => isReceiverPlatform(platform))\n .map(({ message }) => message);\n return {\n hidesHandlers: messages.some(startsWithAny(handlerCoverageGapPrefixes)),\n hidesRegistrations: messages.some(\n startsWithAny(registrationCoverageGapPrefixes),\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/**\n * 핸들러를 가릴 수 있는 한계다.\n *\n * `unjoined-`는 isthmus가 직접 센 값이라 생산자의 신고 개수에 의존하지 않는다.\n */\nconst handlerCoverageGapPrefixes = [\n ...sourceCoverageGapPrefixes,\n 'opaque-handler-bodies:',\n 'unjoined-dynamic-methods:',\n 'unjoined-unattributed-handlers:',\n];\n\n/** 채널 등록을 가릴 수 있는 한계다. */\nconst registrationCoverageGapPrefixes = [\n ...sourceCoverageGapPrefixes,\n 'unjoined-dynamic-channels:',\n];\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\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"]}
@@ -68,7 +68,7 @@ function issueKey(item) {
68
68
  return JSON.stringify([item.code, logicalKey(item)]);
69
69
  }
70
70
  function limitationKey(item) {
71
- return JSON.stringify([item.platform, item.tool, item.message]);
71
+ return JSON.stringify([item.platform, item.target, item.tool, item.message]);
72
72
  }
73
73
  /** 키 집합 차이를 안정적으로 정렬하고 원래 증거를 보존한다. */
74
74
  function difference(left, right, key) {
@@ -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 { 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, issueKey);\n const resolvedIssues = difference(oldReport.issues, newReport.issues, issueKey);\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 issueKey(item: Parameters<typeof logicalKey>[0] & { readonly code: string }): string {\n return JSON.stringify([item.code, logicalKey(item)]);\n}\n\nfunction limitationKey(item: { readonly platform: string; readonly tool: string; readonly message: string }): string {\n return JSON.stringify([item.platform, item.tool, item.message]);\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":"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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 { 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, issueKey);\n const resolvedIssues = difference(oldReport.issues, newReport.issues, issueKey);\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 issueKey(item: Parameters<typeof logicalKey>[0] & { readonly code: string }): string {\n return JSON.stringify([item.code, logicalKey(item)]);\n}\n\nfunction limitationKey(item: { readonly platform: string; readonly target: string | null; readonly tool: string; readonly message: string }): string {\n return JSON.stringify([item.platform, item.target, item.tool, item.message]);\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"]}
@@ -73,7 +73,8 @@ function renderDot(graph) {
73
73
  }
74
74
  /** 분석 한계를 텍스트 그래프의 한 줄 주석으로 안전하게 만든다. */
75
75
  function limitationComment(limitation) {
76
- const text = `${limitation.platform}/${limitation.tool}: ${limitation.message}`;
76
+ const text = `${limitation.platform}/${limitation.target}/${limitation.tool}: `
77
+ + limitation.message;
77
78
  return text.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]+/gu, ' ');
78
79
  }
79
80
  /** DOT 문자열 리터럴로 안전하게 이스케이프한다. */
@@ -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('&', '&amp;')\n .replaceAll('\"', '&quot;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('|', '&#124;')\n .replaceAll('\\r', '&#13;')\n .replaceAll('\\n', '&#10;');\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 = `${limitation.platform}/${limitation.tool}: ${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":"AAWA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AA6BpD,kCAAkC;AAClC,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC;AAEvC,uCAAuC;AACvC,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC9C;QACE,KAAK,CAAC,4BAA4B,eAAe,cAAc,CAAC,CAAC;QACjE,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED,sCAAsC;AACtC,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IACnD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;IAC3C,CAAC;CACF;AAED,iCAAiC;AACjC,MAAM,UAAU,iBAAiB,CAC/B,KAA0B,EAC1B,MAAkC;IAElC,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACtD,IAAI,MAAM,KAAK,KAAK;QAAE,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;IAC9C,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,4CAA4C;AAC5C,SAAS,aAAa,CAAC,KAA0B;IAC/C,MAAM,WAAW,GAAG,IAAI,GAAG,CACzB,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,KAAK,EAAE,CAAC,CAAC,CACzD,CAAC;IACF,MAAM,KAAK,GAAG,CAAC,cAAc,CAAC,CAAC;IAC/B,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;QAC3C,KAAK,CAAC,IAAI,CAAC,oBAAoB,iBAAiB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QAC/F,KAAK,CAAC,IAAI,CAAC,KAAK,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;IAC1D,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpC,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,SAAS,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACjC,CAAC;AAED,4CAA4C;AAC5C,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO,KAAK;SACT,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC;SACzB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC;SACzB,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC;SACzB,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC/B,CAAC;AAED,uCAAuC;AACvC,SAAS,SAAS,CAAC,KAA0B;IAC3C,MAAM,KAAK,GAAG,CAAC,mBAAmB,EAAE,eAAe,CAAC,CAAC;IACrD,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;QAC3C,KAAK,CAAC,IAAI,CAAC,oBAAoB,iBAAiB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,GAAG,IAAI,CAAC,QAAQ,KAAK,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAClE,KAAK,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACrE,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CACR,KAAK,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG;cACnD,UAAU,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAC3C,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACjC,CAAC;AAED,yCAAyC;AACzC,SAAS,iBAAiB,CAAC,UAA0B;IACnD,MAAM,IAAI,GACR,GAAG,UAAU,CAAC,QAAQ,IAAI,UAAU,CAAC,MAAM,IAAI,UAAU,CAAC,IAAI,IAAI;UAChE,UAAU,CAAC,OAAO,CAAC;IACvB,OAAO,IAAI,CAAC,OAAO,CAAC,6CAA6C,EAAE,GAAG,CAAC,CAAC;AAC1E,CAAC;AAED,iCAAiC;AACjC,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED,mCAAmC;AACnC,SAAS,aAAa,CAAC,QAAwB;IAC7C,OAAO,GAAG,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;AAChE,CAAC;AAED,iCAAiC;AACjC,SAAS,SAAS,CAAC,IAAqB;IACtC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,KAAK,SAAS;QACnC,CAAC,CAAC,IAAI,CAAC,OAAO;QACd,CAAC,CAAC,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;IACrC,OAAO,GAAG,IAAI,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC;AAC/B,CAAC;AAED,8BAA8B;AAC9B,MAAM,UAAU,iBAAiB,CAAC,MAAwB;IACxD,IAAI,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IACD,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAG,IAAI,GAAG,EAA2B,CAAC;IACjD,MAAM,KAAK,GAAsB,EAAE,CAAC;IACpC,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IACtC,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO;QACL,MAAM,EAAE,eAAe;QACvB,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QACnF,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC;QAC/B,WAAW,EAAE,MAAM,CAAC,WAAW;KAChC,CAAC;AACJ,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,qBAAqB,CAAC,MAAwB;IAC5D,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,cAAc,GAAG;QACrB,GAAG,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,EAAE,EAAE,CAC7D,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAU,CAClD;QACD,GAAG,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,EAAE,CACzD,CAAC,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAU,CAC/C;KACF,CAAC;IACF,KAAK,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,cAAc,EAAE,CAAC;QAClD,IACE,OAAO,GAAG,CAAC;YACX,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,eAAe,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,EAC/D,CAAC;YACD,MAAM,IAAI,qBAAqB,EAAE,CAAC;QACpC,CAAC;QACD,SAAS,IAAI,SAAS,GAAG,OAAO,CAAC;IACnC,CAAC;AACH,CAAC;AAED,8BAA8B;AAC9B,SAAS,eAAe,CACtB,MAAwB,EACxB,KAAmC,EACnC,KAAwB;IAExB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,eAAe,EAAE,CAAC;QAC3C,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YACvC,KAAK,MAAM,YAAY,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;gBAC/C,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;gBACtC,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;gBACxC,KAAK,CAAC,IAAI,CAAC;oBACT,IAAI;oBACJ,EAAE;oBACF,IAAI,EAAE,SAAS;oBACf,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,OAAO,EAAE,KAAK,CAAC,OAAO;iBACvB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,gCAAgC;AAChC,SAAS,cAAc,CACrB,MAAwB,EACxB,KAAmC,EACnC,KAAwB;IAExB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;QAC1C,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;YAC3C,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACrC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;gBACxC,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBACnC,KAAK,CAAC,IAAI,CAAC;oBACT,IAAI;oBACJ,EAAE;oBACF,IAAI,EAAE,QAAQ;oBACd,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,MAAM,EAAE,KAAK,CAAC,MAAM;iBACrB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,uCAAuC;AACvC,SAAS,OAAO,CACd,KAAmC,EACnC,QAAwB;IAExB,MAAM,EAAE,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,KAAK,CAAC,GAAG,CACP,EAAE,EACF,QAAQ,CAAC,MAAM,KAAK,SAAS;YAC3B,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE;YAClE,CAAC,CAAC;gBACE,EAAE;gBACF,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,MAAM,EAAE,QAAQ,CAAC,MAAM;aAC5B,CACF,CAAC;IACJ,CAAC;SAAM,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC9D,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,IAAI,0BAA0B,CAClC,4CAA4C,CAC7C,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,sCAAsC;AACtC,SAAS,YAAY,CACnB,IAA8B,EAC9B,KAAmB;IAEnB,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACrC,IAAI,IAAI,CAAC,aAAa,KAAK,KAAK,CAAC,aAAa;QAAE,OAAO,SAAS,CAAC;IACjE,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG,EAAE,CAAC;QAChF,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC;IAClC,OAAO,GAAG,KAAK,SAAS;QACtB,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE;QACvC,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,GAAG,EAAE,CAAC;AACjD,CAAC;AAED,wCAAwC;AACxC,SAAS,UAAU,CAAC,QAAwB;IAC1C,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC;IACjD,OAAO,GAAG,QAAQ,CAAC,QAAQ,IAAI,IAAI,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;AAC1D,CAAC;AAED,+BAA+B;AAC/B,SAAS,YAAY,CAAC,IAAqB,EAAE,KAAsB;IACjE,OAAO,CACL,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC;QACrC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC;QACrC,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC,CAClC,CAAC;AACJ,CAAC","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('&', '&amp;')\n .replaceAll('\"', '&quot;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('|', '&#124;')\n .replaceAll('\\r', '&#13;')\n .replaceAll('\\n', '&#10;');\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isthmus-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Join bridge facts across cross-platform application boundaries.",
5
5
  "keywords": [
6
6
  "flutter",