| // Copyright (c) 2012, 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 mirrors_util; |
| |
| import 'dart:collection' show Queue, IterableBase; |
| |
| // TODO(rnystrom): Use "package:" URL (#4968). |
| import 'mirrors.dart'; |
| |
| //------------------------------------------------------------------------------ |
| // Utility functions for using the Mirror API |
| //------------------------------------------------------------------------------ |
| |
| |
| /** |
| * Return the display name for [mirror]. |
| * |
| * The display name is the normal representation of the entity name. In most |
| * cases the display name is the simple name, but for a setter 'foo=' the |
| * display name is simply 'foo' and for the unary minus operator the display |
| * name is 'operator -'. For 'dart:' libraries the display name is the URI and |
| * not the library name, for instance 'dart:core' instead of 'dart.core'. |
| * |
| * The display name is not unique. |
| */ |
| String displayName(DeclarationMirror mirror) { |
| if (mirror is LibraryMirror) { |
| LibraryMirror library = mirror; |
| if (library.uri.scheme == 'dart') { |
| return library.uri.toString(); |
| } |
| } else if (mirror is MethodMirror) { |
| MethodMirror methodMirror = mirror; |
| String simpleName = methodMirror.simpleName; |
| if (methodMirror.isSetter) { |
| // Remove trailing '='. |
| return simpleName.substring(0, simpleName.length-1); |
| } else if (methodMirror.isOperator) { |
| return 'operator ${operatorName(methodMirror)}'; |
| } else if (methodMirror.isConstructor) { |
| String className = displayName(methodMirror.owner); |
| if (simpleName == '') { |
| return className; |
| } else { |
| return '$className.$simpleName'; |
| } |
| } |
| } |
| return mirror.simpleName; |
| } |
| |
| /** |
| * Returns the operator name if [methodMirror] is an operator method, |
| * for instance [:'<':] for [:operator <:] and [:'-':] for the unary minus |
| * operator. Return [:null:] if [methodMirror] is not an operator method. |
| */ |
| String operatorName(MethodMirror methodMirror) { |
| String simpleName = methodMirror.simpleName; |
| if (methodMirror.isOperator) { |
| if (simpleName == Mirror.UNARY_MINUS) { |
| return '-'; |
| } else { |
| return simpleName; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns an iterable over the type declarations directly inheriting from |
| * the declaration of this type. |
| */ |
| Iterable<ClassMirror> computeSubdeclarations(ClassMirror type) { |
| type = type.originalDeclaration; |
| var subtypes = <ClassMirror>[]; |
| type.mirrors.libraries.forEach((_, library) { |
| for (ClassMirror otherType in library.classes.values) { |
| var superClass = otherType.superclass; |
| if (superClass != null) { |
| superClass = superClass.originalDeclaration; |
| if (type.library == superClass.library) { |
| if (superClass == type) { |
| subtypes.add(otherType); |
| } |
| } |
| } |
| final superInterfaces = otherType.superinterfaces; |
| for (ClassMirror superInterface in superInterfaces) { |
| superInterface = superInterface.originalDeclaration; |
| if (type.library == superInterface.library) { |
| if (superInterface == type) { |
| subtypes.add(otherType); |
| } |
| } |
| } |
| } |
| }); |
| return subtypes; |
| } |
| |
| LibraryMirror findLibrary(MemberMirror member) { |
| DeclarationMirror owner = member.owner; |
| if (owner is LibraryMirror) { |
| return owner; |
| } else if (owner is TypeMirror) { |
| TypeMirror mirror = owner; |
| return mirror.library; |
| } |
| throw new Exception('Unexpected owner: ${owner}'); |
| } |
| |
| class HierarchyIterable extends IterableBase<ClassMirror> { |
| final bool includeType; |
| final ClassMirror type; |
| |
| HierarchyIterable(this.type, {bool includeType}) |
| : this.includeType = includeType; |
| |
| Iterator<ClassMirror> get iterator => |
| new HierarchyIterator(type, includeType: includeType); |
| } |
| |
| /** |
| * [HierarchyIterator] iterates through the class hierarchy of the provided |
| * type. |
| * |
| * First the superclass relation is traversed, skipping [Object], next the |
| * superinterface relation and finally is [Object] visited. The supertypes are |
| * visited in breadth first order and a superinterface is visited more than once |
| * if implemented through multiple supertypes. |
| */ |
| class HierarchyIterator implements Iterator<ClassMirror> { |
| final Queue<ClassMirror> queue = new Queue<ClassMirror>(); |
| ClassMirror object; |
| ClassMirror _current; |
| |
| HierarchyIterator(ClassMirror type, {bool includeType}) { |
| if (includeType) { |
| queue.add(type); |
| } else { |
| push(type); |
| } |
| } |
| |
| ClassMirror push(ClassMirror type) { |
| if (type.superclass != null) { |
| if (type.superclass.isObject) { |
| object = type.superclass; |
| } else { |
| queue.addFirst(type.superclass); |
| } |
| } |
| queue.addAll(type.superinterfaces); |
| return type; |
| } |
| |
| ClassMirror get current => _current; |
| |
| bool moveNext() { |
| _current = null; |
| if (queue.isEmpty) { |
| if (object == null) return false; |
| _current = object; |
| object = null; |
| return true; |
| } else { |
| _current = push(queue.removeFirst()); |
| return true; |
| } |
| } |
| } |
| |
| /// Returns `true` if [cls] is declared in a private dart library. |
| bool isFromPrivateDartLibrary(ClassMirror cls) { |
| if (isMixinApplication(cls)) cls = cls.mixin; |
| var uri = cls.library.uri; |
| return uri.scheme == 'dart' && uri.path.startsWith('_'); |
| } |
| |
| /// Returns `true` if [mirror] reflects a mixin application. |
| bool isMixinApplication(Mirror mirror) { |
| return mirror is ClassMirror && mirror.mixin != mirror; |
| } |
| |
| /** |
| * Returns the superclass of [cls] skipping unnamed mixin applications. |
| * |
| * For instance, for all of the following definitions this method returns [:B:]. |
| * |
| * class A extends B {} |
| * class A extends B with C1, C2 {} |
| * class A extends B implements D1, D2 {} |
| * class A extends B with C1, C2 implements D1, D2 {} |
| * class A = B with C1, C2; |
| * abstract class A = B with C1, C2 implements D1, D2; |
| */ |
| ClassMirror getSuperclass(ClassMirror cls) { |
| ClassMirror superclass = cls.superclass; |
| while (isMixinApplication(superclass) && superclass.isNameSynthetic) { |
| superclass = superclass.superclass; |
| } |
| return superclass; |
| } |
| |
| /** |
| * Returns the mixins directly applied to [cls]. |
| * |
| * For instance, for all of the following definitions this method returns |
| * [:C1, C2:]. |
| * |
| * class A extends B with C1, C2 {} |
| * class A extends B with C1, C2 implements D1, D2 {} |
| * class A = B with C1, C2; |
| * abstract class A = B with C1, C2 implements D1, D2; |
| */ |
| Iterable<ClassMirror> getAppliedMixins(ClassMirror cls) { |
| List<ClassMirror> mixins = <ClassMirror>[]; |
| ClassMirror superclass = cls.superclass; |
| while (isMixinApplication(superclass) && superclass.isNameSynthetic) { |
| mixins.add(superclass.mixin); |
| superclass = superclass.superclass; |
| } |
| if (mixins.length > 1) { |
| mixins = new List<ClassMirror>.from(mixins.reversed); |
| } |
| if (isMixinApplication(cls)) { |
| mixins.add(cls.mixin); |
| } |
| return mixins; |
| } |
| |
| /** |
| * Returns the superinterfaces directly and explicitly implemented by [cls]. |
| * |
| * For instance, for all of the following definitions this method returns |
| * [:D1, D2:]. |
| * |
| * class A extends B implements D1, D2 {} |
| * class A extends B with C1, C2 implements D1, D2 {} |
| * abstract class A = B with C1, C2 implements D1, D2; |
| */ |
| Iterable<ClassMirror> getExplicitInterfaces(ClassMirror cls) { |
| if (isMixinApplication(cls)) { |
| bool first = true; |
| ClassMirror mixin = cls.mixin; |
| bool filter(ClassMirror superinterface) { |
| if (first && superinterface == mixin) { |
| first = false; |
| return false; |
| } |
| return true; |
| } |
| return cls.superinterfaces.where(filter); |
| } |
| return cls.superinterfaces; |
| } |
| |
| final RegExp _singleLineCommentStart = new RegExp(r'^///? ?(.*)'); |
| final RegExp _multiLineCommentStartEnd = |
| new RegExp(r'^/\*\*? ?([\s\S]*)\*/$', multiLine: true); |
| final RegExp _multiLineCommentLineStart = new RegExp(r'^[ \t]*\* ?(.*)'); |
| |
| /** |
| * Pulls the raw text out of a comment (i.e. removes the comment |
| * characters). |
| */ |
| String stripComment(String comment) { |
| Match match = _singleLineCommentStart.firstMatch(comment); |
| if (match != null) { |
| return match[1]; |
| } |
| match = _multiLineCommentStartEnd.firstMatch(comment); |
| if (match != null) { |
| comment = match[1]; |
| var sb = new StringBuffer(); |
| List<String> lines = comment.split('\n'); |
| for (int index = 0 ; index < lines.length ; index++) { |
| String line = lines[index]; |
| if (index == 0) { |
| sb.write(line); // Add the first line unprocessed. |
| continue; |
| } |
| sb.write('\n'); |
| match = _multiLineCommentLineStart.firstMatch(line); |
| if (match != null) { |
| sb.write(match[1]); |
| } else if (index < lines.length-1 || !line.trim().isEmpty) { |
| // Do not add the last line if it only contains white space. |
| // This interprets cases like |
| // /* |
| // * Foo |
| // */ |
| // as "\nFoo\n" and not as "\nFoo\n ". |
| sb.write(line); |
| } |
| } |
| return sb.toString(); |
| } |
| throw new ArgumentError('Invalid comment $comment'); |
| } |
| |
| /** |
| * Looks up [name] in the scope [declaration]. |
| * |
| * If [name] is of the form 'a.b.c', 'a' is looked up in the scope of |
| * [declaration] and if unresolved 'a.b' is looked in the scope of |
| * [declaration]. Each identifier of the remaining suffix, 'c' or 'b.c', is |
| * then looked up in the local scope of the previous result. |
| * |
| * For instance, assumming that [:Iterable:] is imported into the scope of |
| * [declaration] via the prefix 'col', 'col.Iterable.E' finds the type |
| * variable of [:Iterable:] and 'col.Iterable.contains.element' finds the |
| * [:element:] parameter of the [:contains:] method on [:Iterable:]. |
| */ |
| DeclarationMirror lookupQualifiedInScope(DeclarationMirror declaration, |
| String name) { |
| // TODO(11653): Support lookup of constructors using the [:new Foo:] |
| // syntax. |
| int offset = 1; |
| List<String> parts = name.split('.'); |
| DeclarationMirror result = declaration.lookupInScope(parts[0]); |
| if (result == null && parts.length > 1) { |
| // Try lookup of `prefix.id`. |
| result = declaration.lookupInScope('${parts[0]}.${parts[1]}'); |
| offset = 2; |
| } |
| if (result == null) return null; |
| while (result != null && offset < parts.length) { |
| result = _lookupLocal(result, parts[offset++]); |
| } |
| return result; |
| } |
| |
| DeclarationMirror _lookupLocal(Mirror mirror, String id) { |
| DeclarationMirror result; |
| if (mirror is ContainerMirror) { |
| ContainerMirror containerMirror = mirror; |
| // Try member lookup. |
| result = containerMirror.members[id]; |
| } |
| if (result != null) return result; |
| if (mirror is ClassMirror) { |
| ClassMirror classMirror = mirror; |
| // Try type variables. |
| result = classMirror.typeVariables.firstWhere( |
| (TypeVariableMirror v) => v.simpleName == id, orElse: () => null); |
| } else if (mirror is MethodMirror) { |
| MethodMirror methodMirror = mirror; |
| result = methodMirror.parameters.firstWhere( |
| (ParameterMirror p) => p.simpleName == id, orElse: () => null); |
| } |
| return result; |
| |
| } |