@sarj/eslint-plugin 16.38.50 → 16.38.53

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.cjs CHANGED
@@ -2573,16 +2573,68 @@ function typeBinding(source, identifier) {
2573
2573
  }
2574
2574
  return null;
2575
2575
  }
2576
+ var REFERENCE_INDEXES = /* @__PURE__ */ new WeakMap();
2576
2577
  function retainsNamedTypeUses(source, removed) {
2577
2578
  const manager = source.scopeManager;
2578
2579
  if (manager === null) return false;
2579
- const references = manager.scopes.flatMap((scope) => scope.references).filter((reference) => reference.isRead() && insideTypeRange(reference.identifier, removed));
2580
- return references.every((reference) => {
2580
+ const index = referenceIndex(source, manager);
2581
+ for (const reference of referencesWithin(index.references, removed)) {
2582
+ if (!reference.isRead()) continue;
2581
2583
  const binding3 = reference.resolved;
2582
2584
  if (!binding3 || !reference.isTypeReference && !reference.isValueReference) return false;
2583
- if (binding3.defs.length === 0) return true;
2584
- return binding3.references.some((retained) => retained.isRead() && (!reference.isTypeReference || retained.isTypeReference) && (!reference.isValueReference || retained.isValueReference) && !insideTypeRange(retained.identifier, removed) && !binding3.defs.some((definition) => insideTypeRange(retained.identifier, definition.node)) && publicTypeUse(retained.identifier));
2585
- });
2585
+ if (binding3.defs.length === 0) continue;
2586
+ const reads = publicReadExtents(index, binding3);
2587
+ const extent = reference.isTypeReference ? reference.isValueReference ? reads.both : reads.type : reads.value;
2588
+ if (extent === null || extent.minStart >= removed.range[0] && extent.maxEnd <= removed.range[1]) return false;
2589
+ }
2590
+ return true;
2591
+ }
2592
+ function referenceIndex(source, manager) {
2593
+ const cached = REFERENCE_INDEXES.get(source);
2594
+ if (cached?.manager === manager) return cached;
2595
+ const references = manager.scopes.flatMap((scope) => scope.references);
2596
+ references.sort((left, right) => left.identifier.range[0] - right.identifier.range[0]);
2597
+ const index = { manager, references, publicReads: /* @__PURE__ */ new Map() };
2598
+ REFERENCE_INDEXES.set(source, index);
2599
+ return index;
2600
+ }
2601
+ function* referencesWithin(references, removed) {
2602
+ let low = 0;
2603
+ let high = references.length;
2604
+ while (low < high) {
2605
+ const middle = low + Math.floor((high - low) / 2);
2606
+ const reference = references[middle];
2607
+ if (reference === void 0 || reference.identifier.range[0] >= removed.range[0]) high = middle;
2608
+ else low = middle + 1;
2609
+ }
2610
+ for (let index = low; index < references.length; index++) {
2611
+ const reference = references[index];
2612
+ if (reference === void 0) continue;
2613
+ if (reference.identifier.range[0] > removed.range[1]) break;
2614
+ if (insideTypeRange(reference.identifier, removed)) yield reference;
2615
+ }
2616
+ }
2617
+ function publicReadExtents(index, binding3) {
2618
+ const cached = index.publicReads.get(binding3);
2619
+ if (cached !== void 0) return cached;
2620
+ let type = null;
2621
+ let value = null;
2622
+ let both = null;
2623
+ for (const retained of binding3.references) {
2624
+ if (!retained.isRead() || !retained.isTypeReference && !retained.isValueReference || binding3.defs.some((definition) => insideTypeRange(retained.identifier, definition.node)) || !publicTypeUse(retained.identifier)) continue;
2625
+ if (retained.isTypeReference) type = extendExtent(type, retained.identifier);
2626
+ if (retained.isValueReference) value = extendExtent(value, retained.identifier);
2627
+ if (retained.isTypeReference && retained.isValueReference) both = extendExtent(both, retained.identifier);
2628
+ }
2629
+ const result = { type, value, both };
2630
+ index.publicReads.set(binding3, result);
2631
+ return result;
2632
+ }
2633
+ function extendExtent(extent, identifier) {
2634
+ return {
2635
+ minStart: Math.min(extent?.minStart ?? identifier.range[0], identifier.range[0]),
2636
+ maxEnd: Math.max(extent?.maxEnd ?? identifier.range[1], identifier.range[1])
2637
+ };
2586
2638
  }
2587
2639
  function publicTypeUse(identifier) {
2588
2640
  let node = identifier;
@@ -3069,6 +3121,7 @@ var CONFIG2 = { path: "tsconfig.json", source: '{"compilerOptions":{"strict":tru
3069
3121
  var EXAMPLE2 = 'export {}; const account = {name: "Ada", email: "a"}; function label(user: {readonly email: string; readonly name: string}): string {return user.name;} label(account);';
3070
3122
  var HELPER_EXAMPLE = 'export {}; const account = {name: "Ada", email: "a"}; function formatName(name: string): string {return name.toUpperCase();} function label(user: {email: string; name: string}): string {return formatName(user.name);} label(account);';
3071
3123
  var NULLABLE_EXAMPLE2 = 'export {}; const account = {name: "Ada", email: null}; function label(user: {email: string | null; name: string}): string {return user.name;} label(account);';
3124
+ var READ_NULLABLE_EXAMPLE = EXAMPLE2.replace('name: "Ada"', "name: null").replace("readonly name: string", "readonly name: string | null").replace("return user.name;", 'return user.name ?? "";');
3072
3125
  var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3073
3126
  defaultLevel: "warning",
3074
3127
  summary: "Prefer the proven read-only subset for a private consumer parameter.",
@@ -3079,7 +3132,7 @@ var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3079
3132
  limitations: [
3080
3133
  "Automatic fixes additionally require every existing identifier caller to be natively assignable to the original contract, without Any, unknown, error or unresolved types on the relevant surface. Comments inside the annotation and TypeScript suppression directives keep the finding manual. Only unread required property signatures are removed; every read or optional member, modifier and terminator token is preserved. Edits that would merge distinct CR and LF terminators remain manual.",
3081
3134
  "Requires parser-owned TypeScript information and a strict external module. Only top-level private function declarations and raw const arrows with exactly one explicit parameter and an independent broad primitive return annotation are supported. Every callable reference must be a direct call with one identifier argument; exports including type-only exports, typeof queries, callbacks, storage, constructors, contextual signatures, overloads, generics, decorators, async functions and direct eval prevent findings.",
3082
- "Only anonymous inline contracts made of broad string, number, boolean or bigint property types are supported. An unread required field may additionally have exactly one such keyword and null or undefined in a direct two-part union; read or optional nullable fields and more complex unions remain excluded. Fields may be plain or readonly; the consumer body must still prove read-only usage. Retained field modifiers and optional members are preserved. Named domain contracts, brands, literal discriminants, classes, methods, callbacks, indexes, mapped types and imported parameter contracts are excluded. At least one read required field and one unread required field must be established; all-optional reduced contracts can reject existing callers through weak-type checks.",
3135
+ "Only anonymous inline contracts made of broad string, number, boolean or bigint property types are supported. A read field or an unread required field may additionally have exactly one such keyword and null or undefined in a direct two-part union; unread optional nullable fields and more complex unions remain excluded. Fields may be plain or readonly; the consumer body must still prove read-only usage. Every read field keeps its exact type, modifiers and optionality. Named domain contracts, brands, literal discriminants, classes, methods, callbacks, indexes, mapped types and imported parameter contracts are excluded. At least one read required field and one unread required field must be established; all-optional reduced contracts can reject existing callers through weak-type checks.",
3083
3136
  "The single return expression may use only fixed scalar field reads, flat destructuring, literals, operators, templates and scalar calls to same-file implemented top-level functions or raw const arrows with an explicit return annotation. These calls require one native resolved declaration, no generic or predicate signature, scalar arguments/result and complete active native checking of the local implementation file. Their helper bodies need not be pure because only scalar values cross the call and only an erased parameter annotation changes. Imported, aliased, ambient, overloaded, optional, spread and member calls remain unsupported. Bound destructured fields count as reads even when their local aliases are unused. Whole-object escape, identity, mutation, closures, reflection, serialization, dynamic keys, implicit Object capabilities and JSX are unsupported. Unknown semantic information causes abstention. Findings outside the fix proof remain manual; this is not a requirement to minimize every object or discard useful producer fields."
3084
3137
  ],
3085
3138
  examples: [
@@ -3088,7 +3141,9 @@ var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3088
3141
  { id: "scalar-helper", scenarioId: "local-scalar-helper", title: "Only the name crosses a local scalar helper call", outcome: "match", files: [{ path: "src/case.ts", source: HELPER_EXAMPLE }, CONFIG2], focusPath: "src/case.ts", expectedCount: 1, public: true, fixedFiles: [{ path: "src/case.ts", source: HELPER_EXAMPLE.replace("email: string;", "") }] },
3089
3142
  { id: "helper-and-other-field", scenarioId: "local-scalar-helper", title: "The consumer still requires a separately read field", outcome: "no-match", files: [{ path: "src/case.ts", source: HELPER_EXAMPLE.replace("return formatName(user.name);", "return formatName(user.name) + user.email;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true },
3090
3143
  { id: "unread-nullable-field", scenarioId: "nullable-private-field", title: "A required nullable field is never inspected", outcome: "match", files: [{ path: "src/case.ts", source: NULLABLE_EXAMPLE2 }, CONFIG2], focusPath: "src/case.ts", expectedCount: 1, public: true, fixedFiles: [{ path: "src/case.ts", source: NULLABLE_EXAMPLE2.replace("email: string | null;", "") }] },
3091
- { id: "read-nullable-field", scenarioId: "nullable-private-field", title: "An inspected nullable field keeps its contract", outcome: "no-match", files: [{ path: "src/case.ts", source: NULLABLE_EXAMPLE2.replace("return user.name;", "return user.email ?? user.name;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true }
3144
+ { id: "read-nullable-field", scenarioId: "nullable-private-field", title: "An inspected nullable field keeps its contract", outcome: "no-match", files: [{ path: "src/case.ts", source: NULLABLE_EXAMPLE2.replace("return user.name;", "return user.email ?? user.name;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true },
3145
+ { id: "retained-nullable-read", scenarioId: "retained-nullable-field", title: "A nullable name remains while an unread email requirement is removed", outcome: "match", files: [{ path: "src/case.ts", source: READ_NULLABLE_EXAMPLE }, CONFIG2], focusPath: "src/case.ts", expectedCount: 1, public: true, fixedFiles: [{ path: "src/case.ts", source: READ_NULLABLE_EXAMPLE.replace("readonly email: string;", "") }] },
3146
+ { id: "retained-nullable-all-used", scenarioId: "retained-nullable-field", title: "Both requirements remain when the nullable view reads email too", outcome: "no-match", files: [{ path: "src/case.ts", source: READ_NULLABLE_EXAMPLE.replace('return user.name ?? "";', "return user.name ?? user.email;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true }
3092
3147
  ]
3093
3148
  };
3094
3149
  function inlineFields(node, reads) {
@@ -3096,7 +3151,7 @@ function inlineFields(node, reads) {
3096
3151
  for (const member3 of node.members) {
3097
3152
  if (!import_typescript8.default.isPropertySignature(member3) || !member3.name || !import_typescript8.default.isIdentifier(member3.name) && !import_typescript8.default.isStringLiteralLike(member3.name) || (member3.modifiers?.length ?? 0) > 1 || member3.modifiers?.some((modifier) => modifier.kind !== import_typescript8.default.SyntaxKind.ReadonlyKeyword)) return null;
3098
3153
  const key = member3.name.text;
3099
- if (!primitiveKeyword(member3.type) && (member3.questionToken || reads.has(key) || !nullishPrimitiveUnion(member3.type))) return null;
3154
+ if (!primitiveKeyword(member3.type) && (!nullishPrimitiveUnion(member3.type) || member3.questionToken && !reads.has(key))) return null;
3100
3155
  if (fields2.has(key)) return null;
3101
3156
  fields2.set(key, !member3.questionToken);
3102
3157
  }
@@ -4209,7 +4264,7 @@ function isSearchParamReceiver(call, services) {
4209
4264
  // package.json
4210
4265
  var package_default = {
4211
4266
  name: "@sarj/eslint-plugin",
4212
- version: "16.38.50",
4267
+ version: "16.38.53",
4213
4268
  packageManager: "npm@12.2.1",
4214
4269
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
4215
4270
  type: "module",
package/dist/index.js CHANGED
@@ -2522,16 +2522,68 @@ function typeBinding(source, identifier) {
2522
2522
  }
2523
2523
  return null;
2524
2524
  }
2525
+ var REFERENCE_INDEXES = /* @__PURE__ */ new WeakMap();
2525
2526
  function retainsNamedTypeUses(source, removed) {
2526
2527
  const manager = source.scopeManager;
2527
2528
  if (manager === null) return false;
2528
- const references = manager.scopes.flatMap((scope) => scope.references).filter((reference) => reference.isRead() && insideTypeRange(reference.identifier, removed));
2529
- return references.every((reference) => {
2529
+ const index = referenceIndex(source, manager);
2530
+ for (const reference of referencesWithin(index.references, removed)) {
2531
+ if (!reference.isRead()) continue;
2530
2532
  const binding3 = reference.resolved;
2531
2533
  if (!binding3 || !reference.isTypeReference && !reference.isValueReference) return false;
2532
- if (binding3.defs.length === 0) return true;
2533
- return binding3.references.some((retained) => retained.isRead() && (!reference.isTypeReference || retained.isTypeReference) && (!reference.isValueReference || retained.isValueReference) && !insideTypeRange(retained.identifier, removed) && !binding3.defs.some((definition) => insideTypeRange(retained.identifier, definition.node)) && publicTypeUse(retained.identifier));
2534
- });
2534
+ if (binding3.defs.length === 0) continue;
2535
+ const reads = publicReadExtents(index, binding3);
2536
+ const extent = reference.isTypeReference ? reference.isValueReference ? reads.both : reads.type : reads.value;
2537
+ if (extent === null || extent.minStart >= removed.range[0] && extent.maxEnd <= removed.range[1]) return false;
2538
+ }
2539
+ return true;
2540
+ }
2541
+ function referenceIndex(source, manager) {
2542
+ const cached = REFERENCE_INDEXES.get(source);
2543
+ if (cached?.manager === manager) return cached;
2544
+ const references = manager.scopes.flatMap((scope) => scope.references);
2545
+ references.sort((left, right) => left.identifier.range[0] - right.identifier.range[0]);
2546
+ const index = { manager, references, publicReads: /* @__PURE__ */ new Map() };
2547
+ REFERENCE_INDEXES.set(source, index);
2548
+ return index;
2549
+ }
2550
+ function* referencesWithin(references, removed) {
2551
+ let low = 0;
2552
+ let high = references.length;
2553
+ while (low < high) {
2554
+ const middle = low + Math.floor((high - low) / 2);
2555
+ const reference = references[middle];
2556
+ if (reference === void 0 || reference.identifier.range[0] >= removed.range[0]) high = middle;
2557
+ else low = middle + 1;
2558
+ }
2559
+ for (let index = low; index < references.length; index++) {
2560
+ const reference = references[index];
2561
+ if (reference === void 0) continue;
2562
+ if (reference.identifier.range[0] > removed.range[1]) break;
2563
+ if (insideTypeRange(reference.identifier, removed)) yield reference;
2564
+ }
2565
+ }
2566
+ function publicReadExtents(index, binding3) {
2567
+ const cached = index.publicReads.get(binding3);
2568
+ if (cached !== void 0) return cached;
2569
+ let type = null;
2570
+ let value = null;
2571
+ let both = null;
2572
+ for (const retained of binding3.references) {
2573
+ if (!retained.isRead() || !retained.isTypeReference && !retained.isValueReference || binding3.defs.some((definition) => insideTypeRange(retained.identifier, definition.node)) || !publicTypeUse(retained.identifier)) continue;
2574
+ if (retained.isTypeReference) type = extendExtent(type, retained.identifier);
2575
+ if (retained.isValueReference) value = extendExtent(value, retained.identifier);
2576
+ if (retained.isTypeReference && retained.isValueReference) both = extendExtent(both, retained.identifier);
2577
+ }
2578
+ const result = { type, value, both };
2579
+ index.publicReads.set(binding3, result);
2580
+ return result;
2581
+ }
2582
+ function extendExtent(extent, identifier) {
2583
+ return {
2584
+ minStart: Math.min(extent?.minStart ?? identifier.range[0], identifier.range[0]),
2585
+ maxEnd: Math.max(extent?.maxEnd ?? identifier.range[1], identifier.range[1])
2586
+ };
2535
2587
  }
2536
2588
  function publicTypeUse(identifier) {
2537
2589
  let node = identifier;
@@ -3018,6 +3070,7 @@ var CONFIG2 = { path: "tsconfig.json", source: '{"compilerOptions":{"strict":tru
3018
3070
  var EXAMPLE2 = 'export {}; const account = {name: "Ada", email: "a"}; function label(user: {readonly email: string; readonly name: string}): string {return user.name;} label(account);';
3019
3071
  var HELPER_EXAMPLE = 'export {}; const account = {name: "Ada", email: "a"}; function formatName(name: string): string {return name.toUpperCase();} function label(user: {email: string; name: string}): string {return formatName(user.name);} label(account);';
3020
3072
  var NULLABLE_EXAMPLE2 = 'export {}; const account = {name: "Ada", email: null}; function label(user: {email: string | null; name: string}): string {return user.name;} label(account);';
3073
+ var READ_NULLABLE_EXAMPLE = EXAMPLE2.replace('name: "Ada"', "name: null").replace("readonly name: string", "readonly name: string | null").replace("return user.name;", 'return user.name ?? "";');
3021
3074
  var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3022
3075
  defaultLevel: "warning",
3023
3076
  summary: "Prefer the proven read-only subset for a private consumer parameter.",
@@ -3028,7 +3081,7 @@ var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3028
3081
  limitations: [
3029
3082
  "Automatic fixes additionally require every existing identifier caller to be natively assignable to the original contract, without Any, unknown, error or unresolved types on the relevant surface. Comments inside the annotation and TypeScript suppression directives keep the finding manual. Only unread required property signatures are removed; every read or optional member, modifier and terminator token is preserved. Edits that would merge distinct CR and LF terminators remain manual.",
3030
3083
  "Requires parser-owned TypeScript information and a strict external module. Only top-level private function declarations and raw const arrows with exactly one explicit parameter and an independent broad primitive return annotation are supported. Every callable reference must be a direct call with one identifier argument; exports including type-only exports, typeof queries, callbacks, storage, constructors, contextual signatures, overloads, generics, decorators, async functions and direct eval prevent findings.",
3031
- "Only anonymous inline contracts made of broad string, number, boolean or bigint property types are supported. An unread required field may additionally have exactly one such keyword and null or undefined in a direct two-part union; read or optional nullable fields and more complex unions remain excluded. Fields may be plain or readonly; the consumer body must still prove read-only usage. Retained field modifiers and optional members are preserved. Named domain contracts, brands, literal discriminants, classes, methods, callbacks, indexes, mapped types and imported parameter contracts are excluded. At least one read required field and one unread required field must be established; all-optional reduced contracts can reject existing callers through weak-type checks.",
3084
+ "Only anonymous inline contracts made of broad string, number, boolean or bigint property types are supported. A read field or an unread required field may additionally have exactly one such keyword and null or undefined in a direct two-part union; unread optional nullable fields and more complex unions remain excluded. Fields may be plain or readonly; the consumer body must still prove read-only usage. Every read field keeps its exact type, modifiers and optionality. Named domain contracts, brands, literal discriminants, classes, methods, callbacks, indexes, mapped types and imported parameter contracts are excluded. At least one read required field and one unread required field must be established; all-optional reduced contracts can reject existing callers through weak-type checks.",
3032
3085
  "The single return expression may use only fixed scalar field reads, flat destructuring, literals, operators, templates and scalar calls to same-file implemented top-level functions or raw const arrows with an explicit return annotation. These calls require one native resolved declaration, no generic or predicate signature, scalar arguments/result and complete active native checking of the local implementation file. Their helper bodies need not be pure because only scalar values cross the call and only an erased parameter annotation changes. Imported, aliased, ambient, overloaded, optional, spread and member calls remain unsupported. Bound destructured fields count as reads even when their local aliases are unused. Whole-object escape, identity, mutation, closures, reflection, serialization, dynamic keys, implicit Object capabilities and JSX are unsupported. Unknown semantic information causes abstention. Findings outside the fix proof remain manual; this is not a requirement to minimize every object or discard useful producer fields."
3033
3086
  ],
3034
3087
  examples: [
@@ -3037,7 +3090,9 @@ var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3037
3090
  { id: "scalar-helper", scenarioId: "local-scalar-helper", title: "Only the name crosses a local scalar helper call", outcome: "match", files: [{ path: "src/case.ts", source: HELPER_EXAMPLE }, CONFIG2], focusPath: "src/case.ts", expectedCount: 1, public: true, fixedFiles: [{ path: "src/case.ts", source: HELPER_EXAMPLE.replace("email: string;", "") }] },
3038
3091
  { id: "helper-and-other-field", scenarioId: "local-scalar-helper", title: "The consumer still requires a separately read field", outcome: "no-match", files: [{ path: "src/case.ts", source: HELPER_EXAMPLE.replace("return formatName(user.name);", "return formatName(user.name) + user.email;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true },
3039
3092
  { id: "unread-nullable-field", scenarioId: "nullable-private-field", title: "A required nullable field is never inspected", outcome: "match", files: [{ path: "src/case.ts", source: NULLABLE_EXAMPLE2 }, CONFIG2], focusPath: "src/case.ts", expectedCount: 1, public: true, fixedFiles: [{ path: "src/case.ts", source: NULLABLE_EXAMPLE2.replace("email: string | null;", "") }] },
3040
- { id: "read-nullable-field", scenarioId: "nullable-private-field", title: "An inspected nullable field keeps its contract", outcome: "no-match", files: [{ path: "src/case.ts", source: NULLABLE_EXAMPLE2.replace("return user.name;", "return user.email ?? user.name;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true }
3093
+ { id: "read-nullable-field", scenarioId: "nullable-private-field", title: "An inspected nullable field keeps its contract", outcome: "no-match", files: [{ path: "src/case.ts", source: NULLABLE_EXAMPLE2.replace("return user.name;", "return user.email ?? user.name;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true },
3094
+ { id: "retained-nullable-read", scenarioId: "retained-nullable-field", title: "A nullable name remains while an unread email requirement is removed", outcome: "match", files: [{ path: "src/case.ts", source: READ_NULLABLE_EXAMPLE }, CONFIG2], focusPath: "src/case.ts", expectedCount: 1, public: true, fixedFiles: [{ path: "src/case.ts", source: READ_NULLABLE_EXAMPLE.replace("readonly email: string;", "") }] },
3095
+ { id: "retained-nullable-all-used", scenarioId: "retained-nullable-field", title: "Both requirements remain when the nullable view reads email too", outcome: "no-match", files: [{ path: "src/case.ts", source: READ_NULLABLE_EXAMPLE.replace('return user.name ?? "";', "return user.name ?? user.email;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true }
3041
3096
  ]
3042
3097
  };
3043
3098
  function inlineFields(node, reads) {
@@ -3045,7 +3100,7 @@ function inlineFields(node, reads) {
3045
3100
  for (const member3 of node.members) {
3046
3101
  if (!ts8.isPropertySignature(member3) || !member3.name || !ts8.isIdentifier(member3.name) && !ts8.isStringLiteralLike(member3.name) || (member3.modifiers?.length ?? 0) > 1 || member3.modifiers?.some((modifier) => modifier.kind !== ts8.SyntaxKind.ReadonlyKeyword)) return null;
3047
3102
  const key = member3.name.text;
3048
- if (!primitiveKeyword(member3.type) && (member3.questionToken || reads.has(key) || !nullishPrimitiveUnion(member3.type))) return null;
3103
+ if (!primitiveKeyword(member3.type) && (!nullishPrimitiveUnion(member3.type) || member3.questionToken && !reads.has(key))) return null;
3049
3104
  if (fields2.has(key)) return null;
3050
3105
  fields2.set(key, !member3.questionToken);
3051
3106
  }
@@ -4162,7 +4217,7 @@ function isSearchParamReceiver(call, services) {
4162
4217
  // package.json
4163
4218
  var package_default = {
4164
4219
  name: "@sarj/eslint-plugin",
4165
- version: "16.38.50",
4220
+ version: "16.38.53",
4166
4221
  packageManager: "npm@12.2.1",
4167
4222
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
4168
4223
  type: "module",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "16.38.50",
3
+ "version": "16.38.53",
4
4
  "packageManager": "npm@12.2.1",
5
5
  "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",