@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.cjs
CHANGED
|
@@ -5890,7 +5890,7 @@ var NO_RAW_FETCH_OUTSIDE_CLIENTS_DOCUMENTATION = {
|
|
|
5890
5890
|
rationale: "Scattered fetch calls bypass shared transport policy and are harder to stub and observe consistently.",
|
|
5891
5891
|
remediation: "Move the request into a client module and call that abstraction from application code.",
|
|
5892
5892
|
category: "architecture",
|
|
5893
|
-
limitations: ["Tests, client-layer paths, constructed handoffs, and pre-signed URL transfers are excluded."],
|
|
5893
|
+
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."],
|
|
5894
5894
|
examples: [
|
|
5895
5895
|
{ 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 },
|
|
5896
5896
|
{ 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 }
|
|
@@ -5935,7 +5935,10 @@ var ANALYTICS_SEGMENTS2 = /* @__PURE__ */ new Set([
|
|
|
5935
5935
|
]);
|
|
5936
5936
|
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\/)/;
|
|
5937
5937
|
var NON_REACT_FRAMEWORK_RE = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
|
|
5938
|
-
var
|
|
5938
|
+
var BASE_PATH_RE = /^\/(?!$)(?!.*[?#])(?:[^/]+\/)*[^/]+$/u;
|
|
5939
|
+
function isValidBasePath(basePath) {
|
|
5940
|
+
return BASE_PATH_RE.test(basePath) && !basePath.split("/").some((segment) => segment === "." || segment === "..");
|
|
5941
|
+
}
|
|
5939
5942
|
function isGlobalFetchCall(node, resolvesToGlobal) {
|
|
5940
5943
|
const callee = node.callee;
|
|
5941
5944
|
if (callee.type === "Identifier") {
|
|
@@ -6031,6 +6034,10 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
6031
6034
|
type: "array",
|
|
6032
6035
|
items: { type: "string" },
|
|
6033
6036
|
description: "Regular-expression sources matched against the filename. Replaces the defaults."
|
|
6037
|
+
},
|
|
6038
|
+
basePath: {
|
|
6039
|
+
type: "string",
|
|
6040
|
+
pattern: "^/(?!$)(?!.*[?#])(?!(?:.*/)?\\.\\.?(?:/|$))(?:[^/]+/)*[^/]+$"
|
|
6034
6041
|
}
|
|
6035
6042
|
},
|
|
6036
6043
|
additionalProperties: false
|
|
@@ -6052,12 +6059,18 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
6052
6059
|
(statement) => statement.type === import_utils33.AST_NODE_TYPES.ImportDeclaration && typeof statement.source.value === "string" && NON_REACT_FRAMEWORK_RE.test(statement.source.value)
|
|
6053
6060
|
);
|
|
6054
6061
|
const hasUseClientDirective = context.sourceCode.ast.body.some(
|
|
6055
|
-
(statement) => statement.type === import_utils33.AST_NODE_TYPES.ExpressionStatement && statement.
|
|
6062
|
+
(statement) => statement.type === import_utils33.AST_NODE_TYPES.ExpressionStatement && statement.directive === "use client"
|
|
6056
6063
|
);
|
|
6057
|
-
const
|
|
6058
|
-
(statement) => statement.type === import_utils33.AST_NODE_TYPES.
|
|
6064
|
+
const hasUseServerDirective = context.sourceCode.ast.body.some(
|
|
6065
|
+
(statement) => statement.type === import_utils33.AST_NODE_TYPES.ExpressionStatement && statement.directive === "use server"
|
|
6059
6066
|
);
|
|
6060
|
-
const
|
|
6067
|
+
const importsServerOnly = context.sourceCode.ast.body.some(
|
|
6068
|
+
(statement) => statement.type === import_utils33.AST_NODE_TYPES.ImportDeclaration && typeof statement.source.value === "string" && (statement.source.value === "server-only" || statement.source.value === "next/server")
|
|
6069
|
+
);
|
|
6070
|
+
const internalApiPrefixes = ["/api"];
|
|
6071
|
+
if (options?.basePath !== void 0 && isValidBasePath(options.basePath)) {
|
|
6072
|
+
internalApiPrefixes.push(`${options.basePath}/api`);
|
|
6073
|
+
}
|
|
6061
6074
|
function resolvesToGlobal(identifier) {
|
|
6062
6075
|
const variable = import_utils33.ASTUtils.findVariable(
|
|
6063
6076
|
context.sourceCode.getScope(identifier),
|
|
@@ -6090,15 +6103,15 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
6090
6103
|
function isInternalApiUrl(node) {
|
|
6091
6104
|
const resolved = resolveNode2(node ?? void 0);
|
|
6092
6105
|
if (resolved?.type === import_utils33.AST_NODE_TYPES.Literal) {
|
|
6093
|
-
return typeof resolved.value === "string" &&
|
|
6106
|
+
return typeof resolved.value === "string" && internalApiPrefixes.some(
|
|
6107
|
+
(prefix) => resolved.value === prefix || resolved.value.startsWith(`${prefix}/`)
|
|
6108
|
+
);
|
|
6094
6109
|
}
|
|
6095
6110
|
if (resolved?.type === import_utils33.AST_NODE_TYPES.TemplateLiteral) {
|
|
6096
6111
|
const prefix = resolved.quasis[0]?.value.cooked;
|
|
6097
|
-
return typeof prefix === "string" &&
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
const first = resolved.arguments[0];
|
|
6101
|
-
return first !== void 0 && first.type !== import_utils33.AST_NODE_TYPES.SpreadElement ? isInternalApiUrl(first) : false;
|
|
6112
|
+
return typeof prefix === "string" && internalApiPrefixes.some(
|
|
6113
|
+
(apiPrefix) => prefix === apiPrefix || prefix.startsWith(`${apiPrefix}/`)
|
|
6114
|
+
);
|
|
6102
6115
|
}
|
|
6103
6116
|
return resolved?.type === import_utils33.AST_NODE_TYPES.BinaryExpression && resolved.operator === "+" && isInternalApiUrl(resolved.left);
|
|
6104
6117
|
}
|
|
@@ -6118,7 +6131,7 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
6118
6131
|
return resolved?.type === import_utils33.AST_NODE_TYPES.LogicalExpression && resolved.operator === "||" && (isMutationMethod2(resolved.left) || isMutationMethod2(resolved.right));
|
|
6119
6132
|
}
|
|
6120
6133
|
function serverActionOwns(node) {
|
|
6121
|
-
if (node.callee.type !== import_utils33.AST_NODE_TYPES.Identifier || !
|
|
6134
|
+
if (node.callee.type !== import_utils33.AST_NODE_TYPES.Identifier || !hasUseClientDirective || hasUseServerDirective || importsServerOnly || SERVER_ACTION_SKIP_FILE_RE.test(filename) || nonReactFramework) {
|
|
6122
6135
|
return false;
|
|
6123
6136
|
}
|
|
6124
6137
|
const url = node.arguments[0];
|
|
@@ -9775,25 +9788,25 @@ var no_unsafe_mock_casting_default = createRule({
|
|
|
9775
9788
|
}
|
|
9776
9789
|
const directBindings = /* @__PURE__ */ new Set();
|
|
9777
9790
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
9778
|
-
function
|
|
9791
|
+
function resolve2(identifier) {
|
|
9779
9792
|
return import_utils55.ASTUtils.findVariable(
|
|
9780
9793
|
context.sourceCode.getScope(identifier),
|
|
9781
9794
|
identifier.name
|
|
9782
9795
|
);
|
|
9783
9796
|
}
|
|
9784
9797
|
function record(identifier, destination) {
|
|
9785
|
-
const binding =
|
|
9798
|
+
const binding = resolve2(identifier);
|
|
9786
9799
|
if (binding !== null) destination.add(binding);
|
|
9787
9800
|
}
|
|
9788
9801
|
function isMockTypeReference(node) {
|
|
9789
9802
|
if (node.type !== import_utils55.AST_NODE_TYPES.TSTypeReference) return false;
|
|
9790
9803
|
const typeName = node.typeName;
|
|
9791
9804
|
if (typeName.type === import_utils55.AST_NODE_TYPES.Identifier) {
|
|
9792
|
-
const binding =
|
|
9805
|
+
const binding = resolve2(typeName);
|
|
9793
9806
|
return binding !== null && directBindings.has(binding);
|
|
9794
9807
|
}
|
|
9795
9808
|
if (typeName.type === import_utils55.AST_NODE_TYPES.TSQualifiedName && typeName.left.type === import_utils55.AST_NODE_TYPES.Identifier && MOCK_TYPE_NAMES.has(typeName.right.name)) {
|
|
9796
|
-
const binding =
|
|
9809
|
+
const binding = resolve2(typeName.left);
|
|
9797
9810
|
return binding !== null && namespaceBindings.has(binding);
|
|
9798
9811
|
}
|
|
9799
9812
|
return false;
|
|
@@ -11197,6 +11210,10 @@ function unwrapTransparentExport(node) {
|
|
|
11197
11210
|
|
|
11198
11211
|
// src/rules/prefer-shadcn-primitives.ts
|
|
11199
11212
|
var import_utils66 = require("@typescript-eslint/utils");
|
|
11213
|
+
var import_typescript_estree = require("@typescript-eslint/typescript-estree");
|
|
11214
|
+
var import_node_fs = require("fs");
|
|
11215
|
+
var import_node_path = require("path");
|
|
11216
|
+
var import_jsonc_parser = require("jsonc-parser");
|
|
11200
11217
|
var PREFER_SHADCN_PRIMITIVES_DOCUMENTATION = {
|
|
11201
11218
|
summary: "Require visible raw JSX controls to use the corresponding shared shadcn primitive.",
|
|
11202
11219
|
rationale: "Shared primitives centralize interaction, accessibility, and visual behavior across the product.",
|
|
@@ -11204,22 +11221,35 @@ var PREFER_SHADCN_PRIMITIVES_DOCUMENTATION = {
|
|
|
11204
11221
|
category: "style",
|
|
11205
11222
|
limitations: [
|
|
11206
11223
|
"Hidden and file inputs, unassociated labels, and non-control semantic elements are excluded.",
|
|
11207
|
-
"Tests and the shared components/ui primitive implementation tree are excluded."
|
|
11224
|
+
"Tests and the shared components/ui primitive implementation tree are excluded.",
|
|
11225
|
+
"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."
|
|
11208
11226
|
],
|
|
11209
11227
|
examples: [
|
|
11210
11228
|
{ 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 },
|
|
11211
11229
|
{ 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 }
|
|
11212
11230
|
]
|
|
11213
11231
|
};
|
|
11214
|
-
var
|
|
11215
|
-
button: "Button",
|
|
11216
|
-
dialog: "Dialog or AlertDialog family",
|
|
11217
|
-
input: "Input",
|
|
11218
|
-
label: "Label",
|
|
11219
|
-
progress: "Progress",
|
|
11220
|
-
select: "Select family",
|
|
11221
|
-
table: "Table family",
|
|
11222
|
-
textarea: "Textarea"
|
|
11232
|
+
var RAW_PRIMITIVES = {
|
|
11233
|
+
button: { capability: "Button", replacement: "Button" },
|
|
11234
|
+
dialog: { capability: "Dialog", replacement: "Dialog or AlertDialog family" },
|
|
11235
|
+
input: { capability: "Input", replacement: "Input" },
|
|
11236
|
+
label: { capability: "Label", replacement: "Label" },
|
|
11237
|
+
progress: { capability: "Progress", replacement: "Progress" },
|
|
11238
|
+
select: { capability: "Select", replacement: "Select family" },
|
|
11239
|
+
table: { capability: "Table", replacement: "Table family" },
|
|
11240
|
+
textarea: { capability: "Textarea", replacement: "Textarea" }
|
|
11241
|
+
};
|
|
11242
|
+
var CAPABILITIES = {
|
|
11243
|
+
Button: "button",
|
|
11244
|
+
Checkbox: "checkbox",
|
|
11245
|
+
Dialog: "dialog",
|
|
11246
|
+
Input: "input",
|
|
11247
|
+
Label: "label",
|
|
11248
|
+
Progress: "progress",
|
|
11249
|
+
RadioGroup: "radio-group",
|
|
11250
|
+
Select: "select",
|
|
11251
|
+
Table: "table",
|
|
11252
|
+
Textarea: "textarea"
|
|
11223
11253
|
};
|
|
11224
11254
|
var LABELABLE_ELEMENTS = /* @__PURE__ */ new Set([
|
|
11225
11255
|
"button",
|
|
@@ -11231,7 +11261,7 @@ var LABELABLE_ELEMENTS = /* @__PURE__ */ new Set([
|
|
|
11231
11261
|
"textarea"
|
|
11232
11262
|
]);
|
|
11233
11263
|
var SHARED_PRIMITIVE_IMPLEMENTATION_RE = /(?:^|\/)components\/ui(?:\/|$)/i;
|
|
11234
|
-
var SHARED_PRIMITIVE_IMPORT_RE = /(?:^|\/)components\/ui\/[^/]
|
|
11264
|
+
var SHARED_PRIMITIVE_IMPORT_RE = /(?:^|\/)components\/ui\/([^/]+)$/i;
|
|
11235
11265
|
var AMBIGUOUS_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
11236
11266
|
"button",
|
|
11237
11267
|
"color",
|
|
@@ -11240,10 +11270,175 @@ var AMBIGUOUS_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
|
11240
11270
|
"reset",
|
|
11241
11271
|
"submit"
|
|
11242
11272
|
]);
|
|
11273
|
+
var MAX_PROJECT_FILE_BYTES = 1048576;
|
|
11274
|
+
var PROJECT_PRIMITIVES_CACHE = /* @__PURE__ */ new Map();
|
|
11275
|
+
function detectProjectPrimitives(filename, requested) {
|
|
11276
|
+
const packageRoot = findPackageRoot((0, import_node_path.dirname)(filename));
|
|
11277
|
+
if (packageRoot === null) return { available: /* @__PURE__ */ new Set(), uiRoot: null };
|
|
11278
|
+
const manifest = readJsonc((0, import_node_path.join)(packageRoot, "components.json"));
|
|
11279
|
+
const alias = stringProperty(manifest, "aliases", "ui");
|
|
11280
|
+
const unresolvedUiRoot = alias === null ? null : resolveAlias(packageRoot, alias);
|
|
11281
|
+
if (unresolvedUiRoot === null) return { available: /* @__PURE__ */ new Set(), uiRoot: null };
|
|
11282
|
+
const uiRoot = safeContainedDirectory(packageRoot, unresolvedUiRoot);
|
|
11283
|
+
if (uiRoot === null) {
|
|
11284
|
+
return { available: /* @__PURE__ */ new Set(), uiRoot: null };
|
|
11285
|
+
}
|
|
11286
|
+
const moduleCandidates = /* @__PURE__ */ new Map();
|
|
11287
|
+
for (const [capability, moduleName] of Object.entries(CAPABILITIES)) {
|
|
11288
|
+
if (!requested.has(capability)) continue;
|
|
11289
|
+
moduleCandidates.set(capability, ["tsx", "ts", "jsx", "js"].flatMap((extension) => [
|
|
11290
|
+
(0, import_node_path.join)(uiRoot, `${moduleName}.${extension}`),
|
|
11291
|
+
(0, import_node_path.join)(uiRoot, moduleName, `index.${extension}`)
|
|
11292
|
+
]));
|
|
11293
|
+
}
|
|
11294
|
+
const fingerprint = [
|
|
11295
|
+
(0, import_node_path.join)(packageRoot, "components.json"),
|
|
11296
|
+
(0, import_node_path.join)(packageRoot, "tsconfig.json"),
|
|
11297
|
+
(0, import_node_path.join)(packageRoot, "jsconfig.json"),
|
|
11298
|
+
...[...moduleCandidates.values()].flat()
|
|
11299
|
+
].map(fileFingerprint).join("|");
|
|
11300
|
+
const cacheKey = `${packageRoot}:${[...requested].sort().join(",")}`;
|
|
11301
|
+
const cached = PROJECT_PRIMITIVES_CACHE.get(cacheKey);
|
|
11302
|
+
if (cached?.fingerprint === fingerprint) return cached.value;
|
|
11303
|
+
const available = /* @__PURE__ */ new Set();
|
|
11304
|
+
for (const [capability, candidates] of moduleCandidates) {
|
|
11305
|
+
if (candidates.some(
|
|
11306
|
+
(candidate2) => exportsPrimitive(candidate2, capability)
|
|
11307
|
+
)) {
|
|
11308
|
+
available.add(capability);
|
|
11309
|
+
}
|
|
11310
|
+
}
|
|
11311
|
+
const value = { available, uiRoot };
|
|
11312
|
+
PROJECT_PRIMITIVES_CACHE.set(cacheKey, { fingerprint, value });
|
|
11313
|
+
return value;
|
|
11314
|
+
}
|
|
11315
|
+
function fileFingerprint(path) {
|
|
11316
|
+
try {
|
|
11317
|
+
const stat = (0, import_node_fs.lstatSync)(path);
|
|
11318
|
+
return stat.isFile() ? `${path}:${stat.size}:${stat.mtimeMs}` : `${path}:excluded`;
|
|
11319
|
+
} catch {
|
|
11320
|
+
return `${path}:missing`;
|
|
11321
|
+
}
|
|
11322
|
+
}
|
|
11323
|
+
function isWithin2(root, candidate2) {
|
|
11324
|
+
const path = (0, import_node_path.relative)(root, candidate2);
|
|
11325
|
+
return path === "" || !path.startsWith(`..${import_node_path.sep}`) && path !== ".." && !(0, import_node_path.isAbsolute)(path);
|
|
11326
|
+
}
|
|
11327
|
+
function safeContainedDirectory(root, candidate2) {
|
|
11328
|
+
try {
|
|
11329
|
+
if (!(0, import_node_fs.lstatSync)(candidate2).isDirectory()) return null;
|
|
11330
|
+
const realRoot = (0, import_node_fs.realpathSync)(root);
|
|
11331
|
+
const realCandidate = (0, import_node_fs.realpathSync)(candidate2);
|
|
11332
|
+
return isWithin2(realRoot, realCandidate) ? realCandidate : null;
|
|
11333
|
+
} catch {
|
|
11334
|
+
return null;
|
|
11335
|
+
}
|
|
11336
|
+
}
|
|
11337
|
+
function findPackageRoot(startDir) {
|
|
11338
|
+
let dir = startDir;
|
|
11339
|
+
const filesystemRoot = (0, import_node_path.parse)(dir).root;
|
|
11340
|
+
for (; ; ) {
|
|
11341
|
+
if ((0, import_node_fs.existsSync)((0, import_node_path.join)(dir, "package.json"))) return dir;
|
|
11342
|
+
const parent = (0, import_node_path.dirname)(dir);
|
|
11343
|
+
if (dir === filesystemRoot || parent === dir) return null;
|
|
11344
|
+
dir = parent;
|
|
11345
|
+
}
|
|
11346
|
+
}
|
|
11347
|
+
function readJsonc(path) {
|
|
11348
|
+
try {
|
|
11349
|
+
const source = readSmallRegularFile(path);
|
|
11350
|
+
if (source === null) return null;
|
|
11351
|
+
const errors = [];
|
|
11352
|
+
const value = (0, import_jsonc_parser.parse)(source, errors, {
|
|
11353
|
+
allowTrailingComma: true,
|
|
11354
|
+
disallowComments: false
|
|
11355
|
+
});
|
|
11356
|
+
return errors.length === 0 ? value : null;
|
|
11357
|
+
} catch {
|
|
11358
|
+
return null;
|
|
11359
|
+
}
|
|
11360
|
+
}
|
|
11361
|
+
function readSmallRegularFile(path) {
|
|
11362
|
+
try {
|
|
11363
|
+
const stat = (0, import_node_fs.lstatSync)(path);
|
|
11364
|
+
if (!stat.isFile() || stat.size > MAX_PROJECT_FILE_BYTES) return null;
|
|
11365
|
+
return (0, import_node_fs.readFileSync)(path, "utf8");
|
|
11366
|
+
} catch {
|
|
11367
|
+
return null;
|
|
11368
|
+
}
|
|
11369
|
+
}
|
|
11370
|
+
function stringProperty(value, ...keys) {
|
|
11371
|
+
let current = value;
|
|
11372
|
+
for (const key of keys) {
|
|
11373
|
+
if (typeof current !== "object" || current === null || !(key in current)) return null;
|
|
11374
|
+
current = current[key];
|
|
11375
|
+
}
|
|
11376
|
+
return typeof current === "string" && current.trim() !== "" ? current : null;
|
|
11377
|
+
}
|
|
11378
|
+
function resolveAlias(packageRoot, alias) {
|
|
11379
|
+
if ((0, import_node_path.isAbsolute)(alias)) return null;
|
|
11380
|
+
if (alias.startsWith(".")) return (0, import_node_path.resolve)(packageRoot, alias);
|
|
11381
|
+
const configPath = ["tsconfig.json", "jsconfig.json"].map((name) => (0, import_node_path.join)(packageRoot, name)).find(import_node_fs.existsSync);
|
|
11382
|
+
if (configPath === void 0) return null;
|
|
11383
|
+
const config = readJsonc(configPath);
|
|
11384
|
+
if (typeof config !== "object" || config === null) return null;
|
|
11385
|
+
const compilerOptions = config["compilerOptions"];
|
|
11386
|
+
if (typeof compilerOptions !== "object" || compilerOptions === null) return null;
|
|
11387
|
+
const options = compilerOptions;
|
|
11388
|
+
const baseUrl = typeof options["baseUrl"] === "string" ? options["baseUrl"] : ".";
|
|
11389
|
+
const paths = options["paths"];
|
|
11390
|
+
if (typeof paths !== "object" || paths === null) return null;
|
|
11391
|
+
const matches = [];
|
|
11392
|
+
for (const [pattern, rawTargets] of Object.entries(paths)) {
|
|
11393
|
+
if (!Array.isArray(rawTargets) || rawTargets.length !== 1) {
|
|
11394
|
+
continue;
|
|
11395
|
+
}
|
|
11396
|
+
const [target] = rawTargets;
|
|
11397
|
+
if (typeof target !== "string") continue;
|
|
11398
|
+
const star = pattern.indexOf("*");
|
|
11399
|
+
if (star === -1) {
|
|
11400
|
+
if (pattern === alias) matches.push(target);
|
|
11401
|
+
continue;
|
|
11402
|
+
}
|
|
11403
|
+
const prefix = pattern.slice(0, star);
|
|
11404
|
+
const suffix = pattern.slice(star + 1);
|
|
11405
|
+
if (!alias.startsWith(prefix) || !alias.endsWith(suffix)) continue;
|
|
11406
|
+
const substitution = alias.slice(prefix.length, alias.length - suffix.length);
|
|
11407
|
+
matches.push(target.replace("*", substitution));
|
|
11408
|
+
}
|
|
11409
|
+
const [match] = matches;
|
|
11410
|
+
if (matches.length !== 1 || match === void 0) return null;
|
|
11411
|
+
return (0, import_node_path.resolve)(packageRoot, baseUrl, match);
|
|
11412
|
+
}
|
|
11413
|
+
function exportsPrimitive(path, exportName) {
|
|
11414
|
+
try {
|
|
11415
|
+
const source = readSmallRegularFile(path);
|
|
11416
|
+
if (source === null) return false;
|
|
11417
|
+
const program = (0, import_typescript_estree.parse)(source, { jsx: true, sourceType: "module" });
|
|
11418
|
+
return program.body.some((statement) => {
|
|
11419
|
+
if (statement.type !== import_utils66.AST_NODE_TYPES.ExportNamedDeclaration) return false;
|
|
11420
|
+
if (statement.exportKind === "type") return false;
|
|
11421
|
+
if (statement.specifiers.some(
|
|
11422
|
+
(specifier) => specifier.type === import_utils66.AST_NODE_TYPES.ExportSpecifier && specifier.exportKind !== "type" && specifier.exported.type === import_utils66.AST_NODE_TYPES.Identifier && specifier.exported.name === exportName
|
|
11423
|
+
)) {
|
|
11424
|
+
return true;
|
|
11425
|
+
}
|
|
11426
|
+
const declaration = statement.declaration;
|
|
11427
|
+
if (declaration?.type === import_utils66.AST_NODE_TYPES.VariableDeclaration) {
|
|
11428
|
+
return declaration.declarations.some(
|
|
11429
|
+
(item) => item.id.type === import_utils66.AST_NODE_TYPES.Identifier && item.id.name === exportName
|
|
11430
|
+
);
|
|
11431
|
+
}
|
|
11432
|
+
return (declaration?.type === import_utils66.AST_NODE_TYPES.FunctionDeclaration || declaration?.type === import_utils66.AST_NODE_TYPES.ClassDeclaration) && declaration.id?.name === exportName;
|
|
11433
|
+
});
|
|
11434
|
+
} catch {
|
|
11435
|
+
return false;
|
|
11436
|
+
}
|
|
11437
|
+
}
|
|
11243
11438
|
function rawElementName(node) {
|
|
11244
11439
|
if (node.name.type !== import_utils66.AST_NODE_TYPES.JSXIdentifier) return null;
|
|
11245
11440
|
const name = node.name.name;
|
|
11246
|
-
return Object.hasOwn(
|
|
11441
|
+
return Object.hasOwn(RAW_PRIMITIVES, name) ? name : null;
|
|
11247
11442
|
}
|
|
11248
11443
|
function effectiveAttribute(node, attributeName) {
|
|
11249
11444
|
for (const attribute of node.attributes.toReversed()) {
|
|
@@ -11312,15 +11507,19 @@ function isStaticallyAssociatedLabel(node) {
|
|
|
11312
11507
|
return node.parent.type === import_utils66.AST_NODE_TYPES.JSXElement && containsLabelableElement(node.parent);
|
|
11313
11508
|
}
|
|
11314
11509
|
function replacementFor(node, element) {
|
|
11315
|
-
if (element !== "input") return
|
|
11510
|
+
if (element !== "input") return RAW_PRIMITIVES[element];
|
|
11316
11511
|
const typeAttribute = effectiveAttribute(node, "type");
|
|
11317
11512
|
if (typeAttribute.kind === "unknown") return null;
|
|
11318
11513
|
const inputType = typeAttribute.kind === "known" ? typeAttribute.value.toLowerCase() : "text";
|
|
11319
11514
|
if (inputType === "hidden" || inputType === "file") return null;
|
|
11320
|
-
if (inputType === "checkbox")
|
|
11321
|
-
|
|
11515
|
+
if (inputType === "checkbox") {
|
|
11516
|
+
return { capability: "Checkbox", replacement: "Checkbox" };
|
|
11517
|
+
}
|
|
11518
|
+
if (inputType === "radio") {
|
|
11519
|
+
return { capability: "RadioGroup", replacement: "RadioGroup family" };
|
|
11520
|
+
}
|
|
11322
11521
|
if (AMBIGUOUS_INPUT_TYPES.has(inputType)) return null;
|
|
11323
|
-
return
|
|
11522
|
+
return RAW_PRIMITIVES.input;
|
|
11324
11523
|
}
|
|
11325
11524
|
var prefer_shadcn_primitives_default = createRule({
|
|
11326
11525
|
name: "prefer-shadcn-primitives",
|
|
@@ -11334,7 +11533,8 @@ var prefer_shadcn_primitives_default = createRule({
|
|
|
11334
11533
|
{
|
|
11335
11534
|
type: "object",
|
|
11336
11535
|
properties: {
|
|
11337
|
-
assumeAvailable: { type: "boolean" }
|
|
11536
|
+
assumeAvailable: { type: "boolean" },
|
|
11537
|
+
detectProjectPrimitives: { type: "boolean" }
|
|
11338
11538
|
},
|
|
11339
11539
|
additionalProperties: false
|
|
11340
11540
|
}
|
|
@@ -11346,15 +11546,21 @@ var prefer_shadcn_primitives_default = createRule({
|
|
|
11346
11546
|
defaultOptions: [{}],
|
|
11347
11547
|
create(context, [options]) {
|
|
11348
11548
|
const filename = context.filename.replaceAll("\\", "/");
|
|
11349
|
-
if (isTestFile(filename) || SHARED_PRIMITIVE_IMPLEMENTATION_RE.test(filename)) {
|
|
11549
|
+
if (isTestFile(filename) || isStoryFile(filename) || isGeneratedFile(filename, context.sourceCode.text) || SHARED_PRIMITIVE_IMPLEMENTATION_RE.test(filename)) {
|
|
11350
11550
|
return {};
|
|
11351
11551
|
}
|
|
11352
|
-
|
|
11552
|
+
const detectsProject = options?.detectProjectPrimitives === true;
|
|
11553
|
+
let hasSharedPrimitiveImport = false;
|
|
11554
|
+
const importedCapabilities = /* @__PURE__ */ new Set();
|
|
11353
11555
|
const candidates = [];
|
|
11354
11556
|
return {
|
|
11355
11557
|
ImportDeclaration(node) {
|
|
11356
|
-
if (typeof node.source.value
|
|
11357
|
-
|
|
11558
|
+
if (typeof node.source.value !== "string") return;
|
|
11559
|
+
const match = SHARED_PRIMITIVE_IMPORT_RE.exec(node.source.value);
|
|
11560
|
+
if (match?.[1] === void 0) return;
|
|
11561
|
+
hasSharedPrimitiveImport = true;
|
|
11562
|
+
for (const [capability, moduleName] of Object.entries(CAPABILITIES)) {
|
|
11563
|
+
if (match[1].toLowerCase() === moduleName) importedCapabilities.add(capability);
|
|
11358
11564
|
}
|
|
11359
11565
|
},
|
|
11360
11566
|
JSXOpeningElement(node) {
|
|
@@ -11363,11 +11569,15 @@ var prefer_shadcn_primitives_default = createRule({
|
|
|
11363
11569
|
if (element === "label" && !isStaticallyAssociatedLabel(node)) return;
|
|
11364
11570
|
const replacement = replacementFor(node, element);
|
|
11365
11571
|
if (replacement === null) return;
|
|
11366
|
-
candidates.push({ element, node, replacement });
|
|
11572
|
+
candidates.push({ element, node, ...replacement });
|
|
11367
11573
|
},
|
|
11368
11574
|
"Program:exit"() {
|
|
11369
|
-
|
|
11370
|
-
|
|
11575
|
+
const requested = new Set(candidates.map(({ capability }) => capability));
|
|
11576
|
+
const projectPrimitives = detectsProject && requested.size > 0 ? detectProjectPrimitives(context.filename, requested) : { available: /* @__PURE__ */ new Set(), uiRoot: null };
|
|
11577
|
+
if (projectPrimitives.uiRoot !== null && isWithin2(projectPrimitives.uiRoot, realpathOrOriginal(context.filename))) return;
|
|
11578
|
+
for (const { capability, element, node, replacement } of candidates) {
|
|
11579
|
+
const available = options?.assumeAvailable === true || (detectsProject ? projectPrimitives.available.has(capability) || importedCapabilities.has(capability) : hasSharedPrimitiveImport);
|
|
11580
|
+
if (!available) continue;
|
|
11371
11581
|
context.report({
|
|
11372
11582
|
node,
|
|
11373
11583
|
messageId: "preferShadcnPrimitive",
|
|
@@ -11378,6 +11588,13 @@ var prefer_shadcn_primitives_default = createRule({
|
|
|
11378
11588
|
};
|
|
11379
11589
|
}
|
|
11380
11590
|
});
|
|
11591
|
+
function realpathOrOriginal(path) {
|
|
11592
|
+
try {
|
|
11593
|
+
return (0, import_node_fs.realpathSync)(path);
|
|
11594
|
+
} catch {
|
|
11595
|
+
return path;
|
|
11596
|
+
}
|
|
11597
|
+
}
|
|
11381
11598
|
|
|
11382
11599
|
// src/rules/prefer-module-level-constant.ts
|
|
11383
11600
|
var import_utils67 = require("@typescript-eslint/utils");
|
|
@@ -12048,99 +12265,367 @@ var prefer_module_level_schema_default = createRule({
|
|
|
12048
12265
|
|
|
12049
12266
|
// src/rules/prefer-module-level-refined-schema.ts
|
|
12050
12267
|
var import_utils69 = require("@typescript-eslint/utils");
|
|
12268
|
+
var BENCHMARK_PATH_RE = /(^|[/\\])(?:benchmarks?|bench)[/\\]/;
|
|
12051
12269
|
var FACTORIES = /* @__PURE__ */ new Set([
|
|
12052
12270
|
"array",
|
|
12271
|
+
"base64",
|
|
12272
|
+
"base64url",
|
|
12053
12273
|
"bigint",
|
|
12054
12274
|
"boolean",
|
|
12275
|
+
"cidrv4",
|
|
12276
|
+
"cidrv6",
|
|
12277
|
+
"codec",
|
|
12278
|
+
"custom",
|
|
12055
12279
|
"date",
|
|
12056
|
-
"
|
|
12057
|
-
"
|
|
12058
|
-
]);
|
|
12059
|
-
var REFINEMENTS = /* @__PURE__ */ new Set([
|
|
12060
|
-
"brand",
|
|
12061
|
-
"check",
|
|
12280
|
+
"datetime",
|
|
12281
|
+
"duration",
|
|
12062
12282
|
"email",
|
|
12063
|
-
"
|
|
12064
|
-
"
|
|
12065
|
-
"
|
|
12066
|
-
"
|
|
12067
|
-
"
|
|
12068
|
-
"
|
|
12069
|
-
"
|
|
12070
|
-
"
|
|
12071
|
-
"
|
|
12072
|
-
"
|
|
12073
|
-
"
|
|
12074
|
-
"
|
|
12075
|
-
"
|
|
12076
|
-
"
|
|
12283
|
+
"emoji",
|
|
12284
|
+
"enum",
|
|
12285
|
+
"file",
|
|
12286
|
+
"function",
|
|
12287
|
+
"hash",
|
|
12288
|
+
"hex",
|
|
12289
|
+
"hostname",
|
|
12290
|
+
"instanceof",
|
|
12291
|
+
"ipv4",
|
|
12292
|
+
"ipv6",
|
|
12293
|
+
"json",
|
|
12294
|
+
"jwt",
|
|
12295
|
+
"literal",
|
|
12296
|
+
"map",
|
|
12297
|
+
"nan",
|
|
12298
|
+
"nativeEnum",
|
|
12299
|
+
"never",
|
|
12300
|
+
"null",
|
|
12301
|
+
"nullable",
|
|
12302
|
+
"nullish",
|
|
12303
|
+
"number",
|
|
12304
|
+
"optional",
|
|
12305
|
+
"partialRecord",
|
|
12306
|
+
"preprocess",
|
|
12307
|
+
"promise",
|
|
12308
|
+
"set",
|
|
12309
|
+
"string",
|
|
12310
|
+
"stringbool",
|
|
12311
|
+
"symbol",
|
|
12312
|
+
"templateLiteral",
|
|
12313
|
+
"time",
|
|
12314
|
+
"undefined",
|
|
12077
12315
|
"url",
|
|
12078
|
-
"uuid"
|
|
12316
|
+
"uuid",
|
|
12317
|
+
"void"
|
|
12318
|
+
]);
|
|
12319
|
+
var COMPOSITE_FACTORIES = /* @__PURE__ */ new Set([
|
|
12320
|
+
"discriminatedUnion",
|
|
12321
|
+
"intersection",
|
|
12322
|
+
"looseObject",
|
|
12323
|
+
"object",
|
|
12324
|
+
"record",
|
|
12325
|
+
"strictObject",
|
|
12326
|
+
"tuple",
|
|
12327
|
+
"union"
|
|
12328
|
+
]);
|
|
12329
|
+
var FACTORY_NAMESPACES = /* @__PURE__ */ new Set(["coerce", "iso"]);
|
|
12330
|
+
var NON_SCHEMA_TERMINALS = /* @__PURE__ */ new Set([
|
|
12331
|
+
"decode",
|
|
12332
|
+
"decodeAsync",
|
|
12333
|
+
"encode",
|
|
12334
|
+
"encodeAsync",
|
|
12335
|
+
"flattenError",
|
|
12336
|
+
"formatError",
|
|
12337
|
+
"implement",
|
|
12338
|
+
"isNullable",
|
|
12339
|
+
"isOptional",
|
|
12340
|
+
"parse",
|
|
12341
|
+
"parseAsync",
|
|
12342
|
+
"prettifyError",
|
|
12343
|
+
"registry",
|
|
12344
|
+
"safeDecode",
|
|
12345
|
+
"safeDecodeAsync",
|
|
12346
|
+
"safeEncode",
|
|
12347
|
+
"safeEncodeAsync",
|
|
12348
|
+
"safeParse",
|
|
12349
|
+
"safeParseAsync",
|
|
12350
|
+
"spa",
|
|
12351
|
+
"toJSONSchema",
|
|
12352
|
+
"treeifyError"
|
|
12353
|
+
]);
|
|
12354
|
+
var MEMO_CALLEES2 = /* @__PURE__ */ new Set([
|
|
12355
|
+
"lazy",
|
|
12356
|
+
"memo",
|
|
12357
|
+
"once",
|
|
12358
|
+
"useMemo"
|
|
12359
|
+
]);
|
|
12360
|
+
var I18N_CALLEE_NAMES2 = /* @__PURE__ */ new Set([
|
|
12361
|
+
"$t",
|
|
12362
|
+
"defineMessage",
|
|
12363
|
+
"gettext",
|
|
12364
|
+
"msg",
|
|
12365
|
+
"ngettext",
|
|
12366
|
+
"t",
|
|
12367
|
+
"translate"
|
|
12368
|
+
]);
|
|
12369
|
+
var I18N_RECEIVER_NAMES2 = /* @__PURE__ */ new Set([
|
|
12370
|
+
"$i18n",
|
|
12371
|
+
"i18n",
|
|
12372
|
+
"intl"
|
|
12373
|
+
]);
|
|
12374
|
+
var FUNCTION_TYPES9 = /* @__PURE__ */ new Set([
|
|
12375
|
+
import_utils69.AST_NODE_TYPES.ArrowFunctionExpression,
|
|
12376
|
+
import_utils69.AST_NODE_TYPES.FunctionDeclaration,
|
|
12377
|
+
import_utils69.AST_NODE_TYPES.FunctionExpression
|
|
12079
12378
|
]);
|
|
12080
12379
|
var PREFER_MODULE_LEVEL_REFINED_SCHEMA_DOCUMENTATION = {
|
|
12081
|
-
summary: "Declare closed
|
|
12380
|
+
summary: "Declare closed Zod scalar, format, and wrapper schemas at module scope.",
|
|
12082
12381
|
rationale: "A closed validation pipeline created inside a function is rebuilt on every invocation and obscures a reusable constraint.",
|
|
12083
|
-
remediation: "Move the validation schema to module scope and call parse on the shared schema.",
|
|
12382
|
+
remediation: "Move the validation schema to module scope, name it with a PascalCase Schema suffix, and call parse on the shared schema.",
|
|
12084
12383
|
category: "performance",
|
|
12085
|
-
limitations: [
|
|
12384
|
+
limitations: [
|
|
12385
|
+
"Composite object/record/tuple/union schemas are owned by prefer-module-level-schema.",
|
|
12386
|
+
"Schemas that depend on function-local or mutable state, localized text, receiver state, lazy construction, or recognized memoization are excluded.",
|
|
12387
|
+
"Literal string z.enum domains are owned by prefer-shared-zod-enum."
|
|
12388
|
+
],
|
|
12086
12389
|
examples: [
|
|
12087
|
-
{
|
|
12088
|
-
|
|
12390
|
+
{
|
|
12391
|
+
id: "module-refinement",
|
|
12392
|
+
title: "Share the validation schema",
|
|
12393
|
+
outcome: "no-match",
|
|
12394
|
+
files: [{
|
|
12395
|
+
path: "src/options.ts",
|
|
12396
|
+
source: "import { z } from 'zod'; const BatchSizeSchema = z.number().int().min(1).max(1000); export function parse(value: unknown) { return BatchSizeSchema.parse(value); }"
|
|
12397
|
+
}],
|
|
12398
|
+
focusPath: "src/options.ts",
|
|
12399
|
+
expectedCount: 0,
|
|
12400
|
+
public: true
|
|
12401
|
+
},
|
|
12402
|
+
{
|
|
12403
|
+
id: "local-refinement",
|
|
12404
|
+
title: "Do not rebuild a closed validation chain",
|
|
12405
|
+
outcome: "match",
|
|
12406
|
+
files: [{
|
|
12407
|
+
path: "src/options.ts",
|
|
12408
|
+
source: "import { z } from 'zod'; export function parse(value: unknown) { return z.string().trim().min(1).max(128).parse(value); }"
|
|
12409
|
+
}],
|
|
12410
|
+
focusPath: "src/options.ts",
|
|
12411
|
+
expectedCount: 1,
|
|
12412
|
+
public: true
|
|
12413
|
+
}
|
|
12089
12414
|
]
|
|
12090
12415
|
};
|
|
12091
|
-
function
|
|
12416
|
+
function outermostEnclosingFunction2(node) {
|
|
12417
|
+
let outermost;
|
|
12092
12418
|
let current = node.parent ?? void 0;
|
|
12093
12419
|
while (current !== void 0) {
|
|
12094
|
-
if (
|
|
12420
|
+
if (FUNCTION_TYPES9.has(current.type)) outermost = current;
|
|
12095
12421
|
current = current.parent ?? void 0;
|
|
12096
12422
|
}
|
|
12097
|
-
return
|
|
12423
|
+
return outermost;
|
|
12098
12424
|
}
|
|
12099
12425
|
function collectReferences2(scope, output) {
|
|
12100
12426
|
output.push(...scope.references);
|
|
12101
12427
|
for (const child of scope.childScopes) collectReferences2(child, output);
|
|
12102
12428
|
}
|
|
12429
|
+
function subtreeSome2(root, predicate) {
|
|
12430
|
+
let found = false;
|
|
12431
|
+
const visit = (value) => {
|
|
12432
|
+
if (found || value === null || typeof value !== "object") return;
|
|
12433
|
+
if (Array.isArray(value)) {
|
|
12434
|
+
for (const item of value) visit(item);
|
|
12435
|
+
return;
|
|
12436
|
+
}
|
|
12437
|
+
const candidate2 = value;
|
|
12438
|
+
if (typeof candidate2.type !== "string") return;
|
|
12439
|
+
if (predicate(candidate2)) {
|
|
12440
|
+
found = true;
|
|
12441
|
+
return;
|
|
12442
|
+
}
|
|
12443
|
+
for (const key of Object.keys(candidate2)) {
|
|
12444
|
+
if (key === "parent" || key === "loc" || key === "range") continue;
|
|
12445
|
+
visit(candidate2[key]);
|
|
12446
|
+
}
|
|
12447
|
+
};
|
|
12448
|
+
visit(root);
|
|
12449
|
+
return found;
|
|
12450
|
+
}
|
|
12451
|
+
function readsReceiver2(node) {
|
|
12452
|
+
return subtreeSome2(
|
|
12453
|
+
node,
|
|
12454
|
+
(inner) => inner.type === import_utils69.AST_NODE_TYPES.ThisExpression || inner.type === import_utils69.AST_NODE_TYPES.Super || inner.type === import_utils69.AST_NODE_TYPES.Identifier && inner.name === "arguments"
|
|
12455
|
+
);
|
|
12456
|
+
}
|
|
12457
|
+
function buildsLocalizedText2(node) {
|
|
12458
|
+
return subtreeSome2(node, (inner) => {
|
|
12459
|
+
if (inner.type === import_utils69.AST_NODE_TYPES.TaggedTemplateExpression) return true;
|
|
12460
|
+
if (inner.type !== import_utils69.AST_NODE_TYPES.CallExpression) return false;
|
|
12461
|
+
const { callee } = inner;
|
|
12462
|
+
if (callee.type === import_utils69.AST_NODE_TYPES.Identifier)
|
|
12463
|
+
return I18N_CALLEE_NAMES2.has(callee.name);
|
|
12464
|
+
return callee.type === import_utils69.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.object.type === import_utils69.AST_NODE_TYPES.Identifier && I18N_RECEIVER_NAMES2.has(callee.object.name);
|
|
12465
|
+
});
|
|
12466
|
+
}
|
|
12467
|
+
function calleeChainRoot(node) {
|
|
12468
|
+
let current = node;
|
|
12469
|
+
for (; ; ) {
|
|
12470
|
+
if (current.type === import_utils69.AST_NODE_TYPES.Identifier) return current;
|
|
12471
|
+
if (current.type === import_utils69.AST_NODE_TYPES.MemberExpression) {
|
|
12472
|
+
current = current.object;
|
|
12473
|
+
continue;
|
|
12474
|
+
}
|
|
12475
|
+
if (current.type === import_utils69.AST_NODE_TYPES.CallExpression) {
|
|
12476
|
+
current = current.callee;
|
|
12477
|
+
continue;
|
|
12478
|
+
}
|
|
12479
|
+
return null;
|
|
12480
|
+
}
|
|
12481
|
+
}
|
|
12482
|
+
function chainMemberNames(node) {
|
|
12483
|
+
const names = [];
|
|
12484
|
+
let current = node;
|
|
12485
|
+
for (; ; ) {
|
|
12486
|
+
if (current.type === import_utils69.AST_NODE_TYPES.MemberExpression) {
|
|
12487
|
+
if (current.computed || current.property.type !== import_utils69.AST_NODE_TYPES.Identifier)
|
|
12488
|
+
return [];
|
|
12489
|
+
names.push(current.property.name);
|
|
12490
|
+
current = current.object;
|
|
12491
|
+
continue;
|
|
12492
|
+
}
|
|
12493
|
+
if (current.type === import_utils69.AST_NODE_TYPES.CallExpression) {
|
|
12494
|
+
current = current.callee;
|
|
12495
|
+
continue;
|
|
12496
|
+
}
|
|
12497
|
+
break;
|
|
12498
|
+
}
|
|
12499
|
+
names.reverse();
|
|
12500
|
+
return names;
|
|
12501
|
+
}
|
|
12502
|
+
function schemaExpression2(node) {
|
|
12503
|
+
let current = node;
|
|
12504
|
+
for (; ; ) {
|
|
12505
|
+
const parent = current.parent ?? void 0;
|
|
12506
|
+
if (parent?.type === import_utils69.AST_NODE_TYPES.MemberExpression && parent.object === current && !parent.computed && parent.property.type === import_utils69.AST_NODE_TYPES.Identifier && parent.parent?.type === import_utils69.AST_NODE_TYPES.CallExpression && parent.parent.callee === parent) {
|
|
12507
|
+
if (NON_SCHEMA_TERMINALS.has(parent.property.name)) return current;
|
|
12508
|
+
current = parent.parent;
|
|
12509
|
+
continue;
|
|
12510
|
+
}
|
|
12511
|
+
if (parent?.type === import_utils69.AST_NODE_TYPES.TSAsExpression || parent?.type === import_utils69.AST_NODE_TYPES.TSNonNullExpression || parent?.type === import_utils69.AST_NODE_TYPES.TSSatisfiesExpression || parent?.type === import_utils69.AST_NODE_TYPES.TSTypeAssertion) {
|
|
12512
|
+
current = parent;
|
|
12513
|
+
continue;
|
|
12514
|
+
}
|
|
12515
|
+
return current;
|
|
12516
|
+
}
|
|
12517
|
+
}
|
|
12103
12518
|
var prefer_module_level_refined_schema_default = createRule({
|
|
12104
12519
|
name: "prefer-module-level-refined-schema",
|
|
12105
12520
|
documentation: PREFER_MODULE_LEVEL_REFINED_SCHEMA_DOCUMENTATION,
|
|
12106
|
-
meta: {
|
|
12521
|
+
meta: {
|
|
12522
|
+
type: "suggestion",
|
|
12523
|
+
docs: {
|
|
12524
|
+
description: "Declare closed Zod scalar, format, and wrapper schemas at module scope."
|
|
12525
|
+
},
|
|
12526
|
+
schema: [],
|
|
12527
|
+
messages: {
|
|
12528
|
+
hoistRefinedSchema: "Move this closed Zod schema to module scope, give it a PascalCase Schema name, and reuse it for parsing."
|
|
12529
|
+
}
|
|
12530
|
+
},
|
|
12107
12531
|
defaultOptions: [],
|
|
12108
12532
|
create(context) {
|
|
12109
|
-
if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text))
|
|
12110
|
-
|
|
12533
|
+
if (isTestFile(context.filename) || BENCHMARK_PATH_RE.test(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text))
|
|
12534
|
+
return {};
|
|
12535
|
+
const zodBindings = /* @__PURE__ */ new Set();
|
|
12536
|
+
function resolvedBinding(identifier) {
|
|
12537
|
+
return import_utils69.ASTUtils.findVariable(
|
|
12538
|
+
context.sourceCode.getScope(identifier),
|
|
12539
|
+
identifier.name
|
|
12540
|
+
);
|
|
12541
|
+
}
|
|
12542
|
+
function recordZodBinding(identifier) {
|
|
12543
|
+
const binding = resolvedBinding(identifier);
|
|
12544
|
+
if (binding !== null) zodBindings.add(binding);
|
|
12545
|
+
}
|
|
12546
|
+
function factoryName(node, allowed) {
|
|
12547
|
+
if (node.callee.type !== import_utils69.AST_NODE_TYPES.MemberExpression) return null;
|
|
12548
|
+
const root = calleeChainRoot(node.callee);
|
|
12549
|
+
if (root === null) return null;
|
|
12550
|
+
const binding = resolvedBinding(root);
|
|
12551
|
+
if (binding === null || !zodBindings.has(binding)) return null;
|
|
12552
|
+
const names = chainMemberNames(node.callee);
|
|
12553
|
+
if (names.length === 1 && allowed.has(names[0] ?? ""))
|
|
12554
|
+
return names[0] ?? null;
|
|
12555
|
+
if (names.length === 2 && FACTORY_NAMESPACES.has(names[0] ?? "") && allowed.has(names[1] ?? ""))
|
|
12556
|
+
return names[1] ?? null;
|
|
12557
|
+
return null;
|
|
12558
|
+
}
|
|
12559
|
+
function isSharedEnumDomain(node, factory) {
|
|
12560
|
+
if (factory !== "enum") return false;
|
|
12561
|
+
const [argument] = node.arguments;
|
|
12562
|
+
return argument?.type === import_utils69.AST_NODE_TYPES.ArrayExpression && argument.elements.length >= 2 && argument.elements.every(
|
|
12563
|
+
(element) => element?.type === import_utils69.AST_NODE_TYPES.Literal && typeof element.value === "string"
|
|
12564
|
+
);
|
|
12565
|
+
}
|
|
12566
|
+
function isNestedInOwnedFactory(node) {
|
|
12567
|
+
let current = node;
|
|
12568
|
+
while (current.parent != null) {
|
|
12569
|
+
current = current.parent;
|
|
12570
|
+
if (FUNCTION_TYPES9.has(current.type)) return false;
|
|
12571
|
+
if (current.type === import_utils69.AST_NODE_TYPES.CallExpression && (factoryName(current, FACTORIES) !== null || factoryName(current, COMPOSITE_FACTORIES) !== null))
|
|
12572
|
+
return true;
|
|
12573
|
+
}
|
|
12574
|
+
return false;
|
|
12575
|
+
}
|
|
12576
|
+
function isMemoized(node) {
|
|
12577
|
+
let current = node.parent ?? void 0;
|
|
12578
|
+
while (current !== void 0) {
|
|
12579
|
+
if (current.type === import_utils69.AST_NODE_TYPES.CallExpression && (current.callee.type === import_utils69.AST_NODE_TYPES.Identifier && MEMO_CALLEES2.has(current.callee.name) || current.callee.type === import_utils69.AST_NODE_TYPES.MemberExpression && !current.callee.computed && current.callee.property.type === import_utils69.AST_NODE_TYPES.Identifier && MEMO_CALLEES2.has(current.callee.property.name)))
|
|
12580
|
+
return true;
|
|
12581
|
+
current = current.parent ?? void 0;
|
|
12582
|
+
}
|
|
12583
|
+
return false;
|
|
12584
|
+
}
|
|
12585
|
+
function closesOverNothing(node, enclosing) {
|
|
12586
|
+
const references = [];
|
|
12587
|
+
collectReferences2(context.sourceCode.getScope(node), references);
|
|
12588
|
+
const [start, end] = node.range;
|
|
12589
|
+
const [functionStart, functionEnd] = enclosing.range;
|
|
12590
|
+
for (const reference of references) {
|
|
12591
|
+
const [referenceStart] = reference.identifier.range;
|
|
12592
|
+
if (referenceStart < start || referenceStart >= end) continue;
|
|
12593
|
+
const resolved = reference.resolved;
|
|
12594
|
+
if (resolved === null) continue;
|
|
12595
|
+
for (const definition of resolved.defs) {
|
|
12596
|
+
if (definition.type === "ImportBinding") {
|
|
12597
|
+
const parent = reference.identifier.parent;
|
|
12598
|
+
if (parent?.type === import_utils69.AST_NODE_TYPES.CallExpression && parent.callee === reference.identifier && !zodBindings.has(resolved))
|
|
12599
|
+
return false;
|
|
12600
|
+
continue;
|
|
12601
|
+
}
|
|
12602
|
+
if (definition.node.type === import_utils69.AST_NODE_TYPES.VariableDeclarator && definition.node.parent.type === import_utils69.AST_NODE_TYPES.VariableDeclaration && definition.node.parent.kind !== "const")
|
|
12603
|
+
return false;
|
|
12604
|
+
const [definitionStart, definitionEnd] = definition.node.range;
|
|
12605
|
+
if (definitionStart >= start && definitionEnd <= end) continue;
|
|
12606
|
+
if (definitionStart >= functionStart && definitionEnd <= functionEnd)
|
|
12607
|
+
return false;
|
|
12608
|
+
}
|
|
12609
|
+
}
|
|
12610
|
+
return true;
|
|
12611
|
+
}
|
|
12111
12612
|
return {
|
|
12112
12613
|
ImportDeclaration(node) {
|
|
12113
12614
|
if (!isZodModule(node.source.value)) return;
|
|
12114
|
-
for (const specifier of node.specifiers)
|
|
12615
|
+
for (const specifier of node.specifiers) {
|
|
12616
|
+
if (specifier.type === import_utils69.AST_NODE_TYPES.ImportDefaultSpecifier || specifier.type === import_utils69.AST_NODE_TYPES.ImportNamespaceSpecifier || specifier.type === import_utils69.AST_NODE_TYPES.ImportSpecifier && (specifier.imported.type === import_utils69.AST_NODE_TYPES.Identifier ? specifier.imported.name === "z" : specifier.imported.value === "z"))
|
|
12617
|
+
recordZodBinding(specifier.local);
|
|
12618
|
+
}
|
|
12115
12619
|
},
|
|
12116
12620
|
CallExpression(node) {
|
|
12117
|
-
const
|
|
12118
|
-
if (
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
|
|
12123
|
-
|
|
12124
|
-
|
|
12125
|
-
if (REFINEMENTS.has(current.parent.property.name)) refinements += 1;
|
|
12126
|
-
current = call;
|
|
12127
|
-
}
|
|
12128
|
-
if (refinements < 2) return;
|
|
12129
|
-
const references = [];
|
|
12130
|
-
collectReferences2(context.sourceCode.getScope(current), references);
|
|
12131
|
-
const [start, end] = current.range;
|
|
12132
|
-
const [functionStart, functionEnd] = enclosing.range;
|
|
12133
|
-
for (const reference of references) {
|
|
12134
|
-
const [referenceStart] = reference.identifier.range;
|
|
12135
|
-
if (referenceStart < start || referenceStart >= end || reference.resolved === null) continue;
|
|
12136
|
-
for (const definition of reference.resolved.defs) {
|
|
12137
|
-
if (definition.type === "ImportBinding") continue;
|
|
12138
|
-
if (definition.node.type === import_utils69.AST_NODE_TYPES.VariableDeclarator && definition.node.parent.type === import_utils69.AST_NODE_TYPES.VariableDeclaration && definition.node.parent.kind !== "const") return;
|
|
12139
|
-
const [definitionStart, definitionEnd] = definition.node.range;
|
|
12140
|
-
if (definitionStart >= start && definitionEnd <= end) continue;
|
|
12141
|
-
if (definitionStart >= functionStart && definitionEnd <= functionEnd) return;
|
|
12142
|
-
}
|
|
12143
|
-
}
|
|
12621
|
+
const factory = factoryName(node, FACTORIES);
|
|
12622
|
+
if (factory === null || isSharedEnumDomain(node, factory) || isNestedInOwnedFactory(node) || isMemoized(node))
|
|
12623
|
+
return;
|
|
12624
|
+
const enclosing = outermostEnclosingFunction2(node);
|
|
12625
|
+
if (enclosing === void 0) return;
|
|
12626
|
+
const expression = schemaExpression2(node);
|
|
12627
|
+
if (readsReceiver2(expression) || buildsLocalizedText2(expression) || !closesOverNothing(expression, enclosing))
|
|
12628
|
+
return;
|
|
12144
12629
|
context.report({ node, messageId: "hoistRefinedSchema" });
|
|
12145
12630
|
}
|
|
12146
12631
|
};
|
|
@@ -12154,20 +12639,21 @@ var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
|
|
|
12154
12639
|
rationale: "One multi-value literal expresses the same closed value domain without repeated schema wrappers.",
|
|
12155
12640
|
remediation: "Replace the union with z.literal([value1, value2, ...]).",
|
|
12156
12641
|
category: "maintainability",
|
|
12642
|
+
autofix: "safe",
|
|
12157
12643
|
limitations: [
|
|
12158
|
-
"Bare
|
|
12644
|
+
"Bare zod imports are analyzed only when the rule option explicitly declares zodMajorVersion: 4; explicit zod/v4 entrypoints are self-declaring.",
|
|
12645
|
+
"All-string domains are left to zod/prefer-enum-over-literal-union.",
|
|
12646
|
+
"A finding containing comments is not autofixed because moving its trivia is ambiguous."
|
|
12159
12647
|
],
|
|
12160
12648
|
examples: [
|
|
12161
12649
|
{
|
|
12162
12650
|
id: "multi-value",
|
|
12163
12651
|
title: "Use one multi-value literal",
|
|
12164
12652
|
outcome: "no-match",
|
|
12165
|
-
files: [
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
}
|
|
12170
|
-
],
|
|
12653
|
+
files: [{
|
|
12654
|
+
path: "src/schema.ts",
|
|
12655
|
+
source: "import { z } from 'zod'; export const Version = z.literal([1, 2, 3]);"
|
|
12656
|
+
}],
|
|
12171
12657
|
focusPath: "src/schema.ts",
|
|
12172
12658
|
expectedCount: 0,
|
|
12173
12659
|
public: true
|
|
@@ -12176,80 +12662,116 @@ var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
|
|
|
12176
12662
|
id: "literal-union",
|
|
12177
12663
|
title: "Avoid repeated literal wrappers",
|
|
12178
12664
|
outcome: "match",
|
|
12179
|
-
files: [
|
|
12180
|
-
|
|
12181
|
-
|
|
12182
|
-
|
|
12183
|
-
|
|
12184
|
-
|
|
12665
|
+
files: [{
|
|
12666
|
+
path: "src/schema.ts",
|
|
12667
|
+
source: "import { z } from 'zod'; export const Version = z.union([z.literal(1), z.literal(2), z.literal(3)]);"
|
|
12668
|
+
}],
|
|
12669
|
+
fixedFiles: [{
|
|
12670
|
+
path: "src/schema.ts",
|
|
12671
|
+
source: "import { z } from 'zod'; export const Version = z.literal([1, 2, 3]);"
|
|
12672
|
+
}],
|
|
12185
12673
|
focusPath: "src/schema.ts",
|
|
12186
12674
|
expectedCount: 1,
|
|
12187
12675
|
public: true
|
|
12188
12676
|
}
|
|
12189
12677
|
]
|
|
12190
12678
|
};
|
|
12191
|
-
function
|
|
12192
|
-
|
|
12679
|
+
function isStaticPrimitive(node, context) {
|
|
12680
|
+
if (node.type === import_utils70.AST_NODE_TYPES.Literal) {
|
|
12681
|
+
return node.value === null || ["bigint", "boolean", "number", "string"].includes(typeof node.value);
|
|
12682
|
+
}
|
|
12683
|
+
if (node.type === import_utils70.AST_NODE_TYPES.TemplateLiteral && node.expressions.length === 0)
|
|
12684
|
+
return true;
|
|
12685
|
+
if (node.type === import_utils70.AST_NODE_TYPES.Identifier && node.name === "undefined") {
|
|
12686
|
+
const binding = import_utils70.ASTUtils.findVariable(
|
|
12687
|
+
context.sourceCode.getScope(node),
|
|
12688
|
+
node.name
|
|
12689
|
+
);
|
|
12690
|
+
return binding === null || binding.defs.length === 0;
|
|
12691
|
+
}
|
|
12692
|
+
return node.type === import_utils70.AST_NODE_TYPES.UnaryExpression && node.operator === "-" && node.argument.type === import_utils70.AST_NODE_TYPES.Literal && ["bigint", "number"].includes(typeof node.argument.value);
|
|
12693
|
+
}
|
|
12694
|
+
function isStaticString(node) {
|
|
12695
|
+
return node.type === import_utils70.AST_NODE_TYPES.Literal && typeof node.value === "string" || node.type === import_utils70.AST_NODE_TYPES.TemplateLiteral && node.expressions.length === 0;
|
|
12193
12696
|
}
|
|
12194
12697
|
var prefer_multi_value_zod_literal_default = createRule({
|
|
12195
12698
|
name: "prefer-multi-value-zod-literal",
|
|
12196
12699
|
documentation: PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION,
|
|
12197
12700
|
meta: {
|
|
12198
12701
|
type: "suggestion",
|
|
12702
|
+
fixable: "code",
|
|
12199
12703
|
docs: {
|
|
12200
12704
|
description: "Use the Zod 4 multi-value literal API instead of a union of literal schemas."
|
|
12201
12705
|
},
|
|
12202
|
-
schema: [
|
|
12203
|
-
|
|
12204
|
-
|
|
12205
|
-
|
|
12206
|
-
|
|
12207
|
-
zodMajorVersion: { type: "integer", minimum: 4, maximum: 4 }
|
|
12208
|
-
}
|
|
12706
|
+
schema: [{
|
|
12707
|
+
type: "object",
|
|
12708
|
+
additionalProperties: false,
|
|
12709
|
+
properties: {
|
|
12710
|
+
zodMajorVersion: { type: "integer", minimum: 4, maximum: 4 }
|
|
12209
12711
|
}
|
|
12210
|
-
],
|
|
12712
|
+
}],
|
|
12211
12713
|
messages: {
|
|
12212
|
-
useMultiValueLiteral: "Replace this literal-schema union with
|
|
12714
|
+
useMultiValueLiteral: "Replace this literal-schema union with {{zod}}.literal([\u2026])."
|
|
12213
12715
|
}
|
|
12214
12716
|
},
|
|
12215
12717
|
defaultOptions: [{}],
|
|
12216
12718
|
create(context, [options]) {
|
|
12217
12719
|
if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text))
|
|
12218
12720
|
return {};
|
|
12219
|
-
const
|
|
12220
|
-
const
|
|
12721
|
+
const zodBindings = /* @__PURE__ */ new Set();
|
|
12722
|
+
const zod4Bindings = /* @__PURE__ */ new Set();
|
|
12723
|
+
function resolvedBinding(identifier) {
|
|
12724
|
+
return import_utils70.ASTUtils.findVariable(
|
|
12725
|
+
context.sourceCode.getScope(identifier),
|
|
12726
|
+
identifier.name
|
|
12727
|
+
);
|
|
12728
|
+
}
|
|
12729
|
+
function directMemberCall(node, binding, method) {
|
|
12730
|
+
if (node.callee.type !== import_utils70.AST_NODE_TYPES.MemberExpression || node.callee.computed || node.callee.object.type !== import_utils70.AST_NODE_TYPES.Identifier || node.callee.property.type !== import_utils70.AST_NODE_TYPES.Identifier || node.callee.property.name !== method)
|
|
12731
|
+
return false;
|
|
12732
|
+
return resolvedBinding(node.callee.object) === binding;
|
|
12733
|
+
}
|
|
12221
12734
|
return {
|
|
12222
12735
|
ImportDeclaration(node) {
|
|
12223
12736
|
if (!isZodModule(node.source.value)) return;
|
|
12737
|
+
const isExplicitV4 = /^zod\/v4(?:$|[-/])/.test(node.source.value);
|
|
12224
12738
|
for (const specifier of node.specifiers) {
|
|
12225
|
-
if (specifier.type === import_utils70.AST_NODE_TYPES.ImportDefaultSpecifier || specifier.type === import_utils70.AST_NODE_TYPES.ImportNamespaceSpecifier || specifier.type === import_utils70.AST_NODE_TYPES.ImportSpecifier && specifier.imported.type === import_utils70.AST_NODE_TYPES.Identifier
|
|
12226
|
-
|
|
12227
|
-
if (
|
|
12228
|
-
|
|
12739
|
+
if (specifier.type === import_utils70.AST_NODE_TYPES.ImportDefaultSpecifier || specifier.type === import_utils70.AST_NODE_TYPES.ImportNamespaceSpecifier || specifier.type === import_utils70.AST_NODE_TYPES.ImportSpecifier && (specifier.imported.type === import_utils70.AST_NODE_TYPES.Identifier ? specifier.imported.name === "z" : specifier.imported.value === "z")) {
|
|
12740
|
+
const binding = resolvedBinding(specifier.local);
|
|
12741
|
+
if (binding === null) continue;
|
|
12742
|
+
zodBindings.add(binding);
|
|
12743
|
+
if (isExplicitV4) zod4Bindings.add(binding);
|
|
12229
12744
|
}
|
|
12230
12745
|
}
|
|
12231
12746
|
},
|
|
12232
12747
|
CallExpression(node) {
|
|
12233
|
-
|
|
12234
|
-
|
|
12235
|
-
);
|
|
12236
|
-
if (
|
|
12748
|
+
if (node.callee.type !== import_utils70.AST_NODE_TYPES.MemberExpression || node.callee.object.type !== import_utils70.AST_NODE_TYPES.Identifier)
|
|
12749
|
+
return;
|
|
12750
|
+
const binding = resolvedBinding(node.callee.object);
|
|
12751
|
+
if (binding === null || !zodBindings.has(binding) || !directMemberCall(node, binding, "union") || options?.zodMajorVersion !== 4 && !zod4Bindings.has(binding) || node.arguments.length !== 1)
|
|
12237
12752
|
return;
|
|
12238
12753
|
const [argument] = node.arguments;
|
|
12239
|
-
if (argument?.type !== import_utils70.AST_NODE_TYPES.ArrayExpression || argument.elements.length <
|
|
12240
|
-
if (element === null || element.type !== import_utils70.AST_NODE_TYPES.CallExpression || !memberCall(element, namespace, "literal") || element.arguments.length !== 1)
|
|
12241
|
-
return true;
|
|
12242
|
-
const [value] = element.arguments;
|
|
12243
|
-
return value === void 0 || value.type === import_utils70.AST_NODE_TYPES.SpreadElement || ![
|
|
12244
|
-
import_utils70.AST_NODE_TYPES.Literal,
|
|
12245
|
-
import_utils70.AST_NODE_TYPES.TemplateLiteral
|
|
12246
|
-
].includes(value.type);
|
|
12247
|
-
}))
|
|
12754
|
+
if (argument?.type !== import_utils70.AST_NODE_TYPES.ArrayExpression || argument.elements.length < 2)
|
|
12248
12755
|
return;
|
|
12756
|
+
const values = [];
|
|
12757
|
+
for (const element of argument.elements) {
|
|
12758
|
+
if (element === null || element.type !== import_utils70.AST_NODE_TYPES.CallExpression || !directMemberCall(element, binding, "literal") || element.arguments.length !== 1)
|
|
12759
|
+
return;
|
|
12760
|
+
const [value] = element.arguments;
|
|
12761
|
+
if (value === void 0 || !isStaticPrimitive(value, context)) return;
|
|
12762
|
+
values.push(value);
|
|
12763
|
+
}
|
|
12764
|
+
if (values.every(isStaticString)) return;
|
|
12765
|
+
const namespace = node.callee.object.name;
|
|
12766
|
+
const hasComments = context.sourceCode.getCommentsInside(node).length > 0;
|
|
12249
12767
|
context.report({
|
|
12250
12768
|
node,
|
|
12251
12769
|
messageId: "useMultiValueLiteral",
|
|
12252
|
-
data: { zod: namespace }
|
|
12770
|
+
data: { zod: namespace },
|
|
12771
|
+
fix: hasComments ? null : (fixer) => fixer.replaceText(
|
|
12772
|
+
node,
|
|
12773
|
+
`${namespace}.literal([${values.map((value) => context.sourceCode.getText(value)).join(", ")}])`
|
|
12774
|
+
)
|
|
12253
12775
|
});
|
|
12254
12776
|
}
|
|
12255
12777
|
};
|
|
@@ -12401,11 +12923,11 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
12401
12923
|
create(context) {
|
|
12402
12924
|
const directBindings = /* @__PURE__ */ new Set();
|
|
12403
12925
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
12404
|
-
function
|
|
12926
|
+
function resolve2(identifier) {
|
|
12405
12927
|
return import_utils73.ASTUtils.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
12406
12928
|
}
|
|
12407
12929
|
function record(identifier, destination) {
|
|
12408
|
-
const variable =
|
|
12930
|
+
const variable = resolve2(identifier);
|
|
12409
12931
|
if (variable !== null) destination.add(variable);
|
|
12410
12932
|
}
|
|
12411
12933
|
function report2(node) {
|
|
@@ -12433,7 +12955,7 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
12433
12955
|
},
|
|
12434
12956
|
VariableDeclarator(node) {
|
|
12435
12957
|
if (node.parent.kind !== "const" || !requireUuid(node.init)) return;
|
|
12436
|
-
if (node.init?.type !== import_utils73.AST_NODE_TYPES.CallExpression || node.init.callee.type !== import_utils73.AST_NODE_TYPES.Identifier || (
|
|
12958
|
+
if (node.init?.type !== import_utils73.AST_NODE_TYPES.CallExpression || node.init.callee.type !== import_utils73.AST_NODE_TYPES.Identifier || (resolve2(node.init.callee)?.defs.length ?? 0) > 0) {
|
|
12437
12959
|
return;
|
|
12438
12960
|
}
|
|
12439
12961
|
if (node.id.type === import_utils73.AST_NODE_TYPES.Identifier) {
|
|
@@ -12450,14 +12972,14 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
12450
12972
|
"CallExpression:exit"(node) {
|
|
12451
12973
|
if (node.arguments.length !== 0) return;
|
|
12452
12974
|
if (node.callee.type === import_utils73.AST_NODE_TYPES.Identifier) {
|
|
12453
|
-
const variable2 =
|
|
12975
|
+
const variable2 = resolve2(node.callee);
|
|
12454
12976
|
if (variable2 !== null && directBindings.has(variable2)) report2(node);
|
|
12455
12977
|
return;
|
|
12456
12978
|
}
|
|
12457
12979
|
if (node.callee.type !== import_utils73.AST_NODE_TYPES.MemberExpression || node.callee.computed || node.callee.object.type !== import_utils73.AST_NODE_TYPES.Identifier || node.callee.property.type !== import_utils73.AST_NODE_TYPES.Identifier || node.callee.property.name !== "v4") {
|
|
12458
12980
|
return;
|
|
12459
12981
|
}
|
|
12460
|
-
const variable =
|
|
12982
|
+
const variable = resolve2(node.callee.object);
|
|
12461
12983
|
if (variable !== null && namespaceBindings.has(variable)) report2(node);
|
|
12462
12984
|
}
|
|
12463
12985
|
};
|
|
@@ -12487,22 +13009,22 @@ function memberName4(node) {
|
|
|
12487
13009
|
}
|
|
12488
13010
|
return null;
|
|
12489
13011
|
}
|
|
12490
|
-
function isCryptoLoader(node,
|
|
13012
|
+
function isCryptoLoader(node, resolve2) {
|
|
12491
13013
|
if (node.type !== import_utils74.AST_NODE_TYPES.CallExpression || node.arguments.length !== 1) return false;
|
|
12492
13014
|
const [argument] = node.arguments;
|
|
12493
13015
|
if (argument === void 0 || argument.type === import_utils74.AST_NODE_TYPES.SpreadElement || !isCryptoSpecifier(argument)) {
|
|
12494
13016
|
return false;
|
|
12495
13017
|
}
|
|
12496
13018
|
if (node.callee.type === import_utils74.AST_NODE_TYPES.Identifier) {
|
|
12497
|
-
return node.callee.name === "require" && isUnshadowedBuiltinIdentifier(node.callee,
|
|
13019
|
+
return node.callee.name === "require" && isUnshadowedBuiltinIdentifier(node.callee, resolve2);
|
|
12498
13020
|
}
|
|
12499
|
-
return node.callee.type === import_utils74.AST_NODE_TYPES.MemberExpression && node.callee.object.type === import_utils74.AST_NODE_TYPES.Identifier && node.callee.object.name === "process" && isUnshadowedBuiltinIdentifier(node.callee.object,
|
|
13021
|
+
return node.callee.type === import_utils74.AST_NODE_TYPES.MemberExpression && node.callee.object.type === import_utils74.AST_NODE_TYPES.Identifier && node.callee.object.name === "process" && isUnshadowedBuiltinIdentifier(node.callee.object, resolve2) && memberName4(node.callee) === "getBuiltinModule";
|
|
12500
13022
|
}
|
|
12501
13023
|
function isCryptoSpecifier(node) {
|
|
12502
13024
|
return node.type === import_utils74.AST_NODE_TYPES.Literal && (node.value === "crypto" || node.value === "node:crypto");
|
|
12503
13025
|
}
|
|
12504
|
-
function isUnshadowedBuiltinIdentifier(identifier,
|
|
12505
|
-
const variable =
|
|
13026
|
+
function isUnshadowedBuiltinIdentifier(identifier, resolve2) {
|
|
13027
|
+
const variable = resolve2(identifier);
|
|
12506
13028
|
return variable === null || variable.defs.length === 0;
|
|
12507
13029
|
}
|
|
12508
13030
|
function propertyName4(node) {
|
|
@@ -12520,14 +13042,14 @@ var prefer_node_crypto_hash_default = createRule({
|
|
|
12520
13042
|
create(context) {
|
|
12521
13043
|
const directBindings = /* @__PURE__ */ new Set();
|
|
12522
13044
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
12523
|
-
function
|
|
13045
|
+
function resolve2(identifier) {
|
|
12524
13046
|
return import_utils74.ASTUtils.findVariable(
|
|
12525
13047
|
context.sourceCode.getScope(identifier),
|
|
12526
13048
|
identifier.name
|
|
12527
13049
|
);
|
|
12528
13050
|
}
|
|
12529
13051
|
function record(identifier, destination) {
|
|
12530
|
-
const variable =
|
|
13052
|
+
const variable = resolve2(identifier);
|
|
12531
13053
|
if (variable !== null) destination.add(variable);
|
|
12532
13054
|
}
|
|
12533
13055
|
return {
|
|
@@ -12542,7 +13064,7 @@ var prefer_node_crypto_hash_default = createRule({
|
|
|
12542
13064
|
}
|
|
12543
13065
|
},
|
|
12544
13066
|
VariableDeclarator(node) {
|
|
12545
|
-
if (node.parent.kind !== "const" || node.init === null || !isCryptoLoader(node.init,
|
|
13067
|
+
if (node.parent.kind !== "const" || node.init === null || !isCryptoLoader(node.init, resolve2)) {
|
|
12546
13068
|
return;
|
|
12547
13069
|
}
|
|
12548
13070
|
if (node.id.type === import_utils74.AST_NODE_TYPES.Identifier) {
|
|
@@ -12566,7 +13088,7 @@ var prefer_node_crypto_hash_default = createRule({
|
|
|
12566
13088
|
create,
|
|
12567
13089
|
directBindings,
|
|
12568
13090
|
namespaceBindings,
|
|
12569
|
-
|
|
13091
|
+
resolve2
|
|
12570
13092
|
))
|
|
12571
13093
|
return;
|
|
12572
13094
|
context.report({ node, messageId: "preferNodeCryptoHash" });
|
|
@@ -12580,17 +13102,17 @@ function importedName5(node) {
|
|
|
12580
13102
|
function isMemberCall(node, name) {
|
|
12581
13103
|
return node.callee.type === import_utils74.AST_NODE_TYPES.MemberExpression && memberName4(node.callee) === name;
|
|
12582
13104
|
}
|
|
12583
|
-
function isCreateHashCall(node, directBindings, namespaceBindings,
|
|
13105
|
+
function isCreateHashCall(node, directBindings, namespaceBindings, resolve2) {
|
|
12584
13106
|
if (node.callee.type === import_utils74.AST_NODE_TYPES.Identifier) {
|
|
12585
|
-
const variable2 =
|
|
13107
|
+
const variable2 = resolve2(node.callee);
|
|
12586
13108
|
return variable2 !== null && directBindings.has(variable2);
|
|
12587
13109
|
}
|
|
12588
13110
|
if (node.callee.type !== import_utils74.AST_NODE_TYPES.MemberExpression || memberName4(node.callee) !== "createHash") {
|
|
12589
13111
|
return false;
|
|
12590
13112
|
}
|
|
12591
|
-
if (isCryptoLoader(node.callee.object,
|
|
13113
|
+
if (isCryptoLoader(node.callee.object, resolve2)) return true;
|
|
12592
13114
|
if (node.callee.object.type !== import_utils74.AST_NODE_TYPES.Identifier) return false;
|
|
12593
|
-
const variable =
|
|
13115
|
+
const variable = resolve2(node.callee.object);
|
|
12594
13116
|
return variable !== null && namespaceBindings.has(variable);
|
|
12595
13117
|
}
|
|
12596
13118
|
|
|
@@ -13912,7 +14434,30 @@ var import_fs = require("fs");
|
|
|
13912
14434
|
var import_path = require("path");
|
|
13913
14435
|
|
|
13914
14436
|
// src/rules/_tailwind.ts
|
|
13915
|
-
var
|
|
14437
|
+
var tailwindVariantPrefix = (token) => {
|
|
14438
|
+
let bracketDepth = 0;
|
|
14439
|
+
let parenthesisDepth = 0;
|
|
14440
|
+
let escaped = false;
|
|
14441
|
+
let end = 0;
|
|
14442
|
+
for (let index = 0; index < token.length; index += 1) {
|
|
14443
|
+
const character = token[index];
|
|
14444
|
+
if (escaped) {
|
|
14445
|
+
escaped = false;
|
|
14446
|
+
continue;
|
|
14447
|
+
}
|
|
14448
|
+
if (character === "\\") {
|
|
14449
|
+
escaped = true;
|
|
14450
|
+
continue;
|
|
14451
|
+
}
|
|
14452
|
+
if (character === "[") bracketDepth += 1;
|
|
14453
|
+
else if (character === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
14454
|
+
else if (character === "(") parenthesisDepth += 1;
|
|
14455
|
+
else if (character === ")") parenthesisDepth = Math.max(0, parenthesisDepth - 1);
|
|
14456
|
+
else if (character === ":" && bracketDepth === 0 && parenthesisDepth === 0) end = index + 1;
|
|
14457
|
+
}
|
|
14458
|
+
return token.slice(0, end);
|
|
14459
|
+
};
|
|
14460
|
+
var tailwindBase = (token) => token.slice(tailwindVariantPrefix(token).length).replace(/^!/, "");
|
|
13916
14461
|
var classTokens = (value) => value.split(/\s+/).filter(Boolean);
|
|
13917
14462
|
|
|
13918
14463
|
// src/rules/prefer-semantic-colors.ts
|
|
@@ -13921,7 +14466,10 @@ var PREFER_SEMANTIC_COLORS_DOCUMENTATION = {
|
|
|
13921
14466
|
rationale: "Semantic tokens keep themes and product meaning consistent while raw colors couple components to a palette value.",
|
|
13922
14467
|
remediation: "Replace raw palette and literal colors with the closest semantic design-system token or CSS variable.",
|
|
13923
14468
|
category: "style",
|
|
13924
|
-
limitations: [
|
|
14469
|
+
limitations: [
|
|
14470
|
+
"Email, PDF, video-rendering, print-only, icon artwork, masks, gradients, stories, and explicitly configured non-token projects have targeted exclusions.",
|
|
14471
|
+
"Opaque-foreground checks are opt-in and require both a same-variant semantic background class and its package-local declared foreground token."
|
|
14472
|
+
],
|
|
13925
14473
|
examples: [
|
|
13926
14474
|
{ 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 },
|
|
13927
14475
|
{ 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 }
|
|
@@ -14049,7 +14597,13 @@ var SVG_SHAPE_PRIMITIVES = /* @__PURE__ */ new Set([
|
|
|
14049
14597
|
"tspan",
|
|
14050
14598
|
"use"
|
|
14051
14599
|
]);
|
|
14052
|
-
var
|
|
14600
|
+
var EXTERNAL_RENDERER_MODULE_RE = /^(?:@react-(?:email|pdf)\/|remotion$|@remotion\/)/;
|
|
14601
|
+
var OPAQUE_FOREGROUND_RE = /^text-(?:white|black)(?:\/100)?$/;
|
|
14602
|
+
var SEMANTIC_BACKGROUND_RE = /^bg-([a-z][a-z0-9-]*)$/;
|
|
14603
|
+
var CSS_COMMENT_RE = /\/\*[\s\S]*?\*\//gu;
|
|
14604
|
+
var DECLARED_FOREGROUND_RE = /--(?:color-)?([a-z][a-z0-9-]*)-foreground\s*:/giu;
|
|
14605
|
+
var MAX_TOKEN_STYLESHEET_BYTES = 1048576;
|
|
14606
|
+
var SEMANTIC_DECLARATIONS_CACHE = /* @__PURE__ */ new Map();
|
|
14053
14607
|
function isSvgLikeElementName(name) {
|
|
14054
14608
|
return name === "svg" || SVG_DEFS_CONTAINERS.has(name) || /svg$/i.test(name);
|
|
14055
14609
|
}
|
|
@@ -14186,20 +14740,68 @@ var expandWorkspaceGlob = (root, glob) => {
|
|
|
14186
14740
|
if (star === -1) return [(0, import_path.join)(root, glob)];
|
|
14187
14741
|
const prefix = glob.slice(0, star).replace(/\/$/u, "");
|
|
14188
14742
|
const parent = prefix === "" ? root : (0, import_path.join)(root, prefix);
|
|
14189
|
-
if (!(0, import_fs.existsSync)(parent)) return [];
|
|
14190
|
-
return (0, import_fs.readdirSync)(parent, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => (0, import_path.join)(parent, entry.name));
|
|
14743
|
+
if (!(0, import_fs.existsSync)(parent) || !(0, import_fs.lstatSync)(parent).isDirectory()) return [];
|
|
14744
|
+
return (0, import_fs.readdirSync)(parent, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => (0, import_path.join)(parent, entry.name));
|
|
14191
14745
|
};
|
|
14192
14746
|
var propName = (key) => {
|
|
14193
14747
|
if (key.type === import_utils82.AST_NODE_TYPES.Identifier) return key.name;
|
|
14194
14748
|
if (key.type === import_utils82.AST_NODE_TYPES.Literal && typeof key.value === "string") return key.value;
|
|
14195
14749
|
return null;
|
|
14196
14750
|
};
|
|
14197
|
-
var
|
|
14751
|
+
var staticallyImportsExternalRenderer = (program) => program.body.some((statement) => {
|
|
14198
14752
|
if (statement.type !== import_utils82.AST_NODE_TYPES.ImportDeclaration && statement.type !== import_utils82.AST_NODE_TYPES.ExportNamedDeclaration && statement.type !== import_utils82.AST_NODE_TYPES.ExportAllDeclaration) {
|
|
14199
14753
|
return false;
|
|
14200
14754
|
}
|
|
14201
|
-
return statement.source !== null && typeof statement.source.value === "string" &&
|
|
14755
|
+
return statement.source !== null && typeof statement.source.value === "string" && EXTERNAL_RENDERER_MODULE_RE.test(statement.source.value);
|
|
14202
14756
|
});
|
|
14757
|
+
var semanticForegroundRoles = (filename) => {
|
|
14758
|
+
const packageRoot = nearestPackageRoot((0, import_path.dirname)(filename));
|
|
14759
|
+
if (packageRoot === null) return /* @__PURE__ */ new Set();
|
|
14760
|
+
const candidates = CSS_DETECTION_FILES.map((relative2) => (0, import_path.join)(packageRoot, relative2));
|
|
14761
|
+
const fingerprint = candidates.map(fileFingerprint2).join("|");
|
|
14762
|
+
const cached = SEMANTIC_DECLARATIONS_CACHE.get(packageRoot);
|
|
14763
|
+
if (cached?.fingerprint === fingerprint) return cached.value;
|
|
14764
|
+
const foregroundRoles = /* @__PURE__ */ new Set();
|
|
14765
|
+
for (const candidate2 of candidates) {
|
|
14766
|
+
const css = readTokenStylesheet(candidate2);
|
|
14767
|
+
if (css === null) continue;
|
|
14768
|
+
const declarations = css.replace(CSS_COMMENT_RE, "");
|
|
14769
|
+
for (const match of declarations.matchAll(DECLARED_FOREGROUND_RE)) {
|
|
14770
|
+
if (match[1] !== void 0) foregroundRoles.add(match[1].toLowerCase());
|
|
14771
|
+
}
|
|
14772
|
+
}
|
|
14773
|
+
SEMANTIC_DECLARATIONS_CACHE.set(packageRoot, { fingerprint, value: foregroundRoles });
|
|
14774
|
+
return foregroundRoles;
|
|
14775
|
+
};
|
|
14776
|
+
var nearestPackageRoot = (startDir) => {
|
|
14777
|
+
let dir = startDir;
|
|
14778
|
+
const root = (0, import_path.parse)(dir).root;
|
|
14779
|
+
for (; ; ) {
|
|
14780
|
+
if ((0, import_fs.existsSync)((0, import_path.join)(dir, "package.json"))) {
|
|
14781
|
+
return dir;
|
|
14782
|
+
}
|
|
14783
|
+
const parent = (0, import_path.dirname)(dir);
|
|
14784
|
+
if (dir === root || parent === dir) return null;
|
|
14785
|
+
dir = parent;
|
|
14786
|
+
}
|
|
14787
|
+
};
|
|
14788
|
+
var fileFingerprint2 = (path) => {
|
|
14789
|
+
try {
|
|
14790
|
+
const stat = (0, import_fs.lstatSync)(path);
|
|
14791
|
+
return stat.isFile() ? `${path}:${stat.size}:${stat.mtimeMs}` : `${path}:excluded`;
|
|
14792
|
+
} catch {
|
|
14793
|
+
return `${path}:missing`;
|
|
14794
|
+
}
|
|
14795
|
+
};
|
|
14796
|
+
var readTokenStylesheet = (path) => {
|
|
14797
|
+
try {
|
|
14798
|
+
const stat = (0, import_fs.lstatSync)(path);
|
|
14799
|
+
if (!stat.isFile() || stat.size > MAX_TOKEN_STYLESHEET_BYTES) return null;
|
|
14800
|
+
return (0, import_fs.readFileSync)(path, "utf8");
|
|
14801
|
+
} catch {
|
|
14802
|
+
return null;
|
|
14803
|
+
}
|
|
14804
|
+
};
|
|
14203
14805
|
var prefer_semantic_colors_default = createRule({
|
|
14204
14806
|
name: "prefer-semantic-colors",
|
|
14205
14807
|
documentation: PREFER_SEMANTIC_COLORS_DOCUMENTATION,
|
|
@@ -14213,30 +14815,34 @@ var prefer_semantic_colors_default = createRule({
|
|
|
14213
14815
|
type: "object",
|
|
14214
14816
|
additionalProperties: false,
|
|
14215
14817
|
properties: {
|
|
14216
|
-
requireSemanticTokens: { type: "boolean" }
|
|
14818
|
+
requireSemanticTokens: { type: "boolean" },
|
|
14819
|
+
opaqueForegroundPairs: { type: "boolean" }
|
|
14217
14820
|
}
|
|
14218
14821
|
}
|
|
14219
14822
|
],
|
|
14220
14823
|
messages: {
|
|
14221
14824
|
rawPalette: "Raw palette class '{{class}}' \u2014 use a semantic token (e.g. text-foreground, bg-primary, text-destructive, bg-muted).",
|
|
14222
14825
|
arbitraryColor: "Hardcoded color '{{class}}' \u2014 use a semantic token, or var(--\u2026). For charts/brand add an eslint-disable with a reason.",
|
|
14223
|
-
inlineColor: "Hardcoded color '{{value}}' \u2014 use a semantic token / CSS variable. For charts/standalone pages add an eslint-disable with a reason."
|
|
14826
|
+
inlineColor: "Hardcoded color '{{value}}' \u2014 use a semantic token / CSS variable. For charts/standalone pages add an eslint-disable with a reason.",
|
|
14827
|
+
opaqueForegroundPair: "'{{class}}' bypasses the declared '{{replacement}}' token paired with '{{background}}'."
|
|
14224
14828
|
}
|
|
14225
14829
|
},
|
|
14226
14830
|
defaultOptions: [{}],
|
|
14227
14831
|
create(context, [options]) {
|
|
14228
14832
|
if (STORIES_FILE_RE.test(context.filename)) return {};
|
|
14229
|
-
if (
|
|
14230
|
-
if (options?.requireSemanticTokens === true && !hasSemanticTokenSystem(context.filename)) {
|
|
14231
|
-
|
|
14232
|
-
|
|
14233
|
-
let
|
|
14234
|
-
const pendingReports =
|
|
14833
|
+
if (staticallyImportsExternalRenderer(context.sourceCode.ast)) return {};
|
|
14834
|
+
if (options?.requireSemanticTokens === true && !hasSemanticTokenSystem(context.filename)) return {};
|
|
14835
|
+
const foregroundRoles = options?.opaqueForegroundPairs === true ? semanticForegroundRoles(context.filename) : /* @__PURE__ */ new Set();
|
|
14836
|
+
const checkOpaqueForegroundPairs = foregroundRoles.size > 0;
|
|
14837
|
+
let importsExternalRenderer = false;
|
|
14838
|
+
const pendingReports = /* @__PURE__ */ new Map();
|
|
14235
14839
|
const report2 = (node, messageId, data) => {
|
|
14236
|
-
|
|
14840
|
+
const key = `${node.range[0]}:${node.range[1]}:${messageId}:${JSON.stringify(data)}`;
|
|
14841
|
+
pendingReports.set(key, { node, messageId, data });
|
|
14237
14842
|
};
|
|
14238
14843
|
const reportClasses = (value, node) => {
|
|
14239
|
-
|
|
14844
|
+
const tokens = classTokens(value);
|
|
14845
|
+
for (const token of tokens) {
|
|
14240
14846
|
const base = tailwindBase(token);
|
|
14241
14847
|
if (RAW_PALETTE_RE.test(base)) {
|
|
14242
14848
|
report2(node, "rawPalette", { class: token });
|
|
@@ -14244,6 +14850,26 @@ var prefer_semantic_colors_default = createRule({
|
|
|
14244
14850
|
report2(node, "arbitraryColor", { class: token });
|
|
14245
14851
|
}
|
|
14246
14852
|
}
|
|
14853
|
+
if (!checkOpaqueForegroundPairs || isInsideSvg(node)) return;
|
|
14854
|
+
for (const token of tokens) {
|
|
14855
|
+
const prefix = tailwindVariantPrefix(token);
|
|
14856
|
+
if (prefix.split(":").includes("print")) continue;
|
|
14857
|
+
const base = tailwindBase(token);
|
|
14858
|
+
if (!OPAQUE_FOREGROUND_RE.test(base)) continue;
|
|
14859
|
+
const semanticBackground = tokens.find((candidate2) => {
|
|
14860
|
+
if (tailwindVariantPrefix(candidate2) !== prefix) return false;
|
|
14861
|
+
const match = SEMANTIC_BACKGROUND_RE.exec(tailwindBase(candidate2));
|
|
14862
|
+
return match?.[1] !== void 0 && foregroundRoles.has(match[1]);
|
|
14863
|
+
});
|
|
14864
|
+
if (semanticBackground === void 0) continue;
|
|
14865
|
+
const role = SEMANTIC_BACKGROUND_RE.exec(tailwindBase(semanticBackground))?.[1];
|
|
14866
|
+
if (role === void 0) continue;
|
|
14867
|
+
report2(node, "opaqueForegroundPair", {
|
|
14868
|
+
background: semanticBackground,
|
|
14869
|
+
class: token,
|
|
14870
|
+
replacement: `${prefix}text-${role}-foreground`
|
|
14871
|
+
});
|
|
14872
|
+
}
|
|
14247
14873
|
};
|
|
14248
14874
|
const checkClassNode = (node) => {
|
|
14249
14875
|
if (node === null) return;
|
|
@@ -14291,8 +14917,8 @@ var prefer_semantic_colors_default = createRule({
|
|
|
14291
14917
|
}
|
|
14292
14918
|
},
|
|
14293
14919
|
CallExpression(node) {
|
|
14294
|
-
if (node.callee.type === import_utils82.AST_NODE_TYPES.Identifier && node.callee.name === "require" && node.arguments[0]?.type === import_utils82.AST_NODE_TYPES.Literal && typeof node.arguments[0].value === "string" &&
|
|
14295
|
-
|
|
14920
|
+
if (node.callee.type === import_utils82.AST_NODE_TYPES.Identifier && node.callee.name === "require" && node.arguments[0]?.type === import_utils82.AST_NODE_TYPES.Literal && typeof node.arguments[0].value === "string" && EXTERNAL_RENDERER_MODULE_RE.test(node.arguments[0].value)) {
|
|
14921
|
+
importsExternalRenderer = true;
|
|
14296
14922
|
}
|
|
14297
14923
|
if (node.callee.type === import_utils82.AST_NODE_TYPES.Identifier && CLASS_FNS.has(node.callee.name)) {
|
|
14298
14924
|
for (const arg of node.arguments) {
|
|
@@ -14327,13 +14953,13 @@ var prefer_semantic_colors_default = createRule({
|
|
|
14327
14953
|
if (name !== null && STYLE_COLOR_PROPS.has(name)) checkColorValueNode(node.value);
|
|
14328
14954
|
},
|
|
14329
14955
|
ImportExpression(node) {
|
|
14330
|
-
if (node.source.type === import_utils82.AST_NODE_TYPES.Literal && typeof node.source.value === "string" &&
|
|
14331
|
-
|
|
14956
|
+
if (node.source.type === import_utils82.AST_NODE_TYPES.Literal && typeof node.source.value === "string" && EXTERNAL_RENDERER_MODULE_RE.test(node.source.value)) {
|
|
14957
|
+
importsExternalRenderer = true;
|
|
14332
14958
|
}
|
|
14333
14959
|
},
|
|
14334
14960
|
"Program:exit"() {
|
|
14335
|
-
if (
|
|
14336
|
-
for (const descriptor of pendingReports) context.report(descriptor);
|
|
14961
|
+
if (importsExternalRenderer) return;
|
|
14962
|
+
for (const descriptor of pendingReports.values()) context.report(descriptor);
|
|
14337
14963
|
}
|
|
14338
14964
|
};
|
|
14339
14965
|
}
|
|
@@ -14342,11 +14968,11 @@ var prefer_semantic_colors_default = createRule({
|
|
|
14342
14968
|
// src/rules/prefer-server-actions.ts
|
|
14343
14969
|
var import_utils83 = require("@typescript-eslint/utils");
|
|
14344
14970
|
var PREFER_SERVER_ACTIONS_DOCUMENTATION = {
|
|
14345
|
-
summary: "Prefer Next.js Server Actions over
|
|
14971
|
+
summary: "Prefer Next.js Server Actions over same-origin API mutations.",
|
|
14346
14972
|
rationale: "Server Actions preserve typed application calls and avoid an internal JSON request-response boundary.",
|
|
14347
14973
|
remediation: "Move the mutation into a Server Action and invoke that action from the React client.",
|
|
14348
14974
|
category: "architecture",
|
|
14349
|
-
limitations: ["Only statically recognizable /api/ mutations
|
|
14975
|
+
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."],
|
|
14350
14976
|
examples: [
|
|
14351
14977
|
{ 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 },
|
|
14352
14978
|
{ 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 }
|
|
@@ -14354,12 +14980,21 @@ var PREFER_SERVER_ACTIONS_DOCUMENTATION = {
|
|
|
14354
14980
|
};
|
|
14355
14981
|
var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
|
|
14356
14982
|
var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
|
|
14357
|
-
var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|-(?:test|spec)\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/__testfixtures__\/|\/scripts
|
|
14983
|
+
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\/)/;
|
|
14358
14984
|
var NON_REACT_FRAMEWORK_RE2 = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
|
|
14359
|
-
var
|
|
14985
|
+
var BASE_PATH_RE2 = /^\/(?!$)(?!.*[?#])(?:[^/]+\/)*[^/]+$/u;
|
|
14360
14986
|
function getScope(context, node) {
|
|
14361
14987
|
return context.sourceCode.getScope(node);
|
|
14362
14988
|
}
|
|
14989
|
+
function resolvesToGlobalFetch(context, identifier) {
|
|
14990
|
+
let scope = getScope(context, identifier);
|
|
14991
|
+
while (scope) {
|
|
14992
|
+
const variable = scope.set.get(identifier.name);
|
|
14993
|
+
if (variable !== void 0) return variable.defs.length === 0;
|
|
14994
|
+
scope = scope.upper;
|
|
14995
|
+
}
|
|
14996
|
+
return true;
|
|
14997
|
+
}
|
|
14363
14998
|
function resolveNode(node, context) {
|
|
14364
14999
|
if (!node) return null;
|
|
14365
15000
|
if (node.type !== "Identifier") return node;
|
|
@@ -14379,22 +15014,29 @@ function resolveNode(node, context) {
|
|
|
14379
15014
|
}
|
|
14380
15015
|
return node;
|
|
14381
15016
|
}
|
|
14382
|
-
function isApiUrl(node, context) {
|
|
15017
|
+
function isApiUrl(node, context, apiPrefixes) {
|
|
14383
15018
|
const resolved = resolveNode(node, context);
|
|
14384
15019
|
if (!resolved) return false;
|
|
14385
15020
|
if (resolved.type === "Literal" && typeof resolved.value === "string") {
|
|
14386
|
-
return
|
|
15021
|
+
return apiPrefixes.some(
|
|
15022
|
+
(prefix) => resolved.value === prefix.slice(0, -1) || resolved.value.startsWith(prefix)
|
|
15023
|
+
);
|
|
14387
15024
|
}
|
|
14388
15025
|
if (resolved.type === "TemplateLiteral") {
|
|
14389
15026
|
const firstQuasi = resolved.quasis[0];
|
|
14390
15027
|
const cooked = firstQuasi?.value.cooked;
|
|
14391
|
-
return typeof cooked === "string" &&
|
|
15028
|
+
return typeof cooked === "string" && apiPrefixes.some(
|
|
15029
|
+
(prefix) => cooked === prefix.slice(0, -1) || cooked.startsWith(prefix)
|
|
15030
|
+
);
|
|
14392
15031
|
}
|
|
14393
15032
|
if (resolved.type === "BinaryExpression" && resolved.operator === "+") {
|
|
14394
|
-
return isApiUrl(resolved.left, context);
|
|
15033
|
+
return isApiUrl(resolved.left, context, apiPrefixes);
|
|
14395
15034
|
}
|
|
14396
15035
|
return false;
|
|
14397
15036
|
}
|
|
15037
|
+
function isValidBasePath2(basePath) {
|
|
15038
|
+
return BASE_PATH_RE2.test(basePath) && !basePath.split("/").some((segment) => segment === "." || segment === "..");
|
|
15039
|
+
}
|
|
14398
15040
|
function isMutationMethod(node, context) {
|
|
14399
15041
|
const resolved = resolveNode(node, context);
|
|
14400
15042
|
if (!resolved) return false;
|
|
@@ -14454,16 +15096,27 @@ var prefer_server_actions_default = createRule({
|
|
|
14454
15096
|
meta: {
|
|
14455
15097
|
type: "suggestion",
|
|
14456
15098
|
docs: {
|
|
14457
|
-
description: "Prefer Next.js Server Actions over
|
|
15099
|
+
description: "Prefer Next.js Server Actions over same-origin API mutations."
|
|
14458
15100
|
},
|
|
14459
|
-
schema: [
|
|
15101
|
+
schema: [
|
|
15102
|
+
{
|
|
15103
|
+
type: "object",
|
|
15104
|
+
additionalProperties: false,
|
|
15105
|
+
properties: {
|
|
15106
|
+
basePath: {
|
|
15107
|
+
type: "string",
|
|
15108
|
+
pattern: "^/(?!$)(?!.*[?#])(?!(?:.*/)?\\.\\.?(?:/|$))(?:[^/]+/)*[^/]+$"
|
|
15109
|
+
}
|
|
15110
|
+
}
|
|
15111
|
+
}
|
|
15112
|
+
],
|
|
14460
15113
|
messages: {
|
|
14461
|
-
preferServerAction: "Mutation against
|
|
15114
|
+
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."
|
|
14462
15115
|
}
|
|
14463
15116
|
},
|
|
14464
|
-
defaultOptions: [],
|
|
14465
|
-
create(context) {
|
|
14466
|
-
const filename = context.filename;
|
|
15117
|
+
defaultOptions: [{}],
|
|
15118
|
+
create(context, [options]) {
|
|
15119
|
+
const filename = context.filename.replaceAll("\\", "/");
|
|
14467
15120
|
if (SKIP_FILE_REGEX.test(filename)) {
|
|
14468
15121
|
return {};
|
|
14469
15122
|
}
|
|
@@ -14471,22 +15124,28 @@ var prefer_server_actions_default = createRule({
|
|
|
14471
15124
|
(node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && NON_REACT_FRAMEWORK_RE2.test(node.source.value)
|
|
14472
15125
|
);
|
|
14473
15126
|
const hasUseClientDirective = context.sourceCode.ast.body.some(
|
|
14474
|
-
(node) => node.type === "ExpressionStatement" && node.
|
|
15127
|
+
(node) => node.type === "ExpressionStatement" && node.directive === "use client"
|
|
14475
15128
|
);
|
|
14476
|
-
const
|
|
14477
|
-
(node) => node.type === "
|
|
15129
|
+
const hasUseServerDirective = context.sourceCode.ast.body.some(
|
|
15130
|
+
(node) => node.type === "ExpressionStatement" && node.directive === "use server"
|
|
14478
15131
|
);
|
|
14479
|
-
const
|
|
14480
|
-
|
|
15132
|
+
const importsServerOnly = context.sourceCode.ast.body.some(
|
|
15133
|
+
(node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && (node.source.value === "server-only" || node.source.value === "next/server")
|
|
15134
|
+
);
|
|
15135
|
+
if (!hasUseClientDirective || hasUseServerDirective || importsServerOnly) {
|
|
14481
15136
|
return {};
|
|
14482
15137
|
}
|
|
15138
|
+
const apiPrefixes = ["/api/"];
|
|
15139
|
+
if (options?.basePath !== void 0 && isValidBasePath2(options.basePath)) {
|
|
15140
|
+
apiPrefixes.push(`${options.basePath}/api/`);
|
|
15141
|
+
}
|
|
14483
15142
|
return {
|
|
14484
15143
|
CallExpression(node) {
|
|
14485
15144
|
if (isNonReactFramework) return;
|
|
14486
15145
|
let isMutation = false;
|
|
14487
|
-
if (node.callee.type === "Identifier" && node.callee.name === "fetch") {
|
|
15146
|
+
if (node.callee.type === "Identifier" && node.callee.name === "fetch" && resolvesToGlobalFetch(context, node.callee)) {
|
|
14488
15147
|
const urlArg = node.arguments[0];
|
|
14489
|
-
if (urlArg && urlArg.type !== "SpreadElement" && isApiUrl(urlArg, context)) {
|
|
15148
|
+
if (urlArg && urlArg.type !== "SpreadElement" && isApiUrl(urlArg, context, apiPrefixes)) {
|
|
14490
15149
|
const initArg = node.arguments[1];
|
|
14491
15150
|
if (initArg && initArg.type !== "SpreadElement") {
|
|
14492
15151
|
const resolvedInit = resolveNode(initArg, context);
|
|
@@ -14503,7 +15162,7 @@ var prefer_server_actions_default = createRule({
|
|
|
14503
15162
|
const hasHandlerArg = node.arguments.some(
|
|
14504
15163
|
(arg) => arg.type !== "SpreadElement" && isFunctionArgument(arg, context)
|
|
14505
15164
|
);
|
|
14506
|
-
if (urlArg && urlArg.type !== "SpreadElement" && !hasHandlerArg && isApiUrl(urlArg, context)) {
|
|
15165
|
+
if (urlArg && urlArg.type !== "SpreadElement" && !hasHandlerArg && isApiUrl(urlArg, context, apiPrefixes)) {
|
|
14507
15166
|
isMutation = true;
|
|
14508
15167
|
}
|
|
14509
15168
|
}
|
|
@@ -14514,7 +15173,7 @@ var prefer_server_actions_default = createRule({
|
|
|
14514
15173
|
if (configArg && configArg.type === "ObjectExpression") {
|
|
14515
15174
|
const urlNode = getPropertyNode(configArg, "url");
|
|
14516
15175
|
const methodNode = getPropertyNode(configArg, "method");
|
|
14517
|
-
if (urlNode && isApiUrl(urlNode, context) && methodNode && isMutationMethod(methodNode, context)) {
|
|
15176
|
+
if (urlNode && isApiUrl(urlNode, context, apiPrefixes) && methodNode && isMutationMethod(methodNode, context)) {
|
|
14518
15177
|
isMutation = true;
|
|
14519
15178
|
}
|
|
14520
15179
|
}
|
|
@@ -18436,8 +19095,8 @@ var REQUIRE_PASCAL_CASE_ZOD_SCHEMA_NAME_DOCUMENTATION = {
|
|
|
18436
19095
|
]
|
|
18437
19096
|
};
|
|
18438
19097
|
var PASCAL_SCHEMA_NAME_RE = /^[A-Z][A-Za-z0-9]*Schema$/;
|
|
18439
|
-
var
|
|
18440
|
-
var
|
|
19098
|
+
var BENCHMARK_PATH_RE2 = /(^|[\\/])(?:benchmarks?|bench)[\\/]/;
|
|
19099
|
+
var NON_SCHEMA_TERMINALS2 = /* @__PURE__ */ new Set([
|
|
18441
19100
|
"parse",
|
|
18442
19101
|
"parseAsync",
|
|
18443
19102
|
"safeParse",
|
|
@@ -18550,7 +19209,7 @@ var SCHEMA_RETURNING_METHODS = /* @__PURE__ */ new Set([
|
|
|
18550
19209
|
"transform"
|
|
18551
19210
|
]);
|
|
18552
19211
|
var terminalMethodName = (callee) => !callee.computed && callee.property.type === import_utils100.AST_NODE_TYPES.Identifier ? callee.property.name : null;
|
|
18553
|
-
var
|
|
19212
|
+
var calleeChainRoot2 = (node) => {
|
|
18554
19213
|
let current = node;
|
|
18555
19214
|
for (; ; ) {
|
|
18556
19215
|
if (current.type === import_utils100.AST_NODE_TYPES.Identifier) {
|
|
@@ -18567,7 +19226,7 @@ var calleeChainRoot = (node) => {
|
|
|
18567
19226
|
return null;
|
|
18568
19227
|
}
|
|
18569
19228
|
};
|
|
18570
|
-
var
|
|
19229
|
+
var chainMemberNames2 = (node) => {
|
|
18571
19230
|
const names = [];
|
|
18572
19231
|
let current = node;
|
|
18573
19232
|
for (; ; ) {
|
|
@@ -18627,7 +19286,7 @@ var require_pascal_case_zod_schema_name_default = createRule({
|
|
|
18627
19286
|
if (binding !== null) zodBindings.add(binding);
|
|
18628
19287
|
}
|
|
18629
19288
|
function isZodChain(node) {
|
|
18630
|
-
const root =
|
|
19289
|
+
const root = calleeChainRoot2(node);
|
|
18631
19290
|
if (root === null) return false;
|
|
18632
19291
|
const binding = resolvedBinding(root);
|
|
18633
19292
|
return binding !== null && zodBindings.has(binding);
|
|
@@ -18643,16 +19302,16 @@ var require_pascal_case_zod_schema_name_default = createRule({
|
|
|
18643
19302
|
return false;
|
|
18644
19303
|
}
|
|
18645
19304
|
const terminal = terminalMethodName(init.callee);
|
|
18646
|
-
if (terminal === null ||
|
|
18647
|
-
const names =
|
|
19305
|
+
if (terminal === null || NON_SCHEMA_TERMINALS2.has(terminal)) return false;
|
|
19306
|
+
const names = chainMemberNames2(init.callee);
|
|
18648
19307
|
if (names.length === 0) return false;
|
|
18649
19308
|
if (isZodChain(init.callee)) {
|
|
18650
19309
|
return ZOD_SCHEMA_FACTORIES.has(names[0] ?? "") || ZOD_FACTORY_NAMESPACES.has(names[0] ?? "") && ZOD_SCHEMA_FACTORIES.has(names[1] ?? "");
|
|
18651
19310
|
}
|
|
18652
|
-
const root =
|
|
19311
|
+
const root = calleeChainRoot2(init.callee);
|
|
18653
19312
|
return root !== null && isSchemaBinding(root) && SCHEMA_RETURNING_METHODS.has(terminal);
|
|
18654
19313
|
}
|
|
18655
|
-
if (isTestFile(context.filename) ||
|
|
19314
|
+
if (isTestFile(context.filename) || BENCHMARK_PATH_RE2.test(context.filename.replaceAll("\\", "/")) || isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
18656
19315
|
return {};
|
|
18657
19316
|
}
|
|
18658
19317
|
return {
|
|
@@ -18855,14 +19514,14 @@ var RULES = {
|
|
|
18855
19514
|
"require-static-next-matcher": require_static_next_matcher_default,
|
|
18856
19515
|
"require-zod-form-validation": require_zod_form_validation_default,
|
|
18857
19516
|
"store-insert-requires-on-conflict": store_insert_requires_on_conflict_default,
|
|
18858
|
-
stepdown: stepdown_default,
|
|
19517
|
+
"stepdown": stepdown_default,
|
|
18859
19518
|
"source-coupled-test": source_coupled_test_default,
|
|
18860
19519
|
"sole-export-matches-filename": sole_export_matches_filename_default,
|
|
18861
19520
|
"require-pascal-case-zod-schema-name": require_pascal_case_zod_schema_name_default
|
|
18862
19521
|
};
|
|
18863
19522
|
var meta = {
|
|
18864
19523
|
name: "@sarj/eslint-plugin",
|
|
18865
|
-
version: "15.17.
|
|
19524
|
+
version: "15.17.3"
|
|
18866
19525
|
};
|
|
18867
19526
|
var APPLICATION_ONLY_RULES = [
|
|
18868
19527
|
"no-restricted-library-load",
|