@blumintinc/eslint-plugin-blumint 1.13.0 → 1.15.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 +169 -139
- package/lib/index.js +200 -9
- package/lib/rules/array-methods-this-context.js +1 -1
- package/lib/rules/avoid-utils-directory.js +0 -4
- package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
- package/lib/rules/consistent-callback-naming.js +43 -4
- package/lib/rules/dynamic-https-errors.d.ts +1 -1
- package/lib/rules/dynamic-https-errors.js +134 -44
- package/lib/rules/enforce-assert-safe-object-key.js +11 -11
- package/lib/rules/enforce-boolean-naming-prefixes.js +11 -235
- 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-date-ttime.d.ts +1 -0
- package/lib/rules/enforce-date-ttime.js +156 -0
- 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.d.ts +2 -1
- package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
- 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-f-extension-for-entry-points.d.ts +8 -0
- package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
- 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 +4 -4
- package/lib/rules/enforce-id-capitalization.js +1 -1
- package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
- package/lib/rules/enforce-memoize-async.js +71 -21
- 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 +44 -18
- 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 +3818 -4644
- 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 +66 -23
- 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 +801 -0
- package/lib/rules/memo-nested-react-components.d.ts +8 -0
- package/lib/rules/memo-nested-react-components.js +415 -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 +215 -12
- package/lib/rules/no-circular-references.d.ts +2 -1
- package/lib/rules/no-circular-references.js +31 -32
- 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 +527 -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 +52 -7
- package/lib/rules/no-excessive-parent-chain.js +4 -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 +465 -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.d.ts +2 -2
- package/lib/rules/no-passthrough-getters.js +120 -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 +508 -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 +344 -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 +143 -59
- 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 +118 -8
- 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 +221 -172
- package/lib/rules/prefer-settings-object.js +27 -10
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +9 -0
- 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-usememo-over-useeffect-usestate.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 +540 -0
- package/lib/rules/react-memoize-literals.d.ts +4 -0
- package/lib/rules/react-memoize-literals.js +614 -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 +155 -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 +18 -10
- 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 +47 -2
- package/lib/utils/ASTHelpers.js +370 -112
- 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
|
@@ -1,22 +1,225 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.noAsyncForEach = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const getNodeStart = (node) => node?.range?.[0] ?? Number.POSITIVE_INFINITY;
|
|
6
|
+
const getFunctionDescription = (node, fallbackName) => {
|
|
7
|
+
const declaredName = (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
8
|
+
node.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
|
|
9
|
+
node.id?.name
|
|
10
|
+
? node.id.name
|
|
11
|
+
: null;
|
|
12
|
+
const isArrowFunction = node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression;
|
|
13
|
+
const functionName = declaredName ?? (isArrowFunction ? undefined : fallbackName);
|
|
14
|
+
if (functionName) {
|
|
15
|
+
return `function "${functionName}"`;
|
|
16
|
+
}
|
|
17
|
+
if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
18
|
+
return fallbackName ? `arrow function "${fallbackName}"` : 'arrow function';
|
|
19
|
+
}
|
|
20
|
+
return 'function expression';
|
|
21
|
+
};
|
|
22
|
+
const findVariableInScope = (scope, name) => {
|
|
23
|
+
let currentScope = scope;
|
|
24
|
+
while (currentScope) {
|
|
25
|
+
const variable = currentScope.set.get(name);
|
|
26
|
+
if (variable) {
|
|
27
|
+
return variable;
|
|
28
|
+
}
|
|
29
|
+
currentScope = currentScope.upper;
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Function declarations are hoisted across their scope regardless of source order.
|
|
35
|
+
* Returning NEGATIVE_INFINITY makes hoisted declarations always "earlier" than any
|
|
36
|
+
* callback usage when we compare source positions.
|
|
37
|
+
*/
|
|
38
|
+
const getDefinitionStart = (definition) => definition.node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration
|
|
39
|
+
? Number.NEGATIVE_INFINITY
|
|
40
|
+
: getNodeStart(definition.node);
|
|
41
|
+
const getReferenceStart = (reference) => getNodeStart(reference.identifier);
|
|
42
|
+
const isWriteReference = (reference) => typeof reference.isWrite === 'function'
|
|
43
|
+
? reference.isWrite()
|
|
44
|
+
: Boolean(reference.isWrite);
|
|
45
|
+
const isAsyncFunctionExpression = (node) => {
|
|
46
|
+
if (!node || typeof node !== 'object') {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const typedNode = node;
|
|
50
|
+
return ((typedNode.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
51
|
+
typedNode.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
|
|
52
|
+
typedNode.async === true);
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Checks if context has direct sourceCode property
|
|
56
|
+
*/
|
|
57
|
+
const hasSourceCodeProperty = (context) => {
|
|
58
|
+
return 'sourceCode' in context && !!context.sourceCode;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Checks if context has getSourceCode method
|
|
62
|
+
*/
|
|
63
|
+
const hasGetSourceCodeMethod = (context) => {
|
|
64
|
+
return ('getSourceCode' in context && typeof context.getSourceCode === 'function');
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Retrieves source code from an ESLint rule context
|
|
68
|
+
*/
|
|
69
|
+
const getSourceCode = (context) => {
|
|
70
|
+
if (hasSourceCodeProperty(context)) {
|
|
71
|
+
return context.sourceCode;
|
|
72
|
+
}
|
|
73
|
+
if (hasGetSourceCodeMethod(context)) {
|
|
74
|
+
return context.getSourceCode();
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`Unable to retrieve source code from context in rule "no-async-foreach". ` +
|
|
77
|
+
`File: ${context.filename ??
|
|
78
|
+
context.getFilename?.() ??
|
|
79
|
+
'unknown'}. ` +
|
|
80
|
+
`Available properties: sourceCode=${typeof context
|
|
81
|
+
.sourceCode}, ` +
|
|
82
|
+
`getSourceCode=${typeof context.getSourceCode}.`);
|
|
83
|
+
};
|
|
84
|
+
const getScope = (context, sourceCode, node) => {
|
|
85
|
+
try {
|
|
86
|
+
const typedSourceCode = sourceCode;
|
|
87
|
+
return typedSourceCode.getScope?.(node) ?? context.getScope?.() ?? null;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return context.getScope?.() ?? null;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const analyzeInlineCallback = (callback) => {
|
|
94
|
+
if (!callback.async) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
callbackLabel: getFunctionDescription(callback),
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
const getAsyncFunctionDeclarationInfo = (definition, callbackName) => {
|
|
102
|
+
if (definition.node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration &&
|
|
103
|
+
definition.node.async) {
|
|
104
|
+
return {
|
|
105
|
+
callbackLabel: getFunctionDescription(definition.node, definition.node.id?.name ?? callbackName),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
};
|
|
110
|
+
const getAsyncVariableDeclaratorInfo = (definition, callbackName) => {
|
|
111
|
+
if (definition.node.type !== utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const initializerExpression = definition.node.init;
|
|
115
|
+
if (!isAsyncFunctionExpression(initializerExpression)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const name = (definition.node.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
119
|
+
definition.node.id.name) ||
|
|
120
|
+
callbackName;
|
|
121
|
+
return {
|
|
122
|
+
callbackLabel: getFunctionDescription(initializerExpression, name),
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
const analyzeVariableDefinition = (definition, callbackName) => getAsyncFunctionDeclarationInfo(definition, callbackName) ??
|
|
126
|
+
getAsyncVariableDeclaratorInfo(definition, callbackName);
|
|
127
|
+
const getReferenceWriteExpression = (reference) => {
|
|
128
|
+
const parent = reference.identifier.parent;
|
|
129
|
+
if (reference.writeExpr) {
|
|
130
|
+
return reference.writeExpr;
|
|
131
|
+
}
|
|
132
|
+
if (parent?.type === utils_1.AST_NODE_TYPES.AssignmentExpression) {
|
|
133
|
+
return parent.right;
|
|
134
|
+
}
|
|
135
|
+
if (parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator && parent.init) {
|
|
136
|
+
return parent.init;
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
};
|
|
140
|
+
const analyzeVariableReference = (reference) => {
|
|
141
|
+
if (!isWriteReference(reference)) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
const writeExpr = getReferenceWriteExpression(reference);
|
|
145
|
+
if (!isAsyncFunctionExpression(writeExpr)) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const name = (writeExpr.type === utils_1.AST_NODE_TYPES.FunctionExpression &&
|
|
149
|
+
writeExpr.id?.name) ||
|
|
150
|
+
reference.identifier.name;
|
|
151
|
+
return {
|
|
152
|
+
callbackLabel: getFunctionDescription(writeExpr, name),
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
const analyzeCallbackAsyncStatus = (callback, scope) => {
|
|
156
|
+
if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
157
|
+
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
158
|
+
return analyzeInlineCallback(callback);
|
|
159
|
+
}
|
|
160
|
+
if (callback.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
const variable = findVariableInScope(scope, callback.name);
|
|
164
|
+
if (!variable) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Track every write to the callback identifier with its source position. Only
|
|
169
|
+
* the last write at or before the callback location determines whether the
|
|
170
|
+
* callback is async when it is passed to forEach. If the callback location is
|
|
171
|
+
* unknown, bail out to avoid blaming writes that might occur after the call
|
|
172
|
+
* (e.g., a later reassignment to async that should not retroactively flag an
|
|
173
|
+
* earlier forEach use).
|
|
174
|
+
*/
|
|
175
|
+
const callbackStart = callback.range?.[0];
|
|
176
|
+
const writes = [];
|
|
177
|
+
for (const definition of variable.defs) {
|
|
178
|
+
const definitionResult = analyzeVariableDefinition(definition, callback.name);
|
|
179
|
+
writes.push({
|
|
180
|
+
start: getDefinitionStart(definition),
|
|
181
|
+
isAsync: Boolean(definitionResult),
|
|
182
|
+
info: definitionResult,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
for (const reference of variable.references) {
|
|
186
|
+
if (!isWriteReference(reference)) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const referenceResult = analyzeVariableReference(reference);
|
|
190
|
+
writes.push({
|
|
191
|
+
start: getReferenceStart(reference),
|
|
192
|
+
isAsync: Boolean(referenceResult),
|
|
193
|
+
info: referenceResult,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const relevantWrites = writes.filter(({ start }) => typeof callbackStart === 'number' && start <= callbackStart);
|
|
197
|
+
if (!relevantWrites.length) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
const lastWrite = relevantWrites.reduce((latest, current) => !latest || current.start > latest.start ? current : latest, null);
|
|
201
|
+
return lastWrite && lastWrite.isAsync ? lastWrite.info : null;
|
|
202
|
+
};
|
|
4
203
|
exports.noAsyncForEach = {
|
|
5
204
|
create(context) {
|
|
205
|
+
const sourceCode = getSourceCode(context);
|
|
6
206
|
return {
|
|
7
207
|
CallExpression(node) {
|
|
8
208
|
const callee = node.callee;
|
|
9
|
-
|
|
10
|
-
|
|
209
|
+
const callback = node.arguments[0];
|
|
210
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
211
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
11
212
|
callee.property.name === 'forEach' &&
|
|
12
|
-
|
|
13
|
-
(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
213
|
+
callback) {
|
|
214
|
+
const scope = getScope(context, sourceCode, callback);
|
|
215
|
+
const asyncCallbackInfo = analyzeCallbackAsyncStatus(callback, scope);
|
|
216
|
+
if (asyncCallbackInfo) {
|
|
217
|
+
context.report({
|
|
218
|
+
node: callback,
|
|
219
|
+
messageId: 'noAsyncForEach',
|
|
220
|
+
data: asyncCallbackInfo,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
20
223
|
}
|
|
21
224
|
},
|
|
22
225
|
};
|
|
@@ -24,11 +227,11 @@ exports.noAsyncForEach = {
|
|
|
24
227
|
meta: {
|
|
25
228
|
type: 'problem',
|
|
26
229
|
docs: {
|
|
27
|
-
description: 'Disallow
|
|
230
|
+
description: 'Disallow async callbacks to Array.forEach',
|
|
28
231
|
recommended: 'error',
|
|
29
232
|
},
|
|
30
233
|
messages: {
|
|
31
|
-
noAsyncForEach: '
|
|
234
|
+
noAsyncForEach: 'Async {{callbackLabel}} passed to Array.forEach runs without awaiting each item. Array.forEach ignores returned promises, so async work executes in parallel and rejections go unhandled. Use a for...of loop to await sequentially or map with Promise.all when you want controlled concurrency.',
|
|
32
235
|
},
|
|
33
236
|
schema: [],
|
|
34
237
|
},
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const noCircularReferences: TSESLint.RuleModule<"circularReference", [], TSESLint.RuleListener>;
|
|
@@ -16,11 +16,21 @@ exports.noCircularReferences = (0, createRule_1.createRule)({
|
|
|
16
16
|
},
|
|
17
17
|
schema: [],
|
|
18
18
|
messages: {
|
|
19
|
-
circularReference: '
|
|
19
|
+
circularReference: 'Reference "{{referenceText}}" makes this object point back to itself (directly or through other objects). Circular object graphs throw in `JSON.stringify()` and keep members reachable longer, which causes memory leaks and unexpected mutations. Store a copy or a serialize-safe identifier instead of the original object when assigning.',
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
22
|
defaultOptions: [],
|
|
23
23
|
create(context) {
|
|
24
|
+
const sourceCode = context.getSourceCode();
|
|
25
|
+
function reportCircularReference(node, reference) {
|
|
26
|
+
context.report({
|
|
27
|
+
node,
|
|
28
|
+
messageId: 'circularReference',
|
|
29
|
+
data: {
|
|
30
|
+
referenceText: sourceCode.getText(reference),
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
24
34
|
function isObjectExpression(node) {
|
|
25
35
|
return node.type === utils_1.AST_NODE_TYPES.ObjectExpression;
|
|
26
36
|
}
|
|
@@ -97,11 +107,20 @@ exports.noCircularReferences = (0, createRule_1.createRule)({
|
|
|
97
107
|
node.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
98
108
|
node.property.name === 'method'));
|
|
99
109
|
}
|
|
110
|
+
function getVariable(name) {
|
|
111
|
+
let scope = context.getScope();
|
|
112
|
+
while (scope) {
|
|
113
|
+
const variable = scope.variables.find((v) => v.name === name);
|
|
114
|
+
if (variable) {
|
|
115
|
+
return variable;
|
|
116
|
+
}
|
|
117
|
+
scope = scope.upper;
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
100
121
|
function getReferencedObject(node) {
|
|
101
122
|
if (isIdentifier(node)) {
|
|
102
|
-
const
|
|
103
|
-
const scopeId = getScopeId(scope);
|
|
104
|
-
const variable = scope.variables.find((v) => v.name === node.name);
|
|
123
|
+
const variable = getVariable(node.name);
|
|
105
124
|
if (variable?.defs[0]?.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
106
125
|
const init = variable.defs[0].node.init;
|
|
107
126
|
if (init) {
|
|
@@ -117,24 +136,13 @@ exports.noCircularReferences = (0, createRule_1.createRule)({
|
|
|
117
136
|
}
|
|
118
137
|
}
|
|
119
138
|
}
|
|
120
|
-
// Check objects in the current scope
|
|
121
|
-
const scopeObjects = scopeMap.get(scopeId);
|
|
122
|
-
if (scopeObjects) {
|
|
123
|
-
for (const obj of scopeObjects) {
|
|
124
|
-
const info = objectMap.get(obj);
|
|
125
|
-
if (info && info.scope === scopeId && !info.isCircular) {
|
|
126
|
-
return obj;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
139
|
}
|
|
131
140
|
else if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
132
141
|
const property = node.property;
|
|
133
142
|
if (property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
134
143
|
const object = node.object;
|
|
135
144
|
if (isIdentifier(object)) {
|
|
136
|
-
const
|
|
137
|
-
const variable = scope.variables.find((v) => v.name === object.name);
|
|
145
|
+
const variable = getVariable(object.name);
|
|
138
146
|
if (variable?.defs[0]?.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
139
147
|
const init = variable.defs[0].node.init;
|
|
140
148
|
if (init) {
|
|
@@ -207,10 +215,7 @@ exports.noCircularReferences = (0, createRule_1.createRule)({
|
|
|
207
215
|
if (targetInfo) {
|
|
208
216
|
targetInfo.references.add(reference);
|
|
209
217
|
if (detectCircularReference(targetObj)) {
|
|
210
|
-
|
|
211
|
-
node: reference,
|
|
212
|
-
messageId: 'circularReference',
|
|
213
|
-
});
|
|
218
|
+
reportCircularReference(reference, reference);
|
|
214
219
|
}
|
|
215
220
|
}
|
|
216
221
|
}
|
|
@@ -502,10 +507,8 @@ exports.noCircularReferences = (0, createRule_1.createRule)({
|
|
|
502
507
|
if (isIdentifier(leftObj)) {
|
|
503
508
|
const leftObjRef = getReferencedObject(leftObj);
|
|
504
509
|
if (leftObjRef === referencedObj) {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
messageId: 'circularReference',
|
|
508
|
-
});
|
|
510
|
+
const reference = assignment.right;
|
|
511
|
+
reportCircularReference(assignment, reference);
|
|
509
512
|
}
|
|
510
513
|
}
|
|
511
514
|
}
|
|
@@ -523,10 +526,8 @@ exports.noCircularReferences = (0, createRule_1.createRule)({
|
|
|
523
526
|
if (isIdentifier(leftObj)) {
|
|
524
527
|
const leftObjRef = getReferencedObject(leftObj);
|
|
525
528
|
if (leftObjRef === referencedObj) {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
messageId: 'circularReference',
|
|
529
|
-
});
|
|
529
|
+
const reference = assignment.right;
|
|
530
|
+
reportCircularReference(assignment, reference);
|
|
530
531
|
}
|
|
531
532
|
}
|
|
532
533
|
}
|
|
@@ -568,10 +569,8 @@ exports.noCircularReferences = (0, createRule_1.createRule)({
|
|
|
568
569
|
parent.right === node) {
|
|
569
570
|
const leftObj = getObjectFromMemberExpression(parent.left);
|
|
570
571
|
if (leftObj === referencedObj) {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
messageId: 'circularReference',
|
|
574
|
-
});
|
|
572
|
+
const reference = node;
|
|
573
|
+
reportCircularReference(node, reference);
|
|
575
574
|
}
|
|
576
575
|
}
|
|
577
576
|
}
|
|
@@ -14,11 +14,55 @@ exports.noClassInstanceDestructuring = (0, createRule_1.createRule)({
|
|
|
14
14
|
fixable: 'code',
|
|
15
15
|
schema: [],
|
|
16
16
|
messages: {
|
|
17
|
-
noClassInstanceDestructuring:
|
|
17
|
+
noClassInstanceDestructuring: [
|
|
18
|
+
"What's wrong: Destructuring {{members}} from class instance {{instance}} detaches those members from the instance.",
|
|
19
|
+
'Why it matters: Methods can run with the wrong `this`, and getters become one-time snapshots that go stale when the instance changes.',
|
|
20
|
+
'How to fix: Access through the instance instead (for example, {{suggestion}}) and bind when you need to pass a method around.',
|
|
21
|
+
].join('\n'),
|
|
18
22
|
},
|
|
19
23
|
},
|
|
20
24
|
defaultOptions: [],
|
|
21
25
|
create(context) {
|
|
26
|
+
const sourceCode = context.getSourceCode();
|
|
27
|
+
function describeMember(prop) {
|
|
28
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
29
|
+
if (prop.computed) {
|
|
30
|
+
const keyText = sourceCode.getText(prop.key);
|
|
31
|
+
return `[${keyText}]`;
|
|
32
|
+
}
|
|
33
|
+
if (prop.key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
34
|
+
return prop.key.name;
|
|
35
|
+
}
|
|
36
|
+
return sourceCode.getText(prop.key);
|
|
37
|
+
}
|
|
38
|
+
if (prop.type === utils_1.AST_NODE_TYPES.RestElement &&
|
|
39
|
+
prop.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
40
|
+
return `...${prop.argument.name}`;
|
|
41
|
+
}
|
|
42
|
+
return 'member';
|
|
43
|
+
}
|
|
44
|
+
function buildAccessPath(initText, prop) {
|
|
45
|
+
const keyText = sourceCode.getText(prop.key);
|
|
46
|
+
if (prop.key.type === utils_1.AST_NODE_TYPES.Identifier && !prop.computed) {
|
|
47
|
+
return `${initText}.${keyText}`;
|
|
48
|
+
}
|
|
49
|
+
return `${initText}[${keyText}]`;
|
|
50
|
+
}
|
|
51
|
+
function formatMembers(properties) {
|
|
52
|
+
const memberNames = properties.map(describeMember).filter(Boolean);
|
|
53
|
+
if (memberNames.length === 0)
|
|
54
|
+
return '`<members>`';
|
|
55
|
+
return memberNames.map((name) => `\`${name}\``).join(', ');
|
|
56
|
+
}
|
|
57
|
+
function formatAccessExamples(properties, initText) {
|
|
58
|
+
const accessPaths = properties
|
|
59
|
+
.filter((prop) => prop.type === utils_1.AST_NODE_TYPES.Property)
|
|
60
|
+
.map((prop) => buildAccessPath(initText, prop));
|
|
61
|
+
if (accessPaths.length === 0) {
|
|
62
|
+
return `\`${initText}.<member>\``;
|
|
63
|
+
}
|
|
64
|
+
return accessPaths.map((path) => `\`${path}\``).join(', ');
|
|
65
|
+
}
|
|
22
66
|
function isClassInstance(node) {
|
|
23
67
|
// Check for new expressions
|
|
24
68
|
if (node.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
@@ -43,11 +87,16 @@ exports.noClassInstanceDestructuring = (0, createRule_1.createRule)({
|
|
|
43
87
|
node.init &&
|
|
44
88
|
isClassInstance(node.init)) {
|
|
45
89
|
const objectPattern = node.id;
|
|
90
|
+
const initText = sourceCode.getText(node.init);
|
|
46
91
|
context.report({
|
|
47
92
|
node,
|
|
48
93
|
messageId: 'noClassInstanceDestructuring',
|
|
94
|
+
data: {
|
|
95
|
+
members: formatMembers(objectPattern.properties),
|
|
96
|
+
instance: `\`${initText}\``,
|
|
97
|
+
suggestion: formatAccessExamples(objectPattern.properties, initText),
|
|
98
|
+
},
|
|
49
99
|
fix(fixer) {
|
|
50
|
-
const sourceCode = context.getSourceCode();
|
|
51
100
|
const properties = objectPattern.properties;
|
|
52
101
|
// Skip if there's no init expression
|
|
53
102
|
if (!node.init)
|
|
@@ -56,14 +105,11 @@ exports.noClassInstanceDestructuring = (0, createRule_1.createRule)({
|
|
|
56
105
|
if (properties.length === 1) {
|
|
57
106
|
const prop = properties[0];
|
|
58
107
|
if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
59
|
-
const key = prop.key.type === utils_1.AST_NODE_TYPES.Identifier
|
|
60
|
-
? prop.key.name
|
|
61
|
-
: sourceCode.getText(prop.key);
|
|
62
108
|
const value = prop.value.type === utils_1.AST_NODE_TYPES.Identifier
|
|
63
109
|
? prop.value.name
|
|
64
110
|
: sourceCode.getText(prop.value);
|
|
65
|
-
const
|
|
66
|
-
return fixer.replaceText(node, `${value} = ${
|
|
111
|
+
const accessPath = buildAccessPath(initText, prop);
|
|
112
|
+
return fixer.replaceText(node, `${value} = ${accessPath}`);
|
|
67
113
|
}
|
|
68
114
|
return null;
|
|
69
115
|
}
|
|
@@ -71,14 +117,11 @@ exports.noClassInstanceDestructuring = (0, createRule_1.createRule)({
|
|
|
71
117
|
const declarations = properties
|
|
72
118
|
.filter((prop) => prop.type === utils_1.AST_NODE_TYPES.Property)
|
|
73
119
|
.map((prop) => {
|
|
74
|
-
const key = prop.key.type === utils_1.AST_NODE_TYPES.Identifier
|
|
75
|
-
? prop.key.name
|
|
76
|
-
: sourceCode.getText(prop.key);
|
|
77
120
|
const value = prop.value.type === utils_1.AST_NODE_TYPES.Identifier
|
|
78
121
|
? prop.value.name
|
|
79
122
|
: sourceCode.getText(prop.value);
|
|
80
|
-
const
|
|
81
|
-
return `${value} = ${
|
|
123
|
+
const accessPath = buildAccessPath(initText, prop);
|
|
124
|
+
return `${value} = ${accessPath}`;
|
|
82
125
|
})
|
|
83
126
|
.join(';\nconst ');
|
|
84
127
|
// Only apply the fix if we have valid declarations
|
|
@@ -14,7 +14,7 @@ exports.noComplexCloudParams = (0, createRule_1.createRule)({
|
|
|
14
14
|
},
|
|
15
15
|
schema: [],
|
|
16
16
|
messages: {
|
|
17
|
-
noComplexObjects: '
|
|
17
|
+
noComplexObjects: 'Cloud function "{{callee}}" receives a value that is not JSON-serializable. Cloud params must stay plain data; class instances, functions, RegExp/BigInt/TypedArray values, or nested complex properties are dropped or cause runtime errors during transport to Firebase. Send only primitives and plain objects/arrays, or serialize the value first (for example, convert a RegExp to a string or JSON.stringify the payload) before calling "{{callee}}".',
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
defaultOptions: [],
|
|
@@ -324,8 +324,9 @@ exports.noComplexCloudParams = (0, createRule_1.createRule)({
|
|
|
324
324
|
return isComplexValue(node);
|
|
325
325
|
}
|
|
326
326
|
function checkCloudFunctionCall(node) {
|
|
327
|
-
|
|
328
|
-
|
|
327
|
+
const callee = node.callee;
|
|
328
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
329
|
+
cloudFunctions.has(callee.name)) {
|
|
329
330
|
// Check each argument for complex objects
|
|
330
331
|
node.arguments.forEach((arg) => {
|
|
331
332
|
if (hasComplexProperties(arg) && !reportedNodes.has(node)) {
|
|
@@ -333,6 +334,9 @@ exports.noComplexCloudParams = (0, createRule_1.createRule)({
|
|
|
333
334
|
context.report({
|
|
334
335
|
node,
|
|
335
336
|
messageId: 'noComplexObjects',
|
|
337
|
+
data: {
|
|
338
|
+
callee: callee.name,
|
|
339
|
+
},
|
|
336
340
|
});
|
|
337
341
|
}
|
|
338
342
|
});
|
|
@@ -37,12 +37,12 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
|
|
|
37
37
|
meta: {
|
|
38
38
|
type: 'suggestion',
|
|
39
39
|
docs: {
|
|
40
|
-
description: '
|
|
40
|
+
description: 'Discourage CSS properties that force GPU compositing layers (e.g., transform, filter, will-change). Extra layers consume GPU memory and split rendering work, which slows scrolling and animation when sprinkled across a page. The rule inspects inline style objects and MUI sx props so layer promotion stays intentional rather than incidental.',
|
|
41
41
|
recommended: 'error',
|
|
42
42
|
},
|
|
43
43
|
schema: [],
|
|
44
44
|
messages: {
|
|
45
|
-
compositingLayer: '{{property}}
|
|
45
|
+
compositingLayer: 'CSS property "{{property}}" promotes this element to its own GPU compositing layer. Extra layers allocate GPU memory and isolate painting, which slows scrolling and animation when used broadly. Remove "{{property}}" or keep it only when the layer promotion is intentional and documented (e.g., eslint-disable with a comment).',
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
48
|
defaultOptions: [],
|
|
@@ -9,12 +9,14 @@ exports.noConditionalLiteralsInJsx = (0, createRule_1.createRule)({
|
|
|
9
9
|
meta: {
|
|
10
10
|
type: 'problem',
|
|
11
11
|
docs: {
|
|
12
|
-
description: 'Disallow
|
|
12
|
+
description: 'Disallow conditional string literals beside other JSX text to avoid fragmented text nodes, translation issues, and hydration mismatches.',
|
|
13
13
|
recommended: 'error',
|
|
14
14
|
},
|
|
15
15
|
schema: [],
|
|
16
16
|
messages: {
|
|
17
|
-
unexpected: 'Conditional text
|
|
17
|
+
unexpected: 'Conditional text literal {{literal}} is rendered next to other JSX text or expressions under condition {{condition}}. ' +
|
|
18
|
+
'This fragments text nodes, confusing translation/i18n tools and potentially causing React hydration mismatches when server and client group the text differently. ' +
|
|
19
|
+
'Wrap the conditional expression in its own element (for example, <span>{ {{expression}} }</span>) or move the entire sentence inside the conditional so it renders as a single text node.',
|
|
18
20
|
},
|
|
19
21
|
},
|
|
20
22
|
defaultOptions: [],
|
|
@@ -42,17 +44,40 @@ exports.noConditionalLiteralsInJsx = (0, createRule_1.createRule)({
|
|
|
42
44
|
'expression' in n &&
|
|
43
45
|
(n.expression.type === 'Identifier' ||
|
|
44
46
|
n.expression.type === 'MemberExpression'));
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
const hasSiblingContent = siblingTextNodes.concat(siblingExpressionNodes).length > 0;
|
|
48
|
+
if (!hasSiblingContent) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const logicalExpression = node.expression;
|
|
52
|
+
const literalNode = logicalExpression.right;
|
|
53
|
+
const conditionalNode = logicalExpression.left;
|
|
54
|
+
// Only enforce when the literal is the expression's return value.
|
|
55
|
+
if (literalNode.type !== utils_1.TSESTree.AST_NODE_TYPES.Literal) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// Only enforce for string literals to avoid misleading messages for
|
|
59
|
+
// numeric or boolean literals rendered conditionally.
|
|
60
|
+
if (typeof literalNode.value !== 'string') {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Ignore logical expressions that do not actually render the literal
|
|
65
|
+
* conditionally (e.g., literal && condition or literal || condition)
|
|
66
|
+
* and expressions with two literals.
|
|
67
|
+
*/
|
|
68
|
+
if (conditionalNode.type === utils_1.TSESTree.AST_NODE_TYPES.Literal) {
|
|
69
|
+
return;
|
|
55
70
|
}
|
|
71
|
+
const sourceCode = context.getSourceCode();
|
|
72
|
+
context.report({
|
|
73
|
+
node,
|
|
74
|
+
messageId: 'unexpected',
|
|
75
|
+
data: {
|
|
76
|
+
literal: sourceCode.getText(literalNode),
|
|
77
|
+
condition: sourceCode.getText(conditionalNode),
|
|
78
|
+
expression: sourceCode.getText(logicalExpression),
|
|
79
|
+
},
|
|
80
|
+
});
|
|
56
81
|
},
|
|
57
82
|
};
|
|
58
83
|
},
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
type Options = [
|
|
3
|
+
{
|
|
4
|
+
ignorePatterns?: string[];
|
|
5
|
+
allowWithUseAlertDialog?: boolean;
|
|
6
|
+
}
|
|
7
|
+
];
|
|
8
|
+
export declare const noConsoleError: TSESLint.RuleModule<"noConsoleError", Options, TSESLint.RuleListener>;
|
|
9
|
+
export {};
|