@shrkcrft/graph 0.1.0-alpha.10
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/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/indexer/detect-workspace.d.ts +18 -0
- package/dist/indexer/detect-workspace.d.ts.map +1 -0
- package/dist/indexer/detect-workspace.js +80 -0
- package/dist/indexer/extract-csharp-file.d.ts +27 -0
- package/dist/indexer/extract-csharp-file.d.ts.map +1 -0
- package/dist/indexer/extract-csharp-file.js +163 -0
- package/dist/indexer/extract-dart-file.d.ts +28 -0
- package/dist/indexer/extract-dart-file.d.ts.map +1 -0
- package/dist/indexer/extract-dart-file.js +167 -0
- package/dist/indexer/extract-elixir-file.d.ts +27 -0
- package/dist/indexer/extract-elixir-file.d.ts.map +1 -0
- package/dist/indexer/extract-elixir-file.js +164 -0
- package/dist/indexer/extract-go-file.d.ts +28 -0
- package/dist/indexer/extract-go-file.d.ts.map +1 -0
- package/dist/indexer/extract-go-file.js +156 -0
- package/dist/indexer/extract-java-file.d.ts +25 -0
- package/dist/indexer/extract-java-file.d.ts.map +1 -0
- package/dist/indexer/extract-java-file.js +140 -0
- package/dist/indexer/extract-kotlin-file.d.ts +20 -0
- package/dist/indexer/extract-kotlin-file.d.ts.map +1 -0
- package/dist/indexer/extract-kotlin-file.js +158 -0
- package/dist/indexer/extract-php-file.d.ts +26 -0
- package/dist/indexer/extract-php-file.d.ts.map +1 -0
- package/dist/indexer/extract-php-file.js +161 -0
- package/dist/indexer/extract-python-file.d.ts +30 -0
- package/dist/indexer/extract-python-file.d.ts.map +1 -0
- package/dist/indexer/extract-python-file.js +196 -0
- package/dist/indexer/extract-ruby-file.d.ts +29 -0
- package/dist/indexer/extract-ruby-file.d.ts.map +1 -0
- package/dist/indexer/extract-ruby-file.js +151 -0
- package/dist/indexer/extract-rust-file.d.ts +27 -0
- package/dist/indexer/extract-rust-file.d.ts.map +1 -0
- package/dist/indexer/extract-rust-file.js +186 -0
- package/dist/indexer/extract-swift-file.d.ts +27 -0
- package/dist/indexer/extract-swift-file.d.ts.map +1 -0
- package/dist/indexer/extract-swift-file.js +168 -0
- package/dist/indexer/extract-ts-file.d.ts +79 -0
- package/dist/indexer/extract-ts-file.d.ts.map +1 -0
- package/dist/indexer/extract-ts-file.js +403 -0
- package/dist/indexer/incremental-updater.d.ts +41 -0
- package/dist/indexer/incremental-updater.d.ts.map +1 -0
- package/dist/indexer/incremental-updater.js +395 -0
- package/dist/indexer/index-builder.d.ts +23 -0
- package/dist/indexer/index-builder.d.ts.map +1 -0
- package/dist/indexer/index-builder.js +289 -0
- package/dist/indexer/resolve-imports.d.ts +36 -0
- package/dist/indexer/resolve-imports.d.ts.map +1 -0
- package/dist/indexer/resolve-imports.js +144 -0
- package/dist/indexer/unresolved-imports.d.ts +20 -0
- package/dist/indexer/unresolved-imports.d.ts.map +1 -0
- package/dist/indexer/unresolved-imports.js +32 -0
- package/dist/query/cycle-detection.d.ts +40 -0
- package/dist/query/cycle-detection.d.ts.map +1 -0
- package/dist/query/cycle-detection.js +135 -0
- package/dist/query/query-api.d.ts +87 -0
- package/dist/query/query-api.d.ts.map +1 -0
- package/dist/query/query-api.js +232 -0
- package/dist/schema/edge-kind.d.ts +31 -0
- package/dist/schema/edge-kind.d.ts.map +1 -0
- package/dist/schema/edge-kind.js +35 -0
- package/dist/schema/edge.d.ts +22 -0
- package/dist/schema/edge.d.ts.map +1 -0
- package/dist/schema/edge.js +1 -0
- package/dist/schema/file-fingerprint.d.ts +22 -0
- package/dist/schema/file-fingerprint.d.ts.map +1 -0
- package/dist/schema/file-fingerprint.js +1 -0
- package/dist/schema/graph-snapshot.d.ts +18 -0
- package/dist/schema/graph-snapshot.d.ts.map +1 -0
- package/dist/schema/graph-snapshot.js +1 -0
- package/dist/schema/manifest.d.ts +47 -0
- package/dist/schema/manifest.d.ts.map +1 -0
- package/dist/schema/manifest.js +1 -0
- package/dist/schema/node-kind.d.ts +21 -0
- package/dist/schema/node-kind.d.ts.map +1 -0
- package/dist/schema/node-kind.js +27 -0
- package/dist/schema/node.d.ts +26 -0
- package/dist/schema/node.d.ts.map +1 -0
- package/dist/schema/node.js +1 -0
- package/dist/schema/schema-version.d.ts +10 -0
- package/dist/schema/schema-version.d.ts.map +1 -0
- package/dist/schema/schema-version.js +8 -0
- package/dist/store/file-fingerprint.d.ts +8 -0
- package/dist/store/file-fingerprint.d.ts.map +1 -0
- package/dist/store/file-fingerprint.js +64 -0
- package/dist/store/graph-store.d.ts +48 -0
- package/dist/store/graph-store.d.ts.map +1 -0
- package/dist/store/graph-store.js +194 -0
- package/package.json +54 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { EdgeKind } from "../schema/edge-kind.js";
|
|
4
|
+
import { NodeKind } from "../schema/node-kind.js";
|
|
5
|
+
export const EXTRACT_ELIXIR_FILE_SOURCE = 'extract-elixir-file@v1';
|
|
6
|
+
/**
|
|
7
|
+
* Regex-based Elixir extractor.
|
|
8
|
+
*
|
|
9
|
+
* Top-level constructs only (column-0):
|
|
10
|
+
* - `defmodule Path.To.Mod do` → module
|
|
11
|
+
* - `def name(...)` → function (exported)
|
|
12
|
+
* - `defp name(...)` → function (local)
|
|
13
|
+
* - `defstruct …` and `defprotocol` / `defimpl` headers → struct / protocol
|
|
14
|
+
*
|
|
15
|
+
* Nested `def`s under a `defmodule` show up at column 0 in this
|
|
16
|
+
* extractor's view; that's intentional because they're the publicly
|
|
17
|
+
* callable functions on that module.
|
|
18
|
+
*
|
|
19
|
+
* Imports: `alias Foo.Bar`, `alias Foo.{A, B}`, `import Enum`,
|
|
20
|
+
* `require Logger`, `use MyAppWeb, :controller`. Aliases inside
|
|
21
|
+
* `{...}` are expanded.
|
|
22
|
+
*
|
|
23
|
+
* Out of scope:
|
|
24
|
+
* - Pipe operator chains.
|
|
25
|
+
* - Macro definitions (`defmacro`/`defmacrop`).
|
|
26
|
+
* - Inline guard clauses (`when ...`).
|
|
27
|
+
*/
|
|
28
|
+
export function extractElixirFile(fingerprint, absPath, content) {
|
|
29
|
+
const text = content ?? readFileSync(absPath, 'utf8');
|
|
30
|
+
const fileNode = makeFileNode(fingerprint, text);
|
|
31
|
+
const symbolNodes = [];
|
|
32
|
+
const edges = [];
|
|
33
|
+
const lines = text.split('\n');
|
|
34
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
35
|
+
const raw = lines[i];
|
|
36
|
+
if (raw.length === 0)
|
|
37
|
+
continue;
|
|
38
|
+
const trimmed = raw.trimStart();
|
|
39
|
+
if (trimmed.startsWith('#'))
|
|
40
|
+
continue;
|
|
41
|
+
// defmodule (can be nested under another defmodule, but we still
|
|
42
|
+
// emit it as a symbol; the file may declare multiple).
|
|
43
|
+
let m = /^\s*defmodule\s+([A-Z][\w.]*)\s+do/.exec(raw);
|
|
44
|
+
if (m) {
|
|
45
|
+
pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'module', i + 1, true);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
// def / defp (top-of-line or under any indentation — we still want them).
|
|
49
|
+
m = /^\s*def\s+([a-z_][\w?!]*)\s*[\(,\s]/.exec(raw);
|
|
50
|
+
if (m) {
|
|
51
|
+
pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'function', i + 1, true);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
m = /^\s*defp\s+([a-z_][\w?!]*)\s*[\(,\s]/.exec(raw);
|
|
55
|
+
if (m) {
|
|
56
|
+
pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'function', i + 1, false);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
m = /^\s*defprotocol\s+([A-Z][\w.]*)/.exec(raw);
|
|
60
|
+
if (m) {
|
|
61
|
+
pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'interface', i + 1, true);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
m = /^\s*defstruct\s+/.exec(raw);
|
|
65
|
+
if (m) {
|
|
66
|
+
pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, '__struct__', 'class', i + 1, true);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
fileNode,
|
|
71
|
+
symbolNodes,
|
|
72
|
+
edges,
|
|
73
|
+
rawImportSpecifiers: scanElixirImports(text),
|
|
74
|
+
importBindings: [],
|
|
75
|
+
identifierReferences: [],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function pushSymbol(fp, nodes, edges, fileId, name, declKind, line, isExported) {
|
|
79
|
+
const sym = {
|
|
80
|
+
id: `symbol:${fp.path}#${name}`,
|
|
81
|
+
kind: NodeKind.Symbol,
|
|
82
|
+
label: name,
|
|
83
|
+
path: fp.path,
|
|
84
|
+
line,
|
|
85
|
+
data: { declKind, visibility: isExported ? 'export' : 'local', isExported, language: 'elixir' },
|
|
86
|
+
};
|
|
87
|
+
nodes.push(sym);
|
|
88
|
+
edges.push({
|
|
89
|
+
id: createHash('sha1').update(`${fileId}|${sym.id}|${EdgeKind.DeclaresSymbol}`).digest('hex'),
|
|
90
|
+
from: fileId,
|
|
91
|
+
to: sym.id,
|
|
92
|
+
kind: EdgeKind.DeclaresSymbol,
|
|
93
|
+
source: EXTRACT_ELIXIR_FILE_SOURCE,
|
|
94
|
+
data: { visibility: isExported ? 'export' : 'local', declKind, line },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function makeFileNode(fp, text) {
|
|
98
|
+
const label = fp.path.split('/').pop() ?? fp.path;
|
|
99
|
+
const tags = ['elixir'];
|
|
100
|
+
if (isElixirTestPath(fp.path))
|
|
101
|
+
tags.push('test');
|
|
102
|
+
// Capture the first defmodule name for convenience.
|
|
103
|
+
const modMatch = /^\s*defmodule\s+([A-Z][\w.]*)\s+do/m.exec(text);
|
|
104
|
+
return {
|
|
105
|
+
id: fp.nodeId,
|
|
106
|
+
kind: NodeKind.File,
|
|
107
|
+
label,
|
|
108
|
+
path: fp.path,
|
|
109
|
+
tags,
|
|
110
|
+
data: {
|
|
111
|
+
language: 'elixir',
|
|
112
|
+
sizeBytes: fp.sizeBytes,
|
|
113
|
+
sha1: fp.sha1,
|
|
114
|
+
hasDefaultExport: false,
|
|
115
|
+
exportCount: 0,
|
|
116
|
+
localCount: 0,
|
|
117
|
+
reExportCount: 0,
|
|
118
|
+
...(modMatch ? { elixirModule: modMatch[1] } : {}),
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function isElixirTestPath(rel) {
|
|
123
|
+
return (/(?:^|\/)test\//.test(rel) ||
|
|
124
|
+
/(?:^|\/)[\w-]+_test\.exs?$/.test(rel));
|
|
125
|
+
}
|
|
126
|
+
function scanElixirImports(text) {
|
|
127
|
+
const out = [];
|
|
128
|
+
// `alias Foo.Bar`, `alias Foo.Bar.{A, B}`, `import Foo`, `require Foo`, `use Foo, :opt`
|
|
129
|
+
const re = /^\s*(alias|import|require|use)\s+([A-Z][\w.]*)(?:\.\{([^}]+)\})?/gm;
|
|
130
|
+
let m;
|
|
131
|
+
while ((m = re.exec(text)) !== null) {
|
|
132
|
+
const kind = `elixir-${m[1]}`;
|
|
133
|
+
const base = m[2];
|
|
134
|
+
const line = lineFromOffset(text, m.index);
|
|
135
|
+
if (m[3]) {
|
|
136
|
+
for (const item of m[3].split(',')) {
|
|
137
|
+
const part = item.trim();
|
|
138
|
+
if (part)
|
|
139
|
+
out.push({ specifier: `${base}.${part}`, line, kind });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
out.push({ specifier: base, line, kind });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const seen = new Set();
|
|
147
|
+
const deduped = [];
|
|
148
|
+
for (const it of out) {
|
|
149
|
+
const k = `${it.kind}|${it.specifier}|${it.line}`;
|
|
150
|
+
if (seen.has(k))
|
|
151
|
+
continue;
|
|
152
|
+
seen.add(k);
|
|
153
|
+
deduped.push(it);
|
|
154
|
+
}
|
|
155
|
+
return deduped;
|
|
156
|
+
}
|
|
157
|
+
function lineFromOffset(text, offset) {
|
|
158
|
+
let line = 1;
|
|
159
|
+
for (let i = 0; i < offset && i < text.length; i++) {
|
|
160
|
+
if (text.charCodeAt(i) === 10)
|
|
161
|
+
line += 1;
|
|
162
|
+
}
|
|
163
|
+
return line;
|
|
164
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { IFileFingerprint } from '../schema/file-fingerprint.js';
|
|
2
|
+
import type { IExtractedFile } from './extract-ts-file.js';
|
|
3
|
+
export declare const EXTRACT_GO_FILE_SOURCE = "extract-go-file@v1";
|
|
4
|
+
/**
|
|
5
|
+
* Regex-based Go extractor.
|
|
6
|
+
*
|
|
7
|
+
* Top-level constructs only:
|
|
8
|
+
* - `func Name(...)` → symbol (function). Methods (`func (r *T) Name`)
|
|
9
|
+
* are also captured with the bare method name.
|
|
10
|
+
* - `type Name struct {...}` → symbol (struct).
|
|
11
|
+
* - `type Name interface {...}` → symbol (interface).
|
|
12
|
+
* - `type Name = ...` → symbol (type-alias).
|
|
13
|
+
*
|
|
14
|
+
* Imports: handles both `import "path"` and the block form
|
|
15
|
+
* `import (\n "a"\n "b"\n)`. Specifiers stored without quotes.
|
|
16
|
+
*
|
|
17
|
+
* Visibility: Go uses leading-uppercase for exported. We tag symbols
|
|
18
|
+
* with `isExported: true` when the name starts with an uppercase
|
|
19
|
+
* letter, false otherwise.
|
|
20
|
+
*
|
|
21
|
+
* Out of scope:
|
|
22
|
+
* - Constants / vars at the package level (would need a separate pass
|
|
23
|
+
* for `var (...)` and `const (...)` blocks). MVP keeps the symbol
|
|
24
|
+
* count tight; can be added later behind a flag.
|
|
25
|
+
* - Embedded types and generic type parameters.
|
|
26
|
+
*/
|
|
27
|
+
export declare function extractGoFile(fingerprint: IFileFingerprint, absPath: string, content?: string): IExtractedFile;
|
|
28
|
+
//# sourceMappingURL=extract-go-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-go-file.d.ts","sourceRoot":"","sources":["../../src/indexer/extract-go-file.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGtE,OAAO,KAAK,EACV,cAAc,EAEf,MAAM,sBAAsB,CAAC;AAE9B,eAAO,MAAM,sBAAsB,uBAAuB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,gBAAgB,EAC7B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,cAAc,CAoChB"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { EdgeKind } from "../schema/edge-kind.js";
|
|
4
|
+
import { NodeKind } from "../schema/node-kind.js";
|
|
5
|
+
export const EXTRACT_GO_FILE_SOURCE = 'extract-go-file@v1';
|
|
6
|
+
/**
|
|
7
|
+
* Regex-based Go extractor.
|
|
8
|
+
*
|
|
9
|
+
* Top-level constructs only:
|
|
10
|
+
* - `func Name(...)` → symbol (function). Methods (`func (r *T) Name`)
|
|
11
|
+
* are also captured with the bare method name.
|
|
12
|
+
* - `type Name struct {...}` → symbol (struct).
|
|
13
|
+
* - `type Name interface {...}` → symbol (interface).
|
|
14
|
+
* - `type Name = ...` → symbol (type-alias).
|
|
15
|
+
*
|
|
16
|
+
* Imports: handles both `import "path"` and the block form
|
|
17
|
+
* `import (\n "a"\n "b"\n)`. Specifiers stored without quotes.
|
|
18
|
+
*
|
|
19
|
+
* Visibility: Go uses leading-uppercase for exported. We tag symbols
|
|
20
|
+
* with `isExported: true` when the name starts with an uppercase
|
|
21
|
+
* letter, false otherwise.
|
|
22
|
+
*
|
|
23
|
+
* Out of scope:
|
|
24
|
+
* - Constants / vars at the package level (would need a separate pass
|
|
25
|
+
* for `var (...)` and `const (...)` blocks). MVP keeps the symbol
|
|
26
|
+
* count tight; can be added later behind a flag.
|
|
27
|
+
* - Embedded types and generic type parameters.
|
|
28
|
+
*/
|
|
29
|
+
export function extractGoFile(fingerprint, absPath, content) {
|
|
30
|
+
const text = content ?? readFileSync(absPath, 'utf8');
|
|
31
|
+
const fileNode = makeFileNode(fingerprint, text);
|
|
32
|
+
const symbolNodes = [];
|
|
33
|
+
const edges = [];
|
|
34
|
+
const lines = text.split('\n');
|
|
35
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
36
|
+
const raw = lines[i];
|
|
37
|
+
const line = i + 1;
|
|
38
|
+
if (raw.length === 0)
|
|
39
|
+
continue;
|
|
40
|
+
if (raw.trimStart().startsWith('//'))
|
|
41
|
+
continue;
|
|
42
|
+
// func — with optional receiver `(r *T)`.
|
|
43
|
+
let m = /^func(?:\s*\([^)]*\))?\s+([A-Za-z_][\w]*)\s*[\(\[]/.exec(raw);
|
|
44
|
+
if (m) {
|
|
45
|
+
const name = m[1];
|
|
46
|
+
pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, name, 'function', line);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// type Name struct { … } / interface { … } / = alias.
|
|
50
|
+
m = /^type\s+([A-Za-z_][\w]*)\s+(struct|interface|=)/.exec(raw);
|
|
51
|
+
if (m) {
|
|
52
|
+
const name = m[1];
|
|
53
|
+
const decl = m[2] === 'struct' ? 'class' : m[2] === 'interface' ? 'interface' : 'type-alias';
|
|
54
|
+
pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, name, decl, line);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
fileNode,
|
|
59
|
+
symbolNodes,
|
|
60
|
+
edges,
|
|
61
|
+
rawImportSpecifiers: scanGoImports(text),
|
|
62
|
+
importBindings: [],
|
|
63
|
+
identifierReferences: [],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function pushSymbol(fp, nodes, edges, fileId, name, declKind, line) {
|
|
67
|
+
const isExported = /^[A-Z]/.test(name);
|
|
68
|
+
const sym = {
|
|
69
|
+
id: `symbol:${fp.path}#${name}`,
|
|
70
|
+
kind: NodeKind.Symbol,
|
|
71
|
+
label: name,
|
|
72
|
+
path: fp.path,
|
|
73
|
+
line,
|
|
74
|
+
data: { declKind, visibility: isExported ? 'export' : 'local', isExported, language: 'go' },
|
|
75
|
+
};
|
|
76
|
+
nodes.push(sym);
|
|
77
|
+
edges.push({
|
|
78
|
+
id: createHash('sha1').update(`${fileId}|${sym.id}|${EdgeKind.DeclaresSymbol}`).digest('hex'),
|
|
79
|
+
from: fileId,
|
|
80
|
+
to: sym.id,
|
|
81
|
+
kind: EdgeKind.DeclaresSymbol,
|
|
82
|
+
source: EXTRACT_GO_FILE_SOURCE,
|
|
83
|
+
data: { visibility: isExported ? 'export' : 'local', declKind, line },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function makeFileNode(fp, text) {
|
|
87
|
+
const label = fp.path.split('/').pop() ?? fp.path;
|
|
88
|
+
const tags = ['go'];
|
|
89
|
+
if (isGoTestPath(fp.path))
|
|
90
|
+
tags.push('test');
|
|
91
|
+
const packageMatch = /^package\s+(\w+)/m.exec(text);
|
|
92
|
+
return {
|
|
93
|
+
id: fp.nodeId,
|
|
94
|
+
kind: NodeKind.File,
|
|
95
|
+
label,
|
|
96
|
+
path: fp.path,
|
|
97
|
+
tags,
|
|
98
|
+
data: {
|
|
99
|
+
language: 'go',
|
|
100
|
+
sizeBytes: fp.sizeBytes,
|
|
101
|
+
sha1: fp.sha1,
|
|
102
|
+
hasDefaultExport: false,
|
|
103
|
+
exportCount: 0,
|
|
104
|
+
localCount: 0,
|
|
105
|
+
reExportCount: 0,
|
|
106
|
+
...(packageMatch ? { goPackage: packageMatch[1] } : {}),
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function isGoTestPath(rel) {
|
|
111
|
+
return /(?:^|\/)[\w-]+_test\.go$/.test(rel);
|
|
112
|
+
}
|
|
113
|
+
function scanGoImports(text) {
|
|
114
|
+
const out = [];
|
|
115
|
+
// Single-line: import "path" or import alias "path"
|
|
116
|
+
const single = /^import\s+(?:[\w.]+\s+)?"([^"\n]+)"/gm;
|
|
117
|
+
let m;
|
|
118
|
+
while ((m = single.exec(text)) !== null) {
|
|
119
|
+
const line = lineFromOffset(text, m.index);
|
|
120
|
+
out.push({ specifier: m[1], line, kind: 'go-import' });
|
|
121
|
+
}
|
|
122
|
+
// Block form: import (\n "a"\n alias "b"\n)
|
|
123
|
+
const block = /^import\s*\(([^)]*)\)/gms;
|
|
124
|
+
while ((m = block.exec(text)) !== null) {
|
|
125
|
+
const body = m[1];
|
|
126
|
+
const startLine = lineFromOffset(text, m.index);
|
|
127
|
+
const bodyLines = body.split('\n');
|
|
128
|
+
for (let i = 0; i < bodyLines.length; i += 1) {
|
|
129
|
+
const ln = bodyLines[i].trim();
|
|
130
|
+
if (!ln || ln.startsWith('//'))
|
|
131
|
+
continue;
|
|
132
|
+
const inner = /^(?:[\w.]+\s+)?"([^"]+)"/.exec(ln);
|
|
133
|
+
if (inner)
|
|
134
|
+
out.push({ specifier: inner[1], line: startLine + i, kind: 'go-import' });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// De-dupe.
|
|
138
|
+
const seen = new Set();
|
|
139
|
+
const deduped = [];
|
|
140
|
+
for (const it of out) {
|
|
141
|
+
const k = `${it.specifier}|${it.line}`;
|
|
142
|
+
if (seen.has(k))
|
|
143
|
+
continue;
|
|
144
|
+
seen.add(k);
|
|
145
|
+
deduped.push(it);
|
|
146
|
+
}
|
|
147
|
+
return deduped;
|
|
148
|
+
}
|
|
149
|
+
function lineFromOffset(text, offset) {
|
|
150
|
+
let line = 1;
|
|
151
|
+
for (let i = 0; i < offset && i < text.length; i++) {
|
|
152
|
+
if (text.charCodeAt(i) === 10)
|
|
153
|
+
line += 1;
|
|
154
|
+
}
|
|
155
|
+
return line;
|
|
156
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { IFileFingerprint } from '../schema/file-fingerprint.js';
|
|
2
|
+
import type { IExtractedFile } from './extract-ts-file.js';
|
|
3
|
+
export declare const EXTRACT_JAVA_FILE_SOURCE = "extract-java-file@v1";
|
|
4
|
+
/**
|
|
5
|
+
* Regex-based Java extractor.
|
|
6
|
+
*
|
|
7
|
+
* Top-level constructs only — we don't track nested classes or class
|
|
8
|
+
* members. For a fuller view a future round can layer a real Java
|
|
9
|
+
* parser (`tree-sitter-java`) without changing the schema.
|
|
10
|
+
*
|
|
11
|
+
* - `public class Name { … }` (and `final` / `abstract` / `sealed`
|
|
12
|
+
* modifiers in any order) → symbol (class).
|
|
13
|
+
* - `interface Name { … }` → symbol (interface).
|
|
14
|
+
* - `enum Name { … }` → symbol (enum).
|
|
15
|
+
* - `record Name(...)` → symbol (class).
|
|
16
|
+
*
|
|
17
|
+
* Visibility is derived from the modifier: `public` → exported,
|
|
18
|
+
* anything else → local (package-private / private / protected).
|
|
19
|
+
*
|
|
20
|
+
* Imports: classic Java `import a.b.C;` / `import a.b.*;` /
|
|
21
|
+
* `import static a.b.C.*;`. The specifier is the fully-qualified path
|
|
22
|
+
* (without trailing `;`).
|
|
23
|
+
*/
|
|
24
|
+
export declare function extractJavaFile(fingerprint: IFileFingerprint, absPath: string, content?: string): IExtractedFile;
|
|
25
|
+
//# sourceMappingURL=extract-java-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-java-file.d.ts","sourceRoot":"","sources":["../../src/indexer/extract-java-file.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGtE,OAAO,KAAK,EACV,cAAc,EAEf,MAAM,sBAAsB,CAAC;AAE9B,eAAO,MAAM,wBAAwB,yBAAyB,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,gBAAgB,EAC7B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,cAAc,CAoChB"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { EdgeKind } from "../schema/edge-kind.js";
|
|
4
|
+
import { NodeKind } from "../schema/node-kind.js";
|
|
5
|
+
export const EXTRACT_JAVA_FILE_SOURCE = 'extract-java-file@v1';
|
|
6
|
+
/**
|
|
7
|
+
* Regex-based Java extractor.
|
|
8
|
+
*
|
|
9
|
+
* Top-level constructs only — we don't track nested classes or class
|
|
10
|
+
* members. For a fuller view a future round can layer a real Java
|
|
11
|
+
* parser (`tree-sitter-java`) without changing the schema.
|
|
12
|
+
*
|
|
13
|
+
* - `public class Name { … }` (and `final` / `abstract` / `sealed`
|
|
14
|
+
* modifiers in any order) → symbol (class).
|
|
15
|
+
* - `interface Name { … }` → symbol (interface).
|
|
16
|
+
* - `enum Name { … }` → symbol (enum).
|
|
17
|
+
* - `record Name(...)` → symbol (class).
|
|
18
|
+
*
|
|
19
|
+
* Visibility is derived from the modifier: `public` → exported,
|
|
20
|
+
* anything else → local (package-private / private / protected).
|
|
21
|
+
*
|
|
22
|
+
* Imports: classic Java `import a.b.C;` / `import a.b.*;` /
|
|
23
|
+
* `import static a.b.C.*;`. The specifier is the fully-qualified path
|
|
24
|
+
* (without trailing `;`).
|
|
25
|
+
*/
|
|
26
|
+
export function extractJavaFile(fingerprint, absPath, content) {
|
|
27
|
+
const text = content ?? readFileSync(absPath, 'utf8');
|
|
28
|
+
const fileNode = makeFileNode(fingerprint, text);
|
|
29
|
+
const symbolNodes = [];
|
|
30
|
+
const edges = [];
|
|
31
|
+
// Capture top-level type declarations. Modifiers can appear in any
|
|
32
|
+
// order and may include annotations (which we discard for the MVP).
|
|
33
|
+
// We require the declaration starts at column 0 to keep nested
|
|
34
|
+
// declarations (class bodies) out of the symbol list.
|
|
35
|
+
const lines = text.split('\n');
|
|
36
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
37
|
+
const raw = lines[i];
|
|
38
|
+
if (raw.length === 0)
|
|
39
|
+
continue;
|
|
40
|
+
if (raw.startsWith(' ') || raw.startsWith('\t'))
|
|
41
|
+
continue;
|
|
42
|
+
if (raw.trimStart().startsWith('//'))
|
|
43
|
+
continue;
|
|
44
|
+
// Strip leading annotations like `@Override`.
|
|
45
|
+
const stripped = raw.replace(/^(?:@\w+(?:\([^)]*\))?\s+)+/, '');
|
|
46
|
+
const m = /^(?:public\s+|protected\s+|private\s+|static\s+|final\s+|abstract\s+|sealed\s+|non-sealed\s+)*\s*(class|interface|enum|record)\s+([A-Za-z_][\w]*)/.exec(stripped);
|
|
47
|
+
if (!m)
|
|
48
|
+
continue;
|
|
49
|
+
const declKind = m[1] === 'class' || m[1] === 'record' ? 'class'
|
|
50
|
+
: m[1] === 'interface' ? 'interface'
|
|
51
|
+
: 'enum';
|
|
52
|
+
const name = m[2];
|
|
53
|
+
const isExported = /\bpublic\b/.test(stripped);
|
|
54
|
+
pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, name, declKind, i + 1, isExported);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
fileNode,
|
|
58
|
+
symbolNodes,
|
|
59
|
+
edges,
|
|
60
|
+
rawImportSpecifiers: scanJavaImports(text),
|
|
61
|
+
importBindings: [],
|
|
62
|
+
identifierReferences: [],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function pushSymbol(fp, nodes, edges, fileId, name, declKind, line, isExported) {
|
|
66
|
+
const sym = {
|
|
67
|
+
id: `symbol:${fp.path}#${name}`,
|
|
68
|
+
kind: NodeKind.Symbol,
|
|
69
|
+
label: name,
|
|
70
|
+
path: fp.path,
|
|
71
|
+
line,
|
|
72
|
+
data: { declKind, visibility: isExported ? 'export' : 'local', isExported, language: 'java' },
|
|
73
|
+
};
|
|
74
|
+
nodes.push(sym);
|
|
75
|
+
edges.push({
|
|
76
|
+
id: createHash('sha1').update(`${fileId}|${sym.id}|${EdgeKind.DeclaresSymbol}`).digest('hex'),
|
|
77
|
+
from: fileId,
|
|
78
|
+
to: sym.id,
|
|
79
|
+
kind: EdgeKind.DeclaresSymbol,
|
|
80
|
+
source: EXTRACT_JAVA_FILE_SOURCE,
|
|
81
|
+
data: { visibility: isExported ? 'export' : 'local', declKind, line },
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function makeFileNode(fp, text) {
|
|
85
|
+
const label = fp.path.split('/').pop() ?? fp.path;
|
|
86
|
+
const tags = ['java'];
|
|
87
|
+
if (isJavaTestPath(fp.path))
|
|
88
|
+
tags.push('test');
|
|
89
|
+
const packageMatch = /^package\s+([\w.]+)\s*;/m.exec(text);
|
|
90
|
+
return {
|
|
91
|
+
id: fp.nodeId,
|
|
92
|
+
kind: NodeKind.File,
|
|
93
|
+
label,
|
|
94
|
+
path: fp.path,
|
|
95
|
+
tags,
|
|
96
|
+
data: {
|
|
97
|
+
language: 'java',
|
|
98
|
+
sizeBytes: fp.sizeBytes,
|
|
99
|
+
sha1: fp.sha1,
|
|
100
|
+
hasDefaultExport: false,
|
|
101
|
+
exportCount: 0,
|
|
102
|
+
localCount: 0,
|
|
103
|
+
reExportCount: 0,
|
|
104
|
+
...(packageMatch ? { javaPackage: packageMatch[1] } : {}),
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function isJavaTestPath(rel) {
|
|
109
|
+
return (/(?:^|\/)src\/test\//.test(rel) ||
|
|
110
|
+
/(?:^|\/)[\w-]+Test\.java$/.test(rel) ||
|
|
111
|
+
/(?:^|\/)[\w-]+Tests\.java$/.test(rel));
|
|
112
|
+
}
|
|
113
|
+
function scanJavaImports(text) {
|
|
114
|
+
const out = [];
|
|
115
|
+
const re = /^import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;/gm;
|
|
116
|
+
let m;
|
|
117
|
+
while ((m = re.exec(text)) !== null) {
|
|
118
|
+
const line = lineFromOffset(text, m.index);
|
|
119
|
+
out.push({ specifier: m[1], line, kind: 'java-import' });
|
|
120
|
+
}
|
|
121
|
+
// De-dupe.
|
|
122
|
+
const seen = new Set();
|
|
123
|
+
const deduped = [];
|
|
124
|
+
for (const it of out) {
|
|
125
|
+
const k = `${it.specifier}|${it.line}`;
|
|
126
|
+
if (seen.has(k))
|
|
127
|
+
continue;
|
|
128
|
+
seen.add(k);
|
|
129
|
+
deduped.push(it);
|
|
130
|
+
}
|
|
131
|
+
return deduped;
|
|
132
|
+
}
|
|
133
|
+
function lineFromOffset(text, offset) {
|
|
134
|
+
let line = 1;
|
|
135
|
+
for (let i = 0; i < offset && i < text.length; i++) {
|
|
136
|
+
if (text.charCodeAt(i) === 10)
|
|
137
|
+
line += 1;
|
|
138
|
+
}
|
|
139
|
+
return line;
|
|
140
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { IFileFingerprint } from '../schema/file-fingerprint.js';
|
|
2
|
+
import type { IExtractedFile } from './extract-ts-file.js';
|
|
3
|
+
export declare const EXTRACT_KOTLIN_FILE_SOURCE = "extract-kotlin-file@v1";
|
|
4
|
+
/**
|
|
5
|
+
* Regex-based Kotlin extractor.
|
|
6
|
+
*
|
|
7
|
+
* Top-level constructs only. Captured:
|
|
8
|
+
* - `fun name(...)`, `inline fun`, `suspend fun` → function
|
|
9
|
+
* - `class Name`, `data class Name`, `value class Name`, `inline class Name`, `sealed class Name`, `abstract class Name`, `open class Name` → class
|
|
10
|
+
* - `interface Name`, `sealed interface Name` → interface
|
|
11
|
+
* - `object Name` → object (rendered as `class`)
|
|
12
|
+
* - `enum class Name` → enum
|
|
13
|
+
* - `typealias Name = ...` → type-alias
|
|
14
|
+
* - `val NAME: T`, `var NAME: T`, `const val NAME` → const
|
|
15
|
+
*
|
|
16
|
+
* Visibility: Kotlin's default is `public`. `private`, `internal`,
|
|
17
|
+
* and `protected` mark a symbol as local; everything else is exported.
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractKotlinFile(fingerprint: IFileFingerprint, absPath: string, content?: string): IExtractedFile;
|
|
20
|
+
//# sourceMappingURL=extract-kotlin-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-kotlin-file.d.ts","sourceRoot":"","sources":["../../src/indexer/extract-kotlin-file.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGtE,OAAO,KAAK,EACV,cAAc,EAEf,MAAM,sBAAsB,CAAC;AAE9B,eAAO,MAAM,0BAA0B,2BAA2B,CAAC;AAEnE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,gBAAgB,EAC7B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,cAAc,CA8DhB"}
|