moduleVisitor.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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' && call.callee.name !== 'define') { return; }
  59. if (call.arguments.length !== 2) { return; }
  60. const modules = call.arguments[0];
  61. if (modules.type !== 'ArrayExpression') { return; }
  62. for (const element of modules.elements) {
  63. if (element.type !== 'Literal') { continue; }
  64. if (typeof element.value !== 'string') { continue; }
  65. if (
  66. element.value === 'require'
  67. || element.value === 'exports'
  68. ) {
  69. continue; // magic modules: https://github.com/requirejs/requirejs/wiki/Differences-between-the-simplified-CommonJS-wrapper-and-standard-AMD-define#magic-modules
  70. }
  71. checkSourceValue(element, element);
  72. }
  73. }
  74. const visitors = {};
  75. if (options.esmodule) {
  76. Object.assign(visitors, {
  77. ImportDeclaration: checkSource,
  78. ExportNamedDeclaration: checkSource,
  79. ExportAllDeclaration: checkSource,
  80. CallExpression: checkImportCall,
  81. ImportExpression: checkImportCall,
  82. });
  83. }
  84. if (options.commonjs || options.amd) {
  85. const currentCallExpression = visitors.CallExpression;
  86. visitors.CallExpression = function (call) {
  87. if (currentCallExpression) { currentCallExpression(call); }
  88. if (options.commonjs) { checkCommon(call); }
  89. if (options.amd) { checkAMD(call); }
  90. };
  91. }
  92. return visitors;
  93. };
  94. /**
  95. * make an options schema for the module visitor, optionally
  96. * adding extra fields.
  97. */
  98. function makeOptionsSchema(additionalProperties) {
  99. const base = {
  100. type: 'object',
  101. properties: {
  102. commonjs: { type: 'boolean' },
  103. amd: { type: 'boolean' },
  104. esmodule: { type: 'boolean' },
  105. ignore: {
  106. type: 'array',
  107. minItems: 1,
  108. items: { type: 'string' },
  109. uniqueItems: true,
  110. },
  111. },
  112. additionalProperties: false,
  113. };
  114. if (additionalProperties) {
  115. for (const key in additionalProperties) {
  116. base.properties[key] = additionalProperties[key];
  117. }
  118. }
  119. return base;
  120. }
  121. exports.makeOptionsSchema = makeOptionsSchema;
  122. /**
  123. * json schema object for options parameter. can be used to build
  124. * rule options schema object.
  125. * @type {Object}
  126. */
  127. exports.optionsSchema = makeOptionsSchema();