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