@sarj/eslint-plugin 19.8.0 → 19.10.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 +62 -20
- package/dist/{chunk-G5BIV4A4.js → chunk-ZWOGM4CV.js} +153 -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-ZWOGM4CV.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
|
|
|
@@ -4444,7 +4478,7 @@ var PREFER_CODED_ERROR_RESULT_DOCUMENTATION = {
|
|
|
4444
4478
|
var PREFER_CONFIGURED_CLOUD_SCOPE_DOCUMENTATION = {
|
|
4445
4479
|
summary: "Prefer validated deployment configuration for account scope in outgoing cloud management requests.",
|
|
4446
4480
|
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
|
|
4481
|
+
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
4482
|
category: "correctness",
|
|
4449
4483
|
defaultLevel: "warning",
|
|
4450
4484
|
autofix: "none",
|
|
@@ -4465,22 +4499,30 @@ var PREFER_CONFIGURED_CLOUD_SCOPE_DOCUMENTATION = {
|
|
|
4465
4499
|
id: "literal-account-scope",
|
|
4466
4500
|
title: "Move outgoing account scope into deployment configuration",
|
|
4467
4501
|
outcome: "match",
|
|
4468
|
-
files: [
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4502
|
+
files: [
|
|
4503
|
+
{
|
|
4504
|
+
path: ".github/scripts/publish.mjs",
|
|
4505
|
+
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}`); }"
|
|
4506
|
+
}
|
|
4507
|
+
],
|
|
4472
4508
|
focusPath: ".github/scripts/publish.mjs",
|
|
4473
4509
|
expectedCount: 1,
|
|
4474
4510
|
public: true
|
|
4475
4511
|
},
|
|
4476
4512
|
{
|
|
4477
4513
|
id: "validated-account-scope",
|
|
4478
|
-
title: "
|
|
4514
|
+
title: "Parse one shared settings object with Zod before requests",
|
|
4479
4515
|
outcome: "no-match",
|
|
4480
|
-
files: [
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4516
|
+
files: [
|
|
4517
|
+
{
|
|
4518
|
+
path: ".github/scripts/publish.mjs",
|
|
4519
|
+
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`); }"
|
|
4520
|
+
},
|
|
4521
|
+
{
|
|
4522
|
+
path: "src/env.mjs",
|
|
4523
|
+
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);"
|
|
4524
|
+
}
|
|
4525
|
+
],
|
|
4484
4526
|
focusPath: ".github/scripts/publish.mjs",
|
|
4485
4527
|
expectedCount: 0,
|
|
4486
4528
|
public: true
|
|
@@ -4527,11 +4569,11 @@ var PREFER_DISCRIMINATED_UNION_DOCUMENTATION = {
|
|
|
4527
4569
|
category: "correctness",
|
|
4528
4570
|
autofix: "safe",
|
|
4529
4571
|
limitations: [
|
|
4530
|
-
"Names, Boolean counts and optional-field counts never establish a finding. A producer's
|
|
4531
|
-
"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
|
|
4532
|
-
"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.",
|
|
4572
|
+
"Names, Boolean counts and optional-field counts never establish a finding. A producer's 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. Boolean members may use boolean, true | false or one unmerged nongeneric local alias of either. All four combinations, constant dimensions, optional/unknown 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. Independently nullable payloads do not erase Boolean evidence.",
|
|
4573
|
+
"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, one local alias of a literal tag domain, and the built-in Promise around async returns. Callable or opaque own then properties abstain for async producers because Promise assimilation can replace the fulfilled value. Inherited then methods and external prototype mutation are not modeled. This is syntax-only evidence of field presence; explicit undefined, void and null payloads do not establish payload requirements, while expression value types are not inferred. Type-only assertion/satisfies/non-null wrappers, literal computed string keys and a sole-use const immediately before its return are recognized. Const alias chains, observation, mutation and dynamic eval abstain. Dynamic returns, spreads, dynamic computed keys, accessors, loops and try statements are deliberately skipped.",
|
|
4574
|
+
"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, dynamic eval 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.",
|
|
4533
4575
|
"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.",
|
|
4534
|
-
"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.
|
|
4576
|
+
"Automatic fixes are limited to an unused private nongeneric synchronous function with a direct inline contract and one conditional return containing at most four bare literal objects. Boolean-pair fixes narrow only the implicated pair, retaining every unrelated member's type, readonly modifier and optionality. Tag/payload fixes express the produced state-specific payload presence while preserving declared payload value types. All runtime code, condition evaluation, object creation, identity and exceptions remain unchanged. Nested conditionals are supported. Assertion/satisfies wrappers, 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."
|
|
4535
4577
|
],
|
|
4536
4578
|
references: ["https://functional-architecture.org/make_illegal_states_unrepresentable/", "https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions"],
|
|
4537
4579
|
examples: [
|
|
@@ -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
|
}
|
|
@@ -22035,7 +22088,7 @@ var prefer_configured_cloud_scope_default = defineRule({
|
|
|
22035
22088
|
},
|
|
22036
22089
|
schema: [],
|
|
22037
22090
|
messages: {
|
|
22038
|
-
configuredScope: "This outgoing Cloudflare management request uses a source-literal account scope.
|
|
22091
|
+
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
22092
|
}
|
|
22040
22093
|
},
|
|
22041
22094
|
createOnce(context) {
|
|
@@ -22062,14 +22115,16 @@ var prefer_configured_cloud_scope_default = defineRule({
|
|
|
22062
22115
|
if (callee.type !== "Identifier") return false;
|
|
22063
22116
|
const binding3 = bindingOf(callee);
|
|
22064
22117
|
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))
|
|
22118
|
+
if (seen.has(binding3) || seen.size >= 16 || binding3.defs.length !== 1 || binding3.references.some((ref) => ref.isWrite() && ref.init !== true))
|
|
22119
|
+
return false;
|
|
22066
22120
|
const next = /* @__PURE__ */ new Set([...seen, binding3]);
|
|
22067
22121
|
const [definition] = binding3.defs;
|
|
22068
22122
|
if (definition?.type === "Parameter") {
|
|
22069
22123
|
const parameter = parentOf(definition.name);
|
|
22070
22124
|
return parameter?.type === "AssignmentPattern" && parameter.left === definition.name && outgoingFetch(parameter.right, next);
|
|
22071
22125
|
}
|
|
22072
|
-
if (definition?.type !== "Variable" || definition.parent?.type !== "VariableDeclaration" || definition.parent.kind !== "const")
|
|
22126
|
+
if (definition?.type !== "Variable" || definition.parent?.type !== "VariableDeclaration" || definition.parent.kind !== "const")
|
|
22127
|
+
return false;
|
|
22073
22128
|
const declarator = definition.node;
|
|
22074
22129
|
return declarator.id.type === "Identifier" && declarator.init !== null && outgoingFetch(declarator.init, next);
|
|
22075
22130
|
}
|
|
@@ -22090,10 +22145,15 @@ var prefer_configured_cloud_scope_default = defineRule({
|
|
|
22090
22145
|
}
|
|
22091
22146
|
return;
|
|
22092
22147
|
case "ObjectPattern":
|
|
22093
|
-
for (const property of value.properties)
|
|
22148
|
+
for (const property of value.properties)
|
|
22149
|
+
checkWrite(
|
|
22150
|
+
property.type === "RestElement" ? property.argument : property.value,
|
|
22151
|
+
depth + 1
|
|
22152
|
+
);
|
|
22094
22153
|
return;
|
|
22095
22154
|
case "ArrayPattern":
|
|
22096
|
-
for (const element of value.elements)
|
|
22155
|
+
for (const element of value.elements)
|
|
22156
|
+
if (element !== null) checkWrite(element, depth + 1);
|
|
22097
22157
|
return;
|
|
22098
22158
|
case "AssignmentPattern":
|
|
22099
22159
|
checkWrite(value.left, depth + 1);
|
|
@@ -22111,15 +22171,20 @@ var prefer_configured_cloud_scope_default = defineRule({
|
|
|
22111
22171
|
}
|
|
22112
22172
|
if (value.type === "Identifier") {
|
|
22113
22173
|
const binding3 = bindingOf(value);
|
|
22114
|
-
if (binding3 === null || seen.has(binding3) || binding3.defs.length !== 1 || binding3.references.some((ref) => ref.isWrite() && ref.init !== true))
|
|
22174
|
+
if (binding3 === null || seen.has(binding3) || binding3.defs.length !== 1 || binding3.references.some((ref) => ref.isWrite() && ref.init !== true))
|
|
22175
|
+
return UNKNOWN;
|
|
22115
22176
|
const [definition] = binding3.defs;
|
|
22116
|
-
if (definition?.type !== "Variable" || definition.parent?.type !== "VariableDeclaration" || definition.parent.kind !== "const")
|
|
22177
|
+
if (definition?.type !== "Variable" || definition.parent?.type !== "VariableDeclaration" || definition.parent.kind !== "const")
|
|
22178
|
+
return UNKNOWN;
|
|
22117
22179
|
const declarator = definition.node;
|
|
22118
22180
|
if (declarator.id.type !== "Identifier" || declarator.init === null) return UNKNOWN;
|
|
22119
22181
|
return urlParts(declarator.init, /* @__PURE__ */ new Set([...seen, binding3]), depth + 1);
|
|
22120
22182
|
}
|
|
22121
22183
|
if (value.type === "BinaryExpression" && value.operator === "+") {
|
|
22122
|
-
return boundedParts([
|
|
22184
|
+
return boundedParts([
|
|
22185
|
+
...urlParts(value.left, seen, depth + 1),
|
|
22186
|
+
...urlParts(value.right, seen, depth + 1)
|
|
22187
|
+
]);
|
|
22123
22188
|
}
|
|
22124
22189
|
if (value.type === "TemplateLiteral") return templateParts(value, seen, depth);
|
|
22125
22190
|
return UNKNOWN;
|
|
@@ -22449,14 +22514,19 @@ function discriminatedUnionReturns(node, sourceCode2) {
|
|
|
22449
22514
|
if (shape === void 0) return void 0;
|
|
22450
22515
|
const returned2 = returnedProperties(paths.objects, shape);
|
|
22451
22516
|
if (returned2 === void 0) return void 0;
|
|
22517
|
+
if (node.async && returned2.some((properties2) => {
|
|
22518
|
+
const then = properties2.get("then");
|
|
22519
|
+
return then !== void 0 && (then.type !== "Literal" || typeof then.value === "object" && then.value !== null);
|
|
22520
|
+
})) return void 0;
|
|
22521
|
+
const emptyPayload = returned2.some((properties2) => hasEmptyPayload(properties2, shape.optional));
|
|
22452
22522
|
for (const [name, member3] of shape.members) {
|
|
22453
|
-
if (member3.optional) continue;
|
|
22454
|
-
const domain = tagDomain(member3.typeAnnotation?.typeAnnotation);
|
|
22523
|
+
if (member3.optional || emptyPayload) continue;
|
|
22524
|
+
const domain = tagDomain(member3.typeAnnotation?.typeAnnotation, sourceCode2);
|
|
22455
22525
|
if (domain !== void 0 && hasTagSpecificPayload(name, domain, shape.optional, returned2)) {
|
|
22456
22526
|
return { reportNode: contract.reportNode, relationship: { kind: "presence", tag: name }, members: shape.members, returned: returned2 };
|
|
22457
22527
|
}
|
|
22458
22528
|
}
|
|
22459
|
-
const fields2 = correlatedBooleanFields(shape, returned2);
|
|
22529
|
+
const fields2 = correlatedBooleanFields(shape, returned2, sourceCode2);
|
|
22460
22530
|
return fields2 === void 0 ? void 0 : {
|
|
22461
22531
|
reportNode: contract.reportNode,
|
|
22462
22532
|
relationship: { kind: "boolean-pair", fields: fields2 },
|
|
@@ -22464,8 +22534,8 @@ function discriminatedUnionReturns(node, sourceCode2) {
|
|
|
22464
22534
|
returned: returned2
|
|
22465
22535
|
};
|
|
22466
22536
|
}
|
|
22467
|
-
function correlatedBooleanFields(shape, returned2) {
|
|
22468
|
-
const fields2 = [...shape.members].filter(([, member3]) => !member3.optional && member3.typeAnnotation?.typeAnnotation
|
|
22537
|
+
function correlatedBooleanFields(shape, returned2, sourceCode2) {
|
|
22538
|
+
const fields2 = [...shape.members].filter(([, member3]) => !member3.optional && booleanType(member3.typeAnnotation?.typeAnnotation, sourceCode2)).map(([name]) => name);
|
|
22469
22539
|
if (fields2.length > 8) return void 0;
|
|
22470
22540
|
for (const [index, left] of fields2.entries()) {
|
|
22471
22541
|
for (const right of fields2.slice(index + 1)) {
|
|
@@ -22475,6 +22545,10 @@ function correlatedBooleanFields(shape, returned2) {
|
|
|
22475
22545
|
}
|
|
22476
22546
|
return void 0;
|
|
22477
22547
|
}
|
|
22548
|
+
function booleanType(type, sourceCode2) {
|
|
22549
|
+
const domain = tagDomain(type, sourceCode2);
|
|
22550
|
+
return domain?.size === 2 && domain.has(true) && domain.has(false);
|
|
22551
|
+
}
|
|
22478
22552
|
function booleanCombinations(left, right, returned2) {
|
|
22479
22553
|
const combinations = /* @__PURE__ */ new Set();
|
|
22480
22554
|
for (const properties2 of returned2) {
|
|
@@ -22514,15 +22588,7 @@ function returnContract(node, sourceCode2) {
|
|
|
22514
22588
|
}
|
|
22515
22589
|
function definitions(reference, sourceCode2) {
|
|
22516
22590
|
if (reference.typeName.type !== "Identifier") return [];
|
|
22517
|
-
|
|
22518
|
-
while (scope !== null) {
|
|
22519
|
-
const found = scope.set.get(reference.typeName.name);
|
|
22520
|
-
const binding3 = found === void 0 ? void 0 : tsVariable(found);
|
|
22521
|
-
const typeDefinitions2 = binding3?.defs.filter((definition) => definition.isTypeDefinition) ?? [];
|
|
22522
|
-
if (typeDefinitions2.length > 0 || binding3?.isTypeVariable === true) return typeDefinitions2;
|
|
22523
|
-
scope = scope.upper;
|
|
22524
|
-
}
|
|
22525
|
-
return [];
|
|
22591
|
+
return typeBinding(sourceCode2, reference.typeName)?.defs.filter((definition) => definition.isTypeDefinition) ?? [];
|
|
22526
22592
|
}
|
|
22527
22593
|
function objectContract(elements) {
|
|
22528
22594
|
const members2 = /* @__PURE__ */ new Map();
|
|
@@ -22543,7 +22609,6 @@ function returnedProperties(objects, contract) {
|
|
|
22543
22609
|
if (properties2 === void 0) return void 0;
|
|
22544
22610
|
if ([...properties2.keys()].some((name) => !contract.members.has(name))) return void 0;
|
|
22545
22611
|
if ([...contract.members].some(([name, member3]) => !member3.optional && !properties2.has(name))) return void 0;
|
|
22546
|
-
if (hasEmptyPayload(properties2, contract.optional)) return void 0;
|
|
22547
22612
|
returned2.push(properties2);
|
|
22548
22613
|
}
|
|
22549
22614
|
return returned2;
|
|
@@ -22568,7 +22633,13 @@ function hasEmptyPayload(properties2, optional) {
|
|
|
22568
22633
|
}
|
|
22569
22634
|
return false;
|
|
22570
22635
|
}
|
|
22571
|
-
function tagDomain(node) {
|
|
22636
|
+
function tagDomain(node, sourceCode2) {
|
|
22637
|
+
if (node?.type === "TSTypeReference" && !isPresent(node.typeArguments)) {
|
|
22638
|
+
const bindings = definitions(node, sourceCode2);
|
|
22639
|
+
const declaration = bindings.length === 1 ? bindings[0]?.node : void 0;
|
|
22640
|
+
if (declaration?.type !== "TSTypeAliasDeclaration" || isPresent(declaration.typeParameters)) return void 0;
|
|
22641
|
+
node = declaration.typeAnnotation;
|
|
22642
|
+
}
|
|
22572
22643
|
if (node?.type === "TSBooleanKeyword") return /* @__PURE__ */ new Set([true, false]);
|
|
22573
22644
|
if (node?.type !== "TSUnionType") return void 0;
|
|
22574
22645
|
const values = /* @__PURE__ */ new Set();
|
|
@@ -22648,6 +22719,7 @@ function reachableSwitch(statement, sourceCode2) {
|
|
|
22648
22719
|
return kind !== void 0 && reachableSwitchLabels(statement, kind);
|
|
22649
22720
|
}
|
|
22650
22721
|
function broadSwitchInput(statement, sourceCode2) {
|
|
22722
|
+
if (hasIdentifierNamed(sourceCode2, "eval")) return void 0;
|
|
22651
22723
|
const body3 = statement.parent;
|
|
22652
22724
|
if (body3.type !== "BlockStatement" || body3.body.length !== 1) return void 0;
|
|
22653
22725
|
const owner = body3.parent;
|
|
@@ -22699,6 +22771,13 @@ function statementsPaths(statements, budget, depth) {
|
|
|
22699
22771
|
}
|
|
22700
22772
|
function returnedObjects(expression, budget, depth) {
|
|
22701
22773
|
if (!budget.within(depth)) return void 0;
|
|
22774
|
+
if (isErasedExpression(expression)) {
|
|
22775
|
+
return returnedObjects(expression.expression, budget, depth + 1);
|
|
22776
|
+
}
|
|
22777
|
+
if (expression.type === "Identifier") {
|
|
22778
|
+
const initializer = closedReturnInitializer(expression, budget.sourceCode);
|
|
22779
|
+
return initializer === void 0 ? void 0 : returnedObjects(initializer, budget, depth + 1);
|
|
22780
|
+
}
|
|
22702
22781
|
if (expression.type === "ObjectExpression") {
|
|
22703
22782
|
return budget.recordObject() ? [expression] : void 0;
|
|
22704
22783
|
}
|
|
@@ -22707,10 +22786,21 @@ function returnedObjects(expression, budget, depth) {
|
|
|
22707
22786
|
const alternate = returnedObjects(expression.alternate, budget, depth + 1);
|
|
22708
22787
|
return consequent === void 0 || alternate === void 0 ? void 0 : [...consequent, ...alternate];
|
|
22709
22788
|
}
|
|
22789
|
+
function closedReturnInitializer(identifier, sourceCode2) {
|
|
22790
|
+
const returned2 = identifier.parent;
|
|
22791
|
+
if (returned2.type !== "ReturnStatement" || returned2.argument !== identifier || returned2.parent.type !== "BlockStatement" || hasIdentifierNamed(sourceCode2, "eval")) return void 0;
|
|
22792
|
+
const block = returned2.parent;
|
|
22793
|
+
const declaration = block.body[block.body.indexOf(returned2) - 1];
|
|
22794
|
+
if (declaration?.type !== "VariableDeclaration" || declaration.kind !== "const" || declaration.declarations.length !== 1) return void 0;
|
|
22795
|
+
const value = declaration.declarations[0];
|
|
22796
|
+
if (value?.id.type !== "Identifier" || value.id.name !== identifier.name || value.init === null) return void 0;
|
|
22797
|
+
const binding3 = findVariable(sourceCode2.getScope(identifier), identifier.name);
|
|
22798
|
+
if (binding3?.defs.length !== 1 || binding3.defs[0]?.node !== value || binding3.eslintUsed || !binding3.references.some((reference) => reference.identifier === identifier && reference.isRead()) || binding3.references.some((reference) => reference.isWrite() ? reference.init !== true : reference.identifier !== identifier)) return void 0;
|
|
22799
|
+
return value.init;
|
|
22800
|
+
}
|
|
22710
22801
|
function propertyName(node) {
|
|
22711
|
-
if (node.computed) return void 0;
|
|
22712
|
-
if (node.key.type === "Identifier") return node.key.name;
|
|
22713
22802
|
if (node.key.type === "Literal" && typeof node.key.value === "string") return node.key.value;
|
|
22803
|
+
if (!node.computed && node.key.type === "Identifier") return node.key.name;
|
|
22714
22804
|
return void 0;
|
|
22715
22805
|
}
|
|
22716
22806
|
function literalTag(node) {
|
|
@@ -22781,19 +22871,27 @@ function returnsOnlyConditionalObject(fn) {
|
|
|
22781
22871
|
if (fn.async || fn.generator || isPresent(fn.typeParameters)) return false;
|
|
22782
22872
|
const statements = fn.body?.body ?? [];
|
|
22783
22873
|
const [returned2] = statements;
|
|
22784
|
-
return statements.length === 1 && returned2?.type === "ReturnStatement" && returned2.argument?.type === "ConditionalExpression";
|
|
22874
|
+
return statements.length === 1 && returned2?.type === "ReturnStatement" && returned2.argument?.type === "ConditionalExpression" && bareConditionalObjects(returned2.argument);
|
|
22875
|
+
}
|
|
22876
|
+
function bareConditionalObjects(expression) {
|
|
22877
|
+
return expression.type === "ObjectExpression" || expression.type === "ConditionalExpression" && bareConditionalObjects(expression.consequent) && bareConditionalObjects(expression.alternate);
|
|
22785
22878
|
}
|
|
22786
22879
|
function privateVariantType(properties2, result, sourceCode2) {
|
|
22787
22880
|
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;
|
|
22788
22881
|
const fields2 = [];
|
|
22789
22882
|
for (const [name, member3] of result.members) {
|
|
22790
22883
|
const value = properties2.get(name);
|
|
22791
|
-
if (value === void 0) continue;
|
|
22884
|
+
if (value === void 0 && result.relationship.kind === "presence") continue;
|
|
22792
22885
|
const annotation = member3.typeAnnotation?.typeAnnotation;
|
|
22793
22886
|
if (annotation === void 0) return null;
|
|
22794
22887
|
const narrow = result.relationship.kind === "presence" ? name === result.relationship.tag : name === result.relationship.fields.left || name === result.relationship.fields.right;
|
|
22795
|
-
|
|
22796
|
-
|
|
22888
|
+
let type = sourceCode2.getText(annotation);
|
|
22889
|
+
if (narrow) {
|
|
22890
|
+
if (value === void 0) return null;
|
|
22891
|
+
type = sourceCode2.getText(value);
|
|
22892
|
+
}
|
|
22893
|
+
const optional = result.relationship.kind === "boolean-pair" && !narrow && member3.optional;
|
|
22894
|
+
fields2.push(`${member3.readonly ? "readonly " : ""}${sourceCode2.getText(member3.key)}${optional ? "?" : ""}: ${type}`);
|
|
22797
22895
|
}
|
|
22798
22896
|
return `{ ${fields2.join("; ")} }`;
|
|
22799
22897
|
}
|
package/dist/index.js
CHANGED