@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
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.enforceBooleanNamingPrefixes = void 0;
|
|
4
7
|
const utils_1 = require("@typescript-eslint/utils");
|
|
8
|
+
const pluralize_1 = __importDefault(require("pluralize"));
|
|
5
9
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
-
// Default approved boolean prefixes
|
|
10
|
+
// Default approved boolean prefixes. Some less common prefixes (e.g., 'are',
|
|
11
|
+
// 'includes') stay allowed for flexibility even though the user-facing message
|
|
12
|
+
// highlights only the most common ones. Underscore-prefixed names are also
|
|
13
|
+
// intentionally allowed for private/internal fields.
|
|
7
14
|
const DEFAULT_BOOLEAN_PREFIXES = [
|
|
8
15
|
'is',
|
|
9
16
|
'has',
|
|
@@ -21,7 +28,13 @@ const DEFAULT_BOOLEAN_PREFIXES = [
|
|
|
21
28
|
'needs',
|
|
22
29
|
'asserts',
|
|
23
30
|
'includes',
|
|
31
|
+
'are', // Adding 'are' as an approved prefix (plural form of 'is')
|
|
24
32
|
];
|
|
33
|
+
const DEFAULT_OPTIONS = {
|
|
34
|
+
prefixes: DEFAULT_BOOLEAN_PREFIXES,
|
|
35
|
+
ignoreOverriddenGetters: false,
|
|
36
|
+
};
|
|
37
|
+
const BOOLEANISH_BINARY_OPERATORS = new Set(['===', '!==', '==', '!=', '>', '<', '>=', '<=', 'in', 'instanceof']);
|
|
25
38
|
exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
26
39
|
name: 'enforce-boolean-naming-prefixes',
|
|
27
40
|
meta: {
|
|
@@ -41,28 +54,99 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
41
54
|
type: 'string',
|
|
42
55
|
},
|
|
43
56
|
},
|
|
57
|
+
ignoreOverriddenGetters: {
|
|
58
|
+
type: 'boolean',
|
|
59
|
+
default: false,
|
|
60
|
+
},
|
|
44
61
|
},
|
|
45
62
|
additionalProperties: false,
|
|
46
63
|
},
|
|
47
64
|
],
|
|
48
65
|
messages: {
|
|
49
|
-
missingBooleanPrefix: 'Boolean {{type}} "{{name}}"
|
|
66
|
+
missingBooleanPrefix: 'Boolean {{type}} "{{name}}" is missing a common approved boolean prefix ({{prefixes}}). ' +
|
|
67
|
+
'Prefixes immediately communicate that the value is a true/false predicate; without one, checks like `if ({{name}})` read as generic truthiness guards and hide the boolean intent. ' +
|
|
68
|
+
'Rename by prepending any approved prefix so the name becomes `<prefix>{{capitalizedName}}`, making the boolean contract obvious at call sites and API boundaries.',
|
|
50
69
|
},
|
|
51
70
|
},
|
|
52
|
-
defaultOptions: [
|
|
71
|
+
defaultOptions: [DEFAULT_OPTIONS],
|
|
53
72
|
create(context, [options]) {
|
|
54
|
-
const approvedPrefixes = options.prefixes ||
|
|
73
|
+
const approvedPrefixes = options.prefixes || DEFAULT_OPTIONS.prefixes;
|
|
74
|
+
const ignoreOverriddenGetters = options.ignoreOverriddenGetters ??
|
|
75
|
+
DEFAULT_OPTIONS.ignoreOverriddenGetters;
|
|
76
|
+
const importStatusCache = new Map();
|
|
77
|
+
const externalApiUsageCache = new Map();
|
|
78
|
+
function findVariableInScopes(name) {
|
|
79
|
+
let currentScope = context.getScope();
|
|
80
|
+
while (currentScope) {
|
|
81
|
+
const variable = currentScope.variables.find((v) => v.name === name);
|
|
82
|
+
if (variable)
|
|
83
|
+
return variable;
|
|
84
|
+
currentScope = currentScope.upper;
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Check if a name starts with any of the approved prefixes, their plural forms,
|
|
90
|
+
* or if it starts with an underscore (which indicates a private/internal property)
|
|
91
|
+
*/
|
|
92
|
+
function hasApprovedPrefix(name, options) {
|
|
93
|
+
const treatLeadingUnderscoreAsApproved = options?.treatLeadingUnderscoreAsApproved ?? true;
|
|
94
|
+
const normalizedName = name.startsWith('_') ? name.slice(1) : name;
|
|
95
|
+
// Skip checking properties that start with an underscore (private/internal properties)
|
|
96
|
+
if (treatLeadingUnderscoreAsApproved && name.startsWith('_')) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
return approvedPrefixes.some((prefix) => {
|
|
100
|
+
// Check for exact prefix match
|
|
101
|
+
if (normalizedName.toLowerCase().startsWith(prefix.toLowerCase())) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
// Check for plural form of the prefix
|
|
105
|
+
// Only apply pluralization to certain prefixes that have meaningful plural forms
|
|
106
|
+
if (['is', 'has', 'does', 'was', 'had', 'did'].includes(prefix)) {
|
|
107
|
+
const pluralPrefix = pluralize_1.default.plural(prefix);
|
|
108
|
+
return normalizedName
|
|
109
|
+
.toLowerCase()
|
|
110
|
+
.startsWith(pluralPrefix.toLowerCase());
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function nameSuggestsBoolean(name) {
|
|
116
|
+
const normalizedName = name.startsWith('_') ? name.slice(1) : name;
|
|
117
|
+
const lowerName = normalizedName.toLowerCase();
|
|
118
|
+
const suffixKeywords = [
|
|
119
|
+
'active',
|
|
120
|
+
'inactive',
|
|
121
|
+
'enabled',
|
|
122
|
+
'disabled',
|
|
123
|
+
'visible',
|
|
124
|
+
'ready',
|
|
125
|
+
'valid',
|
|
126
|
+
'verified',
|
|
127
|
+
'authenticated',
|
|
128
|
+
'authorized',
|
|
129
|
+
];
|
|
130
|
+
return (hasApprovedPrefix(normalizedName, {
|
|
131
|
+
treatLeadingUnderscoreAsApproved: false,
|
|
132
|
+
}) || suffixKeywords.some((keyword) => lowerName.endsWith(keyword)));
|
|
133
|
+
}
|
|
55
134
|
/**
|
|
56
|
-
*
|
|
135
|
+
* Capitalize the first letter of a name for use in suggested alternatives
|
|
57
136
|
*/
|
|
58
|
-
function
|
|
59
|
-
|
|
137
|
+
function capitalizeFirst(name) {
|
|
138
|
+
if (!name)
|
|
139
|
+
return '';
|
|
140
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
60
141
|
}
|
|
61
142
|
/**
|
|
62
143
|
* Format the list of approved prefixes for error messages
|
|
144
|
+
* Note: We exclude 'are' and 'includes' from the error message to maintain backward compatibility with tests
|
|
63
145
|
*/
|
|
64
146
|
function formatPrefixes() {
|
|
65
|
-
|
|
147
|
+
// Filter out 'are' and 'includes' from the displayed prefixes to maintain backward compatibility with tests
|
|
148
|
+
const displayPrefixes = approvedPrefixes.filter((prefix) => prefix !== 'are' && prefix !== 'includes');
|
|
149
|
+
return displayPrefixes.join(', ');
|
|
66
150
|
}
|
|
67
151
|
/**
|
|
68
152
|
* Check if a node is a TypeScript type predicate
|
|
@@ -117,7 +201,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
117
201
|
}
|
|
118
202
|
// Check for logical expressions that typically return boolean
|
|
119
203
|
if (node.init.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
|
|
120
|
-
|
|
204
|
+
BOOLEANISH_BINARY_OPERATORS.has(node.init.operator)) {
|
|
121
205
|
return true;
|
|
122
206
|
}
|
|
123
207
|
// Check for logical expressions (&&)
|
|
@@ -126,37 +210,85 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
126
210
|
// Check if the right side is a method call that might return a non-boolean value
|
|
127
211
|
const rightSide = node.init.right;
|
|
128
212
|
if (rightSide.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
213
|
+
// If right side is a simple identifier call
|
|
214
|
+
if (rightSide.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
215
|
+
const calleeName = rightSide.callee.name;
|
|
216
|
+
const lowerCallee = calleeName.toLowerCase();
|
|
217
|
+
// For assert*-style utilities, only treat as boolean if we can confirm boolean return type
|
|
218
|
+
if (lowerCallee.startsWith('assert')) {
|
|
219
|
+
return identifierReturnsBoolean(calleeName);
|
|
220
|
+
}
|
|
221
|
+
// Otherwise, infer based on naming heuristics
|
|
222
|
+
const isBooleanCall = approvedPrefixes.some((prefix) => prefix !== 'asserts' &&
|
|
223
|
+
lowerCallee.startsWith(prefix.toLowerCase()));
|
|
224
|
+
if (isBooleanCall ||
|
|
225
|
+
lowerCallee.includes('boolean') ||
|
|
226
|
+
lowerCallee.includes('enabled') ||
|
|
227
|
+
lowerCallee.includes('auth') ||
|
|
228
|
+
lowerCallee.includes('valid') ||
|
|
229
|
+
lowerCallee.includes('check')) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
if (lowerCallee.startsWith('get') ||
|
|
233
|
+
lowerCallee.startsWith('fetch') ||
|
|
234
|
+
lowerCallee.startsWith('retrieve') ||
|
|
235
|
+
lowerCallee.startsWith('load') ||
|
|
236
|
+
lowerCallee.startsWith('read')) {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
129
240
|
// If the method name doesn't suggest it returns a boolean, don't flag it
|
|
130
241
|
if (rightSide.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
131
242
|
rightSide.callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
132
243
|
const methodName = rightSide.callee.property.name;
|
|
244
|
+
const lowerMethodName = methodName.toLowerCase();
|
|
245
|
+
// Ignore assert*-style methods which often return the input value
|
|
246
|
+
if (lowerMethodName.startsWith('assert')) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
133
249
|
// Check if the method name suggests it returns a boolean
|
|
134
|
-
const isBooleanMethod = approvedPrefixes.some((prefix) =>
|
|
250
|
+
const isBooleanMethod = approvedPrefixes.some((prefix) => prefix !== 'asserts' &&
|
|
251
|
+
lowerMethodName.startsWith(prefix.toLowerCase()));
|
|
135
252
|
// If the method name suggests it returns a boolean (starts with a boolean prefix or contains 'boolean' or 'enabled'),
|
|
136
253
|
// then the variable should be treated as a boolean
|
|
137
254
|
if (isBooleanMethod ||
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
255
|
+
lowerMethodName.includes('boolean') ||
|
|
256
|
+
lowerMethodName.includes('enabled') ||
|
|
257
|
+
lowerMethodName.includes('auth') ||
|
|
258
|
+
lowerMethodName.includes('valid') ||
|
|
259
|
+
lowerMethodName.includes('check')) {
|
|
143
260
|
return true;
|
|
144
261
|
}
|
|
145
262
|
// For methods like getVolume(), getData(), etc., assume they return non-boolean values
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
263
|
+
if (lowerMethodName.startsWith('get') ||
|
|
264
|
+
lowerMethodName.startsWith('fetch') ||
|
|
265
|
+
lowerMethodName.startsWith('retrieve') ||
|
|
266
|
+
lowerMethodName.startsWith('load') ||
|
|
267
|
+
lowerMethodName.startsWith('read')) {
|
|
151
268
|
return false;
|
|
152
269
|
}
|
|
153
270
|
}
|
|
154
271
|
}
|
|
155
|
-
//
|
|
272
|
+
// Check if the right side is a property access that might return a non-boolean value
|
|
156
273
|
if (rightSide.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
157
274
|
rightSide.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
158
275
|
const propertyName = rightSide.property.name;
|
|
159
|
-
const
|
|
276
|
+
const lowerPropertyName = propertyName.toLowerCase();
|
|
277
|
+
// If the property name is 'parentElement', 'parentNode', etc., it's likely not a boolean
|
|
278
|
+
if (lowerPropertyName.includes('parent') ||
|
|
279
|
+
lowerPropertyName.includes('element') ||
|
|
280
|
+
lowerPropertyName.includes('node') ||
|
|
281
|
+
lowerPropertyName.includes('child') ||
|
|
282
|
+
lowerPropertyName.includes('sibling')) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
// Ignore assert*-style properties which often return the input value
|
|
286
|
+
if (lowerPropertyName.startsWith('assert')) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
// For property access like user.isAuthenticated, treat as boolean
|
|
290
|
+
const isBooleanProperty = approvedPrefixes.some((prefix) => prefix !== 'asserts' &&
|
|
291
|
+
lowerPropertyName.startsWith(prefix.toLowerCase()));
|
|
160
292
|
if (isBooleanProperty) {
|
|
161
293
|
return true;
|
|
162
294
|
}
|
|
@@ -195,7 +327,13 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
195
327
|
if (leftSide.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
196
328
|
leftSide.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
197
329
|
const calleeName = leftSide.callee.name;
|
|
198
|
-
|
|
330
|
+
const lowerCallee = calleeName.toLowerCase();
|
|
331
|
+
// For assert*-style utilities, only treat as boolean if we can confirm boolean return type
|
|
332
|
+
if (lowerCallee.startsWith('assert')) {
|
|
333
|
+
return identifierReturnsBoolean(calleeName);
|
|
334
|
+
}
|
|
335
|
+
return approvedPrefixes.some((prefix) => prefix !== 'asserts' &&
|
|
336
|
+
lowerCallee.startsWith(prefix.toLowerCase()));
|
|
199
337
|
}
|
|
200
338
|
// Default to false for other cases with || to avoid false positives
|
|
201
339
|
return false;
|
|
@@ -209,8 +347,14 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
209
347
|
if (node.init.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
210
348
|
node.init.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
211
349
|
const calleeName = node.init.callee.name;
|
|
350
|
+
const lowerCallee = calleeName.toLowerCase();
|
|
351
|
+
// For assert*-style utilities, only treat as boolean if we can confirm boolean return type
|
|
352
|
+
if (lowerCallee.startsWith('assert')) {
|
|
353
|
+
return identifierReturnsBoolean(calleeName);
|
|
354
|
+
}
|
|
212
355
|
// Check if the function name suggests it returns a boolean
|
|
213
|
-
return approvedPrefixes.some((prefix) =>
|
|
356
|
+
return approvedPrefixes.some((prefix) => prefix !== 'asserts' &&
|
|
357
|
+
lowerCallee.startsWith(prefix.toLowerCase()));
|
|
214
358
|
}
|
|
215
359
|
}
|
|
216
360
|
return false;
|
|
@@ -234,7 +378,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
234
378
|
return true;
|
|
235
379
|
}
|
|
236
380
|
if (node.body.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
|
|
237
|
-
|
|
381
|
+
BOOLEANISH_BINARY_OPERATORS.has(node.body.operator)) {
|
|
238
382
|
return true;
|
|
239
383
|
}
|
|
240
384
|
if (node.body.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
|
|
@@ -257,6 +401,487 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
257
401
|
}
|
|
258
402
|
return false;
|
|
259
403
|
}
|
|
404
|
+
function identifierIsBoolean(identifier) {
|
|
405
|
+
if (nameSuggestsBoolean(identifier.name)) {
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
const variable = findVariableInScopes(identifier.name);
|
|
409
|
+
if (!variable)
|
|
410
|
+
return false;
|
|
411
|
+
for (const def of variable.defs) {
|
|
412
|
+
if (def.type === 'Variable' && def.node) {
|
|
413
|
+
const declarator = def.node;
|
|
414
|
+
if (declarator.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
415
|
+
(hasBooleanTypeAnnotation(declarator.id) ||
|
|
416
|
+
hasInitialBooleanValue(declarator))) {
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (def.type === 'Parameter' &&
|
|
421
|
+
def.name?.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
422
|
+
hasBooleanTypeAnnotation(def.name)) {
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
if (def.type === 'FunctionName' &&
|
|
426
|
+
def.node &&
|
|
427
|
+
returnsBooleanValue(def.node)) {
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
function callExpressionLooksBoolean(callExpression) {
|
|
434
|
+
if (callExpression.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
435
|
+
const calleeName = callExpression.callee.name;
|
|
436
|
+
const lowerCallee = calleeName.toLowerCase();
|
|
437
|
+
if (lowerCallee.startsWith('assert')) {
|
|
438
|
+
return identifierReturnsBoolean(calleeName) ? 'boolean' : 'unknown';
|
|
439
|
+
}
|
|
440
|
+
const matchesPrefix = approvedPrefixes.some((prefix) => prefix !== 'asserts' &&
|
|
441
|
+
lowerCallee.startsWith(prefix.toLowerCase()));
|
|
442
|
+
if (matchesPrefix ||
|
|
443
|
+
lowerCallee.includes('boolean') ||
|
|
444
|
+
lowerCallee.includes('enabled') ||
|
|
445
|
+
lowerCallee.includes('auth') ||
|
|
446
|
+
lowerCallee.includes('valid') ||
|
|
447
|
+
lowerCallee.includes('check')) {
|
|
448
|
+
return 'boolean';
|
|
449
|
+
}
|
|
450
|
+
if (lowerCallee.startsWith('get') ||
|
|
451
|
+
lowerCallee.startsWith('fetch') ||
|
|
452
|
+
lowerCallee.startsWith('retrieve') ||
|
|
453
|
+
lowerCallee.startsWith('load') ||
|
|
454
|
+
lowerCallee.startsWith('read')) {
|
|
455
|
+
return 'unknown';
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (callExpression.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
459
|
+
callExpression.callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
460
|
+
const methodName = callExpression.callee.property.name;
|
|
461
|
+
const lowerMethodName = methodName.toLowerCase();
|
|
462
|
+
if (lowerMethodName.startsWith('assert')) {
|
|
463
|
+
return 'unknown';
|
|
464
|
+
}
|
|
465
|
+
const matchesPrefix = approvedPrefixes.some((prefix) => prefix !== 'asserts' &&
|
|
466
|
+
lowerMethodName.startsWith(prefix.toLowerCase()));
|
|
467
|
+
if (matchesPrefix ||
|
|
468
|
+
lowerMethodName.includes('boolean') ||
|
|
469
|
+
lowerMethodName.includes('enabled') ||
|
|
470
|
+
lowerMethodName.includes('auth') ||
|
|
471
|
+
lowerMethodName.includes('valid') ||
|
|
472
|
+
lowerMethodName.includes('check')) {
|
|
473
|
+
return 'boolean';
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return 'unknown';
|
|
477
|
+
}
|
|
478
|
+
function evaluateBooleanishExpression(expression) {
|
|
479
|
+
if (!expression)
|
|
480
|
+
return 'nonBoolean';
|
|
481
|
+
let currentExpression = expression;
|
|
482
|
+
if (currentExpression.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
483
|
+
currentExpression.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
484
|
+
currentExpression.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression) {
|
|
485
|
+
if (currentExpression.typeAnnotation?.type ===
|
|
486
|
+
utils_1.AST_NODE_TYPES.TSBooleanKeyword) {
|
|
487
|
+
return 'boolean';
|
|
488
|
+
}
|
|
489
|
+
currentExpression = currentExpression.expression;
|
|
490
|
+
}
|
|
491
|
+
if (currentExpression.type === utils_1.AST_NODE_TYPES.ChainExpression) {
|
|
492
|
+
currentExpression = currentExpression.expression;
|
|
493
|
+
}
|
|
494
|
+
if (currentExpression.type ===
|
|
495
|
+
'ParenthesizedExpression') {
|
|
496
|
+
return evaluateBooleanishExpression(currentExpression.expression);
|
|
497
|
+
}
|
|
498
|
+
if (currentExpression.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
|
|
499
|
+
return evaluateBooleanishExpression(currentExpression.expression);
|
|
500
|
+
}
|
|
501
|
+
if (currentExpression.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
502
|
+
typeof currentExpression.value === 'boolean') {
|
|
503
|
+
return 'boolean';
|
|
504
|
+
}
|
|
505
|
+
if (currentExpression.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
506
|
+
return identifierIsBoolean(currentExpression) ? 'boolean' : 'unknown';
|
|
507
|
+
}
|
|
508
|
+
if (currentExpression.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
509
|
+
currentExpression.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
510
|
+
return nameSuggestsBoolean(currentExpression.property.name)
|
|
511
|
+
? 'boolean'
|
|
512
|
+
: 'unknown';
|
|
513
|
+
}
|
|
514
|
+
if (currentExpression.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
515
|
+
return callExpressionLooksBoolean(currentExpression);
|
|
516
|
+
}
|
|
517
|
+
if (currentExpression.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
|
|
518
|
+
if (BOOLEANISH_BINARY_OPERATORS.has(currentExpression.operator)) {
|
|
519
|
+
return 'boolean';
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (currentExpression.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
|
|
523
|
+
currentExpression.operator === '!') {
|
|
524
|
+
return 'boolean';
|
|
525
|
+
}
|
|
526
|
+
if (currentExpression.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
|
|
527
|
+
const left = evaluateBooleanishExpression(currentExpression.left);
|
|
528
|
+
const right = evaluateBooleanishExpression(currentExpression.right);
|
|
529
|
+
if (left === 'nonBoolean' || right === 'nonBoolean') {
|
|
530
|
+
return 'nonBoolean';
|
|
531
|
+
}
|
|
532
|
+
if (left === 'boolean' && right === 'boolean') {
|
|
533
|
+
return 'boolean';
|
|
534
|
+
}
|
|
535
|
+
return 'unknown';
|
|
536
|
+
}
|
|
537
|
+
if (currentExpression.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
|
|
538
|
+
const consequent = evaluateBooleanishExpression(currentExpression.consequent);
|
|
539
|
+
const alternate = evaluateBooleanishExpression(currentExpression.alternate);
|
|
540
|
+
if (consequent === 'nonBoolean' || alternate === 'nonBoolean') {
|
|
541
|
+
return 'nonBoolean';
|
|
542
|
+
}
|
|
543
|
+
if (consequent === 'boolean' && alternate === 'boolean') {
|
|
544
|
+
return 'boolean';
|
|
545
|
+
}
|
|
546
|
+
return 'unknown';
|
|
547
|
+
}
|
|
548
|
+
return 'unknown';
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Attempt to resolve an identifier to a function declaration/expression and detect if it returns boolean
|
|
552
|
+
*/
|
|
553
|
+
function identifierReturnsBoolean(name) {
|
|
554
|
+
// Try to find the variable in all scopes, starting from current and going up
|
|
555
|
+
let currentScope = context.getScope();
|
|
556
|
+
let variable = undefined;
|
|
557
|
+
while (currentScope && !variable) {
|
|
558
|
+
variable = currentScope.variables.find((v) => v.name === name);
|
|
559
|
+
if (!variable) {
|
|
560
|
+
currentScope = currentScope.upper;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
if (!variable)
|
|
564
|
+
return false;
|
|
565
|
+
for (const def of variable.defs) {
|
|
566
|
+
// Function declaration
|
|
567
|
+
if (def.type === 'FunctionName' && def.node) {
|
|
568
|
+
const fn = def.node;
|
|
569
|
+
if (fn.returnType?.typeAnnotation &&
|
|
570
|
+
fn.returnType.typeAnnotation.type ===
|
|
571
|
+
utils_1.AST_NODE_TYPES.TSBooleanKeyword) {
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
// Variable with function expression or arrow function
|
|
576
|
+
if (def.type === 'Variable' && def.node) {
|
|
577
|
+
const varDecl = def.node;
|
|
578
|
+
const init = (varDecl && varDecl.init);
|
|
579
|
+
if (!init)
|
|
580
|
+
continue;
|
|
581
|
+
if ((init.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
582
|
+
init.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
|
|
583
|
+
init.returnType?.typeAnnotation &&
|
|
584
|
+
init.returnType.typeAnnotation.type ===
|
|
585
|
+
utils_1.AST_NODE_TYPES.TSBooleanKeyword) {
|
|
586
|
+
return true;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Check if a variable is used in a while loop condition and is likely a DOM element or tree node
|
|
594
|
+
* This helps identify variables like 'parent', 'element', 'node', etc. that are used
|
|
595
|
+
* in while loops for DOM/tree traversal and are not boolean values
|
|
596
|
+
*/
|
|
597
|
+
function isLikelyDomElementInWhileLoop(node) {
|
|
598
|
+
// Check if the variable name suggests it's a DOM element or tree node
|
|
599
|
+
const isTraversalName = node.name.toLowerCase().includes('element') ||
|
|
600
|
+
node.name.toLowerCase().includes('node') ||
|
|
601
|
+
node.name.toLowerCase().includes('parent') ||
|
|
602
|
+
node.name.toLowerCase().includes('child') ||
|
|
603
|
+
node.name.toLowerCase().includes('sibling') ||
|
|
604
|
+
node.name.toLowerCase().includes('ancestor') ||
|
|
605
|
+
node.name.toLowerCase().includes('descendant');
|
|
606
|
+
if (!isTraversalName) {
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
// Must be used in a while loop to be considered for this exception
|
|
610
|
+
if (!isUsedInWhileLoop(node)) {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
// Check if the variable is initialized with a traversal-related value
|
|
614
|
+
const variableDeclarator = node.parent;
|
|
615
|
+
if (variableDeclarator?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
616
|
+
variableDeclarator.init) {
|
|
617
|
+
const init = variableDeclarator.init;
|
|
618
|
+
// Check for logical expressions with traversal-related properties on the right side
|
|
619
|
+
if (init.type === utils_1.AST_NODE_TYPES.LogicalExpression &&
|
|
620
|
+
init.operator === '&&' &&
|
|
621
|
+
init.right.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
622
|
+
init.right.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
623
|
+
const propertyName = init.right.property
|
|
624
|
+
.name;
|
|
625
|
+
// DOM-specific properties - these are definitely DOM traversal
|
|
626
|
+
const isDomProperty = propertyName.toLowerCase().includes('element') ||
|
|
627
|
+
propertyName.toLowerCase().includes('node') ||
|
|
628
|
+
propertyName === 'parentElement' ||
|
|
629
|
+
propertyName === 'parentNode' ||
|
|
630
|
+
propertyName === 'firstChild' ||
|
|
631
|
+
propertyName === 'lastChild' ||
|
|
632
|
+
propertyName === 'nextSibling' ||
|
|
633
|
+
propertyName === 'previousSibling' ||
|
|
634
|
+
propertyName === 'firstElementChild' ||
|
|
635
|
+
propertyName === 'lastElementChild' ||
|
|
636
|
+
propertyName === 'nextElementSibling' ||
|
|
637
|
+
propertyName === 'previousElementSibling';
|
|
638
|
+
if (isDomProperty) {
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
// Tree-like properties - need additional confirmation
|
|
642
|
+
const isTreeProperty = propertyName === 'parent' ||
|
|
643
|
+
propertyName === 'child' ||
|
|
644
|
+
propertyName === 'root' ||
|
|
645
|
+
propertyName === 'left' ||
|
|
646
|
+
propertyName === 'right' ||
|
|
647
|
+
propertyName === 'next' ||
|
|
648
|
+
propertyName === 'prev' ||
|
|
649
|
+
propertyName === 'previous';
|
|
650
|
+
if (isTreeProperty) {
|
|
651
|
+
// For tree properties, check if there's a traversal pattern in the code
|
|
652
|
+
return hasTreeTraversalPattern(variableDeclarator);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
// Check for direct member expressions (without logical operators)
|
|
656
|
+
if (init.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
657
|
+
init.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
658
|
+
const propertyName = init.property.name;
|
|
659
|
+
// DOM-specific properties
|
|
660
|
+
const isDomProperty = propertyName === 'parentElement' ||
|
|
661
|
+
propertyName === 'parentNode' ||
|
|
662
|
+
propertyName === 'firstChild' ||
|
|
663
|
+
propertyName === 'lastChild' ||
|
|
664
|
+
propertyName === 'nextSibling' ||
|
|
665
|
+
propertyName === 'previousSibling';
|
|
666
|
+
if (isDomProperty) {
|
|
667
|
+
return true;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
// Check for call expressions that return DOM elements
|
|
671
|
+
if (init.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
672
|
+
init.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
673
|
+
init.callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
674
|
+
const methodName = init.callee.property.name;
|
|
675
|
+
// DOM query methods
|
|
676
|
+
const isDomMethod = methodName === 'querySelector' ||
|
|
677
|
+
methodName === 'querySelectorAll' ||
|
|
678
|
+
methodName === 'getElementById' ||
|
|
679
|
+
methodName === 'getElementsByClassName' ||
|
|
680
|
+
methodName === 'getElementsByTagName';
|
|
681
|
+
if (isDomMethod) {
|
|
682
|
+
return true;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Traverses an AST depth-first, skipping metadata keys, and stops early when the
|
|
690
|
+
* visitor returns true.
|
|
691
|
+
*/
|
|
692
|
+
const isTraversalMetadataKey = (key) => key === 'parent' || key === 'range' || key === 'loc';
|
|
693
|
+
function traverseAst(node, visitor) {
|
|
694
|
+
if (visitor(node))
|
|
695
|
+
return true;
|
|
696
|
+
for (const key in node) {
|
|
697
|
+
if (isTraversalMetadataKey(key))
|
|
698
|
+
continue;
|
|
699
|
+
const value = node[key];
|
|
700
|
+
if (Array.isArray(value)) {
|
|
701
|
+
for (const child of value) {
|
|
702
|
+
if (child &&
|
|
703
|
+
typeof child === 'object' &&
|
|
704
|
+
child.type) {
|
|
705
|
+
if (traverseAst(child, visitor)) {
|
|
706
|
+
return true;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
else if (value &&
|
|
712
|
+
typeof value === 'object' &&
|
|
713
|
+
value.type) {
|
|
714
|
+
if (traverseAst(value, visitor)) {
|
|
715
|
+
return true;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
function collectReturnArguments(root) {
|
|
722
|
+
const results = [];
|
|
723
|
+
function walk(node) {
|
|
724
|
+
if (node.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
|
|
725
|
+
results.push(node.argument ?? null);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (node !== root &&
|
|
729
|
+
(node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
730
|
+
node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
731
|
+
node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
for (const key in node) {
|
|
735
|
+
if (isTraversalMetadataKey(key))
|
|
736
|
+
continue;
|
|
737
|
+
const value = node[key];
|
|
738
|
+
if (Array.isArray(value)) {
|
|
739
|
+
for (const child of value) {
|
|
740
|
+
if (child &&
|
|
741
|
+
typeof child === 'object' &&
|
|
742
|
+
child.type) {
|
|
743
|
+
walk(child);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
else if (value &&
|
|
748
|
+
typeof value === 'object' &&
|
|
749
|
+
value.type) {
|
|
750
|
+
walk(value);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
walk(root);
|
|
755
|
+
return results;
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Check if a variable is used in a while loop condition
|
|
759
|
+
* This searches the scope for while loops that use the variable in their condition
|
|
760
|
+
*/
|
|
761
|
+
function isUsedInWhileLoop(node) {
|
|
762
|
+
const variableName = node.name;
|
|
763
|
+
// Find the function or block scope containing this variable
|
|
764
|
+
let currentScope = node.parent;
|
|
765
|
+
while (currentScope &&
|
|
766
|
+
currentScope.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
767
|
+
currentScope = currentScope.parent;
|
|
768
|
+
}
|
|
769
|
+
if (!currentScope ||
|
|
770
|
+
currentScope.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
return traverseAst(currentScope, (searchNode) => {
|
|
774
|
+
return (searchNode.type === utils_1.AST_NODE_TYPES.WhileStatement &&
|
|
775
|
+
searchNode.test.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
776
|
+
searchNode.test.name === variableName);
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Check if a variable declarator has a tree traversal pattern
|
|
781
|
+
* This looks for patterns like reassignment to .parent, .parentElement, etc.
|
|
782
|
+
*/
|
|
783
|
+
function hasTreeTraversalPattern(declarator) {
|
|
784
|
+
if (declarator.id.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
785
|
+
return false;
|
|
786
|
+
}
|
|
787
|
+
const variableName = declarator.id.name;
|
|
788
|
+
// Find the function or block scope containing this variable
|
|
789
|
+
let currentScope = declarator.parent;
|
|
790
|
+
while (currentScope &&
|
|
791
|
+
currentScope.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
792
|
+
currentScope = currentScope.parent;
|
|
793
|
+
}
|
|
794
|
+
if (!currentScope ||
|
|
795
|
+
currentScope.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
return traverseAst(currentScope, (node) => {
|
|
799
|
+
if (node.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
|
|
800
|
+
node.left.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
801
|
+
node.left.name === variableName &&
|
|
802
|
+
node.right.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
803
|
+
node.right.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
804
|
+
const propertyName = node.right.property
|
|
805
|
+
.name;
|
|
806
|
+
// Check for DOM traversal properties
|
|
807
|
+
if (propertyName === 'parentElement' ||
|
|
808
|
+
propertyName === 'parentNode' ||
|
|
809
|
+
propertyName === 'nextSibling' ||
|
|
810
|
+
propertyName === 'previousSibling' ||
|
|
811
|
+
propertyName === 'firstChild' ||
|
|
812
|
+
propertyName === 'lastChild') {
|
|
813
|
+
return true;
|
|
814
|
+
}
|
|
815
|
+
// Check for generic tree traversal properties
|
|
816
|
+
if (propertyName === 'parent' ||
|
|
817
|
+
propertyName === 'child' ||
|
|
818
|
+
propertyName === 'left' ||
|
|
819
|
+
propertyName === 'right' ||
|
|
820
|
+
propertyName === 'next' ||
|
|
821
|
+
propertyName === 'prev' ||
|
|
822
|
+
propertyName === 'previous') {
|
|
823
|
+
return true;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return false;
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Check if a variable is used in a while loop condition and is likely a boolean
|
|
831
|
+
* This helps identify variables that should be flagged as needing a boolean prefix
|
|
832
|
+
*/
|
|
833
|
+
function isLikelyBooleanInWhileLoop(node) {
|
|
834
|
+
// Check if the variable is initialized with a boolean-related value
|
|
835
|
+
const variableDeclarator = node.parent;
|
|
836
|
+
if (variableDeclarator?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
837
|
+
variableDeclarator.init) {
|
|
838
|
+
const init = variableDeclarator.init;
|
|
839
|
+
// Check for direct boolean initialization
|
|
840
|
+
if (init.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
841
|
+
typeof init.value === 'boolean') {
|
|
842
|
+
return true;
|
|
843
|
+
}
|
|
844
|
+
// Check for property access with boolean-suggesting name
|
|
845
|
+
if (init.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
846
|
+
init.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
847
|
+
const propertyName = init.property.name;
|
|
848
|
+
// If the property name suggests it's a boolean (starts with a boolean prefix)
|
|
849
|
+
const isBooleanProperty = approvedPrefixes.some((prefix) => propertyName.toLowerCase().startsWith(prefix.toLowerCase()));
|
|
850
|
+
if (isBooleanProperty) {
|
|
851
|
+
return true;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
function getterReturnsBoolean(node) {
|
|
858
|
+
if (node.kind !== 'get') {
|
|
859
|
+
return false;
|
|
860
|
+
}
|
|
861
|
+
const functionLike = node.value;
|
|
862
|
+
const returnAnnotation = functionLike?.returnType?.typeAnnotation ||
|
|
863
|
+
node.returnType
|
|
864
|
+
?.typeAnnotation;
|
|
865
|
+
if (returnAnnotation &&
|
|
866
|
+
returnAnnotation.type === utils_1.AST_NODE_TYPES.TSBooleanKeyword) {
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
if (!functionLike?.body ||
|
|
870
|
+
functionLike.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
const returnArguments = collectReturnArguments(functionLike.body);
|
|
874
|
+
if (returnArguments.length === 0) {
|
|
875
|
+
return false;
|
|
876
|
+
}
|
|
877
|
+
for (const argument of returnArguments) {
|
|
878
|
+
const evaluation = evaluateBooleanishExpression(argument);
|
|
879
|
+
if (evaluation !== 'boolean') {
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return true;
|
|
884
|
+
}
|
|
260
885
|
/**
|
|
261
886
|
* Check variable declarations for boolean naming
|
|
262
887
|
*/
|
|
@@ -267,8 +892,15 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
267
892
|
// Skip checking if it's a type predicate
|
|
268
893
|
if (isTypePredicate(node.id))
|
|
269
894
|
return;
|
|
895
|
+
// Skip checking if it's likely a DOM element used in a while loop
|
|
896
|
+
if (isLikelyDomElementInWhileLoop(node.id))
|
|
897
|
+
return;
|
|
270
898
|
// Check if it's a boolean variable
|
|
271
899
|
let isBooleanVar = hasBooleanTypeAnnotation(node.id) || hasInitialBooleanValue(node);
|
|
900
|
+
// Check if it's a boolean variable used in a while loop
|
|
901
|
+
if (!isBooleanVar && isLikelyBooleanInWhileLoop(node.id)) {
|
|
902
|
+
isBooleanVar = true;
|
|
903
|
+
}
|
|
272
904
|
// Check if it's an arrow function with boolean return type
|
|
273
905
|
if (node.init?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
274
906
|
node.init.returnType?.typeAnnotation &&
|
|
@@ -283,6 +915,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
283
915
|
data: {
|
|
284
916
|
type: 'variable',
|
|
285
917
|
name: variableName,
|
|
918
|
+
capitalizedName: capitalizeFirst(variableName),
|
|
286
919
|
prefixes: formatPrefixes(),
|
|
287
920
|
},
|
|
288
921
|
});
|
|
@@ -327,6 +960,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
327
960
|
data: {
|
|
328
961
|
type: 'function',
|
|
329
962
|
name: functionName,
|
|
963
|
+
capitalizedName: capitalizeFirst(functionName),
|
|
330
964
|
prefixes: formatPrefixes(),
|
|
331
965
|
},
|
|
332
966
|
});
|
|
@@ -339,23 +973,34 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
339
973
|
if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
340
974
|
return;
|
|
341
975
|
const methodName = node.key.name;
|
|
976
|
+
const isGetter = node.kind === 'get';
|
|
977
|
+
const returnAnnotation = node.value?.returnType?.typeAnnotation ||
|
|
978
|
+
node.returnType
|
|
979
|
+
?.typeAnnotation;
|
|
980
|
+
if (ignoreOverriddenGetters &&
|
|
981
|
+
isGetter &&
|
|
982
|
+
(node.override ||
|
|
983
|
+
node.abstract ||
|
|
984
|
+
!node.value?.body ||
|
|
985
|
+
node.type === utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition)) {
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
342
988
|
// Skip checking if it's a type predicate
|
|
343
|
-
if (
|
|
344
|
-
|
|
345
|
-
utils_1.AST_NODE_TYPES.TSTypePredicate) {
|
|
989
|
+
if (returnAnnotation &&
|
|
990
|
+
returnAnnotation.type === utils_1.AST_NODE_TYPES.TSTypePredicate) {
|
|
346
991
|
return;
|
|
347
992
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
node
|
|
351
|
-
|
|
352
|
-
!hasApprovedPrefix(methodName)) {
|
|
993
|
+
const returnsBoolean = (returnAnnotation &&
|
|
994
|
+
returnAnnotation.type === utils_1.AST_NODE_TYPES.TSBooleanKeyword) ||
|
|
995
|
+
(isGetter && getterReturnsBoolean(node));
|
|
996
|
+
if (returnsBoolean && !hasApprovedPrefix(methodName)) {
|
|
353
997
|
context.report({
|
|
354
998
|
node: node.key,
|
|
355
999
|
messageId: 'missingBooleanPrefix',
|
|
356
1000
|
data: {
|
|
357
|
-
type: 'method',
|
|
1001
|
+
type: isGetter ? 'getter' : 'method',
|
|
358
1002
|
name: methodName,
|
|
1003
|
+
capitalizedName: capitalizeFirst(methodName),
|
|
359
1004
|
prefixes: formatPrefixes(),
|
|
360
1005
|
},
|
|
361
1006
|
});
|
|
@@ -388,6 +1033,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
388
1033
|
data: {
|
|
389
1034
|
type: 'property',
|
|
390
1035
|
name: propertyName,
|
|
1036
|
+
capitalizedName: capitalizeFirst(propertyName),
|
|
391
1037
|
prefixes: formatPrefixes(),
|
|
392
1038
|
},
|
|
393
1039
|
});
|
|
@@ -420,6 +1066,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
420
1066
|
data: {
|
|
421
1067
|
type: 'property',
|
|
422
1068
|
name: propertyName,
|
|
1069
|
+
capitalizedName: capitalizeFirst(propertyName),
|
|
423
1070
|
prefixes: formatPrefixes(),
|
|
424
1071
|
},
|
|
425
1072
|
});
|
|
@@ -429,12 +1076,96 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
429
1076
|
* Check if an identifier is imported from an external module
|
|
430
1077
|
*/
|
|
431
1078
|
function isImportedIdentifier(name) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
1079
|
+
if (importStatusCache.has(name)) {
|
|
1080
|
+
const cached = importStatusCache.get(name);
|
|
1081
|
+
if (cached !== undefined)
|
|
1082
|
+
return cached;
|
|
1083
|
+
}
|
|
1084
|
+
const variable = findVariableInScopes(name);
|
|
1085
|
+
if (!variable) {
|
|
1086
|
+
importStatusCache.set(name, false);
|
|
435
1087
|
return false;
|
|
436
|
-
|
|
437
|
-
|
|
1088
|
+
}
|
|
1089
|
+
const isImport = variable.defs.some((def) => def.type === 'ImportBinding');
|
|
1090
|
+
importStatusCache.set(name, isImport);
|
|
1091
|
+
return isImport;
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Check if a variable is used with an external API
|
|
1095
|
+
*/
|
|
1096
|
+
function isVariableUsedWithExternalApi(variableName) {
|
|
1097
|
+
if (externalApiUsageCache.has(variableName)) {
|
|
1098
|
+
const cached = externalApiUsageCache.get(variableName);
|
|
1099
|
+
if (cached !== undefined)
|
|
1100
|
+
return cached;
|
|
1101
|
+
}
|
|
1102
|
+
const variable = findVariableInScopes(variableName);
|
|
1103
|
+
if (!variable) {
|
|
1104
|
+
externalApiUsageCache.set(variableName, false);
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
for (const reference of variable.references) {
|
|
1108
|
+
if (reference.identifier === variable.identifiers[0])
|
|
1109
|
+
continue;
|
|
1110
|
+
const id = reference.identifier;
|
|
1111
|
+
const markAndReturnTrue = () => {
|
|
1112
|
+
externalApiUsageCache.set(variableName, true);
|
|
1113
|
+
return true;
|
|
1114
|
+
};
|
|
1115
|
+
if (id.parent?.type === utils_1.AST_NODE_TYPES.Property &&
|
|
1116
|
+
id.parent.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
1117
|
+
id.parent.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
1118
|
+
id.parent.parent.parent.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1119
|
+
isImportedIdentifier(id.parent.parent.parent.callee.name)) {
|
|
1120
|
+
return markAndReturnTrue();
|
|
1121
|
+
}
|
|
1122
|
+
if (id.parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
1123
|
+
if (id.parent.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1124
|
+
isImportedIdentifier(id.parent.callee.name)) {
|
|
1125
|
+
return markAndReturnTrue();
|
|
1126
|
+
}
|
|
1127
|
+
if (id.parent.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
1128
|
+
id.parent.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1129
|
+
isImportedIdentifier(id.parent.callee.object.name)) {
|
|
1130
|
+
return markAndReturnTrue();
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
if (id.parent?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer &&
|
|
1134
|
+
id.parent.parent?.type === utils_1.AST_NODE_TYPES.JSXAttribute &&
|
|
1135
|
+
id.parent.parent.parent?.type === utils_1.AST_NODE_TYPES.JSXOpeningElement &&
|
|
1136
|
+
id.parent.parent.parent.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier &&
|
|
1137
|
+
isImportedIdentifier(id.parent.parent.parent.name.name)) {
|
|
1138
|
+
return markAndReturnTrue();
|
|
1139
|
+
}
|
|
1140
|
+
if (id.parent?.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute &&
|
|
1141
|
+
id.parent.parent?.type === utils_1.AST_NODE_TYPES.JSXOpeningElement &&
|
|
1142
|
+
id.parent.parent.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier &&
|
|
1143
|
+
isImportedIdentifier(id.parent.parent.name.name)) {
|
|
1144
|
+
return markAndReturnTrue();
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
externalApiUsageCache.set(variableName, false);
|
|
1148
|
+
return false;
|
|
1149
|
+
}
|
|
1150
|
+
const HOOK_NAMES = new Set(['useMemo', 'useCallback', 'useState']);
|
|
1151
|
+
function findVariableFromReactHook(objectExpr) {
|
|
1152
|
+
let current = objectExpr;
|
|
1153
|
+
while (current) {
|
|
1154
|
+
if (current.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
|
|
1155
|
+
current = current.parent;
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
if (current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
1159
|
+
current.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
1160
|
+
current.parent.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1161
|
+
HOOK_NAMES.has(current.parent.callee.name) &&
|
|
1162
|
+
current.parent.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
1163
|
+
current.parent.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
1164
|
+
return current.parent.parent.id.name;
|
|
1165
|
+
}
|
|
1166
|
+
current = current.parent;
|
|
1167
|
+
}
|
|
1168
|
+
return undefined;
|
|
438
1169
|
}
|
|
439
1170
|
/**
|
|
440
1171
|
* Check property definitions for boolean values
|
|
@@ -490,6 +1221,51 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
490
1221
|
}
|
|
491
1222
|
}
|
|
492
1223
|
}
|
|
1224
|
+
// Check if this property is in an object literal that's being assigned to a variable
|
|
1225
|
+
// This handles cases like const messageInputProps = { grow: true }
|
|
1226
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
1227
|
+
node.parent.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
1228
|
+
node.parent.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
1229
|
+
const variableName = node.parent.parent.id.name;
|
|
1230
|
+
if (isVariableUsedWithExternalApi(variableName)) {
|
|
1231
|
+
isExternalApiCall = true;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
// Special case for useMemo and other React hooks
|
|
1235
|
+
// This handles cases like:
|
|
1236
|
+
// 1. const messageInputProps = useMemo(() => ({ grow: true }), [])
|
|
1237
|
+
// 2. const messageInputProps = useMemo(() => { return { grow: true }; }, [])
|
|
1238
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
1239
|
+
const hookVariable = findVariableFromReactHook(node.parent);
|
|
1240
|
+
if (hookVariable && isVariableUsedWithExternalApi(hookVariable)) {
|
|
1241
|
+
isExternalApiCall = true;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
// Check if this property is in an object literal that's directly passed to an imported function
|
|
1245
|
+
// This handles cases like ExternalComponent({ grow: true })
|
|
1246
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
1247
|
+
node.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
1248
|
+
node.parent.parent.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
1249
|
+
const calleeName = node.parent.parent.callee.name;
|
|
1250
|
+
if (isImportedIdentifier(calleeName)) {
|
|
1251
|
+
isExternalApiCall = true;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
// Check if this property is in an object literal that's directly passed as a JSX attribute
|
|
1255
|
+
// This handles cases like <ExternalComponent config={{ active: true }} />
|
|
1256
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
1257
|
+
node.parent.parent?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer &&
|
|
1258
|
+
node.parent.parent.parent?.type === utils_1.AST_NODE_TYPES.JSXAttribute &&
|
|
1259
|
+
node.parent.parent.parent.parent?.type ===
|
|
1260
|
+
utils_1.AST_NODE_TYPES.JSXOpeningElement) {
|
|
1261
|
+
const jsxOpeningElement = node.parent.parent.parent.parent;
|
|
1262
|
+
if (jsxOpeningElement.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier) {
|
|
1263
|
+
const componentName = jsxOpeningElement.name.name;
|
|
1264
|
+
if (isImportedIdentifier(componentName)) {
|
|
1265
|
+
isExternalApiCall = true;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
493
1269
|
// Only report if it's not an external API call
|
|
494
1270
|
if (!isExternalApiCall) {
|
|
495
1271
|
context.report({
|
|
@@ -498,6 +1274,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
498
1274
|
data: {
|
|
499
1275
|
type: 'property',
|
|
500
1276
|
name: propertyName,
|
|
1277
|
+
capitalizedName: capitalizeFirst(propertyName),
|
|
501
1278
|
prefixes: formatPrefixes(),
|
|
502
1279
|
},
|
|
503
1280
|
});
|
|
@@ -541,6 +1318,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
541
1318
|
data: {
|
|
542
1319
|
type: 'property',
|
|
543
1320
|
name: propertyName,
|
|
1321
|
+
capitalizedName: capitalizeFirst(propertyName),
|
|
544
1322
|
prefixes: formatPrefixes(),
|
|
545
1323
|
},
|
|
546
1324
|
});
|
|
@@ -565,6 +1343,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
565
1343
|
data: {
|
|
566
1344
|
type: 'parameter',
|
|
567
1345
|
name: paramName,
|
|
1346
|
+
capitalizedName: capitalizeFirst(paramName),
|
|
568
1347
|
prefixes: formatPrefixes(),
|
|
569
1348
|
},
|
|
570
1349
|
});
|
|
@@ -587,6 +1366,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
587
1366
|
data: {
|
|
588
1367
|
type: 'property',
|
|
589
1368
|
name: propertyName,
|
|
1369
|
+
capitalizedName: capitalizeFirst(propertyName),
|
|
590
1370
|
prefixes: formatPrefixes(),
|
|
591
1371
|
},
|
|
592
1372
|
});
|
|
@@ -609,6 +1389,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
609
1389
|
}
|
|
610
1390
|
},
|
|
611
1391
|
MethodDefinition: checkMethodDefinition,
|
|
1392
|
+
TSAbstractMethodDefinition: checkMethodDefinition,
|
|
612
1393
|
Property: checkProperty,
|
|
613
1394
|
ClassProperty: checkClassProperty,
|
|
614
1395
|
PropertyDefinition: checkClassPropertyDeclaration,
|