Skip to content

Commit

Permalink
feat: handling private identifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
Akronae committed Aug 2, 2024
1 parent 092c548 commit fb6f1c0
Show file tree
Hide file tree
Showing 17 changed files with 202 additions and 40 deletions.
16 changes: 15 additions & 1 deletion src/rules/might-throw/might-throw.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,18 @@ await testFile(
);

await testFile("src/rules/might-throw/tests/import-ok.ts", [rule.name], []);
await testFile("src/rules/might-throw/tests/module-ok.ts", [rule.name], []);
await testFile(
"src/rules/might-throw/tests/private-identifier-err.ts",
[rule.name],
[
{
messageId: "mightThrow",
line: 9,
},
]
);
await testFile(
"src/rules/might-throw/tests/private-identifier-ok.ts",
[rule.name],
[]
);
Empty file.
13 changes: 13 additions & 0 deletions src/rules/might-throw/tests/private-identifier-err.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { throwingFunc } from "./throwing-func";

export class Test {
public constructor() {
this.#privateMethod();
}

#privateMethod() {
throwingFunc();
}
}

new Test();
11 changes: 11 additions & 0 deletions src/rules/might-throw/tests/private-identifier-ok.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class Test {
public constructor() {
this.#privateMethod();
}

#privateMethod() {
console.log("Hello");
}
}

new Test();
10 changes: 10 additions & 0 deletions src/rules/no-unhandled/no-unhandled.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ await testFile(
]
);
await testFile("src/rules/no-unhandled/tests/module-ok.ts", [rule.name], []);
await testFile(
"src/rules/no-unhandled/tests/private-identifier-ok.ts",
[rule.name],
[]
);
await testFile(
"src/rules/no-unhandled/tests/private-identifier-err.ts",
[rule.name],
[]
);
await testFile("src/rules/no-unhandled/tests/recursive-ok.ts", [rule.name], []);
await testFile(
"src/rules/no-unhandled/tests/recursive-err.ts",
Expand Down
6 changes: 4 additions & 2 deletions src/rules/no-unhandled/no-unhandled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
resolveFunc,
getCallExprId,
isCatchClause,
isMethodDefinition,
} from "@/src/utils";
import { RuleContext } from "@typescript-eslint/utils/ts-eslint";
import { createRule } from "@/src/rules/create-rule";
Expand Down Expand Up @@ -46,8 +47,9 @@ const rule = createRule({

if (throws) {
const parentFunction = findInParent(called, isFunctionDeclaration);
const parentMethod = findInParent(called, isMethodDefinition);
const id = findInChildren(called, isIdentifier);
if (!parentFunction?.id) {
if (!parentFunction?.id && !parentMethod?.key) {
context.report({
node: called,
messageId: "noUnhandled",
Expand All @@ -63,7 +65,7 @@ const rule = createRule({
});

function checkfunc(
node: TSESTree.Identifier,
node: TSESTree.Identifier | TSESTree.PrivateIdentifier,
context: RuleContext<string, unknown[]>
): boolean {
const try_ = findInParent(node, isTryStatement);
Expand Down
6 changes: 6 additions & 0 deletions src/rules/no-unhandled/tests/basic-ok.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { throwingFunc } from "./throwing-func";

const x = 5;
let y = "abc123";
function myFunction() {}
Expand Down Expand Up @@ -45,4 +47,8 @@ try {
console.error(e);
}

function h() {
throwingFunc();
}

export {};
13 changes: 13 additions & 0 deletions src/rules/no-unhandled/tests/private-identifier-err.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { throwingFunc } from "./throwing-func";

class Test {
public constructor() {
this.#privateMethod();
}

#privateMethod() {
throwingFunc();
}
}

new Test();
29 changes: 29 additions & 0 deletions src/rules/no-unhandled/tests/private-identifier-ok.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { throwingFunc } from "./throwing-func";

class Test {
public constructor() {
this.#privateMethod();
}

#privateMethod() {
console.log("Hello");
}
}

new Test();

class Test2 {
public constructor() {
this.#privateMethod();
}

#privateMethod() {
throwingFunc();
}
}

try {
new Test2();
} catch (e) {
console.error(e);
}
18 changes: 18 additions & 0 deletions src/utils/ast-guards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ export function isFunctionDeclaration(
return node != null && node.type === AST_NODE_TYPES.FunctionDeclaration;
}

export function isMethodDefinition(
node: Typed
): node is TSESTree.MethodDefinition {
return node != null && node.type === AST_NODE_TYPES.MethodDefinition;
}

export function isMemberExpression(
node: Typed
): node is TSESTree.MemberExpression {
Expand Down Expand Up @@ -68,6 +74,12 @@ export function isIdentifier(node: Typed): node is TSESTree.Identifier {
return node != null && node.type === AST_NODE_TYPES.Identifier;
}

export function isPrivateIdentifier(
node: Typed
): node is TSESTree.PrivateIdentifier {
return node != null && node.type === AST_NODE_TYPES.PrivateIdentifier;
}

export function isCatchClause(node: Typed): node is TSESTree.CatchClause {
return node != null && node.type === AST_NODE_TYPES.CatchClause;
}
Expand All @@ -85,3 +97,9 @@ export function isObjectExpression(
export function isProperty(node: Typed): node is TSESTree.Property {
return node != null && node.type === AST_NODE_TYPES.Property;
}

export function isClassDeclaration(
node: Typed
): node is TSESTree.ClassDeclaration {
return node != null && node.type === AST_NODE_TYPES.ClassDeclaration;
}
16 changes: 16 additions & 0 deletions src/utils/find-in-children-all.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TSESTree } from "@typescript-eslint/types";
import { InferGuardType } from "./infer-guard-type";
import { exploreChildren } from "./explore-children";

export function findInChildrenAll<
T extends TSESTree.Node,
F extends (x: TSESTree.Node) => x is T
>(node: TSESTree.Node, predicate: F) {
const result: InferGuardType<F>[] = [];
exploreChildren(node, (child) => {
if (predicate(child)) {
result.push(child as InferGuardType<F>);
}
});
return result;
}
14 changes: 10 additions & 4 deletions src/utils/get-call-expr-id.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import { TSESTree } from "@typescript-eslint/utils";
import { isIdentifier } from "@typescript-eslint/utils/ast-utils";
import { isMemberExpression } from "@/src/utils/ast-guards";
import {
isIdentifier,
isMemberExpression,
isPrivateIdentifier,
} from "@/src/utils/ast-guards";

export function getCallExprId(called: TSESTree.CallExpression) {
if (isIdentifier(called.callee)) {
return called.callee;
} else if (isMemberExpression(called.callee)) {
if (isIdentifier(called.callee.property)) {
if (
isIdentifier(called.callee.property) ||
isPrivateIdentifier(called.callee.property)
) {
return called.callee.property;
} else {
throw new Error(
"Cannot handle non-identifier member expression property"
`Cannot handle non-identifier member expression property ${called.callee.property}`
);
}
}
Expand Down
1 change: 0 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,4 @@ export * from "./get-import-declaration-path";
export * from "./infer-guard-type";
export * from "./parse";
export * from "./resolve-func";
export * from "./resolve-imported-func";
export * from "./test-file";
16 changes: 16 additions & 0 deletions src/utils/resolve-class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TSESTree } from "@typescript-eslint/utils";
import { isClassDeclaration } from "@/src/utils/ast-guards";
import { RuleContext } from "@typescript-eslint/utils/ts-eslint";
import { resolveId } from "./resolve-id";
import { findInParent } from "./find-in-parent";

export function resolveClass(
id: TSESTree.Identifier | TSESTree.PrivateIdentifier,
context: RuleContext<string, unknown[]>
) {
const resolved = resolveId(id, context);
if (!resolved) return;
const class_ = findInParent(resolved.id, isClassDeclaration);
if (!class_) return;
return { class: class_, context: resolved.context };
}
27 changes: 9 additions & 18 deletions src/utils/resolve-func.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
import { TSESTree } from "@typescript-eslint/utils";
import {
isFunctionDeclaration,
isImportDeclaration,
} from "@/src/utils/ast-guards";
import { isFunctionDeclaration } from "@/src/utils/ast-guards";
import { RuleContext } from "@typescript-eslint/utils/ts-eslint";
import { findIdentifierInParents } from "@/src/utils/find-identifier-in-parents";
import { resolveImportedFunc } from "@/src/utils/resolve-imported-func";
import { resolveId } from "./resolve-id";
import { findInParent } from "./find-in-parent";

export function resolveFunc(
id: TSESTree.Identifier,
id: TSESTree.Identifier | TSESTree.PrivateIdentifier,
context: RuleContext<string, unknown[]>
) {
const identifier = findIdentifierInParents(id.name, id);
if (isImportDeclaration(identifier?.parent?.parent)) {
const funcInParsed = resolveImportedFunc(context, identifier.parent.parent);
if (!funcInParsed?.func) return;

return funcInParsed;
} else if (isFunctionDeclaration(identifier?.parent)) {
return { func: identifier.parent, context };
}

return null;
const resolved = resolveId(id, context);
if (!resolved) return;
const func = findInParent(resolved.id, isFunctionDeclaration);
if (!func) return;
return { func, context: resolved.context };
}
20 changes: 20 additions & 0 deletions src/utils/resolve-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { TSESTree } from "@typescript-eslint/utils";
import { isImportDeclaration } from "@/src/utils/ast-guards";
import { RuleContext } from "@typescript-eslint/utils/ts-eslint";
import { findIdentifierInParents } from "@/src/utils/find-identifier-in-parents";
import { resolveImportedId } from "./resolve-imported-id";

export function resolveId(
id: TSESTree.Identifier | TSESTree.PrivateIdentifier,
context: RuleContext<string, unknown[]>
) {
const identifier = findIdentifierInParents(id.name, id);
if (!identifier) return;
if (isImportDeclaration(identifier?.parent?.parent)) {
const idInParsed = resolveImportedId(context, identifier.parent.parent);
if (!idInParsed?.id) return;

return idInParsed;
}
return { id: identifier, context };
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
import { TSESTree } from "@typescript-eslint/utils";
import {
isExportNamedDeclaration,
isFunctionDeclaration,
} from "@/src/utils/ast-guards";
import { isExportNamedDeclaration } 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 { findInParent } from "@/src/utils/find-in-parent";

export function resolveImportedFunc(
export function resolveImportedId(
context: RuleContext<string, unknown[]>,
impt: TSESTree.ImportDeclaration
) {
const importPath = getImportDeclarationPath(context, impt);
if (importPath.startsWith("./node_modules")) return;
const content = readFileSync(importPath, "utf-8");
let content = "";
try {
content = readFileSync(importPath, "utf-8");
} catch (e) {
console.error(`Could not read file ${importPath}`);
console.error(e);
return;
}
const parsed = parse(content, context);
const identifierInParsed = findIdentifiersInChildren(
impt.specifiers[0].local.name,
parsed.body
).find(
(x) =>
isFunctionDeclaration(x.parent) &&
isExportNamedDeclaration(x.parent.parent)
);
).find((x) => isExportNamedDeclaration(x.parent.parent));
if (!identifierInParsed) return;
const funcInParsed = findInParent(identifierInParsed, isFunctionDeclaration);

const ctxParsed = {
...context,
Expand All @@ -43,5 +41,5 @@ export function resolveImportedFunc(
settings: context.settings,
};

return { func: funcInParsed, context: ctxParsed };
return { id: identifierInParsed, context: ctxParsed };
}

0 comments on commit fb6f1c0

Please sign in to comment.