• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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