build method

  1. @override
Future<void> build(
  1. BuildStep buildStep
)

Generates the outputs for a given BuildStep.

Implementation

@override
Future<void> build(BuildStep buildStep) async {
  if (!await buildStep.resolver.isLibrary(buildStep.inputId)) return;

  final lib = await buildStep.inputLibrary;
  final reader = LibraryReader(lib);

  final pages = reader.annotatedWith(_pageChecker);
  if (pages.isEmpty) return;

  for (final page in pages) {
    final element = page.element;
    if (element is! ClassElement) continue;

    // Use lookUpGetter to check if the class declares or inherits the getter
    final componentsGetter = element.lookUpGetter(
      name: 'components',
      library: element.library,
    );

    if (componentsGetter == null) continue;

    // Ensure it's not inherited from SparkPage
    final hasComponents =
        componentsGetter.enclosingElement.name != 'SparkPage';

    if (!hasComponents) continue;

    final getterDefiningClass = componentsGetter.enclosingElement;
    // Allow ClassElement or MixinElement
    if (getterDefiningClass is! ClassElement &&
        getterDefiningClass is! MixinElement) {
      continue;
    }

    final session = element.session;
    if (session == null) continue;

    final targetLibrary = getterDefiningClass.library;
    if (targetLibrary == null) continue;

    final parsedLib = await session.getResolvedLibraryByElement(
      targetLibrary,
    );
    if (parsedLib is! ResolvedLibraryResult) continue;

    AstNode? targetNode;
    // Search all units for the getter declaration (avoiding Element.source usage)
    for (final unitResult in parsedLib.units) {
      for (final decl in unitResult.unit.declarations) {
        final String? declName;
        final List<ClassMember> members;

        if (decl is ClassDeclaration) {
          declName = decl.namePart.typeName.lexeme;
          members = (decl.body as BlockClassBody).members;
        } else if (decl is MixinDeclaration) {
          declName = decl.name.lexeme;
          members = decl.body.members;
        } else {
          declName = null;
          members = const [];
        }

        if (declName != null && declName == getterDefiningClass.name) {
          for (final member in members) {
            if (member is MethodDeclaration &&
                member.isGetter &&
                member.name.lexeme == 'components') {
              targetNode = member;
              break;
            } else if (member is FieldDeclaration) {
              for (final field in member.fields.variables) {
                if (field.name.lexeme == 'components') {
                  targetNode = field;
                  break;
                }
              }
            }
          }
        }
        if (targetNode != null) break;
      }
      if (targetNode != null) break;
    }

    if (targetNode != null) {
      log.info('Found components definition in ${getterDefiningClass.name}');
    } else {
      log.warning(
        'Could not find source for components definition in ${getterDefiningClass.name} (Library: ${targetLibrary.identifier})',
      );
    }

    if (targetNode == null) continue;

    ListLiteral? list;

    if (targetNode is MethodDeclaration) {
      final body = targetNode.body;
      if (body is ExpressionFunctionBody) {
        if (body.expression is ListLiteral) {
          list = body.expression as ListLiteral;
        }
      } else if (body is BlockFunctionBody) {
        for (final statement in body.block.statements) {
          if (statement is ReturnStatement &&
              statement.expression is ListLiteral) {
            list = statement.expression as ListLiteral;
            break;
          }
        }
      }
    } else if (targetNode is VariableDeclaration) {
      if (targetNode.initializer is ListLiteral) {
        list = targetNode.initializer as ListLiteral;
      }
    }

    if (list == null) continue;

    if (list.elements.isEmpty) continue;

    final componentImports = <String>{};
    final components = <MapEntry<String, String>>[];

    for (final item in list.elements) {
      final referencedClasses = _findReferencedClasses(item);

      for (final componentElement in referencedClasses) {
        final tagField = componentElement.getField('tag');
        if (tagField == null || !tagField.isStatic) continue;

        final constant = tagField.computeConstantValue();
        if (constant == null) continue;
        final tagValue = constant.toStringValue();
        if (tagValue == null) continue;

        // Use library identifier as import URI (definingCompilationUnit is missing)
        final importUri = componentElement.library.identifier;

        componentImports.add(importUri);
        final name = componentElement.name;
        if (name == null) continue;
        components.add(MapEntry(tagValue, name));
      }
    }

    if (components.isEmpty) continue;

    final outputId = buildStep.allowedOutputs.single;

    final content = _generateWebEntry(componentImports, components);
    await buildStep.writeAsString(
      outputId,
      DartFormatter(
        languageVersion: DartFormatter.latestLanguageVersion,
      ).format(content),
    );
  }
}