// Copyright (c) 2023, 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 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/source/source.dart'; import 'package:analyzer/src/dart/element/element.dart'; import 'package:analyzer/src/dart/element/member.dart'; import 'package:analyzer/src/summary2/reference.dart'; import 'package:test/test.dart'; import 'tree_string_sink.dart'; class ElementPrinter { final TreeStringSink _sink; final ElementPrinterConfiguration _configuration; final String? _selfUriStr; ElementPrinter({ required TreeStringSink sink, required ElementPrinterConfiguration configuration, required String? selfUriStr, }) : _sink = sink, _configuration = configuration, _selfUriStr = selfUriStr; void writeDirectiveUri(DirectiveUri? uri) { if (uri == null) { _sink.writeln(''); } else if (uri is DirectiveUriWithAugmentation) { _sink.writeln('DirectiveUriWithAugmentation'); _sink.withIndent(() { final uriStr = _stringOfSource(uri.augmentation.source); _sink.writelnWithIndent('uri: $uriStr'); }); } else if (uri is DirectiveUriWithLibrary) { _sink.writeln('DirectiveUriWithLibrary'); _sink.withIndent(() { final uriStr = _stringOfSource(uri.library.source); _sink.writelnWithIndent('uri: $uriStr'); }); } else if (uri is DirectiveUriWithUnit) { _sink.writeln('DirectiveUriWithUnit'); _sink.withIndent(() { final uriStr = _stringOfSource(uri.unit.source); _sink.writelnWithIndent('uri: $uriStr'); }); } else if (uri is DirectiveUriWithSource) { _sink.writeln('DirectiveUriWithSource'); _sink.withIndent(() { final uriStr = _stringOfSource(uri.source); _sink.writelnWithIndent('source: $uriStr'); }); } else if (uri is DirectiveUriWithRelativeUri) { _sink.writeln('DirectiveUriWithRelativeUri'); _sink.withIndent(() { _sink.writelnWithIndent('relativeUri: ${uri.relativeUri}'); }); } else if (uri is DirectiveUriWithRelativeUriString) { _sink.writeln('DirectiveUriWithRelativeUriString'); _sink.withIndent(() { _sink.writelnWithIndent('relativeUriString: ${uri.relativeUriString}'); }); } else { _sink.writeln('DirectiveUri'); } } void writeElement(Element? element) { switch (element) { case null: _sink.writeln(''); case Member(): _writeMember(element); case MultiplyDefinedElement(): _sink.writeln(''); case AugmentationImportElement(): _writeAugmentationImportElement(element); case LibraryExportElement(): _writeLibraryExportElement(element); case LibraryImportElement(): _writeLibraryImportElement(element); case PartElement(): _writePartElement(element); default: final referenceStr = _elementToReferenceString(element); _sink.writeln(referenceStr); } } void writeElementList(String name, List elements) { _sink.writeElements(name, elements, (element) { _sink.writeIndent(); writeElement(element); }); } void writeNamedElement(String name, Element? element) { _sink.writeWithIndent('$name: '); writeElement(element); } void writeNamedType(String name, DartType? type) { _sink.writeWithIndent('$name: '); writeType(type); } void writeReference(Reference reference) { final str = _referenceToString(reference); _sink.write(str); } void writeType(DartType? type) { if (type != null) { var typeStr = _typeStr(type); _sink.writeln(typeStr); if (type is InterfaceType) { if (_configuration.withInterfaceTypeElements) { _sink.withIndent(() { writeNamedElement('element', type.element); }); } } var alias = type.alias; if (alias != null) { _sink.withIndent(() { writeNamedElement('alias', alias.element); _sink.withIndent(() { writeTypeList('typeArguments', alias.typeArguments); }); }); } } else { _sink.writeln('null'); } } void writeTypeList(String name, List? types) { if (types != null && types.isNotEmpty) { _sink.writelnWithIndent(name); _sink.withIndent(() { for (final type in types) { _sink.writeIndent(); writeType(type); } }); } } String _elementToReferenceString(Element element) { final enclosingElement = element.enclosingElement; final reference = (element as ElementImpl).reference; if (reference != null) { return _referenceToString(reference); } else if (element is ParameterElement && enclosingElement is! GenericFunctionTypeElement) { // Positional parameters don't have actual references. // But we fabricate one to make the output better. final enclosingStr = enclosingElement != null ? _elementToReferenceString(enclosingElement) : 'root'; return '$enclosingStr::@parameter::${element.name}'; } else if (element is JoinPatternVariableElementImpl) { return [ if (!element.isConsistent) 'notConsistent ', if (element.isFinal) 'final ', element.name, '[', element.variables.map(_elementToReferenceString).join(', '), ']', ].join(''); } else { return '${element.name}@${element.nameOffset}'; } } String _referenceToString(Reference reference) { var parent = reference.parent!; if (parent.parent == null) { var libraryUriStr = reference.name; if (libraryUriStr == _selfUriStr) { return 'self'; } // TODO(scheglov): Make it precise again, after Windows. if (libraryUriStr.startsWith('file:')) { return libraryUriStr.substring(libraryUriStr.lastIndexOf('/') + 1); } return libraryUriStr; } // Ignore the unit, skip to the library. if (parent.name == '@unit') { return _referenceToString(parent.parent!); } var name = reference.name; if (name.isEmpty) { fail('Currently every reference must have a name'); } return '${_referenceToString(parent)}::$name'; } String _stringOfSource(Source source) { return '${source.uri}'; } String _substitutionMapStr(Map map) { var entriesStr = map.entries.map((entry) { return '${entry.key.name}: ${_typeStr(entry.value)}'; }).join(', '); return '{$entriesStr}'; } String _typeStr(DartType type) { return type.getDisplayString(withNullability: true); } void _writeAugmentationImportElement(AugmentationImportElement element) { _sink.writeln('AugmentationImportElement'); _sink.withIndent(() { _sink.writeWithIndent('uri: '); writeDirectiveUri(element.uri); }); } void _writeLibraryExportElement(LibraryExportElement element) { _sink.writeln('LibraryExportElement'); _sink.withIndent(() { _sink.writeWithIndent('uri: '); writeDirectiveUri(element.uri); }); } void _writeLibraryImportElement(LibraryImportElement element) { _sink.writeln('LibraryImportElement'); _sink.withIndent(() { _sink.writeWithIndent('uri: '); writeDirectiveUri(element.uri); }); } void _writeMember(Member element) { _sink.writeln(_nameOfMemberClass(element)); _sink.withIndent(() { writeNamedElement('base', element.declaration); if (element.isLegacy) { _sink.writelnWithIndent('isLegacy: true'); } var map = element.substitution.map; if (map.isNotEmpty) { var mapStr = _substitutionMapStr(map); _sink.writelnWithIndent('substitution: $mapStr'); } if (_configuration.withRedirectedConstructors) { if (element is ConstructorMember) { final redirected = element.redirectedConstructor; writeNamedElement('redirectedConstructor', redirected); } } }); } void _writePartElement(PartElement element) { writeDirectiveUri(element.uri); } static String _nameOfMemberClass(Member member) { return '${member.runtimeType}'; } } class ElementPrinterConfiguration { bool withInterfaceTypeElements = false; bool withRedirectedConstructors = false; }