@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
|
@@ -14,7 +14,7 @@ exports.useCustomRouter = (0, createRule_1.createRule)({
|
|
|
14
14
|
fixable: 'code',
|
|
15
15
|
schema: [],
|
|
16
16
|
messages: {
|
|
17
|
-
useCustomRouter: 'Import
|
|
17
|
+
useCustomRouter: 'useRouter import "{{imports}}" comes from next/router, which bypasses the app-specific hook that applies auth checks, analytics, and redirect helpers. Import it from src/hooks/routing/useRouter so routing code stays consistent with the rest of the app.',
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
defaultOptions: [],
|
|
@@ -29,6 +29,11 @@ exports.useCustomRouter = (0, createRule_1.createRule)({
|
|
|
29
29
|
context.report({
|
|
30
30
|
node,
|
|
31
31
|
messageId: 'useCustomRouter',
|
|
32
|
+
data: {
|
|
33
|
+
imports: specifiers
|
|
34
|
+
.map((specifier) => specifier.local.name)
|
|
35
|
+
.join(', '),
|
|
36
|
+
},
|
|
32
37
|
fix(fixer) {
|
|
33
38
|
// If there are other imports from next/router, keep them
|
|
34
39
|
const otherSpecifiers = node.specifiers.filter((specifier) => specifier.type !== utils_1.AST_NODE_TYPES.ImportSpecifier ||
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useLatestCallback = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.useLatestCallback = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'use-latest-callback',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Enforce using useLatestCallback from use-latest-callback instead of React useCallback',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
useLatestCallback: 'Replace {{currentHook}} with {{recommendedHook}} from "use-latest-callback" so the callback keeps a stable reference while still reading the latest props/state. useCallback recreates functions whenever dependencies change, which can trigger needless renders and stale closures. Drop the dependency array when switching to {{recommendedHook}}.',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
const filename = context.getFilename();
|
|
23
|
+
if (filename.includes('/node_modules/')) {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
// Track if the file already has a useLatestCallback import
|
|
27
|
+
let hasUseLatestCallbackImport = false;
|
|
28
|
+
let useLatestCallbackImportName = 'useLatestCallback';
|
|
29
|
+
// Track if any useCallback calls should be replaced
|
|
30
|
+
let hasNonJsxUseCallbacks = false;
|
|
31
|
+
let useCallbackLocalNames = new Set();
|
|
32
|
+
const reactNamespaceNames = new Set();
|
|
33
|
+
const reactDefaultLikeNames = new Set();
|
|
34
|
+
let hasReactMemberUseCallback = false;
|
|
35
|
+
const isJSXLikeReturn = (node) => {
|
|
36
|
+
if (!node)
|
|
37
|
+
return false;
|
|
38
|
+
if (node.type === utils_1.AST_NODE_TYPES.JSXElement ||
|
|
39
|
+
node.type === utils_1.AST_NODE_TYPES.JSXFragment ||
|
|
40
|
+
node.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (node.type === 'ParenthesizedExpression') {
|
|
44
|
+
return isJSXLikeReturn(node.expression);
|
|
45
|
+
}
|
|
46
|
+
if (node.type === utils_1.AST_NODE_TYPES.SequenceExpression) {
|
|
47
|
+
return isJSXLikeReturn(node.expressions[node.expressions.length - 1]);
|
|
48
|
+
}
|
|
49
|
+
if (node.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
|
|
50
|
+
return node.elements.some((el) => isJSXLikeReturn(el && el.type === utils_1.AST_NODE_TYPES.SpreadElement
|
|
51
|
+
? el.argument
|
|
52
|
+
: el));
|
|
53
|
+
}
|
|
54
|
+
if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
|
|
55
|
+
return (isJSXLikeReturn(node.consequent) || isJSXLikeReturn(node.alternate));
|
|
56
|
+
}
|
|
57
|
+
if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
|
|
58
|
+
return (isJSXLikeReturn(node.left) || isJSXLikeReturn(node.right));
|
|
59
|
+
}
|
|
60
|
+
if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
61
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
62
|
+
node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
63
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
64
|
+
reactNamespaceNames.has(node.callee.object.name) &&
|
|
65
|
+
node.callee.property.name === 'createElement') {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
// First pass: check imports
|
|
73
|
+
ImportDeclaration(node) {
|
|
74
|
+
if (node.importKind && node.importKind !== 'value')
|
|
75
|
+
return;
|
|
76
|
+
if (node.source.value === 'use-latest-callback') {
|
|
77
|
+
// Check if useLatestCallback is imported
|
|
78
|
+
const specifiers = node.specifiers.filter((specifier) => {
|
|
79
|
+
if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier) {
|
|
80
|
+
return (specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
81
|
+
specifier.imported.name === 'useLatestCallback');
|
|
82
|
+
}
|
|
83
|
+
return (specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier ||
|
|
84
|
+
specifier.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier);
|
|
85
|
+
});
|
|
86
|
+
if (specifiers.length > 0) {
|
|
87
|
+
hasUseLatestCallbackImport = true;
|
|
88
|
+
// Get the local name of the import (in case it's renamed)
|
|
89
|
+
if (specifiers[0].type === utils_1.AST_NODE_TYPES.ImportSpecifier) {
|
|
90
|
+
useLatestCallbackImportName = specifiers[0].local.name;
|
|
91
|
+
}
|
|
92
|
+
else if (specifiers[0].type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier) {
|
|
93
|
+
useLatestCallbackImportName = specifiers[0].local.name;
|
|
94
|
+
}
|
|
95
|
+
else if (specifiers[0].type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
|
|
96
|
+
useLatestCallbackImportName = `${specifiers[0].local.name}.useLatestCallback`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (node.source.value === 'react') {
|
|
101
|
+
// Check if useCallback is imported from React
|
|
102
|
+
const useCallbackSpecifiers = node.specifiers.filter((specifier) => {
|
|
103
|
+
return (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
104
|
+
specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
105
|
+
specifier.imported.name === 'useCallback');
|
|
106
|
+
});
|
|
107
|
+
node.specifiers.forEach((specifier) => {
|
|
108
|
+
if (specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier ||
|
|
109
|
+
specifier.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
|
|
110
|
+
reactNamespaceNames.add(specifier.local.name);
|
|
111
|
+
reactDefaultLikeNames.add(specifier.local.name);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
if (useCallbackSpecifiers.length > 0) {
|
|
115
|
+
useCallbackSpecifiers.forEach((spec) => {
|
|
116
|
+
useCallbackLocalNames.add(spec.local.name);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
// Check for useCallback usage and identify JSX-returning callbacks
|
|
122
|
+
CallExpression(node) {
|
|
123
|
+
const isUseCallbackIdentifierCall = node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
124
|
+
useCallbackLocalNames.has(node.callee.name);
|
|
125
|
+
const isUseCallbackMemberCall = node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
126
|
+
node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
127
|
+
reactDefaultLikeNames.has(node.callee.object.name) &&
|
|
128
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
129
|
+
node.callee.property.name === 'useCallback';
|
|
130
|
+
if ((isUseCallbackIdentifierCall || isUseCallbackMemberCall) && node.arguments.length >= 1) {
|
|
131
|
+
if (isUseCallbackMemberCall) {
|
|
132
|
+
hasReactMemberUseCallback = true;
|
|
133
|
+
}
|
|
134
|
+
// Check if this is a JSX-returning callback (edge case where we shouldn't replace)
|
|
135
|
+
const callback = node.arguments[0];
|
|
136
|
+
let isJsxReturning = false;
|
|
137
|
+
if ((callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
138
|
+
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
|
|
139
|
+
callback.body) {
|
|
140
|
+
// For arrow functions with implicit return
|
|
141
|
+
if (callback.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
142
|
+
if (isJSXLikeReturn(callback.body)) {
|
|
143
|
+
isJsxReturning = true;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// For functions with block body - check all return statements
|
|
147
|
+
else if (callback.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
148
|
+
const returnStatements = callback.body.body.filter((statement) => statement.type === utils_1.AST_NODE_TYPES.ReturnStatement);
|
|
149
|
+
// If there are return statements, check if any return JSX
|
|
150
|
+
if (returnStatements.length > 0) {
|
|
151
|
+
isJsxReturning = returnStatements.some((returnStatement) => returnStatement.argument &&
|
|
152
|
+
isJSXLikeReturn(returnStatement.argument));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (!isJsxReturning) {
|
|
157
|
+
hasNonJsxUseCallbacks = true;
|
|
158
|
+
const currentCallbackName = node.callee.type === utils_1.AST_NODE_TYPES.Identifier
|
|
159
|
+
? node.callee.name
|
|
160
|
+
: node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
161
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier
|
|
162
|
+
? node.callee.property.name
|
|
163
|
+
: 'useCallback';
|
|
164
|
+
const replacementName = hasUseLatestCallbackImport
|
|
165
|
+
? useLatestCallbackImportName
|
|
166
|
+
: currentCallbackName === 'useCallback'
|
|
167
|
+
? 'useLatestCallback'
|
|
168
|
+
: currentCallbackName;
|
|
169
|
+
// Report the useCallback call for replacement
|
|
170
|
+
context.report({
|
|
171
|
+
node,
|
|
172
|
+
messageId: 'useLatestCallback',
|
|
173
|
+
data: {
|
|
174
|
+
currentHook: currentCallbackName,
|
|
175
|
+
recommendedHook: replacementName,
|
|
176
|
+
},
|
|
177
|
+
fix(fixer) {
|
|
178
|
+
const sourceCode = context.getSourceCode();
|
|
179
|
+
const callbackText = sourceCode.getText(node.arguments[0]);
|
|
180
|
+
const typeParams = node.typeParameters
|
|
181
|
+
? sourceCode.getText(node.typeParameters)
|
|
182
|
+
: '';
|
|
183
|
+
// Replace useCallback with useLatestCallback and remove the dependency array
|
|
184
|
+
return fixer.replaceText(node, `${replacementName}${typeParams}(${callbackText})`);
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
// Final pass: check for useCallback import from react (only if there are non-JSX callbacks)
|
|
191
|
+
'Program:exit'() {
|
|
192
|
+
if (!hasNonJsxUseCallbacks) {
|
|
193
|
+
return; // Don't modify imports if all useCallback calls return JSX
|
|
194
|
+
}
|
|
195
|
+
const sourceCode = context.getSourceCode();
|
|
196
|
+
const program = sourceCode.ast;
|
|
197
|
+
for (const statement of program.body) {
|
|
198
|
+
if (statement.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
|
|
199
|
+
statement.source.value === 'react') {
|
|
200
|
+
if (statement.importKind && statement.importKind !== 'value') {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const specifiers = statement.specifiers.filter((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
204
|
+
specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
205
|
+
specifier.imported.name === 'useCallback');
|
|
206
|
+
if (specifiers.length > 0 || hasReactMemberUseCallback) {
|
|
207
|
+
const useCallbackLocalName = specifiers[0]?.local.name ?? 'useCallback';
|
|
208
|
+
const recommendedHook = hasUseLatestCallbackImport
|
|
209
|
+
? useLatestCallbackImportName
|
|
210
|
+
: useCallbackLocalName === 'useCallback'
|
|
211
|
+
? 'useLatestCallback'
|
|
212
|
+
: useCallbackLocalName;
|
|
213
|
+
context.report({
|
|
214
|
+
node: statement,
|
|
215
|
+
messageId: 'useLatestCallback',
|
|
216
|
+
data: {
|
|
217
|
+
currentHook: useCallbackLocalName,
|
|
218
|
+
recommendedHook,
|
|
219
|
+
},
|
|
220
|
+
fix(fixer) {
|
|
221
|
+
const importText = `import ${useCallbackLocalName === 'useCallback'
|
|
222
|
+
? 'useLatestCallback'
|
|
223
|
+
: useCallbackLocalName} from 'use-latest-callback';`;
|
|
224
|
+
if (specifiers.length === 0) {
|
|
225
|
+
if (hasUseLatestCallbackImport)
|
|
226
|
+
return null;
|
|
227
|
+
return fixer.insertTextBefore(statement, `${importText}\n`);
|
|
228
|
+
}
|
|
229
|
+
const defaultOrNamespace = statement.specifiers.find((s) => s.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier ||
|
|
230
|
+
s.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier);
|
|
231
|
+
const remainingNamed = statement.specifiers.filter((s) => s.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
232
|
+
s.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
233
|
+
s.imported.name !== 'useCallback');
|
|
234
|
+
const prefix = statement.importKind && statement.importKind !== 'value'
|
|
235
|
+
? 'import type'
|
|
236
|
+
: 'import';
|
|
237
|
+
if (remainingNamed.length === 0 && !defaultOrNamespace) {
|
|
238
|
+
if (!hasUseLatestCallbackImport) {
|
|
239
|
+
return fixer.replaceText(statement, importText);
|
|
240
|
+
}
|
|
241
|
+
return fixer.remove(statement);
|
|
242
|
+
}
|
|
243
|
+
const parts = [];
|
|
244
|
+
if (defaultOrNamespace) {
|
|
245
|
+
parts.push(sourceCode.getText(defaultOrNamespace));
|
|
246
|
+
}
|
|
247
|
+
if (remainingNamed.length > 0) {
|
|
248
|
+
parts.push(`{ ${remainingNamed
|
|
249
|
+
.map((s) => sourceCode.getText(s))
|
|
250
|
+
.join(', ')} }`);
|
|
251
|
+
}
|
|
252
|
+
const replacement = `${prefix} ${parts.join(', ')} from 'react';`;
|
|
253
|
+
const fixes = [];
|
|
254
|
+
if (!hasUseLatestCallbackImport) {
|
|
255
|
+
fixes.push(fixer.insertTextBefore(statement, `${importText}\n`));
|
|
256
|
+
}
|
|
257
|
+
fixes.push(fixer.replaceText(statement, replacement));
|
|
258
|
+
return fixes;
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
exports.default = exports.useLatestCallback;
|
|
270
|
+
//# sourceMappingURL=use-latest-callback.js.map
|
package/lib/utils/ASTHelpers.js
CHANGED
|
@@ -14,9 +14,15 @@ class ASTHelpers {
|
|
|
14
14
|
if (!node) {
|
|
15
15
|
return false;
|
|
16
16
|
}
|
|
17
|
+
// Gracefully handle ParenthesizedExpression without widening AST node types
|
|
18
|
+
if (node.type === 'ParenthesizedExpression') {
|
|
19
|
+
return this.declarationIncludesIdentifier(node.expression);
|
|
20
|
+
}
|
|
17
21
|
switch (node.type) {
|
|
18
22
|
case 'TSNonNullExpression':
|
|
19
23
|
return this.declarationIncludesIdentifier(node.expression);
|
|
24
|
+
case 'TSSatisfiesExpression':
|
|
25
|
+
return this.declarationIncludesIdentifier(node.expression);
|
|
20
26
|
case 'ArrayPattern':
|
|
21
27
|
return node.elements.some((element) => ASTHelpers.declarationIncludesIdentifier(element));
|
|
22
28
|
case 'ObjectPattern':
|
|
@@ -114,6 +120,10 @@ class ASTHelpers {
|
|
|
114
120
|
if (!node) {
|
|
115
121
|
return dependencies;
|
|
116
122
|
}
|
|
123
|
+
// Gracefully handle ParenthesizedExpression without widening AST node types
|
|
124
|
+
if (node.type === 'ParenthesizedExpression') {
|
|
125
|
+
return ASTHelpers.classMethodDependenciesOf(node.expression, graph, className);
|
|
126
|
+
}
|
|
117
127
|
switch (node.type) {
|
|
118
128
|
case 'MethodDefinition':
|
|
119
129
|
const functionBody = node.value.body;
|
package/lib/utils/createRule.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createRule = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
-
exports.createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/BluMintInc/eslint-custom-rules/
|
|
5
|
+
exports.createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/BluMintInc/eslint-custom-rules/docs/rules/${name}.md`);
|
|
6
6
|
//# sourceMappingURL=createRule.js.map
|
|
@@ -86,7 +86,6 @@ class ClassGraphSorterReadability extends ClassGraphSorter_1.ClassGraphSorter {
|
|
|
86
86
|
return dfsSortedNodes;
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
-
exports.ClassGraphSorterReadability = ClassGraphSorterReadability;
|
|
90
89
|
ClassGraphSorterReadability.MODIFIER_PRIORITY_MAP = {
|
|
91
90
|
isStatic: [true, false],
|
|
92
91
|
accessibility: ['public', undefined, 'private'],
|
|
@@ -98,4 +97,5 @@ ClassGraphSorterReadability.SEARCH_NODE_PRIORITY_FUNCTIONS = [
|
|
|
98
97
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
99
98
|
(method, _methods) => method.dependencies.length === 0,
|
|
100
99
|
];
|
|
100
|
+
exports.ClassGraphSorterReadability = ClassGraphSorterReadability;
|
|
101
101
|
//# sourceMappingURL=ClassGraphSorterReadability.js.map
|
package/lib/utils/ruleTester.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ruleTesterJson = exports.ruleTesterJsx = exports.ruleTesterTs = void 0;
|
|
3
|
+
exports.ruleTesterMarkdown = exports.ruleTesterJson = exports.ruleTesterJsx = exports.ruleTesterTs = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const eslint_1 = require("eslint");
|
|
6
6
|
exports.ruleTesterTs = new utils_1.ESLintUtils.RuleTester({
|
|
@@ -20,4 +20,7 @@ exports.ruleTesterJson = new eslint_1.RuleTester({
|
|
|
20
20
|
ecmaVersion: 2020,
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
|
+
exports.ruleTesterMarkdown = new eslint_1.RuleTester({
|
|
24
|
+
parser: require.resolve('markdown-eslint-parser'),
|
|
25
|
+
});
|
|
23
26
|
//# sourceMappingURL=ruleTester.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blumintinc/eslint-plugin-blumint",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Custom eslint rules for use within BluMint",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Brodie McGuire",
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
},
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
|
-
"url": "git+https://github.com/BluMintInc/custom-
|
|
16
|
+
"url": "git+https://github.com/BluMintInc/eslint-custom-rules.git"
|
|
17
17
|
},
|
|
18
18
|
"bugs": {
|
|
19
|
-
"url": "https://github.com/BluMintInc/custom-
|
|
19
|
+
"url": "https://github.com/BluMintInc/eslint-custom-rules/issues"
|
|
20
20
|
},
|
|
21
|
-
"homepage": "https://github.com/BluMintInc/custom-
|
|
21
|
+
"homepage": "https://github.com/BluMintInc/eslint-custom-rules#readme",
|
|
22
22
|
"keywords": [
|
|
23
23
|
"eslint",
|
|
24
24
|
"eslintplugin",
|
|
@@ -29,10 +29,15 @@
|
|
|
29
29
|
"lint": "npm-run-all \"lint:*\"",
|
|
30
30
|
"lint:eslint-docs": "npm-run-all \"update:eslint-docs -- --check\"",
|
|
31
31
|
"lint:js": "eslint ./src",
|
|
32
|
-
"lint:shell": "shellcheck .devcontainer/git-flow-completion.bash",
|
|
33
32
|
"lint:fix": "tsc && eslint \"./src/**/*\" --quiet --fix",
|
|
34
33
|
"test": "jest --passWithNoTests --reporters=default --reporters=jest-junit",
|
|
35
34
|
"test:ci": "jest --ci --passWithNoTests --reporters=default --reporters=jest-junit",
|
|
35
|
+
"address-review": "tsx ./.github/scripts/address-review.ts",
|
|
36
|
+
"merge-review": "tsx ./.github/scripts/merge-review.ts",
|
|
37
|
+
"fetch-unresolved-comments": "./scripts/pr-check-comments.sh",
|
|
38
|
+
"fetch-unresolved-bot-comments": "./scripts/pr-check-bot-comments.sh",
|
|
39
|
+
"resolve-comments": "./scripts/pr-resolve-comments.sh",
|
|
40
|
+
"apply-comment-decisions": "./scripts/pr-apply-comment-decisions.sh",
|
|
36
41
|
"docs": "./scripts/make-docs.sh && npm run update:eslint-docs",
|
|
37
42
|
"update:eslint-docs": "eslint-doc-generator --init-rule-docs",
|
|
38
43
|
"build": "tsc",
|
|
@@ -54,12 +59,12 @@
|
|
|
54
59
|
"devDependencies": {
|
|
55
60
|
"@commitlint/cli": "19.6.1",
|
|
56
61
|
"@commitlint/config-conventional": "19.6.0",
|
|
57
|
-
"@semantic-release/changelog": "6.0.
|
|
58
|
-
"@semantic-release/commit-analyzer": "
|
|
59
|
-
"@semantic-release/exec": "
|
|
62
|
+
"@semantic-release/changelog": "6.0.3",
|
|
63
|
+
"@semantic-release/commit-analyzer": "13.0.1",
|
|
64
|
+
"@semantic-release/exec": "7.1.0",
|
|
60
65
|
"@semantic-release/git": "10.0.1",
|
|
61
|
-
"@semantic-release/npm": "
|
|
62
|
-
"@semantic-release/release-notes-generator": "
|
|
66
|
+
"@semantic-release/npm": "13.1.2",
|
|
67
|
+
"@semantic-release/release-notes-generator": "14.1.0",
|
|
63
68
|
"@types/eslint": "8.37.0",
|
|
64
69
|
"@types/jest": "29.5.14",
|
|
65
70
|
"@types/node": "18.7.13",
|
|
@@ -72,9 +77,9 @@
|
|
|
72
77
|
"cz-conventional-changelog": "3.3.0",
|
|
73
78
|
"del-cli": "4.0.1",
|
|
74
79
|
"dotenv-cli": "5.0.0",
|
|
75
|
-
"eslint": "8.57.
|
|
80
|
+
"eslint": "8.57.1",
|
|
76
81
|
"eslint-config-prettier": "8.5.0",
|
|
77
|
-
"eslint-doc-generator": "
|
|
82
|
+
"eslint-doc-generator": "2.2.0",
|
|
78
83
|
"eslint-import-resolver-typescript": "3.5.5",
|
|
79
84
|
"eslint-plugin-eslint-plugin": "5.0.0",
|
|
80
85
|
"eslint-plugin-import": "2.26.0",
|
|
@@ -87,15 +92,17 @@
|
|
|
87
92
|
"jest": "29.7.0",
|
|
88
93
|
"jest-junit": "14.0.0",
|
|
89
94
|
"jsonc-eslint-parser": "2.3.0",
|
|
95
|
+
"markdown-eslint-parser": "1.2.1",
|
|
90
96
|
"npm-run-all": "4.1.5",
|
|
91
97
|
"prettier": "2.7.1",
|
|
92
98
|
"remark-cli": "10.0.1",
|
|
93
99
|
"remark-lint": "9.1.1",
|
|
94
100
|
"remark-preset-lint-recommended": "6.1.2",
|
|
95
|
-
"semantic-release": "
|
|
101
|
+
"semantic-release": "25.0.2",
|
|
96
102
|
"ts-jest": "29.2.6",
|
|
97
103
|
"ts-node": "10.9.1",
|
|
98
|
-
"
|
|
104
|
+
"tsx": "4.19.4",
|
|
105
|
+
"typescript": "5.0.3"
|
|
99
106
|
},
|
|
100
107
|
"peerDependencies": {
|
|
101
108
|
"eslint": ">=7"
|