@rahmantech/knl-view 0.0.0-stage → 0.2.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/LICENSE +201 -0
- package/README.md +483 -2
- package/bin/knl-view.js +1320 -0
- package/dist/assets/abnfDiagram-WEYOIA3C-hoSbsMOz.js +1 -0
- package/dist/assets/arc-g87Np6E5.js +1 -0
- package/dist/assets/architecture-WOLXFQ4H-DOIq-0IA.js +1 -0
- package/dist/assets/architectureDiagram-ZHVYKT53-Dxwe8n7m.js +36 -0
- package/dist/assets/array-BifhSqXX.js +1 -0
- package/dist/assets/blockDiagram-RJAE7QA3-BnGng59Z.js +140 -0
- package/dist/assets/c4Diagram-XDPPPE54-C5LjqIIo.js +38 -0
- package/dist/assets/channel-BVWkRFBw.js +1 -0
- package/dist/assets/chunk-2E7WHVMD-BJ1dyGLq.js +88 -0
- package/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/dist/assets/chunk-4EMNKR2T-DXF02Sgw.js +1 -0
- package/dist/assets/chunk-55XCU76K-CM1akAbu.js +1 -0
- package/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/dist/assets/chunk-7S6XSTFO-BOUjqldL.js +2 -0
- package/dist/assets/chunk-AQDTSKZB-C4isdNLA.js +72 -0
- package/dist/assets/chunk-DSNLSHJZ-CbOTkSVM.js +129 -0
- package/dist/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
- package/dist/assets/chunk-HAGFOTED-CRfnketR.js +62 -0
- package/dist/assets/chunk-HNE4Q36P-rxlpGUU8.js +1 -0
- package/dist/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
- package/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/dist/assets/chunk-NGNAAXSQ-C2Wy9Xmz.js +136 -0
- package/dist/assets/chunk-NQOKRVIF-BOYCgTmV.js +213 -0
- package/dist/assets/chunk-UHW7PFBP-Cnogp1Fb.js +1 -0
- package/dist/assets/chunk-VQ5W62QI-Cu1WY5Uo.js +1 -0
- package/dist/assets/chunk-WQQL4CDK-BJc3FPE2.js +10 -0
- package/dist/assets/chunk-XXDRQBXY-BlPj4H7s.js +1 -0
- package/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/dist/assets/chunk-YZO67HFG-V85H31x4.js +1 -0
- package/dist/assets/chunk-ZAUX2LTX-C3XKXW09.js +1 -0
- package/dist/assets/chunk-ZCHMQSOR-B0mmpEgW.js +2 -0
- package/dist/assets/classDiagram-v2-4MYWR3UA-oMd7YaHP.js +217 -0
- package/dist/assets/cose-bilkent-JH36ORCC-J0kSVsDf.js +1 -0
- package/dist/assets/cynefin-EF2NZ3EQ-DK2TNhel.js +1 -0
- package/dist/assets/cynefinDiagram-WF24UDRP-DDvKgieA.js +62 -0
- package/dist/assets/cytoscape.esm-BegBNzOV.js +321 -0
- package/dist/assets/dagre-NK4GIZVX-Ca2Fe-i-.js +4 -0
- package/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/dist/assets/diagram-CBK3MWTC-L6dmeQ2V.js +24 -0
- package/dist/assets/diagram-I5XIIV2D-DhG8R-v5.js +24 -0
- package/dist/assets/diagram-ODIL2UCW-BpYYbYBT.js +30 -0
- package/dist/assets/diagram-PFBEJUXE-DKQpHSsC.js +3 -0
- package/dist/assets/diagram-RJA3I4VV-DP7Dsnya.js +41 -0
- package/dist/assets/diagram-TYUNFSNR-CfSFewUS.js +200 -0
- package/dist/assets/dist-CQNjnrld.js +1 -0
- package/dist/assets/ebnfDiagram-V4P3TTPT-DE3MZoJO.js +1 -0
- package/dist/assets/elk-QONTBCTQ-p_aAQPHU.js +27 -0
- package/dist/assets/erDiagram-KYQIGQAA-DZO3vQrk.js +99 -0
- package/dist/assets/eventmodeling-K75KTNOO-BII9EPl5.js +1 -0
- package/dist/assets/flowDiagram-IUGLLWBU-D9uPno39.js +1 -0
- package/dist/assets/ganttDiagram-R7GPNTJY-BKiH3v2r.js +292 -0
- package/dist/assets/gitGraph-VSP46ZUC-oHL6cr73.js +1 -0
- package/dist/assets/gitGraphDiagram-PDYL7XHY-CqPVIEeo.js +106 -0
- package/dist/assets/graphlib-DS17s2tU.js +1 -0
- package/dist/assets/index-CSpirV16.js +76 -0
- package/dist/assets/index-DLgMsUs2.css +1 -0
- package/dist/assets/info-OHQRW6UA-Drrbnhpd.js +1 -0
- package/dist/assets/infoDiagram-MVDRKAX5-DnQQFMAd.js +2 -0
- package/dist/assets/init-C-OQMol4.js +1 -0
- package/dist/assets/ishikawaDiagram-HPEHV2GE-DIpQlxRh.js +70 -0
- package/dist/assets/journeyDiagram-BOSHW7O7-EBR9I5h1.js +139 -0
- package/dist/assets/kanban-definition-FNSA4HC5-CdvNsjik.js +89 -0
- package/dist/assets/katex-B_JcIwqj.js +257 -0
- package/dist/assets/line-CDLzgxDc.js +1 -0
- package/dist/assets/linear-DfXKql9u.js +1 -0
- package/dist/assets/mermaid-parser.core-DnXGphT2.js +7 -0
- package/dist/assets/mermaid.core-h6fiCaSZ.js +44 -0
- package/dist/assets/mindmap-definition-6OY57UG3-BiNfio-j.js +98 -0
- package/dist/assets/ordinal-BDEzSJ7C.js +1 -0
- package/dist/assets/packet-JDAUHWVQ-D76byv1S.js +1 -0
- package/dist/assets/path-fybaL0A-.js +1 -0
- package/dist/assets/pegDiagram-ECIUJNBX-Drs8Pxdt.js +1 -0
- package/dist/assets/pie-XZMESJXO-D_83z1lM.js +1 -0
- package/dist/assets/pieDiagram-GNMPY74T-wvzIphSf.js +39 -0
- package/dist/assets/quadrantDiagram-D57K4MC3-B7EfMRWb.js +7 -0
- package/dist/assets/radar-ABXABTNO-Bs6dVdAR.js +1 -0
- package/dist/assets/railroad-I3PHUGI6-lKaN4miw.js +1 -0
- package/dist/assets/railroad-abnf-LNEFI6M7-Bhh16b7Z.js +1 -0
- package/dist/assets/railroad-ebnf-DWYD2UWJ-m6ybW-Y_.js +1 -0
- package/dist/assets/railroad-peg-JR7BVNR7-DNn72e7r.js +1 -0
- package/dist/assets/railroadDiagram-Y72NRBL7-DNeop1iZ.js +1 -0
- package/dist/assets/requirementDiagram-EQYSW5RA-BVYZ2XYJ.js +84 -0
- package/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
- package/dist/assets/sankeyDiagram-SY24JHO5-B2MP51on.js +40 -0
- package/dist/assets/sequenceDiagram-BLEHNHQU-BUvxstXG.js +169 -0
- package/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
- package/dist/assets/src-BrR0lqlG.js +1 -0
- package/dist/assets/stateDiagram-v2-A6IESKWZ-Ba84OGKE.js +291 -0
- package/dist/assets/swimlanes-B3ENRKFQ-BMSl2PNA.js +1 -0
- package/dist/assets/swimlanesDiagram-AYQRV4AK-M1dmvq7m.js +8 -0
- package/dist/assets/timeline-definition-JXYKGLGB-DOdW-dxq.js +120 -0
- package/dist/assets/treeView-D4JQ5SDB-DZ9KQyn5.js +1 -0
- package/dist/assets/treemap-SAJKECNS-WEC5wtDV.js +1 -0
- package/dist/assets/usecaseDiagram-WPOLBNNX-cldTztFL.js +328 -0
- package/dist/assets/vennDiagram-Q3Y3IDCW-c41KySoY.js +34 -0
- package/dist/assets/wardley-7MLQ67FV-Bf9_aSSZ.js +1 -0
- package/dist/assets/wardleyDiagram-IVAIDKWA-BeKEvEBG.js +78 -0
- package/dist/assets/xychartDiagram-4URNHDU4-tNkAxTxG.js +7 -0
- package/dist/index.html +22 -0
- package/package.json +50 -4
- package/server/config.js +389 -0
- package/server/consensus.js +180 -0
- package/server/graph/anchordb_client.js +253 -0
- package/server/graph/model.js +157 -0
- package/server/graph/parser.js +327 -0
- package/server/graph/sources.js +801 -0
- package/server/graph/syntax.js +437 -0
- package/server/index.js +742 -0
- package/server/install.js +437 -0
- package/server/map.js +126 -0
- package/server/models_registry.js +109 -0
- package/server/scaffold.js +466 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { Parser, Language } from 'web-tree-sitter'
|
|
3
|
+
|
|
4
|
+
// Syntax-aware import extraction with tree-sitter (web-tree-sitter, WASM).
|
|
5
|
+
// One uniform parser API for every supported language: adding a language
|
|
6
|
+
// means adding one grammar package and one extraction case. Grammars are
|
|
7
|
+
// lazy-loaded only when the scanned project actually contains files of
|
|
8
|
+
// that language.
|
|
9
|
+
//
|
|
10
|
+
// Extraction returns the raw import specifiers of a file; deciding which
|
|
11
|
+
// of them point inside the project is the resolver's job (sources.js), so
|
|
12
|
+
// this module stays language-shaped, not project-shaped.
|
|
13
|
+
|
|
14
|
+
const require = createRequire(import.meta.url)
|
|
15
|
+
|
|
16
|
+
const GRAMMAR_WASM = {
|
|
17
|
+
javascript: 'tree-sitter-javascript/tree-sitter-javascript.wasm',
|
|
18
|
+
typescript: 'tree-sitter-typescript/tree-sitter-typescript.wasm',
|
|
19
|
+
tsx: 'tree-sitter-typescript/tree-sitter-tsx.wasm',
|
|
20
|
+
go: 'tree-sitter-go/tree-sitter-go.wasm',
|
|
21
|
+
python: 'tree-sitter-python/tree-sitter-python.wasm',
|
|
22
|
+
rust: 'tree-sitter-rust/tree-sitter-rust.wasm'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The JS/TS, Go, Python, and Rust families; each extension maps to its grammar. */
|
|
26
|
+
export const SYNTAX_LANGUAGES = {
|
|
27
|
+
'.js': 'javascript',
|
|
28
|
+
'.mjs': 'javascript',
|
|
29
|
+
'.cjs': 'javascript',
|
|
30
|
+
'.jsx': 'javascript',
|
|
31
|
+
'.ts': 'typescript',
|
|
32
|
+
'.mts': 'typescript',
|
|
33
|
+
'.cts': 'typescript',
|
|
34
|
+
'.tsx': 'tsx',
|
|
35
|
+
'.go': 'go',
|
|
36
|
+
'.py': 'python',
|
|
37
|
+
'.rs': 'rust'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let initPromise = null
|
|
41
|
+
const languages = new Map() // grammar name -> Language
|
|
42
|
+
const parsers = new Map() // grammar name -> Parser
|
|
43
|
+
|
|
44
|
+
async function languageFor(ext) {
|
|
45
|
+
const name = SYNTAX_LANGUAGES[ext]
|
|
46
|
+
if (languages.has(name)) return languages.get(name)
|
|
47
|
+
initPromise ||= Parser.init()
|
|
48
|
+
await initPromise
|
|
49
|
+
const lang = await Language.load(require.resolve(GRAMMAR_WASM[name]))
|
|
50
|
+
languages.set(name, lang)
|
|
51
|
+
return lang
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parserFor(name, lang) {
|
|
55
|
+
if (!parsers.has(name)) {
|
|
56
|
+
const parser = new Parser()
|
|
57
|
+
parser.setLanguage(lang)
|
|
58
|
+
parsers.set(name, parser)
|
|
59
|
+
}
|
|
60
|
+
return parsers.get(name)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function firstDescendant(node, type) {
|
|
64
|
+
if (node.type === type) return node
|
|
65
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
66
|
+
const found = firstDescendant(node.child(i), type)
|
|
67
|
+
if (found) return found
|
|
68
|
+
}
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Raw specifiers from one parsed tree. JS: ESM import/export sources,
|
|
74
|
+
* dynamic import(), and require(). Go: every import_spec path, alias or
|
|
75
|
+
* not. Bare names, URLs, and package paths are returned as-is; filtering
|
|
76
|
+
* to project-internal targets happens in the resolver.
|
|
77
|
+
*/
|
|
78
|
+
function specsFromTree(grammar, tree) {
|
|
79
|
+
const specs = []
|
|
80
|
+
const names = []
|
|
81
|
+
const visit = (node) => {
|
|
82
|
+
if (grammar === 'javascript' || grammar === 'typescript' || grammar === 'tsx') {
|
|
83
|
+
if (node.type === 'import_statement' || node.type === 'export_statement') {
|
|
84
|
+
const source = node.childForFieldName('source')
|
|
85
|
+
const fragment = source && firstDescendant(source, 'string_fragment')
|
|
86
|
+
if (fragment) specs.push(fragment.text)
|
|
87
|
+
// Local binding names a qualified call may be addressed through:
|
|
88
|
+
// the default import, the namespace alias, and each named import.
|
|
89
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
90
|
+
const clause = node.child(i)
|
|
91
|
+
if (clause.type !== 'import_clause') continue
|
|
92
|
+
for (let j = 0; j < clause.childCount; j++) {
|
|
93
|
+
const part = clause.child(j)
|
|
94
|
+
if (part.type === 'identifier') names.push(part.text)
|
|
95
|
+
else if (part.type === 'namespace_import') {
|
|
96
|
+
for (let k = 0; k < part.childCount; k++) {
|
|
97
|
+
if (part.child(k).type === 'identifier') names.push(part.child(k).text)
|
|
98
|
+
}
|
|
99
|
+
} else if (part.type === 'named_imports') {
|
|
100
|
+
for (let k = 0; k < part.childCount; k++) {
|
|
101
|
+
const specifier = part.child(k)
|
|
102
|
+
if (specifier.type !== 'import_specifier') continue
|
|
103
|
+
const alias = specifier.childForFieldName('alias') || specifier.childForFieldName('name')
|
|
104
|
+
if (alias) names.push(alias.text)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} else if (node.type === 'call_expression') {
|
|
110
|
+
const fn = node.childForFieldName('function')
|
|
111
|
+
if (fn && (fn.text === 'require' || fn.text === 'import')) {
|
|
112
|
+
const args = node.childForFieldName('arguments')
|
|
113
|
+
const fragment = args && firstDescendant(args, 'string_fragment')
|
|
114
|
+
if (fragment) specs.push(fragment.text)
|
|
115
|
+
// `const x = require('y')` / `const x = await import('y')`: the
|
|
116
|
+
// declarator name is the binding: the visit starts above it, so
|
|
117
|
+
// walk down from any enclosing declarator via field lookup here.
|
|
118
|
+
let parent = node.parent
|
|
119
|
+
while (parent && parent.type === 'await_expression') parent = parent.parent
|
|
120
|
+
if (parent && parent.type === 'variable_declarator') {
|
|
121
|
+
const name = parent.childForFieldName('name')
|
|
122
|
+
if (name) names.push(name.text)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} else if (grammar === 'go') {
|
|
127
|
+
if (node.type === 'import_spec') {
|
|
128
|
+
const declared = node.childForFieldName('path')
|
|
129
|
+
const fragment = declared && firstDescendant(declared, 'interpreted_string_literal_content')
|
|
130
|
+
if (fragment) {
|
|
131
|
+
const path = fragment.text
|
|
132
|
+
specs.push(path)
|
|
133
|
+
// The name a qualified call uses: the declared alias, or the last
|
|
134
|
+
// path segment (Go's convention when no alias is given).
|
|
135
|
+
const alias = node.childForFieldName('name')
|
|
136
|
+
if (alias) names.push(alias.text)
|
|
137
|
+
else names.push(path.slice(path.lastIndexOf('/') + 1))
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} else if (grammar === 'python') {
|
|
141
|
+
if (node.type === 'import_statement') {
|
|
142
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
143
|
+
const c = node.child(i)
|
|
144
|
+
if (c.type === 'dotted_name') {
|
|
145
|
+
specs.push(c.text)
|
|
146
|
+
const firstId = c.child(0)
|
|
147
|
+
if (firstId) names.push(firstId.text)
|
|
148
|
+
} else if (c.type === 'aliased_import') {
|
|
149
|
+
const name = c.childForFieldName('name')
|
|
150
|
+
const alias = c.childForFieldName('alias')
|
|
151
|
+
if (name) specs.push(name.text)
|
|
152
|
+
if (alias) names.push(alias.text)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} else if (node.type === 'import_from_statement') {
|
|
156
|
+
const mod = node.childForFieldName('module_name')
|
|
157
|
+
const modText = mod ? mod.text : ''
|
|
158
|
+
if (modText) specs.push(modText)
|
|
159
|
+
let pastImport = false
|
|
160
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
161
|
+
const c = node.child(i)
|
|
162
|
+
if (c.type === 'import') {
|
|
163
|
+
pastImport = true
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
if (!pastImport) continue
|
|
167
|
+
if (c.type === 'dotted_name') {
|
|
168
|
+
names.push(c.text)
|
|
169
|
+
if (modText) specs.push(modText + '.' + c.text)
|
|
170
|
+
} else if (c.type === 'aliased_import') {
|
|
171
|
+
const name = c.childForFieldName('name')
|
|
172
|
+
const alias = c.childForFieldName('alias')
|
|
173
|
+
if (alias) names.push(alias.text)
|
|
174
|
+
if (name && modText) specs.push(modText + '.' + name.text)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} else if (grammar === 'rust') {
|
|
179
|
+
if (node.type === 'mod_item') {
|
|
180
|
+
if (!node.childForFieldName('body')) {
|
|
181
|
+
const name = node.childForFieldName('name')
|
|
182
|
+
if (name) {
|
|
183
|
+
specs.push('./' + name.text)
|
|
184
|
+
names.push(name.text)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} else if (node.type === 'use_declaration') {
|
|
188
|
+
const extractRustUse = (n, prefix = '') => {
|
|
189
|
+
if (!n) return
|
|
190
|
+
if (n.type === 'scoped_use_list') {
|
|
191
|
+
const pathNode = n.childForFieldName('path')
|
|
192
|
+
const listNode = n.childForFieldName('list')
|
|
193
|
+
const p = (prefix ? prefix + '::' : '') + (pathNode ? pathNode.text : '')
|
|
194
|
+
extractRustUse(listNode, p)
|
|
195
|
+
} else if (n.type === 'use_list') {
|
|
196
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
197
|
+
const c = n.child(i)
|
|
198
|
+
if (c.type === 'identifier' || c.type === 'scoped_identifier' || c.type === 'use_as_clause' || c.type === 'scoped_use_list') {
|
|
199
|
+
extractRustUse(c, prefix)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} else if (n.type === 'use_as_clause') {
|
|
203
|
+
const pathNode = n.childForFieldName('path')
|
|
204
|
+
const aliasNode = n.childForFieldName('alias')
|
|
205
|
+
const full = (prefix ? prefix + '::' : '') + (pathNode ? pathNode.text : '')
|
|
206
|
+
specs.push(full)
|
|
207
|
+
if (aliasNode) names.push(aliasNode.text)
|
|
208
|
+
} else if (n.type === 'scoped_identifier') {
|
|
209
|
+
const full = (prefix ? prefix + '::' : '') + n.text
|
|
210
|
+
specs.push(full)
|
|
211
|
+
const name = n.childForFieldName('name')
|
|
212
|
+
if (name) names.push(name.text)
|
|
213
|
+
} else if (n.type === 'identifier') {
|
|
214
|
+
const full = (prefix ? prefix + '::' : '') + n.text
|
|
215
|
+
specs.push(full)
|
|
216
|
+
names.push(n.text)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
extractRustUse(node.childForFieldName('argument'))
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
for (let i = 0; i < node.childCount; i++) visit(node.child(i))
|
|
223
|
+
}
|
|
224
|
+
visit(tree.rootNode)
|
|
225
|
+
return { specs, names }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Parse one file's text with the grammar of its extension and return its
|
|
230
|
+
* raw import specifiers. A syntax error never throws: tree-sitter recovers
|
|
231
|
+
* and whatever imports are well-formed are still extracted.
|
|
232
|
+
* @param {string} ext lowercase extension with the dot, e.g. '.go'
|
|
233
|
+
* @param {string} content file text
|
|
234
|
+
* @returns {Promise<string[]>}
|
|
235
|
+
*/
|
|
236
|
+
export async function extractImportSpecifiers(ext, content) {
|
|
237
|
+
return (await extractCode(ext, content)).imports
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Parse one file once and extract everything the sources graph needs:
|
|
242
|
+
*
|
|
243
|
+
* imports raw import specifiers (see extractImportSpecifiers)
|
|
244
|
+
* importNames local names those imports are addressed by: JS bindings
|
|
245
|
+
* (default/namespace/named, plus `const x = require/import`),
|
|
246
|
+
* Go aliases or last path segments. Evidence that a receiver
|
|
247
|
+
* prefix in a qualified call refers to an import.
|
|
248
|
+
* symbols named definitions: { name, kind, line, lines }, line 1-based,
|
|
249
|
+
* lines = height of the definition
|
|
250
|
+
* calls call sites: { name, recv, row }, row 0-based; recv is the
|
|
251
|
+
* receiver/prefix text for qualified calls (recv.method(),
|
|
252
|
+
* pkg.Fn()) and null for plain identifiers
|
|
253
|
+
*
|
|
254
|
+
* Calls are attributed to nothing here; mapping a call to its enclosing
|
|
255
|
+
* symbol is the caller's job (it needs the full symbol list anyway).
|
|
256
|
+
* A syntax error never throws: tree-sitter recovers and whatever is
|
|
257
|
+
* well-formed is still extracted.
|
|
258
|
+
*/
|
|
259
|
+
export async function extractCode(ext, content) {
|
|
260
|
+
const lang = await languageFor(ext)
|
|
261
|
+
const grammar = SYNTAX_LANGUAGES[ext]
|
|
262
|
+
const parser = parserFor(grammar, lang)
|
|
263
|
+
const tree = parser.parse(content)
|
|
264
|
+
try {
|
|
265
|
+
const { specs: imports, names: importNames } = specsFromTree(grammar, tree)
|
|
266
|
+
const { symbols, calls } = structureFromTree(grammar, tree)
|
|
267
|
+
return { imports, importNames, symbols, calls }
|
|
268
|
+
} finally {
|
|
269
|
+
tree.delete()
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Named definitions and call sites from one parsed tree. Only definitions
|
|
275
|
+
* that carry an explicit name enter the graph; anonymous helpers are left
|
|
276
|
+
* out, deterministically.
|
|
277
|
+
*/
|
|
278
|
+
function structureFromTree(grammar, tree) {
|
|
279
|
+
const symbols = []
|
|
280
|
+
const calls = []
|
|
281
|
+
const pushSymbol = (node, name, kind) => {
|
|
282
|
+
symbols.push({
|
|
283
|
+
name,
|
|
284
|
+
kind,
|
|
285
|
+
line: node.startPosition.row + 1,
|
|
286
|
+
lines: node.endPosition.row - node.startPosition.row + 1
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
const visit = (node, cls) => {
|
|
290
|
+
if (grammar === 'javascript' || grammar === 'typescript' || grammar === 'tsx') {
|
|
291
|
+
if (node.type === 'function_declaration' || node.type === 'generator_function_declaration') {
|
|
292
|
+
const name = node.childForFieldName('name')
|
|
293
|
+
if (name) pushSymbol(node, name.text, 'function')
|
|
294
|
+
} else if (node.type === 'class_declaration') {
|
|
295
|
+
const name = node.childForFieldName('name')
|
|
296
|
+
if (name) {
|
|
297
|
+
pushSymbol(node, name.text, 'class')
|
|
298
|
+
cls = name.text // class body below walks with this name in scope
|
|
299
|
+
}
|
|
300
|
+
} else if (node.type === 'method_definition') {
|
|
301
|
+
const name = node.childForFieldName('name')
|
|
302
|
+
if (name && name.type === 'property_identifier' && cls) {
|
|
303
|
+
pushSymbol(node, `${cls}.${name.text}`, 'method')
|
|
304
|
+
}
|
|
305
|
+
} else if (node.type === 'lexical_declaration' || node.type === 'variable_declaration') {
|
|
306
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
307
|
+
const declarator = node.child(i)
|
|
308
|
+
if (declarator.type !== 'variable_declarator') continue
|
|
309
|
+
const value = declarator.childForFieldName('value')
|
|
310
|
+
const name = declarator.childForFieldName('name')
|
|
311
|
+
if (value && name && (value.type === 'arrow_function' || value.type === 'function_expression')) {
|
|
312
|
+
pushSymbol(declarator, name.text, 'function')
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
} else if (node.type === 'interface_declaration') {
|
|
316
|
+
const name = node.childForFieldName('name')
|
|
317
|
+
if (name) pushSymbol(node, name.text, 'interface')
|
|
318
|
+
} else if (node.type === 'type_alias_declaration') {
|
|
319
|
+
const name = node.childForFieldName('name')
|
|
320
|
+
if (name) pushSymbol(node, name.text, 'type')
|
|
321
|
+
} else if (node.type === 'enum_declaration') {
|
|
322
|
+
const name = node.childForFieldName('name')
|
|
323
|
+
if (name) pushSymbol(node, name.text, 'enum')
|
|
324
|
+
}
|
|
325
|
+
} else if (grammar === 'go') {
|
|
326
|
+
if (node.type === 'function_declaration') {
|
|
327
|
+
const name = node.childForFieldName('name')
|
|
328
|
+
if (name) pushSymbol(node, name.text, 'function')
|
|
329
|
+
} else if (node.type === 'method_declaration') {
|
|
330
|
+
const name = node.childForFieldName('name')
|
|
331
|
+
const receiver = node.childForFieldName('receiver')
|
|
332
|
+
const recvType = receiver && firstDescendant(receiver, 'type_identifier')
|
|
333
|
+
if (name && recvType) pushSymbol(node, `${recvType.text}.${name.text}`, 'method')
|
|
334
|
+
} else if (node.type === 'type_spec') {
|
|
335
|
+
const name = node.childForFieldName('name')
|
|
336
|
+
if (name) pushSymbol(node, name.text, 'type')
|
|
337
|
+
}
|
|
338
|
+
} else if (grammar === 'python') {
|
|
339
|
+
if (node.type === 'class_definition') {
|
|
340
|
+
const name = node.childForFieldName('name')
|
|
341
|
+
if (name) {
|
|
342
|
+
pushSymbol(node, name.text, 'class')
|
|
343
|
+
cls = name.text
|
|
344
|
+
}
|
|
345
|
+
} else if (node.type === 'function_definition') {
|
|
346
|
+
const name = node.childForFieldName('name')
|
|
347
|
+
if (name) {
|
|
348
|
+
if (cls) pushSymbol(node, `${cls}.${name.text}`, 'method')
|
|
349
|
+
else pushSymbol(node, name.text, 'function')
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
} else if (grammar === 'rust') {
|
|
353
|
+
if (node.type === 'struct_item') {
|
|
354
|
+
const name = node.childForFieldName('name')
|
|
355
|
+
if (name) pushSymbol(node, name.text, 'struct')
|
|
356
|
+
} else if (node.type === 'enum_item') {
|
|
357
|
+
const name = node.childForFieldName('name')
|
|
358
|
+
if (name) pushSymbol(node, name.text, 'enum')
|
|
359
|
+
} else if (node.type === 'trait_item') {
|
|
360
|
+
const name = node.childForFieldName('name')
|
|
361
|
+
if (name) pushSymbol(node, name.text, 'trait')
|
|
362
|
+
} else if (node.type === 'type_item') {
|
|
363
|
+
const name = node.childForFieldName('name')
|
|
364
|
+
if (name) pushSymbol(node, name.text, 'type')
|
|
365
|
+
} else if (node.type === 'impl_item') {
|
|
366
|
+
const typeNode = node.childForFieldName('type')
|
|
367
|
+
if (typeNode) cls = typeNode.text
|
|
368
|
+
} else if (node.type === 'function_item') {
|
|
369
|
+
const name = node.childForFieldName('name')
|
|
370
|
+
if (name) {
|
|
371
|
+
if (cls) pushSymbol(node, `${cls}.${name.text}`, 'method')
|
|
372
|
+
else pushSymbol(node, name.text, 'function')
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (grammar === 'python') {
|
|
378
|
+
if (node.type === 'call') {
|
|
379
|
+
const fn = node.childForFieldName('function')
|
|
380
|
+
if (fn) {
|
|
381
|
+
if (fn.type === 'identifier') {
|
|
382
|
+
calls.push({ name: fn.text, recv: null, row: node.startPosition.row })
|
|
383
|
+
} else if (fn.type === 'attribute') {
|
|
384
|
+
const obj = fn.childForFieldName('object')
|
|
385
|
+
const attr = fn.childForFieldName('attribute')
|
|
386
|
+
if (attr) {
|
|
387
|
+
calls.push({ name: attr.text, recv: obj ? obj.text : null, row: node.startPosition.row })
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
} else if (grammar === 'rust') {
|
|
393
|
+
if (node.type === 'call_expression') {
|
|
394
|
+
const fn = node.childForFieldName('function')
|
|
395
|
+
if (fn) {
|
|
396
|
+
if (fn.type === 'identifier') {
|
|
397
|
+
calls.push({ name: fn.text, recv: null, row: node.startPosition.row })
|
|
398
|
+
} else if (fn.type === 'field_expression') {
|
|
399
|
+
const val = fn.childForFieldName('value')
|
|
400
|
+
const field = fn.childForFieldName('field')
|
|
401
|
+
if (field) {
|
|
402
|
+
calls.push({ name: field.text, recv: val ? val.text : null, row: node.startPosition.row })
|
|
403
|
+
}
|
|
404
|
+
} else if (fn.type === 'scoped_identifier') {
|
|
405
|
+
const pathNode = fn.childForFieldName('path')
|
|
406
|
+
const nameNode = fn.childForFieldName('name')
|
|
407
|
+
if (nameNode) {
|
|
408
|
+
const recv = pathNode
|
|
409
|
+
? (pathNode.type === 'scoped_identifier' ? (pathNode.childForFieldName('name') || pathNode).text : pathNode.text)
|
|
410
|
+
: null
|
|
411
|
+
calls.push({ name: nameNode.text, recv, row: node.startPosition.row })
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
if (node.type === 'call_expression' || node.type === 'new_expression') {
|
|
418
|
+
const fn = node.type === 'call_expression'
|
|
419
|
+
? node.childForFieldName('function')
|
|
420
|
+
: node.childForFieldName('constructor')
|
|
421
|
+
if (fn) {
|
|
422
|
+
if (fn.type === 'identifier') {
|
|
423
|
+
calls.push({ name: fn.text, recv: null, row: node.startPosition.row })
|
|
424
|
+
} else if (fn.type === 'member_expression' || fn.type === 'selector_expression') {
|
|
425
|
+
const named = fn.namedChildren
|
|
426
|
+
if (named.length >= 2) {
|
|
427
|
+
calls.push({ name: named[named.length - 1].text, recv: named[0].text, row: node.startPosition.row })
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
for (let i = 0; i < node.childCount; i++) visit(node.child(i), cls)
|
|
434
|
+
}
|
|
435
|
+
visit(tree.rootNode, null)
|
|
436
|
+
return { symbols, calls }
|
|
437
|
+
}
|