dart-lang / tools

This repository is home to tooling related Dart packages.
BSD 3-Clause "New" or "Revised" License
31 stars 24 forks source link

Add support for trailing commas Records #1148

Closed hasimyerlikaya closed 5 months ago

hasimyerlikaya commented 5 months 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 5 months 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 5 months 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 5 months 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 5 months 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 5 months 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 5 months ago

thanku so much sir this will help me a lot

hasimyerlikaya commented 5 months ago

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