Index: lib/src/runner/parse_metadata.dart |
diff --git a/lib/src/runner/parse_metadata.dart b/lib/src/runner/parse_metadata.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..91a9b90454616244a376169594bad466e5bb32ab |
--- /dev/null |
+++ b/lib/src/runner/parse_metadata.dart |
@@ -0,0 +1,97 @@ |
+// 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. |
+ |
+library unittest.runner.parse_metadata; |
+ |
+import 'dart:io'; |
+ |
+import 'package:analyzer/src/generated/ast.dart'; |
+import 'package:path/path.dart' as p; |
+import 'package:source_span/source_span.dart'; |
+ |
+import '../backend/metadata.dart'; |
+import '../util/dart.dart'; |
+ |
+/// Parse the test metadata for the test file at [path]. |
+/// |
+/// Throws an [AnalysisError] if parsing fails or a [FormatException] if the |
+/// test annotations are incorrect. |
+Metadata parseMetadata(String path) { |
+ var testOn; |
+ for (var annotation in parseAnnotations(path)) { |
+ // The annotation syntax is ambiguous between named constructors and |
+ // prefixed annotations. We don't want to have to parse the imports in order |
+ // to resolve this ambiguity, so we just assume that unittest annotations |
Bob Nystrom
2015/03/12 21:03:21
Given that you are parsing the directives now, thi
nweiz
2015/03/12 23:40:22
Done.
|
+ // will never be prefixed. |
+ // |
+ // The analyzer parses "@x.y()" as prefix "x", annotation "y", and named |
+ // constructor null. It parses "@x.y.z()" as prefix "x", annotation "y", and |
+ // named constructor "z". |
+ |
+ // Always skip annotations with constructor names, because they must also |
+ // have prefixes and we assume unittest metadata never has a prefix. |
+ if (annotation.constructorName != null) continue; |
+ |
+ var name; |
+ var constructorName; |
+ var identifier = annotation.name; |
+ if (identifier is PrefixedIdentifier) { |
+ name = identifier.prefix.name; |
+ constructorName = identifier.identifier.name; |
+ } else { |
+ name = identifier.name; |
+ } |
+ |
+ if (name != 'TestOn') continue; |
+ if (constructorName != null) { |
+ throw new SourceSpanFormatException( |
+ 'TestOn doesn\'t have a constructor named "$constructorName".', |
+ _spanFor(identifier.identifier, path)); |
+ } |
+ |
+ if (annotation.arguments == null) { |
+ throw new SourceSpanFormatException( |
+ 'TestOn takes one argument.', _spanFor(annotation, path)); |
+ } |
+ |
+ var args = annotation.arguments.arguments; |
+ if (args.isEmpty) { |
+ throw new SourceSpanFormatException( |
+ 'TestOn takes one argument.', _spanFor(annotation.arguments, path)); |
+ } |
+ |
+ if (args.first is NamedExpression) { |
+ throw new SourceSpanFormatException( |
+ "TestOn doesn't take named parameters.", _spanFor(args.first, path)); |
+ } |
+ |
+ if (args.length > 1) { |
+ throw new SourceSpanFormatException( |
+ "TestOn takes only one argument.", |
+ _spanFor(annotation.arguments, path)); |
+ } |
+ |
+ if (args.first is! StringLiteral) { |
+ throw new SourceSpanFormatException( |
+ "TestOn takes a String.", _spanFor(args.first, path)); |
Bob Nystrom
2015/03/12 21:03:21
Since you don't handle adjacent strings (which is
nweiz
2015/03/12 23:40:22
StringLiteral is the superclass of both adjacent s
|
+ } |
+ |
+ if (testOn != null) { |
+ throw new SourceSpanFormatException( |
+ "Only a single TestOn annotation may be used for a given test file.", |
+ _spanFor(annotation, path)); |
+ } |
+ |
+ testOn = args.first.stringValue; |
+ } |
+ |
+ return new Metadata(testOn); |
+} |
+ |
+/// Creates a [SourceSpan] for [node]. |
+SourceSpan _spanFor(AstNode node, String path) => |
+ // Load a SourceFile from scratch here since we're only ever going to emit |
+ // one error per file anyway. |
+ new SourceFile(new File(path).readAsStringSync(), url: p.toUri(path)) |
+ .span(node.offset, node.end); |