@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 CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  publicDocumentation,
3
3
  ruleSpec,
4
4
  src_default
5
- } from "./chunk-G5BIV4A4.js";
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 direct 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. All four combinations, constant 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.",
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 are skipped, while expression value types are not inferred. Dynamic returns, spreads, 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 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. They narrow only the existing discriminator or implicated Boolean pair and preserve other member types and all runtime code, including condition evaluation, object creation, identity and exceptions. Nested conditionals are supported. 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."
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.type === "TSBooleanKeyword").map(([name]) => name);
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
- let scope = sourceCode2.getScope(reference);
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
- const type = narrow ? sourceCode2.getText(value) : sourceCode2.getText(annotation);
22796
- fields2.push(`${member3.readonly ? "readonly " : ""}${sourceCode2.getText(member3.key)}: ${type}`);
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
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  src_default
3
- } from "./chunk-G5BIV4A4.js";
3
+ } from "./chunk-MFL7OATV.js";
4
4
  export {
5
5
  src_default as default
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "19.8.0",
3
+ "version": "19.9.0",
4
4
  "packageManager": "npm@12.2.1",
5
5
  "description": "Custom Oxlint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",