| // Copyright (c) 2022, 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:_fe_analyzer_shared/src/exhaustiveness/exhaustive.dart'; |
| import 'package:_fe_analyzer_shared/src/exhaustiveness/key.dart'; |
| import 'package:_fe_analyzer_shared/src/exhaustiveness/path.dart'; |
| import 'package:_fe_analyzer_shared/src/exhaustiveness/space.dart'; |
| import 'package:_fe_analyzer_shared/src/exhaustiveness/static_type.dart'; |
| import 'package:test/test.dart'; |
| |
| /// Test that [spaces] is exhaustive over [value]. |
| void expectExhaustive( |
| ObjectPropertyLookup fieldLookup, |
| Space value, |
| List<Space> spaces, |
| ) { |
| _expectExhaustive(fieldLookup, value, spaces, true); |
| } |
| |
| /// Test that [cases] are exhaustive over [type] if and only if all cases are |
| /// included and that all subsets of the cases are not exhaustive. |
| void expectExhaustiveOnlyAll( |
| ObjectPropertyLookup fieldLookup, |
| StaticType type, |
| List<Object> cases, |
| ) { |
| _testCases(fieldLookup, type, cases, true); |
| } |
| |
| /// Test that [cases] are not exhaustive over [type]. Also test that omitting |
| /// each case is still not exhaustive. |
| void expectNeverExhaustive( |
| ObjectPropertyLookup fieldLookup, |
| StaticType type, |
| List<Object> cases, |
| ) { |
| _testCases(fieldLookup, type, cases, false); |
| } |
| |
| /// Test that [spaces] is not exhaustive over [value]. |
| void expectNotExhaustive( |
| ObjectPropertyLookup fieldLookup, |
| Space value, |
| List<Space> spaces, |
| ) { |
| _expectExhaustive(fieldLookup, value, spaces, false); |
| } |
| |
| Map<Key, Space> fieldsToSpace( |
| Map<String, Object> fields, |
| Path path, { |
| required bool asRecordNames, |
| }) => fields.map((String name, Object value) { |
| Key key = asRecordNames ? new RecordNameKey(name) : new NameKey(name); |
| return MapEntry(key, parseSpace(value, path.add(key))); |
| }); |
| |
| Space parseSpace(Object object, [Path path = const Path.root()]) { |
| if (object is Space) return object; |
| if (object == '∅') return Space(path, StaticType.neverType); |
| if (object is StaticType) return Space(path, object); |
| if (object is List<Object>) { |
| Space? spaces; |
| for (Object element in object) { |
| if (spaces == null) { |
| spaces = parseSpace(element, path); |
| } else { |
| spaces = spaces.union(parseSpace(element, path)); |
| } |
| } |
| return spaces ?? new Space.empty(path); |
| } |
| throw ArgumentError('Invalid space $object'); |
| } |
| |
| /// Parse a list of spaces using [parseSpace]. |
| List<Space> parseSpaces(List<Object> objects) => |
| objects.map(parseSpace).toList(); |
| |
| /// Make a [Space] with [type] and [fields]. |
| Space ty( |
| StaticType type, |
| Map<String, Object> fields, [ |
| Path path = const Path.root(), |
| ]) => Space( |
| path, |
| type, |
| properties: fieldsToSpace(fields, path, asRecordNames: type.isRecord), |
| ); |
| |
| void _checkExhaustive( |
| ObjectPropertyLookup fieldLookup, |
| Space value, |
| List<Space> spaces, |
| bool expectation, |
| ) { |
| var actual = isExhaustive(fieldLookup, value, spaces); |
| if (expectation != actual) { |
| if (expectation) { |
| fail('Expected $spaces to cover $value but did not.'); |
| } else { |
| fail('Expected $spaces to not cover $value but did.'); |
| } |
| } |
| } |
| |
| void _expectExhaustive( |
| ObjectPropertyLookup fieldLookup, |
| Space value, |
| List<Space> spaces, |
| bool expectation, |
| ) { |
| test('$value - ${spaces.join(' - ')} ${expectation ? 'is' : 'is not'} ' |
| 'exhaustive', () { |
| _checkExhaustive(fieldLookup, value, spaces, expectation); |
| }); |
| } |
| |
| /// Test that [cases] are not exhaustive over [type]. |
| void _testCases( |
| ObjectPropertyLookup fieldLookup, |
| StaticType type, |
| List<Object> cases, |
| bool expectation, |
| ) { |
| var valueSpace = Space(const Path.root(), type); |
| var spaces = parseSpaces(cases); |
| |
| test('$type with all cases', () { |
| _checkExhaustive(fieldLookup, valueSpace, spaces, expectation); |
| }); |
| |
| // With any single case removed, should also not be exhaustive. |
| for (var i = 0; i < spaces.length; i++) { |
| var filtered = spaces.toList(); |
| filtered.removeAt(i); |
| |
| test('$type without case ${spaces[i]}', () { |
| _checkExhaustive(fieldLookup, valueSpace, filtered, false); |
| }); |
| } |
| } |