getMethodSource method

  1. @visibleForTesting
Future<String?> getMethodSource(
  1. MethodElement method,
  2. BuildStep buildStep
)

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;
}