154 lines
4.2 KiB
JavaScript
154 lines
4.2 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
exports.__esModule = true;
|
||
|
|
||
|
/**
|
||
|
* Returns an object of node visitors that will call
|
||
|
* 'visitor' with every discovered module path.
|
||
|
*
|
||
|
* todo: correct function prototype for visitor
|
||
|
* @param {Function(String)} visitor [description]
|
||
|
* @param {[type]} options [description]
|
||
|
* @return {object}
|
||
|
*/
|
||
|
exports.default = function visitModules(visitor, options) {
|
||
|
// if esmodule is not explicitly disabled, it is assumed to be enabled
|
||
|
options = Object.assign({ esmodule: true }, options);
|
||
|
|
||
|
let ignoreRegExps = [];
|
||
|
if (options.ignore != null) {
|
||
|
ignoreRegExps = options.ignore.map((p) => new RegExp(p));
|
||
|
}
|
||
|
|
||
|
function checkSourceValue(source, importer) {
|
||
|
if (source == null) { return; } //?
|
||
|
|
||
|
// handle ignore
|
||
|
if (ignoreRegExps.some((re) => re.test(source.value))) { return; }
|
||
|
|
||
|
// fire visitor
|
||
|
visitor(source, importer);
|
||
|
}
|
||
|
|
||
|
// for import-y declarations
|
||
|
function checkSource(node) {
|
||
|
checkSourceValue(node.source, node);
|
||
|
}
|
||
|
|
||
|
// for esmodule dynamic `import()` calls
|
||
|
function checkImportCall(node) {
|
||
|
let modulePath;
|
||
|
// refs https://github.com/estree/estree/blob/HEAD/es2020.md#importexpression
|
||
|
if (node.type === 'ImportExpression') {
|
||
|
modulePath = node.source;
|
||
|
} else if (node.type === 'CallExpression') {
|
||
|
if (node.callee.type !== 'Import') { return; }
|
||
|
if (node.arguments.length !== 1) { return; }
|
||
|
|
||
|
modulePath = node.arguments[0];
|
||
|
}
|
||
|
|
||
|
if (modulePath.type !== 'Literal') { return; }
|
||
|
if (typeof modulePath.value !== 'string') { return; }
|
||
|
|
||
|
checkSourceValue(modulePath, node);
|
||
|
}
|
||
|
|
||
|
// for CommonJS `require` calls
|
||
|
// adapted from @mctep: https://git.io/v4rAu
|
||
|
function checkCommon(call) {
|
||
|
if (call.callee.type !== 'Identifier') { return; }
|
||
|
if (call.callee.name !== 'require') { return; }
|
||
|
if (call.arguments.length !== 1) { return; }
|
||
|
|
||
|
const modulePath = call.arguments[0];
|
||
|
if (modulePath.type !== 'Literal') { return; }
|
||
|
if (typeof modulePath.value !== 'string') { return; }
|
||
|
|
||
|
checkSourceValue(modulePath, call);
|
||
|
}
|
||
|
|
||
|
function checkAMD(call) {
|
||
|
if (call.callee.type !== 'Identifier') { return; }
|
||
|
if (call.callee.name !== 'require' && call.callee.name !== 'define') { return; }
|
||
|
if (call.arguments.length !== 2) { return; }
|
||
|
|
||
|
const modules = call.arguments[0];
|
||
|
if (modules.type !== 'ArrayExpression') { return; }
|
||
|
|
||
|
for (const element of modules.elements) {
|
||
|
if (element.type !== 'Literal') { continue; }
|
||
|
if (typeof element.value !== 'string') { continue; }
|
||
|
|
||
|
if (
|
||
|
element.value === 'require'
|
||
|
|| element.value === 'exports'
|
||
|
) {
|
||
|
continue; // magic modules: https://github.com/requirejs/requirejs/wiki/Differences-between-the-simplified-CommonJS-wrapper-and-standard-AMD-define#magic-modules
|
||
|
}
|
||
|
|
||
|
checkSourceValue(element, element);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const visitors = {};
|
||
|
if (options.esmodule) {
|
||
|
Object.assign(visitors, {
|
||
|
ImportDeclaration: checkSource,
|
||
|
ExportNamedDeclaration: checkSource,
|
||
|
ExportAllDeclaration: checkSource,
|
||
|
CallExpression: checkImportCall,
|
||
|
ImportExpression: checkImportCall,
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (options.commonjs || options.amd) {
|
||
|
const currentCallExpression = visitors.CallExpression;
|
||
|
visitors.CallExpression = function (call) {
|
||
|
if (currentCallExpression) { currentCallExpression(call); }
|
||
|
if (options.commonjs) { checkCommon(call); }
|
||
|
if (options.amd) { checkAMD(call); }
|
||
|
};
|
||
|
}
|
||
|
|
||
|
return visitors;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* make an options schema for the module visitor, optionally
|
||
|
* adding extra fields.
|
||
|
*/
|
||
|
function makeOptionsSchema(additionalProperties) {
|
||
|
const base = {
|
||
|
type: 'object',
|
||
|
properties: {
|
||
|
commonjs: { type: 'boolean' },
|
||
|
amd: { type: 'boolean' },
|
||
|
esmodule: { type: 'boolean' },
|
||
|
ignore: {
|
||
|
type: 'array',
|
||
|
minItems: 1,
|
||
|
items: { type: 'string' },
|
||
|
uniqueItems: true,
|
||
|
},
|
||
|
},
|
||
|
additionalProperties: false,
|
||
|
};
|
||
|
|
||
|
if (additionalProperties) {
|
||
|
for (const key in additionalProperties) {
|
||
|
base.properties[key] = additionalProperties[key];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return base;
|
||
|
}
|
||
|
exports.makeOptionsSchema = makeOptionsSchema;
|
||
|
|
||
|
/**
|
||
|
* json schema object for options parameter. can be used to build
|
||
|
* rule options schema object.
|
||
|
* @type {Object}
|
||
|
*/
|
||
|
exports.optionsSchema = makeOptionsSchema();
|