@blumintinc/eslint-plugin-blumint 1.13.0 → 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.
Files changed (186) hide show
  1. package/README.md +170 -139
  2. package/lib/index.js +190 -1
  3. package/lib/rules/array-methods-this-context.js +1 -1
  4. package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
  5. package/lib/rules/consistent-callback-naming.js +1 -1
  6. package/lib/rules/dynamic-https-errors.js +3 -4
  7. package/lib/rules/enforce-assert-safe-object-key.js +11 -11
  8. package/lib/rules/enforce-boolean-naming-prefixes.js +11 -23
  9. package/lib/rules/enforce-callback-memo.js +1 -1
  10. package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
  11. package/lib/rules/enforce-console-error.js +35 -7
  12. package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
  13. package/lib/rules/enforce-dynamic-file-naming.js +53 -28
  14. package/lib/rules/enforce-early-destructuring.d.ts +2 -0
  15. package/lib/rules/enforce-early-destructuring.js +980 -0
  16. package/lib/rules/enforce-empty-object-check.d.ts +11 -0
  17. package/lib/rules/enforce-empty-object-check.js +502 -0
  18. package/lib/rules/enforce-exported-function-types.js +1 -1
  19. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
  20. package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
  21. package/lib/rules/enforce-firestore-facade.js +4 -2
  22. package/lib/rules/enforce-firestore-set-merge.js +3 -4
  23. package/lib/rules/enforce-global-constants.js +1 -1
  24. package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
  25. package/lib/rules/enforce-memoize-async.js +2 -6
  26. package/lib/rules/enforce-memoize-getters.js +39 -15
  27. package/lib/rules/enforce-microdiff.js +1 -1
  28. package/lib/rules/enforce-object-literal-as-const.js +4 -4
  29. package/lib/rules/enforce-positive-naming.js +4 -0
  30. package/lib/rules/enforce-props-argument-name.js +2 -2
  31. package/lib/rules/enforce-safe-stringify.js +1 -1
  32. package/lib/rules/enforce-serializable-params.js +3 -3
  33. package/lib/rules/enforce-singular-type-names.js +1 -1
  34. package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
  35. package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
  36. package/lib/rules/enforce-storage-context.d.ts +9 -0
  37. package/lib/rules/enforce-storage-context.js +646 -0
  38. package/lib/rules/enforce-timestamp-now.js +23 -7
  39. package/lib/rules/enforce-transform-memoization.d.ts +4 -0
  40. package/lib/rules/enforce-transform-memoization.js +416 -0
  41. package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
  42. package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
  43. package/lib/rules/enforce-unique-cursor-headers.js +365 -0
  44. package/lib/rules/enforce-verb-noun-naming.js +5 -4
  45. package/lib/rules/ensure-pointer-events-none.js +28 -2
  46. package/lib/rules/export-if-in-doubt.js +27 -4
  47. package/lib/rules/extract-global-constants.js +40 -13
  48. package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
  49. package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
  50. package/lib/rules/flatten-push-calls.d.ts +2 -0
  51. package/lib/rules/flatten-push-calls.js +428 -0
  52. package/lib/rules/global-const-style.js +50 -19
  53. package/lib/rules/jsdoc-above-field.d.ts +11 -0
  54. package/lib/rules/jsdoc-above-field.js +208 -0
  55. package/lib/rules/key-only-outermost-element.js +21 -3
  56. package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
  57. package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
  58. package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
  59. package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
  60. package/lib/rules/memo-nested-react-components.d.ts +8 -0
  61. package/lib/rules/memo-nested-react-components.js +410 -0
  62. package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
  63. package/lib/rules/no-always-true-false-conditions.js +68 -8
  64. package/lib/rules/no-array-length-in-deps.js +9 -3
  65. package/lib/rules/no-async-foreach.js +210 -12
  66. package/lib/rules/no-circular-references.js +18 -17
  67. package/lib/rules/no-class-instance-destructuring.js +55 -12
  68. package/lib/rules/no-complex-cloud-params.js +7 -3
  69. package/lib/rules/no-compositing-layer-props.js +2 -2
  70. package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
  71. package/lib/rules/no-console-error.d.ts +9 -0
  72. package/lib/rules/no-console-error.js +525 -0
  73. package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
  74. package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
  75. package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
  76. package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
  77. package/lib/rules/no-entire-object-hook-deps.js +4 -6
  78. package/lib/rules/no-excessive-parent-chain.js +1 -1
  79. package/lib/rules/no-explicit-return-type.d.ts +2 -1
  80. package/lib/rules/no-explicit-return-type.js +215 -26
  81. package/lib/rules/no-filter-without-return.js +5 -1
  82. package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
  83. package/lib/rules/no-firestore-jest-mock.js +126 -8
  84. package/lib/rules/no-firestore-object-arrays.js +67 -12
  85. package/lib/rules/no-handler-suffix.d.ts +12 -0
  86. package/lib/rules/no-handler-suffix.js +305 -0
  87. package/lib/rules/no-hungarian.js +1 -1
  88. package/lib/rules/no-inline-component-prop.d.ts +10 -0
  89. package/lib/rules/no-inline-component-prop.js +456 -0
  90. package/lib/rules/no-jsx-in-hooks.js +6 -1
  91. package/lib/rules/no-jsx-whitespace-literal.js +8 -2
  92. package/lib/rules/no-margin-properties.js +6 -6
  93. package/lib/rules/no-memoize-on-static.js +9 -1
  94. package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
  95. package/lib/rules/no-misused-switch-case.js +22 -1
  96. package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
  97. package/lib/rules/no-mixed-firestore-transactions.js +296 -34
  98. package/lib/rules/no-mock-firebase-admin.js +5 -2
  99. package/lib/rules/no-object-values-on-strings.js +10 -2
  100. package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
  101. package/lib/rules/no-passthrough-getters.js +38 -2
  102. package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
  103. package/lib/rules/no-redundant-annotation-assertion.js +402 -0
  104. package/lib/rules/no-redundant-param-types.js +16 -6
  105. package/lib/rules/no-redundant-this-params.d.ts +3 -0
  106. package/lib/rules/no-redundant-this-params.js +459 -0
  107. package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
  108. package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
  109. package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
  110. package/lib/rules/no-restricted-properties-fix.js +11 -10
  111. package/lib/rules/no-separate-loading-state.js +7 -16
  112. package/lib/rules/no-stale-state-across-await.js +1 -1
  113. package/lib/rules/no-type-assertion-returns.js +43 -49
  114. package/lib/rules/no-undefined-null-passthrough.js +61 -31
  115. package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
  116. package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
  117. package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
  118. package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
  119. package/lib/rules/no-unnecessary-destructuring.js +14 -4
  120. package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
  121. package/lib/rules/no-unpinned-dependencies.js +36 -5
  122. package/lib/rules/no-unused-props.d.ts +2 -2
  123. package/lib/rules/no-unused-props.js +295 -91
  124. package/lib/rules/no-unused-usestate.js +6 -2
  125. package/lib/rules/no-useless-fragment.js +28 -2
  126. package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
  127. package/lib/rules/no-useless-usememo-primitives.js +393 -0
  128. package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
  129. package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
  130. package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
  131. package/lib/rules/omit-index-html.d.ts +2 -1
  132. package/lib/rules/omit-index-html.js +62 -7
  133. package/lib/rules/optimize-object-boolean-conditions.js +6 -4
  134. package/lib/rules/parallelize-async-operations.js +24 -5
  135. package/lib/rules/prefer-batch-operations.d.ts +1 -3
  136. package/lib/rules/prefer-batch-operations.js +32 -5
  137. package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
  138. package/lib/rules/prefer-clone-deep.js +3 -3
  139. package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
  140. package/lib/rules/prefer-destructuring-no-class.js +244 -59
  141. package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
  142. package/lib/rules/prefer-docsetter-setall.js +243 -0
  143. package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
  144. package/lib/rules/prefer-fragment-component.js +1 -1
  145. package/lib/rules/prefer-fragment-shorthand.js +2 -1
  146. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
  147. package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
  148. package/lib/rules/prefer-global-router-state-key.js +88 -13
  149. package/lib/rules/prefer-memoized-props.d.ts +3 -0
  150. package/lib/rules/prefer-memoized-props.js +445 -0
  151. package/lib/rules/prefer-next-dynamic.js +60 -69
  152. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +9 -4
  153. package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
  154. package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
  155. package/lib/rules/prefer-params-over-parent-id.js +220 -171
  156. package/lib/rules/prefer-settings-object.js +2 -2
  157. package/lib/rules/prefer-type-over-interface.js +7 -2
  158. package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
  159. package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
  160. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
  161. package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
  162. package/lib/rules/prevent-children-clobber.d.ts +2 -0
  163. package/lib/rules/prevent-children-clobber.js +536 -0
  164. package/lib/rules/react-memoize-literals.d.ts +4 -0
  165. package/lib/rules/react-memoize-literals.js +495 -0
  166. package/lib/rules/react-usememo-should-be-component.js +2 -2
  167. package/lib/rules/require-hooks-default-params.js +17 -16
  168. package/lib/rules/require-https-error-cause.d.ts +4 -0
  169. package/lib/rules/require-https-error-cause.js +136 -0
  170. package/lib/rules/require-https-error.js +43 -21
  171. package/lib/rules/require-image-optimized.js +1 -1
  172. package/lib/rules/require-memo.js +1 -1
  173. package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
  174. package/lib/rules/require-memoize-jsx-returners.js +485 -0
  175. package/lib/rules/require-usememo-object-literals.js +2 -3
  176. package/lib/rules/test-file-location-enforcement.js +1 -1
  177. package/lib/rules/use-latest-callback.js +6 -5
  178. package/lib/rules/vertically-group-related-functions.d.ts +16 -0
  179. package/lib/rules/vertically-group-related-functions.js +480 -0
  180. package/lib/utils/ASTHelpers.d.ts +13 -1
  181. package/lib/utils/ASTHelpers.js +24 -0
  182. package/lib/utils/getMethodName.d.ts +27 -0
  183. package/lib/utils/getMethodName.js +35 -0
  184. package/lib/utils/tsTypeClassifier.d.ts +30 -0
  185. package/lib/utils/tsTypeClassifier.js +149 -0
  186. package/package.json +3 -1
@@ -0,0 +1,648 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.preferGetterOverParameterlessMethod = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const DEFAULT_PREFIXES = [
7
+ 'build',
8
+ 'get',
9
+ 'compute',
10
+ 'calculate',
11
+ 'retrieve',
12
+ 'extract',
13
+ 'create',
14
+ 'generate',
15
+ 'make',
16
+ 'fetch',
17
+ 'load',
18
+ 'derive',
19
+ 'resolve',
20
+ 'determine',
21
+ 'find',
22
+ 'obtain',
23
+ 'produce',
24
+ 'acquire',
25
+ ];
26
+ const DEFAULT_IGNORED_METHODS = [
27
+ 'toString',
28
+ 'toJSON',
29
+ 'valueOf',
30
+ 'clone',
31
+ 'copy',
32
+ 'serialize',
33
+ 'deserialize',
34
+ 'parse',
35
+ 'stringify',
36
+ ];
37
+ const SIDE_EFFECT_TAGS = ['sideEffect', 'sideEffects', 'mutates'];
38
+ const DEFAULT_OPTIONS = {
39
+ stripPrefixes: DEFAULT_PREFIXES,
40
+ ignoredMethods: DEFAULT_IGNORED_METHODS,
41
+ ignoreAsync: true,
42
+ ignoreVoidReturn: true,
43
+ ignoreAbstract: true,
44
+ respectJsDocSideEffects: true,
45
+ minBodyLines: 0,
46
+ };
47
+ const MUTATING_ARRAY_METHODS = [
48
+ 'push',
49
+ 'pop',
50
+ 'shift',
51
+ 'unshift',
52
+ 'splice',
53
+ 'sort',
54
+ 'reverse',
55
+ 'fill',
56
+ 'copyWithin',
57
+ ];
58
+ const MUTATING_COLLECTION_METHODS = ['set', 'add', 'delete', 'clear'];
59
+ function isVoidishType(node) {
60
+ switch (node.type) {
61
+ case utils_1.AST_NODE_TYPES.TSVoidKeyword:
62
+ case utils_1.AST_NODE_TYPES.TSUndefinedKeyword:
63
+ return true;
64
+ case utils_1.AST_NODE_TYPES.TSUnionType:
65
+ return node.types.every(isVoidishType);
66
+ default:
67
+ return false;
68
+ }
69
+ }
70
+ function isFunctionLikeNode(value) {
71
+ return (value.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
72
+ value.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
73
+ value.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression);
74
+ }
75
+ function lowerFirst(text) {
76
+ if (!text)
77
+ return text;
78
+ return text[0].toLowerCase() + text.slice(1);
79
+ }
80
+ function computeBodyLineCount(body) {
81
+ return Math.max(0, body.loc.end.line - body.loc.start.line - 1);
82
+ }
83
+ function hasNameCollision(node, newName) {
84
+ const classBody = node.parent;
85
+ if (!classBody || classBody.type !== utils_1.AST_NODE_TYPES.ClassBody) {
86
+ return false;
87
+ }
88
+ const targetIsStatic = node.static ?? false;
89
+ return classBody.body.some((member) => {
90
+ if (member === node) {
91
+ return false;
92
+ }
93
+ const key = member.key;
94
+ if (!key || member.type === utils_1.AST_NODE_TYPES.StaticBlock) {
95
+ return false;
96
+ }
97
+ if (member.computed) {
98
+ return false;
99
+ }
100
+ const memberIsStatic = member.static ?? false;
101
+ if (memberIsStatic !== targetIsStatic) {
102
+ return false;
103
+ }
104
+ if (member.type === utils_1.AST_NODE_TYPES.MethodDefinition &&
105
+ member.kind === 'set') {
106
+ return false;
107
+ }
108
+ if (key.type === utils_1.AST_NODE_TYPES.Identifier && key.name === newName) {
109
+ return true;
110
+ }
111
+ return false;
112
+ });
113
+ }
114
+ function hasOverloadSignatures(node) {
115
+ const classBody = node.parent;
116
+ if (!classBody || classBody.type !== utils_1.AST_NODE_TYPES.ClassBody) {
117
+ return false;
118
+ }
119
+ if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier) {
120
+ return false;
121
+ }
122
+ const targetName = node.key.name;
123
+ const targetIsStatic = node.static ?? false;
124
+ return classBody.body.some((member) => {
125
+ if (member.type !== utils_1.AST_NODE_TYPES.MethodDefinition &&
126
+ member.type !== utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition) {
127
+ return false;
128
+ }
129
+ if (member.computed) {
130
+ return false;
131
+ }
132
+ const key = member.key;
133
+ if (!key || key.type !== utils_1.AST_NODE_TYPES.Identifier) {
134
+ return false;
135
+ }
136
+ const memberIsStatic = member.static ?? false;
137
+ if (memberIsStatic !== targetIsStatic) {
138
+ return false;
139
+ }
140
+ if (member.type === utils_1.AST_NODE_TYPES.MethodDefinition &&
141
+ member.kind !== 'method') {
142
+ return false;
143
+ }
144
+ const hasBody = member.value?.body;
145
+ return !hasBody && key.name === targetName;
146
+ });
147
+ }
148
+ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
149
+ name: 'prefer-getter-over-parameterless-method',
150
+ meta: {
151
+ type: 'suggestion',
152
+ docs: {
153
+ description: 'Enforce getter syntax for synchronous parameterless methods that return values, improving semantic clarity and avoiding accidental method invocation without parentheses.',
154
+ recommended: 'error',
155
+ },
156
+ fixable: 'code',
157
+ schema: [
158
+ {
159
+ type: 'object',
160
+ properties: {
161
+ stripPrefixes: {
162
+ type: 'array',
163
+ items: { type: 'string' },
164
+ },
165
+ ignoredMethods: {
166
+ type: 'array',
167
+ items: { type: 'string' },
168
+ },
169
+ ignoreAsync: {
170
+ type: 'boolean',
171
+ },
172
+ ignoreVoidReturn: {
173
+ type: 'boolean',
174
+ },
175
+ ignoreAbstract: {
176
+ type: 'boolean',
177
+ },
178
+ respectJsDocSideEffects: {
179
+ type: 'boolean',
180
+ },
181
+ minBodyLines: {
182
+ type: 'number',
183
+ minimum: 0,
184
+ },
185
+ },
186
+ additionalProperties: false,
187
+ },
188
+ ],
189
+ messages: {
190
+ preferGetter: 'Problem: Method "{{name}}" is a parameterless synchronous method that returns a value. → Impact: Calling it with parentheses disguises that it behaves like a computed property, increasing the chance callers omit parentheses or treat it like an action. → Solution: Convert it to a getter: "get {{suggestedName}}()" to signal property semantics and remove call-site parentheses.',
191
+ preferGetterSideEffect: 'Problem: Method "{{name}}" looks like a computed value. → Impact: However, {{reason}}, so converting it to a getter would execute side effects on every property access and violate the principle of least surprise. → Solution: Keep it as a method to indicate it performs work, or remove the side effects before converting to "get {{suggestedName}}()".',
192
+ },
193
+ },
194
+ defaultOptions: [DEFAULT_OPTIONS],
195
+ create(context, [options]) {
196
+ const userOptions = options ?? {};
197
+ const config = {
198
+ ...DEFAULT_OPTIONS,
199
+ ...userOptions,
200
+ stripPrefixes: userOptions.stripPrefixes ?? DEFAULT_OPTIONS.stripPrefixes,
201
+ ignoredMethods: userOptions.ignoredMethods ?? DEFAULT_OPTIONS.ignoredMethods,
202
+ };
203
+ const ignoredMethods = new Set(config.ignoredMethods);
204
+ const prefixList = config.stripPrefixes;
205
+ const sourceCode = context.getSourceCode();
206
+ const ignoredTraversalKeys = new Set(['parent', 'loc', 'range']);
207
+ function ensureGlobalPattern(pattern) {
208
+ const flags = pattern.flags.includes('g')
209
+ ? pattern.flags
210
+ : `${pattern.flags}g`;
211
+ return new RegExp(pattern.source, flags);
212
+ }
213
+ function pushChildNodes(current, stack) {
214
+ for (const [key, value] of Object.entries(current)) {
215
+ if (ignoredTraversalKeys.has(key))
216
+ continue;
217
+ if (!value)
218
+ continue;
219
+ if (Array.isArray(value)) {
220
+ for (const item of value) {
221
+ if (item && typeof item.type === 'string') {
222
+ stack.push(item);
223
+ }
224
+ }
225
+ }
226
+ else if (typeof value.type === 'string') {
227
+ stack.push(value);
228
+ }
229
+ }
230
+ }
231
+ function getEnclosingClassBody(node) {
232
+ let current = node;
233
+ while (current) {
234
+ if (current.type === utils_1.AST_NODE_TYPES.ClassBody) {
235
+ return current;
236
+ }
237
+ current = current.parent ?? null;
238
+ }
239
+ return null;
240
+ }
241
+ function addCallUse(node, propName, callUsedNamesByClass, callUsedNamesInFile) {
242
+ callUsedNamesInFile.add(propName);
243
+ const classBody = getEnclosingClassBody(node);
244
+ if (!classBody) {
245
+ return;
246
+ }
247
+ let names = callUsedNamesByClass.get(classBody);
248
+ if (!names) {
249
+ names = new Set();
250
+ callUsedNamesByClass.set(classBody, names);
251
+ }
252
+ names.add(propName);
253
+ }
254
+ function addCallUseForMember(member, propName, callUsedNamesByClass, callUsedNamesInFile) {
255
+ addCallUse(member, propName, callUsedNamesByClass, callUsedNamesInFile);
256
+ }
257
+ function getJsDoc(node) {
258
+ const comments = sourceCode.getCommentsBefore(node);
259
+ const candidate = comments[comments.length - 1];
260
+ if (candidate &&
261
+ candidate.type === 'Block' &&
262
+ candidate.value.startsWith('*') &&
263
+ candidate.loc &&
264
+ node.loc &&
265
+ candidate.loc.end.line === node.loc.start.line - 1) {
266
+ return candidate;
267
+ }
268
+ return null;
269
+ }
270
+ function hasSideEffectTag(node) {
271
+ const jsDoc = getJsDoc(node);
272
+ if (!jsDoc) {
273
+ return false;
274
+ }
275
+ const value = jsDoc.value;
276
+ if (SIDE_EFFECT_TAGS.some((tag) => value.includes(`@${tag}`))) {
277
+ return true;
278
+ }
279
+ const normalized = value.toLowerCase().replace(/[-\s]+/g, ' ');
280
+ const negationPattern = /\b(?:no|without|not|non|none|never|free of|lack|lacks|lacking|avoid|avoids|avoiding)\b/;
281
+ /**
282
+ * Detects matches that are not negated within a short preceding context.
283
+ * Uses a 24-character lookback to catch nearby negations like "no", "not",
284
+ * or "without" that typically appear immediately before side-effect wording,
285
+ * balancing coverage with keeping false positives low.
286
+ */
287
+ const matchesAffirmative = (pattern) => {
288
+ const globalPattern = ensureGlobalPattern(pattern);
289
+ const matches = normalized.matchAll(globalPattern);
290
+ for (const match of matches) {
291
+ const start = match.index ?? 0;
292
+ const windowStart = Math.max(0, start - 24);
293
+ const window = normalized.slice(windowStart, start);
294
+ if (!negationPattern.test(window)) {
295
+ return true;
296
+ }
297
+ }
298
+ return false;
299
+ };
300
+ return (matchesAffirmative(/\bside effects?\b/) ||
301
+ matchesAffirmative(/\bmutat(?:e|es|ing|ion|ions|ed|ive|ively)?\b/));
302
+ }
303
+ function analyzeMutations(body) {
304
+ const stack = [...body.body];
305
+ while (stack.length) {
306
+ const current = stack.pop();
307
+ if (isFunctionLikeNode(current)) {
308
+ continue;
309
+ }
310
+ if (current.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
311
+ current.operator === 'delete') {
312
+ return `it deletes ${sourceCode.getText(current.argument)}`;
313
+ }
314
+ if (current.type === utils_1.AST_NODE_TYPES.UpdateExpression) {
315
+ return 'it mutates state with ++/--';
316
+ }
317
+ if (current.type === utils_1.AST_NODE_TYPES.AssignmentExpression) {
318
+ if (current.left.type === utils_1.AST_NODE_TYPES.MemberExpression) {
319
+ return `it assigns to ${sourceCode.getText(current.left)}`;
320
+ }
321
+ }
322
+ if (current.type === utils_1.AST_NODE_TYPES.CallExpression) {
323
+ const callee = current.callee;
324
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
325
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
326
+ (MUTATING_ARRAY_METHODS.includes(callee.property.name) ||
327
+ MUTATING_COLLECTION_METHODS.includes(callee.property.name))) {
328
+ return `it calls mutating method ${callee.property.name}()`;
329
+ }
330
+ }
331
+ pushChildNodes(current, stack);
332
+ }
333
+ return null;
334
+ }
335
+ function returnsValue(node) {
336
+ const returnType = node.value.returnType?.typeAnnotation;
337
+ if (returnType) {
338
+ if (isVoidishType(returnType)) {
339
+ return false;
340
+ }
341
+ return true;
342
+ }
343
+ const body = node.value.body;
344
+ if (!body)
345
+ return false;
346
+ const stack = [...body.body];
347
+ while (stack.length) {
348
+ const current = stack.pop();
349
+ if (isFunctionLikeNode(current)) {
350
+ continue;
351
+ }
352
+ if (current.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
353
+ current.argument) {
354
+ return true;
355
+ }
356
+ pushChildNodes(current, stack);
357
+ }
358
+ return false;
359
+ }
360
+ function suggestName(name) {
361
+ for (const prefix of prefixList) {
362
+ if (name.startsWith(prefix) &&
363
+ name.length > prefix.length &&
364
+ /[A-Z_]/.test(name[prefix.length])) {
365
+ const remainder = name.slice(prefix.length);
366
+ return lowerFirst(remainder);
367
+ }
368
+ }
369
+ return name;
370
+ }
371
+ function isThisProperty(member, propName) {
372
+ if (member.object.type !== utils_1.AST_NODE_TYPES.ThisExpression &&
373
+ member.object.type !== utils_1.AST_NODE_TYPES.Super) {
374
+ return false;
375
+ }
376
+ if (!member.computed &&
377
+ member.property.type === utils_1.AST_NODE_TYPES.Identifier) {
378
+ return member.property.name === propName;
379
+ }
380
+ if (member.computed &&
381
+ member.property.type === utils_1.AST_NODE_TYPES.Literal &&
382
+ typeof member.property.value === 'string') {
383
+ return member.property.value === propName;
384
+ }
385
+ return false;
386
+ }
387
+ function bodyReferencesThisProperty(body, propName) {
388
+ const stack = [...body.body];
389
+ while (stack.length) {
390
+ const current = stack.pop();
391
+ if (isFunctionLikeNode(current)) {
392
+ continue;
393
+ }
394
+ if (current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
395
+ if (isThisProperty(current, propName)) {
396
+ return true;
397
+ }
398
+ }
399
+ else if (current.type === utils_1.AST_NODE_TYPES.ChainExpression) {
400
+ const expression = current.expression;
401
+ if (expression.type === utils_1.AST_NODE_TYPES.MemberExpression &&
402
+ isThisProperty(expression, propName)) {
403
+ return true;
404
+ }
405
+ }
406
+ pushChildNodes(current, stack);
407
+ }
408
+ return false;
409
+ }
410
+ function isMemberExpressionNode(value) {
411
+ return value.type === utils_1.AST_NODE_TYPES.MemberExpression;
412
+ }
413
+ function trackMemberCall(member, callUsedNamesByClass, callUsedNamesInFile) {
414
+ if (member.computed ||
415
+ member.property.type !== utils_1.AST_NODE_TYPES.Identifier) {
416
+ return;
417
+ }
418
+ const propName = member.property.name;
419
+ if (propName === 'call' || propName === 'apply' || propName === 'bind') {
420
+ const target = member.object;
421
+ if (isMemberExpressionNode(target) &&
422
+ !target.computed &&
423
+ target.property.type === utils_1.AST_NODE_TYPES.Identifier &&
424
+ target.object.type === utils_1.AST_NODE_TYPES.ThisExpression) {
425
+ addCallUseForMember(member, target.property.name, callUsedNamesByClass, callUsedNamesInFile);
426
+ }
427
+ else if (target.type === utils_1.AST_NODE_TYPES.ThisExpression) {
428
+ addCallUseForMember(member, propName, callUsedNamesByClass, callUsedNamesInFile);
429
+ }
430
+ else if (isMemberExpressionNode(target) &&
431
+ !target.computed &&
432
+ target.property.type === utils_1.AST_NODE_TYPES.Identifier) {
433
+ addCallUseForMember(target, target.property.name, callUsedNamesByClass, callUsedNamesInFile);
434
+ }
435
+ else if (target.type === utils_1.AST_NODE_TYPES.Identifier) {
436
+ addCallUseForMember(member, target.name, callUsedNamesByClass, callUsedNamesInFile);
437
+ }
438
+ else {
439
+ addCallUseForMember(member, propName, callUsedNamesByClass, callUsedNamesInFile);
440
+ }
441
+ return;
442
+ }
443
+ addCallUseForMember(member, propName, callUsedNamesByClass, callUsedNamesInFile);
444
+ }
445
+ function isFunctionReference(member) {
446
+ const parent = member.parent;
447
+ if (!parent)
448
+ return false;
449
+ if (parent.type === utils_1.AST_NODE_TYPES.CallExpression) {
450
+ if (parent.callee === member) {
451
+ return false;
452
+ }
453
+ return true;
454
+ }
455
+ if (parent.type === utils_1.AST_NODE_TYPES.MemberExpression) {
456
+ return false;
457
+ }
458
+ if (parent.type === utils_1.AST_NODE_TYPES.ChainExpression) {
459
+ const chainParent = parent.parent;
460
+ if (chainParent &&
461
+ chainParent.type === utils_1.AST_NODE_TYPES.CallExpression &&
462
+ chainParent.callee ===
463
+ parent) {
464
+ return false;
465
+ }
466
+ return true;
467
+ }
468
+ return true;
469
+ }
470
+ function trackMemberReference(member, callUsedNamesByClass, callUsedNamesInFile) {
471
+ if (member.computed ||
472
+ member.property.type !== utils_1.AST_NODE_TYPES.Identifier) {
473
+ return;
474
+ }
475
+ if (!isFunctionReference(member)) {
476
+ return;
477
+ }
478
+ if (member.object.type === utils_1.AST_NODE_TYPES.ThisExpression) {
479
+ addCallUseForMember(member, member.property.name, callUsedNamesByClass, callUsedNamesInFile);
480
+ return;
481
+ }
482
+ addCallUseForMember(member, member.property.name, callUsedNamesByClass, callUsedNamesInFile);
483
+ }
484
+ function trackThisDestructuring(pattern, init, callUsedNamesByClass, callUsedNamesInFile) {
485
+ if (!init || init.type !== utils_1.AST_NODE_TYPES.ThisExpression) {
486
+ return;
487
+ }
488
+ for (const prop of pattern.properties) {
489
+ if (prop.type === utils_1.AST_NODE_TYPES.Property) {
490
+ if (prop.computed)
491
+ continue;
492
+ const key = prop.key;
493
+ if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
494
+ addCallUse(pattern, key.name, callUsedNamesByClass, callUsedNamesInFile);
495
+ }
496
+ else if (key.type === utils_1.AST_NODE_TYPES.Literal &&
497
+ typeof key.value === 'string') {
498
+ addCallUse(pattern, key.value, callUsedNamesByClass, callUsedNamesInFile);
499
+ }
500
+ }
501
+ }
502
+ }
503
+ const callUsedNamesByClass = new WeakMap();
504
+ const callUsedNamesInFile = new Set();
505
+ const candidates = [];
506
+ return {
507
+ CallExpression(node) {
508
+ const callee = node.callee;
509
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
510
+ trackMemberCall(callee, callUsedNamesByClass, callUsedNamesInFile);
511
+ }
512
+ else if (callee.type === utils_1.AST_NODE_TYPES.ChainExpression) {
513
+ const expression = callee.expression;
514
+ if (expression.type === utils_1.AST_NODE_TYPES.MemberExpression) {
515
+ trackMemberCall(expression, callUsedNamesByClass, callUsedNamesInFile);
516
+ }
517
+ }
518
+ },
519
+ MemberExpression(node) {
520
+ trackMemberReference(node, callUsedNamesByClass, callUsedNamesInFile);
521
+ },
522
+ ChainExpression(node) {
523
+ if (node.expression.type === utils_1.AST_NODE_TYPES.MemberExpression) {
524
+ trackMemberReference(node.expression, callUsedNamesByClass, callUsedNamesInFile);
525
+ }
526
+ },
527
+ VariableDeclarator(node) {
528
+ if (node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
529
+ trackThisDestructuring(node.id, node.init ?? null, callUsedNamesByClass, callUsedNamesInFile);
530
+ }
531
+ },
532
+ AssignmentExpression(node) {
533
+ if (node.left.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
534
+ trackThisDestructuring(node.left, node.right, callUsedNamesByClass, callUsedNamesInFile);
535
+ }
536
+ },
537
+ 'MethodDefinition, TSAbstractMethodDefinition'(node) {
538
+ if (node.kind !== 'method')
539
+ return;
540
+ if (node.value.params.length > 0)
541
+ return;
542
+ if (node.value.generator)
543
+ return;
544
+ if (node.optional)
545
+ return;
546
+ if (node.computed)
547
+ return;
548
+ if (node.value.typeParameters)
549
+ return;
550
+ if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
551
+ return;
552
+ if (config.ignoreAsync && node.value.async)
553
+ return;
554
+ if (config.ignoreAbstract &&
555
+ node.type === utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition) {
556
+ return;
557
+ }
558
+ if (node.override)
559
+ return;
560
+ if (!node.value.body)
561
+ return;
562
+ const name = node.key.name;
563
+ if (ignoredMethods.has(name))
564
+ return;
565
+ if (hasOverloadSignatures(node))
566
+ return;
567
+ const body = node.value.body;
568
+ if (computeBodyLineCount(body) < config.minBodyLines) {
569
+ return;
570
+ }
571
+ if (!returnsValue(node))
572
+ return;
573
+ if (config.respectJsDocSideEffects && hasSideEffectTag(node))
574
+ return;
575
+ const sideEffectReason = analyzeMutations(body);
576
+ const suggestedName = suggestName(name);
577
+ candidates.push({ node, sideEffectReason, suggestedName });
578
+ },
579
+ 'Program:exit'() {
580
+ const suggestedNameCounts = new WeakMap();
581
+ for (const { node, suggestedName } of candidates) {
582
+ const classBody = node.parent;
583
+ if (!classBody || classBody.type !== utils_1.AST_NODE_TYPES.ClassBody) {
584
+ continue;
585
+ }
586
+ const scopeKey = `${(node.static ?? false)
587
+ ? 'static'
588
+ : 'instance'}:${suggestedName}`;
589
+ const existingCounts = suggestedNameCounts.get(classBody) ?? new Map();
590
+ existingCounts.set(scopeKey, (existingCounts.get(scopeKey) ?? 0) + 1);
591
+ suggestedNameCounts.set(classBody, existingCounts);
592
+ }
593
+ for (const { node, sideEffectReason, suggestedName } of candidates) {
594
+ const name = node.key.name;
595
+ const classBody = node.parent;
596
+ const scopeKey = `${(node.static ?? false)
597
+ ? 'static'
598
+ : 'instance'}:${suggestedName}`;
599
+ const leftParen = sourceCode.getTokenAfter(node.key, {
600
+ filter: (token) => token.value === '(',
601
+ });
602
+ const rightParen = leftParen
603
+ ? sourceCode.getTokenAfter(leftParen, {
604
+ filter: (token) => token.value === ')',
605
+ })
606
+ : null;
607
+ const body = node.value.body;
608
+ const referencesSuggestedName = body && body.type === utils_1.AST_NODE_TYPES.BlockStatement
609
+ ? bodyReferencesThisProperty(body, suggestedName)
610
+ : false;
611
+ const hasCollision = hasNameCollision(node, suggestedName);
612
+ const isCallUsed = classBody?.type === utils_1.AST_NODE_TYPES.ClassBody
613
+ ? callUsedNamesByClass.get(classBody)?.has(name) ?? false
614
+ : false;
615
+ const isCallUsedSomewhereInFile = callUsedNamesInFile.has(name);
616
+ const hasDuplicateSuggestedName = classBody?.type === utils_1.AST_NODE_TYPES.ClassBody
617
+ ? (suggestedNameCounts.get(classBody)?.get(scopeKey) ?? 0) > 1
618
+ : false;
619
+ const isAsync = node.value.async;
620
+ context.report({
621
+ node: node.key,
622
+ messageId: sideEffectReason
623
+ ? 'preferGetterSideEffect'
624
+ : 'preferGetter',
625
+ data: {
626
+ name,
627
+ suggestedName,
628
+ reason: sideEffectReason ?? 'it returns a value',
629
+ },
630
+ fix: sideEffectReason ||
631
+ isAsync ||
632
+ !leftParen ||
633
+ !rightParen ||
634
+ hasCollision ||
635
+ isCallUsed ||
636
+ isCallUsedSomewhereInFile ||
637
+ referencesSuggestedName ||
638
+ hasDuplicateSuggestedName
639
+ ? null
640
+ : (fixer) => fixer.replaceTextRange([node.key.range[0], rightParen.range[1]], `get ${suggestedName}()`),
641
+ });
642
+ }
643
+ },
644
+ };
645
+ },
646
+ });
647
+ exports.default = exports.preferGetterOverParameterlessMethod;
648
+ //# sourceMappingURL=prefer-getter-over-parameterless-method.js.map