blob: 695af58b21db35fa94ccdd7e79389d216954bcee [file]
// 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:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart' // ignore: implementation_imports
as diag;
import 'package:analyzer/src/test_utilities/test_code_format.dart'; // ignore: implementation_imports
import 'package:analyzer/src/utilities/extensions/analysis_session.dart'; // ignore: implementation_imports
import 'package:analyzer_testing/src/abstract_context.dart';
import 'package:test/test.dart';
/// A base test class for tests operating on a single test unit ([testFile]).
///
/// Builds on [AbstractContextTest] by providing convenience properties and
/// methods for parsing or resolving [testFile], tracking test code markers,
/// verifying diagnostics, and accessing AST nodes and resolved elements.
class SingleUnitTest extends AbstractContextTest {
/// Whether to verify that the resolved unit has no unexpected diagnostics.
///
/// Defaults to `true`. When `true`, [getResolvedUnit] asserts that the unit
/// has no diagnostics other than a short list of innocuous ignored diagnostic
/// codes (such as unused elements and unused imports).
bool verifyNoTestUnitErrors = true;
TestCode? _parsedTestCode;
ParsedUnitResult? _testParsedResult;
ResolvedLibraryResult? _testLibraryResult;
ResolvedUnitResult? _testAnalysisResult;
CompilationUnit? _testUnit;
/// The [TestCode] representation of the test source code.
///
/// Contains the raw [testCode] stripped of test markers, as well as parsed
/// positions and ranges embedded in the test source.
TestCode get parsedTestCode {
if (_parsedTestCode case var parsedTestCode?) {
return parsedTestCode;
}
throw StateError(
"'parsedTestCode' has not yet been set; this is set by 'addTestSource'.",
);
}
/// The [ResolvedUnitResult] for [testFile].
///
/// Populated when resolving [testFile] via [getResolvedUnit],
/// [resolveTestFile], or [resolveTestCode].
ResolvedUnitResult get testAnalysisResult {
if (_testAnalysisResult case var testAnalysisResult?) {
return testAnalysisResult;
}
throw StateError(
"'testAnalysisResult' has not yet been set; this is set by "
"'getResolvedUnit'.",
);
}
/// The source code of [testFile] without test markers.
///
/// Setting this property parses the code into [parsedTestCode] using
/// [TestCode.parseNormalized].
String get testCode => parsedTestCode.code;
/// The [ResolvedLibraryResult] for the library containing [testFile].
///
/// Populated when resolving [testFile] via [getResolvedUnit],
/// [resolveTestFile], or [resolveTestCode]. May be `null` if the library
/// could not be resolved.
///
/// Typically used by intermediate test classes (such as `FixProcessorTest`
/// and `AssistProcessorTest`) rather than individual test cases to access
/// the containing library context when computing fixes or assists.
ResolvedLibraryResult? get testLibraryResult => _testLibraryResult;
/// The [ParsedUnitResult] obtained from parsing [testFile].
///
/// Populated by [parseTestCode]. Note that this is _not_ populated by
/// [resolveTestCode].
ParsedUnitResult get testParsedResult {
if (_testParsedResult case var testParsedResult?) {
return testParsedResult;
}
throw StateError(
"'testParsedResult' has not yet been set; this is set by "
"'parseTestCode'.",
);
}
/// The [CompilationUnit] of [testFile].
///
/// Populated when parsing [testFile] via [parseTestCode], or when resolving
/// [testFile] via [getResolvedUnit], [resolveTestFile], or [resolveTestCode].
CompilationUnit get testUnit {
if (_testUnit case var testUnit?) {
return testUnit;
}
throw StateError(
"'testUnit' has not yet been set; this is set by "
"'getResolvedUnit' and 'parseTestCode'.",
);
}
/// Sets the test source [code] for [testFile] and creates the file on disk.
///
/// Normalizes and parses [code] with test markers (such as positions or
/// ranges), updates [testCode] and [parsedTestCode], and writes the source to
/// [testFile].
///
/// Does not resolve [testFile]. Use [resolveTestCode] to add source and
/// resolve in a single step, or call [resolveTestFile] after configuring
/// additional files or test options.
void addTestSource(String code) {
_setParsedTestCode(TestCode.parseNormalized(code));
newFile(testFile.path, testCode);
}
/// Parses the given [file] and returns its [ParsedUnitResult].
///
/// Pending file changes in the file's analysis context are applied before
/// parsing.
Future<ParsedUnitResult> getParsedUnit(File file) async {
var path = file.path;
var analysisContext = contextFor2(file);
await analysisContext.applyPendingFileChanges();
var result = analysisContext.currentSession.getParsedUnit(path);
return result as ParsedUnitResult;
}
/// Resolves the given [file] and returns its [ResolvedUnitResult].
///
/// If [file] matches [testFile], this also populates [testLibraryResult],
/// [testAnalysisResult], [testUnit], [findNode], and [findElement].
///
/// If [verifyNoTestUnitErrors] is `true`, verifies that [file] has no
/// diagnostics other than standard ignored diagnostics and any additional
/// diagnostic codes specified in [ignore].
@override
Future<ResolvedUnitResult> getResolvedUnit(
File file, {
List<DiagnosticCode>? ignore,
}) async {
var session = await this.session;
var libraryResult = await session.getResolvedContainingLibrary(file.path);
var unitResult = libraryResult?.unitWithPath(file.path);
unitResult ??= await super.getResolvedUnit(file);
if (file.path == convertPath(testFilePath)) {
_testLibraryResult = libraryResult;
_testAnalysisResult = unitResult;
_testUnit = unitResult.unit;
}
if (verifyNoTestUnitErrors) {
var allIgnored = const <DiagnosticCode>{
diag.deadCode,
diag.unusedCatchClause,
diag.unusedCatchStack,
diag.unusedElement,
diag.unusedField,
diag.unusedImport,
diag.unusedLocalVariable,
};
if (ignore != null) {
allIgnored = {...allIgnored, ...ignore};
}
expect(
unitResult.diagnostics.where(
(d) => !allIgnored.contains(d.diagnosticCode),
),
isEmpty,
);
}
return unitResult;
}
/// Sets [code] as the test source and parses [testFile] without resolving it.
///
/// Writes [code] to [testFile] via [addTestSource], parses it using
/// [getParsedUnit], and initializes [testParsedResult], [testUnit],
/// [findNode], and [findElement].
///
/// Typically used by tests that only require syntactic information (such as
/// directive ordering or member sorting tests) where full semantic resolution
/// is not needed.
Future<void> parseTestCode(String code) async {
addTestSource(code);
_testParsedResult = await getParsedUnit(testFile);
_testUnit = testParsedResult.unit;
}
/// Adds the given [code] as the test source and resolves [testFile].
///
/// Convenience method that combines [addTestSource] and [resolveTestFile].
/// Optionally pass [ignore], a list of diagnostic codes to be ignored.
Future<void> resolveTestCode(
String code, {
List<DiagnosticCode>? ignore,
}) async {
addTestSource(code);
await resolveTestFile(ignore: ignore);
}
/// Resolves [testFile] using [getResolvedUnit].
///
/// Assumes the test source has already been added via [addTestSource] or
/// written to [testFile]. Optionally pass [ignore], a list of diagnostic
/// codes to be ignored.
Future<void> resolveTestFile({List<DiagnosticCode>? ignore}) async {
await getResolvedUnit(testFile, ignore: ignore);
}
void _setParsedTestCode(TestCode value) {
if (_parsedTestCode case var parsedTestCode?) {
throw ArgumentError(
"'parsedTestCode' is already set to '${parsedTestCode.code}'",
);
}
_parsedTestCode = value;
}
}