@sarj/eslint-plugin 16.38.53 → 16.38.55

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
@@ -2662,14 +2662,32 @@ function validConsumerFixCalls(node, parameter, sourceCode, services) {
2662
2662
  if (owner.type !== import_utils24.AST_NODE_TYPES.FunctionDeclaration && owner.type !== import_utils24.AST_NODE_TYPES.VariableDeclarator) return false;
2663
2663
  const binding3 = sourceCode.getDeclaredVariables(owner).find((variable) => variable.defs.some((definition) => definition.node === owner && (definition.type === "FunctionName" || definition.type === "Variable")));
2664
2664
  if (!binding3) return false;
2665
+ const declaration = services.esTreeNodeToTSNodeMap.get(node);
2666
+ if (!import_typescript3.default.isFunctionDeclaration(declaration) && !import_typescript3.default.isArrowFunction(declaration) || parameter.parent !== declaration || declaration.parameters.some((entry) => import_typescript3.default.isIdentifier(entry.name) && entry.name.text === "this")) return false;
2667
+ const selected = declaration.parameters.indexOf(parameter);
2668
+ if (selected < 0) return false;
2665
2669
  const checker = services.program.getTypeChecker();
2666
- const original = checker.getTypeAtLocation(parameter);
2670
+ const originals = declaration.parameters.map((entry) => checker.getTypeAtLocation(entry));
2671
+ const original = originals[selected];
2672
+ if (!original) return false;
2667
2673
  const fields2 = /* @__PURE__ */ new Set();
2668
2674
  const collect = (type) => {
2669
2675
  if (type.isUnionOrIntersection()) type.types.forEach(collect);
2670
2676
  else type.getProperties().forEach((property) => fields2.add(property.name));
2671
2677
  };
2672
2678
  collect(original);
2679
+ const validArguments = (call) => {
2680
+ if (declaration.parameters.length > 1) {
2681
+ const nativeCall = services.esTreeNodeToTSNodeMap.get(call);
2682
+ if (!import_typescript3.default.isCallExpression(nativeCall) || checker.getResolvedSignature(nativeCall)?.declaration !== declaration) return false;
2683
+ }
2684
+ return call.arguments.every((argument, index) => {
2685
+ const target = originals[index];
2686
+ const nativeArgument = services.esTreeNodeToTSNodeMap.get(argument);
2687
+ const type = checker.getTypeAtLocation(nativeArgument);
2688
+ return target !== void 0 && (index === selected ? knownArgument(type, nativeArgument, fields2, checker) : known(type)) && checker.isTypeAssignableTo(type, target);
2689
+ });
2690
+ };
2673
2691
  let calls = 0;
2674
2692
  for (const reference of binding3.references) {
2675
2693
  if (reference.isWrite()) {
@@ -2677,10 +2695,7 @@ function validConsumerFixCalls(node, parameter, sourceCode, services) {
2677
2695
  continue;
2678
2696
  }
2679
2697
  const call = reference.identifier.parent;
2680
- if (reference.isTypeReference || !reference.isReadOnly() || call.type !== import_utils24.AST_NODE_TYPES.CallExpression || call.callee !== reference.identifier || call.optional || call.typeArguments || call.arguments.length !== 1 || call.arguments[0]?.type !== import_utils24.AST_NODE_TYPES.Identifier) return false;
2681
- const argument = services.esTreeNodeToTSNodeMap.get(call.arguments[0]);
2682
- const type = checker.getTypeAtLocation(argument);
2683
- if (!knownArgument(type, argument, fields2, checker) || !checker.isTypeAssignableTo(type, original)) return false;
2698
+ if (reference.isTypeReference || !reference.isReadOnly() || call.type !== import_utils24.AST_NODE_TYPES.CallExpression || call.callee !== reference.identifier || call.optional || call.typeArguments || call.arguments.length !== declaration.parameters.length || !call.arguments.every((argument) => argument.type === import_utils24.AST_NODE_TYPES.Identifier) || !validArguments(call)) return false;
2684
2699
  calls += 1;
2685
2700
  }
2686
2701
  return calls > 0;
@@ -2777,10 +2792,10 @@ var RETURNED_OPERANDS = /* @__PURE__ */ new Set([import_typescript5.default.Synt
2777
2792
  function scalarParameterView(parameter, body3, checker) {
2778
2793
  return parameterReads(parameter, body3, checker, true);
2779
2794
  }
2780
- function scalarParameterReads(parameter, body3, checker, callProgram) {
2781
- return parameterReads(parameter, body3, checker, false, callProgram);
2795
+ function scalarParameterReads(parameter, body3, checker, callProgram, auxiliaries) {
2796
+ return parameterReads(parameter, body3, checker, false, callProgram, auxiliaries);
2782
2797
  }
2783
- function parameterReads(parameter, body3, checker, jsx, callProgram) {
2798
+ function parameterReads(parameter, body3, checker, jsx, callProgram, auxiliaries) {
2784
2799
  if (parameter.dotDotDotToken || parameter.initializer || import_typescript5.default.isArrayBindingPattern(parameter.name)) return null;
2785
2800
  const root = import_typescript5.default.isIdentifier(parameter.name) ? checker.getSymbolAtLocation(parameter.name) : void 0;
2786
2801
  if (import_typescript5.default.isIdentifier(parameter.name) && !root) return null;
@@ -2788,7 +2803,7 @@ function parameterReads(parameter, body3, checker, jsx, callProgram) {
2788
2803
  const aliases = parameterAliases(parameter.name, checker, reads);
2789
2804
  if (!aliases) return null;
2790
2805
  const expression = returnedExpression(body3);
2791
- const view = { checker, props: checker.getTypeAtLocation(parameter), root, aliases, reads, callProgram };
2806
+ const view = { checker, props: checker.getTypeAtLocation(parameter), root, aliases, reads, callProgram, auxiliaries };
2792
2807
  return expression && scalarView(expression, view, jsx) ? reads : null;
2793
2808
  }
2794
2809
  function scalarType(type, allowUndefined = true) {
@@ -2820,6 +2835,7 @@ function scalarView(node, view, jsx = false) {
2820
2835
  if (import_typescript5.default.isJsxExpression(node)) return !node.dotDotDotToken && (!node.expression || scalarView(node.expression, view, jsx));
2821
2836
  if (import_typescript5.default.isIdentifier(node)) {
2822
2837
  const binding3 = view.checker.getSymbolAtLocation(node);
2838
+ if (binding3 && view.auxiliaries?.has(binding3)) return scalarType(view.checker.getTypeAtLocation(node));
2823
2839
  const key = binding3 && view.aliases.get(binding3);
2824
2840
  return key !== void 0 && readField(node, key, view);
2825
2841
  }
@@ -2890,8 +2906,19 @@ function primitiveKeyword(node) {
2890
2906
  function nullishPrimitiveUnion(node) {
2891
2907
  return node !== void 0 && import_typescript6.default.isUnionTypeNode(node) && node.types.length === 2 && node.types.some(primitiveKeyword) && node.types.some((part) => part.kind === import_typescript6.default.SyntaxKind.UndefinedKeyword || import_typescript6.default.isLiteralTypeNode(part) && part.literal.kind === import_typescript6.default.SyntaxKind.NullKeyword);
2892
2908
  }
2893
- function consumerAnnotation(node) {
2894
- if (node.params.length !== 1) return void 0;
2909
+ function consumerAnnotation(node, auxiliaryScalars = false) {
2910
+ if (node.params.length !== 1) {
2911
+ if (!auxiliaryScalars) return void 0;
2912
+ let annotation;
2913
+ for (const parameter2 of node.params) {
2914
+ if (parameter2.type !== import_utils25.AST_NODE_TYPES.Identifier && parameter2.type !== import_utils25.AST_NODE_TYPES.ObjectPattern) continue;
2915
+ const current = parameter2.typeAnnotation?.typeAnnotation;
2916
+ if (current?.type !== import_utils25.AST_NODE_TYPES.TSTypeLiteral) continue;
2917
+ if (annotation) return void 0;
2918
+ annotation = current;
2919
+ }
2920
+ return annotation;
2921
+ }
2895
2922
  const parameter = node.params[0];
2896
2923
  return parameter?.type === import_utils25.AST_NODE_TYPES.Identifier || parameter?.type === import_utils25.AST_NODE_TYPES.ObjectPattern ? parameter.typeAnnotation?.typeAnnotation : void 0;
2897
2924
  }
@@ -2899,29 +2926,48 @@ function exportedSymbols(source, checker) {
2899
2926
  const symbol = checker.getSymbolAtLocation(source);
2900
2927
  return new Set(symbol ? checker.getExportsOfModule(symbol).map((entry) => (entry.flags & import_typescript6.default.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(entry) : entry) : []);
2901
2928
  }
2902
- function privateScalarConsumer(node, sourceCode, services, exports2, scalarCalls = false) {
2929
+ function privateScalarConsumer(node, sourceCode, services, exports2, options = {}) {
2903
2930
  const native = services.esTreeNodeToTSNodeMap.get(node);
2904
- if (!import_typescript6.default.isFunctionDeclaration(native) && !import_typescript6.default.isArrowFunction(native) || !native.body || !independentSignature(native)) return null;
2931
+ if (!import_typescript6.default.isFunctionDeclaration(native) && !import_typescript6.default.isArrowFunction(native) || !native.body) return null;
2932
+ let parameter = native.parameters[0];
2933
+ if (native.parameters.length !== 1) {
2934
+ const annotation = consumerAnnotation(node, options.auxiliaryScalars);
2935
+ const selected = annotation && services.esTreeNodeToTSNodeMap.get(annotation);
2936
+ parameter = native.parameters.find((entry) => entry.type === selected);
2937
+ }
2938
+ if (!parameter || !independentSignature(native, parameter, options.auxiliaryScalars === true)) return null;
2905
2939
  const checker = services.program.getTypeChecker();
2906
2940
  if (!import_typescript6.default.isExternalModule(native.getSourceFile()) || !services.program.getCompilerOptions().strict) return null;
2907
- const parameter = native.parameters[0];
2908
- if (!parameter) return null;
2909
- const binding3 = callableBinding(node, sourceCode);
2910
- if (!binding3 || binding3.defs.length !== 1) return null;
2911
- const name = import_typescript6.default.isFunctionDeclaration(native) ? native.name : import_typescript6.default.isVariableDeclaration(native.parent) ? native.parent.name : void 0;
2912
- const symbol = name && checker.getSymbolAtLocation(name);
2913
- if (!symbol || symbol.declarations?.length !== 1 || exports2.has(symbol) || !directCalls(binding3)) return null;
2941
+ if (!privateDirectCallable(node, native, sourceCode, checker, exports2)) return null;
2914
2942
  const signature = checker.getSignatureFromDeclaration(native);
2915
2943
  if (!signature || signature.typeParameters?.length || checker.getTypePredicateOfSignature(signature)) return null;
2916
2944
  const expression = returnedExpression(native.body);
2917
2945
  if (!expression || !checker.isTypeAssignableTo(checker.getTypeAtLocation(expression), checker.getReturnTypeOfSignature(signature))) return null;
2918
- const reads = scalarParameterReads(parameter, native.body, checker, scalarCalls ? services.program : void 0);
2946
+ const auxiliaries = native.parameters.length > 1 ? auxiliaryBindings(native, parameter, checker) : void 0;
2947
+ if (auxiliaries === null) return null;
2948
+ const reads = scalarParameterReads(parameter, native.body, checker, options.scalarCalls ? services.program : void 0, auxiliaries);
2919
2949
  return reads?.size ? { parameter, reads } : null;
2920
2950
  }
2921
- function independentSignature(native) {
2922
- if (native.parameters.length !== 1 || native.typeParameters?.length || native.asteriskToken || native.modifiers?.length || !primitiveKeyword(native.type)) return false;
2923
- const parameter = native.parameters[0];
2924
- return parameter !== void 0 && parameter.type !== void 0 && !parameter.modifiers?.length && !parameter.questionToken && !parameter.initializer && !parameter.dotDotDotToken && (!import_typescript6.default.isIdentifier(parameter.name) || parameter.name.text !== "this");
2951
+ function privateDirectCallable(node, native, sourceCode, checker, exports2) {
2952
+ const binding3 = callableBinding(node, sourceCode);
2953
+ if (!binding3 || binding3.defs.length !== 1) return false;
2954
+ const name = import_typescript6.default.isFunctionDeclaration(native) ? native.name : import_typescript6.default.isVariableDeclaration(native.parent) ? native.parent.name : void 0;
2955
+ const symbol = name && checker.getSymbolAtLocation(name);
2956
+ return symbol !== void 0 && symbol.declarations?.length === 1 && !exports2.has(symbol) && directCalls(binding3, native.parameters.length);
2957
+ }
2958
+ function independentSignature(native, selected, auxiliaryScalars) {
2959
+ if (!auxiliaryScalars && native.parameters.length !== 1 || native.typeParameters?.length || native.asteriskToken || native.modifiers?.length || !primitiveKeyword(native.type)) return false;
2960
+ return native.parameters.every((parameter) => parameter.type !== void 0 && !parameter.modifiers?.length && !parameter.questionToken && !parameter.initializer && !parameter.dotDotDotToken && (!import_typescript6.default.isIdentifier(parameter.name) || parameter.name.text !== "this") && (parameter === selected || import_typescript6.default.isIdentifier(parameter.name) && primitiveKeyword(parameter.type)));
2961
+ }
2962
+ function auxiliaryBindings(native, selected, checker) {
2963
+ const bindings = /* @__PURE__ */ new Set();
2964
+ for (const parameter of native.parameters) {
2965
+ if (parameter === selected) continue;
2966
+ const symbol = import_typescript6.default.isIdentifier(parameter.name) ? checker.getSymbolAtLocation(parameter.name) : void 0;
2967
+ if (!symbol || symbol.declarations?.length !== 1 || symbol.declarations[0] !== parameter || bindings.has(symbol)) return null;
2968
+ bindings.add(symbol);
2969
+ }
2970
+ return bindings;
2925
2971
  }
2926
2972
  function callableBinding(node, sourceCode) {
2927
2973
  if (node.type === import_utils25.AST_NODE_TYPES.FunctionDeclaration) {
@@ -2932,12 +2978,12 @@ function callableBinding(node, sourceCode) {
2932
2978
  if (declaration.type !== import_utils25.AST_NODE_TYPES.VariableDeclarator || declaration.init !== node || declaration.id.type !== import_utils25.AST_NODE_TYPES.Identifier || declaration.id.typeAnnotation || declaration.parent.kind !== "const" || declaration.parent.parent.type !== import_utils25.AST_NODE_TYPES.Program) return void 0;
2933
2979
  return sourceCode.getDeclaredVariables(declaration).find((variable) => variable.defs.some((definition) => definition.type === "Variable" && definition.node === declaration));
2934
2980
  }
2935
- function directCalls(binding3) {
2981
+ function directCalls(binding3, arity) {
2936
2982
  let calls = 0;
2937
2983
  const stable2 = stableValueBinding(binding3, (reference) => {
2938
2984
  const identifier = reference.identifier;
2939
2985
  const call = identifier.parent;
2940
- if (reference.isTypeReference || !reference.isReadOnly() || call.type !== import_utils25.AST_NODE_TYPES.CallExpression || call.callee !== identifier || call.optional || call.typeArguments || call.arguments.length !== 1 || call.arguments[0]?.type !== import_utils25.AST_NODE_TYPES.Identifier) return false;
2986
+ if (reference.isTypeReference || !reference.isReadOnly() || call.type !== import_utils25.AST_NODE_TYPES.CallExpression || call.callee !== identifier || call.optional || call.typeArguments || call.arguments.length !== arity || !call.arguments.every((argument) => argument.type === import_utils25.AST_NODE_TYPES.Identifier)) return false;
2941
2987
  calls += 1;
2942
2988
  return true;
2943
2989
  });
@@ -3122,6 +3168,7 @@ var EXAMPLE2 = 'export {}; const account = {name: "Ada", email: "a"}; function l
3122
3168
  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);';
3123
3169
  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
3170
  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 ?? "";');
3171
+ var AUXILIARY_EXAMPLE = EXAMPLE2.replaceAll("readonly ", "").replace("function label(user:", 'const prefix = "Hello "; function label(prefix: string, user:').replace("return user.name;", "return prefix + user.name;").replace("label(account);", "label(prefix, account);");
3125
3172
  var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3126
3173
  defaultLevel: "warning",
3127
3174
  summary: "Prefer the proven read-only subset for a private consumer parameter.",
@@ -3130,8 +3177,8 @@ var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3130
3177
  category: "architecture",
3131
3178
  autofix: "safe",
3132
3179
  limitations: [
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.",
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.",
3180
+ "Automatic fixes additionally require every existing identifier argument to be natively assignable to its corresponding original parameter 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 object property signatures are removed; every read or optional member, modifier and terminator token is preserved. Auxiliary annotations and argument order stay unchanged. Edits that would merge distinct CR and LF terminators remain manual.",
3181
+ "Requires parser-owned TypeScript information and a strict external module. Only top-level private function declarations and raw const arrows with one explicit inline object parameter and an independent broad primitive return annotation are supported. Other parameters must be required identifiers annotated directly as string, number, boolean or bigint; optional, default, rest, destructured, domain-specific and explicit this auxiliaries are excluded. Every callable reference must be a direct call with an identifier argument for every parameter; exports including type-only exports, typeof queries, callbacks, storage, constructors, contextual signatures, overloads, generics, decorators, async functions and direct eval prevent findings.",
3135
3182
  "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.",
3136
3183
  "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."
3137
3184
  ],
@@ -3143,7 +3190,9 @@ var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3143
3190
  { 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;", "") }] },
3144
3191
  { 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
3192
  { 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 }
3193
+ { 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 },
3194
+ { id: "auxiliary-scalar", scenarioId: "auxiliary-scalar-parameter", title: "A scalar prefix does not require the object's unread email", outcome: "match", files: [{ path: "src/case.ts", source: AUXILIARY_EXAMPLE }, CONFIG2], focusPath: "src/case.ts", expectedCount: 1, public: true, fixedFiles: [{ path: "src/case.ts", source: AUXILIARY_EXAMPLE.replace("email: string;", "") }] },
3195
+ { id: "auxiliary-all-used", scenarioId: "auxiliary-scalar-parameter", title: "Reading both object fields retains their requirements", outcome: "no-match", files: [{ path: "src/case.ts", source: AUXILIARY_EXAMPLE.replace("return prefix + user.name;", "return prefix + user.name + user.email;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true }
3147
3196
  ]
3148
3197
  };
3149
3198
  function inlineFields(node, reads) {
@@ -3169,7 +3218,7 @@ var prefer_minimal_parameter_contract_default = createRule({
3169
3218
  const candidates = [];
3170
3219
  let dynamic = false;
3171
3220
  const remember = (node) => {
3172
- if (consumerAnnotation(node)?.type === import_utils27.AST_NODE_TYPES.TSTypeLiteral) candidates.push(node);
3221
+ if (consumerAnnotation(node, true)?.type === import_utils27.AST_NODE_TYPES.TSTypeLiteral) candidates.push(node);
3173
3222
  };
3174
3223
  return {
3175
3224
  FunctionDeclaration: remember,
@@ -3185,7 +3234,7 @@ var prefer_minimal_parameter_contract_default = createRule({
3185
3234
  const checker = services.program.getTypeChecker();
3186
3235
  const exports2 = exportedSymbols(source, checker);
3187
3236
  for (const candidate2 of candidates) {
3188
- const consumer = privateScalarConsumer(candidate2, context.sourceCode, services, exports2, true);
3237
+ const consumer = privateScalarConsumer(candidate2, context.sourceCode, services, exports2, { scalarCalls: true, auxiliaryScalars: true });
3189
3238
  const annotation = consumer?.parameter.type;
3190
3239
  if (!consumer || !annotation || !import_typescript8.default.isTypeLiteralNode(annotation)) continue;
3191
3240
  const fields2 = inlineFields(annotation, consumer.reads);
@@ -3646,7 +3695,7 @@ function primitiveResult(value, proof, active) {
3646
3695
  return initializer !== void 0 && initializer.getSourceFile() === value.getSourceFile() && primitiveValue(initializer, proof, active);
3647
3696
  }
3648
3697
  function primitiveMember(node, proof, active) {
3649
- if (!proof.staticScope || !import_typescript10.default.isIdentifier(node.expression)) return false;
3698
+ if (!import_typescript10.default.isIdentifier(node.expression)) return false;
3650
3699
  const key = import_typescript10.default.isPropertyAccessExpression(node) ? node.name.text : node.argumentExpression && (import_typescript10.default.isStringLiteralLike(node.argumentExpression) || import_typescript10.default.isNumericLiteral(node.argumentExpression)) ? node.argumentExpression.text : void 0;
3651
3700
  const symbol = proof.checker.getSymbolAtLocation(node.expression);
3652
3701
  if (!symbol || key === void 0 || (symbol.flags & import_typescript10.default.SymbolFlags.Alias) !== 0) return false;
@@ -3692,7 +3741,7 @@ function privateElementDeclaration(opening) {
3692
3741
  return declaration.type === import_utils28.AST_NODE_TYPES.VariableDeclarator && declaration.init === value && declaration.id.type === import_utils28.AST_NODE_TYPES.Identifier && declaration.parent.kind === "const" && declaration.parent.parent.type !== import_utils28.AST_NODE_TYPES.ExportNamedDeclaration ? declaration : null;
3693
3742
  }
3694
3743
  function closedElementValue(opening, declaration, fields2, reads, proof) {
3695
- if (!proof.staticScope || !closedInvocation(opening, proof) || !constInitializer(proof.services.esTreeNodeToTSNodeMap.get(declaration))) return false;
3744
+ if (!closedInvocation(opening, proof) || !constInitializer(proof.services.esTreeNodeToTSNodeMap.get(declaration))) return false;
3696
3745
  const binding3 = proof.sourceCode.getDeclaredVariables(declaration).find((variable) => variable.defs.some((definition) => definition.type === "Variable" && definition.node === declaration));
3697
3746
  const symbol = proof.checker.getSymbolAtLocation(proof.services.esTreeNodeToTSNodeMap.get(declaration.id));
3698
3747
  if (!symbol || proof.exports.has(symbol)) return false;
@@ -3783,7 +3832,6 @@ var no_excess_jsx_props_default = createRule({
3783
3832
  checker,
3784
3833
  services,
3785
3834
  sourceCode: context.sourceCode,
3786
- staticScope: !dynamic,
3787
3835
  exports: exportedSymbols(services.esTreeNodeToTSNodeMap.get(context.sourceCode.ast), checker),
3788
3836
  values: /* @__PURE__ */ new WeakSet(),
3789
3837
  objects: /* @__PURE__ */ new WeakMap(),
@@ -3809,7 +3857,7 @@ var no_excess_jsx_props_default = createRule({
3809
3857
  if (directEval(node)) dynamic = true;
3810
3858
  },
3811
3859
  "Program:exit"() {
3812
- if (!candidates.length) return;
3860
+ if (!candidates.length || dynamic) return;
3813
3861
  services = import_utils28.ESLintUtils.getParserServices(context);
3814
3862
  if (!currentNativeSource(context.sourceCode, services)) return;
3815
3863
  for (const { node, declaration, fields: fields2 } of candidates) evaluate(node, declaration, fields2);
@@ -4264,7 +4312,7 @@ function isSearchParamReceiver(call, services) {
4264
4312
  // package.json
4265
4313
  var package_default = {
4266
4314
  name: "@sarj/eslint-plugin",
4267
- version: "16.38.53",
4315
+ version: "16.38.55",
4268
4316
  packageManager: "npm@12.2.1",
4269
4317
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
4270
4318
  type: "module",
package/dist/index.js CHANGED
@@ -2611,14 +2611,32 @@ function validConsumerFixCalls(node, parameter, sourceCode, services) {
2611
2611
  if (owner.type !== AST_NODE_TYPES22.FunctionDeclaration && owner.type !== AST_NODE_TYPES22.VariableDeclarator) return false;
2612
2612
  const binding3 = sourceCode.getDeclaredVariables(owner).find((variable) => variable.defs.some((definition) => definition.node === owner && (definition.type === "FunctionName" || definition.type === "Variable")));
2613
2613
  if (!binding3) return false;
2614
+ const declaration = services.esTreeNodeToTSNodeMap.get(node);
2615
+ if (!ts3.isFunctionDeclaration(declaration) && !ts3.isArrowFunction(declaration) || parameter.parent !== declaration || declaration.parameters.some((entry) => ts3.isIdentifier(entry.name) && entry.name.text === "this")) return false;
2616
+ const selected = declaration.parameters.indexOf(parameter);
2617
+ if (selected < 0) return false;
2614
2618
  const checker = services.program.getTypeChecker();
2615
- const original = checker.getTypeAtLocation(parameter);
2619
+ const originals = declaration.parameters.map((entry) => checker.getTypeAtLocation(entry));
2620
+ const original = originals[selected];
2621
+ if (!original) return false;
2616
2622
  const fields2 = /* @__PURE__ */ new Set();
2617
2623
  const collect = (type) => {
2618
2624
  if (type.isUnionOrIntersection()) type.types.forEach(collect);
2619
2625
  else type.getProperties().forEach((property) => fields2.add(property.name));
2620
2626
  };
2621
2627
  collect(original);
2628
+ const validArguments = (call) => {
2629
+ if (declaration.parameters.length > 1) {
2630
+ const nativeCall = services.esTreeNodeToTSNodeMap.get(call);
2631
+ if (!ts3.isCallExpression(nativeCall) || checker.getResolvedSignature(nativeCall)?.declaration !== declaration) return false;
2632
+ }
2633
+ return call.arguments.every((argument, index) => {
2634
+ const target = originals[index];
2635
+ const nativeArgument = services.esTreeNodeToTSNodeMap.get(argument);
2636
+ const type = checker.getTypeAtLocation(nativeArgument);
2637
+ return target !== void 0 && (index === selected ? knownArgument(type, nativeArgument, fields2, checker) : known(type)) && checker.isTypeAssignableTo(type, target);
2638
+ });
2639
+ };
2622
2640
  let calls = 0;
2623
2641
  for (const reference of binding3.references) {
2624
2642
  if (reference.isWrite()) {
@@ -2626,10 +2644,7 @@ function validConsumerFixCalls(node, parameter, sourceCode, services) {
2626
2644
  continue;
2627
2645
  }
2628
2646
  const call = reference.identifier.parent;
2629
- if (reference.isTypeReference || !reference.isReadOnly() || call.type !== AST_NODE_TYPES22.CallExpression || call.callee !== reference.identifier || call.optional || call.typeArguments || call.arguments.length !== 1 || call.arguments[0]?.type !== AST_NODE_TYPES22.Identifier) return false;
2630
- const argument = services.esTreeNodeToTSNodeMap.get(call.arguments[0]);
2631
- const type = checker.getTypeAtLocation(argument);
2632
- if (!knownArgument(type, argument, fields2, checker) || !checker.isTypeAssignableTo(type, original)) return false;
2647
+ if (reference.isTypeReference || !reference.isReadOnly() || call.type !== AST_NODE_TYPES22.CallExpression || call.callee !== reference.identifier || call.optional || call.typeArguments || call.arguments.length !== declaration.parameters.length || !call.arguments.every((argument) => argument.type === AST_NODE_TYPES22.Identifier) || !validArguments(call)) return false;
2633
2648
  calls += 1;
2634
2649
  }
2635
2650
  return calls > 0;
@@ -2726,10 +2741,10 @@ var RETURNED_OPERANDS = /* @__PURE__ */ new Set([ts5.SyntaxKind.AmpersandAmpersa
2726
2741
  function scalarParameterView(parameter, body3, checker) {
2727
2742
  return parameterReads(parameter, body3, checker, true);
2728
2743
  }
2729
- function scalarParameterReads(parameter, body3, checker, callProgram) {
2730
- return parameterReads(parameter, body3, checker, false, callProgram);
2744
+ function scalarParameterReads(parameter, body3, checker, callProgram, auxiliaries) {
2745
+ return parameterReads(parameter, body3, checker, false, callProgram, auxiliaries);
2731
2746
  }
2732
- function parameterReads(parameter, body3, checker, jsx, callProgram) {
2747
+ function parameterReads(parameter, body3, checker, jsx, callProgram, auxiliaries) {
2733
2748
  if (parameter.dotDotDotToken || parameter.initializer || ts5.isArrayBindingPattern(parameter.name)) return null;
2734
2749
  const root = ts5.isIdentifier(parameter.name) ? checker.getSymbolAtLocation(parameter.name) : void 0;
2735
2750
  if (ts5.isIdentifier(parameter.name) && !root) return null;
@@ -2737,7 +2752,7 @@ function parameterReads(parameter, body3, checker, jsx, callProgram) {
2737
2752
  const aliases = parameterAliases(parameter.name, checker, reads);
2738
2753
  if (!aliases) return null;
2739
2754
  const expression = returnedExpression(body3);
2740
- const view = { checker, props: checker.getTypeAtLocation(parameter), root, aliases, reads, callProgram };
2755
+ const view = { checker, props: checker.getTypeAtLocation(parameter), root, aliases, reads, callProgram, auxiliaries };
2741
2756
  return expression && scalarView(expression, view, jsx) ? reads : null;
2742
2757
  }
2743
2758
  function scalarType(type, allowUndefined = true) {
@@ -2769,6 +2784,7 @@ function scalarView(node, view, jsx = false) {
2769
2784
  if (ts5.isJsxExpression(node)) return !node.dotDotDotToken && (!node.expression || scalarView(node.expression, view, jsx));
2770
2785
  if (ts5.isIdentifier(node)) {
2771
2786
  const binding3 = view.checker.getSymbolAtLocation(node);
2787
+ if (binding3 && view.auxiliaries?.has(binding3)) return scalarType(view.checker.getTypeAtLocation(node));
2772
2788
  const key = binding3 && view.aliases.get(binding3);
2773
2789
  return key !== void 0 && readField(node, key, view);
2774
2790
  }
@@ -2839,8 +2855,19 @@ function primitiveKeyword(node) {
2839
2855
  function nullishPrimitiveUnion(node) {
2840
2856
  return node !== void 0 && ts6.isUnionTypeNode(node) && node.types.length === 2 && node.types.some(primitiveKeyword) && node.types.some((part) => part.kind === ts6.SyntaxKind.UndefinedKeyword || ts6.isLiteralTypeNode(part) && part.literal.kind === ts6.SyntaxKind.NullKeyword);
2841
2857
  }
2842
- function consumerAnnotation(node) {
2843
- if (node.params.length !== 1) return void 0;
2858
+ function consumerAnnotation(node, auxiliaryScalars = false) {
2859
+ if (node.params.length !== 1) {
2860
+ if (!auxiliaryScalars) return void 0;
2861
+ let annotation;
2862
+ for (const parameter2 of node.params) {
2863
+ if (parameter2.type !== AST_NODE_TYPES23.Identifier && parameter2.type !== AST_NODE_TYPES23.ObjectPattern) continue;
2864
+ const current = parameter2.typeAnnotation?.typeAnnotation;
2865
+ if (current?.type !== AST_NODE_TYPES23.TSTypeLiteral) continue;
2866
+ if (annotation) return void 0;
2867
+ annotation = current;
2868
+ }
2869
+ return annotation;
2870
+ }
2844
2871
  const parameter = node.params[0];
2845
2872
  return parameter?.type === AST_NODE_TYPES23.Identifier || parameter?.type === AST_NODE_TYPES23.ObjectPattern ? parameter.typeAnnotation?.typeAnnotation : void 0;
2846
2873
  }
@@ -2848,29 +2875,48 @@ function exportedSymbols(source, checker) {
2848
2875
  const symbol = checker.getSymbolAtLocation(source);
2849
2876
  return new Set(symbol ? checker.getExportsOfModule(symbol).map((entry) => (entry.flags & ts6.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(entry) : entry) : []);
2850
2877
  }
2851
- function privateScalarConsumer(node, sourceCode, services, exports, scalarCalls = false) {
2878
+ function privateScalarConsumer(node, sourceCode, services, exports, options = {}) {
2852
2879
  const native = services.esTreeNodeToTSNodeMap.get(node);
2853
- if (!ts6.isFunctionDeclaration(native) && !ts6.isArrowFunction(native) || !native.body || !independentSignature(native)) return null;
2880
+ if (!ts6.isFunctionDeclaration(native) && !ts6.isArrowFunction(native) || !native.body) return null;
2881
+ let parameter = native.parameters[0];
2882
+ if (native.parameters.length !== 1) {
2883
+ const annotation = consumerAnnotation(node, options.auxiliaryScalars);
2884
+ const selected = annotation && services.esTreeNodeToTSNodeMap.get(annotation);
2885
+ parameter = native.parameters.find((entry) => entry.type === selected);
2886
+ }
2887
+ if (!parameter || !independentSignature(native, parameter, options.auxiliaryScalars === true)) return null;
2854
2888
  const checker = services.program.getTypeChecker();
2855
2889
  if (!ts6.isExternalModule(native.getSourceFile()) || !services.program.getCompilerOptions().strict) return null;
2856
- const parameter = native.parameters[0];
2857
- if (!parameter) return null;
2858
- const binding3 = callableBinding(node, sourceCode);
2859
- if (!binding3 || binding3.defs.length !== 1) return null;
2860
- const name = ts6.isFunctionDeclaration(native) ? native.name : ts6.isVariableDeclaration(native.parent) ? native.parent.name : void 0;
2861
- const symbol = name && checker.getSymbolAtLocation(name);
2862
- if (!symbol || symbol.declarations?.length !== 1 || exports.has(symbol) || !directCalls(binding3)) return null;
2890
+ if (!privateDirectCallable(node, native, sourceCode, checker, exports)) return null;
2863
2891
  const signature = checker.getSignatureFromDeclaration(native);
2864
2892
  if (!signature || signature.typeParameters?.length || checker.getTypePredicateOfSignature(signature)) return null;
2865
2893
  const expression = returnedExpression(native.body);
2866
2894
  if (!expression || !checker.isTypeAssignableTo(checker.getTypeAtLocation(expression), checker.getReturnTypeOfSignature(signature))) return null;
2867
- const reads = scalarParameterReads(parameter, native.body, checker, scalarCalls ? services.program : void 0);
2895
+ const auxiliaries = native.parameters.length > 1 ? auxiliaryBindings(native, parameter, checker) : void 0;
2896
+ if (auxiliaries === null) return null;
2897
+ const reads = scalarParameterReads(parameter, native.body, checker, options.scalarCalls ? services.program : void 0, auxiliaries);
2868
2898
  return reads?.size ? { parameter, reads } : null;
2869
2899
  }
2870
- function independentSignature(native) {
2871
- if (native.parameters.length !== 1 || native.typeParameters?.length || native.asteriskToken || native.modifiers?.length || !primitiveKeyword(native.type)) return false;
2872
- const parameter = native.parameters[0];
2873
- return parameter !== void 0 && parameter.type !== void 0 && !parameter.modifiers?.length && !parameter.questionToken && !parameter.initializer && !parameter.dotDotDotToken && (!ts6.isIdentifier(parameter.name) || parameter.name.text !== "this");
2900
+ function privateDirectCallable(node, native, sourceCode, checker, exports) {
2901
+ const binding3 = callableBinding(node, sourceCode);
2902
+ if (!binding3 || binding3.defs.length !== 1) return false;
2903
+ const name = ts6.isFunctionDeclaration(native) ? native.name : ts6.isVariableDeclaration(native.parent) ? native.parent.name : void 0;
2904
+ const symbol = name && checker.getSymbolAtLocation(name);
2905
+ return symbol !== void 0 && symbol.declarations?.length === 1 && !exports.has(symbol) && directCalls(binding3, native.parameters.length);
2906
+ }
2907
+ function independentSignature(native, selected, auxiliaryScalars) {
2908
+ if (!auxiliaryScalars && native.parameters.length !== 1 || native.typeParameters?.length || native.asteriskToken || native.modifiers?.length || !primitiveKeyword(native.type)) return false;
2909
+ return native.parameters.every((parameter) => parameter.type !== void 0 && !parameter.modifiers?.length && !parameter.questionToken && !parameter.initializer && !parameter.dotDotDotToken && (!ts6.isIdentifier(parameter.name) || parameter.name.text !== "this") && (parameter === selected || ts6.isIdentifier(parameter.name) && primitiveKeyword(parameter.type)));
2910
+ }
2911
+ function auxiliaryBindings(native, selected, checker) {
2912
+ const bindings = /* @__PURE__ */ new Set();
2913
+ for (const parameter of native.parameters) {
2914
+ if (parameter === selected) continue;
2915
+ const symbol = ts6.isIdentifier(parameter.name) ? checker.getSymbolAtLocation(parameter.name) : void 0;
2916
+ if (!symbol || symbol.declarations?.length !== 1 || symbol.declarations[0] !== parameter || bindings.has(symbol)) return null;
2917
+ bindings.add(symbol);
2918
+ }
2919
+ return bindings;
2874
2920
  }
2875
2921
  function callableBinding(node, sourceCode) {
2876
2922
  if (node.type === AST_NODE_TYPES23.FunctionDeclaration) {
@@ -2881,12 +2927,12 @@ function callableBinding(node, sourceCode) {
2881
2927
  if (declaration.type !== AST_NODE_TYPES23.VariableDeclarator || declaration.init !== node || declaration.id.type !== AST_NODE_TYPES23.Identifier || declaration.id.typeAnnotation || declaration.parent.kind !== "const" || declaration.parent.parent.type !== AST_NODE_TYPES23.Program) return void 0;
2882
2928
  return sourceCode.getDeclaredVariables(declaration).find((variable) => variable.defs.some((definition) => definition.type === "Variable" && definition.node === declaration));
2883
2929
  }
2884
- function directCalls(binding3) {
2930
+ function directCalls(binding3, arity) {
2885
2931
  let calls = 0;
2886
2932
  const stable2 = stableValueBinding(binding3, (reference) => {
2887
2933
  const identifier = reference.identifier;
2888
2934
  const call = identifier.parent;
2889
- if (reference.isTypeReference || !reference.isReadOnly() || call.type !== AST_NODE_TYPES23.CallExpression || call.callee !== identifier || call.optional || call.typeArguments || call.arguments.length !== 1 || call.arguments[0]?.type !== AST_NODE_TYPES23.Identifier) return false;
2935
+ if (reference.isTypeReference || !reference.isReadOnly() || call.type !== AST_NODE_TYPES23.CallExpression || call.callee !== identifier || call.optional || call.typeArguments || call.arguments.length !== arity || !call.arguments.every((argument) => argument.type === AST_NODE_TYPES23.Identifier)) return false;
2890
2936
  calls += 1;
2891
2937
  return true;
2892
2938
  });
@@ -3071,6 +3117,7 @@ var EXAMPLE2 = 'export {}; const account = {name: "Ada", email: "a"}; function l
3071
3117
  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);';
3072
3118
  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
3119
  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 ?? "";');
3120
+ var AUXILIARY_EXAMPLE = EXAMPLE2.replaceAll("readonly ", "").replace("function label(user:", 'const prefix = "Hello "; function label(prefix: string, user:').replace("return user.name;", "return prefix + user.name;").replace("label(account);", "label(prefix, account);");
3074
3121
  var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3075
3122
  defaultLevel: "warning",
3076
3123
  summary: "Prefer the proven read-only subset for a private consumer parameter.",
@@ -3079,8 +3126,8 @@ var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3079
3126
  category: "architecture",
3080
3127
  autofix: "safe",
3081
3128
  limitations: [
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.",
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.",
3129
+ "Automatic fixes additionally require every existing identifier argument to be natively assignable to its corresponding original parameter 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 object property signatures are removed; every read or optional member, modifier and terminator token is preserved. Auxiliary annotations and argument order stay unchanged. Edits that would merge distinct CR and LF terminators remain manual.",
3130
+ "Requires parser-owned TypeScript information and a strict external module. Only top-level private function declarations and raw const arrows with one explicit inline object parameter and an independent broad primitive return annotation are supported. Other parameters must be required identifiers annotated directly as string, number, boolean or bigint; optional, default, rest, destructured, domain-specific and explicit this auxiliaries are excluded. Every callable reference must be a direct call with an identifier argument for every parameter; exports including type-only exports, typeof queries, callbacks, storage, constructors, contextual signatures, overloads, generics, decorators, async functions and direct eval prevent findings.",
3084
3131
  "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.",
3085
3132
  "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."
3086
3133
  ],
@@ -3092,7 +3139,9 @@ var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3092
3139
  { 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;", "") }] },
3093
3140
  { 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
3141
  { 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 }
3142
+ { 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 },
3143
+ { id: "auxiliary-scalar", scenarioId: "auxiliary-scalar-parameter", title: "A scalar prefix does not require the object's unread email", outcome: "match", files: [{ path: "src/case.ts", source: AUXILIARY_EXAMPLE }, CONFIG2], focusPath: "src/case.ts", expectedCount: 1, public: true, fixedFiles: [{ path: "src/case.ts", source: AUXILIARY_EXAMPLE.replace("email: string;", "") }] },
3144
+ { id: "auxiliary-all-used", scenarioId: "auxiliary-scalar-parameter", title: "Reading both object fields retains their requirements", outcome: "no-match", files: [{ path: "src/case.ts", source: AUXILIARY_EXAMPLE.replace("return prefix + user.name;", "return prefix + user.name + user.email;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true }
3096
3145
  ]
3097
3146
  };
3098
3147
  function inlineFields(node, reads) {
@@ -3118,7 +3167,7 @@ var prefer_minimal_parameter_contract_default = createRule({
3118
3167
  const candidates = [];
3119
3168
  let dynamic = false;
3120
3169
  const remember = (node) => {
3121
- if (consumerAnnotation(node)?.type === AST_NODE_TYPES25.TSTypeLiteral) candidates.push(node);
3170
+ if (consumerAnnotation(node, true)?.type === AST_NODE_TYPES25.TSTypeLiteral) candidates.push(node);
3122
3171
  };
3123
3172
  return {
3124
3173
  FunctionDeclaration: remember,
@@ -3134,7 +3183,7 @@ var prefer_minimal_parameter_contract_default = createRule({
3134
3183
  const checker = services.program.getTypeChecker();
3135
3184
  const exports = exportedSymbols(source, checker);
3136
3185
  for (const candidate2 of candidates) {
3137
- const consumer = privateScalarConsumer(candidate2, context.sourceCode, services, exports, true);
3186
+ const consumer = privateScalarConsumer(candidate2, context.sourceCode, services, exports, { scalarCalls: true, auxiliaryScalars: true });
3138
3187
  const annotation = consumer?.parameter.type;
3139
3188
  if (!consumer || !annotation || !ts8.isTypeLiteralNode(annotation)) continue;
3140
3189
  const fields2 = inlineFields(annotation, consumer.reads);
@@ -3595,7 +3644,7 @@ function primitiveResult(value, proof, active) {
3595
3644
  return initializer !== void 0 && initializer.getSourceFile() === value.getSourceFile() && primitiveValue(initializer, proof, active);
3596
3645
  }
3597
3646
  function primitiveMember(node, proof, active) {
3598
- if (!proof.staticScope || !ts10.isIdentifier(node.expression)) return false;
3647
+ if (!ts10.isIdentifier(node.expression)) return false;
3599
3648
  const key = ts10.isPropertyAccessExpression(node) ? node.name.text : node.argumentExpression && (ts10.isStringLiteralLike(node.argumentExpression) || ts10.isNumericLiteral(node.argumentExpression)) ? node.argumentExpression.text : void 0;
3600
3649
  const symbol = proof.checker.getSymbolAtLocation(node.expression);
3601
3650
  if (!symbol || key === void 0 || (symbol.flags & ts10.SymbolFlags.Alias) !== 0) return false;
@@ -3641,7 +3690,7 @@ function privateElementDeclaration(opening) {
3641
3690
  return declaration.type === AST_NODE_TYPES26.VariableDeclarator && declaration.init === value && declaration.id.type === AST_NODE_TYPES26.Identifier && declaration.parent.kind === "const" && declaration.parent.parent.type !== AST_NODE_TYPES26.ExportNamedDeclaration ? declaration : null;
3642
3691
  }
3643
3692
  function closedElementValue(opening, declaration, fields2, reads, proof) {
3644
- if (!proof.staticScope || !closedInvocation(opening, proof) || !constInitializer(proof.services.esTreeNodeToTSNodeMap.get(declaration))) return false;
3693
+ if (!closedInvocation(opening, proof) || !constInitializer(proof.services.esTreeNodeToTSNodeMap.get(declaration))) return false;
3645
3694
  const binding3 = proof.sourceCode.getDeclaredVariables(declaration).find((variable) => variable.defs.some((definition) => definition.type === "Variable" && definition.node === declaration));
3646
3695
  const symbol = proof.checker.getSymbolAtLocation(proof.services.esTreeNodeToTSNodeMap.get(declaration.id));
3647
3696
  if (!symbol || proof.exports.has(symbol)) return false;
@@ -3732,7 +3781,6 @@ var no_excess_jsx_props_default = createRule({
3732
3781
  checker,
3733
3782
  services,
3734
3783
  sourceCode: context.sourceCode,
3735
- staticScope: !dynamic,
3736
3784
  exports: exportedSymbols(services.esTreeNodeToTSNodeMap.get(context.sourceCode.ast), checker),
3737
3785
  values: /* @__PURE__ */ new WeakSet(),
3738
3786
  objects: /* @__PURE__ */ new WeakMap(),
@@ -3758,7 +3806,7 @@ var no_excess_jsx_props_default = createRule({
3758
3806
  if (directEval(node)) dynamic = true;
3759
3807
  },
3760
3808
  "Program:exit"() {
3761
- if (!candidates.length) return;
3809
+ if (!candidates.length || dynamic) return;
3762
3810
  services = ESLintUtils4.getParserServices(context);
3763
3811
  if (!currentNativeSource(context.sourceCode, services)) return;
3764
3812
  for (const { node, declaration, fields: fields2 } of candidates) evaluate(node, declaration, fields2);
@@ -4217,7 +4265,7 @@ function isSearchParamReceiver(call, services) {
4217
4265
  // package.json
4218
4266
  var package_default = {
4219
4267
  name: "@sarj/eslint-plugin",
4220
- version: "16.38.53",
4268
+ version: "16.38.55",
4221
4269
  packageManager: "npm@12.2.1",
4222
4270
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
4223
4271
  type: "module",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "16.38.53",
3
+ "version": "16.38.55",
4
4
  "packageManager": "npm@12.2.1",
5
5
  "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",