Skip to content

Commit f09bb2c

Browse files
committed
feat: handling native node functions/imports
1 parent 67dee9e commit f09bb2c

17 files changed

+139
-52
lines changed

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@
4747
"scripts": {
4848
"build": "pkgroll",
4949
"test": "yarn build && vitest run --reporter verbose",
50-
"docs": "yarn build && eslint-doc-generator && eslint-doc-generator --init-rule-docs && eslint-doc-generator"
50+
"docs": "yarn build && eslint-doc-generator && eslint-doc-generator --init-rule-docs && eslint-doc-generator",
51+
"v:major": "npm version major -m \"chore: bump major to %s\"",
52+
"v:minor": "npm version minor -m \"chore: bump minor to %s\"",
53+
"v:patch": "npm version patch -m \"chore: bump patch to %s\""
5154
},
5255
"dependencies": {
5356
"@types/eslint": "^8.56.10",

src/rules/might-throw/might-throw.spec.ts

+15
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,18 @@ await testFile(
4040
[rule.name],
4141
[]
4242
);
43+
await testFile(
44+
"src/rules/might-throw/tests/native-modules-err.ts",
45+
[rule.name],
46+
[
47+
{
48+
messageId: "mightThrow",
49+
line: 3,
50+
},
51+
]
52+
);
53+
await testFile(
54+
"src/rules/might-throw/tests/native-modules-ok.ts",
55+
[rule.name],
56+
[]
57+
);

src/rules/might-throw/might-throw.ts

+14-26
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,8 @@
1-
import { ESLintUtils } from "@typescript-eslint/utils";
2-
import {
3-
findInParent,
4-
isFunctionDeclaration,
5-
isThrowStatement,
6-
isTryStatement,
7-
} from "@/src/utils";
8-
import { getFunctionId } from "../../utils/get-function-id";
9-
import { resolveFunc } from "../../utils/resolve-func";
10-
import { getCallExprId } from "../../utils/get-call-expr-id";
11-
import { findInChildren } from "../../utils/find-in-children";
12-
import { createRule } from "../create-rule";
1+
import { findInParent, isFunctionDeclaration } from "@/src/utils";
2+
import { getFunctionId } from "@/src/utils/get-function-id";
3+
import { getCallExprId } from "@/src/utils/get-call-expr-id";
4+
import { createRule } from "@/src/rules/create-rule";
5+
import { canFuncThrow } from "@/src/rules/no-unhandled";
136

147
const name = "might-throw";
158
const rule = createRule({
@@ -43,21 +36,16 @@ const rule = createRule({
4336
CallExpression(node) {
4437
const id = getCallExprId(node);
4538
if (!id) return;
46-
const fun = resolveFunc(id, context);
47-
if (!fun?.func) return;
48-
const throwNode = findInChildren(fun.func, isThrowStatement);
49-
if (throwNode) {
50-
const tryNode = findInParent(node, isTryStatement);
51-
if (tryNode) return;
39+
const throwing = canFuncThrow(id, context);
40+
if (!throwing) return;
5241

53-
context.report({
54-
node,
55-
messageId: "mightThrow",
56-
data: {
57-
name: id.name,
58-
},
59-
});
60-
}
42+
context.report({
43+
node,
44+
messageId: "mightThrow",
45+
data: {
46+
name: id.name,
47+
},
48+
});
6149
},
6250
};
6351
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { readFileSync } from "fs";
2+
3+
const content = readFileSync("this file does not exist", "utf-8");
4+
console.log({ content });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { readFileSync } from "fs";
2+
3+
try {
4+
const content = readFileSync("this file does not exist", "utf-8");
5+
console.log({ content });
6+
} catch (e) {
7+
console.error(e);
8+
}

src/rules/no-unhandled/no-unhandled.spec.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ await testFile(
1010
},
1111
]
1212
);
13-
1413
await testFile("src/rules/no-unhandled/tests/import-ok.ts", [rule.name], []);
1514
await testFile("src/rules/no-unhandled/tests/import-ok-2.ts", [rule.name], []);
1615
await testFile("src/rules/no-unhandled/tests/basic-ok.ts", [rule.name], []);
@@ -76,3 +75,18 @@ await testFile(
7675
},
7776
]
7877
);
78+
await testFile(
79+
"src/rules/no-unhandled/tests/native-modules-err.ts",
80+
[rule.name],
81+
[
82+
{
83+
messageId: "noUnhandled",
84+
line: 3,
85+
},
86+
]
87+
);
88+
await testFile(
89+
"src/rules/no-unhandled/tests/native-modules-ok.ts",
90+
[rule.name],
91+
[]
92+
);

src/rules/no-unhandled/no-unhandled.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from "@/src/utils";
1717
import { RuleContext } from "@typescript-eslint/utils/ts-eslint";
1818
import { createRule } from "@/src/rules/create-rule";
19+
import { nativeThrowing } from "@/src/utils/native-throwing";
1920

2021
const throwFunctions = new Set<string>();
2122
const scannedFunctions = new Set<string>();
@@ -43,7 +44,7 @@ const rule = createRule({
4344
CallExpression(called) {
4445
const id = getCallExprId(called);
4546
if (!id) return;
46-
const throws = checkfunc(id, context);
47+
const throws = canFuncThrow(id, context);
4748

4849
if (throws) {
4950
const parentFunction = findInParent(called, isFunctionDeclaration);
@@ -64,7 +65,7 @@ const rule = createRule({
6465
},
6566
});
6667

67-
function checkfunc(
68+
export function canFuncThrow(
6869
node: TSESTree.Identifier | TSESTree.PrivateIdentifier,
6970
context: RuleContext<string, unknown[]>
7071
): boolean {
@@ -74,6 +75,14 @@ function checkfunc(
7475
}
7576

7677
const res = resolveFunc(node, context);
78+
if (res?.module) {
79+
const found = nativeThrowing.some(
80+
(x) => x.module === res.module && x.method === res.func.id?.name
81+
);
82+
if (found) {
83+
return true;
84+
}
85+
}
7786
if (!res?.func) return false;
7887
return scanfunc(res.func, res.context);
7988
}
@@ -105,7 +114,7 @@ function scanfunc(
105114
return;
106115
}
107116

108-
throws = checkfunc(child.callee, context);
117+
throws = canFuncThrow(child.callee, context);
109118
if (throws) {
110119
if (node.id) throwFunctions.add(getFunctionId(context, node.id));
111120
resolve(true);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { readFileSync } from "fs";
2+
3+
const content = readFileSync("this file does not exist", "utf-8");
4+
console.log({ content });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { readFileSync } from "fs";
2+
3+
try {
4+
const content = readFileSync("this file does not exist", "utf-8");
5+
console.log({ content });
6+
} catch (e) {
7+
console.error(e);
8+
}

src/utils/get-function-id.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { TSESTree } from "@typescript-eslint/types";
22
import { RuleContext } from "@typescript-eslint/utils/ts-eslint";
33
import { isImportDeclaration, isImportSpecifier } from "./ast-guards";
4-
import { getImportDeclarationPath } from "./get-import-declaration-path";
4+
import { getImportDeclaration } from "./get-import-declaration";
55

66
export function getFunctionId(
77
context: RuleContext<string, unknown[]>,
88
node: TSESTree.Identifier | TSESTree.ImportDeclaration
99
) {
1010
if (isImportDeclaration(node)) {
1111
const name = node.specifiers.find(isImportSpecifier)?.local.name;
12-
const fileName = getImportDeclarationPath(context, node);
13-
return `${fileName}#${name}`;
12+
const fileName = getImportDeclaration(context, node);
13+
return `${fileName.path}#${name}`;
1414
}
1515
return `${context.physicalFilename}#${node.name}`;
1616
}

src/utils/get-import-declaration-path.ts src/utils/get-import-declaration.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ function cleanTsConfig(content: string) {
1919

2020
function resolveTSAlias(tsconfigpath: string, to: string, cwd: string) {
2121
const tsconfig = readFileSync(tsconfigpath, "utf-8");
22-
const aliases = (JSON.parse(cleanTsConfig(tsconfig)).compilerOptions
23-
.paths ?? {}) as Record<string, string[]>;
22+
const aliases = (JSON.parse(cleanTsConfig(tsconfig)).compilerOptions.paths ??
23+
{}) as Record<string, string[]>;
2424

2525
let res = Object.entries(aliases)
2626
// sorting by longest - most qualified - alias
@@ -46,7 +46,7 @@ function endsWithAny(str: string, arr: string[]) {
4646
return arr.some((ext) => str.endsWith(ext));
4747
}
4848

49-
export function getImportDeclarationPath(
49+
export function getImportDeclaration(
5050
context: RuleContext<string, unknown[]>,
5151
impt: TSESTree.ImportDeclaration
5252
) {
@@ -62,9 +62,13 @@ export function getImportDeclarationPath(
6262
}
6363

6464
if (!to.startsWith(".")) {
65+
const split = to.split(":");
66+
if (!existsSync(to)) {
67+
return { module: split.at(-1), protocol: split.at(-2) };
68+
}
6569
// no relative path and no TS alias,
6670
// considering it as a node_module
67-
return `./node_modules/${to}`;
71+
return { path: `./node_modules/${to}`, module: to };
6872
}
6973
} else {
7074
res = path.resolve(path.dirname(from), to);
@@ -82,5 +86,5 @@ export function getImportDeclarationPath(
8286
}
8387
}
8488

85-
return res;
89+
return { path: res, module: to };
8690
}

src/utils/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export * from "./find-in-children";
66
export * from "./find-in-parent";
77
export * from "./get-call-expr-id";
88
export * from "./get-function-id";
9-
export * from "./get-import-declaration-path";
9+
export * from "./get-import-declaration";
1010
export * from "./infer-guard-type";
1111
export * from "./parse";
1212
export * from "./resolve-func";

src/utils/native-throwing.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const nativeThrowing = [
2+
{
3+
protocol: "node",
4+
module: "fs",
5+
method: "readFileSync",
6+
},
7+
];

src/utils/resolve-class.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export function resolveClass(
99
context: RuleContext<string, unknown[]>
1010
) {
1111
const resolved = resolveId(id, context);
12-
if (!resolved) return;
12+
if (!resolved?.id) return;
1313
const class_ = findInParent(resolved.id, isClassDeclaration);
1414
if (!class_) return;
1515
return { class: class_, context: resolved.context };

src/utils/resolve-func.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ export function resolveFunc(
99
context: RuleContext<string, unknown[]>
1010
) {
1111
const resolved = resolveId(id, context);
12-
if (!resolved) return;
12+
if (!resolved?.id) return;
1313
const func = findInParent(resolved.id, isFunctionDeclaration);
1414
if (!func) return;
15-
return { func, context: resolved.context };
15+
return {
16+
func,
17+
module: resolved.module,
18+
protocol: resolved.protocol,
19+
context: resolved.context,
20+
};
1621
}

src/utils/resolve-id.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ export function resolveId(
1616

1717
return idInParsed;
1818
}
19-
return { id: identifier, context };
19+
return { id: identifier, module: null, protocol: null, context };
2020
}

src/utils/resolve-imported-id.ts

+26-8
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,35 @@
11
import { TSESTree } from "@typescript-eslint/utils";
2-
import { isExportNamedDeclaration } from "@/src/utils/ast-guards";
2+
import { isExportNamedDeclaration, isIdentifier } from "@/src/utils/ast-guards";
33
import { RuleContext, SourceCode } from "@typescript-eslint/utils/ts-eslint";
44
import { parse } from "@/src/utils/parse";
55
import { findIdentifiersInChildren } from "@/src/utils/find-identifiers-in-children";
66
import { readFileSync } from "fs";
7-
import { getImportDeclarationPath } from "@/src/utils/get-import-declaration-path";
7+
import { getImportDeclaration } from "@/src/utils/get-import-declaration";
8+
import { findInChildren } from "./find-in-children";
89

910
export function resolveImportedId(
1011
context: RuleContext<string, unknown[]>,
1112
impt: TSESTree.ImportDeclaration
1213
) {
13-
const importPath = getImportDeclarationPath(context, impt);
14-
if (importPath.startsWith("./node_modules")) return;
14+
const imp = getImportDeclaration(context, impt);
15+
if (!imp.path || imp.path.startsWith("./node_modules")) {
16+
const id = impt.specifiers[0].local;
17+
18+
return {
19+
id: findInChildren(
20+
parse(`export function ${id.name}() {}`, context),
21+
isIdentifier
22+
),
23+
module: imp.module,
24+
protocol: imp.protocol,
25+
context,
26+
};
27+
}
1528
let content = "";
1629
try {
17-
content = readFileSync(importPath, "utf-8");
30+
content = readFileSync(imp.path, "utf-8");
1831
} catch (e) {
19-
console.error(`Could not read file ${importPath}`);
32+
console.error(`Could not read file ${imp}`);
2033
console.error(e);
2134
return;
2235
}
@@ -29,7 +42,7 @@ export function resolveImportedId(
2942

3043
const ctxParsed = {
3144
...context,
32-
physicalFilename: importPath,
45+
physicalFilename: imp.path,
3346
sourceCode: new SourceCode(content, parsed),
3447
parser: (context as any).parser,
3548
parserOptions: context.parserOptions,
@@ -41,5 +54,10 @@ export function resolveImportedId(
4154
settings: context.settings,
4255
};
4356

44-
return { id: identifierInParsed, context: ctxParsed };
57+
return {
58+
id: identifierInParsed,
59+
module: imp.module,
60+
protocol: imp.protocol,
61+
context: ctxParsed,
62+
};
4563
}

0 commit comments

Comments
 (0)