@blumintinc/eslint-plugin-blumint 1.18.16 → 1.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/lib/index.js +13 -1
- package/lib/rules/consistent-callback-naming.js +24 -23
- package/lib/rules/enforce-single-exported-unit-per-file.d.ts +3 -0
- package/lib/rules/enforce-single-exported-unit-per-file.js +571 -0
- package/lib/rules/no-harness-coupled-disables.d.ts +1 -0
- package/lib/rules/no-harness-coupled-disables.js +134 -0
- package/lib/rules/prefer-clone-deep.js +8 -3
- package/lib/rules/prefer-map-over-conditional-dispatch.d.ts +4 -0
- package/lib/rules/prefer-map-over-conditional-dispatch.js +907 -0
- package/lib/rules/prefer-union-from-const-array.d.ts +3 -0
- package/lib/rules/prefer-union-from-const-array.js +137 -0
- package/package.json +1 -1
- package/release-manifest.json +61 -0
package/README.md
CHANGED
|
@@ -130,6 +130,7 @@ full closed loop is documented in agora's `.claude/skills/eslint-autonomy/SKILL.
|
|
|
130
130
|
| [enforce-render-hits-memoization](docs/rules/enforce-render-hits-memoization.md) | Enforce proper memoization and usage of useRenderHits and renderHits | ✅ | | | | | |
|
|
131
131
|
| [enforce-safe-stringify](docs/rules/enforce-safe-stringify.md) | Enforce using safe-stable-stringify instead of JSON.stringify to handle circular references and ensure deterministic output. JSON.stringify can throw errors on circular references and produce inconsistent output for objects with the same properties in different orders. safe-stable-stringify handles these cases safely. | ✅ | | | 🔧 | | |
|
|
132
132
|
| [enforce-serializable-params](docs/rules/enforce-serializable-params.md) | Enforce serializable parameters for Firebase callable/HTTPS functions. | ✅ | | | | | |
|
|
133
|
+
| [enforce-single-exported-unit-per-file](docs/rules/enforce-single-exported-unit-per-file.md) | Enforce that a .tsx/.jsx file exports at most one React component and any file exports at most one class | ✅ | | | | | |
|
|
133
134
|
| [enforce-singular-type-names](docs/rules/enforce-singular-type-names.md) | Enforce TypeScript type names to be singular | ✅ | | | | | |
|
|
134
135
|
| [enforce-snapshot-state-narrowing](docs/rules/enforce-snapshot-state-narrowing.md) | Enforce correct narrowing of FirestoreSnapshotState<T> variables. Falsy/truthy checks are semantic bugs because all string states are truthy; raw typeof narrowing to data bypasses the isSnapshotReady abstraction. | ✅ | | | | 💡 | |
|
|
135
136
|
| [enforce-stable-hash-spread-props](docs/rules/enforce-stable-hash-spread-props.md) | Require stableHash wrapping when spread props rest objects are used in React hook dependency arrays to avoid re-renders triggered by new object references on every render. | ✅ | | | 🔧 | | |
|
|
@@ -175,6 +176,7 @@ full closed loop is documented in agora's `.claude/skills/eslint-autonomy/SKILL.
|
|
|
175
176
|
| [no-firestore-jest-mock](docs/rules/no-firestore-jest-mock.md) | Prevent importing firestore-jest-mock in test files | ✅ | | | 🔧 | | |
|
|
176
177
|
| [no-firestore-object-arrays](docs/rules/no-firestore-object-arrays.md) | Disallow arrays of object types in Firestore models. Prefer Record maps keyed by id with an index field, or subcollections/arrays of IDs. | ✅ | | | | | |
|
|
177
178
|
| [no-handler-suffix](docs/rules/no-handler-suffix.md) | Disallow the generic "handler" suffix in callback names so names explain the action they perform | ✅ | | | | | |
|
|
179
|
+
| [no-harness-coupled-disables](docs/rules/no-harness-coupled-disables.md) | Disallow eslint-disable justifications that reference the agent development harness (worktree, cwd, stop-hook, claude) instead of the code’s own semantics. | ✅ | | | | | |
|
|
178
180
|
| [no-hungarian](docs/rules/no-hungarian.md) | Disallow Hungarian notation in locally declared variables, types, and classes | ✅ | | | | | |
|
|
179
181
|
| [no-inline-component-prop](docs/rules/no-inline-component-prop.md) | Prevent inline function components defined in render from being passed to component-type props like CatalogWrapper to avoid remounts and UI flashes. | ✅ | | | | | |
|
|
180
182
|
| [no-jsx-in-hooks](docs/rules/no-jsx-in-hooks.md) | Prevent hooks from returning JSX | ✅ | | | | | |
|
|
@@ -233,6 +235,7 @@ full closed loop is documented in agora's `.claude/skills/eslint-autonomy/SKILL.
|
|
|
233
235
|
| [prefer-fragment-shorthand](docs/rules/prefer-fragment-shorthand.md) | Prefer <> shorthand for <React.Fragment> | ✅ | | | 🔧 | | |
|
|
234
236
|
| [prefer-getter-over-parameterless-method](docs/rules/prefer-getter-over-parameterless-method.md) | Enforce getter syntax for synchronous parameterless methods that return values, improving semantic clarity and avoiding accidental method invocation without parentheses. | ✅ | | | 🔧 | | |
|
|
235
237
|
| [prefer-global-router-state-key](docs/rules/prefer-global-router-state-key.md) | Enforce using centralized router state key constants from queryKeys.ts for useRouterState key parameter | ✅ | | | 🔧 | | |
|
|
238
|
+
| [prefer-map-over-conditional-dispatch](docs/rules/prefer-map-over-conditional-dispatch.md) | Prefer a Record<Discriminant, Value> lookup over switch/ternary/if-else dispatch on a literal-union discriminant where every branch returns or assigns a single value. | ✅ | | | 🔧 | | 💭 |
|
|
236
239
|
| [prefer-next-dynamic](docs/rules/prefer-next-dynamic.md) | Prefer Next.js dynamic() over custom useDynamic() for component imports | ✅ | | | 🔧 | | |
|
|
237
240
|
| [prefer-nullish-coalescing-boolean-props](docs/rules/prefer-nullish-coalescing-boolean-props.md) | Prefer nullish coalescing over logical OR, but allow logical OR in boolean contexts | ✅ | | | 🔧 | | |
|
|
238
241
|
| [prefer-params-over-parent-id](docs/rules/prefer-params-over-parent-id.md) | Prefer event.params over ref.parent.id for type-safe Firebase trigger paths. | ✅ | | | 🔧 | | |
|
|
@@ -241,6 +244,7 @@ full closed loop is documented in agora's `.claude/skills/eslint-autonomy/SKILL.
|
|
|
241
244
|
| [prefer-sx-prop-over-system-props](docs/rules/prefer-sx-prop-over-system-props.md) | Enforce using the MUI `sx` prop instead of deprecated system props (e.g. `mt`, `display`, `flexDirection`) on MUI components. | ✅ | | | 🔧 | | |
|
|
242
245
|
| [prefer-type-alias-over-typeof-constant](docs/rules/prefer-type-alias-over-typeof-constant.md) | Prefer named type aliases over `typeof` on same-file global constants; ensure types are declared before constants. | ✅ | | | | | |
|
|
243
246
|
| [prefer-type-over-interface](docs/rules/prefer-type-over-interface.md) | Prefer using type alias over interface | ✅ | | | 🔧 | | |
|
|
247
|
+
| [prefer-union-from-const-array](docs/rules/prefer-union-from-const-array.md) | Derive string-literal union types from an `as const` array instead of declaring the union inline, so the runtime value set and the type share a single source of truth | ✅ | | | 🔧 | | |
|
|
244
248
|
| [prefer-url-tostring-over-tojson](docs/rules/prefer-url-tostring-over-tojson.md) | Enforce the use of toString() over toJSON() on URL objects. | ✅ | | | 🔧 | | |
|
|
245
249
|
| [prefer-use-base62-id](docs/rules/prefer-use-base62-id.md) | Prefer `useBase62Id()` over `uuidv4Base62()` inside useState/useRef/useMemo for stable component IDs to avoid SSR hydration mismatches | ✅ | | | | | |
|
|
246
250
|
| [prefer-use-deep-compare-memo](docs/rules/prefer-use-deep-compare-memo.md) | Enforce using useDeepCompareMemo when dependency array contains non-primitive values (objects, arrays) that are not already memoized. This prevents unnecessary re-renders due to reference changes. | ✅ | | | 🔧 | | |
|
package/lib/index.js
CHANGED
|
@@ -35,6 +35,7 @@ const no_console_error_1 = require("./rules/no-console-error");
|
|
|
35
35
|
const no_conditional_literals_in_jsx_1 = require("./rules/no-conditional-literals-in-jsx");
|
|
36
36
|
const no_filter_without_return_1 = require("./rules/no-filter-without-return");
|
|
37
37
|
const no_hungarian_1 = require("./rules/no-hungarian");
|
|
38
|
+
const no_harness_coupled_disables_1 = require("./rules/no-harness-coupled-disables");
|
|
38
39
|
const no_misused_switch_case_1 = require("./rules/no-misused-switch-case");
|
|
39
40
|
const no_unpinned_dependencies_1 = require("./rules/no-unpinned-dependencies");
|
|
40
41
|
const no_unused_props_1 = require("./rules/no-unused-props");
|
|
@@ -42,8 +43,10 @@ const no_inline_component_prop_1 = require("./rules/no-inline-component-prop");
|
|
|
42
43
|
const no_useless_fragment_1 = require("./rules/no-useless-fragment");
|
|
43
44
|
const prefer_fragment_shorthand_1 = require("./rules/prefer-fragment-shorthand");
|
|
44
45
|
const prefer_getter_over_parameterless_method_1 = require("./rules/prefer-getter-over-parameterless-method");
|
|
46
|
+
const prefer_map_over_conditional_dispatch_1 = require("./rules/prefer-map-over-conditional-dispatch");
|
|
45
47
|
const prefer_type_over_interface_1 = require("./rules/prefer-type-over-interface");
|
|
46
48
|
const prefer_type_alias_over_typeof_constant_1 = require("./rules/prefer-type-alias-over-typeof-constant");
|
|
49
|
+
const prefer_union_from_const_array_1 = require("./rules/prefer-union-from-const-array");
|
|
47
50
|
const require_memo_1 = require("./rules/require-memo");
|
|
48
51
|
const require_memoize_jsx_returners_1 = require("./rules/require-memoize-jsx-returners");
|
|
49
52
|
const no_jsx_whitespace_literal_1 = require("./rules/no-jsx-whitespace-literal");
|
|
@@ -70,6 +73,7 @@ const use_custom_memo_1 = require("./rules/use-custom-memo");
|
|
|
70
73
|
const memoize_root_level_hocs_1 = require("./rules/memoize-root-level-hocs");
|
|
71
74
|
const use_custom_link_1 = require("./rules/use-custom-link");
|
|
72
75
|
const enforce_serializable_params_1 = __importDefault(require("./rules/enforce-serializable-params"));
|
|
76
|
+
const enforce_single_exported_unit_per_file_1 = require("./rules/enforce-single-exported-unit-per-file");
|
|
73
77
|
const enforce_realtimedb_path_utils_1 = require("./rules/enforce-realtimedb-path-utils");
|
|
74
78
|
const enforce_memoize_async_1 = require("./rules/enforce-memoize-async");
|
|
75
79
|
const enforce_exported_function_types_1 = require("./rules/enforce-exported-function-types");
|
|
@@ -218,7 +222,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
|
|
|
218
222
|
module.exports = {
|
|
219
223
|
meta: {
|
|
220
224
|
name: '@blumintinc/eslint-plugin-blumint',
|
|
221
|
-
version: '1.
|
|
225
|
+
version: '1.19.1',
|
|
222
226
|
},
|
|
223
227
|
parseOptions: {
|
|
224
228
|
ecmaVersion: 2020,
|
|
@@ -268,6 +272,7 @@ module.exports = {
|
|
|
268
272
|
'@blumintinc/blumint/no-conditional-literals-in-jsx': 'error',
|
|
269
273
|
'@blumintinc/blumint/no-filter-without-return': 'error',
|
|
270
274
|
'@blumintinc/blumint/no-hungarian': 'error',
|
|
275
|
+
'@blumintinc/blumint/no-harness-coupled-disables': 'error',
|
|
271
276
|
'@blumintinc/blumint/no-handler-suffix': 'error',
|
|
272
277
|
'@blumintinc/blumint/no-misused-switch-case': 'error',
|
|
273
278
|
'@blumintinc/blumint/no-unpinned-dependencies': 'error',
|
|
@@ -278,8 +283,10 @@ module.exports = {
|
|
|
278
283
|
'@blumintinc/blumint/no-useless-usememo-primitives': 'error',
|
|
279
284
|
'@blumintinc/blumint/prefer-fragment-shorthand': 'error',
|
|
280
285
|
'@blumintinc/blumint/prefer-getter-over-parameterless-method': 'error',
|
|
286
|
+
'@blumintinc/blumint/prefer-map-over-conditional-dispatch': 'error',
|
|
281
287
|
'@blumintinc/blumint/prefer-type-over-interface': 'error',
|
|
282
288
|
'@blumintinc/blumint/prefer-type-alias-over-typeof-constant': 'error',
|
|
289
|
+
'@blumintinc/blumint/prefer-union-from-const-array': 'error',
|
|
283
290
|
'@blumintinc/blumint/require-memo': 'error',
|
|
284
291
|
'@blumintinc/blumint/require-memoize-jsx-returners': 'error',
|
|
285
292
|
'@blumintinc/blumint/no-unmemoized-memo-without-props': 'error',
|
|
@@ -304,6 +311,7 @@ module.exports = {
|
|
|
304
311
|
'@blumintinc/blumint/use-custom-memo': 'error',
|
|
305
312
|
'@blumintinc/blumint/use-custom-link': 'error',
|
|
306
313
|
'@blumintinc/blumint/enforce-serializable-params': 'error',
|
|
314
|
+
'@blumintinc/blumint/enforce-single-exported-unit-per-file': 'error',
|
|
307
315
|
'@blumintinc/blumint/enforce-realtimedb-path-utils': 'error',
|
|
308
316
|
'@blumintinc/blumint/enforce-memoize-async': 'error',
|
|
309
317
|
'@blumintinc/blumint/enforce-exported-function-types': 'error',
|
|
@@ -535,6 +543,7 @@ module.exports = {
|
|
|
535
543
|
'no-conditional-literals-in-jsx': no_conditional_literals_in_jsx_1.noConditionalLiteralsInJsx,
|
|
536
544
|
'no-filter-without-return': no_filter_without_return_1.noFilterWithoutReturn,
|
|
537
545
|
'no-hungarian': no_hungarian_1.noHungarian,
|
|
546
|
+
'no-harness-coupled-disables': no_harness_coupled_disables_1.noHarnessCoupledDisables,
|
|
538
547
|
'no-handler-suffix': no_handler_suffix_1.noHandlerSuffix,
|
|
539
548
|
'no-misused-switch-case': no_misused_switch_case_1.noMisusedSwitchCase,
|
|
540
549
|
'no-unpinned-dependencies': no_unpinned_dependencies_1.noUnpinnedDependencies,
|
|
@@ -545,8 +554,10 @@ module.exports = {
|
|
|
545
554
|
'enforce-dynamic-file-naming': enforce_dynamic_file_naming_1.default,
|
|
546
555
|
'prefer-fragment-shorthand': prefer_fragment_shorthand_1.preferFragmentShorthand,
|
|
547
556
|
'prefer-getter-over-parameterless-method': prefer_getter_over_parameterless_method_1.preferGetterOverParameterlessMethod,
|
|
557
|
+
'prefer-map-over-conditional-dispatch': prefer_map_over_conditional_dispatch_1.preferMapOverConditionalDispatch,
|
|
548
558
|
'prefer-type-over-interface': prefer_type_over_interface_1.preferTypeOverInterface,
|
|
549
559
|
'prefer-type-alias-over-typeof-constant': prefer_type_alias_over_typeof_constant_1.preferTypeAliasOverTypeofConstant,
|
|
560
|
+
'prefer-union-from-const-array': prefer_union_from_const_array_1.preferUnionFromConstArray,
|
|
550
561
|
'require-memo': require_memo_1.requireMemo,
|
|
551
562
|
'require-memoize-jsx-returners': require_memoize_jsx_returners_1.requireMemoizeJsxReturners,
|
|
552
563
|
'no-unmemoized-memo-without-props': no_unmemoized_memo_without_props_1.noUnmemoizedMemoWithoutProps,
|
|
@@ -574,6 +585,7 @@ module.exports = {
|
|
|
574
585
|
'use-custom-memo': use_custom_memo_1.useCustomMemo,
|
|
575
586
|
'use-custom-link': use_custom_link_1.useCustomLink,
|
|
576
587
|
'enforce-serializable-params': enforce_serializable_params_1.default,
|
|
588
|
+
'enforce-single-exported-unit-per-file': enforce_single_exported_unit_per_file_1.enforceSingleExportedUnitPerFile,
|
|
577
589
|
'enforce-realtimedb-path-utils': enforce_realtimedb_path_utils_1.enforceRealtimedbPathUtils,
|
|
578
590
|
'enforce-memoize-async': enforce_memoize_async_1.enforceMemoizeAsync,
|
|
579
591
|
'enforce-exported-function-types': enforce_exported_function_types_1.enforceExportedFunctionTypes,
|
|
@@ -26,6 +26,19 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
26
26
|
const createRule_1 = require("../utils/createRule");
|
|
27
27
|
const utils_1 = require("@typescript-eslint/utils");
|
|
28
28
|
const ts = __importStar(require("typescript"));
|
|
29
|
+
// The "handle" prefix the rule targets is the verb-phrase pattern
|
|
30
|
+
// `handle<Something>` — six letters immediately followed by a capitalized word
|
|
31
|
+
// (handleClick, handleSubmit, handleFormSubmit). When "handle" is followed by a
|
|
32
|
+
// lowercase letter the six letters are part of an ordinary word, not the prefix:
|
|
33
|
+
// the past participle "handled" (and derived names like "handledFingerprints"),
|
|
34
|
+
// the noun "handler(s)", "handles", "handling", the adjective "handleable". Those
|
|
35
|
+
// are plain identifiers, so flagging them — and worse, autofix-stripping the
|
|
36
|
+
// leading "handle" to leave nonsense like `d`/`dFingerprints` — silently corrupts
|
|
37
|
+
// unrelated code (Bug #1301). A bare "handle" is likewise a whole word, not a
|
|
38
|
+
// prefix.
|
|
39
|
+
function hasHandlePrefix(name) {
|
|
40
|
+
return /^handle[A-Z]/.test(name);
|
|
41
|
+
}
|
|
29
42
|
module.exports = (0, createRule_1.createRule)({
|
|
30
43
|
name: 'consistent-callback-naming',
|
|
31
44
|
meta: {
|
|
@@ -48,9 +61,15 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
48
61
|
defaultOptions: [],
|
|
49
62
|
create(context) {
|
|
50
63
|
const parserServices = context.parserServices;
|
|
51
|
-
//
|
|
64
|
+
// This rule is type-aware, but a single eslint invocation routinely mixes
|
|
65
|
+
// in-project files with out-of-project ones (plain-Node `.mjs` scripts,
|
|
66
|
+
// config files, etc.) that the TS `project` never parses. Throwing here
|
|
67
|
+
// aborts rule loading for the ENTIRE run — one out-of-project file in argv
|
|
68
|
+
// kills diagnostics for every file (Bug #1302). Degrade gracefully instead:
|
|
69
|
+
// skip files without type information with a no-op visitor, matching how
|
|
70
|
+
// @typescript-eslint rules tolerate missing parser services per file.
|
|
52
71
|
if (!parserServices?.program || !parserServices?.esTreeNodeToTSNodeMap) {
|
|
53
|
-
|
|
72
|
+
return {};
|
|
54
73
|
}
|
|
55
74
|
const checker = parserServices.program.getTypeChecker();
|
|
56
75
|
function isReactComponentType(node) {
|
|
@@ -251,16 +270,7 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
251
270
|
// Check function declarations and variable declarations for callback functions
|
|
252
271
|
'FunctionDeclaration, VariableDeclarator'(node) {
|
|
253
272
|
const functionName = node.id?.type === 'Identifier' ? node.id.name : undefined;
|
|
254
|
-
if (functionName && functionName
|
|
255
|
-
// Skip autofixing for "handler" and "handlers"
|
|
256
|
-
if (functionName === 'handler' || functionName === 'handlers') {
|
|
257
|
-
context.report({
|
|
258
|
-
node,
|
|
259
|
-
messageId: 'callbackFunctionPrefix',
|
|
260
|
-
data: { functionName },
|
|
261
|
-
});
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
273
|
+
if (functionName && hasHandlePrefix(functionName) && node.id) {
|
|
264
274
|
// Skip autofixing for class parameters and getters
|
|
265
275
|
const parent = node.parent;
|
|
266
276
|
if (parent?.type === utils_1.AST_NODE_TYPES.PropertyDefinition ||
|
|
@@ -342,17 +352,8 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
342
352
|
'MethodDefinition, Property'(node) {
|
|
343
353
|
if (node.key.type === 'Identifier' &&
|
|
344
354
|
node.key.name &&
|
|
345
|
-
node.key.name
|
|
355
|
+
hasHandlePrefix(node.key.name)) {
|
|
346
356
|
const name = node.key.name;
|
|
347
|
-
// Skip autofixing for "handler" and "handlers"
|
|
348
|
-
if (name === 'handler' || name === 'handlers') {
|
|
349
|
-
context.report({
|
|
350
|
-
node: node.key,
|
|
351
|
-
messageId: 'callbackFunctionPrefix',
|
|
352
|
-
data: { functionName: name },
|
|
353
|
-
});
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
357
|
// Skip autofixing for class parameters and getters
|
|
357
358
|
if (node.type === 'MethodDefinition' && node.kind === 'get') {
|
|
358
359
|
context.report({
|
|
@@ -377,7 +378,7 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
377
378
|
// Check constructor parameters
|
|
378
379
|
TSParameterProperty(node) {
|
|
379
380
|
if (node.parameter.type === 'Identifier' &&
|
|
380
|
-
node.parameter.name
|
|
381
|
+
hasHandlePrefix(node.parameter.name)) {
|
|
381
382
|
context.report({
|
|
382
383
|
node,
|
|
383
384
|
messageId: 'callbackFunctionPrefix',
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
type MessageIds = 'multipleExportedComponents' | 'multipleExportedClasses';
|
|
2
|
+
export declare const enforceSingleExportedUnitPerFile: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
3
|
+
export {};
|