diff --git a/lib/exporter/dojov1.js b/lib/exporter/dojov1.js index b9a3a06..cd5e244 100644 --- a/lib/exporter/dojov1.js +++ b/lib/exporter/dojov1.js @@ -1,4 +1,12 @@ -define([ '../Module', '../Value', './util', '../console', '../node!fs' ], function (Module, Value, util, console, fs) { +define([ + 'dojo/string', + '../Module', + '../Value', + './util', + '../console', + 'dojo/node!fs', +], function (stringUtil, Module, Value, util, console, fs) { + /** * Takes information from metadata stored alongside a Value and adds it to the output. * @param node The node to add metadata to. @@ -34,6 +42,66 @@ define([ '../Module', '../Value', './util', '../console', '../node!fs' ], functi } } + /** + * Given metadata with a type annotation, attempt to resolve the annotated type as an object and (hackily) apply + * information about the object’s default properties to the metadata description property. + */ + function processTypeAnnotation(/**Object*/ metadata) { + if (!metadata.type || typeof metadata.type === 'string') { + return; + } + + var propertyTemplate = '
  • ${key}${type}${summary}
  • ', + annotationObject = metadata.type, + additionalDescription = ''; + + if (annotationObject.relatedModule) { + metadata.type = annotationObject.relatedModule.id; + return; + } + + metadata.type = 'Object'; + additionalDescription += '

    ' + (metadata.description ? + 'The following properties are supported:' : + 'An object with the following properties:') + '

    '; + } + /** * Takes an array of return Values and processes it for return types, discarding all * duplicates, and applies the resulting list of properties to the node given in returnsNode. @@ -67,6 +135,9 @@ define([ '../Module', '../Value', './util', '../console', '../node!fs' ], functi i; for (i = 0; (parameter = property.parameters[i]); ++i) { + if (typeof parameter.metadata.type !== 'string') { + processTypeAnnotation(parameter.metadata); + } parameterType = parameter.metadata.type || parameter.type || 'unknown'; parameterNode = parametersNode.createNode('parameter', { name: parameter.name, @@ -105,6 +176,9 @@ define([ '../Module', '../Value', './util', '../console', '../node!fs' ], functi propertyNode; function makePropertyObject(name, value) { + if (typeof value.metadata.type !== 'string') { + processTypeAnnotation(value.metadata); + } var object = { name: name, scope: scope, diff --git a/lib/processor/dojodoc.js b/lib/processor/dojodoc.js index 5c0ad8a..26acfe7 100644 --- a/lib/processor/dojodoc.js +++ b/lib/processor/dojodoc.js @@ -45,14 +45,22 @@ define([ * avoid accidentally overwriting data that already exists with empty data. */ function mixinMetadata(destination, source) { - for (var k in source) { + var raw, + k; + + for (k in source) { if (_hasOwnProperty.call(source, k) && source[k]) { if (source[k] instanceof Array && destination[k] instanceof Array) { destination[k] = destination[k].concat(source[k]); } - else if (k === 'type') { + else if (k === 'type' && typeof source[k] === 'string') { destination[k] = source[k].replace(optionalTypeRe, ''); } + else if (k === 'raw') { + // this keeps the mixin/override semantics of raw the same as the metadata itself + raw = destination[k]; + raw ? mixinMetadata(raw, source[k]) : destination[k] = source[k]; + } else if (typeof source[k] !== 'string' || trim(source[k])) { destination[k] = source[k]; } @@ -113,60 +121,24 @@ define([ } /** - * Given metadata with a type annotation, attempt to resolve the annotated type as an object and (hackily) apply - * information about the object’s default properties to the metadata description property. - * TODO: This should really end up happening in the dojov1 exporter instead. + * Given metadata with a type annotation, attempt to resolve the annotated type as an object and + * provide that object to the exporter as the type property of the metadata. */ function processTypeAnnotation(/**Object*/ metadata) { if (!metadata.type) { return; } - var propertyTemplate = '\n* ${key}${type}${summary}', - annotationObject = env.scope.getVariable(metadata.type.replace(/[^\w$\.]+$/g, '').split('.')), - additionalDescription = ''; + var annotationObject = env.scope.getVariable(metadata.type.replace(/[^\w$\.]+$/g, '').split('.')); if (!annotationObject || annotationObject.type === Value.TYPE_UNDEFINED || /* not a built-in */ !annotationObject.file) { return; } - if (annotationObject.relatedModule) { - metadata.type = annotationObject.relatedModule.id; - return; - } - - // TODO: The fact that evaluate exists on annotation objects seems to indicate that we’re failing to - // evaluate all function expressions; this might be an issue - annotationObject.evaluate && annotationObject.evaluate(); - - metadata.type = 'Object'; - additionalDescription += metadata.description ? - '\n\nThe following properties are supported:\n' : - 'An object with the following properties:\n'; - - (function readProperties(object) { - var propertyMetadata; - for (var k in object.properties) { - if (_hasOwnProperty.call(object.properties, k)) { - // Type descriptor could be a plain JS object, or could be a constructor. It is often the - // latter. - if (k === 'prototype') { - readProperties(object.properties[k]); - } - // Filter out built-ins and constructor properties which come from dojo/_base/declare - else if (k !== 'constructor' && object.properties[k].file) { - propertyMetadata = object.properties[k].metadata; - additionalDescription += stringUtil.substitute(propertyTemplate, { - key: k, - type: propertyMetadata.type ? ' (' + propertyMetadata.type + (propertyMetadata.isOptional ? ', optional' : '') + ')' : '', - summary: propertyMetadata.summary ? ': ' + propertyMetadata.summary : '' - }); - } - } - } - }(annotationObject)); - - metadata.description = (metadata.description || '') + parseMarkdown(additionalDescription); + // defer resolving this to do it in the exporter. annotationObject might be the return + // value of the module currently being processed and in that case it won't have been tagged + // with a relatedModule yet. + metadata.type = annotationObject; } /** @@ -194,6 +166,47 @@ define([ } } + /** + * Processes metadata to parse the markdown of certain properties + * @param metadata Metadata object to parse for markdown + */ + function processMetadata(/**Object*/ metadata) { + var example, + i, + k, + raw = metadata.raw = { + summary: metadata.summary, + description: metadata.description, + // handle examples in the loop below to avoid redundant iteration. also, by + // recursing for properties and returns, they will get their own raw objects. + }; + + // The style guide says `summary` isn’t markdown, only `description` is markdown, but everyone sticks markdown + // in the summary anyway, so handle it as markdown too + metadata.summary = parseMarkdown(metadata.summary || ''); + metadata.description = parseMarkdown(metadata.description || ''); + + if (metadata.examples) { + raw.examples = []; + for (i = 0; (example = metadata.examples[i]); ++i) { + raw.examples[i] = example; + metadata.examples[i] = parseMarkdown(example); + } + } + + if (metadata.properties) { + for (k in metadata.properties) { + if (_hasOwnProperty.call(metadata.properties, k)) { + processMetadata(metadata.properties[k]); + } + } + } + + if (metadata.returns) { + processMetadata(metadata.returns); + } + } + /** * Processes a dojodoc multi-line comment block, which consists of key lines that identify the metadata and * subsequent indented lines containing the actual metadata. @@ -306,19 +319,7 @@ define([ } } - // The style guide says `summary` isn’t markdown, only `description` is markdown, but everyone sticks markdown - // in the summary anyway, so handle it as markdown too - metadata.summary = parseMarkdown(metadata.summary); - metadata.description = parseMarkdown(metadata.description); - for (var i = 0, example; (example = metadata.examples[i]); ++i) { - metadata.examples[i] = parseMarkdown(example); - } - - for (var k in metadata.properties) { - if (_hasOwnProperty.call(metadata.properties, k)) { - metadata.properties[k].summary = parseMarkdown(metadata.properties[k].summary); - } - } + processMetadata(metadata); return metadata; } @@ -369,8 +370,11 @@ define([ candidate = /^[^\n]*\/\/(.*?)\n/.exec(util.getSourceForRange(value.raw.range)); if (candidate) { - metadata = { type: trim(candidate[1]) }; + value.evaluated.metadata.type = trim(candidate[1]); + processTypeAnnotation(value.evaluated.metadata); } + // if we get here, we have no need to mixin any metadata + return; } // Function or object body