@antithrow/eslint-plugin 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ <div align="center">
2
+ <h1>@antithrow/eslint-plugin</h1>
3
+ <p>
4
+ ESLint rules for <a href="https://github.com/jack-weilage/antithrow">antithrow</a> Result types
5
+ </p>
6
+
7
+ ![NPM Version](https://img.shields.io/npm/v/@antithrow/eslint-plugin)
8
+ ![NPM License](https://img.shields.io/npm/l/@antithrow/eslint-plugin)
9
+ </div>
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ bun add -d @antithrow/eslint-plugin
15
+ ```
16
+
17
+ This plugin requires [typed linting](https://typescript-eslint.io/getting-started/typed-linting/) to be configured.
18
+
19
+ ## Usage
20
+
21
+ Add the recommended config to your `eslint.config.ts`:
22
+
23
+ ```ts
24
+ import antithrow from "@antithrow/eslint-plugin";
25
+
26
+ export default [
27
+ // ... your other configs
28
+ antithrow.configs.recommended,
29
+ ];
30
+ ```
31
+
32
+ Or configure rules individually:
33
+
34
+ ```ts
35
+ import antithrow from "@antithrow/eslint-plugin";
36
+
37
+ export default [
38
+ {
39
+ plugins: {
40
+ "@antithrow": antithrow,
41
+ },
42
+ rules: {
43
+ "@antithrow/no-unused-result": "error",
44
+ },
45
+ },
46
+ ];
47
+ ```
48
+
49
+ ## Rules
50
+
51
+ | Rule | Description | Recommended |
52
+ | --- | --- | --- |
53
+ | [`no-unused-result`](./docs/rules/no-unused-result.md) | Require `Result` and `ResultAsync` values to be used | `error` |
@@ -0,0 +1,10 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+ /** @lintignore */
3
+ export interface AntithrowPluginDocs {
4
+ description: string;
5
+ recommended?: boolean;
6
+ requiresTypeChecking?: boolean;
7
+ }
8
+ export declare const createRule: <Options extends readonly unknown[], MessageIds extends string>({ meta, name, ...rule }: Readonly<ESLintUtils.RuleWithMetaAndName<Options, MessageIds, AntithrowPluginDocs>>) => ESLintUtils.RuleModule<MessageIds, Options, AntithrowPluginDocs, ESLintUtils.RuleListener> & {
9
+ name: string;
10
+ };
@@ -0,0 +1,2 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+ export const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/jack-weilage/antithrow/blob/main/packages/eslint-plugin/docs/rules/${name}.md`);
@@ -0,0 +1,6 @@
1
+ import type { TSESLint } from "@typescript-eslint/utils";
2
+ type Plugin = TSESLint.FlatConfig.Plugin & {
3
+ configs: Record<string, TSESLint.FlatConfig.Config>;
4
+ };
5
+ declare const plugin: Plugin;
6
+ export default plugin;
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ import packageJson from "../package.json" with { type: "json" };
2
+ import { noUnusedResult } from "./rules/index.js";
3
+ const plugin = {
4
+ meta: {
5
+ name: packageJson.name,
6
+ version: packageJson.version,
7
+ },
8
+ rules: {
9
+ "no-unused-result": noUnusedResult,
10
+ },
11
+ configs: {},
12
+ };
13
+ plugin.configs = {
14
+ ...plugin.configs,
15
+ recommended: {
16
+ plugins: {
17
+ "@antithrow": plugin,
18
+ },
19
+ rules: {
20
+ "@antithrow/no-unused-result": "error",
21
+ },
22
+ },
23
+ };
24
+ export default plugin;
@@ -0,0 +1 @@
1
+ export { noUnusedResult } from "./no-unused-result.js";
@@ -0,0 +1 @@
1
+ export { noUnusedResult } from "./no-unused-result.js";
@@ -0,0 +1,4 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+ export declare const noUnusedResult: ESLintUtils.RuleModule<"unusedResult", [], import("../create-rule.js").AntithrowPluginDocs, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
@@ -0,0 +1,85 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+ import ts from "typescript";
3
+ import { createRule } from "../create-rule.js";
4
+ const RESULT_TYPE_NAMES = new Set(["Ok", "Err", "ResultAsync"]);
5
+ function isResultType(type) {
6
+ if (type.isUnion()) {
7
+ return type.types.some((t) => isResultType(t));
8
+ }
9
+ const flags = type.getFlags();
10
+ if (flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never)) {
11
+ return false;
12
+ }
13
+ const symbol = type.getSymbol();
14
+ if (!symbol || !RESULT_TYPE_NAMES.has(symbol.getName())) {
15
+ return false;
16
+ }
17
+ const declarations = symbol.getDeclarations() ?? [];
18
+ return declarations.some((decl) => {
19
+ const sourceFile = decl.getSourceFile();
20
+ return sourceFile.fileName.includes("antithrow");
21
+ });
22
+ }
23
+ export const noUnusedResult = createRule({
24
+ name: "no-unused-result",
25
+ meta: {
26
+ type: "problem",
27
+ docs: {
28
+ description: "Require Result and ResultAsync values to be used, preventing silently ignored errors.",
29
+ recommended: true,
30
+ requiresTypeChecking: true,
31
+ },
32
+ messages: {
33
+ unusedResult: "This Result must be used. Handle the error case or explicitly discard it with `void`.",
34
+ },
35
+ schema: [],
36
+ },
37
+ defaultOptions: [],
38
+ create(context) {
39
+ const services = ESLintUtils.getParserServices(context);
40
+ const checker = services.program.getTypeChecker();
41
+ function checkExpression(node) {
42
+ switch (node.type) {
43
+ case "UnaryExpression":
44
+ if (node.operator === "void") {
45
+ return;
46
+ }
47
+ break;
48
+ case "ConditionalExpression":
49
+ checkExpression(node.consequent);
50
+ checkExpression(node.alternate);
51
+ return;
52
+ case "LogicalExpression":
53
+ checkExpression(node.left);
54
+ checkExpression(node.right);
55
+ return;
56
+ case "SequenceExpression":
57
+ for (const expr of node.expressions) {
58
+ checkExpression(expr);
59
+ }
60
+ return;
61
+ case "TSAsExpression":
62
+ case "TSNonNullExpression":
63
+ checkExpression(node.expression);
64
+ return;
65
+ case "ChainExpression":
66
+ checkExpression(node.expression);
67
+ return;
68
+ }
69
+ const tsNode = services.esTreeNodeToTSNodeMap.get(node);
70
+ const type = checker.getTypeAtLocation(tsNode);
71
+ if (!isResultType(type)) {
72
+ return;
73
+ }
74
+ context.report({
75
+ node,
76
+ messageId: "unusedResult",
77
+ });
78
+ }
79
+ return {
80
+ ExpressionStatement(node) {
81
+ checkExpression(node.expression);
82
+ },
83
+ };
84
+ },
85
+ });
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@antithrow/eslint-plugin",
3
+ "version": "0.0.0",
4
+ "description": "ESLint plugin for antithrow Result types",
5
+ "license": "MIT",
6
+ "author": "Jack Weilage",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/jack-weilage/antithrow.git",
10
+ "directory": "packages/eslint-plugin"
11
+ },
12
+ "keywords": [
13
+ "eslint",
14
+ "eslintplugin",
15
+ "result",
16
+ "error-handling",
17
+ "typescript"
18
+ ],
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "main": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsc -p tsconfig.build.json",
33
+ "clean": "rm -rf dist",
34
+ "lint": "bun run lint:publint && bun run lint:types",
35
+ "lint:publint": "publint",
36
+ "lint:types": "tsc --noEmit"
37
+ },
38
+ "dependencies": {
39
+ "@typescript-eslint/utils": "8.54.0"
40
+ },
41
+ "devDependencies": {
42
+ "@typescript-eslint/parser": "8.54.0",
43
+ "@typescript-eslint/rule-tester": "8.54.0",
44
+ "antithrow": "workspace:*",
45
+ "eslint": "9.39.2",
46
+ "typescript": "5.9.3"
47
+ },
48
+ "peerDependencies": {
49
+ "eslint": "^9.0.0",
50
+ "typescript": ">=5.0.0"
51
+ }
52
+ }