@pobammer-ts/eslint-cease-nonsense-rules 1.6.0 → 1.7.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 +594 -535
- package/dist/build-metadata.json +3 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1211 -716
- package/dist/index.js.map +33 -28
- package/dist/oxfmt-sync.d.ts +4 -0
- package/dist/oxfmt-sync.d.ts.map +1 -0
- package/dist/oxfmt-worker.d.ts +12 -0
- package/dist/oxfmt-worker.d.ts.map +1 -0
- package/dist/oxfmt-worker.js +172786 -0
- package/dist/oxfmt-worker.js.map +27 -0
- package/dist/recognizers/camel-case-detector.d.ts +2 -2
- package/dist/recognizers/camel-case-detector.d.ts.map +1 -0
- package/dist/recognizers/code-recognizer.d.ts +6 -6
- package/dist/recognizers/code-recognizer.d.ts.map +1 -0
- package/dist/recognizers/contains-detector.d.ts +2 -2
- package/dist/recognizers/contains-detector.d.ts.map +1 -0
- package/dist/recognizers/detector.d.ts +3 -4
- package/dist/recognizers/detector.d.ts.map +1 -0
- package/dist/recognizers/end-with-detector.d.ts +2 -2
- package/dist/recognizers/end-with-detector.d.ts.map +1 -0
- package/dist/recognizers/javascript-footprint.d.ts +2 -2
- package/dist/recognizers/javascript-footprint.d.ts.map +1 -0
- package/dist/recognizers/keywords-detector.d.ts +2 -2
- package/dist/recognizers/keywords-detector.d.ts.map +1 -0
- package/dist/rules/ban-instances.d.ts +1 -0
- package/dist/rules/ban-instances.d.ts.map +1 -0
- package/dist/rules/ban-react-fc.d.ts +1 -0
- package/dist/rules/ban-react-fc.d.ts.map +1 -0
- package/dist/rules/enforce-ianitor-check-type.d.ts +2 -1
- package/dist/rules/enforce-ianitor-check-type.d.ts.map +1 -0
- package/dist/rules/fast-format.d.ts +36 -0
- package/dist/rules/fast-format.d.ts.map +1 -0
- package/dist/rules/no-async-constructor.d.ts +1 -0
- package/dist/rules/no-async-constructor.d.ts.map +1 -0
- package/dist/rules/no-color3-constructor.d.ts +1 -0
- package/dist/rules/no-color3-constructor.d.ts.map +1 -0
- package/dist/rules/no-commented-code.d.ts +1 -0
- package/dist/rules/no-commented-code.d.ts.map +1 -0
- package/dist/rules/no-god-components.d.ts +1 -0
- package/dist/rules/no-god-components.d.ts.map +1 -0
- package/dist/rules/no-identity-map.d.ts +9 -0
- package/dist/rules/no-identity-map.d.ts.map +1 -0
- package/dist/rules/no-instance-methods-without-this.d.ts +1 -0
- package/dist/rules/no-instance-methods-without-this.d.ts.map +1 -0
- package/dist/rules/no-print.d.ts +1 -0
- package/dist/rules/no-print.d.ts.map +1 -0
- package/dist/rules/no-shorthand-names.d.ts +1 -0
- package/dist/rules/no-shorthand-names.d.ts.map +1 -0
- package/dist/rules/no-useless-use-spring.d.ts +1 -0
- package/dist/rules/no-useless-use-spring.d.ts.map +1 -0
- package/dist/rules/no-warn.d.ts +1 -0
- package/dist/rules/no-warn.d.ts.map +1 -0
- package/dist/rules/prefer-sequence-overloads.d.ts +1 -0
- package/dist/rules/prefer-sequence-overloads.d.ts.map +1 -0
- package/dist/rules/prefer-udim2-shorthand.d.ts +1 -0
- package/dist/rules/prefer-udim2-shorthand.d.ts.map +1 -0
- package/dist/rules/require-named-effect-functions.d.ts +4 -3
- package/dist/rules/require-named-effect-functions.d.ts.map +1 -0
- package/dist/rules/require-paired-calls.d.ts +1 -0
- package/dist/rules/require-paired-calls.d.ts.map +1 -0
- package/dist/rules/require-react-component-keys.d.ts +1 -0
- package/dist/rules/require-react-component-keys.d.ts.map +1 -0
- package/dist/rules/use-exhaustive-dependencies.d.ts +1 -0
- package/dist/rules/use-exhaustive-dependencies.d.ts.map +1 -0
- package/dist/rules/use-hook-at-top-level.d.ts +1 -0
- package/dist/rules/use-hook-at-top-level.d.ts.map +1 -0
- package/dist/types/oxfmt.d.ts +88 -0
- package/dist/types/oxfmt.d.ts.map +1 -0
- package/dist/{configure-utilities.d.ts → utilities/configure-utilities.d.ts} +12 -11
- package/dist/utilities/configure-utilities.d.ts.map +1 -0
- package/dist/utilities/error-utilities.d.ts +25 -0
- package/dist/utilities/error-utilities.d.ts.map +1 -0
- package/dist/utilities/format-utilities.d.ts +20 -0
- package/dist/utilities/format-utilities.d.ts.map +1 -0
- package/dist/utilities/typebox-utilities.d.ts +4 -0
- package/dist/utilities/typebox-utilities.d.ts.map +1 -0
- package/package.json +23 -10
package/README.md
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
# eslint-cease-nonsense-rules
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An ESLint plugin that catches common mistakes before they reach production. This collection of rules helps prevent patterns that lead to bugs, performance issues, and maintainability problems.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Usage](#usage)
|
|
9
|
+
- [Rules](#rules)
|
|
10
|
+
- [Type Safety](#type-safety)
|
|
11
|
+
- [React](#react)
|
|
12
|
+
- [Logging](#logging)
|
|
13
|
+
- [Resource Management](#resource-management)
|
|
14
|
+
- [Code Quality](#code-quality)
|
|
15
|
+
- [Performance](#performance)
|
|
16
|
+
- [License](#license)
|
|
6
17
|
|
|
7
18
|
## Installation
|
|
8
19
|
|
|
@@ -12,42 +23,50 @@ bun add -D @pobammer-ts/eslint-cease-nonsense-rules
|
|
|
12
23
|
|
|
13
24
|
## Usage
|
|
14
25
|
|
|
15
|
-
|
|
26
|
+
### Basic Setup
|
|
27
|
+
|
|
28
|
+
Add the plugin to your ESLint configuration:
|
|
16
29
|
|
|
17
30
|
```typescript
|
|
18
31
|
import ceaseNonsense from "@pobammer-ts/eslint-cease-nonsense-rules";
|
|
19
32
|
|
|
20
33
|
export default [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
{
|
|
35
|
+
plugins: {
|
|
36
|
+
"cease-nonsense": ceaseNonsense,
|
|
37
|
+
},
|
|
38
|
+
rules: {
|
|
39
|
+
// Enable all rules (recommended)
|
|
40
|
+
"cease-nonsense/ban-instances": "error",
|
|
41
|
+
"cease-nonsense/ban-react-fc": "error",
|
|
42
|
+
"cease-nonsense/enforce-ianitor-check-type": "error",
|
|
43
|
+
"cease-nonsense/fast-format": "error",
|
|
44
|
+
"cease-nonsense/no-async-constructor": "error",
|
|
45
|
+
"cease-nonsense/no-color3-constructor": "error",
|
|
46
|
+
"cease-nonsense/no-commented-code": "error",
|
|
47
|
+
"cease-nonsense/no-god-components": "error",
|
|
48
|
+
"cease-nonsense/no-identity-map": "error",
|
|
49
|
+
"cease-nonsense/no-instance-methods-without-this": "error",
|
|
50
|
+
"cease-nonsense/no-print": "error",
|
|
51
|
+
"cease-nonsense/no-shorthand-names": "error",
|
|
52
|
+
"cease-nonsense/no-useless-use-spring": "error",
|
|
53
|
+
"cease-nonsense/no-warn": "error",
|
|
54
|
+
"cease-nonsense/prefer-sequence-overloads": "error",
|
|
55
|
+
"cease-nonsense/prefer-udim2-shorthand": "error",
|
|
56
|
+
"cease-nonsense/require-named-effect-functions": "error",
|
|
57
|
+
"cease-nonsense/require-paired-calls": "error",
|
|
58
|
+
"cease-nonsense/require-react-component-keys": "error",
|
|
59
|
+
"cease-nonsense/use-exhaustive-dependencies": "error",
|
|
60
|
+
"cease-nonsense/use-hook-at-top-level": "error",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
45
63
|
];
|
|
64
|
+
```
|
|
46
65
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
];
|
|
66
|
+
### Using the Preset
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
export default [ceaseNonsense.configs.recommended];
|
|
51
70
|
```
|
|
52
71
|
|
|
53
72
|
## Rules
|
|
@@ -56,36 +75,50 @@ export default [
|
|
|
56
75
|
|
|
57
76
|
#### `enforce-ianitor-check-type`
|
|
58
77
|
|
|
59
|
-
Enforces `Ianitor.Check<T>` type annotations on complex TypeScript types to ensure runtime validation.
|
|
78
|
+
Enforces `Ianitor.Check<T>` type annotations on complex TypeScript types to ensure runtime validation.
|
|
60
79
|
|
|
61
80
|
Calculates structural complexity of types and requires Ianitor validators when complexity exceeds thresholds.
|
|
62
81
|
|
|
63
|
-
|
|
82
|
+
**Configuration**
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
{
|
|
86
|
+
"cease-nonsense/enforce-ianitor-check-type": ["error", {
|
|
87
|
+
"baseThreshold": 10, // Minimum complexity to require validation
|
|
88
|
+
"warnThreshold": 15, // Warning threshold
|
|
89
|
+
"errorThreshold": 25, // Error threshold
|
|
90
|
+
"interfacePenalty": 20, // Complexity penalty for interfaces
|
|
91
|
+
"performanceMode": true // Enable performance optimizations
|
|
92
|
+
}]
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**❌ Bad**
|
|
64
97
|
|
|
65
98
|
```typescript
|
|
66
99
|
// Complex type without runtime validation
|
|
67
100
|
type UserConfig = {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
101
|
+
id: number;
|
|
102
|
+
name: string;
|
|
103
|
+
settings: {
|
|
104
|
+
theme: string;
|
|
105
|
+
notifications: boolean;
|
|
106
|
+
};
|
|
74
107
|
};
|
|
75
108
|
|
|
76
109
|
const config = getUserConfig(); // No runtime check!
|
|
77
110
|
```
|
|
78
111
|
|
|
79
|
-
**✅ Good
|
|
112
|
+
**✅ Good**
|
|
80
113
|
|
|
81
114
|
```typescript
|
|
82
115
|
const userConfigValidator = Ianitor.interface({
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
116
|
+
id: Ianitor.number(),
|
|
117
|
+
name: Ianitor.string(),
|
|
118
|
+
settings: Ianitor.interface({
|
|
119
|
+
theme: Ianitor.string(),
|
|
120
|
+
notifications: Ianitor.boolean(),
|
|
121
|
+
}),
|
|
89
122
|
});
|
|
90
123
|
|
|
91
124
|
type UserConfig = Ianitor.Static<typeof userConfigValidator>;
|
|
@@ -93,58 +126,15 @@ type UserConfig = Ianitor.Static<typeof userConfigValidator>;
|
|
|
93
126
|
const config = userConfigValidator.check(getUserConfig());
|
|
94
127
|
```
|
|
95
128
|
|
|
96
|
-
**Configuration:**
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
{
|
|
100
|
-
"cease-nonsense/enforce-ianitor-check-type": ["error", {
|
|
101
|
-
"baseThreshold": 10, // Minimum complexity to require validation
|
|
102
|
-
"warnThreshold": 15, // Warning threshold
|
|
103
|
-
"errorThreshold": 25, // Error threshold
|
|
104
|
-
"interfacePenalty": 20, // Complexity penalty for interfaces
|
|
105
|
-
"performanceMode": true // Enable performance optimizations
|
|
106
|
-
}]
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
129
|
### React
|
|
111
130
|
|
|
112
131
|
#### `ban-react-fc`
|
|
113
132
|
|
|
114
133
|
Bans React.FC and similar component type annotations. Use explicit function declarations instead.
|
|
115
134
|
|
|
116
|
-
React.FC
|
|
117
|
-
|
|
118
|
-
#### `no-god-components`
|
|
119
|
-
|
|
120
|
-
Flags React components that are too large or doing too much, pushing you toward extracting hooks/components/utilities.
|
|
121
|
-
|
|
122
|
-
Checks (defaults):
|
|
123
|
-
|
|
124
|
-
- Component body line count: target `120`, hard max `200`
|
|
125
|
-
- TSX nesting depth ≤ `3`
|
|
126
|
-
- Stateful hooks ≤ `5` (counts `useState`, `useReducer`, `useBinding` by default)
|
|
127
|
-
- Destructured props in parameters ≤ `5`
|
|
128
|
-
- Runtime `null` literals are always banned
|
|
129
|
-
|
|
130
|
-
**Configuration:**
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
{
|
|
134
|
-
"cease-nonsense/no-god-components": ["error", {
|
|
135
|
-
targetLines: 120,
|
|
136
|
-
maxLines: 200,
|
|
137
|
-
maxTsxNesting: 3,
|
|
138
|
-
maxStateHooks: 5,
|
|
139
|
-
stateHooks: ["useState", "useReducer", "useBinding"],
|
|
140
|
-
maxDestructuredProps: 5,
|
|
141
|
-
enforceTargetLines: true,
|
|
142
|
-
ignoreComponents: ["LegacyComponent"]
|
|
143
|
-
}]
|
|
144
|
-
}
|
|
145
|
-
```
|
|
135
|
+
React.FC types break debug information in React DevTools and encourage poor patterns.
|
|
146
136
|
|
|
147
|
-
**❌ Bad
|
|
137
|
+
**❌ Bad**
|
|
148
138
|
|
|
149
139
|
```typescript
|
|
150
140
|
export const MyComponent: React.FC<Props> = ({ children }) => {
|
|
@@ -158,7 +148,7 @@ const Modal: React.FunctionComponent = () => <div>Modal</div>;
|
|
|
158
148
|
const Input: VFC = () => <input />;
|
|
159
149
|
```
|
|
160
150
|
|
|
161
|
-
**✅ Good
|
|
151
|
+
**✅ Good**
|
|
162
152
|
|
|
163
153
|
```typescript
|
|
164
154
|
export function MyComponent({ children }: Props) {
|
|
@@ -178,11 +168,51 @@ function Input() {
|
|
|
178
168
|
}
|
|
179
169
|
```
|
|
180
170
|
|
|
171
|
+
#### `no-god-components`
|
|
172
|
+
|
|
173
|
+
Flags React components that are too large or doing too much, encouraging better separation of concerns.
|
|
174
|
+
|
|
175
|
+
**Default thresholds**
|
|
176
|
+
|
|
177
|
+
- Component body line count: target `120`, hard max `200`
|
|
178
|
+
- TSX nesting depth ≤ `3`
|
|
179
|
+
- Stateful hooks ≤ `5`
|
|
180
|
+
- Destructured props in parameters ≤ `5`
|
|
181
|
+
- Runtime `null` literals are always banned
|
|
182
|
+
|
|
183
|
+
**Configuration**
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
{
|
|
187
|
+
"cease-nonsense/no-god-components": ["error", {
|
|
188
|
+
targetLines: 120,
|
|
189
|
+
maxLines: 200,
|
|
190
|
+
maxTsxNesting: 3,
|
|
191
|
+
maxStateHooks: 5,
|
|
192
|
+
stateHooks: ["useState", "useReducer", "useBinding"],
|
|
193
|
+
maxDestructuredProps: 5,
|
|
194
|
+
enforceTargetLines: true,
|
|
195
|
+
ignoreComponents: ["LegacyComponent"]
|
|
196
|
+
}]
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
181
200
|
#### `require-react-component-keys`
|
|
182
201
|
|
|
183
202
|
Enforces key props on all React elements except top-level returns from components.
|
|
184
203
|
|
|
185
|
-
|
|
204
|
+
**Configuration**
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
{
|
|
208
|
+
"cease-nonsense/require-react-component-keys": ["error", {
|
|
209
|
+
"allowRootKeys": false, // Allow keys on root returns
|
|
210
|
+
"ignoreCallExpressions": ["ReactTree.mount"] // Functions to ignore
|
|
211
|
+
}]
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**❌ Bad**
|
|
186
216
|
|
|
187
217
|
```typescript
|
|
188
218
|
function UserList({ users }) {
|
|
@@ -196,115 +226,127 @@ function UserList({ users }) {
|
|
|
196
226
|
}
|
|
197
227
|
```
|
|
198
228
|
|
|
229
|
+
**✅ Good**
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
function UserList({ users }) {
|
|
233
|
+
return (
|
|
234
|
+
<div>
|
|
235
|
+
{users.map(user => (
|
|
236
|
+
<UserCard key={user.id} user={user} />
|
|
237
|
+
))}
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
199
243
|
#### `require-named-effect-functions`
|
|
200
244
|
|
|
201
245
|
Enforce named effect functions for better debuggability. Prevent inline arrow functions in `useEffect` and similar hooks.
|
|
202
246
|
|
|
203
|
-
Behavior by environment
|
|
247
|
+
**Behavior by environment**
|
|
248
|
+
|
|
249
|
+
- `roblox-ts` (default): Only identifiers allowed (e.g., `useEffect(onTick, [...])`)
|
|
250
|
+
- `standard`: Identifiers and named function expressions allowed
|
|
204
251
|
|
|
205
|
-
|
|
206
|
-
- `standard`: identifiers and named function expressions are allowed; arrows are still reported.
|
|
252
|
+
**Configuration**
|
|
207
253
|
|
|
208
|
-
|
|
254
|
+
```typescript
|
|
255
|
+
{
|
|
256
|
+
"cease-nonsense/require-named-effect-functions": ["error", {
|
|
257
|
+
"environment": "roblox-ts", // or "standard"
|
|
258
|
+
"hooks": ["useEffect", "useLayoutEffect", "useInsertionEffect"]
|
|
259
|
+
}]
|
|
260
|
+
}
|
|
261
|
+
```
|
|
209
262
|
|
|
210
|
-
**❌ Bad
|
|
263
|
+
**❌ Bad**
|
|
211
264
|
|
|
212
265
|
```typescript
|
|
213
266
|
// Arrow function
|
|
214
267
|
useEffect(() => {
|
|
215
|
-
|
|
268
|
+
doThing();
|
|
216
269
|
}, [dep]);
|
|
217
270
|
|
|
218
271
|
// Anonymous function expression
|
|
219
|
-
useEffect(
|
|
220
|
-
|
|
221
|
-
|
|
272
|
+
useEffect(
|
|
273
|
+
function () {
|
|
274
|
+
doThing();
|
|
275
|
+
},
|
|
276
|
+
[dep],
|
|
277
|
+
);
|
|
222
278
|
```
|
|
223
279
|
|
|
224
|
-
**✅ Good
|
|
280
|
+
**✅ Good**
|
|
225
281
|
|
|
226
282
|
```typescript
|
|
227
283
|
// Preferred: reference a named function
|
|
228
284
|
function onDepChange() {
|
|
229
|
-
|
|
285
|
+
doThing();
|
|
230
286
|
}
|
|
231
287
|
useEffect(onDepChange, [dep]);
|
|
232
288
|
|
|
233
289
|
// Allowed in `standard` mode
|
|
234
|
-
useEffect(
|
|
235
|
-
|
|
236
|
-
|
|
290
|
+
useEffect(
|
|
291
|
+
function onDepChange() {
|
|
292
|
+
doThing();
|
|
293
|
+
},
|
|
294
|
+
[dep],
|
|
295
|
+
);
|
|
237
296
|
```
|
|
238
297
|
|
|
239
|
-
|
|
298
|
+
#### `use-exhaustive-dependencies`
|
|
299
|
+
|
|
300
|
+
Enforces exhaustive and correct dependency specification in React hooks to prevent stale closures and unnecessary re-renders.
|
|
301
|
+
|
|
302
|
+
**Configuration**
|
|
240
303
|
|
|
241
304
|
```typescript
|
|
242
305
|
{
|
|
243
|
-
"cease-nonsense/
|
|
244
|
-
"
|
|
245
|
-
"
|
|
306
|
+
"cease-nonsense/use-exhaustive-dependencies": ["error", {
|
|
307
|
+
"reportMissingDependenciesArray": true,
|
|
308
|
+
"reportUnnecessaryDependencies": true,
|
|
309
|
+
"hooks": [
|
|
310
|
+
{
|
|
311
|
+
"name": "useCustomHook",
|
|
312
|
+
"closureIndex": 0,
|
|
313
|
+
"dependenciesIndex": 1,
|
|
314
|
+
"stableResult": true
|
|
315
|
+
}
|
|
316
|
+
]
|
|
246
317
|
}]
|
|
247
318
|
}
|
|
248
319
|
```
|
|
249
320
|
|
|
250
|
-
|
|
321
|
+
**❌ Bad**
|
|
251
322
|
|
|
252
323
|
```typescript
|
|
253
|
-
function
|
|
254
|
-
|
|
255
|
-
<div>
|
|
256
|
-
{users.map(user => (
|
|
257
|
-
<UserCard key={user.id} user={user} />
|
|
258
|
-
))}
|
|
259
|
-
</div>
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
**Configuration:**
|
|
324
|
+
function UserProfile({ userId }) {
|
|
325
|
+
const [user, setUser] = useState(null);
|
|
265
326
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
"allowRootKeys": false, // Allow keys on root returns
|
|
270
|
-
"ignoreCallExpressions": ["ReactTree.mount"] // Functions to ignore
|
|
271
|
-
}]
|
|
327
|
+
useEffect(() => {
|
|
328
|
+
fetchUser(userId).then(setUser);
|
|
329
|
+
}, []); // Missing userId dependency!
|
|
272
330
|
}
|
|
273
331
|
```
|
|
274
332
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
Enforces exhaustive and correct dependency specification in React hooks to prevent stale closures and unnecessary re-renders.
|
|
278
|
-
|
|
279
|
-
**❌ Bad:**
|
|
333
|
+
**✅ Good**
|
|
280
334
|
|
|
281
335
|
```typescript
|
|
282
336
|
function UserProfile({ userId }) {
|
|
283
|
-
|
|
337
|
+
const [user, setUser] = useState(null);
|
|
284
338
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
339
|
+
useEffect(() => {
|
|
340
|
+
fetchUser(userId).then(setUser);
|
|
341
|
+
}, [userId]);
|
|
288
342
|
}
|
|
289
343
|
```
|
|
290
344
|
|
|
291
345
|
#### `no-useless-use-spring`
|
|
292
346
|
|
|
293
|
-
Flags `useSpring`-style hooks that never change (static config plus non-updating deps).
|
|
347
|
+
Flags `useSpring`-style hooks that never change (static config plus non-updating deps).
|
|
294
348
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
```typescript
|
|
298
|
-
const spring = useSpring({ opacity: 1 }, []);
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
**✅ Good:**
|
|
302
|
-
|
|
303
|
-
```typescript
|
|
304
|
-
const spring = useSpring({ opacity: isOpen ? 1 : 0 }, [isOpen]);
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
**Configuration:**
|
|
349
|
+
**Configuration**
|
|
308
350
|
|
|
309
351
|
```typescript
|
|
310
352
|
{
|
|
@@ -315,112 +357,64 @@ const spring = useSpring({ opacity: isOpen ? 1 : 0 }, [isOpen]);
|
|
|
315
357
|
}
|
|
316
358
|
```
|
|
317
359
|
|
|
318
|
-
|
|
360
|
+
**❌ Bad**
|
|
319
361
|
|
|
320
362
|
```typescript
|
|
321
|
-
|
|
322
|
-
const [user, setUser] = useState(null);
|
|
323
|
-
|
|
324
|
-
useEffect(() => {
|
|
325
|
-
fetchUser(userId).then(setUser);
|
|
326
|
-
}, [userId]);
|
|
327
|
-
}
|
|
363
|
+
const spring = useSpring({ opacity: 1 }, []);
|
|
328
364
|
```
|
|
329
365
|
|
|
330
|
-
**
|
|
366
|
+
**✅ Good**
|
|
331
367
|
|
|
332
368
|
```typescript
|
|
333
|
-
{
|
|
334
|
-
"cease-nonsense/use-exhaustive-dependencies": ["error", {
|
|
335
|
-
"reportMissingDependenciesArray": true,
|
|
336
|
-
"reportUnnecessaryDependencies": true,
|
|
337
|
-
"hooks": [
|
|
338
|
-
{
|
|
339
|
-
"name": "useCustomHook",
|
|
340
|
-
"closureIndex": 0,
|
|
341
|
-
"dependenciesIndex": 1,
|
|
342
|
-
"stableResult": true
|
|
343
|
-
}
|
|
344
|
-
]
|
|
345
|
-
}]
|
|
346
|
-
}
|
|
369
|
+
const spring = useSpring({ opacity: isOpen ? 1 : 0 }, [isOpen]);
|
|
347
370
|
```
|
|
348
371
|
|
|
349
372
|
#### `use-hook-at-top-level`
|
|
350
373
|
|
|
351
374
|
Enforces that React hooks are only called at the top level of components or custom hooks, never conditionally or in nested functions.
|
|
352
375
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
```typescript
|
|
356
|
-
function UserProfile({ userId }) {
|
|
357
|
-
if (userId) {
|
|
358
|
-
useEffect(() => { // Hook in conditional!
|
|
359
|
-
fetchUser(userId);
|
|
360
|
-
}, [userId]);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
**✅ Good:**
|
|
366
|
-
|
|
367
|
-
```typescript
|
|
368
|
-
function UserProfile({ userId }) {
|
|
369
|
-
useEffect(() => {
|
|
370
|
-
if (userId) {
|
|
371
|
-
fetchUser(userId);
|
|
372
|
-
}
|
|
373
|
-
}, [userId]);
|
|
374
|
-
}
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
**Configuration:**
|
|
376
|
+
**Configuration**
|
|
378
377
|
|
|
379
378
|
```typescript
|
|
380
379
|
{
|
|
381
380
|
"cease-nonsense/use-hook-at-top-level": ["error", {
|
|
382
381
|
// Strategy 1: Ignore hooks by name
|
|
383
382
|
"ignoreHooks": ["useEntity", "useComponent"],
|
|
384
|
-
|
|
385
|
-
// Strategy 2: Control by import source
|
|
383
|
+
|
|
384
|
+
// Strategy 2: Control by import source
|
|
386
385
|
"importSources": {
|
|
387
386
|
"react": true, // Check hooks from React
|
|
388
387
|
"my-ecs-library": false // Ignore ECS hooks
|
|
389
388
|
},
|
|
390
|
-
|
|
391
|
-
// Strategy 3: Whitelist mode
|
|
389
|
+
|
|
390
|
+
// Strategy 3: Whitelist mode
|
|
392
391
|
"onlyHooks": ["useState", "useEffect", "useContext"]
|
|
393
392
|
}]
|
|
394
393
|
}
|
|
395
394
|
```
|
|
396
395
|
|
|
397
|
-
**
|
|
396
|
+
**❌ Bad**
|
|
398
397
|
|
|
399
398
|
```typescript
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
399
|
+
function UserProfile({ userId }) {
|
|
400
|
+
if (userId) {
|
|
401
|
+
useEffect(() => {
|
|
402
|
+
// Hook in conditional!
|
|
403
|
+
fetchUser(userId);
|
|
404
|
+
}, [userId]);
|
|
405
|
+
}
|
|
406
406
|
}
|
|
407
|
+
```
|
|
407
408
|
|
|
408
|
-
|
|
409
|
-
import { useState } from 'react';
|
|
410
|
-
import { useState as useEcsState } from 'my-ecs';
|
|
411
|
-
function Component() {
|
|
412
|
-
if (condition) {
|
|
413
|
-
useState(0); // ❌ Error - React hook checked
|
|
414
|
-
useEcsState(0); // ✅ Ignored - ECS hook ignored
|
|
415
|
-
}
|
|
416
|
-
}
|
|
409
|
+
**✅ Good**
|
|
417
410
|
|
|
418
|
-
|
|
419
|
-
function
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
411
|
+
```typescript
|
|
412
|
+
function UserProfile({ userId }) {
|
|
413
|
+
useEffect(() => {
|
|
414
|
+
if (userId) {
|
|
415
|
+
fetchUser(userId);
|
|
416
|
+
}
|
|
417
|
+
}, [userId]);
|
|
424
418
|
}
|
|
425
419
|
```
|
|
426
420
|
|
|
@@ -430,13 +424,13 @@ function Component() {
|
|
|
430
424
|
|
|
431
425
|
Bans use of `print()` function calls. Use `Log` instead.
|
|
432
426
|
|
|
433
|
-
**❌ Bad
|
|
427
|
+
**❌ Bad**
|
|
434
428
|
|
|
435
429
|
```typescript
|
|
436
430
|
print("Debug message");
|
|
437
431
|
```
|
|
438
432
|
|
|
439
|
-
**✅ Good
|
|
433
|
+
**✅ Good**
|
|
440
434
|
|
|
441
435
|
```typescript
|
|
442
436
|
Log.info("Debug message");
|
|
@@ -446,13 +440,13 @@ Log.info("Debug message");
|
|
|
446
440
|
|
|
447
441
|
Bans use of `warn()` function calls. Use `Log` instead.
|
|
448
442
|
|
|
449
|
-
**❌ Bad
|
|
443
|
+
**❌ Bad**
|
|
450
444
|
|
|
451
445
|
```typescript
|
|
452
446
|
warn("Warning message");
|
|
453
447
|
```
|
|
454
448
|
|
|
455
|
-
**✅ Good
|
|
449
|
+
**✅ Good**
|
|
456
450
|
|
|
457
451
|
```typescript
|
|
458
452
|
Log.warn("Warning message");
|
|
@@ -462,311 +456,411 @@ Log.warn("Warning message");
|
|
|
462
456
|
|
|
463
457
|
#### `require-paired-calls`
|
|
464
458
|
|
|
465
|
-
Enforces that paired function calls (opener/closer) are properly balanced across all execution paths with LIFO ordering.
|
|
466
|
-
|
|
467
|
-
**Key features:**
|
|
459
|
+
Enforces that paired function calls (opener/closer) are properly balanced across all execution paths with LIFO ordering.
|
|
468
460
|
|
|
469
|
-
|
|
470
|
-
- Early exit detection (return, throw, break, continue)
|
|
471
|
-
- LIFO validation for nested pairs
|
|
472
|
-
- Async operation detection (await/yield) with `requireSync`
|
|
473
|
-
- Roblox-specific auto-close detection for yielding functions
|
|
474
|
-
|
|
475
|
-
**❌ Bad:**
|
|
461
|
+
**Configuration**
|
|
476
462
|
|
|
477
463
|
```typescript
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
464
|
+
{
|
|
465
|
+
"cease-nonsense/require-paired-calls": ["error", {
|
|
466
|
+
"pairs": [{
|
|
467
|
+
"opener": "debug.profilebegin",
|
|
468
|
+
"closer": "debug.profileend",
|
|
469
|
+
"alternatives": ["db.rollback"],
|
|
470
|
+
"requireSync": true,
|
|
471
|
+
"platform": "roblox",
|
|
472
|
+
"yieldingFunctions": [
|
|
473
|
+
"task.wait",
|
|
474
|
+
"*.WaitForChild"
|
|
475
|
+
]
|
|
476
|
+
}],
|
|
477
|
+
"allowConditionalClosers": false,
|
|
478
|
+
"allowMultipleOpeners": true,
|
|
479
|
+
"maxNestingDepth": 0
|
|
480
|
+
}]
|
|
483
481
|
}
|
|
482
|
+
```
|
|
484
483
|
|
|
485
|
-
|
|
486
|
-
function test() {
|
|
487
|
-
doWork();
|
|
488
|
-
debug.profileend(); // No matching profilebegin
|
|
489
|
-
}
|
|
484
|
+
**Pair configuration options**
|
|
490
485
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
486
|
+
- `opener` (required): Function name that starts the paired operation
|
|
487
|
+
- `openerAlternatives` (optional): Additional opener names sharing the same closer
|
|
488
|
+
- `closer` (required): Function name(s) that close the operation
|
|
489
|
+
- `alternatives` (optional): Alternative closers for error paths
|
|
490
|
+
- `requireSync` (optional): Disallow `await`/`yield` between opener and closer
|
|
491
|
+
- `platform` (optional): Enables `"roblox"`-specific behavior
|
|
492
|
+
- `yieldingFunctions` (optional): Custom patterns for Roblox yielding functions (supports wildcards)
|
|
498
493
|
|
|
499
|
-
|
|
500
|
-
async function test() {
|
|
501
|
-
debug.profilebegin("task");
|
|
502
|
-
await fetch("/api"); // Cannot await between profilebegin/end
|
|
503
|
-
debug.profileend();
|
|
504
|
-
}
|
|
494
|
+
**Top-level options**
|
|
505
495
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
debug.profileend(); // This will error - already closed
|
|
511
|
-
}
|
|
496
|
+
- `pairs` (required): Array of pair configurations
|
|
497
|
+
- `allowConditionalClosers` (optional): Allow closers in some but not all branches
|
|
498
|
+
- `allowMultipleOpeners` (optional): Allow consecutive opener calls
|
|
499
|
+
- `maxNestingDepth` (optional): Maximum nesting depth (0 = unlimited)
|
|
512
500
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
501
|
+
**Default configuration (Roblox profiling)**
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
{
|
|
505
|
+
"cease-nonsense/require-paired-calls": ["error", {
|
|
506
|
+
"pairs": [{
|
|
507
|
+
"opener": "debug.profilebegin",
|
|
508
|
+
"closer": "debug.profileend",
|
|
509
|
+
"platform": "roblox",
|
|
510
|
+
"requireSync": true,
|
|
511
|
+
"yieldingFunctions": ["task.wait", "wait", "*.WaitForChild"]
|
|
512
|
+
}]
|
|
513
|
+
}]
|
|
521
514
|
}
|
|
515
|
+
```
|
|
522
516
|
|
|
523
|
-
|
|
517
|
+
**❌ Bad**
|
|
518
|
+
|
|
519
|
+
```typescript
|
|
520
|
+
// Missing closer on early return
|
|
524
521
|
function test() {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
debug.profileend();
|
|
522
|
+
debug.profilebegin("task");
|
|
523
|
+
if (error) return; // Never closed on this path
|
|
524
|
+
debug.profileend();
|
|
530
525
|
}
|
|
531
526
|
|
|
532
|
-
//
|
|
527
|
+
// Wrong LIFO order
|
|
533
528
|
function test() {
|
|
534
|
-
|
|
529
|
+
debug.profilebegin("outer");
|
|
530
|
+
debug.profilebegin("inner");
|
|
531
|
+
debug.profileend(); // closes inner
|
|
532
|
+
// outer is never closed
|
|
535
533
|
}
|
|
536
534
|
|
|
537
|
-
//
|
|
538
|
-
function test() {
|
|
539
|
-
|
|
540
|
-
|
|
535
|
+
// Async operation with requireSync
|
|
536
|
+
async function test() {
|
|
537
|
+
debug.profilebegin("task");
|
|
538
|
+
await fetch("/api");
|
|
539
|
+
debug.profileend();
|
|
541
540
|
}
|
|
542
541
|
```
|
|
543
542
|
|
|
544
|
-
**✅ Good
|
|
543
|
+
**✅ Good**
|
|
545
544
|
|
|
546
545
|
```typescript
|
|
547
546
|
// Simple pairing
|
|
548
547
|
function test() {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
548
|
+
debug.profilebegin("task");
|
|
549
|
+
doWork();
|
|
550
|
+
debug.profileend();
|
|
552
551
|
}
|
|
553
552
|
|
|
554
553
|
// Proper LIFO nesting
|
|
555
554
|
function test() {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
555
|
+
debug.profilebegin("outer");
|
|
556
|
+
debug.profilebegin("inner");
|
|
557
|
+
debug.profileend();
|
|
558
|
+
debug.profileend();
|
|
560
559
|
}
|
|
561
560
|
|
|
562
561
|
// Try-finally ensures closer on all paths
|
|
563
562
|
function test() {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
563
|
+
debug.profilebegin("task");
|
|
564
|
+
try {
|
|
565
|
+
riskyOperation();
|
|
566
|
+
} finally {
|
|
567
|
+
debug.profileend();
|
|
568
|
+
}
|
|
570
569
|
}
|
|
570
|
+
```
|
|
571
571
|
|
|
572
|
-
|
|
573
|
-
function test() {
|
|
574
|
-
db.transaction();
|
|
575
|
-
try {
|
|
576
|
-
db.users.insert({ name: "test" });
|
|
577
|
-
db.commit(); // Normal closer
|
|
578
|
-
} catch (err) {
|
|
579
|
-
db.rollback(); // Alternative closer
|
|
580
|
-
throw err;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
572
|
+
**Real-world examples**
|
|
583
573
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
574
|
+
```typescript
|
|
575
|
+
// Database transactions
|
|
576
|
+
{
|
|
577
|
+
"pairs": [{
|
|
578
|
+
"opener": "db.transaction",
|
|
579
|
+
"closer": "db.commit",
|
|
580
|
+
"alternatives": ["db.rollback"]
|
|
581
|
+
}]
|
|
592
582
|
}
|
|
593
583
|
|
|
594
|
-
//
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
584
|
+
// Lock acquire/release
|
|
585
|
+
{
|
|
586
|
+
"pairs": [{
|
|
587
|
+
"opener": "lock.acquire",
|
|
588
|
+
"closer": ["lock.release", "lock.free"]
|
|
589
|
+
}]
|
|
601
590
|
}
|
|
602
591
|
|
|
603
|
-
//
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
592
|
+
// Roblox Iris widgets
|
|
593
|
+
{
|
|
594
|
+
"pairs": [{
|
|
595
|
+
"opener": "Iris.CollapsingHeader",
|
|
596
|
+
"openerAlternatives": ["Iris.Window", "Iris.TreeNode", "Iris.Table"],
|
|
597
|
+
"closer": "Iris.End",
|
|
598
|
+
"platform": "roblox",
|
|
599
|
+
"requireSync": true
|
|
600
|
+
}]
|
|
610
601
|
}
|
|
611
602
|
```
|
|
612
603
|
|
|
613
|
-
|
|
604
|
+
### Code Quality
|
|
605
|
+
|
|
606
|
+
#### `ban-instances`
|
|
607
|
+
|
|
608
|
+
Bans specified Roblox Instance classes in `new Instance()` calls and JSX elements.
|
|
609
|
+
|
|
610
|
+
**Configuration**
|
|
614
611
|
|
|
615
612
|
```typescript
|
|
613
|
+
// Array format (default message)
|
|
616
614
|
{
|
|
617
|
-
"cease-nonsense/
|
|
618
|
-
"
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
"allowConditionalClosers": false, // Allow closers in some branches
|
|
630
|
-
"allowMultipleOpeners": true, // Allow consecutive openers
|
|
631
|
-
"maxNestingDepth": 0 // Nesting limit (0 = unlimited)
|
|
615
|
+
"cease-nonsense/ban-instances": ["error", {
|
|
616
|
+
"bannedInstances": ["Part", "Script", "LocalScript"]
|
|
617
|
+
}]
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Object format (custom messages)
|
|
621
|
+
{
|
|
622
|
+
"cease-nonsense/ban-instances": ["error", {
|
|
623
|
+
"bannedInstances": {
|
|
624
|
+
"Part": "Use MeshPart instead for better performance",
|
|
625
|
+
"Script": "Scripts should not be created at runtime"
|
|
626
|
+
}
|
|
632
627
|
}]
|
|
633
628
|
}
|
|
634
629
|
```
|
|
635
630
|
|
|
636
|
-
**
|
|
631
|
+
**❌ Bad**
|
|
637
632
|
|
|
638
|
-
|
|
633
|
+
```typescript
|
|
634
|
+
// With config: { bannedInstances: ["Part", "Script"] }
|
|
635
|
+
const part = new Instance("Part");
|
|
636
|
+
const script = new Instance("Script");
|
|
639
637
|
|
|
640
|
-
|
|
638
|
+
// JSX (lowercase = Roblox Instance)
|
|
639
|
+
<part Size={new Vector3(1, 1, 1)} />
|
|
640
|
+
```
|
|
641
641
|
|
|
642
|
-
|
|
642
|
+
**✅ Good**
|
|
643
643
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
644
|
+
```typescript
|
|
645
|
+
const meshPart = new Instance("MeshPart");
|
|
646
|
+
<meshpart Size={new Vector3(1, 1, 1)} />
|
|
647
|
+
```
|
|
647
648
|
|
|
648
|
-
|
|
649
|
+
#### `fast-format`
|
|
649
650
|
|
|
650
|
-
|
|
651
|
+
Enforces oxfmt code formatting. Reports INSERT, DELETE, and REPLACE operations for formatting differences.
|
|
651
652
|
|
|
652
|
-
|
|
653
|
+
**Features**
|
|
653
654
|
|
|
654
|
-
-
|
|
655
|
-
|
|
656
|
-
- When a yielding function is called, ALL open profiles are automatically closed
|
|
657
|
-
- Subsequent closer calls after yielding will report errors (already closed)
|
|
655
|
+
- ✨ Has auto-fix
|
|
656
|
+
- Uses an LRU cache to avoid re-formatting unchanged files
|
|
658
657
|
|
|
659
|
-
|
|
660
|
-
- Exact match: `"task.wait"` matches only `task.wait()`
|
|
661
|
-
- Wildcard method: `"*.WaitForChild"` matches `instance.WaitForChild()`, `player.WaitForChild()`, etc.
|
|
662
|
-
- Default: `["task.wait", "wait", "*.WaitForChild"]`
|
|
658
|
+
#### `no-async-constructor`
|
|
663
659
|
|
|
664
|
-
|
|
660
|
+
Disallows asynchronous operations inside class constructors.
|
|
665
661
|
|
|
666
|
-
|
|
662
|
+
**Why**
|
|
667
663
|
|
|
668
|
-
|
|
669
|
-
- `false` (strict): Requires closer on every path (if/else both branches, all switch cases, etc.)
|
|
670
|
-
- `true` (permissive): Allows closers in some but not all branches
|
|
664
|
+
Constructors return immediately, so async work causes race conditions, unhandled rejections, and incomplete object states.
|
|
671
665
|
|
|
672
|
-
|
|
673
|
-
- `true`: Allows multiple opener calls before closers (nesting)
|
|
674
|
-
- `false`: Reports error if opener is called again before closer
|
|
666
|
+
**Detected violations**
|
|
675
667
|
|
|
676
|
-
- `
|
|
677
|
-
|
|
678
|
-
|
|
668
|
+
- `await` expressions
|
|
669
|
+
- Promise chains (`.then()`, `.catch()`, `.finally()`)
|
|
670
|
+
- Async IIFEs (`(async () => {})()`)
|
|
671
|
+
- Unhandled async method calls (`this.asyncMethod()`)
|
|
672
|
+
- Orphaned promises (`const p = this.asyncMethod()`)
|
|
679
673
|
|
|
680
|
-
**
|
|
674
|
+
**❌ Bad**
|
|
681
675
|
|
|
682
676
|
```typescript
|
|
683
|
-
{
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
677
|
+
class UserService {
|
|
678
|
+
constructor() {
|
|
679
|
+
await this.initialize(); // Direct await
|
|
680
|
+
this.loadData().then((data) => (this.data = data)); // Promise chain
|
|
681
|
+
(async () => {
|
|
682
|
+
await this.setup();
|
|
683
|
+
})(); // Async IIFE
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async initialize() {
|
|
687
|
+
/* ... */
|
|
688
|
+
}
|
|
689
|
+
async loadData() {
|
|
690
|
+
/* ... */
|
|
691
|
+
}
|
|
692
|
+
async setup() {
|
|
693
|
+
/* ... */
|
|
694
|
+
}
|
|
693
695
|
}
|
|
694
696
|
```
|
|
695
697
|
|
|
696
|
-
**
|
|
698
|
+
**✅ Good**
|
|
697
699
|
|
|
698
700
|
```typescript
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
701
|
+
class UserService {
|
|
702
|
+
private initPromise: Promise<void>;
|
|
703
|
+
|
|
704
|
+
constructor() {
|
|
705
|
+
this.initPromise = this.initialize();
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
async initialize() {
|
|
709
|
+
/* ... */
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Factory pattern
|
|
713
|
+
static async create(): Promise<UserService> {
|
|
714
|
+
const service = new UserService();
|
|
715
|
+
await service.initPromise;
|
|
716
|
+
return service;
|
|
717
|
+
}
|
|
706
718
|
}
|
|
719
|
+
```
|
|
707
720
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
721
|
+
#### `no-commented-code`
|
|
722
|
+
|
|
723
|
+
Detects and reports commented-out code.
|
|
724
|
+
|
|
725
|
+
**Features**
|
|
726
|
+
|
|
727
|
+
- 💡 Has suggestions
|
|
728
|
+
- Groups adjacent line comments and block comments
|
|
729
|
+
- Uses heuristic detection combined with parsing to minimize false positives
|
|
730
|
+
|
|
731
|
+
**❌ Bad**
|
|
732
|
+
|
|
733
|
+
```typescript
|
|
734
|
+
function calculate(x: number) {
|
|
735
|
+
// const result = x * 2;
|
|
736
|
+
// return result;
|
|
737
|
+
|
|
738
|
+
/* if (x > 10) {
|
|
739
|
+
return x;
|
|
740
|
+
} */
|
|
741
|
+
|
|
742
|
+
return x + 1;
|
|
714
743
|
}
|
|
744
|
+
```
|
|
715
745
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
"requireSync": true,
|
|
724
|
-
"yieldingFunctions": ["task.wait", "wait", "*.WaitForChild", "*.*Async"]
|
|
725
|
-
}]
|
|
746
|
+
**✅ Good**
|
|
747
|
+
|
|
748
|
+
```typescript
|
|
749
|
+
function calculate(x: number) {
|
|
750
|
+
// TODO: Consider multiplying by 2 instead
|
|
751
|
+
// Note: This is a simplified version
|
|
752
|
+
return x + 1;
|
|
726
753
|
}
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
#### `no-identity-map`
|
|
757
|
+
|
|
758
|
+
Bans pointless identity `.map()` calls that return the parameter unchanged.
|
|
759
|
+
|
|
760
|
+
**Features**
|
|
727
761
|
|
|
728
|
-
|
|
762
|
+
- ✨ Has auto-fix
|
|
763
|
+
- Context-aware messages for Bindings vs Arrays
|
|
764
|
+
|
|
765
|
+
**❌ Bad**
|
|
766
|
+
|
|
767
|
+
```typescript
|
|
768
|
+
// Bindings
|
|
769
|
+
const result = scaleBinding.map((value) => value);
|
|
770
|
+
|
|
771
|
+
// Arrays - pointless shallow copy
|
|
772
|
+
const copied = items.map((item) => item);
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
**✅ Good**
|
|
776
|
+
|
|
777
|
+
```typescript
|
|
778
|
+
// Bindings - use directly
|
|
779
|
+
const result = scaleBinding;
|
|
780
|
+
|
|
781
|
+
// Arrays - use table.clone or spread
|
|
782
|
+
const copied = table.clone(items);
|
|
783
|
+
const copied2 = [...items];
|
|
784
|
+
|
|
785
|
+
// Actual transformations are fine
|
|
786
|
+
const doubled = items.map((x) => x * 2);
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
**Configuration**
|
|
790
|
+
|
|
791
|
+
```typescript
|
|
729
792
|
{
|
|
730
|
-
"
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
{ "opener": "file.open", "closer": "file.close" }
|
|
734
|
-
]
|
|
793
|
+
"cease-nonsense/no-identity-map": ["error", {
|
|
794
|
+
"bindingPatterns": ["binding"] // Case-insensitive patterns
|
|
795
|
+
}]
|
|
735
796
|
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
#### `no-shorthand-names`
|
|
800
|
+
|
|
801
|
+
Bans shorthand variable names in favor of descriptive full names.
|
|
802
|
+
|
|
803
|
+
**Default mappings**
|
|
804
|
+
|
|
805
|
+
- `plr` → `player` (or `localPlayer` for `Players.LocalPlayer`)
|
|
806
|
+
- `args` → `parameters`
|
|
807
|
+
- `dt` → `deltaTime`
|
|
808
|
+
- `char` → `character`
|
|
809
|
+
- `btn` → `button`
|
|
736
810
|
|
|
737
|
-
|
|
811
|
+
**Configuration**
|
|
812
|
+
|
|
813
|
+
```typescript
|
|
738
814
|
{
|
|
739
|
-
"
|
|
740
|
-
|
|
741
|
-
|
|
815
|
+
"cease-nonsense/no-shorthand-names": ["error", {
|
|
816
|
+
"shorthands": {
|
|
817
|
+
"plr": "player",
|
|
818
|
+
"args": "parameters",
|
|
819
|
+
"dt": "deltaTime",
|
|
820
|
+
"char": "character",
|
|
821
|
+
"btn": "button"
|
|
822
|
+
},
|
|
823
|
+
"allowPropertyAccess": ["char"] // Allow as property
|
|
824
|
+
}]
|
|
742
825
|
}
|
|
743
826
|
```
|
|
744
827
|
|
|
745
|
-
**
|
|
828
|
+
**❌ Bad**
|
|
746
829
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
- Any begin/end API pattern
|
|
830
|
+
```typescript
|
|
831
|
+
const plr = getPlayer();
|
|
832
|
+
const args = [1, 2, 3];
|
|
833
|
+
const dt = 0.016;
|
|
834
|
+
```
|
|
753
835
|
|
|
754
|
-
|
|
836
|
+
**✅ Good**
|
|
837
|
+
|
|
838
|
+
```typescript
|
|
839
|
+
const player = getPlayer();
|
|
840
|
+
const localPlayer = Players.LocalPlayer;
|
|
841
|
+
const parameters = [1, 2, 3];
|
|
842
|
+
const deltaTime = 0.016;
|
|
843
|
+
const model = entity.char; // Property access is allowed
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
### Performance
|
|
755
847
|
|
|
756
848
|
#### `no-color3-constructor`
|
|
757
849
|
|
|
758
|
-
Bans `new Color3(...)` except for `new Color3()` or `new Color3(0, 0, 0)`. Use `Color3.fromRGB()` instead
|
|
850
|
+
Bans `new Color3(...)` except for `new Color3()` or `new Color3(0, 0, 0)`. Use `Color3.fromRGB()` instead.
|
|
851
|
+
|
|
852
|
+
**Features**
|
|
759
853
|
|
|
760
|
-
|
|
854
|
+
- ✨ Has auto-fix
|
|
761
855
|
|
|
762
|
-
**❌ Bad
|
|
856
|
+
**❌ Bad**
|
|
763
857
|
|
|
764
858
|
```typescript
|
|
765
859
|
const red = new Color3(255, 0, 0);
|
|
766
860
|
const blue = new Color3(0.5, 0.5, 1);
|
|
767
861
|
```
|
|
768
862
|
|
|
769
|
-
**✅ Good
|
|
863
|
+
**✅ Good**
|
|
770
864
|
|
|
771
865
|
```typescript
|
|
772
866
|
const red = Color3.fromRGB(255, 0, 0);
|
|
@@ -776,16 +870,21 @@ const black = new Color3(0, 0, 0); // Allowed
|
|
|
776
870
|
|
|
777
871
|
#### `prefer-udim2-shorthand`
|
|
778
872
|
|
|
779
|
-
Prefer `UDim2.fromScale()` or `UDim2.fromOffset()` when all offsets or all scales are zero.
|
|
873
|
+
Prefer `UDim2.fromScale()` or `UDim2.fromOffset()` when all offsets or all scales are zero.
|
|
874
|
+
|
|
875
|
+
**Features**
|
|
876
|
+
|
|
877
|
+
- ✨ Has auto-fix
|
|
878
|
+
- Leaves `new UDim2(0, 0, 0, 0)` alone
|
|
780
879
|
|
|
781
|
-
**❌ Bad
|
|
880
|
+
**❌ Bad**
|
|
782
881
|
|
|
783
882
|
```typescript
|
|
784
883
|
new UDim2(1, 0, 1, 0);
|
|
785
884
|
new UDim2(0, 100, 0, 50);
|
|
786
885
|
```
|
|
787
886
|
|
|
788
|
-
**✅ Good
|
|
887
|
+
**✅ Good**
|
|
789
888
|
|
|
790
889
|
```typescript
|
|
791
890
|
UDim2.fromScale(1, 1);
|
|
@@ -795,23 +894,25 @@ new UDim2(0, 0, 0, 0); // Allowed
|
|
|
795
894
|
|
|
796
895
|
#### `prefer-sequence-overloads`
|
|
797
896
|
|
|
798
|
-
Prefer the optimized `ColorSequence` and `NumberSequence` constructor overloads instead of building an array of `*SequenceKeypoint`s
|
|
897
|
+
Prefer the optimized `ColorSequence` and `NumberSequence` constructor overloads instead of building an array of `*SequenceKeypoint`s.
|
|
799
898
|
|
|
800
|
-
|
|
899
|
+
**Features**
|
|
900
|
+
|
|
901
|
+
- ✨ Has auto-fix
|
|
902
|
+
- Automatically collapses identical 0/1 endpoints
|
|
903
|
+
|
|
904
|
+
**❌ Bad**
|
|
801
905
|
|
|
802
906
|
```typescript
|
|
803
907
|
new ColorSequence([
|
|
804
|
-
|
|
805
|
-
|
|
908
|
+
new ColorSequenceKeypoint(0, Color3.fromRGB(100, 200, 255)),
|
|
909
|
+
new ColorSequenceKeypoint(1, Color3.fromRGB(255, 100, 200)),
|
|
806
910
|
]);
|
|
807
911
|
|
|
808
|
-
new NumberSequence([
|
|
809
|
-
new NumberSequenceKeypoint(0, 0),
|
|
810
|
-
new NumberSequenceKeypoint(1, 100),
|
|
811
|
-
]);
|
|
912
|
+
new NumberSequence([new NumberSequenceKeypoint(0, 0), new NumberSequenceKeypoint(1, 100)]);
|
|
812
913
|
```
|
|
813
914
|
|
|
814
|
-
**✅ Good
|
|
915
|
+
**✅ Good**
|
|
815
916
|
|
|
816
917
|
```typescript
|
|
817
918
|
new ColorSequence(Color3.fromRGB(100, 200, 255), Color3.fromRGB(255, 100, 200));
|
|
@@ -823,116 +924,74 @@ new NumberSequence(0, 100);
|
|
|
823
924
|
new NumberSequence(42);
|
|
824
925
|
```
|
|
825
926
|
|
|
826
|
-
Automatically collapses identical 0/1 endpoints to the single-argument overload.
|
|
827
|
-
|
|
828
927
|
#### `no-instance-methods-without-this`
|
|
829
928
|
|
|
830
|
-
Detects instance methods that don't use `this` and should be converted to standalone functions
|
|
831
|
-
|
|
832
|
-
In roblox-ts, instance methods create metatable objects with significant performance overhead. Methods that don't use `this` can be moved outside the class and called as standalone functions, eliminating this overhead entirely.
|
|
929
|
+
Detects instance methods that don't use `this` and should be converted to standalone functions.
|
|
833
930
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
```typescript
|
|
837
|
-
type OnChange = (currentValue: number, previousValue: number) => void;
|
|
931
|
+
**Why**
|
|
838
932
|
|
|
839
|
-
|
|
840
|
-
private readonly onChanges = new Array<OnChange>();
|
|
841
|
-
private value = 0;
|
|
933
|
+
In roblox-ts, instance methods create metatable objects with significant performance overhead. Methods that don't use `this` can be moved outside the class.
|
|
842
934
|
|
|
843
|
-
|
|
844
|
-
const previousValue = this.value;
|
|
845
|
-
const value = previousValue + 1;
|
|
846
|
-
this.value = value;
|
|
847
|
-
this.notifyChanges(value, previousValue); // ← Bad: method doesn't use this
|
|
848
|
-
}
|
|
935
|
+
**Configuration**
|
|
849
936
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
937
|
+
```typescript
|
|
938
|
+
{
|
|
939
|
+
"cease-nonsense/no-instance-methods-without-this": ["error", {
|
|
940
|
+
"checkPrivate": true, // Default: true
|
|
941
|
+
"checkProtected": true, // Default: true
|
|
942
|
+
"checkPublic": true // Default: true
|
|
943
|
+
}]
|
|
853
944
|
}
|
|
854
945
|
```
|
|
855
946
|
|
|
856
|
-
|
|
947
|
+
**❌ Bad**
|
|
857
948
|
|
|
858
949
|
```typescript
|
|
859
950
|
type OnChange = (currentValue: number, previousValue: number) => void;
|
|
860
951
|
|
|
861
|
-
function notifyChanges(value: number, previousValue: number, onChanges: ReadonlyArray<OnChange>): void {
|
|
862
|
-
for (const onChange of onChanges) onChange(value, previousValue);
|
|
863
|
-
}
|
|
864
|
-
|
|
865
952
|
class MyClass {
|
|
866
|
-
|
|
867
|
-
|
|
953
|
+
private readonly onChanges = new Array<OnChange>();
|
|
954
|
+
private value = 0;
|
|
868
955
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
}
|
|
876
|
-
```
|
|
956
|
+
public increment(): void {
|
|
957
|
+
const previousValue = this.value;
|
|
958
|
+
const value = previousValue + 1;
|
|
959
|
+
this.value = value;
|
|
960
|
+
this.notifyChanges(value, previousValue); // Doesn't use this
|
|
961
|
+
}
|
|
877
962
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
"checkPrivate": true, // Check private methods (default: true)
|
|
884
|
-
"checkProtected": true, // Check protected methods (default: true)
|
|
885
|
-
"checkPublic": true // Check public methods (default: true)
|
|
886
|
-
}]
|
|
963
|
+
private notifyChanges(value: number, previousValue: number): void {
|
|
964
|
+
for (const onChange of this.onChanges) {
|
|
965
|
+
onChange(value, previousValue);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
887
968
|
}
|
|
888
969
|
```
|
|
889
970
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
Bans shorthand variable names in favor of descriptive full names.
|
|
893
|
-
|
|
894
|
-
**Default mappings:**
|
|
895
|
-
|
|
896
|
-
- `plr` → `player` (or `localPlayer` for `Players.LocalPlayer`)
|
|
897
|
-
- `args` → `parameters`
|
|
898
|
-
- `dt` → `deltaTime`
|
|
899
|
-
- `char` → `character`
|
|
900
|
-
|
|
901
|
-
**❌ Bad:**
|
|
971
|
+
**✅ Good**
|
|
902
972
|
|
|
903
973
|
```typescript
|
|
904
|
-
|
|
905
|
-
const args = [1, 2, 3];
|
|
906
|
-
const dt = 0.016;
|
|
907
|
-
```
|
|
908
|
-
|
|
909
|
-
**✅ Good:**
|
|
974
|
+
type OnChange = (currentValue: number, previousValue: number) => void;
|
|
910
975
|
|
|
911
|
-
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
const model = entity.char; // Property access is allowed
|
|
917
|
-
```
|
|
976
|
+
function notifyChanges(value: number, previousValue: number, onChanges: ReadonlyArray<OnChange>): void {
|
|
977
|
+
for (const onChange of onChanges) {
|
|
978
|
+
onChange(value, previousValue);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
918
981
|
|
|
919
|
-
|
|
982
|
+
class MyClass {
|
|
983
|
+
private readonly onChanges = new Array<OnChange>();
|
|
984
|
+
private value = 0;
|
|
920
985
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
"dt": "deltaTime",
|
|
928
|
-
"char": "character",
|
|
929
|
-
"btn": "button" // Add custom mappings
|
|
930
|
-
},
|
|
931
|
-
"allowPropertyAccess": ["char"] // Allow as property
|
|
932
|
-
}]
|
|
986
|
+
public increment(): void {
|
|
987
|
+
const previousValue = this.value;
|
|
988
|
+
const value = previousValue + 1;
|
|
989
|
+
this.value = value;
|
|
990
|
+
notifyChanges(value, previousValue, this.onChanges);
|
|
991
|
+
}
|
|
933
992
|
}
|
|
934
993
|
```
|
|
935
994
|
|
|
936
995
|
## License
|
|
937
996
|
|
|
938
|
-
|
|
997
|
+
MIT License - feel free to use this code however you want.
|