blob: bc8c91b0af2a49fb451c3abbc9edf5a83adf796f [file] [log] [blame]
Tim van der Lippefdbd42e2020-04-07 15:14:36 +01001'use strict'
2exports.__esModule = true
3
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
15 options = Object.assign({ esmodule: true }, options)
16
17 let ignoreRegExps = []
18 if (options.ignore != null) {
19 ignoreRegExps = options.ignore.map(p => new RegExp(p))
20 }
21
22 function checkSourceValue(source, importer) {
23 if (source == null) return //?
24
25 // handle ignore
26 if (ignoreRegExps.some(re => re.test(source.value))) return
27
28 // fire visitor
29 visitor(source, importer)
30 }
31
32 // for import-y declarations
33 function checkSource(node) {
34 checkSourceValue(node.source, node)
35 }
36
37 // for esmodule dynamic `import()` calls
38 function checkImportCall(node) {
39 if (node.callee.type !== 'Import') return
40 if (node.arguments.length !== 1) return
41
42 const modulePath = node.arguments[0]
43 if (modulePath.type !== 'Literal') return
44 if (typeof modulePath.value !== 'string') return
45
46 checkSourceValue(modulePath, node)
47 }
48
49 // for CommonJS `require` calls
50 // adapted from @mctep: http://git.io/v4rAu
51 function checkCommon(call) {
52 if (call.callee.type !== 'Identifier') return
53 if (call.callee.name !== 'require') return
54 if (call.arguments.length !== 1) return
55
56 const modulePath = call.arguments[0]
57 if (modulePath.type !== 'Literal') return
58 if (typeof modulePath.value !== 'string') return
59
60 checkSourceValue(modulePath, call)
61 }
62
63 function checkAMD(call) {
64 if (call.callee.type !== 'Identifier') return
65 if (call.callee.name !== 'require' &&
66 call.callee.name !== 'define') return
67 if (call.arguments.length !== 2) return
68
69 const modules = call.arguments[0]
70 if (modules.type !== 'ArrayExpression') return
71
72 for (let element of modules.elements) {
73 if (element.type !== 'Literal') continue
74 if (typeof element.value !== 'string') continue
75
76 if (element.value === 'require' ||
77 element.value === 'exports') continue // magic modules: http://git.io/vByan
78
79 checkSourceValue(element, element)
80 }
81 }
82
83 const visitors = {}
84 if (options.esmodule) {
85 Object.assign(visitors, {
86 'ImportDeclaration': checkSource,
87 'ExportNamedDeclaration': checkSource,
88 'ExportAllDeclaration': checkSource,
89 'CallExpression': checkImportCall,
90 })
91 }
92
93 if (options.commonjs || options.amd) {
94 const currentCallExpression = visitors['CallExpression']
95 visitors['CallExpression'] = function (call) {
96 if (currentCallExpression) currentCallExpression(call)
97 if (options.commonjs) checkCommon(call)
98 if (options.amd) checkAMD(call)
99 }
100 }
101
102 return visitors
103}
104
105/**
106 * make an options schema for the module visitor, optionally
107 * adding extra fields.
108 */
109function makeOptionsSchema(additionalProperties) {
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
126 if (additionalProperties){
127 for (let key in additionalProperties) {
128 base.properties[key] = additionalProperties[key]
129 }
130 }
131
132 return base
133}
134exports.makeOptionsSchema = makeOptionsSchema
135
136/**
137 * json schema object for options parameter. can be used to build
138 * rule options schema object.
139 * @type {Object}
140 */
141exports.optionsSchema = makeOptionsSchema()