@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 +7 -4
- package/dist/index.js +7 -4
- package/package.json +1 -1
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|