diff --git a/index.js b/index.js index 4adffa3..5d9e379 100644 --- a/index.js +++ b/index.js @@ -77,7 +77,7 @@ function mergeItems(itemType, currentItem, newItem, ignoreLocations) { currentItem.description = newItem.description; } - if (!currentItem.defaultValue && typeof newItem.defaultValue != 'undefined') { + if (!currentItem.defaultValue && typeof newItem.defaultValue !== 'undefined') { currentItem.defaultValue = newItem.defaultValue; } @@ -87,7 +87,7 @@ function mergeItems(itemType, currentItem, newItem, ignoreLocations) { } } - if ((!currentItem.keywords || currentItem.keywords.length == 0) && newItem.keywords) { + if ((!currentItem.keywords || currentItem.keywords.length === 0) && newItem.keywords) { currentItem.keywords = newItem.keywords; } diff --git a/lib/utils.js b/lib/utils.js index 421e8ac..c78ac8e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -484,7 +484,13 @@ const inferTypeFromVariableDeclaration = (variable) => { const typeOfValue = expressionsMap[variable.declarator.init.type] || typeof value; if (typeOfValue !== 'undefined') { - return { kind: 'type', text: typeOfValue, type: typeOfValue }; + return { + kind: typeOfValue === 'function' + ? 'function' + : 'type', + text: typeOfValue, + type: typeOfValue, + }; } return anyType; diff --git a/lib/v3/script.js b/lib/v3/script.js index 7904d7c..06582fe 100644 --- a/lib/v3/script.js +++ b/lib/v3/script.js @@ -81,6 +81,8 @@ class ScriptParser extends EventEmitter { emitDataItem(variable, parseContext, defaultVisibility, parentComment) { const comment = parentComment || getCommentFromSourceCode(variable.node, parseContext.sourceCode, { defaultVisibility, useLeading: true, useTrailing: false }); + const inferredType = inferTypeFromVariableDeclaration(variable); + /** @type {import('../../typings').SvelteDataItem} */ const item = { ...comment, @@ -88,7 +90,7 @@ class ScriptParser extends EventEmitter { kind: variable.kind, static: parseContext.scopeType === SCOPE_STATIC, readonly: variable.kind === 'const', - type: inferTypeFromVariableDeclaration(variable), + type: inferredType, importPath: variable.importPath, originalName: variable.originalName, localName: variable.localName, @@ -98,6 +100,12 @@ class ScriptParser extends EventEmitter { item.defaultValue = variable.declarator.init.value; } + if (inferredType.type === 'function') { + parseAndMergeKeywords(comment.keywords, variable); + inferredType.params = variable.params; + inferredType.return = variable.return; + } + this.attachLocationsIfRequired(item, variable, parseContext); updateType(item); @@ -236,9 +244,9 @@ class ScriptParser extends EventEmitter { const variables = parseVariableDeclaration(declaration); variables.forEach((variable, index) => { - if (level === 0) { let _comment = comment; + if (index > 0) { _comment = getCommentFromSourceCode(variable.declarator, context.sourceCode, { defaultVisibility: visibility, useLeading: true, useTrailing: false }); } @@ -370,6 +378,7 @@ class ScriptParser extends EventEmitter { if (node.consequent) { this.parseBodyRecursively(node.consequent, parseContext, level + 1); } + if (node.alternate) { this.parseBodyRecursively(node.alternate, parseContext, level + 1); } diff --git a/lib/v3/v3-utils.js b/lib/v3/v3-utils.js index d0ed079..2fd1103 100644 --- a/lib/v3/v3-utils.js +++ b/lib/v3/v3-utils.js @@ -54,36 +54,52 @@ function getInnermostBody(node) { return node; } +/** + * + * @param {AstNode} nodeParams Array of node parameters + * @return {import('../../typings').SvelteMethodParamItem[]} + */ +function parseParams(nodeParams) { + /** @type {import('../../typings').SvelteMethodParamItem[]} */ + const params = []; + + if (nodeParams && nodeParams.length) { + nodeParams.forEach((param) => { + if (param.type === 'Identifier') { + params.push({ + name: param.name + }); + } + + if (param.type === 'AssignmentPattern') { + const inferredType = inferTypeFromVariableDeclaration({ + // fake variable declarator block + declarator: { + init: param.right + } + }); + + params.push({ + name: param.left.name, + type: inferredType, + description: null, + optional: true, + defaultValue: param.right.raw, + }); + } + }); + } + + return params; +} + /** * @param {AstNode} node a 'FunctionDeclaration' AST node */ function parseFunctionDeclaration(node) { assertNodeType(node, 'FunctionDeclaration'); - const params = []; - - node.params.forEach((param) => { - if (param.type === 'Identifier') { - params.push({ - name: param.name - }); - } - if (param.type == 'AssignmentPattern') { - let inferred_type = inferTypeFromVariableDeclaration({ - //fake variable declarator block - declarator: { - init: param.right - } - }); - params.push({ - name: param.left.name, - type: inferred_type, - description: null, - optional: true, - defaultValue: param.right.raw - }); - } - }); + const params = parseParams(node.params); const output = { node: node, @@ -108,9 +124,10 @@ function parseVariableDeclaration(node) { node.declarations.forEach(declarator => { const idNode = declarator.id; + const init = declarator.init; if (idNode.type === 'Identifier') { - result.push({ + const data = { name: idNode.name, kind: node.kind, node: node, @@ -119,7 +136,20 @@ function parseVariableDeclaration(node) { start: idNode.start, end: idNode.end } - }); + }; + + if ( + init && + init.params && + ( + init.type === 'FunctionExpression' || + init.type === 'ArrowFunctionExpression' + ) + ) { + data.params = parseParams(init.params); + } + + result.push(data); } else if (idNode.type === 'ObjectPattern') { idNode.properties.forEach(propertyNode => { const propertyIdNode = propertyNode.key; @@ -195,11 +225,11 @@ function parseAndMergeKeywords(keywords = [], event, allowToParseReturn = true) * present from parsing the AST node. */ if (pIndex >= 0) { - if( + if ( parsedParam.type && event.params[pIndex].type && - parsedParam.type.text == '*' - ){ + parsedParam.type.text === '*' + ) { /** * Only `getDefaultJSDocType()` has type.text = "*" * so, if parsedParams contain that, we can be sure that diff --git a/test/svelte3/integration/data/data.export.functionExpression.svelte b/test/svelte3/integration/data/data.export.functionExpression.svelte new file mode 100644 index 0000000..a6abdd6 --- /dev/null +++ b/test/svelte3/integration/data/data.export.functionExpression.svelte @@ -0,0 +1,29 @@ + \ No newline at end of file diff --git a/test/svelte3/integration/data/data.spec.js b/test/svelte3/integration/data/data.spec.js index 3bac4ec..2736508 100644 --- a/test/svelte3/integration/data/data.spec.js +++ b/test/svelte3/integration/data/data.spec.js @@ -209,6 +209,110 @@ describe('SvelteDoc v3 - Props', () => { }); }); + it('Function expression declarations should be parsed', (done) => { + parser.parse({ + version: 3, + filename: path.resolve(__dirname, 'data.export.functionExpression.svelte'), + features: ['data'], + ignoredVisibilities: [] + }).then((doc) => { + expect(doc, 'Document should be provided').to.exist; + + expect(doc.data, 'Document methods should be parsed').to.exist; + expect(doc.data.length).to.equal(4); + + const data0 = doc.data[0]; + const data1 = doc.data[1]; + const data2 = doc.data[2]; + const data3 = doc.data[3]; + + expect(data0.name).to.equal('add'); + expect(data0.type.type).to.equal('function'); + expect(data0.type.kind).to.equal('function'); + expect(data0.type.params, 'Function expression arguments should be parsed').to.exist; + expect(data0.description).to.equal('Adds two numbers `a` and `b`'); + + const data0params0 = data0.type.params[0]; + + expect(data0params0.name).to.equal('a'); + expect(data0params0.description).to.be.null; + expect(data0params0.type.type).to.equal('number'); + expect(data0params0.defaultValue).to.equal('0'); + expect(data0params0.optional).to.be.true; + + const data0params1 = data0.type.params[1]; + + expect(data0params1.name).to.equal('b'); + expect(data0params1.description).to.be.null; + expect(data0params1.type.type).to.equal('number'); + expect(data0params1.defaultValue).to.equal('0'); + expect(data0params1.optional).to.be.true; + + expect(data1.name).to.equal('subtract'); + expect(data1.type.type).to.equal('function'); + expect(data1.type.kind).to.equal('function'); + expect(data1.type.params, 'Function expression arguments should be parsed').to.exist; + expect(data1.description).to.equal('Subtracts two numbers `b` from `a`'); + + const data1params0 = data1.type.params[0]; + + expect(data1params0.name).to.equal('a'); + expect(data1params0.description).to.equal('first number'); + expect(data1params0.type.type).to.equal('number'); + expect(data1params0.defaultValue).to.be.undefined; + expect(data1params0.optional).to.be.false; + + const data1params1 = data1.type.params[1]; + + expect(data1params1.name).to.equal('b'); + expect(data1params1.description).to.equal('second number'); + expect(data1params1.type.type).to.equal('number'); + expect(data1params1.defaultValue).to.equal('0'); + expect(data1params1.optional).to.be.true; + + expect(data1.type.return, 'function expression return keyword should be parsed').to.exist; + expect(data1.type.return.type).to.exist; + expect(data1.type.return.type.type).to.equal('number'); + expect(data1.type.return.description).to.equal('the difference'); + + expect(data2.name).to.equal('multiply'); + expect(data2.type.type).to.equal('function'); + expect(data2.type.params, 'Function expression arguments should be parsed').to.exist; + expect(data2.description).to.equal('Multiplies two numbers `a` and `b`'); + + const data2params0 = data2.type.params[0]; + + expect(data2params0.name).to.equal('a'); + expect(data2params0.description).to.equal('first number'); + expect(data2params0.type.type).to.equal('number'); + expect(data2params0.defaultValue).to.equal('0'); + expect(data2params0.optional).to.be.true; + + const data2params1 = data2.type.params[1]; + + expect(data2params1.name).to.equal('b'); + expect(data2params1.description).to.equal('second number'); + expect(data2params1.type.type).to.equal('number'); + expect(data2params1.defaultValue).to.equal('1'); + expect(data2params1.optional).to.be.true; + + expect(data2.type.return, 'function expression return keyword should be parsed').to.exist; + expect(data2.type.return.type).to.exist; + expect(data2.type.return.type.type).to.equal('number'); + expect(data2.type.return.description).to.equal('the total'); + + expect(data3.name).to.equal('done'); + expect(data3.type.type).to.equal('function'); + expect(data3.type.kind).to.equal('function'); + expect(data3.type.params).to.not.exist; + expect(data3.description).to.be.null; + + done(); + }).catch(e => { + done(e); + }); + }); + it('Imported default data should be parsed with comment', (done) => { parser.parse({ version: 3, @@ -436,10 +540,11 @@ describe('SvelteDoc v3 - Props', () => { expect(localProp, 'Local prop definition also must be provided').to.exist; const prop2 = doc.data.find(d => d.name === 'switch'); + expect(prop2).to.exist; expect(prop2.name, 'Aliace name must be exposed instead of original name').to.equal('switch'); expect(prop2.localName, 'Local name must be stored').to.equal('switchValue'); - expect(prop2.defaultValue).to.equal("main"); + expect(prop2.defaultValue).to.equal('main'); expect(prop2.keywords).to.exist; expect(prop2.keywords.length).to.equal(1); diff --git a/test/svelte3/integration/events/events.spec.js b/test/svelte3/integration/events/events.spec.js index 17a540a..73f4891 100644 --- a/test/svelte3/integration/events/events.spec.js +++ b/test/svelte3/integration/events/events.spec.js @@ -109,21 +109,25 @@ describe('SvelteDoc v3 - Events', () => { expect(location, 'Location should be correct identified').is.deep.equals({ start: 122, end: 130 }); const event1 = doc.events[1]; + expect(event1, 'Event should be a valid entity').to.exist; expect(event1.name).to.equal('start'); expect(event1.visibility).to.equal('public'); const event2 = doc.events[2]; + expect(event2, 'Event should be a valid entity').to.exist; expect(event2.name).to.equal('end'); expect(event2.visibility).to.equal('public'); - const event3= doc.events[3]; + const event3 = doc.events[3]; + expect(event3, 'Event should be a valid entity').to.exist; expect(event3.name).to.equal('running'); expect(event3.visibility).to.equal('public'); const event4 = doc.events[4]; + expect(event4, 'Event should be a valid entity').to.exist; expect(event4.name).to.equal('failed'); expect(event4.visibility).to.equal('public'); diff --git a/typings.d.ts b/typings.d.ts index 320197f..131f8f5 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -14,7 +14,19 @@ export interface JSDocKeyword { description: string; } +export type JSDocTypeKind = 'type' | 'const' | 'union' | 'function'; + export interface JSDocTypeBase { + /** + * The kind of JSDocType object. + * That property can identify which additional properties included in that object. + * + * @see JSDocTypeElement + * @see JSDocTypeConst + * @see JSDocTypeUnion + * @see JSDocTypeFunction + */ + kind: JSDocTypeKind; /** * The text representation of this type. */ @@ -63,7 +75,26 @@ export interface JSDocTypeUnion extends JSDocTypeBase { type: JSDocType[]; } -export type JSDocType = JSDocTypeElement | JSDocTypeConst | JSDocTypeUnion; +export interface IMethodDefinition { + /** + * The list of parameter items of the function expression. + */ + params?: SvelteMethodParamItem[]; + /** + * The return item of the function expression. This exists if an item with 'name' equal + * to 'returns' or 'return' exists in 'keywords'. + */ + return?: SvelteMethodReturnItem; +} + +/** + * @since {4.2.0} + */ +export interface JSDocTypeFunction extends JSDocTypeBase, IMethodDefinition { + kind: 'function'; +} + +export type JSDocType = JSDocTypeElement | JSDocTypeConst | JSDocTypeUnion | JSDocTypeFunction; /** * Represents a source location of symbol. @@ -244,19 +275,7 @@ export interface SvelteMethodReturnItem { description?: string; } -export interface SvelteMethodItem extends ISvelteItem { - /** - * The list of parameter items of the method. - * @since {4.0.0} - */ - params?: SvelteMethodParamItem[]; - - /** - * The return item of the method. This exists if an item with 'name' equal - * to 'returns' or 'return' exists in 'keywords'. - * @since {4.0.0} - */ - return?: SvelteMethodReturnItem; +export interface SvelteMethodItem extends ISvelteItem, IMethodDefinition { } export interface SvelteComponentItem extends ISvelteItem {