autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
602 lines (601 loc) • 23 kB
JavaScript
/**
* @module lang-java
* @description Java AST Walker 插件
*
* 提取: class, interface, enum, record, method, field, import, annotation
* 模式: Singleton, Builder, Factory, DI, Stream Pipeline
*/
import { ImportRecord } from '../analysis/ImportRecord.js';
function walkJava(root, ctx) {
_walkJavaNode(root, ctx, null);
}
function _walkJavaNode(node, ctx, parentClassName) {
for (let i = 0; i < node.namedChildCount; i++) {
const child = node.namedChild(i);
switch (child.type) {
case 'import_declaration': {
const isStatic = child.namedChildren.some((c) => c.text === 'static');
const path = child.namedChildren.find((c) => c.type === 'scoped_identifier');
if (path) {
const fullPath = path.text; // e.g. com.example.MyClass or com.example.MyClass.myMethod
const segments = fullPath.split('.');
const lastName = segments[segments.length - 1];
// Java wildcard: import com.example.*
const isWildcard = child.text.includes('.*');
if (isWildcard) {
ctx.imports.push(new ImportRecord(fullPath, { symbols: ['*'], kind: 'namespace' }));
}
else if (isStatic) {
// static import: import static com.example.MyClass.method
ctx.imports.push(new ImportRecord(fullPath, { symbols: [lastName], kind: 'named', isTypeOnly: false }));
}
else {
// regular: import com.example.MyClass
ctx.imports.push(new ImportRecord(fullPath, { symbols: [lastName], alias: lastName, kind: 'named' }));
}
}
break;
}
case 'package_declaration': {
const pkg = child.namedChildren.find((c) => c.type === 'scoped_identifier');
if (pkg) {
ctx.metadata = ctx.metadata || {};
ctx.metadata.packageName = pkg.text;
}
break;
}
case 'class_declaration': {
const classInfo = _parseJavaClass(child);
ctx.classes.push(classInfo);
const body = child.namedChildren.find((c) => c.type === 'class_body');
if (body) {
_walkJavaClassBody(body, ctx, classInfo.name);
}
break;
}
case 'interface_declaration': {
const ifaceInfo = _parseJavaInterface(child);
ctx.protocols.push(ifaceInfo);
const body = child.namedChildren.find((c) => c.type === 'interface_body');
if (body) {
_walkJavaInterfaceBody(body, ctx, ifaceInfo.name);
}
break;
}
case 'enum_declaration': {
const name = child.namedChildren.find((c) => c.type === 'identifier')?.text || 'Unknown';
ctx.classes.push({
name,
kind: 'enum',
line: child.startPosition.row + 1,
endLine: child.endPosition.row + 1,
});
break;
}
case 'record_declaration': {
const name = child.namedChildren.find((c) => c.type === 'identifier')?.text || 'Unknown';
ctx.classes.push({
name,
kind: 'record',
line: child.startPosition.row + 1,
endLine: child.endPosition.row + 1,
});
break;
}
default: {
if (child.namedChildCount > 0 && child.type !== 'block') {
_walkJavaNode(child, ctx, parentClassName);
}
}
}
}
}
function _walkJavaClassBody(body, ctx, className) {
for (let i = 0; i < body.namedChildCount; i++) {
const child = body.namedChild(i);
switch (child.type) {
case 'method_declaration': {
ctx.methods.push(_parseJavaMethod(child, className));
break;
}
case 'constructor_declaration': {
const m = _parseJavaMethod(child, className);
m.isConstructor = true;
ctx.methods.push(m);
break;
}
case 'field_declaration': {
const p = _parseJavaField(child, className);
if (p) {
ctx.properties.push(p);
}
break;
}
case 'class_declaration': {
// 内部类
const inner = _parseJavaClass(child);
inner.outerClass = className;
ctx.classes.push(inner);
const innerBody = child.namedChildren.find((c) => c.type === 'class_body');
if (innerBody) {
_walkJavaClassBody(innerBody, ctx, inner.name);
}
break;
}
case 'interface_declaration': {
const inner = _parseJavaInterface(child);
inner.outerClass = className;
ctx.protocols.push(inner);
break;
}
case 'enum_declaration': {
const name = child.namedChildren.find((c) => c.type === 'identifier')?.text || 'Unknown';
ctx.classes.push({
name,
kind: 'enum',
outerClass: className,
line: child.startPosition.row + 1,
});
break;
}
}
}
}
function _walkJavaInterfaceBody(body, ctx, ifaceName) {
for (let i = 0; i < body.namedChildCount; i++) {
const child = body.namedChild(i);
if (child.type === 'method_declaration') {
ctx.methods.push(_parseJavaMethod(child, ifaceName));
}
}
}
function _parseJavaClass(node) {
const name = node.namedChildren.find((c) => c.type === 'identifier')?.text || 'Unknown';
let superclass = null;
const protocols = [];
for (const child of node.namedChildren) {
if (child.type === 'superclass') {
const typeId = child.namedChildren.find((c) => c.type === 'type_identifier');
if (typeId) {
superclass = typeId.text;
}
}
if (child.type === 'super_interfaces') {
for (const impl of child.namedChildren) {
if (impl.type === 'type_list') {
for (const t of impl.namedChildren) {
if (t.type === 'type_identifier' || t.type === 'generic_type') {
protocols.push(t.text);
}
}
}
}
}
}
// 提取注解(tree-sitter-java: 注解在 modifiers 子节点内)
const annotations = _extractAnnotations(node);
// 修饰符
const modifiers = node.namedChildren.find((c) => c.type === 'modifiers');
const isAbstract = modifiers?.text?.includes('abstract') || false;
return {
name,
kind: 'class',
superclass,
protocols,
annotations,
abstract: isAbstract,
line: node.startPosition.row + 1,
endLine: node.endPosition.row + 1,
};
}
function _parseJavaInterface(node) {
const name = node.namedChildren.find((c) => c.type === 'identifier')?.text || 'Unknown';
const inherits = [];
for (const child of node.namedChildren) {
if (child.type === 'extends_interfaces') {
for (const ext of child.namedChildren) {
if (ext.type === 'type_list') {
for (const t of ext.namedChildren) {
if (t.type === 'type_identifier' || t.type === 'generic_type') {
inherits.push(t.text);
}
}
}
}
}
}
return { name, inherits, line: node.startPosition.row + 1 };
}
function _parseJavaMethod(node, className) {
const name = node.namedChildren.find((c) => c.type === 'identifier')?.text || 'unknown';
const modifiers = node.namedChildren.find((c) => c.type === 'modifiers');
const isStatic = modifiers?.text?.includes('static') || 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;
const annotations = _extractAnnotations(node);
return {
name,
className,
isClassMethod: isStatic,
annotations,
bodyLines,
complexity,
nestingDepth,
line: node.startPosition.row + 1,
kind: 'definition',
};
}
function _parseJavaField(node, className) {
const declNode = node.namedChildren.find((c) => c.type === 'variable_declarator');
const name = declNode?.namedChildren?.find((c) => c.type === 'identifier')?.text;
if (!name) {
return null;
}
const modifiers = node.namedChildren.find((c) => c.type === 'modifiers');
const isStatic = modifiers?.text?.includes('static') || false;
const isFinal = modifiers?.text?.includes('final') || false;
const isPrivate = modifiers?.text?.includes('private') || false;
const annotations = _extractAnnotations(node);
// Phase 5.3: Extract field type for DI resolution
// field_declaration: [modifiers] type_identifier variable_declarator
let typeAnnotation = null;
const typeNode = node.namedChildren.find((c) => c.type === 'type_identifier' ||
c.type === 'generic_type' ||
c.type === 'scoped_type_identifier');
if (typeNode) {
// Strip generics: List<User> → List, Repository<User, Long> → Repository
const text = typeNode.text;
const bracketIdx = text.indexOf('<');
typeAnnotation = bracketIdx > 0 ? text.slice(0, bracketIdx) : text;
}
return {
name,
className,
isStatic,
isFinal,
isPrivate,
annotations,
typeAnnotation,
line: node.startPosition.row + 1,
};
}
/**
* tree-sitter-java 中注解位于 modifiers 子节点内,而非声明节点的直接子节点。
* 此辅助函数同时搜索两层:node.namedChildren + modifiers.namedChildren。
*/
function _extractAnnotations(node) {
// 1. 直接子节点(兼容其他可能的 AST 结构)
const direct = node.namedChildren
.filter((c) => c.type === 'marker_annotation' || c.type === 'annotation')
.map((a) => a.text);
if (direct.length > 0) {
return direct;
}
// 2. modifiers 子节点
const modifiers = node.namedChildren.find((c) => c.type === 'modifiers');
if (modifiers) {
return modifiers.namedChildren
.filter((c) => c.type === 'marker_annotation' || c.type === 'annotation')
.map((a) => a.text);
}
return [];
}
// ── Java 模式检测 ──
function detectJavaPatterns(root, lang, methods, properties, classes) {
const patterns = [];
// Singleton: private constructor + static getInstance
const classMethodMap = {};
for (const m of methods) {
if (m.className) {
if (!classMethodMap[m.className]) {
classMethodMap[m.className] = [];
}
classMethodMap[m.className].push(m);
}
}
for (const [cls, methodList] of Object.entries(classMethodMap)) {
const _hasPrivateConstructor = methodList.some((m) => m.isConstructor);
const hasGetInstance = methodList.some((m) => m.isClassMethod && /^getInstance$|^get$/.test(m.name));
if (hasGetInstance) {
patterns.push({ type: 'singleton', className: cls, confidence: 0.85 });
}
// Builder pattern: 内部 Builder 类
const builderClass = classes.find((c) => c.name === 'Builder' && c.outerClass === cls);
if (builderClass) {
patterns.push({ type: 'builder', className: cls, confidence: 0.9 });
}
}
// Factory: static create/of/from
for (const m of methods) {
if (m.isClassMethod && /^create$|^of$|^from$|^newInstance$|^build$/.test(m.name)) {
patterns.push({
type: 'factory',
className: m.className,
methodName: m.name,
line: m.line,
confidence: 0.8,
});
}
}
// DI: @Inject/@Autowired
for (const p of properties) {
if (p.annotations?.some((a) => /@Inject|@Autowired/.test(a))) {
patterns.push({
type: 'dependency-injection',
className: p.className,
propertyName: p.name,
line: p.line,
confidence: 0.95,
});
}
}
for (const m of methods) {
if (m.annotations?.some((a) => /@Inject|@Autowired/.test(a))) {
patterns.push({
type: 'dependency-injection',
className: m.className,
methodName: m.name,
line: m.line,
confidence: 0.95,
});
}
}
// Spring annotations
for (const cls of classes) {
if (cls.annotations?.some((a) => /@RestController|@Controller/.test(a))) {
patterns.push({
type: 'rest-controller',
className: cls.name,
line: cls.line,
confidence: 0.95,
});
}
if (cls.annotations?.some((a) => /@Service/.test(a))) {
patterns.push({ type: 'service', className: cls.name, line: cls.line, confidence: 0.9 });
}
if (cls.annotations?.some((a) => /@Repository/.test(a))) {
patterns.push({ type: 'repository', className: cls.name, line: cls.line, confidence: 0.9 });
}
if (cls.annotations?.some((a) => /@Entity/.test(a))) {
patterns.push({ type: 'entity', className: cls.name, line: cls.line, confidence: 0.95 });
}
}
return patterns;
}
// ── 工具函数 ──
function _estimateComplexity(node) {
let complexity = 1;
const BRANCH_TYPES = new Set([
'if_statement',
'for_statement',
'enhanced_for_statement',
'while_statement',
'switch_expression',
'switch_block_statement_group',
'catch_clause',
'ternary_expression',
'do_statement',
]);
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',
'enhanced_for_statement',
'while_statement',
'switch_expression',
'try_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;
}
// ── Java Call Site 提取 (Phase 5) ─────────────────────────────
/**
* 从 Java AST root 提取所有调用点
* 遍历 method_declaration / constructor_declaration 中的 block → method_invocation / object_creation_expression
*/
function extractCallSitesJava(root, ctx, _lang) {
const scopes = _collectJavaScopes(root);
for (const scope of scopes) {
_extractJavaCallSitesFromBody(scope.body, scope.className, scope.methodName, ctx);
}
}
/** 递归收集 Java 中所有方法体作用域 */
function _collectJavaScopes(root) {
const scopes = [];
function visit(node, className) {
for (let i = 0; i < node.namedChildCount; i++) {
const child = node.namedChild(i);
if (child.type === 'class_declaration' ||
child.type === 'enum_declaration' ||
child.type === 'record_declaration') {
const name = child.namedChildren.find((c) => c.type === 'identifier')?.text;
const body = child.namedChildren.find((c) => c.type === 'class_body' || c.type === 'enum_body');
if (body) {
visit(body, name || className);
}
}
else if (child.type === 'interface_declaration') {
const name = child.namedChildren.find((c) => c.type === 'identifier')?.text;
const body = child.namedChildren.find((c) => c.type === 'interface_body');
if (body) {
visit(body, name || className);
}
}
else if (child.type === 'method_declaration' || child.type === 'constructor_declaration') {
const name = child.namedChildren.find((c) => c.type === 'identifier')?.text || '<init>';
const body = child.namedChildren.find((c) => c.type === 'block');
if (body) {
scopes.push({ body, className, methodName: name });
}
}
}
}
visit(root, null);
return scopes;
}
/** 从 Java method body 中递归提取调用点 */
function _extractJavaCallSitesFromBody(bodyNode, className, methodName, ctx) {
if (!bodyNode) {
return;
}
const JAVA_NOISE = new Set([
'System',
'Math',
'String',
'Integer',
'Long',
'Double',
'Float',
'Boolean',
'Character',
'Byte',
'Short',
'Arrays',
'Collections',
'Objects',
'Optional',
'Stream',
'Collectors',
'List',
'Map',
'Set',
]);
function walk(node) {
if (!node || node.type === 'ERROR' || node.isMissing) {
return;
}
if (node.type === 'method_invocation') {
// obj.method(args) or method(args)
// In Java tree-sitter: namedChildren = [object?, name(identifier), argument_list]
// The method name is the LAST identifier before argument_list
const identifiers = node.namedChildren.filter((c) => c.type === 'identifier');
const args = node.namedChildren.find((c) => c.type === 'argument_list');
const argCount = args ? args.namedChildCount : 0;
let callee, receiver = null, receiverType = null, callType;
if (identifiers.length >= 2) {
// obj.method() — first identifier is receiver, last is method name
const objectNode = identifiers[0];
const methodNode = identifiers[identifiers.length - 1];
receiver = objectNode.text?.slice(0, 80);
callee = methodNode.text;
callType = 'method';
// Check known noise
if (receiver && JAVA_NOISE.has(receiver.split('.')[0])) {
receiverType = receiver;
callType = 'static';
}
}
else if (identifiers.length === 1) {
// Could be: unqualified method(args) or receiver is an expression
const objectNode = node.namedChildren[0];
if (objectNode && objectNode.type !== 'identifier' && objectNode.type !== 'argument_list') {
// Expression receiver: e.g. getService().doWork() or super.method()
receiver = objectNode.text?.slice(0, 80);
callee = identifiers[0].text;
callType = 'method';
// super.xxx() → CHA 解析到父类
if (receiver === 'super') {
callType = 'super';
receiverType = className;
}
}
else {
callee = identifiers[0].text;
callType = 'function'; // unqualified method call within same class
}
}
else {
callee = node.text?.split('(')[0]?.slice(0, 80) || 'unknown';
callType = 'function';
}
ctx.callSites.push({
callee,
callerMethod: methodName,
callerClass: className,
callType,
receiver,
receiverType,
argCount,
line: node.startPosition.row + 1,
isAwait: false,
});
// walk arguments for nested calls
if (args) {
walkChildren(args);
}
return;
}
if (node.type === 'object_creation_expression') {
// new ClassName(args)
const typeNode = node.namedChildren.find((c) => c.type === 'type_identifier' ||
c.type === 'generic_type' ||
c.type === 'scoped_type_identifier');
const args = node.namedChildren.find((c) => c.type === 'argument_list');
const argCount = args ? args.namedChildCount : 0;
const typeName = typeNode?.text || 'Unknown';
ctx.callSites.push({
callee: typeName,
callerMethod: methodName,
callerClass: className,
callType: 'constructor',
receiver: null,
receiverType: typeName,
argCount,
line: node.startPosition.row + 1,
isAwait: false,
});
if (args) {
walkChildren(args);
}
return;
}
walkChildren(node);
}
function walkChildren(node) {
for (let i = 0; i < node.namedChildCount; i++) {
walk(node.namedChild(i));
}
}
walk(bodyNode);
}
// ── 插件导出 ──
let _grammar = null;
function getGrammar() {
return _grammar;
}
export function setGrammar(grammar) {
_grammar = grammar;
}
export const plugin = {
getGrammar,
walk: walkJava,
detectPatterns: detectJavaPatterns,
extractCallSites: extractCallSitesJava,
extensions: ['.java'],
};