discoverEntries function

Future<List<PreviewEntry>> discoverEntries(
  1. String projectRoot
)

Implementation

Future<List<PreviewEntry>> discoverEntries(String projectRoot) async {
  final packageName = packageNameOf(projectRoot);
  if (packageName == null) return const [];
  final libDir = Directory(p.join(projectRoot, 'lib'));
  if (!libDir.existsSync()) return const [];

  final files =
      libDir
          .listSync(recursive: true)
          .whereType<File>()
          .where((f) => f.path.endsWith('.dart') && !f.path.endsWith('.g.dart'))
          .map((f) => p.normalize(f.absolute.path))
          .toList()
        ..sort();
  final collection = AnalysisContextCollection(
    includedPaths: [p.normalize(libDir.absolute.path)],
  );

  final entries = <PreviewEntry>[];
  for (final file in files) {
    final session = collection.contextFor(file).currentSession;
    final resolved = await session.getResolvedUnit(file);
    if (resolved is! ResolvedUnitResult) continue;
    final unit = resolved.unit;
    if (unit.directives.whereType<PartOfDirective>().isNotEmpty) continue;
    final rel = p.relative(file, from: libDir.absolute.path);
    final importUri = 'package:$packageName/${p.split(rel).join('/')}';

    void addAll(String expr, List<Annotation> metadata) {
      var i = 0;
      for (final a in metadata) {
        final c = _classify(a);
        if (c == null) continue;
        final suffix = i == 0 ? '' : '#$i';
        final id = '$rel::$expr$suffix';
        i++;
        final name = a.elementAnnotation
            ?.computeConstantValue()
            ?.getField('name')
            ?.toStringValue();
        final rewritten = _reEmitAnnotation(a, c.type, resolved.content);
        entries.add(
          PreviewEntry(
            id: id,
            importUri: importUri,
            expr: expr,
            kind: 'preview',
            name: name,
            annoCode: rewritten.blocked == null ? rewritten.code : null,
            isMulti: c.multi,
            note: rewritten.blocked == null
                ? null
                : 'annotation on $id cannot be re-emitted '
                      '(${rewritten.blocked}) — mounted with NO preview '
                      'fields applied',
          ),
        );
      }
    }

    for (final decl in unit.declarations) {
      if (decl is FunctionDeclaration) {
        addAll('${decl.name.lexeme}()', decl.metadata.toList());
        continue;
      }
      if (decl is! ClassDeclaration) continue;
      final cls = decl.declaredFragment?.element;
      if (cls == null) continue;
      final className = '${cls.name}';
      if (className.startsWith('_')) continue;

      final before = entries.length;
      for (final m in decl.body.members.whereType<MethodDeclaration>()) {
        if (!m.isStatic) continue;
        addAll('$className.${m.name.lexeme}()', m.metadata.toList());
      }
      for (final c in decl.body.members.whereType<ConstructorDeclaration>()) {
        if (c.metadata.isEmpty) continue;
        if (c.parameters.parameters.any((param) => param.isRequired)) {
          continue;
        }
        final ctor = c.name == null ? '' : '.${c.name!.lexeme}';
        addAll('$className$ctor()', c.metadata.toList());
      }
      final hasOwnEntry = entries.length > before;
      if (hasOwnEntry) continue;

      // Bare-mount candidate — else the obligation. Transitive bases via
      // the element model; InheritedWidgets are supplies, not subjects.
      final isWidget =
          !cls.isAbstract &&
          cls.allSupertypes.any((s) => s.element.name == 'Widget');
      if (!isWidget) continue;
      if (cls.allSupertypes.any((s) => s.element.name == 'InheritedWidget')) {
        continue;
      }
      final defaultCtors = [
        for (final ctor in cls.constructors)
          if (ctor.name == 'new' || (ctor.name ?? '') == '') ctor,
      ];
      final mountable =
          defaultCtors.isNotEmpty &&
          !defaultCtors.first.formalParameters.any(
            (param) => param.isRequiredNamed || param.isRequiredPositional,
          );
      if (mountable) {
        entries.add(
          PreviewEntry(
            id: '$rel::$className()',
            importUri: importUri,
            expr: '$className()',
            kind: 'bare',
          ),
        );
      } else {
        entries.add(
          PreviewEntry(
            id: '$rel::$className',
            importUri: importUri,
            expr: '',
            kind: 'missing',
            className: className,
          ),
        );
      }
    }
  }
  return entries;
}