@blumintinc/eslint-plugin-blumint 1.12.6 → 1.14.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 +171 -99
- package/lib/index.js +300 -17
- package/lib/rules/array-methods-this-context.js +20 -2
- package/lib/rules/avoid-utils-directory.js +11 -2
- package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
- package/lib/rules/consistent-callback-naming.js +1 -1
- package/lib/rules/dynamic-https-errors.d.ts +3 -1
- package/lib/rules/dynamic-https-errors.js +48 -11
- package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
- package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +52 -96
- package/lib/rules/enforce-assert-throws.d.ts +3 -1
- package/lib/rules/enforce-assert-throws.js +493 -51
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
- package/lib/rules/enforce-boolean-naming-prefixes.js +820 -39
- package/lib/rules/enforce-callable-types.js +4 -4
- package/lib/rules/enforce-callback-memo.js +143 -18
- package/lib/rules/enforce-centralized-mock-firestore.js +5 -2
- package/lib/rules/enforce-console-error.d.ts +3 -0
- package/lib/rules/enforce-console-error.js +278 -0
- package/lib/rules/enforce-css-media-queries.js +24 -46
- package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
- package/lib/rules/enforce-dynamic-file-naming.js +53 -28
- package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- package/lib/rules/enforce-early-destructuring.d.ts +2 -0
- package/lib/rules/enforce-early-destructuring.js +980 -0
- package/lib/rules/enforce-empty-object-check.d.ts +11 -0
- package/lib/rules/enforce-empty-object-check.js +502 -0
- package/lib/rules/enforce-exported-function-types.js +4 -4
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +262 -0
- package/lib/rules/enforce-firestore-doc-ref-generic.js +350 -3
- package/lib/rules/enforce-firestore-facade.js +349 -157
- package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
- package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
- package/lib/rules/enforce-firestore-set-merge.js +3 -4
- package/lib/rules/enforce-global-constants.d.ts +4 -1
- package/lib/rules/enforce-global-constants.js +240 -12
- package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
- package/lib/rules/enforce-memoize-async.d.ts +2 -1
- package/lib/rules/enforce-memoize-async.js +92 -27
- package/lib/rules/enforce-memoize-getters.d.ts +3 -0
- package/lib/rules/enforce-memoize-getters.js +147 -0
- package/lib/rules/enforce-microdiff.js +3 -2
- package/lib/rules/enforce-object-literal-as-const.js +4 -4
- package/lib/rules/enforce-positive-naming.js +9 -7
- package/lib/rules/enforce-props-argument-name.d.ts +2 -7
- package/lib/rules/enforce-props-argument-name.js +165 -151
- package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
- package/lib/rules/enforce-props-naming-consistency.js +145 -0
- package/lib/rules/enforce-querykey-ts.d.ts +9 -0
- package/lib/rules/enforce-querykey-ts.js +274 -0
- package/lib/rules/enforce-safe-stringify.js +1 -1
- package/lib/rules/enforce-serializable-params.js +3 -3
- package/lib/rules/enforce-singular-type-names.js +10 -6
- package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
- package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
- package/lib/rules/enforce-storage-context.d.ts +9 -0
- package/lib/rules/enforce-storage-context.js +646 -0
- package/lib/rules/enforce-timestamp-now.js +23 -7
- package/lib/rules/enforce-transform-memoization.d.ts +4 -0
- package/lib/rules/enforce-transform-memoization.js +416 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +100 -0
- package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
- package/lib/rules/enforce-unique-cursor-headers.js +365 -0
- package/lib/rules/enforce-verb-noun-naming.js +90 -14
- package/lib/rules/ensure-pointer-events-none.js +28 -2
- package/lib/rules/export-if-in-doubt.js +27 -4
- package/lib/rules/extract-global-constants.js +92 -53
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
- package/lib/rules/fast-deep-equal-over-microdiff.js +351 -83
- package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
- package/lib/rules/firestore-transaction-reads-before-writes.js +277 -0
- package/lib/rules/flatten-push-calls.d.ts +2 -0
- package/lib/rules/flatten-push-calls.js +428 -0
- package/lib/rules/generic-starts-with-t.js +8 -2
- package/lib/rules/global-const-style.js +50 -19
- package/lib/rules/jsdoc-above-field.d.ts +11 -0
- package/lib/rules/jsdoc-above-field.js +208 -0
- package/lib/rules/key-only-outermost-element.js +21 -3
- package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
- package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
- package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
- package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
- package/lib/rules/memo-nested-react-components.d.ts +8 -0
- package/lib/rules/memo-nested-react-components.js +410 -0
- package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
- package/lib/rules/memoize-root-level-hocs.js +242 -0
- package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
- package/lib/rules/no-always-true-false-conditions.js +68 -8
- package/lib/rules/no-array-length-in-deps.d.ts +12 -0
- package/lib/rules/no-array-length-in-deps.js +314 -0
- package/lib/rules/no-async-array-filter.js +5 -2
- package/lib/rules/no-async-foreach.js +210 -12
- package/lib/rules/no-circular-references.js +18 -17
- package/lib/rules/no-class-instance-destructuring.js +55 -12
- package/lib/rules/no-complex-cloud-params.js +22 -5
- package/lib/rules/no-compositing-layer-props.js +6 -4
- package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
- package/lib/rules/no-console-error.d.ts +9 -0
- package/lib/rules/no-console-error.js +525 -0
- package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
- package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
- package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
- package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
- package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
- package/lib/rules/no-entire-object-hook-deps.js +320 -68
- package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
- package/lib/rules/no-excessive-parent-chain.js +273 -0
- package/lib/rules/no-explicit-return-type.d.ts +2 -1
- package/lib/rules/no-explicit-return-type.js +215 -26
- package/lib/rules/no-filter-without-return.js +5 -1
- package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
- package/lib/rules/no-firestore-jest-mock.js +126 -8
- package/lib/rules/no-firestore-object-arrays.js +398 -28
- package/lib/rules/no-handler-suffix.d.ts +12 -0
- package/lib/rules/no-handler-suffix.js +305 -0
- package/lib/rules/no-hungarian.js +14 -14
- package/lib/rules/no-inline-component-prop.d.ts +10 -0
- package/lib/rules/no-inline-component-prop.js +456 -0
- package/lib/rules/no-jsx-in-hooks.js +6 -1
- package/lib/rules/no-jsx-whitespace-literal.js +8 -2
- package/lib/rules/no-margin-properties.d.ts +7 -1
- package/lib/rules/no-margin-properties.js +22 -9
- package/lib/rules/no-memoize-on-static.js +9 -1
- package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
- package/lib/rules/no-misleading-boolean-prefixes.js +331 -0
- package/lib/rules/no-misused-switch-case.js +22 -1
- package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
- package/lib/rules/no-mixed-firestore-transactions.js +296 -34
- package/lib/rules/no-mock-firebase-admin.js +5 -2
- package/lib/rules/no-object-values-on-strings.js +10 -2
- package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.js +272 -0
- package/lib/rules/no-passthrough-getters.d.ts +4 -0
- package/lib/rules/no-passthrough-getters.js +206 -0
- package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
- package/lib/rules/no-redundant-annotation-assertion.js +402 -0
- package/lib/rules/no-redundant-param-types.js +16 -6
- package/lib/rules/no-redundant-this-params.d.ts +3 -0
- package/lib/rules/no-redundant-this-params.js +459 -0
- package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +328 -0
- package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
- package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
- package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
- package/lib/rules/no-restricted-properties-fix.js +173 -0
- package/lib/rules/no-separate-loading-state.d.ts +8 -0
- package/lib/rules/no-separate-loading-state.js +133 -0
- package/lib/rules/no-stale-state-across-await.d.ts +1 -0
- package/lib/rules/no-stale-state-across-await.js +185 -0
- package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
- package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
- package/lib/rules/no-type-assertion-returns.js +49 -49
- package/lib/rules/no-undefined-null-passthrough.js +217 -40
- package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
- package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
- package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
- package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
- package/lib/rules/no-unnecessary-destructuring.js +14 -4
- package/lib/rules/no-unnecessary-verb-suffix.js +2 -3
- package/lib/rules/no-unpinned-dependencies.js +36 -5
- package/lib/rules/no-unsafe-firestore-spread.js +8 -5
- package/lib/rules/no-unused-props.d.ts +5 -2
- package/lib/rules/no-unused-props.js +451 -112
- package/lib/rules/no-unused-usestate.js +6 -2
- package/lib/rules/no-useless-fragment.js +28 -2
- package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
- package/lib/rules/no-useless-usememo-primitives.js +393 -0
- package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
- package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
- package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
- package/lib/rules/omit-index-html.d.ts +2 -1
- package/lib/rules/omit-index-html.js +62 -7
- package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
- package/lib/rules/optimize-object-boolean-conditions.js +236 -0
- package/lib/rules/parallelize-async-operations.d.ts +8 -0
- package/lib/rules/parallelize-async-operations.js +394 -0
- package/lib/rules/prefer-batch-operations.d.ts +1 -3
- package/lib/rules/prefer-batch-operations.js +228 -35
- package/lib/rules/prefer-block-comments-for-declarations.js +22 -7
- package/lib/rules/prefer-clone-deep.js +3 -3
- package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
- package/lib/rules/prefer-destructuring-no-class.js +244 -59
- package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
- package/lib/rules/prefer-docsetter-setall.js +243 -0
- package/lib/rules/prefer-document-flattening.d.ts +4 -0
- package/lib/rules/prefer-document-flattening.js +239 -0
- package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
- package/lib/rules/prefer-field-paths-in-transforms.js +251 -0
- package/lib/rules/prefer-fragment-component.js +4 -4
- package/lib/rules/prefer-fragment-shorthand.js +2 -1
- package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
- package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
- package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
- package/lib/rules/prefer-global-router-state-key.js +305 -31
- package/lib/rules/prefer-memoized-props.d.ts +3 -0
- package/lib/rules/prefer-memoized-props.js +445 -0
- package/lib/rules/prefer-next-dynamic.d.ts +9 -0
- package/lib/rules/prefer-next-dynamic.js +357 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +435 -0
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +7 -0
- package/lib/rules/prefer-nullish-coalescing-override.js +188 -0
- package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
- package/lib/rules/prefer-params-over-parent-id.js +277 -0
- package/lib/rules/prefer-settings-object.js +23 -15
- package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
- package/lib/rules/prefer-type-over-interface.js +7 -2
- package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
- package/lib/rules/prefer-url-tostring-over-tojson.js +169 -0
- package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
- package/lib/rules/prefer-use-deep-compare-memo.js +431 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +25 -2
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
- package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
- package/lib/rules/prevent-children-clobber.d.ts +2 -0
- package/lib/rules/prevent-children-clobber.js +536 -0
- package/lib/rules/react-memoize-literals.d.ts +4 -0
- package/lib/rules/react-memoize-literals.js +495 -0
- package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
- package/lib/rules/react-usememo-should-be-component.js +144 -78
- package/lib/rules/require-dynamic-firebase-imports.js +2 -1
- package/lib/rules/require-hooks-default-params.js +17 -16
- package/lib/rules/require-https-error-cause.d.ts +4 -0
- package/lib/rules/require-https-error-cause.js +136 -0
- package/lib/rules/require-https-error.js +43 -21
- package/lib/rules/require-image-optimized.js +2 -2
- package/lib/rules/require-memo.js +12 -2
- package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
- package/lib/rules/require-memoize-jsx-returners.js +485 -0
- package/lib/rules/require-usememo-object-literals.js +9 -4
- package/lib/rules/semantic-function-prefixes.js +38 -18
- package/lib/rules/sync-onwrite-name-func.js +5 -1
- package/lib/rules/test-file-location-enforcement.d.ts +1 -0
- package/lib/rules/test-file-location-enforcement.js +57 -0
- package/lib/rules/use-custom-link.js +6 -1
- package/lib/rules/use-custom-router.js +6 -1
- package/lib/rules/use-latest-callback.d.ts +3 -0
- package/lib/rules/use-latest-callback.js +271 -0
- package/lib/rules/vertically-group-related-functions.d.ts +16 -0
- package/lib/rules/vertically-group-related-functions.js +480 -0
- package/lib/utils/ASTHelpers.d.ts +13 -1
- package/lib/utils/ASTHelpers.js +34 -0
- package/lib/utils/createRule.js +1 -1
- package/lib/utils/getMethodName.d.ts +27 -0
- package/lib/utils/getMethodName.js +35 -0
- package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
- package/lib/utils/ruleTester.d.ts +1 -0
- package/lib/utils/ruleTester.js +4 -1
- package/lib/utils/tsTypeClassifier.d.ts +30 -0
- package/lib/utils/tsTypeClassifier.js +149 -0
- package/package.json +23 -14
- package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
type Options = [
|
|
3
|
+
{
|
|
4
|
+
objectNamePattern?: string[];
|
|
5
|
+
ignoreInLoops?: boolean;
|
|
6
|
+
emptyCheckFunctions?: string[];
|
|
7
|
+
}
|
|
8
|
+
];
|
|
9
|
+
type MessageIds = 'missingEmptyObjectCheck';
|
|
10
|
+
export declare const enforceEmptyObjectCheck: TSESLint.RuleModule<MessageIds, Options>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.enforceEmptyObjectCheck = void 0;
|
|
27
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
28
|
+
const ts = __importStar(require("typescript"));
|
|
29
|
+
const createRule_1 = require("../utils/createRule");
|
|
30
|
+
const DEFAULT_OBJECT_SUFFIXES = [
|
|
31
|
+
'Config',
|
|
32
|
+
'Configs',
|
|
33
|
+
'Data',
|
|
34
|
+
'Info',
|
|
35
|
+
'Settings',
|
|
36
|
+
'Options',
|
|
37
|
+
'Props',
|
|
38
|
+
'State',
|
|
39
|
+
'Response',
|
|
40
|
+
'Result',
|
|
41
|
+
'Payload',
|
|
42
|
+
'Map',
|
|
43
|
+
'Record',
|
|
44
|
+
'Object',
|
|
45
|
+
'Obj',
|
|
46
|
+
'Details',
|
|
47
|
+
'Meta',
|
|
48
|
+
'Profile',
|
|
49
|
+
'Request',
|
|
50
|
+
'Params',
|
|
51
|
+
'Context',
|
|
52
|
+
];
|
|
53
|
+
const DEFAULT_EMPTY_CHECK_FUNCTIONS = ['isEmpty'];
|
|
54
|
+
const BOOLEAN_PREFIXES = [
|
|
55
|
+
'is',
|
|
56
|
+
'has',
|
|
57
|
+
'can',
|
|
58
|
+
'should',
|
|
59
|
+
'was',
|
|
60
|
+
'were',
|
|
61
|
+
'will',
|
|
62
|
+
'did',
|
|
63
|
+
];
|
|
64
|
+
const NON_OBJECT_LIKE_NAMES = [
|
|
65
|
+
'count',
|
|
66
|
+
'index',
|
|
67
|
+
'idx',
|
|
68
|
+
'length',
|
|
69
|
+
'size',
|
|
70
|
+
'total',
|
|
71
|
+
'flag',
|
|
72
|
+
'enabled',
|
|
73
|
+
'ready',
|
|
74
|
+
'items',
|
|
75
|
+
'item',
|
|
76
|
+
'list',
|
|
77
|
+
'lists',
|
|
78
|
+
'array',
|
|
79
|
+
'arr',
|
|
80
|
+
];
|
|
81
|
+
function hasBooleanPrefixBoundary(name) {
|
|
82
|
+
const lower = name.toLowerCase();
|
|
83
|
+
return BOOLEAN_PREFIXES.some((prefix) => {
|
|
84
|
+
if (!lower.startsWith(prefix)) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
const boundary = name.charAt(prefix.length);
|
|
88
|
+
return boundary !== '' && boundary >= 'A' && boundary <= 'Z';
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function isLoopLike(node) {
|
|
92
|
+
return (node.type === utils_1.AST_NODE_TYPES.ForStatement ||
|
|
93
|
+
node.type === utils_1.AST_NODE_TYPES.ForInStatement ||
|
|
94
|
+
node.type === utils_1.AST_NODE_TYPES.ForOfStatement ||
|
|
95
|
+
node.type === utils_1.AST_NODE_TYPES.WhileStatement ||
|
|
96
|
+
node.type === utils_1.AST_NODE_TYPES.DoWhileStatement);
|
|
97
|
+
}
|
|
98
|
+
function isInsideLoop(node) {
|
|
99
|
+
let current = node;
|
|
100
|
+
while (current && current.parent) {
|
|
101
|
+
if (isLoopLike(current.parent)) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
current = current.parent;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
function isObjectLikeName(name, patterns) {
|
|
109
|
+
const lower = name.toLowerCase();
|
|
110
|
+
if (hasBooleanPrefixBoundary(name)) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
if (NON_OBJECT_LIKE_NAMES.includes(lower)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
for (const pattern of patterns) {
|
|
117
|
+
if (name.endsWith(pattern) || lower.endsWith(pattern.toLowerCase())) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
function isNullableType(type) {
|
|
124
|
+
return ((type.flags & ts.TypeFlags.Null) !== 0 ||
|
|
125
|
+
(type.flags & ts.TypeFlags.Undefined) !== 0 ||
|
|
126
|
+
(type.flags & ts.TypeFlags.Void) !== 0);
|
|
127
|
+
}
|
|
128
|
+
function isNonObjectPrimitive(type) {
|
|
129
|
+
const flag = type.flags;
|
|
130
|
+
return ((flag & ts.TypeFlags.StringLike) !== 0 ||
|
|
131
|
+
(flag & ts.TypeFlags.NumberLike) !== 0 ||
|
|
132
|
+
(flag & ts.TypeFlags.BooleanLike) !== 0 ||
|
|
133
|
+
(flag & ts.TypeFlags.BigIntLike) !== 0 ||
|
|
134
|
+
(flag & ts.TypeFlags.ESSymbolLike) !== 0 ||
|
|
135
|
+
(flag & ts.TypeFlags.EnumLike) !== 0);
|
|
136
|
+
}
|
|
137
|
+
function isAnyOrUnknown(type) {
|
|
138
|
+
return ((type.flags & ts.TypeFlags.Any) !== 0 ||
|
|
139
|
+
(type.flags & ts.TypeFlags.Unknown) !== 0);
|
|
140
|
+
}
|
|
141
|
+
function hasRequiredProperties(type, checker) {
|
|
142
|
+
const properties = checker.getPropertiesOfType(type);
|
|
143
|
+
return properties.some((property) => (property.getFlags() & ts.SymbolFlags.Optional) === 0);
|
|
144
|
+
}
|
|
145
|
+
function isObjectLikeType(type, checker) {
|
|
146
|
+
if (type.isUnion()) {
|
|
147
|
+
let hasObject = false;
|
|
148
|
+
let hasUnknown = false;
|
|
149
|
+
let hasNonObject = false;
|
|
150
|
+
let hasNonNullable = false;
|
|
151
|
+
for (const part of type.types) {
|
|
152
|
+
if (isNullableType(part)) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
hasNonNullable = true;
|
|
156
|
+
if (isNonObjectPrimitive(part)) {
|
|
157
|
+
hasNonObject = true;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const analysis = isObjectLikeType(part, checker);
|
|
161
|
+
if (analysis === 'object') {
|
|
162
|
+
hasObject = true;
|
|
163
|
+
}
|
|
164
|
+
else if (analysis === 'unknown') {
|
|
165
|
+
hasUnknown = true;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
hasNonObject = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (hasObject) {
|
|
172
|
+
return 'object';
|
|
173
|
+
}
|
|
174
|
+
if (!hasNonNullable) {
|
|
175
|
+
return 'non-object';
|
|
176
|
+
}
|
|
177
|
+
if (hasUnknown) {
|
|
178
|
+
return 'unknown';
|
|
179
|
+
}
|
|
180
|
+
return hasNonObject ? 'non-object' : 'unknown';
|
|
181
|
+
}
|
|
182
|
+
if ((type.flags & ts.TypeFlags.Intersection) !== 0) {
|
|
183
|
+
const intersectionType = type;
|
|
184
|
+
let hasObject = false;
|
|
185
|
+
let hasUnknown = false;
|
|
186
|
+
for (const part of intersectionType.types) {
|
|
187
|
+
const analysis = isObjectLikeType(part, checker);
|
|
188
|
+
if (analysis === 'non-object') {
|
|
189
|
+
return 'non-object';
|
|
190
|
+
}
|
|
191
|
+
if (analysis === 'object') {
|
|
192
|
+
hasObject = true;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
hasUnknown = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (hasObject) {
|
|
199
|
+
return 'object';
|
|
200
|
+
}
|
|
201
|
+
return hasUnknown ? 'unknown' : 'non-object';
|
|
202
|
+
}
|
|
203
|
+
if (isAnyOrUnknown(type)) {
|
|
204
|
+
return 'unknown';
|
|
205
|
+
}
|
|
206
|
+
if (isNonObjectPrimitive(type) || isNullableType(type)) {
|
|
207
|
+
return 'non-object';
|
|
208
|
+
}
|
|
209
|
+
if ((type.flags & ts.TypeFlags.Object) === 0) {
|
|
210
|
+
return 'non-object';
|
|
211
|
+
}
|
|
212
|
+
if (checker.isArrayType(type) || checker.isTupleType(type)) {
|
|
213
|
+
return 'non-object';
|
|
214
|
+
}
|
|
215
|
+
if (type.getCallSignatures().length > 0) {
|
|
216
|
+
return 'non-object';
|
|
217
|
+
}
|
|
218
|
+
if (hasRequiredProperties(type, checker)) {
|
|
219
|
+
return 'non-object';
|
|
220
|
+
}
|
|
221
|
+
return 'object';
|
|
222
|
+
}
|
|
223
|
+
function isObjectKeysLengthExpression(node, name) {
|
|
224
|
+
if (node.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
225
|
+
!node.computed &&
|
|
226
|
+
node.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
227
|
+
node.property.name === 'length' &&
|
|
228
|
+
node.object.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
229
|
+
node.object.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
230
|
+
!node.object.callee.computed &&
|
|
231
|
+
node.object.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
232
|
+
node.object.callee.object.name === 'Object' &&
|
|
233
|
+
node.object.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
234
|
+
node.object.callee.property.name === 'keys' &&
|
|
235
|
+
node.object.arguments.length === 1 &&
|
|
236
|
+
node.object.arguments[0].type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
237
|
+
node.object.arguments[0].name === name) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
function isZeroLiteral(node) {
|
|
243
|
+
return node.type === utils_1.AST_NODE_TYPES.Literal && node.value === 0;
|
|
244
|
+
}
|
|
245
|
+
function isLengthZeroComparison(node, name) {
|
|
246
|
+
const { operator, left, right } = node;
|
|
247
|
+
const leftIsLength = isObjectKeysLengthExpression(left, name);
|
|
248
|
+
const rightIsLength = isObjectKeysLengthExpression(right, name);
|
|
249
|
+
const leftIsZero = isZeroLiteral(left);
|
|
250
|
+
const rightIsZero = isZeroLiteral(right);
|
|
251
|
+
/**
|
|
252
|
+
* Only zero-length checks signal emptiness: length never drops below zero, and
|
|
253
|
+
* `> 0` means data is present. Restrict operators to equality and zero-bound
|
|
254
|
+
* comparisons to avoid mistaking impossible `length < 0` or presence checks for
|
|
255
|
+
* valid emptiness guards.
|
|
256
|
+
*/
|
|
257
|
+
if (operator === '===' || operator === '==') {
|
|
258
|
+
return (leftIsLength && rightIsZero) || (rightIsLength && leftIsZero);
|
|
259
|
+
}
|
|
260
|
+
if (operator === '<=') {
|
|
261
|
+
return leftIsLength && rightIsZero;
|
|
262
|
+
}
|
|
263
|
+
if (operator === '>=') {
|
|
264
|
+
return leftIsZero && rightIsLength;
|
|
265
|
+
}
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
function conditionHasEmptyCheck(node, name, emptyCheckFunctions, negationDepth = 0) {
|
|
269
|
+
if (!node)
|
|
270
|
+
return false;
|
|
271
|
+
switch (node.type) {
|
|
272
|
+
case utils_1.AST_NODE_TYPES.LogicalExpression:
|
|
273
|
+
return (conditionHasEmptyCheck(node.left, name, emptyCheckFunctions, negationDepth) ||
|
|
274
|
+
conditionHasEmptyCheck(node.right, name, emptyCheckFunctions, negationDepth));
|
|
275
|
+
case utils_1.AST_NODE_TYPES.BinaryExpression:
|
|
276
|
+
if (isLengthZeroComparison(node, name)) {
|
|
277
|
+
return negationDepth % 2 === 0;
|
|
278
|
+
}
|
|
279
|
+
return (conditionHasEmptyCheck(node.left, name, emptyCheckFunctions, negationDepth) ||
|
|
280
|
+
conditionHasEmptyCheck(node.right, name, emptyCheckFunctions, negationDepth));
|
|
281
|
+
case utils_1.AST_NODE_TYPES.UnaryExpression:
|
|
282
|
+
if (node.operator === '!') {
|
|
283
|
+
return conditionHasEmptyCheck(node.argument, name, emptyCheckFunctions, negationDepth + 1);
|
|
284
|
+
}
|
|
285
|
+
return conditionHasEmptyCheck(node.argument, name, emptyCheckFunctions, negationDepth);
|
|
286
|
+
case utils_1.AST_NODE_TYPES.CallExpression: {
|
|
287
|
+
const callee = node.callee;
|
|
288
|
+
const firstArgIsTarget = node.arguments[0] &&
|
|
289
|
+
node.arguments[0].type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
290
|
+
node.arguments[0].name === name;
|
|
291
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
292
|
+
emptyCheckFunctions.has(callee.name) &&
|
|
293
|
+
firstArgIsTarget) {
|
|
294
|
+
return negationDepth % 2 === 0;
|
|
295
|
+
}
|
|
296
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
297
|
+
!callee.computed &&
|
|
298
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
299
|
+
emptyCheckFunctions.has(callee.property.name) &&
|
|
300
|
+
firstArgIsTarget) {
|
|
301
|
+
return negationDepth % 2 === 0;
|
|
302
|
+
}
|
|
303
|
+
return (conditionHasEmptyCheck(callee, name, emptyCheckFunctions, negationDepth) ||
|
|
304
|
+
node.arguments.some((argument) => conditionHasEmptyCheck(argument, name, emptyCheckFunctions, negationDepth)));
|
|
305
|
+
}
|
|
306
|
+
case utils_1.AST_NODE_TYPES.MemberExpression:
|
|
307
|
+
/**
|
|
308
|
+
* `!Object.keys(name).length` counts as an emptiness check through negation
|
|
309
|
+
* depth; comparisons like `length < 0` or `length > 0` remain excluded because
|
|
310
|
+
* length is never negative and `> 0` signals presence rather than emptiness.
|
|
311
|
+
*/
|
|
312
|
+
if (isObjectKeysLengthExpression(node, name)) {
|
|
313
|
+
return negationDepth % 2 === 1;
|
|
314
|
+
}
|
|
315
|
+
return conditionHasEmptyCheck(node.object, name, emptyCheckFunctions, negationDepth);
|
|
316
|
+
case utils_1.AST_NODE_TYPES.ConditionalExpression:
|
|
317
|
+
return (conditionHasEmptyCheck(node.test, name, emptyCheckFunctions, negationDepth) ||
|
|
318
|
+
conditionHasEmptyCheck(node.consequent, name, emptyCheckFunctions, negationDepth) ||
|
|
319
|
+
conditionHasEmptyCheck(node.alternate, name, emptyCheckFunctions, negationDepth));
|
|
320
|
+
default:
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function collectNegations(node, results) {
|
|
325
|
+
if (node.type === utils_1.AST_NODE_TYPES.UnaryExpression && node.operator === '!') {
|
|
326
|
+
results.push(node);
|
|
327
|
+
}
|
|
328
|
+
else if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
|
|
329
|
+
collectNegations(node.left, results);
|
|
330
|
+
collectNegations(node.right, results);
|
|
331
|
+
}
|
|
332
|
+
else if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
|
|
333
|
+
collectNegations(node.test, results);
|
|
334
|
+
collectNegations(node.consequent, results);
|
|
335
|
+
collectNegations(node.alternate, results);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function getRootCondition(node) {
|
|
339
|
+
let current = node;
|
|
340
|
+
while (current && current.parent) {
|
|
341
|
+
const parent = current.parent;
|
|
342
|
+
if ((parent.type === utils_1.AST_NODE_TYPES.IfStatement && parent.test === current) ||
|
|
343
|
+
(parent.type === utils_1.AST_NODE_TYPES.WhileStatement &&
|
|
344
|
+
parent.test === current) ||
|
|
345
|
+
(parent.type === utils_1.AST_NODE_TYPES.DoWhileStatement &&
|
|
346
|
+
parent.test === current) ||
|
|
347
|
+
(parent.type === utils_1.AST_NODE_TYPES.ForStatement &&
|
|
348
|
+
parent.test === current) ||
|
|
349
|
+
(parent.type === utils_1.AST_NODE_TYPES.ConditionalExpression &&
|
|
350
|
+
parent.test === current)) {
|
|
351
|
+
return current;
|
|
352
|
+
}
|
|
353
|
+
if (parent.type === utils_1.AST_NODE_TYPES.LogicalExpression ||
|
|
354
|
+
parent.type === utils_1.AST_NODE_TYPES.BinaryExpression ||
|
|
355
|
+
parent.type === utils_1.AST_NODE_TYPES.UnaryExpression ||
|
|
356
|
+
parent.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
|
|
357
|
+
current = parent;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
exports.enforceEmptyObjectCheck = (0, createRule_1.createRule)({
|
|
365
|
+
name: 'enforce-empty-object-check',
|
|
366
|
+
meta: {
|
|
367
|
+
type: 'problem',
|
|
368
|
+
docs: {
|
|
369
|
+
description: 'Ensure object existence checks also guard against empty objects so that empty payloads are treated like missing data.',
|
|
370
|
+
recommended: 'error',
|
|
371
|
+
},
|
|
372
|
+
fixable: 'code',
|
|
373
|
+
schema: [
|
|
374
|
+
{
|
|
375
|
+
type: 'object',
|
|
376
|
+
properties: {
|
|
377
|
+
objectNamePattern: {
|
|
378
|
+
type: 'array',
|
|
379
|
+
items: { type: 'string' },
|
|
380
|
+
},
|
|
381
|
+
ignoreInLoops: {
|
|
382
|
+
type: 'boolean',
|
|
383
|
+
},
|
|
384
|
+
emptyCheckFunctions: {
|
|
385
|
+
type: 'array',
|
|
386
|
+
items: { type: 'string' },
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
additionalProperties: false,
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
messages: {
|
|
393
|
+
missingEmptyObjectCheck: 'What\'s wrong: "{{name}}" is only checked for falsiness, so `{}` passes the guard. Why it matters: empty payloads or configs behave like missing data and can execute "has data" logic incorrectly. How to fix: also check emptiness (for example, Object.keys({{name}}).length === 0 or a configured empty-check helper).',
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
defaultOptions: [{}],
|
|
397
|
+
create(context) {
|
|
398
|
+
const sourceCode = context.getSourceCode();
|
|
399
|
+
const parserServices = sourceCode.parserServices;
|
|
400
|
+
const checker = parserServices?.program?.getTypeChecker();
|
|
401
|
+
const options = context.options[0] ?? {};
|
|
402
|
+
const { objectNamePattern = [], ignoreInLoops = false, emptyCheckFunctions = [], } = options;
|
|
403
|
+
const patternSet = new Set([
|
|
404
|
+
...DEFAULT_OBJECT_SUFFIXES,
|
|
405
|
+
...objectNamePattern,
|
|
406
|
+
]);
|
|
407
|
+
const emptyCheckFunctionsSet = new Set([
|
|
408
|
+
...DEFAULT_EMPTY_CHECK_FUNCTIONS,
|
|
409
|
+
...emptyCheckFunctions,
|
|
410
|
+
]);
|
|
411
|
+
const processedExpressions = new WeakSet();
|
|
412
|
+
const processedNegations = new WeakSet();
|
|
413
|
+
function isLikelyObject(identifier) {
|
|
414
|
+
if (checker && parserServices?.esTreeNodeToTSNodeMap) {
|
|
415
|
+
try {
|
|
416
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(identifier);
|
|
417
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
418
|
+
const analysis = isObjectLikeType(type, checker);
|
|
419
|
+
if (analysis === 'object') {
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
if (analysis === 'non-object') {
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
// TypeScript parser services can throw when AST-to-TS node mapping fails; fall back to naming heuristic so linting does not crash.
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return isObjectLikeName(identifier.name, patternSet);
|
|
431
|
+
}
|
|
432
|
+
function reportNegation(node, identifier) {
|
|
433
|
+
if (processedNegations.has(node)) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
processedNegations.add(node);
|
|
437
|
+
if (ignoreInLoops && isInsideLoop(node)) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const conditionRoot = getRootCondition(node);
|
|
441
|
+
if (conditionRoot &&
|
|
442
|
+
conditionHasEmptyCheck(conditionRoot, identifier.name, emptyCheckFunctionsSet)) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (!isLikelyObject(identifier)) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
context.report({
|
|
449
|
+
node,
|
|
450
|
+
messageId: 'missingEmptyObjectCheck',
|
|
451
|
+
data: {
|
|
452
|
+
name: identifier.name,
|
|
453
|
+
},
|
|
454
|
+
fix(fixer) {
|
|
455
|
+
const identifierText = sourceCode.getText(identifier);
|
|
456
|
+
const replacement = `(${node.operator}${identifierText} || Object.keys(${identifierText}).length === 0)`;
|
|
457
|
+
return fixer.replaceText(node, replacement);
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
function handleTestExpression(expression) {
|
|
462
|
+
if (processedExpressions.has(expression)) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
processedExpressions.add(expression);
|
|
466
|
+
const negations = [];
|
|
467
|
+
collectNegations(expression, negations);
|
|
468
|
+
for (const negation of negations) {
|
|
469
|
+
if (negation.argument.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
470
|
+
negation.operator === '!') {
|
|
471
|
+
reportNegation(negation, negation.argument);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
IfStatement(node) {
|
|
477
|
+
if (node.test) {
|
|
478
|
+
handleTestExpression(node.test);
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
WhileStatement(node) {
|
|
482
|
+
if (node.test) {
|
|
483
|
+
handleTestExpression(node.test);
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
DoWhileStatement(node) {
|
|
487
|
+
if (node.test) {
|
|
488
|
+
handleTestExpression(node.test);
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
ForStatement(node) {
|
|
492
|
+
if (node.test) {
|
|
493
|
+
handleTestExpression(node.test);
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
ConditionalExpression(node) {
|
|
497
|
+
handleTestExpression(node.test);
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
//# sourceMappingURL=enforce-empty-object-check.js.map
|
|
@@ -14,9 +14,9 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
14
14
|
},
|
|
15
15
|
schema: [],
|
|
16
16
|
messages: {
|
|
17
|
-
missingExportedType: 'Type {{typeName}}
|
|
18
|
-
missingExportedReturnType: 'Return type {{typeName}}
|
|
19
|
-
missingExportedPropsType: 'Props type {{typeName}}
|
|
17
|
+
missingExportedType: 'Type "{{typeName}}" is used in a parameter of an exported function but is not exported. Callers cannot import the parameter contract, which forces duplicate or ad-hoc types and makes the API drift. Export the type (e.g., `export type {{typeName}} = ...`) or reuse an already exported type.',
|
|
18
|
+
missingExportedReturnType: 'Return type "{{typeName}}" belongs to an exported function but is not exported. Consumers cannot reference the returned shape for validation or composition, which leads to duplicated types and diverging contracts. Export the return type alias or interface so callers can import the shared contract.',
|
|
19
|
+
missingExportedPropsType: 'Props type "{{typeName}}" is used by an exported React component but is not exported. Other modules cannot type the props when composing the component and end up recreating the shape. Export the props type (e.g., `export type {{typeName}} = ...`) or reference an existing exported props contract.',
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
22
|
defaultOptions: [],
|
|
@@ -402,7 +402,7 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
402
402
|
if (isBuiltInType(typeName)) {
|
|
403
403
|
return true;
|
|
404
404
|
}
|
|
405
|
-
const sourceCode = context.
|
|
405
|
+
const sourceCode = context.sourceCode;
|
|
406
406
|
const program = sourceCode.ast;
|
|
407
407
|
// Check for imported types first - if found, return true immediately
|
|
408
408
|
// since imported types are already available to consumers
|