blob: 0ac7d52d6c49b37cabaeb5d3ce3fe5fb088cfb95 [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.
import 'package:analyzer/src/util/yaml.dart';
import 'package:yaml/yaml.dart';
/// Parses [optionsMap] into a [LintConfig], returning the config, or `null` if
/// [optionsMap] does not have `linter` map.
LintConfig? parseConfig(YamlMap optionsMap) {
var options = optionsMap.valueAt('linter');
// Quick check of basic contract.
if (options is YamlMap) {
return LintConfig.parseMap(options);
}
return null;
}
/// Process the given option [fileContents] and produce a corresponding
/// [LintConfig]. Return `null` if [fileContents] is not a YAML map, or
/// does not have the `linter` child map.
LintConfig? processAnalysisOptionsFile(String fileContents, {String? fileUrl}) {
var yaml = loadYamlNode(fileContents,
sourceUrl: fileUrl != null ? Uri.parse(fileUrl) : null);
if (yaml is YamlMap) {
return parseConfig(yaml);
}
return null;
}
/// The configuration of lint rules within an analysis options file.
class LintConfig {
final List<String> fileIncludes;
final List<String> fileExcludes;
final List<RuleConfig> ruleConfigs;
LintConfig(this.fileIncludes, this.fileExcludes, this.ruleConfigs);
factory LintConfig.parse(String source, {String? sourceUrl}) {
var yaml = loadYamlNode(source,
sourceUrl: sourceUrl != null ? Uri.parse(sourceUrl) : null);
if (yaml is! YamlMap) {
throw StateError("Expected YAML at '$source' to be a Map, but is "
'${yaml.runtimeType}.');
}
return LintConfig.parseMap(yaml);
}
factory LintConfig.parseMap(YamlMap yaml) {
var fileIncludes = <String>[];
var fileExcludes = <String>[];
var ruleConfigs = <RuleConfig>[];
yaml.nodes.forEach((key, value) {
if (key is! YamlScalar) {
return;
}
switch (key.toString()) {
case 'files':
if (value is YamlMap) {
_addAsListOrString(value['include'], fileIncludes);
_addAsListOrString(value['exclude'], fileExcludes);
}
case 'rules':
ruleConfigs.addAll(_ruleConfigs(value));
}
});
return LintConfig(fileIncludes, fileExcludes, ruleConfigs);
}
static void _addAsListOrString(Object? value, List<String> list) {
if (value is List) {
for (var entry in value) {
list.add(entry as String);
}
} else if (value is String) {
list.add(value);
}
}
static bool? _asBool(YamlNode scalar) {
var value = scalar is YamlScalar ? scalar.valueOrThrow : scalar;
return switch (value) {
bool() => value,
'true' => true,
'false' => false,
_ => null,
};
}
static String? _asString(Object scalar) {
var value = scalar is YamlScalar ? scalar.value : scalar;
return value is String ? value : null;
}
static Map<String, bool> _parseArgs(YamlNode args) {
var enabled = _asBool(args);
if (enabled != null) {
return {'enabled': enabled};
}
return {};
}
static List<RuleConfig> _ruleConfigs(YamlNode value) {
// For example:
//
// ```yaml
// - unnecessary_getters
// - camel_case_types
// ```
if (value is YamlList) {
return [
for (var rule in value.nodes)
RuleConfig(name: _asString(rule), args: {'enabled': true}),
];
}
// style_guide: {unnecessary_getters: false, camel_case_types: true}
if (value is YamlMap) {
var ruleConfigs = <RuleConfig>[];
value.nodes.cast<Object, YamlNode>().forEach((key, value) {
// For example: `{unnecessary_getters: false}`.
var valueAsBool = _asBool(value);
if (valueAsBool != null) {
ruleConfigs.add(RuleConfig(
name: _asString(key),
args: {'enabled': valueAsBool},
));
}
// style_guide: {unnecessary_getters: false, camel_case_types: true}
if (value is YamlMap) {
value.nodes.cast<Object, YamlNode>().forEach((rule, args) {
// TODO(brianwilkerson): verify format.
// For example: `unnecessary_getters: false`.
ruleConfigs.add(RuleConfig(
name: _asString(rule),
args: _parseArgs(args),
group: _asString(key),
));
});
}
});
return ruleConfigs;
}
return const [];
}
}
/// The configuration of a single lint rule within an analysis options file.
class RuleConfig {
final Map<String, bool> args;
final String? group;
final String? name;
RuleConfig({required this.name, required this.args, this.group});
bool disables(String ruleName) =>
ruleName == name && args['enabled'] == false;
bool enables(String ruleName) => ruleName == name && args['enabled'] == true;
}