diff --git a/declarations/comment.js b/declarations/comment.js index a876bd6e0..ecfefe5ce 100644 --- a/declarations/comment.js +++ b/declarations/comment.js @@ -1,4 +1,4 @@ -declare type DocumentationConfig = { +type DocumentationConfig = { polyglot?: boolean, inferPrivate?: boolean, noPackage?: boolean, @@ -12,17 +12,17 @@ declare type DocumentationConfig = { parseExtension: Array }; -declare type InputsConfig = { +type InputsConfig = { inputs: Array, config: DocumentationConfig }; -declare type CommentError = { +type CommentError = { message: string, commentLineNumber?: number }; -declare type DoctrineType = { +type DoctrineType = { elements?: Array, expression?: DoctrineType, applications?: Array, @@ -30,7 +30,7 @@ declare type DoctrineType = { name?: string }; -declare type CommentLoc = { +type CommentLoc = { start: { line: number }, @@ -39,12 +39,12 @@ declare type CommentLoc = { } }; -declare type SourceFile = { +type SourceFile = { source?: string, file: string }; -declare type CommentContext = { +type CommentContext = { sortKey: string, file: string, ast: Object, @@ -53,12 +53,26 @@ declare type CommentContext = { github?: CommentContextGitHub }; -declare type CommentContextGitHub = { +type CommentContextGitHub = { path: string, url: string }; -declare type CommentTag = { +type CommentTagBase = { + title: string +}; + +type CommentTag = CommentTagBase & { + name?: string, + title: string, + description?: Object, + default?: any, + lineNumber?: number, + type?: DoctrineType, + properties?: Array +}; + +type CommentTagNamed = CommentTag & { name?: string, title: string, description?: Object, @@ -68,7 +82,7 @@ declare type CommentTag = { properties?: Array }; -declare type CommentMembers = { +type CommentMembers = { static: Array, instance: Array, events: Array, @@ -76,32 +90,33 @@ declare type CommentMembers = { inner: Array }; -declare type CommentExample = { +type CommentExample = { caption?: string, description?: Object }; -declare type Remark = { +type Remark = { type: string, children: Array }; -declare type Access = 'private' | 'public' | 'protected'; -declare type Scope = 'instance' | 'static' | 'inner' | 'global'; -declare type Kind = 'class' | - 'constant' | - 'event' | - 'external' | - 'file' | - 'function' | - 'member' | - 'mixin' | - 'module' | - 'namespace' | - 'typedef' | - 'interface'; - -declare type Comment = { +type Access = 'private' | 'public' | 'protected'; +type Scope = 'instance' | 'static' | 'inner' | 'global'; +type Kind = + | 'class' + | 'constant' + | 'event' + | 'external' + | 'file' + | 'function' + | 'member' + | 'mixin' + | 'module' + | 'namespace' + | 'typedef' + | 'interface'; + +type Comment = { errors: Array, tags: Array, @@ -148,8 +163,8 @@ declare type Comment = { }> }; -declare type ReducedComment = { +type ReducedComment = { name: string, kind: ?Kind, scope?: ?Scope -} +}; diff --git a/default_theme/section._ b/default_theme/section._ index 4ef057fee..a7158640f 100644 --- a/default_theme/section._ +++ b/default_theme/section._ @@ -52,7 +52,7 @@ = <%- param.default %><% } %>) <%= md(param.description, true) %> - <% if (param.properties) { %> + <% if (param.properties && param.properties.length) { %> @@ -93,7 +93,7 @@ <% } %><% if (property.description) { %>: <%= md(property.description, true) %><% } %> - <% if (property.properties) { %> + <% if (property.properties && property.properties.length) { %>
    <% property.properties.forEach(function(property) { %>
  • <%- property.name %> <%= formatType(property.type) %> diff --git a/index.js b/index.js index 6ae6d0987..0c91f4c86 100644 --- a/index.js +++ b/index.js @@ -45,13 +45,10 @@ function pipeline() { }; } - - -function configure(indexes, args)/*: Promise */ { +function configure(indexes, args) /*: Promise */ { let mergedConfig = mergeConfig(args); return mergedConfig.then(config => { - let expandedInputs = expandInputs(indexes, config); return expandedInputs.then(inputs => { @@ -71,8 +68,10 @@ function configure(indexes, args)/*: Promise */ { * @param {Object} config options * @returns {Promise>} promise with results */ -function expandInputs(indexes/*: string|Array */, - config /*: DocumentationConfig */) { +function expandInputs( + indexes /*: string|Array */, + config /*: DocumentationConfig */ +) { // Ensure that indexes is an array of strings indexes = [].concat(indexes); @@ -91,23 +90,24 @@ function buildInternal(inputsAndConfig) { config.access = ['public', 'undefined', 'protected']; } - var parseFn = (config.polyglot) ? polyglot : parseJavaScript; + var parseFn = config.polyglot ? polyglot : parseJavaScript; var buildPipeline = pipeline( inferName, inferAccess(config.inferPrivate), inferAugments, inferKind, + nest, inferParams, inferProperties, inferReturn, inferMembership(), inferType, - nest, config.github && github, - garbageCollect); + garbageCollect + ); - let extractedComments = _.flatMap(inputs, function (sourceFile) { + let extractedComments = _.flatMap(inputs, function(sourceFile) { if (!sourceFile.source) { sourceFile.source = fs.readFileSync(sourceFile.file, 'utf8'); } @@ -115,16 +115,17 @@ function buildInternal(inputsAndConfig) { return parseFn(sourceFile, config).map(buildPipeline); }).filter(Boolean); - return filterAccess(config.access, - hierarchy( - sort(extractedComments, config))); + return filterAccess( + config.access, + hierarchy(sort(extractedComments, config)) + ); } function lintInternal(inputsAndConfig) { let inputs = inputsAndConfig.inputs; let config = inputsAndConfig.config; - let parseFn = (config.polyglot) ? polyglot : parseJavaScript; + let parseFn = config.polyglot ? polyglot : parseJavaScript; let lintPipeline = pipeline( lintComments, @@ -137,7 +138,8 @@ function lintInternal(inputsAndConfig) { inferReturn, inferMembership(), inferType, - nest); + nest + ); let extractedComments = _.flatMap(inputs, sourceFile => { if (!sourceFile.source) { @@ -183,8 +185,7 @@ function lintInternal(inputsAndConfig) { * } * }); */ -let lint = (indexes, args) => configure(indexes, args) - .then(lintInternal); +let lint = (indexes, args) => configure(indexes, args).then(lintInternal); /** * Generate JavaScript documentation as a list of parsed JSDoc @@ -227,8 +228,7 @@ let lint = (indexes, args) => configure(indexes, args) * // any other kind of code data. * }); */ -let build = (indexes, args) => configure(indexes, args) - .then(buildInternal); +let build = (indexes, args) => configure(indexes, args).then(buildInternal); /** * Documentation's formats are modular methods that take comments @@ -240,9 +240,8 @@ let build = (indexes, args) => configure(indexes, args) var formats = { html: require('./lib/output/html'), md: require('./lib/output/markdown'), - remark: (comments/*: Array */, config/*: DocumentationConfig */) => - markdownAST(comments, config) - .then(res => JSON.stringify(res, null, 2)), + remark: (comments /*: Array */, config /*: DocumentationConfig */) => + markdownAST(comments, config).then(res => JSON.stringify(res, null, 2)), json: require('./lib/output/json') }; diff --git a/lib/infer/params.js b/lib/infer/params.js index ffa430034..2861bee70 100644 --- a/lib/infer/params.js +++ b/lib/infer/params.js @@ -1,59 +1,57 @@ 'use strict'; /* @flow */ -var t = require('babel-types'), - findTarget = require('./finders').findTarget, - flowDoctrine = require('../flow_doctrine'); - -function addPrefix(doc, prefix) { - if (!Array.isArray(doc) && doc.name) { - doc.name = prefix + doc.name; - } - return doc; -} +const t = require('babel-types'); +const generate = require('babel-generator').default; +const _ = require('lodash'); +const findTarget = require('./finders').findTarget; +const flowDoctrine = require('../flow_doctrine'); +const util = require('util'); +const debuglog = util.debuglog('infer'); /** - * Given a parameter like - * - * function a(b = 1) - * - * Format it as an optional parameter in JSDoc land + * Infers param tags by reading function parameter names * - * @param {Object} param ESTree node - * @returns {Object} JSDoc param + * @param {Object} comment parsed comment + * @returns {Object} comment with parameters */ -function paramWithDefaultToDoc( - param, - comment, - i -) /*: CommentTag | Array */ { - var newParam = paramToDoc(param.left, comment, i, ''); +function inferParams(comment /*: Comment */) { + var path = findTarget(comment.context.ast); - var defaultValue = comment.context.code.substring( - param.right.start, - param.right.end - ); + // In case of `/** */ var x = function () {}` findTarget returns + // the declarator. + if (t.isVariableDeclarator(path)) { + path = path.get('init'); + } - // this is a destructuring parameter with defaults - if (Array.isArray(newParam)) { - newParam[0].default = defaultValue; - return newParam; + if (!t.isFunction(path)) { + return comment; } - var optionalParam /*: CommentTag */ = { - title: 'param', - name: newParam.name, - default: defaultValue - }; + var inferredParams = path.node.params.map((param, i) => + paramToDoc(param, '', i)); - if (newParam.type) { - optionalParam.type = { - type: 'OptionalType', - expression: newParam.type - }; - } + var mergedParams = mergeTrees(inferredParams, comment.params); - return optionalParam; + // Then merge the trees. This is the hard part. + return _.assign(comment, { + params: mergedParams + }); +} + +// Utility methods ============================================================ +// +const PATH_SPLIT_CAPTURING = /(\[])?(\.)/g; + +/** + * Index tags by their `name` property into an ES6 map. + */ +function mapTags(tags) { + return new Map( + tags.map(tag => { + return [tag.name, tag]; + }) + ); } /** @@ -69,192 +67,262 @@ function paramWithDefaultToDoc( * * @private * @param {Object} param the abstract syntax tree of the parameter in JavaScript - * @param {Object} comment the full comment object * @param {number} i the number of this parameter, in argument order * @param {string} prefix of the comment, if it is nested, like in the case of destructuring * @returns {Object} parameter with inference. */ function paramToDoc( param, - comment /*: Comment */, - i /*: number */, - prefix /*: string */ -) /*: Array | CommentTag */ { - function destructuringPropertyToDoc( - property - ) /*: Array | CommentTag */ { - if (property.type === 'ObjectProperty') { - return paramToDoc( - property.value, - comment, - i, - prefix + '$' + String(i) + '.' - ); - } else if (property.type === 'Identifier') { - // if the destructuring type is an array, the elements - // in it are identifiers - return paramToDoc(property, comment, i, prefix + '$' + String(i) + '.'); - } else if (property.type === 'RestProperty') { - return paramToDoc(property, comment, i, prefix + '$' + String(i) + '.'); - } else if (property.type === 'RestElement') { - return paramToDoc(property, comment, i, prefix + '$' + String(i) + '.'); - } - throw new Error(`Unknown property encountered: ${property.type}`); - } + prefix /*: string */, + i /*: ?number */ +) /*: CommentTag|Array */ { + const autoName = '$' + String(i); + const prefixedName = prefix + '.' + param.name; - function destructuringObjectParamToDoc(param) /*: Array */ { - return [ - { - title: 'param', - name: '$' + String(i), - type: flowDoctrine(param) || { - type: 'NameExpression', - name: 'Object' - } + switch (param.type) { + case 'AssignmentPattern': // (a = b) + const newAssignmentParam = paramToDoc(param.left, '', i); + + if (Array.isArray(newAssignmentParam)) { + throw new Error('Encountered an unexpected parameter type'); } - ].concat(param.properties.map(destructuringPropertyToDoc)); - } - function destructuringArrayParamToDoc(param) /*: Array */ { - return [ - { - title: 'param', - name: '$' + String(i), - type: flowDoctrine(param) || { - type: 'NameExpression', - name: 'Array' + return _.assign(newAssignmentParam, { + default: generate(param.right, { + compact: true + }).code, + type: { + type: 'OptionalType', + expression: newAssignmentParam.type } + }); + // ObjectPattern + case 'ObjectPattern': // { a } + if (prefix === '') { + // If this is a root-level param, like f({ x }), then we need to name + // it, like $0 or $1, depending on its position. + return { + title: 'param', + name: autoName, + anonymous: true, + type: (param.typeAnnotation && flowDoctrine(param)) || { + type: 'NameExpression', + name: 'Object' + }, + properties: _.flatMap(param.properties, prop => { + return paramToDoc(prop, prefix + autoName); + }) + }; + } else if (param.indexed) { + // Likewise, if this object pattern sits inside of an ArrayPattern, + // like [{ foo }], it shouldn't just look like $0.foo, but like $0.0.foo, + // so make sure it isn't indexed first. + return { + title: 'param', + name: prefixedName, + anonymous: true, + type: (param.typeAnnotation && flowDoctrine(param)) || { + type: 'NameExpression', + name: 'Object' + }, + properties: _.flatMap(param.properties, prop => { + return paramToDoc(prop, prefixedName); + }) + }; } - ].concat(param.elements.map(destructuringPropertyToDoc)); - } - - function restParamToDoc(param) /*: CommentTag */ { - let type /*: DoctrineType */ = { - type: 'RestType' - }; - if (param.typeAnnotation) { - type.expression = flowDoctrine(param.typeAnnotation.typeAnnotation); - } - var newParam = { - title: 'param', - name: param.argument.name, - lineNumber: param.loc.start.line, - type - }; - return newParam; - } - - // ES6 default - if (param.type === 'AssignmentPattern') { - return addPrefix(paramWithDefaultToDoc(param, comment, i), prefix); - } + // If, otherwise, this is nested, we don't really represent it as + // a parameter in and of itself - we just want its children, and + // it will be the . in obj.prop + return _.flatMap(param.properties, prop => { + return paramToDoc(prop, prefix); + }); + // ArrayPattern + case 'ArrayPattern': // ([a, b, { c }]) + if (prefix === '') { + return { + title: 'param', + name: autoName, + anonymous: true, + type: (param.typeAnnotation && flowDoctrine(param)) || { + type: 'NameExpression', + name: 'Array' + }, + // Array destructuring lets you name the elements in the array, + // but those names don't really make sense within the JSDoc + // indexing tradition, or have any external meaning. So + // instead we're going to (immutably) rename the parameters to their + // indices + properties: _.flatMap(param.elements, (element, idx) => { + var indexedElement = _.assign({}, element, { + name: String(idx), + indexed: true + }); + return paramToDoc(indexedElement, autoName); + }) + }; + } + return _.flatMap(param.elements, (element, idx) => { + var indexedElement = _.assign({}, element, { + name: String(idx) + }); + return paramToDoc(indexedElement, prefix); + }); + case 'ObjectProperty': + return _.assign(paramToDoc(param.value, prefix + '.' + param.key.name), { + name: prefix + '.' + param.key.name + }); + case 'RestProperty': // (a, ...b) + case 'RestElement': + let type /*: DoctrineType */ = { + type: 'RestType' + }; + if (param.typeAnnotation) { + type.expression = flowDoctrine(param.typeAnnotation.typeAnnotation); + } + return { + title: 'param', + name: param.argument.name, + name: prefix ? `${prefix}.${param.argument.name}` : param.argument.name, + lineNumber: param.loc.start.line, + type + }; + default: + // (a) + var newParam /*: CommentTagNamed */ = { + title: 'param', + name: prefix ? prefixedName : param.name, + lineNumber: param.loc.start.line + }; - if (param.type === 'ObjectPattern') { - return destructuringObjectParamToDoc(param); - } + // Flow/TS annotations + if (param.typeAnnotation && param.typeAnnotation.typeAnnotation) { + newParam.type = flowDoctrine(param.typeAnnotation.typeAnnotation); + } - if (param.type === 'ArrayPattern') { - return destructuringArrayParamToDoc(param); + return newParam; } +} - if (param.type === 'RestProperty' || param.type === 'RestElement') { - return addPrefix(restParamToDoc(param), prefix); +/** + * Recurse through a potentially nested parameter tag, + * replacing the auto-generated name, like $0, with an explicit + * name provided from a JSDoc comment. For instance, if you have a code + * block like + * + * function f({ x }); + * + * It would by default be documented with a first param $0, with a member $0.x + * + * If you specify the name of the param, then it could be documented with, say, + * options and options.x. So we need to recursively rename not just $0 but + * also $0.x and maybe $0.x.y.z all to options.x and options.x.y.z + */ +function renameTree(node, explicitName) { + var parts = node.name.split(PATH_SPLIT_CAPTURING); + parts[0] = explicitName; + node.name = parts.join(''); + if (node.properties) { + node.properties.forEach(property => renameTree(property, explicitName)); } +} - var newParam /*: CommentTag */ = { - title: 'param', - name: param.name, - lineNumber: param.loc.start.line - }; +function mergeTrees(inferred, explicit) { + // The first order of business is ensuring that the root types are specified + // in the right order. For the order of arguments, the inferred reality + // is the ground-truth: a function like + // function addThem(a, b, c) {} + // Should always see (a, b, c) in that order - // Flow/TS annotations - if (param.typeAnnotation && param.typeAnnotation.typeAnnotation) { - newParam.type = flowDoctrine(param.typeAnnotation.typeAnnotation); + // First, if all parameters are specified, allow explicit names to apply + // to destructuring parameters, which do not have inferred names. This is + // _only_ enabled in the case in which all parameters are specified explicitly + if (inferred.length === explicit.length) { + for (var i = 0; i < inferred.length; i++) { + if (inferred[i].anonymous === true) { + renameTree(inferred[i], explicit[i].name); + } + } } - return addPrefix(newParam, prefix); + return mergeTopNodes(inferred, explicit); } -function insertBeforeDependents(comment, comments) { - var dependentNamePrefix = comment.name + '.'; - for ( - var insertionIndex = 0; - insertionIndex < comments.length; - insertionIndex++ - ) { - let commentName = comments[insertionIndex].name; - if (commentName && commentName.indexOf(dependentNamePrefix) === 0) { - break; - } +function mergeTopNodes(inferred, explicit) { + const mapExplicit = mapTags(explicit); + const inferredNames = new Set(inferred.map(tag => tag.name)); + const explicitTagsWithoutInference = explicit.filter( + tag => !inferredNames.has(tag.name) + ); + + if (explicitTagsWithoutInference.length) { + debuglog( + `${explicitTagsWithoutInference.length} tags were specified but didn't match ` + + `inferred information ${explicitTagsWithoutInference + .map(t => t.name) + .join(', ')}` + ); } - return comments - .slice(0, insertionIndex) - .concat(comment) - .concat(comments.slice(insertionIndex)); + + return inferred + .map(inferredTag => { + const explicitTag = mapExplicit.get(inferredTag.name); + return explicitTag ? combineTags(inferredTag, explicitTag) : inferredTag; + }) + .concat(explicitTagsWithoutInference); } -/** - * Infers param tags by reading function parameter names - * - * @param {Object} comment parsed comment - * @returns {Object} comment with parameters - */ -function inferParams(comment /*: Comment */) { - var path = findTarget(comment.context.ast); +// This method is used for _non-root_ properties only - we use mergeTopNodes +// for root properties, which strictly requires inferred only. In this case, +// we combine all tags: +// - inferred & explicit +// - explicit only +// - inferred only +function mergeNodes(inferred, explicit) { + const intersection = _.intersectionBy(inferred, explicit, tag => tag.name); + const explicitOnly = _.differenceBy(explicit, inferred, tag => tag.name); + const inferredOnly = _.differenceBy(inferred, explicit, tag => tag.name); + const mapExplicit = mapTags(explicit); - // In case of `/** */ var x = function () {}` findTarget returns - // the declarator. - if (t.isVariableDeclarator(path)) { - path = path.get('init'); - } + return intersection + .map(inferredTag => { + const explicitTag = mapExplicit.get(inferredTag.name); + return explicitTag ? combineTags(inferredTag, explicitTag) : inferredTag; + }) + .concat(explicitOnly) + .concat(inferredOnly); +} - if (!t.isFunction(path)) { - return comment; +function combineTags(inferredTag, explicitTag) { + let type = explicitTag.type; + var defaultValue; + if (!explicitTag.type) { + type = inferredTag.type; + } else if (explicitTag.type.type !== 'OptionalType' && inferredTag.default) { + type = { + type: 'OptionalType', + expression: explicitTag.type + }; + defaultValue = inferredTag.default; } - // Ensure that explicitly specified parameters are not overridden - // by inferred parameters - var existingParams = {}; - comment.params.forEach(function(param) { - if (typeof param.name === 'string') { - existingParams[param.name] = param; - } - }); - - var paramOrder = {}; - var i = 0; + const hasProperties = (inferredTag.properties && + inferredTag.properties.length) || + (explicitTag.properties && explicitTag.properties.length); - path.node.params - .reduce( - function(params, param, i) { - return params.concat(paramToDoc(param, comment, i, '')); - }, - [] - ) - .forEach(function(doc) { - if (!existingParams.hasOwnProperty(doc.name)) { - // This type is not explicitly documented - comment.params = insertBeforeDependents(doc, comment.params); - } else if (!existingParams[doc.name].type) { - // This param has a description, but potentially it can - // be have an inferred type. Infer its type without - // dropping the description. - if (doc.type) { - existingParams[doc.name].type = doc.type; + return _.assign( + explicitTag, + hasProperties + ? { + properties: mergeNodes( + inferredTag.properties || [], + explicitTag.properties || [] + ) } - } else if ( - existingParams[doc.name].type.type !== 'OptionalType' && doc.default - ) { - existingParams[doc.name].type = { - type: 'OptionalType', - expression: existingParams[doc.name].type, - default: doc.default - }; - } - paramOrder[doc.name] = i++; - }); - - return comment; + : {}, + { type }, + defaultValue ? { default: defaultValue } : {} + ); } module.exports = inferParams; +module.exports.mergeTrees = mergeTrees; diff --git a/lib/nest.js b/lib/nest.js index 753adf6e3..5dd91d317 100644 --- a/lib/nest.js +++ b/lib/nest.js @@ -1,9 +1,35 @@ /* @flow */ 'use strict'; +var _ = require('lodash'); + +const PATH_SPLIT = /(?:\[])?\./g; + +function removeUnnamedTags( + tags /*: Array */ +) /*: Array */ { + return tags.filter(tag => typeof tag.name === 'string'); +} + +var tagDepth = tag => tag.name.split(PATH_SPLIT).length; + /** * Nest nestable tags, like param and property, into nested * arrays that are suitable for output. + * Okay, so we're building a tree of comments, with the tag.name + * being the indexer. We sort by depth, so that we add each + * level of the tree incrementally, and throw if we run against + * a node that doesn't have a parent. + * + * foo.abe + * foo.bar.baz + * foo.bar.a + * foo.bar[].bax + * + * foo -> .abe + * \-> .bar -> .baz + * \-> .a + * \-> [].baz * * @private * @param {Object} comment a comment with tags @@ -11,52 +37,50 @@ * @param {string} target the tag to nest into * @returns {Object} nested comment */ -function nestTag( - comment /*: Comment */, - tagTitle /*: string */, - target /*: string */ -) { - if (!comment[target]) { - return comment; - } +var nestTag = ( + tags /*: Array */ + // Use lodash here both for brevity and also because, unlike JavaScript's + // sort, it's stable. +) => + _.sortBy(removeUnnamedTags(tags), tagDepth).reduce( + (memo, tag) => { + function insertTag(node, parts) { + // The 'done' case: we're at parts.length === 1, + // this is where the node should be placed. We reliably + // get to this case because the recursive method + // is always passed parts.slice(1) + if (parts.length === 1) { + _.assign(node, { + properties: (node.properties || []).concat(tag) + }); + } else { + // The recursive case: try to find the child that owns + // this tag. + let child = node.properties && + node.properties.find( + property => parts[0] === _.last(property.name.split(PATH_SPLIT)) + ); - var result = [], index = {}; + if (!child) { + if (tag.name.match(/^(\$\d+)/)) { + throw new Error( + `Parent of ${tag.name} not found. To document a destructuring\n` + + `type, add a @param tag in its position to specify the name of the\n` + + `destructured parameter` + ); + } + throw new Error(`Parent of ${tag.name} not found`); + } - comment[target].forEach(tag => { - var tagName = tag.name; - if (tagName) { - index[tagName] = tag; - var parts = tagName - .split(/(\[\])?\./) - .filter(part => part && part !== '[]'); - if (parts.length > 1) { - var parent = index[parts.slice(0, -1).join('.')]; - if (parent === undefined) { - comment.errors.push({ - message: '@' + - tagTitle + - ' ' + - tag.name + - "'s parent " + - parts[0] + - ' not found', - commentLineNumber: tag.lineNumber - }); - result.push(tag); - return; + insertTag(child, parts.slice(1)); } - parent.properties = parent.properties || []; - parent.properties.push(tag); - } else { - result.push(tag); } - } - }); - comment[target] = result; - - return comment; -} + insertTag(memo, tag.name.split(PATH_SPLIT)); + return memo; + }, + { properties: [] } + ).properties; /** * Nests @@ -70,8 +94,11 @@ function nestTag( * @param {Object} comment input comment * @return {Object} nested comment */ -function nest(comment /*: Comment*/) { - return nestTag(nestTag(comment, 'param', 'params'), 'property', 'properties'); -} +var nest = (comment /*: Comment*/) => + _.assign(comment, { + params: nestTag(comment.params), + properties: nestTag(comment.properties) + }); module.exports = nest; +module.exports.nestTag = nestTag; diff --git a/lib/output/markdown_ast.js b/lib/output/markdown_ast.js index 29e0537a3..2a7f10bec 100644 --- a/lib/output/markdown_ast.js +++ b/lib/output/markdown_ast.js @@ -72,6 +72,7 @@ function buildMarkdownAST( } function paramList(params /*: Array */) { + if (params.length === 0) return []; return u( 'list', { ordered: false }, diff --git a/lib/output/util/format_type.js b/lib/output/util/format_type.js index 072542866..37d79b8b4 100644 --- a/lib/output/util/format_type.js +++ b/lib/output/util/format_type.js @@ -1,6 +1,7 @@ /* @flow */ 'use strict'; -var Syntax = require('doctrine').Syntax, u = require('unist-builder'); +var Syntax = require('doctrine-temporary-fork').Syntax, + u = require('unist-builder'); /** * Shortcut to create a new text node diff --git a/lib/output/util/formatters.js b/lib/output/util/formatters.js index c368c3eb2..861df2114 100644 --- a/lib/output/util/formatters.js +++ b/lib/output/util/formatters.js @@ -2,7 +2,7 @@ 'use strict'; var remark = require('remark'), html = require('remark-html'), - Syntax = require('doctrine').Syntax, + Syntax = require('doctrine-temporary-fork').Syntax, u = require('unist-builder'), _rerouteLinks = require('./reroute_links'), highlighter = require('../highlighter'), diff --git a/lib/parse.js b/lib/parse.js index eccd60573..653a0a9a8 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1,7 +1,7 @@ 'use strict'; /* @flow */ -var doctrine = require('doctrine'); +var doctrine = require('doctrine-temporary-fork'); var parseMarkdown = require('./parse_markdown'); /** diff --git a/package.json b/package.json index 105c69efc..1b5c8ebfa 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "chokidar": "^1.2.0", "concat-stream": "^1.5.0", "disparity": "^2.0.0", - "doctrine": "^2.0.0", + "doctrine-temporary-fork": "2.0.0-alpha-allowarrayindex", "get-comments": "^1.0.1", "git-url-parse": "^6.0.1", "github-slugger": "1.1.1", diff --git a/test/fixture/es6.output-toc.md b/test/fixture/es6.output-toc.md index 95a3c2c68..4060592c0 100644 --- a/test/fixture/es6.output-toc.md +++ b/test/fixture/es6.output-toc.md @@ -7,9 +7,9 @@ have any parameter descriptions. **Parameters** -- `$0` **any** (optional, default `{}`) - - `$0.phoneNumbers` (optional, default `[]`) - - `$0.emailAddresses` (optional, default `[]`) +- `$0` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** (optional, default `{}`) + - `$0.phoneNumbers` **any?** (optional, default `[]`) + - `$0.emailAddresses` **any?** (optional, default `[]`) - `$0.params` **...any** ## destructure @@ -18,10 +18,10 @@ Similar, but with an array **Parameters** -- `$0` **any** - - `$0.a` - - `$0.b` - - `$0.c` +- `$0` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** + - `$0.0` + - `$0.1` + - `$0.2` **Examples** @@ -113,7 +113,7 @@ This tests our support of optional parameters in ES6 **Parameters** -- `foo` (optional, default `'bar'`) +- `foo` **any?** (optional, default `'bar'`) ## iAmProtected @@ -135,6 +135,6 @@ Regression check for #498 - `array1` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<T>** - `array2` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<T>** -- `compareFunction` **function (a: T, b: T): [boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `(a: T, b: T): boolean => a === b`) +- `compareFunction` **function (a: T, b: T): [boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `(a:T,b:T):boolean=>a===b`) Returns **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** diff --git a/test/fixture/es6.output.json b/test/fixture/es6.output.json index 7ada1a342..2f66ac9b6 100644 --- a/test/fixture/es6.output.json +++ b/test/fixture/es6.output.json @@ -86,20 +86,32 @@ { "title": "param", "name": "$0", + "anonymous": true, "type": { - "type": "AllLiteral" + "type": "OptionalType", + "expression": { + "type": "NameExpression", + "name": "Object" + } }, - "default": "{}", "properties": [ { "title": "param", "name": "$0.phoneNumbers", - "default": "[]" + "lineNumber": 6, + "default": "[]", + "type": { + "type": "OptionalType" + } }, { "title": "param", "name": "$0.emailAddresses", - "default": "[]" + "lineNumber": 6, + "default": "[]", + "type": { + "type": "OptionalType" + } }, { "title": "param", @@ -109,7 +121,8 @@ "type": "RestType" } } - ] + ], + "default": "{}" } ], "properties": [], @@ -227,23 +240,25 @@ { "title": "param", "name": "$0", + "anonymous": true, "type": { - "type": "AllLiteral" + "type": "NameExpression", + "name": "Array" }, "properties": [ { "title": "param", - "name": "$0.a", + "name": "$0.0", "lineNumber": 14 }, { "title": "param", - "name": "$0.b", + "name": "$0.1", "lineNumber": 14 }, { "title": "param", - "name": "$0.c", + "name": "$0.2", "lineNumber": 14 } ] @@ -2299,7 +2314,11 @@ { "title": "param", "name": "foo", - "default": "'bar'" + "lineNumber": 114, + "default": "'bar'", + "type": { + "type": "OptionalType" + } } ], "properties": [], @@ -2761,7 +2780,7 @@ { "title": "param", "name": "compareFunction", - "default": "(a: T, b: T): boolean => a === b", + "lineNumber": 153, "type": { "type": "OptionalType", "expression": { @@ -2789,7 +2808,8 @@ "name": "boolean" } } - } + }, + "default": "(a:T,b:T):boolean=>a===b" } ], "properties": [], diff --git a/test/fixture/es6.output.md b/test/fixture/es6.output.md index 985716788..416e00c6a 100644 --- a/test/fixture/es6.output.md +++ b/test/fixture/es6.output.md @@ -30,9 +30,9 @@ have any parameter descriptions. **Parameters** -- `$0` **any** (optional, default `{}`) - - `$0.phoneNumbers` (optional, default `[]`) - - `$0.emailAddresses` (optional, default `[]`) +- `$0` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** (optional, default `{}`) + - `$0.phoneNumbers` **any?** (optional, default `[]`) + - `$0.emailAddresses` **any?** (optional, default `[]`) - `$0.params` **...any** ## destructure @@ -41,10 +41,10 @@ Similar, but with an array **Parameters** -- `$0` **any** - - `$0.a` - - `$0.b` - - `$0.c` +- `$0` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** + - `$0.0` + - `$0.1` + - `$0.2` **Examples** @@ -136,7 +136,7 @@ This tests our support of optional parameters in ES6 **Parameters** -- `foo` (optional, default `'bar'`) +- `foo` **any?** (optional, default `'bar'`) ## iAmProtected @@ -158,6 +158,6 @@ Regression check for #498 - `array1` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<T>** - `array2` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<T>** -- `compareFunction` **function (a: T, b: T): [boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `(a: T, b: T): boolean => a === b`) +- `compareFunction` **function (a: T, b: T): [boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `(a:T,b:T):boolean=>a===b`) Returns **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** diff --git a/test/fixture/es6.output.md.json b/test/fixture/es6.output.md.json index 7826d97d5..3c6e7eb2b 100644 --- a/test/fixture/es6.output.md.json +++ b/test/fixture/es6.output.md.json @@ -84,9 +84,20 @@ { "type": "strong", "children": [ + { + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", + "type": "link", + "children": [ + { + "type": "text", + "value": "Object" + } + ] + }, { "type": "text", - "value": "any" + "value": "?" } ] }, @@ -131,6 +142,19 @@ "type": "text", "value": " " }, + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "any" + }, + { + "type": "text", + "value": "?" + } + ] + }, { "type": "text", "value": " " @@ -170,6 +194,19 @@ "type": "text", "value": " " }, + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "any" + }, + { + "type": "text", + "value": "?" + } + ] + }, { "type": "text", "value": " " @@ -312,8 +349,15 @@ "type": "strong", "children": [ { - "type": "text", - "value": "any" + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array", + "type": "link", + "children": [ + { + "type": "text", + "value": "Array" + } + ] } ] }, @@ -335,7 +379,7 @@ "children": [ { "type": "inlineCode", - "value": "$0.a" + "value": "$0.0" }, { "type": "text", @@ -357,7 +401,7 @@ "children": [ { "type": "inlineCode", - "value": "$0.b" + "value": "$0.1" }, { "type": "text", @@ -379,7 +423,7 @@ "children": [ { "type": "inlineCode", - "value": "$0.c" + "value": "$0.2" }, { "type": "text", @@ -1773,6 +1817,19 @@ "type": "text", "value": " " }, + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "any" + }, + { + "type": "text", + "value": "?" + } + ] + }, { "type": "text", "value": " " @@ -2172,7 +2229,7 @@ }, { "type": "inlineCode", - "value": "(a: T, b: T): boolean => a === b" + "value": "(a:T,b:T):boolean=>a===b" }, { "type": "text", diff --git a/test/fixture/params.input.js b/test/fixture/params.input.js index cea9becdf..50e7b514e 100644 --- a/test/fixture/params.input.js +++ b/test/fixture/params.input.js @@ -8,7 +8,8 @@ function addThem(a, b, c, { d, e, f }) { /** * This method has partially inferred params - * @param {String} $0.fishes number of kinds of fish + * @param {Object} options + * @param {String} options.fishes number of kinds of fish */ function fishesAndFoxes({ fishes, foxes }) { return fishes + foxes; @@ -99,8 +100,9 @@ function foo(address) { * This tests our support for iterator rest inside an * iterator destructure (RestElement) * - * @param {any} $0.x head of iterator - * @param {any[]} ...$0.xs body of iterator + * @param {Array} input + * @param {any} input.0 head of iterator + * @param {...any} input.xs body of iterator * * @returns {any[]} rotated such that the last element was the first */ diff --git a/test/fixture/params.output.json b/test/fixture/params.output.json index 01241aec6..129cc02b3 100644 --- a/test/fixture/params.output.json +++ b/test/fixture/params.output.json @@ -90,6 +90,11 @@ "errors": [], "examples": [], "params": [ + { + "title": "param", + "name": "a", + "lineNumber": 5 + }, { "title": "param", "name": "b", @@ -151,11 +156,6 @@ "name": "number" } }, - { - "title": "param", - "name": "a", - "lineNumber": 5 - }, { "title": "param", "name": "c", @@ -164,8 +164,10 @@ { "title": "param", "name": "$3", + "anonymous": true, "type": { - "type": "AllLiteral" + "type": "NameExpression", + "name": "Object" }, "properties": [ { @@ -264,13 +266,23 @@ "tags": [ { "title": "param", - "description": "number of kinds of fish", + "description": null, "lineNumber": 2, + "type": { + "type": "NameExpression", + "name": "Object" + }, + "name": "options" + }, + { + "title": "param", + "description": "number of kinds of fish", + "lineNumber": 3, "type": { "type": "NameExpression", "name": "String" }, - "name": "$0.fishes" + "name": "options.fishes" } ], "loc": { @@ -279,18 +291,18 @@ "column": 0 }, "end": { - "line": 12, + "line": 13, "column": 3 } }, "context": { "loc": { "start": { - "line": 13, + "line": 14, "column": 0 }, "end": { - "line": 15, + "line": 16, "column": 1 } } @@ -301,15 +313,17 @@ "params": [ { "title": "param", - "name": "$0", + "name": "options", + "lineNumber": 2, "type": { - "type": "AllLiteral" + "type": "NameExpression", + "name": "Object" }, "properties": [ { "title": "param", - "name": "$0.fishes", - "lineNumber": 2, + "name": "options.fishes", + "lineNumber": 3, "description": { "type": "root", "children": [ @@ -369,8 +383,8 @@ }, { "title": "param", - "name": "$0.foxes", - "lineNumber": 13 + "name": "options.foxes", + "lineNumber": 14 } ] } @@ -464,22 +478,22 @@ ], "loc": { "start": { - "line": 17, + "line": 18, "column": 0 }, "end": { - "line": 20, + "line": 21, "column": 3 } }, "context": { "loc": { "start": { - "line": 21, + "line": 22, "column": 0 }, "end": { - "line": 23, + "line": 24, "column": 1 } } @@ -497,9 +511,9 @@ "expression": { "type": "NameExpression", "name": "number" - }, - "default": "2" - } + } + }, + "default": "2" } ], "properties": [], @@ -580,22 +594,22 @@ "tags": [], "loc": { "start": { - "line": 25, + "line": 26, "column": 0 }, "end": { - "line": 27, + "line": 28, "column": 3 } }, "context": { "loc": { "start": { - "line": 28, + "line": 29, "column": 0 }, "end": { - "line": 34, + "line": 35, "column": 1 } } @@ -682,22 +696,22 @@ ], "loc": { "start": { - "line": 29, + "line": 30, "column": 2 }, "end": { - "line": 32, + "line": 33, "column": 5 } }, "context": { "loc": { "start": { - "line": 33, + "line": 34, "column": 2 }, "end": { - "line": 33, + "line": 34, "column": 14 } } @@ -865,22 +879,22 @@ "tags": [], "loc": { "start": { - "line": 36, + "line": 37, "column": 0 }, "end": { - "line": 38, + "line": 39, "column": 3 } }, "context": { "loc": { "start": { - "line": 39, + "line": 40, "column": 0 }, "end": { - "line": 46, + "line": 47, "column": 2 } } @@ -957,22 +971,22 @@ "tags": [], "loc": { "start": { - "line": 40, + "line": 41, "column": 2 }, "end": { - "line": 42, + "line": 43, "column": 5 } }, "context": { "loc": { "start": { - "line": 43, + "line": 44, "column": 2 }, "end": { - "line": 45, + "line": 46, "column": 3 } } @@ -984,7 +998,7 @@ { "title": "param", "name": "x", - "lineNumber": 43 + "lineNumber": 44 } ], "properties": [], @@ -1180,22 +1194,22 @@ ], "loc": { "start": { - "line": 48, + "line": 49, "column": 0 }, "end": { - "line": 59, + "line": 60, "column": 3 } }, "context": { "loc": { "start": { - "line": 60, + "line": 61, "column": 0 }, "end": { - "line": 60, + "line": 61, "column": 22 } } @@ -1658,22 +1672,22 @@ ], "loc": { "start": { - "line": 62, + "line": 63, "column": 0 }, "end": { - "line": 73, + "line": 74, "column": 3 } }, "context": { "loc": { "start": { - "line": 74, + "line": 75, "column": 0 }, "end": { - "line": 76, + "line": 77, "column": 1 } } @@ -2118,22 +2132,22 @@ ], "loc": { "start": { - "line": 78, + "line": 79, "column": 0 }, "end": { - "line": 85, + "line": 86, "column": 3 } }, "context": { "loc": { "start": { - "line": 86, + "line": 87, "column": 0 }, "end": { - "line": 86, + "line": 87, "column": 37 } } @@ -2203,9 +2217,9 @@ "expression": { "type": "NameExpression", "name": "number" - }, - "default": "123" - } + } + }, + "default": "123" } ], "properties": [], @@ -2358,22 +2372,22 @@ ], "loc": { "start": { - "line": 88, + "line": 89, "column": 0 }, "end": { - "line": 93, + "line": 94, "column": 3 } }, "context": { "loc": { "start": { - "line": 94, + "line": 95, "column": 0 }, "end": { - "line": 96, + "line": 97, "column": 1 } } @@ -2522,40 +2536,41 @@ "tags": [ { "title": "param", - "description": "head of iterator", + "description": null, "lineNumber": 4, "type": { "type": "NameExpression", - "name": "any" + "name": "Array" }, - "name": "$0.x" + "name": "input" }, { "title": "param", - "description": "...$0.xs body of iterator", + "description": "head of iterator", "lineNumber": 5, "type": { - "type": "TypeApplication", + "type": "NameExpression", + "name": "any" + }, + "name": "input.0" + }, + { + "title": "param", + "description": "body of iterator", + "lineNumber": 6, + "type": { + "type": "RestType", "expression": { "type": "NameExpression", - "name": "Array" - }, - "applications": [ - { - "type": "NameExpression", - "name": "any" - } - ] + "name": "any" + } }, - "name": null, - "errors": [ - "Missing or invalid tag name" - ] + "name": "input.xs" }, { "title": "returns", "description": "rotated such that the last element was the first", - "lineNumber": 7, + "lineNumber": 8, "type": { "type": "TypeApplication", "expression": { @@ -2573,45 +2588,43 @@ ], "loc": { "start": { - "line": 98, + "line": 99, "column": 0 }, "end": { - "line": 106, + "line": 108, "column": 3 } }, "context": { "loc": { "start": { - "line": 107, + "line": 109, "column": 0 }, "end": { - "line": 109, + "line": 111, "column": 1 } } }, "augments": [], - "errors": [ - { - "message": "Missing or invalid tag name" - } - ], + "errors": [], "examples": [], "params": [ { "title": "param", - "name": "$0", + "name": "input", + "lineNumber": 4, "type": { - "type": "AllLiteral" + "type": "NameExpression", + "name": "Array" }, "properties": [ { "title": "param", - "name": "$0.x", - "lineNumber": 4, + "name": "input.0", + "lineNumber": 5, "description": { "type": "root", "children": [ @@ -2671,10 +2684,66 @@ }, { "title": "param", - "name": "$0.xs", - "lineNumber": 107, + "name": "input.xs", + "lineNumber": 6, + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "body of iterator", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 17, + "offset": 16 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 17, + "offset": 16 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 17, + "offset": 16 + } + } + }, "type": { - "type": "RestType" + "type": "RestType", + "expression": { + "type": "NameExpression", + "name": "any" + } } } ] diff --git a/test/fixture/params.output.md b/test/fixture/params.output.md index c9df3621a..6413e14bf 100644 --- a/test/fixture/params.output.md +++ b/test/fixture/params.output.md @@ -21,10 +21,10 @@ This function returns the number one. **Parameters** -- `b` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** the second param - `a` +- `b` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** the second param - `c` -- `$3` **any** +- `$3` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** - `$3.d` - `$3.e` - `$3.f` @@ -35,9 +35,9 @@ This method has partially inferred params **Parameters** -- `$0` **any** - - `$0.fishes` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** number of kinds of fish - - `$0.foxes` +- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** + - `options.fishes` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** number of kinds of fish + - `options.foxes` ## withDefault @@ -45,7 +45,7 @@ This method has a type in the description and a default in the code **Parameters** -- `x` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?= 2** +- `x` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** (optional, default `2`) ## Foo @@ -112,7 +112,7 @@ values specified in code. **Parameters** -- `x` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?= 123** an argument +- `x` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** an argument (optional, default `123`) Returns **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** some @@ -132,8 +132,8 @@ iterator destructure (RestElement) **Parameters** -- `$0` **any** - - `$0.x` **any** head of iterator - - `$0.xs` **...any** +- `input` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** + - `input.0` **any** head of iterator + - `input.xs` **...any** body of iterator Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<any>** rotated such that the last element was the first diff --git a/test/fixture/params.output.md.json b/test/fixture/params.output.md.json index f8f56c216..69f263493 100644 --- a/test/fixture/params.output.md.json +++ b/test/fixture/params.output.md.json @@ -63,6 +63,28 @@ "ordered": false, "type": "list", "children": [ + { + "type": "listItem", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "a" + }, + { + "type": "text", + "value": " " + }, + { + "type": "text", + "value": " " + } + ] + } + ] + }, { "type": "listItem", "children": [ @@ -136,28 +158,6 @@ } ] }, - { - "type": "listItem", - "children": [ - { - "type": "paragraph", - "children": [ - { - "type": "inlineCode", - "value": "a" - }, - { - "type": "text", - "value": " " - }, - { - "type": "text", - "value": " " - } - ] - } - ] - }, { "type": "listItem", "children": [ @@ -198,8 +198,15 @@ "type": "strong", "children": [ { - "type": "text", - "value": "any" + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", + "type": "link", + "children": [ + { + "type": "text", + "value": "Object" + } + ] } ] }, @@ -351,7 +358,7 @@ "children": [ { "type": "inlineCode", - "value": "$0" + "value": "options" }, { "type": "text", @@ -361,8 +368,15 @@ "type": "strong", "children": [ { - "type": "text", - "value": "any" + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", + "type": "link", + "children": [ + { + "type": "text", + "value": "Object" + } + ] } ] }, @@ -384,7 +398,7 @@ "children": [ { "type": "inlineCode", - "value": "$0.fishes" + "value": "options.fishes" }, { "type": "text", @@ -457,7 +471,7 @@ "children": [ { "type": "inlineCode", - "value": "$0.foxes" + "value": "options.foxes" }, { "type": "text", @@ -566,16 +580,29 @@ { "type": "text", "value": "?" - }, - { - "type": "text", - "value": "= 2" } ] }, { "type": "text", "value": " " + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " (optional, default " + }, + { + "type": "inlineCode", + "value": "2" + }, + { + "type": "text", + "value": ")" + } + ] } ] } @@ -1954,10 +1981,6 @@ { "type": "text", "value": "?" - }, - { - "type": "text", - "value": "= 123" } ] }, @@ -1999,6 +2022,23 @@ }, "indent": [] } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": " (optional, default " + }, + { + "type": "inlineCode", + "value": "123" + }, + { + "type": "text", + "value": ")" + } + ] } ] } @@ -2261,7 +2301,7 @@ "children": [ { "type": "inlineCode", - "value": "$0" + "value": "input" }, { "type": "text", @@ -2271,8 +2311,15 @@ "type": "strong", "children": [ { - "type": "text", - "value": "any" + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array", + "type": "link", + "children": [ + { + "type": "text", + "value": "Array" + } + ] } ] }, @@ -2294,7 +2341,7 @@ "children": [ { "type": "inlineCode", - "value": "$0.x" + "value": "input.0" }, { "type": "text", @@ -2360,7 +2407,7 @@ "children": [ { "type": "inlineCode", - "value": "$0.xs" + "value": "input.xs" }, { "type": "text", @@ -2382,6 +2429,41 @@ { "type": "text", "value": " " + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "body of iterator", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 17, + "offset": 16 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 17, + "offset": 16 + }, + "indent": [] + } } ] } diff --git a/test/format_type.js b/test/format_type.js index 827a3dcca..667c7613d 100644 --- a/test/format_type.js +++ b/test/format_type.js @@ -4,7 +4,7 @@ var _formatType = require('../lib/output/util/format_type'), LinkerStack = require('../lib/output/util/linker_stack'), remark = require('remark'), - parse = require('doctrine').parse, + parse = require('doctrine-temporary-fork').parse, test = require('tap').test; function stringify(children) { diff --git a/test/lib/infer/params.js b/test/lib/infer/params.js index 628f3b71a..8049ee6de 100644 --- a/test/lib/infer/params.js +++ b/test/lib/infer/params.js @@ -18,6 +18,101 @@ function evaluate(fn, file) { return inferParams(toComment(fn, file)); } +test('mergeTrees', function(t) { + t.deepEqual( + inferParams.mergeTrees( + [], + [ + { + title: 'param', + description: 'First arg!', + name: 'a', + type: { + type: 'NameExpression', + name: 'string' + } + } + ] + ), + [ + { + title: 'param', + description: 'First arg!', + name: 'a', + type: { + type: 'NameExpression', + name: 'string' + } + } + ] + ); + + t.deepEqual( + inferParams.mergeTrees( + [ + { + title: 'param', + name: '$0', + anonymous: true, + parameterIndex: 0, + type: { + type: 'NameExpression', + name: 'object' + }, + properties: [ + { + title: 'param', + name: '$0.a', + parameterIndex: 0, + type: { + type: 'NameExpression', + name: 'string' + }, + properties: [] + } + ] + } + ], + [ + { + title: 'param', + description: 'First arg!', + name: 'a', + type: { + type: 'NameExpression', + name: 'object' + } + } + ] + ), + [ + { + title: 'param', + description: 'First arg!', + name: 'a', + type: { + type: 'NameExpression', + name: 'object' + }, + properties: [ + { + title: 'param', + name: 'a.a', + parameterIndex: 0, + type: { + type: 'NameExpression', + name: 'string' + }, + properties: [] + } + ] + } + ] + ); + + t.end(); +}); + test('inferParams', function(t) { t.deepEqual( evaluate(function() { @@ -35,6 +130,119 @@ test('inferParams', function(t) { [{ lineNumber: 3, name: 'x', title: 'param' }] ); + t.deepEqual( + evaluate(`/** Test */function f({ x, ...xs }) {};`).params, + [ + { + title: 'param', + name: '$0', + anonymous: true, + type: { + type: 'NameExpression', + name: 'Object' + }, + properties: [ + { + title: 'param', + name: '$0.x', + lineNumber: 1 + }, + { + title: 'param', + name: '$0.xs', + lineNumber: 1, + type: { + type: 'RestType' + } + } + ] + } + ], + 'object spread property' + ); + + t.deepEqual( + evaluate( + ` + /** + * Test + * @param {Object} a renamed destructuring param + */ + var f = function({ x }) {}; + ` + ).params, + [ + { + description: { + children: [ + { + children: [ + { + position: { + end: { + column: 28, + line: 1, + offset: 27 + }, + indent: [], + start: { + column: 1, + line: 1, + offset: 0 + } + }, + type: 'text', + value: 'renamed destructuring param' + } + ], + position: { + end: { + column: 28, + line: 1, + offset: 27 + }, + indent: [], + start: { + column: 1, + line: 1, + offset: 0 + } + }, + type: 'paragraph' + } + ], + position: { + end: { + column: 28, + line: 1, + offset: 27 + }, + start: { + column: 1, + line: 1, + offset: 0 + } + }, + type: 'root' + }, + lineNumber: 2, + name: 'a', + properties: [ + { + lineNumber: 6, + name: 'a.x', + title: 'param' + } + ], + title: 'param', + type: { + name: 'Object', + type: 'NameExpression' + } + } + ] + ); + t.deepEqual(evaluate('/** Test */ var f = (x) => {}').params, [ { lineNumber: 1, name: 'x', title: 'param' } ]); @@ -49,6 +257,111 @@ test('inferParams', function(t) { [{ lineNumber: 5, name: 'x', title: 'param' }] ); + t.deepEqual( + evaluate( + ` + /** Test */ + function f(x = 4) {} + ` + ).params, + [ + { + default: '4', + name: 'x', + title: 'param', + lineNumber: 3, + type: { + expression: null, + type: 'OptionalType' + } + } + ], + 'default params' + ); + + t.deepEqual( + evaluate( + ` + /** Test + * @param {number} x + */ + function f(x = 4) {} + ` + ).params, + [ + { + default: '4', + name: 'x', + title: 'param', + lineNumber: 1, + type: { + expression: { + type: 'NameExpression', + name: 'number' + }, + type: 'OptionalType' + } + } + ], + 'default params with type' + ); + + t.deepEqual( + evaluate( + ` + /** Test */ + function f({ x: y }) {} + ` + ).params, + [ + { + anonymous: true, + name: '$0', + properties: [ + { + lineNumber: 3, + name: '$0.x', + title: 'param' + } + ], + title: 'param', + type: { + name: 'Object', + type: 'NameExpression' + } + } + ], + 'renaming' + ); + + t.deepEqual( + evaluate( + ` + /** Test */ + function f({ x: { y: { z } } }) {} + ` + ).params, + [ + { + anonymous: true, + name: '$0', + properties: [ + { + lineNumber: 3, + name: '$0.x.y.z', + title: 'param' + } + ], + title: 'param', + type: { + name: 'Object', + type: 'NameExpression' + } + } + ], + 'renaming' + ); + t.deepEqual(evaluate('/** Test */ export function f(x) {}').params, [ { lineNumber: 1, name: 'x', title: 'param' } ]); diff --git a/test/lib/nest.js b/test/lib/nest.js index 8210ce45b..fb603cabd 100644 --- a/test/lib/nest.js +++ b/test/lib/nest.js @@ -1,101 +1,117 @@ 'use strict'; -var test = require('tap').test, - parse = require('../../lib/parsers/javascript'), - nest = require('../../lib/nest'); +var test = require('tap').test; +var nestTag = require('../../lib/nest').nestTag; -function toComment(fn, filename) { - return parse( - { - file: filename, - source: fn instanceof Function ? '(' + fn.toString() + ')' : fn - }, - {} - ).map(nest); -} +// Print a tree of tags in a way that's easy to test. +var printTree = indent => + node => + `${new Array(indent + 1).join(' ')}- ${node.name}${node.properties ? '\n' : ''}${(node.properties || [ + ]) + .map(printTree(indent + 1)) + .join('\n')}`; -test('nest params - no params', function(t) { - t.deepEqual( - toComment(function() { - /** @name foo */ - })[0].params, - [], - 'no params' - ); - t.end(); -}); +var printNesting = params => + printTree(0)({ properties: nestTag(params), name: 'root' }); -test('nest params - no nesting', function(t) { - var result = toComment(function() { - /** @param {Object} foo */ - }); - t.equal(result[0].params.length, 1); - t.equal(result[0].params[0].name, 'foo'); - t.equal(result[0].params[0].properties, undefined); +test('nest params - basic', function(t) { + var params = [ + 'foo', + 'foo.bar', + 'foo.bar.third', + 'foo.third', + 'foo.third[].baz' + ].map(name => ({ name })); + t.equal( + printNesting(params), + `- root + - foo + - foo.bar + - foo.bar.third + - foo.third + - foo.third[].baz` + ); t.end(); }); -test('nest params - basic', function(t) { - var result = toComment(function() { - /** - * @param {Object} foo - * @param {string} foo.bar - * @param {string} foo.baz - */ - }); - t.equal(result[0].params.length, 1); - t.equal(result[0].params[0].name, 'foo'); - t.equal(result[0].params[0].properties.length, 2); - t.equal(result[0].params[0].properties[0].name, 'foo.bar'); - t.equal(result[0].params[0].properties[1].name, 'foo.baz'); +test('nest params - multiple roots', function(t) { + var params = ['a', 'b', 'c'].map(name => ({ name })); + t.equal( + printNesting(params), + `- root + - a + - b + - c` + ); t.end(); }); -test('nest properties - basic', function(t) { - var result = toComment(function() { - /** - * @property {Object} foo - * @property {string} foo.bar - * @property {string} foo.baz - */ +test('nest params - missing parent', function(t) { + var params = ['foo', 'foo.bar.third'].map(name => ({ name })); + t.throws(() => { + nestTag(params); }); - t.equal(result[0].properties.length, 1); - t.equal(result[0].properties[0].name, 'foo'); - t.equal(result[0].properties[0].properties.length, 2); - t.equal(result[0].properties[0].properties[0].name, 'foo.bar'); - t.equal(result[0].properties[0].properties[1].name, 'foo.baz'); t.end(); }); -test('nest params - array', function(t) { - var result = toComment(function() { - /** - * @param {Object[]} employees - The employees who are responsible for the project. - * @param {string} employees[].name - The name of an employee. - * @param {string} employees[].department - The employee's department. - */ - }); - t.equal(result[0].params.length, 1); - t.equal(result[0].params[0].name, 'employees'); - t.equal(result[0].params[0].properties.length, 2); - t.equal(result[0].params[0].properties[0].name, 'employees[].name'); - t.equal(result[0].params[0].properties[1].name, 'employees[].department'); +test('nest params - #658', function(t) { + var params = [ + 'state', + 'payload', + 'payload.input_meter_levels', + 'payload.input_meter_levels[].peak', + 'payload.input_meter_levels[].rms', + 'payload.output_meter_levels', + 'payload.output_meter_levels[].peak', + 'payload.output_meter_levels[].rms' + ].map(name => ({ name })); + t.equal( + printNesting(params), + `- root + - state + - payload + - payload.input_meter_levels + - payload.input_meter_levels[].peak + - payload.input_meter_levels[].rms + - payload.output_meter_levels + - payload.output_meter_levels[].peak + - payload.output_meter_levels[].rms` + ); t.end(); }); -test('nest params - missing parent', function(t) { - var result = toComment(function() { - /** @param {string} foo.bar */ - }); - t.equal(result[0].params.length, 1); - t.deepEqual( - result[0].errors[0], - { - message: "@param foo.bar's parent foo not found", - commentLineNumber: 0 - }, - 'correct error message' +test('nest params - #554', function(t) { + var params = [ + 'x', + 'yIn', + 'options', + 'options.sgOption', + 'options.minMaxRatio', + 'options.broadRatio', + 'options.noiseLevel', + 'options.maxCriteria', + 'options.smoothY', + 'options.realTopDetection', + 'options.heightFactor', + 'options.boundaries', + 'options.derivativeThreshold' + ].map(name => ({ name })); + t.equal( + printNesting(params), + `- root + - x + - yIn + - options + - options.sgOption + - options.minMaxRatio + - options.broadRatio + - options.noiseLevel + - options.maxCriteria + - options.smoothY + - options.realTopDetection + - options.heightFactor + - options.boundaries + - options.derivativeThreshold` ); - t.equal(result[0].params[0].name, 'foo.bar'); t.end(); }); diff --git a/test/lib/parse.js b/test/lib/parse.js index 558979c2a..ebc18d566 100644 --- a/test/lib/parse.js +++ b/test/lib/parse.js @@ -115,7 +115,9 @@ test('parse - @description', function(t) { * @description This tagged description wins, and [is markdown](http://markdown.com). */ })[0].description, - remark().parse('This tagged description wins, and [is markdown](http://markdown.com).'), + remark().parse( + 'This tagged description wins, and [is markdown](http://markdown.com).' + ), 'description' );