// Copyright (c) 2019, 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 'dart:io'; import 'package:_fe_analyzer_shared/src/testing/id.dart' show ActualData, Id; import 'package:_fe_analyzer_shared/src/testing/id_testing.dart'; import 'package:analyzer/dart/analysis/features.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/src/dart/analysis/testing_data.dart'; import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart'; import 'package:analyzer/src/util/ast_data_extractor.dart'; import 'package:test/test.dart'; import '../util/id_testing_helper.dart'; main(List args) async { Directory dataDir = Directory.fromUri(Platform.script.resolve( '../../../_fe_analyzer_shared/test/flow_analysis/reachability/data')); return runTests>(dataDir, args: args, createUriForFileName: createUriForFileName, onFailure: onFailure, runTest: runTestFor(const _ReachabilityDataComputer(), [analyzerNnbdConfig])); } class FlowTestBase { late final FlowAnalysisDataForTesting flowResult; /// Resolve the given [code] and track nullability in the unit. Future trackCode(String code) async { TestResult> testResult = await checkTests( code, const _ReachabilityDataComputer(), FeatureSet.forTesting( sdkVersion: '2.2.2', additionalFeatures: [Feature.non_nullable])); if (testResult.hasFailures) { fail('Failure(s)'); } } } enum _ReachabilityAssertion { doesNotComplete, unreachable, } class _ReachabilityDataComputer extends DataComputer> { const _ReachabilityDataComputer(); @override DataInterpreter> get dataValidator => const _ReachabilityDataInterpreter(); @override void computeUnitData(TestingData testingData, CompilationUnit unit, Map>> actualMap) { var unitElement = unit.declaredElement!; var flowResult = testingData.uriToFlowAnalysisData[unitElement.source.uri]!; _ReachabilityDataExtractor(unitElement.source.uri, actualMap, flowResult) .run(unit); } } class _ReachabilityDataExtractor extends AstDataExtractor> { final FlowAnalysisDataForTesting _flowResult; _ReachabilityDataExtractor( Uri uri, Map>> actualMap, this._flowResult) : super(uri, actualMap); @override Set<_ReachabilityAssertion>? computeNodeValue(Id id, AstNode node) { Set<_ReachabilityAssertion> result = {}; if (node is Expression && node.parent is ExpressionStatement) { // The reachability of an expression statement and the statement it // contains should always be the same. We check this with an assert // statement, and only annotate the expression statement, to reduce the // amount of redundancy in the test files. assert(_flowResult.unreachableNodes.contains(node) == _flowResult.unreachableNodes.contains(node.parent)); } else if (_flowResult.unreachableNodes.contains(node)) { result.add(_ReachabilityAssertion.unreachable); } if (node is FunctionDeclaration) { _checkBodyCompletion(node.functionExpression.body, result); } else if (node is ConstructorDeclaration) { _checkBodyCompletion(node.body, result); } else if (node is MethodDeclaration) { _checkBodyCompletion(node.body, result); } return result.isEmpty ? null : result; } void _checkBodyCompletion( FunctionBody? body, Set<_ReachabilityAssertion> result) { if (body != null && _flowResult.functionBodiesThatDontComplete.contains(body)) { result.add(_ReachabilityAssertion.doesNotComplete); } } } class _ReachabilityDataInterpreter implements DataInterpreter> { const _ReachabilityDataInterpreter(); @override String getText(Set<_ReachabilityAssertion> actualData, [String? indentation]) => _sortedRepresentation(_toStrings(actualData)); @override String? isAsExpected( Set<_ReachabilityAssertion> actualData, String? expectedData) { var actualStrings = _toStrings(actualData); var actualSorted = _sortedRepresentation(actualStrings); var expectedSorted = _sortedRepresentation(expectedData?.split(',')); if (actualSorted == expectedSorted) { return null; } else { return 'Expected $expectedData, got $actualSorted'; } } @override bool isEmpty(Set<_ReachabilityAssertion> actualData) => actualData.isEmpty; String _sortedRepresentation(Iterable? values) { var list = values == null || values.isEmpty ? ['none'] : values.toList(); list.sort(); return list.join(','); } List _toStrings(Set<_ReachabilityAssertion> actualData) => actualData .map((flowAssertion) => flowAssertion.toString().split('.')[1]) .toList(); }