From 180d067df20b1b5e25077a19635b6f63fb700198 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Tue, 14 Nov 2023 13:25:31 -0800 Subject: [PATCH 1/5] Add support for extension types https://github.com/dart-lang/code_builder/issues/399 --- CHANGELOG.md | 1 + lib/code_builder.dart | 1 + lib/src/emitter.dart | 57 +++ lib/src/specs/extension_type.dart | 152 ++++++++ lib/src/specs/extension_type.g.dart | 537 ++++++++++++++++++++++++++++ lib/src/visitors.dart | 3 + pubspec.yaml | 7 +- test/specs/extension_test.dart | 21 -- test/specs/extension_type_test.dart | 242 +++++++++++++ 9 files changed, 999 insertions(+), 22 deletions(-) create mode 100644 lib/src/specs/extension_type.dart create mode 100644 lib/src/specs/extension_type.g.dart create mode 100644 test/specs/extension_type_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 5db7a2f8..2777beb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ * Add `Expression.bitwiseXorAssign` * Add `Expression.bitwiseOrAssign` * Allow passing an `Expression` through `literal` without an exception. +* Add support for extension types. ## 4.7.0 diff --git a/lib/code_builder.dart b/lib/code_builder.dart index c98e19ed..17a11ef3 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -48,6 +48,7 @@ export 'src/specs/expression.dart' literalString, literalTrue; export 'src/specs/extension.dart' show Extension, ExtensionBuilder; +export 'src/specs/extension_type.dart' show ExtensionType, ExtensionTypeBuilder; export 'src/specs/field.dart' show Field, FieldBuilder, FieldModifier; export 'src/specs/library.dart' show Library, LibraryBuilder; export 'src/specs/method.dart' diff --git a/lib/src/emitter.dart b/lib/src/emitter.dart index 79b0e0db..591b992c 100644 --- a/lib/src/emitter.dart +++ b/lib/src/emitter.dart @@ -11,6 +11,7 @@ import 'specs/directive.dart'; import 'specs/enum.dart'; import 'specs/expression.dart'; import 'specs/extension.dart'; +import 'specs/extension_type.dart'; import 'specs/field.dart'; import 'specs/library.dart'; import 'specs/method.dart'; @@ -336,6 +337,62 @@ class DartEmitter extends Object return out; } + @override + StringSink visitExtensionType(ExtensionType spec, [StringSink? output]) { + final out = output ??= StringBuffer(); + spec.docs.forEach(out.writeln); + for (var a in spec.annotations) { + visitAnnotation(a, out); + } + + out.write('extension type '); + if (spec.constant) out.write('const '); + out.write(spec.name); + visitTypeParameters(spec.types.map((r) => r.type), out); + if (spec.primaryConstructorName.isNotEmpty) { + out.write('.${spec.primaryConstructorName}'); + } + out.write('('); + _visitRepresentationDeclaration(spec.representationDeclaration, out); + out.write(')'); + + if (spec.implements.isNotEmpty) { + out + ..write(' implements ') + ..writeAll( + spec.implements.map((m) => m.type.accept(this)), ','); + } + + out.writeln(' {'); + for (var c in spec.constructors) { + visitConstructor(c, spec.name, out); + out.writeln(); + } + for (var f in spec.fields) { + visitField(f, out); + out.writeln(); + } + for (var m in spec.methods) { + visitMethod(m, out); + if (_isLambdaMethod(m)) { + out.writeln(';'); + } + out.writeln(); + } + out.writeln('}'); + return out; + } + + void _visitRepresentationDeclaration( + RepresentationDeclaration spec, StringSink out) { + spec.docs.forEach(out.writeln); + for (var a in spec.annotations) { + visitAnnotation(a, out); + } + spec.declaredRepresentationType.accept(this, out); + out.write(' ${spec.name}'); + } + @override StringSink visitDirective(Directive spec, [StringSink? output]) { output ??= StringBuffer(); diff --git a/lib/src/specs/extension_type.dart b/lib/src/specs/extension_type.dart new file mode 100644 index 00000000..ec3bd335 --- /dev/null +++ b/lib/src/specs/extension_type.dart @@ -0,0 +1,152 @@ +// Copyright (c) 2023, 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 'package:built_collection/built_collection.dart'; +import 'package:built_value/built_value.dart'; +import 'package:meta/meta.dart'; + +import '../base.dart'; +import '../mixins/annotations.dart'; +import '../mixins/dartdoc.dart'; +import '../mixins/generics.dart'; +import '../visitors.dart'; +import 'constructor.dart'; +import 'expression.dart'; +import 'field.dart'; +import 'method.dart'; +import 'reference.dart'; + +part 'extension_type.g.dart'; + +@immutable +abstract class ExtensionType extends Object + with HasAnnotations, HasDartDocs, HasGenerics + implements Built, Spec { + factory ExtensionType([void Function(ExtensionTypeBuilder)? updates]) = + _$ExtensionType; + + ExtensionType._(); + + @override + BuiltList get annotations; + + @override + BuiltList get docs; + + /// Whether this extension type is declared as `const`. + bool get constant; + + String get name; + + @override + BuiltList get types; + + /// Name of the extension type's primary constructor. An empty string + /// will make it unnamed. + String get primaryConstructorName; + + RepresentationDeclaration get representationDeclaration; + + BuiltList get implements; + + BuiltList get constructors; + + BuiltList get fields; + + BuiltList get methods; + + @override + R accept( + SpecVisitor visitor, [ + R? context, + ]) => + visitor.visitExtensionType(this, context); +} + +abstract class ExtensionTypeBuilder extends Object + with HasAnnotationsBuilder, HasDartDocsBuilder, HasGenericsBuilder + implements Builder { + factory ExtensionTypeBuilder() = _$ExtensionTypeBuilder; + + ExtensionTypeBuilder._(); + + @override + void update(void Function(ExtensionTypeBuilder)? updates) { + updates?.call(this); + } + + @override + ListBuilder annotations = ListBuilder(); + + @override + ListBuilder docs = ListBuilder(); + + /// Whether this extension type is declared as `const`. + bool constant = false; + + String? name; + + @override + ListBuilder types = ListBuilder(); + + /// Name of the extension type's primary constructor. An empty string + /// will make it unnamed. + String primaryConstructorName = ''; + + RepresentationDeclaration? representationDeclaration; + + ListBuilder implements = ListBuilder(); + + ListBuilder constructors = ListBuilder(); + + ListBuilder fields = ListBuilder(); + + ListBuilder methods = ListBuilder(); +} + +abstract class RepresentationDeclaration extends Object + with HasAnnotations, HasDartDocs + implements + Built { + factory RepresentationDeclaration( + [void Function(RepresentationDeclarationBuilder)? updates]) = + _$RepresentationDeclaration; + + RepresentationDeclaration._(); + + @override + BuiltList get annotations; + + @override + BuiltList get docs; + + Reference get declaredRepresentationType; + + String get name; +} + +abstract class RepresentationDeclarationBuilder extends Object + with HasAnnotationsBuilder, HasDartDocsBuilder + implements + Builder { + factory RepresentationDeclarationBuilder() = + _$RepresentationDeclarationBuilder; + + RepresentationDeclarationBuilder._(); + + @override + void update(void Function(RepresentationDeclarationBuilder)? updates) { + updates?.call(this); + } + + @override + ListBuilder annotations = ListBuilder(); + + @override + ListBuilder docs = ListBuilder(); + + Reference? declaredRepresentationType; + + String? name; +} diff --git a/lib/src/specs/extension_type.g.dart b/lib/src/specs/extension_type.g.dart new file mode 100644 index 00000000..57698403 --- /dev/null +++ b/lib/src/specs/extension_type.g.dart @@ -0,0 +1,537 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'extension_type.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +class _$ExtensionType extends ExtensionType { + @override + final BuiltList annotations; + @override + final BuiltList docs; + @override + final bool constant; + @override + final String name; + @override + final BuiltList types; + @override + final String primaryConstructorName; + @override + final RepresentationDeclaration representationDeclaration; + @override + final BuiltList implements; + @override + final BuiltList constructors; + @override + final BuiltList fields; + @override + final BuiltList methods; + + factory _$ExtensionType([void Function(ExtensionTypeBuilder)? updates]) => + (new ExtensionTypeBuilder()..update(updates)).build() as _$ExtensionType; + + _$ExtensionType._( + {required this.annotations, + required this.docs, + required this.constant, + required this.name, + required this.types, + required this.primaryConstructorName, + required this.representationDeclaration, + required this.implements, + required this.constructors, + required this.fields, + required this.methods}) + : super._() { + BuiltValueNullFieldError.checkNotNull( + annotations, r'ExtensionType', 'annotations'); + BuiltValueNullFieldError.checkNotNull(docs, r'ExtensionType', 'docs'); + BuiltValueNullFieldError.checkNotNull( + constant, r'ExtensionType', 'constant'); + BuiltValueNullFieldError.checkNotNull(name, r'ExtensionType', 'name'); + BuiltValueNullFieldError.checkNotNull(types, r'ExtensionType', 'types'); + BuiltValueNullFieldError.checkNotNull( + primaryConstructorName, r'ExtensionType', 'primaryConstructorName'); + BuiltValueNullFieldError.checkNotNull(representationDeclaration, + r'ExtensionType', 'representationDeclaration'); + BuiltValueNullFieldError.checkNotNull( + implements, r'ExtensionType', 'implements'); + BuiltValueNullFieldError.checkNotNull( + constructors, r'ExtensionType', 'constructors'); + BuiltValueNullFieldError.checkNotNull(fields, r'ExtensionType', 'fields'); + BuiltValueNullFieldError.checkNotNull(methods, r'ExtensionType', 'methods'); + } + + @override + ExtensionType rebuild(void Function(ExtensionTypeBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + _$ExtensionTypeBuilder toBuilder() => + new _$ExtensionTypeBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is ExtensionType && + annotations == other.annotations && + docs == other.docs && + constant == other.constant && + name == other.name && + types == other.types && + primaryConstructorName == other.primaryConstructorName && + representationDeclaration == other.representationDeclaration && + implements == other.implements && + constructors == other.constructors && + fields == other.fields && + methods == other.methods; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, annotations.hashCode); + _$hash = $jc(_$hash, docs.hashCode); + _$hash = $jc(_$hash, constant.hashCode); + _$hash = $jc(_$hash, name.hashCode); + _$hash = $jc(_$hash, types.hashCode); + _$hash = $jc(_$hash, primaryConstructorName.hashCode); + _$hash = $jc(_$hash, representationDeclaration.hashCode); + _$hash = $jc(_$hash, implements.hashCode); + _$hash = $jc(_$hash, constructors.hashCode); + _$hash = $jc(_$hash, fields.hashCode); + _$hash = $jc(_$hash, methods.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'ExtensionType') + ..add('annotations', annotations) + ..add('docs', docs) + ..add('constant', constant) + ..add('name', name) + ..add('types', types) + ..add('primaryConstructorName', primaryConstructorName) + ..add('representationDeclaration', representationDeclaration) + ..add('implements', implements) + ..add('constructors', constructors) + ..add('fields', fields) + ..add('methods', methods)) + .toString(); + } +} + +class _$ExtensionTypeBuilder extends ExtensionTypeBuilder { + _$ExtensionType? _$v; + + @override + ListBuilder get annotations { + _$this; + return super.annotations; + } + + @override + set annotations(ListBuilder annotations) { + _$this; + super.annotations = annotations; + } + + @override + ListBuilder get docs { + _$this; + return super.docs; + } + + @override + set docs(ListBuilder docs) { + _$this; + super.docs = docs; + } + + @override + bool get constant { + _$this; + return super.constant; + } + + @override + set constant(bool constant) { + _$this; + super.constant = constant; + } + + @override + String? get name { + _$this; + return super.name; + } + + @override + set name(String? name) { + _$this; + super.name = name; + } + + @override + ListBuilder get types { + _$this; + return super.types; + } + + @override + set types(ListBuilder types) { + _$this; + super.types = types; + } + + @override + String get primaryConstructorName { + _$this; + return super.primaryConstructorName; + } + + @override + set primaryConstructorName(String primaryConstructorName) { + _$this; + super.primaryConstructorName = primaryConstructorName; + } + + @override + RepresentationDeclaration? get representationDeclaration { + _$this; + return super.representationDeclaration; + } + + @override + set representationDeclaration( + RepresentationDeclaration? representationDeclaration) { + _$this; + super.representationDeclaration = representationDeclaration; + } + + @override + ListBuilder get implements { + _$this; + return super.implements; + } + + @override + set implements(ListBuilder implements) { + _$this; + super.implements = implements; + } + + @override + ListBuilder get constructors { + _$this; + return super.constructors; + } + + @override + set constructors(ListBuilder constructors) { + _$this; + super.constructors = constructors; + } + + @override + ListBuilder get fields { + _$this; + return super.fields; + } + + @override + set fields(ListBuilder fields) { + _$this; + super.fields = fields; + } + + @override + ListBuilder get methods { + _$this; + return super.methods; + } + + @override + set methods(ListBuilder methods) { + _$this; + super.methods = methods; + } + + _$ExtensionTypeBuilder() : super._(); + + ExtensionTypeBuilder get _$this { + final $v = _$v; + if ($v != null) { + super.annotations = $v.annotations.toBuilder(); + super.docs = $v.docs.toBuilder(); + super.constant = $v.constant; + super.name = $v.name; + super.types = $v.types.toBuilder(); + super.primaryConstructorName = $v.primaryConstructorName; + super.representationDeclaration = $v.representationDeclaration; + super.implements = $v.implements.toBuilder(); + super.constructors = $v.constructors.toBuilder(); + super.fields = $v.fields.toBuilder(); + super.methods = $v.methods.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(ExtensionType other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$ExtensionType; + } + + @override + void update(void Function(ExtensionTypeBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + ExtensionType build() => _build(); + + _$ExtensionType _build() { + _$ExtensionType _$result; + try { + _$result = _$v ?? + new _$ExtensionType._( + annotations: annotations.build(), + docs: docs.build(), + constant: BuiltValueNullFieldError.checkNotNull( + constant, r'ExtensionType', 'constant'), + name: BuiltValueNullFieldError.checkNotNull( + name, r'ExtensionType', 'name'), + types: types.build(), + primaryConstructorName: BuiltValueNullFieldError.checkNotNull( + primaryConstructorName, + r'ExtensionType', + 'primaryConstructorName'), + representationDeclaration: BuiltValueNullFieldError.checkNotNull( + representationDeclaration, + r'ExtensionType', + 'representationDeclaration'), + implements: implements.build(), + constructors: constructors.build(), + fields: fields.build(), + methods: methods.build()); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'annotations'; + annotations.build(); + _$failedField = 'docs'; + docs.build(); + + _$failedField = 'types'; + types.build(); + + _$failedField = 'implements'; + implements.build(); + _$failedField = 'constructors'; + constructors.build(); + _$failedField = 'fields'; + fields.build(); + _$failedField = 'methods'; + methods.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + r'ExtensionType', _$failedField, e.toString()); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +class _$RepresentationDeclaration extends RepresentationDeclaration { + @override + final BuiltList annotations; + @override + final BuiltList docs; + @override + final Reference declaredRepresentationType; + @override + final String name; + + factory _$RepresentationDeclaration( + [void Function(RepresentationDeclarationBuilder)? updates]) => + (new RepresentationDeclarationBuilder()..update(updates)).build() + as _$RepresentationDeclaration; + + _$RepresentationDeclaration._( + {required this.annotations, + required this.docs, + required this.declaredRepresentationType, + required this.name}) + : super._() { + BuiltValueNullFieldError.checkNotNull( + annotations, r'RepresentationDeclaration', 'annotations'); + BuiltValueNullFieldError.checkNotNull( + docs, r'RepresentationDeclaration', 'docs'); + BuiltValueNullFieldError.checkNotNull(declaredRepresentationType, + r'RepresentationDeclaration', 'declaredRepresentationType'); + BuiltValueNullFieldError.checkNotNull( + name, r'RepresentationDeclaration', 'name'); + } + + @override + RepresentationDeclaration rebuild( + void Function(RepresentationDeclarationBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + _$RepresentationDeclarationBuilder toBuilder() => + new _$RepresentationDeclarationBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is RepresentationDeclaration && + annotations == other.annotations && + docs == other.docs && + declaredRepresentationType == other.declaredRepresentationType && + name == other.name; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, annotations.hashCode); + _$hash = $jc(_$hash, docs.hashCode); + _$hash = $jc(_$hash, declaredRepresentationType.hashCode); + _$hash = $jc(_$hash, name.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'RepresentationDeclaration') + ..add('annotations', annotations) + ..add('docs', docs) + ..add('declaredRepresentationType', declaredRepresentationType) + ..add('name', name)) + .toString(); + } +} + +class _$RepresentationDeclarationBuilder + extends RepresentationDeclarationBuilder { + _$RepresentationDeclaration? _$v; + + @override + ListBuilder get annotations { + _$this; + return super.annotations; + } + + @override + set annotations(ListBuilder annotations) { + _$this; + super.annotations = annotations; + } + + @override + ListBuilder get docs { + _$this; + return super.docs; + } + + @override + set docs(ListBuilder docs) { + _$this; + super.docs = docs; + } + + @override + Reference? get declaredRepresentationType { + _$this; + return super.declaredRepresentationType; + } + + @override + set declaredRepresentationType(Reference? declaredRepresentationType) { + _$this; + super.declaredRepresentationType = declaredRepresentationType; + } + + @override + String? get name { + _$this; + return super.name; + } + + @override + set name(String? name) { + _$this; + super.name = name; + } + + _$RepresentationDeclarationBuilder() : super._(); + + RepresentationDeclarationBuilder get _$this { + final $v = _$v; + if ($v != null) { + super.annotations = $v.annotations.toBuilder(); + super.docs = $v.docs.toBuilder(); + super.declaredRepresentationType = $v.declaredRepresentationType; + super.name = $v.name; + _$v = null; + } + return this; + } + + @override + void replace(RepresentationDeclaration other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$RepresentationDeclaration; + } + + @override + void update(void Function(RepresentationDeclarationBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + RepresentationDeclaration build() => _build(); + + _$RepresentationDeclaration _build() { + _$RepresentationDeclaration _$result; + try { + _$result = _$v ?? + new _$RepresentationDeclaration._( + annotations: annotations.build(), + docs: docs.build(), + declaredRepresentationType: BuiltValueNullFieldError.checkNotNull( + declaredRepresentationType, + r'RepresentationDeclaration', + 'declaredRepresentationType'), + name: BuiltValueNullFieldError.checkNotNull( + name, r'RepresentationDeclaration', 'name')); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'annotations'; + annotations.build(); + _$failedField = 'docs'; + docs.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + r'RepresentationDeclaration', _$failedField, e.toString()); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/lib/src/visitors.dart b/lib/src/visitors.dart index 0075bd7a..10fe0d81 100644 --- a/lib/src/visitors.dart +++ b/lib/src/visitors.dart @@ -11,6 +11,7 @@ import 'specs/directive.dart'; import 'specs/enum.dart'; import 'specs/expression.dart'; import 'specs/extension.dart'; +import 'specs/extension_type.dart'; import 'specs/field.dart'; import 'specs/library.dart'; import 'specs/method.dart'; @@ -33,6 +34,8 @@ abstract class SpecVisitor { T visitExtension(Extension spec, [T? context]); + T visitExtensionType(ExtensionType spec, [T? context]); + T visitEnum(Enum spec, [T? context]); T visitConstructor(Constructor spec, String clazz, [T? context]); diff --git a/pubspec.yaml b/pubspec.yaml index 6d490c10..7eb9f5b6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,11 @@ dev_dependencies: build_runner: ^2.0.3 built_value_generator: ^8.0.0 dart_flutter_team_lints: ^1.0.0 - dart_style: ^2.0.0 + # TODO(srujzs): Restore this to ^2.0.0 once 2.3.4 is released and format the + # expectations in test/specs/extension_type_test.dart. We don't yet have a + # released version of dart_style that formats extension types. If we do not + # pin this version, the above test would start failing when dart_style + # publishes the new version. + dart_style: 2.3.3 source_gen: ^1.0.0 test: ^1.16.0 diff --git a/test/specs/extension_test.dart b/test/specs/extension_test.dart index 6c34ebea..b45fa658 100644 --- a/test/specs/extension_test.dart +++ b/test/specs/extension_test.dart @@ -134,25 +134,4 @@ void main() { '''), ); }); - - test('should create an extension with a method', () { - expect( - Extension((b) => b - ..name = 'Foo' - ..on = TypeReference((b) => b.symbol = 'Bar') - ..methods.add(Method((b) => b - ..name = 'parseInt' - ..returns = refer('int') - ..body = Code.scope( - (a) => 'return int.parse(this);', - )))), - equalsDart(r''' - extension Foo on Bar { - int parseInt() { - return int.parse(this); - } - } - '''), - ); - }); } diff --git a/test/specs/extension_type_test.dart b/test/specs/extension_type_test.dart new file mode 100644 index 00000000..df4e1ecd --- /dev/null +++ b/test/specs/extension_type_test.dart @@ -0,0 +1,242 @@ +// Copyright (c) 2023, 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 'package:code_builder/code_builder.dart'; +import 'package:code_builder/src/specs/extension_type.dart'; +import 'package:test/test.dart'; + +import '../common.dart'; + +void main() { + useDartfmt(); + + test('minimum extension type', () { + expect( + ExtensionType((b) => b + ..name = 'Foo' + ..representationDeclaration = RepresentationDeclaration((b) => b + ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') + ..name = 'bar')), + equalsDart(r''' + extension type Foo(int bar) { } + '''), + ); + }); + + test('const extension type', () { + expect( + ExtensionType((b) => b + ..name = 'Foo' + ..constant = true + ..representationDeclaration = RepresentationDeclaration((b) => b + ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') + ..name = 'bar')), + equalsDart(r''' + extension type const Foo(int bar) { } + '''), + ); + }); + + test('extension type with metadata', () { + expect( + ExtensionType((b) => b + ..name = 'Foo' + ..representationDeclaration = RepresentationDeclaration((b) => b + ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') + ..name = 'bar') + ..docs.add( + '/// My favorite extension type.', + ) + ..annotations.addAll([ + refer('deprecated'), + refer('Deprecated') + .call([literalString('This is an old extension type')]) + ])), + equalsDart(r''' + /// My favorite extension type. + @deprecated + @Deprecated('This is an old extension type') + extension type Foo(int bar) { } + '''), + ); + }); + + test('extension type with generics', () { + expect( + ExtensionType((b) => b + ..name = 'Foo' + ..types.addAll([ + TypeReference((b) => b..symbol = 'T'), + TypeReference((b) => b..symbol = 'U') + ]) + ..representationDeclaration = RepresentationDeclaration((b) => b + ..declaredRepresentationType = TypeReference((b) => b.symbol = 'T') + ..name = 'bar')), + equalsDart(r''' + extension type Foo(T bar) { } + '''), + ); + }); + + test('extension type with generics bound', () { + expect( + ExtensionType((b) => b + ..name = 'Foo' + ..types.add(TypeReference((b) => b + ..symbol = 'T' + ..bound = TypeReference((b) => b..symbol = 'num'))) + ..representationDeclaration = RepresentationDeclaration((b) => b + ..declaredRepresentationType = TypeReference((b) => b.symbol = 'T') + ..name = 'bar')), + equalsDart(r''' + extension type Foo(T bar) { } + '''), + ); + }); + + test('extension type with named primary constructor', () { + expect( + ExtensionType((b) => b + ..name = 'Foo' + ..primaryConstructorName = 'named' + ..representationDeclaration = RepresentationDeclaration((b) => b + ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') + ..name = 'bar')), + equalsDart(r''' + extension type Foo.named(int bar) { } + '''), + ); + }); + + test('extension type with metadata on field', () { + expect( + ExtensionType((b) => b + ..name = 'Foo' + ..representationDeclaration = RepresentationDeclaration((b) => b + ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') + ..name = 'bar' + ..docs.add( + '/// My favorite representation declaration.', + ) + ..annotations.addAll([ + refer('deprecated'), + refer('Deprecated').call( + [literalString('This is an old representation declaration')]) + ]))), + equalsDart(r''' + extension type Foo(/// My favorite representation declaration. + @deprecated + @Deprecated('This is an old representation declaration') + int bar) { } + '''), + ); + }); + + test('extension type with implements', () { + expect( + ExtensionType((b) => b + ..name = 'Foo' + ..implements.add(TypeReference((b) => b.symbol = 'num')) + ..representationDeclaration = RepresentationDeclaration((b) => b + ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') + ..name = 'bar')), + equalsDart(r''' + extension type Foo(int bar) implements num { } + '''), + ); + }); + + test('extension type with multiple implements', () { + expect( + ExtensionType((b) => b + ..name = 'Foo' + ..implements.addAll([ + TypeReference((b) => b.symbol = 'num'), + TypeReference((b) => b.symbol = 'Object') + ]) + ..representationDeclaration = RepresentationDeclaration((b) => b + ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') + ..name = 'bar')), + equalsDart(r''' + extension type Foo(int bar) implements num,Object { } + '''), + ); + }); + + test('extension type with constructors', () { + expect( + ExtensionType((b) => b + ..name = 'Foo' + ..primaryConstructorName = '_' + ..representationDeclaration = RepresentationDeclaration((b) => b + ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') + ..name = 'bar') + ..constructors.addAll([ + Constructor((b) => b.requiredParameters.add(Parameter((b) => b + ..toThis = true + ..name = 'bar'))), + Constructor((b) => b + ..name = 'named' + ..factory = true + ..requiredParameters.add(Parameter((b) => b + ..type = TypeReference((b) => b.symbol = 'int') + ..name = 'baz')) + ..body = const Code('return Foo(baz);')) + ])), + equalsDart(r''' + extension type Foo._(int bar) { + Foo(this.bar); + factory Foo.named(int baz) { return Foo(baz); } + } + '''), + ); + }); + + test('extension type with external field', () { + expect( + ExtensionType((b) => b + ..name = 'Foo' + ..representationDeclaration = RepresentationDeclaration((b) => b + ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') + ..name = 'bar') + ..fields.add(Field((b) => b + ..external = true + ..type = TypeReference((b) => b.symbol = 'int') + ..name = 'property'))), + equalsDart(r''' + extension type Foo(int bar) { + external int property; + } + '''), + ); + }); + + test('extension type with methods', () { + expect( + ExtensionType((b) => b + ..name = 'Foo' + ..representationDeclaration = RepresentationDeclaration((b) => b + ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') + ..name = 'bar') + ..methods.addAll([ + Method((b) => b + ..type = MethodType.getter + ..returns = TypeReference((b) => b.symbol = 'int') + ..name = 'value' + ..body = const Code('return this.bar;')), + Method((b) => b + ..returns = TypeReference((b) => b.symbol = 'int') + ..name = 'getValue' + ..lambda = true + ..body = const Code('this.bar')) + ])), + equalsDart(r''' + extension type Foo(int bar) { + int get value { return this.bar; } + int getValue() => this.bar; + } + '''), + ); + }); +} From a3ace8b1017006426a9a04f7c0cc4bc60852b130 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 20 Nov 2023 11:11:51 -0800 Subject: [PATCH 2/5] Update SDK and dart_style constraints --- pubspec.yaml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 7eb9f5b6..6f961980 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ description: >- repository: https://github.com/dart-lang/code_builder environment: - sdk: '>=2.19.0 <3.0.0' + sdk: '>=3.0.0 <4.0.0' dependencies: built_collection: ^5.0.0 @@ -19,11 +19,6 @@ dev_dependencies: build_runner: ^2.0.3 built_value_generator: ^8.0.0 dart_flutter_team_lints: ^1.0.0 - # TODO(srujzs): Restore this to ^2.0.0 once 2.3.4 is released and format the - # expectations in test/specs/extension_type_test.dart. We don't yet have a - # released version of dart_style that formats extension types. If we do not - # pin this version, the above test would start failing when dart_style - # publishes the new version. - dart_style: 2.3.3 + dart_style: ^2.3.3 source_gen: ^1.0.0 test: ^1.16.0 From 0484efd961c7bc0905ba642b71c9424518c1ca2d Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 20 Nov 2023 11:16:54 -0800 Subject: [PATCH 3/5] Add mixin keyword to support Dart 3.0 --- lib/src/mixins/annotations.dart | 4 ++-- lib/src/mixins/dartdoc.dart | 4 ++-- lib/src/mixins/generics.dart | 4 ++-- lib/src/specs/code.dart | 2 +- lib/src/specs/expression.dart | 3 ++- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/src/mixins/annotations.dart b/lib/src/mixins/annotations.dart index b4b3be28..7ce6a20b 100644 --- a/lib/src/mixins/annotations.dart +++ b/lib/src/mixins/annotations.dart @@ -7,13 +7,13 @@ import 'package:built_collection/built_collection.dart'; import '../specs/expression.dart'; /// A type of AST node that can have metadata [annotations]. -abstract class HasAnnotations { +abstract mixin class HasAnnotations { /// Annotations as metadata on the node. BuiltList get annotations; } /// Compliment to the [HasAnnotations] mixin for metadata [annotations]. -abstract class HasAnnotationsBuilder { +abstract mixin class HasAnnotationsBuilder { /// Annotations as metadata on the node. abstract ListBuilder annotations; } diff --git a/lib/src/mixins/dartdoc.dart b/lib/src/mixins/dartdoc.dart index 52d69c04..78144f6f 100644 --- a/lib/src/mixins/dartdoc.dart +++ b/lib/src/mixins/dartdoc.dart @@ -4,12 +4,12 @@ import 'package:built_collection/built_collection.dart'; -abstract class HasDartDocs { +abstract mixin class HasDartDocs { /// Dart docs. BuiltList get docs; } -abstract class HasDartDocsBuilder { +abstract mixin class HasDartDocsBuilder { /// Dart docs. abstract ListBuilder docs; } diff --git a/lib/src/mixins/generics.dart b/lib/src/mixins/generics.dart index 51aae63f..b18a858c 100644 --- a/lib/src/mixins/generics.dart +++ b/lib/src/mixins/generics.dart @@ -6,12 +6,12 @@ import 'package:built_collection/built_collection.dart'; import '../specs/reference.dart'; -abstract class HasGenerics { +abstract mixin class HasGenerics { /// Generic type parameters. BuiltList get types; } -abstract class HasGenericsBuilder { +abstract mixin class HasGenericsBuilder { /// Generic type parameters. abstract ListBuilder types; } diff --git a/lib/src/specs/code.dart b/lib/src/specs/code.dart index 5cc52f5e..521eb1fe 100644 --- a/lib/src/specs/code.dart +++ b/lib/src/specs/code.dart @@ -87,7 +87,7 @@ abstract class CodeVisitor implements SpecVisitor { } /// Knowledge of how to write valid Dart code from [CodeVisitor]. -abstract class CodeEmitter implements CodeVisitor { +abstract mixin class CodeEmitter implements CodeVisitor { @protected Allocator get allocator; diff --git a/lib/src/specs/expression.dart b/lib/src/specs/expression.dart index 09336274..b9193e6f 100644 --- a/lib/src/specs/expression.dart +++ b/lib/src/specs/expression.dart @@ -526,7 +526,8 @@ abstract class ExpressionVisitor implements SpecVisitor { /// Knowledge of how to write valid Dart code from [ExpressionVisitor]. /// /// **INTERNAL ONLY**. -abstract class ExpressionEmitter implements ExpressionVisitor { +abstract mixin class ExpressionEmitter + implements ExpressionVisitor { @override StringSink visitToCodeExpression(ToCodeExpression expression, [StringSink? output]) { From fb790e4ccc46442fe735103302525e15d0fa73d3 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 20 Nov 2023 11:24:11 -0800 Subject: [PATCH 4/5] Update CI to use Dart 3.0.0 --- .github/workflows/test-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index a2666f72..5a327cf2 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -43,7 +43,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - sdk: [2.19.0, dev] + sdk: [3.0.0, dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d From a43e59964b708a945cff3860f4528463af533bc0 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 20 Nov 2023 11:36:56 -0800 Subject: [PATCH 5/5] Comment out use of dart format and prep for publish of 4.8.0 --- CHANGELOG.md | 3 ++- pubspec.yaml | 2 +- test/specs/extension_type_test.dart | 9 +++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2777beb4..c216a928 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.8.0-wip +## 4.8.0 * Add `Expression.operatorSubtract` * Deprecate `Expression.operatorSubstract` @@ -29,6 +29,7 @@ * Add `Expression.bitwiseOrAssign` * Allow passing an `Expression` through `literal` without an exception. * Add support for extension types. +* Update SDK version constraints to `>=3.0.0`. ## 4.7.0 diff --git a/pubspec.yaml b/pubspec.yaml index 6f961980..0d26965f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: code_builder -version: 4.8.0-wip +version: 4.8.0 description: >- A fluent, builder-based library for generating valid Dart code repository: https://github.com/dart-lang/code_builder diff --git a/test/specs/extension_type_test.dart b/test/specs/extension_type_test.dart index df4e1ecd..fbe3c2ea 100644 --- a/test/specs/extension_type_test.dart +++ b/test/specs/extension_type_test.dart @@ -6,10 +6,15 @@ import 'package:code_builder/code_builder.dart'; import 'package:code_builder/src/specs/extension_type.dart'; import 'package:test/test.dart'; -import '../common.dart'; +// import '../common.dart'; void main() { - useDartfmt(); + // TODO(srujzs): Uncomment this and the import once we have dart_style 2.3.4. + // When 2.3.4 is published, extension type formatting will be enabled and + // these expectations will start failing. To avoid that, we don't use dart + // format temporarily. Once 2.3.4 is published, we can update the expectations + // in this file and re-enable dart format. + // useDartfmt(); test('minimum extension type', () { expect(