generateDocs function

Future<List<DocComponent>> generateDocs({
  1. required String gitRef,
  2. required String? out,
  3. required Directory dartRoot,
  4. required Directory gitRoot,
  5. required bool shouldCache,
})

Implementation

Future<List<DocComponent>> generateDocs({
  required String gitRef,
  required String? out,
  required Directory dartRoot,
  required Directory gitRoot,
  required bool shouldCache,
}) async {
  final cache = Cache();
  // Check if we're in a git repository
  if (!await GitUtils.isGitRepository(gitRoot.path)) {
    logger.err('Not in a git repository. Cannot proceed with ref-based generation.');
    exit(1);
  }

  // Check for uncommitted changes if we're going to checkout a ref
  if (gitRef != 'HEAD' && GitUtils.hasUncommittedChanges(gitRoot.path)) {
    logger.err('Repository has uncommitted changes. Please commit or stash them before proceeding.');
    logger.err('This prevents potential data loss during ref checkout.');
    exit(1);
  }

  final originalRef = await GitUtils.getCurrentRef(gitRoot.path);
  final originalBranch = await GitUtils.getCurrentBranch(gitRoot.path);

  // If the ref is HEAD, get the current ref (commit hash)
  if (gitRef == 'HEAD') {
    gitRef = await GitUtils.getCurrentRef(gitRoot.path);
  }

  logger.info('Checking out ref: $gitRef');
  await GitUtils.checkoutRef(gitRef, gitRoot.path);
  logger.info('Successfully checked out ref: $gitRef');
  final effectiveRef = await GitUtils.getCurrentRef(gitRoot.path);

  final repoPath = GitUtils.getRepositoryRoot(gitRoot.path);
  final classes = <DocComponent>[];

  Future<void> restoreOriginalState() async {
    try {
      logger.info('Restoring original state...');
      if (originalBranch != null) {
        await GitUtils.checkoutRef(originalBranch, gitRoot.path);
      } else {
        await GitUtils.checkoutRef(originalRef, gitRoot.path);
      }
      logger.info('Successfully restored original state');
    } catch (e) {
      logger.err('Warning: Failed to restore original state: $e');
      logger.err('Please manually checkout your original branch/ref');
    }
  }

  if (shouldCache) {
    if (cache.hasApiFile(repoPath, effectiveRef)) {
      logger.success('Using cached API documentation for $effectiveRef');
      final cachedContent = await cache.retrieveApiFile(repoPath, effectiveRef);
      if (cachedContent != null) {
        logger.success('Using cached API documentation for $effectiveRef');
        await restoreOriginalState();

        // Write the cached content to file if requested
        if (out != null) {
          if (!File(out).existsSync()) {
            File(out).createSync();
          }
          await File(out).writeAsString(cachedContent);
          logger.success('Wrote cached documentation to $out');
        }

        return parseDocComponentsFile(cachedContent);
      }
    }
  }

  try {
    final (dartFiles, exclusions) = evaluateTargetFiles(dartRoot.path);

    if (dartFiles.isEmpty) {
      logger.err('No Dart files found in the specified paths. Exiting');
      exit(1);
    }

    final contextCollection = AnalysisContextCollection(
      includedPaths: dartFiles.toList(),
      excludedPaths: exclusions.toList(),
    );

    final progress = logger.progress("Analyzing dart files");

    // Analyze each file
    for (final file in dartFiles) {
      try {
        final context = contextCollection.contextFor(file);
        final library = await context.currentSession.getResolvedLibrary(file);
        if (library is! ResolvedLibraryResult) {
          throw StateError('Library not resolved.');
        }

        final visitor = DocVisitor(
          filePath: relative(
            file,
            from: contextCollection.contextFor(file).contextRoot.root.path,
          ),
        );
        library.element2.accept2(visitor);
        classes.addAll(visitor.components);
      } catch (e) {
        logger.err('Error analyzing file $file: $e');
      }
      progress.update(
        "Analyzed $file [${dartFiles.toList().indexOf(file) + 1}/${dartFiles.length}]",
      );
    }

    progress.complete();

    logger.success(
      'Found ${classes.length} classes: ${classes.map((e) => e.name).join(', ')}',
    );
    contextCollection.dispose();

    final outputProgress = logger.progress("Generating output");

    // Generate output
    final output = const JsonEncoder.withIndent('  ').convert(classes);

    outputProgress.complete();

    // Cache the generated documentation if requested
    if (shouldCache) {
      await cache.storeApiFile(repoPath, effectiveRef, output);
      logger.success('Cached documentation for ref: $effectiveRef');
    }

    // Write the generated documentation to a file if requested
    if (out != null) {
      if (!File(out).existsSync()) {
        File(out).createSync();
      }
      await File(out).writeAsString(output);
      logger.success('Wrote generated documentation to $out');
    }
  } finally {
    restoreOriginalState();
  }

  return classes;
}