Skip to content

Commit

Permalink
fix(47): Improve markup comments parsing
Browse files Browse the repository at this point in the history
Generalize the comments parsing in template parser. Now it should correctly transform and handle JSDoc comments in all places in markup.
  • Loading branch information
alexprey committed Feb 13, 2021
1 parent a157fb3 commit 9a7bfc4
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 13 deletions.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ function subscribeOnParserEvents(parser, ignoredVisibilities, version, resolve,
/**
* Main parse function.
* @param {SvelteParserOptions} options
* @return {Promise<import('./typings').SvelteComponentDoc>}
* @example
* const { parse } = require('sveltedoc-parser');
* // basic usage only requires 'filename' to be set.
Expand Down
39 changes: 39 additions & 0 deletions lib/jsdoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,51 @@ 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,
parseReturnKeyword,
parseTypeKeyword,
parseJSTypeFromValueNode,

convertToJsDocComment,

DEFAULT_TYPE
};
5 changes: 5 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
Expand Down
35 changes: 22 additions & 13 deletions lib/v3/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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
};
Expand Down
11 changes: 11 additions & 0 deletions test/svelte3/integration/slots/slot.comments.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div>
<!-- The first slot description. -->
<slot name="first" />
<div>
<slot />

<button on:click />
</div>
<!-- The second slot description. -->
<slot name="second" />
</div>
32 changes: 32 additions & 0 deletions test/svelte3/integration/slots/slots.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
33 changes: 33 additions & 0 deletions test/unit/jsdoc/jsdoc.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/`);
});
});
});

0 comments on commit 9a7bfc4

Please sign in to comment.