Index: utils/dartdoc/dartdoc.dart |
diff --git a/utils/dartdoc/dartdoc.dart b/utils/dartdoc/dartdoc.dart |
index a77fb1ae3898baa7474a53c521dbb1afb548b8f5..91a97ef092542eded879c65932fe4e03316d0d90 100644 |
--- a/utils/dartdoc/dartdoc.dart |
+++ b/utils/dartdoc/dartdoc.dart |
@@ -21,6 +21,7 @@ |
#import('../markdown/lib.dart', prefix: 'md'); |
#source('classify.dart'); |
+#source('comment_map.dart'); |
#source('files.dart'); |
#source('utils.dart'); |
@@ -30,63 +31,19 @@ final corePath = 'lib'; |
/** Path to generate html files into. */ |
final outdir = 'docs'; |
-/** Set to `false` to not include the source code in the generated docs. */ |
-bool includeSource = true; |
- |
FileSystem files; |
-/** Special comment position used to store the library-level doc comment. */ |
-final _libraryDoc = -1; |
- |
-/** The library that we're currently generating docs for. */ |
-Library _currentLibrary; |
- |
-/** The type that we're currently generating docs for. */ |
-Type _currentType; |
- |
-/** The member that we're currently generating docs for. */ |
-Member _currentMember; |
- |
-/** |
- * The cached lookup-table to associate doc comments with spans. The outer map |
- * is from filenames to doc comments in that file. The inner map maps from the |
- * token positions to doc comments. Each position is the starting offset of the |
- * next non-comment token *following* the doc comment. For example, the position |
- * for this comment would be the position of the "Map" token below. |
- */ |
-Map<String, Map<int, String>> _comments; |
- |
-/** A callback that returns additional Markdown documentation for a type. */ |
-typedef String TypeDocumenter(Type type); |
- |
-/** A list of callbacks registered for documenting types. */ |
-List<TypeDocumenter> _typeDocumenters; |
- |
-/** A callback that returns additional Markdown documentation for a method. */ |
-typedef String MethodDocumenter(MethodMember method); |
- |
-/** A list of callbacks registered for documenting methods. */ |
-List<MethodDocumenter> _methodDocumenters; |
- |
-/** A callback that returns additional Markdown documentation for a field. */ |
-typedef String FieldDocumenter(FieldMember field); |
- |
-/** A list of callbacks registered for documenting fields. */ |
-List<FieldDocumenter> _fieldDocumenters; |
- |
-int _totalLibraries = 0; |
-int _totalTypes = 0; |
-int _totalMembers = 0; |
- |
/** |
* Run this from the `utils/dartdoc` directory. |
*/ |
void main() { |
// The entrypoint of the library to generate docs for. |
- final entrypoint = process.argv[2]; |
+ final entrypoint = process.argv[process.argv.length - 1]; |
// Parse the dartdoc options. |
- for (int i = 3; i < process.argv.length; i++) { |
+ bool includeSource = true; |
+ |
+ for (int i = 2; i < process.argv.length - 1; i++) { |
final arg = process.argv[i]; |
switch (arg) { |
case '--no-code': |
@@ -102,811 +59,732 @@ void main() { |
parseOptions('../../frog', [] /* args */, files); |
initializeWorld(files); |
+ var dartdoc; |
final elapsed = time(() { |
- initializeDartDoc(); |
- document(entrypoint); |
+ dartdoc = new Dartdoc(); |
+ dartdoc.includeSource = includeSource; |
+ dartdoc.document(entrypoint); |
}); |
- printStats(elapsed); |
-} |
- |
-void initializeDartDoc() { |
- _comments = <Map<int, String>>{}; |
- _typeDocumenters = <TypeDocumenter>[]; |
- _methodDocumenters = <MethodDocumenter>[]; |
- _fieldDocumenters = <FieldDocumenter>[]; |
- |
- // Patch in support for [:...:]-style code to the markdown parser. |
- // TODO(rnystrom): Markdown already has syntax for this. Phase this out? |
- md.InlineParser.syntaxes.insertRange(0, 1, |
- new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); |
- |
- md.setImplicitLinkResolver(resolveNameReference); |
+ print('Documented ${dartdoc._totalLibraries} libraries, ' + |
+ '${dartdoc._totalTypes} types, and ' + |
+ '${dartdoc._totalMembers} members in ${elapsed}msec.'); |
} |
-document(String entrypoint) { |
- try { |
- var oldDietParse = options.dietParse; |
- options.dietParse = true; |
- |
- // Handle the built-in entrypoints. |
- switch (entrypoint) { |
- case 'corelib': |
- world.getOrAddLibrary('dart:core'); |
- world.getOrAddLibrary('dart:coreimpl'); |
- world.getOrAddLibrary('dart:json'); |
- world.process(); |
- break; |
- |
- case 'dom': |
- world.getOrAddLibrary('dart:core'); |
- world.getOrAddLibrary('dart:coreimpl'); |
- world.getOrAddLibrary('dart:json'); |
- world.getOrAddLibrary('dart:dom'); |
- world.process(); |
- break; |
+class Dartdoc { |
+ /** Set to `false` to not include the source code in the generated docs. */ |
+ bool includeSource = true; |
+ |
+ CommentMap _comments; |
+ |
+ /** The library that we're currently generating docs for. */ |
+ Library _currentLibrary; |
+ |
+ /** The type that we're currently generating docs for. */ |
+ Type _currentType; |
+ |
+ /** The member that we're currently generating docs for. */ |
+ Member _currentMember; |
+ |
+ int _totalLibraries = 0; |
+ int _totalTypes = 0; |
+ int _totalMembers = 0; |
+ |
+ Dartdoc() |
+ : _comments = new CommentMap() { |
+ // Patch in support for [:...:]-style code to the markdown parser. |
+ // TODO(rnystrom): Markdown already has syntax for this. Phase this out? |
+ md.InlineParser.syntaxes.insertRange(0, 1, |
+ new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); |
+ |
+ md.setImplicitLinkResolver(resolveNameReference); |
+ } |
+ |
+ document(String entrypoint) { |
+ try { |
+ var oldDietParse = options.dietParse; |
+ options.dietParse = true; |
+ |
+ // Handle the built-in entrypoints. |
+ switch (entrypoint) { |
+ case 'corelib': |
+ world.getOrAddLibrary('dart:core'); |
+ world.getOrAddLibrary('dart:coreimpl'); |
+ world.getOrAddLibrary('dart:json'); |
+ world.process(); |
+ break; |
+ |
+ case 'dom': |
+ world.getOrAddLibrary('dart:core'); |
+ world.getOrAddLibrary('dart:coreimpl'); |
+ world.getOrAddLibrary('dart:json'); |
+ world.getOrAddLibrary('dart:dom'); |
+ world.process(); |
+ break; |
+ |
+ case 'html': |
+ world.getOrAddLibrary('dart:core'); |
+ world.getOrAddLibrary('dart:coreimpl'); |
+ world.getOrAddLibrary('dart:json'); |
+ world.getOrAddLibrary('dart:dom'); |
+ world.getOrAddLibrary('dart:html'); |
+ world.process(); |
+ break; |
+ |
+ default: |
+ // Normal entrypoint script. |
+ world.processDartScript(entrypoint); |
+ } |
- case 'html': |
- world.getOrAddLibrary('dart:core'); |
- world.getOrAddLibrary('dart:coreimpl'); |
- world.getOrAddLibrary('dart:json'); |
- world.getOrAddLibrary('dart:dom'); |
- world.getOrAddLibrary('dart:html'); |
- world.process(); |
- break; |
+ world.resolveAll(); |
- default: |
- // Normal entrypoint script. |
- world.processDartScript(entrypoint); |
+ // Generate the docs. |
+ docIndex(); |
+ for (final library in world.libraries.getValues()) { |
+ docLibrary(library); |
+ } |
+ } finally { |
+ options.dietParse = oldDietParse; |
} |
+ } |
- world.resolveAll(); |
- |
- // Generate the docs. |
- docIndex(); |
- for (final library in world.libraries.getValues()) { |
- docLibrary(library); |
- } |
- } finally { |
- options.dietParse = oldDietParse; |
+ writeHeader(String title) { |
+ writeln( |
+ ''' |
+ <!DOCTYPE html> |
+ <html> |
+ <head> |
+ <meta charset="utf-8"> |
+ <title>$title</title> |
+ <link rel="stylesheet" type="text/css" |
+ href="${relativePath('styles.css')}" /> |
+ <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700,800" rel="stylesheet" type="text/css"> |
+ <script src="${relativePath('interact.js')}"></script> |
+ </head> |
+ <body> |
+ <div class="page"> |
+ '''); |
+ docNavigation(); |
+ writeln('<div class="content">'); |
} |
-} |
-printStats(num elapsed) { |
- print('Documented $_totalLibraries libraries, $_totalTypes types, and ' + |
- '$_totalMembers members in ${elapsed}msec.'); |
-} |
+ writeFooter() { |
+ writeln( |
+ ''' |
+ </div> |
+ <div class="footer"</div> |
+ </body></html> |
+ '''); |
+ } |
-writeHeader(String title) { |
- writeln( |
- ''' |
- <!DOCTYPE html> |
- <html> |
- <head> |
- <meta charset="utf-8"> |
- <title>$title</title> |
- <link rel="stylesheet" type="text/css" |
- href="${relativePath('styles.css')}" /> |
- <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700,800" rel="stylesheet" type="text/css"> |
- <script src="${relativePath('interact.js')}"></script> |
- </head> |
- <body> |
- <div class="page"> |
- '''); |
- docNavigation(); |
- writeln('<div class="content">'); |
-} |
+ docIndex() { |
+ startFile('index.html'); |
-writeFooter() { |
- writeln( |
- ''' |
- </div> |
- <div class="footer"</div> |
- </body></html> |
- '''); |
-} |
+ writeHeader('Dart Documentation'); |
-docIndex() { |
- startFile('index.html'); |
+ writeln('<h1>Dart Documentation</h1>'); |
+ writeln('<h3>Libraries</h3>'); |
- writeHeader('Dart Documentation'); |
+ for (final library in orderByName(world.libraries)) { |
+ writeln( |
+ ''' |
+ <h4>${a(libraryUrl(library), library.name)}</h4> |
+ '''); |
+ } |
- writeln('<h1>Dart Documentation</h1>'); |
- writeln('<h3>Libraries</h3>'); |
+ writeFooter(); |
+ endFile(); |
+ } |
- for (final library in orderByName(world.libraries)) { |
+ docNavigation() { |
writeln( |
''' |
- <h4>${a(libraryUrl(library), library.name)}</h4> |
+ <div class="nav"> |
+ <h1>${a("index.html", "Dart Documentation")}</h1> |
'''); |
- } |
- writeFooter(); |
- endFile(); |
-} |
- |
-docNavigation() { |
- writeln( |
- ''' |
- <div class="nav"> |
- <h1>${a("index.html", "Dart Documentation")}</h1> |
- '''); |
+ for (final library in orderByName(world.libraries)) { |
+ write('<h2><div class="icon-library"></div>'); |
- for (final library in orderByName(world.libraries)) { |
- write('<h2><div class="icon-library"></div>'); |
+ if ((_currentLibrary == library) && (_currentType == null)) { |
+ write('<strong>${library.name}</strong>'); |
+ } else { |
+ write('${a(libraryUrl(library), library.name)}'); |
+ } |
+ write('</h2>'); |
- if ((_currentLibrary == library) && (_currentType == null)) { |
- write('<strong>${library.name}</strong>'); |
- } else { |
- write('${a(libraryUrl(library), library.name)}'); |
+ // Only expand classes in navigation for current library. |
+ if (_currentLibrary == library) docLibraryNavigation(library); |
} |
- write('</h2>'); |
- // Only expand classes in navigation for current library. |
- if (_currentLibrary == library) docLibraryNavigation(library); |
+ writeln('</div>'); |
} |
- writeln('</div>'); |
-} |
- |
-/** Writes the navigation for the types contained by the given library. */ |
-docLibraryNavigation(Library library) { |
- // Show the exception types separately. |
- final types = <Type>[]; |
- final exceptions = <Type>[]; |
+ /** Writes the navigation for the types contained by the given library. */ |
+ docLibraryNavigation(Library library) { |
+ // Show the exception types separately. |
+ final types = <Type>[]; |
+ final exceptions = <Type>[]; |
- for (final type in orderByName(library.types)) { |
- if (type.isTop) continue; |
- if (type.name.startsWith('_')) continue; |
+ for (final type in orderByName(library.types)) { |
+ if (type.isTop) continue; |
+ if (type.name.startsWith('_')) continue; |
- if (type.name.endsWith('Exception')) { |
- exceptions.add(type); |
- } else { |
- types.add(type); |
+ if (type.name.endsWith('Exception')) { |
+ exceptions.add(type); |
+ } else { |
+ types.add(type); |
+ } |
} |
- } |
- if ((types.length == 0) && (exceptions.length == 0)) return; |
+ if ((types.length == 0) && (exceptions.length == 0)) return; |
- writeType(String icon, Type type) { |
- write('<li>'); |
- if (_currentType == type) { |
- write( |
- '<div class="icon-$icon"></div><strong>${typeName(type)}</strong>'); |
- } else { |
- write(a(typeUrl(type), |
- '<div class="icon-$icon"></div>${typeName(type)}')); |
+ writeType(String icon, Type type) { |
+ write('<li>'); |
+ if (_currentType == type) { |
+ write( |
+ '<div class="icon-$icon"></div><strong>${typeName(type)}</strong>'); |
+ } else { |
+ write(a(typeUrl(type), |
+ '<div class="icon-$icon"></div>${typeName(type)}')); |
+ } |
+ writeln('</li>'); |
} |
- writeln('</li>'); |
+ |
+ writeln('<ul>'); |
+ types.forEach((type) => writeType(type.isClass ? 'class' : 'interface', |
+ type)); |
+ exceptions.forEach((type) => writeType('exception', type)); |
+ writeln('</ul>'); |
} |
- writeln('<ul>'); |
- types.forEach((type) => writeType(type.isClass ? 'class' : 'interface', |
- type)); |
- exceptions.forEach((type) => writeType('exception', type)); |
- writeln('</ul>'); |
-} |
+ docLibrary(Library library) { |
+ _totalLibraries++; |
+ _currentLibrary = library; |
+ _currentType = null; |
-String _runDocumenters(var item, List<Function> documenters) => |
- Strings.join(map(documenters, (doc) => doc(item)), '\n\n'); |
+ startFile(libraryUrl(library)); |
+ writeHeader(library.name); |
+ writeln('<h1>Library <strong>${library.name}</strong></h1>'); |
-docLibrary(Library library) { |
- _totalLibraries++; |
- _currentLibrary = library; |
- _currentType = null; |
+ // Look for a comment for the entire library. |
+ final comment = _comments.findLibrary(library.baseSource); |
+ if (comment != null) { |
+ final html = md.markdownToHtml(comment); |
+ writeln('<div class="doc">$html</div>'); |
+ } |
- startFile(libraryUrl(library)); |
- writeHeader(library.name); |
- writeln('<h1>Library <strong>${library.name}</strong></h1>'); |
+ // Document the top-level members. |
+ docMembers(library.topType); |
- // Look for a comment for the entire library. |
- final comment = findCommentInFile(library.baseSource, _libraryDoc); |
- if (comment != null) { |
- final html = md.markdownToHtml(comment); |
- writeln('<div class="doc">$html</div>'); |
- } |
+ // Document the types. |
+ final classes = <Type>[]; |
+ final interfaces = <Type>[]; |
+ final exceptions = <Type>[]; |
+ |
+ for (final type in orderByName(library.types)) { |
+ if (type.isTop) continue; |
+ if (type.name.startsWith('_')) continue; |
- // Document the top-level members. |
- docMembers(library.topType); |
+ if (type.name.endsWith('Exception')) { |
+ exceptions.add(type); |
+ } else if (type.isClass) { |
+ classes.add(type); |
+ } else { |
+ interfaces.add(type); |
+ } |
+ } |
- // Document the types. |
- final classes = <Type>[]; |
- final interfaces = <Type>[]; |
- final exceptions = <Type>[]; |
+ docTypes(classes, 'Classes'); |
+ docTypes(interfaces, 'Interfaces'); |
+ docTypes(exceptions, 'Exceptions'); |
- for (final type in orderByName(library.types)) { |
- if (type.isTop) continue; |
- if (type.name.startsWith('_')) continue; |
+ writeFooter(); |
+ endFile(); |
- if (type.name.endsWith('Exception')) { |
- exceptions.add(type); |
- } else if (type.isClass) { |
- classes.add(type); |
- } else { |
- interfaces.add(type); |
+ for (final type in library.types.getValues()) { |
+ if (!type.isTop) docType(type); |
} |
} |
- docTypes(classes, 'Classes'); |
- docTypes(interfaces, 'Interfaces'); |
- docTypes(exceptions, 'Exceptions'); |
+ docTypes(List<Type> types, String header) { |
+ if (types.length == 0) return; |
- writeFooter(); |
- endFile(); |
+ writeln('<h3>$header</h3>'); |
- for (final type in library.types.getValues()) { |
- if (!type.isTop) docType(type); |
+ for (final type in types) { |
+ writeln( |
+ ''' |
+ <div class="type"> |
+ <h4> |
+ ${a(typeUrl(type), "<strong>${typeName(type)}</strong>")} |
+ </h4> |
+ </div> |
+ '''); |
+ } |
} |
-} |
-docTypes(List<Type> types, String header) { |
- if (types.length == 0) return; |
+ docType(Type type) { |
+ _totalTypes++; |
+ _currentType = type; |
- writeln('<h3>$header</h3>'); |
+ startFile(typeUrl(type)); |
- for (final type in types) { |
+ final typeTitle = |
+ '${type.isClass ? "Class" : "Interface"} ${typeName(type)}'; |
+ writeHeader('Library ${type.library.name} / $typeTitle'); |
writeln( |
''' |
- <div class="type"> |
- <h4> |
- ${a(typeUrl(type), "<strong>${typeName(type)}</strong>")} |
- </h4> |
- </div> |
+ <h1>${a(libraryUrl(type.library), |
+ "Library <strong>${type.library.name}</strong>")}</h1> |
+ <h2>${type.isClass ? "Class" : "Interface"} |
+ <strong>${typeName(type, showBounds: true)}</strong></h2> |
'''); |
- } |
-} |
-docType(Type type) { |
- _totalTypes++; |
- _currentType = type; |
- |
- startFile(typeUrl(type)); |
- |
- final typeTitle = '${type.isClass ? "Class" : "Interface"} ${typeName(type)}'; |
- writeHeader('Library ${type.library.name} / $typeTitle'); |
- writeln( |
- ''' |
- <h1>${a(libraryUrl(type.library), |
- "Library <strong>${type.library.name}</strong>")}</h1> |
- <h2>${type.isClass ? "Class" : "Interface"} |
- <strong>${typeName(type, showBounds: true)}</strong></h2> |
- '''); |
- |
- docInheritance(type); |
- docCode(type.span, _runDocumenters(type, _typeDocumenters)); |
- docConstructors(type); |
- docMembers(type); |
- |
- writeFooter(); |
- endFile(); |
-} |
- |
-void docMembers(Type type) { |
- // Collect the different kinds of members. |
- final methods = []; |
- final fields = []; |
+ docInheritance(type); |
- for (final member in orderByName(type.members)) { |
- if (member.name.startsWith('_')) continue; |
+ docCode(type.span, getTypeComment(type)); |
+ docConstructors(type); |
+ docMembers(type); |
- if (member.isProperty) { |
- if (member.canGet) methods.add(member.getter); |
- if (member.canSet) methods.add(member.setter); |
- } else if (member.isMethod) { |
- methods.add(member); |
- } else if (member.isField) { |
- fields.add(member); |
- } |
+ writeFooter(); |
+ endFile(); |
} |
- if (methods.length > 0) { |
- writeln('<h3>Methods</h3>'); |
- for (final method in methods) docMethod(type, method); |
- } |
- |
- if (fields.length > 0) { |
- writeln('<h3>Fields</h3>'); |
- for (final field in fields) docField(type, field); |
- } |
-} |
+ /** Document the superclass, superinterfaces and default class of [Type]. */ |
+ docInheritance(Type type) { |
+ final isSubclass = (type.parent != null) && !type.parent.isObject; |
-/** Document the superclass, superinterfaces and factory of [Type]. */ |
-docInheritance(Type type) { |
- final isSubclass = (type.parent != null) && !type.parent.isObject; |
- |
- Type factory; |
- if (type.definition is TypeDefinition) { |
- TypeDefinition definition = type.definition; |
- if (definition.factoryType != null) { |
- factory = definition.factoryType.type; |
+ Type defaultType; |
+ if (type.definition is TypeDefinition) { |
+ TypeDefinition definition = type.definition; |
+ if (definition.defaultType != null) { |
+ defaultType = definition.defaultType.type; |
+ } |
} |
- } |
- if (isSubclass || |
- (type.interfaces != null && type.interfaces.length > 0) || |
- (factory != null)) { |
- writeln('<p>'); |
+ if (isSubclass || |
+ (type.interfaces != null && type.interfaces.length > 0) || |
+ (defaultType != null)) { |
+ writeln('<p>'); |
- if (isSubclass) { |
- write('Extends ${typeReference(type.parent)}. '); |
- } |
+ if (isSubclass) { |
+ write('Extends ${typeReference(type.parent)}. '); |
+ } |
- if (type.interfaces != null && type.interfaces.length > 0) { |
- var interfaceStr = joinWithCommas(map(type.interfaces, typeReference)); |
- write('Implements ${interfaceStr}. '); |
- } |
+ if (type.interfaces != null && type.interfaces.length > 0) { |
+ var interfaceStr = joinWithCommas(map(type.interfaces, typeReference)); |
+ write('Implements ${interfaceStr}. '); |
+ } |
- if (factory != null) { |
- write('Has factory class ${typeReference(factory)}.'); |
+ if (defaultType != null) { |
+ write('Has default class ${typeReference(defaultType)}.'); |
+ } |
} |
} |
-} |
-/** Document the constructors for [Type], if any. */ |
-docConstructors(Type type) { |
- final names = type.constructors.getKeys().filter( |
- (name) => !name.startsWith('_')); |
+ /** Document the constructors for [Type], if any. */ |
+ docConstructors(Type type) { |
+ final names = type.constructors.getKeys().filter( |
+ (name) => !name.startsWith('_')); |
- if (names.length > 0) { |
- writeln('<h3>Constructors</h3>'); |
- names.sort((x, y) => x.toUpperCase().compareTo(y.toUpperCase())); |
+ if (names.length > 0) { |
+ writeln('<h3>Constructors</h3>'); |
+ names.sort((x, y) => x.toUpperCase().compareTo(y.toUpperCase())); |
- for (final name in names) { |
- docMethod(type, type.constructors[name], constructorName: name); |
+ for (final name in names) { |
+ docMethod(type, type.constructors[name], constructorName: name); |
+ } |
} |
} |
-} |
- |
-/** |
- * Documents the [method] in type [type]. Handles all kinds of methods |
- * including getters, setters, and constructors. |
- */ |
-docMethod(Type type, MethodMember method, [String constructorName = null]) { |
- _totalMembers++; |
- _currentMember = method; |
- |
- writeln('<div class="method"><h4 id="${memberAnchor(method)}">'); |
- if (includeSource) { |
- writeln('<span class="show-code">Code</span>'); |
- } |
+ void docMembers(Type type) { |
+ // Collect the different kinds of members. |
+ final methods = []; |
+ final fields = []; |
- if (method.isStatic && !type.isTop) { |
- write('static '); |
- } |
+ for (final member in orderByName(type.members)) { |
+ if (member.name.startsWith('_')) continue; |
- if (method.isConstructor) { |
- write(method.isConst ? 'const ' : 'new '); |
- } |
+ if (member.isProperty) { |
+ if (member.canGet) methods.add(member.getter); |
+ if (member.canSet) methods.add(member.setter); |
+ } else if (member.isMethod) { |
+ methods.add(member); |
+ } else if (member.isField) { |
+ fields.add(member); |
+ } |
+ } |
- if (constructorName == null) { |
- annotateType(type, method.returnType); |
- } |
+ if (methods.length > 0) { |
+ writeln('<h3>Methods</h3>'); |
+ for (final method in methods) docMethod(type, method); |
+ } |
- // Translate specially-named methods: getters, setters, operators. |
- var name = method.name; |
- if (name.startsWith('get:')) { |
- // Getter. |
- name = 'get ${name.substring(4)}'; |
- } else if (name.startsWith('set:')) { |
- // Setter. |
- name = 'set ${name.substring(4)}'; |
- } else { |
- // See if it's an operator. |
- name = TokenKind.rawOperatorFromMethod(name); |
- if (name == null) { |
- name = method.name; |
- } else { |
- name = 'operator $name'; |
+ if (fields.length > 0) { |
+ writeln('<h3>Fields</h3>'); |
+ for (final field in fields) docField(type, field); |
} |
} |
- write('<strong>$name</strong>'); |
- |
- // Named constructors. |
- if (constructorName != null && constructorName != '') { |
- write('.'); |
- write(constructorName); |
- } |
+ /** |
+ * Documents the [method] in type [type]. Handles all kinds of methods |
+ * including getters, setters, and constructors. |
+ */ |
+ docMethod(Type type, MethodMember method, [String constructorName = null]) { |
+ _totalMembers++; |
+ _currentMember = method; |
- docParamList(type, method); |
+ writeln('<div class="method"><h4 id="${memberAnchor(method)}">'); |
- write(''' <a class="anchor-link" href="#${memberAnchor(method)}" |
- title="Permalink to ${typeName(type)}.$name">#</a>'''); |
- writeln('</h4>'); |
+ if (includeSource) { |
+ writeln('<span class="show-code">Code</span>'); |
+ } |
- docCode(method.span, _runDocumenters(method, _methodDocumenters), |
- showCode: true); |
+ if (method.isStatic && !type.isTop) { |
+ write('static '); |
+ } |
- writeln('</div>'); |
-} |
+ if (method.isConstructor) { |
+ write(method.isConst ? 'const ' : 'new '); |
+ } |
-docParamList(Type enclosingType, MethodMember member) { |
- write('('); |
- bool first = true; |
- bool inOptionals = false; |
- for (final parameter in member.parameters) { |
- if (!first) write(', '); |
+ if (constructorName == null) { |
+ annotateType(type, method.returnType); |
+ } |
- if (!inOptionals && parameter.isOptional) { |
- write('['); |
- inOptionals = true; |
+ // Translate specially-named methods: getters, setters, operators. |
+ var name = method.name; |
+ if (name.startsWith('get:')) { |
+ // Getter. |
+ name = 'get ${name.substring(4)}'; |
+ } else if (name.startsWith('set:')) { |
+ // Setter. |
+ name = 'set ${name.substring(4)}'; |
+ } else { |
+ // See if it's an operator. |
+ name = TokenKind.rawOperatorFromMethod(name); |
+ if (name == null) { |
+ name = method.name; |
+ } else { |
+ name = 'operator $name'; |
+ } |
} |
- annotateType(enclosingType, parameter.type, parameter.name); |
+ write('<strong>$name</strong>'); |
- // Show the default value for named optional parameters. |
- if (parameter.isOptional && parameter.hasDefaultValue) { |
- write(' = '); |
- // TODO(rnystrom): Using the definition text here is a bit cheap. |
- // We really should be pretty-printing the AST so that if you have: |
- // foo([arg = 1 + /* comment */ 2]) |
- // the docs should just show: |
- // foo([arg = 1 + 2]) |
- // For now, we'll assume you don't do that. |
- write(parameter.definition.value.span.text); |
+ // Named constructors. |
+ if (constructorName != null && constructorName != '') { |
+ write('.'); |
+ write(constructorName); |
} |
- first = false; |
- } |
- |
- if (inOptionals) write(']'); |
- write(')'); |
-} |
+ docParamList(type, method); |
-/** Documents the field [field] of type [type]. */ |
-docField(Type type, FieldMember field) { |
- _totalMembers++; |
- _currentMember = field; |
+ write(''' <a class="anchor-link" href="#${memberAnchor(method)}" |
+ title="Permalink to ${typeName(type)}.$name">#</a>'''); |
+ writeln('</h4>'); |
- writeln('<div class="field"><h4 id="${memberAnchor(field)}">'); |
+ docCode(method.span, getMethodComment(method), showCode: true); |
- if (includeSource) { |
- writeln('<span class="show-code">Code</span>'); |
+ writeln('</div>'); |
} |
- if (field.isStatic && !type.isTop) { |
- write('static '); |
- } |
+ /** Documents the field [field] of type [type]. */ |
+ docField(Type type, FieldMember field) { |
+ _totalMembers++; |
+ _currentMember = field; |
- if (field.isFinal) { |
- write('final '); |
- } else if (field.type.name == 'Dynamic') { |
- write('var '); |
- } |
+ writeln('<div class="field"><h4 id="${memberAnchor(field)}">'); |
- annotateType(type, field.type); |
- write( |
- ''' |
- <strong>${field.name}</strong> <a class="anchor-link" |
- href="#${memberAnchor(field)}" |
- title="Permalink to ${typeName(type)}.${field.name}">#</a> |
- </h4> |
- '''); |
- |
- docCode(field.span, _runDocumenters(field, _fieldDocumenters), |
- showCode: true); |
- writeln('</div>'); |
-} |
+ if (includeSource) { |
+ writeln('<span class="show-code">Code</span>'); |
+ } |
-/** |
- * Creates a hyperlink. Handles turning the [href] into an appropriate relative |
- * path from the current file. |
- */ |
-String a(String href, String contents, [String class]) { |
- final css = class == null ? '' : ' class="$class"'; |
- return '<a href="${relativePath(href)}"$css>$contents</a>'; |
-} |
+ if (field.isStatic && !type.isTop) { |
+ write('static '); |
+ } |
-/** Generates a human-friendly string representation for a type. */ |
-typeName(Type type, [bool showBounds = false]) { |
- // See if it's a generic type. |
- if (type.isGeneric) { |
- final typeParams = []; |
- for (final typeParam in type.genericType.typeParameters) { |
- if (showBounds && |
- (typeParam.extendsType != null) && |
- !typeParam.extendsType.isObject) { |
- final bound = typeName(typeParam.extendsType, showBounds: true); |
- typeParams.add('${typeParam.name} extends $bound'); |
- } else { |
- typeParams.add(typeParam.name); |
- } |
+ if (field.isFinal) { |
+ write('final '); |
+ } else if (field.type.name == 'Dynamic') { |
+ write('var '); |
} |
- final params = Strings.join(typeParams, ', '); |
- return '${type.name}<$params>'; |
- } |
+ annotateType(type, field.type); |
+ write( |
+ ''' |
+ <strong>${field.name}</strong> <a class="anchor-link" |
+ href="#${memberAnchor(field)}" |
+ title="Permalink to ${typeName(type)}.${field.name}">#</a> |
+ </h4> |
+ '''); |
- // See if it's an instantiation of a generic type. |
- final typeArgs = type.typeArgsInOrder; |
- if (typeArgs != null) { |
- final args = Strings.join(map(typeArgs, typeName), ', '); |
- return '${type.genericType.name}<$args>'; |
+ docCode(field.span, getFieldComment(field), showCode: true); |
+ writeln('</div>'); |
} |
- // Regular type. |
- return type.name; |
-} |
+ docParamList(Type enclosingType, MethodMember member) { |
+ write('('); |
+ bool first = true; |
+ bool inOptionals = false; |
+ for (final parameter in member.parameters) { |
+ if (!first) write(', '); |
-/** Writes a link to a human-friendly string representation for a type. */ |
-linkToType(Type enclosingType, Type type) { |
- if (type is ParameterType) { |
- // If we're using a type parameter within the body of a generic class then |
- // just link back up to the class. |
- write(a(typeUrl(enclosingType), type.name)); |
- return; |
- } |
+ if (!inOptionals && parameter.isOptional) { |
+ write('['); |
+ inOptionals = true; |
+ } |
- // Link to the type. |
- // Use .genericType to avoid writing the <...> here. |
- write(a(typeUrl(type), type.genericType.name)); |
- |
- // See if it's a generic type. |
- if (type.isGeneric) { |
- // TODO(rnystrom): This relies on a weird corner case of frog. Currently, |
- // the only time we get into this case is when we have a "raw" generic |
- // that's been instantiated with Dynamic for all type arguments. It's kind |
- // of strange that frog works that way, but we take advantage of it to |
- // show raw types without any type arguments. |
- return; |
- } |
+ annotateType(enclosingType, parameter.type, parameter.name); |
+ |
+ // Show the default value for named optional parameters. |
+ if (parameter.isOptional && parameter.hasDefaultValue) { |
+ write(' = '); |
+ // TODO(rnystrom): Using the definition text here is a bit cheap. |
+ // We really should be pretty-printing the AST so that if you have: |
+ // foo([arg = 1 + /* comment */ 2]) |
+ // the docs should just show: |
+ // foo([arg = 1 + 2]) |
+ // For now, we'll assume you don't do that. |
+ write(parameter.definition.value.span.text); |
+ } |
- // See if it's an instantiation of a generic type. |
- final typeArgs = type.typeArgsInOrder; |
- if (typeArgs != null) { |
- write('<'); |
- bool first = true; |
- for (final arg in typeArgs) { |
- if (!first) write(', '); |
first = false; |
- linkToType(enclosingType, arg); |
} |
- write('>'); |
- } |
-} |
- |
-/** Creates a linked cross reference to [type]. */ |
-typeReference(Type type) { |
- // TODO(rnystrom): Do we need to handle ParameterTypes here like |
- // annotation() does? |
- return a(typeUrl(type), typeName(type), class: 'crossref'); |
-} |
-/** |
- * Writes a type annotation for the given type and (optional) parameter name. |
- */ |
-annotateType(Type enclosingType, Type type, [String paramName = null]) { |
- // Don't bother explicitly displaying Dynamic. |
- if (type.isVar) { |
- if (paramName !== null) write(paramName); |
- return; |
+ if (inOptionals) write(']'); |
+ write(')'); |
} |
- // For parameters, handle non-typedefed function types. |
- if (paramName !== null) { |
- final call = type.getCallMethod(); |
- if (call != null) { |
- annotateType(enclosingType, call.returnType); |
- write(paramName); |
+ /** |
+ * Documents the code contained within [span] with [comment]. If [showCode] |
+ * is `true` (and [includeSource] is set), also includes the source code. |
+ */ |
+ docCode(SourceSpan span, String comment, [bool showCode = false]) { |
+ writeln('<div class="doc">'); |
+ if (comment != null) { |
+ writeln(md.markdownToHtml(comment)); |
+ } |
- docParamList(enclosingType, call); |
- return; |
+ if (includeSource && showCode) { |
+ writeln('<pre class="source">'); |
+ write(formatCode(span)); |
+ writeln('</pre>'); |
} |
+ |
+ writeln('</div>'); |
} |
- linkToType(enclosingType, type); |
+ /** Get the doc comment associated with the given type. */ |
+ String getTypeComment(Type type) => _comments.find(type.span); |
- write(' '); |
- if (paramName !== null) write(paramName); |
-} |
+ /** Get the doc comment associated with the given method. */ |
+ String getMethodComment(MethodMember method) => _comments.find(method.span); |
+ /** Get the doc comment associated with the given field. */ |
+ String getFieldComment(FieldMember field) => _comments.find(field.span); |
-/** |
- * This will be called whenever a doc comment hits a `[name]` in square |
- * brackets. It will try to figure out what the name refers to and link or |
- * style it appropriately. |
- */ |
-md.Node resolveNameReference(String name) { |
- makeLink(String href) { |
- final anchor = new md.Element.text('a', name); |
- anchor.attributes['href'] = relativePath(href); |
- anchor.attributes['class'] = 'crossref'; |
- return anchor; |
+ /** |
+ * Creates a hyperlink. Handles turning the [href] into an appropriate |
+ * relative path from the current file. |
+ */ |
+ String a(String href, String contents, [String class]) { |
+ final css = class == null ? '' : ' class="$class"'; |
+ return '<a href="${relativePath(href)}"$css>$contents</a>'; |
} |
- findMember(Type type) { |
- final member = type.members[name]; |
- if (member == null) return null; |
- |
- // Special case: if the member we've resolved is a property (i.e. it wraps |
- // a getter and/or setter then *that* member itself won't be on the docs, |
- // just the getter or setter will be. So pick one of those to link to. |
- if (member.isProperty) { |
- return member.canGet ? member.getter : member.setter; |
+ /** |
+ * Writes a type annotation for the given type and (optional) parameter name. |
+ */ |
+ annotateType(Type enclosingType, Type type, [String paramName = null]) { |
+ // Don't bother explicitly displaying Dynamic. |
+ if (type.isVar) { |
+ if (paramName !== null) write(paramName); |
+ return; |
} |
- return member; |
- } |
+ // For parameters, handle non-typedefed function types. |
+ if (paramName !== null) { |
+ final call = type.getCallMethod(); |
+ if (call != null) { |
+ annotateType(enclosingType, call.returnType); |
+ write(paramName); |
- // See if it's a parameter of the current method. |
- if (_currentMember != null) { |
- for (final parameter in _currentMember.parameters) { |
- if (parameter.name == name) { |
- final element = new md.Element.text('span', name); |
- element.attributes['class'] = 'param'; |
- return element; |
+ docParamList(enclosingType, call); |
+ return; |
} |
} |
+ |
+ linkToType(enclosingType, type); |
+ |
+ write(' '); |
+ if (paramName !== null) write(paramName); |
} |
- // See if it's another member of the current type. |
- if (_currentType != null) { |
- final member = findMember(_currentType); |
- if (member != null) { |
- return makeLink(memberUrl(member)); |
+ /** Writes a link to a human-friendly string representation for a type. */ |
+ linkToType(Type enclosingType, Type type) { |
+ if (type is ParameterType) { |
+ // If we're using a type parameter within the body of a generic class then |
+ // just link back up to the class. |
+ write(a(typeUrl(enclosingType), type.name)); |
+ return; |
} |
- } |
- // See if it's another type in the current library. |
- if (_currentLibrary != null) { |
- final type = _currentLibrary.types[name]; |
- if (type != null) { |
- return makeLink(typeUrl(type)); |
+ // Link to the type. |
+ // Use .genericType to avoid writing the <...> here. |
+ write(a(typeUrl(type), type.genericType.name)); |
+ |
+ // See if it's a generic type. |
+ if (type.isGeneric) { |
+ // TODO(rnystrom): This relies on a weird corner case of frog. Currently, |
+ // the only time we get into this case is when we have a "raw" generic |
+ // that's been instantiated with Dynamic for all type arguments. It's kind |
+ // of strange that frog works that way, but we take advantage of it to |
+ // show raw types without any type arguments. |
+ return; |
} |
- // See if it's a top-level member in the current library. |
- final member = findMember(_currentLibrary.topType); |
- if (member != null) { |
- return makeLink(memberUrl(member)); |
+ // See if it's an instantiation of a generic type. |
+ final typeArgs = type.typeArgsInOrder; |
+ if (typeArgs != null) { |
+ write('<'); |
+ bool first = true; |
+ for (final arg in typeArgs) { |
+ if (!first) write(', '); |
+ first = false; |
+ linkToType(enclosingType, arg); |
+ } |
+ write('>'); |
} |
} |
- // TODO(rnystrom): Should also consider: |
- // * Names imported by libraries this library imports. |
- // * Type parameters of the enclosing type. |
+ /** Creates a linked cross reference to [type]. */ |
+ typeReference(Type type) { |
+ // TODO(rnystrom): Do we need to handle ParameterTypes here like |
+ // annotation() does? |
+ return a(typeUrl(type), typeName(type), class: 'crossref'); |
+ } |
+ |
+ /** Generates a human-friendly string representation for a type. */ |
+ typeName(Type type, [bool showBounds = false]) { |
+ // See if it's a generic type. |
+ if (type.isGeneric) { |
+ final typeParams = []; |
+ for (final typeParam in type.genericType.typeParameters) { |
+ if (showBounds && |
+ (typeParam.extendsType != null) && |
+ !typeParam.extendsType.isObject) { |
+ final bound = typeName(typeParam.extendsType, showBounds: true); |
+ typeParams.add('${typeParam.name} extends $bound'); |
+ } else { |
+ typeParams.add(typeParam.name); |
+ } |
+ } |
- return new md.Element.text('code', name); |
-} |
+ final params = Strings.join(typeParams, ', '); |
+ return '${type.name}<$params>'; |
+ } |
-/** |
- * Documents the code contained within [span]. Will include the previous |
- * Dartdoc associated with that span if found, and will include the syntax |
- * highlighted code itself if desired. |
- */ |
-docCode(SourceSpan span, String extraMarkdown, [bool showCode = false]) { |
- if (span == null) return; |
- |
- writeln('<div class="doc">'); |
- final comment = findComment(span); |
- if (comment != null) { |
- writeln(md.markdownToHtml('${comment}\n\n${extraMarkdown}')); |
- } else { |
- writeln(md.markdownToHtml(extraMarkdown)); |
+ // See if it's an instantiation of a generic type. |
+ final typeArgs = type.typeArgsInOrder; |
+ if (typeArgs != null) { |
+ final args = Strings.join(map(typeArgs, (arg) => typeName(arg)), ', '); |
+ return '${type.genericType.name}<$args>'; |
+ } |
+ |
+ // Regular type. |
+ return type.name; |
} |
- if (includeSource && showCode) { |
- writeln('<pre class="source">'); |
- write(formatCode(span)); |
- writeln('</pre>'); |
+ /** |
+ * Takes a string of Dart code and turns it into sanitized HTML. |
+ */ |
+ formatCode(SourceSpan span) { |
+ // Remove leading indentation to line up with first line. |
+ final column = getSpanColumn(span); |
+ final lines = span.text.split('\n'); |
+ // TODO(rnystrom): Dirty hack. |
+ for (final i = 1; i < lines.length; i++) { |
+ lines[i] = unindent(lines[i], column); |
+ } |
+ |
+ final code = Strings.join(lines, '\n'); |
+ |
+ // Syntax highlight. |
+ return classifySource(new SourceFile('', code)); |
} |
- writeln('</div>'); |
-} |
+ /** |
+ * This will be called whenever a doc comment hits a `[name]` in square |
+ * brackets. It will try to figure out what the name refers to and link or |
+ * style it appropriately. |
+ */ |
+ md.Node resolveNameReference(String name) { |
+ makeLink(String href) { |
+ final anchor = new md.Element.text('a', name); |
+ anchor.attributes['href'] = relativePath(href); |
+ anchor.attributes['class'] = 'crossref'; |
+ return anchor; |
+ } |
-/** Finds the doc comment preceding the given source span, if there is one. */ |
-findComment(SourceSpan span) => findCommentInFile(span.file, span.start); |
+ findMember(Type type) { |
+ final member = type.members[name]; |
+ if (member == null) return null; |
-/** Finds the doc comment preceding the given source span, if there is one. */ |
-findCommentInFile(SourceFile file, int position) { |
- // Get the doc comments for this file. |
- final fileComments = _comments.putIfAbsent(file.filename, |
- () => parseDocComments(file)); |
+ // Special case: if the member we've resolved is a property (i.e. it wraps |
+ // a getter and/or setter then *that* member itself won't be on the docs, |
+ // just the getter or setter will be. So pick one of those to link to. |
+ if (member.isProperty) { |
+ return member.canGet ? member.getter : member.setter; |
+ } |
- return fileComments[position]; |
-} |
+ return member; |
+ } |
-parseDocComments(SourceFile file) { |
- final comments = new Map<int, String>(); |
- |
- final tokenizer = new Tokenizer(file, false); |
- var lastComment = null; |
- |
- while (true) { |
- final token = tokenizer.next(); |
- if (token.kind == TokenKind.END_OF_FILE) break; |
- |
- if (token.kind == TokenKind.COMMENT) { |
- final text = token.text; |
- if (text.startsWith('/**')) { |
- // Remember that we've encountered a doc comment. |
- lastComment = stripComment(token.text); |
- } else if (text.startsWith('///')) { |
- var line = text.substring(3, text.length); |
- // Allow a leading space. |
- if (line.startsWith(' ')) line = line.substring(1, text.length); |
- if (lastComment == null) { |
- lastComment = line; |
- } else { |
- lastComment = '$lastComment$line'; |
+ // See if it's a parameter of the current method. |
+ if (_currentMember != null) { |
+ for (final parameter in _currentMember.parameters) { |
+ if (parameter.name == name) { |
+ final element = new md.Element.text('span', name); |
+ element.attributes['class'] = 'param'; |
+ return element; |
} |
} |
- } else if (token.kind == TokenKind.WHITESPACE) { |
- // Ignore whitespace tokens. |
- } else if (token.kind == TokenKind.HASH) { |
- // Look for #library() to find the library comment. |
- final next = tokenizer.next(); |
- if ((lastComment != null) && (next.kind == TokenKind.LIBRARY)) { |
- comments[_libraryDoc] = lastComment; |
- lastComment = null; |
- } |
- } else { |
- if (lastComment != null) { |
- // We haven't attached the last doc comment to something yet, so stick |
- // it to this token. |
- comments[token.start] = lastComment; |
- lastComment = null; |
- } |
} |
- } |
- return comments; |
-} |
- |
-/** |
- * Takes a string of Dart code and turns it into sanitized HTML. |
- */ |
-formatCode(SourceSpan span) { |
- // Remove leading indentation to line up with first line. |
- final column = getSpanColumn(span); |
- final lines = span.text.split('\n'); |
- // TODO(rnystrom): Dirty hack. |
- for (final i = 1; i < lines.length; i++) { |
- lines[i] = unindent(lines[i], column); |
- } |
+ // See if it's another member of the current type. |
+ if (_currentType != null) { |
+ final member = findMember(_currentType); |
+ if (member != null) { |
+ return makeLink(memberUrl(member)); |
+ } |
+ } |
- final code = Strings.join(lines, '\n'); |
+ // See if it's another type in the current library. |
+ if (_currentLibrary != null) { |
+ final type = _currentLibrary.types[name]; |
+ if (type != null) { |
+ return makeLink(typeUrl(type)); |
+ } |
- // Syntax highlight. |
- return classifySource(new SourceFile('', code)); |
-} |
+ // See if it's a top-level member in the current library. |
+ final member = findMember(_currentLibrary.topType); |
+ if (member != null) { |
+ return makeLink(memberUrl(member)); |
+ } |
+ } |
-// TODO(rnystrom): Move into SourceSpan? |
-int getSpanColumn(SourceSpan span) { |
- final line = span.file.getLine(span.start); |
- return span.file.getColumn(line, span.start); |
-} |
+ // TODO(rnystrom): Should also consider: |
+ // * Names imported by libraries this library imports. |
+ // * Type parameters of the enclosing type. |
-/** |
- * Pulls the raw text out of a doc comment (i.e. removes the comment |
- * characters). |
- */ |
-stripComment(comment) { |
- StringBuffer buf = new StringBuffer(); |
- |
- for (final line in comment.split('\n')) { |
- line = line.trim(); |
- if (line.startsWith('/**')) line = line.substring(3, line.length); |
- if (line.endsWith('*/')) line = line.substring(0, line.length - 2); |
- line = line.trim(); |
- if (line.startsWith('* ')) { |
- line = line.substring(2, line.length); |
- } else if (line.startsWith('*')) { |
- line = line.substring(1, line.length); |
- } |
- |
- buf.add(line); |
- buf.add('\n'); |
+ return new md.Element.text('code', name); |
} |
- return buf.toString(); |
+ // TODO(rnystrom): Move into SourceSpan? |
+ int getSpanColumn(SourceSpan span) { |
+ final line = span.file.getLine(span.start); |
+ return span.file.getColumn(line, span.start); |
+ } |
} |
- |
-/** Register a callback to add additional documentation to a type. */ |
-addTypeDocumenter(TypeDocumenter fn) => _typeDocumenters.add(fn); |
- |
-/** Register a callback to add additional documentation to a method. */ |
-addMethodDocumenter(MethodDocumenter fn) => _methodDocumenters.add(fn); |
- |
-/** Register a callback to add additional documentation to a field. */ |
-addFieldDocumenter(FieldDocumenter fn) => _fieldDocumenters.add(fn); |