| OLD | NEW |
| 1 // Copyright (c) 2013, 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 |
| 1 library apidoc_model; | 5 library apidoc_model; |
| 2 | 6 |
| 3 import 'package:web_ui/watcher.dart' as watchers; | 7 import 'package:web_ui/watcher.dart' as watchers; |
| 4 import 'package:web_ui/safe_html.dart'; | 8 import 'package:web_ui/safe_html.dart'; |
| 5 import 'markdown.dart' as md; | 9 import 'markdown.dart' as md; |
| 6 import 'dart:html' as html; | 10 import 'dart:html' as html; |
| 7 import 'dart:json'; | 11 import 'dart:json'; |
| 8 import 'ast.dart'; | 12 import 'ast.dart'; |
| 9 | 13 |
| 14 // TODO(jacobr): specify the version # in the JSON file. |
| 10 String svnRevisionNumber = "15605"; | 15 String svnRevisionNumber = "15605"; |
| 11 | 16 |
| 12 String _activeReferenceId; | 17 /** |
| 18 * Reference id of [currentElement]. |
| 19 * |
| 20 * Stored in addition to [currentElement] as [currentElement] may |
| 21 * not yet be available if the data model for the library it is part of has |
| 22 * not yet been loaded. |
| 23 */ |
| 24 String _currentReferenceId; |
| 13 | 25 |
| 14 /// Current state of the application. | 26 /** |
| 27 * Current library the user is browsing if any. |
| 28 */ |
| 15 LibraryElement currentLibrary; | 29 LibraryElement currentLibrary; |
| 30 |
| 31 /** |
| 32 * Current type the user is viewing if any. |
| 33 * Should be either a [ClassElement] or a [TypedefElement]. |
| 34 */ |
| 16 Element currentType; | 35 Element currentType; |
| 36 /** |
| 37 * Current member of either [currentLibrary] or [currentType] that the user is |
| 38 * viewing. |
| 39 */ |
| 17 Element currentMember; | 40 Element currentMember; |
| 41 |
| 42 /** |
| 43 * Element corresponding to [_currentReferenceId]. |
| 44 * The most specific element of [currentLibrary]. [currentType], and |
| 45 * [currentMember]. |
| 46 */ |
| 18 Element currentElement; | 47 Element currentElement; |
| 19 | 48 |
| 49 /** |
| 50 * Recomputes the Elements part of the current active state from the data model. |
| 51 * |
| 52 * This method should be invoked after additional libraries are loaded from the |
| 53 * server or after the user navigates to a different element in the UI. |
| 54 */ |
| 20 void _recomputeActiveState() { | 55 void _recomputeActiveState() { |
| 21 currentLibrary = null; | 56 currentLibrary = null; |
| 22 currentType = null; | 57 currentType = null; |
| 23 currentMember = null; | 58 currentMember = null; |
| 24 currentElement = null; | 59 currentElement = null; |
| 25 if (_activeReferenceId != null) { | 60 if (_currentReferenceId != null) { |
| 26 var path = lookupReferenceId(_activeReferenceId).path; | 61 var referenceElement = lookupReferenceId(_currentReferenceId); |
| 27 | 62 if (referenceElement != null) { |
| 28 if (path.length > 0) { | 63 var path = referenceElement.path; |
| 29 currentLibrary = path[0]; | 64 if (path.length > 0) { |
| 30 } | 65 currentLibrary = path[0]; |
| 31 if (path.length > 1) { | 66 } |
| 32 if (path[1] is ClassElement || path[1] is TypedefElement) { | 67 if (path.length > 1) { |
| 33 currentType = path[1]; | 68 if (path[1] is ClassElement || path[1] is TypedefElement) { |
| 34 if (path.length > 2) { | 69 currentType = path[1]; |
| 35 currentMember = path[2]; | 70 if (path.length > 2) { |
| 71 currentMember = path[2]; |
| 72 } |
| 73 } else { |
| 74 currentMember = path[1]; |
| 36 } | 75 } |
| 76 } |
| 77 if (currentMember != null) { |
| 78 currentElement = currentMember; |
| 79 } else if (currentType != null) { |
| 80 currentElement = currentType; |
| 37 } else { | 81 } else { |
| 38 currentMember = path[1]; | 82 currentElement = currentLibrary; |
| 39 } | 83 } |
| 40 } | 84 } |
| 41 if (currentMember != null) { | |
| 42 currentElement = currentMember; | |
| 43 } else if (currentType != null) { | |
| 44 currentElement = currentType; | |
| 45 } else { | |
| 46 currentElement = currentLibrary; | |
| 47 } | |
| 48 } | 85 } |
| 49 } | 86 } |
| 50 | 87 |
| 88 /** |
| 89 * Scrolls the [currentElement] into view. |
| 90 */ |
| 51 void scrollIntoView() { | 91 void scrollIntoView() { |
| 52 // TODO(jacobr): there should be a cleaner way to run code that executes | 92 // TODO(jacobr): there should be a cleaner way to run code that executes |
| 53 // after the UI updates. | 93 // after the UI updates. https://github.com/dart-lang/web-ui/issues/188 |
| 54 html.window.setTimeout(() { | 94 html.window.setTimeout(() { |
| 55 if (currentElement != null) { | 95 if (currentElement != null) { |
| 56 for (var e in html.queryAll('[data-id="${currentElement.id}"]')) { | 96 for (var e in html.queryAll('[data-id="${currentElement.id}"]')) { |
| 57 e.scrollIntoView(false); | 97 e.scrollIntoView(false); |
| 58 } | 98 } |
| 59 } | 99 } |
| 60 }, 0); | 100 }, 0); |
| 61 } | 101 } |
| 62 | 102 |
| 63 onDataModelChanged() { | 103 /** |
| 104 * Invoke every time the data model changes. |
| 105 */ |
| 106 void _onDataModelChanged() { |
| 64 _recomputeActiveState(); | 107 _recomputeActiveState(); |
| 65 scrollIntoView(); | 108 scrollIntoView(); |
| 66 } | 109 } |
| 67 | 110 |
| 68 /** | 111 /** |
| 69 * Generate a CSS class given an element that may be a class, member, method, | 112 * Generate a CSS class given an element that may be a class, member, method, |
| 70 * etc. | 113 * etc. |
| 71 */ | 114 */ |
| 72 String kindCssClass(Element element) { | 115 String kindCssClass(Element element) { |
| 73 String classes = 'kind kind-${normalizedKind(element)}'; | 116 String classes = 'kind kind-${_normalizedKind(element)}'; |
| 74 if (element.isPrivate == true) { | 117 if (element.isPrivate == true) { |
| 75 classes = '$classes private'; | 118 classes = '$classes private'; |
| 76 } else if (element is MethodElementBase && element.isStatic) { | 119 } else if (element is MethodLikeElement && element.isStatic) { |
| 77 classes = '$classes static'; | 120 classes = '$classes static'; |
| 78 } | 121 } |
| 79 | 122 |
| 80 // Setters are viewed as methods by the AST. | 123 // Setters are viewed as methods by the AST. |
| 81 if (element is PropertyElement) { | 124 if (element is PropertyElement) { |
| 82 classes = '$classes getter'; | 125 classes = '$classes getter'; |
| 83 } | 126 } |
| 84 | 127 |
| 85 if (element is MethodElementBase && element.isSetter) { | 128 if (element is MethodLikeElement && element.isSetter) { |
| 86 classes = '$classes setter'; | 129 classes = '$classes setter'; |
| 87 } | 130 } |
| 88 | 131 |
| 89 return classes; | 132 return classes; |
| 90 } | 133 } |
| 91 | 134 |
| 92 String normalizedKind(obj) { | 135 String _normalizedKind(Element element) { |
| 93 if (obj is Element) return normalizedKindFromElement(obj); | |
| 94 return obj; | |
| 95 } | |
| 96 | |
| 97 String normalizedKindFromElement(Element element) { | |
| 98 var kind = element.kind; | 136 var kind = element.kind; |
| 99 var name = element.name; | 137 var name = element.name; |
| 100 if (kind == 'method' && element.isOperator) { | 138 if (kind == 'method' && element.isOperator) { |
| 101 kind = 'operator'; | 139 kind = 'operator'; |
| 102 } | 140 } |
| 103 // TODO(jacobr): this is horrible but matches what DartDoc does | 141 // TODO(jacobr): this is horrible but matches what DartDoc does |
| 104 if (kind == 'class' && name.endsWith('Exception')) { | 142 if (kind == 'class' && name.endsWith('Exception')) { |
| 105 kind = 'exception'; | 143 kind = 'exception'; |
| 106 } | 144 } |
| 107 return kind; | 145 return kind; |
| 108 } | 146 } |
| 109 | 147 |
| 110 String toUserVisibleKind(Element element) { | 148 String toUserVisibleKind(Element element) { |
| 111 return KIND_TITLES[normalizedKind(element)]; | 149 return KIND_TITLES[_normalizedKind(element)]; |
| 112 } | 150 } |
| 113 | 151 |
| 114 /** | 152 /** |
| 115 * [obj] shoudl be a [Reference] or [Element]. | 153 * [obj] shoudl be a [Reference] or [Element]. |
| 116 */ | 154 */ |
| 117 String permalink(var obj) { | 155 String permalink(var obj) { |
| 118 var data = {'id': obj.refId}; | 156 var data = {'id': obj.refId}; |
| 157 // TODO(jacobr): evaluate whether the persistent UI state will stay just a |
| 158 // single reference ID in which case this is overkill. |
| 119 return "#!${JSON.stringify(data)}"; | 159 return "#!${JSON.stringify(data)}"; |
| 120 } | 160 } |
| 121 | 161 |
| 122 void loadStateFromUrl() { | 162 void loadStateFromUrl() { |
| 123 String link = html.window.location.hash; | 163 String link = html.window.location.hash; |
| 124 var data = {}; | 164 var data = {}; |
| 125 if (link.length > 2) { | 165 if (link.length > 2) { |
| 126 try { | 166 try { |
| 127 // strip #! and parse json. | 167 // strip #! and parse json. |
| 128 data = JSON.parse(link.substring(2)); | 168 data = JSON.parse(link.substring(2)); |
| 129 } catch (e) { | 169 } catch (e) { |
| 130 html.window.console.error("Invalid link url"); | 170 html.window.console.error("Invalid link url"); |
| 131 // TODO(jacobr): redirect to default page or better yet attempt to fixup. | 171 // TODO(jacobr): redirect to default page or better yet attempt to fixup. |
| 132 } | 172 } |
| 133 } | 173 } |
| 134 _activeReferenceId = data['id']; | 174 _currentReferenceId = data['id']; |
| 135 _recomputeActiveState(); | 175 _recomputeActiveState(); |
| 136 scrollIntoView(); | 176 scrollIntoView(); |
| 137 } | 177 } |
| 138 | 178 |
| 139 Future loadModel() { | 179 Future loadModel() { |
| 140 html.window.on.popState.add((e) { | 180 // Note: listen on both popState and hashChange, because IE9 doens't support |
| 181 // history API, and it doesn't work properly on Opera 12. |
| 182 // See http://dartbug.com/5483 |
| 183 updateState(e) { |
| 141 loadStateFromUrl(); | 184 loadStateFromUrl(); |
| 142 watchers.dispatch(); | 185 watchers.dispatch(); |
| 143 }); | 186 } |
| 187 html.window.on |
| 188 ..popState.add(updateState) |
| 189 ..hashChange.add(updateState); |
| 144 | 190 |
| 145 // Patch in support for [:...:]-style code to the markdown parser. | 191 // Patch in support for [:...:]-style code to the markdown parser. |
| 146 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? | 192 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? |
| 147 md.InlineParser.syntaxes.insertRange(0, 1, | 193 md.InlineParser.syntaxes.insertRange(0, 1, |
| 148 new md.CodeSyntax(r'\[\:((?:.|\n)*?)\:\]')); | 194 new md.CodeSyntax(r'\[\:((?:.|\n)*?)\:\]')); |
| 149 | 195 |
| 150 md.setImplicitLinkResolver(_resolveNameReference); | 196 md.setImplicitLinkResolver(_resolveNameReference); |
| 151 var completer = new Completer(); | 197 var completer = new Completer(); |
| 152 // TODO(jacobr): shouldn't have to get this from the parent directory. | 198 // TODO(jacobr): shouldn't have to get this from the parent directory. |
| 153 new html.HttpRequest.get('../static/apidoc.json', onSuccess(html.HttpRequest r
eq) { | 199 new html.HttpRequest.get('../static/apidoc.json', (req) { |
| 154 for (var libraryJson in JSON.parse(req.responseText)) { | 200 for (var libraryJson in JSON.parse(req.responseText)) { |
| 155 var library = new LibraryElement(libraryJson, null); | 201 var library = new LibraryElement(libraryJson, null); |
| 156 libraries[library.id] = library; | 202 libraries[library.id] = library; |
| 157 } | 203 } |
| 158 onDataModelChanged(); | 204 _onDataModelChanged(); |
| 159 completer.complete(true); | 205 completer.complete(true); |
| 160 }); | 206 }); |
| 161 return completer.future; | 207 return completer.future; |
| 162 } | 208 } |
| 163 | 209 |
| 164 /** XXX NOT USED TODO(jacobr) remove. | 210 // TODO(jacobr): remove this method and resolve refences to types in the json |
| 165 class DocComment { | 211 // generation. That way the existing correct logic in Dart2Js can be used rather |
| 166 final String text; | 212 // than this rather busted logic. |
| 167 | |
| 168 /** | |
| 169 * Non-null if the comment is inherited from another declaration. | |
| 170 */ | |
| 171 final inheritedFrom; // InterfaceMirror? | |
| 172 | |
| 173 DocComment(this.text, [this.inheritedFrom = null]) { | |
| 174 assert(text != null && !text.trim().isEmpty); | |
| 175 } | |
| 176 | |
| 177 SafeHtml get html => new SafeHtml.unsafe(md.markdownToHtml(text)); | |
| 178 | |
| 179 String toString() => text; | |
| 180 } | |
| 181 | |
| 182 */ | |
| 183 | |
| 184 /** | 213 /** |
| 185 * This will be called whenever a doc comment hits a `[name]` in square | 214 * This will be called whenever a doc comment hits a `[name]` in square |
| 186 * brackets. It will try to figure out what the name refers to and link or | 215 * brackets. It will try to figure out what the name refers to and link or |
| 187 * style it appropriately. | 216 * style it appropriately. |
| 188 */ | 217 */ |
| 189 md.Node _resolveNameReference(String name) { | 218 md.Node _resolveNameReference(String name) { |
| 190 // TODO(jacobr): this isn't right yet and we have made this code quite ugly | 219 // TODO(jacobr): this isn't right yet and we have made this code quite ugly |
| 191 // by using the verbose universal permalink member even though library is | 220 // by using the verbose universal permalink member even though library is |
| 192 // always currentLibrary. | 221 // always currentLibrary. |
| 193 makeLink(String href) { | 222 makeLink(String href) { |
| 194 return new md.Element.text('a', name) | 223 return new md.Element.text('a', name) |
| 195 ..attributes['href'] = href | 224 ..attributes['href'] = href |
| 196 ..attributes['class'] = 'crossref'; | 225 ..attributes['class'] = 'crossref'; |
| 197 } | 226 } |
| 198 | 227 |
| 199 // See if it's a parameter of the current method. | 228 // See if it's a parameter of the current method. |
| 200 if (currentMember != null && currentMember.kind == 'method') { | 229 if (currentMember != null && currentMember.kind == 'method') { |
| 201 var parameters = currentMember.children; | 230 var parameters = currentMember.children; |
| 202 for (final parameter in parameters) { | 231 for (final parameter in parameters) { |
| 203 if (parameter.name == name) { | 232 if (parameter.name == name) { |
| 204 final element = new md.Element.text('span', name); | 233 final element = new md.Element.text('span', name); |
| 205 element.attributes['class'] = 'param'; | 234 element.attributes['class'] = 'param'; |
| 206 return element; | 235 return element; |
| 207 } | 236 } |
| 208 } | 237 } |
| 209 } | 238 } |
| 210 | 239 |
| 211 // See if it's another member of the current type. | 240 // See if it's another member of the current type. |
| 212 // TODO(jacobr): fixme. this is wrong... members are by id now not simple stri
ng name... | 241 // TODO(jacobr): fixme. this is wrong... members are by id now not simple |
| 242 // string name... |
| 213 if (currentType != null) { | 243 if (currentType != null) { |
| 214 final foundMember = currentType.members[name]; | 244 // TODO(jacobr): this should be foundMember = currentType.members[name]; |
| 245 final foundMember = null; |
| 215 if (foundMember != null) { | 246 if (foundMember != null) { |
| 216 return makeLink(permalink(foundMember)); | 247 return makeLink(permalink(foundMember)); |
| 217 } | 248 } |
| 218 } | 249 } |
| 219 | 250 |
| 220 // See if it's another type or a member of another type in the current | 251 // See if it's another type or a member of another type in the current |
| 221 // library. | 252 // library. |
| 222 if (currentLibrary != null) { | 253 if (currentLibrary != null) { |
| 223 // See if it's a constructor | 254 // See if it's a constructor |
| 224 final constructorLink = (() { | 255 final constructorLink = (() { |
| (...skipping 11 matching lines...) Expand all Loading... |
| 236 return makeLink(permalink(constructor)); | 267 return makeLink(permalink(constructor)); |
| 237 })(); | 268 })(); |
| 238 if (constructorLink != null) return constructorLink; | 269 if (constructorLink != null) return constructorLink; |
| 239 | 270 |
| 240 // See if it's a member of another type | 271 // See if it's a member of another type |
| 241 final foreignMemberLink = (() { | 272 final foreignMemberLink = (() { |
| 242 final match = new RegExp(r'([\w$]+)\.([\w$]+)').firstMatch(name); | 273 final match = new RegExp(r'([\w$]+)\.([\w$]+)').firstMatch(name); |
| 243 if (match == null) return null; | 274 if (match == null) return null; |
| 244 var foundType = currentLibrary.classes[match[1]]; | 275 var foundType = currentLibrary.classes[match[1]]; |
| 245 if (foundType == null) return null; | 276 if (foundType == null) return null; |
| 246 var foundMember = foundType.members[match[2]]; | 277 // TODO(jacobr): should be foundMember = foundType.members[match[2]]; |
| 278 var foundMember = null; |
| 247 if (foundMember == null) return null; | 279 if (foundMember == null) return null; |
| 248 return makeLink(permalink(foundMember)); | 280 return makeLink(permalink(foundMember)); |
| 249 })(); | 281 })(); |
| 250 if (foreignMemberLink != null) return foreignMemberLink; | 282 if (foreignMemberLink != null) return foreignMemberLink; |
| 251 | 283 |
| 252 var foundType = currentLibrary.classes[name]; | 284 var foundType = currentLibrary.classes[name]; |
| 253 if (foundType != null) { | 285 if (foundType != null) { |
| 254 return makeLink(permalink(foundType)); | 286 return makeLink(permalink(foundType)); |
| 255 } | 287 } |
| 256 | 288 |
| 257 // See if it's a top-level member in the current library. | 289 // See if it's a top-level member in the current library. |
| 258 var foundMember = currentLibrary.members[name]; | 290 // TODO(jacobr): should be foundMember = currentLibrary.members[name]; |
| 291 var foundMember = null; |
| 259 if (foundMember != null) { | 292 if (foundMember != null) { |
| 260 return makeLink(permalink(foundMember)); | 293 return makeLink(permalink(foundMember)); |
| 261 } | 294 } |
| 262 } | 295 } |
| 263 | 296 |
| 264 // TODO(jacobr): Should also consider: | 297 // TODO(jacobr): Should also consider: |
| 265 // * Names imported by libraries this library imports. Don't think we even | 298 // * Names imported by libraries this library imports. Don't think we even |
| 266 // store this in the AST. | 299 // store this in the AST. |
| 267 // * Type parameters of the enclosing type. | 300 // * Type parameters of the enclosing type. |
| 268 | 301 |
| 269 return new md.Element.text('code', name); | 302 return new md.Element.text('code', name); |
| 270 } | 303 } |
| OLD | NEW |