@sarj/eslint-plugin 19.4.0 → 19.8.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 +268 -6
- package/dist/{chunk-6RX5RU75.js → chunk-G5BIV4A4.js} +527 -172
- 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-G5BIV4A4.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",
|
|
@@ -4227,6 +4440,54 @@ var PREFER_CODED_ERROR_RESULT_DOCUMENTATION = {
|
|
|
4227
4440
|
]
|
|
4228
4441
|
};
|
|
4229
4442
|
|
|
4443
|
+
// src/docs/prefer-configured-cloud-scope.ts
|
|
4444
|
+
var PREFER_CONFIGURED_CLOUD_SCOPE_DOCUMENTATION = {
|
|
4445
|
+
summary: "Prefer validated deployment configuration for account scope in outgoing cloud management requests.",
|
|
4446
|
+
rationale: "A source-literal cloud account scope pins operational requests to one deployment even when the credential or environment changes.",
|
|
4447
|
+
remediation: "Read the account ID from environment variables or injected configuration, validate it at startup, and pass the validated value to the request. Preserve independent security allowlists and fail closed when required configuration is missing.",
|
|
4448
|
+
category: "correctness",
|
|
4449
|
+
defaultLevel: "warning",
|
|
4450
|
+
autofix: "none",
|
|
4451
|
+
requiresTypeInformation: false,
|
|
4452
|
+
limitations: [
|
|
4453
|
+
"Initial coverage is canonical HTTPS Cloudflare v4 account-management URLs with a 32-character hexadecimal account segment sent to an unshadowed fetch, stable const alias, or fetch-default parameter. Other providers, SDKs, arbitrary endpoints, Worker names and resource IDs require separate evidence.",
|
|
4454
|
+
"Follows bounded same-file const aliases, concatenations and template strings; an unknown account, mutable binding, function return, object property, fallback expression, URL/Request object or cross-file value is not inferred. Direct writes to the ambient fetch or its global receivers veto findings; indirect runtime monkeypatches are not modeled.",
|
|
4455
|
+
"Analysis stops at 16 nested expressions or fetch aliases, 256 URL-expression visits per request, 128 string fragments or 8,192 UTF-16 code units of assembled URL text. These limits deliberately leave more complex expressions unreported.",
|
|
4456
|
+
"Scripts remain covered. Test/fixture and generated files are excluded. Comparisons, schemas, allowlists and unused URL strings do not produce findings. Each originating account literal is reported at most once per file.",
|
|
4457
|
+
"This is a warning without an automatic fix: the deployment configuration boundary and independent authorization policy require review. Application code should import validated configuration as required by no-raw-env; browser-exposed variables must never contain secrets."
|
|
4458
|
+
],
|
|
4459
|
+
references: [
|
|
4460
|
+
"https://developers.cloudflare.com/workers/wrangler/system-environment-variables/",
|
|
4461
|
+
"https://eslint.org/docs/latest/rules/no-restricted-syntax"
|
|
4462
|
+
],
|
|
4463
|
+
examples: [
|
|
4464
|
+
{
|
|
4465
|
+
id: "literal-account-scope",
|
|
4466
|
+
title: "Move outgoing account scope into deployment configuration",
|
|
4467
|
+
outcome: "match",
|
|
4468
|
+
files: [{
|
|
4469
|
+
path: ".github/scripts/publish.mjs",
|
|
4470
|
+
source: "const accountId = '0123456789abcdef0123456789abcdef';\nconst base = `https://api.cloudflare.com/client/v4/accounts/${accountId}`;\nexport async function publish(path, request = fetch) { return request(`${base}/${path}`); }"
|
|
4471
|
+
}],
|
|
4472
|
+
focusPath: ".github/scripts/publish.mjs",
|
|
4473
|
+
expectedCount: 1,
|
|
4474
|
+
public: true
|
|
4475
|
+
},
|
|
4476
|
+
{
|
|
4477
|
+
id: "validated-account-scope",
|
|
4478
|
+
title: "Pass validated deployment configuration to the request",
|
|
4479
|
+
outcome: "no-match",
|
|
4480
|
+
files: [{
|
|
4481
|
+
path: ".github/scripts/publish.mjs",
|
|
4482
|
+
source: "import { env } from './env.mjs';\nconst base = `https://api.cloudflare.com/client/v4/accounts/${env.CLOUDFLARE_ACCOUNT_ID}`;\nexport async function publish(path, request = fetch) { return request(`${base}/${path}`); }"
|
|
4483
|
+
}],
|
|
4484
|
+
focusPath: ".github/scripts/publish.mjs",
|
|
4485
|
+
expectedCount: 0,
|
|
4486
|
+
public: true
|
|
4487
|
+
}
|
|
4488
|
+
]
|
|
4489
|
+
};
|
|
4490
|
+
|
|
4230
4491
|
// src/docs/prefer-constant-time-secret-compare.ts
|
|
4231
4492
|
var PREFER_CONSTANT_TIME_SECRET_COMPARE_DOCUMENTATION = {
|
|
4232
4493
|
summary: "Prefer a supported constant-time comparison primitive for secret-like values.",
|
|
@@ -6629,6 +6890,7 @@ var RULE_DOCUMENTATION = {
|
|
|
6629
6890
|
"no-zod-parse-typed-object": NO_ZOD_PARSE_TYPED_OBJECT_DOCUMENTATION,
|
|
6630
6891
|
"prefer-await-in-async-return": PREFER_AWAIT_IN_ASYNC_RETURN_DOCUMENTATION,
|
|
6631
6892
|
"prefer-coded-error-result": PREFER_CODED_ERROR_RESULT_DOCUMENTATION,
|
|
6893
|
+
"prefer-configured-cloud-scope": PREFER_CONFIGURED_CLOUD_SCOPE_DOCUMENTATION,
|
|
6632
6894
|
"prefer-constant-time-secret-compare": PREFER_CONSTANT_TIME_SECRET_COMPARE_DOCUMENTATION,
|
|
6633
6895
|
"prefer-dependency-interface": PREFER_DEPENDENCY_INTERFACE_DOCUMENTATION,
|
|
6634
6896
|
"prefer-discriminated-union": PREFER_DISCRIMINATED_UNION_DOCUMENTATION,
|