| // 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/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: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; |
| |
| MacroKernelBuilder? macroKernelBuilder; |
| macro.MultiMacroExecutor? macroExecutor; |
| |
| /// 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) { |
| newFile2(path, contents); |
| } |
| |
| Future<LibraryElementImpl> buildLibrary( |
| String text, { |
| bool allowErrors = false, |
| bool dumpSummaries = false, |
| List<Set<String>>? preBuildSequence, |
| }) async { |
| var testFile = newFile2(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, |
| ), |
| ); |
| |
| _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'); |
| } |
| |
| 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. |
| void _buildMacroLibraries( |
| LinkedElementFactory elementFactory, |
| List<MacroLibrary> macroLibraries, |
| ) { |
| if (macroLibraries.isEmpty) { |
| return; |
| } |
| |
| final macroKernelBuilder = this.macroKernelBuilder; |
| if (macroKernelBuilder == null) { |
| return; |
| } |
| |
| final macroExecutor = this.macroExecutor; |
| if (macroExecutor == null) { |
| return; |
| } |
| |
| var macroKernelBytes = 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. |
| void _linkConfiguredLibraries( |
| LinkedElementFactory elementFactory, |
| List<LinkInputLibrary> inputLibraries, |
| List<Set<String>>? uriStrSetList, |
| ) { |
| 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); |
| } |
| } |
| |
| link( |
| elementFactory, |
| cycleInputLibraries, |
| macroExecutor: macroExecutor, |
| ); |
| |
| _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, |
| }); |
| } |