generateForAnnotatedElement method

  1. @override
FutureOr<String> generateForAnnotatedElement(
  1. covariant ClassElement element,
  2. ConstantReader annotation,
  3. BuildStep buildStep
)

Implement to return source code to generate for element.

This method is invoked based on finding elements annotated with an instance of T. The annotation is provided as a ConstantReader.

Supported return values include a single String or multiple String instances within an Iterable or Stream. It is also valid to return a Future of String, Iterable, or Stream.

Implementations should return null when no content is generated. Empty or whitespace-only String instances are also ignored.

Implementation

@override
FutureOr<String> generateForAnnotatedElement(
  covariant ClassElement element,
  ConstantReader annotation,
  BuildStep buildStep,
) async {
  final className = element.name;
  final classConstructor = element.unnamedConstructor!;
  final combineAspects = classConstructor.parameters.any(_isAspectParameter);
  final objectValue = annotation.objectValue;
  final baseClassType = objectValue.getField('base')!.toTypeValue()!;
  final realBaseClassName = baseClassType.element!.name;
  final isBaseClassEntityProcessingSystem =
      realBaseClassName == 'EntityProcessingSystem';
  final baseClassTypeParameters =
      (baseClassType.element! as ClassElement).typeParameters;
  final mapper = _getValues(objectValue, 'mapper');
  final systems = _getValues(objectValue, 'systems');
  final managers = _getValues(objectValue, 'manager');
  final allOfAspects = _getValues(objectValue, 'allOf');
  final oneOfAspects = _getValues(objectValue, 'oneOf');
  final excludedAspects = _getValues(objectValue, 'exclude');
  final shouldCreateCustomProcessEntity = isBaseClassEntityProcessingSystem &&
      (allOfAspects.isNotEmpty || oneOfAspects.isNotEmpty);
  final baseClassName =
      shouldCreateCustomProcessEntity ? 'EntitySystem' : realBaseClassName;
  final baseClassConstructor =
      (annotation.read('base').typeValue.element! as ClassElement)
          .unnamedConstructor!;
  final hasGeneratedAspects = allOfAspects.isNotEmpty ||
      oneOfAspects.isNotEmpty ||
      excludedAspects.isNotEmpty;
  final useSuperParameters = !hasGeneratedAspects;
  final constructorParameter = [
    baseClassConstructor.parameters
        .where((parameterElement) => parameterElement.isRequiredPositional)
        .where(
          (parameterElement) =>
              !_isAspectParameter(parameterElement) ||
              combineAspects ||
              useSuperParameters,
        )
        .map(
          (parameterElement) => useSuperParameters
              ? 'super.${parameterElement.name}'
              : '''${parameterElement.type.getDisplayString()} ${parameterElement.name}''',
        )
        .join(', '),
  ];
  final superCallParameter = useSuperParameters
      ? ''
      : baseClassConstructor.parameters
          .where((parameterElement) => parameterElement.isPositional)
          .map(
            (parameterElement) => _isAspectParameter(parameterElement)
                ? _createAspectParameter(
                    allOfAspects,
                    oneOfAspects,
                    excludedAspects,
                    combineAspects,
                  )
                : parameterElement.name,
          )
          .join(', ');
  final needsConstructor =
      constructorParameter[0].isNotEmpty || superCallParameter.isNotEmpty;
  if (needsConstructor) {
    final optionalPositionalParameters = baseClassConstructor.parameters
        .where((parameter) => parameter.isOptionalPositional)
        .map((parameter) => 'super.${parameter.name}')
        .join(', ');
    if (optionalPositionalParameters.isNotEmpty) {
      constructorParameter.add('[$optionalPositionalParameters]');
    }
    final optionalNamedParameters = baseClassConstructor.parameters
        .where((parameter) => parameter.isNamed)
        .map(
          (parameter) =>
              '''${parameter.isRequired ? 'required ' : ''}super.${parameter.name}''',
        )
        .join(', ');
    if (optionalNamedParameters.isNotEmpty) {
      constructorParameter.add('{$optionalNamedParameters}');
    }
  }
  final components = {...allOfAspects, ...mapper};
  final optionalComponents = {...oneOfAspects};
  final mapperDeclarations = components
      .map(
        (component) =>
            '  late final Mapper<$component> ${_toMapperName(component)};',
      )
      .join('\n');
  final optionalMapperDeclarations = optionalComponents
      .map(
        (component) =>
            '''  late final OptionalMapper<$component> ${_toMapperName(component)};''',
      )
      .join('\n');
  final systemDeclarations = systems
      .map((system) => '  late final $system ${_toVariableName(system)};')
      .join('\n');
  final managerDeclarations = managers
      .map((manager) => '  late final $manager ${_toVariableName(manager)};')
      .join('\n');
  final mapperInitializations = components
      .map(
        (component) =>
            '    ${_toMapperName(component)} = Mapper<$component>(world);',
      )
      .join('\n');
  final optionalMapperInitializations = optionalComponents
      .map(
        (component) =>
            '''    ${_toMapperName(component)} = OptionalMapper<$component>(world);''',
      )
      .join('\n');
  final systemInitializations = systems
      .map(
        (system) =>
            '    ${_toVariableName(system)} = world.getSystem<$system>();',
      )
      .join('\n');
  final managerInitializations = managers
      .map(
        (manager) =>
            '    ${_toVariableName(manager)} = world.getManager<$manager>();',
      )
      .join('\n');
  final result = baseClassTypeParameters.isEmpty
      ? StringBuffer('abstract class _\$$className extends $baseClassName {')
      : StringBuffer(
          '''abstract class _\$$className<${_baseClassBoundedTypeParameters(baseClassTypeParameters)}> extends $baseClassName<${_baseClassUnboundedTypeParameters(baseClassTypeParameters)}> {''',
        );
  final hasFields =
      _declaresFields(components, optionalComponents, systems, managers);
  if (hasFields) {
    result.writeln();
    if (components.isNotEmpty) {
      result.writeln(mapperDeclarations);
    }
    if (optionalComponents.isNotEmpty) {
      result.writeln(optionalMapperDeclarations);
    }
    if (systems.isNotEmpty) {
      result.writeln(systemDeclarations);
    }
    if (managers.isNotEmpty) {
      result.writeln(managerDeclarations);
    }
  }
  if (needsConstructor) {
    if (!hasFields) {
      result.writeln();
    }
    final constructorParameterString =
        constructorParameter.where((param) => param.isNotEmpty).join(', ');
    result.write('  _\$$className($constructorParameterString)');
    if (!useSuperParameters) {
      result.write(' : super($superCallParameter)');
    }
    result.writeln(';');
  }

  if (hasFields) {
    result
      ..writeln('  @override')
      ..writeln('  void initialize(World world) {')
      ..writeln('    super.initialize(world);');
    if (components.isNotEmpty) {
      result.writeln(mapperInitializations);
    }
    if (optionalComponents.isNotEmpty) {
      result.writeln(optionalMapperInitializations);
    }
    if (systems.isNotEmpty) {
      result.writeln(systemInitializations);
    }
    if (managers.isNotEmpty) {
      result.writeln(managerInitializations);
    }
    result.writeln('  }');
  }

  if (shouldCreateCustomProcessEntity) {
    result
      ..writeln('  @override')
      ..writeln('  void processEntities(Iterable<Entity> entities) {');
    for (final aspect in allOfAspects.followedBy(oneOfAspects)) {
      final mapperName = _toMapperName(aspect);
      result.writeln('    final $mapperName = this.$mapperName;');
    }
    final arguments = allOfAspects
        .followedBy(oneOfAspects)
        .map((e) => '${_toMapperName(e)}[entity]')
        .join(', ');
    result
      ..writeln('    for (final entity in entities) {')
      ..writeln('      processEntity(entity, $arguments);')
      ..writeln('    }')
      ..writeln('  }');

    final parameters = [
      ...allOfAspects.map((e) => '$e ${_toVariableName(e)}'),
      ...oneOfAspects.map((e) => '$e? ${_toVariableName(e)}'),
    ].join(', ');
    result.writeln('  void processEntity(Entity entity, $parameters);');
  }

  result.writeln('}');

  return result.toString();
}