UNPKG

5.46 kBJavaScriptView Raw
1'use strict';
2
3exports.__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 */
15exports.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 */
132function 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}
159exports.makeOptionsSchema = makeOptionsSchema;
160
161/**
162 * json schema object for options parameter. can be used to build rule options schema object.
163 */
164exports.optionsSchema = makeOptionsSchema();