blob: 344d7f2621f8c5a574eabc5d1b7c82aeacb1eb5e [file] [edit]
// 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:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:path/path.dart' as p;
import 'package:yaml/yaml.dart';
import 'src/api_builder.dart';
import 'src/api_declaration.dart';
import 'src/api_summary_customizer.dart';
export 'src/api_declaration.dart';
export 'src/api_facet.dart';
export 'src/api_summary_customizer.dart'
show ApiSummaryContext, ApiSummaryCustomizer;
export 'src/api_type.dart';
export 'src/js_facet.dart';
export 'src/meta_facet.dart';
/// Creates a canonical [ApiSummary] model of the public API of a package.
///
/// [packagePath] is the path to the directory containing the package's
/// `pubspec.yaml` file.
///
/// [packageName] is the name of the package, or extracted from `pubspec.yaml`
/// if omitted.
///
/// If [customizer] is provided, it will be used to customize the behavior of
/// the tool.
Future<ApiSummary> apiSummary(
String packagePath, {
String? packageName,
ApiSummaryCustomizer? customizer,
}) async {
final pubspec = _extractPubspecDetails(packagePath);
final resolvedPackageName = packageName ?? pubspec.name;
final provider = PhysicalResourceProvider.INSTANCE;
final libPath = provider.pathContext.join(packagePath, 'lib');
if (!provider.getFolder(libPath).exists) {
throw ArgumentError('No "lib" directory found for "$packagePath".');
}
final collection = AnalysisContextCollection(
resourceProvider: provider,
includedPaths: [libPath],
);
final context = collection.contextFor(libPath);
return buildApiPackage(
resolvedPackageName,
context,
customizer ?? const ApiSummaryCustomizer(),
environment: pubspec.environment,
executables: pubspec.executables,
);
}
typedef _PubspecDetails = ({
String name,
Map<String, String> environment,
Map<String, String?> executables,
});
_PubspecDetails _extractPubspecDetails(String packagePath) {
final pubspecFile = File(p.join(packagePath, 'pubspec.yaml'));
if (!pubspecFile.existsSync()) {
throw ArgumentError('No pubspec.yaml found at "$packagePath".');
}
final content = pubspecFile.readAsStringSync();
final YamlMap yaml;
try {
yaml = switch (loadYaml(content, sourceUrl: pubspecFile.uri)) {
final YamlMap map => map,
_ => throw FormatException('Expected pubspec to be a YAML map.', content),
};
} on FormatException catch (e) {
throw FormatException(
'Failed to parse pubspec.yaml at ${pubspecFile.path}: ${e.message}',
content,
e.offset,
);
}
final name = switch (yaml['name']) {
final String name => name,
_ => throw FormatException(
'Failed to parse pubspec.yaml at ${pubspecFile.path}: '
'Expected pubspec to contain a "name" string.',
content,
),
};
final environment = switch (yaml['environment']) {
final Map<dynamic, dynamic> envMap => {
for (final MapEntry(:key, :value) in envMap.entries)
if (key is String && value != null)
key: switch (value) {
final String s => s,
final num n => n.toString(),
final bool b => b.toString(),
_ => throw FormatException(
'Failed to parse pubspec.yaml at ${pubspecFile.path}: '
'Expected environment constraint for "$key" to be a string or '
'scalar.',
content,
),
},
},
null => const <String, String>{},
_ => throw FormatException(
'Failed to parse pubspec.yaml at ${pubspecFile.path}: '
'Expected "environment" to be a YAML map.',
content,
),
};
final executables = switch (yaml['executables']) {
final Map<dynamic, dynamic> execMap => {
for (final MapEntry(:key, :value) in execMap.entries)
if (key is String)
key: switch (value) {
final String? s => s,
_ => throw FormatException(
'Failed to parse pubspec.yaml at ${pubspecFile.path}: '
'Expected executable target for "$key" to be a string or null.',
content,
),
},
},
null => const <String, String?>{},
_ => throw FormatException(
'Failed to parse pubspec.yaml at ${pubspecFile.path}: '
'Expected "executables" to be a YAML map.',
content,
),
};
return (name: name, environment: environment, executables: executables);
}