OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library unittest.runner.parse_metadata; |
| 6 |
| 7 import 'dart:io'; |
| 8 |
| 9 import 'package:analyzer/analyzer.dart'; |
| 10 import 'package:analyzer/src/generated/ast.dart'; |
| 11 import 'package:path/path.dart' as p; |
| 12 import 'package:source_span/source_span.dart'; |
| 13 |
| 14 import '../backend/metadata.dart'; |
| 15 import '../util/dart.dart'; |
| 16 |
| 17 /// Parse the test metadata for the test file at [path]. |
| 18 /// |
| 19 /// Throws an [AnalysisError] if parsing fails or a [FormatException] if the |
| 20 /// test annotations are incorrect. |
| 21 Metadata parseMetadata(String path) { |
| 22 var testOn; |
| 23 |
| 24 var contents = new File(path).readAsStringSync(); |
| 25 var directives = parseDirectives(contents, name: path).directives; |
| 26 var annotations = directives.isEmpty ? [] : directives.first.metadata; |
| 27 |
| 28 // We explicitly *don't* just look for "package:unittest" imports here, |
| 29 // because it could be re-exported from another library. |
| 30 var prefixes = directives.map((directive) { |
| 31 if (directive is! ImportDirective) return null; |
| 32 if (directive.prefix == null) return null; |
| 33 return directive.prefix.name; |
| 34 }).where((prefix) => prefix != null).toSet(); |
| 35 |
| 36 for (var annotation in annotations) { |
| 37 // The annotation syntax is ambiguous between named constructors and |
| 38 // prefixed annotations, so we need to resolve that ambiguity using the |
| 39 // known prefixes. The analyzer parses "@x.y()" as prefix "x", annotation |
| 40 // "y", and named constructor null. It parses "@x.y.z()" as prefix "x", |
| 41 // annotation "y", and named constructor "z". |
| 42 var name; |
| 43 var constructorName; |
| 44 var identifier = annotation.name; |
| 45 if (identifier is PrefixedIdentifier && |
| 46 !prefixes.contains(identifier.prefix.name) && |
| 47 annotation.constructorName == null) { |
| 48 name = identifier.prefix.name; |
| 49 constructorName = identifier.identifier.name; |
| 50 } else { |
| 51 name = identifier is PrefixedIdentifier |
| 52 ? identifier.identifier.name |
| 53 : identifier.name; |
| 54 if (annotation.constructorName != null) { |
| 55 constructorName = annotation.constructorName.name; |
| 56 } |
| 57 } |
| 58 |
| 59 if (name != 'TestOn') continue; |
| 60 if (constructorName != null) { |
| 61 throw new SourceSpanFormatException( |
| 62 'TestOn doesn\'t have a constructor named "$constructorName".', |
| 63 _spanFor(identifier.identifier, path)); |
| 64 } |
| 65 |
| 66 if (annotation.arguments == null) { |
| 67 throw new SourceSpanFormatException( |
| 68 'TestOn takes one argument.', _spanFor(annotation, path)); |
| 69 } |
| 70 |
| 71 var args = annotation.arguments.arguments; |
| 72 if (args.isEmpty) { |
| 73 throw new SourceSpanFormatException( |
| 74 'TestOn takes one argument.', _spanFor(annotation.arguments, path)); |
| 75 } |
| 76 |
| 77 if (args.first is NamedExpression) { |
| 78 throw new SourceSpanFormatException( |
| 79 "TestOn doesn't take named parameters.", _spanFor(args.first, path)); |
| 80 } |
| 81 |
| 82 if (args.length > 1) { |
| 83 throw new SourceSpanFormatException( |
| 84 "TestOn takes only one argument.", |
| 85 _spanFor(annotation.arguments, path)); |
| 86 } |
| 87 |
| 88 if (args.first is! StringLiteral) { |
| 89 throw new SourceSpanFormatException( |
| 90 "TestOn takes a String.", _spanFor(args.first, path)); |
| 91 } |
| 92 |
| 93 if (testOn != null) { |
| 94 throw new SourceSpanFormatException( |
| 95 "Only a single TestOn annotation may be used for a given test file.", |
| 96 _spanFor(annotation, path)); |
| 97 } |
| 98 |
| 99 testOn = args.first.stringValue; |
| 100 } |
| 101 |
| 102 return new Metadata(testOn); |
| 103 } |
| 104 |
| 105 /// Creates a [SourceSpan] for [node]. |
| 106 SourceSpan _spanFor(AstNode node, String path) => |
| 107 // Load a SourceFile from scratch here since we're only ever going to emit |
| 108 // one error per file anyway. |
| 109 new SourceFile(new File(path).readAsStringSync(), url: p.toUri(path)) |
| 110 .span(node.offset, node.end); |
OLD | NEW |