| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library source.analysis_options_provider; | 5 library analyzer.source.analysis_options_provider; |
| 6 |
| 7 import 'dart:core'; |
| 6 | 8 |
| 7 import 'package:analyzer/file_system/file_system.dart'; | 9 import 'package:analyzer/file_system/file_system.dart'; |
| 8 import 'package:analyzer/src/generated/engine.dart'; | 10 import 'package:analyzer/src/generated/engine.dart'; |
| 11 import 'package:analyzer/src/util/yaml.dart'; |
| 12 import 'package:source_span/source_span.dart'; |
| 9 import 'package:yaml/yaml.dart'; | 13 import 'package:yaml/yaml.dart'; |
| 10 | 14 |
| 11 /// Provide the options found in the `.analysis_options` file. | 15 /// Provide the options found in the analysis options file. |
| 12 class AnalysisOptionsProvider { | 16 class AnalysisOptionsProvider { |
| 13 /// Provide the options found in [root]/[ANALYSIS_OPTIONS_FILE]. | 17 /// Provide the options found in either |
| 18 /// [root]/[AnalysisEngine.ANALYSIS_OPTIONS_FILE] or |
| 19 /// [root]/[AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE]. |
| 14 /// Return an empty options map if the file does not exist. | 20 /// Return an empty options map if the file does not exist. |
| 15 Map<String, YamlNode> getOptions(Folder root) { | 21 Map<String, YamlNode> getOptions(Folder root, {bool crawlUp: false}) { |
| 16 var optionsSource = _readAnalysisOptionsFile( | 22 Resource resource; |
| 17 root.getChild(AnalysisEngine.ANALYSIS_OPTIONS_FILE)); | 23 for (Folder folder = root; folder != null; folder = folder.parent) { |
| 18 return getOptionsFromString(optionsSource); | 24 resource = folder.getChild(AnalysisEngine.ANALYSIS_OPTIONS_FILE); |
| 25 if (resource.exists) { |
| 26 break; |
| 27 } |
| 28 resource = folder.getChild(AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE); |
| 29 if (resource.exists || !crawlUp) { |
| 30 break; |
| 31 } |
| 32 } |
| 33 String optionsText = _readAnalysisOptionsFile(resource); |
| 34 return getOptionsFromString(optionsText); |
| 19 } | 35 } |
| 20 | 36 |
| 21 /// Provide the options found in [file]. | 37 /// Provide the options found in [file]. |
| 22 /// Return an empty options map if the file does not exist. | 38 /// Return an empty options map if the file does not exist. |
| 23 Map<String, YamlNode> getOptionsFromFile(File file) { | 39 Map<String, YamlNode> getOptionsFromFile(File file) { |
| 24 var optionsSource = _readAnalysisOptionsFile(file); | 40 var optionsSource = _readAnalysisOptionsFile(file); |
| 25 return getOptionsFromString(optionsSource); | 41 return getOptionsFromString(optionsSource); |
| 26 } | 42 } |
| 27 | 43 |
| 28 /// Provide the options found in [optionsSource]. | 44 /// Provide the options found in [optionsSource]. |
| 29 /// Return an empty options map if the source is null. | 45 /// Return an empty options map if the source is null. |
| 30 Map<String, YamlNode> getOptionsFromString(String optionsSource) { | 46 Map<String, YamlNode> getOptionsFromString(String optionsSource) { |
| 31 var options = <String, YamlNode>{}; | 47 Map<String, YamlNode> options = <String, YamlNode>{}; |
| 32 if (optionsSource == null) { | 48 if (optionsSource == null) { |
| 33 return options; | 49 return options; |
| 34 } | 50 } |
| 35 var doc = loadYaml(optionsSource); | 51 |
| 52 YamlNode safelyLoadYamlNode() { |
| 53 try { |
| 54 return loadYamlNode(optionsSource); |
| 55 } on YamlException catch (e) { |
| 56 throw new OptionsFormatException(e.message, e.span); |
| 57 } catch (e) { |
| 58 throw new OptionsFormatException('Unable to parse YAML document.'); |
| 59 } |
| 60 } |
| 61 |
| 62 YamlNode doc = safelyLoadYamlNode(); |
| 63 |
| 64 // Empty options. |
| 65 if (doc is YamlScalar && doc.value == null) { |
| 66 return options; |
| 67 } |
| 36 if ((doc != null) && (doc is! YamlMap)) { | 68 if ((doc != null) && (doc is! YamlMap)) { |
| 37 throw new Exception( | 69 throw new OptionsFormatException( |
| 38 'Bad options file format (expected map, got ${doc.runtimeType})\n' | 70 'Bad options file format (expected map, got ${doc.runtimeType})', |
| 39 'contents of options file:\n' | 71 doc.span); |
| 40 '$optionsSource\n'); | |
| 41 } | 72 } |
| 42 if (doc is YamlMap) { | 73 if (doc is YamlMap) { |
| 43 doc.forEach((k, v) { | 74 doc.nodes.forEach((k, YamlNode v) { |
| 44 if (k is! String) { | 75 var key; |
| 45 throw new Exception( | 76 if (k is YamlScalar) { |
| 77 key = k.value; |
| 78 } |
| 79 if (key is! String) { |
| 80 throw new OptionsFormatException( |
| 46 'Bad options file format (expected String scope key, ' | 81 'Bad options file format (expected String scope key, ' |
| 47 'got ${k.runtimeType})'); | 82 'got ${k.runtimeType})', |
| 83 (k ?? doc).span); |
| 48 } | 84 } |
| 49 options[k] = v; | 85 if (v != null && v is! YamlNode) { |
| 86 throw new OptionsFormatException( |
| 87 'Bad options file format (expected Node value, ' |
| 88 'got ${v.runtimeType}: `${v.toString()}`)', |
| 89 doc.span); |
| 90 } |
| 91 options[key] = v; |
| 50 }); | 92 }); |
| 51 } | 93 } |
| 52 return options; | 94 return options; |
| 53 } | 95 } |
| 54 | 96 |
| 97 /// Merge the given options contents where the values in [defaults] may be |
| 98 /// overridden by [overrides]. |
| 99 /// |
| 100 /// Some notes about merge semantics: |
| 101 /// |
| 102 /// * lists are merged (without duplicates). |
| 103 /// * lists of scalar values can be promoted to simple maps when merged with |
| 104 /// maps of strings to booleans (e.g., ['opt1', 'opt2'] becomes |
| 105 /// {'opt1': true, 'opt2': true}. |
| 106 /// * maps are merged recursively. |
| 107 /// * if map values cannot be merged, the overriding value is taken. |
| 108 /// |
| 109 Map<String, YamlNode> merge( |
| 110 Map<String, YamlNode> defaults, Map<String, YamlNode> overrides) => |
| 111 new Merger().merge(defaults, overrides) as Map<String, YamlNode>; |
| 112 |
| 55 /// Read the contents of [file] as a string. | 113 /// Read the contents of [file] as a string. |
| 56 /// Returns null if file does not exist. | 114 /// Returns null if file does not exist. |
| 57 String _readAnalysisOptionsFile(File file) { | 115 String _readAnalysisOptionsFile(File file) { |
| 58 try { | 116 try { |
| 59 return file.readAsStringSync(); | 117 return file.readAsStringSync(); |
| 60 } on FileSystemException { | 118 } on FileSystemException { |
| 61 // File can't be read. | 119 // File can't be read. |
| 62 return null; | 120 return null; |
| 63 } | 121 } |
| 64 } | 122 } |
| 65 } | 123 } |
| 124 |
| 125 /// Thrown on options format exceptions. |
| 126 class OptionsFormatException implements Exception { |
| 127 final String message; |
| 128 final SourceSpan span; |
| 129 OptionsFormatException(this.message, [this.span]); |
| 130 |
| 131 @override |
| 132 String toString() => |
| 133 'OptionsFormatException: ${message?.toString()}, ${span?.toString()}'; |
| 134 } |
| OLD | NEW |