blob: 2137f1195c89aa807315177509eb08b9b770252c [file] [log] [blame]
// Copyright (c) 2015, 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.
library analyzer.src.plugin.plugin_configuration;
import 'package:analyzer/plugin/options.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:yaml/yaml.dart';
const _analyzerOptionScope = 'analyzer';
const _pluginOptionScope = 'plugins';
/// Parse the given string into a plugin manifest.
PluginManifest parsePluginManifestString(String manifestSource) {
var yaml = loadYaml(manifestSource);
if (yaml == null) {
return null;
}
_verifyMap(yaml, 'plugin manifest');
Iterable<String> pluginHost = _parseHosts(yaml['contributes_to']);
PluginInfo plugin = _parsePlugin(yaml);
return new PluginManifest(contributesTo: pluginHost, plugin: plugin);
}
String _asString(dynamic yaml) {
if (yaml != null && yaml is! String) {
throw new PluginConfigFormatException(
'Unable to parse pugin manifest, '
'expected `String`, got `${yaml.runtimeType}`',
yaml);
}
return yaml;
}
Iterable<String> _parseHosts(dynamic yaml) {
List<String> hosts = <String>[];
if (yaml is String) {
hosts.add(yaml);
} else if (yaml is YamlList) {
yaml.forEach((h) => hosts.add(_asString(h)));
}
return hosts;
}
PluginInfo _parsePlugin(dynamic yaml) {
if (yaml != null) {
_verifyMap(yaml, 'plugin manifest');
return new PluginInfo._fromYaml(details: yaml);
}
return null;
}
PluginInfo _processPluginMapping(dynamic name, dynamic details) {
if (name is String) {
if (details is String) {
return new PluginInfo(name: name, version: details);
}
if (details is YamlMap) {
return new PluginInfo._fromYaml(name: name, details: details);
}
}
return null;
}
_verifyMap(dynamic yaml, String context) {
if (yaml is! YamlMap) {
throw new PluginConfigFormatException(
'Unable to parse $context, '
'expected `YamlMap`, got `${yaml.runtimeType}`',
yaml);
}
}
/// A callback for error handling.
typedef ErrorHandler(Exception e);
/// Describes plugin configuration information as extracted from an
/// analysis options map or plugin manifest.
class PluginConfig {
final Iterable<PluginInfo> plugins;
PluginConfig(this.plugins);
/// Create a plugin configuration from an options map.
factory PluginConfig.fromOptions(Map<String, Object> options) {
List<PluginInfo> plugins = [];
var analyzerOptions = options[_analyzerOptionScope];
if (analyzerOptions != null) {
//TODO(pq): handle "raw" maps (https://github.com/dart-lang/sdk/issues/25126)
if (analyzerOptions is YamlMap) {
var pluginConfig = analyzerOptions[_pluginOptionScope];
if (pluginConfig is YamlMap) {
pluginConfig.forEach((name, details) {
var plugin = _processPluginMapping(name, details);
if (plugin != null) {
plugins.add(plugin);
}
});
} else {
// Anything but an empty list of plugins is treated as a format error.
if (pluginConfig != null) {
throw new PluginConfigFormatException(
'Unrecognized plugin config format, expected `YamlMap`, '
'got `${pluginConfig.runtimeType}`',
pluginConfig);
}
}
}
}
return new PluginConfig(plugins);
}
}
/// Thrown on bad plugin config format.
class PluginConfigFormatException implements Exception {
/// Descriptive message.
final message;
/// The `plugin:` yaml node for generating detailed error feedback.
final yamlNode;
PluginConfigFormatException(this.message, this.yamlNode);
}
/// Extracts plugin config details from analysis options.
class PluginConfigOptionsProcessor extends OptionsProcessor {
final ErrorHandler _errorHandler;
PluginConfig _config;
PluginConfigOptionsProcessor([this._errorHandler]);
/// The processed plugin config.
PluginConfig get config => _config;
@override
void onError(Exception exception) {
if (_errorHandler != null) {
_errorHandler(exception);
}
}
@override
void optionsProcessed(AnalysisContext context, Map<String, Object> options) {
_config = new PluginConfig.fromOptions(options);
}
}
/// Describes plugin information.
class PluginInfo {
final String name;
final String className;
final String version;
final String libraryUri;
final String packageName;
final String path;
PluginInfo(
{this.name,
this.version,
this.className,
this.libraryUri,
this.packageName,
this.path});
factory PluginInfo._fromYaml({String name, YamlMap details}) =>
new PluginInfo(
name: name,
version: _asString(details['version']),
className: _asString(details['class_name']),
libraryUri: _asString(details['library_uri']),
packageName: _asString(details['package_name']),
path: _asString(details['path']));
}
/// Plugin manifests accompany plugin packages, providing
/// configuration information for published plugins.
///
/// Provisionally, plugin manifests live in a file `plugin.yaml`
/// at the root of the plugin package.
///
/// my_plugin/
/// bin/
/// lib/
/// plugin.yaml
/// pubspec.yaml
///
/// Provisional manifest file format:
///
/// class_name: MyAnalyzerPlugin
/// library_uri: 'my_plugin/my_analyzer_plugin.dart'
/// contributes_to: analyzer
class PluginManifest {
PluginInfo plugin;
Iterable<String> contributesTo;
PluginManifest({this.plugin, this.contributesTo});
}