@sarj/eslint-plugin 19.0.2 → 19.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/catalog.js +120 -5
- package/dist/{chunk-KBGBBVAC.js → chunk-6RX5RU75.js} +671 -423
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/catalog.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
publicDocumentation,
|
|
3
3
|
ruleSpec,
|
|
4
4
|
src_default
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-6RX5RU75.js";
|
|
6
6
|
|
|
7
7
|
// src/docs/directive-disable-enable-pair.ts
|
|
8
8
|
var DIRECTIVE_DISABLE_ENABLE_PAIR_DOCUMENTATION = {
|
|
@@ -4116,6 +4116,117 @@ var PREFER_AWAIT_IN_ASYNC_RETURN_DOCUMENTATION = {
|
|
|
4116
4116
|
]
|
|
4117
4117
|
};
|
|
4118
4118
|
|
|
4119
|
+
// src/docs/prefer-coded-error-result.ts
|
|
4120
|
+
var PREFER_CODED_ERROR_RESULT_DOCUMENTATION = {
|
|
4121
|
+
summary: "Return a stable error code and typed context instead of only a rendered failure message.",
|
|
4122
|
+
defaultLevel: "warning",
|
|
4123
|
+
rationale: "A message-only failure hides the identity needed for repair, conflict handling, localization and exhaustive dispatch. Preserve a finite error code and the minimum handling context; let the consumer format its message.",
|
|
4124
|
+
remediation: "Use the existing Result or outcome union with a finite error code and variant-specific context. Preserve public behavior through a presentation adapter, and handle or propagate each relevant variant without parsing message text.",
|
|
4125
|
+
category: "correctness",
|
|
4126
|
+
autofix: "none",
|
|
4127
|
+
limitations: [
|
|
4128
|
+
"Checks source-resolved local unions with required literal ok/success true and false branches, and named neverthrow Result/ResultAsync imports. Broad string error/message/reason payloads and open string code fields are review candidates. String literal error-code unions remain valid.",
|
|
4129
|
+
"Local aliases, unmerged interfaces without inheritance and generic substitutions are bounded to 16 resolution levels, 64 union members and 256 traversal steps. Imported local declarations, namespace imports, conditional/intersection/mapped types and unresolved contracts abstain. Optional discriminants, independent success/error payloads and partial-data failures are excluded.",
|
|
4130
|
+
"Required code/kind/type/tag fields of unresolved or imported type defer to the type checker; a finite code does not prove that its cases or payloads are useful. Generic error parameters are retained rather than guessed. Ordinary string formatters, absence, validation-library errors and redacted failure states without a string payload are not flagged.",
|
|
4131
|
+
"A directly returned plain object with literal ok/success false and a literal string code/kind/type/tag also receives review when its message/reason is an interpolated template or proven string concatenation. Nested plain error objects and expression-bodied arrows are supported. Spreads, accessors, computed keys, helper formatting calls and nonliteral codes abstain. This proves formatting ownership, not which context the domain must expose; presentation adapters remain a reviewed exception.",
|
|
4132
|
+
"This warning requests domain review: a final presentation boundary may intentionally accept every failure and render one message. No fix invents error codes, fields, handlers or changes serialization, exception unwinding, transactions, retries or logging."
|
|
4133
|
+
],
|
|
4134
|
+
references: [
|
|
4135
|
+
"https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions",
|
|
4136
|
+
"https://github.com/supermacro/neverthrow"
|
|
4137
|
+
],
|
|
4138
|
+
examples: [
|
|
4139
|
+
{
|
|
4140
|
+
id: "message-only-result",
|
|
4141
|
+
title: "A failure erases identity into a message",
|
|
4142
|
+
outcome: "match",
|
|
4143
|
+
files: [
|
|
4144
|
+
{
|
|
4145
|
+
path: "app/result.ts",
|
|
4146
|
+
source: "export type Result<T> = { ok: true; data: T } | { ok: false; message: string };"
|
|
4147
|
+
}
|
|
4148
|
+
],
|
|
4149
|
+
focusPath: "app/result.ts",
|
|
4150
|
+
expectedCount: 1,
|
|
4151
|
+
public: true
|
|
4152
|
+
},
|
|
4153
|
+
{
|
|
4154
|
+
id: "code-and-context",
|
|
4155
|
+
title: "The consumer can choose behavior and format context",
|
|
4156
|
+
outcome: "no-match",
|
|
4157
|
+
files: [
|
|
4158
|
+
{
|
|
4159
|
+
path: "app/result.ts",
|
|
4160
|
+
source: "type Error = { code: 'duplicate'; name: string } | { code: 'invalid_transition' }; export type Result<T> = { ok: true; data: T } | { ok: false; error: Error };"
|
|
4161
|
+
}
|
|
4162
|
+
],
|
|
4163
|
+
focusPath: "app/result.ts",
|
|
4164
|
+
expectedCount: 0,
|
|
4165
|
+
public: true
|
|
4166
|
+
},
|
|
4167
|
+
{
|
|
4168
|
+
id: "generic-error-instantiation",
|
|
4169
|
+
scenarioId: "generic-error",
|
|
4170
|
+
title: "A concrete generic error channel erases its identity",
|
|
4171
|
+
outcome: "match",
|
|
4172
|
+
files: [
|
|
4173
|
+
{
|
|
4174
|
+
path: "app/result.ts",
|
|
4175
|
+
source: "type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }; export type Saved = Result<number, string>;"
|
|
4176
|
+
}
|
|
4177
|
+
],
|
|
4178
|
+
focusPath: "app/result.ts",
|
|
4179
|
+
expectedCount: 1,
|
|
4180
|
+
public: true
|
|
4181
|
+
},
|
|
4182
|
+
{
|
|
4183
|
+
id: "finite-error-codes",
|
|
4184
|
+
scenarioId: "generic-error",
|
|
4185
|
+
title: "An error code needs no invented payload",
|
|
4186
|
+
outcome: "no-match",
|
|
4187
|
+
files: [
|
|
4188
|
+
{
|
|
4189
|
+
path: "app/result.ts",
|
|
4190
|
+
source: "type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }; export type Saved = Result<number, 'duplicate' | 'invalid_transition'>;"
|
|
4191
|
+
}
|
|
4192
|
+
],
|
|
4193
|
+
focusPath: "app/result.ts",
|
|
4194
|
+
expectedCount: 0,
|
|
4195
|
+
public: true
|
|
4196
|
+
},
|
|
4197
|
+
{
|
|
4198
|
+
id: "producer-renders-context",
|
|
4199
|
+
scenarioId: "consumer-formatting",
|
|
4200
|
+
title: "A code alone does not move message formatting to the consumer",
|
|
4201
|
+
outcome: "match",
|
|
4202
|
+
files: [
|
|
4203
|
+
{
|
|
4204
|
+
path: "app/result.ts",
|
|
4205
|
+
source: "export function reject(name: string) { return { ok: false, code: 'duplicate', message: `Duplicate ${name}` }; }"
|
|
4206
|
+
}
|
|
4207
|
+
],
|
|
4208
|
+
focusPath: "app/result.ts",
|
|
4209
|
+
expectedCount: 1,
|
|
4210
|
+
public: true
|
|
4211
|
+
},
|
|
4212
|
+
{
|
|
4213
|
+
id: "consumer-renders-context",
|
|
4214
|
+
scenarioId: "consumer-formatting",
|
|
4215
|
+
title: "Retain the typed context and format after handling the code",
|
|
4216
|
+
outcome: "no-match",
|
|
4217
|
+
files: [
|
|
4218
|
+
{
|
|
4219
|
+
path: "app/result.ts",
|
|
4220
|
+
source: "type Duplicate = { code: 'duplicate'; name: string }; export function reject(name: string): { ok: false; error: Duplicate } { return { ok: false, error: { code: 'duplicate', name } }; } export function format(error: Duplicate): string { return `Duplicate ${error.name}`; }"
|
|
4221
|
+
}
|
|
4222
|
+
],
|
|
4223
|
+
focusPath: "app/result.ts",
|
|
4224
|
+
expectedCount: 0,
|
|
4225
|
+
public: true
|
|
4226
|
+
}
|
|
4227
|
+
]
|
|
4228
|
+
};
|
|
4229
|
+
|
|
4119
4230
|
// src/docs/prefer-constant-time-secret-compare.ts
|
|
4120
4231
|
var PREFER_CONSTANT_TIME_SECRET_COMPARE_DOCUMENTATION = {
|
|
4121
4232
|
summary: "Prefer a supported constant-time comparison primitive for secret-like values.",
|
|
@@ -4148,22 +4259,25 @@ var PREFER_DEPENDENCY_INTERFACE_DOCUMENTATION = {
|
|
|
4148
4259
|
|
|
4149
4260
|
// src/docs/prefer-discriminated-union.ts
|
|
4150
4261
|
var PREFER_DISCRIMINATED_UNION_DOCUMENTATION = {
|
|
4151
|
-
summary: "
|
|
4262
|
+
summary: "Preserve producer-proven tag/payload and Boolean state relationships in return types.",
|
|
4152
4263
|
defaultLevel: "warning",
|
|
4153
4264
|
rationale: "A flat object with optional branch data loses the relationship between a tag and its payload. Callers cannot narrow to the required fields, and the type permits incomplete or contradictory combinations.",
|
|
4154
4265
|
remediation: "If the variants are mutually exclusive, represent each with a literal tag and its required payload. Preserve shared fields on every member, including payload value types and nullability. For a producer returning a subset of a broader wire contract, give the producer a precise domain return type after validation.",
|
|
4155
4266
|
category: "correctness",
|
|
4156
4267
|
autofix: "safe",
|
|
4157
4268
|
limitations: [
|
|
4158
|
-
"
|
|
4269
|
+
"Names, Boolean counts and optional-field counts never establish a finding. A producer's direct literal writes establish either tag-specific payload presence or a pair of required Boolean fields with two or three distinct combinations, with both fields varying. All four combinations, constant dimensions and expression-valued flags are excluded. At most eight Boolean fields are compared. Only one relationship is reported per contract; payload evidence takes precedence.",
|
|
4159
4270
|
"Producer analysis requires a finite literal tag, every declared tag syntactically witnessed, exhaustive simple return paths without literal constant conditions, plain object returns, and consistent optional-field presence for each tag. It resolves only unmerged local aliases/interfaces without inheritance or generics, and the built-in Promise around async returns. This is syntax-only evidence of field presence; explicit undefined, void and null payloads are skipped, while expression value types are not inferred. Dynamic returns, spreads, computed keys, accessors, loops and try statements are deliberately skipped.",
|
|
4160
4271
|
"A switch producer is supported only for a non-overloaded function declaration when its sole body statement switches on an unchanged direct string or number parameter, has unique same-kind literal labels and a default, and every nonempty case body is a sole object return or throw, optionally inside sole blocks. Empty labels may share the next terminating body; nested conditions, conditional returns, statement prefixes, breaks, other fallthrough, captured/closed inputs and switches under preceding control flow abstain. Broad parameter domains establish input witnesses for each label and the default; this does not infer branch-specific runtime effects or expression value types. Path visits, depth and returned objects are bounded conservatively.",
|
|
4161
|
-
"
|
|
4272
|
+
"These are refactoring advisories, not proven defects or proof that other writers cannot produce more states. Historical observations, partial results, unknown states and independently completing operations may need the broader contract. Review all readers and writers before migrating a shared contract; preserve every valid combination. A union of existing literal flag pairs is the smallest type-only replacement; a named disposition is a reviewed domain migration.",
|
|
4273
|
+
"Automatic fixes are limited to an unused private nongeneric synchronous function with a direct inline contract and one conditional return containing at most four literal objects. They narrow only the existing discriminator or implicated Boolean pair and preserve other member types and all runtime code, including condition evaluation, object creation, identity and exceptions. Nested conditionals are supported. Shared/observed contracts, aliases, exports, async functions, comments, expression payloads and generated/declaration/test files remain excluded. Public interfaces, serialization and cancellation are never rewritten."
|
|
4162
4274
|
],
|
|
4163
4275
|
references: ["https://functional-architecture.org/make_illegal_states_unrepresentable/", "https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions"],
|
|
4164
4276
|
examples: [
|
|
4165
4277
|
{ id: "explicit-result-branches", title: "Use explicit result branches", outcome: "no-match", files: [{ path: "src/result.ts", source: "type Result = { ok: true; data: string } | { ok: false; error: string };" }], focusPath: "src/result.ts", expectedCount: 0, public: true },
|
|
4166
|
-
{ id: "optional-result-payloads", title: "
|
|
4278
|
+
{ id: "optional-result-payloads", title: "A producer establishes the status-to-payload relationship", outcome: "match", files: [{ path: "src/result.ts", source: 'function result(ok: boolean): { ok: boolean; data?: string; error?: string } { return ok ? { ok: true, data: "value" } : { ok: false, error: "failed" }; }' }], focusPath: "src/result.ts", expectedCount: 1, public: true },
|
|
4279
|
+
{ id: "failure-dispositions", scenarioId: "boolean-state", title: "A producer returns retry, terminal and saved-partial combinations", outcome: "match", files: [{ path: "src/failure.ts", source: "function failure(retry: boolean, saved: boolean): { retriable: boolean; persisted: boolean } { return saved ? { retriable: false, persisted: true } : retry ? { retriable: true, persisted: false } : { retriable: false, persisted: false }; }" }], focusPath: "src/failure.ts", expectedCount: 1, public: true },
|
|
4280
|
+
{ id: "independent-observations", scenarioId: "boolean-state", title: "Independent observations retain all four combinations", outcome: "no-match", files: [{ path: "src/observations.ts", source: "function observations(a: boolean, b: boolean): { started: boolean; reported: boolean } { return a ? b ? { started: true, reported: true } : { started: true, reported: false } : b ? { started: false, reported: true } : { started: false, reported: false }; }" }], focusPath: "src/observations.ts", expectedCount: 0, public: true },
|
|
4167
4281
|
{ id: "flat-produced-variants", scenarioId: "produced-variants", title: "Preserve producer variant relationships", outcome: "match", files: [{ path: "src/job.ts", source: 'function job(done: boolean): { phase: "queued" | "complete"; ticket?: string } { return done ? { phase: "complete" } : { phase: "queued", ticket: "t" }; }' }], focusPath: "src/job.ts", expectedCount: 1, public: true },
|
|
4168
4282
|
{ id: "explicit-produced-variants", scenarioId: "produced-variants", title: "Give each produced variant its payload", outcome: "no-match", files: [{ path: "src/job.ts", source: 'function job(done: boolean): { phase: "queued"; ticket: string } | { phase: "complete" } { return done ? { phase: "complete" } : { phase: "queued", ticket: "t" }; }' }], focusPath: "src/job.ts", expectedCount: 0, public: true },
|
|
4169
4283
|
{ id: "flat-switch-producer", scenarioId: "switch-producer", title: "A complete dispatcher preserves each returned variant", outcome: "match", files: [{ path: "src/job.ts", source: 'function job(command: string): { phase: "queued" | "complete"; ticket?: string } { switch (command) { case "start": return { phase: "queued", ticket: "t" }; default: return { phase: "complete" }; } }' }], focusPath: "src/job.ts", expectedCount: 1, public: true },
|
|
@@ -6514,6 +6628,7 @@ var RULE_DOCUMENTATION = {
|
|
|
6514
6628
|
"no-zod-native-enum": NO_ZOD_NATIVE_ENUM_DOCUMENTATION,
|
|
6515
6629
|
"no-zod-parse-typed-object": NO_ZOD_PARSE_TYPED_OBJECT_DOCUMENTATION,
|
|
6516
6630
|
"prefer-await-in-async-return": PREFER_AWAIT_IN_ASYNC_RETURN_DOCUMENTATION,
|
|
6631
|
+
"prefer-coded-error-result": PREFER_CODED_ERROR_RESULT_DOCUMENTATION,
|
|
6517
6632
|
"prefer-constant-time-secret-compare": PREFER_CONSTANT_TIME_SECRET_COMPARE_DOCUMENTATION,
|
|
6518
6633
|
"prefer-dependency-interface": PREFER_DEPENDENCY_INTERFACE_DOCUMENTATION,
|
|
6519
6634
|
"prefer-discriminated-union": PREFER_DISCRIMINATED_UNION_DOCUMENTATION,
|