@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,428 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.flattenPushCalls = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const PUSH_METHOD_NAME = 'push';
|
|
7
|
+
function getRangeStart(node, sourceCode) {
|
|
8
|
+
return node.range?.[0] ?? sourceCode.getIndexFromLoc(node.loc.start);
|
|
9
|
+
}
|
|
10
|
+
function getRangeEnd(node, sourceCode) {
|
|
11
|
+
return node.range?.[1] ?? sourceCode.getIndexFromLoc(node.loc.end);
|
|
12
|
+
}
|
|
13
|
+
function unwrapExpression(expression) {
|
|
14
|
+
let current = expression;
|
|
15
|
+
/**
|
|
16
|
+
* Peel off harmless wrappers to compare the underlying array identity
|
|
17
|
+
* (e.g., arr!.push(), arr as Foo, (arr).push()).
|
|
18
|
+
*/
|
|
19
|
+
while (true) {
|
|
20
|
+
if (current.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
|
|
21
|
+
current = current.expression;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
25
|
+
current.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
26
|
+
current.type === utils_1.AST_NODE_TYPES.TSInstantiationExpression ||
|
|
27
|
+
current.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression) {
|
|
28
|
+
current = current.expression;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (current.type === utils_1.AST_NODE_TYPES.ChainExpression) {
|
|
32
|
+
current = current
|
|
33
|
+
.expression;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
return current;
|
|
39
|
+
}
|
|
40
|
+
function getPropertyKey(property, computed) {
|
|
41
|
+
if (!computed && property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
42
|
+
return property.name;
|
|
43
|
+
}
|
|
44
|
+
if (property.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
45
|
+
const value = typeof property.value === 'string' || typeof property.value === 'number'
|
|
46
|
+
? property.value
|
|
47
|
+
: null;
|
|
48
|
+
return value !== null ? String(value) : null;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
function getCalleeWithTypeParams(call, sourceCode) {
|
|
53
|
+
const calleeStart = getRangeStart(call.callee, sourceCode);
|
|
54
|
+
const calleeEnd = call.typeParameters
|
|
55
|
+
? getRangeEnd(call.typeParameters, sourceCode)
|
|
56
|
+
: getRangeEnd(call.callee, sourceCode);
|
|
57
|
+
return sourceCode.text.slice(calleeStart, calleeEnd);
|
|
58
|
+
}
|
|
59
|
+
function getPreferredCalleeText(group) {
|
|
60
|
+
const withTypeParams = group.find((entry) => entry.call.typeParameters);
|
|
61
|
+
return withTypeParams ? withTypeParams.calleeText : group[0].calleeText;
|
|
62
|
+
}
|
|
63
|
+
function getExpressionIdentity(expression) {
|
|
64
|
+
const node = unwrapExpression(expression);
|
|
65
|
+
switch (node.type) {
|
|
66
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
67
|
+
return `id:${node.name}`;
|
|
68
|
+
case utils_1.AST_NODE_TYPES.ThisExpression:
|
|
69
|
+
return 'this';
|
|
70
|
+
case utils_1.AST_NODE_TYPES.Super:
|
|
71
|
+
return 'super';
|
|
72
|
+
case utils_1.AST_NODE_TYPES.Literal:
|
|
73
|
+
if (typeof node.value === 'string' || typeof node.value === 'number') {
|
|
74
|
+
return `lit:${String(node.value)}`;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
case utils_1.AST_NODE_TYPES.MemberExpression: {
|
|
78
|
+
if (node.property.type === utils_1.AST_NODE_TYPES.PrivateIdentifier)
|
|
79
|
+
return null;
|
|
80
|
+
const objectKey = getExpressionIdentity(node.object);
|
|
81
|
+
const propertyKey = getPropertyKey(node.property, Boolean(node.computed));
|
|
82
|
+
if (!objectKey || !propertyKey)
|
|
83
|
+
return null;
|
|
84
|
+
return `${objectKey}.${propertyKey}`;
|
|
85
|
+
}
|
|
86
|
+
default:
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function isSafeMemberChain(expression) {
|
|
91
|
+
const node = unwrapExpression(expression);
|
|
92
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier ||
|
|
93
|
+
node.type === utils_1.AST_NODE_TYPES.ThisExpression ||
|
|
94
|
+
node.type === utils_1.AST_NODE_TYPES.Super) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
98
|
+
if (node.property.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if (node.computed || node.property.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return isSafeMemberChain(node.object);
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
function hasForbiddenSideEffects(node) {
|
|
109
|
+
if (!node)
|
|
110
|
+
return false;
|
|
111
|
+
const astNodeType = node.type;
|
|
112
|
+
if (astNodeType === 'ParenthesizedExpression') {
|
|
113
|
+
return hasForbiddenSideEffects(node.expression);
|
|
114
|
+
}
|
|
115
|
+
switch (node.type) {
|
|
116
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
117
|
+
case utils_1.AST_NODE_TYPES.Literal:
|
|
118
|
+
case utils_1.AST_NODE_TYPES.ThisExpression:
|
|
119
|
+
case utils_1.AST_NODE_TYPES.Super:
|
|
120
|
+
return false;
|
|
121
|
+
case utils_1.AST_NODE_TYPES.CallExpression:
|
|
122
|
+
case utils_1.AST_NODE_TYPES.NewExpression:
|
|
123
|
+
case utils_1.AST_NODE_TYPES.UpdateExpression:
|
|
124
|
+
case utils_1.AST_NODE_TYPES.AwaitExpression:
|
|
125
|
+
case utils_1.AST_NODE_TYPES.YieldExpression:
|
|
126
|
+
case utils_1.AST_NODE_TYPES.TaggedTemplateExpression:
|
|
127
|
+
case utils_1.AST_NODE_TYPES.ImportExpression:
|
|
128
|
+
case utils_1.AST_NODE_TYPES.AssignmentExpression:
|
|
129
|
+
return true;
|
|
130
|
+
case utils_1.AST_NODE_TYPES.UnaryExpression:
|
|
131
|
+
if (node.operator === 'delete')
|
|
132
|
+
return true;
|
|
133
|
+
return hasForbiddenSideEffects(node.argument);
|
|
134
|
+
case utils_1.AST_NODE_TYPES.BinaryExpression:
|
|
135
|
+
case utils_1.AST_NODE_TYPES.LogicalExpression:
|
|
136
|
+
return (hasForbiddenSideEffects(node.left) ||
|
|
137
|
+
hasForbiddenSideEffects(node.right));
|
|
138
|
+
case utils_1.AST_NODE_TYPES.ConditionalExpression:
|
|
139
|
+
return (hasForbiddenSideEffects(node.test) ||
|
|
140
|
+
hasForbiddenSideEffects(node.consequent) ||
|
|
141
|
+
hasForbiddenSideEffects(node.alternate));
|
|
142
|
+
case utils_1.AST_NODE_TYPES.MemberExpression:
|
|
143
|
+
return (hasForbiddenSideEffects(node.object) ||
|
|
144
|
+
(node.computed &&
|
|
145
|
+
hasForbiddenSideEffects(node.property)));
|
|
146
|
+
case utils_1.AST_NODE_TYPES.ChainExpression:
|
|
147
|
+
return hasForbiddenSideEffects(node.expression);
|
|
148
|
+
case utils_1.AST_NODE_TYPES.SequenceExpression:
|
|
149
|
+
return node.expressions.some((expr) => hasForbiddenSideEffects(expr));
|
|
150
|
+
case utils_1.AST_NODE_TYPES.TemplateLiteral:
|
|
151
|
+
return node.expressions.some((expr) => hasForbiddenSideEffects(expr));
|
|
152
|
+
case utils_1.AST_NODE_TYPES.ArrayExpression:
|
|
153
|
+
return node.elements.some((elem) => elem ? hasForbiddenSideEffects(elem) : false);
|
|
154
|
+
case utils_1.AST_NODE_TYPES.ObjectExpression:
|
|
155
|
+
return node.properties.some((prop) => {
|
|
156
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
157
|
+
return ((prop.computed &&
|
|
158
|
+
hasForbiddenSideEffects(prop.key)) ||
|
|
159
|
+
hasForbiddenSideEffects(prop.value));
|
|
160
|
+
}
|
|
161
|
+
if (prop.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
162
|
+
return hasForbiddenSideEffects(prop.argument);
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
});
|
|
166
|
+
case utils_1.AST_NODE_TYPES.SpreadElement:
|
|
167
|
+
return hasForbiddenSideEffects(node.argument);
|
|
168
|
+
case utils_1.AST_NODE_TYPES.TSAsExpression:
|
|
169
|
+
case utils_1.AST_NODE_TYPES.TSTypeAssertion:
|
|
170
|
+
case utils_1.AST_NODE_TYPES.TSNonNullExpression:
|
|
171
|
+
case utils_1.AST_NODE_TYPES.TSInstantiationExpression:
|
|
172
|
+
case utils_1.AST_NODE_TYPES.TSSatisfiesExpression:
|
|
173
|
+
return hasForbiddenSideEffects(node.expression);
|
|
174
|
+
default:
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function canSafelyFix(group) {
|
|
179
|
+
return group.every((entry) => {
|
|
180
|
+
const callee = entry.call.callee;
|
|
181
|
+
if (callee.property.type === utils_1.AST_NODE_TYPES.PrivateIdentifier ||
|
|
182
|
+
callee.computed ||
|
|
183
|
+
callee.property.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
if (!isSafeMemberChain(callee.object)) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
if (hasForbiddenSideEffects(callee.object)) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
return entry.call.arguments.every((arg) => {
|
|
193
|
+
if (arg.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
194
|
+
return !hasForbiddenSideEffects(arg.argument);
|
|
195
|
+
}
|
|
196
|
+
return !hasForbiddenSideEffects(arg);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function isPushCallStatement(statement, sourceCode) {
|
|
201
|
+
if (statement.type !== utils_1.AST_NODE_TYPES.ExpressionStatement)
|
|
202
|
+
return null;
|
|
203
|
+
const expr = statement.expression;
|
|
204
|
+
if (expr.type !== utils_1.AST_NODE_TYPES.CallExpression)
|
|
205
|
+
return null;
|
|
206
|
+
if (expr.optional)
|
|
207
|
+
return null;
|
|
208
|
+
const callee = expr.callee;
|
|
209
|
+
if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression ||
|
|
210
|
+
callee.optional ||
|
|
211
|
+
callee.property.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
const propertyName = callee.property.type === utils_1.AST_NODE_TYPES.Identifier
|
|
215
|
+
? callee.property.name
|
|
216
|
+
: callee.property.type === utils_1.AST_NODE_TYPES.Literal
|
|
217
|
+
? callee.property.value
|
|
218
|
+
: null;
|
|
219
|
+
if (propertyName !== PUSH_METHOD_NAME)
|
|
220
|
+
return null;
|
|
221
|
+
const targetKey = getExpressionIdentity(callee.object);
|
|
222
|
+
if (!targetKey)
|
|
223
|
+
return null;
|
|
224
|
+
return {
|
|
225
|
+
statement,
|
|
226
|
+
call: expr,
|
|
227
|
+
targetKey,
|
|
228
|
+
calleeText: getCalleeWithTypeParams(expr, sourceCode),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function getLineIndent(targetNode, sourceCode) {
|
|
232
|
+
const start = getRangeStart(targetNode, sourceCode);
|
|
233
|
+
const text = sourceCode.text;
|
|
234
|
+
const lineStart = text.lastIndexOf('\n', start - 1) + 1;
|
|
235
|
+
const indentMatch = text.slice(lineStart, start).match(/^[\t ]*/u);
|
|
236
|
+
return indentMatch ? indentMatch[0] : '';
|
|
237
|
+
}
|
|
238
|
+
function normalizeIndentation(text) {
|
|
239
|
+
const lines = text.split('\n');
|
|
240
|
+
const indents = lines
|
|
241
|
+
.slice(1)
|
|
242
|
+
.filter((line) => line.trim().length > 0)
|
|
243
|
+
.map((line) => line.match(/^[\t ]*/u)?.[0].length ?? 0);
|
|
244
|
+
const minIndent = indents.length > 0 ? Math.min(...indents) : 0;
|
|
245
|
+
if (minIndent === 0)
|
|
246
|
+
return text;
|
|
247
|
+
return [
|
|
248
|
+
lines[0],
|
|
249
|
+
...lines
|
|
250
|
+
.slice(1)
|
|
251
|
+
.map((line) => line.slice(Math.min(minIndent, line.length))),
|
|
252
|
+
].join('\n');
|
|
253
|
+
}
|
|
254
|
+
function indentText(text, indent) {
|
|
255
|
+
const normalized = normalizeIndentation(text);
|
|
256
|
+
return normalized
|
|
257
|
+
.split('\n')
|
|
258
|
+
.map((line) => indent + line)
|
|
259
|
+
.join('\n');
|
|
260
|
+
}
|
|
261
|
+
function formatComments(comments, indent, sourceCode) {
|
|
262
|
+
return comments.map((comment) => indentText(sourceCode.getText(comment), indent));
|
|
263
|
+
}
|
|
264
|
+
function getLeadingCommentsBetween(sourceCode, previousStatement, current) {
|
|
265
|
+
const previousEnd = getRangeEnd(previousStatement, sourceCode);
|
|
266
|
+
return sourceCode
|
|
267
|
+
.getCommentsBefore(current)
|
|
268
|
+
.filter((comment) => comment.range[0] >= previousEnd);
|
|
269
|
+
}
|
|
270
|
+
exports.flattenPushCalls = (0, createRule_1.createRule)({
|
|
271
|
+
name: 'flatten-push-calls',
|
|
272
|
+
meta: {
|
|
273
|
+
type: 'suggestion',
|
|
274
|
+
docs: {
|
|
275
|
+
description: 'Consolidate consecutive push calls on the same array into a single push with multiple arguments.',
|
|
276
|
+
recommended: 'error',
|
|
277
|
+
},
|
|
278
|
+
fixable: 'code',
|
|
279
|
+
schema: [],
|
|
280
|
+
messages: {
|
|
281
|
+
flattenPushCalls: [
|
|
282
|
+
'What’s wrong: "{{target}}" is pushed to using multiple consecutive ".push(...)" calls.',
|
|
283
|
+
'Why it matters: repeated calls add property-access overhead and obscure that these values belong to one append operation.',
|
|
284
|
+
'How to fix: merge them into a single ".push(...)" call with multiple arguments (for example, "{{target}}.push(a, b, c)").',
|
|
285
|
+
].join(' '),
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
defaultOptions: [],
|
|
289
|
+
create(context) {
|
|
290
|
+
const sourceCode = context.getSourceCode();
|
|
291
|
+
function buildSegments(group) {
|
|
292
|
+
return group.map((entry, index) => {
|
|
293
|
+
const previousStatement = index > 0 ? group[index - 1].statement : null;
|
|
294
|
+
const comments = previousStatement === null
|
|
295
|
+
? []
|
|
296
|
+
: getLeadingCommentsBetween(sourceCode, previousStatement, entry.statement);
|
|
297
|
+
return { comments, args: entry.call.arguments };
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
function shouldUseMultilineFormat(segments, group, totalArgs) {
|
|
301
|
+
const hasInterstitialComments = segments.some((segment) => segment.comments.length > 0);
|
|
302
|
+
const hasMultilineArgument = group.some((entry) => entry.call.arguments.some((arg) => sourceCode.getText(arg).includes('\n')));
|
|
303
|
+
return hasInterstitialComments || hasMultilineArgument || totalArgs > 2;
|
|
304
|
+
}
|
|
305
|
+
function detectSemicolon(first, last) {
|
|
306
|
+
return (sourceCode.getLastToken(last.statement)?.value === ';' ||
|
|
307
|
+
sourceCode.getLastToken(first.statement)?.value === ';');
|
|
308
|
+
}
|
|
309
|
+
function formatArguments(segments, shouldUseMultiline, argumentIndent) {
|
|
310
|
+
if (!shouldUseMultiline) {
|
|
311
|
+
return segments.flatMap((segment) => segment.args.map((arg) => sourceCode.getText(arg)));
|
|
312
|
+
}
|
|
313
|
+
return formatMultilineArguments(segments, argumentIndent);
|
|
314
|
+
}
|
|
315
|
+
function formatMultilineArguments(segments, argumentIndent) {
|
|
316
|
+
const argumentParts = [];
|
|
317
|
+
let pendingComments = [];
|
|
318
|
+
segments.forEach((segment) => {
|
|
319
|
+
const formattedComments = formatComments(segment.comments, argumentIndent, sourceCode);
|
|
320
|
+
pendingComments = pendingComments.concat(formattedComments);
|
|
321
|
+
if (segment.args.length === 0)
|
|
322
|
+
return;
|
|
323
|
+
segment.args.forEach((arg, index) => {
|
|
324
|
+
const argText = indentText(sourceCode.getText(arg), argumentIndent);
|
|
325
|
+
if (index === 0 && pendingComments.length > 0) {
|
|
326
|
+
argumentParts.push(`${pendingComments.join('\n')}\n${argText}`);
|
|
327
|
+
pendingComments = [];
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
argumentParts.push(argText);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
return attachTrailingComments(argumentParts, pendingComments);
|
|
335
|
+
}
|
|
336
|
+
function attachTrailingComments(argumentParts, pendingComments) {
|
|
337
|
+
if (pendingComments.length > 0 && argumentParts.length > 0) {
|
|
338
|
+
const lastIndex = argumentParts.length - 1;
|
|
339
|
+
argumentParts[lastIndex] = `${argumentParts[lastIndex]}\n${pendingComments.join('\n')}`;
|
|
340
|
+
}
|
|
341
|
+
return argumentParts;
|
|
342
|
+
}
|
|
343
|
+
function buildFinalReplacement(calleeText, argumentParts, shouldUseMultiline, baseIndent, hasSemicolon) {
|
|
344
|
+
const argsText = shouldUseMultiline
|
|
345
|
+
? `\n${argumentParts.join(',\n')}\n${baseIndent}`
|
|
346
|
+
: argumentParts.join(', ');
|
|
347
|
+
return `${calleeText}(${argsText})${hasSemicolon ? ';' : ''}`;
|
|
348
|
+
}
|
|
349
|
+
function buildReplacement(group) {
|
|
350
|
+
const first = group[0];
|
|
351
|
+
const last = group[group.length - 1];
|
|
352
|
+
const totalArgs = group.reduce((count, item) => count + item.call.arguments.length, 0);
|
|
353
|
+
const calleeText = getPreferredCalleeText(group);
|
|
354
|
+
const segments = buildSegments(group);
|
|
355
|
+
const shouldUseMultiline = shouldUseMultilineFormat(segments, group, totalArgs);
|
|
356
|
+
const baseIndent = getLineIndent(first.statement, sourceCode);
|
|
357
|
+
const argumentIndent = `${baseIndent} `;
|
|
358
|
+
const argumentParts = formatArguments(segments, shouldUseMultiline, argumentIndent);
|
|
359
|
+
const hasSemicolon = detectSemicolon(first, last);
|
|
360
|
+
const replacement = buildFinalReplacement(calleeText, argumentParts, shouldUseMultiline, baseIndent, hasSemicolon);
|
|
361
|
+
const currentText = sourceCode
|
|
362
|
+
.getText()
|
|
363
|
+
.slice(first.statement.range[0], last.statement.range[1]);
|
|
364
|
+
return currentText === replacement ? null : replacement;
|
|
365
|
+
}
|
|
366
|
+
function findConsecutivePushGroup(statements, startIndex, firstInfo) {
|
|
367
|
+
const group = [firstInfo];
|
|
368
|
+
let cursor = startIndex + 1;
|
|
369
|
+
while (cursor < statements.length) {
|
|
370
|
+
const next = isPushCallStatement(statements[cursor], sourceCode);
|
|
371
|
+
if (!next || next.targetKey !== firstInfo.targetKey)
|
|
372
|
+
break;
|
|
373
|
+
group.push(next);
|
|
374
|
+
cursor += 1;
|
|
375
|
+
}
|
|
376
|
+
return { group, nextIndex: cursor };
|
|
377
|
+
}
|
|
378
|
+
function shouldReportViolation(group) {
|
|
379
|
+
if (group.length <= 1)
|
|
380
|
+
return false;
|
|
381
|
+
const totalArgs = group.reduce((count, entry) => count + entry.call.arguments.length, 0);
|
|
382
|
+
const firstArgs = group[0].call.arguments.length;
|
|
383
|
+
return totalArgs > firstArgs && canSafelyFix(group);
|
|
384
|
+
}
|
|
385
|
+
function reportViolation(group) {
|
|
386
|
+
context.report({
|
|
387
|
+
node: group[0].call.callee,
|
|
388
|
+
messageId: 'flattenPushCalls',
|
|
389
|
+
data: {
|
|
390
|
+
target: sourceCode.getText(group[0].call.callee.object),
|
|
391
|
+
},
|
|
392
|
+
fix(fixer) {
|
|
393
|
+
const replacement = buildReplacement(group);
|
|
394
|
+
if (!replacement)
|
|
395
|
+
return null;
|
|
396
|
+
return fixer.replaceTextRange([
|
|
397
|
+
group[0].statement.range[0],
|
|
398
|
+
group[group.length - 1].statement.range[1],
|
|
399
|
+
], replacement);
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
function checkStatements(statements) {
|
|
404
|
+
for (let i = 0; i < statements.length; i++) {
|
|
405
|
+
const info = isPushCallStatement(statements[i], sourceCode);
|
|
406
|
+
if (!info)
|
|
407
|
+
continue;
|
|
408
|
+
const { group, nextIndex } = findConsecutivePushGroup(statements, i, info);
|
|
409
|
+
if (shouldReportViolation(group)) {
|
|
410
|
+
reportViolation(group);
|
|
411
|
+
}
|
|
412
|
+
i = nextIndex - 1;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
Program(node) {
|
|
417
|
+
checkStatements(node.body);
|
|
418
|
+
},
|
|
419
|
+
BlockStatement(node) {
|
|
420
|
+
checkStatements(node.body);
|
|
421
|
+
},
|
|
422
|
+
SwitchCase(node) {
|
|
423
|
+
checkStatements(node.consequent);
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
//# sourceMappingURL=flatten-push-calls.js.map
|
|
@@ -9,9 +9,15 @@ exports.genericStartsWithT = (0, createRule_1.createRule)({
|
|
|
9
9
|
for (const param of node.params) {
|
|
10
10
|
if (typeof param.name.name === 'string' &&
|
|
11
11
|
param.name.name[0] !== 'T') {
|
|
12
|
+
const name = param.name.name;
|
|
13
|
+
const suggestedName = `T${name}`;
|
|
12
14
|
context.report({
|
|
13
15
|
node: param,
|
|
14
16
|
messageId: 'genericStartsWithT',
|
|
17
|
+
data: {
|
|
18
|
+
name,
|
|
19
|
+
suggestedName,
|
|
20
|
+
},
|
|
15
21
|
});
|
|
16
22
|
}
|
|
17
23
|
}
|
|
@@ -22,12 +28,12 @@ exports.genericStartsWithT = (0, createRule_1.createRule)({
|
|
|
22
28
|
meta: {
|
|
23
29
|
type: 'suggestion',
|
|
24
30
|
docs: {
|
|
25
|
-
description: 'Enforce TypeScript generic
|
|
31
|
+
description: 'Enforce TypeScript generic type parameters to start with T so they stand out from runtime values.',
|
|
26
32
|
recommended: 'error',
|
|
27
33
|
},
|
|
28
34
|
schema: [],
|
|
29
35
|
messages: {
|
|
30
|
-
genericStartsWithT: 'Generic type parameter should start with T.',
|
|
36
|
+
genericStartsWithT: 'Generic type parameter "{{name}}" should start with "T" (e.g., "{{suggestedName}}") so readers immediately recognize it as a generic type rather than a concrete value. T-prefixed generics make type parameters stand out in signatures and prevent confusion with runtime parameters.',
|
|
31
37
|
},
|
|
32
38
|
},
|
|
33
39
|
defaultOptions: [],
|
|
@@ -14,8 +14,8 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
14
14
|
fixable: 'code',
|
|
15
15
|
schema: [],
|
|
16
16
|
messages: {
|
|
17
|
-
upperSnakeCase: 'Global
|
|
18
|
-
asConst: 'Global
|
|
17
|
+
upperSnakeCase: 'Global constant "{{name}}" should be written in UPPER_SNAKE_CASE (e.g., "{{suggestedName}}") so it reads as a module-level configuration value that never changes; rename it to make its immutability obvious.',
|
|
18
|
+
asConst: 'Global constant "{{name}}" is initialized with {{valueKind}} but lacks `as const`, so TypeScript widens the type and code can mutate it accidentally; append `as const` to freeze the value and preserve literal types.',
|
|
19
19
|
},
|
|
20
20
|
},
|
|
21
21
|
defaultOptions: [],
|
|
@@ -23,6 +23,27 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
23
23
|
// Check if the file is a TypeScript file
|
|
24
24
|
const isTypeScript = context.getFilename().endsWith('.ts') ||
|
|
25
25
|
context.getFilename().endsWith('.tsx');
|
|
26
|
+
const unwrapAssertions = (node) => {
|
|
27
|
+
let target = node;
|
|
28
|
+
while (target.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
29
|
+
target.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
|
|
30
|
+
target = target.expression;
|
|
31
|
+
}
|
|
32
|
+
return target;
|
|
33
|
+
};
|
|
34
|
+
const describeValueKind = (node) => {
|
|
35
|
+
const target = unwrapAssertions(node);
|
|
36
|
+
if (target.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
|
|
37
|
+
return 'an array literal';
|
|
38
|
+
}
|
|
39
|
+
if (target.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
40
|
+
return 'an object literal';
|
|
41
|
+
}
|
|
42
|
+
if (target.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
43
|
+
return 'a literal value';
|
|
44
|
+
}
|
|
45
|
+
return 'a value';
|
|
46
|
+
};
|
|
26
47
|
return {
|
|
27
48
|
VariableDeclaration(node) {
|
|
28
49
|
// Only check top-level const declarations
|
|
@@ -87,7 +108,7 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
87
108
|
init.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
88
109
|
return;
|
|
89
110
|
}
|
|
90
|
-
const sourceCode = context.
|
|
111
|
+
const sourceCode = context.sourceCode;
|
|
91
112
|
const initText = sourceCode.getText(init);
|
|
92
113
|
const typeAnnotation = declaration.id.typeAnnotation;
|
|
93
114
|
const typeText = typeAnnotation
|
|
@@ -95,42 +116,48 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
95
116
|
: '';
|
|
96
117
|
// Only check for as const in TypeScript files
|
|
97
118
|
if (isTypeScript) {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
119
|
+
const hasAsConstAssertion = (node) => {
|
|
120
|
+
let current = node;
|
|
121
|
+
while (current &&
|
|
122
|
+
(current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
123
|
+
current.type === utils_1.AST_NODE_TYPES.TSTypeAssertion)) {
|
|
124
|
+
const { typeAnnotation } = current;
|
|
125
|
+
if (typeAnnotation?.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
126
|
+
typeAnnotation.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
127
|
+
typeAnnotation.typeName.name === 'const') {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
current = current.expression;
|
|
104
131
|
}
|
|
105
132
|
return false;
|
|
106
133
|
};
|
|
107
134
|
const shouldHaveAsConst = (node) => {
|
|
108
135
|
// Skip if it's already an as const expression
|
|
109
|
-
if (
|
|
136
|
+
if (hasAsConstAssertion(node)) {
|
|
110
137
|
return false;
|
|
111
138
|
}
|
|
112
|
-
|
|
113
|
-
if (node.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
114
|
-
node.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
|
|
115
|
-
return shouldHaveAsConst(node.expression);
|
|
116
|
-
}
|
|
139
|
+
const target = unwrapAssertions(node);
|
|
117
140
|
// Skip if there's an explicit type annotation
|
|
118
141
|
if (declaration.id.typeAnnotation) {
|
|
119
142
|
return false;
|
|
120
143
|
}
|
|
121
144
|
// Check if it's a literal, array, or object that should have as const
|
|
122
145
|
// Skip regular expressions as they are already immutable
|
|
123
|
-
if (
|
|
146
|
+
if (target.type === utils_1.AST_NODE_TYPES.Literal && 'regex' in target) {
|
|
124
147
|
return false;
|
|
125
148
|
}
|
|
126
|
-
return (
|
|
127
|
-
|
|
128
|
-
|
|
149
|
+
return (target.type === utils_1.AST_NODE_TYPES.Literal ||
|
|
150
|
+
target.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
|
|
151
|
+
target.type === utils_1.AST_NODE_TYPES.ObjectExpression);
|
|
129
152
|
};
|
|
130
153
|
if (shouldHaveAsConst(init)) {
|
|
131
154
|
context.report({
|
|
132
155
|
node: declaration,
|
|
133
156
|
messageId: 'asConst',
|
|
157
|
+
data: {
|
|
158
|
+
name,
|
|
159
|
+
valueKind: describeValueKind(init),
|
|
160
|
+
},
|
|
134
161
|
fix(fixer) {
|
|
135
162
|
return fixer.replaceText(init, `${initText} as const`);
|
|
136
163
|
},
|
|
@@ -146,6 +173,10 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
146
173
|
context.report({
|
|
147
174
|
node: declaration,
|
|
148
175
|
messageId: 'upperSnakeCase',
|
|
176
|
+
data: {
|
|
177
|
+
name,
|
|
178
|
+
suggestedName: newName,
|
|
179
|
+
},
|
|
149
180
|
fix(fixer) {
|
|
150
181
|
if (typeAnnotation) {
|
|
151
182
|
return fixer.replaceText(declaration, `${newName}${typeText} = ${initText}`);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type Options = [
|
|
2
|
+
{
|
|
3
|
+
/**
|
|
4
|
+
* When true, the rule also flags inline JSDoc on object literal properties.
|
|
5
|
+
* By default, only interface/type literals and class fields are checked.
|
|
6
|
+
*/
|
|
7
|
+
checkObjectLiterals?: boolean;
|
|
8
|
+
}
|
|
9
|
+
];
|
|
10
|
+
export declare const jsdocAboveField: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"moveJsdocAbove", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
11
|
+
export {};
|