oxlint-plugin-arch 0.13.0 → 0.13.1

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.js CHANGED
@@ -50,6 +50,9 @@ function optionsAssertMockingApis(patterns) {
50
50
  throw new OptionsError("Test mocking APIs must be `*` or `<vi|jest|*>.<method|*>`.");
51
51
  }
52
52
  }
53
+ function optionsEscape(value) {
54
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
55
+ }
53
56
 
54
57
  // src/utils/ast.utils.ts
55
58
  var astSkippedKeys = new Set(["parent", "type", "start", "end", "range", "loc", "tokens", "comments"]);
@@ -237,9 +240,6 @@ function astIsInlineFunction(node) {
237
240
  function astIsCall(node) {
238
241
  return node.type === "CallExpression" || node.type === "NewExpression";
239
242
  }
240
- function astIsPipe(node) {
241
- return node.callee.type === "MemberExpression" && astMemberName(node.callee) === "pipe";
242
- }
243
243
  function astChildren(node) {
244
244
  return Object.entries(node).filter(([key]) => !astSkippedKeys.has(key)).map(([, child]) => child);
245
245
  }
@@ -249,17 +249,20 @@ function astSome(value, test, enter = () => true) {
249
249
  }
250
250
  return astIsNode(value) && (test(value) || enter(value) && astChildren(value).some((child) => astSome(child, test, enter)));
251
251
  }
252
- function astCallDepth(value) {
252
+ function astCallDepth(value, callbacks = false) {
253
253
  if (Array.isArray(value)) {
254
- return Math.max(0, ...value.map(astCallDepth));
254
+ return Math.max(0, ...value.map((item) => astCallDepth(item, callbacks)));
255
255
  }
256
- if (!astIsNode(value) || astIsInlineFunction(value)) {
256
+ if (!astIsNode(value)) {
257
257
  return 0;
258
258
  }
259
+ if (astIsInlineFunction(value)) {
260
+ return callbacks ? astCallDepth(value.body, callbacks) : 0;
261
+ }
259
262
  if (astIsCall(value)) {
260
- return Math.max(value.arguments.length === 0 ? 0 : 1 + astCallDepth(value.arguments), astCallDepth(value.callee));
263
+ return Math.max(value.arguments.length === 0 ? 0 : 1 + astCallDepth(value.arguments, callbacks), astCallDepth(value.callee, callbacks));
261
264
  }
262
- return astCallDepth(astChildren(value));
265
+ return astCallDepth(astChildren(value), callbacks);
263
266
  }
264
267
 
265
268
  // src/rules/backend/backend-orpc-output.ts
@@ -842,6 +845,27 @@ import { defineRule as defineRule5 } from "@oxlint/plugins";
842
845
  // src/utils/boundaries.utils.ts
843
846
  import { builtinModules } from "node:module";
844
847
  import { dirname, resolve } from "node:path";
848
+ var boundariesRuntimeConventions = {
849
+ server: ["server"],
850
+ rsc: ["rsc"],
851
+ actions: ["action"],
852
+ client: ["client"],
853
+ clientOnly: ["client-only"],
854
+ hooks: ["hooks"],
855
+ shared: ["utils", "constants"],
856
+ lib: "lib"
857
+ };
858
+ var boundariesSuffixList = { type: "array", items: { type: "string", minLength: 1 } };
859
+ function boundariesConventionsFor(options) {
860
+ return { ...boundariesRuntimeConventions, ...options.conventions };
861
+ }
862
+ function boundariesHasSuffix(suffixes, path, optionalExtension = false) {
863
+ const extension = optionalExtension ? "(?:\\.[cm]?[jt]sx?)?" : "\\.[cm]?[jt]sx?";
864
+ return suffixes.length > 0 && new RegExp(`\\.(?:${suffixes.map(optionsEscape).join("|")})${extension}$`).test(path);
865
+ }
866
+ function boundariesIsClientOnly(conventions, target) {
867
+ return conventions.hooks.some((folder) => target.includes(`/${folder}/`)) || conventions.clientOnly.some((suffix) => new RegExp(`\\.${optionsEscape(suffix)}(?:\\.|$)`).test(target));
868
+ }
845
869
  var boundariesServerGlobals = ["process", "Bun", "Deno", "Buffer", "__dirname", "__filename", "require"];
846
870
  var boundariesBrowserGlobals = [
847
871
  "window",
@@ -877,7 +901,21 @@ var boundariesRuntimeProperties = {
877
901
  dynamic: { type: "array", items: { type: "string" } },
878
902
  serverPackages: { type: "array", items: { type: "string" } },
879
903
  clientPackages: { type: "array", items: { type: "string" } },
880
- hookPackages: { type: "array", items: { type: "string" } }
904
+ hookPackages: { type: "array", items: { type: "string" } },
905
+ conventions: {
906
+ type: "object",
907
+ additionalProperties: false,
908
+ properties: {
909
+ server: boundariesSuffixList,
910
+ rsc: boundariesSuffixList,
911
+ actions: boundariesSuffixList,
912
+ client: boundariesSuffixList,
913
+ clientOnly: boundariesSuffixList,
914
+ hooks: boundariesSuffixList,
915
+ shared: boundariesSuffixList,
916
+ lib: { type: "string", minLength: 1 }
917
+ }
918
+ }
881
919
  };
882
920
  var boundariesServerPackages = ["@orpc/server", "react-dom/server"];
883
921
  var boundariesRuntimeMessages = {
@@ -908,16 +946,21 @@ function boundariesReport(context, node, source, reason) {
908
946
  data: { source, reason }
909
947
  });
910
948
  }
911
- function boundariesImportExpression(context, node, allowComputed, check) {
912
- const source = boundariesLiteralSource(node);
913
- if (source !== undefined) {
914
- check(source);
915
- } else if (!allowComputed) {
916
- context.report({ node, messageId: "computed" });
917
- }
949
+ function boundariesRuntimeImportVisitors(context, check, allowComputed) {
950
+ return {
951
+ ...boundariesExportVisitors(check),
952
+ ImportExpression(node) {
953
+ const source = boundariesLiteralSource(node);
954
+ if (source !== undefined) {
955
+ check(node, source, false, false);
956
+ } else if (!allowComputed()) {
957
+ context.report({ node, messageId: "computed" });
958
+ }
959
+ }
960
+ };
918
961
  }
919
- function boundariesIsSharedFile(options, file) {
920
- return /\.(?:utils|constants)\.[cm]?[jt]sx?$/.test(file) || options.web.some((path) => file.startsWith(`${path}/lib/`));
962
+ function boundariesIsSharedFile(options, conventions, file) {
963
+ return boundariesHasSuffix(conventions.shared, file) || options.web.some((path) => file.startsWith(`${path}/${conventions.lib}/`));
921
964
  }
922
965
  function boundariesDeclaredVariable(start, name) {
923
966
  for (let scope = start;scope; scope = scope.upper) {
@@ -946,7 +989,7 @@ function boundariesMemberName(node) {
946
989
  }
947
990
  return node.computed && node.property.type === "Literal" && typeof node.property.value === "string" ? node.property.value : null;
948
991
  }
949
- function boundariesResolveTarget(options, file, source) {
992
+ function boundariesResolveTarget(options, file, source, services = "services") {
950
993
  const owner = [...options.web, ...options.backend].sort((a, b) => b.length - a.length).find((root) => file.startsWith(`${root}/`));
951
994
  const web = owner && options.web.includes(owner) ? owner : undefined;
952
995
  const alias = Object.entries(options.aliases).sort(([a], [b]) => b.length - a.length).find(([prefix]) => source === prefix || source.startsWith(`${prefix}/`));
@@ -957,13 +1000,13 @@ function boundariesResolveTarget(options, file, source) {
957
1000
  return resolve(dirname(file), source).replaceAll("\\", "/");
958
1001
  }
959
1002
  if (source.startsWith("@/") && web) {
960
- return `${web}/${source.slice(2)}`;
1003
+ return resolve(web, source.slice(2)).replaceAll("\\", "/");
961
1004
  }
962
1005
  if (source.startsWith("#/") && owner) {
963
- return `${owner}/${source.slice(2)}`;
1006
+ return resolve(owner, source.slice(2)).replaceAll("\\", "/");
964
1007
  }
965
1008
  if (source.startsWith("#services/") && owner) {
966
- return `${owner}/services/${source.slice(10)}`;
1009
+ return resolve(owner, services, source.slice("#services/".length)).replaceAll("\\", "/");
967
1010
  }
968
1011
  return source;
969
1012
  }
@@ -1145,7 +1188,25 @@ var boundariesDeny = defineRule6({
1145
1188
  import { defineRule as defineRule7 } from "@oxlint/plugins";
1146
1189
  import { basename, resolve as resolve2 } from "node:path";
1147
1190
  var boundariesSurfaces = ["service", "client", "server", "rsc", "utils"];
1148
- var boundariesActionOrQuery = /\/(?:[^/]+)-(?:action|query)\./;
1191
+ var boundariesConventions = {
1192
+ services: "services",
1193
+ registry: "service",
1194
+ types: "types",
1195
+ lib: "lib",
1196
+ contracts: ["types", "constants"],
1197
+ implementations: ["service", "utils"],
1198
+ implementationFolders: ["core"],
1199
+ operations: ["action", "query"],
1200
+ operationFolders: ["actions", "queries"],
1201
+ domainFiles: ["action", "query", "driver"],
1202
+ clients: ["client"]
1203
+ };
1204
+ function boundariesAny(values) {
1205
+ return values.length === 0 ? "(?!)" : `(?:${values.map(optionsEscape).join("|")})`;
1206
+ }
1207
+ function boundariesIsOperation(conventions, path) {
1208
+ return new RegExp(`/[^/]+-${boundariesAny(conventions.operations)}\\.`).test(path);
1209
+ }
1149
1210
  function boundariesContains(directory, candidate) {
1150
1211
  return candidate === directory || candidate.startsWith(`${directory}/`);
1151
1212
  }
@@ -1155,44 +1216,56 @@ function boundariesIsSurface(options, stem) {
1155
1216
  function boundariesStripExtension(path) {
1156
1217
  return path.replace(/\.[cm]?[jt]sx?$/, "");
1157
1218
  }
1219
+ function boundariesConventionsOf(options) {
1220
+ return { ...boundariesConventions, ...options.conventions };
1221
+ }
1158
1222
  function boundariesServiceFolder(options, root) {
1159
1223
  const nested = [...options.web, ...options.backend].filter((other) => other.length > root.length && boundariesContains(root, other));
1160
- return options.services?.find((folder) => folder.startsWith(`${root}/`) && !nested.some((other) => boundariesContains(other, folder))) ?? `${root}/services`;
1224
+ return options.services?.find((folder) => folder.startsWith(`${root}/`) && !nested.some((other) => boundariesContains(other, folder))) ?? `${root}/${boundariesConventionsOf(options).services}`;
1161
1225
  }
1162
- function boundariesDomain(path, appRoot, services) {
1226
+ function boundariesDomain(conventions, path, appRoot, services) {
1163
1227
  const relative = path.slice(appRoot.length + 1);
1164
- if (relative.startsWith("types/") && relative.endsWith(".types.ts")) {
1165
- return relative.slice("types/".length).split(".")[0];
1228
+ if (relative.startsWith(`${conventions.types}/`) && relative.endsWith(`.${conventions.types}.ts`)) {
1229
+ return relative.slice(conventions.types.length + 1).split(".")[0];
1166
1230
  }
1167
1231
  if (!boundariesContains(services, path) || path === services) {
1168
1232
  return;
1169
1233
  }
1170
1234
  const parts = path.slice(services.length + 1).split("/");
1171
- return parts.length > 1 ? parts[0] : parts[0].split(".")[0].replace(/-(?:action|query|driver)$/, "");
1235
+ return parts.length > 1 ? parts[0] : parts[0].split(".")[0].replace(new RegExp(`-${boundariesAny(conventions.domainFiles)}$`), "");
1172
1236
  }
1173
1237
  function boundariesPrivateDomain({ options, file, target, reExport, roots, owner }) {
1174
1238
  const roles = options.roles;
1239
+ const conventions = boundariesConventionsOf(options);
1175
1240
  if (!roles?.length) {
1176
1241
  return;
1177
1242
  }
1178
1243
  const targetOwner = roots.find((path) => boundariesContains(path, target));
1179
- const fileDomain = owner ? boundariesDomain(file, owner, boundariesServiceFolder(options, owner)) : undefined;
1180
- const targetDomain = targetOwner ? boundariesDomain(target, targetOwner, boundariesServiceFolder(options, targetOwner)) : undefined;
1244
+ const fileDomain = owner ? boundariesDomain(conventions, file, owner, boundariesServiceFolder(options, owner)) : undefined;
1245
+ const targetDomain = targetOwner ? boundariesDomain(conventions, target, targetOwner, boundariesServiceFolder(options, targetOwner)) : undefined;
1181
1246
  const sameDomain = owner && owner === targetOwner && fileDomain && fileDomain === targetDomain;
1182
1247
  const targetStem = boundariesStripExtension(target);
1183
- const publicSurface = boundariesIsSurface(options, targetStem) && !boundariesActionOrQuery.test(targetStem);
1184
- const privateTarget = roles.some((role) => targetStem.endsWith(`.${role}`)) || targetDomain && !publicSurface && !/\.(?:types|constants)$/.test(targetStem);
1248
+ const publicSurface = boundariesIsSurface(options, targetStem) && !boundariesIsOperation(conventions, targetStem);
1249
+ const privateTarget = roles.some((role) => targetStem.endsWith(`.${role}`)) || targetDomain && !publicSurface && !new RegExp(`\\.${boundariesAny(conventions.contracts)}$`).test(targetStem);
1185
1250
  if (!privateTarget) {
1186
1251
  return;
1187
1252
  }
1188
- if (reExport && file.endsWith(".ts") && boundariesIsSurface(options, file.slice(0, -".ts".length)) && !boundariesActionOrQuery.test(file)) {
1253
+ if (reExport && file.endsWith(".ts") && boundariesIsSurface(options, file.slice(0, -".ts".length)) && !boundariesIsOperation(conventions, file)) {
1189
1254
  return "public service and utility surfaces must not re-export private domain internals";
1190
1255
  }
1191
1256
  return sameDomain ? undefined : "private domain files stay in the same domain and application; use its service API or shared utilities";
1192
1257
  }
1258
+ function boundariesPortableFolder(options) {
1259
+ if (!options.portable) {
1260
+ return;
1261
+ }
1262
+ return typeof options.portable === "string" ? options.portable : boundariesConventionsOf(options).lib;
1263
+ }
1193
1264
  function boundariesPortableLib(check) {
1194
1265
  const { options, file, source, target, typeOnly, reExport, roots, web, backendImport } = check;
1195
- if (!options.portable || !web || !boundariesContains(`${web}/lib`, file) || boundariesServiceFolder(options, web) === `${web}/lib`) {
1266
+ const { types } = boundariesConventionsOf(options);
1267
+ const lib = boundariesPortableFolder(options);
1268
+ if (!lib || !web || !boundariesContains(`${web}/${lib}`, file) || boundariesServiceFolder(options, web) === `${web}/${lib}`) {
1196
1269
  return;
1197
1270
  }
1198
1271
  const portableTarget = target.startsWith("/") ? resolve2(target).replaceAll("\\", "/") : target;
@@ -1202,11 +1275,11 @@ function boundariesPortableLib(check) {
1202
1275
  if (backendImport || backendTarget || appService || appPackage) {
1203
1276
  return "portable frontend lib does not import application services or application APIs";
1204
1277
  }
1205
- const appType = roots.some((path) => boundariesContains(`${path}/types`, portableTarget));
1278
+ const appType = roots.some((path) => boundariesContains(`${path}/${types}`, portableTarget));
1206
1279
  const stem = boundariesStripExtension(basename(file)).replace(/\.utils$/, "");
1207
- const ownType = typeOnly && !reExport && boundariesStripExtension(portableTarget) === `${web}/types/${stem}.types`;
1280
+ const ownType = typeOnly && !reExport && boundariesStripExtension(portableTarget) === `${web}/${types}/${stem}.${types}`;
1208
1281
  if (appType && !ownType) {
1209
- return `portable frontend lib may only type-import its own contract '${web}/types/${stem}.types.ts'; app type re-exports are forbidden`;
1282
+ return `portable frontend lib may only type-import its own contract '${web}/${types}/${stem}.${types}.ts'; app type re-exports are forbidden`;
1210
1283
  }
1211
1284
  return;
1212
1285
  }
@@ -1225,7 +1298,24 @@ var boundariesImports = defineRule7({
1225
1298
  roles: { type: "array", items: { type: "string" } },
1226
1299
  surfaces: { type: "array", items: { type: "string", minLength: 1 }, default: boundariesSurfaces },
1227
1300
  services: { type: "array", items: { type: "string" } },
1228
- portable: { type: "boolean" },
1301
+ portable: { anyOf: [{ type: "boolean" }, { type: "string", minLength: 1 }] },
1302
+ conventions: {
1303
+ type: "object",
1304
+ additionalProperties: false,
1305
+ properties: {
1306
+ services: { type: "string", minLength: 1 },
1307
+ registry: { type: "string", minLength: 1 },
1308
+ types: { type: "string", minLength: 1 },
1309
+ lib: { type: "string", minLength: 1 },
1310
+ contracts: { type: "array", items: { type: "string", minLength: 1 } },
1311
+ implementations: { type: "array", items: { type: "string", minLength: 1 } },
1312
+ implementationFolders: { type: "array", items: { type: "string", minLength: 1 } },
1313
+ operations: { type: "array", items: { type: "string", minLength: 1 } },
1314
+ operationFolders: { type: "array", items: { type: "string", minLength: 1 } },
1315
+ domainFiles: { type: "array", items: { type: "string", minLength: 1 } },
1316
+ clients: { type: "array", items: { type: "string", minLength: 1 } }
1317
+ }
1318
+ },
1229
1319
  backendPackages: { type: "array", items: { type: "string" } },
1230
1320
  entrypoints: { type: "array", items: { type: "string" } },
1231
1321
  appPackages: { type: "array", items: { type: "string" } },
@@ -1244,7 +1334,8 @@ var boundariesImports = defineRule7({
1244
1334
  const file = context.filename.replaceAll("\\", "/");
1245
1335
  const roots = [...options.web, ...options.backend].sort((a, b) => b.length - a.length);
1246
1336
  const owner = roots.find((path) => boundariesContains(path, file));
1247
- const target = boundariesResolveTarget(options, file, source);
1337
+ const conventions = boundariesConventionsOf(options);
1338
+ const target = boundariesResolveTarget(options, file, source, conventions.services);
1248
1339
  const web = owner && options.web.includes(owner) ? owner : undefined;
1249
1340
  const backendImport = options.backend.some((path) => boundariesContains(path, target)) || boundariesMatchesImport(options.backendPackages, source);
1250
1341
  const imported = { options, file, source, target, typeOnly, reExport, roots, owner, web, backendImport };
@@ -1253,13 +1344,16 @@ var boundariesImports = defineRule7({
1253
1344
  const serviceFolders = roots.map((path) => boundariesServiceFolder(options, path));
1254
1345
  const serviceTarget = serviceFolders.find((folder) => boundariesContains(folder, target));
1255
1346
  const serviceFile = serviceFolders.find((folder) => boundariesContains(folder, file));
1256
- const domainContract = /\.(?:types|constants)\.ts$/.test(file) && !typeOnly && (/\/(?:core\/|services\/.*(?:\.(?:service|utils)|-(?:action|query)\.|\/(?:actions|queries)\/))/.test(target) || serviceTarget !== undefined && /\/.*(?:\.(?:service|utils)|-(?:action|query)\.|\/(?:actions|queries)\/)/.test(target.slice(serviceTarget.length)));
1347
+ const implementation = `(?:\\.${boundariesAny(conventions.implementations)}|-${boundariesAny(conventions.operations)}\\.|/${boundariesAny(conventions.operationFolders)}/)`;
1348
+ const services = optionsEscape(conventions.services);
1349
+ const domainContract = new RegExp(`\\.${boundariesAny(conventions.contracts)}\\.ts$`).test(file) && !typeOnly && (new RegExp(`/(?:${boundariesAny(conventions.implementationFolders)}/|${services}/.*${implementation})`).test(target) || serviceTarget !== undefined && new RegExp(`/.*${implementation}`).test(target.slice(serviceTarget.length)));
1350
+ const registry = `/${optionsEscape(conventions.registry)}(?:\\.ts)?$`;
1257
1351
  const reason = [
1258
1352
  web && backendImport && !options.entrypoints.includes(source) ? "frontend code uses only public backend entry points" : undefined,
1259
1353
  inPackage && appTarget ? "shared packages do not import app internals" : undefined,
1260
- (file.includes("/services/") && /\/services\/service(?:\.ts)?$/.test(target) || serviceFile !== undefined && /^\/service(?:\.ts)?$/.test(target.slice(serviceFile.length))) && !typeOnly ? "service dependencies are injected rather than read from the registry" : undefined,
1354
+ (file.includes(`/${conventions.services}/`) && new RegExp(`/${services}${registry}`).test(target) || serviceFile !== undefined && new RegExp(`^${registry}`).test(target.slice(serviceFile.length))) && !typeOnly ? "service dependencies are injected rather than read from the registry" : undefined,
1261
1355
  domainContract ? "domain types/constants use type-only imports from implementations" : undefined,
1262
- file.endsWith(".client.ts") && !typeOnly && boundariesMatchesImport(options.uiPackages ?? ["react", "react-dom"], source) ? "client services are React-free; use hooks for React behavior" : undefined,
1356
+ conventions.clients.some((client) => file.endsWith(`.${client}.ts`)) && !typeOnly && boundariesMatchesImport(options.uiPackages ?? ["react", "react-dom"], source) ? "client services are React-free; use hooks for React behavior" : undefined,
1263
1357
  boundariesPrivateDomain(imported),
1264
1358
  boundariesPortableLib(imported)
1265
1359
  ].findLast((candidate) => candidate !== undefined);
@@ -1300,7 +1394,8 @@ var boundariesImports = defineRule7({
1300
1394
  TSImportType(node) {
1301
1395
  const options = { ...boundariesImportsDefaults, ...optionsFirst(context) };
1302
1396
  const file = context.filename.replaceAll("\\", "/");
1303
- if (options.portable && options.web.some((path) => file.startsWith(`${path}/lib/`) && boundariesServiceFolder(options, path) !== `${path}/lib`)) {
1397
+ const lib = boundariesPortableFolder(options);
1398
+ if (lib && options.web.some((path) => file.startsWith(`${path}/${lib}/`) && boundariesServiceFolder(options, path) !== `${path}/${lib}`)) {
1304
1399
  check(node, node.source.value, true);
1305
1400
  }
1306
1401
  }
@@ -1344,6 +1439,7 @@ var boundariesNext = defineRule8({
1344
1439
  },
1345
1440
  createOnce(context) {
1346
1441
  let options;
1442
+ let conventions = boundariesConventionsFor({});
1347
1443
  let file = "";
1348
1444
  let server = false;
1349
1445
  let client = false;
@@ -1355,12 +1451,12 @@ var boundariesNext = defineRule8({
1355
1451
  return;
1356
1452
  }
1357
1453
  const target = boundariesResolveTarget(options, file, source);
1358
- const clientOnly = /(?:\/hooks\/|\.client-only(?:\.|$))/.test(target) || boundariesMatchesImport(options.clientPackages, source) || boundariesMatchesImport(options.client, source);
1454
+ const clientOnly = boundariesIsClientOnly(conventions, target) || boundariesMatchesImport(options.clientPackages, source) || boundariesMatchesImport(options.client, source);
1359
1455
  if (clientOnly && (server || shared)) {
1360
1456
  boundariesReport(context, node, source, "browser-only dependencies do not belong in server, RSC or shared modules");
1361
1457
  return;
1362
1458
  }
1363
- const serverOnly = boundariesIsServerPlatformImport(source, options.serverPackages) || boundariesIsBackendImport(options, target, source) || /\.(?:server|rsc)(?:\.[cm]?[jt]sx?)?$/.test(target) || boundariesMatchesImport(options.server, source);
1459
+ const serverOnly = boundariesIsServerPlatformImport(source, options.serverPackages) || boundariesIsBackendImport(options, target, source) || boundariesHasSuffix([...conventions.server, ...conventions.rsc], target, true) || boundariesMatchesImport(options.server, source);
1364
1460
  if (!serverOnly || server) {
1365
1461
  return;
1366
1462
  }
@@ -1369,6 +1465,7 @@ var boundariesNext = defineRule8({
1369
1465
  return {
1370
1466
  Program(program) {
1371
1467
  options = { ...boundariesNextDefaults, ...optionsFirst(context) };
1468
+ conventions = boundariesConventionsFor(options);
1372
1469
  reactNamespaces.clear();
1373
1470
  file = context.filename.replaceAll("\\", "/");
1374
1471
  const directives = new Map;
@@ -1379,9 +1476,9 @@ var boundariesNext = defineRule8({
1379
1476
  }
1380
1477
  const useServer = directives.get("use server");
1381
1478
  const useClient = directives.get("use client");
1382
- const serverName = /\.(?:server|rsc|action)\.[cm]?[jt]sx?$/.test(file);
1383
- const clientName = /\.client\.[cm]?[jt]sx?$/.test(file);
1384
- shared = boundariesIsSharedFile(options, file);
1479
+ const serverName = boundariesHasSuffix([...conventions.server, ...conventions.rsc, ...conventions.actions], file);
1480
+ const clientName = boundariesHasSuffix(conventions.client, file);
1481
+ shared = boundariesIsSharedFile(options, conventions, file);
1385
1482
  server = !useClient && (serverName || Boolean(useServer));
1386
1483
  client = clientName || Boolean(useClient);
1387
1484
  if (useClient && serverName) {
@@ -1398,7 +1495,7 @@ var boundariesNext = defineRule8({
1398
1495
  data: { directive: "use server", side: "client" }
1399
1496
  });
1400
1497
  }
1401
- if (/\.action\.[cm]?[jt]sx?$/.test(file) && !useServer) {
1498
+ if (boundariesHasSuffix(conventions.actions, file) && !useServer) {
1402
1499
  context.report({ node: program, messageId: "action" });
1403
1500
  }
1404
1501
  },
@@ -1428,10 +1525,7 @@ var boundariesNext = defineRule8({
1428
1525
  boundariesReport(context, node, `${node.object.name}.${name}`, boundariesClientHookReason);
1429
1526
  }
1430
1527
  },
1431
- ...boundariesExportVisitors(check),
1432
- ImportExpression(node) {
1433
- boundariesImportExpression(context, node, Boolean(options.dynamic?.includes(file)), (source) => check(node, source, false));
1434
- },
1528
+ ...boundariesRuntimeImportVisitors(context, check, () => Boolean(options.dynamic?.includes(file))),
1435
1529
  "Program:exit"() {
1436
1530
  const scope = context.sourceCode.scopeManager.scopes.at(0);
1437
1531
  if (!scope) {
@@ -1548,11 +1642,9 @@ var boundariesTanstackDefaults = {
1548
1642
  hookPackages: ["react", "@tanstack/react-query", "@tanstack/react-router"],
1549
1643
  startPackages: ["@tanstack/react-start"],
1550
1644
  rscPackages: ["@tanstack/react-start/rsc", "@tanstack/react-start-rsc"],
1551
- renderers: ["renderServerComponent", "renderToReadableStream"]
1645
+ renderers: ["renderServerComponent", "renderToReadableStream"],
1646
+ serverHooks: ["useId"]
1552
1647
  };
1553
- function boundariesIsClientHook(name) {
1554
- return /^use[A-Z]/.test(name) && name !== "useId";
1555
- }
1556
1648
  var boundariesTanstack = defineRule10({
1557
1649
  meta: {
1558
1650
  type: "problem",
@@ -1565,7 +1657,8 @@ var boundariesTanstack = defineRule10({
1565
1657
  rpc: { type: "array", items: { type: "string" } },
1566
1658
  startPackages: { type: "array", items: { type: "string" } },
1567
1659
  rscPackages: { type: "array", items: { type: "string" } },
1568
- renderers: { type: "array", items: { type: "string", minLength: 1 }, default: boundariesTanstackDefaults.renderers }
1660
+ renderers: { type: "array", items: { type: "string", minLength: 1 }, default: boundariesTanstackDefaults.renderers },
1661
+ serverHooks: { type: "array", items: { type: "string", minLength: 1 }, default: boundariesTanstackDefaults.serverHooks }
1569
1662
  }
1570
1663
  }
1571
1664
  ],
@@ -1573,6 +1666,7 @@ var boundariesTanstack = defineRule10({
1573
1666
  },
1574
1667
  createOnce(context) {
1575
1668
  let options;
1669
+ let conventions = boundariesConventionsFor({});
1576
1670
  let file = "";
1577
1671
  let serverFile = false;
1578
1672
  let rscFile = false;
@@ -1581,6 +1675,7 @@ var boundariesTanstack = defineRule10({
1581
1675
  let referenced;
1582
1676
  let imports = new Map;
1583
1677
  const boundaries = [];
1678
+ const clientHook = (name) => /^use[A-Z]/.test(name) && !options.serverHooks.includes(name);
1584
1679
  function imported(node) {
1585
1680
  const entry = imports.get(node.name);
1586
1681
  const variable = entry ? boundariesDeclaredVariable(context.sourceCode.getScope(node), node.name) : null;
@@ -1623,7 +1718,7 @@ var boundariesTanstack = defineRule10({
1623
1718
  return;
1624
1719
  }
1625
1720
  const target = boundariesResolveTarget(options, file, source);
1626
- const clientImport = /(?:\/hooks\/|\.client-only(?:\.|$))/.test(target) || boundariesMatchesImport(options.clientPackages, source) || boundariesMatchesImport(options.client, source);
1721
+ const clientImport = boundariesIsClientOnly(conventions, target) || boundariesMatchesImport(options.clientPackages, source) || boundariesMatchesImport(options.client, source);
1627
1722
  if (clientImport && (serverFile || rscFile || shared) && !clientSide(node.start)) {
1628
1723
  const references = node.type === "ImportDeclaration" ? runtimeReferences(node) : [];
1629
1724
  if (shared || references.length === 0 || !references.every(clientSide)) {
@@ -1631,7 +1726,7 @@ var boundariesTanstack = defineRule10({
1631
1726
  }
1632
1727
  return;
1633
1728
  }
1634
- const serverImport = boundariesIsServerPlatformImport(source, options.serverPackages) || boundariesIsBackendImport(options, target, source) || /\.server(?:\.[cm]?[jt]sx?)?$/.test(target) || boundariesMatchesImport(options.server, source);
1729
+ const serverImport = boundariesIsServerPlatformImport(source, options.serverPackages) || boundariesIsBackendImport(options, target, source) || boundariesHasSuffix(conventions.server, target, true) || boundariesMatchesImport(options.server, source);
1635
1730
  if (!renderer && !serverImport) {
1636
1731
  return;
1637
1732
  }
@@ -1659,10 +1754,11 @@ var boundariesTanstack = defineRule10({
1659
1754
  return {
1660
1755
  Program(program) {
1661
1756
  options = { ...boundariesTanstackDefaults, ...optionsFirst(context) };
1757
+ conventions = boundariesConventionsFor(options);
1662
1758
  file = context.filename.replaceAll("\\", "/");
1663
- serverFile = /\.server\.[cm]?[jt]sx?$/.test(file);
1664
- rscFile = /\.rsc\.[cm]?[jt]sx?$/.test(file);
1665
- shared = boundariesIsSharedFile(options, file);
1759
+ serverFile = boundariesHasSuffix(conventions.server, file);
1760
+ rscFile = boundariesHasSuffix(conventions.rsc, file);
1761
+ shared = boundariesIsSharedFile(options, conventions, file);
1666
1762
  runtimeIdentifiers.clear();
1667
1763
  referenced = undefined;
1668
1764
  boundaries.length = 0;
@@ -1729,7 +1825,7 @@ var boundariesTanstack = defineRule10({
1729
1825
  return;
1730
1826
  }
1731
1827
  for (const specifier of node.specifiers) {
1732
- if (specifier.type !== "ImportSpecifier" || specifier.importKind === "type" || specifier.imported.type !== "Identifier" || !boundariesIsClientHook(specifier.imported.name)) {
1828
+ if (specifier.type !== "ImportSpecifier" || specifier.importKind === "type" || specifier.imported.type !== "Identifier" || !clientHook(specifier.imported.name)) {
1733
1829
  continue;
1734
1830
  }
1735
1831
  const references = runtimeReferences(node, specifier.local.name);
@@ -1738,10 +1834,7 @@ var boundariesTanstack = defineRule10({
1738
1834
  }
1739
1835
  }
1740
1836
  },
1741
- ...boundariesExportVisitors(check),
1742
- ImportExpression(node) {
1743
- boundariesImportExpression(context, node, Boolean(options.dynamic?.includes(file)), (source) => check(node, source, false));
1744
- },
1837
+ ...boundariesRuntimeImportVisitors(context, check, () => Boolean(options.dynamic?.includes(file))),
1745
1838
  MemberExpression(node) {
1746
1839
  const name = boundariesMemberName(node);
1747
1840
  if (!name || node.object.type !== "Identifier") {
@@ -1751,7 +1844,7 @@ var boundariesTanstack = defineRule10({
1751
1844
  if (kind === "rsc-renderers" && options.renderers.includes(name) && (shared || !serverPosition(node.start, true))) {
1752
1845
  boundariesReport(context, node, name, "server renderers require a server execution boundary");
1753
1846
  }
1754
- if ((serverFile || rscFile) && kind === "client-hooks" && boundariesIsClientHook(name) && !clientSide(node.start)) {
1847
+ if ((serverFile || rscFile) && kind === "client-hooks" && clientHook(name) && !clientSide(node.start)) {
1755
1848
  boundariesReport(context, node, name, boundariesClientHookReason2);
1756
1849
  }
1757
1850
  },
@@ -2230,6 +2323,7 @@ var codeInline = defineRule16({
2230
2323
  properties: {
2231
2324
  max: { type: "integer", minimum: 1, default: 160 },
2232
2325
  nesting: { type: "integer", minimum: 1, default: 2 },
2326
+ callbacks: { type: "boolean", default: false },
2233
2327
  allow: optionsPatternSchema,
2234
2328
  keep: optionsPatternSchema,
2235
2329
  services: { type: "array", items: { type: "string" } }
@@ -2258,7 +2352,8 @@ var codeInline = defineRule16({
2258
2352
  return;
2259
2353
  }
2260
2354
  if (uses.length === 1) {
2261
- if (init && codeInlineEffectBetween(context.sourceCode.ast, node.range[1], uses[0].identifier.range[0])) {
2355
+ const use = uses[0].identifier;
2356
+ if (init && (codeInlineEffectBetween(context.sourceCode.ast, node.range[1], use.range[0]) || codeInlineEnclosingCalls(use) + astCallDepth(init, options.callbacks ?? false) > (options.nesting ?? 2))) {
2262
2357
  return;
2263
2358
  }
2264
2359
  context.report({
@@ -2270,7 +2365,12 @@ var codeInline = defineRule16({
2270
2365
  }
2271
2366
  const arrow = init?.type === "ArrowFunctionExpression" && init.body.type !== "BlockStatement" && !astSome(init.body, astIsInlineFunction);
2272
2367
  const call = init?.type === "CallExpression" && !astSome(init.arguments, astIsInlineFunction) && astCallDepth(init) <= 2;
2273
- if (!optionsPatternsTest(kept, id.name) && (arrow || call)) {
2368
+ const text = context.sourceCode.text;
2369
+ const fits = init !== null && uses.every(({ identifier }) => {
2370
+ const line = text.slice(sourceLineStart(text, identifier.range[0]), sourceLineEnd(text, identifier.range[0]));
2371
+ return line.length - id.name.length + context.sourceCode.getText(init).length <= (options.max ?? 160) && codeInlineEnclosingCalls(identifier) + astCallDepth(init, options.callbacks ?? false) <= (options.nesting ?? 2);
2372
+ });
2373
+ if (!optionsPatternsTest(kept, id.name) && (arrow || call) && fits) {
2274
2374
  context.report({
2275
2375
  node: id,
2276
2376
  messageId: "trivial",
@@ -2298,7 +2398,7 @@ var codeInline = defineRule16({
2298
2398
  deferred ||= current.type === "FunctionDeclaration" || astIsInlineFunction(current);
2299
2399
  }
2300
2400
  const wrapped = codeInlineWrapped.has(init.type) && use.parent.type === "MemberExpression" && use.parent.object === use;
2301
- if (service || wrapped || deferred || codeInlineEnclosingCalls(use) + astCallDepth(init) > (options.nesting ?? 2) || line.length - id.name.length + context.sourceCode.getText(init).length > (options.max ?? 160) || codeInlineEffectBetween(context.sourceCode.ast, declarator.range[1], use.range[0])) {
2401
+ if (service || wrapped || deferred || codeInlineEnclosingCalls(use) + astCallDepth(init, options.callbacks ?? false) > (options.nesting ?? 2) || line.length - id.name.length + context.sourceCode.getText(init).length > (options.max ?? 160) || codeInlineEffectBetween(context.sourceCode.ast, declarator.range[1], use.range[0])) {
2302
2402
  return;
2303
2403
  }
2304
2404
  context.report({
@@ -3232,7 +3332,8 @@ var commentsCode = defineRule26({
3232
3332
  why: { anyOf: [{ type: "boolean" }, { type: "string", minLength: 1 }], default: true },
3233
3333
  allow: { type: "array", items: { type: "string" } },
3234
3334
  jsdoc: { type: "boolean", default: false },
3235
- members: { type: "string", enum: ["off", "also", "only"] }
3335
+ members: { type: "string", enum: ["off", "also", "only"] },
3336
+ steps: { type: "boolean", default: false }
3236
3337
  }
3237
3338
  }
3238
3339
  ],
@@ -3247,9 +3348,31 @@ var commentsCode = defineRule26({
3247
3348
  let allowJsdoc = false;
3248
3349
  let allowed = [];
3249
3350
  let members = "off";
3351
+ let steps = false;
3352
+ let functionDepth = 0;
3250
3353
  const memberComments = new Set;
3354
+ const stepStarts = new Set;
3355
+ function enterFunction() {
3356
+ functionDepth++;
3357
+ }
3358
+ function exitFunction() {
3359
+ functionDepth--;
3360
+ }
3361
+ function addSteps(nodes, inFunction) {
3362
+ if (steps && inFunction) {
3363
+ for (const node of nodes) {
3364
+ stepStarts.add(node.range[0]);
3365
+ }
3366
+ }
3367
+ }
3368
+ function isStep(comment) {
3369
+ const source = context.sourceCode;
3370
+ const before = source.getTokenBefore(comment, { includeComments: true });
3371
+ const after = source.getTokenAfter(comment, { includeComments: true });
3372
+ return comment.type === "Line" && before?.loc.end.line !== comment.loc.start.line && !(before && (before.type === "Line" || before.type === "Block") && before.loc.end.line === comment.loc.start.line - 1) && after !== null && after.type !== "Line" && after.type !== "Block" && after.loc.start.line === comment.loc.end.line + 1 && stepStarts.has(after.range[0]);
3373
+ }
3251
3374
  function keepComment(comment, value, isWhy) {
3252
- return allowWhy && isWhy || sourceIsDirective(value) || allowJsdoc && comment.type === "Block" && context.sourceCode.text.startsWith("/**", comment.range[0]) || allowed.some((pattern) => pattern.test(value.trim()));
3375
+ return allowWhy && isWhy || sourceIsDirective(value) || allowJsdoc && comment.type === "Block" && context.sourceCode.text.startsWith("/**", comment.range[0]) || allowed.some((pattern) => pattern.test(value.trim())) || steps && isStep(comment);
3253
3376
  }
3254
3377
  function checkMember(node) {
3255
3378
  const parent = node.parent;
@@ -3273,7 +3396,10 @@ var commentsCode = defineRule26({
3273
3396
  allowJsdoc = options.jsdoc ?? false;
3274
3397
  allowed = (options.allow ?? []).map((pattern) => new RegExp(pattern));
3275
3398
  members = options.members ?? "off";
3399
+ steps = options.steps ?? false;
3400
+ functionDepth = 0;
3276
3401
  memberComments.clear();
3402
+ stepStarts.clear();
3277
3403
  },
3278
3404
  TSPropertySignature: checkMember,
3279
3405
  TSMethodSignature: checkMember,
@@ -3282,6 +3408,17 @@ var commentsCode = defineRule26({
3282
3408
  TSConstructSignatureDeclaration: checkMember,
3283
3409
  Property: checkMember,
3284
3410
  SpreadElement: checkMember,
3411
+ FunctionDeclaration: enterFunction,
3412
+ "FunctionDeclaration:exit": exitFunction,
3413
+ FunctionExpression: enterFunction,
3414
+ "FunctionExpression:exit": exitFunction,
3415
+ ArrowFunctionExpression: enterFunction,
3416
+ "ArrowFunctionExpression:exit": exitFunction,
3417
+ BlockStatement: (node) => addSteps(node.body, functionDepth > 0),
3418
+ SwitchCase: (node) => addSteps(node.consequent, functionDepth > 0),
3419
+ IfStatement: (node) => addSteps([node.consequent, ...node.alternate ? [node.alternate] : []], functionDepth > 0),
3420
+ "ForStatement, ForInStatement, ForOfStatement, WhileStatement, DoWhileStatement, LabeledStatement": (node) => addSteps([node.body], functionDepth > 0),
3421
+ ObjectExpression: (node) => addSteps(node.properties, true),
3285
3422
  "Program:exit"(program) {
3286
3423
  const text = context.sourceCode.text;
3287
3424
  const found = [];
@@ -3527,6 +3664,7 @@ var effectLayerFile = defineRule29({
3527
3664
  layer: { type: "string" },
3528
3665
  make: { type: "string" },
3529
3666
  prefix: { type: "string" },
3667
+ suffix: { type: "string" },
3530
3668
  builders: { type: "array", items: { type: "string" } },
3531
3669
  layers: optionsPatternSchema,
3532
3670
  services: optionsPatternSchema,
@@ -3539,8 +3677,8 @@ var effectLayerFile = defineRule29({
3539
3677
  notLayer: "`{{layer}}` is built with Layer: a Layer call, a Layer pipe, or a function returning one.",
3540
3678
  builderName: "The Effect that builds the service is named `{{make}}`, one per layer file.",
3541
3679
  moduleMethod: "Only `{{make}}` is an Effect at module level; move this into `{{make}}`, or into a utils file if two files use it.",
3542
- moduleHelper: "A layer file holds only `{{make}}`, {{prefix}}<Role> layers and `{{layer}}`; move this into `{{make}}`, or into a utils file if two files use it.",
3543
- localLayer: "A Layer other than `{{layer}}` is named {{prefix}}<Role> and is not exported.",
3680
+ moduleHelper: "A layer file holds only `{{make}}`, {{prefix}}<Role>{{suffix}} layers and `{{layer}}`; move this into `{{make}}`, or into a utils file if two files use it.",
3681
+ localLayer: "A Layer other than `{{layer}}` is named {{prefix}}<Role>{{suffix}} and is not exported.",
3544
3682
  makeReturn: "`{{make}}` returns the service through its tag (Tag.of({ … })), a Context, or a Layer.",
3545
3683
  inlineBuilder: "Build this layer from `{{make}}`, not an inline Effect: Layer.effect(Tag, {{make}}). One built layer per file."
3546
3684
  }
@@ -3549,6 +3687,7 @@ var effectLayerFile = defineRule29({
3549
3687
  let layer = "layer";
3550
3688
  let make = "make";
3551
3689
  let prefix = "layer";
3690
+ let suffix = "";
3552
3691
  let builders = new Set(effectBuilders);
3553
3692
  let layers = [];
3554
3693
  let services = [];
@@ -3565,14 +3704,15 @@ var effectLayerFile = defineRule29({
3565
3704
  const options = optionsFirst(context, {});
3566
3705
  layer = options.layer ?? "layer";
3567
3706
  make = options.make ?? "make";
3568
- prefix = options.prefix ?? "layer";
3707
+ suffix = options.suffix ?? "";
3708
+ prefix = options.prefix ?? (suffix ? "" : "layer");
3569
3709
  builders = new Set(options.builders ?? effectBuilders);
3570
3710
  constructors = new Set(options.constructors ?? effectLayerFileDefaultConstructors);
3571
- layers = optionsPatterns(options.layers ?? effectLayerFileDefaultLayers);
3711
+ layers = optionsPatterns(options.layers ?? [...effectLayerFileDefaultLayers, ...suffix ? [`(?:^|\\.)\\w+${optionsEscape(suffix)}$`] : []]);
3572
3712
  services = [...optionsPatterns(options.services ?? effectLayerFileDefaultServices), ...layers];
3573
3713
  },
3574
3714
  Program(program) {
3575
- const data = { layer, make, prefix };
3715
+ const data = { layer, make, prefix, suffix };
3576
3716
  const declarators = program.body.flatMap((statement) => {
3577
3717
  const declaration = astDeclaration(statement);
3578
3718
  const exported = statement.type === "ExportNamedDeclaration";
@@ -3632,7 +3772,8 @@ var effectLayerFile = defineRule29({
3632
3772
  data
3633
3773
  });
3634
3774
  }
3635
- if (isLayer && name !== layer && (exported || !name.startsWith(prefix) || !/^[A-Z]\w*$/.test(name.slice(prefix.length)))) {
3775
+ const role = name.endsWith(suffix) ? name.slice(prefix.length, name.length - suffix.length) : "";
3776
+ if (isLayer && name !== layer && (exported || !name.startsWith(prefix) || !/^[A-Z]\w*$/.test(role))) {
3636
3777
  context.report({
3637
3778
  node,
3638
3779
  messageId: "localLayer",
@@ -3669,7 +3810,7 @@ var effectLayerFile = defineRule29({
3669
3810
  context.report({
3670
3811
  node: argument,
3671
3812
  messageId: "inlineBuilder",
3672
- data: { layer, make, prefix }
3813
+ data: { layer, make, prefix, suffix }
3673
3814
  });
3674
3815
  }
3675
3816
  }
@@ -4548,9 +4689,6 @@ var filesDomainConstants = defineRule36({
4548
4689
  // src/rules/files/files-exports.ts
4549
4690
  import { defineRule as defineRule37 } from "@oxlint/plugins";
4550
4691
  import { posix as posix2 } from "node:path";
4551
- function escapeRegExp(value) {
4552
- return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
4553
- }
4554
4692
  var filesExports = defineRule37({
4555
4693
  meta: {
4556
4694
  type: "problem",
@@ -4612,7 +4750,7 @@ var filesExports = defineRule37({
4612
4750
  const camelDomain = namingCamelCase(domain);
4613
4751
  const allowed = (options.names ?? []).map((template) => template.replaceAll("{Domain}", pascalDomain).replaceAll("{domain}", camelDomain));
4614
4752
  const patterns = (options.patterns ?? []).map((template) => {
4615
- const source = template.replaceAll("{Domain}", escapeRegExp(pascalDomain)).replaceAll("{domain}", escapeRegExp(camelDomain));
4753
+ const source = template.replaceAll("{Domain}", optionsEscape(pascalDomain)).replaceAll("{domain}", optionsEscape(camelDomain));
4616
4754
  return new RegExp(`^(?:${source})$`);
4617
4755
  });
4618
4756
  const allowedLabel = [...allowed, ...patterns.map((pattern) => pattern.toString())].join(", ");
@@ -5443,6 +5581,12 @@ var layoutCallArguments = defineRule46({
5443
5581
  type: "boolean",
5444
5582
  default: true
5445
5583
  },
5584
+ pipe: {
5585
+ type: "object",
5586
+ additionalProperties: false,
5587
+ required: ["min"],
5588
+ properties: { min: { type: "integer", minimum: 2 }, methods: { type: "array", items: { type: "string" } } }
5589
+ },
5446
5590
  hug: {
5447
5591
  type: "object",
5448
5592
  additionalProperties: false,
@@ -5465,6 +5609,7 @@ var layoutCallArguments = defineRule46({
5465
5609
  ],
5466
5610
  messages: {
5467
5611
  multiline: "A call with {{min}}+ arguments puts each argument on its own line.",
5612
+ pipe: "A pipe with {{min}}+ steps puts each step on its own line.",
5468
5613
  hug: "Put every argument on its own line. Only a last callback, object or array may hug the call, after simple arguments.",
5469
5614
  prefer: "Hug the last argument: it is a callback, object or array after simple arguments.",
5470
5615
  join: "This fits on one line, so keep it on one line."
@@ -5474,11 +5619,13 @@ var layoutCallArguments = defineRule46({
5474
5619
  let options = {};
5475
5620
  let minArguments = 3;
5476
5621
  let indentUnit = sourceIndentUnit();
5477
- const split = (node) => {
5622
+ let pipeMethods = new Set;
5623
+ const isPipe = (node) => node.callee.type === "MemberExpression" && pipeMethods.has(astMemberName(node.callee) ?? "");
5624
+ const split = (node, min, messageId) => {
5478
5625
  const args = node.arguments;
5479
5626
  const last = args.at(-1);
5480
5627
  const source = context.sourceCode;
5481
- if (args.length < minArguments || !last || args.some((arg) => (arg.type === "ArrowFunctionExpression" || arg.type === "FunctionExpression") && arg.loc.start.line !== arg.loc.end.line)) {
5628
+ if (args.length < min || !last || args.some((arg) => (arg.type === "ArrowFunctionExpression" || arg.type === "FunctionExpression") && arg.loc.start.line !== arg.loc.end.line)) {
5482
5629
  return;
5483
5630
  }
5484
5631
  const text = source.text;
@@ -5500,8 +5647,8 @@ var layoutCallArguments = defineRule46({
5500
5647
  const argumentIndent = `${baseIndent}${indentUnit}`;
5501
5648
  context.report({
5502
5649
  node,
5503
- messageId: "multiline",
5504
- data: { min: String(minArguments) },
5650
+ messageId,
5651
+ data: { min: String(min) },
5505
5652
  fix: unsafe ? undefined : (fixer) => gaps.map((gap, index) => {
5506
5653
  if (index === 0) {
5507
5654
  return fixer.replaceTextRange(gap, `${newline}${argumentIndent}`);
@@ -5519,11 +5666,23 @@ var layoutCallArguments = defineRule46({
5519
5666
  const last = args.at(-1);
5520
5667
  const open = source.getTokenAfter(node.typeArguments ?? node.callee);
5521
5668
  const close = source.getLastToken(node);
5522
- if (!first || !last || !open || !close || args.every(astIsOneLine) || source.getCommentsInside(node).length > 0) {
5669
+ if (!first || !last || !open || !close || source.getCommentsInside(node).length > 0) {
5670
+ return;
5671
+ }
5672
+ if (args.every(astIsOneLine)) {
5673
+ if (first.loc.start.line > open.loc.end.line && close.loc.start.line === last.loc.end.line) {
5674
+ const newline2 = sourceNewline(text);
5675
+ const base2 = sourceLineIndent(text, open.range[0]);
5676
+ context.report({
5677
+ node,
5678
+ messageId: "hug",
5679
+ fix: (fixer) => sourceRespace(fixer, text, sourceItemGaps(open.range[1], args, close.range[0]), (_, end) => `${newline2}${end ? base2 : `${base2}${indentUnit}`}`)
5680
+ });
5681
+ }
5523
5682
  return;
5524
5683
  }
5525
5684
  const leading = args.slice(0, -1);
5526
- const canHug = !astIsPipe(node) && leading.every((arg) => astIsOneLine(arg) && astCallDepth(arg) <= 1) && layoutCallArgumentsHuggable(last);
5685
+ const canHug = !isPipe(node) && leading.every((arg) => astIsOneLine(arg) && astCallDepth(arg) <= 1) && layoutCallArgumentsHuggable(last);
5527
5686
  const gaps = sourceItemGaps(open.range[1], args, close.range[0]);
5528
5687
  if (first.loc.start.line > open.loc.end.line) {
5529
5688
  const head = [...leading.map((arg) => source.getText(arg)), text.slice(last.range[0], sourceLineEnd(text, last.range[0]))];
@@ -5569,13 +5728,15 @@ var layoutCallArguments = defineRule46({
5569
5728
  };
5570
5729
  const call = (node) => {
5571
5730
  const source = context.sourceCode;
5572
- if (options.split !== false) {
5573
- split(node);
5731
+ if (options.pipe && isPipe(node)) {
5732
+ split(node, options.pipe.min, "pipe");
5733
+ } else if (options.split !== false) {
5734
+ split(node, minArguments, "multiline");
5574
5735
  }
5575
5736
  if (options.hug) {
5576
5737
  hug(node, options.hug.max);
5577
5738
  }
5578
- if (options.join && !(options.join.keep ?? []).includes(source.getText(node.callee)) && !(astIsPipe(node) && node.arguments.length > 1) && astCallDepth(node) <= (options.join.nesting ?? Number.POSITIVE_INFINITY)) {
5739
+ if (options.join && !(options.join.keep ?? []).includes(source.getText(node.callee)) && !(isPipe(node) && (node.arguments.length > 1 || options.pipe)) && astCallDepth(node) <= (options.join.nesting ?? Number.POSITIVE_INFINITY)) {
5579
5740
  join3(node, node.arguments, source.getTokenAfter(node.typeArguments ?? node.callee), source.getLastToken(node));
5580
5741
  }
5581
5742
  };
@@ -5593,6 +5754,7 @@ var layoutCallArguments = defineRule46({
5593
5754
  options = optionsFirst(context, {});
5594
5755
  minArguments = options.min ?? 3;
5595
5756
  indentUnit = sourceIndentUnit(options.indent);
5757
+ pipeMethods = new Set(options.pipe?.methods ?? ["pipe"]);
5596
5758
  },
5597
5759
  CallExpression: call,
5598
5760
  NewExpression: call,
@@ -5728,12 +5890,19 @@ var layoutCallArray = defineRule47({
5728
5890
 
5729
5891
  // src/rules/layout/layout-call-depth.ts
5730
5892
  import { defineRule as defineRule48 } from "@oxlint/plugins";
5731
- function layoutCallDepthOuter(node) {
5893
+ function layoutCallDepthOuter(node, callbacks) {
5732
5894
  const parent = node.parent;
5733
- if (!parent || parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression" || parent.type.endsWith("Statement")) {
5895
+ if (!parent || !callbacks && (parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression")) {
5734
5896
  return;
5735
5897
  }
5736
- return astIsCall(parent) && parent.arguments.some((argument) => argument === node) ? parent : layoutCallDepthOuter(parent);
5898
+ if (parent.type.endsWith("Statement")) {
5899
+ let owner = parent;
5900
+ while (owner?.type.endsWith("Statement")) {
5901
+ owner = owner.parent;
5902
+ }
5903
+ return callbacks && owner && astIsInlineFunction(owner) ? layoutCallDepthOuter(owner, callbacks) : undefined;
5904
+ }
5905
+ return astIsCall(parent) && parent.arguments.some((argument) => argument === node) ? parent : layoutCallDepthOuter(parent, callbacks);
5737
5906
  }
5738
5907
  var layoutCallDepth = defineRule48({
5739
5908
  meta: {
@@ -5742,7 +5911,7 @@ var layoutCallDepth = defineRule48({
5742
5911
  {
5743
5912
  type: "object",
5744
5913
  additionalProperties: false,
5745
- properties: { max: { type: "integer", minimum: 1, default: 2 } }
5914
+ properties: { max: { type: "integer", minimum: 1, default: 2 }, callbacks: { type: "boolean", default: false } }
5746
5915
  }
5747
5916
  ],
5748
5917
  messages: {
@@ -5751,10 +5920,11 @@ var layoutCallDepth = defineRule48({
5751
5920
  },
5752
5921
  createOnce(context) {
5753
5922
  let max = 2;
5923
+ let callbacks = false;
5754
5924
  const check = (node) => {
5755
- const depth = astCallDepth(node);
5756
- const outer = layoutCallDepthOuter(node);
5757
- if (!astIsOneLine(node) || depth <= max || outer !== undefined && astIsOneLine(outer) && astCallDepth(outer) > max) {
5925
+ const depth = astCallDepth(node, callbacks);
5926
+ const outer = layoutCallDepthOuter(node, callbacks);
5927
+ if (!astIsOneLine(node) || depth <= max || outer !== undefined && astIsOneLine(outer) && astCallDepth(outer, callbacks) > max) {
5758
5928
  return;
5759
5929
  }
5760
5930
  context.report({
@@ -5765,7 +5935,9 @@ var layoutCallDepth = defineRule48({
5765
5935
  };
5766
5936
  return {
5767
5937
  before() {
5768
- max = optionsFirst(context, {}).max ?? 2;
5938
+ const options = optionsFirst(context, {});
5939
+ max = options.max ?? 2;
5940
+ callbacks = options.callbacks ?? false;
5769
5941
  },
5770
5942
  CallExpression: check,
5771
5943
  NewExpression: check
@@ -6096,6 +6268,10 @@ var layoutObject = defineRule53({
6096
6268
  min: { type: "integer", minimum: 1, default: 3 },
6097
6269
  scope: { type: "string", enum: ["call-args", "all"], default: "call-args" },
6098
6270
  indent: { ...sourceIndentSchema, default: 2 },
6271
+ params: { type: "boolean", default: false },
6272
+ nested: { type: "boolean", default: false },
6273
+ siblings: { type: "boolean", default: false },
6274
+ inner: { type: "integer", minimum: 1 },
6099
6275
  join: {
6100
6276
  type: "object",
6101
6277
  additionalProperties: false,
@@ -6111,6 +6287,11 @@ var layoutObject = defineRule53({
6111
6287
  messages: {
6112
6288
  callArgs: "Object with {{min}}+ properties in a call should have each property on its own line.",
6113
6289
  always: "Object with {{min}}+ properties should have each property on its own line.",
6290
+ nested: "An object holding an object or function should have each property on its own line.",
6291
+ nestedName: "An object holding an object or function should have each property on its own line. Name this call first: split here, it would break a chained call or a ternary.",
6292
+ siblings: "A sibling call in this list splits its object; split this one too.",
6293
+ inner: "An object with {{inner}}+ entries inside a split object should have each entry on its own line.",
6294
+ params: "Destructured parameter with {{min}}+ properties should have each property on its own line.",
6114
6295
  join: "This object fits on one line, so keep it on one line."
6115
6296
  }
6116
6297
  },
@@ -6121,6 +6302,119 @@ var layoutObject = defineRule53({
6121
6302
  let newline = `
6122
6303
  `;
6123
6304
  let join3;
6305
+ let params = false;
6306
+ let nested = false;
6307
+ let siblings = false;
6308
+ let inner;
6309
+ const splitCounts = new WeakMap;
6310
+ function holdsNested(node) {
6311
+ return nested && astIsOneLine(node) && node.properties.some((property) => property.type === "Property" && (property.value.type === "ObjectExpression" && property.value.properties.length > 0 || astIsInlineFunction(property.value))) && !insideOneLineObject(node.parent);
6312
+ }
6313
+ function insideOneLineObject(start) {
6314
+ for (let ancestor = start;ancestor && !ancestor.type.endsWith("Statement"); ancestor = ancestor.parent) {
6315
+ if (ancestor.type === "ObjectExpression" && astIsOneLine(ancestor)) {
6316
+ return true;
6317
+ }
6318
+ }
6319
+ return false;
6320
+ }
6321
+ function breaksAround(node) {
6322
+ for (let child = node, parent = node.parent;parent; child = parent, parent = parent.parent) {
6323
+ if (parent.type === "ConditionalExpression" || parent.type === "MemberExpression" && parent.object === child && astIsCall(child)) {
6324
+ return true;
6325
+ }
6326
+ if (astIsInlineFunction(parent) || parent.type.endsWith("Statement") || parent.type.endsWith("Declaration")) {
6327
+ return false;
6328
+ }
6329
+ }
6330
+ return false;
6331
+ }
6332
+ function keptByInner(node) {
6333
+ const parent = node.parent;
6334
+ return inner !== undefined && node.properties.length >= inner && parent.type === "Property" && parent.value === node && !astIsOneLine(parent.parent);
6335
+ }
6336
+ function splitSiblings(node) {
6337
+ const call = node.parent;
6338
+ const list = call.parent;
6339
+ if (!siblings || node.properties.length === 0 || !astIsCall(call) || list?.type !== "ArrayExpression") {
6340
+ return 0;
6341
+ }
6342
+ let counts = splitCounts.get(list);
6343
+ if (!counts) {
6344
+ counts = new Map;
6345
+ for (const element of list.elements) {
6346
+ if (!element || !astIsCall(element)) {
6347
+ continue;
6348
+ }
6349
+ for (const [index, argument] of element.arguments.entries()) {
6350
+ if (argument.type === "ObjectExpression" && !astIsOneLine(argument)) {
6351
+ const key = `${context.sourceCode.getText(element.callee)}#${index}`;
6352
+ counts.set(key, (counts.get(key) ?? 0) + 1);
6353
+ }
6354
+ }
6355
+ }
6356
+ splitCounts.set(list, counts);
6357
+ }
6358
+ return counts.get(`${context.sourceCode.getText(call.callee)}#${call.arguments.indexOf(node)}`) ?? 0;
6359
+ }
6360
+ function joinBlocked(node, call) {
6361
+ const callee = call && astIsCall(call) ? astDottedName(call.callee) : undefined;
6362
+ const kept = callee !== undefined && (join3?.keep ?? []).some((name) => callee === name || callee.endsWith(`.${name}`));
6363
+ return kept || keptByInner(node) || splitSiblings(node) > 1 || node.properties.length === 0 || !node.properties.every(layoutObjectIsFlat) || context.sourceCode.getCommentsInside(node).length > 0;
6364
+ }
6365
+ function reportSplit(node, properties, open, close, messageId) {
6366
+ const source = context.sourceCode;
6367
+ const text = source.text;
6368
+ const baseIndent = sourceLineIndent(text, open);
6369
+ const propertyIndent = `${baseIndent}${indentUnit}`;
6370
+ context.report({
6371
+ node,
6372
+ messageId,
6373
+ data: { min: String(minProperties), inner: String(inner ?? "") },
6374
+ fix: (fixer) => {
6375
+ if (messageId === "nestedName" || properties.length === 0 || source.getCommentsInside(node).length > 0 || !layoutObjectIsWhitespace(text.slice(open + 1, properties[0].range[0]))) {
6376
+ return null;
6377
+ }
6378
+ const gaps = [[open + 1, properties[0].range[0]]];
6379
+ for (const [index, property] of properties.entries()) {
6380
+ const next = properties.at(index + 1);
6381
+ const after = property.range[1];
6382
+ const limit = next ? next.range[0] : close;
6383
+ const between = text.slice(after, limit);
6384
+ const comma = between.indexOf(",");
6385
+ if (comma === -1) {
6386
+ if (next || !layoutObjectIsWhitespace(between)) {
6387
+ return null;
6388
+ }
6389
+ gaps.push([after, close]);
6390
+ continue;
6391
+ }
6392
+ if (!layoutObjectIsWhitespace(between.slice(0, comma)) || !layoutObjectIsWhitespace(between.slice(comma + 1))) {
6393
+ return null;
6394
+ }
6395
+ gaps.push([after + comma + 1, limit]);
6396
+ }
6397
+ return gaps.map((gap, index) => fixer.replaceTextRange(gap, index === gaps.length - 1 ? `${newline}${baseIndent}` : `${newline}${propertyIndent}`));
6398
+ }
6399
+ });
6400
+ }
6401
+ function checkParams(node) {
6402
+ if (!params) {
6403
+ return;
6404
+ }
6405
+ for (const param of node.params) {
6406
+ const pattern = param.type === "AssignmentPattern" ? param.left : param;
6407
+ if (pattern.type !== "ObjectPattern" || pattern.properties.length < minProperties) {
6408
+ continue;
6409
+ }
6410
+ const text = context.sourceCode.text;
6411
+ const close = text.lastIndexOf("}", (pattern.typeAnnotation?.range[0] ?? pattern.range[1]) - 1);
6412
+ if (!text.slice(pattern.range[0], close).includes(`
6413
+ `)) {
6414
+ reportSplit(pattern, pattern.properties, pattern.range[0], close, "params");
6415
+ }
6416
+ }
6417
+ }
6124
6418
  return {
6125
6419
  before() {
6126
6420
  const options = optionsFirst(context, {});
@@ -6129,24 +6423,39 @@ var layoutObject = defineRule53({
6129
6423
  indentUnit = sourceIndentUnit(options.indent);
6130
6424
  newline = sourceNewline(context.sourceCode.text);
6131
6425
  join3 = options.join;
6426
+ params = options.params ?? false;
6427
+ nested = options.nested ?? false;
6428
+ siblings = options.siblings ?? false;
6429
+ inner = options.inner;
6132
6430
  },
6431
+ FunctionDeclaration: checkParams,
6432
+ FunctionExpression: checkParams,
6433
+ ArrowFunctionExpression: checkParams,
6133
6434
  ObjectExpression(node) {
6134
6435
  const source = context.sourceCode;
6135
6436
  const text = source.text;
6136
6437
  const properties = node.properties;
6137
- const split = properties.length >= minProperties;
6438
+ const parent = node.parent;
6439
+ const full = properties.length >= minProperties;
6440
+ const holds = !full && holdsNested(node);
6441
+ if (!full && !holds && astIsOneLine(node) && splitSiblings(node) > 0) {
6442
+ reportSplit(node, properties, node.range[0], node.range[1] - 1, "siblings");
6443
+ return;
6444
+ }
6445
+ if (astIsOneLine(node) && keptByInner(node)) {
6446
+ reportSplit(node, properties, node.range[0], node.range[1] - 1, "inner");
6447
+ return;
6448
+ }
6449
+ const split = full || holds;
6138
6450
  if (split ? !astIsOneLine(node) : !join3 || astIsOneLine(node)) {
6139
6451
  return;
6140
6452
  }
6141
- const parent = node.parent;
6142
6453
  const call = (parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node) ? parent : undefined;
6143
6454
  if (scope === "call-args" && !call) {
6144
6455
  return;
6145
6456
  }
6146
6457
  if (!split) {
6147
- const callee = call ? astDottedName(call.callee) : undefined;
6148
- const kept = callee !== undefined && (join3?.keep ?? []).some((name) => callee === name || callee.endsWith(`.${name}`));
6149
- if (kept || properties.length === 0 || !properties.every(layoutObjectIsFlat) || source.getCommentsInside(node).length > 0) {
6458
+ if (joinBlocked(node, call)) {
6150
6459
  return;
6151
6460
  }
6152
6461
  const gaps = sourceItemGaps(node.range[0] + 1, properties, node.range[1] - 1);
@@ -6162,39 +6471,9 @@ var layoutObject = defineRule53({
6162
6471
  });
6163
6472
  return;
6164
6473
  }
6165
- const baseIndent = sourceLineIndent(text, node.range[0]);
6166
- const propertyIndent = `${baseIndent}${indentUnit}`;
6167
- const close = node.range[1] - 1;
6168
- context.report({
6169
- node,
6170
- messageId: scope === "all" ? "always" : "callArgs",
6171
- data: { min: String(minProperties) },
6172
- fix: (fixer) => {
6173
- if (source.getCommentsInside(node).length > 0 || !layoutObjectIsWhitespace(text.slice(node.range[0] + 1, properties[0].range[0]))) {
6174
- return null;
6175
- }
6176
- const gaps = [[node.range[0] + 1, properties[0].range[0]]];
6177
- for (const [index, property] of properties.entries()) {
6178
- const next = properties.at(index + 1);
6179
- const after = property.range[1];
6180
- const limit = next ? next.range[0] : close;
6181
- const between = text.slice(after, limit);
6182
- const comma = between.indexOf(",");
6183
- if (comma === -1) {
6184
- if (next || !layoutObjectIsWhitespace(between)) {
6185
- return null;
6186
- }
6187
- gaps.push([after, close]);
6188
- continue;
6189
- }
6190
- if (!layoutObjectIsWhitespace(between.slice(0, comma)) || !layoutObjectIsWhitespace(between.slice(comma + 1))) {
6191
- return null;
6192
- }
6193
- gaps.push([after + comma + 1, limit]);
6194
- }
6195
- return gaps.map((gap, index) => fixer.replaceTextRange(gap, index === gaps.length - 1 ? `${newline}${baseIndent}` : `${newline}${propertyIndent}`));
6196
- }
6197
- });
6474
+ const messageId = scope === "all" ? "always" : "callArgs";
6475
+ const nestedId = breaksAround(node) ? "nestedName" : "nested";
6476
+ reportSplit(node, properties, node.range[0], node.range[1] - 1, holds ? nestedId : messageId);
6198
6477
  }
6199
6478
  };
6200
6479
  }
@@ -6323,19 +6602,29 @@ import { defineRule as defineRule55 } from "@oxlint/plugins";
6323
6602
  var layoutPipe = defineRule55({
6324
6603
  meta: {
6325
6604
  type: "layout",
6605
+ schema: [{ type: "object", additionalProperties: false, properties: { methods: { type: "array", items: { type: "string" } } } }],
6326
6606
  messages: {
6327
- after: "No ).pipe( after a call that spans lines. Name the value and pipe the name, or pass the call to the step data-first.",
6607
+ after: "No ).{{method}}( after a call that spans lines. Name the value and pipe the name, or pass the call to the step data-first.",
6328
6608
  step: "A pipe step may span lines only through a callback, object or array. Name this call first, then pass the name."
6329
6609
  }
6330
6610
  },
6331
6611
  createOnce(context) {
6612
+ let methods = new Set;
6332
6613
  return {
6614
+ before() {
6615
+ methods = new Set(optionsFirst(context, {}).methods ?? ["pipe"]);
6616
+ },
6333
6617
  CallExpression(node) {
6334
6618
  const callee = node.callee;
6335
- if (callee.type === "MemberExpression" && astMemberName(callee) === "pipe" && astIsCall(callee.object) && !astIsOneLine(callee.object) && callee.property.loc.start.line === callee.object.loc.end.line) {
6336
- context.report({ node: callee.property, messageId: "after" });
6619
+ const piped = callee.type === "MemberExpression" && methods.has(astMemberName(callee) ?? "");
6620
+ if (piped && astIsCall(callee.object) && !astIsOneLine(callee.object) && callee.property.loc.start.line === callee.object.loc.end.line) {
6621
+ context.report({
6622
+ node: callee.property,
6623
+ messageId: "after",
6624
+ data: { method: astMemberName(callee) ?? "pipe" }
6625
+ });
6337
6626
  }
6338
- if (!astIsPipe(node)) {
6627
+ if (!piped) {
6339
6628
  return;
6340
6629
  }
6341
6630
  for (const step of node.arguments) {
@@ -6474,10 +6763,70 @@ var layoutTernary = defineRule56({
6474
6763
  }
6475
6764
  });
6476
6765
 
6477
- // src/rules/naming/naming-declarations.ts
6766
+ // src/rules/layout/layout-type-arguments.ts
6478
6767
  import { defineRule as defineRule57 } from "@oxlint/plugins";
6768
+ var layoutTypeArguments = defineRule57({
6769
+ meta: {
6770
+ type: "layout",
6771
+ fixable: "code",
6772
+ schema: [
6773
+ {
6774
+ type: "object",
6775
+ additionalProperties: false,
6776
+ properties: {
6777
+ min: { type: "integer", minimum: 2, default: 3 },
6778
+ indent: { ...sourceIndentSchema, default: 2 }
6779
+ }
6780
+ }
6781
+ ],
6782
+ messages: {
6783
+ union: "A union of {{min}}+ types in type arguments puts each type on its own line."
6784
+ }
6785
+ },
6786
+ createOnce(context) {
6787
+ let minMembers = 3;
6788
+ let indentUnit = sourceIndentUnit();
6789
+ return {
6790
+ before() {
6791
+ const options = optionsFirst(context, {});
6792
+ minMembers = options.min ?? 3;
6793
+ indentUnit = sourceIndentUnit(options.indent);
6794
+ },
6795
+ TSTypeParameterInstantiation(node) {
6796
+ const unions = node.params.flatMap((param) => param.type === "TSUnionType" && param.types.length >= minMembers ? [param] : []);
6797
+ if (unions.length === 0 || !astIsOneLine(node)) {
6798
+ return;
6799
+ }
6800
+ const source = context.sourceCode;
6801
+ const text = source.text;
6802
+ const items = node.params.flatMap((param) => param.type === "TSUnionType" && unions.includes(param) ? param.types : [param]);
6803
+ const gaps = sourceItemGaps(node.range[0] + 1, items, node.range[1] - 1);
6804
+ const firsts = new Set(unions.map((union) => union.types[0]));
6805
+ const matches = gaps.map((gap) => /^\s*(,?)\s*(\|?)\s*$/.exec(text.slice(...gap)));
6806
+ const newline = sourceNewline(text);
6807
+ const base = sourceLineIndent(text, node.range[0]);
6808
+ const inner = `${base}${indentUnit}`;
6809
+ const unsafe = text[node.range[0]] !== "<" || text[node.range[1] - 1] !== ">" || source.getCommentsInside(node).length > 0 || matches.some((match) => !match);
6810
+ context.report({
6811
+ node,
6812
+ messageId: "union",
6813
+ data: { min: String(minMembers) },
6814
+ fix: unsafe ? undefined : (fixer) => gaps.map((gap, index) => {
6815
+ const comma = matches[index]?.[1] ?? "";
6816
+ const next = items.at(index);
6817
+ const bar = matches[index]?.[2] || next && firsts.has(next) ? "| " : "";
6818
+ return fixer.replaceTextRange(gap, next ? `${comma}${newline}${inner}${bar}` : `${comma}${newline}${base}`);
6819
+ })
6820
+ });
6821
+ }
6822
+ };
6823
+ }
6824
+ });
6825
+
6826
+ // src/rules/naming/naming-declarations.ts
6827
+ import { defineRule as defineRule58 } from "@oxlint/plugins";
6479
6828
  var namingDeclarationsKinds = ["function", "const", "let", "var", "class", "type", "interface", "enum"];
6480
- var namingDeclarations = defineRule57({
6829
+ var namingDeclarations = defineRule58({
6481
6830
  meta: {
6482
6831
  type: "problem",
6483
6832
  schema: [
@@ -6554,7 +6903,7 @@ var namingDeclarations = defineRule57({
6554
6903
  });
6555
6904
 
6556
6905
  // src/rules/naming/naming-exports.ts
6557
- import { defineRule as defineRule58 } from "@oxlint/plugins";
6906
+ import { defineRule as defineRule59 } from "@oxlint/plugins";
6558
6907
  var namingExportsScopeProperties = {
6559
6908
  scope: { type: "string", enum: ["exports", "functions", "declarations"] },
6560
6909
  types: { type: "boolean" },
@@ -6580,7 +6929,7 @@ function matchTemplate(basename3, template, placeholder = "[a-z0-9-]+") {
6580
6929
  }
6581
6930
  return new RegExp(`^${pattern}$`).exec(basename3)?.groups ?? null;
6582
6931
  }
6583
- var namingExports = defineRule58({
6932
+ var namingExports = defineRule59({
6584
6933
  meta: {
6585
6934
  type: "problem",
6586
6935
  schema: {
@@ -6717,10 +7066,10 @@ var namingExports = defineRule58({
6717
7066
  });
6718
7067
 
6719
7068
  // src/rules/naming/naming-file.ts
6720
- import { defineRule as defineRule59 } from "@oxlint/plugins";
7069
+ import { defineRule as defineRule60 } from "@oxlint/plugins";
6721
7070
  import { readdirSync as readdirSync2 } from "node:fs";
6722
7071
  import { dirname as dirname4 } from "node:path";
6723
- var namingFile = defineRule59({
7072
+ var namingFile = defineRule60({
6724
7073
  meta: {
6725
7074
  type: "problem",
6726
7075
  schema: {
@@ -6790,9 +7139,9 @@ var namingFile = defineRule59({
6790
7139
  });
6791
7140
 
6792
7141
  // src/rules/naming/naming-folder.ts
6793
- import { defineRule as defineRule60 } from "@oxlint/plugins";
7142
+ import { defineRule as defineRule61 } from "@oxlint/plugins";
6794
7143
  import { posix as posix3 } from "node:path";
6795
- var namingFolder = defineRule60({
7144
+ var namingFolder = defineRule61({
6796
7145
  meta: {
6797
7146
  type: "problem",
6798
7147
  schema: [
@@ -6857,45 +7206,174 @@ var namingFolder = defineRule60({
6857
7206
  });
6858
7207
 
6859
7208
  // src/rules/naming/naming-types.ts
6860
- var namingTypes = astSelectorRule("TSInterfaceDeclaration:not([id.name=/^[A-Z][a-zA-Z0-9]*$/])", "Use PascalCase for interfaces.");
7209
+ import { defineRule as defineRule62 } from "@oxlint/plugins";
7210
+ var namingTypes = defineRule62({
7211
+ meta: {
7212
+ type: "problem",
7213
+ schema: [{ type: "object", additionalProperties: false, properties: { pattern: optionsPatternSchema, message: { type: "string" } } }],
7214
+ messages: { forbidden: "{{message}}" }
7215
+ },
7216
+ createOnce(context) {
7217
+ let patterns = [];
7218
+ let message = "";
7219
+ return {
7220
+ before() {
7221
+ const options = optionsFirst(context, {});
7222
+ patterns = optionsPatterns(options.pattern ?? "^[A-Z][a-zA-Z0-9]*$");
7223
+ message = options.message ?? "Use PascalCase for interfaces.";
7224
+ },
7225
+ TSInterfaceDeclaration(node) {
7226
+ if (!optionsPatternsTest(patterns, node.id.name)) {
7227
+ context.report({
7228
+ node,
7229
+ messageId: "forbidden",
7230
+ data: { message }
7231
+ });
7232
+ }
7233
+ }
7234
+ };
7235
+ }
7236
+ });
6861
7237
 
6862
7238
  // src/rules/react/react-dynamic-classes.ts
6863
- var reactDynamicClasses = astSelectorRule(':matches(JSXAttribute[name.name="className"] TemplateLiteral[expressions.length>0], JSXAttribute[name.name="className"] BinaryExpression[operator="+"])', "Write complete class names instead of constructing them dynamically.");
7239
+ import { defineRule as defineRule63 } from "@oxlint/plugins";
7240
+ var reactDynamicClasses = defineRule63({
7241
+ meta: {
7242
+ type: "problem",
7243
+ schema: [
7244
+ {
7245
+ type: "object",
7246
+ additionalProperties: false,
7247
+ properties: { attributes: { type: "array", items: { type: "string" } }, message: { type: "string" } }
7248
+ }
7249
+ ],
7250
+ messages: { forbidden: "{{message}}" }
7251
+ },
7252
+ createOnce(context) {
7253
+ let attributes = new Set;
7254
+ let message = "";
7255
+ const check = (node) => {
7256
+ for (let parent = node.parent;parent; parent = parent.parent) {
7257
+ if (parent.type === "JSXAttribute" && parent.name.type === "JSXIdentifier" && attributes.has(parent.name.name)) {
7258
+ context.report({
7259
+ node,
7260
+ messageId: "forbidden",
7261
+ data: { message }
7262
+ });
7263
+ return;
7264
+ }
7265
+ }
7266
+ };
7267
+ return {
7268
+ before() {
7269
+ const options = optionsFirst(context, {});
7270
+ attributes = new Set(options.attributes ?? ["className"]);
7271
+ message = options.message ?? "Write complete class names instead of constructing them dynamically.";
7272
+ },
7273
+ "JSXAttribute TemplateLiteral[expressions.length>0]": check,
7274
+ 'JSXAttribute BinaryExpression[operator="+"]': check
7275
+ };
7276
+ }
7277
+ });
6864
7278
 
6865
7279
  // src/rules/react/react-empty-effect.ts
6866
- var reactEmptyEffect = astSelectorRule(':matches(CallExpression[callee.name=/^use(Layout)?Effect$/], CallExpression[callee.object.name="React"][callee.property.name=/^use(Layout)?Effect$/]) > :matches(ArrowFunctionExpression, FunctionExpression) > BlockStatement[body.length=0]', "Remove the empty effect.");
7280
+ import { defineRule as defineRule64 } from "@oxlint/plugins";
7281
+ var reactEmptyEffect = defineRule64({
7282
+ meta: {
7283
+ type: "problem",
7284
+ schema: [
7285
+ {
7286
+ type: "object",
7287
+ additionalProperties: false,
7288
+ properties: { hooks: { type: "array", items: { type: "string" } }, message: { type: "string" } }
7289
+ }
7290
+ ],
7291
+ messages: { forbidden: "{{message}}" }
7292
+ },
7293
+ createOnce(context) {
7294
+ let hooks = new Set;
7295
+ let message = "";
7296
+ return {
7297
+ before() {
7298
+ const options = optionsFirst(context, {});
7299
+ hooks = new Set(options.hooks ?? ["useEffect", "useLayoutEffect", "React.useEffect", "React.useLayoutEffect"]);
7300
+ message = options.message ?? "Remove the empty effect.";
7301
+ },
7302
+ CallExpression(node) {
7303
+ if (!hooks.has(astDottedName(node.callee) ?? "")) {
7304
+ return;
7305
+ }
7306
+ for (const argument of node.arguments) {
7307
+ if (astIsInlineFunction(argument) && argument.body?.type === "BlockStatement" && argument.body.body.length === 0) {
7308
+ context.report({
7309
+ node: argument.body,
7310
+ messageId: "forbidden",
7311
+ data: { message }
7312
+ });
7313
+ }
7314
+ }
7315
+ }
7316
+ };
7317
+ }
7318
+ });
6867
7319
 
6868
7320
  // src/rules/react/react-memo.ts
6869
- import { defineRule as defineRule61 } from "@oxlint/plugins";
6870
- var reactMemoNames = ["memo", "useMemo", "useCallback"];
6871
- var reactMemo = defineRule61({
7321
+ import { defineRule as defineRule65 } from "@oxlint/plugins";
7322
+ var reactMemo = defineRule65({
6872
7323
  meta: {
6873
7324
  type: "problem",
6874
- schema: [],
7325
+ schema: [
7326
+ {
7327
+ type: "object",
7328
+ additionalProperties: false,
7329
+ properties: {
7330
+ names: { type: "array", items: { type: "string" } },
7331
+ sources: { type: "array", items: { type: "string" } },
7332
+ message: { type: "string" }
7333
+ }
7334
+ }
7335
+ ],
6875
7336
  messages: {
6876
- memo: "Use the React Compiler rather than manual memoization."
7337
+ memo: "{{message}}"
6877
7338
  }
6878
7339
  },
6879
7340
  createOnce(context) {
7341
+ let names = [];
7342
+ let sources = [];
7343
+ let message = "";
6880
7344
  return {
7345
+ before() {
7346
+ const options = optionsFirst(context, {});
7347
+ names = options.names ?? ["memo", "useMemo", "useCallback"];
7348
+ sources = options.sources ?? ["react"];
7349
+ message = options.message ?? "Use the React Compiler rather than manual memoization.";
7350
+ },
6881
7351
  ImportDeclaration(node) {
6882
- if (node.source.value !== "react") {
7352
+ if (!sources.includes(node.source.value)) {
6883
7353
  return;
6884
7354
  }
6885
7355
  for (const specifier of node.specifiers) {
6886
- if (specifier.type === "ImportSpecifier" && reactMemoNames.includes(astModuleName(specifier.imported))) {
6887
- context.report({ node: specifier, messageId: "memo" });
7356
+ if (specifier.type === "ImportSpecifier" && names.includes(astModuleName(specifier.imported))) {
7357
+ context.report({
7358
+ node: specifier,
7359
+ messageId: "memo",
7360
+ data: { message }
7361
+ });
6888
7362
  }
6889
7363
  }
6890
7364
  },
6891
7365
  MemberExpression(node) {
6892
- if (node.object.type !== "Identifier" || !reactMemoNames.includes(astMemberName(node) ?? "")) {
7366
+ if (node.object.type !== "Identifier" || !names.includes(boundariesMemberName(node) ?? "")) {
6893
7367
  return;
6894
7368
  }
6895
7369
  const variable = boundariesDeclaredVariable(context.sourceCode.getScope(node), node.object.name);
6896
- const isReact = variable ? variable.defs.some((def) => def.type === "ImportBinding" && def.node.type !== "ImportSpecifier" && def.parent?.type === "ImportDeclaration" && def.parent.source.value === "react") : node.object.name === "React";
7370
+ const isReact = variable ? variable.defs.some((def) => def.type === "ImportBinding" && (def.node.type !== "ImportSpecifier" || astModuleName(def.node.imported) === "default") && def.parent?.type === "ImportDeclaration" && sources.includes(def.parent.source.value)) : node.object.name === "React" && sources.includes("react");
6897
7371
  if (isReact) {
6898
- context.report({ node, messageId: "memo" });
7372
+ context.report({
7373
+ node,
7374
+ messageId: "memo",
7375
+ data: { message }
7376
+ });
6899
7377
  }
6900
7378
  }
6901
7379
  };
@@ -6903,7 +7381,7 @@ var reactMemo = defineRule61({
6903
7381
  });
6904
7382
 
6905
7383
  // src/rules/react/react-props.ts
6906
- import { defineRule as defineRule62 } from "@oxlint/plugins";
7384
+ import { defineRule as defineRule66 } from "@oxlint/plugins";
6907
7385
  function reactPropsTarget(node) {
6908
7386
  if (node?.type === "FunctionDeclaration" || node?.type === "FunctionExpression" || node?.type === "ArrowFunctionExpression") {
6909
7387
  return { fn: node };
@@ -6930,7 +7408,7 @@ function reactIsPassThrough(type) {
6930
7408
  const argument = type.typeArguments?.params.length === 1 ? type.typeArguments.params[0] : undefined;
6931
7409
  return name.type === "Identifier" && name.name === "ComponentProps" && (argument?.type === "TSTypeQuery" || argument?.type === "TSLiteralType" && argument.literal.type === "Literal" && typeof argument.literal.value === "string");
6932
7410
  }
6933
- var reactProps = defineRule62({
7411
+ var reactProps = defineRule66({
6934
7412
  meta: {
6935
7413
  type: "problem",
6936
7414
  schema: [
@@ -7029,8 +7507,8 @@ var reactProps = defineRule62({
7029
7507
  });
7030
7508
 
7031
7509
  // src/rules/react/react-query-props.ts
7032
- import { defineRule as defineRule63 } from "@oxlint/plugins";
7033
- var reactQueryProps = defineRule63({
7510
+ import { defineRule as defineRule67 } from "@oxlint/plugins";
7511
+ var reactQueryProps = defineRule67({
7034
7512
  meta: {
7035
7513
  type: "problem",
7036
7514
  schema: [
@@ -7111,7 +7589,7 @@ var reactQueryProps = defineRule63({
7111
7589
  });
7112
7590
 
7113
7591
  // src/rules/schemas/schemas-inline.ts
7114
- import { defineRule as defineRule64 } from "@oxlint/plugins";
7592
+ import { defineRule as defineRule68 } from "@oxlint/plugins";
7115
7593
 
7116
7594
  // src/utils/schemas.utils.ts
7117
7595
  var schemasElementOptionsSchema = {
@@ -7147,7 +7625,7 @@ function schemasIsAllowedElement(node, allowScalars, namespaces, structuralMetho
7147
7625
  }
7148
7626
 
7149
7627
  // src/rules/schemas/schemas-inline.ts
7150
- var schemasInline = defineRule64({
7628
+ var schemasInline = defineRule68({
7151
7629
  meta: {
7152
7630
  type: "problem",
7153
7631
  schema: [schemasElementOptionsSchema],
@@ -7196,11 +7674,11 @@ var schemasInline = defineRule64({
7196
7674
  });
7197
7675
 
7198
7676
  // src/rules/schemas/schemas-local.ts
7199
- import { defineRule as defineRule65 } from "@oxlint/plugins";
7677
+ import { defineRule as defineRule69 } from "@oxlint/plugins";
7200
7678
  var schemasLocalImportMessage = "Do not import runtime schema constructors from {{source}}; import a named schema instead.";
7201
7679
  var schemasLocalConstructionMessage = "Do not construct schemas locally; import a named schema instead.";
7202
7680
  var schemasLocalCalls = ["config", "prettifyError", "treeifyError", "flattenError", "formatError"];
7203
- var schemasLocal = defineRule65({
7681
+ var schemasLocal = defineRule69({
7204
7682
  meta: {
7205
7683
  type: "problem",
7206
7684
  schema: [
@@ -7349,8 +7827,8 @@ var schemasLocal = defineRule65({
7349
7827
  });
7350
7828
 
7351
7829
  // src/rules/schemas/schemas-rederive.ts
7352
- import { defineRule as defineRule66 } from "@oxlint/plugins";
7353
- var schemasRederive = defineRule66({
7830
+ import { defineRule as defineRule70 } from "@oxlint/plugins";
7831
+ var schemasRederive = defineRule70({
7354
7832
  meta: {
7355
7833
  type: "problem",
7356
7834
  schema: [
@@ -7396,7 +7874,7 @@ var schemasRederive = defineRule66({
7396
7874
  });
7397
7875
 
7398
7876
  // src/rules/schemas/schemas-single-use.ts
7399
- import { defineRule as defineRule67 } from "@oxlint/plugins";
7877
+ import { defineRule as defineRule71 } from "@oxlint/plugins";
7400
7878
  function isScalarSchema(node, namespaces, structuralMethods) {
7401
7879
  if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") {
7402
7880
  return false;
@@ -7410,7 +7888,7 @@ function isScalarSchema(node, namespaces, structuralMethods) {
7410
7888
  }
7411
7889
  return node.callee.object.type === "CallExpression" && isScalarSchema(node.callee.object, namespaces, structuralMethods);
7412
7890
  }
7413
- var schemasSingleUse = defineRule67({
7891
+ var schemasSingleUse = defineRule71({
7414
7892
  meta: {
7415
7893
  type: "suggestion",
7416
7894
  schema: [schemasElementOptionsSchema],
@@ -7486,7 +7964,7 @@ var schemasSingleUse = defineRule67({
7486
7964
  });
7487
7965
 
7488
7966
  // src/rules/tests/tests-assertions.ts
7489
- import { defineRule as defineRule68 } from "@oxlint/plugins";
7967
+ import { defineRule as defineRule72 } from "@oxlint/plugins";
7490
7968
  var testsAssertionsModifiers = new Set(["for", "each", "concurrent", "sequential", "only", "skip"]);
7491
7969
  var testsAssertionsFunctions = [
7492
7970
  "expect",
@@ -7676,7 +8154,7 @@ function isAssertedTest(registration, sourceCode, state) {
7676
8154
  }
7677
8155
  return rows.length > 0 && rows.every((row) => hasAssertion(callback, row.bindings, new Set, state));
7678
8156
  }
7679
- var testsAssertions = defineRule68({
8157
+ var testsAssertions = defineRule72({
7680
8158
  meta: {
7681
8159
  type: "problem",
7682
8160
  schema: [
@@ -7802,8 +8280,8 @@ var testsAssertions = defineRule68({
7802
8280
  });
7803
8281
 
7804
8282
  // src/rules/tests/tests-concurrent-db.ts
7805
- import { defineRule as defineRule69 } from "@oxlint/plugins";
7806
- var testsConcurrentDb = defineRule69({
8283
+ import { defineRule as defineRule73 } from "@oxlint/plugins";
8284
+ var testsConcurrentDb = defineRule73({
7807
8285
  meta: {
7808
8286
  type: "problem",
7809
8287
  schema: [
@@ -7859,7 +8337,7 @@ var testsConcurrentDb = defineRule69({
7859
8337
  });
7860
8338
 
7861
8339
  // src/rules/tests/tests-modifiers.ts
7862
- import { defineRule as defineRule70 } from "@oxlint/plugins";
8340
+ import { defineRule as defineRule74 } from "@oxlint/plugins";
7863
8341
  var testsModifiersNames = ["test", "it", "describe", "suite"];
7864
8342
  var testsModifiersPackages = new Set(["vitest", "bun:test", "@playwright/test", "@jest/globals"]);
7865
8343
  var testsModifiersBanned = ["only", "skip", "todo"];
@@ -7878,7 +8356,7 @@ function modifierName(node) {
7878
8356
  const property = astMemberName(node);
7879
8357
  return object && property ? `${object}.${property}` : undefined;
7880
8358
  }
7881
- var testsModifiers = defineRule70({
8359
+ var testsModifiers = defineRule74({
7882
8360
  meta: {
7883
8361
  type: "problem",
7884
8362
  schema: [
@@ -7932,9 +8410,9 @@ var testsModifiers = defineRule70({
7932
8410
  });
7933
8411
 
7934
8412
  // src/rules/tests/tests-titles.ts
7935
- import { defineRule as defineRule71 } from "@oxlint/plugins";
8413
+ import { defineRule as defineRule75 } from "@oxlint/plugins";
7936
8414
  var testsTitlesModifiers = ["only", "skip", "concurrent", "sequential", "todo", "each", "for"];
7937
- var testsTitles = defineRule71({
8415
+ var testsTitles = defineRule75({
7938
8416
  meta: {
7939
8417
  type: "suggestion",
7940
8418
  schema: [
@@ -8012,11 +8490,11 @@ var testsTitles = defineRule71({
8012
8490
  });
8013
8491
 
8014
8492
  // src/rules/types/types-declarations.ts
8015
- import { defineRule as defineRule72 } from "@oxlint/plugins";
8493
+ import { defineRule as defineRule76 } from "@oxlint/plugins";
8016
8494
  function isValuePattern(source) {
8017
8495
  return /^\^?=/.test(source);
8018
8496
  }
8019
- var typesDeclarations = defineRule72({
8497
+ var typesDeclarations = defineRule76({
8020
8498
  meta: {
8021
8499
  type: "problem",
8022
8500
  schema: [
@@ -8067,8 +8545,8 @@ var typesDeclarations = defineRule72({
8067
8545
  });
8068
8546
 
8069
8547
  // src/rules/types/types-duplicate.ts
8070
- import { defineRule as defineRule73 } from "@oxlint/plugins";
8071
- var typesDuplicate = defineRule73({
8548
+ import { defineRule as defineRule77 } from "@oxlint/plugins";
8549
+ var typesDuplicate = defineRule77({
8072
8550
  meta: {
8073
8551
  type: "suggestion",
8074
8552
  schema: [
@@ -8132,7 +8610,7 @@ var typesDuplicate = defineRule73({
8132
8610
  });
8133
8611
 
8134
8612
  // src/rules/types/types-imported-alias.ts
8135
- import { defineRule as defineRule74 } from "@oxlint/plugins";
8613
+ import { defineRule as defineRule78 } from "@oxlint/plugins";
8136
8614
  function importedAliasTarget(declaration, imports) {
8137
8615
  if (declaration?.type !== "TSTypeAliasDeclaration") {
8138
8616
  return;
@@ -8144,7 +8622,7 @@ function importedAliasTarget(declaration, imports) {
8144
8622
  const { name } = type.typeName;
8145
8623
  return imports.has(name) && !declaration.typeParameters?.params.some((parameter) => parameter.name.name === name) ? name : undefined;
8146
8624
  }
8147
- var typesImportedAlias = defineRule74({
8625
+ var typesImportedAlias = defineRule78({
8148
8626
  meta: {
8149
8627
  type: "problem",
8150
8628
  schema: [],
@@ -8206,7 +8684,7 @@ var typesImportedAlias = defineRule74({
8206
8684
  });
8207
8685
 
8208
8686
  // src/rules/types/types-inline.ts
8209
- import { defineRule as defineRule75 } from "@oxlint/plugins";
8687
+ import { defineRule as defineRule79 } from "@oxlint/plugins";
8210
8688
  function parameterAnnotation(parameter) {
8211
8689
  if (parameter.type === "TSParameterProperty") {
8212
8690
  return parameterAnnotation(parameter.parameter);
@@ -8216,7 +8694,7 @@ function parameterAnnotation(parameter) {
8216
8694
  }
8217
8695
  return parameter.typeAnnotation;
8218
8696
  }
8219
- var typesInline = defineRule75({
8697
+ var typesInline = defineRule79({
8220
8698
  meta: {
8221
8699
  type: "problem",
8222
8700
  schema: [
@@ -8328,11 +8806,38 @@ var typesInline = defineRule75({
8328
8806
  });
8329
8807
 
8330
8808
  // src/rules/types/types-local-alias.ts
8331
- var typesLocalAlias = astSelectorRule("TSTypeAliasDeclaration", "Local component/hook contracts use interfaces named for their owner. Derived or reusable aliases belong in domain types; do not substitute an empty interface.");
8809
+ import { defineRule as defineRule80 } from "@oxlint/plugins";
8810
+ var typesLocalAlias = defineRule80({
8811
+ meta: {
8812
+ type: "problem",
8813
+ schema: [{ type: "object", additionalProperties: false, properties: { allow: optionsPatternSchema, message: { type: "string" } } }],
8814
+ messages: { forbidden: "{{message}}" }
8815
+ },
8816
+ createOnce(context) {
8817
+ let allow = [];
8818
+ let message = "";
8819
+ return {
8820
+ before() {
8821
+ const options = optionsFirst(context, {});
8822
+ allow = optionsPatterns(options.allow);
8823
+ message = options.message ?? "Local component/hook contracts use interfaces named for their owner. Derived or reusable aliases belong in domain types; do not substitute an empty interface.";
8824
+ },
8825
+ TSTypeAliasDeclaration(node) {
8826
+ if (!optionsPatternsTest(allow, node.id.name)) {
8827
+ context.report({
8828
+ node,
8829
+ messageId: "forbidden",
8830
+ data: { message }
8831
+ });
8832
+ }
8833
+ }
8834
+ };
8835
+ }
8836
+ });
8332
8837
 
8333
8838
  // src/rules/types/types-namespace-import.ts
8334
- import { defineRule as defineRule76 } from "@oxlint/plugins";
8335
- var typesNamespaceImport = defineRule76({
8839
+ import { defineRule as defineRule81 } from "@oxlint/plugins";
8840
+ var typesNamespaceImport = defineRule81({
8336
8841
  meta: {
8337
8842
  type: "suggestion",
8338
8843
  schema: [
@@ -8374,8 +8879,8 @@ var typesNamespaceImport = defineRule76({
8374
8879
  });
8375
8880
 
8376
8881
  // src/rules/types/types-service.ts
8377
- import { defineRule as defineRule77 } from "@oxlint/plugins";
8378
- var typesService = defineRule77({
8882
+ import { defineRule as defineRule82 } from "@oxlint/plugins";
8883
+ var typesService = defineRule82({
8379
8884
  meta: {
8380
8885
  type: "problem",
8381
8886
  schema: [
@@ -8425,12 +8930,12 @@ var typesService = defineRule77({
8425
8930
  });
8426
8931
 
8427
8932
  // src/rules/types/types-unknown.ts
8428
- import { defineRule as defineRule78 } from "@oxlint/plugins";
8933
+ import { defineRule as defineRule83 } from "@oxlint/plugins";
8429
8934
  function isCatchName(node) {
8430
8935
  const key = "key" in node && !(("computed" in node) && node.computed) ? node.key : undefined;
8431
8936
  return key?.type === "Identifier" && key.name === "catch";
8432
8937
  }
8433
- var typesUnknown = defineRule78({
8938
+ var typesUnknown = defineRule83({
8434
8939
  meta: { type: "problem", schema: [], messages: { forbidden: "Use an owned domain type rather than unknown." } },
8435
8940
  createOnce(context) {
8436
8941
  return {
@@ -8514,6 +9019,7 @@ var src_default = eslintCompatPlugin({
8514
9019
  "layout-padding": layoutPadding,
8515
9020
  "layout-pipe": layoutPipe,
8516
9021
  "layout-ternary": layoutTernary,
9022
+ "layout-type-arguments": layoutTypeArguments,
8517
9023
  "naming-declarations": namingDeclarations,
8518
9024
  "naming-exports": namingExports,
8519
9025
  "naming-file": namingFile,