oxlint-plugin-arch 0.12.6 → 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
@@ -37,7 +37,7 @@ function optionsPatternsTest(patterns, value) {
37
37
  function optionsGlob(pattern) {
38
38
  const negated = [];
39
39
  const source = pattern.replaceAll(/!\(([^()]*)\)/g, (_, options) => {
40
- negated.push(options.split("|").map((option) => option.replaceAll(/[.+^$()|[\]\\*?{}]/g, "\\$&")).join("|"));
40
+ negated.push(options.split("|").map((option) => option.replaceAll(/[.+^$()|[\]\\?{}]/g, "\\$&").replaceAll("*", "[^/]*")).join("|"));
41
41
  return `\x02${negated.length - 1}\x03`;
42
42
  }).replaceAll(/[.+^$()|[\]\\]/g, "\\$&").replaceAll("?", "[^/]").replaceAll("**/", "\x00").replaceAll("**", "\x01").replaceAll("*", "[^/]*").replaceAll("\x00", "(?:.*/)?").replaceAll("\x01", ".*").replaceAll(/\{([^}]*)\}/g, (_, options) => `(?:${options.split(",").join("|")})`);
43
43
  return new RegExp(`^${negated.reduce((text, names, index) => text.replace(`\x02${index}\x03`, `(?!(?:${names})(?:/|$))[^/]*`), source)}$`);
@@ -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
@@ -283,16 +286,14 @@ var backendOrpcOutput = defineRule2({
283
286
  type: "object",
284
287
  additionalProperties: false,
285
288
  properties: {
286
- composers: { type: "array", items: { type: "string" } },
287
289
  handler: { type: "string" },
288
290
  output: { type: "string" }
289
- },
290
- required: ["composers"]
291
+ }
291
292
  }
292
293
  ],
293
294
  messages: {
294
295
  missing: "oRPC procedures must declare .output() before .handler().",
295
- invalid: "oRPC .output() must use a named schema or configured composer with a named schema."
296
+ invalid: "oRPC .output() must use a named schema or a plain function called with a named schema."
296
297
  }
297
298
  },
298
299
  createOnce(context) {
@@ -301,7 +302,7 @@ var backendOrpcOutput = defineRule2({
301
302
  if (node.callee.type !== "MemberExpression") {
302
303
  return;
303
304
  }
304
- const { composers, handler: handlerMethod = "handler", output: outputMethod = "output" } = optionsFirst(context);
305
+ const { handler: handlerMethod = "handler", output: outputMethod = "output" } = optionsFirst(context, {});
305
306
  if (astMemberName(node.callee) !== handlerMethod) {
306
307
  return;
307
308
  }
@@ -311,7 +312,7 @@ var backendOrpcOutput = defineRule2({
311
312
  return;
312
313
  }
313
314
  const schema = output.arguments.at(0);
314
- const named = schema?.type === "Identifier" || schema?.type === "CallExpression" && schema.callee.type === "Identifier" && composers.includes(schema.callee.name) && schema.arguments[0]?.type === "Identifier";
315
+ const named = schema?.type === "Identifier" || schema?.type === "CallExpression" && schema.callee.type === "Identifier" && schema.arguments[0]?.type === "Identifier";
315
316
  if (!named) {
316
317
  context.report({ node: output, messageId: "invalid" });
317
318
  }
@@ -844,6 +845,27 @@ import { defineRule as defineRule5 } from "@oxlint/plugins";
844
845
  // src/utils/boundaries.utils.ts
845
846
  import { builtinModules } from "node:module";
846
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
+ }
847
869
  var boundariesServerGlobals = ["process", "Bun", "Deno", "Buffer", "__dirname", "__filename", "require"];
848
870
  var boundariesBrowserGlobals = [
849
871
  "window",
@@ -879,7 +901,21 @@ var boundariesRuntimeProperties = {
879
901
  dynamic: { type: "array", items: { type: "string" } },
880
902
  serverPackages: { type: "array", items: { type: "string" } },
881
903
  clientPackages: { type: "array", items: { type: "string" } },
882
- 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
+ }
883
919
  };
884
920
  var boundariesServerPackages = ["@orpc/server", "react-dom/server"];
885
921
  var boundariesRuntimeMessages = {
@@ -910,16 +946,21 @@ function boundariesReport(context, node, source, reason) {
910
946
  data: { source, reason }
911
947
  });
912
948
  }
913
- function boundariesImportExpression(context, node, allowComputed, check) {
914
- const source = boundariesLiteralSource(node);
915
- if (source !== undefined) {
916
- check(source);
917
- } else if (!allowComputed) {
918
- context.report({ node, messageId: "computed" });
919
- }
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
+ };
920
961
  }
921
- function boundariesIsSharedFile(options, file) {
922
- 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}/`));
923
964
  }
924
965
  function boundariesDeclaredVariable(start, name) {
925
966
  for (let scope = start;scope; scope = scope.upper) {
@@ -948,7 +989,7 @@ function boundariesMemberName(node) {
948
989
  }
949
990
  return node.computed && node.property.type === "Literal" && typeof node.property.value === "string" ? node.property.value : null;
950
991
  }
951
- function boundariesResolveTarget(options, file, source) {
992
+ function boundariesResolveTarget(options, file, source, services = "services") {
952
993
  const owner = [...options.web, ...options.backend].sort((a, b) => b.length - a.length).find((root) => file.startsWith(`${root}/`));
953
994
  const web = owner && options.web.includes(owner) ? owner : undefined;
954
995
  const alias = Object.entries(options.aliases).sort(([a], [b]) => b.length - a.length).find(([prefix]) => source === prefix || source.startsWith(`${prefix}/`));
@@ -959,18 +1000,24 @@ function boundariesResolveTarget(options, file, source) {
959
1000
  return resolve(dirname(file), source).replaceAll("\\", "/");
960
1001
  }
961
1002
  if (source.startsWith("@/") && web) {
962
- return `${web}/${source.slice(2)}`;
1003
+ return resolve(web, source.slice(2)).replaceAll("\\", "/");
963
1004
  }
964
1005
  if (source.startsWith("#/") && owner) {
965
- return `${owner}/${source.slice(2)}`;
1006
+ return resolve(owner, source.slice(2)).replaceAll("\\", "/");
966
1007
  }
967
1008
  if (source.startsWith("#services/") && owner) {
968
- return `${owner}/services/${source.slice(10)}`;
1009
+ return resolve(owner, services, source.slice("#services/".length)).replaceAll("\\", "/");
969
1010
  }
970
1011
  return source;
971
1012
  }
972
1013
 
973
1014
  // src/rules/backend/backend-sql-like.ts
1015
+ var backendSqlLikeDefaults = {
1016
+ methods: ["like", "ilike"],
1017
+ sanitizers: ["escapeLikeWildcards"],
1018
+ bindings: true,
1019
+ operators: ["where"]
1020
+ };
974
1021
  var backendSqlLike = defineRule5({
975
1022
  meta: {
976
1023
  type: "problem",
@@ -983,17 +1030,16 @@ var backendSqlLike = defineRule5({
983
1030
  sanitizers: { type: "array", items: { type: "string" } },
984
1031
  bindings: { type: "boolean" },
985
1032
  operators: { type: "array", items: { type: "string" } }
986
- },
987
- required: ["methods", "sanitizers"]
1033
+ }
988
1034
  }
989
1035
  ],
990
1036
  messages: { unescaped: ".{{method}}() values must use a configured sanitizer." }
991
1037
  },
992
1038
  createOnce(context) {
993
- let options = { methods: [], sanitizers: [] };
1039
+ let options = backendSqlLikeDefaults;
994
1040
  const seen = new Set;
995
1041
  function sanitizedBinding(identifier, name) {
996
- const variable = options.bindings === false ? null : boundariesDeclaredVariable(context.sourceCode.getScope(identifier), name);
1042
+ const variable = options.bindings ? boundariesDeclaredVariable(context.sourceCode.getScope(identifier), name) : null;
997
1043
  const declarator = variable?.defs.length === 1 && variable.defs[0].type === "Variable" ? variable.defs[0].node : undefined;
998
1044
  const statement = declarator?.parent?.parent;
999
1045
  if (!variable || declarator?.type !== "VariableDeclarator" || seen.has(variable) || !(statement?.type === "Program" || statement?.type === "ExportNamedDeclaration" && statement.parent.type === "Program") || variable.references.some((reference) => reference.isWrite() && !reference.init)) {
@@ -1034,8 +1080,8 @@ var backendSqlLike = defineRule5({
1034
1080
  if (node.callee.type !== "MemberExpression") {
1035
1081
  return;
1036
1082
  }
1037
- options = optionsFirst(context);
1038
- const { methods, operators: operatorMethods = [] } = options;
1083
+ options = { ...backendSqlLikeDefaults, ...optionsFirst(context, {}) };
1084
+ const { methods, operators: operatorMethods } = options;
1039
1085
  const method = astMemberName(node.callee);
1040
1086
  if (!method) {
1041
1087
  return;
@@ -1142,7 +1188,25 @@ var boundariesDeny = defineRule6({
1142
1188
  import { defineRule as defineRule7 } from "@oxlint/plugins";
1143
1189
  import { basename, resolve as resolve2 } from "node:path";
1144
1190
  var boundariesSurfaces = ["service", "client", "server", "rsc", "utils"];
1145
- 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
+ }
1146
1210
  function boundariesContains(directory, candidate) {
1147
1211
  return candidate === directory || candidate.startsWith(`${directory}/`);
1148
1212
  }
@@ -1152,44 +1216,56 @@ function boundariesIsSurface(options, stem) {
1152
1216
  function boundariesStripExtension(path) {
1153
1217
  return path.replace(/\.[cm]?[jt]sx?$/, "");
1154
1218
  }
1219
+ function boundariesConventionsOf(options) {
1220
+ return { ...boundariesConventions, ...options.conventions };
1221
+ }
1155
1222
  function boundariesServiceFolder(options, root) {
1156
1223
  const nested = [...options.web, ...options.backend].filter((other) => other.length > root.length && boundariesContains(root, other));
1157
- 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}`;
1158
1225
  }
1159
- function boundariesDomain(path, appRoot, services) {
1226
+ function boundariesDomain(conventions, path, appRoot, services) {
1160
1227
  const relative = path.slice(appRoot.length + 1);
1161
- if (relative.startsWith("types/") && relative.endsWith(".types.ts")) {
1162
- 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];
1163
1230
  }
1164
1231
  if (!boundariesContains(services, path) || path === services) {
1165
1232
  return;
1166
1233
  }
1167
1234
  const parts = path.slice(services.length + 1).split("/");
1168
- 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)}$`), "");
1169
1236
  }
1170
1237
  function boundariesPrivateDomain({ options, file, target, reExport, roots, owner }) {
1171
1238
  const roles = options.roles;
1239
+ const conventions = boundariesConventionsOf(options);
1172
1240
  if (!roles?.length) {
1173
1241
  return;
1174
1242
  }
1175
1243
  const targetOwner = roots.find((path) => boundariesContains(path, target));
1176
- const fileDomain = owner ? boundariesDomain(file, owner, boundariesServiceFolder(options, owner)) : undefined;
1177
- 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;
1178
1246
  const sameDomain = owner && owner === targetOwner && fileDomain && fileDomain === targetDomain;
1179
1247
  const targetStem = boundariesStripExtension(target);
1180
- const publicSurface = boundariesIsSurface(options, targetStem) && !boundariesActionOrQuery.test(targetStem);
1181
- 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);
1182
1250
  if (!privateTarget) {
1183
1251
  return;
1184
1252
  }
1185
- 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)) {
1186
1254
  return "public service and utility surfaces must not re-export private domain internals";
1187
1255
  }
1188
1256
  return sameDomain ? undefined : "private domain files stay in the same domain and application; use its service API or shared utilities";
1189
1257
  }
1258
+ function boundariesPortableFolder(options) {
1259
+ if (!options.portable) {
1260
+ return;
1261
+ }
1262
+ return typeof options.portable === "string" ? options.portable : boundariesConventionsOf(options).lib;
1263
+ }
1190
1264
  function boundariesPortableLib(check) {
1191
1265
  const { options, file, source, target, typeOnly, reExport, roots, web, backendImport } = check;
1192
- 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}`) {
1193
1269
  return;
1194
1270
  }
1195
1271
  const portableTarget = target.startsWith("/") ? resolve2(target).replaceAll("\\", "/") : target;
@@ -1199,11 +1275,11 @@ function boundariesPortableLib(check) {
1199
1275
  if (backendImport || backendTarget || appService || appPackage) {
1200
1276
  return "portable frontend lib does not import application services or application APIs";
1201
1277
  }
1202
- const appType = roots.some((path) => boundariesContains(`${path}/types`, portableTarget));
1278
+ const appType = roots.some((path) => boundariesContains(`${path}/${types}`, portableTarget));
1203
1279
  const stem = boundariesStripExtension(basename(file)).replace(/\.utils$/, "");
1204
- const ownType = typeOnly && !reExport && boundariesStripExtension(portableTarget) === `${web}/types/${stem}.types`;
1280
+ const ownType = typeOnly && !reExport && boundariesStripExtension(portableTarget) === `${web}/${types}/${stem}.${types}`;
1205
1281
  if (appType && !ownType) {
1206
- 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`;
1207
1283
  }
1208
1284
  return;
1209
1285
  }
@@ -1222,7 +1298,24 @@ var boundariesImports = defineRule7({
1222
1298
  roles: { type: "array", items: { type: "string" } },
1223
1299
  surfaces: { type: "array", items: { type: "string", minLength: 1 }, default: boundariesSurfaces },
1224
1300
  services: { type: "array", items: { type: "string" } },
1225
- 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
+ },
1226
1319
  backendPackages: { type: "array", items: { type: "string" } },
1227
1320
  entrypoints: { type: "array", items: { type: "string" } },
1228
1321
  appPackages: { type: "array", items: { type: "string" } },
@@ -1241,7 +1334,8 @@ var boundariesImports = defineRule7({
1241
1334
  const file = context.filename.replaceAll("\\", "/");
1242
1335
  const roots = [...options.web, ...options.backend].sort((a, b) => b.length - a.length);
1243
1336
  const owner = roots.find((path) => boundariesContains(path, file));
1244
- const target = boundariesResolveTarget(options, file, source);
1337
+ const conventions = boundariesConventionsOf(options);
1338
+ const target = boundariesResolveTarget(options, file, source, conventions.services);
1245
1339
  const web = owner && options.web.includes(owner) ? owner : undefined;
1246
1340
  const backendImport = options.backend.some((path) => boundariesContains(path, target)) || boundariesMatchesImport(options.backendPackages, source);
1247
1341
  const imported = { options, file, source, target, typeOnly, reExport, roots, owner, web, backendImport };
@@ -1250,13 +1344,16 @@ var boundariesImports = defineRule7({
1250
1344
  const serviceFolders = roots.map((path) => boundariesServiceFolder(options, path));
1251
1345
  const serviceTarget = serviceFolders.find((folder) => boundariesContains(folder, target));
1252
1346
  const serviceFile = serviceFolders.find((folder) => boundariesContains(folder, file));
1253
- 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)?$`;
1254
1351
  const reason = [
1255
1352
  web && backendImport && !options.entrypoints.includes(source) ? "frontend code uses only public backend entry points" : undefined,
1256
1353
  inPackage && appTarget ? "shared packages do not import app internals" : undefined,
1257
- (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,
1258
1355
  domainContract ? "domain types/constants use type-only imports from implementations" : undefined,
1259
- 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,
1260
1357
  boundariesPrivateDomain(imported),
1261
1358
  boundariesPortableLib(imported)
1262
1359
  ].findLast((candidate) => candidate !== undefined);
@@ -1297,7 +1394,8 @@ var boundariesImports = defineRule7({
1297
1394
  TSImportType(node) {
1298
1395
  const options = { ...boundariesImportsDefaults, ...optionsFirst(context) };
1299
1396
  const file = context.filename.replaceAll("\\", "/");
1300
- 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}`)) {
1301
1399
  check(node, node.source.value, true);
1302
1400
  }
1303
1401
  }
@@ -1341,6 +1439,7 @@ var boundariesNext = defineRule8({
1341
1439
  },
1342
1440
  createOnce(context) {
1343
1441
  let options;
1442
+ let conventions = boundariesConventionsFor({});
1344
1443
  let file = "";
1345
1444
  let server = false;
1346
1445
  let client = false;
@@ -1352,12 +1451,12 @@ var boundariesNext = defineRule8({
1352
1451
  return;
1353
1452
  }
1354
1453
  const target = boundariesResolveTarget(options, file, source);
1355
- 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);
1356
1455
  if (clientOnly && (server || shared)) {
1357
1456
  boundariesReport(context, node, source, "browser-only dependencies do not belong in server, RSC or shared modules");
1358
1457
  return;
1359
1458
  }
1360
- 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);
1361
1460
  if (!serverOnly || server) {
1362
1461
  return;
1363
1462
  }
@@ -1366,6 +1465,7 @@ var boundariesNext = defineRule8({
1366
1465
  return {
1367
1466
  Program(program) {
1368
1467
  options = { ...boundariesNextDefaults, ...optionsFirst(context) };
1468
+ conventions = boundariesConventionsFor(options);
1369
1469
  reactNamespaces.clear();
1370
1470
  file = context.filename.replaceAll("\\", "/");
1371
1471
  const directives = new Map;
@@ -1376,9 +1476,9 @@ var boundariesNext = defineRule8({
1376
1476
  }
1377
1477
  const useServer = directives.get("use server");
1378
1478
  const useClient = directives.get("use client");
1379
- const serverName = /\.(?:server|rsc|action)\.[cm]?[jt]sx?$/.test(file);
1380
- const clientName = /\.client\.[cm]?[jt]sx?$/.test(file);
1381
- 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);
1382
1482
  server = !useClient && (serverName || Boolean(useServer));
1383
1483
  client = clientName || Boolean(useClient);
1384
1484
  if (useClient && serverName) {
@@ -1395,7 +1495,7 @@ var boundariesNext = defineRule8({
1395
1495
  data: { directive: "use server", side: "client" }
1396
1496
  });
1397
1497
  }
1398
- if (/\.action\.[cm]?[jt]sx?$/.test(file) && !useServer) {
1498
+ if (boundariesHasSuffix(conventions.actions, file) && !useServer) {
1399
1499
  context.report({ node: program, messageId: "action" });
1400
1500
  }
1401
1501
  },
@@ -1425,10 +1525,7 @@ var boundariesNext = defineRule8({
1425
1525
  boundariesReport(context, node, `${node.object.name}.${name}`, boundariesClientHookReason);
1426
1526
  }
1427
1527
  },
1428
- ...boundariesExportVisitors(check),
1429
- ImportExpression(node) {
1430
- boundariesImportExpression(context, node, Boolean(options.dynamic?.includes(file)), (source) => check(node, source, false));
1431
- },
1528
+ ...boundariesRuntimeImportVisitors(context, check, () => Boolean(options.dynamic?.includes(file))),
1432
1529
  "Program:exit"() {
1433
1530
  const scope = context.sourceCode.scopeManager.scopes.at(0);
1434
1531
  if (!scope) {
@@ -1545,11 +1642,9 @@ var boundariesTanstackDefaults = {
1545
1642
  hookPackages: ["react", "@tanstack/react-query", "@tanstack/react-router"],
1546
1643
  startPackages: ["@tanstack/react-start"],
1547
1644
  rscPackages: ["@tanstack/react-start/rsc", "@tanstack/react-start-rsc"],
1548
- renderers: ["renderServerComponent", "renderToReadableStream"]
1645
+ renderers: ["renderServerComponent", "renderToReadableStream"],
1646
+ serverHooks: ["useId"]
1549
1647
  };
1550
- function boundariesIsClientHook(name) {
1551
- return /^use[A-Z]/.test(name) && name !== "useId";
1552
- }
1553
1648
  var boundariesTanstack = defineRule10({
1554
1649
  meta: {
1555
1650
  type: "problem",
@@ -1562,7 +1657,8 @@ var boundariesTanstack = defineRule10({
1562
1657
  rpc: { type: "array", items: { type: "string" } },
1563
1658
  startPackages: { type: "array", items: { type: "string" } },
1564
1659
  rscPackages: { type: "array", items: { type: "string" } },
1565
- 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 }
1566
1662
  }
1567
1663
  }
1568
1664
  ],
@@ -1570,6 +1666,7 @@ var boundariesTanstack = defineRule10({
1570
1666
  },
1571
1667
  createOnce(context) {
1572
1668
  let options;
1669
+ let conventions = boundariesConventionsFor({});
1573
1670
  let file = "";
1574
1671
  let serverFile = false;
1575
1672
  let rscFile = false;
@@ -1578,6 +1675,7 @@ var boundariesTanstack = defineRule10({
1578
1675
  let referenced;
1579
1676
  let imports = new Map;
1580
1677
  const boundaries = [];
1678
+ const clientHook = (name) => /^use[A-Z]/.test(name) && !options.serverHooks.includes(name);
1581
1679
  function imported(node) {
1582
1680
  const entry = imports.get(node.name);
1583
1681
  const variable = entry ? boundariesDeclaredVariable(context.sourceCode.getScope(node), node.name) : null;
@@ -1620,7 +1718,7 @@ var boundariesTanstack = defineRule10({
1620
1718
  return;
1621
1719
  }
1622
1720
  const target = boundariesResolveTarget(options, file, source);
1623
- 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);
1624
1722
  if (clientImport && (serverFile || rscFile || shared) && !clientSide(node.start)) {
1625
1723
  const references = node.type === "ImportDeclaration" ? runtimeReferences(node) : [];
1626
1724
  if (shared || references.length === 0 || !references.every(clientSide)) {
@@ -1628,7 +1726,7 @@ var boundariesTanstack = defineRule10({
1628
1726
  }
1629
1727
  return;
1630
1728
  }
1631
- 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);
1632
1730
  if (!renderer && !serverImport) {
1633
1731
  return;
1634
1732
  }
@@ -1656,10 +1754,11 @@ var boundariesTanstack = defineRule10({
1656
1754
  return {
1657
1755
  Program(program) {
1658
1756
  options = { ...boundariesTanstackDefaults, ...optionsFirst(context) };
1757
+ conventions = boundariesConventionsFor(options);
1659
1758
  file = context.filename.replaceAll("\\", "/");
1660
- serverFile = /\.server\.[cm]?[jt]sx?$/.test(file);
1661
- rscFile = /\.rsc\.[cm]?[jt]sx?$/.test(file);
1662
- shared = boundariesIsSharedFile(options, file);
1759
+ serverFile = boundariesHasSuffix(conventions.server, file);
1760
+ rscFile = boundariesHasSuffix(conventions.rsc, file);
1761
+ shared = boundariesIsSharedFile(options, conventions, file);
1663
1762
  runtimeIdentifiers.clear();
1664
1763
  referenced = undefined;
1665
1764
  boundaries.length = 0;
@@ -1726,7 +1825,7 @@ var boundariesTanstack = defineRule10({
1726
1825
  return;
1727
1826
  }
1728
1827
  for (const specifier of node.specifiers) {
1729
- 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)) {
1730
1829
  continue;
1731
1830
  }
1732
1831
  const references = runtimeReferences(node, specifier.local.name);
@@ -1735,10 +1834,7 @@ var boundariesTanstack = defineRule10({
1735
1834
  }
1736
1835
  }
1737
1836
  },
1738
- ...boundariesExportVisitors(check),
1739
- ImportExpression(node) {
1740
- boundariesImportExpression(context, node, Boolean(options.dynamic?.includes(file)), (source) => check(node, source, false));
1741
- },
1837
+ ...boundariesRuntimeImportVisitors(context, check, () => Boolean(options.dynamic?.includes(file))),
1742
1838
  MemberExpression(node) {
1743
1839
  const name = boundariesMemberName(node);
1744
1840
  if (!name || node.object.type !== "Identifier") {
@@ -1748,7 +1844,7 @@ var boundariesTanstack = defineRule10({
1748
1844
  if (kind === "rsc-renderers" && options.renderers.includes(name) && (shared || !serverPosition(node.start, true))) {
1749
1845
  boundariesReport(context, node, name, "server renderers require a server execution boundary");
1750
1846
  }
1751
- if ((serverFile || rscFile) && kind === "client-hooks" && boundariesIsClientHook(name) && !clientSide(node.start)) {
1847
+ if ((serverFile || rscFile) && kind === "client-hooks" && clientHook(name) && !clientSide(node.start)) {
1752
1848
  boundariesReport(context, node, name, boundariesClientHookReason2);
1753
1849
  }
1754
1850
  },
@@ -2016,12 +2112,82 @@ var codeConstructor = defineRule13({
2016
2112
  }
2017
2113
  });
2018
2114
 
2115
+ // src/rules/code/code-delegate.ts
2116
+ import { defineRule as defineRule14 } from "@oxlint/plugins";
2117
+ var codeDelegate = defineRule14({
2118
+ meta: {
2119
+ type: "suggestion",
2120
+ schema: [
2121
+ {
2122
+ type: "object",
2123
+ additionalProperties: false,
2124
+ properties: {
2125
+ methods: { type: "array", items: { type: "string" } },
2126
+ reads: { type: "array", items: { type: "string" } },
2127
+ max: { type: "integer", minimum: 0 }
2128
+ }
2129
+ }
2130
+ ],
2131
+ messages: {
2132
+ statement: "A handler only delegates: read services, make one call, return. Move this to an action.",
2133
+ calls: "A handler's yielded or awaited calls are limited to {{max}}; move the rest into the action it calls.",
2134
+ ternary: "A handler does not branch; move the condition into the action it calls.",
2135
+ nested: "A handler holds no function; move it into the action it calls."
2136
+ }
2137
+ },
2138
+ createOnce(context) {
2139
+ return {
2140
+ CallExpression(node) {
2141
+ const { methods = ["handler", "effect"], reads = ["Effect.serviceOption"], max = 1 } = optionsFirst(context, {});
2142
+ if (!methods.includes(astCallName(node) ?? "")) {
2143
+ return;
2144
+ }
2145
+ const awaited = (value) => value?.type === "YieldExpression" || value?.type === "AwaitExpression";
2146
+ for (const handler of node.arguments.filter(astIsInlineFunction)) {
2147
+ let calls = 0;
2148
+ const scan = (value) => astSome(value, (child) => {
2149
+ if (child.type === "ConditionalExpression") {
2150
+ context.report({ node: child, messageId: "ternary" });
2151
+ }
2152
+ if (child.type === "FunctionDeclaration" || astIsInlineFunction(child)) {
2153
+ context.report({ node: child, messageId: "nested" });
2154
+ }
2155
+ const target = awaited(child) && "argument" in child ? child.argument : null;
2156
+ if (target?.type === "CallExpression" && !reads.includes(astDottedName(target.callee) ?? "")) {
2157
+ calls += 1;
2158
+ if (calls > max) {
2159
+ context.report({
2160
+ node: child,
2161
+ messageId: "calls",
2162
+ data: { max: String(max) }
2163
+ });
2164
+ }
2165
+ }
2166
+ return false;
2167
+ }, (child) => child.type !== "FunctionDeclaration" && !astIsInlineFunction(child));
2168
+ const { body } = handler;
2169
+ if (body && body.type !== "BlockStatement") {
2170
+ scan(body);
2171
+ }
2172
+ for (const statement of body?.type === "BlockStatement" ? body.body : []) {
2173
+ const allowed = statement.type === "ReturnStatement" || statement.type === "VariableDeclaration" && statement.declarations.every((declarator) => awaited(declarator.init)) || statement.type === "ExpressionStatement" && awaited(statement.expression) && "argument" in statement.expression && statement.expression.argument?.type === "CallExpression";
2174
+ if (!allowed) {
2175
+ context.report({ node: statement, messageId: "statement" });
2176
+ }
2177
+ scan(statement);
2178
+ }
2179
+ }
2180
+ }
2181
+ };
2182
+ }
2183
+ });
2184
+
2019
2185
  // src/rules/code/code-double-negation.ts
2020
2186
  var codeDoubleNegation = astSelectorRule('UnaryExpression[operator="!"] > UnaryExpression[operator="!"]', "Use Boolean(value) or an explicit comparison.");
2021
2187
 
2022
2188
  // src/rules/code/code-function-nesting.ts
2023
- import { defineRule as defineRule14 } from "@oxlint/plugins";
2024
- var codeFunctionNesting = defineRule14({
2189
+ import { defineRule as defineRule15 } from "@oxlint/plugins";
2190
+ var codeFunctionNesting = defineRule15({
2025
2191
  meta: {
2026
2192
  type: "suggestion",
2027
2193
  schema: [
@@ -2062,7 +2228,7 @@ var codeFunctionNesting = defineRule14({
2062
2228
  });
2063
2229
 
2064
2230
  // src/rules/code/code-inline.ts
2065
- import { defineRule as defineRule15 } from "@oxlint/plugins";
2231
+ import { defineRule as defineRule16 } from "@oxlint/plugins";
2066
2232
 
2067
2233
  // src/utils/source.utils.ts
2068
2234
  var sourceDirective = /^\s*(?:\/\s*<(?:reference\b|amd-[\w-]+\b)|@ts-(?:check|ignore|expect-error|nocheck)\b|(?:#|@)__(?:PURE|NO_SIDE_EFFECTS)__\b|@jsx(?:ImportSource|Runtime|Frag)?\b|@vite-ignore\b|(?:[#@]\s*)?sourceMappingURL=|(?:es|ox)lint-(?:disable|enable)|prettier-ignore|biome-ignore|[cv]8 ignore|istanbul ignore|webpack[A-Z])/;
@@ -2147,7 +2313,7 @@ var codeInlineEffects = new Set([
2147
2313
  function codeInlineEffectBetween(program, start, end) {
2148
2314
  return astSome(program, (node) => node.range[0] >= start && node.range[1] <= end && (codeInlineEffects.has(node.type) || node.type === "UnaryExpression" && node.operator === "delete"), (node) => node.range[1] > start && node.range[0] < end && !(node.range[0] >= start && (node.type === "FunctionDeclaration" || astIsInlineFunction(node))));
2149
2315
  }
2150
- var codeInline = defineRule15({
2316
+ var codeInline = defineRule16({
2151
2317
  meta: {
2152
2318
  type: "suggestion",
2153
2319
  schema: [
@@ -2157,6 +2323,7 @@ var codeInline = defineRule15({
2157
2323
  properties: {
2158
2324
  max: { type: "integer", minimum: 1, default: 160 },
2159
2325
  nesting: { type: "integer", minimum: 1, default: 2 },
2326
+ callbacks: { type: "boolean", default: false },
2160
2327
  allow: optionsPatternSchema,
2161
2328
  keep: optionsPatternSchema,
2162
2329
  services: { type: "array", items: { type: "string" } }
@@ -2185,7 +2352,8 @@ var codeInline = defineRule15({
2185
2352
  return;
2186
2353
  }
2187
2354
  if (uses.length === 1) {
2188
- 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))) {
2189
2357
  return;
2190
2358
  }
2191
2359
  context.report({
@@ -2197,7 +2365,12 @@ var codeInline = defineRule15({
2197
2365
  }
2198
2366
  const arrow = init?.type === "ArrowFunctionExpression" && init.body.type !== "BlockStatement" && !astSome(init.body, astIsInlineFunction);
2199
2367
  const call = init?.type === "CallExpression" && !astSome(init.arguments, astIsInlineFunction) && astCallDepth(init) <= 2;
2200
- 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) {
2201
2374
  context.report({
2202
2375
  node: id,
2203
2376
  messageId: "trivial",
@@ -2225,7 +2398,7 @@ var codeInline = defineRule15({
2225
2398
  deferred ||= current.type === "FunctionDeclaration" || astIsInlineFunction(current);
2226
2399
  }
2227
2400
  const wrapped = codeInlineWrapped.has(init.type) && use.parent.type === "MemberExpression" && use.parent.object === use;
2228
- 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])) {
2229
2402
  return;
2230
2403
  }
2231
2404
  context.report({
@@ -2260,8 +2433,8 @@ var codeInline = defineRule15({
2260
2433
  });
2261
2434
 
2262
2435
  // src/rules/code/code-justified-call.ts
2263
- import { defineRule as defineRule16 } from "@oxlint/plugins";
2264
- var codeJustifiedCall = defineRule16({
2436
+ import { defineRule as defineRule17 } from "@oxlint/plugins";
2437
+ var codeJustifiedCall = defineRule17({
2265
2438
  meta: {
2266
2439
  type: "problem",
2267
2440
  schema: [
@@ -2315,8 +2488,8 @@ var codeJustifiedCall = defineRule16({
2315
2488
  });
2316
2489
 
2317
2490
  // src/rules/code/code-literal-in.ts
2318
- import { defineRule as defineRule17 } from "@oxlint/plugins";
2319
- var codeLiteralIn = defineRule17({
2491
+ import { defineRule as defineRule18 } from "@oxlint/plugins";
2492
+ var codeLiteralIn = defineRule18({
2320
2493
  meta: {
2321
2494
  type: "suggestion",
2322
2495
  schema: [
@@ -2357,7 +2530,7 @@ var codeLiteralIn = defineRule17({
2357
2530
  });
2358
2531
 
2359
2532
  // src/rules/code/code-mutable-state.ts
2360
- import { defineRule as defineRule18 } from "@oxlint/plugins";
2533
+ import { defineRule as defineRule19 } from "@oxlint/plugins";
2361
2534
  var functionScopes = new Set([
2362
2535
  "FunctionDeclaration",
2363
2536
  "FunctionExpression",
@@ -2374,7 +2547,7 @@ function hasAncestor(node, test) {
2374
2547
  }
2375
2548
  return false;
2376
2549
  }
2377
- var codeMutableState = defineRule18({
2550
+ var codeMutableState = defineRule19({
2378
2551
  meta: {
2379
2552
  type: "problem",
2380
2553
  schema: [
@@ -2432,7 +2605,7 @@ var codeMutableState = defineRule18({
2432
2605
  });
2433
2606
 
2434
2607
  // src/rules/code/code-object-params.ts
2435
- import { defineRule as defineRule19 } from "@oxlint/plugins";
2608
+ import { defineRule as defineRule20 } from "@oxlint/plugins";
2436
2609
  function isObjectParam(node) {
2437
2610
  if (node.type === "ObjectPattern") {
2438
2611
  return true;
@@ -2446,7 +2619,7 @@ function isObjectParam(node) {
2446
2619
  const annotation = node.typeAnnotation?.typeAnnotation;
2447
2620
  return !annotation || annotation.type === "TSTypeReference" || annotation.type === "TSTypeLiteral" || annotation.type === "TSIntersectionType";
2448
2621
  }
2449
- var codeObjectParams = defineRule19({
2622
+ var codeObjectParams = defineRule20({
2450
2623
  meta: {
2451
2624
  type: "problem",
2452
2625
  schema: [
@@ -2495,8 +2668,8 @@ var codeObjectParams = defineRule19({
2495
2668
  });
2496
2669
 
2497
2670
  // src/rules/code/code-paired-call.ts
2498
- import { defineRule as defineRule20 } from "@oxlint/plugins";
2499
- var codePairedCall = defineRule20({
2671
+ import { defineRule as defineRule21 } from "@oxlint/plugins";
2672
+ var codePairedCall = defineRule21({
2500
2673
  meta: {
2501
2674
  type: "problem",
2502
2675
  schema: [
@@ -2563,7 +2736,7 @@ var codePairedCall = defineRule20({
2563
2736
  });
2564
2737
 
2565
2738
  // src/rules/code/code-promise-mutation.ts
2566
- import { defineRule as defineRule21 } from "@oxlint/plugins";
2739
+ import { defineRule as defineRule22 } from "@oxlint/plugins";
2567
2740
  function mutationBinding(node) {
2568
2741
  if (node.type === "Identifier") {
2569
2742
  return node;
@@ -2592,7 +2765,7 @@ function mutationBindings(node) {
2592
2765
  const binding = mutationBinding(node);
2593
2766
  return binding ? [binding] : [];
2594
2767
  }
2595
- var codePromiseMutation = defineRule21({
2768
+ var codePromiseMutation = defineRule22({
2596
2769
  meta: {
2597
2770
  type: "problem",
2598
2771
  schema: [
@@ -2685,8 +2858,8 @@ var codePromiseMutation = defineRule21({
2685
2858
  var codeSwitch = astSelectorRule("SwitchStatement", "Avoid switch statements.");
2686
2859
 
2687
2860
  // src/rules/code/code-traced.ts
2688
- import { defineRule as defineRule22 } from "@oxlint/plugins";
2689
- var codeTraced = defineRule22({
2861
+ import { defineRule as defineRule23 } from "@oxlint/plugins";
2862
+ var codeTraced = defineRule23({
2690
2863
  meta: {
2691
2864
  type: "problem",
2692
2865
  schema: [
@@ -2773,7 +2946,7 @@ var codeTraced = defineRule22({
2773
2946
  });
2774
2947
 
2775
2948
  // src/rules/code/code-trivial-functions.ts
2776
- import { defineRule as defineRule23 } from "@oxlint/plugins";
2949
+ import { defineRule as defineRule24 } from "@oxlint/plugins";
2777
2950
  var codeTrivialFunctionsBanned = [
2778
2951
  "isRecord",
2779
2952
  "isPlainObject",
@@ -2903,7 +3076,35 @@ function guardStatement(node, state) {
2903
3076
  return guardFallback(declaration.init);
2904
3077
  });
2905
3078
  }
2906
- var codeTrivialFunctions = defineRule23({
3079
+ function codeTrivialFunctionsDefaults(program) {
3080
+ const starts = [];
3081
+ const names = new Set;
3082
+ for (const statement of program.body) {
3083
+ const declaration = statement.type === "ExportDefaultDeclaration" ? statement.declaration : undefined;
3084
+ if (declaration?.type === "FunctionDeclaration" || declaration && astIsInlineFunction(declaration)) {
3085
+ starts.push(declaration.start);
3086
+ } else if (declaration?.type === "Identifier") {
3087
+ names.add(declaration.name);
3088
+ } else if (statement.type === "ExportNamedDeclaration" && !statement.source) {
3089
+ const specifier = statement.specifiers.find((item) => astModuleName(item.exported) === "default");
3090
+ if (specifier) {
3091
+ names.add(astModuleName(specifier.local));
3092
+ }
3093
+ }
3094
+ }
3095
+ for (const statement of program.body) {
3096
+ if (statement.type === "FunctionDeclaration" && statement.id && names.has(statement.id.name)) {
3097
+ starts.push(statement.start);
3098
+ }
3099
+ for (const declarator of statement.type === "VariableDeclaration" ? statement.declarations : []) {
3100
+ if (declarator.id.type === "Identifier" && names.has(declarator.id.name) && declarator.init && astIsInlineFunction(declarator.init)) {
3101
+ starts.push(declarator.init.start);
3102
+ }
3103
+ }
3104
+ }
3105
+ return starts;
3106
+ }
3107
+ var codeTrivialFunctions = defineRule24({
2907
3108
  meta: {
2908
3109
  type: "problem",
2909
3110
  schema: [
@@ -2917,7 +3118,8 @@ var codeTrivialFunctions = defineRule23({
2917
3118
  services: { type: "boolean" },
2918
3119
  mode: { type: "string", enum: ["legacy", "precise"] },
2919
3120
  deny: { type: "array", items: { type: "string" }, uniqueItems: true },
2920
- guards: { type: "boolean" }
3121
+ guards: { type: "boolean" },
3122
+ defaults: { type: "boolean" }
2921
3123
  }
2922
3124
  }
2923
3125
  ],
@@ -2937,8 +3139,10 @@ var codeTrivialFunctions = defineRule23({
2937
3139
  let checkServiceMethods = false;
2938
3140
  const reported = new Set;
2939
3141
  const checked = new Set;
3142
+ const defaults = new Set;
3143
+ let checkDefaults = true;
2940
3144
  function checkHelper(node) {
2941
- if (bannedNames.size === 0 && !checkGenericGuards || checked.has(node.start)) {
3145
+ if (bannedNames.size === 0 && !checkGenericGuards || checked.has(node.start) || defaults.has(node.start)) {
2942
3146
  return;
2943
3147
  }
2944
3148
  checked.add(node.start);
@@ -3003,12 +3207,20 @@ var codeTrivialFunctions = defineRule23({
3003
3207
  allowAsync = options.async ?? false;
3004
3208
  reported.clear();
3005
3209
  checked.clear();
3210
+ defaults.clear();
3211
+ checkDefaults = options.defaults ?? true;
3006
3212
  },
3007
3213
  FunctionDeclaration: checkHelper,
3008
3214
  FunctionExpression: checkHelper,
3009
3215
  ArrowFunctionExpression: checkHelper,
3010
3216
  Program(program) {
3217
+ for (const start of checkDefaults ? [] : codeTrivialFunctionsDefaults(program)) {
3218
+ defaults.add(start);
3219
+ }
3011
3220
  for (const { name, node } of exportsCheckedFunctions(program, true, checkServiceMethods, allowed)) {
3221
+ if (defaults.has(node.start)) {
3222
+ continue;
3223
+ }
3012
3224
  checkHelper(node);
3013
3225
  if (reported.has(node.start) || node.returnType?.typeAnnotation.type === "TSTypePredicate") {
3014
3226
  continue;
@@ -3034,8 +3246,8 @@ var codeTrivialFunctions = defineRule23({
3034
3246
  });
3035
3247
 
3036
3248
  // src/rules/code/code-yield-argument.ts
3037
- import { defineRule as defineRule24 } from "@oxlint/plugins";
3038
- var codeYieldArgument = defineRule24({
3249
+ import { defineRule as defineRule25 } from "@oxlint/plugins";
3250
+ var codeYieldArgument = defineRule25({
3039
3251
  meta: {
3040
3252
  type: "suggestion",
3041
3253
  messages: {
@@ -3057,7 +3269,7 @@ var codeYieldArgument = defineRule24({
3057
3269
  });
3058
3270
 
3059
3271
  // src/rules/comments/comments-code.ts
3060
- import { defineRule as defineRule25 } from "@oxlint/plugins";
3272
+ import { defineRule as defineRule26 } from "@oxlint/plugins";
3061
3273
  var commentsLineGap = /^[\t \r\n\u2028\u2029]*$/;
3062
3274
  var commentsLineTerminator = /\r\n|[\n\r\u2028\u2029]/;
3063
3275
  var commentsUnicodeTerminator = /[\u2028\u2029]/;
@@ -3108,7 +3320,7 @@ function commentsFollowsOpeningBrace(text, offset) {
3108
3320
  }
3109
3321
  return false;
3110
3322
  }
3111
- var commentsCode = defineRule25({
3323
+ var commentsCode = defineRule26({
3112
3324
  meta: {
3113
3325
  type: "suggestion",
3114
3326
  fixable: "code",
@@ -3120,7 +3332,8 @@ var commentsCode = defineRule25({
3120
3332
  why: { anyOf: [{ type: "boolean" }, { type: "string", minLength: 1 }], default: true },
3121
3333
  allow: { type: "array", items: { type: "string" } },
3122
3334
  jsdoc: { type: "boolean", default: false },
3123
- members: { type: "string", enum: ["off", "also", "only"] }
3335
+ members: { type: "string", enum: ["off", "also", "only"] },
3336
+ steps: { type: "boolean", default: false }
3124
3337
  }
3125
3338
  }
3126
3339
  ],
@@ -3135,9 +3348,31 @@ var commentsCode = defineRule25({
3135
3348
  let allowJsdoc = false;
3136
3349
  let allowed = [];
3137
3350
  let members = "off";
3351
+ let steps = false;
3352
+ let functionDepth = 0;
3138
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
+ }
3139
3374
  function keepComment(comment, value, isWhy) {
3140
- 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);
3141
3376
  }
3142
3377
  function checkMember(node) {
3143
3378
  const parent = node.parent;
@@ -3161,7 +3396,10 @@ var commentsCode = defineRule25({
3161
3396
  allowJsdoc = options.jsdoc ?? false;
3162
3397
  allowed = (options.allow ?? []).map((pattern) => new RegExp(pattern));
3163
3398
  members = options.members ?? "off";
3399
+ steps = options.steps ?? false;
3400
+ functionDepth = 0;
3164
3401
  memberComments.clear();
3402
+ stepStarts.clear();
3165
3403
  },
3166
3404
  TSPropertySignature: checkMember,
3167
3405
  TSMethodSignature: checkMember,
@@ -3170,6 +3408,17 @@ var commentsCode = defineRule25({
3170
3408
  TSConstructSignatureDeclaration: checkMember,
3171
3409
  Property: checkMember,
3172
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),
3173
3422
  "Program:exit"(program) {
3174
3423
  const text = context.sourceCode.text;
3175
3424
  const found = [];
@@ -3274,9 +3523,9 @@ var commentsCode = defineRule25({
3274
3523
  });
3275
3524
 
3276
3525
  // src/rules/comments/comments-directives.ts
3277
- import { defineRule as defineRule26 } from "@oxlint/plugins";
3526
+ import { defineRule as defineRule27 } from "@oxlint/plugins";
3278
3527
  var directive = /^\s*(?:oxlint|eslint)-disable(?:-next-line|-line)?\b(.*)$/s;
3279
- var commentsDirectives = defineRule26({
3528
+ var commentsDirectives = defineRule27({
3280
3529
  meta: {
3281
3530
  type: "problem",
3282
3531
  schema: [],
@@ -3304,9 +3553,9 @@ var commentsDirectives = defineRule26({
3304
3553
  });
3305
3554
 
3306
3555
  // src/rules/copy/copy-dashes.ts
3307
- import { defineRule as defineRule27 } from "@oxlint/plugins";
3556
+ import { defineRule as defineRule28 } from "@oxlint/plugins";
3308
3557
  var copyDashesMessage = "Use a comma, colon, period or parentheses instead.";
3309
- var copyDashes = defineRule27({
3558
+ var copyDashes = defineRule28({
3310
3559
  meta: {
3311
3560
  type: "suggestion",
3312
3561
  schema: [
@@ -3362,7 +3611,7 @@ var copyDashes = defineRule27({
3362
3611
  });
3363
3612
 
3364
3613
  // src/rules/effect/effect-layer-file.ts
3365
- import { defineRule as defineRule28 } from "@oxlint/plugins";
3614
+ import { defineRule as defineRule29 } from "@oxlint/plugins";
3366
3615
 
3367
3616
  // src/utils/effect.utils.ts
3368
3617
  var effectBuilders = ["Effect.gen", "Effect.fn", "Effect.fnUntraced"];
@@ -3404,7 +3653,7 @@ var effectLayerFileDefaultConstructors = [
3404
3653
  "Layer.sync",
3405
3654
  "Layer.unwrap"
3406
3655
  ];
3407
- var effectLayerFile = defineRule28({
3656
+ var effectLayerFile = defineRule29({
3408
3657
  meta: {
3409
3658
  type: "problem",
3410
3659
  schema: [
@@ -3415,6 +3664,7 @@ var effectLayerFile = defineRule28({
3415
3664
  layer: { type: "string" },
3416
3665
  make: { type: "string" },
3417
3666
  prefix: { type: "string" },
3667
+ suffix: { type: "string" },
3418
3668
  builders: { type: "array", items: { type: "string" } },
3419
3669
  layers: optionsPatternSchema,
3420
3670
  services: optionsPatternSchema,
@@ -3427,8 +3677,8 @@ var effectLayerFile = defineRule28({
3427
3677
  notLayer: "`{{layer}}` is built with Layer: a Layer call, a Layer pipe, or a function returning one.",
3428
3678
  builderName: "The Effect that builds the service is named `{{make}}`, one per layer file.",
3429
3679
  moduleMethod: "Only `{{make}}` is an Effect at module level; move this into `{{make}}`, or into a utils file if two files use it.",
3430
- 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.",
3431
- 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.",
3432
3682
  makeReturn: "`{{make}}` returns the service through its tag (Tag.of({ … })), a Context, or a Layer.",
3433
3683
  inlineBuilder: "Build this layer from `{{make}}`, not an inline Effect: Layer.effect(Tag, {{make}}). One built layer per file."
3434
3684
  }
@@ -3437,6 +3687,7 @@ var effectLayerFile = defineRule28({
3437
3687
  let layer = "layer";
3438
3688
  let make = "make";
3439
3689
  let prefix = "layer";
3690
+ let suffix = "";
3440
3691
  let builders = new Set(effectBuilders);
3441
3692
  let layers = [];
3442
3693
  let services = [];
@@ -3453,14 +3704,15 @@ var effectLayerFile = defineRule28({
3453
3704
  const options = optionsFirst(context, {});
3454
3705
  layer = options.layer ?? "layer";
3455
3706
  make = options.make ?? "make";
3456
- prefix = options.prefix ?? "layer";
3707
+ suffix = options.suffix ?? "";
3708
+ prefix = options.prefix ?? (suffix ? "" : "layer");
3457
3709
  builders = new Set(options.builders ?? effectBuilders);
3458
3710
  constructors = new Set(options.constructors ?? effectLayerFileDefaultConstructors);
3459
- layers = optionsPatterns(options.layers ?? effectLayerFileDefaultLayers);
3711
+ layers = optionsPatterns(options.layers ?? [...effectLayerFileDefaultLayers, ...suffix ? [`(?:^|\\.)\\w+${optionsEscape(suffix)}$`] : []]);
3460
3712
  services = [...optionsPatterns(options.services ?? effectLayerFileDefaultServices), ...layers];
3461
3713
  },
3462
3714
  Program(program) {
3463
- const data = { layer, make, prefix };
3715
+ const data = { layer, make, prefix, suffix };
3464
3716
  const declarators = program.body.flatMap((statement) => {
3465
3717
  const declaration = astDeclaration(statement);
3466
3718
  const exported = statement.type === "ExportNamedDeclaration";
@@ -3520,7 +3772,8 @@ var effectLayerFile = defineRule28({
3520
3772
  data
3521
3773
  });
3522
3774
  }
3523
- 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))) {
3524
3777
  context.report({
3525
3778
  node,
3526
3779
  messageId: "localLayer",
@@ -3557,7 +3810,7 @@ var effectLayerFile = defineRule28({
3557
3810
  context.report({
3558
3811
  node: argument,
3559
3812
  messageId: "inlineBuilder",
3560
- data: { layer, make, prefix }
3813
+ data: { layer, make, prefix, suffix }
3561
3814
  });
3562
3815
  }
3563
3816
  }
@@ -3567,7 +3820,7 @@ var effectLayerFile = defineRule28({
3567
3820
  });
3568
3821
 
3569
3822
  // src/rules/effect/effect-layers.ts
3570
- import { defineRule as defineRule29 } from "@oxlint/plugins";
3823
+ import { defineRule as defineRule30 } from "@oxlint/plugins";
3571
3824
  var effectLayersDefaultMerge = ["Layer.mergeAll"];
3572
3825
  var effectLayersDefaultProvide = ["Effect.provide", "Layer.provide", "Layer.provideMerge"];
3573
3826
  var effectLayersDefaultMin = 2;
@@ -3579,7 +3832,7 @@ var effectLayersGroupSchema = {
3579
3832
  min: { type: "integer", minimum: 1 }
3580
3833
  }
3581
3834
  };
3582
- var effectLayers = defineRule29({
3835
+ var effectLayers = defineRule30({
3583
3836
  meta: {
3584
3837
  type: "layout",
3585
3838
  fixable: "whitespace",
@@ -3661,11 +3914,11 @@ var effectLayers = defineRule29({
3661
3914
  });
3662
3915
 
3663
3916
  // src/rules/effect/effect-merge-pipe.ts
3664
- import { defineRule as defineRule30 } from "@oxlint/plugins";
3917
+ import { defineRule as defineRule31 } from "@oxlint/plugins";
3665
3918
  var effectMergePipeDefaultMerge = ["Layer.mergeAll"];
3666
3919
  var effectMergePipeDefaultPipe = ["pipe", "Function.pipe"];
3667
3920
  var effectMergePipeDefaultMethods = ["pipe"];
3668
- var effectMergePipe = defineRule30({
3921
+ var effectMergePipe = defineRule31({
3669
3922
  meta: {
3670
3923
  type: "layout",
3671
3924
  schema: [
@@ -3710,7 +3963,7 @@ var effectMergePipe = defineRule30({
3710
3963
  });
3711
3964
 
3712
3965
  // src/rules/effect/effect-schema-first.ts
3713
- import { defineRule as defineRule31 } from "@oxlint/plugins";
3966
+ import { defineRule as defineRule32 } from "@oxlint/plugins";
3714
3967
  var effectSchemaFirstDefaultRuntime = [
3715
3968
  "Effect",
3716
3969
  "Layer",
@@ -3772,7 +4025,7 @@ function effectSchemaFirstReference(node) {
3772
4025
  }
3773
4026
  return node.type === "TSInterfaceHeritage" ? astDottedName(node.expression) : undefined;
3774
4027
  }
3775
- var effectSchemaFirst = defineRule31({
4028
+ var effectSchemaFirst = defineRule32({
3776
4029
  meta: {
3777
4030
  type: "suggestion",
3778
4031
  schema: [
@@ -3945,7 +4198,7 @@ var effectSchemaFirst = defineRule31({
3945
4198
  });
3946
4199
 
3947
4200
  // src/rules/effect/effect-values-first.ts
3948
- import { defineRule as defineRule32 } from "@oxlint/plugins";
4201
+ import { defineRule as defineRule33 } from "@oxlint/plugins";
3949
4202
  var effectValuesFirstDefaultPinned = [
3950
4203
  "^Layer\\.",
3951
4204
  "^Context\\.",
@@ -3954,7 +4207,7 @@ var effectValuesFirstDefaultPinned = [
3954
4207
  "(?:^|\\.)layer[^.]*$",
3955
4208
  "^Schema\\.(?:asserts|decode\\w*|encode\\w*|is|isSchema|isSchemaError|resolveAnnotations)$"
3956
4209
  ];
3957
- var effectValuesFirst = defineRule32({
4210
+ var effectValuesFirst = defineRule33({
3958
4211
  meta: {
3959
4212
  type: "suggestion",
3960
4213
  schema: [
@@ -4037,7 +4290,7 @@ var effectValuesFirst = defineRule32({
4037
4290
  });
4038
4291
 
4039
4292
  // src/rules/files/files-components-only.ts
4040
- import { defineRule as defineRule33 } from "@oxlint/plugins";
4293
+ import { defineRule as defineRule34 } from "@oxlint/plugins";
4041
4294
 
4042
4295
  // src/utils/react.utils.ts
4043
4296
  function reactReturnsOutput(program, node) {
@@ -4109,7 +4362,7 @@ function reactIsComponent(program, candidate) {
4109
4362
  function componentName(basename2) {
4110
4363
  return basename2.replace(/\.(tsx?|jsx?)$/, "").replaceAll(/(?:^[-.]*|[-.]+)(.?)/g, (_, char) => char.toUpperCase());
4111
4364
  }
4112
- var filesComponentsOnly = defineRule33({
4365
+ var filesComponentsOnly = defineRule34({
4113
4366
  meta: {
4114
4367
  type: "problem",
4115
4368
  schema: [
@@ -4197,7 +4450,7 @@ var filesComponentsOnly = defineRule33({
4197
4450
  });
4198
4451
 
4199
4452
  // src/rules/files/files-constants-only.ts
4200
- import { defineRule as defineRule34 } from "@oxlint/plugins";
4453
+ import { defineRule as defineRule35 } from "@oxlint/plugins";
4201
4454
  function unwrap(expression) {
4202
4455
  if (expression.type === "ParenthesizedExpression" || expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSTypeAssertion" || expression.type === "TSNonNullExpression" || expression.type === "TSInstantiationExpression") {
4203
4456
  return unwrap(expression.expression);
@@ -4252,7 +4505,7 @@ function collectBindings(program) {
4252
4505
  }
4253
4506
  return bindings;
4254
4507
  }
4255
- var filesConstantsOnly = defineRule34({
4508
+ var filesConstantsOnly = defineRule35({
4256
4509
  meta: {
4257
4510
  type: "problem",
4258
4511
  schema: [
@@ -4351,7 +4604,7 @@ var filesConstantsOnly = defineRule34({
4351
4604
  });
4352
4605
 
4353
4606
  // src/rules/files/files-domain-constants.ts
4354
- import { defineRule as defineRule35 } from "@oxlint/plugins";
4607
+ import { defineRule as defineRule36 } from "@oxlint/plugins";
4355
4608
  import { basename as basename2, dirname as dirname2 } from "node:path";
4356
4609
  function isData(expression) {
4357
4610
  const value = astUnwrap(expression);
@@ -4369,7 +4622,7 @@ function isData(expression) {
4369
4622
  }
4370
4623
  return value.type === "NewExpression" && value.callee.type === "Identifier" && ["Map", "Set"].includes(value.callee.name) && value.arguments[0]?.type === "ArrayExpression" && value.arguments[0].elements.length > 0;
4371
4624
  }
4372
- var filesDomainConstants = defineRule35({
4625
+ var filesDomainConstants = defineRule36({
4373
4626
  meta: {
4374
4627
  type: "problem",
4375
4628
  schema: [
@@ -4434,11 +4687,9 @@ var filesDomainConstants = defineRule35({
4434
4687
  });
4435
4688
 
4436
4689
  // src/rules/files/files-exports.ts
4437
- import { defineRule as defineRule36 } from "@oxlint/plugins";
4438
- function escapeRegExp(value) {
4439
- return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
4440
- }
4441
- var filesExports = defineRule36({
4690
+ import { defineRule as defineRule37 } from "@oxlint/plugins";
4691
+ import { posix as posix2 } from "node:path";
4692
+ var filesExports = defineRule37({
4442
4693
  meta: {
4443
4694
  type: "problem",
4444
4695
  schema: [
@@ -4448,7 +4699,8 @@ var filesExports = defineRule36({
4448
4699
  properties: {
4449
4700
  names: { type: "array", items: { type: "string" } },
4450
4701
  patterns: { type: "array", items: { type: "string" } },
4451
- stem: { type: "string", enum: [...namingStemModes] },
4702
+ stem: { type: "string", enum: [...namingStemModes, "folder"] },
4703
+ singularize: { type: "string", enum: ["none", "trailing-s"] },
4452
4704
  roles: { type: "array", items: { type: "string" } },
4453
4705
  separators: { type: "array", items: { type: "string" } },
4454
4706
  types: { type: "boolean" },
@@ -4491,12 +4743,14 @@ var filesExports = defineRule36({
4491
4743
  if (options.names === undefined && options.patterns === undefined) {
4492
4744
  return;
4493
4745
  }
4494
- const domain = namingFileStem(basename3, options.stem, options.roles, options.separators);
4746
+ const read = options.stem === "folder" ? namingBasename(posix2.dirname(context.filename.replaceAll("\\", "/"))) : namingFileStem(basename3, options.stem, options.roles, options.separators);
4747
+ const last = read.split(/[-_]+/).at(-1) ?? "";
4748
+ const domain = options.singularize === "trailing-s" && last.length > 1 && last.endsWith("s") ? read.slice(0, -1) : read;
4495
4749
  const pascalDomain = namingPascalCase(domain);
4496
4750
  const camelDomain = namingCamelCase(domain);
4497
4751
  const allowed = (options.names ?? []).map((template) => template.replaceAll("{Domain}", pascalDomain).replaceAll("{domain}", camelDomain));
4498
4752
  const patterns = (options.patterns ?? []).map((template) => {
4499
- const source = template.replaceAll("{Domain}", escapeRegExp(pascalDomain)).replaceAll("{domain}", escapeRegExp(camelDomain));
4753
+ const source = template.replaceAll("{Domain}", optionsEscape(pascalDomain)).replaceAll("{domain}", optionsEscape(camelDomain));
4500
4754
  return new RegExp(`^(?:${source})$`);
4501
4755
  });
4502
4756
  const allowedLabel = [...allowed, ...patterns.map((pattern) => pattern.toString())].join(", ");
@@ -4519,12 +4773,12 @@ var filesExports = defineRule36({
4519
4773
  });
4520
4774
 
4521
4775
  // src/rules/files/files-externals.ts
4522
- import { defineRule as defineRule37 } from "@oxlint/plugins";
4776
+ import { defineRule as defineRule38 } from "@oxlint/plugins";
4523
4777
  import { existsSync as existsSync2, readFileSync } from "node:fs";
4524
4778
  import { builtinModules as builtinModules2 } from "node:module";
4525
4779
  import { dirname as dirname3, join as join2 } from "node:path";
4526
4780
  var filesExternalsConfigs = "^(?:vite|next)\\.config\\.[cm]?[jt]s$";
4527
- var filesExternals = defineRule37({
4781
+ var filesExternals = defineRule38({
4528
4782
  meta: {
4529
4783
  type: "problem",
4530
4784
  schema: [
@@ -4617,7 +4871,7 @@ var filesExternals = defineRule37({
4617
4871
  });
4618
4872
 
4619
4873
  // src/rules/files/files-factory-keys.ts
4620
- import { defineRule as defineRule38 } from "@oxlint/plugins";
4874
+ import { defineRule as defineRule39 } from "@oxlint/plugins";
4621
4875
  function returnedObject(expression) {
4622
4876
  if (expression?.type === "TSAsExpression" || expression?.type === "TSSatisfiesExpression" || expression?.type === "TSTypeAssertion" || expression?.type === "TSNonNullExpression") {
4623
4877
  return returnedObject(expression.expression);
@@ -4628,7 +4882,7 @@ function compileKeys(pattern, keys, requireKeys) {
4628
4882
  const allowedList = keys.concat(requireKeys.filter((key) => !keys.includes(key)));
4629
4883
  return { patterns: optionsPatternsWithEmpty(pattern), allowed: new Set(allowedList), allowedList, required: requireKeys };
4630
4884
  }
4631
- var filesFactoryKeys = defineRule38({
4885
+ var filesFactoryKeys = defineRule39({
4632
4886
  meta: {
4633
4887
  type: "problem",
4634
4888
  schema: [
@@ -4746,8 +5000,8 @@ var filesFactoryKeys = defineRule38({
4746
5000
  });
4747
5001
 
4748
5002
  // src/rules/files/files-helpers.ts
4749
- import { defineRule as defineRule39 } from "@oxlint/plugins";
4750
- var filesHelpers = defineRule39({
5003
+ import { defineRule as defineRule40 } from "@oxlint/plugins";
5004
+ var filesHelpers = defineRule40({
4751
5005
  meta: {
4752
5006
  type: "problem",
4753
5007
  schema: [
@@ -4795,8 +5049,8 @@ var filesHelpers = defineRule39({
4795
5049
  });
4796
5050
 
4797
5051
  // src/rules/files/files-no-functions.ts
4798
- import { defineRule as defineRule40 } from "@oxlint/plugins";
4799
- var filesNoFunctions = defineRule40({
5052
+ import { defineRule as defineRule41 } from "@oxlint/plugins";
5053
+ var filesNoFunctions = defineRule41({
4800
5054
  meta: {
4801
5055
  type: "problem",
4802
5056
  schema: [
@@ -4850,8 +5104,8 @@ var filesNoFunctions = defineRule40({
4850
5104
  });
4851
5105
 
4852
5106
  // src/rules/files/files-restricted.ts
4853
- import { defineRule as defineRule41 } from "@oxlint/plugins";
4854
- var filesRestricted = defineRule41({
5107
+ import { defineRule as defineRule42 } from "@oxlint/plugins";
5108
+ var filesRestricted = defineRule42({
4855
5109
  meta: {
4856
5110
  type: "problem",
4857
5111
  schema: [
@@ -4883,7 +5137,7 @@ var filesRestricted = defineRule41({
4883
5137
  });
4884
5138
 
4885
5139
  // src/rules/files/files-route.ts
4886
- import { defineRule as defineRule42 } from "@oxlint/plugins";
5140
+ import { defineRule as defineRule43 } from "@oxlint/plugins";
4887
5141
  function inspectAdapter(context, item, bannedHooks, banIntrinsicJsx) {
4888
5142
  astVisit(item.node.body, [item.node], (node) => {
4889
5143
  if (banIntrinsicJsx && node.type === "JSXOpeningElement" && node.name.type === "JSXIdentifier" && /^[a-z]/.test(node.name.name)) {
@@ -4903,7 +5157,7 @@ function inspectAdapter(context, item, bannedHooks, banIntrinsicJsx) {
4903
5157
  }
4904
5158
  });
4905
5159
  }
4906
- var filesRoute = defineRule42({
5160
+ var filesRoute = defineRule43({
4907
5161
  meta: {
4908
5162
  type: "problem",
4909
5163
  schema: [
@@ -4973,7 +5227,7 @@ var filesRoute = defineRule42({
4973
5227
  });
4974
5228
 
4975
5229
  // src/rules/files/files-types-only.ts
4976
- import { defineRule as defineRule43 } from "@oxlint/plugins";
5230
+ import { defineRule as defineRule44 } from "@oxlint/plugins";
4977
5231
  function runtimeImportLocalNames(statement) {
4978
5232
  return statement.specifiers.flatMap((specifier) => specifier.type !== "ImportSpecifier" || specifier.importKind !== "type" ? [specifier.local.name] : []);
4979
5233
  }
@@ -5142,7 +5396,7 @@ function runtimeKind(node) {
5142
5396
  }
5143
5397
  return node.type === "FunctionDeclaration" ? "function" : "variable";
5144
5398
  }
5145
- var filesTypesOnly = defineRule43({
5399
+ var filesTypesOnly = defineRule44({
5146
5400
  meta: {
5147
5401
  type: "problem",
5148
5402
  schema: [
@@ -5250,8 +5504,8 @@ var filesTypesOnly = defineRule43({
5250
5504
  });
5251
5505
 
5252
5506
  // src/rules/layout/layout-arrow-body.ts
5253
- import { defineRule as defineRule44 } from "@oxlint/plugins";
5254
- var layoutArrowBody = defineRule44({
5507
+ import { defineRule as defineRule45 } from "@oxlint/plugins";
5508
+ var layoutArrowBody = defineRule45({
5255
5509
  meta: {
5256
5510
  type: "layout",
5257
5511
  fixable: "whitespace",
@@ -5303,12 +5557,12 @@ var layoutArrowBody = defineRule44({
5303
5557
  });
5304
5558
 
5305
5559
  // src/rules/layout/layout-call-arguments.ts
5306
- import { defineRule as defineRule45 } from "@oxlint/plugins";
5560
+ import { defineRule as defineRule46 } from "@oxlint/plugins";
5307
5561
  function layoutCallArgumentsHuggable(node) {
5308
5562
  return astIsInlineFunction(node) || node.type === "ObjectExpression" || node.type === "ArrayExpression" || astIsCall(node) && node.arguments.length === 1 && (node.callee.type !== "MemberExpression" || astIsOneLine(node.callee)) && node.arguments.every(layoutCallArgumentsHuggable);
5309
5563
  }
5310
5564
  var layoutCallArgumentsMax = { type: "integer", minimum: 1 };
5311
- var layoutCallArguments = defineRule45({
5565
+ var layoutCallArguments = defineRule46({
5312
5566
  meta: {
5313
5567
  type: "layout",
5314
5568
  fixable: "whitespace",
@@ -5327,6 +5581,12 @@ var layoutCallArguments = defineRule45({
5327
5581
  type: "boolean",
5328
5582
  default: true
5329
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
+ },
5330
5590
  hug: {
5331
5591
  type: "object",
5332
5592
  additionalProperties: false,
@@ -5349,6 +5609,7 @@ var layoutCallArguments = defineRule45({
5349
5609
  ],
5350
5610
  messages: {
5351
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.",
5352
5613
  hug: "Put every argument on its own line. Only a last callback, object or array may hug the call, after simple arguments.",
5353
5614
  prefer: "Hug the last argument: it is a callback, object or array after simple arguments.",
5354
5615
  join: "This fits on one line, so keep it on one line."
@@ -5358,11 +5619,13 @@ var layoutCallArguments = defineRule45({
5358
5619
  let options = {};
5359
5620
  let minArguments = 3;
5360
5621
  let indentUnit = sourceIndentUnit();
5361
- 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) => {
5362
5625
  const args = node.arguments;
5363
5626
  const last = args.at(-1);
5364
5627
  const source = context.sourceCode;
5365
- 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)) {
5366
5629
  return;
5367
5630
  }
5368
5631
  const text = source.text;
@@ -5384,8 +5647,8 @@ var layoutCallArguments = defineRule45({
5384
5647
  const argumentIndent = `${baseIndent}${indentUnit}`;
5385
5648
  context.report({
5386
5649
  node,
5387
- messageId: "multiline",
5388
- data: { min: String(minArguments) },
5650
+ messageId,
5651
+ data: { min: String(min) },
5389
5652
  fix: unsafe ? undefined : (fixer) => gaps.map((gap, index) => {
5390
5653
  if (index === 0) {
5391
5654
  return fixer.replaceTextRange(gap, `${newline}${argumentIndent}`);
@@ -5403,11 +5666,23 @@ var layoutCallArguments = defineRule45({
5403
5666
  const last = args.at(-1);
5404
5667
  const open = source.getTokenAfter(node.typeArguments ?? node.callee);
5405
5668
  const close = source.getLastToken(node);
5406
- 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
+ }
5407
5682
  return;
5408
5683
  }
5409
5684
  const leading = args.slice(0, -1);
5410
- 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);
5411
5686
  const gaps = sourceItemGaps(open.range[1], args, close.range[0]);
5412
5687
  if (first.loc.start.line > open.loc.end.line) {
5413
5688
  const head = [...leading.map((arg) => source.getText(arg)), text.slice(last.range[0], sourceLineEnd(text, last.range[0]))];
@@ -5453,13 +5728,15 @@ var layoutCallArguments = defineRule45({
5453
5728
  };
5454
5729
  const call = (node) => {
5455
5730
  const source = context.sourceCode;
5456
- if (options.split !== false) {
5457
- 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");
5458
5735
  }
5459
5736
  if (options.hug) {
5460
5737
  hug(node, options.hug.max);
5461
5738
  }
5462
- 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)) {
5463
5740
  join3(node, node.arguments, source.getTokenAfter(node.typeArguments ?? node.callee), source.getLastToken(node));
5464
5741
  }
5465
5742
  };
@@ -5477,6 +5754,7 @@ var layoutCallArguments = defineRule45({
5477
5754
  options = optionsFirst(context, {});
5478
5755
  minArguments = options.min ?? 3;
5479
5756
  indentUnit = sourceIndentUnit(options.indent);
5757
+ pipeMethods = new Set(options.pipe?.methods ?? ["pipe"]);
5480
5758
  },
5481
5759
  CallExpression: call,
5482
5760
  NewExpression: call,
@@ -5490,12 +5768,12 @@ var layoutCallArguments = defineRule45({
5490
5768
  });
5491
5769
 
5492
5770
  // src/rules/layout/layout-call-array.ts
5493
- import { defineRule as defineRule46 } from "@oxlint/plugins";
5771
+ import { defineRule as defineRule47 } from "@oxlint/plugins";
5494
5772
  var layoutCallArrayDefaultCallees = ["Promise.all", "Promise.allSettled"];
5495
5773
  function layoutCallArrayRoot(node) {
5496
5774
  return node.type === "MemberExpression" ? layoutCallArrayRoot(node.object) : node;
5497
5775
  }
5498
- var layoutCallArray = defineRule46({
5776
+ var layoutCallArray = defineRule47({
5499
5777
  meta: {
5500
5778
  type: "layout",
5501
5779
  fixable: "whitespace",
@@ -5611,22 +5889,29 @@ var layoutCallArray = defineRule46({
5611
5889
  });
5612
5890
 
5613
5891
  // src/rules/layout/layout-call-depth.ts
5614
- import { defineRule as defineRule47 } from "@oxlint/plugins";
5615
- function layoutCallDepthOuter(node) {
5892
+ import { defineRule as defineRule48 } from "@oxlint/plugins";
5893
+ function layoutCallDepthOuter(node, callbacks) {
5616
5894
  const parent = node.parent;
5617
- if (!parent || parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression" || parent.type.endsWith("Statement")) {
5895
+ if (!parent || !callbacks && (parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression")) {
5618
5896
  return;
5619
5897
  }
5620
- 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);
5621
5906
  }
5622
- var layoutCallDepth = defineRule47({
5907
+ var layoutCallDepth = defineRule48({
5623
5908
  meta: {
5624
5909
  type: "layout",
5625
5910
  schema: [
5626
5911
  {
5627
5912
  type: "object",
5628
5913
  additionalProperties: false,
5629
- properties: { max: { type: "integer", minimum: 1, default: 2 } }
5914
+ properties: { max: { type: "integer", minimum: 1, default: 2 }, callbacks: { type: "boolean", default: false } }
5630
5915
  }
5631
5916
  ],
5632
5917
  messages: {
@@ -5635,10 +5920,11 @@ var layoutCallDepth = defineRule47({
5635
5920
  },
5636
5921
  createOnce(context) {
5637
5922
  let max = 2;
5923
+ let callbacks = false;
5638
5924
  const check = (node) => {
5639
- const depth = astCallDepth(node);
5640
- const outer = layoutCallDepthOuter(node);
5641
- 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) {
5642
5928
  return;
5643
5929
  }
5644
5930
  context.report({
@@ -5649,7 +5935,9 @@ var layoutCallDepth = defineRule47({
5649
5935
  };
5650
5936
  return {
5651
5937
  before() {
5652
- max = optionsFirst(context, {}).max ?? 2;
5938
+ const options = optionsFirst(context, {});
5939
+ max = options.max ?? 2;
5940
+ callbacks = options.callbacks ?? false;
5653
5941
  },
5654
5942
  CallExpression: check,
5655
5943
  NewExpression: check
@@ -5658,8 +5946,8 @@ var layoutCallDepth = defineRule47({
5658
5946
  });
5659
5947
 
5660
5948
  // src/rules/layout/layout-chain.ts
5661
- import { defineRule as defineRule48 } from "@oxlint/plugins";
5662
- var layoutChain = defineRule48({
5949
+ import { defineRule as defineRule49 } from "@oxlint/plugins";
5950
+ var layoutChain = defineRule49({
5663
5951
  meta: {
5664
5952
  type: "layout",
5665
5953
  fixable: "whitespace",
@@ -5778,8 +6066,8 @@ var layoutChain = defineRule48({
5778
6066
  });
5779
6067
 
5780
6068
  // src/rules/layout/layout-items.ts
5781
- import { defineRule as defineRule49 } from "@oxlint/plugins";
5782
- var layoutItems = defineRule49({
6069
+ import { defineRule as defineRule50 } from "@oxlint/plugins";
6070
+ var layoutItems = defineRule50({
5783
6071
  meta: {
5784
6072
  type: "layout",
5785
6073
  fixable: "whitespace",
@@ -5834,8 +6122,8 @@ var layoutItems = defineRule49({
5834
6122
  });
5835
6123
 
5836
6124
  // src/rules/layout/layout-jsx-attributes.ts
5837
- import { defineRule as defineRule50 } from "@oxlint/plugins";
5838
- var layoutJsxAttributes = defineRule50({
6125
+ import { defineRule as defineRule51 } from "@oxlint/plugins";
6126
+ var layoutJsxAttributes = defineRule51({
5839
6127
  meta: {
5840
6128
  type: "layout",
5841
6129
  fixable: "whitespace",
@@ -5923,8 +6211,8 @@ var layoutJsxAttributes = defineRule50({
5923
6211
  });
5924
6212
 
5925
6213
  // src/rules/layout/layout-key-value.ts
5926
- import { defineRule as defineRule51 } from "@oxlint/plugins";
5927
- var layoutKeyValue = defineRule51({
6214
+ import { defineRule as defineRule52 } from "@oxlint/plugins";
6215
+ var layoutKeyValue = defineRule52({
5928
6216
  meta: {
5929
6217
  type: "layout",
5930
6218
  fixable: "whitespace",
@@ -5960,7 +6248,7 @@ var layoutKeyValue = defineRule51({
5960
6248
  });
5961
6249
 
5962
6250
  // src/rules/layout/layout-object.ts
5963
- import { defineRule as defineRule52 } from "@oxlint/plugins";
6251
+ import { defineRule as defineRule53 } from "@oxlint/plugins";
5964
6252
  function layoutObjectIsWhitespace(text) {
5965
6253
  return /^[\t\n\r\f ]*$/.test(text);
5966
6254
  }
@@ -5968,7 +6256,7 @@ function layoutObjectIsFlat(property) {
5968
6256
  const value = property.type === "SpreadElement" ? property.argument : property.value;
5969
6257
  return astIsOneLine(property) && (property.type === "SpreadElement" || !property.method && property.kind === "init") && value.type !== "ObjectExpression" && value.type !== "ArrayExpression" && !astIsInlineFunction(value);
5970
6258
  }
5971
- var layoutObject = defineRule52({
6259
+ var layoutObject = defineRule53({
5972
6260
  meta: {
5973
6261
  type: "layout",
5974
6262
  fixable: "whitespace",
@@ -5980,6 +6268,10 @@ var layoutObject = defineRule52({
5980
6268
  min: { type: "integer", minimum: 1, default: 3 },
5981
6269
  scope: { type: "string", enum: ["call-args", "all"], default: "call-args" },
5982
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 },
5983
6275
  join: {
5984
6276
  type: "object",
5985
6277
  additionalProperties: false,
@@ -5995,6 +6287,11 @@ var layoutObject = defineRule52({
5995
6287
  messages: {
5996
6288
  callArgs: "Object with {{min}}+ properties in a call should have each property on its own line.",
5997
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.",
5998
6295
  join: "This object fits on one line, so keep it on one line."
5999
6296
  }
6000
6297
  },
@@ -6005,6 +6302,119 @@ var layoutObject = defineRule52({
6005
6302
  let newline = `
6006
6303
  `;
6007
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
+ }
6008
6418
  return {
6009
6419
  before() {
6010
6420
  const options = optionsFirst(context, {});
@@ -6013,24 +6423,39 @@ var layoutObject = defineRule52({
6013
6423
  indentUnit = sourceIndentUnit(options.indent);
6014
6424
  newline = sourceNewline(context.sourceCode.text);
6015
6425
  join3 = options.join;
6426
+ params = options.params ?? false;
6427
+ nested = options.nested ?? false;
6428
+ siblings = options.siblings ?? false;
6429
+ inner = options.inner;
6016
6430
  },
6431
+ FunctionDeclaration: checkParams,
6432
+ FunctionExpression: checkParams,
6433
+ ArrowFunctionExpression: checkParams,
6017
6434
  ObjectExpression(node) {
6018
6435
  const source = context.sourceCode;
6019
6436
  const text = source.text;
6020
6437
  const properties = node.properties;
6021
- 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;
6022
6450
  if (split ? !astIsOneLine(node) : !join3 || astIsOneLine(node)) {
6023
6451
  return;
6024
6452
  }
6025
- const parent = node.parent;
6026
6453
  const call = (parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node) ? parent : undefined;
6027
6454
  if (scope === "call-args" && !call) {
6028
6455
  return;
6029
6456
  }
6030
6457
  if (!split) {
6031
- const callee = call ? astDottedName(call.callee) : undefined;
6032
- const kept = callee !== undefined && (join3?.keep ?? []).some((name) => callee === name || callee.endsWith(`.${name}`));
6033
- if (kept || properties.length === 0 || !properties.every(layoutObjectIsFlat) || source.getCommentsInside(node).length > 0) {
6458
+ if (joinBlocked(node, call)) {
6034
6459
  return;
6035
6460
  }
6036
6461
  const gaps = sourceItemGaps(node.range[0] + 1, properties, node.range[1] - 1);
@@ -6046,46 +6471,16 @@ var layoutObject = defineRule52({
6046
6471
  });
6047
6472
  return;
6048
6473
  }
6049
- const baseIndent = sourceLineIndent(text, node.range[0]);
6050
- const propertyIndent = `${baseIndent}${indentUnit}`;
6051
- const close = node.range[1] - 1;
6052
- context.report({
6053
- node,
6054
- messageId: scope === "all" ? "always" : "callArgs",
6055
- data: { min: String(minProperties) },
6056
- fix: (fixer) => {
6057
- if (source.getCommentsInside(node).length > 0 || !layoutObjectIsWhitespace(text.slice(node.range[0] + 1, properties[0].range[0]))) {
6058
- return null;
6059
- }
6060
- const gaps = [[node.range[0] + 1, properties[0].range[0]]];
6061
- for (const [index, property] of properties.entries()) {
6062
- const next = properties.at(index + 1);
6063
- const after = property.range[1];
6064
- const limit = next ? next.range[0] : close;
6065
- const between = text.slice(after, limit);
6066
- const comma = between.indexOf(",");
6067
- if (comma === -1) {
6068
- if (next || !layoutObjectIsWhitespace(between)) {
6069
- return null;
6070
- }
6071
- gaps.push([after, close]);
6072
- continue;
6073
- }
6074
- if (!layoutObjectIsWhitespace(between.slice(0, comma)) || !layoutObjectIsWhitespace(between.slice(comma + 1))) {
6075
- return null;
6076
- }
6077
- gaps.push([after + comma + 1, limit]);
6078
- }
6079
- return gaps.map((gap, index) => fixer.replaceTextRange(gap, index === gaps.length - 1 ? `${newline}${baseIndent}` : `${newline}${propertyIndent}`));
6080
- }
6081
- });
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);
6082
6477
  }
6083
6478
  };
6084
6479
  }
6085
6480
  });
6086
6481
 
6087
6482
  // src/rules/layout/layout-padding.ts
6088
- import { defineRule as defineRule53 } from "@oxlint/plugins";
6483
+ import { defineRule as defineRule54 } from "@oxlint/plugins";
6089
6484
  var layoutPaddingControl = new Set([
6090
6485
  "IfStatement",
6091
6486
  "ForStatement",
@@ -6114,7 +6509,7 @@ function layoutPaddingKind(node) {
6114
6509
  function layoutPaddingIsMultilineVariable(node, kind) {
6115
6510
  return kind === "variable" && node.loc.start.line !== node.loc.end.line;
6116
6511
  }
6117
- var layoutPadding = defineRule53({
6512
+ var layoutPadding = defineRule54({
6118
6513
  meta: {
6119
6514
  type: "layout",
6120
6515
  fixable: "whitespace",
@@ -6203,23 +6598,33 @@ var layoutPadding = defineRule53({
6203
6598
  });
6204
6599
 
6205
6600
  // src/rules/layout/layout-pipe.ts
6206
- import { defineRule as defineRule54 } from "@oxlint/plugins";
6207
- var layoutPipe = defineRule54({
6601
+ import { defineRule as defineRule55 } from "@oxlint/plugins";
6602
+ var layoutPipe = defineRule55({
6208
6603
  meta: {
6209
6604
  type: "layout",
6605
+ schema: [{ type: "object", additionalProperties: false, properties: { methods: { type: "array", items: { type: "string" } } } }],
6210
6606
  messages: {
6211
- 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.",
6212
6608
  step: "A pipe step may span lines only through a callback, object or array. Name this call first, then pass the name."
6213
6609
  }
6214
6610
  },
6215
6611
  createOnce(context) {
6612
+ let methods = new Set;
6216
6613
  return {
6614
+ before() {
6615
+ methods = new Set(optionsFirst(context, {}).methods ?? ["pipe"]);
6616
+ },
6217
6617
  CallExpression(node) {
6218
6618
  const callee = node.callee;
6219
- if (callee.type === "MemberExpression" && astMemberName(callee) === "pipe" && astIsCall(callee.object) && !astIsOneLine(callee.object) && callee.property.loc.start.line === callee.object.loc.end.line) {
6220
- 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
+ });
6221
6626
  }
6222
- if (!astIsPipe(node)) {
6627
+ if (!piped) {
6223
6628
  return;
6224
6629
  }
6225
6630
  for (const step of node.arguments) {
@@ -6235,7 +6640,7 @@ var layoutPipe = defineRule54({
6235
6640
  });
6236
6641
 
6237
6642
  // src/rules/layout/layout-ternary.ts
6238
- import { defineRule as defineRule55 } from "@oxlint/plugins";
6643
+ import { defineRule as defineRule56 } from "@oxlint/plugins";
6239
6644
  function layoutTernaryLines(node) {
6240
6645
  return node.loc.end.line - node.loc.start.line + 1;
6241
6646
  }
@@ -6243,7 +6648,7 @@ function layoutTernaryBreak(between, operator, newline) {
6243
6648
  const at = between.indexOf(operator);
6244
6649
  return `${between.slice(0, at).trimEnd()}${newline}${operator} ${between.slice(at + 1).trimStart()}`;
6245
6650
  }
6246
- var layoutTernary = defineRule55({
6651
+ var layoutTernary = defineRule56({
6247
6652
  meta: {
6248
6653
  type: "layout",
6249
6654
  fixable: "whitespace",
@@ -6358,10 +6763,70 @@ var layoutTernary = defineRule55({
6358
6763
  }
6359
6764
  });
6360
6765
 
6766
+ // src/rules/layout/layout-type-arguments.ts
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
+
6361
6826
  // src/rules/naming/naming-declarations.ts
6362
- import { defineRule as defineRule56 } from "@oxlint/plugins";
6827
+ import { defineRule as defineRule58 } from "@oxlint/plugins";
6363
6828
  var namingDeclarationsKinds = ["function", "const", "let", "var", "class", "type", "interface", "enum"];
6364
- var namingDeclarations = defineRule56({
6829
+ var namingDeclarations = defineRule58({
6365
6830
  meta: {
6366
6831
  type: "problem",
6367
6832
  schema: [
@@ -6438,7 +6903,7 @@ var namingDeclarations = defineRule56({
6438
6903
  });
6439
6904
 
6440
6905
  // src/rules/naming/naming-exports.ts
6441
- import { defineRule as defineRule57 } from "@oxlint/plugins";
6906
+ import { defineRule as defineRule59 } from "@oxlint/plugins";
6442
6907
  var namingExportsScopeProperties = {
6443
6908
  scope: { type: "string", enum: ["exports", "functions", "declarations"] },
6444
6909
  types: { type: "boolean" },
@@ -6464,7 +6929,7 @@ function matchTemplate(basename3, template, placeholder = "[a-z0-9-]+") {
6464
6929
  }
6465
6930
  return new RegExp(`^${pattern}$`).exec(basename3)?.groups ?? null;
6466
6931
  }
6467
- var namingExports = defineRule57({
6932
+ var namingExports = defineRule59({
6468
6933
  meta: {
6469
6934
  type: "problem",
6470
6935
  schema: {
@@ -6601,10 +7066,10 @@ var namingExports = defineRule57({
6601
7066
  });
6602
7067
 
6603
7068
  // src/rules/naming/naming-file.ts
6604
- import { defineRule as defineRule58 } from "@oxlint/plugins";
7069
+ import { defineRule as defineRule60 } from "@oxlint/plugins";
6605
7070
  import { readdirSync as readdirSync2 } from "node:fs";
6606
7071
  import { dirname as dirname4 } from "node:path";
6607
- var namingFile = defineRule58({
7072
+ var namingFile = defineRule60({
6608
7073
  meta: {
6609
7074
  type: "problem",
6610
7075
  schema: {
@@ -6674,9 +7139,9 @@ var namingFile = defineRule58({
6674
7139
  });
6675
7140
 
6676
7141
  // src/rules/naming/naming-folder.ts
6677
- import { defineRule as defineRule59 } from "@oxlint/plugins";
6678
- import { posix as posix2 } from "node:path";
6679
- var namingFolder = defineRule59({
7142
+ import { defineRule as defineRule61 } from "@oxlint/plugins";
7143
+ import { posix as posix3 } from "node:path";
7144
+ var namingFolder = defineRule61({
6680
7145
  meta: {
6681
7146
  type: "problem",
6682
7147
  schema: [
@@ -6699,7 +7164,7 @@ var namingFolder = defineRule59({
6699
7164
  return {
6700
7165
  Program(program) {
6701
7166
  const { singularize = "trailing-s", separators = ["-"], after, prefixes: stripPrefixes = [] } = optionsFirst(context, {});
6702
- const all = posix2.dirname(context.filename.replaceAll("\\", "/")).split("/").filter((part) => part && part !== ".");
7167
+ const all = posix3.dirname(context.filename.replaceAll("\\", "/")).split("/").filter((part) => part && part !== ".");
6703
7168
  const parent = [all.at(-1) ?? ""];
6704
7169
  let folders = after ? undefined : parent;
6705
7170
  const marker = (after ?? "").split("/").filter(Boolean);
@@ -6741,45 +7206,174 @@ var namingFolder = defineRule59({
6741
7206
  });
6742
7207
 
6743
7208
  // src/rules/naming/naming-types.ts
6744
- 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
+ });
6745
7237
 
6746
7238
  // src/rules/react/react-dynamic-classes.ts
6747
- 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
+ });
6748
7278
 
6749
7279
  // src/rules/react/react-empty-effect.ts
6750
- 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
+ });
6751
7319
 
6752
7320
  // src/rules/react/react-memo.ts
6753
- import { defineRule as defineRule60 } from "@oxlint/plugins";
6754
- var reactMemoNames = ["memo", "useMemo", "useCallback"];
6755
- var reactMemo = defineRule60({
7321
+ import { defineRule as defineRule65 } from "@oxlint/plugins";
7322
+ var reactMemo = defineRule65({
6756
7323
  meta: {
6757
7324
  type: "problem",
6758
- 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
+ ],
6759
7336
  messages: {
6760
- memo: "Use the React Compiler rather than manual memoization."
7337
+ memo: "{{message}}"
6761
7338
  }
6762
7339
  },
6763
7340
  createOnce(context) {
7341
+ let names = [];
7342
+ let sources = [];
7343
+ let message = "";
6764
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
+ },
6765
7351
  ImportDeclaration(node) {
6766
- if (node.source.value !== "react") {
7352
+ if (!sources.includes(node.source.value)) {
6767
7353
  return;
6768
7354
  }
6769
7355
  for (const specifier of node.specifiers) {
6770
- if (specifier.type === "ImportSpecifier" && reactMemoNames.includes(astModuleName(specifier.imported))) {
6771
- 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
+ });
6772
7362
  }
6773
7363
  }
6774
7364
  },
6775
7365
  MemberExpression(node) {
6776
- if (node.object.type !== "Identifier" || !reactMemoNames.includes(astMemberName(node) ?? "")) {
7366
+ if (node.object.type !== "Identifier" || !names.includes(boundariesMemberName(node) ?? "")) {
6777
7367
  return;
6778
7368
  }
6779
7369
  const variable = boundariesDeclaredVariable(context.sourceCode.getScope(node), node.object.name);
6780
- 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");
6781
7371
  if (isReact) {
6782
- context.report({ node, messageId: "memo" });
7372
+ context.report({
7373
+ node,
7374
+ messageId: "memo",
7375
+ data: { message }
7376
+ });
6783
7377
  }
6784
7378
  }
6785
7379
  };
@@ -6787,7 +7381,7 @@ var reactMemo = defineRule60({
6787
7381
  });
6788
7382
 
6789
7383
  // src/rules/react/react-props.ts
6790
- import { defineRule as defineRule61 } from "@oxlint/plugins";
7384
+ import { defineRule as defineRule66 } from "@oxlint/plugins";
6791
7385
  function reactPropsTarget(node) {
6792
7386
  if (node?.type === "FunctionDeclaration" || node?.type === "FunctionExpression" || node?.type === "ArrowFunctionExpression") {
6793
7387
  return { fn: node };
@@ -6814,7 +7408,7 @@ function reactIsPassThrough(type) {
6814
7408
  const argument = type.typeArguments?.params.length === 1 ? type.typeArguments.params[0] : undefined;
6815
7409
  return name.type === "Identifier" && name.name === "ComponentProps" && (argument?.type === "TSTypeQuery" || argument?.type === "TSLiteralType" && argument.literal.type === "Literal" && typeof argument.literal.value === "string");
6816
7410
  }
6817
- var reactProps = defineRule61({
7411
+ var reactProps = defineRule66({
6818
7412
  meta: {
6819
7413
  type: "problem",
6820
7414
  schema: [
@@ -6912,8 +7506,90 @@ var reactProps = defineRule61({
6912
7506
  }
6913
7507
  });
6914
7508
 
7509
+ // src/rules/react/react-query-props.ts
7510
+ import { defineRule as defineRule67 } from "@oxlint/plugins";
7511
+ var reactQueryProps = defineRule67({
7512
+ meta: {
7513
+ type: "problem",
7514
+ schema: [
7515
+ {
7516
+ type: "object",
7517
+ additionalProperties: false,
7518
+ properties: {
7519
+ hooks: { type: "array", items: { type: "string" } },
7520
+ functions: { type: "array", items: { type: "string" } }
7521
+ }
7522
+ }
7523
+ ],
7524
+ messages: {
7525
+ options: "Build the {{hook}} options here from props; ready-made options hide where the data comes from.",
7526
+ source: "{{key}} must be a prop or call one; pass the data function in from the app."
7527
+ }
7528
+ },
7529
+ createOnce(context) {
7530
+ return {
7531
+ CallExpression(node) {
7532
+ const { hooks = ["useQuery", "useMutation", "useInfiniteQuery", "useSuspenseQuery"], functions = ["queryFn", "mutationFn"] } = optionsFirst(context, {});
7533
+ const hook = astCallName(node);
7534
+ let owner = node.parent;
7535
+ while (owner && owner.type !== "FunctionDeclaration" && !astIsInlineFunction(owner)) {
7536
+ owner = owner.parent;
7537
+ }
7538
+ if (!hook || !hooks.includes(hook) || !owner || owner.type !== "FunctionDeclaration" && !astIsInlineFunction(owner)) {
7539
+ return;
7540
+ }
7541
+ const component = owner;
7542
+ const first = component.params.at(0);
7543
+ const parameter = first?.type === "AssignmentPattern" ? first.left : first;
7544
+ const definition = (identifier) => boundariesDeclaredVariable(context.sourceCode.getScope(identifier), identifier.name)?.defs.at(0);
7545
+ const isProps = (value) => {
7546
+ const found = value?.type === "Identifier" ? definition(value) : undefined;
7547
+ return found?.type === "Parameter" && found.node === component && parameter?.type === "Identifier";
7548
+ };
7549
+ const isProp = (value) => {
7550
+ if (value.type === "MemberExpression") {
7551
+ return isProps(value.object);
7552
+ }
7553
+ const found = value.type === "Identifier" ? definition(value) : undefined;
7554
+ if (found?.type === "Parameter") {
7555
+ return found.node === component && parameter?.type === "ObjectPattern";
7556
+ }
7557
+ return found?.type === "Variable" && found.node.type === "VariableDeclarator" && found.node.id.type === "ObjectPattern" && isProps(found.node.init);
7558
+ };
7559
+ const options = node.arguments.at(0);
7560
+ if (options?.type !== "ObjectExpression" || options.properties.some((property) => property.type === "SpreadElement")) {
7561
+ context.report({
7562
+ node: options ?? node,
7563
+ messageId: "options",
7564
+ data: { hook }
7565
+ });
7566
+ return;
7567
+ }
7568
+ for (const property of options.properties) {
7569
+ if (property.type !== "Property") {
7570
+ continue;
7571
+ }
7572
+ const key = property.key.type === "Identifier" && !property.computed ? property.key.name : astStaticString(property.key);
7573
+ if (key === undefined || !functions.includes(key)) {
7574
+ continue;
7575
+ }
7576
+ const { value } = property;
7577
+ const calls = astIsInlineFunction(value) && astSome(value.body, (child) => child.type === "CallExpression" && isProp(child.callee));
7578
+ if (!isProp(value) && !calls) {
7579
+ context.report({
7580
+ node: value,
7581
+ messageId: "source",
7582
+ data: { key }
7583
+ });
7584
+ }
7585
+ }
7586
+ }
7587
+ };
7588
+ }
7589
+ });
7590
+
6915
7591
  // src/rules/schemas/schemas-inline.ts
6916
- import { defineRule as defineRule62 } from "@oxlint/plugins";
7592
+ import { defineRule as defineRule68 } from "@oxlint/plugins";
6917
7593
 
6918
7594
  // src/utils/schemas.utils.ts
6919
7595
  var schemasElementOptionsSchema = {
@@ -6949,7 +7625,7 @@ function schemasIsAllowedElement(node, allowScalars, namespaces, structuralMetho
6949
7625
  }
6950
7626
 
6951
7627
  // src/rules/schemas/schemas-inline.ts
6952
- var schemasInline = defineRule62({
7628
+ var schemasInline = defineRule68({
6953
7629
  meta: {
6954
7630
  type: "problem",
6955
7631
  schema: [schemasElementOptionsSchema],
@@ -6998,11 +7674,11 @@ var schemasInline = defineRule62({
6998
7674
  });
6999
7675
 
7000
7676
  // src/rules/schemas/schemas-local.ts
7001
- import { defineRule as defineRule63 } from "@oxlint/plugins";
7677
+ import { defineRule as defineRule69 } from "@oxlint/plugins";
7002
7678
  var schemasLocalImportMessage = "Do not import runtime schema constructors from {{source}}; import a named schema instead.";
7003
7679
  var schemasLocalConstructionMessage = "Do not construct schemas locally; import a named schema instead.";
7004
7680
  var schemasLocalCalls = ["config", "prettifyError", "treeifyError", "flattenError", "formatError"];
7005
- var schemasLocal = defineRule63({
7681
+ var schemasLocal = defineRule69({
7006
7682
  meta: {
7007
7683
  type: "problem",
7008
7684
  schema: [
@@ -7151,8 +7827,8 @@ var schemasLocal = defineRule63({
7151
7827
  });
7152
7828
 
7153
7829
  // src/rules/schemas/schemas-rederive.ts
7154
- import { defineRule as defineRule64 } from "@oxlint/plugins";
7155
- var schemasRederive = defineRule64({
7830
+ import { defineRule as defineRule70 } from "@oxlint/plugins";
7831
+ var schemasRederive = defineRule70({
7156
7832
  meta: {
7157
7833
  type: "problem",
7158
7834
  schema: [
@@ -7198,7 +7874,7 @@ var schemasRederive = defineRule64({
7198
7874
  });
7199
7875
 
7200
7876
  // src/rules/schemas/schemas-single-use.ts
7201
- import { defineRule as defineRule65 } from "@oxlint/plugins";
7877
+ import { defineRule as defineRule71 } from "@oxlint/plugins";
7202
7878
  function isScalarSchema(node, namespaces, structuralMethods) {
7203
7879
  if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") {
7204
7880
  return false;
@@ -7212,7 +7888,7 @@ function isScalarSchema(node, namespaces, structuralMethods) {
7212
7888
  }
7213
7889
  return node.callee.object.type === "CallExpression" && isScalarSchema(node.callee.object, namespaces, structuralMethods);
7214
7890
  }
7215
- var schemasSingleUse = defineRule65({
7891
+ var schemasSingleUse = defineRule71({
7216
7892
  meta: {
7217
7893
  type: "suggestion",
7218
7894
  schema: [schemasElementOptionsSchema],
@@ -7288,7 +7964,7 @@ var schemasSingleUse = defineRule65({
7288
7964
  });
7289
7965
 
7290
7966
  // src/rules/tests/tests-assertions.ts
7291
- import { defineRule as defineRule66 } from "@oxlint/plugins";
7967
+ import { defineRule as defineRule72 } from "@oxlint/plugins";
7292
7968
  var testsAssertionsModifiers = new Set(["for", "each", "concurrent", "sequential", "only", "skip"]);
7293
7969
  var testsAssertionsFunctions = [
7294
7970
  "expect",
@@ -7478,7 +8154,7 @@ function isAssertedTest(registration, sourceCode, state) {
7478
8154
  }
7479
8155
  return rows.length > 0 && rows.every((row) => hasAssertion(callback, row.bindings, new Set, state));
7480
8156
  }
7481
- var testsAssertions = defineRule66({
8157
+ var testsAssertions = defineRule72({
7482
8158
  meta: {
7483
8159
  type: "problem",
7484
8160
  schema: [
@@ -7604,8 +8280,8 @@ var testsAssertions = defineRule66({
7604
8280
  });
7605
8281
 
7606
8282
  // src/rules/tests/tests-concurrent-db.ts
7607
- import { defineRule as defineRule67 } from "@oxlint/plugins";
7608
- var testsConcurrentDb = defineRule67({
8283
+ import { defineRule as defineRule73 } from "@oxlint/plugins";
8284
+ var testsConcurrentDb = defineRule73({
7609
8285
  meta: {
7610
8286
  type: "problem",
7611
8287
  schema: [
@@ -7661,7 +8337,7 @@ var testsConcurrentDb = defineRule67({
7661
8337
  });
7662
8338
 
7663
8339
  // src/rules/tests/tests-modifiers.ts
7664
- import { defineRule as defineRule68 } from "@oxlint/plugins";
8340
+ import { defineRule as defineRule74 } from "@oxlint/plugins";
7665
8341
  var testsModifiersNames = ["test", "it", "describe", "suite"];
7666
8342
  var testsModifiersPackages = new Set(["vitest", "bun:test", "@playwright/test", "@jest/globals"]);
7667
8343
  var testsModifiersBanned = ["only", "skip", "todo"];
@@ -7680,7 +8356,7 @@ function modifierName(node) {
7680
8356
  const property = astMemberName(node);
7681
8357
  return object && property ? `${object}.${property}` : undefined;
7682
8358
  }
7683
- var testsModifiers = defineRule68({
8359
+ var testsModifiers = defineRule74({
7684
8360
  meta: {
7685
8361
  type: "problem",
7686
8362
  schema: [
@@ -7734,9 +8410,9 @@ var testsModifiers = defineRule68({
7734
8410
  });
7735
8411
 
7736
8412
  // src/rules/tests/tests-titles.ts
7737
- import { defineRule as defineRule69 } from "@oxlint/plugins";
8413
+ import { defineRule as defineRule75 } from "@oxlint/plugins";
7738
8414
  var testsTitlesModifiers = ["only", "skip", "concurrent", "sequential", "todo", "each", "for"];
7739
- var testsTitles = defineRule69({
8415
+ var testsTitles = defineRule75({
7740
8416
  meta: {
7741
8417
  type: "suggestion",
7742
8418
  schema: [
@@ -7814,11 +8490,11 @@ var testsTitles = defineRule69({
7814
8490
  });
7815
8491
 
7816
8492
  // src/rules/types/types-declarations.ts
7817
- import { defineRule as defineRule70 } from "@oxlint/plugins";
8493
+ import { defineRule as defineRule76 } from "@oxlint/plugins";
7818
8494
  function isValuePattern(source) {
7819
8495
  return /^\^?=/.test(source);
7820
8496
  }
7821
- var typesDeclarations = defineRule70({
8497
+ var typesDeclarations = defineRule76({
7822
8498
  meta: {
7823
8499
  type: "problem",
7824
8500
  schema: [
@@ -7826,7 +8502,8 @@ var typesDeclarations = defineRule70({
7826
8502
  type: "object",
7827
8503
  additionalProperties: false,
7828
8504
  properties: {
7829
- allow: optionsPatternSchema
8505
+ allow: optionsPatternSchema,
8506
+ modules: { type: "boolean" }
7830
8507
  }
7831
8508
  }
7832
8509
  ],
@@ -7837,8 +8514,14 @@ var typesDeclarations = defineRule70({
7837
8514
  createOnce(context) {
7838
8515
  let allowedNames = [];
7839
8516
  let allowedValues = [];
8517
+ let allowModules = false;
7840
8518
  function check(node) {
7841
- if (optionsPatternsTest(allowedNames, node.id.name) || node.type === "TSTypeAliasDeclaration" && allowedValues.length > 0 && optionsPatternsTest(allowedValues, `= ${context.sourceCode.getText(node.typeAnnotation)}`)) {
8519
+ let ancestor = node.parent;
8520
+ while (allowModules && ancestor && ancestor.type !== "TSModuleDeclaration") {
8521
+ ancestor = ancestor.parent;
8522
+ }
8523
+ const ambient = ancestor?.type === "TSModuleDeclaration" && (ancestor.kind === "global" || ancestor.id.type === "Literal");
8524
+ if (allowModules && ambient || optionsPatternsTest(allowedNames, node.id.name) || node.type === "TSTypeAliasDeclaration" && allowedValues.length > 0 && optionsPatternsTest(allowedValues, `= ${context.sourceCode.getText(node.typeAnnotation)}`)) {
7842
8525
  return;
7843
8526
  }
7844
8527
  context.report({
@@ -7849,7 +8532,8 @@ var typesDeclarations = defineRule70({
7849
8532
  }
7850
8533
  return {
7851
8534
  before() {
7852
- const { allow: allowPattern } = optionsFirst(context, {});
8535
+ const { allow: allowPattern, modules = false } = optionsFirst(context, {});
8536
+ allowModules = modules;
7853
8537
  const sources = allowPattern === undefined ? [] : [allowPattern].flat();
7854
8538
  allowedNames = optionsPatterns(sources.filter((source) => !isValuePattern(source)));
7855
8539
  allowedValues = optionsPatterns(sources.filter(isValuePattern));
@@ -7861,8 +8545,8 @@ var typesDeclarations = defineRule70({
7861
8545
  });
7862
8546
 
7863
8547
  // src/rules/types/types-duplicate.ts
7864
- import { defineRule as defineRule71 } from "@oxlint/plugins";
7865
- var typesDuplicate = defineRule71({
8548
+ import { defineRule as defineRule77 } from "@oxlint/plugins";
8549
+ var typesDuplicate = defineRule77({
7866
8550
  meta: {
7867
8551
  type: "suggestion",
7868
8552
  schema: [
@@ -7926,7 +8610,7 @@ var typesDuplicate = defineRule71({
7926
8610
  });
7927
8611
 
7928
8612
  // src/rules/types/types-imported-alias.ts
7929
- import { defineRule as defineRule72 } from "@oxlint/plugins";
8613
+ import { defineRule as defineRule78 } from "@oxlint/plugins";
7930
8614
  function importedAliasTarget(declaration, imports) {
7931
8615
  if (declaration?.type !== "TSTypeAliasDeclaration") {
7932
8616
  return;
@@ -7938,7 +8622,7 @@ function importedAliasTarget(declaration, imports) {
7938
8622
  const { name } = type.typeName;
7939
8623
  return imports.has(name) && !declaration.typeParameters?.params.some((parameter) => parameter.name.name === name) ? name : undefined;
7940
8624
  }
7941
- var typesImportedAlias = defineRule72({
8625
+ var typesImportedAlias = defineRule78({
7942
8626
  meta: {
7943
8627
  type: "problem",
7944
8628
  schema: [],
@@ -8000,7 +8684,7 @@ var typesImportedAlias = defineRule72({
8000
8684
  });
8001
8685
 
8002
8686
  // src/rules/types/types-inline.ts
8003
- import { defineRule as defineRule73 } from "@oxlint/plugins";
8687
+ import { defineRule as defineRule79 } from "@oxlint/plugins";
8004
8688
  function parameterAnnotation(parameter) {
8005
8689
  if (parameter.type === "TSParameterProperty") {
8006
8690
  return parameterAnnotation(parameter.parameter);
@@ -8010,7 +8694,7 @@ function parameterAnnotation(parameter) {
8010
8694
  }
8011
8695
  return parameter.typeAnnotation;
8012
8696
  }
8013
- var typesInline = defineRule73({
8697
+ var typesInline = defineRule79({
8014
8698
  meta: {
8015
8699
  type: "problem",
8016
8700
  schema: [
@@ -8021,6 +8705,9 @@ var typesInline = defineRule73({
8021
8705
  parameters: { type: "boolean" },
8022
8706
  returns: { type: "boolean" },
8023
8707
  functions: { type: "boolean" },
8708
+ variables: { type: "boolean" },
8709
+ assertions: { type: "boolean" },
8710
+ arguments: { type: "boolean" },
8024
8711
  min: { type: "integer", minimum: 1 }
8025
8712
  }
8026
8713
  }
@@ -8033,6 +8720,9 @@ var typesInline = defineRule73({
8033
8720
  let checkParameters = true;
8034
8721
  let checkReturns = true;
8035
8722
  let checkFunctionTypes = true;
8723
+ let checkVariables = false;
8724
+ let checkAssertions = false;
8725
+ let checkArguments = false;
8036
8726
  let minMembers = 1;
8037
8727
  function isInline(node) {
8038
8728
  if (node.type === "TSTypeLiteral") {
@@ -8068,11 +8758,44 @@ var typesInline = defineRule73({
8068
8758
  checkParameters = options.parameters ?? true;
8069
8759
  checkReturns = options.returns ?? true;
8070
8760
  checkFunctionTypes = options.functions ?? true;
8761
+ checkVariables = options.variables ?? false;
8762
+ checkAssertions = options.assertions ?? false;
8763
+ checkArguments = options.arguments ?? false;
8071
8764
  minMembers = options.min ?? 1;
8072
- if (!checkParameters && !checkReturns) {
8765
+ if (!checkParameters && !checkReturns && !checkVariables && !checkAssertions && !checkArguments) {
8073
8766
  return false;
8074
8767
  }
8075
8768
  },
8769
+ VariableDeclarator(node) {
8770
+ if (checkVariables) {
8771
+ check(parameterAnnotation(node.id), "variable");
8772
+ }
8773
+ },
8774
+ PropertyDefinition(node) {
8775
+ if (checkVariables) {
8776
+ check(node.typeAnnotation, "variable");
8777
+ }
8778
+ },
8779
+ TSAsExpression(node) {
8780
+ if (checkAssertions) {
8781
+ check(node.typeAnnotation, "assertion");
8782
+ }
8783
+ },
8784
+ TSSatisfiesExpression(node) {
8785
+ if (checkAssertions) {
8786
+ check(node.typeAnnotation, "assertion");
8787
+ }
8788
+ },
8789
+ CallExpression(node) {
8790
+ if (checkArguments) {
8791
+ check(node.typeArguments, "type argument");
8792
+ }
8793
+ },
8794
+ NewExpression(node) {
8795
+ if (checkArguments) {
8796
+ check(node.typeArguments, "type argument");
8797
+ }
8798
+ },
8076
8799
  FunctionDeclaration: checkFunction,
8077
8800
  FunctionExpression: checkFunction,
8078
8801
  ArrowFunctionExpression: checkFunction,
@@ -8083,11 +8806,38 @@ var typesInline = defineRule73({
8083
8806
  });
8084
8807
 
8085
8808
  // src/rules/types/types-local-alias.ts
8086
- 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
+ });
8087
8837
 
8088
8838
  // src/rules/types/types-namespace-import.ts
8089
- import { defineRule as defineRule74 } from "@oxlint/plugins";
8090
- var typesNamespaceImport = defineRule74({
8839
+ import { defineRule as defineRule81 } from "@oxlint/plugins";
8840
+ var typesNamespaceImport = defineRule81({
8091
8841
  meta: {
8092
8842
  type: "suggestion",
8093
8843
  schema: [
@@ -8129,8 +8879,8 @@ var typesNamespaceImport = defineRule74({
8129
8879
  });
8130
8880
 
8131
8881
  // src/rules/types/types-service.ts
8132
- import { defineRule as defineRule75 } from "@oxlint/plugins";
8133
- var typesService = defineRule75({
8882
+ import { defineRule as defineRule82 } from "@oxlint/plugins";
8883
+ var typesService = defineRule82({
8134
8884
  meta: {
8135
8885
  type: "problem",
8136
8886
  schema: [
@@ -8180,12 +8930,12 @@ var typesService = defineRule75({
8180
8930
  });
8181
8931
 
8182
8932
  // src/rules/types/types-unknown.ts
8183
- import { defineRule as defineRule76 } from "@oxlint/plugins";
8933
+ import { defineRule as defineRule83 } from "@oxlint/plugins";
8184
8934
  function isCatchName(node) {
8185
8935
  const key = "key" in node && !(("computed" in node) && node.computed) ? node.key : undefined;
8186
8936
  return key?.type === "Identifier" && key.name === "catch";
8187
8937
  }
8188
- var typesUnknown = defineRule76({
8938
+ var typesUnknown = defineRule83({
8189
8939
  meta: { type: "problem", schema: [], messages: { forbidden: "Use an owned domain type rather than unknown." } },
8190
8940
  createOnce(context) {
8191
8941
  return {
@@ -8224,6 +8974,7 @@ var src_default = eslintCompatPlugin({
8224
8974
  "boundaries-token-owner": boundariesTokenOwner,
8225
8975
  "code-branch-once": codeBranchOnce,
8226
8976
  "code-constructor": codeConstructor,
8977
+ "code-delegate": codeDelegate,
8227
8978
  "code-double-negation": codeDoubleNegation,
8228
8979
  "code-function-nesting": codeFunctionNesting,
8229
8980
  "code-inline": codeInline,
@@ -8268,6 +9019,7 @@ var src_default = eslintCompatPlugin({
8268
9019
  "layout-padding": layoutPadding,
8269
9020
  "layout-pipe": layoutPipe,
8270
9021
  "layout-ternary": layoutTernary,
9022
+ "layout-type-arguments": layoutTypeArguments,
8271
9023
  "naming-declarations": namingDeclarations,
8272
9024
  "naming-exports": namingExports,
8273
9025
  "naming-file": namingFile,
@@ -8277,6 +9029,7 @@ var src_default = eslintCompatPlugin({
8277
9029
  "react-empty-effect": reactEmptyEffect,
8278
9030
  "react-memo": reactMemo,
8279
9031
  "react-props": reactProps,
9032
+ "react-query-props": reactQueryProps,
8280
9033
  "schemas-inline": schemasInline,
8281
9034
  "schemas-local": schemasLocal,
8282
9035
  "schemas-rederive": schemasRederive,