@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
|
@@ -12,6 +12,47 @@ const DEFAULT_USE_DYNAMIC_SOURCES = [
|
|
|
12
12
|
function matchesAllowedSource(source, allowedSources) {
|
|
13
13
|
return allowedSources.some((pattern) => source === pattern || source.endsWith(`/${pattern}`));
|
|
14
14
|
}
|
|
15
|
+
function hasNodeStructure(value) {
|
|
16
|
+
return (!!value &&
|
|
17
|
+
typeof value === 'object' &&
|
|
18
|
+
'type' in value &&
|
|
19
|
+
typeof value.type === 'string');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Walks the AST and invokes the visitor on each node.
|
|
23
|
+
* Uses WeakSet to prevent infinite cycles.
|
|
24
|
+
*
|
|
25
|
+
* @param visitor - Callback invoked on each node. Return `false` to stop
|
|
26
|
+
* traversal; return `undefined` (or nothing) to continue walking.
|
|
27
|
+
* @returns `false` if traversal stopped early; `true` when traversal completes.
|
|
28
|
+
*/
|
|
29
|
+
function walkAst(node, visitor, visited = new WeakSet()) {
|
|
30
|
+
if (visited.has(node))
|
|
31
|
+
return true;
|
|
32
|
+
visited.add(node);
|
|
33
|
+
const shouldContinue = visitor(node);
|
|
34
|
+
if (shouldContinue === false)
|
|
35
|
+
return false;
|
|
36
|
+
const anyNode = node;
|
|
37
|
+
for (const key of Object.keys(anyNode)) {
|
|
38
|
+
if (key === 'parent')
|
|
39
|
+
continue;
|
|
40
|
+
const child = anyNode[key];
|
|
41
|
+
if (Array.isArray(child)) {
|
|
42
|
+
for (const c of child) {
|
|
43
|
+
if (hasNodeStructure(c)) {
|
|
44
|
+
if (!walkAst(c, visitor, visited))
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (hasNodeStructure(child)) {
|
|
50
|
+
if (!walkAst(child, visitor, visited))
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
15
56
|
function isUseDynamicCall(node) {
|
|
16
57
|
const { callee, arguments: args } = node;
|
|
17
58
|
if (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
@@ -126,7 +167,7 @@ exports.preferNextDynamic = (0, createRule_1.createRule)({
|
|
|
126
167
|
docs: {
|
|
127
168
|
description: 'Prefer Next.js dynamic() over custom useDynamic() for component imports',
|
|
128
169
|
recommended: 'error',
|
|
129
|
-
requiresTypeChecking:
|
|
170
|
+
requiresTypeChecking: false,
|
|
130
171
|
},
|
|
131
172
|
fixable: 'code',
|
|
132
173
|
schema: [
|
|
@@ -143,9 +184,9 @@ exports.preferNextDynamic = (0, createRule_1.createRule)({
|
|
|
143
184
|
},
|
|
144
185
|
],
|
|
145
186
|
messages: {
|
|
146
|
-
preferNextDynamic: '
|
|
147
|
-
addNextDynamicImport: "
|
|
148
|
-
removeUseDynamicImport: 'Remove unused useDynamic import',
|
|
187
|
+
preferNextDynamic: 'Component "{{componentName}}" is created with useDynamic(import(...)), which bypasses Next.js dynamic() handling for client-only components and leaves SSR control to a custom wrapper. Wrap the import in dynamic(() => import(...), { ssr: false }) so Next.js manages code-splitting and disables server rendering safely.',
|
|
188
|
+
addNextDynamicImport: "The auto-fix will replace useDynamic(import(...)) with dynamic(() => import(...), { ssr: false }), which references Next.js's dynamic function. Without importing dynamic from 'next/dynamic', the fixed code will throw a ReferenceError at runtime when the module loads. Add `import dynamic from 'next/dynamic'` at the top of the file to make the dynamic identifier available.",
|
|
189
|
+
removeUseDynamicImport: 'Remove the unused useDynamic import after migrating to dynamic(); leaving the custom hook imported invites accidental reuse and keeps dead code in the bundle.',
|
|
149
190
|
},
|
|
150
191
|
},
|
|
151
192
|
defaultOptions: [{}],
|
|
@@ -154,7 +195,7 @@ exports.preferNextDynamic = (0, createRule_1.createRule)({
|
|
|
154
195
|
const allowedUseDynamicSources = options.useDynamicSources && options.useDynamicSources.length > 0
|
|
155
196
|
? options.useDynamicSources
|
|
156
197
|
: DEFAULT_USE_DYNAMIC_SOURCES;
|
|
157
|
-
const sourceCode = context.
|
|
198
|
+
const sourceCode = context.sourceCode;
|
|
158
199
|
return {
|
|
159
200
|
VariableDeclarator(node) {
|
|
160
201
|
const info = inferVariableInfo(node);
|
|
@@ -169,50 +210,18 @@ exports.preferNextDynamic = (0, createRule_1.createRule)({
|
|
|
169
210
|
if (!useDynamicImportInfo)
|
|
170
211
|
return;
|
|
171
212
|
const identifierName = info.idText;
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
// Walk AST to find JSX usage; track visited nodes to avoid cycles
|
|
175
|
-
// Note: We intentionally avoid relying on tokens here.
|
|
176
|
-
// Fallback AST traversal for JSX usage
|
|
177
|
-
const visited = new WeakSet();
|
|
178
|
-
const checkJsxUsage = (n) => {
|
|
179
|
-
if (visited.has(n))
|
|
180
|
-
return;
|
|
181
|
-
visited.add(n);
|
|
182
|
-
if (usedInJsx)
|
|
183
|
-
return;
|
|
213
|
+
// walkAst returns false when visitor returns false (early exit on match)
|
|
214
|
+
const usedInJsx = program.body.some((b) => walkAst(b, (n) => {
|
|
184
215
|
if (n.type === utils_1.AST_NODE_TYPES.JSXOpeningElement) {
|
|
185
216
|
const name = n.name;
|
|
186
217
|
if (name.type === utils_1.AST_NODE_TYPES.JSXIdentifier) {
|
|
187
|
-
if (name.name === identifierName)
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
else if (name.type === utils_1.AST_NODE_TYPES.JSXMemberExpression) {
|
|
191
|
-
// Not matching identifier simple usage
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
// recurse
|
|
195
|
-
const anyNode = n;
|
|
196
|
-
for (const key of Object.keys(anyNode)) {
|
|
197
|
-
if (key === 'parent')
|
|
198
|
-
continue;
|
|
199
|
-
const child = anyNode[key];
|
|
200
|
-
if (Array.isArray(child)) {
|
|
201
|
-
for (const c of child) {
|
|
202
|
-
if (c && typeof c.type === 'string') {
|
|
203
|
-
checkJsxUsage(c);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
else if (child && typeof child === 'object') {
|
|
208
|
-
if (child.type &&
|
|
209
|
-
typeof child.type === 'string') {
|
|
210
|
-
checkJsxUsage(child);
|
|
218
|
+
if (name.name === identifierName) {
|
|
219
|
+
return false;
|
|
211
220
|
}
|
|
212
221
|
}
|
|
213
222
|
}
|
|
214
|
-
|
|
215
|
-
|
|
223
|
+
return undefined;
|
|
224
|
+
}) === false);
|
|
216
225
|
if (!usedInJsx) {
|
|
217
226
|
// Skip to avoid flagging non-component dynamic imports
|
|
218
227
|
return;
|
|
@@ -223,10 +232,11 @@ exports.preferNextDynamic = (0, createRule_1.createRule)({
|
|
|
223
232
|
context.report({
|
|
224
233
|
node: init,
|
|
225
234
|
messageId: 'preferNextDynamic',
|
|
235
|
+
data: { componentName: identifierName },
|
|
226
236
|
fix(fixer) {
|
|
227
237
|
const fixes = [];
|
|
228
238
|
// ensure dynamic import is present
|
|
229
|
-
const programNode =
|
|
239
|
+
const programNode = program;
|
|
230
240
|
let dynamicLocal = getNextDynamicLocalName(programNode);
|
|
231
241
|
const hasDynamic = !!dynamicLocal;
|
|
232
242
|
if (!hasDynamic) {
|
|
@@ -266,33 +276,14 @@ exports.preferNextDynamic = (0, createRule_1.createRule)({
|
|
|
266
276
|
const latestUseDynamicImport = findUseDynamicImport(programNode, allowedUseDynamicSources);
|
|
267
277
|
if (latestUseDynamicImport) {
|
|
268
278
|
// Abort removal if there are other useDynamic(import(...)) calls in the file
|
|
269
|
-
|
|
270
|
-
const visit = (n) => {
|
|
271
|
-
if (otherUseDynamicCalls)
|
|
272
|
-
return;
|
|
279
|
+
const otherUseDynamicCalls = programNode.body.some((b) => walkAst(b, (n) => {
|
|
273
280
|
if (n.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
274
|
-
isUseDynamicCall(n)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
const anyNode = n;
|
|
279
|
-
for (const key of Object.keys(anyNode)) {
|
|
280
|
-
if (key === 'parent')
|
|
281
|
-
continue;
|
|
282
|
-
const child = anyNode[key];
|
|
283
|
-
if (Array.isArray(child)) {
|
|
284
|
-
for (const c of child)
|
|
285
|
-
if (c && typeof c.type === 'string')
|
|
286
|
-
visit(c);
|
|
287
|
-
}
|
|
288
|
-
else if (child &&
|
|
289
|
-
typeof child === 'object' &&
|
|
290
|
-
child.type) {
|
|
291
|
-
visit(child);
|
|
292
|
-
}
|
|
281
|
+
isUseDynamicCall(n) &&
|
|
282
|
+
n !== init) {
|
|
283
|
+
return false;
|
|
293
284
|
}
|
|
294
|
-
|
|
295
|
-
|
|
285
|
+
return undefined;
|
|
286
|
+
}) === false);
|
|
296
287
|
if (otherUseDynamicCalls) {
|
|
297
288
|
return fixes; // keep the import; other occurrences still rely on it
|
|
298
289
|
}
|
|
@@ -396,7 +396,7 @@ exports.preferNullishCoalescingBooleanProps = (0, createRule_1.createRule)({
|
|
|
396
396
|
},
|
|
397
397
|
fixable: 'code',
|
|
398
398
|
messages: {
|
|
399
|
-
preferNullishCoalescing: '
|
|
399
|
+
preferNullishCoalescing: 'Logical OR between "{{left}}" and "{{right}}" treats every falsy value (false, 0, "", NaN) as missing and will override intentional boolean or empty states. Use the nullish coalescing operator (??) so "{{right}}" only applies when "{{left}}" is null or undefined, preserving explicit false/0/"" values.',
|
|
400
400
|
},
|
|
401
401
|
schema: [],
|
|
402
402
|
},
|
|
@@ -412,13 +412,18 @@ exports.preferNullishCoalescingBooleanProps = (0, createRule_1.createRule)({
|
|
|
412
412
|
// Check if this could benefit from nullish coalescing
|
|
413
413
|
// We only suggest nullish coalescing when the left operand could be nullish
|
|
414
414
|
if (couldBeNullish(node.left)) {
|
|
415
|
+
const sourceCode = context.getSourceCode();
|
|
416
|
+
const leftText = sourceCode.getText(node.left);
|
|
417
|
+
const rightText = sourceCode.getText(node.right);
|
|
415
418
|
context.report({
|
|
416
419
|
node,
|
|
417
420
|
messageId: 'preferNullishCoalescing',
|
|
421
|
+
data: {
|
|
422
|
+
left: leftText,
|
|
423
|
+
right: rightText,
|
|
424
|
+
},
|
|
418
425
|
fix(fixer) {
|
|
419
|
-
return fixer.replaceText(node, `${
|
|
420
|
-
.getSourceCode()
|
|
421
|
-
.getText(node.right)}`);
|
|
426
|
+
return fixer.replaceText(node, `${leftText} ?? ${rightText}`);
|
|
422
427
|
},
|
|
423
428
|
});
|
|
424
429
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
1
2
|
/**
|
|
2
3
|
* This rule overrides the behavior of @typescript-eslint/prefer-nullish-coalescing
|
|
3
4
|
* to only suggest using the nullish coalescing operator when checking for null/undefined,
|
|
4
5
|
* not when intentionally checking for all falsy values.
|
|
5
6
|
*/
|
|
6
|
-
export declare const preferNullishCoalescingOverride:
|
|
7
|
+
export declare const preferNullishCoalescingOverride: TSESLint.RuleModule<"preferNullishCoalescing", [], TSESLint.RuleListener>;
|
|
@@ -19,11 +19,13 @@ exports.preferNullishCoalescingOverride = (0, createRule_1.createRule)({
|
|
|
19
19
|
fixable: 'code',
|
|
20
20
|
schema: [],
|
|
21
21
|
messages: {
|
|
22
|
-
preferNullishCoalescing: '
|
|
22
|
+
preferNullishCoalescing: 'Replace "{{left}} || {{right}}" with nullish coalescing when you only want a fallback for null or undefined. The logical OR operator treats "", 0, and false as missing values and silently swaps in the fallback, which hides valid data. Use "{{left}} ?? {{right}}" so only nullish inputs trigger the fallback.',
|
|
23
23
|
},
|
|
24
24
|
},
|
|
25
25
|
defaultOptions: [],
|
|
26
26
|
create(context) {
|
|
27
|
+
const contextWithSource = context;
|
|
28
|
+
const sourceCode = contextWithSource.sourceCode ?? contextWithSource.getSourceCode();
|
|
27
29
|
/**
|
|
28
30
|
* Checks if a node is in a boolean context where truthiness matters
|
|
29
31
|
*/
|
|
@@ -154,7 +156,8 @@ exports.preferNullishCoalescingOverride = (0, createRule_1.createRule)({
|
|
|
154
156
|
/**
|
|
155
157
|
* Checks if this logical OR should be converted to nullish coalescing
|
|
156
158
|
*/
|
|
157
|
-
function shouldConvertToNullishCoalescing(
|
|
159
|
+
function shouldConvertToNullishCoalescing(node) {
|
|
160
|
+
void node;
|
|
158
161
|
return false;
|
|
159
162
|
}
|
|
160
163
|
void isInBooleanContext;
|
|
@@ -164,12 +167,15 @@ exports.preferNullishCoalescingOverride = (0, createRule_1.createRule)({
|
|
|
164
167
|
return {
|
|
165
168
|
LogicalExpression(node) {
|
|
166
169
|
if (shouldConvertToNullishCoalescing(node)) {
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
const rightText = sc.getText(node.right);
|
|
170
|
+
const leftText = sourceCode.getText(node.left);
|
|
171
|
+
const rightText = sourceCode.getText(node.right);
|
|
170
172
|
context.report({
|
|
171
173
|
node,
|
|
172
174
|
messageId: 'preferNullishCoalescing',
|
|
175
|
+
data: {
|
|
176
|
+
left: leftText,
|
|
177
|
+
right: rightText,
|
|
178
|
+
},
|
|
173
179
|
fix(fixer) {
|
|
174
180
|
return fixer.replaceText(node, `${leftText} ?? ${rightText}`);
|
|
175
181
|
},
|