Bump tools to 338a2c8380059afb250a1d0c26555e4da24c6ccc

Also removed SDK-version of api_summary.

Migrates the `api_summary` package from the SDK (`pkg/api_summary`)
to the `tools` monorepo (`third_party/pkg/tools/pkgs/api_summary`).

Updates SDK workspaces and dependency overrides in `pubspec.yaml`
and unit tests in `tools/bots/test_matrix.json`.

The API summary when through a review when landed in tools and includes additional changes. See https://github.com/dart-lang/tools/pull/2412/commits

The changes to `api.txt` files are due to this bug fix:
https://github.com/dart-lang/tools/pull/2412/changes/1fcb09f9466740e21b93a1b082d2bd4cc9ded9c7#diff-ee68240679409574825ff54e43ecf132ff6eb7214844aabeb88b576944f4d5b7R372

Changes:
```
> git log --format="%C(auto) %h %s" 6a7dd15..338a2c8
 https://dart.googlesource.com/tools.git/+/338a2c83 [api_summary] Include mixins in textual API summaries (2417)
 https://dart.googlesource.com/tools.git/+/c436f994 feat(api_summary): Move api_summary package into the tools monorepo (2412)
 https://dart.googlesource.com/tools.git/+/abd67801 [markdown] fix new lint RE await in an async function (2416)
 https://dart.googlesource.com/tools.git/+/fc11c6ea [code_builder] Expand code coverage (2409)
 https://dart.googlesource.com/tools.git/+/999a2df4 [code_builder] Emit ignore_for_file bits correctly (2406)
 https://dart.googlesource.com/tools.git/+/215ed97c [code_builder] Drop unused dev_deps and rebuild (2407)
 https://dart.googlesource.com/tools.git/+/6b99b07f Remove some extra spaces in unformatted output (2403)
 https://dart.googlesource.com/tools.git/+/2bf09dee Mark AnsiCode and AnsiCodeType final (2402)
 https://dart.googlesource.com/tools.git/+/03f2c205 publish cli_util version 0.5.1 (2397)
```

Diff: https://dart.googlesource.com/tools.git/+/6a7dd15748e63db7d41cfee8294c54636b668f41..338a2c8380059afb250a1d0c26555e4da24c6ccc/

Change-Id: I239f01adc4f0ba5e939e517abcaa1143d538c949
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/507264
Reviewed-by: Jaime Wren <jwren@google.com>
Commit-Queue: Kevin Moore <kevmoo@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
Auto-Submit: Kevin Moore <kevmoo@google.com>
Commit-Queue: Jaime Wren <jwren@google.com>
This commit is contained in:
kevmoo
2026-06-02 11:54:20 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent e61679f449
commit 45c27304eb
28 changed files with 18 additions and 2487 deletions
+1 -1
View File
@@ -149,7 +149,7 @@ vars = {
"sync_http_rev": "6666fff944221891182e1f80bf56569338164d72",
"tar_rev": "13479f7c2a18f499e840ad470cfcca8c579f6909",
"test_rev": "14f99ecdcb74778654d936ce26c70798a3b6649f",
"tools_rev": "6a7dd15748e63db7d41cfee8294c54636b668f41",
"tools_rev": "338a2c8380059afb250a1d0c26555e4da24c6ccc",
"vector_math_rev": "7bf60fb95e0fbbd7648944071de9ab5e32ce7387",
"web_rev": "428cc812ac022c995abfd2b74e3903610f1360fd",
"webdev_rev": "f9a56607fac5ad0c979d2647cf11d3e3be993bf6",
+14
View File
@@ -206,6 +206,7 @@ package:analyzer_plugin/protocol/protocol_common.dart:
INFO (static getter: AnalysisErrorSeverity)
WARNING (static getter: AnalysisErrorSeverity)
values (static getter: List<AnalysisErrorSeverity>)
fromJson (constructor: AnalysisErrorSeverity Function(JsonDecoder, String, Object?, {ClientUriConverter? clientUriConverter}))
toJson (method: String Function({ClientUriConverter? clientUriConverter}))
toString (method: String Function())
AnalysisErrorType (enum):
@@ -218,6 +219,7 @@ package:analyzer_plugin/protocol/protocol_common.dart:
SYNTACTIC_ERROR (static getter: AnalysisErrorType)
TODO (static getter: AnalysisErrorType)
values (static getter: List<AnalysisErrorType>)
fromJson (constructor: AnalysisErrorType Function(JsonDecoder, String, Object?, {ClientUriConverter? clientUriConverter}))
toJson (method: String Function({ClientUriConverter? clientUriConverter}))
toString (method: String Function())
AssistDescription (class extends Object implements HasToJson):
@@ -311,6 +313,7 @@ package:analyzer_plugin/protocol/protocol_common.dart:
PACKAGE_NAME (static getter: CompletionSuggestionKind)
PARAMETER (static getter: CompletionSuggestionKind)
values (static getter: List<CompletionSuggestionKind>)
fromJson (constructor: CompletionSuggestionKind Function(JsonDecoder, String, Object?, {ClientUriConverter? clientUriConverter}))
toJson (method: String Function({ClientUriConverter? clientUriConverter}))
toString (method: String Function())
DiagnosticMessage@1 (class extends Object implements HasToJson):
@@ -393,6 +396,7 @@ package:analyzer_plugin/protocol/protocol_common.dart:
UNIT_TEST_TEST (static getter: ElementKind@1)
UNKNOWN (static getter: ElementKind@1)
values (static getter: List<ElementKind@1>)
fromJson (constructor: ElementKind@1 Function(JsonDecoder, String, Object?, {ClientUriConverter? clientUriConverter}))
toJson (method: String Function({ClientUriConverter? clientUriConverter}))
toString (method: String Function())
FixDescription (class extends Object implements HasToJson):
@@ -421,6 +425,7 @@ package:analyzer_plugin/protocol/protocol_common.dart:
LITERAL (static getter: FoldingKind)
PARAMETERS (static getter: FoldingKind)
values (static getter: List<FoldingKind>)
fromJson (constructor: FoldingKind Function(JsonDecoder, String, Object?, {ClientUriConverter? clientUriConverter}))
toJson (method: String Function({ClientUriConverter? clientUriConverter}))
toString (method: String Function())
FoldingRegion (class extends Object implements HasToJson):
@@ -534,6 +539,7 @@ package:analyzer_plugin/protocol/protocol_common.dart:
UNRESOLVED_INSTANCE_MEMBER_REFERENCE (static getter: HighlightRegionType)
VALID_STRING_ESCAPE (static getter: HighlightRegionType)
values (static getter: List<HighlightRegionType>)
fromJson (constructor: HighlightRegionType Function(JsonDecoder, String, Object?, {ClientUriConverter? clientUriConverter}))
toJson (method: String Function({ClientUriConverter? clientUriConverter}))
toString (method: String Function())
LinkedEditGroup (class extends Object implements HasToJson):
@@ -569,6 +575,7 @@ package:analyzer_plugin/protocol/protocol_common.dart:
TYPE (static getter: LinkedEditSuggestionKind)
VARIABLE (static getter: LinkedEditSuggestionKind)
values (static getter: List<LinkedEditSuggestionKind>)
fromJson (constructor: LinkedEditSuggestionKind Function(JsonDecoder, String, Object?, {ClientUriConverter? clientUriConverter}))
toJson (method: String Function({ClientUriConverter? clientUriConverter}))
toString (method: String Function())
Location (class extends Object implements HasToJson):
@@ -681,6 +688,7 @@ package:analyzer_plugin/protocol/protocol_common.dart:
REQUIRED_NAMED (static getter: ParameterKind)
REQUIRED_POSITIONAL (static getter: ParameterKind)
values (static getter: List<ParameterKind>)
fromJson (constructor: ParameterKind Function(JsonDecoder, String, Object?, {ClientUriConverter? clientUriConverter}))
toJson (method: String Function({ClientUriConverter? clientUriConverter}))
toString (method: String Function())
PluginDetails (class extends Object implements HasToJson):
@@ -735,6 +743,7 @@ package:analyzer_plugin/protocol/protocol_common.dart:
MOVE_FILE (static getter: RefactoringKind)
RENAME (static getter: RefactoringKind)
values (static getter: List<RefactoringKind>)
fromJson (constructor: RefactoringKind Function(JsonDecoder, String, Object?, {ClientUriConverter? clientUriConverter}))
toJson (method: String Function({ClientUriConverter? clientUriConverter}))
toString (method: String Function())
RefactoringMethodParameter (class extends Object implements HasToJson):
@@ -759,6 +768,7 @@ package:analyzer_plugin/protocol/protocol_common.dart:
POSITIONAL (static getter: RefactoringMethodParameterKind)
REQUIRED (static getter: RefactoringMethodParameterKind)
values (static getter: List<RefactoringMethodParameterKind>)
fromJson (constructor: RefactoringMethodParameterKind Function(JsonDecoder, String, Object?, {ClientUriConverter? clientUriConverter}))
toJson (method: String Function({ClientUriConverter? clientUriConverter}))
toString (method: String Function())
RefactoringProblem (class extends Object implements HasToJson):
@@ -781,6 +791,7 @@ package:analyzer_plugin/protocol/protocol_common.dart:
WARNING (static getter: RefactoringProblemSeverity)
values (static getter: List<RefactoringProblemSeverity>)
max (static method: RefactoringProblemSeverity? Function(RefactoringProblemSeverity?, RefactoringProblemSeverity?))
fromJson (constructor: RefactoringProblemSeverity Function(JsonDecoder, String, Object?, {ClientUriConverter? clientUriConverter}))
toJson (method: String Function({ClientUriConverter? clientUriConverter}))
toString (method: String Function())
RemoveContentOverlay (class extends Object implements HasToJson):
@@ -1090,6 +1101,7 @@ package:analyzer_plugin/protocol/protocol_generated.dart:
OCCURRENCES (static getter: AnalysisService)
OUTLINE (static getter: AnalysisService)
values (static getter: List<AnalysisService>)
fromJson (constructor: AnalysisService Function(JsonDecoder, String, Object?, {ClientUriConverter? clientUriConverter}))
toJson (method: String Function({ClientUriConverter? clientUriConverter}))
toString (method: String Function())
AnalysisSetAnalysisRootsParams (class extends Object implements RequestParams):
@@ -1656,6 +1668,7 @@ package:analyzer_plugin/protocol/protocol_generated.dart:
PLUGIN_ERROR (static getter: RequestErrorCode)
UNKNOWN_REQUEST (static getter: RequestErrorCode)
values (static getter: List<RequestErrorCode>)
fromJson (constructor: RequestErrorCode Function(JsonDecoder, String, Object?, {ClientUriConverter? clientUriConverter}))
toJson (method: String Function({ClientUriConverter? clientUriConverter}))
toString (method: String Function())
WatchEvent (class extends Object implements HasToJson):
@@ -1674,6 +1687,7 @@ package:analyzer_plugin/protocol/protocol_generated.dart:
MODIFY (static getter: WatchEventType)
REMOVE (static getter: WatchEventType)
values (static getter: List<WatchEventType>)
fromJson (constructor: WatchEventType Function(JsonDecoder, String, Object?, {ClientUriConverter? clientUriConverter}))
toJson (method: String Function({ClientUriConverter? clientUriConverter}))
toString (method: String Function())
package:analyzer_plugin/src/protocol/protocol_internal.dart:
-3
View File
@@ -1,3 +0,0 @@
## 0.1.0-wip
- First release.
-27
View File
@@ -1,27 +0,0 @@
Copyright 2026, the Dart project authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-3
View File
@@ -1,3 +0,0 @@
file:/tools/OWNERS_DEVELOPER_EXPERIENCE
file:/tools/OWNERS_MODEL
kevmoo@google.com
-1
View File
@@ -1 +0,0 @@
A tool to create a human-readable text summary of the public API of a Dart package. This is useful for auditing API changes with a `diff` tool.
-5
View File
@@ -1,5 +0,0 @@
include: package:dart_flutter_team_lints/analysis_options.yaml
linter:
rules:
- unnecessary_ignore
-25
View File
@@ -1,25 +0,0 @@
package:api_summary/api_summary.dart:
summarizePackage (function: Future<String> Function(String, String, {ApiSummaryCustomizer Function()? createCustomizer}))
ApiSummaryCustomizer (class extends Object, base):
new (constructor: ApiSummaryCustomizer Function())
analysisContext= (setter: AnalysisContext)
packageName= (setter: String)
publicApiLibraries= (setter: Iterable<LibraryElement>)
topLevelPublicElements (getter: Set<Element>)
topLevelPublicElements= (setter: Set<Element>)
initialScanComplete (method: Future<void> Function())
setupComplete (method: Future<void> Function())
shouldShowDetails (method: bool Function(Element))
dart:async:
Future (referenced)
dart:core:
Iterable (referenced)
Object (referenced)
Set (referenced)
String (referenced)
bool (referenced)
package:analyzer/dart/analysis/analysis_context.dart:
AnalysisContext (referenced)
package:analyzer/dart/element/element.dart:
Element (referenced)
LibraryElement (referenced)
-81
View File
@@ -1,81 +0,0 @@
// 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.
import 'dart:io';
import 'package:api_summary/api_summary.dart';
import 'package:args/args.dart';
import 'package:path/path.dart' as p;
import 'package:yaml/yaml.dart';
Future<void> main(List<String> arguments) async {
try {
final results = parser.parse(arguments);
if (results.flag('help')) {
print('Usage: api_summary [options]');
print(parser.usage);
return;
}
final packagePath =
results.option('package-path') ?? Directory.current.path;
final absolutePath = p.normalize(p.absolute(packagePath));
final pubspecFile = File(p.join(absolutePath, 'pubspec.yaml'));
if (!pubspecFile.existsSync()) {
stderr.writeln('Error: No pubspec.yaml found at "$absolutePath".');
exitCode = 1;
return;
}
final packageName = _extractPackageName(pubspecFile);
final summary = await summarizePackage(absolutePath, packageName);
stdout.write(summary);
} on FormatException catch (e) {
stderr.writeln('Error: ${e.message}');
stderr.writeln('\nUsage: api_summary [options]');
stderr.writeln(parser.usage);
exitCode = 64;
return;
}
}
final parser = ArgParser()
..addOption(
'package-path',
abbr: 'p',
help:
'The path to the package to summarize. Defaults to the current '
'directory.',
)
..addFlag(
'help',
abbr: 'h',
help: 'Print this usage information.',
negatable: false,
);
String _extractPackageName(File pubspecFile) {
final content = pubspecFile.readAsStringSync();
final yaml = loadYaml(content);
if (yaml is! Map) {
throw ArgumentError(
'Expected pubspec.yaml at ${pubspecFile.path} to be a YAML map.',
);
}
final name = yaml['name'];
if (name == null) {
throw ArgumentError(
'Could not find a "name" field in pubspec.yaml at ${pubspecFile.path}.',
);
}
if (name is! String) {
throw ArgumentError(
'The "name" field in pubspec.yaml at ${pubspecFile.path} must be a '
'String.',
);
}
return name;
}
-46
View File
@@ -1,46 +0,0 @@
// 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.
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'src/api_description.dart';
import 'src/api_summary_customizer.dart';
import 'src/node.dart';
export 'src/api_summary_customizer.dart' show ApiSummaryCustomizer;
/// Creates a human-readable text summary of the public API of a package, in a
/// format suitable for auditing with a `diff` tool.
///
/// [packagePath] is the path to the directory containing the package's
/// `pubspec.yaml` file.
///
/// [packageName] is the name of the package.
///
/// If [createCustomizer] is provided, it will be called to create an instance
/// of [ApiSummaryCustomizer] which will be used to customize the behavior of
/// the tool.
Future<String> summarizePackage(
String packagePath,
String packageName, {
ApiSummaryCustomizer Function()? createCustomizer,
}) async {
var provider = PhysicalResourceProvider.INSTANCE;
var libPath = provider.pathContext.join(packagePath, 'lib');
var collection = AnalysisContextCollection(
resourceProvider: provider,
includedPaths: [libPath],
);
// Use `.single` to make sure that `collection` just contains a single
// context. This ensures that `publicApi.build` will see all the files in
// the package.
var context = collection.contexts.single;
var publicApi = ApiDescription(
packageName,
createCustomizer?.call() ?? ApiSummaryCustomizer(),
);
var stringBuffer = StringBuffer();
printNodes(stringBuffer, await publicApi.build(context));
return stringBuffer.toString();
}
@@ -1,494 +0,0 @@
// 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.
import 'dart:collection';
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:collection/collection.dart';
import 'api_summary_customizer.dart';
import 'extensions.dart';
import 'member_sorting.dart';
import 'node.dart';
import 'unique_namer.dart';
import 'uri_sorting.dart';
/// Data structure keeping track of a package's API while walking it to produce
/// `api.txt`.
class ApiDescription {
final ApiSummaryCustomizer _customizer;
final String _pkgName;
/// Top level elements that have already had their child elements dumped.
///
/// If an element is seen again in a different library, it will be followed
/// with `(see above)` (rather than having its child elements dumped twice).
final _dumpedTopLevelElements = <Element>{};
/// Top level elements that have been referenced so far and haven't yet been
/// processed by [build].
///
/// This is used to ensure that all elements referred to by the public API
/// (e.g., by being mentioned in the type of an API element) also show up in
/// the output.
final _potentiallyDanglingReferences = Queue<Element>();
final _uniqueNamer = UniqueNamer();
/// Cache of values returned by [_getOrComputeImmediateSubinterfaceMap], to
/// avoid unnecessary recomputation.
final _immediateSubinterfaceCache =
<LibraryElement, Map<ClassElement, Set<InterfaceElement>>>{};
ApiDescription(this._pkgName, this._customizer);
/// Builds a list of [Node] objects representing all the libraries that are
/// relevant to the package's public API.
///
/// This includes libraries that are in the package's public API as well as
/// libraries that are referenced by the package's public API (either by being
/// re-exported as part of the package's public API, or by being used as part
/// of the type of something in the public API).
///
/// Each library node is pared with a [UriSortKey] indicating the order in
/// which the nodes should be output.
Future<List<(UriSortKey, Node)>> build(AnalysisContext context) async {
_customizer.packageName = _pkgName;
_customizer.analysisContext = context;
await _customizer.setupComplete();
// First, find all the libraries comprising the package's public API, and
// all the top level elements they export.
var publicApiLibraries = <LibraryElement>[];
var topLevelPublicElements = <Element>{};
for (var file in context.contextRoot.analyzedFiles().sorted()) {
if (!file.endsWith('.dart')) continue;
var fileResult = context.currentSession.getFile(file) as FileResult;
var uri = fileResult.uri;
if (fileResult.isLibrary && uri.isInPublicLibOf(_pkgName)) {
var resolvedLibraryResult =
(await context.currentSession.getResolvedLibrary(file))
as ResolvedLibraryResult;
var library = resolvedLibraryResult.element;
topLevelPublicElements.addAll(
library.exportNamespace.definedNames2.values,
);
publicApiLibraries.add(library);
}
}
_customizer.publicApiLibraries = publicApiLibraries;
_customizer.topLevelPublicElements = topLevelPublicElements;
await _customizer.initialScanComplete();
// Then, dump all the libraries in the package's public API.
var nodes = <Uri, Node<MemberSortKey>>{};
for (var library in publicApiLibraries) {
var node = nodes[library.uri] = Node<MemberSortKey>();
_dumpLibrary(library, node);
}
// Finally, dump anything referenced by those public libraries.
while (_potentiallyDanglingReferences.isNotEmpty) {
var element = _potentiallyDanglingReferences.removeFirst();
if (!_dumpedTopLevelElements.add(element)) continue;
var containingLibraryUri = element.library!.uri;
var childNode = Node<MemberSortKey>()
..text.add(_uniqueNamer.name(element));
_dumpElement(element, childNode);
(nodes[containingLibraryUri] ??= Node<MemberSortKey>()
..text.add('$containingLibraryUri:'))
.childNodes
.add((MemberSortKey(element), childNode));
}
return [
for (var entry in nodes.entries)
(UriSortKey(entry.key, _pkgName), entry.value),
];
}
/// Creates a list of objects which, when their string representations are
/// concatenated, describes [type].
///
/// The reason we use this method rather than [DartType.toString] is to make
/// sure that (a) every element mentioned by the type is added to
/// [_potentiallyDanglingReferences], and (b) if an ambiguous name is used,
/// the ambiguity will be taken care of by [_uniqueNamer].
List<Object?> _describeType(DartType type) {
var suffix = switch (type.nullabilitySuffix) {
NullabilitySuffix.none => '',
NullabilitySuffix.star => '*',
NullabilitySuffix.question => '?',
};
switch (type) {
case DynamicType():
return ['dynamic'];
case FunctionType(
:var returnType,
:var typeParameters,
:var formalParameters,
):
var params = <List<Object?>>[];
var optionalParams = <List<Object?>>[];
var namedParams = <String, List<Object?>>{};
for (var formalParameter in formalParameters) {
if (formalParameter.isNamed) {
namedParams[formalParameter.name!] = [
if (formalParameter.isDeprecated) 'deprecated ',
if (formalParameter.isRequired) 'required ',
..._describeType(formalParameter.type),
];
} else if (formalParameter.isOptional) {
optionalParams.add([
if (formalParameter.isDeprecated) 'deprecated ',
..._describeType(formalParameter.type),
]);
} else {
params.add([
if (formalParameter.isDeprecated) 'deprecated ',
..._describeType(formalParameter.type),
]);
}
}
if (optionalParams.isNotEmpty) {
params.add(optionalParams.separatedBy(prefix: '[', suffix: ']'));
}
if (namedParams.isNotEmpty) {
params.add(
namedParams.entries
.sortedBy((e) => e.key)
.map((e) => [...e.value, ' ${e.key}'])
.separatedBy(prefix: '{', suffix: '}'),
);
}
return <Object?>[
..._describeType(returnType),
' Function',
if (typeParameters.isNotEmpty)
...typeParameters
.map(_describeTypeParameter)
.separatedBy(prefix: '<', suffix: '>'),
'(',
...params.separatedBy(),
')',
suffix,
];
case InterfaceType(:var element, :var typeArguments):
_potentiallyDanglingReferences.addLast(element);
return [
_uniqueNamer.name(element),
if (typeArguments.isNotEmpty)
...typeArguments
.map(_describeType)
.separatedBy(prefix: '<', suffix: '>'),
suffix,
];
case RecordType(:var positionalFields, :var namedFields):
if (positionalFields.length == 1 && namedFields.isEmpty) {
return [
'(',
..._describeType(positionalFields[0].type),
',)',
suffix,
];
}
return [
...[
for (var positionalField in positionalFields)
_describeType(positionalField.type),
if (namedFields.isNotEmpty)
namedFields
.sortedBy((f) => f.name)
.map((f) => [..._describeType(f.type), ' ', f.name])
.separatedBy(prefix: '{', suffix: '}'),
].separatedBy(prefix: '(', suffix: ')'),
suffix,
];
case TypeParameterType(:var element):
return [element.name!, suffix];
case VoidType():
return ['void'];
case dynamic(:var runtimeType):
throw UnimplementedError('Unexpected type: $runtimeType');
}
}
/// Creates a list of objects which, when their string representations are
/// concatenated, describes [typeParameter].
List<Object?> _describeTypeParameter(TypeParameterElement typeParameter) {
return [
typeParameter.name!,
if (typeParameter.bound case var bound?) ...[
' extends ',
..._describeType(bound),
],
];
}
/// Appends information to [node] describing [element].
void _dumpElement(Element element, Node<MemberSortKey> node) {
var enclosingElement = element.enclosingElement;
if (enclosingElement is LibraryElement &&
!_customizer.shouldShowDetails(element)) {
if (!enclosingElement.uri.isIn(_pkgName)) {
node.text.add(' (referenced)');
} else {
node.text.add(' (non-public)');
}
return;
}
var parentheticals = <List<Object?>>[];
switch (element) {
case TypeAliasElement(:var aliasedType, :var typeParameters):
var description = <Object?>['type alias'];
if (typeParameters.isNotEmpty) {
description.addAll(
typeParameters
.map(_describeTypeParameter)
.separatedBy(prefix: '<', suffix: '>'),
);
}
description.addAll([' for ', ..._describeType(aliasedType)]);
parentheticals.add(description);
case InstanceElement():
switch (element) {
case InterfaceElement(
:var typeParameters,
:var supertype,
:var mixins,
:var interfaces,
):
var instanceDescription = <Object?>[
switch (element) {
ClassElement() => 'class',
EnumElement() => 'enum',
MixinElement() => 'mixin',
ExtensionTypeElement() => 'extension type',
dynamic(:var runtimeType) => 'TODO: $runtimeType',
},
];
if (typeParameters.isNotEmpty) {
instanceDescription.addAll(
typeParameters
.map(_describeTypeParameter)
.separatedBy(prefix: '<', suffix: '>'),
);
}
if (element is! EnumElement && supertype != null) {
instanceDescription.addAll([
' extends ',
..._describeType(supertype),
]);
}
if (mixins.isNotEmpty) {
instanceDescription.addAll(
mixins.map(_describeType).separatedBy(prefix: ' with '),
);
}
if (element is MixinElement &&
element.superclassConstraints.isNotEmpty) {
instanceDescription.addAll(
element.superclassConstraints
.map(_describeType)
.separatedBy(prefix: ' on '),
);
}
if (interfaces.isNotEmpty) {
instanceDescription.addAll(
interfaces
.map(_describeType)
.separatedBy(prefix: ' implements '),
);
}
parentheticals.add(instanceDescription);
if (element is ClassElement) {
if (element.isSealed) {
var parenthetical = <Object>['sealed'];
parentheticals.add(parenthetical);
if (_getOrComputeImmediateSubinterfaceMap(
element.library,
)[element]
case var subinterfaces?) {
parenthetical.add(' (immediate subtypes: ');
// Note: it's tempting to just do
// `subinterfaces.map(_uniqueNamer.name).join(', ')`, but that
// won't work, because the names returned by
// `UniqueName.toString()` aren't finalized until we've
// visited the entire API and seen if there are class names
// that need to be disambiguated. So we accumulate the
// `UniqueName` objects into the `parenthetical` list and rely
// on `printNodes` converting everything to a string when the
// final API description is being output.
var commaNeeded = false;
for (var subinterface in subinterfaces) {
if (commaNeeded) {
parenthetical.add(', ');
} else {
commaNeeded = true;
}
parenthetical.add(_uniqueNamer.name(subinterface));
}
parenthetical.add(')');
}
} else {
if (element.isAbstract) {
parentheticals.add(['abstract']);
}
if (element.isBase) {
parentheticals.add(['base']);
}
if (element.isMixinClass) {
parentheticals.add(['mixin']);
}
if (element.isInterface) {
parentheticals.add(['interface']);
}
if (element.isFinal) {
parentheticals.add(['final']);
}
}
} else if (element is MixinElement) {
if (element.isBase) {
parentheticals.add(['base']);
}
}
case ExtensionElement(:var extendedType):
parentheticals.add([
'extension on ',
..._describeType(extendedType),
]);
case dynamic(:var runtimeType):
throw UnimplementedError('Unexpected element: $runtimeType');
}
for (var member in element.children.sortedBy((m) => m.name ?? '')) {
if (member.name case var name? when name.startsWith('_')) {
// Ignore private members
continue;
}
if (member is FieldElement) {
// Ignore fields; we care about the getters and setters they induce.
continue;
}
if (member is ConstructorElement &&
element is ClassElement &&
element.isAbstract &&
(element.isFinal || element.isInterface || element.isSealed)) {
// The class can't be constructed from outside of the library that
// declares it, so its constructors aren't part of the public API.
continue;
}
if (member is ConstructorElement && element is EnumElement) {
// Enum constructors can't be called from outside the enum itself,
// so they aren't part of the public API.
continue;
}
var childNode = Node<MemberSortKey>();
childNode.text.add(member.apiName);
_dumpElement(member, childNode);
node.childNodes.add((MemberSortKey(member), childNode));
}
case TopLevelFunctionElement(:var type):
parentheticals.add(['function: ', ..._describeType(type)]);
case ExecutableElement(:var isStatic):
var maybeStatic = isStatic ? 'static ' : '';
switch (element) {
case GetterElement(:var type):
parentheticals.add([
'${maybeStatic}getter: ',
..._describeType(type.returnType),
]);
case SetterElement(:var type):
parentheticals.add([
'${maybeStatic}setter: ',
..._describeType(type.formalParameters.single.type),
]);
case MethodElement(:var type):
parentheticals.add([
'${maybeStatic}method: ',
..._describeType(type),
]);
case ConstructorElement(:var type):
parentheticals.add(['constructor: ', ..._describeType(type)]);
case dynamic(:var runtimeType):
throw UnimplementedError('Unexpected element: $runtimeType');
}
case dynamic(:var runtimeType):
throw UnimplementedError('Unexpected element: $runtimeType');
}
// For synthetic elements such as getters/setters induced by top level
// variables and fields, annotations can be found on the corresponding
// non-synthetic element.
var nonSyntheticElement = element.nonSynthetic;
if (nonSyntheticElement.metadata.hasDeprecated) {
parentheticals.add(['deprecated']);
}
if (nonSyntheticElement.metadata.hasExperimental) {
parentheticals.add(['experimental']);
}
if (parentheticals.isNotEmpty) {
node.text.addAll(parentheticals.separatedBy(prefix: ' (', suffix: ')'));
}
if (node.childNodes.isNotEmpty) {
node.text.add(':');
}
}
/// Appends information to [node] describing [library].
void _dumpLibrary(LibraryElement library, Node<MemberSortKey> node) {
var uri = library.uri;
node.text.addAll([uri, ':']);
var definedNames = library.exportNamespace.definedNames2;
for (var key in definedNames.keys.sorted()) {
var element = definedNames[key]!;
var childNode = Node<MemberSortKey>()
..text.add(_uniqueNamer.name(element));
if (!_dumpedTopLevelElements.add(element)) {
childNode.text.add(' (see above)');
} else {
_dumpElement(element, childNode);
}
node.childNodes.add((MemberSortKey(element), childNode));
}
}
/// Returns a map from each sealed class in [library] to the set of its
/// immediate sub-interfaces.
///
/// If this method has been called before with the same [library], a cached
/// map is returned from [_immediateSubinterfaceCache]. Otherwise a fresh map
/// is computed.
Map<ClassElement, Set<InterfaceElement>>
_getOrComputeImmediateSubinterfaceMap(LibraryElement library) {
if (_immediateSubinterfaceCache[library] case var m?) return m;
var result = <ClassElement, Set<InterfaceElement>>{};
for (var interface in [
...library.classes,
...library.mixins,
...library.enums,
...library.extensionTypes,
]..sortBy((e) => e.name!)) {
for (var superinterface in [
interface.supertype,
...interface.interfaces,
...interface.mixins,
if (interface is MixinElement) ...interface.superclassConstraints,
]) {
if (superinterface == null) continue;
var superinterfaceElement = superinterface.element;
if (superinterfaceElement is ClassElement &&
superinterfaceElement.isSealed) {
(result[superinterfaceElement] ??= {}).add(interface);
}
}
}
_immediateSubinterfaceCache[library] = result;
return result;
}
}
@@ -1,53 +0,0 @@
// 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.
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/element/element.dart';
/// Clients of the API summary tool may extend this class to customize its
/// behavior.
///
/// Clients should not *implement* this class, however, because additional
/// methods may be added in the future.
base class ApiSummaryCustomizer {
/// The top level elements exported by the libraries in [publicApiLibraries].
///
/// This value is set by the tool before [initialScanComplete] is called.
late final Set<Element> topLevelPublicElements;
/// The analysis context for the package being summarized.
///
/// This value is set by the tool before [setupComplete] is called.
set analysisContext(AnalysisContext analysisContext) {}
/// The name of the package whose API is being summarized.
///
/// This value is set by the tool before [setupComplete] is called.
set packageName(String value) {}
/// The libraries that comprise the package's public API.
///
/// This value is set by the tool before [initialScanComplete] is called.
set publicApiLibraries(Iterable<LibraryElement> value) {}
/// Called after [publicApiLibraries] and [topLevelPublicElements] have been
/// set, but before any analysis has been performed.
///
/// Further analysis won't be performed until the returned Future completes.
Future<void> initialScanComplete() async {}
/// Called after [packageName] and [analysisContext] have been set, but before
/// any analysis has been performed.
///
/// The initial scan won't be performed until the returned Future completes.
Future<void> setupComplete() async {}
/// Called after [initialScanComplete] to determine if details about an
/// element should be shown in the API summary.
///
/// The default behavior is to show details about elements in
/// [topLevelPublicElements].
bool shouldShowDetails(Element element) =>
topLevelPublicElements.contains(element);
}
-69
View File
@@ -1,69 +0,0 @@
// 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.
import 'package:analyzer/dart/element/element.dart';
extension ElementExtension on Element {
/// Returns the appropriate name for describing the element in `api.txt`.
///
/// The name is the same as [name], but with `=` appended for setters.
String get apiName {
var apiName = name!;
if (this is SetterElement) {
apiName += '=';
}
return apiName;
}
}
extension FormalParameterElementExtension on FormalParameterElement {
bool get isDeprecated {
// TODO(paulberry): add this to the analyzer public API
return metadata.hasDeprecated;
}
}
extension IterableIterableExtension on Iterable<Iterable<Object?>> {
/// Forms a list containing [prefix], followed by the elements of `this`
/// (separated by [separator]), followed by [suffix].
///
/// Each element of `this` is also an iterable; these elements are added to
/// the resulting list using `.addAll`, so one level of iterable nesting is
/// removed.
List<Object?> separatedBy({
String separator = ', ',
String prefix = '',
String suffix = '',
}) {
var result = <Object?>[prefix];
var first = true;
for (var item in this) {
if (first) {
first = false;
} else {
result.add(separator);
}
result.addAll(item);
}
result.add(suffix);
return result;
}
}
extension StringExtension on String {
bool get isPublic => !startsWith('_');
}
extension UriExtension on Uri {
bool isIn(String packageName) =>
scheme == 'package' &&
pathSegments.isNotEmpty &&
pathSegments[0] == packageName;
bool isInPublicLibOf(String packageName) =>
scheme == 'package' &&
pathSegments.length > 1 &&
pathSegments[0] == packageName &&
pathSegments[1] != 'src';
}
@@ -1,67 +0,0 @@
// 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.
import 'package:analyzer/dart/element/element.dart';
/// Element categorization used by [MemberSortKey].
enum MemberCategory {
constructor,
propertyAccessor,
topLevelFunctionOrMethod,
interface,
extension,
typeAlias,
}
/// Sort key used to sort elements in the output.
class MemberSortKey implements Comparable<MemberSortKey> {
final bool _isInstanceMember;
final MemberCategory _category;
final String _name;
final bool _isSetter;
MemberSortKey(Element element)
: _isInstanceMember = _computeIsInstanceMember(element),
_category = _computeCategory(element),
_name = element.displayName,
_isSetter = element is SetterElement;
@override
int compareTo(MemberSortKey other) {
if ((_isInstanceMember ? 1 : 0).compareTo(other._isInstanceMember ? 1 : 0)
case var value when value != 0) {
return value;
}
if (_category.index.compareTo(other._category.index) case var value
when value != 0) {
return value;
}
if (_name.compareTo(other._name) case var value when value != 0) {
return value;
}
return (_isSetter ? 1 : 0).compareTo(other._isSetter ? 1 : 0);
}
static MemberCategory _computeCategory(Element element) => switch (element) {
ConstructorElement() => MemberCategory.constructor,
PropertyAccessorElement() => MemberCategory.propertyAccessor,
TopLevelFunctionElement() => MemberCategory.topLevelFunctionOrMethod,
MethodElement() => MemberCategory.topLevelFunctionOrMethod,
InterfaceElement() => MemberCategory.interface,
ExtensionElement() => MemberCategory.extension,
TypeAliasElement() => MemberCategory.typeAlias,
dynamic(:var runtimeType) => throw UnimplementedError(
'Unexpected element: $runtimeType',
),
};
static bool _computeIsInstanceMember(Element element) =>
element.enclosingElement is InstanceElement &&
switch (element) {
ExecutableElement(:var isStatic) => !isStatic,
dynamic(:var runtimeType) => throw UnimplementedError(
'Unexpected element: $runtimeType',
),
};
}
-43
View File
@@ -1,43 +0,0 @@
// 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.
/// @docImport 'unique_namer.dart';
library;
import 'package:collection/collection.dart';
/// Outputs the contents of [nodes] to [sink], prepending [prefix] to every
/// line.
void printNodes<SortKey extends Comparable<SortKey>>(
StringSink sink,
List<(SortKey, Node)> nodes, {
String prefix = '',
}) {
for (var entry in nodes.sortedBy((n) => n.$1)) {
var node = entry.$2;
sink.writeln('$prefix${node.text.join()}');
node.printChildren(sink, prefix: '$prefix ');
}
}
/// A node to be printed to the output.
class Node<ChildSortKey extends Comparable<ChildSortKey>> {
/// A list of objects which, when their string representations are
/// concatenated, is the text that should be displayed on the first line of
/// the node.
///
/// The reason this is a list rather than a single string is to allow elements
/// of the list to be [UniqueName] objects, which may acquire a disambiguation
/// suffix at a later time.
final text = <Object?>[];
/// A list of child nodes, paired with a sort key indicating the order in
/// which they should be output.
final childNodes = <(ChildSortKey, Node)>[];
/// Outputs [childNodes], prepending [prefix] to every line.
void printChildren(StringSink sink, {required String prefix}) {
printNodes(sink, childNodes, prefix: prefix);
}
}
-53
View File
@@ -1,53 +0,0 @@
// 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.
import 'package:analyzer/dart/element/element.dart';
import 'extensions.dart';
/// Object that will have a unique string representation within the context of a
/// given [UniqueNamer] instance.
///
/// If two or more [UniqueName] objects are constructed with reference to the
/// same [UniqueNamer], and they have the same [_nameHint], then all such
/// objects' [toString] methods will append a unique suffix of the form
/// `@INTEGER`, so that the resulting strings are unique.
class UniqueName {
/// The name that will be returned by [toString] if no disambiguation is
/// needed.
final String _nameHint;
/// If not `Null`, the integer that [toString] will use to disambiguate this
/// [UniqueName] from other ones with the same [_nameHint].
int? _disambiguator;
UniqueName(UniqueNamer uniqueNamer, this._nameHint)
// The uniqueness guarantee depends on `_nameHint` not containing an `@`.
: assert(!_nameHint.contains('@')) {
var conflicts = uniqueNamer._conflicts[_nameHint] ??= [];
if (conflicts.length == 1) {
conflicts[0]._disambiguator = 1;
}
conflicts.add(this);
if (conflicts.length > 1) {
_disambiguator = conflicts.length;
}
}
@override
String toString() => [
_nameHint,
if (_disambiguator case var disambiguator?) '@$disambiguator',
].join();
}
/// Manager of unique names for elements.
class UniqueNamer {
final _names = <Element, UniqueName>{};
final _conflicts = <String, List<UniqueName>>{};
/// Returns a [UniqueName] object whose [toString] method will produce a
/// unique name for [element].
UniqueName name(Element element) =>
_names[element] ??= UniqueName(this, element.apiName);
}
-32
View File
@@ -1,32 +0,0 @@
// 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.
import 'extensions.dart';
/// URI categorization used by [UriSortKey].
enum UriCategory { inPackage, notInPackage }
/// Sort key used to sort libraries in the output.
///
/// Libraries in the specified package will be output first (sorted by URI),
/// followed by libraries not in the package.
class UriSortKey implements Comparable<UriSortKey> {
final UriCategory _category;
final String _uriString;
UriSortKey(Uri uri, String pkgName)
: _category = uri.isIn(pkgName)
? UriCategory.inPackage
: UriCategory.notInPackage,
_uriString = uri.toString();
@override
int compareTo(UriSortKey other) {
if (_category.index.compareTo(other._category.index) case var value
when value != 0) {
return value;
}
return _uriString.compareTo(other._uriString);
}
}
-24
View File
@@ -1,24 +0,0 @@
name: api_summary
version: 0.1.0-wip
description: Creates an API summary for a package.
publish_to: none
environment:
sdk: '^3.12.0-0'
resolution: workspace
# Use 'any' constraints here; we get our versions from the DEPS file.
dependencies:
analyzer: any
args: any
collection: any
path: any
yaml: any
# Use 'any' constraints here; we get our versions from the DEPS file.
dev_dependencies:
analyzer_testing: any
dart_flutter_team_lints: any
test: any
test_reflective_loader: any
@@ -1,793 +0,0 @@
// 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);
}
}
-62
View File
@@ -1,62 +0,0 @@
// 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.
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
void main() {
test(
'api_summary output matches api_summary.txt',
timeout: const Timeout.factor(3),
() async {
final packageDir = _pkgDir();
final result = await Process.run(Platform.resolvedExecutable, [
if (Platform.packageConfig != null)
'--packages=${Platform.packageConfig}',
p.join(packageDir, 'bin', 'api_summary.dart'),
'-p',
packageDir,
], workingDirectory: packageDir);
expect(
result.exitCode,
equals(0),
reason: 'CLI run failed with stderr:\n${result.stderr}',
);
final goldenFile = File(p.join(packageDir, 'api_summary.txt'));
final expectedOutput = LineSplitter.split(
goldenFile.readAsStringSync(),
).join('\n');
final actualOutput = LineSplitter.split(
result.stdout.toString(),
).join('\n');
expect(actualOutput, equals(expectedOutput));
},
);
}
// Dynamically locate the api_summary package root
String _pkgDir() {
var packageDir = p.normalize(p.absolute(Directory.current.path));
if (!_isApiSummaryDir(packageDir)) {
// We might be running from the SDK root
final candidate = p.join(packageDir, 'pkg', 'api_summary');
if (_isApiSummaryDir(candidate)) {
packageDir = candidate;
}
}
return packageDir;
}
bool _isApiSummaryDir(String dir) {
final pubspec = File(p.join(dir, 'pubspec.yaml'));
if (!pubspec.existsSync()) return false;
return pubspec.readAsStringSync().contains('name: api_summary');
}
-128
View File
@@ -1,128 +0,0 @@
// 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 'package:api_summary/src/extensions.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'test_utils.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(ExtensionsTest);
});
}
@reflectiveTest
class ExtensionsTest extends ApiSummaryTest {
Future<void> test_element_apiName_classMember() async {
var class_ = (await analyzeLibrary('''
class C {
void method() {}
int get getter => 0;
set setter(int value) {}
int field = 0;
static const constant = 0;
}
''')).getClass('C')!;
expect(class_.getMethod('method')!.apiName, 'method');
expect(class_.getGetter('getter')!.apiName, 'getter');
expect(class_.getSetter('setter')!.apiName, 'setter=');
expect(class_.getField('field')!.apiName, 'field');
expect(class_.getField('constant')!.apiName, 'constant');
}
Future<void> test_element_apiName_topLevel() async {
var lib = await analyzeLibrary('''
void function() {}
int get getter => 0;
set setter(int value) {}
int variable = 0;
const constant = 0;
class Class {}
mixin Mixin {}
enum Enum { e }
extension Extension on int {}
extension type ExtensionType(int i) {}
''');
expect(lib.getTopLevelFunction('function')!.apiName, 'function');
expect(lib.getGetter('getter')!.apiName, 'getter');
expect(lib.getSetter('setter')!.apiName, 'setter=');
expect(lib.getTopLevelVariable('variable')!.apiName, 'variable');
expect(lib.getTopLevelVariable('constant')!.apiName, 'constant');
expect(lib.getClass('Class')!.apiName, 'Class');
expect(lib.getMixin('Mixin')!.apiName, 'Mixin');
expect(lib.getEnum('Enum')!.apiName, 'Enum');
expect(lib.getExtension('Extension')!.apiName, 'Extension');
expect(lib.getExtensionType('ExtensionType')!.apiName, 'ExtensionType');
}
Future<void> test_formalParameterElement_isDeprecated() async {
var f = (await analyzeLibrary(
'f({int? i, @deprecated int? j}) {}',
)).getTopLevelFunction('f')!;
expect(f.formalParameters[0].isDeprecated, isFalse);
expect(f.formalParameters[1].isDeprecated, isTrue);
}
void test_iterableIterable_separatedBy() {
expect(
[
['a', 'b'],
['c', 'd'],
].separatedBy(),
['', 'a', 'b', ', ', 'c', 'd', ''],
);
expect(
[
['a', 'b'],
['c', 'd'],
].separatedBy(prefix: '[', separator: '|', suffix: ']'),
['[', 'a', 'b', '|', 'c', 'd', ']'],
);
expect(
<Iterable<Object?>>[].separatedBy(
prefix: '[',
separator: '|',
suffix: ']',
),
['[', ']'],
);
}
void test_string_isPublic() {
expect('_'.isPublic, isFalse);
expect('foo'.isPublic, isTrue);
expect('_foo'.isPublic, isFalse);
}
void test_uri_isIn() {
expect(Uri.parse('package:foo/bar.dart').isIn('foo'), isTrue);
expect(Uri.parse('package:foo/bar.dart').isIn('bar.dart'), isFalse);
expect(Uri.parse('dart:core').isIn('foo'), isFalse);
expect(Uri.parse('dart:core').isIn('dart'), isFalse);
expect(Uri.parse('dart:core').isIn('core'), isFalse);
}
void test_uri_isInPublicLibOf() {
expect(Uri.parse('package:foo/bar.dart').isInPublicLibOf('foo'), isTrue);
expect(
Uri.parse('package:foo/bar.dart').isInPublicLibOf('bar.dart'),
isFalse,
);
expect(
Uri.parse('package:foo/src/bar.dart').isInPublicLibOf('foo'),
isFalse,
);
expect(
Uri.parse('package:foo/src/bar.dart').isInPublicLibOf('src'),
isFalse,
);
expect(Uri.parse('dart:core').isInPublicLibOf('foo'), isFalse);
expect(Uri.parse('dart:core').isInPublicLibOf('dart'), isFalse);
expect(Uri.parse('dart:core').isInPublicLibOf('core'), isFalse);
}
}
@@ -1,207 +0,0 @@
// 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 'package:analyzer/dart/element/element.dart';
import 'package:api_summary/src/extensions.dart';
import 'package:api_summary/src/member_sorting.dart';
import 'package:collection/collection.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'test_utils.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(MemberTest);
});
}
@reflectiveTest
class MemberTest extends ApiSummaryTest {
Future<void> test_sortOrder_member_categoryBeforeName() async {
_checkSorting(
elements: (await analyzeLibrary('''
class C {
C.a1();
get a2 => 0;
a3() {}
C.z1();
get z2 => 0;
z3() {}
}
''')).getClass('C')!.childrenExcludingPropertyInducingElements,
expectedOrder: ['a1', 'z1', 'a2', 'z2', 'a3', 'z3'],
);
}
Future<void> test_sortOrder_member_gettersAndSettersTogether() async {
// Note that it's not good enough for the implementation to sort by apiName,
// because `9` is ASCII 0x39 and `=` is ASCII 0x3d, so sorting by apiName
// would put `a9` and `a9=` between `a` and `a=`.
_checkSorting(
elements: (await analyzeLibrary('''
class C {
get a => 0;
get a9 => 0;
get aA => 0;
set a(value) {}
set a9(value) {}
set aA(value) {}
}
''')).getClass('C')!.childrenExcludingPropertyInducingElements,
expectedOrder: ['new', 'a', 'a=', 'a9', 'a9=', 'aA', 'aA='],
);
}
Future<void> test_sortOrder_member_staticBeforeInstance() async {
_checkSorting(
elements: (await analyzeLibrary('''
class C {
C.a1();
C.z1();
get a2 => 0;
get z2 => 0;
static get a3 => 0;
static get z3 => 0;
a4() {}
z4() {}
static a5() {}
static z5() {}
}
''')).getClass('C')!.childrenExcludingPropertyInducingElements,
expectedOrder: [
'a3',
'z3',
'a5',
'z5',
'a1',
'z1',
'a2',
'z2',
'a4',
'z4',
],
);
}
Future<void> test_sortOrder_member_unnamedConstructorBeforeOthers() async {
// Note that it's not good enough for the implementation to sort by apiName,
// because that would put `new` after `A`.
_checkSorting(
elements: (await analyzeLibrary('''
class C {
C();
C.A();
C.z();
}
''')).getClass('C')!.childrenExcludingPropertyInducingElements,
expectedOrder: ['new', 'A', 'z'],
);
}
Future<void> test_sortOrder_topLevel_categoryBeforeName() async {
_checkSorting(
elements: (await analyzeLibrary('''
get a1 => 0;
a2() {}
class a3 {}
extension a4 on int {}
typedef a5 = int;
get z1 => 0;
z2() {}
class z3 {}
extension z4 on int {}
typedef z5 = int;
''')).childrenExcludingPropertyInducingElements,
expectedOrder: [
'a1',
'z1',
'a2',
'z2',
'a3',
'z3',
'a4',
'z4',
'a5',
'z5',
],
);
}
Future<void> test_sortOrder_topLevel_gettersAndSettersTogether() async {
// Note that it's not good enough for the implementation to sort by apiName,
// because `9` is ASCII 0x39 and `=` is ASCII 0x3d, so sorting by apiName
// would put `a9` and `a9=` between `a` and `a=`.
_checkSorting(
elements: (await analyzeLibrary('''
get a => 0;
get a9 => 0;
get aA => 0;
set a(value) {}
set a9(value) {}
set aA(value) {}
''')).childrenExcludingPropertyInducingElements,
expectedOrder: ['a', 'a=', 'a9', 'a9=', 'aA', 'aA='],
);
}
Future<void> test_sortOrder_topLevel_interfaceTypesTogether() async {
_checkSorting(
elements: (await analyzeLibrary('''
class A1 {}
class Z1 {}
mixin A2 {}
mixin Z2 {}
enum A3 { v }
enum Z3 { v }
extension type A4(int i) {}
extension type Z4(int i) {}
''')).childrenExcludingPropertyInducingElements,
expectedOrder: ['A1', 'A2', 'A3', 'A4', 'Z1', 'Z2', 'Z3', 'Z4'],
);
}
Future<void> test_sortOrder_topLevel_oldAndNewTypedefsTogether() async {
_checkSorting(
elements: (await analyzeLibrary('''
typedef void A1();
typedef void Z1();
typedef A2 = void Function();
typedef Z2 = void Function();
typedef A3 = int;
typedef Z3 = int;
''')).childrenExcludingPropertyInducingElements,
expectedOrder: ['A1', 'A2', 'A3', 'Z1', 'Z2', 'Z3'],
);
}
void _checkSorting({
required List<Element> elements,
required List<String> expectedOrder,
}) {
expect(
elements.sortedBy(MemberSortKey.new).map((e) => e.apiName).toList(),
expectedOrder,
);
expect(
elements.reversed
.sortedBy(MemberSortKey.new)
.map((e) => e.apiName)
.toList(),
expectedOrder,
);
}
}
extension on Element {
/// All children of `this` excluding [PropertyInducingElement]s.
///
/// This is used for testing the sort order of class members, since the API
/// summary only considers getters and setters; it ignores the fields and top
/// level variables that induce them.
List<Element> get childrenExcludingPropertyInducingElements =>
children.whereNot((e) => e is PropertyInducingElement).toList();
}
-97
View File
@@ -1,97 +0,0 @@
// 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 'package:api_summary/src/node.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(NodeTest);
});
}
@reflectiveTest
class NodeTest {
void test_printNodes_indentChildNodes() {
var buf = StringBuffer();
printNodes(buf, [
(
1,
_SimpleNode('one', [
(2, _SimpleNode('two')),
(3, _SimpleNode('three')),
]),
),
(
4,
_SimpleNode('four', [
(5, _SimpleNode('five')),
(6, _SimpleNode('six')),
]),
),
]);
expect(buf.toString(), '''
one
two
three
four
five
six
''');
}
void test_printNodes_joinTextStrings() {
var buf = StringBuffer();
printNodes(buf, [
(1, Node<num>()..text.addAll(['x', 0])),
]);
expect(buf.toString(), '''
x0
''');
}
void test_printNodes_sortChildNodesByKey() {
var buf = StringBuffer();
printNodes(buf, [
(
0,
_SimpleNode('zero', [
(2, _SimpleNode('two')),
(1, _SimpleNode('one')),
(3, _SimpleNode('three')),
]),
),
]);
expect(buf.toString(), '''
zero
one
two
three
''');
}
void test_printNodes_sortedByKey() {
var buf = StringBuffer();
printNodes(buf, [
(2, _SimpleNode('two')),
(1, _SimpleNode('one')),
(3, _SimpleNode('three')),
]);
expect(buf.toString(), '''
one
two
three
''');
}
}
class _SimpleNode extends Node<num> {
_SimpleNode(String text, [List<(num, Node<num>)> childNodes = const []]) {
this.text.add(text);
this.childNodes.addAll(childNodes);
}
}
-26
View File
@@ -1,26 +0,0 @@
// 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.
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
// TODO(paulberry): move pub_package_resolution.dart out of src
import 'package:analyzer_testing/src/analysis_rule/pub_package_resolution.dart';
import 'package:test/test.dart';
class ApiSummaryTest extends PubPackageResolutionTest {
Future<LibraryElement> analyzeLibrary(
String content, {
String pathWithinLib = 'test.dart',
}) async {
var file = newFile('$testPackageLibPath/$pathWithinLib', content);
var resolvedUnitResult = await resolveFile(file.path);
expect(
resolvedUnitResult.diagnostics.where(
(diagnostic) => diagnostic.severity == Severity.error,
),
isEmpty,
);
return resolvedUnitResult.libraryElement;
}
}
@@ -1,82 +0,0 @@
// 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 'package:api_summary/src/unique_namer.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'test_utils.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(UniqueNamerTest);
});
}
@reflectiveTest
class UniqueNamerTest extends ApiSummaryTest {
Future<void> test_collidingNamesAreDisambiguated() async {
var f1 = (await analyzeLibrary(
pathWithinLib: 'file1.dart',
'f() {}',
)).getTopLevelFunction('f')!;
var f2 = (await analyzeLibrary(
pathWithinLib: 'file2.dart',
'f() {}',
)).getTopLevelFunction('f')!;
var uniqueNamer = UniqueNamer();
var f1Name = uniqueNamer.name(f1);
var f2Name = uniqueNamer.name(f2);
expect(f1Name.toString(), 'f@1');
expect(f2Name.toString(), 'f@2');
}
Future<void> test_name_returnsSameNameOnSuccessiveCalls() async {
var f = (await analyzeLibrary('f() {}')).getTopLevelFunction('f')!;
var uniqueNamer = UniqueNamer();
var name1 = uniqueNamer.name(f);
var name2 = uniqueNamer.name(f);
expect(name1, same(name2));
}
Future<void> test_nonCollidingNamesAreNotDisambiguated() async {
var f = (await analyzeLibrary(
pathWithinLib: 'file1.dart',
'f() {}',
)).getTopLevelFunction('f')!;
var g = (await analyzeLibrary(
pathWithinLib: 'file2.dart',
'g() {}',
)).getTopLevelFunction('g')!;
var uniqueNamer = UniqueNamer();
var fName = uniqueNamer.name(f);
var gName = uniqueNamer.name(g);
expect(fName.toString(), 'f');
expect(gName.toString(), 'g');
}
Future<void> test_three_collisions() async {
var f1 = (await analyzeLibrary(
pathWithinLib: 'file1.dart',
'f() {}',
)).getTopLevelFunction('f')!;
var f2 = (await analyzeLibrary(
pathWithinLib: 'file2.dart',
'f() {}',
)).getTopLevelFunction('f')!;
var f3 = (await analyzeLibrary(
pathWithinLib: 'file3.dart',
'f() {}',
)).getTopLevelFunction('f')!;
var uniqueNamer = UniqueNamer();
var f1Name = uniqueNamer.name(f1);
var f2Name = uniqueNamer.name(f2);
var f3Name = uniqueNamer.name(f3);
expect(f1Name.toString(), 'f@1');
expect(f2Name.toString(), 'f@2');
expect(f3Name.toString(), 'f@3');
}
}
@@ -1,56 +0,0 @@
// 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 'package:api_summary/src/uri_sorting.dart';
import 'package:collection/collection.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(UriTest);
});
}
@reflectiveTest
class UriTest {
void test_sortOrder_inOrOutOfPackageBeforeName() {
_checkSorting(
uris: [
Uri.parse('package:a/a.dart'),
Uri.parse('package:b/b.dart'),
Uri.parse('package:c/c.dart'),
],
expectedOrder: [
'package:b/b.dart',
'package:a/a.dart',
'package:c/c.dart',
],
packageName: 'b',
);
}
void _checkSorting({
required List<Uri> uris,
required List<String> expectedOrder,
required String packageName,
}) {
expect(
uris
.sortedBy((e) => UriSortKey(e, packageName))
.map((e) => e.toString())
.toList(),
expectedOrder,
);
expect(
uris.reversed
.sortedBy((e) => UriSortKey(e, packageName))
.map((e) => e.toString())
.toList(),
expectedOrder,
);
}
}
+2 -1
View File
@@ -19,7 +19,6 @@ workspace:
- pkg/analysis_server_client
- pkg/analysis_server_plugin
- pkg/analyzer
- pkg/api_summary
- pkg/analyzer_cli
- pkg/analyzer_plugin
- pkg/analyzer_testing
@@ -106,6 +105,8 @@ workspace:
# All third_party packages are retrieved via the DEPS-file and overridden here.
dependency_overrides:
api_summary:
path: third_party/pkg/tools/pkgs/api_summary
args:
path: third_party/pkg/core/pkgs/args
async:
+1 -8
View File
@@ -2970,7 +2970,7 @@
"name": "unit tests",
"arguments": [
"-nanalyzer-unittest-asserts-${mode}-${system}",
"pkg/(analyzer|analysis_server|analysis_server_plugin|analyzer_cli|analyzer_plugin|api_summary)"
"pkg/(analyzer|analysis_server|analysis_server_plugin|analyzer_cli|analyzer_plugin)"
],
"shards": 9,
"fileset": "analyzer_unit_tests"
@@ -3053,13 +3053,6 @@
"pkg/analyzer_plugin"
]
},
{
"name": "api_summary unit tests",
"arguments": [
"-nanalyzer-unittest-asserts-${mode}-${system}",
"pkg/api_summary"
]
},
{
"name": "benchmark analysis server",
"script": "out/ReleaseX64/dart-sdk/bin/dart",