| // Copyright (c) 2026, 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/features.dart'; | |
| import 'package:analyzer/dart/analysis/results.dart'; | |
| import 'package:analyzer/dart/analysis/session.dart'; | |
| import 'package:analyzer/file_system/file_system.dart'; | |
| import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart'; // ignore: implementation_imports | |
| import 'package:analyzer/src/dart/analysis/byte_store.dart'; // ignore: implementation_imports | |
| import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart'; // ignore: implementation_imports | |
| import 'package:analyzer/src/generated/engine.dart' // ignore: implementation_imports | |
| show AnalysisEngine; | |
| import 'package:analyzer/src/test_utilities/mock_sdk.dart'; // ignore: implementation_imports | |
| import 'package:analyzer/src/test_utilities/platform.dart'; // ignore: implementation_imports | |
| import 'package:analyzer/src/util/file_paths.dart' // ignore: implementation_imports | |
| as file_paths; | |
| import 'package:analyzer_testing/configuration_files_mixin.dart'; | |
| import 'package:analyzer_testing/experiments/experiments.dart'; | |
| import 'package:analyzer_testing/mock_packages/mock_packages.dart'; | |
| import 'package:analyzer_testing/resource_provider_mixin.dart'; | |
| import 'package:analyzer_testing/utilities/utilities.dart'; | |
| import 'package:meta/meta.dart'; | |
| /// A base class for analyzer and analysis server tests. | |
| /// | |
| /// Sets up an in-memory file system with a stub SDK, package configuration, | |
| /// and default analysis options. Provides helper methods and properties for | |
| /// creating test files, configuring analysis contexts, and obtaining resolved | |
| /// ASTs. | |
| class AbstractContextTest | |
| with MockPackagesMixin, ConfigurationFilesMixin, ResourceProviderMixin { | |
| /// The byte store that is reused between tests. | |
| /// | |
| /// This allows reusing all unlinked and linked summaries for SDK, so that | |
| /// tests run much faster. However nothing is preserved between Dart VM runs, | |
| /// so changes to the implementation are still fully verified. | |
| static final MemoryByteStore _sharedByteStore = MemoryByteStore(); | |
| final MemoryByteStore _byteStore = _sharedByteStore; | |
| final Map<String, String> _declaredVariables = {}; | |
| AnalysisContextCollectionImpl? _analysisContextCollection; | |
| /// The file system path for `analysis_options.yaml` in [testPackageRootPath]. | |
| String get analysisOptionsPath => | |
| '$testPackageRootPath/analysis_options.yaml'; | |
| /// The [ByteStore] reused between tests. | |
| ByteStore get byteStore => _byteStore; | |
| /// The [AnalysisContextCollection] managing the analysis contexts. | |
| AnalysisContextCollection get contextCollection { | |
| _createAnalysisContexts(); | |
| return _analysisContextCollection!; | |
| } | |
| /// The file system path to the mock SDK root directory. | |
| @override | |
| String get dartSdkPath => sdkRoot.path; | |
| /// The line terminator being used for test files and to be expected in edits. | |
| String get eol => testEol; | |
| /// Return a list of the experimental features that are to be enabled for | |
| /// tests in this class. | |
| List<Feature> get experimentalFeatures => experimentalFeaturesForTests; | |
| /// The path that is not in [workspaceRootPath], contains external packages. | |
| @override | |
| String get packagesRootPath => '/packages'; | |
| /// The [Folder] representing the stub SDK root directory. | |
| /// | |
| /// Can be overridden by subclasses that require a custom SDK setup. | |
| Folder get sdkRoot => newFolder('/sdk'); | |
| /// The [AnalysisSession] for the test package's [testFile], after applying | |
| /// any pending file changes. | |
| Future<AnalysisSession> get session async { | |
| var analysisContext = contextFor2(testFile); | |
| await analysisContext.applyPendingFileChanges(); | |
| return analysisContext.currentSession; | |
| } | |
| /// The default [File] for testing, located at [testFilePath]. | |
| /// | |
| /// Most single-file tests write source code to this file. | |
| File get testFile => getFile(testFilePath); | |
| /// The name of the test file. | |
| String get testFileName => 'test.dart'; | |
| /// The file system path of [testFile]. | |
| String get testFilePath => '$testPackageLibPath/$testFileName'; | |
| /// The file system path of the `lib` directory of the package-under-test. | |
| String get testPackageLibPath => '$testPackageRootPath/lib'; | |
| /// The file system path for `pubspec.yaml` in [testPackageRootPath]. | |
| String get testPackagePubspecPath => testPubspecPath; | |
| /// The file system path of the root of the package-under-test. | |
| @override | |
| String get testPackageRootPath => '$workspaceRootPath/test'; | |
| /// The file system path to the `test` directory of the package-under-test. | |
| /// | |
| /// Rarely used except when a test specifically tests files situated inside a | |
| /// package's `test` directory. | |
| String get testPackageTestPath => '$testPackageRootPath/test'; | |
| /// The file system path for `pubspec.yaml` in [testPackageRootPath]. | |
| String get testPubspecPath => '$testPackageRootPath/pubspec.yaml'; | |
| /// The file system path of the root of the workspace. | |
| String get workspaceRootPath => '/home'; | |
| List<String> get _collectionIncludedPaths => [workspaceRootPath]; | |
| /// Returns the existing analysis context that should be used to analyze the | |
| /// given [path], or throw [StateError] if the [path] is not analyzed in any | |
| /// of the created analysis contexts. | |
| @Deprecated("Use 'contextFor2'") | |
| DriverBasedAnalysisContext contextFor(String path) { | |
| _createAnalysisContexts(); | |
| var convertedPath = convertPath(path); | |
| return _analysisContextCollection!.contextFor(convertedPath); | |
| } | |
| /// Returns the existing analysis context that should be used to analyze the | |
| /// given [file], or throw [StateError] if the [file] is not analyzed in any | |
| /// of the created analysis contexts. | |
| DriverBasedAnalysisContext contextFor2(File file) { | |
| _createAnalysisContexts(); | |
| return _analysisContextCollection!.contextFor(file.path); | |
| } | |
| /// Create an analysis options file based on the given arguments. | |
| void createAnalysisOptionsFile({ | |
| List<String> includes = const [], | |
| List<Feature>? experimentalFeatures, | |
| List<String> legacyPlugins = const [], | |
| List<String> cannotIgnore = const [], | |
| List<String> lints = const [], | |
| Map<String, Object?> errors = const {}, | |
| bool propagateLinterExceptions = true, | |
| bool strictCasts = false, | |
| bool strictInference = false, | |
| bool strictRawTypes = false, | |
| }) { | |
| writeAnalysisOptionsFile( | |
| analysisOptionsContent( | |
| includes: includes, | |
| experimentalFeatures: experimentalFeatures ?? this.experimentalFeatures, | |
| legacyPlugins: legacyPlugins, | |
| propagateLinterExceptions: propagateLinterExceptions, | |
| rules: lints, | |
| errors: errors, | |
| unignorableNames: cannotIgnore, | |
| strictCasts: strictCasts, | |
| strictInference: strictInference, | |
| strictRawTypes: strictRawTypes, | |
| ), | |
| ); | |
| } | |
| /// Resolves and returns the [ResolvedUnitResult] for the given [file]. | |
| Future<ResolvedUnitResult> getResolvedUnit(File file) async { | |
| return resolveFile(file.path); | |
| } | |
| /// Creates or updates a file at [path] with the given [content]. | |
| /// | |
| /// Line endings in [content] are normalized using [normalizeSource], and the | |
| /// file is added to relevant analysis drivers if analysis contexts have | |
| /// already been created. | |
| /// | |
| /// Throws a [StateError] if a non-Dart file is modified after the analysis | |
| /// context collection has already been initialized. | |
| @override | |
| File newFile(String path, String content) { | |
| if (_analysisContextCollection != null && !path.endsWith('.dart')) { | |
| throw StateError('Only dart files can be changed after analysis.'); | |
| } | |
| var file = super.newFile(path, normalizeSource(content)); | |
| _addAnalyzedFileToDrivers(file); | |
| return file; | |
| } | |
| /// Normalizes newlines in [code] for the current platform. | |
| String normalizeSource(String code) => normalizeNewlinesForPlatform(code); | |
| /// Resolves a Dart source file at [filePath]. | |
| Future<ResolvedUnitResult> resolveFile(String filePath) async { | |
| var file = getFile(filePath); | |
| var analysisContext = contextFor2(file); | |
| await analysisContext.applyPendingFileChanges(); | |
| var session = analysisContext.currentSession; | |
| return await session.getResolvedUnit(file.path) as ResolvedUnitResult; | |
| } | |
| /// Initializes the test environment before each test. | |
| @mustCallSuper | |
| void setUp() { | |
| createMockSdk(resourceProvider: resourceProvider, root: sdkRoot); | |
| writeTestPackageConfig2(); | |
| createAnalysisOptionsFile(experimentalFeatures: experimentalFeatures); | |
| } | |
| /// Disposes of the test environment after each test. | |
| /// | |
| /// Clears analysis engine caches and disposes of the analysis context | |
| /// collection. | |
| @mustCallSuper | |
| Future<void> tearDown() async { | |
| AnalysisEngine.instance.clearCaches(); | |
| await _analysisContextCollection?.dispose(); | |
| _analysisContextCollection = null; | |
| } | |
| /// Updates `pubspec.yaml` and creates the driver. | |
| void updateTestPubspecFile(String content) { | |
| newFile(testPubspecPath, content); | |
| } | |
| /// Writes string content as an analysis options file. | |
| void writeAnalysisOptionsFile(String content) { | |
| newFile(analysisOptionsPath, content); | |
| } | |
| void _addAnalyzedFilesToDrivers() { | |
| for (var analysisContext in _analysisContextCollection!.contexts) { | |
| for (var path in analysisContext.contextRoot.analyzedFiles()) { | |
| if (file_paths.isDart(resourceProvider.pathContext, path)) { | |
| analysisContext.driver.addFile(path); | |
| } | |
| } | |
| } | |
| } | |
| void _addAnalyzedFileToDrivers(File file) { | |
| var path = file.path; | |
| var collection = _analysisContextCollection; | |
| if (collection != null) { | |
| for (var analysisContext in collection.contexts) { | |
| if (analysisContext.contextRoot.isAnalyzed(path)) { | |
| analysisContext.driver.addFile(path); | |
| } | |
| } | |
| } | |
| } | |
| /// Create all analysis contexts in [_collectionIncludedPaths]. | |
| void _createAnalysisContexts() { | |
| if (_analysisContextCollection != null) { | |
| return; | |
| } | |
| _analysisContextCollection = AnalysisContextCollectionImpl( | |
| byteStore: _byteStore, | |
| declaredVariables: _declaredVariables, | |
| enableIndex: true, | |
| includedPaths: _collectionIncludedPaths.map(convertPath).toList(), | |
| resourceProvider: resourceProvider, | |
| sdkPath: sdkRoot.path, | |
| withFineDependencies: true, | |
| ); | |
| _addAnalyzedFilesToDrivers(); | |
| } | |
| } |