blob: 025443188582e469e995203022b7d7293834cb91 [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 'dart:typed_data';
import 'package:_fe_analyzer_shared/src/macros/executor/multi_executor.dart'
as macro;
import 'package:analyzer/dart/analysis/declared_variables.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/context/context.dart';
import 'package:analyzer/src/dart/analysis/session.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/class_hierarchy.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/source/package_map_resolver.dart';
import 'package:analyzer/src/summary2/bundle_reader.dart';
import 'package:analyzer/src/summary2/informative_data.dart';
import 'package:analyzer/src/summary2/kernel_compilation_service.dart';
import 'package:analyzer/src/summary2/link.dart';
import 'package:analyzer/src/summary2/linked_element_factory.dart';
import 'package:analyzer/src/summary2/macro.dart';
import 'package:analyzer/src/summary2/reference.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/uri.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as package_path;
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../util/feature_sets.dart';
import 'test_strategies.dart';
/// A base for testing building elements.
@reflectiveTest
abstract class ElementsBaseTest with ResourceProviderMixin {
/// The shared SDK bundle, computed once and shared among test invocations.
static _SdkBundle? _sdkBundle;
/// The instance of macro executor that is used for all macros.
final macro.MultiMacroExecutor _macroExecutor = macro.MultiMacroExecutor();
/// The set of features enabled in this test.
FeatureSet featureSet = FeatureSets.latestWithExperiments;
DeclaredVariables declaredVariables = DeclaredVariables();
late final SourceFactory sourceFactory;
late final FolderBasedDartSdk sdk;
ElementsBaseTest() {
var sdkRoot = newFolder('/sdk');
createMockSdk(
resourceProvider: resourceProvider,
root: sdkRoot,
);
sdk = FolderBasedDartSdk(resourceProvider, sdkRoot);
sourceFactory = SourceFactory([
DartUriResolver(sdk),
PackageMapUriResolver(resourceProvider, {
'test': [
getFolder('/home/test/lib'),
],
}),
ResourceUriResolver(resourceProvider),
]);
}
/// We need to test both cases - when we keep linking libraries (happens for
/// new or invalidated libraries), and when we load libraries from bytes
/// (happens internally in Blaze or when we have cached summaries).
bool get keepLinkingLibraries;
Future<_SdkBundle> get sdkBundle async {
if (_sdkBundle != null) {
return _sdkBundle!;
}
var featureSet = FeatureSet.latestLanguageVersion();
var inputLibraries = <LinkInputLibrary>[];
for (var sdkLibrary in sdk.sdkLibraries) {
var source = sourceFactory.resolveUri(null, sdkLibrary.shortName)!;
var text = getFile(source.fullName).readAsStringSync();
var unit = parseText(source, text, featureSet);
var inputUnits = <LinkInputUnit>[];
_addLibraryUnits(source, unit, inputUnits, featureSet);
inputLibraries.add(
LinkInputLibrary(
source: source,
units: inputUnits,
),
);
}
var elementFactory = LinkedElementFactory(
AnalysisContextImpl(
SynchronousSession(
AnalysisOptionsImpl(),
declaredVariables,
),
sourceFactory,
),
_AnalysisSessionForLinking(),
Reference.root(),
);
var sdkLinkResult = await link(elementFactory, inputLibraries);
return _sdkBundle = _SdkBundle(
resolutionBytes: sdkLinkResult.resolutionBytes,
);
}
String get testFilePath => '$testPackageLibPath/test.dart';
String get testPackageLibPath => '$testPackageRootPath/lib';
String get testPackageRootPath => '$workspaceRootPath/test';
String get workspaceRootPath => '/home';
void addSource(String path, String contents) {
newFile(path, contents);
}
Future<LibraryElementImpl> buildLibrary(
String text, {
bool allowErrors = false,
bool dumpSummaries = false,
List<Set<String>>? preBuildSequence,
}) async {
var testFile = newFile(testFilePath, text);
var testUri = sourceFactory.pathToUri(testFile.path)!;
var testSource = sourceFactory.forUri2(testUri)!;
var inputLibraries = <LinkInputLibrary>[];
_addNonDartLibraries({}, inputLibraries, testSource);
var unitsInformativeBytes = <Uri, Uint8List>{};
for (var inputLibrary in inputLibraries) {
for (var inputUnit in inputLibrary.units) {
var informativeBytes = writeUnitInformative(inputUnit.unit);
unitsInformativeBytes[inputUnit.uri] = informativeBytes;
}
}
var analysisContext = AnalysisContextImpl(
SynchronousSession(
AnalysisOptionsImpl()..contextFeatures = featureSet,
declaredVariables,
),
sourceFactory,
);
var elementFactory = LinkedElementFactory(
analysisContext,
_AnalysisSessionForLinking(),
Reference.root(),
);
elementFactory.addBundle(
BundleReader(
elementFactory: elementFactory,
unitsInformativeBytes: {},
resolutionBytes: (await sdkBundle).resolutionBytes,
),
);
await _linkConfiguredLibraries(
elementFactory,
inputLibraries,
preBuildSequence,
);
var linkResult = await link(
elementFactory,
inputLibraries,
macroExecutor: _macroExecutor,
);
for (var macroUnit in linkResult.macroGeneratedUnits) {
var informativeBytes = writeUnitInformative(macroUnit.unit);
unitsInformativeBytes[macroUnit.uri] = informativeBytes;
}
if (!keepLinkingLibraries) {
elementFactory.removeBundle(
inputLibraries.map((e) => e.uriStr).toSet(),
);
elementFactory.addBundle(
BundleReader(
elementFactory: elementFactory,
unitsInformativeBytes: unitsInformativeBytes,
resolutionBytes: linkResult.resolutionBytes,
),
);
}
return elementFactory.libraryOfUri2('$testUri');
}
@mustCallSuper
Future<void> tearDown() async {
await _macroExecutor.close();
KernelCompilationService.disposeDelayed(
const Duration(milliseconds: 100),
);
}
void _addLibraryUnits(
Source definingSource,
CompilationUnit definingUnit,
List<LinkInputUnit> units,
FeatureSet featureSet,
) {
units.add(
LinkInputUnit(
partDirectiveIndex: null,
source: definingSource,
isSynthetic: false,
unit: definingUnit,
),
);
var partDirectiveIndex = -1;
for (var directive in definingUnit.directives) {
if (directive is PartDirective) {
++partDirectiveIndex;
var relativeUriStr = directive.uri.stringValue;
var partSource = sourceFactory.resolveUri(
definingSource,
relativeUriStr,
);
if (partSource != null) {
var text = _readSafely(partSource.fullName);
var unit = parseText(partSource, text, featureSet);
units.add(
LinkInputUnit(
partDirectiveIndex: partDirectiveIndex,
partUriStr: relativeUriStr,
source: partSource,
isSynthetic: false,
unit: unit,
),
);
}
}
}
}
void _addNonDartLibraries(
Set<Source> addedLibraries,
List<LinkInputLibrary> libraries,
Source source,
) {
if (source.uri.isScheme('dart') || !addedLibraries.add(source)) {
return;
}
var text = _readSafely(source.fullName);
var unit = parseText(source, text, featureSet);
var units = <LinkInputUnit>[];
_addLibraryUnits(source, unit, units, featureSet);
libraries.add(
LinkInputLibrary(
source: source,
units: units,
),
);
void addRelativeUriStr(StringLiteral uriNode) {
var relativeUriStr = uriNode.stringValue;
if (relativeUriStr == null) {
return;
}
Uri relativeUri;
try {
relativeUri = Uri.parse(relativeUriStr);
} on FormatException {
return;
}
var absoluteUri = resolveRelativeUri(source.uri, relativeUri);
var rewrittenUri = rewriteToCanonicalUri(sourceFactory, absoluteUri);
if (rewrittenUri == null) {
return;
}
var uriSource = sourceFactory.forUri2(rewrittenUri);
if (uriSource == null) {
return;
}
_addNonDartLibraries(addedLibraries, libraries, uriSource);
}
for (var directive in unit.directives) {
if (directive is NamespaceDirective) {
addRelativeUriStr(directive.uri);
for (var configuration in directive.configurations) {
addRelativeUriStr(configuration.uri);
}
}
}
}
/// If there are any [macroLibraries], build the kernel and prepare for
/// execution.
Future<void> _buildMacroLibraries(
LinkedElementFactory elementFactory,
List<MacroLibrary> macroLibraries,
) async {
if (macroLibraries.isEmpty) {
return;
}
final macroKernelBuilder = const MacroKernelBuilder();
var macroKernelBytes = await macroKernelBuilder.build(
fileSystem: _MacroFileSystem(resourceProvider),
libraries: macroLibraries,
);
var bundleMacroExecutor = BundleMacroExecutor(
macroExecutor: _macroExecutor,
kernelBytes: macroKernelBytes,
libraries: macroLibraries.map((e) => e.uri).toSet(),
);
for (var macroLibrary in macroLibraries) {
var uriStr = macroLibrary.uriStr;
var element = elementFactory.libraryOfUri2(uriStr);
element.bundleMacroExecutor = bundleMacroExecutor;
}
}
/// If there are any libraries in the [uriStrSetList], link these subsets
/// of [inputLibraries] (and remove from it), build macro kernels, prepare
/// for executing macros.
Future<void> _linkConfiguredLibraries(
LinkedElementFactory elementFactory,
List<LinkInputLibrary> inputLibraries,
List<Set<String>>? uriStrSetList,
) async {
if (uriStrSetList == null) {
return;
}
for (var uriStrSet in uriStrSetList) {
var cycleInputLibraries = <LinkInputLibrary>[];
var macroLibraries = <MacroLibrary>[];
for (var inputLibrary in inputLibraries) {
if (uriStrSet.contains(inputLibrary.uriStr)) {
cycleInputLibraries.add(inputLibrary);
_addMacroLibrary(macroLibraries, inputLibrary);
}
}
await link(
elementFactory,
cycleInputLibraries,
macroExecutor: _macroExecutor,
);
await _buildMacroLibraries(elementFactory, macroLibraries);
// Remove libraries that we just linked.
cycleInputLibraries.forEach(inputLibraries.remove);
}
}
String _readSafely(String path) {
try {
var file = resourceProvider.getFile(path);
return file.readAsStringSync();
} catch (_) {
return '';
}
}
/// If there are any macros in the [inputLibrary], add it.
static void _addMacroLibrary(
List<MacroLibrary> macroLibraries,
LinkInputLibrary inputLibrary,
) {
var macroClasses = <MacroClass>[];
for (var inputUnit in inputLibrary.units) {
for (var declaration in inputUnit.unit.declarations) {
if (declaration is ClassDeclarationImpl &&
declaration.macroKeyword != null) {
var constructors =
declaration.members.whereType<ConstructorDeclaration>().toList();
if (constructors.isEmpty) {
macroClasses.add(
MacroClass(
name: declaration.name.name,
constructors: [''],
),
);
} else {
var constructorNames = constructors
.map((e) => e.name?.name ?? '')
.where((e) => !e.startsWith('_'))
.toList();
if (constructorNames.isNotEmpty) {
macroClasses.add(
MacroClass(
name: declaration.name.name,
constructors: constructorNames,
),
);
}
}
}
}
}
if (macroClasses.isNotEmpty) {
macroLibraries.add(
MacroLibrary(
uri: inputLibrary.uri,
path: inputLibrary.source.fullName,
classes: macroClasses,
),
);
}
}
}
class _AnalysisSessionForLinking implements AnalysisSessionImpl {
@override
final ClassHierarchy classHierarchy = ClassHierarchy();
@override
InheritanceManager3 inheritanceManager = InheritanceManager3();
@override
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
/// [MacroFileEntry] adapter for [File].
class _MacroFileEntry implements MacroFileEntry {
final File file;
_MacroFileEntry(this.file);
@override
String get content => file.readAsStringSync();
@override
bool get exists => file.exists;
}
/// [MacroFileSystem] adapter for [ResourceProvider].
class _MacroFileSystem implements MacroFileSystem {
final ResourceProvider resourceProvider;
_MacroFileSystem(this.resourceProvider);
@override
package_path.Context get pathContext => resourceProvider.pathContext;
@override
MacroFileEntry getFile(String path) {
var file = resourceProvider.getFile(path);
return _MacroFileEntry(file);
}
}
class _SdkBundle {
final Uint8List resolutionBytes;
_SdkBundle({
required this.resolutionBytes,
});
}