| /** |
| * @fileoverview Require usage of specified node modules. |
| * @author Rich Trott |
| */ |
| 'use strict'; |
| |
| const { isRequireCall, isString } = require('./rules-utils.js'); |
| |
| //------------------------------------------------------------------------------ |
| // Rule Definition |
| //------------------------------------------------------------------------------ |
| |
| module.exports = function(context) { |
| // Trim required module names |
| const options = context.options[0]; |
| const requiredModules = options ? Object.keys(options).map((x) => { |
| return [ x, new RegExp(options[x]) ]; |
| }) : []; |
| const isESM = context.parserOptions.sourceType === 'module'; |
| |
| const foundModules = []; |
| |
| // If no modules are required we don't need to check the CallExpressions |
| if (requiredModules.length === 0) { |
| return {}; |
| } |
| |
| /** |
| * Function to check if the path is a required module and return its name. |
| * @param {String} str The path to check |
| * @returns {undefined|String} required module name or undefined |
| */ |
| function getRequiredModuleName(str) { |
| const match = requiredModules.find(([, test]) => { |
| return test.test(str); |
| }); |
| return match ? match[0] : undefined; |
| } |
| |
| /** |
| * Function to check if a node has an argument that is a required module and |
| * return its name. |
| * @param {ASTNode} node The node to check |
| * @returns {undefined|String} required module name or undefined |
| */ |
| function getRequiredModuleNameFromCall(node) { |
| // Node has arguments and first argument is string |
| if (node.arguments.length && isString(node.arguments[0])) { |
| return getRequiredModuleName(node.arguments[0].value.trim()); |
| } |
| |
| return undefined; |
| } |
| |
| const rules = { |
| 'Program:exit'(node) { |
| if (foundModules.length < requiredModules.length) { |
| const missingModules = requiredModules.filter( |
| ([module]) => foundModules.indexOf(module) === -1 |
| ); |
| missingModules.forEach(([moduleName]) => { |
| context.report( |
| node, |
| 'Mandatory module "{{moduleName}}" must be loaded.', |
| { moduleName: moduleName } |
| ); |
| }); |
| } |
| } |
| }; |
| |
| if (isESM) { |
| rules.ImportDeclaration = (node) => { |
| const requiredModuleName = getRequiredModuleName(node.source.value); |
| if (requiredModuleName) { |
| foundModules.push(requiredModuleName); |
| } |
| }; |
| } else { |
| rules.CallExpression = (node) => { |
| if (isRequireCall(node)) { |
| const requiredModuleName = getRequiredModuleNameFromCall(node); |
| |
| if (requiredModuleName) { |
| foundModules.push(requiredModuleName); |
| } |
| } |
| }; |
| } |
| |
| return rules; |
| }; |
| |
| module.exports.meta = { |
| schema: [{ |
| 'type': 'object', |
| 'additionalProperties': { |
| 'type': 'string' |
| }, |
| }], |
| }; |