| // Copyright (c) 2014, 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:analysis_server/src/protocol_server.dart'; |
| import 'package:analysis_server/src/services/correction/fix_internal.dart'; |
| import 'package:analyzer/dart/analysis/analysis_context.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'; |
| import 'package:analyzer/src/dart/analysis/byte_store.dart'; |
| import 'package:analyzer/src/dart/analysis/driver.dart'; |
| import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart'; |
| import 'package:analyzer/src/generated/engine.dart' show AnalysisEngine; |
| import 'package:analyzer/src/test_utilities/mock_sdk.dart'; |
| import 'package:analyzer/src/test_utilities/platform.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/utilities/extensions/file_system.dart'; |
| import 'package:analyzer_utilities/test/experiments/experiments.dart'; |
| import 'package:analyzer_utilities/test/mock_packages/mock_packages.dart'; |
| import 'package:linter/src/rules.dart'; |
| import 'package:meta/meta.dart'; |
| import 'package:test/test.dart'; |
| |
| import 'support/configuration_files.dart'; |
| import 'test_macros.dart'; |
| |
| class AbstractContextTest |
| with |
| MockPackagesMixin, |
| ConfigurationFilesMixin, |
| ResourceProviderMixin, |
| TestMacros { |
| static bool _lintRulesAreRegistered = false; |
| |
| static final ByteStore _byteStore = MemoryByteStore(); |
| |
| /// Whether to rewrite line endings in test code based on platform. |
| bool useLineEndingsForPlatform = false; |
| |
| final Map<String, String> _declaredVariables = {}; |
| AnalysisContextCollectionImpl? _analysisContextCollection; |
| |
| /// If not `null`, [getResolvedUnit] will use the context that corresponds |
| /// to this file, instead of the given file. |
| File? fileForContextSelection; |
| |
| // TODO(scheglov): Stop writing into it. Convert into getter. |
| late String testFilePath = '$testPackageLibPath/test.dart'; |
| |
| List<AnalysisDriver> get allDrivers { |
| _createAnalysisContexts(); |
| return _analysisContextCollection!.contexts.map((e) => e.driver).toList(); |
| } |
| |
| /// The file system specific path for `analysis_options.yaml` in |
| /// [testPackageRootPath]. |
| String get analysisOptionsPath => |
| convertPath('$testPackageRootPath/analysis_options.yaml'); |
| |
| List<String> get collectionIncludedPaths => [workspaceRootPath]; |
| |
| /// Return a list of the experiments that are to be enabled for tests in this |
| /// class, an empty list if there are no experiments that should be enabled. |
| List<String> get experiments => experimentsForTests; |
| |
| /// The path that is not in [workspaceRootPath], contains external packages. |
| @override |
| String get packagesRootPath => '/packages'; |
| |
| Folder get sdkRoot => newFolder('/sdk'); |
| |
| Future<AnalysisSession> get session => sessionFor(testFile); |
| |
| /// The path for `analysis_options.yaml` in [testPackageRootPath]. |
| String get testAnalysisOptionsPath => |
| convertPath('$testPackageRootPath/analysis_options.yaml'); |
| |
| File get testFile => getFile(testFilePath); |
| |
| String get testPackageLibPath => '$testPackageRootPath/lib'; |
| |
| @override |
| String get testPackageRootPath => '$workspaceRootPath/test'; |
| |
| String get testPackageTestPath => '$testPackageRootPath/test'; |
| |
| /// The file system specific path for `pubspec.yaml` in [testPackageRootPath]. |
| String get testPubspecPath => |
| convertPath('$testPackageRootPath/pubspec.yaml'); |
| |
| String get workspaceRootPath => '/home'; |
| |
| Future<void> analyzeTestPackageFiles() async { |
| var analysisContext = contextFor(testFile); |
| var files = analysisContext.contextRoot.analyzedFiles().toList(); |
| for (var path in files) { |
| await analysisContext.applyPendingFileChanges(); |
| await analysisContext.currentSession.getResolvedUnit(path); |
| } |
| } |
| |
| void assertSourceChange(SourceChange sourceChange, String expected) { |
| var buffer = StringBuffer(); |
| _writeSourceChangeToBuffer( |
| buffer: buffer, |
| sourceChange: sourceChange, |
| ); |
| _assertTextExpectation(buffer.toString(), expected); |
| } |
| |
| void changeFile(File file) { |
| var path = file.path; |
| driverFor(file).changeFile(path); |
| } |
| |
| /// 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. |
| AnalysisContext contextFor(File file) { |
| return _contextFor(file); |
| } |
| |
| /// Create an analysis options file based on the given arguments. |
| void createAnalysisOptionsFile({ |
| List<String> experiments = const [], |
| List<String> cannotIgnore = const [], |
| List<String> lints = const [], |
| Map<String, Object?> errors = const {}, |
| }) { |
| var buffer = StringBuffer(); |
| |
| if (experiments.isNotEmpty || |
| cannotIgnore.isNotEmpty || |
| errors.isNotEmpty) { |
| buffer.writeln('analyzer:'); |
| } |
| |
| if (errors.isNotEmpty) { |
| buffer.writeln(' errors:'); |
| for (var error in errors.entries) { |
| buffer.writeln(' ${error.key}: ${error.value}'); |
| } |
| } |
| |
| if (experiments.isNotEmpty) { |
| buffer.writeln(' enable-experiment:'); |
| for (var experiment in experiments) { |
| buffer.writeln(' - $experiment'); |
| } |
| } |
| |
| if (cannotIgnore.isNotEmpty) { |
| buffer.writeln(' cannot-ignore:'); |
| for (var unignorable in cannotIgnore) { |
| buffer.writeln(' - $unignorable'); |
| } |
| } |
| |
| if (lints.isNotEmpty) { |
| buffer.writeln('linter:'); |
| buffer.writeln(' rules:'); |
| for (var lint in lints) { |
| buffer.writeln(' - $lint'); |
| } |
| } |
| |
| writeAnalysisOptionsFile(buffer.toString()); |
| } |
| |
| /// Returns the existing analysis driver 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. |
| AnalysisDriver driverFor(File file) { |
| return _contextFor(file).driver; |
| } |
| |
| Future<ParsedUnitResult> getParsedUnit(File file) async { |
| var path = file.path; |
| var session = await sessionFor(fileForContextSelection ?? file); |
| var result = session.getParsedUnit(path); |
| return result as ParsedUnitResult; |
| } |
| |
| Future<ResolvedUnitResult> getResolvedUnit(File file) async { |
| var path = file.path; |
| var session = await sessionFor(fileForContextSelection ?? file); |
| var result = await session.getResolvedUnit(path); |
| return result as ResolvedUnitResult; |
| } |
| |
| @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; |
| } |
| |
| /// Convenience function to normalize newlines in [code] for the current |
| /// platform if [useLineEndingsForPlatform] is `true`. |
| String normalizeSource(String code) => |
| useLineEndingsForPlatform ? normalizeNewlinesForPlatform(code) : code; |
| |
| Future<AnalysisSession> sessionFor(File file) async { |
| var analysisContext = _contextFor(file); |
| await analysisContext.applyPendingFileChanges(); |
| return analysisContext.currentSession; |
| } |
| |
| @mustCallSuper |
| void setUp() { |
| if (!_lintRulesAreRegistered) { |
| registerLintRules(); |
| _lintRulesAreRegistered = true; |
| registerBuiltInProducers(); |
| } |
| |
| setupResourceProvider(); |
| |
| createMockSdk( |
| resourceProvider: resourceProvider, |
| root: sdkRoot, |
| ); |
| |
| writeTestPackageConfig(); |
| |
| createAnalysisOptionsFile( |
| experiments: experiments, |
| ); |
| } |
| |
| void setupResourceProvider() {} |
| |
| @mustCallSuper |
| Future<void> tearDown() async { |
| AnalysisEngine.instance.clearCaches(); |
| await _analysisContextCollection?.dispose(); |
| } |
| |
| /// Update `pubspec.yaml` and create the driver. |
| void updateTestPubspecFile(String content) { |
| newFile(testPubspecPath, content); |
| } |
| |
| void verifyCreatedCollection() {} |
| |
| /// 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); |
| } |
| } |
| } |
| } |
| |
| void _assertTextExpectation(String actual, String expected) { |
| if (actual != expected) { |
| print('-' * 64); |
| print(actual.trimRight()); |
| print('-' * 64); |
| } |
| expect(actual, expected); |
| } |
| |
| DriverBasedAnalysisContext _contextFor(File file) { |
| _createAnalysisContexts(); |
| return _analysisContextCollection!.contextFor(file.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, |
| ); |
| |
| _addAnalyzedFilesToDrivers(); |
| verifyCreatedCollection(); |
| } |
| |
| void _writeSourceChangeToBuffer({ |
| required StringBuffer buffer, |
| required SourceChange sourceChange, |
| }) { |
| for (var fileEdit in sourceChange.edits) { |
| var file = getFile(fileEdit.file); |
| buffer.writeln('>>>>>>>>>> ${file.posixPath}'); |
| var current = file.readAsStringSync(); |
| var updated = SourceEdit.applySequence(current, fileEdit.edits); |
| buffer.write(updated); |
| } |
| } |
| } |