-
Notifications
You must be signed in to change notification settings - Fork 36
/
index.js
203 lines (167 loc) · 5.28 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
'use strict';
const fs = require('fs');
const path = require('path');
const process = require('process');
const { debuglog } = require('util');
const getModuleType = require('module-definition');
const Walker = require('node-source-walk');
const detectiveAmd = require('detective-amd');
const detectiveCjs = require('detective-cjs');
const detectiveEs6 = require('detective-es6');
const detectiveLess = require('@dependents/detective-less');
const detectivePostcss = require('detective-postcss');
const detectiveSass = require('detective-sass');
const detectiveScss = require('detective-scss');
const detectiveStylus = require('detective-stylus');
const detectiveTypeScript = require('detective-typescript');
const detectiveVue = require('detective-vue2');
const debug = debuglog('precinct');
// eslint-disable-next-line n/no-deprecated-api
const natives = process.binding('natives');
/**
* Finds the list of dependencies for the given file
*
* @param {String|Object} content - File's content or AST
* @param {Object} [options]
* @param {String} [options.type] - The type of content being passed in. Useful if you want to use a non-js detective
* @return {String[]}
*/
function precinct(content, options = {}) {
debug('options given: %o', options);
let ast;
// We assume we're dealing with a JS file
if (!options.type && typeof content !== 'object') {
debug('we assume this is JS');
const walker = new Walker();
try {
// Parse once and distribute the AST to all detectives
ast = walker.parse(content);
debug('parsed the file content into an ast');
precinct.ast = ast;
} catch (error) {
// In case a previous call had it populated
precinct.ast = null;
debug('could not parse content: %s', error.message);
return [];
}
// SASS files shouldn't be parsed by Acorn
} else {
ast = content;
if (typeof content === 'object') {
precinct.ast = content;
}
}
const type = options.type ?? getModuleType.fromSource(ast);
debug('module type: %s', type);
const detective = getDetective(type, options);
let dependencies = [];
if (detective) {
dependencies = detective(ast, options[type]);
} else {
debug('no detective found for: %s', type);
}
// For non-JS files that we don't parse
if (detective?.ast) {
precinct.ast = detective.ast;
}
return dependencies;
}
/**
* Returns the dependencies for the given file path
*
* @param {String} filename
* @param {Object} [options]
* @param {Boolean} [options.includeCore=true] - Whether or not to include core modules in the dependency list
* @param {Object} [options.fileSystem=undefined] - An alternative fs implementation to use for reading the file path.
* @return {String[]}
*/
precinct.paperwork = (filename, options = {}) => {
options = { includeCore: true, ...options };
const fileSystem = options.fileSystem || fs;
const content = fileSystem.readFileSync(filename, 'utf8');
const ext = path.extname(filename);
let type;
if (ext === '.styl') {
debug('paperwork: converting .styl into the stylus type');
type = 'stylus';
} else if (ext === '.cjs') {
debug('paperwork: converting .cjs into the commonjs type');
type = 'commonjs';
// We need to sniff the JS module to find its type, not by extension.
// Other possible types pass through normally
} else if (!['.js', '.jsx'].includes(ext)) {
debug('paperwork: stripping the dot from the extension to serve as the type');
type = ext.replace('.', '');
}
if (type) {
debug('paperwork: setting the module type');
options.type = type;
}
debug('paperwork: invoking precinct');
const dependencies = precinct(content, options);
if (!options.includeCore) {
return dependencies.filter(dependency => {
if (dependency.startsWith('node:')) return false;
// In Node.js 18, node:test is a builtin but shows up under natives["test"],
// but can only be imported by "node:test." We're correcting this so "test"
// isn't unnecessarily stripped from the imports
if (dependency === 'test') {
debug('paperwork: allowing test import to avoid builtin/natives consideration');
return true;
}
return !natives[dependency];
});
}
debug('paperwork: got these results\n', dependencies);
return dependencies;
};
function getDetective(type, options) {
const mixedMode = options.es6?.mixedImports;
switch (type) {
case 'cjs':
case 'commonjs': {
return mixedMode ? detectiveEs6Cjs : detectiveCjs;
}
case 'css': {
return detectivePostcss;
}
case 'amd': {
return detectiveAmd;
}
case 'mjs':
case 'esm':
case 'es6': {
return mixedMode ? detectiveEs6Cjs : detectiveEs6;
}
case 'sass': {
return detectiveSass;
}
case 'less': {
return detectiveLess;
}
case 'scss': {
return detectiveScss;
}
case 'stylus': {
return detectiveStylus;
}
case 'ts': {
return detectiveTypeScript;
}
case 'tsx': {
return detectiveTypeScript.tsx;
}
case 'vue': {
return detectiveVue;
}
default:
// nothing
}
}
function detectiveEs6Cjs(ast, detectiveOptions) {
return [
...detectiveEs6(ast, detectiveOptions),
...detectiveCjs(ast, detectiveOptions)
];
}
module.exports = precinct;