@blumintinc/eslint-plugin-blumint 1.12.6 → 1.13.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 (141) hide show
  1. package/README.md +140 -99
  2. package/lib/index.js +111 -17
  3. package/lib/rules/array-methods-this-context.js +20 -2
  4. package/lib/rules/avoid-utils-directory.js +11 -2
  5. package/lib/rules/dynamic-https-errors.d.ts +3 -1
  6. package/lib/rules/dynamic-https-errors.js +48 -10
  7. package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
  8. package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +51 -95
  9. package/lib/rules/enforce-assert-throws.d.ts +3 -1
  10. package/lib/rules/enforce-assert-throws.js +493 -51
  11. package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
  12. package/lib/rules/enforce-boolean-naming-prefixes.js +832 -39
  13. package/lib/rules/enforce-callable-types.js +4 -4
  14. package/lib/rules/enforce-callback-memo.js +143 -18
  15. package/lib/rules/enforce-centralized-mock-firestore.js +4 -1
  16. package/lib/rules/enforce-console-error.d.ts +3 -0
  17. package/lib/rules/enforce-console-error.js +250 -0
  18. package/lib/rules/enforce-css-media-queries.js +24 -46
  19. package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
  20. package/lib/rules/enforce-dynamic-imports.js +1 -1
  21. package/lib/rules/enforce-exported-function-types.js +3 -3
  22. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
  23. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +212 -0
  24. package/lib/rules/enforce-firestore-doc-ref-generic.js +335 -0
  25. package/lib/rules/enforce-firestore-facade.js +347 -157
  26. package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
  27. package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
  28. package/lib/rules/enforce-global-constants.d.ts +4 -1
  29. package/lib/rules/enforce-global-constants.js +240 -12
  30. package/lib/rules/enforce-memoize-async.d.ts +2 -1
  31. package/lib/rules/enforce-memoize-async.js +96 -27
  32. package/lib/rules/enforce-memoize-getters.d.ts +3 -0
  33. package/lib/rules/enforce-memoize-getters.js +123 -0
  34. package/lib/rules/enforce-microdiff.js +2 -1
  35. package/lib/rules/enforce-positive-naming.js +5 -7
  36. package/lib/rules/enforce-props-argument-name.d.ts +2 -7
  37. package/lib/rules/enforce-props-argument-name.js +165 -151
  38. package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
  39. package/lib/rules/enforce-props-naming-consistency.js +145 -0
  40. package/lib/rules/enforce-querykey-ts.d.ts +9 -0
  41. package/lib/rules/enforce-querykey-ts.js +274 -0
  42. package/lib/rules/enforce-singular-type-names.js +9 -5
  43. package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
  44. package/lib/rules/enforce-typescript-markdown-code-blocks.js +96 -0
  45. package/lib/rules/enforce-verb-noun-naming.js +85 -10
  46. package/lib/rules/extract-global-constants.js +57 -45
  47. package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
  48. package/lib/rules/fast-deep-equal-over-microdiff.js +356 -78
  49. package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
  50. package/lib/rules/firestore-transaction-reads-before-writes.js +231 -0
  51. package/lib/rules/generic-starts-with-t.js +8 -2
  52. package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
  53. package/lib/rules/memoize-root-level-hocs.js +242 -0
  54. package/lib/rules/no-array-length-in-deps.d.ts +12 -0
  55. package/lib/rules/no-array-length-in-deps.js +308 -0
  56. package/lib/rules/no-async-array-filter.js +5 -2
  57. package/lib/rules/no-complex-cloud-params.js +15 -2
  58. package/lib/rules/no-compositing-layer-props.js +4 -2
  59. package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
  60. package/lib/rules/no-entire-object-hook-deps.js +317 -63
  61. package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
  62. package/lib/rules/no-excessive-parent-chain.js +273 -0
  63. package/lib/rules/no-firestore-object-arrays.js +345 -30
  64. package/lib/rules/no-hungarian.js +13 -13
  65. package/lib/rules/no-margin-properties.d.ts +7 -1
  66. package/lib/rules/no-margin-properties.js +16 -3
  67. package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
  68. package/lib/rules/no-misleading-boolean-prefixes.js +327 -0
  69. package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
  70. package/lib/rules/no-overridable-method-calls-in-constructor.js +260 -0
  71. package/lib/rules/no-passthrough-getters.d.ts +4 -0
  72. package/lib/rules/no-passthrough-getters.js +170 -0
  73. package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
  74. package/lib/rules/no-redundant-usecallback-wrapper.js +315 -0
  75. package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
  76. package/lib/rules/no-restricted-properties-fix.js +172 -0
  77. package/lib/rules/no-separate-loading-state.d.ts +8 -0
  78. package/lib/rules/no-separate-loading-state.js +142 -0
  79. package/lib/rules/no-stale-state-across-await.d.ts +1 -0
  80. package/lib/rules/no-stale-state-across-await.js +185 -0
  81. package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
  82. package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
  83. package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
  84. package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
  85. package/lib/rules/no-type-assertion-returns.js +6 -0
  86. package/lib/rules/no-undefined-null-passthrough.js +166 -19
  87. package/lib/rules/no-unnecessary-verb-suffix.js +0 -2
  88. package/lib/rules/no-unsafe-firestore-spread.js +8 -5
  89. package/lib/rules/no-unused-props.d.ts +3 -0
  90. package/lib/rules/no-unused-props.js +213 -78
  91. package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
  92. package/lib/rules/optimize-object-boolean-conditions.js +234 -0
  93. package/lib/rules/parallelize-async-operations.d.ts +8 -0
  94. package/lib/rules/parallelize-async-operations.js +375 -0
  95. package/lib/rules/prefer-batch-operations.js +199 -33
  96. package/lib/rules/prefer-block-comments-for-declarations.js +13 -3
  97. package/lib/rules/prefer-document-flattening.d.ts +4 -0
  98. package/lib/rules/prefer-document-flattening.js +239 -0
  99. package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
  100. package/lib/rules/prefer-field-paths-in-transforms.js +250 -0
  101. package/lib/rules/prefer-fragment-component.js +3 -3
  102. package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
  103. package/lib/rules/prefer-global-router-state-key.js +230 -31
  104. package/lib/rules/prefer-next-dynamic.d.ts +9 -0
  105. package/lib/rules/prefer-next-dynamic.js +366 -0
  106. package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
  107. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +430 -0
  108. package/lib/rules/prefer-nullish-coalescing-override.d.ts +6 -0
  109. package/lib/rules/prefer-nullish-coalescing-override.js +182 -0
  110. package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
  111. package/lib/rules/prefer-params-over-parent-id.js +228 -0
  112. package/lib/rules/prefer-settings-object.js +23 -15
  113. package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
  114. package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
  115. package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
  116. package/lib/rules/prefer-url-tostring-over-tojson.js +166 -0
  117. package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
  118. package/lib/rules/prefer-use-deep-compare-memo.js +428 -0
  119. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +24 -1
  120. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
  121. package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
  122. package/lib/rules/react-usememo-should-be-component.js +144 -78
  123. package/lib/rules/require-dynamic-firebase-imports.js +2 -1
  124. package/lib/rules/require-image-optimized.js +1 -1
  125. package/lib/rules/require-memo.js +11 -1
  126. package/lib/rules/require-usememo-object-literals.js +7 -1
  127. package/lib/rules/semantic-function-prefixes.js +38 -18
  128. package/lib/rules/sync-onwrite-name-func.js +5 -1
  129. package/lib/rules/test-file-location-enforcement.d.ts +1 -0
  130. package/lib/rules/test-file-location-enforcement.js +57 -0
  131. package/lib/rules/use-custom-link.js +6 -1
  132. package/lib/rules/use-custom-router.js +6 -1
  133. package/lib/rules/use-latest-callback.d.ts +3 -0
  134. package/lib/rules/use-latest-callback.js +270 -0
  135. package/lib/utils/ASTHelpers.js +10 -0
  136. package/lib/utils/createRule.js +1 -1
  137. package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
  138. package/lib/utils/ruleTester.d.ts +1 -0
  139. package/lib/utils/ruleTester.js +4 -1
  140. package/package.json +21 -14
  141. package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
@@ -0,0 +1,8 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ type Options = [
3
+ {
4
+ patterns?: string[];
5
+ }?
6
+ ];
7
+ export declare const noSeparateLoadingState: TSESLint.RuleModule<"separateLoadingState", Options, TSESLint.RuleListener>;
8
+ export {};
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noSeparateLoadingState = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const LOADING_PATTERNS = [
7
+ /^is.*Loading$/i,
8
+ /^isLoading.+/i, // isLoadingX pattern
9
+ ];
10
+ exports.noSeparateLoadingState = (0, createRule_1.createRule)({
11
+ name: 'no-separate-loading-state',
12
+ meta: {
13
+ type: 'suggestion',
14
+ docs: {
15
+ description: 'Disallow separate loading state variables that track the loading status of other state',
16
+ recommended: 'error',
17
+ },
18
+ fixable: undefined,
19
+ schema: [
20
+ {
21
+ type: 'object',
22
+ properties: {
23
+ patterns: {
24
+ type: 'array',
25
+ items: { type: 'string' },
26
+ },
27
+ },
28
+ additionalProperties: false,
29
+ },
30
+ ],
31
+ messages: {
32
+ separateLoadingState: 'Avoid separate loading state. Encode loading status directly in the primary state using a sentinel value like "loading".',
33
+ },
34
+ },
35
+ defaultOptions: [{}],
36
+ create(context, [options]) {
37
+ const effectivePatterns = options?.patterns?.map((p) => new RegExp(p, 'i')) ?? LOADING_PATTERNS;
38
+ const setterTrackers = [];
39
+ function isLoadingPattern(name) {
40
+ return effectivePatterns.some((pattern) => pattern.test(name));
41
+ }
42
+ function isUseStateCall(node) {
43
+ if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
44
+ node.callee.name === 'useState') {
45
+ return true;
46
+ }
47
+ if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
48
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
49
+ node.callee.property.name === 'useState') {
50
+ return true;
51
+ }
52
+ return false;
53
+ }
54
+ function isTruthyValue(node) {
55
+ if (node.type === utils_1.AST_NODE_TYPES.Literal) {
56
+ return Boolean(node.value);
57
+ }
58
+ return false;
59
+ }
60
+ function isFalsyValue(node) {
61
+ if (node.type === utils_1.AST_NODE_TYPES.Literal) {
62
+ return !node.value;
63
+ }
64
+ return false;
65
+ }
66
+ function getSetterName(declarator) {
67
+ if (declarator.id.type === utils_1.AST_NODE_TYPES.ArrayPattern &&
68
+ declarator.id.elements.length >= 2) {
69
+ const setterElement = declarator.id.elements[1];
70
+ if (setterElement?.type === utils_1.AST_NODE_TYPES.Identifier) {
71
+ return setterElement.name;
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+ return {
77
+ VariableDeclarator(node) {
78
+ // Check for useState destructuring patterns
79
+ if (node.id.type === utils_1.AST_NODE_TYPES.ArrayPattern &&
80
+ node.init?.type === utils_1.AST_NODE_TYPES.CallExpression &&
81
+ isUseStateCall(node.init)) {
82
+ const arrayPattern = node.id;
83
+ // Check if we have at least 2 elements (state and setter)
84
+ if (arrayPattern.elements.length >= 2) {
85
+ const stateElement = arrayPattern.elements[0];
86
+ const setterElement = arrayPattern.elements[1];
87
+ if (stateElement?.type === utils_1.AST_NODE_TYPES.Identifier &&
88
+ setterElement?.type === utils_1.AST_NODE_TYPES.Identifier &&
89
+ isLoadingPattern(stateElement.name)) {
90
+ const declared = context.getDeclaredVariables(node);
91
+ const setterVar = declared.find((v) => v.name === setterElement.name) ?? null;
92
+ if (!setterVar)
93
+ return;
94
+ setterTrackers.push({
95
+ declarator: node,
96
+ setterVar,
97
+ usage: { truthy: false, falsy: false },
98
+ });
99
+ }
100
+ }
101
+ }
102
+ },
103
+ CallExpression(_node) {
104
+ // Setter usage is resolved via scope references in Program:exit
105
+ return;
106
+ },
107
+ 'Program:exit'() {
108
+ // Analyze collected data to determine violations
109
+ for (const tracker of setterTrackers) {
110
+ // Walk references of the exact setter variable to classify usages
111
+ for (const ref of tracker.setterVar.references) {
112
+ const parent = ref.identifier.parent;
113
+ if (parent &&
114
+ parent.type === utils_1.AST_NODE_TYPES.CallExpression &&
115
+ parent.callee === ref.identifier &&
116
+ parent.arguments.length > 0) {
117
+ const argument = parent.arguments[0];
118
+ if (isTruthyValue(argument)) {
119
+ tracker.usage.truthy = true;
120
+ }
121
+ else if (isFalsyValue(argument)) {
122
+ tracker.usage.falsy = true;
123
+ }
124
+ }
125
+ }
126
+ const { declarator, usage } = tracker;
127
+ const setterName = getSetterName(declarator);
128
+ if (!setterName)
129
+ continue;
130
+ // If we have both truthy and falsy setter calls, it's likely a loading state pattern
131
+ if (usage.truthy && usage.falsy) {
132
+ context.report({
133
+ node: declarator,
134
+ messageId: 'separateLoadingState',
135
+ });
136
+ }
137
+ }
138
+ },
139
+ };
140
+ },
141
+ });
142
+ //# sourceMappingURL=no-separate-loading-state.js.map
@@ -0,0 +1 @@
1
+ export declare const noStaleStateAcrossAwait: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"staleStateAcrossAwait", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noStaleStateAcrossAwait = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.noStaleStateAcrossAwait = (0, createRule_1.createRule)({
7
+ name: 'no-stale-state-across-await',
8
+ meta: {
9
+ type: 'problem',
10
+ docs: {
11
+ description: 'Prevent stale intermediate state by disallowing useState setter calls both before and after async boundaries (await, .then(), yield) within the same function',
12
+ recommended: 'error',
13
+ },
14
+ fixable: undefined,
15
+ schema: [],
16
+ messages: {
17
+ staleStateAcrossAwait: 'State setter "{{setterName}}" runs on both sides of {{boundaryType}}. Updates issued before the async boundary can resolve after later updates and overwrite fresher data, leaving the UI in a stale loading or placeholder state. Keep "{{setterName}}" updates on one side of the async boundary or consolidate into a single atomic update after the async work; if you intentionally use a sentinel value, document it with // eslint-disable-next-line @blumintinc/blumint/no-stale-state-across-await and a short comment explaining why.',
18
+ },
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ return {
23
+ // Check functions for violations
24
+ 'FunctionDeclaration, FunctionExpression, ArrowFunctionExpression'(node) {
25
+ const sourceCode = context.getSourceCode();
26
+ const scopeManager = sourceCode.scopeManager;
27
+ if (!scopeManager)
28
+ return;
29
+ const acquiredScope = scopeManager.acquire(node);
30
+ if (!acquiredScope)
31
+ return;
32
+ const scope = acquiredScope;
33
+ // Build local set of setter identifiers declared via useState in this scope
34
+ const localSetterNames = new Set();
35
+ let scopeCursor = scope;
36
+ while (scopeCursor) {
37
+ for (const variable of scopeCursor.variables) {
38
+ for (const def of variable.defs) {
39
+ if (def.type === 'Variable' &&
40
+ def.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
41
+ def.node.id.type === utils_1.AST_NODE_TYPES.ArrayPattern &&
42
+ def.node.init?.type === utils_1.AST_NODE_TYPES.CallExpression &&
43
+ def.node.init.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
44
+ def.node.init.callee.name === 'useState') {
45
+ const arrayPattern = def.node.id;
46
+ if (arrayPattern.elements.length >= 2 &&
47
+ arrayPattern.elements[1]?.type === utils_1.AST_NODE_TYPES.Identifier) {
48
+ localSetterNames.add(arrayPattern.elements[1].name);
49
+ }
50
+ }
51
+ }
52
+ }
53
+ scopeCursor = scopeCursor.upper;
54
+ }
55
+ function isLocalSetter(identifier) {
56
+ return localSetterNames.has(identifier.name);
57
+ }
58
+ // Collect all setter calls and async boundaries in this function
59
+ const setterCalls = [];
60
+ const asyncBoundaries = [];
61
+ // Walk through the function body to find setter calls and async boundaries
62
+ function walkNode(n, skipNestedFunctions = true) {
63
+ if (n.type === utils_1.AST_NODE_TYPES.CallExpression) {
64
+ // Check for setter calls
65
+ if (n.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
66
+ isLocalSetter(n.callee)) {
67
+ setterCalls.push({
68
+ name: n.callee.name,
69
+ position: n.range[0],
70
+ });
71
+ }
72
+ // Check for .then()/.catch()/.finally() calls
73
+ if (n.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
74
+ n.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
75
+ (n.callee.property.name === 'then' ||
76
+ n.callee.property.name === 'catch' ||
77
+ n.callee.property.name === 'finally')) {
78
+ const methodName = n.callee.property.name;
79
+ if (methodName === 'then' || methodName === 'catch') {
80
+ asyncBoundaries.push({
81
+ position: n.range[0],
82
+ label: `a .${methodName}() callback`,
83
+ });
84
+ }
85
+ // Walk into callback arguments to find setter calls
86
+ if (n.arguments && n.arguments.length > 0) {
87
+ for (const arg of n.arguments) {
88
+ if (arg.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
89
+ arg.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
90
+ // Walk into the callback function body
91
+ walkNode(arg, false);
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
97
+ if (n.type === utils_1.AST_NODE_TYPES.AwaitExpression) {
98
+ asyncBoundaries.push({
99
+ position: n.range[0],
100
+ label: 'an await boundary',
101
+ });
102
+ }
103
+ if (n.type === utils_1.AST_NODE_TYPES.YieldExpression) {
104
+ asyncBoundaries.push({
105
+ position: n.range[0],
106
+ label: 'a yield boundary',
107
+ });
108
+ }
109
+ if (n.type === utils_1.AST_NODE_TYPES.ForOfStatement &&
110
+ n.await === true) {
111
+ asyncBoundaries.push({
112
+ position: n.range[0],
113
+ label: 'a yield boundary',
114
+ });
115
+ }
116
+ // Recursively walk child nodes
117
+ for (const key in n) {
118
+ if (key === 'parent')
119
+ continue;
120
+ const value = n[key];
121
+ if (Array.isArray(value)) {
122
+ for (const item of value) {
123
+ if (item && typeof item === 'object' && item.type) {
124
+ // Skip nested functions unless we're explicitly walking into callbacks
125
+ if (skipNestedFunctions &&
126
+ (item.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
127
+ item.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
128
+ item.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
129
+ continue;
130
+ }
131
+ walkNode(item, skipNestedFunctions);
132
+ }
133
+ }
134
+ }
135
+ else if (value && typeof value === 'object' && value.type) {
136
+ // Skip nested functions unless we're explicitly walking into callbacks
137
+ if (skipNestedFunctions &&
138
+ (value.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
139
+ value.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
140
+ value.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
141
+ return;
142
+ }
143
+ walkNode(value, skipNestedFunctions);
144
+ }
145
+ }
146
+ }
147
+ // Walk the function body
148
+ if (node.body) {
149
+ walkNode(node.body);
150
+ }
151
+ // Check for violations
152
+ const setterCallsByName = new Map();
153
+ for (const call of setterCalls) {
154
+ if (!setterCallsByName.has(call.name)) {
155
+ setterCallsByName.set(call.name, []);
156
+ }
157
+ setterCallsByName.get(call.name).push(call);
158
+ }
159
+ // Check each setter for violations
160
+ for (const [setterName, calls] of setterCallsByName) {
161
+ if (calls.length < 2)
162
+ continue; // Need at least 2 calls to have a violation
163
+ // Check if there are calls both before and after any async boundary
164
+ for (const boundary of asyncBoundaries) {
165
+ const callsBefore = calls.filter((call) => call.position < boundary.position);
166
+ const callsAfter = calls.filter((call) => call.position > boundary.position);
167
+ if (callsBefore.length > 0 && callsAfter.length > 0) {
168
+ // Report violation on the function node
169
+ context.report({
170
+ node,
171
+ messageId: 'staleStateAcrossAwait',
172
+ data: {
173
+ setterName,
174
+ boundaryType: boundary.label,
175
+ },
176
+ });
177
+ break; // Only report once per setter
178
+ }
179
+ }
180
+ }
181
+ },
182
+ };
183
+ },
184
+ });
185
+ //# sourceMappingURL=no-stale-state-across-await.js.map
@@ -0,0 +1,3 @@
1
+ export declare const RULE_NAME = "no-static-constants-in-dynamic-files";
2
+ declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noStaticConstantInDynamicFile", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
3
+ export default _default;
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULE_NAME = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.RULE_NAME = 'no-static-constants-in-dynamic-files';
7
+ const isDynamicFilename = (filename) => /\.dynamic\.tsx?$/.test(filename);
8
+ const isScreamingSnakeCase = (name) => /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$/.test(name);
9
+ /**
10
+ * Records a leaf binding identifier so SCREAMING_SNAKE_CASE exports are checked.
11
+ */
12
+ const collectFromIdentifier = (identifier, identifiers) => {
13
+ identifiers.push(identifier);
14
+ };
15
+ /**
16
+ * Queues the rest element argument when it is a supported binding pattern so
17
+ * nested destructuring inside `...` is still inspected for exported constants.
18
+ */
19
+ const collectFromRestElement = (restElement, queue) => {
20
+ const argument = restElement.argument;
21
+ if (argument.type === utils_1.AST_NODE_TYPES.Identifier ||
22
+ argument.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
23
+ argument.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
24
+ queue.push(argument);
25
+ }
26
+ };
27
+ /**
28
+ * Walks object destructuring to capture identifiers and queue nested patterns
29
+ * (including defaults and rest spreads) so every bound name is inspected.
30
+ */
31
+ const collectFromObjectPattern = (pattern, identifiers, queue) => {
32
+ for (const property of pattern.properties) {
33
+ if (property.type === utils_1.AST_NODE_TYPES.Property) {
34
+ const value = property.value;
35
+ if (value.type === utils_1.AST_NODE_TYPES.Identifier) {
36
+ identifiers.push(value);
37
+ }
38
+ else if (value.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
39
+ queue.push(value.left);
40
+ }
41
+ else if (value.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
42
+ value.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
43
+ queue.push(value);
44
+ }
45
+ }
46
+ else if (property.type === utils_1.AST_NODE_TYPES.RestElement) {
47
+ collectFromRestElement(property, queue);
48
+ }
49
+ }
50
+ };
51
+ /**
52
+ * Walks array destructuring to collect identifiers and queue nested patterns
53
+ * so array defaults, nesting, and rest elements are not skipped.
54
+ */
55
+ const collectFromArrayPattern = (pattern, identifiers, queue) => {
56
+ for (const element of pattern.elements) {
57
+ if (!element) {
58
+ continue;
59
+ }
60
+ if (element.type === utils_1.AST_NODE_TYPES.Identifier) {
61
+ identifiers.push(element);
62
+ }
63
+ else if (element.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
64
+ queue.push(element.left);
65
+ }
66
+ else if (element.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
67
+ element.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
68
+ queue.push(element);
69
+ }
70
+ else if (element.type === utils_1.AST_NODE_TYPES.RestElement) {
71
+ collectFromRestElement(element, queue);
72
+ }
73
+ }
74
+ };
75
+ /**
76
+ * Traverses a binding pattern depth-first and returns every leaf Identifier
77
+ * so exported destructuring binds are all checked for SCREAMING_SNAKE_CASE.
78
+ */
79
+ const collectBindingIdentifiers = (pattern) => {
80
+ const identifiers = [];
81
+ const queue = [pattern];
82
+ while (queue.length > 0) {
83
+ const current = queue.pop();
84
+ if (!current) {
85
+ continue;
86
+ }
87
+ switch (current.type) {
88
+ case utils_1.AST_NODE_TYPES.Identifier:
89
+ collectFromIdentifier(current, identifiers);
90
+ break;
91
+ case utils_1.AST_NODE_TYPES.ObjectPattern:
92
+ collectFromObjectPattern(current, identifiers, queue);
93
+ break;
94
+ case utils_1.AST_NODE_TYPES.ArrayPattern:
95
+ collectFromArrayPattern(current, identifiers, queue);
96
+ break;
97
+ default:
98
+ break;
99
+ }
100
+ }
101
+ return identifiers;
102
+ };
103
+ exports.default = (0, createRule_1.createRule)({
104
+ name: exports.RULE_NAME,
105
+ meta: {
106
+ type: 'problem',
107
+ docs: {
108
+ description: 'Disallow exporting SCREAMING_SNAKE_CASE constants from .dynamic.ts/.dynamic.tsx files; move static constants to non-dynamic modules instead.',
109
+ recommended: 'error',
110
+ },
111
+ fixable: undefined,
112
+ schema: [],
113
+ messages: {
114
+ noStaticConstantInDynamicFile: 'Global constant "{{name}}" is exported from a .dynamic file. .dynamic files are reserved for runtime-only behavior; move this constant to a non-dynamic module (e.g., config.ts) and import it so static values do not live alongside dynamic code paths.',
115
+ },
116
+ },
117
+ defaultOptions: [],
118
+ create(context) {
119
+ const filename = context.filename ?? context.getFilename();
120
+ if (!isDynamicFilename(filename)) {
121
+ return {};
122
+ }
123
+ return {
124
+ ExportNamedDeclaration(node) {
125
+ const declaration = node.declaration;
126
+ if (!declaration ||
127
+ declaration.type !== utils_1.AST_NODE_TYPES.VariableDeclaration ||
128
+ declaration.kind !== 'const') {
129
+ return;
130
+ }
131
+ declaration.declarations.forEach((declarator) => {
132
+ const { id } = declarator;
133
+ const identifiers = collectBindingIdentifiers(id);
134
+ for (const identifier of identifiers) {
135
+ if (!isScreamingSnakeCase(identifier.name)) {
136
+ continue;
137
+ }
138
+ context.report({
139
+ node: identifier,
140
+ messageId: 'noStaticConstantInDynamicFile',
141
+ data: { name: identifier.name },
142
+ });
143
+ }
144
+ });
145
+ },
146
+ };
147
+ },
148
+ });
149
+ //# sourceMappingURL=no-static-constants-in-dynamic-files.js.map
@@ -0,0 +1 @@
1
+ export declare const noTryCatchAlreadyExistsInTransaction: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noAlreadyExistsCatchInTransaction", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;