@sarj/eslint-plugin 19.8.0 → 19.9.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/catalog.js +5 -5
- package/dist/{chunk-G5BIV4A4.js → chunk-MFL7OATV.js} +55 -22
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/catalog.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
publicDocumentation,
|
|
3
3
|
ruleSpec,
|
|
4
4
|
src_default
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MFL7OATV.js";
|
|
6
6
|
|
|
7
7
|
// src/docs/directive-disable-enable-pair.ts
|
|
8
8
|
var DIRECTIVE_DISABLE_ENABLE_PAIR_DOCUMENTATION = {
|
|
@@ -4527,11 +4527,11 @@ var PREFER_DISCRIMINATED_UNION_DOCUMENTATION = {
|
|
|
4527
4527
|
category: "correctness",
|
|
4528
4528
|
autofix: "safe",
|
|
4529
4529
|
limitations: [
|
|
4530
|
-
"Names, Boolean counts and optional-field counts never establish a finding. A producer's
|
|
4531
|
-
"Producer analysis requires a finite literal tag, every declared tag syntactically witnessed, exhaustive simple return paths without literal constant conditions, plain object returns, and consistent optional-field presence for each tag. It resolves only unmerged local aliases/interfaces without inheritance or generics, and the built-in Promise around async returns. This is syntax-only evidence of field presence; explicit undefined, void and null payloads
|
|
4532
|
-
"A switch producer is supported only for a non-overloaded function declaration when its sole body statement switches on an unchanged direct string or number parameter, has unique same-kind literal labels and a default, and every nonempty case body is a sole object return or throw, optionally inside sole blocks. Empty labels may share the next terminating body; nested conditions, conditional returns, statement prefixes, breaks, other fallthrough, captured/closed inputs and switches under preceding control flow abstain. Broad parameter domains establish input witnesses for each label and the default; this does not infer branch-specific runtime effects or expression value types. Path visits, depth and returned objects are bounded conservatively.",
|
|
4530
|
+
"Names, Boolean counts and optional-field counts never establish a finding. A producer's literal writes establish either tag-specific payload presence or a pair of required Boolean fields with two or three distinct combinations, with both fields varying. Boolean members may use boolean, true | false or one unmerged nongeneric local alias of either. All four combinations, constant dimensions, optional/unknown dimensions and expression-valued flags are excluded. At most eight Boolean fields are compared. Only one relationship is reported per contract; payload evidence takes precedence. Independently nullable payloads do not erase Boolean evidence.",
|
|
4531
|
+
"Producer analysis requires a finite literal tag, every declared tag syntactically witnessed, exhaustive simple return paths without literal constant conditions, plain object returns, and consistent optional-field presence for each tag. It resolves only unmerged local aliases/interfaces without inheritance or generics, one local alias of a literal tag domain, and the built-in Promise around async returns. Callable or opaque own then properties abstain for async producers because Promise assimilation can replace the fulfilled value. Inherited then methods and external prototype mutation are not modeled. This is syntax-only evidence of field presence; explicit undefined, void and null payloads do not establish payload requirements, while expression value types are not inferred. Type-only assertion/satisfies/non-null wrappers, literal computed string keys and a sole-use const immediately before its return are recognized. Const alias chains, observation, mutation and dynamic eval abstain. Dynamic returns, spreads, dynamic computed keys, accessors, loops and try statements are deliberately skipped.",
|
|
4532
|
+
"A switch producer is supported only for a non-overloaded function declaration when its sole body statement switches on an unchanged direct string or number parameter, has unique same-kind literal labels and a default, and every nonempty case body is a sole object return or throw, optionally inside sole blocks. Empty labels may share the next terminating body; nested conditions, conditional returns, statement prefixes, breaks, other fallthrough, captured/closed inputs, dynamic eval and switches under preceding control flow abstain. Broad parameter domains establish input witnesses for each label and the default; this does not infer branch-specific runtime effects or expression value types. Path visits, depth and returned objects are bounded conservatively.",
|
|
4533
4533
|
"These are refactoring advisories, not proven defects or proof that other writers cannot produce more states. Historical observations, partial results, unknown states and independently completing operations may need the broader contract. Review all readers and writers before migrating a shared contract; preserve every valid combination. A union of existing literal flag pairs is the smallest type-only replacement; a named disposition is a reviewed domain migration.",
|
|
4534
|
-
"Automatic fixes are limited to an unused private nongeneric synchronous function with a direct inline contract and one conditional return containing at most four literal objects.
|
|
4534
|
+
"Automatic fixes are limited to an unused private nongeneric synchronous function with a direct inline contract and one conditional return containing at most four bare literal objects. Boolean-pair fixes narrow only the implicated pair, retaining every unrelated member's type, readonly modifier and optionality. Tag/payload fixes express the produced state-specific payload presence while preserving declared payload value types. All runtime code, condition evaluation, object creation, identity and exceptions remain unchanged. Nested conditionals are supported. Assertion/satisfies wrappers, shared/observed contracts, aliases, exports, async functions, comments, expression payloads and generated/declaration/test files remain excluded. Public interfaces, serialization and cancellation are never rewritten."
|
|
4535
4535
|
],
|
|
4536
4536
|
references: ["https://functional-architecture.org/make_illegal_states_unrepresentable/", "https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions"],
|
|
4537
4537
|
examples: [
|
|
@@ -22449,14 +22449,19 @@ function discriminatedUnionReturns(node, sourceCode2) {
|
|
|
22449
22449
|
if (shape === void 0) return void 0;
|
|
22450
22450
|
const returned2 = returnedProperties(paths.objects, shape);
|
|
22451
22451
|
if (returned2 === void 0) return void 0;
|
|
22452
|
+
if (node.async && returned2.some((properties2) => {
|
|
22453
|
+
const then = properties2.get("then");
|
|
22454
|
+
return then !== void 0 && (then.type !== "Literal" || typeof then.value === "object" && then.value !== null);
|
|
22455
|
+
})) return void 0;
|
|
22456
|
+
const emptyPayload = returned2.some((properties2) => hasEmptyPayload(properties2, shape.optional));
|
|
22452
22457
|
for (const [name, member3] of shape.members) {
|
|
22453
|
-
if (member3.optional) continue;
|
|
22454
|
-
const domain = tagDomain(member3.typeAnnotation?.typeAnnotation);
|
|
22458
|
+
if (member3.optional || emptyPayload) continue;
|
|
22459
|
+
const domain = tagDomain(member3.typeAnnotation?.typeAnnotation, sourceCode2);
|
|
22455
22460
|
if (domain !== void 0 && hasTagSpecificPayload(name, domain, shape.optional, returned2)) {
|
|
22456
22461
|
return { reportNode: contract.reportNode, relationship: { kind: "presence", tag: name }, members: shape.members, returned: returned2 };
|
|
22457
22462
|
}
|
|
22458
22463
|
}
|
|
22459
|
-
const fields2 = correlatedBooleanFields(shape, returned2);
|
|
22464
|
+
const fields2 = correlatedBooleanFields(shape, returned2, sourceCode2);
|
|
22460
22465
|
return fields2 === void 0 ? void 0 : {
|
|
22461
22466
|
reportNode: contract.reportNode,
|
|
22462
22467
|
relationship: { kind: "boolean-pair", fields: fields2 },
|
|
@@ -22464,8 +22469,8 @@ function discriminatedUnionReturns(node, sourceCode2) {
|
|
|
22464
22469
|
returned: returned2
|
|
22465
22470
|
};
|
|
22466
22471
|
}
|
|
22467
|
-
function correlatedBooleanFields(shape, returned2) {
|
|
22468
|
-
const fields2 = [...shape.members].filter(([, member3]) => !member3.optional && member3.typeAnnotation?.typeAnnotation
|
|
22472
|
+
function correlatedBooleanFields(shape, returned2, sourceCode2) {
|
|
22473
|
+
const fields2 = [...shape.members].filter(([, member3]) => !member3.optional && booleanType(member3.typeAnnotation?.typeAnnotation, sourceCode2)).map(([name]) => name);
|
|
22469
22474
|
if (fields2.length > 8) return void 0;
|
|
22470
22475
|
for (const [index, left] of fields2.entries()) {
|
|
22471
22476
|
for (const right of fields2.slice(index + 1)) {
|
|
@@ -22475,6 +22480,10 @@ function correlatedBooleanFields(shape, returned2) {
|
|
|
22475
22480
|
}
|
|
22476
22481
|
return void 0;
|
|
22477
22482
|
}
|
|
22483
|
+
function booleanType(type, sourceCode2) {
|
|
22484
|
+
const domain = tagDomain(type, sourceCode2);
|
|
22485
|
+
return domain?.size === 2 && domain.has(true) && domain.has(false);
|
|
22486
|
+
}
|
|
22478
22487
|
function booleanCombinations(left, right, returned2) {
|
|
22479
22488
|
const combinations = /* @__PURE__ */ new Set();
|
|
22480
22489
|
for (const properties2 of returned2) {
|
|
@@ -22514,15 +22523,7 @@ function returnContract(node, sourceCode2) {
|
|
|
22514
22523
|
}
|
|
22515
22524
|
function definitions(reference, sourceCode2) {
|
|
22516
22525
|
if (reference.typeName.type !== "Identifier") return [];
|
|
22517
|
-
|
|
22518
|
-
while (scope !== null) {
|
|
22519
|
-
const found = scope.set.get(reference.typeName.name);
|
|
22520
|
-
const binding3 = found === void 0 ? void 0 : tsVariable(found);
|
|
22521
|
-
const typeDefinitions2 = binding3?.defs.filter((definition) => definition.isTypeDefinition) ?? [];
|
|
22522
|
-
if (typeDefinitions2.length > 0 || binding3?.isTypeVariable === true) return typeDefinitions2;
|
|
22523
|
-
scope = scope.upper;
|
|
22524
|
-
}
|
|
22525
|
-
return [];
|
|
22526
|
+
return typeBinding(sourceCode2, reference.typeName)?.defs.filter((definition) => definition.isTypeDefinition) ?? [];
|
|
22526
22527
|
}
|
|
22527
22528
|
function objectContract(elements) {
|
|
22528
22529
|
const members2 = /* @__PURE__ */ new Map();
|
|
@@ -22543,7 +22544,6 @@ function returnedProperties(objects, contract) {
|
|
|
22543
22544
|
if (properties2 === void 0) return void 0;
|
|
22544
22545
|
if ([...properties2.keys()].some((name) => !contract.members.has(name))) return void 0;
|
|
22545
22546
|
if ([...contract.members].some(([name, member3]) => !member3.optional && !properties2.has(name))) return void 0;
|
|
22546
|
-
if (hasEmptyPayload(properties2, contract.optional)) return void 0;
|
|
22547
22547
|
returned2.push(properties2);
|
|
22548
22548
|
}
|
|
22549
22549
|
return returned2;
|
|
@@ -22568,7 +22568,13 @@ function hasEmptyPayload(properties2, optional) {
|
|
|
22568
22568
|
}
|
|
22569
22569
|
return false;
|
|
22570
22570
|
}
|
|
22571
|
-
function tagDomain(node) {
|
|
22571
|
+
function tagDomain(node, sourceCode2) {
|
|
22572
|
+
if (node?.type === "TSTypeReference" && !isPresent(node.typeArguments)) {
|
|
22573
|
+
const bindings = definitions(node, sourceCode2);
|
|
22574
|
+
const declaration = bindings.length === 1 ? bindings[0]?.node : void 0;
|
|
22575
|
+
if (declaration?.type !== "TSTypeAliasDeclaration" || isPresent(declaration.typeParameters)) return void 0;
|
|
22576
|
+
node = declaration.typeAnnotation;
|
|
22577
|
+
}
|
|
22572
22578
|
if (node?.type === "TSBooleanKeyword") return /* @__PURE__ */ new Set([true, false]);
|
|
22573
22579
|
if (node?.type !== "TSUnionType") return void 0;
|
|
22574
22580
|
const values = /* @__PURE__ */ new Set();
|
|
@@ -22648,6 +22654,7 @@ function reachableSwitch(statement, sourceCode2) {
|
|
|
22648
22654
|
return kind !== void 0 && reachableSwitchLabels(statement, kind);
|
|
22649
22655
|
}
|
|
22650
22656
|
function broadSwitchInput(statement, sourceCode2) {
|
|
22657
|
+
if (hasIdentifierNamed(sourceCode2, "eval")) return void 0;
|
|
22651
22658
|
const body3 = statement.parent;
|
|
22652
22659
|
if (body3.type !== "BlockStatement" || body3.body.length !== 1) return void 0;
|
|
22653
22660
|
const owner = body3.parent;
|
|
@@ -22699,6 +22706,13 @@ function statementsPaths(statements, budget, depth) {
|
|
|
22699
22706
|
}
|
|
22700
22707
|
function returnedObjects(expression, budget, depth) {
|
|
22701
22708
|
if (!budget.within(depth)) return void 0;
|
|
22709
|
+
if (isErasedExpression(expression)) {
|
|
22710
|
+
return returnedObjects(expression.expression, budget, depth + 1);
|
|
22711
|
+
}
|
|
22712
|
+
if (expression.type === "Identifier") {
|
|
22713
|
+
const initializer = closedReturnInitializer(expression, budget.sourceCode);
|
|
22714
|
+
return initializer === void 0 ? void 0 : returnedObjects(initializer, budget, depth + 1);
|
|
22715
|
+
}
|
|
22702
22716
|
if (expression.type === "ObjectExpression") {
|
|
22703
22717
|
return budget.recordObject() ? [expression] : void 0;
|
|
22704
22718
|
}
|
|
@@ -22707,10 +22721,21 @@ function returnedObjects(expression, budget, depth) {
|
|
|
22707
22721
|
const alternate = returnedObjects(expression.alternate, budget, depth + 1);
|
|
22708
22722
|
return consequent === void 0 || alternate === void 0 ? void 0 : [...consequent, ...alternate];
|
|
22709
22723
|
}
|
|
22724
|
+
function closedReturnInitializer(identifier, sourceCode2) {
|
|
22725
|
+
const returned2 = identifier.parent;
|
|
22726
|
+
if (returned2.type !== "ReturnStatement" || returned2.argument !== identifier || returned2.parent.type !== "BlockStatement" || hasIdentifierNamed(sourceCode2, "eval")) return void 0;
|
|
22727
|
+
const block = returned2.parent;
|
|
22728
|
+
const declaration = block.body[block.body.indexOf(returned2) - 1];
|
|
22729
|
+
if (declaration?.type !== "VariableDeclaration" || declaration.kind !== "const" || declaration.declarations.length !== 1) return void 0;
|
|
22730
|
+
const value = declaration.declarations[0];
|
|
22731
|
+
if (value?.id.type !== "Identifier" || value.id.name !== identifier.name || value.init === null) return void 0;
|
|
22732
|
+
const binding3 = findVariable(sourceCode2.getScope(identifier), identifier.name);
|
|
22733
|
+
if (binding3?.defs.length !== 1 || binding3.defs[0]?.node !== value || binding3.eslintUsed || !binding3.references.some((reference) => reference.identifier === identifier && reference.isRead()) || binding3.references.some((reference) => reference.isWrite() ? reference.init !== true : reference.identifier !== identifier)) return void 0;
|
|
22734
|
+
return value.init;
|
|
22735
|
+
}
|
|
22710
22736
|
function propertyName(node) {
|
|
22711
|
-
if (node.computed) return void 0;
|
|
22712
|
-
if (node.key.type === "Identifier") return node.key.name;
|
|
22713
22737
|
if (node.key.type === "Literal" && typeof node.key.value === "string") return node.key.value;
|
|
22738
|
+
if (!node.computed && node.key.type === "Identifier") return node.key.name;
|
|
22714
22739
|
return void 0;
|
|
22715
22740
|
}
|
|
22716
22741
|
function literalTag(node) {
|
|
@@ -22781,19 +22806,27 @@ function returnsOnlyConditionalObject(fn) {
|
|
|
22781
22806
|
if (fn.async || fn.generator || isPresent(fn.typeParameters)) return false;
|
|
22782
22807
|
const statements = fn.body?.body ?? [];
|
|
22783
22808
|
const [returned2] = statements;
|
|
22784
|
-
return statements.length === 1 && returned2?.type === "ReturnStatement" && returned2.argument?.type === "ConditionalExpression";
|
|
22809
|
+
return statements.length === 1 && returned2?.type === "ReturnStatement" && returned2.argument?.type === "ConditionalExpression" && bareConditionalObjects(returned2.argument);
|
|
22810
|
+
}
|
|
22811
|
+
function bareConditionalObjects(expression) {
|
|
22812
|
+
return expression.type === "ObjectExpression" || expression.type === "ConditionalExpression" && bareConditionalObjects(expression.consequent) && bareConditionalObjects(expression.alternate);
|
|
22785
22813
|
}
|
|
22786
22814
|
function privateVariantType(properties2, result, sourceCode2) {
|
|
22787
22815
|
if ([...properties2.values()].some((value) => value.type !== "Literal" || typeof value.value === "object" && value.value !== null || typeof value.value === "number" && !Number.isFinite(value.value))) return null;
|
|
22788
22816
|
const fields2 = [];
|
|
22789
22817
|
for (const [name, member3] of result.members) {
|
|
22790
22818
|
const value = properties2.get(name);
|
|
22791
|
-
if (value === void 0) continue;
|
|
22819
|
+
if (value === void 0 && result.relationship.kind === "presence") continue;
|
|
22792
22820
|
const annotation = member3.typeAnnotation?.typeAnnotation;
|
|
22793
22821
|
if (annotation === void 0) return null;
|
|
22794
22822
|
const narrow = result.relationship.kind === "presence" ? name === result.relationship.tag : name === result.relationship.fields.left || name === result.relationship.fields.right;
|
|
22795
|
-
|
|
22796
|
-
|
|
22823
|
+
let type = sourceCode2.getText(annotation);
|
|
22824
|
+
if (narrow) {
|
|
22825
|
+
if (value === void 0) return null;
|
|
22826
|
+
type = sourceCode2.getText(value);
|
|
22827
|
+
}
|
|
22828
|
+
const optional = result.relationship.kind === "boolean-pair" && !narrow && member3.optional;
|
|
22829
|
+
fields2.push(`${member3.readonly ? "readonly " : ""}${sourceCode2.getText(member3.key)}${optional ? "?" : ""}: ${type}`);
|
|
22797
22830
|
}
|
|
22798
22831
|
return `{ ${fields2.join("; ")} }`;
|
|
22799
22832
|
}
|
package/dist/index.js
CHANGED