From 328d1ba9d28691ddb539822dcb0f6d9314c0aed5 Mon Sep 17 00:00:00 2001 From: ben hockey Date: Fri, 22 Mar 2013 22:38:41 -0500 Subject: [PATCH 1/5] parse the return descriptions as markdown --- lib/processor/dojodoc.js | 47 +++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/lib/processor/dojodoc.js b/lib/processor/dojodoc.js index 5c0ad8a..846dd03 100644 --- a/lib/processor/dojodoc.js +++ b/lib/processor/dojodoc.js @@ -194,6 +194,39 @@ 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; + + // 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) { + for (i = 0; (example = metadata.examples[i]); ++i) { + metadata.examples[i] = parseMarkdown(example); + } + } + + if (metadata.properties) { + for (k in metadata.properties) { + if (_hasOwnProperty.call(metadata.properties, k)) { + metadata.properties[k].summary = parseMarkdown(metadata.properties[k].summary); + } + } + } + + 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 +339,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; } From 58cfcf66e3aa3d9a5b51085ff0426abe58a88617 Mon Sep 17 00:00:00 2001 From: ben hockey Date: Sat, 23 Mar 2013 20:59:27 -0500 Subject: [PATCH 2/5] recurse with processMetadata for properties of the metadata --- lib/processor/dojodoc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/processor/dojodoc.js b/lib/processor/dojodoc.js index 846dd03..8b46cc1 100644 --- a/lib/processor/dojodoc.js +++ b/lib/processor/dojodoc.js @@ -217,7 +217,7 @@ define([ if (metadata.properties) { for (k in metadata.properties) { if (_hasOwnProperty.call(metadata.properties, k)) { - metadata.properties[k].summary = parseMarkdown(metadata.properties[k].summary); + processMetadata(metadata.properties[k]); } } } From e8d05e35470d418b49d88339bda68c20884abacc Mon Sep 17 00:00:00 2001 From: ben hockey Date: Sat, 23 Mar 2013 01:41:35 -0500 Subject: [PATCH 3/5] defer resolving the annotated object. by resolving this in the exporter, we can avoid a race condtion where sometimes relatedModule had not been indicated when it should have been. --- lib/exporter/dojov1.js | 76 +++++++++++++++++++++++++++++++++++++++- lib/processor/dojodoc.js | 52 +++++---------------------- 2 files changed, 83 insertions(+), 45 deletions(-) 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:') + '

      '; + + (function readProperties(object) { + var propertyMetadata, + properties = object.properties, + k; + + // if the annotationObject is a function, we don't want to pick up any properties apart + // from what's on the prototype. + if (object.type === 'function') { + if (_hasOwnProperty.call(properties, 'prototype')) { + readProperties(properties.prototype); + } + return; + } + + for (k in properties) { + if (_hasOwnProperty.call(properties, k)) { + // Type descriptor could be a plain JS object, or could be a constructor. It is often the + // latter. + if (k === 'prototype') { + readProperties(properties[k]); + } + // Filter out built-ins and constructor properties which come from dojo/_base/declare + else if (k !== 'constructor' && properties[k].file) { + propertyMetadata = 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 || '') + additionalDescription + '
    '; + } + /** * 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 8b46cc1..71d8b0e 100644 --- a/lib/processor/dojodoc.js +++ b/lib/processor/dojodoc.js @@ -50,7 +50,7 @@ define([ 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 (typeof source[k] !== 'string' || trim(source[k])) { @@ -113,60 +113,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; } /** From b31056c97fcd7ae8d12d0c9cfc2b6cb4c90dc05f Mon Sep 17 00:00:00 2001 From: ben hockey Date: Sat, 23 Mar 2013 22:55:42 -0500 Subject: [PATCH 4/5] add raw property to hold unparsed metadata --- lib/processor/dojodoc.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/processor/dojodoc.js b/lib/processor/dojodoc.js index 71d8b0e..a36061e 100644 --- a/lib/processor/dojodoc.js +++ b/lib/processor/dojodoc.js @@ -45,7 +45,10 @@ 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]); @@ -53,6 +56,11 @@ define([ 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]; } @@ -165,7 +173,13 @@ define([ function processMetadata(/**Object*/ metadata) { var example, i, - k; + 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 @@ -173,7 +187,9 @@ define([ 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); } } From d86cc3ae2ddfa577d4afd3c26d2f74a7a40a42a2 Mon Sep 17 00:00:00 2001 From: ben hockey Date: Fri, 22 Mar 2013 22:58:07 -0500 Subject: [PATCH 5/5] it's possible that return types might reference annotated objects --- lib/processor/dojodoc.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/processor/dojodoc.js b/lib/processor/dojodoc.js index a36061e..26acfe7 100644 --- a/lib/processor/dojodoc.js +++ b/lib/processor/dojodoc.js @@ -370,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