Files
sdk/pkg/api_summary/test/api_description_test.dart
T
kevmoo 1e8a9d5d45 [api_summary] Include mixins in textual API summaries
Fixes an issue where with mixin clauses were omitted when generating textual API summaries for class and interface declarations.

Regenerates api.txt for analyzer and analyzer_plugin.

Change-Id: Ic33d76955cefb31709e265ec4ea9d5df9a065f7b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/503701
Auto-Submit: Kevin Moore <kevmoo@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Kevin Moore <kevmoo@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
2026-05-22 11:35:09 -07:00

794 lines
20 KiB
Dart

// 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<E1>)
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<void> 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<void> 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<void> 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<void> 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<void> 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<void> test_topLevel_interfaceType() async {
var summary = await _build({
'$testPackageLibPath/file.dart': '''
class I {}
class B {}
class C<T> 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<T> extends B implements I):
new (constructor: C<T> Function())
E (enum implements I):
e1 (static getter: E)
values (static getter: List<E>)
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<void> test_topLevel_nonDartFile() async {
var summary = await _build({'$testPackageLibPath/file.dar': 'f() {}'});
expect(summary, '');
}
Future<void> 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<void> 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<void> 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<void> 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<void> 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<void> test_topLevel_typedef() async {
var summary = await _build({
'$testPackageLibPath/file.dart': '''
typedef void oldStyleFunctionTypedef();
typedef void oldStyleFunctionTypedefGeneric<T>(T t);
typedef newStyleFunctionTypedef = void Function();
typedef newStyleFunctionTypedefGeneric = void Function<T>(T);
typedef nonFunctionTypedef = int;
''',
});
expect(summary, '''
package:test/file.dart:
newStyleFunctionTypedef (type alias for void Function())
newStyleFunctionTypedefGeneric (type alias for void Function<T>(T))
nonFunctionTypedef (type alias for int)
oldStyleFunctionTypedef (type alias for void Function())
oldStyleFunctionTypedefGeneric (type alias<T> for void Function(T))
dart:core:
int (referenced)
''');
}
Future<void> 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<int>? 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>(T t1, T? t2) {} // Also tests type parameter types
void f6<T extends num>(T t) {}
// Interface types
void f7(Map<String, int> m1, Map<String, int>? 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<int>?)
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, T?))
f6 (function: void Function<T extends num>(T))
f7 (function: void Function(Map<String, int>, Map<String, int>?))
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<String> _build(
Map<String, String> 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<LibraryElement> value) {
expect(setupCompleteCalled, isTrue);
expect(publicApiLibrariesCalled, isFalse);
publicApiLibrariesCalled = true;
super.publicApiLibraries = value;
}
@override
set topLevelPublicElements(Set<Element> value) {
expect(setupCompleteCalled, isTrue);
expect(topLevelPublicElementsCalled, isFalse);
topLevelPublicElementsCalled = true;
super.topLevelPublicElements = value;
}
@override
Future<void> initialScanComplete() async {
expect(topLevelPublicElementsCalled, isTrue);
expect(publicApiLibrariesCalled, isTrue);
initialScanCompleteCalled = true;
await super.initialScanComplete();
}
@override
Future<void> 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);
}
}