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. When multiple values are returned through an iterable or stream they will be deduplicated. Typically each value will be an independent unit of code and the deduplication prevents re-defining the same member multiple times. For example if multiple annotated elements may need a specific utility method available it can be output for each one, and the single deduplicated definition can be shared.

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.formalParameters.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.formalParameters
        .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.formalParameters
          .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.formalParameters
        .where((parameter) => parameter.isOptionalPositional)
        .map((parameter) => 'super.${parameter.name}')
        .join(', ');
    if (optionalPositionalParameters.isNotEmpty) {
      constructorParameter.add('[$optionalPositionalParameters]');
    }
    final optionalNamedParameters = baseClassConstructor.formalParameters
        .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();
}