UNPKG

@vuedoc/parser

Version:

Generate a JSON documentation for a Vue file

203 lines (161 loc) 5.65 kB
import { VuedocParser } from './parsers/VuedocParser.js'; import { Parser } from '../types/Parser.js'; import { Entry } from '../types/Entry.js'; import { Feature, DEFAULT_IGNORED_VISIBILITIES, DEFAULT_ENCODING, FeatureEvent } from './lib/Enum.js'; import { KeywordsUtils } from './utils/KeywordsUtils.js'; import { PropEntry } from './entity/PropEntry.js'; import { ParsingOptions, ParsingResult } from '../types/main.js'; export * from './lib/Enum.js'; export { Loader } from './lib/Loader.js'; export { VuedocParser } from './parsers/VuedocParser.js'; type ExtendedParsingResult = ParsingResult & { model?: Entry.ModelEntry[]; }; export async function parseOptions(options: ParsingOptions) { if (!options) { throw new Error('Missing options argument'); } const _options = { ...options }; if (!_options.encoding) { _options.encoding = DEFAULT_ENCODING; } if ('filename' in _options && !_options.filename) { throw new Error('options.filename cannot be empty'); } if (!_options.ignoredVisibilities) { _options.ignoredVisibilities = DEFAULT_IGNORED_VISIBILITIES; } _options.composition = { data: _options.composition?.data || [], methods: _options.composition?.methods || [], computed: _options.composition?.computed || [], props: _options.composition?.props || [], }; if (!_options.resolver) { _options.resolver = {}; } return _options as Parser.Options; } export function synchronizeParsingResult(parser: VuedocParser, component: ExtendedParsingResult) { const defaultModelProp = parser.file.script?.attrs.setup ? 'model-value' : 'value'; const additionalProps: Entry.PropEntry[] = []; if (!component.props) { component.props = []; } component.model?.forEach((model) => { const props = component.props?.filter((prop) => prop.name === model.prop); if (props?.length) { for (const prop of props) { prop.describeModel = true; } } else { const prop = new PropEntry({ name: model.prop, describeModel: true, }); prop.description = model.description; prop.keywords = model.keywords; additionalProps.push(prop); } }); component.props.push(...additionalProps); delete component.model; if (Feature.props in component) { for (const prop of component.props) { if (prop.name === defaultModelProp) { prop.describeModel = true; } if (prop.describeModel) { prop.name = 'v-model'; } else if (parser.file.script?.attrs.setup && Feature.events in component) { const hasUpdateEvent = component.events?.some((event) => event.name === `update:${prop.name}`); if (hasUpdateEvent) { prop.name = `v-model:${prop.name}`; prop.describeModel = true; } } } } } export async function parseComponent(options: ParsingOptions): Promise<ParsingResult> { const resolvedOptions = await parseOptions(options); return new Promise((resolve, reject) => { const component: ExtendedParsingResult = { name: undefined, description: undefined, category: undefined, since: undefined, version: undefined, see: undefined, inheritAttrs: true, errors: [], warnings: [], keywords: [], model: [], props: undefined, data: undefined, computed: undefined, methods: undefined, events: undefined, slots: undefined, }; const parser: Parser.Private = new VuedocParser(resolvedOptions); parser.addEventListener('error', (event) => { component.errors.push(event.message); }); parser.addEventListener('warning', (event) => { component.warnings.push(event.message); }); parser.addEventListener<Entry.KeywordsEntry>('keyword', ({ entry }) => { component.keywords.push(...entry.value); KeywordsUtils.parseCommonEntryTags(component); }); parser.addEventListener<Entry.InheritAttrsEntry>('inheritAttrs', ({ entry }) => { component.inheritAttrs = entry.value; }); parser.addEventListener<Entry.ModelEntry>('model', handleEventEntry(component.model)); parser.addEventListener('fatal', (event) => reject(event.error)); parser.addEventListener('end', () => { if ('file' in parser && (parser as any).file) { synchronizeParsingResult(parser as any, component); } for (const plugin of (parser as VuedocParser).plugins) { if ('handleParsingResult' in plugin) { plugin.handleParsingResult(component); } } resolve(component); }); for (const feature of parser.features) { const eventName: any = FeatureEvent[feature]; switch (feature) { case Feature.name: case Feature.description: parser.addEventListener<Entry.NameEntry | Entry.DescriptionEntry>(eventName, ({ entry }) => { component[feature as any] = entry.value; }); break; case Feature.keywords: // already handled break; default: { const items: Entry.TypeKeywords[] = []; component[feature as string] = items; parser.addEventListener<Entry.TypeKeywords>(eventName, handleEventEntry(items)); break; } } } parser.walk(); }); } function handleEventEntry(items: Entry.TypeKeywords[]) { return ({ entry }: Parser.EntryEvent<Entry.TypeKeywords>) => { const index = items.findIndex((item) => item.name === entry.name); if (index > -1) { items.splice(index, 1, entry); } else { items.push(entry); } }; }