| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import '../ast.dart'; |
| import 'text_util.dart'; |
| |
| /// AST printer strategy used by default in `Node.toString`. |
| /// |
| /// Don't use this for testing. Instead use [astTextStrategyForTesting] or |
| /// make an explicit strategy for the test to avoid test dependency on the |
| /// `Node.toString` implementation. |
| const AstTextStrategy defaultAstTextStrategy = const AstTextStrategy(); |
| |
| /// Strategy used for printing AST nodes. |
| /// |
| /// This is used to avoid dependency on the `Node.toString` implementations |
| /// in testing. |
| const AstTextStrategy astTextStrategyForTesting = const AstTextStrategy( |
| includeLibraryNamesInMembers: true, |
| includeLibraryNamesInTypes: true, |
| includeAuxiliaryProperties: false, |
| useMultiline: false); |
| |
| class AstTextStrategy { |
| /// If `true`, references to classes and typedefs in types are prefixed by the |
| /// name of their enclosing library. |
| final bool includeLibraryNamesInTypes; |
| |
| /// If `true`, references to members, classes and typedefs are prefixed by the |
| /// name of their enclosing library. |
| final bool includeLibraryNamesInMembers; |
| |
| /// If `true`, auxiliary node properties are on include in the textual |
| /// representation. |
| final bool includeAuxiliaryProperties; |
| |
| /// If `true`, newlines are used to separate statements. |
| final bool useMultiline; |
| |
| /// If [useMultiline] is `true`, [indentation] is used for indentation. |
| final String indentation; |
| |
| /// If non-null, a maximum of [maxStatementDepth] nested statements are |
| /// printed. If exceeded, '...' is printed instead. |
| final int? maxStatementDepth; |
| |
| /// If non-null, a maximum of [maxStatementsLength] statements are printed |
| /// within the same block. |
| final int? maxStatementsLength; |
| |
| /// If non-null, a maximum of [maxExpressionDepth] nested expression are |
| /// printed. If exceeded, '...' is printed instead. |
| final int? maxExpressionDepth; |
| |
| /// If non-null, a maximum of [maxExpressionsLength] expression are printed |
| /// within the same list of expressions, for instance in list/set literals. |
| /// If exceeded, '...' is printed instead. |
| final int? maxExpressionsLength; |
| |
| const AstTextStrategy( |
| {this.includeLibraryNamesInTypes: false, |
| this.includeLibraryNamesInMembers: false, |
| this.includeAuxiliaryProperties: false, |
| this.useMultiline: true, |
| this.indentation: ' ', |
| this.maxStatementDepth: null, |
| this.maxStatementsLength: null, |
| this.maxExpressionDepth: null, |
| this.maxExpressionsLength: null}); |
| } |
| |
| class AstPrinter { |
| final AstTextStrategy _strategy; |
| final StringBuffer _sb = new StringBuffer(); |
| int _statementLevel = 0; |
| int _expressionLevel = 0; |
| int _indentationLevel = 0; |
| late final Map<LabeledStatement, String> _labelNames = {}; |
| late final Map<VariableDeclaration, String> _variableNames = {}; |
| |
| AstPrinter(this._strategy); |
| |
| bool get includeAuxiliaryProperties => _strategy.includeAuxiliaryProperties; |
| |
| void incIndentation() { |
| _indentationLevel++; |
| } |
| |
| void decIndentation() { |
| _indentationLevel--; |
| } |
| |
| void write(String value) { |
| _sb.write(value); |
| } |
| |
| void writeClassName(Reference? reference, {bool forType: false}) { |
| _sb.write(qualifiedClassNameToStringByReference(reference, |
| includeLibraryName: forType |
| ? _strategy.includeLibraryNamesInTypes |
| : _strategy.includeLibraryNamesInMembers)); |
| } |
| |
| void writeTypedefName(Reference? reference) { |
| _sb.write(qualifiedTypedefNameToStringByReference(reference, |
| includeLibraryName: _strategy.includeLibraryNamesInTypes)); |
| } |
| |
| void writeExtensionName(Reference? reference) { |
| _sb.write(qualifiedExtensionNameToStringByReference(reference, |
| includeLibraryName: _strategy.includeLibraryNamesInMembers)); |
| } |
| |
| void writeMemberName(Reference? reference) { |
| _sb.write(qualifiedMemberNameToStringByReference(reference, |
| includeLibraryName: _strategy.includeLibraryNamesInMembers)); |
| } |
| |
| void writeInterfaceMemberName(Reference? reference, Name? name) { |
| if (name != null && (reference == null || reference.node == null)) { |
| writeName(name); |
| } else { |
| write('{'); |
| _sb.write(qualifiedMemberNameToStringByReference(reference, |
| includeLibraryName: _strategy.includeLibraryNamesInMembers)); |
| write('}'); |
| } |
| } |
| |
| void writeName(Name? name) { |
| _sb.write(nameToString(name, |
| includeLibraryName: _strategy.includeLibraryNamesInMembers)); |
| } |
| |
| void writeNamedType(NamedType node) { |
| node.toTextInternal(this); |
| } |
| |
| void writeTypeParameterName(TypeParameter parameter) { |
| _sb.write(qualifiedTypeParameterNameToString(parameter, |
| includeLibraryName: _strategy.includeLibraryNamesInTypes)); |
| } |
| |
| void newLine() { |
| if (_strategy.useMultiline) { |
| _sb.writeln(); |
| _sb.write(_strategy.indentation * _indentationLevel); |
| } else { |
| _sb.write(' '); |
| } |
| } |
| |
| String getLabelName(LabeledStatement node) { |
| return _labelNames[node] ??= 'label${_labelNames.length}'; |
| } |
| |
| String getVariableName(VariableDeclaration node) { |
| String? name = node.name; |
| if (name != null) { |
| return name; |
| } |
| return _variableNames[node] ??= '#${_variableNames.length}'; |
| } |
| |
| String getSwitchCaseName(SwitchCase node) { |
| if (node.isDefault) { |
| return '"default:"'; |
| } else { |
| return '"case ${node.expressions.first.toText(_strategy)}:"'; |
| } |
| } |
| |
| void writeStatement(Statement node) { |
| int oldStatementLevel = _statementLevel; |
| _statementLevel++; |
| if (_strategy.maxStatementDepth != null && |
| _statementLevel > _strategy.maxStatementDepth!) { |
| _sb.write('...'); |
| } else { |
| node.toTextInternal(this); |
| } |
| _statementLevel = oldStatementLevel; |
| } |
| |
| void writeExpression(Expression node, {int? minimumPrecedence}) { |
| int oldExpressionLevel = _expressionLevel; |
| _expressionLevel++; |
| if (_strategy.maxExpressionDepth != null && |
| _expressionLevel > _strategy.maxExpressionDepth!) { |
| _sb.write('...'); |
| } else { |
| bool needsParentheses = |
| minimumPrecedence != null && node.precedence < minimumPrecedence; |
| if (needsParentheses) { |
| _sb.write('('); |
| } |
| node.toTextInternal(this); |
| if (needsParentheses) { |
| _sb.write(')'); |
| } |
| } |
| _expressionLevel = oldExpressionLevel; |
| } |
| |
| void writeNamedExpression(NamedExpression node) { |
| node.toTextInternal(this); |
| } |
| |
| void writeCatch(Catch node) { |
| node.toTextInternal(this); |
| } |
| |
| void writeSwitchCase(SwitchCase node) { |
| node.toTextInternal(this); |
| } |
| |
| void writeType(DartType node) { |
| node.toTextInternal(this); |
| } |
| |
| void writeConstant(Constant node) { |
| node.toTextInternal(this); |
| } |
| |
| void writeMapEntry(MapLiteralEntry node) { |
| node.toTextInternal(this); |
| } |
| |
| /// Writes [types] to the printer buffer separated by ', '. |
| void writeTypes(List<DartType> types) { |
| for (int index = 0; index < types.length; index++) { |
| if (index > 0) { |
| _sb.write(', '); |
| } |
| writeType(types[index]); |
| } |
| } |
| |
| /// If [types] is non-empty, writes [types] to the printer buffer delimited by |
| /// '<' and '>', and separated by ', '. |
| void writeTypeArguments(List<DartType> types) { |
| if (types.isNotEmpty) { |
| _sb.write('<'); |
| writeTypes(types); |
| _sb.write('>'); |
| } |
| } |
| |
| /// If [typeParameters] is non-empty, writes [typeParameters] to the printer |
| /// buffer delimited by '<' and '>', and separated by ', '. |
| /// |
| /// The bound of a type parameter is included, as 'T extends Bound', if the |
| /// bound is neither `Object?` nor `Object*`. |
| void writeTypeParameters(List<TypeParameter> typeParameters) { |
| if (typeParameters.isNotEmpty) { |
| _sb.write("<"); |
| String comma = ""; |
| for (TypeParameter typeParameter in typeParameters) { |
| _sb.write(comma); |
| _sb.write(typeParameter.name); |
| DartType bound = typeParameter.bound; |
| |
| bool isTopObject(DartType type) { |
| if (type is InterfaceType && |
| type.className.node != null && |
| type.classNode.name == 'Object') { |
| Uri uri = type.classNode.enclosingLibrary.importUri; |
| return uri.scheme == 'dart' && |
| uri.path == 'core' && |
| (type.nullability == Nullability.legacy || |
| type.nullability == Nullability.nullable); |
| } |
| return false; |
| } |
| |
| if (!isTopObject(bound) || isTopObject(typeParameter.defaultType)) { |
| // Include explicit bounds only. |
| _sb.write(' extends '); |
| writeType(bound); |
| } |
| comma = ", "; |
| } |
| _sb.write(">"); |
| } |
| } |
| |
| /// Writes [expressions] to the printer buffer separated by ', '. |
| void writeExpressions(List<Expression> expressions) { |
| if (expressions.isNotEmpty && |
| _strategy.maxExpressionDepth != null && |
| _expressionLevel + 1 > _strategy.maxExpressionDepth!) { |
| // The maximum expression depth will be exceeded for all [expressions]. |
| // Print the list as one occurrence '...' instead one per expression. |
| _sb.write('...'); |
| } else if (_strategy.maxExpressionsLength != null && |
| expressions.length > _strategy.maxExpressionsLength!) { |
| _sb.write('...'); |
| } else { |
| for (int index = 0; index < expressions.length; index++) { |
| if (index > 0) { |
| _sb.write(', '); |
| } |
| writeExpression(expressions[index]); |
| } |
| } |
| } |
| |
| /// Writes [statements] to the printer buffer delimited by '{' and '}'. |
| /// |
| /// If using a multiline strategy, the statements printed on separate lines |
| /// that are indented one level. |
| void writeBlock(List<Statement> statements) { |
| if (statements.isEmpty) { |
| write('{}'); |
| } else { |
| write('{'); |
| incIndentation(); |
| writeStatements(statements); |
| decIndentation(); |
| newLine(); |
| write('}'); |
| } |
| } |
| |
| /// Writes [statements] to the printer buffer. |
| /// |
| /// If using a multiline strategy, the statements printed on separate lines |
| /// that are indented one level. |
| void writeStatements(List<Statement> statements) { |
| if (statements.isNotEmpty && |
| _strategy.maxStatementDepth != null && |
| _statementLevel + 1 > _strategy.maxStatementDepth!) { |
| // The maximum statement depth will be exceeded for all [statements]. |
| // Print the list as one occurrence '...' instead one per statement. |
| _sb.write(' ...'); |
| } else if (_strategy.maxStatementsLength != null && |
| statements.length > _strategy.maxStatementsLength!) { |
| _sb.write(' ...'); |
| } else { |
| for (Statement statement in statements) { |
| newLine(); |
| writeStatement(statement); |
| } |
| } |
| } |
| |
| /// Writes arguments [node] to the printer buffer. |
| /// |
| /// If [includeTypeArguments] is `true` type arguments in [node] are included. |
| /// Otherwise only the positional and named arguments are included. |
| void writeArguments(Arguments node, {bool includeTypeArguments: true}) { |
| node.toTextInternal(this, includeTypeArguments: includeTypeArguments); |
| } |
| |
| /// Writes the variable declaration [node] to the printer buffer. |
| /// |
| /// If [includeModifiersAndType] is `true`, the declaration is prefixed by |
| /// the modifiers and declared type of the variable. Otherwise only the |
| /// name and the initializer, if present, are included. |
| /// |
| /// If [isLate] and [type] are provided, these values are used instead of |
| /// the corresponding properties on [node]. |
| void writeVariableDeclaration(VariableDeclaration node, |
| {bool includeModifiersAndType: true, |
| bool? isLate, |
| DartType? type, |
| bool includeInitializer: true}) { |
| if (includeModifiersAndType) { |
| if (node.isRequired) { |
| _sb.write('required '); |
| } |
| if (isLate ?? node.isLate) { |
| _sb.write('late '); |
| } |
| if (node.isFinal) { |
| _sb.write('final '); |
| } |
| if (node.isConst) { |
| _sb.write('const '); |
| } |
| writeType(type ?? node.type); |
| _sb.write(' '); |
| } |
| _sb.write(getVariableName(node)); |
| if (includeInitializer && node.initializer != null && !node.isRequired) { |
| _sb.write(' = '); |
| writeExpression(node.initializer!); |
| } |
| } |
| |
| void writeFunctionNode(FunctionNode node, String name) { |
| writeType(node.returnType); |
| _sb.write(' '); |
| _sb.write(name); |
| if (node.typeParameters.isNotEmpty) { |
| _sb.write('<'); |
| for (int index = 0; index < node.typeParameters.length; index++) { |
| if (index > 0) { |
| _sb.write(', '); |
| } |
| _sb.write(node.typeParameters[index].name); |
| _sb.write(' extends '); |
| writeType(node.typeParameters[index].bound); |
| } |
| _sb.write('>'); |
| } |
| _sb.write('('); |
| for (int index = 0; index < node.positionalParameters.length; index++) { |
| if (index > 0) { |
| _sb.write(', '); |
| } |
| if (index == node.requiredParameterCount) { |
| _sb.write('['); |
| } |
| writeVariableDeclaration(node.positionalParameters[index]); |
| } |
| if (node.requiredParameterCount < node.positionalParameters.length) { |
| _sb.write(']'); |
| } |
| if (node.namedParameters.isNotEmpty) { |
| if (node.positionalParameters.isNotEmpty) { |
| _sb.write(', '); |
| } |
| _sb.write('{'); |
| for (int index = 0; index < node.namedParameters.length; index++) { |
| if (index > 0) { |
| _sb.write(', '); |
| } |
| writeVariableDeclaration(node.namedParameters[index]); |
| } |
| _sb.write('}'); |
| } |
| _sb.write(')'); |
| Statement? body = node.body; |
| // ignore: unnecessary_null_comparison |
| if (body != null) { |
| if (body is ReturnStatement) { |
| _sb.write(' => '); |
| writeExpression(body.expression!); |
| } else { |
| _sb.write(' '); |
| writeStatement(body); |
| } |
| } else { |
| _sb.write(';'); |
| } |
| } |
| |
| void writeConstantMapEntry(ConstantMapEntry node) { |
| node.toTextInternal(this); |
| } |
| |
| /// Returns the text written to this printer. |
| String getText() => _sb.toString(); |
| } |