blob: 89af3ef277a421349ae7bdb99b9769481aa50e74 [file] [log] [blame]
// Copyright (c) 2022, 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/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/analysis/library_context.dart';
import 'package:analyzer/src/dart/analysis/library_graph.dart';
import 'package:analyzer/src/dart/micro/resolve_file.dart';
import 'package:collection/collection.dart';
import 'package:path/path.dart';
import 'package:test/test.dart';
class AnalyzerStatePrinter {
final MemoryByteStore byteStore;
final IdProvider idProvider;
final LibraryContext libraryContext;
final bool omitSdkFiles;
final ResourceProvider resourceProvider;
final StringSink sink;
final bool withKeysGetPut;
String _indent = '';
AnalyzerStatePrinter({
required this.byteStore,
required this.idProvider,
required this.libraryContext,
required this.omitSdkFiles,
required this.resourceProvider,
required this.sink,
required this.withKeysGetPut,
});
FileSystemState get fileSystemState => libraryContext.fileSystemState;
void writeAnalysisDriver(AnalysisDriverTestView testData) {
_writeFiles(testData.fileSystem);
_writeLibraryContext(testData.libraryContext);
_writeElementFactory();
}
void writeFileResolver(FileResolverTestData testData) {
_writeFiles(testData.fileSystem);
_writeLibraryContext(testData.libraryContext);
_writeElementFactory();
_writeByteStore();
}
/// If the path style is `Windows`, returns the corresponding Posix path.
/// Otherwise the path is already a Posix path, and it is returned as is.
String _posixPath(File file) {
final pathContext = resourceProvider.pathContext;
if (pathContext.style == Style.windows) {
final components = pathContext.split(file.path);
return '/${components.skip(1).join('/')}';
} else {
return file.path;
}
}
String _stringOfLibraryCycle(LibraryCycle cycle) {
if (omitSdkFiles) {
final isSdkLibrary = cycle.libraries.any((library) {
return library.file.uri.isScheme('dart');
});
if (isSdkLibrary) {
if (cycle.libraries.any((e) => e.file.uriStr == 'dart:core')) {
return 'dart:core';
} else {
throw UnimplementedError('$cycle');
}
}
}
return idProvider.libraryCycle(cycle);
}
String _stringOfUriStr(String uriStr) {
if (uriStr.trim().isEmpty) {
return "'$uriStr'";
} else {
return uriStr;
}
}
void _withIndent(void Function() f) {
var indent = _indent;
_indent = '$_indent ';
f();
_indent = indent;
}
void _writeAugmentations(LibraryOrAugmentationFileKind container) {
_writeElements<ImportAugmentationDirectiveState>(
'augmentations',
container.augmentations,
(import) {
if (import is ImportAugmentationDirectiveWithFile) {
expect(import.container, same(container));
final file = import.importedFile;
sink.write(_indent);
final importedAugmentation = import.importedAugmentation;
if (importedAugmentation != null) {
expect(importedAugmentation.file, file);
sink.write(idProvider.fileStateKind(importedAugmentation));
} else {
sink.write('notAugmentation ${idProvider.fileState(file)}');
}
sink.writeln();
} else if (import is ImportAugmentationWithUri) {
final uriStr = _stringOfUriStr(import.uri.relativeUriStr);
_writelnWithIndent('uri: $uriStr');
} else {
_writelnWithIndent('noUri');
}
},
);
}
void _writeByteStore() {
_writelnWithIndent('byteStore');
_withIndent(() {
final groups = byteStore.map.entries.groupListsBy((element) {
return element.value.refCount;
});
for (final groupEntry in groups.entries) {
final keys = groupEntry.value.map((e) => e.key).toList();
final shortKeys = idProvider.shortKeys(keys)..sort();
_writelnWithIndent('${groupEntry.key}: $shortKeys');
}
});
}
void _writeElementFactory() {
_writelnWithIndent('elementFactory');
_withIndent(() {
final elementFactory = libraryContext.elementFactory;
_writeUriList(
'hasElement',
elementFactory.uriListWithLibraryElements,
);
_writeUriList(
'hasReader',
elementFactory.uriListWithLibraryReaders,
);
});
}
void _writeElements<T>(String name, List<T> elements, void Function(T) f) {
if (elements.isNotEmpty) {
_writelnWithIndent(name);
_withIndent(() {
for (var element in elements) {
f(element);
}
});
}
}
void _writeFile(FileState file) {
_withIndent(() {
_writelnWithIndent('id: ${idProvider.fileState(file)}');
_writeFileKind(file);
_writeReferencingFiles(file);
_writeFileUnlinkedKey(file);
});
}
void _writeFileExports(LibraryOrAugmentationFileKind container) {
_writeElements<ExportDirectiveState>(
'exports',
container.exports,
(export) {
if (export is ExportDirectiveWithFile) {
expect(export.container, same(container));
final file = export.exportedFile;
sink.write(_indent);
final exportedLibrary = export.exportedLibrary;
if (exportedLibrary != null) {
expect(exportedLibrary.file, file);
sink.write(idProvider.fileStateKind(exportedLibrary));
} else {
sink.write('notLibrary ${idProvider.fileState(file)}');
}
if (omitSdkFiles && file.uri.isScheme('dart')) {
sink.write(' ${file.uri}');
}
sink.writeln();
} else if (export is ExportDirectiveWithInSummarySource) {
sink.write(_indent);
sink.write('inSummary ${export.exportedSource.uri}');
final librarySource = export.exportedLibrarySource;
if (librarySource != null) {
expect(librarySource, same(export.exportedSource));
} else {
sink.write(' notLibrary');
}
sink.writeln();
} else if (export is ExportDirectiveWithUri) {
final uriStr = _stringOfUriStr(export.selectedUri.relativeUriStr);
_writelnWithIndent('uri: $uriStr');
} else {
_writelnWithIndent('noUri');
}
},
);
}
void _writeFileImports(LibraryOrAugmentationFileKind container) {
_writeElements<ImportDirectiveState>(
'imports',
container.imports,
(import) {
if (import is ImportDirectiveWithFile) {
expect(import.container, same(container));
final file = import.importedFile;
sink.write(_indent);
final importedLibrary = import.importedLibrary;
if (importedLibrary != null) {
expect(importedLibrary.file, file);
sink.write(idProvider.fileStateKind(importedLibrary));
} else {
sink.write('notLibrary ${idProvider.fileState(file)}');
}
if (omitSdkFiles && file.uri.isScheme('dart')) {
sink.write(' ${file.uri}');
}
if (import.isSyntheticDartCore) {
sink.write(' synthetic');
}
sink.writeln();
} else if (import is ImportDirectiveWithInSummarySource) {
sink.write(_indent);
sink.write('inSummary ${import.importedSource.uri}');
final librarySource = import.importedLibrarySource;
if (librarySource != null) {
expect(librarySource, same(import.importedSource));
} else {
sink.write(' notLibrary');
}
if (import.isSyntheticDartCore) {
sink.write(' synthetic');
}
sink.writeln();
} else if (import is ImportDirectiveWithUri) {
final uriStr = _stringOfUriStr(import.selectedUri.relativeUriStr);
sink.write(_indent);
sink.write('uri: $uriStr');
if (import.isSyntheticDartCore) {
sink.write(' synthetic');
}
sink.writeln();
} else {
_writelnWithIndent('noUri');
}
},
);
}
void _writeFileKind(FileState file) {
final kind = file.kind;
expect(kind.file, same(file));
_writelnWithIndent('kind: ${idProvider.fileStateKind(kind)}');
if (kind is AugmentationKnownFileStateKind) {
_withIndent(() {
final augmented = kind.augmented;
if (augmented != null) {
final id = idProvider.fileStateKind(augmented);
_writelnWithIndent('augmented: $id');
} else {
final id = idProvider.fileState(kind.uriFile);
_writelnWithIndent('uriFile: $id');
}
final library = kind.library;
if (library != null) {
final id = idProvider.fileStateKind(library);
_writelnWithIndent('library: $id');
}
_writeFileImports(kind);
_writeFileExports(kind);
_writeAugmentations(kind);
});
} else if (kind is AugmentationUnknownFileStateKind) {
_withIndent(() {
_writelnWithIndent('uri: ${kind.directive.uri}');
});
} else if (kind is LibraryFileStateKind) {
expect(kind.library, same(kind));
_withIndent(() {
final name = kind.name;
if (name != null) {
_writelnWithIndent('name: $name');
}
_writeFileImports(kind);
_writeFileExports(kind);
_writeLibraryParts(kind);
_writeAugmentations(kind);
_writeLibraryCycle(kind);
});
} else if (kind is PartOfNameFileStateKind) {
_withIndent(() {
final libraries = kind.libraries;
if (libraries.isNotEmpty) {
final keys = libraries
.map(idProvider.fileStateKind)
.sorted(compareNatural)
.join(' ');
_writelnWithIndent('libraries: $keys');
}
final library = kind.library;
if (library != null) {
final id = idProvider.fileStateKind(library);
_writelnWithIndent('library: $id');
} else {
_writelnWithIndent('name: ${kind.directive.name}');
}
});
} else if (kind is PartOfUriKnownFileStateKind) {
_withIndent(() {
final library = kind.library;
if (library != null) {
final id = idProvider.fileStateKind(library);
_writelnWithIndent('library: $id');
} else {
final id = idProvider.fileState(kind.uriFile);
_writelnWithIndent('uriFile: $id');
}
});
} else if (kind is PartOfUriUnknownFileStateKind) {
_withIndent(() {
_writelnWithIndent('uri: ${kind.directive.uri}');
expect(kind.library, isNull);
});
} else {
throw UnimplementedError('${kind.runtimeType}');
}
}
void _writeFiles(FileSystemTestData testData) {
fileSystemState.pullReferencedFiles();
// Discover libraries for parts.
// This is required for consistency checking.
for (final fileData in testData.files.values.toList()) {
final current = fileSystemState.getExisting(fileData.file);
if (current != null) {
final kind = current.kind;
if (kind is PartOfNameFileStateKind) {
kind.discoverLibraries();
}
}
}
// Discover referenced files.
// This is required for consistency checking.
for (final fileData in testData.files.values.toList()) {
final current = fileSystemState.getExisting(fileData.file);
if (current != null) {
final kind = current.kind;
if (kind is LibraryOrAugmentationFileKind) {
kind.discoverReferencedFiles();
}
}
}
// Sort, mostly by path.
// But sort SDK libraries to the end, with `dart:core` first.
final fileDataList = testData.files.values.toList();
fileDataList.sort((first, second) {
final firstPath = first.file.path;
final secondPath = second.file.path;
if (omitSdkFiles) {
final firstUri = first.uri;
final secondUri = second.uri;
final firstIsSdk = firstUri.isScheme('dart');
final secondIsSdk = secondUri.isScheme('dart');
if (firstIsSdk && !secondIsSdk) {
return 1;
} else if (!firstIsSdk && secondIsSdk) {
return -1;
} else if (firstIsSdk && secondIsSdk) {
if ('$firstUri' == 'dart:core') {
return -1;
} else if ('$secondUri' == 'dart:core') {
return 1;
}
}
}
return firstPath.compareTo(secondPath);
});
// Ask ID for every file in the sorted order, so that IDs are nice.
// Register objects that can be referenced.
idProvider.resetRegisteredObject();
for (final fileData in fileDataList) {
final current = fileSystemState.getExisting(fileData.file);
if (current != null) {
idProvider.registerFileState(current);
final kind = current.kind;
idProvider.registerFileStateKind(kind);
if (kind is LibraryFileStateKind) {
idProvider.registerLibraryCycle(kind.libraryCycle);
}
}
}
_writelnWithIndent('files');
_withIndent(() {
for (final fileData in fileDataList) {
if (omitSdkFiles && fileData.uri.isScheme('dart')) {
continue;
}
final file = fileData.file;
_writelnWithIndent(_posixPath(file));
_withIndent(() {
_writelnWithIndent('uri: ${fileData.uri}');
final current = fileSystemState.getExisting(file);
if (current != null) {
_writelnWithIndent('current');
_writeFile(current);
}
if (withKeysGetPut) {
final shortGets = idProvider.shortKeys(fileData.unlinkedKeyGet);
final shortPuts = idProvider.shortKeys(fileData.unlinkedKeyPut);
_writelnWithIndent('unlinkedGet: $shortGets');
_writelnWithIndent('unlinkedPut: $shortPuts');
}
});
}
});
}
void _writeFileUnlinkedKey(FileState file) {
final unlinkedShort = idProvider.shortKey(file.unlinkedKey);
_writelnWithIndent('unlinkedKey: $unlinkedShort');
}
void _writeLibraryContext(LibraryContextTestData testData) {
_writelnWithIndent('libraryCycles');
_withIndent(() {
final cyclesToPrint = <_LibraryCycleToPrint>[];
for (final entry in testData.libraryCycles.entries) {
if (omitSdkFiles && entry.key.any((e) => e.uri.isScheme('dart'))) {
continue;
}
cyclesToPrint.add(
_LibraryCycleToPrint(
entry.key.map((e) => _posixPath(e.file)).join(' '),
entry.value,
),
);
}
cyclesToPrint.sortBy((e) => e.pathListStr);
final loadedBundlesMap = Map.fromEntries(
libraryContext.loadedBundles.map((cycle) {
final pathListStr = cycle.libraries
.map((library) => _posixPath(library.file.resource))
.sorted()
.join(' ');
return MapEntry(pathListStr, cycle);
}),
);
for (final cycleToPrint in cyclesToPrint) {
_writelnWithIndent(cycleToPrint.pathListStr);
_withIndent(() {
final current = loadedBundlesMap[cycleToPrint.pathListStr];
if (current != null) {
final id = idProvider.libraryCycle(current);
_writelnWithIndent('current: $id');
_withIndent(() {
// TODO(scheglov) Print it with the cycle instead?
final short = idProvider.shortKey(current.linkedKey);
_writelnWithIndent('key: $short');
});
}
final cycleData = cycleToPrint.data;
final shortGets = idProvider.shortKeys(cycleData.getKeys);
final shortPuts = idProvider.shortKeys(cycleData.putKeys);
_writelnWithIndent('get: $shortGets');
_writelnWithIndent('put: $shortPuts');
});
}
});
}
void _writeLibraryCycle(LibraryFileStateKind library) {
final cycle = library.libraryCycle;
_writelnWithIndent(idProvider.libraryCycle(cycle));
_withIndent(() {
final dependencyIds = cycle.directDependencies
.map(_stringOfLibraryCycle)
.sorted(compareNatural)
.join(' ');
if (dependencyIds.isNotEmpty) {
_writelnWithIndent('dependencies: $dependencyIds');
} else {
_writelnWithIndent('dependencies: none');
}
final libraryIds = cycle.libraries
.map(idProvider.fileStateKind)
.sorted(compareNatural)
.join(' ');
_writelnWithIndent('libraries: $libraryIds');
_writelnWithIndent(idProvider.apiSignature(cycle.apiSignature));
final userIds = cycle.directUsers
.map(_stringOfLibraryCycle)
.sorted(compareNatural)
.join(' ');
if (userIds.isNotEmpty) {
_writelnWithIndent('users: $userIds');
}
});
}
void _writeLibraryParts(LibraryFileStateKind library) {
_writeElements<PartDirectiveState>('parts', library.parts, (part) {
expect(part.library, same(library));
if (part is PartDirectiveWithFile) {
final file = part.includedFile;
sink.write(_indent);
final includedPart = part.includedPart;
if (includedPart != null) {
expect(includedPart.file, file);
sink.write(idProvider.fileStateKind(includedPart));
} else {
sink.write('notPart ${idProvider.fileState(file)}');
}
sink.writeln();
} else if (part is PartDirectiveWithUri) {
final uriStr = _stringOfUriStr(part.uri.relativeUriStr);
_writelnWithIndent('uri: $uriStr');
} else {
_writelnWithIndent('noUri');
}
});
}
void _writelnWithIndent(String line) {
sink.write(_indent);
sink.writeln(line);
}
void _writeReferencingFiles(FileState file) {
final referencingFiles = file.referencingFiles;
if (referencingFiles.isNotEmpty) {
final fileIds = referencingFiles
.map(idProvider.fileState)
.sorted(compareNatural)
.join(' ');
_writelnWithIndent('referencingFiles: $fileIds');
}
}
void _writeUriList(String name, Iterable<Uri> uriIterable) {
final uriStrList = <String>[];
for (final uri in uriIterable) {
if (omitSdkFiles && uri.isScheme('dart')) {
continue;
}
uriStrList.add('$uri');
}
if (uriStrList.isNotEmpty) {
uriStrList.sort();
_writelnWithIndent(name);
_withIndent(() {
for (final uriStr in uriStrList) {
_writelnWithIndent(uriStr);
}
});
}
}
}
/// Encoder of object identifies into short identifiers.
class IdProvider {
final Map<FileState, String> _fileState = Map.identity();
final Map<LibraryCycle, String> _libraryCycle = Map.identity();
final Map<FileStateKind, String> _fileStateKind = Map.identity();
final Map<String, String> _keyToShort = {};
final Map<String, String> _shortToKey = {};
final Map<String, String> _apiSignature = {};
Set<FileState> _currentFiles = {};
Set<FileStateKind> _currentKinds = {};
Set<LibraryCycle> _currentCycles = {};
String apiSignature(String signature) {
final length = _apiSignature.length;
return _apiSignature[signature] ??= 'apiSignature_$length';
}
String fileState(FileState file) {
if (!_currentFiles.contains(file)) {
throw StateError('$file');
}
return _fileState[file] ??= 'file_${_fileState.length}';
}
String fileStateKind(FileStateKind kind) {
if (!_currentKinds.contains(kind)) {
throw StateError('$kind');
}
return _fileStateKind[kind] ??= () {
if (kind is AugmentationKnownFileStateKind) {
return 'augmentation_${_fileStateKind.length}';
} else if (kind is AugmentationUnknownFileStateKind) {
return 'augmentationUnknown_${_fileStateKind.length}';
} else if (kind is LibraryFileStateKind) {
return 'library_${_fileStateKind.length}';
} else if (kind is PartOfNameFileStateKind) {
return 'partOfName_${_fileStateKind.length}';
} else if (kind is PartOfUriKnownFileStateKind) {
return 'partOfUriKnown_${_fileStateKind.length}';
} else if (kind is PartFileStateKind) {
return 'partOfUriUnknown_${_fileStateKind.length}';
} else {
throw UnimplementedError('${kind.runtimeType}');
}
}();
}
String libraryCycle(LibraryCycle cycle) {
if (!_currentCycles.contains(cycle)) {
throw StateError('$cycle');
}
return _libraryCycle[cycle] ??= 'cycle_${_libraryCycle.length}';
}
/// Register that [file] is an object that can be referenced.
void registerFileState(FileState file) {
if (_currentFiles.contains(file)) {
throw StateError('Duplicate: $file');
}
_currentFiles.add(file);
fileState(file);
}
/// Register that [kind] is an object that can be referenced.
void registerFileStateKind(FileStateKind kind) {
if (_currentKinds.contains(kind)) {
throw StateError('Duplicate: $kind');
}
_currentKinds.add(kind);
fileStateKind(kind);
}
/// Register that [cycle] is an object that can be referenced.
void registerLibraryCycle(LibraryCycle cycle) {
_currentCycles.add(cycle);
libraryCycle(cycle);
}
void resetRegisteredObject() {
_currentFiles = {};
_currentKinds = {};
_currentCycles = {};
}
String shortKey(String key) {
var short = _keyToShort[key];
if (short == null) {
short = 'k${_keyToShort.length.toString().padLeft(2, '0')}';
_keyToShort[key] = short;
_shortToKey[short] = key;
}
return short;
}
List<String> shortKeys(List<String> keys) {
return keys.map(shortKey).toList();
}
}
class _LibraryCycleToPrint {
final String pathListStr;
final LibraryCycleTestData data;
_LibraryCycleToPrint(this.pathListStr, this.data);
}