blob: f35ea0c96b6bb6836cb968e51c8680dc9fe07dec [file] [log] [blame]
// Copyright (c) 2023, 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:extension_discovery/extension_discovery.dart';
import 'package:path/path.dart' as path;
class VsCodeExtensionsManager {
/// Reads metadata about the VS Code extensions for the packages used by the
/// project at [rootPath].
Future<VsCodeExtensionResults> findVsCodeExtensions(String rootPath) async {
const targetName = 'vs_code';
final packageConfig =
Uri.file(path.join(rootPath, '.dart_tool', 'package_config.json'));
final results = VsCodeExtensionResults();
try {
final extensions = await findExtensions(
targetName,
packageConfig: packageConfig,
);
for (final extension in extensions) {
try {
results.extensions.add(
VsCodeExtensionConfig.parse(extension.package, extension.config),
);
} on Error catch (e) {
results.parseErrors.add(VsCodeExtensionParseError(
packageName: extension.package, error: e.toString()));
}
}
} on PackageConfigException {
// If the package_config doesn't exist or is invalid, we'll just return
// the empty results.
}
return results;
}
}
class VsCodeExtensionResults {
final extensions = <VsCodeExtensionConfig>[];
final parseErrors = <VsCodeExtensionParseError>[];
Map<String, Object?> toJson() => {
'extensions': extensions,
'parseErrors': parseErrors,
};
}
class VsCodeExtensionParseError {
VsCodeExtensionParseError({required this.packageName, required this.error});
final String packageName;
final String error;
Map<String, Object?> toJson() => {
'packageName': packageName,
'error': error,
};
}
class VsCodeExtensionConfig {
VsCodeExtensionConfig._({
required this.packageName,
required this.vsCodeExtensionId,
});
factory VsCodeExtensionConfig.parse(
String packageName,
Map<String, Object?> json,
) {
if (json
case {
vsCodeExtensionIdKey: final String vsCodeExtensionId,
}) {
return VsCodeExtensionConfig._(
packageName: packageName,
vsCodeExtensionId: vsCodeExtensionId,
);
} else {
const requiredKeysFromConfigFile = <String>{
vsCodeExtensionIdKey,
};
final missing = requiredKeysFromConfigFile.difference(json.keys.toSet());
if (missing.isNotEmpty) {
throw StateError(
'Missing required fields $missing in the extension '
'config.yaml.',
);
} else {
// All the required keys are present, but the value types did not match.
final sb = StringBuffer();
for (final entry in json.entries) {
sb.writeln(
' ${entry.key}: ${entry.value} (${entry.value.runtimeType})',
);
}
throw StateError(
'Unexpected value types in the extension config.yaml. Expected all '
'values to be of type String, but one or more had a different type:\n'
'$sb',
);
}
}
}
Map<String, Object?> toJson() => {
'packageName': packageName,
VsCodeExtensionConfig.vsCodeExtensionIdKey: vsCodeExtensionId,
};
/// The YAML key for the ID of the VS Code extension.
///
/// This is 'extension' in the YAML because users have to type it and it's
/// already in a VS-Code-specific file, but in code we use a more descriptive
/// name.
static const vsCodeExtensionIdKey = 'extension';
/// The name of the package promoting the extension.
final String packageName;
/// The ID of the VS Code extension being promoted.
///
/// The format should be 'publisher.extension' matching what's shown inside
/// VS Code and on the market place.
final String vsCodeExtensionId;
}