@sarj/eslint-plugin 15.17.1 → 15.17.3
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/index.cjs +879 -220
- package/dist/index.d.cts +14 -5
- package/dist/index.d.ts +14 -5
- package/dist/index.js +936 -271
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -5849,7 +5849,7 @@ var NO_RAW_FETCH_OUTSIDE_CLIENTS_DOCUMENTATION = {
|
|
|
5849
5849
|
rationale: "Scattered fetch calls bypass shared transport policy and are harder to stub and observe consistently.",
|
|
5850
5850
|
remediation: "Move the request into a client module and call that abstraction from application code.",
|
|
5851
5851
|
category: "architecture",
|
|
5852
|
-
limitations: ["Tests, client-layer paths, constructed handoffs, and pre-signed URL transfers are excluded."],
|
|
5852
|
+
limitations: ["Tests, client-layer paths, constructed handoffs, and pre-signed URL transfers are excluded. Configure the same literal Next.js basePath here and on prefer-server-actions so one rule owns each internal mutation."],
|
|
5853
5853
|
examples: [
|
|
5854
5854
|
{ id: "client-call", title: "Use a client abstraction", outcome: "no-match", files: [{ path: "src/routes/handler.ts", source: "const response = await billingClient.getInvoice(id);" }], focusPath: "src/routes/handler.ts", expectedCount: 0, public: true },
|
|
5855
5855
|
{ id: "raw-fetch", title: "Do not call global fetch here", outcome: "match", files: [{ path: "src/routes/handler.ts", source: "const response = await fetch('/api/invoices');" }], focusPath: "src/routes/handler.ts", expectedCount: 1, public: true }
|
|
@@ -5894,7 +5894,10 @@ var ANALYTICS_SEGMENTS2 = /* @__PURE__ */ new Set([
|
|
|
5894
5894
|
]);
|
|
5895
5895
|
var SERVER_ACTION_SKIP_FILE_RE = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|-(?:test|spec)\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/__testfixtures__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
|
|
5896
5896
|
var NON_REACT_FRAMEWORK_RE = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
|
|
5897
|
-
var
|
|
5897
|
+
var BASE_PATH_RE = /^\/(?!$)(?!.*[?#])(?:[^/]+\/)*[^/]+$/u;
|
|
5898
|
+
function isValidBasePath(basePath) {
|
|
5899
|
+
return BASE_PATH_RE.test(basePath) && !basePath.split("/").some((segment) => segment === "." || segment === "..");
|
|
5900
|
+
}
|
|
5898
5901
|
function isGlobalFetchCall(node, resolvesToGlobal) {
|
|
5899
5902
|
const callee = node.callee;
|
|
5900
5903
|
if (callee.type === "Identifier") {
|
|
@@ -5990,6 +5993,10 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
5990
5993
|
type: "array",
|
|
5991
5994
|
items: { type: "string" },
|
|
5992
5995
|
description: "Regular-expression sources matched against the filename. Replaces the defaults."
|
|
5996
|
+
},
|
|
5997
|
+
basePath: {
|
|
5998
|
+
type: "string",
|
|
5999
|
+
pattern: "^/(?!$)(?!.*[?#])(?!(?:.*/)?\\.\\.?(?:/|$))(?:[^/]+/)*[^/]+$"
|
|
5993
6000
|
}
|
|
5994
6001
|
},
|
|
5995
6002
|
additionalProperties: false
|
|
@@ -6011,12 +6018,18 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
6011
6018
|
(statement) => statement.type === AST_NODE_TYPES24.ImportDeclaration && typeof statement.source.value === "string" && NON_REACT_FRAMEWORK_RE.test(statement.source.value)
|
|
6012
6019
|
);
|
|
6013
6020
|
const hasUseClientDirective = context.sourceCode.ast.body.some(
|
|
6014
|
-
(statement) => statement.type === AST_NODE_TYPES24.ExpressionStatement && statement.
|
|
6021
|
+
(statement) => statement.type === AST_NODE_TYPES24.ExpressionStatement && statement.directive === "use client"
|
|
6015
6022
|
);
|
|
6016
|
-
const
|
|
6017
|
-
(statement) => statement.type === AST_NODE_TYPES24.
|
|
6023
|
+
const hasUseServerDirective = context.sourceCode.ast.body.some(
|
|
6024
|
+
(statement) => statement.type === AST_NODE_TYPES24.ExpressionStatement && statement.directive === "use server"
|
|
6018
6025
|
);
|
|
6019
|
-
const
|
|
6026
|
+
const importsServerOnly = context.sourceCode.ast.body.some(
|
|
6027
|
+
(statement) => statement.type === AST_NODE_TYPES24.ImportDeclaration && typeof statement.source.value === "string" && (statement.source.value === "server-only" || statement.source.value === "next/server")
|
|
6028
|
+
);
|
|
6029
|
+
const internalApiPrefixes = ["/api"];
|
|
6030
|
+
if (options?.basePath !== void 0 && isValidBasePath(options.basePath)) {
|
|
6031
|
+
internalApiPrefixes.push(`${options.basePath}/api`);
|
|
6032
|
+
}
|
|
6020
6033
|
function resolvesToGlobal(identifier) {
|
|
6021
6034
|
const variable = ASTUtils7.findVariable(
|
|
6022
6035
|
context.sourceCode.getScope(identifier),
|
|
@@ -6049,15 +6062,15 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
6049
6062
|
function isInternalApiUrl(node) {
|
|
6050
6063
|
const resolved = resolveNode2(node ?? void 0);
|
|
6051
6064
|
if (resolved?.type === AST_NODE_TYPES24.Literal) {
|
|
6052
|
-
return typeof resolved.value === "string" &&
|
|
6065
|
+
return typeof resolved.value === "string" && internalApiPrefixes.some(
|
|
6066
|
+
(prefix) => resolved.value === prefix || resolved.value.startsWith(`${prefix}/`)
|
|
6067
|
+
);
|
|
6053
6068
|
}
|
|
6054
6069
|
if (resolved?.type === AST_NODE_TYPES24.TemplateLiteral) {
|
|
6055
6070
|
const prefix = resolved.quasis[0]?.value.cooked;
|
|
6056
|
-
return typeof prefix === "string" &&
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
const first = resolved.arguments[0];
|
|
6060
|
-
return first !== void 0 && first.type !== AST_NODE_TYPES24.SpreadElement ? isInternalApiUrl(first) : false;
|
|
6071
|
+
return typeof prefix === "string" && internalApiPrefixes.some(
|
|
6072
|
+
(apiPrefix) => prefix === apiPrefix || prefix.startsWith(`${apiPrefix}/`)
|
|
6073
|
+
);
|
|
6061
6074
|
}
|
|
6062
6075
|
return resolved?.type === AST_NODE_TYPES24.BinaryExpression && resolved.operator === "+" && isInternalApiUrl(resolved.left);
|
|
6063
6076
|
}
|
|
@@ -6077,7 +6090,7 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
6077
6090
|
return resolved?.type === AST_NODE_TYPES24.LogicalExpression && resolved.operator === "||" && (isMutationMethod2(resolved.left) || isMutationMethod2(resolved.right));
|
|
6078
6091
|
}
|
|
6079
6092
|
function serverActionOwns(node) {
|
|
6080
|
-
if (node.callee.type !== AST_NODE_TYPES24.Identifier || !
|
|
6093
|
+
if (node.callee.type !== AST_NODE_TYPES24.Identifier || !hasUseClientDirective || hasUseServerDirective || importsServerOnly || SERVER_ACTION_SKIP_FILE_RE.test(filename) || nonReactFramework) {
|
|
6081
6094
|
return false;
|
|
6082
6095
|
}
|
|
6083
6096
|
const url = node.arguments[0];
|
|
@@ -9737,25 +9750,25 @@ var no_unsafe_mock_casting_default = createRule({
|
|
|
9737
9750
|
}
|
|
9738
9751
|
const directBindings = /* @__PURE__ */ new Set();
|
|
9739
9752
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
9740
|
-
function
|
|
9753
|
+
function resolve2(identifier) {
|
|
9741
9754
|
return ASTUtils10.findVariable(
|
|
9742
9755
|
context.sourceCode.getScope(identifier),
|
|
9743
9756
|
identifier.name
|
|
9744
9757
|
);
|
|
9745
9758
|
}
|
|
9746
9759
|
function record(identifier, destination) {
|
|
9747
|
-
const binding =
|
|
9760
|
+
const binding = resolve2(identifier);
|
|
9748
9761
|
if (binding !== null) destination.add(binding);
|
|
9749
9762
|
}
|
|
9750
9763
|
function isMockTypeReference(node) {
|
|
9751
9764
|
if (node.type !== AST_NODE_TYPES40.TSTypeReference) return false;
|
|
9752
9765
|
const typeName = node.typeName;
|
|
9753
9766
|
if (typeName.type === AST_NODE_TYPES40.Identifier) {
|
|
9754
|
-
const binding =
|
|
9767
|
+
const binding = resolve2(typeName);
|
|
9755
9768
|
return binding !== null && directBindings.has(binding);
|
|
9756
9769
|
}
|
|
9757
9770
|
if (typeName.type === AST_NODE_TYPES40.TSQualifiedName && typeName.left.type === AST_NODE_TYPES40.Identifier && MOCK_TYPE_NAMES.has(typeName.right.name)) {
|
|
9758
|
-
const binding =
|
|
9771
|
+
const binding = resolve2(typeName.left);
|
|
9759
9772
|
return binding !== null && namespaceBindings.has(binding);
|
|
9760
9773
|
}
|
|
9761
9774
|
return false;
|
|
@@ -11169,6 +11182,10 @@ function unwrapTransparentExport(node) {
|
|
|
11169
11182
|
|
|
11170
11183
|
// src/rules/prefer-shadcn-primitives.ts
|
|
11171
11184
|
import { AST_NODE_TYPES as AST_NODE_TYPES50 } from "@typescript-eslint/utils";
|
|
11185
|
+
import { parse as parseTypeScript } from "@typescript-eslint/typescript-estree";
|
|
11186
|
+
import { existsSync, lstatSync, readFileSync, realpathSync } from "fs";
|
|
11187
|
+
import { dirname, isAbsolute, join, parse as parsePath, relative, resolve, sep } from "path";
|
|
11188
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
11172
11189
|
var PREFER_SHADCN_PRIMITIVES_DOCUMENTATION = {
|
|
11173
11190
|
summary: "Require visible raw JSX controls to use the corresponding shared shadcn primitive.",
|
|
11174
11191
|
rationale: "Shared primitives centralize interaction, accessibility, and visual behavior across the product.",
|
|
@@ -11176,22 +11193,35 @@ var PREFER_SHADCN_PRIMITIVES_DOCUMENTATION = {
|
|
|
11176
11193
|
category: "style",
|
|
11177
11194
|
limitations: [
|
|
11178
11195
|
"Hidden and file inputs, unassociated labels, and non-control semantic elements are excluded.",
|
|
11179
|
-
"Tests and the shared components/ui primitive implementation tree are excluded."
|
|
11196
|
+
"Tests and the shared components/ui primitive implementation tree are excluded.",
|
|
11197
|
+
"Package-local project detection is opt-in and fails closed unless components.json, one unambiguous tsconfig/jsconfig alias, the exact primitive module, and its expected export all exist."
|
|
11180
11198
|
],
|
|
11181
11199
|
examples: [
|
|
11182
11200
|
{ id: "shared-button", title: "Use a shared button", outcome: "no-match", files: [{ path: "src/form.tsx", source: "import { Button } from '@/components/ui/button'; const action = <Button>Save</Button>;" }], focusPath: "src/form.tsx", expectedCount: 0, public: true },
|
|
11183
11201
|
{ id: "raw-button", title: "Do not use a raw button", outcome: "match", files: [{ path: "src/form.tsx", source: "import { Card } from '@/components/ui/card'; const action = <button>Save</button>;" }], focusPath: "src/form.tsx", expectedCount: 1, public: true }
|
|
11184
11202
|
]
|
|
11185
11203
|
};
|
|
11186
|
-
var
|
|
11187
|
-
button: "Button",
|
|
11188
|
-
dialog: "Dialog or AlertDialog family",
|
|
11189
|
-
input: "Input",
|
|
11190
|
-
label: "Label",
|
|
11191
|
-
progress: "Progress",
|
|
11192
|
-
select: "Select family",
|
|
11193
|
-
table: "Table family",
|
|
11194
|
-
textarea: "Textarea"
|
|
11204
|
+
var RAW_PRIMITIVES = {
|
|
11205
|
+
button: { capability: "Button", replacement: "Button" },
|
|
11206
|
+
dialog: { capability: "Dialog", replacement: "Dialog or AlertDialog family" },
|
|
11207
|
+
input: { capability: "Input", replacement: "Input" },
|
|
11208
|
+
label: { capability: "Label", replacement: "Label" },
|
|
11209
|
+
progress: { capability: "Progress", replacement: "Progress" },
|
|
11210
|
+
select: { capability: "Select", replacement: "Select family" },
|
|
11211
|
+
table: { capability: "Table", replacement: "Table family" },
|
|
11212
|
+
textarea: { capability: "Textarea", replacement: "Textarea" }
|
|
11213
|
+
};
|
|
11214
|
+
var CAPABILITIES = {
|
|
11215
|
+
Button: "button",
|
|
11216
|
+
Checkbox: "checkbox",
|
|
11217
|
+
Dialog: "dialog",
|
|
11218
|
+
Input: "input",
|
|
11219
|
+
Label: "label",
|
|
11220
|
+
Progress: "progress",
|
|
11221
|
+
RadioGroup: "radio-group",
|
|
11222
|
+
Select: "select",
|
|
11223
|
+
Table: "table",
|
|
11224
|
+
Textarea: "textarea"
|
|
11195
11225
|
};
|
|
11196
11226
|
var LABELABLE_ELEMENTS = /* @__PURE__ */ new Set([
|
|
11197
11227
|
"button",
|
|
@@ -11203,7 +11233,7 @@ var LABELABLE_ELEMENTS = /* @__PURE__ */ new Set([
|
|
|
11203
11233
|
"textarea"
|
|
11204
11234
|
]);
|
|
11205
11235
|
var SHARED_PRIMITIVE_IMPLEMENTATION_RE = /(?:^|\/)components\/ui(?:\/|$)/i;
|
|
11206
|
-
var SHARED_PRIMITIVE_IMPORT_RE = /(?:^|\/)components\/ui\/[^/]
|
|
11236
|
+
var SHARED_PRIMITIVE_IMPORT_RE = /(?:^|\/)components\/ui\/([^/]+)$/i;
|
|
11207
11237
|
var AMBIGUOUS_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
11208
11238
|
"button",
|
|
11209
11239
|
"color",
|
|
@@ -11212,10 +11242,175 @@ var AMBIGUOUS_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
|
11212
11242
|
"reset",
|
|
11213
11243
|
"submit"
|
|
11214
11244
|
]);
|
|
11245
|
+
var MAX_PROJECT_FILE_BYTES = 1048576;
|
|
11246
|
+
var PROJECT_PRIMITIVES_CACHE = /* @__PURE__ */ new Map();
|
|
11247
|
+
function detectProjectPrimitives(filename, requested) {
|
|
11248
|
+
const packageRoot = findPackageRoot(dirname(filename));
|
|
11249
|
+
if (packageRoot === null) return { available: /* @__PURE__ */ new Set(), uiRoot: null };
|
|
11250
|
+
const manifest = readJsonc(join(packageRoot, "components.json"));
|
|
11251
|
+
const alias = stringProperty(manifest, "aliases", "ui");
|
|
11252
|
+
const unresolvedUiRoot = alias === null ? null : resolveAlias(packageRoot, alias);
|
|
11253
|
+
if (unresolvedUiRoot === null) return { available: /* @__PURE__ */ new Set(), uiRoot: null };
|
|
11254
|
+
const uiRoot = safeContainedDirectory(packageRoot, unresolvedUiRoot);
|
|
11255
|
+
if (uiRoot === null) {
|
|
11256
|
+
return { available: /* @__PURE__ */ new Set(), uiRoot: null };
|
|
11257
|
+
}
|
|
11258
|
+
const moduleCandidates = /* @__PURE__ */ new Map();
|
|
11259
|
+
for (const [capability, moduleName] of Object.entries(CAPABILITIES)) {
|
|
11260
|
+
if (!requested.has(capability)) continue;
|
|
11261
|
+
moduleCandidates.set(capability, ["tsx", "ts", "jsx", "js"].flatMap((extension) => [
|
|
11262
|
+
join(uiRoot, `${moduleName}.${extension}`),
|
|
11263
|
+
join(uiRoot, moduleName, `index.${extension}`)
|
|
11264
|
+
]));
|
|
11265
|
+
}
|
|
11266
|
+
const fingerprint = [
|
|
11267
|
+
join(packageRoot, "components.json"),
|
|
11268
|
+
join(packageRoot, "tsconfig.json"),
|
|
11269
|
+
join(packageRoot, "jsconfig.json"),
|
|
11270
|
+
...[...moduleCandidates.values()].flat()
|
|
11271
|
+
].map(fileFingerprint).join("|");
|
|
11272
|
+
const cacheKey = `${packageRoot}:${[...requested].sort().join(",")}`;
|
|
11273
|
+
const cached = PROJECT_PRIMITIVES_CACHE.get(cacheKey);
|
|
11274
|
+
if (cached?.fingerprint === fingerprint) return cached.value;
|
|
11275
|
+
const available = /* @__PURE__ */ new Set();
|
|
11276
|
+
for (const [capability, candidates] of moduleCandidates) {
|
|
11277
|
+
if (candidates.some(
|
|
11278
|
+
(candidate2) => exportsPrimitive(candidate2, capability)
|
|
11279
|
+
)) {
|
|
11280
|
+
available.add(capability);
|
|
11281
|
+
}
|
|
11282
|
+
}
|
|
11283
|
+
const value = { available, uiRoot };
|
|
11284
|
+
PROJECT_PRIMITIVES_CACHE.set(cacheKey, { fingerprint, value });
|
|
11285
|
+
return value;
|
|
11286
|
+
}
|
|
11287
|
+
function fileFingerprint(path) {
|
|
11288
|
+
try {
|
|
11289
|
+
const stat = lstatSync(path);
|
|
11290
|
+
return stat.isFile() ? `${path}:${stat.size}:${stat.mtimeMs}` : `${path}:excluded`;
|
|
11291
|
+
} catch {
|
|
11292
|
+
return `${path}:missing`;
|
|
11293
|
+
}
|
|
11294
|
+
}
|
|
11295
|
+
function isWithin2(root, candidate2) {
|
|
11296
|
+
const path = relative(root, candidate2);
|
|
11297
|
+
return path === "" || !path.startsWith(`..${sep}`) && path !== ".." && !isAbsolute(path);
|
|
11298
|
+
}
|
|
11299
|
+
function safeContainedDirectory(root, candidate2) {
|
|
11300
|
+
try {
|
|
11301
|
+
if (!lstatSync(candidate2).isDirectory()) return null;
|
|
11302
|
+
const realRoot = realpathSync(root);
|
|
11303
|
+
const realCandidate = realpathSync(candidate2);
|
|
11304
|
+
return isWithin2(realRoot, realCandidate) ? realCandidate : null;
|
|
11305
|
+
} catch {
|
|
11306
|
+
return null;
|
|
11307
|
+
}
|
|
11308
|
+
}
|
|
11309
|
+
function findPackageRoot(startDir) {
|
|
11310
|
+
let dir = startDir;
|
|
11311
|
+
const filesystemRoot = parsePath(dir).root;
|
|
11312
|
+
for (; ; ) {
|
|
11313
|
+
if (existsSync(join(dir, "package.json"))) return dir;
|
|
11314
|
+
const parent = dirname(dir);
|
|
11315
|
+
if (dir === filesystemRoot || parent === dir) return null;
|
|
11316
|
+
dir = parent;
|
|
11317
|
+
}
|
|
11318
|
+
}
|
|
11319
|
+
function readJsonc(path) {
|
|
11320
|
+
try {
|
|
11321
|
+
const source = readSmallRegularFile(path);
|
|
11322
|
+
if (source === null) return null;
|
|
11323
|
+
const errors = [];
|
|
11324
|
+
const value = parseJsonc(source, errors, {
|
|
11325
|
+
allowTrailingComma: true,
|
|
11326
|
+
disallowComments: false
|
|
11327
|
+
});
|
|
11328
|
+
return errors.length === 0 ? value : null;
|
|
11329
|
+
} catch {
|
|
11330
|
+
return null;
|
|
11331
|
+
}
|
|
11332
|
+
}
|
|
11333
|
+
function readSmallRegularFile(path) {
|
|
11334
|
+
try {
|
|
11335
|
+
const stat = lstatSync(path);
|
|
11336
|
+
if (!stat.isFile() || stat.size > MAX_PROJECT_FILE_BYTES) return null;
|
|
11337
|
+
return readFileSync(path, "utf8");
|
|
11338
|
+
} catch {
|
|
11339
|
+
return null;
|
|
11340
|
+
}
|
|
11341
|
+
}
|
|
11342
|
+
function stringProperty(value, ...keys) {
|
|
11343
|
+
let current = value;
|
|
11344
|
+
for (const key of keys) {
|
|
11345
|
+
if (typeof current !== "object" || current === null || !(key in current)) return null;
|
|
11346
|
+
current = current[key];
|
|
11347
|
+
}
|
|
11348
|
+
return typeof current === "string" && current.trim() !== "" ? current : null;
|
|
11349
|
+
}
|
|
11350
|
+
function resolveAlias(packageRoot, alias) {
|
|
11351
|
+
if (isAbsolute(alias)) return null;
|
|
11352
|
+
if (alias.startsWith(".")) return resolve(packageRoot, alias);
|
|
11353
|
+
const configPath = ["tsconfig.json", "jsconfig.json"].map((name) => join(packageRoot, name)).find(existsSync);
|
|
11354
|
+
if (configPath === void 0) return null;
|
|
11355
|
+
const config = readJsonc(configPath);
|
|
11356
|
+
if (typeof config !== "object" || config === null) return null;
|
|
11357
|
+
const compilerOptions = config["compilerOptions"];
|
|
11358
|
+
if (typeof compilerOptions !== "object" || compilerOptions === null) return null;
|
|
11359
|
+
const options = compilerOptions;
|
|
11360
|
+
const baseUrl = typeof options["baseUrl"] === "string" ? options["baseUrl"] : ".";
|
|
11361
|
+
const paths = options["paths"];
|
|
11362
|
+
if (typeof paths !== "object" || paths === null) return null;
|
|
11363
|
+
const matches = [];
|
|
11364
|
+
for (const [pattern, rawTargets] of Object.entries(paths)) {
|
|
11365
|
+
if (!Array.isArray(rawTargets) || rawTargets.length !== 1) {
|
|
11366
|
+
continue;
|
|
11367
|
+
}
|
|
11368
|
+
const [target] = rawTargets;
|
|
11369
|
+
if (typeof target !== "string") continue;
|
|
11370
|
+
const star = pattern.indexOf("*");
|
|
11371
|
+
if (star === -1) {
|
|
11372
|
+
if (pattern === alias) matches.push(target);
|
|
11373
|
+
continue;
|
|
11374
|
+
}
|
|
11375
|
+
const prefix = pattern.slice(0, star);
|
|
11376
|
+
const suffix = pattern.slice(star + 1);
|
|
11377
|
+
if (!alias.startsWith(prefix) || !alias.endsWith(suffix)) continue;
|
|
11378
|
+
const substitution = alias.slice(prefix.length, alias.length - suffix.length);
|
|
11379
|
+
matches.push(target.replace("*", substitution));
|
|
11380
|
+
}
|
|
11381
|
+
const [match] = matches;
|
|
11382
|
+
if (matches.length !== 1 || match === void 0) return null;
|
|
11383
|
+
return resolve(packageRoot, baseUrl, match);
|
|
11384
|
+
}
|
|
11385
|
+
function exportsPrimitive(path, exportName) {
|
|
11386
|
+
try {
|
|
11387
|
+
const source = readSmallRegularFile(path);
|
|
11388
|
+
if (source === null) return false;
|
|
11389
|
+
const program = parseTypeScript(source, { jsx: true, sourceType: "module" });
|
|
11390
|
+
return program.body.some((statement) => {
|
|
11391
|
+
if (statement.type !== AST_NODE_TYPES50.ExportNamedDeclaration) return false;
|
|
11392
|
+
if (statement.exportKind === "type") return false;
|
|
11393
|
+
if (statement.specifiers.some(
|
|
11394
|
+
(specifier) => specifier.type === AST_NODE_TYPES50.ExportSpecifier && specifier.exportKind !== "type" && specifier.exported.type === AST_NODE_TYPES50.Identifier && specifier.exported.name === exportName
|
|
11395
|
+
)) {
|
|
11396
|
+
return true;
|
|
11397
|
+
}
|
|
11398
|
+
const declaration = statement.declaration;
|
|
11399
|
+
if (declaration?.type === AST_NODE_TYPES50.VariableDeclaration) {
|
|
11400
|
+
return declaration.declarations.some(
|
|
11401
|
+
(item) => item.id.type === AST_NODE_TYPES50.Identifier && item.id.name === exportName
|
|
11402
|
+
);
|
|
11403
|
+
}
|
|
11404
|
+
return (declaration?.type === AST_NODE_TYPES50.FunctionDeclaration || declaration?.type === AST_NODE_TYPES50.ClassDeclaration) && declaration.id?.name === exportName;
|
|
11405
|
+
});
|
|
11406
|
+
} catch {
|
|
11407
|
+
return false;
|
|
11408
|
+
}
|
|
11409
|
+
}
|
|
11215
11410
|
function rawElementName(node) {
|
|
11216
11411
|
if (node.name.type !== AST_NODE_TYPES50.JSXIdentifier) return null;
|
|
11217
11412
|
const name = node.name.name;
|
|
11218
|
-
return Object.hasOwn(
|
|
11413
|
+
return Object.hasOwn(RAW_PRIMITIVES, name) ? name : null;
|
|
11219
11414
|
}
|
|
11220
11415
|
function effectiveAttribute(node, attributeName) {
|
|
11221
11416
|
for (const attribute of node.attributes.toReversed()) {
|
|
@@ -11284,15 +11479,19 @@ function isStaticallyAssociatedLabel(node) {
|
|
|
11284
11479
|
return node.parent.type === AST_NODE_TYPES50.JSXElement && containsLabelableElement(node.parent);
|
|
11285
11480
|
}
|
|
11286
11481
|
function replacementFor(node, element) {
|
|
11287
|
-
if (element !== "input") return
|
|
11482
|
+
if (element !== "input") return RAW_PRIMITIVES[element];
|
|
11288
11483
|
const typeAttribute = effectiveAttribute(node, "type");
|
|
11289
11484
|
if (typeAttribute.kind === "unknown") return null;
|
|
11290
11485
|
const inputType = typeAttribute.kind === "known" ? typeAttribute.value.toLowerCase() : "text";
|
|
11291
11486
|
if (inputType === "hidden" || inputType === "file") return null;
|
|
11292
|
-
if (inputType === "checkbox")
|
|
11293
|
-
|
|
11487
|
+
if (inputType === "checkbox") {
|
|
11488
|
+
return { capability: "Checkbox", replacement: "Checkbox" };
|
|
11489
|
+
}
|
|
11490
|
+
if (inputType === "radio") {
|
|
11491
|
+
return { capability: "RadioGroup", replacement: "RadioGroup family" };
|
|
11492
|
+
}
|
|
11294
11493
|
if (AMBIGUOUS_INPUT_TYPES.has(inputType)) return null;
|
|
11295
|
-
return
|
|
11494
|
+
return RAW_PRIMITIVES.input;
|
|
11296
11495
|
}
|
|
11297
11496
|
var prefer_shadcn_primitives_default = createRule({
|
|
11298
11497
|
name: "prefer-shadcn-primitives",
|
|
@@ -11306,7 +11505,8 @@ var prefer_shadcn_primitives_default = createRule({
|
|
|
11306
11505
|
{
|
|
11307
11506
|
type: "object",
|
|
11308
11507
|
properties: {
|
|
11309
|
-
assumeAvailable: { type: "boolean" }
|
|
11508
|
+
assumeAvailable: { type: "boolean" },
|
|
11509
|
+
detectProjectPrimitives: { type: "boolean" }
|
|
11310
11510
|
},
|
|
11311
11511
|
additionalProperties: false
|
|
11312
11512
|
}
|
|
@@ -11318,15 +11518,21 @@ var prefer_shadcn_primitives_default = createRule({
|
|
|
11318
11518
|
defaultOptions: [{}],
|
|
11319
11519
|
create(context, [options]) {
|
|
11320
11520
|
const filename = context.filename.replaceAll("\\", "/");
|
|
11321
|
-
if (isTestFile(filename) || SHARED_PRIMITIVE_IMPLEMENTATION_RE.test(filename)) {
|
|
11521
|
+
if (isTestFile(filename) || isStoryFile(filename) || isGeneratedFile(filename, context.sourceCode.text) || SHARED_PRIMITIVE_IMPLEMENTATION_RE.test(filename)) {
|
|
11322
11522
|
return {};
|
|
11323
11523
|
}
|
|
11324
|
-
|
|
11524
|
+
const detectsProject = options?.detectProjectPrimitives === true;
|
|
11525
|
+
let hasSharedPrimitiveImport = false;
|
|
11526
|
+
const importedCapabilities = /* @__PURE__ */ new Set();
|
|
11325
11527
|
const candidates = [];
|
|
11326
11528
|
return {
|
|
11327
11529
|
ImportDeclaration(node) {
|
|
11328
|
-
if (typeof node.source.value
|
|
11329
|
-
|
|
11530
|
+
if (typeof node.source.value !== "string") return;
|
|
11531
|
+
const match = SHARED_PRIMITIVE_IMPORT_RE.exec(node.source.value);
|
|
11532
|
+
if (match?.[1] === void 0) return;
|
|
11533
|
+
hasSharedPrimitiveImport = true;
|
|
11534
|
+
for (const [capability, moduleName] of Object.entries(CAPABILITIES)) {
|
|
11535
|
+
if (match[1].toLowerCase() === moduleName) importedCapabilities.add(capability);
|
|
11330
11536
|
}
|
|
11331
11537
|
},
|
|
11332
11538
|
JSXOpeningElement(node) {
|
|
@@ -11335,11 +11541,15 @@ var prefer_shadcn_primitives_default = createRule({
|
|
|
11335
11541
|
if (element === "label" && !isStaticallyAssociatedLabel(node)) return;
|
|
11336
11542
|
const replacement = replacementFor(node, element);
|
|
11337
11543
|
if (replacement === null) return;
|
|
11338
|
-
candidates.push({ element, node, replacement });
|
|
11544
|
+
candidates.push({ element, node, ...replacement });
|
|
11339
11545
|
},
|
|
11340
11546
|
"Program:exit"() {
|
|
11341
|
-
|
|
11342
|
-
|
|
11547
|
+
const requested = new Set(candidates.map(({ capability }) => capability));
|
|
11548
|
+
const projectPrimitives = detectsProject && requested.size > 0 ? detectProjectPrimitives(context.filename, requested) : { available: /* @__PURE__ */ new Set(), uiRoot: null };
|
|
11549
|
+
if (projectPrimitives.uiRoot !== null && isWithin2(projectPrimitives.uiRoot, realpathOrOriginal(context.filename))) return;
|
|
11550
|
+
for (const { capability, element, node, replacement } of candidates) {
|
|
11551
|
+
const available = options?.assumeAvailable === true || (detectsProject ? projectPrimitives.available.has(capability) || importedCapabilities.has(capability) : hasSharedPrimitiveImport);
|
|
11552
|
+
if (!available) continue;
|
|
11343
11553
|
context.report({
|
|
11344
11554
|
node,
|
|
11345
11555
|
messageId: "preferShadcnPrimitive",
|
|
@@ -11350,6 +11560,13 @@ var prefer_shadcn_primitives_default = createRule({
|
|
|
11350
11560
|
};
|
|
11351
11561
|
}
|
|
11352
11562
|
});
|
|
11563
|
+
function realpathOrOriginal(path) {
|
|
11564
|
+
try {
|
|
11565
|
+
return realpathSync(path);
|
|
11566
|
+
} catch {
|
|
11567
|
+
return path;
|
|
11568
|
+
}
|
|
11569
|
+
}
|
|
11353
11570
|
|
|
11354
11571
|
// src/rules/prefer-module-level-constant.ts
|
|
11355
11572
|
import { AST_NODE_TYPES as AST_NODE_TYPES51 } from "@typescript-eslint/utils";
|
|
@@ -12019,100 +12236,371 @@ var prefer_module_level_schema_default = createRule({
|
|
|
12019
12236
|
});
|
|
12020
12237
|
|
|
12021
12238
|
// src/rules/prefer-module-level-refined-schema.ts
|
|
12022
|
-
import {
|
|
12239
|
+
import {
|
|
12240
|
+
AST_NODE_TYPES as AST_NODE_TYPES53,
|
|
12241
|
+
ASTUtils as ASTUtils15
|
|
12242
|
+
} from "@typescript-eslint/utils";
|
|
12243
|
+
var BENCHMARK_PATH_RE = /(^|[/\\])(?:benchmarks?|bench)[/\\]/;
|
|
12023
12244
|
var FACTORIES = /* @__PURE__ */ new Set([
|
|
12024
12245
|
"array",
|
|
12246
|
+
"base64",
|
|
12247
|
+
"base64url",
|
|
12025
12248
|
"bigint",
|
|
12026
12249
|
"boolean",
|
|
12250
|
+
"cidrv4",
|
|
12251
|
+
"cidrv6",
|
|
12252
|
+
"codec",
|
|
12253
|
+
"custom",
|
|
12027
12254
|
"date",
|
|
12028
|
-
"
|
|
12029
|
-
"
|
|
12030
|
-
]);
|
|
12031
|
-
var REFINEMENTS = /* @__PURE__ */ new Set([
|
|
12032
|
-
"brand",
|
|
12033
|
-
"check",
|
|
12255
|
+
"datetime",
|
|
12256
|
+
"duration",
|
|
12034
12257
|
"email",
|
|
12035
|
-
"
|
|
12036
|
-
"
|
|
12037
|
-
"
|
|
12038
|
-
"
|
|
12039
|
-
"
|
|
12040
|
-
"
|
|
12041
|
-
"
|
|
12042
|
-
"
|
|
12043
|
-
"
|
|
12044
|
-
"
|
|
12045
|
-
"
|
|
12046
|
-
"
|
|
12047
|
-
"
|
|
12048
|
-
"
|
|
12258
|
+
"emoji",
|
|
12259
|
+
"enum",
|
|
12260
|
+
"file",
|
|
12261
|
+
"function",
|
|
12262
|
+
"hash",
|
|
12263
|
+
"hex",
|
|
12264
|
+
"hostname",
|
|
12265
|
+
"instanceof",
|
|
12266
|
+
"ipv4",
|
|
12267
|
+
"ipv6",
|
|
12268
|
+
"json",
|
|
12269
|
+
"jwt",
|
|
12270
|
+
"literal",
|
|
12271
|
+
"map",
|
|
12272
|
+
"nan",
|
|
12273
|
+
"nativeEnum",
|
|
12274
|
+
"never",
|
|
12275
|
+
"null",
|
|
12276
|
+
"nullable",
|
|
12277
|
+
"nullish",
|
|
12278
|
+
"number",
|
|
12279
|
+
"optional",
|
|
12280
|
+
"partialRecord",
|
|
12281
|
+
"preprocess",
|
|
12282
|
+
"promise",
|
|
12283
|
+
"set",
|
|
12284
|
+
"string",
|
|
12285
|
+
"stringbool",
|
|
12286
|
+
"symbol",
|
|
12287
|
+
"templateLiteral",
|
|
12288
|
+
"time",
|
|
12289
|
+
"undefined",
|
|
12049
12290
|
"url",
|
|
12050
|
-
"uuid"
|
|
12291
|
+
"uuid",
|
|
12292
|
+
"void"
|
|
12293
|
+
]);
|
|
12294
|
+
var COMPOSITE_FACTORIES = /* @__PURE__ */ new Set([
|
|
12295
|
+
"discriminatedUnion",
|
|
12296
|
+
"intersection",
|
|
12297
|
+
"looseObject",
|
|
12298
|
+
"object",
|
|
12299
|
+
"record",
|
|
12300
|
+
"strictObject",
|
|
12301
|
+
"tuple",
|
|
12302
|
+
"union"
|
|
12303
|
+
]);
|
|
12304
|
+
var FACTORY_NAMESPACES = /* @__PURE__ */ new Set(["coerce", "iso"]);
|
|
12305
|
+
var NON_SCHEMA_TERMINALS = /* @__PURE__ */ new Set([
|
|
12306
|
+
"decode",
|
|
12307
|
+
"decodeAsync",
|
|
12308
|
+
"encode",
|
|
12309
|
+
"encodeAsync",
|
|
12310
|
+
"flattenError",
|
|
12311
|
+
"formatError",
|
|
12312
|
+
"implement",
|
|
12313
|
+
"isNullable",
|
|
12314
|
+
"isOptional",
|
|
12315
|
+
"parse",
|
|
12316
|
+
"parseAsync",
|
|
12317
|
+
"prettifyError",
|
|
12318
|
+
"registry",
|
|
12319
|
+
"safeDecode",
|
|
12320
|
+
"safeDecodeAsync",
|
|
12321
|
+
"safeEncode",
|
|
12322
|
+
"safeEncodeAsync",
|
|
12323
|
+
"safeParse",
|
|
12324
|
+
"safeParseAsync",
|
|
12325
|
+
"spa",
|
|
12326
|
+
"toJSONSchema",
|
|
12327
|
+
"treeifyError"
|
|
12328
|
+
]);
|
|
12329
|
+
var MEMO_CALLEES2 = /* @__PURE__ */ new Set([
|
|
12330
|
+
"lazy",
|
|
12331
|
+
"memo",
|
|
12332
|
+
"once",
|
|
12333
|
+
"useMemo"
|
|
12334
|
+
]);
|
|
12335
|
+
var I18N_CALLEE_NAMES2 = /* @__PURE__ */ new Set([
|
|
12336
|
+
"$t",
|
|
12337
|
+
"defineMessage",
|
|
12338
|
+
"gettext",
|
|
12339
|
+
"msg",
|
|
12340
|
+
"ngettext",
|
|
12341
|
+
"t",
|
|
12342
|
+
"translate"
|
|
12343
|
+
]);
|
|
12344
|
+
var I18N_RECEIVER_NAMES2 = /* @__PURE__ */ new Set([
|
|
12345
|
+
"$i18n",
|
|
12346
|
+
"i18n",
|
|
12347
|
+
"intl"
|
|
12348
|
+
]);
|
|
12349
|
+
var FUNCTION_TYPES9 = /* @__PURE__ */ new Set([
|
|
12350
|
+
AST_NODE_TYPES53.ArrowFunctionExpression,
|
|
12351
|
+
AST_NODE_TYPES53.FunctionDeclaration,
|
|
12352
|
+
AST_NODE_TYPES53.FunctionExpression
|
|
12051
12353
|
]);
|
|
12052
12354
|
var PREFER_MODULE_LEVEL_REFINED_SCHEMA_DOCUMENTATION = {
|
|
12053
|
-
summary: "Declare closed
|
|
12355
|
+
summary: "Declare closed Zod scalar, format, and wrapper schemas at module scope.",
|
|
12054
12356
|
rationale: "A closed validation pipeline created inside a function is rebuilt on every invocation and obscures a reusable constraint.",
|
|
12055
|
-
remediation: "Move the validation schema to module scope and call parse on the shared schema.",
|
|
12357
|
+
remediation: "Move the validation schema to module scope, name it with a PascalCase Schema suffix, and call parse on the shared schema.",
|
|
12056
12358
|
category: "performance",
|
|
12057
|
-
limitations: [
|
|
12359
|
+
limitations: [
|
|
12360
|
+
"Composite object/record/tuple/union schemas are owned by prefer-module-level-schema.",
|
|
12361
|
+
"Schemas that depend on function-local or mutable state, localized text, receiver state, lazy construction, or recognized memoization are excluded.",
|
|
12362
|
+
"Literal string z.enum domains are owned by prefer-shared-zod-enum."
|
|
12363
|
+
],
|
|
12058
12364
|
examples: [
|
|
12059
|
-
{
|
|
12060
|
-
|
|
12365
|
+
{
|
|
12366
|
+
id: "module-refinement",
|
|
12367
|
+
title: "Share the validation schema",
|
|
12368
|
+
outcome: "no-match",
|
|
12369
|
+
files: [{
|
|
12370
|
+
path: "src/options.ts",
|
|
12371
|
+
source: "import { z } from 'zod'; const BatchSizeSchema = z.number().int().min(1).max(1000); export function parse(value: unknown) { return BatchSizeSchema.parse(value); }"
|
|
12372
|
+
}],
|
|
12373
|
+
focusPath: "src/options.ts",
|
|
12374
|
+
expectedCount: 0,
|
|
12375
|
+
public: true
|
|
12376
|
+
},
|
|
12377
|
+
{
|
|
12378
|
+
id: "local-refinement",
|
|
12379
|
+
title: "Do not rebuild a closed validation chain",
|
|
12380
|
+
outcome: "match",
|
|
12381
|
+
files: [{
|
|
12382
|
+
path: "src/options.ts",
|
|
12383
|
+
source: "import { z } from 'zod'; export function parse(value: unknown) { return z.string().trim().min(1).max(128).parse(value); }"
|
|
12384
|
+
}],
|
|
12385
|
+
focusPath: "src/options.ts",
|
|
12386
|
+
expectedCount: 1,
|
|
12387
|
+
public: true
|
|
12388
|
+
}
|
|
12061
12389
|
]
|
|
12062
12390
|
};
|
|
12063
|
-
function
|
|
12391
|
+
function outermostEnclosingFunction2(node) {
|
|
12392
|
+
let outermost;
|
|
12064
12393
|
let current = node.parent ?? void 0;
|
|
12065
12394
|
while (current !== void 0) {
|
|
12066
|
-
if (
|
|
12395
|
+
if (FUNCTION_TYPES9.has(current.type)) outermost = current;
|
|
12067
12396
|
current = current.parent ?? void 0;
|
|
12068
12397
|
}
|
|
12069
|
-
return
|
|
12398
|
+
return outermost;
|
|
12070
12399
|
}
|
|
12071
12400
|
function collectReferences2(scope, output) {
|
|
12072
12401
|
output.push(...scope.references);
|
|
12073
12402
|
for (const child of scope.childScopes) collectReferences2(child, output);
|
|
12074
12403
|
}
|
|
12404
|
+
function subtreeSome2(root, predicate) {
|
|
12405
|
+
let found = false;
|
|
12406
|
+
const visit = (value) => {
|
|
12407
|
+
if (found || value === null || typeof value !== "object") return;
|
|
12408
|
+
if (Array.isArray(value)) {
|
|
12409
|
+
for (const item of value) visit(item);
|
|
12410
|
+
return;
|
|
12411
|
+
}
|
|
12412
|
+
const candidate2 = value;
|
|
12413
|
+
if (typeof candidate2.type !== "string") return;
|
|
12414
|
+
if (predicate(candidate2)) {
|
|
12415
|
+
found = true;
|
|
12416
|
+
return;
|
|
12417
|
+
}
|
|
12418
|
+
for (const key of Object.keys(candidate2)) {
|
|
12419
|
+
if (key === "parent" || key === "loc" || key === "range") continue;
|
|
12420
|
+
visit(candidate2[key]);
|
|
12421
|
+
}
|
|
12422
|
+
};
|
|
12423
|
+
visit(root);
|
|
12424
|
+
return found;
|
|
12425
|
+
}
|
|
12426
|
+
function readsReceiver2(node) {
|
|
12427
|
+
return subtreeSome2(
|
|
12428
|
+
node,
|
|
12429
|
+
(inner) => inner.type === AST_NODE_TYPES53.ThisExpression || inner.type === AST_NODE_TYPES53.Super || inner.type === AST_NODE_TYPES53.Identifier && inner.name === "arguments"
|
|
12430
|
+
);
|
|
12431
|
+
}
|
|
12432
|
+
function buildsLocalizedText2(node) {
|
|
12433
|
+
return subtreeSome2(node, (inner) => {
|
|
12434
|
+
if (inner.type === AST_NODE_TYPES53.TaggedTemplateExpression) return true;
|
|
12435
|
+
if (inner.type !== AST_NODE_TYPES53.CallExpression) return false;
|
|
12436
|
+
const { callee } = inner;
|
|
12437
|
+
if (callee.type === AST_NODE_TYPES53.Identifier)
|
|
12438
|
+
return I18N_CALLEE_NAMES2.has(callee.name);
|
|
12439
|
+
return callee.type === AST_NODE_TYPES53.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES53.Identifier && I18N_RECEIVER_NAMES2.has(callee.object.name);
|
|
12440
|
+
});
|
|
12441
|
+
}
|
|
12442
|
+
function calleeChainRoot(node) {
|
|
12443
|
+
let current = node;
|
|
12444
|
+
for (; ; ) {
|
|
12445
|
+
if (current.type === AST_NODE_TYPES53.Identifier) return current;
|
|
12446
|
+
if (current.type === AST_NODE_TYPES53.MemberExpression) {
|
|
12447
|
+
current = current.object;
|
|
12448
|
+
continue;
|
|
12449
|
+
}
|
|
12450
|
+
if (current.type === AST_NODE_TYPES53.CallExpression) {
|
|
12451
|
+
current = current.callee;
|
|
12452
|
+
continue;
|
|
12453
|
+
}
|
|
12454
|
+
return null;
|
|
12455
|
+
}
|
|
12456
|
+
}
|
|
12457
|
+
function chainMemberNames(node) {
|
|
12458
|
+
const names = [];
|
|
12459
|
+
let current = node;
|
|
12460
|
+
for (; ; ) {
|
|
12461
|
+
if (current.type === AST_NODE_TYPES53.MemberExpression) {
|
|
12462
|
+
if (current.computed || current.property.type !== AST_NODE_TYPES53.Identifier)
|
|
12463
|
+
return [];
|
|
12464
|
+
names.push(current.property.name);
|
|
12465
|
+
current = current.object;
|
|
12466
|
+
continue;
|
|
12467
|
+
}
|
|
12468
|
+
if (current.type === AST_NODE_TYPES53.CallExpression) {
|
|
12469
|
+
current = current.callee;
|
|
12470
|
+
continue;
|
|
12471
|
+
}
|
|
12472
|
+
break;
|
|
12473
|
+
}
|
|
12474
|
+
names.reverse();
|
|
12475
|
+
return names;
|
|
12476
|
+
}
|
|
12477
|
+
function schemaExpression2(node) {
|
|
12478
|
+
let current = node;
|
|
12479
|
+
for (; ; ) {
|
|
12480
|
+
const parent = current.parent ?? void 0;
|
|
12481
|
+
if (parent?.type === AST_NODE_TYPES53.MemberExpression && parent.object === current && !parent.computed && parent.property.type === AST_NODE_TYPES53.Identifier && parent.parent?.type === AST_NODE_TYPES53.CallExpression && parent.parent.callee === parent) {
|
|
12482
|
+
if (NON_SCHEMA_TERMINALS.has(parent.property.name)) return current;
|
|
12483
|
+
current = parent.parent;
|
|
12484
|
+
continue;
|
|
12485
|
+
}
|
|
12486
|
+
if (parent?.type === AST_NODE_TYPES53.TSAsExpression || parent?.type === AST_NODE_TYPES53.TSNonNullExpression || parent?.type === AST_NODE_TYPES53.TSSatisfiesExpression || parent?.type === AST_NODE_TYPES53.TSTypeAssertion) {
|
|
12487
|
+
current = parent;
|
|
12488
|
+
continue;
|
|
12489
|
+
}
|
|
12490
|
+
return current;
|
|
12491
|
+
}
|
|
12492
|
+
}
|
|
12075
12493
|
var prefer_module_level_refined_schema_default = createRule({
|
|
12076
12494
|
name: "prefer-module-level-refined-schema",
|
|
12077
12495
|
documentation: PREFER_MODULE_LEVEL_REFINED_SCHEMA_DOCUMENTATION,
|
|
12078
|
-
meta: {
|
|
12496
|
+
meta: {
|
|
12497
|
+
type: "suggestion",
|
|
12498
|
+
docs: {
|
|
12499
|
+
description: "Declare closed Zod scalar, format, and wrapper schemas at module scope."
|
|
12500
|
+
},
|
|
12501
|
+
schema: [],
|
|
12502
|
+
messages: {
|
|
12503
|
+
hoistRefinedSchema: "Move this closed Zod schema to module scope, give it a PascalCase Schema name, and reuse it for parsing."
|
|
12504
|
+
}
|
|
12505
|
+
},
|
|
12079
12506
|
defaultOptions: [],
|
|
12080
12507
|
create(context) {
|
|
12081
|
-
if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text))
|
|
12082
|
-
|
|
12508
|
+
if (isTestFile(context.filename) || BENCHMARK_PATH_RE.test(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text))
|
|
12509
|
+
return {};
|
|
12510
|
+
const zodBindings = /* @__PURE__ */ new Set();
|
|
12511
|
+
function resolvedBinding(identifier) {
|
|
12512
|
+
return ASTUtils15.findVariable(
|
|
12513
|
+
context.sourceCode.getScope(identifier),
|
|
12514
|
+
identifier.name
|
|
12515
|
+
);
|
|
12516
|
+
}
|
|
12517
|
+
function recordZodBinding(identifier) {
|
|
12518
|
+
const binding = resolvedBinding(identifier);
|
|
12519
|
+
if (binding !== null) zodBindings.add(binding);
|
|
12520
|
+
}
|
|
12521
|
+
function factoryName(node, allowed) {
|
|
12522
|
+
if (node.callee.type !== AST_NODE_TYPES53.MemberExpression) return null;
|
|
12523
|
+
const root = calleeChainRoot(node.callee);
|
|
12524
|
+
if (root === null) return null;
|
|
12525
|
+
const binding = resolvedBinding(root);
|
|
12526
|
+
if (binding === null || !zodBindings.has(binding)) return null;
|
|
12527
|
+
const names = chainMemberNames(node.callee);
|
|
12528
|
+
if (names.length === 1 && allowed.has(names[0] ?? ""))
|
|
12529
|
+
return names[0] ?? null;
|
|
12530
|
+
if (names.length === 2 && FACTORY_NAMESPACES.has(names[0] ?? "") && allowed.has(names[1] ?? ""))
|
|
12531
|
+
return names[1] ?? null;
|
|
12532
|
+
return null;
|
|
12533
|
+
}
|
|
12534
|
+
function isSharedEnumDomain(node, factory) {
|
|
12535
|
+
if (factory !== "enum") return false;
|
|
12536
|
+
const [argument] = node.arguments;
|
|
12537
|
+
return argument?.type === AST_NODE_TYPES53.ArrayExpression && argument.elements.length >= 2 && argument.elements.every(
|
|
12538
|
+
(element) => element?.type === AST_NODE_TYPES53.Literal && typeof element.value === "string"
|
|
12539
|
+
);
|
|
12540
|
+
}
|
|
12541
|
+
function isNestedInOwnedFactory(node) {
|
|
12542
|
+
let current = node;
|
|
12543
|
+
while (current.parent != null) {
|
|
12544
|
+
current = current.parent;
|
|
12545
|
+
if (FUNCTION_TYPES9.has(current.type)) return false;
|
|
12546
|
+
if (current.type === AST_NODE_TYPES53.CallExpression && (factoryName(current, FACTORIES) !== null || factoryName(current, COMPOSITE_FACTORIES) !== null))
|
|
12547
|
+
return true;
|
|
12548
|
+
}
|
|
12549
|
+
return false;
|
|
12550
|
+
}
|
|
12551
|
+
function isMemoized(node) {
|
|
12552
|
+
let current = node.parent ?? void 0;
|
|
12553
|
+
while (current !== void 0) {
|
|
12554
|
+
if (current.type === AST_NODE_TYPES53.CallExpression && (current.callee.type === AST_NODE_TYPES53.Identifier && MEMO_CALLEES2.has(current.callee.name) || current.callee.type === AST_NODE_TYPES53.MemberExpression && !current.callee.computed && current.callee.property.type === AST_NODE_TYPES53.Identifier && MEMO_CALLEES2.has(current.callee.property.name)))
|
|
12555
|
+
return true;
|
|
12556
|
+
current = current.parent ?? void 0;
|
|
12557
|
+
}
|
|
12558
|
+
return false;
|
|
12559
|
+
}
|
|
12560
|
+
function closesOverNothing(node, enclosing) {
|
|
12561
|
+
const references = [];
|
|
12562
|
+
collectReferences2(context.sourceCode.getScope(node), references);
|
|
12563
|
+
const [start, end] = node.range;
|
|
12564
|
+
const [functionStart, functionEnd] = enclosing.range;
|
|
12565
|
+
for (const reference of references) {
|
|
12566
|
+
const [referenceStart] = reference.identifier.range;
|
|
12567
|
+
if (referenceStart < start || referenceStart >= end) continue;
|
|
12568
|
+
const resolved = reference.resolved;
|
|
12569
|
+
if (resolved === null) continue;
|
|
12570
|
+
for (const definition of resolved.defs) {
|
|
12571
|
+
if (definition.type === "ImportBinding") {
|
|
12572
|
+
const parent = reference.identifier.parent;
|
|
12573
|
+
if (parent?.type === AST_NODE_TYPES53.CallExpression && parent.callee === reference.identifier && !zodBindings.has(resolved))
|
|
12574
|
+
return false;
|
|
12575
|
+
continue;
|
|
12576
|
+
}
|
|
12577
|
+
if (definition.node.type === AST_NODE_TYPES53.VariableDeclarator && definition.node.parent.type === AST_NODE_TYPES53.VariableDeclaration && definition.node.parent.kind !== "const")
|
|
12578
|
+
return false;
|
|
12579
|
+
const [definitionStart, definitionEnd] = definition.node.range;
|
|
12580
|
+
if (definitionStart >= start && definitionEnd <= end) continue;
|
|
12581
|
+
if (definitionStart >= functionStart && definitionEnd <= functionEnd)
|
|
12582
|
+
return false;
|
|
12583
|
+
}
|
|
12584
|
+
}
|
|
12585
|
+
return true;
|
|
12586
|
+
}
|
|
12083
12587
|
return {
|
|
12084
12588
|
ImportDeclaration(node) {
|
|
12085
12589
|
if (!isZodModule(node.source.value)) return;
|
|
12086
|
-
for (const specifier of node.specifiers)
|
|
12590
|
+
for (const specifier of node.specifiers) {
|
|
12591
|
+
if (specifier.type === AST_NODE_TYPES53.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES53.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES53.ImportSpecifier && (specifier.imported.type === AST_NODE_TYPES53.Identifier ? specifier.imported.name === "z" : specifier.imported.value === "z"))
|
|
12592
|
+
recordZodBinding(specifier.local);
|
|
12593
|
+
}
|
|
12087
12594
|
},
|
|
12088
12595
|
CallExpression(node) {
|
|
12089
|
-
const
|
|
12090
|
-
if (
|
|
12091
|
-
|
|
12092
|
-
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
if (REFINEMENTS.has(current.parent.property.name)) refinements += 1;
|
|
12098
|
-
current = call;
|
|
12099
|
-
}
|
|
12100
|
-
if (refinements < 2) return;
|
|
12101
|
-
const references = [];
|
|
12102
|
-
collectReferences2(context.sourceCode.getScope(current), references);
|
|
12103
|
-
const [start, end] = current.range;
|
|
12104
|
-
const [functionStart, functionEnd] = enclosing.range;
|
|
12105
|
-
for (const reference of references) {
|
|
12106
|
-
const [referenceStart] = reference.identifier.range;
|
|
12107
|
-
if (referenceStart < start || referenceStart >= end || reference.resolved === null) continue;
|
|
12108
|
-
for (const definition of reference.resolved.defs) {
|
|
12109
|
-
if (definition.type === "ImportBinding") continue;
|
|
12110
|
-
if (definition.node.type === AST_NODE_TYPES53.VariableDeclarator && definition.node.parent.type === AST_NODE_TYPES53.VariableDeclaration && definition.node.parent.kind !== "const") return;
|
|
12111
|
-
const [definitionStart, definitionEnd] = definition.node.range;
|
|
12112
|
-
if (definitionStart >= start && definitionEnd <= end) continue;
|
|
12113
|
-
if (definitionStart >= functionStart && definitionEnd <= functionEnd) return;
|
|
12114
|
-
}
|
|
12115
|
-
}
|
|
12596
|
+
const factory = factoryName(node, FACTORIES);
|
|
12597
|
+
if (factory === null || isSharedEnumDomain(node, factory) || isNestedInOwnedFactory(node) || isMemoized(node))
|
|
12598
|
+
return;
|
|
12599
|
+
const enclosing = outermostEnclosingFunction2(node);
|
|
12600
|
+
if (enclosing === void 0) return;
|
|
12601
|
+
const expression = schemaExpression2(node);
|
|
12602
|
+
if (readsReceiver2(expression) || buildsLocalizedText2(expression) || !closesOverNothing(expression, enclosing))
|
|
12603
|
+
return;
|
|
12116
12604
|
context.report({ node, messageId: "hoistRefinedSchema" });
|
|
12117
12605
|
}
|
|
12118
12606
|
};
|
|
@@ -12120,26 +12608,30 @@ var prefer_module_level_refined_schema_default = createRule({
|
|
|
12120
12608
|
});
|
|
12121
12609
|
|
|
12122
12610
|
// src/rules/prefer-multi-value-zod-literal.ts
|
|
12123
|
-
import {
|
|
12611
|
+
import {
|
|
12612
|
+
AST_NODE_TYPES as AST_NODE_TYPES54,
|
|
12613
|
+
ASTUtils as ASTUtils16
|
|
12614
|
+
} from "@typescript-eslint/utils";
|
|
12124
12615
|
var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
|
|
12125
12616
|
summary: "Use the Zod 4 multi-value literal API instead of a union of literal schemas.",
|
|
12126
12617
|
rationale: "One multi-value literal expresses the same closed value domain without repeated schema wrappers.",
|
|
12127
12618
|
remediation: "Replace the union with z.literal([value1, value2, ...]).",
|
|
12128
12619
|
category: "maintainability",
|
|
12620
|
+
autofix: "safe",
|
|
12129
12621
|
limitations: [
|
|
12130
|
-
"Bare
|
|
12622
|
+
"Bare zod imports are analyzed only when the rule option explicitly declares zodMajorVersion: 4; explicit zod/v4 entrypoints are self-declaring.",
|
|
12623
|
+
"All-string domains are left to zod/prefer-enum-over-literal-union.",
|
|
12624
|
+
"A finding containing comments is not autofixed because moving its trivia is ambiguous."
|
|
12131
12625
|
],
|
|
12132
12626
|
examples: [
|
|
12133
12627
|
{
|
|
12134
12628
|
id: "multi-value",
|
|
12135
12629
|
title: "Use one multi-value literal",
|
|
12136
12630
|
outcome: "no-match",
|
|
12137
|
-
files: [
|
|
12138
|
-
|
|
12139
|
-
|
|
12140
|
-
|
|
12141
|
-
}
|
|
12142
|
-
],
|
|
12631
|
+
files: [{
|
|
12632
|
+
path: "src/schema.ts",
|
|
12633
|
+
source: "import { z } from 'zod'; export const Version = z.literal([1, 2, 3]);"
|
|
12634
|
+
}],
|
|
12143
12635
|
focusPath: "src/schema.ts",
|
|
12144
12636
|
expectedCount: 0,
|
|
12145
12637
|
public: true
|
|
@@ -12148,80 +12640,116 @@ var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
|
|
|
12148
12640
|
id: "literal-union",
|
|
12149
12641
|
title: "Avoid repeated literal wrappers",
|
|
12150
12642
|
outcome: "match",
|
|
12151
|
-
files: [
|
|
12152
|
-
|
|
12153
|
-
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
|
|
12643
|
+
files: [{
|
|
12644
|
+
path: "src/schema.ts",
|
|
12645
|
+
source: "import { z } from 'zod'; export const Version = z.union([z.literal(1), z.literal(2), z.literal(3)]);"
|
|
12646
|
+
}],
|
|
12647
|
+
fixedFiles: [{
|
|
12648
|
+
path: "src/schema.ts",
|
|
12649
|
+
source: "import { z } from 'zod'; export const Version = z.literal([1, 2, 3]);"
|
|
12650
|
+
}],
|
|
12157
12651
|
focusPath: "src/schema.ts",
|
|
12158
12652
|
expectedCount: 1,
|
|
12159
12653
|
public: true
|
|
12160
12654
|
}
|
|
12161
12655
|
]
|
|
12162
12656
|
};
|
|
12163
|
-
function
|
|
12164
|
-
|
|
12657
|
+
function isStaticPrimitive(node, context) {
|
|
12658
|
+
if (node.type === AST_NODE_TYPES54.Literal) {
|
|
12659
|
+
return node.value === null || ["bigint", "boolean", "number", "string"].includes(typeof node.value);
|
|
12660
|
+
}
|
|
12661
|
+
if (node.type === AST_NODE_TYPES54.TemplateLiteral && node.expressions.length === 0)
|
|
12662
|
+
return true;
|
|
12663
|
+
if (node.type === AST_NODE_TYPES54.Identifier && node.name === "undefined") {
|
|
12664
|
+
const binding = ASTUtils16.findVariable(
|
|
12665
|
+
context.sourceCode.getScope(node),
|
|
12666
|
+
node.name
|
|
12667
|
+
);
|
|
12668
|
+
return binding === null || binding.defs.length === 0;
|
|
12669
|
+
}
|
|
12670
|
+
return node.type === AST_NODE_TYPES54.UnaryExpression && node.operator === "-" && node.argument.type === AST_NODE_TYPES54.Literal && ["bigint", "number"].includes(typeof node.argument.value);
|
|
12671
|
+
}
|
|
12672
|
+
function isStaticString(node) {
|
|
12673
|
+
return node.type === AST_NODE_TYPES54.Literal && typeof node.value === "string" || node.type === AST_NODE_TYPES54.TemplateLiteral && node.expressions.length === 0;
|
|
12165
12674
|
}
|
|
12166
12675
|
var prefer_multi_value_zod_literal_default = createRule({
|
|
12167
12676
|
name: "prefer-multi-value-zod-literal",
|
|
12168
12677
|
documentation: PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION,
|
|
12169
12678
|
meta: {
|
|
12170
12679
|
type: "suggestion",
|
|
12680
|
+
fixable: "code",
|
|
12171
12681
|
docs: {
|
|
12172
12682
|
description: "Use the Zod 4 multi-value literal API instead of a union of literal schemas."
|
|
12173
12683
|
},
|
|
12174
|
-
schema: [
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
|
|
12178
|
-
|
|
12179
|
-
zodMajorVersion: { type: "integer", minimum: 4, maximum: 4 }
|
|
12180
|
-
}
|
|
12684
|
+
schema: [{
|
|
12685
|
+
type: "object",
|
|
12686
|
+
additionalProperties: false,
|
|
12687
|
+
properties: {
|
|
12688
|
+
zodMajorVersion: { type: "integer", minimum: 4, maximum: 4 }
|
|
12181
12689
|
}
|
|
12182
|
-
],
|
|
12690
|
+
}],
|
|
12183
12691
|
messages: {
|
|
12184
|
-
useMultiValueLiteral: "Replace this literal-schema union with
|
|
12692
|
+
useMultiValueLiteral: "Replace this literal-schema union with {{zod}}.literal([\u2026])."
|
|
12185
12693
|
}
|
|
12186
12694
|
},
|
|
12187
12695
|
defaultOptions: [{}],
|
|
12188
12696
|
create(context, [options]) {
|
|
12189
12697
|
if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text))
|
|
12190
12698
|
return {};
|
|
12191
|
-
const
|
|
12192
|
-
const
|
|
12699
|
+
const zodBindings = /* @__PURE__ */ new Set();
|
|
12700
|
+
const zod4Bindings = /* @__PURE__ */ new Set();
|
|
12701
|
+
function resolvedBinding(identifier) {
|
|
12702
|
+
return ASTUtils16.findVariable(
|
|
12703
|
+
context.sourceCode.getScope(identifier),
|
|
12704
|
+
identifier.name
|
|
12705
|
+
);
|
|
12706
|
+
}
|
|
12707
|
+
function directMemberCall(node, binding, method) {
|
|
12708
|
+
if (node.callee.type !== AST_NODE_TYPES54.MemberExpression || node.callee.computed || node.callee.object.type !== AST_NODE_TYPES54.Identifier || node.callee.property.type !== AST_NODE_TYPES54.Identifier || node.callee.property.name !== method)
|
|
12709
|
+
return false;
|
|
12710
|
+
return resolvedBinding(node.callee.object) === binding;
|
|
12711
|
+
}
|
|
12193
12712
|
return {
|
|
12194
12713
|
ImportDeclaration(node) {
|
|
12195
12714
|
if (!isZodModule(node.source.value)) return;
|
|
12715
|
+
const isExplicitV4 = /^zod\/v4(?:$|[-/])/.test(node.source.value);
|
|
12196
12716
|
for (const specifier of node.specifiers) {
|
|
12197
|
-
if (specifier.type === AST_NODE_TYPES54.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES54.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES54.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES54.Identifier
|
|
12198
|
-
|
|
12199
|
-
if (
|
|
12200
|
-
|
|
12717
|
+
if (specifier.type === AST_NODE_TYPES54.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES54.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES54.ImportSpecifier && (specifier.imported.type === AST_NODE_TYPES54.Identifier ? specifier.imported.name === "z" : specifier.imported.value === "z")) {
|
|
12718
|
+
const binding = resolvedBinding(specifier.local);
|
|
12719
|
+
if (binding === null) continue;
|
|
12720
|
+
zodBindings.add(binding);
|
|
12721
|
+
if (isExplicitV4) zod4Bindings.add(binding);
|
|
12201
12722
|
}
|
|
12202
12723
|
}
|
|
12203
12724
|
},
|
|
12204
12725
|
CallExpression(node) {
|
|
12205
|
-
|
|
12206
|
-
|
|
12207
|
-
);
|
|
12208
|
-
if (
|
|
12726
|
+
if (node.callee.type !== AST_NODE_TYPES54.MemberExpression || node.callee.object.type !== AST_NODE_TYPES54.Identifier)
|
|
12727
|
+
return;
|
|
12728
|
+
const binding = resolvedBinding(node.callee.object);
|
|
12729
|
+
if (binding === null || !zodBindings.has(binding) || !directMemberCall(node, binding, "union") || options?.zodMajorVersion !== 4 && !zod4Bindings.has(binding) || node.arguments.length !== 1)
|
|
12209
12730
|
return;
|
|
12210
12731
|
const [argument] = node.arguments;
|
|
12211
|
-
if (argument?.type !== AST_NODE_TYPES54.ArrayExpression || argument.elements.length <
|
|
12212
|
-
if (element === null || element.type !== AST_NODE_TYPES54.CallExpression || !memberCall(element, namespace, "literal") || element.arguments.length !== 1)
|
|
12213
|
-
return true;
|
|
12214
|
-
const [value] = element.arguments;
|
|
12215
|
-
return value === void 0 || value.type === AST_NODE_TYPES54.SpreadElement || ![
|
|
12216
|
-
AST_NODE_TYPES54.Literal,
|
|
12217
|
-
AST_NODE_TYPES54.TemplateLiteral
|
|
12218
|
-
].includes(value.type);
|
|
12219
|
-
}))
|
|
12732
|
+
if (argument?.type !== AST_NODE_TYPES54.ArrayExpression || argument.elements.length < 2)
|
|
12220
12733
|
return;
|
|
12734
|
+
const values = [];
|
|
12735
|
+
for (const element of argument.elements) {
|
|
12736
|
+
if (element === null || element.type !== AST_NODE_TYPES54.CallExpression || !directMemberCall(element, binding, "literal") || element.arguments.length !== 1)
|
|
12737
|
+
return;
|
|
12738
|
+
const [value] = element.arguments;
|
|
12739
|
+
if (value === void 0 || !isStaticPrimitive(value, context)) return;
|
|
12740
|
+
values.push(value);
|
|
12741
|
+
}
|
|
12742
|
+
if (values.every(isStaticString)) return;
|
|
12743
|
+
const namespace = node.callee.object.name;
|
|
12744
|
+
const hasComments = context.sourceCode.getCommentsInside(node).length > 0;
|
|
12221
12745
|
context.report({
|
|
12222
12746
|
node,
|
|
12223
12747
|
messageId: "useMultiValueLiteral",
|
|
12224
|
-
data: { zod: namespace }
|
|
12748
|
+
data: { zod: namespace },
|
|
12749
|
+
fix: hasComments ? null : (fixer) => fixer.replaceText(
|
|
12750
|
+
node,
|
|
12751
|
+
`${namespace}.literal([${values.map((value) => context.sourceCode.getText(value)).join(", ")}])`
|
|
12752
|
+
)
|
|
12225
12753
|
});
|
|
12226
12754
|
}
|
|
12227
12755
|
};
|
|
@@ -12338,7 +12866,7 @@ var prefer_named_complex_return_type_default = createRule({
|
|
|
12338
12866
|
});
|
|
12339
12867
|
|
|
12340
12868
|
// src/rules/prefer-native-random-uuid.ts
|
|
12341
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES57, ASTUtils as
|
|
12869
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES57, ASTUtils as ASTUtils17 } from "@typescript-eslint/utils";
|
|
12342
12870
|
var PREFER_NATIVE_RANDOM_UUID_DOCUMENTATION = {
|
|
12343
12871
|
summary: "Prefer `globalThis.crypto.randomUUID()` over resolved zero-argument UUID v4 bindings from the `uuid` package.",
|
|
12344
12872
|
rationale: "The platform implementation avoids an unnecessary dependency for standard random UUID generation.",
|
|
@@ -12373,11 +12901,11 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
12373
12901
|
create(context) {
|
|
12374
12902
|
const directBindings = /* @__PURE__ */ new Set();
|
|
12375
12903
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
12376
|
-
function
|
|
12377
|
-
return
|
|
12904
|
+
function resolve2(identifier) {
|
|
12905
|
+
return ASTUtils17.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
12378
12906
|
}
|
|
12379
12907
|
function record(identifier, destination) {
|
|
12380
|
-
const variable =
|
|
12908
|
+
const variable = resolve2(identifier);
|
|
12381
12909
|
if (variable !== null) destination.add(variable);
|
|
12382
12910
|
}
|
|
12383
12911
|
function report2(node) {
|
|
@@ -12405,7 +12933,7 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
12405
12933
|
},
|
|
12406
12934
|
VariableDeclarator(node) {
|
|
12407
12935
|
if (node.parent.kind !== "const" || !requireUuid(node.init)) return;
|
|
12408
|
-
if (node.init?.type !== AST_NODE_TYPES57.CallExpression || node.init.callee.type !== AST_NODE_TYPES57.Identifier || (
|
|
12936
|
+
if (node.init?.type !== AST_NODE_TYPES57.CallExpression || node.init.callee.type !== AST_NODE_TYPES57.Identifier || (resolve2(node.init.callee)?.defs.length ?? 0) > 0) {
|
|
12409
12937
|
return;
|
|
12410
12938
|
}
|
|
12411
12939
|
if (node.id.type === AST_NODE_TYPES57.Identifier) {
|
|
@@ -12422,14 +12950,14 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
12422
12950
|
"CallExpression:exit"(node) {
|
|
12423
12951
|
if (node.arguments.length !== 0) return;
|
|
12424
12952
|
if (node.callee.type === AST_NODE_TYPES57.Identifier) {
|
|
12425
|
-
const variable2 =
|
|
12953
|
+
const variable2 = resolve2(node.callee);
|
|
12426
12954
|
if (variable2 !== null && directBindings.has(variable2)) report2(node);
|
|
12427
12955
|
return;
|
|
12428
12956
|
}
|
|
12429
12957
|
if (node.callee.type !== AST_NODE_TYPES57.MemberExpression || node.callee.computed || node.callee.object.type !== AST_NODE_TYPES57.Identifier || node.callee.property.type !== AST_NODE_TYPES57.Identifier || node.callee.property.name !== "v4") {
|
|
12430
12958
|
return;
|
|
12431
12959
|
}
|
|
12432
|
-
const variable =
|
|
12960
|
+
const variable = resolve2(node.callee.object);
|
|
12433
12961
|
if (variable !== null && namespaceBindings.has(variable)) report2(node);
|
|
12434
12962
|
}
|
|
12435
12963
|
};
|
|
@@ -12437,7 +12965,7 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
12437
12965
|
});
|
|
12438
12966
|
|
|
12439
12967
|
// src/rules/prefer-node-crypto-hash.ts
|
|
12440
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES58, ASTUtils as
|
|
12968
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES58, ASTUtils as ASTUtils18 } from "@typescript-eslint/utils";
|
|
12441
12969
|
var PREFER_NODE_CRYPTO_HASH_DOCUMENTATION = {
|
|
12442
12970
|
summary: "Prefer the modern one-shot node:crypto hash API when streaming state is unnecessary.",
|
|
12443
12971
|
rationale: "A createHash-update-digest chain allocates mutable streaming state for a single in-memory value; Node's built-in hash function expresses the one-shot operation directly and can use its optimized fast path.",
|
|
@@ -12459,22 +12987,22 @@ function memberName4(node) {
|
|
|
12459
12987
|
}
|
|
12460
12988
|
return null;
|
|
12461
12989
|
}
|
|
12462
|
-
function isCryptoLoader(node,
|
|
12990
|
+
function isCryptoLoader(node, resolve2) {
|
|
12463
12991
|
if (node.type !== AST_NODE_TYPES58.CallExpression || node.arguments.length !== 1) return false;
|
|
12464
12992
|
const [argument] = node.arguments;
|
|
12465
12993
|
if (argument === void 0 || argument.type === AST_NODE_TYPES58.SpreadElement || !isCryptoSpecifier(argument)) {
|
|
12466
12994
|
return false;
|
|
12467
12995
|
}
|
|
12468
12996
|
if (node.callee.type === AST_NODE_TYPES58.Identifier) {
|
|
12469
|
-
return node.callee.name === "require" && isUnshadowedBuiltinIdentifier(node.callee,
|
|
12997
|
+
return node.callee.name === "require" && isUnshadowedBuiltinIdentifier(node.callee, resolve2);
|
|
12470
12998
|
}
|
|
12471
|
-
return node.callee.type === AST_NODE_TYPES58.MemberExpression && node.callee.object.type === AST_NODE_TYPES58.Identifier && node.callee.object.name === "process" && isUnshadowedBuiltinIdentifier(node.callee.object,
|
|
12999
|
+
return node.callee.type === AST_NODE_TYPES58.MemberExpression && node.callee.object.type === AST_NODE_TYPES58.Identifier && node.callee.object.name === "process" && isUnshadowedBuiltinIdentifier(node.callee.object, resolve2) && memberName4(node.callee) === "getBuiltinModule";
|
|
12472
13000
|
}
|
|
12473
13001
|
function isCryptoSpecifier(node) {
|
|
12474
13002
|
return node.type === AST_NODE_TYPES58.Literal && (node.value === "crypto" || node.value === "node:crypto");
|
|
12475
13003
|
}
|
|
12476
|
-
function isUnshadowedBuiltinIdentifier(identifier,
|
|
12477
|
-
const variable =
|
|
13004
|
+
function isUnshadowedBuiltinIdentifier(identifier, resolve2) {
|
|
13005
|
+
const variable = resolve2(identifier);
|
|
12478
13006
|
return variable === null || variable.defs.length === 0;
|
|
12479
13007
|
}
|
|
12480
13008
|
function propertyName4(node) {
|
|
@@ -12492,14 +13020,14 @@ var prefer_node_crypto_hash_default = createRule({
|
|
|
12492
13020
|
create(context) {
|
|
12493
13021
|
const directBindings = /* @__PURE__ */ new Set();
|
|
12494
13022
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
12495
|
-
function
|
|
12496
|
-
return
|
|
13023
|
+
function resolve2(identifier) {
|
|
13024
|
+
return ASTUtils18.findVariable(
|
|
12497
13025
|
context.sourceCode.getScope(identifier),
|
|
12498
13026
|
identifier.name
|
|
12499
13027
|
);
|
|
12500
13028
|
}
|
|
12501
13029
|
function record(identifier, destination) {
|
|
12502
|
-
const variable =
|
|
13030
|
+
const variable = resolve2(identifier);
|
|
12503
13031
|
if (variable !== null) destination.add(variable);
|
|
12504
13032
|
}
|
|
12505
13033
|
return {
|
|
@@ -12514,7 +13042,7 @@ var prefer_node_crypto_hash_default = createRule({
|
|
|
12514
13042
|
}
|
|
12515
13043
|
},
|
|
12516
13044
|
VariableDeclarator(node) {
|
|
12517
|
-
if (node.parent.kind !== "const" || node.init === null || !isCryptoLoader(node.init,
|
|
13045
|
+
if (node.parent.kind !== "const" || node.init === null || !isCryptoLoader(node.init, resolve2)) {
|
|
12518
13046
|
return;
|
|
12519
13047
|
}
|
|
12520
13048
|
if (node.id.type === AST_NODE_TYPES58.Identifier) {
|
|
@@ -12538,7 +13066,7 @@ var prefer_node_crypto_hash_default = createRule({
|
|
|
12538
13066
|
create,
|
|
12539
13067
|
directBindings,
|
|
12540
13068
|
namespaceBindings,
|
|
12541
|
-
|
|
13069
|
+
resolve2
|
|
12542
13070
|
))
|
|
12543
13071
|
return;
|
|
12544
13072
|
context.report({ node, messageId: "preferNodeCryptoHash" });
|
|
@@ -12552,17 +13080,17 @@ function importedName5(node) {
|
|
|
12552
13080
|
function isMemberCall(node, name) {
|
|
12553
13081
|
return node.callee.type === AST_NODE_TYPES58.MemberExpression && memberName4(node.callee) === name;
|
|
12554
13082
|
}
|
|
12555
|
-
function isCreateHashCall(node, directBindings, namespaceBindings,
|
|
13083
|
+
function isCreateHashCall(node, directBindings, namespaceBindings, resolve2) {
|
|
12556
13084
|
if (node.callee.type === AST_NODE_TYPES58.Identifier) {
|
|
12557
|
-
const variable2 =
|
|
13085
|
+
const variable2 = resolve2(node.callee);
|
|
12558
13086
|
return variable2 !== null && directBindings.has(variable2);
|
|
12559
13087
|
}
|
|
12560
13088
|
if (node.callee.type !== AST_NODE_TYPES58.MemberExpression || memberName4(node.callee) !== "createHash") {
|
|
12561
13089
|
return false;
|
|
12562
13090
|
}
|
|
12563
|
-
if (isCryptoLoader(node.callee.object,
|
|
13091
|
+
if (isCryptoLoader(node.callee.object, resolve2)) return true;
|
|
12564
13092
|
if (node.callee.object.type !== AST_NODE_TYPES58.Identifier) return false;
|
|
12565
|
-
const variable =
|
|
13093
|
+
const variable = resolve2(node.callee.object);
|
|
12566
13094
|
return variable !== null && namespaceBindings.has(variable);
|
|
12567
13095
|
}
|
|
12568
13096
|
|
|
@@ -12680,7 +13208,7 @@ var prefer_node_fs_promises_default = createRule({
|
|
|
12680
13208
|
});
|
|
12681
13209
|
|
|
12682
13210
|
// src/rules/prefer-non-nullable-collection.ts
|
|
12683
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as
|
|
13211
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as ASTUtils19 } from "@typescript-eslint/utils";
|
|
12684
13212
|
var PREFER_NON_NULLABLE_COLLECTION_DOCUMENTATION = {
|
|
12685
13213
|
summary: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection.",
|
|
12686
13214
|
rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
|
|
@@ -12810,14 +13338,14 @@ function directlyCoalesced(node) {
|
|
|
12810
13338
|
return parent?.type === AST_NODE_TYPES60.LogicalExpression && parent.left === node && (parent.operator === "??" || parent.operator === "||") && emptyArray(parent.right);
|
|
12811
13339
|
}
|
|
12812
13340
|
function identifierIsOnlyCoalesced(context, binding, fn) {
|
|
12813
|
-
const variable =
|
|
13341
|
+
const variable = ASTUtils19.findVariable(context.sourceCode.getScope(binding), binding.name);
|
|
12814
13342
|
if (variable === null || variable.references.length === 0) return false;
|
|
12815
13343
|
return variable.references.every(
|
|
12816
13344
|
(reference) => belongsToFunction(reference.identifier, fn) && directlyCoalesced(reference.identifier)
|
|
12817
13345
|
);
|
|
12818
13346
|
}
|
|
12819
13347
|
function memberIsOnlyCoalesced(context, object, property, fn) {
|
|
12820
|
-
const variable =
|
|
13348
|
+
const variable = ASTUtils19.findVariable(context.sourceCode.getScope(object), object.name);
|
|
12821
13349
|
if (variable === null) return false;
|
|
12822
13350
|
const accesses = variable.references.flatMap((reference) => {
|
|
12823
13351
|
if (!belongsToFunction(reference.identifier, fn)) return [null];
|
|
@@ -12927,7 +13455,7 @@ var prefer_non_nullable_collection_default = createRule({
|
|
|
12927
13455
|
// src/rules/prefer-nullish-filter-predicate.ts
|
|
12928
13456
|
import {
|
|
12929
13457
|
AST_NODE_TYPES as AST_NODE_TYPES61,
|
|
12930
|
-
ASTUtils as
|
|
13458
|
+
ASTUtils as ASTUtils20,
|
|
12931
13459
|
ESLintUtils as ESLintUtils5
|
|
12932
13460
|
} from "@typescript-eslint/utils";
|
|
12933
13461
|
import ts3 from "typescript";
|
|
@@ -12973,7 +13501,7 @@ var PREFER_NULLISH_FILTER_PREDICATE_DOCUMENTATION = {
|
|
|
12973
13501
|
]
|
|
12974
13502
|
};
|
|
12975
13503
|
function isUnshadowedBoolean(node, context) {
|
|
12976
|
-
const variable =
|
|
13504
|
+
const variable = ASTUtils20.findVariable(context.sourceCode.getScope(node), node.name);
|
|
12977
13505
|
return variable === null || variable.defs.length === 0;
|
|
12978
13506
|
}
|
|
12979
13507
|
function isBuiltinArrayFilter(node, services) {
|
|
@@ -13032,7 +13560,7 @@ function isProvablyTruthy(type, checker) {
|
|
|
13032
13560
|
}
|
|
13033
13561
|
function availableParameterName(node, context) {
|
|
13034
13562
|
for (const name of ["value", "item", "element", "candidate"]) {
|
|
13035
|
-
if (
|
|
13563
|
+
if (ASTUtils20.findVariable(context.sourceCode.getScope(node), name) === null) return name;
|
|
13036
13564
|
}
|
|
13037
13565
|
return null;
|
|
13038
13566
|
}
|
|
@@ -13086,7 +13614,7 @@ var prefer_nullish_filter_predicate_default = createRule({
|
|
|
13086
13614
|
|
|
13087
13615
|
// src/rules/prefer-await-in-async-return.ts
|
|
13088
13616
|
import {
|
|
13089
|
-
ASTUtils as
|
|
13617
|
+
ASTUtils as ASTUtils21,
|
|
13090
13618
|
ESLintUtils as ESLintUtils6,
|
|
13091
13619
|
AST_NODE_TYPES as AST_NODE_TYPES62
|
|
13092
13620
|
} from "@typescript-eslint/utils";
|
|
@@ -13200,13 +13728,13 @@ var prefer_await_in_async_return_default = createRule({
|
|
|
13200
13728
|
if (services === null) return {};
|
|
13201
13729
|
const frameworkLoaders = /* @__PURE__ */ new Set();
|
|
13202
13730
|
const rememberFrameworkLoader = (identifier) => {
|
|
13203
|
-
const variable =
|
|
13731
|
+
const variable = ASTUtils21.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
13204
13732
|
if (variable !== null) frameworkLoaders.add(variable);
|
|
13205
13733
|
};
|
|
13206
13734
|
const isFrameworkLoaderCallback = (owner) => {
|
|
13207
13735
|
const parent = owner.parent;
|
|
13208
13736
|
if (parent.type !== AST_NODE_TYPES62.CallExpression || parent.arguments[0] !== owner || parent.callee.type !== AST_NODE_TYPES62.Identifier) return false;
|
|
13209
|
-
const variable =
|
|
13737
|
+
const variable = ASTUtils21.findVariable(context.sourceCode.getScope(parent.callee), parent.callee.name);
|
|
13210
13738
|
return variable !== null && frameworkLoaders.has(variable);
|
|
13211
13739
|
};
|
|
13212
13740
|
return {
|
|
@@ -13888,11 +14416,34 @@ var prefer_switch_for_repeated_equality_default = createRule({
|
|
|
13888
14416
|
|
|
13889
14417
|
// src/rules/prefer-semantic-colors.ts
|
|
13890
14418
|
import { AST_NODE_TYPES as AST_NODE_TYPES66 } from "@typescript-eslint/utils";
|
|
13891
|
-
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
13892
|
-
import { dirname, join, parse } from "path";
|
|
14419
|
+
import { existsSync as existsSync2, lstatSync as lstatSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
14420
|
+
import { dirname as dirname2, join as join2, parse } from "path";
|
|
13893
14421
|
|
|
13894
14422
|
// src/rules/_tailwind.ts
|
|
13895
|
-
var
|
|
14423
|
+
var tailwindVariantPrefix = (token) => {
|
|
14424
|
+
let bracketDepth = 0;
|
|
14425
|
+
let parenthesisDepth = 0;
|
|
14426
|
+
let escaped = false;
|
|
14427
|
+
let end = 0;
|
|
14428
|
+
for (let index = 0; index < token.length; index += 1) {
|
|
14429
|
+
const character = token[index];
|
|
14430
|
+
if (escaped) {
|
|
14431
|
+
escaped = false;
|
|
14432
|
+
continue;
|
|
14433
|
+
}
|
|
14434
|
+
if (character === "\\") {
|
|
14435
|
+
escaped = true;
|
|
14436
|
+
continue;
|
|
14437
|
+
}
|
|
14438
|
+
if (character === "[") bracketDepth += 1;
|
|
14439
|
+
else if (character === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
14440
|
+
else if (character === "(") parenthesisDepth += 1;
|
|
14441
|
+
else if (character === ")") parenthesisDepth = Math.max(0, parenthesisDepth - 1);
|
|
14442
|
+
else if (character === ":" && bracketDepth === 0 && parenthesisDepth === 0) end = index + 1;
|
|
14443
|
+
}
|
|
14444
|
+
return token.slice(0, end);
|
|
14445
|
+
};
|
|
14446
|
+
var tailwindBase = (token) => token.slice(tailwindVariantPrefix(token).length).replace(/^!/, "");
|
|
13896
14447
|
var classTokens = (value) => value.split(/\s+/).filter(Boolean);
|
|
13897
14448
|
|
|
13898
14449
|
// src/rules/prefer-semantic-colors.ts
|
|
@@ -13901,7 +14452,10 @@ var PREFER_SEMANTIC_COLORS_DOCUMENTATION = {
|
|
|
13901
14452
|
rationale: "Semantic tokens keep themes and product meaning consistent while raw colors couple components to a palette value.",
|
|
13902
14453
|
remediation: "Replace raw palette and literal colors with the closest semantic design-system token or CSS variable.",
|
|
13903
14454
|
category: "style",
|
|
13904
|
-
limitations: [
|
|
14455
|
+
limitations: [
|
|
14456
|
+
"Email, PDF, video-rendering, print-only, icon artwork, masks, gradients, stories, and explicitly configured non-token projects have targeted exclusions.",
|
|
14457
|
+
"Opaque-foreground checks are opt-in and require both a same-variant semantic background class and its package-local declared foreground token."
|
|
14458
|
+
],
|
|
13905
14459
|
examples: [
|
|
13906
14460
|
{ id: "semantic-text-color", title: "Use a semantic color token", outcome: "no-match", files: [{ path: "src/notice.tsx", source: 'const notice = <div className="text-destructive" />;' }], focusPath: "src/notice.tsx", expectedCount: 0, public: true },
|
|
13907
14461
|
{ id: "raw-text-color", title: "Do not use a raw palette color", outcome: "match", files: [{ path: "src/notice.tsx", source: 'const notice = <div className="text-red-500" />;' }], focusPath: "src/notice.tsx", expectedCount: 1, public: true }
|
|
@@ -14029,7 +14583,13 @@ var SVG_SHAPE_PRIMITIVES = /* @__PURE__ */ new Set([
|
|
|
14029
14583
|
"tspan",
|
|
14030
14584
|
"use"
|
|
14031
14585
|
]);
|
|
14032
|
-
var
|
|
14586
|
+
var EXTERNAL_RENDERER_MODULE_RE = /^(?:@react-(?:email|pdf)\/|remotion$|@remotion\/)/;
|
|
14587
|
+
var OPAQUE_FOREGROUND_RE = /^text-(?:white|black)(?:\/100)?$/;
|
|
14588
|
+
var SEMANTIC_BACKGROUND_RE = /^bg-([a-z][a-z0-9-]*)$/;
|
|
14589
|
+
var CSS_COMMENT_RE = /\/\*[\s\S]*?\*\//gu;
|
|
14590
|
+
var DECLARED_FOREGROUND_RE = /--(?:color-)?([a-z][a-z0-9-]*)-foreground\s*:/giu;
|
|
14591
|
+
var MAX_TOKEN_STYLESHEET_BYTES = 1048576;
|
|
14592
|
+
var SEMANTIC_DECLARATIONS_CACHE = /* @__PURE__ */ new Map();
|
|
14033
14593
|
function isSvgLikeElementName(name) {
|
|
14034
14594
|
return name === "svg" || SVG_DEFS_CONTAINERS.has(name) || /svg$/i.test(name);
|
|
14035
14595
|
}
|
|
@@ -14044,12 +14604,12 @@ var isInsideIconFactoryPath = (node) => {
|
|
|
14044
14604
|
return false;
|
|
14045
14605
|
};
|
|
14046
14606
|
var hasMarkerAt = (dir) => {
|
|
14047
|
-
if (PRESENCE_MARKERS.some((rel) =>
|
|
14607
|
+
if (PRESENCE_MARKERS.some((rel) => existsSync2(join2(dir, rel)))) return true;
|
|
14048
14608
|
for (const rel of CSS_DETECTION_FILES) {
|
|
14049
|
-
const candidate2 =
|
|
14050
|
-
if (!
|
|
14609
|
+
const candidate2 = join2(dir, rel);
|
|
14610
|
+
if (!existsSync2(candidate2)) continue;
|
|
14051
14611
|
try {
|
|
14052
|
-
const css =
|
|
14612
|
+
const css = readFileSync2(candidate2, "utf8");
|
|
14053
14613
|
if (SEMANTIC_TOKEN_RE.test(css) || THEME_BLOCK_RE.test(css)) return true;
|
|
14054
14614
|
} catch {
|
|
14055
14615
|
}
|
|
@@ -14058,10 +14618,10 @@ var hasMarkerAt = (dir) => {
|
|
|
14058
14618
|
};
|
|
14059
14619
|
var readWorkspaceGlobs = (dir) => {
|
|
14060
14620
|
const globs = [];
|
|
14061
|
-
const packageJson =
|
|
14062
|
-
if (
|
|
14621
|
+
const packageJson = join2(dir, "package.json");
|
|
14622
|
+
if (existsSync2(packageJson)) {
|
|
14063
14623
|
try {
|
|
14064
|
-
const parsed = JSON.parse(
|
|
14624
|
+
const parsed = JSON.parse(readFileSync2(packageJson, "utf8"));
|
|
14065
14625
|
const declared = typeof parsed === "object" && parsed !== null && "workspaces" in parsed ? parsed.workspaces : void 0;
|
|
14066
14626
|
const list = Array.isArray(declared) ? declared : typeof declared === "object" && declared !== null && Array.isArray(declared.packages) ? declared.packages : [];
|
|
14067
14627
|
for (const entry of list) if (typeof entry === "string") globs.push(entry);
|
|
@@ -14069,10 +14629,10 @@ var readWorkspaceGlobs = (dir) => {
|
|
|
14069
14629
|
}
|
|
14070
14630
|
}
|
|
14071
14631
|
for (const name of ["pnpm-workspace.yaml", "pnpm-workspace.yml"]) {
|
|
14072
|
-
const yaml =
|
|
14073
|
-
if (!
|
|
14632
|
+
const yaml = join2(dir, name);
|
|
14633
|
+
if (!existsSync2(yaml)) continue;
|
|
14074
14634
|
try {
|
|
14075
|
-
for (const line of
|
|
14635
|
+
for (const line of readFileSync2(yaml, "utf8").split("\n")) {
|
|
14076
14636
|
const match = /^\s*-\s*["']?([^"'#\s]+)["']?\s*$/u.exec(line);
|
|
14077
14637
|
if (match?.[1] !== void 0) globs.push(match[1]);
|
|
14078
14638
|
}
|
|
@@ -14082,7 +14642,7 @@ var readWorkspaceGlobs = (dir) => {
|
|
|
14082
14642
|
return globs;
|
|
14083
14643
|
};
|
|
14084
14644
|
var hasSemanticTokenSystem = (filename) => {
|
|
14085
|
-
const dir =
|
|
14645
|
+
const dir = dirname2(filename);
|
|
14086
14646
|
if (hasMarkerAtOrAbove(dir)) return true;
|
|
14087
14647
|
const root = findWorkspaceRoot(dir);
|
|
14088
14648
|
return root !== null && workspaceHasMarker(root);
|
|
@@ -14103,7 +14663,7 @@ var hasMarkerAtOrAbove = (startDir) => {
|
|
|
14103
14663
|
answer = true;
|
|
14104
14664
|
break;
|
|
14105
14665
|
}
|
|
14106
|
-
const parent =
|
|
14666
|
+
const parent = dirname2(dir);
|
|
14107
14667
|
if (dir === root || parent === dir) {
|
|
14108
14668
|
answer = false;
|
|
14109
14669
|
break;
|
|
@@ -14125,11 +14685,11 @@ var findWorkspaceRoot = (startDir) => {
|
|
|
14125
14685
|
break;
|
|
14126
14686
|
}
|
|
14127
14687
|
visited.push(dir);
|
|
14128
|
-
if (WORKSPACE_ROOT_FILES.some((name) =>
|
|
14688
|
+
if (WORKSPACE_ROOT_FILES.some((name) => existsSync2(join2(dir, name))) || readWorkspaceGlobs(dir).length > 0) {
|
|
14129
14689
|
answer = dir;
|
|
14130
14690
|
break;
|
|
14131
14691
|
}
|
|
14132
|
-
const parent =
|
|
14692
|
+
const parent = dirname2(dir);
|
|
14133
14693
|
if (dir === root || parent === dir) {
|
|
14134
14694
|
answer = null;
|
|
14135
14695
|
break;
|
|
@@ -14163,23 +14723,71 @@ var workspaceHasMarker = (root) => {
|
|
|
14163
14723
|
};
|
|
14164
14724
|
var expandWorkspaceGlob = (root, glob) => {
|
|
14165
14725
|
const star = glob.indexOf("*");
|
|
14166
|
-
if (star === -1) return [
|
|
14726
|
+
if (star === -1) return [join2(root, glob)];
|
|
14167
14727
|
const prefix = glob.slice(0, star).replace(/\/$/u, "");
|
|
14168
|
-
const parent = prefix === "" ? root :
|
|
14169
|
-
if (!
|
|
14170
|
-
return readdirSync(parent, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) =>
|
|
14728
|
+
const parent = prefix === "" ? root : join2(root, prefix);
|
|
14729
|
+
if (!existsSync2(parent) || !lstatSync2(parent).isDirectory()) return [];
|
|
14730
|
+
return readdirSync(parent, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => join2(parent, entry.name));
|
|
14171
14731
|
};
|
|
14172
14732
|
var propName = (key) => {
|
|
14173
14733
|
if (key.type === AST_NODE_TYPES66.Identifier) return key.name;
|
|
14174
14734
|
if (key.type === AST_NODE_TYPES66.Literal && typeof key.value === "string") return key.value;
|
|
14175
14735
|
return null;
|
|
14176
14736
|
};
|
|
14177
|
-
var
|
|
14737
|
+
var staticallyImportsExternalRenderer = (program) => program.body.some((statement) => {
|
|
14178
14738
|
if (statement.type !== AST_NODE_TYPES66.ImportDeclaration && statement.type !== AST_NODE_TYPES66.ExportNamedDeclaration && statement.type !== AST_NODE_TYPES66.ExportAllDeclaration) {
|
|
14179
14739
|
return false;
|
|
14180
14740
|
}
|
|
14181
|
-
return statement.source !== null && typeof statement.source.value === "string" &&
|
|
14741
|
+
return statement.source !== null && typeof statement.source.value === "string" && EXTERNAL_RENDERER_MODULE_RE.test(statement.source.value);
|
|
14182
14742
|
});
|
|
14743
|
+
var semanticForegroundRoles = (filename) => {
|
|
14744
|
+
const packageRoot = nearestPackageRoot(dirname2(filename));
|
|
14745
|
+
if (packageRoot === null) return /* @__PURE__ */ new Set();
|
|
14746
|
+
const candidates = CSS_DETECTION_FILES.map((relative2) => join2(packageRoot, relative2));
|
|
14747
|
+
const fingerprint = candidates.map(fileFingerprint2).join("|");
|
|
14748
|
+
const cached = SEMANTIC_DECLARATIONS_CACHE.get(packageRoot);
|
|
14749
|
+
if (cached?.fingerprint === fingerprint) return cached.value;
|
|
14750
|
+
const foregroundRoles = /* @__PURE__ */ new Set();
|
|
14751
|
+
for (const candidate2 of candidates) {
|
|
14752
|
+
const css = readTokenStylesheet(candidate2);
|
|
14753
|
+
if (css === null) continue;
|
|
14754
|
+
const declarations = css.replace(CSS_COMMENT_RE, "");
|
|
14755
|
+
for (const match of declarations.matchAll(DECLARED_FOREGROUND_RE)) {
|
|
14756
|
+
if (match[1] !== void 0) foregroundRoles.add(match[1].toLowerCase());
|
|
14757
|
+
}
|
|
14758
|
+
}
|
|
14759
|
+
SEMANTIC_DECLARATIONS_CACHE.set(packageRoot, { fingerprint, value: foregroundRoles });
|
|
14760
|
+
return foregroundRoles;
|
|
14761
|
+
};
|
|
14762
|
+
var nearestPackageRoot = (startDir) => {
|
|
14763
|
+
let dir = startDir;
|
|
14764
|
+
const root = parse(dir).root;
|
|
14765
|
+
for (; ; ) {
|
|
14766
|
+
if (existsSync2(join2(dir, "package.json"))) {
|
|
14767
|
+
return dir;
|
|
14768
|
+
}
|
|
14769
|
+
const parent = dirname2(dir);
|
|
14770
|
+
if (dir === root || parent === dir) return null;
|
|
14771
|
+
dir = parent;
|
|
14772
|
+
}
|
|
14773
|
+
};
|
|
14774
|
+
var fileFingerprint2 = (path) => {
|
|
14775
|
+
try {
|
|
14776
|
+
const stat = lstatSync2(path);
|
|
14777
|
+
return stat.isFile() ? `${path}:${stat.size}:${stat.mtimeMs}` : `${path}:excluded`;
|
|
14778
|
+
} catch {
|
|
14779
|
+
return `${path}:missing`;
|
|
14780
|
+
}
|
|
14781
|
+
};
|
|
14782
|
+
var readTokenStylesheet = (path) => {
|
|
14783
|
+
try {
|
|
14784
|
+
const stat = lstatSync2(path);
|
|
14785
|
+
if (!stat.isFile() || stat.size > MAX_TOKEN_STYLESHEET_BYTES) return null;
|
|
14786
|
+
return readFileSync2(path, "utf8");
|
|
14787
|
+
} catch {
|
|
14788
|
+
return null;
|
|
14789
|
+
}
|
|
14790
|
+
};
|
|
14183
14791
|
var prefer_semantic_colors_default = createRule({
|
|
14184
14792
|
name: "prefer-semantic-colors",
|
|
14185
14793
|
documentation: PREFER_SEMANTIC_COLORS_DOCUMENTATION,
|
|
@@ -14193,30 +14801,34 @@ var prefer_semantic_colors_default = createRule({
|
|
|
14193
14801
|
type: "object",
|
|
14194
14802
|
additionalProperties: false,
|
|
14195
14803
|
properties: {
|
|
14196
|
-
requireSemanticTokens: { type: "boolean" }
|
|
14804
|
+
requireSemanticTokens: { type: "boolean" },
|
|
14805
|
+
opaqueForegroundPairs: { type: "boolean" }
|
|
14197
14806
|
}
|
|
14198
14807
|
}
|
|
14199
14808
|
],
|
|
14200
14809
|
messages: {
|
|
14201
14810
|
rawPalette: "Raw palette class '{{class}}' \u2014 use a semantic token (e.g. text-foreground, bg-primary, text-destructive, bg-muted).",
|
|
14202
14811
|
arbitraryColor: "Hardcoded color '{{class}}' \u2014 use a semantic token, or var(--\u2026). For charts/brand add an eslint-disable with a reason.",
|
|
14203
|
-
inlineColor: "Hardcoded color '{{value}}' \u2014 use a semantic token / CSS variable. For charts/standalone pages add an eslint-disable with a reason."
|
|
14812
|
+
inlineColor: "Hardcoded color '{{value}}' \u2014 use a semantic token / CSS variable. For charts/standalone pages add an eslint-disable with a reason.",
|
|
14813
|
+
opaqueForegroundPair: "'{{class}}' bypasses the declared '{{replacement}}' token paired with '{{background}}'."
|
|
14204
14814
|
}
|
|
14205
14815
|
},
|
|
14206
14816
|
defaultOptions: [{}],
|
|
14207
14817
|
create(context, [options]) {
|
|
14208
14818
|
if (STORIES_FILE_RE.test(context.filename)) return {};
|
|
14209
|
-
if (
|
|
14210
|
-
if (options?.requireSemanticTokens === true && !hasSemanticTokenSystem(context.filename)) {
|
|
14211
|
-
|
|
14212
|
-
|
|
14213
|
-
let
|
|
14214
|
-
const pendingReports =
|
|
14819
|
+
if (staticallyImportsExternalRenderer(context.sourceCode.ast)) return {};
|
|
14820
|
+
if (options?.requireSemanticTokens === true && !hasSemanticTokenSystem(context.filename)) return {};
|
|
14821
|
+
const foregroundRoles = options?.opaqueForegroundPairs === true ? semanticForegroundRoles(context.filename) : /* @__PURE__ */ new Set();
|
|
14822
|
+
const checkOpaqueForegroundPairs = foregroundRoles.size > 0;
|
|
14823
|
+
let importsExternalRenderer = false;
|
|
14824
|
+
const pendingReports = /* @__PURE__ */ new Map();
|
|
14215
14825
|
const report2 = (node, messageId, data) => {
|
|
14216
|
-
|
|
14826
|
+
const key = `${node.range[0]}:${node.range[1]}:${messageId}:${JSON.stringify(data)}`;
|
|
14827
|
+
pendingReports.set(key, { node, messageId, data });
|
|
14217
14828
|
};
|
|
14218
14829
|
const reportClasses = (value, node) => {
|
|
14219
|
-
|
|
14830
|
+
const tokens = classTokens(value);
|
|
14831
|
+
for (const token of tokens) {
|
|
14220
14832
|
const base = tailwindBase(token);
|
|
14221
14833
|
if (RAW_PALETTE_RE.test(base)) {
|
|
14222
14834
|
report2(node, "rawPalette", { class: token });
|
|
@@ -14224,6 +14836,26 @@ var prefer_semantic_colors_default = createRule({
|
|
|
14224
14836
|
report2(node, "arbitraryColor", { class: token });
|
|
14225
14837
|
}
|
|
14226
14838
|
}
|
|
14839
|
+
if (!checkOpaqueForegroundPairs || isInsideSvg(node)) return;
|
|
14840
|
+
for (const token of tokens) {
|
|
14841
|
+
const prefix = tailwindVariantPrefix(token);
|
|
14842
|
+
if (prefix.split(":").includes("print")) continue;
|
|
14843
|
+
const base = tailwindBase(token);
|
|
14844
|
+
if (!OPAQUE_FOREGROUND_RE.test(base)) continue;
|
|
14845
|
+
const semanticBackground = tokens.find((candidate2) => {
|
|
14846
|
+
if (tailwindVariantPrefix(candidate2) !== prefix) return false;
|
|
14847
|
+
const match = SEMANTIC_BACKGROUND_RE.exec(tailwindBase(candidate2));
|
|
14848
|
+
return match?.[1] !== void 0 && foregroundRoles.has(match[1]);
|
|
14849
|
+
});
|
|
14850
|
+
if (semanticBackground === void 0) continue;
|
|
14851
|
+
const role = SEMANTIC_BACKGROUND_RE.exec(tailwindBase(semanticBackground))?.[1];
|
|
14852
|
+
if (role === void 0) continue;
|
|
14853
|
+
report2(node, "opaqueForegroundPair", {
|
|
14854
|
+
background: semanticBackground,
|
|
14855
|
+
class: token,
|
|
14856
|
+
replacement: `${prefix}text-${role}-foreground`
|
|
14857
|
+
});
|
|
14858
|
+
}
|
|
14227
14859
|
};
|
|
14228
14860
|
const checkClassNode = (node) => {
|
|
14229
14861
|
if (node === null) return;
|
|
@@ -14271,8 +14903,8 @@ var prefer_semantic_colors_default = createRule({
|
|
|
14271
14903
|
}
|
|
14272
14904
|
},
|
|
14273
14905
|
CallExpression(node) {
|
|
14274
|
-
if (node.callee.type === AST_NODE_TYPES66.Identifier && node.callee.name === "require" && node.arguments[0]?.type === AST_NODE_TYPES66.Literal && typeof node.arguments[0].value === "string" &&
|
|
14275
|
-
|
|
14906
|
+
if (node.callee.type === AST_NODE_TYPES66.Identifier && node.callee.name === "require" && node.arguments[0]?.type === AST_NODE_TYPES66.Literal && typeof node.arguments[0].value === "string" && EXTERNAL_RENDERER_MODULE_RE.test(node.arguments[0].value)) {
|
|
14907
|
+
importsExternalRenderer = true;
|
|
14276
14908
|
}
|
|
14277
14909
|
if (node.callee.type === AST_NODE_TYPES66.Identifier && CLASS_FNS.has(node.callee.name)) {
|
|
14278
14910
|
for (const arg of node.arguments) {
|
|
@@ -14307,13 +14939,13 @@ var prefer_semantic_colors_default = createRule({
|
|
|
14307
14939
|
if (name !== null && STYLE_COLOR_PROPS.has(name)) checkColorValueNode(node.value);
|
|
14308
14940
|
},
|
|
14309
14941
|
ImportExpression(node) {
|
|
14310
|
-
if (node.source.type === AST_NODE_TYPES66.Literal && typeof node.source.value === "string" &&
|
|
14311
|
-
|
|
14942
|
+
if (node.source.type === AST_NODE_TYPES66.Literal && typeof node.source.value === "string" && EXTERNAL_RENDERER_MODULE_RE.test(node.source.value)) {
|
|
14943
|
+
importsExternalRenderer = true;
|
|
14312
14944
|
}
|
|
14313
14945
|
},
|
|
14314
14946
|
"Program:exit"() {
|
|
14315
|
-
if (
|
|
14316
|
-
for (const descriptor of pendingReports) context.report(descriptor);
|
|
14947
|
+
if (importsExternalRenderer) return;
|
|
14948
|
+
for (const descriptor of pendingReports.values()) context.report(descriptor);
|
|
14317
14949
|
}
|
|
14318
14950
|
};
|
|
14319
14951
|
}
|
|
@@ -14322,11 +14954,11 @@ var prefer_semantic_colors_default = createRule({
|
|
|
14322
14954
|
// src/rules/prefer-server-actions.ts
|
|
14323
14955
|
import "@typescript-eslint/utils";
|
|
14324
14956
|
var PREFER_SERVER_ACTIONS_DOCUMENTATION = {
|
|
14325
|
-
summary: "Prefer Next.js Server Actions over
|
|
14957
|
+
summary: "Prefer Next.js Server Actions over same-origin API mutations.",
|
|
14326
14958
|
rationale: "Server Actions preserve typed application calls and avoid an internal JSON request-response boundary.",
|
|
14327
14959
|
remediation: "Move the mutation into a Server Action and invoke that action from the React client.",
|
|
14328
14960
|
category: "architecture",
|
|
14329
|
-
limitations: ["Only statically recognizable /api/ mutations
|
|
14961
|
+
limitations: ["Only statically recognizable /api/ mutations, including one explicitly configured literal deployment base path, in use-client modules are reported; server boundaries and route handlers are excluded."],
|
|
14330
14962
|
examples: [
|
|
14331
14963
|
{ id: "server-action-call", title: "Call a Server Action", outcome: "no-match", files: [{ path: "app/tasks/page.tsx", source: "import { createTask } from './actions'; await createTask(input);" }], focusPath: "app/tasks/page.tsx", expectedCount: 0, public: true },
|
|
14332
14964
|
{ id: "api-mutation", title: "Do not mutate through an API route", outcome: "match", files: [{ path: "app/tasks/page.tsx", source: "'use client'; await fetch('/api/tasks', { method: 'POST', body });" }], focusPath: "app/tasks/page.tsx", expectedCount: 1, public: true }
|
|
@@ -14334,12 +14966,21 @@ var PREFER_SERVER_ACTIONS_DOCUMENTATION = {
|
|
|
14334
14966
|
};
|
|
14335
14967
|
var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
|
|
14336
14968
|
var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
|
|
14337
|
-
var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|-(?:test|spec)\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/__testfixtures__\/|\/scripts
|
|
14969
|
+
var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|-(?:test|spec)\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/__testfixtures__\/|\/scripts?\/|(?:^|\/)app(?:\/.*)?\/route\.[jt]sx?$|(?:^|\/)middleware\.[jt]sx?$|\/pages\/api\/)/;
|
|
14338
14970
|
var NON_REACT_FRAMEWORK_RE2 = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
|
|
14339
|
-
var
|
|
14971
|
+
var BASE_PATH_RE2 = /^\/(?!$)(?!.*[?#])(?:[^/]+\/)*[^/]+$/u;
|
|
14340
14972
|
function getScope(context, node) {
|
|
14341
14973
|
return context.sourceCode.getScope(node);
|
|
14342
14974
|
}
|
|
14975
|
+
function resolvesToGlobalFetch(context, identifier) {
|
|
14976
|
+
let scope = getScope(context, identifier);
|
|
14977
|
+
while (scope) {
|
|
14978
|
+
const variable = scope.set.get(identifier.name);
|
|
14979
|
+
if (variable !== void 0) return variable.defs.length === 0;
|
|
14980
|
+
scope = scope.upper;
|
|
14981
|
+
}
|
|
14982
|
+
return true;
|
|
14983
|
+
}
|
|
14343
14984
|
function resolveNode(node, context) {
|
|
14344
14985
|
if (!node) return null;
|
|
14345
14986
|
if (node.type !== "Identifier") return node;
|
|
@@ -14359,22 +15000,29 @@ function resolveNode(node, context) {
|
|
|
14359
15000
|
}
|
|
14360
15001
|
return node;
|
|
14361
15002
|
}
|
|
14362
|
-
function isApiUrl(node, context) {
|
|
15003
|
+
function isApiUrl(node, context, apiPrefixes) {
|
|
14363
15004
|
const resolved = resolveNode(node, context);
|
|
14364
15005
|
if (!resolved) return false;
|
|
14365
15006
|
if (resolved.type === "Literal" && typeof resolved.value === "string") {
|
|
14366
|
-
return
|
|
15007
|
+
return apiPrefixes.some(
|
|
15008
|
+
(prefix) => resolved.value === prefix.slice(0, -1) || resolved.value.startsWith(prefix)
|
|
15009
|
+
);
|
|
14367
15010
|
}
|
|
14368
15011
|
if (resolved.type === "TemplateLiteral") {
|
|
14369
15012
|
const firstQuasi = resolved.quasis[0];
|
|
14370
15013
|
const cooked = firstQuasi?.value.cooked;
|
|
14371
|
-
return typeof cooked === "string" &&
|
|
15014
|
+
return typeof cooked === "string" && apiPrefixes.some(
|
|
15015
|
+
(prefix) => cooked === prefix.slice(0, -1) || cooked.startsWith(prefix)
|
|
15016
|
+
);
|
|
14372
15017
|
}
|
|
14373
15018
|
if (resolved.type === "BinaryExpression" && resolved.operator === "+") {
|
|
14374
|
-
return isApiUrl(resolved.left, context);
|
|
15019
|
+
return isApiUrl(resolved.left, context, apiPrefixes);
|
|
14375
15020
|
}
|
|
14376
15021
|
return false;
|
|
14377
15022
|
}
|
|
15023
|
+
function isValidBasePath2(basePath) {
|
|
15024
|
+
return BASE_PATH_RE2.test(basePath) && !basePath.split("/").some((segment) => segment === "." || segment === "..");
|
|
15025
|
+
}
|
|
14378
15026
|
function isMutationMethod(node, context) {
|
|
14379
15027
|
const resolved = resolveNode(node, context);
|
|
14380
15028
|
if (!resolved) return false;
|
|
@@ -14434,16 +15082,27 @@ var prefer_server_actions_default = createRule({
|
|
|
14434
15082
|
meta: {
|
|
14435
15083
|
type: "suggestion",
|
|
14436
15084
|
docs: {
|
|
14437
|
-
description: "Prefer Next.js Server Actions over
|
|
15085
|
+
description: "Prefer Next.js Server Actions over same-origin API mutations."
|
|
14438
15086
|
},
|
|
14439
|
-
schema: [
|
|
15087
|
+
schema: [
|
|
15088
|
+
{
|
|
15089
|
+
type: "object",
|
|
15090
|
+
additionalProperties: false,
|
|
15091
|
+
properties: {
|
|
15092
|
+
basePath: {
|
|
15093
|
+
type: "string",
|
|
15094
|
+
pattern: "^/(?!$)(?!.*[?#])(?!(?:.*/)?\\.\\.?(?:/|$))(?:[^/]+/)*[^/]+$"
|
|
15095
|
+
}
|
|
15096
|
+
}
|
|
15097
|
+
}
|
|
15098
|
+
],
|
|
14440
15099
|
messages: {
|
|
14441
|
-
preferServerAction: "Mutation against
|
|
15100
|
+
preferServerAction: "Mutation against a same-origin API route \u2014 prefer a Next.js Server Action for type-safety and to avoid the JSON round-trip."
|
|
14442
15101
|
}
|
|
14443
15102
|
},
|
|
14444
|
-
defaultOptions: [],
|
|
14445
|
-
create(context) {
|
|
14446
|
-
const filename = context.filename;
|
|
15103
|
+
defaultOptions: [{}],
|
|
15104
|
+
create(context, [options]) {
|
|
15105
|
+
const filename = context.filename.replaceAll("\\", "/");
|
|
14447
15106
|
if (SKIP_FILE_REGEX.test(filename)) {
|
|
14448
15107
|
return {};
|
|
14449
15108
|
}
|
|
@@ -14451,22 +15110,28 @@ var prefer_server_actions_default = createRule({
|
|
|
14451
15110
|
(node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && NON_REACT_FRAMEWORK_RE2.test(node.source.value)
|
|
14452
15111
|
);
|
|
14453
15112
|
const hasUseClientDirective = context.sourceCode.ast.body.some(
|
|
14454
|
-
(node) => node.type === "ExpressionStatement" && node.
|
|
15113
|
+
(node) => node.type === "ExpressionStatement" && node.directive === "use client"
|
|
15114
|
+
);
|
|
15115
|
+
const hasUseServerDirective = context.sourceCode.ast.body.some(
|
|
15116
|
+
(node) => node.type === "ExpressionStatement" && node.directive === "use server"
|
|
14455
15117
|
);
|
|
14456
|
-
const
|
|
14457
|
-
(node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && (node.source.value === "
|
|
15118
|
+
const importsServerOnly = context.sourceCode.ast.body.some(
|
|
15119
|
+
(node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && (node.source.value === "server-only" || node.source.value === "next/server")
|
|
14458
15120
|
);
|
|
14459
|
-
|
|
14460
|
-
if (!hasNextEvidence) {
|
|
15121
|
+
if (!hasUseClientDirective || hasUseServerDirective || importsServerOnly) {
|
|
14461
15122
|
return {};
|
|
14462
15123
|
}
|
|
15124
|
+
const apiPrefixes = ["/api/"];
|
|
15125
|
+
if (options?.basePath !== void 0 && isValidBasePath2(options.basePath)) {
|
|
15126
|
+
apiPrefixes.push(`${options.basePath}/api/`);
|
|
15127
|
+
}
|
|
14463
15128
|
return {
|
|
14464
15129
|
CallExpression(node) {
|
|
14465
15130
|
if (isNonReactFramework) return;
|
|
14466
15131
|
let isMutation = false;
|
|
14467
|
-
if (node.callee.type === "Identifier" && node.callee.name === "fetch") {
|
|
15132
|
+
if (node.callee.type === "Identifier" && node.callee.name === "fetch" && resolvesToGlobalFetch(context, node.callee)) {
|
|
14468
15133
|
const urlArg = node.arguments[0];
|
|
14469
|
-
if (urlArg && urlArg.type !== "SpreadElement" && isApiUrl(urlArg, context)) {
|
|
15134
|
+
if (urlArg && urlArg.type !== "SpreadElement" && isApiUrl(urlArg, context, apiPrefixes)) {
|
|
14470
15135
|
const initArg = node.arguments[1];
|
|
14471
15136
|
if (initArg && initArg.type !== "SpreadElement") {
|
|
14472
15137
|
const resolvedInit = resolveNode(initArg, context);
|
|
@@ -14483,7 +15148,7 @@ var prefer_server_actions_default = createRule({
|
|
|
14483
15148
|
const hasHandlerArg = node.arguments.some(
|
|
14484
15149
|
(arg) => arg.type !== "SpreadElement" && isFunctionArgument(arg, context)
|
|
14485
15150
|
);
|
|
14486
|
-
if (urlArg && urlArg.type !== "SpreadElement" && !hasHandlerArg && isApiUrl(urlArg, context)) {
|
|
15151
|
+
if (urlArg && urlArg.type !== "SpreadElement" && !hasHandlerArg && isApiUrl(urlArg, context, apiPrefixes)) {
|
|
14487
15152
|
isMutation = true;
|
|
14488
15153
|
}
|
|
14489
15154
|
}
|
|
@@ -14494,7 +15159,7 @@ var prefer_server_actions_default = createRule({
|
|
|
14494
15159
|
if (configArg && configArg.type === "ObjectExpression") {
|
|
14495
15160
|
const urlNode = getPropertyNode(configArg, "url");
|
|
14496
15161
|
const methodNode = getPropertyNode(configArg, "method");
|
|
14497
|
-
if (urlNode && isApiUrl(urlNode, context) && methodNode && isMutationMethod(methodNode, context)) {
|
|
15162
|
+
if (urlNode && isApiUrl(urlNode, context, apiPrefixes) && methodNode && isMutationMethod(methodNode, context)) {
|
|
14498
15163
|
isMutation = true;
|
|
14499
15164
|
}
|
|
14500
15165
|
}
|
|
@@ -14786,7 +15451,7 @@ var prefer_whole_object_assertion_default = createRule({
|
|
|
14786
15451
|
});
|
|
14787
15452
|
|
|
14788
15453
|
// src/rules/repeated-static-call-cases.ts
|
|
14789
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES68, ASTUtils as
|
|
15454
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES68, ASTUtils as ASTUtils22 } from "@typescript-eslint/utils";
|
|
14790
15455
|
var REPEATED_STATIC_CALL_CASES_DOCUMENTATION = {
|
|
14791
15456
|
summary: "Report three or more consecutive literal call assertions that should be independently named test cases.",
|
|
14792
15457
|
rationale: "Copy-pasted cases obscure the input table and stop later cases from being reported after the first failure.",
|
|
@@ -14812,7 +15477,7 @@ function staticMemberName5(node) {
|
|
|
14812
15477
|
return null;
|
|
14813
15478
|
}
|
|
14814
15479
|
function importedName6(identifier, context, modules) {
|
|
14815
|
-
const variable =
|
|
15480
|
+
const variable = ASTUtils22.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
14816
15481
|
if (variable === null || variable.defs.length === 0) return identifier.name;
|
|
14817
15482
|
for (const definition of variable.defs) {
|
|
14818
15483
|
if (definition.node.type !== AST_NODE_TYPES68.ImportSpecifier) continue;
|
|
@@ -15813,7 +16478,7 @@ var require_assert_never_default = createRule({
|
|
|
15813
16478
|
});
|
|
15814
16479
|
|
|
15815
16480
|
// src/rules/require-fetch-timeout.ts
|
|
15816
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES71, ASTUtils as
|
|
16481
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES71, ASTUtils as ASTUtils23 } from "@typescript-eslint/utils";
|
|
15817
16482
|
var REQUIRE_FETCH_TIMEOUT_DOCUMENTATION = {
|
|
15818
16483
|
summary: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever.",
|
|
15819
16484
|
rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
|
|
@@ -15894,7 +16559,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
15894
16559
|
}
|
|
15895
16560
|
function resolvesToGlobal(identifier) {
|
|
15896
16561
|
const scope = context.sourceCode.getScope(identifier);
|
|
15897
|
-
const variable =
|
|
16562
|
+
const variable = ASTUtils23.findVariable(scope, identifier.name);
|
|
15898
16563
|
return variable === null || variable.defs.length === 0;
|
|
15899
16564
|
}
|
|
15900
16565
|
function isGlobalFetchCall2(callee) {
|
|
@@ -15904,7 +16569,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
15904
16569
|
return callee.type === AST_NODE_TYPES71.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES71.Identifier && callee.property.name === "fetch" && callee.object.type === AST_NODE_TYPES71.Identifier && GLOBAL_OBJECTS2.has(callee.object.name) && resolvesToGlobal(callee.object);
|
|
15905
16570
|
}
|
|
15906
16571
|
function localConstInitProvablyLacksSignal(identifier) {
|
|
15907
|
-
const variable =
|
|
16572
|
+
const variable = ASTUtils23.findVariable(
|
|
15908
16573
|
context.sourceCode.getScope(identifier),
|
|
15909
16574
|
identifier.name
|
|
15910
16575
|
);
|
|
@@ -16993,7 +17658,7 @@ var require_static_next_matcher_default = createRule({
|
|
|
16993
17658
|
});
|
|
16994
17659
|
|
|
16995
17660
|
// src/rules/require-use-form-default-values.ts
|
|
16996
|
-
import { ASTUtils as
|
|
17661
|
+
import { ASTUtils as ASTUtils24 } from "@typescript-eslint/utils";
|
|
16997
17662
|
var REQUIRE_USE_FORM_DEFAULT_VALUES_DOCUMENTATION = {
|
|
16998
17663
|
summary: "react-hook-form useForm call without defaultValues",
|
|
16999
17664
|
rationale: "Without an explicit initial value, fields can change from uncontrolled to controlled as data arrives, reset behavior becomes ambiguous, and the form's initial shape no longer documents the values users can edit.",
|
|
@@ -17047,13 +17712,13 @@ var require_use_form_default_values_default = createRule({
|
|
|
17047
17712
|
if (node.source.value !== "react-hook-form") return;
|
|
17048
17713
|
for (const specifier of node.specifiers) {
|
|
17049
17714
|
if (specifier.type !== "ImportSpecifier" || (specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value) !== "useForm") continue;
|
|
17050
|
-
const variable =
|
|
17715
|
+
const variable = ASTUtils24.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
|
|
17051
17716
|
if (variable) importedHooks.add(variable);
|
|
17052
17717
|
}
|
|
17053
17718
|
},
|
|
17054
17719
|
CallExpression(node) {
|
|
17055
17720
|
if (node.callee.type !== "Identifier") return;
|
|
17056
|
-
const variable =
|
|
17721
|
+
const variable = ASTUtils24.findVariable(context.sourceCode.getScope(node.callee), node.callee.name);
|
|
17057
17722
|
const options = node.arguments[0];
|
|
17058
17723
|
if (!variable || !importedHooks.has(variable) || options !== void 0 && options.type !== "ObjectExpression" || hasDefaultValues(options)) return;
|
|
17059
17724
|
context.report({ node, messageId: "requireUseFormDefaultValues" });
|
|
@@ -17131,7 +17796,7 @@ var require_use_server_in_actions_file_default = createRule({
|
|
|
17131
17796
|
// src/rules/require-zod-form-validation.ts
|
|
17132
17797
|
import {
|
|
17133
17798
|
AST_NODE_TYPES as AST_NODE_TYPES76,
|
|
17134
|
-
ASTUtils as
|
|
17799
|
+
ASTUtils as ASTUtils25
|
|
17135
17800
|
} from "@typescript-eslint/utils";
|
|
17136
17801
|
var REQUIRE_ZOD_FORM_VALIDATION_DOCUMENTATION = {
|
|
17137
17802
|
summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
|
|
@@ -17199,7 +17864,7 @@ var require_zod_form_validation_default = createRule({
|
|
|
17199
17864
|
return {};
|
|
17200
17865
|
}
|
|
17201
17866
|
const zodBindings = /* @__PURE__ */ new Set();
|
|
17202
|
-
const resolvedBinding = (identifier) =>
|
|
17867
|
+
const resolvedBinding = (identifier) => ASTUtils25.findVariable(
|
|
17203
17868
|
context.sourceCode.getScope(identifier),
|
|
17204
17869
|
identifier.name
|
|
17205
17870
|
);
|
|
@@ -17496,7 +18161,7 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
17496
18161
|
});
|
|
17497
18162
|
|
|
17498
18163
|
// src/rules/stepdown.ts
|
|
17499
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES77, ASTUtils as
|
|
18164
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES77, ASTUtils as ASTUtils26 } from "@typescript-eslint/utils";
|
|
17500
18165
|
var STEPDOWN_DOCUMENTATION = {
|
|
17501
18166
|
summary: "Place a private helper below its sole direct same-scope caller.",
|
|
17502
18167
|
rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
|
|
@@ -17701,7 +18366,7 @@ function methodName(node) {
|
|
|
17701
18366
|
return !node.computed && node.key.type === AST_NODE_TYPES77.Identifier ? node.key.name : null;
|
|
17702
18367
|
}
|
|
17703
18368
|
function referencedMethod(context, node, classVariables) {
|
|
17704
|
-
const objectVariable = node.object.type === AST_NODE_TYPES77.Identifier ?
|
|
18369
|
+
const objectVariable = node.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils26.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
|
|
17705
18370
|
const isClassReference = objectVariable !== null && classVariables.has(objectVariable);
|
|
17706
18371
|
if (node.object.type !== AST_NODE_TYPES77.ThisExpression && !isClassReference) return null;
|
|
17707
18372
|
if (node.property.type === AST_NODE_TYPES77.PrivateIdentifier) return `#${node.property.name}`;
|
|
@@ -17754,11 +18419,11 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
17754
18419
|
const pinned = /* @__PURE__ */ new Set();
|
|
17755
18420
|
const classVariables = /* @__PURE__ */ new Set();
|
|
17756
18421
|
if (node.id !== null) {
|
|
17757
|
-
const internal =
|
|
18422
|
+
const internal = ASTUtils26.findVariable(context.sourceCode.getScope(node), node.id.name);
|
|
17758
18423
|
if (internal !== null) classVariables.add(internal);
|
|
17759
18424
|
}
|
|
17760
18425
|
if (node.type === AST_NODE_TYPES77.ClassExpression && node.parent.type === AST_NODE_TYPES77.VariableDeclarator && node.parent.id.type === AST_NODE_TYPES77.Identifier) {
|
|
17761
|
-
const outer =
|
|
18426
|
+
const outer = ASTUtils26.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
|
|
17762
18427
|
if (outer !== null) classVariables.add(outer);
|
|
17763
18428
|
}
|
|
17764
18429
|
for (const method of methods) {
|
|
@@ -17794,7 +18459,7 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
17794
18459
|
return;
|
|
17795
18460
|
}
|
|
17796
18461
|
if (binding.type !== AST_NODE_TYPES77.Identifier) return;
|
|
17797
|
-
const variable =
|
|
18462
|
+
const variable = ASTUtils26.findVariable(context.sourceCode.getScope(binding), binding.name);
|
|
17798
18463
|
if (variable !== null) {
|
|
17799
18464
|
methodClassVariables.add(variable);
|
|
17800
18465
|
methodAliases.add(variable);
|
|
@@ -17824,7 +18489,7 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
17824
18489
|
return;
|
|
17825
18490
|
}
|
|
17826
18491
|
if (!privateNames.has(target)) return;
|
|
17827
|
-
const objectVariable = current.object.type === AST_NODE_TYPES77.Identifier ?
|
|
18492
|
+
const objectVariable = current.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils26.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
|
|
17828
18493
|
if (objectVariable !== null && methodAliases.has(objectVariable)) {
|
|
17829
18494
|
pinned.add(target);
|
|
17830
18495
|
return;
|
|
@@ -18405,7 +19070,7 @@ var iac_source_coupled_test_default = createSourceCoupledRule(
|
|
|
18405
19070
|
// src/rules/require-pascal-case-zod-schema-name.ts
|
|
18406
19071
|
import {
|
|
18407
19072
|
AST_NODE_TYPES as AST_NODE_TYPES80,
|
|
18408
|
-
ASTUtils as
|
|
19073
|
+
ASTUtils as ASTUtils27
|
|
18409
19074
|
} from "@typescript-eslint/utils";
|
|
18410
19075
|
var REQUIRE_PASCAL_CASE_ZOD_SCHEMA_NAME_DOCUMENTATION = {
|
|
18411
19076
|
summary: "Require confirmed module-level Zod schema contracts to use PascalCase with a `Schema` suffix.",
|
|
@@ -18425,8 +19090,8 @@ var REQUIRE_PASCAL_CASE_ZOD_SCHEMA_NAME_DOCUMENTATION = {
|
|
|
18425
19090
|
]
|
|
18426
19091
|
};
|
|
18427
19092
|
var PASCAL_SCHEMA_NAME_RE = /^[A-Z][A-Za-z0-9]*Schema$/;
|
|
18428
|
-
var
|
|
18429
|
-
var
|
|
19093
|
+
var BENCHMARK_PATH_RE2 = /(^|[\\/])(?:benchmarks?|bench)[\\/]/;
|
|
19094
|
+
var NON_SCHEMA_TERMINALS2 = /* @__PURE__ */ new Set([
|
|
18430
19095
|
"parse",
|
|
18431
19096
|
"parseAsync",
|
|
18432
19097
|
"safeParse",
|
|
@@ -18539,7 +19204,7 @@ var SCHEMA_RETURNING_METHODS = /* @__PURE__ */ new Set([
|
|
|
18539
19204
|
"transform"
|
|
18540
19205
|
]);
|
|
18541
19206
|
var terminalMethodName = (callee) => !callee.computed && callee.property.type === AST_NODE_TYPES80.Identifier ? callee.property.name : null;
|
|
18542
|
-
var
|
|
19207
|
+
var calleeChainRoot2 = (node) => {
|
|
18543
19208
|
let current = node;
|
|
18544
19209
|
for (; ; ) {
|
|
18545
19210
|
if (current.type === AST_NODE_TYPES80.Identifier) {
|
|
@@ -18556,7 +19221,7 @@ var calleeChainRoot = (node) => {
|
|
|
18556
19221
|
return null;
|
|
18557
19222
|
}
|
|
18558
19223
|
};
|
|
18559
|
-
var
|
|
19224
|
+
var chainMemberNames2 = (node) => {
|
|
18560
19225
|
const names = [];
|
|
18561
19226
|
let current = node;
|
|
18562
19227
|
for (; ; ) {
|
|
@@ -18606,7 +19271,7 @@ var require_pascal_case_zod_schema_name_default = createRule({
|
|
|
18606
19271
|
const zodBindings = /* @__PURE__ */ new Set();
|
|
18607
19272
|
const schemaBindings = /* @__PURE__ */ new Set();
|
|
18608
19273
|
function resolvedBinding(identifier) {
|
|
18609
|
-
return
|
|
19274
|
+
return ASTUtils27.findVariable(
|
|
18610
19275
|
context.sourceCode.getScope(identifier),
|
|
18611
19276
|
identifier.name
|
|
18612
19277
|
);
|
|
@@ -18616,7 +19281,7 @@ var require_pascal_case_zod_schema_name_default = createRule({
|
|
|
18616
19281
|
if (binding !== null) zodBindings.add(binding);
|
|
18617
19282
|
}
|
|
18618
19283
|
function isZodChain(node) {
|
|
18619
|
-
const root =
|
|
19284
|
+
const root = calleeChainRoot2(node);
|
|
18620
19285
|
if (root === null) return false;
|
|
18621
19286
|
const binding = resolvedBinding(root);
|
|
18622
19287
|
return binding !== null && zodBindings.has(binding);
|
|
@@ -18632,16 +19297,16 @@ var require_pascal_case_zod_schema_name_default = createRule({
|
|
|
18632
19297
|
return false;
|
|
18633
19298
|
}
|
|
18634
19299
|
const terminal = terminalMethodName(init.callee);
|
|
18635
|
-
if (terminal === null ||
|
|
18636
|
-
const names =
|
|
19300
|
+
if (terminal === null || NON_SCHEMA_TERMINALS2.has(terminal)) return false;
|
|
19301
|
+
const names = chainMemberNames2(init.callee);
|
|
18637
19302
|
if (names.length === 0) return false;
|
|
18638
19303
|
if (isZodChain(init.callee)) {
|
|
18639
19304
|
return ZOD_SCHEMA_FACTORIES.has(names[0] ?? "") || ZOD_FACTORY_NAMESPACES.has(names[0] ?? "") && ZOD_SCHEMA_FACTORIES.has(names[1] ?? "");
|
|
18640
19305
|
}
|
|
18641
|
-
const root =
|
|
19306
|
+
const root = calleeChainRoot2(init.callee);
|
|
18642
19307
|
return root !== null && isSchemaBinding(root) && SCHEMA_RETURNING_METHODS.has(terminal);
|
|
18643
19308
|
}
|
|
18644
|
-
if (isTestFile(context.filename) ||
|
|
19309
|
+
if (isTestFile(context.filename) || BENCHMARK_PATH_RE2.test(context.filename.replaceAll("\\", "/")) || isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
18645
19310
|
return {};
|
|
18646
19311
|
}
|
|
18647
19312
|
return {
|
|
@@ -18844,14 +19509,14 @@ var RULES = {
|
|
|
18844
19509
|
"require-static-next-matcher": require_static_next_matcher_default,
|
|
18845
19510
|
"require-zod-form-validation": require_zod_form_validation_default,
|
|
18846
19511
|
"store-insert-requires-on-conflict": store_insert_requires_on_conflict_default,
|
|
18847
|
-
stepdown: stepdown_default,
|
|
19512
|
+
"stepdown": stepdown_default,
|
|
18848
19513
|
"source-coupled-test": source_coupled_test_default,
|
|
18849
19514
|
"sole-export-matches-filename": sole_export_matches_filename_default,
|
|
18850
19515
|
"require-pascal-case-zod-schema-name": require_pascal_case_zod_schema_name_default
|
|
18851
19516
|
};
|
|
18852
19517
|
var meta = {
|
|
18853
19518
|
name: "@sarj/eslint-plugin",
|
|
18854
|
-
version: "15.17.
|
|
19519
|
+
version: "15.17.3"
|
|
18855
19520
|
};
|
|
18856
19521
|
var APPLICATION_ONLY_RULES = [
|
|
18857
19522
|
"no-restricted-library-load",
|