// Copyright (c) 2016, 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 kernel.ast_to_text; import 'dart:core' hide MapEntry; import 'dart:core' as core show MapEntry; import 'dart:convert' show json; import '../ast.dart'; import '../import_table.dart'; import '../src/text_util.dart'; abstract class Namer { int index = 0; final Map map = {}; String getName(T key) => map.putIfAbsent(key, () => '$prefix${++index}'); String get prefix; } class NormalNamer extends Namer { final String prefix; NormalNamer(this.prefix); } class ConstantNamer extends RecursiveResultVisitor with Namer { final String prefix; ConstantNamer(this.prefix); String getName(Constant constant) { if (!map.containsKey(constant)) { // When printing a non-fully linked kernel AST (i.e. some [Reference]s // are not bound) to text, we need to avoid dereferencing any // references. // // The normal visitor API causes references to be dereferenced in order // to call the `visit()` / `visitReference()`. // // We therefore handle any subclass of [Constant] which has [Reference]s // specially here. // if (constant is InstanceConstant) { // Avoid visiting `InstanceConstant.classReference`. for (final Constant value in constant.fieldValues.values) { // Name everything in post-order visit of DAG. getName(value); } } else if (constant is TearOffConstant) { // We only care about naming the constants themselves. [TearOffConstant] // has no Constant children. // Avoid visiting `TearOffConstant.procedureReference`. } else { // Name everything in post-order visit of DAG. constant.visitChildren(this); } } return super.getName(constant); } defaultConstantReference(Constant constant) { getName(constant); } defaultDartType(DartType type) { // No need to recurse into dart types, we only care about naming the // constants themselves. } } class Disambiguator { final Map namesT = {}; final Map namesU = {}; final Set usedNames = new Set(); String disambiguate(T? key1, U? key2, String proposeName()) { String getNewName() { String proposedName = proposeName(); if (usedNames.add(proposedName)) return proposedName; int i = 2; while (!usedNames.add('$proposedName$i')) { ++i; } return '$proposedName$i'; } if (key1 != null) { String? result = namesT[key1]; if (result != null) return result; return namesT[key1] = getNewName(); } if (key2 != null) { String? result = namesU[key2]; if (result != null) return result; return namesU[key2] = getNewName(); } throw "Cannot disambiguate"; } } NameSystem globalDebuggingNames = new NameSystem(); String debugLibraryName(Library? node) { return node == null ? 'null' : node.name ?? globalDebuggingNames.nameLibrary(node); } String debugClassName(Class? node) { return node == null ? 'null' : node.name; } String debugQualifiedClassName(Class node) { return debugLibraryName(node.enclosingLibrary) + '::' + debugClassName(node); } String debugMemberName(Member node) { return node.name.text; } String debugQualifiedMemberName(Member node) { if (node.enclosingClass != null) { return debugQualifiedClassName(node.enclosingClass!) + '::' + debugMemberName(node); } else { return debugLibraryName(node.enclosingLibrary) + '::' + debugMemberName(node); } } String debugTypeParameterName(TypeParameter node) { return node.name ?? globalDebuggingNames.nameTypeParameter(node); } String debugQualifiedTypeParameterName(TypeParameter node) { TreeNode? parent = node.parent; if (parent is Class) { return debugQualifiedClassName(parent) + '::' + debugTypeParameterName(node); } if (parent is Member) { return debugQualifiedMemberName(parent) + '::' + debugTypeParameterName(node); } return debugTypeParameterName(node); } String debugVariableDeclarationName(VariableDeclaration node) { return node.name ?? globalDebuggingNames.nameVariable(node); } String debugNodeToString(Node node) { StringBuffer buffer = new StringBuffer(); new Printer(buffer, syntheticNames: globalDebuggingNames).writeNode(node); return '$buffer'; } String debugLibraryToString(Library library) { StringBuffer buffer = new StringBuffer(); new Printer(buffer, syntheticNames: globalDebuggingNames) .writeLibraryFile(library); return '$buffer'; } String debugComponentToString(Component component) { StringBuffer buffer = new StringBuffer(); new Printer(buffer, syntheticNames: new NameSystem()) .writeComponentFile(component); return '$buffer'; } String componentToString(Component node) { StringBuffer buffer = new StringBuffer(); new Printer(buffer, syntheticNames: new NameSystem()) .writeComponentFile(node); return '$buffer'; } class NameSystem { final Namer variables = new NormalNamer('#t'); final Namer members = new NormalNamer('#m'); final Namer classes = new NormalNamer('#class'); final Namer extensions = new NormalNamer('#extension'); final Namer libraries = new NormalNamer('#lib'); final Namer typeParameters = new NormalNamer('#T'); final Namer labels = new NormalNamer('#L'); final Namer constants = new ConstantNamer('#C'); final Disambiguator prefixes = new Disambiguator(); nameVariable(VariableDeclaration node) => variables.getName(node); nameMember(Member node) => members.getName(node); nameClass(Class node) => classes.getName(node); nameExtension(Extension node) => extensions.getName(node); nameLibrary(Library node) => libraries.getName(node); nameTypeParameter(TypeParameter node) => typeParameters.getName(node); nameSwitchCase(SwitchCase node) => labels.getName(node); nameLabeledStatement(LabeledStatement node) => labels.getName(node); nameConstant(Constant node) => constants.getName(node); final RegExp pathSeparator = new RegExp('[\\/]'); String nameLibraryPrefix(Library node, {String? proposedName}) { return prefixes.disambiguate(node.reference, node.reference.canonicalName, () { if (proposedName != null) { return proposedName; } String? name = node.name; if (name != null) { return abbreviateName(name); } // ignore: unnecessary_null_comparison if (node.importUri != null) { String path = node.importUri.hasEmptyPath ? '${node.importUri}' : node.importUri.pathSegments.last; if (path.endsWith('.dart')) { path = path.substring(0, path.length - '.dart'.length); } return abbreviateName(path); } return 'L'; }); } nameCanonicalNameAsLibraryPrefix(Reference? node, CanonicalName? name, {String? proposedName}) { return prefixes.disambiguate(node, name, () { if (proposedName != null) return proposedName; CanonicalName? canonicalName = name ?? node?.canonicalName; if (canonicalName?.name != null) { String path = canonicalName!.name; int slash = path.lastIndexOf(pathSeparator); if (slash >= 0) { path = path.substring(slash + 1); } if (path.endsWith('.dart')) { path = path.substring(0, path.length - '.dart'.length); } return abbreviateName(path); } return 'L'; }); } final RegExp punctuation = new RegExp('[.:]'); String abbreviateName(String name) { int dot = name.lastIndexOf(punctuation); if (dot != -1) { name = name.substring(dot + 1); } if (name.length > 4) { return name.substring(0, 3); } return name; } } abstract class Annotator { String annotateVariable(Printer printer, VariableDeclaration node); String annotateReturn(Printer printer, FunctionNode node); String annotateField(Printer printer, Field node); } /// A quick and dirty ambiguous text printer. class Printer extends Visitor with VisitorVoidMixin { final NameSystem syntheticNames; final StringSink sink; final Annotator? annotator; final Map>? metadata; ImportTable? importTable; int indentation = 0; int column = 0; bool showOffsets; bool showMetadata; static int SPACE = 0; static int WORD = 1; static int SYMBOL = 2; int state = SPACE; Printer(this.sink, {NameSystem? syntheticNames, this.showOffsets: false, this.showMetadata: false, this.importTable, this.annotator, this.metadata}) : this.syntheticNames = syntheticNames ?? new NameSystem(); Printer createInner(ImportTable importTable, Map>? metadata) { return new Printer(sink, importTable: importTable, metadata: metadata, syntheticNames: syntheticNames, annotator: annotator, showOffsets: showOffsets, showMetadata: showMetadata); } bool shouldHighlight(Node node) { return false; } void startHighlight(Node node) {} void endHighlight(Node node) {} String getLibraryName(Library node) { return node.name ?? syntheticNames.nameLibrary(node); } String getLibraryReference(Library node) { // ignore: unnecessary_null_comparison if (node == null) return ''; if (importTable != null && importTable?.getImportIndex(node) != -1) { return syntheticNames.nameLibraryPrefix(node); } return getLibraryName(node); } String getClassName(Class node) { return node.name; } String getExtensionName(Extension node) { return node.name; } String getClassReference(Class node) { // ignore: unnecessary_null_comparison if (node == null) return ''; String name = getClassName(node); String library = getLibraryReference(node.enclosingLibrary); return '$library::$name'; } String getExtensionReference(Extension node) { // ignore: unnecessary_null_comparison if (node == null) return ''; String name = getExtensionName(node); String library = getLibraryReference(node.enclosingLibrary); return '$library::$name'; } String getTypedefReference(Typedef node) { // ignore: unnecessary_null_comparison if (node == null) return ''; String library = getLibraryReference(node.enclosingLibrary); return '$library::${node.name}'; } static final String emptyNameString = '•'; static final Name emptyName = new Name(emptyNameString); Name getMemberName(Member node) { if (node.name.text == '') return emptyName; // ignore: unnecessary_null_comparison if (node.name != null) return node.name; return new Name(syntheticNames.nameMember(node)); } String getMemberReference(Member node) { // ignore: unnecessary_null_comparison if (node == null) return ''; String name = getMemberName(node).text; Class? enclosingClass = node.enclosingClass; if (enclosingClass != null) { String className = getClassReference(enclosingClass); return '$className::$name'; } else { String library = getLibraryReference(node.enclosingLibrary); return '$library::$name'; } } String getVariableName(VariableDeclaration node) { return node.name ?? syntheticNames.nameVariable(node); } String getVariableReference(VariableDeclaration node) { // ignore: unnecessary_null_comparison if (node == null) return ''; return getVariableName(node); } String getTypeParameterName(TypeParameter node) { return node.name ?? syntheticNames.nameTypeParameter(node); } String getTypeParameterReference(TypeParameter node) { // ignore: unnecessary_null_comparison if (node == null) return ''; String name = getTypeParameterName(node); TreeNode? parent = node.parent; if (parent is FunctionNode && parent.parent is Member) { String member = getMemberReference(parent.parent as Member); return '$member::$name'; } else if (parent is Class) { String className = getClassReference(parent); return '$className::$name'; } else { return name; // Bound inside a function type. } } void writeComponentProblems(Component component) { writeProblemsAsJson("Problems in component", component.problemsAsJson); } void writeProblemsAsJson(String header, List? problemsAsJson) { if (problemsAsJson != null && problemsAsJson.isNotEmpty) { endLine("//"); write("// "); write(header); endLine(":"); endLine("//"); for (String s in problemsAsJson) { Map decoded = json.decode(s); List plainTextFormatted = decoded["plainTextFormatted"] as List; List lines = plainTextFormatted.join("\n").split("\n"); for (int i = 0; i < lines.length; i++) { write("//"); String trimmed = lines[i].trimRight(); if (trimmed.isNotEmpty) write(" "); endLine(trimmed); } if (lines.isNotEmpty) endLine("//"); } } } void writeLibraryFile(Library library) { writeAnnotationList(library.annotations); writeWord('library'); String? name = library.name; if (name != null) { writeWord(name); } if (library.isNonNullableByDefault) { writeWord("/*isNonNullableByDefault*/"); } endLine(';'); LibraryImportTable imports = new LibraryImportTable(library); Printer inner = createInner(imports, library.enclosingComponent?.metadata); inner.writeStandardLibraryContent(library, outerPrinter: this, importsToPrint: imports); } void printLibraryImportTable(LibraryImportTable imports) { for (Library library in imports.importedLibraries) { String importPath = imports.getImportPath(library); if (importPath == "") { String prefix = syntheticNames.nameLibraryPrefix(library, proposedName: 'self'); endLine('import self as $prefix;'); } else { String prefix = syntheticNames.nameLibraryPrefix(library); endLine('import "$importPath" as $prefix;'); } } } void writeStandardLibraryContent(Library library, {Printer? outerPrinter, LibraryImportTable? importsToPrint}) { outerPrinter ??= this; outerPrinter.writeProblemsAsJson( "Problems in library", library.problemsAsJson); if (importsToPrint != null) { outerPrinter.printLibraryImportTable(importsToPrint); } writeAdditionalExports(library.additionalExports); endLine(); library.dependencies.forEach(writeNode); if (library.dependencies.isNotEmpty) endLine(); library.parts.forEach(writeNode); library.typedefs.forEach(writeNode); library.classes.forEach(writeNode); library.extensions.forEach(writeNode); library.fields.forEach(writeNode); library.procedures.forEach(writeNode); } void writeAdditionalExports(List additionalExports) { if (additionalExports.isEmpty) return; write('additionalExports = ('); for (int i = 0; i < additionalExports.length; i++) { Reference reference = additionalExports[i]; NamedNode? node = reference.node; if (node is Class) { Library nodeLibrary = node.enclosingLibrary; String prefix = syntheticNames.nameLibraryPrefix(nodeLibrary); write(prefix + '::' + node.name); } else if (node is Extension) { Library nodeLibrary = node.enclosingLibrary; String prefix = syntheticNames.nameLibraryPrefix(nodeLibrary); write(prefix + '::' + node.name); } else if (node is Field) { Library nodeLibrary = node.enclosingLibrary; String prefix = syntheticNames.nameLibraryPrefix(nodeLibrary); write(prefix + '::' + node.name.text); } else if (node is Procedure) { Library nodeLibrary = node.enclosingLibrary; String prefix = syntheticNames.nameLibraryPrefix(nodeLibrary); write(prefix + '::' + node.name.text); } else if (node is Typedef) { Library nodeLibrary = node.enclosingLibrary; String prefix = syntheticNames.nameLibraryPrefix(nodeLibrary); write(prefix + '::' + node.name); } else if (reference.canonicalName != null) { write(reference.canonicalName.toString()); } else { throw new UnimplementedError('${node.runtimeType}'); } if (i + 1 == additionalExports.length) { endLine(")"); } else { endLine(","); write(" "); } } } void writeComponentFile(Component component) { ImportTable imports = new ComponentImportTable(component); Printer inner = createInner(imports, component.metadata); writeWord('main'); writeSpaced('='); inner.writeMemberReferenceFromReference(component.mainMethodName); endLine(';'); if (showMetadata) { inner.writeMetadata(component); } writeComponentProblems(component); for (Library library in component.libraries) { if (showMetadata) { inner.writeMetadata(library); } writeAnnotationList(library.annotations); writeWord('library'); String? name = library.name; if (name != null) { writeWord(name); } // ignore: unnecessary_null_comparison if (library.importUri != null) { writeSpaced('from'); writeWord('"${library.importUri}"'); } String prefix = syntheticNames.nameLibraryPrefix(library); writeSpaced('as'); writeWord(prefix); endLine(' {'); ++inner.indentation; inner.writeStandardLibraryContent(library); --inner.indentation; endLine('}'); } writeConstantTable(component); } void writeConstantTable(Component component) { if (syntheticNames.constants.map.isEmpty) return; ImportTable imports = new ComponentImportTable(component); Printer inner = createInner(imports, component.metadata); writeWord('constants '); endLine(' {'); ++inner.indentation; for (final Constant constant in syntheticNames.constants.map.keys.toList()) { inner.writeNode(constant); } --inner.indentation; endLine('}'); } int getPrecedence(Expression node) { return Precedence.of(node); } void write(String string) { sink.write(string); column += string.length; } void writeSpace([String string = ' ']) { write(string); state = SPACE; } void ensureSpace() { if (state != SPACE) writeSpace(); } void writeSymbol(String string) { write(string); state = SYMBOL; } void writeSpaced(String string) { ensureSpace(); write(string); writeSpace(); } void writeComma([String string = ',']) { write(string); writeSpace(); } void writeWord(String string) { if (string.isEmpty) return; ensureWordBoundary(); write(string); state = WORD; } void ensureWordBoundary() { if (state == WORD) { writeSpace(); } } void writeIndentation() { writeSpace(' ' * indentation); } void writeNode(Node? node) { if (node == null) { writeSymbol(""); } else { final bool highlight = shouldHighlight(node); if (highlight) { startHighlight(node); } if (showOffsets && node is TreeNode) { writeWord("[${node.fileOffset}]"); } if (showMetadata && node is TreeNode) { writeMetadata(node); } node.accept(this); if (highlight) { endHighlight(node); } } } void writeMetadata(TreeNode node) { if (metadata != null) { for (MetadataRepository md in metadata!.values) { final dynamic nodeMetadata = md.mapping[node]; if (nodeMetadata != null) { writeWord("[@${md.tag}=${nodeMetadata}]"); } } } } void writeAnnotatedType(DartType type, String? annotation) { writeType(type); if (annotation != null) { write('/'); write(annotation); state = WORD; } } void writeType(DartType type) { // ignore: unnecessary_null_comparison if (type == null) { write(''); } else { type.accept(this); } } void writeOptionalType(DartType type) { // ignore: unnecessary_null_comparison if (type != null) { type.accept(this); } } visitSupertype(Supertype type) { // ignore: unnecessary_null_comparison if (type == null) { write(''); } else { writeClassReferenceFromReference(type.className); if (type.typeArguments.isNotEmpty) { writeSymbol('<'); writeList(type.typeArguments, writeType); writeSymbol('>'); } } } visitTypedefType(TypedefType type) { writeTypedefReference(type.typedefNode); if (type.typeArguments.isNotEmpty) { writeSymbol('<'); writeList(type.typeArguments, writeType); writeSymbol('>'); } } void writeModifier(bool isThere, String name) { if (isThere) { writeWord(name); } } void writeName(Name name) { if (name.text == '') { writeWord(emptyNameString); } else { writeWord(name.text); // TODO: write library name } } void endLine([String? string]) { if (string != null) { write(string); } write('\n'); state = SPACE; column = 0; } void writeFunction(FunctionNode function, {name, List? initializers, bool terminateLine: true}) { if (name is String) { writeWord(name); } else if (name is Name) { writeName(name); } else { assert(name == null); } writeTypeParameterList(function.typeParameters); writeParameterList(function.positionalParameters, function.namedParameters, function.requiredParameterCount); writeReturnType( function.returnType, annotator?.annotateReturn(this, function)); if (initializers != null && initializers.isNotEmpty) { endLine(); ++indentation; writeIndentation(); writeComma(':'); writeList(initializers, writeNode); --indentation; } if (function.asyncMarker != AsyncMarker.Sync) { writeSpaced(getAsyncMarkerKeyword(function.asyncMarker)); } if (function.dartAsyncMarker != AsyncMarker.Sync && function.dartAsyncMarker != function.asyncMarker) { writeSpaced("/* originally"); writeSpaced(getAsyncMarkerKeyword(function.dartAsyncMarker)); writeSpaced("*/"); } Statement? body = function.body; if (body != null) { writeFunctionBody(body, terminateLine: terminateLine); } else if (terminateLine) { endLine(';'); } else { writeSymbol(';'); } } String getAsyncMarkerKeyword(AsyncMarker marker) { switch (marker) { case AsyncMarker.Sync: return 'sync'; case AsyncMarker.SyncStar: return 'sync*'; case AsyncMarker.Async: return 'async'; case AsyncMarker.AsyncStar: return 'async*'; case AsyncMarker.SyncYielding: return 'yielding'; default: return ''; } } void writeFunctionBody(Statement body, {bool terminateLine: true}) { if (body is Block && body.statements.isEmpty) { ensureSpace(); writeSymbol('{}'); state = WORD; if (terminateLine) { endLine(); } } else if (body is Block) { ensureSpace(); endLine('{'); ++indentation; body.statements.forEach(writeNode); --indentation; writeIndentation(); writeSymbol('}'); state = WORD; if (terminateLine) { endLine(); } } else if (body is ReturnStatement && !terminateLine) { writeSpaced('=>'); writeExpression(body.expression!); } else { writeBody(body); } } writeFunctionType(FunctionType node, {List? typedefPositional, List? typedefNamed}) { if (state == WORD) { ensureSpace(); } writeTypeParameterList(node.typeParameters); writeSymbol('('); List positional = node.positionalParameters; bool parametersAnnotated = false; if (typedefPositional != null) { for (VariableDeclaration formal in typedefPositional) { parametersAnnotated = parametersAnnotated || formal.annotations.length > 0; } } if (typedefNamed != null) { for (VariableDeclaration formal in typedefNamed) { parametersAnnotated = parametersAnnotated || formal.annotations.length > 0; } } if (parametersAnnotated && typedefPositional != null) { writeList(typedefPositional.take(node.requiredParameterCount), writeVariableDeclaration); } else { writeList(positional.take(node.requiredParameterCount), writeType); } if (node.requiredParameterCount < positional.length) { if (node.requiredParameterCount > 0) { writeComma(); } writeSymbol('['); if (parametersAnnotated && typedefPositional != null) { writeList(typedefPositional.skip(node.requiredParameterCount), writeVariableDeclaration); } else { writeList(positional.skip(node.requiredParameterCount), writeType); } writeSymbol(']'); } if (node.namedParameters.isNotEmpty) { if (node.positionalParameters.isNotEmpty) { writeComma(); } writeSymbol('{'); if (parametersAnnotated && typedefNamed != null) { writeList(typedefNamed, writeVariableDeclaration); } else { writeList(node.namedParameters, visitNamedType); } writeSymbol('}'); } writeSymbol(')'); ensureSpace(); write('→'); writeNullability(node.nullability); writeSpace(); writeType(node.returnType); } void writeBody(Statement body) { if (body is Block) { endLine(' {'); ++indentation; body.statements.forEach(writeNode); --indentation; writeIndentation(); endLine('}'); } else { endLine(); ++indentation; writeNode(body); --indentation; } } void writeReturnType(DartType type, String? annotation) { // ignore: unnecessary_null_comparison if (type == null) return; writeSpaced('→'); writeAnnotatedType(type, annotation); } void writeTypeParameterList(List typeParameters) { if (typeParameters.isEmpty) return; writeSymbol('<'); writeList(typeParameters, writeNode); writeSymbol('>'); state = WORD; // Ensure space if not followed by another symbol. } void writeParameterList(List positional, List named, int requiredParameterCount) { writeSymbol('('); writeList( positional.take(requiredParameterCount), writeVariableDeclaration); if (requiredParameterCount < positional.length) { if (requiredParameterCount > 0) { writeComma(); } writeSymbol('['); writeList( positional.skip(requiredParameterCount), writeVariableDeclaration); writeSymbol(']'); } if (named.isNotEmpty) { if (positional.isNotEmpty) { writeComma(); } writeSymbol('{'); writeList(named, writeVariableDeclaration); writeSymbol('}'); } writeSymbol(')'); } void writeList(Iterable nodes, void callback(T x), {String separator: ','}) { bool first = true; for (T node in nodes) { if (first) { first = false; } else { writeComma(separator); } callback(node); } } void writeClassReferenceFromReference(Reference reference) { writeWord(getClassReferenceFromReference(reference)); } String getClassReferenceFromReference(Reference reference) { // ignore: unnecessary_null_comparison if (reference == null) return ''; if (reference.node != null) return getClassReference(reference.asClass); if (reference.canonicalName != null) { return getCanonicalNameString(reference.canonicalName!); } throw "Neither node nor canonical name found"; } void writeExtensionReferenceFromReference(Reference reference) { writeWord(getExtensionReferenceFromReference(reference)); } String getExtensionReferenceFromReference(Reference reference) { // ignore: unnecessary_null_comparison if (reference == null) return ''; if (reference.node != null) { return getExtensionReference(reference.asExtension); } if (reference.canonicalName != null) { return getCanonicalNameString(reference.canonicalName!); } throw "Neither node nor canonical name found"; } void writeMemberReferenceFromReference(Reference? reference) { writeWord(getMemberReferenceFromReference(reference)); } String getMemberReferenceFromReference(Reference? reference) { if (reference == null) return ''; if (reference.node != null) return getMemberReference(reference.asMember); if (reference.canonicalName != null) { return getCanonicalNameString(reference.canonicalName!); } throw "Neither node nor canonical name found"; } String getCanonicalNameString(CanonicalName name) { if (name.isRoot) throw 'unexpected root'; if (name.name.startsWith('@')) throw 'unexpected @'; String libraryString(CanonicalName lib) { if (lib.reference.node != null) { return getLibraryReference(lib.reference.asLibrary); } return syntheticNames.nameCanonicalNameAsLibraryPrefix( lib.reference, lib); } String classString(CanonicalName cls) => libraryString(cls.parent!) + '::' + cls.name; if (name.parent!.isRoot) return libraryString(name); if (name.parent!.parent!.isRoot) return classString(name); CanonicalName atNode = name.parent!; while (!atNode.name.startsWith('@')) { atNode = atNode.parent!; } String parent = ""; if (atNode.parent!.parent!.isRoot) { parent = libraryString(atNode.parent!); } else { parent = classString(atNode.parent!); } if (name.name == '') return "$parent::$emptyNameString"; return "$parent::${name.name}"; } void writeTypedefReference(Typedef typedefNode) { writeWord(getTypedefReference(typedefNode)); } void writeVariableReference(VariableDeclaration variable) { final bool highlight = shouldHighlight(variable); if (highlight) { startHighlight(variable); } writeWord(getVariableReference(variable)); if (highlight) { endHighlight(variable); } } void writeTypeParameterReference(TypeParameter node) { writeWord(getTypeParameterReference(node)); } void writeExpression(Expression node, [int? minimumPrecedence]) { final bool highlight = shouldHighlight(node); if (highlight) { startHighlight(node); } if (showOffsets) writeWord("[${node.fileOffset}]"); bool needsParenteses = false; if (minimumPrecedence != null && getPrecedence(node) < minimumPrecedence) { needsParenteses = true; writeSymbol('('); } writeNode(node); if (needsParenteses) { writeSymbol(')'); } if (highlight) { endHighlight(node); } } void writeAnnotation(Expression node) { writeSymbol('@'); if (node is ConstructorInvocation) { writeMemberReferenceFromReference(node.targetReference); visitArguments(node.arguments); } else { writeExpression(node); } } void writeAnnotationList(List nodes, {bool separateLines: true}) { for (Expression node in nodes) { if (separateLines) { writeIndentation(); } writeAnnotation(node); if (separateLines) { endLine(); } else { writeSpace(); } } } visitLibrary(Library node) {} visitField(Field node) { writeAnnotationList(node.annotations); writeIndentation(); writeModifier(node.isLate, 'late'); writeModifier(node.isStatic, 'static'); writeModifier(node.isCovariant, 'covariant'); writeModifier(node.isGenericCovariantImpl, 'generic-covariant-impl'); writeModifier(node.isFinal, 'final'); writeModifier(node.isConst, 'const'); // Only show implicit getter/setter modifiers in cases where they are // out of the ordinary. if (node.isFinal) { writeModifier(node.hasSetter, '[setter]'); } writeWord('field'); writeSpace(); writeAnnotatedType(node.type, annotator?.annotateField(this, node)); writeName(getMemberName(node)); Expression? initializer = node.initializer; if (initializer != null) { writeSpaced('='); writeExpression(initializer); } List features = []; if (node.enclosingLibrary.isNonNullableByDefault != node.isNonNullableByDefault) { if (node.isNonNullableByDefault) { features.add("isNonNullableByDefault"); } else { features.add("isLegacy"); } } Class? enclosingClass = node.enclosingClass; if ((enclosingClass == null && node.enclosingLibrary.fileUri != node.fileUri) || (enclosingClass != null && enclosingClass.fileUri != node.fileUri)) { features.add(" from ${node.fileUri} "); } if (features.isNotEmpty) { writeWord("/*${features.join(',')}*/"); } endLine(';'); } visitProcedure(Procedure node) { writeAnnotationList(node.annotations); writeIndentation(); writeModifier(node.isExternal, 'external'); writeModifier(node.isStatic, 'static'); writeModifier(node.isAbstract, 'abstract'); writeModifier(node.isForwardingStub, 'forwarding-stub'); writeModifier(node.isForwardingSemiStub, 'forwarding-semi-stub'); switch (node.stubKind) { case ProcedureStubKind.Regular: case ProcedureStubKind.AbstractForwardingStub: case ProcedureStubKind.ConcreteForwardingStub: break; case ProcedureStubKind.NoSuchMethodForwarder: writeWord('no-such-method-forwarder'); break; case ProcedureStubKind.MemberSignature: writeWord('member-signature'); break; case ProcedureStubKind.AbstractMixinStub: writeWord('mixin-stub'); break; case ProcedureStubKind.ConcreteMixinStub: writeWord('mixin-super-stub'); break; } writeWord(procedureKindToString(node.kind)); List features = []; if (node.enclosingLibrary.isNonNullableByDefault != node.isNonNullableByDefault) { if (node.isNonNullableByDefault) { features.add("isNonNullableByDefault"); } else { features.add("isLegacy"); } } Class? enclosingClass = node.enclosingClass; if ((enclosingClass == null && node.enclosingLibrary.fileUri != node.fileUri) || (enclosingClass != null && enclosingClass.fileUri != node.fileUri)) { features.add(" from ${node.fileUri} "); } if (features.isNotEmpty) { writeWord("/*${features.join(',')}*/"); } switch (node.stubKind) { case ProcedureStubKind.Regular: case ProcedureStubKind.AbstractForwardingStub: case ProcedureStubKind.ConcreteForwardingStub: case ProcedureStubKind.NoSuchMethodForwarder: case ProcedureStubKind.ConcreteMixinStub: writeFunction(node.function, name: getMemberName(node)); break; case ProcedureStubKind.MemberSignature: case ProcedureStubKind.AbstractMixinStub: writeFunction(node.function, name: getMemberName(node), terminateLine: false); if (node.function.body is ReturnStatement) { writeSymbol(';'); } writeSymbol(' -> '); writeMemberReferenceFromReference(node.stubTargetReference!); endLine(); break; } } visitConstructor(Constructor node) { writeAnnotationList(node.annotations); writeIndentation(); writeModifier(node.isExternal, 'external'); writeModifier(node.isConst, 'const'); writeModifier(node.isSynthetic, 'synthetic'); writeWord('constructor'); List features = []; if (node.enclosingLibrary.isNonNullableByDefault != node.isNonNullableByDefault) { if (node.isNonNullableByDefault) { features.add("isNonNullableByDefault"); } else { features.add("isLegacy"); } } if (features.isNotEmpty) { writeWord("/*${features.join(',')}*/"); } writeFunction(node.function, name: node.name, initializers: node.initializers); } visitRedirectingFactoryConstructor(RedirectingFactoryConstructor node) { writeAnnotationList(node.annotations); writeIndentation(); writeModifier(node.isExternal, 'external'); writeModifier(node.isConst, 'const'); writeWord('redirecting_factory'); // ignore: unnecessary_null_comparison if (node.name != null) { writeName(node.name); } writeTypeParameterList(node.typeParameters); writeParameterList(node.positionalParameters, node.namedParameters, node.requiredParameterCount); writeSpaced('='); writeMemberReferenceFromReference(node.targetReference!); if (node.typeArguments.isNotEmpty) { writeSymbol('<'); writeList(node.typeArguments, writeType); writeSymbol('>'); } List features = []; if (node.enclosingLibrary.isNonNullableByDefault != node.isNonNullableByDefault) { if (node.isNonNullableByDefault) { features.add("isNonNullableByDefault"); } else { features.add("isLegacy"); } } if (features.isNotEmpty) { writeWord("/*${features.join(',')}*/"); } endLine(';'); } visitClass(Class node) { writeAnnotationList(node.annotations); writeIndentation(); writeModifier(node.isAbstract, 'abstract'); writeWord('class'); writeWord(getClassName(node)); writeTypeParameterList(node.typeParameters); if (node.isMixinApplication) { writeSpaced('='); visitSupertype(node.supertype!); writeSpaced('with'); visitSupertype(node.mixedInType!); } else if (node.supertype != null) { writeSpaced('extends'); visitSupertype(node.supertype!); } if (node.implementedTypes.isNotEmpty) { writeSpaced('implements'); writeList(node.implementedTypes, visitSupertype); } List features = []; if (node.isEnum) { features.add('isEnum'); } if (node.isAnonymousMixin) { features.add('isAnonymousMixin'); } if (node.isEliminatedMixin) { features.add('isEliminatedMixin'); } if (node.isMixinDeclaration) { features.add('isMixinDeclaration'); } if (node.hasConstConstructor) { features.add('hasConstConstructor'); } if (features.isNotEmpty) { writeSpaced('/*${features.join(',')}*/'); } String endLineString = ' {'; if (node.enclosingLibrary.fileUri != node.fileUri) { endLineString += ' // from ${node.fileUri}'; } endLine(endLineString); ++indentation; node.fields.forEach(writeNode); node.constructors.forEach(writeNode); node.procedures.forEach(writeNode); node.redirectingFactoryConstructors.forEach(writeNode); --indentation; writeIndentation(); endLine('}'); } visitExtension(Extension node) { writeAnnotationList(node.annotations); writeIndentation(); writeWord('extension'); writeWord(getExtensionName(node)); writeTypeParameterList(node.typeParameters); writeSpaced('on'); writeType(node.onType); String endLineString = ' {'; if (node.enclosingLibrary.fileUri != node.fileUri) { endLineString += ' // from ${node.fileUri}'; } endLine(endLineString); ++indentation; node.members.forEach((ExtensionMemberDescriptor descriptor) { writeIndentation(); writeModifier(descriptor.isStatic, 'static'); switch (descriptor.kind) { case ExtensionMemberKind.Method: writeWord('method'); break; case ExtensionMemberKind.Getter: writeWord('get'); break; case ExtensionMemberKind.Setter: writeWord('set'); break; case ExtensionMemberKind.Operator: writeWord('operator'); break; case ExtensionMemberKind.Field: writeWord('field'); break; case ExtensionMemberKind.TearOff: writeWord('tearoff'); break; } writeName(descriptor.name); writeSpaced('='); Member member = descriptor.member.asMember; if (member is Procedure) { if (member.isGetter) { writeWord('get'); } else if (member.isSetter) { writeWord('set'); } } writeMemberReferenceFromReference(descriptor.member); endLine(';'); }); --indentation; writeIndentation(); endLine('}'); } visitTypedef(Typedef node) { writeAnnotationList(node.annotations); writeIndentation(); writeWord('typedef'); writeWord(node.name); writeTypeParameterList(node.typeParameters); writeSpaced('='); DartType? type = node.type; if (type is FunctionType) { writeFunctionType(type, typedefPositional: node.positionalParameters, typedefNamed: node.namedParameters); } else { writeNode(type); } endLine(';'); } visitInvalidExpression(InvalidExpression node) { writeWord('invalid-expression'); if (node.message != null) { writeWord('"${escapeString(node.message!)}"'); } } void _writeDynamicAccessKind(DynamicAccessKind kind) { switch (kind) { case DynamicAccessKind.Dynamic: writeSymbol('{dynamic}.'); break; case DynamicAccessKind.Never: writeSymbol('{Never}.'); break; case DynamicAccessKind.Invalid: writeSymbol('{}.'); break; case DynamicAccessKind.Unresolved: writeSymbol('{}.'); break; } } visitDynamicInvocation(DynamicInvocation node) { writeExpression(node.receiver, Precedence.PRIMARY); _writeDynamicAccessKind(node.kind); writeName( node.name, ); writeNode(node.arguments); } void _writeFunctionAccessKind(FunctionAccessKind kind) { switch (kind) { case FunctionAccessKind.Function: case FunctionAccessKind.FunctionType: break; case FunctionAccessKind.Inapplicable: writeSymbol('{}.'); break; case FunctionAccessKind.Nullable: writeSymbol('{}.'); break; } } visitFunctionInvocation(FunctionInvocation node) { writeExpression(node.receiver, Precedence.PRIMARY); _writeFunctionAccessKind(node.kind); writeNode(node.arguments); if (node.functionType != null) { writeSymbol('{'); writeType(node.functionType!); writeSymbol('}'); } } @override visitLocalFunctionInvocation(LocalFunctionInvocation node) { writeVariableReference(node.variable); writeNode(node.arguments); writeSymbol('{'); writeType(node.functionType); writeSymbol('}'); } void _writeInstanceAccessKind(InstanceAccessKind kind) { switch (kind) { case InstanceAccessKind.Instance: case InstanceAccessKind.Object: break; case InstanceAccessKind.Inapplicable: writeSymbol('{}.'); break; case InstanceAccessKind.Nullable: writeSymbol('{}.'); break; } } visitInstanceInvocation(InstanceInvocation node) { writeExpression(node.receiver, Precedence.PRIMARY); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); _writeInstanceAccessKind(node.kind); writeNode(node.arguments); writeSymbol('{'); writeType(node.functionType); writeSymbol('}'); } visitInstanceGetterInvocation(InstanceGetterInvocation node) { writeExpression(node.receiver, Precedence.PRIMARY); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); _writeInstanceAccessKind(node.kind); writeNode(node.arguments); if (node.functionType != null) { writeSymbol('{'); writeType(node.functionType!); writeSymbol('}'); } } visitEqualsCall(EqualsCall node) { int precedence = Precedence.EQUALITY; writeExpression(node.left, precedence); writeSpace(); writeSymbol('=='); writeInterfaceTarget(Name.equalsName, node.interfaceTargetReference); writeSymbol('{'); writeType(node.functionType); writeSymbol('}'); writeSpace(); writeExpression(node.right, precedence + 1); } visitEqualsNull(EqualsNull node) { writeExpression(node.expression, Precedence.EQUALITY); writeSpace(); writeSymbol('=='); writeSpace(); writeSymbol('null'); } visitMethodInvocation(MethodInvocation node) { writeExpression(node.receiver, Precedence.PRIMARY); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); List flags = []; if (node.isInvariant) { flags.add('Invariant'); } if (node.isBoundsSafe) { flags.add('BoundsSafe'); } if (flags.isNotEmpty) { write('{${flags.join(',')}}'); } writeNode(node.arguments); } visitSuperMethodInvocation(SuperMethodInvocation node) { writeWord('super'); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); writeNode(node.arguments); } visitStaticInvocation(StaticInvocation node) { writeModifier(node.isConst, 'const'); writeMemberReferenceFromReference(node.targetReference); writeNode(node.arguments); } visitConstructorInvocation(ConstructorInvocation node) { writeWord(node.isConst ? 'const' : 'new'); writeMemberReferenceFromReference(node.targetReference); writeNode(node.arguments); } visitNot(Not node) { writeSymbol('!'); writeExpression(node.operand, Precedence.PREFIX); } visitNullCheck(NullCheck node) { writeExpression(node.operand, Precedence.POSTFIX); writeSymbol('!'); } visitLogicalExpression(LogicalExpression node) { int precedence = Precedence.binaryPrecedence[ logicalExpressionOperatorToString(node.operatorEnum)]!; writeExpression(node.left, precedence); writeSpaced(logicalExpressionOperatorToString(node.operatorEnum)); writeExpression(node.right, precedence + 1); } visitConditionalExpression(ConditionalExpression node) { writeExpression(node.condition, Precedence.LOGICAL_OR); ensureSpace(); write('?'); writeStaticType(node.staticType); writeSpace(); writeExpression(node.then); writeSpaced(':'); writeExpression(node.otherwise); } visitStringConcatenation(StringConcatenation node) { if (state == WORD) { writeSpace(); } write('"'); for (Expression part in node.expressions) { if (part is StringLiteral) { writeSymbol(escapeString(part.value)); } else { writeSymbol(r'${'); writeExpression(part); writeSymbol('}'); } } write('"'); state = WORD; } visitListConcatenation(ListConcatenation node) { bool first = true; for (Expression part in node.lists) { if (!first) writeSpaced('+'); writeExpression(part); first = false; } } visitSetConcatenation(SetConcatenation node) { bool first = true; for (Expression part in node.sets) { if (!first) writeSpaced('+'); writeExpression(part); first = false; } } visitMapConcatenation(MapConcatenation node) { bool first = true; for (Expression part in node.maps) { if (!first) writeSpaced('+'); writeExpression(part); first = false; } } visitInstanceCreation(InstanceCreation node) { writeClassReferenceFromReference(node.classReference); if (node.typeArguments.isNotEmpty) { writeSymbol('<'); writeList(node.typeArguments, writeType); writeSymbol('>'); } writeSymbol('{'); bool first = true; node.fieldValues.forEach((Reference fieldRef, Expression value) { if (!first) { writeComma(); } writeWord('${fieldRef.asField.name.text}'); writeSymbol(':'); writeExpression(value); first = false; }); for (AssertStatement assert_ in node.asserts) { if (!first) { writeComma(); } write('assert('); writeExpression(assert_.condition); Expression? message = assert_.message; if (message != null) { writeComma(); writeExpression(message); } write(')'); first = false; } for (Expression unusedArgument in node.unusedArguments) { if (!first) { writeComma(); } writeExpression(unusedArgument); first = false; } writeSymbol('}'); } visitFileUriExpression(FileUriExpression node) { writeExpression(node.expression); } visitIsExpression(IsExpression node) { writeExpression(node.operand, Precedence.BITWISE_OR); writeSpaced( node.isForNonNullableByDefault ? 'is{ForNonNullableByDefault}' : 'is'); writeType(node.type); } visitAsExpression(AsExpression node) { writeExpression(node.operand, Precedence.BITWISE_OR); List flags = []; if (node.isTypeError) { flags.add('TypeError'); } if (node.isCovarianceCheck) { flags.add('CovarianceCheck'); } if (node.isForDynamic) { flags.add('ForDynamic'); } if (node.isForNonNullableByDefault) { flags.add('ForNonNullableByDefault'); } writeSpaced(flags.isNotEmpty ? 'as{${flags.join(',')}}' : 'as'); writeType(node.type); } visitSymbolLiteral(SymbolLiteral node) { writeSymbol('#'); writeWord(node.value); } visitTypeLiteral(TypeLiteral node) { writeType(node.type); } visitThisExpression(ThisExpression node) { writeWord('this'); } visitRethrow(Rethrow node) { writeWord('rethrow'); } visitThrow(Throw node) { writeWord('throw'); writeSpace(); writeExpression(node.expression); } visitListLiteral(ListLiteral node) { if (node.isConst) { writeWord('const'); writeSpace(); } // ignore: unnecessary_null_comparison if (node.typeArgument != null) { writeSymbol('<'); writeType(node.typeArgument); writeSymbol('>'); } writeSymbol('['); writeList(node.expressions, writeNode); writeSymbol(']'); } visitSetLiteral(SetLiteral node) { if (node.isConst) { writeWord('const'); writeSpace(); } // ignore: unnecessary_null_comparison if (node.typeArgument != null) { writeSymbol('<'); writeType(node.typeArgument); writeSymbol('>'); } writeSymbol('{'); writeList(node.expressions, writeNode); writeSymbol('}'); } visitMapLiteral(MapLiteral node) { if (node.isConst) { writeWord('const'); writeSpace(); } // ignore: unnecessary_null_comparison if (node.keyType != null) { writeSymbol('<'); writeList([node.keyType, node.valueType], writeType); writeSymbol('>'); } writeSymbol('{'); writeList(node.entries, writeNode); writeSymbol('}'); } visitMapEntry(MapEntry node) { writeExpression(node.key); writeComma(':'); writeExpression(node.value); } visitAwaitExpression(AwaitExpression node) { writeWord('await'); writeExpression(node.operand); } visitFunctionExpression(FunctionExpression node) { writeFunction(node.function, terminateLine: false); } visitStringLiteral(StringLiteral node) { writeWord('"${escapeString(node.value)}"'); } visitIntLiteral(IntLiteral node) { writeWord('${node.value}'); } visitDoubleLiteral(DoubleLiteral node) { writeWord('${node.value}'); } visitBoolLiteral(BoolLiteral node) { writeWord('${node.value}'); } visitNullLiteral(NullLiteral node) { writeWord('null'); } visitLet(Let node) { writeWord('let'); writeVariableDeclaration(node.variable); writeSpaced('in'); writeExpression(node.body); } visitBlockExpression(BlockExpression node) { writeSpaced('block'); writeBlockBody(node.body.statements, asExpression: true); writeSymbol(' =>'); writeExpression(node.value); } visitInstantiation(Instantiation node) { writeExpression(node.expression); writeSymbol('<'); writeList(node.typeArguments, writeType); writeSymbol('>'); } visitLoadLibrary(LoadLibrary node) { writeWord('LoadLibrary'); writeSymbol('('); writeWord(node.import.name!); writeSymbol(')'); state = WORD; } visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) { writeWord('CheckLibraryIsLoaded'); writeSymbol('('); writeWord(node.import.name!); writeSymbol(')'); state = WORD; } visitLibraryPart(LibraryPart node) { writeAnnotationList(node.annotations); writeIndentation(); writeWord('part'); writeWord(node.partUri); endLine(";"); } visitLibraryDependency(LibraryDependency node) { writeIndentation(); writeWord(node.isImport ? 'import' : 'export'); String uriString; if (node.importedLibraryReference.node != null) { uriString = '${node.targetLibrary.importUri}'; } else { uriString = '${node.importedLibraryReference.canonicalName?.name}'; } writeWord('"$uriString"'); if (node.isDeferred) { writeWord('deferred'); } String? name = node.name; if (name != null) { writeWord('as'); writeWord(name); } String? last; final String show = 'show'; final String hide = 'hide'; if (node.combinators.isNotEmpty) { for (Combinator combinator in node.combinators) { if (combinator.isShow && last != show) { last = show; writeWord(show); } else if (combinator.isHide && last != hide) { last = hide; writeWord(hide); } bool first = true; for (String name in combinator.names) { if (!first) writeComma(); writeWord(name); first = false; } } } endLine(';'); } defaultExpression(Expression node) { writeWord('${node.runtimeType}'); } visitVariableGet(VariableGet node) { writeVariableReference(node.variable); DartType? promotedType = node.promotedType; if (promotedType != null) { writeSymbol('{'); writeNode(promotedType); writeSymbol('}'); state = WORD; } } visitVariableSet(VariableSet node) { writeVariableReference(node.variable); writeSpaced('='); writeExpression(node.value); } void writeInterfaceTarget(Name name, Reference? target) { if (target != null) { writeSymbol('{'); writeMemberReferenceFromReference(target); writeSymbol('}'); } else { writeName(name); } } void writeStaticType(DartType type) { // ignore: unnecessary_null_comparison if (type != null) { writeSymbol('{'); writeType(type); writeSymbol('}'); } } visitDynamicGet(DynamicGet node) { writeExpression(node.receiver, Precedence.PRIMARY); _writeDynamicAccessKind(node.kind); writeName(node.name); } visitFunctionTearOff(FunctionTearOff node) { writeExpression(node.receiver, Precedence.PRIMARY); writeSymbol('.'); writeSymbol('call'); } visitInstanceGet(InstanceGet node) { writeExpression(node.receiver, Precedence.PRIMARY); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); _writeInstanceAccessKind(node.kind); writeSymbol('{'); writeType(node.resultType); writeSymbol('}'); } visitInstanceTearOff(InstanceTearOff node) { writeExpression(node.receiver, Precedence.PRIMARY); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); _writeInstanceAccessKind(node.kind); writeSymbol('{'); writeType(node.resultType); writeSymbol('}'); } visitPropertyGet(PropertyGet node) { writeExpression(node.receiver, Precedence.PRIMARY); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); } visitDynamicSet(DynamicSet node) { writeExpression(node.receiver, Precedence.PRIMARY); _writeDynamicAccessKind(node.kind); writeName(node.name); writeSpaced('='); writeExpression(node.value); } visitInstanceSet(InstanceSet node) { writeExpression(node.receiver, Precedence.PRIMARY); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); _writeInstanceAccessKind(node.kind); writeSpaced('='); writeExpression(node.value); } visitPropertySet(PropertySet node) { writeExpression(node.receiver, Precedence.PRIMARY); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); writeSpaced('='); writeExpression(node.value); } visitSuperPropertyGet(SuperPropertyGet node) { writeWord('super'); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); } visitSuperPropertySet(SuperPropertySet node) { writeWord('super'); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); writeSpaced('='); writeExpression(node.value); } visitStaticTearOff(StaticTearOff node) { writeMemberReferenceFromReference(node.targetReference); } visitStaticGet(StaticGet node) { writeMemberReferenceFromReference(node.targetReference); } visitStaticSet(StaticSet node) { writeMemberReferenceFromReference(node.targetReference); writeSpaced('='); writeExpression(node.value); } visitExpressionStatement(ExpressionStatement node) { writeIndentation(); writeExpression(node.expression); endLine(';'); } void writeBlockBody(List statements, {bool asExpression = false}) { if (statements.isEmpty) { asExpression ? writeSymbol('{}') : endLine('{}'); return; } endLine('{'); ++indentation; statements.forEach(writeNode); --indentation; writeIndentation(); asExpression ? writeSymbol('}') : endLine('}'); } visitBlock(Block node) { writeIndentation(); writeBlockBody(node.statements); } visitAssertBlock(AssertBlock node) { writeIndentation(); writeSpaced('assert'); writeBlockBody(node.statements); } visitEmptyStatement(EmptyStatement node) { writeIndentation(); endLine(';'); } visitAssertStatement(AssertStatement node, {bool asExpression = false}) { if (!asExpression) { writeIndentation(); } writeWord('assert'); writeSymbol('('); writeExpression(node.condition); Expression? message = node.message; if (message != null) { writeComma(); writeExpression(message); } if (!asExpression) { endLine(');'); } else { writeSymbol(')'); } } visitLabeledStatement(LabeledStatement node) { writeIndentation(); writeWord(syntheticNames.nameLabeledStatement(node)); endLine(':'); writeNode(node.body); } visitBreakStatement(BreakStatement node) { writeIndentation(); writeWord('break'); writeWord(syntheticNames.nameLabeledStatement(node.target)); endLine(';'); } visitWhileStatement(WhileStatement node) { writeIndentation(); writeSpaced('while'); writeSymbol('('); writeExpression(node.condition); writeSymbol(')'); writeBody(node.body); } visitDoStatement(DoStatement node) { writeIndentation(); writeWord('do'); writeBody(node.body); writeIndentation(); writeSpaced('while'); writeSymbol('('); writeExpression(node.condition); endLine(')'); } visitForStatement(ForStatement node) { writeIndentation(); writeSpaced('for'); writeSymbol('('); writeList(node.variables, writeVariableDeclaration); writeComma(';'); Expression? condition = node.condition; if (condition != null) { writeExpression(condition); } writeComma(';'); writeList(node.updates, writeExpression); writeSymbol(')'); writeBody(node.body); } visitForInStatement(ForInStatement node) { writeIndentation(); if (node.isAsync) { writeSpaced('await'); } writeSpaced('for'); writeSymbol('('); writeVariableDeclaration(node.variable, useVarKeyword: true); writeSpaced('in'); writeExpression(node.iterable); writeSymbol(')'); writeBody(node.body); } visitSwitchStatement(SwitchStatement node) { writeIndentation(); writeWord('switch'); writeSymbol('('); writeExpression(node.expression); endLine(') {'); ++indentation; node.cases.forEach(writeNode); --indentation; writeIndentation(); endLine('}'); } visitSwitchCase(SwitchCase node) { String label = syntheticNames.nameSwitchCase(node); writeIndentation(); writeWord(label); endLine(':'); for (Expression expression in node.expressions) { writeIndentation(); writeWord('case'); writeExpression(expression); endLine(':'); } if (node.isDefault) { writeIndentation(); writeWord('default'); endLine(':'); } ++indentation; writeNode(node.body); --indentation; } visitContinueSwitchStatement(ContinueSwitchStatement node) { writeIndentation(); writeWord('continue'); writeWord(syntheticNames.nameSwitchCase(node.target)); endLine(';'); } visitIfStatement(IfStatement node) { writeIndentation(); writeWord('if'); writeSymbol('('); writeExpression(node.condition); writeSymbol(')'); writeBody(node.then); Statement? otherwise = node.otherwise; if (otherwise != null) { writeIndentation(); writeWord('else'); writeBody(otherwise); } } visitReturnStatement(ReturnStatement node) { writeIndentation(); writeWord('return'); Expression? expression = node.expression; if (expression != null) { writeSpace(); writeExpression(expression); } endLine(';'); } visitTryCatch(TryCatch node) { writeIndentation(); writeWord('try'); writeBody(node.body); node.catches.forEach(writeNode); } visitCatch(Catch node) { writeIndentation(); // ignore: unnecessary_null_comparison if (node.guard != null) { writeWord('on'); writeType(node.guard); writeSpace(); } writeWord('catch'); writeSymbol('('); VariableDeclaration? exception = node.exception; if (exception != null) { writeVariableDeclaration(exception); } else { writeWord('no-exception-var'); } VariableDeclaration? stackTrace = node.stackTrace; if (stackTrace != null) { writeComma(); writeVariableDeclaration(stackTrace); } writeSymbol(')'); writeBody(node.body); } visitTryFinally(TryFinally node) { writeIndentation(); writeWord('try'); writeBody(node.body); writeIndentation(); writeWord('finally'); writeBody(node.finalizer); } visitYieldStatement(YieldStatement node) { writeIndentation(); if (node.isYieldStar) { writeWord('yield*'); } else if (node.isNative) { writeWord('[yield]'); } else { writeWord('yield'); } writeExpression(node.expression); endLine(';'); } visitVariableDeclaration(VariableDeclaration node) { writeIndentation(); writeVariableDeclaration(node, useVarKeyword: true); endLine(';'); } visitFunctionDeclaration(FunctionDeclaration node) { writeAnnotationList(node.variable.annotations); writeIndentation(); writeWord('function'); // ignore: unnecessary_null_comparison if (node.function != null) { writeFunction(node.function, name: getVariableName(node.variable)); } else { writeWord(getVariableName(node.variable)); endLine('...;'); } } void writeVariableDeclaration(VariableDeclaration node, {bool useVarKeyword: false}) { if (showOffsets) writeWord("[${node.fileOffset}]"); if (showMetadata) writeMetadata(node); writeAnnotationList(node.annotations, separateLines: false); writeModifier(node.isLowered, 'lowered'); writeModifier(node.isLate, 'late'); writeModifier(node.isRequired, 'required'); writeModifier(node.isCovariant, 'covariant'); writeModifier(node.isGenericCovariantImpl, 'generic-covariant-impl'); writeModifier(node.isFinal, 'final'); writeModifier(node.isConst, 'const'); // ignore: unnecessary_null_comparison if (node.type != null) { writeAnnotatedType(node.type, annotator?.annotateVariable(this, node)); } // ignore: unnecessary_null_comparison if (useVarKeyword && !node.isFinal && !node.isConst && node.type == null) { writeWord('var'); } writeWord(getVariableName(node)); Expression? initializer = node.initializer; if (initializer != null) { writeSpaced('='); writeExpression(initializer); } } visitArguments(Arguments node) { if (node.types.isNotEmpty) { writeSymbol('<'); writeList(node.types, writeType); writeSymbol('>'); } writeSymbol('('); Iterable allArgs = >[node.positional, node.named].expand((x) => x); writeList(allArgs, writeNode); writeSymbol(')'); } visitNamedExpression(NamedExpression node) { writeWord(node.name); writeComma(':'); writeExpression(node.value); } defaultStatement(Statement node) { writeIndentation(); endLine('${node.runtimeType}'); } visitInvalidInitializer(InvalidInitializer node) { writeWord('invalid-initializer'); } visitFieldInitializer(FieldInitializer node) { writeMemberReferenceFromReference(node.fieldReference); writeSpaced('='); writeExpression(node.value); } visitSuperInitializer(SuperInitializer node) { writeWord('super'); writeMemberReferenceFromReference(node.targetReference); writeNode(node.arguments); } visitRedirectingInitializer(RedirectingInitializer node) { writeWord('this'); writeMemberReferenceFromReference(node.targetReference); writeNode(node.arguments); } visitLocalInitializer(LocalInitializer node) { writeVariableDeclaration(node.variable); } visitAssertInitializer(AssertInitializer node) { visitAssertStatement(node.statement, asExpression: true); } defaultInitializer(Initializer node) { writeIndentation(); endLine(': ${node.runtimeType}'); } void writeNullability(Nullability nullability, {bool inComment = false}) { switch (nullability) { case Nullability.legacy: writeSymbol('*'); if (!inComment) { state = WORD; // Disallow a word immediately after the '*'. } break; case Nullability.nullable: writeSymbol('?'); if (!inComment) { state = WORD; // Disallow a word immediately after the '?'. } break; case Nullability.undetermined: writeSymbol('%'); if (!inComment) { state = WORD; // Disallow a word immediately after the '%'. } break; case Nullability.nonNullable: if (inComment) { writeSymbol("!"); } break; } } void writeDartTypeNullability(DartType type, {bool inComment = false}) { if (type is InvalidType) { writeNullability(Nullability.undetermined); } else { writeNullability(type.nullability, inComment: inComment); } } visitInvalidType(InvalidType node) { writeWord('invalid-type'); } visitDynamicType(DynamicType node) { writeWord('dynamic'); } visitVoidType(VoidType node) { writeWord('void'); } visitNeverType(NeverType node) { writeWord('Never'); writeNullability(node.nullability); } visitNullType(NullType node) { writeWord('Null'); } visitInterfaceType(InterfaceType node) { writeClassReferenceFromReference(node.className); if (node.typeArguments.isNotEmpty) { writeSymbol('<'); writeList(node.typeArguments, writeType); writeSymbol('>'); state = WORD; // Disallow a word immediately after the '>'. } writeNullability(node.nullability); } visitExtensionType(ExtensionType node) { writeExtensionReferenceFromReference(node.extensionReference); if (node.typeArguments.isNotEmpty) { writeSymbol('<'); writeList(node.typeArguments, writeType); writeSymbol('>'); state = Printer.WORD; } writeNullability(node.declaredNullability); } visitFutureOrType(FutureOrType node) { writeWord('FutureOr'); writeSymbol('<'); writeNode(node.typeArgument); writeSymbol('>'); writeNullability(node.declaredNullability); } visitFunctionType(FunctionType node) { writeFunctionType(node); } visitNamedType(NamedType node) { writeModifier(node.isRequired, 'required'); writeWord(node.name); writeSymbol(':'); writeSpace(); writeType(node.type); } visitTypeParameterType(TypeParameterType node) { writeTypeParameterReference(node.parameter); writeNullability(node.declaredNullability); DartType? promotedBound = node.promotedBound; if (promotedBound != null) { writeSpaced('&'); writeType(promotedBound); writeWord("/* '"); writeNullability(node.declaredNullability, inComment: true); writeWord("' & '"); writeDartTypeNullability(promotedBound, inComment: true); writeWord("' = '"); writeNullability(node.nullability, inComment: true); writeWord("' */"); } } visitTypeParameter(TypeParameter node) { writeModifier(node.isGenericCovariantImpl, 'generic-covariant-impl'); writeAnnotationList(node.annotations, separateLines: false); if (node.variance != Variance.covariant) { writeWord(const [ "unrelated", "covariant", "contravariant", "invariant" ][node.variance]); } writeWord(getTypeParameterName(node)); writeSpaced('extends'); writeType(node.bound); // ignore: unnecessary_null_comparison if (node.defaultType != node.bound) { writeSpaced('='); writeType(node.defaultType); } } void writeConstantReference(Constant node) { writeWord(syntheticNames.nameConstant(node)); } visitConstantExpression(ConstantExpression node) { writeConstantReference(node.constant); } defaultConstant(Constant node) { writeIndentation(); writeConstantReference(node); writeSpaced('='); endLine('${node.runtimeType}'); } visitNullConstant(NullConstant node) { writeIndentation(); writeConstantReference(node); writeSpaced('='); endLine('${node.value}'); } visitBoolConstant(BoolConstant node) { writeIndentation(); writeConstantReference(node); writeSpaced('='); endLine('${node.value}'); } visitIntConstant(IntConstant node) { writeIndentation(); writeConstantReference(node); writeSpaced('='); endLine('${node.value}'); } visitDoubleConstant(DoubleConstant node) { writeIndentation(); writeConstantReference(node); writeSpaced('='); endLine('${node.value}'); } visitSymbolConstant(SymbolConstant node) { writeIndentation(); writeConstantReference(node); writeSpaced('='); Reference? libraryReference = node.libraryReference; String text = libraryReference != null ? '#${libraryReference.asLibrary.importUri}::${node.name}' : '#${node.name}'; endLine('${text}'); } visitListConstant(ListConstant node) { writeIndentation(); writeConstantReference(node); writeSpaced('='); writeSymbol('<'); writeType(node.typeArgument); writeSymbol('>['); writeList(node.entries, writeConstantReference); endLine(']'); } visitSetConstant(SetConstant node) { writeIndentation(); writeConstantReference(node); writeSpaced('='); writeSymbol('<'); writeType(node.typeArgument); writeSymbol('>{'); writeList(node.entries, writeConstantReference); endLine('}'); } visitMapConstant(MapConstant node) { writeIndentation(); writeConstantReference(node); writeSpaced('='); writeSymbol('<'); writeList([node.keyType, node.valueType], writeType); writeSymbol('>{'); writeList(node.entries, (ConstantMapEntry entry) { writeConstantReference(entry.key); writeSymbol(':'); writeConstantReference(entry.value); }); endLine(')'); } visitTypeLiteralConstant(TypeLiteralConstant node) { writeIndentation(); writeConstantReference(node); writeSpaced('='); writeWord('${node.runtimeType}'); writeSymbol('('); writeNode(node.type); endLine(')'); } visitInstanceConstant(InstanceConstant node) { writeIndentation(); writeConstantReference(node); writeSpaced('='); writeClassReferenceFromReference(node.classReference); if (!node.typeArguments.isEmpty) { writeSymbol('<'); writeList(node.typeArguments, writeType); writeSymbol('>'); } writeSymbol(' {'); writeList(node.fieldValues.entries, (core.MapEntry entry) { if (entry.key.node != null) { writeWord('${entry.key.asField.name.text}'); } else { writeWord('${entry.key.canonicalName!.name}'); } writeSymbol(':'); writeConstantReference(entry.value); }); endLine('}'); } visitPartialInstantiationConstant(PartialInstantiationConstant node) { writeIndentation(); writeConstantReference(node); writeSpaced('='); writeWord('partial-instantiation'); writeSpace(); writeMemberReferenceFromReference(node.tearOffConstant.procedureReference); writeSpace(); writeSymbol('<'); writeList(node.types, writeType); writeSymbol('>'); endLine(); } visitStringConstant(StringConstant node) { writeIndentation(); writeConstantReference(node); writeSpaced('='); endLine('"${escapeString(node.value)}"'); } visitTearOffConstant(TearOffConstant node) { writeIndentation(); writeConstantReference(node); writeSpaced('='); writeWord('tearoff'); writeSpace(); writeMemberReferenceFromReference(node.procedureReference); endLine(); } visitUnevaluatedConstant(UnevaluatedConstant node) { writeIndentation(); writeConstantReference(node); writeSpaced('='); writeSymbol('eval'); writeSpace(); writeExpression(node.expression); endLine(); } defaultNode(Node node) { write('<${node.runtimeType}>'); } } class Precedence implements ExpressionVisitor { static final Precedence instance = new Precedence(); static int of(Expression node) => node.accept(instance); static const int EXPRESSION = 1; static const int CONDITIONAL = 2; static const int LOGICAL_NULL_AWARE = 3; static const int LOGICAL_OR = 4; static const int LOGICAL_AND = 5; static const int EQUALITY = 6; static const int RELATIONAL = 7; static const int BITWISE_OR = 8; static const int BITWISE_XOR = 9; static const int BITWISE_AND = 10; static const int SHIFT = 11; static const int ADDITIVE = 12; static const int MULTIPLICATIVE = 13; static const int PREFIX = 14; static const int POSTFIX = 15; static const int TYPE_LITERAL = 19; static const int PRIMARY = 20; static const int CALLEE = 21; static const Map binaryPrecedence = const { '&&': LOGICAL_AND, '||': LOGICAL_OR, '??': LOGICAL_NULL_AWARE, '==': EQUALITY, '!=': EQUALITY, '>': RELATIONAL, '>=': RELATIONAL, '<': RELATIONAL, '<=': RELATIONAL, '|': BITWISE_OR, '^': BITWISE_XOR, '&': BITWISE_AND, '>>': SHIFT, '<<': SHIFT, '+': ADDITIVE, '-': ADDITIVE, '*': MULTIPLICATIVE, '%': MULTIPLICATIVE, '/': MULTIPLICATIVE, '~/': MULTIPLICATIVE, null: EXPRESSION, }; static bool isAssociativeBinaryOperator(int precedence) { return precedence != EQUALITY && precedence != RELATIONAL; } @override int defaultExpression(Expression node) => EXPRESSION; @override int visitInvalidExpression(InvalidExpression node) => CALLEE; @override int visitMethodInvocation(MethodInvocation node) => CALLEE; @override int visitInstanceInvocation(InstanceInvocation node) => CALLEE; @override int visitInstanceGetterInvocation(InstanceGetterInvocation node) => CALLEE; @override int visitDynamicInvocation(DynamicInvocation node) => CALLEE; @override int visitFunctionInvocation(FunctionInvocation node) => CALLEE; @override int visitLocalFunctionInvocation(LocalFunctionInvocation node) => CALLEE; @override int visitEqualsCall(EqualsCall node) => EQUALITY; @override int visitEqualsNull(EqualsNull node) => EQUALITY; @override int visitSuperMethodInvocation(SuperMethodInvocation node) => CALLEE; @override int visitStaticInvocation(StaticInvocation node) => CALLEE; @override int visitConstructorInvocation(ConstructorInvocation node) => CALLEE; @override int visitNot(Not node) => PREFIX; @override int visitNullCheck(NullCheck node) => PRIMARY; @override int visitLogicalExpression(LogicalExpression node) => binaryPrecedence[logicalExpressionOperatorToString(node.operatorEnum)]!; @override int visitConditionalExpression(ConditionalExpression node) => CONDITIONAL; @override int visitStringConcatenation(StringConcatenation node) => PRIMARY; @override int visitIsExpression(IsExpression node) => RELATIONAL; @override int visitAsExpression(AsExpression node) => RELATIONAL; @override int visitSymbolLiteral(SymbolLiteral node) => PRIMARY; @override int visitTypeLiteral(TypeLiteral node) => PRIMARY; @override int visitThisExpression(ThisExpression node) => CALLEE; @override int visitRethrow(Rethrow node) => PRIMARY; @override int visitThrow(Throw node) => EXPRESSION; @override int visitListLiteral(ListLiteral node) => PRIMARY; @override int visitSetLiteral(SetLiteral node) => PRIMARY; @override int visitMapLiteral(MapLiteral node) => PRIMARY; @override int visitAwaitExpression(AwaitExpression node) => PREFIX; @override int visitFunctionExpression(FunctionExpression node) => EXPRESSION; @override int visitStringLiteral(StringLiteral node) => CALLEE; @override int visitIntLiteral(IntLiteral node) => CALLEE; @override int visitDoubleLiteral(DoubleLiteral node) => CALLEE; @override int visitBoolLiteral(BoolLiteral node) => CALLEE; @override int visitNullLiteral(NullLiteral node) => CALLEE; @override int visitVariableGet(VariableGet node) => PRIMARY; @override int visitVariableSet(VariableSet node) => EXPRESSION; @override int visitPropertyGet(PropertyGet node) => PRIMARY; @override int visitInstanceGet(InstanceGet node) => PRIMARY; @override int visitDynamicGet(DynamicGet node) => PRIMARY; @override int visitInstanceTearOff(InstanceTearOff node) => PRIMARY; @override int visitFunctionTearOff(FunctionTearOff node) => PRIMARY; @override int visitPropertySet(PropertySet node) => EXPRESSION; @override int visitSuperPropertyGet(SuperPropertyGet node) => PRIMARY; @override int visitSuperPropertySet(SuperPropertySet node) => EXPRESSION; @override int visitStaticGet(StaticGet node) => PRIMARY; @override int visitStaticTearOff(StaticTearOff node) => PRIMARY; @override int visitStaticSet(StaticSet node) => EXPRESSION; @override int visitLet(Let node) => EXPRESSION; @override int defaultBasicLiteral(BasicLiteral node) => CALLEE; @override int visitBlockExpression(BlockExpression node) => EXPRESSION; @override int visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) => EXPRESSION; @override int visitConstantExpression(ConstantExpression node) => EXPRESSION; @override int visitDynamicSet(DynamicSet node) => EXPRESSION; @override int visitFileUriExpression(FileUriExpression node) => EXPRESSION; @override int visitInstanceCreation(InstanceCreation node) => EXPRESSION; @override int visitInstanceSet(InstanceSet node) => EXPRESSION; @override int visitInstantiation(Instantiation node) => EXPRESSION; @override int visitListConcatenation(ListConcatenation node) => EXPRESSION; @override int visitLoadLibrary(LoadLibrary node) => EXPRESSION; @override int visitMapConcatenation(MapConcatenation node) => EXPRESSION; @override int visitSetConcatenation(SetConcatenation node) => EXPRESSION; } String procedureKindToString(ProcedureKind kind) { switch (kind) { case ProcedureKind.Method: return 'method'; case ProcedureKind.Getter: return 'get'; case ProcedureKind.Setter: return 'set'; case ProcedureKind.Operator: return 'operator'; case ProcedureKind.Factory: return 'factory'; } }