diff --git a/lib/src/library.dart b/lib/src/library.dart index eb3db2e2..9cbe704c 100644 --- a/lib/src/library.dart +++ b/lib/src/library.dart @@ -168,18 +168,60 @@ class LibraryReader { Iterable get classElements => element.definingCompilationUnit.types; - Iterable _getElements(CompilationUnitMember member) { + static Iterable _getElements(CompilationUnitMember member) { if (member is TopLevelVariableDeclaration) { return member.variables.variables .map(resolutionMap.elementDeclaredByVariableDeclaration); } var element = resolutionMap.elementDeclaredByDeclaration(member); - if (element == null) { - print([member, member.runtimeType, member.element]); - throw new Exception('Could not find any elements for the provided unit.'); + throw new StateError( + 'Could not find any elements for the provided unit.'); } - return [element]; } + + /// Returns the identifier prefix of [element] if it is referenced by one. + /// + /// For example in the following file: + /// ``` + /// import 'bar.dart' as bar; + /// + /// bar.Bar b; + /// ``` + /// + /// ... we'd assume that `b`'s type has a prefix of `bar`. + /// + /// If there is no prefix, one could not be computed, `null` is returned. + /// + /// If there is an attempt to read a prefix of a file _other_ than the current + /// library being read this will throw [StateError], as it is not always + /// possible to read details of the source file from other inputs. + String _prefixForType(Element element) { + final astNode = element.computeNode(); + if (astNode is VariableDeclaration) { + final parentNode = astNode.parent as VariableDeclarationList; + return _prefixForTypeAnnotation(parentNode.type); + } + return null; + } + + static String _prefixForTypeAnnotation(TypeAnnotation astNode) { + if (astNode is NamedType) { + return _prefixForIdentifier(astNode.name); + } + return null; + } + + static String _prefixForIdentifier(Identifier id) { + return id is PrefixedIdentifier ? id.prefix.name : null; + } } + +// Testing-only access to LibraryReader._prefixFor. +// +// This is used to iterate on the tests without launching the feature. +// Additionally it looks ike `computeNode()` will be deprecated, so we might +// have to rewrite the entire body of the function in the near future; at least +// the tests can help for correctness. +String testingPrefixForType(LibraryReader r, Element e) => r._prefixForType(e); diff --git a/test/library/prefix_for_type_test.dart b/test/library/prefix_for_type_test.dart new file mode 100644 index 00000000..70045288 --- /dev/null +++ b/test/library/prefix_for_type_test.dart @@ -0,0 +1,84 @@ +// Copyright (c) 2017, 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. + +// Increase timeouts on this test which resolves source code and can be slow. +@Timeout.factor(2.0) +import 'dart:async'; + +import 'package:analyzer/dart/element/element.dart'; +import 'package:build/build.dart'; +import 'package:build_test/build_test.dart'; +import 'package:source_gen/source_gen.dart'; +import 'package:test/test.dart'; + +import 'package:source_gen/src/library.dart' show testingPrefixForType; + +void main() { + // Holds the analyzer open until all of the tests complete. + final doneWithResolver = new Completer(); + + LibraryReader b; + + setUpAll(() async { + final resolver = await resolveSources( + { + // The order here is important; pkg/build_test has a bug where only the + // first library can be accessed via "libraryFor", the others throw that + // the file is not a library. + // + // See https://github.com/dart-lang/build/issues/843. + 'test_lib|lib/b.dart': sourceB, + 'test_lib|lib/a.dart': sourceA, + }, + (r) => r, + tearDown: doneWithResolver.future, + ); + b = new LibraryReader( + await resolver.libraryFor(new AssetId('test_lib', 'lib/b.dart')), + ); + expect(b.element, isNotNull); + }); + + tearDownAll(doneWithResolver.complete); + + group('top level fields', () { + List topLevelFieldTypes; + + setUpAll(() { + topLevelFieldTypes = b.element.definingCompilationUnit.topLevelVariables; + }); + + Element field(String name) => + topLevelFieldTypes.firstWhere((e) => e.name == name, + orElse: () => throw new ArgumentError.value( + name, 'name', 'Could not find a field named $name.')); + + test('should read the prefix of a type', () { + expect( + testingPrefixForType(b, field('topLevelFieldWithPrefix')), + 'a_prefixed', + ); + }); + + test('should read null when the type is not prefixed', () { + expect( + testingPrefixForType(b, field('topLevelFieldWithoutPrefix')), + isNull, + ); + }); + }); +} + +const sourceA = r''' + class A {} + class B {} +'''; + +const sourceB = r''' + import 'a.dart' show B; + import 'a.dart' as a_prefixed; + + a_prefixed.A topLevelFieldWithPrefix; // [0] + B topLevelFieldWithoutPrefix; // [1] +''';