adamantite 0.38.0 → 0.40.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/dist/index.js +27 -27
- package/dist/presets/lint/vendor/antislop/license.md +1 -1
- package/dist/presets/lint/vendor/antislop/plugin.mjs +442 -201
- package/package.json +8 -7
- package/CHANGELOG.md +0 -1194
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Vendored build of the anti-slop oxlint plugin.
|
|
2
|
-
// Source: https://github.com/dmmulroy/anti-slop (MIT) at commit
|
|
2
|
+
// Source: https://github.com/dmmulroy/anti-slop (MIT) at commit e8c4880471b23ab7f216fba7b27d173a6ef07d4c.
|
|
3
3
|
// Its dependencies (MIT) are inlined so this file is self-contained.
|
|
4
4
|
// See license.md for the full license text.
|
|
5
5
|
//
|
|
@@ -433,6 +433,182 @@ const noConditionalEmptyObjectSpreadRule = defineRule({
|
|
|
433
433
|
}
|
|
434
434
|
});
|
|
435
435
|
//#endregion
|
|
436
|
+
//#region src/shared/lexical-type-parameters.ts
|
|
437
|
+
function isNode$1(value) {
|
|
438
|
+
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
|
|
439
|
+
}
|
|
440
|
+
function collectInferTypeParameterNames(node, visitorKeys, names) {
|
|
441
|
+
if (node.type === "TSInferType") names.add(node.typeParameter.name.name);
|
|
442
|
+
const record = node;
|
|
443
|
+
for (const key of visitorKeys[node.type] ?? []) {
|
|
444
|
+
const value = record[key];
|
|
445
|
+
if (isNode$1(value)) {
|
|
446
|
+
collectInferTypeParameterNames(value, visitorKeys, names);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (!Array.isArray(value)) continue;
|
|
450
|
+
for (const child of value) if (isNode$1(child)) collectInferTypeParameterNames(child, visitorKeys, names);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/** Collect type binders that are in scope at a node and can shadow module aliases. */
|
|
454
|
+
function lexicalTypeParameterNames(node, visitorKeys) {
|
|
455
|
+
const names = /* @__PURE__ */ new Set();
|
|
456
|
+
let descendant = node;
|
|
457
|
+
let current = node;
|
|
458
|
+
while (current !== null && current.type !== "Program") {
|
|
459
|
+
if ("typeParameters" in current) for (const parameter of current.typeParameters?.params ?? []) names.add(parameter.name.name);
|
|
460
|
+
if (current.type === "TSMappedType" && (descendant === current.nameType || descendant === current.typeAnnotation)) names.add(current.key.name);
|
|
461
|
+
if (current.type === "TSConditionalType" && descendant === current.trueType) collectInferTypeParameterNames(current.extendsType, visitorKeys, names);
|
|
462
|
+
descendant = current;
|
|
463
|
+
current = current.parent;
|
|
464
|
+
}
|
|
465
|
+
return names;
|
|
466
|
+
}
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region src/shared/type-alias-resolution.ts
|
|
469
|
+
const environmentsByProgram = /* @__PURE__ */ new WeakMap();
|
|
470
|
+
function isNode(value) {
|
|
471
|
+
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
|
|
472
|
+
}
|
|
473
|
+
function enclosingTypeScope(node) {
|
|
474
|
+
let current = node.parent;
|
|
475
|
+
while (current !== null) {
|
|
476
|
+
if (current.type === "Program" || current.type === "BlockStatement" || current.type === "TSModuleBlock" || current.type === "StaticBlock" || current.type === "SwitchStatement") return current;
|
|
477
|
+
current = current.parent;
|
|
478
|
+
}
|
|
479
|
+
return node;
|
|
480
|
+
}
|
|
481
|
+
function declaredTypeBinding(node) {
|
|
482
|
+
if (node.type === "TSTypeAliasDeclaration") return {
|
|
483
|
+
alias: node,
|
|
484
|
+
name: node.id.name
|
|
485
|
+
};
|
|
486
|
+
if (node.type === "TSInterfaceDeclaration" || node.type === "TSEnumDeclaration" || node.type === "ClassDeclaration" || node.type === "ClassExpression") return node.id === null ? null : {
|
|
487
|
+
alias: null,
|
|
488
|
+
name: node.id.name
|
|
489
|
+
};
|
|
490
|
+
if (node.type === "ImportSpecifier" || node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier") return {
|
|
491
|
+
alias: null,
|
|
492
|
+
name: node.local.name
|
|
493
|
+
};
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
function collectTypeBindings(node, visitorKeys, bindingsByName, aliases) {
|
|
497
|
+
const declared = declaredTypeBinding(node);
|
|
498
|
+
if (declared !== null) {
|
|
499
|
+
const bindings = bindingsByName.get(declared.name) ?? [];
|
|
500
|
+
bindings.push({
|
|
501
|
+
...declared,
|
|
502
|
+
scope: enclosingTypeScope(node)
|
|
503
|
+
});
|
|
504
|
+
bindingsByName.set(declared.name, bindings);
|
|
505
|
+
if (declared.alias !== null) aliases.push(declared.alias);
|
|
506
|
+
}
|
|
507
|
+
const fields = node;
|
|
508
|
+
for (const key of visitorKeys[node.type] ?? []) {
|
|
509
|
+
const value = fields[key];
|
|
510
|
+
if (isNode(value)) {
|
|
511
|
+
collectTypeBindings(value, visitorKeys, bindingsByName, aliases);
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
if (!Array.isArray(value)) continue;
|
|
515
|
+
for (const child of value) if (isNode(child)) collectTypeBindings(child, visitorKeys, bindingsByName, aliases);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
/** Collect every lexical type alias and competing type binding in a program. */
|
|
519
|
+
function createTypeAliasEnvironment(program, visitorKeys) {
|
|
520
|
+
const cached = environmentsByProgram.get(program);
|
|
521
|
+
if (cached !== void 0) return cached;
|
|
522
|
+
const bindingsByName = /* @__PURE__ */ new Map();
|
|
523
|
+
const aliases = [];
|
|
524
|
+
collectTypeBindings(program, visitorKeys, bindingsByName, aliases);
|
|
525
|
+
const environment = {
|
|
526
|
+
aliases,
|
|
527
|
+
bindingsByName,
|
|
528
|
+
visitorKeys
|
|
529
|
+
};
|
|
530
|
+
environmentsByProgram.set(program, environment);
|
|
531
|
+
return environment;
|
|
532
|
+
}
|
|
533
|
+
function ancestorDistance(ancestor, node) {
|
|
534
|
+
let current = node;
|
|
535
|
+
let distance = 0;
|
|
536
|
+
while (current !== null) {
|
|
537
|
+
if (current === ancestor) return distance;
|
|
538
|
+
current = current.parent;
|
|
539
|
+
distance += 1;
|
|
540
|
+
}
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
function nearestTypeBindings(name, use, environment) {
|
|
544
|
+
const candidates = environment.bindingsByName.get(name) ?? [];
|
|
545
|
+
let nearestDistance = Number.POSITIVE_INFINITY;
|
|
546
|
+
let nearest = [];
|
|
547
|
+
for (const candidate of candidates) {
|
|
548
|
+
const distance = ancestorDistance(candidate.scope, use);
|
|
549
|
+
if (distance === null || distance > nearestDistance) continue;
|
|
550
|
+
if (distance === nearestDistance) {
|
|
551
|
+
nearest.push(candidate);
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
nearestDistance = distance;
|
|
555
|
+
nearest = [candidate];
|
|
556
|
+
}
|
|
557
|
+
return nearest;
|
|
558
|
+
}
|
|
559
|
+
/** Resolve the nearest visible alias with this name, respecting lexical shadowing. */
|
|
560
|
+
function visibleTypeAlias(name, use, environment) {
|
|
561
|
+
if (lexicalTypeParameterNames(use, environment.visitorKeys).has(name)) return null;
|
|
562
|
+
const bindings = nearestTypeBindings(name, use, environment);
|
|
563
|
+
return bindings.length === 1 ? bindings[0]?.alias ?? null : null;
|
|
564
|
+
}
|
|
565
|
+
/** Return whether a local declaration shadows a built-in type at this use. */
|
|
566
|
+
function hasVisibleTypeBinding(name, use, environment) {
|
|
567
|
+
return lexicalTypeParameterNames(use, environment.visitorKeys).has(name) || nearestTypeBindings(name, use, environment).length > 0;
|
|
568
|
+
}
|
|
569
|
+
function typeReferenceName$3(type) {
|
|
570
|
+
return type.typeName.type === "Identifier" ? type.typeName.name : null;
|
|
571
|
+
}
|
|
572
|
+
function aliasSubstitutions(alias, reference, base) {
|
|
573
|
+
const parameters = alias.typeParameters?.params ?? [];
|
|
574
|
+
const arguments_ = reference.typeArguments?.params ?? [];
|
|
575
|
+
const next = new Map(base);
|
|
576
|
+
for (const [index, parameter] of parameters.entries()) {
|
|
577
|
+
const explicitArgument = arguments_[index];
|
|
578
|
+
const argument = explicitArgument ?? parameter.default;
|
|
579
|
+
if (argument === null || argument === void 0) return null;
|
|
580
|
+
const argumentSubstitutions = explicitArgument === void 0 ? next : base;
|
|
581
|
+
next.set(parameter.name.name, {
|
|
582
|
+
type: argument,
|
|
583
|
+
substitutions: new Map(argumentSubstitutions)
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
return next;
|
|
587
|
+
}
|
|
588
|
+
/** Match a type after resolving visible aliases and substituting their type parameters. */
|
|
589
|
+
function resolvedTypeMatches(type, environment, matcher) {
|
|
590
|
+
const evaluate = (current, substitutions, resolvingAliases) => {
|
|
591
|
+
if (current.type === "TSTypeReference") {
|
|
592
|
+
const name = typeReferenceName$3(current);
|
|
593
|
+
if (name !== null) {
|
|
594
|
+
const substitution = substitutions.get(name);
|
|
595
|
+
if (substitution !== void 0 && !current.typeArguments?.params.length) return evaluate(substitution.type, substitution.substitutions, resolvingAliases);
|
|
596
|
+
const alias = visibleTypeAlias(name, current, environment);
|
|
597
|
+
if (alias !== null && !resolvingAliases.has(alias)) {
|
|
598
|
+
const nextSubstitutions = aliasSubstitutions(alias, current, substitutions);
|
|
599
|
+
if (nextSubstitutions !== null) {
|
|
600
|
+
const nextResolving = new Set(resolvingAliases);
|
|
601
|
+
nextResolving.add(alias);
|
|
602
|
+
return evaluate(alias.typeAnnotation, nextSubstitutions, nextResolving);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return matcher(current, (child) => evaluate(child, substitutions, resolvingAliases));
|
|
608
|
+
};
|
|
609
|
+
return evaluate(type, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Set());
|
|
610
|
+
}
|
|
611
|
+
//#endregion
|
|
436
612
|
//#region src/shared/dictionary-types.ts
|
|
437
613
|
const BUILT_INS = /* @__PURE__ */ new Set([
|
|
438
614
|
"Record",
|
|
@@ -453,48 +629,25 @@ const TRANSPARENT_WRAPPERS = /* @__PURE__ */ new Set([
|
|
|
453
629
|
function declaredStatement(statement) {
|
|
454
630
|
return statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration ?? null : statement;
|
|
455
631
|
}
|
|
456
|
-
function createTypeEnvironment(program) {
|
|
457
|
-
const aliases = /* @__PURE__ */ new Map();
|
|
632
|
+
function createTypeEnvironment(program, visitorKeys) {
|
|
458
633
|
const interfaces = /* @__PURE__ */ new Map();
|
|
459
|
-
const shadowedBuiltIns = /* @__PURE__ */ new Set();
|
|
460
634
|
for (const statement of program.body) {
|
|
461
635
|
const declaration = declaredStatement(statement);
|
|
462
|
-
if (declaration?.type
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if (declaration?.type === "TSTypeAliasDeclaration") {
|
|
467
|
-
if (aliases.get(declaration.id.name) === void 0) aliases.set(declaration.id.name, declaration);
|
|
468
|
-
else shadowedBuiltIns.add(declaration.id.name);
|
|
469
|
-
if (BUILT_INS.has(declaration.id.name)) shadowedBuiltIns.add(declaration.id.name);
|
|
470
|
-
continue;
|
|
471
|
-
}
|
|
472
|
-
if (declaration?.type === "TSInterfaceDeclaration") {
|
|
473
|
-
const declarations = interfaces.get(declaration.id.name) ?? [];
|
|
474
|
-
declarations.push(declaration);
|
|
475
|
-
interfaces.set(declaration.id.name, declarations);
|
|
476
|
-
if (BUILT_INS.has(declaration.id.name)) shadowedBuiltIns.add(declaration.id.name);
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
if (declaration?.type === "TSEnumDeclaration") {
|
|
480
|
-
if (BUILT_INS.has(declaration.id.name)) shadowedBuiltIns.add(declaration.id.name);
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
|
-
if ((declaration?.type === "ClassDeclaration" || declaration?.type === "FunctionDeclaration") && declaration.id !== null) {
|
|
484
|
-
if (BUILT_INS.has(declaration.id.name)) shadowedBuiltIns.add(declaration.id.name);
|
|
485
|
-
}
|
|
636
|
+
if (declaration?.type !== "TSInterfaceDeclaration") continue;
|
|
637
|
+
const declarations = interfaces.get(declaration.id.name) ?? [];
|
|
638
|
+
declarations.push(declaration);
|
|
639
|
+
interfaces.set(declaration.id.name, declarations);
|
|
486
640
|
}
|
|
487
641
|
return {
|
|
488
|
-
aliases,
|
|
489
642
|
interfaces,
|
|
490
|
-
|
|
643
|
+
typeAliases: createTypeAliasEnvironment(program, visitorKeys)
|
|
491
644
|
};
|
|
492
645
|
}
|
|
493
646
|
function typeReferenceName$2(type) {
|
|
494
647
|
return type.typeName.type === "Identifier" ? type.typeName.name : null;
|
|
495
648
|
}
|
|
496
|
-
function isBuiltIn(name, environment) {
|
|
497
|
-
return BUILT_INS.has(name) && !environment.
|
|
649
|
+
function isBuiltIn(name, use, environment) {
|
|
650
|
+
return BUILT_INS.has(name) && !hasVisibleTypeBinding(name, use, environment.typeAliases);
|
|
498
651
|
}
|
|
499
652
|
function isUnappliedReferenceTo(type, name) {
|
|
500
653
|
const unwrapped = unwrapTransparentType(type);
|
|
@@ -556,7 +709,7 @@ function unsafeDirectValue(type, environment, substitutions, resolvingAliases) {
|
|
|
556
709
|
if (unwrapped.type !== "TSTypeReference") return null;
|
|
557
710
|
const name = typeReferenceName$2(unwrapped);
|
|
558
711
|
if (name === null) return null;
|
|
559
|
-
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, environment)) {
|
|
712
|
+
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, unwrapped, environment)) {
|
|
560
713
|
const wrapped = unwrapped.typeArguments?.params[0];
|
|
561
714
|
return wrapped === void 0 ? null : unsafeDirectValue(wrapped, environment, substitutions, resolvingAliases);
|
|
562
715
|
}
|
|
@@ -564,8 +717,8 @@ function unsafeDirectValue(type, environment, substitutions, resolvingAliases) {
|
|
|
564
717
|
if (substitution !== void 0) return isUnappliedReferenceTo(substitution, name) ? null : unsafeDirectValue(substitution, environment, substitutions, resolvingAliases);
|
|
565
718
|
const interfaceDeclarations = environment.interfaces.get(name);
|
|
566
719
|
if (interfaceDeclarations !== void 0) return isEffectivelyEmptyInterface(interfaceDeclarations) ? "empty-object" : null;
|
|
567
|
-
const alias = environment.
|
|
568
|
-
if (alias ===
|
|
720
|
+
const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
|
|
721
|
+
if (alias === null || resolvingAliases.has(name)) return null;
|
|
569
722
|
const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
|
|
570
723
|
if (nextSubstitutions === null) return null;
|
|
571
724
|
const nextResolving = new Set(resolvingAliases);
|
|
@@ -587,23 +740,23 @@ function dictionaryValueTypes(type, environment, substitutions, resolvingAliases
|
|
|
587
740
|
if (name === null) return [];
|
|
588
741
|
const substitution = substitutions.get(name);
|
|
589
742
|
if (substitution !== void 0) return isUnappliedReferenceTo(substitution, name) ? [] : dictionaryValueTypes(substitution, environment, substitutions, resolvingAliases);
|
|
590
|
-
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, environment)) {
|
|
743
|
+
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, unwrapped, environment)) {
|
|
591
744
|
const wrapped = unwrapped.typeArguments?.params[0];
|
|
592
745
|
return wrapped === void 0 ? [] : dictionaryValueTypes(wrapped, environment, substitutions, resolvingAliases);
|
|
593
746
|
}
|
|
594
|
-
if (name === "Record" && isBuiltIn(name, environment)) {
|
|
747
|
+
if (name === "Record" && isBuiltIn(name, unwrapped, environment)) {
|
|
595
748
|
const value = unwrapped.typeArguments?.params[1] ?? null;
|
|
596
749
|
return value === null ? [] : [{
|
|
597
750
|
type: value,
|
|
598
751
|
substitutions
|
|
599
752
|
}];
|
|
600
753
|
}
|
|
601
|
-
if ((name === "Pick" || name === "Omit") && isBuiltIn(name, environment)) {
|
|
754
|
+
if ((name === "Pick" || name === "Omit") && isBuiltIn(name, unwrapped, environment)) {
|
|
602
755
|
const source = unwrapped.typeArguments?.params[0];
|
|
603
756
|
return source === void 0 ? [] : dictionaryValueTypes(source, environment, substitutions, resolvingAliases);
|
|
604
757
|
}
|
|
605
|
-
const alias = environment.
|
|
606
|
-
if (alias ===
|
|
758
|
+
const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
|
|
759
|
+
if (alias === null || resolvingAliases.has(name)) return [];
|
|
607
760
|
const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
|
|
608
761
|
if (nextSubstitutions === null) return [];
|
|
609
762
|
const nextResolving = new Set(resolvingAliases);
|
|
@@ -627,9 +780,6 @@ function classifyUnsafeDictionary(type, environment) {
|
|
|
627
780
|
}
|
|
628
781
|
return null;
|
|
629
782
|
}
|
|
630
|
-
function resolvesToDictionary(type, environment, substitutions, resolvingAliases) {
|
|
631
|
-
return dictionaryValueTypes(type, environment, substitutions, resolvingAliases).length > 0;
|
|
632
|
-
}
|
|
633
783
|
function classifyWideningTarget(type, environment) {
|
|
634
784
|
const unwrapped = unwrapTransparentType(type);
|
|
635
785
|
if (unwrapped.type === "TSUnknownKeyword") return { kind: "unknown" };
|
|
@@ -639,31 +789,40 @@ function classifyWideningTarget(type, environment) {
|
|
|
639
789
|
if (unwrapped.type !== "TSTypeReference") return null;
|
|
640
790
|
const name = typeReferenceName$2(unwrapped);
|
|
641
791
|
if (name === null) return null;
|
|
642
|
-
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, environment)) {
|
|
792
|
+
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, unwrapped, environment)) {
|
|
643
793
|
const wrapped = unwrapped.typeArguments?.params[0];
|
|
644
794
|
return wrapped === void 0 ? null : classifyWideningTarget(wrapped, environment);
|
|
645
795
|
}
|
|
646
|
-
if (name === "Record" && isBuiltIn(name, environment)) return { kind: "open dictionary" };
|
|
647
|
-
const alias = environment.
|
|
648
|
-
if (alias ===
|
|
796
|
+
if (name === "Record" && isBuiltIn(name, unwrapped, environment)) return hasBroadRecordKey(unwrapped, environment, /* @__PURE__ */ new Map()) ? { kind: "open dictionary" } : null;
|
|
797
|
+
const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
|
|
798
|
+
if (alias === null) return null;
|
|
649
799
|
if ((alias.typeParameters?.params.length ?? 0) > 0) {
|
|
650
800
|
const substitutions = aliasSubstitution(alias, unwrapped, /* @__PURE__ */ new Map());
|
|
651
|
-
return substitutions
|
|
801
|
+
return (substitutions === null ? null : classifyAliasBroadTarget(alias.typeAnnotation, environment, substitutions, /* @__PURE__ */ new Set([name])))?.kind === "open dictionary" ? { kind: "generic container" } : null;
|
|
652
802
|
}
|
|
653
803
|
const substitutions = aliasSubstitution(alias, unwrapped, /* @__PURE__ */ new Map());
|
|
654
804
|
if (substitutions === null) return null;
|
|
655
805
|
return classifyAliasBroadTarget(alias.typeAnnotation, environment, substitutions, /* @__PURE__ */ new Set([name]));
|
|
656
806
|
}
|
|
657
|
-
function
|
|
807
|
+
function hasBroadRecordKey(type, environment, substitutions) {
|
|
808
|
+
const key = type.typeArguments?.params[0];
|
|
809
|
+
return key === void 0 || isBroadMappedKey(key, environment, substitutions);
|
|
810
|
+
}
|
|
811
|
+
function isBroadMappedKey(type, environment, substitutions, visitedAliases = /* @__PURE__ */ new Set()) {
|
|
658
812
|
const unwrapped = unwrapTransparentType(type);
|
|
659
813
|
if (unwrapped.type === "TSStringKeyword" || unwrapped.type === "TSNumberKeyword" || unwrapped.type === "TSSymbolKeyword") return true;
|
|
660
|
-
if (unwrapped.type === "TSUnionType") return unwrapped.types.
|
|
814
|
+
if (unwrapped.type === "TSUnionType") return unwrapped.types.some((member) => isBroadMappedKey(member, environment, substitutions, visitedAliases));
|
|
661
815
|
if (unwrapped.type !== "TSTypeReference") return false;
|
|
662
816
|
const name = typeReferenceName$2(unwrapped);
|
|
663
817
|
if (name === null) return false;
|
|
664
818
|
const substitution = substitutions.get(name);
|
|
665
|
-
if (substitution !== void 0 && !isUnappliedReferenceTo(substitution, name)) return isBroadMappedKey(substitution, environment, substitutions);
|
|
666
|
-
|
|
819
|
+
if (substitution !== void 0 && !isUnappliedReferenceTo(substitution, name)) return isBroadMappedKey(substitution, environment, substitutions, visitedAliases);
|
|
820
|
+
if (name === "PropertyKey" && isBuiltIn(name, unwrapped, environment)) return true;
|
|
821
|
+
const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
|
|
822
|
+
if (alias === null || (alias.typeParameters?.params.length ?? 0) > 0 || visitedAliases.has(name)) return false;
|
|
823
|
+
const nextVisited = new Set(visitedAliases);
|
|
824
|
+
nextVisited.add(name);
|
|
825
|
+
return isBroadMappedKey(alias.typeAnnotation, environment, substitutions, nextVisited);
|
|
667
826
|
}
|
|
668
827
|
function classifyAliasBroadTarget(type, environment, substitutions, resolvingAliases) {
|
|
669
828
|
const unwrapped = unwrapTransparentType(type);
|
|
@@ -676,13 +835,13 @@ function classifyAliasBroadTarget(type, environment, substitutions, resolvingAli
|
|
|
676
835
|
if (name === null) return null;
|
|
677
836
|
const substitution = substitutions.get(name);
|
|
678
837
|
if (substitution !== void 0) return isUnappliedReferenceTo(substitution, name) ? null : classifyAliasBroadTarget(substitution, environment, substitutions, resolvingAliases);
|
|
679
|
-
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, environment)) {
|
|
838
|
+
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, unwrapped, environment)) {
|
|
680
839
|
const wrapped = unwrapped.typeArguments?.params[0];
|
|
681
840
|
return wrapped === void 0 ? null : classifyAliasBroadTarget(wrapped, environment, substitutions, resolvingAliases);
|
|
682
841
|
}
|
|
683
|
-
if (name === "Record" && isBuiltIn(name, environment)) return { kind: "open dictionary" };
|
|
684
|
-
const alias = environment.
|
|
685
|
-
if (alias ===
|
|
842
|
+
if (name === "Record" && isBuiltIn(name, unwrapped, environment)) return hasBroadRecordKey(unwrapped, environment, substitutions) ? { kind: "open dictionary" } : null;
|
|
843
|
+
const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
|
|
844
|
+
if (alias === null || resolvingAliases.has(name)) return null;
|
|
686
845
|
const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
|
|
687
846
|
if (nextSubstitutions === null) return null;
|
|
688
847
|
const nextResolving = new Set(resolvingAliases);
|
|
@@ -696,6 +855,31 @@ function isKnownEvidenceExpression(expression) {
|
|
|
696
855
|
return current.type === "ArrayExpression" || current.type === "ArrowFunctionExpression" || current.type === "ClassExpression" || current.type === "FunctionExpression" || current.type === "NewExpression" || current.type === "Literal" || current.type === "TemplateLiteral" || current.type === "UnaryExpression";
|
|
697
856
|
}
|
|
698
857
|
//#endregion
|
|
858
|
+
//#region src/shared/function-parameters.ts
|
|
859
|
+
/** Return whether a type is or contains TypeScript's absorbing unknown top type. */
|
|
860
|
+
function containsUnknownType(type) {
|
|
861
|
+
if (type.type === "TSUnknownKeyword") return true;
|
|
862
|
+
if (type.type === "TSParenthesizedType") return containsUnknownType(type.typeAnnotation);
|
|
863
|
+
return type.type === "TSUnionType" && type.types.some(containsUnknownType);
|
|
864
|
+
}
|
|
865
|
+
/** Return the TypeScript annotation attached to a function parameter or its wrapped binding. */
|
|
866
|
+
function functionParameterTypeAnnotation(parameter) {
|
|
867
|
+
if (parameter.type === "TSParameterProperty") return functionParameterTypeAnnotation(parameter.parameter);
|
|
868
|
+
if (parameter.type === "RestElement") return parameter.typeAnnotation ?? functionParameterTypeAnnotation(parameter.argument);
|
|
869
|
+
if (parameter.type === "AssignmentPattern") return parameter.typeAnnotation ?? functionParameterTypeAnnotation(parameter.left);
|
|
870
|
+
return parameter.typeAnnotation;
|
|
871
|
+
}
|
|
872
|
+
/** Return only a function parameter's local binding, excluding its annotation and default value. */
|
|
873
|
+
function functionParameterBindingName(parameter, sourceCode) {
|
|
874
|
+
if (parameter.type === "TSParameterProperty") return functionParameterBindingName(parameter.parameter, sourceCode);
|
|
875
|
+
if (parameter.type === "AssignmentPattern") return functionParameterBindingName(parameter.left, sourceCode);
|
|
876
|
+
if (parameter.type === "RestElement") return functionParameterBindingName(parameter.argument, sourceCode);
|
|
877
|
+
if (parameter.type === "Identifier") return parameter.name;
|
|
878
|
+
const sourceText = sourceCode.getText(parameter);
|
|
879
|
+
const annotationStart = parameter.typeAnnotation?.start;
|
|
880
|
+
return annotationStart === void 0 ? sourceText : sourceText.slice(0, annotationStart - parameter.start).trimEnd();
|
|
881
|
+
}
|
|
882
|
+
//#endregion
|
|
699
883
|
//#region src/rules/no-known-value-widening.ts
|
|
700
884
|
function unwrapExpression(expression) {
|
|
701
885
|
let current = expression;
|
|
@@ -730,6 +914,61 @@ function hasKnownEvidence(sourceCode, expression, visitedVariables = /* @__PURE_
|
|
|
730
914
|
visitedVariables.add(variable);
|
|
731
915
|
return hasKnownEvidence(sourceCode, declarator.init, visitedVariables);
|
|
732
916
|
}
|
|
917
|
+
function isFunctionExpression(node) {
|
|
918
|
+
return node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "TSDeclareFunction" || node.type === "TSEmptyBodyFunctionExpression";
|
|
919
|
+
}
|
|
920
|
+
function localFunctionForCall(sourceCode, callee) {
|
|
921
|
+
const unwrapped = unwrapExpression(callee);
|
|
922
|
+
if (isFunctionExpression(unwrapped)) return unwrapped;
|
|
923
|
+
if (unwrapped.type !== "Identifier") return null;
|
|
924
|
+
const variable = resolveVariable$2(sourceCode, unwrapped);
|
|
925
|
+
if (variable === null || variable.defs.length !== 1) return null;
|
|
926
|
+
const [definition] = variable.defs;
|
|
927
|
+
if (definition === void 0) return null;
|
|
928
|
+
if (definition.type === "FunctionName" && isFunctionExpression(definition.node)) return definition.node;
|
|
929
|
+
if (definition.type !== "Variable" || definition.node.type !== "VariableDeclarator") return null;
|
|
930
|
+
const initializer = definition.node.init;
|
|
931
|
+
if (initializer === null) return null;
|
|
932
|
+
const unwrappedInitializer = unwrapExpression(initializer);
|
|
933
|
+
return isFunctionExpression(unwrappedInitializer) ? unwrappedInitializer : null;
|
|
934
|
+
}
|
|
935
|
+
function variableTypeAnnotation(sourceCode, variable) {
|
|
936
|
+
if (variable.defs.length !== 1) return null;
|
|
937
|
+
const [definition] = variable.defs;
|
|
938
|
+
if (definition === void 0) return null;
|
|
939
|
+
if (definition.type === "Variable" && definition.node.type === "VariableDeclarator" && definition.node.id.type === "Identifier") return definition.node.id.typeAnnotation ?? null;
|
|
940
|
+
if (definition.type !== "Parameter" || !isFunctionExpression(definition.node)) return null;
|
|
941
|
+
const parameter = definition.node.params.find((candidate) => functionParameterBindingName(candidate, sourceCode) === variable.name);
|
|
942
|
+
return parameter === void 0 ? null : functionParameterTypeAnnotation(parameter) ?? null;
|
|
943
|
+
}
|
|
944
|
+
function hasInformativeType(type, environment) {
|
|
945
|
+
return classifyUnsafeDictionaryValue(type, environment) === null;
|
|
946
|
+
}
|
|
947
|
+
function hasKnownCallArgumentEvidence(sourceCode, expression, environment, visitedVariables = /* @__PURE__ */ new Set()) {
|
|
948
|
+
if (expression.type === "ParenthesizedExpression" || expression.type === "TSNonNullExpression") return hasKnownCallArgumentEvidence(sourceCode, expression.expression, environment, visitedVariables);
|
|
949
|
+
if (expression.type === "TSAsExpression" || expression.type === "TSTypeAssertion") return hasInformativeType(expression.typeAnnotation, environment);
|
|
950
|
+
if (expression.type === "TSSatisfiesExpression") return hasKnownCallArgumentEvidence(sourceCode, expression.expression, environment, visitedVariables);
|
|
951
|
+
if (expression.type === "CallExpression") {
|
|
952
|
+
const returnType = localFunctionForCall(sourceCode, expression.callee)?.returnType?.typeAnnotation;
|
|
953
|
+
return returnType !== void 0 && hasInformativeType(returnType, environment);
|
|
954
|
+
}
|
|
955
|
+
if (expression.type !== "Identifier") return isKnownEvidenceExpression(expression);
|
|
956
|
+
const variable = resolveVariable$2(sourceCode, expression);
|
|
957
|
+
if (variable === null || visitedVariables.has(variable)) return false;
|
|
958
|
+
const annotation = variableTypeAnnotation(sourceCode, variable);
|
|
959
|
+
if (annotation !== null) return hasInformativeType(annotation.typeAnnotation, environment);
|
|
960
|
+
const declarator = variableDeclarator$1(variable);
|
|
961
|
+
if (declarator === null || declarator.init === null || !isStableConstVariable(variable, declarator)) return false;
|
|
962
|
+
visitedVariables.add(variable);
|
|
963
|
+
return hasKnownCallArgumentEvidence(sourceCode, declarator.init, environment, visitedVariables);
|
|
964
|
+
}
|
|
965
|
+
function typePredicateSubjectIndex(sourceCode, owner) {
|
|
966
|
+
const predicate = owner.returnType?.typeAnnotation;
|
|
967
|
+
if (predicate?.type !== "TSTypePredicate" || predicate.parameterName.type !== "Identifier") return null;
|
|
968
|
+
const predicateParameterName = predicate.parameterName.name;
|
|
969
|
+
const index = owner.params.findIndex((parameter) => functionParameterBindingName(parameter, sourceCode) === predicateParameterName);
|
|
970
|
+
return index === -1 ? null : index;
|
|
971
|
+
}
|
|
733
972
|
function annotationTarget(annotation, environment) {
|
|
734
973
|
return annotation === null || annotation === void 0 ? null : classifyWideningTarget(annotation.typeAnnotation, environment);
|
|
735
974
|
}
|
|
@@ -789,7 +1028,7 @@ const noKnownValueWideningRule = defineRule({
|
|
|
789
1028
|
const targetFromAnnotation = (annotation) => environment === null ? null : annotationTarget(annotation, environment);
|
|
790
1029
|
return {
|
|
791
1030
|
Program(node) {
|
|
792
|
-
environment = createTypeEnvironment(node);
|
|
1031
|
+
environment = createTypeEnvironment(node, context.sourceCode.visitorKeys);
|
|
793
1032
|
},
|
|
794
1033
|
VariableDeclarator(node) {
|
|
795
1034
|
if (node.init === null || node.id.type !== "Identifier") return;
|
|
@@ -811,6 +1050,27 @@ const noKnownValueWideningRule = defineRule({
|
|
|
811
1050
|
if (declarator === null || declarator.id.type !== "Identifier") return;
|
|
812
1051
|
reportFlow(node.right, targetFromAnnotation(declarator.id.typeAnnotation), `binding \`${declarator.id.name}\``);
|
|
813
1052
|
},
|
|
1053
|
+
CallExpression(node) {
|
|
1054
|
+
if (environment === null) return;
|
|
1055
|
+
const owner = localFunctionForCall(context.sourceCode, node.callee);
|
|
1056
|
+
if (owner === null) return;
|
|
1057
|
+
const parameterIndex = typePredicateSubjectIndex(context.sourceCode, owner);
|
|
1058
|
+
if (parameterIndex === null) return;
|
|
1059
|
+
const parameter = owner.params[parameterIndex];
|
|
1060
|
+
const argument = node.arguments[parameterIndex];
|
|
1061
|
+
if (parameter === void 0 || argument === void 0 || argument.type === "SpreadElement") return;
|
|
1062
|
+
const parameterAnnotation = functionParameterTypeAnnotation(parameter);
|
|
1063
|
+
if (parameterAnnotation === null || parameterAnnotation === void 0 || !containsUnknownType(parameterAnnotation.typeAnnotation)) return;
|
|
1064
|
+
if (!hasKnownCallArgumentEvidence(context.sourceCode, argument, environment)) return;
|
|
1065
|
+
context.report({
|
|
1066
|
+
node: argument,
|
|
1067
|
+
messageId: "widening",
|
|
1068
|
+
data: {
|
|
1069
|
+
subject: `argument for parameter \`${functionParameterBindingName(parameter, context.sourceCode)}\` of \`${functionName(context.sourceCode, owner)}\``,
|
|
1070
|
+
target: "unknown"
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
},
|
|
814
1074
|
ReturnStatement(node) {
|
|
815
1075
|
if (node.argument === null) return;
|
|
816
1076
|
const owner = enclosingFunction(node);
|
|
@@ -888,48 +1148,7 @@ const noModuleMockingRule = defineRule({
|
|
|
888
1148
|
}
|
|
889
1149
|
});
|
|
890
1150
|
//#endregion
|
|
891
|
-
//#region src/shared/lexical-type-parameters.ts
|
|
892
|
-
function isNode(value) {
|
|
893
|
-
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
|
|
894
|
-
}
|
|
895
|
-
function collectInferTypeParameterNames(node, visitorKeys, names) {
|
|
896
|
-
if (node.type === "TSInferType") names.add(node.typeParameter.name.name);
|
|
897
|
-
const record = node;
|
|
898
|
-
for (const key of visitorKeys[node.type] ?? []) {
|
|
899
|
-
const value = record[key];
|
|
900
|
-
if (isNode(value)) {
|
|
901
|
-
collectInferTypeParameterNames(value, visitorKeys, names);
|
|
902
|
-
continue;
|
|
903
|
-
}
|
|
904
|
-
if (!Array.isArray(value)) continue;
|
|
905
|
-
for (const child of value) if (isNode(child)) collectInferTypeParameterNames(child, visitorKeys, names);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
/** Collect type binders that are in scope at a node and can shadow module aliases. */
|
|
909
|
-
function lexicalTypeParameterNames(node, visitorKeys) {
|
|
910
|
-
const names = /* @__PURE__ */ new Set();
|
|
911
|
-
let descendant = node;
|
|
912
|
-
let current = node;
|
|
913
|
-
while (current !== null && current.type !== "Program") {
|
|
914
|
-
if ("typeParameters" in current) for (const parameter of current.typeParameters?.params ?? []) names.add(parameter.name.name);
|
|
915
|
-
if (current.type === "TSMappedType" && (descendant === current.nameType || descendant === current.typeAnnotation)) names.add(current.key.name);
|
|
916
|
-
if (current.type === "TSConditionalType" && descendant === current.trueType) collectInferTypeParameterNames(current.extendsType, visitorKeys, names);
|
|
917
|
-
descendant = current;
|
|
918
|
-
current = current.parent;
|
|
919
|
-
}
|
|
920
|
-
return names;
|
|
921
|
-
}
|
|
922
|
-
//#endregion
|
|
923
1151
|
//#region src/rules/no-object-parameters.ts
|
|
924
|
-
function parameterAnnotation$1(parameter) {
|
|
925
|
-
if (parameter.type === "TSParameterProperty") return parameterAnnotation$1(parameter.parameter);
|
|
926
|
-
if (parameter.type === "RestElement") return parameter.typeAnnotation ?? parameterAnnotation$1(parameter.argument);
|
|
927
|
-
if (parameter.type === "AssignmentPattern") return parameter.typeAnnotation ?? parameter.left.typeAnnotation;
|
|
928
|
-
return parameter.typeAnnotation;
|
|
929
|
-
}
|
|
930
|
-
function parameterName$1(parameter, sourceCode) {
|
|
931
|
-
return parameter.type === "Identifier" ? parameter.name : sourceCode.getText(parameter).replace(/\s*:\s*object\s*$/u, "");
|
|
932
|
-
}
|
|
933
1152
|
/** Ban the broad object type on function inputs, including local aliases to object. */
|
|
934
1153
|
const noObjectParametersRule = defineRule({
|
|
935
1154
|
meta: {
|
|
@@ -938,38 +1157,27 @@ const noObjectParametersRule = defineRule({
|
|
|
938
1157
|
messages: { objectParameter: "Parameter `{{parameter}}` uses the broad `object` type. Accept a named owner type; parse external input at its boundary before calling this function." }
|
|
939
1158
|
},
|
|
940
1159
|
createOnce(context) {
|
|
941
|
-
|
|
942
|
-
const resolvesToObject = (type
|
|
943
|
-
if (
|
|
944
|
-
if (
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
const alias = aliases.get(type.typeName.name);
|
|
948
|
-
if (alias === void 0) return false;
|
|
949
|
-
const nextVisited = new Set(visited);
|
|
950
|
-
nextVisited.add(type.typeName.name);
|
|
951
|
-
return resolvesToObject(alias, shadowedAliases, nextVisited);
|
|
952
|
-
};
|
|
1160
|
+
let environment = null;
|
|
1161
|
+
const resolvesToObject = (type) => environment !== null && resolvedTypeMatches(type, environment, (resolved, matches) => {
|
|
1162
|
+
if (resolved.type === "TSObjectKeyword") return true;
|
|
1163
|
+
if (resolved.type === "TSParenthesizedType") return matches(resolved.typeAnnotation);
|
|
1164
|
+
return resolved.type === "TSUnionType" && resolved.types.some(matches);
|
|
1165
|
+
});
|
|
953
1166
|
const checkParameters = (node) => {
|
|
954
|
-
const shadowedAliases = lexicalTypeParameterNames(node, context.sourceCode.visitorKeys);
|
|
955
1167
|
for (const parameter of node.params) {
|
|
956
|
-
const annotation =
|
|
1168
|
+
const annotation = functionParameterTypeAnnotation(parameter);
|
|
957
1169
|
if (annotation === null || annotation === void 0) continue;
|
|
958
|
-
if (!resolvesToObject(annotation.typeAnnotation
|
|
1170
|
+
if (!resolvesToObject(annotation.typeAnnotation)) continue;
|
|
959
1171
|
context.report({
|
|
960
1172
|
node: annotation.typeAnnotation,
|
|
961
1173
|
messageId: "objectParameter",
|
|
962
|
-
data: { parameter:
|
|
1174
|
+
data: { parameter: functionParameterBindingName(parameter, context.sourceCode) }
|
|
963
1175
|
});
|
|
964
1176
|
}
|
|
965
1177
|
};
|
|
966
1178
|
return {
|
|
967
1179
|
Program(node) {
|
|
968
|
-
|
|
969
|
-
for (const statement of node.body) {
|
|
970
|
-
const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
|
|
971
|
-
if (declaration?.type === "TSTypeAliasDeclaration" && (declaration.typeParameters === null || declaration.typeParameters === void 0)) aliases.set(declaration.id.name, declaration.typeAnnotation);
|
|
972
|
-
}
|
|
1180
|
+
environment = createTypeAliasEnvironment(node, context.sourceCode.visitorKeys);
|
|
973
1181
|
},
|
|
974
1182
|
ArrowFunctionExpression: checkParameters,
|
|
975
1183
|
FunctionDeclaration: checkParameters,
|
|
@@ -1059,6 +1267,19 @@ function isInsideTypeGuard(node) {
|
|
|
1059
1267
|
}
|
|
1060
1268
|
return false;
|
|
1061
1269
|
}
|
|
1270
|
+
/** Return whether typeof safely probes for the existence of a possibly absent binding. */
|
|
1271
|
+
function isExistenceProbe(node) {
|
|
1272
|
+
const parent = node.parent;
|
|
1273
|
+
if (parent.type !== "BinaryExpression") return false;
|
|
1274
|
+
if (![
|
|
1275
|
+
"===",
|
|
1276
|
+
"!==",
|
|
1277
|
+
"==",
|
|
1278
|
+
"!="
|
|
1279
|
+
].includes(parent.operator)) return false;
|
|
1280
|
+
const other = parent.left === node ? parent.right : parent.left;
|
|
1281
|
+
return other.type === "Literal" && other.value === "undefined";
|
|
1282
|
+
}
|
|
1062
1283
|
/** Disallow runtime typeof checks that narrow unparsed values instead of decoding them. */
|
|
1063
1284
|
const noRuntimeTypeofRule = defineRule({
|
|
1064
1285
|
meta: {
|
|
@@ -1076,7 +1297,7 @@ const noRuntimeTypeofRule = defineRule({
|
|
|
1076
1297
|
return { UnaryExpression(node) {
|
|
1077
1298
|
const option = context.options?.[0];
|
|
1078
1299
|
const allowInTypeGuards = typeof option === "object" && option !== null && !Array.isArray(option) && option.allowInTypeGuards === true;
|
|
1079
|
-
if (node.operator === "typeof" && (!allowInTypeGuards || !isInsideTypeGuard(node))) context.report({
|
|
1300
|
+
if (node.operator === "typeof" && !isExistenceProbe(node) && (!allowInTypeGuards || !isInsideTypeGuard(node))) context.report({
|
|
1080
1301
|
node,
|
|
1081
1302
|
messageId: "runtimeTypeof"
|
|
1082
1303
|
});
|
|
@@ -1089,6 +1310,12 @@ const FORBIDDEN_SYMBOL_NAME = "shape";
|
|
|
1089
1310
|
function containsForbiddenSymbolName(name) {
|
|
1090
1311
|
return name.toLowerCase().includes(FORBIDDEN_SYMBOL_NAME);
|
|
1091
1312
|
}
|
|
1313
|
+
/** Return whether an identifier names a statically accessed member owned by another value. */
|
|
1314
|
+
function isBorrowedMemberName(node) {
|
|
1315
|
+
const parent = node.parent;
|
|
1316
|
+
if (parent === null || parent.type !== "MemberExpression") return false;
|
|
1317
|
+
return parent.property === node && parent.computed === false;
|
|
1318
|
+
}
|
|
1092
1319
|
/** Ban the case-insensitive substring "shape" in every JavaScript and TypeScript symbol name. */
|
|
1093
1320
|
const noForbiddenTermInSymbolNamesRule = defineRule({
|
|
1094
1321
|
meta: {
|
|
@@ -1098,7 +1325,7 @@ const noForbiddenTermInSymbolNamesRule = defineRule({
|
|
|
1098
1325
|
},
|
|
1099
1326
|
createOnce(context) {
|
|
1100
1327
|
const reportForbiddenSymbolName = (node) => {
|
|
1101
|
-
if (!containsForbiddenSymbolName(node.name)) return;
|
|
1328
|
+
if (!containsForbiddenSymbolName(node.name) || isBorrowedMemberName(node)) return;
|
|
1102
1329
|
context.report({
|
|
1103
1330
|
node,
|
|
1104
1331
|
messageId: "forbiddenSymbolName",
|
|
@@ -1114,32 +1341,25 @@ const noForbiddenTermInSymbolNamesRule = defineRule({
|
|
|
1114
1341
|
});
|
|
1115
1342
|
//#endregion
|
|
1116
1343
|
//#region src/rules/no-unknown-parameters.ts
|
|
1117
|
-
function
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
if (parameter.type === "AssignmentPattern") return parameter.typeAnnotation ?? parameter.left.typeAnnotation;
|
|
1121
|
-
return parameter.typeAnnotation;
|
|
1122
|
-
}
|
|
1123
|
-
function parameterName(parameter, sourceText) {
|
|
1124
|
-
if (parameter.type === "TSParameterProperty") return parameterName(parameter.parameter, sourceText);
|
|
1125
|
-
if (parameter.type === "AssignmentPattern") return parameterName(parameter.left, sourceText);
|
|
1126
|
-
if (parameter.type === "RestElement") return parameterName(parameter.argument, sourceText);
|
|
1127
|
-
return parameter.type === "Identifier" ? parameter.name : sourceText.replace(/\s*:\s*unknown\s*$/u, "");
|
|
1344
|
+
function isTypePredicateSubject(owner, parameterName) {
|
|
1345
|
+
const predicate = owner.returnType?.typeAnnotation;
|
|
1346
|
+
return predicate?.type === "TSTypePredicate" && predicate.parameterName.type === "Identifier" && predicate.parameterName.name === parameterName;
|
|
1128
1347
|
}
|
|
1129
1348
|
/** Disallow unknown inputs except explicitly named error-cause enrichment. */
|
|
1130
1349
|
const noUnknownParametersRule = defineRule({
|
|
1131
1350
|
meta: {
|
|
1132
1351
|
type: "problem",
|
|
1133
|
-
docs: { description: "Disallow explicitly unknown function parameters except `cause
|
|
1352
|
+
docs: { description: "Disallow explicitly unknown function parameters except `cause` and type-predicate subjects; decode unknown input at its I/O boundary instead." },
|
|
1134
1353
|
messages: { unknownParameter: "Parameter `{{parameter}}` leaves input unparsed. Accept a named domain type; run the expected schema or parser at the I/O boundary before calling this function." }
|
|
1135
1354
|
},
|
|
1136
1355
|
createOnce(context) {
|
|
1137
1356
|
const checkParameters = (node) => {
|
|
1138
1357
|
for (const parameter of node.params) {
|
|
1139
|
-
const annotation =
|
|
1140
|
-
if (annotation
|
|
1141
|
-
|
|
1142
|
-
|
|
1358
|
+
const annotation = functionParameterTypeAnnotation(parameter);
|
|
1359
|
+
if (annotation === null || annotation === void 0) continue;
|
|
1360
|
+
if (!containsUnknownType(annotation.typeAnnotation)) continue;
|
|
1361
|
+
const name = functionParameterBindingName(parameter, context.sourceCode);
|
|
1362
|
+
if (name === "cause" || isTypePredicateSubject(node, name)) continue;
|
|
1143
1363
|
context.report({
|
|
1144
1364
|
node: annotation.typeAnnotation,
|
|
1145
1365
|
messageId: "unknownParameter",
|
|
@@ -1163,11 +1383,6 @@ const noUnknownParametersRule = defineRule({
|
|
|
1163
1383
|
});
|
|
1164
1384
|
//#endregion
|
|
1165
1385
|
//#region src/rules/no-unknown-returns.ts
|
|
1166
|
-
function referencedAliasName$1(type) {
|
|
1167
|
-
if (type.type === "TSParenthesizedType") return referencedAliasName$1(type.typeAnnotation);
|
|
1168
|
-
if (type.type !== "TSTypeReference" || type.typeName.type !== "Identifier") return null;
|
|
1169
|
-
return type.typeArguments === null || type.typeArguments === void 0 || type.typeArguments.params.length === 0 ? type.typeName.name : null;
|
|
1170
|
-
}
|
|
1171
1386
|
/** Ban function contracts that return unknown instead of a parsed domain type. */
|
|
1172
1387
|
const noUnknownReturnsRule = defineRule({
|
|
1173
1388
|
meta: {
|
|
@@ -1176,27 +1391,19 @@ const noUnknownReturnsRule = defineRule({
|
|
|
1176
1391
|
messages: { unknownReturn: "This function exposes `unknown` to its caller. Parse the value at its boundary and return a named domain type." }
|
|
1177
1392
|
},
|
|
1178
1393
|
createOnce(context) {
|
|
1179
|
-
|
|
1180
|
-
const resolvesToUnknown = (type
|
|
1181
|
-
if (
|
|
1182
|
-
if (
|
|
1183
|
-
if (
|
|
1184
|
-
if (
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
const name = referencedAliasName$1(type);
|
|
1189
|
-
if (name === null || visited.has(name) || shadowedAliases.has(name)) return false;
|
|
1190
|
-
const alias = aliases.get(name);
|
|
1191
|
-
if (alias === void 0 || alias.typeParameters !== null && alias.typeParameters !== void 0) return false;
|
|
1192
|
-
const nextVisited = new Set(visited);
|
|
1193
|
-
nextVisited.add(name);
|
|
1194
|
-
return resolvesToUnknown(alias.typeAnnotation, shadowedAliases, nextVisited);
|
|
1195
|
-
};
|
|
1394
|
+
let environment = null;
|
|
1395
|
+
const resolvesToUnknown = (type) => environment !== null && resolvedTypeMatches(type, environment, (resolved, matches) => {
|
|
1396
|
+
if (resolved.type === "TSUnknownKeyword") return true;
|
|
1397
|
+
if (resolved.type === "TSParenthesizedType") return matches(resolved.typeAnnotation);
|
|
1398
|
+
if (resolved.type === "TSUnionType") return resolved.types.some(matches);
|
|
1399
|
+
if (resolved.type !== "TSTypeReference" || resolved.typeName.type !== "Identifier" || resolved.typeName.name !== "Promise" && resolved.typeName.name !== "PromiseLike") return false;
|
|
1400
|
+
const value = resolved.typeArguments?.params[0];
|
|
1401
|
+
return value !== void 0 && matches(value);
|
|
1402
|
+
});
|
|
1196
1403
|
const checkReturnType = (node) => {
|
|
1197
1404
|
const annotation = node.returnType;
|
|
1198
1405
|
if (annotation === null || annotation === void 0) return;
|
|
1199
|
-
if (!resolvesToUnknown(annotation.typeAnnotation
|
|
1406
|
+
if (!resolvesToUnknown(annotation.typeAnnotation)) return;
|
|
1200
1407
|
context.report({
|
|
1201
1408
|
node: annotation.typeAnnotation,
|
|
1202
1409
|
messageId: "unknownReturn"
|
|
@@ -1204,11 +1411,7 @@ const noUnknownReturnsRule = defineRule({
|
|
|
1204
1411
|
};
|
|
1205
1412
|
return {
|
|
1206
1413
|
Program(node) {
|
|
1207
|
-
|
|
1208
|
-
for (const statement of node.body) {
|
|
1209
|
-
const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
|
|
1210
|
-
if (declaration?.type === "TSTypeAliasDeclaration") aliases.set(declaration.id.name, declaration);
|
|
1211
|
-
}
|
|
1414
|
+
environment = createTypeAliasEnvironment(node, context.sourceCode.visitorKeys);
|
|
1212
1415
|
},
|
|
1213
1416
|
ArrowFunctionExpression: checkReturnType,
|
|
1214
1417
|
FunctionDeclaration: checkReturnType,
|
|
@@ -1225,11 +1428,6 @@ const noUnknownReturnsRule = defineRule({
|
|
|
1225
1428
|
});
|
|
1226
1429
|
//#endregion
|
|
1227
1430
|
//#region src/rules/no-unknown-type-aliases.ts
|
|
1228
|
-
function referencedAliasName(type) {
|
|
1229
|
-
if (type.type === "TSParenthesizedType") return referencedAliasName(type.typeAnnotation);
|
|
1230
|
-
if (type.type !== "TSTypeReference" || type.typeName.type !== "Identifier") return null;
|
|
1231
|
-
return type.typeArguments === null || type.typeArguments === void 0 || type.typeArguments.params.length === 0 ? type.typeName.name : null;
|
|
1232
|
-
}
|
|
1233
1431
|
/** Ban named aliases that merely conceal TypeScript's unknown top type. */
|
|
1234
1432
|
const noUnknownTypeAliasesRule = defineRule({
|
|
1235
1433
|
meta: {
|
|
@@ -1238,33 +1436,25 @@ const noUnknownTypeAliasesRule = defineRule({
|
|
|
1238
1436
|
messages: { unknownAlias: "Type alias `{{alias}}` hides `unknown`. Keep `unknown` explicit at the parsing boundary or on an allowed `cause` field; otherwise use the parsed owner type." }
|
|
1239
1437
|
},
|
|
1240
1438
|
createOnce(context) {
|
|
1241
|
-
|
|
1242
|
-
const resolvesToUnknown = (type
|
|
1243
|
-
if (
|
|
1244
|
-
if (
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
return { Program(node) {
|
|
1254
|
-
aliases.clear();
|
|
1255
|
-
for (const statement of node.body) {
|
|
1256
|
-
const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
|
|
1257
|
-
if (declaration?.type === "TSTypeAliasDeclaration") aliases.set(declaration.id.name, declaration);
|
|
1258
|
-
}
|
|
1259
|
-
for (const alias of aliases.values()) {
|
|
1260
|
-
if (!resolvesToUnknown(alias.typeAnnotation, /* @__PURE__ */ new Set([alias.id.name]))) continue;
|
|
1439
|
+
let environment = null;
|
|
1440
|
+
const resolvesToUnknown = (type) => environment !== null && resolvedTypeMatches(type, environment, (resolved, matches) => {
|
|
1441
|
+
if (resolved.type === "TSUnknownKeyword") return true;
|
|
1442
|
+
if (resolved.type === "TSParenthesizedType") return matches(resolved.typeAnnotation);
|
|
1443
|
+
return resolved.type === "TSUnionType" && resolved.types.some(matches);
|
|
1444
|
+
});
|
|
1445
|
+
return {
|
|
1446
|
+
Program(node) {
|
|
1447
|
+
environment = createTypeAliasEnvironment(node, context.sourceCode.visitorKeys);
|
|
1448
|
+
},
|
|
1449
|
+
TSTypeAliasDeclaration(node) {
|
|
1450
|
+
if (!resolvesToUnknown(node.typeAnnotation)) return;
|
|
1261
1451
|
context.report({
|
|
1262
|
-
node:
|
|
1452
|
+
node: node.id,
|
|
1263
1453
|
messageId: "unknownAlias",
|
|
1264
|
-
data: { alias:
|
|
1454
|
+
data: { alias: node.id.name }
|
|
1265
1455
|
});
|
|
1266
1456
|
}
|
|
1267
|
-
}
|
|
1457
|
+
};
|
|
1268
1458
|
}
|
|
1269
1459
|
});
|
|
1270
1460
|
//#endregion
|
|
@@ -1325,9 +1515,20 @@ function isInsideTypeAliasDeclaration(node) {
|
|
|
1325
1515
|
function isPlainAliasConsumerUse(node, environment) {
|
|
1326
1516
|
if (node.type !== "TSTypeReference" || node.typeArguments?.params.length) return false;
|
|
1327
1517
|
const name = typeReferenceName$1(node);
|
|
1328
|
-
return name !== null && environment.
|
|
1518
|
+
return name !== null && visibleTypeAlias(name, node, environment.typeAliases) !== null && !isInsideTypeAliasDeclaration(node);
|
|
1519
|
+
}
|
|
1520
|
+
function isInsideTypeParameterConstraint(node) {
|
|
1521
|
+
let child = node;
|
|
1522
|
+
let parent = child.parent;
|
|
1523
|
+
while (parent !== null && parent.type !== "Program") {
|
|
1524
|
+
if (parent.type === "TSTypeParameter" && parent.constraint === child) return true;
|
|
1525
|
+
child = parent;
|
|
1526
|
+
parent = child.parent;
|
|
1527
|
+
}
|
|
1528
|
+
return false;
|
|
1329
1529
|
}
|
|
1330
1530
|
function shouldReportType(node, environment) {
|
|
1531
|
+
if (isInsideTypeParameterConstraint(node)) return false;
|
|
1331
1532
|
if (isPlainAliasConsumerUse(node, environment)) return false;
|
|
1332
1533
|
if (classifyUnsafeDictionary(node, environment) === null) return false;
|
|
1333
1534
|
let current = node.parent;
|
|
@@ -1361,7 +1562,7 @@ const noUnsafeDictionaryTypeRule = defineRule({
|
|
|
1361
1562
|
};
|
|
1362
1563
|
return {
|
|
1363
1564
|
Program(node) {
|
|
1364
|
-
environment = createTypeEnvironment(node);
|
|
1565
|
+
environment = createTypeEnvironment(node, context.sourceCode.visitorKeys);
|
|
1365
1566
|
},
|
|
1366
1567
|
TSTypeReference: reportIfUnsafe,
|
|
1367
1568
|
TSTypeLiteral: reportIfUnsafe,
|
|
@@ -1565,6 +1766,7 @@ const noWidenThenAssertRule = defineRule({
|
|
|
1565
1766
|
});
|
|
1566
1767
|
//#endregion
|
|
1567
1768
|
//#region src/rules/require-safety-comment-for-type-assertion.ts
|
|
1769
|
+
const DEFAULT_SAFETY_MARKERS = ["SAFETY"];
|
|
1568
1770
|
const commentOwnerKinds = /* @__PURE__ */ new Set([
|
|
1569
1771
|
"ExpressionStatement",
|
|
1570
1772
|
"PropertyDefinition",
|
|
@@ -1575,11 +1777,29 @@ const commentOwnerKinds = /* @__PURE__ */ new Set([
|
|
|
1575
1777
|
function isConstAssertion(node) {
|
|
1576
1778
|
return node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "const";
|
|
1577
1779
|
}
|
|
1578
|
-
function
|
|
1780
|
+
function configuredSafetyMarkers(option) {
|
|
1781
|
+
if (typeof option !== "object" || option === null || !("markers" in option)) return DEFAULT_SAFETY_MARKERS;
|
|
1782
|
+
const configured = option.markers;
|
|
1783
|
+
if (!Array.isArray(configured)) return DEFAULT_SAFETY_MARKERS;
|
|
1784
|
+
const markers = configured.flatMap((marker) => typeof marker === "string" && marker.trim().length > 0 ? [marker.trim()] : []);
|
|
1785
|
+
return markers.length > 0 ? markers : DEFAULT_SAFETY_MARKERS;
|
|
1786
|
+
}
|
|
1787
|
+
function markerPattern(markers) {
|
|
1788
|
+
const alternation = markers.map((marker) => marker.replaceAll(/[.*+?^${}()|[\]\\]/gu, String.raw`\$&`)).join("|");
|
|
1789
|
+
return new RegExp(String.raw`(?:^|[^\p{L}\p{N}_])(?:${alternation})\s*:\s*\S`, "u");
|
|
1790
|
+
}
|
|
1791
|
+
function hasSafetyJustificationBefore(sourceCode, owner, assertion, pattern) {
|
|
1792
|
+
return sourceCode.getCommentsBefore(owner).some((comment) => comment.end <= assertion.start && pattern.test(comment.value));
|
|
1793
|
+
}
|
|
1794
|
+
function hasSafetyComment(sourceCode, node, pattern) {
|
|
1579
1795
|
let current = node;
|
|
1580
1796
|
while (true) {
|
|
1581
|
-
if (sourceCode
|
|
1582
|
-
if (commentOwnerKinds.has(current.type)
|
|
1797
|
+
if (hasSafetyJustificationBefore(sourceCode, current, node, pattern)) return true;
|
|
1798
|
+
if (commentOwnerKinds.has(current.type)) {
|
|
1799
|
+
const exportDeclaration = current.parent;
|
|
1800
|
+
return exportDeclaration.type === "ExportNamedDeclaration" && exportDeclaration.declaration === current && hasSafetyJustificationBefore(sourceCode, exportDeclaration, node, pattern);
|
|
1801
|
+
}
|
|
1802
|
+
if (current.parent.type === "Program") return false;
|
|
1583
1803
|
current = current.parent;
|
|
1584
1804
|
}
|
|
1585
1805
|
}
|
|
@@ -1607,14 +1827,35 @@ const antiSlopPlugin = eslintCompatPlugin({
|
|
|
1607
1827
|
meta: {
|
|
1608
1828
|
type: "problem",
|
|
1609
1829
|
docs: { description: "Require a nearby SAFETY comment for every TypeScript type assertion except const assertions." },
|
|
1610
|
-
messages: { missingSafetyComment: "This type assertion has no `
|
|
1830
|
+
messages: { missingSafetyComment: "This type assertion has no `{{marker}}:` justification. State the checked invariant immediately before the assertion or its containing statement." },
|
|
1831
|
+
schema: [{
|
|
1832
|
+
type: "object",
|
|
1833
|
+
properties: { markers: {
|
|
1834
|
+
type: "array",
|
|
1835
|
+
items: {
|
|
1836
|
+
type: "string",
|
|
1837
|
+
minLength: 1
|
|
1838
|
+
},
|
|
1839
|
+
minItems: 1,
|
|
1840
|
+
uniqueItems: true
|
|
1841
|
+
} },
|
|
1842
|
+
additionalProperties: false
|
|
1843
|
+
}],
|
|
1844
|
+
defaultOptions: [{ markers: ["SAFETY"] }]
|
|
1611
1845
|
},
|
|
1612
1846
|
createOnce(context) {
|
|
1847
|
+
const patterns = /* @__PURE__ */ new Map();
|
|
1613
1848
|
const checkAssertion = (node) => {
|
|
1614
|
-
if (isConstAssertion(node)
|
|
1849
|
+
if (isConstAssertion(node)) return;
|
|
1850
|
+
const markers = configuredSafetyMarkers(context.options?.[0]);
|
|
1851
|
+
const patternKey = markers.join("\0");
|
|
1852
|
+
const pattern = patterns.get(patternKey) ?? markerPattern(markers);
|
|
1853
|
+
patterns.set(patternKey, pattern);
|
|
1854
|
+
if (hasSafetyComment(context.sourceCode, node, pattern)) return;
|
|
1615
1855
|
context.report({
|
|
1616
1856
|
node,
|
|
1617
|
-
messageId: "missingSafetyComment"
|
|
1857
|
+
messageId: "missingSafetyComment",
|
|
1858
|
+
data: { marker: markers[0] ?? DEFAULT_SAFETY_MARKERS[0] }
|
|
1618
1859
|
});
|
|
1619
1860
|
};
|
|
1620
1861
|
return {
|