@sarj/eslint-plugin 15.17.2 → 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 +460 -106
- package/dist/index.d.cts +14 -5
- package/dist/index.d.ts +14 -5
- package/dist/index.js +478 -124
- 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");
|
|
@@ -12706,11 +12923,11 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
12706
12923
|
create(context) {
|
|
12707
12924
|
const directBindings = /* @__PURE__ */ new Set();
|
|
12708
12925
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
12709
|
-
function
|
|
12926
|
+
function resolve2(identifier) {
|
|
12710
12927
|
return import_utils73.ASTUtils.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
12711
12928
|
}
|
|
12712
12929
|
function record(identifier, destination) {
|
|
12713
|
-
const variable =
|
|
12930
|
+
const variable = resolve2(identifier);
|
|
12714
12931
|
if (variable !== null) destination.add(variable);
|
|
12715
12932
|
}
|
|
12716
12933
|
function report2(node) {
|
|
@@ -12738,7 +12955,7 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
12738
12955
|
},
|
|
12739
12956
|
VariableDeclarator(node) {
|
|
12740
12957
|
if (node.parent.kind !== "const" || !requireUuid(node.init)) return;
|
|
12741
|
-
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) {
|
|
12742
12959
|
return;
|
|
12743
12960
|
}
|
|
12744
12961
|
if (node.id.type === import_utils73.AST_NODE_TYPES.Identifier) {
|
|
@@ -12755,14 +12972,14 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
12755
12972
|
"CallExpression:exit"(node) {
|
|
12756
12973
|
if (node.arguments.length !== 0) return;
|
|
12757
12974
|
if (node.callee.type === import_utils73.AST_NODE_TYPES.Identifier) {
|
|
12758
|
-
const variable2 =
|
|
12975
|
+
const variable2 = resolve2(node.callee);
|
|
12759
12976
|
if (variable2 !== null && directBindings.has(variable2)) report2(node);
|
|
12760
12977
|
return;
|
|
12761
12978
|
}
|
|
12762
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") {
|
|
12763
12980
|
return;
|
|
12764
12981
|
}
|
|
12765
|
-
const variable =
|
|
12982
|
+
const variable = resolve2(node.callee.object);
|
|
12766
12983
|
if (variable !== null && namespaceBindings.has(variable)) report2(node);
|
|
12767
12984
|
}
|
|
12768
12985
|
};
|
|
@@ -12792,22 +13009,22 @@ function memberName4(node) {
|
|
|
12792
13009
|
}
|
|
12793
13010
|
return null;
|
|
12794
13011
|
}
|
|
12795
|
-
function isCryptoLoader(node,
|
|
13012
|
+
function isCryptoLoader(node, resolve2) {
|
|
12796
13013
|
if (node.type !== import_utils74.AST_NODE_TYPES.CallExpression || node.arguments.length !== 1) return false;
|
|
12797
13014
|
const [argument] = node.arguments;
|
|
12798
13015
|
if (argument === void 0 || argument.type === import_utils74.AST_NODE_TYPES.SpreadElement || !isCryptoSpecifier(argument)) {
|
|
12799
13016
|
return false;
|
|
12800
13017
|
}
|
|
12801
13018
|
if (node.callee.type === import_utils74.AST_NODE_TYPES.Identifier) {
|
|
12802
|
-
return node.callee.name === "require" && isUnshadowedBuiltinIdentifier(node.callee,
|
|
13019
|
+
return node.callee.name === "require" && isUnshadowedBuiltinIdentifier(node.callee, resolve2);
|
|
12803
13020
|
}
|
|
12804
|
-
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";
|
|
12805
13022
|
}
|
|
12806
13023
|
function isCryptoSpecifier(node) {
|
|
12807
13024
|
return node.type === import_utils74.AST_NODE_TYPES.Literal && (node.value === "crypto" || node.value === "node:crypto");
|
|
12808
13025
|
}
|
|
12809
|
-
function isUnshadowedBuiltinIdentifier(identifier,
|
|
12810
|
-
const variable =
|
|
13026
|
+
function isUnshadowedBuiltinIdentifier(identifier, resolve2) {
|
|
13027
|
+
const variable = resolve2(identifier);
|
|
12811
13028
|
return variable === null || variable.defs.length === 0;
|
|
12812
13029
|
}
|
|
12813
13030
|
function propertyName4(node) {
|
|
@@ -12825,14 +13042,14 @@ var prefer_node_crypto_hash_default = createRule({
|
|
|
12825
13042
|
create(context) {
|
|
12826
13043
|
const directBindings = /* @__PURE__ */ new Set();
|
|
12827
13044
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
12828
|
-
function
|
|
13045
|
+
function resolve2(identifier) {
|
|
12829
13046
|
return import_utils74.ASTUtils.findVariable(
|
|
12830
13047
|
context.sourceCode.getScope(identifier),
|
|
12831
13048
|
identifier.name
|
|
12832
13049
|
);
|
|
12833
13050
|
}
|
|
12834
13051
|
function record(identifier, destination) {
|
|
12835
|
-
const variable =
|
|
13052
|
+
const variable = resolve2(identifier);
|
|
12836
13053
|
if (variable !== null) destination.add(variable);
|
|
12837
13054
|
}
|
|
12838
13055
|
return {
|
|
@@ -12847,7 +13064,7 @@ var prefer_node_crypto_hash_default = createRule({
|
|
|
12847
13064
|
}
|
|
12848
13065
|
},
|
|
12849
13066
|
VariableDeclarator(node) {
|
|
12850
|
-
if (node.parent.kind !== "const" || node.init === null || !isCryptoLoader(node.init,
|
|
13067
|
+
if (node.parent.kind !== "const" || node.init === null || !isCryptoLoader(node.init, resolve2)) {
|
|
12851
13068
|
return;
|
|
12852
13069
|
}
|
|
12853
13070
|
if (node.id.type === import_utils74.AST_NODE_TYPES.Identifier) {
|
|
@@ -12871,7 +13088,7 @@ var prefer_node_crypto_hash_default = createRule({
|
|
|
12871
13088
|
create,
|
|
12872
13089
|
directBindings,
|
|
12873
13090
|
namespaceBindings,
|
|
12874
|
-
|
|
13091
|
+
resolve2
|
|
12875
13092
|
))
|
|
12876
13093
|
return;
|
|
12877
13094
|
context.report({ node, messageId: "preferNodeCryptoHash" });
|
|
@@ -12885,17 +13102,17 @@ function importedName5(node) {
|
|
|
12885
13102
|
function isMemberCall(node, name) {
|
|
12886
13103
|
return node.callee.type === import_utils74.AST_NODE_TYPES.MemberExpression && memberName4(node.callee) === name;
|
|
12887
13104
|
}
|
|
12888
|
-
function isCreateHashCall(node, directBindings, namespaceBindings,
|
|
13105
|
+
function isCreateHashCall(node, directBindings, namespaceBindings, resolve2) {
|
|
12889
13106
|
if (node.callee.type === import_utils74.AST_NODE_TYPES.Identifier) {
|
|
12890
|
-
const variable2 =
|
|
13107
|
+
const variable2 = resolve2(node.callee);
|
|
12891
13108
|
return variable2 !== null && directBindings.has(variable2);
|
|
12892
13109
|
}
|
|
12893
13110
|
if (node.callee.type !== import_utils74.AST_NODE_TYPES.MemberExpression || memberName4(node.callee) !== "createHash") {
|
|
12894
13111
|
return false;
|
|
12895
13112
|
}
|
|
12896
|
-
if (isCryptoLoader(node.callee.object,
|
|
13113
|
+
if (isCryptoLoader(node.callee.object, resolve2)) return true;
|
|
12897
13114
|
if (node.callee.object.type !== import_utils74.AST_NODE_TYPES.Identifier) return false;
|
|
12898
|
-
const variable =
|
|
13115
|
+
const variable = resolve2(node.callee.object);
|
|
12899
13116
|
return variable !== null && namespaceBindings.has(variable);
|
|
12900
13117
|
}
|
|
12901
13118
|
|
|
@@ -14217,7 +14434,30 @@ var import_fs = require("fs");
|
|
|
14217
14434
|
var import_path = require("path");
|
|
14218
14435
|
|
|
14219
14436
|
// src/rules/_tailwind.ts
|
|
14220
|
-
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(/^!/, "");
|
|
14221
14461
|
var classTokens = (value) => value.split(/\s+/).filter(Boolean);
|
|
14222
14462
|
|
|
14223
14463
|
// src/rules/prefer-semantic-colors.ts
|
|
@@ -14226,7 +14466,10 @@ var PREFER_SEMANTIC_COLORS_DOCUMENTATION = {
|
|
|
14226
14466
|
rationale: "Semantic tokens keep themes and product meaning consistent while raw colors couple components to a palette value.",
|
|
14227
14467
|
remediation: "Replace raw palette and literal colors with the closest semantic design-system token or CSS variable.",
|
|
14228
14468
|
category: "style",
|
|
14229
|
-
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
|
+
],
|
|
14230
14473
|
examples: [
|
|
14231
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 },
|
|
14232
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 }
|
|
@@ -14354,7 +14597,13 @@ var SVG_SHAPE_PRIMITIVES = /* @__PURE__ */ new Set([
|
|
|
14354
14597
|
"tspan",
|
|
14355
14598
|
"use"
|
|
14356
14599
|
]);
|
|
14357
|
-
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();
|
|
14358
14607
|
function isSvgLikeElementName(name) {
|
|
14359
14608
|
return name === "svg" || SVG_DEFS_CONTAINERS.has(name) || /svg$/i.test(name);
|
|
14360
14609
|
}
|
|
@@ -14491,20 +14740,68 @@ var expandWorkspaceGlob = (root, glob) => {
|
|
|
14491
14740
|
if (star === -1) return [(0, import_path.join)(root, glob)];
|
|
14492
14741
|
const prefix = glob.slice(0, star).replace(/\/$/u, "");
|
|
14493
14742
|
const parent = prefix === "" ? root : (0, import_path.join)(root, prefix);
|
|
14494
|
-
if (!(0, import_fs.existsSync)(parent)) return [];
|
|
14495
|
-
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));
|
|
14496
14745
|
};
|
|
14497
14746
|
var propName = (key) => {
|
|
14498
14747
|
if (key.type === import_utils82.AST_NODE_TYPES.Identifier) return key.name;
|
|
14499
14748
|
if (key.type === import_utils82.AST_NODE_TYPES.Literal && typeof key.value === "string") return key.value;
|
|
14500
14749
|
return null;
|
|
14501
14750
|
};
|
|
14502
|
-
var
|
|
14751
|
+
var staticallyImportsExternalRenderer = (program) => program.body.some((statement) => {
|
|
14503
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) {
|
|
14504
14753
|
return false;
|
|
14505
14754
|
}
|
|
14506
|
-
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);
|
|
14507
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
|
+
};
|
|
14508
14805
|
var prefer_semantic_colors_default = createRule({
|
|
14509
14806
|
name: "prefer-semantic-colors",
|
|
14510
14807
|
documentation: PREFER_SEMANTIC_COLORS_DOCUMENTATION,
|
|
@@ -14518,30 +14815,34 @@ var prefer_semantic_colors_default = createRule({
|
|
|
14518
14815
|
type: "object",
|
|
14519
14816
|
additionalProperties: false,
|
|
14520
14817
|
properties: {
|
|
14521
|
-
requireSemanticTokens: { type: "boolean" }
|
|
14818
|
+
requireSemanticTokens: { type: "boolean" },
|
|
14819
|
+
opaqueForegroundPairs: { type: "boolean" }
|
|
14522
14820
|
}
|
|
14523
14821
|
}
|
|
14524
14822
|
],
|
|
14525
14823
|
messages: {
|
|
14526
14824
|
rawPalette: "Raw palette class '{{class}}' \u2014 use a semantic token (e.g. text-foreground, bg-primary, text-destructive, bg-muted).",
|
|
14527
14825
|
arbitraryColor: "Hardcoded color '{{class}}' \u2014 use a semantic token, or var(--\u2026). For charts/brand add an eslint-disable with a reason.",
|
|
14528
|
-
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}}'."
|
|
14529
14828
|
}
|
|
14530
14829
|
},
|
|
14531
14830
|
defaultOptions: [{}],
|
|
14532
14831
|
create(context, [options]) {
|
|
14533
14832
|
if (STORIES_FILE_RE.test(context.filename)) return {};
|
|
14534
|
-
if (
|
|
14535
|
-
if (options?.requireSemanticTokens === true && !hasSemanticTokenSystem(context.filename)) {
|
|
14536
|
-
|
|
14537
|
-
|
|
14538
|
-
let
|
|
14539
|
-
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();
|
|
14540
14839
|
const report2 = (node, messageId, data) => {
|
|
14541
|
-
|
|
14840
|
+
const key = `${node.range[0]}:${node.range[1]}:${messageId}:${JSON.stringify(data)}`;
|
|
14841
|
+
pendingReports.set(key, { node, messageId, data });
|
|
14542
14842
|
};
|
|
14543
14843
|
const reportClasses = (value, node) => {
|
|
14544
|
-
|
|
14844
|
+
const tokens = classTokens(value);
|
|
14845
|
+
for (const token of tokens) {
|
|
14545
14846
|
const base = tailwindBase(token);
|
|
14546
14847
|
if (RAW_PALETTE_RE.test(base)) {
|
|
14547
14848
|
report2(node, "rawPalette", { class: token });
|
|
@@ -14549,6 +14850,26 @@ var prefer_semantic_colors_default = createRule({
|
|
|
14549
14850
|
report2(node, "arbitraryColor", { class: token });
|
|
14550
14851
|
}
|
|
14551
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
|
+
}
|
|
14552
14873
|
};
|
|
14553
14874
|
const checkClassNode = (node) => {
|
|
14554
14875
|
if (node === null) return;
|
|
@@ -14596,8 +14917,8 @@ var prefer_semantic_colors_default = createRule({
|
|
|
14596
14917
|
}
|
|
14597
14918
|
},
|
|
14598
14919
|
CallExpression(node) {
|
|
14599
|
-
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" &&
|
|
14600
|
-
|
|
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;
|
|
14601
14922
|
}
|
|
14602
14923
|
if (node.callee.type === import_utils82.AST_NODE_TYPES.Identifier && CLASS_FNS.has(node.callee.name)) {
|
|
14603
14924
|
for (const arg of node.arguments) {
|
|
@@ -14632,13 +14953,13 @@ var prefer_semantic_colors_default = createRule({
|
|
|
14632
14953
|
if (name !== null && STYLE_COLOR_PROPS.has(name)) checkColorValueNode(node.value);
|
|
14633
14954
|
},
|
|
14634
14955
|
ImportExpression(node) {
|
|
14635
|
-
if (node.source.type === import_utils82.AST_NODE_TYPES.Literal && typeof node.source.value === "string" &&
|
|
14636
|
-
|
|
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;
|
|
14637
14958
|
}
|
|
14638
14959
|
},
|
|
14639
14960
|
"Program:exit"() {
|
|
14640
|
-
if (
|
|
14641
|
-
for (const descriptor of pendingReports) context.report(descriptor);
|
|
14961
|
+
if (importsExternalRenderer) return;
|
|
14962
|
+
for (const descriptor of pendingReports.values()) context.report(descriptor);
|
|
14642
14963
|
}
|
|
14643
14964
|
};
|
|
14644
14965
|
}
|
|
@@ -14647,11 +14968,11 @@ var prefer_semantic_colors_default = createRule({
|
|
|
14647
14968
|
// src/rules/prefer-server-actions.ts
|
|
14648
14969
|
var import_utils83 = require("@typescript-eslint/utils");
|
|
14649
14970
|
var PREFER_SERVER_ACTIONS_DOCUMENTATION = {
|
|
14650
|
-
summary: "Prefer Next.js Server Actions over
|
|
14971
|
+
summary: "Prefer Next.js Server Actions over same-origin API mutations.",
|
|
14651
14972
|
rationale: "Server Actions preserve typed application calls and avoid an internal JSON request-response boundary.",
|
|
14652
14973
|
remediation: "Move the mutation into a Server Action and invoke that action from the React client.",
|
|
14653
14974
|
category: "architecture",
|
|
14654
|
-
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."],
|
|
14655
14976
|
examples: [
|
|
14656
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 },
|
|
14657
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 }
|
|
@@ -14659,12 +14980,21 @@ var PREFER_SERVER_ACTIONS_DOCUMENTATION = {
|
|
|
14659
14980
|
};
|
|
14660
14981
|
var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
|
|
14661
14982
|
var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
|
|
14662
|
-
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\/)/;
|
|
14663
14984
|
var NON_REACT_FRAMEWORK_RE2 = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
|
|
14664
|
-
var
|
|
14985
|
+
var BASE_PATH_RE2 = /^\/(?!$)(?!.*[?#])(?:[^/]+\/)*[^/]+$/u;
|
|
14665
14986
|
function getScope(context, node) {
|
|
14666
14987
|
return context.sourceCode.getScope(node);
|
|
14667
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
|
+
}
|
|
14668
14998
|
function resolveNode(node, context) {
|
|
14669
14999
|
if (!node) return null;
|
|
14670
15000
|
if (node.type !== "Identifier") return node;
|
|
@@ -14684,22 +15014,29 @@ function resolveNode(node, context) {
|
|
|
14684
15014
|
}
|
|
14685
15015
|
return node;
|
|
14686
15016
|
}
|
|
14687
|
-
function isApiUrl(node, context) {
|
|
15017
|
+
function isApiUrl(node, context, apiPrefixes) {
|
|
14688
15018
|
const resolved = resolveNode(node, context);
|
|
14689
15019
|
if (!resolved) return false;
|
|
14690
15020
|
if (resolved.type === "Literal" && typeof resolved.value === "string") {
|
|
14691
|
-
return
|
|
15021
|
+
return apiPrefixes.some(
|
|
15022
|
+
(prefix) => resolved.value === prefix.slice(0, -1) || resolved.value.startsWith(prefix)
|
|
15023
|
+
);
|
|
14692
15024
|
}
|
|
14693
15025
|
if (resolved.type === "TemplateLiteral") {
|
|
14694
15026
|
const firstQuasi = resolved.quasis[0];
|
|
14695
15027
|
const cooked = firstQuasi?.value.cooked;
|
|
14696
|
-
return typeof cooked === "string" &&
|
|
15028
|
+
return typeof cooked === "string" && apiPrefixes.some(
|
|
15029
|
+
(prefix) => cooked === prefix.slice(0, -1) || cooked.startsWith(prefix)
|
|
15030
|
+
);
|
|
14697
15031
|
}
|
|
14698
15032
|
if (resolved.type === "BinaryExpression" && resolved.operator === "+") {
|
|
14699
|
-
return isApiUrl(resolved.left, context);
|
|
15033
|
+
return isApiUrl(resolved.left, context, apiPrefixes);
|
|
14700
15034
|
}
|
|
14701
15035
|
return false;
|
|
14702
15036
|
}
|
|
15037
|
+
function isValidBasePath2(basePath) {
|
|
15038
|
+
return BASE_PATH_RE2.test(basePath) && !basePath.split("/").some((segment) => segment === "." || segment === "..");
|
|
15039
|
+
}
|
|
14703
15040
|
function isMutationMethod(node, context) {
|
|
14704
15041
|
const resolved = resolveNode(node, context);
|
|
14705
15042
|
if (!resolved) return false;
|
|
@@ -14759,16 +15096,27 @@ var prefer_server_actions_default = createRule({
|
|
|
14759
15096
|
meta: {
|
|
14760
15097
|
type: "suggestion",
|
|
14761
15098
|
docs: {
|
|
14762
|
-
description: "Prefer Next.js Server Actions over
|
|
15099
|
+
description: "Prefer Next.js Server Actions over same-origin API mutations."
|
|
14763
15100
|
},
|
|
14764
|
-
schema: [
|
|
15101
|
+
schema: [
|
|
15102
|
+
{
|
|
15103
|
+
type: "object",
|
|
15104
|
+
additionalProperties: false,
|
|
15105
|
+
properties: {
|
|
15106
|
+
basePath: {
|
|
15107
|
+
type: "string",
|
|
15108
|
+
pattern: "^/(?!$)(?!.*[?#])(?!(?:.*/)?\\.\\.?(?:/|$))(?:[^/]+/)*[^/]+$"
|
|
15109
|
+
}
|
|
15110
|
+
}
|
|
15111
|
+
}
|
|
15112
|
+
],
|
|
14765
15113
|
messages: {
|
|
14766
|
-
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."
|
|
14767
15115
|
}
|
|
14768
15116
|
},
|
|
14769
|
-
defaultOptions: [],
|
|
14770
|
-
create(context) {
|
|
14771
|
-
const filename = context.filename;
|
|
15117
|
+
defaultOptions: [{}],
|
|
15118
|
+
create(context, [options]) {
|
|
15119
|
+
const filename = context.filename.replaceAll("\\", "/");
|
|
14772
15120
|
if (SKIP_FILE_REGEX.test(filename)) {
|
|
14773
15121
|
return {};
|
|
14774
15122
|
}
|
|
@@ -14776,22 +15124,28 @@ var prefer_server_actions_default = createRule({
|
|
|
14776
15124
|
(node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && NON_REACT_FRAMEWORK_RE2.test(node.source.value)
|
|
14777
15125
|
);
|
|
14778
15126
|
const hasUseClientDirective = context.sourceCode.ast.body.some(
|
|
14779
|
-
(node) => node.type === "ExpressionStatement" && node.
|
|
15127
|
+
(node) => node.type === "ExpressionStatement" && node.directive === "use client"
|
|
15128
|
+
);
|
|
15129
|
+
const hasUseServerDirective = context.sourceCode.ast.body.some(
|
|
15130
|
+
(node) => node.type === "ExpressionStatement" && node.directive === "use server"
|
|
14780
15131
|
);
|
|
14781
|
-
const
|
|
14782
|
-
(node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && (node.source.value === "
|
|
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")
|
|
14783
15134
|
);
|
|
14784
|
-
|
|
14785
|
-
if (!hasNextEvidence) {
|
|
15135
|
+
if (!hasUseClientDirective || hasUseServerDirective || importsServerOnly) {
|
|
14786
15136
|
return {};
|
|
14787
15137
|
}
|
|
15138
|
+
const apiPrefixes = ["/api/"];
|
|
15139
|
+
if (options?.basePath !== void 0 && isValidBasePath2(options.basePath)) {
|
|
15140
|
+
apiPrefixes.push(`${options.basePath}/api/`);
|
|
15141
|
+
}
|
|
14788
15142
|
return {
|
|
14789
15143
|
CallExpression(node) {
|
|
14790
15144
|
if (isNonReactFramework) return;
|
|
14791
15145
|
let isMutation = false;
|
|
14792
|
-
if (node.callee.type === "Identifier" && node.callee.name === "fetch") {
|
|
15146
|
+
if (node.callee.type === "Identifier" && node.callee.name === "fetch" && resolvesToGlobalFetch(context, node.callee)) {
|
|
14793
15147
|
const urlArg = node.arguments[0];
|
|
14794
|
-
if (urlArg && urlArg.type !== "SpreadElement" && isApiUrl(urlArg, context)) {
|
|
15148
|
+
if (urlArg && urlArg.type !== "SpreadElement" && isApiUrl(urlArg, context, apiPrefixes)) {
|
|
14795
15149
|
const initArg = node.arguments[1];
|
|
14796
15150
|
if (initArg && initArg.type !== "SpreadElement") {
|
|
14797
15151
|
const resolvedInit = resolveNode(initArg, context);
|
|
@@ -14808,7 +15162,7 @@ var prefer_server_actions_default = createRule({
|
|
|
14808
15162
|
const hasHandlerArg = node.arguments.some(
|
|
14809
15163
|
(arg) => arg.type !== "SpreadElement" && isFunctionArgument(arg, context)
|
|
14810
15164
|
);
|
|
14811
|
-
if (urlArg && urlArg.type !== "SpreadElement" && !hasHandlerArg && isApiUrl(urlArg, context)) {
|
|
15165
|
+
if (urlArg && urlArg.type !== "SpreadElement" && !hasHandlerArg && isApiUrl(urlArg, context, apiPrefixes)) {
|
|
14812
15166
|
isMutation = true;
|
|
14813
15167
|
}
|
|
14814
15168
|
}
|
|
@@ -14819,7 +15173,7 @@ var prefer_server_actions_default = createRule({
|
|
|
14819
15173
|
if (configArg && configArg.type === "ObjectExpression") {
|
|
14820
15174
|
const urlNode = getPropertyNode(configArg, "url");
|
|
14821
15175
|
const methodNode = getPropertyNode(configArg, "method");
|
|
14822
|
-
if (urlNode && isApiUrl(urlNode, context) && methodNode && isMutationMethod(methodNode, context)) {
|
|
15176
|
+
if (urlNode && isApiUrl(urlNode, context, apiPrefixes) && methodNode && isMutationMethod(methodNode, context)) {
|
|
14823
15177
|
isMutation = true;
|
|
14824
15178
|
}
|
|
14825
15179
|
}
|
|
@@ -19167,7 +19521,7 @@ var RULES = {
|
|
|
19167
19521
|
};
|
|
19168
19522
|
var meta = {
|
|
19169
19523
|
name: "@sarj/eslint-plugin",
|
|
19170
|
-
version: "15.17.
|
|
19524
|
+
version: "15.17.3"
|
|
19171
19525
|
};
|
|
19172
19526
|
var APPLICATION_ONLY_RULES = [
|
|
19173
19527
|
"no-restricted-library-load",
|