1/** 2 * @fileoverview Require usage of specified node modules. 3 * @author Rich Trott 4 */ 5'use strict'; 6 7const { isRequireCall, isString } = require('./rules-utils.js'); 8 9//------------------------------------------------------------------------------ 10// Rule Definition 11//------------------------------------------------------------------------------ 12 13module.exports = function(context) { 14 // Trim required module names 15 const options = context.options[0]; 16 const requiredModules = options ? Object.keys(options).map((x) => { 17 return [ x, new RegExp(options[x]) ]; 18 }) : []; 19 const isESM = context.parserOptions.sourceType === 'module'; 20 21 const foundModules = []; 22 23 // If no modules are required we don't need to check the CallExpressions 24 if (requiredModules.length === 0) { 25 return {}; 26 } 27 28 /** 29 * Function to check if the path is a required module and return its name. 30 * @param {String} str The path to check 31 * @returns {undefined|String} required module name or undefined 32 */ 33 function getRequiredModuleName(str) { 34 const match = requiredModules.find(([, test]) => { 35 return test.test(str); 36 }); 37 return match ? match[0] : undefined; 38 } 39 40 /** 41 * Function to check if a node has an argument that is a required module and 42 * return its name. 43 * @param {ASTNode} node The node to check 44 * @returns {undefined|String} required module name or undefined 45 */ 46 function getRequiredModuleNameFromCall(node) { 47 // Node has arguments and first argument is string 48 if (node.arguments.length && isString(node.arguments[0])) { 49 return getRequiredModuleName(node.arguments[0].value.trim()); 50 } 51 52 return undefined; 53 } 54 55 const rules = { 56 'Program:exit'(node) { 57 if (foundModules.length < requiredModules.length) { 58 const missingModules = requiredModules.filter( 59 ([module]) => foundModules.indexOf(module) === -1 60 ); 61 missingModules.forEach(([moduleName]) => { 62 context.report( 63 node, 64 'Mandatory module "{{moduleName}}" must be loaded.', 65 { moduleName: moduleName } 66 ); 67 }); 68 } 69 } 70 }; 71 72 if (isESM) { 73 rules.ImportDeclaration = (node) => { 74 const requiredModuleName = getRequiredModuleName(node.source.value); 75 if (requiredModuleName) { 76 foundModules.push(requiredModuleName); 77 } 78 }; 79 } else { 80 rules.CallExpression = (node) => { 81 if (isRequireCall(node)) { 82 const requiredModuleName = getRequiredModuleNameFromCall(node); 83 84 if (requiredModuleName) { 85 foundModules.push(requiredModuleName); 86 } 87 } 88 }; 89 } 90 91 return rules; 92}; 93 94module.exports.meta = { 95 schema: [{ 96 'type': 'object', 97 'additionalProperties': { 98 'type': 'string' 99 }, 100 }], 101}; 102