pkg:api_summary

- Moved existing summary logic to the new package

Change-Id: I47d032f5a253cfa32d9b685d9ff18bb08f534177
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499440
Commit-Queue: Vijay Menon <vsm@google.com>
Auto-Submit: Kevin Moore <kevmoo@google.com>
Reviewed-by: Vijay Menon <vsm@google.com>
This commit is contained in:
kevmoo
2026-05-01 11:29:56 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 6f45aa5329
commit a2055fbb82
27 changed files with 114 additions and 41 deletions
+1
View File
@@ -33,6 +33,7 @@ dev_dependencies:
analysis_server_client: any
analyzer_testing: any
analyzer_utilities: any
api_summary: any
args: any
async: any
checks: any
+1 -1
View File
@@ -9,9 +9,9 @@ import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer_testing/package_root.dart' as pkg_root;
import 'package:analyzer_utilities/src/api_summary/src/api_summary_customizer.dart';
import 'package:analyzer_utilities/tool/api.dart';
import 'package:analyzer_utilities/tools.dart';
import 'package:api_summary/api_summary.dart';
Future<void> main() async {
await GeneratedContent.generateAll(pkg_root.packageRoot, allTargets);
+1 -1
View File
@@ -5,7 +5,7 @@
/// Generates the file `api.txt`, which describes a package's public API.
library;
import 'package:analyzer_utilities/src/api_summary/summarize_package.dart';
import 'package:api_summary/api_summary.dart';
import 'package:analyzer_utilities/tools.dart';
import 'package:path/path.dart';
+1 -1
View File
@@ -11,6 +11,7 @@ resolution: workspace
dependencies:
analyzer: any
analyzer_testing: any
api_summary: any
collection: any
package_config: any
path: any
@@ -23,4 +24,3 @@ dependencies:
dev_dependencies:
analyzer_plugin: any
lints: any
test_reflective_loader: any
+3
View File
@@ -0,0 +1,3 @@
## 0.1.0-wip
- First release.
+27
View File
@@ -0,0 +1,27 @@
Copyright 2026, the Dart project authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+3
View File
@@ -0,0 +1,3 @@
file:/tools/OWNERS_DEVELOPER_EXPERIENCE
file:/tools/OWNERS_MODEL
kevmoo@google.com
+1
View File
@@ -0,0 +1 @@
A tool to create a human-readable text summary of the public API of a Dart package. This is useful for auditing API changes with a `diff` tool.
+5
View File
@@ -0,0 +1,5 @@
include: package:dart_flutter_team_lints/analysis_options.yaml
linter:
rules:
- unnecessary_ignore
@@ -4,12 +4,11 @@
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer_utilities/src/api_summary/src/api_description.dart';
import 'package:analyzer_utilities/src/api_summary/src/api_summary_customizer.dart';
import 'package:analyzer_utilities/src/api_summary/src/node.dart';
import 'src/api_description.dart';
import 'src/api_summary_customizer.dart';
import 'src/node.dart';
export 'package:analyzer_utilities/src/api_summary/src/api_summary_customizer.dart'
show ApiSummaryCustomizer;
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.
@@ -9,14 +9,15 @@ 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:analyzer_utilities/src/api_summary/src/api_summary_customizer.dart';
import 'package:analyzer_utilities/src/api_summary/src/extensions.dart';
import 'package:analyzer_utilities/src/api_summary/src/member_sorting.dart';
import 'package:analyzer_utilities/src/api_summary/src/node.dart';
import 'package:analyzer_utilities/src/api_summary/src/unique_namer.dart';
import 'package:analyzer_utilities/src/api_summary/src/uri_sorting.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 {
@@ -244,7 +245,7 @@ class ApiDescription {
var parentheticals = <List<Object?>>[];
switch (element) {
case TypeAliasElement(:var aliasedType, :var typeParameters):
List<Object?> description = ['type alias'];
var description = <Object?>['type alias'];
if (typeParameters.isNotEmpty) {
description.addAll(
typeParameters
@@ -261,7 +262,7 @@ class ApiDescription {
:var supertype,
:var interfaces,
):
List<Object?> instanceDescription = [
var instanceDescription = <Object?>[
switch (element) {
ClassElement() => 'class',
EnumElement() => 'enum',
@@ -388,7 +389,7 @@ class ApiDescription {
case TopLevelFunctionElement(:var type):
parentheticals.add(['function: ', ..._describeType(type)]);
case ExecutableElement(:var isStatic):
String maybeStatic = isStatic ? 'static ' : '';
var maybeStatic = isStatic ? 'static ' : '';
switch (element) {
case GetterElement(:var type):
parentheticals.add([
@@ -433,7 +434,7 @@ class ApiDescription {
}
}
/// Appends information to [node] describing [element].
/// Appends information to [node] describing [library].
void _dumpLibrary(LibraryElement library, Node<MemberSortKey> node) {
var uri = library.uri;
node.text.addAll([uri, ':']);
@@ -46,7 +46,8 @@ base class ApiSummaryCustomizer {
/// 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].
/// The default behavior is to show details about elements in
/// [topLevelPublicElements].
bool shouldShowDetails(Element element) =>
topLevelPublicElements.contains(element);
}
@@ -2,6 +2,9 @@
// 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
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer_utilities/src/api_summary/src/extensions.dart';
import 'extensions.dart';
/// Object that will have a unique string representation within the context of a
/// given [UniqueNamer] instance.
@@ -21,9 +21,9 @@ class UniqueName {
/// [UniqueName] from other ones with the same [_nameHint].
int? _disambiguator;
UniqueName(UniqueNamer uniqueNamer, this._nameHint) {
UniqueName(UniqueNamer uniqueNamer, this._nameHint)
// The uniqueness guarantee depends on `_nameHint` not containing an `@`.
assert(!_nameHint.contains('@'));
: assert(!_nameHint.contains('@')) {
var conflicts = uniqueNamer._conflicts[_nameHint] ??= [];
if (conflicts.length == 1) {
conflicts[0]._disambiguator = 1;
@@ -2,7 +2,7 @@
// 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_utilities/src/api_summary/src/extensions.dart';
import 'extensions.dart';
/// URI categorization used by [UriSortKey].
enum UriCategory { inPackage, notInPackage }
+21
View File
@@ -0,0 +1,21 @@
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
collection: 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
@@ -8,13 +8,13 @@ import 'dart:core';
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer_utilities/src/api_summary/src/api_description.dart';
import 'package:analyzer_utilities/src/api_summary/src/api_summary_customizer.dart';
import 'package:analyzer_utilities/src/api_summary/src/node.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 '../utilities.dart';
import 'test_utils.dart';
void main() {
defineReflectiveSuite(() {
@@ -141,8 +141,8 @@ dart:core:
}
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.
// 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';
@@ -4,11 +4,11 @@
// ignore_for_file: non_constant_identifier_names
import 'package:analyzer_utilities/src/api_summary/src/extensions.dart';
import 'package:api_summary/src/extensions.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../utilities.dart';
import 'test_utils.dart';
void main() {
defineReflectiveSuite(() {
@@ -5,13 +5,13 @@
// ignore_for_file: non_constant_identifier_names
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer_utilities/src/api_summary/src/extensions.dart';
import 'package:analyzer_utilities/src/api_summary/src/member_sorting.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 '../utilities.dart';
import 'test_utils.dart';
void main() {
defineReflectiveSuite(() {
@@ -183,12 +183,12 @@ typedef Z3 = int;
required List<String> expectedOrder,
}) {
expect(
elements.sortedBy((e) => MemberSortKey(e)).map((e) => e.apiName).toList(),
elements.sortedBy(MemberSortKey.new).map((e) => e.apiName).toList(),
expectedOrder,
);
expect(
elements.reversed
.sortedBy((e) => MemberSortKey(e))
.sortedBy(MemberSortKey.new)
.map((e) => e.apiName)
.toList(),
expectedOrder,
@@ -4,7 +4,7 @@
// ignore_for_file: non_constant_identifier_names
import 'package:analyzer_utilities/src/api_summary/src/node.dart';
import 'package:api_summary/src/node.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -4,11 +4,11 @@
// ignore_for_file: non_constant_identifier_names
import 'package:analyzer_utilities/src/api_summary/src/unique_namer.dart';
import 'package:api_summary/src/unique_namer.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../utilities.dart';
import 'test_utils.dart';
void main() {
defineReflectiveSuite(() {
@@ -4,7 +4,7 @@
// ignore_for_file: non_constant_identifier_names
import 'package:analyzer_utilities/src/api_summary/src/uri_sorting.dart';
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';
+1
View File
@@ -19,6 +19,7 @@ workspace:
- pkg/analysis_server_client
- pkg/analysis_server_plugin
- pkg/analyzer
- pkg/api_summary
- pkg/analyzer_cli
- pkg/analyzer_plugin
- pkg/analyzer_testing
+8 -1
View File
@@ -2932,7 +2932,7 @@
"name": "unit tests",
"arguments": [
"-nanalyzer-unittest-asserts-${mode}-${system}",
"pkg/(analyzer|analysis_server|analysis_server_plugin|analyzer_cli|analyzer_plugin)"
"pkg/(analyzer|analysis_server|analysis_server_plugin|analyzer_cli|analyzer_plugin|api_summary)"
],
"shards": 9,
"fileset": "analyzer_unit_tests"
@@ -3015,6 +3015,13 @@
"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",