@vuedoc/parser
Version:
Generate a JSON documentation for a Vue file
179 lines • 5.6 kB
JavaScript
import resolve from 'resolve/sync.js';
import _traverse from '@babel/traverse';
import { readFileSync } from 'node:fs';
import { extname, join } from 'node:path';
import { parse } from '@babel/parser';
import { merge } from '@b613/utils/lib/object.js';
import { Loader } from './Loader.js';
// https://github.com/babel/babel/issues/13855#issuecomment-945123514
const traverse = typeof _traverse === 'function' ? _traverse : _traverse.default;
const defaultResolver = {
basedir: process.cwd(),
extensions: ['.js', '.jsx', '.ts', '.tsx', '.d.ts', '.vue'],
};
const BABEL_DEFAULT_PLUGINS = [
'asyncGenerators',
'bigInt',
'classPrivateMethods',
'classPrivateProperties',
'classProperties',
'decorators-legacy',
'doExpressions',
'dynamicImport',
'exportDefaultFrom',
'exportNamespaceFrom',
// 'flow',
'flowComments',
'functionBind',
'functionSent',
'importMeta',
'logicalAssignment',
'nullishCoalescingOperator',
'numericSeparator',
'objectRestSpread',
'optionalCatchBinding',
'optionalChaining',
'partialApplication',
['pipelineOperator', { proposal: 'smart' }],
'placeholders',
'privateIn',
'throwExpressions',
'topLevelAwait',
];
function parseAst(content, plugins = []) {
return parse(content, {
allowImportExportEverywhere: true,
allowAwaitOutsideFunction: true,
allowReturnOutsideFunction: true,
allowSuperOutsideMethod: true,
allowUndeclaredExports: true,
createParenthesizedExpressions: false,
errorRecovery: false,
plugins: [...BABEL_DEFAULT_PLUGINS, ...plugins],
sourceType: 'module',
strictMode: false,
ranges: true,
tokens: false,
});
}
function parseAlias(path, aliasMap) {
for (const prefix in aliasMap) {
if (path.startsWith(prefix)) {
const alias = aliasMap[prefix];
return join(alias, path.slice(prefix.length));
}
}
return path;
}
function findComment({ trailingComments = [], leadingComments = trailingComments, ...node }) {
const leadingComment = leadingComments?.reverse()[0];
if (leadingComment) {
return leadingComment.start && node.end && leadingComment.start > node.end ? null : leadingComment.value;
}
return null;
}
function loadFileContent(loaderName, filecontent, { plugins = [], ...options }) {
const loaderOptions = {
definitions: options.loaders,
source: {
errors: [],
},
};
const LoaderClass = Loader.get(loaderName, loaderOptions);
const loader = new LoaderClass(loaderOptions);
loader.load({
attrs: {
lang: loaderName,
},
content: filecontent,
});
const file = loader.source;
if (file.errors.length) {
throw new ParsingError(loader.source.errors[0]);
}
if (file.script?.content) {
if (plugins.length === 0) {
plugins = parsePlugins(file.script.attrs.lang, options);
}
file.script.ast = parseAst(file.script.content, plugins);
traverse(file.script.ast, {
enter(path) {
if (path.node) {
if (!path.node.extra) {
path.node.extra = {};
}
path.node.extra.file = file;
path.node.extra.comment = findComment(path.node);
}
},
});
}
else {
delete file.script;
}
return file;
}
function parsePlugins(lang, options = {}) {
const plugins = [];
switch (lang) {
case 'ts':
if (options.path?.endsWith('.d.ts')) {
plugins.push(['typescript', { dts: true }]);
}
else {
plugins.push('typescript');
}
break;
case 'tsx':
plugins.push('typescript');
plugins.push('jsx');
break;
case 'jsx':
plugins.push('jsx');
break;
default:
plugins.push('typescript');
if (options.jsx) {
plugins.push('jsx');
}
break;
}
return plugins;
}
export class ParsingError extends Error {
}
export class FS {
constructor(options) {
this.index = {};
this.options = options;
}
loadFile(filename, resolver = this.options.resolver || defaultResolver) {
const source = resolver.alias ? parseAlias(filename, resolver.alias) : filename;
if (source in this.index) {
return this.index[source];
}
let path;
const currentResolver = merge({ ...defaultResolver }, resolver);
try {
path = resolve(source, currentResolver);
}
catch {
throw new Error(`Cannot find module '${filename}'. Make sure to define options.resolver`);
}
if (path in this.index) {
return this.index[path];
}
const lang = extname(path).substring(1);
const plugins = parsePlugins(lang, { path, jsx: this.options.jsx });
const filecontent = readFileSync(path, this.options.encoding || 'utf8');
const file = loadFileContent(lang, filecontent, { ...this.options, plugins });
file.filename = path;
this.index[source] = file;
this.index[path] = file;
return file;
}
loadContent(loaderName, filecontent) {
return loadFileContent(loaderName, filecontent, this.options);
}
}
//# sourceMappingURL=FS.js.map