@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
|
@@ -12,6 +12,7 @@ exports.ARRAY_METHODS = [
|
|
|
12
12
|
];
|
|
13
13
|
exports.arrayMethodsThisContext = (0, createRule_1.createRule)({
|
|
14
14
|
create(context) {
|
|
15
|
+
const sourceCode = context.sourceCode;
|
|
15
16
|
return {
|
|
16
17
|
CallExpression(node) {
|
|
17
18
|
// Array method called with a class method reference
|
|
@@ -21,9 +22,23 @@ exports.arrayMethodsThisContext = (0, createRule_1.createRule)({
|
|
|
21
22
|
node.arguments.length > 0 &&
|
|
22
23
|
node.arguments[0].type === 'MemberExpression' &&
|
|
23
24
|
node.arguments[0].object.type === 'ThisExpression') {
|
|
25
|
+
const methodProperty = node.arguments[0].property;
|
|
26
|
+
const methodAccessor = node.arguments[0].computed
|
|
27
|
+
? `[${sourceCode.getText(methodProperty)}]`
|
|
28
|
+
: methodProperty.type === 'Identifier'
|
|
29
|
+
? `.${methodProperty.name}`
|
|
30
|
+
: methodProperty.type === 'PrivateIdentifier'
|
|
31
|
+
? `.#${methodProperty.name}`
|
|
32
|
+
: `.${sourceCode.getText(methodProperty)}`;
|
|
33
|
+
const methodReference = `this${methodAccessor}`;
|
|
24
34
|
context.report({
|
|
25
35
|
node: node.arguments[0],
|
|
26
36
|
messageId: 'unexpected',
|
|
37
|
+
data: {
|
|
38
|
+
arrayMethod: node.callee.property.name,
|
|
39
|
+
methodAccessor,
|
|
40
|
+
methodReference,
|
|
41
|
+
},
|
|
27
42
|
});
|
|
28
43
|
}
|
|
29
44
|
// Function expression bound to `this` in array method
|
|
@@ -41,6 +56,9 @@ exports.arrayMethodsThisContext = (0, createRule_1.createRule)({
|
|
|
41
56
|
context.report({
|
|
42
57
|
node: node.arguments[0],
|
|
43
58
|
messageId: 'preferArrow',
|
|
59
|
+
data: {
|
|
60
|
+
arrayMethod: node.callee.property.name,
|
|
61
|
+
},
|
|
44
62
|
});
|
|
45
63
|
}
|
|
46
64
|
},
|
|
@@ -55,8 +73,8 @@ exports.arrayMethodsThisContext = (0, createRule_1.createRule)({
|
|
|
55
73
|
},
|
|
56
74
|
schema: [],
|
|
57
75
|
messages: {
|
|
58
|
-
unexpected: '
|
|
59
|
-
preferArrow: '
|
|
76
|
+
unexpected: 'Array method "{{arrayMethod}}" receives the class method reference {{methodReference}}, which strips its "this" binding when the callback runs. Use an arrow callback so the class instance remains available, e.g. {{arrayMethod}}((item) => this{{methodAccessor}}(item)).',
|
|
77
|
+
preferArrow: 'Array method "{{arrayMethod}}" binds a callback with ".bind(this)", which allocates a new function and hides that the code depends on the class instance. Prefer an arrow callback that captures "this" without rebinding, e.g. {{arrayMethod}}((item) => this.handleItem(item)).',
|
|
60
78
|
},
|
|
61
79
|
},
|
|
62
80
|
defaultOptions: [],
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.avoidUtilsDirectory = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
4
8
|
const createRule_1 = require("../utils/createRule");
|
|
5
9
|
exports.avoidUtilsDirectory = (0, createRule_1.createRule)({
|
|
6
10
|
name: 'avoid-utils-directory',
|
|
@@ -10,10 +14,9 @@ exports.avoidUtilsDirectory = (0, createRule_1.createRule)({
|
|
|
10
14
|
description: 'Enforce using util/ instead of utils/ directory',
|
|
11
15
|
recommended: 'error',
|
|
12
16
|
},
|
|
13
|
-
fixable: 'code',
|
|
14
17
|
schema: [],
|
|
15
18
|
messages: {
|
|
16
|
-
avoidUtils: '
|
|
19
|
+
avoidUtils: 'Path "{{path}}" lives under a "utils/" directory. Generic "utils" folders become grab bags where unrelated helpers accumulate, which makes imports unpredictable and hides ownership. Move this file into a focused "util/" folder (e.g., "util/date" or "util/string") so callers know where to find it and understand its responsibility.',
|
|
17
20
|
},
|
|
18
21
|
},
|
|
19
22
|
defaultOptions: [],
|
|
@@ -21,6 +24,9 @@ exports.avoidUtilsDirectory = (0, createRule_1.createRule)({
|
|
|
21
24
|
return {
|
|
22
25
|
Program(node) {
|
|
23
26
|
const filename = context.getFilename();
|
|
27
|
+
const relativePath = path_1.default.isAbsolute(filename)
|
|
28
|
+
? path_1.default.relative(process.cwd(), filename) || filename
|
|
29
|
+
: filename;
|
|
24
30
|
// Skip files in node_modules
|
|
25
31
|
if (filename.includes('node_modules')) {
|
|
26
32
|
return;
|
|
@@ -31,6 +37,9 @@ exports.avoidUtilsDirectory = (0, createRule_1.createRule)({
|
|
|
31
37
|
context.report({
|
|
32
38
|
node,
|
|
33
39
|
messageId: 'avoidUtils',
|
|
40
|
+
data: {
|
|
41
|
+
path: relativePath,
|
|
42
|
+
},
|
|
34
43
|
fix() {
|
|
35
44
|
// We can't provide an auto-fix since directory renaming is beyond ESLint's scope
|
|
36
45
|
return null;
|
|
@@ -15,23 +15,31 @@ exports.classMethodsReadTopToBottom = (0, createRule_1.createRule)({
|
|
|
15
15
|
meta: {
|
|
16
16
|
type: 'suggestion',
|
|
17
17
|
docs: {
|
|
18
|
-
description: '
|
|
18
|
+
description: 'Enforces a top-to-bottom class layout so callers lead into the helpers they rely on.',
|
|
19
19
|
recommended: 'warn',
|
|
20
20
|
},
|
|
21
21
|
schema: [],
|
|
22
22
|
messages: {
|
|
23
|
-
classMethodsReadTopToBottom:
|
|
23
|
+
classMethodsReadTopToBottom: [
|
|
24
|
+
"What's wrong: In {{className}}, {{actualMember}} appears before {{expectedMember}}.",
|
|
25
|
+
'Why it matters: Top-down flow enables local reasoning: you can verify each caller without scrolling back. Upward jumps make code reviews harder (must verify call chains in reverse), obscure which fields a helper assumes are initialized, and increase the risk of calling helpers before state is ready (leading to null reference errors or accessing uninitialized fields).',
|
|
26
|
+
'How to fix: Move {{expectedMember}} above {{actualMember}} so the class reads top-to-bottom (fields to constructor to callers to helpers).',
|
|
27
|
+
].join('\n'),
|
|
24
28
|
},
|
|
25
29
|
fixable: 'code', // To allow ESLint to autofix issues.
|
|
26
30
|
},
|
|
27
31
|
defaultOptions: [],
|
|
28
32
|
create(context) {
|
|
29
|
-
|
|
33
|
+
const classNames = new WeakMap();
|
|
30
34
|
return {
|
|
31
35
|
ClassDeclaration(node) {
|
|
32
|
-
|
|
36
|
+
classNames.set(node.body, node.id?.name || '');
|
|
37
|
+
},
|
|
38
|
+
ClassExpression(node) {
|
|
39
|
+
classNames.set(node.body, node.id?.name || '');
|
|
33
40
|
},
|
|
34
41
|
'ClassBody:exit'(node) {
|
|
42
|
+
const className = classNames.get(node) || '';
|
|
35
43
|
const graphBuilder = new ClassGraphBuilder_1.ClassGraphBuilder(className, node);
|
|
36
44
|
const sortedOrder = graphBuilder.memberNamesSorted;
|
|
37
45
|
const actualOrder = node.body
|
|
@@ -51,7 +59,13 @@ exports.classMethodsReadTopToBottom = (0, createRule_1.createRule)({
|
|
|
51
59
|
return; // Skip if there are actual duplicates
|
|
52
60
|
}
|
|
53
61
|
for (let i = 0; i < actualOrder.length; i++) {
|
|
54
|
-
|
|
62
|
+
const actualMember = actualOrder[i];
|
|
63
|
+
const expectedMember = sortedOrder[i];
|
|
64
|
+
if (!actualMember || !expectedMember) {
|
|
65
|
+
throw new Error(`class-methods-read-top-to-bottom invariant violated while comparing members in ${className || 'an unnamed class'} at position ${i}: actualMember=${String(actualMember)}, expectedMember=${String(expectedMember)}, actualOrder.length=${actualOrder.length}, sortedOrder.length=${sortedOrder.length}`);
|
|
66
|
+
}
|
|
67
|
+
if (actualMember !== expectedMember) {
|
|
68
|
+
const classNameReport = className || 'this class';
|
|
55
69
|
const sourceCode = context.getSourceCode();
|
|
56
70
|
const newClassBody = sortedOrder
|
|
57
71
|
.map((n) => {
|
|
@@ -75,6 +89,11 @@ exports.classMethodsReadTopToBottom = (0, createRule_1.createRule)({
|
|
|
75
89
|
return context.report({
|
|
76
90
|
node,
|
|
77
91
|
messageId: 'classMethodsReadTopToBottom',
|
|
92
|
+
data: {
|
|
93
|
+
className: classNameReport,
|
|
94
|
+
actualMember,
|
|
95
|
+
expectedMember,
|
|
96
|
+
},
|
|
78
97
|
fix(fixer) {
|
|
79
98
|
return fixer.replaceTextRange([node.range[0] + 1, node.range[1] - 1], // Exclude the curly braces
|
|
80
99
|
newClassBody);
|
|
@@ -210,7 +210,7 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
// Get references from global scope
|
|
213
|
-
const sourceCode = context.
|
|
213
|
+
const sourceCode = context.sourceCode;
|
|
214
214
|
if (sourceCode.scopeManager?.globalScope) {
|
|
215
215
|
const globalVar = sourceCode.scopeManager.globalScope.variables.find((v) => v.name === functionName);
|
|
216
216
|
if (globalVar) {
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
-
|
|
2
|
+
type MessageIds = 'dynamicHttpsErrors' | 'missingThirdArgument';
|
|
3
|
+
export declare const dynamicHttpsErrors: TSESLint.RuleModule<MessageIds, never[]>;
|
|
4
|
+
export {};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.dynamicHttpsErrors = void 0;
|
|
4
4
|
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
6
|
const isHttpsErrorCall = (callee) => {
|
|
6
7
|
if (callee.type === 'MemberExpression') {
|
|
7
8
|
return (callee.object.type === 'Identifier' &&
|
|
@@ -19,12 +20,13 @@ exports.dynamicHttpsErrors = (0, createRule_1.createRule)({
|
|
|
19
20
|
meta: {
|
|
20
21
|
type: 'suggestion',
|
|
21
22
|
docs: {
|
|
22
|
-
description: '
|
|
23
|
+
description: 'Keep HttpsError messages static and move request-specific details to the third argument so error identifiers remain stable and debugging context is preserved.',
|
|
23
24
|
recommended: 'error',
|
|
24
25
|
},
|
|
25
26
|
schema: [],
|
|
26
27
|
messages: {
|
|
27
|
-
dynamicHttpsErrors: '
|
|
28
|
+
dynamicHttpsErrors: 'The HttpsError message (second argument) must stay static. Template expressions here change the hashed message and explode the number of error ids for the same failure. Keep this argument constant and move interpolated values into the third "details" argument so monitoring groups the error while still capturing request context.',
|
|
29
|
+
missingThirdArgument: 'HttpsError calls must include a third "details" argument. The message (second argument) is hashed into a stable identifier, so omitting details leaves errors hard to debug and encourages packing variables into the hashed message. Provide a third argument with the request-specific context (object or string) to keep identifiers stable and diagnostics useful.',
|
|
28
30
|
},
|
|
29
31
|
},
|
|
30
32
|
defaultOptions: [],
|
|
@@ -32,23 +34,58 @@ exports.dynamicHttpsErrors = (0, createRule_1.createRule)({
|
|
|
32
34
|
const checkForHttpsError = (node) => {
|
|
33
35
|
const callee = node.callee;
|
|
34
36
|
if (isHttpsErrorCall(callee)) {
|
|
37
|
+
// Check for missing third argument
|
|
38
|
+
if (node.arguments.length < 3) {
|
|
39
|
+
context.report({
|
|
40
|
+
node,
|
|
41
|
+
messageId: 'missingThirdArgument',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// Check for dynamic content in second argument (existing functionality)
|
|
35
45
|
const secondArg = node.arguments[1];
|
|
36
|
-
if (secondArg
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
if (!secondArg)
|
|
47
|
+
return;
|
|
48
|
+
if (secondArg.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
|
|
49
|
+
secondArg.expressions.length > 0) {
|
|
50
|
+
context.report({
|
|
51
|
+
node: secondArg,
|
|
52
|
+
messageId: 'dynamicHttpsErrors',
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// Only string concatenation with "+" can be static; all other operators
|
|
57
|
+
// are treated as dynamic to avoid hashing non-literal message content.
|
|
58
|
+
const isDynamicBinaryExpression = (expression) => {
|
|
59
|
+
if (expression.operator !== '+')
|
|
60
|
+
return true;
|
|
61
|
+
const isStaticLiteral = (expr) => expr.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
62
|
+
typeof expr.value === 'string';
|
|
63
|
+
const isSafe = (expr) => {
|
|
64
|
+
if (expr.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (expr.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
|
|
68
|
+
return !isDynamicBinaryExpression(expr);
|
|
69
|
+
}
|
|
70
|
+
return isStaticLiteral(expr);
|
|
71
|
+
};
|
|
72
|
+
return !(isSafe(expression.left) && isSafe(expression.right));
|
|
73
|
+
};
|
|
74
|
+
if (secondArg.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
|
|
75
|
+
isDynamicBinaryExpression(secondArg)) {
|
|
76
|
+
context.report({
|
|
77
|
+
node: secondArg,
|
|
78
|
+
messageId: 'dynamicHttpsErrors',
|
|
79
|
+
});
|
|
43
80
|
}
|
|
44
81
|
}
|
|
45
82
|
};
|
|
46
83
|
return {
|
|
47
84
|
NewExpression(node) {
|
|
48
|
-
|
|
85
|
+
checkForHttpsError(node);
|
|
49
86
|
},
|
|
50
87
|
CallExpression(node) {
|
|
51
|
-
|
|
88
|
+
checkForHttpsError(node);
|
|
52
89
|
},
|
|
53
90
|
};
|
|
54
91
|
},
|
|
@@ -3,8 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.enforceAssertSafeObjectKey = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const DEFAULT_IMPORT_PATH = 'functions/src/util/assertSafe';
|
|
6
7
|
exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
7
|
-
name: 'enforce-
|
|
8
|
+
name: 'enforce-assert-safe-object-key',
|
|
8
9
|
meta: {
|
|
9
10
|
type: 'problem',
|
|
10
11
|
docs: {
|
|
@@ -12,21 +13,30 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
12
13
|
recommended: 'error',
|
|
13
14
|
},
|
|
14
15
|
fixable: 'code',
|
|
15
|
-
schema: [
|
|
16
|
+
schema: [
|
|
17
|
+
{
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
assertSafeImportPath: { type: 'string' },
|
|
21
|
+
},
|
|
22
|
+
additionalProperties: false,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
16
25
|
messages: {
|
|
17
|
-
useAssertSafe: '
|
|
26
|
+
useAssertSafe: 'Dynamic object key "{{key}}" is used without assertSafe() validation. Unvalidated keys can resolve to unexpected properties (including prototype fields) and make lookups fragile or unsafe. Wrap the key with assertSafe({{key}}) before accessing the object.',
|
|
18
27
|
},
|
|
19
28
|
},
|
|
20
|
-
defaultOptions: [],
|
|
21
|
-
create(context) {
|
|
29
|
+
defaultOptions: [{}],
|
|
30
|
+
create(context, [options]) {
|
|
31
|
+
const importPath = options?.assertSafeImportPath || DEFAULT_IMPORT_PATH;
|
|
22
32
|
let hasAssertSafeImport = false;
|
|
23
33
|
/**
|
|
24
34
|
* Helper function to add assertSafe import if needed
|
|
25
35
|
*/
|
|
26
36
|
const addAssertSafeImport = (fixer) => {
|
|
27
|
-
const program = context.
|
|
37
|
+
const program = context.sourceCode.ast;
|
|
28
38
|
const firstImport = program.body.find((node) => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
|
|
29
|
-
const importStatement =
|
|
39
|
+
const importStatement = `import { assertSafe } from '${importPath}';\n`;
|
|
30
40
|
if (firstImport) {
|
|
31
41
|
return fixer.insertTextBefore(firstImport, importStatement);
|
|
32
42
|
}
|
|
@@ -48,6 +58,14 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
48
58
|
fixes.push(fixer.replaceText(node, `assertSafe(${argText})`));
|
|
49
59
|
return fixes;
|
|
50
60
|
};
|
|
61
|
+
const reportUseAssertSafe = (node, expressionText) => context.report({
|
|
62
|
+
node,
|
|
63
|
+
messageId: 'useAssertSafe',
|
|
64
|
+
data: { key: expressionText },
|
|
65
|
+
fix(fixer) {
|
|
66
|
+
return createFixes(fixer, node, expressionText);
|
|
67
|
+
},
|
|
68
|
+
});
|
|
51
69
|
return {
|
|
52
70
|
ImportDeclaration(node) {
|
|
53
71
|
// Check if assertSafe is already imported
|
|
@@ -64,15 +82,9 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
64
82
|
if (key.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
65
83
|
key.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
66
84
|
key.callee.name === 'String') {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
fix(fixer) {
|
|
71
|
-
const arg = key.arguments[0];
|
|
72
|
-
const argText = context.getSourceCode().getText(arg);
|
|
73
|
-
return createFixes(fixer, key, argText);
|
|
74
|
-
},
|
|
75
|
-
});
|
|
85
|
+
const arg = key.arguments[0];
|
|
86
|
+
const argText = context.sourceCode.getText(arg);
|
|
87
|
+
reportUseAssertSafe(key, argText);
|
|
76
88
|
}
|
|
77
89
|
// Check for template literals like `${id}`
|
|
78
90
|
if (key.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
|
|
@@ -80,15 +92,9 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
80
92
|
key.quasis.length === 2 &&
|
|
81
93
|
key.quasis[0].value.raw === '' &&
|
|
82
94
|
key.quasis[1].value.raw === '') {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
fix(fixer) {
|
|
87
|
-
const expr = key.expressions[0];
|
|
88
|
-
const exprText = context.getSourceCode().getText(expr);
|
|
89
|
-
return createFixes(fixer, key, exprText);
|
|
90
|
-
},
|
|
91
|
-
});
|
|
95
|
+
const expr = key.expressions[0];
|
|
96
|
+
const exprText = context.sourceCode.getText(expr);
|
|
97
|
+
reportUseAssertSafe(key, exprText);
|
|
92
98
|
}
|
|
93
99
|
}
|
|
94
100
|
},
|
|
@@ -100,15 +106,9 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
100
106
|
if (left.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
101
107
|
left.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
102
108
|
left.callee.name === 'String') {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
fix(fixer) {
|
|
107
|
-
const arg = left.arguments[0];
|
|
108
|
-
const argText = context.getSourceCode().getText(arg);
|
|
109
|
-
return createFixes(fixer, left, argText);
|
|
110
|
-
},
|
|
111
|
-
});
|
|
109
|
+
const arg = left.arguments[0];
|
|
110
|
+
const argText = context.sourceCode.getText(arg);
|
|
111
|
+
reportUseAssertSafe(left, argText);
|
|
112
112
|
}
|
|
113
113
|
// Check for template literals like `${id}`
|
|
114
114
|
if (left.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
|
|
@@ -116,15 +116,9 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
116
116
|
left.quasis.length === 2 &&
|
|
117
117
|
left.quasis[0].value.raw === '' &&
|
|
118
118
|
left.quasis[1].value.raw === '') {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
fix(fixer) {
|
|
123
|
-
const expr = left.expressions[0];
|
|
124
|
-
const exprText = context.getSourceCode().getText(expr);
|
|
125
|
-
return createFixes(fixer, left, exprText);
|
|
126
|
-
},
|
|
127
|
-
});
|
|
119
|
+
const expr = left.expressions[0];
|
|
120
|
+
const exprText = context.sourceCode.getText(expr);
|
|
121
|
+
reportUseAssertSafe(left, exprText);
|
|
128
122
|
}
|
|
129
123
|
}
|
|
130
124
|
},
|
|
@@ -163,15 +157,9 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
163
157
|
if (property.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
164
158
|
property.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
165
159
|
property.callee.name === 'String') {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
fix(fixer) {
|
|
170
|
-
const arg = property.arguments[0];
|
|
171
|
-
const argText = context.getSourceCode().getText(arg);
|
|
172
|
-
return createFixes(fixer, property, argText);
|
|
173
|
-
},
|
|
174
|
-
});
|
|
160
|
+
const arg = property.arguments[0];
|
|
161
|
+
const argText = context.sourceCode.getText(arg);
|
|
162
|
+
reportUseAssertSafe(property, argText);
|
|
175
163
|
return;
|
|
176
164
|
}
|
|
177
165
|
// Check for template literals
|
|
@@ -190,16 +178,9 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
190
178
|
// Complex template literals with additional text are fine
|
|
191
179
|
return;
|
|
192
180
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
fix(fixer) {
|
|
197
|
-
// Extract the expression from the template literal
|
|
198
|
-
const expr = property.expressions[0];
|
|
199
|
-
const exprText = context.getSourceCode().getText(expr);
|
|
200
|
-
return createFixes(fixer, property, exprText);
|
|
201
|
-
},
|
|
202
|
-
});
|
|
181
|
+
const expr = property.expressions[0];
|
|
182
|
+
const exprText = context.sourceCode.getText(expr);
|
|
183
|
+
reportUseAssertSafe(property, exprText);
|
|
203
184
|
return;
|
|
204
185
|
}
|
|
205
186
|
// Check for direct variable usage (identifiers)
|
|
@@ -212,15 +193,8 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
212
193
|
if (isLikelyArray) {
|
|
213
194
|
return;
|
|
214
195
|
}
|
|
215
|
-
context.
|
|
216
|
-
|
|
217
|
-
messageId: 'useAssertSafe',
|
|
218
|
-
fix(fixer) {
|
|
219
|
-
// For direct variable use, just use the variable name
|
|
220
|
-
const propText = context.getSourceCode().getText(property);
|
|
221
|
-
return createFixes(fixer, property, propText);
|
|
222
|
-
},
|
|
223
|
-
});
|
|
196
|
+
const propText = context.sourceCode.getText(property);
|
|
197
|
+
reportUseAssertSafe(property, propText);
|
|
224
198
|
return;
|
|
225
199
|
}
|
|
226
200
|
// Check for binary expressions (like index + 1)
|
|
@@ -229,14 +203,8 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
229
203
|
if (isLikelyArray) {
|
|
230
204
|
return;
|
|
231
205
|
}
|
|
232
|
-
context.
|
|
233
|
-
|
|
234
|
-
messageId: 'useAssertSafe',
|
|
235
|
-
fix(fixer) {
|
|
236
|
-
const propText = context.getSourceCode().getText(property);
|
|
237
|
-
return createFixes(fixer, property, propText);
|
|
238
|
-
},
|
|
239
|
-
});
|
|
206
|
+
const propText = context.sourceCode.getText(property);
|
|
207
|
+
reportUseAssertSafe(property, propText);
|
|
240
208
|
return;
|
|
241
209
|
}
|
|
242
210
|
// Check for boolean expressions and other literals
|
|
@@ -247,14 +215,8 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
247
215
|
if (isLikelyArray) {
|
|
248
216
|
return;
|
|
249
217
|
}
|
|
250
|
-
context.
|
|
251
|
-
|
|
252
|
-
messageId: 'useAssertSafe',
|
|
253
|
-
fix(fixer) {
|
|
254
|
-
const propText = context.getSourceCode().getText(property);
|
|
255
|
-
return createFixes(fixer, property, propText);
|
|
256
|
-
},
|
|
257
|
-
});
|
|
218
|
+
const propText = context.sourceCode.getText(property);
|
|
219
|
+
reportUseAssertSafe(property, propText);
|
|
258
220
|
return;
|
|
259
221
|
}
|
|
260
222
|
// Check for function calls (anything that isn't handled above)
|
|
@@ -266,14 +228,8 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
266
228
|
if (isLikelyArray) {
|
|
267
229
|
return;
|
|
268
230
|
}
|
|
269
|
-
context.
|
|
270
|
-
|
|
271
|
-
messageId: 'useAssertSafe',
|
|
272
|
-
fix(fixer) {
|
|
273
|
-
const propText = context.getSourceCode().getText(property);
|
|
274
|
-
return createFixes(fixer, property, propText);
|
|
275
|
-
},
|
|
276
|
-
});
|
|
231
|
+
const propText = context.sourceCode.getText(property);
|
|
232
|
+
reportUseAssertSafe(property, propText);
|
|
277
233
|
return;
|
|
278
234
|
}
|
|
279
235
|
}
|
|
@@ -281,4 +237,4 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
281
237
|
};
|
|
282
238
|
},
|
|
283
239
|
});
|
|
284
|
-
//# sourceMappingURL=enforce-
|
|
240
|
+
//# sourceMappingURL=enforce-assert-safe-object-key.js.map
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
type MessageIds = 'assertShouldThrow' | 'shouldBeAssertPrefixed';
|
|
2
|
+
export declare const enforceAssertThrows: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
3
|
+
export {};
|