| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, 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 docgen.library_helpers; | |
| 6 | |
| 7 import 'package:logging/logging.dart'; | |
| 8 import 'package:markdown/markdown.dart' as markdown; | |
| 9 | |
| 10 import 'exports/source_mirrors.dart'; | |
| 11 import 'exports/mirrors_util.dart' as dart2js_util; | |
| 12 | |
| 13 import 'models/indexable.dart'; | |
| 14 import 'models/library.dart'; | |
| 15 import 'models/dummy_mirror.dart'; | |
| 16 | |
| 17 typedef DeclarationMirror LookupFunction(DeclarationSourceMirror declaration, | |
| 18 String name); | |
| 19 | |
| 20 /// Support for [:foo:]-style code comments to the markdown parser. | |
| 21 final List<markdown.InlineSyntax> MARKDOWN_SYNTAXES = | |
| 22 [new markdown.CodeSyntax(r'\[:\s?((?:.|\n)*?)\s?:\]')]; | |
| 23 | |
| 24 bool get includePrivateMembers { | |
| 25 if (_includePrivate == null) { | |
| 26 throw new StateError('includePrivate has not been set'); | |
| 27 } | |
| 28 return _includePrivate; | |
| 29 } | |
| 30 | |
| 31 void set includePrivateMembers(bool value) { | |
| 32 if (value == null) throw new ArgumentError('includePrivate cannot be null'); | |
| 33 _includePrivate = value; | |
| 34 } | |
| 35 | |
| 36 bool _includePrivate; | |
| 37 | |
| 38 /// Return true if this item and all of its owners are all visible. | |
| 39 bool isFullChainVisible(Indexable item) { | |
| 40 return includePrivateMembers || (!item.isPrivate && (item.owner != null ? | |
| 41 isFullChainVisible(item.owner) : true)); | |
| 42 } | |
| 43 | |
| 44 /// Logger for printing out progress of documentation generation. | |
| 45 final Logger logger = new Logger('Docgen'); | |
| 46 | |
| 47 /// The dart:core library, which contains all types that are always available | |
| 48 /// without import. | |
| 49 Library coreLibrary; | |
| 50 | |
| 51 /// Set of libraries declared in the SDK, so libraries that can be accessed | |
| 52 /// when running dart by default. | |
| 53 Iterable<LibraryMirror> get sdkLibraries => _sdkLibraries; | |
| 54 Iterable<LibraryMirror> _sdkLibraries; | |
| 55 | |
| 56 ////// Top level resolution functions | |
| 57 /// Converts all [foo] references in comments to <a>libraryName.foo</a>. | |
| 58 markdown.Node globalFixReference(String name) { | |
| 59 // Attempt the look up the whole name up in the scope. | |
| 60 String elementName = findElementInScopeWithPrefix(name, ''); | |
| 61 if (elementName != null) { | |
| 62 return new markdown.Element.text('a', elementName); | |
| 63 } | |
| 64 return fixComplexReference(name); | |
| 65 } | |
| 66 | |
| 67 /// This is a more complex reference. Try to break up if its of the form A<B> | |
| 68 /// where A is an alphanumeric string and B is an A, a list of B ("B, B, B"), | |
| 69 /// or of the form A<B>. Note: unlike other the other markdown-style links, | |
| 70 /// all text inside the square brackets is treated as part of the link (aka | |
| 71 /// the * is interpreted literally as a *, not as a indicator for bold <em>. | |
| 72 /// | |
| 73 /// Example: [foo<_bar_>] will produce | |
| 74 /// <a>resolvedFoo</a><<a>resolved_bar_</a>> rather than an italicized | |
| 75 /// version of resolvedBar. | |
| 76 markdown.Node fixComplexReference(String name) { | |
| 77 // Parse into multiple elements we can try to resolve. | |
| 78 var tokens = _tokenizeComplexReference(name); | |
| 79 | |
| 80 // Produce an html representation of our elements. Group unresolved and | |
| 81 // plain text are grouped into "link" elements so they display as code. | |
| 82 final textElements = [' ', ',', '>', _LESS_THAN]; | |
| 83 var accumulatedHtml = ''; | |
| 84 | |
| 85 for (var token in tokens) { | |
| 86 bool added = false; | |
| 87 if (!textElements.contains(token)) { | |
| 88 String elementName = findElementInScopeWithPrefix(token, ''); | |
| 89 if (elementName != null) { | |
| 90 accumulatedHtml += markdown.renderToHtml([new markdown.Element.text('a', | |
| 91 elementName)]); | |
| 92 added = true; | |
| 93 } | |
| 94 } | |
| 95 if (!added) { | |
| 96 accumulatedHtml += token; | |
| 97 } | |
| 98 } | |
| 99 return new markdown.Text(accumulatedHtml); | |
| 100 } | |
| 101 | |
| 102 String findElementInScopeWithPrefix(String name, String packagePrefix) { | |
| 103 var lookupFunc = determineLookupFunc(name); | |
| 104 // Look in the dart core library scope. | |
| 105 var coreScope = coreLibrary == null ? null : lookupFunc(coreLibrary.mirror, | |
| 106 name); | |
| 107 if (coreScope != null) return packagePrefix + coreLibrary.docName; | |
| 108 | |
| 109 // If it's a reference that starts with a another library name, then it | |
| 110 // looks for a match of that library name in the other sdk libraries. | |
| 111 if (name.contains('.')) { | |
| 112 var index = name.indexOf('.'); | |
| 113 var libraryName = name.substring(0, index); | |
| 114 var remainingName = name.substring(index + 1); | |
| 115 foundLibraryName(library) => library.uri.pathSegments[0] == libraryName; | |
| 116 | |
| 117 if (_sdkLibraries.any(foundLibraryName)) { | |
| 118 var library = _sdkLibraries.singleWhere(foundLibraryName); | |
| 119 // Look to see if it's a fully qualified library name. | |
| 120 var scope = determineLookupFunc(remainingName)(library, remainingName); | |
| 121 if (scope != null) { | |
| 122 var result = getDocgenObject(scope); | |
| 123 if (result is DummyMirror) { | |
| 124 return packagePrefix + result.docName; | |
| 125 } else { | |
| 126 return result.packagePrefix + result.docName; | |
| 127 } | |
| 128 } | |
| 129 } | |
| 130 } | |
| 131 return null; | |
| 132 } | |
| 133 | |
| 134 /// Given a Dart2jsMirror, find the corresponding Docgen [MirrorBased] object. | |
| 135 /// | |
| 136 /// We have this global lookup function to avoid re-implementing looking up | |
| 137 /// the scoping rules for comment resolution here (it is currently done in | |
| 138 /// mirrors). If no corresponding MirrorBased object is found, we return a | |
| 139 /// [DummyMirror] that simply returns the original mirror's qualifiedName | |
| 140 /// while behaving like a MirrorBased object. | |
| 141 Indexable getDocgenObject(DeclarationMirror mirror, [Indexable owner]) { | |
| 142 Map<String, Indexable> docgenObj = lookupIndexableMap(mirror); | |
| 143 | |
| 144 if (docgenObj == null) { | |
| 145 return new DummyMirror(mirror, owner); | |
| 146 } | |
| 147 | |
| 148 var setToExamine = new Set(); | |
| 149 if (owner != null) { | |
| 150 var firstSet = docgenObj[owner.docName]; | |
| 151 if (firstSet != null) setToExamine.add(firstSet); | |
| 152 if (coreLibrary != null && docgenObj[coreLibrary.docName] != null) { | |
| 153 setToExamine.add(docgenObj[coreLibrary.docName]); | |
| 154 } | |
| 155 } else { | |
| 156 setToExamine.addAll(docgenObj.values); | |
| 157 } | |
| 158 | |
| 159 Set<Indexable> results = new Set<Indexable>(); | |
| 160 for (Indexable indexable in setToExamine) { | |
| 161 if (indexable.mirror.qualifiedName == mirror.qualifiedName && | |
| 162 indexable.isValidMirror(mirror)) { | |
| 163 results.add(indexable); | |
| 164 } | |
| 165 } | |
| 166 | |
| 167 if (results.length > 0) { | |
| 168 // This might occur if we didn't specify an "owner." | |
| 169 return results.first; | |
| 170 } | |
| 171 return new DummyMirror(mirror, owner); | |
| 172 } | |
| 173 | |
| 174 void initializeTopLevelLibraries(MirrorSystem mirrorSystem) { | |
| 175 _sdkLibraries = mirrorSystem.libraries.values.where( | |
| 176 (each) => each.uri.scheme == 'dart'); | |
| 177 coreLibrary = new Library(_sdkLibraries.singleWhere((lib) => | |
| 178 lib.uri.toString().startsWith('dart:core'))); | |
| 179 } | |
| 180 | |
| 181 /// For a given name, determine if we need to resolve it as a qualified name | |
| 182 /// or a simple name in the source mirors. | |
| 183 LookupFunction determineLookupFunc(String name) => name.contains('.') ? | |
| 184 dart2js_util.lookupQualifiedInScope : | |
| 185 (mirror, name) => mirror.lookupInScope(name); | |
| 186 | |
| 187 /// Chunk the provided name into individual parts to be resolved. We take a | |
| 188 /// simplistic approach to chunking, though, we break at " ", ",", "<" | |
| 189 /// and ">". All other characters are grouped into the name to be resolved. | |
| 190 /// As a result, these characters will all be treated as part of the item to | |
| 191 /// be resolved (aka the * is interpreted literally as a *, not as an | |
| 192 /// indicator for bold <em>. | |
| 193 List<String> _tokenizeComplexReference(String name) { | |
| 194 var tokens = []; | |
| 195 var append = false; | |
| 196 var index = 0; | |
| 197 while (index < name.length) { | |
| 198 if (name.indexOf(_LESS_THAN, index) == index) { | |
| 199 tokens.add(_LESS_THAN); | |
| 200 append = false; | |
| 201 index += _LESS_THAN.length; | |
| 202 } else if (name[index] == ' ' || name[index] == ',' || name[index] == '>') { | |
| 203 tokens.add(name[index]); | |
| 204 append = false; | |
| 205 index++; | |
| 206 } else { | |
| 207 if (append) { | |
| 208 tokens[tokens.length - 1] = tokens.last + name[index]; | |
| 209 } else { | |
| 210 tokens.add(name[index]); | |
| 211 append = true; | |
| 212 } | |
| 213 index++; | |
| 214 } | |
| 215 } | |
| 216 return tokens; | |
| 217 } | |
| 218 | |
| 219 // HTML escaped version of '<' character. | |
| 220 const _LESS_THAN = '<'; | |
| OLD | NEW |