getMethodSource method
Extracts method source code from a method element. Finds the method by name and extracts from the previous declaration boundary.
Implementation
@visibleForTesting
Future<String?> getMethodSource(
MethodElement method,
BuildStep buildStep,
) async {
final methodName = method.name;
if (methodName == null) return null;
try {
final contents = await buildStep.readAsString(buildStep.inputId);
// Match method declarations with return type or modifiers before method name.
// We look for:
// 1. Optional annotations (@\w+, @some.path, @some(args))
// 2. EITHER:
// a. At least one recognized modifier (static, override, final, late, const)
// b. A return type (like "Element" or "Future<void>") that is NOT a control flow keyword.
// 3. The method name itself followed by (
final pattern = RegExp(
r'(?:^|\n)\s*'
r'(?:@[\w\.]+\s*(?:\([^)]*\))?\s+)*'
r'(?:'
r'(?:(?:static|const|final|late|override)\s+)+'
r'|'
r'(?:(?!(?:if|else|for|while|switch|return)\b)[a-zA-Z_]\w*(?:<[^>]+>)?(?:\?)?\s+)'
r')'
'\\b${RegExp.escape(methodName)}\\b'
r'\s*\(',
multiLine: true,
);
final matches = pattern.allMatches(contents);
if (matches.isEmpty) {
return null;
}
final match = matches.first;
// Extract the full match and find where the actual declaration starts
// Skip any leading newlines
int start = match.start;
while (start < contents.length &&
(contents[start] == '\n' || contents[start] == '\r')) {
start++;
}
// Find the matching closing parenthesis of the parameter list
int pos = match.end;
int parenCount = 1;
while (pos < contents.length && parenCount > 0) {
if (contents[pos] == '(') {
parenCount++;
} else if (contents[pos] == ')') {
parenCount--;
}
pos++;
}
// Find opening brace or arrow after method signature
bool isArrowFunction = false;
while (pos < contents.length &&
contents[pos] != '{' &&
contents[pos] != '=') {
pos++;
}
if (pos >= contents.length) return null;
// Check if it's an arrow function
if (pos + 1 < contents.length &&
contents[pos] == '=' &&
contents[pos + 1] == '>') {
isArrowFunction = true;
}
int end;
if (isArrowFunction) {
// Arrow function - find semicolon or end of expression
end = pos + 2;
int braceCount = 0;
int innerParenCount = 0;
int bracketCount = 0;
while (end < contents.length) {
final char = contents[end];
if (char == '{') {
braceCount++;
} else if (char == '}') {
braceCount--;
} else if (char == '(') {
innerParenCount++;
} else if (char == ')') {
innerParenCount--;
} else if (char == '[') {
bracketCount++;
} else if (char == ']') {
bracketCount--;
} else if (char == ';' &&
braceCount == 0 &&
innerParenCount == 0 &&
bracketCount == 0) {
end++;
break;
}
end++;
}
} else {
// Block function - find matching closing brace
int braceCount = 0;
end = pos;
while (end < contents.length) {
if (contents[end] == '{') {
braceCount++;
} else if (contents[end] == '}') {
braceCount--;
if (braceCount == 0) {
end++;
break;
}
}
end++;
}
}
if (end > start) {
final extracted = contents.substring(start, end).trim();
// Verify this looks like a valid method (basic sanity check)
if (extracted.contains(methodName) &&
(extracted.contains('{') || extracted.contains('=>'))) {
return ' $extracted';
}
}
} catch (e) {
print('Failed to extract method source for $methodName: $e');
}
return null;
}