5
5
6
6
'use strict' ;
7
7
8
+ const fsMetaData = require ( './data/fsFunctionData.json' ) ;
9
+ const funcNames = Object . keys ( fsMetaData ) ;
10
+ const fsPackageNames = [ 'fs' , 'node:fs' , 'fs/promises' , 'node:fs/promises' , 'fs-extra' ] ;
11
+
12
+ const { getImportDeclaration, getVariableDeclaration } = require ( './utils/import-utils' ) ;
13
+
8
14
//------------------------------------------------------------------------------
9
- // Rule Definition
15
+ // Utils
10
16
//------------------------------------------------------------------------------
11
17
12
- const fsMetaData = require ( './data/fsFunctionData.json' ) ;
13
- const funcNames = Object . keys ( fsMetaData ) ;
18
+ function getIndices ( node , argMeta ) {
19
+ return ( argMeta || [ ] ) . filter ( ( argIndex ) => node . arguments [ argIndex ] . type !== 'Literal' ) ;
20
+ }
21
+
22
+ function generateReport ( { context, node, packageName, methodName, indices } ) {
23
+ if ( ! indices || indices . length === 0 ) {
24
+ return null ;
25
+ }
26
+ return context . report ( { node, message : `Found ${ methodName } from package "${ packageName } " with non literal argument at index ${ indices . join ( ',' ) } ` } ) ;
27
+ }
28
+
29
+ /**
30
+ * Detects:
31
+ * | var something = require('fs').readFile;
32
+ * | something(a);
33
+ */
34
+ function detectOnRequireWithProperty ( { context, methodName, node, program } ) {
35
+ const declaration = getVariableDeclaration ( {
36
+ condition : ( declaration ) => declaration . init . parent . id . name === methodName ,
37
+ hasObject : true ,
38
+ methodName,
39
+ packageNames : fsPackageNames ,
40
+ program,
41
+ } ) ;
42
+
43
+ if ( ! declaration ) {
44
+ return null ;
45
+ }
46
+
47
+ // we found the require for our method!
48
+ const fsFunction = declaration . init . property . name ;
49
+ const packageName = declaration . init . object . arguments [ 0 ] . value ;
50
+ const fnName = declaration . init . property . name ;
51
+
52
+ const indices = getIndices ( node , fsMetaData [ fsFunction ] ) ;
53
+
54
+ return generateReport ( {
55
+ context,
56
+ node,
57
+ packageName,
58
+ methodName : fnName ,
59
+ indices,
60
+ } ) ;
61
+ }
62
+
63
+ /**
64
+ * Detects:
65
+ * | var something = require('fs');
66
+ * | something.readFile(c);
67
+ */
68
+ function detectOnMethodCall ( { context, node, program, methodName } ) {
69
+ const declaration = getVariableDeclaration ( {
70
+ packageNames : fsPackageNames ,
71
+ hasObject : false ,
72
+ program,
73
+ } ) ;
74
+
75
+ if ( ! declaration ) {
76
+ return null ;
77
+ }
78
+
79
+ const indices = getIndices ( node . parent , fsMetaData [ methodName ] ) ;
80
+
81
+ return generateReport ( {
82
+ context,
83
+ node,
84
+ packageName : declaration . init . arguments [ 0 ] . value ,
85
+ methodName,
86
+ indices,
87
+ } ) ;
88
+ }
89
+
90
+ /**
91
+ * Detects:
92
+ * | var { readFile: something } = require('fs')
93
+ * | readFile(filename)
94
+ */
95
+ function detectOnDestructuredRequire ( { context, methodName, node, program } ) {
96
+ const declaration = getVariableDeclaration ( {
97
+ condition : ( declaration ) => declaration && declaration . id && declaration . id . properties && declaration . id . properties . some ( ( p ) => p . value . name === methodName ) ,
98
+ hasObject : false ,
99
+ methodName,
100
+ packageNames : fsPackageNames ,
101
+ program,
102
+ } ) ;
103
+
104
+ if ( ! declaration ) {
105
+ return null ;
106
+ }
107
+
108
+ const realMethodName = declaration . id . properties . find ( ( p ) => p . value . name === methodName ) . key . name ;
109
+
110
+ const meta = fsMetaData [ realMethodName ] ;
111
+ const indices = getIndices ( node , meta ) ;
112
+
113
+ return generateReport ( {
114
+ context,
115
+ node,
116
+ packageName : declaration . init . arguments [ 0 ] . value ,
117
+ methodName : realMethodName ,
118
+ indices,
119
+ } ) ;
120
+ }
121
+
122
+ /**
123
+ * Detects:
124
+ * | import { readFile as something } from 'fs';
125
+ * | something(filename);
126
+ */
127
+ function detectOnDestructuredImport ( { context, methodName, node, program } ) {
128
+ const importDeclaration = getImportDeclaration ( { methodName, packageNames : fsPackageNames , program } ) ;
129
+
130
+ const specifier = importDeclaration && importDeclaration . specifiers && importDeclaration . specifiers . find ( ( s ) => ! ! funcNames . includes ( s . imported . name ) ) ;
131
+
132
+ if ( ! specifier ) {
133
+ return null ;
134
+ }
135
+
136
+ const fnName = specifier . imported . name ;
137
+ const meta = fsMetaData [ fnName ] ;
138
+ const indices = getIndices ( node , meta ) ;
139
+
140
+ return generateReport ( {
141
+ context,
142
+ node,
143
+ packageName : specifier . parent . source . value ,
144
+ methodName : fnName ,
145
+ indices,
146
+ } ) ;
147
+ }
148
+
149
+ /**
150
+ * Detects:
151
+ * | import * as something from 'fs';
152
+ * | something.readFile(c);
153
+ */
154
+ function detectOnDefaultImport ( { context, methodName, node, objectName, program } ) {
155
+ if ( ! funcNames . includes ( methodName ) ) {
156
+ return null ;
157
+ }
158
+
159
+ const importDeclaration = getImportDeclaration ( { methodName : objectName , packageNames : fsPackageNames , program } ) ;
160
+
161
+ if ( ! importDeclaration ) {
162
+ return null ;
163
+ }
164
+
165
+ const meta = fsMetaData [ methodName ] ;
166
+ const indices = getIndices ( node . parent , meta ) ;
167
+
168
+ return generateReport ( {
169
+ context,
170
+ node,
171
+ packageName : importDeclaration . source . value ,
172
+ methodName,
173
+ indices,
174
+ } ) ;
175
+ }
176
+
177
+ /**
178
+ * Detects:
179
+ * | var something = require('fs').promises;
180
+ * | something.readFile(filename)
181
+ */
182
+ function detectOnPromiseProperty ( { context, methodName, node, objectName, program } ) {
183
+ const declaration = program . body
184
+ . filter ( ( entry ) => entry . type === 'VariableDeclaration' )
185
+ . flatMap ( ( entry ) => entry . declarations )
186
+ . find (
187
+ ( declaration ) =>
188
+ declaration . id . name === objectName &&
189
+ declaration . init . type === 'MemberExpression' &&
190
+ // package name is fs / fs-extra
191
+ fsPackageNames . includes ( declaration . init . object . arguments [ 0 ] . value )
192
+ ) ;
193
+
194
+ if ( ! declaration ) {
195
+ return null ;
196
+ }
197
+ const meta = fsMetaData [ methodName ] ;
198
+ const indices = getIndices ( node . parent , meta ) ;
199
+
200
+ return generateReport ( {
201
+ context,
202
+ node,
203
+ packageName : declaration . init . object . arguments [ 0 ] . value ,
204
+ methodName,
205
+ indices,
206
+ } ) ;
207
+ }
208
+
209
+ //------------------------------------------------------------------------------
210
+ // Rule Definition
211
+ //------------------------------------------------------------------------------
14
212
15
213
module . exports = {
16
214
meta : {
@@ -24,30 +222,88 @@ module.exports = {
24
222
} ,
25
223
create : function ( context ) {
26
224
return {
225
+ CallExpression : function ( node ) {
226
+ // readFile/open/... (but might be renamed)
227
+ const localMethodName = node . callee . name ;
228
+
229
+ // don't check require. If all arguments are Literals, it's surely safe!
230
+ if ( ! localMethodName || localMethodName === 'require' || node . arguments . every ( ( argument ) => argument . type === 'Literal' ) ) {
231
+ return ;
232
+ }
233
+
234
+ // this only works, when imports are on top level!
235
+ const program = context . getAncestors ( ) [ 0 ] ;
236
+
237
+ const requireReport = detectOnRequireWithProperty ( {
238
+ context,
239
+ methodName : localMethodName ,
240
+ node,
241
+ program,
242
+ } ) ;
243
+ if ( requireReport ) {
244
+ return requireReport ;
245
+ }
246
+
247
+ const destructuredRequireReport = detectOnDestructuredRequire ( {
248
+ context,
249
+ methodName : localMethodName ,
250
+ node,
251
+ program,
252
+ } ) ;
253
+ if ( destructuredRequireReport ) {
254
+ return destructuredRequireReport ;
255
+ }
256
+
257
+ const importReport = detectOnDestructuredImport ( {
258
+ context,
259
+ methodName : localMethodName ,
260
+ node,
261
+ program,
262
+ } ) ;
263
+ if ( importReport ) {
264
+ return importReport ;
265
+ }
266
+ } ,
27
267
MemberExpression : function ( node ) {
28
- const result = [ ] ;
29
- if ( funcNames . indexOf ( node . property . name ) !== - 1 ) {
30
- const meta = fsMetaData [ node . property . name ] ;
31
- const args = node . parent . arguments ;
32
- meta . forEach ( ( i ) => {
33
- if ( args && args . length > i ) {
34
- if ( args [ i ] . type !== 'Literal' ) {
35
- result . push ( i ) ;
36
- }
37
- }
38
- } ) ;
268
+ const realMethodName = node . property . name ; // readFile/open/... (not renamed)
269
+ const localObjectName = node . object . name ; // fs/node:fs/... (but might be renamed)
270
+
271
+ // this only works, when imports are on top level!
272
+ const program = context . getAncestors ( ) [ 0 ] ;
273
+
274
+ const methodCallReport = detectOnMethodCall ( {
275
+ context,
276
+ methodName : realMethodName ,
277
+ node,
278
+ program,
279
+ } ) ;
280
+ if ( methodCallReport ) {
281
+ return methodCallReport ;
39
282
}
40
283
41
- if ( result . length > 0 ) {
42
- return context . report ( { node : node , message : `Found fs.${ node . property . name } with non literal argument at index ${ result . join ( ',' ) } ` } ) ;
284
+ const defaultImportReport = detectOnDefaultImport ( {
285
+ program,
286
+ objectName : localObjectName ,
287
+ methodName : realMethodName ,
288
+ context,
289
+ node,
290
+ } ) ;
291
+
292
+ if ( defaultImportReport ) {
293
+ return defaultImportReport ;
43
294
}
44
295
45
- /*
46
- if (node.parent && node.parent.arguments && node.parent.arguments[index].value) {
47
- return context.report(node, 'found Buffer.' + node.property.name + ' with noAssert flag set true');
296
+ const promisePropertyReport = detectOnPromiseProperty ( {
297
+ context,
298
+ methodName : realMethodName ,
299
+ node,
300
+ objectName : localObjectName ,
301
+ program,
302
+ } ) ;
48
303
49
- }
50
- */
304
+ if ( promisePropertyReport ) {
305
+ return promisePropertyReport ;
306
+ }
51
307
} ,
52
308
} ;
53
309
} ,
0 commit comments