@sarj/eslint-plugin 19.9.0 → 19.12.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 +89 -17
- package/dist/{chunk-MFL7OATV.js → chunk-7SYRLUE7.js} +133 -55
- 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-7SYRLUE7.js";
|
|
6
6
|
|
|
7
7
|
// src/docs/directive-disable-enable-pair.ts
|
|
8
8
|
var DIRECTIVE_DISABLE_ENABLE_PAIR_DOCUMENTATION = {
|
|
@@ -2519,13 +2519,47 @@ var NO_PRODUCTION_BROWSER_SOURCE_MAPS_DOCUMENTATION = {
|
|
|
2519
2519
|
// src/docs/no-raw-env.ts
|
|
2520
2520
|
var NO_RAW_ENV_DOCUMENTATION = {
|
|
2521
2521
|
summary: "Disallow direct `process.env` and `import.meta.env` reads outside validated boundaries.",
|
|
2522
|
-
rationale: "Raw environment reads are untyped and defer invalid configuration failures until use.",
|
|
2523
|
-
remediation: "
|
|
2522
|
+
rationale: "Raw environment reads are untyped and defer invalid configuration failures until use. One shared validated settings object gives consumers a consistent startup configuration.",
|
|
2523
|
+
remediation: "Define a Zod settings schema, call SettingsSchema.parse(process.env) once at startup, and export or inject that single parsed settings object. Use Pydantic/Pydantic Settings for the equivalent Python boundary. Required values must fail before remote effects; preserve independent security policy and existing optional defaults.",
|
|
2524
2524
|
category: "correctness",
|
|
2525
|
-
limitations: [
|
|
2525
|
+
limitations: [
|
|
2526
|
+
"Host markers, assignment targets, tests, ordinary scripts and build config retain their policy exemptions. Boundary-named modules, including env.mjs and settings.ts, are checked. A boundary filename or schema declaration alone does not exempt raw reads: each read must flow through a parse/safeParse/createEnv argument, directly or through stable nonexported const aliases whose every use reaches validation. Only unshadowed Number/String/Boolean/BigInt calls are traversed as scalar coercions; arbitrary calls cannot consume raw settings before the parser.",
|
|
2527
|
+
"This is bounded same-file syntax analysis (24 flow steps, 128 reads per alias and 512 total expression visits per raw read); flows exceeding these budgets are unreported. It does not prove a parser's implementation, cross-file schemas, schema completeness, safeParse failure handling or a project-wide singleton. Handle safeParse failure before exposing result.data; prefer throwing parse at startup. Nested component settings and separate server/client runtime boundaries remain valid.",
|
|
2528
|
+
"Assignment-target exemptions also include compound writes that read the previous value. Whole-environment spreads outside boundary modules retain the existing forwarding exemption; a spread inside a boundary must also reach its parser. No automatic rewrite."
|
|
2529
|
+
],
|
|
2526
2530
|
examples: [
|
|
2527
|
-
{
|
|
2528
|
-
|
|
2531
|
+
{
|
|
2532
|
+
id: "validated-environment",
|
|
2533
|
+
title: "Parse once and reuse one settings object",
|
|
2534
|
+
outcome: "no-match",
|
|
2535
|
+
files: [
|
|
2536
|
+
{
|
|
2537
|
+
path: "src/database.ts",
|
|
2538
|
+
source: "import { settings } from './env.js';\nexport const primaryUrl = settings.DATABASE_URL;\nexport const replicaUrl = settings.DATABASE_URL;"
|
|
2539
|
+
},
|
|
2540
|
+
{
|
|
2541
|
+
path: "src/env.ts",
|
|
2542
|
+
source: "import { z } from 'zod';\nconst SettingsSchema = z.object({ DATABASE_URL: z.url() });\nexport const settings = SettingsSchema.parse(process.env);"
|
|
2543
|
+
}
|
|
2544
|
+
],
|
|
2545
|
+
focusPath: "src/env.ts",
|
|
2546
|
+
expectedCount: 0,
|
|
2547
|
+
public: true
|
|
2548
|
+
},
|
|
2549
|
+
{
|
|
2550
|
+
id: "raw-environment-read",
|
|
2551
|
+
title: "Declaring a schema does not validate a raw export",
|
|
2552
|
+
outcome: "match",
|
|
2553
|
+
files: [
|
|
2554
|
+
{
|
|
2555
|
+
path: "src/env.ts",
|
|
2556
|
+
source: "import { z } from 'zod';\nconst SettingsSchema = z.object({ DATABASE_URL: z.url() });\nexport const settings = { DATABASE_URL: process.env.DATABASE_URL };"
|
|
2557
|
+
}
|
|
2558
|
+
],
|
|
2559
|
+
focusPath: "src/env.ts",
|
|
2560
|
+
expectedCount: 1,
|
|
2561
|
+
public: true
|
|
2562
|
+
}
|
|
2529
2563
|
]
|
|
2530
2564
|
};
|
|
2531
2565
|
|
|
@@ -4129,7 +4163,7 @@ var PREFER_CODED_ERROR_RESULT_DOCUMENTATION = {
|
|
|
4129
4163
|
"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
4164
|
"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
4165
|
"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.",
|
|
4166
|
+
"Local aliases, unmerged interfaces without inheritance and generic substitutions are bounded to 16 resolution levels, 64 union members and 256 traversal steps. Transparent aliases retain the proven neverthrow Result/ResultAsync error position, including renamed/namespace imports, generic defaults and unshadowed native Promise/PromiseLike wrappers. Imported local declarations, conditional/intersection/mapped types and unresolved contracts abstain. Optional discriminants, independent success/error payloads and partial-data failures are excluded.",
|
|
4133
4167
|
"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.",
|
|
4134
4168
|
"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
4169
|
"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."
|
|
@@ -4139,6 +4173,36 @@ var PREFER_CODED_ERROR_RESULT_DOCUMENTATION = {
|
|
|
4139
4173
|
"https://github.com/supermacro/neverthrow"
|
|
4140
4174
|
],
|
|
4141
4175
|
examples: [
|
|
4176
|
+
{
|
|
4177
|
+
id: "aliased-library-message",
|
|
4178
|
+
scenarioId: "aliased-library-error",
|
|
4179
|
+
title: "A transparent alias must preserve the library error contract",
|
|
4180
|
+
outcome: "match",
|
|
4181
|
+
files: [
|
|
4182
|
+
{
|
|
4183
|
+
path: "app/result.ts",
|
|
4184
|
+
source: "import type { Result as NativeResult } from 'neverthrow'; type Saved<T, E> = Promise<NativeResult<T, E>>; export type Output = Saved<number, string>;"
|
|
4185
|
+
}
|
|
4186
|
+
],
|
|
4187
|
+
focusPath: "app/result.ts",
|
|
4188
|
+
expectedCount: 1,
|
|
4189
|
+
public: true
|
|
4190
|
+
},
|
|
4191
|
+
{
|
|
4192
|
+
id: "aliased-library-domain-error",
|
|
4193
|
+
scenarioId: "aliased-library-error",
|
|
4194
|
+
title: "Keep a finite error code and only the context its consumer needs",
|
|
4195
|
+
outcome: "no-match",
|
|
4196
|
+
files: [
|
|
4197
|
+
{
|
|
4198
|
+
path: "app/result.ts",
|
|
4199
|
+
source: "import type { Result as NativeResult } from 'neverthrow'; type Saved<T, E> = Promise<NativeResult<T, E>>; export type Output = Saved<number, { code: 'duplicate'; name: string } | { code: 'denied' }>;"
|
|
4200
|
+
}
|
|
4201
|
+
],
|
|
4202
|
+
focusPath: "app/result.ts",
|
|
4203
|
+
expectedCount: 0,
|
|
4204
|
+
public: true
|
|
4205
|
+
},
|
|
4142
4206
|
{
|
|
4143
4207
|
id: "presence-selected-message",
|
|
4144
4208
|
scenarioId: "presence-selected-result",
|
|
@@ -4444,7 +4508,7 @@ var PREFER_CODED_ERROR_RESULT_DOCUMENTATION = {
|
|
|
4444
4508
|
var PREFER_CONFIGURED_CLOUD_SCOPE_DOCUMENTATION = {
|
|
4445
4509
|
summary: "Prefer validated deployment configuration for account scope in outgoing cloud management requests.",
|
|
4446
4510
|
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
|
|
4511
|
+
remediation: "Read the account ID through one shared settings object validated by a Zod schema at startup, then import or inject that parsed object into consumers. Required values must fail before remote effects. Preserve independent security allowlists; an environment read alone is not validation.",
|
|
4448
4512
|
category: "correctness",
|
|
4449
4513
|
defaultLevel: "warning",
|
|
4450
4514
|
autofix: "none",
|
|
@@ -4465,22 +4529,30 @@ var PREFER_CONFIGURED_CLOUD_SCOPE_DOCUMENTATION = {
|
|
|
4465
4529
|
id: "literal-account-scope",
|
|
4466
4530
|
title: "Move outgoing account scope into deployment configuration",
|
|
4467
4531
|
outcome: "match",
|
|
4468
|
-
files: [
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4532
|
+
files: [
|
|
4533
|
+
{
|
|
4534
|
+
path: ".github/scripts/publish.mjs",
|
|
4535
|
+
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}`); }"
|
|
4536
|
+
}
|
|
4537
|
+
],
|
|
4472
4538
|
focusPath: ".github/scripts/publish.mjs",
|
|
4473
4539
|
expectedCount: 1,
|
|
4474
4540
|
public: true
|
|
4475
4541
|
},
|
|
4476
4542
|
{
|
|
4477
4543
|
id: "validated-account-scope",
|
|
4478
|
-
title: "
|
|
4544
|
+
title: "Parse one shared settings object with Zod before requests",
|
|
4479
4545
|
outcome: "no-match",
|
|
4480
|
-
files: [
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4546
|
+
files: [
|
|
4547
|
+
{
|
|
4548
|
+
path: ".github/scripts/publish.mjs",
|
|
4549
|
+
source: "import { settings } from '../../src/env.mjs';\nconst base = `https://api.cloudflare.com/client/v4/accounts/${settings.CLOUDFLARE_ACCOUNT_ID}`;\nexport async function publish(path, request = fetch) { return request(`${base}/${path}`); }\nexport async function list(request = fetch) { return request(`${base}/workers/scripts`); }"
|
|
4550
|
+
},
|
|
4551
|
+
{
|
|
4552
|
+
path: "src/env.mjs",
|
|
4553
|
+
source: "import { z } from 'zod';\nconst SettingsSchema = z.object({ CLOUDFLARE_ACCOUNT_ID: z.string().length(32).regex(/^[a-fA-F0-9]+$/u) });\nexport const settings = SettingsSchema.parse(process.env);"
|
|
4554
|
+
}
|
|
4555
|
+
],
|
|
4484
4556
|
focusPath: ".github/scripts/publish.mjs",
|
|
4485
4557
|
expectedCount: 0,
|
|
4486
4558
|
public: true
|
|
@@ -15557,11 +15557,25 @@ var no_production_browser_source_maps_default = defineRule({
|
|
|
15557
15557
|
|
|
15558
15558
|
// src/rules/no-raw-env.ts
|
|
15559
15559
|
var CONFIG_FILE_RE = /(^|[\\/])[\w.-]+\.config\.[cm]?[jt]sx?$/;
|
|
15560
|
-
var ENV_BOUNDARY_FILE_RE = /(^|[\\/])(?:env|client-env|server-env|client-settings|server-settings)\.[cm]?[jt]sx?$/;
|
|
15560
|
+
var ENV_BOUNDARY_FILE_RE = /(^|[\\/])(?:env|settings|client-env|server-env|client-settings|server-settings)\.[cm]?[jt]sx?$/;
|
|
15561
15561
|
var PROCESS_MODULES = ["node:process", "process"];
|
|
15562
|
-
var PROCESS_IMPORT_SPECIFIERS = [
|
|
15563
|
-
|
|
15564
|
-
|
|
15562
|
+
var PROCESS_IMPORT_SPECIFIERS = [
|
|
15563
|
+
"ImportDefaultSpecifier",
|
|
15564
|
+
"ImportNamespaceSpecifier"
|
|
15565
|
+
];
|
|
15566
|
+
var BUILD_TIME_CONSTANTS = /* @__PURE__ */ new Set([
|
|
15567
|
+
"NODE_ENV",
|
|
15568
|
+
"MODE",
|
|
15569
|
+
"DEV",
|
|
15570
|
+
"PROD",
|
|
15571
|
+
"SSR"
|
|
15572
|
+
]);
|
|
15573
|
+
var PLATFORM_MARKERS = /* @__PURE__ */ new Set([
|
|
15574
|
+
"NEXT_RUNTIME",
|
|
15575
|
+
"VERCEL",
|
|
15576
|
+
"VERCEL_ENV",
|
|
15577
|
+
"CI"
|
|
15578
|
+
]);
|
|
15565
15579
|
function isProcessEnv(node, sourceCode2) {
|
|
15566
15580
|
const receiver = unwrapExpression(node.object);
|
|
15567
15581
|
if (receiver.type !== "Identifier" || getPropertyName(node) !== "env") return false;
|
|
@@ -15597,6 +15611,58 @@ function isWholeEnvSpread(node) {
|
|
|
15597
15611
|
const parent = parentOf(node);
|
|
15598
15612
|
return parent?.type === "SpreadElement" && parent.argument === node;
|
|
15599
15613
|
}
|
|
15614
|
+
function reachesValidation(node, sourceCode2, seen = /* @__PURE__ */ new Set(), depth = 0, budget = { remaining: 512 }) {
|
|
15615
|
+
if (depth >= 24 || budget.remaining-- <= 0) return true;
|
|
15616
|
+
const parent = parentOf(node);
|
|
15617
|
+
if (parent === null) return false;
|
|
15618
|
+
switch (parent.type) {
|
|
15619
|
+
case "CallExpression":
|
|
15620
|
+
return parent.arguments.some((argument) => argument === node) && (isValidationCall(parent) || isScalarCoercion(parent, sourceCode2) && reachesValidation(parent, sourceCode2, seen, depth + 1, budget));
|
|
15621
|
+
case "VariableDeclarator":
|
|
15622
|
+
return parent.init === node && validatedAlias(parent, sourceCode2, seen, depth, budget);
|
|
15623
|
+
case "MemberExpression":
|
|
15624
|
+
return parent.object === node && reachesValidation(parent, sourceCode2, seen, depth + 1, budget);
|
|
15625
|
+
case "Property":
|
|
15626
|
+
return parent.value === node && parent.kind === "init" && !parent.method && reachesValidation(parent, sourceCode2, seen, depth + 1, budget);
|
|
15627
|
+
case "ObjectExpression":
|
|
15628
|
+
case "ArrayExpression":
|
|
15629
|
+
case "SpreadElement":
|
|
15630
|
+
case "LogicalExpression":
|
|
15631
|
+
case "BinaryExpression":
|
|
15632
|
+
case "ConditionalExpression":
|
|
15633
|
+
case "TemplateLiteral":
|
|
15634
|
+
case "TSAsExpression":
|
|
15635
|
+
case "TSNonNullExpression":
|
|
15636
|
+
case "TSSatisfiesExpression":
|
|
15637
|
+
return reachesValidation(parent, sourceCode2, seen, depth + 1, budget);
|
|
15638
|
+
default:
|
|
15639
|
+
return false;
|
|
15640
|
+
}
|
|
15641
|
+
}
|
|
15642
|
+
function isValidationCall(node) {
|
|
15643
|
+
const callee = unwrapExpression(node.callee);
|
|
15644
|
+
return callee.type === "Identifier" ? callee.name === "createEnv" : callee.type === "MemberExpression" && ["parse", "safeParse"].includes(getPropertyName(callee) ?? "");
|
|
15645
|
+
}
|
|
15646
|
+
function isScalarCoercion(node, sourceCode2) {
|
|
15647
|
+
const callee = unwrapExpression(node.callee);
|
|
15648
|
+
if (callee.type !== "Identifier" || !["Number", "String", "Boolean", "BigInt"].includes(callee.name) || node.arguments.length !== 1)
|
|
15649
|
+
return false;
|
|
15650
|
+
const binding3 = findVariable(sourceCode2.getScope(callee), callee.name);
|
|
15651
|
+
return binding3 === null || binding3.defs.length === 0;
|
|
15652
|
+
}
|
|
15653
|
+
function validatedAlias(node, sourceCode2, seen, depth, budget) {
|
|
15654
|
+
const declaration = parentOf(node);
|
|
15655
|
+
if (node.id.type !== "Identifier" || declaration?.type !== "VariableDeclaration" || declaration.kind !== "const")
|
|
15656
|
+
return false;
|
|
15657
|
+
if (parentOf(declaration)?.type === "ExportNamedDeclaration") return false;
|
|
15658
|
+
const variable = findVariable(sourceCode2.getScope(node.id), node.id.name);
|
|
15659
|
+
if (variable === null || seen.has(variable) || variable.references.some((ref) => ref.isWrite() && ref.init !== true))
|
|
15660
|
+
return false;
|
|
15661
|
+
const reads = variable.references.filter((ref) => ref.isRead());
|
|
15662
|
+
const next = /* @__PURE__ */ new Set([...seen, variable]);
|
|
15663
|
+
if (reads.length > 128) return true;
|
|
15664
|
+
return reads.length > 0 && reads.every((ref) => reachesValidation(ref.identifier, sourceCode2, next, depth + 1, budget));
|
|
15665
|
+
}
|
|
15600
15666
|
var no_raw_env_default = defineRule({
|
|
15601
15667
|
meta: {
|
|
15602
15668
|
type: "problem",
|
|
@@ -15606,48 +15672,35 @@ var no_raw_env_default = defineRule({
|
|
|
15606
15672
|
},
|
|
15607
15673
|
schema: [],
|
|
15608
15674
|
messages: {
|
|
15609
|
-
noRawEnv: "
|
|
15675
|
+
noRawEnv: "Read one shared settings object validated at startup with Zod (or the existing typed settings loader). Parse required environment values once at the boundary and inject or import the validated result."
|
|
15610
15676
|
}
|
|
15611
15677
|
},
|
|
15612
15678
|
createOnce(context) {
|
|
15613
15679
|
let sourceCode2;
|
|
15614
15680
|
let reads = [];
|
|
15615
15681
|
let boundaryFile = false;
|
|
15616
|
-
let hasValidationCall = false;
|
|
15617
15682
|
return {
|
|
15618
15683
|
before() {
|
|
15619
15684
|
const filename = context.filename;
|
|
15620
15685
|
const file = { filename, cwd: context.cwd };
|
|
15621
|
-
|
|
15686
|
+
boundaryFile = ENV_BOUNDARY_FILE_RE.test(filename);
|
|
15687
|
+
if (isTestFile(file) || isScriptFile(file) && !boundaryFile || CONFIG_FILE_RE.test(filename)) {
|
|
15622
15688
|
return false;
|
|
15623
15689
|
}
|
|
15624
15690
|
sourceCode2 = context.sourceCode;
|
|
15625
15691
|
reads = [];
|
|
15626
|
-
boundaryFile = ENV_BOUNDARY_FILE_RE.test(filename);
|
|
15627
|
-
hasValidationCall = false;
|
|
15628
15692
|
return void 0;
|
|
15629
15693
|
},
|
|
15630
|
-
CallExpression(node) {
|
|
15631
|
-
if (!boundaryFile) return;
|
|
15632
|
-
const callee = node.callee;
|
|
15633
|
-
if (callee.type === "Identifier" && callee.name === "createEnv") {
|
|
15634
|
-
hasValidationCall = true;
|
|
15635
|
-
} else if (callee.type === "MemberExpression") {
|
|
15636
|
-
const calleeReceiver = unwrapExpression(callee.object);
|
|
15637
|
-
const name = getPropertyName(callee);
|
|
15638
|
-
if (name === "parse" || name === "safeParse" || name === "object" && calleeReceiver.type === "Identifier" && calleeReceiver.name === "z") {
|
|
15639
|
-
hasValidationCall = true;
|
|
15640
|
-
}
|
|
15641
|
-
}
|
|
15642
|
-
},
|
|
15643
15694
|
MemberExpression(node) {
|
|
15644
|
-
if ((isProcessEnv(node, sourceCode2) || isImportMetaEnv(node)) && !isExemptVariableAccess(node) && !isWriteTarget(node) && !isWholeEnvSpread(node)) {
|
|
15695
|
+
if ((isProcessEnv(node, sourceCode2) || isImportMetaEnv(node)) && !isExemptVariableAccess(node) && !isWriteTarget(node) && !(isWholeEnvSpread(node) && !boundaryFile)) {
|
|
15645
15696
|
reads.push(node);
|
|
15646
15697
|
}
|
|
15647
15698
|
},
|
|
15648
15699
|
"Program:exit"() {
|
|
15649
|
-
|
|
15650
|
-
|
|
15700
|
+
for (const node of reads) {
|
|
15701
|
+
if (!boundaryFile || !reachesValidation(node, sourceCode2))
|
|
15702
|
+
context.report({ node, messageId: "noRawEnv" });
|
|
15703
|
+
}
|
|
15651
15704
|
}
|
|
15652
15705
|
};
|
|
15653
15706
|
}
|
|
@@ -21535,14 +21588,15 @@ function resolve2(value, source, depth = 0) {
|
|
|
21535
21588
|
if (depth >= MAX_DEPTH) return null;
|
|
21536
21589
|
const node = value.node;
|
|
21537
21590
|
if (node.type !== "TSTypeReference") return value;
|
|
21538
|
-
if (node.typeName.type !== "Identifier")
|
|
21591
|
+
if (node.typeName.type !== "Identifier")
|
|
21592
|
+
return libraryResult(node, source) ? value : null;
|
|
21539
21593
|
const variable = typeBinding(source, node.typeName);
|
|
21540
21594
|
if (variable === null || variable.defs.length === 0) return value;
|
|
21541
21595
|
const bound = value.bindings.get(variable);
|
|
21542
21596
|
if (bound !== void 0) return resolve2(bound, source, depth + 1);
|
|
21543
21597
|
const declaration = variable.defs.length === 1 ? variable.defs[0]?.node : void 0;
|
|
21544
21598
|
if (declaration?.type !== "TSTypeAliasDeclaration" && declaration?.type !== "TSInterfaceDeclaration")
|
|
21545
|
-
return null;
|
|
21599
|
+
return libraryResult(node, source) ? value : null;
|
|
21546
21600
|
if (declaration.type === "TSInterfaceDeclaration" && declaration.extends.length !== 0)
|
|
21547
21601
|
return null;
|
|
21548
21602
|
const bindings = bindArguments(declaration, node, value, source);
|
|
@@ -21708,6 +21762,26 @@ function openStringField(fields2, owner, source, names, budget) {
|
|
|
21708
21762
|
}
|
|
21709
21763
|
return null;
|
|
21710
21764
|
}
|
|
21765
|
+
function outcomeFailures(value, source, depth = 0, budget = { remaining: MAX_TYPE_STEPS }) {
|
|
21766
|
+
if (depth >= MAX_DEPTH || --budget.remaining < 0) return [];
|
|
21767
|
+
const resolved = resolve2(value, source);
|
|
21768
|
+
if (resolved === null) return [];
|
|
21769
|
+
const structural = failureMessages(resolved, source);
|
|
21770
|
+
if (structural.length !== 0) return structural;
|
|
21771
|
+
const library = libraryError(resolved, source);
|
|
21772
|
+
if (library !== null) return [library];
|
|
21773
|
+
const node = resolved.node;
|
|
21774
|
+
if (node.type !== "TSTypeReference") return [];
|
|
21775
|
+
if (node.typeName.type !== "Identifier" || !["Promise", "PromiseLike"].includes(node.typeName.name) || (typeBinding(source, node.typeName)?.defs.length ?? 0) !== 0 || node.typeArguments?.params.length !== 1)
|
|
21776
|
+
return [];
|
|
21777
|
+
const argument = node.typeArguments.params[0];
|
|
21778
|
+
return argument === void 0 ? [] : outcomeFailures(
|
|
21779
|
+
{ ...resolved, node: argument, origin: argument },
|
|
21780
|
+
source,
|
|
21781
|
+
depth + 1,
|
|
21782
|
+
budget
|
|
21783
|
+
);
|
|
21784
|
+
}
|
|
21711
21785
|
function failureMessages(value, source) {
|
|
21712
21786
|
const members2 = variants(value, source);
|
|
21713
21787
|
if (members2 === null || members2.length < 2) return [];
|
|
@@ -21800,14 +21874,18 @@ function variants(value, source, depth = 0, budget = { remaining: MAX_TYPE_STEPS
|
|
|
21800
21874
|
}
|
|
21801
21875
|
return result;
|
|
21802
21876
|
}
|
|
21803
|
-
function libraryError(
|
|
21804
|
-
|
|
21805
|
-
if (
|
|
21806
|
-
libraryName(reference.typeName, source, true) ?? ""
|
|
21807
|
-
))
|
|
21877
|
+
function libraryError(value, source) {
|
|
21878
|
+
const reference = value.node;
|
|
21879
|
+
if (reference.type !== "TSTypeReference" || !libraryResult(reference, source))
|
|
21808
21880
|
return null;
|
|
21881
|
+
if (reference.typeArguments?.params.length !== 2) return null;
|
|
21809
21882
|
const error = reference.typeArguments.params[1];
|
|
21810
|
-
return error === void 0 ? null : weakError(
|
|
21883
|
+
return error === void 0 ? null : weakError({ ...value, node: error, origin: error }, source);
|
|
21884
|
+
}
|
|
21885
|
+
function libraryResult(reference, source) {
|
|
21886
|
+
return ["Result", "ResultAsync"].includes(
|
|
21887
|
+
libraryName(reference.typeName, source, true) ?? ""
|
|
21888
|
+
);
|
|
21811
21889
|
}
|
|
21812
21890
|
function libraryName(node, source, typeSpace) {
|
|
21813
21891
|
const reference = libraryReference(node);
|
|
@@ -21833,16 +21911,6 @@ function libraryReference(node) {
|
|
|
21833
21911
|
return { root: node.object, member: node.property.name };
|
|
21834
21912
|
return null;
|
|
21835
21913
|
}
|
|
21836
|
-
function weakAnnotation(node, source, depth = 0, budget = { remaining: MAX_TYPE_STEPS }) {
|
|
21837
|
-
if (depth >= MAX_DEPTH || --budget.remaining < 0) return false;
|
|
21838
|
-
if (failureMessages(view(node), source).length !== 0) return true;
|
|
21839
|
-
if (node.type !== "TSTypeReference") return false;
|
|
21840
|
-
if (libraryError(node, source) !== null) return true;
|
|
21841
|
-
if (node.typeName.type !== "Identifier" || !["Promise", "PromiseLike"].includes(node.typeName.name) || typeBinding(source, node.typeName) !== null || node.typeArguments?.params.length !== 1)
|
|
21842
|
-
return false;
|
|
21843
|
-
const argument = node.typeArguments.params[0];
|
|
21844
|
-
return argument !== void 0 && weakAnnotation(argument, source, depth + 1, budget);
|
|
21845
|
-
}
|
|
21846
21914
|
function renderedFailure(node, source) {
|
|
21847
21915
|
let value = unwrapExpression(node);
|
|
21848
21916
|
if (value.type === "AwaitExpression")
|
|
@@ -21964,9 +22032,7 @@ var prefer_coded_error_result_default = defineRule({
|
|
|
21964
22032
|
createOnce(context) {
|
|
21965
22033
|
const reported = /* @__PURE__ */ new Set();
|
|
21966
22034
|
function check(node) {
|
|
21967
|
-
const
|
|
21968
|
-
const library = node.type === "TSTypeReference" ? libraryError(node, context.sourceCode) : null;
|
|
21969
|
-
for (const candidate2 of library === null ? candidates2 : [...candidates2, library]) {
|
|
22035
|
+
for (const candidate2 of outcomeFailures(view(node), context.sourceCode)) {
|
|
21970
22036
|
if (reported.has(candidate2)) continue;
|
|
21971
22037
|
reported.add(candidate2);
|
|
21972
22038
|
context.report({
|
|
@@ -21980,7 +22046,7 @@ var prefer_coded_error_result_default = defineRule({
|
|
|
21980
22046
|
const rendered = renderedFailure(value, context.sourceCode);
|
|
21981
22047
|
if (rendered === null || reported.has(rendered.node)) return;
|
|
21982
22048
|
const contract = owner.returnType?.typeAnnotation;
|
|
21983
|
-
if (contract !== void 0 &&
|
|
22049
|
+
if (contract !== void 0 && outcomeFailures(view(contract), context.sourceCode).length !== 0)
|
|
21984
22050
|
return;
|
|
21985
22051
|
reported.add(rendered.node);
|
|
21986
22052
|
context.report(rendered);
|
|
@@ -22035,7 +22101,7 @@ var prefer_configured_cloud_scope_default = defineRule({
|
|
|
22035
22101
|
},
|
|
22036
22102
|
schema: [],
|
|
22037
22103
|
messages: {
|
|
22038
|
-
configuredScope: "This outgoing Cloudflare management request uses a source-literal account scope.
|
|
22104
|
+
configuredScope: "This outgoing Cloudflare management request uses a source-literal account scope. Load the account ID into one shared Zod-validated settings object at startup, then inject or import it; keep independent security allowlists intact."
|
|
22039
22105
|
}
|
|
22040
22106
|
},
|
|
22041
22107
|
createOnce(context) {
|
|
@@ -22062,14 +22128,16 @@ var prefer_configured_cloud_scope_default = defineRule({
|
|
|
22062
22128
|
if (callee.type !== "Identifier") return false;
|
|
22063
22129
|
const binding3 = bindingOf(callee);
|
|
22064
22130
|
if (binding3 === null || binding3.defs.length === 0) return callee.name === "fetch";
|
|
22065
|
-
if (seen.has(binding3) || seen.size >= 16 || binding3.defs.length !== 1 || binding3.references.some((ref) => ref.isWrite() && ref.init !== true))
|
|
22131
|
+
if (seen.has(binding3) || seen.size >= 16 || binding3.defs.length !== 1 || binding3.references.some((ref) => ref.isWrite() && ref.init !== true))
|
|
22132
|
+
return false;
|
|
22066
22133
|
const next = /* @__PURE__ */ new Set([...seen, binding3]);
|
|
22067
22134
|
const [definition] = binding3.defs;
|
|
22068
22135
|
if (definition?.type === "Parameter") {
|
|
22069
22136
|
const parameter = parentOf(definition.name);
|
|
22070
22137
|
return parameter?.type === "AssignmentPattern" && parameter.left === definition.name && outgoingFetch(parameter.right, next);
|
|
22071
22138
|
}
|
|
22072
|
-
if (definition?.type !== "Variable" || definition.parent?.type !== "VariableDeclaration" || definition.parent.kind !== "const")
|
|
22139
|
+
if (definition?.type !== "Variable" || definition.parent?.type !== "VariableDeclaration" || definition.parent.kind !== "const")
|
|
22140
|
+
return false;
|
|
22073
22141
|
const declarator = definition.node;
|
|
22074
22142
|
return declarator.id.type === "Identifier" && declarator.init !== null && outgoingFetch(declarator.init, next);
|
|
22075
22143
|
}
|
|
@@ -22090,10 +22158,15 @@ var prefer_configured_cloud_scope_default = defineRule({
|
|
|
22090
22158
|
}
|
|
22091
22159
|
return;
|
|
22092
22160
|
case "ObjectPattern":
|
|
22093
|
-
for (const property of value.properties)
|
|
22161
|
+
for (const property of value.properties)
|
|
22162
|
+
checkWrite(
|
|
22163
|
+
property.type === "RestElement" ? property.argument : property.value,
|
|
22164
|
+
depth + 1
|
|
22165
|
+
);
|
|
22094
22166
|
return;
|
|
22095
22167
|
case "ArrayPattern":
|
|
22096
|
-
for (const element of value.elements)
|
|
22168
|
+
for (const element of value.elements)
|
|
22169
|
+
if (element !== null) checkWrite(element, depth + 1);
|
|
22097
22170
|
return;
|
|
22098
22171
|
case "AssignmentPattern":
|
|
22099
22172
|
checkWrite(value.left, depth + 1);
|
|
@@ -22111,15 +22184,20 @@ var prefer_configured_cloud_scope_default = defineRule({
|
|
|
22111
22184
|
}
|
|
22112
22185
|
if (value.type === "Identifier") {
|
|
22113
22186
|
const binding3 = bindingOf(value);
|
|
22114
|
-
if (binding3 === null || seen.has(binding3) || binding3.defs.length !== 1 || binding3.references.some((ref) => ref.isWrite() && ref.init !== true))
|
|
22187
|
+
if (binding3 === null || seen.has(binding3) || binding3.defs.length !== 1 || binding3.references.some((ref) => ref.isWrite() && ref.init !== true))
|
|
22188
|
+
return UNKNOWN;
|
|
22115
22189
|
const [definition] = binding3.defs;
|
|
22116
|
-
if (definition?.type !== "Variable" || definition.parent?.type !== "VariableDeclaration" || definition.parent.kind !== "const")
|
|
22190
|
+
if (definition?.type !== "Variable" || definition.parent?.type !== "VariableDeclaration" || definition.parent.kind !== "const")
|
|
22191
|
+
return UNKNOWN;
|
|
22117
22192
|
const declarator = definition.node;
|
|
22118
22193
|
if (declarator.id.type !== "Identifier" || declarator.init === null) return UNKNOWN;
|
|
22119
22194
|
return urlParts(declarator.init, /* @__PURE__ */ new Set([...seen, binding3]), depth + 1);
|
|
22120
22195
|
}
|
|
22121
22196
|
if (value.type === "BinaryExpression" && value.operator === "+") {
|
|
22122
|
-
return boundedParts([
|
|
22197
|
+
return boundedParts([
|
|
22198
|
+
...urlParts(value.left, seen, depth + 1),
|
|
22199
|
+
...urlParts(value.right, seen, depth + 1)
|
|
22200
|
+
]);
|
|
22123
22201
|
}
|
|
22124
22202
|
if (value.type === "TemplateLiteral") return templateParts(value, seen, depth);
|
|
22125
22203
|
return UNKNOWN;
|
package/dist/index.js
CHANGED