| // 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 'allocator.dart'; | |
| import 'base.dart'; | |
| import 'specs/class.dart'; | |
| import 'specs/code.dart'; | |
| import 'specs/constructor.dart'; | |
| import 'specs/control.dart'; | |
| 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'; | |
| import 'specs/mixin.dart'; | |
| import 'specs/pattern.dart'; | |
| import 'specs/reference.dart'; | |
| import 'specs/type_function.dart'; | |
| import 'specs/type_record.dart'; | |
| import 'specs/type_reference.dart'; | |
| import 'specs/typedef.dart'; | |
| import 'visitors.dart'; | |
| /// 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; | |
| } | |
| class DartEmitter extends Object | |
| with CodeEmitter, ExpressionEmitter, PatternEmitter, ControlEmitter | |
| implements SpecVisitor<StringSink> { | |
| @override | |
| final Allocator allocator; | |
| /// If directives should be ordered while emitting. | |
| /// | |
| /// Ordering rules follow the guidance in | |
| /// [Effective Dart](https://dart.dev/guides/language/effective-dart/style#ordering) | |
| /// and the | |
| /// [directives_ordering](https://dart-lang.github.io/linter/lints/directives_ordering.html) | |
| /// lint. | |
| final bool orderDirectives; | |
| /// If nullable types should be emitted with the nullable suffix ("?"). | |
| /// | |
| /// Null safety syntax should only be enabled if the output will be used with | |
| /// a Dart language version which supports it. | |
| final bool _useNullSafetySyntax; | |
| /// Creates a new instance of [DartEmitter]. | |
| /// | |
| /// May specify an [Allocator] to use for references and imports, | |
| /// otherwise uses [Allocator.none] which never prefixes references and will | |
| /// not automatically emit import directives. | |
| DartEmitter({ | |
| this.allocator = Allocator.none, | |
| this.orderDirectives = false, | |
| bool useNullSafetySyntax = false, | |
| }) : _useNullSafetySyntax = useNullSafetySyntax; | |
| /// Creates a new instance of [DartEmitter] with simple automatic imports. | |
| factory DartEmitter.scoped({ | |
| bool orderDirectives = false, | |
| bool useNullSafetySyntax = false, | |
| }) => DartEmitter( | |
| allocator: Allocator.simplePrefixing(), | |
| orderDirectives: orderDirectives, | |
| useNullSafetySyntax: useNullSafetySyntax, | |
| ); | |
| static bool _isLambdaBody(Code? code) => | |
| code is ToCodeExpression && !code.isStatement; | |
| /// Whether the provided [method] is considered a lambda method. | |
| static bool _isLambdaMethod(Method method) => | |
| method.lambda ?? _isLambdaBody(method.body); | |
| /// Whether the provided [constructor] is considered a lambda method. | |
| static bool _isLambdaConstructor(Constructor constructor) => | |
| constructor.lambda ?? | |
| constructor.factory && _isLambdaBody(constructor.body); | |
| @override | |
| StringSink visitAnnotation(Expression spec, [StringSink? output]) { | |
| (output ??= StringBuffer()).write('@'); | |
| spec.accept(this, output); | |
| output.write(' '); | |
| return output; | |
| } | |
| @override | |
| StringSink visitClass(Class spec, [StringSink? output]) { | |
| final out = output ??= StringBuffer(); | |
| spec.docs.forEach(out.writeln); | |
| for (var a in spec.annotations) { | |
| visitAnnotation(a, out); | |
| } | |
| void writeModifier() { | |
| if (spec.modifier != null) { | |
| out.write('${spec.modifier!.name} '); | |
| } | |
| } | |
| if (spec.sealed) { | |
| out.write('sealed '); | |
| } else { | |
| if (spec.abstract) { | |
| out.write('abstract '); | |
| } | |
| writeModifier(); | |
| if (spec.mixin) { | |
| out.write('mixin '); | |
| } | |
| } | |
| out.write('class ${spec.name}'); | |
| visitTypeParameters(spec.types.map((r) => r.type), out); | |
| if (spec.extend != null) { | |
| out.write(' extends '); | |
| spec.extend!.type.accept(this, out); | |
| } | |
| if (spec.mixins.isNotEmpty) { | |
| out | |
| ..write(' with ') | |
| ..writeAll( | |
| spec.mixins.map<StringSink>((m) => m.type.accept(this)), | |
| ',', | |
| ); | |
| } | |
| if (spec.implements.isNotEmpty) { | |
| out | |
| ..write(' implements ') | |
| ..writeAll( | |
| spec.implements.map<StringSink>((m) => m.type.accept(this)), | |
| ',', | |
| ); | |
| } | |
| out.write(' {'); | |
| for (var c in spec.constructors) { | |
| visitConstructor(c, spec.name, out); | |
| out.writeln(); | |
| } | |
| for (var f in spec.fields) { | |
| visitField(f, out); | |
| out.writeln(); | |
| } | |
| _visitMethods(spec.methods, out); | |
| out.writeln('}'); | |
| return out; | |
| } | |
| @override | |
| StringSink visitMixin(Mixin spec, [StringSink? output]) { | |
| final out = output ??= StringBuffer(); | |
| spec.docs.forEach(out.writeln); | |
| for (var a in spec.annotations) { | |
| visitAnnotation(a, out); | |
| } | |
| if (spec.base) { | |
| out.write('base '); | |
| } | |
| out.write('mixin ${spec.name}'); | |
| visitTypeParameters(spec.types.map((r) => r.type), out); | |
| if (spec.onTypes.isNotEmpty) { | |
| out.write(' on '); | |
| visitAll<Reference>(spec.onTypes, out, (r) { | |
| r.type.accept(this, out); | |
| }, ','); | |
| } else if (spec.on != null) { | |
| out.write(' on '); | |
| spec.on!.type.accept(this, out); | |
| } | |
| if (spec.implements.isNotEmpty) { | |
| out | |
| ..write(' implements ') | |
| ..writeAll( | |
| spec.implements.map<StringSink>((m) => m.type.accept(this)), | |
| ',', | |
| ); | |
| } | |
| out.write(' {'); | |
| for (var f in spec.fields) { | |
| visitField(f, out); | |
| out.writeln(); | |
| } | |
| _visitMethods(spec.methods, out); | |
| out.write('}'); | |
| return out; | |
| } | |
| @override | |
| StringSink visitConstructor( | |
| Constructor spec, | |
| String clazz, [ | |
| StringSink? output, | |
| ]) { | |
| output ??= StringBuffer(); | |
| spec.docs.forEach(output.writeln); | |
| for (var a in spec.annotations) { | |
| visitAnnotation(a, output); | |
| } | |
| if (spec.external) { | |
| output.write('external '); | |
| } | |
| if (spec.constant) { | |
| output.write('const '); | |
| } | |
| if (spec.factory) { | |
| output.write('factory '); | |
| } | |
| output.write(clazz); | |
| if (spec.name != null) { | |
| output | |
| ..write('.') | |
| ..write(spec.name); | |
| } | |
| output.write('('); | |
| final hasMultipleParameters = | |
| spec.requiredParameters.length + spec.optionalParameters.length > 1; | |
| if (spec.requiredParameters.isNotEmpty) { | |
| var count = 0; | |
| for (final p in spec.requiredParameters) { | |
| count++; | |
| _visitParameter(p, output); | |
| if (hasMultipleParameters || | |
| spec.requiredParameters.length != count || | |
| spec.optionalParameters.isNotEmpty) { | |
| output.write(', '); | |
| } | |
| } | |
| } | |
| if (spec.optionalParameters.isNotEmpty) { | |
| final named = spec.optionalParameters.any((p) => p.named); | |
| if (named) { | |
| output.write('{'); | |
| } else { | |
| output.write('['); | |
| } | |
| var count = 0; | |
| for (final p in spec.optionalParameters) { | |
| count++; | |
| _visitParameter(p, output, optional: true, named: named); | |
| if (hasMultipleParameters || spec.optionalParameters.length != count) { | |
| output.write(', '); | |
| } | |
| } | |
| if (named) { | |
| output.write('}'); | |
| } else { | |
| output.write(']'); | |
| } | |
| } | |
| output.write(')'); | |
| if (spec.initializers.isNotEmpty) { | |
| output.write(' : '); | |
| var count = 0; | |
| for (final initializer in spec.initializers) { | |
| count++; | |
| initializer.accept(this, output); | |
| if (count != spec.initializers.length) { | |
| output.write(', '); | |
| } | |
| } | |
| } | |
| if (spec.redirect != null) { | |
| output.write(' = '); | |
| spec.redirect!.type.accept(this, output); | |
| output.write(';'); | |
| } else if (spec.body != null) { | |
| if (_isLambdaConstructor(spec)) { | |
| output.write(' => '); | |
| spec.body!.accept(this, output); | |
| output.write(';'); | |
| } else { | |
| output.write(' {'); | |
| spec.body!.accept(this, output); | |
| output.write('}'); | |
| } | |
| } else { | |
| output.write(';'); | |
| } | |
| output.writeln(); | |
| return output; | |
| } | |
| @override | |
| StringSink visitExtension(Extension spec, [StringSink? output]) { | |
| final out = output ??= StringBuffer(); | |
| spec.docs.forEach(out.writeln); | |
| for (var a in spec.annotations) { | |
| visitAnnotation(a, out); | |
| } | |
| out.write('extension'); | |
| if (spec.name != null) { | |
| out.write(' ${spec.name}'); | |
| } | |
| visitTypeParameters(spec.types.map((r) => r.type), out); | |
| if (spec.on != null) { | |
| out.write(' on '); | |
| spec.on!.type.accept(this, out); | |
| } | |
| out.write(' {'); | |
| for (var f in spec.fields) { | |
| visitField(f, out); | |
| out.writeln(); | |
| } | |
| _visitMethods(spec.methods, out); | |
| out.writeln('}'); | |
| 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<StringSink>((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(); | |
| } | |
| _visitMethods(spec.methods, out); | |
| 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(); | |
| switch (spec.type) { | |
| case DirectiveType.import: | |
| output.write('import '); | |
| break; | |
| case DirectiveType.export: | |
| output.write('export '); | |
| break; | |
| case DirectiveType.part: | |
| output.write('part '); | |
| break; | |
| case DirectiveType.partOf: | |
| output.write('part of '); | |
| break; | |
| } | |
| output.write("'${spec.url}'"); | |
| if (spec.as != null) { | |
| if (spec.deferred) { | |
| output.write(' deferred '); | |
| } | |
| output.write(' as ${spec.as}'); | |
| } | |
| if (spec.show.isNotEmpty) { | |
| output | |
| ..write(' show ') | |
| ..writeAll(spec.show, ', '); | |
| } else if (spec.hide.isNotEmpty) { | |
| output | |
| ..write(' hide ') | |
| ..writeAll(spec.hide, ', '); | |
| } | |
| output.write(';'); | |
| return output; | |
| } | |
| @override | |
| StringSink visitField(Field spec, [StringSink? output]) { | |
| output ??= StringBuffer(); | |
| spec.docs.forEach(output.writeln); | |
| for (var a in spec.annotations) { | |
| visitAnnotation(a, output); | |
| } | |
| if (spec.external) { | |
| output.write('external '); | |
| } | |
| if (spec.static) { | |
| output.write('static '); | |
| } | |
| if (spec.late && _useNullSafetySyntax) { | |
| output.write('late '); | |
| } | |
| switch (spec.modifier) { | |
| case FieldModifier.var$: | |
| if (spec.type == null) { | |
| output.write('var '); | |
| } | |
| break; | |
| case FieldModifier.final$: | |
| output.write('final '); | |
| break; | |
| case FieldModifier.constant: | |
| output.write('const '); | |
| break; | |
| } | |
| if (spec.type != null) { | |
| spec.type!.type.accept(this, output); | |
| output.write(' '); | |
| } | |
| output.write(spec.name); | |
| if (spec.assignment != null) { | |
| output.write(' = '); | |
| startConstCode(spec.modifier == FieldModifier.constant, () { | |
| spec.assignment!.accept(this, output); | |
| }); | |
| } | |
| output.writeln(';'); | |
| return output; | |
| } | |
| @override | |
| StringSink visitLibrary(Library spec, [StringSink? output]) { | |
| output ??= StringBuffer(); | |
| if (spec.comments.isNotEmpty) { | |
| spec.comments.map((line) => '// $line').forEach(output.writeln); | |
| output.writeln(); | |
| } | |
| if (spec.generatedByComment != null) { | |
| output | |
| ..writeln('// ${spec.generatedByComment}') | |
| ..writeln(); | |
| } | |
| // Process the body first in order to prime the allocators. | |
| final body = StringBuffer(); | |
| for (final spec in spec.body) { | |
| spec.accept(this, body); | |
| if (spec is Method && _isLambdaMethod(spec)) { | |
| body.write(';'); | |
| } | |
| } | |
| final header = StringBuffer(); | |
| spec.docs.forEach(header.writeln); | |
| for (var a in spec.annotations) { | |
| visitAnnotation(a, header); | |
| } | |
| if (spec.name != null) { | |
| header.write('library ${spec.name!};'); | |
| } else if (spec.annotations.isNotEmpty || spec.docs.isNotEmpty) { | |
| // An explicit _unnamed_ library directive is only required if there are | |
| // annotations or doc comments on the library. | |
| header.write('library;'); | |
| } | |
| final directives = <Directive>[...allocator.imports, ...spec.directives]; | |
| if (orderDirectives) { | |
| directives.sort(); | |
| } | |
| final ignores = [ | |
| ...spec.ignoreForFile, | |
| if (directives.any((d) => d.as?.startsWith('_') ?? false)) | |
| 'no_leading_underscores_for_library_prefixes', | |
| ]; | |
| if (ignores.isNotEmpty) { | |
| ignores.sort(); | |
| const prefix = '// ignore_for_file: '; | |
| final lines = [StringBuffer(prefix)..write(ignores.first)]; | |
| for (var ignore in ignores.skip(1)) { | |
| if (lines.last.length + 2 + ignore.length > 80) { | |
| lines.add(StringBuffer(prefix)..write(ignore)); | |
| } else { | |
| lines.last | |
| ..write(', ') | |
| ..write(ignore); | |
| } | |
| } | |
| lines.forEach(output.writeln); | |
| output.writeln(); | |
| } | |
| output.write(header); | |
| Directive? previous; | |
| for (final directive in directives) { | |
| if (_newLineBetween(orderDirectives, previous, directive)) { | |
| // Note: dartfmt handles creating new lines between directives. | |
| // 2 lines are written here. The first one comes after the previous | |
| // directive `;`, the second is the empty line. | |
| output | |
| ..writeln() | |
| ..writeln(); | |
| } | |
| directive.accept(this, output); | |
| previous = directive; | |
| } | |
| output.write(body); | |
| return output; | |
| } | |
| @override | |
| StringSink visitFunctionType(FunctionType spec, [StringSink? output]) { | |
| final out = output ??= StringBuffer(); | |
| if (spec.returnType != null) { | |
| spec.returnType!.accept(this, out); | |
| out.write(' '); | |
| } | |
| out.write('Function'); | |
| if (spec.types.isNotEmpty) { | |
| out.write('<'); | |
| visitAll<Reference>(spec.types, out, (spec) { | |
| spec.accept(this, out); | |
| }); | |
| out.write('>'); | |
| } | |
| out.write('('); | |
| final needsTrailingComma = | |
| spec.requiredParameters.length + | |
| spec.optionalParameters.length + | |
| spec.namedRequiredParameters.length + | |
| spec.namedParameters.length > | |
| 1; | |
| visitAll<Reference>(spec.requiredParameters, out, (spec) { | |
| spec.accept(this, out); | |
| }); | |
| final hasNamedParameters = | |
| spec.namedRequiredParameters.isNotEmpty || | |
| spec.namedParameters.isNotEmpty; | |
| if (spec.requiredParameters.isNotEmpty && | |
| (needsTrailingComma || | |
| spec.optionalParameters.isNotEmpty || | |
| hasNamedParameters)) { | |
| out.write(', '); | |
| } | |
| if (spec.optionalParameters.isNotEmpty) { | |
| out.write('['); | |
| visitAll<Reference>(spec.optionalParameters, out, (spec) { | |
| spec.accept(this, out); | |
| }); | |
| if (needsTrailingComma) { | |
| out.write(', '); | |
| } | |
| out.write(']'); | |
| } else if (hasNamedParameters) { | |
| out.write('{'); | |
| visitAll<String>(spec.namedRequiredParameters.keys, out, (name) { | |
| out.write('required '); | |
| spec.namedRequiredParameters[name]!.accept(this, out); | |
| out | |
| ..write(' ') | |
| ..write(name); | |
| }); | |
| if (spec.namedRequiredParameters.isNotEmpty && | |
| spec.namedParameters.isNotEmpty) { | |
| out.write(', '); | |
| } | |
| visitAll<String>(spec.namedParameters.keys, out, (name) { | |
| spec.namedParameters[name]!.accept(this, out); | |
| out | |
| ..write(' ') | |
| ..write(name); | |
| }); | |
| if (needsTrailingComma) { | |
| out.write(', '); | |
| } | |
| out.write('}'); | |
| } | |
| out.write(')'); | |
| if (_useNullSafetySyntax && (spec.isNullable ?? false)) { | |
| out.write('?'); | |
| } | |
| return out; | |
| } | |
| @override | |
| StringSink visitRecordType(RecordType spec, [StringSink? output]) { | |
| final out = (output ??= StringBuffer())..write('('); | |
| visitAll<Reference>(spec.positionalFieldTypes, out, (spec) { | |
| spec.accept(this, out); | |
| }); | |
| if (spec.namedFieldTypes.isNotEmpty) { | |
| if (spec.positionalFieldTypes.isNotEmpty) { | |
| out.write(', '); | |
| } | |
| out.write('{'); | |
| visitAll<MapEntry<String, Reference>>(spec.namedFieldTypes.entries, out, ( | |
| entry, | |
| ) { | |
| entry.value.accept(this, out); | |
| out.write(' ${entry.key}'); | |
| }); | |
| out.write('}'); | |
| } else if (spec.positionalFieldTypes.length == 1) { | |
| out.write(','); | |
| } | |
| out.write(')'); | |
| // It doesn't really make sense to use records without | |
| // `_useNullSafetySyntax`, but since code_builder is generally very | |
| // permissive, follow it here too. | |
| if (_useNullSafetySyntax && (spec.isNullable ?? false)) { | |
| out.write('?'); | |
| } | |
| return out; | |
| } | |
| @override | |
| StringSink visitTypeDef(TypeDef spec, [StringSink? output]) { | |
| final out = output ??= StringBuffer(); | |
| spec.docs.forEach(out.writeln); | |
| for (var a in spec.annotations) { | |
| visitAnnotation(a, out); | |
| } | |
| out.write('typedef ${spec.name}'); | |
| visitTypeParameters(spec.types.map((r) => r.type), out); | |
| out.write(' = '); | |
| spec.definition.accept(this, out); | |
| out.writeln(';'); | |
| return out; | |
| } | |
| @override | |
| StringSink visitMethod(Method spec, [StringSink? output]) { | |
| output ??= StringBuffer(); | |
| spec.docs.forEach(output.writeln); | |
| for (var a in spec.annotations) { | |
| visitAnnotation(a, output); | |
| } | |
| if (spec.external) { | |
| output.write('external '); | |
| } | |
| if (spec.static) { | |
| output.write('static '); | |
| } | |
| if (spec.returns != null) { | |
| spec.returns!.accept(this, output); | |
| output.write(' '); | |
| } | |
| if (spec.type == MethodType.getter) { | |
| output | |
| ..write('get ') | |
| ..write(spec.name); | |
| } else { | |
| if (spec.type == MethodType.setter) { | |
| output.write('set '); | |
| } | |
| if (spec.name != null) { | |
| output.write(spec.name); | |
| } | |
| visitTypeParameters(spec.types.map((r) => r.type), output); | |
| output.write('('); | |
| final hasMultipleParameters = | |
| spec.requiredParameters.length + spec.optionalParameters.length > 1; | |
| if (spec.requiredParameters.isNotEmpty) { | |
| var count = 0; | |
| for (final p in spec.requiredParameters) { | |
| count++; | |
| _visitParameter(p, output); | |
| if (hasMultipleParameters || | |
| spec.requiredParameters.length != count || | |
| spec.optionalParameters.isNotEmpty) { | |
| output.write(', '); | |
| } | |
| } | |
| } | |
| if (spec.optionalParameters.isNotEmpty) { | |
| final named = spec.optionalParameters.any((p) => p.named); | |
| if (named) { | |
| output.write('{'); | |
| } else { | |
| output.write('['); | |
| } | |
| var count = 0; | |
| for (final p in spec.optionalParameters) { | |
| count++; | |
| _visitParameter(p, output, optional: true, named: named); | |
| if (hasMultipleParameters || | |
| spec.optionalParameters.length != count) { | |
| output.write(', '); | |
| } | |
| } | |
| if (named) { | |
| output.write('}'); | |
| } else { | |
| output.write(']'); | |
| } | |
| } | |
| output.write(')'); | |
| } | |
| if (spec.body != null) { | |
| if (spec.modifier != null) { | |
| switch (spec.modifier!) { | |
| case MethodModifier.async: | |
| output.write(' async'); | |
| break; | |
| case MethodModifier.asyncStar: | |
| output.write(' async*'); | |
| break; | |
| case MethodModifier.syncStar: | |
| output.write(' sync*'); | |
| break; | |
| } | |
| } | |
| if (_isLambdaMethod(spec)) { | |
| output.write(' => '); | |
| } else { | |
| output.write(' {'); | |
| } | |
| spec.body!.accept(this, output); | |
| if (!_isLambdaMethod(spec)) { | |
| output.write('}'); | |
| } | |
| } else { | |
| output.write(';'); | |
| } | |
| return output; | |
| } | |
| // Expose as a first-class visit function only if needed. | |
| void _visitParameter( | |
| Parameter spec, | |
| StringSink output, { | |
| bool optional = false, | |
| bool named = false, | |
| }) { | |
| spec.docs.forEach(output.writeln); | |
| for (var a in spec.annotations) { | |
| visitAnnotation(a, output); | |
| } | |
| // The `required` keyword must precede the `covariant` keyword. | |
| if (spec.required) { | |
| output.write('required '); | |
| } | |
| if (spec.covariant) { | |
| output.write('covariant '); | |
| } | |
| if (spec.type != null) { | |
| spec.type!.type.accept(this, output); | |
| output.write(' '); | |
| } | |
| if (spec.toThis) { | |
| output.write('this.'); | |
| } | |
| if (spec.toSuper) { | |
| output.write('super.'); | |
| } | |
| output.write(spec.name); | |
| if (optional && spec.defaultTo != null) { | |
| output.write(' = '); | |
| spec.defaultTo!.accept(this, output); | |
| } | |
| } | |
| void _visitMethods(Iterable<Method> methods, StringSink out) { | |
| for (final m in methods) { | |
| visitMethod(m, out); | |
| if (_isLambdaMethod(m)) { | |
| out.writeln(';'); | |
| } | |
| out.writeln(); | |
| } | |
| } | |
| @override | |
| StringSink visitReference(Reference spec, [StringSink? output]) => | |
| (output ??= StringBuffer())..write(allocator.allocate(spec)); | |
| @override | |
| StringSink visitSpec(Spec spec, [StringSink? output]) => | |
| spec.accept(this, output); | |
| @override | |
| StringSink visitType(TypeReference spec, [StringSink? output]) { | |
| output ??= StringBuffer(); | |
| // Intentionally not .accept to avoid stack overflow. | |
| visitReference(spec, output); | |
| if (spec.bound != null) { | |
| output.write(' extends '); | |
| spec.bound!.type.accept(this, output); | |
| } | |
| visitTypeParameters(spec.types.map((r) => r.type), output); | |
| if (_useNullSafetySyntax && (spec.isNullable ?? false)) { | |
| output.write('?'); | |
| } | |
| return output; | |
| } | |
| @override | |
| StringSink visitTypeParameters( | |
| Iterable<Reference> specs, [ | |
| StringSink? output, | |
| ]) { | |
| output ??= StringBuffer(); | |
| if (specs.isNotEmpty) { | |
| output | |
| ..write('<') | |
| ..writeAll(specs.map<StringSink>((s) => s.accept(this)), ',') | |
| ..write('>'); | |
| } | |
| return output; | |
| } | |
| @override | |
| StringSink visitEnum(Enum spec, [StringSink? output]) { | |
| final out = output ??= StringBuffer(); | |
| spec.docs.forEach(out.writeln); | |
| for (var a in spec.annotations) { | |
| visitAnnotation(a, out); | |
| } | |
| out.write('enum ${spec.name}'); | |
| visitTypeParameters(spec.types.map((r) => r.type), out); | |
| if (spec.mixins.isNotEmpty) { | |
| out | |
| ..write(' with ') | |
| ..writeAll( | |
| spec.mixins.map<StringSink>((m) => m.type.accept(this)), | |
| ', ', | |
| ); | |
| } | |
| if (spec.implements.isNotEmpty) { | |
| out | |
| ..write(' implements ') | |
| ..writeAll( | |
| spec.implements.map<StringSink>((m) => m.type.accept(this)), | |
| ', ', | |
| ); | |
| } | |
| out.write(' {'); | |
| for (var v in spec.values) { | |
| v.docs.forEach(out.writeln); | |
| for (var a in v.annotations) { | |
| visitAnnotation(a, out); | |
| } | |
| out.write(v.name); | |
| if (v.constructorName != null) { | |
| out.write('.${v.constructorName}'); | |
| } | |
| visitTypeParameters(v.types.map((r) => r.type), out); | |
| final takesArguments = | |
| v.constructorName != null || | |
| v.arguments.isNotEmpty || | |
| v.namedArguments.isNotEmpty; | |
| if (takesArguments) { | |
| out.write('('); | |
| } | |
| if (v.arguments.isNotEmpty) { | |
| out.writeAll( | |
| v.arguments.map<StringSink>((arg) => arg.accept(this)), | |
| ', ', | |
| ); | |
| } | |
| if (v.arguments.isNotEmpty && v.namedArguments.isNotEmpty) { | |
| out.write(', '); | |
| } | |
| visitAll<String>(v.namedArguments.keys, out, (name) { | |
| out | |
| ..write(name) | |
| ..write(': '); | |
| v.namedArguments[name]!.accept(this, out); | |
| }); | |
| if (takesArguments) { | |
| out.write(')'); | |
| } | |
| if (v != spec.values.last) { | |
| out.writeln(','); | |
| } else if (spec.constructors.isNotEmpty || | |
| spec.fields.isNotEmpty || | |
| spec.methods.isNotEmpty) { | |
| 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(); | |
| } | |
| _visitMethods(spec.methods, out); | |
| out.writeln('}'); | |
| return out; | |
| } | |
| } | |
| /// Returns `true` if: | |
| /// | |
| /// * [ordered] is `true` | |
| /// * [a] is non-`null` | |
| /// * If there should be an empty line before [b] if it's emitted after [a]. | |
| bool _newLineBetween(bool ordered, Directive? a, Directive? b) { | |
| if (!ordered) return false; | |
| if (a == null) return false; | |
| assert(b != null); | |
| // Put a line between imports and exports | |
| if (a.type != b!.type) return true; | |
| // Within exports, don't put in extra blank lines | |
| if (a.type == DirectiveType.export) { | |
| assert(b.type == DirectiveType.export); | |
| return false; | |
| } | |
| // Return `true` if the schemes for [a] and [b] are different | |
| return !Uri.parse(a.url).isScheme(Uri.parse(b.url).scheme); | |
| } |