autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
975 lines (974 loc) • 38.1 kB
JavaScript
/**
* @module lang-dart
* @description Dart AST Walker 插件
*
* 提取: class, mixin, extension, enum, typedef, function, method, field, import
* 模式: Flutter Widget (Stateless/Stateful/Consumer), Factory, Singleton,
* Builder, BLoC/Cubit, Provider/Riverpod, Freezed
*
* Phase 5: 新增 ImportRecord 结构化导入 + extractCallSites 调用点提取
*
* 注意: tree-sitter-dart 目前尚无兼容 tree-sitter ≥0.25 的稳定版。
* 已迁移至 web-tree-sitter (WASM),无原生编译依赖。
*/
import { ImportRecord } from '../analysis/ImportRecord.js';
function walkDart(root, ctx) {
_walkNode(root, ctx, null);
}
function _walkNode(node, ctx, parentClassName) {
for (let i = 0; i < node.namedChildCount; i++) {
const child = node.namedChild(i);
switch (child.type) {
case 'import_or_export': // tree-sitter-dart import 节点
case 'import_specification':
case 'library_import': {
// tree-sitter-dart AST 嵌套: import_or_export > library_import > import_specification
// URI 节点埋在深层,直接从文本中用正则提取更可靠
const text = child.text;
const pathMatch = text.match(/import\s+(['"])(.+?)\1/);
if (pathMatch) {
const importPath = pathMatch[2];
// Dart: import 'pkg' as alias
const asMatch = text.match(/\bas\s+(\w+)/);
const alias = asMatch ? asMatch[1] : null;
// Dart: import 'pkg' show A, B
const showClause = text.match(/\bshow\s+([\w\s,]+)/);
// Dart: import 'pkg' hide A, B (暂不使用,记录备查)
// const hideClause = text.match(/\bhide\s+([\w\s,]+)/);
let symbols = ['*'];
let kind = 'namespace';
if (showClause) {
symbols = showClause[1]
.split(',')
.map((s) => s.trim())
.filter(Boolean);
kind = 'named';
}
ctx.imports.push(new ImportRecord(importPath, { symbols, alias, kind }));
}
break;
}
case 'class_definition': {
_parseClassDef(child, ctx);
break;
}
case 'mixin_declaration': {
_parseMixinDecl(child, ctx);
break;
}
case 'extension_declaration': {
_parseExtensionDecl(child, ctx);
break;
}
case 'enum_declaration': {
_parseEnumDecl(child, ctx);
break;
}
case 'type_alias': {
const nameNode = child.namedChildren.find((c) => c.type === 'identifier' || c.type === 'type_identifier');
if (nameNode) {
ctx.classes.push({
name: nameNode.text,
kind: 'typedef',
line: child.startPosition.row + 1,
endLine: child.endPosition.row + 1,
});
}
break;
}
case 'function_signature': {
// 顶层 function_signature 同样需要向前看兄弟 function_body
const nextSib = node.namedChild(i + 1);
const bodyNode = nextSib?.type === 'function_body' ? nextSib : null;
const func = _parseDartMethod(child, parentClassName, bodyNode);
if (func) {
ctx.methods.push(func);
}
break;
}
case 'function_definition':
case 'top_level_definition': {
const func = _parseFunctionDef(child, parentClassName);
if (func) {
ctx.methods.push(func);
}
break;
}
case 'initialized_variable_definition':
case 'static_final_declaration':
case 'declaration': {
const prop = _parsePropertyDecl(child, parentClassName);
if (prop) {
ctx.properties.push(prop);
}
break;
}
default: {
// 递归进入未明确处理的容器节点
if (child.namedChildCount > 0 &&
!['function_body', 'block', 'string_literal', 'arguments'].includes(child.type)) {
_walkNode(child, ctx, parentClassName);
}
}
}
}
}
// ── Class ────────────────────────────────────────────────────
function _parseClassDef(node, ctx) {
const nameNode = node.namedChildren.find((c) => c.type === 'identifier' || c.type === 'type_identifier');
const name = nameNode?.text || 'Unknown';
// 检查修饰符
const isAbstract = node.text.trimStart().startsWith('abstract');
const isSealed = node.text.trimStart().startsWith('sealed');
// 父类 (extends)
let superclass = null;
const superClause = node.namedChildren.find((c) => c.type === 'superclass');
if (superClause) {
const superType = superClause.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'identifier');
superclass = superType?.text || null;
}
// 实现的接口 (implements)
const implClause = node.namedChildren.find((c) => c.type === 'interfaces');
const protocols = [];
if (implClause) {
for (let i = 0; i < implClause.namedChildCount; i++) {
const t = implClause.namedChild(i);
if (t.type === 'type_identifier' || t.type === 'identifier') {
protocols.push(t.text);
}
}
}
// Mixin (with)
const mixinClause = node.namedChildren.find((c) => c.type === 'mixins');
const mixins = [];
if (mixinClause) {
for (let i = 0; i < mixinClause.namedChildCount; i++) {
const t = mixinClause.namedChild(i);
if (t.type === 'type_identifier' || t.type === 'identifier') {
mixins.push(t.text);
}
}
}
let kind = 'class';
if (isAbstract) {
kind = 'abstract-class';
}
if (isSealed) {
kind = 'sealed-class';
}
ctx.classes.push({
name,
kind,
superclass,
protocols,
mixins,
line: node.startPosition.row + 1,
endLine: node.endPosition.row + 1,
});
// Walk class body
const body = node.namedChildren.find((c) => c.type === 'class_body');
if (body) {
_walkClassBody(body, ctx, name);
}
}
function _walkClassBody(body, ctx, className) {
for (let i = 0; i < body.namedChildCount; i++) {
const child = body.namedChild(i);
switch (child.type) {
case 'method_signature':
case 'getter_signature':
case 'setter_signature': {
// tree-sitter-dart: method_signature 和 function_body 是兄弟节点
// 需要向前看下一个兄弟,合并传递给解析器
const nextSibling = body.namedChild(i + 1);
const bodyNode = nextSibling?.type === 'function_body' ? nextSibling : null;
const method = _parseDartMethod(child, className, bodyNode);
if (method) {
ctx.methods.push(method);
}
break;
}
case 'function_definition': {
const method = _parseFunctionDef(child, className);
if (method) {
ctx.methods.push(method);
}
break;
}
case 'declaration':
case 'initialized_variable_definition':
case 'static_final_declaration': {
const prop = _parsePropertyDecl(child, className);
if (prop) {
ctx.properties.push(prop);
}
break;
}
case 'constructor_signature': {
const nameNode = child.namedChildren.find((c) => c.type === 'identifier' || c.type === 'constructor_name');
const isFactory = child.text.trimStart().startsWith('factory');
ctx.methods.push({
name: nameNode?.text || className,
className,
isClassMethod: false,
isExported: true,
isFactory,
paramCount: _countChildParams(child),
line: child.startPosition.row + 1,
kind: isFactory ? 'factory' : 'constructor',
});
break;
}
default: {
if (child.namedChildCount > 0) {
_walkNode(child, ctx, className);
}
}
}
}
}
// ── Mixin ────────────────────────────────────────────────────
function _parseMixinDecl(node, ctx) {
const nameNode = node.namedChildren.find((c) => c.type === 'identifier' || c.type === 'type_identifier');
const name = nameNode?.text || 'Unknown';
const onClause = node.namedChildren.find((c) => c.type === 'on_clause' || c.type === 'superclass');
const constraints = [];
if (onClause) {
for (let i = 0; i < onClause.namedChildCount; i++) {
const t = onClause.namedChild(i);
if (t.type === 'type_identifier' || t.type === 'identifier') {
constraints.push(t.text);
}
}
}
ctx.classes.push({
name,
kind: 'mixin',
superclass: constraints[0] || null,
protocols: constraints.slice(1),
line: node.startPosition.row + 1,
endLine: node.endPosition.row + 1,
});
const body = node.namedChildren.find((c) => c.type === 'class_body');
if (body) {
_walkClassBody(body, ctx, name);
}
}
// ── Extension ────────────────────────────────────────────────
function _parseExtensionDecl(node, ctx) {
const nameNode = node.namedChildren.find((c) => c.type === 'identifier' || c.type === 'type_identifier');
const name = nameNode?.text || 'anonymous_extension';
// on Type
const onType = node.namedChildren.find((c) => c.type === 'type_identifier' && c !== nameNode);
ctx.categories.push({
name,
targetClass: onType?.text || null,
line: node.startPosition.row + 1,
endLine: node.endPosition.row + 1,
});
const body = node.namedChildren.find((c) => c.type === 'class_body' || c.type === 'extension_body');
if (body) {
_walkClassBody(body, ctx, name);
}
}
// ── Enum ─────────────────────────────────────────────────────
function _parseEnumDecl(node, ctx) {
const nameNode = node.namedChildren.find((c) => c.type === 'identifier' || c.type === 'type_identifier');
const name = nameNode?.text || 'Unknown';
ctx.classes.push({
name,
kind: 'enum',
line: node.startPosition.row + 1,
endLine: node.endPosition.row + 1,
});
}
// ── Function / Method ────────────────────────────────────────
/**
* 解析 Dart class body 中的 method_signature / getter_signature / setter_signature。
* tree-sitter-dart 中这些节点的 identifier 嵌在 function_signature 子节点内,
* 且 function_body 是兄弟节点而非子节点。
*/
function _parseDartMethod(sigNode, className, bodyNode) {
// 递归搜索第一个 identifier(跳过类型标识符)
const name = _findMethodName(sigNode);
if (!name) {
return null;
}
const text = sigNode.text;
const isStatic = text.includes('static ');
const isAsync = bodyNode
? bodyNode.text.includes('async') || bodyNode.text.includes('async*')
: false;
const isOverride = text.includes('@override');
const bodyLines = bodyNode ? bodyNode.endPosition.row - bodyNode.startPosition.row + 1 : 0;
const complexity = bodyNode ? _estimateComplexity(bodyNode) : 1;
const nestingDepth = bodyNode ? _maxNesting(bodyNode, 0) : 0;
let kind = 'definition';
if (sigNode.type === 'getter_signature') {
kind = 'getter';
}
if (sigNode.type === 'setter_signature') {
kind = 'setter';
}
return {
name,
className: className || null,
isClassMethod: isStatic,
isExported: !name.startsWith('_'),
isAsync,
isOverride,
paramCount: _countChildParams(sigNode),
bodyLines,
complexity,
nestingDepth,
line: sigNode.startPosition.row + 1,
kind,
};
}
function _findMethodName(node) {
// method_signature > function_signature > identifier
// getter_signature > identifier
// setter_signature > identifier
for (let i = 0; i < node.namedChildCount; i++) {
const c = node.namedChild(i);
if (c.type === 'identifier') {
return c.text;
}
if (c.type === 'function_signature') {
const id = c.namedChildren.find((n) => n.type === 'identifier');
if (id) {
return id.text;
}
}
}
return null;
}
function _parseFunctionDef(node, className) {
// 先尝试直接子节点,再递归搜索
let name = node.namedChildren.find((c) => c.type === 'identifier' || c.type === 'function_name')?.text;
if (!name) {
name = _findMethodName(node);
}
if (!name) {
return null;
}
const isStatic = node.text.includes('static ');
const isAsync = node.text.includes('async') || node.text.includes('async*');
const isOverride = node.text.includes('@override');
const body = node.namedChildren.find((c) => c.type === 'function_body' || 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: className || null,
isClassMethod: isStatic || !className,
isExported: !name.startsWith('_'),
isAsync,
isOverride,
paramCount: _countChildParams(node),
bodyLines,
complexity,
nestingDepth,
line: node.startPosition.row + 1,
kind: 'definition',
};
}
// ── Property / Field ─────────────────────────────────────────
function _parsePropertyDecl(node, className) {
// tree-sitter-dart property 结构:
// declaration > type_identifier + initialized_identifier_list > initialized_identifier > identifier
// declaration > identifier (简单)
// static_final_declaration > identifier
let name = null;
// 1. 直接子节点 identifier
const directId = node.namedChildren.find((c) => c.type === 'identifier');
if (directId) {
name = directId.text;
}
// 2. 嵌套在 initialized_identifier_list > initialized_identifier > identifier
if (!name) {
const idList = node.namedChildren.find((c) => c.type === 'initialized_identifier_list');
if (idList) {
const initId = idList.namedChildren.find((c) => c.type === 'initialized_identifier');
if (initId) {
const id = initId.namedChildren.find((c) => c.type === 'identifier');
if (id) {
name = id.text;
}
}
}
}
// 3. 直接 initialized_identifier
if (!name) {
const initId = node.namedChildren.find((c) => c.type === 'initialized_identifier');
if (initId) {
const id = initId.namedChildren.find((c) => c.type === 'identifier');
name = id?.text || initId.text;
}
}
if (!name) {
return null;
}
const text = node.text;
const isFinal = text.includes('final ');
const isLate = text.includes('late ');
const isConst = text.includes('const ');
const isStatic = text.includes('static ');
return {
name,
className: className || null,
isExported: !name.startsWith('_'),
isFinal,
isLate,
isConst,
isStatic,
line: node.startPosition.row + 1,
};
}
// ── Dart Pattern Detection ───────────────────────────────────
function detectDartPatterns(root, lang, methods, properties, classes) {
const patterns = [];
// 构建 class → methods/properties 索引
const classMethodMap = {};
const classPropMap = {};
for (const m of methods) {
if (m.className) {
if (!classMethodMap[m.className]) {
classMethodMap[m.className] = [];
}
classMethodMap[m.className].push(m);
}
}
for (const p of properties) {
if (p.className) {
if (!classPropMap[p.className]) {
classPropMap[p.className] = [];
}
classPropMap[p.className].push(p);
}
}
for (const cls of classes) {
// Flutter Widget 模式
if (cls.superclass === 'StatelessWidget' ||
cls.superclass === 'HookWidget' ||
cls.superclass === 'HookConsumerWidget' ||
cls.superclass === 'ConsumerWidget') {
patterns.push({
type: 'stateless-widget',
className: cls.name,
superclass: cls.superclass,
line: cls.line,
confidence: 0.95,
});
}
if (cls.superclass === 'StatefulWidget') {
patterns.push({
type: 'stateful-widget',
className: cls.name,
line: cls.line,
confidence: 0.95,
});
}
if (cls.superclass === 'State') {
patterns.push({
type: 'state-class',
className: cls.name,
line: cls.line,
confidence: 0.9,
});
}
// BLoC / Cubit 模式
if (cls.superclass === 'Bloc' || cls.superclass === 'Cubit') {
patterns.push({
type: 'bloc',
className: cls.name,
variant: cls.superclass.toLowerCase(),
line: cls.line,
confidence: 0.95,
});
}
// ChangeNotifier (Provider pattern)
if (cls.superclass === 'ChangeNotifier' || cls.mixins?.includes('ChangeNotifier')) {
patterns.push({
type: 'change-notifier',
className: cls.name,
line: cls.line,
confidence: 0.9,
});
}
// Singleton pattern — private constructor + static instance
const classMethods = classMethodMap[cls.name] || [];
const classProps = classPropMap[cls.name] || [];
const hasPrivateConstructor = classMethods.some((m) => m.kind === 'constructor' && m.name.startsWith('_'));
const hasStaticInstance = classProps.some((p) => p.isStatic && (p.isFinal || p.isConst));
const hasFactoryConstructor = classMethods.some((m) => m.kind === 'factory');
if (hasPrivateConstructor && (hasStaticInstance || hasFactoryConstructor)) {
patterns.push({
type: 'singleton',
className: cls.name,
line: cls.line,
confidence: 0.85,
});
}
// Factory pattern — factory constructors
if (hasFactoryConstructor) {
patterns.push({
type: 'factory',
className: cls.name,
line: cls.line,
confidence: 0.8,
});
}
// Mixin pattern
if (cls.kind === 'mixin') {
patterns.push({
type: 'mixin',
className: cls.name,
line: cls.line,
confidence: 0.9,
});
}
// Sealed class (algebraic data type)
if (cls.kind === 'sealed-class') {
patterns.push({
type: 'sealed-class',
className: cls.name,
line: cls.line,
confidence: 0.95,
});
}
// Freezed pattern — @freezed/@Freezed annotation + with _$ClassName mixin
if (cls.mixins?.some((m) => m.startsWith('_$'))) {
patterns.push({
type: 'freezed',
className: cls.name,
line: cls.line,
confidence: 0.9,
});
}
// Repository 分层
if (cls.kind === 'abstract-class' && /(Repository|DataSource|Service)$/.test(cls.name)) {
patterns.push({
type: 'repository-abstraction',
className: cls.name,
line: cls.line,
confidence: 0.7,
});
}
}
// Extension methods
_detectExtensions(root, patterns);
// Stream 使用
_detectStreamUsage(root, patterns);
return patterns;
}
function _detectExtensions(root, patterns) {
let count = 0;
function walk(node) {
if (node.type === 'extension_declaration') {
count++;
}
for (let i = 0; i < node.namedChildCount; i++) {
walk(node.namedChild(i));
}
}
walk(root);
if (count > 0) {
patterns.push({
type: 'extension-methods',
count,
confidence: 0.9,
});
}
}
function _detectStreamUsage(root, patterns) {
let streamCount = 0;
function walk(node) {
if (node.type === 'type_identifier' && node.text === 'Stream') {
streamCount++;
}
if (node.type === 'identifier' &&
(node.text === 'StreamController' || node.text === 'StreamSubscription')) {
streamCount++;
}
for (let i = 0; i < node.namedChildCount; i++) {
walk(node.namedChild(i));
}
}
walk(root);
if (streamCount > 0) {
patterns.push({
type: 'stream-reactive',
count: streamCount,
confidence: 0.85,
});
}
}
// ── Utility ──────────────────────────────────────────────────
function _countChildParams(node) {
let count = 0;
function walk(n) {
if (n.type === 'formal_parameter' ||
n.type === 'normal_formal_parameter' ||
n.type === 'default_formal_parameter') {
count++;
return;
}
for (let i = 0; i < n.namedChildCount; i++) {
walk(n.namedChild(i));
}
}
walk(node);
return count;
}
function _estimateComplexity(node) {
let complexity = 1;
const BRANCH_TYPES = new Set([
'if_statement',
'for_statement',
'for_in_statement',
'while_statement',
'do_statement',
'switch_statement',
'switch_expression',
'case_clause',
'catch_clause',
'conditional_expression', // ternary ? :
]);
function walk(n) {
if (BRANCH_TYPES.has(n.type)) {
complexity++;
}
if (n.type === 'binary_expression') {
const text = n.text;
if (text.includes('&&') || text.includes('||')) {
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',
'do_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;
}
// ── Dart Call Site 提取 (Phase 5) ────────────────────────────
/**
* 从 Dart AST root 提取所有调用点
* 遍历 function_definition / method 中的 body → 各种 invocation 节点
*/
function extractCallSitesDart(root, ctx, _lang) {
const scopes = _collectDartScopes(root);
for (const scope of scopes) {
_extractDartCallSitesFromBody(scope.body, scope.className, scope.methodName, ctx);
}
}
/** 递归收集 Dart 中所有函数/方法体作用域 */
function _collectDartScopes(root) {
const scopes = [];
function visit(node, className) {
for (let i = 0; i < node.namedChildCount; i++) {
const child = node.namedChild(i);
if (child.type === 'class_definition') {
const name = child.namedChildren.find((c) => c.type === 'identifier' || c.type === 'type_identifier')?.text;
const body = child.namedChildren.find((c) => c.type === 'class_body');
if (body) {
visit(body, name || className);
}
}
else if (child.type === 'mixin_declaration') {
const name = child.namedChildren.find((c) => c.type === 'identifier' || c.type === 'type_identifier')?.text;
const body = child.namedChildren.find((c) => c.type === 'class_body');
if (body) {
visit(body, name || className);
}
}
else if (child.type === 'extension_declaration') {
const name = child.namedChildren.find((c) => c.type === 'identifier' || c.type === 'type_identifier')?.text;
const body = child.namedChildren.find((c) => c.type === 'class_body' || c.type === 'extension_body');
if (body) {
visit(body, name || className);
}
}
else if (child.type === 'function_definition' ||
child.type === 'method_signature' ||
child.type === 'function_signature') {
// 提取方法/函数名
let name;
if (child.type === 'method_signature' || child.type === 'function_signature') {
// method_signature 可能包含嵌套 function_signature
const funcSig = child.namedChildren.find((c) => c.type === 'function_signature') || child;
name = funcSig.namedChildren.find((c) => c.type === 'identifier' || c.type === 'function_name')?.text;
}
else {
name = child.namedChildren.find((c) => c.type === 'identifier' || c.type === 'function_name')?.text;
}
// tree-sitter-dart: function_body 可能是子节点或下一个兄弟节点
let body = child.namedChildren.find((c) => c.type === 'function_body' || c.type === 'block');
// 如果 body 不在子节点中,检查下一个兄弟节点 (tree-sitter-dart 的 sibling 结构)
if (!body && i + 1 < node.namedChildCount) {
const nextSibling = node.namedChild(i + 1);
if (nextSibling?.type === 'function_body' || nextSibling?.type === 'block') {
body = nextSibling;
i++; // 跳过已消费的 body 节点
}
}
if (name && body) {
scopes.push({ body, className, methodName: name });
}
}
else if (child.type === 'getter_signature' || child.type === 'setter_signature') {
const name = child.namedChildren.find((c) => c.type === 'identifier')?.text;
let body = child.namedChildren.find((c) => c.type === 'function_body' || c.type === 'block');
if (!body && i + 1 < node.namedChildCount) {
const nextSibling = node.namedChild(i + 1);
if (nextSibling?.type === 'function_body' || nextSibling?.type === 'block') {
body = nextSibling;
i++;
}
}
if (name && body) {
scopes.push({ body, className, methodName: `get_${name}` });
}
}
}
}
visit(root, null);
return scopes;
}
/**
* 从 Dart function body 中递归提取调用点
*
* tree-sitter-dart 的调用表达式由 **兄弟节点序列** 构成(而非单个 call_expression 节点):
* Pattern A: identifier + selector("(args)") → 直接调用: func(args)
* Pattern B: (identifier|this|super) + selector(".method") → 方法调用: obj.method(args)
* + selector("(args)")
* 因此需要 sibling-aware scanning,避免逐个 walk 子节点时丢失上下文。
*/
function _extractDartCallSitesFromBody(bodyNode, className, methodName, ctx) {
if (!bodyNode) {
return;
}
const DART_NOISE = new Set([
'print',
'debugPrint',
'log',
'setState',
'notifyListeners',
'List',
'Map',
'Set',
'Future',
'Stream',
]);
/** 在 selectorNode 的子树中查找 arguments / argument_part */
function findArgs(selectorNode) {
return selectorNode.namedChildren.find((c) => c.type === 'arguments' || c.type === 'argument_part');
}
/**
* 尝试从 parent.namedChild(idx) 开始消费一条调用链。
* 成功 → 返回消费到的最后一个子节点索引;失败 → 返回 null。
*/
function tryConsumeCall(parent, idx, startNode, isAwaited) {
const sib1 = parent.namedChild(idx + 1);
if (!sib1) {
return null;
}
// ── Pattern A: identifier + selector("(args)") → 直接调用 ────────
if ((startNode.type === 'identifier' || startNode.type === 'type_identifier') &&
sib1.type === 'selector' &&
/^\s*\(/.test(sib1.text)) {
const callee = startNode.text;
if (!DART_NOISE.has(callee)) {
const callType = /^[A-Z]/.test(callee) ? 'constructor' : 'function';
const args = findArgs(sib1);
ctx.callSites.push({
callee,
callerMethod: methodName,
callerClass: className,
callType,
receiver: null,
receiverType: callType === 'constructor' ? callee : null,
argCount: args ? args.namedChildCount : 0,
line: startNode.startPosition.row + 1,
isAwait: isAwaited,
});
}
// 递归扫描 selector 内部(处理嵌套调用,如 MyApp() 内的参数调用)
scanChildren(sib1, false);
return idx + 1;
}
// ── Pattern B: receiver + methodSelector + argsSelector → 方法调用 ──
const sib2 = parent.namedChild(idx + 2);
const isMethodSel = sib1.type === 'selector' || sib1.type === 'unconditional_assignable_selector';
const isArgsSel = sib2?.type === 'selector' && sib2.text.includes('(');
if (isMethodSel && isArgsSel) {
const methodMatch = sib1.text.match(/\.(\w+)/);
if (methodMatch) {
const callee = methodMatch[1];
const receiverText = startNode.text;
const receiver = receiverText;
let receiverType = null;
let callType;
if (startNode.type === 'this' || receiver === 'this' || receiver === 'self') {
receiverType = className;
callType = 'method';
}
else if (startNode.type === 'super' || receiver === 'super') {
receiverType = className;
callType = 'super';
}
else if (/^[A-Z]/.test(receiver)) {
receiverType = receiver;
callType = 'static';
}
else {
callType = 'method';
}
if (!DART_NOISE.has(callee)) {
const args = findArgs(sib2);
ctx.callSites.push({
callee,
callerMethod: methodName,
callerClass: className,
callType,
receiver,
receiverType,
argCount: args ? args.namedChildCount : 0,
line: startNode.startPosition.row + 1,
isAwait: isAwaited,
});
}
// 递归扫描 argsSelector 内部
scanChildren(sib2, false);
return idx + 2;
}
}
return null; // 未匹配任何模式
}
/**
* 以 sibling-aware 方式扫描 node 的 namedChildren。
* 当发现调用起始节点(identifier / this / super)时,尝试消费完整调用链并跳过已消费兄弟。
*/
function scanChildren(node, isAwaited) {
for (let i = 0; i < node.namedChildCount; i++) {
const child = node.namedChild(i);
// await → 标记子树为 awaited
if (child.type === 'await_expression') {
scanChildren(child, true);
continue;
}
// function_expression_invocation / method_invocation (部分 grammar 变体)
if (child.type === 'function_expression_invocation' || child.type === 'method_invocation') {
_processDartCall(child, className, methodName, ctx, isAwaited, DART_NOISE);
const args = child.namedChildren.find((c) => c.type === 'arguments' || c.type === 'argument_part');
if (args) {
scanChildren(args, false);
}
continue;
}
// 尝试从当前位置消费调用模式
const isCallStarter = child.type === 'identifier' ||
child.type === 'type_identifier' ||
child.type === 'this' ||
child.type === 'super';
if (isCallStarter) {
const consumed = tryConsumeCall(node, i, child, isAwaited);
if (consumed !== null) {
i = consumed; // 跳过已消费的兄弟
continue;
}
}
// Dart cascade: obj..method1()..method2()
if (child.type === 'cascade_section') {
_processDartCall(child, className, methodName, ctx, isAwaited, DART_NOISE);
scanChildren(child, false);
continue;
}
// 未匹配 → 递归进入子节点
scanChildren(child, isAwaited);
}
}
scanChildren(bodyNode, false);
}
/** 处理 Dart 函数/方法调用节点 */
function _processDartCall(node, className, methodName, ctx, isAwaited, DART_NOISE) {
const text = node.text || '';
const callMatch = text.match(/^(?:(\w[\w.]*?)\.)?(\w+)\s*\(/);
if (!callMatch) {
return;
}
const receiverText = callMatch[1] || null;
const callee = callMatch[2];
if (DART_NOISE.has(callee)) {
return;
}
const receiver = receiverText;
let receiverType = null;
let callType;
if (receiver === 'this' || receiver === 'super') {
receiverType = className;
callType = receiver === 'super' ? 'super' : 'method';
}
else if (receiver && /^[A-Z]/.test(receiver)) {
receiverType = receiver;
callType = 'static';
}
else if (receiver) {
callType = 'method';
}
else {
callType = /^[A-Z]/.test(callee) ? 'constructor' : 'function';
if (callType === 'constructor') {
receiverType = callee;
}
}
const args = node.namedChildren.find((c) => c.type === 'arguments' || c.type === 'argument_part');
const argCount = args ? args.namedChildCount : 0;
ctx.callSites.push({
callee,
callerMethod: methodName,
callerClass: className,
callType,
receiver,
receiverType,
argCount,
line: node.startPosition.row + 1,
isAwait: isAwaited,
});
}
// ── Plugin Export ────────────────────────────────────────────
let _grammar = null;
function getGrammar() {
return _grammar;
}
export function setGrammar(grammar) {
_grammar = grammar;
}
export const plugin = {
getGrammar,
walk: walkDart,
detectPatterns: detectDartPatterns,
extractCallSites: extractCallSitesDart,
extensions: ['.dart'],
};