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

Support emitting to a part file (`.g.dart`) #442

Open TekExplorer opened 6 months ago

TekExplorer commented 6 months ago

I have a generator that emits to the shared .g.dart file, but it doesn't appear to work if the source file has aliased imports.

Of course, i cant add any imports myself due to being a part file, so how do I make use of the pre-existing aliased imports?

I'm defining urls in Reference/refer as normal, but I cant seem to find an option that correctly uses the aliased imports without generating imports ourselves, as part files cant have imports.

Isakdl commented 5 months ago

Did you find a solution to this?

Isakdl commented 5 months ago

I did some digging in the code to figure out how to achieve an automated handling of imports. As you mentioned there is no functionality built in to deal with this today.

But it was fairly easy to implement a solution!

The imports are handled by the Allocator class and can be injected to the DartEmitter when generating the code. Unfortunately, there are only 3 different implementations of the Allocator that either resolve no imports, all imports in the file without prefixes, and all imports in the file with prefixes.

But you can create your own implementation, I added two new Allocators one PartOfAllocator and one PartAllocator.

These will preserve the state and keep the prefixes in sync between the part of and the part file.

class PartOfAllocator implements Allocator {
  static final _doNotPrefix = ['dart:core'];

  final _imports = <String, int>{};
  var _keys = 1;

  PartOfAllocator(List<String>? doNotPrefix) {
    _doNotPrefix.addAll(doNotPrefix ?? []);
  }

  @override
  String allocate(Reference reference) {
    final symbol = reference.symbol;
    final url = reference.url;
    if (url == null || _doNotPrefix.contains(url)) {
      return symbol!;
    }
    return '_i${_imports.putIfAbsent(url, _nextKey)}.$symbol';
  }

  int _nextKey() => _keys++;

  @override
  Iterable<Directive> get imports => [];
}
class PartAllocator implements Allocator {
  PartOfAllocator _partOfAllocator;

  PartAllocator._(this._partOfAllocator);

  factory PartAllocator(PartOfAllocator partOfAllocator) {
    return PartAllocator._(partOfAllocator);
  }

  @override
  String allocate(Reference reference) {
    return _partOfAllocator.allocate(reference);
  }

  @override
  Iterable<Directive> get imports => _partOfAllocator._imports.keys.map(
        (u) => Directive.import(u, as: '_i${_partOfAllocator._imports[u]}'),
      );
}

Then you can use the allocators when generating the dart code like so:

final partOfAllocator = PartOfAllocator();
final partOfFile = Library(...).accept(DartEmitter(allocator: partOfAllocator)).toString()

final partFile = Library(...).accept(DartEmitter(allocator: PartAllocator(partOfAllocator))).toString()

I may create a PR with these later when I have time!

TekExplorer commented 2 weeks ago

@Isakdl I'm seeing a lot of _i Where's the "use existing imports and aliases" option?