@blumintinc/eslint-plugin-blumint 1.20.8 → 1.20.10
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/lib/index.js +1 -1
- package/lib/rules/enforce-early-destructuring.js +14 -0
- package/lib/rules/enforce-m3-sentence-case.js +211 -45
- package/lib/rules/enforce-props-naming-consistency.js +127 -8
- package/lib/rules/enforce-querykey-ts.js +170 -16
- package/lib/rules/enforce-react-type-naming.d.ts +2 -1
- package/lib/rules/enforce-react-type-naming.js +46 -17
- package/lib/rules/enforce-snapshot-state-narrowing.d.ts +3 -1
- package/lib/rules/enforce-snapshot-state-narrowing.js +213 -53
- package/lib/rules/no-class-instance-destructuring.js +7 -0
- package/lib/rules/no-excessive-parent-chain.js +43 -10
- package/lib/rules/no-unnecessary-destructuring.js +8 -1
- package/lib/rules/prefer-clone-deep.d.ts +2 -1
- package/lib/rules/prefer-clone-deep.js +250 -192
- package/lib/rules/prefer-destructuring-no-class.js +8 -0
- package/lib/rules/prefer-spread-over-reassembly.d.ts +2 -1
- package/lib/rules/prefer-spread-over-reassembly.js +30 -2
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.d.ts +2 -1
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +192 -5
- package/lib/rules/use-custom-memo.d.ts +2 -1
- package/lib/rules/use-custom-memo.js +51 -36
- package/lib/rules/use-latest-callback.js +142 -92
- package/lib/utils/renameFixes.d.ts +69 -0
- package/lib/utils/renameFixes.js +202 -0
- package/package.json +1 -1
- package/release-manifest.json +132 -0
|
@@ -3,6 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.enforceQueryKeyTs = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
/**
|
|
7
|
+
* The canonical queryKeys module. The rule takes no options and already hard-codes
|
|
8
|
+
* this path as what makes a key valid, so the fixer writes the very same path
|
|
9
|
+
* instead of guessing one when a file has no queryKeys import to copy from.
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_QUERY_KEYS_SOURCE = '@/util/routing/queryKeys';
|
|
6
12
|
/**
|
|
7
13
|
* Rule to enforce the use of centralized router state key constants imported from
|
|
8
14
|
* `src/util/routing/queryKeys.ts` instead of arbitrary string literals when calling
|
|
@@ -29,15 +35,26 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
|
|
|
29
35
|
const queryKeyImports = new Map();
|
|
30
36
|
const localUseRouterStateNames = new Set(['useRouterState']);
|
|
31
37
|
const validQueryKeySources = new Set([
|
|
32
|
-
|
|
38
|
+
DEFAULT_QUERY_KEYS_SOURCE,
|
|
33
39
|
'src/util/routing/queryKeys',
|
|
34
40
|
]);
|
|
35
41
|
const allowedQueryKeyFactories = new Set(['makeQueryKey', 'getQueryKey']);
|
|
42
|
+
const sourceCode = context.getSourceCode();
|
|
36
43
|
/**
|
|
37
|
-
*
|
|
44
|
+
* Reports are buffered until the whole file has been walked so the fixer can
|
|
45
|
+
* put every substituted constant into a single import instead of racing
|
|
46
|
+
* per-violation insertions that overlap and get dropped.
|
|
38
47
|
*/
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
const pendingReports = [];
|
|
49
|
+
/**
|
|
50
|
+
* `SourceCode#getScope` supersedes the deprecated `context.getScope`; the
|
|
51
|
+
* fallback keeps the rule working on ESLint versions that predate it.
|
|
52
|
+
*/
|
|
53
|
+
function scopeOf(node) {
|
|
54
|
+
const scoped = sourceCode;
|
|
55
|
+
return typeof scoped.getScope === 'function'
|
|
56
|
+
? scoped.getScope(node)
|
|
57
|
+
: context.getScope();
|
|
41
58
|
}
|
|
42
59
|
/**
|
|
43
60
|
* Check if a source path refers to queryKeys.ts
|
|
@@ -46,6 +63,138 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
|
|
|
46
63
|
return (validQueryKeySources.has(source) ||
|
|
47
64
|
source.endsWith('/util/routing/queryKeys'));
|
|
48
65
|
}
|
|
66
|
+
function importDeclarationsOf() {
|
|
67
|
+
return sourceCode.ast.body.filter((statement) => statement.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
|
|
68
|
+
typeof statement.source.value === 'string');
|
|
69
|
+
}
|
|
70
|
+
function isValueImportSpecifier(specifier) {
|
|
71
|
+
return (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
72
|
+
specifier.importKind !== 'type');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A file that already imports the export under another name reaches it
|
|
76
|
+
* through that name; re-importing it would leave two bindings for one
|
|
77
|
+
* constant.
|
|
78
|
+
*/
|
|
79
|
+
function localNameOf(constant) {
|
|
80
|
+
for (const declaration of importDeclarationsOf()) {
|
|
81
|
+
if (declaration.importKind === 'type' ||
|
|
82
|
+
!isQueryKeysSource(String(declaration.source.value))) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
for (const specifier of declaration.specifiers) {
|
|
86
|
+
if (isValueImportSpecifier(specifier) &&
|
|
87
|
+
specifier.imported.name === constant) {
|
|
88
|
+
return specifier.local.name;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return constant;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Decide whether the suggested constant can be substituted, and whether the
|
|
96
|
+
* substitution has to carry an import with it.
|
|
97
|
+
*/
|
|
98
|
+
function resolveBinding(scope, constant) {
|
|
99
|
+
const variable = utils_1.ASTUtils.findVariable(scope, constant);
|
|
100
|
+
if (!variable) {
|
|
101
|
+
return 'missing';
|
|
102
|
+
}
|
|
103
|
+
const [definition] = variable.defs;
|
|
104
|
+
if (!definition) {
|
|
105
|
+
return 'conflict';
|
|
106
|
+
}
|
|
107
|
+
const definitionNode = definition.node;
|
|
108
|
+
if (definitionNode.type !== utils_1.AST_NODE_TYPES.ImportSpecifier ||
|
|
109
|
+
definitionNode.importKind === 'type' ||
|
|
110
|
+
!isValidQueryKeyConstant(definitionNode.imported.name)) {
|
|
111
|
+
return 'conflict';
|
|
112
|
+
}
|
|
113
|
+
const declaration = definitionNode.parent;
|
|
114
|
+
if (!declaration ||
|
|
115
|
+
declaration.type !== utils_1.AST_NODE_TYPES.ImportDeclaration ||
|
|
116
|
+
declaration.importKind === 'type' ||
|
|
117
|
+
typeof declaration.source.value !== 'string' ||
|
|
118
|
+
!isQueryKeysSource(declaration.source.value)) {
|
|
119
|
+
return 'conflict';
|
|
120
|
+
}
|
|
121
|
+
return 'bound';
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Make the substituted constants resolve: extend the file's queryKeys import
|
|
125
|
+
* when there is one to extend, otherwise add a fresh import statement.
|
|
126
|
+
*/
|
|
127
|
+
function buildImportFix(fixer, constants) {
|
|
128
|
+
const importDeclarations = importDeclarationsOf();
|
|
129
|
+
const queryKeysDeclarations = importDeclarations.filter((declaration) => isQueryKeysSource(String(declaration.source.value)));
|
|
130
|
+
const reusable = queryKeysDeclarations.find((declaration) => declaration.importKind !== 'type' &&
|
|
131
|
+
declaration.specifiers.some(isValueImportSpecifier));
|
|
132
|
+
if (reusable) {
|
|
133
|
+
const namedSpecifiers = reusable.specifiers.filter(isValueImportSpecifier);
|
|
134
|
+
const lastSpecifier = namedSpecifiers[namedSpecifiers.length - 1];
|
|
135
|
+
return fixer.insertTextAfter(lastSpecifier, constants.map((constant) => `, ${constant}`).join(''));
|
|
136
|
+
}
|
|
137
|
+
// A namespace or type-only queryKeys import cannot take named value
|
|
138
|
+
// specifiers, but its path is proof of how this file reaches the module.
|
|
139
|
+
const source = queryKeysDeclarations.length
|
|
140
|
+
? String(queryKeysDeclarations[0].source.value)
|
|
141
|
+
: DEFAULT_QUERY_KEYS_SOURCE;
|
|
142
|
+
const importText = `import { ${constants.join(', ')} } from '${source}';\n`;
|
|
143
|
+
const [firstImport] = importDeclarations;
|
|
144
|
+
if (firstImport) {
|
|
145
|
+
return fixer.insertTextBefore(firstImport, importText);
|
|
146
|
+
}
|
|
147
|
+
// Keep the import visually separated from the code it precedes unless the
|
|
148
|
+
// file already opens with a blank line.
|
|
149
|
+
const separator = /^\r?\n/.test(sourceCode.text) ? '' : '\n';
|
|
150
|
+
return fixer.insertTextBeforeRange([0, 0], `${importText}${separator}`);
|
|
151
|
+
}
|
|
152
|
+
function flushReports() {
|
|
153
|
+
const resolutions = new Map();
|
|
154
|
+
const missingConstants = [];
|
|
155
|
+
for (const report of pendingReports) {
|
|
156
|
+
if (!report.substitution) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const { constant, scope } = report.substitution;
|
|
160
|
+
const name = localNameOf(constant);
|
|
161
|
+
const state = resolveBinding(scope, name);
|
|
162
|
+
resolutions.set(report, { name, state });
|
|
163
|
+
if (state === 'missing' && !missingConstants.includes(name)) {
|
|
164
|
+
missingConstants.push(name);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// The first applied substitution carries the import for every other one:
|
|
168
|
+
// its fix range then starts at the top of the file and ends before the
|
|
169
|
+
// remaining literals, so no two fixes of this rule overlap in a pass.
|
|
170
|
+
const importCarrier = pendingReports.find((report) => {
|
|
171
|
+
const resolution = resolutions.get(report);
|
|
172
|
+
return resolution !== undefined && resolution.state !== 'conflict';
|
|
173
|
+
});
|
|
174
|
+
for (const report of pendingReports) {
|
|
175
|
+
context.report({
|
|
176
|
+
node: report.node,
|
|
177
|
+
messageId: report.messageId,
|
|
178
|
+
data: report.data,
|
|
179
|
+
fix(fixer) {
|
|
180
|
+
const { substitution } = report;
|
|
181
|
+
const resolution = resolutions.get(report);
|
|
182
|
+
if (!substitution ||
|
|
183
|
+
!resolution ||
|
|
184
|
+
resolution.state === 'conflict') {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const fixes = [
|
|
188
|
+
fixer.replaceText(substitution.keyNode, resolution.name),
|
|
189
|
+
];
|
|
190
|
+
if (report === importCarrier && missingConstants.length > 0) {
|
|
191
|
+
fixes.unshift(buildImportFix(fixer, missingConstants));
|
|
192
|
+
}
|
|
193
|
+
return fixes;
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
49
198
|
/**
|
|
50
199
|
* Check if an identifier is a valid QUERY_KEY constant
|
|
51
200
|
*/
|
|
@@ -236,25 +385,27 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
|
|
|
236
385
|
if (!isValidQueryKeyUsage(keyValue)) {
|
|
237
386
|
// Check if it contains invalid string literals
|
|
238
387
|
if (containsInvalidStringLiteral(keyValue)) {
|
|
239
|
-
|
|
388
|
+
// Only simple string literals can be auto-fixed
|
|
389
|
+
const suggestedConstant = keyValue.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
390
|
+
typeof keyValue.value === 'string'
|
|
391
|
+
? generateAutoFix(keyValue.value)
|
|
392
|
+
: null;
|
|
393
|
+
pendingReports.push({
|
|
240
394
|
node: keyValue,
|
|
241
395
|
messageId: 'enforceQueryKeyImport',
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return buildImportFixes(fixer, keyValue, suggestedConstant);
|
|
249
|
-
}
|
|
396
|
+
substitution: suggestedConstant &&
|
|
397
|
+
keyValue.type === utils_1.AST_NODE_TYPES.Literal
|
|
398
|
+
? {
|
|
399
|
+
keyNode: keyValue,
|
|
400
|
+
constant: suggestedConstant,
|
|
401
|
+
scope: scopeOf(keyValue),
|
|
250
402
|
}
|
|
251
|
-
|
|
252
|
-
},
|
|
403
|
+
: undefined,
|
|
253
404
|
});
|
|
254
405
|
}
|
|
255
406
|
else if (keyValue.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
256
407
|
// Report variables that aren't from the correct source
|
|
257
|
-
|
|
408
|
+
pendingReports.push({
|
|
258
409
|
node: keyValue,
|
|
259
410
|
messageId: 'enforceQueryKeyConstant',
|
|
260
411
|
data: {
|
|
@@ -268,6 +419,9 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
|
|
|
268
419
|
}
|
|
269
420
|
}
|
|
270
421
|
},
|
|
422
|
+
'Program:exit'() {
|
|
423
|
+
flushReports();
|
|
424
|
+
},
|
|
271
425
|
};
|
|
272
426
|
},
|
|
273
427
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
1
2
|
type MessageIds = 'reactNodeShouldBeLowercase' | 'componentTypeShouldBeUppercase';
|
|
2
|
-
export declare const enforceReactTypeNaming:
|
|
3
|
+
export declare const enforceReactTypeNaming: TSESLint.RuleModule<MessageIds, [], TSESLint.RuleListener>;
|
|
3
4
|
export {};
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.enforceReactTypeNaming = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const renameFixes_1 = require("../utils/renameFixes");
|
|
6
7
|
// Types that should have lowercase variable names
|
|
7
8
|
const LOWERCASE_TYPES = ['ReactNode', 'JSX.Element'];
|
|
8
9
|
// Types that should have uppercase variable names
|
|
@@ -86,18 +87,45 @@ exports.enforceReactTypeNaming = (0, createRule_1.createRule)({
|
|
|
86
87
|
(node.parent?.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
87
88
|
node.parent.local.name !== node.parent.imported?.name));
|
|
88
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Builds the rename for a reported identifier: the declaration plus every
|
|
92
|
+
* in-file reference, rewriting only name tokens so the type annotation that
|
|
93
|
+
* triggered the report survives (Issue #1357).
|
|
94
|
+
*
|
|
95
|
+
* The variable is resolved by declaration identity rather than by name,
|
|
96
|
+
* because a shape like `function Content(Content: ReactNode)` declares two
|
|
97
|
+
* distinct symbols under one name and a name-based lookup picks the wrong
|
|
98
|
+
* one. When the symbol cannot be resolved the fix is withheld entirely — a
|
|
99
|
+
* partial rename is worse than no rename.
|
|
100
|
+
*/
|
|
101
|
+
function buildRenameFixes(fixer, owner, declarationId, newName) {
|
|
102
|
+
const variable = context
|
|
103
|
+
.getDeclaredVariables(owner)
|
|
104
|
+
.find((candidate) => candidate.defs.some((def) => def.name === declarationId)) ?? null;
|
|
105
|
+
if (!variable) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return (0, renameFixes_1.buildVariableRenameFixes)({
|
|
109
|
+
fixer,
|
|
110
|
+
sourceCode: context.getSourceCode(),
|
|
111
|
+
variable,
|
|
112
|
+
declarationId,
|
|
113
|
+
newName,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
89
116
|
/**
|
|
90
117
|
* Check variable declarations for React type naming conventions
|
|
91
118
|
*/
|
|
92
119
|
function checkVariableDeclaration(node) {
|
|
93
120
|
if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
94
121
|
return;
|
|
122
|
+
const id = node.id;
|
|
95
123
|
// Skip destructured variables
|
|
96
|
-
if (isDestructured(
|
|
124
|
+
if (isDestructured(id))
|
|
97
125
|
return;
|
|
98
|
-
const variableName =
|
|
126
|
+
const variableName = id.name;
|
|
99
127
|
// Get the type annotation
|
|
100
|
-
const typeAnnotation =
|
|
128
|
+
const typeAnnotation = id.typeAnnotation?.typeAnnotation;
|
|
101
129
|
const typeName = getTypeName(typeAnnotation);
|
|
102
130
|
if (!typeName)
|
|
103
131
|
return;
|
|
@@ -105,44 +133,45 @@ exports.enforceReactTypeNaming = (0, createRule_1.createRule)({
|
|
|
105
133
|
if (LOWERCASE_TYPES.includes(typeName) && isUppercase(variableName)) {
|
|
106
134
|
const suggestion = toLowercase(variableName);
|
|
107
135
|
context.report({
|
|
108
|
-
node:
|
|
136
|
+
node: id,
|
|
109
137
|
messageId: 'reactNodeShouldBeLowercase',
|
|
110
138
|
data: {
|
|
111
139
|
type: typeName,
|
|
112
140
|
suggestion,
|
|
113
141
|
},
|
|
114
|
-
fix: (fixer) => fixer
|
|
142
|
+
fix: (fixer) => buildRenameFixes(fixer, node, id, suggestion),
|
|
115
143
|
});
|
|
116
144
|
}
|
|
117
145
|
// Check if it's a ComponentType or FC (should be uppercase)
|
|
118
146
|
if (UPPERCASE_TYPES.includes(typeName) && !isUppercase(variableName)) {
|
|
119
147
|
const suggestion = toUppercase(variableName);
|
|
120
148
|
context.report({
|
|
121
|
-
node:
|
|
149
|
+
node: id,
|
|
122
150
|
messageId: 'componentTypeShouldBeUppercase',
|
|
123
151
|
data: {
|
|
124
152
|
type: typeName,
|
|
125
153
|
suggestion,
|
|
126
154
|
},
|
|
127
|
-
fix: (fixer) => fixer
|
|
155
|
+
fix: (fixer) => buildRenameFixes(fixer, node, id, suggestion),
|
|
128
156
|
});
|
|
129
157
|
}
|
|
130
158
|
}
|
|
131
159
|
/**
|
|
132
160
|
* Check function parameters for React type naming conventions
|
|
133
161
|
*/
|
|
134
|
-
function checkParameter(node) {
|
|
162
|
+
function checkParameter(node, owner) {
|
|
135
163
|
if (node.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
136
164
|
return;
|
|
165
|
+
const id = node;
|
|
137
166
|
// Skip destructured parameters
|
|
138
|
-
if (isDestructured(
|
|
167
|
+
if (isDestructured(id))
|
|
139
168
|
return;
|
|
140
169
|
// Skip default imports
|
|
141
|
-
if (isDefaultImport(
|
|
170
|
+
if (isDefaultImport(id))
|
|
142
171
|
return;
|
|
143
|
-
const paramName =
|
|
172
|
+
const paramName = id.name;
|
|
144
173
|
// Get the type annotation
|
|
145
|
-
const typeAnnotation =
|
|
174
|
+
const typeAnnotation = id.typeAnnotation?.typeAnnotation;
|
|
146
175
|
const typeName = getTypeName(typeAnnotation);
|
|
147
176
|
if (!typeName)
|
|
148
177
|
return;
|
|
@@ -150,26 +179,26 @@ exports.enforceReactTypeNaming = (0, createRule_1.createRule)({
|
|
|
150
179
|
if (LOWERCASE_TYPES.includes(typeName) && isUppercase(paramName)) {
|
|
151
180
|
const suggestion = toLowercase(paramName);
|
|
152
181
|
context.report({
|
|
153
|
-
node,
|
|
182
|
+
node: id,
|
|
154
183
|
messageId: 'reactNodeShouldBeLowercase',
|
|
155
184
|
data: {
|
|
156
185
|
type: typeName,
|
|
157
186
|
suggestion,
|
|
158
187
|
},
|
|
159
|
-
fix: (fixer) => fixer
|
|
188
|
+
fix: (fixer) => buildRenameFixes(fixer, owner, id, suggestion),
|
|
160
189
|
});
|
|
161
190
|
}
|
|
162
191
|
// Check if it's a ComponentType or FC (should be uppercase)
|
|
163
192
|
if (UPPERCASE_TYPES.includes(typeName) && !isUppercase(paramName)) {
|
|
164
193
|
const suggestion = toUppercase(paramName);
|
|
165
194
|
context.report({
|
|
166
|
-
node,
|
|
195
|
+
node: id,
|
|
167
196
|
messageId: 'componentTypeShouldBeUppercase',
|
|
168
197
|
data: {
|
|
169
198
|
type: typeName,
|
|
170
199
|
suggestion,
|
|
171
200
|
},
|
|
172
|
-
fix: (fixer) => fixer
|
|
201
|
+
fix: (fixer) => buildRenameFixes(fixer, owner, id, suggestion),
|
|
173
202
|
});
|
|
174
203
|
}
|
|
175
204
|
}
|
|
@@ -182,7 +211,7 @@ exports.enforceReactTypeNaming = (0, createRule_1.createRule)({
|
|
|
182
211
|
node.parent.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
183
212
|
node.parent.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) &&
|
|
184
213
|
node.parent.params.includes(node)) {
|
|
185
|
-
checkParameter(node);
|
|
214
|
+
checkParameter(node, node.parent);
|
|
186
215
|
}
|
|
187
216
|
},
|
|
188
217
|
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
1
2
|
type MessageIds = 'noFalsyCheck' | 'noRawTypeof';
|
|
2
3
|
type Options = [
|
|
3
4
|
{
|
|
4
5
|
snapshotHooks?: string[];
|
|
5
6
|
guardFunctions?: string[];
|
|
6
7
|
excludeFiles?: string[];
|
|
8
|
+
guardImportSource?: string;
|
|
7
9
|
}?
|
|
8
10
|
];
|
|
9
|
-
export declare const enforceSnapshotStateNarrowing:
|
|
11
|
+
export declare const enforceSnapshotStateNarrowing: TSESLint.RuleModule<MessageIds, Options, TSESLint.RuleListener>;
|
|
10
12
|
export {};
|