isthmus-cli 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +135 -31
- package/README.md +134 -16
- package/Skills/isthmus/SKILL.md +17 -7
- package/compatibility.json +10 -0
- package/dist/cli/check-command.d.ts +2 -2
- package/dist/cli/check-command.js +22 -9
- package/dist/cli/check-command.js.map +1 -1
- package/dist/cli/command-support.d.ts +19 -1
- package/dist/cli/command-support.js +37 -8
- package/dist/cli/command-support.js.map +1 -1
- package/dist/cli/diff-command.js +4 -3
- package/dist/cli/diff-command.js.map +1 -1
- package/dist/cli/doctor-command.d.ts +11 -0
- package/dist/cli/doctor-command.js +128 -0
- package/dist/cli/doctor-command.js.map +1 -0
- package/dist/cli/extract-js-command.d.ts +33 -0
- package/dist/cli/extract-js-command.js +233 -0
- package/dist/cli/extract-js-command.js.map +1 -0
- package/dist/cli/graph-command.js +13 -6
- package/dist/cli/graph-command.js.map +1 -1
- package/dist/cli/init-command.d.ts +11 -0
- package/dist/cli/init-command.js +115 -0
- package/dist/cli/init-command.js.map +1 -0
- package/dist/cli/main.js +52 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/mcp-server.d.ts +16 -0
- package/dist/cli/mcp-server.js +534 -0
- package/dist/cli/mcp-server.js.map +1 -0
- package/dist/cli/query-command.js +13 -6
- package/dist/cli/query-command.js.map +1 -1
- package/dist/cli/retentions-command.js +14 -7
- package/dist/cli/retentions-command.js.map +1 -1
- package/dist/cli/serve-command.d.ts +13 -0
- package/dist/cli/serve-command.js +43 -0
- package/dist/cli/serve-command.js.map +1 -0
- package/dist/exchange/capture-config.d.ts +18 -0
- package/dist/exchange/capture-config.js +100 -0
- package/dist/exchange/capture-config.js.map +1 -0
- package/dist/exchange/messages.d.ts +9 -24
- package/dist/exchange/messages.js +25 -55
- package/dist/exchange/messages.js.map +1 -1
- package/dist/exchange/parse.d.ts +61 -0
- package/dist/exchange/parse.js +115 -12
- package/dist/exchange/parse.js.map +1 -1
- package/dist/extract/js-document.d.ts +22 -0
- package/dist/extract/js-document.js +230 -0
- package/dist/extract/js-document.js.map +1 -0
- package/dist/extract/js-scan.d.ts +93 -0
- package/dist/extract/js-scan.js +1205 -0
- package/dist/extract/js-scan.js.map +1 -0
- package/dist/extract/lexer.d.ts +26 -0
- package/dist/extract/lexer.js +316 -0
- package/dist/extract/lexer.js.map +1 -0
- package/dist/join/join.d.ts +66 -1
- package/dist/join/join.js +171 -1
- package/dist/join/join.js.map +1 -1
- package/dist/join/messages.d.ts +7 -4
- package/dist/join/messages.js +15 -8
- package/dist/join/messages.js.map +1 -1
- package/dist/report/check-report.d.ts +20 -3
- package/dist/report/check-report.js +240 -8
- package/dist/report/check-report.js.map +1 -1
- package/dist/report/codequality.d.ts +45 -0
- package/dist/report/codequality.js +74 -0
- package/dist/report/codequality.js.map +1 -0
- package/dist/report/diff.d.ts +25 -3
- package/dist/report/diff.js +91 -11
- package/dist/report/diff.js.map +1 -1
- package/dist/report/graph.d.ts +10 -4
- package/dist/report/graph.js +60 -6
- package/dist/report/graph.js.map +1 -1
- package/dist/report/preflight-view.js +28 -1
- package/dist/report/preflight-view.js.map +1 -1
- package/dist/report/preflight.d.ts +4 -4
- package/dist/report/preflight.js +81 -46
- package/dist/report/preflight.js.map +1 -1
- package/dist/report/query.d.ts +11 -4
- package/dist/report/query.js +84 -17
- package/dist/report/query.js.map +1 -1
- package/dist/report/retentions.d.ts +17 -5
- package/dist/report/retentions.js +85 -8
- package/dist/report/retentions.js.map +1 -1
- package/dist/report/rules.d.ts +16 -0
- package/dist/report/rules.js +30 -0
- package/dist/report/rules.js.map +1 -0
- package/dist/report/sarif.d.ts +2 -8
- package/dist/report/sarif.js +2 -10
- package/dist/report/sarif.js.map +1 -1
- package/docs/BRIDGE-EVENTS.md +87 -0
- package/docs/BRIDGE-MESSAGES.md +23 -8
- package/docs/GRAPH-EXCHANGE.md +141 -14
- package/docs/IMPACT.md +1 -1
- package/docs/MCP.md +62 -0
- package/docs/PREFLIGHT.md +24 -8
- package/docs/RUNTIME.md +2 -2
- package/docs/TOOLCHAIN.md +5 -5
- package/package.json +5 -2
- package/scripts/capture-preflight.mjs +32 -44
- package/scripts/run-child.mjs +1 -0
package/dist/join/messages.js
CHANGED
|
@@ -2,11 +2,16 @@ import { compareStrings } from "../compare.js";
|
|
|
2
2
|
import { validateMessageDocuments } from "../exchange/messages.js";
|
|
3
3
|
import { BridgeJoinValidationError } from "./join.js";
|
|
4
4
|
import { MessageAddressIndex } from "./message-address.js";
|
|
5
|
-
/**
|
|
5
|
+
/** 발신 측 사실 종류다. Dart 문서만 이 종류를 담는다. */
|
|
6
|
+
function isSenderKind(kind) {
|
|
7
|
+
return kind === 'message-send' || kind === 'stream-listen';
|
|
8
|
+
}
|
|
9
|
+
/** 서로 같은 project의 양쪽 v2 문서만 조인하며 transport와 MethodChannel 키 공간을 분리한다. */
|
|
6
10
|
export function joinMessageBridges(documents, project) {
|
|
7
11
|
if (documents.length === 0)
|
|
8
|
-
return { routes: [], unresolved: [], limitations: [] };
|
|
12
|
+
return { routes: [], unresolved: [], limitations: [], observedFacts: 0 };
|
|
9
13
|
validateMessageDocuments(documents, project);
|
|
14
|
+
const observedFacts = documents.reduce((total, document) => total + document.facts.length, 0);
|
|
10
15
|
const groups = new Map();
|
|
11
16
|
const unresolved = new Map();
|
|
12
17
|
const limitations = [];
|
|
@@ -29,13 +34,13 @@ export function joinMessageBridges(documents, project) {
|
|
|
29
34
|
continue;
|
|
30
35
|
}
|
|
31
36
|
const matching = fact.dynamic ? 'prefix' : 'literal';
|
|
32
|
-
const key = JSON.stringify([matching, channel]);
|
|
37
|
+
const key = JSON.stringify([document.transport, matching, channel]);
|
|
33
38
|
let group = groups.get(key);
|
|
34
39
|
if (group === undefined) {
|
|
35
|
-
group = { channel, matching, senders: new Map(), handlers: new Map() };
|
|
40
|
+
group = { transport: document.transport, channel, matching, senders: new Map(), handlers: new Map() };
|
|
36
41
|
groups.set(key, group);
|
|
37
42
|
}
|
|
38
|
-
(fact.kind
|
|
43
|
+
(isSenderKind(fact.kind) ? group.senders : group.handlers).set(identity, endpoint);
|
|
39
44
|
}
|
|
40
45
|
}
|
|
41
46
|
const endpoints = (values) => [...values.entries()]
|
|
@@ -59,7 +64,8 @@ export function joinMessageBridges(documents, project) {
|
|
|
59
64
|
};
|
|
60
65
|
for (const group of groups.values()) {
|
|
61
66
|
for (const prefix of prefixIndex.matching(group.channel)) {
|
|
62
|
-
|
|
67
|
+
// transport가 다른 주소는 같은 이름이라도 서로 다른 경계다.
|
|
68
|
+
if (prefix.group === group || prefix.group.transport !== group.transport)
|
|
63
69
|
continue;
|
|
64
70
|
if (group.matching === 'literal') {
|
|
65
71
|
merge(prefix.group.senders, group.senders);
|
|
@@ -73,8 +79,9 @@ export function joinMessageBridges(documents, project) {
|
|
|
73
79
|
}
|
|
74
80
|
return {
|
|
75
81
|
routes: [...groups.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, group]) => ({
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
transport: group.transport, channel: group.channel, matching: group.matching,
|
|
83
|
+
senders: endpoints(group.senders), handlers: endpoints(group.handlers),
|
|
84
|
+
})), unresolved: endpoints(unresolved), limitations, observedFacts,
|
|
78
85
|
};
|
|
79
86
|
}
|
|
80
87
|
//# sourceMappingURL=messages.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/join/messages.ts"],"names":[],"mappings":"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","sourcesContent":["import { compareStrings } from '../compare.ts';\nimport type { BridgeHandlerDependency, BridgeHandlerScope, BridgeMessageDocument } from '../exchange/messages.ts';\nimport { validateMessageDocuments } from '../exchange/messages.ts';\nimport type { BridgeEndpoint, JoinLimitation } from './join.ts';\nimport { BridgeJoinValidationError } from './join.ts';\nimport { MessageAddressIndex } from './message-address.ts';\n\n/** 동적 접두사의 원래 표현식도 근거에 보존한다. */\nexport interface MessageEndpoint extends BridgeEndpoint {\n readonly channelExpression?: string;\n readonly handlerScope?: BridgeHandlerScope;\n readonly dependencies?: readonly BridgeHandlerDependency[];\n}\n\n/** literal 주소와 prefix 후보를 다른 키로 연결한다. */\nexport interface MessageBridgeRoute {\n readonly channel: string;\n readonly matching: 'literal' | 'prefix';\n readonly senders: readonly MessageEndpoint[];\n readonly handlers: readonly MessageEndpoint[];\n}\n\n/** Basic 범위에서 관찰한 경계와 끝내 연결하지 못한 사실이다. */\nexport interface MessageBridgeJoin {\n readonly routes: readonly MessageBridgeRoute[];\n readonly unresolved: readonly MessageEndpoint[];\n readonly limitations: readonly JoinLimitation[];\n}\n\n/** 서로 같은 project의 양쪽 Basic 문서만 조인하며 MethodChannel 키 공간을 사용하지 않는다. */\nexport function joinMessageBridges(documents: readonly BridgeMessageDocument[], project: string): MessageBridgeJoin {\n if (documents.length === 0) return { routes: [], unresolved: [], limitations: [] };\n validateMessageDocuments(documents, project);\n const groups = new Map<string, { channel: string; matching: 'literal' | 'prefix';\n senders: Map<string, MessageEndpoint>; handlers: Map<string, MessageEndpoint> }>();\n const unresolved = new Map<string, MessageEndpoint>();\n const limitations: JoinLimitation[] = [];\n for (const document of documents) {\n for (const message of document.limitations) limitations.push({ platform: document.platform,\n target: document.target, tool: document.tool.name, message });\n for (const fact of document.facts) {\n const endpoint: MessageEndpoint = { platform: document.platform, location: fact.location,\n ...(fact.symbol === undefined ? {} : { symbol: fact.symbol }),\n ...(fact.sourceLanguage === undefined ? {} : { sourceLanguage: fact.sourceLanguage }),\n ...(fact.handlerScope === undefined ? {} : { handlerScope: fact.handlerScope, dependencies: fact.dependencies! }),\n ...(fact.dynamic && fact.channel !== null ? { channelExpression: fact.channel } : {}) };\n const identity = JSON.stringify([document.platform, fact.location.path, fact.location.line, fact.location.column,\n fact.symbol?.usr ?? null, fact.symbol?.qualifiedName ?? null, fact.sourceLanguage ?? null,\n fact.dynamic, fact.channel, fact.channelPrefix ?? null, fact.handlerScope ?? null, fact.dependencies ?? null]);\n const channel = fact.dynamic ? fact.channelPrefix : fact.channel;\n if (!channel) { unresolved.set(identity, endpoint); continue; }\n const matching = fact.dynamic ? 'prefix' : 'literal';\n const key = JSON.stringify([matching, channel]);\n let group = groups.get(key);\n if (group === undefined) {\n group = { channel, matching, senders: new Map(), handlers: new Map() };\n groups.set(key, group);\n }\n (fact.kind === 'message-send' ? group.senders : group.handlers).set(identity, endpoint);\n }\n }\n const endpoints = (values: Map<string, MessageEndpoint>) => [...values.entries()]\n .sort(([a], [b]) => compareStrings(a, b)).map(([, value]) => value);\n // 전파로 추가한 literal을 더 좁은 prefix로 다시 복사하면 서로 다른 주소가 연결된다.\n // 원래 prefix 사실만 스냅샷으로 두고, 교집합 범위에 한 번씩 투영한다.\n const prefixes = [...groups.values()].filter(({ matching }) => matching === 'prefix').map((group) => ({\n group, senders: new Map(group.senders), handlers: new Map(group.handlers),\n }));\n const prefixIndex = new MessageAddressIndex<(typeof prefixes)[number]>();\n for (const prefix of prefixes) prefixIndex.add(prefix.group.channel, 'prefix', prefix);\n let projections = 0;\n const merge = (target: Map<string, MessageEndpoint>, source: Map<string, MessageEndpoint>) => {\n for (const [key, endpoint] of source) if (!target.has(key)) {\n if (++projections > 1_000_000) throw new BridgeJoinValidationError('Message prefix projection budget exceeded.');\n target.set(key, endpoint);\n }\n };\n for (const group of groups.values()) {\n for (const prefix of prefixIndex.matching(group.channel)) {\n if (prefix.group === group) continue;\n if (group.matching === 'literal') {\n merge(prefix.group.senders, group.senders); merge(prefix.group.handlers, group.handlers);\n } else {\n merge(group.senders, prefix.senders); merge(group.handlers, prefix.handlers);\n }\n }\n }\n return {\n routes: [...groups.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, group]) => ({\n channel: group.channel, matching: group.matching, senders: endpoints(group.senders), handlers: endpoints(group.handlers),\n })), unresolved: endpoints(unresolved), limitations,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/join/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAEnE,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AA2B3D,uCAAuC;AACvC,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,IAAI,KAAK,cAAc,IAAI,IAAI,KAAK,eAAe,CAAC;AAC7D,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,kBAAkB,CAAC,SAA2C,EAAE,OAAe;IAC7F,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;IACrG,wBAAwB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC7C,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC9F,MAAM,MAAM,GAAG,IAAI,GAAG,EAC8D,CAAC;IACrF,MAAM,UAAU,GAAG,IAAI,GAAG,EAA2B,CAAC;IACtD,MAAM,WAAW,GAAqB,EAAE,CAAC;IACzC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,WAAW;YAAE,WAAW,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBACxF,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAChE,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;YAClC,MAAM,QAAQ,GAAoB,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACtF,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC7D,GAAG,CAAC,IAAI,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC;gBACrF,GAAG,CAAC,IAAI,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,YAAY,EAAE,IAAI,CAAC,YAAa,EAAE,CAAC;gBACjH,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;YAC1F,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM;gBAC9G,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa,IAAI,IAAI,EAAE,IAAI,CAAC,cAAc,IAAI,IAAI;gBACzF,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,aAAa,IAAI,IAAI,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC;YACjH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;YACjE,IAAI,CAAC,OAAO,EAAE,CAAC;gBAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;gBAAC,SAAS;YAAC,CAAC;YAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;YACrD,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;YACpE,IAAI,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,KAAK,GAAG,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;gBACtG,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACzB,CAAC;YACD,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;IACD,MAAM,SAAS,GAAG,CAAC,MAAoC,EAAE,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;SAC9E,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;IACtE,wDAAwD;IACxD,6CAA6C;IAC7C,MAAM,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACpG,KAAK,EAAE,OAAO,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC;KAC1E,CAAC,CAAC,CAAC;IACJ,MAAM,WAAW,GAAG,IAAI,mBAAmB,EAA6B,CAAC;IACzE,KAAK,MAAM,MAAM,IAAI,QAAQ;QAAE,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACvF,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,MAAM,KAAK,GAAG,CAAC,MAAoC,EAAE,MAAoC,EAAE,EAAE;QAC3F,KAAK,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,MAAM;YAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC3D,IAAI,EAAE,WAAW,GAAG,SAAS;oBAAE,MAAM,IAAI,yBAAyB,CAAC,4CAA4C,CAAC,CAAC;gBACjH,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;YAC5B,CAAC;IACH,CAAC,CAAC;IACF,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;QACpC,KAAK,MAAM,MAAM,IAAI,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACzD,wCAAwC;YACxC,IAAI,MAAM,CAAC,KAAK,KAAK,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS;gBAAE,SAAS;YACnF,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACjC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;gBAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC3F,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;gBAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC/E,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO;QACL,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YACzF,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ;YAC5E,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC;SACvE,CAAC,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,UAAU,CAAC,EAAE,WAAW,EAAE,aAAa;KACnE,CAAC;AACJ,CAAC","sourcesContent":["import { compareStrings } from '../compare.ts';\nimport type { BridgeHandlerDependency, BridgeHandlerScope, BridgeMessageDocument, BridgeMessageTransport } from '../exchange/messages.ts';\nimport { validateMessageDocuments } from '../exchange/messages.ts';\nimport type { BridgeEndpoint, JoinLimitation } from './join.ts';\nimport { BridgeJoinValidationError } from './join.ts';\nimport { MessageAddressIndex } from './message-address.ts';\n\n/** 동적 접두사의 원래 표현식도 근거에 보존한다. */\nexport interface MessageEndpoint extends BridgeEndpoint {\n readonly channelExpression?: string;\n readonly handlerScope?: BridgeHandlerScope;\n readonly dependencies?: readonly BridgeHandlerDependency[];\n}\n\n/** literal 주소와 prefix 후보를 다른 키로 연결한다. transport는 같은 주소라도 섞지 않는다. */\nexport interface MessageBridgeRoute {\n readonly transport: BridgeMessageTransport;\n readonly channel: string;\n readonly matching: 'literal' | 'prefix';\n readonly senders: readonly MessageEndpoint[];\n readonly handlers: readonly MessageEndpoint[];\n}\n\n/** v2 transport 범위에서 관찰한 경계와 끝내 연결하지 못한 사실이다. */\nexport interface MessageBridgeJoin {\n readonly routes: readonly MessageBridgeRoute[];\n readonly unresolved: readonly MessageEndpoint[];\n readonly limitations: readonly JoinLimitation[];\n /** 입력 v2 문서 전체가 관찰한 fact 수다. 조인 여부와 무관한 관찰량이다. */\n readonly observedFacts: number;\n}\n\n/** 발신 측 사실 종류다. Dart 문서만 이 종류를 담는다. */\nfunction isSenderKind(kind: string): boolean {\n return kind === 'message-send' || kind === 'stream-listen';\n}\n\n/** 서로 같은 project의 양쪽 v2 문서만 조인하며 transport와 MethodChannel 키 공간을 분리한다. */\nexport function joinMessageBridges(documents: readonly BridgeMessageDocument[], project: string): MessageBridgeJoin {\n if (documents.length === 0) return { routes: [], unresolved: [], limitations: [], observedFacts: 0 };\n validateMessageDocuments(documents, project);\n const observedFacts = documents.reduce((total, document) => total + document.facts.length, 0);\n const groups = new Map<string, { transport: BridgeMessageTransport; channel: string; matching: 'literal' | 'prefix';\n senders: Map<string, MessageEndpoint>; handlers: Map<string, MessageEndpoint> }>();\n const unresolved = new Map<string, MessageEndpoint>();\n const limitations: JoinLimitation[] = [];\n for (const document of documents) {\n for (const message of document.limitations) limitations.push({ platform: document.platform,\n target: document.target, tool: document.tool.name, message });\n for (const fact of document.facts) {\n const endpoint: MessageEndpoint = { platform: document.platform, location: fact.location,\n ...(fact.symbol === undefined ? {} : { symbol: fact.symbol }),\n ...(fact.sourceLanguage === undefined ? {} : { sourceLanguage: fact.sourceLanguage }),\n ...(fact.handlerScope === undefined ? {} : { handlerScope: fact.handlerScope, dependencies: fact.dependencies! }),\n ...(fact.dynamic && fact.channel !== null ? { channelExpression: fact.channel } : {}) };\n const identity = JSON.stringify([document.platform, fact.location.path, fact.location.line, fact.location.column,\n fact.symbol?.usr ?? null, fact.symbol?.qualifiedName ?? null, fact.sourceLanguage ?? null,\n fact.dynamic, fact.channel, fact.channelPrefix ?? null, fact.handlerScope ?? null, fact.dependencies ?? null]);\n const channel = fact.dynamic ? fact.channelPrefix : fact.channel;\n if (!channel) { unresolved.set(identity, endpoint); continue; }\n const matching = fact.dynamic ? 'prefix' : 'literal';\n const key = JSON.stringify([document.transport, matching, channel]);\n let group = groups.get(key);\n if (group === undefined) {\n group = { transport: document.transport, channel, matching, senders: new Map(), handlers: new Map() };\n groups.set(key, group);\n }\n (isSenderKind(fact.kind) ? group.senders : group.handlers).set(identity, endpoint);\n }\n }\n const endpoints = (values: Map<string, MessageEndpoint>) => [...values.entries()]\n .sort(([a], [b]) => compareStrings(a, b)).map(([, value]) => value);\n // 전파로 추가한 literal을 더 좁은 prefix로 다시 복사하면 서로 다른 주소가 연결된다.\n // 원래 prefix 사실만 스냅샷으로 두고, 교집합 범위에 한 번씩 투영한다.\n const prefixes = [...groups.values()].filter(({ matching }) => matching === 'prefix').map((group) => ({\n group, senders: new Map(group.senders), handlers: new Map(group.handlers),\n }));\n const prefixIndex = new MessageAddressIndex<(typeof prefixes)[number]>();\n for (const prefix of prefixes) prefixIndex.add(prefix.group.channel, 'prefix', prefix);\n let projections = 0;\n const merge = (target: Map<string, MessageEndpoint>, source: Map<string, MessageEndpoint>) => {\n for (const [key, endpoint] of source) if (!target.has(key)) {\n if (++projections > 1_000_000) throw new BridgeJoinValidationError('Message prefix projection budget exceeded.');\n target.set(key, endpoint);\n }\n };\n for (const group of groups.values()) {\n for (const prefix of prefixIndex.matching(group.channel)) {\n // transport가 다른 주소는 같은 이름이라도 서로 다른 경계다.\n if (prefix.group === group || prefix.group.transport !== group.transport) continue;\n if (group.matching === 'literal') {\n merge(prefix.group.senders, group.senders); merge(prefix.group.handlers, group.handlers);\n } else {\n merge(group.senders, prefix.senders); merge(group.handlers, prefix.handlers);\n }\n }\n }\n return {\n routes: [...groups.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, group]) => ({\n transport: group.transport, channel: group.channel, matching: group.matching,\n senders: endpoints(group.senders), handlers: endpoints(group.handlers),\n })), unresolved: endpoints(unresolved), limitations, observedFacts,\n };\n}\n"]}
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import type { BridgeTarget } from '../exchange/parse.ts';
|
|
2
2
|
import type { BridgeEndpoint, BridgeJoinResult, JoinLimitation } from '../join/join.ts';
|
|
3
|
+
import type { MessageBridgeJoin } from '../join/messages.ts';
|
|
3
4
|
/** check 결과 개수를 빠르게 판단할 요약이다. */
|
|
4
5
|
export interface CheckSummary {
|
|
5
6
|
readonly errors: number;
|
|
6
7
|
readonly warnings: number;
|
|
7
8
|
readonly matchedChannels: number;
|
|
8
9
|
readonly matchedMethods: number;
|
|
10
|
+
readonly matchedModules: number;
|
|
11
|
+
readonly matchedComponents: number;
|
|
12
|
+
/**
|
|
13
|
+
* v2 BasicMessageChannel 입력이 있을 때만 실린다 — 양쪽이 관찰된 채널 수다.
|
|
14
|
+
*
|
|
15
|
+
* v1만 입력하면 필드 자체가 없어 기존 소비자의 요약 비교를 깨지 않는다.
|
|
16
|
+
*/
|
|
17
|
+
readonly matchedMessages?: number;
|
|
18
|
+
/** v2 EventChannel 입력이 있을 때만 실리는, 양쪽이 관찰된 스트림 수다. */
|
|
19
|
+
readonly matchedStreams?: number;
|
|
9
20
|
/** 입력 문서 전체가 관찰한 fact 수다. 0이면 아무것도 관찰하지 못한 실행이다. */
|
|
10
21
|
readonly observedFacts: number;
|
|
11
22
|
/** 이 실행에 보고된 분석 한계 수다. */
|
|
@@ -21,7 +32,7 @@ export interface CheckSummary {
|
|
|
21
32
|
* `-unverified` 종류는 수신 측이 스스로 분석 공백을 신고해, 핸들러가 없는 것인지
|
|
22
33
|
* 보지 못한 것인지 구분할 수 없는 경우다. 사실과 증거는 같지만 판정이 아니다.
|
|
23
34
|
*/
|
|
24
|
-
export declare const checkIssueCodes: readonly ["unhandled-invocation", "unhandled-invocation-unverified", "unregistered-channel-creation", "unregistered-channel-creation-unverified", "registration-without-creation", "handler-without-invocation"];
|
|
35
|
+
export declare const checkIssueCodes: readonly ["unhandled-invocation", "unhandled-invocation-unverified", "unregistered-channel-creation", "unregistered-channel-creation-unverified", "registration-without-creation", "handler-without-invocation", "module-import-without-export", "module-import-without-export-unverified", "module-import-without-export-optional", "module-import-mechanism-mismatch", "module-export-without-import", "module-export-mechanism-mismatch", "component-require-without-export", "component-require-without-export-unverified", "component-require-mechanism-mismatch", "component-export-without-require", "component-export-mechanism-mismatch", "unhandled-message-send", "unhandled-message-send-unverified", "message-handler-without-send", "unhandled-stream-listen", "unhandled-stream-listen-unverified", "stream-handler-without-listen"];
|
|
25
36
|
/** check가 보고하는 안정적인 진단 종류다. */
|
|
26
37
|
export type CheckIssueCode = (typeof checkIssueCodes)[number];
|
|
27
38
|
/** 삭제 판정 없이 경계 불일치 사실과 증거만 전달한다. */
|
|
@@ -51,5 +62,11 @@ export interface CheckReport {
|
|
|
51
62
|
}
|
|
52
63
|
/** check 문서를 결정적인 JSON 문자열로 인코딩한다. */
|
|
53
64
|
export declare function encodeCheckReport(report: CheckReport): string;
|
|
54
|
-
/**
|
|
55
|
-
|
|
65
|
+
/**
|
|
66
|
+
* v1 조인 결과와 선택적 v2 메시지 조인을 정책 심각도가 포함된 check 문서로 바꾼다.
|
|
67
|
+
*
|
|
68
|
+
* `messages`를 주면 transport별 진단 코드와 수신 공백 완화를 함께 싣고,
|
|
69
|
+
* 요약에 `matchedMessages`/`matchedStreams`를 더한다. v1만 쓰는 호출자의
|
|
70
|
+
* 출력은 그대로 유지된다.
|
|
71
|
+
*/
|
|
72
|
+
export declare function createCheckReport(joined: BridgeJoinResult, messages?: MessageBridgeJoin): CheckReport;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isReceiverPlatform } from "../exchange/parse.js";
|
|
2
|
-
import { isBridgeJoinDeferred } from "../join/join.js";
|
|
2
|
+
import { compareLimitations, isBridgeJoinDeferred } from "../join/join.js";
|
|
3
3
|
import { encodeSortedJson } from "./sorted-json.js";
|
|
4
4
|
/**
|
|
5
5
|
* check가 보고하는 안정적인 진단 종류다.
|
|
@@ -14,17 +14,44 @@ export const checkIssueCodes = [
|
|
|
14
14
|
'unregistered-channel-creation-unverified',
|
|
15
15
|
'registration-without-creation',
|
|
16
16
|
'handler-without-invocation',
|
|
17
|
+
'module-import-without-export',
|
|
18
|
+
'module-import-without-export-unverified',
|
|
19
|
+
'module-import-without-export-optional',
|
|
20
|
+
'module-import-mechanism-mismatch',
|
|
21
|
+
'module-export-without-import',
|
|
22
|
+
'module-export-mechanism-mismatch',
|
|
23
|
+
'component-require-without-export',
|
|
24
|
+
'component-require-without-export-unverified',
|
|
25
|
+
'component-require-mechanism-mismatch',
|
|
26
|
+
'component-export-without-require',
|
|
27
|
+
'component-export-mechanism-mismatch',
|
|
28
|
+
'unhandled-message-send',
|
|
29
|
+
'unhandled-message-send-unverified',
|
|
30
|
+
'message-handler-without-send',
|
|
31
|
+
'unhandled-stream-listen',
|
|
32
|
+
'unhandled-stream-listen-unverified',
|
|
33
|
+
'stream-handler-without-listen',
|
|
17
34
|
];
|
|
18
35
|
/** check 문서를 결정적인 JSON 문자열로 인코딩한다. */
|
|
19
36
|
export function encodeCheckReport(report) {
|
|
20
37
|
return encodeSortedJson(report);
|
|
21
38
|
}
|
|
22
|
-
/**
|
|
23
|
-
|
|
39
|
+
/**
|
|
40
|
+
* v1 조인 결과와 선택적 v2 메시지 조인을 정책 심각도가 포함된 check 문서로 바꾼다.
|
|
41
|
+
*
|
|
42
|
+
* `messages`를 주면 transport별 진단 코드와 수신 공백 완화를 함께 싣고,
|
|
43
|
+
* 요약에 `matchedMessages`/`matchedStreams`를 더한다. v1만 쓰는 호출자의
|
|
44
|
+
* 출력은 그대로 유지된다.
|
|
45
|
+
*/
|
|
46
|
+
export function createCheckReport(joined, messages) {
|
|
24
47
|
if (isBridgeJoinDeferred(joined)) {
|
|
25
48
|
throw new Error('Cannot create a check report from a deferred bridge join.');
|
|
26
49
|
}
|
|
27
|
-
const
|
|
50
|
+
const limitations = messages === undefined
|
|
51
|
+
? joined.limitations
|
|
52
|
+
: [...joined.limitations, ...messages.limitations,
|
|
53
|
+
...messageConsumerLimitations(messages)].sort(compareLimitations);
|
|
54
|
+
const gaps = receiverCoverageGaps(limitations);
|
|
28
55
|
const issues = [
|
|
29
56
|
...joined.unhandledInvocations.map((item) => ({
|
|
30
57
|
severity: gaps.hidesHandlers(item.target, item.channel) ? 'warning' : 'error',
|
|
@@ -60,6 +87,78 @@ export function createCheckReport(joined) {
|
|
|
60
87
|
method: item.method,
|
|
61
88
|
evidence: item.handlers,
|
|
62
89
|
})),
|
|
90
|
+
...joined.moduleImportsWithoutExports.map((item) => {
|
|
91
|
+
// 같은 이름의 export가 mechanism만 다르게 관찰됐다면 진짜 공백이 아니라
|
|
92
|
+
// 해석 경로 불일치다 — 코어 호출×Expo export의 상호운용 여부가 미해결이므로
|
|
93
|
+
// error가 아니라 별도 warning으로 내린다. optional 판정보다 앞서야 한다 —
|
|
94
|
+
// 수신 측 export가 관찰된 상황에 "미검증" 문구를 붙이면 틀리다.
|
|
95
|
+
const mismatched = item.incompatibleReceivers !== undefined;
|
|
96
|
+
// 호출자 전부가 부재 허용 API(requireOptionalNativeModule·Registry.get)를
|
|
97
|
+
// 썼다면 미수출은 크래시가 아니라 null 반환이다 — 호출자가 감당하므로
|
|
98
|
+
// warning으로 내린다. 하나라도 던지는 호출자가 있으면 error를 유지한다.
|
|
99
|
+
const allOptional = item.callers.length > 0 &&
|
|
100
|
+
item.callers.every((caller) => caller.optional === true);
|
|
101
|
+
return {
|
|
102
|
+
severity: mismatched || allOptional || gaps.hidesExports(item.target)
|
|
103
|
+
? 'warning'
|
|
104
|
+
: 'error',
|
|
105
|
+
code: mismatched
|
|
106
|
+
? 'module-import-mechanism-mismatch'
|
|
107
|
+
: allOptional
|
|
108
|
+
? 'module-import-without-export-optional'
|
|
109
|
+
: gaps.hidesExports(item.target)
|
|
110
|
+
? 'module-import-without-export-unverified'
|
|
111
|
+
: 'module-import-without-export',
|
|
112
|
+
target: item.target,
|
|
113
|
+
channel: item.channel,
|
|
114
|
+
// 불일치 수신 측 위치까지 실어야 어느 export가 다른 경로로
|
|
115
|
+
// 해석되는지 보고서에서 보인다.
|
|
116
|
+
evidence: [...item.callers, ...(item.incompatibleReceivers ?? [])],
|
|
117
|
+
};
|
|
118
|
+
}),
|
|
119
|
+
...joined.moduleExportsWithoutImports.map((item) => ({
|
|
120
|
+
severity: 'warning',
|
|
121
|
+
// 같은 이름의 호출이 mechanism만 다르게 관찰됐다면 미호출이 아니라
|
|
122
|
+
// 해석 경로 불일치다.
|
|
123
|
+
code: item.incompatibleCallers !== undefined
|
|
124
|
+
? 'module-export-mechanism-mismatch'
|
|
125
|
+
: 'module-export-without-import',
|
|
126
|
+
target: item.target,
|
|
127
|
+
channel: item.channel,
|
|
128
|
+
evidence: [...item.receivers, ...(item.incompatibleCallers ?? [])],
|
|
129
|
+
})),
|
|
130
|
+
...joined.componentRequiresWithoutExports.map((item) => {
|
|
131
|
+
const mismatched = item.incompatibleReceivers !== undefined;
|
|
132
|
+
// Expo 측 requireNativeViewManager에는 코어 폴백이 없어, 관찰된 export가
|
|
133
|
+
// 모두 코어라면 호출은 확정된 미수출 error다. 코어 호출×Expo export만은
|
|
134
|
+
// 상호운용이 미해결이므로 warning으로 내린다.
|
|
135
|
+
const allExpoCallers = item.callers.length > 0 &&
|
|
136
|
+
item.callers.every((caller) => caller.mechanism === 'expo');
|
|
137
|
+
const unresolved = mismatched && !allExpoCallers;
|
|
138
|
+
return {
|
|
139
|
+
severity: unresolved || gaps.hidesExports(item.target)
|
|
140
|
+
? 'warning'
|
|
141
|
+
: 'error',
|
|
142
|
+
code: unresolved
|
|
143
|
+
? 'component-require-mechanism-mismatch'
|
|
144
|
+
: gaps.hidesExports(item.target)
|
|
145
|
+
? 'component-require-without-export-unverified'
|
|
146
|
+
: 'component-require-without-export',
|
|
147
|
+
target: item.target,
|
|
148
|
+
channel: item.channel,
|
|
149
|
+
evidence: [...item.callers, ...(item.incompatibleReceivers ?? [])],
|
|
150
|
+
};
|
|
151
|
+
}),
|
|
152
|
+
...joined.componentExportsWithoutRequires.map((item) => ({
|
|
153
|
+
severity: 'warning',
|
|
154
|
+
code: item.incompatibleCallers !== undefined
|
|
155
|
+
? 'component-export-mechanism-mismatch'
|
|
156
|
+
: 'component-export-without-require',
|
|
157
|
+
target: item.target,
|
|
158
|
+
channel: item.channel,
|
|
159
|
+
evidence: [...item.receivers, ...(item.incompatibleCallers ?? [])],
|
|
160
|
+
})),
|
|
161
|
+
...(messages === undefined ? [] : createMessageIssues(messages, gaps)),
|
|
63
162
|
];
|
|
64
163
|
return {
|
|
65
164
|
format: 'isthmus-check',
|
|
@@ -69,13 +168,135 @@ export function createCheckReport(joined) {
|
|
|
69
168
|
warnings: issues.filter(({ severity }) => severity === 'warning').length,
|
|
70
169
|
matchedChannels: joined.matchedChannels.length,
|
|
71
170
|
matchedMethods: joined.matchedMethods.length,
|
|
72
|
-
|
|
73
|
-
|
|
171
|
+
matchedModules: joined.matchedModules.length,
|
|
172
|
+
matchedComponents: joined.matchedComponents.length,
|
|
173
|
+
...(messages === undefined ? {} : {
|
|
174
|
+
matchedMessages: matchedMessageRoutes(messages, 'basic-message-channel'),
|
|
175
|
+
matchedStreams: matchedMessageRoutes(messages, 'event-channel'),
|
|
176
|
+
}),
|
|
177
|
+
observedFacts: joined.observedFacts + (messages?.observedFacts ?? 0),
|
|
178
|
+
observedLimitations: limitations.length,
|
|
74
179
|
},
|
|
75
180
|
issues,
|
|
76
|
-
limitations
|
|
181
|
+
limitations,
|
|
77
182
|
};
|
|
78
183
|
}
|
|
184
|
+
/** v2 transport별 check 진단 코드·수신 공백 접두사·미대응 한계 이름이다. */
|
|
185
|
+
const messageTransportPolicies = {
|
|
186
|
+
'basic-message-channel': {
|
|
187
|
+
send: 'unhandled-message-send',
|
|
188
|
+
sendUnverified: 'unhandled-message-send-unverified',
|
|
189
|
+
handler: 'message-handler-without-send',
|
|
190
|
+
gap: 'unattributed-message-handles:',
|
|
191
|
+
dynamic: 'dynamic-message-address',
|
|
192
|
+
unmatched: 'unmatched-message-boundary',
|
|
193
|
+
subject: 'message',
|
|
194
|
+
},
|
|
195
|
+
'event-channel': {
|
|
196
|
+
send: 'unhandled-stream-listen',
|
|
197
|
+
sendUnverified: 'unhandled-stream-listen-unverified',
|
|
198
|
+
handler: 'stream-handler-without-listen',
|
|
199
|
+
gap: 'unattributed-stream-handles:',
|
|
200
|
+
dynamic: 'dynamic-stream-address',
|
|
201
|
+
unmatched: 'unmatched-stream-boundary',
|
|
202
|
+
subject: 'stream',
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
/** 같은 transport에서 literal channel을 덮는 prefix 후보를 찾는다. */
|
|
206
|
+
function coveredByPrefix(messages, route, side) {
|
|
207
|
+
return messages.routes.some((candidate) => candidate.matching === 'prefix' && candidate.transport === route.transport &&
|
|
208
|
+
route.channel.startsWith(candidate.channel) && candidate[side].length > 0);
|
|
209
|
+
}
|
|
210
|
+
/** v1과 같은 짝 규칙으로 v2 메시지·스트림 경계 진단을 만든다. */
|
|
211
|
+
function createMessageIssues(messages, gaps) {
|
|
212
|
+
const issues = [];
|
|
213
|
+
for (const route of messages.routes) {
|
|
214
|
+
const policy = messageTransportPolicies[route.transport];
|
|
215
|
+
const hasSenders = route.senders.length > 0;
|
|
216
|
+
const hasHandlers = route.handlers.length > 0;
|
|
217
|
+
// 양쪽이 관찰됐거나 dynamic prefix 후보뿐이면 error/warning을 내지 않는다.
|
|
218
|
+
// prefix 후보의 미대응은 아래 소비자 한계로 남긴다. literal 경계라도
|
|
219
|
+
// 그 prefix 후보가 빠진 쪽을 채우면 후보로 강등해 오탐을 만들지 않는다.
|
|
220
|
+
if ((hasSenders && hasHandlers) || route.matching === 'prefix')
|
|
221
|
+
continue;
|
|
222
|
+
if (hasSenders && coveredByPrefix(messages, route, 'handlers'))
|
|
223
|
+
continue;
|
|
224
|
+
if (hasHandlers && coveredByPrefix(messages, route, 'senders'))
|
|
225
|
+
continue;
|
|
226
|
+
if (hasSenders) {
|
|
227
|
+
const hidden = gaps.hidesHandlers('flutter', route.channel) ||
|
|
228
|
+
messages.limitations.some(({ platform, message }) => isReceiverPlatform(platform) && message.startsWith(policy.gap));
|
|
229
|
+
issues.push({
|
|
230
|
+
severity: hidden ? 'warning' : 'error',
|
|
231
|
+
code: hidden ? policy.sendUnverified : policy.send,
|
|
232
|
+
target: 'flutter',
|
|
233
|
+
channel: route.channel,
|
|
234
|
+
evidence: route.senders,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
issues.push({
|
|
239
|
+
severity: 'warning',
|
|
240
|
+
code: policy.handler,
|
|
241
|
+
target: 'flutter',
|
|
242
|
+
channel: route.channel,
|
|
243
|
+
evidence: route.handlers,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return issues;
|
|
248
|
+
}
|
|
249
|
+
/** 한 transport에서 literal로 발신·수신이 모두 관찰된 경계 수를 센다. */
|
|
250
|
+
function matchedMessageRoutes(messages, transport) {
|
|
251
|
+
return messages.routes.filter((route) => route.transport === transport && route.matching === 'literal' &&
|
|
252
|
+
route.senders.length > 0 && route.handlers.length > 0).length;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* 이음매 없는 v2 입력에서 소비자가 직접 센 한계를 만든다.
|
|
256
|
+
*
|
|
257
|
+
* literal로 확정되지 않은 dynamic prefix 후보와, literal도 prefix도 없는
|
|
258
|
+
* 미해석 주소를 관찰량으로 남긴다. 이들은 error가 아니라 한계다 —
|
|
259
|
+
* 실제 suffix/instance가 맞는지는 정적 사실로 확정할 수 없다.
|
|
260
|
+
*/
|
|
261
|
+
function messageConsumerLimitations(messages) {
|
|
262
|
+
const limitations = [];
|
|
263
|
+
for (const route of messages.routes) {
|
|
264
|
+
if (route.matching !== 'prefix')
|
|
265
|
+
continue;
|
|
266
|
+
const policy = messageTransportPolicies[route.transport];
|
|
267
|
+
limitations.push({
|
|
268
|
+
platform: 'cross-platform',
|
|
269
|
+
target: 'flutter',
|
|
270
|
+
tool: 'isthmus',
|
|
271
|
+
origin: 'consumer',
|
|
272
|
+
message: `${policy.dynamic}: ${policy.subject} channel prefix ${route.channel} `
|
|
273
|
+
+ 'describes possible routes; suffix and instance wiring are not resolved',
|
|
274
|
+
channels: [route.channel],
|
|
275
|
+
});
|
|
276
|
+
if (route.senders.length > 0 && route.handlers.length > 0)
|
|
277
|
+
continue;
|
|
278
|
+
limitations.push({
|
|
279
|
+
platform: 'cross-platform',
|
|
280
|
+
target: 'flutter',
|
|
281
|
+
tool: 'isthmus',
|
|
282
|
+
origin: 'consumer',
|
|
283
|
+
message: `${policy.unmatched}: a related ${policy.subject} boundary has no observed `
|
|
284
|
+
+ `counterpart for the proven prefix ${route.channel}`,
|
|
285
|
+
channels: [route.channel],
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
if (messages.unresolved.length > 0) {
|
|
289
|
+
limitations.push({
|
|
290
|
+
platform: 'cross-platform',
|
|
291
|
+
target: 'flutter',
|
|
292
|
+
tool: 'isthmus',
|
|
293
|
+
origin: 'consumer',
|
|
294
|
+
message: `unresolved-message-addresses: ${messages.unresolved.length} message facts `
|
|
295
|
+
+ 'have no literal or proven prefix and were not joined',
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return limitations;
|
|
299
|
+
}
|
|
79
300
|
/**
|
|
80
301
|
* 수신 측이 핸들러나 등록을 놓쳤을 수 있다고 스스로 알렸는지 target별로 확인한다.
|
|
81
302
|
*
|
|
@@ -106,7 +327,7 @@ function receiverCoverageGaps(limitations) {
|
|
|
106
327
|
if (existing !== undefined)
|
|
107
328
|
return existing;
|
|
108
329
|
const gaps = {
|
|
109
|
-
allHandlers: false, allRegistrations: false,
|
|
330
|
+
allHandlers: false, allRegistrations: false, allExports: false,
|
|
110
331
|
handlerChannels: new Set(), registrationChannels: new Set(),
|
|
111
332
|
};
|
|
112
333
|
for (const { target: gapTarget, tool, message, channels, origin } of receiverLimitations) {
|
|
@@ -116,10 +337,13 @@ function receiverCoverageGaps(limitations) {
|
|
|
116
337
|
(origin === 'consumer' && tool === 'isthmus' && startsWithAny(isthmusHandlerGapPrefixes)(message));
|
|
117
338
|
const registrations = startsWithAny(sourceCoverageGapPrefixes)(message) ||
|
|
118
339
|
(origin === 'consumer' && tool === 'isthmus' && startsWithAny(isthmusRegistrationGapPrefixes)(message));
|
|
340
|
+
const exports = origin === 'consumer' && tool === 'isthmus' &&
|
|
341
|
+
startsWithAny(isthmusExportGapPrefixes)(message);
|
|
119
342
|
// 하나라도 범위가 불명확한 공백이 있으면 같은 target의 좁은 범위로 덮지 않는다.
|
|
120
343
|
if (channels === undefined) {
|
|
121
344
|
gaps.allHandlers ||= handlers;
|
|
122
345
|
gaps.allRegistrations ||= registrations;
|
|
346
|
+
gaps.allExports ||= exports;
|
|
123
347
|
}
|
|
124
348
|
else {
|
|
125
349
|
for (const channel of channels) {
|
|
@@ -142,6 +366,7 @@ function receiverCoverageGaps(limitations) {
|
|
|
142
366
|
const gaps = gapsFor(target);
|
|
143
367
|
return gaps.allRegistrations || gaps.registrationChannels.has(channel);
|
|
144
368
|
},
|
|
369
|
+
hidesExports: (target) => gapsFor(target).allExports,
|
|
145
370
|
};
|
|
146
371
|
}
|
|
147
372
|
/** 주어진 접두사 중 하나로 시작하는지 검사하는 술어를 만든다. */
|
|
@@ -176,4 +401,11 @@ const isthmusHandlerGapPrefixes = [
|
|
|
176
401
|
];
|
|
177
402
|
/** isthmus가 직접 센 한계 중 채널 등록을 가리는 접두사다. */
|
|
178
403
|
const isthmusRegistrationGapPrefixes = ['unjoined-dynamic-channels:'];
|
|
404
|
+
/**
|
|
405
|
+
* isthmus가 직접 센 한계 중 모듈·컴포넌트 export를 가리는 접두사다.
|
|
406
|
+
*
|
|
407
|
+
* RN의 export 사실은 이름이 리터럴일 때만 조인되므로, 동적 이름을 가진
|
|
408
|
+
* 수신 측 사실이 곧 가려진 export의 상한이다.
|
|
409
|
+
*/
|
|
410
|
+
const isthmusExportGapPrefixes = ['unjoined-dynamic-exports:'];
|
|
179
411
|
//# 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 /** 입력 문서 전체가 관찰한 fact 수다. 0이면 아무것도 관찰하지 못한 실행이다. */\n readonly observedFacts: number;\n /** 이 실행에 보고된 분석 한계 수다. */\n readonly observedLimitations: number;\n /** 베이스라인이 적용된 실행에서만 실리는 억제된 이슈 수다. */\n readonly suppressed?: number;\n /** 베이스라인이 적용된 실행에서만 실리는, 현재 이슈와 맞지 않는 항목 수다. */\n readonly staleBaselineEntries?: number;\n}\n\n/**\n * check가 보고하는 안정적인 진단 종류다.\n *\n * `-unverified` 종류는 수신 측이 스스로 분석 공백을 신고해, 핸들러가 없는 것인지\n * 보지 못한 것인지 구분할 수 없는 경우다. 사실과 증거는 같지만 판정이 아니다.\n */\nexport const checkIssueCodes = [\n 'unhandled-invocation',\n 'unhandled-invocation-unverified',\n 'unregistered-channel-creation',\n 'unregistered-channel-creation-unverified',\n 'registration-without-creation',\n 'handler-without-invocation',\n] as const;\n\n/** check가 보고하는 안정적인 진단 종류다. */\nexport type CheckIssueCode = (typeof checkIssueCodes)[number];\n\n/** 삭제 판정 없이 경계 불일치 사실과 증거만 전달한다. */\nexport interface CheckIssue {\n readonly severity: 'error' | 'warning';\n readonly code: CheckIssueCode;\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method?: string;\n readonly evidence: readonly BridgeEndpoint[];\n /**\n * 베이스라인이 이 이슈를 인정된 상태로 억제했다는 표시다.\n *\n * 사실·증거·심각도는 그대로 보존하고, 요약의 error·warning 계산과\n * `--strict`에서만 빼는다. 억제 자체를 지우면 베이스라인이 무엇을\n * 삼켰는지 보고서에서 사라진다.\n */\n readonly suppressed?: true;\n}\n\n/** 에이전트와 CI가 소비할 check 문서다. */\nexport interface CheckReport {\n readonly format: 'isthmus-check';\n readonly version: 1;\n readonly summary: CheckSummary;\n readonly issues: readonly CheckIssue[];\n readonly limitations: readonly JoinLimitation[];\n}\n\n/** check 문서를 결정적인 JSON 문자열로 인코딩한다. */\nexport function encodeCheckReport(report: CheckReport): string {\n return encodeSortedJson(report);\n}\n\n/** 조인 결과를 정책 심각도가 포함된 check 문서로 바꾼다. */\nexport function createCheckReport(joined: BridgeJoinResult): CheckReport {\n if (isBridgeJoinDeferred(joined)) {\n throw new Error('Cannot create a check report from a deferred bridge join.');\n }\n const gaps = receiverCoverageGaps(joined.limitations);\n const issues: CheckIssue[] = [\n ...joined.unhandledInvocations.map<CheckIssue>((item) => ({\n severity: gaps.hidesHandlers(item.target, item.channel) ? 'warning' : 'error',\n code: gaps.hidesHandlers(item.target, item.channel)\n ? 'unhandled-invocation-unverified'\n : 'unhandled-invocation',\n target: item.target,\n channel: item.channel,\n method: item.method,\n evidence: item.invocations,\n })),\n ...joined.unregisteredChannelCreations.map<CheckIssue>((item) => ({\n severity: gaps.hidesRegistrations(item.target, item.channel) ? 'warning' : 'error',\n code: gaps.hidesRegistrations(item.target, item.channel)\n ? 'unregistered-channel-creation-unverified'\n : 'unregistered-channel-creation',\n target: item.target,\n channel: item.channel,\n evidence: item.creations,\n })),\n ...joined.registrationsWithoutCreations.map<CheckIssue>((item) => ({\n severity: 'warning',\n code: 'registration-without-creation',\n target: item.target,\n channel: item.channel,\n evidence: item.registrations,\n })),\n ...joined.handlersWithoutInvocations.map<CheckIssue>((item) => ({\n severity: 'warning',\n code: 'handler-without-invocation',\n target: item.target,\n channel: item.channel,\n method: item.method,\n evidence: item.handlers,\n })),\n ];\n return {\n format: 'isthmus-check',\n version: 1,\n summary: {\n errors: issues.filter(({ severity }) => severity === 'error').length,\n warnings: issues.filter(({ severity }) => severity === 'warning').length,\n matchedChannels: joined.matchedChannels.length,\n matchedMethods: joined.matchedMethods.length,\n observedFacts: joined.observedFacts,\n observedLimitations: joined.limitations.length,\n },\n issues,\n limitations: joined.limitations,\n };\n}\n\n/** 수신 측이 스스로 알린 분석 공백이 target별로 무엇을 가리는지 나타낸다. */\ninterface ReceiverCoverageGaps {\n hidesHandlers(target: BridgeTarget, channel: string): boolean;\n hidesRegistrations(target: BridgeTarget, channel: string): boolean;\n}\n\n/**\n * 수신 측이 핸들러나 등록을 놓쳤을 수 있다고 스스로 알렸는지 target별로 확인한다.\n *\n * 이때 \"핸들러 없는 호출\"은 경계 불일치가 아니라 판정 불가다. Objective-C로 쓰인\n * Flutter 핸들러처럼 수신 측 분석에 아예 나타나지 않는 코드가 실제로 있어서,\n * error로 단정하면 이 도구가 없애려던 오탐을 이 도구가 만든다.\n *\n * 공백의 종류는 구분한다. 이름이 리터럴이 아닌 채널 등록 하나가 무관한 메서드\n * 진단까지 무르게 하면 안 된다. 호출 측 한계는 네이티브 코드를 가리지 않으므로\n * 수신 측 플랫폼의 한계만 본다.\n *\n * 완화 단위는 진단의 target이다. 사실은 target별로만 조인되므로, 다른 target의\n * 수신 문서가 신고한 공백은 현재 target의 핸들러를 가릴 수 없다. target이 없는\n * (사실이 없는) 수신 문서는 어느 target을 분석했는지 특정할 수 없어 모든 target에\n * 적용한다. 같은 target에 귀속된 수신 문서가 사실과 함께 존재해도 마찬가지다.\n * 수신 문서 여러 개가 소스 트리를 나누어 가졌을 수 있으므로, 귀속 없는 문서가\n * 본 소스가 해당 target의 핸들러를 가릴 가능성을 배제할 수 없기 때문이다.\n *\n * `unjoined-` 접두사는 isthmus가 직접 세어 자신을 출처로 밝힌 한계만 인정한다.\n * 생산자 문자열은 신뢰의 근거가 아니므로, 같은 접두사를 차용한 생산자 신고는\n * 완화 근거가 되지 못한다.\n */\nfunction receiverCoverageGaps(\n limitations: readonly JoinLimitation[],\n): ReceiverCoverageGaps {\n const receiverLimitations = limitations.filter(({ platform }) =>\n isReceiverPlatform(platform),\n );\n const memoized = new Map<BridgeTarget, {\n allHandlers: boolean; allRegistrations: boolean;\n handlerChannels: Set<string>; registrationChannels: Set<string>;\n }>();\n const gapsFor = (target: BridgeTarget) => {\n const existing = memoized.get(target);\n if (existing !== undefined) return existing;\n const gaps = {\n allHandlers: false, allRegistrations: false,\n handlerChannels: new Set<string>(), registrationChannels: new Set<string>(),\n };\n for (const { target: gapTarget, tool, message, channels, origin } of receiverLimitations) {\n if (gapTarget !== null && gapTarget !== target) continue;\n const handlers = startsWithAny(producerHandlerGapPrefixes)(message) ||\n (origin === 'consumer' && tool === 'isthmus' && startsWithAny(isthmusHandlerGapPrefixes)(message));\n const registrations = startsWithAny(sourceCoverageGapPrefixes)(message) ||\n (origin === 'consumer' && tool === 'isthmus' && startsWithAny(isthmusRegistrationGapPrefixes)(message));\n // 하나라도 범위가 불명확한 공백이 있으면 같은 target의 좁은 범위로 덮지 않는다.\n if (channels === undefined) {\n gaps.allHandlers ||= handlers;\n gaps.allRegistrations ||= registrations;\n } else {\n for (const channel of channels) {\n if (handlers) gaps.handlerChannels.add(channel);\n if (registrations) gaps.registrationChannels.add(channel);\n }\n }\n }\n memoized.set(target, gaps);\n return gaps;\n };\n return {\n hidesHandlers: (target, channel) => {\n const gaps = gapsFor(target);\n return gaps.allHandlers || gaps.handlerChannels.has(channel);\n },\n hidesRegistrations: (target, channel) => {\n const gaps = gapsFor(target);\n return gaps.allRegistrations || gaps.registrationChannels.has(channel);\n },\n };\n}\n\n/** 주어진 접두사 중 하나로 시작하는지 검사하는 술어를 만든다. */\nfunction startsWithAny(\n prefixes: readonly string[],\n): (message: string) => boolean {\n return (message) => prefixes.some((prefix) => message.startsWith(prefix));\n}\n\n/**\n * 수신 측 소스 자체가 분석되지 않아 등록과 핸들러를 모두 가리는 한계다.\n *\n * 알려진 접두사만 인정한다. 모르는 한계를 공백으로 넓게 해석하면 진짜 불일치가\n * 경고로 묻힌다.\n */\nconst sourceCoverageGapPrefixes = [\n 'objective-c-sources:',\n 'shadowed-flutter-method-channel:',\n];\n\n/** 생산자가 신고하는, 핸들러 본문만 가리는 한계다. */\nconst handlerBodyCoverageGapPrefixes = ['opaque-handler-bodies:'];\n\n/** 생산자 신고 중 핸들러를 가릴 수 있는 한계 전체다. */\nconst producerHandlerGapPrefixes = [\n ...sourceCoverageGapPrefixes,\n ...handlerBodyCoverageGapPrefixes,\n];\n\n/**\n * isthmus가 직접 센 한계 중 핸들러를 가리는 접두사다.\n *\n * 생산자의 신고 개수에 의존하지 않으므로 `tool`이 `isthmus`인 항목만 인정한다.\n */\nconst isthmusHandlerGapPrefixes = [\n 'unjoined-dynamic-methods:',\n 'unjoined-unattributed-handlers:',\n];\n\n/** isthmus가 직접 센 한계 중 채널 등록을 가리는 접두사다. */\nconst isthmusRegistrationGapPrefixes = ['unjoined-dynamic-channels:'];\n"]}
|
|
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 { BridgeMessageTransport } from '../exchange/messages.ts';\nimport type {\n BridgeEndpoint,\n BridgeJoinResult,\n JoinLimitation,\n} from '../join/join.ts';\nimport { compareLimitations, isBridgeJoinDeferred } from '../join/join.ts';\nimport type { MessageBridgeJoin } from '../join/messages.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 readonly matchedModules: number;\n readonly matchedComponents: number;\n /**\n * v2 BasicMessageChannel 입력이 있을 때만 실린다 — 양쪽이 관찰된 채널 수다.\n *\n * v1만 입력하면 필드 자체가 없어 기존 소비자의 요약 비교를 깨지 않는다.\n */\n readonly matchedMessages?: number;\n /** v2 EventChannel 입력이 있을 때만 실리는, 양쪽이 관찰된 스트림 수다. */\n readonly matchedStreams?: number;\n /** 입력 문서 전체가 관찰한 fact 수다. 0이면 아무것도 관찰하지 못한 실행이다. */\n readonly observedFacts: number;\n /** 이 실행에 보고된 분석 한계 수다. */\n readonly observedLimitations: number;\n /** 베이스라인이 적용된 실행에서만 실리는 억제된 이슈 수다. */\n readonly suppressed?: number;\n /** 베이스라인이 적용된 실행에서만 실리는, 현재 이슈와 맞지 않는 항목 수다. */\n readonly staleBaselineEntries?: number;\n}\n\n/**\n * check가 보고하는 안정적인 진단 종류다.\n *\n * `-unverified` 종류는 수신 측이 스스로 분석 공백을 신고해, 핸들러가 없는 것인지\n * 보지 못한 것인지 구분할 수 없는 경우다. 사실과 증거는 같지만 판정이 아니다.\n */\nexport const checkIssueCodes = [\n 'unhandled-invocation',\n 'unhandled-invocation-unverified',\n 'unregistered-channel-creation',\n 'unregistered-channel-creation-unverified',\n 'registration-without-creation',\n 'handler-without-invocation',\n 'module-import-without-export',\n 'module-import-without-export-unverified',\n 'module-import-without-export-optional',\n 'module-import-mechanism-mismatch',\n 'module-export-without-import',\n 'module-export-mechanism-mismatch',\n 'component-require-without-export',\n 'component-require-without-export-unverified',\n 'component-require-mechanism-mismatch',\n 'component-export-without-require',\n 'component-export-mechanism-mismatch',\n 'unhandled-message-send',\n 'unhandled-message-send-unverified',\n 'message-handler-without-send',\n 'unhandled-stream-listen',\n 'unhandled-stream-listen-unverified',\n 'stream-handler-without-listen',\n] as const;\n\n/** check가 보고하는 안정적인 진단 종류다. */\nexport type CheckIssueCode = (typeof checkIssueCodes)[number];\n\n/** 삭제 판정 없이 경계 불일치 사실과 증거만 전달한다. */\nexport interface CheckIssue {\n readonly severity: 'error' | 'warning';\n readonly code: CheckIssueCode;\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method?: string;\n readonly evidence: readonly BridgeEndpoint[];\n /**\n * 베이스라인이 이 이슈를 인정된 상태로 억제했다는 표시다.\n *\n * 사실·증거·심각도는 그대로 보존하고, 요약의 error·warning 계산과\n * `--strict`에서만 빼는다. 억제 자체를 지우면 베이스라인이 무엇을\n * 삼켰는지 보고서에서 사라진다.\n */\n readonly suppressed?: true;\n}\n\n/** 에이전트와 CI가 소비할 check 문서다. */\nexport interface CheckReport {\n readonly format: 'isthmus-check';\n readonly version: 1;\n readonly summary: CheckSummary;\n readonly issues: readonly CheckIssue[];\n readonly limitations: readonly JoinLimitation[];\n}\n\n/** check 문서를 결정적인 JSON 문자열로 인코딩한다. */\nexport function encodeCheckReport(report: CheckReport): string {\n return encodeSortedJson(report);\n}\n\n/**\n * v1 조인 결과와 선택적 v2 메시지 조인을 정책 심각도가 포함된 check 문서로 바꾼다.\n *\n * `messages`를 주면 transport별 진단 코드와 수신 공백 완화를 함께 싣고,\n * 요약에 `matchedMessages`/`matchedStreams`를 더한다. v1만 쓰는 호출자의\n * 출력은 그대로 유지된다.\n */\nexport function createCheckReport(\n joined: BridgeJoinResult,\n messages?: MessageBridgeJoin,\n): CheckReport {\n if (isBridgeJoinDeferred(joined)) {\n throw new Error('Cannot create a check report from a deferred bridge join.');\n }\n const limitations = messages === undefined\n ? joined.limitations\n : [...joined.limitations, ...messages.limitations,\n ...messageConsumerLimitations(messages)].sort(compareLimitations);\n const gaps = receiverCoverageGaps(limitations);\n const issues: CheckIssue[] = [\n ...joined.unhandledInvocations.map<CheckIssue>((item) => ({\n severity: gaps.hidesHandlers(item.target, item.channel) ? 'warning' : 'error',\n code: gaps.hidesHandlers(item.target, item.channel)\n ? 'unhandled-invocation-unverified'\n : 'unhandled-invocation',\n target: item.target,\n channel: item.channel,\n method: item.method,\n evidence: item.invocations,\n })),\n ...joined.unregisteredChannelCreations.map<CheckIssue>((item) => ({\n severity: gaps.hidesRegistrations(item.target, item.channel) ? 'warning' : 'error',\n code: gaps.hidesRegistrations(item.target, item.channel)\n ? 'unregistered-channel-creation-unverified'\n : 'unregistered-channel-creation',\n target: item.target,\n channel: item.channel,\n evidence: item.creations,\n })),\n ...joined.registrationsWithoutCreations.map<CheckIssue>((item) => ({\n severity: 'warning',\n code: 'registration-without-creation',\n target: item.target,\n channel: item.channel,\n evidence: item.registrations,\n })),\n ...joined.handlersWithoutInvocations.map<CheckIssue>((item) => ({\n severity: 'warning',\n code: 'handler-without-invocation',\n target: item.target,\n channel: item.channel,\n method: item.method,\n evidence: item.handlers,\n })),\n ...joined.moduleImportsWithoutExports.map<CheckIssue>((item) => {\n // 같은 이름의 export가 mechanism만 다르게 관찰됐다면 진짜 공백이 아니라\n // 해석 경로 불일치다 — 코어 호출×Expo export의 상호운용 여부가 미해결이므로\n // error가 아니라 별도 warning으로 내린다. optional 판정보다 앞서야 한다 —\n // 수신 측 export가 관찰된 상황에 \"미검증\" 문구를 붙이면 틀리다.\n const mismatched = item.incompatibleReceivers !== undefined;\n // 호출자 전부가 부재 허용 API(requireOptionalNativeModule·Registry.get)를\n // 썼다면 미수출은 크래시가 아니라 null 반환이다 — 호출자가 감당하므로\n // warning으로 내린다. 하나라도 던지는 호출자가 있으면 error를 유지한다.\n const allOptional = item.callers.length > 0 &&\n item.callers.every((caller) => caller.optional === true);\n return {\n severity: mismatched || allOptional || gaps.hidesExports(item.target)\n ? 'warning'\n : 'error',\n code: mismatched\n ? 'module-import-mechanism-mismatch'\n : allOptional\n ? 'module-import-without-export-optional'\n : gaps.hidesExports(item.target)\n ? 'module-import-without-export-unverified'\n : 'module-import-without-export',\n target: item.target,\n channel: item.channel,\n // 불일치 수신 측 위치까지 실어야 어느 export가 다른 경로로\n // 해석되는지 보고서에서 보인다.\n evidence: [...item.callers, ...(item.incompatibleReceivers ?? [])],\n };\n }),\n ...joined.moduleExportsWithoutImports.map<CheckIssue>((item) => ({\n severity: 'warning',\n // 같은 이름의 호출이 mechanism만 다르게 관찰됐다면 미호출이 아니라\n // 해석 경로 불일치다.\n code: item.incompatibleCallers !== undefined\n ? 'module-export-mechanism-mismatch'\n : 'module-export-without-import',\n target: item.target,\n channel: item.channel,\n evidence: [...item.receivers, ...(item.incompatibleCallers ?? [])],\n })),\n ...joined.componentRequiresWithoutExports.map<CheckIssue>((item) => {\n const mismatched = item.incompatibleReceivers !== undefined;\n // Expo 측 requireNativeViewManager에는 코어 폴백이 없어, 관찰된 export가\n // 모두 코어라면 호출은 확정된 미수출 error다. 코어 호출×Expo export만은\n // 상호운용이 미해결이므로 warning으로 내린다.\n const allExpoCallers = item.callers.length > 0 &&\n item.callers.every((caller) => caller.mechanism === 'expo');\n const unresolved = mismatched && !allExpoCallers;\n return {\n severity: unresolved || gaps.hidesExports(item.target)\n ? 'warning'\n : 'error',\n code: unresolved\n ? 'component-require-mechanism-mismatch'\n : gaps.hidesExports(item.target)\n ? 'component-require-without-export-unverified'\n : 'component-require-without-export',\n target: item.target,\n channel: item.channel,\n evidence: [...item.callers, ...(item.incompatibleReceivers ?? [])],\n };\n }),\n ...joined.componentExportsWithoutRequires.map<CheckIssue>((item) => ({\n severity: 'warning',\n code: item.incompatibleCallers !== undefined\n ? 'component-export-mechanism-mismatch'\n : 'component-export-without-require',\n target: item.target,\n channel: item.channel,\n evidence: [...item.receivers, ...(item.incompatibleCallers ?? [])],\n })),\n ...(messages === undefined ? [] : createMessageIssues(messages, gaps)),\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 matchedModules: joined.matchedModules.length,\n matchedComponents: joined.matchedComponents.length,\n ...(messages === undefined ? {} : {\n matchedMessages: matchedMessageRoutes(messages, 'basic-message-channel'),\n matchedStreams: matchedMessageRoutes(messages, 'event-channel'),\n }),\n observedFacts: joined.observedFacts + (messages?.observedFacts ?? 0),\n observedLimitations: limitations.length,\n },\n issues,\n limitations,\n };\n}\n\n/** v2 transport별 check 진단 코드·수신 공백 접두사·미대응 한계 이름이다. */\nconst messageTransportPolicies = {\n 'basic-message-channel': {\n send: 'unhandled-message-send',\n sendUnverified: 'unhandled-message-send-unverified',\n handler: 'message-handler-without-send',\n gap: 'unattributed-message-handles:',\n dynamic: 'dynamic-message-address',\n unmatched: 'unmatched-message-boundary',\n subject: 'message',\n },\n 'event-channel': {\n send: 'unhandled-stream-listen',\n sendUnverified: 'unhandled-stream-listen-unverified',\n handler: 'stream-handler-without-listen',\n gap: 'unattributed-stream-handles:',\n dynamic: 'dynamic-stream-address',\n unmatched: 'unmatched-stream-boundary',\n subject: 'stream',\n },\n} as const satisfies Record<BridgeMessageTransport, {\n send: CheckIssueCode;\n sendUnverified: CheckIssueCode;\n handler: CheckIssueCode;\n gap: string;\n dynamic: string;\n unmatched: string;\n subject: string;\n}>;\n\n/** 같은 transport에서 literal channel을 덮는 prefix 후보를 찾는다. */\nfunction coveredByPrefix(\n messages: MessageBridgeJoin,\n route: { readonly channel: string; readonly transport: BridgeMessageTransport },\n side: 'senders' | 'handlers',\n): boolean {\n return messages.routes.some((candidate) =>\n candidate.matching === 'prefix' && candidate.transport === route.transport &&\n route.channel.startsWith(candidate.channel) && candidate[side].length > 0);\n}\n\n/** v1과 같은 짝 규칙으로 v2 메시지·스트림 경계 진단을 만든다. */\nfunction createMessageIssues(\n messages: MessageBridgeJoin,\n gaps: ReceiverCoverageGaps,\n): CheckIssue[] {\n const issues: CheckIssue[] = [];\n for (const route of messages.routes) {\n const policy = messageTransportPolicies[route.transport];\n const hasSenders = route.senders.length > 0;\n const hasHandlers = route.handlers.length > 0;\n // 양쪽이 관찰됐거나 dynamic prefix 후보뿐이면 error/warning을 내지 않는다.\n // prefix 후보의 미대응은 아래 소비자 한계로 남긴다. literal 경계라도\n // 그 prefix 후보가 빠진 쪽을 채우면 후보로 강등해 오탐을 만들지 않는다.\n if ((hasSenders && hasHandlers) || route.matching === 'prefix') continue;\n if (hasSenders && coveredByPrefix(messages, route, 'handlers')) continue;\n if (hasHandlers && coveredByPrefix(messages, route, 'senders')) continue;\n if (hasSenders) {\n const hidden = gaps.hidesHandlers('flutter', route.channel) ||\n messages.limitations.some(({ platform, message }) =>\n isReceiverPlatform(platform) && message.startsWith(policy.gap));\n issues.push({\n severity: hidden ? 'warning' : 'error',\n code: hidden ? policy.sendUnverified : policy.send,\n target: 'flutter',\n channel: route.channel,\n evidence: route.senders,\n });\n } else {\n issues.push({\n severity: 'warning',\n code: policy.handler,\n target: 'flutter',\n channel: route.channel,\n evidence: route.handlers,\n });\n }\n }\n return issues;\n}\n\n/** 한 transport에서 literal로 발신·수신이 모두 관찰된 경계 수를 센다. */\nfunction matchedMessageRoutes(\n messages: MessageBridgeJoin,\n transport: BridgeMessageTransport,\n): number {\n return messages.routes.filter((route) =>\n route.transport === transport && route.matching === 'literal' &&\n route.senders.length > 0 && route.handlers.length > 0,\n ).length;\n}\n\n/**\n * 이음매 없는 v2 입력에서 소비자가 직접 센 한계를 만든다.\n *\n * literal로 확정되지 않은 dynamic prefix 후보와, literal도 prefix도 없는\n * 미해석 주소를 관찰량으로 남긴다. 이들은 error가 아니라 한계다 —\n * 실제 suffix/instance가 맞는지는 정적 사실로 확정할 수 없다.\n */\nfunction messageConsumerLimitations(\n messages: MessageBridgeJoin,\n): JoinLimitation[] {\n const limitations: JoinLimitation[] = [];\n for (const route of messages.routes) {\n if (route.matching !== 'prefix') continue;\n const policy = messageTransportPolicies[route.transport];\n limitations.push({\n platform: 'cross-platform',\n target: 'flutter',\n tool: 'isthmus',\n origin: 'consumer',\n message: `${policy.dynamic}: ${policy.subject} channel prefix ${route.channel} `\n + 'describes possible routes; suffix and instance wiring are not resolved',\n channels: [route.channel],\n });\n if (route.senders.length > 0 && route.handlers.length > 0) continue;\n limitations.push({\n platform: 'cross-platform',\n target: 'flutter',\n tool: 'isthmus',\n origin: 'consumer',\n message: `${policy.unmatched}: a related ${policy.subject} boundary has no observed `\n + `counterpart for the proven prefix ${route.channel}`,\n channels: [route.channel],\n });\n }\n if (messages.unresolved.length > 0) {\n limitations.push({\n platform: 'cross-platform',\n target: 'flutter',\n tool: 'isthmus',\n origin: 'consumer',\n message: `unresolved-message-addresses: ${messages.unresolved.length} message facts `\n + 'have no literal or proven prefix and were not joined',\n });\n }\n return limitations;\n}\n\n/** 수신 측이 스스로 알린 분석 공백이 target별로 무엇을 가리는지 나타낸다. */\ninterface ReceiverCoverageGaps {\n hidesHandlers(target: BridgeTarget, channel: string): boolean;\n hidesRegistrations(target: BridgeTarget, channel: string): boolean;\n /**\n * 동적 export 계수는 소비자만 세며 소비자 한계는 채널 범위를 갖지 않으므로\n * 이 공백은 target 단위로만 적용된다.\n */\n hidesExports(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<BridgeTarget, {\n allHandlers: boolean; allRegistrations: boolean; allExports: boolean;\n handlerChannels: Set<string>; registrationChannels: Set<string>;\n }>();\n const gapsFor = (target: BridgeTarget) => {\n const existing = memoized.get(target);\n if (existing !== undefined) return existing;\n const gaps = {\n allHandlers: false, allRegistrations: false, allExports: false,\n handlerChannels: new Set<string>(), registrationChannels: new Set<string>(),\n };\n for (const { target: gapTarget, tool, message, channels, origin } of receiverLimitations) {\n if (gapTarget !== null && gapTarget !== target) continue;\n const handlers = startsWithAny(producerHandlerGapPrefixes)(message) ||\n (origin === 'consumer' && tool === 'isthmus' && startsWithAny(isthmusHandlerGapPrefixes)(message));\n const registrations = startsWithAny(sourceCoverageGapPrefixes)(message) ||\n (origin === 'consumer' && tool === 'isthmus' && startsWithAny(isthmusRegistrationGapPrefixes)(message));\n const exports = origin === 'consumer' && tool === 'isthmus' &&\n startsWithAny(isthmusExportGapPrefixes)(message);\n // 하나라도 범위가 불명확한 공백이 있으면 같은 target의 좁은 범위로 덮지 않는다.\n if (channels === undefined) {\n gaps.allHandlers ||= handlers;\n gaps.allRegistrations ||= registrations;\n gaps.allExports ||= exports;\n } else {\n for (const channel of channels) {\n if (handlers) gaps.handlerChannels.add(channel);\n if (registrations) gaps.registrationChannels.add(channel);\n }\n }\n }\n memoized.set(target, gaps);\n return gaps;\n };\n return {\n hidesHandlers: (target, channel) => {\n const gaps = gapsFor(target);\n return gaps.allHandlers || gaps.handlerChannels.has(channel);\n },\n hidesRegistrations: (target, channel) => {\n const gaps = gapsFor(target);\n return gaps.allRegistrations || gaps.registrationChannels.has(channel);\n },\n hidesExports: (target) => gapsFor(target).allExports,\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\n/**\n * isthmus가 직접 센 한계 중 모듈·컴포넌트 export를 가리는 접두사다.\n *\n * RN의 export 사실은 이름이 리터럴일 때만 조인되므로, 동적 이름을 가진\n * 수신 측 사실이 곧 가려진 export의 상한이다.\n */\nconst isthmusExportGapPrefixes = ['unjoined-dynamic-exports:'];\n"]}
|