@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.
- package/README.md +140 -99
- package/lib/index.js +111 -17
- package/lib/rules/array-methods-this-context.js +20 -2
- package/lib/rules/avoid-utils-directory.js +11 -2
- package/lib/rules/dynamic-https-errors.d.ts +3 -1
- package/lib/rules/dynamic-https-errors.js +48 -10
- package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
- package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +51 -95
- package/lib/rules/enforce-assert-throws.d.ts +3 -1
- package/lib/rules/enforce-assert-throws.js +493 -51
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
- package/lib/rules/enforce-boolean-naming-prefixes.js +832 -39
- package/lib/rules/enforce-callable-types.js +4 -4
- package/lib/rules/enforce-callback-memo.js +143 -18
- package/lib/rules/enforce-centralized-mock-firestore.js +4 -1
- package/lib/rules/enforce-console-error.d.ts +3 -0
- package/lib/rules/enforce-console-error.js +250 -0
- package/lib/rules/enforce-css-media-queries.js +24 -46
- package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- package/lib/rules/enforce-exported-function-types.js +3 -3
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +212 -0
- package/lib/rules/enforce-firestore-doc-ref-generic.js +335 -0
- package/lib/rules/enforce-firestore-facade.js +347 -157
- package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
- package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
- package/lib/rules/enforce-global-constants.d.ts +4 -1
- package/lib/rules/enforce-global-constants.js +240 -12
- package/lib/rules/enforce-memoize-async.d.ts +2 -1
- package/lib/rules/enforce-memoize-async.js +96 -27
- package/lib/rules/enforce-memoize-getters.d.ts +3 -0
- package/lib/rules/enforce-memoize-getters.js +123 -0
- package/lib/rules/enforce-microdiff.js +2 -1
- package/lib/rules/enforce-positive-naming.js +5 -7
- package/lib/rules/enforce-props-argument-name.d.ts +2 -7
- package/lib/rules/enforce-props-argument-name.js +165 -151
- package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
- package/lib/rules/enforce-props-naming-consistency.js +145 -0
- package/lib/rules/enforce-querykey-ts.d.ts +9 -0
- package/lib/rules/enforce-querykey-ts.js +274 -0
- package/lib/rules/enforce-singular-type-names.js +9 -5
- package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +96 -0
- package/lib/rules/enforce-verb-noun-naming.js +85 -10
- package/lib/rules/extract-global-constants.js +57 -45
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
- package/lib/rules/fast-deep-equal-over-microdiff.js +356 -78
- package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
- package/lib/rules/firestore-transaction-reads-before-writes.js +231 -0
- package/lib/rules/generic-starts-with-t.js +8 -2
- package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
- package/lib/rules/memoize-root-level-hocs.js +242 -0
- package/lib/rules/no-array-length-in-deps.d.ts +12 -0
- package/lib/rules/no-array-length-in-deps.js +308 -0
- package/lib/rules/no-async-array-filter.js +5 -2
- package/lib/rules/no-complex-cloud-params.js +15 -2
- package/lib/rules/no-compositing-layer-props.js +4 -2
- package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
- package/lib/rules/no-entire-object-hook-deps.js +317 -63
- package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
- package/lib/rules/no-excessive-parent-chain.js +273 -0
- package/lib/rules/no-firestore-object-arrays.js +345 -30
- package/lib/rules/no-hungarian.js +13 -13
- package/lib/rules/no-margin-properties.d.ts +7 -1
- package/lib/rules/no-margin-properties.js +16 -3
- package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
- package/lib/rules/no-misleading-boolean-prefixes.js +327 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.js +260 -0
- package/lib/rules/no-passthrough-getters.d.ts +4 -0
- package/lib/rules/no-passthrough-getters.js +170 -0
- package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +315 -0
- package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
- package/lib/rules/no-restricted-properties-fix.js +172 -0
- package/lib/rules/no-separate-loading-state.d.ts +8 -0
- package/lib/rules/no-separate-loading-state.js +142 -0
- package/lib/rules/no-stale-state-across-await.d.ts +1 -0
- package/lib/rules/no-stale-state-across-await.js +185 -0
- package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
- package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
- package/lib/rules/no-type-assertion-returns.js +6 -0
- package/lib/rules/no-undefined-null-passthrough.js +166 -19
- package/lib/rules/no-unnecessary-verb-suffix.js +0 -2
- package/lib/rules/no-unsafe-firestore-spread.js +8 -5
- package/lib/rules/no-unused-props.d.ts +3 -0
- package/lib/rules/no-unused-props.js +213 -78
- package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
- package/lib/rules/optimize-object-boolean-conditions.js +234 -0
- package/lib/rules/parallelize-async-operations.d.ts +8 -0
- package/lib/rules/parallelize-async-operations.js +375 -0
- package/lib/rules/prefer-batch-operations.js +199 -33
- package/lib/rules/prefer-block-comments-for-declarations.js +13 -3
- package/lib/rules/prefer-document-flattening.d.ts +4 -0
- package/lib/rules/prefer-document-flattening.js +239 -0
- package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
- package/lib/rules/prefer-field-paths-in-transforms.js +250 -0
- package/lib/rules/prefer-fragment-component.js +3 -3
- package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
- package/lib/rules/prefer-global-router-state-key.js +230 -31
- package/lib/rules/prefer-next-dynamic.d.ts +9 -0
- package/lib/rules/prefer-next-dynamic.js +366 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +430 -0
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +6 -0
- package/lib/rules/prefer-nullish-coalescing-override.js +182 -0
- package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
- package/lib/rules/prefer-params-over-parent-id.js +228 -0
- package/lib/rules/prefer-settings-object.js +23 -15
- package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
- package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
- package/lib/rules/prefer-url-tostring-over-tojson.js +166 -0
- package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
- package/lib/rules/prefer-use-deep-compare-memo.js +428 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +24 -1
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
- package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
- package/lib/rules/react-usememo-should-be-component.js +144 -78
- package/lib/rules/require-dynamic-firebase-imports.js +2 -1
- package/lib/rules/require-image-optimized.js +1 -1
- package/lib/rules/require-memo.js +11 -1
- package/lib/rules/require-usememo-object-literals.js +7 -1
- package/lib/rules/semantic-function-prefixes.js +38 -18
- package/lib/rules/sync-onwrite-name-func.js +5 -1
- package/lib/rules/test-file-location-enforcement.d.ts +1 -0
- package/lib/rules/test-file-location-enforcement.js +57 -0
- package/lib/rules/use-custom-link.js +6 -1
- package/lib/rules/use-custom-router.js +6 -1
- package/lib/rules/use-latest-callback.d.ts +3 -0
- package/lib/rules/use-latest-callback.js +270 -0
- package/lib/utils/ASTHelpers.js +10 -0
- package/lib/utils/createRule.js +1 -1
- package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
- package/lib/utils/ruleTester.d.ts +1 -0
- package/lib/utils/ruleTester.js +4 -1
- package/package.json +21 -14
- package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
|
@@ -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>;
|