@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,646 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.enforceStorageContext = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const minimatch_1 = require("minimatch");
9
+ const utils_1 = require("@typescript-eslint/utils");
10
+ const createRule_1 = require("../utils/createRule");
11
+ const STORAGE_NAMES = new Set(['localStorage', 'sessionStorage']);
12
+ const GLOBAL_NAMES = new Set(['window', 'global', 'globalThis']);
13
+ const CONTEXT_BASENAMES = new Set(['LocalStorage.tsx', 'SessionStorage.tsx']);
14
+ const TEST_FILE_REGEX = /\.(test|spec)\.[jt]sx?$/i;
15
+ const isParenthesizedExpression = (node) => node.type === 'ParenthesizedExpression';
16
+ const isChainExpression = (node) => node.type === 'ChainExpression';
17
+ const createAliasState = () => {
18
+ const stack = [new Map()];
19
+ const scopeKinds = ['function'];
20
+ const currentScope = () => stack[stack.length - 1];
21
+ const pushScope = (kind = 'block') => {
22
+ stack.push(new Map());
23
+ scopeKinds.push(kind);
24
+ };
25
+ const popScope = () => {
26
+ if (stack.length > 1) {
27
+ stack.pop();
28
+ scopeKinds.pop();
29
+ }
30
+ };
31
+ const findFunctionScope = () => {
32
+ for (let i = stack.length - 1; i >= 0; i -= 1) {
33
+ if (scopeKinds[i] === 'function') {
34
+ return stack[i];
35
+ }
36
+ }
37
+ return stack[0];
38
+ };
39
+ const setAlias = (name, value, options) => {
40
+ const targetScope = options?.hoistToFunctionScope
41
+ ? findFunctionScope()
42
+ : currentScope();
43
+ targetScope.set(name, value);
44
+ };
45
+ const setAliasInExistingBindingScope = (name, value) => {
46
+ for (let i = stack.length - 1; i >= 0; i -= 1) {
47
+ if (stack[i].has(name)) {
48
+ stack[i].set(name, value);
49
+ return;
50
+ }
51
+ }
52
+ if (STORAGE_NAMES.has(name) || GLOBAL_NAMES.has(name)) {
53
+ /**
54
+ * Avoid synthesizing a new binding for global storage identifiers; treating
55
+ * the assignment as a shadow creates false negatives for later accesses.
56
+ */
57
+ return;
58
+ }
59
+ setAlias(name, value);
60
+ };
61
+ const markShadowed = (name, options) => setAlias(name, 'shadowed', options);
62
+ const reset = () => {
63
+ stack.length = 1;
64
+ scopeKinds.length = 1;
65
+ scopeKinds[0] = 'function';
66
+ stack[0].clear();
67
+ };
68
+ return {
69
+ stack,
70
+ currentScope,
71
+ pushScope,
72
+ popScope,
73
+ reset,
74
+ setAliasInExistingBindingScope,
75
+ setAlias,
76
+ markShadowed,
77
+ };
78
+ };
79
+ /**
80
+ * Unwraps TypeScript-only wrappers and parentheses to expose the runtime
81
+ * expression. Storage checks need the underlying value even when the code uses
82
+ * TS assertions, satisfies expressions, non-null assertions, optional chaining,
83
+ * or parentheses that do not change runtime behavior (e.g., `(localStorage as Storage)?.getItem()`).
84
+ */
85
+ const unwrapExpression = (expression) => {
86
+ let current = expression;
87
+ while (true) {
88
+ if (current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
89
+ current.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
90
+ current.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
91
+ current.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
92
+ current = current.expression;
93
+ continue;
94
+ }
95
+ if (isParenthesizedExpression(current)) {
96
+ current = current.expression;
97
+ continue;
98
+ }
99
+ if (isChainExpression(current)) {
100
+ current = current.expression;
101
+ continue;
102
+ }
103
+ break;
104
+ }
105
+ return current;
106
+ };
107
+ const getPropertyName = (node) => {
108
+ const unwrapped = node.type === utils_1.AST_NODE_TYPES.PrivateIdentifier
109
+ ? node
110
+ : unwrapExpression(node);
111
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.Identifier)
112
+ return unwrapped.name;
113
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.Literal &&
114
+ typeof unwrapped.value === 'string') {
115
+ return unwrapped.value;
116
+ }
117
+ return null;
118
+ };
119
+ const getAliasFromStack = (aliases, name) => {
120
+ for (let i = aliases.length - 1; i >= 0; i -= 1) {
121
+ const value = aliases[i].get(name);
122
+ if (value !== undefined)
123
+ return value;
124
+ }
125
+ return null;
126
+ };
127
+ const checkIfNameShadowsStorage = (name, storageAliases, options = {}) => {
128
+ const lookupStack = options.includeCurrentScope
129
+ ? storageAliases
130
+ : storageAliases.slice(0, -1);
131
+ const outerAlias = getAliasFromStack(lookupStack, name);
132
+ const shadowsGlobal = STORAGE_NAMES.has(name) || GLOBAL_NAMES.has(name);
133
+ const shadowsOuterStorageAlias = outerAlias === 'localStorage' || outerAlias === 'sessionStorage';
134
+ return shadowsGlobal || shadowsOuterStorageAlias;
135
+ };
136
+ const isGlobalLike = (node, aliases) => {
137
+ const base = unwrapExpression(node);
138
+ if (base.type !== utils_1.AST_NODE_TYPES.Identifier)
139
+ return false;
140
+ if (getAliasFromStack(aliases, base.name))
141
+ return false;
142
+ return GLOBAL_NAMES.has(base.name);
143
+ };
144
+ /**
145
+ * Resolves an expression to a storage kind when it ultimately refers to
146
+ * localStorage/sessionStorage. Follows aliases through member access,
147
+ * conditionals, and logical expressions so indirect access like
148
+ * `const s = window?.localStorage` is still caught. Ignores identifiers that are
149
+ * explicitly shadowed within the current lexical scope.
150
+ */
151
+ const resolveStorageObject = (expression, aliases) => {
152
+ const target = unwrapExpression(expression);
153
+ if (target.type === utils_1.AST_NODE_TYPES.Identifier) {
154
+ const alias = getAliasFromStack(aliases, target.name);
155
+ if (alias === 'shadowed')
156
+ return null;
157
+ if (alias)
158
+ return alias;
159
+ if (STORAGE_NAMES.has(target.name)) {
160
+ return target.name;
161
+ }
162
+ return null;
163
+ }
164
+ if (target.type === utils_1.AST_NODE_TYPES.MemberExpression) {
165
+ const objectKind = resolveStorageObject(target.object, aliases);
166
+ if (objectKind)
167
+ return objectKind;
168
+ const propertyName = getPropertyName(target.property);
169
+ if (propertyName &&
170
+ STORAGE_NAMES.has(propertyName) &&
171
+ isGlobalLike(target.object, aliases)) {
172
+ return propertyName;
173
+ }
174
+ }
175
+ if (target.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
176
+ const consequentKind = resolveStorageObject(target.consequent, aliases);
177
+ if (consequentKind)
178
+ return consequentKind;
179
+ const alternateKind = resolveStorageObject(target.alternate, aliases);
180
+ if (alternateKind)
181
+ return alternateKind;
182
+ }
183
+ if (target.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
184
+ return (resolveStorageObject(target.left, aliases) ??
185
+ resolveStorageObject(target.right, aliases));
186
+ }
187
+ return null;
188
+ };
189
+ const parentMatchesBinding = (parent, type, propertyName, value) => {
190
+ if (parent.type !== type)
191
+ return false;
192
+ if (!propertyName)
193
+ return true;
194
+ const parentProperty = parent[propertyName];
195
+ if (propertyName === 'params' && Array.isArray(parentProperty)) {
196
+ return parentProperty.includes(value);
197
+ }
198
+ return parentProperty === value;
199
+ };
200
+ const identifierHasDeclarationParent = (node) => {
201
+ const parent = node.parent;
202
+ if (!parent)
203
+ return false;
204
+ if (parent.type === utils_1.AST_NODE_TYPES.Property &&
205
+ parent.parent?.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
206
+ return true;
207
+ }
208
+ if (parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.ObjectPattern) ||
209
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.ArrayPattern) ||
210
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.RestElement, 'argument', node) ||
211
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.VariableDeclarator, 'id', node) ||
212
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.AssignmentExpression, 'left', node) ||
213
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.AssignmentPattern, 'left', node) ||
214
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.FunctionDeclaration, 'id', node) ||
215
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.FunctionExpression, 'id', node) ||
216
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.ClassExpression, 'id', node) ||
217
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.CatchClause, 'param', node) ||
218
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.ClassDeclaration, 'id', node) ||
219
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.ImportSpecifier) ||
220
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.ImportDefaultSpecifier) ||
221
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) ||
222
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.ExportSpecifier) ||
223
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.TSEnumMember, 'id', node)) {
224
+ return true;
225
+ }
226
+ if (parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.FunctionDeclaration, 'params', node) ||
227
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.FunctionExpression, 'params', node) ||
228
+ parentMatchesBinding(parent, utils_1.AST_NODE_TYPES.ArrowFunctionExpression, 'params', node)) {
229
+ return true;
230
+ }
231
+ return false;
232
+ };
233
+ const isObjectLiteralKeyDeclaration = (node, parent) => {
234
+ if (parent.type !== utils_1.AST_NODE_TYPES.Property)
235
+ return false;
236
+ if (parent.shorthand)
237
+ return false;
238
+ return parent.key === node;
239
+ };
240
+ const isClassMemberKeyDeclaration = (node, parent) => (parent.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
241
+ parent.type === utils_1.AST_NODE_TYPES.PropertyDefinition) &&
242
+ parent.key === node;
243
+ const isTypeScriptDeclarationIdentifier = (node, parent) => (parent.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration &&
244
+ parent.id === node) ||
245
+ (parent.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration &&
246
+ parent.id === node) ||
247
+ (parent.type === utils_1.AST_NODE_TYPES.TSEnumDeclaration && parent.id === node) ||
248
+ (parent.type === utils_1.AST_NODE_TYPES.TSEnumMember && parent.id === node) ||
249
+ (parent.type === utils_1.AST_NODE_TYPES.TSModuleDeclaration && parent.id === node);
250
+ const isLabelIdentifier = (node, parent) => (parent.type === utils_1.AST_NODE_TYPES.LabeledStatement && parent.label === node) ||
251
+ ((parent.type === utils_1.AST_NODE_TYPES.BreakStatement ||
252
+ parent.type === utils_1.AST_NODE_TYPES.ContinueStatement) &&
253
+ parent.label === node);
254
+ /**
255
+ * Distinguishes declaration sites (bindings) from usage sites. The rule reports
256
+ * reads of storage, not the act of declaring a variable/property/parameter, so
257
+ * declarations across destructuring, params, imports, class members, and
258
+ * assignment targets must be ignored.
259
+ */
260
+ const identifierRepresentsDeclaration = (node) => {
261
+ const parent = node.parent;
262
+ if (!parent)
263
+ return false;
264
+ if (identifierHasDeclarationParent(node)) {
265
+ return true;
266
+ }
267
+ if (isObjectLiteralKeyDeclaration(node, parent))
268
+ return true;
269
+ if (isClassMemberKeyDeclaration(node, parent))
270
+ return true;
271
+ if (isTypeScriptDeclarationIdentifier(node, parent))
272
+ return true;
273
+ return isLabelIdentifier(node, parent);
274
+ };
275
+ const isVarBinding = (node) => {
276
+ let child = node;
277
+ let parent = node.parent ?? null;
278
+ while (parent) {
279
+ if (parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
280
+ if (parent.init === child)
281
+ return false;
282
+ const declaration = parent.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclaration
283
+ ? parent.parent
284
+ : null;
285
+ return declaration?.kind === 'var';
286
+ }
287
+ child = parent;
288
+ parent = parent.parent ?? null;
289
+ }
290
+ return false;
291
+ };
292
+ const isMemberExpressionObject = (node) => {
293
+ const parent = node.parent;
294
+ return (!!parent &&
295
+ parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
296
+ parent.object === node);
297
+ };
298
+ const isStorageContainerSource = (init, aliases) => {
299
+ const source = unwrapExpression(init);
300
+ if (isGlobalLike(source, aliases))
301
+ return true;
302
+ if (source.type === utils_1.AST_NODE_TYPES.MemberExpression) {
303
+ if (resolveStorageObject(source, aliases))
304
+ return true;
305
+ return isGlobalLike(source.object, aliases);
306
+ }
307
+ return false;
308
+ };
309
+ /**
310
+ * Registers aliases introduced via object destructuring of storage containers
311
+ * (e.g., `const { localStorage: store } = window`). The callback fires at the
312
+ * destructuring site so violations point to the extraction point. Aliases are
313
+ * stored on the current lexical scope to avoid leaking across nested scopes.
314
+ */
315
+ const recordAliasFromPattern = (pattern, init, aliases, setAlias, reportStorageProperty) => {
316
+ if (!init)
317
+ return;
318
+ if (!isStorageContainerSource(init, aliases))
319
+ return;
320
+ for (const prop of pattern.properties) {
321
+ if (prop.type !== utils_1.AST_NODE_TYPES.Property)
322
+ continue;
323
+ if (prop.computed)
324
+ continue;
325
+ const keyName = getPropertyName(prop.key);
326
+ if (!keyName || !STORAGE_NAMES.has(keyName))
327
+ continue;
328
+ reportStorageProperty?.(prop.key, keyName, `property "${keyName}"`);
329
+ if (prop.value.type === utils_1.AST_NODE_TYPES.Identifier) {
330
+ setAlias(prop.value.name, keyName);
331
+ }
332
+ else if (prop.value.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
333
+ prop.value.left.type === utils_1.AST_NODE_TYPES.Identifier) {
334
+ setAlias(prop.value.left.name, keyName);
335
+ }
336
+ }
337
+ };
338
+ const shouldIgnoreFile = (filename, options) => {
339
+ const normalizedFilename = filename.replace(/\\/g, '/');
340
+ const base = path_1.default.basename(normalizedFilename);
341
+ if (CONTEXT_BASENAMES.has(base))
342
+ return true;
343
+ const allowInTests = options?.allowInTests ?? true;
344
+ const isTestFile = TEST_FILE_REGEX.test(normalizedFilename) ||
345
+ /(^|\/)__mocks__(\/|$)/.test(normalizedFilename);
346
+ if (allowInTests && isTestFile)
347
+ return true;
348
+ const allowPatterns = options?.allow ?? [];
349
+ const normalizedAllowPatterns = allowPatterns.map((pattern) => pattern.replace(/\\/g, '/'));
350
+ return normalizedAllowPatterns.some((pattern) => (0, minimatch_1.minimatch)(normalizedFilename, pattern, { windowsPathsNoEscape: true }));
351
+ };
352
+ const createUsageReporter = (context) => {
353
+ const reportedNodes = new WeakSet();
354
+ const reportedLocations = new Set();
355
+ return (node, storage, accessType) => {
356
+ const locationKey = node.range ? `${node.range[0]}:${node.range[1]}` : null;
357
+ if (locationKey && reportedLocations.has(locationKey))
358
+ return;
359
+ if (locationKey)
360
+ reportedLocations.add(locationKey);
361
+ if (reportedNodes.has(node))
362
+ return;
363
+ reportedNodes.add(node);
364
+ const hook = storage === 'localStorage' ? 'useLocalStorage' : 'useSessionStorage';
365
+ context.report({
366
+ node,
367
+ messageId: 'useStorageContext',
368
+ data: {
369
+ storage,
370
+ accessType,
371
+ hook,
372
+ },
373
+ });
374
+ };
375
+ };
376
+ const createIdentifierHandler = (aliases, storageAliases, reportUsage) => {
377
+ return (node) => {
378
+ const parent = node.parent;
379
+ if (identifierRepresentsDeclaration(node)) {
380
+ const hoistToFunctionScope = isVarBinding(node);
381
+ const existingAlias = getAliasFromStack(storageAliases, node.name);
382
+ const isAssignmentTarget = parent?.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
383
+ parent.left === node;
384
+ if (isAssignmentTarget &&
385
+ !existingAlias &&
386
+ (STORAGE_NAMES.has(node.name) ||
387
+ GLOBAL_NAMES.has(node.name))) {
388
+ /**
389
+ * Ignore assignments to global storage identifiers without an existing
390
+ * local binding. Treating them as declarations would shadow the global
391
+ * object and mask subsequent violations.
392
+ */
393
+ return;
394
+ }
395
+ if (existingAlias === 'localStorage' ||
396
+ existingAlias === 'sessionStorage') {
397
+ /**
398
+ * Preserve existing aliases introduced earlier in the traversal
399
+ * (e.g., destructuring) so later declaration-node visits do not
400
+ * override them as shadowed bindings.
401
+ */
402
+ return;
403
+ }
404
+ if (parent?.type === utils_1.AST_NODE_TYPES.ClassExpression &&
405
+ parent.id === node) {
406
+ /**
407
+ * Class expression names are only bound to the class body and should not
408
+ * be treated as shadowing outer scope storage references.
409
+ */
410
+ return;
411
+ }
412
+ if (identifierHasDeclarationParent(node) &&
413
+ !aliases.currentScope().has(node.name)) {
414
+ const shadowsStorage = checkIfNameShadowsStorage(node.name, storageAliases);
415
+ if (shadowsStorage) {
416
+ aliases.markShadowed(node.name, { hoistToFunctionScope });
417
+ }
418
+ }
419
+ return;
420
+ }
421
+ const storageKind = resolveStorageObject(node, storageAliases);
422
+ if (!storageKind)
423
+ return;
424
+ if (isMemberExpressionObject(node))
425
+ return;
426
+ if (node.parent &&
427
+ node.parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
428
+ node.parent.property === node)
429
+ return;
430
+ reportUsage(node, storageKind, 'reference');
431
+ };
432
+ };
433
+ const createMemberExpressionHandler = (storageAliases, reportUsage) => {
434
+ return (node) => {
435
+ const object = node.object;
436
+ const isAssignmentTarget = node.parent?.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
437
+ node.parent.left === node;
438
+ if (isAssignmentTarget) {
439
+ return;
440
+ }
441
+ const storageKind = resolveStorageObject(object, storageAliases);
442
+ if (storageKind) {
443
+ if (node.parent &&
444
+ node.parent.type === utils_1.AST_NODE_TYPES.CallExpression &&
445
+ node.parent.callee === node) {
446
+ return;
447
+ }
448
+ if (node.parent &&
449
+ node.parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
450
+ node.parent.object === node) {
451
+ return;
452
+ }
453
+ const propName = getPropertyName(node.property);
454
+ reportUsage(node, storageKind, propName ? `property "${propName}"` : 'property access');
455
+ return;
456
+ }
457
+ const propertyName = getPropertyName(node.property);
458
+ if (propertyName &&
459
+ STORAGE_NAMES.has(propertyName) &&
460
+ isGlobalLike(object, storageAliases)) {
461
+ if (node.parent &&
462
+ node.parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
463
+ node.parent.object === node) {
464
+ return;
465
+ }
466
+ reportUsage(node.property, propertyName, 'reference');
467
+ }
468
+ };
469
+ };
470
+ const createCallExpressionHandler = (storageAliases, reportUsage) => {
471
+ return (node) => {
472
+ const callee = unwrapExpression(node.callee);
473
+ if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression)
474
+ return;
475
+ const storageKind = resolveStorageObject(callee.object, storageAliases);
476
+ if (!storageKind)
477
+ return;
478
+ const propName = getPropertyName(callee.property);
479
+ reportUsage(callee.property, storageKind, propName ? `method "${propName}"` : 'storage method');
480
+ };
481
+ };
482
+ const createVariableDeclaratorHandler = (aliases, storageAliases, reportUsage) => {
483
+ return (node) => {
484
+ const declaration = node.parent && node.parent.type === utils_1.AST_NODE_TYPES.VariableDeclaration
485
+ ? node.parent
486
+ : null;
487
+ const hoistToFunctionScope = declaration?.kind === 'var';
488
+ if (node.id.type === utils_1.AST_NODE_TYPES.Identifier) {
489
+ const storageKind = node.init
490
+ ? resolveStorageObject(node.init, storageAliases)
491
+ : null;
492
+ if (storageKind) {
493
+ aliases.setAlias(node.id.name, storageKind, { hoistToFunctionScope });
494
+ }
495
+ else {
496
+ const shadowsStorage = checkIfNameShadowsStorage(node.id.name, storageAliases, {
497
+ includeCurrentScope: true,
498
+ });
499
+ if (shadowsStorage) {
500
+ aliases.markShadowed(node.id.name, { hoistToFunctionScope });
501
+ }
502
+ }
503
+ }
504
+ else if (node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
505
+ const initExpr = node.init ?? null;
506
+ recordAliasFromPattern(node.id, initExpr, storageAliases, (name, storageKind) => aliases.setAlias(name, storageKind, { hoistToFunctionScope }), (propertyNode, storageKind, accessType) => {
507
+ reportUsage(propertyNode, storageKind, accessType);
508
+ });
509
+ }
510
+ };
511
+ };
512
+ const createAssignmentExpressionHandler = (aliases, storageAliases, reportUsage) => {
513
+ return (node) => {
514
+ if (node.left.type === utils_1.AST_NODE_TYPES.Identifier) {
515
+ const right = node.right;
516
+ const storageKind = resolveStorageObject(right, storageAliases);
517
+ if (storageKind) {
518
+ aliases.setAliasInExistingBindingScope(node.left.name, storageKind);
519
+ }
520
+ else {
521
+ const shadowsStorage = checkIfNameShadowsStorage(node.left.name, storageAliases, {
522
+ includeCurrentScope: true,
523
+ });
524
+ if (shadowsStorage) {
525
+ aliases.setAliasInExistingBindingScope(node.left.name, 'shadowed');
526
+ }
527
+ }
528
+ }
529
+ else if (node.left.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
530
+ recordAliasFromPattern(node.left, node.right, storageAliases, (name, storageKind) => aliases.setAliasInExistingBindingScope(name, storageKind), (propertyNode, storageKind, accessType) => {
531
+ reportUsage(propertyNode, storageKind, accessType);
532
+ });
533
+ }
534
+ };
535
+ };
536
+ const createScopeListeners = (aliases, storageAliases) => ({
537
+ Program: aliases.reset,
538
+ BlockStatement: () => aliases.pushScope(),
539
+ 'BlockStatement:exit': aliases.popScope,
540
+ TSModuleBlock: () => aliases.pushScope(),
541
+ 'TSModuleBlock:exit': aliases.popScope,
542
+ ClassBody: (node) => {
543
+ const parent = node.parent;
544
+ if (parent?.type === utils_1.AST_NODE_TYPES.ClassDeclaration &&
545
+ parent.id &&
546
+ checkIfNameShadowsStorage(parent.id.name, storageAliases, {
547
+ includeCurrentScope: true,
548
+ })) {
549
+ aliases.setAlias(parent.id.name, 'shadowed');
550
+ }
551
+ aliases.pushScope();
552
+ if (parent?.type === utils_1.AST_NODE_TYPES.ClassExpression &&
553
+ parent.id &&
554
+ checkIfNameShadowsStorage(parent.id.name, storageAliases, {
555
+ includeCurrentScope: true,
556
+ })) {
557
+ aliases.setAlias(parent.id.name, 'shadowed');
558
+ }
559
+ },
560
+ 'ClassBody:exit': aliases.popScope,
561
+ StaticBlock: () => aliases.pushScope(),
562
+ 'StaticBlock:exit': aliases.popScope,
563
+ SwitchStatement: () => aliases.pushScope(),
564
+ 'SwitchStatement:exit': aliases.popScope,
565
+ ForStatement: () => aliases.pushScope(),
566
+ 'ForStatement:exit': aliases.popScope,
567
+ ForInStatement: () => aliases.pushScope(),
568
+ 'ForInStatement:exit': aliases.popScope,
569
+ ForOfStatement: () => aliases.pushScope(),
570
+ 'ForOfStatement:exit': aliases.popScope,
571
+ CatchClause: () => aliases.pushScope(),
572
+ 'CatchClause:exit': aliases.popScope,
573
+ FunctionDeclaration: (node) => {
574
+ if (node.id) {
575
+ const name = node.id.name;
576
+ if (checkIfNameShadowsStorage(name, storageAliases, {
577
+ includeCurrentScope: true,
578
+ })) {
579
+ aliases.setAlias(name, 'shadowed');
580
+ }
581
+ }
582
+ aliases.pushScope('function');
583
+ },
584
+ 'FunctionDeclaration:exit': aliases.popScope,
585
+ FunctionExpression: () => aliases.pushScope('function'),
586
+ 'FunctionExpression:exit': aliases.popScope,
587
+ ArrowFunctionExpression: () => aliases.pushScope('function'),
588
+ 'ArrowFunctionExpression:exit': aliases.popScope,
589
+ });
590
+ const createStorageVisitors = (aliases, reportUsage) => {
591
+ const storageAliases = aliases.stack;
592
+ return {
593
+ ...createScopeListeners(aliases, storageAliases),
594
+ Identifier: createIdentifierHandler(aliases, storageAliases, reportUsage),
595
+ MemberExpression: createMemberExpressionHandler(storageAliases, reportUsage),
596
+ CallExpression: createCallExpressionHandler(storageAliases, reportUsage),
597
+ VariableDeclarator: createVariableDeclaratorHandler(aliases, storageAliases, reportUsage),
598
+ AssignmentExpression: createAssignmentExpressionHandler(aliases, storageAliases, reportUsage),
599
+ };
600
+ };
601
+ exports.enforceStorageContext = (0, createRule_1.createRule)({
602
+ name: 'enforce-storage-context',
603
+ meta: {
604
+ type: 'problem',
605
+ docs: {
606
+ description: 'Require storage access to go through the LocalStorage and SessionStorage context providers instead of direct browser APIs',
607
+ recommended: 'error',
608
+ },
609
+ fixable: undefined,
610
+ schema: [
611
+ {
612
+ type: 'object',
613
+ properties: {
614
+ allowInTests: {
615
+ type: 'boolean',
616
+ default: true,
617
+ },
618
+ allow: {
619
+ type: 'array',
620
+ items: { type: 'string' },
621
+ default: [],
622
+ description: 'Glob patterns that permit direct storage access (normalized to forward slashes) so polyfills and approved shims can bypass the context providers.',
623
+ },
624
+ },
625
+ additionalProperties: false,
626
+ },
627
+ ],
628
+ messages: {
629
+ useStorageContext: "What's wrong: Direct {{storage}} {{accessType}} bypasses the LocalStorage/SessionStorage context providers.\n\nWhy it matters: (1) UI will not re-render when storage changes because React state is never updated, (2) code can crash during server-side rendering when `window` is undefined, and (3) storage errors go unhandled and break user flows.\n\nHow to fix: Call {{hook}}() at the component top level and use its returned methods (getItem, setItem, removeItem, clear) instead of accessing {{storage}} directly.",
630
+ },
631
+ },
632
+ defaultOptions: [{}],
633
+ create(context) {
634
+ const filename = context.filename ??
635
+ context.getFilename?.() ??
636
+ '';
637
+ const [options] = context.options;
638
+ if (shouldIgnoreFile(filename, options)) {
639
+ return {};
640
+ }
641
+ const aliases = createAliasState();
642
+ const reportUsage = createUsageReporter(context);
643
+ return createStorageVisitors(aliases, reportUsage);
644
+ },
645
+ });
646
+ //# sourceMappingURL=enforce-storage-context.js.map