@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.
Files changed (79) hide show
  1. package/README.md +594 -535
  2. package/dist/build-metadata.json +3 -3
  3. package/dist/index.d.ts +7 -3
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +1211 -716
  6. package/dist/index.js.map +33 -28
  7. package/dist/oxfmt-sync.d.ts +4 -0
  8. package/dist/oxfmt-sync.d.ts.map +1 -0
  9. package/dist/oxfmt-worker.d.ts +12 -0
  10. package/dist/oxfmt-worker.d.ts.map +1 -0
  11. package/dist/oxfmt-worker.js +172786 -0
  12. package/dist/oxfmt-worker.js.map +27 -0
  13. package/dist/recognizers/camel-case-detector.d.ts +2 -2
  14. package/dist/recognizers/camel-case-detector.d.ts.map +1 -0
  15. package/dist/recognizers/code-recognizer.d.ts +6 -6
  16. package/dist/recognizers/code-recognizer.d.ts.map +1 -0
  17. package/dist/recognizers/contains-detector.d.ts +2 -2
  18. package/dist/recognizers/contains-detector.d.ts.map +1 -0
  19. package/dist/recognizers/detector.d.ts +3 -4
  20. package/dist/recognizers/detector.d.ts.map +1 -0
  21. package/dist/recognizers/end-with-detector.d.ts +2 -2
  22. package/dist/recognizers/end-with-detector.d.ts.map +1 -0
  23. package/dist/recognizers/javascript-footprint.d.ts +2 -2
  24. package/dist/recognizers/javascript-footprint.d.ts.map +1 -0
  25. package/dist/recognizers/keywords-detector.d.ts +2 -2
  26. package/dist/recognizers/keywords-detector.d.ts.map +1 -0
  27. package/dist/rules/ban-instances.d.ts +1 -0
  28. package/dist/rules/ban-instances.d.ts.map +1 -0
  29. package/dist/rules/ban-react-fc.d.ts +1 -0
  30. package/dist/rules/ban-react-fc.d.ts.map +1 -0
  31. package/dist/rules/enforce-ianitor-check-type.d.ts +2 -1
  32. package/dist/rules/enforce-ianitor-check-type.d.ts.map +1 -0
  33. package/dist/rules/fast-format.d.ts +36 -0
  34. package/dist/rules/fast-format.d.ts.map +1 -0
  35. package/dist/rules/no-async-constructor.d.ts +1 -0
  36. package/dist/rules/no-async-constructor.d.ts.map +1 -0
  37. package/dist/rules/no-color3-constructor.d.ts +1 -0
  38. package/dist/rules/no-color3-constructor.d.ts.map +1 -0
  39. package/dist/rules/no-commented-code.d.ts +1 -0
  40. package/dist/rules/no-commented-code.d.ts.map +1 -0
  41. package/dist/rules/no-god-components.d.ts +1 -0
  42. package/dist/rules/no-god-components.d.ts.map +1 -0
  43. package/dist/rules/no-identity-map.d.ts +9 -0
  44. package/dist/rules/no-identity-map.d.ts.map +1 -0
  45. package/dist/rules/no-instance-methods-without-this.d.ts +1 -0
  46. package/dist/rules/no-instance-methods-without-this.d.ts.map +1 -0
  47. package/dist/rules/no-print.d.ts +1 -0
  48. package/dist/rules/no-print.d.ts.map +1 -0
  49. package/dist/rules/no-shorthand-names.d.ts +1 -0
  50. package/dist/rules/no-shorthand-names.d.ts.map +1 -0
  51. package/dist/rules/no-useless-use-spring.d.ts +1 -0
  52. package/dist/rules/no-useless-use-spring.d.ts.map +1 -0
  53. package/dist/rules/no-warn.d.ts +1 -0
  54. package/dist/rules/no-warn.d.ts.map +1 -0
  55. package/dist/rules/prefer-sequence-overloads.d.ts +1 -0
  56. package/dist/rules/prefer-sequence-overloads.d.ts.map +1 -0
  57. package/dist/rules/prefer-udim2-shorthand.d.ts +1 -0
  58. package/dist/rules/prefer-udim2-shorthand.d.ts.map +1 -0
  59. package/dist/rules/require-named-effect-functions.d.ts +4 -3
  60. package/dist/rules/require-named-effect-functions.d.ts.map +1 -0
  61. package/dist/rules/require-paired-calls.d.ts +1 -0
  62. package/dist/rules/require-paired-calls.d.ts.map +1 -0
  63. package/dist/rules/require-react-component-keys.d.ts +1 -0
  64. package/dist/rules/require-react-component-keys.d.ts.map +1 -0
  65. package/dist/rules/use-exhaustive-dependencies.d.ts +1 -0
  66. package/dist/rules/use-exhaustive-dependencies.d.ts.map +1 -0
  67. package/dist/rules/use-hook-at-top-level.d.ts +1 -0
  68. package/dist/rules/use-hook-at-top-level.d.ts.map +1 -0
  69. package/dist/types/oxfmt.d.ts +88 -0
  70. package/dist/types/oxfmt.d.ts.map +1 -0
  71. package/dist/{configure-utilities.d.ts → utilities/configure-utilities.d.ts} +12 -11
  72. package/dist/utilities/configure-utilities.d.ts.map +1 -0
  73. package/dist/utilities/error-utilities.d.ts +25 -0
  74. package/dist/utilities/error-utilities.d.ts.map +1 -0
  75. package/dist/utilities/format-utilities.d.ts +20 -0
  76. package/dist/utilities/format-utilities.d.ts.map +1 -0
  77. package/dist/utilities/typebox-utilities.d.ts +4 -0
  78. package/dist/utilities/typebox-utilities.d.ts.map +1 -0
  79. package/package.json +23 -10
package/README.md CHANGED
@@ -1,8 +1,19 @@
1
1
  # eslint-cease-nonsense-rules
2
2
 
3
- A bunch of lints to prevent idiot mistakes I encounter with frequency.
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
- **NOTE** I did not write this junk, an LLM did because I do not care.
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
- Add to your ESLint config:
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
- plugins: {
23
- "cease-nonsense": ceaseNonsense,
24
- },
25
- rules: {
26
- // Enable all rules (recommended)
27
- "cease-nonsense/ban-react-fc": "error",
28
- "cease-nonsense/enforce-ianitor-check-type": "error",
29
- "cease-nonsense/no-color3-constructor": "error",
30
- "cease-nonsense/no-instance-methods-without-this": "error",
31
- "cease-nonsense/no-print": "error",
32
- "cease-nonsense/no-shorthand-names": "error",
33
- "cease-nonsense/no-warn": "error",
34
- "cease-nonsense/prefer-sequence-overloads": "error",
35
- "cease-nonsense/prefer-udim2-shorthand": "error",
36
- "cease-nonsense/require-named-effect-functions": "error",
37
- "cease-nonsense/require-paired-calls": "error",
38
- "cease-nonsense/require-react-component-keys": "error",
39
- "cease-nonsense/no-god-components": "error",
40
- "cease-nonsense/no-useless-use-spring": "error",
41
- "cease-nonsense/use-exhaustive-dependencies": "error",
42
- "cease-nonsense/use-hook-at-top-level": "error",
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
- // Or just include the preset
48
- export default [
49
- ceaseNonsense.configs.recommended,
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. You don't really need this.
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
- **❌ Bad:**
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
- id: number;
69
- name: string;
70
- settings: {
71
- theme: string;
72
- notifications: boolean;
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
- id: Ianitor.number(),
84
- name: Ianitor.string(),
85
- settings: Ianitor.interface({
86
- theme: Ianitor.string(),
87
- notifications: Ianitor.boolean(),
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 (Function Component) and related types break debug information in React DevTools, making profiling exponentially harder. They also encourage poor patterns and add unnecessary complexity.
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
- **❌ Bad:**
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 (option `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
- - `roblox-ts` (default): only identifiers allowed (e.g., `useEffect(onTick, [...])`). Inline function expressions or arrows are reported.
206
- - `standard`: identifiers and named function expressions are allowed; arrows are still reported.
252
+ **Configuration**
207
253
 
208
- Default hooks checked: `useEffect`, `useLayoutEffect`, `useInsertionEffect`.
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
- doThing();
268
+ doThing();
216
269
  }, [dep]);
217
270
 
218
271
  // Anonymous function expression
219
- useEffect(function () {
220
- doThing();
221
- }, [dep]);
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
- doThing();
285
+ doThing();
230
286
  }
231
287
  useEffect(onDepChange, [dep]);
232
288
 
233
289
  // Allowed in `standard` mode
234
- useEffect(function onDepChange() {
235
- doThing();
236
- }, [dep]);
290
+ useEffect(
291
+ function onDepChange() {
292
+ doThing();
293
+ },
294
+ [dep],
295
+ );
237
296
  ```
238
297
 
239
- **Configuration:**
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/require-named-effect-functions": ["error", {
244
- "environment": "roblox-ts", // or "standard"
245
- "hooks": ["useEffect", "useLayoutEffect", "useInsertionEffect"]
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
- **✅ Good:**
321
+ **❌ Bad**
251
322
 
252
323
  ```typescript
253
- function UserList({ users }) {
254
- return (
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
- ```typescript
267
- {
268
- "cease-nonsense/require-react-component-keys": ["error", {
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
- #### `use-exhaustive-dependencies`
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
- const [user, setUser] = useState(null);
337
+ const [user, setUser] = useState(null);
284
338
 
285
- useEffect(() => {
286
- fetchUser(userId).then(setUser);
287
- }, []); // Missing userId dependency!
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). Defaults: `springHooks: ["useSpring"]`, `treatEmptyDepsAsViolation: true`.
347
+ Flags `useSpring`-style hooks that never change (static config plus non-updating deps).
294
348
 
295
- **❌ Bad:**
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
- **✅ Good:**
360
+ **❌ Bad**
319
361
 
320
362
  ```typescript
321
- function UserProfile({ userId }) {
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
- **Configuration:**
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
- **❌ Bad:**
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 (handles naming conflicts)
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 (only check these hooks)
389
+
390
+ // Strategy 3: Whitelist mode
392
391
  "onlyHooks": ["useState", "useEffect", "useContext"]
393
392
  }]
394
393
  }
395
394
  ```
396
395
 
397
- **Examples:**
396
+ **❌ Bad**
398
397
 
399
398
  ```typescript
400
- // Ignore ECS hooks: { "ignoreHooks": ["useEntity"] }
401
- function Component() {
402
- if (condition) {
403
- useEntity(0); // Ignored
404
- }
405
- useState(0); // ❌ Error - still checked
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
- // Handle naming conflicts: { "importSources": { "react": true, "my-ecs": false } }
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
- // Whitelist: { "onlyHooks": ["useState"] }
419
- function Component() {
420
- if (condition) {
421
- useState(0); // ❌ Error - in whitelist
422
- useEffect(f); // ✅ Ignored - not in whitelist
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. Prevents resource leaks, unbalanced operations, and control flow errors.
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
- - Control flow analysis across all paths (if/else, switch, try/catch, loops)
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
- // Missing closer on early return
479
- function test() {
480
- debug.profilebegin("task");
481
- if (error) return; // profilebegin never closed on this path
482
- debug.profileend();
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
- // Unpaired closer (no matching opener)
486
- function test() {
487
- doWork();
488
- debug.profileend(); // No matching profilebegin
489
- }
484
+ **Pair configuration options**
490
485
 
491
- // Wrong LIFO order
492
- function test() {
493
- debug.profilebegin("outer");
494
- debug.profilebegin("inner");
495
- debug.profileend(); // closes inner
496
- // outer is never closed
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
- // Async operation with requireSync: true
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
- // Roblox yielding function auto-closes
507
- function test() {
508
- debug.profilebegin("task");
509
- task.wait(1); // Auto-closes all profiles
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
- // Conditional branch missing closer
514
- function test() {
515
- debug.profilebegin("task");
516
- if (condition) {
517
- debug.profileend();
518
- } else {
519
- return; // Missing closer on this path
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
- // Break/continue skip closer
517
+ **❌ Bad**
518
+
519
+ ```typescript
520
+ // Missing closer on early return
524
521
  function test() {
525
- debug.profilebegin("loop");
526
- for (const item of items) {
527
- if (item.stop) break; // Skips closer
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
- // Contextual error: empty stack
527
+ // Wrong LIFO order
533
528
  function test() {
534
- Iris.End(); // Error: Unexpected call to 'Iris.End' - no matching opener on stack
529
+ debug.profilebegin("outer");
530
+ debug.profilebegin("inner");
531
+ debug.profileend(); // closes inner
532
+ // outer is never closed
535
533
  }
536
534
 
537
- // Contextual error: wrong closer
538
- function test() {
539
- Iris.CollapsingHeader(["Units"]);
540
- debug.profileend(); // Error: Unexpected call to 'debug.profileend' - expected one of: Iris.End
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
- debug.profilebegin("task");
550
- doWork();
551
- debug.profileend();
548
+ debug.profilebegin("task");
549
+ doWork();
550
+ debug.profileend();
552
551
  }
553
552
 
554
553
  // Proper LIFO nesting
555
554
  function test() {
556
- debug.profilebegin("outer");
557
- debug.profilebegin("inner");
558
- debug.profileend(); // closes inner
559
- debug.profileend(); // closes outer
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
- debug.profilebegin("task");
565
- try {
566
- riskyOperation();
567
- } finally {
568
- debug.profileend();
569
- }
563
+ debug.profilebegin("task");
564
+ try {
565
+ riskyOperation();
566
+ } finally {
567
+ debug.profileend();
568
+ }
570
569
  }
570
+ ```
571
571
 
572
- // Try-catch with alternative closers
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
- // Closer in all branches
585
- function test() {
586
- debug.profilebegin("task");
587
- if (condition) {
588
- debug.profileend();
589
- } else {
590
- debug.profileend();
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
- // Pairs inside loop iterations (not across)
595
- function test() {
596
- for (const item of items) {
597
- debug.profilebegin("item");
598
- process(item);
599
- debug.profileend();
600
- }
584
+ // Lock acquire/release
585
+ {
586
+ "pairs": [{
587
+ "opener": "lock.acquire",
588
+ "closer": ["lock.release", "lock.free"]
589
+ }]
601
590
  }
602
591
 
603
- // Multiple closers with requireAll
604
- function test() {
605
- resource.init();
606
- resource.setup();
607
- // Both cleanup1 and cleanup2 must be called
608
- resource.cleanup1();
609
- resource.cleanup2();
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
- **Configuration:**
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/require-paired-calls": ["error", {
618
- "pairs": [{
619
- "opener": "debug.profilebegin", // Opener function name
620
- "closer": "debug.profileend", // Closer function name(s)
621
- "alternatives": ["db.rollback"], // Alternative closers (any one)
622
- "requireSync": true, // Disallow await/yield
623
- "platform": "roblox", // Platform-specific behavior
624
- "yieldingFunctions": [ // Custom yielding patterns
625
- "task.wait",
626
- "*.WaitForChild" // Supports wildcards
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
- **Configuration Options:**
631
+ **❌ Bad**
637
632
 
638
- **Pair Configuration** (per-pair settings in the `pairs` array):
633
+ ```typescript
634
+ // With config: { bannedInstances: ["Part", "Script"] }
635
+ const part = new Instance("Part");
636
+ const script = new Instance("Script");
639
637
 
640
- - `opener` (required, string) - Function name that starts the paired operation (e.g., `"debug.profilebegin"`). Must be an exact function name including member access (e.g., `"obj.method"`).
638
+ // JSX (lowercase = Roblox Instance)
639
+ <part Size={new Vector3(1, 1, 1)} />
640
+ ```
641
641
 
642
- - `openerAlternatives` (optional, string[]) - Additional opener names that share the same closer. Every name in this array is treated exactly like `opener` (same stack entry, same diagnostics). Handy for APIs like Iris where `Iris.End` closes windows, collapsing headers, etc.
642
+ **✅ Good**
643
643
 
644
- - `closer` (required, string | string[]) - Function name(s) that close the paired operation. Can be:
645
- - Single string: `"debug.profileend"` - only this function can close
646
- - Array of strings: `["lock.release", "lock.free"]` - ANY of these functions can close (alternatives within closer)
644
+ ```typescript
645
+ const meshPart = new Instance("MeshPart");
646
+ <meshpart Size={new Vector3(1, 1, 1)} />
647
+ ```
647
648
 
648
- - Multiple pair configs can share the same closer. The rule will always pop the most recent opener whose configuration allows that closer, so you can keep separate telemetry per widget/button/etc. while still reusing a single `End()` call.
649
+ #### `fast-format`
649
650
 
650
- - `alternatives` (optional, string[]) - Alternative closer function names used for error paths. When present, ANY ONE of the `closer` or `alternatives` satisfies the requirement. Example: `"closer": "db.commit", "alternatives": ["db.rollback"]` means either commit OR rollback closes the transaction.
651
+ Enforces oxfmt code formatting. Reports INSERT, DELETE, and REPLACE operations for formatting differences.
651
652
 
652
- - `requireSync` (optional, boolean, default: `false`) - When `true`, disallows `await` or `yield` expressions between opener and closer. Reports error if async operations occur within the paired scope.
653
+ **Features**
653
654
 
654
- - `platform` (optional, `"roblox"`) - Enables Roblox-specific behavior:
655
- - Auto-detects yielding function calls (configured via `yieldingFunctions`)
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
- - `yieldingFunctions` (optional, string[], only with `platform: "roblox"`) - Custom patterns for Roblox yielding functions. Supports wildcards:
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
- **Top-Level Options:**
660
+ Disallows asynchronous operations inside class constructors.
665
661
 
666
- - `pairs` (required, array) - Array of pair configurations to enforce. Rule checks all configured pairs simultaneously.
662
+ **Why**
667
663
 
668
- - `allowConditionalClosers` (optional, boolean, default: `false`) - Controls whether closers must be called on ALL execution paths:
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
- - `allowMultipleOpeners` (optional, boolean, default: `true`) - Controls consecutive opener calls:
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
- - `maxNestingDepth` (optional, number, default: `0`) - Maximum nesting depth for paired calls:
677
- - `0`: Unlimited nesting
678
- - `> 0`: Reports error if nesting exceeds this depth
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
- **Default configuration (Roblox profiling):**
674
+ **❌ Bad**
681
675
 
682
676
  ```typescript
683
- {
684
- "cease-nonsense/require-paired-calls": ["error", {
685
- "pairs": [{
686
- "opener": "debug.profilebegin",
687
- "closer": "debug.profileend",
688
- "platform": "roblox",
689
- "requireSync": true,
690
- "yieldingFunctions": ["task.wait", "wait", "*.WaitForChild"]
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
- **Real-world examples:**
698
+ **✅ Good**
697
699
 
698
700
  ```typescript
699
- // Database transactions
700
- {
701
- "pairs": [{
702
- "opener": "db.transaction",
703
- "closer": "db.commit",
704
- "alternatives": ["db.rollback"]
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
- // Lock acquire/release
709
- {
710
- "pairs": [{
711
- "opener": "lock.acquire",
712
- "closer": ["lock.release", "lock.free"]
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
- // Roblox Iris widgets all using Iris.End
717
- {
718
- "pairs": [{
719
- "opener": "Iris.CollapsingHeader",
720
- "openerAlternatives": ["Iris.Window", "Iris.TreeNode", "Iris.Table"],
721
- "closer": "Iris.End",
722
- "platform": "roblox",
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
- // Multiple pairs
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
- "pairs": [
731
- { "opener": "debug.profilebegin", "closer": "debug.profileend" },
732
- { "opener": "db.transaction", "closer": "db.commit", "alternatives": ["db.rollback"] },
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
- // Strict nesting
811
+ **Configuration**
812
+
813
+ ```typescript
738
814
  {
739
- "pairs": [{ "opener": "begin", "closer": "end" }],
740
- "maxNestingDepth": 2,
741
- "allowMultipleOpeners": false
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
- **Use cases:**
828
+ **❌ Bad**
746
829
 
747
- - Roblox/Luau profiling (debug.profilebegin/end)
748
- - Database transactions (transaction/commit/rollback)
749
- - Resource locks (acquire/release)
750
- - File handles (open/close)
751
- - Network connections (connect/disconnect)
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
- ### Code Quality
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 for better performance.
850
+ Bans `new Color3(...)` except for `new Color3()` or `new Color3(0, 0, 0)`. Use `Color3.fromRGB()` instead.
851
+
852
+ **Features**
759
853
 
760
- ##### ✨ Has auto-fix
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. Leaves `new UDim2(0, 0, 0, 0)` alone. Includes auto-fix.
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 for the 0/1 endpoints. Includes auto-fix.
897
+ Prefer the optimized `ColorSequence` and `NumberSequence` constructor overloads instead of building an array of `*SequenceKeypoint`s.
799
898
 
800
- **❌ Bad:**
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
- new ColorSequenceKeypoint(0, Color3.fromRGB(100, 200, 255)),
805
- new ColorSequenceKeypoint(1, Color3.fromRGB(255, 100, 200)),
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 for better performance in roblox-ts.
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
- **❌ Bad:**
835
-
836
- ```typescript
837
- type OnChange = (currentValue: number, previousValue: number) => void;
931
+ **Why**
838
932
 
839
- class MyClass {
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
- public increment(): void {
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
- private notifyChanges(value: number, previousValue: number): void {
851
- for (const onChange of this.onChanges) onChange(value, previousValue);
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
- **✅ Good:**
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
- private readonly onChanges = new Array<OnChange>();
867
- private value = 0;
953
+ private readonly onChanges = new Array<OnChange>();
954
+ private value = 0;
868
955
 
869
- public increment(): void {
870
- const previousValue = this.value;
871
- const value = previousValue + 1;
872
- this.value = value;
873
- notifyChanges(value, previousValue, this.onChanges); // Standalone function call
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
- **Configuration:**
879
-
880
- ```typescript
881
- {
882
- "cease-nonsense/no-instance-methods-without-this": ["error", {
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
- #### `no-shorthand-names`
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
- const plr = getPlayer();
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
- ```typescript
912
- const player = getPlayer();
913
- const localPlayer = Players.LocalPlayer;
914
- const parameters = [1, 2, 3];
915
- const deltaTime = 0.016;
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
- **Configuration:**
982
+ class MyClass {
983
+ private readonly onChanges = new Array<OnChange>();
984
+ private value = 0;
920
985
 
921
- ```typescript
922
- {
923
- "cease-nonsense/no-shorthand-names": ["error", {
924
- "shorthands": {
925
- "plr": "player",
926
- "args": "parameters",
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
- Do whatever you want with this code. I don't care. I know it says MIT but that is genuinely just a formality.
997
+ MIT License - feel free to use this code however you want.