@blumintinc/eslint-plugin-blumint 1.12.6 → 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 +171 -99
- package/lib/index.js +300 -17
- package/lib/rules/array-methods-this-context.js +20 -2
- package/lib/rules/avoid-utils-directory.js +11 -2
- 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.d.ts +3 -1
- package/lib/rules/dynamic-https-errors.js +48 -11
- 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} +52 -96
- 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 +820 -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 +5 -2
- package/lib/rules/enforce-console-error.d.ts +3 -0
- package/lib/rules/enforce-console-error.js +278 -0
- package/lib/rules/enforce-css-media-queries.js +24 -46
- 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-dynamic-firebase-imports.js +100 -19
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- 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 +4 -4
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +262 -0
- package/lib/rules/enforce-firestore-doc-ref-generic.js +350 -3
- package/lib/rules/enforce-firestore-facade.js +349 -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-firestore-set-merge.js +3 -4
- package/lib/rules/enforce-global-constants.d.ts +4 -1
- package/lib/rules/enforce-global-constants.js +240 -12
- package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
- package/lib/rules/enforce-memoize-async.d.ts +2 -1
- package/lib/rules/enforce-memoize-async.js +92 -27
- package/lib/rules/enforce-memoize-getters.d.ts +3 -0
- package/lib/rules/enforce-memoize-getters.js +147 -0
- package/lib/rules/enforce-microdiff.js +3 -2
- package/lib/rules/enforce-object-literal-as-const.js +4 -4
- package/lib/rules/enforce-positive-naming.js +9 -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-safe-stringify.js +1 -1
- package/lib/rules/enforce-serializable-params.js +3 -3
- package/lib/rules/enforce-singular-type-names.js +10 -6
- 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.d.ts +1 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +100 -0
- 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 +90 -14
- 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 +92 -53
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
- package/lib/rules/fast-deep-equal-over-microdiff.js +351 -83
- package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
- package/lib/rules/firestore-transaction-reads-before-writes.js +277 -0
- package/lib/rules/flatten-push-calls.d.ts +2 -0
- package/lib/rules/flatten-push-calls.js +428 -0
- package/lib/rules/generic-starts-with-t.js +8 -2
- 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/memoize-root-level-hocs.d.ts +7 -0
- package/lib/rules/memoize-root-level-hocs.js +242 -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.d.ts +12 -0
- package/lib/rules/no-array-length-in-deps.js +314 -0
- package/lib/rules/no-async-array-filter.js +5 -2
- 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 +22 -5
- package/lib/rules/no-compositing-layer-props.js +6 -4
- 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.d.ts +3 -1
- package/lib/rules/no-entire-object-hook-deps.js +320 -68
- 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-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 +398 -28
- 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 +14 -14
- 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.d.ts +7 -1
- package/lib/rules/no-margin-properties.js +22 -9
- package/lib/rules/no-memoize-on-static.js +9 -1
- package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
- package/lib/rules/no-misleading-boolean-prefixes.js +331 -0
- 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.d.ts +1 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.js +272 -0
- package/lib/rules/no-passthrough-getters.d.ts +4 -0
- package/lib/rules/no-passthrough-getters.js +206 -0
- 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.d.ts +8 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +328 -0
- 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.d.ts +12 -0
- package/lib/rules/no-restricted-properties-fix.js +173 -0
- package/lib/rules/no-separate-loading-state.d.ts +8 -0
- package/lib/rules/no-separate-loading-state.js +133 -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 +49 -49
- package/lib/rules/no-undefined-null-passthrough.js +217 -40
- 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 -3
- package/lib/rules/no-unpinned-dependencies.js +36 -5
- package/lib/rules/no-unsafe-firestore-spread.js +8 -5
- package/lib/rules/no-unused-props.d.ts +5 -2
- package/lib/rules/no-unused-props.js +451 -112
- 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.d.ts +1 -0
- package/lib/rules/optimize-object-boolean-conditions.js +236 -0
- package/lib/rules/parallelize-async-operations.d.ts +8 -0
- package/lib/rules/parallelize-async-operations.js +394 -0
- package/lib/rules/prefer-batch-operations.d.ts +1 -3
- package/lib/rules/prefer-batch-operations.js +228 -35
- package/lib/rules/prefer-block-comments-for-declarations.js +22 -7
- 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-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 +251 -0
- package/lib/rules/prefer-fragment-component.js +4 -4
- 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.d.ts +7 -3
- package/lib/rules/prefer-global-router-state-key.js +305 -31
- 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.d.ts +9 -0
- package/lib/rules/prefer-next-dynamic.js +357 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +435 -0
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +7 -0
- package/lib/rules/prefer-nullish-coalescing-override.js +188 -0
- package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
- package/lib/rules/prefer-params-over-parent-id.js +277 -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-type-over-interface.js +7 -2
- package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
- package/lib/rules/prefer-url-tostring-over-tojson.js +169 -0
- package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
- package/lib/rules/prefer-use-deep-compare-memo.js +431 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +25 -2
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
- 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.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-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 +2 -2
- package/lib/rules/require-memo.js +12 -2
- 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 +9 -4
- 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 +271 -0
- 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 +34 -0
- package/lib/utils/createRule.js +1 -1
- package/lib/utils/getMethodName.d.ts +27 -0
- package/lib/utils/getMethodName.js +35 -0
- package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
- package/lib/utils/ruleTester.d.ts +1 -0
- package/lib/utils/ruleTester.js +4 -1
- package/lib/utils/tsTypeClassifier.d.ts +30 -0
- package/lib/utils/tsTypeClassifier.js +149 -0
- package/package.json +23 -14
- package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preferNextDynamic = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const DEFAULT_USE_DYNAMIC_SOURCES = [
|
|
7
|
+
'useDynamic',
|
|
8
|
+
'./useDynamic',
|
|
9
|
+
'../hooks/useDynamic',
|
|
10
|
+
'../../hooks/useDynamic',
|
|
11
|
+
];
|
|
12
|
+
function matchesAllowedSource(source, allowedSources) {
|
|
13
|
+
return allowedSources.some((pattern) => source === pattern || source.endsWith(`/${pattern}`));
|
|
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
|
+
}
|
|
56
|
+
function isUseDynamicCall(node) {
|
|
57
|
+
const { callee, arguments: args } = node;
|
|
58
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
59
|
+
callee.name === 'useDynamic' &&
|
|
60
|
+
args.length === 1 &&
|
|
61
|
+
args[0].type === utils_1.AST_NODE_TYPES.ImportExpression) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
function findProgramNode(node) {
|
|
67
|
+
let current = node;
|
|
68
|
+
while (current && current.type !== utils_1.AST_NODE_TYPES.Program) {
|
|
69
|
+
current = current.parent;
|
|
70
|
+
}
|
|
71
|
+
return current || null;
|
|
72
|
+
}
|
|
73
|
+
function getImportDeclarations(program) {
|
|
74
|
+
return program.body.filter((n) => n.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
|
|
75
|
+
}
|
|
76
|
+
function findUseDynamicImport(program, allowedSources) {
|
|
77
|
+
const imports = getImportDeclarations(program);
|
|
78
|
+
for (const imp of imports) {
|
|
79
|
+
if (typeof imp.source.value !== 'string')
|
|
80
|
+
continue;
|
|
81
|
+
const source = imp.source.value;
|
|
82
|
+
if (!matchesAllowedSource(source, allowedSources))
|
|
83
|
+
continue;
|
|
84
|
+
const importedUseDynamic = imp.specifiers.find((s) => (s.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
85
|
+
s.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
86
|
+
s.imported.name === 'useDynamic') ||
|
|
87
|
+
s.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier ||
|
|
88
|
+
(s.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
89
|
+
s.local.name === 'useDynamic'));
|
|
90
|
+
if (importedUseDynamic && importedUseDynamic.local.name === 'useDynamic') {
|
|
91
|
+
return {
|
|
92
|
+
importNode: imp,
|
|
93
|
+
specifier: importedUseDynamic,
|
|
94
|
+
localName: importedUseDynamic.local.name,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
function getNextDynamicLocalName(program) {
|
|
101
|
+
for (const imp of getImportDeclarations(program)) {
|
|
102
|
+
if (imp.source.value === 'next/dynamic') {
|
|
103
|
+
const def = imp.specifiers.find((s) => s.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier);
|
|
104
|
+
if (def)
|
|
105
|
+
return def.local.name;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function buildDynamicReplacement(call, variableKind, variableIdText, namedExportKey, sourceCode, dynamicIdent) {
|
|
111
|
+
const expr = buildDynamicExpression(call, namedExportKey, sourceCode, dynamicIdent);
|
|
112
|
+
return `${variableKind} ${variableIdText} = ${expr};`;
|
|
113
|
+
}
|
|
114
|
+
function buildDynamicExpression(call, namedExportKey, sourceCode, dynamicIdent) {
|
|
115
|
+
// call.arguments[0] is ImportExpression
|
|
116
|
+
const importExpr = call.arguments[0];
|
|
117
|
+
const importArgText = sourceCode.getText(importExpr.source);
|
|
118
|
+
const returnExpr = namedExportKey ? `mod.${namedExportKey}` : 'mod.default';
|
|
119
|
+
const dynamicText = `${dynamicIdent}(
|
|
120
|
+
async () => {
|
|
121
|
+
const mod = await import(${importArgText});
|
|
122
|
+
return ${returnExpr};
|
|
123
|
+
},
|
|
124
|
+
{ ssr: false }
|
|
125
|
+
)`;
|
|
126
|
+
return dynamicText;
|
|
127
|
+
}
|
|
128
|
+
function inferVariableInfo(node) {
|
|
129
|
+
// Support: const Foo = useDynamic(import('...'));
|
|
130
|
+
// or: const { Picker } = useDynamic(import('...'));
|
|
131
|
+
const decl = node;
|
|
132
|
+
if (!decl.init || decl.init.type !== utils_1.AST_NODE_TYPES.CallExpression) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
if (!isUseDynamicCall(decl.init))
|
|
136
|
+
return null;
|
|
137
|
+
let kind = 'const';
|
|
138
|
+
const parent = decl.parent;
|
|
139
|
+
if (parent &&
|
|
140
|
+
parent.type === utils_1.AST_NODE_TYPES.VariableDeclaration &&
|
|
141
|
+
(parent.kind === 'const' || parent.kind === 'let' || parent.kind === 'var')) {
|
|
142
|
+
kind = parent.kind;
|
|
143
|
+
}
|
|
144
|
+
if (decl.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
145
|
+
return { kind, idText: decl.id.name, namedExportKey: null };
|
|
146
|
+
}
|
|
147
|
+
if (decl.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
148
|
+
// Only handle a single-property destructure like { Picker }
|
|
149
|
+
if (decl.id.properties.length !== 1) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const prop = decl.id.properties[0];
|
|
153
|
+
if (prop && prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
154
|
+
const key = prop.key.type === utils_1.AST_NODE_TYPES.Identifier ? prop.key.name : null;
|
|
155
|
+
const valueName = prop.value.type === utils_1.AST_NODE_TYPES.Identifier ? prop.value.name : null;
|
|
156
|
+
if (key && valueName) {
|
|
157
|
+
return { kind, idText: valueName, namedExportKey: key };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
exports.preferNextDynamic = (0, createRule_1.createRule)({
|
|
164
|
+
name: 'prefer-next-dynamic',
|
|
165
|
+
meta: {
|
|
166
|
+
type: 'problem',
|
|
167
|
+
docs: {
|
|
168
|
+
description: 'Prefer Next.js dynamic() over custom useDynamic() for component imports',
|
|
169
|
+
recommended: 'error',
|
|
170
|
+
requiresTypeChecking: false,
|
|
171
|
+
},
|
|
172
|
+
fixable: 'code',
|
|
173
|
+
schema: [
|
|
174
|
+
{
|
|
175
|
+
type: 'object',
|
|
176
|
+
properties: {
|
|
177
|
+
useDynamicSources: {
|
|
178
|
+
type: 'array',
|
|
179
|
+
items: { type: 'string' },
|
|
180
|
+
minItems: 1,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
additionalProperties: false,
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
messages: {
|
|
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.',
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
defaultOptions: [{}],
|
|
193
|
+
create(context) {
|
|
194
|
+
const [options = {}] = context.options;
|
|
195
|
+
const allowedUseDynamicSources = options.useDynamicSources && options.useDynamicSources.length > 0
|
|
196
|
+
? options.useDynamicSources
|
|
197
|
+
: DEFAULT_USE_DYNAMIC_SOURCES;
|
|
198
|
+
const sourceCode = context.sourceCode;
|
|
199
|
+
return {
|
|
200
|
+
VariableDeclarator(node) {
|
|
201
|
+
const info = inferVariableInfo(node);
|
|
202
|
+
if (!info)
|
|
203
|
+
return;
|
|
204
|
+
// For edge case 1 (Non-Component Imports): we conservatively only transform when the LHS is used in JSX.
|
|
205
|
+
// Heuristic: if identifier appears in any JSXOpeningElement as name, consider a component.
|
|
206
|
+
const program = findProgramNode(node);
|
|
207
|
+
if (!program)
|
|
208
|
+
return;
|
|
209
|
+
const useDynamicImportInfo = findUseDynamicImport(program, allowedUseDynamicSources);
|
|
210
|
+
if (!useDynamicImportInfo)
|
|
211
|
+
return;
|
|
212
|
+
const identifierName = info.idText;
|
|
213
|
+
// walkAst returns false when visitor returns false (early exit on match)
|
|
214
|
+
const usedInJsx = program.body.some((b) => walkAst(b, (n) => {
|
|
215
|
+
if (n.type === utils_1.AST_NODE_TYPES.JSXOpeningElement) {
|
|
216
|
+
const name = n.name;
|
|
217
|
+
if (name.type === utils_1.AST_NODE_TYPES.JSXIdentifier) {
|
|
218
|
+
if (name.name === identifierName) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return undefined;
|
|
224
|
+
}) === false);
|
|
225
|
+
if (!usedInJsx) {
|
|
226
|
+
// Skip to avoid flagging non-component dynamic imports
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
// Now we are confident enough to report and fix
|
|
230
|
+
const init = node.init;
|
|
231
|
+
const parentDecl = node.parent;
|
|
232
|
+
context.report({
|
|
233
|
+
node: init,
|
|
234
|
+
messageId: 'preferNextDynamic',
|
|
235
|
+
data: { componentName: identifierName },
|
|
236
|
+
fix(fixer) {
|
|
237
|
+
const fixes = [];
|
|
238
|
+
// ensure dynamic import is present
|
|
239
|
+
const programNode = program;
|
|
240
|
+
let dynamicLocal = getNextDynamicLocalName(programNode);
|
|
241
|
+
const hasDynamic = !!dynamicLocal;
|
|
242
|
+
if (!hasDynamic) {
|
|
243
|
+
// Insert after directive prologue (e.g., "use client")
|
|
244
|
+
const insertionIndex = programNode.body.findIndex((stmt) => {
|
|
245
|
+
return !(stmt.type === utils_1.AST_NODE_TYPES.ExpressionStatement &&
|
|
246
|
+
stmt.expression.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
247
|
+
typeof stmt.expression.value === 'string');
|
|
248
|
+
});
|
|
249
|
+
const target = insertionIndex === -1
|
|
250
|
+
? programNode.body[0]
|
|
251
|
+
: programNode.body[insertionIndex];
|
|
252
|
+
const indentation = '';
|
|
253
|
+
fixes.push(fixer.insertTextBefore(target, `${indentation}import dynamic from 'next/dynamic';\n`));
|
|
254
|
+
dynamicLocal = 'dynamic';
|
|
255
|
+
}
|
|
256
|
+
// Replace the variable declarator text with dynamic(...) usage
|
|
257
|
+
if (parentDecl.declarations.length === 1) {
|
|
258
|
+
const variableText = buildDynamicReplacement(init, parentDecl.kind, info.idText, info.namedExportKey, sourceCode, dynamicLocal || 'dynamic');
|
|
259
|
+
fixes.push(fixer.replaceText(parentDecl, variableText));
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
// Multiple declarators:
|
|
263
|
+
if (node.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
264
|
+
// Replace only the initializer expression
|
|
265
|
+
const dynamicExpr = buildDynamicExpression(init, info.namedExportKey, sourceCode, dynamicLocal || 'dynamic');
|
|
266
|
+
fixes.push(fixer.replaceText(init, dynamicExpr));
|
|
267
|
+
}
|
|
268
|
+
else if (node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
269
|
+
// Replace the whole declarator with "<localName> = dynamic(...)"
|
|
270
|
+
const dynamicExpr = buildDynamicExpression(init, info.namedExportKey, sourceCode, dynamicLocal || 'dynamic');
|
|
271
|
+
const replacement = `${info.idText} = ${dynamicExpr}`;
|
|
272
|
+
fixes.push(fixer.replaceText(node, replacement));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Remove unused useDynamic import if present and no longer referenced
|
|
276
|
+
const latestUseDynamicImport = findUseDynamicImport(programNode, allowedUseDynamicSources);
|
|
277
|
+
if (latestUseDynamicImport) {
|
|
278
|
+
// Abort removal if there are other useDynamic(import(...)) calls in the file
|
|
279
|
+
const otherUseDynamicCalls = programNode.body.some((b) => walkAst(b, (n) => {
|
|
280
|
+
if (n.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
281
|
+
isUseDynamicCall(n) &&
|
|
282
|
+
n !== init) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
return undefined;
|
|
286
|
+
}) === false);
|
|
287
|
+
if (otherUseDynamicCalls) {
|
|
288
|
+
return fixes; // keep the import; other occurrences still rely on it
|
|
289
|
+
}
|
|
290
|
+
// If import had only useDynamic, remove entire declaration; else remove just its specifier
|
|
291
|
+
const specifiers = latestUseDynamicImport.importNode.specifiers;
|
|
292
|
+
const useDynamicSpecifier = specifiers.find((s) => (s.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
293
|
+
s.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
294
|
+
s.imported.name === 'useDynamic') ||
|
|
295
|
+
(s.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier &&
|
|
296
|
+
s.local.name === 'useDynamic') ||
|
|
297
|
+
(s.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
298
|
+
s.local.name === 'useDynamic'));
|
|
299
|
+
if (useDynamicSpecifier) {
|
|
300
|
+
if (specifiers.length === 1) {
|
|
301
|
+
// Remove entire import
|
|
302
|
+
fixes.push(fixer.remove(latestUseDynamicImport.importNode));
|
|
303
|
+
// If dynamic was already present (we didn't insert), collapse the extra newline left by removal
|
|
304
|
+
if (hasDynamic) {
|
|
305
|
+
const after = latestUseDynamicImport.importNode.range[1];
|
|
306
|
+
if (after < sourceCode.text.length) {
|
|
307
|
+
const ch = sourceCode.text[after];
|
|
308
|
+
if (ch === '\n' || ch === '\r') {
|
|
309
|
+
fixes.push(fixer.removeRange([after, after + 1]));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
// If only named specifiers exist, reconstruct a clean import text
|
|
316
|
+
const onlyNamed = specifiers.every((s) => s.type === utils_1.AST_NODE_TYPES.ImportSpecifier);
|
|
317
|
+
if (onlyNamed) {
|
|
318
|
+
const remaining = specifiers.filter((s) => s !== useDynamicSpecifier);
|
|
319
|
+
const specText = remaining
|
|
320
|
+
.map((s) => s.imported.name === s.local.name
|
|
321
|
+
? s.local.name
|
|
322
|
+
: `${s.imported.name} as ${s.local.name}`)
|
|
323
|
+
.join(', ');
|
|
324
|
+
const newText = `import { ${specText} } from '${latestUseDynamicImport.importNode.source.value}';`;
|
|
325
|
+
fixes.push(fixer.replaceText(latestUseDynamicImport.importNode, newText));
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
// Otherwise, remove the specifier with proper comma handling
|
|
329
|
+
const tokenAfter = sourceCode.getTokenAfter(useDynamicSpecifier);
|
|
330
|
+
const tokenBefore = sourceCode.getTokenBefore(useDynamicSpecifier);
|
|
331
|
+
if (tokenAfter && tokenAfter.value === ',') {
|
|
332
|
+
fixes.push(fixer.removeRange([
|
|
333
|
+
useDynamicSpecifier.range[0],
|
|
334
|
+
tokenAfter.range[1],
|
|
335
|
+
]));
|
|
336
|
+
}
|
|
337
|
+
else if (tokenBefore && tokenBefore.value === ',') {
|
|
338
|
+
fixes.push(fixer.removeRange([
|
|
339
|
+
tokenBefore.range[0],
|
|
340
|
+
useDynamicSpecifier.range[1],
|
|
341
|
+
]));
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
fixes.push(fixer.remove(useDynamicSpecifier));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return fixes;
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
//# sourceMappingURL=prefer-next-dynamic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const preferNullishCoalescingBooleanProps: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"preferNullishCoalescing", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|