autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
417 lines (416 loc) • 14.6 kB
JavaScript
/**
* @module lang-objc
* @description ObjC AST Walker 插件 - 从 AstAnalyzer.js 迁移
*/
// ── ObjC AST 遍历 ──
function walkObjC(root, ctx) {
for (let i = 0; i < root.namedChildCount; i++) {
const node = root.namedChild(i);
switch (node.type) {
case 'preproc_include': {
const pathNode = node.namedChildren.find((c) => c.type === 'string_literal' || c.type === 'system_lib_string');
if (pathNode) {
ctx.imports.push(pathNode.text.replace(/^["<]|[">]$/g, ''));
}
break;
}
case 'class_interface': {
const classInfo = _parseObjCInterface(node);
if (classInfo.isCategory) {
ctx.categories.push(classInfo);
}
else {
ctx.classes.push(classInfo);
}
for (const child of node.namedChildren) {
if (child.type === 'method_declaration') {
ctx.methods.push(_parseObjCMethodDecl(child, classInfo.name));
}
else if (child.type === 'property_declaration') {
ctx.properties.push(_parseObjCProperty(child, classInfo.name));
}
}
break;
}
case 'protocol_declaration': {
ctx.protocols.push(_parseObjCProtocol(node));
break;
}
case 'class_implementation': {
const implName = _findIdentifier(node);
for (const child of node.namedChildren) {
if (child.type === 'implementation_definition') {
for (const implChild of child.namedChildren) {
if (implChild.type === 'method_definition') {
const m = _parseObjCMethodDef(implChild, implName);
ctx.methods.push(m);
}
}
}
}
break;
}
case 'category_implementation': {
const catImplName = _findIdentifier(node);
for (const child of node.namedChildren) {
if (child.type === 'implementation_definition') {
for (const implChild of child.namedChildren) {
if (implChild.type === 'method_definition') {
ctx.methods.push(_parseObjCMethodDef(implChild, catImplName));
}
}
}
}
break;
}
}
}
}
function _parseObjCInterface(node) {
const identifiers = node.namedChildren.filter((c) => c.type === 'identifier');
const name = identifiers[0]?.text || 'Unknown';
const isCategory = node.text.includes('(') &&
identifiers.length >= 2 &&
node.text.indexOf('(') < node.text.indexOf(identifiers[1].text);
let superclass = null;
let categoryName = null;
if (isCategory) {
categoryName = identifiers[1]?.text;
}
else if (identifiers.length >= 2) {
superclass = identifiers[1]?.text;
}
const protocols = [];
const protoList = node.namedChildren.find((c) => c.type === 'parameterized_arguments');
if (protoList) {
for (const child of protoList.namedChildren) {
if (child.type === 'type_name') {
const ti = child.namedChildren.find((c) => c.type === 'type_identifier');
if (ti) {
protocols.push(ti.text);
}
}
}
}
const methods = [];
for (const child of node.namedChildren) {
if (child.type === 'method_declaration') {
methods.push(_parseObjCMethodDecl(child, name));
}
}
const result = {
name,
superclass,
protocols,
isCategory,
line: node.startPosition.row + 1,
endLine: node.endPosition.row + 1,
};
if (isCategory) {
result.className = name;
result.categoryName = categoryName;
result.methods = methods;
}
return result;
}
function _parseObjCProtocol(node) {
const name = _findIdentifier(node) || 'Unknown';
const inherits = [];
const protoRef = node.namedChildren.find((c) => c.type === 'protocol_reference_list');
if (protoRef) {
for (const child of protoRef.namedChildren) {
if (child.type === 'identifier') {
inherits.push(child.text);
}
}
}
const methods = [];
let isOptional = false;
for (const child of node.namedChildren) {
if (child.type === 'qualified_protocol_interface_declaration') {
isOptional = true;
for (const sub of child.namedChildren) {
if (sub.type === 'method_declaration') {
const m = _parseObjCMethodDecl(sub, name);
m.isOptional = true;
methods.push(m);
}
}
}
else if (child.type === 'method_declaration') {
const m = _parseObjCMethodDecl(child, name);
m.isOptional = isOptional;
methods.push(m);
}
}
return { name, inherits, methods, line: node.startPosition.row + 1 };
}
function _parseObjCMethodDecl(node, className) {
const isClassMethod = node.text.trimStart().startsWith('+');
const name = _findIdentifier(node) || 'unknown';
const params = [];
for (const child of node.namedChildren) {
if (child.type === 'method_parameter') {
const paramName = _findIdentifier(child);
params.push(paramName || '?');
}
}
let returnType = 'void';
const methodType = node.namedChildren.find((c) => c.type === 'method_type');
if (methodType) {
const tn = methodType.namedChildren.find((c) => c.type === 'type_name');
if (tn) {
const ti = tn.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'primitive_type');
if (ti) {
returnType = ti.text;
}
}
}
const selector = params.length > 0
? `${name}:${params
.slice(1)
.map((p) => `${p}:`)
.join('')}`
: name;
return {
name,
selector,
className,
isClassMethod,
returnType,
paramCount: params.length,
line: node.startPosition.row + 1,
kind: 'declaration',
};
}
function _parseObjCMethodDef(node, className) {
const isClassMethod = node.text.trimStart().startsWith('+');
const name = _findIdentifier(node) || 'unknown';
const params = [];
for (const child of node.namedChildren) {
if (child.type === 'method_parameter') {
const paramName = _findIdentifier(child);
params.push(paramName || '?');
}
}
const body = node.namedChildren.find((c) => c.type === 'compound_statement');
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,
isClassMethod,
paramCount: params.length,
bodyLines,
complexity,
nestingDepth,
line: node.startPosition.row + 1,
kind: 'definition',
};
}
function _parseObjCProperty(node, className) {
const attrs = [];
const attrDecl = node.namedChildren.find((c) => c.type === 'property_attributes_declaration');
if (attrDecl) {
for (const attr of attrDecl.namedChildren) {
if (attr.type === 'property_attribute') {
const id = attr.namedChildren.find((c) => c.type === 'identifier');
if (id) {
attrs.push(id.text);
}
}
}
}
let propName = 'unknown';
let propType = 'id';
const structDecl = node.namedChildren.find((c) => c.type === 'struct_declaration');
if (structDecl) {
const ti = structDecl.namedChildren.find((c) => c.type === 'type_identifier');
if (ti) {
propType = ti.text;
}
const sd = structDecl.namedChildren.find((c) => c.type === 'struct_declarator');
if (sd) {
const findName = (n) => {
if (n.type === 'identifier') {
return n.text;
}
for (let j = 0; j < n.namedChildCount; j++) {
const r = findName(n.namedChild(j));
if (r) {
return r;
}
}
return null;
};
propName = findName(sd) || propName;
}
}
return {
name: propName,
type: propType,
attributes: attrs,
className,
line: node.startPosition.row + 1,
};
}
// ── ObjC 模式检测 ──
function detectObjCPatterns(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: +sharedInstance / +shared / +defaultManager ──
const singletonMethod = clsMethods.find((m) => m.isClassMethod && /^shared|^default|^current|^instance$/.test(m.name));
if (singletonMethod) {
patterns.push({
type: 'singleton',
className: cls.name,
methodName: singletonMethod.name,
line: singletonMethod.line,
confidence: 0.9,
});
}
// ── Delegate: @property (weak) id<XXXDelegate> delegate ──
for (const p of clsProps) {
if (/delegate/i.test(p.name)) {
const isWeak = (p.attributes || []).some((a) => a === 'weak');
patterns.push({
type: 'delegate',
className: cls.name,
propertyName: p.name,
isWeakRef: isWeak,
line: p.line,
confidence: 0.95,
});
}
if (/dataSource/i.test(p.name)) {
patterns.push({
type: 'delegate',
className: cls.name,
propertyName: p.name,
isWeakRef: true,
line: p.line,
confidence: 0.85,
});
}
}
// ── Factory: +classWithXxx / +xxxWithYyy (class factory methods) ──
for (const m of clsMethods) {
if (m.isClassMethod && /With[A-Z]/.test(m.name)) {
patterns.push({
type: 'factory',
className: cls.name,
methodName: m.name,
line: m.line,
confidence: 0.8,
});
}
}
// ── KVO Observer: observeValueForKeyPath / addObserver ──
const hasKVO = clsMethods.some((m) => /^observeValueForKeyPath$|^addObserver$|^removeObserver$/.test(m.name));
if (hasKVO) {
patterns.push({ type: 'observer', className: cls.name, line: cls.line, confidence: 0.85 });
}
// ── Notification Observer: NSNotificationCenter pattern ──
const hasNSNotif = clsMethods.some((m) => /notification|handleNotification|didReceiveNotification/i.test(m.name));
if (hasNSNotif) {
patterns.push({ type: 'observer', className: cls.name, line: cls.line, confidence: 0.7 });
}
}
return patterns;
}
// ── 工具函数 ──
function _findIdentifier(node) {
for (let i = 0; i < node.namedChildCount; i++) {
const child = node.namedChild(i);
if (child.type === 'identifier' ||
child.type === 'simple_identifier' ||
child.type === 'type_identifier') {
return child.text;
}
}
return null;
}
function _estimateComplexity(node) {
let complexity = 1;
const BRANCH_TYPES = new Set([
'if_statement',
'for_statement',
'for_in_statement',
'while_statement',
'switch_statement',
'case_statement',
'catch_clause',
'conditional_expression',
'ternary_expression',
'guard_statement',
'for_in_expression',
]);
function walk(n) {
if (BRANCH_TYPES.has(n.type)) {
complexity++;
}
if (n.type === 'binary_expression') {
const op = n.children?.find((c) => c.type === '&&' || c.type === '||' || 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: walkObjC,
detectPatterns: detectObjCPatterns,
extensions: ['.m', '.h', '.mm'],
};