Skip to content
This repository was archived by the owner on Apr 8, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/code_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

export 'src/allocator.dart' show Allocator;
export 'src/base.dart' show Spec;
export 'src/emitter.dart' show DartEmitter;
export 'src/matchers.dart' show equalsDart;
export 'src/specs/annotation.dart' show Annotation, AnnotationBuilder;
export 'src/specs/class.dart' show Class, ClassBuilder;
export 'src/specs/code.dart' show Code, CodeBuilder;
export 'src/specs/constructor.dart' show Constructor, ConstructorBuilder;
export 'src/specs/directive.dart'
show Directive, DirectiveType, DirectiveBuilder;
export 'src/specs/field.dart' show Field, FieldBuilder, FieldModifier;
export 'src/specs/file.dart' show File, FileBuilder;
export 'src/specs/method.dart'
show Method, MethodBuilder, MethodType, Parameter, ParameterBuilder;
export 'src/specs/reference.dart' show Reference;
Expand Down
95 changes: 95 additions & 0 deletions lib/src/allocator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'specs/directive.dart';
import 'specs/reference.dart';

/// Collects references and automatically allocates prefixes and imports.
///
/// `Allocator` takes out the manual work of deciding whether a symbol will
/// clash with other imports in your generated code, or what imports are needed
/// to resolve all symbols in your generated code.
abstract class Allocator {
/// An allocator that does not prefix symbols nor collects imports.
static const Allocator none = const _NullAllocator();

/// Creates a new default allocator that applies no prefixing.
factory Allocator() = _Allocator;

/// Creates a new allocator that applies naive prefixing to avoid conflicts.
///
/// This implementation is not optimized for any particular code generation
/// style and instead takes a conservative approach of prefixing _every_
/// import except references to `dart:core` (which are considered always
/// imported).
factory Allocator.simplePrefixing() = _PrefixedAllocator;

/// Returns a reference string given a [reference] object.
///
/// For example, a no-op implementation:
/// ```dart
/// allocate(const Reference('List', 'dart:core')); // Returns 'List'.
/// ```
///
/// Where-as an implementation that prefixes imports might output:
/// ```dart
/// allocate(const Reference('Foo', 'package:foo')); // Returns '_i1.Foo'.
/// ```
String allocate(Reference reference);

/// All imports that have so far been added implicitly via [allocate].
Iterable<Directive> get imports;
}

class _Allocator implements Allocator {
final _imports = new Set<String>();

@override
String allocate(Reference reference) {
if (reference.url != null) {
_imports.add(reference.url);
}
return reference.symbol;
}

@override
Iterable<Directive> get imports {
return _imports.map((u) => new Directive.import(u));
}
}

class _NullAllocator implements Allocator {
const _NullAllocator();

@override
String allocate(Reference reference) => reference.symbol;

@override
Iterable<Directive> get imports => const [];
}

class _PrefixedAllocator implements Allocator {
static const _doNotPrefix = const ['dart:core'];

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

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

int _nextKey() => _keys++;

@override
Iterable<Directive> get imports {
return _imports.keys.map(
(u) => new Directive.import(u, as: '_${_imports[u]}'),
);
}
}
56 changes: 53 additions & 3 deletions lib/src/emitter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,30 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'allocator.dart';
import 'base.dart';
import 'specs/annotation.dart';
import 'specs/class.dart';
import 'specs/code.dart';
import 'specs/constructor.dart';
import 'specs/directive.dart';
import 'specs/field.dart';
import 'specs/file.dart';
import 'specs/method.dart';
import 'specs/reference.dart';
import 'specs/type_reference.dart';
import 'visitors.dart';

class DartEmitter extends GeneralizingSpecVisitor<StringSink> {
const DartEmitter();
class DartEmitter implements SpecVisitor<StringSink> {
final Allocator _allocator;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think _allocator.imports is ever called. So how would you actually print out the imports in a file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good catch. Added to visitFile and updated file_test.


/// Creates a new instance of [DartEmitter].
///
/// May specify an [Allocator] to use for symbols, otherwise uses a no-op.
const DartEmitter([this._allocator = Allocator.none]);

/// Creates a new instance of [DartEmitter] with a default [Allocator].
factory DartEmitter.scoped() => new DartEmitter(new Allocator());

@override
visitAnnotation(Annotation spec, [StringSink output]) {
Expand Down Expand Up @@ -154,6 +166,22 @@ class DartEmitter extends GeneralizingSpecVisitor<StringSink> {
return output..write(code);
}

@override
visitDirective(Directive spec, [StringSink output]) {
output ??= new StringBuffer();
if (spec.type == DirectiveType.import) {
output.write('import ');
} else {
output.write('export ');
}
output.write("'${spec.url}'");
if (spec.as != null) {
output.write(' as ${spec.as}');
}
output.write(';');
return output;
}

@override
visitField(Field spec, [StringSink output]) {
output ??= new StringBuffer();
Expand Down Expand Up @@ -188,6 +216,25 @@ class DartEmitter extends GeneralizingSpecVisitor<StringSink> {
return output;
}

@override
visitFile(File spec, [StringSink output]) {
output ??= new StringBuffer();
// Process the body first in order to prime the allocators.
final body = new StringBuffer();
for (final spec in spec.body) {
body.write(visitSpec(spec));
}
// TODO: Allow some sort of logical ordering.
for (final directive in spec.directives) {
visitDirective(directive, output);
}
for (final directive in _allocator.imports) {
visitDirective(directive, output);
}
output.write(body);
return output;
}

@override
visitMethod(Method spec, [StringSink output]) {
output ??= new StringBuffer();
Expand Down Expand Up @@ -293,9 +340,12 @@ class DartEmitter extends GeneralizingSpecVisitor<StringSink> {

@override
visitReference(Reference spec, [StringSink output]) {
return (output ??= new StringBuffer())..write(spec.symbol);
return (output ??= new StringBuffer())..write(_allocator.allocate(spec));
}

@override
visitSpec(Spec spec) => spec.accept(this);

@override
visitType(TypeReference spec, [StringSink output]) {
output ??= new StringBuffer();
Expand Down
30 changes: 21 additions & 9 deletions lib/src/matchers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,21 @@ String _dartfmt(String source) {
}

/// Encodes [spec] as Dart source code.
String _dart(Spec spec) =>
_dartfmt(spec.accept<StringSink>(const DartEmitter()).toString());
String _dart(Spec spec, DartEmitter emitter) =>
_dartfmt(spec.accept<StringSink>(emitter).toString()).trim();

/// Returns a matcher for Dart source code.
Matcher equalsDart(String source) => new _EqualsDart(_dartfmt(source));
Matcher equalsDart(
String source, [
DartEmitter emitter = const DartEmitter(),
]) =>
new _EqualsDart(_dartfmt(source).trim(), emitter);

class _EqualsDart extends Matcher {
final DartEmitter _emitter;
final String _source;

const _EqualsDart(this._source);
const _EqualsDart(this._source, this._emitter);

@override
Description describe(Description description) => description.add(_source);
Expand All @@ -40,11 +45,18 @@ class _EqualsDart extends Matcher {
Description describeMismatch(
covariant Spec item,
Description mismatchDescription,
_,
__,
) =>
mismatchDescription.add(_dart(item));
state,
verbose,
) {
final result = _dart(item, _emitter);
return equals(result).describeMismatch(
_source,
mismatchDescription,
state,
verbose,
);
}

@override
bool matches(covariant Spec item, _) => _dart(item) == _source;
bool matches(covariant Spec item, _) => _dart(item, _emitter) == _source;
}
61 changes: 61 additions & 0 deletions lib/src/specs/directive.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library code_builder.src.specs.directive;

import 'package:built_value/built_value.dart';
import 'package:meta/meta.dart';

import '../base.dart';
import '../visitors.dart';

part 'directive.g.dart';

@immutable
abstract class Directive implements Built<Directive, DirectiveBuilder>, Spec {
factory Directive([void updates(DirectiveBuilder b)]) = _$Directive;

factory Directive.import(
String url, {
String as,
}) =>
new Directive((builder) => builder
..as = as
..type = DirectiveType.import
..url = url);

factory Directive.export(String url) => new Directive((builder) => builder
..type = DirectiveType.export
..url = url);

Directive._();

@nullable
String get as;

String get url;

DirectiveType get type;

@override
R accept<R>(SpecVisitor<R> visitor) => visitor.visitDirective(this);
}

abstract class DirectiveBuilder
implements Builder<Directive, DirectiveBuilder> {
factory DirectiveBuilder() = _$DirectiveBuilder;

DirectiveBuilder._();

String as;

String url;

DirectiveType type;
}

enum DirectiveType {
import,
export,
}
Loading