Skip to content

Commit

Permalink
Merge pull request #79 from ekhaled/func-expr-param-parse
Browse files Browse the repository at this point in the history
fix #78: Add additional metadata for function expressions
  • Loading branch information
alexprey authored Dec 14, 2021
2 parents 8afe9e7 + cd14754 commit c9f72ae
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 50 deletions.
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand Down
8 changes: 7 additions & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 11 additions & 2 deletions lib/v3/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,16 @@ 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,
name: variable.name,
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,
Expand All @@ -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);
Expand Down Expand Up @@ -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 });
}
Expand Down Expand Up @@ -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);
}
Expand Down
88 changes: 59 additions & 29 deletions lib/v3/v3-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script>
/* Adds two numbers `a` and `b`*/
export let add = function(a = 0, b = 0){
return a + b
},
/**
* Subtracts two numbers `b` from `a`
* @param {number} a - first number
* @param {number} [b=0] - second number
* @returns {number} - the difference
*/
subtract = function(a, b){
return a - b;
},
/**
* Multiplies two numbers `a` and `b`
* @param a - first number
* @param b - second number
* @returns {number} - the total
*/
multiply = (a = 0, b = 1) => {
return a * b
};
export let done = function(){
//nothing happens
}
</script>
107 changes: 106 additions & 1 deletion test/svelte3/integration/data/data.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);

Expand Down
6 changes: 5 additions & 1 deletion test/svelte3/integration/events/events.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
Loading

0 comments on commit c9f72ae

Please sign in to comment.