@blumintinc/eslint-plugin-blumint 1.21.2 → 1.21.4
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/lib/index.js +1 -1
- package/lib/rules/enforce-boolean-naming-prefixes.js +28 -5
- package/lib/rules/enforce-centralized-mock-firestore.d.ts +2 -1
- package/lib/rules/enforce-centralized-mock-firestore.js +173 -38
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +78 -13
- package/lib/rules/enforce-firestore-set-merge.js +155 -4
- package/lib/rules/enforce-object-literal-as-const.js +85 -4
- package/lib/rules/enforce-positive-naming.js +17 -13
- package/lib/rules/enforce-snapshot-state-narrowing.d.ts +1 -1
- package/lib/rules/enforce-snapshot-state-narrowing.js +39 -19
- package/lib/rules/memo-nested-react-components.js +12 -0
- package/lib/rules/no-entire-object-hook-deps.js +26 -3
- package/lib/rules/no-hungarian.js +14 -0
- package/lib/rules/no-inline-component-prop.js +83 -14
- package/lib/rules/no-restricted-properties-fix.js +53 -3
- package/lib/rules/no-useless-usememo-primitives.js +43 -11
- package/lib/rules/prefer-destructuring-no-class.js +65 -11
- package/lib/rules/prefer-document-flattening.js +71 -35
- package/lib/rules/prefer-map-over-conditional-dispatch.js +112 -5
- package/lib/rules/require-memoize-jsx-returners.js +18 -4
- package/lib/rules/use-custom-memo.js +4 -6
- package/lib/utils/memoModule.d.ts +26 -0
- package/lib/utils/memoModule.js +54 -0
- package/package.json +1 -1
- package/release-manifest.json +148 -0
|
@@ -140,6 +140,32 @@ function attributePrintedName(intended, printed, type) {
|
|
|
140
140
|
}
|
|
141
141
|
recordAttribution(intended, printed, symbol);
|
|
142
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* The bare identifier a declaration's own type annotation is written as, or
|
|
145
|
+
* null when the annotation is anything else.
|
|
146
|
+
*
|
|
147
|
+
* Only an un-parameterised reference to a plain identifier qualifies. A
|
|
148
|
+
* qualified name (`Theme.Mode`) needs its namespace to resolve at the fix site,
|
|
149
|
+
* which this answer cannot establish; a parameterised one (`Alias<'x'>`) is not
|
|
150
|
+
* denoted by its head at all, and emitting the head alone would name a
|
|
151
|
+
* different type. Both keep the printed union rather than guess.
|
|
152
|
+
*/
|
|
153
|
+
function annotatedTypeReferenceName(declaration) {
|
|
154
|
+
const annotation = ts.isParameter(declaration) ||
|
|
155
|
+
ts.isVariableDeclaration(declaration) ||
|
|
156
|
+
ts.isPropertySignature(declaration) ||
|
|
157
|
+
ts.isPropertyDeclaration(declaration)
|
|
158
|
+
? declaration.type
|
|
159
|
+
: undefined;
|
|
160
|
+
if (!annotation ||
|
|
161
|
+
!ts.isTypeReferenceNode(annotation) ||
|
|
162
|
+
!ts.isIdentifier(annotation.typeName) ||
|
|
163
|
+
(annotation.typeArguments?.length ?? 0) > 0) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const name = annotation.typeName.text;
|
|
167
|
+
return BARE_TYPE_NAME.test(name) ? name : null;
|
|
168
|
+
}
|
|
143
169
|
/**
|
|
144
170
|
* Function/constructor/conditional type notation must be parenthesized to
|
|
145
171
|
* appear as a `|` union member, or the emitted annotation does not parse
|
|
@@ -833,11 +859,75 @@ exports.preferMapOverConditionalDispatch = (0, createRule_1.createRule)({
|
|
|
833
859
|
return null;
|
|
834
860
|
}
|
|
835
861
|
}
|
|
862
|
+
/**
|
|
863
|
+
* The bare type-alias name written on the discriminant's own annotation,
|
|
864
|
+
* when that name is provably the union the checker printed the members of.
|
|
865
|
+
*
|
|
866
|
+
* `checker.typeToString()` keeps an alias only while the type still carries
|
|
867
|
+
* an `aliasSymbol`, and TypeScript drops that resolving an indexed access
|
|
868
|
+
* over a `typeof` query — `const M = ['a', 'b'] as const; type Mode =
|
|
869
|
+
* (typeof M)[number]`, which is exactly the shape the key-space companion
|
|
870
|
+
* rule named on this rule's docs page emits. The printed text there is the
|
|
871
|
+
* widened literal union, the one spelling this rule's exhaustiveness
|
|
872
|
+
* promise exists to avoid: a stale `Record<'a' | 'b', V>` keeps
|
|
873
|
+
* typechecking after the union grows and only the LOOKUP complains, as
|
|
874
|
+
* TS7053 — an implicit-any diagnostic that disappears entirely under
|
|
875
|
+
* `noImplicitAny: false` and leaves the new member yielding `undefined` at
|
|
876
|
+
* runtime (#2009).
|
|
877
|
+
*
|
|
878
|
+
* The name is read off SYNTAX because the type no longer carries it, so it
|
|
879
|
+
* ships only against evidence that it denotes this very union: a bare,
|
|
880
|
+
* un-parameterised type reference written on the declaration the
|
|
881
|
+
* discriminant resolves to, whose symbol — resolved where the `Record`
|
|
882
|
+
* lands, not where the annotation was written — declares exactly this type.
|
|
883
|
+
* Mere presence of the name is not enough. Naming the WRONG union in the
|
|
884
|
+
* emitted `Record` breaks the build where the widened union merely weakens
|
|
885
|
+
* it, so anything short of type identity declines and keeps the printed
|
|
886
|
+
* text. Flow narrowing declines through the same check: a discriminant
|
|
887
|
+
* narrowed above the dispatch has a type the alias does not declare, and
|
|
888
|
+
* the alias would demand keys the construct has no branches for.
|
|
889
|
+
*/
|
|
890
|
+
function annotationAliasTypeText(discriminant, discriminantType) {
|
|
891
|
+
const tsNode = esTreeNodeToTSNodeMap.get(unwrapLink(discriminant));
|
|
892
|
+
const tsFixSite = esTreeNodeToTSNodeMap.get(discriminant);
|
|
893
|
+
if (!tsNode || !tsFixSite) {
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
try {
|
|
897
|
+
const symbol = checker.getSymbolAtLocation(tsNode);
|
|
898
|
+
const names = new Set((symbol?.declarations ?? [])
|
|
899
|
+
.map(annotatedTypeReferenceName)
|
|
900
|
+
.filter((name) => name !== null));
|
|
901
|
+
// Two declarations annotated with different names (a merged symbol, an
|
|
902
|
+
// overload set) leave no single spelling that is right for both.
|
|
903
|
+
if (names.size !== 1) {
|
|
904
|
+
return null;
|
|
905
|
+
}
|
|
906
|
+
const [name] = names;
|
|
907
|
+
const inScope = checker
|
|
908
|
+
.getSymbolsInScope(tsFixSite, ts.SymbolFlags.Type | ts.SymbolFlags.Alias)
|
|
909
|
+
.find((candidate) => candidate.name === name);
|
|
910
|
+
if (!inScope) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
const declaredSymbol = inScope.flags & ts.SymbolFlags.Alias
|
|
914
|
+
? checker.getAliasedSymbol(inScope)
|
|
915
|
+
: inScope;
|
|
916
|
+
if (checker.getDeclaredTypeOfSymbol(declaredSymbol) !== discriminantType) {
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
return { text: name, symbol: declaredSymbol };
|
|
920
|
+
}
|
|
921
|
+
catch {
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
836
925
|
/**
|
|
837
926
|
* Key type for the emitted `Record`. A discriminant whose own type prints
|
|
838
927
|
* as a bare name already carries the union's identity, so that name is
|
|
839
|
-
* kept; otherwise the fix reaches for the tag's declaring type
|
|
840
|
-
*
|
|
928
|
+
* kept; otherwise the fix reaches for the tag's declaring type (#1926),
|
|
929
|
+
* then for the alias its annotation names where the checker resolved past
|
|
930
|
+
* one (#2009), before settling for the resolved literal union.
|
|
841
931
|
*/
|
|
842
932
|
function discriminantTypeText(type, discriminant) {
|
|
843
933
|
let printed;
|
|
@@ -865,10 +955,27 @@ exports.preferMapOverConditionalDispatch = (0, createRule_1.createRule)({
|
|
|
865
955
|
// already ships only when the name resolves, at the fix site, to exactly
|
|
866
956
|
// the object's declared type, and identity of the TYPE accepts a nearer
|
|
867
957
|
// alias that denotes the same type — which compiles, and reads the same.
|
|
868
|
-
const
|
|
869
|
-
|
|
958
|
+
const indexed = indexedAccessTypeText(discriminant, type);
|
|
959
|
+
if (indexed !== null) {
|
|
960
|
+
return { text: normalizeTypeQuotes(indexed, singleQuote), intended };
|
|
961
|
+
}
|
|
962
|
+
// Recovery runs only where the printer already produced usable text, so
|
|
963
|
+
// it can only change the SPELLING of a key type the rule was going to
|
|
964
|
+
// emit anyway — never whether the rule fires. A discriminant the printer
|
|
965
|
+
// could not print at all stays silent exactly as before.
|
|
966
|
+
if (printed !== null) {
|
|
967
|
+
const alias = annotationAliasTypeText(discriminant, type);
|
|
968
|
+
if (alias) {
|
|
969
|
+
// Unlike the printed bare name, this one was never checked against
|
|
970
|
+
// the fix site's scope by the printer, so it carries its symbol into
|
|
971
|
+
// the gate for the same shadow check (#2229).
|
|
972
|
+
recordAttribution(intended, alias.text, alias.symbol);
|
|
973
|
+
return { text: alias.text, intended };
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return printed === null
|
|
870
977
|
? null
|
|
871
|
-
: { text: normalizeTypeQuotes(
|
|
978
|
+
: { text: normalizeTypeQuotes(printed, singleQuote), intended };
|
|
872
979
|
}
|
|
873
980
|
/**
|
|
874
981
|
* Whether the binding a type name reaches at the fix site is the symbol the
|
|
@@ -6,6 +6,7 @@ const createRule_1 = require("../utils/createRule");
|
|
|
6
6
|
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
7
7
|
const disableDirectives_1 = require("../utils/disableDirectives");
|
|
8
8
|
const importInsertion_1 = require("../utils/importInsertion");
|
|
9
|
+
const memoModule_1 = require("../utils/memoModule");
|
|
9
10
|
const MEMOIZE_PREFERRED_MODULE = '@blumintinc/typescript-memoize';
|
|
10
11
|
const MEMOIZE_MODULES = new Set([
|
|
11
12
|
MEMOIZE_PREFERRED_MODULE,
|
|
@@ -292,7 +293,7 @@ function callExpressionReturnsJSX(expression, knownFunctions, cache, factoryCont
|
|
|
292
293
|
if (isIdentifierReturningJsx(callee, knownFunctions, cache, factoryContext)) {
|
|
293
294
|
return true;
|
|
294
295
|
}
|
|
295
|
-
if (factoryContext.
|
|
296
|
+
if (factoryContext.memoIdentifiers.has(callee.name) &&
|
|
296
297
|
firstNonSpreadArgument &&
|
|
297
298
|
expressionReturnsJSX(firstNonSpreadArgument, knownFunctions, cache, factoryContext)) {
|
|
298
299
|
return true;
|
|
@@ -567,11 +568,11 @@ exports.requireMemoizeJsxReturners = (0, createRule_1.createRule)({
|
|
|
567
568
|
*/
|
|
568
569
|
const isReportSuppressed = (0, disableDirectives_1.createSuppressionChecker)(context);
|
|
569
570
|
const jsxReturnCache = new WeakMap();
|
|
570
|
-
const
|
|
571
|
+
const memoIdentifiers = new Set();
|
|
571
572
|
const reactNamespaceIdentifiers = new Set();
|
|
572
573
|
const reactCreateElementIdentifiers = new Set();
|
|
573
574
|
const factoryContext = {
|
|
574
|
-
|
|
575
|
+
memoIdentifiers,
|
|
575
576
|
reactNamespaceIdentifiers,
|
|
576
577
|
reactCreateElementIdentifiers,
|
|
577
578
|
};
|
|
@@ -583,7 +584,7 @@ exports.requireMemoizeJsxReturners = (0, createRule_1.createRule)({
|
|
|
583
584
|
if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
584
585
|
specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
585
586
|
specifier.imported.name === 'memo') {
|
|
586
|
-
|
|
587
|
+
memoIdentifiers.add(specifier.local?.name ?? specifier.imported.name);
|
|
587
588
|
}
|
|
588
589
|
else if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
589
590
|
specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
@@ -598,6 +599,19 @@ exports.requireMemoizeJsxReturners = (0, createRule_1.createRule)({
|
|
|
598
599
|
}
|
|
599
600
|
});
|
|
600
601
|
}
|
|
602
|
+
else if ((0, memoModule_1.isCustomMemoModuleImport)(sourceValue, filename)) {
|
|
603
|
+
// The wrapper re-exports React's `memo` and nothing else this rule
|
|
604
|
+
// reads, so only the `memo` binding is collected: `createElement` and
|
|
605
|
+
// a namespace object stay bound to a react import, keeping an
|
|
606
|
+
// unrelated `X.createElement` out.
|
|
607
|
+
node.specifiers.forEach((specifier) => {
|
|
608
|
+
if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
609
|
+
specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
610
|
+
specifier.imported.name === 'memo') {
|
|
611
|
+
memoIdentifiers.add(specifier.local?.name ?? specifier.imported.name);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
}
|
|
601
615
|
if (!MEMOIZE_MODULES.has(sourceValue)) {
|
|
602
616
|
return;
|
|
603
617
|
}
|
|
@@ -3,9 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.useCustomMemo = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
-
const
|
|
7
|
-
/** `MEMO_MODULE` carries the quotes the fixer emits; a path never does. */
|
|
8
|
-
const MEMO_MODULE_PATH = MEMO_MODULE.slice(1, -1);
|
|
6
|
+
const memoModule_1 = require("../utils/memoModule");
|
|
9
7
|
const SOURCE_EXTENSION = /\.(?:ts|tsx|js|jsx)$/;
|
|
10
8
|
/**
|
|
11
9
|
* The module the fixer points every `memo` import at is the one module that must
|
|
@@ -20,10 +18,10 @@ const SOURCE_EXTENSION = /\.(?:ts|tsx|js|jsx)$/;
|
|
|
20
18
|
*/
|
|
21
19
|
const isMemoModule = (filename) => {
|
|
22
20
|
const normalized = filename.replace(/\\/g, '/').replace(SOURCE_EXTENSION, '');
|
|
23
|
-
if (!normalized.endsWith(
|
|
21
|
+
if (!normalized.endsWith(memoModule_1.CUSTOM_MEMO_MODULE_PATH)) {
|
|
24
22
|
return false;
|
|
25
23
|
}
|
|
26
|
-
const suffixStart = normalized.length -
|
|
24
|
+
const suffixStart = normalized.length - memoModule_1.CUSTOM_MEMO_MODULE_PATH.length;
|
|
27
25
|
return suffixStart === 0 || normalized[suffixStart - 1] === '/';
|
|
28
26
|
};
|
|
29
27
|
const isComment = (token) => token.type === utils_1.AST_TOKEN_TYPES.Line || token.type === utils_1.AST_TOKEN_TYPES.Block;
|
|
@@ -206,7 +204,7 @@ exports.useCustomMemo = (0, createRule_1.createRule)({
|
|
|
206
204
|
messageId: 'useCustomMemo',
|
|
207
205
|
fix(fixer) {
|
|
208
206
|
const sourceCode = context.getSourceCode();
|
|
209
|
-
const memoImport = buildImport(memoSpecifiers,
|
|
207
|
+
const memoImport = buildImport(memoSpecifiers, memoModule_1.CUSTOM_MEMO_MODULE_SOURCE, node.importKind, sourceCode);
|
|
210
208
|
const survivingSpecifiers = node.specifiers.filter((specifier) => !isMemoSpecifier(specifier));
|
|
211
209
|
const comments = sourceCode
|
|
212
210
|
.getCommentsInside(node)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The project's memo wrapper: the module `use-custom-memo`'s fixer points every
|
|
3
|
+
* `import { memo } from 'react'` at. It re-exports React's `memo`, so a rule
|
|
4
|
+
* that recognises a `memo(...)` call has to accept this module's binding as
|
|
5
|
+
* React's — keying recognition on the `'react'` spelling alone lets that fixer
|
|
6
|
+
* switch the recogniser off on the very convention this plugin enforces.
|
|
7
|
+
*
|
|
8
|
+
* The quotes live inside the value because the fixer splices the specifier into
|
|
9
|
+
* emitted import text verbatim, beside `sourceCode.getText(node.source)`, which
|
|
10
|
+
* carries its own quotes.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CUSTOM_MEMO_MODULE_SOURCE = "'src/util/memo'";
|
|
13
|
+
/** `CUSTOM_MEMO_MODULE_SOURCE` carries the quotes the fixer emits; a path never does. */
|
|
14
|
+
export declare const CUSTOM_MEMO_MODULE_PATH: string;
|
|
15
|
+
/** Whether an import specifier, read alone, names the memo wrapper module. */
|
|
16
|
+
export declare const isCustomMemoModuleSource: (source: string) => boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Whether `source`, imported from `filename`, names the memo wrapper module.
|
|
19
|
+
*
|
|
20
|
+
* A relative specifier is resolved against the importing file before matching,
|
|
21
|
+
* because a file that sits inside the wrapper's own directory spells the same
|
|
22
|
+
* module `../memo` — a specifier naming no `util` segment of its own. Only
|
|
23
|
+
* relative specifiers resolve: a bare specifier is a package or an alias whose
|
|
24
|
+
* target the linter cannot know without a resolver.
|
|
25
|
+
*/
|
|
26
|
+
export declare const isCustomMemoModuleImport: (source: string, filename?: string) => boolean;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isCustomMemoModuleImport = exports.isCustomMemoModuleSource = exports.CUSTOM_MEMO_MODULE_PATH = exports.CUSTOM_MEMO_MODULE_SOURCE = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
/**
|
|
9
|
+
* The project's memo wrapper: the module `use-custom-memo`'s fixer points every
|
|
10
|
+
* `import { memo } from 'react'` at. It re-exports React's `memo`, so a rule
|
|
11
|
+
* that recognises a `memo(...)` call has to accept this module's binding as
|
|
12
|
+
* React's — keying recognition on the `'react'` spelling alone lets that fixer
|
|
13
|
+
* switch the recogniser off on the very convention this plugin enforces.
|
|
14
|
+
*
|
|
15
|
+
* The quotes live inside the value because the fixer splices the specifier into
|
|
16
|
+
* emitted import text verbatim, beside `sourceCode.getText(node.source)`, which
|
|
17
|
+
* carries its own quotes.
|
|
18
|
+
*/
|
|
19
|
+
exports.CUSTOM_MEMO_MODULE_SOURCE = `'src/util/memo'`;
|
|
20
|
+
/** `CUSTOM_MEMO_MODULE_SOURCE` carries the quotes the fixer emits; a path never does. */
|
|
21
|
+
exports.CUSTOM_MEMO_MODULE_PATH = exports.CUSTOM_MEMO_MODULE_SOURCE.slice(1, -1);
|
|
22
|
+
/**
|
|
23
|
+
* The trailing segments that identify the wrapper module however it is spelled.
|
|
24
|
+
* Only the leading segments vary between spellings of the same module — the
|
|
25
|
+
* `src/` alias the fixer emits, a `@/` alias, or any depth of `../` — so the
|
|
26
|
+
* tail is what a specifier has to carry. It has to land on a segment boundary,
|
|
27
|
+
* so `./memo`, `lodash-memo` and `react-memo` name other modules and stay out.
|
|
28
|
+
*/
|
|
29
|
+
const MODULE_TAIL = exports.CUSTOM_MEMO_MODULE_PATH.split('/').slice(-2).join('/');
|
|
30
|
+
const MODULE_TAIL_PATTERN = new RegExp(`(?:^|/)${MODULE_TAIL}$`);
|
|
31
|
+
/** Whether an import specifier, read alone, names the memo wrapper module. */
|
|
32
|
+
const isCustomMemoModuleSource = (source) => MODULE_TAIL_PATTERN.test(source);
|
|
33
|
+
exports.isCustomMemoModuleSource = isCustomMemoModuleSource;
|
|
34
|
+
/**
|
|
35
|
+
* Whether `source`, imported from `filename`, names the memo wrapper module.
|
|
36
|
+
*
|
|
37
|
+
* A relative specifier is resolved against the importing file before matching,
|
|
38
|
+
* because a file that sits inside the wrapper's own directory spells the same
|
|
39
|
+
* module `../memo` — a specifier naming no `util` segment of its own. Only
|
|
40
|
+
* relative specifiers resolve: a bare specifier is a package or an alias whose
|
|
41
|
+
* target the linter cannot know without a resolver.
|
|
42
|
+
*/
|
|
43
|
+
const isCustomMemoModuleImport = (source, filename) => {
|
|
44
|
+
if ((0, exports.isCustomMemoModuleSource)(source)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (!filename || !source.startsWith('.')) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const importerDirectory = path_1.default.posix.dirname(filename.replace(/\\/g, '/'));
|
|
51
|
+
return (0, exports.isCustomMemoModuleSource)(path_1.default.posix.normalize(path_1.default.posix.join(importerDirectory, source)));
|
|
52
|
+
};
|
|
53
|
+
exports.isCustomMemoModuleImport = isCustomMemoModuleImport;
|
|
54
|
+
//# sourceMappingURL=memoModule.js.map
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,152 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.21.4",
|
|
4
|
+
"date": "2026-09-04T11:47:38.427Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "prefer-map-over-conditional-dispatch",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
2009
|
|
11
|
+
],
|
|
12
|
+
"summary": "keep the alias when the checker resolves past it (closes #2009)"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"version": "1.21.3",
|
|
18
|
+
"date": "2026-09-03T23:34:34.310Z",
|
|
19
|
+
"rules": [
|
|
20
|
+
{
|
|
21
|
+
"name": "enforce-boolean-naming-prefixes",
|
|
22
|
+
"changeType": "fix",
|
|
23
|
+
"issues": [
|
|
24
|
+
2308
|
|
25
|
+
],
|
|
26
|
+
"summary": "accept `??` as a boolean fallback operator (closes #2308)"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "enforce-centralized-mock-firestore",
|
|
30
|
+
"changeType": "fix",
|
|
31
|
+
"issues": [
|
|
32
|
+
2307
|
|
33
|
+
],
|
|
34
|
+
"summary": "match the local mock by normalized name so a MOCK_FIRESTORE constant is flagged (closes #2307)"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "enforce-fieldpath-syntax-in-docsetter",
|
|
38
|
+
"changeType": "fix",
|
|
39
|
+
"issues": [
|
|
40
|
+
2303
|
|
41
|
+
],
|
|
42
|
+
"summary": "decline the fix when a flattened key collides (closes #2303)"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "enforce-firestore-set-merge",
|
|
46
|
+
"changeType": "fix",
|
|
47
|
+
"issues": [
|
|
48
|
+
2311
|
|
49
|
+
],
|
|
50
|
+
"summary": "decline the merge rewrite on update's varargs and precondition forms (closes #2311)"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "enforce-object-literal-as-const",
|
|
54
|
+
"changeType": "fix",
|
|
55
|
+
"issues": [
|
|
56
|
+
2319
|
|
57
|
+
],
|
|
58
|
+
"summary": "fall back to the sole return's own assertion for the declared type (closes #2319)"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "enforce-positive-naming",
|
|
62
|
+
"changeType": "fix",
|
|
63
|
+
"issues": [
|
|
64
|
+
2306
|
|
65
|
+
],
|
|
66
|
+
"summary": "make the boolean-like gate spelling-independent (closes #2306)"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "enforce-snapshot-state-narrowing",
|
|
70
|
+
"changeType": "fix",
|
|
71
|
+
"issues": [
|
|
72
|
+
2315
|
|
73
|
+
],
|
|
74
|
+
"summary": "accept ?? alongside || in the logical guard (closes #2315)"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "memo-nested-react-components",
|
|
78
|
+
"changeType": "fix",
|
|
79
|
+
"issues": [
|
|
80
|
+
2313
|
|
81
|
+
],
|
|
82
|
+
"summary": "admit useLatestCallback as a callback hook (closes #2313)"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "no-entire-object-hook-deps",
|
|
86
|
+
"changeType": "fix",
|
|
87
|
+
"issues": [
|
|
88
|
+
2309
|
|
89
|
+
],
|
|
90
|
+
"summary": "govern the deep-compare hooks, which the useMemo rename walked out of view (closes #2309)"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "no-hungarian",
|
|
94
|
+
"changeType": "fix",
|
|
95
|
+
"issues": [
|
|
96
|
+
2317
|
|
97
|
+
],
|
|
98
|
+
"summary": "read a leading single-letter segment as a prefix in SCREAMING_SNAKE_CASE (closes #2317)"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "no-inline-component-prop",
|
|
102
|
+
"changeType": "fix",
|
|
103
|
+
"issues": [
|
|
104
|
+
2314
|
|
105
|
+
],
|
|
106
|
+
"summary": "resolve a memo(namedReference) argument to its declaration (closes #2314)"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "no-restricted-properties-fix",
|
|
110
|
+
"changeType": "fix",
|
|
111
|
+
"issues": [
|
|
112
|
+
2318
|
|
113
|
+
],
|
|
114
|
+
"summary": "match a configured object through global-const-style's rename (closes #2318)"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "no-useless-usememo-primitives",
|
|
118
|
+
"changeType": "fix",
|
|
119
|
+
"issues": [
|
|
120
|
+
2312
|
|
121
|
+
],
|
|
122
|
+
"summary": "recognise the deep-compare memo spelling so a renamed callee still reports (closes #2312)"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"name": "prefer-destructuring-no-class",
|
|
126
|
+
"changeType": "fix",
|
|
127
|
+
"issues": [
|
|
128
|
+
2316
|
|
129
|
+
],
|
|
130
|
+
"summary": "match a binding to its property ignoring case and underscores (closes #2316)"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "prefer-document-flattening",
|
|
134
|
+
"changeType": "fix",
|
|
135
|
+
"issues": [
|
|
136
|
+
2304
|
|
137
|
+
],
|
|
138
|
+
"summary": "rewrite an existing shouldFlatten in place rather than appending a duplicate key (closes #2304)"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"name": "require-memoize-jsx-returners",
|
|
142
|
+
"changeType": "fix",
|
|
143
|
+
"issues": [
|
|
144
|
+
2305
|
|
145
|
+
],
|
|
146
|
+
"summary": "re-cut the degenerate-identifier duplicate ceiling for the wrapper-memo fixtures (closes #2305); recognise memo imported from the project memo wrapper (closes #2305)"
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
},
|
|
2
150
|
{
|
|
3
151
|
"version": "1.21.2",
|
|
4
152
|
"date": "2026-09-03T01:55:23.074Z",
|