@vuedoc/parser
Version:
Generate a JSON documentation for a Vue file
142 lines (112 loc) • 3.99 kB
text/typescript
import { ASTElement, compile } from 'vue-template-compiler';
import { AbstractSourceParser } from './AbstractSourceParser.js';
import { SlotParser } from './SlotParser.js';
import { CommentParser } from './CommentParser.js';
import { SlotEntry } from '../entity/SlotEntry.js';
import { EventEntry, EventArgumentGenerator } from '../entity/EventEntry.js';
import { JSDoc } from '../lib/JSDoc.js';
import { Feature, Tag } from '../lib/Enum.js';
import { Parser } from '../../types/Parser.js';
const EVENT_EMIT_RE = /^\$emit\(['"](.+)['"]/;
export class MarkupTemplateParser extends AbstractSourceParser<Parser.Source, null> {
nodes: any[] = [];
constructor(emitter: Parser.Interface, source: Parser.Source) {
super(null, emitter, source, emitter.scope);
source.content = source.content?.trim() || '';
this.features = emitter.features;
}
getSiblingNode(node) {
const reversedTokens = this.nodes.slice(0).reverse();
const siblingNode = reversedTokens.find(({ level, parent }) => {
return level === node.level && parent === node.parent;
});
if (siblingNode) {
return siblingNode;
}
return reversedTokens.find(({ level }) => level < node.level);
}
findComment(node: any) {
const token = this.getSiblingNode(node) || { end: 0 };
const code = this.source.content.substring(token.end, node.start).trim();
const lines = code.split(/\n/g).reverse();
const parsedLines: string[] = [];
for (const line of lines) {
if (line.trim().endsWith('-->') && parsedLines.length) {
break;
}
parsedLines.push(line);
}
const comment = parsedLines.reverse().join('\n');
return CommentParser.format(comment) || '';
}
parseEntryComment(entry, node) {
const commentText = this.findComment(node);
const comment = CommentParser.parse(commentText);
entry.visibility = comment.visibility;
entry.description = comment.description || undefined;
entry.keywords = comment.keywords;
return comment;
}
parseSlot(node) {
const name = node.attrsMap.name || node.attrsMap[':name'] || node.attrsMap['v-bind:name'];
const entry = new SlotEntry(name);
const comment = this.parseEntryComment(entry, node);
const slots = SlotParser.extractSlotKeywords(comment.keywords);
if (slots.length) {
slots.forEach((slot) => this.emit(slot));
} else {
SlotParser.parseTemplateSlots(entry, node.attrsList, comment);
this.emit(entry);
}
}
parseEvents(node) {
Object.values(node.attrsMap)
.map((value: any) => EVENT_EMIT_RE.exec(value))
.filter((result) => result !== null)
.map((matches: any) => matches[1])
.forEach((name) => {
const entry = new EventEntry(name);
const comment = this.parseEntryComment(entry, node);
JSDoc.parseParams(this, comment.keywords, entry.arguments, EventArgumentGenerator);
this.emit(entry);
});
}
parseNode(node) {
if (!node.level) {
node.level = 1;
node.childrenLength = node.children.length;
if (node.parent) {
node.level += node.parent.childrenLength;
node.level += node.parent.level;
}
}
if (node.tag === Tag.slot) {
if (this.features.includes(Feature.slots)) {
this.parseSlot(node);
}
} else if (this.features.includes(Feature.events)) {
this.parseEvents(node);
}
this.nodes.push(node);
}
parse() {
if (!this.features.includes(Feature.slots)
&& !this.features.includes(Feature.events)) {
return;
}
compile(this.source.content, {
outputSourceRange: true,
modules: [
{
preTransformNode: (node: ASTElement) => {
this.parseNode(node);
return node;
},
transformNode: (node: ASTElement) => node,
postTransformNode: (node: ASTElement) => node,
genData: (_node: ASTElement) => '',
},
],
});
}
}