api_summary: add basic CLI and validation test
Change-Id: I3e9b5a4c3f1d31967a503c4caf273b9d8f239095 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/502840 Auto-Submit: Kevin Moore <kevmoo@google.com> Commit-Queue: Kevin Moore <kevmoo@google.com> Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Paul Berry <paulberry@google.com>
This commit is contained in:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
06bfb29baa
commit
58e1cbe1a7
@@ -0,0 +1,25 @@
|
||||
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)
|
||||
@@ -0,0 +1,81 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -11,7 +11,10 @@ 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:
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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', () 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');
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
analyzer/test/verify_diagnostics_test: Slow, Pass
|
||||
analyzer/test/verify_docs_test: Slow, Pass
|
||||
analyzer_plugin/test/plugin/folding_mixin_test: Slow, Pass
|
||||
api_summary/test/app_test: Slow, Pass
|
||||
compiler/test/analyses/analyze_test: Slow, Pass
|
||||
compiler/test/analyses/api_dynamic_test: Slow, Pass
|
||||
compiler/test/closure/closure_test: Slow, Pass
|
||||
|
||||
Reference in New Issue
Block a user