| // Copyright (c) 2014, 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 docgen.models.indexable; |
| |
| import 'dart:collection'; |
| |
| import 'package:markdown/markdown.dart' as markdown; |
| |
| import '../exports/mirrors_util.dart' as dart2js_util; |
| import '../exports/source_mirrors.dart'; |
| |
| import '../library_helpers.dart'; |
| import 'library.dart'; |
| import 'mirror_based.dart'; |
| import 'model_helpers.dart'; |
| |
| /// An item that is categorized in our mirrorToDocgen map, as a distinct, |
| /// searchable element. |
| /// |
| /// These are items that refer to concrete entities (a Class, for example, |
| /// but not a Type, which is a "pointer" to a class) that we wish to be |
| /// globally resolvable. This includes things such as class methods and |
| /// variables, but parameters for methods are not "Indexable" as we do not want |
| /// the user to be able to search for a method based on its parameter names! |
| /// The set of indexable items also includes Typedefs, since the user can refer |
| /// to them as concrete entities in a particular scope. |
| abstract class Indexable<TMirror extends DeclarationMirror> |
| extends MirrorBased<TMirror> { |
| |
| Library get owningLibrary => owner.owningLibrary; |
| |
| /// The reference to this element based on where it is printed as a |
| /// documentation file and also the unique URL to refer to this item. |
| /// |
| /// The qualified name (for URL purposes) and the file name are the same, |
| /// of the form packageName/ClassName or packageName/ClassName.methodName. |
| /// This defines both the URL and the directory structure. |
| String get qualifiedName => packagePrefix + ownerPrefix + name; |
| |
| final TMirror mirror; |
| final bool isPrivate; |
| /// The comment text pre-resolution. We keep this around because inherited |
| /// methods need to resolve links differently from the superclass. |
| String unresolvedComment = ''; |
| |
| Indexable(TMirror mirror) |
| : this.mirror = mirror, |
| this.isPrivate = isHidden(mirror) { |
| |
| var mirrorQualifiedName = dart2js_util.qualifiedNameOf(this.mirror); |
| |
| var map = _mirrorToDocgen.putIfAbsent(mirrorQualifiedName, |
| () => new HashMap<String, Indexable>()); |
| |
| var added = false; |
| map.putIfAbsent(owner.docName, () { |
| added = true; |
| return this; |
| }); |
| |
| if (!added) { |
| throw new StateError('An indexable has already been stored for ' |
| '${owner.docName}'); |
| } |
| } |
| |
| /// Returns this object's qualified name, but following the conventions |
| /// we're using in Dartdoc, which is that library names with dots in them |
| /// have them replaced with hyphens. |
| String get docName; |
| |
| /// Converts all [foo] references in comments to <a>libraryName.foo</a>. |
| markdown.Node fixReference(String name) { |
| // Attempt the look up the whole name up in the scope. |
| String elementName = findElementInScope(name); |
| if (elementName != null) { |
| return new markdown.Element.text('a', elementName); |
| } |
| return fixComplexReference(name); |
| } |
| |
| /// Look for the specified name starting with the current member, and |
| /// progressively working outward to the current library scope. |
| String findElementInScope(String name) => |
| findElementInScopeWithPrefix(name, packagePrefix); |
| |
| /// The full docName of the owner element, appended with a '.' for this |
| /// object's name to be appended. |
| String get ownerPrefix => owner.docName != '' ? owner.docName + '.' : ''; |
| |
| /// The prefix String to refer to the package that this item is in, for URLs |
| /// and comment resolution. |
| /// |
| /// The prefix can be prepended to a qualified name to get a fully unique |
| /// name among all packages. |
| String get packagePrefix; |
| |
| /// Documentation comment with converted markdown and all links resolved. |
| String commentField; |
| |
| /// Accessor to documentation comment with markdown converted to html and all |
| /// links resolved. |
| String get comment { |
| if (commentField != null) return commentField; |
| |
| commentField = commentToHtml(); |
| if (commentField.isEmpty) { |
| commentField = getMdnComment(); |
| } |
| return commentField; |
| } |
| |
| void set comment(x) { |
| commentField = x; |
| } |
| |
| /// The simple name to refer to this item. |
| String get name => dart2js_util.nameOf(mirror); |
| |
| /// Accessor to the parent item that owns this item. |
| /// |
| /// "Owning" is defined as the object one scope-level above which this item |
| /// is defined. Ex: The owner for a top level class, would be its enclosing |
| /// library. The owner of a local variable in a method would be the enclosing |
| /// method. |
| Indexable get owner; |
| |
| /// Generates MDN comments from database.json. |
| String getMdnComment(); |
| |
| /// The type of this member to be used in index.txt. |
| String get typeName; |
| |
| /// Creates a [Map] with this [Indexable]'s name and a preview comment. |
| Map get previewMap { |
| var finalMap = { 'name' : name, 'qualifiedName' : qualifiedName }; |
| var pre = preview; |
| if (pre != null) finalMap['preview'] = pre; |
| return finalMap; |
| } |
| |
| String get preview { |
| if (comment != '') { |
| var index = comment.indexOf('</p>'); |
| return index > 0 ? |
| '${comment.substring(0, index)}</p>' : |
| '<p><i>Comment preview not available</i></p>'; |
| } |
| return null; |
| } |
| |
| /// Accessor to obtain the raw comment text for a given item, _without_ any |
| /// of the links resolved. |
| String get _commentText { |
| String commentText; |
| mirror.metadata.forEach((metadata) { |
| if (metadata is CommentInstanceMirror) { |
| CommentInstanceMirror comment = metadata; |
| if (comment.isDocComment) { |
| if (commentText == null) { |
| commentText = comment.trimmedText; |
| } else { |
| commentText = '$commentText\n${comment.trimmedText}'; |
| } |
| } |
| } |
| }); |
| return commentText; |
| } |
| |
| /// Returns any documentation comments associated with a mirror with |
| /// simple markdown converted to html. |
| /// |
| /// By default we resolve any comment references within our own scope. |
| /// However, if a method is inherited, we want the inherited comments, but |
| /// links to the subclasses's version of the methods. |
| String commentToHtml([Indexable resolvingScope]) { |
| if (resolvingScope == null) resolvingScope = this; |
| var commentText = _commentText; |
| unresolvedComment = commentText; |
| |
| commentText = commentText == null ? '' : |
| markdown.markdownToHtml(commentText.trim(), |
| linkResolver: resolvingScope.fixReference, |
| inlineSyntaxes: MARKDOWN_SYNTAXES); |
| return commentText; |
| } |
| |
| /// Return a map representation of this type. |
| Map toMap(); |
| |
| /// Accessor to determine if this item and all of its owners are visible. |
| bool get isVisible => isFullChainVisible(this); |
| |
| /// Returns true if [mirror] is the correct type of mirror that this Docgen |
| /// object wraps. (Workaround for the fact that Types are not first class.) |
| bool isValidMirror(DeclarationMirror mirror); |
| } |
| |
| /// Index of all the dart2js mirrors examined to corresponding MirrorBased |
| /// docgen objects. |
| /// |
| /// Used for lookup because of the dart2js mirrors exports |
| /// issue. The second level map is indexed by owner docName for faster lookup. |
| /// Why two levels of lookup? Speed, man. Speed. |
| final Map<String, Map<String, Indexable>> _mirrorToDocgen = |
| new HashMap<String, Map<String, Indexable>>(); |
| |
| Iterable<Indexable> get allIndexables => |
| _mirrorToDocgen.values.expand((map) => map.values); |
| |
| Map<String, Indexable> lookupIndexableMap(DeclarationMirror mirror) { |
| return _mirrorToDocgen[dart2js_util.qualifiedNameOf(mirror)]; |
| } |