blob: fd4edb74592f6e50a4f89140ef8168618dcd2e9d [file]
// 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/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/results.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/constant/value.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/test_utilities/find_element2.dart';
import 'package:analyzer/src/test_utilities/find_node.dart';
import 'package:analyzer_testing/resource_provider_mixin.dart';
import 'package:analyzer_testing/src/expected_diagnostics.dart';
import 'package:analyzer_utilities/testing/tree_string_sink.dart';
import 'package:test/test.dart';
import '../../../util/diff.dart';
import '../../../util/element_printer.dart';
import '../../summary/resolved_ast_printer.dart';
import '../analysis/result_printer.dart';
import 'dart_object_printer.dart';
import 'node_text_expectations.dart';
final isDynamicType = TypeMatcher<DynamicTypeImpl>();
final isNeverType = TypeMatcher<NeverTypeImpl>();
final isVoidType = TypeMatcher<VoidTypeImpl>();
/// Base for resolution tests.
mixin ResolutionTest implements ResourceProviderMixin {
final ResolvedNodeTextConfiguration nodeTextConfiguration =
ResolvedNodeTextConfiguration();
final DartObjectPrinterConfiguration dartObjectPrinterConfiguration =
DartObjectPrinterConfiguration();
File get testFile;
void addTestFile(String content) {
newFile(testFile.path, content);
}
void assertDartObjectText(DartObject? object, String expected) {
var buffer = StringBuffer();
var sink = TreeStringSink(sink: buffer, indent: '');
var elementPrinter = ElementPrinter(
sink: sink,
configuration: ElementPrinterConfiguration(),
);
DartObjectPrinter(
configuration: dartObjectPrinterConfiguration,
sink: sink,
elementPrinter: elementPrinter,
).write(object as DartObjectImpl?);
var actual = buffer.toString();
if (actual != expected) {
NodeTextExpectationsCollector.add(actual);
if (NodeTextExpectationsCollector.shouldPrintFailureDetails) {
printPrettyDiff(expected, actual);
}
fail('See the difference above.');
}
}
void assertElement(
Object? nodeOrElement, {
required Element declaration,
Map<String, String> substitution = const {},
}) {
Element? element;
if (nodeOrElement is AstNode) {
element = getNodeElement2(nodeOrElement);
} else {
element = nodeOrElement as Element?;
}
var actualDeclaration = element?.baseElement;
expect(actualDeclaration, same(declaration));
if (element is SubstitutedElementImpl) {
assertSubstitution(element.substitution, substitution);
} else if (substitution.isNotEmpty) {
fail('Expected to be a Member: (${element.runtimeType}) $element');
}
}
void assertElementNull(Element? element) {
expect(element, isNull);
}
void assertElementTypes(
List<DartType>? types,
List<String> expected, {
bool ordered = false,
}) {
if (types == null) {
fail('Expected types, actually null.');
}
var typeStrList = types.map(typeString).toList();
if (ordered) {
expect(typeStrList, expected);
} else {
expect(typeStrList, unorderedEquals(expected));
}
}
void assertParsedNodeText(AstNode node, String expected) {
var buffer = StringBuffer();
var sink = TreeStringSink(sink: buffer, indent: '');
var elementPrinter = ElementPrinter(
sink: sink,
configuration: ElementPrinterConfiguration(),
);
node.accept(
ResolvedAstPrinter(
sink: sink,
elementPrinter: elementPrinter,
configuration: ResolvedNodeTextConfiguration(),
withResolution: false,
),
);
var actual = buffer.toString();
if (actual != expected) {
NodeTextExpectationsCollector.add(actual);
if (NodeTextExpectationsCollector.shouldPrintFailureDetails) {
printPrettyDiff(expected, actual);
}
fail('See the difference above.');
}
}
void assertResolvedLibraryResultText(
SomeResolvedLibraryResult result,
String expected, {
void Function(ResolvedLibraryResultPrinterConfiguration)? configure,
}) {
var configuration = ResolvedLibraryResultPrinterConfiguration();
configure?.call(configuration);
var buffer = StringBuffer();
var sink = TreeStringSink(sink: buffer, indent: '');
var idProvider = IdProvider();
ResolvedLibraryResultPrinter(
configuration: configuration,
sink: sink,
idProvider: idProvider,
elementPrinter: ElementPrinter(
sink: sink,
configuration: ElementPrinterConfiguration(),
),
).write(result);
var actual = buffer.toString();
if (actual != expected) {
NodeTextExpectationsCollector.add(actual);
if (NodeTextExpectationsCollector.shouldPrintFailureDetails) {
printPrettyDiff(expected, actual);
}
fail('See the difference above.');
}
}
void assertResolvedNodeText(AstNode node, String expected) {
var actual = _resolvedNodeText(node);
if (actual != expected) {
NodeTextExpectationsCollector.add(actual);
if (NodeTextExpectationsCollector.shouldPrintFailureDetails) {
printPrettyDiff(expected, actual);
}
fail('See the difference above.');
}
}
void assertSubstitution(
MapSubstitution substitution,
Map<String, String> expected,
) {
var actualMapString = Map.fromEntries(
substitution.map.entries
.where((entry) {
return entry.key.enclosingElement is! ExecutableElement;
})
.map((entry) {
return MapEntry(entry.key.name, typeString(entry.value));
}),
);
expect(actualMapString, expected);
}
void assertType(Object? typeOrNode, String? expected) {
DartType? actual;
if (typeOrNode is DartType) {
actual = typeOrNode;
} else if (typeOrNode is Expression) {
actual = typeOrNode.staticType;
} else if (typeOrNode is GenericFunctionType) {
actual = typeOrNode.type;
} else if (typeOrNode is NamedType) {
actual = typeOrNode.type;
} else {
fail('Unsupported node: (${typeOrNode.runtimeType}) $typeOrNode');
}
if (expected == null) {
expect(actual, isNull);
} else if (actual == null) {
fail('Null, expected: $expected');
} else {
expect(typeString(actual), expected);
}
}
void assertTypeDynamic(Object? typeOrExpression) {
DartType? actual;
if (typeOrExpression case DartType? type) {
actual = typeOrExpression;
expect(type, isDynamicType);
} else {
actual = (typeOrExpression as Expression).staticType;
}
expect(actual, isDynamicType);
}
Element? getNodeElement2(AstNode node) {
if (node is Annotation) {
return node.element;
} else if (node is AssignmentExpression) {
return node.element;
} else if (node is BinaryExpression) {
return node.element;
} else if (node is ConstructorReference) {
return node.constructorName.element;
} else if (node is Declaration) {
return node.declaredFragment?.element;
} else if (node is ExtensionOverride) {
return node.element;
} else if (node is FormalParameter) {
return node.declaredFragment?.element;
} else if (node is FunctionExpressionInvocation) {
return node.element;
} else if (node is FunctionReference) {
var function = node.function.unParenthesized;
if (function is Identifier) {
return function.element;
} else if (function is PropertyAccess) {
return function.propertyName.element;
} else if (function is ConstructorReference) {
return function.constructorName.element;
} else {
fail('Unsupported node: (${function.runtimeType}) $function');
}
} else if (node is Identifier) {
return node.element;
} else if (node is ImplicitCallReference) {
return node.element;
} else if (node is IndexExpression) {
return node.element;
} else if (node is InstanceCreationExpression) {
return node.constructorName.element;
} else if (node is MethodInvocation) {
return node.methodName.element;
} else if (node is PostfixExpression) {
return node.element;
} else if (node is PrefixExpression) {
return node.element;
} else if (node is PropertyAccess) {
return node.propertyName.element;
} else if (node is NamedType) {
return node.element;
} else {
fail('Unsupported node: (${node.runtimeType}) $node');
}
}
Future<ResolvedUnitResultImpl> resolveFile(File file);
/// Resolve [file] and return a test view of it.
Future<TestResolvedUnitResult> resolveFile2(File file) async {
var result = await resolveFile(file);
return TestResolvedUnitResult(result);
}
/// Create a new file with the [path] and [content], and resolve it.
Future<TestResolvedUnitResult> resolveFileCode(String path, String content) {
var file = newFile(path, content);
return resolveFile2(file);
}
/// Writes all [filesToCode], resolves each file, and checks that each file's
/// inline diagnostic markers match its diagnostics.
///
/// All files are written before any file is resolved. This supports tests
/// where resolving one file cleanly requires related files to already exist,
/// such as a library with its parts.
Future<Map<File, TestResolvedUnitResult>> resolveFilesWithDiagnostics(
Map<File, String> filesToCode,
) async {
var files = <({File file, String code, String cleanCode})>[];
for (var entry in filesToCode.entries) {
var cleanCode = removeDiagnosticExpectations(entry.value);
modifyFile2(entry.key, cleanCode);
files.add((file: entry.key, code: entry.value, cleanCode: cleanCode));
}
var results = <File, TestResolvedUnitResult>{};
var diagnosticsByFile = <File, List<Diagnostic>>{};
for (var file in files) {
var result = await resolveFile2(file.file);
results[file.file] = result;
diagnosticsByFile[file.file] = result.diagnostics;
}
var actualCodeByFile = updateExpectedDiagnosticsForFiles(
contentByFile: {for (var file in files) file.file: file.cleanCode},
actualDiagnosticsByFile: diagnosticsByFile,
);
var hasMismatch = false;
for (var index = 0; index < files.length; index++) {
var file = files[index];
var actual = actualCodeByFile[file.file]!;
if (actual != file.code) {
NodeTextExpectationsCollector.add(actual, intraInvocationId: '$index');
if (NodeTextExpectationsCollector.shouldPrintFailureDetails) {
print('-------- ${file.file.path} --------');
printPrettyDiff(file.code, actual);
}
hasMismatch = true;
}
}
if (hasMismatch) {
fail('See the difference above.');
}
return results;
}
/// Writes [code] to [file], resolves it, and checks that its inline
/// diagnostic markers match its diagnostics.
Future<TestResolvedUnitResult> resolveFileWithDiagnostics(
File file,
String code,
) async {
return await _resolveFileWithDiagnostics(file, code);
}
/// Put the [code] into the test file, and resolve it.
Future<TestResolvedUnitResult> resolveTestCode(String code) {
addTestFile(code);
return resolveTestFile();
}
/// Resolves [code] and checks that its inline diagnostic markers match the
/// diagnostics. Unmarked code is expected to have no diagnostics.
Future<TestResolvedUnitResult> resolveTestCodeWithDiagnostics(
String code,
) async {
return await _resolveFileWithDiagnostics(testFile, code);
}
Future<TestResolvedUnitResult> resolveTestFile() {
return resolveFile2(testFile);
}
/// Return a textual representation of the [type] that is appropriate for
/// tests.
String typeString(DartType type) => type.getDisplayString();
String _resolvedNodeText(AstNode node) {
var buffer = StringBuffer();
var sink = TreeStringSink(sink: buffer, indent: '');
var elementPrinter = ElementPrinter(
sink: sink,
configuration: ElementPrinterConfiguration()
..withInterfaceTypeElements =
nodeTextConfiguration.withInterfaceTypeElements
..withRedirectedConstructors =
nodeTextConfiguration.withRedirectedConstructors
..withSuperConstructors = nodeTextConfiguration.withSuperConstructors,
);
node.accept(
ResolvedAstPrinter(
sink: sink,
elementPrinter: elementPrinter,
configuration: nodeTextConfiguration,
),
);
var unit = node.thisOrAncestorOfType<CompilationUnitImpl>();
if (unit != null) {
sink.writeElements('invalidNodes', unit.invalidNodes, (node) {
var range = '[${node.offset}, ${node.end})';
sink.writelnWithIndent('${node.runtimeType} $range');
});
}
return buffer.toString();
}
Future<TestResolvedUnitResult> _resolveFileWithDiagnostics(
File file,
String code,
) async {
var cleanCode = removeDiagnosticExpectations(code);
modifyFile2(file, cleanCode);
var result = await resolveFile2(file);
var actual = updateExpectedDiagnostics(
content: cleanCode,
actualDiagnostics: result.diagnostics,
);
if (actual != code) {
NodeTextExpectationsCollector.add(actual);
if (NodeTextExpectationsCollector.shouldPrintFailureDetails) {
printPrettyDiff(code, actual);
}
fail('See the difference above.');
}
return result;
}
}
/// A test-facing view of a resolved unit, with utilities derived from it.
final class TestResolvedUnitResult {
final ResolvedUnitResultImpl analysisResult;
late final FindElement2 findElement = FindElement2(unit);
late final FindNode findNode = FindNode(content, unit);
TestResolvedUnitResult(this.analysisResult);
String get content => analysisResult.content;
List<Diagnostic> get diagnostics => analysisResult.diagnostics;
List<Diagnostic> get errors => analysisResult.errors;
bool get exists => analysisResult.exists;
File get file => analysisResult.file;
InheritanceManager3 get inheritanceManager {
return libraryElement.session.inheritanceManager;
}
bool get isLibrary => analysisResult.isLibrary;
bool get isPart => analysisResult.isPart;
LibraryElementImpl get libraryElement => analysisResult.libraryElement;
LibraryFragmentImpl get libraryFragment => analysisResult.libraryFragment;
String get path => analysisResult.path;
AnalysisSession get session => analysisResult.session;
TypeProviderImpl get typeProvider => analysisResult.typeProvider;
TypeSystemImpl get typeSystem => analysisResult.typeSystem;
CompilationUnitImpl get unit => analysisResult.unit;
Uri get uri => analysisResult.uri;
String get uriStr => '$uri';
}
extension ResolvedUnitResultExtension on ResolvedUnitResult {
FindElement2 get findElement2 {
return FindElement2(unit);
}
FindNode get findNode {
return FindNode(content, unit);
}
InheritanceManager3 get inheritanceManager {
var library = libraryElement as LibraryElementImpl;
return library.session.inheritanceManager;
}
String get uriStr => '$uri';
}