@custom-elements-manifest/analyzer
Version:
<!-- [=> See Source <=](../../docs/analyzer/index.md) -->
158 lines (145 loc) • 5.99 kB
JavaScript
import { parse } from 'comment-parser';
import { handleJsDocType, normalizeDescription } from '../../utils/jsdoc.js';
import { has, safe } from '../../utils/index.js';
/**
* CLASS-JSDOC
*
* Deals with any JSDoc above a class
*/
export function classJsDocPlugin() {
return {
name: 'CORE - CLASS-JSDOC',
analyzePhase({ts, node, moduleDoc}){
switch (node.kind) {
case ts.SyntaxKind.ClassDeclaration:
const className = node?.name?.getText();
const classDoc = moduleDoc?.declarations?.find(declaration => declaration.name === className);
/**
* Because we use a bunch of 'non-standard' JSDoc annotations, TS doesn't recognize most of them.
* Instead we use `comment-parser` to parse the JSDoc.
*
* Loops through each JSDoc (yes, there can be multiple) above a class, and parses every JSDoc annotation
*
* Checks to see if the item is already in the classDoc, and if so merge and overwrite (JSDoc takes precedence)
*/
node?.jsDoc?.forEach(jsDoc => {
const parsed = parse(jsDoc?.getFullText());
parsed?.forEach(parsedJsDoc => {
/**
* If any of the tags is a `@typedef`, we ignore it; this JSDoc comment may be above a class,
* it probably doesnt _belong_ to the class, but something else in the file
*/
if(parsedJsDoc?.tags?.some(tag => tag?.tag === 'typedef')) return;
parsedJsDoc?.tags?.forEach(jsDoc => {
switch(jsDoc.tag) {
case 'attr':
case 'attribute':
const attributeAlreadyExists = classDoc?.attributes?.find(attr => attr.name === jsDoc.name);
let attributeDoc = attributeAlreadyExists || {};
attributeDoc = handleClassJsDoc(attributeDoc, jsDoc);
if(!attributeAlreadyExists) {
classDoc.attributes.push(attributeDoc);
}
break;
case 'prop':
case 'property':
const fieldAlreadyExists = classDoc?.members?.find(member => member.name === jsDoc.name);
let fieldDoc = fieldAlreadyExists || {};
fieldDoc = handleClassJsDoc(fieldDoc, jsDoc);
fieldDoc.kind = 'field';
if(!fieldAlreadyExists) {
classDoc.members.push(fieldDoc);
}
break;
case 'fires':
case 'event':
const eventAlreadyExists = classDoc?.events?.find(event => event.name === jsDoc.name);
let eventDoc = eventAlreadyExists || {};
eventDoc = handleClassJsDoc(eventDoc, jsDoc);
delete eventDoc.privacy;
if(!eventAlreadyExists) {
classDoc.events.push(eventDoc);
}
break;
case 'csspart':
case 'part':
let cssPartDoc = {};
cssPartDoc = handleClassJsDoc(cssPartDoc, jsDoc);
classDoc.cssParts.push(cssPartDoc);
break;
case 'cssprop':
case 'cssproperty':
let cssPropertyDoc = {};
cssPropertyDoc = handleClassJsDoc(cssPropertyDoc, jsDoc);
classDoc.cssProperties.push(cssPropertyDoc);
break;
case 'slot':
let slotDoc = {};
slotDoc = handleClassJsDoc(slotDoc, jsDoc);
classDoc.slots.push(slotDoc);
break;
case 'tag':
case 'tagname':
case 'element':
case 'customElement':
case 'customelement':
classDoc.tagName = jsDoc?.name || '';
classDoc.customElement = true;
break;
case 'cssState':
case 'cssstate':
let statePropertyDoc = {};
statePropertyDoc = handleClassJsDoc(statePropertyDoc, jsDoc);
classDoc.cssStates.push(statePropertyDoc);
break;
case 'deprecated':
classDoc.deprecated = jsDoc?.name ? `${jsDoc.name} ${jsDoc?.description}`.trim() : "true";
break;
}
})
});
/**
* Description
*/
if(jsDoc?.comment) {
if(has(jsDoc?.comment)) {
classDoc.description = jsDoc.comment.map(com => `${safe(() => com?.name?.getText()) ?? ''}${com.text}`).join('');
} else {
classDoc.description = normalizeDescription(jsDoc.comment);
}
}
/**
* Comment-parse doesn't handle annotations with only a description correctly, for example:
* @summary foo bar
* will output only 'bar' as the description.
*
* Instead, we use TS for this JSDoc annotation.
*/
jsDoc?.tags?.forEach(tag => {
switch(safe(() => tag?.tagName?.getText())) {
case 'summary':
classDoc.summary = tag?.comment;
break;
}
});
});
break;
}
}
}
}
function handleClassJsDoc(doc, tag) {
if(tag?.type) {
doc.type = { text: handleJsDocType(tag.type) }
}
if(tag?.description) {
doc.description = normalizeDescription(tag.description);
}
if(tag?.name) {
doc.name = tag.name === '-' ? '' : tag.name;
}
if(tag?.default) {
doc.default = tag.default;
}
return doc;
}