@rahmantech/knl-view 0.1.0 → 0.3.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.md +133 -32
- package/bin/knl-view.js +591 -58
- package/dist/assets/{abnfDiagram-WEYOIA3C-B1gaBaSI.js → abnfDiagram-WEYOIA3C-hoSbsMOz.js} +1 -1
- package/dist/assets/{arc-NZ9uRaCl.js → arc-g87Np6E5.js} +1 -1
- package/dist/assets/{architecture-WOLXFQ4H-8MalLwTd.js → architecture-WOLXFQ4H-DOIq-0IA.js} +1 -1
- package/dist/assets/{architectureDiagram-ZHVYKT53-eZ8i5cl4.js → architectureDiagram-ZHVYKT53-Dxwe8n7m.js} +1 -1
- package/dist/assets/{blockDiagram-RJAE7QA3-BavOE8BM.js → blockDiagram-RJAE7QA3-BnGng59Z.js} +1 -1
- package/dist/assets/{c4Diagram-XDPPPE54-DqMaTAzx.js → c4Diagram-XDPPPE54-C5LjqIIo.js} +1 -1
- package/dist/assets/channel-BVWkRFBw.js +1 -0
- package/dist/assets/{chunk-2E7WHVMD-B1Hj2BjJ.js → chunk-2E7WHVMD-BJ1dyGLq.js} +1 -1
- package/dist/assets/{chunk-4EMNKR2T-HDscrCiq.js → chunk-4EMNKR2T-DXF02Sgw.js} +1 -1
- package/dist/assets/{chunk-55XCU76K-QR_ARj8I.js → chunk-55XCU76K-CM1akAbu.js} +1 -1
- package/dist/assets/{chunk-7S6XSTFO-B0p3o547.js → chunk-7S6XSTFO-BOUjqldL.js} +1 -1
- package/dist/assets/{chunk-AQDTSKZB-CCxwNyyA.js → chunk-AQDTSKZB-C4isdNLA.js} +1 -1
- package/dist/assets/{chunk-DSNLSHJZ-BM5PBFmz.js → chunk-DSNLSHJZ-CbOTkSVM.js} +1 -1
- package/dist/assets/{chunk-HAGFOTED-UCDVFuN_.js → chunk-HAGFOTED-CRfnketR.js} +1 -1
- package/dist/assets/{chunk-HNE4Q36P-C_1sakp2.js → chunk-HNE4Q36P-rxlpGUU8.js} +1 -1
- package/dist/assets/{chunk-NQOKRVIF-b2D1Z4oK.js → chunk-NQOKRVIF-BOYCgTmV.js} +1 -1
- package/dist/assets/{chunk-UHW7PFBP-CJs0VwBY.js → chunk-UHW7PFBP-Cnogp1Fb.js} +1 -1
- package/dist/assets/{chunk-VQ5W62QI-DQxjgE4_.js → chunk-VQ5W62QI-Cu1WY5Uo.js} +1 -1
- package/dist/assets/{chunk-WQQL4CDK-DcjPeAfQ.js → chunk-WQQL4CDK-BJc3FPE2.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-glA0kB5s.js → chunk-XXDRQBXY-BlPj4H7s.js} +1 -1
- package/dist/assets/{chunk-YZO67HFG-DJqACL5V.js → chunk-YZO67HFG-V85H31x4.js} +1 -1
- package/dist/assets/{chunk-ZAUX2LTX-CWfaYtwb.js → chunk-ZAUX2LTX-C3XKXW09.js} +1 -1
- package/dist/assets/{chunk-ZCHMQSOR-BGX73Hho.js → chunk-ZCHMQSOR-B0mmpEgW.js} +1 -1
- package/dist/assets/{classDiagram-v2-4MYWR3UA-BpYXPz-e.js → classDiagram-v2-4MYWR3UA-oMd7YaHP.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-e1quKm0Q.js → cose-bilkent-JH36ORCC-J0kSVsDf.js} +1 -1
- package/dist/assets/{cynefin-EF2NZ3EQ-hb3oUmFO.js → cynefin-EF2NZ3EQ-DK2TNhel.js} +1 -1
- package/dist/assets/{cynefinDiagram-WF24UDRP-CnMQvLqi.js → cynefinDiagram-WF24UDRP-DDvKgieA.js} +1 -1
- package/dist/assets/{dagre-NK4GIZVX-BH_Y8y3F.js → dagre-NK4GIZVX-Ca2Fe-i-.js} +1 -1
- package/dist/assets/{diagram-CBK3MWTC-Dh79VetZ.js → diagram-CBK3MWTC-L6dmeQ2V.js} +1 -1
- package/dist/assets/{diagram-I5XIIV2D-D3u-o1RV.js → diagram-I5XIIV2D-DhG8R-v5.js} +1 -1
- package/dist/assets/{diagram-ODIL2UCW-Bfuo-OKS.js → diagram-ODIL2UCW-BpYYbYBT.js} +1 -1
- package/dist/assets/{diagram-PFBEJUXE-CwHasIbs.js → diagram-PFBEJUXE-DKQpHSsC.js} +1 -1
- package/dist/assets/{diagram-RJA3I4VV-D8ttmuE8.js → diagram-RJA3I4VV-DP7Dsnya.js} +1 -1
- package/dist/assets/{diagram-TYUNFSNR-CzC-8RZ0.js → diagram-TYUNFSNR-CfSFewUS.js} +1 -1
- package/dist/assets/{dist-CUJYGnnI.js → dist-CQNjnrld.js} +1 -1
- package/dist/assets/{ebnfDiagram-V4P3TTPT-9AAhU7In.js → ebnfDiagram-V4P3TTPT-DE3MZoJO.js} +1 -1
- package/dist/assets/{elk-QONTBCTQ-BgvnD1v_.js → elk-QONTBCTQ-p_aAQPHU.js} +1 -1
- package/dist/assets/{erDiagram-KYQIGQAA-RLtbiLlP.js → erDiagram-KYQIGQAA-DZO3vQrk.js} +1 -1
- package/dist/assets/{eventmodeling-K75KTNOO-DVY22vNZ.js → eventmodeling-K75KTNOO-BII9EPl5.js} +1 -1
- package/dist/assets/flowDiagram-IUGLLWBU-D9uPno39.js +1 -0
- package/dist/assets/{ganttDiagram-R7GPNTJY-hHiBGa9W.js → ganttDiagram-R7GPNTJY-BKiH3v2r.js} +1 -1
- package/dist/assets/{gitGraph-VSP46ZUC-Cvxay1wh.js → gitGraph-VSP46ZUC-oHL6cr73.js} +1 -1
- package/dist/assets/{gitGraphDiagram-PDYL7XHY-AOhlpSqg.js → gitGraphDiagram-PDYL7XHY-CqPVIEeo.js} +1 -1
- package/dist/assets/{index-CBLNQvK3.js → index-CSpirV16.js} +3 -3
- package/dist/assets/{info-OHQRW6UA-CJ8EUO1m.js → info-OHQRW6UA-Drrbnhpd.js} +1 -1
- package/dist/assets/{infoDiagram-MVDRKAX5-DOQu7oYI.js → infoDiagram-MVDRKAX5-DnQQFMAd.js} +1 -1
- package/dist/assets/{ishikawaDiagram-HPEHV2GE-DcVMvGmx.js → ishikawaDiagram-HPEHV2GE-DIpQlxRh.js} +1 -1
- package/dist/assets/{journeyDiagram-BOSHW7O7-luMeqEqz.js → journeyDiagram-BOSHW7O7-EBR9I5h1.js} +1 -1
- package/dist/assets/{kanban-definition-FNSA4HC5-KTZbmrW2.js → kanban-definition-FNSA4HC5-CdvNsjik.js} +1 -1
- package/dist/assets/{line-CmPHiOVm.js → line-CDLzgxDc.js} +1 -1
- package/dist/assets/{linear-DHYlKXBA.js → linear-DfXKql9u.js} +1 -1
- package/dist/assets/{mermaid-parser.core-BRDI-WhC.js → mermaid-parser.core-DnXGphT2.js} +3 -3
- package/dist/assets/{mermaid.core-DPTRaYLy.js → mermaid.core-h6fiCaSZ.js} +4 -4
- package/dist/assets/{mindmap-definition-6OY57UG3-70EN5Kw_.js → mindmap-definition-6OY57UG3-BiNfio-j.js} +1 -1
- package/dist/assets/{packet-JDAUHWVQ-CXWz83oS.js → packet-JDAUHWVQ-D76byv1S.js} +1 -1
- package/dist/assets/{pegDiagram-ECIUJNBX-z5UXKmp4.js → pegDiagram-ECIUJNBX-Drs8Pxdt.js} +1 -1
- package/dist/assets/{pie-XZMESJXO-rWI1HIQ7.js → pie-XZMESJXO-D_83z1lM.js} +1 -1
- package/dist/assets/{pieDiagram-GNMPY74T-Bh4Kv3NQ.js → pieDiagram-GNMPY74T-wvzIphSf.js} +1 -1
- package/dist/assets/{quadrantDiagram-D57K4MC3-wGM4Ckig.js → quadrantDiagram-D57K4MC3-B7EfMRWb.js} +1 -1
- package/dist/assets/{radar-ABXABTNO-CIJ92p5N.js → radar-ABXABTNO-Bs6dVdAR.js} +1 -1
- package/dist/assets/{railroad-I3PHUGI6-8bxua-Hf.js → railroad-I3PHUGI6-lKaN4miw.js} +1 -1
- package/dist/assets/{railroad-abnf-LNEFI6M7-B9KVI10m.js → railroad-abnf-LNEFI6M7-Bhh16b7Z.js} +1 -1
- package/dist/assets/{railroad-ebnf-DWYD2UWJ-Bt73rnyJ.js → railroad-ebnf-DWYD2UWJ-m6ybW-Y_.js} +1 -1
- package/dist/assets/{railroad-peg-JR7BVNR7-Cie82UPT.js → railroad-peg-JR7BVNR7-DNn72e7r.js} +1 -1
- package/dist/assets/{railroadDiagram-Y72NRBL7-C1nmi_tl.js → railroadDiagram-Y72NRBL7-DNeop1iZ.js} +1 -1
- package/dist/assets/{requirementDiagram-EQYSW5RA-BSianW40.js → requirementDiagram-EQYSW5RA-BVYZ2XYJ.js} +1 -1
- package/dist/assets/{sankeyDiagram-SY24JHO5-DIa11-h7.js → sankeyDiagram-SY24JHO5-B2MP51on.js} +1 -1
- package/dist/assets/{sequenceDiagram-BLEHNHQU-Df5rZKz2.js → sequenceDiagram-BLEHNHQU-BUvxstXG.js} +1 -1
- package/dist/assets/{src-dClg85lX.js → src-BrR0lqlG.js} +1 -1
- package/dist/assets/{stateDiagram-v2-A6IESKWZ-B15PTSvX.js → stateDiagram-v2-A6IESKWZ-Ba84OGKE.js} +1 -1
- package/dist/assets/{swimlanes-B3ENRKFQ-C9mgRCMi.js → swimlanes-B3ENRKFQ-BMSl2PNA.js} +1 -1
- package/dist/assets/swimlanesDiagram-AYQRV4AK-M1dmvq7m.js +8 -0
- package/dist/assets/{timeline-definition-JXYKGLGB-BDVf_mIv.js → timeline-definition-JXYKGLGB-DOdW-dxq.js} +1 -1
- package/dist/assets/{treeView-D4JQ5SDB-BbBK-25J.js → treeView-D4JQ5SDB-DZ9KQyn5.js} +1 -1
- package/dist/assets/{treemap-SAJKECNS-CEDX8apk.js → treemap-SAJKECNS-WEC5wtDV.js} +1 -1
- package/dist/assets/{usecaseDiagram-WPOLBNNX-D1QkwFF8.js → usecaseDiagram-WPOLBNNX-cldTztFL.js} +1 -1
- package/dist/assets/{vennDiagram-Q3Y3IDCW-5PeGZvEO.js → vennDiagram-Q3Y3IDCW-c41KySoY.js} +1 -1
- package/dist/assets/{wardley-7MLQ67FV-BatN94pO.js → wardley-7MLQ67FV-Bf9_aSSZ.js} +1 -1
- package/dist/assets/{wardleyDiagram-IVAIDKWA-DyCcDS3Y.js → wardleyDiagram-IVAIDKWA-BeKEvEBG.js} +1 -1
- package/dist/assets/{xychartDiagram-4URNHDU4-CbCXcRlg.js → xychartDiagram-4URNHDU4-tNkAxTxG.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +5 -1
- package/server/config.js +140 -14
- package/server/consensus.js +18 -13
- package/server/graph/modules_builder.js +290 -0
- package/server/graph/modules_catalog.js +288 -0
- package/server/graph/parser.js +3 -2
- package/server/graph/sources.js +165 -24
- package/server/graph/syntax.js +245 -28
- package/server/index.js +124 -61
- package/server/install.js +350 -10
- package/server/models_registry.js +34 -9
- package/server/scaffold.js +109 -9
- package/dist/assets/channel-BZ2_TTTJ.js +0 -1
- package/dist/assets/flowDiagram-IUGLLWBU-l7L5DTb5.js +0 -1
- package/dist/assets/swimlanesDiagram-AYQRV4AK-BGFq4t7O.js +0 -8
package/server/graph/sources.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path'
|
|
|
3
3
|
import { GraphNode } from './model.js'
|
|
4
4
|
import { edgeKey } from './anchordb_client.js'
|
|
5
5
|
import { SYNTAX_LANGUAGES, extractCode } from './syntax.js'
|
|
6
|
+
import { createPathMatcher } from '../config.js'
|
|
6
7
|
|
|
7
8
|
// Sources graph: the whole project as a dependency graph in AnchorDB, not
|
|
8
9
|
// just the .knowledges markdown. Nodes are the files (id = repo relative
|
|
@@ -47,7 +48,7 @@ const REVERSE_EDGE = {
|
|
|
47
48
|
const DEFAULT_LABEL_DIR = '.knowledges/.knl-data'
|
|
48
49
|
const DEFAULT_IGNORES = ['node_modules', 'dist', 'build', 'coverage']
|
|
49
50
|
|
|
50
|
-
const
|
|
51
|
+
const JS_TS_EXTS = new Set(['.js', '.mjs', '.cjs', '.jsx', '.ts', '.mts', '.cts', '.tsx'])
|
|
51
52
|
|
|
52
53
|
/** One ignore rule after parsing a .gitignore line. */
|
|
53
54
|
function ignoreRule(line) {
|
|
@@ -72,27 +73,42 @@ function fileAttrs(stat) {
|
|
|
72
73
|
* graph: { rel -> { size, mtime } }. Skips dot-prefixed folders except
|
|
73
74
|
* .github, the default ignore list, and basic .gitignore patterns.
|
|
74
75
|
*/
|
|
75
|
-
|
|
76
|
+
function compileIgnorePattern(pattern) {
|
|
77
|
+
if (!pattern || !pattern.includes('*')) return null
|
|
78
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^/]*')
|
|
79
|
+
try {
|
|
80
|
+
return new RegExp('^' + escaped + '$')
|
|
81
|
+
} catch {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function scanSourceFiles(rootAbs, { include = null, exclude = null } = {}) {
|
|
87
|
+
const excludeFilter = createPathMatcher(exclude)
|
|
88
|
+
const includeFilter = createPathMatcher(include)
|
|
89
|
+
|
|
76
90
|
const rules = []
|
|
77
91
|
try {
|
|
78
92
|
const gitignore = await fs.readFile(path.join(rootAbs, '.gitignore'), 'utf8')
|
|
79
93
|
for (const line of gitignore.split('\n')) {
|
|
80
94
|
const rule = ignoreRule(line)
|
|
81
|
-
if (rule)
|
|
95
|
+
if (rule) {
|
|
96
|
+
rule.regex = compileIgnorePattern(rule.pattern)
|
|
97
|
+
rules.push(rule)
|
|
98
|
+
}
|
|
82
99
|
}
|
|
83
100
|
} catch {
|
|
84
101
|
// No .gitignore: the default list still applies.
|
|
85
102
|
}
|
|
86
|
-
rules.push(...DEFAULT_IGNORES.map((pattern) => ({ dirOnly: true, pattern })))
|
|
103
|
+
rules.push(...DEFAULT_IGNORES.map((pattern) => ({ dirOnly: true, pattern, regex: compileIgnorePattern(pattern) })))
|
|
87
104
|
|
|
88
|
-
const
|
|
105
|
+
const isDefaultIgnored = (rel, isDir) => {
|
|
89
106
|
const segments = rel.split('/')
|
|
90
107
|
if (segments.some((s) => s.startsWith('.') && s !== '.github')) return true
|
|
91
108
|
return segments.some((segment) =>
|
|
92
109
|
rules.some((rule) => {
|
|
93
|
-
if (rule.
|
|
94
|
-
|
|
95
|
-
return re.test(segment) || re.test(rel)
|
|
110
|
+
if (rule.regex) {
|
|
111
|
+
return rule.regex.test(segment) || rule.regex.test(rel)
|
|
96
112
|
}
|
|
97
113
|
if (rule.dirOnly) return segments.includes(rule.pattern)
|
|
98
114
|
return segment === rule.pattern || rel === rule.pattern
|
|
@@ -109,15 +125,39 @@ export async function scanSourceFiles(rootAbs) {
|
|
|
109
125
|
return
|
|
110
126
|
}
|
|
111
127
|
for (const entry of entries) {
|
|
112
|
-
if (entry.name.startsWith('.') && entry.name !== '.github') continue
|
|
113
128
|
const childRel = rel ? `${rel}/${entry.name}` : entry.name
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
|
|
130
|
+
// 1. Hard exclude: knl-config exclude is truly excluded, always wins
|
|
131
|
+
if (excludeFilter && excludeFilter.matches(childRel, entry.name)) {
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 2. Explicit include overrides default excludes
|
|
136
|
+
const isExplicitlyIncluded = includeFilter && includeFilter.matches(childRel, entry.name)
|
|
137
|
+
const isDirPrefixOfInclude = entry.isDirectory() && includeFilter && includeFilter.isPrefixOfAny(childRel)
|
|
138
|
+
|
|
116
139
|
if (entry.isDirectory()) {
|
|
117
|
-
|
|
140
|
+
if (!isExplicitlyIncluded && !isDirPrefixOfInclude) {
|
|
141
|
+
if (includeFilter) {
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
144
|
+
if (isDefaultIgnored(childRel, true)) {
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
await walk(path.join(dirAbs, entry.name), childRel)
|
|
118
149
|
continue
|
|
119
150
|
}
|
|
151
|
+
|
|
120
152
|
if (!entry.isFile()) continue
|
|
153
|
+
|
|
154
|
+
if (includeFilter) {
|
|
155
|
+
if (!isExplicitlyIncluded) continue
|
|
156
|
+
} else {
|
|
157
|
+
if (isDefaultIgnored(childRel, false)) continue
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const childAbs = path.join(dirAbs, entry.name)
|
|
121
161
|
try {
|
|
122
162
|
files[childRel] = fileAttrs(await fs.stat(childAbs))
|
|
123
163
|
} catch {
|
|
@@ -175,17 +215,108 @@ export function resolveGoSpecifiers(spec, modulePath, files) {
|
|
|
175
215
|
|
|
176
216
|
/**
|
|
177
217
|
* Resolve one relative specifier from file `fromRel` against the set of
|
|
178
|
-
* scanned files. Tries the exact path,
|
|
179
|
-
*
|
|
218
|
+
* scanned files. Tries the exact path, JS/TS extension fallbacks (including
|
|
219
|
+
* mapping .js specifiers in TS ESM to .ts/.tsx on disk), and directory index files.
|
|
180
220
|
*/
|
|
181
221
|
export function resolveImportSpecifier(fromRel, spec, files) {
|
|
182
222
|
const base = path.posix.normalize(path.posix.join(path.posix.dirname(fromRel), spec))
|
|
183
223
|
const candidates = [base]
|
|
184
|
-
|
|
185
|
-
|
|
224
|
+
if (base.endsWith('.js')) {
|
|
225
|
+
candidates.push(base.slice(0, -3) + '.ts', base.slice(0, -3) + '.tsx')
|
|
226
|
+
} else if (base.endsWith('.mjs')) {
|
|
227
|
+
candidates.push(base.slice(0, -4) + '.mts')
|
|
228
|
+
} else if (base.endsWith('.cjs')) {
|
|
229
|
+
candidates.push(base.slice(0, -4) + '.cts')
|
|
230
|
+
}
|
|
231
|
+
for (const ext of JS_TS_EXTS) candidates.push(base + ext)
|
|
232
|
+
for (const ext of JS_TS_EXTS) candidates.push(`${base}/index${ext}`)
|
|
186
233
|
return candidates.find((candidate) => files[candidate]) || null
|
|
187
234
|
}
|
|
188
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Resolve Python import specifiers: relative dots (e.g. .local, ..utils) and
|
|
238
|
+
* module paths (e.g. pkg.sub) to .py or /__init__.py files in scanned files.
|
|
239
|
+
*/
|
|
240
|
+
export function resolvePySpecifiers(fromRel, specs, files) {
|
|
241
|
+
const dir = path.posix.dirname(fromRel)
|
|
242
|
+
const resolved = []
|
|
243
|
+
for (const spec of specs) {
|
|
244
|
+
const candidates = []
|
|
245
|
+
const m = spec.match(/^(\.+)(.*)$/)
|
|
246
|
+
if (m) {
|
|
247
|
+
const dotCount = m[1].length
|
|
248
|
+
const rest = m[2].replace(/\./g, '/')
|
|
249
|
+
let targetDir = dir
|
|
250
|
+
for (let i = 1; i < dotCount; i++) targetDir = path.posix.dirname(targetDir)
|
|
251
|
+
const base = rest ? path.posix.normalize(path.posix.join(targetDir, rest)) : targetDir
|
|
252
|
+
candidates.push(base + '.py', path.posix.join(base, '__init__.py'))
|
|
253
|
+
} else {
|
|
254
|
+
const asPath = spec.replace(/\./g, '/')
|
|
255
|
+
candidates.push(
|
|
256
|
+
asPath + '.py',
|
|
257
|
+
path.posix.join(asPath, '__init__.py'),
|
|
258
|
+
path.posix.normalize(path.posix.join(dir, asPath)) + '.py',
|
|
259
|
+
path.posix.normalize(path.posix.join(dir, asPath, '__init__.py'))
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
const found = candidates.find((c) => files[c])
|
|
263
|
+
if (found && !resolved.includes(found)) resolved.push(found)
|
|
264
|
+
}
|
|
265
|
+
return resolved
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Resolve Rust import specifiers: `crate::`, `super::`, `self::`, and `./`
|
|
270
|
+
* (from `mod foo;`) to .rs or /mod.rs files in scanned files.
|
|
271
|
+
*/
|
|
272
|
+
export function resolveRustSpecifiers(fromRel, specs, files) {
|
|
273
|
+
const dir = path.posix.dirname(fromRel)
|
|
274
|
+
let crateRoot = '.'
|
|
275
|
+
const srcIdx = fromRel.indexOf('src/')
|
|
276
|
+
if (srcIdx >= 0) {
|
|
277
|
+
crateRoot = fromRel.slice(0, srcIdx + 3)
|
|
278
|
+
}
|
|
279
|
+
const resolved = []
|
|
280
|
+
for (const spec of specs) {
|
|
281
|
+
const candidates = []
|
|
282
|
+
if (spec.startsWith('crate::')) {
|
|
283
|
+
const rest = spec.slice('crate::'.length).split('::').join('/')
|
|
284
|
+
const parts = rest.split('/')
|
|
285
|
+
for (let i = parts.length; i >= 1; i--) {
|
|
286
|
+
const sub = parts.slice(0, i).join('/')
|
|
287
|
+
candidates.push(
|
|
288
|
+
path.posix.normalize(path.posix.join(crateRoot, sub)) + '.rs',
|
|
289
|
+
path.posix.normalize(path.posix.join(crateRoot, sub, 'mod.rs'))
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
} else if (spec.startsWith('super::')) {
|
|
293
|
+
const rest = spec.slice('super::'.length).split('::').join('/')
|
|
294
|
+
const parentDir = path.posix.dirname(dir)
|
|
295
|
+
const parts = rest.split('/')
|
|
296
|
+
for (let i = parts.length; i >= 1; i--) {
|
|
297
|
+
const sub = parts.slice(0, i).join('/')
|
|
298
|
+
candidates.push(
|
|
299
|
+
path.posix.normalize(path.posix.join(parentDir, sub)) + '.rs',
|
|
300
|
+
path.posix.normalize(path.posix.join(parentDir, sub, 'mod.rs'))
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
} else if (spec.startsWith('self::') || spec.startsWith('./')) {
|
|
304
|
+
const raw = spec.startsWith('self::') ? spec.slice('self::'.length).split('::').join('/') : spec.slice(2)
|
|
305
|
+
const parts = raw.split('/')
|
|
306
|
+
for (let i = parts.length; i >= 1; i--) {
|
|
307
|
+
const sub = parts.slice(0, i).join('/')
|
|
308
|
+
candidates.push(
|
|
309
|
+
path.posix.normalize(path.posix.join(dir, sub)) + '.rs',
|
|
310
|
+
path.posix.normalize(path.posix.join(dir, sub, 'mod.rs'))
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const found = candidates.find((c) => files[c])
|
|
315
|
+
if (found && !resolved.includes(found)) resolved.push(found)
|
|
316
|
+
}
|
|
317
|
+
return resolved
|
|
318
|
+
}
|
|
319
|
+
|
|
189
320
|
/**
|
|
190
321
|
* Build the desired sources graph: nodes for every scanned file plus one
|
|
191
322
|
* node per named symbol, and edges from tree-sitter extraction (file-level
|
|
@@ -193,8 +324,8 @@ export function resolveImportSpecifier(fromRel, spec, files) {
|
|
|
193
324
|
* `<path>#<name>`; class methods are `Class.method`. Returns plain arrays
|
|
194
325
|
* so callers can diff without depending on the DirectedGraph type.
|
|
195
326
|
*/
|
|
196
|
-
export async function buildSourcesGraph(rootAbs, files = null) {
|
|
197
|
-
const scanned = files || await scanSourceFiles(rootAbs)
|
|
327
|
+
export async function buildSourcesGraph(rootAbs, files = null, options = {}) {
|
|
328
|
+
const scanned = files || await scanSourceFiles(rootAbs, options)
|
|
198
329
|
const modulePath = await goModulePath(rootAbs)
|
|
199
330
|
const edges = []
|
|
200
331
|
const symbolIndex = new Map() // symbol name -> [node id, ...] for call resolution
|
|
@@ -241,9 +372,16 @@ export async function buildSourcesGraph(rootAbs, files = null) {
|
|
|
241
372
|
}
|
|
242
373
|
|
|
243
374
|
for (const [rel, file] of Object.entries(structure)) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
375
|
+
let targets = []
|
|
376
|
+
if (file.ext === '.go') {
|
|
377
|
+
targets = file.imports.flatMap((spec) => resolveGoSpecifiers(spec, file.modulePath, scanned))
|
|
378
|
+
} else if (file.ext === '.py') {
|
|
379
|
+
targets = resolvePySpecifiers(rel, file.imports, scanned)
|
|
380
|
+
} else if (file.ext === '.rs') {
|
|
381
|
+
targets = resolveRustSpecifiers(rel, file.imports, scanned)
|
|
382
|
+
} else {
|
|
383
|
+
targets = resolveJsSpecifiers(rel, file.imports, scanned)
|
|
384
|
+
}
|
|
247
385
|
for (const target of targets) {
|
|
248
386
|
if (target && target !== rel) edges.push({ from: rel, to: target, type: 'imports' })
|
|
249
387
|
}
|
|
@@ -359,7 +497,10 @@ export function labelContent(rel, symbols) {
|
|
|
359
497
|
* symbol is gone. Generated files only; never touches anything else.
|
|
360
498
|
*/
|
|
361
499
|
export async function writeLabelFiles(rootAbs, labelDir = DEFAULT_LABEL_DIR, symbolsByFile) {
|
|
362
|
-
const base = path.
|
|
500
|
+
const base = path.resolve(rootAbs, labelDir)
|
|
501
|
+
if (base !== rootAbs && !base.startsWith(rootAbs + path.sep)) {
|
|
502
|
+
throw new Error(`labelDir "${labelDir}" resolves outside the project root`)
|
|
503
|
+
}
|
|
363
504
|
const expected = new Map()
|
|
364
505
|
for (const [rel, symbols] of symbolsByFile) expected.set(rel, labelContent(rel, symbols))
|
|
365
506
|
|
|
@@ -423,8 +564,8 @@ export async function writeLabelFiles(rootAbs, labelDir = DEFAULT_LABEL_DIR, sym
|
|
|
423
564
|
* @returns {Promise<{linked, unlinked, saved, tombstoned, failed, orphans,
|
|
424
565
|
* files, symbols, edges, labels}>}
|
|
425
566
|
*/
|
|
426
|
-
export async function syncSourcesToAnchorDB(client, rootAbs, { labelDir } = {}) {
|
|
427
|
-
const { nodes, edges, symbolsByFile } = await buildSourcesGraph(rootAbs)
|
|
567
|
+
export async function syncSourcesToAnchorDB(client, rootAbs, { labelDir, include = null, exclude = null } = {}) {
|
|
568
|
+
const { nodes, edges, symbolsByFile } = await buildSourcesGraph(rootAbs, null, { include, exclude })
|
|
428
569
|
const desired = desiredSourceEdgeKeys(edges)
|
|
429
570
|
const diskIds = new Set(nodes.map((node) => node.id))
|
|
430
571
|
|
package/server/graph/syntax.js
CHANGED
|
@@ -15,15 +15,26 @@ const require = createRequire(import.meta.url)
|
|
|
15
15
|
|
|
16
16
|
const GRAMMAR_WASM = {
|
|
17
17
|
javascript: 'tree-sitter-javascript/tree-sitter-javascript.wasm',
|
|
18
|
-
|
|
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'
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
/** The JS
|
|
25
|
+
/** The JS/TS, Go, Python, and Rust families; each extension maps to its grammar. */
|
|
22
26
|
export const SYNTAX_LANGUAGES = {
|
|
23
27
|
'.js': 'javascript',
|
|
24
28
|
'.mjs': 'javascript',
|
|
25
29
|
'.cjs': 'javascript',
|
|
26
|
-
'.
|
|
30
|
+
'.jsx': 'javascript',
|
|
31
|
+
'.ts': 'typescript',
|
|
32
|
+
'.mts': 'typescript',
|
|
33
|
+
'.cts': 'typescript',
|
|
34
|
+
'.tsx': 'tsx',
|
|
35
|
+
'.go': 'go',
|
|
36
|
+
'.py': 'python',
|
|
37
|
+
'.rs': 'rust'
|
|
27
38
|
}
|
|
28
39
|
|
|
29
40
|
let initPromise = null
|
|
@@ -67,12 +78,14 @@ function firstDescendant(node, type) {
|
|
|
67
78
|
function specsFromTree(grammar, tree) {
|
|
68
79
|
const specs = []
|
|
69
80
|
const names = []
|
|
81
|
+
const bindings = []
|
|
70
82
|
const visit = (node) => {
|
|
71
|
-
if (grammar === 'javascript') {
|
|
83
|
+
if (grammar === 'javascript' || grammar === 'typescript' || grammar === 'tsx') {
|
|
72
84
|
if (node.type === 'import_statement' || node.type === 'export_statement') {
|
|
73
85
|
const source = node.childForFieldName('source')
|
|
74
86
|
const fragment = source && firstDescendant(source, 'string_fragment')
|
|
75
|
-
|
|
87
|
+
const specText = fragment ? fragment.text : null
|
|
88
|
+
if (specText) specs.push(specText)
|
|
76
89
|
// Local binding names a qualified call may be addressed through:
|
|
77
90
|
// the default import, the namespace alias, and each named import.
|
|
78
91
|
for (let i = 0; i < node.childCount; i++) {
|
|
@@ -80,17 +93,25 @@ function specsFromTree(grammar, tree) {
|
|
|
80
93
|
if (clause.type !== 'import_clause') continue
|
|
81
94
|
for (let j = 0; j < clause.childCount; j++) {
|
|
82
95
|
const part = clause.child(j)
|
|
83
|
-
if (part.type === 'identifier')
|
|
84
|
-
|
|
96
|
+
if (part.type === 'identifier') {
|
|
97
|
+
names.push(part.text)
|
|
98
|
+
if (specText) bindings.push({ name: part.text, spec: specText })
|
|
99
|
+
} else if (part.type === 'namespace_import') {
|
|
85
100
|
for (let k = 0; k < part.childCount; k++) {
|
|
86
|
-
if (part.child(k).type === 'identifier')
|
|
101
|
+
if (part.child(k).type === 'identifier') {
|
|
102
|
+
names.push(part.child(k).text)
|
|
103
|
+
if (specText) bindings.push({ name: part.child(k).text, spec: specText })
|
|
104
|
+
}
|
|
87
105
|
}
|
|
88
106
|
} else if (part.type === 'named_imports') {
|
|
89
107
|
for (let k = 0; k < part.childCount; k++) {
|
|
90
108
|
const specifier = part.child(k)
|
|
91
109
|
if (specifier.type !== 'import_specifier') continue
|
|
92
110
|
const alias = specifier.childForFieldName('alias') || specifier.childForFieldName('name')
|
|
93
|
-
if (alias)
|
|
111
|
+
if (alias) {
|
|
112
|
+
names.push(alias.text)
|
|
113
|
+
if (specText) bindings.push({ name: alias.text, spec: specText })
|
|
114
|
+
}
|
|
94
115
|
}
|
|
95
116
|
}
|
|
96
117
|
}
|
|
@@ -100,7 +121,8 @@ function specsFromTree(grammar, tree) {
|
|
|
100
121
|
if (fn && (fn.text === 'require' || fn.text === 'import')) {
|
|
101
122
|
const args = node.childForFieldName('arguments')
|
|
102
123
|
const fragment = args && firstDescendant(args, 'string_fragment')
|
|
103
|
-
|
|
124
|
+
const specText = fragment ? fragment.text : null
|
|
125
|
+
if (specText) specs.push(specText)
|
|
104
126
|
// `const x = require('y')` / `const x = await import('y')`: the
|
|
105
127
|
// declarator name is the binding: the visit starts above it, so
|
|
106
128
|
// walk down from any enclosing declarator via field lookup here.
|
|
@@ -108,7 +130,10 @@ function specsFromTree(grammar, tree) {
|
|
|
108
130
|
while (parent && parent.type === 'await_expression') parent = parent.parent
|
|
109
131
|
if (parent && parent.type === 'variable_declarator') {
|
|
110
132
|
const name = parent.childForFieldName('name')
|
|
111
|
-
if (name)
|
|
133
|
+
if (name) {
|
|
134
|
+
names.push(name.text)
|
|
135
|
+
if (specText) bindings.push({ name: name.text, spec: specText })
|
|
136
|
+
}
|
|
112
137
|
}
|
|
113
138
|
}
|
|
114
139
|
}
|
|
@@ -122,15 +147,119 @@ function specsFromTree(grammar, tree) {
|
|
|
122
147
|
// The name a qualified call uses: the declared alias, or the last
|
|
123
148
|
// path segment (Go's convention when no alias is given).
|
|
124
149
|
const alias = node.childForFieldName('name')
|
|
125
|
-
|
|
126
|
-
|
|
150
|
+
const name = alias ? alias.text : path.slice(path.lastIndexOf('/') + 1)
|
|
151
|
+
names.push(name)
|
|
152
|
+
bindings.push({ name, spec: path })
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} else if (grammar === 'python') {
|
|
156
|
+
if (node.type === 'import_statement') {
|
|
157
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
158
|
+
const c = node.child(i)
|
|
159
|
+
if (c.type === 'dotted_name') {
|
|
160
|
+
specs.push(c.text)
|
|
161
|
+
const firstId = c.child(0)
|
|
162
|
+
if (firstId) {
|
|
163
|
+
names.push(firstId.text)
|
|
164
|
+
bindings.push({ name: firstId.text, spec: c.text })
|
|
165
|
+
}
|
|
166
|
+
} else if (c.type === 'aliased_import') {
|
|
167
|
+
const name = c.childForFieldName('name')
|
|
168
|
+
const alias = c.childForFieldName('alias')
|
|
169
|
+
if (name) specs.push(name.text)
|
|
170
|
+
if (alias) {
|
|
171
|
+
names.push(alias.text)
|
|
172
|
+
bindings.push({ name: alias.text, spec: name ? name.text : alias.text })
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} else if (node.type === 'import_from_statement') {
|
|
177
|
+
const mod = node.childForFieldName('module_name')
|
|
178
|
+
const modText = mod ? mod.text : ''
|
|
179
|
+
if (modText) specs.push(modText)
|
|
180
|
+
let pastImport = false
|
|
181
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
182
|
+
const c = node.child(i)
|
|
183
|
+
if (c.type === 'import') {
|
|
184
|
+
pastImport = true
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
if (!pastImport) continue
|
|
188
|
+
if (c.type === 'dotted_name') {
|
|
189
|
+
names.push(c.text)
|
|
190
|
+
if (modText) specs.push(modText + '.' + c.text)
|
|
191
|
+
bindings.push({ name: c.text, spec: modText || c.text })
|
|
192
|
+
} else if (c.type === 'aliased_import') {
|
|
193
|
+
const name = c.childForFieldName('name')
|
|
194
|
+
const alias = c.childForFieldName('alias')
|
|
195
|
+
if (alias) {
|
|
196
|
+
names.push(alias.text)
|
|
197
|
+
bindings.push({ name: alias.text, spec: modText || (name ? name.text : alias.text) })
|
|
198
|
+
}
|
|
199
|
+
if (name && modText) specs.push(modText + '.' + name.text)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} else if (grammar === 'rust') {
|
|
204
|
+
if (node.type === 'mod_item') {
|
|
205
|
+
if (!node.childForFieldName('body')) {
|
|
206
|
+
const name = node.childForFieldName('name')
|
|
207
|
+
if (name) {
|
|
208
|
+
specs.push('./' + name.text)
|
|
209
|
+
names.push(name.text)
|
|
210
|
+
bindings.push({ name: name.text, spec: './' + name.text })
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} else if (node.type === 'use_declaration') {
|
|
214
|
+
const extractRustUse = (n, prefix = '') => {
|
|
215
|
+
if (!n) return
|
|
216
|
+
if (n.type === 'scoped_use_list') {
|
|
217
|
+
const pathNode = n.childForFieldName('path')
|
|
218
|
+
const listNode = n.childForFieldName('list')
|
|
219
|
+
const p = (prefix ? prefix + '::' : '') + (pathNode ? pathNode.text : '')
|
|
220
|
+
extractRustUse(listNode, p)
|
|
221
|
+
} else if (n.type === 'use_list') {
|
|
222
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
223
|
+
const c = n.child(i)
|
|
224
|
+
if (c.type === 'identifier' || c.type === 'scoped_identifier' || c.type === 'use_as_clause' || c.type === 'scoped_use_list') {
|
|
225
|
+
extractRustUse(c, prefix)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} else if (n.type === 'use_as_clause') {
|
|
229
|
+
const pathNode = n.childForFieldName('path')
|
|
230
|
+
const aliasNode = n.childForFieldName('alias')
|
|
231
|
+
const full = (prefix ? prefix + '::' : '') + (pathNode ? pathNode.text : '')
|
|
232
|
+
specs.push(full)
|
|
233
|
+
const alias = aliasNode ? aliasNode.text : null
|
|
234
|
+
if (alias) {
|
|
235
|
+
names.push(alias)
|
|
236
|
+
const crateName = full.split('::')[0]
|
|
237
|
+
bindings.push({ name: alias, spec: crateName, full })
|
|
238
|
+
}
|
|
239
|
+
} else if (n.type === 'scoped_identifier') {
|
|
240
|
+
const full = (prefix ? prefix + '::' : '') + n.text
|
|
241
|
+
specs.push(full)
|
|
242
|
+
const name = n.childForFieldName('name')
|
|
243
|
+
if (name) {
|
|
244
|
+
names.push(name.text)
|
|
245
|
+
const crateName = full.split('::')[0]
|
|
246
|
+
bindings.push({ name: name.text, spec: crateName, full })
|
|
247
|
+
}
|
|
248
|
+
} else if (n.type === 'identifier') {
|
|
249
|
+
const full = (prefix ? prefix + '::' : '') + n.text
|
|
250
|
+
specs.push(full)
|
|
251
|
+
names.push(n.text)
|
|
252
|
+
const crateName = full.split('::')[0]
|
|
253
|
+
bindings.push({ name: n.text, spec: crateName, full })
|
|
254
|
+
}
|
|
127
255
|
}
|
|
256
|
+
extractRustUse(node.childForFieldName('argument'))
|
|
128
257
|
}
|
|
129
258
|
}
|
|
130
259
|
for (let i = 0; i < node.childCount; i++) visit(node.child(i))
|
|
131
260
|
}
|
|
132
261
|
visit(tree.rootNode)
|
|
133
|
-
return { specs, names }
|
|
262
|
+
return { specs, names, bindings }
|
|
134
263
|
}
|
|
135
264
|
|
|
136
265
|
/**
|
|
@@ -170,9 +299,9 @@ export async function extractCode(ext, content) {
|
|
|
170
299
|
const parser = parserFor(grammar, lang)
|
|
171
300
|
const tree = parser.parse(content)
|
|
172
301
|
try {
|
|
173
|
-
const { specs: imports, names: importNames } = specsFromTree(grammar, tree)
|
|
302
|
+
const { specs: imports, names: importNames, bindings: importBindings } = specsFromTree(grammar, tree)
|
|
174
303
|
const { symbols, calls } = structureFromTree(grammar, tree)
|
|
175
|
-
return { imports, importNames, symbols, calls }
|
|
304
|
+
return { imports, importNames, importBindings, symbols, calls }
|
|
176
305
|
} finally {
|
|
177
306
|
tree.delete()
|
|
178
307
|
}
|
|
@@ -195,7 +324,7 @@ function structureFromTree(grammar, tree) {
|
|
|
195
324
|
})
|
|
196
325
|
}
|
|
197
326
|
const visit = (node, cls) => {
|
|
198
|
-
if (grammar === 'javascript') {
|
|
327
|
+
if (grammar === 'javascript' || grammar === 'typescript' || grammar === 'tsx') {
|
|
199
328
|
if (node.type === 'function_declaration' || node.type === 'generator_function_declaration') {
|
|
200
329
|
const name = node.childForFieldName('name')
|
|
201
330
|
if (name) pushSymbol(node, name.text, 'function')
|
|
@@ -220,6 +349,15 @@ function structureFromTree(grammar, tree) {
|
|
|
220
349
|
pushSymbol(declarator, name.text, 'function')
|
|
221
350
|
}
|
|
222
351
|
}
|
|
352
|
+
} else if (node.type === 'interface_declaration') {
|
|
353
|
+
const name = node.childForFieldName('name')
|
|
354
|
+
if (name) pushSymbol(node, name.text, 'interface')
|
|
355
|
+
} else if (node.type === 'type_alias_declaration') {
|
|
356
|
+
const name = node.childForFieldName('name')
|
|
357
|
+
if (name) pushSymbol(node, name.text, 'type')
|
|
358
|
+
} else if (node.type === 'enum_declaration') {
|
|
359
|
+
const name = node.childForFieldName('name')
|
|
360
|
+
if (name) pushSymbol(node, name.text, 'enum')
|
|
223
361
|
}
|
|
224
362
|
} else if (grammar === 'go') {
|
|
225
363
|
if (node.type === 'function_declaration') {
|
|
@@ -234,18 +372,97 @@ function structureFromTree(grammar, tree) {
|
|
|
234
372
|
const name = node.childForFieldName('name')
|
|
235
373
|
if (name) pushSymbol(node, name.text, 'type')
|
|
236
374
|
}
|
|
375
|
+
} else if (grammar === 'python') {
|
|
376
|
+
if (node.type === 'class_definition') {
|
|
377
|
+
const name = node.childForFieldName('name')
|
|
378
|
+
if (name) {
|
|
379
|
+
pushSymbol(node, name.text, 'class')
|
|
380
|
+
cls = name.text
|
|
381
|
+
}
|
|
382
|
+
} else if (node.type === 'function_definition') {
|
|
383
|
+
const name = node.childForFieldName('name')
|
|
384
|
+
if (name) {
|
|
385
|
+
if (cls) pushSymbol(node, `${cls}.${name.text}`, 'method')
|
|
386
|
+
else pushSymbol(node, name.text, 'function')
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
} else if (grammar === 'rust') {
|
|
390
|
+
if (node.type === 'struct_item') {
|
|
391
|
+
const name = node.childForFieldName('name')
|
|
392
|
+
if (name) pushSymbol(node, name.text, 'struct')
|
|
393
|
+
} else if (node.type === 'enum_item') {
|
|
394
|
+
const name = node.childForFieldName('name')
|
|
395
|
+
if (name) pushSymbol(node, name.text, 'enum')
|
|
396
|
+
} else if (node.type === 'trait_item') {
|
|
397
|
+
const name = node.childForFieldName('name')
|
|
398
|
+
if (name) pushSymbol(node, name.text, 'trait')
|
|
399
|
+
} else if (node.type === 'type_item') {
|
|
400
|
+
const name = node.childForFieldName('name')
|
|
401
|
+
if (name) pushSymbol(node, name.text, 'type')
|
|
402
|
+
} else if (node.type === 'impl_item') {
|
|
403
|
+
const typeNode = node.childForFieldName('type')
|
|
404
|
+
if (typeNode) cls = typeNode.text
|
|
405
|
+
} else if (node.type === 'function_item') {
|
|
406
|
+
const name = node.childForFieldName('name')
|
|
407
|
+
if (name) {
|
|
408
|
+
if (cls) pushSymbol(node, `${cls}.${name.text}`, 'method')
|
|
409
|
+
else pushSymbol(node, name.text, 'function')
|
|
410
|
+
}
|
|
411
|
+
}
|
|
237
412
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
413
|
+
|
|
414
|
+
if (grammar === 'python') {
|
|
415
|
+
if (node.type === 'call') {
|
|
416
|
+
const fn = node.childForFieldName('function')
|
|
417
|
+
if (fn) {
|
|
418
|
+
if (fn.type === 'identifier') {
|
|
419
|
+
calls.push({ name: fn.text, recv: null, row: node.startPosition.row })
|
|
420
|
+
} else if (fn.type === 'attribute') {
|
|
421
|
+
const obj = fn.childForFieldName('object')
|
|
422
|
+
const attr = fn.childForFieldName('attribute')
|
|
423
|
+
if (attr) {
|
|
424
|
+
calls.push({ name: attr.text, recv: obj ? obj.text : null, row: node.startPosition.row })
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
} else if (grammar === 'rust') {
|
|
430
|
+
if (node.type === 'call_expression') {
|
|
431
|
+
const fn = node.childForFieldName('function')
|
|
432
|
+
if (fn) {
|
|
433
|
+
if (fn.type === 'identifier') {
|
|
434
|
+
calls.push({ name: fn.text, recv: null, row: node.startPosition.row })
|
|
435
|
+
} else if (fn.type === 'field_expression') {
|
|
436
|
+
const val = fn.childForFieldName('value')
|
|
437
|
+
const field = fn.childForFieldName('field')
|
|
438
|
+
if (field) {
|
|
439
|
+
calls.push({ name: field.text, recv: val ? val.text : null, row: node.startPosition.row })
|
|
440
|
+
}
|
|
441
|
+
} else if (fn.type === 'scoped_identifier') {
|
|
442
|
+
const pathNode = fn.childForFieldName('path')
|
|
443
|
+
const nameNode = fn.childForFieldName('name')
|
|
444
|
+
if (nameNode) {
|
|
445
|
+
const recv = pathNode
|
|
446
|
+
? (pathNode.type === 'scoped_identifier' ? (pathNode.childForFieldName('name') || pathNode).text : pathNode.text)
|
|
447
|
+
: null
|
|
448
|
+
calls.push({ name: nameNode.text, recv, row: node.startPosition.row })
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
if (node.type === 'call_expression' || node.type === 'new_expression') {
|
|
455
|
+
const fn = node.type === 'call_expression'
|
|
456
|
+
? node.childForFieldName('function')
|
|
457
|
+
: node.childForFieldName('constructor')
|
|
458
|
+
if (fn) {
|
|
459
|
+
if (fn.type === 'identifier') {
|
|
460
|
+
calls.push({ name: fn.text, recv: null, row: node.startPosition.row })
|
|
461
|
+
} else if (fn.type === 'member_expression' || fn.type === 'selector_expression') {
|
|
462
|
+
const named = fn.namedChildren
|
|
463
|
+
if (named.length >= 2) {
|
|
464
|
+
calls.push({ name: named[named.length - 1].text, recv: named[0].text, row: node.startPosition.row })
|
|
465
|
+
}
|
|
249
466
|
}
|
|
250
467
|
}
|
|
251
468
|
}
|