huncwot
Version:
A Programming Environment for TypeScript apps built on top of VS Code
112 lines (93 loc) • 3.06 kB
JavaScript
const fs = require('fs-extra');
const JSDocFunctionNameRegex = /@name (\w+)/;
const JSDocParamRegex = /@param (?:{(\w+)}\s+)?(\$?\w+)(.*)/g;
const SectionRegex = /(\/\*\*.*?\*\/)\n*(.*?)(?=(\/\*)|$)/sg;
const SQLParamRegex = / \${(\w+)}/gi;
function difference(set1, set2) {
const s1 = new Set(set1);
const s2 = new Set(set2);
const result = new Set();
for (const value of s1) {
if (!s2.has(value)) {
result.add(value);
}
}
return result;
}
function anonymize(sqlStatement) {
let counter = 0;
const sortedParams = [];
const namedToPositions = sqlStatement.replace(SQLParamRegex, (match, name) => {
sortedParams.push(name);
counter += 1;
return ` $${counter}`;
});
return {
sortedParams,
query: namedToPositions,
};
}
const extractSQLParams = (statement) => {
const matches = [...statement.matchAll(SQLParamRegex)];
return matches.map(({ 1: name }) => name);
};
const extractJSDocParams = (comment) => {
const matches = [...comment.matchAll(JSDocParamRegex)];
return matches
.map(({ 1: type = 'string', 2: name }) => ({ [name]: type }))
.reduce((stored, current) => ({ ...stored, ...current }), []);
};
function checkParameters(statement, comment) {
const SQLParams = extractSQLParams(statement);
const JSDocParams = extractJSDocParams(comment);
const SQLvsJSDoc = difference(SQLParams, Object.keys(JSDocParams));
if (SQLvsJSDoc.size !== 0) {
throw new SyntaxError(`
"${[...SQLvsJSDoc]}" params in the SQL statement:
${statement}
but not in the JSDoc section:
${comment}`);
}
const JSDocvsSQL = difference(Object.keys(JSDocParams), SQLParams);
if (JSDocvsSQL.size !== 0) {
throw new SyntaxError(`
"${[...JSDocvsSQL]}" params in JSDoc section:
${comment}
but not in the SQL statement:
${statement}`);
}
return Object.entries(JSDocParams);
}
function* parseContent(fileContent) {
for (const { 1: docstring, 2: statement } of fileContent.matchAll(SectionRegex)) {
if (!docstring.includes('@private')) {
const functionBlock = JSDocFunctionNameRegex.exec(docstring);
if (functionBlock == null) {
throw new SyntaxError(`Missing @name in the JSDoc comment ${docstring}`);
}
const [jsDocLine, name] = functionBlock;
const params = checkParameters(statement, docstring);
const { query, sortedParams } = anonymize(statement);
yield {
name,
params,
sortedParams,
query: query.trim(),
docstring: docstring.replace(jsDocLine, ''),
};
}
}
}
const compile = async filepath => {
const fileContent = await fs.readFile(filepath, 'utf8');
const sections = parseContent(fileContent);
let output = '// THIS FILE IS AUTOGENERATED - DO NOT MODIFY\n';
for (const { name, params, query, sortedParams } of sections) {
output += `export const ${name} = (${params.map(([param, type]) => `${param}: ${type.toLowerCase()}`).join(', ')}) => ({
text: '${query}',
values: [${sortedParams}]
});\n\n`;
}
return output;
};
module.exports = { compile };