Skip to content

Commit d3b1543

Browse files
authored
feat(detect-non-literal-fs-filename): change to track non-top-level require() as well (#105)
* feat(detect-non-literal-fs-filename): change to track non-top-level require as well * move files * using linter instance for test * revert
1 parent 7d482c5 commit d3b1543

8 files changed

+436
-295
lines changed

.eslintrc

+10-1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,14 @@
1818
],
1919
"eslint-plugin/require-meta-schema": "off", // TODO: enable
2020
"eslint-plugin/require-meta-type": "off" // TODO: enable
21-
}
21+
},
22+
"overrides": [
23+
{
24+
"files": ["test/**/*.js"],
25+
"globals": {
26+
"describe": "readonly",
27+
"it": "readonly"
28+
}
29+
}
30+
]
2231
}

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"lint:js": "eslint .",
1414
"lint:js:fix": "npm run lint:js -- --fix",
1515
"release": "npx semantic-release",
16-
"test": "mocha test/**/*",
16+
"test": "mocha test/**",
1717
"update:eslint-docs": "eslint-doc-generator"
1818
},
1919
"repository": {

rules/detect-non-literal-fs-filename.js

+44-252
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
'use strict';
77

8-
const fsMetaData = require('./data/fsFunctionData.json');
8+
const fsMetaData = require('../utils/data/fsFunctionData.json');
99
const funcNames = Object.keys(fsMetaData);
1010
const fsPackageNames = ['fs', 'node:fs', 'fs/promises', 'node:fs/promises', 'fs-extra'];
1111

12-
const { getImportDeclaration, getVariableDeclaration } = require('./utils/import-utils');
12+
const { getImportAccessPath } = require('../utils/import-utils');
1313

1414
//------------------------------------------------------------------------------
1515
// Utils
@@ -21,189 +21,9 @@ function getIndices(node, argMeta) {
2121

2222
function generateReport({ context, node, packageName, methodName, indices }) {
2323
if (!indices || indices.length === 0) {
24-
return null;
24+
return;
2525
}
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-
});
26+
context.report({ node, message: `Found ${methodName} from package "${packageName}" with non literal argument at index ${indices.join(',')}` });
20727
}
20828

20929
//------------------------------------------------------------------------------
@@ -223,87 +43,59 @@ module.exports = {
22343
create: function (context) {
22444
return {
22545
CallExpression: function (node) {
226-
// readFile/open/... (but might be renamed)
227-
const localMethodName = node.callee.name;
228-
22946
// 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')) {
47+
if ((node.callee.type === 'Identifier' && node.callee.name === 'require') || node.arguments.every((argument) => argument.type === 'Literal')) {
23148
return;
23249
}
23350

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,
51+
const pathInfo = getImportAccessPath({
52+
node: node.callee,
53+
scope: context.getScope(),
54+
packageNames: fsPackageNames,
25255
});
253-
if (destructuredRequireReport) {
254-
return destructuredRequireReport;
56+
if (!pathInfo) {
57+
return;
25558
}
256-
257-
const importReport = detectOnDestructuredImport({
258-
context,
259-
methodName: localMethodName,
260-
node,
261-
program,
262-
});
263-
if (importReport) {
264-
return importReport;
59+
let fnName;
60+
if (pathInfo.path.length === 1) {
61+
// Check for:
62+
// | var something = require('fs').readFile;
63+
// | something(a);
64+
// ,
65+
// | var something = require('fs');
66+
// | something.readFile(c);
67+
// ,
68+
// | var { readFile: something } = require('fs')
69+
// | readFile(filename);
70+
// ,
71+
// | import { readFile as something } from 'fs';
72+
// | something(filename);
73+
// , or
74+
// | import * as something from 'fs';
75+
// | something.readFile(c);
76+
fnName = pathInfo.path[0];
77+
} else if (pathInfo.path.length === 2) {
78+
// Check for:
79+
// | var something = require('fs').promises;
80+
// | something.readFile(filename)
81+
fnName = pathInfo.path[1];
82+
} else {
83+
return;
26584
}
266-
},
267-
MemberExpression: function (node) {
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;
85+
if (!funcNames.includes(fnName)) {
86+
return false;
28287
}
88+
const packageName = pathInfo.packageName;
28389

284-
const defaultImportReport = detectOnDefaultImport({
285-
program,
286-
objectName: localObjectName,
287-
methodName: realMethodName,
288-
context,
289-
node,
290-
});
291-
292-
if (defaultImportReport) {
293-
return defaultImportReport;
294-
}
90+
const indices = getIndices(node, fsMetaData[fnName]);
29591

296-
const promisePropertyReport = detectOnPromiseProperty({
92+
generateReport({
29793
context,
298-
methodName: realMethodName,
29994
node,
300-
objectName: localObjectName,
301-
program,
95+
packageName,
96+
methodName: fnName,
97+
indices,
30298
});
303-
304-
if (promisePropertyReport) {
305-
return promisePropertyReport;
306-
}
30799
},
308100
};
309101
},

0 commit comments

Comments
 (0)