blob: 5874ca7065ad5431074a4bcf6631171926e68f45 [file] [log] [blame]
// 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(MapEntry 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();
}