autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
910 lines (909 loc) • 34.2 kB
JavaScript
/**
* @module lang-rust
* @description Rust AST Walker 插件
*
* 提取: struct, enum, trait, impl, function, method, mod, use, const/static
* 模式: Builder, Newtype, Factory (new/from), Error Handling (Result/Option/?),
* Async (tokio/async-std), Unsafe block, Derive macro
*
* Phase 5: 新增 ImportRecord 结构化导入 + extractCallSites 调用点提取
*/
import { ImportRecord } from '../analysis/ImportRecord.js';
function walkRust(root, ctx) {
for (let i = 0; i < root.namedChildCount; i++) {
const child = root.namedChild(i);
_walkNode(child, ctx);
}
}
function _walkNode(node, ctx) {
switch (node.type) {
case 'use_declaration': {
_parseUseDecl(node, ctx);
break;
}
case 'mod_item': {
_parseModItem(node, ctx);
break;
}
case 'struct_item': {
_parseStruct(node, ctx);
break;
}
case 'enum_item': {
_parseEnum(node, ctx);
break;
}
case 'trait_item': {
_parseTrait(node, ctx);
break;
}
case 'impl_item': {
_parseImpl(node, ctx);
break;
}
case 'function_item': {
const funcInfo = _parseFunctionItem(node);
if (funcInfo) {
ctx.methods.push(funcInfo);
}
break;
}
case 'const_item':
case 'static_item': {
_parseConstStatic(node, ctx);
break;
}
case 'type_item': {
_parseTypeAlias(node, ctx);
break;
}
case 'macro_definition': {
_parseMacroDef(node, ctx);
break;
}
// 带 attribute 的顶层项
case 'attribute_item':
case 'inner_attribute_item':
break;
default:
break;
}
}
// ── Use Declaration ──────────────────────────────────────────
function _parseUseDecl(node, ctx) {
const argNode = node.namedChildren.find((c) => c.type === 'use_wildcard' ||
c.type === 'use_list' ||
c.type === 'use_as_clause' ||
c.type === 'scoped_identifier' ||
c.type === 'identifier' ||
c.type === 'scoped_use_list');
if (!argNode) {
return;
}
const text = argNode.text;
if (argNode.type === 'use_as_clause') {
// use crate::mod::Foo as Bar
const pathNode = argNode.namedChildren.find((c) => c.type === 'scoped_identifier' || c.type === 'identifier');
const aliasNode = argNode.namedChildren.find((c) => c.type === 'identifier' && c !== pathNode);
const fullPath = pathNode?.text || text;
const segments = fullPath.split('::');
const lastName = segments[segments.length - 1];
ctx.imports.push(new ImportRecord(fullPath, {
symbols: [lastName],
alias: aliasNode?.text || lastName,
kind: 'named',
}));
}
else if (argNode.type === 'use_wildcard') {
// use crate::mod::*
const pathPart = text.replace(/::\*$/, '');
ctx.imports.push(new ImportRecord(pathPart, { symbols: ['*'], kind: 'namespace' }));
}
else if (argNode.type === 'use_list' || argNode.type === 'scoped_use_list') {
// use crate::mod::{A, B, C} or use {A, B}
// Extract path prefix and symbol list from text
const match = text.match(/^(.+)::\{(.+)\}$/s);
if (match) {
const prefix = match[1];
const symbolsStr = match[2];
const symbols = symbolsStr
.split(',')
.map((s) => s.trim().split('::').pop().split(' as ')[0].trim())
.filter(Boolean);
ctx.imports.push(new ImportRecord(prefix, { symbols, kind: 'named' }));
}
else {
ctx.imports.push(new ImportRecord(text));
}
}
else if (argNode.type === 'scoped_identifier') {
// use crate::mod::Struct
const segments = text.split('::');
const lastName = segments[segments.length - 1];
const prefix = segments.slice(0, -1).join('::');
ctx.imports.push(new ImportRecord(prefix || text, { symbols: [lastName], alias: lastName, kind: 'named' }));
}
else {
// use identifier (rare: e.g. `use std;`)
ctx.imports.push(new ImportRecord(text, { symbols: ['*'], alias: text, kind: 'namespace' }));
}
}
// ── Mod Item ─────────────────────────────────────────────────
function _parseModItem(node, ctx) {
const nameNode = node.namedChildren.find((c) => c.type === 'identifier');
if (nameNode) {
ctx.metadata = ctx.metadata || {};
ctx.metadata.modules = ctx.metadata.modules || [];
ctx.metadata.modules.push(nameNode.text);
}
// 如果是 inline mod { ... },递归内部声明
const body = node.namedChildren.find((c) => c.type === 'declaration_list');
if (body) {
for (let i = 0; i < body.namedChildCount; i++) {
_walkNode(body.namedChild(i), ctx);
}
}
}
// ── Struct ───────────────────────────────────────────────────
function _parseStruct(node, ctx) {
const nameNode = node.namedChildren.find((c) => c.type === 'type_identifier');
const name = nameNode?.text || 'Unknown';
const fields = [];
const derives = _extractDerives(node);
// Named fields (struct Foo { field: Type })
const fieldList = node.namedChildren.find((c) => c.type === 'field_declaration_list');
if (fieldList) {
for (let i = 0; i < fieldList.namedChildCount; i++) {
const field = fieldList.namedChild(i);
if (field.type !== 'field_declaration') {
continue;
}
const fieldId = field.namedChildren.find((c) => c.type === 'field_identifier');
if (fieldId) {
const isPublic = _hasPubVisibility(field);
ctx.properties.push({
name: fieldId.text,
className: name,
isExported: isPublic,
line: field.startPosition.row + 1,
});
fields.push(fieldId.text);
}
}
}
// Tuple struct fields (struct Foo(Type1, Type2))
const orderedFields = node.namedChildren.find((c) => c.type === 'ordered_field_declaration_list');
if (orderedFields) {
let idx = 0;
for (let i = 0; i < orderedFields.namedChildCount; i++) {
const child = orderedFields.namedChild(i);
// Skip visibility markers
if (child.type === 'visibility_modifier') {
continue;
}
if (child.type.includes('type') ||
child.type === 'primitive_type' ||
child.type === 'scoped_type_identifier' ||
child.type === 'type_identifier' ||
child.type === 'generic_type' ||
child.type === 'reference_type') {
fields.push(`${idx}`);
idx++;
}
}
}
ctx.classes.push({
name,
kind: 'struct',
superclass: null,
protocols: [],
derives,
fieldCount: fields.length,
line: node.startPosition.row + 1,
endLine: node.endPosition.row + 1,
});
}
// ── Enum ─────────────────────────────────────────────────────
function _parseEnum(node, ctx) {
const nameNode = node.namedChildren.find((c) => c.type === 'type_identifier');
const name = nameNode?.text || 'Unknown';
const derives = _extractDerives(node);
const variants = [];
const body = node.namedChildren.find((c) => c.type === 'enum_variant_list');
if (body) {
for (let i = 0; i < body.namedChildCount; i++) {
const variant = body.namedChild(i);
if (variant.type === 'enum_variant') {
const variantName = variant.namedChildren.find((c) => c.type === 'identifier');
if (variantName) {
variants.push(variantName.text);
}
}
}
}
ctx.classes.push({
name,
kind: 'enum',
superclass: null,
protocols: [],
derives,
variants,
variantCount: variants.length,
line: node.startPosition.row + 1,
endLine: node.endPosition.row + 1,
});
}
// ── Trait ─────────────────────────────────────────────────────
function _parseTrait(node, ctx) {
const nameNode = node.namedChildren.find((c) => c.type === 'type_identifier');
const name = nameNode?.text || 'Unknown';
const methods = [];
const superTraits = [];
// Trait bounds (trait Foo: Bar + Baz)
const bounds = node.namedChildren.find((c) => c.type === 'trait_bounds');
if (bounds) {
for (let i = 0; i < bounds.namedChildCount; i++) {
const bound = bounds.namedChild(i);
if (bound.type === 'type_identifier' ||
bound.type === 'scoped_type_identifier' ||
bound.type === 'generic_type') {
superTraits.push(bound.text);
}
}
}
// Trait body — collect method signatures
const body = node.namedChildren.find((c) => c.type === 'declaration_list');
if (body) {
for (let i = 0; i < body.namedChildCount; i++) {
const item = body.namedChild(i);
if (item.type === 'function_signature_item' || item.type === 'function_item') {
const methodName = item.namedChildren.find((c) => c.type === 'identifier');
if (methodName) {
methods.push(methodName.text);
}
}
}
}
ctx.protocols.push({
name,
inherits: superTraits,
methods,
line: node.startPosition.row + 1,
endLine: node.endPosition.row + 1,
});
}
// ── Impl Block ───────────────────────────────────────────────
function _parseImpl(node, ctx) {
// impl Type { ... } 或 impl Trait for Type { ... }
let selfType = null;
let traitName = null;
const typeIdNodes = node.namedChildren.filter((c) => c.type === 'type_identifier' ||
c.type === 'scoped_type_identifier' ||
c.type === 'generic_type');
// 检查是否有 "for" — trait impl
const hasFor = node.children?.some((c) => c.type === 'for');
if (hasFor && typeIdNodes.length >= 2) {
traitName = typeIdNodes[0]?.text;
selfType = typeIdNodes[1]?.text;
}
else if (typeIdNodes.length >= 1) {
selfType = typeIdNodes[0]?.text;
}
const body = node.namedChildren.find((c) => c.type === 'declaration_list');
if (!body || !selfType) {
return;
}
for (let i = 0; i < body.namedChildCount; i++) {
const item = body.namedChild(i);
if (item.type === 'function_item') {
const methodInfo = _parseImplMethod(item, selfType, traitName);
if (methodInfo) {
ctx.methods.push(methodInfo);
}
}
}
}
function _parseImplMethod(node, selfType, traitName) {
const nameNode = node.namedChildren.find((c) => c.type === 'identifier');
const name = nameNode?.text;
if (!name) {
return null;
}
const params = node.namedChildren.find((c) => c.type === 'parameters');
const { paramCount, hasSelfParam } = params
? _countRustParams(params)
: { paramCount: 0, hasSelfParam: false };
const isPublic = _hasPubVisibility(node);
const isAsync = node.children?.some((c) => c.text === 'async') || false;
const body = node.namedChildren.find((c) => c.type === 'block');
const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
const complexity = body ? _estimateComplexity(body) : 1;
const nestingDepth = body ? _maxNesting(body, 0) : 0;
return {
name,
className: selfType,
isClassMethod: !hasSelfParam, // 无 self → associated function (static)
isExported: isPublic,
isAsync,
traitImpl: traitName || null,
paramCount,
bodyLines,
complexity,
nestingDepth,
line: node.startPosition.row + 1,
kind: 'definition',
};
}
// ── Function Item (free fn) ──────────────────────────────────
function _parseFunctionItem(node) {
const nameNode = node.namedChildren.find((c) => c.type === 'identifier');
const name = nameNode?.text;
if (!name) {
return null;
}
const params = node.namedChildren.find((c) => c.type === 'parameters');
const { paramCount } = params ? _countRustParams(params) : { paramCount: 0 };
const isPublic = _hasPubVisibility(node);
const isAsync = node.children?.some((c) => c.text === 'async') || false;
const isUnsafe = node.children?.some((c) => c.text === 'unsafe') || false;
const body = node.namedChildren.find((c) => c.type === 'block');
const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
const complexity = body ? _estimateComplexity(body) : 1;
const nestingDepth = body ? _maxNesting(body, 0) : 0;
return {
name,
className: null,
isClassMethod: true, // free function → "static" equivalent
isExported: isPublic,
isAsync,
isUnsafe,
paramCount,
bodyLines,
complexity,
nestingDepth,
line: node.startPosition.row + 1,
kind: 'definition',
};
}
// ── Const / Static ───────────────────────────────────────────
function _parseConstStatic(node, ctx) {
const isConst = node.type === 'const_item';
const nameNode = node.namedChildren.find((c) => c.type === 'identifier');
if (nameNode) {
ctx.properties.push({
name: nameNode.text,
className: null,
isExported: _hasPubVisibility(node),
isConst,
isStatic: !isConst,
line: node.startPosition.row + 1,
});
}
}
// ── Type Alias ───────────────────────────────────────────────
function _parseTypeAlias(node, ctx) {
const nameNode = node.namedChildren.find((c) => c.type === 'type_identifier');
if (nameNode) {
ctx.classes.push({
name: nameNode.text,
kind: 'type-alias',
line: node.startPosition.row + 1,
endLine: node.endPosition.row + 1,
});
}
}
// ── Macro Definition ─────────────────────────────────────────
function _parseMacroDef(node, ctx) {
const nameNode = node.namedChildren.find((c) => c.type === 'identifier');
if (nameNode) {
ctx.metadata = ctx.metadata || {};
ctx.metadata.macros = ctx.metadata.macros || [];
ctx.metadata.macros.push({
name: nameNode.text,
line: node.startPosition.row + 1,
});
}
}
// ── Rust Pattern Detection ───────────────────────────────────
function detectRustPatterns(root, lang, methods, properties, classes) {
const patterns = [];
// 构建 type → methods 索引
const typeMethodMap = {};
for (const m of methods) {
if (m.className) {
if (!typeMethodMap[m.className]) {
typeMethodMap[m.className] = [];
}
typeMethodMap[m.className].push(m);
}
}
// Builder pattern: struct 有 builder() 或一系列链式 with_*/set_* 方法
for (const [typeName, methodList] of Object.entries(typeMethodMap)) {
const hasBuilder = methodList.some((m) => m.name === 'builder' || m.name === 'build');
const chainMethods = methodList.filter((m) => /^(?:with_|set_|add_)/.test(m.name));
if (hasBuilder || chainMethods.length >= 3) {
patterns.push({
type: 'builder',
className: typeName,
confidence: hasBuilder ? 0.9 : 0.7,
});
}
}
// Factory: new() / from() / create() associated functions
for (const m of methods) {
if (m.className &&
m.isClassMethod && // associated function (no self)
/^(?:new|from|create|open|connect|with_capacity|default)$/.test(m.name)) {
patterns.push({
type: 'factory',
className: m.className,
methodName: m.name,
line: m.line,
confidence: 0.85,
});
}
}
// Newtype pattern: struct with single field
for (const cls of classes) {
if (cls.kind === 'struct' && cls.fieldCount === 1) {
patterns.push({
type: 'newtype',
className: cls.name,
line: cls.line,
confidence: 0.75,
});
}
}
// Error enum pattern: enum with Error/Err suffix or derives thiserror
for (const cls of classes) {
if (cls.kind === 'enum' && /(?:Error|Err)$/.test(cls.name)) {
patterns.push({
type: 'error-enum',
className: cls.name,
variantCount: cls.variantCount || 0,
line: cls.line,
confidence: 0.9,
});
}
}
// Trait impl richness: types with many methods suggest impl-heavy design
for (const [typeName, methodList] of Object.entries(typeMethodMap)) {
if (methodList.length >= 3) {
const traitImpls = new Set(methodList.filter((m) => m.traitImpl).map((m) => m.traitImpl));
patterns.push({
type: 'impl-rich',
className: typeName,
methodCount: methodList.length,
traitImplCount: traitImpls.size,
confidence: 0.7,
});
}
}
// Unsafe usage
_detectUnsafe(root, patterns);
// Async usage
_detectAsync(root, patterns);
// Derive macro analysis
_detectDerives(classes, patterns);
return patterns;
}
function _detectUnsafe(root, patterns) {
let count = 0;
function walk(node) {
if (node.type === 'unsafe_block') {
count++;
}
for (let i = 0; i < node.namedChildCount; i++) {
walk(node.namedChild(i));
}
}
walk(root);
if (count > 0) {
patterns.push({
type: 'unsafe',
count,
confidence: 0.95,
});
}
}
function _detectAsync(root, patterns) {
let asyncFnCount = 0;
let awaitCount = 0;
function walk(node) {
if (node.type === 'function_item' || node.type === 'function_signature_item') {
const isAsync = node.children?.some((c) => c.text === 'async');
if (isAsync) {
asyncFnCount++;
}
}
if (node.type === 'await_expression') {
awaitCount++;
}
for (let i = 0; i < node.namedChildCount; i++) {
walk(node.namedChild(i));
}
}
walk(root);
if (asyncFnCount > 0 || awaitCount > 0) {
patterns.push({
type: 'async',
asyncFunctions: asyncFnCount,
awaitExpressions: awaitCount,
confidence: 0.9,
});
}
}
function _detectDerives(classes, patterns) {
const deriveCounts = {};
for (const cls of classes) {
if (cls.derives) {
for (const d of cls.derives) {
deriveCounts[d] = (deriveCounts[d] || 0) + 1;
}
}
}
const commonDerives = Object.entries(deriveCounts)
.filter(([, count]) => count >= 2)
.map(([name, count]) => ({ name, count }));
if (commonDerives.length > 0) {
patterns.push({
type: 'derive-usage',
derives: commonDerives,
confidence: 0.8,
});
}
}
// ── Helper: Extract #[derive(...)] ──────────────────────────
function _extractDerives(node) {
const derives = [];
// Look at preceding siblings (attribute_item nodes)
if (node.parent) {
const siblings = [];
for (let i = 0; i < node.parent.namedChildCount; i++) {
const sib = node.parent.namedChild(i);
if (sib === node) {
break;
}
siblings.push(sib);
}
// Collect attribute_item nodes immediately before this node
for (let i = siblings.length - 1; i >= 0; i--) {
const sib = siblings[i];
if (sib.type !== 'attribute_item') {
break;
}
const text = sib.text;
const deriveMatch = text.match(/#\[derive\(([^)]+)\)\]/);
if (deriveMatch) {
const items = deriveMatch[1].split(',').map((s) => s.trim());
derives.push(...items);
}
}
}
return derives;
}
// ── Helper: Visibility ──────────────────────────────────────
function _hasPubVisibility(node) {
return (node.children?.some((c) => c.type === 'visibility_modifier' || c.text === 'pub') || false);
}
// ── Helper: Count Parameters ────────────────────────────────
function _countRustParams(paramList) {
let paramCount = 0;
let hasSelfParam = false;
for (let i = 0; i < paramList.namedChildCount; i++) {
const child = paramList.namedChild(i);
if (child.type === 'self_parameter') {
hasSelfParam = true;
// Don't count self in paramCount
}
else if (child.type === 'parameter') {
paramCount++;
}
}
return { paramCount, hasSelfParam };
}
// ── Utility: Complexity ─────────────────────────────────────
function _estimateComplexity(node) {
let complexity = 1;
const BRANCH_TYPES = new Set([
'if_expression',
'if_let_expression',
'for_expression',
'while_expression',
'while_let_expression',
'loop_expression',
'match_expression',
'match_arm',
]);
function walk(n) {
if (BRANCH_TYPES.has(n.type)) {
complexity++;
}
if (n.type === 'binary_expression') {
const op = n.children?.find((c) => c.text === '&&' || c.text === '||');
if (op) {
complexity++;
}
}
for (let i = 0; i < n.namedChildCount; i++) {
walk(n.namedChild(i));
}
}
walk(node);
return complexity;
}
function _maxNesting(node, depth) {
const NESTING_TYPES = new Set([
'if_expression',
'if_let_expression',
'for_expression',
'while_expression',
'while_let_expression',
'loop_expression',
'match_expression',
]);
let max = depth;
const nextDepth = NESTING_TYPES.has(node.type) ? depth + 1 : depth;
for (let i = 0; i < node.namedChildCount; i++) {
const childMax = _maxNesting(node.namedChild(i), nextDepth);
if (childMax > max) {
max = childMax;
}
}
return max;
}
// ── Rust Call Site 提取 (Phase 5) ────────────────────────────
/**
* 从 Rust AST root 提取所有调用点
* 遍历 function_item / impl method 中的 block → call_expression / method_call_expression
*/
function extractCallSitesRust(root, ctx, _lang) {
const scopes = _collectRustScopes(root);
for (const scope of scopes) {
_extractRustCallSitesFromBody(scope.body, scope.className, scope.methodName, ctx);
}
}
/** 递归收集 Rust 中所有函数/方法体作用域 */
function _collectRustScopes(root) {
const scopes = [];
function visit(node, className) {
for (let i = 0; i < node.namedChildCount; i++) {
const child = node.namedChild(i);
if (child.type === 'impl_item') {
// impl Type { ... } or impl Trait for Type { ... }
const typeIdNodes = child.namedChildren.filter((c) => c.type === 'type_identifier' ||
c.type === 'scoped_type_identifier' ||
c.type === 'generic_type');
const hasFor = child.children?.some((c) => c.type === 'for');
let selfType = null;
if (hasFor && typeIdNodes.length >= 2) {
selfType = typeIdNodes[1]?.text;
}
else if (typeIdNodes.length >= 1) {
selfType = typeIdNodes[0]?.text;
}
const body = child.namedChildren.find((c) => c.type === 'declaration_list');
if (body) {
visit(body, selfType || className);
}
}
else if (child.type === 'function_item') {
const name = child.namedChildren.find((c) => c.type === 'identifier')?.text;
const body = child.namedChildren.find((c) => c.type === 'block');
if (name && body) {
scopes.push({ body, className, methodName: name });
}
}
else if (child.type === 'mod_item') {
const body = child.namedChildren.find((c) => c.type === 'declaration_list');
if (body) {
visit(body, null);
}
}
}
}
visit(root, null);
return scopes;
}
/** 从 Rust block 中递归提取调用点 */
function _extractRustCallSitesFromBody(bodyNode, className, methodName, ctx) {
if (!bodyNode) {
return;
}
const RUST_NOISE = new Set([
'println',
'eprintln',
'print',
'eprint',
'dbg',
'format',
'vec',
'panic',
'assert',
'assert_eq',
'assert_ne',
'debug_assert',
'todo',
'unimplemented',
'unreachable',
'cfg',
'write',
'writeln',
'log',
'info',
'warn',
'error',
'debug',
'trace',
]);
function walk(node, isAwaited) {
if (!node || node.type === 'ERROR' || node.isMissing) {
return;
}
// await expression: expr.await
if (node.type === 'await_expression') {
for (let i = 0; i < node.namedChildCount; i++) {
walk(node.namedChild(i), true);
}
return;
}
// call_expression: func(args) or Struct::method(args)
if (node.type === 'call_expression') {
const func = node.namedChildren[0];
if (!func) {
walkChildren(node, false);
return;
}
let callee, receiver = null, receiverType = null, callType;
if (func.type === 'scoped_identifier' || func.type === 'scoped_type_identifier') {
// Struct::method() or crate::mod::func()
const parts = func.text.split('::');
if (parts.length >= 2) {
callee = parts[parts.length - 1];
receiver = parts.slice(0, -1).join('::');
// Check if receiver looks like a type (PascalCase)
const lastReceiver = parts[parts.length - 2];
if (/^[A-Z]/.test(lastReceiver)) {
receiverType = lastReceiver;
callType = callee === 'new' || callee === 'default' ? 'constructor' : 'static';
}
else {
callType = 'function'; // module-qualified function
}
}
else {
callee = func.text;
callType = 'function';
}
}
else if (func.type === 'field_expression') {
// obj.func() — though Rust usually uses method_call_expression for this
const parts = func.text.split('.');
if (parts.length >= 2) {
receiver = parts.slice(0, -1).join('.');
callee = parts[parts.length - 1];
callType = 'method';
if (receiver === 'self' || receiver === '&self' || receiver === '&mut self') {
receiverType = className;
}
}
else {
callee = func.text;
callType = 'function';
}
}
else if (func.type === 'identifier') {
callee = func.text;
if (RUST_NOISE.has(callee)) {
walkChildren(node, false);
return;
}
// PascalCase → constructor pattern (rare in Rust — turbofish/struct literal more common)
callType = /^[A-Z]/.test(callee) ? 'constructor' : 'function';
if (callType === 'constructor') {
receiverType = callee;
}
}
else {
callee = func.text?.slice(0, 80) || 'unknown';
callType = 'function';
}
const args = node.namedChildren.find((c) => c.type === 'arguments');
const argCount = args ? args.namedChildCount : 0;
ctx.callSites.push({
callee,
callerMethod: methodName,
callerClass: className,
callType,
receiver,
receiverType,
argCount,
line: node.startPosition.row + 1,
isAwait: isAwaited,
});
if (args) {
walkChildren(args, false);
}
return;
}
// method_call_expression: obj.method(args) — Rust-specific
if (node.type === 'method_call_expression') {
const valueNode = node.namedChildren.find((c) => c.type !== 'field_identifier' && c.type !== 'arguments' && c.type !== 'type_arguments');
const nameNode = node.namedChildren.find((c) => c.type === 'field_identifier');
const args = node.namedChildren.find((c) => c.type === 'arguments');
const callee = nameNode?.text || 'unknown';
const receiver = valueNode?.text?.slice(0, 80) || null;
let receiverType = null;
const callType = 'method';
if (receiver === 'self' || receiver === '&self' || receiver === '&mut self') {
receiverType = className;
}
else if (receiver && /^[A-Z]/.test(receiver)) {
receiverType = receiver;
}
// Skip noise methods
if (RUST_NOISE.has(callee)) {
walkChildren(node, false);
return;
}
const argCount = args ? args.namedChildCount : 0;
ctx.callSites.push({
callee,
callerMethod: methodName,
callerClass: className,
callType,
receiver,
receiverType,
argCount,
line: node.startPosition.row + 1,
isAwait: isAwaited,
});
if (args) {
walkChildren(args, false);
}
return;
}
// macro_invocation: some_macro!(args) — skip noise macros
if (node.type === 'macro_invocation') {
const macroName = node.namedChildren.find((c) => c.type === 'identifier')?.text;
if (macroName && !RUST_NOISE.has(macroName) && !RUST_NOISE.has(macroName.replace(/!$/, ''))) {
// Only record non-noise macros as function calls
ctx.callSites.push({
callee: macroName,
callerMethod: methodName,
callerClass: className,
callType: 'function',
receiver: null,
receiverType: null,
argCount: 0,
line: node.startPosition.row + 1,
isAwait: false,
});
}
// Don't recurse into macro bodies
return;
}
walkChildren(node, false);
}
function walkChildren(node, isAwaited) {
for (let i = 0; i < node.namedChildCount; i++) {
walk(node.namedChild(i), isAwaited);
}
}
walk(bodyNode, false);
}
// ── Plugin Export ────────────────────────────────────────────
let _grammar = null;
function getGrammar() {
return _grammar;
}
export function setGrammar(grammar) {
_grammar = grammar;
}
export const plugin = {
getGrammar,
walk: walkRust,
detectPatterns: detectRustPatterns,
extractCallSites: extractCallSitesRust,
extensions: ['.rs'],
};