blob: 1bb4e31c1caec2661d0dc1f1d4bb2d0f84c12bcd [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/analysis/unlinked_unit_store.dart';
import 'package:analyzer/src/dart/micro/resolve_file.dart';
import 'package:analyzer/src/utilities/extensions/file_system.dart';
import 'package:analyzer_utilities/testing/tree_string_sink.dart';
import 'package:collection/collection.dart';
import 'package:test/test.dart';
class AnalyzerStatePrinter {
final ByteStore byteStore;
final UnlinkedUnitStoreImpl unlinkedUnitStore;
final IdProvider idProvider;
final LibraryContext libraryContext;
final AnalyzerStatePrinterConfiguration configuration;
final ResourceProvider resourceProvider;
final TreeStringSink sink;
final bool withKeysGetPut;
final Set<LibraryCycle> _libraryCyclesWithWrittenDetails = Set.identity();
AnalyzerStatePrinter({
required this.byteStore,
required this.unlinkedUnitStore,
required this.idProvider,
required this.libraryContext,
required this.configuration,
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();
_writeUnlinkedUnitStore();
_writeByteStore();
}
String _stringOfLibraryCycle(LibraryCycle cycle) {
if (configuration.omitSdkFiles) {
var 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 if (cycle.libraries.any(
(e) => e.file.uriStr == 'dart:collection',
)) {
return 'dart:collection';
} else if (cycle.libraries.any((e) => e.file.uriStr == 'dart:io')) {
return 'dart:io';
} else {
throw UnimplementedError('$cycle');
}
}
}
return idProvider.libraryCycle(cycle);
}
String _stringOfUriStr(String uriStr) {
if (uriStr.trim().isEmpty) {
return "'$uriStr'";
} else {
return uriStr;
}
}
void _verifyKnownFiles() {
var uriFiles = fileSystemState.test.uriToFile.values.toSet();
var pathFiles = fileSystemState.test.uriToFile.values.toSet();
expect(pathFiles.difference(uriFiles), isEmpty);
expect(uriFiles.difference(pathFiles), isEmpty);
var knownFilesNotInUriFiles = fileSystemState.knownFiles.toSet();
knownFilesNotInUriFiles.removeAll(uriFiles);
expect(knownFilesNotInUriFiles, isEmpty);
}
void _writeByteStore() {
sink.writelnWithIndent('byteStore');
sink.withIndent(() {
switch (byteStore) {
case CiderByteStore byteStore:
var groups = byteStore.map.entries.groupListsBy((element) {
return element.value.refCount;
});
for (var groupEntry in groups.entries) {
var keys = groupEntry.value.map((e) => e.key).toList();
var shortKeys = idProvider.shortKeys(keys)..sort();
sink.writelnWithIndent('${groupEntry.key}: $shortKeys');
}
default:
throw UnimplementedError('${byteStore.runtimeType}');
}
});
}
void _writeDocImports(FileKind container) {
_writeElements<LibraryImportState>(
'docLibraryImports',
container.docLibraryImports,
(import) {
expect(import.isDocImport, isTrue);
_writeLibraryImport(container, import);
},
);
}
void _writeElementFactory() {
sink.writelnWithIndent('elementFactory');
sink.withIndent(() {
var 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) {
sink.writelnWithIndent(name);
sink.withIndent(() {
for (var element in elements) {
f(element);
}
});
}
}
void _writeFile(FileState file) {
sink.withIndent(() {
sink.writelnWithIndent('id: ${idProvider.fileState(file)}');
_writeFileContent(file);
_writeFileKind(file);
_writeReferencingFiles(file);
_writeFileUnlinkedKey(file);
});
}
void _writeFileContent(FileState file) {
if (configuration.filesToPrintContent.any((e) => e.path == file.path)) {
sink.writelnWithIndent('content\n---\n${file.content}---');
}
}
void _writeFileKind(FileState file) {
var kind = file.kind;
expect(kind.file, same(file));
sink.writelnWithIndent('kind: ${idProvider.fileKind(kind)}');
switch (kind) {
case LibraryFileKind():
expect(kind.library, same(kind));
sink.withIndent(() {
var name = kind.name;
if (name != null) {
sink.writelnWithIndent('name: $name');
}
_writeLibraryImports(kind);
_writeLibraryExports(kind);
_writePartIncludes(kind);
_writeDocImports(kind);
var filesIds = kind.fileKinds.map(idProvider.fileKind);
sink.writelnWithIndent('fileKinds: ${filesIds.join(' ')}');
if (configuration.withResolvedKey) {
var id = idProvider.shortKey(kind.resolvedKey);
sink.writelnWithIndent('resolvedKey: $id');
}
_writeLibraryCycle(kind);
});
case PartOfNameFileKind():
sink.withIndent(() {
var libraries = kind.libraries;
if (libraries.isNotEmpty) {
var keys = libraries
.map(idProvider.fileKind)
.sorted(compareNatural)
.join(' ');
sink.writelnWithIndent('libraries: $keys');
}
var library = kind.library;
if (library != null) {
var id = idProvider.fileKind(library);
sink.writelnWithIndent('library: $id');
} else {
sink.writelnWithIndent('name: ${kind.unlinked.name}');
}
_writeLibraryImports(kind);
_writeLibraryExports(kind);
_writePartIncludes(kind);
_writeDocImports(kind);
});
case PartOfUriKnownFileKind():
sink.withIndent(() {
var uriFileId = idProvider.fileState(kind.uriFile);
sink.writelnWithIndent('uriFile: $uriFileId');
if (kind.library case var library?) {
var id = idProvider.fileKind(library);
sink.writelnWithIndent('library: $id');
}
_writeLibraryImports(kind);
_writeLibraryExports(kind);
_writePartIncludes(kind);
_writeDocImports(kind);
});
case PartOfUriUnknownFileKind():
sink.withIndent(() {
sink.writelnWithIndent('uri: ${kind.unlinked.uri}');
expect(kind.library, isNull);
});
default:
throw UnimplementedError('${kind.runtimeType}');
}
}
void _writeFiles(FileSystemTestData testData) {
fileSystemState.discoverReferencedFiles();
_verifyKnownFiles();
// Discover libraries for parts.
// This is required for consistency checking.
for (var fileData in testData.files.values.toList()) {
var current = fileSystemState.getExisting(fileData.file);
if (current != null) {
var kind = current.kind;
if (kind is PartOfNameFileKind) {
kind.discoverLibraries();
}
}
}
// This is required for consistency checking.
fileSystemState.discoverReferencedFiles();
// Sort, mostly by path.
// But sort SDK libraries to the end, with `dart:core` first.
var fileDataList = testData.files.values.toList();
fileDataList.sort((first, second) {
var firstPath = first.file.path;
var secondPath = second.file.path;
if (configuration.omitSdkFiles) {
var firstUri = first.uri;
var secondUri = second.uri;
var firstIsSdk = firstUri.isScheme('dart');
var 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 (var fileData in fileDataList) {
var current = fileSystemState.getExisting(fileData.file);
if (current != null) {
idProvider.registerFileState(current);
var kind = current.kind;
idProvider.registerFileKind(kind);
if (kind is LibraryFileKind) {
idProvider.registerLibraryCycle(kind.libraryCycle);
}
}
}
sink.writelnWithIndent('files');
sink.withIndent(() {
for (var fileData in fileDataList) {
if (configuration.omitSdkFiles && fileData.uri.isScheme('dart')) {
continue;
}
var file = fileData.file;
sink.writelnWithIndent(file.posixPath);
sink.withIndent(() {
sink.writelnWithIndent('uri: ${fileData.uri}');
var current = fileSystemState.getExisting(file);
if (current != null) {
sink.writelnWithIndent('current');
_writeFile(current);
}
if (withKeysGetPut) {
var shortGets = idProvider.shortKeys(fileData.unlinkedKeyGet);
var shortPuts = idProvider.shortKeys(fileData.unlinkedKeyPut);
sink.writelnWithIndent('unlinkedGet: $shortGets');
sink.writelnWithIndent('unlinkedPut: $shortPuts');
}
});
}
});
}
void _writeFileUnlinkedKey(FileState file) {
var unlinkedShort = idProvider.shortKey(file.unlinkedKey);
sink.writelnWithIndent('unlinkedKey: $unlinkedShort');
}
void _writeLibraryContext(LibraryContextTestData testData) {
sink.writelnWithIndent('libraryCycles');
sink.withIndent(() {
var cyclesToPrint = <_LibraryCycleToPrint>[];
for (var entry in testData.libraryCycles.entries) {
if (configuration.omitSdkFiles &&
entry.key.any((e) => e.uri.isScheme('dart'))) {
continue;
}
cyclesToPrint.add(
_LibraryCycleToPrint(
entry.key.map((e) => e.file.posixPath).join(' '),
entry.value,
),
);
}
cyclesToPrint.sortBy((e) => e.pathListStr);
var loadedBundlesMap = Map.fromEntries(
libraryContext.loadedBundles.map((cycle) {
var pathListStr = cycle.libraries
.map((library) => library.file.resource.posixPath)
.sorted()
.join(' ');
return MapEntry(pathListStr, cycle);
}),
);
for (var cycleToPrint in cyclesToPrint) {
sink.writelnWithIndent(cycleToPrint.pathListStr);
sink.withIndent(() {
var current = loadedBundlesMap[cycleToPrint.pathListStr];
if (current != null) {
var id = idProvider.libraryCycle(current);
sink.writelnWithIndent('current: $id');
sink.withIndent(() {
// TODO(scheglov): Print it with the cycle instead?
var short = idProvider.shortKey(current.linkedKey);
sink.writelnWithIndent('key: $short');
});
}
var cycleData = cycleToPrint.data;
var shortGets = idProvider.shortKeys(cycleData.getKeys);
var shortPuts = idProvider.shortKeys(cycleData.putKeys);
sink.writelnWithIndent('get: $shortGets');
sink.writelnWithIndent('put: $shortPuts');
});
}
});
if (testData.instance case var libraryContext?) {
var bundleProvider = libraryContext.linkedBundleProvider;
var bundleKeys =
bundleProvider.map.entries
.map((entry) => idProvider.shortKey(entry.key))
.sorted();
if (bundleKeys.isNotEmpty) {
sink.writelnWithIndent('linkedBundleProvider: $bundleKeys');
}
}
}
void _writeLibraryCycle(LibraryFileKind library) {
var cycle = library.libraryCycle;
sink.writelnWithIndent(idProvider.libraryCycle(cycle));
if (!_libraryCyclesWithWrittenDetails.add(cycle)) {
return;
}
sink.withIndent(() {
var dependencyIds = cycle.directDependencies
.map(_stringOfLibraryCycle)
.sorted(compareNatural)
.join(' ');
if (dependencyIds.isNotEmpty) {
sink.writelnWithIndent('dependencies: $dependencyIds');
} else {
sink.writelnWithIndent('dependencies: none');
}
var libraryIds = cycle.libraries
.map(idProvider.fileKind)
.sorted(compareNatural)
.join(' ');
sink.writelnWithIndent('libraries: $libraryIds');
sink.writelnWithIndent(idProvider.apiSignature(cycle.apiSignature));
var userIds = cycle.directUsers
.map(_stringOfLibraryCycle)
.sorted(compareNatural)
.join(' ');
if (userIds.isNotEmpty) {
sink.writelnWithIndent('users: $userIds');
}
});
}
void _writeLibraryExport(
FileKind container,
LibraryExportState<DirectiveUri> export,
) {
expect(export.container, same(container));
switch (export) {
case LibraryExportWithFile():
sink.writeIndentedLine(() {
var file = export.exportedFile;
var exportedLibrary = export.exportedLibrary;
if (exportedLibrary != null) {
expect(exportedLibrary.file, file);
sink.write(idProvider.fileKind(exportedLibrary));
} else {
sink.write('notLibrary ${idProvider.fileState(file)}');
}
if (configuration.omitSdkFiles && file.uri.isScheme('dart')) {
sink.write(' ${file.uri}');
}
});
case LibraryExportWithInSummarySource():
sink.writeIndentedLine(() {
sink.write('inSummary ${export.exportedSource.uri}');
var librarySource = export.exportedLibrarySource;
if (librarySource != null) {
expect(librarySource, same(export.exportedSource));
} else {
sink.write(' notLibrary');
}
});
case LibraryExportWithUri():
sink.writelnWithIndent('uri: ${export.selectedUri.relativeUri}');
case LibraryExportWithUriStr():
var uriStr = _stringOfUriStr(export.selectedUri.relativeUriStr);
sink.writelnWithIndent('uriStr: $uriStr');
default:
sink.writelnWithIndent('noUriStr');
}
}
void _writeLibraryExports(FileKind container) {
_writeElements<LibraryExportState>(
'libraryExports',
container.libraryExports,
(export) {
_writeLibraryExport(container, export);
},
);
}
void _writeLibraryImport(
FileKind container,
LibraryImportState<DirectiveUri> import,
) {
expect(import.container, same(container));
switch (import) {
case LibraryImportWithFile():
sink.writeIndentedLine(() {
var file = import.importedFile;
var importedLibrary = import.importedLibrary;
if (importedLibrary != null) {
expect(importedLibrary.file, file);
sink.write(idProvider.fileKind(importedLibrary));
} else {
sink.write('notLibrary ${idProvider.fileState(file)}');
}
if (configuration.omitSdkFiles && file.uri.isScheme('dart')) {
sink.write(' ${file.uri}');
}
if (import.isSyntheticDartCore) {
sink.write(' synthetic');
}
});
case LibraryImportWithInSummarySource():
sink.writeIndentedLine(() {
sink.write('inSummary ${import.importedSource.uri}');
var librarySource = import.importedLibrarySource;
if (librarySource != null) {
expect(librarySource, same(import.importedSource));
} else {
sink.write(' notLibrary');
}
if (import.isSyntheticDartCore) {
sink.write(' synthetic');
}
});
case LibraryImportWithUri():
sink.writeIndentedLine(() {
sink.write('uri: ${import.selectedUri.relativeUri}');
if (import.isSyntheticDartCore) {
sink.write(' synthetic');
}
});
case LibraryImportWithUriStr():
sink.writeIndentedLine(() {
var uriStr = _stringOfUriStr(import.selectedUri.relativeUriStr);
sink.write('uriStr: $uriStr');
if (import.isSyntheticDartCore) {
sink.write(' synthetic');
}
});
default:
sink.writelnWithIndent('noUriStr');
}
}
void _writeLibraryImports(FileKind container) {
_writeElements<LibraryImportState>(
'libraryImports',
container.libraryImports,
(import) {
_writeLibraryImport(container, import);
},
);
}
void _writePartIncludes(FileKind container) {
_writeElements<PartIncludeState>('partIncludes', container.partIncludes, (
part,
) {
expect(part.container, same(container));
switch (part) {
case PartIncludeWithFile():
sink.writeIndentedLine(() {
var file = part.includedFile;
var includedPart = part.includedPart;
if (includedPart != null) {
expect(includedPart.file, file);
sink.write(idProvider.fileKind(includedPart));
} else {
sink.write('notPart ${idProvider.fileState(file)}');
}
});
case PartIncludeWithUri():
var uriStr = _stringOfUriStr(part.selectedUri.relativeUriStr);
sink.writelnWithIndent('uri: $uriStr');
default:
sink.writelnWithIndent('noUri');
}
});
}
void _writeReferencingFiles(FileState file) {
var referencingFiles = file.referencingFiles;
if (referencingFiles.isNotEmpty) {
var fileIds = referencingFiles
.map(idProvider.fileState)
.sorted(compareNatural)
.join(' ');
sink.writelnWithIndent('referencingFiles: $fileIds');
}
}
void _writeUnlinkedUnitStore() {
sink.writelnWithIndent('unlinkedUnitStore');
sink.withIndent(() {
var groups = unlinkedUnitStore.map.entries.groupListsBy((element) {
return element.value.usageCount;
});
for (var groupEntry in groups.entries) {
var keys = groupEntry.value.map((e) => e.key).toList();
var shortKeys = idProvider.shortKeys(keys)..sort();
sink.writelnWithIndent('${groupEntry.key}: $shortKeys');
}
});
}
void _writeUriList(String name, Iterable<Uri> uriIterable) {
var uriStrList = <String>[];
for (var uri in uriIterable) {
if (configuration.omitSdkFiles && uri.isScheme('dart')) {
continue;
}
uriStrList.add('$uri');
}
if (uriStrList.isNotEmpty) {
uriStrList.sort();
sink.writelnWithIndent(name);
sink.withIndent(() {
for (var uriStr in uriStrList) {
sink.writelnWithIndent(uriStr);
}
});
}
}
}
class AnalyzerStatePrinterConfiguration {
Set<File> filesToPrintContent = {};
bool omitSdkFiles = true;
bool withResolvedKey = false;
}
/// 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<FileKind, String> _fileKind = Map.identity();
final Map<String, String> _keyToShort = {};
final Map<String, String> _shortToKey = {};
final Map<String, String> _apiSignature = {};
Set<FileState> _currentFiles = {};
Set<FileKind> _currentFileKinds = {};
Set<LibraryCycle> _currentCycles = {};
String apiSignature(String signature) {
var length = _apiSignature.length;
return _apiSignature[signature] ??= 'apiSignature_$length';
}
String fileKind(FileKind kind) {
if (!_currentFileKinds.contains(kind)) {
throw StateError('$kind');
}
return _fileKind[kind] ??= () {
switch (kind) {
case LibraryFileKind():
return 'library_${_fileKind.length}';
case PartOfNameFileKind():
return 'partOfName_${_fileKind.length}';
case PartOfUriKnownFileKind():
return 'partOfUriKnown_${_fileKind.length}';
case PartFileKind():
return 'partOfUriUnknown_${_fileKind.length}';
default:
throw UnimplementedError('${kind.runtimeType}');
}
}();
}
String fileState(FileState file) {
if (!_currentFiles.contains(file)) {
throw StateError('$file');
}
return _fileState[file] ??= 'file_${_fileState.length}';
}
String libraryCycle(LibraryCycle cycle) {
if (!_currentCycles.contains(cycle)) {
throw StateError('$cycle');
}
return _libraryCycle[cycle] ??= 'cycle_${_libraryCycle.length}';
}
/// Register that [kind] is an object that can be referenced.
void registerFileKind(FileKind kind) {
if (_currentFileKinds.contains(kind)) {
throw StateError('Duplicate: $kind');
}
_currentFileKinds.add(kind);
fileKind(kind);
}
/// 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 [cycle] is an object that can be referenced.
void registerLibraryCycle(LibraryCycle cycle) {
_currentCycles.add(cycle);
libraryCycle(cycle);
}
void resetRegisteredObject() {
_currentFiles = {};
_currentFileKinds = {};
_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);
}