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 test.runner.parse_metadata; | 5 library test.runner.parse_metadata; |
6 | 6 |
7 import 'dart:io'; | 7 import 'dart:io'; |
8 | 8 |
9 import 'package:analyzer/analyzer.dart'; | 9 import 'package:analyzer/analyzer.dart'; |
10 import 'package:analyzer/src/generated/ast.dart'; | 10 import 'package:analyzer/src/generated/ast.dart'; |
11 import 'package:path/path.dart' as p; | 11 import 'package:path/path.dart' as p; |
12 import 'package:source_span/source_span.dart'; | 12 import 'package:source_span/source_span.dart'; |
13 | 13 |
14 import '../backend/metadata.dart'; | 14 import '../backend/metadata.dart'; |
| 15 import '../backend/platform_selector.dart'; |
15 import '../frontend/timeout.dart'; | 16 import '../frontend/timeout.dart'; |
16 import '../util/dart.dart'; | 17 import '../util/dart.dart'; |
| 18 import '../utils.dart'; |
17 | 19 |
18 /// Parse the test metadata for the test file at [path]. | 20 /// Parse the test metadata for the test file at [path]. |
19 /// | 21 /// |
20 /// Throws an [AnalysisError] if parsing fails or a [FormatException] if the | 22 /// Throws an [AnalysisError] if parsing fails or a [FormatException] if the |
21 /// test annotations are incorrect. | 23 /// test annotations are incorrect. |
22 Metadata parseMetadata(String path) => new _Parser(path).parse(); | 24 Metadata parseMetadata(String path) => new _Parser(path).parse(); |
23 | 25 |
24 /// A parser for test suite metadata. | 26 /// A parser for test suite metadata. |
25 class _Parser { | 27 class _Parser { |
26 /// The path to the test suite. | 28 /// The path to the test suite. |
(...skipping 18 matching lines...) Expand all Loading... |
45 if (directive.prefix == null) return null; | 47 if (directive.prefix == null) return null; |
46 return directive.prefix.name; | 48 return directive.prefix.name; |
47 }).where((prefix) => prefix != null).toSet(); | 49 }).where((prefix) => prefix != null).toSet(); |
48 } | 50 } |
49 | 51 |
50 /// Parses the metadata. | 52 /// Parses the metadata. |
51 Metadata parse() { | 53 Metadata parse() { |
52 var timeout; | 54 var timeout; |
53 var testOn; | 55 var testOn; |
54 var skip; | 56 var skip; |
| 57 var onPlatform; |
55 | 58 |
56 for (var annotation in _annotations) { | 59 for (var annotation in _annotations) { |
57 // The annotation syntax is ambiguous between named constructors and | 60 var pair = _resolveConstructor( |
58 // prefixed annotations, so we need to resolve that ambiguity using the | 61 annotation.name, annotation.constructorName); |
59 // known prefixes. The analyzer parses "@x.y()" as prefix "x", annotation | 62 var name = pair.first; |
60 // "y", and named constructor null. It parses "@x.y.z()" as prefix "x", | 63 var constructorName = pair.last; |
61 // annotation "y", and named constructor "z". | |
62 var name; | |
63 var constructorName; | |
64 var identifier = annotation.name; | |
65 if (identifier is PrefixedIdentifier && | |
66 !_prefixes.contains(identifier.prefix.name) && | |
67 annotation.constructorName == null) { | |
68 name = identifier.prefix.name; | |
69 constructorName = identifier.identifier.name; | |
70 } else { | |
71 name = identifier is PrefixedIdentifier | |
72 ? identifier.identifier.name | |
73 : identifier.name; | |
74 if (annotation.constructorName != null) { | |
75 constructorName = annotation.constructorName.name; | |
76 } | |
77 } | |
78 | 64 |
79 if (name == 'TestOn') { | 65 if (name == 'TestOn') { |
80 _assertSingleAnnotation(testOn, 'TestOn', annotation); | 66 _assertSingle(testOn, 'TestOn', annotation); |
81 testOn = _parseTestOn(annotation, constructorName); | 67 testOn = _parseTestOn(annotation, constructorName); |
82 } else if (name == 'Timeout') { | 68 } else if (name == 'Timeout') { |
83 _assertSingleAnnotation(timeout, 'Timeout', annotation); | 69 _assertSingle(timeout, 'Timeout', annotation); |
84 timeout = _parseTimeout(annotation, constructorName); | 70 timeout = _parseTimeout(annotation, constructorName); |
85 } else if (name == 'Skip') { | 71 } else if (name == 'Skip') { |
86 _assertSingleAnnotation(skip, 'Skip', annotation); | 72 _assertSingle(skip, 'Skip', annotation); |
87 skip = _parseSkip(annotation, constructorName); | 73 skip = _parseSkip(annotation, constructorName); |
| 74 } else if (name == 'OnPlatform') { |
| 75 _assertSingle(onPlatform, 'OnPlatform', annotation); |
| 76 onPlatform = _parseOnPlatform(annotation, constructorName); |
88 } | 77 } |
89 } | 78 } |
90 | 79 |
91 try { | 80 return new Metadata( |
92 return new Metadata.parse( | 81 testOn: testOn, |
93 testOn: testOn == null ? null : testOn.stringValue, | 82 timeout: timeout, |
94 timeout: timeout, | 83 skip: skip != null, |
95 skip: skip); | 84 skipReason: skip is String ? skip : null, |
96 } on SourceSpanFormatException catch (error) { | 85 onPlatform: onPlatform); |
97 var file = new SourceFile(new File(_path).readAsStringSync(), | |
98 url: p.toUri(_path)); | |
99 var span = contextualizeSpan(error.span, testOn, file); | |
100 if (span == null) rethrow; | |
101 throw new SourceSpanFormatException(error.message, span); | |
102 } | |
103 } | 86 } |
104 | 87 |
105 /// Parses a `@TestOn` annotation. | 88 /// Parses a `@TestOn` annotation. |
106 /// | 89 /// |
107 /// [annotation] is the annotation. [constructorName] is the name of the named | 90 /// [annotation] is the annotation. [constructorName] is the name of the named |
108 /// constructor for the annotation, if any. | 91 /// constructor for the annotation, if any. |
109 StringLiteral _parseTestOn(Annotation annotation, String constructorName) { | 92 PlatformSelector _parseTestOn(Annotation annotation, String constructorName) { |
110 _assertConstructorName(constructorName, 'TestOn', annotation); | 93 _assertConstructorName(constructorName, 'TestOn', annotation); |
111 _assertArguments(annotation.arguments, 'TestOn', annotation, positional: 1); | 94 _assertArguments(annotation.arguments, 'TestOn', annotation, positional: 1); |
112 return _parseString(annotation.arguments.arguments.first); | 95 var literal = _parseString(annotation.arguments.arguments.first); |
| 96 return _contextualize(literal, |
| 97 () => new PlatformSelector.parse(literal.stringValue)); |
113 } | 98 } |
114 | 99 |
115 /// Parses a `@Timeout` annotation. | 100 /// Parses a `@Timeout` annotation. |
116 /// | 101 /// |
117 /// [annotation] is the annotation. [constructorName] is the name of the named | 102 /// [annotation] is the annotation. [constructorName] is the name of the named |
118 /// constructor for the annotation, if any. | 103 /// constructor for the annotation, if any. |
119 Timeout _parseTimeout(Annotation annotation, String constructorName) { | 104 Timeout _parseTimeout(Annotation annotation, String constructorName) { |
120 _assertConstructorName(constructorName, 'Timeout', annotation, | 105 _assertConstructorName(constructorName, 'Timeout', annotation, |
121 validNames: [null, 'factor']); | 106 validNames: [null, 'factor']); |
122 | 107 |
123 var description = 'Timeout'; | 108 var description = 'Timeout'; |
124 if (constructorName != null) description += '.$constructorName'; | 109 if (constructorName != null) description += '.$constructorName'; |
125 | 110 |
126 _assertArguments(annotation.arguments, description, annotation, | 111 _assertArguments(annotation.arguments, description, annotation, |
127 positional: 1); | 112 positional: 1); |
128 | 113 |
129 var args = annotation.arguments.arguments; | 114 var args = annotation.arguments.arguments; |
130 if (constructorName == null) return new Timeout(_parseDuration(args.first)); | 115 if (constructorName == null) return new Timeout(_parseDuration(args.first)); |
131 return new Timeout.factor(_parseNum(args.first)); | 116 return new Timeout.factor(_parseNum(args.first)); |
132 } | 117 } |
133 | 118 |
| 119 /// Parses a `Timeout` constructor. |
| 120 Timeout _parseTimeoutConstructor(InstanceCreationExpression constructor) { |
| 121 var name = _parseConstructor(constructor, 'Timeout', |
| 122 validNames: [null, 'factor']); |
| 123 |
| 124 var description = 'Timeout'; |
| 125 if (name != null) description += '.$name'; |
| 126 |
| 127 _assertArguments(constructor.argumentList, description, constructor, |
| 128 positional: 1); |
| 129 |
| 130 var args = constructor.argumentList.arguments; |
| 131 if (name == null) return new Timeout(_parseDuration(args.first)); |
| 132 return new Timeout.factor(_parseNum(args.first)); |
| 133 } |
| 134 |
134 /// Parses a `@Skip` annotation. | 135 /// Parses a `@Skip` annotation. |
135 /// | 136 /// |
136 /// [annotation] is the annotation. [constructorName] is the name of the named | 137 /// [annotation] is the annotation. [constructorName] is the name of the named |
137 /// constructor for the annotation, if any. | 138 /// constructor for the annotation, if any. |
138 /// | 139 /// |
139 /// Returns either `true` or a reason string. | 140 /// Returns either `true` or a reason string. |
140 _parseSkip(Annotation annotation, String constructorName) { | 141 _parseSkip(Annotation annotation, String constructorName) { |
141 _assertConstructorName(constructorName, 'Skip', annotation); | 142 _assertConstructorName(constructorName, 'Skip', annotation); |
142 _assertArguments(annotation.arguments, 'Skip', annotation, optional: 1); | 143 _assertArguments(annotation.arguments, 'Skip', annotation, optional: 1); |
143 | 144 |
144 var args = annotation.arguments.arguments; | 145 var args = annotation.arguments.arguments; |
145 return args.isEmpty ? true : _parseString(args.first).stringValue; | 146 return args.isEmpty ? true : _parseString(args.first).stringValue; |
146 } | 147 } |
147 | 148 |
| 149 /// Parses a `Skip` constructor. |
| 150 /// |
| 151 /// Returns either `true` or a reason string. |
| 152 _parseSkipConstructor(InstanceCreationExpression constructor) { |
| 153 _parseConstructor(constructor, 'Skip'); |
| 154 _assertArguments(constructor.argumentList, 'Skip', constructor, |
| 155 optional: 1); |
| 156 |
| 157 var args = constructor.argumentList.arguments; |
| 158 return args.isEmpty ? true : _parseString(args.first).stringValue; |
| 159 } |
| 160 |
| 161 /// Parses an `@OnPlatform` annotation. |
| 162 /// |
| 163 /// [annotation] is the annotation. [constructorName] is the name of the named |
| 164 /// constructor for the annotation, if any. |
| 165 Map<PlatformSelector, Metadata> _parseOnPlatform(Annotation annotation, |
| 166 String constructorName) { |
| 167 _assertConstructorName(constructorName, 'OnPlatform', annotation); |
| 168 _assertArguments(annotation.arguments, 'OnPlatform', annotation, |
| 169 positional: 1); |
| 170 |
| 171 return _parseMap(annotation.arguments.arguments.first, key: (key) { |
| 172 var selector = _parseString(key); |
| 173 return _contextualize(selector, |
| 174 () => new PlatformSelector.parse(selector.stringValue)); |
| 175 }, value: (value) { |
| 176 var expressions = []; |
| 177 if (value is ListLiteral) { |
| 178 expressions = _parseList(value); |
| 179 } else if (value is InstanceCreationExpression) { |
| 180 expressions = [value]; |
| 181 } else { |
| 182 throw new SourceSpanFormatException( |
| 183 'Expected a Timeout, Skip, or List of those.', |
| 184 _spanFor(value)); |
| 185 } |
| 186 |
| 187 var timeout; |
| 188 var skip; |
| 189 for (var expression in expressions) { |
| 190 var className = expression is InstanceCreationExpression |
| 191 ? _resolveConstructor( |
| 192 expression.constructorName.type.name, |
| 193 expression.constructorName.name).first |
| 194 : null; |
| 195 |
| 196 if (className == 'Timeout') { |
| 197 _assertSingle(timeout, 'Timeout', expression); |
| 198 timeout = _parseTimeoutConstructor(expression); |
| 199 } else if (className == 'Skip') { |
| 200 _assertSingle(skip, 'Skip', expression); |
| 201 skip = _parseSkipConstructor(expression); |
| 202 } else { |
| 203 throw new SourceSpanFormatException( |
| 204 'Expected a Timeout or Skip.', |
| 205 _spanFor(expression)); |
| 206 } |
| 207 } |
| 208 |
| 209 return new Metadata.parse(timeout: timeout, skip: skip); |
| 210 }); |
| 211 } |
| 212 |
148 /// Parses a `const Duration` expression. | 213 /// Parses a `const Duration` expression. |
149 Duration _parseDuration(Expression expression) { | 214 Duration _parseDuration(Expression expression) { |
150 _parseConstructor(expression, 'Duration'); | 215 _parseConstructor(expression, 'Duration'); |
151 | 216 |
152 var constructor = expression as InstanceCreationExpression; | 217 var constructor = expression as InstanceCreationExpression; |
153 var values = _assertArguments( | 218 var values = _assertArguments( |
154 constructor.argumentList, 'Duration', constructor, named: [ | 219 constructor.argumentList, 'Duration', constructor, named: [ |
155 'days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds' | 220 'days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds' |
156 ]); | 221 ]); |
157 | 222 |
158 for (var key in values.keys.toList()) { | 223 for (var key in values.keys.toList()) { |
159 if (values.containsKey(key)) values[key] = _parseInt(values[key]); | 224 if (values.containsKey(key)) values[key] = _parseInt(values[key]); |
160 } | 225 } |
161 | 226 |
162 return new Duration( | 227 return new Duration( |
163 days: values["days"] == null ? 0 : values["days"], | 228 days: values["days"] == null ? 0 : values["days"], |
164 hours: values["hours"] == null ? 0 : values["hours"], | 229 hours: values["hours"] == null ? 0 : values["hours"], |
165 minutes: values["minutes"] == null ? 0 : values["minutes"], | 230 minutes: values["minutes"] == null ? 0 : values["minutes"], |
166 seconds: values["seconds"] == null ? 0 : values["seconds"], | 231 seconds: values["seconds"] == null ? 0 : values["seconds"], |
167 milliseconds: values["milliseconds"] == null ? 0 : values["milliseconds"
], | 232 milliseconds: values["milliseconds"] == null ? 0 : values["milliseconds"
], |
168 microseconds: | 233 microseconds: |
169 values["microseconds"] == null ? 0 : values["microseconds"]); | 234 values["microseconds"] == null ? 0 : values["microseconds"]); |
170 } | 235 } |
171 | 236 |
172 /// Asserts that [existing] is null. | 237 /// Asserts that [existing] is null. |
173 /// | 238 /// |
174 /// [name] is the name of the annotation and [node] is its location, used for | 239 /// [name] is the name of the annotation and [node] is its location, used for |
175 /// error reporting. | 240 /// error reporting. |
176 void _assertSingleAnnotation(Object existing, String name, AstNode node) { | 241 void _assertSingle(Object existing, String name, AstNode node) { |
177 if (existing == null) return; | 242 if (existing == null) return; |
178 throw new SourceSpanFormatException( | 243 throw new SourceSpanFormatException( |
179 "Only a single $name annotation may be used for a given test file.", | 244 "Only a single $name may be used.", _spanFor(node)); |
180 _spanFor(node)); | 245 } |
| 246 |
| 247 /// Resolves a constructor name from its type [identifier] and its |
| 248 /// [constructorName]. |
| 249 /// |
| 250 /// Since the parsed file isn't fully resolved, this is necessary to |
| 251 /// disambiguate between prefixed names and named constructors. |
| 252 Pair<String, String> _resolveConstructor(Identifier identifier, |
| 253 SimpleIdentifier constructorName) { |
| 254 // The syntax is ambiguous between named constructors and prefixed |
| 255 // annotations, so we need to resolve that ambiguity using the known |
| 256 // prefixes. The analyzer parses "new x.y()" as prefix "x", annotation "y", |
| 257 // and named constructor null. It parses "new x.y.z()" as prefix "x", |
| 258 // annotation "y", and named constructor "z". |
| 259 var className; |
| 260 var namedConstructor; |
| 261 if (identifier is PrefixedIdentifier && |
| 262 !_prefixes.contains(identifier.prefix.name) && |
| 263 constructorName == null) { |
| 264 className = identifier.prefix.name; |
| 265 namedConstructor = identifier.identifier.name; |
| 266 } else { |
| 267 className = identifier is PrefixedIdentifier |
| 268 ? identifier.identifier.name |
| 269 : identifier.name; |
| 270 if (constructorName != null) namedConstructor = constructorName.name; |
| 271 } |
| 272 return new Pair(className, namedConstructor); |
181 } | 273 } |
182 | 274 |
183 /// Asserts that [constructorName] is a valid constructor name for an AST | 275 /// Asserts that [constructorName] is a valid constructor name for an AST |
184 /// node. | 276 /// node. |
185 /// | 277 /// |
186 /// [nodeName] is the name of the class being constructed, and [node] is the | 278 /// [nodeName] is the name of the class being constructed, and [node] is the |
187 /// AST node for that class. [validNames], if passed, is the set of valid | 279 /// AST node for that class. [validNames], if passed, is the set of valid |
188 /// constructor names; if an unnamed constructor is valid, it should include | 280 /// constructor names; if an unnamed constructor is valid, it should include |
189 /// `null`. By default, only an unnamed constructor is allowed. | 281 /// `null`. By default, only an unnamed constructor is allowed. |
190 void _assertConstructorName(String constructorName, String nodeName, | 282 void _assertConstructorName(String constructorName, String nodeName, |
(...skipping 22 matching lines...) Expand all Loading... |
213 String _parseConstructor(Expression expression, String className, | 305 String _parseConstructor(Expression expression, String className, |
214 {Iterable<String> validNames}) { | 306 {Iterable<String> validNames}) { |
215 if (validNames == null) validNames = [null]; | 307 if (validNames == null) validNames = [null]; |
216 | 308 |
217 if (expression is! InstanceCreationExpression) { | 309 if (expression is! InstanceCreationExpression) { |
218 throw new SourceSpanFormatException( | 310 throw new SourceSpanFormatException( |
219 "Expected a $className.", _spanFor(expression)); | 311 "Expected a $className.", _spanFor(expression)); |
220 } | 312 } |
221 | 313 |
222 var constructor = expression as InstanceCreationExpression; | 314 var constructor = expression as InstanceCreationExpression; |
223 if (constructor.constructorName.type.name.name != className) { | 315 var pair = _resolveConstructor( |
| 316 constructor.constructorName.type.name, |
| 317 constructor.constructorName.name); |
| 318 var actualClassName = pair.first; |
| 319 var constructorName = pair.last; |
| 320 |
| 321 if (actualClassName != className) { |
224 throw new SourceSpanFormatException( | 322 throw new SourceSpanFormatException( |
225 "Expected a $className.", _spanFor(constructor)); | 323 "Expected a $className.", _spanFor(constructor)); |
226 } | 324 } |
227 | 325 |
228 if (constructor.keyword.lexeme != "const") { | 326 if (constructor.keyword.lexeme != "const") { |
229 throw new SourceSpanFormatException( | 327 throw new SourceSpanFormatException( |
230 "$className must use a const constructor.", _spanFor(constructor)); | 328 "$className must use a const constructor.", _spanFor(constructor)); |
231 } | 329 } |
232 | 330 |
233 var name = constructor.constructorName == null | 331 _assertConstructorName(constructorName, className, expression, |
234 ? null | |
235 : constructor.constructorName.name; | |
236 _assertConstructorName(name, className, expression, | |
237 validNames: validNames); | 332 validNames: validNames); |
238 return name; | 333 return constructorName; |
239 } | 334 } |
240 | 335 |
241 /// Assert that [arguments] is a valid argument list. | 336 /// Assert that [arguments] is a valid argument list. |
242 /// | 337 /// |
243 /// [name] describes the function and [node] is its AST node. [positional] is | 338 /// [name] describes the function and [node] is its AST node. [positional] is |
244 /// the number of required positional arguments, [optional] the number of | 339 /// the number of required positional arguments, [optional] the number of |
245 /// optional positional arguments, and [named] the set of valid argument | 340 /// optional positional arguments, and [named] the set of valid argument |
246 /// names. | 341 /// names. |
247 /// | 342 /// |
248 /// The set of parsed named arguments is returned. | 343 /// The set of parsed named arguments is returned. |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
305 buffer.write("${positional + optional} argument"); | 400 buffer.write("${positional + optional} argument"); |
306 if (positional > 1) buffer.write("s"); | 401 if (positional > 1) buffer.write("s"); |
307 buffer.write("."); | 402 buffer.write("."); |
308 throw new SourceSpanFormatException( | 403 throw new SourceSpanFormatException( |
309 buffer.toString(), _spanFor(arguments)); | 404 buffer.toString(), _spanFor(arguments)); |
310 } | 405 } |
311 | 406 |
312 return namedValues; | 407 return namedValues; |
313 } | 408 } |
314 | 409 |
| 410 /// Parses a Map literal. |
| 411 /// |
| 412 /// By default, returns [Expression] keys and values. These can be overridden |
| 413 /// with the [key] and [value] parameters. |
| 414 Map _parseMap(Expression expression, {key(Expression expression), |
| 415 value(Expression expression)}) { |
| 416 if (key == null) key = (expression) => expression; |
| 417 if (value == null) value = (expression) => expression; |
| 418 |
| 419 if (expression is! MapLiteral) { |
| 420 throw new SourceSpanFormatException( |
| 421 "Expected a Map.", _spanFor(expression)); |
| 422 } |
| 423 |
| 424 var map = expression as MapLiteral; |
| 425 if (map.constKeyword == null) { |
| 426 throw new SourceSpanFormatException( |
| 427 "Map literals must be const.", _spanFor(map)); |
| 428 } |
| 429 |
| 430 return new Map.fromIterable(map.entries, |
| 431 key: (entry) => key(entry.key), |
| 432 value: (entry) => value(entry.value)); |
| 433 } |
| 434 |
| 435 /// Parses a List literal. |
| 436 List<Expression> _parseList(Expression expression) { |
| 437 if (expression is! ListLiteral) { |
| 438 throw new SourceSpanFormatException( |
| 439 "Expected a List.", _spanFor(expression)); |
| 440 } |
| 441 |
| 442 var list = expression as ListLiteral; |
| 443 if (list.constKeyword == null) { |
| 444 throw new SourceSpanFormatException( |
| 445 "List literals must be const.", _spanFor(list)); |
| 446 } |
| 447 |
| 448 return list.elements; |
| 449 } |
| 450 |
315 /// Parses a constant number literal. | 451 /// Parses a constant number literal. |
316 num _parseNum(Expression expression) { | 452 num _parseNum(Expression expression) { |
317 if (expression is IntegerLiteral) return expression.value; | 453 if (expression is IntegerLiteral) return expression.value; |
318 if (expression is DoubleLiteral) return expression.value; | 454 if (expression is DoubleLiteral) return expression.value; |
319 throw new SourceSpanFormatException( | 455 throw new SourceSpanFormatException( |
320 "Expected a number.", _spanFor(expression)); | 456 "Expected a number.", _spanFor(expression)); |
321 } | 457 } |
322 | 458 |
323 /// Parses a constant int literal. | 459 /// Parses a constant int literal. |
324 int _parseInt(Expression expression) { | 460 int _parseInt(Expression expression) { |
(...skipping 10 matching lines...) Expand all Loading... |
335 } | 471 } |
336 | 472 |
337 /// Creates a [SourceSpan] for [node]. | 473 /// Creates a [SourceSpan] for [node]. |
338 SourceSpan _spanFor(AstNode node) { | 474 SourceSpan _spanFor(AstNode node) { |
339 // Load a SourceFile from scratch here since we're only ever going to emit | 475 // Load a SourceFile from scratch here since we're only ever going to emit |
340 // one error per file anyway. | 476 // one error per file anyway. |
341 var contents = new File(_path).readAsStringSync(); | 477 var contents = new File(_path).readAsStringSync(); |
342 return new SourceFile(contents, url: p.toUri(_path)) | 478 return new SourceFile(contents, url: p.toUri(_path)) |
343 .span(node.offset, node.end); | 479 .span(node.offset, node.end); |
344 } | 480 } |
| 481 |
| 482 /// Runs [fn] and contextualizes any [SourceSpanFormatException]s that occur |
| 483 /// in it relative to [literal]. |
| 484 _contextualize(StringLiteral literal, fn()) { |
| 485 try { |
| 486 return fn(); |
| 487 } on SourceSpanFormatException catch (error) { |
| 488 var file = new SourceFile(new File(_path).readAsStringSync(), |
| 489 url: p.toUri(_path)); |
| 490 var span = contextualizeSpan(error.span, literal, file); |
| 491 if (span == null) rethrow; |
| 492 throw new SourceSpanFormatException(error.message, span); |
| 493 } |
| 494 } |
345 } | 495 } |
OLD | NEW |