@sarj/eslint-plugin 16.38.52 → 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
@@ -3121,6 +3121,7 @@ var CONFIG2 = { path: "tsconfig.json", source: '{"compilerOptions":{"strict":tru
3121
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);';
3122
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);';
3123
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 ?? "";');
3124
3125
  var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3125
3126
  defaultLevel: "warning",
3126
3127
  summary: "Prefer the proven read-only subset for a private consumer parameter.",
@@ -3131,7 +3132,7 @@ var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3131
3132
  limitations: [
3132
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.",
3133
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.",
3134
- "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.",
3135
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."
3136
3137
  ],
3137
3138
  examples: [
@@ -3140,7 +3141,9 @@ var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3140
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;", "") }] },
3141
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 },
3142
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;", "") }] },
3143
- { 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 }
3144
3147
  ]
3145
3148
  };
3146
3149
  function inlineFields(node, reads) {
@@ -3148,7 +3151,7 @@ function inlineFields(node, reads) {
3148
3151
  for (const member3 of node.members) {
3149
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;
3150
3153
  const key = member3.name.text;
3151
- 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;
3152
3155
  if (fields2.has(key)) return null;
3153
3156
  fields2.set(key, !member3.questionToken);
3154
3157
  }
@@ -4261,7 +4264,7 @@ function isSearchParamReceiver(call, services) {
4261
4264
  // package.json
4262
4265
  var package_default = {
4263
4266
  name: "@sarj/eslint-plugin",
4264
- version: "16.38.52",
4267
+ version: "16.38.53",
4265
4268
  packageManager: "npm@12.2.1",
4266
4269
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
4267
4270
  type: "module",
package/dist/index.js CHANGED
@@ -3070,6 +3070,7 @@ var CONFIG2 = { path: "tsconfig.json", source: '{"compilerOptions":{"strict":tru
3070
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);';
3071
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);';
3072
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 ?? "";');
3073
3074
  var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3074
3075
  defaultLevel: "warning",
3075
3076
  summary: "Prefer the proven read-only subset for a private consumer parameter.",
@@ -3080,7 +3081,7 @@ var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3080
3081
  limitations: [
3081
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.",
3082
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.",
3083
- "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.",
3084
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."
3085
3086
  ],
3086
3087
  examples: [
@@ -3089,7 +3090,9 @@ var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
3089
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;", "") }] },
3090
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 },
3091
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;", "") }] },
3092
- { 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 }
3093
3096
  ]
3094
3097
  };
3095
3098
  function inlineFields(node, reads) {
@@ -3097,7 +3100,7 @@ function inlineFields(node, reads) {
3097
3100
  for (const member3 of node.members) {
3098
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;
3099
3102
  const key = member3.name.text;
3100
- 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;
3101
3104
  if (fields2.has(key)) return null;
3102
3105
  fields2.set(key, !member3.questionToken);
3103
3106
  }
@@ -4214,7 +4217,7 @@ function isSearchParamReceiver(call, services) {
4214
4217
  // package.json
4215
4218
  var package_default = {
4216
4219
  name: "@sarj/eslint-plugin",
4217
- version: "16.38.52",
4220
+ version: "16.38.53",
4218
4221
  packageManager: "npm@12.2.1",
4219
4222
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
4220
4223
  type: "module",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "16.38.52",
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",