Skip to content

Commit

Permalink
feat: handling native node functions/imports
Browse files Browse the repository at this point in the history
  • Loading branch information
Akronae committed Sep 21, 2024
1 parent 67dee9e commit f09bb2c
Show file tree
Hide file tree
Showing 17 changed files with 139 additions and 52 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@
"scripts": {
"build": "pkgroll",
"test": "yarn build && vitest run --reporter verbose",
"docs": "yarn build && eslint-doc-generator && eslint-doc-generator --init-rule-docs && eslint-doc-generator"
"docs": "yarn build && eslint-doc-generator && eslint-doc-generator --init-rule-docs && eslint-doc-generator",
"v:major": "npm version major -m \"chore: bump major to %s\"",
"v:minor": "npm version minor -m \"chore: bump minor to %s\"",
"v:patch": "npm version patch -m \"chore: bump patch to %s\""
},
"dependencies": {
"@types/eslint": "^8.56.10",
Expand Down
15 changes: 15 additions & 0 deletions src/rules/might-throw/might-throw.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,18 @@ await testFile(
[rule.name],
[]
);
await testFile(
"src/rules/might-throw/tests/native-modules-err.ts",
[rule.name],
[
{
messageId: "mightThrow",
line: 3,
},
]
);
await testFile(
"src/rules/might-throw/tests/native-modules-ok.ts",
[rule.name],
[]
);
40 changes: 14 additions & 26 deletions src/rules/might-throw/might-throw.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import { ESLintUtils } from "@typescript-eslint/utils";
import {
findInParent,
isFunctionDeclaration,
isThrowStatement,
isTryStatement,
} from "@/src/utils";
import { getFunctionId } from "../../utils/get-function-id";
import { resolveFunc } from "../../utils/resolve-func";
import { getCallExprId } from "../../utils/get-call-expr-id";
import { findInChildren } from "../../utils/find-in-children";
import { createRule } from "../create-rule";
import { findInParent, isFunctionDeclaration } from "@/src/utils";
import { getFunctionId } from "@/src/utils/get-function-id";
import { getCallExprId } from "@/src/utils/get-call-expr-id";
import { createRule } from "@/src/rules/create-rule";
import { canFuncThrow } from "@/src/rules/no-unhandled";

const name = "might-throw";
const rule = createRule({
Expand Down Expand Up @@ -43,21 +36,16 @@ const rule = createRule({
CallExpression(node) {
const id = getCallExprId(node);
if (!id) return;
const fun = resolveFunc(id, context);
if (!fun?.func) return;
const throwNode = findInChildren(fun.func, isThrowStatement);
if (throwNode) {
const tryNode = findInParent(node, isTryStatement);
if (tryNode) return;
const throwing = canFuncThrow(id, context);
if (!throwing) return;

context.report({
node,
messageId: "mightThrow",
data: {
name: id.name,
},
});
}
context.report({
node,
messageId: "mightThrow",
data: {
name: id.name,
},
});
},
};
},
Expand Down
4 changes: 4 additions & 0 deletions src/rules/might-throw/tests/native-modules-err.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { readFileSync } from "fs";

const content = readFileSync("this file does not exist", "utf-8");
console.log({ content });
8 changes: 8 additions & 0 deletions src/rules/might-throw/tests/native-modules-ok.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { readFileSync } from "fs";

try {
const content = readFileSync("this file does not exist", "utf-8");
console.log({ content });
} catch (e) {
console.error(e);
}
16 changes: 15 additions & 1 deletion src/rules/no-unhandled/no-unhandled.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ await testFile(
},
]
);

await testFile("src/rules/no-unhandled/tests/import-ok.ts", [rule.name], []);
await testFile("src/rules/no-unhandled/tests/import-ok-2.ts", [rule.name], []);
await testFile("src/rules/no-unhandled/tests/basic-ok.ts", [rule.name], []);
Expand Down Expand Up @@ -76,3 +75,18 @@ await testFile(
},
]
);
await testFile(
"src/rules/no-unhandled/tests/native-modules-err.ts",
[rule.name],
[
{
messageId: "noUnhandled",
line: 3,
},
]
);
await testFile(
"src/rules/no-unhandled/tests/native-modules-ok.ts",
[rule.name],
[]
);
15 changes: 12 additions & 3 deletions src/rules/no-unhandled/no-unhandled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from "@/src/utils";
import { RuleContext } from "@typescript-eslint/utils/ts-eslint";
import { createRule } from "@/src/rules/create-rule";
import { nativeThrowing } from "@/src/utils/native-throwing";

const throwFunctions = new Set<string>();
const scannedFunctions = new Set<string>();
Expand Down Expand Up @@ -43,7 +44,7 @@ const rule = createRule({
CallExpression(called) {
const id = getCallExprId(called);
if (!id) return;
const throws = checkfunc(id, context);
const throws = canFuncThrow(id, context);

if (throws) {
const parentFunction = findInParent(called, isFunctionDeclaration);
Expand All @@ -64,7 +65,7 @@ const rule = createRule({
},
});

function checkfunc(
export function canFuncThrow(
node: TSESTree.Identifier | TSESTree.PrivateIdentifier,
context: RuleContext<string, unknown[]>
): boolean {
Expand All @@ -74,6 +75,14 @@ function checkfunc(
}

const res = resolveFunc(node, context);
if (res?.module) {
const found = nativeThrowing.some(
(x) => x.module === res.module && x.method === res.func.id?.name
);
if (found) {
return true;
}
}
if (!res?.func) return false;
return scanfunc(res.func, res.context);
}
Expand Down Expand Up @@ -105,7 +114,7 @@ function scanfunc(
return;
}

throws = checkfunc(child.callee, context);
throws = canFuncThrow(child.callee, context);
if (throws) {
if (node.id) throwFunctions.add(getFunctionId(context, node.id));
resolve(true);
Expand Down
4 changes: 4 additions & 0 deletions src/rules/no-unhandled/tests/native-modules-err.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { readFileSync } from "fs";

const content = readFileSync("this file does not exist", "utf-8");
console.log({ content });
8 changes: 8 additions & 0 deletions src/rules/no-unhandled/tests/native-modules-ok.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { readFileSync } from "fs";

try {
const content = readFileSync("this file does not exist", "utf-8");
console.log({ content });
} catch (e) {
console.error(e);
}
6 changes: 3 additions & 3 deletions src/utils/get-function-id.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { TSESTree } from "@typescript-eslint/types";
import { RuleContext } from "@typescript-eslint/utils/ts-eslint";
import { isImportDeclaration, isImportSpecifier } from "./ast-guards";
import { getImportDeclarationPath } from "./get-import-declaration-path";
import { getImportDeclaration } from "./get-import-declaration";

export function getFunctionId(
context: RuleContext<string, unknown[]>,
node: TSESTree.Identifier | TSESTree.ImportDeclaration
) {
if (isImportDeclaration(node)) {
const name = node.specifiers.find(isImportSpecifier)?.local.name;
const fileName = getImportDeclarationPath(context, node);
return `${fileName}#${name}`;
const fileName = getImportDeclaration(context, node);
return `${fileName.path}#${name}`;
}
return `${context.physicalFilename}#${node.name}`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ function cleanTsConfig(content: string) {

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

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

export function getImportDeclarationPath(
export function getImportDeclaration(
context: RuleContext<string, unknown[]>,
impt: TSESTree.ImportDeclaration
) {
Expand All @@ -62,9 +62,13 @@ export function getImportDeclarationPath(
}

if (!to.startsWith(".")) {
const split = to.split(":");
if (!existsSync(to)) {
return { module: split.at(-1), protocol: split.at(-2) };
}
// no relative path and no TS alias,
// considering it as a node_module
return `./node_modules/${to}`;
return { path: `./node_modules/${to}`, module: to };
}
} else {
res = path.resolve(path.dirname(from), to);
Expand All @@ -82,5 +86,5 @@ export function getImportDeclarationPath(
}
}

return res;
return { path: res, module: to };
}
2 changes: 1 addition & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export * from "./find-in-children";
export * from "./find-in-parent";
export * from "./get-call-expr-id";
export * from "./get-function-id";
export * from "./get-import-declaration-path";
export * from "./get-import-declaration";
export * from "./infer-guard-type";
export * from "./parse";
export * from "./resolve-func";
Expand Down
7 changes: 7 additions & 0 deletions src/utils/native-throwing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const nativeThrowing = [
{
protocol: "node",
module: "fs",
method: "readFileSync",
},
];
2 changes: 1 addition & 1 deletion src/utils/resolve-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function resolveClass(
context: RuleContext<string, unknown[]>
) {
const resolved = resolveId(id, context);
if (!resolved) return;
if (!resolved?.id) return;
const class_ = findInParent(resolved.id, isClassDeclaration);
if (!class_) return;
return { class: class_, context: resolved.context };
Expand Down
9 changes: 7 additions & 2 deletions src/utils/resolve-func.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ export function resolveFunc(
context: RuleContext<string, unknown[]>
) {
const resolved = resolveId(id, context);
if (!resolved) return;
if (!resolved?.id) return;
const func = findInParent(resolved.id, isFunctionDeclaration);
if (!func) return;
return { func, context: resolved.context };
return {
func,
module: resolved.module,
protocol: resolved.protocol,
context: resolved.context,
};
}
2 changes: 1 addition & 1 deletion src/utils/resolve-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ export function resolveId(

return idInParsed;
}
return { id: identifier, context };
return { id: identifier, module: null, protocol: null, context };
}
34 changes: 26 additions & 8 deletions src/utils/resolve-imported-id.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
import { TSESTree } from "@typescript-eslint/utils";
import { isExportNamedDeclaration } from "@/src/utils/ast-guards";
import { isExportNamedDeclaration, isIdentifier } from "@/src/utils/ast-guards";
import { RuleContext, SourceCode } from "@typescript-eslint/utils/ts-eslint";
import { parse } from "@/src/utils/parse";
import { findIdentifiersInChildren } from "@/src/utils/find-identifiers-in-children";
import { readFileSync } from "fs";
import { getImportDeclarationPath } from "@/src/utils/get-import-declaration-path";
import { getImportDeclaration } from "@/src/utils/get-import-declaration";
import { findInChildren } from "./find-in-children";

export function resolveImportedId(
context: RuleContext<string, unknown[]>,
impt: TSESTree.ImportDeclaration
) {
const importPath = getImportDeclarationPath(context, impt);
if (importPath.startsWith("./node_modules")) return;
const imp = getImportDeclaration(context, impt);
if (!imp.path || imp.path.startsWith("./node_modules")) {
const id = impt.specifiers[0].local;

return {
id: findInChildren(
parse(`export function ${id.name}() {}`, context),
isIdentifier
),
module: imp.module,
protocol: imp.protocol,
context,
};
}
let content = "";
try {
content = readFileSync(importPath, "utf-8");
content = readFileSync(imp.path, "utf-8");
} catch (e) {
console.error(`Could not read file ${importPath}`);
console.error(`Could not read file ${imp}`);
console.error(e);
return;
}
Expand All @@ -29,7 +42,7 @@ export function resolveImportedId(

const ctxParsed = {
...context,
physicalFilename: importPath,
physicalFilename: imp.path,
sourceCode: new SourceCode(content, parsed),
parser: (context as any).parser,
parserOptions: context.parserOptions,
Expand All @@ -41,5 +54,10 @@ export function resolveImportedId(
settings: context.settings,
};

return { id: identifierInParsed, context: ctxParsed };
return {
id: identifierInParsed,
module: imp.module,
protocol: imp.protocol,
context: ctxParsed,
};
}

0 comments on commit f09bb2c

Please sign in to comment.