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.
Files changed (99) hide show
  1. package/README.ko.md +135 -31
  2. package/README.md +134 -16
  3. package/Skills/isthmus/SKILL.md +17 -7
  4. package/compatibility.json +10 -0
  5. package/dist/cli/check-command.d.ts +2 -2
  6. package/dist/cli/check-command.js +22 -9
  7. package/dist/cli/check-command.js.map +1 -1
  8. package/dist/cli/command-support.d.ts +19 -1
  9. package/dist/cli/command-support.js +37 -8
  10. package/dist/cli/command-support.js.map +1 -1
  11. package/dist/cli/diff-command.js +4 -3
  12. package/dist/cli/diff-command.js.map +1 -1
  13. package/dist/cli/doctor-command.d.ts +11 -0
  14. package/dist/cli/doctor-command.js +128 -0
  15. package/dist/cli/doctor-command.js.map +1 -0
  16. package/dist/cli/extract-js-command.d.ts +33 -0
  17. package/dist/cli/extract-js-command.js +233 -0
  18. package/dist/cli/extract-js-command.js.map +1 -0
  19. package/dist/cli/graph-command.js +13 -6
  20. package/dist/cli/graph-command.js.map +1 -1
  21. package/dist/cli/init-command.d.ts +11 -0
  22. package/dist/cli/init-command.js +115 -0
  23. package/dist/cli/init-command.js.map +1 -0
  24. package/dist/cli/main.js +52 -1
  25. package/dist/cli/main.js.map +1 -1
  26. package/dist/cli/mcp-server.d.ts +16 -0
  27. package/dist/cli/mcp-server.js +534 -0
  28. package/dist/cli/mcp-server.js.map +1 -0
  29. package/dist/cli/query-command.js +13 -6
  30. package/dist/cli/query-command.js.map +1 -1
  31. package/dist/cli/retentions-command.js +14 -7
  32. package/dist/cli/retentions-command.js.map +1 -1
  33. package/dist/cli/serve-command.d.ts +13 -0
  34. package/dist/cli/serve-command.js +43 -0
  35. package/dist/cli/serve-command.js.map +1 -0
  36. package/dist/exchange/capture-config.d.ts +18 -0
  37. package/dist/exchange/capture-config.js +100 -0
  38. package/dist/exchange/capture-config.js.map +1 -0
  39. package/dist/exchange/messages.d.ts +9 -24
  40. package/dist/exchange/messages.js +25 -55
  41. package/dist/exchange/messages.js.map +1 -1
  42. package/dist/exchange/parse.d.ts +61 -0
  43. package/dist/exchange/parse.js +115 -12
  44. package/dist/exchange/parse.js.map +1 -1
  45. package/dist/extract/js-document.d.ts +22 -0
  46. package/dist/extract/js-document.js +230 -0
  47. package/dist/extract/js-document.js.map +1 -0
  48. package/dist/extract/js-scan.d.ts +93 -0
  49. package/dist/extract/js-scan.js +1205 -0
  50. package/dist/extract/js-scan.js.map +1 -0
  51. package/dist/extract/lexer.d.ts +26 -0
  52. package/dist/extract/lexer.js +316 -0
  53. package/dist/extract/lexer.js.map +1 -0
  54. package/dist/join/join.d.ts +66 -1
  55. package/dist/join/join.js +171 -1
  56. package/dist/join/join.js.map +1 -1
  57. package/dist/join/messages.d.ts +7 -4
  58. package/dist/join/messages.js +15 -8
  59. package/dist/join/messages.js.map +1 -1
  60. package/dist/report/check-report.d.ts +20 -3
  61. package/dist/report/check-report.js +240 -8
  62. package/dist/report/check-report.js.map +1 -1
  63. package/dist/report/codequality.d.ts +45 -0
  64. package/dist/report/codequality.js +74 -0
  65. package/dist/report/codequality.js.map +1 -0
  66. package/dist/report/diff.d.ts +25 -3
  67. package/dist/report/diff.js +91 -11
  68. package/dist/report/diff.js.map +1 -1
  69. package/dist/report/graph.d.ts +10 -4
  70. package/dist/report/graph.js +60 -6
  71. package/dist/report/graph.js.map +1 -1
  72. package/dist/report/preflight-view.js +28 -1
  73. package/dist/report/preflight-view.js.map +1 -1
  74. package/dist/report/preflight.d.ts +4 -4
  75. package/dist/report/preflight.js +81 -46
  76. package/dist/report/preflight.js.map +1 -1
  77. package/dist/report/query.d.ts +11 -4
  78. package/dist/report/query.js +84 -17
  79. package/dist/report/query.js.map +1 -1
  80. package/dist/report/retentions.d.ts +17 -5
  81. package/dist/report/retentions.js +85 -8
  82. package/dist/report/retentions.js.map +1 -1
  83. package/dist/report/rules.d.ts +16 -0
  84. package/dist/report/rules.js +30 -0
  85. package/dist/report/rules.js.map +1 -0
  86. package/dist/report/sarif.d.ts +2 -8
  87. package/dist/report/sarif.js +2 -10
  88. package/dist/report/sarif.js.map +1 -1
  89. package/docs/BRIDGE-EVENTS.md +87 -0
  90. package/docs/BRIDGE-MESSAGES.md +23 -8
  91. package/docs/GRAPH-EXCHANGE.md +141 -14
  92. package/docs/IMPACT.md +1 -1
  93. package/docs/MCP.md +62 -0
  94. package/docs/PREFLIGHT.md +24 -8
  95. package/docs/RUNTIME.md +2 -2
  96. package/docs/TOOLCHAIN.md +5 -5
  97. package/package.json +5 -2
  98. package/scripts/capture-preflight.mjs +32 -44
  99. package/scripts/run-child.mjs +1 -0
@@ -0,0 +1,230 @@
1
+ /**
2
+ * extract-js의 문서 조립 레이어다.
3
+ *
4
+ * 파일별 스캔 결과를 모아 상대 경로 import를 스캔 집합 안에서 해석하고,
5
+ * 바인딩이 확정된 식별자의 멤버 호출을 method-invoke 사실로 바꾼다.
6
+ * 파일시스템을 읽지 않는 순수 조립이다 — 경로 해석·읽기는 cli/가 담당한다.
7
+ */
8
+ import { scanJsSource } from "./js-scan.js";
9
+ /** 스캔 대상이 되는 JS/TS 확장자다. */
10
+ export const JS_SOURCE_EXTENSIONS = [
11
+ 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'mts', 'cts',
12
+ ];
13
+ /**
14
+ * 스캔한 JS/TS 파일들을 bridge-facts v1 문서로 조립한다.
15
+ *
16
+ * 사실이 하나도 없으면 계약상 target은 null이다 — 관찰했으나 없음과 관찰
17
+ * 자체가 불가를 구분해 빈 사실이 "청정"으로 읽히지 않게 한다.
18
+ */
19
+ export function createJsFactsDocument(files, toolVersion, generatedAt, project) {
20
+ const scans = new Map();
21
+ for (const file of files)
22
+ scans.set(file.path, scanJsSource(file.text));
23
+ const facts = [];
24
+ let unattributedRelativeCalls = 0;
25
+ let unattributedPackageCalls = 0;
26
+ for (const file of files) {
27
+ const scan = scans.get(file.path);
28
+ const resolvedIdents = new Map(scan.bindings);
29
+ for (const entry of scan.imports) {
30
+ const bound = resolveImport(scans, file.path, entry);
31
+ if (bound !== undefined) {
32
+ resolvedIdents.set(entry.localName, bound);
33
+ }
34
+ else if (entry.specifier.startsWith('.')) {
35
+ unattributedRelativeCalls += countMemberCalls(scan, entry.localName);
36
+ }
37
+ else {
38
+ unattributedPackageCalls += countMemberCalls(scan, entry.localName);
39
+ }
40
+ }
41
+ for (const fact of scan.facts) {
42
+ facts.push(toFact(file.path, fact));
43
+ }
44
+ for (const call of scan.memberCalls) {
45
+ const bound = resolvedIdents.get(call.ident);
46
+ if (bound === undefined)
47
+ continue;
48
+ const name = 'name' in bound ? bound.name : bound.dynamicExpression;
49
+ facts.push({
50
+ kind: 'method-invoke',
51
+ channel: name,
52
+ method: call.method,
53
+ dynamic: !('name' in bound) || call.dynamicMethod,
54
+ location: locationOf(file.path, call.token),
55
+ });
56
+ }
57
+ }
58
+ const sorted = dedupeFacts(facts);
59
+ const limitations = buildLimitations(files.length, scans, {
60
+ unattributedRelativeCalls,
61
+ unattributedPackageCalls,
62
+ });
63
+ return {
64
+ format: 'bridge-facts',
65
+ version: 1,
66
+ tool: { name: 'isthmus', version: toolVersion },
67
+ generatedAt,
68
+ platform: 'js',
69
+ target: sorted.length === 0 ? null : 'react-native',
70
+ project,
71
+ facts: sorted,
72
+ limitations,
73
+ };
74
+ }
75
+ /** 한 식별자에 대한 멤버 호출 수를 센다(미귀속 한계 계수용). */
76
+ function countMemberCalls(scan, ident) {
77
+ return scan.memberCalls.filter((call) => call.ident === ident).length;
78
+ }
79
+ /**
80
+ * `import X from 'spec'`을 스캔 집합 안에서 해석한다.
81
+ *
82
+ * 상대 specifier만 해석한다 — 패키지 specifier는 스캔 집합(node_modules는
83
+ * 제외됨) 밖이라 귀속 근거가 없다. 배럴 재수출은 순환을 피하기 위해 홉 상한
84
+ * 안에서 따라간다.
85
+ */
86
+ function resolveImport(scans, fromPath, entry) {
87
+ let specifier = entry.specifier;
88
+ let exportedName = entry.exportedName;
89
+ let currentDir = dirnameOf(fromPath);
90
+ for (let hop = 0; hop < 4; hop++) {
91
+ if (!specifier.startsWith('.'))
92
+ return undefined;
93
+ const target = resolveSpecifier(scans, currentDir, specifier);
94
+ if (target === undefined)
95
+ return undefined;
96
+ const scan = scans.get(target);
97
+ const bound = exportedName === 'default'
98
+ ? scan.exports.defaultName
99
+ : scan.exports.named.get(exportedName);
100
+ if (bound !== undefined)
101
+ return bound;
102
+ const reexport = exportedName === 'default'
103
+ ? scan.exports.reexported.get('default')
104
+ : scan.exports.reexported.get(exportedName);
105
+ if (reexport === undefined)
106
+ return undefined;
107
+ specifier = reexport.specifier;
108
+ exportedName = reexport.exportedName;
109
+ currentDir = dirnameOf(target);
110
+ }
111
+ return undefined;
112
+ }
113
+ /** specifier를 디렉터리 기준으로 정규화해 스캔 집합에 있는 파일을 찾는다. */
114
+ function resolveSpecifier(scans, dir, specifier) {
115
+ const base = normalizePath(dir === '' ? specifier : `${dir}/${specifier}`);
116
+ const candidates = [
117
+ base,
118
+ ...JS_SOURCE_EXTENSIONS.map((ext) => `${base}.${ext}`),
119
+ ...JS_SOURCE_EXTENSIONS.map((ext) => `${base}/index.${ext}`),
120
+ ];
121
+ return candidates.find((candidate) => scans.has(candidate));
122
+ }
123
+ /** `.`·`..` 세그먼트를 접은 POSIX 상대 경로다. 스캔 집합 밖 탈출은 해석 불가다. */
124
+ function normalizePath(path) {
125
+ const segments = [];
126
+ for (const segment of path.split('/')) {
127
+ if (segment === '' || segment === '.')
128
+ continue;
129
+ if (segment === '..') {
130
+ if (segments.length === 0)
131
+ return '';
132
+ segments.pop();
133
+ continue;
134
+ }
135
+ segments.push(segment);
136
+ }
137
+ return segments.join('/');
138
+ }
139
+ /** 프로젝트 상대 경로의 디렉터리 부분이다. */
140
+ function dirnameOf(path) {
141
+ const slash = path.lastIndexOf('/');
142
+ return slash === -1 ? '' : path.slice(0, slash);
143
+ }
144
+ /** 스캔 사실을 계약 필드 모양으로 바꾼다. */
145
+ function toFact(path, fact) {
146
+ return {
147
+ kind: fact.kind,
148
+ channel: fact.channel,
149
+ ...(fact.method === undefined ? {} : { method: fact.method }),
150
+ ...(fact.mechanism === undefined ? {} : { mechanism: fact.mechanism }),
151
+ ...(fact.optional === undefined ? {} : { optional: fact.optional }),
152
+ dynamic: fact.dynamic,
153
+ location: locationOf(path, fact.token),
154
+ };
155
+ }
156
+ /** 토큰 위치를 계약 위치로 바꾼다. */
157
+ function locationOf(path, token) {
158
+ return { path, line: token.line, column: token.column };
159
+ }
160
+ /** 같은 위치·같은 키의 중복 사실을 한 번만 남기고 결정적으로 정렬한다. */
161
+ function dedupeFacts(facts) {
162
+ const seen = new Map();
163
+ for (const fact of facts) {
164
+ const key = JSON.stringify([
165
+ fact.kind,
166
+ fact.channel,
167
+ fact.method ?? null,
168
+ fact.location.path,
169
+ fact.location.line,
170
+ fact.location.column,
171
+ fact.dynamic,
172
+ ]);
173
+ if (!seen.has(key))
174
+ seen.set(key, fact);
175
+ }
176
+ return [...seen.values()].sort((left, right) => {
177
+ const byPath = left.location.path.localeCompare(right.location.path);
178
+ if (byPath !== 0)
179
+ return byPath;
180
+ if (left.location.line !== right.location.line) {
181
+ return left.location.line - right.location.line;
182
+ }
183
+ if (left.location.column !== right.location.column) {
184
+ return left.location.column - right.location.column;
185
+ }
186
+ return (left.channel ?? '').localeCompare(right.channel ?? '');
187
+ });
188
+ }
189
+ /** 집계 계수와 고정 범위 설명을 생산자 limitation 문자열로 바꾼다. */
190
+ function buildLimitations(fileCount, scans, extra) {
191
+ let dynamicModuleNames = 0;
192
+ let dynamicComponentNames = 0;
193
+ let dynamicMethodNames = 0;
194
+ for (const scan of scans.values()) {
195
+ dynamicModuleNames += scan.counts.dynamicModuleNames;
196
+ dynamicComponentNames += scan.counts.dynamicComponentNames;
197
+ dynamicMethodNames += scan.counts.dynamicMethodNames;
198
+ }
199
+ const limitations = [
200
+ 'js-binding-scope: member calls through bindings that leave the scan set '
201
+ + '(package imports, function results, instance state) are not attributed '
202
+ + 'to a module name',
203
+ ];
204
+ if (dynamicModuleNames > 0) {
205
+ limitations.push(`dynamic-module-names: ${dynamicModuleNames} module references use a `
206
+ + 'non-literal name');
207
+ }
208
+ if (dynamicComponentNames > 0) {
209
+ limitations.push(`dynamic-component-names: ${dynamicComponentNames} component requires `
210
+ + 'use a non-literal name');
211
+ }
212
+ if (dynamicMethodNames > 0) {
213
+ limitations.push(`dynamic-method-names: ${dynamicMethodNames} bridge method calls use a `
214
+ + 'non-literal method name');
215
+ }
216
+ if (extra.unattributedRelativeCalls > 0) {
217
+ limitations.push(`unattributed-js-relative-calls: ${extra.unattributedRelativeCalls} `
218
+ + 'member calls on imports that did not resolve inside the scanned '
219
+ + 'file set could not be attributed to a module name');
220
+ }
221
+ if (extra.unattributedPackageCalls > 0) {
222
+ limitations.push(`unattributed-js-package-calls: ${extra.unattributedPackageCalls} member `
223
+ + 'calls on package imports could not be attributed to a module name');
224
+ }
225
+ if (fileCount === 0) {
226
+ limitations.push('empty-js-scan: no JS/TS source files were scanned');
227
+ }
228
+ return limitations;
229
+ }
230
+ //# sourceMappingURL=js-document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"js-document.js","sourceRoot":"","sources":["../../src/extract/js-document.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * extract-js의 문서 조립 레이어다.\n *\n * 파일별 스캔 결과를 모아 상대 경로 import를 스캔 집합 안에서 해석하고,\n * 바인딩이 확정된 식별자의 멤버 호출을 method-invoke 사실로 바꾼다.\n * 파일시스템을 읽지 않는 순수 조립이다 — 경로 해석·읽기는 cli/가 담당한다.\n */\n\nimport type { BridgeFact, BridgeFactsDocument } from '../exchange/parse.ts';\nimport type { BoundName, ScannedImport } from './js-scan.ts';\nimport { scanJsSource } from './js-scan.ts';\nimport type { JsFileScan } from './js-scan.ts';\n\n/** 스캔 대상이 되는 JS/TS 확장자다. */\nexport const JS_SOURCE_EXTENSIONS = [\n 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'mts', 'cts',\n] as const;\n\n/** 조립 입력 — 프로젝트 상대 경로와 UTF-8 텍스트다. */\nexport interface JsSourceFile {\n readonly path: string;\n readonly text: string;\n}\n\n/**\n * 스캔한 JS/TS 파일들을 bridge-facts v1 문서로 조립한다.\n *\n * 사실이 하나도 없으면 계약상 target은 null이다 — 관찰했으나 없음과 관찰\n * 자체가 불가를 구분해 빈 사실이 \"청정\"으로 읽히지 않게 한다.\n */\nexport function createJsFactsDocument(\n files: readonly JsSourceFile[],\n toolVersion: string,\n generatedAt: string,\n project: string,\n): BridgeFactsDocument {\n const scans = new Map<string, JsFileScan>();\n for (const file of files) scans.set(file.path, scanJsSource(file.text));\n\n const facts: BridgeFact[] = [];\n let unattributedRelativeCalls = 0;\n let unattributedPackageCalls = 0;\n\n for (const file of files) {\n const scan = scans.get(file.path)!;\n const resolvedIdents = new Map<string, BoundName>(scan.bindings);\n for (const entry of scan.imports) {\n const bound = resolveImport(scans, file.path, entry);\n if (bound !== undefined) {\n resolvedIdents.set(entry.localName, bound);\n } else if (entry.specifier.startsWith('.')) {\n unattributedRelativeCalls += countMemberCalls(scan, entry.localName);\n } else {\n unattributedPackageCalls += countMemberCalls(scan, entry.localName);\n }\n }\n for (const fact of scan.facts) {\n facts.push(toFact(file.path, fact));\n }\n for (const call of scan.memberCalls) {\n const bound = resolvedIdents.get(call.ident);\n if (bound === undefined) continue;\n const name = 'name' in bound ? bound.name : bound.dynamicExpression;\n facts.push({\n kind: 'method-invoke',\n channel: name,\n method: call.method,\n dynamic: !('name' in bound) || call.dynamicMethod,\n location: locationOf(file.path, call.token),\n });\n }\n }\n\n const sorted = dedupeFacts(facts);\n const limitations = buildLimitations(files.length, scans, {\n unattributedRelativeCalls,\n unattributedPackageCalls,\n });\n\n return {\n format: 'bridge-facts',\n version: 1,\n tool: { name: 'isthmus', version: toolVersion },\n generatedAt,\n platform: 'js',\n target: sorted.length === 0 ? null : 'react-native',\n project,\n facts: sorted,\n limitations,\n };\n}\n\n/** 한 식별자에 대한 멤버 호출 수를 센다(미귀속 한계 계수용). */\nfunction countMemberCalls(scan: JsFileScan, ident: string): number {\n return scan.memberCalls.filter((call) => call.ident === ident).length;\n}\n\n/**\n * `import X from 'spec'`을 스캔 집합 안에서 해석한다.\n *\n * 상대 specifier만 해석한다 — 패키지 specifier는 스캔 집합(node_modules는\n * 제외됨) 밖이라 귀속 근거가 없다. 배럴 재수출은 순환을 피하기 위해 홉 상한\n * 안에서 따라간다.\n */\nfunction resolveImport(\n scans: ReadonlyMap<string, JsFileScan>,\n fromPath: string,\n entry: ScannedImport,\n): BoundName | undefined {\n let specifier = entry.specifier;\n let exportedName: string | 'default' = entry.exportedName;\n let currentDir = dirnameOf(fromPath);\n for (let hop = 0; hop < 4; hop++) {\n if (!specifier.startsWith('.')) return undefined;\n const target = resolveSpecifier(scans, currentDir, specifier);\n if (target === undefined) return undefined;\n const scan = scans.get(target)!;\n const bound = exportedName === 'default'\n ? scan.exports.defaultName\n : scan.exports.named.get(exportedName);\n if (bound !== undefined) return bound;\n const reexport = exportedName === 'default'\n ? scan.exports.reexported.get('default')\n : scan.exports.reexported.get(exportedName);\n if (reexport === undefined) return undefined;\n specifier = reexport.specifier;\n exportedName = reexport.exportedName;\n currentDir = dirnameOf(target);\n }\n return undefined;\n}\n\n/** specifier를 디렉터리 기준으로 정규화해 스캔 집합에 있는 파일을 찾는다. */\nfunction resolveSpecifier(\n scans: ReadonlyMap<string, JsFileScan>,\n dir: string,\n specifier: string,\n): string | undefined {\n const base = normalizePath(dir === '' ? specifier : `${dir}/${specifier}`);\n const candidates = [\n base,\n ...JS_SOURCE_EXTENSIONS.map((ext) => `${base}.${ext}`),\n ...JS_SOURCE_EXTENSIONS.map((ext) => `${base}/index.${ext}`),\n ];\n return candidates.find((candidate) => scans.has(candidate));\n}\n\n/** `.`·`..` 세그먼트를 접은 POSIX 상대 경로다. 스캔 집합 밖 탈출은 해석 불가다. */\nfunction normalizePath(path: string): string {\n const segments: string[] = [];\n for (const segment of path.split('/')) {\n if (segment === '' || segment === '.') continue;\n if (segment === '..') {\n if (segments.length === 0) return '';\n segments.pop();\n continue;\n }\n segments.push(segment);\n }\n return segments.join('/');\n}\n\n/** 프로젝트 상대 경로의 디렉터리 부분이다. */\nfunction dirnameOf(path: string): string {\n const slash = path.lastIndexOf('/');\n return slash === -1 ? '' : path.slice(0, slash);\n}\n\n/** 스캔 사실을 계약 필드 모양으로 바꾼다. */\nfunction toFact(path: string, fact: {\n kind: BridgeFact['kind'];\n channel: string;\n method?: string;\n mechanism?: BridgeFact['mechanism'];\n optional?: BridgeFact['optional'];\n dynamic: boolean;\n token: { line: number; column: number };\n}): BridgeFact {\n return {\n kind: fact.kind,\n channel: fact.channel,\n ...(fact.method === undefined ? {} : { method: fact.method }),\n ...(fact.mechanism === undefined ? {} : { mechanism: fact.mechanism }),\n ...(fact.optional === undefined ? {} : { optional: fact.optional }),\n dynamic: fact.dynamic,\n location: locationOf(path, fact.token),\n };\n}\n\n/** 토큰 위치를 계약 위치로 바꾼다. */\nfunction locationOf(\n path: string,\n token: { line: number; column: number },\n): BridgeFact['location'] {\n return { path, line: token.line, column: token.column };\n}\n\n/** 같은 위치·같은 키의 중복 사실을 한 번만 남기고 결정적으로 정렬한다. */\nfunction dedupeFacts(facts: readonly BridgeFact[]): BridgeFact[] {\n const seen = new Map<string, BridgeFact>();\n for (const fact of facts) {\n const key = JSON.stringify([\n fact.kind,\n fact.channel,\n fact.method ?? null,\n fact.location.path,\n fact.location.line,\n fact.location.column,\n fact.dynamic,\n ]);\n if (!seen.has(key)) seen.set(key, fact);\n }\n return [...seen.values()].sort((left, right) => {\n const byPath = left.location.path.localeCompare(right.location.path);\n if (byPath !== 0) return byPath;\n if (left.location.line !== right.location.line) {\n return left.location.line - right.location.line;\n }\n if (left.location.column !== right.location.column) {\n return left.location.column - right.location.column;\n }\n return (left.channel ?? '').localeCompare(right.channel ?? '');\n });\n}\n\n/** 집계 계수와 고정 범위 설명을 생산자 limitation 문자열로 바꾼다. */\nfunction buildLimitations(\n fileCount: number,\n scans: ReadonlyMap<string, JsFileScan>,\n extra: {\n unattributedRelativeCalls: number;\n unattributedPackageCalls: number;\n },\n): string[] {\n let dynamicModuleNames = 0;\n let dynamicComponentNames = 0;\n let dynamicMethodNames = 0;\n for (const scan of scans.values()) {\n dynamicModuleNames += scan.counts.dynamicModuleNames;\n dynamicComponentNames += scan.counts.dynamicComponentNames;\n dynamicMethodNames += scan.counts.dynamicMethodNames;\n }\n const limitations: string[] = [\n 'js-binding-scope: member calls through bindings that leave the scan set '\n + '(package imports, function results, instance state) are not attributed '\n + 'to a module name',\n ];\n if (dynamicModuleNames > 0) {\n limitations.push(\n `dynamic-module-names: ${dynamicModuleNames} module references use a `\n + 'non-literal name',\n );\n }\n if (dynamicComponentNames > 0) {\n limitations.push(\n `dynamic-component-names: ${dynamicComponentNames} component requires `\n + 'use a non-literal name',\n );\n }\n if (dynamicMethodNames > 0) {\n limitations.push(\n `dynamic-method-names: ${dynamicMethodNames} bridge method calls use a `\n + 'non-literal method name',\n );\n }\n if (extra.unattributedRelativeCalls > 0) {\n limitations.push(\n `unattributed-js-relative-calls: ${extra.unattributedRelativeCalls} `\n + 'member calls on imports that did not resolve inside the scanned '\n + 'file set could not be attributed to a module name',\n );\n }\n if (extra.unattributedPackageCalls > 0) {\n limitations.push(\n `unattributed-js-package-calls: ${extra.unattributedPackageCalls} member `\n + 'calls on package imports could not be attributed to a module name',\n );\n }\n if (fileCount === 0) {\n limitations.push('empty-js-scan: no JS/TS source files were scanned');\n }\n return limitations;\n}\n"]}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * extract-js의 파일 한 개 스캐너다.
3
+ *
4
+ * 토큰 열 위에서 코어 RN과 Expo Modules의 브리지 호출 형태를 찾는다.
5
+ * 모듈·컴포넌트 이름 경계 사실은 즉시 확정하고, 멤버 호출(`M.foo()`)은
6
+ * 바인딩 정보와 함께 문서 조립 단계로 넘겨 같은 파일·가져오기 해석을 거친다.
7
+ *
8
+ * 이 스캐너는 이름을 정적으로 확정할 수 있는 형태만 fact로 낸다. 비리터럴
9
+ * 인자는 `dynamic: true`에 원문 표현식을 실어 한계 계수에 맡긴다.
10
+ */
11
+ import type { BridgeFact, BridgeMechanism } from '../exchange/parse.ts';
12
+ import type { JsToken } from './lexer.ts';
13
+ /** 스캔이 모은 한 파일의 결과다. */
14
+ export interface JsFileScan {
15
+ /** 이미 확정된 경계·메서드 사실이다. location은 토큰 위치로 호출자가 채운다. */
16
+ readonly facts: readonly ScannedFact[];
17
+ /**
18
+ * 베이스가 로컬·import 식별자인 멤버 호출 후보다. 문서 단계에서 바인딩을
19
+ * 해석해 메서드 호출 사실로 바꾼다.
20
+ */
21
+ readonly memberCalls: readonly ScannedMemberCall[];
22
+ /** 같은 파일에서 식별자가 가리키는 모듈·컴포넌트 이름이다. */
23
+ readonly bindings: ReadonlyMap<string, BoundName>;
24
+ /** 한 단계 문자열 상수(`const X = 'lit'` / 백틱)다. */
25
+ readonly constStrings: ReadonlyMap<string, string>;
26
+ /** `import X from 'spec'` 바인딩이다. 문서 단계에서 파일 해석을 거친다. */
27
+ readonly imports: readonly ScannedImport[];
28
+ /** 다른 파일이 이 파일의 export를 해석하는 데 쓰는 이름 지도다. */
29
+ readonly exports: ScannedExports;
30
+ /** 생산자 limitation에 쓰는 원시 계수다. */
31
+ readonly counts: JsScanCounts;
32
+ }
33
+ /** 스캔이 세는 한계 분류다. */
34
+ export interface JsScanCounts {
35
+ readonly dynamicModuleNames: number;
36
+ readonly dynamicComponentNames: number;
37
+ readonly dynamicMethodNames: number;
38
+ }
39
+ /** 위치 정보가 붙기 전의 사실 골격이다. */
40
+ export interface ScannedFact {
41
+ readonly kind: BridgeFact['kind'];
42
+ readonly channel: string;
43
+ readonly method?: string;
44
+ readonly mechanism?: BridgeMechanism;
45
+ /** 호출 API가 모듈 부재를 허용한다(`null` 반환)는 module-import 증거다. */
46
+ readonly optional?: boolean;
47
+ readonly dynamic: boolean;
48
+ readonly token: JsToken;
49
+ }
50
+ /** 바인딩이 확정된 이름인지 원문 표현식인지 구분한다. */
51
+ export type BoundName = {
52
+ readonly name: string;
53
+ } | {
54
+ readonly dynamicExpression: string;
55
+ };
56
+ /** `ident.method(...)` 또는 `ident[expr](...)` 호출 후보다. */
57
+ export interface ScannedMemberCall {
58
+ /** 베이스 식별자다. */
59
+ readonly ident: string;
60
+ /** 리터럴이면 메서드명, 아니면 원문 표현식이다. */
61
+ readonly method: string;
62
+ readonly dynamicMethod: boolean;
63
+ readonly token: JsToken;
64
+ }
65
+ /** 한 파일의 import 바인딩이다. */
66
+ export interface ScannedImport {
67
+ readonly localName: string;
68
+ readonly specifier: string;
69
+ /** default export 해석인지 named export 해석인지 구분한다. */
70
+ readonly exportedName: string | 'default';
71
+ readonly token: JsToken;
72
+ }
73
+ /** 이 파일이 외부에보내는 모듈 바인딩이다. */
74
+ export interface ScannedExports {
75
+ readonly defaultName?: BoundName;
76
+ readonly named: ReadonlyMap<string, BoundName>;
77
+ /**
78
+ * `export { A as B } from 'spec'`·`export { default as X } from 'spec'`의
79
+ * 배럴 재수출이다. 키는 이 파일이보내는 이름, 값은 원본 specifier다.
80
+ */
81
+ readonly reexported: ReadonlyMap<string, {
82
+ readonly specifier: string;
83
+ readonly exportedName: string | 'default';
84
+ }>;
85
+ }
86
+ /** 한 소스 텍스트를 스캔해 사실 후보·바인딩·계수를 모은다. */
87
+ export declare function scanJsSource(source: string): JsFileScan;
88
+ /**
89
+ * 동적 표현식의 원문을 계약 필드가 허용하는 안전한 문자열로 바꾼다.
90
+ *
91
+ * 제어 문자는 공백으로 접고 길이를 제한해 원문 표현이 키 파싱을 깨지 않게 한다.
92
+ */
93
+ export declare function sanitizeExpression(expression: string): string;