moduleVisitor.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. 'use strict';
  2. exports.__esModule = true;
  3. /**
  4. * Returns an object of node visitors that will call
  5. * 'visitor' with every discovered module path.
  6. *
  7. * todo: correct function prototype for visitor
  8. * @param {Function(String)} visitor [description]
  9. * @param {[type]} options [description]
  10. * @return {object}
  11. */
  12. exports.default = function visitModules(visitor, options) {
  13. // if esmodule is not explicitly disabled, it is assumed to be enabled
  14. options = Object.assign({ esmodule: true }, options);
  15. let ignoreRegExps = [];
  16. if (options.ignore != null) {
  17. ignoreRegExps = options.ignore.map(p => new RegExp(p));
  18. }
  19. function checkSourceValue(source, importer) {
  20. if (source == null) return; //?
  21. // handle ignore
  22. if (ignoreRegExps.some(re => re.test(source.value))) return;
  23. // fire visitor
  24. visitor(source, importer);
  25. }
  26. // for import-y declarations
  27. function checkSource(node) {
  28. checkSourceValue(node.source, node);
  29. }
  30. // for esmodule dynamic `import()` calls
  31. function checkImportCall(node) {
  32. let modulePath;
  33. // refs https://github.com/estree/estree/blob/HEAD/es2020.md#importexpression
  34. if (node.type === 'ImportExpression') {
  35. modulePath = node.source;
  36. } else if (node.type === 'CallExpression') {
  37. if (node.callee.type !== 'Import') return;
  38. if (node.arguments.length !== 1) return;
  39. modulePath = node.arguments[0];
  40. }
  41. if (modulePath.type !== 'Literal') return;
  42. if (typeof modulePath.value !== 'string') return;
  43. checkSourceValue(modulePath, node);
  44. }
  45. // for CommonJS `require` calls
  46. // adapted from @mctep: https://git.io/v4rAu
  47. function checkCommon(call) {
  48. if (call.callee.type !== 'Identifier') return;
  49. if (call.callee.name !== 'require') return;
  50. if (call.arguments.length !== 1) return;
  51. const modulePath = call.arguments[0];
  52. if (modulePath.type !== 'Literal') return;
  53. if (typeof modulePath.value !== 'string') return;
  54. checkSourceValue(modulePath, call);
  55. }
  56. function checkAMD(call) {
  57. if (call.callee.type !== 'Identifier') return;
  58. if (call.callee.name !== 'require' &&
  59. call.callee.name !== 'define') return;
  60. if (call.arguments.length !== 2) return;
  61. const modules = call.arguments[0];
  62. if (modules.type !== 'ArrayExpression') return;
  63. for (const element of modules.elements) {
  64. if (element.type !== 'Literal') continue;
  65. if (typeof element.value !== 'string') continue;
  66. if (element.value === 'require' ||
  67. element.value === 'exports') continue; // magic modules: https://git.io/vByan
  68. checkSourceValue(element, element);
  69. }
  70. }
  71. const visitors = {};
  72. if (options.esmodule) {
  73. Object.assign(visitors, {
  74. 'ImportDeclaration': checkSource,
  75. 'ExportNamedDeclaration': checkSource,
  76. 'ExportAllDeclaration': checkSource,
  77. 'CallExpression': checkImportCall,
  78. 'ImportExpression': checkImportCall,
  79. });
  80. }
  81. if (options.commonjs || options.amd) {
  82. const currentCallExpression = visitors['CallExpression'];
  83. visitors['CallExpression'] = function (call) {
  84. if (currentCallExpression) currentCallExpression(call);
  85. if (options.commonjs) checkCommon(call);
  86. if (options.amd) checkAMD(call);
  87. };
  88. }
  89. return visitors;
  90. };
  91. /**
  92. * make an options schema for the module visitor, optionally
  93. * adding extra fields.
  94. */
  95. function makeOptionsSchema(additionalProperties) {
  96. const base = {
  97. 'type': 'object',
  98. 'properties': {
  99. 'commonjs': { 'type': 'boolean' },
  100. 'amd': { 'type': 'boolean' },
  101. 'esmodule': { 'type': 'boolean' },
  102. 'ignore': {
  103. 'type': 'array',
  104. 'minItems': 1,
  105. 'items': { 'type': 'string' },
  106. 'uniqueItems': true,
  107. },
  108. },
  109. 'additionalProperties': false,
  110. };
  111. if (additionalProperties) {
  112. for (const key in additionalProperties) {
  113. base.properties[key] = additionalProperties[key];
  114. }
  115. }
  116. return base;
  117. }
  118. exports.makeOptionsSchema = makeOptionsSchema;
  119. /**
  120. * json schema object for options parameter. can be used to build
  121. * rule options schema object.
  122. * @type {Object}
  123. */
  124. exports.optionsSchema = makeOptionsSchema();