analyze method

  1. @override
Future<DiagnosticResult> analyze(
  1. AnalyzerContext context
)
override

Public method or function.

Implementation

@override
/// Public method or function.
Future<DiagnosticResult> analyze(AnalyzerContext context) async {
  final startTime = DateTime.now();
  final issues = <DiagnosticIssue>[];
  final fs = context.fileSystem;
  final projectPath = context.projectPath;

  final iosPath = fs.join(projectPath, 'ios');
  if (!fs.exists(iosPath) || !fs.isDirectory(iosPath)) {
    return DiagnosticResult(
      analyzerName: name,
      status: CheckStatus.skipped,
      issues: issues,
      duration: DateTime.now().difference(startTime),
      timestamp: DateTime.now(),
    );
  }

  final googleServicePath = fs.join(
    fs.join(iosPath, 'Runner'),
    'GoogleService-Info.plist',
  );
  final podfilePath = fs.join(iosPath, 'Podfile');
  final podfileLockPath = fs.join(iosPath, 'Podfile.lock');
  final pbxprojPath = fs.join(
    fs.join(iosPath, 'Runner.xcodeproj'),
    'project.pbxproj',
  );
  final infoPlistPath = fs.join(fs.join(iosPath, 'Runner'), 'Info.plist');

  Map<String, String>? googleServiceInfo;
  if (!fs.exists(googleServicePath)) {
    issues.add(
      DiagnosticIssue(
        severity: Severity.critical,
        code: 'FD500',
        title: 'Missing GoogleService-Info.plist',
        description:
            'The ios/Runner/GoogleService-Info.plist file is missing. '
            'This file is required for Firebase services on iOS.',
        recommendation:
            'Run "flutterfire configure" to generate the '
            'GoogleService-Info.plist file or download it from Firebase Console.',
        filePath: googleServicePath,
      ),
    );
  } else {
    final content = fs.readAsString(googleServicePath);
    googleServiceInfo = _plistParser.parseGoogleServiceInfoPlist(content);

    if (googleServiceInfo == null) {
      issues.add(
        DiagnosticIssue(
          severity: Severity.error,
          code: 'FD501',
          title: 'Invalid GoogleService-Info.plist',
          description:
              'The GoogleService-Info.plist file contains invalid or malformed content.',
          recommendation:
              'Regenerate the file using "flutterfire configure" '
              'or download a fresh copy from Firebase Console.',
          filePath: googleServicePath,
        ),
      );
    }
  }

  String? podfileContent;
  if (!fs.exists(podfilePath)) {
    issues.add(
      DiagnosticIssue(
        severity: Severity.error,
        code: 'FD508',
        title: 'Missing Podfile',
        description:
            'The ios/Podfile file is missing. This file is required for '
            'managing iOS dependencies.',
        recommendation:
            'Run "flutter create ." in the ios directory or '
            'create a Podfile manually.',
        filePath: podfilePath,
      ),
    );
  } else {
    podfileContent = fs.readAsString(podfilePath);
  }

  ({
    String? bundleIdentifier,
    String? runnerTargetName,
    bool hasPushCapability,
    bool hasBackgroundModes,
  })?
  pbxprojInfo;
  if (fs.exists(pbxprojPath)) {
    final pbxprojContent = fs.readAsString(pbxprojPath);
    pbxprojInfo = _pbxprojParser.parse(pbxprojContent);
  }

  ({List<String> backgroundModes, bool hasFirebaseAppDelegateProxy})?
  infoPlistInfo;
  if (fs.exists(infoPlistPath)) {
    final infoPlistContent = fs.readAsString(infoPlistPath);
    infoPlistInfo = _plistParser.parseInfoPlist(infoPlistContent);
  }

  if (googleServiceInfo != null &&
      googleServiceInfo['BUNDLE_ID'] != null &&
      pbxprojInfo != null &&
      pbxprojInfo.bundleIdentifier != null) {
    final plistBundleId = googleServiceInfo['BUNDLE_ID']!;
    if (plistBundleId != pbxprojInfo.bundleIdentifier) {
      issues.add(
        DiagnosticIssue(
          severity: Severity.error,
          code: 'FD502',
          title: 'Bundle ID mismatch in GoogleService-Info.plist',
          description:
              'The bundle ID "$plistBundleId" in GoogleService-Info.plist '
              'does not match the PRODUCT_BUNDLE_IDENTIFIER '
              '"${pbxprojInfo.bundleIdentifier}" in the Xcode project.',
          recommendation:
              'Update the PRODUCT_BUNDLE_IDENTIFIER in your Xcode project or '
              'regenerate GoogleService-Info.plist with '
              '"flutterfire configure".',
          filePath: googleServicePath,
        ),
      );
    }
  }

  if (pbxprojInfo != null && pbxprojInfo.bundleIdentifier == null) {
    issues.add(
      DiagnosticIssue(
        severity: Severity.warning,
        code: 'FD503',
        title: 'Bundle identifier not detected',
        description:
            'Could not detect the bundle identifier from the Xcode project.',
        recommendation:
            'Ensure PRODUCT_BUNDLE_IDENTIFIER is set in your Xcode project settings.',
        filePath: pbxprojPath,
      ),
    );
  }

  if (pbxprojInfo != null && pbxprojInfo.runnerTargetName == null) {
    issues.add(
      DiagnosticIssue(
        severity: Severity.error,
        code: 'FD504',
        title: 'Runner target not found in Xcode project',
        description:
            'The "Runner" target was not found in the Xcode project file.',
        recommendation:
            'Ensure the Xcode project contains a target named "Runner".',
        filePath: pbxprojPath,
      ),
    );
  }

  if (pbxprojInfo != null && !pbxprojInfo.hasPushCapability) {
    issues.add(
      DiagnosticIssue(
        severity: Severity.warning,
        code: 'FD505',
        title: 'Push Notifications capability missing',
        description:
            'The Push Notifications capability is not enabled in the Xcode project.',
        recommendation:
            'Enable Push Notifications in Xcode: '
            'Signing & Capabilities > + Capability > Push Notifications.',
        filePath: pbxprojPath,
      ),
    );
  }

  if (pbxprojInfo != null && !pbxprojInfo.hasBackgroundModes) {
    issues.add(
      DiagnosticIssue(
        severity: Severity.warning,
        code: 'FD506',
        title: 'Background Modes capability missing',
        description:
            'The Background Modes capability is not enabled in the Xcode project.',
        recommendation:
            'Enable Background Modes in Xcode: '
            'Signing & Capabilities > + Capability > Background Modes.',
        filePath: pbxprojPath,
      ),
    );
  }

  // Only flag missing remote-notification background mode if the project
  // actually uses Firebase Cloud Messaging
  bool needsRemoteNotifications = false;
  if (!needsRemoteNotifications) {
    final pubspecPath = fs.join(projectPath, 'pubspec.yaml');
    if (fs.exists(pubspecPath)) {
      final pubspec = await PubspecParser.parseFromFile(pubspecPath, fs);
      needsRemoteNotifications =
          pubspec != null &&
          (pubspec.hasDependency('firebase_messaging') ||
              pubspec.hasDevDependency('firebase_messaging'));
    }
  }

  if (infoPlistInfo != null &&
      needsRemoteNotifications &&
      !infoPlistInfo.backgroundModes.contains('remote-notification')) {
    issues.add(
      DiagnosticIssue(
        severity: Severity.warning,
        code: 'FD507',
        title: 'Remote Notifications background mode missing',
        description:
            'The "remote-notification" background mode is not configured in Info.plist.',
        recommendation:
            'Add "remote-notification" to UIBackgroundModes in Info.plist:\n'
            '  <key>UIBackgroundModes</key>\n'
            '  <array>\n'
            '    <string>remote-notification</string>\n'
            '  </array>',
        filePath: infoPlistPath,
      ),
    );
  }

  ({
    double? iosVersion,
    bool hasFirebasePods,
    List<String> pods,
    bool hasRunnerTarget,
  })?
  podfileInfo;
  if (podfileContent != null) {
    podfileInfo = _podfileParser.parse(podfileContent);
    if (podfileInfo != null &&
        podfileInfo.iosVersion != null &&
        podfileInfo.iosVersion! < 12.0) {
      issues.add(
        DiagnosticIssue(
          severity: Severity.warning,
          code: 'FD509',
          title: 'iOS platform version below 12.0 in Podfile',
          description:
              'The Podfile platform version is set to ${podfileInfo.iosVersion}. '
              'Firebase SDKs require iOS 12.0 or higher.',
          recommendation:
              'Update the platform version in your Podfile:\n'
              '  platform :ios, \'12.0\'',
          filePath: podfilePath,
        ),
      );
    }
  }

  bool hasFirebasePods = false;
  if (podfileInfo != null && podfileInfo.hasFirebasePods) {
    hasFirebasePods = true;
  } else if (fs.exists(podfileLockPath)) {
    final lockContent = fs.readAsString(podfileLockPath);
    final lockInfo = _podfileLockParser.parse(lockContent);
    if (lockInfo.hasFirebasePods) {
      hasFirebasePods = true;
    }
  }

  if (!hasFirebasePods &&
      (googleServiceInfo != null || fs.exists(podfilePath))) {
    issues.add(
      DiagnosticIssue(
        severity: Severity.warning,
        code: 'FD510',
        title: 'No Firebase pods found',
        description:
            'No Firebase pods were found in the Podfile or Podfile.lock.',
        recommendation:
            'Add Firebase pods to your Podfile:\n'
            '  pod \'Firebase/Core\'',
        filePath: podfilePath,
      ),
    );
  }

  if (googleServiceInfo != null) {
    final libPath = fs.join(projectPath, 'lib');
    bool hasFirebaseImport = false;
    if (fs.exists(libPath) && fs.isDirectory(libPath)) {
      hasFirebaseImport = _scanForFirebaseImports(fs, libPath);
    }
    if (!hasFirebaseImport) {
      issues.add(
        DiagnosticIssue(
          severity: Severity.info,
          code: 'FD511',
          title: 'GoogleService-Info.plist not referenced',
          description:
              'GoogleService-Info.plist exists but no Firebase imports '
              'were found in Dart files.',
          recommendation:
              'Add Firebase to your Dart code:\n'
              '  import \'package:firebase_core/firebase_core.dart\';',
          filePath: googleServicePath,
        ),
      );
    }
  }

  if (googleServiceInfo != null &&
      infoPlistInfo != null &&
      !infoPlistInfo.hasFirebaseAppDelegateProxy) {
    issues.add(
      DiagnosticIssue(
        severity: Severity.info,
        code: 'FD512',
        title: 'APNs configuration warning',
        description:
            'FirebaseAppDelegateProxyEnabled is not configured in Info.plist. '
            'Without this setting, Firebase may not properly handle APNs '
            'token registration.',
        recommendation:
            'Consider adding to your Info.plist:\n'
            '  <key>FirebaseAppDelegateProxyEnabled</key>\n'
            '  <false/>',
        filePath: infoPlistPath,
      ),
    );
  }

  final hasCriticalOrError = issues.any(
    (i) => i.severity == Severity.critical || i.severity == Severity.error,
  );
  final hasWarning = issues.any((i) => i.severity == Severity.warning);

  final status = hasCriticalOrError
      ? CheckStatus.failed
      : hasWarning
      ? CheckStatus.warning
      : CheckStatus.passed;

  return DiagnosticResult(
    analyzerName: name,
    status: status,
    issues: issues,
    duration: DateTime.now().difference(startTime),
    timestamp: DateTime.now(),
  );
}