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:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
e61679f449
commit
45c27304eb
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.1.0-wip
|
||||
|
||||
- First release.
|
||||
@@ -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.
|
||||
@@ -1,3 +0,0 @@
|
||||
file:/tools/OWNERS_DEVELOPER_EXPERIENCE
|
||||
file:/tools/OWNERS_MODEL
|
||||
kevmoo@google.com
|
||||
@@ -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.
|
||||
@@ -1,5 +0,0 @@
|
||||
include: package:dart_flutter_team_lints/analysis_options.yaml
|
||||
|
||||
linter:
|
||||
rules:
|
||||
- unnecessary_ignore
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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',
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user