chai-latte
Version:
Build expressive & readable fluent interface libraries.
172 lines (168 loc) • 7.51 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getBuilderFromLocalFile = exports.createTypegingForBuilders = exports.generateTypedApiFromPath = exports.readConfigFile = void 0;
const tslib_1 = require("tslib");
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const path_1 = tslib_1.__importDefault(require("path"));
const dedent_1 = tslib_1.__importDefault(require("dedent"));
const ConfigurableCallback_1 = require("../builder/lib/ConfigurableCallback");
const readConfigFile = (filePath) => {
const configPath = path_1.default.resolve(filePath, 'codegen.json');
const fileContent = fs_extra_1.default.readFileSync(configPath, 'utf8');
const json = JSON.parse(fileContent);
return json;
};
exports.readConfigFile = readConfigFile;
const generateTypedApiFromPath = async (config) => {
const compiled = await (0, exports.getBuilderFromLocalFile)(config);
const typings = await (0, exports.createTypegingForBuilders)(config, compiled);
await writeGeneratedFile(config, typings);
return true;
};
exports.generateTypedApiFromPath = generateTypedApiFromPath;
const writeGeneratedFile = async (config, typings) => {
const outputFilePath = await getOutputFilePath(config);
await fs_extra_1.default.ensureFile(outputFilePath);
await fs_extra_1.default.writeFile(outputFilePath, typings);
};
const getOutputFilePath = async (config) => {
const pathStats = await fs_extra_1.default.stat(config.input + '.ts');
let configFilePath = config.input;
if (pathStats.isFile()) {
const filePaths = config.input.split('/');
filePaths.pop();
configFilePath = filePaths.join('/');
}
const outputFileName = config.output || 'generated.ts';
const relativePath = path_1.default.relative(__dirname, configFilePath + '/' + outputFileName);
const absolutePath = path_1.default.resolve(__dirname, relativePath);
return absolutePath;
};
const createTypegingForBuilders = async (config, compiled) => {
const getTabs = (tabs) => {
const TAB = ' ';
return {
start: Array.from({ length: tabs }).map(() => '').join(TAB),
end: Array.from({ length: tabs - 1 }).map(() => '').join(TAB),
next: tabs + 1,
};
};
const getArgumentNameFromArgType = (arg) => {
const firstChar = arg.name.toLowerCase().slice(0, 1);
const lastChars = arg.name.slice(1);
const argName = firstChar + lastChars;
switch (argName) {
case 'string': return 'str';
case 'boolean': return 'bool';
case 'number': return 'num';
case 'object': return 'obj';
}
return argName;
};
const getArgumentTypeFromCallback = (callback, arg) => {
const originalCallback = callback.originCallbackByArg.get(arg);
const rowIdx = (originalCallback || callback).expression.index;
const callIdx = callback.callIndex;
return `Arg<${rowIdx}, ${callIdx}>`;
};
const buildKeys = (currentTabs, obj) => {
const tabs = getTabs(currentTabs);
let typings = "{\n";
Object.keys(obj).map(key => {
if (key.startsWith('__'))
return;
typings += `${tabs.start}${key}: ${buildValue(tabs.next, obj[key])}\n`;
}).join(';\n');
typings += `${tabs.end}};`;
return typings;
};
const buildFunction = (currentTabs, fn) => {
const callback = ConfigurableCallback_1.ConfigurableCallback.configByCallback.get(fn);
const tabs = getTabs(currentTabs);
const returnByArg = Array.from(callback.returnByArg)[0];
const returned = returnByArg[1];
const isLastCall = returned == callback.expression.callback;
let typings = '{\n';
Object.keys(callback.props).forEach((key) => {
typings += `${tabs.start}${key}: ${buildValue(tabs.next, callback.props[key])}\n`;
});
callback.returnByArg.forEach((value, arg) => {
const argName = getArgumentNameFromArgType(arg);
const argType = getArgumentTypeFromCallback(callback, arg);
const functionCallType = `${tabs.start}(${argName}: ${argType})`;
if (isLastCall) {
const originalCallback = callback.originCallbackByArg.get(arg);
const rowIdx = (originalCallback || callback).expression.index;
typings += `${functionCallType} : Return<${rowIdx}>;\n`;
}
else {
typings += `${functionCallType} : ${buildKeys(tabs.next, value)}\n`;
}
});
typings += `${tabs.end}};`;
return typings;
};
const buildValue = (tabs, val) => {
if (typeof val === 'object') {
return buildKeys(tabs, val);
}
if (typeof val === 'function') {
return buildFunction(tabs, val);
}
return 'any';
};
const typings = (0, dedent_1.default) `
${createBaseTypings(config)}
type Root = ${buildKeys(3, compiled)}
export default builder as unknown as Root;
`;
return typings;
};
exports.createTypegingForBuilders = createTypegingForBuilders;
const getBuilderFromLocalFile = async (config) => {
const relativePath = path_1.default.relative(__dirname, config.input);
const { default: builders } = await Promise.resolve().then(() => tslib_1.__importStar(require('./' + relativePath)));
return builders;
};
exports.getBuilderFromLocalFile = getBuilderFromLocalFile;
const buildTypeForObject = ({ api, builder, args, index }) => {
let typeings = '';
const [key, value] = Object.entries(api)[0];
const innerType = typeof value === 'function'
? buildTypesForFunction({ callback: value, builder, args, index })
: buildTypeForObject({ api: value, builder, args, index });
typeings += `${key}: { ${innerType}; }`;
return typeings;
};
const buildTypesForFunction = ({ callback, builder, args, index }) => {
const callbackConfig = ConfigurableCallback_1.ConfigurableCallback.configByCallback.get(callback);
const returnByArg = Array.from(callbackConfig.returnByArg)[0];
const returned = returnByArg[1];
const argumentOrder = args.lastIndexOf(undefined) + 1;
const argumentName = args[argumentOrder];
args[argumentOrder] = undefined;
const argumentType = `Arg<${index}, ${argumentOrder}>`;
const isLastCall = builder.callback == returned;
const returnType = isLastCall
? `Return<${index}>`
: `{ ${buildTypeForObject({ api: returned, builder, args, index })} }`;
return `(${argumentName}: ${argumentType}) : ${returnType}`;
};
const createBaseTypings = (config) => {
// console.log('builderFilePath', builderFilePath);
const localPath = './' + config.input.split('/').pop();
return (0, dedent_1.default) `
/* ------------------------------------
* Generated by chai-latte
* Please do not edit this file directly
* Instead, edit the file '${localPath}'
* ------------------------------------
*/
import builder from '${localPath}';
type Expressions = typeof builder.__expressions;
type ExpressionCallback<Idx extends number> = Expressions[Idx][0]['callback'];
type Arg<Idx extends number, ArgIndex extends number> = Parameters<ExpressionCallback<Idx>>[ArgIndex];
type Return<Idx extends number> = ReturnType<ExpressionCallback<Idx>>;
`;
};
//# sourceMappingURL=index.js.map