332 lines
9.8 KiB
JavaScript
332 lines
9.8 KiB
JavaScript
const {
|
|
isArrayExpression,
|
|
isArrowFunctionExpression,
|
|
isIdentifier,
|
|
isLiteral,
|
|
isMemberExpression,
|
|
isObjectExpression,
|
|
isStringLiteral,
|
|
isThisExpression,
|
|
isVariableDeclaration,
|
|
isBlockStatement,
|
|
isReturnStatement,
|
|
isFunction,
|
|
isCallExpression,
|
|
} = require('@babel/types');
|
|
const {Flecks} = require('@flecks/core/build/flecks');
|
|
const {parse: parseComment} = require('comment-parser');
|
|
|
|
function visitProperties(properties, fn) {
|
|
properties.forEach((property) => {
|
|
const {key} = property;
|
|
if (isLiteral(key)) {
|
|
fn(property);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Test Flecks.hooks(require.context(...))
|
|
function testRight(right, fn) {
|
|
if (isCallExpression(right)) {
|
|
if (isMemberExpression(right.callee)) {
|
|
if (
|
|
isIdentifier(right.callee.object) && 'Flecks' === right.callee.object.name
|
|
&& isIdentifier(right.callee.property) && 'hooks' === right.callee.property.name
|
|
) {
|
|
if (isCallExpression(right.arguments[0])) {
|
|
if (
|
|
isIdentifier(right.arguments[0].callee.object)
|
|
&& 'require' === right.arguments[0].callee.object.name
|
|
&& isIdentifier(right.arguments[0].callee.property)
|
|
&& 'context' === right.arguments[0].callee.property.name
|
|
) {
|
|
fn(right.arguments[0].arguments);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
exports.hookBaseVisitor = (fn) => ({
|
|
// exports.hooks = Flecks.hooks(require.context(...))
|
|
AssignmentExpression(path) {
|
|
const {left, right} = path.node;
|
|
if (isMemberExpression(left)) {
|
|
if (isIdentifier(left.object) && 'exports' === left.object.name) {
|
|
if (isIdentifier(left.property) && 'hooks' === left.property.name) {
|
|
testRight(right, fn);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
// export const hooks = Flecks.hooks(require.context(...))
|
|
ExportNamedDeclaration(path) {
|
|
const {declaration} = path.node;
|
|
if (isVariableDeclaration(declaration)) {
|
|
const {declarations} = declaration;
|
|
declarations.forEach((declarator) => {
|
|
if ('hooks' === declarator.id.name) {
|
|
testRight(declarator.init, fn);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
});
|
|
|
|
exports.hookImplementationVisitor = (fn) => ({
|
|
// exports.hooks = {...}
|
|
AssignmentExpression(path) {
|
|
const {left, right} = path.node;
|
|
if (isMemberExpression(left)) {
|
|
if (isIdentifier(left.object) && 'exports' === left.object.name) {
|
|
if (isIdentifier(left.property) && 'hooks' === left.property.name) {
|
|
if (isObjectExpression(right)) {
|
|
visitProperties(right.properties, fn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
// export const hooks = {...}
|
|
ExportNamedDeclaration(path) {
|
|
const {declaration} = path.node;
|
|
if (isVariableDeclaration(declaration)) {
|
|
const {declarations} = declaration;
|
|
declarations.forEach((declarator) => {
|
|
if ('hooks' === declarator.id.name) {
|
|
if (isObjectExpression(declarator.init)) {
|
|
visitProperties(declarator.init.properties, fn);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
},
|
|
});
|
|
|
|
exports.hookVisitor = (hook) => (fn) => (
|
|
exports.hookImplementationVisitor((property) => (
|
|
hook === property.key.value ? fn(property) : undefined
|
|
))
|
|
);
|
|
|
|
function functionResultVisitor(value, fn) {
|
|
if (isArrowFunctionExpression(value)) {
|
|
fn(value.body);
|
|
}
|
|
if (isFunction(value)) {
|
|
if (isBlockStatement(value.body)) {
|
|
for (let i = 0; i < value.body.body.length; ++i) {
|
|
if (isReturnStatement(value.body.body[i])) {
|
|
fn(value.body.body[i].argument);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
exports.buildFileVisitorRaw = (node, fn) => {
|
|
if (isArrayExpression(node)) {
|
|
node.elements
|
|
.map((element) => {
|
|
let filename;
|
|
if (isStringLiteral(element)) {
|
|
filename = element.value;
|
|
}
|
|
if (!filename) {
|
|
return undefined;
|
|
}
|
|
return {
|
|
filename,
|
|
description: (
|
|
(element.leadingComments?.length > 0)
|
|
? element.leadingComments.pop().value.split('\n')
|
|
.map((line) => line.trim())
|
|
.map((line) => line.replace(/^\*/, ''))
|
|
.map((line) => line.trim())
|
|
.join('\n')
|
|
.trim()
|
|
: undefined
|
|
),
|
|
};
|
|
})
|
|
.filter((buildFile) => buildFile)
|
|
.forEach(fn);
|
|
}
|
|
};
|
|
|
|
exports.buildFileVisitor = (fn) => exports.hookVisitor('@flecks/build.files')(
|
|
(property) => {
|
|
functionResultVisitor(property.value, (node) => {
|
|
exports.buildFileVisitorRaw(node, fn);
|
|
});
|
|
},
|
|
);
|
|
|
|
exports.configVisitorRaw = (node, fn) => {
|
|
if (isObjectExpression(node)) {
|
|
node.properties.forEach((property) => {
|
|
if (isIdentifier(property.key) || isStringLiteral(property.key)) {
|
|
fn({
|
|
key: property.key.name || property.key.value,
|
|
description: (property.leadingComments?.length > 0)
|
|
? property.leadingComments.pop().value.split('\n')
|
|
.map((line) => line.trim())
|
|
.map((line) => line.replace(/^\*/, ''))
|
|
.map((line) => line.trim())
|
|
.filter((line) => !!line)
|
|
.join(' ')
|
|
.trim()
|
|
: undefined,
|
|
location: property.value.loc,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
exports.configVisitor = (fn) => exports.hookVisitor('@flecks/core.config')(
|
|
(property) => {
|
|
functionResultVisitor(property.value, (node) => {
|
|
exports.configVisitorRaw(node, fn);
|
|
});
|
|
},
|
|
);
|
|
|
|
exports.hookInvocationVisitor = (fn) => ({
|
|
CallExpression(path) {
|
|
if (isMemberExpression(path.node.callee)) {
|
|
if (
|
|
(
|
|
isIdentifier(path.node.callee.object)
|
|
&& 'flecks' === path.node.callee.object.name
|
|
)
|
|
|| (
|
|
isIdentifier(path.node.callee.object.property)
|
|
&& 'flecks' === path.node.callee.object.property.name
|
|
)
|
|
|| isThisExpression(path.node.callee.object)
|
|
) {
|
|
if (isIdentifier(path.node.callee.property)) {
|
|
const invocation = {
|
|
isThis: isThisExpression(path.node.callee.object),
|
|
location: path.node.loc,
|
|
};
|
|
if (path.node.callee.property.name.match(/^invoke.*/)) {
|
|
if (path.node.arguments.length > 0) {
|
|
if (isStringLiteral(path.node.arguments[0])) {
|
|
fn({
|
|
...invocation,
|
|
hook: path.node.arguments[0].value,
|
|
type: path.node.callee.property.name,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if ('makeMiddleware' === path.node.callee.property.name) {
|
|
if (path.node.arguments.length > 0) {
|
|
if (isStringLiteral(path.node.arguments[0])) {
|
|
fn({
|
|
...invocation,
|
|
hook: path.node.arguments[0].value,
|
|
type: 'invokeFleck',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if ('gather' === path.node.callee.property.name) {
|
|
if (path.node.arguments.length > 0) {
|
|
if (isStringLiteral(path.node.arguments[0])) {
|
|
fn({
|
|
...invocation,
|
|
hook: path.node.arguments[0].value,
|
|
type: 'invokeMerge',
|
|
});
|
|
fn({
|
|
...invocation,
|
|
hook: `${path.node.arguments[0].value}.decorate`,
|
|
type: 'invokeComposed',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
});
|
|
|
|
exports.hookSpecificationVisitor = (fn) => (
|
|
exports.hookImplementationVisitor((property) => {
|
|
if (property.leadingComments) {
|
|
const {key, value: example} = property;
|
|
const [{value}] = property.leadingComments;
|
|
const [{description, tags}] = parseComment(`/**\n${value}\n*/`, {spacing: 'preserve'});
|
|
const [invoke] = tags
|
|
.filter(({tag}) => 'invoke' === tag)
|
|
.map(({name}) => (name ? `invoke${name}` : 'invoke'));
|
|
const [returns] = tags
|
|
.filter(({tag}) => 'returns' === tag)
|
|
.map(({description, name, type}) => ({description: `${name} ${description}`.trim(), type}));
|
|
fn({
|
|
hook: key.value,
|
|
description: description.trim(),
|
|
location: example.loc,
|
|
params: tags
|
|
.filter(({tag}) => 'param' === tag)
|
|
.map(({description, name, type}) => ({description, name, type})),
|
|
...returns && {returns},
|
|
...invoke && {invoke},
|
|
});
|
|
}
|
|
})
|
|
);
|
|
|
|
exports.todoVisitor = (fn) => ({
|
|
enter(path) {
|
|
if (path.node.leadingComments) {
|
|
path.node.leadingComments.forEach((comment, i) => {
|
|
if (comment.value.toLowerCase().match('@todo')) {
|
|
fn({
|
|
description: path.node.leadingComments
|
|
.slice(i)
|
|
.map(({value}) => {
|
|
const index = value.indexOf('@todo');
|
|
return -1 === index ? value : value.slice(index + 6);
|
|
})
|
|
.join(''),
|
|
location: path.node.loc,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
},
|
|
});
|
|
|
|
exports.hookExportVisitor = (fn) => ({
|
|
AssignmentExpression(path) {
|
|
const {left, right} = path.node;
|
|
if (isMemberExpression(left)) {
|
|
if (isIdentifier(left.object) && 'exports' === left.object.name) {
|
|
if (isIdentifier(left.property) && 'hook' === left.property.name) {
|
|
if (isFunction(right)) {
|
|
functionResultVisitor(right, fn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
ExportNamedDeclaration(path) {
|
|
if (isVariableDeclaration(path.node.declaration)) {
|
|
if ('hook' === Flecks.get(path, 'node.declaration.declarations[0].id.name')) {
|
|
if (isFunction(path.node.declaration.declarations[0].id.init)) {
|
|
functionResultVisitor(path.node.declaration.declarations[0].id.init, fn);
|
|
}
|
|
}
|
|
}
|
|
if (isFunction(path.node.declaration)) {
|
|
functionResultVisitor(path.node.declaration, fn);
|
|
}
|
|
},
|
|
});
|