graph-indexer 2.1.1 → 3.0.1

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 (131) hide show
  1. package/CHANGELOG.md +312 -1
  2. package/README.md +236 -312
  3. package/SECURITY.md +53 -58
  4. package/bin/graph-indexer.mjs +251 -0
  5. package/integrations/claude-code/.claude-plugin/plugin.json +9 -0
  6. package/integrations/claude-code/.mcp.json +8 -0
  7. package/integrations/claude-code/agents/code-structure.md +22 -0
  8. package/integrations/claude-code/hooks/gi-hook.mjs +103 -0
  9. package/integrations/claude-code/hooks/hooks.json +16 -0
  10. package/integrations/opencode/graph-indexer.js +129 -0
  11. package/package.json +32 -109
  12. package/src/cli/helper.mjs +67 -0
  13. package/src/cli/hook.mjs +366 -0
  14. package/src/cli/init.mjs +272 -0
  15. package/src/cli/resident.mjs +185 -0
  16. package/src/index/discover.mjs +128 -0
  17. package/src/index/fingerprint.mjs +30 -0
  18. package/src/index/graph.mjs +61 -0
  19. package/src/index/indexer.mjs +476 -0
  20. package/src/index/modules.mjs +332 -0
  21. package/src/index/resolver.mjs +961 -0
  22. package/src/mcp/render.mjs +60 -0
  23. package/src/mcp/server.mjs +147 -0
  24. package/src/mcp/tools.mjs +974 -0
  25. package/src/parse/arity.mjs +153 -0
  26. package/src/parse/extract.mjs +1038 -0
  27. package/src/parse/lang/csharp.mjs +106 -0
  28. package/src/parse/lang/ecma.mjs +438 -0
  29. package/src/parse/lang/go.mjs +194 -0
  30. package/src/parse/lang/jvm.mjs +118 -0
  31. package/src/parse/lang/kotlin.mjs +97 -0
  32. package/src/parse/lang/misc.mjs +115 -0
  33. package/src/parse/lang/native.mjs +126 -0
  34. package/src/parse/lang/python.mjs +153 -0
  35. package/src/parse/lang/rust.mjs +157 -0
  36. package/src/parse/lang/scripting.mjs +219 -0
  37. package/src/parse/languages.mjs +48 -0
  38. package/src/parse/runtime.mjs +56 -0
  39. package/src/query/check.mjs +345 -0
  40. package/src/query/facts.mjs +323 -0
  41. package/src/query/intel.mjs +683 -0
  42. package/src/query/read.mjs +81 -0
  43. package/src/query/testcmd.mjs +130 -0
  44. package/src/query/textsearch.mjs +178 -0
  45. package/src/search/search.mjs +210 -0
  46. package/src/search/tokenize.mjs +188 -0
  47. package/src/store/db.mjs +169 -0
  48. package/src/util/git.mjs +30 -0
  49. package/src/util/paths.mjs +63 -0
  50. package/src/util/regex.mjs +2 -0
  51. package/vendor/grammars/bash.wasm.gz +0 -0
  52. package/vendor/grammars/c.wasm.gz +0 -0
  53. package/vendor/grammars/c_sharp.wasm.gz +0 -0
  54. package/vendor/grammars/cpp.wasm.gz +0 -0
  55. package/vendor/grammars/css.wasm.gz +0 -0
  56. package/vendor/grammars/go.wasm.gz +0 -0
  57. package/vendor/grammars/java.wasm.gz +0 -0
  58. package/vendor/grammars/javascript.wasm.gz +0 -0
  59. package/vendor/grammars/kotlin.wasm.gz +0 -0
  60. package/vendor/grammars/manifest.json +148 -0
  61. package/vendor/grammars/php.wasm.gz +0 -0
  62. package/vendor/grammars/python.wasm.gz +0 -0
  63. package/vendor/grammars/ruby.wasm.gz +0 -0
  64. package/vendor/grammars/rust.wasm.gz +0 -0
  65. package/vendor/grammars/scala.wasm.gz +0 -0
  66. package/vendor/grammars/tsx.wasm.gz +0 -0
  67. package/vendor/grammars/typescript.wasm.gz +0 -0
  68. package/vendor/web-tree-sitter/web-tree-sitter.js +3980 -0
  69. package/vendor/web-tree-sitter/web-tree-sitter.wasm +0 -0
  70. package/cli-ui.mjs +0 -78
  71. package/colbert.mjs +0 -168
  72. package/config.mjs +0 -494
  73. package/daemon-ctl.mjs +0 -175
  74. package/daemon-lock.mjs +0 -69
  75. package/embedders/python/mlx_embed_server.py +0 -61
  76. package/embedders/python/requirements-mlx.txt +0 -5
  77. package/embedders/setup-mlx.mjs +0 -110
  78. package/embeddings.mjs +0 -571
  79. package/engine/binary.mjs +0 -437
  80. package/engine/memory.mjs +0 -1003
  81. package/engine/sqlite.mjs +0 -1136
  82. package/enrichment.mjs +0 -521
  83. package/git-signals.mjs +0 -248
  84. package/indexer.mjs +0 -546
  85. package/init.mjs +0 -1809
  86. package/layout.mjs +0 -123
  87. package/mcp/centrality.mjs +0 -117
  88. package/mcp/format.mjs +0 -61
  89. package/mcp/resolver.mjs +0 -116
  90. package/mcp/symbolgraph.mjs +0 -137
  91. package/mcp/taint.mjs +0 -225
  92. package/mcp/tools.mjs +0 -1582
  93. package/mcp/topology.mjs +0 -676
  94. package/mcp-server.mjs +0 -197
  95. package/parse/extractor.mjs +0 -628
  96. package/parse/imports.mjs +0 -365
  97. package/parse/interprocedural.mjs +0 -134
  98. package/parse/languages.mjs +0 -253
  99. package/parse/metadata.mjs +0 -701
  100. package/parse/routes.mjs +0 -327
  101. package/parse/scip.mjs +0 -391
  102. package/parse/taint-patterns.mjs +0 -119
  103. package/prompts/CORE.md +0 -91
  104. package/prompts/DOMAIN_TEMPLATE.md +0 -46
  105. package/prompts/INTEGRATION.md +0 -123
  106. package/prompts/frameworks/ANDROID.md +0 -17
  107. package/prompts/frameworks/ASPNET_CORE.md +0 -19
  108. package/prompts/frameworks/FASTAPI_DJANGO.md +0 -20
  109. package/prompts/frameworks/LARAVEL_SYMFONY.md +0 -22
  110. package/prompts/frameworks/NODE_EXPRESS_NESTJS.md +0 -19
  111. package/prompts/frameworks/RAILS.md +0 -20
  112. package/prompts/frameworks/REACT.md +0 -20
  113. package/prompts/frameworks/SPRING_BOOT.md +0 -20
  114. package/prompts/languages/BASH.md +0 -22
  115. package/prompts/languages/C.md +0 -21
  116. package/prompts/languages/CSHARP.md +0 -24
  117. package/prompts/languages/CSS_SCSS.md +0 -22
  118. package/prompts/languages/GO.md +0 -24
  119. package/prompts/languages/JAVA.md +0 -23
  120. package/prompts/languages/JAVASCRIPT_TYPESCRIPT.md +0 -24
  121. package/prompts/languages/KOTLIN.md +0 -24
  122. package/prompts/languages/PHP.md +0 -23
  123. package/prompts/languages/PYTHON.md +0 -23
  124. package/prompts/languages/RUBY.md +0 -23
  125. package/prompts/languages/RUST.md +0 -21
  126. package/prompts/languages/SWIFT.md +0 -21
  127. package/seal.mjs +0 -400
  128. package/search-core.mjs +0 -777
  129. package/search-sparse.mjs +0 -183
  130. package/storage.mjs +0 -73
  131. package/watch-daemon.mjs +0 -289
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Module resolution: turns an import's source string into repository files (or a package
3
+ * directory). Everything is computed from the file list plus a few manifest files
4
+ * (package.json workspaces, tsconfig paths, go.mod) — no build, no network.
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { readRepoFile } from '../util/paths.mjs';
9
+
10
+ const ECMA_EXTS = ['.ts', '.tsx', '.d.ts', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts'];
11
+
12
+ function readJson(root, rel) {
13
+ try {
14
+ const text = readRepoFile(root, rel).replace(/\/\*[\s\S]*?\*\/|^\s*\/\/.*$/gm, '').replace(/,(\s*[}\]])/g, '$1');
15
+ return JSON.parse(text);
16
+ } catch { return null; }
17
+ }
18
+
19
+ export class ModuleResolver {
20
+ constructor(root, filePaths) {
21
+ this.root = root;
22
+ this.setFiles(filePaths);
23
+ }
24
+
25
+ setFiles(filePaths) {
26
+ this.files = new Set(filePaths);
27
+ this.bySuffix = null; // lazy
28
+ this.dirs = new Map(); // dir -> [paths]
29
+ for (const f of filePaths) {
30
+ const d = path.posix.dirname(f);
31
+ (this.dirs.get(d) ?? this.dirs.set(d, []).get(d)).push(f);
32
+ }
33
+ this.#loadManifests();
34
+ }
35
+
36
+ addFile(f) {
37
+ if (this.files.has(f)) return;
38
+ this.files.add(f);
39
+ const d = path.posix.dirname(f);
40
+ (this.dirs.get(d) ?? this.dirs.set(d, []).get(d)).push(f);
41
+ this.bySuffix = null;
42
+ }
43
+
44
+ removeFile(f) {
45
+ if (!this.files.delete(f)) return;
46
+ const d = path.posix.dirname(f);
47
+ const list = this.dirs.get(d);
48
+ if (list) { const i = list.indexOf(f); if (i >= 0) list.splice(i, 1); }
49
+ this.bySuffix = null;
50
+ }
51
+
52
+ #loadManifests() {
53
+ // JS/TS workspace packages: package name -> directory
54
+ this.packages = new Map();
55
+ const pkgJsons = [...this.files].length ? this.#findManifests('package.json') : [];
56
+ for (const rel of pkgJsons) {
57
+ const j = readJson(this.root, rel);
58
+ if (j?.name) this.packages.set(j.name, { dir: path.posix.dirname(rel), json: j });
59
+ }
60
+ // tsconfig paths and baseUrl, per project: a file resolves through the nearest tsconfig.json
61
+ // above it (in a monorepo each package maps its own aliases, often the same `@/*` to a
62
+ // different directory), then through the root's
63
+ this.tsScopes = new Map();
64
+ const rootCfg = fs.existsSync(path.join(this.root, 'tsconfig.json')) ? 'tsconfig.json' : 'tsconfig.base.json';
65
+ const rootScope = this.#tsScope(rootCfg);
66
+ if (rootScope) this.tsScopes.set('.', rootScope);
67
+ for (const rel of this.#findManifests('tsconfig.json')) {
68
+ const dir = path.posix.dirname(rel);
69
+ if (dir === '.') continue;
70
+ const scope = this.#tsScope(rel);
71
+ if (scope) this.tsScopes.set(dir, scope);
72
+ }
73
+ // Go module path
74
+ this.goModules = [];
75
+ for (const rel of this.#findManifests('go.mod')) {
76
+ try {
77
+ const m = /^module\s+(\S+)/m.exec(readRepoFile(this.root, rel) ?? '');
78
+ if (m) this.goModules.push({ module: m[1], dir: path.posix.dirname(rel) });
79
+ } catch { /* unreadable */ }
80
+ }
81
+ this.goModules.sort((a, b) => b.module.length - a.module.length);
82
+ }
83
+
84
+ /** compilerOptions of a tsconfig file, following relative `extends` (a child's paths replace its parent's). */
85
+ #tsOptions(rel, depth = 0) {
86
+ const j = readJson(this.root, rel);
87
+ if (!j || depth > 5) return null;
88
+ const dir = path.posix.dirname(rel);
89
+ let out = { paths: null, pathsDir: null, baseUrl: null };
90
+ for (const ext of [j.extends ?? []].flat()) {
91
+ if (typeof ext !== 'string' || !ext.startsWith('.')) continue;
92
+ const parent = this.#tsOptions(path.posix.normalize(path.posix.join(dir, ext.endsWith('.json') ? ext : ext + '.json')), depth + 1);
93
+ if (parent) out = { paths: parent.paths ?? out.paths, pathsDir: parent.paths ? parent.pathsDir : out.pathsDir, baseUrl: parent.baseUrl ?? out.baseUrl };
94
+ }
95
+ const co = j.compilerOptions ?? {};
96
+ if (co.baseUrl != null) out.baseUrl = path.posix.normalize(path.posix.join(dir, co.baseUrl));
97
+ if (co.paths) { out.paths = co.paths; out.pathsDir = dir; }
98
+ return out;
99
+ }
100
+
101
+ /** The aliases a tsconfig (or, when it has none, the app/lib configs next to it) declares. */
102
+ #tsScope(rel) {
103
+ let o = this.#tsOptions(rel);
104
+ const dir = path.posix.dirname(rel);
105
+ if (o && !o.paths && !o.baseUrl) {
106
+ for (const alt of ['tsconfig.app.json', 'tsconfig.lib.json', 'tsconfig.build.json']) {
107
+ const a = this.#tsOptions(dir === '.' ? alt : `${dir}/${alt}`);
108
+ if (a?.paths || a?.baseUrl) { o = a; break; }
109
+ }
110
+ }
111
+ if (!o || (!o.paths && !o.baseUrl)) return null;
112
+ // targets are relative to baseUrl, or to the config that declares them when there is none
113
+ const base = o.baseUrl ?? o.pathsDir;
114
+ const paths = Object.entries(o.paths ?? {}).map(([pat, targets]) => ({ pat, targets: [targets].flat().map(t => path.posix.normalize(path.posix.join(base, t))) }));
115
+ return { paths, baseUrl: o.baseUrl };
116
+ }
117
+
118
+ #tsScopesFor(fromDir) {
119
+ const out = [];
120
+ for (let d = fromDir; ; d = path.posix.dirname(d)) {
121
+ const s = this.tsScopes.get(d);
122
+ if (s && d !== '.') { out.push(s); break; }
123
+ if (d === '.' || d === '/' || !d) break;
124
+ }
125
+ const root = this.tsScopes.get('.');
126
+ if (root) out.push(root);
127
+ return out;
128
+ }
129
+
130
+ #findManifests(name) {
131
+ // manifests are not source files, so look for them next to source directories (bounded)
132
+ const out = [];
133
+ const seen = new Set();
134
+ const consider = (dir) => {
135
+ if (seen.has(dir)) return; seen.add(dir);
136
+ const rel = dir === '.' ? name : `${dir}/${name}`;
137
+ if (fs.existsSync(path.join(this.root, rel))) out.push(rel);
138
+ };
139
+ consider('.');
140
+ for (const d of this.dirs.keys()) {
141
+ let cur = d;
142
+ for (let i = 0; i < 6 && cur && cur !== '.'; i++) {
143
+ if (/(^|\/)node_modules(\/|$)/.test(cur)) break;
144
+ consider(cur);
145
+ cur = path.posix.dirname(cur);
146
+ }
147
+ }
148
+ return out;
149
+ }
150
+
151
+ #suffixIndex() {
152
+ if (!this.bySuffix) {
153
+ this.bySuffix = new Map();
154
+ for (const f of this.files) {
155
+ const parts = f.split('/');
156
+ for (let i = Math.max(0, parts.length - 6); i < parts.length; i++) {
157
+ const suf = parts.slice(i).join('/');
158
+ (this.bySuffix.get(suf) ?? this.bySuffix.set(suf, []).get(suf)).push(f);
159
+ }
160
+ }
161
+ }
162
+ return this.bySuffix;
163
+ }
164
+
165
+ #ecmaFile(base) {
166
+ if (this.files.has(base)) return base;
167
+ const noJs = base.replace(/\.(m|c)?js$/, '');
168
+ for (const ext of ECMA_EXTS) if (this.files.has(noJs + ext)) return noJs + ext;
169
+ for (const ext of ECMA_EXTS) if (this.files.has(`${noJs}/index${ext}`)) return `${noJs}/index${ext}`;
170
+ return null;
171
+ }
172
+
173
+ /**
174
+ * Resolve an import from `fromFile`.
175
+ * @returns {{ file?: string, dir?: string } | null} null → external dependency
176
+ */
177
+ resolve(lang, fromFile, source) {
178
+ if (!source) return null;
179
+ const fromDir = path.posix.dirname(fromFile);
180
+ switch (lang) {
181
+ case 'javascript': case 'typescript': case 'tsx': return this.#resolveEcma(fromDir, source);
182
+ case 'python': return this.#resolvePython(fromDir, source);
183
+ case 'go': return this.#resolveGo(source);
184
+ case 'c': case 'cpp': return this.#resolveInclude(fromDir, source);
185
+ case 'ruby': case 'bash': case 'css': return this.#resolveRelativeOrSuffix(fromDir, source, lang);
186
+ case 'rust': return this.#resolveRust(fromFile, source);
187
+ default: return null; // package-based languages resolve through the package index
188
+ }
189
+ }
190
+
191
+ #resolveEcma(fromDir, source) {
192
+ if (source.startsWith('.')) {
193
+ const f = this.#ecmaFile(path.posix.normalize(path.posix.join(fromDir, source)));
194
+ return f ? { file: f } : null;
195
+ }
196
+ if (source.startsWith('/')) return null;
197
+ const scopes = this.#tsScopesFor(fromDir);
198
+ for (const { pat, targets } of scopes.flatMap(s => s.paths)) {
199
+ const star = pat.indexOf('*');
200
+ if (star < 0 ? pat === source : (source.startsWith(pat.slice(0, star)) && source.endsWith(pat.slice(star + 1)))) {
201
+ const mid = star < 0 ? '' : source.slice(star, source.length - (pat.length - star - 1));
202
+ for (const t of targets) {
203
+ const f = this.#ecmaFile(t.replace('*', mid));
204
+ if (f) return { file: f };
205
+ }
206
+ }
207
+ }
208
+ // workspace package (monorepo): '@scope/pkg' or '@scope/pkg/sub/path'
209
+ const parts = source.split('/');
210
+ const pkgName = source.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
211
+ const sub = source.startsWith('@') ? parts.slice(2).join('/') : parts.slice(1).join('/');
212
+ const pkg = this.packages.get(pkgName);
213
+ if (pkg) {
214
+ const base = pkg.dir === '.' ? '' : pkg.dir + '/';
215
+ if (sub) {
216
+ const f = this.#ecmaFile(base + sub) ?? this.#ecmaFile(base + 'src/' + sub) ?? this.#ecmaFile(base + 'lib/' + sub);
217
+ if (f) return { file: f };
218
+ }
219
+ const j = pkg.json;
220
+ const entries = [j.source, j.types, j.typings, j.module, j.main, 'index', 'src/index', 'lib/index'].filter(Boolean);
221
+ for (const e of entries) {
222
+ const f = this.#ecmaFile(path.posix.normalize(base + String(e).replace(/^\.\//, '')).replace(/\.d\.ts$/, '')) ?? this.#ecmaFile(path.posix.normalize(base + String(e).replace(/^\.\//, '')));
223
+ if (f) return { file: f };
224
+ }
225
+ }
226
+ for (const s of scopes) if (s.baseUrl) {
227
+ const f = this.#ecmaFile(path.posix.normalize(path.posix.join(s.baseUrl, source)));
228
+ if (f) return { file: f };
229
+ }
230
+ return null;
231
+ }
232
+
233
+ #resolvePython(fromDir, source) {
234
+ let base;
235
+ let mod = source;
236
+ if (source.startsWith('.')) {
237
+ const dots = /^\.+/.exec(source)[0].length;
238
+ base = fromDir;
239
+ for (let i = 1; i < dots; i++) base = path.posix.dirname(base);
240
+ mod = source.slice(dots);
241
+ const p = mod ? path.posix.join(base, mod.replace(/\./g, '/')) : base;
242
+ if (this.files.has(p + '.py')) return { file: p + '.py' };
243
+ if (this.files.has(p + '/__init__.py')) return { file: p + '/__init__.py', dir: p };
244
+ if (this.dirs.has(p)) return { dir: p };
245
+ return null;
246
+ }
247
+ const rel = mod.replace(/\./g, '/');
248
+ const idx = this.#suffixIndex();
249
+ const cands = [...(idx.get(rel + '.py') ?? []), ...(idx.get(rel + '/__init__.py') ?? [])]
250
+ .filter(f => f === rel + '.py' || f === rel + '/__init__.py' || f.endsWith('/' + rel + '.py') || f.endsWith('/' + rel + '/__init__.py'));
251
+ if (cands.length) {
252
+ cands.sort((a, b) => a.length - b.length);
253
+ const f = cands[0];
254
+ return f.endsWith('/__init__.py') ? { file: f, dir: path.posix.dirname(f) } : { file: f };
255
+ }
256
+ return null;
257
+ }
258
+
259
+ #resolveGo(source) {
260
+ for (const { module, dir } of this.goModules) {
261
+ if (source === module || source.startsWith(module + '/')) {
262
+ const sub = source.slice(module.length).replace(/^\//, '');
263
+ const d = path.posix.normalize(dir === '.' ? (sub || '.') : (sub ? `${dir}/${sub}` : dir));
264
+ return this.dirs.has(d) ? { dir: d } : null;
265
+ }
266
+ }
267
+ return null;
268
+ }
269
+
270
+ #resolveInclude(fromDir, source) {
271
+ const direct = path.posix.normalize(path.posix.join(fromDir, source));
272
+ if (this.files.has(direct)) return { file: direct };
273
+ const idx = this.#suffixIndex();
274
+ const cands = (idx.get(source) ?? []).filter(f => f === source || f.endsWith('/' + source));
275
+ if (cands.length) {
276
+ cands.sort((a, b) => a.length - b.length);
277
+ return { file: cands[0] };
278
+ }
279
+ return null;
280
+ }
281
+
282
+ #resolveRelativeOrSuffix(fromDir, source, lang) {
283
+ const exts = lang === 'ruby' ? ['', '.rb'] : lang === 'css' ? ['', '.css', '.scss'] : ['', '.sh', '.bash'];
284
+ if (source.startsWith('.') || lang === 'bash') {
285
+ for (const e of exts) {
286
+ const p = path.posix.normalize(path.posix.join(fromDir, source + e));
287
+ if (this.files.has(p)) return { file: p };
288
+ }
289
+ }
290
+ const idx = this.#suffixIndex();
291
+ for (const e of exts) {
292
+ const s = source.replace(/^\.\//, '') + e;
293
+ const cands = (idx.get(s) ?? []).filter(f => f === s || f.endsWith('/' + s));
294
+ if (cands.length) { cands.sort((a, b) => a.length - b.length); return { file: cands[0] }; }
295
+ }
296
+ return null;
297
+ }
298
+
299
+ #resolveRust(fromFile, source) {
300
+ // crate::a::b / self::a / super::a → src/a/b.rs | src/a/b/mod.rs | src/a.rs (item b inside)
301
+ const segs = source.split('::').filter(Boolean);
302
+ if (!segs.length) return null;
303
+ let baseDir;
304
+ const crateRoot = this.#rustCrateRoot(fromFile);
305
+ if (segs[0] === 'crate') { baseDir = crateRoot; segs.shift(); }
306
+ else if (segs[0] === 'self') { baseDir = this.#rustModDir(fromFile); segs.shift(); }
307
+ else if (segs[0] === 'super') { baseDir = path.posix.dirname(this.#rustModDir(fromFile)); segs.shift(); while (segs[0] === 'super') { baseDir = path.posix.dirname(baseDir); segs.shift(); } }
308
+ else return null; // external crate
309
+ for (let n = segs.length; n > 0; n--) {
310
+ const p = path.posix.join(baseDir, ...segs.slice(0, n));
311
+ if (this.files.has(p + '.rs')) return { file: p + '.rs' };
312
+ if (this.files.has(p + '/mod.rs')) return { file: p + '/mod.rs' };
313
+ }
314
+ const lib = ['lib.rs', 'main.rs', 'mod.rs'].map(f => path.posix.join(baseDir, f)).find(f => this.files.has(f));
315
+ return lib ? { file: lib } : null;
316
+ }
317
+
318
+ #rustCrateRoot(fromFile) {
319
+ let d = path.posix.dirname(fromFile);
320
+ while (d && d !== '.') {
321
+ if (this.files.has(d + '/lib.rs') || this.files.has(d + '/main.rs')) return d;
322
+ d = path.posix.dirname(d);
323
+ }
324
+ return 'src';
325
+ }
326
+
327
+ #rustModDir(fromFile) {
328
+ const base = path.posix.basename(fromFile);
329
+ const dir = path.posix.dirname(fromFile);
330
+ return (base === 'mod.rs' || base === 'lib.rs' || base === 'main.rs') ? dir : path.posix.join(dir, base.replace(/\.rs$/, ''));
331
+ }
332
+ }