| // 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'; |
| import 'dart:convert' show json; |
| |
| import '../ast.dart'; |
| import '../import_table.dart'; |
| import '../src/text_util.dart'; |
| |
| abstract class Namer<T> { |
| int index = 0; |
| final Map<T, String> map = <T, String>{}; |
| |
| String getName(T key) => map.putIfAbsent(key, () => '$prefix${++index}'); |
| |
| String get prefix; |
| } |
| |
| class NormalNamer<T> extends Namer<T> { |
| @override |
| final String prefix; |
| |
| NormalNamer(this.prefix); |
| } |
| |
| class ConstantNamer extends RecursiveVisitor with Namer<Constant> { |
| @override |
| final String prefix; |
| |
| ConstantNamer(this.prefix); |
| |
| @override |
| 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<name>(<name>)` / `visit<name>Reference(<name>)`. |
| // |
| // 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 StaticTearOffConstant) { |
| // 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); |
| } |
| |
| @override |
| void defaultConstantReference(Constant constant) { |
| getName(constant); |
| } |
| |
| @override |
| void defaultDartType(DartType type) { |
| // No need to recurse into dart types, we only care about naming the |
| // constants themselves. |
| } |
| } |
| |
| class Disambiguator<T, U> { |
| final Map<T, String> namesT = <T, String>{}; |
| final Map<U, String> namesU = <U, String>{}; |
| final Set<String> usedNames = new Set<String>(); |
| |
| 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<VariableDeclaration> variables = |
| new NormalNamer<VariableDeclaration>('#t'); |
| final Namer<Member> members = new NormalNamer<Member>('#m'); |
| final Namer<Class> classes = new NormalNamer<Class>('#class'); |
| final Namer<Extension> extensions = new NormalNamer<Extension>('#extension'); |
| final Namer<Library> libraries = new NormalNamer<Library>('#lib'); |
| final Namer<TypeParameter> typeParameters = |
| new NormalNamer<TypeParameter>('#T'); |
| final Namer<TreeNode> labels = new NormalNamer<TreeNode>('#L'); |
| final Namer<Constant> constants = new ConstantNamer('#C'); |
| final Disambiguator<Reference, CanonicalName> prefixes = |
| new Disambiguator<Reference, CanonicalName>(); |
| |
| String nameVariable(VariableDeclaration node) => variables.getName(node); |
| String nameMember(Member node) => members.getName(node); |
| String nameClass(Class node) => classes.getName(node); |
| String nameExtension(Extension node) => extensions.getName(node); |
| String nameLibrary(Library node) => libraries.getName(node); |
| String nameTypeParameter(TypeParameter node) => typeParameters.getName(node); |
| String nameSwitchCase(SwitchCase node) => labels.getName(node); |
| String nameLabeledStatement(LabeledStatement node) => labels.getName(node); |
| String 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'; |
| }); |
| } |
| |
| String 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<void> with VisitorVoidMixin { |
| final NameSystem syntheticNames; |
| final StringSink sink; |
| final Annotator? annotator; |
| final Map<String, MetadataRepository<dynamic>>? 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<String, MetadataRepository<dynamic>>? 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 '<No Library>'; |
| 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 '<No Class>'; |
| String name = getClassName(node); |
| String library = getLibraryReference(node.enclosingLibrary); |
| return '$library::$name'; |
| } |
| |
| String getExtensionReference(Extension node) { |
| // ignore: unnecessary_null_comparison |
| if (node == null) return '<No Extension>'; |
| String name = getExtensionName(node); |
| String library = getLibraryReference(node.enclosingLibrary); |
| return '$library::$name'; |
| } |
| |
| String getTypedefReference(Typedef node) { |
| // ignore: unnecessary_null_comparison |
| if (node == null) return '<No Typedef>'; |
| 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 '<No Member>'; |
| 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 '<No VariableDeclaration>'; |
| 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 '<No TypeParameter>'; |
| 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<String>? problemsAsJson) { |
| if (problemsAsJson != null && problemsAsJson.isNotEmpty) { |
| endLine("//"); |
| write("// "); |
| write(header); |
| endLine(":"); |
| endLine("//"); |
| for (String s in problemsAsJson) { |
| Map<String, dynamic> decoded = json.decode(s); |
| List<dynamic> plainTextFormatted = |
| decoded["plainTextFormatted"] as List<dynamic>; |
| List<String> 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); |
| } |
| List<String> flags = []; |
| if (library.isUnsupported) { |
| flags.add('isUnsupported'); |
| } |
| if (library.isNonNullableByDefault) { |
| flags.add('isNonNullableByDefault'); |
| } |
| if (flags.isNotEmpty) { |
| writeWord('/*${flags.join(',')}*/'); |
| } |
| 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<Reference> 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("<Null>"); |
| } 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<dynamic> 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('<No DartType>'); |
| } else { |
| type.accept(this); |
| } |
| } |
| |
| void writeOptionalType(DartType type) { |
| // ignore: unnecessary_null_comparison |
| if (type != null) { |
| type.accept(this); |
| } |
| } |
| |
| @override |
| void visitSupertype(Supertype type) { |
| // ignore: unnecessary_null_comparison |
| if (type == null) { |
| write('<No Supertype>'); |
| } else { |
| writeClassReferenceFromReference(type.className); |
| if (type.typeArguments.isNotEmpty) { |
| writeSymbol('<'); |
| writeList(type.typeArguments, writeType); |
| writeSymbol('>'); |
| } |
| } |
| } |
| |
| @override |
| void 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<Initializer>? 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 '<Invalid async marker: $marker>'; |
| } |
| } |
| |
| 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); |
| } |
| } |
| |
| void writeFunctionType(FunctionType node, |
| {List<VariableDeclaration>? typedefPositional, |
| List<VariableDeclaration>? typedefNamed}) { |
| if (state == WORD) { |
| ensureSpace(); |
| } |
| writeTypeParameterList(node.typeParameters); |
| writeSymbol('('); |
| List<DartType> 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<TypeParameter> typeParameters) { |
| if (typeParameters.isEmpty) return; |
| writeSymbol('<'); |
| writeList(typeParameters, writeNode); |
| writeSymbol('>'); |
| state = WORD; // Ensure space if not followed by another symbol. |
| } |
| |
| void writeParameterList(List<VariableDeclaration> positional, |
| List<VariableDeclaration> 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<T>(Iterable<T> 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 '<No Class>'; |
| 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 '<No Extension>'; |
| 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 '<No Member>'; |
| 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<Expression> nodes, {bool separateLines: true}) { |
| for (Expression node in nodes) { |
| if (separateLines) { |
| writeIndentation(); |
| } |
| writeAnnotation(node); |
| if (separateLines) { |
| endLine(); |
| } else { |
| writeSpace(); |
| } |
| } |
| } |
| |
| @override |
| void visitLibrary(Library node) {} |
| |
| @override |
| void visitField(Field node) { |
| writeAnnotationList(node.annotations); |
| writeIndentation(); |
| writeModifier(node.isLate, 'late'); |
| writeModifier(node.isStatic, 'static'); |
| writeModifier(node.isCovariantByDeclaration, 'covariant-by-declaration'); |
| writeModifier(node.isCovariantByClass, 'covariant-by-class'); |
| 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<String> features = <String>[]; |
| 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(';'); |
| } |
| |
| @override |
| void 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<String> features = <String>[]; |
| 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(',')}*/"); |
| } |
| if (node.signatureType != null) { |
| writeWord('/* signature-type:'); |
| writeType(node.signatureType!); |
| writeWord('*/'); |
| } |
| 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; |
| } |
| } |
| |
| @override |
| void visitConstructor(Constructor node) { |
| writeAnnotationList(node.annotations); |
| writeIndentation(); |
| writeModifier(node.isExternal, 'external'); |
| writeModifier(node.isConst, 'const'); |
| writeModifier(node.isSynthetic, 'synthetic'); |
| writeWord('constructor'); |
| List<String> features = <String>[]; |
| 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); |
| } |
| |
| @override |
| void visitRedirectingFactory(RedirectingFactory node) { |
| writeAnnotationList(node.annotations); |
| writeIndentation(); |
| writeModifier(node.isExternal, 'external'); |
| writeModifier(node.isConst, 'const'); |
| writeWord('redirecting_factory'); |
| writeFunction(node.function, name: node.name); |
| writeSpaced('='); |
| writeMemberReferenceFromReference(node.targetReference!); |
| if (node.typeArguments.isNotEmpty) { |
| writeSymbol('<'); |
| writeList(node.typeArguments, writeType); |
| writeSymbol('>'); |
| } |
| List<String> features = <String>[]; |
| if (node.enclosingLibrary.isNonNullableByDefault != |
| node.isNonNullableByDefault) { |
| if (node.isNonNullableByDefault) { |
| features.add("isNonNullableByDefault"); |
| } else { |
| features.add("isLegacy"); |
| } |
| } |
| if (features.isNotEmpty) { |
| writeWord("/*${features.join(',')}*/"); |
| } |
| endLine(';'); |
| } |
| |
| @override |
| void visitClass(Class node) { |
| writeAnnotationList(node.annotations); |
| writeIndentation(); |
| writeModifier(node.isAbstract, 'abstract'); |
| writeModifier(node.isMacro, 'macro'); |
| 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<String> features = <String>[]; |
| 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.redirectingFactories.forEach(writeNode); |
| --indentation; |
| writeIndentation(); |
| endLine('}'); |
| } |
| |
| @override |
| void visitExtension(Extension node) { |
| writeAnnotationList(node.annotations); |
| writeIndentation(); |
| writeWord('extension'); |
| if (node.isExtensionTypeDeclaration) { |
| writeWord('type'); |
| } |
| writeWord(getExtensionName(node)); |
| writeTypeParameterList(node.typeParameters); |
| writeSpaced('on'); |
| writeType(node.onType); |
| |
| ExtensionTypeShowHideClause? showHideClause = node.showHideClause; |
| if (showHideClause != null) { |
| // 'Show' clause elements. |
| if (showHideClause.shownSupertypes.isNotEmpty) { |
| writeSpaced('show-types'); |
| writeList(showHideClause.shownSupertypes, visitSupertype); |
| } |
| if (showHideClause.shownMethods.isNotEmpty) { |
| writeSpaced('show-methods'); |
| writeList( |
| showHideClause.shownMethods, writeMemberReferenceFromReference); |
| } |
| if (showHideClause.shownGetters.isNotEmpty) { |
| writeSpaced('show-getters'); |
| writeList( |
| showHideClause.shownGetters, writeMemberReferenceFromReference); |
| } |
| if (showHideClause.shownSetters.isNotEmpty) { |
| writeSpaced('show-setters'); |
| writeList( |
| showHideClause.shownSetters, writeMemberReferenceFromReference); |
| } |
| if (showHideClause.shownOperators.isNotEmpty) { |
| writeSpaced('show-operators'); |
| writeList( |
| showHideClause.shownOperators, writeMemberReferenceFromReference); |
| } |
| |
| // 'Hide' clause elements. |
| if (showHideClause.hiddenSupertypes.isNotEmpty) { |
| writeSpaced('hide-types'); |
| writeList(showHideClause.hiddenSupertypes, visitSupertype); |
| } |
| if (showHideClause.hiddenMethods.isNotEmpty) { |
| writeSpaced('hide-methods'); |
| writeList( |
| showHideClause.hiddenMethods, writeMemberReferenceFromReference); |
| } |
| if (showHideClause.hiddenGetters.isNotEmpty) { |
| writeSpaced('hide-getters'); |
| writeList( |
| showHideClause.hiddenGetters, writeMemberReferenceFromReference); |
| } |
| if (showHideClause.hiddenSetters.isNotEmpty) { |
| writeSpaced('hide-setters'); |
| writeList( |
| showHideClause.hiddenSetters, writeMemberReferenceFromReference); |
| } |
| if (showHideClause.hiddenOperators.isNotEmpty) { |
| writeSpaced('hide-operators'); |
| writeList( |
| showHideClause.hiddenOperators, writeMemberReferenceFromReference); |
| } |
| } |
| |
| 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('}'); |
| } |
| |
| @override |
| void 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(';'); |
| } |
| |
| @override |
| void visitInvalidExpression(InvalidExpression node) { |
| writeWord('invalid-expression'); |
| if (node.message != null) { |
| writeWord('"${escapeString(node.message!)}"'); |
| } |
| if (node.expression != null) { |
| writeSpaced('in'); |
| writeNode(node.expression!); |
| } |
| } |
| |
| void _writeDynamicAccessKind(DynamicAccessKind kind) { |
| switch (kind) { |
| case DynamicAccessKind.Dynamic: |
| writeSymbol('{dynamic}.'); |
| break; |
| case DynamicAccessKind.Never: |
| writeSymbol('{Never}.'); |
| break; |
| case DynamicAccessKind.Invalid: |
| writeSymbol('{<invalid>}.'); |
| break; |
| case DynamicAccessKind.Unresolved: |
| writeSymbol('{<unresolved>}.'); |
| break; |
| } |
| } |
| |
| @override |
| void 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('{<inapplicable>}.'); |
| break; |
| case FunctionAccessKind.Nullable: |
| writeSymbol('{<nullable>}.'); |
| break; |
| } |
| } |
| |
| @override |
| void visitFunctionInvocation(FunctionInvocation node) { |
| writeExpression(node.receiver, Precedence.PRIMARY); |
| _writeFunctionAccessKind(node.kind); |
| writeNode(node.arguments); |
| if (node.functionType != null) { |
| writeSymbol('{'); |
| writeType(node.functionType!); |
| writeSymbol('}'); |
| } |
| } |
| |
| @override |
| void 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('{<inapplicable>}.'); |
| break; |
| case InstanceAccessKind.Nullable: |
| writeSymbol('{<nullable>}.'); |
| break; |
| } |
| } |
| |
| @override |
| void visitInstanceInvocation(InstanceInvocation node) { |
| writeExpression(node.receiver, Precedence.PRIMARY); |
| writeSymbol('.'); |
| writeInterfaceTarget(node.name, node.interfaceTargetReference); |
| _writeInstanceAccessKind(node.kind); |
| List<String> flags = <String>[]; |
| if (node.isInvariant) { |
| flags.add('Invariant'); |
| } |
| if (node.isBoundsSafe) { |
| flags.add('BoundsSafe'); |
| } |
| if (flags.isNotEmpty) { |
| write('{${flags.join(',')}}'); |
| } |
| writeNode(node.arguments); |
| writeSymbol('{'); |
| writeType(node.functionType); |
| writeSymbol('}'); |
| } |
| |
| @override |
| void 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('}'); |
| } |
| } |
| |
| @override |
| void 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); |
| } |
| |
| @override |
| void visitEqualsNull(EqualsNull node) { |
| writeExpression(node.expression, Precedence.EQUALITY); |
| writeSpace(); |
| writeSymbol('=='); |
| writeSpace(); |
| writeSymbol('null'); |
| } |
| |
| @override |
| void visitSuperMethodInvocation(SuperMethodInvocation node) { |
| writeWord('super'); |
| writeSymbol('.'); |
| writeInterfaceTarget(node.name, node.interfaceTargetReference); |
| writeNode(node.arguments); |
| } |
| |
| @override |
| void visitStaticInvocation(StaticInvocation node) { |
| writeModifier(node.isConst, 'const'); |
| writeMemberReferenceFromReference(node.targetReference); |
| writeNode(node.arguments); |
| } |
| |
| @override |
| void visitConstructorInvocation(ConstructorInvocation node) { |
| writeWord(node.isConst ? 'const' : 'new'); |
| writeMemberReferenceFromReference(node.targetReference); |
| writeNode(node.arguments); |
| } |
| |
| @override |
| void visitNot(Not node) { |
| writeSymbol('!'); |
| writeExpression(node.operand, Precedence.PREFIX); |
| } |
| |
| @override |
| void visitNullCheck(NullCheck node) { |
| writeExpression(node.operand, Precedence.POSTFIX); |
| writeSymbol('!'); |
| } |
| |
| @override |
| void visitLogicalExpression(LogicalExpression node) { |
| int precedence = Precedence.binaryPrecedence[ |
| logicalExpressionOperatorToString(node.operatorEnum)]!; |
| writeExpression(node.left, precedence); |
| writeSpaced(logicalExpressionOperatorToString(node.operatorEnum)); |
| writeExpression(node.right, precedence + 1); |
| } |
| |
| @override |
| void visitConditionalExpression(ConditionalExpression node) { |
| writeExpression(node.condition, Precedence.LOGICAL_OR); |
| ensureSpace(); |
| write('?'); |
| writeStaticType(node.staticType); |
| writeSpace(); |
| writeExpression(node.then); |
| writeSpaced(':'); |
| writeExpression(node.otherwise); |
| } |
| |
| @override |
| void 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; |
| } |
| |
| @override |
| void visitListConcatenation(ListConcatenation node) { |
| bool first = true; |
| for (Expression part in node.lists) { |
| if (!first) writeSpaced('+'); |
| writeExpression(part); |
| first = false; |
| } |
| } |
| |
| @override |
| void visitSetConcatenation(SetConcatenation node) { |
| bool first = true; |
| for (Expression part in node.sets) { |
| if (!first) writeSpaced('+'); |
| writeExpression(part); |
| first = false; |
| } |
| } |
| |
| @override |
| void visitMapConcatenation(MapConcatenation node) { |
| bool first = true; |
| for (Expression part in node.maps) { |
| if (!first) writeSpaced('+'); |
| writeExpression(part); |
| first = false; |
| } |
| } |
| |
| @override |
| void 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('}'); |
| } |
| |
| @override |
| void visitFileUriExpression(FileUriExpression node) { |
| writeExpression(node.expression); |
| } |
| |
| @override |
| void visitIsExpression(IsExpression node) { |
| writeExpression(node.operand, Precedence.BITWISE_OR); |
| writeSpaced( |
| node.isForNonNullableByDefault ? 'is{ForNonNullableByDefault}' : 'is'); |
| writeType(node.type); |
| } |
| |
| @override |
| void visitAsExpression(AsExpression node) { |
| writeExpression(node.operand, Precedence.BITWISE_OR); |
| List<String> flags = <String>[]; |
| 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); |
| } |
| |
| @override |
| void visitSymbolLiteral(SymbolLiteral node) { |
| writeSymbol('#'); |
| writeWord(node.value); |
| } |
| |
| @override |
| void visitTypeLiteral(TypeLiteral node) { |
| writeType(node.type); |
| } |
| |
| @override |
| void visitThisExpression(ThisExpression node) { |
| writeWord('this'); |
| } |
| |
| @override |
| void visitRethrow(Rethrow node) { |
| writeWord('rethrow'); |
| } |
| |
| @override |
| void visitThrow(Throw node) { |
| writeWord('throw'); |
| writeSpace(); |
| writeExpression(node.expression); |
| } |
| |
| @override |
| void 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(']'); |
| } |
| |
| @override |
| void 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('}'); |
| } |
| |
| @override |
| void 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('}'); |
| } |
| |
| @override |
| void visitMapLiteralEntry(MapLiteralEntry node) { |
| writeExpression(node.key); |
| writeComma(':'); |
| writeExpression(node.value); |
| } |
| |
| @override |
| void visitAwaitExpression(AwaitExpression node) { |
| writeWord('await'); |
| writeExpression(node.operand); |
| } |
| |
| @override |
| void visitFunctionExpression(FunctionExpression node) { |
| writeFunction(node.function, terminateLine: false); |
| } |
| |
| @override |
| void visitStringLiteral(StringLiteral node) { |
| writeWord('"${escapeString(node.value)}"'); |
| } |
| |
| @override |
| void visitIntLiteral(IntLiteral node) { |
| writeWord('${node.value}'); |
| } |
| |
| @override |
| void visitDoubleLiteral(DoubleLiteral node) { |
| writeWord('${node.value}'); |
| } |
| |
| @override |
| void visitBoolLiteral(BoolLiteral node) { |
| writeWord('${node.value}'); |
| } |
| |
| @override |
| void visitNullLiteral(NullLiteral node) { |
| writeWord('null'); |
| } |
| |
| @override |
| void visitLet(Let node) { |
| writeWord('let'); |
| writeVariableDeclaration(node.variable); |
| writeSpaced('in'); |
| writeExpression(node.body); |
| } |
| |
| @override |
| void visitBlockExpression(BlockExpression node) { |
| writeSpaced('block'); |
| writeBlockBody(node.body.statements, asExpression: true); |
| writeSymbol(' =>'); |
| writeExpression(node.value); |
| } |
| |
| @override |
| void visitInstantiation(Instantiation node) { |
| writeExpression(node.expression, Precedence.TYPE_LITERAL); |
| writeSymbol('<'); |
| writeList(node.typeArguments, writeType); |
| writeSymbol('>'); |
| } |
| |
| @override |
| void visitLoadLibrary(LoadLibrary node) { |
| writeWord('LoadLibrary'); |
| writeSymbol('('); |
| writeWord(node.import.name!); |
| writeSymbol(')'); |
| state = WORD; |
| } |
| |
| @override |
| void visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) { |
| writeWord('CheckLibraryIsLoaded'); |
| writeSymbol('('); |
| writeWord(node.import.name!); |
| writeSymbol(')'); |
| state = WORD; |
| } |
| |
| @override |
| void visitLibraryPart(LibraryPart node) { |
| writeAnnotationList(node.annotations); |
| writeIndentation(); |
| writeWord('part'); |
| writeWord(node.partUri); |
| endLine(";"); |
| } |
| |
| @override |
| void 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(';'); |
| } |
| |
| @override |
| void defaultExpression(Expression node) { |
| writeWord('${node.runtimeType}'); |
| } |
| |
| @override |
| void visitVariableGet(VariableGet node) { |
| writeVariableReference(node.variable); |
| DartType? promotedType = node.promotedType; |
| if (promotedType != null) { |
| writeSymbol('{'); |
| writeNode(promotedType); |
| writeSymbol('}'); |
| state = WORD; |
| } |
| } |
| |
| @override |
| void 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('}'); |
| } |
| } |
| |
| @override |
| void visitDynamicGet(DynamicGet node) { |
| writeExpression(node.receiver, Precedence.PRIMARY); |
| _writeDynamicAccessKind(node.kind); |
| writeName(node.name); |
| } |
| |
| @override |
| void visitFunctionTearOff(FunctionTearOff node) { |
| writeExpression(node.receiver, Precedence.PRIMARY); |
| writeSymbol('.'); |
| writeSymbol('call'); |
| } |
| |
| @override |
| void visitInstanceGet(InstanceGet node) { |
| writeExpression(node.receiver, Precedence.PRIMARY); |
| writeSymbol('.'); |
| writeInterfaceTarget(node.name, node.interfaceTargetReference); |
| _writeInstanceAccessKind(node.kind); |
| writeSymbol('{'); |
| writeType(node.resultType); |
| writeSymbol('}'); |
| } |
| |
| @override |
| void visitInstanceTearOff(InstanceTearOff node) { |
| writeExpression(node.receiver, Precedence.PRIMARY); |
| writeSymbol('.'); |
| writeInterfaceTarget(node.name, node.interfaceTargetReference); |
| _writeInstanceAccessKind(node.kind); |
| writeSymbol('{'); |
| writeType(node.resultType); |
| writeSymbol('}'); |
| } |
| |
| @override |
| void visitDynamicSet(DynamicSet node) { |
| writeExpression(node.receiver, Precedence.PRIMARY); |
| _writeDynamicAccessKind(node.kind); |
| writeName(node.name); |
| writeSpaced('='); |
| writeExpression(node.value); |
| } |
| |
| @override |
| void visitInstanceSet(InstanceSet node) { |
| writeExpression(node.receiver, Precedence.PRIMARY); |
| writeSymbol('.'); |
| writeInterfaceTarget(node.name, node.interfaceTargetReference); |
| _writeInstanceAccessKind(node.kind); |
| writeSpaced('='); |
| writeExpression(node.value); |
| } |
| |
| @override |
| void visitSuperPropertyGet(SuperPropertyGet node) { |
| writeWord('super'); |
| writeSymbol('.'); |
| writeInterfaceTarget(node.name, node.interfaceTargetReference); |
| } |
| |
| @override |
| void visitSuperPropertySet(SuperPropertySet node) { |
| writeWord('super'); |
| writeSymbol('.'); |
| writeInterfaceTarget(node.name, node.interfaceTargetReference); |
| writeSpaced('='); |
| writeExpression(node.value); |
| } |
| |
| @override |
| void visitStaticTearOff(StaticTearOff node) { |
| writeMemberReferenceFromReference(node.targetReference); |
| } |
| |
| @override |
| void visitStaticGet(StaticGet node) { |
| writeMemberReferenceFromReference(node.targetReference); |
| } |
| |
| @override |
| void visitStaticSet(StaticSet node) { |
| writeMemberReferenceFromReference(node.targetReference); |
| writeSpaced('='); |
| writeExpression(node.value); |
| } |
| |
| @override |
| void visitConstructorTearOff(ConstructorTearOff node) { |
| writeMemberReferenceFromReference(node.targetReference); |
| } |
| |
| @override |
| void visitRedirectingFactoryTearOff(RedirectingFactoryTearOff node) { |
| writeMemberReferenceFromReference(node.targetReference); |
| } |
| |
| @override |
| void visitTypedefTearOff(TypedefTearOff node) { |
| writeTypeParameterList(node.typeParameters); |
| state = SYMBOL; |
| writeSymbol('.('); |
| writeNode(node.expression); |
| if (node.typeArguments.isNotEmpty) { |
| writeSymbol('<'); |
| writeList(node.typeArguments, writeType); |
| writeSymbol('>'); |
| } |
| writeSymbol(')'); |
| state = WORD; |
| } |
| |
| @override |
| void visitExpressionStatement(ExpressionStatement node) { |
| writeIndentation(); |
| writeExpression(node.expression); |
| endLine(';'); |
| } |
| |
| void writeBlockBody(List<Statement> statements, {bool asExpression = false}) { |
| if (statements.isEmpty) { |
| asExpression ? writeSymbol('{}') : endLine('{}'); |
| return; |
| } |
| endLine('{'); |
| ++indentation; |
| statements.forEach(writeNode); |
| --indentation; |
| writeIndentation(); |
| asExpression ? writeSymbol('}') : endLine('}'); |
| } |
| |
| @override |
| void visitBlock(Block node) { |
| writeIndentation(); |
| writeBlockBody(node.statements); |
| } |
| |
| @override |
| void visitAssertBlock(AssertBlock node) { |
| writeIndentation(); |
| writeSpaced('assert'); |
| writeBlockBody(node.statements); |
| } |
| |
| @override |
| void visitEmptyStatement(EmptyStatement node) { |
| writeIndentation(); |
| endLine(';'); |
| } |
| |
| @override |
| void 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(')'); |
| } |
| } |
| |
| @override |
| void visitLabeledStatement(LabeledStatement node) { |
| writeIndentation(); |
| writeWord(syntheticNames.nameLabeledStatement(node)); |
| endLine(':'); |
| writeNode(node.body); |
| } |
| |
| @override |
| void visitBreakStatement(BreakStatement node) { |
| writeIndentation(); |
| writeWord('break'); |
| writeWord(syntheticNames.nameLabeledStatement(node.target)); |
| endLine(';'); |
| } |
| |
| @override |
| void visitWhileStatement(WhileStatement node) { |
| writeIndentation(); |
| writeSpaced('while'); |
| writeSymbol('('); |
| writeExpression(node.condition); |
| writeSymbol(')'); |
| writeBody(node.body); |
| } |
| |
| @override |
| void visitDoStatement(DoStatement node) { |
| writeIndentation(); |
| writeWord('do'); |
| writeBody(node.body); |
| writeIndentation(); |
| writeSpaced('while'); |
| writeSymbol('('); |
| writeExpression(node.condition); |
| endLine(')'); |
| } |
| |
| @override |
| void 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); |
| } |
| |
| @override |
| void 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); |
| } |
| |
| @override |
| void visitSwitchStatement(SwitchStatement node) { |
| writeIndentation(); |
| writeWord('switch'); |
| writeSymbol('('); |
| writeExpression(node.expression); |
| endLine(') {'); |
| ++indentation; |
| node.cases.forEach(writeNode); |
| --indentation; |
| writeIndentation(); |
| endLine('}'); |
| } |
| |
| @override |
| void 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; |
| } |
| |
| @override |
| void visitContinueSwitchStatement(ContinueSwitchStatement node) { |
| writeIndentation(); |
| writeWord('continue'); |
| writeWord(syntheticNames.nameSwitchCase(node.target)); |
| endLine(';'); |
| } |
| |
| @override |
| void 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); |
| } |
| } |
| |
| @override |
| void visitReturnStatement(ReturnStatement node) { |
| writeIndentation(); |
| writeWord('return'); |
| Expression? expression = node.expression; |
| if (expression != null) { |
| writeSpace(); |
| writeExpression(expression); |
| } |
| endLine(';'); |
| } |
| |
| @override |
| void visitTryCatch(TryCatch node) { |
| writeIndentation(); |
| writeWord('try'); |
| writeBody(node.body); |
| node.catches.forEach(writeNode); |
| } |
| |
| @override |
| void 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); |
| } |
| |
| @override |
| void visitTryFinally(TryFinally node) { |
| writeIndentation(); |
| writeWord('try'); |
| writeBody(node.body); |
| writeIndentation(); |
| writeWord('finally'); |
| writeBody(node.finalizer); |
| } |
| |
| @override |
| void visitYieldStatement(YieldStatement node) { |
| writeIndentation(); |
| if (node.isYieldStar) { |
| writeWord('yield*'); |
| } else if (node.isNative) { |
| writeWord('[yield]'); |
| } else { |
| writeWord('yield'); |
| } |
| writeExpression(node.expression); |
| endLine(';'); |
| } |
| |
| @override |
| void visitVariableDeclaration(VariableDeclaration node) { |
| writeIndentation(); |
| writeVariableDeclaration(node, useVarKeyword: true); |
| endLine(';'); |
| } |
| |
| @override |
| void 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.isCovariantByDeclaration, 'covariant-by-declaration'); |
| writeModifier(node.isCovariantByClass, 'covariant-by-class'); |
| 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); |
| } |
| } |
| |
| @override |
| void visitArguments(Arguments node) { |
| if (node.types.isNotEmpty) { |
| writeSymbol('<'); |
| writeList(node.types, writeType); |
| writeSymbol('>'); |
| } |
| writeSymbol('('); |
| Iterable<TreeNode> allArgs = |
| <List<TreeNode>>[node.positional, node.named].expand((x) => x); |
| writeList(allArgs, writeNode); |
| writeSymbol(')'); |
| } |
| |
| @override |
| void visitNamedExpression(NamedExpression node) { |
| writeWord(node.name); |
| writeComma(':'); |
| writeExpression(node.value); |
| } |
| |
| @override |
| void defaultStatement(Statement node) { |
| writeIndentation(); |
| endLine('${node.runtimeType}'); |
| } |
| |
| @override |
| void visitInvalidInitializer(InvalidInitializer node) { |
| writeWord('invalid-initializer'); |
| } |
| |
| @override |
| void visitFieldInitializer(FieldInitializer node) { |
| writeMemberReferenceFromReference(node.fieldReference); |
| writeSpaced('='); |
| writeExpression(node.value); |
| } |
| |
| @override |
| void visitSuperInitializer(SuperInitializer node) { |
| writeWord('super'); |
| writeMemberReferenceFromReference(node.targetReference); |
| writeNode(node.arguments); |
| } |
| |
| @override |
| void visitRedirectingInitializer(RedirectingInitializer node) { |
| writeWord('this'); |
| writeMemberReferenceFromReference(node.targetReference); |
| writeNode(node.arguments); |
| } |
| |
| @override |
| void visitLocalInitializer(LocalInitializer node) { |
| writeVariableDeclaration(node.variable); |
| } |
| |
| @override |
| void visitAssertInitializer(AssertInitializer node) { |
| visitAssertStatement(node.statement, asExpression: true); |
| } |
| |
| @override |
| void 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); |
| } |
| } |
| |
| @override |
| void visitInvalidType(InvalidType node) { |
| writeWord('invalid-type'); |
| } |
| |
| @override |
| void visitDynamicType(DynamicType node) { |
| writeWord('dynamic'); |
| } |
| |
| @override |
| void visitVoidType(VoidType node) { |
| writeWord('void'); |
| } |
| |
| @override |
| void visitNeverType(NeverType node) { |
| writeWord('Never'); |
| writeNullability(node.nullability); |
| } |
| |
| @override |
| void visitNullType(NullType node) { |
| writeWord('Null'); |
| } |
| |
| @override |
| void 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); |
| } |
| |
| @override |
| void visitExtensionType(ExtensionType node) { |
| writeExtensionReferenceFromReference(node.extensionReference); |
| if (node.typeArguments.isNotEmpty) { |
| writeSymbol('<'); |
| writeList(node.typeArguments, writeType); |
| writeSymbol('>'); |
| state = Printer.WORD; |
| } |
| writeNullability(node.declaredNullability); |
| } |
| |
| @override |
| void visitFutureOrType(FutureOrType node) { |
| writeWord('FutureOr'); |
| writeSymbol('<'); |
| writeNode(node.typeArgument); |
| writeSymbol('>'); |
| writeNullability(node.declaredNullability); |
| } |
| |
| @override |
| void visitFunctionType(FunctionType node) { |
| writeFunctionType(node); |
| } |
| |
| @override |
| void visitNamedType(NamedType node) { |
| writeModifier(node.isRequired, 'required'); |
| writeWord(node.name); |
| writeSymbol(':'); |
| writeSpace(); |
| writeType(node.type); |
| } |
| |
| @override |
| void 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("' */"); |
| } |
| } |
| |
| @override |
| void visitTypeParameter(TypeParameter node) { |
| writeModifier(node.isCovariantByClass, 'covariant-by-class'); |
| writeAnnotationList(node.annotations, separateLines: false); |
| if (node.variance != Variance.covariant) { |
| writeWord(const <String>[ |
| "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)); |
| } |
| |
| @override |
| void visitConstantExpression(ConstantExpression node) { |
| writeConstantReference(node.constant); |
| } |
| |
| @override |
| void defaultConstant(Constant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| endLine('${node.runtimeType}'); |
| } |
| |
| @override |
| void visitNullConstant(NullConstant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| endLine('${node.value}'); |
| } |
| |
| @override |
| void visitBoolConstant(BoolConstant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| endLine('${node.value}'); |
| } |
| |
| @override |
| void visitIntConstant(IntConstant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| endLine('${node.value}'); |
| } |
| |
| @override |
| void visitDoubleConstant(DoubleConstant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| endLine('${node.value}'); |
| } |
| |
| @override |
| void visitSymbolConstant(SymbolConstant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| Reference? libraryReference = node.libraryReference; |
| String text = libraryReference != null |
| ? '#${libraryReference.asLibrary.importUri}::${node.name}' |
| : '#${node.name}'; |
| endLine('${text}'); |
| } |
| |
| @override |
| void visitListConstant(ListConstant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| writeSymbol('<'); |
| writeType(node.typeArgument); |
| writeSymbol('>['); |
| writeList(node.entries, writeConstantReference); |
| endLine(']'); |
| } |
| |
| @override |
| void visitSetConstant(SetConstant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| writeSymbol('<'); |
| writeType(node.typeArgument); |
| writeSymbol('>{'); |
| writeList(node.entries, writeConstantReference); |
| endLine('}'); |
| } |
| |
| @override |
| void 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(')'); |
| } |
| |
| @override |
| void visitTypeLiteralConstant(TypeLiteralConstant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| writeWord('${node.runtimeType}'); |
| writeSymbol('('); |
| writeNode(node.type); |
| endLine(')'); |
| } |
| |
| @override |
| void 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, (MapEntry<Reference, Constant> entry) { |
| if (entry.key.node != null) { |
| writeWord('${entry.key.asField.name.text}'); |
| } else { |
| writeWord('${entry.key.canonicalName!.name}'); |
| } |
| writeSymbol(':'); |
| writeConstantReference(entry.value); |
| }); |
| endLine('}'); |
| } |
| |
| @override |
| void visitInstantiationConstant(InstantiationConstant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| writeWord('instantiation'); |
| writeSpace(); |
| writeConstantReference(node.tearOffConstant); |
| writeSpace(); |
| writeSymbol('<'); |
| writeList(node.types, writeType); |
| writeSymbol('>'); |
| endLine(); |
| } |
| |
| @override |
| void visitStringConstant(StringConstant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| endLine('"${escapeString(node.value)}"'); |
| } |
| |
| @override |
| void visitStaticTearOffConstant(StaticTearOffConstant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| writeWord('static-tearoff'); |
| writeSpace(); |
| writeMemberReferenceFromReference(node.targetReference); |
| endLine(); |
| } |
| |
| @override |
| void visitTypedefTearOffConstant(TypedefTearOffConstant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| writeWord('typedef-tearoff'); |
| writeSpace(); |
| writeTypeParameterList(node.parameters); |
| state = SYMBOL; |
| writeSymbol('.('); |
| writeConstantReference(node.tearOffConstant); |
| if (node.types.isNotEmpty) { |
| writeSymbol('<'); |
| writeList(node.types, writeType); |
| writeSymbol('>'); |
| } |
| writeSymbol(')'); |
| state = WORD; |
| endLine(); |
| } |
| |
| @override |
| void visitUnevaluatedConstant(UnevaluatedConstant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| writeSymbol('eval'); |
| writeSpace(); |
| writeExpression(node.expression); |
| endLine(); |
| } |
| |
| @override |
| void visitConstructorTearOffConstant(ConstructorTearOffConstant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| writeWord('constructor-tearoff'); |
| writeSpace(); |
| writeMemberReferenceFromReference(node.targetReference); |
| endLine(); |
| } |
| |
| @override |
| void visitRedirectingFactoryTearOffConstant( |
| RedirectingFactoryTearOffConstant node) { |
| writeIndentation(); |
| writeConstantReference(node); |
| writeSpaced('='); |
| writeWord('redirecting-factory-tearoff'); |
| writeSpace(); |
| writeMemberReferenceFromReference(node.targetReference); |
| endLine(); |
| } |
| |
| @override |
| void defaultNode(Node node) { |
| write('<${node.runtimeType}>'); |
| } |
| } |
| |
| class Precedence implements ExpressionVisitor<int> { |
| 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<String?, int> 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 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 |
| |