dart-lang / code_builder

A fluent API for generating valid Dart source code
https://pub.dev/packages/code_builder
BSD 3-Clause "New" or "Revised" License
423 stars 66 forks source link

Add support for trailing commas Records #461

Closed hasimyerlikaya closed 1 month ago

hasimyerlikaya commented 1 month ago

Hi,

I'm using the code_builder package to create some classes and I'm mainly using variables of type Records. There is no comma at the end of the named fields, so the format is corrupted when I save the file.

The trailing comma feature has been added to Emitter for some data types, but I think not for Records.

Do you have a suggestion?

Thank you.

Source:

import 'package:lang_generator/lang_generator.dart';

@AppLangConfig(jsonExport: true)
@AppLangImporter(
  path: "lib/app_lang_source.en_US.json",
  locale: "en_US",
)
class AppLangSource {
  var incomeTransaction = (
    fields: (
      amount: (tr_TR: "Fiyat", en_US: "Amount"),
      paymentDate: (tr_TR: "Ödeme Tarihi", en_US: "Payment Date"),
    ),
    actions: (
      update: (tr_TR: "Düzenle", en_US: "Edit"),
      detail: (tr_TR: "Detay", en_US: "Detail"),
      addTag: (tr_TR: "Etiket Ekle", en_US: "Add Tag"),
    ),
    validations: (
      amount: (
        notNull: (tr_TR: "Tutar girmelisiniz", en_US: "You must enter an amount"),
        lessThen: (tr_TR: "Tutar 100'den küçük olmalı", en_US: "Amount must be less than 100"),
      ),
    ),
  );

  var expenseTransaction = (
    fields: (
      amount: (tr_TR: "Fiyat", en_US: "Amount"),
      paymentDate: (tr_TR: "Ödeme Tarihi", en_US: "Payment Date"),
    ),
  );
}

Generated:

import 'package:lang_generator/lang_generator.dart';

@AppLangConfig(jsonExport: false)
@AppLangImporter(path: "lib/app_lang_source.en_US.json", locale: "en_US")
class AppLangSource {
  var incomeTransaction = (
    fields: (
      amount: (tr_TR: 'Fiyat', en_US: 'Amount'),
      paymentDate: (tr_TR: 'Ödeme Tarihi', en_US: 'Payment Date')
    ),
    actions: (
      update: (tr_TR: 'Düzenle', en_US: 'Edit'),
      detail: (tr_TR: 'Detay', en_US: 'Detail'),
      addTag: (tr_TR: 'Etiket Ekle', en_US: 'Add Tag')
    ),
    validations: (
      amount: (
        notNull: (
          tr_TR: 'Tutar girmelisiniz',
          en_US: 'You must enter an amount'
        ),
        lessThen: (
          tr_TR: 'Tutar 100\'den küçük olmalı',
          en_US: 'Amount must be less than 100'
        )
      )
    )
  );

  var expenseTransaction = (
    fields: (
      amount: (tr_TR: 'Fiyat', en_US: 'Amount'),
      paymentDate: (tr_TR: 'Ödeme Tarihi', en_US: 'Payment Date')
    )
  );
}
AkashKeote commented 1 month ago

import 'package:code_builder/code_builder.dart';

class CustomEmitter extends DartEmitter { CustomEmitter() : super(Allocator.simplePrefixing(), true, false);

@override StringSink visitExpression(Expression expression, [StringSink? output]) { var sink = output ?? StringBuffer(); if (expression is CodeExpression) { expression.code.accept(this, sink); } else if (expression is LiteralExpression) { final value = expression.literal.toString(); if (value.startsWith("'") && value.endsWith("'")) { sink.write('"' + value.substring(1, value.length - 1) + '"'); } else { sink.write(value); } } else { super.visitExpression(expression, sink); } return sink; }

@override StringSink visitRecordLiteral(RecordLiteral literal, [StringSink? output]) { var sink = output ?? StringBuffer(); sink.write('('); var needsComma = false; for (var field in literal.fields) { if (needsComma) { sink.write(', '); } field.accept(this, sink); needsComma = true; } if (needsComma) { sink.write(','); // Add trailing comma for named fields } sink.write(')'); return sink; } }

void main() { var emitter = CustomEmitter(); var spec = Code(''' import 'package:lang_generator/lang_generator.dart';

@AppLangConfig(jsonExport: true) @AppLangImporter( path: "lib/app_lang_source.en_US.json", locale: "en_US", ) class AppLangSource { var incomeTransaction = ( fields: ( amount: (tr_TR: "Fiyat", en_US: "Amount"), paymentDate: (tr_TR: "Ödeme Tarihi", en_US: "Payment Date"), ), actions: ( update: (tr_TR: "Düzenle", en_US: "Edit"), detail: (tr_TR: "Detay", en_US: "Detail"), addTag: (tr_TR: "Etiket Ekle", en_US: "Add Tag"), ), validations: ( amount: ( notNull: (tr_TR: "Tutar girmelisiniz", en_US: "You must enter an amount"), lessThen: (tr_TR: "Tutar 100'den küçük olmalı", en_US: "Amount must be less than 100"), ), ), );

var expenseTransaction = ( fields: ( amount: (tr_TR: "Fiyat", en_US: "Amount"), paymentDate: (tr_TR: "Ödeme Tarihi", en_US: "Payment Date"), ), ); } '''); print(spec.accept(emitter)); }

AkashKeote commented 1 month ago

The CustomEmitter class inherits from DartEmitter: ----- The visitExpression method overrides the default behavior to ensure double quotes are retained. The visitRecordLiteral method is customized to add a trailing comma after each named field.

hasimyerlikaya commented 1 month ago

@AkashKeote Thank you so much. It seems you are using different version of code_builder. But you gave me the idea and I did it.

code_builder: ^4.10.0

import 'package:code_builder/code_builder.dart';
import 'package:code_builder/src/specs/expression.dart';

// region [p]
class CustomEmitter extends DartEmitter {
  CustomEmitter()
      : super(
          allocator: Allocator.simplePrefixing(),
          orderDirectives: true,
          useNullSafetySyntax: false,
        );

  @override
  StringSink visitLiteralExpression(LiteralExpression expression, [StringSink? output]) {
    output ??= StringBuffer();
    final escaped = expression.literal.replaceAll('\\\'', '\'').replaceAll('\\n', '\n');

    if (escaped.startsWith("'") && escaped.endsWith("'")) {
      return output..write('"${escaped.substring(1, escaped.length - 1)}"');
    }

    return output..write(escaped);
  }

  @override
  StringSink visitLiteralRecordExpression(LiteralRecordExpression expression, [StringSink? output]) {
    final out = output ??= StringBuffer();
    out.write('(');
    _visitAll<Object?>(expression.positionalFieldValues, out, (value) {
      _acceptLiteral(value, out);
    });
    if (expression.namedFieldValues.isNotEmpty) {
      if (expression.positionalFieldValues.isNotEmpty) {
        out.write(', ');
      }
    } else if (expression.positionalFieldValues.length == 1) {
      out.write(',');
    }
    _visitAll<MapEntry<String, Object?>>(expression.namedFieldValues.entries, out, (entry) {
      out.write('${entry.key}: ');
      _acceptLiteral(entry.value, out);
    });
    if (expression.namedFieldValues.isNotEmpty) {
      out.write(',');
    }
    return out..write(')');
  }

  /// Helper method improving on [StringSink.writeAll].
  ///
  /// For every `Spec` in [elements], executing [visit].
  ///
  /// If [elements] is at least 2 elements, inserts [separator] delimiting them.
  StringSink _visitAll<T>(
    Iterable<T> elements,
    StringSink output,
    void Function(T) visit, [
    String separator = ', ',
  ]) {
    // Basically, this whole method is an improvement on
    //   output.writeAll(specs.map((s) => s.accept(visitor));
    //
    // ... which would allocate more StringBuffer(s) for a one-time use.
    if (elements.isEmpty) {
      return output;
    }
    final iterator = elements.iterator..moveNext();
    visit(iterator.current);
    while (iterator.moveNext()) {
      output.write(separator);
      visit(iterator.current);
    }
    return output;
  }

  void _acceptLiteral(Object? literalOrSpec, StringSink output) {
    if (literalOrSpec is Spec) {
      literalOrSpec.accept(this, output);
      return;
    }
    literal(literalOrSpec).accept(this, output);
  }
}

// endregion
AkashKeote commented 1 month ago

need your help how to contribute on the gssoc as my exam is over now and i am in 2nd year dont have much idea about pull request and fork button not here

hasimyerlikaya commented 1 month ago

@AkashKeote

Fork Button on the home page of the repo. You can make changes and then you can create pull request on your forked repo.

image
AkashKeote commented 1 month ago

thanku so much sir this will help me a lot

hasimyerlikaya commented 1 month ago

@AkashKeote You are welcome. You helped me a lot, too. Thank you. 👍