@blumintinc/eslint-plugin-blumint 1.21.15 → 1.21.16
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-empty-object-check.js +381 -24
- package/lib/rules/enforce-memoize-async.js +300 -54
- package/lib/rules/global-const-style.js +239 -26
- package/lib/rules/no-compositing-layer-props.js +33 -14
- package/lib/rules/require-memo.js +64 -2
- package/lib/utils/composedFixConfig.js +2 -3
- package/lib/utils/docsFixtures.js +3 -3
- package/lib/utils/fixtureCorpus.js +2 -3
- package/lib/utils/loadPlugin.d.ts +41 -0
- package/lib/utils/loadPlugin.js +33 -0
- package/package.json +5 -1
- package/release-manifest.json +50 -0
package/lib/index.js
CHANGED
|
@@ -235,6 +235,234 @@ function isObjectLikeType(type, checker) {
|
|
|
235
235
|
}
|
|
236
236
|
return 'object';
|
|
237
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Lib wrappers that hand back the shape they are given.
|
|
240
|
+
*
|
|
241
|
+
* `Readonly<T>` only adds modifiers and `Partial<T>` only removes
|
|
242
|
+
* required-ness, so a pinned `T` stays pinned through either, which is what
|
|
243
|
+
* makes `Readonly<Record<string, string>>` readable as the dictionary it is.
|
|
244
|
+
* The unwrapping applies only when the INNER type is itself pinned, so
|
|
245
|
+
* `Readonly<NextResponse>` stays unread and the class instance it names keeps
|
|
246
|
+
* its decline.
|
|
247
|
+
*/
|
|
248
|
+
const SHAPE_PRESERVING_TYPE_WRAPPERS = new Set(['Readonly', 'Partial']);
|
|
249
|
+
/** Type keywords that contribute no shape to a union. */
|
|
250
|
+
const NULLABLE_TYPE_KEYWORDS = new Set([
|
|
251
|
+
utils_1.AST_NODE_TYPES.TSNullKeyword,
|
|
252
|
+
utils_1.AST_NODE_TYPES.TSUndefinedKeyword,
|
|
253
|
+
utils_1.AST_NODE_TYPES.TSVoidKeyword,
|
|
254
|
+
]);
|
|
255
|
+
/** The statements a node holds directly, for a same-file declaration lookup. */
|
|
256
|
+
function statementsOf(node) {
|
|
257
|
+
switch (node.type) {
|
|
258
|
+
case utils_1.AST_NODE_TYPES.Program:
|
|
259
|
+
case utils_1.AST_NODE_TYPES.BlockStatement:
|
|
260
|
+
case utils_1.AST_NODE_TYPES.TSModuleBlock:
|
|
261
|
+
return node.body;
|
|
262
|
+
default:
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* The type a SAME-FILE alias stands for, walking outwards from the reference so
|
|
268
|
+
* that an inner declaration answers ahead of an outer one.
|
|
269
|
+
*
|
|
270
|
+
* Same-file is the whole boundary: an alias imported from a module this program
|
|
271
|
+
* did not load is not found here and stays unpinned, which is the line #2344
|
|
272
|
+
* drew. Following the import instead would need exactly the cross-file
|
|
273
|
+
* resolution whose absence puts this code on the fall-through in the first
|
|
274
|
+
* place.
|
|
275
|
+
*
|
|
276
|
+
* A GENERIC alias is unpinned for a related reason: its body is written against
|
|
277
|
+
* parameters this lookup does not substitute, and each parameter SHADOWS any
|
|
278
|
+
* same-file alias of the same name. Reading `type Wrapper<Cfg> = Readonly<Cfg>`
|
|
279
|
+
* against a sibling `type Cfg = Record<string, string>` calls
|
|
280
|
+
* `Wrapper<NextResponse>` a dictionary and reinstates the inverted guard.
|
|
281
|
+
*/
|
|
282
|
+
function resolveTypeAlias(reference, name) {
|
|
283
|
+
let current = reference;
|
|
284
|
+
while (current) {
|
|
285
|
+
const statements = statementsOf(current);
|
|
286
|
+
if (statements) {
|
|
287
|
+
for (const statement of statements) {
|
|
288
|
+
const declaration = statement.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration &&
|
|
289
|
+
statement.declaration
|
|
290
|
+
? statement.declaration
|
|
291
|
+
: statement;
|
|
292
|
+
if (declaration.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration &&
|
|
293
|
+
declaration.id.name === name) {
|
|
294
|
+
return declaration.typeParameters ? null : declaration.typeAnnotation;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
current = current.parent;
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* The shape an object type literal writes out.
|
|
304
|
+
*
|
|
305
|
+
* The members answer the same question `isObjectLikeType` asks of a resolved
|
|
306
|
+
* type: a required property makes `Object.keys()` non-empty for every valid
|
|
307
|
+
* value, and a call or construct signature marks behaviour rather than data, so
|
|
308
|
+
* either one leaves the literal unpinned.
|
|
309
|
+
*/
|
|
310
|
+
function pinnedShapeOfMembers(members) {
|
|
311
|
+
let shape = 'optional';
|
|
312
|
+
for (const member of members) {
|
|
313
|
+
switch (member.type) {
|
|
314
|
+
case utils_1.AST_NODE_TYPES.TSIndexSignature:
|
|
315
|
+
shape = 'index';
|
|
316
|
+
break;
|
|
317
|
+
case utils_1.AST_NODE_TYPES.TSPropertySignature:
|
|
318
|
+
case utils_1.AST_NODE_TYPES.TSMethodSignature:
|
|
319
|
+
if (!member.optional) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
break;
|
|
323
|
+
default:
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return shape;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Whether a declared type the checker cannot resolve nonetheless SPELLS a
|
|
331
|
+
* dictionary, and with which shape.
|
|
332
|
+
*
|
|
333
|
+
* A union answers as soon as one member spells one, mirroring
|
|
334
|
+
* `isObjectLikeType`, where any object-like member makes the whole union
|
|
335
|
+
* object-like. An intersection is the opposite and needs every member pinned:
|
|
336
|
+
* `Record<string, string> & SomeClass` carries the class's required members, so
|
|
337
|
+
* a complete program calls it `non-object`, and reading it as a dictionary
|
|
338
|
+
* would recreate the inverted guard the surrounding branch exists to prevent.
|
|
339
|
+
*
|
|
340
|
+
* `expanding` holds the aliases on the current expansion PATH rather than every
|
|
341
|
+
* alias seen, so a self-referential alias terminates while an alias named twice
|
|
342
|
+
* in sibling positions is still read at each of them.
|
|
343
|
+
*/
|
|
344
|
+
function pinnedShapeOf(node, expanding = new Set()) {
|
|
345
|
+
switch (node.type) {
|
|
346
|
+
case utils_1.AST_NODE_TYPES.TSTypeLiteral:
|
|
347
|
+
return pinnedShapeOfMembers(node.members);
|
|
348
|
+
case utils_1.AST_NODE_TYPES.TSUnionType: {
|
|
349
|
+
const shapes = node.types
|
|
350
|
+
.filter((member) => !NULLABLE_TYPE_KEYWORDS.has(member.type))
|
|
351
|
+
.map((member) => pinnedShapeOf(member, expanding));
|
|
352
|
+
if (!shapes.some((shape) => shape !== null)) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
return shapes.every((shape) => shape === 'index') ? 'index' : 'optional';
|
|
356
|
+
}
|
|
357
|
+
case utils_1.AST_NODE_TYPES.TSIntersectionType: {
|
|
358
|
+
const shapes = node.types.map((member) => pinnedShapeOf(member, expanding));
|
|
359
|
+
if (shapes.some((shape) => shape === null)) {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
return shapes.every((shape) => shape === 'index') ? 'index' : 'optional';
|
|
363
|
+
}
|
|
364
|
+
case utils_1.AST_NODE_TYPES.TSTypeReference: {
|
|
365
|
+
if (node.typeName.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
const { name } = node.typeName;
|
|
369
|
+
/**
|
|
370
|
+
* A same-file alias is read ahead of the lib names, so a file declaring
|
|
371
|
+
* its own `Record` or `Readonly` is answered by what it wrote.
|
|
372
|
+
*/
|
|
373
|
+
const alias = resolveTypeAlias(node, name);
|
|
374
|
+
if (alias) {
|
|
375
|
+
if (expanding.has(alias)) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
expanding.add(alias);
|
|
379
|
+
const aliased = pinnedShapeOf(alias, expanding);
|
|
380
|
+
expanding.delete(alias);
|
|
381
|
+
return aliased;
|
|
382
|
+
}
|
|
383
|
+
if (name === 'Record') {
|
|
384
|
+
return 'index';
|
|
385
|
+
}
|
|
386
|
+
const args = node.typeParameters?.params ?? [];
|
|
387
|
+
if (args.length !== 1) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
if (SHAPE_PRESERVING_TYPE_WRAPPERS.has(name)) {
|
|
391
|
+
return pinnedShapeOf(args[0], expanding);
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* `Required<T>` makes every named member required, so it preserves only a
|
|
395
|
+
* type whose keys come from an index signature. `Required<{ a?: string }>`
|
|
396
|
+
* carries a required property and is `non-object` to a complete program.
|
|
397
|
+
*/
|
|
398
|
+
if (name === 'Required') {
|
|
399
|
+
return pinnedShapeOf(args[0], expanding) === 'index' ? 'index' : null;
|
|
400
|
+
}
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
default:
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/** Wrappers whose awaited value is the single argument they carry. */
|
|
408
|
+
const AWAITED_TYPE_WRAPPERS = new Set(['Promise', 'PromiseLike']);
|
|
409
|
+
/**
|
|
410
|
+
* The value an `await` on this declared type produces.
|
|
411
|
+
*
|
|
412
|
+
* Without it an `async` declaration states its shape one wrapper out of reach:
|
|
413
|
+
* `Promise<Record<string, string>>` is not itself a dictionary, so the awaited
|
|
414
|
+
* dictionary would go unread and the guard the rule exists to add would be
|
|
415
|
+
* dropped. A declared type that is not a promise passes through unchanged,
|
|
416
|
+
* because awaiting a plain value yields that same value.
|
|
417
|
+
*/
|
|
418
|
+
function unwrapAwaitedType(node) {
|
|
419
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
420
|
+
node.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
421
|
+
AWAITED_TYPE_WRAPPERS.has(node.typeName.name) &&
|
|
422
|
+
node.typeParameters?.params.length === 1) {
|
|
423
|
+
return unwrapAwaitedType(node.typeParameters.params[0]);
|
|
424
|
+
}
|
|
425
|
+
return node;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Whether an assertion is `as const`, which pins mutability rather than shape.
|
|
429
|
+
*
|
|
430
|
+
* `const` is not a type the source names, so treating it as one would answer
|
|
431
|
+
* `load() as const` with "the source says nothing about the shape" when the
|
|
432
|
+
* asserted expression may state it perfectly well.
|
|
433
|
+
*/
|
|
434
|
+
function isConstAssertion(node) {
|
|
435
|
+
return (node.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
436
|
+
node.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
437
|
+
node.typeName.name === 'const');
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* The return type a class writes on the named member, when it writes one.
|
|
441
|
+
*
|
|
442
|
+
* Only the class's OWN body is read: a return type inherited through `extends`
|
|
443
|
+
* lives in whichever declaration that clause resolves to, and reaching it is
|
|
444
|
+
* the cross-file resolution whose absence puts this code on the fall-through.
|
|
445
|
+
*/
|
|
446
|
+
function returnTypeOfMethod(declaration, name) {
|
|
447
|
+
for (const member of declaration.body.body) {
|
|
448
|
+
if (member.type !== utils_1.AST_NODE_TYPES.MethodDefinition &&
|
|
449
|
+
member.type !== utils_1.AST_NODE_TYPES.PropertyDefinition) {
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (member.computed ||
|
|
453
|
+
member.key.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
454
|
+
member.key.name !== name) {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
const value = member.value;
|
|
458
|
+
if (value &&
|
|
459
|
+
(value.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
460
|
+
value.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
|
|
461
|
+
return value.returnType?.typeAnnotation ?? null;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
238
466
|
/**
|
|
239
467
|
* Reads through an optional chain to the member access or call it holds.
|
|
240
468
|
* `Object?.keys?.(payload)?.length` parses as a single `ChainExpression`
|
|
@@ -1405,18 +1633,29 @@ exports.enforceEmptyObjectCheck = (0, createRule_1.createRule)({
|
|
|
1405
1633
|
return false;
|
|
1406
1634
|
}
|
|
1407
1635
|
/**
|
|
1408
|
-
* The type the
|
|
1636
|
+
* The type the DECLARATION behind this value writes, if it writes one.
|
|
1637
|
+
*
|
|
1638
|
+
* An import is answered ahead of this by `tracesToImport`; what remains
|
|
1639
|
+
* is every place the file states the type of the value a guard tests. A
|
|
1640
|
+
* binding annotation is one of them and not the only one — an annotated
|
|
1641
|
+
* function return, a class method's return and a type assertion state it
|
|
1642
|
+
* just as directly — and reading only the binding left the naming
|
|
1643
|
+
* heuristic deciding alone at six other declaration sites, which is how
|
|
1644
|
+
* `const response = build()` off a `Readonly<NextResponse>` return kept
|
|
1645
|
+
* being rewritten into a guard that holds for every valid value (#2346).
|
|
1409
1646
|
*
|
|
1410
|
-
*
|
|
1411
|
-
*
|
|
1412
|
-
*
|
|
1413
|
-
*
|
|
1414
|
-
*
|
|
1415
|
-
*
|
|
1647
|
+
* The annotation has to sit on the BINDING rather than around it: the one
|
|
1648
|
+
* on `const { config }: Props = load()` describes the container, and
|
|
1649
|
+
* reading it as a verdict on a single property would need exactly the
|
|
1650
|
+
* resolution that failed, so a destructured binding keeps the heuristic.
|
|
1651
|
+
*
|
|
1652
|
+
* `expanding` holds the bindings on the current expansion PATH, so a
|
|
1653
|
+
* binding initialized from itself terminates while a binding named twice
|
|
1654
|
+
* in sibling positions is still read at each of them.
|
|
1416
1655
|
*/
|
|
1417
|
-
function declaredTypeOf(identifier) {
|
|
1656
|
+
function declaredTypeOf(identifier, expanding = new Set()) {
|
|
1418
1657
|
const variable = variableFor(identifier);
|
|
1419
|
-
if (!variable) {
|
|
1658
|
+
if (!variable || expanding.has(variable)) {
|
|
1420
1659
|
return null;
|
|
1421
1660
|
}
|
|
1422
1661
|
for (const def of variable.defs) {
|
|
@@ -1426,6 +1665,124 @@ exports.enforceEmptyObjectCheck = (0, createRule_1.createRule)({
|
|
|
1426
1665
|
if (def.name.typeAnnotation) {
|
|
1427
1666
|
return def.name.typeAnnotation.typeAnnotation;
|
|
1428
1667
|
}
|
|
1668
|
+
if (def.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
1669
|
+
def.node.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1670
|
+
def.node.init) {
|
|
1671
|
+
expanding.add(variable);
|
|
1672
|
+
const initialized = declaredTypeOfExpression(def.node.init, expanding);
|
|
1673
|
+
expanding.delete(variable);
|
|
1674
|
+
if (initialized) {
|
|
1675
|
+
return initialized;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
return null;
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* The type an initializer states for the value it produces.
|
|
1683
|
+
*
|
|
1684
|
+
* `satisfies` is deliberately absent: it checks an expression against a
|
|
1685
|
+
* type without changing the type, so the value keeps whatever the
|
|
1686
|
+
* expression already carried and a complete program reaches the heuristic
|
|
1687
|
+
* there too. A spelling this switch does not recognize answers `null`,
|
|
1688
|
+
* which leaves the guard exactly where it sat before the declaration
|
|
1689
|
+
* sites were read at all.
|
|
1690
|
+
*/
|
|
1691
|
+
function declaredTypeOfExpression(node, expanding) {
|
|
1692
|
+
switch (node.type) {
|
|
1693
|
+
case utils_1.AST_NODE_TYPES.TSAsExpression:
|
|
1694
|
+
case utils_1.AST_NODE_TYPES.TSTypeAssertion:
|
|
1695
|
+
return isConstAssertion(node.typeAnnotation)
|
|
1696
|
+
? declaredTypeOfExpression(node.expression, expanding)
|
|
1697
|
+
: node.typeAnnotation;
|
|
1698
|
+
case utils_1.AST_NODE_TYPES.TSNonNullExpression:
|
|
1699
|
+
case utils_1.AST_NODE_TYPES.ChainExpression:
|
|
1700
|
+
return declaredTypeOfExpression(node.expression, expanding);
|
|
1701
|
+
case utils_1.AST_NODE_TYPES.AwaitExpression: {
|
|
1702
|
+
const awaited = declaredTypeOfExpression(node.argument, expanding);
|
|
1703
|
+
return awaited ? unwrapAwaitedType(awaited) : null;
|
|
1704
|
+
}
|
|
1705
|
+
case utils_1.AST_NODE_TYPES.CallExpression:
|
|
1706
|
+
return declaredReturnTypeOf(node.callee, expanding);
|
|
1707
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
1708
|
+
return declaredTypeOf(node, expanding);
|
|
1709
|
+
default:
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* The return type the callee's SAME-FILE declaration writes.
|
|
1715
|
+
*
|
|
1716
|
+
* A callee resolving into a module this program did not load is answered
|
|
1717
|
+
* by `tracesToImport` before this runs, so reaching here means the
|
|
1718
|
+
* declaration is one this file can be read for — the same boundary the
|
|
1719
|
+
* same-file alias lookup draws.
|
|
1720
|
+
*/
|
|
1721
|
+
function declaredReturnTypeOf(callee, expanding) {
|
|
1722
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
1723
|
+
const variable = variableFor(callee);
|
|
1724
|
+
if (!variable) {
|
|
1725
|
+
return null;
|
|
1726
|
+
}
|
|
1727
|
+
for (const def of variable.defs) {
|
|
1728
|
+
if (def.type === 'FunctionName') {
|
|
1729
|
+
return def.node.returnType?.typeAnnotation ?? null;
|
|
1730
|
+
}
|
|
1731
|
+
if (def.type === 'Variable' &&
|
|
1732
|
+
def.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
1733
|
+
(def.node.init?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
1734
|
+
def.node.init?.type === utils_1.AST_NODE_TYPES.FunctionExpression)) {
|
|
1735
|
+
return def.node.init.returnType?.typeAnnotation ?? null;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
return null;
|
|
1739
|
+
}
|
|
1740
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
1741
|
+
!callee.computed &&
|
|
1742
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
1743
|
+
const declaration = classDeclarationOf(callee.object, expanding);
|
|
1744
|
+
return declaration
|
|
1745
|
+
? returnTypeOfMethod(declaration, callee.property.name)
|
|
1746
|
+
: null;
|
|
1747
|
+
}
|
|
1748
|
+
return null;
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* The same-file class an expression is an instance of, or names.
|
|
1752
|
+
*
|
|
1753
|
+
* A receiver whose class is not written in this file yields `null`, so
|
|
1754
|
+
* the method's return type stays unread rather than being guessed from a
|
|
1755
|
+
* same-named method on an unrelated class.
|
|
1756
|
+
*/
|
|
1757
|
+
function classDeclarationOf(node, expanding) {
|
|
1758
|
+
if (node.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
1759
|
+
return null;
|
|
1760
|
+
}
|
|
1761
|
+
const variable = variableFor(node);
|
|
1762
|
+
if (!variable || expanding.has(variable)) {
|
|
1763
|
+
return null;
|
|
1764
|
+
}
|
|
1765
|
+
for (const def of variable.defs) {
|
|
1766
|
+
if (def.type === 'ClassName') {
|
|
1767
|
+
return def.node;
|
|
1768
|
+
}
|
|
1769
|
+
if (def.type !== 'Variable' ||
|
|
1770
|
+
def.node.type !== utils_1.AST_NODE_TYPES.VariableDeclarator ||
|
|
1771
|
+
!def.node.init) {
|
|
1772
|
+
continue;
|
|
1773
|
+
}
|
|
1774
|
+
const { init } = def.node;
|
|
1775
|
+
if (init.type === utils_1.AST_NODE_TYPES.ClassExpression) {
|
|
1776
|
+
return init;
|
|
1777
|
+
}
|
|
1778
|
+
if (init.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
1779
|
+
expanding.add(variable);
|
|
1780
|
+
const declaration = classDeclarationOf(init.callee, expanding);
|
|
1781
|
+
expanding.delete(variable);
|
|
1782
|
+
if (declaration) {
|
|
1783
|
+
return declaration;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1429
1786
|
}
|
|
1430
1787
|
return null;
|
|
1431
1788
|
}
|
|
@@ -1434,13 +1791,15 @@ exports.enforceEmptyObjectCheck = (0, createRule_1.createRule)({
|
|
|
1434
1791
|
*
|
|
1435
1792
|
* An explicit `any` is the checker reporting what the source told it, not
|
|
1436
1793
|
* a resolution failure, so it leaves the value in the same position as an
|
|
1437
|
-
* unannotated one and the naming heuristic keeps answering.
|
|
1438
|
-
*
|
|
1439
|
-
* `unknown`
|
|
1440
|
-
*
|
|
1794
|
+
* unannotated one and the naming heuristic keeps answering. An
|
|
1795
|
+
* intersection carrying `any` reduces to `any`, so it says as little as a
|
|
1796
|
+
* union carrying one. `unknown` is excluded from this reading on purpose:
|
|
1797
|
+
* `Object.keys` rejects an `unknown` operand, so the fix the heuristic
|
|
1798
|
+
* would attach there does not typecheck.
|
|
1441
1799
|
*/
|
|
1442
1800
|
function declaresNothing(node) {
|
|
1443
|
-
if (node.type === utils_1.AST_NODE_TYPES.TSUnionType
|
|
1801
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSUnionType ||
|
|
1802
|
+
node.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
|
|
1444
1803
|
return node.types.some(declaresNothing);
|
|
1445
1804
|
}
|
|
1446
1805
|
return node.type === utils_1.AST_NODE_TYPES.TSAnyKeyword;
|
|
@@ -1453,18 +1812,16 @@ exports.enforceEmptyObjectCheck = (0, createRule_1.createRule)({
|
|
|
1453
1812
|
* so the value is a plain data map rather than a class instance and
|
|
1454
1813
|
* `Object.keys` measures its emptiness correctly. Reading that from the
|
|
1455
1814
|
* source keeps the verdict a complete program gives — `object` — reachable
|
|
1456
|
-
* without one,
|
|
1457
|
-
*
|
|
1458
|
-
*
|
|
1459
|
-
*
|
|
1815
|
+
* without one, and the SPELLING is not what carries the guarantee: a
|
|
1816
|
+
* shape-preserving wrapper, a union member, an intersection whose every
|
|
1817
|
+
* member is pinned and a same-file alias each state the same index
|
|
1818
|
+
* signature, and reading only the bare reference left ten such shapes
|
|
1819
|
+
* silent that a complete program reports (#2345). A reference the source
|
|
1820
|
+
* does not pin this way, `Readonly<NextResponse>` among them, carries no
|
|
1821
|
+
* such guarantee.
|
|
1460
1822
|
*/
|
|
1461
1823
|
function spellsDictionary(node) {
|
|
1462
|
-
|
|
1463
|
-
return node.types.some(spellsDictionary);
|
|
1464
|
-
}
|
|
1465
|
-
return (node.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
1466
|
-
node.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1467
|
-
node.typeName.name === 'Record');
|
|
1824
|
+
return pinnedShapeOf(node) !== null;
|
|
1468
1825
|
}
|
|
1469
1826
|
function isLikelyObject(identifier) {
|
|
1470
1827
|
if (checker && parserServices?.esTreeNodeToTSNodeMap) {
|