@renseiai/agentfactory-code-intelligence 0.8.4
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 +21 -0
- package/dist/src/__tests__/types.test.d.ts +2 -0
- package/dist/src/__tests__/types.test.d.ts.map +1 -0
- package/dist/src/__tests__/types.test.js +187 -0
- package/dist/src/index.d.ts +26 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +29 -0
- package/dist/src/indexing/__tests__/indexing.test.d.ts +2 -0
- package/dist/src/indexing/__tests__/indexing.test.d.ts.map +1 -0
- package/dist/src/indexing/__tests__/indexing.test.js +193 -0
- package/dist/src/indexing/change-detector.d.ts +16 -0
- package/dist/src/indexing/change-detector.d.ts.map +1 -0
- package/dist/src/indexing/change-detector.js +38 -0
- package/dist/src/indexing/git-hash-provider.d.ts +11 -0
- package/dist/src/indexing/git-hash-provider.d.ts.map +1 -0
- package/dist/src/indexing/git-hash-provider.js +25 -0
- package/dist/src/indexing/incremental-indexer.d.ts +38 -0
- package/dist/src/indexing/incremental-indexer.d.ts.map +1 -0
- package/dist/src/indexing/incremental-indexer.js +122 -0
- package/dist/src/indexing/merkle-tree.d.ts +33 -0
- package/dist/src/indexing/merkle-tree.d.ts.map +1 -0
- package/dist/src/indexing/merkle-tree.js +107 -0
- package/dist/src/memory/__tests__/dedup.test.d.ts +2 -0
- package/dist/src/memory/__tests__/dedup.test.d.ts.map +1 -0
- package/dist/src/memory/__tests__/dedup.test.js +173 -0
- package/dist/src/memory/dedup-pipeline.d.ts +24 -0
- package/dist/src/memory/dedup-pipeline.d.ts.map +1 -0
- package/dist/src/memory/dedup-pipeline.js +73 -0
- package/dist/src/memory/memory-store.d.ts +22 -0
- package/dist/src/memory/memory-store.d.ts.map +1 -0
- package/dist/src/memory/memory-store.js +32 -0
- package/dist/src/memory/simhash.d.ts +16 -0
- package/dist/src/memory/simhash.d.ts.map +1 -0
- package/dist/src/memory/simhash.js +67 -0
- package/dist/src/memory/xxhash.d.ts +3 -0
- package/dist/src/memory/xxhash.d.ts.map +1 -0
- package/dist/src/memory/xxhash.js +13 -0
- package/dist/src/parser/__tests__/multi-language.test.d.ts +2 -0
- package/dist/src/parser/__tests__/multi-language.test.d.ts.map +1 -0
- package/dist/src/parser/__tests__/multi-language.test.js +350 -0
- package/dist/src/parser/__tests__/symbol-extractor.test.d.ts +2 -0
- package/dist/src/parser/__tests__/symbol-extractor.test.d.ts.map +1 -0
- package/dist/src/parser/__tests__/symbol-extractor.test.js +188 -0
- package/dist/src/parser/go-extractor.d.ts +8 -0
- package/dist/src/parser/go-extractor.d.ts.map +1 -0
- package/dist/src/parser/go-extractor.js +127 -0
- package/dist/src/parser/python-extractor.d.ts +8 -0
- package/dist/src/parser/python-extractor.d.ts.map +1 -0
- package/dist/src/parser/python-extractor.js +92 -0
- package/dist/src/parser/rust-extractor.d.ts +8 -0
- package/dist/src/parser/rust-extractor.d.ts.map +1 -0
- package/dist/src/parser/rust-extractor.js +168 -0
- package/dist/src/parser/symbol-extractor.d.ts +14 -0
- package/dist/src/parser/symbol-extractor.d.ts.map +1 -0
- package/dist/src/parser/symbol-extractor.js +47 -0
- package/dist/src/parser/typescript-extractor.d.ts +13 -0
- package/dist/src/parser/typescript-extractor.d.ts.map +1 -0
- package/dist/src/parser/typescript-extractor.js +229 -0
- package/dist/src/plugin/__tests__/plugin.test.d.ts +2 -0
- package/dist/src/plugin/__tests__/plugin.test.d.ts.map +1 -0
- package/dist/src/plugin/__tests__/plugin.test.js +48 -0
- package/dist/src/plugin/code-intelligence-plugin.d.ts +15 -0
- package/dist/src/plugin/code-intelligence-plugin.d.ts.map +1 -0
- package/dist/src/plugin/code-intelligence-plugin.js +102 -0
- package/dist/src/repo-map/__tests__/repo-map.test.d.ts +2 -0
- package/dist/src/repo-map/__tests__/repo-map.test.d.ts.map +1 -0
- package/dist/src/repo-map/__tests__/repo-map.test.js +186 -0
- package/dist/src/repo-map/dependency-graph.d.ts +30 -0
- package/dist/src/repo-map/dependency-graph.d.ts.map +1 -0
- package/dist/src/repo-map/dependency-graph.js +105 -0
- package/dist/src/repo-map/pagerank.d.ts +20 -0
- package/dist/src/repo-map/pagerank.d.ts.map +1 -0
- package/dist/src/repo-map/pagerank.js +68 -0
- package/dist/src/repo-map/repo-map-generator.d.ts +20 -0
- package/dist/src/repo-map/repo-map-generator.d.ts.map +1 -0
- package/dist/src/repo-map/repo-map-generator.js +66 -0
- package/dist/src/search/__tests__/search.test.d.ts +2 -0
- package/dist/src/search/__tests__/search.test.d.ts.map +1 -0
- package/dist/src/search/__tests__/search.test.js +191 -0
- package/dist/src/search/bm25.d.ts +24 -0
- package/dist/src/search/bm25.d.ts.map +1 -0
- package/dist/src/search/bm25.js +44 -0
- package/dist/src/search/inverted-index.d.ts +31 -0
- package/dist/src/search/inverted-index.d.ts.map +1 -0
- package/dist/src/search/inverted-index.js +72 -0
- package/dist/src/search/search-engine.d.ts +22 -0
- package/dist/src/search/search-engine.d.ts.map +1 -0
- package/dist/src/search/search-engine.js +76 -0
- package/dist/src/search/tokenizer.d.ts +11 -0
- package/dist/src/search/tokenizer.d.ts.map +1 -0
- package/dist/src/search/tokenizer.js +48 -0
- package/dist/src/types.d.ts +242 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +96 -0
- package/package.json +74 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SimHash implementation for near-duplicate detection.
|
|
3
|
+
* Produces a 64-bit fingerprint where similar content produces similar hashes.
|
|
4
|
+
*/
|
|
5
|
+
/** Simple string hash for tokens (FNV-1a-like, 64-bit safe). */
|
|
6
|
+
function tokenHash(token) {
|
|
7
|
+
let hash = 0xcbf29ce484222325n;
|
|
8
|
+
for (let i = 0; i < token.length; i++) {
|
|
9
|
+
hash ^= BigInt(token.charCodeAt(i));
|
|
10
|
+
hash = BigInt.asUintN(64, hash * 0x100000001b3n);
|
|
11
|
+
}
|
|
12
|
+
return hash;
|
|
13
|
+
}
|
|
14
|
+
export class SimHash {
|
|
15
|
+
bits;
|
|
16
|
+
constructor(bits = 64) {
|
|
17
|
+
this.bits = bits;
|
|
18
|
+
}
|
|
19
|
+
/** Compute a SimHash fingerprint for the given text. */
|
|
20
|
+
compute(text) {
|
|
21
|
+
const tokens = this.tokenize(text);
|
|
22
|
+
if (tokens.length === 0)
|
|
23
|
+
return 0n;
|
|
24
|
+
// Weight vector: one entry per bit
|
|
25
|
+
const weights = new Float64Array(this.bits);
|
|
26
|
+
for (const token of tokens) {
|
|
27
|
+
const hash = tokenHash(token);
|
|
28
|
+
for (let i = 0; i < this.bits; i++) {
|
|
29
|
+
if ((hash >> BigInt(i)) & 1n) {
|
|
30
|
+
weights[i] += 1;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
weights[i] -= 1;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Convert weight vector to fingerprint
|
|
38
|
+
let fingerprint = 0n;
|
|
39
|
+
for (let i = 0; i < this.bits; i++) {
|
|
40
|
+
if (weights[i] > 0) {
|
|
41
|
+
fingerprint |= 1n << BigInt(i);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return fingerprint;
|
|
45
|
+
}
|
|
46
|
+
/** Compute Hamming distance between two fingerprints. */
|
|
47
|
+
hammingDistance(a, b) {
|
|
48
|
+
let xor = a ^ b;
|
|
49
|
+
let count = 0;
|
|
50
|
+
while (xor > 0n) {
|
|
51
|
+
count += Number(xor & 1n);
|
|
52
|
+
xor >>= 1n;
|
|
53
|
+
}
|
|
54
|
+
return count;
|
|
55
|
+
}
|
|
56
|
+
/** Check if two fingerprints are near-duplicates within threshold. */
|
|
57
|
+
isNearDuplicate(a, b, threshold = 3) {
|
|
58
|
+
return this.hammingDistance(a, b) <= threshold;
|
|
59
|
+
}
|
|
60
|
+
tokenize(text) {
|
|
61
|
+
// Split on whitespace and punctuation, lowercase, filter short tokens
|
|
62
|
+
return text
|
|
63
|
+
.toLowerCase()
|
|
64
|
+
.split(/[\s\p{P}]+/u)
|
|
65
|
+
.filter(t => t.length >= 2);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xxhash.d.ts","sourceRoot":"","sources":["../../../src/memory/xxhash.ts"],"names":[],"mappings":"AAWA,uDAAuD;AACvD,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG/D"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import xxhashWasm from 'xxhash-wasm';
|
|
2
|
+
let hasherPromise;
|
|
3
|
+
function getHasher() {
|
|
4
|
+
if (!hasherPromise) {
|
|
5
|
+
hasherPromise = xxhashWasm();
|
|
6
|
+
}
|
|
7
|
+
return hasherPromise;
|
|
8
|
+
}
|
|
9
|
+
/** Compute a 64-bit xxHash hex string from content. */
|
|
10
|
+
export async function xxhash64(content) {
|
|
11
|
+
const hasher = await getHasher();
|
|
12
|
+
return hasher.h64ToString(content);
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-language.test.d.ts","sourceRoot":"","sources":["../../../../src/parser/__tests__/multi-language.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { PythonExtractor } from '../python-extractor.js';
|
|
3
|
+
import { GoExtractor } from '../go-extractor.js';
|
|
4
|
+
import { RustExtractor } from '../rust-extractor.js';
|
|
5
|
+
import { SymbolExtractor } from '../symbol-extractor.js';
|
|
6
|
+
// ── Python ────────────────────────────────────────────────────────────
|
|
7
|
+
const samplePython = `
|
|
8
|
+
import os
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class User:
|
|
14
|
+
name: str
|
|
15
|
+
email: str
|
|
16
|
+
|
|
17
|
+
def full_name(self) -> str:
|
|
18
|
+
return self.name
|
|
19
|
+
|
|
20
|
+
class _InternalHelper:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def process_data(items: List[str]) -> List[str]:
|
|
24
|
+
return [item.strip() for item in items]
|
|
25
|
+
|
|
26
|
+
async def fetch_users(url: str) -> List[User]:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
_private_func = lambda x: x
|
|
30
|
+
|
|
31
|
+
MAX_RETRIES = 3
|
|
32
|
+
`;
|
|
33
|
+
describe('PythonExtractor', () => {
|
|
34
|
+
const extractor = new PythonExtractor();
|
|
35
|
+
it('extracts imports', () => {
|
|
36
|
+
const result = extractor.extract(samplePython, 'main.py');
|
|
37
|
+
expect(result.imports).toContain('os');
|
|
38
|
+
expect(result.imports).toContain('typing');
|
|
39
|
+
});
|
|
40
|
+
it('extracts classes', () => {
|
|
41
|
+
const result = extractor.extract(samplePython, 'main.py');
|
|
42
|
+
const user = result.symbols.find(s => s.name === 'User' && s.kind === 'class');
|
|
43
|
+
expect(user).toBeDefined();
|
|
44
|
+
expect(user.exported).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
it('marks internal classes as not exported', () => {
|
|
47
|
+
const result = extractor.extract(samplePython, 'main.py');
|
|
48
|
+
const internal = result.symbols.find(s => s.name === '_InternalHelper');
|
|
49
|
+
expect(internal).toBeDefined();
|
|
50
|
+
expect(internal.exported).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
it('extracts functions', () => {
|
|
53
|
+
const result = extractor.extract(samplePython, 'main.py');
|
|
54
|
+
const fn = result.symbols.find(s => s.name === 'process_data' && s.kind === 'function');
|
|
55
|
+
expect(fn).toBeDefined();
|
|
56
|
+
expect(fn.exported).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
it('extracts async functions', () => {
|
|
59
|
+
const result = extractor.extract(samplePython, 'main.py');
|
|
60
|
+
const fn = result.symbols.find(s => s.name === 'fetch_users' && s.kind === 'function');
|
|
61
|
+
expect(fn).toBeDefined();
|
|
62
|
+
});
|
|
63
|
+
it('extracts methods', () => {
|
|
64
|
+
const result = extractor.extract(samplePython, 'main.py');
|
|
65
|
+
const method = result.symbols.find(s => s.name === 'full_name' && s.kind === 'method');
|
|
66
|
+
expect(method).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
it('extracts decorators', () => {
|
|
69
|
+
const result = extractor.extract(samplePython, 'main.py');
|
|
70
|
+
const dec = result.symbols.find(s => s.name === 'dataclass' && s.kind === 'decorator');
|
|
71
|
+
expect(dec).toBeDefined();
|
|
72
|
+
});
|
|
73
|
+
it('sets language to python', () => {
|
|
74
|
+
const result = extractor.extract(samplePython, 'main.py');
|
|
75
|
+
expect(result.language).toBe('python');
|
|
76
|
+
for (const s of result.symbols) {
|
|
77
|
+
expect(s.language).toBe('python');
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
// ── Go ────────────────────────────────────────────────────────────────
|
|
82
|
+
const sampleGo = `
|
|
83
|
+
package main
|
|
84
|
+
|
|
85
|
+
import (
|
|
86
|
+
"fmt"
|
|
87
|
+
"net/http"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
// Server represents an HTTP server
|
|
91
|
+
type Server struct {
|
|
92
|
+
Port int
|
|
93
|
+
Host string
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Handler is the request handler interface
|
|
97
|
+
type Handler interface {
|
|
98
|
+
ServeHTTP(w http.ResponseWriter, r *http.Request)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
type myInternal struct{}
|
|
102
|
+
|
|
103
|
+
// NewServer creates a new Server instance
|
|
104
|
+
func NewServer(port int) *Server {
|
|
105
|
+
return &Server{Port: port}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Start starts the server
|
|
109
|
+
func (s *Server) Start() error {
|
|
110
|
+
return http.ListenAndServe(fmt.Sprintf("%s:%d", s.Host, s.Port), nil)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
func (s *Server) internal() {}
|
|
114
|
+
|
|
115
|
+
var DefaultPort = 8080
|
|
116
|
+
const Version = "1.0"
|
|
117
|
+
`;
|
|
118
|
+
describe('GoExtractor', () => {
|
|
119
|
+
const extractor = new GoExtractor();
|
|
120
|
+
it('extracts imports', () => {
|
|
121
|
+
const result = extractor.extract(sampleGo, 'main.go');
|
|
122
|
+
expect(result.imports).toContain('fmt');
|
|
123
|
+
expect(result.imports).toContain('net/http');
|
|
124
|
+
});
|
|
125
|
+
it('extracts structs', () => {
|
|
126
|
+
const result = extractor.extract(sampleGo, 'main.go');
|
|
127
|
+
const srv = result.symbols.find(s => s.name === 'Server' && s.kind === 'struct');
|
|
128
|
+
expect(srv).toBeDefined();
|
|
129
|
+
expect(srv.exported).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
it('extracts interfaces', () => {
|
|
132
|
+
const result = extractor.extract(sampleGo, 'main.go');
|
|
133
|
+
const handler = result.symbols.find(s => s.name === 'Handler' && s.kind === 'interface');
|
|
134
|
+
expect(handler).toBeDefined();
|
|
135
|
+
expect(handler.exported).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
it('marks unexported types', () => {
|
|
138
|
+
const result = extractor.extract(sampleGo, 'main.go');
|
|
139
|
+
const internal = result.symbols.find(s => s.name === 'myInternal');
|
|
140
|
+
expect(internal).toBeDefined();
|
|
141
|
+
expect(internal.exported).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
it('extracts functions', () => {
|
|
144
|
+
const result = extractor.extract(sampleGo, 'main.go');
|
|
145
|
+
const fn = result.symbols.find(s => s.name === 'NewServer' && s.kind === 'function');
|
|
146
|
+
expect(fn).toBeDefined();
|
|
147
|
+
expect(fn.exported).toBe(true);
|
|
148
|
+
expect(fn.documentation).toContain('creates a new Server instance');
|
|
149
|
+
});
|
|
150
|
+
it('extracts methods with receivers', () => {
|
|
151
|
+
const result = extractor.extract(sampleGo, 'main.go');
|
|
152
|
+
const method = result.symbols.find(s => s.name === 'Start' && s.kind === 'method');
|
|
153
|
+
expect(method).toBeDefined();
|
|
154
|
+
expect(method.parentName).toBe('Server');
|
|
155
|
+
expect(method.exported).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
it('marks unexported methods', () => {
|
|
158
|
+
const result = extractor.extract(sampleGo, 'main.go');
|
|
159
|
+
const method = result.symbols.find(s => s.name === 'internal' && s.kind === 'method');
|
|
160
|
+
expect(method).toBeDefined();
|
|
161
|
+
expect(method.exported).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
it('extracts variables and constants', () => {
|
|
164
|
+
const result = extractor.extract(sampleGo, 'main.go');
|
|
165
|
+
const v = result.symbols.find(s => s.name === 'DefaultPort');
|
|
166
|
+
expect(v).toBeDefined();
|
|
167
|
+
const c = result.symbols.find(s => s.name === 'Version');
|
|
168
|
+
expect(c).toBeDefined();
|
|
169
|
+
});
|
|
170
|
+
it('sets language to go', () => {
|
|
171
|
+
const result = extractor.extract(sampleGo, 'main.go');
|
|
172
|
+
expect(result.language).toBe('go');
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
// ── Rust ──────────────────────────────────────────────────────────────
|
|
176
|
+
const sampleRust = `
|
|
177
|
+
use std::io::{self, Read};
|
|
178
|
+
use serde::{Serialize, Deserialize};
|
|
179
|
+
|
|
180
|
+
/// A configuration struct
|
|
181
|
+
pub struct Config {
|
|
182
|
+
pub name: String,
|
|
183
|
+
pub version: u32,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
struct InternalState {
|
|
187
|
+
data: Vec<u8>,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/// The main trait for services
|
|
191
|
+
pub trait Service {
|
|
192
|
+
fn handle(&self, request: Request) -> Response;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
impl Service for Config {
|
|
196
|
+
fn handle(&self, request: Request) -> Response {
|
|
197
|
+
unimplemented!()
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
impl Config {
|
|
202
|
+
pub fn new(name: String) -> Self {
|
|
203
|
+
Config { name, version: 1 }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// Process incoming data
|
|
208
|
+
pub async fn process(data: &[u8]) -> io::Result<Vec<u8>> {
|
|
209
|
+
Ok(data.to_vec())
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fn internal_helper() -> bool {
|
|
213
|
+
true
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
pub enum Status {
|
|
217
|
+
Active,
|
|
218
|
+
Inactive,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
macro_rules! log_error {
|
|
222
|
+
($msg:expr) => {
|
|
223
|
+
eprintln!("ERROR: {}", $msg);
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
pub const MAX_SIZE: usize = 1024;
|
|
228
|
+
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
|
229
|
+
pub mod utils;
|
|
230
|
+
`;
|
|
231
|
+
describe('RustExtractor', () => {
|
|
232
|
+
const extractor = new RustExtractor();
|
|
233
|
+
it('extracts use statements', () => {
|
|
234
|
+
const result = extractor.extract(sampleRust, 'lib.rs');
|
|
235
|
+
expect(result.imports).toContain('std::io::{self, Read}');
|
|
236
|
+
expect(result.imports).toContain('serde::{Serialize, Deserialize}');
|
|
237
|
+
});
|
|
238
|
+
it('extracts pub structs', () => {
|
|
239
|
+
const result = extractor.extract(sampleRust, 'lib.rs');
|
|
240
|
+
const cfg = result.symbols.find(s => s.name === 'Config' && s.kind === 'struct');
|
|
241
|
+
expect(cfg).toBeDefined();
|
|
242
|
+
expect(cfg.exported).toBe(true);
|
|
243
|
+
expect(cfg.documentation).toContain('configuration struct');
|
|
244
|
+
});
|
|
245
|
+
it('marks private structs', () => {
|
|
246
|
+
const result = extractor.extract(sampleRust, 'lib.rs');
|
|
247
|
+
const internal = result.symbols.find(s => s.name === 'InternalState');
|
|
248
|
+
expect(internal).toBeDefined();
|
|
249
|
+
expect(internal.exported).toBe(false);
|
|
250
|
+
});
|
|
251
|
+
it('extracts traits', () => {
|
|
252
|
+
const result = extractor.extract(sampleRust, 'lib.rs');
|
|
253
|
+
const trait_ = result.symbols.find(s => s.name === 'Service' && s.kind === 'trait');
|
|
254
|
+
expect(trait_).toBeDefined();
|
|
255
|
+
expect(trait_.exported).toBe(true);
|
|
256
|
+
});
|
|
257
|
+
it('extracts impl blocks', () => {
|
|
258
|
+
const result = extractor.extract(sampleRust, 'lib.rs');
|
|
259
|
+
const impls = result.symbols.filter(s => s.kind === 'impl');
|
|
260
|
+
expect(impls.length).toBeGreaterThanOrEqual(2);
|
|
261
|
+
const traitImpl = impls.find(s => s.name.includes('Service for Config'));
|
|
262
|
+
expect(traitImpl).toBeDefined();
|
|
263
|
+
});
|
|
264
|
+
it('extracts functions', () => {
|
|
265
|
+
const result = extractor.extract(sampleRust, 'lib.rs');
|
|
266
|
+
const fn_ = result.symbols.find(s => s.name === 'process' && s.kind === 'function');
|
|
267
|
+
expect(fn_).toBeDefined();
|
|
268
|
+
expect(fn_.exported).toBe(true);
|
|
269
|
+
expect(fn_.documentation).toContain('Process incoming data');
|
|
270
|
+
});
|
|
271
|
+
it('marks private functions', () => {
|
|
272
|
+
const result = extractor.extract(sampleRust, 'lib.rs');
|
|
273
|
+
const fn_ = result.symbols.find(s => s.name === 'internal_helper');
|
|
274
|
+
expect(fn_).toBeDefined();
|
|
275
|
+
expect(fn_.exported).toBe(false);
|
|
276
|
+
});
|
|
277
|
+
it('extracts enums', () => {
|
|
278
|
+
const result = extractor.extract(sampleRust, 'lib.rs');
|
|
279
|
+
const en = result.symbols.find(s => s.name === 'Status' && s.kind === 'enum');
|
|
280
|
+
expect(en).toBeDefined();
|
|
281
|
+
expect(en.exported).toBe(true);
|
|
282
|
+
});
|
|
283
|
+
it('extracts macros', () => {
|
|
284
|
+
const result = extractor.extract(sampleRust, 'lib.rs');
|
|
285
|
+
const macro_ = result.symbols.find(s => s.name === 'log_error' && s.kind === 'macro');
|
|
286
|
+
expect(macro_).toBeDefined();
|
|
287
|
+
});
|
|
288
|
+
it('extracts const/type/mod', () => {
|
|
289
|
+
const result = extractor.extract(sampleRust, 'lib.rs');
|
|
290
|
+
expect(result.symbols.find(s => s.name === 'MAX_SIZE' && s.kind === 'variable')).toBeDefined();
|
|
291
|
+
expect(result.symbols.find(s => s.name === 'Result' && s.kind === 'type')).toBeDefined();
|
|
292
|
+
expect(result.symbols.find(s => s.name === 'utils' && s.kind === 'module')).toBeDefined();
|
|
293
|
+
});
|
|
294
|
+
it('sets language to rust', () => {
|
|
295
|
+
const result = extractor.extract(sampleRust, 'lib.rs');
|
|
296
|
+
expect(result.language).toBe('rust');
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
// ── Multi-language registration ───────────────────────────────────────
|
|
300
|
+
describe('SymbolExtractor multi-language', () => {
|
|
301
|
+
it('registers additional extractors', () => {
|
|
302
|
+
const extractor = new SymbolExtractor();
|
|
303
|
+
extractor.registerExtractor(new PythonExtractor());
|
|
304
|
+
extractor.registerExtractor(new GoExtractor());
|
|
305
|
+
extractor.registerExtractor(new RustExtractor());
|
|
306
|
+
expect(extractor.supportsLanguage('python')).toBe(true);
|
|
307
|
+
expect(extractor.supportsLanguage('go')).toBe(true);
|
|
308
|
+
expect(extractor.supportsLanguage('rust')).toBe(true);
|
|
309
|
+
});
|
|
310
|
+
it('extracts Python from source', () => {
|
|
311
|
+
const extractor = new SymbolExtractor();
|
|
312
|
+
extractor.registerExtractor(new PythonExtractor());
|
|
313
|
+
const result = extractor.extractFromSource('def hello(): pass', 'test.py');
|
|
314
|
+
expect(result.symbols.length).toBeGreaterThan(0);
|
|
315
|
+
expect(result.language).toBe('python');
|
|
316
|
+
});
|
|
317
|
+
it('extracts Go from source', () => {
|
|
318
|
+
const extractor = new SymbolExtractor();
|
|
319
|
+
extractor.registerExtractor(new GoExtractor());
|
|
320
|
+
const result = extractor.extractFromSource('func main() {}', 'main.go');
|
|
321
|
+
expect(result.symbols.length).toBeGreaterThan(0);
|
|
322
|
+
expect(result.language).toBe('go');
|
|
323
|
+
});
|
|
324
|
+
it('extracts Rust from source', () => {
|
|
325
|
+
const extractor = new SymbolExtractor();
|
|
326
|
+
extractor.registerExtractor(new RustExtractor());
|
|
327
|
+
const result = extractor.extractFromSource('pub fn main() {}', 'main.rs');
|
|
328
|
+
expect(result.symbols.length).toBeGreaterThan(0);
|
|
329
|
+
expect(result.language).toBe('rust');
|
|
330
|
+
});
|
|
331
|
+
it('all languages map to unified SymbolKind types', () => {
|
|
332
|
+
const validKinds = new Set([
|
|
333
|
+
'function', 'class', 'interface', 'type', 'variable', 'method',
|
|
334
|
+
'property', 'import', 'export', 'enum', 'struct', 'trait',
|
|
335
|
+
'impl', 'macro', 'decorator', 'module',
|
|
336
|
+
]);
|
|
337
|
+
const extractor = new SymbolExtractor();
|
|
338
|
+
extractor.registerExtractor(new PythonExtractor());
|
|
339
|
+
extractor.registerExtractor(new GoExtractor());
|
|
340
|
+
extractor.registerExtractor(new RustExtractor());
|
|
341
|
+
const py = extractor.extractFromSource(samplePython, 'test.py');
|
|
342
|
+
const go = extractor.extractFromSource(sampleGo, 'test.go');
|
|
343
|
+
const rs = extractor.extractFromSource(sampleRust, 'test.rs');
|
|
344
|
+
for (const ast of [py, go, rs]) {
|
|
345
|
+
for (const symbol of ast.symbols) {
|
|
346
|
+
expect(validKinds.has(symbol.kind)).toBe(true);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"symbol-extractor.test.d.ts","sourceRoot":"","sources":["../../../../src/parser/__tests__/symbol-extractor.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { SymbolExtractor } from '../symbol-extractor.js';
|
|
3
|
+
import { TypeScriptExtractor } from '../typescript-extractor.js';
|
|
4
|
+
const sampleTS = `
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
import type { Foo } from './foo'
|
|
7
|
+
|
|
8
|
+
/** Configuration interface */
|
|
9
|
+
export interface Config {
|
|
10
|
+
name: string
|
|
11
|
+
version: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type Result<T> = { data: T; error?: string }
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_TIMEOUT = 5000
|
|
17
|
+
|
|
18
|
+
/** Main handler function */
|
|
19
|
+
export async function handleRequest(req: Request): Promise<Response> {
|
|
20
|
+
return new Response('ok')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const processData = (input: string) => {
|
|
24
|
+
return input.trim()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class UserService {
|
|
28
|
+
private db: Database
|
|
29
|
+
|
|
30
|
+
constructor(db: Database) {
|
|
31
|
+
this.db = db
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async getUser(id: string): Promise<User> {
|
|
35
|
+
return this.db.find(id)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async deleteUser(id: string): Promise<void> {
|
|
39
|
+
await this.db.delete(id)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export enum Status {
|
|
44
|
+
Active = 'active',
|
|
45
|
+
Inactive = 'inactive',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@deprecated
|
|
49
|
+
class OldService {}
|
|
50
|
+
`;
|
|
51
|
+
const sampleJS = `
|
|
52
|
+
const express = require('express')
|
|
53
|
+
const { Router } = require('express')
|
|
54
|
+
|
|
55
|
+
function createServer(port) {
|
|
56
|
+
const app = express()
|
|
57
|
+
return app.listen(port)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
class APIClient {
|
|
61
|
+
constructor(baseUrl) {
|
|
62
|
+
this.baseUrl = baseUrl
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async fetch(path) {
|
|
66
|
+
return fetch(this.baseUrl + path)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const helper = (x) => x * 2
|
|
71
|
+
|
|
72
|
+
module.exports = { createServer, APIClient }
|
|
73
|
+
`;
|
|
74
|
+
describe('TypeScriptExtractor', () => {
|
|
75
|
+
const extractor = new TypeScriptExtractor();
|
|
76
|
+
it('extracts imports', () => {
|
|
77
|
+
const result = extractor.extract(sampleTS, 'sample.ts');
|
|
78
|
+
expect(result.imports).toContain('zod');
|
|
79
|
+
expect(result.imports).toContain('./foo');
|
|
80
|
+
});
|
|
81
|
+
it('extracts interface', () => {
|
|
82
|
+
const result = extractor.extract(sampleTS, 'sample.ts');
|
|
83
|
+
const iface = result.symbols.find(s => s.name === 'Config' && s.kind === 'interface');
|
|
84
|
+
expect(iface).toBeDefined();
|
|
85
|
+
expect(iface.exported).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
it('extracts type alias', () => {
|
|
88
|
+
const result = extractor.extract(sampleTS, 'sample.ts');
|
|
89
|
+
const type = result.symbols.find(s => s.name === 'Result' && s.kind === 'type');
|
|
90
|
+
expect(type).toBeDefined();
|
|
91
|
+
expect(type.exported).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
it('extracts exported variable', () => {
|
|
94
|
+
const result = extractor.extract(sampleTS, 'sample.ts');
|
|
95
|
+
const v = result.symbols.find(s => s.name === 'DEFAULT_TIMEOUT' && s.kind === 'variable');
|
|
96
|
+
expect(v).toBeDefined();
|
|
97
|
+
expect(v.exported).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
it('extracts async function with JSDoc', () => {
|
|
100
|
+
const result = extractor.extract(sampleTS, 'sample.ts');
|
|
101
|
+
const fn = result.symbols.find(s => s.name === 'handleRequest' && s.kind === 'function');
|
|
102
|
+
expect(fn).toBeDefined();
|
|
103
|
+
expect(fn.exported).toBe(true);
|
|
104
|
+
expect(fn.documentation).toContain('Main handler function');
|
|
105
|
+
});
|
|
106
|
+
it('extracts arrow function', () => {
|
|
107
|
+
const result = extractor.extract(sampleTS, 'sample.ts');
|
|
108
|
+
const fn = result.symbols.find(s => s.name === 'processData' && s.kind === 'function');
|
|
109
|
+
expect(fn).toBeDefined();
|
|
110
|
+
expect(fn.exported).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
it('extracts class with methods', () => {
|
|
113
|
+
const result = extractor.extract(sampleTS, 'sample.ts');
|
|
114
|
+
const cls = result.symbols.find(s => s.name === 'UserService' && s.kind === 'class');
|
|
115
|
+
expect(cls).toBeDefined();
|
|
116
|
+
expect(cls.exported).toBe(true);
|
|
117
|
+
const methods = result.symbols.filter(s => s.parentName === 'UserService' && s.kind === 'method');
|
|
118
|
+
const methodNames = methods.map(m => m.name);
|
|
119
|
+
expect(methodNames).toContain('getUser');
|
|
120
|
+
expect(methodNames).toContain('deleteUser');
|
|
121
|
+
});
|
|
122
|
+
it('extracts enum', () => {
|
|
123
|
+
const result = extractor.extract(sampleTS, 'sample.ts');
|
|
124
|
+
const en = result.symbols.find(s => s.name === 'Status' && s.kind === 'enum');
|
|
125
|
+
expect(en).toBeDefined();
|
|
126
|
+
});
|
|
127
|
+
it('extracts decorators', () => {
|
|
128
|
+
const result = extractor.extract(sampleTS, 'sample.ts');
|
|
129
|
+
const dec = result.symbols.find(s => s.name === 'deprecated' && s.kind === 'decorator');
|
|
130
|
+
expect(dec).toBeDefined();
|
|
131
|
+
});
|
|
132
|
+
it('tracks exports', () => {
|
|
133
|
+
const result = extractor.extract(sampleTS, 'sample.ts');
|
|
134
|
+
expect(result.exports).toContain('Config');
|
|
135
|
+
expect(result.exports).toContain('handleRequest');
|
|
136
|
+
expect(result.exports).toContain('UserService');
|
|
137
|
+
});
|
|
138
|
+
it('sets correct language for .ts files', () => {
|
|
139
|
+
const result = extractor.extract(sampleTS, 'sample.ts');
|
|
140
|
+
expect(result.language).toBe('typescript');
|
|
141
|
+
});
|
|
142
|
+
it('sets correct language for .js files', () => {
|
|
143
|
+
const result = extractor.extract(sampleJS, 'sample.js');
|
|
144
|
+
expect(result.language).toBe('javascript');
|
|
145
|
+
});
|
|
146
|
+
it('extracts JavaScript functions and classes', () => {
|
|
147
|
+
const result = extractor.extract(sampleJS, 'sample.js');
|
|
148
|
+
const fn = result.symbols.find(s => s.name === 'createServer' && s.kind === 'function');
|
|
149
|
+
expect(fn).toBeDefined();
|
|
150
|
+
const cls = result.symbols.find(s => s.name === 'APIClient' && s.kind === 'class');
|
|
151
|
+
expect(cls).toBeDefined();
|
|
152
|
+
});
|
|
153
|
+
it('extracts JS arrow functions', () => {
|
|
154
|
+
const result = extractor.extract(sampleJS, 'sample.js');
|
|
155
|
+
const fn = result.symbols.find(s => s.name === 'helper' && s.kind === 'function');
|
|
156
|
+
expect(fn).toBeDefined();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
describe('SymbolExtractor', () => {
|
|
160
|
+
const extractor = new SymbolExtractor();
|
|
161
|
+
it('detects typescript language', () => {
|
|
162
|
+
expect(extractor.detectLanguage('src/main.ts')).toBe('typescript');
|
|
163
|
+
expect(extractor.detectLanguage('src/app.tsx')).toBe('typescript');
|
|
164
|
+
});
|
|
165
|
+
it('detects javascript language', () => {
|
|
166
|
+
expect(extractor.detectLanguage('lib/utils.js')).toBe('javascript');
|
|
167
|
+
expect(extractor.detectLanguage('lib/utils.mjs')).toBe('javascript');
|
|
168
|
+
});
|
|
169
|
+
it('detects unknown language', () => {
|
|
170
|
+
expect(extractor.detectLanguage('main.py')).toBe('python');
|
|
171
|
+
expect(extractor.detectLanguage('readme.md')).toBe('unknown');
|
|
172
|
+
});
|
|
173
|
+
it('supports typescript and javascript', () => {
|
|
174
|
+
expect(extractor.supportsLanguage('typescript')).toBe(true);
|
|
175
|
+
expect(extractor.supportsLanguage('javascript')).toBe(true);
|
|
176
|
+
expect(extractor.supportsLanguage('python')).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
it('extracts from source', () => {
|
|
179
|
+
const result = extractor.extractFromSource(sampleTS, 'test.ts');
|
|
180
|
+
expect(result.symbols.length).toBeGreaterThan(0);
|
|
181
|
+
expect(result.language).toBe('typescript');
|
|
182
|
+
});
|
|
183
|
+
it('returns empty for unsupported language', () => {
|
|
184
|
+
const result = extractor.extractFromSource('def hello(): pass', 'test.py');
|
|
185
|
+
expect(result.symbols).toHaveLength(0);
|
|
186
|
+
expect(result.language).toBe('python');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FileAST } from '../types.js';
|
|
2
|
+
import type { LanguageExtractor } from './symbol-extractor.js';
|
|
3
|
+
/** Regex-based Go symbol extractor. */
|
|
4
|
+
export declare class GoExtractor implements LanguageExtractor {
|
|
5
|
+
languages: string[];
|
|
6
|
+
extract(source: string, filePath: string): FileAST;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=go-extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"go-extractor.d.ts","sourceRoot":"","sources":["../../../src/parser/go-extractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,OAAO,EAAc,MAAM,aAAa,CAAA;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAE9D,uCAAuC;AACvC,qBAAa,WAAY,YAAW,iBAAiB;IACnD,SAAS,WAAS;IAElB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;CAgInD"}
|