autosnippet 3.0.3 → 3.0.7
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 +85 -240
- package/dashboard/dist/assets/{icons-Cdq22n2i.js → icons-eQ_rWCus.js} +97 -102
- package/dashboard/dist/assets/index-B3Nnkdxi.js +133 -0
- package/dashboard/dist/assets/index-BFNDAqh3.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/core/AstAnalyzer.js +2 -2
- package/lib/core/ast/ensure-grammars.js +2 -0
- package/lib/core/ast/index.js +8 -0
- package/lib/core/ast/lang-rust.js +695 -0
- package/lib/core/discovery/PythonDiscoverer.js +3 -0
- package/lib/core/discovery/RustDiscoverer.js +467 -0
- package/lib/core/discovery/index.js +3 -0
- package/lib/core/enhancement/django-enhancement.js +169 -3
- package/lib/core/enhancement/fastapi-enhancement.js +149 -3
- package/lib/core/enhancement/go-grpc-enhancement.js +4 -0
- package/lib/core/enhancement/go-web-enhancement.js +6 -0
- package/lib/core/enhancement/index.js +5 -0
- package/lib/core/enhancement/langchain-enhancement.js +233 -0
- package/lib/core/enhancement/ml-enhancement.js +265 -0
- package/lib/core/enhancement/nextjs-enhancement.js +219 -0
- package/lib/core/enhancement/node-server-enhancement.js +178 -4
- package/lib/core/enhancement/react-enhancement.js +165 -4
- package/lib/core/enhancement/rust-tokio-enhancement.js +231 -0
- package/lib/core/enhancement/rust-web-enhancement.js +256 -0
- package/lib/core/enhancement/spring-enhancement.js +2 -0
- package/lib/core/enhancement/vue-enhancement.js +143 -2
- package/lib/external/ai/AiProvider.js +45 -6
- package/lib/external/mcp/handlers/bootstrap/skills.js +2 -0
- package/lib/external/mcp/handlers/bootstrap.js +33 -9
- package/lib/external/mcp/handlers/guard.js +42 -0
- package/lib/http/routes/candidates.js +7 -1
- package/lib/service/chat/ChatAgent.js +1 -0
- package/lib/service/chat/tools.js +5 -1
- package/lib/service/guard/ComplianceReporter.js +20 -7
- package/lib/service/guard/GuardCheckEngine.js +156 -5
- package/lib/service/guard/SourceFileCollector.js +15 -0
- package/package.json +28 -6
- package/skills/autosnippet-coldstart/SKILL.md +4 -2
- package/skills/autosnippet-concepts/SKILL.md +5 -3
- package/skills/autosnippet-reference-rust/SKILL.md +401 -0
- package/skills/autosnippet-structure/SKILL.md +1 -1
- package/templates/recipes-setup/README.md +2 -2
- package/templates/recipes-setup/_template.md +1 -1
- package/dashboard/dist/assets/index-ClkyPkDX.js +0 -133
- package/dashboard/dist/assets/index-t4QrJwv1.css +0 -1
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module lang-rust
|
|
3
|
+
* @description Rust AST Walker 插件
|
|
4
|
+
*
|
|
5
|
+
* 提取: struct, enum, trait, impl, function, method, mod, use, const/static
|
|
6
|
+
* 模式: Builder, Newtype, Factory (new/from), Error Handling (Result/Option/?),
|
|
7
|
+
* Async (tokio/async-std), Unsafe block, Derive macro
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
|
|
14
|
+
function walkRust(root, ctx) {
|
|
15
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
16
|
+
const child = root.namedChild(i);
|
|
17
|
+
_walkNode(child, ctx);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function _walkNode(node, ctx) {
|
|
22
|
+
switch (node.type) {
|
|
23
|
+
case 'use_declaration': {
|
|
24
|
+
_parseUseDecl(node, ctx);
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
case 'mod_item': {
|
|
29
|
+
_parseModItem(node, ctx);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
case 'struct_item': {
|
|
34
|
+
_parseStruct(node, ctx);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
case 'enum_item': {
|
|
39
|
+
_parseEnum(node, ctx);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
case 'trait_item': {
|
|
44
|
+
_parseTrait(node, ctx);
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
case 'impl_item': {
|
|
49
|
+
_parseImpl(node, ctx);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
case 'function_item': {
|
|
54
|
+
const funcInfo = _parseFunctionItem(node);
|
|
55
|
+
if (funcInfo) {
|
|
56
|
+
ctx.methods.push(funcInfo);
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
case 'const_item':
|
|
62
|
+
case 'static_item': {
|
|
63
|
+
_parseConstStatic(node, ctx);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
case 'type_item': {
|
|
68
|
+
_parseTypeAlias(node, ctx);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
case 'macro_definition': {
|
|
73
|
+
_parseMacroDef(node, ctx);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 带 attribute 的顶层项
|
|
78
|
+
case 'attribute_item':
|
|
79
|
+
case 'inner_attribute_item':
|
|
80
|
+
break;
|
|
81
|
+
|
|
82
|
+
default:
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Use Declaration ──────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
function _parseUseDecl(node, ctx) {
|
|
90
|
+
// 提取 use 路径文本
|
|
91
|
+
const argNode = node.namedChildren.find(
|
|
92
|
+
(c) => c.type === 'use_wildcard' ||
|
|
93
|
+
c.type === 'use_list' ||
|
|
94
|
+
c.type === 'use_as_clause' ||
|
|
95
|
+
c.type === 'scoped_identifier' ||
|
|
96
|
+
c.type === 'identifier' ||
|
|
97
|
+
c.type === 'scoped_use_list'
|
|
98
|
+
);
|
|
99
|
+
if (argNode) {
|
|
100
|
+
ctx.imports.push(argNode.text);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Mod Item ─────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
function _parseModItem(node, ctx) {
|
|
107
|
+
const nameNode = node.namedChildren.find((c) => c.type === 'identifier');
|
|
108
|
+
if (nameNode) {
|
|
109
|
+
ctx.metadata = ctx.metadata || {};
|
|
110
|
+
ctx.metadata.modules = ctx.metadata.modules || [];
|
|
111
|
+
ctx.metadata.modules.push(nameNode.text);
|
|
112
|
+
}
|
|
113
|
+
// 如果是 inline mod { ... },递归内部声明
|
|
114
|
+
const body = node.namedChildren.find((c) => c.type === 'declaration_list');
|
|
115
|
+
if (body) {
|
|
116
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
117
|
+
_walkNode(body.namedChild(i), ctx);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Struct ───────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
function _parseStruct(node, ctx) {
|
|
125
|
+
const nameNode = node.namedChildren.find((c) => c.type === 'type_identifier');
|
|
126
|
+
const name = nameNode?.text || 'Unknown';
|
|
127
|
+
|
|
128
|
+
const fields = [];
|
|
129
|
+
const derives = _extractDerives(node);
|
|
130
|
+
|
|
131
|
+
// Named fields (struct Foo { field: Type })
|
|
132
|
+
const fieldList = node.namedChildren.find((c) => c.type === 'field_declaration_list');
|
|
133
|
+
if (fieldList) {
|
|
134
|
+
for (let i = 0; i < fieldList.namedChildCount; i++) {
|
|
135
|
+
const field = fieldList.namedChild(i);
|
|
136
|
+
if (field.type !== 'field_declaration') continue;
|
|
137
|
+
const fieldId = field.namedChildren.find((c) => c.type === 'field_identifier');
|
|
138
|
+
if (fieldId) {
|
|
139
|
+
const isPublic = _hasPubVisibility(field);
|
|
140
|
+
ctx.properties.push({
|
|
141
|
+
name: fieldId.text,
|
|
142
|
+
className: name,
|
|
143
|
+
isExported: isPublic,
|
|
144
|
+
line: field.startPosition.row + 1,
|
|
145
|
+
});
|
|
146
|
+
fields.push(fieldId.text);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Tuple struct fields (struct Foo(Type1, Type2))
|
|
152
|
+
const orderedFields = node.namedChildren.find((c) => c.type === 'ordered_field_declaration_list');
|
|
153
|
+
if (orderedFields) {
|
|
154
|
+
let idx = 0;
|
|
155
|
+
for (let i = 0; i < orderedFields.namedChildCount; i++) {
|
|
156
|
+
const child = orderedFields.namedChild(i);
|
|
157
|
+
// Skip visibility markers
|
|
158
|
+
if (child.type === 'visibility_modifier') continue;
|
|
159
|
+
if (child.type.includes('type') || child.type === 'primitive_type' ||
|
|
160
|
+
child.type === 'scoped_type_identifier' || child.type === 'type_identifier' ||
|
|
161
|
+
child.type === 'generic_type' || child.type === 'reference_type') {
|
|
162
|
+
fields.push(`${idx}`);
|
|
163
|
+
idx++;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
ctx.classes.push({
|
|
169
|
+
name,
|
|
170
|
+
kind: 'struct',
|
|
171
|
+
superclass: null,
|
|
172
|
+
protocols: [],
|
|
173
|
+
derives,
|
|
174
|
+
fieldCount: fields.length,
|
|
175
|
+
line: node.startPosition.row + 1,
|
|
176
|
+
endLine: node.endPosition.row + 1,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── Enum ─────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
function _parseEnum(node, ctx) {
|
|
183
|
+
const nameNode = node.namedChildren.find((c) => c.type === 'type_identifier');
|
|
184
|
+
const name = nameNode?.text || 'Unknown';
|
|
185
|
+
const derives = _extractDerives(node);
|
|
186
|
+
|
|
187
|
+
const variants = [];
|
|
188
|
+
const body = node.namedChildren.find((c) => c.type === 'enum_variant_list');
|
|
189
|
+
if (body) {
|
|
190
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
191
|
+
const variant = body.namedChild(i);
|
|
192
|
+
if (variant.type === 'enum_variant') {
|
|
193
|
+
const variantName = variant.namedChildren.find((c) => c.type === 'identifier');
|
|
194
|
+
if (variantName) {
|
|
195
|
+
variants.push(variantName.text);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
ctx.classes.push({
|
|
202
|
+
name,
|
|
203
|
+
kind: 'enum',
|
|
204
|
+
superclass: null,
|
|
205
|
+
protocols: [],
|
|
206
|
+
derives,
|
|
207
|
+
variants,
|
|
208
|
+
variantCount: variants.length,
|
|
209
|
+
line: node.startPosition.row + 1,
|
|
210
|
+
endLine: node.endPosition.row + 1,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ── Trait ─────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
function _parseTrait(node, ctx) {
|
|
217
|
+
const nameNode = node.namedChildren.find((c) => c.type === 'type_identifier');
|
|
218
|
+
const name = nameNode?.text || 'Unknown';
|
|
219
|
+
|
|
220
|
+
const methods = [];
|
|
221
|
+
const superTraits = [];
|
|
222
|
+
|
|
223
|
+
// Trait bounds (trait Foo: Bar + Baz)
|
|
224
|
+
const bounds = node.namedChildren.find((c) => c.type === 'trait_bounds');
|
|
225
|
+
if (bounds) {
|
|
226
|
+
for (let i = 0; i < bounds.namedChildCount; i++) {
|
|
227
|
+
const bound = bounds.namedChild(i);
|
|
228
|
+
if (bound.type === 'type_identifier' || bound.type === 'scoped_type_identifier' ||
|
|
229
|
+
bound.type === 'generic_type') {
|
|
230
|
+
superTraits.push(bound.text);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Trait body — collect method signatures
|
|
236
|
+
const body = node.namedChildren.find((c) => c.type === 'declaration_list');
|
|
237
|
+
if (body) {
|
|
238
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
239
|
+
const item = body.namedChild(i);
|
|
240
|
+
if (item.type === 'function_signature_item' || item.type === 'function_item') {
|
|
241
|
+
const methodName = item.namedChildren.find((c) => c.type === 'identifier');
|
|
242
|
+
if (methodName) {
|
|
243
|
+
methods.push(methodName.text);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
ctx.protocols.push({
|
|
250
|
+
name,
|
|
251
|
+
inherits: superTraits,
|
|
252
|
+
methods,
|
|
253
|
+
line: node.startPosition.row + 1,
|
|
254
|
+
endLine: node.endPosition.row + 1,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ── Impl Block ───────────────────────────────────────────────
|
|
259
|
+
|
|
260
|
+
function _parseImpl(node, ctx) {
|
|
261
|
+
// impl Type { ... } 或 impl Trait for Type { ... }
|
|
262
|
+
let selfType = null;
|
|
263
|
+
let traitName = null;
|
|
264
|
+
|
|
265
|
+
const typeIdNodes = node.namedChildren.filter(
|
|
266
|
+
(c) => c.type === 'type_identifier' || c.type === 'scoped_type_identifier' ||
|
|
267
|
+
c.type === 'generic_type'
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
// 检查是否有 "for" — trait impl
|
|
271
|
+
const hasFor = node.children?.some((c) => c.type === 'for');
|
|
272
|
+
|
|
273
|
+
if (hasFor && typeIdNodes.length >= 2) {
|
|
274
|
+
traitName = typeIdNodes[0]?.text;
|
|
275
|
+
selfType = typeIdNodes[1]?.text;
|
|
276
|
+
} else if (typeIdNodes.length >= 1) {
|
|
277
|
+
selfType = typeIdNodes[0]?.text;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const body = node.namedChildren.find((c) => c.type === 'declaration_list');
|
|
281
|
+
if (!body || !selfType) return;
|
|
282
|
+
|
|
283
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
284
|
+
const item = body.namedChild(i);
|
|
285
|
+
if (item.type === 'function_item') {
|
|
286
|
+
const methodInfo = _parseImplMethod(item, selfType, traitName);
|
|
287
|
+
if (methodInfo) {
|
|
288
|
+
ctx.methods.push(methodInfo);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function _parseImplMethod(node, selfType, traitName) {
|
|
295
|
+
const nameNode = node.namedChildren.find((c) => c.type === 'identifier');
|
|
296
|
+
const name = nameNode?.text;
|
|
297
|
+
if (!name) return null;
|
|
298
|
+
|
|
299
|
+
const params = node.namedChildren.find((c) => c.type === 'parameters');
|
|
300
|
+
const { paramCount, hasSelfParam } = params ? _countRustParams(params) : { paramCount: 0, hasSelfParam: false };
|
|
301
|
+
|
|
302
|
+
const isPublic = _hasPubVisibility(node);
|
|
303
|
+
const isAsync = node.children?.some((c) => c.text === 'async') || false;
|
|
304
|
+
|
|
305
|
+
const body = node.namedChildren.find((c) => c.type === 'block');
|
|
306
|
+
const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
|
|
307
|
+
const complexity = body ? _estimateComplexity(body) : 1;
|
|
308
|
+
const nestingDepth = body ? _maxNesting(body, 0) : 0;
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
name,
|
|
312
|
+
className: selfType,
|
|
313
|
+
isClassMethod: !hasSelfParam, // 无 self → associated function (static)
|
|
314
|
+
isExported: isPublic,
|
|
315
|
+
isAsync,
|
|
316
|
+
traitImpl: traitName || null,
|
|
317
|
+
paramCount,
|
|
318
|
+
bodyLines,
|
|
319
|
+
complexity,
|
|
320
|
+
nestingDepth,
|
|
321
|
+
line: node.startPosition.row + 1,
|
|
322
|
+
kind: 'definition',
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ── Function Item (free fn) ──────────────────────────────────
|
|
327
|
+
|
|
328
|
+
function _parseFunctionItem(node) {
|
|
329
|
+
const nameNode = node.namedChildren.find((c) => c.type === 'identifier');
|
|
330
|
+
const name = nameNode?.text;
|
|
331
|
+
if (!name) return null;
|
|
332
|
+
|
|
333
|
+
const params = node.namedChildren.find((c) => c.type === 'parameters');
|
|
334
|
+
const { paramCount } = params ? _countRustParams(params) : { paramCount: 0 };
|
|
335
|
+
|
|
336
|
+
const isPublic = _hasPubVisibility(node);
|
|
337
|
+
const isAsync = node.children?.some((c) => c.text === 'async') || false;
|
|
338
|
+
const isUnsafe = node.children?.some((c) => c.text === 'unsafe') || false;
|
|
339
|
+
|
|
340
|
+
const body = node.namedChildren.find((c) => c.type === 'block');
|
|
341
|
+
const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
|
|
342
|
+
const complexity = body ? _estimateComplexity(body) : 1;
|
|
343
|
+
const nestingDepth = body ? _maxNesting(body, 0) : 0;
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
name,
|
|
347
|
+
className: null,
|
|
348
|
+
isClassMethod: true, // free function → "static" equivalent
|
|
349
|
+
isExported: isPublic,
|
|
350
|
+
isAsync,
|
|
351
|
+
isUnsafe,
|
|
352
|
+
paramCount,
|
|
353
|
+
bodyLines,
|
|
354
|
+
complexity,
|
|
355
|
+
nestingDepth,
|
|
356
|
+
line: node.startPosition.row + 1,
|
|
357
|
+
kind: 'definition',
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ── Const / Static ───────────────────────────────────────────
|
|
362
|
+
|
|
363
|
+
function _parseConstStatic(node, ctx) {
|
|
364
|
+
const isConst = node.type === 'const_item';
|
|
365
|
+
const nameNode = node.namedChildren.find((c) => c.type === 'identifier');
|
|
366
|
+
if (nameNode) {
|
|
367
|
+
ctx.properties.push({
|
|
368
|
+
name: nameNode.text,
|
|
369
|
+
className: null,
|
|
370
|
+
isExported: _hasPubVisibility(node),
|
|
371
|
+
isConst,
|
|
372
|
+
isStatic: !isConst,
|
|
373
|
+
line: node.startPosition.row + 1,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ── Type Alias ───────────────────────────────────────────────
|
|
379
|
+
|
|
380
|
+
function _parseTypeAlias(node, ctx) {
|
|
381
|
+
const nameNode = node.namedChildren.find((c) => c.type === 'type_identifier');
|
|
382
|
+
if (nameNode) {
|
|
383
|
+
ctx.classes.push({
|
|
384
|
+
name: nameNode.text,
|
|
385
|
+
kind: 'type-alias',
|
|
386
|
+
line: node.startPosition.row + 1,
|
|
387
|
+
endLine: node.endPosition.row + 1,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ── Macro Definition ─────────────────────────────────────────
|
|
393
|
+
|
|
394
|
+
function _parseMacroDef(node, ctx) {
|
|
395
|
+
const nameNode = node.namedChildren.find((c) => c.type === 'identifier');
|
|
396
|
+
if (nameNode) {
|
|
397
|
+
ctx.metadata = ctx.metadata || {};
|
|
398
|
+
ctx.metadata.macros = ctx.metadata.macros || [];
|
|
399
|
+
ctx.metadata.macros.push({
|
|
400
|
+
name: nameNode.text,
|
|
401
|
+
line: node.startPosition.row + 1,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ── Rust Pattern Detection ───────────────────────────────────
|
|
407
|
+
|
|
408
|
+
function detectRustPatterns(root, lang, methods, properties, classes) {
|
|
409
|
+
const patterns = [];
|
|
410
|
+
|
|
411
|
+
// 构建 type → methods 索引
|
|
412
|
+
const typeMethodMap = {};
|
|
413
|
+
for (const m of methods) {
|
|
414
|
+
if (m.className) {
|
|
415
|
+
if (!typeMethodMap[m.className]) {
|
|
416
|
+
typeMethodMap[m.className] = [];
|
|
417
|
+
}
|
|
418
|
+
typeMethodMap[m.className].push(m);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Builder pattern: struct 有 builder() 或一系列链式 with_*/set_* 方法
|
|
423
|
+
for (const [typeName, methodList] of Object.entries(typeMethodMap)) {
|
|
424
|
+
const hasBuilder = methodList.some((m) => m.name === 'builder' || m.name === 'build');
|
|
425
|
+
const chainMethods = methodList.filter((m) =>
|
|
426
|
+
/^(?:with_|set_|add_)/.test(m.name)
|
|
427
|
+
);
|
|
428
|
+
if (hasBuilder || chainMethods.length >= 3) {
|
|
429
|
+
patterns.push({
|
|
430
|
+
type: 'builder',
|
|
431
|
+
className: typeName,
|
|
432
|
+
confidence: hasBuilder ? 0.9 : 0.7,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Factory: new() / from() / create() associated functions
|
|
438
|
+
for (const m of methods) {
|
|
439
|
+
if (
|
|
440
|
+
m.className &&
|
|
441
|
+
m.isClassMethod && // associated function (no self)
|
|
442
|
+
/^(?:new|from|create|open|connect|with_capacity|default)$/.test(m.name)
|
|
443
|
+
) {
|
|
444
|
+
patterns.push({
|
|
445
|
+
type: 'factory',
|
|
446
|
+
className: m.className,
|
|
447
|
+
methodName: m.name,
|
|
448
|
+
line: m.line,
|
|
449
|
+
confidence: 0.85,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Newtype pattern: struct with single field
|
|
455
|
+
for (const cls of classes) {
|
|
456
|
+
if (cls.kind === 'struct' && cls.fieldCount === 1) {
|
|
457
|
+
patterns.push({
|
|
458
|
+
type: 'newtype',
|
|
459
|
+
className: cls.name,
|
|
460
|
+
line: cls.line,
|
|
461
|
+
confidence: 0.75,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Error enum pattern: enum with Error/Err suffix or derives thiserror
|
|
467
|
+
for (const cls of classes) {
|
|
468
|
+
if (cls.kind === 'enum' && /(?:Error|Err)$/.test(cls.name)) {
|
|
469
|
+
patterns.push({
|
|
470
|
+
type: 'error-enum',
|
|
471
|
+
className: cls.name,
|
|
472
|
+
variantCount: cls.variantCount || 0,
|
|
473
|
+
line: cls.line,
|
|
474
|
+
confidence: 0.9,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Trait impl richness: types with many methods suggest impl-heavy design
|
|
480
|
+
for (const [typeName, methodList] of Object.entries(typeMethodMap)) {
|
|
481
|
+
if (methodList.length >= 3) {
|
|
482
|
+
const traitImpls = new Set(methodList.filter((m) => m.traitImpl).map((m) => m.traitImpl));
|
|
483
|
+
patterns.push({
|
|
484
|
+
type: 'impl-rich',
|
|
485
|
+
className: typeName,
|
|
486
|
+
methodCount: methodList.length,
|
|
487
|
+
traitImplCount: traitImpls.size,
|
|
488
|
+
confidence: 0.7,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Unsafe usage
|
|
494
|
+
_detectUnsafe(root, patterns);
|
|
495
|
+
|
|
496
|
+
// Async usage
|
|
497
|
+
_detectAsync(root, patterns);
|
|
498
|
+
|
|
499
|
+
// Derive macro analysis
|
|
500
|
+
_detectDerives(classes, patterns);
|
|
501
|
+
|
|
502
|
+
return patterns;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function _detectUnsafe(root, patterns) {
|
|
506
|
+
let count = 0;
|
|
507
|
+
function walk(node) {
|
|
508
|
+
if (node.type === 'unsafe_block') {
|
|
509
|
+
count++;
|
|
510
|
+
}
|
|
511
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
512
|
+
walk(node.namedChild(i));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
walk(root);
|
|
516
|
+
if (count > 0) {
|
|
517
|
+
patterns.push({
|
|
518
|
+
type: 'unsafe',
|
|
519
|
+
count,
|
|
520
|
+
confidence: 0.95,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function _detectAsync(root, patterns) {
|
|
526
|
+
let asyncFnCount = 0;
|
|
527
|
+
let awaitCount = 0;
|
|
528
|
+
function walk(node) {
|
|
529
|
+
if (node.type === 'function_item' || node.type === 'function_signature_item') {
|
|
530
|
+
const isAsync = node.children?.some((c) => c.text === 'async');
|
|
531
|
+
if (isAsync) asyncFnCount++;
|
|
532
|
+
}
|
|
533
|
+
if (node.type === 'await_expression') {
|
|
534
|
+
awaitCount++;
|
|
535
|
+
}
|
|
536
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
537
|
+
walk(node.namedChild(i));
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
walk(root);
|
|
541
|
+
if (asyncFnCount > 0 || awaitCount > 0) {
|
|
542
|
+
patterns.push({
|
|
543
|
+
type: 'async',
|
|
544
|
+
asyncFunctions: asyncFnCount,
|
|
545
|
+
awaitExpressions: awaitCount,
|
|
546
|
+
confidence: 0.9,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function _detectDerives(classes, patterns) {
|
|
552
|
+
const deriveCounts = {};
|
|
553
|
+
for (const cls of classes) {
|
|
554
|
+
if (cls.derives) {
|
|
555
|
+
for (const d of cls.derives) {
|
|
556
|
+
deriveCounts[d] = (deriveCounts[d] || 0) + 1;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
const commonDerives = Object.entries(deriveCounts)
|
|
561
|
+
.filter(([, count]) => count >= 2)
|
|
562
|
+
.map(([name, count]) => ({ name, count }));
|
|
563
|
+
|
|
564
|
+
if (commonDerives.length > 0) {
|
|
565
|
+
patterns.push({
|
|
566
|
+
type: 'derive-usage',
|
|
567
|
+
derives: commonDerives,
|
|
568
|
+
confidence: 0.8,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// ── Helper: Extract #[derive(...)] ──────────────────────────
|
|
574
|
+
|
|
575
|
+
function _extractDerives(node) {
|
|
576
|
+
const derives = [];
|
|
577
|
+
// Look at preceding siblings (attribute_item nodes)
|
|
578
|
+
if (node.parent) {
|
|
579
|
+
const siblings = [];
|
|
580
|
+
for (let i = 0; i < node.parent.namedChildCount; i++) {
|
|
581
|
+
const sib = node.parent.namedChild(i);
|
|
582
|
+
if (sib === node) break;
|
|
583
|
+
siblings.push(sib);
|
|
584
|
+
}
|
|
585
|
+
// Collect attribute_item nodes immediately before this node
|
|
586
|
+
for (let i = siblings.length - 1; i >= 0; i--) {
|
|
587
|
+
const sib = siblings[i];
|
|
588
|
+
if (sib.type !== 'attribute_item') break;
|
|
589
|
+
const text = sib.text;
|
|
590
|
+
const deriveMatch = text.match(/#\[derive\(([^)]+)\)\]/);
|
|
591
|
+
if (deriveMatch) {
|
|
592
|
+
const items = deriveMatch[1].split(',').map((s) => s.trim());
|
|
593
|
+
derives.push(...items);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return derives;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ── Helper: Visibility ──────────────────────────────────────
|
|
601
|
+
|
|
602
|
+
function _hasPubVisibility(node) {
|
|
603
|
+
return node.children?.some(
|
|
604
|
+
(c) => c.type === 'visibility_modifier' || c.text === 'pub'
|
|
605
|
+
) || false;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// ── Helper: Count Parameters ────────────────────────────────
|
|
609
|
+
|
|
610
|
+
function _countRustParams(paramList) {
|
|
611
|
+
let paramCount = 0;
|
|
612
|
+
let hasSelfParam = false;
|
|
613
|
+
|
|
614
|
+
for (let i = 0; i < paramList.namedChildCount; i++) {
|
|
615
|
+
const child = paramList.namedChild(i);
|
|
616
|
+
if (child.type === 'self_parameter') {
|
|
617
|
+
hasSelfParam = true;
|
|
618
|
+
// Don't count self in paramCount
|
|
619
|
+
} else if (child.type === 'parameter') {
|
|
620
|
+
paramCount++;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return { paramCount, hasSelfParam };
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// ── Utility: Complexity ─────────────────────────────────────
|
|
627
|
+
|
|
628
|
+
function _estimateComplexity(node) {
|
|
629
|
+
let complexity = 1;
|
|
630
|
+
const BRANCH_TYPES = new Set([
|
|
631
|
+
'if_expression',
|
|
632
|
+
'if_let_expression',
|
|
633
|
+
'for_expression',
|
|
634
|
+
'while_expression',
|
|
635
|
+
'while_let_expression',
|
|
636
|
+
'loop_expression',
|
|
637
|
+
'match_expression',
|
|
638
|
+
'match_arm',
|
|
639
|
+
]);
|
|
640
|
+
function walk(n) {
|
|
641
|
+
if (BRANCH_TYPES.has(n.type)) {
|
|
642
|
+
complexity++;
|
|
643
|
+
}
|
|
644
|
+
if (n.type === 'binary_expression') {
|
|
645
|
+
const op = n.children?.find((c) => c.text === '&&' || c.text === '||');
|
|
646
|
+
if (op) {
|
|
647
|
+
complexity++;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
651
|
+
walk(n.namedChild(i));
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
walk(node);
|
|
655
|
+
return complexity;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function _maxNesting(node, depth) {
|
|
659
|
+
const NESTING_TYPES = new Set([
|
|
660
|
+
'if_expression',
|
|
661
|
+
'if_let_expression',
|
|
662
|
+
'for_expression',
|
|
663
|
+
'while_expression',
|
|
664
|
+
'while_let_expression',
|
|
665
|
+
'loop_expression',
|
|
666
|
+
'match_expression',
|
|
667
|
+
'block',
|
|
668
|
+
]);
|
|
669
|
+
let max = depth;
|
|
670
|
+
const nextDepth = NESTING_TYPES.has(node.type) ? depth + 1 : depth;
|
|
671
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
672
|
+
const childMax = _maxNesting(node.namedChild(i), nextDepth);
|
|
673
|
+
if (childMax > max) {
|
|
674
|
+
max = childMax;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return max;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// ── Plugin Export ────────────────────────────────────────────
|
|
681
|
+
|
|
682
|
+
let _grammar = null;
|
|
683
|
+
function getGrammar() {
|
|
684
|
+
if (!_grammar) {
|
|
685
|
+
_grammar = require('tree-sitter-rust');
|
|
686
|
+
}
|
|
687
|
+
return _grammar;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
export const plugin = {
|
|
691
|
+
getGrammar,
|
|
692
|
+
walk: walkRust,
|
|
693
|
+
detectPatterns: detectRustPatterns,
|
|
694
|
+
extensions: ['.rs'],
|
|
695
|
+
};
|
|
@@ -214,6 +214,9 @@ export class PythonDiscoverer extends ProjectDiscoverer {
|
|
|
214
214
|
if (deps.has('fastapi')) {
|
|
215
215
|
return 'fastapi';
|
|
216
216
|
}
|
|
217
|
+
if (deps.has('langchain') || deps.has('langchain-core') || deps.has('langgraph') || deps.has('llama-index') || deps.has('llama_index')) {
|
|
218
|
+
return 'langchain';
|
|
219
|
+
}
|
|
217
220
|
if (deps.has('torch') || deps.has('tensorflow')) {
|
|
218
221
|
return 'ml';
|
|
219
222
|
}
|