|  | // 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; | 
|  | } |