• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Check that common.hasCrypto is used if crypto, tls,
3 * https, or http2 modules are required.
4 *
5 * This rule can be ignored using // eslint-disable-line crypto-check
6 *
7 * @author Daniel Bevenius <daniel.bevenius@gmail.com>
8 */
9'use strict';
10
11const utils = require('./rules-utils.js');
12
13//------------------------------------------------------------------------------
14// Rule Definition
15//------------------------------------------------------------------------------
16const msg = 'Please add a hasCrypto check to allow this test to be skipped ' +
17            'when Node is built "--without-ssl".';
18
19const cryptoModules = ['crypto', 'http2'];
20const requireModules = cryptoModules.concat(['tls', 'https']);
21const bindingModules = cryptoModules.concat(['tls_wrap']);
22
23module.exports = function(context) {
24  const missingCheckNodes = [];
25  const requireNodes = [];
26  let commonModuleNode = null;
27  let hasSkipCall = false;
28
29  function testCryptoUsage(node) {
30    if (utils.isRequired(node, requireModules) ||
31        utils.isBinding(node, bindingModules)) {
32      requireNodes.push(node);
33    }
34
35    if (utils.isCommonModule(node)) {
36      commonModuleNode = node;
37    }
38  }
39
40  function testIfStatement(node) {
41    if (node.test.argument === undefined) {
42      return;
43    }
44    if (isCryptoCheck(node.test.argument)) {
45      checkCryptoCall(node);
46    }
47  }
48
49  function isCryptoCheck(node) {
50    return utils.usesCommonProperty(node, ['hasCrypto', 'hasFipsCrypto']);
51  }
52
53  function checkCryptoCall(node) {
54    if (utils.inSkipBlock(node)) {
55      hasSkipCall = true;
56    } else {
57      missingCheckNodes.push(node);
58    }
59  }
60
61  function testMemberExpression(node) {
62    if (isCryptoCheck(node)) {
63      checkCryptoCall(node);
64    }
65  }
66
67  function reportIfMissingCheck() {
68    if (hasSkipCall) {
69      // There is a skip, which is good, but verify that the require() calls
70      // in question come after at least one check.
71      if (missingCheckNodes.length > 0) {
72        requireNodes.forEach((requireNode) => {
73          const beforeAllChecks = missingCheckNodes.every((checkNode) => {
74            return requireNode.range[0] < checkNode.range[0];
75          });
76
77          if (beforeAllChecks) {
78            context.report({
79              node: requireNode,
80              message: msg
81            });
82          }
83        });
84      }
85      return;
86    }
87
88    if (requireNodes.length > 0) {
89      if (missingCheckNodes.length > 0) {
90        report(missingCheckNodes);
91      } else {
92        report(requireNodes);
93      }
94    }
95  }
96
97  function report(nodes) {
98    nodes.forEach((node) => {
99      context.report({
100        node,
101        message: msg,
102        fix: (fixer) => {
103          if (commonModuleNode) {
104            return fixer.insertTextAfter(
105              commonModuleNode,
106              '\nif (!common.hasCrypto) {' +
107              ' common.skip("missing crypto");' +
108              '}'
109            );
110          }
111        }
112      });
113    });
114  }
115
116  return {
117    'CallExpression': (node) => testCryptoUsage(node),
118    'IfStatement:exit': (node) => testIfStatement(node),
119    'MemberExpression:exit': (node) => testMemberExpression(node),
120    'Program:exit': () => reportIfMissingCheck()
121  };
122};
123
124module.exports.meta = {
125  fixable: 'code'
126};
127