flecks/packages/dox/build/parser.js
2024-01-25 23:03:49 -06:00

131 lines
3.4 KiB
JavaScript

const {readFile} = require('fs/promises');
const {join, relative} = require('path');
const {transformAsync} = require('@babel/core');
const {default: traverse} = require('@babel/traverse');
const {glob} = require('@flecks/core/server');
const {
buildFileVisitor,
configVisitor,
hookImplementationVisitor,
hookInvocationVisitor,
hookSpecificationVisitor,
todoVisitor,
} = require('./visitors');
exports.parseCode = async (code) => {
const {ast} = await transformAsync(code, {ast: true, code: false});
return ast;
};
exports.parseNormalSource = async (path, source) => {
const ast = await exports.parseCode(source);
const buildFiles = [];
const configs = [];
const hookImplementations = [];
const hookInvocations = [];
const todos = [];
const pushOne = (array) => (element) => {
array.push(element);
};
traverse(ast, buildFileVisitor(pushOne(buildFiles)));
traverse(ast, configVisitor((config) => {
const {description, key, location: {start: {index: start}, end: {index: end}}} = config;
configs.push({
defaultValue: source.slice(start, end),
description,
key,
});
}));
traverse(ast, hookImplementationVisitor((hookImplementation) => {
const {key: {value: hook}, loc: {start: {column, line}}} = hookImplementation;
hookImplementations.push({
column,
hook,
line,
});
}));
traverse(ast, hookInvocationVisitor((hookInvocation) => {
const isClassFile = [
'@flecks/core/build/flecks.js',
'@flecks/build/build/build.js',
]
.includes(path);
if (!isClassFile && hookInvocation.isThis) {
return;
}
const {location: {start: {column, line}}, hook, type} = hookInvocation;
hookInvocations.push({
column,
hook,
line,
type,
});
}));
traverse(ast, todoVisitor((todo) => {
const {description, location: {start: {index: start}, end: {index: end}}} = todo;
todos.push({
context: source.slice(start, end),
description,
});
}));
return {
buildFiles,
config: configs,
hookImplementations,
hookInvocations,
todos,
};
};
exports.parseHookSpecificationSource = async (path, source) => {
const ast = await exports.parseCode(source);
const hookSpecifications = [];
traverse(ast, hookSpecificationVisitor((hookSpecification) => {
const {
description,
hook,
location: {start: {index: start}, end: {index: end}},
params,
} = hookSpecification;
hookSpecifications.push({
description,
example: source.slice(start, end),
hook,
params,
});
}));
return {
hookSpecifications,
};
};
exports.parseSource = async (path, source) => {
if (path.match(/build\/flecks\.hooks\.js$/)) {
return exports.parseHookSpecificationSource(path, source);
}
return exports.parseNormalSource(path, source);
};
exports.parseFleckRoot = async (request) => (
Promise.all(
(await Promise.all([
...await glob(join(request, 'src', '**', '*.js')),
...await glob(join(request, 'build', '**', '*.js')),
]))
.map((filename) => [relative(request, filename), filename])
.map(async ([path, filename]) => {
const buffer = await readFile(filename);
return [path, await exports.parseSource(path, buffer.toString('utf8'))];
}),
)
);
exports.parseFlecks = async (flecks) => (
Promise.all(
flecks.roots
.map(async ([path, request]) => [path, await exports.parseFleckRoot(request)]),
)
);