moduleVisitor.js 5.3 KB

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