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