blob: 5346328530a333ae4ccb54977fb62eb957b680a5 [file] [log] [blame]
// 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 'dart:async';
import 'package:_fe_analyzer_shared/src/macros/api.dart';
/*macro*/ class Introspect
implements
ClassDeclarationsMacro,
ConstructorDeclarationsMacro,
EnumDeclarationsMacro,
EnumValueDeclarationsMacro,
ExtensionDeclarationsMacro,
ExtensionTypeDeclarationsMacro,
FieldDeclarationsMacro,
FunctionDeclarationsMacro,
LibraryDeclarationsMacro,
MethodDeclarationsMacro,
MixinDeclarationsMacro,
VariableDeclarationsMacro {
final Set<Object?> withDetailsFor;
final bool withMetadata;
final bool withUnnamedConstructor;
const Introspect({
this.withDetailsFor = const {},
this.withMetadata = false,
this.withUnnamedConstructor = false,
});
@override
Future<void> buildDeclarationsForClass(
ClassDeclaration declaration,
MemberDeclarationBuilder builder,
) async {
await _write(builder, declaration, (printer) async {
await printer.writeClassDeclaration(declaration);
});
}
@override
Future<void> buildDeclarationsForConstructor(
ConstructorDeclaration declaration,
MemberDeclarationBuilder builder,
) async {
await _write(builder, declaration, (printer) async {
await printer.writeConstructorDeclaration(declaration);
});
}
@override
Future<void> buildDeclarationsForEnum(
EnumDeclaration declaration,
EnumDeclarationBuilder builder,
) async {
await _write(builder, declaration, (printer) async {
await printer.writeEnumDeclaration(declaration);
});
}
@override
Future<void> buildDeclarationsForEnumValue(
EnumValueDeclaration declaration,
EnumDeclarationBuilder builder,
) async {
await _write(builder, declaration, (printer) async {
await printer.writeEnumValueDeclaration(declaration);
});
}
@override
Future<void> buildDeclarationsForExtension(
ExtensionDeclaration declaration,
MemberDeclarationBuilder builder,
) async {
await _write(builder, declaration, (printer) async {
await printer.writeExtensionDeclaration(declaration);
});
}
@override
Future<void> buildDeclarationsForExtensionType(
ExtensionTypeDeclaration declaration,
MemberDeclarationBuilder builder,
) async {
await _write(builder, declaration, (printer) async {
await printer.writeExtensionTypeDeclaration(declaration);
});
}
@override
Future<void> buildDeclarationsForField(
FieldDeclaration declaration,
MemberDeclarationBuilder builder,
) async {
await _write(builder, declaration, (printer) async {
await printer.writeField(declaration);
});
}
@override
Future<void> buildDeclarationsForFunction(
FunctionDeclaration declaration,
DeclarationBuilder builder,
) async {
await _write(builder, declaration, (printer) async {
await printer.writeFunctionDeclaration(declaration);
});
}
@override
Future<void> buildDeclarationsForLibrary(
Library library,
DeclarationBuilder builder,
) async {
final buffer = StringBuffer();
final sink = TreeStringSink(
sink: buffer,
indent: '',
);
final types = await builder.typesOf(library);
final includedDeclarations = <Declaration>{};
final printer = _Printer(
sink: sink,
withMetadata: withMetadata,
withUnnamedConstructor: withUnnamedConstructor,
introspector: builder,
shouldWriteDetailsFor: (declaration) {
return includedDeclarations.remove(declaration);
},
);
for (final type in types) {
includedDeclarations.add(type);
await printer.writeAnyDeclaration(type);
}
final text = buffer.toString();
_declareIntrospectResult(builder, text);
}
@override
Future<void> buildDeclarationsForMethod(
MethodDeclaration declaration,
MemberDeclarationBuilder builder,
) async {
await _write(builder, declaration, (printer) async {
await printer.writeMethodDeclaration(declaration);
});
}
@override
Future<void> buildDeclarationsForMixin(
MixinDeclaration declaration,
MemberDeclarationBuilder builder,
) async {
await _write(builder, declaration, (printer) async {
await printer.writeMixinDeclaration(declaration);
});
}
@override
Future<void> buildDeclarationsForVariable(
VariableDeclaration declaration,
DeclarationBuilder builder,
) async {
await _write(builder, declaration, (printer) async {
await printer.writeVariable(declaration);
});
}
void _declareIntrospectResult(DeclarationBuilder builder, String text) {
builder.declareInLibrary(
DeclarationCode.fromString(
'const _introspect = r"""$text""";',
),
);
}
Future<void> _write(
DeclarationBuilder builder,
Declaration declaration,
Future<void> Function(_Printer printer) withPrinter,
) async {
final buffer = StringBuffer();
final sink = TreeStringSink(
sink: buffer,
indent: '',
);
final includedNames = {
declaration.identifier.name,
...withDetailsFor,
};
final printer = _Printer(
sink: sink,
withMetadata: withMetadata,
withUnnamedConstructor: withUnnamedConstructor,
introspector: builder,
shouldWriteDetailsFor: (declaration) {
final nameToCheck = declaration.identifier.name;
return includedNames.remove(nameToCheck);
},
);
await withPrinter(printer);
final text = buffer.toString();
_declareIntrospectResult(builder, text);
}
}
/*macro*/ class IntrospectDeclaration implements FunctionDefinitionMacro {
final String uriStr;
final String name;
final bool withUnnamedConstructor;
IntrospectDeclaration({
required this.uriStr,
required this.name,
this.withUnnamedConstructor = false,
});
@override
Future<void> buildDefinitionForFunction(
FunctionDeclaration declaration,
FunctionDefinitionBuilder builder,
) async {
final buffer = StringBuffer();
final sink = TreeStringSink(
sink: buffer,
indent: '',
);
final includedNames = {name};
final printer = _Printer(
sink: sink,
withMetadata: true,
withUnnamedConstructor: withUnnamedConstructor,
introspector: builder,
shouldWriteDetailsFor: (declaration) {
final name = declaration.identifier.name;
return includedNames.remove(name);
},
);
// ignore: deprecated_member_use
final identifier = await builder.resolveIdentifier(
Uri.parse(uriStr),
name,
);
final declaration = await builder.declarationOf(identifier);
await printer.writeAnyDeclaration(declaration);
final text = buffer.toString();
builder.augment(
FunctionBodyCode.fromString('=> r"""$text""";'),
);
}
}
/// We use [nameToFind] only because we cannot get [Library] by URI.
/*macro*/ class LibraryTopLevelDeclarations implements FunctionDefinitionMacro {
final String uriStr;
final String nameToFind;
LibraryTopLevelDeclarations({
required this.uriStr,
required this.nameToFind,
});
@override
Future<void> buildDefinitionForFunction(
FunctionDeclaration declaration,
FunctionDefinitionBuilder builder,
) async {
final buffer = StringBuffer();
final sink = TreeStringSink(
sink: buffer,
indent: '',
);
final includedNames = <String>{};
final printer = _Printer(
sink: sink,
withMetadata: true,
withUnnamedConstructor: false,
introspector: builder,
shouldWriteDetailsFor: (declaration) {
final name = declaration.identifier.name;
return includedNames.remove(name);
},
);
// ignore: deprecated_member_use
final identifier = await builder.resolveIdentifier(
Uri.parse(uriStr),
nameToFind,
);
final declaration = await builder.declarationOf(identifier);
final library = declaration.library;
sink.writelnWithIndent('topLevelDeclarationsOf');
await sink.withIndent(() async {
final topDeclarations = await builder.topLevelDeclarationsOf(library);
for (final declaration in topDeclarations) {
final name = declaration.identifier.name;
if (name != '_starter') {
includedNames.add(name);
await printer.writeAnyDeclaration(declaration);
}
}
});
final text = buffer.toString();
builder.augment(
FunctionBodyCode.fromString('=> r"""$text""";'),
);
}
}
/// Wrapper around a [StringSink] for writing tree structures.
class TreeStringSink {
final StringSink _sink;
String _indent = '';
TreeStringSink({
required StringSink sink,
required String indent,
}) : _sink = sink,
_indent = indent;
Future<void> withIndent(Future<void> Function() f) async {
final indent = _indent;
_indent = '$indent ';
await f();
_indent = indent;
}
void write(Object object) {
_sink.write(object);
}
Future<void> writeElements<T extends Object>(
String name,
Iterable<T> elements,
Future<void> Function(T) f,
) async {
if (elements.isNotEmpty) {
writelnWithIndent(name);
await withIndent(() async {
for (final element in elements) {
await f(element);
}
});
}
}
Future<void> writeFlags(Map<String, bool> flags) async {
if (flags.values.any((flag) => flag)) {
await writeIndentedLine(() async {
write('flags:');
for (final entry in flags.entries) {
if (entry.value) {
write(' ${entry.key}');
}
}
});
}
}
void writeIf(bool flag, Object object) {
if (flag) {
write(object);
}
}
void writeIndent() {
_sink.write(_indent);
}
Future<void> writeIndentedLine(void Function() f) async {
writeIndent();
f();
writeln();
}
void writeln([Object? object = '']) {
_sink.writeln(object);
}
void writelnWithIndent(Object object) {
_sink.write(_indent);
_sink.writeln(object);
}
void writeWithIndent(Object object) {
_sink.write(_indent);
_sink.write(object);
}
}
class _Printer {
final TreeStringSink sink;
final bool withMetadata;
final bool withUnnamedConstructor;
final DeclarationPhaseIntrospector introspector;
final bool Function(Declaration declaration) shouldWriteDetailsFor;
Identifier? _enclosingDeclarationIdentifier;
_Printer({
required this.sink,
required this.withMetadata,
required this.withUnnamedConstructor,
required this.introspector,
required this.shouldWriteDetailsFor,
});
Future<void> writeAnyDeclaration(Declaration declaration) async {
switch (declaration) {
case ClassDeclaration():
await writeClassDeclaration(declaration);
case EnumDeclaration():
await writeEnumDeclaration(declaration);
case ExtensionDeclaration():
await writeExtensionDeclaration(declaration);
case ExtensionTypeDeclaration():
await writeExtensionTypeDeclaration(declaration);
case FunctionDeclaration():
await writeFunctionDeclaration(declaration);
case MixinDeclaration():
await writeMixinDeclaration(declaration);
case VariableDeclaration():
await writeVariable(declaration);
default:
throw UnimplementedError('${declaration.runtimeType}');
}
}
Future<void> writeClassDeclaration(ClassDeclaration e) async {
if (!shouldWriteDetailsFor(e)) {
return;
}
sink.writelnWithIndent('class ${e.identifier.name}');
await sink.withIndent(() async {
await sink.writeFlags({
'hasAbstract': e.hasAbstract,
'hasBase': e.hasBase,
'hasExternal': e.hasExternal,
'hasFinal': e.hasFinal,
'hasInterface': e.hasInterface,
'hasMixin': e.hasMixin,
'hasSealed': e.hasSealed,
});
await _writeMetadata(e);
if (e.superclass case final superclass?) {
await _writeNamedTypeAnnotation('superclass', superclass);
}
await _writeTypeParameters(e.typeParameters);
await _writeTypeAnnotations('mixins', e.mixins);
await _writeTypeAnnotations('interfaces', e.interfaces);
await _writeTypeDeclarationMembers(e);
});
}
Future<void> writeConstructorDeclaration(ConstructorDeclaration e) async {
_assertEnclosingClass(e);
sink.writelnWithIndent(
e.identifier.name.ifNotEmptyOrElse('<unnamed>'),
);
await sink.withIndent(() async {
await sink.writeFlags({
'hasBody': e.hasBody,
'hasExternal': e.hasExternal,
'isFactory': e.isFactory,
'isGetter': e.isGetter,
'isOperator': e.isOperator,
'isSetter': e.isSetter,
'isStatic': e.isStatic,
});
await _writeMetadata(e);
await _writeNamedFormalParameters(e.namedParameters);
await _writePositionalFormalParameters(e.positionalParameters);
await _writeNamedTypeAnnotation('returnType', e.returnType);
await _writeTypeParameters(e.typeParameters);
});
}
Future<void> writeEnumDeclaration(EnumDeclaration e) async {
if (!shouldWriteDetailsFor(e)) {
return;
}
sink.writelnWithIndent('enum ${e.identifier.name}');
await sink.withIndent(() async {
await _writeMetadata(e);
await _writeTypeParameters(e.typeParameters);
await _writeTypeAnnotations('mixins', e.mixins);
await _writeTypeAnnotations('interfaces', e.interfaces);
await sink.writeElements(
'values',
await introspector.valuesOf(e),
writeEnumValueDeclaration,
);
await _writeTypeDeclarationMembers(e);
});
}
Future<void> writeEnumValueDeclaration(EnumValueDeclaration e) async {
final enclosing = _enclosingDeclarationIdentifier;
if (enclosing != null && e.definingEnum != enclosing) {
throw StateError('Mismatch: definingEnum');
}
sink.writelnWithIndent(e.identifier.name);
await sink.withIndent(() async {
await _writeMetadata(e);
// TODO(scheglov): Write, when added.
// await _writeNamedTypeAnnotation('type', e.type);
});
}
Future<void> writeExtensionDeclaration(ExtensionDeclaration e) async {
if (!shouldWriteDetailsFor(e)) {
return;
}
sink.writelnWithIndent('extension ${e.identifier.name}');
await sink.withIndent(() async {
await _writeMetadata(e);
await _writeTypeParameters(e.typeParameters);
await _writeNamedTypeAnnotation('onType', e.onType);
await _writeTypeDeclarationMembers(e);
});
}
Future<void> writeExtensionTypeDeclaration(ExtensionTypeDeclaration e) async {
if (!shouldWriteDetailsFor(e)) {
return;
}
sink.writelnWithIndent('extension type ${e.identifier.name}');
await sink.withIndent(() async {
await _writeMetadata(e);
await _writeTypeParameters(e.typeParameters);
await _writeNamedTypeAnnotation(
'representationType',
e.representationType,
);
await _writeTypeDeclarationMembers(e);
});
}
Future<void> writeField(FieldDeclaration e) async {
_assertEnclosingClass(e);
sink.writelnWithIndent(e.identifier.name);
await sink.withIndent(() async {
await sink.writeFlags({
'hasAbstract': e.hasAbstract,
'hasExternal': e.hasExternal,
'hasFinal': e.hasFinal,
'hasLate': e.hasLate,
'isStatic': e.isStatic,
});
await _writeMetadata(e);
await _writeNamedTypeAnnotation('type', e.type);
});
}
Future<void> writeFunctionDeclaration(FunctionDeclaration e) async {
sink.writelnWithIndent(e.identifier.name);
await sink.withIndent(() async {
await sink.writeFlags({
'hasBody': e.hasBody,
'hasExternal': e.hasExternal,
'isGetter': e.isGetter,
'isOperator': e.isOperator,
'isSetter': e.isSetter,
});
await _writeMetadata(e);
await _writeNamedFormalParameters(e.namedParameters);
await _writePositionalFormalParameters(e.positionalParameters);
await _writeNamedTypeAnnotation('returnType', e.returnType);
await _writeTypeParameters(e.typeParameters);
});
}
Future<void> writeMethodDeclaration(MethodDeclaration e) async {
_assertEnclosingClass(e);
sink.writelnWithIndent(e.identifier.name);
await sink.withIndent(() async {
await sink.writeFlags({
'hasBody': e.hasBody,
'hasExternal': e.hasExternal,
'isGetter': e.isGetter,
'isOperator': e.isOperator,
'isSetter': e.isSetter,
'isStatic': e.isStatic,
});
await _writeMetadata(e);
await _writeNamedFormalParameters(e.namedParameters);
await _writePositionalFormalParameters(e.positionalParameters);
await _writeNamedTypeAnnotation('returnType', e.returnType);
await _writeTypeParameters(e.typeParameters);
});
}
Future<void> writeMixinDeclaration(MixinDeclaration e) async {
if (!shouldWriteDetailsFor(e)) {
return;
}
sink.writelnWithIndent('mixin ${e.identifier.name}');
await sink.withIndent(() async {
await sink.writeFlags({
'hasBase': e.hasBase,
});
await _writeMetadata(e);
await _writeTypeParameters(e.typeParameters);
await _writeTypeAnnotations(
'superclassConstraints',
e.superclassConstraints,
);
await _writeTypeAnnotations('interfaces', e.interfaces);
await _writeTypeDeclarationMembers(e);
});
}
Future<void> writeVariable(VariableDeclaration e) async {
sink.writelnWithIndent(e.identifier.name);
await sink.withIndent(() async {
await sink.writeFlags({
'hasExternal': e.hasExternal,
'hasFinal': e.hasFinal,
'hasLate': e.hasLate,
});
await _writeMetadata(e);
await _writeNamedTypeAnnotation('type', e.type);
});
}
void _assertEnclosingClass(MemberDeclaration e) {
final enclosing = _enclosingDeclarationIdentifier;
if (enclosing != null && e.definingType != enclosing) {
throw StateError('Mismatch: definingClass');
}
}
bool _shouldWriteArguments(ConstructorMetadataAnnotation annotation) {
return !const {
'Introspect',
}.contains(annotation.type.name);
}
Future<void> _writeExpressionCode(
ExpressionCode code, {
String? name,
}) async {
await sink.writeIndentedLine(() async {
if (name != null) {
sink.write('$name: ');
}
sink.write('${code.parts}');
});
}
Future<void> _writeFormalParameterDeclaration(
FormalParameterDeclaration e) async {
sink.writelnWithIndent(e.identifier.name);
await sink.withIndent(() async {
await sink.writeFlags({
'isNamed': e.isNamed,
'isRequired': e.isRequired,
});
await _writeMetadata(e);
await _writeNamedTypeAnnotation('type', e.type);
});
}
/// If [type] is [OmittedTypeAnnotation], write the inferred type.
Future<void> _writeInferredTypeAnnotation(TypeAnnotation type) async {
if (type is OmittedTypeAnnotation) {
if (introspector case final DefinitionPhaseIntrospector introspector) {
final inferred = await introspector.inferType(type);
await sink.withIndent(() async {
sink.writelnWithIndent('inferred: ${inferred.asString}');
});
}
}
}
Future<void> _writeMetadata(Annotatable e) async {
if (withMetadata) {
await sink.writeElements(
'metadata',
e.metadata,
_writeMetadataAnnotation,
);
}
}
Future<void> _writeMetadataAnnotation(MetadataAnnotation e) async {
switch (e) {
case ConstructorMetadataAnnotation():
sink.writelnWithIndent('ConstructorMetadataAnnotation');
await sink.withIndent(() async {
sink.writelnWithIndent('type: ${e.type.name}');
final constructorName = e.constructor.name;
if (constructorName.isNotEmpty) {
sink.writelnWithIndent('constructorName: $constructorName');
}
if (_shouldWriteArguments(e)) {
await sink.writeElements(
'positionalArguments',
e.positionalArguments,
(argument) async {
await _writeExpressionCode(argument);
},
);
await sink.writeElements(
'namedArguments',
e.namedArguments.entries,
(entry) async {
await _writeExpressionCode(name: entry.key, entry.value);
},
);
}
});
case IdentifierMetadataAnnotation():
sink.writelnWithIndent('IdentifierMetadataAnnotation');
await sink.withIndent(() async {
sink.writelnWithIndent('identifier: ${e.identifier.name}');
});
default:
}
}
Future<void> _writeNamedFormalParameters(
Iterable<FormalParameterDeclaration> elements,
) async {
await sink.writeElements(
'namedParameters',
elements,
_writeFormalParameterDeclaration,
);
}
Future<void> _writeNamedTypeAnnotation(
String name,
TypeAnnotation? type,
) async {
sink.writeWithIndent('$name: ');
await _writeTypeAnnotation(type);
}
Future<void> _writePositionalFormalParameters(
Iterable<FormalParameterDeclaration> elements,
) async {
await sink.writeElements(
'positionalParameters',
elements,
_writeFormalParameterDeclaration,
);
}
Future<void> _writeTypeAnnotation(TypeAnnotation? type) async {
if (type != null) {
sink.writeln(type.asString);
await _writeInferredTypeAnnotation(type);
await _writeTypeAnnotationDeclaration(type);
} else {
sink.writeln('null');
}
}
Future<void> _writeTypeAnnotationDeclaration(TypeAnnotation type) async {
await sink.withIndent(() async {
switch (type) {
case FunctionTypeAnnotation():
// No declaration.
break;
case NamedTypeAnnotation():
final identifier = type.identifier;
if (identifier.name == 'void') {
return;
}
TypeDeclaration declaration;
try {
declaration = await introspector.typeDeclarationOf(identifier);
} on MacroImplementationException {
sink.writelnWithIndent('noDeclaration');
return;
}
switch (declaration) {
case ClassDeclaration():
await writeClassDeclaration(declaration);
case EnumDeclaration():
await writeEnumDeclaration(declaration);
case MixinDeclaration():
await writeMixinDeclaration(declaration);
default:
throw UnimplementedError('${declaration.runtimeType}');
}
case OmittedTypeAnnotation():
// No declaration, yet.
break;
default:
throw UnimplementedError('(${type.runtimeType}) $type');
}
});
}
Future<void> _writeTypeAnnotations(
String name,
Iterable<TypeAnnotation> types,
) async {
await sink.writeElements(name, types, (type) async {
sink.writeIndent();
await _writeTypeAnnotation(type);
});
}
Future<void> _writeTypeDeclarationMembers(TypeDeclaration e) async {
_enclosingDeclarationIdentifier = e.identifier;
final constructors = await introspector.constructorsOf(e);
await sink.writeElements(
'constructors',
constructors.where((element) {
return element.identifier.name.isNotEmpty || withUnnamedConstructor;
}),
writeConstructorDeclaration,
);
await sink.writeElements(
'fields',
await introspector.fieldsOf(e),
writeField,
);
await sink.writeElements(
'methods',
await introspector.methodsOf(e),
writeMethodDeclaration,
);
_enclosingDeclarationIdentifier = null;
}
Future<void> _writeTypeParameter(TypeParameterDeclaration e) async {
sink.writelnWithIndent(e.identifier.name);
await sink.withIndent(() async {
await _writeMetadata(e);
if (e.bound case final bound?) {
await _writeNamedTypeAnnotation('bound', bound);
}
});
}
Future<void> _writeTypeParameters(
Iterable<TypeParameterDeclaration> elements,
) async {
await sink.writeElements('typeParameters', elements, _writeTypeParameter);
}
}
class _TypeAnnotationStringBuilder {
final StringSink _sink;
_TypeAnnotationStringBuilder(this._sink);
void write(TypeAnnotation type) {
if (type is FunctionTypeAnnotation) {
_writeFunctionTypeAnnotation(type);
} else if (type is NamedTypeAnnotation) {
_writeNamedTypeAnnotation(type);
} else if (type is OmittedTypeAnnotation) {
_sink.write('OmittedType');
} else {
throw UnimplementedError('(${type.runtimeType}) $type');
}
if (type.isNullable) {
_sink.write('?');
}
}
void _writeFormalParameter(FormalParameter node) {
final String closeSeparator;
if (node.isNamed) {
_sink.write('{');
closeSeparator = '}';
if (node.isRequired) {
_sink.write('required ');
}
} else if (!node.isRequired) {
_sink.write('[');
closeSeparator = ']';
} else {
closeSeparator = '';
}
write(node.type);
if (node.name != null) {
_sink.write(' ');
_sink.write(node.name);
}
_sink.write(closeSeparator);
}
void _writeFunctionTypeAnnotation(FunctionTypeAnnotation type) {
write(type.returnType);
_sink.write(' Function');
_sink.writeList(
elements: type.typeParameters,
write: _writeTypeParameter,
separator: ', ',
open: '<',
close: '>',
);
_sink.write('(');
var hasFormalParameter = false;
for (final formalParameter in type.positionalParameters) {
if (hasFormalParameter) {
_sink.write(', ');
}
_writeFormalParameter(formalParameter);
hasFormalParameter = true;
}
for (final formalParameter in type.namedParameters) {
if (hasFormalParameter) {
_sink.write(', ');
}
_writeFormalParameter(formalParameter);
hasFormalParameter = true;
}
_sink.write(')');
}
void _writeNamedTypeAnnotation(NamedTypeAnnotation type) {
_sink.write(type.identifier.name);
_sink.writeList(
elements: type.typeArguments,
write: write,
separator: ', ',
open: '<',
close: '>',
);
}
void _writeTypeParameter(TypeParameter node) {
_sink.write(node.name);
final bound = node.bound;
if (bound != null) {
_sink.write(' extends ');
write(bound);
}
}
}
extension on StringSink {
void writeList<T>({
required Iterable<T> elements,
required void Function(T element) write,
required String separator,
String? open,
String? close,
}) {
elements = elements.toList();
if (elements.isEmpty) {
return;
}
if (open != null) {
this.write(open);
}
var isFirst = true;
for (var element in elements) {
if (isFirst) {
isFirst = false;
} else {
this.write(separator);
}
write(element);
}
if (close != null) {
this.write(close);
}
}
}
extension E on TypeAnnotation {
String get asString {
final buffer = StringBuffer();
_TypeAnnotationStringBuilder(buffer).write(this);
return buffer.toString();
}
}
extension StringExtension on String {
String ifNotEmptyOrElse(String orElse) {
return isNotEmpty ? this : orElse;
}
}