@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 +39 -0
- package/package.json +26 -0
- package/src/index.d.ts +3 -0
- package/src/index.js +9 -0
- package/src/index.js.map +1 -0
- package/src/lib/createRule.d.ts +3 -0
- package/src/lib/createRule.js +6 -0
- package/src/lib/createRule.js.map +1 -0
- package/src/lib/rules/enforce-ts-rest-in-controllers/index.d.ts +2 -0
- package/src/lib/rules/enforce-ts-rest-in-controllers/index.js +135 -0
- package/src/lib/rules/enforce-ts-rest-in-controllers/index.js.map +1 -0
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
|
package/src/index.js.map
ADDED
|
@@ -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,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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"}
|