diff --git a/index.js b/index.js index fc51618..af30601 100644 --- a/index.js +++ b/index.js @@ -61,6 +61,39 @@ function getEventName(feature) { : feature; } +function convertVisibilityToLevel(visibility) { + switch (visibility) { + case 'public': + return 3; + case 'protected': + return 2; + case 'private': + return 1; + } + + return 0; +} + +function mergeItems(currentItem, newItem) { + if (convertVisibilityToLevel(currentItem.visibility) < convertVisibilityToLevel(newItem.visibility)) { + currentItem.visibility = newItem.visibility; + } + + if (!currentItem.description && newItem.description) { + currentItem.description = newItem; + } + + if (!currentItem.keywords && newItem.keywords) { + currentItem.keywords = newItem.keywords; + } + + if (!currentItem.bind && newItem.bind) { + currentItem.bind = newItem.bind; + } + + return currentItem; +} + function subscribeOnParserEvents(parser, options, version, resolve, reject) { const component = { version: version @@ -90,7 +123,13 @@ function subscribeOnParserEvents(parser, options, version, resolve, reject) { if (itemIndex < 0) { component[feature].push(value); } else { - component[feature][itemIndex] = value; + // Use merge logic of items information for specific features + if (['data'].includes(feature)) { + const currentItem = component[feature][itemIndex]; + component[feature][itemIndex] = mergeItems(currentItem, value); + } else { + component[feature][itemIndex] = value; + } } }); } diff --git a/lib/utils.js b/lib/utils.js index ac9c061..8c99122 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -70,12 +70,14 @@ const parseComment = (text, defaultVisibility = DEFAULT_VISIBILITY, features = [ const getComment = (property, defaultVisibility = DEFAULT_VISIBILITY, features, useLeading = true, useTrailing = true) => { let lastComment = null; - if (useLeading && property.leadingComments && property.leadingComments.length > 0) { - lastComment = property.leadingComments[property.leadingComments.length - 1].value; - } - - if (useTrailing && property.trailingComments && property.trailingComments.length > 0) { - lastComment = property.trailingComments[property.trailingComments.length - 1].value; + if (property) { + if (useLeading && property.leadingComments && property.leadingComments.length > 0) { + lastComment = property.leadingComments[property.leadingComments.length - 1].value; + } + + if (useTrailing && property.trailingComments && property.trailingComments.length > 0) { + lastComment = property.trailingComments[property.trailingComments.length - 1].value; + } } if (lastComment) { diff --git a/lib/v3/parser.js b/lib/v3/parser.js index 7b9e39f..0e9af47 100644 --- a/lib/v3/parser.js +++ b/lib/v3/parser.js @@ -569,6 +569,46 @@ class Parser extends EventEmitter { lastComment = null; } + if (this.features.includes('data')) { + const bindProperties = Object.keys(attrs) + .filter(name => name.length > 5 && name.indexOf('bind:') === 0) + .filter(name => name !== 'bind:this') + .map(name => { + const sourcePropertyName = name.substr(5); + let targetPropertyName = sourcePropertyName; + const attributeValue = attrs[name]; + if (attributeValue && attributeValue.length > 2 && attributeValue.charAt(0) === '{' && attributeValue.charAt(attributeValue.length - 1) === '}') { + targetPropertyName = attributeValue.substr(1, attributeValue.length - 2); + } + + return { + sourcePropertyName: sourcePropertyName, + targetPropertyName: targetPropertyName, + parent: tagName, + loc: this.includeSourceLocations && lastAttributeLocations.hasOwnProperty(name) + ? lastAttributeLocations[name] + : null + }; + }); + + bindProperties.forEach(bindProperty => { + const dataItem = { + name: bindProperty.targetPropertyName, + kind: 'let', + bind: { + source: bindProperty.parent, + property: bindProperty.sourcePropertyName + }, + loc: bindProperty.loc, + visibility: 'private', + static: false, + readonly: false + }; + + this.emit('data', dataItem); + }); + } + if (this.features.includes('refs')) { if (attrs.hasOwnProperty('bind:this') && attrs['bind:this']) { const value = attrs['bind:this']; diff --git a/test/svelte3/integration/bind/bind.exported.svelte b/test/svelte3/integration/bind/bind.exported.svelte new file mode 100644 index 0000000..e39cbdb --- /dev/null +++ b/test/svelte3/integration/bind/bind.exported.svelte @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/test/svelte3/integration/bind/bind.spec.js b/test/svelte3/integration/bind/bind.spec.js index 146ab0c..7c34b79 100644 --- a/test/svelte3/integration/bind/bind.spec.js +++ b/test/svelte3/integration/bind/bind.spec.js @@ -82,4 +82,31 @@ describe('SvelteDoc v3 - Bind', () => { done(e); }); }); + + it('Bind declared and exported definition should be parsed', (done) => { + parser.parse({ + version: 3, + filename: path.resolve(__dirname, 'bind.exported.svelte'), + features: ['data', 'description'], + ignoredVisibilities: [] + }).then((doc) => { + expect(doc, 'Document should be provided').to.exist; + expect(doc.data, 'Document data should be parsed').to.exist; + + expect(doc.data.length).to.equal(1); + const item = doc.data[0]; + + expect(item, 'Bind item should be a valid entity').to.exist; + expect(item.name).to.equal('totalCost'); + expect(item.visibility).to.equal('public'); + expect(item.description).to.equal('The comment of public binded property.'); + expect(item.bind, 'Bind information should be presented').to.exist; + expect(item.bind.source).to.equal('ShopingCart'); + expect(item.bind.property).to.equal('totalCost'); + + done(); + }).catch(e => { + done(e); + }); + }); }); \ No newline at end of file diff --git a/typings.d.ts b/typings.d.ts index eaadfda..c2cdc30 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -98,7 +98,7 @@ export interface SvelteDataItem extends ISvelteItem { * @since Svelte V3 * @since {2.0.0} */ - let?: 'var'|'let'|'const'; + kind?: 'var'|'let'|'const'; /** * Provides information about property binding. * @since Svelte V3