blob: ade475e2a917e086d86aade58c79372f0242da87 [file] [log] [blame]
Tim van der Lippe2c891972021-07-29 16:22:50 +01001'use strict';
2exports.__esModule = true;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +01003
4/**
5 * Returns an object of node visitors that will call
6 * 'visitor' with every discovered module path.
7 *
8 * todo: correct function prototype for visitor
9 * @param {Function(String)} visitor [description]
10 * @param {[type]} options [description]
11 * @return {object}
12 */
13exports.default = function visitModules(visitor, options) {
14 // if esmodule is not explicitly disabled, it is assumed to be enabled
Tim van der Lippe2c891972021-07-29 16:22:50 +010015 options = Object.assign({ esmodule: true }, options);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010016
Tim van der Lippe2c891972021-07-29 16:22:50 +010017 let ignoreRegExps = [];
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010018 if (options.ignore != null) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010019 ignoreRegExps = options.ignore.map(p => new RegExp(p));
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010020 }
21
22 function checkSourceValue(source, importer) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010023 if (source == null) return; //?
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010024
25 // handle ignore
Tim van der Lippe2c891972021-07-29 16:22:50 +010026 if (ignoreRegExps.some(re => re.test(source.value))) return;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010027
28 // fire visitor
Tim van der Lippe2c891972021-07-29 16:22:50 +010029 visitor(source, importer);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010030 }
31
32 // for import-y declarations
33 function checkSource(node) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010034 checkSourceValue(node.source, node);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010035 }
36
37 // for esmodule dynamic `import()` calls
38 function checkImportCall(node) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010039 let modulePath;
Tim van der Lippebc3a0b72021-11-08 15:22:37 +000040 // refs https://github.com/estree/estree/blob/HEAD/es2020.md#importexpression
Tim van der Lippe2c891972021-07-29 16:22:50 +010041 if (node.type === 'ImportExpression') {
42 modulePath = node.source;
43 } else if (node.type === 'CallExpression') {
44 if (node.callee.type !== 'Import') return;
45 if (node.arguments.length !== 1) return;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010046
Tim van der Lippe2c891972021-07-29 16:22:50 +010047 modulePath = node.arguments[0];
48 }
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010049
Tim van der Lippe2c891972021-07-29 16:22:50 +010050 if (modulePath.type !== 'Literal') return;
51 if (typeof modulePath.value !== 'string') return;
52
53 checkSourceValue(modulePath, node);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010054 }
55
56 // for CommonJS `require` calls
Tim van der Lippe0ceb4652022-01-06 14:23:36 +010057 // adapted from @mctep: https://git.io/v4rAu
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010058 function checkCommon(call) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010059 if (call.callee.type !== 'Identifier') return;
60 if (call.callee.name !== 'require') return;
61 if (call.arguments.length !== 1) return;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010062
Tim van der Lippe2c891972021-07-29 16:22:50 +010063 const modulePath = call.arguments[0];
64 if (modulePath.type !== 'Literal') return;
65 if (typeof modulePath.value !== 'string') return;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010066
Tim van der Lippe2c891972021-07-29 16:22:50 +010067 checkSourceValue(modulePath, call);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010068 }
69
70 function checkAMD(call) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010071 if (call.callee.type !== 'Identifier') return;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010072 if (call.callee.name !== 'require' &&
Tim van der Lippe2c891972021-07-29 16:22:50 +010073 call.callee.name !== 'define') return;
74 if (call.arguments.length !== 2) return;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010075
Tim van der Lippe2c891972021-07-29 16:22:50 +010076 const modules = call.arguments[0];
77 if (modules.type !== 'ArrayExpression') return;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010078
Tim van der Lippe2c891972021-07-29 16:22:50 +010079 for (const element of modules.elements) {
80 if (element.type !== 'Literal') continue;
81 if (typeof element.value !== 'string') continue;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010082
83 if (element.value === 'require' ||
Tim van der Lippe0ceb4652022-01-06 14:23:36 +010084 element.value === 'exports') continue; // magic modules: https://git.io/vByan
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010085
Tim van der Lippe2c891972021-07-29 16:22:50 +010086 checkSourceValue(element, element);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010087 }
88 }
89
Tim van der Lippe2c891972021-07-29 16:22:50 +010090 const visitors = {};
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010091 if (options.esmodule) {
92 Object.assign(visitors, {
93 'ImportDeclaration': checkSource,
94 'ExportNamedDeclaration': checkSource,
95 'ExportAllDeclaration': checkSource,
96 'CallExpression': checkImportCall,
Tim van der Lippe2c891972021-07-29 16:22:50 +010097 'ImportExpression': checkImportCall,
98 });
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010099 }
100
101 if (options.commonjs || options.amd) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100102 const currentCallExpression = visitors['CallExpression'];
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100103 visitors['CallExpression'] = function (call) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100104 if (currentCallExpression) currentCallExpression(call);
105 if (options.commonjs) checkCommon(call);
106 if (options.amd) checkAMD(call);
107 };
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100108 }
109
Tim van der Lippe2c891972021-07-29 16:22:50 +0100110 return visitors;
111};
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100112
113/**
114 * make an options schema for the module visitor, optionally
115 * adding extra fields.
116 */
117function makeOptionsSchema(additionalProperties) {
118 const base = {
119 'type': 'object',
120 'properties': {
121 'commonjs': { 'type': 'boolean' },
122 'amd': { 'type': 'boolean' },
123 'esmodule': { 'type': 'boolean' },
124 'ignore': {
125 'type': 'array',
126 'minItems': 1,
127 'items': { 'type': 'string' },
128 'uniqueItems': true,
129 },
130 },
131 'additionalProperties': false,
Tim van der Lippe2c891972021-07-29 16:22:50 +0100132 };
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100133
Tim van der Lippebc3a0b72021-11-08 15:22:37 +0000134 if (additionalProperties) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100135 for (const key in additionalProperties) {
136 base.properties[key] = additionalProperties[key];
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100137 }
138 }
139
Tim van der Lippe2c891972021-07-29 16:22:50 +0100140 return base;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100141}
Tim van der Lippe2c891972021-07-29 16:22:50 +0100142exports.makeOptionsSchema = makeOptionsSchema;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100143
144/**
145 * json schema object for options parameter. can be used to build
146 * rule options schema object.
147 * @type {Object}
148 */
Tim van der Lippe2c891972021-07-29 16:22:50 +0100149exports.optionsSchema = makeOptionsSchema();