@antithrow/eslint-plugin 1.2.0 → 1.2.1
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
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|

|
|
9
|
+
|
|
9
10
|
</div>
|
|
10
11
|
|
|
11
12
|
## Installation
|
|
@@ -50,8 +51,8 @@ export default [
|
|
|
50
51
|
|
|
51
52
|
## Rules
|
|
52
53
|
|
|
53
|
-
| Rule
|
|
54
|
-
|
|
|
55
|
-
| [`no-throwing-call`](
|
|
56
|
-
| [`no-unsafe-unwrap`](
|
|
57
|
-
| [`no-unused-result`](
|
|
54
|
+
| Rule | Description | Recommended |
|
|
55
|
+
| ------------------------------------------------------ | --------------------------------------------------------------------------- | ----------- |
|
|
56
|
+
| [`no-throwing-call`](https://antithrow.dev/docs/api/eslint-plugin/no-throwing-call) | Disallow calls to throwing built-in APIs with `@antithrow/std` replacements | `warn` |
|
|
57
|
+
| [`no-unsafe-unwrap`](https://antithrow.dev/docs/api/eslint-plugin/no-unsafe-unwrap) | Disallow `unwrap`/`expect` APIs on antithrow `Result` values | `warn` |
|
|
58
|
+
| [`no-unused-result`](https://antithrow.dev/docs/api/eslint-plugin/no-unused-result) | Require `Result` and `ResultAsync` values to be used | `error` |
|
package/dist/create-rule.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
-
export const createRule = ESLintUtils.RuleCreator((name) => `https://
|
|
2
|
+
export const createRule = ESLintUtils.RuleCreator((name) => `https://antithrow.dev/docs/api/eslint-plugin/${name}`);
|
|
@@ -5,7 +5,7 @@ import { createRule } from "../create-rule.js";
|
|
|
5
5
|
export const MessageId = {
|
|
6
6
|
THROWING_CALL: "throwingCall",
|
|
7
7
|
};
|
|
8
|
-
const
|
|
8
|
+
const BANNED_GLOBAL_CALLS = new Set([
|
|
9
9
|
"fetch",
|
|
10
10
|
"atob",
|
|
11
11
|
"btoa",
|
|
@@ -14,8 +14,9 @@ const GLOBAL_FUNCTIONS = new Set([
|
|
|
14
14
|
"decodeURIComponent",
|
|
15
15
|
"encodeURI",
|
|
16
16
|
"encodeURIComponent",
|
|
17
|
+
"JSON.parse",
|
|
18
|
+
"JSON.stringify",
|
|
17
19
|
]);
|
|
18
|
-
const JSON_METHODS = new Set(["parse", "stringify"]);
|
|
19
20
|
const RESPONSE_BODY_METHODS = new Set(["json", "text", "arrayBuffer", "blob", "formData"]);
|
|
20
21
|
const GLOBAL_OBJECTS = new Set(["globalThis", "window", "self"]);
|
|
21
22
|
const NULLISH_TYPE_FLAGS = ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.Void;
|
|
@@ -53,8 +54,63 @@ function isImplicitGlobal(name, scope) {
|
|
|
53
54
|
}
|
|
54
55
|
return true;
|
|
55
56
|
}
|
|
56
|
-
function
|
|
57
|
-
|
|
57
|
+
function collectStaticMemberPath(node) {
|
|
58
|
+
const propertyName = getStaticMemberName(node);
|
|
59
|
+
if (!propertyName) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
if (node.object.type === "Identifier") {
|
|
63
|
+
return {
|
|
64
|
+
segments: [node.object.name, propertyName],
|
|
65
|
+
rootIdentifier: node.object,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (node.object.type !== "MemberExpression") {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const parentPath = collectStaticMemberPath(node.object);
|
|
72
|
+
if (!parentPath) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
segments: [...parentPath.segments, propertyName],
|
|
77
|
+
rootIdentifier: parentPath.rootIdentifier,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function getStaticCalleePath(callee) {
|
|
81
|
+
if (callee.type === "Identifier") {
|
|
82
|
+
return {
|
|
83
|
+
segments: [callee.name],
|
|
84
|
+
rootIdentifier: callee,
|
|
85
|
+
memberExpression: null,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (callee.type !== "MemberExpression") {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const memberPath = collectStaticMemberPath(callee);
|
|
92
|
+
if (!memberPath) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
segments: memberPath.segments,
|
|
97
|
+
rootIdentifier: memberPath.rootIdentifier,
|
|
98
|
+
memberExpression: callee,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function normalizeGlobalPath(calleePath, scope) {
|
|
102
|
+
// Invariant: getStaticCalleePath always returns at least one segment.
|
|
103
|
+
const [root = "", ...rest] = calleePath.segments;
|
|
104
|
+
if (GLOBAL_OBJECTS.has(root)) {
|
|
105
|
+
if (!isImplicitGlobal(root, scope)) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return rest.length > 0 ? rest : null;
|
|
109
|
+
}
|
|
110
|
+
if (!isImplicitGlobal(root, scope)) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return calleePath.segments;
|
|
58
114
|
}
|
|
59
115
|
function containsGlobalResponseType(type, globalResponseType, checker) {
|
|
60
116
|
if (type.isUnionOrIntersection()) {
|
|
@@ -97,66 +153,30 @@ export const noThrowingCall = createRule({
|
|
|
97
153
|
: null;
|
|
98
154
|
return {
|
|
99
155
|
CallExpression(node) {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
if (callee.type === "Identifier" && GLOBAL_FUNCTIONS.has(callee.name)) {
|
|
103
|
-
const scope = context.sourceCode.getScope(callee);
|
|
104
|
-
if (isImplicitGlobal(callee.name, scope)) {
|
|
105
|
-
context.report({
|
|
106
|
-
node,
|
|
107
|
-
messageId: MessageId.THROWING_CALL,
|
|
108
|
-
data: { api: callee.name },
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (callee.type !== "MemberExpression") {
|
|
156
|
+
const calleePath = getStaticCalleePath(node.callee);
|
|
157
|
+
if (!calleePath) {
|
|
114
158
|
return;
|
|
115
159
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (GLOBAL_FUNCTIONS.has(methodName) &&
|
|
122
|
-
callee.object.type === "Identifier" &&
|
|
123
|
-
isImplicitGlobalObject(callee.object, context.sourceCode.getScope(callee.object))) {
|
|
124
|
-
context.report({
|
|
125
|
-
node,
|
|
126
|
-
messageId: MessageId.THROWING_CALL,
|
|
127
|
-
data: { api: methodName },
|
|
128
|
-
});
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
// JSON.parse(), JSON.stringify()
|
|
132
|
-
if (JSON_METHODS.has(methodName) &&
|
|
133
|
-
callee.object.type === "Identifier" &&
|
|
134
|
-
callee.object.name === "JSON") {
|
|
135
|
-
const scope = context.sourceCode.getScope(callee.object);
|
|
136
|
-
if (isImplicitGlobal("JSON", scope)) {
|
|
160
|
+
const rootScope = context.sourceCode.getScope(calleePath.rootIdentifier);
|
|
161
|
+
const normalizedGlobalPath = normalizeGlobalPath(calleePath, rootScope);
|
|
162
|
+
if (normalizedGlobalPath) {
|
|
163
|
+
const api = normalizedGlobalPath.join(".");
|
|
164
|
+
if (BANNED_GLOBAL_CALLS.has(api)) {
|
|
137
165
|
context.report({
|
|
138
166
|
node,
|
|
139
167
|
messageId: MessageId.THROWING_CALL,
|
|
140
|
-
data: { api
|
|
168
|
+
data: { api },
|
|
141
169
|
});
|
|
170
|
+
return;
|
|
142
171
|
}
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
// globalThis.JSON.parse(), window.JSON.stringify(), etc.
|
|
146
|
-
if (JSON_METHODS.has(methodName) &&
|
|
147
|
-
callee.object.type === "MemberExpression" &&
|
|
148
|
-
getStaticMemberName(callee.object) === "JSON" &&
|
|
149
|
-
callee.object.object.type === "Identifier" &&
|
|
150
|
-
isImplicitGlobalObject(callee.object.object, context.sourceCode.getScope(callee.object.object))) {
|
|
151
|
-
context.report({
|
|
152
|
-
node,
|
|
153
|
-
messageId: MessageId.THROWING_CALL,
|
|
154
|
-
data: { api: `JSON.${methodName}` },
|
|
155
|
-
});
|
|
156
|
-
return;
|
|
157
172
|
}
|
|
158
|
-
|
|
159
|
-
|
|
173
|
+
const methodName = calleePath.segments[calleePath.segments.length - 1];
|
|
174
|
+
const { memberExpression } = calleePath;
|
|
175
|
+
if (methodName &&
|
|
176
|
+
memberExpression &&
|
|
177
|
+
RESPONSE_BODY_METHODS.has(methodName) &&
|
|
178
|
+
globalResponseType) {
|
|
179
|
+
const tsNode = services.esTreeNodeToTSNodeMap.get(memberExpression.object);
|
|
160
180
|
const type = checker.getTypeAtLocation(tsNode);
|
|
161
181
|
if (containsGlobalResponseType(type, globalResponseType, checker)) {
|
|
162
182
|
context.report({
|
|
@@ -100,7 +100,7 @@ export const noUnsafeUnwrap = createRule({
|
|
|
100
100
|
return {
|
|
101
101
|
MemberExpression(node) {
|
|
102
102
|
const method = getStaticMemberName(node);
|
|
103
|
-
if (!method
|
|
103
|
+
if (!(method && BANNED_METHOD_NAMES.has(method))) {
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
106
106
|
const tsNode = services.esTreeNodeToTSNodeMap.get(node.object);
|
|
@@ -153,7 +153,7 @@ export const noUnsafeUnwrap = createRule({
|
|
|
153
153
|
return;
|
|
154
154
|
}
|
|
155
155
|
const method = getDestructuredPropertyName(node);
|
|
156
|
-
if (!method
|
|
156
|
+
if (!(method && BANNED_METHOD_NAMES.has(method))) {
|
|
157
157
|
return;
|
|
158
158
|
}
|
|
159
159
|
// Resolve the node whose type represents the value being destructured.
|
|
@@ -31,7 +31,7 @@ function collectResultTypes(type, collection) {
|
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
33
|
const symbol = type.getSymbol();
|
|
34
|
-
if (!symbol
|
|
34
|
+
if (!(symbol && isAntithrowResultTypeSymbol(symbol))) {
|
|
35
35
|
collection.hasNonResultMembers = true;
|
|
36
36
|
return;
|
|
37
37
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antithrow/eslint-plugin",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "ESLint plugin for antithrow Result types",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jack Weilage",
|
|
@@ -9,13 +9,20 @@
|
|
|
9
9
|
"url": "git+https://github.com/antithrow/antithrow.git",
|
|
10
10
|
"directory": "packages/eslint-plugin"
|
|
11
11
|
},
|
|
12
|
+
"homepage": "https://antithrow.dev",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/antithrow/antithrow/issues"
|
|
15
|
+
},
|
|
12
16
|
"keywords": [
|
|
17
|
+
"antithrow",
|
|
13
18
|
"eslint",
|
|
14
19
|
"eslintplugin",
|
|
20
|
+
"eslint-plugin",
|
|
15
21
|
"result",
|
|
16
22
|
"error-handling",
|
|
17
23
|
"typescript"
|
|
18
24
|
],
|
|
25
|
+
"sideEffects": false,
|
|
19
26
|
"type": "module",
|
|
20
27
|
"exports": {
|
|
21
28
|
".": {
|
|
@@ -41,7 +48,7 @@
|
|
|
41
48
|
"devDependencies": {
|
|
42
49
|
"@typescript-eslint/parser": "8.54.0",
|
|
43
50
|
"@typescript-eslint/rule-tester": "8.54.0",
|
|
44
|
-
"antithrow": "workspace
|
|
51
|
+
"antithrow": "workspace:^",
|
|
45
52
|
"eslint": "9.39.2",
|
|
46
53
|
"typescript": "5.9.3"
|
|
47
54
|
},
|