@clipboard-health/eslint-plugin 0.1.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,39 @@
1
+ # @clipboard-health/eslint-plugin <!-- omit from toc -->
2
+
3
+ Clipboard's [ESLint](https://eslint.org/) plugin.
4
+
5
+ ## Table of contents <!-- omit from toc -->
6
+
7
+ - [Install](#install)
8
+ - [Local development commands](#local-development-commands)
9
+
10
+ ## Install
11
+
12
+ > [!NOTE]
13
+ > Take a look at our [eslint-config](https://github.com/ClipboardHealth/core-utils/tree/main/packages/eslint-config) which contains this plugin.
14
+ > In most cases you can just use the eslint-config instead of directly installing this plugin.
15
+
16
+ ```bash
17
+ npm install -D @clipboard-health/eslint-plugin
18
+ ```
19
+
20
+ Then, modify your `.eslintrc.js` file to configure individual rules in this plugin:
21
+
22
+ ```js
23
+ module.exports = {
24
+ plugins: ["@clipboard-health"],
25
+ overrides: [
26
+ {
27
+ files: ["**/*.controller.ts", "**/*.controllers.ts"],
28
+ rules: {
29
+ "@clipboard-health/enforce-ts-rest-in-controllers": "error",
30
+ },
31
+ },
32
+ ],
33
+ root: true,
34
+ };
35
+ ```
36
+
37
+ ## Local development commands
38
+
39
+ See [`package.json`](./package.json) `scripts` for a list of commands.
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@clipboard-health/eslint-plugin",
3
+ "description": "Clipboard's ESLint Plugin",
4
+ "version": "0.1.0",
5
+ "dependencies": {
6
+ "@typescript-eslint/utils": "8.33.0",
7
+ "tslib": "2.8.1"
8
+ },
9
+ "keywords": [
10
+ "eslint",
11
+ "plugin"
12
+ ],
13
+ "license": "MIT",
14
+ "main": "./src/index.js",
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "repository": {
19
+ "directory": "packages/eslint-plugin",
20
+ "type": "git",
21
+ "url": "git+https://github.com/ClipboardHealth/core-utils.git"
22
+ },
23
+ "type": "commonjs",
24
+ "typings": "./src/index.d.ts",
25
+ "types": "./src/index.d.ts"
26
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare const rules: {
2
+ "enforce-ts-rest-in-controllers": import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"missingDecorator" | "missingReturn" | "decoratorNotFromPackage" | "callNotFromPackage", [], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
3
+ };
package/src/index.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rules = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const enforce_ts_rest_in_controllers_1 = tslib_1.__importDefault(require("./lib/rules/enforce-ts-rest-in-controllers"));
6
+ exports.rules = {
7
+ "enforce-ts-rest-in-controllers": enforce_ts_rest_in_controllers_1.default,
8
+ };
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/eslint-plugin/src/index.ts"],"names":[],"mappings":";;;;AAAA,wHAAoF;AAEvE,QAAA,KAAK,GAAG;IACnB,gCAAgC,EAAE,wCAA0B;CAC7D,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+ declare const createRule: <Options extends readonly unknown[], MessageIds extends string>({ meta, name, ...rule }: Readonly<ESLintUtils.RuleWithMetaAndName<Options, MessageIds, unknown>>) => ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener>;
3
+ export default createRule;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("@typescript-eslint/utils");
4
+ const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/ClipboardHealth/core-utils/tree/main/packages/eslint-plugin/src/lib/rules/${name}`);
5
+ exports.default = createRule;
6
+ //# sourceMappingURL=createRule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createRule.js","sourceRoot":"","sources":["../../../../../packages/eslint-plugin/src/lib/createRule.ts"],"names":[],"mappings":";;AAAA,oDAAuD;AAEvD,MAAM,UAAU,GAAG,mBAAW,CAAC,WAAW,CACxC,CAAC,IAAI,EAAE,EAAE,CACP,gGAAgG,IAAI,EAAE,CACzG,CAAC;AAEF,kBAAe,UAAU,CAAC"}
@@ -0,0 +1,2 @@
1
+ declare const rule: import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"missingDecorator" | "missingReturn" | "decoratorNotFromPackage" | "callNotFromPackage", [], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
2
+ export default rule;
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ /**
5
+ * @fileoverview Rule to require controller methods to use ts-rest as per our best practices on backend REST APIs
6
+ */
7
+ const utils_1 = require("@typescript-eslint/utils");
8
+ const createRule_1 = tslib_1.__importDefault(require("../../createRule"));
9
+ const rule = (0, createRule_1.default)({
10
+ name: "enforce-ts-rest-in-controllers",
11
+ defaultOptions: [],
12
+ meta: {
13
+ type: "problem",
14
+ docs: {
15
+ description: "Require use of ts-rest on all controller methods",
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ missingDecorator: "Controller method '{{name}}' must be decorated with `@TsRestHandler()` from `@ts-rest/nest` package. See https://www.notion.so/BP-REST-API-f769b7fe745c4cf38f6eca2e9ad8a843 for more information.",
20
+ missingReturn: "Controller method '{{name}}' must only return the result of calling `tsRestHandler()` from `@ts-rest/nest` package. See https://www.notion.so/BP-REST-API-f769b7fe745c4cf38f6eca2e9ad8a843 for more information.",
21
+ decoratorNotFromPackage: "Decorator `TsRestHandler` must be imported from `@ts-rest/nest`",
22
+ callNotFromPackage: "Method `tsRestHandler` must be imported from `@ts-rest/nest`",
23
+ },
24
+ },
25
+ create(context) {
26
+ let methodImportedCorrectly = false;
27
+ let decoratorImportedCorrectly = false;
28
+ return {
29
+ /*
30
+ * This method iterates through import declarations and verifies that the
31
+ * the `TsRestHandler` decorator and `tsRestHandler` method are imported
32
+ * from `@ts-rest/nest` package. We'll use this information to later
33
+ * highlight usages of those symbols to point out that they need to be
34
+ * imported from the correct source.
35
+ */
36
+ // eslint-disable-next-line sonarjs/cognitive-complexity
37
+ ImportDeclaration(node) {
38
+ if (node.source.value === "@ts-rest/nest") {
39
+ for (const spec of node.specifiers) {
40
+ if (spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier) {
41
+ if (spec.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
42
+ spec.imported.name === "tsRestHandler") {
43
+ methodImportedCorrectly = true;
44
+ }
45
+ if (spec.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
46
+ spec.imported.name === "TsRestHandler") {
47
+ decoratorImportedCorrectly = true;
48
+ }
49
+ }
50
+ }
51
+ }
52
+ },
53
+ /*
54
+ * This method verifies that non-constructor and non-private methods use the
55
+ * `TsRestHandler` decorator and return the result of `tsRestHandler` as the
56
+ * only expression in it.
57
+ */
58
+ // eslint-disable-next-line sonarjs/cognitive-complexity, complexity
59
+ MethodDefinition(node) {
60
+ const symbolName = (node.key.type === utils_1.AST_NODE_TYPES.Identifier && node.key.name) || "<unknown>";
61
+ // Ignore this rule for constructor and private methods
62
+ if (node.kind === "constructor" ||
63
+ node.accessibility === "private" ||
64
+ (node.key.type === utils_1.AST_NODE_TYPES.Identifier && node.key.name === "constructor")) {
65
+ return;
66
+ }
67
+ // Check for use of `@TsRestHandler()` decorator and ensure it comes from `@ts-rest/nest` package
68
+ const decorators = node.decorators || [];
69
+ const hasMatchingDecorator = decorators.some((decorator) => decorator.expression.type === utils_1.AST_NODE_TYPES.CallExpression &&
70
+ decorator.expression.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
71
+ decorator.expression.callee.name === "TsRestHandler");
72
+ if (!hasMatchingDecorator) {
73
+ context.report({
74
+ node,
75
+ messageId: "missingDecorator",
76
+ data: {
77
+ name: symbolName,
78
+ },
79
+ });
80
+ }
81
+ decorators.forEach((decorator) => {
82
+ if (decorator.expression.type === utils_1.AST_NODE_TYPES.CallExpression &&
83
+ decorator.expression.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
84
+ decorator.expression.callee.name === "TsRestHandler" &&
85
+ !decoratorImportedCorrectly) {
86
+ context.report({
87
+ node: decorator,
88
+ messageId: "decoratorNotFromPackage",
89
+ });
90
+ }
91
+ });
92
+ // Check for returning the result of `tsRestHandler()` method (without any other statement present), and ensure it comes from `@ts-rest/nest` package
93
+ const body = node.value?.body;
94
+ if (!body ||
95
+ body.type !== utils_1.AST_NODE_TYPES.BlockStatement ||
96
+ body.body.length !== 1 ||
97
+ body.body[0]?.type !== utils_1.AST_NODE_TYPES.ReturnStatement) {
98
+ context.report({
99
+ node,
100
+ messageId: "missingReturn",
101
+ data: {
102
+ name: symbolName,
103
+ },
104
+ });
105
+ return;
106
+ }
107
+ const returnValueExpr = body.body[0].argument;
108
+ if (!returnValueExpr ||
109
+ returnValueExpr.type !== utils_1.AST_NODE_TYPES.CallExpression ||
110
+ returnValueExpr.callee.type !== utils_1.AST_NODE_TYPES.Identifier ||
111
+ returnValueExpr.callee.name !== "tsRestHandler") {
112
+ context.report({
113
+ node,
114
+ messageId: "missingReturn",
115
+ data: {
116
+ name: symbolName,
117
+ },
118
+ });
119
+ }
120
+ if (returnValueExpr &&
121
+ returnValueExpr?.type === utils_1.AST_NODE_TYPES.CallExpression &&
122
+ returnValueExpr.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
123
+ returnValueExpr.callee.name === "tsRestHandler" &&
124
+ !methodImportedCorrectly) {
125
+ context.report({
126
+ node: returnValueExpr,
127
+ messageId: "callNotFromPackage",
128
+ });
129
+ }
130
+ },
131
+ };
132
+ },
133
+ });
134
+ exports.default = rule;
135
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../../packages/eslint-plugin/src/lib/rules/enforce-ts-rest-in-controllers/index.ts"],"names":[],"mappings":"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"}