lion/packages-node/providence-analytics/src/cli/prompt-analyzer-menu.js
2021-11-22 19:01:40 +01:00

133 lines
4.2 KiB
JavaScript

const fs = require('fs');
const pathLib = require('path');
const inquirer = require('inquirer');
const { default: traverse } = require('@babel/traverse');
const { InputDataService } = require('../program/services/InputDataService.js');
const { AstService } = require('../program/services/AstService.js');
const { LogService } = require('../program/services/LogService.js');
const JsdocCommentParser = require('../program/utils/jsdoc-comment-parser.js');
/**
* Extracts name, defaultValue, optional, type, desc from JsdocCommentParser.parse method
* result
* @param {string[]} jsdoc
* @returns {{ name:string, defaultValue:string, optional:boolean, type:string, desc:string }}
*/
function getPropsFromParsedJsDoc(jsdoc) {
const jsdocProps = jsdoc.filter(p => p.tagName === '@property');
const options = jsdocProps.map(({ tagValue }) => {
// eslint-disable-next-line no-unused-vars
const [_, type, nameOptionalDefault, desc] = tagValue.match(/\{(.*)\}\s*([^\s]*)\s*(.*)/);
let nameDefault = nameOptionalDefault;
let optional = false;
if (nameOptionalDefault.startsWith('[') && nameOptionalDefault.endsWith(']')) {
optional = true;
nameDefault = nameOptionalDefault.slice(1).slice(0, -1);
}
const [name, defaultValue] = nameDefault.split('=');
return { name, defaultValue, optional, type, desc };
});
return options;
}
function getAnalyzerOptions(file) {
const code = fs.readFileSync(file, 'utf8');
const ast = AstService.getAst(code, 'babel', { filePath: file });
let commentNode;
traverse(ast, {
// eslint-disable-next-line no-shadow
VariableDeclaration(path) {
if (!path.node.leadingComments) {
return;
}
const decls = path.node.declarations || [];
decls.forEach(decl => {
if (decl && decl.id && decl.id.name === 'cfg') {
[commentNode] = path.node.leadingComments;
}
});
},
});
if (commentNode) {
const jsdoc = JsdocCommentParser.parse(commentNode);
return getPropsFromParsedJsDoc(jsdoc);
}
return undefined;
}
function gatherAnalyzers(dir, getConfigOptions) {
return InputDataService.gatherFilesFromDir(dir, { depth: 0 }).map(file => {
const analyzerObj = { file, name: pathLib.basename(file, '.js') };
if (getConfigOptions) {
analyzerObj.options = getAnalyzerOptions(file);
}
return analyzerObj;
});
}
async function promptAnalyzerConfigMenu(
analyzerName,
promptOptionalConfig,
dir = pathLib.resolve(__dirname, '../program/analyzers'),
) {
const menuOptions = gatherAnalyzers(dir, true);
const analyzer = menuOptions.find(o => o.name === analyzerName);
if (!analyzer) {
LogService.error(`[promptAnalyzerConfigMenu] analyzer "${analyzerName}" not found.`);
process.exit(1);
}
let configAnswers;
if (analyzer.options) {
configAnswers = await inquirer.prompt(
analyzer.options
.filter(a => promptOptionalConfig || !a.optional)
.map(a => ({
name: a.name,
message: a.description,
...(a.defaultValue ? { default: a.defaultValue } : {}),
})),
);
Object.entries(configAnswers).forEach(([key, value]) => {
const { type } = analyzer.options.find(o => o.name === key);
if (type.toLowerCase() === 'boolean') {
configAnswers[key] = value === 'false' ? false : Boolean(value);
} else if (type.toLowerCase() === 'number') {
configAnswers[key] = Number(value);
} else if (type.toLowerCase() !== 'string') {
if (value) {
configAnswers[key] = JSON.parse(value);
} else {
// Make sure to not override predefined values with undefined ones
delete configAnswers[key];
}
}
});
}
return {
analyzerConfig: configAnswers,
};
}
async function promptAnalyzerMenu(dir = pathLib.resolve(__dirname, '../program/analyzers')) {
const menuOptions = gatherAnalyzers(dir);
const answers = await inquirer.prompt([
{
type: 'list',
name: 'analyzerName',
message: 'Which analyzer do you want to run?',
choices: menuOptions.map(o => o.name),
},
]);
return {
analyzerName: answers.analyzerName,
};
}
module.exports = {
promptAnalyzerMenu,
promptAnalyzerConfigMenu,
};