@sarj/eslint-plugin 19.2.0 → 19.7.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 +226 -10
- package/dist/{chunk-JBAPB4JX.js → chunk-OX5DMK42.js} +286 -182
- 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-OX5DMK42.js";
|
|
6
6
|
|
|
7
7
|
// src/docs/directive-disable-enable-pair.ts
|
|
8
8
|
var DIRECTIVE_DISABLE_ENABLE_PAIR_DOCUMENTATION = {
|
|
@@ -4120,22 +4120,175 @@ var PREFER_AWAIT_IN_ASYNC_RETURN_DOCUMENTATION = {
|
|
|
4120
4120
|
var PREFER_CODED_ERROR_RESULT_DOCUMENTATION = {
|
|
4121
4121
|
summary: "Return a stable error code and typed context instead of only a rendered failure message.",
|
|
4122
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.",
|
|
4123
|
+
rationale: "A message-only or opaque 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. Reuse the native no-explicit-any rule, compiler narrowing and type-aware exhaustiveness checks.",
|
|
4124
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
4125
|
category: "correctness",
|
|
4126
4126
|
autofix: "none",
|
|
4127
4127
|
limitations: [
|
|
4128
|
-
"
|
|
4129
|
-
"
|
|
4128
|
+
"Accepting an inferred producer does not prove a finite inferred contract: object payloads or errAsync can widen a code to string. Annotate the existing Result/ResultAsync return when necessary. Native assignability, narrowing and exhaustive-handler checks remain authoritative.",
|
|
4129
|
+
"Checks source-resolved local unions with required literal ok/success booleans, or status/state/kind/type/tag values ok/success versus err/error/failure/failed (case insensitive). Conflicting outcome tags abstain. A shared outcome tag does not also count as a domain error code. Named and namespace neverthrow Result/ResultAsync imports establish their generic error position.",
|
|
4130
|
+
"Presence-selected unions require an error property on every failure branch and the same single required non-control value property on every success branch. Optional error/value fields, multiple value keys, loading states and error branches carrying the success value abstain. Required errorMessage and Error-suffixed failure channels are also reviewed; multiple custom channels abstain and a primary error payload retains precedence.",
|
|
4131
|
+
"Broad string and unknown error channels, unshadowed native Error-family types and open string/unknown code fields receive review. Explicit Any stays with the existing no-explicit-any rule. Finite literal codes need no invented payload. Imported domain errors, validation-library errors, generic error parameters, ordinary formatters, legitimate absence and redacted failure states remain valid.",
|
|
4132
|
+
"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, conditional/intersection/mapped types and unresolved contracts abstain. Optional discriminants, independent success/error payloads and partial-data failures are excluded.",
|
|
4130
4133
|
"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
|
|
4132
|
-
"This warning requests domain review: a final presentation boundary may
|
|
4134
|
+
"A directly returned failure object, neverthrow err/errAsync call or new Err with an interpolated template or proven string concatenation receives review. Without a code, the diagnostic asks for a domain contract; with a literal code, it asks for consumer formatting. Literal-only compositions, including earlier stable local constants, remain valid codes. Nested plain error objects, expression-bodied arrows and one direct await are supported. Import identity and value-space shadowing are checked; arbitrary aliases, 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.",
|
|
4135
|
+
"This warning requests domain review: an infrastructure adapter may intentionally retain unknown/Error, and a final presentation boundary may accept every failure and render one message. Neither proves a recoverable domain rejection. No fix invents error codes, fields, handlers or changes serialization, exception unwinding, transactions, retries or logging."
|
|
4133
4136
|
],
|
|
4134
4137
|
references: [
|
|
4135
4138
|
"https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions",
|
|
4136
4139
|
"https://github.com/supermacro/neverthrow"
|
|
4137
4140
|
],
|
|
4138
4141
|
examples: [
|
|
4142
|
+
{
|
|
4143
|
+
id: "presence-selected-message",
|
|
4144
|
+
scenarioId: "presence-selected-result",
|
|
4145
|
+
title: "An error-presence check still needs a useful error contract",
|
|
4146
|
+
outcome: "match",
|
|
4147
|
+
files: [
|
|
4148
|
+
{
|
|
4149
|
+
path: "app/result.ts",
|
|
4150
|
+
source: "export type Outcome<T> = { value: T } | { error: string; field?: 'name' };"
|
|
4151
|
+
}
|
|
4152
|
+
],
|
|
4153
|
+
focusPath: "app/result.ts",
|
|
4154
|
+
expectedCount: 1,
|
|
4155
|
+
public: true
|
|
4156
|
+
},
|
|
4157
|
+
{
|
|
4158
|
+
id: "presence-selected-domain-error",
|
|
4159
|
+
scenarioId: "presence-selected-result",
|
|
4160
|
+
title: "Retain the existing presence check with a finite error union",
|
|
4161
|
+
outcome: "no-match",
|
|
4162
|
+
files: [
|
|
4163
|
+
{
|
|
4164
|
+
path: "app/result.ts",
|
|
4165
|
+
source: "type SaveError = { code: 'duplicate'; name: string } | { code: 'denied' }; export type Outcome<T> = { value: T } | { error: SaveError };"
|
|
4166
|
+
}
|
|
4167
|
+
],
|
|
4168
|
+
focusPath: "app/result.ts",
|
|
4169
|
+
expectedCount: 0,
|
|
4170
|
+
public: true
|
|
4171
|
+
},
|
|
4172
|
+
{
|
|
4173
|
+
id: "string-outcome-tag",
|
|
4174
|
+
scenarioId: "string-outcome",
|
|
4175
|
+
title: "An outcome tag distinguishes success from failure, not the failure's cause",
|
|
4176
|
+
outcome: "match",
|
|
4177
|
+
files: [
|
|
4178
|
+
{
|
|
4179
|
+
path: "app/result.ts",
|
|
4180
|
+
source: "export type Outcome<T> = { kind: 'ok'; value: T } | { kind: 'error'; message: string };"
|
|
4181
|
+
}
|
|
4182
|
+
],
|
|
4183
|
+
focusPath: "app/result.ts",
|
|
4184
|
+
expectedCount: 1,
|
|
4185
|
+
public: true
|
|
4186
|
+
},
|
|
4187
|
+
{
|
|
4188
|
+
id: "string-outcome-domain-code",
|
|
4189
|
+
scenarioId: "string-outcome",
|
|
4190
|
+
title: "Preserve the established string tag and add the actionable error",
|
|
4191
|
+
outcome: "no-match",
|
|
4192
|
+
files: [
|
|
4193
|
+
{
|
|
4194
|
+
path: "app/result.ts",
|
|
4195
|
+
source: "export type Outcome<T> = { kind: 'ok'; value: T } | { kind: 'error'; error: { code: 'duplicate'; name: string } };"
|
|
4196
|
+
}
|
|
4197
|
+
],
|
|
4198
|
+
focusPath: "app/result.ts",
|
|
4199
|
+
expectedCount: 0,
|
|
4200
|
+
public: true
|
|
4201
|
+
},
|
|
4202
|
+
{
|
|
4203
|
+
id: "opaque-error-channel",
|
|
4204
|
+
scenarioId: "opaque-error",
|
|
4205
|
+
title: "Unknown preserves the need to narrow but hides the domain's cases",
|
|
4206
|
+
outcome: "match",
|
|
4207
|
+
files: [
|
|
4208
|
+
{
|
|
4209
|
+
path: "app/result.ts",
|
|
4210
|
+
source: "type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }; export type Saved = Result<number, unknown>;"
|
|
4211
|
+
}
|
|
4212
|
+
],
|
|
4213
|
+
focusPath: "app/result.ts",
|
|
4214
|
+
expectedCount: 1,
|
|
4215
|
+
public: true
|
|
4216
|
+
},
|
|
4217
|
+
{
|
|
4218
|
+
id: "specific-error-channel",
|
|
4219
|
+
scenarioId: "opaque-error",
|
|
4220
|
+
title: "Expose the domain's rejection case without inventing context",
|
|
4221
|
+
outcome: "no-match",
|
|
4222
|
+
files: [
|
|
4223
|
+
{
|
|
4224
|
+
path: "app/result.ts",
|
|
4225
|
+
source: "type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }; export type Saved = Result<number, { code: 'unavailable' }>;"
|
|
4226
|
+
}
|
|
4227
|
+
],
|
|
4228
|
+
focusPath: "app/result.ts",
|
|
4229
|
+
expectedCount: 0,
|
|
4230
|
+
public: true
|
|
4231
|
+
},
|
|
4232
|
+
{
|
|
4233
|
+
id: "named-error-carrier",
|
|
4234
|
+
scenarioId: "named-error-channel",
|
|
4235
|
+
title: "A failure-only Error-suffixed field can still erase its cases",
|
|
4236
|
+
outcome: "match",
|
|
4237
|
+
files: [
|
|
4238
|
+
{
|
|
4239
|
+
path: "app/result.ts",
|
|
4240
|
+
source: "export type Outcome<T> = { ok: true; value: T } | { ok: false; fallbackError: string };"
|
|
4241
|
+
}
|
|
4242
|
+
],
|
|
4243
|
+
focusPath: "app/result.ts",
|
|
4244
|
+
expectedCount: 1,
|
|
4245
|
+
public: true
|
|
4246
|
+
},
|
|
4247
|
+
{
|
|
4248
|
+
id: "typed-named-error-carrier",
|
|
4249
|
+
scenarioId: "named-error-channel",
|
|
4250
|
+
title: "Preserve the existing field name with the required context",
|
|
4251
|
+
outcome: "no-match",
|
|
4252
|
+
files: [
|
|
4253
|
+
{
|
|
4254
|
+
path: "app/result.ts",
|
|
4255
|
+
source: "export type Outcome<T> = { ok: true; value: T } | { ok: false; fallbackError: { code: 'unsupported'; requestedKind: string } };"
|
|
4256
|
+
}
|
|
4257
|
+
],
|
|
4258
|
+
focusPath: "app/result.ts",
|
|
4259
|
+
expectedCount: 0,
|
|
4260
|
+
public: true
|
|
4261
|
+
},
|
|
4262
|
+
{
|
|
4263
|
+
id: "library-error-constructor",
|
|
4264
|
+
scenarioId: "library-error-presentation",
|
|
4265
|
+
title: "A proven Result constructor does not make formatted text actionable",
|
|
4266
|
+
outcome: "match",
|
|
4267
|
+
files: [
|
|
4268
|
+
{
|
|
4269
|
+
path: "app/result.ts",
|
|
4270
|
+
source: "import { err } from 'neverthrow'; export function reject(name: string) { return err(`Missing ${name}`); }"
|
|
4271
|
+
}
|
|
4272
|
+
],
|
|
4273
|
+
focusPath: "app/result.ts",
|
|
4274
|
+
expectedCount: 1,
|
|
4275
|
+
public: true
|
|
4276
|
+
},
|
|
4277
|
+
{
|
|
4278
|
+
id: "library-domain-constructor",
|
|
4279
|
+
scenarioId: "library-error-presentation",
|
|
4280
|
+
title: "Reuse the established Result library with a typed domain error",
|
|
4281
|
+
outcome: "no-match",
|
|
4282
|
+
files: [
|
|
4283
|
+
{
|
|
4284
|
+
path: "app/result.ts",
|
|
4285
|
+
source: "import { err, type Result } from 'neverthrow'; type Missing = { code: 'missing'; name: string }; export function reject(name: string): Result<never, Missing> { return err({ code: 'missing', name }); }"
|
|
4286
|
+
}
|
|
4287
|
+
],
|
|
4288
|
+
focusPath: "app/result.ts",
|
|
4289
|
+
expectedCount: 0,
|
|
4290
|
+
public: true
|
|
4291
|
+
},
|
|
4139
4292
|
{
|
|
4140
4293
|
id: "message-only-result",
|
|
4141
4294
|
title: "A failure erases identity into a message",
|
|
@@ -4194,6 +4347,66 @@ var PREFER_CODED_ERROR_RESULT_DOCUMENTATION = {
|
|
|
4194
4347
|
expectedCount: 0,
|
|
4195
4348
|
public: true
|
|
4196
4349
|
},
|
|
4350
|
+
{
|
|
4351
|
+
id: "exclusive-message-outcome",
|
|
4352
|
+
scenarioId: "partial-data",
|
|
4353
|
+
title: "An exclusive failure still needs an actionable identity",
|
|
4354
|
+
outcome: "match",
|
|
4355
|
+
files: [
|
|
4356
|
+
{
|
|
4357
|
+
path: "app/result.ts",
|
|
4358
|
+
source: "export type Outcome<T> = { ok: true; data: T } | { ok: false; error: string };"
|
|
4359
|
+
}
|
|
4360
|
+
],
|
|
4361
|
+
focusPath: "app/result.ts",
|
|
4362
|
+
expectedCount: 1,
|
|
4363
|
+
public: true
|
|
4364
|
+
},
|
|
4365
|
+
{
|
|
4366
|
+
id: "independent-partial-outcomes",
|
|
4367
|
+
scenarioId: "partial-data",
|
|
4368
|
+
title: "Partial data and its independent error remain separate outcomes",
|
|
4369
|
+
outcome: "no-match",
|
|
4370
|
+
files: [
|
|
4371
|
+
{
|
|
4372
|
+
path: "app/result.ts",
|
|
4373
|
+
source: "export type Partial<T> = { ok: true; data: T; error?: string } | { ok: false; data: T; error: string };"
|
|
4374
|
+
}
|
|
4375
|
+
],
|
|
4376
|
+
focusPath: "app/result.ts",
|
|
4377
|
+
expectedCount: 0,
|
|
4378
|
+
public: true
|
|
4379
|
+
},
|
|
4380
|
+
{
|
|
4381
|
+
id: "exclusive-error-state",
|
|
4382
|
+
scenarioId: "absence-state",
|
|
4383
|
+
title: "Conventional success and failure tags establish one Result",
|
|
4384
|
+
outcome: "match",
|
|
4385
|
+
files: [
|
|
4386
|
+
{
|
|
4387
|
+
path: "app/result.ts",
|
|
4388
|
+
source: "export type Outcome<T> = { status: 'success'; value: T } | { status: 'failure'; reason: string };"
|
|
4389
|
+
}
|
|
4390
|
+
],
|
|
4391
|
+
focusPath: "app/result.ts",
|
|
4392
|
+
expectedCount: 1,
|
|
4393
|
+
public: true
|
|
4394
|
+
},
|
|
4395
|
+
{
|
|
4396
|
+
id: "legitimate-unconfigured-state",
|
|
4397
|
+
scenarioId: "absence-state",
|
|
4398
|
+
title: "A loading report can preserve an unconfigured absence state",
|
|
4399
|
+
outcome: "no-match",
|
|
4400
|
+
files: [
|
|
4401
|
+
{
|
|
4402
|
+
path: "app/result.ts",
|
|
4403
|
+
source: "export type Source<T> = { status: 'loaded'; value: T } | { status: 'failed' | 'unconfigured'; reason: string };"
|
|
4404
|
+
}
|
|
4405
|
+
],
|
|
4406
|
+
focusPath: "app/result.ts",
|
|
4407
|
+
expectedCount: 0,
|
|
4408
|
+
public: true
|
|
4409
|
+
},
|
|
4197
4410
|
{
|
|
4198
4411
|
id: "producer-renders-context",
|
|
4199
4412
|
scenarioId: "consumer-formatting",
|
|
@@ -4259,22 +4472,25 @@ var PREFER_DEPENDENCY_INTERFACE_DOCUMENTATION = {
|
|
|
4259
4472
|
|
|
4260
4473
|
// src/docs/prefer-discriminated-union.ts
|
|
4261
4474
|
var PREFER_DISCRIMINATED_UNION_DOCUMENTATION = {
|
|
4262
|
-
summary: "
|
|
4475
|
+
summary: "Preserve producer-proven tag/payload and Boolean state relationships in return types.",
|
|
4263
4476
|
defaultLevel: "warning",
|
|
4264
4477
|
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.",
|
|
4265
4478
|
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.",
|
|
4266
4479
|
category: "correctness",
|
|
4267
4480
|
autofix: "safe",
|
|
4268
4481
|
limitations: [
|
|
4269
|
-
"
|
|
4482
|
+
"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.",
|
|
4270
4483
|
"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.",
|
|
4271
4484
|
"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.",
|
|
4272
|
-
"
|
|
4485
|
+
"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.",
|
|
4486
|
+
"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."
|
|
4273
4487
|
],
|
|
4274
4488
|
references: ["https://functional-architecture.org/make_illegal_states_unrepresentable/", "https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions"],
|
|
4275
4489
|
examples: [
|
|
4276
4490
|
{ 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 },
|
|
4277
|
-
{ id: "optional-result-payloads", title: "
|
|
4491
|
+
{ 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 },
|
|
4492
|
+
{ 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 },
|
|
4493
|
+
{ 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 },
|
|
4278
4494
|
{ 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 },
|
|
4279
4495
|
{ 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 },
|
|
4280
4496
|
{ 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 },
|
|
@@ -21504,8 +21504,30 @@ var NAME91 = "prefer-coded-error-result";
|
|
|
21504
21504
|
var MAX_DEPTH = 16;
|
|
21505
21505
|
var MAX_VARIANTS = 64;
|
|
21506
21506
|
var MAX_TYPE_STEPS = 256;
|
|
21507
|
-
var
|
|
21507
|
+
var BOOLEAN_STATUS_NAMES = ["ok", "success"];
|
|
21508
|
+
var STRING_STATUS_NAMES = ["status", "state", "kind", "type", "tag"];
|
|
21509
|
+
var STATUS_NAMES = [
|
|
21510
|
+
...BOOLEAN_STATUS_NAMES,
|
|
21511
|
+
...STRING_STATUS_NAMES
|
|
21512
|
+
];
|
|
21508
21513
|
var CODE_NAMES = ["code", "kind", "type", "tag"];
|
|
21514
|
+
var SUCCESS_TAGS = /* @__PURE__ */ new Set(["ok", "success"]);
|
|
21515
|
+
var FAILURE_TAGS = /* @__PURE__ */ new Set([
|
|
21516
|
+
"err",
|
|
21517
|
+
"error",
|
|
21518
|
+
"failure",
|
|
21519
|
+
"failed"
|
|
21520
|
+
]);
|
|
21521
|
+
var NATIVE_ERRORS = /* @__PURE__ */ new Set([
|
|
21522
|
+
"Error",
|
|
21523
|
+
"EvalError",
|
|
21524
|
+
"RangeError",
|
|
21525
|
+
"ReferenceError",
|
|
21526
|
+
"SyntaxError",
|
|
21527
|
+
"TypeError",
|
|
21528
|
+
"URIError",
|
|
21529
|
+
"AggregateError"
|
|
21530
|
+
]);
|
|
21509
21531
|
function view(node) {
|
|
21510
21532
|
return { node, origin: node, bindings: /* @__PURE__ */ new Map() };
|
|
21511
21533
|
}
|
|
@@ -21515,7 +21537,7 @@ function resolve2(value, source, depth = 0) {
|
|
|
21515
21537
|
if (node.type !== "TSTypeReference") return value;
|
|
21516
21538
|
if (node.typeName.type !== "Identifier") return null;
|
|
21517
21539
|
const variable = typeBinding(source, node.typeName);
|
|
21518
|
-
if (variable === null) return
|
|
21540
|
+
if (variable === null || variable.defs.length === 0) return value;
|
|
21519
21541
|
const bound = value.bindings.get(variable);
|
|
21520
21542
|
if (bound !== void 0) return resolve2(bound, source, depth + 1);
|
|
21521
21543
|
const declaration = variable.defs.length === 1 ? variable.defs[0]?.node : void 0;
|
|
@@ -21577,30 +21599,40 @@ function propertyView(property, owner) {
|
|
|
21577
21599
|
const node = property.typeAnnotation?.typeAnnotation;
|
|
21578
21600
|
return node === void 0 ? null : { node, origin: node, bindings: owner.bindings };
|
|
21579
21601
|
}
|
|
21580
|
-
function
|
|
21602
|
+
function outcomeTag(property, owner, source, name) {
|
|
21581
21603
|
if (property === void 0 || property.optional) return null;
|
|
21582
21604
|
const type = propertyView(property, owner);
|
|
21583
21605
|
const resolved = type === null ? null : resolve2(type, source);
|
|
21584
21606
|
const node = resolved?.node;
|
|
21585
|
-
return node?.type === "TSLiteralType" && node.literal.type === "Literal"
|
|
21607
|
+
return node?.type === "TSLiteralType" && node.literal.type === "Literal" ? outcomeValue(name, node.literal.value) : null;
|
|
21608
|
+
}
|
|
21609
|
+
function outcomeValue(name, value) {
|
|
21610
|
+
if (BOOLEAN_STATUS_NAMES.some((candidate2) => candidate2 === name))
|
|
21611
|
+
return typeof value === "boolean" ? value : null;
|
|
21612
|
+
if (!STRING_STATUS_NAMES.some((candidate2) => candidate2 === name) || typeof value !== "string")
|
|
21613
|
+
return null;
|
|
21614
|
+
const tag = value.toLowerCase();
|
|
21615
|
+
return SUCCESS_TAGS.has(tag) ? true : FAILURE_TAGS.has(tag) ? false : null;
|
|
21586
21616
|
}
|
|
21587
|
-
function hasCode(fields2, owner, source) {
|
|
21617
|
+
function hasCode(fields2, owner, source, ignoredCodes = []) {
|
|
21588
21618
|
return CODE_NAMES.some((name) => {
|
|
21619
|
+
if (ignoredCodes.includes(name)) return false;
|
|
21589
21620
|
const property = fields2.get(name);
|
|
21590
21621
|
if (property === void 0 || property.optional) return false;
|
|
21591
21622
|
const type = propertyView(property, owner);
|
|
21592
|
-
return type !== null &&
|
|
21623
|
+
return type !== null && openScalar(type, source) === null;
|
|
21593
21624
|
});
|
|
21594
21625
|
}
|
|
21595
|
-
function
|
|
21626
|
+
function openScalar(value, source, depth = 0, budget = { remaining: MAX_TYPE_STEPS }) {
|
|
21596
21627
|
if (depth >= MAX_DEPTH || --budget.remaining < 0) return null;
|
|
21597
21628
|
const resolved = resolve2(value, source);
|
|
21598
21629
|
if (resolved === null) return null;
|
|
21599
|
-
if (resolved.node.type === "TSStringKeyword"
|
|
21630
|
+
if (resolved.node.type === "TSStringKeyword" || resolved.node.type === "TSUnknownKeyword")
|
|
21631
|
+
return resolved.origin;
|
|
21600
21632
|
if (resolved.node.type !== "TSUnionType" || resolved.node.types.length > MAX_VARIANTS)
|
|
21601
21633
|
return null;
|
|
21602
21634
|
for (const member3 of resolved.node.types) {
|
|
21603
|
-
const broad =
|
|
21635
|
+
const broad = openScalar(
|
|
21604
21636
|
{ ...resolved, node: member3 },
|
|
21605
21637
|
source,
|
|
21606
21638
|
depth + 1,
|
|
@@ -21614,7 +21646,9 @@ function weakError(value, source, depth = 0, budget = { remaining: MAX_TYPE_STEP
|
|
|
21614
21646
|
if (depth >= MAX_DEPTH || --budget.remaining < 0) return null;
|
|
21615
21647
|
const resolved = resolve2(value, source);
|
|
21616
21648
|
if (resolved === null) return null;
|
|
21617
|
-
if (resolved.node.type === "TSStringKeyword"
|
|
21649
|
+
if (resolved.node.type === "TSStringKeyword" || resolved.node.type === "TSUnknownKeyword")
|
|
21650
|
+
return resolved.origin;
|
|
21651
|
+
if (nativeErrorType(resolved.node, source)) return resolved.origin;
|
|
21618
21652
|
if (resolved.node.type === "TSUnionType") {
|
|
21619
21653
|
if (resolved.node.types.length > MAX_VARIANTS) return null;
|
|
21620
21654
|
for (const member3 of resolved.node.types) {
|
|
@@ -21632,22 +21666,44 @@ function weakError(value, source, depth = 0, budget = { remaining: MAX_TYPE_STEP
|
|
|
21632
21666
|
if (fields2 === null || hasCode(fields2, resolved, source)) return null;
|
|
21633
21667
|
return weakObject(fields2, resolved, source, depth, budget);
|
|
21634
21668
|
}
|
|
21635
|
-
function
|
|
21636
|
-
|
|
21669
|
+
function nativeErrorType(node, source) {
|
|
21670
|
+
if (node.type !== "TSTypeReference" || node.typeName.type !== "Identifier" || !NATIVE_ERRORS.has(node.typeName.name) || (node.typeArguments?.params.length ?? 0) !== 0)
|
|
21671
|
+
return false;
|
|
21672
|
+
const binding3 = typeBinding(source, node.typeName);
|
|
21673
|
+
return binding3 === null || binding3.defs.length === 0;
|
|
21674
|
+
}
|
|
21675
|
+
function weakObject(fields2, owner, source, depth, budget, ignoredCodes = []) {
|
|
21676
|
+
const code = openStringField(
|
|
21677
|
+
fields2,
|
|
21678
|
+
owner,
|
|
21679
|
+
source,
|
|
21680
|
+
CODE_NAMES.filter((name) => !ignoredCodes.includes(name)),
|
|
21681
|
+
budget
|
|
21682
|
+
);
|
|
21637
21683
|
if (code !== null) return code;
|
|
21638
21684
|
const error = fields2.get("error");
|
|
21639
21685
|
if (error !== void 0) {
|
|
21640
21686
|
const inner = propertyView(error, owner);
|
|
21641
21687
|
return inner === null || error.optional ? null : weakError(inner, source, depth + 1, budget);
|
|
21642
21688
|
}
|
|
21689
|
+
const carriers = [...fields2].filter(([name]) => errorCarrier(name));
|
|
21690
|
+
if (carriers.length > 1) return null;
|
|
21691
|
+
const carrier = carriers[0]?.[1];
|
|
21692
|
+
if (carrier !== void 0) {
|
|
21693
|
+
const inner = propertyView(carrier, owner);
|
|
21694
|
+
return inner === null || carrier.optional ? null : weakError(inner, source, depth + 1, budget);
|
|
21695
|
+
}
|
|
21643
21696
|
return openStringField(fields2, owner, source, ["message", "reason"], budget);
|
|
21644
21697
|
}
|
|
21698
|
+
function errorCarrier(name) {
|
|
21699
|
+
return name === "error" || name === "errorMessage" || name.endsWith("Error");
|
|
21700
|
+
}
|
|
21645
21701
|
function openStringField(fields2, owner, source, names, budget) {
|
|
21646
21702
|
for (const name of names) {
|
|
21647
21703
|
const property = fields2.get(name);
|
|
21648
21704
|
if (property === void 0 || property.optional) continue;
|
|
21649
21705
|
const inner = propertyView(property, owner);
|
|
21650
|
-
const broad = inner === null ? null :
|
|
21706
|
+
const broad = inner === null ? null : openScalar(inner, source, 0, budget);
|
|
21651
21707
|
if (broad !== null) return broad;
|
|
21652
21708
|
}
|
|
21653
21709
|
return null;
|
|
@@ -21660,26 +21716,70 @@ function failureMessages(value, source) {
|
|
|
21660
21716
|
fields: properties(member3)
|
|
21661
21717
|
}));
|
|
21662
21718
|
if (objects.some((object) => object.fields === null)) return [];
|
|
21719
|
+
let selected = null;
|
|
21720
|
+
const discriminators = [];
|
|
21663
21721
|
for (const status of STATUS_NAMES) {
|
|
21664
|
-
const
|
|
21665
|
-
(object) =>
|
|
21722
|
+
const tags2 = objects.map(
|
|
21723
|
+
(object) => outcomeTag(object.fields?.get(status), object.member, source, status)
|
|
21666
21724
|
);
|
|
21667
|
-
if (
|
|
21725
|
+
if (tags2.some((tag) => tag === null) || !tags2.includes(true) || !tags2.includes(false))
|
|
21668
21726
|
continue;
|
|
21669
|
-
if (
|
|
21670
|
-
|
|
21671
|
-
)
|
|
21672
|
-
|
|
21673
|
-
|
|
21674
|
-
|
|
21675
|
-
return [];
|
|
21676
|
-
if (["data", "value", "result"].some((name) => object.fields?.has(name)))
|
|
21677
|
-
return [];
|
|
21678
|
-
const error = weakError(object.member, source);
|
|
21679
|
-
return error === null ? [] : [error];
|
|
21680
|
-
});
|
|
21727
|
+
if (selected !== null && tags2.some((tag, index) => tag !== selected?.[index]))
|
|
21728
|
+
return [];
|
|
21729
|
+
discriminators.push(status);
|
|
21730
|
+
if (selected === null) {
|
|
21731
|
+
selected = tags2;
|
|
21732
|
+
}
|
|
21681
21733
|
}
|
|
21682
|
-
return
|
|
21734
|
+
if (selected === null) return presenceFailures(objects, source);
|
|
21735
|
+
const tags = selected;
|
|
21736
|
+
if (objects.some(
|
|
21737
|
+
(object, index) => tags[index] === true && [...object.fields?.keys() ?? []].some(
|
|
21738
|
+
(name) => name === "errors" || errorCarrier(name)
|
|
21739
|
+
)
|
|
21740
|
+
))
|
|
21741
|
+
return [];
|
|
21742
|
+
return objects.flatMap((object, index) => {
|
|
21743
|
+
if (tags[index] !== false || object.fields === null || hasCode(object.fields, object.member, source, discriminators) || ["data", "value", "result"].some((name) => object.fields?.has(name)))
|
|
21744
|
+
return [];
|
|
21745
|
+
const error = weakObject(
|
|
21746
|
+
object.fields,
|
|
21747
|
+
object.member,
|
|
21748
|
+
source,
|
|
21749
|
+
0,
|
|
21750
|
+
{ remaining: MAX_TYPE_STEPS },
|
|
21751
|
+
discriminators
|
|
21752
|
+
);
|
|
21753
|
+
return error === null ? [] : [error];
|
|
21754
|
+
});
|
|
21755
|
+
}
|
|
21756
|
+
function presenceFailures(objects, source) {
|
|
21757
|
+
const failed2 = objects.filter((object) => object.fields?.has("error"));
|
|
21758
|
+
const successful = objects.filter((object) => !object.fields?.has("error"));
|
|
21759
|
+
if (failed2.length === 0 || successful.length === 0) return [];
|
|
21760
|
+
let successKey = null;
|
|
21761
|
+
for (const object of successful) {
|
|
21762
|
+
if (object.fields === null || object.fields.size !== 1) return [];
|
|
21763
|
+
const property = [...object.fields][0];
|
|
21764
|
+
if (property === void 0 || property[1].optional || errorCarrier(property[0]) || property[0] === "errors" || STATUS_NAMES.includes(property[0]) || CODE_NAMES.some((name) => name === property[0]))
|
|
21765
|
+
return [];
|
|
21766
|
+
if (successKey !== null && successKey !== property[0]) return [];
|
|
21767
|
+
successKey = property[0];
|
|
21768
|
+
}
|
|
21769
|
+
if (successKey === null) return [];
|
|
21770
|
+
const key = successKey;
|
|
21771
|
+
if (failed2.some(
|
|
21772
|
+
(object) => object.fields?.get("error")?.optional || object.fields?.has(key)
|
|
21773
|
+
))
|
|
21774
|
+
return [];
|
|
21775
|
+
return failed2.flatMap((object) => {
|
|
21776
|
+
if (object.fields === null || hasCode(object.fields, object.member, source))
|
|
21777
|
+
return [];
|
|
21778
|
+
const error = object.fields.get("error");
|
|
21779
|
+
const inner = error === void 0 ? null : propertyView(error, object.member);
|
|
21780
|
+
const weak = inner === null ? null : weakError(inner, source);
|
|
21781
|
+
return weak === null ? [] : [weak];
|
|
21782
|
+
});
|
|
21683
21783
|
}
|
|
21684
21784
|
function variants(value, source, depth = 0, budget = { remaining: MAX_TYPE_STEPS }) {
|
|
21685
21785
|
if (--budget.remaining < 0) return null;
|
|
@@ -21701,18 +21801,37 @@ function variants(value, source, depth = 0, budget = { remaining: MAX_TYPE_STEPS
|
|
|
21701
21801
|
return result;
|
|
21702
21802
|
}
|
|
21703
21803
|
function libraryError(reference, source) {
|
|
21704
|
-
if (reference.
|
|
21804
|
+
if (reference.typeArguments?.params.length !== 2) return null;
|
|
21805
|
+
if (!["Result", "ResultAsync"].includes(
|
|
21806
|
+
libraryName(reference.typeName, source, true) ?? ""
|
|
21807
|
+
))
|
|
21705
21808
|
return null;
|
|
21706
|
-
const
|
|
21809
|
+
const error = reference.typeArguments.params[1];
|
|
21810
|
+
return error === void 0 ? null : weakError(view(error), source);
|
|
21811
|
+
}
|
|
21812
|
+
function libraryName(node, source, typeSpace) {
|
|
21813
|
+
const reference = libraryReference(node);
|
|
21814
|
+
if (reference === null) return null;
|
|
21815
|
+
const { root, member: member3 } = reference;
|
|
21816
|
+
const variable = typeSpace ? typeBinding(source, root) : findVariable(source.getScope(root), root.name);
|
|
21707
21817
|
const definition = variable?.defs.length === 1 ? variable.defs[0]?.node : void 0;
|
|
21708
|
-
if (definition?.type !== "ImportSpecifier"
|
|
21818
|
+
if (definition?.type !== "ImportSpecifier" && definition?.type !== "ImportNamespaceSpecifier")
|
|
21819
|
+
return null;
|
|
21820
|
+
if (variable === null || !stableValueBinding(variable)) return null;
|
|
21709
21821
|
const declaration = parentOf(definition);
|
|
21710
21822
|
if (declaration?.type !== "ImportDeclaration" || declaration.source.value !== "neverthrow")
|
|
21711
21823
|
return null;
|
|
21712
|
-
if (!
|
|
21824
|
+
if (!typeSpace && (declaration.importKind === "type" || definition.type === "ImportSpecifier" && definition.importKind === "type"))
|
|
21713
21825
|
return null;
|
|
21714
|
-
|
|
21715
|
-
|
|
21826
|
+
return member3 === null && definition.type === "ImportSpecifier" ? importSpecifierName(definition) : member3 !== null && definition.type === "ImportNamespaceSpecifier" ? member3 : null;
|
|
21827
|
+
}
|
|
21828
|
+
function libraryReference(node) {
|
|
21829
|
+
if (node.type === "Identifier") return { root: node, member: null };
|
|
21830
|
+
if (node.type === "TSQualifiedName" && node.left.type === "Identifier" && node.right.type === "Identifier")
|
|
21831
|
+
return { root: node.left, member: node.right.name };
|
|
21832
|
+
if (node.type === "MemberExpression" && !node.computed && !node.optional && node.object.type === "Identifier" && node.property.type === "Identifier")
|
|
21833
|
+
return { root: node.object, member: node.property.name };
|
|
21834
|
+
return null;
|
|
21716
21835
|
}
|
|
21717
21836
|
function weakAnnotation(node, source, depth = 0, budget = { remaining: MAX_TYPE_STEPS }) {
|
|
21718
21837
|
if (depth >= MAX_DEPTH || --budget.remaining < 0) return false;
|
|
@@ -21724,23 +21843,65 @@ function weakAnnotation(node, source, depth = 0, budget = { remaining: MAX_TYPE_
|
|
|
21724
21843
|
const argument = node.typeArguments.params[0];
|
|
21725
21844
|
return argument !== void 0 && weakAnnotation(argument, source, depth + 1, budget);
|
|
21726
21845
|
}
|
|
21727
|
-
function renderedFailure(node) {
|
|
21728
|
-
|
|
21729
|
-
if (
|
|
21846
|
+
function renderedFailure(node, source) {
|
|
21847
|
+
let value = unwrapExpression(node);
|
|
21848
|
+
if (value.type === "AwaitExpression")
|
|
21849
|
+
value = unwrapExpression(value.argument);
|
|
21850
|
+
if (value.type === "CallExpression" || value.type === "NewExpression") {
|
|
21851
|
+
if (value.type === "CallExpression" && value.optional || value.arguments.length !== 1)
|
|
21852
|
+
return null;
|
|
21853
|
+
const name = libraryName(value.callee, source, false);
|
|
21854
|
+
if (value.type === "NewExpression" ? name !== "Err" : name !== "err" && name !== "errAsync")
|
|
21855
|
+
return null;
|
|
21856
|
+
const argument = value.arguments[0];
|
|
21857
|
+
if (argument === void 0 || argument.type === "SpreadElement")
|
|
21858
|
+
return null;
|
|
21859
|
+
return renderedPayload(argument, source);
|
|
21860
|
+
}
|
|
21861
|
+
const outer = objectFields(value);
|
|
21862
|
+
if (outer === null) return null;
|
|
21863
|
+
const discriminators = STATUS_NAMES.filter((name) => {
|
|
21864
|
+
const status = outer.get(name);
|
|
21865
|
+
return status?.type === "Literal" && outcomeValue(name, status.value) === false;
|
|
21866
|
+
});
|
|
21867
|
+
if (discriminators.length === 0) return null;
|
|
21868
|
+
if (STATUS_NAMES.some((name) => {
|
|
21730
21869
|
const status = outer.get(name);
|
|
21731
|
-
return status?.type === "Literal" && status.value ===
|
|
21732
|
-
}))
|
|
21870
|
+
return status?.type === "Literal" && outcomeValue(name, status.value) === true;
|
|
21871
|
+
}) || ["data", "value", "result"].some((name) => outer.has(name)))
|
|
21733
21872
|
return null;
|
|
21734
21873
|
const error = outer.get("error");
|
|
21735
|
-
|
|
21736
|
-
|
|
21874
|
+
return renderedPayload(
|
|
21875
|
+
error ?? value,
|
|
21876
|
+
source,
|
|
21877
|
+
error === void 0 ? discriminators : []
|
|
21878
|
+
);
|
|
21879
|
+
}
|
|
21880
|
+
function renderedPayload(node, source, ignoredCodes = []) {
|
|
21881
|
+
if (renderedString(node, source))
|
|
21882
|
+
return { node, messageId: "preferCodedErrorResult" };
|
|
21883
|
+
const fields2 = objectFields(unwrapExpression(node));
|
|
21884
|
+
if (fields2 === null) return null;
|
|
21885
|
+
const codes = CODE_NAMES.filter(
|
|
21886
|
+
(name) => !ignoredCodes.includes(name) && fields2.has(name)
|
|
21887
|
+
);
|
|
21888
|
+
const coded = codes.some((name) => {
|
|
21737
21889
|
const code = fields2.get(name);
|
|
21738
21890
|
return code?.type === "Literal" && typeof code.value === "string";
|
|
21739
|
-
})
|
|
21740
|
-
|
|
21741
|
-
|
|
21742
|
-
|
|
21743
|
-
|
|
21891
|
+
});
|
|
21892
|
+
if (codes.length !== 0 && !coded) return null;
|
|
21893
|
+
const primary = fields2.get("error");
|
|
21894
|
+
const carriers = [...fields2].filter(([name]) => errorCarrier(name));
|
|
21895
|
+
if (primary === void 0 && carriers.length > 1) return null;
|
|
21896
|
+
const carrier = primary ?? carriers[0]?.[1];
|
|
21897
|
+
if (!coded && carrier !== void 0)
|
|
21898
|
+
return renderedString(carrier, source) ? { node: carrier, messageId: "preferCodedErrorResult" } : null;
|
|
21899
|
+
for (const [name, value] of fields2) {
|
|
21900
|
+
if ((name === "message" || name === "reason" || errorCarrier(name)) && renderedString(value, source))
|
|
21901
|
+
return {
|
|
21902
|
+
node: value,
|
|
21903
|
+
messageId: coded ? "formatAtConsumer" : "preferCodedErrorResult"
|
|
21904
|
+
};
|
|
21744
21905
|
}
|
|
21745
21906
|
return null;
|
|
21746
21907
|
}
|
|
@@ -21756,14 +21917,36 @@ function objectFields(node) {
|
|
|
21756
21917
|
}
|
|
21757
21918
|
return fields2;
|
|
21758
21919
|
}
|
|
21759
|
-
function renderedString(node,
|
|
21760
|
-
|
|
21761
|
-
|
|
21762
|
-
|
|
21763
|
-
|
|
21764
|
-
|
|
21765
|
-
|
|
21766
|
-
|
|
21920
|
+
function renderedString(node, source) {
|
|
21921
|
+
let remaining = MAX_TYPE_STEPS;
|
|
21922
|
+
function literalComposition(input, depth) {
|
|
21923
|
+
if (depth >= MAX_DEPTH || --remaining < 0) return null;
|
|
21924
|
+
const value = unwrapExpression(input);
|
|
21925
|
+
if (value.type === "Identifier") {
|
|
21926
|
+
const initializer = stableConstInitializer(source, value);
|
|
21927
|
+
return initializer === null ? false : literalComposition(initializer.value, depth + 1);
|
|
21928
|
+
}
|
|
21929
|
+
if (value.type === "Literal")
|
|
21930
|
+
return value.value === null || ["string", "number", "boolean", "bigint"].includes(typeof value.value);
|
|
21931
|
+
const children = value.type === "TemplateLiteral" ? value.expressions : value.type === "BinaryExpression" && value.operator === "+" ? [value.left, value.right] : null;
|
|
21932
|
+
if (children === null) return false;
|
|
21933
|
+
for (const child of children) {
|
|
21934
|
+
const constant = literalComposition(child, depth + 1);
|
|
21935
|
+
if (constant !== true) return constant;
|
|
21936
|
+
}
|
|
21937
|
+
return true;
|
|
21938
|
+
}
|
|
21939
|
+
function rendered(input, depth) {
|
|
21940
|
+
if (depth >= MAX_DEPTH || --remaining < 0) return false;
|
|
21941
|
+
const value = unwrapExpression(input);
|
|
21942
|
+
if (value.type === "TemplateLiteral") return value.expressions.length > 0;
|
|
21943
|
+
if (value.type !== "BinaryExpression" || value.operator !== "+")
|
|
21944
|
+
return false;
|
|
21945
|
+
return [value.left, value.right].some(
|
|
21946
|
+
(part) => part.type === "Literal" && typeof part.value === "string" || rendered(part, depth + 1)
|
|
21947
|
+
);
|
|
21948
|
+
}
|
|
21949
|
+
return literalComposition(node, 0) === false && rendered(node, 0);
|
|
21767
21950
|
}
|
|
21768
21951
|
var prefer_coded_error_result_default = defineRule({
|
|
21769
21952
|
meta: {
|
|
@@ -21774,7 +21957,7 @@ var prefer_coded_error_result_default = defineRule({
|
|
|
21774
21957
|
},
|
|
21775
21958
|
schema: [],
|
|
21776
21959
|
messages: {
|
|
21777
|
-
preferCodedErrorResult: "This Result failure
|
|
21960
|
+
preferCodedErrorResult: "This Result failure hides its identity in a broad string, unknown, or generic Error channel. Expose expected failures as finite domain error codes with the typed context needed by the caller, and format the message at the consumer. Review intentional infrastructure and presentation boundaries explicitly.",
|
|
21778
21961
|
formatAtConsumer: "This coded failure formats context into a message at the producer. Return the code and typed context, and let the consumer format the message. Preserve an intentional presentation boundary explicitly."
|
|
21779
21962
|
}
|
|
21780
21963
|
},
|
|
@@ -21794,13 +21977,13 @@ var prefer_coded_error_result_default = defineRule({
|
|
|
21794
21977
|
}
|
|
21795
21978
|
function checkProducer(owner, value) {
|
|
21796
21979
|
if (owner === null) return;
|
|
21797
|
-
const rendered = renderedFailure(value);
|
|
21798
|
-
if (rendered === null || reported.has(rendered)) return;
|
|
21980
|
+
const rendered = renderedFailure(value, context.sourceCode);
|
|
21981
|
+
if (rendered === null || reported.has(rendered.node)) return;
|
|
21799
21982
|
const contract = owner.returnType?.typeAnnotation;
|
|
21800
21983
|
if (contract !== void 0 && weakAnnotation(contract, context.sourceCode))
|
|
21801
21984
|
return;
|
|
21802
|
-
reported.add(rendered);
|
|
21803
|
-
context.report(
|
|
21985
|
+
reported.add(rendered.node);
|
|
21986
|
+
context.report(rendered);
|
|
21804
21987
|
}
|
|
21805
21988
|
return {
|
|
21806
21989
|
before() {
|
|
@@ -22099,11 +22282,43 @@ function discriminatedUnionReturns(node, sourceCode2) {
|
|
|
22099
22282
|
if (member3.optional) continue;
|
|
22100
22283
|
const domain = tagDomain(member3.typeAnnotation?.typeAnnotation);
|
|
22101
22284
|
if (domain !== void 0 && hasTagSpecificPayload(name, domain, shape.optional, returned2)) {
|
|
22102
|
-
return { reportNode: contract.reportNode, tag: name, members: shape.members, returned: returned2 };
|
|
22285
|
+
return { reportNode: contract.reportNode, relationship: { kind: "presence", tag: name }, members: shape.members, returned: returned2 };
|
|
22286
|
+
}
|
|
22287
|
+
}
|
|
22288
|
+
const fields2 = correlatedBooleanFields(shape, returned2);
|
|
22289
|
+
return fields2 === void 0 ? void 0 : {
|
|
22290
|
+
reportNode: contract.reportNode,
|
|
22291
|
+
relationship: { kind: "boolean-pair", fields: fields2 },
|
|
22292
|
+
members: shape.members,
|
|
22293
|
+
returned: returned2
|
|
22294
|
+
};
|
|
22295
|
+
}
|
|
22296
|
+
function correlatedBooleanFields(shape, returned2) {
|
|
22297
|
+
const fields2 = [...shape.members].filter(([, member3]) => !member3.optional && member3.typeAnnotation?.typeAnnotation.type === "TSBooleanKeyword").map(([name]) => name);
|
|
22298
|
+
if (fields2.length > 8) return void 0;
|
|
22299
|
+
for (const [index, left] of fields2.entries()) {
|
|
22300
|
+
for (const right of fields2.slice(index + 1)) {
|
|
22301
|
+
const combinations = booleanCombinations(left, right, returned2);
|
|
22302
|
+
if (combinations !== void 0 && restrictedVaryingPair(combinations)) return { left, right };
|
|
22103
22303
|
}
|
|
22104
22304
|
}
|
|
22105
22305
|
return void 0;
|
|
22106
22306
|
}
|
|
22307
|
+
function booleanCombinations(left, right, returned2) {
|
|
22308
|
+
const combinations = /* @__PURE__ */ new Set();
|
|
22309
|
+
for (const properties2 of returned2) {
|
|
22310
|
+
const a = properties2.get(left);
|
|
22311
|
+
const b = properties2.get(right);
|
|
22312
|
+
if (a?.type !== "Literal" || typeof a.value !== "boolean" || b?.type !== "Literal" || typeof b.value !== "boolean") return void 0;
|
|
22313
|
+
combinations.add(`${Number(a.value)}${Number(b.value)}`);
|
|
22314
|
+
}
|
|
22315
|
+
return combinations;
|
|
22316
|
+
}
|
|
22317
|
+
function restrictedVaryingPair(combinations) {
|
|
22318
|
+
if (combinations.size < 2 || combinations.size === 4) return false;
|
|
22319
|
+
const values = [...combinations];
|
|
22320
|
+
return [0, 1].every((index) => values.some((value) => value[index] === "0") && values.some((value) => value[index] === "1"));
|
|
22321
|
+
}
|
|
22107
22322
|
function returnContract(node, sourceCode2) {
|
|
22108
22323
|
const annotation = node.returnType;
|
|
22109
22324
|
if (!isPresent(annotation)) return void 0;
|
|
@@ -22148,7 +22363,7 @@ function objectContract(elements) {
|
|
|
22148
22363
|
members2.set(name, member3);
|
|
22149
22364
|
if (member3.optional) optional.add(name);
|
|
22150
22365
|
}
|
|
22151
|
-
return
|
|
22366
|
+
return { members: members2, optional };
|
|
22152
22367
|
}
|
|
22153
22368
|
function returnedProperties(objects, contract) {
|
|
22154
22369
|
const returned2 = [];
|
|
@@ -22334,102 +22549,18 @@ function literalTag(node) {
|
|
|
22334
22549
|
|
|
22335
22550
|
// src/rules/prefer-discriminated-union.ts
|
|
22336
22551
|
var NAME94 = "prefer-discriminated-union";
|
|
22337
|
-
var STATUS_MEMBER_NAMES = /* @__PURE__ */ new Set([
|
|
22338
|
-
"success",
|
|
22339
|
-
"ok"
|
|
22340
|
-
]);
|
|
22341
|
-
var FAILURE_MEMBER_NAMES = /* @__PURE__ */ new Set([
|
|
22342
|
-
"error",
|
|
22343
|
-
"errors",
|
|
22344
|
-
"reason",
|
|
22345
|
-
"cause"
|
|
22346
|
-
]);
|
|
22347
|
-
var SUCCESS_PAYLOAD_MEMBER_NAMES = /* @__PURE__ */ new Set([
|
|
22348
|
-
"data",
|
|
22349
|
-
"events",
|
|
22350
|
-
"payload",
|
|
22351
|
-
"response",
|
|
22352
|
-
"result",
|
|
22353
|
-
"value"
|
|
22354
|
-
]);
|
|
22355
|
-
var REQUIRED_STATUS_MEMBER_COUNT = 1;
|
|
22356
|
-
var FUNCTION_RETURN_OWNER_TYPES = /* @__PURE__ */ new Set([
|
|
22357
|
-
"ArrowFunctionExpression",
|
|
22358
|
-
"FunctionDeclaration",
|
|
22359
|
-
"FunctionExpression",
|
|
22360
|
-
"TSDeclareFunction",
|
|
22361
|
-
"TSEmptyBodyFunctionExpression",
|
|
22362
|
-
"TSFunctionType",
|
|
22363
|
-
"TSMethodSignature"
|
|
22364
|
-
]);
|
|
22365
|
-
function looksLikeMutuallyExclusiveState(typeLiteral) {
|
|
22366
|
-
let statusMemberCount = 0;
|
|
22367
|
-
let hasFailurePayload = false;
|
|
22368
|
-
let hasSuccessPayload = false;
|
|
22369
|
-
let hasUnrecognizedMember = false;
|
|
22370
|
-
for (const member3 of typeLiteral.members) {
|
|
22371
|
-
if (member3.type !== "TSPropertySignature") {
|
|
22372
|
-
hasUnrecognizedMember = true;
|
|
22373
|
-
continue;
|
|
22374
|
-
}
|
|
22375
|
-
const name = getMemberName(member3);
|
|
22376
|
-
if (name !== null && STATUS_MEMBER_NAMES.has(name) && isBooleanTyped(member3) && !member3.optional) {
|
|
22377
|
-
statusMemberCount += 1;
|
|
22378
|
-
continue;
|
|
22379
|
-
}
|
|
22380
|
-
if (!member3.optional || isBooleanTyped(member3) || name === null) {
|
|
22381
|
-
hasUnrecognizedMember = true;
|
|
22382
|
-
continue;
|
|
22383
|
-
}
|
|
22384
|
-
if (FAILURE_MEMBER_NAMES.has(name)) {
|
|
22385
|
-
hasFailurePayload = true;
|
|
22386
|
-
} else if (SUCCESS_PAYLOAD_MEMBER_NAMES.has(name)) {
|
|
22387
|
-
hasSuccessPayload = true;
|
|
22388
|
-
} else {
|
|
22389
|
-
hasUnrecognizedMember = true;
|
|
22390
|
-
}
|
|
22391
|
-
}
|
|
22392
|
-
return statusMemberCount === REQUIRED_STATUS_MEMBER_COUNT && hasFailurePayload && (hasSuccessPayload || !hasUnrecognizedMember);
|
|
22393
|
-
}
|
|
22394
|
-
function getMemberName(member3) {
|
|
22395
|
-
if (member3.type !== "TSPropertySignature" || member3.computed) {
|
|
22396
|
-
return null;
|
|
22397
|
-
}
|
|
22398
|
-
const { key } = member3;
|
|
22399
|
-
if (key.type === "Identifier") {
|
|
22400
|
-
return key.name;
|
|
22401
|
-
}
|
|
22402
|
-
if (key.type === "Literal" && typeof key.value === "string") {
|
|
22403
|
-
return key.value;
|
|
22404
|
-
}
|
|
22405
|
-
return null;
|
|
22406
|
-
}
|
|
22407
|
-
function isBooleanTyped(member3) {
|
|
22408
|
-
return member3.typeAnnotation?.typeAnnotation.type === "TSBooleanKeyword";
|
|
22409
|
-
}
|
|
22410
|
-
function inlineReturnTypeLiteral(node) {
|
|
22411
|
-
let annotation = null;
|
|
22412
|
-
if (node.parent.type === "TSTypeAnnotation") {
|
|
22413
|
-
annotation = node.parent;
|
|
22414
|
-
} else if (node.parent.type === "TSTypeParameterInstantiation" && node.parent.params.length === 1 && node.parent.params[0] === node && node.parent.parent.type === "TSTypeReference" && node.parent.parent.typeName.type === "Identifier" && node.parent.parent.typeName.name === "Promise" && node.parent.parent.parent.type === "TSTypeAnnotation") {
|
|
22415
|
-
annotation = node.parent.parent.parent;
|
|
22416
|
-
}
|
|
22417
|
-
if (annotation === null) return null;
|
|
22418
|
-
const owner = annotation.parent;
|
|
22419
|
-
return FUNCTION_RETURN_OWNER_TYPES.has(owner.type) && "returnType" in owner && owner.returnType === annotation ? annotation : null;
|
|
22420
|
-
}
|
|
22421
22552
|
var prefer_discriminated_union_default = defineRule({
|
|
22422
22553
|
meta: {
|
|
22423
22554
|
type: "suggestion",
|
|
22424
22555
|
fixable: "code",
|
|
22425
22556
|
docs: {
|
|
22426
|
-
description: "
|
|
22557
|
+
description: "Preserve producer-proven tag/payload and Boolean state relationships in return types.",
|
|
22427
22558
|
url: docsUrl(NAME94)
|
|
22428
22559
|
},
|
|
22429
22560
|
schema: [],
|
|
22430
22561
|
messages: {
|
|
22431
22562
|
preferProducedVariants: "This producer returns tag-specific payloads for `{{tag}}`, but its flat return contract makes them optional. If these variants define the domain, use a discriminated union with each variant's required payload, or give this producer a precise return type separate from the broader contract.",
|
|
22432
|
-
|
|
22563
|
+
preferProducedState: "This producer returns a restricted set of literal combinations for `{{fields}}`, while its Boolean fields permit all four. Review a union preserving every returned combination, or a typed disposition if these are domain phases. Other writers and readers of a shared contract require review."
|
|
22433
22564
|
}
|
|
22434
22565
|
},
|
|
22435
22566
|
createOnce(context) {
|
|
@@ -22443,30 +22574,13 @@ var prefer_discriminated_union_default = defineRule({
|
|
|
22443
22574
|
if (result === void 0 || reportedContracts.has(result.reportNode)) return;
|
|
22444
22575
|
reportedContracts.add(result.reportNode);
|
|
22445
22576
|
const fix = privateVariantReturn(context.sourceCode, context.filename, node, result);
|
|
22577
|
+
const relationship = result.relationship;
|
|
22446
22578
|
context.report({
|
|
22447
22579
|
node: result.reportNode,
|
|
22448
|
-
messageId: "preferProducedVariants",
|
|
22449
22580
|
...fix !== null && { fix },
|
|
22450
|
-
data: { tag:
|
|
22581
|
+
...relationship.kind === "presence" ? { messageId: "preferProducedVariants", data: { tag: relationship.tag } } : { messageId: "preferProducedState", data: { fields: [relationship.fields.left, relationship.fields.right].join("`, `") } }
|
|
22451
22582
|
});
|
|
22452
22583
|
}
|
|
22453
|
-
function checkTypeLiteral(typeLiteral, reportNode) {
|
|
22454
|
-
if (looksLikeMutuallyExclusiveState(typeLiteral) && !reportedContracts.has(reportNode)) {
|
|
22455
|
-
reportedContracts.add(reportNode);
|
|
22456
|
-
const fix = inlineProducerFix(reportNode);
|
|
22457
|
-
context.report({
|
|
22458
|
-
node: reportNode,
|
|
22459
|
-
messageId: "preferDiscriminatedUnion",
|
|
22460
|
-
...fix !== null && { fix }
|
|
22461
|
-
});
|
|
22462
|
-
}
|
|
22463
|
-
}
|
|
22464
|
-
function inlineProducerFix(reportNode) {
|
|
22465
|
-
if (reportNode.type !== "TSTypeAnnotation" || reportNode.parent.type !== "FunctionDeclaration") return null;
|
|
22466
|
-
const owner = reportNode.parent;
|
|
22467
|
-
const result = discriminatedUnionReturns(owner, context.sourceCode);
|
|
22468
|
-
return result === void 0 ? null : privateVariantReturn(context.sourceCode, context.filename, owner, result);
|
|
22469
|
-
}
|
|
22470
22584
|
return {
|
|
22471
22585
|
before() {
|
|
22472
22586
|
reportedContracts.clear();
|
|
@@ -22480,34 +22594,23 @@ var prefer_discriminated_union_default = defineRule({
|
|
|
22480
22594
|
FunctionExpression: rememberProducer,
|
|
22481
22595
|
"Program:exit"() {
|
|
22482
22596
|
for (const producer of producers) checkProducer(producer);
|
|
22483
|
-
},
|
|
22484
|
-
TSInterfaceDeclaration(node) {
|
|
22485
|
-
if (node.extends.length > 0) {
|
|
22486
|
-
return;
|
|
22487
|
-
}
|
|
22488
|
-
checkTypeLiteral({ members: node.body.body }, node);
|
|
22489
|
-
},
|
|
22490
|
-
TSTypeLiteral(node) {
|
|
22491
|
-
if (node.parent.type === "TSTypeAliasDeclaration") checkTypeLiteral(node, node.parent);
|
|
22492
|
-
const annotation = inlineReturnTypeLiteral(node);
|
|
22493
|
-
if (annotation !== null) checkTypeLiteral(node, annotation);
|
|
22494
22597
|
}
|
|
22495
22598
|
};
|
|
22496
22599
|
}
|
|
22497
22600
|
});
|
|
22498
22601
|
function privateVariantReturn(sourceCode2, filename, owner, result) {
|
|
22499
22602
|
const annotation = owner.returnType?.typeAnnotation;
|
|
22500
|
-
if (owner.type !== "FunctionDeclaration" || owner.id === null || !returnsOnlyConditionalObject(owner) || annotation?.type !== "TSTypeLiteral" || result.reportNode !== owner.returnType || result.returned.length
|
|
22603
|
+
if (owner.type !== "FunctionDeclaration" || owner.id === null || !returnsOnlyConditionalObject(owner) || annotation?.type !== "TSTypeLiteral" || result.reportNode !== owner.returnType || result.returned.length > 4 || sourceCode2.getCommentsInside(owner).length > 0 || !isUnusedPrivateBinding(sourceCode2, owner, owner.id, filename) || !retainsNamedTypeUses(sourceCode2, annotation)) return null;
|
|
22501
22604
|
const variants2 = result.returned.map((properties2) => privateVariantType(properties2, result, sourceCode2));
|
|
22502
22605
|
if (variants2.some((variant) => variant === null)) return null;
|
|
22503
|
-
const text = `${variants2.join(" | ")}${sourceCode2.getText(annotation).replace(/[^\r\n]/gu, "")}`;
|
|
22606
|
+
const text = `${variants2.join(" | ")}${sourceCode2.getText(annotation).replace(/[^\r\n\u2028\u2029]/gu, "")}`;
|
|
22504
22607
|
return (fixer) => fixer.replaceText(annotation, text);
|
|
22505
22608
|
}
|
|
22506
22609
|
function returnsOnlyConditionalObject(fn) {
|
|
22507
22610
|
if (fn.async || fn.generator || isPresent(fn.typeParameters)) return false;
|
|
22508
22611
|
const statements = fn.body?.body ?? [];
|
|
22509
22612
|
const [returned2] = statements;
|
|
22510
|
-
return statements.length === 1 && returned2?.type === "ReturnStatement" && returned2.argument?.type === "ConditionalExpression"
|
|
22613
|
+
return statements.length === 1 && returned2?.type === "ReturnStatement" && returned2.argument?.type === "ConditionalExpression";
|
|
22511
22614
|
}
|
|
22512
22615
|
function privateVariantType(properties2, result, sourceCode2) {
|
|
22513
22616
|
if ([...properties2.values()].some((value) => value.type !== "Literal" || typeof value.value === "object" && value.value !== null || typeof value.value === "number" && !Number.isFinite(value.value))) return null;
|
|
@@ -22517,7 +22620,8 @@ function privateVariantType(properties2, result, sourceCode2) {
|
|
|
22517
22620
|
if (value === void 0) continue;
|
|
22518
22621
|
const annotation = member3.typeAnnotation?.typeAnnotation;
|
|
22519
22622
|
if (annotation === void 0) return null;
|
|
22520
|
-
const
|
|
22623
|
+
const narrow = result.relationship.kind === "presence" ? name === result.relationship.tag : name === result.relationship.fields.left || name === result.relationship.fields.right;
|
|
22624
|
+
const type = narrow ? sourceCode2.getText(value) : sourceCode2.getText(annotation);
|
|
22521
22625
|
fields2.push(`${member3.readonly ? "readonly " : ""}${sourceCode2.getText(member3.key)}: ${type}`);
|
|
22522
22626
|
}
|
|
22523
22627
|
return `{ ${fields2.join("; ")} }`;
|
package/dist/index.js
CHANGED