oxlint-plugin-arch 0.12.4 → 0.12.5

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
@@ -510,14 +510,15 @@ var backendRegistry = defineRule3({
510
510
  registry: { type: "string", minLength: 1 },
511
511
  services: { type: "string", minLength: 1 },
512
512
  skip: { type: "array", items: { type: "string" } },
513
- object: { type: "string", minLength: 1 }
513
+ object: { type: "string", minLength: 1 },
514
+ file: { type: "string", minLength: 1, default: "{folder}.service.ts" }
514
515
  },
515
516
  required: ["registry", "services"]
516
517
  }
517
518
  ],
518
519
  messages: {
519
520
  unregistered: "Register the {{folder}} service here under '{{key}}'.",
520
- missing: "Domain folder '{{folder}}' needs {{folder}}.service.ts; add it, or list the folder in skip."
521
+ missing: "Domain folder '{{folder}}' needs {{file}}; add it, or list the folder in skip."
521
522
  }
522
523
  },
523
524
  createOnce(context) {
@@ -559,11 +560,12 @@ var backendRegistry = defineRule3({
559
560
  const domains = entries.filter((entry) => entry.isDirectory() && !(options.skip ?? []).includes(entry.name)).map((entry) => entry.name);
560
561
  for (const folder of domains) {
561
562
  const key = namingCamelCase(folder);
562
- if (!existsSync(join(options.services, folder, `${folder}.service.ts`))) {
563
+ const file = (options.file ?? "{folder}.service.ts").replaceAll("{folder}", folder);
564
+ if (!existsSync(join(options.services, folder, file))) {
563
565
  context.report({
564
566
  node: program,
565
567
  messageId: "missing",
566
- data: { folder }
568
+ data: { folder, file }
567
569
  });
568
570
  } else if (![key, `${key}s`, key.replace(/y$/, "ies")].some((name) => keys.has(name))) {
569
571
  context.report({
@@ -1136,11 +1138,14 @@ var boundariesDeny = defineRule6({
1136
1138
  // src/rules/boundaries/boundaries-imports.ts
1137
1139
  import { defineRule as defineRule7 } from "@oxlint/plugins";
1138
1140
  import { basename, resolve as resolve2 } from "node:path";
1139
- var boundariesPublicSurface = /\.(?:service|client|server|rsc|utils)$/;
1141
+ var boundariesSurfaces = ["service", "client", "server", "rsc", "utils"];
1140
1142
  var boundariesActionOrQuery = /\/(?:[^/]+)-(?:action|query)\./;
1141
1143
  function boundariesContains(directory, candidate) {
1142
1144
  return candidate === directory || candidate.startsWith(`${directory}/`);
1143
1145
  }
1146
+ function boundariesIsSurface(options, stem) {
1147
+ return (options.surfaces ?? boundariesSurfaces).some((surface) => stem.endsWith(`.${surface}`));
1148
+ }
1144
1149
  function boundariesStripExtension(path) {
1145
1150
  return path.replace(/\.[cm]?[jt]sx?$/, "");
1146
1151
  }
@@ -1169,12 +1174,12 @@ function boundariesPrivateDomain({ options, file, target, reExport, roots, owner
1169
1174
  const targetDomain = targetOwner ? boundariesDomain(target, targetOwner, boundariesServiceFolder(options, targetOwner)) : undefined;
1170
1175
  const sameDomain = owner && owner === targetOwner && fileDomain && fileDomain === targetDomain;
1171
1176
  const targetStem = boundariesStripExtension(target);
1172
- const publicSurface = boundariesPublicSurface.test(targetStem) && !boundariesActionOrQuery.test(targetStem);
1177
+ const publicSurface = boundariesIsSurface(options, targetStem) && !boundariesActionOrQuery.test(targetStem);
1173
1178
  const privateTarget = roles.some((role) => targetStem.endsWith(`.${role}`)) || targetDomain && !publicSurface && !/\.(?:types|constants)$/.test(targetStem);
1174
1179
  if (!privateTarget) {
1175
1180
  return;
1176
1181
  }
1177
- if (reExport && /\.(?:service|client|server|rsc|utils)\.ts$/.test(file) && !boundariesActionOrQuery.test(file)) {
1182
+ if (reExport && file.endsWith(".ts") && boundariesIsSurface(options, file.slice(0, -".ts".length)) && !boundariesActionOrQuery.test(file)) {
1178
1183
  return "public service and utility surfaces must not re-export private domain internals";
1179
1184
  }
1180
1185
  return sameDomain ? undefined : "private domain files stay in the same domain and application; use its service API or shared utilities";
@@ -1212,6 +1217,7 @@ var boundariesImports = defineRule7({
1212
1217
  backend: { type: "array", items: { type: "string" } },
1213
1218
  packages: { anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }] },
1214
1219
  roles: { type: "array", items: { type: "string" } },
1220
+ surfaces: { type: "array", items: { type: "string", minLength: 1 }, default: boundariesSurfaces },
1215
1221
  services: { type: "array", items: { type: "string" } },
1216
1222
  portable: { type: "boolean" },
1217
1223
  backendPackages: { type: "array", items: { type: "string" } },
@@ -1307,11 +1313,9 @@ var boundariesNextDefaults = {
1307
1313
  serverPackages: [...boundariesServerPackages, "server-only", "next/headers", "next/cache", "next/server"],
1308
1314
  clientPackages: ["client-only", "react-dom/client"],
1309
1315
  hookPackages: ["react"],
1310
- publicEnv: "NEXT_PUBLIC_"
1316
+ publicEnv: "NEXT_PUBLIC_",
1317
+ serverHooks: ["useId", "use"]
1311
1318
  };
1312
- function boundariesIsClientHook(name) {
1313
- return /^use[A-Z]/.test(name) && name !== "useId" && name !== "use";
1314
- }
1315
1319
  var boundariesNext = defineRule8({
1316
1320
  meta: {
1317
1321
  type: "problem",
@@ -1319,7 +1323,11 @@ var boundariesNext = defineRule8({
1319
1323
  {
1320
1324
  type: "object",
1321
1325
  additionalProperties: false,
1322
- properties: { ...boundariesRuntimeProperties, publicEnv: { type: "string" } }
1326
+ properties: {
1327
+ ...boundariesRuntimeProperties,
1328
+ publicEnv: { type: "string" },
1329
+ serverHooks: { type: "array", items: { type: "string", minLength: 1 }, default: boundariesNextDefaults.serverHooks }
1330
+ }
1323
1331
  }
1324
1332
  ],
1325
1333
  messages: {
@@ -1335,6 +1343,7 @@ var boundariesNext = defineRule8({
1335
1343
  let client = false;
1336
1344
  let shared = false;
1337
1345
  const reactNamespaces = new Set;
1346
+ const clientHook = (name) => /^use[A-Z]/.test(name) && !options.serverHooks.includes(name);
1338
1347
  function check(node, source, typeOnly) {
1339
1348
  if (typeOnly) {
1340
1349
  return;
@@ -1398,14 +1407,14 @@ var boundariesNext = defineRule8({
1398
1407
  reactNamespaces.add(specifier);
1399
1408
  continue;
1400
1409
  }
1401
- if (specifier.importKind !== "type" && specifier.imported.type === "Identifier" && boundariesIsClientHook(specifier.imported.name)) {
1410
+ if (specifier.importKind !== "type" && specifier.imported.type === "Identifier" && clientHook(specifier.imported.name)) {
1402
1411
  boundariesReport(context, specifier, specifier.imported.name, boundariesClientHookReason);
1403
1412
  }
1404
1413
  }
1405
1414
  },
1406
1415
  MemberExpression(node) {
1407
1416
  const name = boundariesMemberName(node);
1408
- if (!name || !boundariesIsClientHook(name) || node.object.type !== "Identifier") {
1417
+ if (!name || !clientHook(name) || node.object.type !== "Identifier") {
1409
1418
  return;
1410
1419
  }
1411
1420
  const binding = boundariesDeclaredVariable(context.sourceCode.getScope(node.object), node.object.name)?.defs[0]?.node;
@@ -1449,12 +1458,54 @@ var boundariesNext = defineRule8({
1449
1458
  });
1450
1459
 
1451
1460
  // src/rules/boundaries/boundaries-rpc-database.ts
1452
- var boundariesQueryMethods = "/^(selectFrom|insertInto|updateTable|deleteFrom|mergeInto|executeQuery)$/";
1453
- var boundariesRpcDatabase = astSelectorRule(`CallExpression[callee.computed=false][callee.property.name=${boundariesQueryMethods}], CallExpression[callee.computed=true][callee.property.value=${boundariesQueryMethods}]`, "RPC handlers delegate database work to services.");
1461
+ import { defineRule as defineRule9 } from "@oxlint/plugins";
1462
+ var boundariesRpcDatabaseMethods = ["selectFrom", "insertInto", "updateTable", "deleteFrom", "mergeInto", "executeQuery"];
1463
+ var boundariesRpcDatabase = defineRule9({
1464
+ meta: {
1465
+ type: "problem",
1466
+ schema: [
1467
+ {
1468
+ type: "object",
1469
+ additionalProperties: false,
1470
+ properties: {
1471
+ message: { type: "string" },
1472
+ methods: { type: "array", items: { type: "string", minLength: 1 }, default: boundariesRpcDatabaseMethods }
1473
+ }
1474
+ }
1475
+ ],
1476
+ messages: { forbidden: "{{message}}" }
1477
+ },
1478
+ createOnce(context) {
1479
+ let message = "";
1480
+ let methods = new Set;
1481
+ return {
1482
+ before() {
1483
+ const options = optionsFirst(context, {});
1484
+ message = options.message ?? "RPC handlers delegate database work to services.";
1485
+ methods = new Set(options.methods ?? boundariesRpcDatabaseMethods);
1486
+ },
1487
+ CallExpression(node) {
1488
+ const callee = node.callee;
1489
+ if (callee.type !== "MemberExpression") {
1490
+ return;
1491
+ }
1492
+ const property = callee.property;
1493
+ const literal = property.type === "Literal" && typeof property.value === "string" ? property.value : undefined;
1494
+ const name = callee.computed ? literal : astMemberName(callee);
1495
+ if (name !== undefined && methods.has(name)) {
1496
+ context.report({
1497
+ node,
1498
+ messageId: "forbidden",
1499
+ data: { message }
1500
+ });
1501
+ }
1502
+ }
1503
+ };
1504
+ }
1505
+ });
1454
1506
 
1455
1507
  // src/rules/boundaries/boundaries-tanstack.ts
1456
- import { defineRule as defineRule9 } from "@oxlint/plugins";
1457
- var boundariesRenderers = ["renderServerComponent", "renderToReadableStream"];
1508
+ import { defineRule as defineRule10 } from "@oxlint/plugins";
1458
1509
  var boundariesChainMethods = ["handler", "server", "client", "middleware", "validator", "inputValidator"];
1459
1510
  var boundariesSplitFactories = ["createIsomorphicFn", "createMiddleware"];
1460
1511
  var boundariesRuntimeTypeWrappers = [
@@ -1490,12 +1541,13 @@ var boundariesTanstackDefaults = {
1490
1541
  clientPackages: ["react-dom/client", "@tanstack/react-start/client-only", "@tanstack/solid-start/client-only", "@tanstack/vue-start/client-only"],
1491
1542
  hookPackages: ["react", "@tanstack/react-query", "@tanstack/react-router"],
1492
1543
  startPackages: ["@tanstack/react-start"],
1493
- rscPackages: ["@tanstack/react-start/rsc", "@tanstack/react-start-rsc"]
1544
+ rscPackages: ["@tanstack/react-start/rsc", "@tanstack/react-start-rsc"],
1545
+ renderers: ["renderServerComponent", "renderToReadableStream"]
1494
1546
  };
1495
- function boundariesIsClientHook2(name) {
1547
+ function boundariesIsClientHook(name) {
1496
1548
  return /^use[A-Z]/.test(name) && name !== "useId";
1497
1549
  }
1498
- var boundariesTanstack = defineRule9({
1550
+ var boundariesTanstack = defineRule10({
1499
1551
  meta: {
1500
1552
  type: "problem",
1501
1553
  schema: [
@@ -1506,7 +1558,8 @@ var boundariesTanstack = defineRule9({
1506
1558
  ...boundariesRuntimeProperties,
1507
1559
  rpc: { type: "array", items: { type: "string" } },
1508
1560
  startPackages: { type: "array", items: { type: "string" } },
1509
- rscPackages: { type: "array", items: { type: "string" } }
1561
+ rscPackages: { type: "array", items: { type: "string" } },
1562
+ renderers: { type: "array", items: { type: "string", minLength: 1 }, default: boundariesTanstackDefaults.renderers }
1510
1563
  }
1511
1564
  }
1512
1565
  ],
@@ -1662,7 +1715,7 @@ var boundariesTanstack = defineRule9({
1662
1715
  return;
1663
1716
  }
1664
1717
  for (const specifier of node.specifiers) {
1665
- if (options.rscPackages.includes(node.source.value) && specifier.type === "ImportSpecifier" && specifier.importKind !== "type" && specifier.imported.type === "Identifier" && boundariesRenderers.includes(specifier.imported.name)) {
1718
+ if (options.rscPackages.includes(node.source.value) && specifier.type === "ImportSpecifier" && specifier.importKind !== "type" && specifier.imported.type === "Identifier" && options.renderers.includes(specifier.imported.name)) {
1666
1719
  check(node, node.source.value, false, false, specifier);
1667
1720
  }
1668
1721
  }
@@ -1670,7 +1723,7 @@ var boundariesTanstack = defineRule9({
1670
1723
  return;
1671
1724
  }
1672
1725
  for (const specifier of node.specifiers) {
1673
- if (specifier.type !== "ImportSpecifier" || specifier.importKind === "type" || specifier.imported.type !== "Identifier" || !boundariesIsClientHook2(specifier.imported.name)) {
1726
+ if (specifier.type !== "ImportSpecifier" || specifier.importKind === "type" || specifier.imported.type !== "Identifier" || !boundariesIsClientHook(specifier.imported.name)) {
1674
1727
  continue;
1675
1728
  }
1676
1729
  const references = runtimeReferences(node, specifier.local.name);
@@ -1689,10 +1742,10 @@ var boundariesTanstack = defineRule9({
1689
1742
  return;
1690
1743
  }
1691
1744
  const kind = imported(node.object);
1692
- if (kind === "rsc-renderers" && boundariesRenderers.includes(name) && (shared || !serverPosition(node.start, true))) {
1745
+ if (kind === "rsc-renderers" && options.renderers.includes(name) && (shared || !serverPosition(node.start, true))) {
1693
1746
  boundariesReport(context, node, name, "server renderers require a server execution boundary");
1694
1747
  }
1695
- if ((serverFile || rscFile) && kind === "client-hooks" && boundariesIsClientHook2(name) && !clientSide(node.start)) {
1748
+ if ((serverFile || rscFile) && kind === "client-hooks" && boundariesIsClientHook(name) && !clientSide(node.start)) {
1696
1749
  boundariesReport(context, node, name, boundariesClientHookReason2);
1697
1750
  }
1698
1751
  },
@@ -1724,8 +1777,8 @@ var boundariesTanstack = defineRule9({
1724
1777
  });
1725
1778
 
1726
1779
  // src/rules/boundaries/boundaries-token-owner.ts
1727
- import { defineRule as defineRule10 } from "@oxlint/plugins";
1728
- var boundariesTokenOwner = defineRule10({
1780
+ import { defineRule as defineRule11 } from "@oxlint/plugins";
1781
+ var boundariesTokenOwner = defineRule11({
1729
1782
  meta: {
1730
1783
  type: "problem",
1731
1784
  schema: [
@@ -1820,7 +1873,7 @@ var boundariesTokenOwner = defineRule10({
1820
1873
  });
1821
1874
 
1822
1875
  // src/rules/code/code-branch-once.ts
1823
- import { defineRule as defineRule11 } from "@oxlint/plugins";
1876
+ import { defineRule as defineRule12 } from "@oxlint/plugins";
1824
1877
  function codeBranchOnceSubjects(node, source) {
1825
1878
  if (node.type === "LogicalExpression") {
1826
1879
  return [...codeBranchOnceSubjects(node.left, source), ...codeBranchOnceSubjects(node.right, source)];
@@ -1836,18 +1889,27 @@ function codeBranchOnceSubjects(node, source) {
1836
1889
  const literal = sides.some((side) => side.type === "Literal" && typeof side.value === "string");
1837
1890
  return member && literal ? [source.getText(member)] : [];
1838
1891
  }
1839
- var codeBranchOnce = defineRule11({
1892
+ var codeBranchOnce = defineRule12({
1840
1893
  meta: {
1841
1894
  type: "suggestion",
1895
+ schema: [
1896
+ {
1897
+ type: "object",
1898
+ additionalProperties: false,
1899
+ properties: { min: { type: "integer", minimum: 2, default: 2 } }
1900
+ }
1901
+ ],
1842
1902
  messages: {
1843
1903
  once: "{{subject}} is compared to a string in {{count}} ternaries in this file. Branch on it once and return a record."
1844
1904
  }
1845
1905
  },
1846
1906
  createOnce(context) {
1847
1907
  let groups = new Map;
1908
+ let min = 2;
1848
1909
  return {
1849
1910
  before() {
1850
1911
  groups = new Map;
1912
+ min = optionsFirst(context, {}).min ?? 2;
1851
1913
  },
1852
1914
  ConditionalExpression(node) {
1853
1915
  for (const subject of new Set(codeBranchOnceSubjects(node.test, context.sourceCode))) {
@@ -1856,7 +1918,7 @@ var codeBranchOnce = defineRule11({
1856
1918
  },
1857
1919
  "Program:exit"() {
1858
1920
  for (const [subject, ternaries] of groups) {
1859
- for (const node of ternaries.length > 1 ? ternaries : []) {
1921
+ for (const node of ternaries.length >= min ? ternaries : []) {
1860
1922
  context.report({
1861
1923
  node,
1862
1924
  messageId: "once",
@@ -1870,8 +1932,8 @@ var codeBranchOnce = defineRule11({
1870
1932
  });
1871
1933
 
1872
1934
  // src/rules/code/code-constructor.ts
1873
- import { defineRule as defineRule12 } from "@oxlint/plugins";
1874
- var codeConstructor = defineRule12({
1935
+ import { defineRule as defineRule13 } from "@oxlint/plugins";
1936
+ var codeConstructor = defineRule13({
1875
1937
  meta: {
1876
1938
  type: "problem",
1877
1939
  schema: [
@@ -1955,8 +2017,8 @@ var codeConstructor = defineRule12({
1955
2017
  var codeDoubleNegation = astSelectorRule('UnaryExpression[operator="!"] > UnaryExpression[operator="!"]', "Use Boolean(value) or an explicit comparison.");
1956
2018
 
1957
2019
  // src/rules/code/code-function-nesting.ts
1958
- import { defineRule as defineRule13 } from "@oxlint/plugins";
1959
- var codeFunctionNesting = defineRule13({
2020
+ import { defineRule as defineRule14 } from "@oxlint/plugins";
2021
+ var codeFunctionNesting = defineRule14({
1960
2022
  meta: {
1961
2023
  type: "suggestion",
1962
2024
  schema: [
@@ -1997,7 +2059,7 @@ var codeFunctionNesting = defineRule13({
1997
2059
  });
1998
2060
 
1999
2061
  // src/rules/code/code-inline.ts
2000
- import { defineRule as defineRule14 } from "@oxlint/plugins";
2062
+ import { defineRule as defineRule15 } from "@oxlint/plugins";
2001
2063
 
2002
2064
  // src/utils/source.utils.ts
2003
2065
  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])/;
@@ -2038,6 +2100,9 @@ function sourceLineEnd(text, offset) {
2038
2100
  `, offset);
2039
2101
  return end === -1 ? text.length : end;
2040
2102
  }
2103
+ function sourceJoinedWidth(text, start, joined, end) {
2104
+ return start - sourceLineStart(text, start) + joined.length + text.slice(end, sourceLineEnd(text, end)).trimEnd().length;
2105
+ }
2041
2106
  function sourceItemGaps(open, items, close) {
2042
2107
  const edges = [open, ...items.flatMap((item) => item.range), close];
2043
2108
  return Array.from({ length: items.length + 1 }, (_, index) => [edges[index * 2] ?? open, edges[index * 2 + 1] ?? close]);
@@ -2066,7 +2131,20 @@ function codeInlineEnclosingCalls(node) {
2066
2131
  return outer && astIsOneLine(outer) ? 1 + codeInlineEnclosingCalls(outer) : 0;
2067
2132
  }
2068
2133
  var codeInlineWrapped = new Set(["ConditionalExpression", "LogicalExpression", "BinaryExpression", "ArrowFunctionExpression"]);
2069
- var codeInline = defineRule14({
2134
+ var codeInlineEffects = new Set([
2135
+ "CallExpression",
2136
+ "NewExpression",
2137
+ "AssignmentExpression",
2138
+ "UpdateExpression",
2139
+ "AwaitExpression",
2140
+ "YieldExpression",
2141
+ "TaggedTemplateExpression",
2142
+ "ImportExpression"
2143
+ ]);
2144
+ function codeInlineEffectBetween(program, start, end) {
2145
+ 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))));
2146
+ }
2147
+ var codeInline = defineRule15({
2070
2148
  meta: {
2071
2149
  type: "suggestion",
2072
2150
  schema: [
@@ -2104,6 +2182,9 @@ var codeInline = defineRule14({
2104
2182
  return;
2105
2183
  }
2106
2184
  if (uses.length === 1) {
2185
+ if (init && codeInlineEffectBetween(context.sourceCode.ast, node.range[1], uses[0].identifier.range[0])) {
2186
+ return;
2187
+ }
2107
2188
  context.report({
2108
2189
  node: id,
2109
2190
  messageId: "once",
@@ -2141,7 +2222,7 @@ var codeInline = defineRule14({
2141
2222
  deferred ||= current.type === "FunctionDeclaration" || astIsInlineFunction(current);
2142
2223
  }
2143
2224
  const wrapped = codeInlineWrapped.has(init.type) && use.parent.type === "MemberExpression" && use.parent.object === use;
2144
- if (service || wrapped || deferred || codeInlineEnclosingCalls(use) + astCallDepth(init) > (options.nesting ?? 2) || line.length - id.name.length + context.sourceCode.getText(init).length > (options.max ?? 160)) {
2225
+ 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])) {
2145
2226
  return;
2146
2227
  }
2147
2228
  context.report({
@@ -2176,8 +2257,8 @@ var codeInline = defineRule14({
2176
2257
  });
2177
2258
 
2178
2259
  // src/rules/code/code-justified-call.ts
2179
- import { defineRule as defineRule15 } from "@oxlint/plugins";
2180
- var codeJustifiedCall = defineRule15({
2260
+ import { defineRule as defineRule16 } from "@oxlint/plugins";
2261
+ var codeJustifiedCall = defineRule16({
2181
2262
  meta: {
2182
2263
  type: "problem",
2183
2264
  schema: [
@@ -2231,8 +2312,8 @@ var codeJustifiedCall = defineRule15({
2231
2312
  });
2232
2313
 
2233
2314
  // src/rules/code/code-literal-in.ts
2234
- import { defineRule as defineRule16 } from "@oxlint/plugins";
2235
- var codeLiteralIn = defineRule16({
2315
+ import { defineRule as defineRule17 } from "@oxlint/plugins";
2316
+ var codeLiteralIn = defineRule17({
2236
2317
  meta: {
2237
2318
  type: "suggestion",
2238
2319
  schema: [
@@ -2273,7 +2354,7 @@ var codeLiteralIn = defineRule16({
2273
2354
  });
2274
2355
 
2275
2356
  // src/rules/code/code-mutable-state.ts
2276
- import { defineRule as defineRule17 } from "@oxlint/plugins";
2357
+ import { defineRule as defineRule18 } from "@oxlint/plugins";
2277
2358
  var functionScopes = new Set([
2278
2359
  "FunctionDeclaration",
2279
2360
  "FunctionExpression",
@@ -2290,7 +2371,7 @@ function hasAncestor(node, test) {
2290
2371
  }
2291
2372
  return false;
2292
2373
  }
2293
- var codeMutableState = defineRule17({
2374
+ var codeMutableState = defineRule18({
2294
2375
  meta: {
2295
2376
  type: "problem",
2296
2377
  schema: [
@@ -2348,7 +2429,7 @@ var codeMutableState = defineRule17({
2348
2429
  });
2349
2430
 
2350
2431
  // src/rules/code/code-object-params.ts
2351
- import { defineRule as defineRule18 } from "@oxlint/plugins";
2432
+ import { defineRule as defineRule19 } from "@oxlint/plugins";
2352
2433
  function isObjectParam(node) {
2353
2434
  if (node.type === "ObjectPattern") {
2354
2435
  return true;
@@ -2362,7 +2443,7 @@ function isObjectParam(node) {
2362
2443
  const annotation = node.typeAnnotation?.typeAnnotation;
2363
2444
  return !annotation || annotation.type === "TSTypeReference" || annotation.type === "TSTypeLiteral" || annotation.type === "TSIntersectionType";
2364
2445
  }
2365
- var codeObjectParams = defineRule18({
2446
+ var codeObjectParams = defineRule19({
2366
2447
  meta: {
2367
2448
  type: "problem",
2368
2449
  schema: [
@@ -2411,8 +2492,8 @@ var codeObjectParams = defineRule18({
2411
2492
  });
2412
2493
 
2413
2494
  // src/rules/code/code-paired-call.ts
2414
- import { defineRule as defineRule19 } from "@oxlint/plugins";
2415
- var codePairedCall = defineRule19({
2495
+ import { defineRule as defineRule20 } from "@oxlint/plugins";
2496
+ var codePairedCall = defineRule20({
2416
2497
  meta: {
2417
2498
  type: "problem",
2418
2499
  schema: [
@@ -2443,7 +2524,6 @@ var codePairedCall = defineRule19({
2443
2524
  }
2444
2525
  },
2445
2526
  createOnce(context) {
2446
- const aliases = new Map;
2447
2527
  let pairs = [];
2448
2528
  return {
2449
2529
  before() {
@@ -2451,26 +2531,10 @@ var codePairedCall = defineRule19({
2451
2531
  const all = when && require2 ? [{ when, require: require2 }, ...configured] : configured;
2452
2532
  pairs = all.map((pair) => ({ ...pair, firstWhen: null, sawRequired: false }));
2453
2533
  },
2454
- Program(program) {
2455
- aliases.clear();
2456
- for (const statement of program.body) {
2457
- if (statement.type !== "ImportDeclaration") {
2458
- continue;
2459
- }
2460
- for (const specifier of statement.specifiers) {
2461
- if (specifier.type !== "ImportSpecifier") {
2462
- continue;
2463
- }
2464
- const imported = astModuleName(specifier.imported);
2465
- if (imported !== specifier.local.name) {
2466
- aliases.set(specifier.local.name, imported);
2467
- }
2468
- }
2469
- }
2470
- },
2471
2534
  CallExpression(node) {
2472
- const called = astCallName(node);
2473
- const name = called ? aliases.get(called) ?? called : undefined;
2535
+ const callee = node.callee;
2536
+ const binding = callee.type === "Identifier" ? boundariesDeclaredVariable(context.sourceCode.getScope(node), callee.name)?.defs.at(0)?.node : undefined;
2537
+ const name = binding?.type === "ImportSpecifier" ? astModuleName(binding.imported) : astCallName(node);
2474
2538
  for (const pair of pairs) {
2475
2539
  if (name === pair.when) {
2476
2540
  pair.firstWhen ??= node;
@@ -2496,7 +2560,7 @@ var codePairedCall = defineRule19({
2496
2560
  });
2497
2561
 
2498
2562
  // src/rules/code/code-promise-mutation.ts
2499
- import { defineRule as defineRule20 } from "@oxlint/plugins";
2563
+ import { defineRule as defineRule21 } from "@oxlint/plugins";
2500
2564
  function mutationBinding(node) {
2501
2565
  if (node.type === "Identifier") {
2502
2566
  return node;
@@ -2525,7 +2589,7 @@ function mutationBindings(node) {
2525
2589
  const binding = mutationBinding(node);
2526
2590
  return binding ? [binding] : [];
2527
2591
  }
2528
- var codePromiseMutation = defineRule20({
2592
+ var codePromiseMutation = defineRule21({
2529
2593
  meta: {
2530
2594
  type: "problem",
2531
2595
  schema: [
@@ -2618,8 +2682,8 @@ var codePromiseMutation = defineRule20({
2618
2682
  var codeSwitch = astSelectorRule("SwitchStatement", "Avoid switch statements.");
2619
2683
 
2620
2684
  // src/rules/code/code-traced.ts
2621
- import { defineRule as defineRule21 } from "@oxlint/plugins";
2622
- var codeTraced = defineRule21({
2685
+ import { defineRule as defineRule22 } from "@oxlint/plugins";
2686
+ var codeTraced = defineRule22({
2623
2687
  meta: {
2624
2688
  type: "problem",
2625
2689
  schema: [
@@ -2668,17 +2732,17 @@ var codeTraced = defineRule21({
2668
2732
  message = options.message ?? `An exported function or service method gets a span: replace ${[...untraced].join(", ")} with its traced form.`;
2669
2733
  },
2670
2734
  ExportNamedDeclaration(node) {
2671
- if (!exported || node.declaration?.type !== "VariableDeclaration") {
2735
+ if (!exported) {
2672
2736
  return;
2673
2737
  }
2674
- for (const declarator of node.declaration.declarations) {
2675
- if (isUntraced(declarator.init)) {
2676
- context.report({
2677
- node: declarator,
2678
- messageId: "surface",
2679
- data: { message }
2680
- });
2681
- }
2738
+ const declarators = node.declaration?.type === "VariableDeclaration" ? node.declaration.declarations.filter((declarator) => isUntraced(declarator.init)) : [];
2739
+ const specifiers = node.source ? [] : node.specifiers.filter((specifier) => resolves(specifier.local));
2740
+ for (const surface of [...declarators, ...specifiers]) {
2741
+ context.report({
2742
+ node: surface,
2743
+ messageId: "surface",
2744
+ data: { message }
2745
+ });
2682
2746
  }
2683
2747
  },
2684
2748
  CallExpression(node) {
@@ -2706,7 +2770,7 @@ var codeTraced = defineRule21({
2706
2770
  });
2707
2771
 
2708
2772
  // src/rules/code/code-trivial-functions.ts
2709
- import { defineRule as defineRule22 } from "@oxlint/plugins";
2773
+ import { defineRule as defineRule23 } from "@oxlint/plugins";
2710
2774
  var codeTrivialFunctionsBanned = [
2711
2775
  "isRecord",
2712
2776
  "isPlainObject",
@@ -2836,7 +2900,7 @@ function guardStatement(node, state) {
2836
2900
  return guardFallback(declaration.init);
2837
2901
  });
2838
2902
  }
2839
- var codeTrivialFunctions = defineRule22({
2903
+ var codeTrivialFunctions = defineRule23({
2840
2904
  meta: {
2841
2905
  type: "problem",
2842
2906
  schema: [
@@ -2967,8 +3031,8 @@ var codeTrivialFunctions = defineRule22({
2967
3031
  });
2968
3032
 
2969
3033
  // src/rules/code/code-yield-argument.ts
2970
- import { defineRule as defineRule23 } from "@oxlint/plugins";
2971
- var codeYieldArgument = defineRule23({
3034
+ import { defineRule as defineRule24 } from "@oxlint/plugins";
3035
+ var codeYieldArgument = defineRule24({
2972
3036
  meta: {
2973
3037
  type: "suggestion",
2974
3038
  messages: {
@@ -2990,7 +3054,7 @@ var codeYieldArgument = defineRule23({
2990
3054
  });
2991
3055
 
2992
3056
  // src/rules/comments/comments-code.ts
2993
- import { defineRule as defineRule24 } from "@oxlint/plugins";
3057
+ import { defineRule as defineRule25 } from "@oxlint/plugins";
2994
3058
  var commentsLineGap = /^[\t \r\n\u2028\u2029]*$/;
2995
3059
  var commentsLineTerminator = /\r\n|[\n\r\u2028\u2029]/;
2996
3060
  var commentsUnicodeTerminator = /[\u2028\u2029]/;
@@ -3041,7 +3105,7 @@ function commentsFollowsOpeningBrace(text, offset) {
3041
3105
  }
3042
3106
  return false;
3043
3107
  }
3044
- var commentsCode = defineRule24({
3108
+ var commentsCode = defineRule25({
3045
3109
  meta: {
3046
3110
  type: "suggestion",
3047
3111
  fixable: "code",
@@ -3050,7 +3114,7 @@ var commentsCode = defineRule24({
3050
3114
  type: "object",
3051
3115
  additionalProperties: false,
3052
3116
  properties: {
3053
- why: { type: "boolean", default: true },
3117
+ why: { anyOf: [{ type: "boolean" }, { type: "string", minLength: 1 }], default: true },
3054
3118
  allow: { type: "array", items: { type: "string" } },
3055
3119
  jsdoc: { type: "boolean", default: false },
3056
3120
  members: { type: "string", enum: ["off", "also", "only"] }
@@ -3064,6 +3128,7 @@ var commentsCode = defineRule24({
3064
3128
  },
3065
3129
  createOnce(context) {
3066
3130
  let allowWhy = true;
3131
+ let whyPrefix = "Why: ";
3067
3132
  let allowJsdoc = false;
3068
3133
  let allowed = [];
3069
3134
  let members = "off";
@@ -3088,7 +3153,8 @@ var commentsCode = defineRule24({
3088
3153
  return {
3089
3154
  before() {
3090
3155
  const options = optionsFirst(context, {});
3091
- allowWhy = options.why ?? true;
3156
+ allowWhy = options.why !== false;
3157
+ whyPrefix = `${typeof options.why === "string" ? options.why.trim() : "Why:"} `;
3092
3158
  allowJsdoc = options.jsdoc ?? false;
3093
3159
  allowed = (options.allow ?? []).map((pattern) => new RegExp(pattern));
3094
3160
  members = options.members ?? "off";
@@ -3117,7 +3183,7 @@ var commentsCode = defineRule24({
3117
3183
  mightContainJsxComment = true;
3118
3184
  }
3119
3185
  const followsPrevious = previous !== undefined && comment.loc.start.line === previous.loc.end.line + 1 && commentsLineGap.test(text.slice(previous.range[1], comment.range[0]));
3120
- const isWhy = value.startsWith("Why: ") || comment.type === "Line" && whyContinuation && followsPrevious;
3186
+ const isWhy = value.startsWith(whyPrefix) || comment.type === "Line" && whyContinuation && followsPrevious;
3121
3187
  if (/^(?:eslint|oxlint)-disable-next-line\b/.test(value)) {
3122
3188
  protectedLines.push(comment.loc.end.line + 1);
3123
3189
  }
@@ -3205,9 +3271,9 @@ var commentsCode = defineRule24({
3205
3271
  });
3206
3272
 
3207
3273
  // src/rules/comments/comments-directives.ts
3208
- import { defineRule as defineRule25 } from "@oxlint/plugins";
3274
+ import { defineRule as defineRule26 } from "@oxlint/plugins";
3209
3275
  var directive = /^\s*(?:oxlint|eslint)-disable(?:-next-line|-line)?\b(.*)$/s;
3210
- var commentsDirectives = defineRule25({
3276
+ var commentsDirectives = defineRule26({
3211
3277
  meta: {
3212
3278
  type: "problem",
3213
3279
  schema: [],
@@ -3235,9 +3301,9 @@ var commentsDirectives = defineRule25({
3235
3301
  });
3236
3302
 
3237
3303
  // src/rules/copy/copy-dashes.ts
3238
- import { defineRule as defineRule26 } from "@oxlint/plugins";
3304
+ import { defineRule as defineRule27 } from "@oxlint/plugins";
3239
3305
  var copyDashesMessage = "Use a comma, colon, period or parentheses instead.";
3240
- var copyDashes = defineRule26({
3306
+ var copyDashes = defineRule27({
3241
3307
  meta: {
3242
3308
  type: "suggestion",
3243
3309
  schema: [
@@ -3293,7 +3359,7 @@ var copyDashes = defineRule26({
3293
3359
  });
3294
3360
 
3295
3361
  // src/rules/files/files-components-only.ts
3296
- import { defineRule as defineRule27 } from "@oxlint/plugins";
3362
+ import { defineRule as defineRule28 } from "@oxlint/plugins";
3297
3363
 
3298
3364
  // src/utils/react.utils.ts
3299
3365
  function reactReturnsOutput(program, node) {
@@ -3365,7 +3431,7 @@ function reactIsComponent(program, candidate) {
3365
3431
  function componentName(basename2) {
3366
3432
  return basename2.replace(/\.(tsx?|jsx?)$/, "").replaceAll(/(?:^[-.]*|[-.]+)(.?)/g, (_, char) => char.toUpperCase());
3367
3433
  }
3368
- var filesComponentsOnly = defineRule27({
3434
+ var filesComponentsOnly = defineRule28({
3369
3435
  meta: {
3370
3436
  type: "problem",
3371
3437
  schema: [
@@ -3373,7 +3439,7 @@ var filesComponentsOnly = defineRule27({
3373
3439
  type: "object",
3374
3440
  additionalProperties: false,
3375
3441
  properties: {
3376
- filename: { type: "boolean" },
3442
+ filename: { oneOf: [{ type: "boolean" }, { type: "string", enum: ["each"] }] },
3377
3443
  roles: { type: "array", items: { type: "string" } },
3378
3444
  types: { type: "boolean" },
3379
3445
  deny: optionsPatternSchema
@@ -3434,10 +3500,13 @@ var filesComponentsOnly = defineRule27({
3434
3500
  return;
3435
3501
  }
3436
3502
  const names = components.map((candidate) => candidate.name.toLowerCase());
3437
- if (expectedKeys.some((key) => names.includes(key) || names.every((name) => name.startsWith(key)))) {
3503
+ if (matchFileName !== "each" && expectedKeys.some((key) => names.includes(key) || names.every((name) => name.startsWith(key)))) {
3438
3504
  return;
3439
3505
  }
3440
3506
  for (const { name, node } of components) {
3507
+ if (matchFileName === "each" && expectedKeys.some((key) => name.toLowerCase().startsWith(key))) {
3508
+ continue;
3509
+ }
3441
3510
  context.report({
3442
3511
  node,
3443
3512
  messageId: "nameMismatch",
@@ -3450,7 +3519,7 @@ var filesComponentsOnly = defineRule27({
3450
3519
  });
3451
3520
 
3452
3521
  // src/rules/files/files-constants-only.ts
3453
- import { defineRule as defineRule28 } from "@oxlint/plugins";
3522
+ import { defineRule as defineRule29 } from "@oxlint/plugins";
3454
3523
  function unwrap(expression) {
3455
3524
  if (expression.type === "ParenthesizedExpression" || expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSTypeAssertion" || expression.type === "TSNonNullExpression" || expression.type === "TSInstantiationExpression") {
3456
3525
  return unwrap(expression.expression);
@@ -3505,7 +3574,7 @@ function collectBindings(program) {
3505
3574
  }
3506
3575
  return bindings;
3507
3576
  }
3508
- var filesConstantsOnly = defineRule28({
3577
+ var filesConstantsOnly = defineRule29({
3509
3578
  meta: {
3510
3579
  type: "problem",
3511
3580
  schema: [
@@ -3604,7 +3673,7 @@ var filesConstantsOnly = defineRule28({
3604
3673
  });
3605
3674
 
3606
3675
  // src/rules/files/files-domain-constants.ts
3607
- import { defineRule as defineRule29 } from "@oxlint/plugins";
3676
+ import { defineRule as defineRule30 } from "@oxlint/plugins";
3608
3677
  import { basename as basename2, dirname as dirname2 } from "node:path";
3609
3678
  function isData(expression) {
3610
3679
  const value = astUnwrap(expression);
@@ -3622,14 +3691,21 @@ function isData(expression) {
3622
3691
  }
3623
3692
  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;
3624
3693
  }
3625
- var filesDomainConstants = defineRule29({
3694
+ var filesDomainConstants = defineRule30({
3626
3695
  meta: {
3627
3696
  type: "problem",
3628
3697
  schema: [
3629
3698
  {
3630
3699
  type: "object",
3631
3700
  additionalProperties: false,
3632
- properties: { data: { type: "boolean" }, services: { type: "boolean" } }
3701
+ properties: {
3702
+ data: { type: "boolean" },
3703
+ services: { type: "boolean" },
3704
+ pattern: { ...optionsPatternSchema, default: "^[A-Z][A-Z0-9_]+$" },
3705
+ folders: { type: "array", items: { type: "string", minLength: 1 }, default: ["services"] },
3706
+ roles: { type: "array", items: { type: "string", minLength: 1 }, default: ["action", "query"] },
3707
+ file: { type: "string", minLength: 1, default: "{domain}.constants.ts" }
3708
+ }
3633
3709
  }
3634
3710
  ],
3635
3711
  messages: {
@@ -3638,11 +3714,19 @@ var filesDomainConstants = defineRule29({
3638
3714
  },
3639
3715
  createOnce(context) {
3640
3716
  let includeData = false;
3717
+ let names = [];
3718
+ let folders = [];
3719
+ let roles = [];
3720
+ let target = "";
3641
3721
  let serviceObjects = new Set;
3642
3722
  return {
3643
3723
  Program(program) {
3644
3724
  const options = optionsFirst(context, {});
3645
3725
  includeData = Boolean(options.data);
3726
+ names = optionsPatterns(options.pattern ?? "^[A-Z][A-Z0-9_]+$");
3727
+ folders = options.folders ?? ["services"];
3728
+ roles = options.roles ?? ["action", "query"];
3729
+ target = options.file ?? "{domain}.constants.ts";
3646
3730
  serviceObjects = new Set(options.services ? declarationsPublicServiceMethods(program).map((method) => method.binding) : []);
3647
3731
  },
3648
3732
  VariableDeclarator(node) {
@@ -3652,15 +3736,19 @@ var filesDomainConstants = defineRule29({
3652
3736
  return;
3653
3737
  }
3654
3738
  const name = node.id.type === "Identifier" ? node.id.name : undefined;
3655
- if ((!name || !/^[A-Z][A-Z0-9_]+$/.test(name)) && (!includeData || !node.init || !isData(node.init))) {
3739
+ if ((!name || !optionsPatternsTest(names, name)) && (!includeData || !node.init || !isData(node.init))) {
3656
3740
  return;
3657
3741
  }
3658
3742
  const file = context.filename.replaceAll("\\", "/");
3659
- const owner = file.includes("/services/") && dirname2(file).split("/").at(-1) !== "services" ? basename2(dirname2(file)) : basename2(file).split(".")[0].replace(/-(?:action|query)$/, "");
3743
+ const folder = basename2(dirname2(file));
3744
+ const stem = basename2(file).split(".")[0];
3745
+ const role = roles.find((candidate) => stem.endsWith(`-${candidate}`));
3746
+ const domain = role ? stem.slice(0, -role.length - 1) : stem;
3747
+ const owner = folders.some((candidate) => file.includes(`/${candidate}/`) && folder !== candidate) ? folder : domain;
3660
3748
  context.report({
3661
3749
  node,
3662
3750
  messageId: "forbidden",
3663
- data: { name: name ?? "<destructured binding>", file: `${owner}.constants.ts` }
3751
+ data: { name: name ?? "<destructured binding>", file: target.replaceAll("{domain}", owner) }
3664
3752
  });
3665
3753
  }
3666
3754
  };
@@ -3668,11 +3756,11 @@ var filesDomainConstants = defineRule29({
3668
3756
  });
3669
3757
 
3670
3758
  // src/rules/files/files-exports.ts
3671
- import { defineRule as defineRule30 } from "@oxlint/plugins";
3759
+ import { defineRule as defineRule31 } from "@oxlint/plugins";
3672
3760
  function escapeRegExp(value) {
3673
3761
  return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
3674
3762
  }
3675
- var filesExports = defineRule30({
3763
+ var filesExports = defineRule31({
3676
3764
  meta: {
3677
3765
  type: "problem",
3678
3766
  schema: [
@@ -3753,11 +3841,12 @@ var filesExports = defineRule30({
3753
3841
  });
3754
3842
 
3755
3843
  // src/rules/files/files-externals.ts
3756
- import { defineRule as defineRule31 } from "@oxlint/plugins";
3844
+ import { defineRule as defineRule32 } from "@oxlint/plugins";
3757
3845
  import { existsSync as existsSync2, readFileSync } from "node:fs";
3758
3846
  import { builtinModules as builtinModules2 } from "node:module";
3759
3847
  import { dirname as dirname3, join as join2 } from "node:path";
3760
- var filesExternals = defineRule31({
3848
+ var filesExternalsConfigs = "^(?:vite|next)\\.config\\.[cm]?[jt]s$";
3849
+ var filesExternals = defineRule32({
3761
3850
  meta: {
3762
3851
  type: "problem",
3763
3852
  schema: [
@@ -3765,7 +3854,8 @@ var filesExternals = defineRule31({
3765
3854
  type: "object",
3766
3855
  additionalProperties: false,
3767
3856
  properties: {
3768
- packages: { type: "array", items: { type: "string", minLength: 1 } }
3857
+ packages: { type: "array", items: { type: "string", minLength: 1 } },
3858
+ files: { ...optionsPatternSchema, default: filesExternalsConfigs }
3769
3859
  }
3770
3860
  }
3771
3861
  ],
@@ -3821,7 +3911,8 @@ var filesExternals = defineRule31({
3821
3911
  }
3822
3912
  return {
3823
3913
  before() {
3824
- if (!/^(?:vite|next)\.config\.[cm]?[jt]s$/.test(namingBasename(context.filename))) {
3914
+ const files = optionsPatterns(optionsFirst(context, {}).files ?? filesExternalsConfigs);
3915
+ if (!optionsPatternsTest(files, namingBasename(context.filename))) {
3825
3916
  return false;
3826
3917
  }
3827
3918
  },
@@ -3848,7 +3939,7 @@ var filesExternals = defineRule31({
3848
3939
  });
3849
3940
 
3850
3941
  // src/rules/files/files-factory-keys.ts
3851
- import { defineRule as defineRule32 } from "@oxlint/plugins";
3942
+ import { defineRule as defineRule33 } from "@oxlint/plugins";
3852
3943
  function returnedObject(expression) {
3853
3944
  if (expression?.type === "TSAsExpression" || expression?.type === "TSSatisfiesExpression" || expression?.type === "TSTypeAssertion" || expression?.type === "TSNonNullExpression") {
3854
3945
  return returnedObject(expression.expression);
@@ -3859,7 +3950,7 @@ function compileKeys(pattern, keys, requireKeys) {
3859
3950
  const allowedList = keys.concat(requireKeys.filter((key) => !keys.includes(key)));
3860
3951
  return { patterns: optionsPatternsWithEmpty(pattern), allowed: new Set(allowedList), allowedList, required: requireKeys };
3861
3952
  }
3862
- var filesFactoryKeys = defineRule32({
3953
+ var filesFactoryKeys = defineRule33({
3863
3954
  meta: {
3864
3955
  type: "problem",
3865
3956
  schema: [
@@ -3977,8 +4068,8 @@ var filesFactoryKeys = defineRule32({
3977
4068
  });
3978
4069
 
3979
4070
  // src/rules/files/files-helpers.ts
3980
- import { defineRule as defineRule33 } from "@oxlint/plugins";
3981
- var filesHelpers = defineRule33({
4071
+ import { defineRule as defineRule34 } from "@oxlint/plugins";
4072
+ var filesHelpers = defineRule34({
3982
4073
  meta: {
3983
4074
  type: "problem",
3984
4075
  schema: [
@@ -4026,8 +4117,8 @@ var filesHelpers = defineRule33({
4026
4117
  });
4027
4118
 
4028
4119
  // src/rules/files/files-no-functions.ts
4029
- import { defineRule as defineRule34 } from "@oxlint/plugins";
4030
- var filesNoFunctions = defineRule34({
4120
+ import { defineRule as defineRule35 } from "@oxlint/plugins";
4121
+ var filesNoFunctions = defineRule35({
4031
4122
  meta: {
4032
4123
  type: "problem",
4033
4124
  schema: [
@@ -4081,8 +4172,8 @@ var filesNoFunctions = defineRule34({
4081
4172
  });
4082
4173
 
4083
4174
  // src/rules/files/files-restricted.ts
4084
- import { defineRule as defineRule35 } from "@oxlint/plugins";
4085
- var filesRestricted = defineRule35({
4175
+ import { defineRule as defineRule36 } from "@oxlint/plugins";
4176
+ var filesRestricted = defineRule36({
4086
4177
  meta: {
4087
4178
  type: "problem",
4088
4179
  schema: [
@@ -4114,7 +4205,7 @@ var filesRestricted = defineRule35({
4114
4205
  });
4115
4206
 
4116
4207
  // src/rules/files/files-route.ts
4117
- import { defineRule as defineRule36 } from "@oxlint/plugins";
4208
+ import { defineRule as defineRule37 } from "@oxlint/plugins";
4118
4209
  function inspectAdapter(context, item, bannedHooks, banIntrinsicJsx) {
4119
4210
  astVisit(item.node.body, [item.node], (node) => {
4120
4211
  if (banIntrinsicJsx && node.type === "JSXOpeningElement" && node.name.type === "JSXIdentifier" && /^[a-z]/.test(node.name.name)) {
@@ -4134,7 +4225,7 @@ function inspectAdapter(context, item, bannedHooks, banIntrinsicJsx) {
4134
4225
  }
4135
4226
  });
4136
4227
  }
4137
- var filesRoute = defineRule36({
4228
+ var filesRoute = defineRule37({
4138
4229
  meta: {
4139
4230
  type: "problem",
4140
4231
  schema: [
@@ -4204,7 +4295,7 @@ var filesRoute = defineRule36({
4204
4295
  });
4205
4296
 
4206
4297
  // src/rules/files/files-types-only.ts
4207
- import { defineRule as defineRule37 } from "@oxlint/plugins";
4298
+ import { defineRule as defineRule38 } from "@oxlint/plugins";
4208
4299
  function runtimeImportLocalNames(statement) {
4209
4300
  return statement.specifiers.flatMap((specifier) => specifier.type !== "ImportSpecifier" || specifier.importKind !== "type" ? [specifier.local.name] : []);
4210
4301
  }
@@ -4373,7 +4464,7 @@ function runtimeKind(node) {
4373
4464
  }
4374
4465
  return node.type === "FunctionDeclaration" ? "function" : "variable";
4375
4466
  }
4376
- var filesTypesOnly = defineRule37({
4467
+ var filesTypesOnly = defineRule38({
4377
4468
  meta: {
4378
4469
  type: "problem",
4379
4470
  schema: [
@@ -4481,8 +4572,8 @@ var filesTypesOnly = defineRule37({
4481
4572
  });
4482
4573
 
4483
4574
  // src/rules/layout/layout-arrow-body.ts
4484
- import { defineRule as defineRule38 } from "@oxlint/plugins";
4485
- var layoutArrowBody = defineRule38({
4575
+ import { defineRule as defineRule39 } from "@oxlint/plugins";
4576
+ var layoutArrowBody = defineRule39({
4486
4577
  meta: {
4487
4578
  type: "layout",
4488
4579
  fixable: "whitespace",
@@ -4534,12 +4625,12 @@ var layoutArrowBody = defineRule38({
4534
4625
  });
4535
4626
 
4536
4627
  // src/rules/layout/layout-call-arguments.ts
4537
- import { defineRule as defineRule39 } from "@oxlint/plugins";
4628
+ import { defineRule as defineRule40 } from "@oxlint/plugins";
4538
4629
  function layoutCallArgumentsHuggable(node) {
4539
4630
  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);
4540
4631
  }
4541
4632
  var layoutCallArgumentsMax = { type: "integer", minimum: 1 };
4542
- var layoutCallArguments = defineRule39({
4633
+ var layoutCallArguments = defineRule40({
4543
4634
  meta: {
4544
4635
  type: "layout",
4545
4636
  fixable: "whitespace",
@@ -4553,11 +4644,7 @@ var layoutCallArguments = defineRule39({
4553
4644
  minimum: 2,
4554
4645
  default: 3
4555
4646
  },
4556
- indent: {
4557
- type: "integer",
4558
- minimum: 1,
4559
- default: 2
4560
- },
4647
+ indent: { ...sourceIndentSchema, default: 2 },
4561
4648
  split: {
4562
4649
  type: "boolean",
4563
4650
  default: true
@@ -4575,6 +4662,7 @@ var layoutCallArguments = defineRule39({
4575
4662
  properties: {
4576
4663
  max: layoutCallArgumentsMax,
4577
4664
  nesting: layoutCallArgumentsMax,
4665
+ items: layoutCallArgumentsMax,
4578
4666
  keep: { type: "array", items: { type: "string" } }
4579
4667
  }
4580
4668
  }
@@ -4591,7 +4679,7 @@ var layoutCallArguments = defineRule39({
4591
4679
  createOnce(context) {
4592
4680
  let options = {};
4593
4681
  let minArguments = 3;
4594
- let indent = 2;
4682
+ let indentUnit = sourceIndentUnit();
4595
4683
  const split = (node) => {
4596
4684
  const args = node.arguments;
4597
4685
  const last = args.at(-1);
@@ -4615,7 +4703,7 @@ var layoutCallArguments = defineRule39({
4615
4703
  const unsafe = source.getCommentsInside(node).length > 0 || text[open] !== "(" || text[close] !== ")" || !/^\s*$/.test(between[0]) || between.slice(1, -1).some((gap) => !/^\s*,\s*$/.test(gap)) || !/^\s*,?\s*$/.test(between.at(-1) ?? "");
4616
4704
  const baseIndent = sourceLineIndent(text, open);
4617
4705
  const newline = sourceNewline(text);
4618
- const argumentIndent = `${baseIndent}${" ".repeat(indent)}`;
4706
+ const argumentIndent = `${baseIndent}${indentUnit}`;
4619
4707
  context.report({
4620
4708
  node,
4621
4709
  messageId: "multiline",
@@ -4660,7 +4748,7 @@ var layoutCallArguments = defineRule39({
4660
4748
  }
4661
4749
  const newline = sourceNewline(text);
4662
4750
  const base = sourceLineIndent(text, open.range[0]);
4663
- const inner = `${base}${" ".repeat(indent)}`;
4751
+ const inner = `${base}${indentUnit}`;
4664
4752
  context.report({
4665
4753
  node,
4666
4754
  messageId: "hug",
@@ -4671,16 +4759,14 @@ var layoutCallArguments = defineRule39({
4671
4759
  const source = context.sourceCode;
4672
4760
  const text = source.text;
4673
4761
  const first = items.at(0);
4674
- if (!first || !open || !close || items.length > 2 || first.loc.start.line === open.loc.end.line || !items.every(astIsOneLine) || source.commentsExistBetween(open, close)) {
4762
+ if (!first || !open || !close || items.length > Math.min(options.join?.items ?? 2, options.split === false ? Number.POSITIVE_INFINITY : minArguments - 1) || first.loc.start.line === open.loc.end.line || !items.every(astIsOneLine) || source.commentsExistBetween(open, close)) {
4675
4763
  return;
4676
4764
  }
4677
- const inside = items.map((item) => source.getText(item)).join(", ");
4678
- const before = text.slice(sourceLineStart(text, open.range[0]), open.range[1]);
4679
- const after = text.slice(close.range[0], sourceLineEnd(text, close.range[0]));
4680
- if (`${before}${inside}${after}`.length > (options.join?.max ?? 0)) {
4765
+ const gaps = sourceItemGaps(open.range[1], items, close.range[0]);
4766
+ const comma = text.slice(...gaps.at(-1) ?? [0, 0]).includes(",") ? "," : "";
4767
+ if (sourceJoinedWidth(text, open.range[1], `${items.map((item) => source.getText(item)).join(", ")}${comma}`, close.range[0]) > (options.join?.max ?? 0)) {
4681
4768
  return;
4682
4769
  }
4683
- const gaps = sourceItemGaps(open.range[1], items, close.range[0]);
4684
4770
  context.report({
4685
4771
  node,
4686
4772
  messageId: "join",
@@ -4712,7 +4798,7 @@ var layoutCallArguments = defineRule39({
4712
4798
  before() {
4713
4799
  options = optionsFirst(context, {});
4714
4800
  minArguments = options.min ?? 3;
4715
- indent = options.indent ?? 2;
4801
+ indentUnit = sourceIndentUnit(options.indent);
4716
4802
  },
4717
4803
  CallExpression: call,
4718
4804
  NewExpression: call,
@@ -4726,12 +4812,12 @@ var layoutCallArguments = defineRule39({
4726
4812
  });
4727
4813
 
4728
4814
  // src/rules/layout/layout-call-array.ts
4729
- import { defineRule as defineRule40 } from "@oxlint/plugins";
4815
+ import { defineRule as defineRule41 } from "@oxlint/plugins";
4730
4816
  var layoutCallArrayDefaultCallees = ["Promise.all", "Promise.allSettled"];
4731
4817
  function layoutCallArrayRoot(node) {
4732
4818
  return node.type === "MemberExpression" ? layoutCallArrayRoot(node.object) : node;
4733
4819
  }
4734
- var layoutCallArray = defineRule40({
4820
+ var layoutCallArray = defineRule41({
4735
4821
  meta: {
4736
4822
  type: "layout",
4737
4823
  fixable: "whitespace",
@@ -4847,7 +4933,7 @@ var layoutCallArray = defineRule40({
4847
4933
  });
4848
4934
 
4849
4935
  // src/rules/layout/layout-call-depth.ts
4850
- import { defineRule as defineRule41 } from "@oxlint/plugins";
4936
+ import { defineRule as defineRule42 } from "@oxlint/plugins";
4851
4937
  function layoutCallDepthOuter(node) {
4852
4938
  const parent = node.parent;
4853
4939
  if (!parent || parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression" || parent.type.endsWith("Statement")) {
@@ -4855,7 +4941,7 @@ function layoutCallDepthOuter(node) {
4855
4941
  }
4856
4942
  return astIsCall(parent) && parent.arguments.some((argument) => argument === node) ? parent : layoutCallDepthOuter(parent);
4857
4943
  }
4858
- var layoutCallDepth = defineRule41({
4944
+ var layoutCallDepth = defineRule42({
4859
4945
  meta: {
4860
4946
  type: "layout",
4861
4947
  schema: [
@@ -4894,8 +4980,8 @@ var layoutCallDepth = defineRule41({
4894
4980
  });
4895
4981
 
4896
4982
  // src/rules/layout/layout-chain.ts
4897
- import { defineRule as defineRule42 } from "@oxlint/plugins";
4898
- var layoutChain = defineRule42({
4983
+ import { defineRule as defineRule43 } from "@oxlint/plugins";
4984
+ var layoutChain = defineRule43({
4899
4985
  meta: {
4900
4986
  type: "layout",
4901
4987
  fixable: "whitespace",
@@ -5014,8 +5100,8 @@ var layoutChain = defineRule42({
5014
5100
  });
5015
5101
 
5016
5102
  // src/rules/layout/layout-items.ts
5017
- import { defineRule as defineRule43 } from "@oxlint/plugins";
5018
- var layoutItems = defineRule43({
5103
+ import { defineRule as defineRule44 } from "@oxlint/plugins";
5104
+ var layoutItems = defineRule44({
5019
5105
  meta: {
5020
5106
  type: "layout",
5021
5107
  fixable: "whitespace",
@@ -5070,8 +5156,8 @@ var layoutItems = defineRule43({
5070
5156
  });
5071
5157
 
5072
5158
  // src/rules/layout/layout-jsx-attributes.ts
5073
- import { defineRule as defineRule44 } from "@oxlint/plugins";
5074
- var layoutJsxAttributes = defineRule44({
5159
+ import { defineRule as defineRule45 } from "@oxlint/plugins";
5160
+ var layoutJsxAttributes = defineRule45({
5075
5161
  meta: {
5076
5162
  type: "layout",
5077
5163
  fixable: "whitespace",
@@ -5081,12 +5167,19 @@ var layoutJsxAttributes = defineRule44({
5081
5167
  additionalProperties: false,
5082
5168
  properties: {
5083
5169
  min: { type: "integer", minimum: 1 },
5084
- indent: sourceIndentSchema
5170
+ indent: sourceIndentSchema,
5171
+ join: {
5172
+ type: "object",
5173
+ additionalProperties: false,
5174
+ required: ["max"],
5175
+ properties: { max: { type: "integer", minimum: 1 } }
5176
+ }
5085
5177
  }
5086
5178
  }
5087
5179
  ],
5088
5180
  messages: {
5089
- multiline: "Put every JSX attribute and the closing token on its own line."
5181
+ multiline: "Put every JSX attribute and the closing token on its own line.",
5182
+ join: "This tag fits on one line, so keep it on one line."
5090
5183
  }
5091
5184
  },
5092
5185
  createOnce(context) {
@@ -5094,35 +5187,52 @@ var layoutJsxAttributes = defineRule44({
5094
5187
  let indentUnit = sourceIndentUnit();
5095
5188
  let newline = `
5096
5189
  `;
5190
+ let joinMax;
5097
5191
  return {
5098
5192
  before() {
5099
5193
  const options = optionsFirst(context, {});
5100
5194
  minAttributes = options.min ?? 3;
5101
5195
  indentUnit = sourceIndentUnit(options.indent);
5102
5196
  newline = sourceNewline(context.sourceCode.text);
5197
+ joinMax = options.join?.max;
5103
5198
  },
5104
5199
  JSXOpeningElement(node) {
5105
- if (node.attributes.length < minAttributes || node.loc.start.line !== node.loc.end.line) {
5106
- return;
5107
- }
5108
5200
  const source = context.sourceCode;
5109
5201
  const text = source.text;
5202
+ const attributes = node.attributes;
5203
+ const split = attributes.length >= minAttributes;
5204
+ if (split ? !astIsOneLine(node) : joinMax === undefined || astIsOneLine(node)) {
5205
+ return;
5206
+ }
5207
+ if (!split) {
5208
+ const start = (node.typeArguments ?? node.name).range[1];
5209
+ if (attributes.length === 0 || !attributes.every(astIsOneLine) || text.slice(node.range[0], start).includes(`
5210
+ `) || source.getCommentsInside(node).length > 0) {
5211
+ return;
5212
+ }
5213
+ const end = node.range[1] - (node.selfClosing ? 2 : 1);
5214
+ const ranges = sourceItemGaps(start, attributes, end);
5215
+ const closing = node.selfClosing ? " " : "";
5216
+ if (sourceJoinedWidth(text, start, ` ${attributes.map((attribute) => source.getText(attribute)).join(" ")}${closing}`, end) > (joinMax ?? 0)) {
5217
+ return;
5218
+ }
5219
+ context.report({
5220
+ node,
5221
+ messageId: "join",
5222
+ fix: (fixer) => ranges.some((range) => !/^\s*$/.test(text.slice(...range))) ? null : ranges.map((range, index) => fixer.replaceTextRange(range, index === ranges.length - 1 ? closing : " "))
5223
+ });
5224
+ return;
5225
+ }
5110
5226
  const baseIndent = sourceLineIndent(text, node.range[0]);
5111
5227
  const attributeIndent = baseIndent + indentUnit;
5112
- const attributes = node.attributes;
5113
5228
  context.report({
5114
5229
  node,
5115
5230
  messageId: "multiline",
5116
5231
  fix: (fixer) => {
5117
- const last = attributes.at(-1);
5118
- if (source.getCommentsInside(node).length > 0 || !last) {
5232
+ if (source.getCommentsInside(node).length > 0) {
5119
5233
  return null;
5120
5234
  }
5121
- const ranges = [
5122
- [(node.typeArguments ?? node.name).range[1], attributes[0].range[0]],
5123
- ...attributes.slice(1).map((attribute, index) => [attributes[index].range[1], attribute.range[0]]),
5124
- [last.range[1], node.range[1] - (node.selfClosing ? 2 : 1)]
5125
- ];
5235
+ const ranges = sourceItemGaps((node.typeArguments ?? node.name).range[1], attributes, node.range[1] - (node.selfClosing ? 2 : 1));
5126
5236
  if (ranges.some((range) => !/^\s*$/.test(text.slice(...range)))) {
5127
5237
  return null;
5128
5238
  }
@@ -5135,8 +5245,8 @@ var layoutJsxAttributes = defineRule44({
5135
5245
  });
5136
5246
 
5137
5247
  // src/rules/layout/layout-key-value.ts
5138
- import { defineRule as defineRule45 } from "@oxlint/plugins";
5139
- var layoutKeyValue = defineRule45({
5248
+ import { defineRule as defineRule46 } from "@oxlint/plugins";
5249
+ var layoutKeyValue = defineRule46({
5140
5250
  meta: {
5141
5251
  type: "layout",
5142
5252
  fixable: "whitespace",
@@ -5172,11 +5282,15 @@ var layoutKeyValue = defineRule45({
5172
5282
  });
5173
5283
 
5174
5284
  // src/rules/layout/layout-object.ts
5175
- import { defineRule as defineRule46 } from "@oxlint/plugins";
5285
+ import { defineRule as defineRule47 } from "@oxlint/plugins";
5176
5286
  function layoutObjectIsWhitespace(text) {
5177
5287
  return /^[\t\n\r\f ]*$/.test(text);
5178
5288
  }
5179
- var layoutObject = defineRule46({
5289
+ function layoutObjectIsFlat(property) {
5290
+ const value = property.type === "SpreadElement" ? property.argument : property.value;
5291
+ return astIsOneLine(property) && (property.type === "SpreadElement" || !property.method && property.kind === "init") && value.type !== "ObjectExpression" && value.type !== "ArrayExpression" && !astIsInlineFunction(value);
5292
+ }
5293
+ var layoutObject = defineRule47({
5180
5294
  meta: {
5181
5295
  type: "layout",
5182
5296
  fixable: "whitespace",
@@ -5187,13 +5301,23 @@ var layoutObject = defineRule46({
5187
5301
  properties: {
5188
5302
  min: { type: "integer", minimum: 1, default: 3 },
5189
5303
  scope: { type: "string", enum: ["call-args", "all"], default: "call-args" },
5190
- indent: { ...sourceIndentSchema, default: 2 }
5304
+ indent: { ...sourceIndentSchema, default: 2 },
5305
+ join: {
5306
+ type: "object",
5307
+ additionalProperties: false,
5308
+ required: ["max"],
5309
+ properties: {
5310
+ max: { type: "integer", minimum: 1 },
5311
+ keep: { type: "array", items: { type: "string" } }
5312
+ }
5313
+ }
5191
5314
  }
5192
5315
  }
5193
5316
  ],
5194
5317
  messages: {
5195
5318
  callArgs: "Object with {{min}}+ properties in a call should have each property on its own line.",
5196
- always: "Object with {{min}}+ properties should have each property on its own line."
5319
+ always: "Object with {{min}}+ properties should have each property on its own line.",
5320
+ join: "This object fits on one line, so keep it on one line."
5197
5321
  }
5198
5322
  },
5199
5323
  createOnce(context) {
@@ -5202,6 +5326,7 @@ var layoutObject = defineRule46({
5202
5326
  let indentUnit = sourceIndentUnit();
5203
5327
  let newline = `
5204
5328
  `;
5329
+ let join3;
5205
5330
  return {
5206
5331
  before() {
5207
5332
  const options = optionsFirst(context, {});
@@ -5209,21 +5334,43 @@ var layoutObject = defineRule46({
5209
5334
  scope = options.scope ?? "call-args";
5210
5335
  indentUnit = sourceIndentUnit(options.indent);
5211
5336
  newline = sourceNewline(context.sourceCode.text);
5337
+ join3 = options.join;
5212
5338
  },
5213
5339
  ObjectExpression(node) {
5214
- if (node.properties.length < minProperties || node.loc.start.line !== node.loc.end.line) {
5340
+ const source = context.sourceCode;
5341
+ const text = source.text;
5342
+ const properties = node.properties;
5343
+ const split = properties.length >= minProperties;
5344
+ if (split ? !astIsOneLine(node) : !join3 || astIsOneLine(node)) {
5215
5345
  return;
5216
5346
  }
5217
5347
  const parent = node.parent;
5218
- if (scope === "call-args" && !((parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node))) {
5348
+ const call = (parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node) ? parent : undefined;
5349
+ if (scope === "call-args" && !call) {
5350
+ return;
5351
+ }
5352
+ if (!split) {
5353
+ const callee = call ? astDottedName(call.callee) : undefined;
5354
+ const kept = callee !== undefined && (join3?.keep ?? []).some((name) => callee === name || callee.endsWith(`.${name}`));
5355
+ if (kept || properties.length === 0 || !properties.every(layoutObjectIsFlat) || source.getCommentsInside(node).length > 0) {
5356
+ return;
5357
+ }
5358
+ const gaps = sourceItemGaps(node.range[0] + 1, properties, node.range[1] - 1);
5359
+ const comma = text.slice(...gaps.at(-1) ?? [0, 0]).includes(",") ? "," : "";
5360
+ const joined = ` ${properties.map((property) => source.getText(property)).join(", ")}${comma} `;
5361
+ if (sourceJoinedWidth(text, node.range[0] + 1, joined, node.range[1] - 1) > (join3?.max ?? 0)) {
5362
+ return;
5363
+ }
5364
+ context.report({
5365
+ node,
5366
+ messageId: "join",
5367
+ fix: (fixer) => sourceRespace(fixer, text, gaps, () => " ")
5368
+ });
5219
5369
  return;
5220
5370
  }
5221
- const source = context.sourceCode;
5222
- const text = source.text;
5223
5371
  const baseIndent = sourceLineIndent(text, node.range[0]);
5224
5372
  const propertyIndent = `${baseIndent}${indentUnit}`;
5225
5373
  const close = node.range[1] - 1;
5226
- const properties = node.properties;
5227
5374
  context.report({
5228
5375
  node,
5229
5376
  messageId: scope === "all" ? "always" : "callArgs",
@@ -5260,7 +5407,7 @@ var layoutObject = defineRule46({
5260
5407
  });
5261
5408
 
5262
5409
  // src/rules/layout/layout-padding.ts
5263
- import { defineRule as defineRule47 } from "@oxlint/plugins";
5410
+ import { defineRule as defineRule48 } from "@oxlint/plugins";
5264
5411
  var layoutPaddingControl = new Set([
5265
5412
  "IfStatement",
5266
5413
  "ForStatement",
@@ -5289,7 +5436,7 @@ function layoutPaddingKind(node) {
5289
5436
  function layoutPaddingIsMultilineVariable(node, kind) {
5290
5437
  return kind === "variable" && node.loc.start.line !== node.loc.end.line;
5291
5438
  }
5292
- var layoutPadding = defineRule47({
5439
+ var layoutPadding = defineRule48({
5293
5440
  meta: {
5294
5441
  type: "layout",
5295
5442
  fixable: "whitespace",
@@ -5378,8 +5525,8 @@ var layoutPadding = defineRule47({
5378
5525
  });
5379
5526
 
5380
5527
  // src/rules/layout/layout-pipe.ts
5381
- import { defineRule as defineRule48 } from "@oxlint/plugins";
5382
- var layoutPipe = defineRule48({
5528
+ import { defineRule as defineRule49 } from "@oxlint/plugins";
5529
+ var layoutPipe = defineRule49({
5383
5530
  meta: {
5384
5531
  type: "layout",
5385
5532
  messages: {
@@ -5410,7 +5557,7 @@ var layoutPipe = defineRule48({
5410
5557
  });
5411
5558
 
5412
5559
  // src/rules/layout/layout-ternary.ts
5413
- import { defineRule as defineRule49 } from "@oxlint/plugins";
5560
+ import { defineRule as defineRule50 } from "@oxlint/plugins";
5414
5561
  function layoutTernaryLines(node) {
5415
5562
  return node.loc.end.line - node.loc.start.line + 1;
5416
5563
  }
@@ -5418,7 +5565,7 @@ function layoutTernaryBreak(between, operator, newline) {
5418
5565
  const at = between.indexOf(operator);
5419
5566
  return `${between.slice(0, at).trimEnd()}${newline}${operator} ${between.slice(at + 1).trimStart()}`;
5420
5567
  }
5421
- var layoutTernary = defineRule49({
5568
+ var layoutTernary = defineRule50({
5422
5569
  meta: {
5423
5570
  type: "layout",
5424
5571
  fixable: "whitespace",
@@ -5432,11 +5579,7 @@ var layoutTernary = defineRule49({
5432
5579
  minimum: 1,
5433
5580
  default: 160
5434
5581
  },
5435
- indent: {
5436
- type: "integer",
5437
- minimum: 1,
5438
- default: 2
5439
- },
5582
+ indent: { ...sourceIndentSchema, default: 2 },
5440
5583
  fit: {
5441
5584
  type: "boolean",
5442
5585
  default: true
@@ -5459,7 +5602,7 @@ var layoutTernary = defineRule49({
5459
5602
  },
5460
5603
  createOnce(context) {
5461
5604
  let maxLength = 160;
5462
- let indent = 2;
5605
+ let indentUnit = sourceIndentUnit();
5463
5606
  let fit = true;
5464
5607
  let branchMax;
5465
5608
  const branch = (node, max) => {
@@ -5480,7 +5623,7 @@ var layoutTernary = defineRule49({
5480
5623
  before() {
5481
5624
  const options = optionsFirst(context, {});
5482
5625
  maxLength = options.max ?? 160;
5483
- indent = options.indent ?? 2;
5626
+ indentUnit = sourceIndentUnit(options.indent);
5484
5627
  fit = options.fit ?? true;
5485
5628
  branchMax = options.branch?.max;
5486
5629
  },
@@ -5520,7 +5663,7 @@ var layoutTernary = defineRule49({
5520
5663
  if (!onOneLine || interpolated || length <= maxLength) {
5521
5664
  return;
5522
5665
  }
5523
- const newline = `${sourceNewline(text)}${sourceLineIndent(text, node.range[0])}${" ".repeat(indent)}`;
5666
+ const newline = `${sourceNewline(text)}${sourceLineIndent(text, node.range[0])}${indentUnit}`;
5524
5667
  const question = [node.test.range[1], node.consequent.range[0]];
5525
5668
  const colon = [node.consequent.range[1], node.alternate.range[0]];
5526
5669
  context.report({
@@ -5538,9 +5681,9 @@ var layoutTernary = defineRule49({
5538
5681
  });
5539
5682
 
5540
5683
  // src/rules/naming/naming-declarations.ts
5541
- import { defineRule as defineRule50 } from "@oxlint/plugins";
5684
+ import { defineRule as defineRule51 } from "@oxlint/plugins";
5542
5685
  var namingDeclarationsKinds = ["function", "const", "let", "var", "class", "type", "interface", "enum"];
5543
- var namingDeclarations = defineRule50({
5686
+ var namingDeclarations = defineRule51({
5544
5687
  meta: {
5545
5688
  type: "problem",
5546
5689
  schema: [
@@ -5572,11 +5715,9 @@ var namingDeclarations = defineRule50({
5572
5715
  const declarator = node.type === "VariableDeclarator";
5573
5716
  const kind = declarationsKindOf(node, declarator ? [node.parent] : []);
5574
5717
  const name = declarationsIdName(node);
5575
- let insideLoop = false;
5576
- for (let current = declarator ? node.parent : null;current && !insideLoop; current = current.parent) {
5577
- insideLoop = current.type === "ForStatement" || current.type === "ForInStatement" || current.type === "ForOfStatement";
5578
- }
5579
- if (!kind || !name || insideLoop || kinds && !kinds.has(kind) || optionsPatternsTest(allowed, name)) {
5718
+ const loop = declarator ? node.parent.parent : null;
5719
+ const loopHeader = loop?.type === "ForStatement" ? loop.init === node.parent : (loop?.type === "ForInStatement" || loop?.type === "ForOfStatement") && loop.left === node.parent;
5720
+ if (!kind || !name || loopHeader || kinds && !kinds.has(kind) || optionsPatternsTest(allowed, name)) {
5580
5721
  return;
5581
5722
  }
5582
5723
  if (expected.length > 0) {
@@ -5619,7 +5760,7 @@ var namingDeclarations = defineRule50({
5619
5760
  });
5620
5761
 
5621
5762
  // src/rules/naming/naming-exports.ts
5622
- import { defineRule as defineRule51 } from "@oxlint/plugins";
5763
+ import { defineRule as defineRule52 } from "@oxlint/plugins";
5623
5764
  var namingExportsScopeProperties = {
5624
5765
  scope: { type: "string", enum: ["exports", "functions", "declarations"] },
5625
5766
  types: { type: "boolean" },
@@ -5645,7 +5786,7 @@ function matchTemplate(basename3, template, placeholder = "[a-z0-9-]+") {
5645
5786
  }
5646
5787
  return new RegExp(`^${pattern}$`).exec(basename3)?.groups ?? null;
5647
5788
  }
5648
- var namingExports = defineRule51({
5789
+ var namingExports = defineRule52({
5649
5790
  meta: {
5650
5791
  type: "problem",
5651
5792
  schema: {
@@ -5740,9 +5881,12 @@ var namingExports = defineRule51({
5740
5881
  }
5741
5882
  continue;
5742
5883
  }
5743
- const names = scope === "declarations" ? [...declarations ??= declarationsNamed(program)] : (bindings ??= exportsCollect(program)).flatMap((binding) => options.types && binding.typeOnly ? [] : [{ name: binding.localName ?? binding.exportedName, node: binding.node }]);
5884
+ const names = scope === "declarations" ? [...declarations ??= declarationsNamed(program)] : (bindings ??= exportsCollect(program)).flatMap((binding) => {
5885
+ const name = binding.default ? binding.localName ?? binding.exportedName : binding.exportedName;
5886
+ return options.types && binding.typeOnly ? [] : [{ name, node: binding.node }];
5887
+ });
5744
5888
  if (scope === "functions") {
5745
- const exported = new Set(names.map((item) => item.name));
5889
+ const exported = new Set([...names.map((item) => item.name), ...(bindings ?? []).flatMap((binding) => binding.localName ?? [])]);
5746
5890
  names.push(...(declarations ??= declarationsNamed(program)).filter(({ name, node }) => !exported.has(name) && (node.type === "FunctionDeclaration" || node.type === "VariableDeclarator" && declarationsIsFunctionValue(node.init))));
5747
5891
  }
5748
5892
  if (options.services) {
@@ -5779,10 +5923,10 @@ var namingExports = defineRule51({
5779
5923
  });
5780
5924
 
5781
5925
  // src/rules/naming/naming-file.ts
5782
- import { defineRule as defineRule52 } from "@oxlint/plugins";
5926
+ import { defineRule as defineRule53 } from "@oxlint/plugins";
5783
5927
  import { readdirSync as readdirSync2 } from "node:fs";
5784
5928
  import { dirname as dirname4 } from "node:path";
5785
- var namingFile = defineRule52({
5929
+ var namingFile = defineRule53({
5786
5930
  meta: {
5787
5931
  type: "problem",
5788
5932
  schema: {
@@ -5852,9 +5996,9 @@ var namingFile = defineRule52({
5852
5996
  });
5853
5997
 
5854
5998
  // src/rules/naming/naming-folder.ts
5855
- import { defineRule as defineRule53 } from "@oxlint/plugins";
5999
+ import { defineRule as defineRule54 } from "@oxlint/plugins";
5856
6000
  import { posix as posix2 } from "node:path";
5857
- var namingFolder = defineRule53({
6001
+ var namingFolder = defineRule54({
5858
6002
  meta: {
5859
6003
  type: "problem",
5860
6004
  schema: [
@@ -5928,9 +6072,9 @@ var reactDynamicClasses = astSelectorRule(':matches(JSXAttribute[name.name="clas
5928
6072
  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.");
5929
6073
 
5930
6074
  // src/rules/react/react-memo.ts
5931
- import { defineRule as defineRule54 } from "@oxlint/plugins";
6075
+ import { defineRule as defineRule55 } from "@oxlint/plugins";
5932
6076
  var reactMemoNames = ["memo", "useMemo", "useCallback"];
5933
- var reactMemo = defineRule54({
6077
+ var reactMemo = defineRule55({
5934
6078
  meta: {
5935
6079
  type: "problem",
5936
6080
  schema: [],
@@ -5965,7 +6109,7 @@ var reactMemo = defineRule54({
5965
6109
  });
5966
6110
 
5967
6111
  // src/rules/react/react-props.ts
5968
- import { defineRule as defineRule55 } from "@oxlint/plugins";
6112
+ import { defineRule as defineRule56 } from "@oxlint/plugins";
5969
6113
  function reactPropsTarget(node) {
5970
6114
  if (node?.type === "FunctionDeclaration" || node?.type === "FunctionExpression" || node?.type === "ArrowFunctionExpression") {
5971
6115
  return { fn: node };
@@ -5992,7 +6136,7 @@ function reactIsPassThrough(type) {
5992
6136
  const argument = type.typeArguments?.params.length === 1 ? type.typeArguments.params[0] : undefined;
5993
6137
  return name.type === "Identifier" && name.name === "ComponentProps" && (argument?.type === "TSTypeQuery" || argument?.type === "TSLiteralType" && argument.literal.type === "Literal" && typeof argument.literal.value === "string");
5994
6138
  }
5995
- var reactProps = defineRule55({
6139
+ var reactProps = defineRule56({
5996
6140
  meta: {
5997
6141
  type: "problem",
5998
6142
  schema: [
@@ -6000,7 +6144,8 @@ var reactProps = defineRule55({
6000
6144
  type: "object",
6001
6145
  additionalProperties: false,
6002
6146
  properties: {
6003
- prefix: { type: "boolean" }
6147
+ prefix: { type: "boolean" },
6148
+ suffix: { type: "string", minLength: 1, default: "Props" }
6004
6149
  }
6005
6150
  }
6006
6151
  ],
@@ -6014,7 +6159,7 @@ var reactProps = defineRule55({
6014
6159
  createOnce(context) {
6015
6160
  return {
6016
6161
  Program(program) {
6017
- const { prefix: filePrefix = false } = optionsFirst(context, {});
6162
+ const { prefix: filePrefix = false, suffix = "Props" } = optionsFirst(context, {});
6018
6163
  const stem = namingFileStem(namingBasename(context.filename).replace(/\.(tsx?|jsx?)$/, ""));
6019
6164
  const prefix = namingNormalized(stem, "remove-separators");
6020
6165
  const exported = new Set(exportsCollect(program).flatMap((binding) => binding.typeOnly ? [] : [binding.localName]));
@@ -6039,7 +6184,7 @@ var reactProps = defineRule55({
6039
6184
  continue;
6040
6185
  }
6041
6186
  const type = target.typeArgument ?? reactParameterType(parameter);
6042
- const expected = `${candidate.name}Props`;
6187
+ const expected = `${candidate.name}${suffix}`;
6043
6188
  const data = { name: candidate.name, expected };
6044
6189
  if (filePrefix && !exported.has(candidate.name) && !candidate.name.toLowerCase().startsWith(prefix)) {
6045
6190
  context.report({
@@ -6090,7 +6235,7 @@ var reactProps = defineRule55({
6090
6235
  });
6091
6236
 
6092
6237
  // src/rules/schemas/schemas-inline.ts
6093
- import { defineRule as defineRule56 } from "@oxlint/plugins";
6238
+ import { defineRule as defineRule57 } from "@oxlint/plugins";
6094
6239
 
6095
6240
  // src/utils/schemas.utils.ts
6096
6241
  var schemasElementOptionsSchema = {
@@ -6126,7 +6271,7 @@ function schemasIsAllowedElement(node, allowScalars, namespaces, structuralMetho
6126
6271
  }
6127
6272
 
6128
6273
  // src/rules/schemas/schemas-inline.ts
6129
- var schemasInline = defineRule56({
6274
+ var schemasInline = defineRule57({
6130
6275
  meta: {
6131
6276
  type: "problem",
6132
6277
  schema: [schemasElementOptionsSchema],
@@ -6175,11 +6320,11 @@ var schemasInline = defineRule56({
6175
6320
  });
6176
6321
 
6177
6322
  // src/rules/schemas/schemas-local.ts
6178
- import { defineRule as defineRule57 } from "@oxlint/plugins";
6323
+ import { defineRule as defineRule58 } from "@oxlint/plugins";
6179
6324
  var schemasLocalImportMessage = "Do not import runtime schema constructors from {{source}}; import a named schema instead.";
6180
6325
  var schemasLocalConstructionMessage = "Do not construct schemas locally; import a named schema instead.";
6181
6326
  var schemasLocalCalls = ["config", "prettifyError", "treeifyError", "flattenError", "formatError"];
6182
- var schemasLocal = defineRule57({
6327
+ var schemasLocal = defineRule58({
6183
6328
  meta: {
6184
6329
  type: "problem",
6185
6330
  schema: [
@@ -6209,8 +6354,6 @@ var schemasLocal = defineRule57({
6209
6354
  let packages = new Set;
6210
6355
  let calls = new Set;
6211
6356
  let pending = [];
6212
- let schemaUses = new Set;
6213
- let callUses = new Set;
6214
6357
  let importMessage = schemasLocalImportMessage;
6215
6358
  let constructionMessage = schemasLocalConstructionMessage;
6216
6359
  function isAllowedCall(node, member) {
@@ -6242,8 +6385,6 @@ var schemasLocal = defineRule57({
6242
6385
  packages = new Set(options.packages);
6243
6386
  calls = new Set(options.calls ?? schemasLocalCalls);
6244
6387
  pending = [];
6245
- schemaUses = new Set;
6246
- callUses = new Set;
6247
6388
  importMessage = options.importMessage ?? options.message ?? schemasLocalImportMessage;
6248
6389
  constructionMessage = options.constructionMessage ?? options.message ?? schemasLocalConstructionMessage;
6249
6390
  if ((options.allow ?? []).some((suffix) => filename.endsWith(suffix)) || (options.patterns ?? []).some((pattern) => new RegExp(pattern).test(filename))) {
@@ -6267,18 +6408,8 @@ var schemasLocal = defineRule57({
6267
6408
  }
6268
6409
  pending.push({ node, locals });
6269
6410
  },
6270
- Identifier(node) {
6271
- if (!constructors.has(node.name) || node.parent.type.startsWith("Import")) {
6272
- return;
6273
- }
6274
- if (isAllowedCall(node, node.parent)) {
6275
- callUses.add(node.name);
6276
- return;
6277
- }
6278
- schemaUses.add(node.name);
6279
- },
6280
6411
  "Program:exit"() {
6281
- const runtime = pending.filter(({ locals }) => locals.some((name) => schemaUses.has(name) || !callUses.has(name)));
6412
+ const runtime = pending.filter(({ node, locals }) => context.sourceCode.getDeclaredVariables(node).filter((variable) => locals.includes(variable.name)).some((variable) => variable.references.length === 0 || variable.references.some((reference) => !isAllowedCall(reference.identifier, reference.identifier.parent))));
6282
6413
  for (const { node } of runtime) {
6283
6414
  reportRuntimeImport(node, node.source.value);
6284
6415
  }
@@ -6304,8 +6435,8 @@ var schemasLocal = defineRule57({
6304
6435
  });
6305
6436
 
6306
6437
  // src/rules/schemas/schemas-rederive.ts
6307
- import { defineRule as defineRule58 } from "@oxlint/plugins";
6308
- var schemasRederive = defineRule58({
6438
+ import { defineRule as defineRule59 } from "@oxlint/plugins";
6439
+ var schemasRederive = defineRule59({
6309
6440
  meta: {
6310
6441
  type: "problem",
6311
6442
  schema: [
@@ -6351,7 +6482,7 @@ var schemasRederive = defineRule58({
6351
6482
  });
6352
6483
 
6353
6484
  // src/rules/schemas/schemas-single-use.ts
6354
- import { defineRule as defineRule59 } from "@oxlint/plugins";
6485
+ import { defineRule as defineRule60 } from "@oxlint/plugins";
6355
6486
  function isScalarSchema(node, namespaces, structuralMethods) {
6356
6487
  if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") {
6357
6488
  return false;
@@ -6365,7 +6496,7 @@ function isScalarSchema(node, namespaces, structuralMethods) {
6365
6496
  }
6366
6497
  return node.callee.object.type === "CallExpression" && isScalarSchema(node.callee.object, namespaces, structuralMethods);
6367
6498
  }
6368
- var schemasSingleUse = defineRule59({
6499
+ var schemasSingleUse = defineRule60({
6369
6500
  meta: {
6370
6501
  type: "suggestion",
6371
6502
  schema: [schemasElementOptionsSchema],
@@ -6441,7 +6572,7 @@ var schemasSingleUse = defineRule59({
6441
6572
  });
6442
6573
 
6443
6574
  // src/rules/tests/tests-assertions.ts
6444
- import { defineRule as defineRule60 } from "@oxlint/plugins";
6575
+ import { defineRule as defineRule61 } from "@oxlint/plugins";
6445
6576
  var testsAssertionsModifiers = new Set(["for", "each", "concurrent", "sequential", "only", "skip"]);
6446
6577
  var testsAssertionsFunctions = [
6447
6578
  "expect",
@@ -6631,7 +6762,7 @@ function isAssertedTest(registration, sourceCode, state) {
6631
6762
  }
6632
6763
  return rows.length > 0 && rows.every((row) => hasAssertion(callback, row.bindings, new Set, state));
6633
6764
  }
6634
- var testsAssertions = defineRule60({
6765
+ var testsAssertions = defineRule61({
6635
6766
  meta: {
6636
6767
  type: "problem",
6637
6768
  schema: [
@@ -6757,17 +6888,36 @@ var testsAssertions = defineRule60({
6757
6888
  });
6758
6889
 
6759
6890
  // src/rules/tests/tests-concurrent-db.ts
6760
- import { defineRule as defineRule61 } from "@oxlint/plugins";
6761
- var testsConcurrentDb = defineRule61({
6891
+ import { defineRule as defineRule62 } from "@oxlint/plugins";
6892
+ var testsConcurrentDb = defineRule62({
6762
6893
  meta: {
6763
6894
  type: "problem",
6764
- schema: [{ type: "object", properties: { message: { type: "string" } }, additionalProperties: false }],
6895
+ schema: [
6896
+ {
6897
+ type: "object",
6898
+ additionalProperties: false,
6899
+ properties: {
6900
+ message: { type: "string" },
6901
+ fixtures: { type: "array", items: { type: "string", minLength: 1 }, default: ["db"] },
6902
+ callees: { type: "array", items: { type: "string", minLength: 1 }, default: ["test.concurrent"] }
6903
+ }
6904
+ }
6905
+ ],
6765
6906
  messages: { forbidden: "{{message}}" }
6766
6907
  },
6767
6908
  createOnce(context) {
6909
+ let message = "";
6910
+ let fixtures = new Set;
6911
+ let callees = new Set;
6768
6912
  return {
6913
+ before() {
6914
+ const options = optionsFirst(context, {});
6915
+ message = options.message ?? "Obtain an isolated database fixture for concurrent tests.";
6916
+ fixtures = new Set(options.fixtures ?? ["db"]);
6917
+ callees = new Set(options.callees ?? ["test.concurrent"]);
6918
+ },
6769
6919
  CallExpression(node) {
6770
- const callback = astDottedName(node.callee) === "test.concurrent" ? node.arguments.findLast(declarationsIsFunctionValue) : undefined;
6920
+ const callback = callees.has(astDottedName(node.callee) ?? "") ? node.arguments.findLast(declarationsIsFunctionValue) : undefined;
6771
6921
  const parameter = callback?.params.at(0);
6772
6922
  const patterns = parameter?.type === "ObjectPattern" ? [parameter] : [];
6773
6923
  if (callback && parameter?.type === "Identifier") {
@@ -6780,8 +6930,8 @@ var testsConcurrentDb = defineRule61({
6780
6930
  }
6781
6931
  }
6782
6932
  for (const property of patterns.flatMap((pattern) => pattern.properties)) {
6783
- if (property.type === "Property" && property.key.type === "Identifier" && property.key.name === "db") {
6784
- const { message = "Obtain an isolated database fixture for concurrent tests." } = optionsFirst(context, {});
6933
+ const key = property.type === "Property" && !property.computed ? property.key : undefined;
6934
+ if (fixtures.has((key?.type === "Identifier" ? key.name : key && astStaticString(key)) ?? "")) {
6785
6935
  context.report({
6786
6936
  node: property,
6787
6937
  messageId: "forbidden",
@@ -6795,10 +6945,10 @@ var testsConcurrentDb = defineRule61({
6795
6945
  });
6796
6946
 
6797
6947
  // src/rules/tests/tests-modifiers.ts
6798
- import { defineRule as defineRule62 } from "@oxlint/plugins";
6948
+ import { defineRule as defineRule63 } from "@oxlint/plugins";
6799
6949
  var testsModifiersNames = ["test", "it", "describe", "suite"];
6800
6950
  var testsModifiersPackages = new Set(["vitest", "bun:test", "@playwright/test", "@jest/globals"]);
6801
- var testsModifiersBanned = new Set(["only", "skip", "todo"]);
6951
+ var testsModifiersBanned = ["only", "skip", "todo"];
6802
6952
  var testsModifiersBuilders = new Set(["if", "runIf", "skipIf"]);
6803
6953
  function modifierName(node) {
6804
6954
  if (node.type === "Identifier") {
@@ -6814,14 +6964,17 @@ function modifierName(node) {
6814
6964
  const property = astMemberName(node);
6815
6965
  return object && property ? `${object}.${property}` : undefined;
6816
6966
  }
6817
- var testsModifiers = defineRule62({
6967
+ var testsModifiers = defineRule63({
6818
6968
  meta: {
6819
6969
  type: "problem",
6820
6970
  schema: [
6821
6971
  {
6822
6972
  type: "object",
6823
6973
  additionalProperties: false,
6824
- properties: { functions: { type: "array", items: { type: "string" } } }
6974
+ properties: {
6975
+ functions: { type: "array", items: { type: "string" } },
6976
+ modifiers: { type: "array", items: { type: "string", minLength: 1 }, default: testsModifiersBanned }
6977
+ }
6825
6978
  }
6826
6979
  ],
6827
6980
  messages: {
@@ -6830,12 +6983,12 @@ var testsModifiers = defineRule62({
6830
6983
  },
6831
6984
  createOnce(context) {
6832
6985
  let names = new Set;
6986
+ let banned = new Set;
6833
6987
  return {
6834
6988
  before() {
6835
- names = new Set([
6836
- ...testsModifiersNames,
6837
- ...optionsFirst(context, {}).functions ?? []
6838
- ]);
6989
+ const options = optionsFirst(context, {});
6990
+ names = new Set([...testsModifiersNames, ...options.functions ?? []]);
6991
+ banned = new Set(options.modifiers ?? testsModifiersBanned);
6839
6992
  },
6840
6993
  ImportDeclaration(node) {
6841
6994
  if (!testsModifiersPackages.has(node.source.value)) {
@@ -6853,7 +7006,7 @@ var testsModifiers = defineRule62({
6853
7006
  },
6854
7007
  MemberExpression(node) {
6855
7008
  const name = modifierName(node);
6856
- if (!name || !testsModifiersBanned.has(name.split(".").at(-1) ?? "")) {
7009
+ if (!name || !banned.has(name.split(".").at(-1) ?? "")) {
6857
7010
  return;
6858
7011
  }
6859
7012
  if ([...names].some((test) => name.startsWith(`${test}.`))) {
@@ -6865,9 +7018,9 @@ var testsModifiers = defineRule62({
6865
7018
  });
6866
7019
 
6867
7020
  // src/rules/tests/tests-titles.ts
6868
- import { defineRule as defineRule63 } from "@oxlint/plugins";
6869
- var testsTitlesModifiers = new Set(["only", "skip", "concurrent", "sequential", "todo", "each", "for"]);
6870
- var testsTitles = defineRule63({
7021
+ import { defineRule as defineRule64 } from "@oxlint/plugins";
7022
+ var testsTitlesModifiers = ["only", "skip", "concurrent", "sequential", "todo", "each", "for"];
7023
+ var testsTitles = defineRule64({
6871
7024
  meta: {
6872
7025
  type: "suggestion",
6873
7026
  schema: [
@@ -6876,6 +7029,7 @@ var testsTitles = defineRule63({
6876
7029
  additionalProperties: false,
6877
7030
  properties: {
6878
7031
  callees: { type: "array", items: { type: "string" } },
7032
+ modifiers: { type: "array", items: { type: "string", minLength: 1 }, default: testsTitlesModifiers },
6879
7033
  deny: optionsPatternSchema,
6880
7034
  require: optionsPatternSchema,
6881
7035
  flags: { type: "string", pattern: "^[dgimsuvy]*$" }
@@ -6895,6 +7049,7 @@ var testsTitles = defineRule63({
6895
7049
  },
6896
7050
  createOnce(context) {
6897
7051
  let callees = [];
7052
+ let modifiers = new Set;
6898
7053
  let forbid = [];
6899
7054
  let required = [];
6900
7055
  let requiredLabel = "";
@@ -6903,6 +7058,7 @@ var testsTitles = defineRule63({
6903
7058
  const options = optionsFirst(context);
6904
7059
  const flags = options.flags ?? "";
6905
7060
  callees = options.callees;
7061
+ modifiers = new Set(options.modifiers ?? testsTitlesModifiers);
6906
7062
  forbid = options.deny === undefined ? [] : [options.deny].flat().map((source) => ({ regex: new RegExp(source, flags), pattern: source }));
6907
7063
  required = optionsPatternsWithEmpty(options.require, flags);
6908
7064
  requiredLabel = optionsPatternLabel(options.require);
@@ -6913,7 +7069,7 @@ var testsTitles = defineRule63({
6913
7069
  CallExpression(node) {
6914
7070
  const title = node.arguments.at(0);
6915
7071
  const callee = node.callee;
6916
- const configured = astMatchesCallee(astDottedName(callee), callees, testsTitlesModifiers) || callee.type === "CallExpression" && astMatchesCallee(astDottedName(callee.callee), callees, testsTitlesModifiers) || callee.type === "TaggedTemplateExpression" && astMatchesCallee(astDottedName(callee.tag), callees, testsTitlesModifiers);
7072
+ const configured = astMatchesCallee(astDottedName(callee), callees, modifiers) || callee.type === "CallExpression" && astMatchesCallee(astDottedName(callee.callee), callees, modifiers) || callee.type === "TaggedTemplateExpression" && astMatchesCallee(astDottedName(callee.tag), callees, modifiers);
6917
7073
  if (!title || !configured) {
6918
7074
  return;
6919
7075
  }
@@ -6942,11 +7098,11 @@ var testsTitles = defineRule63({
6942
7098
  });
6943
7099
 
6944
7100
  // src/rules/types/types-declarations.ts
6945
- import { defineRule as defineRule64 } from "@oxlint/plugins";
7101
+ import { defineRule as defineRule65 } from "@oxlint/plugins";
6946
7102
  function isValuePattern(source) {
6947
7103
  return /^\^?=/.test(source);
6948
7104
  }
6949
- var typesDeclarations = defineRule64({
7105
+ var typesDeclarations = defineRule65({
6950
7106
  meta: {
6951
7107
  type: "problem",
6952
7108
  schema: [
@@ -6989,7 +7145,7 @@ var typesDeclarations = defineRule64({
6989
7145
  });
6990
7146
 
6991
7147
  // src/rules/types/types-imported-alias.ts
6992
- import { defineRule as defineRule65 } from "@oxlint/plugins";
7148
+ import { defineRule as defineRule66 } from "@oxlint/plugins";
6993
7149
  function importedAliasTarget(declaration, imports) {
6994
7150
  if (declaration?.type !== "TSTypeAliasDeclaration") {
6995
7151
  return;
@@ -7001,7 +7157,7 @@ function importedAliasTarget(declaration, imports) {
7001
7157
  const { name } = type.typeName;
7002
7158
  return imports.has(name) && !declaration.typeParameters?.params.some((parameter) => parameter.name.name === name) ? name : undefined;
7003
7159
  }
7004
- var typesImportedAlias = defineRule65({
7160
+ var typesImportedAlias = defineRule66({
7005
7161
  meta: {
7006
7162
  type: "problem",
7007
7163
  schema: [],
@@ -7063,7 +7219,7 @@ var typesImportedAlias = defineRule65({
7063
7219
  });
7064
7220
 
7065
7221
  // src/rules/types/types-inline.ts
7066
- import { defineRule as defineRule66 } from "@oxlint/plugins";
7222
+ import { defineRule as defineRule67 } from "@oxlint/plugins";
7067
7223
  function parameterAnnotation(parameter) {
7068
7224
  if (parameter.type === "TSParameterProperty") {
7069
7225
  return parameterAnnotation(parameter.parameter);
@@ -7073,7 +7229,7 @@ function parameterAnnotation(parameter) {
7073
7229
  }
7074
7230
  return parameter.typeAnnotation;
7075
7231
  }
7076
- var typesInline = defineRule66({
7232
+ var typesInline = defineRule67({
7077
7233
  meta: {
7078
7234
  type: "problem",
7079
7235
  schema: [
@@ -7149,8 +7305,8 @@ var typesInline = defineRule66({
7149
7305
  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.");
7150
7306
 
7151
7307
  // src/rules/types/types-namespace-import.ts
7152
- import { defineRule as defineRule67 } from "@oxlint/plugins";
7153
- var typesNamespaceImport = defineRule67({
7308
+ import { defineRule as defineRule68 } from "@oxlint/plugins";
7309
+ var typesNamespaceImport = defineRule68({
7154
7310
  meta: {
7155
7311
  type: "suggestion",
7156
7312
  schema: [
@@ -7192,26 +7348,50 @@ var typesNamespaceImport = defineRule67({
7192
7348
  });
7193
7349
 
7194
7350
  // src/rules/types/types-service.ts
7195
- import { defineRule as defineRule68 } from "@oxlint/plugins";
7196
- var typesService = defineRule68({
7351
+ import { defineRule as defineRule69 } from "@oxlint/plugins";
7352
+ var typesService = defineRule69({
7197
7353
  meta: {
7198
7354
  type: "problem",
7199
- schema: [],
7355
+ schema: [
7356
+ {
7357
+ type: "object",
7358
+ additionalProperties: false,
7359
+ properties: {
7360
+ suffix: { type: "string", minLength: 1, default: "Service" },
7361
+ factory: { type: "string", minLength: 1, default: "make" }
7362
+ }
7363
+ }
7364
+ ],
7200
7365
  messages: {
7201
- domain: "Move this type to its domain types file; only the matching factory-derived Service alias belongs here."
7366
+ domain: "Move this type to its domain types file; only the matching factory-derived {{suffix}} alias belongs here."
7202
7367
  }
7203
7368
  },
7204
7369
  createOnce(context) {
7370
+ let suffix = "Service";
7371
+ let factory = "make";
7205
7372
  return {
7373
+ before() {
7374
+ const options = optionsFirst(context, {});
7375
+ suffix = options.suffix ?? "Service";
7376
+ factory = options.factory ?? "make";
7377
+ },
7206
7378
  TSInterfaceDeclaration(node) {
7207
- context.report({ node, messageId: "domain" });
7379
+ context.report({
7380
+ node,
7381
+ messageId: "domain",
7382
+ data: { suffix }
7383
+ });
7208
7384
  },
7209
7385
  TSTypeAliasDeclaration(node) {
7210
7386
  const type = node.typeAnnotation;
7211
7387
  const argument = type.type === "TSTypeReference" && type.typeArguments?.params.length === 1 ? type.typeArguments.params[0] : undefined;
7212
- const isFactoryService = node.id.name.endsWith("Service") && type.type === "TSTypeReference" && type.typeName.type === "Identifier" && type.typeName.name === "ReturnType" && argument?.type === "TSTypeQuery" && argument.exprName.type === "Identifier" && argument.exprName.name === `make${node.id.name}`;
7388
+ const isFactoryService = node.id.name.endsWith(suffix) && type.type === "TSTypeReference" && type.typeName.type === "Identifier" && type.typeName.name === "ReturnType" && argument?.type === "TSTypeQuery" && argument.exprName.type === "Identifier" && argument.exprName.name === `${factory}${node.id.name}`;
7213
7389
  if (!isFactoryService) {
7214
- context.report({ node, messageId: "domain" });
7390
+ context.report({
7391
+ node,
7392
+ messageId: "domain",
7393
+ data: { suffix }
7394
+ });
7215
7395
  }
7216
7396
  }
7217
7397
  };
@@ -7219,12 +7399,12 @@ var typesService = defineRule68({
7219
7399
  });
7220
7400
 
7221
7401
  // src/rules/types/types-unknown.ts
7222
- import { defineRule as defineRule69 } from "@oxlint/plugins";
7402
+ import { defineRule as defineRule70 } from "@oxlint/plugins";
7223
7403
  function isCatchName(node) {
7224
7404
  const key = "key" in node && !(("computed" in node) && node.computed) ? node.key : undefined;
7225
7405
  return key?.type === "Identifier" && key.name === "catch";
7226
7406
  }
7227
- var typesUnknown = defineRule69({
7407
+ var typesUnknown = defineRule70({
7228
7408
  meta: { type: "problem", schema: [], messages: { forbidden: "Use an owned domain type rather than unknown." } },
7229
7409
  createOnce(context) {
7230
7410
  return {