autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
625 lines (624 loc) • 23.3 kB
JavaScript
/**
* @module lang-swift
* @description Swift AST Walker 插件 - 从 AstAnalyzer.js 迁移
*
* Phase 5: 新增 ImportRecord 结构化导入 + extractCallSites 调用点提取
*/
import { ImportRecord } from '../analysis/ImportRecord.js';
// ── Swift AST 遍历 ──
function walkSwift(root, ctx) {
_walkSwiftNode(root, ctx, null);
}
function _walkSwiftNode(node, ctx, parentClassName) {
for (let i = 0; i < node.namedChildCount; i++) {
const child = node.namedChild(i);
switch (child.type) {
case 'import_declaration': {
const mod = child.namedChildren.find((c) => c.type === 'identifier' || c.type === 'simple_identifier');
if (mod) {
ctx.imports.push(new ImportRecord(mod.text, { symbols: ['*'], alias: mod.text, kind: 'namespace' }));
}
break;
}
case 'class_declaration':
case 'struct_declaration':
case 'enum_declaration': {
// tree-sitter-swift maps protocol/extension to class_declaration too.
// Detect the actual keyword child and dispatch accordingly.
const keyword = _findKeywordChild(child);
if (keyword === 'protocol') {
const protoInfo = _parseSwiftProtocol(child);
ctx.protocols.push(protoInfo);
break;
}
if (keyword === 'extension') {
const extInfo = _parseSwiftExtension(child);
ctx.categories.push(extInfo);
const extBody = child.namedChildren.find((c) => c.type === 'class_body' || c.type === 'extension_body');
if (extBody) {
_walkSwiftNode(extBody, ctx, extInfo.className);
}
break;
}
const classInfo = _parseSwiftTypeDecl(child);
ctx.classes.push(classInfo);
const body = child.namedChildren.find((c) => c.type === 'class_body' ||
c.type === 'struct_body' ||
c.type === 'enum_body' ||
c.type === 'enum_class_body');
if (body) {
_walkSwiftNode(body, ctx, classInfo.name);
}
break;
}
case 'protocol_declaration': {
const protoInfo = _parseSwiftProtocol(child);
ctx.protocols.push(protoInfo);
break;
}
case 'extension_declaration': {
const extInfo = _parseSwiftExtension(child);
ctx.categories.push(extInfo);
const body = child.namedChildren.find((c) => c.type === 'extension_body');
if (body) {
_walkSwiftNode(body, ctx, extInfo.className);
}
break;
}
case 'function_declaration': {
const m = _parseSwiftFunction(child, parentClassName);
ctx.methods.push(m);
break;
}
case 'property_declaration': {
const p = _parseSwiftProperty(child, parentClassName);
if (p) {
ctx.properties.push(p);
}
break;
}
default: {
if (child.namedChildCount > 0 &&
!['function_body', 'computed_property', 'willSet_didSet_block'].includes(child.type)) {
_walkSwiftNode(child, ctx, parentClassName);
}
}
}
}
}
/** Detect the actual Swift keyword from a class_declaration node's children. */
const _swiftKeywords = ['struct', 'class', 'enum', 'actor', 'protocol', 'extension'];
function _findKeywordChild(node) {
for (let i = 0; i < node.childCount; i++) {
const childType = node.child(i).type;
if (_swiftKeywords.includes(childType)) {
return childType;
}
}
return 'class';
}
function _parseSwiftTypeDecl(node) {
const name = node.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'simple_identifier')?.text || 'Unknown';
// tree-sitter-swift maps struct/class/enum/actor all to `class_declaration`.
// Detect the actual kind from the keyword child node (e.g. struct="struct").
const kind = _findKeywordChild(node);
const _superclass = null;
const protocols = [];
for (const child of node.namedChildren) {
if (child.type === 'inheritance_specifier') {
const typeNode = child.namedChildren.find((c) => c.type === 'user_type');
if (typeNode) {
const typeName = typeNode.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'simple_identifier')?.text;
if (typeName) {
protocols.push(typeName);
}
}
}
}
let detectedSuper = null;
if (protocols.length > 0 && kind === 'class') {
const first = protocols[0];
if (!first.endsWith('Protocol') &&
!first.endsWith('Delegate') &&
!first.endsWith('DataSource')) {
detectedSuper = first;
}
}
return {
name,
kind,
superclass: detectedSuper,
protocols,
line: node.startPosition.row + 1,
endLine: node.endPosition.row + 1,
};
}
function _parseSwiftProtocol(node) {
const name = node.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'simple_identifier')?.text || 'Unknown';
const inherits = [];
for (const child of node.namedChildren) {
if (child.type === 'inheritance_specifier') {
const t = child.namedChildren.find((c) => c.type === 'user_type');
if (t) {
const n = t.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'simple_identifier');
if (n) {
inherits.push(n.text);
}
}
}
}
return { name, inherits, line: node.startPosition.row + 1 };
}
function _parseSwiftExtension(node) {
const className = node.namedChildren.find((c) => c.type === 'user_type' || c.type === 'type_identifier')
?.text || 'Unknown';
const protocols = [];
for (const child of node.namedChildren) {
if (child.type === 'inheritance_specifier') {
const t = child.namedChildren.find((c) => c.type === 'user_type');
if (t) {
const n = t.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'simple_identifier');
if (n) {
protocols.push(n.text);
}
}
}
}
const methods = [];
const body = node.namedChildren.find((c) => c.type === 'extension_body' || c.type === 'class_body');
if (body) {
for (const child of body.namedChildren) {
if (child.type === 'function_declaration') {
methods.push(_parseSwiftFunction(child, className));
}
}
}
return {
className,
categoryName: protocols.length > 0 ? protocols.join('+') : 'ext',
protocols,
methods,
line: node.startPosition.row + 1,
};
}
function _parseSwiftFunction(node, className) {
const name = node.namedChildren.find((c) => c.type === 'simple_identifier')?.text || 'unknown';
const modifiers = [];
for (const child of node.namedChildren) {
if (child.type === 'modifiers' || child.type === 'modifier') {
modifiers.push(child.text);
}
}
const isClassMethod = modifiers.some((m) => /\b(static|class)\b/.test(m));
const body = node.namedChildren.find((c) => c.type === 'function_body');
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,
bodyLines,
complexity,
nestingDepth,
line: node.startPosition.row + 1,
kind: 'definition',
};
}
function _parseSwiftProperty(node, className) {
const name = node.namedChildren.find((c) => c.type === 'simple_identifier' || c.type === 'pattern')
?.text || null;
if (!name) {
return null;
}
const modifiers = [];
for (const child of node.namedChildren) {
if (child.type === 'modifiers' || child.type === 'modifier') {
modifiers.push(child.text);
}
}
const isStatic = modifiers.some((m) => /\b(static|class)\b/.test(m));
const isLet = node.text.includes(' let ');
return {
name,
className,
isStatic,
isConstant: isLet,
attributes: modifiers,
line: node.startPosition.row + 1,
};
}
// ── Swift 模式检测 ──
function detectSwiftPatterns(root, _lang, methods, properties, classes) {
const patterns = [];
// Index properties by className for quick lookup
const propsByClass = new Map();
for (const p of properties) {
if (p.className) {
const arr = propsByClass.get(p.className) || [];
arr.push(p);
propsByClass.set(p.className, arr);
}
}
// Index methods by className
const methodsByClass = new Map();
for (const m of methods) {
if (m.className && m.kind === 'definition') {
const arr = methodsByClass.get(m.className) || [];
arr.push(m);
methodsByClass.set(m.className, arr);
}
}
for (const cls of classes) {
const kind = cls.kind;
const className = cls.name;
const clsProps = propsByClass.get(className) || [];
const clsMethods = methodsByClass.get(className) || [];
// ── 1. Singleton: `static let shared = ...` 属性 ──
const sharedProp = clsProps.find((p) => p.isStatic &&
(p.isConstant || p.isConst) &&
/^shared$|^default$|^instance$|^current$/.test(p.name));
if (sharedProp) {
patterns.push({
type: 'singleton',
className,
propertyName: sharedProp.name,
line: sharedProp.line,
confidence: 0.95,
});
}
// ── 2. Delegate: `weak var delegate` 属性或 protocol 命名以 Delegate/DataSource 结尾 ──
for (const p of clsProps) {
if (/delegate/i.test(p.name) && !p.name.startsWith('_')) {
patterns.push({
type: 'delegate',
className,
propertyName: p.name,
isWeakRef: /weak/.test(`${(p.attributes || []).join(' ')} ${p.modifiers || ''}`),
line: p.line,
confidence: 0.95,
});
}
}
// ── 3. Factory: `static func make/create/from/build` ──
for (const m of clsMethods) {
if (m.isClassMethod && /^make|^create|^from|^build/.test(m.name)) {
patterns.push({
type: 'factory',
className,
methodName: m.name,
line: m.line,
confidence: 0.85,
});
}
}
// ── 4. Actor: Swift concurrency primitive ──
if (kind === 'actor') {
patterns.push({
type: 'actor',
className,
line: cls.line,
confidence: 0.95,
});
}
// ── 5. Value type (struct): immutable-by-default semantics ──
if (kind === 'struct' && !className.endsWith('Error')) {
// Only flag structs with methods (not pure data containers)
if (clsMethods.length > 0) {
patterns.push({
type: 'value-type',
className,
methodCount: clsMethods.length,
line: cls.line,
confidence: 0.9,
});
}
}
// ── 6. Protocol-oriented default implementation ──
// (detected via categories/extensions that match a protocol name)
// Handled below after the class loop.
// ── 7. ViewModel: class name ends with ViewModel ──
if (/ViewModel$/.test(className)) {
patterns.push({
type: 'viewmodel',
className,
line: cls.line,
confidence: 0.9,
});
}
// ── 8. Coordinator pattern ──
if (/Coordinator$/.test(className)) {
patterns.push({
type: 'coordinator',
className,
line: cls.line,
confidence: 0.85,
});
}
// ── 9. Error type: enum conforming to Error ──
if (kind === 'enum') {
const conformsError = (cls.protocols || []).some((p) => p === 'Error' || p === 'LocalizedError');
if (conformsError || className.endsWith('Error')) {
patterns.push({
type: 'error-type',
className,
line: cls.line,
confidence: 0.9,
});
}
}
// ── 10. Middleware / Interceptor pattern ──
if (/Middleware$|Interceptor$/.test(className)) {
patterns.push({
type: 'middleware',
className,
line: cls.line,
confidence: 0.85,
});
}
}
// ── 11. Observer: methods matching didChange/willChange/observe/subscribe ──
for (const m of methods) {
if (m.kind !== 'definition') {
continue;
}
if (/^didChange|^willChange|^observe|^addObserver|^subscribe|^on[A-Z]/.test(m.name)) {
patterns.push({
type: 'observer',
className: m.className,
methodName: m.name,
line: m.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',
]);
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;
}
// ── Swift Call Site 提取 (Phase 5) ───────────────────────────
/**
* 从 Swift AST root 提取所有调用点
* 遍历 function_declaration 中的 function_body → call_expression
*/
function extractCallSitesSwift(root, ctx, _lang) {
const scopes = _collectSwiftScopes(root);
for (const scope of scopes) {
_extractSwiftCallSitesFromBody(scope.body, scope.className, scope.methodName, ctx);
}
}
/** 递归收集 Swift 中所有函数体作用域 */
function _collectSwiftScopes(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 === 'struct_declaration' ||
child.type === 'enum_declaration') {
const name = child.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'simple_identifier')?.text;
const body = child.namedChildren.find((c) => c.type === 'class_body' ||
c.type === 'struct_body' ||
c.type === 'enum_body' ||
c.type === 'enum_class_body');
if (body) {
visit(body, name || className);
}
}
else if (child.type === 'extension_declaration') {
const extName = child.namedChildren.find((c) => c.type === 'user_type' || c.type === 'type_identifier')?.text;
const body = child.namedChildren.find((c) => c.type === 'extension_body');
if (body) {
visit(body, extName || className);
}
}
else if (child.type === 'function_declaration') {
const name = child.namedChildren.find((c) => c.type === 'simple_identifier')?.text || 'unknown';
const body = child.namedChildren.find((c) => c.type === 'function_body');
if (body) {
scopes.push({ body, className, methodName: name });
}
}
else if (child.type === 'property_declaration') {
// computed property with getter
const computed = child.namedChildren.find((c) => c.type === 'computed_property' || c.type === 'willSet_didSet_block');
if (computed) {
const propName = child.namedChildren.find((c) => c.type === 'simple_identifier' || c.type === 'pattern')?.text;
scopes.push({ body: computed, className, methodName: `get_${propName || 'prop'}` });
}
}
}
}
visit(root, null);
return scopes;
}
/** 从 Swift function body 中递归提取调用点 */
function _extractSwiftCallSitesFromBody(bodyNode, className, methodName, ctx) {
if (!bodyNode) {
return;
}
const SWIFT_NOISE = new Set([
'print',
'debugPrint',
'dump',
'fatalError',
'precondition',
'preconditionFailure',
'assert',
'assertionFailure',
'NSLog',
'min',
'max',
'abs',
'stride',
'zip',
'type',
]);
function walk(node) {
if (!node || node.type === 'ERROR' || node.isMissing) {
return;
}
// call_expression in Swift tree-sitter
if (node.type === 'call_expression') {
const func = node.namedChildren[0];
if (!func) {
walkChildren(node);
return;
}
let callee, receiver = null, receiverType = null, callType;
let isAwait = false;
// Check if parent is await
if (node.parent?.type === 'await_expression') {
isAwait = true;
}
if (func.type === 'navigation_expression' || func.type === 'member_access') {
// obj.method() or Type.staticMethod()
const parts = func.text.split('.');
if (parts.length >= 2) {
receiver = parts.slice(0, -1).join('.');
callee = parts[parts.length - 1];
if (receiver === 'self') {
receiverType = className;
callType = 'method';
}
else if (receiver === 'super') {
receiverType = className;
callType = 'super';
}
else if (/^[A-Z]/.test(receiver)) {
receiverType = receiver;
callType = 'static';
}
else {
callType = 'method';
}
}
else {
callee = func.text;
callType = 'function';
}
}
else if (func.type === 'simple_identifier' || func.type === 'identifier') {
callee = func.text;
if (SWIFT_NOISE.has(callee)) {
walkChildren(node);
return;
}
// PascalCase → constructor (Swift initializer)
callType = /^[A-Z]/.test(callee) ? 'constructor' : 'function';
if (callType === 'constructor') {
receiverType = callee;
}
}
else {
callee = func.text?.slice(0, 80) || 'unknown';
callType = 'function';
}
// Count arguments
const callSuffix = node.namedChildren.find((c) => c.type === 'call_suffix' || c.type === 'value_arguments');
const argCount = callSuffix ? callSuffix.namedChildCount : 0;
ctx.callSites.push({
callee,
callerMethod: methodName,
callerClass: className,
callType,
receiver,
receiverType,
argCount,
line: node.startPosition.row + 1,
isAwait,
});
// walk arguments for nested calls
if (callSuffix) {
walkChildren(callSuffix);
}
return;
}
// try expression → walk into children
if (node.type === 'try_expression' || node.type === 'await_expression') {
walkChildren(node);
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: walkSwift,
detectPatterns: detectSwiftPatterns,
extractCallSites: extractCallSitesSwift,
extensions: ['.swift'],
};