From 9a7bfc4b4b6d13e75a0ee294bf24040262d0e426 Mon Sep 17 00:00:00 2001 From: "Alexey Mulyukin (alexprey)" Date: Sat, 13 Feb 2021 17:44:40 +0300 Subject: [PATCH] fix(47): Improve markup comments parsing Generalize the comments parsing in template parser. Now it should correctly transform and handle JSDoc comments in all places in markup. --- index.js | 1 + lib/jsdoc.js | 39 +++++++++++++++++++ lib/utils.js | 5 +++ lib/v3/template.js | 35 ++++++++++------- .../integration/slots/slot.comments.svelte | 11 ++++++ test/svelte3/integration/slots/slots.spec.js | 32 +++++++++++++++ test/unit/jsdoc/jsdoc.spec.js | 33 ++++++++++++++++ 7 files changed, 143 insertions(+), 13 deletions(-) create mode 100644 test/svelte3/integration/slots/slot.comments.svelte diff --git a/index.js b/index.js index 1a7f813..7b8d287 100644 --- a/index.js +++ b/index.js @@ -176,6 +176,7 @@ function subscribeOnParserEvents(parser, ignoredVisibilities, version, resolve, /** * Main parse function. * @param {SvelteParserOptions} options + * @return {Promise} * @example * const { parse } = require('sveltedoc-parser'); * // basic usage only requires 'filename' to be set. diff --git a/lib/jsdoc.js b/lib/jsdoc.js index bde1caf..5af7ed4 100644 --- a/lib/jsdoc.js +++ b/lib/jsdoc.js @@ -192,6 +192,43 @@ function parseReturnKeyword(text) { return output; } +/** + * @param {string} comment + * @return {string} + */ +const convertToJsDocComment = (comment) => { + if (!comment) { + return ''; + } + + comment = comment.trim(); + + // Comment content already is JSDoc format + if (comment.startsWith('/**') && comment.endsWith('*/')) { + return comment; + } + + const lines = comment.split('\n'); + + return lines.map((line, lineNumber) => { + line = line.trim(); + + if (lineNumber === 0) { + if (!line.startsWith('/**')) { + line = `/**\n * ${line}`; + } + } + + if (lineNumber === lines.length - 1) { + if (!line.endsWith('*/')) { + line = `${line}\n */`; + } + } + + return line.trimEnd(); + }).join('\n * '); +}; + module.exports = { parseType, parseParamKeyword, @@ -199,5 +236,7 @@ module.exports = { parseTypeKeyword, parseJSTypeFromValueNode, + convertToJsDocComment, + DEFAULT_TYPE }; diff --git a/lib/utils.js b/lib/utils.js index e32abdf..830b190 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -21,6 +21,11 @@ const getVisibility = (keywords, defaultVisibility) => { return defaultVisibility; }; +/** + * @param {string} text + * @param {JSVisibilityScope} defaultVisibility + * @return {import('../typings').IScopedCommentItem} + */ const parseComment = (text, defaultVisibility = DEFAULT_VISIBILITY) => { const result = { keywords: [], diff --git a/lib/v3/template.js b/lib/v3/template.js index 7f9236b..33798ee 100644 --- a/lib/v3/template.js +++ b/lib/v3/template.js @@ -4,6 +4,7 @@ const { Parser: HtmlParser } = require('htmlparser2-svelte'); const { parseAndMergeKeywords } = require('./v3-utils'); const { parseComment, hasOwnProperty } = require('../utils'); const { TemplateEvent } = require('./events'); +const jsdoc = require('../jsdoc'); /** * @typedef {import('../../typings').Svelte3FeatureKeys} Svelte3FeatureKeys @@ -56,6 +57,17 @@ class TemplateParser extends EventEmitter { let lastTagName = null; let parser = null; + const parseAndConsumeLastComment = (defaultVisibility) => { + const parsedComment = parseComment( + jsdoc.convertToJsDocComment(lastComment), + defaultVisibility + ); + + lastComment = null; + + return parsedComment; + }; + return { onparserinit: (parserInstance) => { parser = parserInstance; @@ -85,7 +97,10 @@ class TemplateParser extends EventEmitter { if (name.length > 3 && name.indexOf('on:') === 0 && !value) { const nameWithModificators = name.substr(3).split('|'); + const parsedComment = parseAndConsumeLastComment(); + const baseEvent = { + ...parsedComment, name: nameWithModificators[0], parent: lastTagName, modificators: nameWithModificators.slice(1), @@ -94,20 +109,10 @@ class TemplateParser extends EventEmitter { : null }; - if (lastComment) { - lastComment = `/** ${lastComment} */`; - } - - const comment = parseComment(lastComment || ''); - - baseEvent.visibility = comment.visibility; - baseEvent.description = comment.description || ''; - baseEvent.keywords = comment.keywords; - if (!hasOwnProperty(this.eventsEmitted, baseEvent.name)) { this.eventsEmitted[baseEvent.name] = baseEvent; - parseAndMergeKeywords(comment.keywords, baseEvent); + parseAndMergeKeywords(parsedComment.keywords, baseEvent); this.emit(TemplateEvent.EVENT, baseEvent); } @@ -133,7 +138,7 @@ class TemplateParser extends EventEmitter { if (isTopLevelElement && isNotStyleOrScript) { if (lastComment && rootElementIndex === 0) { - const parsedComment = parseComment(lastComment); + const parsedComment = parseAndConsumeLastComment(); this.emit(TemplateEvent.GLOBAL_COMMENT, parsedComment); } @@ -150,9 +155,13 @@ class TemplateParser extends EventEmitter { visibility: 'public' })); + const parsedComment = parseAndConsumeLastComment('public'); + + // TODO parse parameters description and types for slot + const slot = { + ...parsedComment, name: attrs.name || 'default', - description: lastComment, visibility: 'public', parameters: exposedParameters }; diff --git a/test/svelte3/integration/slots/slot.comments.svelte b/test/svelte3/integration/slots/slot.comments.svelte new file mode 100644 index 0000000..145ea54 --- /dev/null +++ b/test/svelte3/integration/slots/slot.comments.svelte @@ -0,0 +1,11 @@ +
+ + +
+ + +
+ + +
\ No newline at end of file diff --git a/test/svelte3/integration/slots/slots.spec.js b/test/svelte3/integration/slots/slots.spec.js index 9ea896b..7c6778c 100644 --- a/test/svelte3/integration/slots/slots.spec.js +++ b/test/svelte3/integration/slots/slots.spec.js @@ -87,4 +87,36 @@ describe('SvelteDoc v3 - Slots', () => { done(e); }); }); + + it('Slot comments should be correctly parsed', done => { + parser.parse({ + version: 3, + filename: path.resolve(__dirname, 'slot.comments.svelte'), + features: ['slots', 'events'], + ignoredVisibilities: [] + }).then(doc => { + expect(doc, 'Document should be provided').to.exist; + expect(doc.slots, 'Document slots should be parsed').to.exist; + + expect(doc.slots).to.have.length(3); + + const firstSlot = doc.slots.find(slot => slot.name === 'first'); + const defaultSlot = doc.slots.find(slot => slot.name === 'default'); + const secondSlot = doc.slots.find(slot => slot.name === 'second'); + + expect(firstSlot.description).to.equal('The first slot description.'); + expect(defaultSlot.description).to.empty; + expect(secondSlot.description).to.equal('The second slot description.'); + + expect(doc.events).to.have.length(1); + + const event = doc.events[0]; + + expect(event.description).to.be.empty; + + done(); + }).catch(e => { + done(e); + }); + }); }); diff --git a/test/unit/jsdoc/jsdoc.spec.js b/test/unit/jsdoc/jsdoc.spec.js index 179cd28..cc4bfc8 100644 --- a/test/unit/jsdoc/jsdoc.spec.js +++ b/test/unit/jsdoc/jsdoc.spec.js @@ -417,4 +417,37 @@ describe('JSDoc parser module tests', () => { expect(returns.type.type).to.equal('any'); }); }); + + describe('convertToJsDocComment', () => { + it('when single line text then should be wrapped', () => { + const output = jsdoc.convertToJsDocComment(' The simple component description. '); + + expect(output).to.equal(`/** + * The simple component description. + */` + ); + }); + + it('when comment is already jsdoc then should be w/o any changes', () => { + const output = jsdoc.convertToJsDocComment('/** The JSDoc comment **/'); + + expect(output).to.equal('/** The JSDoc comment **/'); + }); + + it('when multiline comment then should be correcly wrapped', () => { + const output = jsdoc.convertToJsDocComment(` + The simple component description. + + @author Alexey + @param {string} title + `); + + expect(output).to.equal(`/** + * The simple component description. + * + * @author Alexey + * @param {string} title + */`); + }); + }); });