@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.
- package/README.md +170 -139
- package/lib/index.js +190 -1
- package/lib/rules/array-methods-this-context.js +1 -1
- package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
- package/lib/rules/consistent-callback-naming.js +1 -1
- package/lib/rules/dynamic-https-errors.js +3 -4
- package/lib/rules/enforce-assert-safe-object-key.js +11 -11
- package/lib/rules/enforce-boolean-naming-prefixes.js +11 -23
- package/lib/rules/enforce-callback-memo.js +1 -1
- package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
- package/lib/rules/enforce-console-error.js +35 -7
- package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
- package/lib/rules/enforce-dynamic-file-naming.js +53 -28
- package/lib/rules/enforce-early-destructuring.d.ts +2 -0
- package/lib/rules/enforce-early-destructuring.js +980 -0
- package/lib/rules/enforce-empty-object-check.d.ts +11 -0
- package/lib/rules/enforce-empty-object-check.js +502 -0
- package/lib/rules/enforce-exported-function-types.js +1 -1
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
- package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
- package/lib/rules/enforce-firestore-facade.js +4 -2
- package/lib/rules/enforce-firestore-set-merge.js +3 -4
- package/lib/rules/enforce-global-constants.js +1 -1
- package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
- package/lib/rules/enforce-memoize-async.js +2 -6
- package/lib/rules/enforce-memoize-getters.js +39 -15
- package/lib/rules/enforce-microdiff.js +1 -1
- package/lib/rules/enforce-object-literal-as-const.js +4 -4
- package/lib/rules/enforce-positive-naming.js +4 -0
- package/lib/rules/enforce-props-argument-name.js +2 -2
- package/lib/rules/enforce-safe-stringify.js +1 -1
- package/lib/rules/enforce-serializable-params.js +3 -3
- package/lib/rules/enforce-singular-type-names.js +1 -1
- package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
- package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
- package/lib/rules/enforce-storage-context.d.ts +9 -0
- package/lib/rules/enforce-storage-context.js +646 -0
- package/lib/rules/enforce-timestamp-now.js +23 -7
- package/lib/rules/enforce-transform-memoization.d.ts +4 -0
- package/lib/rules/enforce-transform-memoization.js +416 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
- package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
- package/lib/rules/enforce-unique-cursor-headers.js +365 -0
- package/lib/rules/enforce-verb-noun-naming.js +5 -4
- package/lib/rules/ensure-pointer-events-none.js +28 -2
- package/lib/rules/export-if-in-doubt.js +27 -4
- package/lib/rules/extract-global-constants.js +40 -13
- package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
- package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
- package/lib/rules/flatten-push-calls.d.ts +2 -0
- package/lib/rules/flatten-push-calls.js +428 -0
- package/lib/rules/global-const-style.js +50 -19
- package/lib/rules/jsdoc-above-field.d.ts +11 -0
- package/lib/rules/jsdoc-above-field.js +208 -0
- package/lib/rules/key-only-outermost-element.js +21 -3
- package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
- package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
- package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
- package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
- package/lib/rules/memo-nested-react-components.d.ts +8 -0
- package/lib/rules/memo-nested-react-components.js +410 -0
- package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
- package/lib/rules/no-always-true-false-conditions.js +68 -8
- package/lib/rules/no-array-length-in-deps.js +9 -3
- package/lib/rules/no-async-foreach.js +210 -12
- package/lib/rules/no-circular-references.js +18 -17
- package/lib/rules/no-class-instance-destructuring.js +55 -12
- package/lib/rules/no-complex-cloud-params.js +7 -3
- package/lib/rules/no-compositing-layer-props.js +2 -2
- package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
- package/lib/rules/no-console-error.d.ts +9 -0
- package/lib/rules/no-console-error.js +525 -0
- package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
- package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
- package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
- package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
- package/lib/rules/no-entire-object-hook-deps.js +4 -6
- package/lib/rules/no-excessive-parent-chain.js +1 -1
- package/lib/rules/no-explicit-return-type.d.ts +2 -1
- package/lib/rules/no-explicit-return-type.js +215 -26
- package/lib/rules/no-filter-without-return.js +5 -1
- package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
- package/lib/rules/no-firestore-jest-mock.js +126 -8
- package/lib/rules/no-firestore-object-arrays.js +67 -12
- package/lib/rules/no-handler-suffix.d.ts +12 -0
- package/lib/rules/no-handler-suffix.js +305 -0
- package/lib/rules/no-hungarian.js +1 -1
- package/lib/rules/no-inline-component-prop.d.ts +10 -0
- package/lib/rules/no-inline-component-prop.js +456 -0
- package/lib/rules/no-jsx-in-hooks.js +6 -1
- package/lib/rules/no-jsx-whitespace-literal.js +8 -2
- package/lib/rules/no-margin-properties.js +6 -6
- package/lib/rules/no-memoize-on-static.js +9 -1
- package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
- package/lib/rules/no-misused-switch-case.js +22 -1
- package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
- package/lib/rules/no-mixed-firestore-transactions.js +296 -34
- package/lib/rules/no-mock-firebase-admin.js +5 -2
- package/lib/rules/no-object-values-on-strings.js +10 -2
- package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
- package/lib/rules/no-passthrough-getters.js +38 -2
- package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
- package/lib/rules/no-redundant-annotation-assertion.js +402 -0
- package/lib/rules/no-redundant-param-types.js +16 -6
- package/lib/rules/no-redundant-this-params.d.ts +3 -0
- package/lib/rules/no-redundant-this-params.js +459 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
- package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
- package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
- package/lib/rules/no-restricted-properties-fix.js +11 -10
- package/lib/rules/no-separate-loading-state.js +7 -16
- package/lib/rules/no-stale-state-across-await.js +1 -1
- package/lib/rules/no-type-assertion-returns.js +43 -49
- package/lib/rules/no-undefined-null-passthrough.js +61 -31
- package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
- package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
- package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
- package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
- package/lib/rules/no-unnecessary-destructuring.js +14 -4
- package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
- package/lib/rules/no-unpinned-dependencies.js +36 -5
- package/lib/rules/no-unused-props.d.ts +2 -2
- package/lib/rules/no-unused-props.js +295 -91
- package/lib/rules/no-unused-usestate.js +6 -2
- package/lib/rules/no-useless-fragment.js +28 -2
- package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
- package/lib/rules/no-useless-usememo-primitives.js +393 -0
- package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
- package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
- package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
- package/lib/rules/omit-index-html.d.ts +2 -1
- package/lib/rules/omit-index-html.js +62 -7
- package/lib/rules/optimize-object-boolean-conditions.js +6 -4
- package/lib/rules/parallelize-async-operations.js +24 -5
- package/lib/rules/prefer-batch-operations.d.ts +1 -3
- package/lib/rules/prefer-batch-operations.js +32 -5
- package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
- package/lib/rules/prefer-clone-deep.js +3 -3
- package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
- package/lib/rules/prefer-destructuring-no-class.js +244 -59
- package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
- package/lib/rules/prefer-docsetter-setall.js +243 -0
- package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
- package/lib/rules/prefer-fragment-component.js +1 -1
- package/lib/rules/prefer-fragment-shorthand.js +2 -1
- package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
- package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
- package/lib/rules/prefer-global-router-state-key.js +88 -13
- package/lib/rules/prefer-memoized-props.d.ts +3 -0
- package/lib/rules/prefer-memoized-props.js +445 -0
- package/lib/rules/prefer-next-dynamic.js +60 -69
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +9 -4
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
- package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
- package/lib/rules/prefer-params-over-parent-id.js +220 -171
- package/lib/rules/prefer-settings-object.js +2 -2
- package/lib/rules/prefer-type-over-interface.js +7 -2
- package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
- package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
- package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
- package/lib/rules/prevent-children-clobber.d.ts +2 -0
- package/lib/rules/prevent-children-clobber.js +536 -0
- package/lib/rules/react-memoize-literals.d.ts +4 -0
- package/lib/rules/react-memoize-literals.js +495 -0
- package/lib/rules/react-usememo-should-be-component.js +2 -2
- package/lib/rules/require-hooks-default-params.js +17 -16
- package/lib/rules/require-https-error-cause.d.ts +4 -0
- package/lib/rules/require-https-error-cause.js +136 -0
- package/lib/rules/require-https-error.js +43 -21
- package/lib/rules/require-image-optimized.js +1 -1
- package/lib/rules/require-memo.js +1 -1
- package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
- package/lib/rules/require-memoize-jsx-returners.js +485 -0
- package/lib/rules/require-usememo-object-literals.js +2 -3
- package/lib/rules/test-file-location-enforcement.js +1 -1
- package/lib/rules/use-latest-callback.js +6 -5
- package/lib/rules/vertically-group-related-functions.d.ts +16 -0
- package/lib/rules/vertically-group-related-functions.js +480 -0
- package/lib/utils/ASTHelpers.d.ts +13 -1
- package/lib/utils/ASTHelpers.js +24 -0
- package/lib/utils/getMethodName.d.ts +27 -0
- package/lib/utils/getMethodName.js +35 -0
- package/lib/utils/tsTypeClassifier.d.ts +30 -0
- package/lib/utils/tsTypeClassifier.js +149 -0
- 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
|