From 8ded7af5f5821d65810f9ce62a4d9783e966b7a9 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Wed, 1 Jun 2022 06:35:14 +0000 Subject: [PATCH] Add LibraryOrAugmentationFileKind.exports/imports These will replace eventually FileState.exported/importedFiles, because only libraries or augmentation can have them. Also, we will build Export/ImportElement(s) from them, when google3 switches to using AnalysisDriver.buildPackageBundle(), so we are free to make changes to the way linker works. Change-Id: I66674dc7720aa704cfc36d16fc78e17dd05a8b93 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/246681 Commit-Queue: Konstantin Shcheglov Reviewed-by: Brian Wilkerson --- .../lib/src/dart/analysis/driver.dart | 2 +- .../lib/src/dart/analysis/file_state.dart | 199 +++++ .../lib/src/dart/analysis/unlinked_data.dart | 5 + .../lib/src/dart/micro/library_graph.dart | 1 + .../src/summary/package_bundle_reader.dart | 18 +- pkg/analyzer/lib/src/workspace/simple.dart | 7 +- .../src/dart/analysis/file_state_test.dart | 698 ++++++++++++++++++ .../context_collection_resolution.dart | 8 + .../src/summary/in_summary_source_test.dart | 57 -- pkg/analyzer/test/src/summary/test_all.dart | 2 - .../test/src/workspace/bazel_test.dart | 6 +- .../lib/check/equality.dart | 6 + pkg/analyzer_utilities/lib/check/type.dart | 9 + .../test/check/check_test.dart | 12 + 14 files changed, 963 insertions(+), 67 deletions(-) delete mode 100644 pkg/analyzer/test/src/summary/in_summary_source_test.dart diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart index 17a79a5a822..3aa510f210b 100644 --- a/pkg/analyzer/lib/src/dart/analysis/driver.dart +++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart @@ -86,7 +86,7 @@ import 'package:meta/meta.dart'; /// TODO(scheglov) Clean up the list of implicitly analyzed files. class AnalysisDriver implements AnalysisDriverGeneric { /// The version of data format, should be incremented on every format change. - static const int DATA_VERSION = 221; + static const int DATA_VERSION = 222; /// The number of exception contexts allowed to write. Once this field is /// zero, we stop writing any new exception contexts in this process. diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart index a6763f82876..66b5ac019e5 100644 --- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart +++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart @@ -85,6 +85,75 @@ class AugmentationUnknownFileStateKind extends AugmentationFileStateKind { }); } +/// Information about a single `import` directive. +class ExportDirectiveState { + final UnlinkedNamespaceDirective directive; + + ExportDirectiveState({ + required this.directive, + }); + + /// If [exportedSource] corresponds to a library, returns it. + Source? get exportedLibrarySource => null; + + /// Returns a [Source] that is referenced by this directive. If the are + /// configurations, selects the one which satisfies the conditions. + /// + /// Returns `null` if the selected URI is not valid, or cannot be resolved + /// into a [Source]. + Source? get exportedSource => null; +} + +/// [ExportDirectiveState] that has a valid URI that references a file. +class ExportDirectiveWithFile extends ExportDirectiveState { + final FileState exportedFile; + + ExportDirectiveWithFile({ + required super.directive, + required this.exportedFile, + }); + + /// Returns [exportedFile] if it is a library. + LibraryFileStateKind? get exportedLibrary { + final kind = exportedFile.kind; + if (kind is LibraryFileStateKind) { + return kind; + } + return null; + } + + @override + Source? get exportedLibrarySource { + if (exportedFile.kind is LibraryFileStateKind) { + return exportedSource; + } + return null; + } + + @override + Source get exportedSource => exportedFile.source; +} + +/// [ExportDirectiveState] with a URI that resolves to [InSummarySource]. +class ExportDirectiveWithInSummarySource extends ExportDirectiveState { + @override + final InSummarySource exportedSource; + + ExportDirectiveWithInSummarySource({ + required super.directive, + required this.exportedSource, + }); + + @override + Source? get exportedLibrarySource { + if (exportedSource.kind == InSummarySourceKind.library) { + return exportedSource; + } else { + return null; + } + } +} + /// A library from [SummaryDataStore]. class ExternalLibrary { final InSummarySource source; @@ -587,6 +656,7 @@ class FileState { return unit; } + /// TODO(scheglov) move to _fsState? String _selectRelativeUri(UnlinkedNamespaceDirective directive) { for (var configuration in directive.configurations) { var name = configuration.name; @@ -805,6 +875,7 @@ class FileState { imports.add( UnlinkedNamespaceDirective( configurations: [], + isSyntheticDartCoreImport: true, uri: 'dart:core', ), ); @@ -1193,6 +1264,77 @@ class FileUriProperties { bool get isSrc => (_flags & _isSrc) != 0; } +/// Information about a single `import` directive. +class ImportDirectiveState { + final UnlinkedNamespaceDirective directive; + + ImportDirectiveState({ + required this.directive, + }); + + /// If [importedSource] corresponds to a library, returns it. + Source? get importedLibrarySource => null; + + /// Returns a [Source] that is referenced by this directive. If the are + /// configurations, selects the one which satisfies the conditions. + /// + /// Returns `null` if the selected URI is not valid, or cannot be resolved + /// into a [Source]. + Source? get importedSource => null; + + bool get isSyntheticDartCoreImport => directive.isSyntheticDartCoreImport; +} + +/// [ImportDirectiveState] that has a valid URI that references a file. +class ImportDirectiveWithFile extends ImportDirectiveState { + final FileState importedFile; + + ImportDirectiveWithFile({ + required super.directive, + required this.importedFile, + }); + + /// Returns [importedFile] if it is a library. + LibraryFileStateKind? get importedLibrary { + final kind = importedFile.kind; + if (kind is LibraryFileStateKind) { + return kind; + } + return null; + } + + @override + Source? get importedLibrarySource { + if (importedFile.kind is LibraryFileStateKind) { + return importedSource; + } + return null; + } + + @override + Source get importedSource => importedFile.source; +} + +/// [ImportDirectiveState] with a URI that resolves to [InSummarySource]. +class ImportDirectiveWithInSummarySource extends ImportDirectiveState { + @override + final InSummarySource importedSource; + + ImportDirectiveWithInSummarySource({ + required super.directive, + required this.importedSource, + }); + + @override + Source? get importedLibrarySource { + if (importedSource.kind == InSummarySourceKind.library) { + return importedSource; + } else { + return null; + } + } +} + class LibraryFileStateKind extends LibraryOrAugmentationFileKind { /// The name of the library from the `library` directive. /// Or `null` if no `library` directive. @@ -1209,10 +1351,67 @@ class LibraryFileStateKind extends LibraryOrAugmentationFileKind { } abstract class LibraryOrAugmentationFileKind extends FileStateKind { + List? _exports; + List? _imports; + LibraryOrAugmentationFileKind({ required super.file, }); + List get exports { + return _exports ??= file.unlinked2.exports.map((directive) { + final uriStr = file._selectRelativeUri(directive); + return file._fileForRelativeUri(uriStr).map( + (refFile) { + if (refFile != null) { + refFile.referencingFiles.add(file); + return ExportDirectiveWithFile( + directive: directive, + exportedFile: refFile, + ); + } else { + return ExportDirectiveState( + directive: directive, + ); + } + }, + (externalLibrary) { + return ExportDirectiveWithInSummarySource( + directive: directive, + exportedSource: externalLibrary.source, + ); + }, + ); + }).toList(); + } + + List get imports { + return _imports ??= file.unlinked2.imports.map((directive) { + final uriStr = file._selectRelativeUri(directive); + return file._fileForRelativeUri(uriStr).map( + (refFile) { + if (refFile != null) { + refFile.referencingFiles.add(file); + return ImportDirectiveWithFile( + directive: directive, + importedFile: refFile, + ); + } else { + return ImportDirectiveState( + directive: directive, + ); + } + }, + (externalLibrary) { + return ImportDirectiveWithInSummarySource( + directive: directive, + importedSource: externalLibrary.source, + ); + }, + ); + }).toList(); + } + bool hasAugmentation(AugmentationFileStateKind augmentation) { return file.augmentationFiles.contains(augmentation.file); } diff --git a/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart b/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart index 605461c70b8..d9a7714ddff 100644 --- a/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart +++ b/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart @@ -159,12 +159,15 @@ class UnlinkedNamespaceDirective { /// The configurations that control which library will actually be used. final List configurations; + final bool isSyntheticDartCoreImport; + /// The URI referenced by this directive, nad used by default when none /// of the [configurations] matches. final String uri; UnlinkedNamespaceDirective({ required this.configurations, + this.isSyntheticDartCoreImport = false, required this.uri, }); @@ -174,6 +177,7 @@ class UnlinkedNamespaceDirective { () => UnlinkedNamespaceDirectiveConfiguration.read(reader), ), uri: reader.readStringUtf8(), + isSyntheticDartCoreImport: reader.readBool(), ); } @@ -185,6 +189,7 @@ class UnlinkedNamespaceDirective { }, ); sink.writeStringUtf8(uri); + sink.writeBool(isSyntheticDartCoreImport); } } diff --git a/pkg/analyzer/lib/src/dart/micro/library_graph.dart b/pkg/analyzer/lib/src/dart/micro/library_graph.dart index f560b033a2a..4a51ff166c2 100644 --- a/pkg/analyzer/lib/src/dart/micro/library_graph.dart +++ b/pkg/analyzer/lib/src/dart/micro/library_graph.dart @@ -975,6 +975,7 @@ class _FileStateUnlinked { imports.add( UnlinkedNamespaceDirective( configurations: [], + isSyntheticDartCoreImport: true, uri: 'dart:core', ), ); diff --git a/pkg/analyzer/lib/src/summary/package_bundle_reader.dart b/pkg/analyzer/lib/src/summary/package_bundle_reader.dart index 34780d52338..8648d5581b8 100644 --- a/pkg/analyzer/lib/src/summary/package_bundle_reader.dart +++ b/pkg/analyzer/lib/src/summary/package_bundle_reader.dart @@ -65,7 +65,13 @@ class InSummarySource extends BasicSource { /// The summary file where this source was defined. final String summaryPath; - InSummarySource(super.uri, this.summaryPath); + final InSummarySourceKind kind; + + InSummarySource({ + required Uri uri, + required this.summaryPath, + required this.kind, + }) : super(uri); @override TimestampedData get contents => TimestampedData(0, ''); @@ -77,6 +83,8 @@ class InSummarySource extends BasicSource { String toString() => uri.toString(); } +enum InSummarySourceKind { library, part } + /// The [UriResolver] that knows about sources that are served from their /// summaries. class InSummaryUriResolver extends UriResolver { @@ -92,7 +100,13 @@ class InSummaryUriResolver extends UriResolver { String uriString = uri.toString(); String? summaryPath = _dataStore.uriToSummaryPath[uriString]; if (summaryPath != null) { - return InSummarySource(uri, summaryPath); + final isLibrary = _dataStore._libraryUris.contains(uriString); + return InSummarySource( + uri: uri, + summaryPath: summaryPath, + kind: + isLibrary ? InSummarySourceKind.library : InSummarySourceKind.part, + ); } return null; } diff --git a/pkg/analyzer/lib/src/workspace/simple.dart b/pkg/analyzer/lib/src/workspace/simple.dart index dc9b69410f0..6430e61c340 100644 --- a/pkg/analyzer/lib/src/workspace/simple.dart +++ b/pkg/analyzer/lib/src/workspace/simple.dart @@ -43,11 +43,10 @@ abstract class SimpleWorkspace extends Workspace { DartSdk? sdk, SummaryDataStore? summaryData, ) { - if (summaryData != null) { - throw UnsupportedError( - 'Summary files are not supported in a Pub workspace.'); - } List resolvers = []; + if (summaryData != null) { + resolvers.add(InSummaryUriResolver(summaryData)); + } if (sdk != null) { resolvers.add(DartUriResolver(sdk)); } diff --git a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart index c0b0ac1e838..8dd30401f01 100644 --- a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart +++ b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart @@ -7,6 +7,7 @@ import 'dart:typed_data'; import 'package:analyzer/dart/analysis/declared_variables.dart'; import 'package:analyzer/dart/analysis/features.dart'; +import 'package:analyzer/dart/sdk/build_sdk_summary.dart'; import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/src/context/packages.dart'; import 'package:analyzer/src/dart/analysis/byte_store.dart'; @@ -21,12 +22,15 @@ import 'package:analyzer/src/generated/engine.dart' show AnalysisOptions, AnalysisOptionsImpl; import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/source/package_map_resolver.dart'; +import 'package:analyzer/src/summary/package_bundle_reader.dart'; import 'package:analyzer/src/test_utilities/mock_sdk.dart'; import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart'; import 'package:analyzer/src/util/either.dart'; import 'package:analyzer/src/workspace/basic.dart'; +import 'package:analyzer_utilities/check/check.dart'; import 'package:convert/convert.dart'; import 'package:crypto/crypto.dart'; +import 'package:meta/meta.dart'; import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; @@ -121,10 +125,31 @@ class FileSystemState_BazelWorkspaceTest extends BazelWorkspaceResolutionTest { @reflectiveTest class FileSystemState_PubPackageTest extends PubPackageResolutionTest { + FileState get _dartAsyncState { + return fileStateForUriStr('dart:async'); + } + + FileState get _dartCoreState { + return fileStateForUriStr('dart:core'); + } + + FileState get _dartMathState { + return fileStateForUriStr('dart:math'); + } + FileState fileStateFor(File file) { return fsStateFor(file).getFileForPath(file.path); } + FileState fileStateForUri(Uri uri) { + return fsStateFor(testFile).getFileForUri(uri).t1!; + } + + FileState fileStateForUriStr(String uriStr) { + final uri = Uri.parse(uriStr); + return fileStateForUri(uri); + } + FileSystemState fsStateFor(File file) { return driverFor(file.path).fsState; } @@ -539,6 +564,487 @@ import augment 'c.dart'; }); } + test_newFile_library_exports_dart() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +export 'dart:async'; +export 'dart:math'; +'''); + + final aState = fileStateFor(a); + aState.assertKind((kind) { + kind as LibraryFileStateKind; + check(kind.exports).matches([ + (export) => export.isLibrary(_dartAsyncState), + (export) => export.isLibrary(_dartMathState), + ]); + }); + } + + test_newFile_library_exports_inSummary_library() async { + // Prepare a bundle where `package:foo/foo.dart` is a library. + final librarySummaryFiles = []; + { + final fooRoot = getFolder('$workspaceRootPath/foo'); + + newFile('${fooRoot.path}/lib/foo.dart', 'class F {}'); + + final fooPackageConfigFile = getFile( + '${fooRoot.path}/.dart_tool/package_config.json', + ); + + writePackageConfig( + fooPackageConfigFile.path, + PackageConfigFileBuilder()..add(name: 'foo', rootPath: fooRoot.path), + ); + + final analysisDriver = driverFor(fooRoot.path); + final bundleBytes = await analysisDriver.buildPackageBundle( + uriList: [ + Uri.parse('package:foo/foo.dart'), + ], + ); + + final bundleFile = + resourceProvider.getFile('/home/summaries/packages.sum'); + bundleFile.writeAsBytesSync(bundleBytes); + + librarySummaryFiles.add(bundleFile); + + // Delete, so it is not available as a file. + // We don't have a package config for it anyway, but just to be sure. + fooRoot.delete(); + } + + // Prepare for recreating the collection, with summaries. + sdkSummaryFile = await _writeSdkSummary(); + this.librarySummaryFiles = librarySummaryFiles; + + disposeAnalysisContextCollection(); + + final a = newFile('$testPackageLibPath/a.dart', r''' +export 'dart:async'; +export 'package:foo/foo.dart'; +export 'b.dart'; +'''); + + final b = getFile('$testPackageLibPath/b.dart'); + + final aState = fileStateFor(a); + final bState = fileStateFor(b); + + aState.assertKind((kind) { + kind as LibraryFileStateKind; + check(kind.exports).matches([ + (import) { + final expected = Uri.parse('dart:async'); + import.withInSummaryLibrary(expected); + }, + (import) { + final expected = Uri.parse('package:foo/foo.dart'); + import.withInSummaryLibrary(expected); + }, + (import) => import.isLibrary(bState), + ]); + }); + } + + test_newFile_library_exports_inSummary_part() async { + // Prepare a bundle where `package:foo/foo2.dart` is a part. + final librarySummaryFiles = []; + { + final fooRoot = getFolder('$workspaceRootPath/foo'); + + newFile('${fooRoot.path}/lib/foo.dart', r''' +part 'foo2.dart'; +'''); + + newFile('${fooRoot.path}/lib/foo2.dart', r''' +part of 'foo.dart'; +'''); + + final fooPackageConfigFile = getFile( + '${fooRoot.path}/.dart_tool/package_config.json', + ); + + writePackageConfig( + fooPackageConfigFile.path, + PackageConfigFileBuilder()..add(name: 'foo', rootPath: fooRoot.path), + ); + + final analysisDriver = driverFor(fooRoot.path); + final bundleBytes = await analysisDriver.buildPackageBundle( + uriList: [ + Uri.parse('package:foo/foo.dart'), + ], + ); + + final bundleFile = + resourceProvider.getFile('/home/summaries/packages.sum'); + bundleFile.writeAsBytesSync(bundleBytes); + + librarySummaryFiles.add(bundleFile); + + // Delete, so it is not available as a file. + // We don't have a package config for it anyway, but just to be sure. + fooRoot.delete(); + } + + // Prepare for recreating the collection, with summaries. + sdkSummaryFile = await _writeSdkSummary(); + this.librarySummaryFiles = librarySummaryFiles; + + disposeAnalysisContextCollection(); + + final a = newFile('$testPackageLibPath/a.dart', r''' +export 'package:foo/foo2.dart'; +export 'b.dart'; +'''); + + final b = getFile('$testPackageLibPath/b.dart'); + + final aState = fileStateFor(a); + final bState = fileStateFor(b); + + aState.assertKind((kind) { + kind as LibraryFileStateKind; + check(kind.exports).matches([ + (export) { + final expected = Uri.parse('package:foo/foo2.dart'); + export.withInSummaryNotLibrary(expected); + }, + (export) => export.isLibrary(bState), + ]); + }); + } + + test_newFile_library_exports_invalidRelativeUri() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +export '::net'; +'''); + + final aState = fileStateFor(a); + aState.assertKind((kind) { + kind as LibraryFileStateKind; + check(kind.exports).matches([ + (export) => export.isNotFile(), + ]); + }); + } + + test_newFile_library_exports_package() async { + final a = newFile('$testPackageLibPath/a.dart', ''); + final b = newFile('$testPackageLibPath/b.dart', ''); + + final c = newFile('$testPackageLibPath/c.dart', r''' +export 'a.dart'; +export 'package:test/b.dart'; +'''); + + final aState = fileStateFor(a); + final bState = fileStateFor(b); + final cState = fileStateFor(c); + + cState.assertKind((kind) { + kind as LibraryFileStateKind; + check(kind.exports).matches([ + (export) => export.isLibrary(aState), + (export) => export.isLibrary(bState), + ]); + }); + } + + test_newFile_library_exports_part() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +part of my.lib; +'''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +export 'a.dart'; +'''); + + final aState = fileStateFor(a); + final bState = fileStateFor(b); + + bState.assertKind((kind) { + kind as LibraryFileStateKind; + check(kind.exports).matches([ + (export) => export.isFile(aState), + ]); + }); + } + + test_newFile_library_imports_library_augmentation() async { + final b = newFile('$testPackageLibPath/b.dart', r''' +library augment 'a.dart'; +'''); + + final c = newFile('$testPackageLibPath/c.dart', r''' +import 'b.dart'; +'''); + + final bState = fileStateFor(b); + final cState = fileStateFor(c); + + cState.assertKind((kind) { + kind as LibraryFileStateKind; + check(kind.imports).matches([ + (import) => import.isFile(bState), + (import) => import + ..isLibrary(_dartCoreState) + ..isSyntheticDartCoreImport.isTrue, + ]); + }); + } + + test_newFile_library_imports_library_dart() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +import 'dart:async'; +import 'dart:math'; +'''); + + final aState = fileStateFor(a); + aState.assertKind((kind) { + kind as LibraryFileStateKind; + check(kind.imports).matches([ + (import) => import.isLibrary(_dartAsyncState), + (import) => import.isLibrary(_dartMathState), + (import) => import + ..isLibrary(_dartCoreState) + ..isSyntheticDartCoreImport.isTrue, + ]); + }); + } + + test_newFile_library_imports_library_dart_explicitDartCore() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +import 'dart:core'; +import 'dart:math'; +'''); + + final aState = fileStateFor(a); + aState.assertKind((kind) { + kind as LibraryFileStateKind; + check(kind.imports).matches([ + (import) => import + ..isLibrary(_dartCoreState) + ..isSyntheticDartCoreImport.isFalse, + (import) => import.isLibrary(_dartMathState), + ]); + }); + } + + test_newFile_library_imports_library_inSummary_library() async { + // Prepare a bundle where `package:foo/foo.dart` is a library. + final librarySummaryFiles = []; + { + final fooRoot = getFolder('$workspaceRootPath/foo'); + + newFile('${fooRoot.path}/lib/foo.dart', 'class F {}'); + + final fooPackageConfigFile = getFile( + '${fooRoot.path}/.dart_tool/package_config.json', + ); + + writePackageConfig( + fooPackageConfigFile.path, + PackageConfigFileBuilder()..add(name: 'foo', rootPath: fooRoot.path), + ); + + final analysisDriver = driverFor(fooRoot.path); + final bundleBytes = await analysisDriver.buildPackageBundle( + uriList: [ + Uri.parse('package:foo/foo.dart'), + ], + ); + + final bundleFile = + resourceProvider.getFile('/home/summaries/packages.sum'); + bundleFile.writeAsBytesSync(bundleBytes); + + librarySummaryFiles.add(bundleFile); + + // Delete, so it is not available as a file. + // We don't have a package config for it anyway, but just to be sure. + fooRoot.delete(); + } + + // Prepare for recreating the collection, with summaries. + sdkSummaryFile = await _writeSdkSummary(); + this.librarySummaryFiles = librarySummaryFiles; + + disposeAnalysisContextCollection(); + + final a = newFile('$testPackageLibPath/a.dart', r''' +import 'dart:async'; +import 'package:foo/foo.dart'; +import 'b.dart'; +'''); + + final b = getFile('$testPackageLibPath/b.dart'); + + final aState = fileStateFor(a); + final bState = fileStateFor(b); + + aState.assertKind((kind) { + kind as LibraryFileStateKind; + check(kind.imports).matches([ + (import) { + final expected = Uri.parse('dart:async'); + import.withInSummaryLibrary(expected); + }, + (import) { + final expected = Uri.parse('package:foo/foo.dart'); + import.withInSummaryLibrary(expected); + }, + (import) => import.isLibrary(bState), + (import) { + final expected = Uri.parse('dart:core'); + import + ..withInSummaryLibrary(expected) + ..isSyntheticDartCoreImport.isTrue; + }, + ]); + }); + } + + test_newFile_library_imports_library_inSummary_part() async { + // Prepare a bundle where `package:foo/foo2.dart` is a part. + final librarySummaryFiles = []; + { + final fooRoot = getFolder('$workspaceRootPath/foo'); + + newFile('${fooRoot.path}/lib/foo.dart', r''' +part 'foo2.dart'; +'''); + + newFile('${fooRoot.path}/lib/foo2.dart', r''' +part of 'foo.dart'; +'''); + + final fooPackageConfigFile = getFile( + '${fooRoot.path}/.dart_tool/package_config.json', + ); + + writePackageConfig( + fooPackageConfigFile.path, + PackageConfigFileBuilder()..add(name: 'foo', rootPath: fooRoot.path), + ); + + final analysisDriver = driverFor(fooRoot.path); + final bundleBytes = await analysisDriver.buildPackageBundle( + uriList: [ + Uri.parse('package:foo/foo.dart'), + ], + ); + + final bundleFile = + resourceProvider.getFile('/home/summaries/packages.sum'); + bundleFile.writeAsBytesSync(bundleBytes); + + librarySummaryFiles.add(bundleFile); + + // Delete, so it is not available as a file. + // We don't have a package config for it anyway, but just to be sure. + fooRoot.delete(); + } + + // Prepare for recreating the collection, with summaries. + sdkSummaryFile = await _writeSdkSummary(); + this.librarySummaryFiles = librarySummaryFiles; + + disposeAnalysisContextCollection(); + + final a = newFile('$testPackageLibPath/a.dart', r''' +import 'package:foo/foo2.dart'; +import 'b.dart'; +'''); + + final b = getFile('$testPackageLibPath/b.dart'); + + final aState = fileStateFor(a); + final bState = fileStateFor(b); + + aState.assertKind((kind) { + kind as LibraryFileStateKind; + check(kind.imports).matches([ + (export) { + final expected = Uri.parse('package:foo/foo2.dart'); + export.withInSummaryNotLibrary(expected); + }, + (export) => export.isLibrary(bState), + (export) { + final expected = Uri.parse('dart:core'); + export + ..withInSummaryLibrary(expected) + ..isSyntheticDartCoreImport.isTrue; + }, + ]); + }); + } + + test_newFile_library_imports_library_invalidRelativeUri() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +import '::net'; +'''); + + final aState = fileStateFor(a); + aState.assertKind((kind) { + kind as LibraryFileStateKind; + check(kind.imports).matches([ + (import) => import.isNotFile(), + (import) => import.isLibrary(_dartCoreState), + ]); + }); + } + + test_newFile_library_imports_library_package() async { + final a = newFile('$testPackageLibPath/a.dart', ''); + final b = newFile('$testPackageLibPath/b.dart', ''); + + final c = newFile('$testPackageLibPath/c.dart', r''' +import 'a.dart'; +import 'package:test/b.dart'; +'''); + + final aState = fileStateFor(a); + final bState = fileStateFor(b); + final cState = fileStateFor(c); + + cState.assertKind((kind) { + kind as LibraryFileStateKind; + check(kind.imports).matches([ + (import) => import.isLibrary(aState), + (import) => import.isLibrary(bState), + (import) => import + ..isLibrary(_dartCoreState) + ..isSyntheticDartCoreImport.isTrue, + ]); + }); + } + + test_newFile_library_imports_library_part() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +part of my.lib; +'''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +import 'a.dart'; +'''); + + final aState = fileStateFor(a); + final bState = fileStateFor(b); + + bState.assertKind((kind) { + kind as LibraryFileStateKind; + check(kind.imports).matches([ + (import) => import.isFile(aState), + (import) => import + ..isLibrary(_dartCoreState) + ..isSyntheticDartCoreImport.isTrue, + ]); + }); + } + test_newFile_library_includePart_withoutPartOf() async { final a = newFile('$testPackageLibPath/a.dart', r''' part 'b.dart'; @@ -1609,6 +2115,16 @@ class A2 {} }).toList(); expect(actualFiles, expected); } + + Future _writeSdkSummary() async { + final file = resourceProvider.getFile('/home/summaries/sdk.sum'); + final bytes = await buildSdkSummary2( + resourceProvider: resourceProvider, + sdkPath: sdkRoot.path, + ); + file.writeAsBytesSync(bytes); + return file; + } } @reflectiveTest @@ -2466,6 +2982,188 @@ extension on PartOfNameFileStateKind { } } +extension on CheckTarget { + @useResult + CheckTarget get importedLibrarySource { + return nest( + value.importedLibrarySource, + (selected) => 'importedLibrarySource ${valueStr(selected)}', + ); + } + + @useResult + CheckTarget get isSyntheticDartCoreImport { + return nest( + value.isSyntheticDartCoreImport, + (selected) => 'isSyntheticDartCoreImport ${valueStr(selected)}', + ); + } + + /// Is [ImportDirectiveWithFile], but not a library. + void isFile(FileState expected) { + this.isA() + ..importedFile.isIdenticalTo(expected) + ..importedSource.uri.isEqualTo(expected.uri) + ..importedLibrary.isNull + ..importedLibrarySource.isNull; + } + + /// Is [ImportDirectiveWithFile], and is a library. + void isLibrary(FileState expected) { + final expectedKind = expected.kind as LibraryFileStateKind; + this.isA() + ..importedFile.isIdenticalTo(expected) + ..importedSource.uri.isEqualTo(expected.uri) + ..importedLibrary.isNotNull.isIdenticalTo(expectedKind) + ..importedLibrarySource.isNotNull.uri.isEqualTo(expected.uri); + } + + /// Exactly [ImportDirectiveState], even the file is not known. + void isNotFile() { + hasExactType(); + } + + void withInSummaryLibrary(Uri expected) { + this.isA() + ..importedSource.uri.isEqualTo(expected) + ..importedLibrarySource.isNotNull.uri.isEqualTo(expected); + } + + void withInSummaryNotLibrary(Uri expected) { + this.isA() + ..importedSource.uri.isEqualTo(expected) + ..importedLibrarySource.isNull; + } +} + +extension on CheckTarget { + @useResult + CheckTarget get exportedLibrarySource { + return nest( + value.exportedLibrarySource, + (selected) => 'exportedLibrarySource ${valueStr(selected)}', + ); + } + + /// Is [ExportDirectiveWithFile], but not a library. + void isFile(FileState expected) { + this.isA() + ..exportedFile.isIdenticalTo(expected) + ..exportedSource.uri.isEqualTo(expected.uri) + ..exportedLibrary.isNull + ..exportedLibrarySource.isNull; + } + + /// Is [ExportDirectiveWithFile], and is a library. + void isLibrary(FileState expected) { + final expectedKind = expected.kind as LibraryFileStateKind; + this.isA() + ..exportedFile.isIdenticalTo(expected) + ..exportedSource.uri.isEqualTo(expected.uri) + ..exportedLibrary.isIdenticalTo(expectedKind) + ..exportedLibrarySource.isNotNull.uri.isEqualTo(expected.uri); + } + + /// Exactly [ExportDirectiveState], even the file is not known. + void isNotFile() { + hasExactType(); + } + + void withInSummaryLibrary(Uri expected) { + this.isA() + ..exportedSource.uri.isEqualTo(expected) + ..exportedLibrarySource.isNotNull.uri.isEqualTo(expected); + } + + void withInSummaryNotLibrary(Uri expected) { + this.isA() + ..exportedSource.uri.isEqualTo(expected) + ..exportedLibrarySource.isNull; + } +} + +extension on CheckTarget { + @useResult + CheckTarget get importedFile { + return nest( + value.importedFile, + (selected) => 'importedFile ${valueStr(selected)}', + ); + } + + @useResult + CheckTarget get importedLibrary { + return nest( + value.importedLibrary, + (selected) => 'importedLibrary ${valueStr(selected)}', + ); + } + + @useResult + CheckTarget get importedSource { + return nest( + value.importedSource, + (selected) => 'importedSource ${valueStr(selected)}', + ); + } +} + +extension on CheckTarget { + @useResult + CheckTarget get exportedFile { + return nest( + value.exportedFile, + (selected) => 'exportedFile ${valueStr(selected)}', + ); + } + + @useResult + CheckTarget get exportedLibrary { + return nest( + value.exportedLibrary, + (selected) => 'exportedLibrary ${valueStr(selected)}', + ); + } + + @useResult + CheckTarget get exportedSource { + return nest( + value.exportedSource, + (selected) => 'exportedSource ${valueStr(selected)}', + ); + } +} + +extension on CheckTarget { + @useResult + CheckTarget get importedSource { + return nest( + value.importedSource, + (selected) => 'importedSource ${valueStr(selected)}', + ); + } +} + +extension on CheckTarget { + @useResult + CheckTarget get exportedSource { + return nest( + value.exportedSource, + (selected) => 'exportedSource ${valueStr(selected)}', + ); + } +} + +extension on CheckTarget { + @useResult + CheckTarget get uri { + return nest( + value.uri, + (selected) => 'uri ${valueStr(selected)}', + ); + } +} + extension _Either2Extension on Either2 { T1 get t1 { late T1 result; diff --git a/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart b/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart index 8773df40316..c2aa8453d44 100644 --- a/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart +++ b/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart @@ -129,6 +129,12 @@ abstract class ContextResolutionTest /// to this path, instead of the given path. String? pathForContextSelection; + /// Optional Dart SDK summary file, to be used instead of [sdkRoot]. + File? sdkSummaryFile; + + /// Optional summaries to provide for the collection. + List? librarySummaryFiles; + List get additionalMockSdkLibraries => []; List get collectionIncludedPaths; @@ -259,6 +265,8 @@ abstract class ContextResolutionTest resourceProvider: resourceProvider, retainDataForTesting: retainDataForTesting, sdkPath: sdkRoot.path, + sdkSummaryPath: sdkSummaryFile?.path, + librarySummaryPaths: librarySummaryFiles?.map((e) => e.path).toList(), updateAnalysisOptions: updateAnalysisOptions, ); diff --git a/pkg/analyzer/test/src/summary/in_summary_source_test.dart b/pkg/analyzer/test/src/summary/in_summary_source_test.dart deleted file mode 100644 index 0587667e6c0..00000000000 --- a/pkg/analyzer/test/src/summary/in_summary_source_test.dart +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2016, 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/src/generated/source.dart'; -import 'package:analyzer/src/summary/package_bundle_reader.dart'; -import 'package:test/test.dart'; -import 'package:test_reflective_loader/test_reflective_loader.dart'; - -main() { - defineReflectiveSuite(() { - defineReflectiveTests(InSummarySourceTest); - }); -} - -@reflectiveTest -class InSummarySourceTest { - test_InSummarySource() { - var sourceFactory = SourceFactory([ - InSummaryUriResolver( - MockSummaryDataStore.fake({ - 'package:foo/foo.dart': 'foo.sum', - 'package:foo/src/foo_impl.dart': 'foo.sum', - 'package:bar/baz.dart': 'bar.sum', - }), - ) - ]); - - var source = - sourceFactory.forUri('package:foo/foo.dart') as InSummarySource; - expect(source, isNotNull); - expect(source.summaryPath, 'foo.sum'); - - source = sourceFactory.forUri('package:foo/src/foo_impl.dart') - as InSummarySource; - expect(source, isNotNull); - expect(source.summaryPath, 'foo.sum'); - - source = sourceFactory.forUri('package:bar/baz.dart') as InSummarySource; - expect(source, isNotNull); - expect(source.summaryPath, 'bar.sum'); - } -} - -class MockSummaryDataStore implements SummaryDataStore { - @override - final Map uriToSummaryPath; - - MockSummaryDataStore(this.uriToSummaryPath); - - factory MockSummaryDataStore.fake(Map uriToSummary) { - return MockSummaryDataStore(uriToSummary); - } - - @override - noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -} diff --git a/pkg/analyzer/test/src/summary/test_all.dart b/pkg/analyzer/test/src/summary/test_all.dart index 41786a1e604..9c877391ca4 100644 --- a/pkg/analyzer/test/src/summary/test_all.dart +++ b/pkg/analyzer/test/src/summary/test_all.dart @@ -7,7 +7,6 @@ import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'api_signature_test.dart' as api_signature; import 'elements_test.dart' as elements; import 'flat_buffers_test.dart' as flat_buffers; -import 'in_summary_source_test.dart' as in_summary_source; import 'macro_test.dart' as macro; import 'top_level_inference_test.dart' as top_level_inference; @@ -16,7 +15,6 @@ main() { api_signature.main(); elements.main(); flat_buffers.main(); - in_summary_source.main(); macro.main(); top_level_inference.main(); }, name: 'summary'); diff --git a/pkg/analyzer/test/src/workspace/bazel_test.dart b/pkg/analyzer/test/src/workspace/bazel_test.dart index a79b85e0a19..784c9b8fbba 100644 --- a/pkg/analyzer/test/src/workspace/bazel_test.dart +++ b/pkg/analyzer/test/src/workspace/bazel_test.dart @@ -901,7 +901,11 @@ class BazelWorkspacePackageTest with ResourceProviderMixin { Source _inSummarySource(String uriStr) { var uri = Uri.parse(uriStr); - return InSummarySource(uri, ''); + return InSummarySource( + uri: uri, + summaryPath: '', + kind: InSummarySourceKind.library, + ); } void _setUpPackage() { diff --git a/pkg/analyzer_utilities/lib/check/equality.dart b/pkg/analyzer_utilities/lib/check/equality.dart index 2d265b88a4d..9701bb35dcd 100644 --- a/pkg/analyzer_utilities/lib/check/equality.dart +++ b/pkg/analyzer_utilities/lib/check/equality.dart @@ -11,6 +11,12 @@ extension EqualityExtension on CheckTarget { } } + void isIdenticalTo(Object? other) { + if (!identical(value, other)) { + fail('is not identical to $other'); + } + } + void isNotEqualTo(Object? other) { if (value == other) { fail('is equal to $other'); diff --git a/pkg/analyzer_utilities/lib/check/type.dart b/pkg/analyzer_utilities/lib/check/type.dart index ea28b9e1690..c261e2a74fd 100644 --- a/pkg/analyzer_utilities/lib/check/type.dart +++ b/pkg/analyzer_utilities/lib/check/type.dart @@ -5,6 +5,15 @@ import 'package:analyzer_utilities/check/check.dart'; extension IsExtension on CheckTarget { + CheckTarget hasExactType() { + final value = this.value; + if (value.runtimeType == U) { + return nest(value as U, (_) => 'is of type $U'); + } else { + fail('is not of type $U'); + } + } + CheckTarget isA() { final value = this.value; if (value is U) { diff --git a/pkg/analyzer_utilities/test/check/check_test.dart b/pkg/analyzer_utilities/test/check/check_test.dart index 9e5ee83eb69..0f9bda17e0f 100644 --- a/pkg/analyzer_utilities/test/check/check_test.dart +++ b/pkg/analyzer_utilities/test/check/check_test.dart @@ -29,6 +29,14 @@ void main() { _fails(() => check(false).isTrue); }); }); + group('equality', () { + test('isIdenticalTo', () { + final a = Object(); + final b = Object(); + check(a).isIdenticalTo(a); + _fails(() => check(a).isIdenticalTo(b)); + }); + }); group('int', () { test('isEqualTo', () { check(0).isEqualTo(0); @@ -332,6 +340,10 @@ void main() { }); }); group('type', () { + test('hasExactType', () { + check(42).hasExactType(); + _fails(() => check(42 as dynamic).hasExactType()); + }); test('isA', () { check(0).isA(); _fails(() => check('abc' as dynamic).isA());