@vuedoc/parser
Version:
Generate a JSON documentation for a Vue file
286 lines (240 loc) • 7.89 kB
text/typescript
// import { Feature, Parser, PluginInterface, Syntax, Type } from '@vuedoc/parser';
import { Parser, PluginInterface } from '@vuedoc/parser';
import { Feature, Syntax, Type } from '../main.js';
import { generateUndefineValue } from '../entity/Value.js';
import * as Babel from '@babel/types';
type StoreEntry = Record<string, Parser.NS>;
type Store = Record<'default' | string, StoreEntry>;
const fnameKey = {
mapState: 'state',
mapGetters: 'getters',
mapActions: 'actions',
};
export function createVuexPlugin(storeFilenames: string[] = []): PluginInterface {
return () => {
const store: Store = {
default: {},
};
const useStoreComposition: Parser.CompositionDeclaration = {
fname: 'useStore',
identifierSuffixes: ['state', 'getters'],
typeParameterIndex: 0,
returningValue: '',
parseEntryValue(node, context) {
if (node.arguments.length) {
const mainStore = createStore(node.arguments[0], context);
Object.assign(store, mainStore);
} else if ('typeParameters' in node && node.typeParameters.params.length) {
return context.getTypingValue(node.typeParameters.params[0]);
}
return generateUndefineValue.next().value;
},
parseEntryNode(node, context) {
this.parseEntryValue(node, context);
},
};
return {
resolver: {
alias: {
vuex: 'vuex/types/index.d.ts',
},
},
preload: storeFilenames,
composition: {
data: [
useStoreComposition,
{
...useStoreComposition,
fname: 'createStore',
},
],
computed: [
{
fname: 'mapState',
parseEntryNode(node, context) {
parseNodeEntry.call(this, node, context, store);
},
},
{
fname: 'mapGetters',
parseEntryNode(node, context) {
parseNodeEntry.call(this, node, context, store);
},
},
],
methods: [
{
fname: 'mapActions',
parseEntryNode(node, context) {
parseNodeEntry.call(this, node, context, store);
},
},
],
},
};
};
}
function createStore(node: Babel.Node, context: Parser.Context) {
const store: Store = {
default: {},
};
switch (node.type) {
case Syntax.Identifier: {
const identifier = context.getIdentifier(node);
if (identifier && 'node' in identifier) {
if (identifier.node.value.type === Syntax.ObjectExpression && identifier.value.rawObject) {
for (const key in identifier.value.rawObject) {
const ref = identifier.value.rawObject[key];
const refNode = identifier.value.rawNode[key];
context.setScopeEntry({
key,
value: ref,
node: {
value: refNode,
type: refNode,
comment: refNode,
},
});
}
}
const identifierStore = createStore(identifier.node.value, context);
Object.assign(store, identifierStore);
}
break;
}
case Syntax.ObjectExpression: {
const properties = context.parseElements(node.properties);
for (const property of properties) {
if ('name' in property.key && property.type === Syntax.ObjectProperty) {
switch (property.key.name) {
case 'state':
case 'getters':
case 'actions':
parseStoreEntry(property.key.name, property, context, store.default);
break;
case 'modules':
if (property.value.type === Syntax.ObjectExpression) {
const modules = context.parseElements(property.value.properties);
for (const module of modules) {
if ('name' in module.key && module.type === Syntax.ObjectProperty) {
store[module.key.name] = createStore(module.value, context).default;
}
}
}
break;
}
}
}
}
}
return store;
}
function parseStoreEntry(type: string, property: Babel.ObjectProperty, context: Parser.Context, storeEntry: StoreEntry) {
let objectExpressionNode: Babel.Node = property.value;
if (property.value.type === Syntax.Identifier) {
const identifier = context.getIdentifier(property.value);
if (identifier) {
if ('$ns' in identifier) {
storeEntry[type] = identifier;
return;
}
context.setScopeEntry(identifier);
objectExpressionNode = identifier.node.value;
}
}
if (objectExpressionNode.type === Syntax.ObjectExpression) {
const ns = context.createNamespace();
const items = context.parseElements(objectExpressionNode.properties);
storeEntry[type] = ns;
for (const item of items) {
if ('name' in item.key) {
const key = item.key.name;
const nodeValue = item.type === Syntax.ObjectProperty ? item.value : item;
const value = context.getValue(nodeValue);
ns.scope[key] = {
key,
value,
node: {
value: nodeValue,
type: nodeValue,
comment: item,
},
};
}
}
}
}
function parseFeatureEntry(
fname: string,
feature: Parser.Feature,
entryName: string,
node: Babel.Node,
context: Parser.Context,
store: StoreEntry
) {
let entryRef: Parser.Value;
let entryNode: Babel.Node;
if (fname in fnameKey) {
const key = fnameKey[fname];
if (key in store) {
const ns = store[key];
if (entryName in ns.scope) {
const ref = ns.scope[entryName];
entryRef = ref.value;
entryNode = ref.node.value;
if ('params' in entryNode) {
const [param] = entryNode.params.splice(0, 1);
if (key === 'getters') {
const state = context.getScopeValue('state');
if (state && param.type === Syntax.Identifier) {
context.scope[param.name] = state;
}
}
}
if (feature === Feature.computed && 'params' in entryNode) {
entryRef.type = context.getReturnType(entryNode);
}
}
}
}
if (!entryRef) {
entryRef = context.getValue(node);
entryRef.type = Type.unknown;
entryNode = node;
}
context.emitEntryFeature({
feature,
name: entryName,
ref: entryRef,
node: entryNode,
});
}
function parseNodeEntry(node: Babel.CallExpression, context: Parser.Context, store: Store) {
if (node.arguments.length) {
const firstArgNode = node.arguments[0];
const firstArgRef = context.getValue(firstArgNode);
const namespaceName = typeof firstArgRef.value === 'string' ? firstArgRef.value : 'default';
const storeEntry = store[namespaceName] || {};
for (const argument of node.arguments) {
const elements = argument.type === Syntax.ObjectExpression
? context.parseElements(argument.properties)
: argument.type === Syntax.ArrayExpression
? context.parseElements(argument.elements)
: [];
for (const element of elements) {
const entryFeature = this.fname === 'mapActions' ? Feature.methods : Feature.computed;
switch (element.type) {
case Syntax.ObjectProperty:
case Syntax.ObjectMethod: {
const name = context.getValue<string>(element.key);
parseFeatureEntry(this.fname, entryFeature, name.value, element, context, storeEntry);
break;
}
case Syntax.StringLiteral:
parseFeatureEntry(this.fname, entryFeature, element.value, element, context, storeEntry);
break;
}
}
}
}
}