autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
670 lines (669 loc) • 24.7 kB
JavaScript
/**
* @module lang-go
* @description Go AST Walker 插件
*
* 提取: struct, interface, method (with receiver), function, field, import
* 模式: Singleton (sync.Once), Factory (New*), Constructor (New*),
* Goroutine, Channel, Middleware (http.Handler chain)
*
* Phase 5: 新增 ImportRecord 结构化导入 + extractCallSites 调用点提取
*/
import { ImportRecord } from '../analysis/ImportRecord.js';
function walkGo(root, ctx) {
for (let i = 0; i < root.namedChildCount; i++) {
const child = root.namedChild(i);
switch (child.type) {
case 'package_clause': {
const pkgId = child.namedChildren.find((c) => c.type === 'package_identifier');
if (pkgId) {
ctx.metadata = ctx.metadata || {};
ctx.metadata.packageName = pkgId.text;
}
break;
}
case 'import_declaration': {
const specList = child.namedChildren.find((c) => c.type === 'import_spec_list');
if (specList) {
for (let j = 0; j < specList.namedChildCount; j++) {
const spec = specList.namedChild(j);
if (spec.type === 'import_spec') {
const rec = _parseGoImportSpec(spec);
if (rec) {
ctx.imports.push(rec);
}
}
}
}
else {
// 单行 import
const spec = child.namedChildren.find((c) => c.type === 'import_spec');
if (spec) {
const rec = _parseGoImportSpec(spec);
if (rec) {
ctx.imports.push(rec);
}
}
}
break;
}
case 'type_declaration': {
_walkTypeDeclaration(child, ctx);
break;
}
case 'function_declaration': {
const funcInfo = _parseFunctionDecl(child);
if (funcInfo) {
ctx.methods.push(funcInfo);
}
break;
}
case 'method_declaration': {
const methodInfo = _parseMethodDecl(child);
if (methodInfo) {
ctx.methods.push(methodInfo);
}
break;
}
case 'const_declaration':
case 'var_declaration': {
_walkVarConstDecl(child, ctx);
break;
}
default:
break;
}
}
}
// ── Type Declaration ─────────────────────────────────────────
function _walkTypeDeclaration(node, ctx) {
for (let i = 0; i < node.namedChildCount; i++) {
const spec = node.namedChild(i);
if (spec.type !== 'type_spec') {
continue;
}
const nameNode = spec.namedChildren.find((c) => c.type === 'type_identifier');
const name = nameNode?.text || 'Unknown';
// 判断是 struct / interface / type alias
const structType = spec.namedChildren.find((c) => c.type === 'struct_type');
const ifaceType = spec.namedChildren.find((c) => c.type === 'interface_type');
if (structType) {
_parseStruct(name, structType, spec, ctx);
}
else if (ifaceType) {
_parseInterface(name, ifaceType, spec, ctx);
}
else {
// type alias (e.g. type HandlerFunc func(*Context))
ctx.classes.push({
name,
kind: 'type-alias',
line: spec.startPosition.row + 1,
endLine: spec.endPosition.row + 1,
});
}
}
}
function _parseStruct(name, structNode, specNode, ctx) {
const fields = [];
const embeddedTypes = [];
const fieldList = structNode.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) {
// Named field
const fieldName = fieldId.text;
const isExported = fieldName[0] === fieldName[0].toUpperCase();
ctx.properties.push({
name: fieldName,
className: name,
isExported,
line: field.startPosition.row + 1,
});
fields.push(fieldName);
}
else {
// Embedded type (anonymous field)
const typeId = field.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'qualified_type');
if (typeId) {
embeddedTypes.push(typeId.text);
}
}
}
}
ctx.classes.push({
name,
kind: 'struct',
superclass: embeddedTypes[0] || null,
protocols: embeddedTypes.length > 1 ? embeddedTypes.slice(1) : [],
embeddedTypes,
fieldCount: fields.length,
line: specNode.startPosition.row + 1,
endLine: specNode.endPosition.row + 1,
});
}
function _parseInterface(name, ifaceNode, specNode, ctx) {
const methods = [];
const embeddedInterfaces = [];
for (let i = 0; i < ifaceNode.namedChildCount; i++) {
const child = ifaceNode.namedChild(i);
if (child.type === 'method_elem') {
const methodName = child.namedChildren.find((c) => c.type === 'field_identifier');
if (methodName) {
methods.push(methodName.text);
}
}
else if (child.type === 'type_elem') {
// Embedded interface / type constraint
const typeId = child.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'qualified_type');
if (typeId) {
embeddedInterfaces.push(typeId.text);
}
}
else if (child.type === 'constraint_elem') {
// Go generics type constraint
const typeId = child.namedChildren.find((c) => c.type === 'type_identifier');
if (typeId) {
embeddedInterfaces.push(typeId.text);
}
}
}
ctx.protocols.push({
name,
inherits: embeddedInterfaces,
methods,
line: specNode.startPosition.row + 1,
endLine: specNode.endPosition.row + 1,
});
}
// ── Function & Method ────────────────────────────────────────
function _parseFunctionDecl(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 === 'parameter_list');
const paramCount = params ? _countParams(params) : 0;
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, // package-level function == "static"
isExported: name[0] === name[0].toUpperCase(),
paramCount,
bodyLines,
complexity,
nestingDepth,
line: node.startPosition.row + 1,
kind: 'definition',
};
}
function _parseMethodDecl(node) {
// 第一个 parameter_list 是 receiver
const paramLists = node.namedChildren.filter((c) => c.type === 'parameter_list');
const receiverList = paramLists[0];
const paramList = paramLists[1];
let receiverType = null;
let isPointerReceiver = false;
if (receiverList) {
const paramDecl = receiverList.namedChildren.find((c) => c.type === 'parameter_declaration');
if (paramDecl) {
const pointer = paramDecl.namedChildren.find((c) => c.type === 'pointer_type');
if (pointer) {
isPointerReceiver = true;
const typeId = pointer.namedChildren.find((c) => c.type === 'type_identifier');
receiverType = typeId?.text || null;
}
else {
const typeId = paramDecl.namedChildren.find((c) => c.type === 'type_identifier');
receiverType = typeId?.text || null;
}
}
}
const nameNode = node.namedChildren.find((c) => c.type === 'field_identifier');
const name = nameNode?.text;
if (!name) {
return null;
}
const paramCount = paramList ? _countParams(paramList) : 0;
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: receiverType,
isClassMethod: false,
isExported: name[0] === name[0].toUpperCase(),
isPointerReceiver,
paramCount,
bodyLines,
complexity,
nestingDepth,
line: node.startPosition.row + 1,
kind: 'definition',
};
}
// ── Var / Const ──────────────────────────────────────────────
function _walkVarConstDecl(node, ctx) {
const isConst = node.type === 'const_declaration';
for (let i = 0; i < node.namedChildCount; i++) {
const spec = node.namedChild(i);
if (spec.type !== 'const_spec' && spec.type !== 'var_spec') {
continue;
}
const nameNode = spec.namedChildren.find((c) => c.type === 'identifier');
if (nameNode) {
ctx.properties.push({
name: nameNode.text,
className: null, // package-level
isExported: nameNode.text[0] === nameNode.text[0].toUpperCase(),
isConst,
line: spec.startPosition.row + 1,
});
}
}
}
// ── Go Pattern Detection ─────────────────────────────────────
function detectGoPatterns(root, lang, methods, properties, classes) {
const patterns = [];
// 构建 struct → methods 索引
const structMethodMap = {};
for (const m of methods) {
if (m.className) {
if (!structMethodMap[m.className]) {
structMethodMap[m.className] = [];
}
structMethodMap[m.className].push(m);
}
}
// Singleton (sync.Once pattern): var instance + func GetInstance / sync.Once
for (const cls of classes) {
if (cls.kind !== 'struct') {
continue;
}
// 检查是否有 package-level var 指向此 struct
const hasPackageVar = properties.some((p) => !p.className && !p.isConst && !p.isExported);
const hasNewFunc = methods.some((m) => !m.className && /^(?:New|Get|Default)/.test(m.name) && m.isExported);
if (hasPackageVar && hasNewFunc) {
patterns.push({
type: 'singleton',
className: cls.name,
confidence: 0.6,
});
}
}
// Factory: New* / Create* package-level functions
for (const m of methods) {
if (!m.className && m.isExported && /^(?:New|Create|Make|Build|Open|Connect)/.test(m.name)) {
patterns.push({
type: 'factory',
methodName: m.name,
line: m.line,
confidence: 0.85,
});
}
}
// Interface satisfaction: struct 的方法集合覆盖某 interface
// (简化版: 不做完整检查, 只记录 struct-has-methods 的关系)
for (const [structName, methodList] of Object.entries(structMethodMap)) {
if (methodList.length >= 3) {
patterns.push({
type: 'struct-methods',
className: structName,
methodCount: methodList.length,
confidence: 0.7,
});
}
}
// Goroutine launching
_detectGoroutines(root, patterns);
// Channel usage
_detectChannels(root, patterns);
// HTTP Handler / Middleware pattern
for (const m of methods) {
if (m.className && m.isExported && /^(?:ServeHTTP|Handle|Handler)$/.test(m.name)) {
patterns.push({
type: 'http-handler',
className: m.className,
methodName: m.name,
line: m.line,
confidence: 0.9,
});
}
}
return patterns;
}
function _detectGoroutines(root, patterns) {
let count = 0;
function walk(node) {
if (node.type === 'go_statement') {
count++;
}
for (let i = 0; i < node.namedChildCount; i++) {
walk(node.namedChild(i));
}
}
walk(root);
if (count > 0) {
patterns.push({
type: 'goroutine',
count,
confidence: 0.95,
});
}
}
function _detectChannels(root, patterns) {
let chanMakeCount = 0;
let selectCount = 0;
function walk(node) {
if (node.type === 'channel_type') {
chanMakeCount++;
}
if (node.type === 'select_statement') {
selectCount++;
}
for (let i = 0; i < node.namedChildCount; i++) {
walk(node.namedChild(i));
}
}
walk(root);
if (chanMakeCount > 0 || selectCount > 0) {
patterns.push({
type: 'channel',
channels: chanMakeCount,
selects: selectCount,
confidence: 0.9,
});
}
}
// ── Utility ──────────────────────────────────────────────────
function _countParams(paramList) {
let count = 0;
for (let i = 0; i < paramList.namedChildCount; i++) {
const child = paramList.namedChild(i);
if (child.type === 'parameter_declaration' || child.type === 'variadic_parameter_declaration') {
// Each identifier in the same declaration is a parameter
const ids = child.namedChildren.filter((c) => c.type === 'identifier');
count += Math.max(ids.length, 1);
}
}
return count;
}
function _estimateComplexity(node) {
let complexity = 1;
const BRANCH_TYPES = new Set([
'if_statement',
'for_statement',
'expression_switch_statement',
'type_switch_statement',
'expression_case',
'type_case',
'select_statement',
'communication_case',
]);
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_statement',
'for_statement',
'expression_switch_statement',
'type_switch_statement',
'select_statement',
]);
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;
}
// ── Go Import 解析 ──────────────────────────────────────────
/**
* 解析 Go import_spec 节点为 ImportRecord
*
* Go import 语法:
* import "fmt" → namespace, alias='fmt'
* import alias "pkg/path" → namespace, alias=alias
* import . "pkg/path" → named (dot import, 类似全部导入)
* import _ "pkg/path" → side-effect
*
* @param spec import_spec 节点
*/
function _parseGoImportSpec(spec) {
const strLit = spec.namedChildren.find((c) => c.type === 'interpreted_string_literal');
if (!strLit) {
return null;
}
const importPath = strLit.text.replace(/"/g, '');
const aliasNode = spec.namedChildren.find((c) => c.type === 'package_identifier' ||
c.type === 'dot' ||
c.type === 'blank_identifier' ||
(c.type === 'identifier' && (c.text === '.' || c.text === '_')));
if (aliasNode) {
if (aliasNode.text === '.' || aliasNode.type === 'dot') {
// dot import: import . "pkg" → all exports available
return new ImportRecord(importPath, { symbols: ['*'], kind: 'named' });
}
if (aliasNode.text === '_' || aliasNode.type === 'blank_identifier') {
// blank import: side-effect only
return new ImportRecord(importPath, { symbols: [], kind: 'side-effect' });
}
// explicit alias: import alias "pkg/path"
return new ImportRecord(importPath, {
symbols: ['*'],
alias: aliasNode.text,
kind: 'namespace',
});
}
// default: import "pkg/path" → alias is last segment of path
const parts = importPath.split('/');
const defaultAlias = parts[parts.length - 1];
return new ImportRecord(importPath, {
symbols: ['*'],
alias: defaultAlias,
kind: 'namespace',
});
}
// ── Go Call Site 提取 (Phase 5) ─────────────────────────────
/**
* 从 Go AST root 提取所有调用点
* 遍历 function_declaration / method_declaration 中的 block → call_expression
*/
function extractCallSitesGo(root, ctx, _lang) {
const scopes = _collectGoScopes(root);
for (const scope of scopes) {
_extractGoCallSitesFromBody(scope.body, scope.className, scope.methodName, ctx);
}
}
/** 收集 Go 中所有函数/方法作用域 */
function _collectGoScopes(root) {
const scopes = [];
for (let i = 0; i < root.namedChildCount; i++) {
const child = root.namedChild(i);
if (child.type === 'function_declaration') {
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: null, methodName: name });
}
}
else if (child.type === 'method_declaration') {
const name = child.namedChildren.find((c) => c.type === 'field_identifier')?.text;
const body = child.namedChildren.find((c) => c.type === 'block');
// 提取 receiver type
const paramLists = child.namedChildren.filter((c) => c.type === 'parameter_list');
let receiverType = null;
if (paramLists[0]) {
const paramDecl = paramLists[0].namedChildren.find((c) => c.type === 'parameter_declaration');
if (paramDecl) {
const pointer = paramDecl.namedChildren.find((c) => c.type === 'pointer_type');
if (pointer) {
receiverType = pointer.namedChildren.find((c) => c.type === 'type_identifier')?.text;
}
else {
receiverType = paramDecl.namedChildren.find((c) => c.type === 'type_identifier')?.text;
}
}
}
if (name && body) {
scopes.push({ body, className: receiverType, methodName: name });
}
}
}
return scopes;
}
/** 从 Go block 中递归提取调用点 */
function _extractGoCallSitesFromBody(bodyNode, className, methodName, ctx) {
if (!bodyNode) {
return;
}
const GO_NOISE = new Set([
'fmt',
'log',
'errors',
'strings',
'strconv',
'math',
'sort',
'time',
'sync',
'context',
'reflect',
'unsafe',
'os',
'io',
'bytes',
'bufio',
'regexp',
'path',
'filepath',
'encoding',
]);
function walk(node) {
if (!node || node.type === 'ERROR' || node.isMissing) {
return;
}
if (node.type === 'call_expression') {
const func = node.namedChildren[0];
if (!func) {
walkChildren(node);
return;
}
let callee, receiver = null, receiverType = null, callType;
if (func.type === 'selector_expression') {
// pkg.Func() or obj.Method()
const parts = func.text.split('.');
if (parts.length >= 2) {
receiver = parts.slice(0, -1).join('.');
callee = parts[parts.length - 1];
callType = 'method';
// Go: uppercase receiver might be package name → static
if (receiver && /^[a-z]/.test(receiver) && !GO_NOISE.has(receiver)) {
receiverType = null; // instance method
}
else if (GO_NOISE.has(receiver)) {
walkChildren(node);
return; // skip noise
}
else {
receiverType = receiver;
callType = 'static';
}
}
else {
callee = func.text;
callType = 'function';
}
}
else if (func.type === 'identifier') {
callee = func.text;
// Go: uppercase = exported, New* = constructor pattern
if (/^New[A-Z]/.test(callee)) {
callType = 'constructor';
receiverType = callee.slice(3); // NewUserService → UserService
}
else {
callType = 'function';
}
}
else {
callee = func.text?.slice(0, 80) || 'unknown';
callType = 'function';
}
// 计算参数数量
const args = node.namedChildren.find((c) => c.type === 'argument_list');
const argCount = args ? args.namedChildCount : 0;
ctx.callSites.push({
callee,
callerMethod: methodName,
callerClass: className,
callType,
receiver,
receiverType,
argCount,
line: node.startPosition.row + 1,
isAwait: false, // Go 不使用 await
});
// 遍历参数中的嵌套调用
if (args) {
walkChildren(args);
}
return;
}
// go goroutine: go func() — 异步调用
if (node.type === 'go_statement') {
walkChildren(node);
return;
}
walkChildren(node);
}
function walkChildren(node) {
for (let i = 0; i < node.namedChildCount; i++) {
walk(node.namedChild(i));
}
}
walk(bodyNode);
}
// ── Plugin Export ────────────────────────────────────────────
let _grammar = null;
function getGrammar() {
return _grammar;
}
export function setGrammar(grammar) {
_grammar = grammar;
}
export const plugin = {
getGrammar,
walk: walkGo,
detectPatterns: detectGoPatterns,
extractCallSites: extractCallSitesGo,
extensions: ['.go'],
};