autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
319 lines (318 loc) • 12.5 kB
JavaScript
/**
* @module lang-javascript
* @description JavaScript AST Walker 插件
*
* 与 TypeScript walker 共享大部分逻辑,grammar 使用 web-tree-sitter (WASM)
*/
// JavaScript walker 与 TypeScript walker 结构相同
// 复用 lang-typescript 的 walker 逻辑
function walkJavaScript(root, ctx) {
_walkJSNode(root, ctx, null);
}
function _walkJSNode(node, ctx, parentClassName) {
for (let i = 0; i < node.namedChildCount; i++) {
const child = node.namedChild(i);
switch (child.type) {
case 'import_statement': {
const source = child.namedChildren.find((c) => c.type === 'string' || c.type === 'string_fragment');
if (source) {
ctx.imports.push(source.text.replace(/^['"]|['"]$/g, ''));
}
break;
}
case 'export_statement': {
ctx.exports = ctx.exports || [];
ctx.exports.push({ line: child.startPosition.row + 1, text: child.text.substring(0, 100) });
_walkJSNode(child, ctx, parentClassName);
break;
}
case 'class_declaration': {
const classInfo = _parseJSClass(child);
ctx.classes.push(classInfo);
const body = child.namedChildren.find((c) => c.type === 'class_body');
if (body) {
_walkJSClassBody(body, ctx, classInfo.name);
}
break;
}
case 'function_declaration': {
ctx.methods.push(_parseJSFunction(child, parentClassName));
break;
}
case 'lexical_declaration':
case 'variable_declaration': {
_parseJSVariableDecl(child, ctx, parentClassName);
break;
}
default: {
if (child.namedChildCount > 0 &&
!['function_body', 'statement_block', 'template_string'].includes(child.type)) {
_walkJSNode(child, ctx, parentClassName);
}
}
}
}
}
function _walkJSClassBody(body, ctx, className) {
for (let i = 0; i < body.namedChildCount; i++) {
const child = body.namedChild(i);
if (child.type === 'method_definition') {
const name = child.namedChildren.find((c) => c.type === 'property_identifier' || c.type === 'identifier')?.text || 'unknown';
const isStatic = child.text.trimStart().startsWith('static');
const bodyNode = child.namedChildren.find((c) => c.type === 'statement_block');
const bodyLines = bodyNode ? bodyNode.endPosition.row - bodyNode.startPosition.row + 1 : 0;
ctx.methods.push({
name,
className,
isClassMethod: isStatic,
bodyLines,
complexity: bodyNode ? _estimateComplexity(bodyNode) : 1,
nestingDepth: bodyNode ? _maxNesting(bodyNode, 0) : 0,
line: child.startPosition.row + 1,
kind: 'definition',
});
}
else if (child.type === 'field_definition' || child.type === 'public_field_definition') {
const name = child.namedChildren.find((c) => c.type === 'property_identifier')?.text;
if (name) {
const isStatic = child.text.trimStart().startsWith('static');
ctx.properties.push({
name,
className,
isStatic,
isConstant: false,
line: child.startPosition.row + 1,
});
}
}
}
// 从 constructor 中提取 this.xxx = ... 赋值属性
_extractConstructorProperties(body, ctx, className);
}
function _extractConstructorProperties(body, ctx, className) {
if (!body) {
return;
}
for (let i = 0; i < body.namedChildCount; i++) {
const child = body.namedChild(i);
if (child.type !== 'method_definition') {
continue;
}
const nameNode = child.namedChildren.find((c) => c.type === 'property_identifier' || c.type === 'identifier');
if (nameNode?.text !== 'constructor') {
continue;
}
const stmtBlock = child.namedChildren.find((c) => c.type === 'statement_block');
if (!stmtBlock) {
continue;
}
const seen = new Set(ctx.properties.filter((p) => p.className === className).map((p) => p.name));
_walkForThisAssignments(stmtBlock, ctx, className, seen);
}
}
function _walkForThisAssignments(node, ctx, className, seen) {
for (let i = 0; i < node.childCount; i++) {
const child = node.child(i);
if (child.type === 'expression_statement') {
const expr = child.namedChildren.find((c) => c.type === 'assignment_expression');
if (expr) {
const left = expr.namedChildren[0];
if (left?.type === 'member_expression') {
const obj = left.namedChildren.find((c) => c.type === 'this');
const prop = left.namedChildren.find((c) => c.type === 'property_identifier');
if (obj && prop && !seen.has(prop.text)) {
seen.add(prop.text);
ctx.properties.push({
name: prop.text,
className,
isStatic: false,
isConstant: false,
line: child.startPosition.row + 1,
});
}
}
}
}
else if (child.namedChildCount > 0 &&
child.type !== 'function' &&
child.type !== 'arrow_function') {
_walkForThisAssignments(child, ctx, className, seen);
}
}
}
function _parseJSClass(node) {
const name = node.namedChildren.find((c) => c.type === 'identifier')?.text || 'Unknown';
let superclass = null;
for (const child of node.namedChildren) {
if (child.type === 'class_heritage') {
// tree-sitter-javascript: class_heritage → identifier (直接子节点, 无 extends_clause 包装)
const typeNode = child.namedChildren.find((c) => c.type === 'identifier' || c.type === 'member_expression');
if (typeNode) {
superclass = typeNode.text;
}
}
}
return {
name,
kind: 'class',
superclass,
protocols: [],
line: node.startPosition.row + 1,
endLine: node.endPosition.row + 1,
};
}
function _parseJSFunction(node, className) {
const name = node.namedChildren.find((c) => c.type === 'identifier')?.text || 'unknown';
const body = node.namedChildren.find((c) => c.type === 'statement_block');
const isAsync = node.text.trimStart().startsWith('async');
return {
name,
className,
isClassMethod: false,
isAsync,
bodyLines: body ? body.endPosition.row - body.startPosition.row + 1 : 0,
complexity: body ? _estimateComplexity(body) : 1,
nestingDepth: body ? _maxNesting(body, 0) : 0,
line: node.startPosition.row + 1,
kind: 'definition',
};
}
function _parseJSVariableDecl(node, ctx, parentClassName) {
for (const child of node.namedChildren) {
if (child.type === 'variable_declarator') {
const nameNode = child.namedChildren.find((c) => c.type === 'identifier');
const valueNode = child.namedChildren.find((c) => c.type === 'arrow_function' || c.type === 'function');
if (nameNode && valueNode) {
const body = valueNode.namedChildren.find((c) => c.type === 'statement_block');
ctx.methods.push({
name: nameNode.text,
className: parentClassName,
isClassMethod: false,
bodyLines: body ? body.endPosition.row - body.startPosition.row + 1 : 0,
complexity: body ? _estimateComplexity(body) : 1,
nestingDepth: body ? _maxNesting(body, 0) : 0,
line: child.startPosition.row + 1,
kind: 'definition',
});
}
}
}
}
function detectJSPatterns(root, lang, methods, properties, classes) {
const patterns = [];
// 按 className 分组
const methodsByClass = new Map();
const propsByClass = new Map();
for (const m of methods) {
const k = m.className || '';
if (!methodsByClass.has(k)) {
methodsByClass.set(k, []);
}
methodsByClass.get(k).push(m);
}
for (const p of properties) {
const k = p.className || '';
if (!propsByClass.has(k)) {
propsByClass.set(k, []);
}
propsByClass.get(k).push(p);
}
for (const cls of classes) {
const clsMethods = methodsByClass.get(cls.name) || [];
const clsProps = propsByClass.get(cls.name) || [];
// ── Singleton: static getInstance() / static instance ──
const hasGetInstance = clsMethods.some((m) => m.isClassMethod && /^getInstance$|^shared$/.test(m.name));
const hasStaticInstance = clsProps.some((p) => p.isStatic && /^instance$|^shared$|^default$/.test(p.name));
if (hasGetInstance || hasStaticInstance) {
patterns.push({ type: 'singleton', className: cls.name, line: cls.line, confidence: 0.9 });
}
// ── Observer / EventEmitter ──
const emitMethods = clsMethods.filter((m) => /^on$|^emit$|^addEventListener$|^removeEventListener$|^subscribe$|^addListener$/.test(m.name));
if (emitMethods.length >= 2) {
patterns.push({ type: 'observer', className: cls.name, line: cls.line, confidence: 0.85 });
}
// ── Middleware ──
if (/middleware|interceptor/i.test(cls.name) ||
clsMethods.some((m) => /^use$|^handle$/.test(m.name) && m.isClassMethod === false)) {
patterns.push({ type: 'middleware', className: cls.name, line: cls.line, confidence: 0.8 });
}
}
// 自由函数级别的模式
for (const m of methods) {
if (/^use[A-Z]/.test(m.name) && !m.className) {
patterns.push({ type: 'react-hook', methodName: m.name, line: m.line, confidence: 0.9 });
}
if (/^create[A-Z]|^make[A-Z]/.test(m.name)) {
patterns.push({
type: 'factory',
className: m.className,
methodName: m.name,
line: m.line,
confidence: 0.8,
});
}
}
return patterns;
}
// ── 工具函数 ──
function _estimateComplexity(node) {
let complexity = 1;
const BRANCH_TYPES = new Set([
'if_statement',
'for_statement',
'for_in_statement',
'while_statement',
'switch_statement',
'case_clause',
'catch_clause',
'ternary_expression',
]);
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',
'for_in_statement',
'while_statement',
'switch_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;
}
// ── 插件导出 ──
let _grammar = null;
function getGrammar() {
return _grammar;
}
export function setGrammar(grammar) {
_grammar = grammar;
}
export const plugin = {
getGrammar,
walk: walkJavaScript,
detectPatterns: detectJSPatterns,
extensions: ['.js', '.jsx', '.mjs', '.cjs'],
};