| Index: packages/analyzer/lib/source/analysis_options_provider.dart
|
| diff --git a/packages/analyzer/lib/source/analysis_options_provider.dart b/packages/analyzer/lib/source/analysis_options_provider.dart
|
| index aeac0531ecd0900f04baa9823592b8be49697002..cdd912cc3d5c41680c0ae3e3165b8fd8236a2619 100644
|
| --- a/packages/analyzer/lib/source/analysis_options_provider.dart
|
| +++ b/packages/analyzer/lib/source/analysis_options_provider.dart
|
| @@ -2,20 +2,36 @@
|
| // 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 source.analysis_options_provider;
|
| +library analyzer.source.analysis_options_provider;
|
| +
|
| +import 'dart:core';
|
|
|
| import 'package:analyzer/file_system/file_system.dart';
|
| import 'package:analyzer/src/generated/engine.dart';
|
| +import 'package:analyzer/src/util/yaml.dart';
|
| +import 'package:source_span/source_span.dart';
|
| import 'package:yaml/yaml.dart';
|
|
|
| -/// Provide the options found in the `.analysis_options` file.
|
| +/// Provide the options found in the analysis options file.
|
| class AnalysisOptionsProvider {
|
| - /// Provide the options found in [root]/[ANALYSIS_OPTIONS_FILE].
|
| + /// Provide the options found in either
|
| + /// [root]/[AnalysisEngine.ANALYSIS_OPTIONS_FILE] or
|
| + /// [root]/[AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE].
|
| /// Return an empty options map if the file does not exist.
|
| - Map<String, YamlNode> getOptions(Folder root) {
|
| - var optionsSource = _readAnalysisOptionsFile(
|
| - root.getChild(AnalysisEngine.ANALYSIS_OPTIONS_FILE));
|
| - return getOptionsFromString(optionsSource);
|
| + Map<String, YamlNode> getOptions(Folder root, {bool crawlUp: false}) {
|
| + Resource resource;
|
| + for (Folder folder = root; folder != null; folder = folder.parent) {
|
| + resource = folder.getChild(AnalysisEngine.ANALYSIS_OPTIONS_FILE);
|
| + if (resource.exists) {
|
| + break;
|
| + }
|
| + resource = folder.getChild(AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
|
| + if (resource.exists || !crawlUp) {
|
| + break;
|
| + }
|
| + }
|
| + String optionsText = _readAnalysisOptionsFile(resource);
|
| + return getOptionsFromString(optionsText);
|
| }
|
|
|
| /// Provide the options found in [file].
|
| @@ -28,30 +44,72 @@ class AnalysisOptionsProvider {
|
| /// Provide the options found in [optionsSource].
|
| /// Return an empty options map if the source is null.
|
| Map<String, YamlNode> getOptionsFromString(String optionsSource) {
|
| - var options = <String, YamlNode>{};
|
| + Map<String, YamlNode> options = <String, YamlNode>{};
|
| if (optionsSource == null) {
|
| return options;
|
| }
|
| - var doc = loadYaml(optionsSource);
|
| +
|
| + YamlNode safelyLoadYamlNode() {
|
| + try {
|
| + return loadYamlNode(optionsSource);
|
| + } on YamlException catch (e) {
|
| + throw new OptionsFormatException(e.message, e.span);
|
| + } catch (e) {
|
| + throw new OptionsFormatException('Unable to parse YAML document.');
|
| + }
|
| + }
|
| +
|
| + YamlNode doc = safelyLoadYamlNode();
|
| +
|
| + // Empty options.
|
| + if (doc is YamlScalar && doc.value == null) {
|
| + return options;
|
| + }
|
| if ((doc != null) && (doc is! YamlMap)) {
|
| - throw new Exception(
|
| - 'Bad options file format (expected map, got ${doc.runtimeType})\n'
|
| - 'contents of options file:\n'
|
| - '$optionsSource\n');
|
| + throw new OptionsFormatException(
|
| + 'Bad options file format (expected map, got ${doc.runtimeType})',
|
| + doc.span);
|
| }
|
| if (doc is YamlMap) {
|
| - doc.forEach((k, v) {
|
| - if (k is! String) {
|
| - throw new Exception(
|
| + doc.nodes.forEach((k, YamlNode v) {
|
| + var key;
|
| + if (k is YamlScalar) {
|
| + key = k.value;
|
| + }
|
| + if (key is! String) {
|
| + throw new OptionsFormatException(
|
| 'Bad options file format (expected String scope key, '
|
| - 'got ${k.runtimeType})');
|
| + 'got ${k.runtimeType})',
|
| + (k ?? doc).span);
|
| }
|
| - options[k] = v;
|
| + if (v != null && v is! YamlNode) {
|
| + throw new OptionsFormatException(
|
| + 'Bad options file format (expected Node value, '
|
| + 'got ${v.runtimeType}: `${v.toString()}`)',
|
| + doc.span);
|
| + }
|
| + options[key] = v;
|
| });
|
| }
|
| return options;
|
| }
|
|
|
| + /// Merge the given options contents where the values in [defaults] may be
|
| + /// overridden by [overrides].
|
| + ///
|
| + /// Some notes about merge semantics:
|
| + ///
|
| + /// * lists are merged (without duplicates).
|
| + /// * lists of scalar values can be promoted to simple maps when merged with
|
| + /// maps of strings to booleans (e.g., ['opt1', 'opt2'] becomes
|
| + /// {'opt1': true, 'opt2': true}.
|
| + /// * maps are merged recursively.
|
| + /// * if map values cannot be merged, the overriding value is taken.
|
| + ///
|
| + Map<String, YamlNode> merge(
|
| + Map<String, YamlNode> defaults, Map<String, YamlNode> overrides) =>
|
| + new Merger().merge(defaults, overrides) as Map<String, YamlNode>;
|
| +
|
| /// Read the contents of [file] as a string.
|
| /// Returns null if file does not exist.
|
| String _readAnalysisOptionsFile(File file) {
|
| @@ -63,3 +121,14 @@ class AnalysisOptionsProvider {
|
| }
|
| }
|
| }
|
| +
|
| +/// Thrown on options format exceptions.
|
| +class OptionsFormatException implements Exception {
|
| + final String message;
|
| + final SourceSpan span;
|
| + OptionsFormatException(this.message, [this.span]);
|
| +
|
| + @override
|
| + String toString() =>
|
| + 'OptionsFormatException: ${message?.toString()}, ${span?.toString()}';
|
| +}
|
|
|