diff --git a/pkg/analyzer/lib/src/dart/analysis/analysis_context_collection2.dart b/pkg/analyzer/lib/src/dart/analysis/analysis_context_collection2.dart new file mode 100644 index 00000000000..1f1341e445a --- /dev/null +++ b/pkg/analyzer/lib/src/dart/analysis/analysis_context_collection2.dart @@ -0,0 +1,178 @@ +// Copyright (c) 2018, 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/dart/analysis/analysis_context_collection.dart'; +import 'package:analyzer/dart/analysis/context_locator.dart'; +import 'package:analyzer/dart/analysis/context_root.dart'; +import 'package:analyzer/dart/analysis/declared_variables.dart'; +import 'package:analyzer/file_system/file_system.dart'; +import 'package:analyzer/file_system/physical_file_system.dart'; +import 'package:analyzer/src/dart/analysis/byte_store.dart'; +import 'package:analyzer/src/dart/analysis/context_builder.dart'; +import 'package:analyzer/src/dart/analysis/driver.dart'; +import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart'; +import 'package:analyzer/src/dart/analysis/file_content_cache.dart'; +import 'package:analyzer/src/dart/analysis/info_declaration_store.dart'; +import 'package:analyzer/src/dart/analysis/performance_logger.dart'; +import 'package:analyzer/src/dart/analysis/unlinked_unit_store.dart'; +import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl; +import 'package:analyzer/src/generated/sdk.dart'; +import 'package:analyzer/src/summary2/kernel_compilation_service.dart'; +import 'package:analyzer/src/summary2/macro.dart'; +import 'package:analyzer/src/util/sdk.dart'; + +/// An implementation of [AnalysisContextCollection]. +class AnalysisContextCollectionImpl2 implements AnalysisContextCollection { + /// The resource provider used to access the file system. + final ResourceProvider resourceProvider; + + /// The support for executing macros. + late final MacroSupport macroSupport; + + /// The shared container into which drivers record files ownership. + final OwnedFiles ownedFiles = OwnedFiles(); + + /// The list of analysis contexts. + @override + final List contexts = []; + + /// Initialize a newly created analysis context manager. + AnalysisContextCollectionImpl2({ + ByteStore? byteStore, + Map? declaredVariables, + bool drainStreams = true, + bool enableIndex = false, + required List includedPaths, + List? excludedPaths, + List? librarySummaryPaths, + String? optionsFile, + String? packagesFile, + PerformanceLog? performanceLog, + ResourceProvider? resourceProvider, + bool retainDataForTesting = false, + String? sdkPath, + String? sdkSummaryPath, + AnalysisDriverScheduler? scheduler, + FileContentCache? fileContentCache, + UnlinkedUnitStore? unlinkedUnitStore, + InfoDeclarationStore? infoDeclarationStore, + @Deprecated('Use updateAnalysisOptions2, which must be a function that ' + 'accepts a second parameter') + void Function(AnalysisOptionsImpl)? updateAnalysisOptions, + void Function({ + required AnalysisOptionsImpl analysisOptions, + required ContextRoot contextRoot, + required DartSdk sdk, + })? updateAnalysisOptions2, + MacroSupport? macroSupport, + }) : resourceProvider = + resourceProvider ?? PhysicalResourceProvider.INSTANCE { + sdkPath ??= getSdkPath(); + + _throwIfAnyNotAbsoluteNormalizedPath(includedPaths); + _throwIfNotAbsoluteNormalizedPath(sdkPath); + + if (updateAnalysisOptions != null && updateAnalysisOptions2 != null) { + throw ArgumentError( + 'Either updateAnalysisOptions or updateAnalysisOptions2 must be ' + 'given, but not both.'); + } + + this.macroSupport = macroSupport ??= KernelMacroSupport(); + + var contextLocator = ContextLocator( + resourceProvider: this.resourceProvider, + ); + var roots = contextLocator.locateRoots( + includedPaths: includedPaths, + excludedPaths: excludedPaths, + optionsFile: optionsFile, + packagesFile: packagesFile, + ); + for (var root in roots) { + var contextBuilder = ContextBuilderImpl( + resourceProvider: this.resourceProvider, + ); + var context = contextBuilder.createContext( + byteStore: byteStore, + contextRoot: root, + declaredVariables: DeclaredVariables.fromMap(declaredVariables ?? {}), + drainStreams: drainStreams, + enableIndex: enableIndex, + librarySummaryPaths: librarySummaryPaths, + performanceLog: performanceLog, + retainDataForTesting: retainDataForTesting, + sdkPath: sdkPath, + sdkSummaryPath: sdkSummaryPath, + scheduler: scheduler, + // ignore: deprecated_member_use_from_same_package + updateAnalysisOptions: updateAnalysisOptions, + updateAnalysisOptions2: updateAnalysisOptions2, + fileContentCache: fileContentCache, + unlinkedUnitStore: unlinkedUnitStore ?? UnlinkedUnitStoreImpl(), + infoDeclarationStore: infoDeclarationStore, + macroSupport: macroSupport, + ownedFiles: ownedFiles, + ); + contexts.add(context); + } + } + + /// Return `true` if the read state of configuration files is consistent + /// with their current state on the file system. We use this as a work + /// around an issue with watching for file system changes. + bool get areWorkspacesConsistent { + for (var analysisContext in contexts) { + var contextRoot = analysisContext.contextRoot; + var workspace = contextRoot.workspace; + if (!workspace.isConsistentWithFileSystem) { + return false; + } + } + return true; + } + + @override + DriverBasedAnalysisContext contextFor(String path) { + _throwIfNotAbsoluteNormalizedPath(path); + + for (var context in contexts) { + if (context.contextRoot.isAnalyzed(path)) { + return context; + } + } + + throw StateError('Unable to find the context to $path'); + } + + Future dispose({ + bool forTesting = false, + }) async { + for (final analysisContext in contexts) { + await analysisContext.driver.dispose2(); + } + await macroSupport.dispose(); + // If there are other collections, they will have to start it again. + if (!forTesting) { + await KernelCompilationService.dispose(); + } + } + + /// Check every element with [_throwIfNotAbsoluteNormalizedPath]. + void _throwIfAnyNotAbsoluteNormalizedPath(List paths) { + for (var path in paths) { + _throwIfNotAbsoluteNormalizedPath(path); + } + } + + /// The driver supports only absolute normalized paths, this method is used + /// to validate any input paths to prevent errors later. + void _throwIfNotAbsoluteNormalizedPath(String path) { + var pathContext = resourceProvider.pathContext; + if (!pathContext.isAbsolute(path) || pathContext.normalize(path) != path) { + throw ArgumentError( + 'Only absolute normalized paths are supported: $path'); + } + } +} diff --git a/pkg/analyzer/lib/src/dart/analysis/context_builder2.dart b/pkg/analyzer/lib/src/dart/analysis/context_builder2.dart new file mode 100644 index 00000000000..297dbf95626 --- /dev/null +++ b/pkg/analyzer/lib/src/dart/analysis/context_builder2.dart @@ -0,0 +1,271 @@ +// Copyright (c) 2018, 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/dart/analysis/context_builder.dart'; +import 'package:analyzer/dart/analysis/context_root.dart'; +import 'package:analyzer/dart/analysis/declared_variables.dart'; +import 'package:analyzer/file_system/file_system.dart'; +import 'package:analyzer/file_system/physical_file_system.dart'; +import 'package:analyzer/src/analysis_options/analysis_options_provider.dart'; +import 'package:analyzer/src/analysis_options/apply_options.dart'; +import 'package:analyzer/src/context/builder.dart' show EmbedderYamlLocator; +import 'package:analyzer/src/context/packages.dart'; +import 'package:analyzer/src/dart/analysis/byte_store.dart' + show ByteStore, MemoryByteStore; +import 'package:analyzer/src/dart/analysis/driver.dart' + show + AnalysisDriver, + AnalysisDriverScheduler, + AnalysisDriverTestView, + OwnedFiles; +import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart'; +import 'package:analyzer/src/dart/analysis/file_content_cache.dart'; +import 'package:analyzer/src/dart/analysis/info_declaration_store.dart'; +import 'package:analyzer/src/dart/analysis/performance_logger.dart' + show PerformanceLog; +import 'package:analyzer/src/dart/analysis/unlinked_unit_store.dart'; +import 'package:analyzer/src/dart/sdk/sdk.dart'; +import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl; +import 'package:analyzer/src/generated/sdk.dart' show DartSdk; +import 'package:analyzer/src/generated/source.dart'; +import 'package:analyzer/src/hint/sdk_constraint_extractor.dart'; +import 'package:analyzer/src/summary/package_bundle_reader.dart'; +import 'package:analyzer/src/summary/summary_sdk.dart'; +import 'package:analyzer/src/summary2/macro.dart'; +import 'package:analyzer/src/summary2/package_bundle_format.dart'; +import 'package:analyzer/src/util/file_paths.dart' as file_paths; +import 'package:analyzer/src/util/sdk.dart'; +import 'package:analyzer/src/workspace/workspace.dart'; + +/// An implementation of a context builder. +class ContextBuilderImpl2 implements ContextBuilder { + /// The resource provider used to access the file system. + final ResourceProvider resourceProvider; + + /// Initialize a newly created context builder. If a [resourceProvider] is + /// given, then it will be used to access the file system, otherwise the + /// default resource provider will be used. + ContextBuilderImpl2({ResourceProvider? resourceProvider}) + : resourceProvider = + resourceProvider ?? PhysicalResourceProvider.INSTANCE; + + @override + DriverBasedAnalysisContext createContext({ + ByteStore? byteStore, + required ContextRoot contextRoot, + DeclaredVariables? declaredVariables, + bool drainStreams = true, + bool enableIndex = false, + List? librarySummaryPaths, + PerformanceLog? performanceLog, + bool retainDataForTesting = false, + AnalysisDriverScheduler? scheduler, + String? sdkPath, + String? sdkSummaryPath, + @Deprecated('Use updateAnalysisOptions2') + void Function(AnalysisOptionsImpl)? updateAnalysisOptions, + void Function({ + required AnalysisOptionsImpl analysisOptions, + required ContextRoot contextRoot, + required DartSdk sdk, + })? updateAnalysisOptions2, + FileContentCache? fileContentCache, + UnlinkedUnitStore? unlinkedUnitStore, + InfoDeclarationStore? infoDeclarationStore, + MacroSupport? macroSupport, + OwnedFiles? ownedFiles, + }) { + // TODO(scheglov) Remove this, and make `sdkPath` required. + sdkPath ??= getSdkPath(); + ArgumentError.checkNotNull(sdkPath, 'sdkPath'); + if (updateAnalysisOptions != null && updateAnalysisOptions2 != null) { + throw ArgumentError( + 'Either updateAnalysisOptions or updateAnalysisOptions2 must be ' + 'given, but not both.'); + } + + byteStore ??= MemoryByteStore(); + performanceLog ??= PerformanceLog(null); + + if (scheduler == null) { + scheduler = AnalysisDriverScheduler(performanceLog); + scheduler.start(); + } + + SummaryDataStore? summaryData; + if (librarySummaryPaths != null) { + summaryData = SummaryDataStore(); + for (var summaryPath in librarySummaryPaths) { + var bytes = resourceProvider.getFile(summaryPath).readAsBytesSync(); + var bundle = PackageBundleReader(bytes); + summaryData.addBundle(summaryPath, bundle); + } + } + + var workspace = contextRoot.workspace; + var sdk = _createSdk( + workspace: workspace, + sdkPath: sdkPath, + sdkSummaryPath: sdkSummaryPath, + ); + + // TODO(scheglov) Ensure that "librarySummaryPaths" not null only + // when "sdkSummaryPath" is not null. + if (sdk is SummaryBasedDartSdk) { + summaryData?.addBundle(null, sdk.bundle); + } + + var sourceFactory = workspace.createSourceFactory(sdk, summaryData); + + var options = _getAnalysisOptions(contextRoot, sourceFactory); + if (updateAnalysisOptions != null) { + updateAnalysisOptions(options); + } else if (updateAnalysisOptions2 != null) { + updateAnalysisOptions2( + analysisOptions: options, + contextRoot: contextRoot, + sdk: sdk, + ); + } + + final analysisContext = + DriverBasedAnalysisContext(resourceProvider, contextRoot); + + var driver = AnalysisDriver( + scheduler: scheduler, + logger: performanceLog, + resourceProvider: resourceProvider, + byteStore: byteStore, + sourceFactory: sourceFactory, + analysisOptions: options, + packages: _createPackageMap( + contextRoot: contextRoot, + ), + analysisContext: analysisContext, + enableIndex: enableIndex, + externalSummaries: summaryData, + retainDataForTesting: retainDataForTesting, + fileContentCache: fileContentCache, + unlinkedUnitStore: unlinkedUnitStore, + infoDeclarationStore: infoDeclarationStore, + macroSupport: macroSupport, + declaredVariables: declaredVariables, + testView: retainDataForTesting ? AnalysisDriverTestView() : null, + ownedFiles: ownedFiles, + ); + + // AnalysisDriver reports results into streams. + // We need to drain these streams to avoid memory leak. + if (drainStreams) { + driver.results.drain(); + driver.exceptions.drain(); + } + + return analysisContext; + } + + /// Return [Packages] to analyze the [contextRoot]. + /// + /// TODO(scheglov) Get [Packages] from [Workspace]? + Packages _createPackageMap({ + required ContextRoot contextRoot, + }) { + var packagesFile = contextRoot.packagesFile; + if (packagesFile != null) { + return parsePackageConfigJsonFile(resourceProvider, packagesFile); + } else { + return Packages.empty; + } + } + + /// Return the SDK that should be used to analyze code. + DartSdk _createSdk({ + required Workspace workspace, + String? sdkPath, + String? sdkSummaryPath, + }) { + if (sdkSummaryPath != null) { + var file = resourceProvider.getFile(sdkSummaryPath); + var bytes = file.readAsBytesSync(); + return SummaryBasedDartSdk.forBundle( + PackageBundleReader(bytes), + ); + } + + var folderSdk = FolderBasedDartSdk( + resourceProvider, + resourceProvider.getFolder(sdkPath!), + ); + + { + // TODO(scheglov) We already had partial SourceFactory in ContextLocatorImpl. + var partialSourceFactory = workspace.createSourceFactory(null, null); + var embedderYamlSource = partialSourceFactory.forUri( + 'package:sky_engine/_embedder.yaml', + ); + if (embedderYamlSource != null) { + var embedderYamlPath = embedderYamlSource.fullName; + var libFolder = resourceProvider.getFile(embedderYamlPath).parent; + var locator = EmbedderYamlLocator.forLibFolder(libFolder); + var embedderMap = locator.embedderYamls; + if (embedderMap.isNotEmpty) { + return EmbedderSdk( + resourceProvider, + embedderMap, + languageVersion: folderSdk.languageVersion, + ); + } + } + } + + return folderSdk; + } + + /// Return the `pubspec.yaml` file that should be used when analyzing code in + /// the [contextRoot], possibly `null`. + /// + /// TODO(scheglov) Get it from [Workspace]? + File? _findPubspecFile(ContextRoot contextRoot) { + for (var current in contextRoot.root.withAncestors) { + var file = current.getChildAssumingFile(file_paths.pubspecYaml); + if (file.exists) { + return file; + } + } + return null; + } + + /// Return the analysis options that should be used to analyze code in the + /// [contextRoot]. + /// + /// TODO(scheglov) We have already loaded it once in [ContextLocatorImpl]. + AnalysisOptionsImpl _getAnalysisOptions( + ContextRoot contextRoot, + SourceFactory sourceFactory, + ) { + var options = AnalysisOptionsImpl(); + + var optionsFile = contextRoot.optionsFile; + if (optionsFile != null) { + try { + var provider = AnalysisOptionsProvider(sourceFactory); + var optionsMap = provider.getOptionsFromFile(optionsFile); + options.applyOptions(optionsMap); + } catch (e) { + // ignore + } + } + + var pubspecFile = _findPubspecFile(contextRoot); + if (pubspecFile != null) { + var extractor = SdkConstraintExtractor(pubspecFile); + var sdkVersionConstraint = extractor.constraint(); + if (sdkVersionConstraint != null) { + options.sdkVersionConstraint = sdkVersionConstraint; + } + } + + return options; + } +} diff --git a/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart b/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart index 5eeaae4a63c..46c56b22b70 100644 --- a/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart +++ b/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart @@ -28,7 +28,7 @@ class DriverBasedAnalysisContext implements AnalysisContext { this.resourceProvider, this.contextRoot, [ @Deprecated('AnalysisDriver will set itself, remove this') - AnalysisDriver? analysisDriver, + AnalysisDriver? analysisDriver, ]); @override diff --git a/pkg/analyzer/test/src/dart/analysis/analysis_context_collection2_test.dart b/pkg/analyzer/test/src/dart/analysis/analysis_context_collection2_test.dart new file mode 100644 index 00000000000..e71335fd598 --- /dev/null +++ b/pkg/analyzer/test/src/dart/analysis/analysis_context_collection2_test.dart @@ -0,0 +1,181 @@ +// Copyright (c) 2018, 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/analysis_context_collection.dart'; +import 'package:analyzer/src/dart/analysis/analysis_context_collection2.dart'; +import 'package:analyzer/src/test_utilities/mock_sdk.dart'; +import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart'; +import 'package:linter/src/rules.dart'; +import 'package:test/test.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../resolution/context_collection_resolution.dart'; + +main() { + defineReflectiveSuite(() { + defineReflectiveTests(AnalysisContextCollectionTest); + }); +} + +@reflectiveTest +class AnalysisContextCollectionTest with ResourceProviderMixin { + Folder get sdkRoot => newFolder('/sdk'); + + void setUp() { + createMockSdk( + resourceProvider: resourceProvider, + root: sdkRoot, + ); + registerLintRules(); + } + + test_contextFor_noContext() { + var collection = _newCollection(includedPaths: [convertPath('/root')]); + expect( + () => collection.contextFor(convertPath('/other/test.dart')), + throwsStateError, + ); + } + + test_contextFor_notAbsolute() { + var collection = _newCollection(includedPaths: [convertPath('/root')]); + expect( + () => collection.contextFor(convertPath('test.dart')), + throwsArgumentError, + ); + } + + test_contextFor_notNormalized() { + var collection = _newCollection(includedPaths: [convertPath('/root')]); + expect( + () => collection.contextFor(convertPath('/test/lib/../lib/test.dart')), + throwsArgumentError, + ); + } + + test_new_analysisOptions_includes() { + var rootFolder = newFolder('/home/test'); + var fooFolder = newFolder('/home/packages/foo'); + newFile('${fooFolder.path}/lib/included.yaml', r''' +linter: + rules: + - empty_statements +'''); + + var packageConfigFileBuilder = PackageConfigFileBuilder() + ..add(name: 'foo', rootPath: fooFolder.path); + newPackageConfigJsonFile( + rootFolder.path, + packageConfigFileBuilder.toContent(toUriStr: toUriStr), + ); + + newAnalysisOptionsYamlFile(rootFolder.path, r''' +include: package:foo/included.yaml + +linter: + rules: + - unnecessary_parenthesis +'''); + + var collection = _newCollection(includedPaths: [rootFolder.path]); + var analysisContext = collection.contextFor(rootFolder.path); + var analysisOptions = analysisContext.analysisOptions; + + expect( + analysisOptions.lintRules.map((e) => e.name), + unorderedEquals(['empty_statements', 'unnecessary_parenthesis']), + ); + } + + test_new_analysisOptions_lintRules() { + var rootFolder = newFolder('/home/test'); + newAnalysisOptionsYamlFile(rootFolder.path, r''' +linter: + rules: + - non_existent_lint_rule + - unnecessary_parenthesis +'''); + + var collection = _newCollection(includedPaths: [rootFolder.path]); + var analysisContext = collection.contextFor(rootFolder.path); + var analysisOptions = analysisContext.analysisOptions; + + expect( + analysisOptions.lintRules.map((e) => e.name), + unorderedEquals(['unnecessary_parenthesis']), + ); + } + + test_new_includedPaths_notAbsolute() { + expect( + () => AnalysisContextCollectionImpl(includedPaths: ['root']), + throwsArgumentError, + ); + } + + test_new_includedPaths_notNormalized() { + expect( + () => AnalysisContextCollectionImpl( + includedPaths: [convertPath('/root/lib/../lib')]), + throwsArgumentError, + ); + } + + test_new_outer_inner() { + var outerFolder = newFolder('/test/outer'); + newFile('/test/outer/lib/outer.dart', ''); + + var innerFolder = newFolder('/test/outer/inner'); + newAnalysisOptionsYamlFile('/test/outer/inner', ''); + newFile('/test/outer/inner/inner.dart', ''); + + var collection = _newCollection(includedPaths: [outerFolder.path]); + + expect(collection.contexts, hasLength(2)); + + var outerContext = collection.contexts + .singleWhere((c) => c.contextRoot.root == outerFolder); + var innerContext = collection.contexts + .singleWhere((c) => c.contextRoot.root == innerFolder); + expect(innerContext, isNot(same(outerContext))); + + // Outer and inner contexts own corresponding files. + expect(collection.contextFor(convertPath('/test/outer/lib/outer.dart')), + same(outerContext)); + expect(collection.contextFor(convertPath('/test/outer/inner/inner.dart')), + same(innerContext)); + + // The file does not have to exist, during creation, or at all. + expect(collection.contextFor(convertPath('/test/outer/lib/outer2.dart')), + same(outerContext)); + expect(collection.contextFor(convertPath('/test/outer/inner/inner2.dart')), + same(innerContext)); + } + + test_new_sdkPath_notAbsolute() { + expect( + () => AnalysisContextCollectionImpl( + includedPaths: ['/root'], sdkPath: 'sdk'), + throwsArgumentError, + ); + } + + test_new_sdkPath_notNormalized() { + expect( + () => AnalysisContextCollectionImpl( + includedPaths: [convertPath('/root')], sdkPath: '/home/sdk/../sdk'), + throwsArgumentError, + ); + } + + AnalysisContextCollectionImpl2 _newCollection( + {required List includedPaths}) { + return AnalysisContextCollectionImpl2( + resourceProvider: resourceProvider, + includedPaths: includedPaths, + sdkPath: sdkRoot.path, + ); + } +} diff --git a/pkg/analyzer/test/src/dart/analysis/context_builder2_test.dart b/pkg/analyzer/test/src/dart/analysis/context_builder2_test.dart new file mode 100644 index 00000000000..51981232410 --- /dev/null +++ b/pkg/analyzer/test/src/dart/analysis/context_builder2_test.dart @@ -0,0 +1,249 @@ +// Copyright (c) 2018, 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/dart/analysis/analysis_context.dart'; +import 'package:analyzer/dart/analysis/context_root.dart'; +import 'package:analyzer/dart/analysis/declared_variables.dart'; +import 'package:analyzer/file_system/file_system.dart'; +import 'package:analyzer/src/context/packages.dart'; +import 'package:analyzer/src/context/source.dart'; +import 'package:analyzer/src/dart/analysis/context_builder.dart'; +import 'package:analyzer/src/dart/analysis/context_builder2.dart'; +import 'package:analyzer/src/dart/analysis/context_locator.dart'; +import 'package:analyzer/src/dart/analysis/context_root.dart'; +import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart'; +import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl; +import 'package:analyzer/src/generated/source.dart'; +import 'package:analyzer/src/source/package_map_resolver.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/file_paths.dart' as file_paths; +import 'package:analyzer/src/workspace/basic.dart'; +import 'package:analyzer/src/workspace/blaze.dart'; +import 'package:analyzer/src/workspace/pub.dart'; +import 'package:test/test.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../resolution/context_collection_resolution.dart'; + +main() { + defineReflectiveSuite(() { + defineReflectiveTests(ContextBuilderImplTest); + }); +} + +@reflectiveTest +class ContextBuilderImplTest with ResourceProviderMixin { + late final ContextBuilderImpl2 contextBuilder; + late final ContextRoot contextRoot; + + Folder get sdkRoot => newFolder('/sdk'); + + void assertEquals(DeclaredVariables actual, DeclaredVariables expected) { + Iterable actualNames = actual.variableNames; + Iterable expectedNames = expected.variableNames; + expect(actualNames, expectedNames); + for (String name in expectedNames) { + expect(actual.get(name), expected.get(name)); + } + } + + void setUp() { + createMockSdk( + resourceProvider: resourceProvider, + root: sdkRoot, + ); + + var folder = newFolder('/home/test'); + contextBuilder = ContextBuilderImpl2(resourceProvider: resourceProvider); + var workspace = + BasicWorkspace.find(resourceProvider, Packages.empty, folder.path); + contextRoot = ContextRootImpl(resourceProvider, folder, workspace); + } + + void test_analysisOptions_invalid() { + var projectPath = convertPath('/home/test'); + newAnalysisOptionsYamlFile(projectPath, ';'); + + var analysisContext = _createSingleAnalysisContext(projectPath); + var analysisOptions = analysisContext.analysisOptionsImpl; + _expectEqualOptions(analysisOptions, AnalysisOptionsImpl()); + } + + void test_analysisOptions_languageOptions() { + var projectPath = convertPath('/home/test'); + newAnalysisOptionsYamlFile( + projectPath, + AnalysisOptionsFileConfig( + strictRawTypes: true, + ).toContent(), + ); + + var analysisContext = _createSingleAnalysisContext(projectPath); + var analysisOptions = analysisContext.analysisOptionsImpl; + _expectEqualOptions( + analysisOptions, + AnalysisOptionsImpl()..strictRawTypes = true, + ); + } + + void test_analysisOptions_sdkVersionConstraint_hasPubspec_hasSdk() { + var projectPath = convertPath('/home/test'); + newPubspecYamlFile(projectPath, ''' +environment: + sdk: ^2.1.0 +'''); + + var analysisContext = _createSingleAnalysisContext(projectPath); + var analysisOptions = analysisContext.analysisOptionsImpl; + expect(analysisOptions.sdkVersionConstraint.toString(), '^2.1.0'); + } + + void test_analysisOptions_sdkVersionConstraint_noPubspec() { + var projectPath = convertPath('/home/test'); + newFile('$projectPath/lib/a.dart', ''); + + var analysisContext = _createSingleAnalysisContext(projectPath); + var analysisOptions = analysisContext.driver.analysisOptions; + expect(analysisOptions.sdkVersionConstraint, isNull); + } + + test_createContext_declaredVariables() { + DeclaredVariables declaredVariables = + DeclaredVariables.fromMap({'foo': 'true'}); + var context = contextBuilder.createContext( + contextRoot: contextRoot, + declaredVariables: declaredVariables, + sdkPath: sdkRoot.path, + ); + expect(context.analysisOptions, isNotNull); + expect(context.contextRoot, contextRoot); + assertEquals(context.driver.declaredVariables, declaredVariables); + } + + test_createContext_declaredVariables_sdkPath() { + DeclaredVariables declaredVariables = + DeclaredVariables.fromMap({'bar': 'true'}); + var context = contextBuilder.createContext( + contextRoot: contextRoot, + declaredVariables: declaredVariables, + sdkPath: sdkRoot.path, + ); + expect(context.analysisOptions, isNotNull); + expect(context.contextRoot, contextRoot); + assertEquals(context.driver.declaredVariables, declaredVariables); + expect( + context.driver.sourceFactory.dartSdk!.mapDartUri('dart:core')!.fullName, + sdkRoot.getChildAssumingFile('lib/core/core.dart').path, + ); + } + + test_createContext_defaults() { + AnalysisContext context = contextBuilder.createContext( + contextRoot: contextRoot, + sdkPath: sdkRoot.path, + ); + expect(context.analysisOptions, isNotNull); + expect(context.contextRoot, contextRoot); + } + + test_createContext_sdkPath() { + var context = contextBuilder.createContext( + contextRoot: contextRoot, + sdkPath: sdkRoot.path, + ); + expect(context.analysisOptions, isNotNull); + expect(context.contextRoot, contextRoot); + expect( + context.driver.sourceFactory.dartSdk!.mapDartUri('dart:core')!.fullName, + sdkRoot.getChildAssumingFile('lib/core/core.dart').path, + ); + } + + test_createContext_sdkRoot() { + var context = contextBuilder.createContext( + contextRoot: contextRoot, sdkPath: sdkRoot.path); + expect(context.analysisOptions, isNotNull); + expect(context.contextRoot, contextRoot); + expect(context.sdkRoot, sdkRoot); + } + + void test_sourceFactory_blazeWorkspace() { + var projectPath = convertPath('/workspace/my/module'); + newFile('/workspace/${file_paths.blazeWorkspaceMarker}', ''); + newFolder('/workspace/blaze-bin'); + newFolder('/workspace/blaze-genfiles'); + + var analysisContext = _createSingleAnalysisContext(projectPath); + expect(analysisContext.contextRoot.workspace, isA()); + + expect( + analysisContext.uriResolvers, + unorderedEquals([ + isA(), + isA(), + isA(), + ]), + ); + } + + void test_sourceFactory_pubWorkspace() { + var projectPath = convertPath('/home/my'); + newFile('/home/my/pubspec.yaml', ''); + + var analysisContext = _createSingleAnalysisContext(projectPath); + expect(analysisContext.contextRoot.workspace, isA()); + + expect( + analysisContext.uriResolvers, + unorderedEquals([ + isA(), + isA(), + isA(), + ]), + ); + } + + /// Return a single expected analysis context at the [path]. + DriverBasedAnalysisContext _createSingleAnalysisContext(String path) { + var roots = ContextLocatorImpl( + resourceProvider: resourceProvider, + ).locateRoots(includedPaths: [path]); + + return ContextBuilderImpl( + resourceProvider: resourceProvider, + ).createContext( + contextRoot: roots.single, + sdkPath: sdkRoot.path, + ); + } + + static void _expectEqualOptions( + AnalysisOptionsImpl actual, + AnalysisOptionsImpl expected, + ) { + // TODO(brianwilkerson) Consider moving this to AnalysisOptionsImpl.==. + expect(actual.enableTiming, expected.enableTiming); + expect(actual.lint, expected.lint); + expect(actual.warning, expected.warning); + expect( + actual.lintRules.map((l) => l.name), + unorderedEquals(expected.lintRules.map((l) => l.name)), + ); + expect( + actual.propagateLinterExceptions, expected.propagateLinterExceptions); + expect(actual.strictInference, expected.strictInference); + expect(actual.strictRawTypes, expected.strictRawTypes); + } +} + +extension on DriverBasedAnalysisContext { + AnalysisOptionsImpl get analysisOptionsImpl { + return driver.analysisOptions as AnalysisOptionsImpl; + } + + List get uriResolvers { + return (driver.sourceFactory as SourceFactoryImpl).resolvers; + } +} diff --git a/pkg/analyzer/test/src/dart/analysis/test_all.dart b/pkg/analyzer/test/src/dart/analysis/test_all.dart index 9cd9ba66817..83922b14bde 100644 --- a/pkg/analyzer/test/src/dart/analysis/test_all.dart +++ b/pkg/analyzer/test/src/dart/analysis/test_all.dart @@ -4,10 +4,12 @@ import 'package:test_reflective_loader/test_reflective_loader.dart'; +import 'analysis_context_collection2_test.dart' as analysis_context_collection2; import 'analysis_context_collection_test.dart' as analysis_context_collection; import 'analysis_options_map_test.dart' as analysis_options_map; import 'byte_store_test.dart' as byte_store_test; import 'cache_test.dart' as cache_test; +import 'context_builder2_test.dart' as context_builder2; import 'context_builder_test.dart' as context_builder; import 'context_locator2_test.dart' as context_locator2; import 'context_locator_test.dart' as context_locator; @@ -35,10 +37,12 @@ import 'uri_converter_test.dart' as uri_converter; main() { defineReflectiveSuite(() { analysis_context_collection.main(); + analysis_context_collection2.main(); analysis_options_map.main(); byte_store_test.main(); cache_test.main(); context_builder.main(); + context_builder2.main(); context_locator.main(); context_locator2.main(); context_root.main();