| // Copyright (c) 2026, 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. | |
| // ignore_for_file: non_constant_identifier_names | |
| import 'dart:convert'; | |
| import 'dart:core'; | |
| import 'package:api_summary/api_summary.dart'; | |
| import 'package:api_summary/src/api_builder.dart'; | |
| import 'package:test/test.dart'; | |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; | |
| import 'test_utils.dart'; | |
| void main() { | |
| defineReflectiveSuite(() { | |
| defineReflectiveTests(ApiBuilderTest); | |
| }); | |
| } | |
| @reflectiveTest | |
| class ApiBuilderTest extends ApiSummaryTest { | |
| @override | |
| bool get addMetaPackageDep => true; | |
| @override | |
| void setUp() { | |
| newPackage('foo').addFile('lib/foo.dart', r''' | |
| foo() {} | |
| class Foo {} | |
| '''); | |
| super.setUp(); | |
| } | |
| Future<void> test_fromJson_roundTrip() async { | |
| final summary = await _build({ | |
| '$testPackageLibPath/file.dart': ''' | |
| class C<T extends Object> { | |
| int x = 0; | |
| void m(String requiredParam, {int? optionalParam}) {} | |
| } | |
| enum E { a, b } | |
| extension Ext on int {} | |
| ''', | |
| }); | |
| final decodedMap = jsonDecode(summary) as Map<String, dynamic>; | |
| final rehydrated = ApiSummary.fromJson(decodedMap); | |
| expect(rehydrated.name, 'test'); | |
| expect(rehydrated.libraries, hasLength(1)); | |
| final library = rehydrated.libraries.single; | |
| expect(library.uri, 'package:test/file.dart'); | |
| expect(library.classes, hasLength(1)); | |
| expect(library.enums, hasLength(1)); | |
| expect(library.extensions, hasLength(1)); | |
| final cls = library.classes.single; | |
| expect(cls.name, 'C'); | |
| expect(cls.typeParameters, contains('T')); | |
| expect( | |
| cls.typeParameters['T'], | |
| isA<ApiInterfaceType>().having((t) => t.name, 'name', 'Object'), | |
| ); | |
| expect(cls.methods.map((m) => m.name), containsAll(['m', 'x', 'x='])); | |
| final methodM = cls.methods.firstWhere((m) => m.name == 'm'); | |
| expect( | |
| methodM, | |
| isA<ApiExecutable>() | |
| .having((m) => m.returnType, 'returnType', isA<ApiVoidType>()) | |
| .having((m) => m.parameters, 'parameters', hasLength(2)), | |
| ); | |
| expect( | |
| methodM.parameters[0], | |
| isA<ApiParameter>() | |
| .having((p) => p.name, 'name', 'requiredParam') | |
| .having( | |
| (p) => p.type, | |
| 'type', | |
| isA<ApiInterfaceType>().having((t) => t.name, 'name', 'String'), | |
| ) | |
| .having((p) => p.isRequired, 'isRequired', isTrue), | |
| ); | |
| expect( | |
| methodM.parameters[1], | |
| isA<ApiParameter>() | |
| .having((p) => p.name, 'name', 'optionalParam') | |
| .having( | |
| (p) => p.type, | |
| 'type', | |
| isA<ApiInterfaceType>().having((t) => t.name, 'name', 'int'), | |
| ) | |
| .having((p) => p.isNamed, 'isNamed', isTrue), | |
| ); | |
| // Verify re-encoding produces the same output | |
| final reEncodedSummary = jsonEncode(rehydrated.toJson()); | |
| expect(reEncodedSummary, summary); | |
| } | |
| Future<void> test_fromJson_roundTrip_comprehensive() async { | |
| final summary = await _build({ | |
| '$testPackageLibPath/comprehensive.dart': ''' | |
| import 'package:meta/meta.dart'; | |
| @experimental | |
| @deprecated | |
| int topLevelVar = 42; | |
| typedef IntAlias = int; | |
| typedef BoundedAlias<T extends num> = Map<String, T>; | |
| void takeRecord((int, {String name}) rec) {} | |
| void takeFunc(int Function(String) fn) {} | |
| void takeGenericFunc(void Function<T extends num>(T) fn) {} | |
| void genericMethod<T extends num>(T t) {} | |
| extension type Id(int value) {} | |
| extension type IdBounded<T extends num>(int value) implements int {} | |
| extension Ext<T extends num> on List<T> {} | |
| mixin M on Object {} | |
| ''', | |
| }); | |
| final decodedMap = jsonDecode(summary) as Map<String, dynamic>; | |
| final rehydrated = ApiSummary.fromJson(decodedMap); | |
| final reEncodedSummary = jsonEncode(rehydrated.toJson()); | |
| expect(reEncodedSummary, summary); | |
| final renderedText = rehydrated.toString(); | |
| expect( | |
| renderedText, | |
| equals(''' | |
| package:test/comprehensive.dart: | |
| topLevelVar (static getter: int, deprecated, experimental) | |
| topLevelVar= (static setter: int, deprecated, experimental) | |
| genericMethod (function: void Function<T extends num>(T)) | |
| takeFunc (function: void Function(int Function(String))) | |
| takeGenericFunc (function: void Function(void Function<T extends num>(T))) | |
| takeRecord (function: void Function((int, {String name}))) | |
| Id (extension type): | |
| new (constructor: Id Function(int)) | |
| value (getter: int) | |
| IdBounded (extension type<T extends num> implements int): | |
| new (constructor: IdBounded<T> Function(int)) | |
| value (getter: int) | |
| M (mixin on Object) | |
| Ext (extension on List<T>) | |
| BoundedAlias (type alias<T extends num> for Map<String, T>) | |
| IntAlias (type alias for int) | |
| dart:core: | |
| List (referenced) | |
| Map (referenced) | |
| Object (referenced) | |
| String (referenced) | |
| int (referenced) | |
| num (referenced) | |
| '''), | |
| ); | |
| } | |
| Future<void> test_jsonOutput() async { | |
| final summary = await _build({ | |
| '$testPackageLibPath/file.dart': ''' | |
| class C { | |
| int x = 0; | |
| void m() {} | |
| } | |
| ''', | |
| }); | |
| final decoded = jsonDecode(summary) as Map<String, dynamic>; | |
| expect( | |
| decoded, | |
| equals({ | |
| 'name': 'test', | |
| 'libraries': [ | |
| { | |
| 'uri': 'package:test/file.dart', | |
| 'isPublicEntryPoint': true, | |
| 'classes': [ | |
| { | |
| 'name': 'C', | |
| 'locationUri': 'package:test/file.dart', | |
| 'supertype': { | |
| 'kind': 'interface', | |
| 'name': 'Object', | |
| 'libraryUri': 'dart:core', | |
| }, | |
| 'constructors': [ | |
| { | |
| 'name': 'new', | |
| 'locationUri': 'package:test/file.dart', | |
| 'kind': 'constructor', | |
| 'returnType': { | |
| 'kind': 'interface', | |
| 'name': 'C', | |
| 'libraryUri': 'package:test/file.dart', | |
| }, | |
| }, | |
| ], | |
| 'methods': [ | |
| { | |
| 'name': 'm', | |
| 'locationUri': 'package:test/file.dart', | |
| 'kind': 'method', | |
| 'returnType': {'kind': 'void'}, | |
| }, | |
| { | |
| 'name': 'x', | |
| 'locationUri': 'package:test/file.dart', | |
| 'kind': 'getter', | |
| 'returnType': { | |
| 'kind': 'interface', | |
| 'name': 'int', | |
| 'libraryUri': 'dart:core', | |
| }, | |
| }, | |
| { | |
| 'name': 'x=', | |
| 'locationUri': 'package:test/file.dart', | |
| 'kind': 'setter', | |
| 'returnType': {'kind': 'void'}, | |
| 'parameters': [ | |
| { | |
| 'name': 'value', | |
| 'type': { | |
| 'kind': 'interface', | |
| 'name': 'int', | |
| 'libraryUri': 'dart:core', | |
| }, | |
| 'isRequired': true, | |
| }, | |
| ], | |
| }, | |
| ], | |
| }, | |
| ], | |
| }, | |
| ], | |
| }), | |
| ); | |
| } | |
| Future<void> test_includeReferencedTypes() async { | |
| final summary = await _build({ | |
| '$testPackageLibPath/file.dart': ''' | |
| import 'package:foo/foo.dart'; | |
| import 'src/private.dart'; | |
| class MyClass extends Foo implements PrivateInterface {} | |
| ''', | |
| '$testPackageLibPath/src/private.dart': ''' | |
| class PrivateInterface {} | |
| ''', | |
| }, customizer: const ApiSummaryCustomizer(includeReferencedTypes: true)); | |
| final decodedMap = jsonDecode(summary) as Map<String, dynamic>; | |
| final rehydrated = ApiSummary.fromJson(decodedMap); | |
| // Verify package:foo/foo.dart is in the summary libraries | |
| final fooLib = rehydrated.libraries.firstWhere( | |
| (l) => l.uri == 'package:foo/foo.dart', | |
| ); | |
| expect(fooLib.isPublicEntryPoint, isFalse); | |
| expect(fooLib.classes, hasLength(1)); | |
| final fooClass = fooLib.classes.single; | |
| expect(fooClass.name, 'Foo'); | |
| expect(fooClass.status, ApiDeclarationStatus.referenced); | |
| expect(fooClass.constructors, isEmpty); | |
| expect(fooClass.methods, isEmpty); | |
| // Verify private.dart is in the summary libraries | |
| final privateLib = rehydrated.libraries.firstWhere( | |
| (l) => l.uri == 'package:test/src/private.dart', | |
| ); | |
| expect(privateLib.isPublicEntryPoint, isFalse); | |
| expect(privateLib.classes, hasLength(1)); | |
| final privateClass = privateLib.classes.single; | |
| expect(privateClass.name, 'PrivateInterface'); | |
| expect(privateClass.status, ApiDeclarationStatus.nonPublic); | |
| expect(privateClass.constructors, isEmpty); | |
| expect(privateClass.methods, isEmpty); | |
| // Verify dart:core is also in the summary libraries for Object | |
| final coreLib = rehydrated.libraries.firstWhere( | |
| (l) => l.uri == 'dart:core', | |
| ); | |
| expect(coreLib.isPublicEntryPoint, isFalse); | |
| expect(coreLib.classes.map((c) => c.name), contains('Object')); | |
| final objectClass = coreLib.classes.firstWhere((c) => c.name == 'Object'); | |
| expect(objectClass.status, ApiDeclarationStatus.referenced); | |
| expect(objectClass.constructors, isEmpty); | |
| } | |
| Future<void> test_constMembers() async { | |
| final summary = await _build({ | |
| '$testPackageLibPath/file.dart': ''' | |
| class MyClass { | |
| static const int myConstField = 42; | |
| final int myFinalField; | |
| const MyClass(this.myFinalField); | |
| MyClass.nonConst() : myFinalField = 0; | |
| } | |
| const int myTopLevelConst = 100; | |
| enum MyEnum { | |
| v1, v2; | |
| static const int customConst = 3; | |
| } | |
| ''', | |
| }); | |
| final decodedMap = jsonDecode(summary) as Map<String, dynamic>; | |
| final rehydrated = ApiSummary.fromJson(decodedMap); | |
| final lib = rehydrated.libraries.single; | |
| // Verify top-level const | |
| final topLevelConst = lib.functions.firstWhere( | |
| (e) => e.name == 'myTopLevelConst', | |
| ); | |
| expect(topLevelConst.kind, ApiExecutableKind.getter); | |
| expect(topLevelConst.isConst, isTrue); | |
| expect(topLevelConst.isEnumConstant, isFalse); | |
| expect(topLevelConst.constantValue, '100'); | |
| final myClass = lib.classes.single; | |
| // Verify static const field (getter) | |
| final constField = myClass.methods.firstWhere( | |
| (e) => e.name == 'myConstField', | |
| ); | |
| expect(constField.kind, ApiExecutableKind.getter); | |
| expect(constField.isConst, isTrue); | |
| expect(constField.isEnumConstant, isFalse); | |
| expect(constField.constantValue, '42'); | |
| // Verify final field (getter) | |
| final finalField = myClass.methods.firstWhere( | |
| (e) => e.name == 'myFinalField', | |
| ); | |
| expect(finalField.kind, ApiExecutableKind.getter); | |
| expect(finalField.isConst, isFalse); | |
| expect(finalField.isEnumConstant, isFalse); | |
| expect(finalField.constantValue, isNull); | |
| // Verify const constructor | |
| final constConstructor = myClass.constructors.firstWhere( | |
| (e) => e.name == 'new', | |
| ); | |
| expect(constConstructor.kind, ApiExecutableKind.constructor); | |
| expect(constConstructor.isConst, isTrue); | |
| // Verify non-const constructor | |
| final nonConstConstructor = myClass.constructors.firstWhere( | |
| (e) => e.name == 'nonConst', | |
| ); | |
| expect(nonConstConstructor.kind, ApiExecutableKind.constructor); | |
| expect(nonConstConstructor.isConst, isFalse); | |
| // Verify enum constants | |
| final myEnum = lib.enums.single; | |
| final enumVal1 = myEnum.methods.firstWhere((e) => e.name == 'v1'); | |
| expect(enumVal1.isConst, isTrue); | |
| expect(enumVal1.isEnumConstant, isTrue); | |
| expect(enumVal1.constantValue, isNull); | |
| final enumCustomConst = myEnum.methods.firstWhere( | |
| (e) => e.name == 'customConst', | |
| ); | |
| expect(enumCustomConst.isConst, isTrue); | |
| expect(enumCustomConst.isEnumConstant, isFalse); | |
| expect(enumCustomConst.constantValue, '3'); | |
| final enumValues = myEnum.methods.firstWhere((e) => e.name == 'values'); | |
| expect(enumValues.isConst, isTrue); | |
| expect(enumValues.isEnumConstant, isFalse); | |
| expect(enumValues.constantValue, isNull); | |
| } | |
| Future<void> test_parameterDefaultValues() async { | |
| final summary = await _build({ | |
| '$testPackageLibPath/file.dart': r''' | |
| enum Mode { fast, safe } | |
| class Config { | |
| final int count; | |
| final String label; | |
| const Config(this.count, {this.label = 'default'}); | |
| } | |
| class _PrivateSentinel { | |
| const _PrivateSentinel(); | |
| } | |
| class PublicWithPrivateCtor { | |
| const PublicWithPrivateCtor._(); | |
| } | |
| class Base { | |
| const Base({int inherited = 10, int overridden = 20, int? noDefault}); | |
| } | |
| class Sub extends Base { | |
| const Sub({super.inherited, super.overridden = 99, super.noDefault}); | |
| } | |
| void configure( | |
| int requiredPositional, [ | |
| int optionalInt = 7, | |
| int? implicitNull, | |
| int? explicitNull = null, | |
| ]) {} | |
| void withNamed({ | |
| required int req, | |
| int count = 42, | |
| String name = 'a\nb', | |
| Mode mode = Mode.safe, | |
| Config config = const Config(1, label: 'ok'), | |
| Object redactedClass = const _PrivateSentinel(), | |
| Object redactedCtor = const PublicWithPrivateCtor._(), | |
| Object redactedTypeArg = const <_PrivateSentinel>[], | |
| }) {} | |
| ''', | |
| }); | |
| final decodedMap = jsonDecode(summary) as Map<String, dynamic>; | |
| final rehydrated = ApiSummary.fromJson(decodedMap); | |
| final lib = rehydrated.libraries.singleWhere( | |
| (l) => l.uri == 'package:test/file.dart', | |
| ); | |
| final configureFn = lib.functions.firstWhere((f) => f.name == 'configure'); | |
| final posParams = { | |
| for (final p in configureFn.parameters) p.name: p.defaultValue, | |
| }; | |
| expect(posParams, { | |
| 'requiredPositional': null, | |
| 'optionalInt': '7', | |
| 'implicitNull': null, | |
| 'explicitNull': null, | |
| }); | |
| final withNamedFn = lib.functions.firstWhere((f) => f.name == 'withNamed'); | |
| final namedParams = { | |
| for (final p in withNamedFn.parameters) p.name: p.defaultValue, | |
| }; | |
| expect(namedParams, { | |
| 'req': null, | |
| 'count': '42', | |
| 'name': r"'a\nb'", | |
| 'mode': 'Mode.safe', | |
| 'config': "Config(1, label: 'ok')", | |
| 'redactedClass': null, | |
| 'redactedCtor': null, | |
| 'redactedTypeArg': null, | |
| }); | |
| final subClass = lib.classes.firstWhere((c) => c.name == 'Sub'); | |
| final subCtor = subClass.constructors.single; | |
| final subParams = { | |
| for (final p in subCtor.parameters) p.name: p.defaultValue, | |
| }; | |
| expect(subParams, { | |
| 'inherited': '10', | |
| 'overridden': '99', | |
| 'noDefault': null, | |
| }); | |
| final rendered = rehydrated.toString(); | |
| expect( | |
| rendered, | |
| contains( | |
| 'configure (function: void Function(int, ' | |
| '[int = 7, int?, int?]))', | |
| ), | |
| ); | |
| expect( | |
| rendered, | |
| contains( | |
| "Config(1, label: 'ok'), " | |
| 'int count = 42, ' | |
| 'Mode mode = Mode.safe, ' | |
| r"String name = 'a\nb', " | |
| 'Object redactedClass, ' | |
| 'Object redactedCtor, ' | |
| 'Object redactedTypeArg, ' | |
| 'required int req', | |
| ), | |
| ); | |
| expect( | |
| rendered, | |
| contains( | |
| 'new (const constructor: Sub Function({int inherited = 10, ' | |
| 'int? noDefault, int overridden = 99}))', | |
| ), | |
| ); | |
| } | |
| Future<void> test_constantValues() async { | |
| final summary = await _build({ | |
| '$testPackageLibPath/file.dart': r''' | |
| enum Color { red, blue } | |
| enum _PrivateEnum { a } | |
| class _Private { | |
| const _Private(); | |
| } | |
| typedef PublicRecord = (int, {String label, bool flag}); | |
| typedef SinglePosRecord = (int,); | |
| typedef PrivateRecord = (int, {_Private priv}); | |
| typedef PublicCallback = | |
| String Function<T extends num>( | |
| T, [ | |
| int?, | |
| // ignore: avoid_positional_boolean_parameters | |
| ]); | |
| typedef NamedCallback = | |
| void Function({required bool flag, String label}); | |
| typedef PrivateCallback = void Function(_Private); | |
| void publicTopFunc() {} | |
| void _privateTopFunc() {} | |
| class Holder { | |
| final int x; | |
| final String label; | |
| final bool flag; | |
| const Holder(this.x, {this.label = '', this.flag = false}); | |
| const Holder.named() : x = 0, label = 'named', flag = true; | |
| const Holder._private() : x = -1, label = '', flag = false; | |
| static void publicStatic() {} | |
| static void _privateStatic() {} | |
| } | |
| const bool kBool = false; | |
| const int kInt = -42; | |
| const double kDouble = 3.14; | |
| const double kNaN = double.nan; | |
| const double kInfinity = double.infinity; | |
| const double kNegativeInfinity = double.negativeInfinity; | |
| const String kString = 'a\n\$b\'c\\d'; | |
| const Object? kNull = null; | |
| const Symbol kPublicSymbol = #mySymbol; | |
| const Symbol kPrivateSymbol = #_secret; | |
| const Type kSimpleType = int; | |
| const Type kGenericType = Map<String, List<int?>>; | |
| const Type kRecordType = PublicRecord; | |
| const Type kSingleRecordType = SinglePosRecord; | |
| const Type kPrivateRecordType = PrivateRecord; | |
| const Type kFunctionType = PublicCallback; | |
| const Type kNamedFunctionType = NamedCallback; | |
| const Type kPrivateFunctionType = PrivateCallback; | |
| const Type kPrivateType = _Private; | |
| const Type kPrivateTypeArg = List<_Private>; | |
| const List<int> kList = [1, 2]; | |
| const Set<String> kSet = {'a', 'b'}; | |
| const Map<String, int> kMap = {'x': 1}; | |
| const List<Object> kRedactedList = [_Private()]; | |
| const List<Object> kRedactedTypeArgList = <_Private>[]; | |
| const (int,) kSingleRecord = (1,); | |
| const (int, {String a, bool b}) kNamedRecord = (1, b: true, a: 'hi'); | |
| const Color kEnum = Color.blue; | |
| const Object kPrivateEnum = _PrivateEnum.a; | |
| const void Function() kTopFunc = publicTopFunc; | |
| const void Function() kPrivateTopFunc = _privateTopFunc; | |
| const void Function() kStaticMethod = Holder.publicStatic; | |
| const void Function() kPrivateStaticMethod = Holder._privateStatic; | |
| const Holder Function(int, {bool flag, String label}) kCtorTearOff = Holder.new; | |
| const Holder Function() kNamedCtorTearOff = Holder.named; | |
| const Object kPrivateCtorTearOff = Holder._private; | |
| const Holder kConstInstance = Holder(5, label: 'z', flag: true); | |
| const Holder kNamedInstance = Holder.named(); | |
| const Holder kPrivateCtorInstance = Holder._private(); | |
| const Object kPrivateClassInstance = _Private(); | |
| int get kComputedGetter => 1; | |
| ''', | |
| }); | |
| final decodedMap = jsonDecode(summary) as Map<String, dynamic>; | |
| final rehydrated = ApiSummary.fromJson(decodedMap); | |
| final lib = rehydrated.libraries.singleWhere( | |
| (l) => l.uri == 'package:test/file.dart', | |
| ); | |
| final constants = { | |
| for (final f in lib.functions) | |
| if (f.kind == ApiExecutableKind.getter) f.name: f.constantValue, | |
| }; | |
| expect(constants, { | |
| 'kBool': 'false', | |
| 'kInt': '-42', | |
| 'kDouble': '3.14', | |
| 'kNaN': 'double.nan', | |
| 'kInfinity': 'double.infinity', | |
| 'kNegativeInfinity': 'double.negativeInfinity', | |
| 'kString': r"'a\n\$b\'c\\d'", | |
| 'kNull': 'null', | |
| 'kPublicSymbol': '#mySymbol', | |
| 'kPrivateSymbol': null, | |
| 'kSimpleType': 'int', | |
| 'kGenericType': 'Map<String, List<int?>>', | |
| 'kRecordType': '(int, {bool flag, String label})', | |
| 'kSingleRecordType': '(int,)', | |
| 'kPrivateRecordType': null, | |
| 'kFunctionType': 'String Function<T extends num>(T, [int?])', | |
| 'kNamedFunctionType': 'void Function({required bool flag, String label})', | |
| 'kPrivateFunctionType': null, | |
| 'kPrivateType': null, | |
| 'kPrivateTypeArg': null, | |
| 'kList': '[1, 2]', | |
| 'kSet': "{'a', 'b'}", | |
| 'kMap': "{'x': 1}", | |
| 'kRedactedList': null, | |
| 'kRedactedTypeArgList': null, | |
| 'kSingleRecord': '(1,)', | |
| 'kNamedRecord': "(1, a: 'hi', b: true)", | |
| 'kEnum': 'Color.blue', | |
| 'kPrivateEnum': null, | |
| 'kTopFunc': 'publicTopFunc', | |
| 'kPrivateTopFunc': null, | |
| 'kStaticMethod': 'Holder.publicStatic', | |
| 'kPrivateStaticMethod': null, | |
| 'kCtorTearOff': 'Holder.new', | |
| 'kNamedCtorTearOff': 'Holder.named', | |
| 'kPrivateCtorTearOff': null, | |
| 'kConstInstance': "Holder(5, flag: true, label: 'z')", | |
| 'kNamedInstance': 'Holder.named()', | |
| 'kPrivateCtorInstance': null, | |
| 'kPrivateClassInstance': null, | |
| 'kComputedGetter': null, | |
| }); | |
| final rendered = rehydrated.toString(); | |
| expect(rendered, contains('kInt (static const getter: int = -42)')); | |
| expect(rendered, contains('kNull (static const getter: Object? = null)')); | |
| expect( | |
| rendered, | |
| contains('kPrivateClassInstance (static const getter: Object)\n'), | |
| ); | |
| } | |
| Future<void> test_customizerConstructorFlags() async { | |
| final summary = await _build( | |
| { | |
| '$testPackageLibPath/file.dart': ''' | |
| import 'src/private.dart'; | |
| class PublicClass extends LeakedBase {} | |
| ''', | |
| '$testPackageLibPath/src/private.dart': ''' | |
| class LeakedBase { | |
| void leakedMethod(TransitiveLeak arg) {} | |
| } | |
| class TransitiveLeak { | |
| int get count => 0; | |
| } | |
| ''', | |
| }, | |
| customizer: const ApiSummaryCustomizer( | |
| includeImplicitNonPublicMembers: true, | |
| ), | |
| ); | |
| final decodedMap = jsonDecode(summary) as Map<String, dynamic>; | |
| final rehydrated = ApiSummary.fromJson(decodedMap); | |
| final privateLib = rehydrated.libraries.firstWhere( | |
| (l) => l.uri == 'package:test/src/private.dart', | |
| ); | |
| expect(privateLib.isPublicEntryPoint, isFalse); | |
| expect( | |
| privateLib.classes.map((c) => c.name), | |
| containsAll(['LeakedBase', 'TransitiveLeak']), | |
| ); | |
| final leakedBase = privateLib.classes.firstWhere( | |
| (c) => c.name == 'LeakedBase', | |
| ); | |
| expect(leakedBase.status, ApiDeclarationStatus.nonPublic); | |
| expect(leakedBase.methods.map((m) => m.name), contains('leakedMethod')); | |
| final transitiveLeak = privateLib.classes.firstWhere( | |
| (c) => c.name == 'TransitiveLeak', | |
| ); | |
| expect(transitiveLeak.status, ApiDeclarationStatus.nonPublic); | |
| expect(transitiveLeak.methods.map((m) => m.name), contains('count')); | |
| } | |
| Future<String> _build( | |
| Map<String, String> files, { | |
| ApiSummaryCustomizer? customizer, | |
| }) async { | |
| // Create all the files. | |
| files.forEach(newFile); | |
| // As a sanity check, make sure there are no errors in any of the files. | |
| for (final file in files.keys) { | |
| if (file.endsWith('.dart')) await assertNoDiagnosticsInFile(file); | |
| } | |
| // Generate the API description. | |
| final context = contextCollection.contextFor( | |
| convertPath(testPackageLibPath), | |
| ); | |
| final resolvedCustomizer = customizer ?? const ApiSummaryCustomizer(); | |
| final package = await buildApiPackage('test', context, resolvedCustomizer); | |
| return jsonEncode(package.toJson()); | |
| } | |
| } |