// 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:core'; import 'package:analyzer/dart/analysis/analysis_context.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:api_summary/src/api_description.dart'; import 'package:api_summary/src/api_summary_customizer.dart'; import 'package:api_summary/src/node.dart'; import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'test_utils.dart'; void main() { defineReflectiveSuite(() { defineReflectiveTests(ApiDescriptionTest); }); } @reflectiveTest class ApiDescriptionTest extends ApiSummaryTest { @override bool get addMetaPackageDep => true; @override void setUp() { newPackage('foo').addFile('lib/foo.dart', r''' foo() {} class Foo {} '''); super.setUp(); } Future test_class_mixins() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' mixin M1 {} mixin M2 {} class C1 extends Object with M1 {} class C2 extends Object with M2 implements M1 {} ''', }); expect(summary, ''' package:test/file.dart: C1 (class extends Object with M1): new (constructor: C1 Function()) C2 (class extends Object with M2 implements M1): new (constructor: C2 Function()) M1 (mixin on Object) M2 (mixin on Object) dart:core: Object (referenced) '''); } Future test_class_modifiers() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' class C {} abstract class A {} base class B {} base mixin class BM {} mixin class M {} interface class I {} final class F {} abstract base class AB {} abstract base mixin class ABM {} abstract interface class AI {} abstract final class AF {} abstract mixin class AM {} ''', }); expect(summary, ''' package:test/file.dart: A (class extends Object, abstract): new (constructor: A Function()) AB (class extends Object, abstract, base): new (constructor: AB Function()) ABM (class extends Object, abstract, base, mixin): new (constructor: ABM Function()) AF (class extends Object, abstract, final) AI (class extends Object, abstract, interface) AM (class extends Object, abstract, mixin): new (constructor: AM Function()) B (class extends Object, base): new (constructor: B Function()) BM (class extends Object, base, mixin): new (constructor: BM Function()) C (class extends Object): new (constructor: C Function()) F (class extends Object, final): new (constructor: F Function()) I (class extends Object, interface): new (constructor: I Function()) M (class extends Object, mixin): new (constructor: M Function()) dart:core: Object (referenced) '''); } Future test_customize_shouldShowDetails() async { var summary = await _build( { '$testPackageLibPath/public.dart': ''' import 'src/private.dart'; void shown1(Shown2 x, Hidden2 y) {} void hidden1(Shown3 x, Hidden3 y) {} ''', '$testPackageLibPath/src/private.dart': ''' class Shown2 {} class Hidden2 {} class Shown3 {} class Hidden3 {} ''', }, createCustomizer: () => _ShouldShowDetailsCustomizer( (e) => e.name!.toLowerCase().contains('shown'), ), ); // Note: Shown2 and Hidden2 are included in the summary because they are // referenced by shown1. Details are only shown for shown1 and Shown2. expect(summary, ''' package:test/public.dart: hidden1 (non-public) shown1 (function: void Function(Shown2, Hidden2)) package:test/src/private.dart: Hidden2 (non-public) Shown2 (class extends Object): new (constructor: Shown2 Function()) dart:core: Object (referenced) '''); } Future test_field_deprecated() async { // Marking a field as deprecated causes its corresponding getter and setter // to be marked as deprecated in the summary. var summary = await _build({ '$testPackageLibPath/file.dart': ''' class C { @deprecated int x = 0; } ''', }); expect(summary, ''' package:test/file.dart: C (class extends Object): new (constructor: C Function()) x (getter: int, deprecated) x= (setter: int, deprecated) dart:core: Object (referenced) int (referenced) '''); } Future test_field_experimental() async { // Marking a field as experimental causes its corresponding getter and // setter to be marked as experimental in the summary. var summary = await _build({ '$testPackageLibPath/file.dart': ''' import 'package:meta/meta.dart'; class C { @experimental int x = 0; } ''', }); expect(summary, ''' package:test/file.dart: C (class extends Object): new (constructor: C Function()) x (getter: int, experimental) x= (setter: int, experimental) dart:core: Object (referenced) int (referenced) '''); } Future test_member_field() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' class C { int x = 0; } ''', }); // The summary output contains the getters and setters induced by the field, // not the field itself. expect(summary, ''' package:test/file.dart: C (class extends Object): new (constructor: C Function()) x (getter: int) x= (setter: int) dart:core: Object (referenced) int (referenced) '''); } Future test_member_getterSetterPair() async { // This test verifies that even if a getter and a setter have the same name, // both are included in the summary output. var summary = await _build({ '$testPackageLibPath/file.dart': ''' class C { get x => 0; set x(value) {} } ''', }); expect(summary, ''' package:test/file.dart: C (class extends Object): new (constructor: C Function()) x (getter: dynamic) x= (setter: dynamic) dart:core: Object (referenced) '''); } Future test_member_method() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' class C { void f() {} } ''', }); expect(summary, ''' package:test/file.dart: C (class extends Object): new (constructor: C Function()) f (method: void Function()) dart:core: Object (referenced) '''); } Future test_member_privateName() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' class C { f() { _f(); } _f() {} } ''', }); // The private member _f is not included in the summary output. expect(summary, ''' package:test/file.dart: C (class extends Object): new (constructor: C Function()) f (method: dynamic Function()) dart:core: Object (referenced) '''); } Future test_minimallyDescribesReferencedNamesInOtherPackages() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' import 'package:foo/foo.dart'; void f(Foo foo) {} ''', }); expect(summary, ''' package:test/file.dart: f (function: void Function(Foo)) package:foo/foo.dart: Foo (referenced) '''); } Future test_minimallyDescribesReferencedNonPublicNames() async { var summary = await _build({ '$testPackageLibPath/public.dart': ''' import 'src/private.dart'; void f(Foo foo) {} ''', '$testPackageLibPath/src/private.dart': 'class Foo {}', }); expect(summary, ''' package:test/public.dart: f (function: void Function(Foo)) package:test/src/private.dart: Foo (non-public) '''); } Future test_mixin_modifiers() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' mixin M {} base mixin BM {} ''', }); expect(summary, ''' package:test/file.dart: BM (mixin on Object, base) M (mixin on Object) dart:core: Object (referenced) '''); } Future test_nonConstructibleClass() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' abstract final class C1 {} abstract interface class C2 {} sealed class C3 {} ''', }); // These classes can't be constructed from outside the library, so their // constructors aren't included in the summary output. expect(summary, ''' package:test/file.dart: C1 (class extends Object, abstract, final) C2 (class extends Object, abstract, interface) C3 (class extends Object, sealed) dart:core: Object (referenced) '''); } Future test_sealedClass() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' sealed class C {} // Note: immediate subinterfaces will be sorted in summary output class C2 extends C {} class C1 extends C {} ''', }); expect(summary, ''' package:test/file.dart: C (class extends Object, sealed (immediate subtypes: C1, C2)) C1 (class extends C): new (constructor: C1 Function()) C2 (class extends C): new (constructor: C2 Function()) dart:core: Object (referenced) '''); } Future test_sealedClass_allKindsAndRelationships() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' sealed class S {} class C1 extends S {} class C2 implements S {} mixin M1 on S {} mixin M2 implements S {} enum E1 implements S { v } extension type T(C1 c) implements S {} ''', }); expect(summary, ''' package:test/file.dart: C1 (class extends S): new (constructor: C1 Function()) C2 (class extends Object implements S): new (constructor: C2 Function()) E1 (enum implements S): v (static getter: E1) values (static getter: List) M1 (mixin on S) M2 (mixin on Object implements S) S (class extends Object, sealed (immediate subtypes: C1, C2, E1, M1, M2, T)) T (extension type implements S): new (constructor: T Function(C1)) c (getter: C1) dart:core: List (referenced) Object (referenced) '''); } Future test_topLevel_collapsesRedundantElements() async { // When an element is exported by multiple libraries, it is only described // once; later references use the text "(see above)". var summary = await _build({ '$testPackageLibPath/file1.dart': 'export "file2.dart";', '$testPackageLibPath/file2.dart': 'class C {}', '$testPackageLibPath/file3.dart': 'export "file2.dart";', }); expect(summary, ''' package:test/file1.dart: C (class extends Object): new (constructor: C Function()) package:test/file2.dart: C (see above) package:test/file3.dart: C (see above) dart:core: Object (referenced) '''); } Future test_topLevel_disambiguatesNames() async { // If two libraries declare top level elements with the same name, the names // are disambiguated so that references are clear. var summary = await _build({ '$testPackageLibPath/file1.dart': ''' class A {} class B extends A {} ''', '$testPackageLibPath/file2.dart': ''' class A {} class B extends A {} ''', }); expect(summary, ''' package:test/file1.dart: A@1 (class extends Object): new (constructor: A@1 Function()) B@1 (class extends A@1): new (constructor: B@1 Function()) package:test/file2.dart: A@2 (class extends Object): new (constructor: A@2 Function()) B@2 (class extends A@2): new (constructor: B@2 Function()) dart:core: Object (referenced) '''); } Future test_topLevel_extension() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' extension E on int { void f() {} } ''', }); expect(summary, ''' package:test/file.dart: E (extension on int): f (method: void Function()) dart:core: int (referenced) '''); } Future test_topLevel_filesInSrc() async { var summary = await _build({ '$testPackageLibPath/public.dart': 'export "src/private1.dart";', '$testPackageLibPath/src/private1.dart': 'f() {}', '$testPackageLibPath/src/private2.dart': 'g() {}', }); // `f` is considered part of the public API because it is exported by // `public.dart`. expect(summary, ''' package:test/public.dart: f (function: dynamic Function()) '''); } Future test_topLevel_getterSetterPair() async { // This test verifies that even if getter and a setter have the same name, // both are included in the summary output. var summary = await _build({ '$testPackageLibPath/file.dart': ''' get x => 0; set x(value) {} ''', }); expect(summary, ''' package:test/file.dart: x (static getter: dynamic) x= (static setter: dynamic) '''); } Future test_topLevel_interfaceType() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' class I {} class B {} class C extends B implements I {} enum E implements I { e1 } mixin M on B implements I {} extension type T(int i) {} ''', }); expect(summary, ''' package:test/file.dart: B (class extends Object): new (constructor: B Function()) C (class extends B implements I): new (constructor: C Function()) E (enum implements I): e1 (static getter: E) values (static getter: List) I (class extends Object): new (constructor: I Function()) M (mixin on B implements I) T (extension type): new (constructor: T Function(int)) i (getter: int) dart:core: List (referenced) Object (referenced) int (referenced) '''); } Future test_topLevel_nonDartFile() async { var summary = await _build({'$testPackageLibPath/file.dar': 'f() {}'}); expect(summary, ''); } Future test_topLevel_otherPackagePublicApi() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' import 'package:foo/foo.dart'; main() { foo(); } ''', }); expect(summary, ''' package:test/file.dart: main (function: dynamic Function()) '''); } Future test_topLevel_partFile() async { // Declarations in a part file are considered part of the public API of the // containing library. var summary = await _build({ '$testPackageLibPath/lib.dart': 'part "part.dart";', '$testPackageLibPath/part.dart': ''' part of "lib.dart"; f() {} ''', }); expect(summary, ''' package:test/lib.dart: f (function: dynamic Function()) '''); } Future test_topLevel_privateName() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' f() { _g(); } _g() {} ''', }); // The private member _g is not included in the summary output. expect(summary, ''' package:test/file.dart: f (function: dynamic Function()) '''); } Future test_topLevel_sorted() async { // This test just verifies that sorting occurs. See `member_test.dart` for // tests of the precise nature of the sort order. var summary = await _build({ '$testPackageLibPath/file.dart': ''' g() {} f() {} get x => 0; class C {} ''', }); expect(summary, ''' package:test/file.dart: x (static getter: dynamic) f (function: dynamic Function()) g (function: dynamic Function()) C (class extends Object): new (constructor: C Function()) dart:core: Object (referenced) '''); } Future test_topLevel_sortsLibrariesByUri() async { var summary = await _build({ '$testPackageLibPath/file2.dart': '', '$testPackageLibPath/file1.dart': '', '$testPackageLibPath/file3.dart': '', }); expect(summary, ''' package:test/file1.dart: package:test/file2.dart: package:test/file3.dart: '''); } Future test_topLevel_typedef() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' typedef void oldStyleFunctionTypedef(); typedef void oldStyleFunctionTypedefGeneric(T t); typedef newStyleFunctionTypedef = void Function(); typedef newStyleFunctionTypedefGeneric = void Function(T); typedef nonFunctionTypedef = int; ''', }); expect(summary, ''' package:test/file.dart: newStyleFunctionTypedef (type alias for void Function()) newStyleFunctionTypedefGeneric (type alias for void Function(T)) nonFunctionTypedef (type alias for int) oldStyleFunctionTypedef (type alias for void Function()) oldStyleFunctionTypedefGeneric (type alias for void Function(T)) dart:core: int (referenced) '''); } Future test_types() async { var summary = await _build({ '$testPackageLibPath/file.dart': ''' import 'dart:async'; // Dynamic type dynamic get d => 0; // Null type Null get n => null; // FutureOr type FutureOr? get fo => null; // Function types void Function(int requiredPositionalParam, [int? optionalPositionalParam]) get f1 => throw ''; void Function({ // Note: named params will be sorted by name required int requiredNamedParam, int? optionalNamedParam})? get f2 => null; void f3(@deprecated int i, [@deprecated int? j]) {} void f4({@deprecated int? i}) {} void f5(T t1, T? t2) {} // Also tests type parameter types void f6(T t) {} // Interface types void f7(Map m1, Map? m2) {} // Record types void f8((int, String) r1, (int, {String s})? r2, // Note: named record fields will be sorted by name ({String s, int i}) r3) {} ''', }); expect(summary, ''' package:test/file.dart: d (static getter: dynamic) f1 (static getter: void Function(int, [int?])) f2 (static getter: void Function({int? optionalNamedParam, required int requiredNamedParam})?) fo (static getter: FutureOr?) n (static getter: Null) f3 (function: void Function(deprecated int, [deprecated int?])) f4 (function: void Function({deprecated int? i})) f5 (function: void Function(T, T?)) f6 (function: void Function(T)) f7 (function: void Function(Map, Map?)) f8 (function: void Function((int, String), (int, {String s})?, ({int i, String s}))) dart:async: FutureOr (referenced) dart:core: Map (referenced) Null (referenced) String (referenced) int (referenced) num (referenced) '''); } Future _build( Map files, { _ValidatingCustomizer Function()? createCustomizer, }) async { // Create all the files. files.forEach(newFile); // As a sanity check, make sure there are no errors in any of the files. for (var file in files.keys) { if (file.endsWith('.dart')) await assertNoDiagnosticsInFile(file); } // Generate the API description. var context = contextCollection.contextFor(convertPath(testPackageLibPath)); var customizer = createCustomizer?.call() ?? _ValidatingCustomizer(); var apiDescription = ApiDescription('test', customizer); var stringBuffer = StringBuffer(); var nodes = await apiDescription.build(context); expect(customizer.initialScanCompleteCalled, isTrue); printNodes(stringBuffer, nodes); return stringBuffer.toString(); } } final class _ShouldShowDetailsCustomizer extends _ValidatingCustomizer { final bool Function(Element) _shouldShowDetails; _ShouldShowDetailsCustomizer(this._shouldShowDetails); @override bool shouldShowDetails(Element element) { expect(initialScanCompleteCalled, isTrue); return _shouldShowDetails(element); } } base class _ValidatingCustomizer extends ApiSummaryCustomizer { bool topLevelPublicElementsCalled = false; bool analysisContextCalled = false; bool packageNameCalled = false; bool publicApiLibrariesCalled = false; bool initialScanCompleteCalled = false; bool setupCompleteCalled = false; @override set analysisContext(AnalysisContext value) { expect(analysisContextCalled, isFalse); analysisContextCalled = true; super.analysisContext = value; } @override set packageName(String value) { expect(packageNameCalled, isFalse); packageNameCalled = true; super.packageName = value; } @override set publicApiLibraries(Iterable value) { expect(setupCompleteCalled, isTrue); expect(publicApiLibrariesCalled, isFalse); publicApiLibrariesCalled = true; super.publicApiLibraries = value; } @override set topLevelPublicElements(Set value) { expect(setupCompleteCalled, isTrue); expect(topLevelPublicElementsCalled, isFalse); topLevelPublicElementsCalled = true; super.topLevelPublicElements = value; } @override Future initialScanComplete() async { expect(topLevelPublicElementsCalled, isTrue); expect(publicApiLibrariesCalled, isTrue); initialScanCompleteCalled = true; await super.initialScanComplete(); } @override Future setupComplete() async { expect(packageNameCalled, isTrue); expect(analysisContextCalled, isTrue); setupCompleteCalled = true; await super.setupComplete(); } @override bool shouldShowDetails(Element element) { expect(initialScanCompleteCalled, isTrue); return super.shouldShowDetails(element); } }