oxlint-plugin-arch 0.12.4 → 0.12.6

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
@@ -1,3 +1,6 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
1
4
  // src/index.ts
2
5
  import { eslintCompatPlugin } from "@oxlint/plugins";
3
6
 
@@ -510,14 +513,15 @@ var backendRegistry = defineRule3({
510
513
  registry: { type: "string", minLength: 1 },
511
514
  services: { type: "string", minLength: 1 },
512
515
  skip: { type: "array", items: { type: "string" } },
513
- object: { type: "string", minLength: 1 }
516
+ object: { type: "string", minLength: 1 },
517
+ file: { type: "string", minLength: 1, default: "{folder}.service.ts" }
514
518
  },
515
519
  required: ["registry", "services"]
516
520
  }
517
521
  ],
518
522
  messages: {
519
523
  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."
524
+ missing: "Domain folder '{{folder}}' needs {{file}}; add it, or list the folder in skip."
521
525
  }
522
526
  },
523
527
  createOnce(context) {
@@ -559,11 +563,12 @@ var backendRegistry = defineRule3({
559
563
  const domains = entries.filter((entry) => entry.isDirectory() && !(options.skip ?? []).includes(entry.name)).map((entry) => entry.name);
560
564
  for (const folder of domains) {
561
565
  const key = namingCamelCase(folder);
562
- if (!existsSync(join(options.services, folder, `${folder}.service.ts`))) {
566
+ const file = (options.file ?? "{folder}.service.ts").replaceAll("{folder}", folder);
567
+ if (!existsSync(join(options.services, folder, file))) {
563
568
  context.report({
564
569
  node: program,
565
570
  messageId: "missing",
566
- data: { folder }
571
+ data: { folder, file }
567
572
  });
568
573
  } else if (![key, `${key}s`, key.replace(/y$/, "ies")].some((name) => keys.has(name))) {
569
574
  context.report({
@@ -1136,11 +1141,14 @@ var boundariesDeny = defineRule6({
1136
1141
  // src/rules/boundaries/boundaries-imports.ts
1137
1142
  import { defineRule as defineRule7 } from "@oxlint/plugins";
1138
1143
  import { basename, resolve as resolve2 } from "node:path";
1139
- var boundariesPublicSurface = /\.(?:service|client|server|rsc|utils)$/;
1144
+ var boundariesSurfaces = ["service", "client", "server", "rsc", "utils"];
1140
1145
  var boundariesActionOrQuery = /\/(?:[^/]+)-(?:action|query)\./;
1141
1146
  function boundariesContains(directory, candidate) {
1142
1147
  return candidate === directory || candidate.startsWith(`${directory}/`);
1143
1148
  }
1149
+ function boundariesIsSurface(options, stem) {
1150
+ return (options.surfaces ?? boundariesSurfaces).some((surface) => stem.endsWith(`.${surface}`));
1151
+ }
1144
1152
  function boundariesStripExtension(path) {
1145
1153
  return path.replace(/\.[cm]?[jt]sx?$/, "");
1146
1154
  }
@@ -1169,12 +1177,12 @@ function boundariesPrivateDomain({ options, file, target, reExport, roots, owner
1169
1177
  const targetDomain = targetOwner ? boundariesDomain(target, targetOwner, boundariesServiceFolder(options, targetOwner)) : undefined;
1170
1178
  const sameDomain = owner && owner === targetOwner && fileDomain && fileDomain === targetDomain;
1171
1179
  const targetStem = boundariesStripExtension(target);
1172
- const publicSurface = boundariesPublicSurface.test(targetStem) && !boundariesActionOrQuery.test(targetStem);
1180
+ const publicSurface = boundariesIsSurface(options, targetStem) && !boundariesActionOrQuery.test(targetStem);
1173
1181
  const privateTarget = roles.some((role) => targetStem.endsWith(`.${role}`)) || targetDomain && !publicSurface && !/\.(?:types|constants)$/.test(targetStem);
1174
1182
  if (!privateTarget) {
1175
1183
  return;
1176
1184
  }
1177
- if (reExport && /\.(?:service|client|server|rsc|utils)\.ts$/.test(file) && !boundariesActionOrQuery.test(file)) {
1185
+ if (reExport && file.endsWith(".ts") && boundariesIsSurface(options, file.slice(0, -".ts".length)) && !boundariesActionOrQuery.test(file)) {
1178
1186
  return "public service and utility surfaces must not re-export private domain internals";
1179
1187
  }
1180
1188
  return sameDomain ? undefined : "private domain files stay in the same domain and application; use its service API or shared utilities";
@@ -1212,6 +1220,7 @@ var boundariesImports = defineRule7({
1212
1220
  backend: { type: "array", items: { type: "string" } },
1213
1221
  packages: { anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }] },
1214
1222
  roles: { type: "array", items: { type: "string" } },
1223
+ surfaces: { type: "array", items: { type: "string", minLength: 1 }, default: boundariesSurfaces },
1215
1224
  services: { type: "array", items: { type: "string" } },
1216
1225
  portable: { type: "boolean" },
1217
1226
  backendPackages: { type: "array", items: { type: "string" } },
@@ -1307,11 +1316,9 @@ var boundariesNextDefaults = {
1307
1316
  serverPackages: [...boundariesServerPackages, "server-only", "next/headers", "next/cache", "next/server"],
1308
1317
  clientPackages: ["client-only", "react-dom/client"],
1309
1318
  hookPackages: ["react"],
1310
- publicEnv: "NEXT_PUBLIC_"
1319
+ publicEnv: "NEXT_PUBLIC_",
1320
+ serverHooks: ["useId", "use"]
1311
1321
  };
1312
- function boundariesIsClientHook(name) {
1313
- return /^use[A-Z]/.test(name) && name !== "useId" && name !== "use";
1314
- }
1315
1322
  var boundariesNext = defineRule8({
1316
1323
  meta: {
1317
1324
  type: "problem",
@@ -1319,7 +1326,11 @@ var boundariesNext = defineRule8({
1319
1326
  {
1320
1327
  type: "object",
1321
1328
  additionalProperties: false,
1322
- properties: { ...boundariesRuntimeProperties, publicEnv: { type: "string" } }
1329
+ properties: {
1330
+ ...boundariesRuntimeProperties,
1331
+ publicEnv: { type: "string" },
1332
+ serverHooks: { type: "array", items: { type: "string", minLength: 1 }, default: boundariesNextDefaults.serverHooks }
1333
+ }
1323
1334
  }
1324
1335
  ],
1325
1336
  messages: {
@@ -1335,6 +1346,7 @@ var boundariesNext = defineRule8({
1335
1346
  let client = false;
1336
1347
  let shared = false;
1337
1348
  const reactNamespaces = new Set;
1349
+ const clientHook = (name) => /^use[A-Z]/.test(name) && !options.serverHooks.includes(name);
1338
1350
  function check(node, source, typeOnly) {
1339
1351
  if (typeOnly) {
1340
1352
  return;
@@ -1398,14 +1410,14 @@ var boundariesNext = defineRule8({
1398
1410
  reactNamespaces.add(specifier);
1399
1411
  continue;
1400
1412
  }
1401
- if (specifier.importKind !== "type" && specifier.imported.type === "Identifier" && boundariesIsClientHook(specifier.imported.name)) {
1413
+ if (specifier.importKind !== "type" && specifier.imported.type === "Identifier" && clientHook(specifier.imported.name)) {
1402
1414
  boundariesReport(context, specifier, specifier.imported.name, boundariesClientHookReason);
1403
1415
  }
1404
1416
  }
1405
1417
  },
1406
1418
  MemberExpression(node) {
1407
1419
  const name = boundariesMemberName(node);
1408
- if (!name || !boundariesIsClientHook(name) || node.object.type !== "Identifier") {
1420
+ if (!name || !clientHook(name) || node.object.type !== "Identifier") {
1409
1421
  return;
1410
1422
  }
1411
1423
  const binding = boundariesDeclaredVariable(context.sourceCode.getScope(node.object), node.object.name)?.defs[0]?.node;
@@ -1449,12 +1461,54 @@ var boundariesNext = defineRule8({
1449
1461
  });
1450
1462
 
1451
1463
  // 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.");
1464
+ import { defineRule as defineRule9 } from "@oxlint/plugins";
1465
+ var boundariesRpcDatabaseMethods = ["selectFrom", "insertInto", "updateTable", "deleteFrom", "mergeInto", "executeQuery"];
1466
+ var boundariesRpcDatabase = defineRule9({
1467
+ meta: {
1468
+ type: "problem",
1469
+ schema: [
1470
+ {
1471
+ type: "object",
1472
+ additionalProperties: false,
1473
+ properties: {
1474
+ message: { type: "string" },
1475
+ methods: { type: "array", items: { type: "string", minLength: 1 }, default: boundariesRpcDatabaseMethods }
1476
+ }
1477
+ }
1478
+ ],
1479
+ messages: { forbidden: "{{message}}" }
1480
+ },
1481
+ createOnce(context) {
1482
+ let message = "";
1483
+ let methods = new Set;
1484
+ return {
1485
+ before() {
1486
+ const options = optionsFirst(context, {});
1487
+ message = options.message ?? "RPC handlers delegate database work to services.";
1488
+ methods = new Set(options.methods ?? boundariesRpcDatabaseMethods);
1489
+ },
1490
+ CallExpression(node) {
1491
+ const callee = node.callee;
1492
+ if (callee.type !== "MemberExpression") {
1493
+ return;
1494
+ }
1495
+ const property = callee.property;
1496
+ const literal = property.type === "Literal" && typeof property.value === "string" ? property.value : undefined;
1497
+ const name = callee.computed ? literal : astMemberName(callee);
1498
+ if (name !== undefined && methods.has(name)) {
1499
+ context.report({
1500
+ node,
1501
+ messageId: "forbidden",
1502
+ data: { message }
1503
+ });
1504
+ }
1505
+ }
1506
+ };
1507
+ }
1508
+ });
1454
1509
 
1455
1510
  // src/rules/boundaries/boundaries-tanstack.ts
1456
- import { defineRule as defineRule9 } from "@oxlint/plugins";
1457
- var boundariesRenderers = ["renderServerComponent", "renderToReadableStream"];
1511
+ import { defineRule as defineRule10 } from "@oxlint/plugins";
1458
1512
  var boundariesChainMethods = ["handler", "server", "client", "middleware", "validator", "inputValidator"];
1459
1513
  var boundariesSplitFactories = ["createIsomorphicFn", "createMiddleware"];
1460
1514
  var boundariesRuntimeTypeWrappers = [
@@ -1490,12 +1544,13 @@ var boundariesTanstackDefaults = {
1490
1544
  clientPackages: ["react-dom/client", "@tanstack/react-start/client-only", "@tanstack/solid-start/client-only", "@tanstack/vue-start/client-only"],
1491
1545
  hookPackages: ["react", "@tanstack/react-query", "@tanstack/react-router"],
1492
1546
  startPackages: ["@tanstack/react-start"],
1493
- rscPackages: ["@tanstack/react-start/rsc", "@tanstack/react-start-rsc"]
1547
+ rscPackages: ["@tanstack/react-start/rsc", "@tanstack/react-start-rsc"],
1548
+ renderers: ["renderServerComponent", "renderToReadableStream"]
1494
1549
  };
1495
- function boundariesIsClientHook2(name) {
1550
+ function boundariesIsClientHook(name) {
1496
1551
  return /^use[A-Z]/.test(name) && name !== "useId";
1497
1552
  }
1498
- var boundariesTanstack = defineRule9({
1553
+ var boundariesTanstack = defineRule10({
1499
1554
  meta: {
1500
1555
  type: "problem",
1501
1556
  schema: [
@@ -1506,7 +1561,8 @@ var boundariesTanstack = defineRule9({
1506
1561
  ...boundariesRuntimeProperties,
1507
1562
  rpc: { type: "array", items: { type: "string" } },
1508
1563
  startPackages: { type: "array", items: { type: "string" } },
1509
- rscPackages: { type: "array", items: { type: "string" } }
1564
+ rscPackages: { type: "array", items: { type: "string" } },
1565
+ renderers: { type: "array", items: { type: "string", minLength: 1 }, default: boundariesTanstackDefaults.renderers }
1510
1566
  }
1511
1567
  }
1512
1568
  ],
@@ -1662,7 +1718,7 @@ var boundariesTanstack = defineRule9({
1662
1718
  return;
1663
1719
  }
1664
1720
  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)) {
1721
+ if (options.rscPackages.includes(node.source.value) && specifier.type === "ImportSpecifier" && specifier.importKind !== "type" && specifier.imported.type === "Identifier" && options.renderers.includes(specifier.imported.name)) {
1666
1722
  check(node, node.source.value, false, false, specifier);
1667
1723
  }
1668
1724
  }
@@ -1670,7 +1726,7 @@ var boundariesTanstack = defineRule9({
1670
1726
  return;
1671
1727
  }
1672
1728
  for (const specifier of node.specifiers) {
1673
- if (specifier.type !== "ImportSpecifier" || specifier.importKind === "type" || specifier.imported.type !== "Identifier" || !boundariesIsClientHook2(specifier.imported.name)) {
1729
+ if (specifier.type !== "ImportSpecifier" || specifier.importKind === "type" || specifier.imported.type !== "Identifier" || !boundariesIsClientHook(specifier.imported.name)) {
1674
1730
  continue;
1675
1731
  }
1676
1732
  const references = runtimeReferences(node, specifier.local.name);
@@ -1689,10 +1745,10 @@ var boundariesTanstack = defineRule9({
1689
1745
  return;
1690
1746
  }
1691
1747
  const kind = imported(node.object);
1692
- if (kind === "rsc-renderers" && boundariesRenderers.includes(name) && (shared || !serverPosition(node.start, true))) {
1748
+ if (kind === "rsc-renderers" && options.renderers.includes(name) && (shared || !serverPosition(node.start, true))) {
1693
1749
  boundariesReport(context, node, name, "server renderers require a server execution boundary");
1694
1750
  }
1695
- if ((serverFile || rscFile) && kind === "client-hooks" && boundariesIsClientHook2(name) && !clientSide(node.start)) {
1751
+ if ((serverFile || rscFile) && kind === "client-hooks" && boundariesIsClientHook(name) && !clientSide(node.start)) {
1696
1752
  boundariesReport(context, node, name, boundariesClientHookReason2);
1697
1753
  }
1698
1754
  },
@@ -1724,8 +1780,8 @@ var boundariesTanstack = defineRule9({
1724
1780
  });
1725
1781
 
1726
1782
  // src/rules/boundaries/boundaries-token-owner.ts
1727
- import { defineRule as defineRule10 } from "@oxlint/plugins";
1728
- var boundariesTokenOwner = defineRule10({
1783
+ import { defineRule as defineRule11 } from "@oxlint/plugins";
1784
+ var boundariesTokenOwner = defineRule11({
1729
1785
  meta: {
1730
1786
  type: "problem",
1731
1787
  schema: [
@@ -1820,7 +1876,7 @@ var boundariesTokenOwner = defineRule10({
1820
1876
  });
1821
1877
 
1822
1878
  // src/rules/code/code-branch-once.ts
1823
- import { defineRule as defineRule11 } from "@oxlint/plugins";
1879
+ import { defineRule as defineRule12 } from "@oxlint/plugins";
1824
1880
  function codeBranchOnceSubjects(node, source) {
1825
1881
  if (node.type === "LogicalExpression") {
1826
1882
  return [...codeBranchOnceSubjects(node.left, source), ...codeBranchOnceSubjects(node.right, source)];
@@ -1836,18 +1892,27 @@ function codeBranchOnceSubjects(node, source) {
1836
1892
  const literal = sides.some((side) => side.type === "Literal" && typeof side.value === "string");
1837
1893
  return member && literal ? [source.getText(member)] : [];
1838
1894
  }
1839
- var codeBranchOnce = defineRule11({
1895
+ var codeBranchOnce = defineRule12({
1840
1896
  meta: {
1841
1897
  type: "suggestion",
1898
+ schema: [
1899
+ {
1900
+ type: "object",
1901
+ additionalProperties: false,
1902
+ properties: { min: { type: "integer", minimum: 2, default: 2 } }
1903
+ }
1904
+ ],
1842
1905
  messages: {
1843
1906
  once: "{{subject}} is compared to a string in {{count}} ternaries in this file. Branch on it once and return a record."
1844
1907
  }
1845
1908
  },
1846
1909
  createOnce(context) {
1847
1910
  let groups = new Map;
1911
+ let min = 2;
1848
1912
  return {
1849
1913
  before() {
1850
1914
  groups = new Map;
1915
+ min = optionsFirst(context, {}).min ?? 2;
1851
1916
  },
1852
1917
  ConditionalExpression(node) {
1853
1918
  for (const subject of new Set(codeBranchOnceSubjects(node.test, context.sourceCode))) {
@@ -1856,7 +1921,7 @@ var codeBranchOnce = defineRule11({
1856
1921
  },
1857
1922
  "Program:exit"() {
1858
1923
  for (const [subject, ternaries] of groups) {
1859
- for (const node of ternaries.length > 1 ? ternaries : []) {
1924
+ for (const node of ternaries.length >= min ? ternaries : []) {
1860
1925
  context.report({
1861
1926
  node,
1862
1927
  messageId: "once",
@@ -1870,8 +1935,8 @@ var codeBranchOnce = defineRule11({
1870
1935
  });
1871
1936
 
1872
1937
  // src/rules/code/code-constructor.ts
1873
- import { defineRule as defineRule12 } from "@oxlint/plugins";
1874
- var codeConstructor = defineRule12({
1938
+ import { defineRule as defineRule13 } from "@oxlint/plugins";
1939
+ var codeConstructor = defineRule13({
1875
1940
  meta: {
1876
1941
  type: "problem",
1877
1942
  schema: [
@@ -1955,8 +2020,8 @@ var codeConstructor = defineRule12({
1955
2020
  var codeDoubleNegation = astSelectorRule('UnaryExpression[operator="!"] > UnaryExpression[operator="!"]', "Use Boolean(value) or an explicit comparison.");
1956
2021
 
1957
2022
  // src/rules/code/code-function-nesting.ts
1958
- import { defineRule as defineRule13 } from "@oxlint/plugins";
1959
- var codeFunctionNesting = defineRule13({
2023
+ import { defineRule as defineRule14 } from "@oxlint/plugins";
2024
+ var codeFunctionNesting = defineRule14({
1960
2025
  meta: {
1961
2026
  type: "suggestion",
1962
2027
  schema: [
@@ -1997,7 +2062,7 @@ var codeFunctionNesting = defineRule13({
1997
2062
  });
1998
2063
 
1999
2064
  // src/rules/code/code-inline.ts
2000
- import { defineRule as defineRule14 } from "@oxlint/plugins";
2065
+ import { defineRule as defineRule15 } from "@oxlint/plugins";
2001
2066
 
2002
2067
  // src/utils/source.utils.ts
2003
2068
  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 +2103,9 @@ function sourceLineEnd(text, offset) {
2038
2103
  `, offset);
2039
2104
  return end === -1 ? text.length : end;
2040
2105
  }
2106
+ function sourceJoinedWidth(text, start, joined, end) {
2107
+ return start - sourceLineStart(text, start) + joined.length + text.slice(end, sourceLineEnd(text, end)).trimEnd().length;
2108
+ }
2041
2109
  function sourceItemGaps(open, items, close) {
2042
2110
  const edges = [open, ...items.flatMap((item) => item.range), close];
2043
2111
  return Array.from({ length: items.length + 1 }, (_, index) => [edges[index * 2] ?? open, edges[index * 2 + 1] ?? close]);
@@ -2066,7 +2134,20 @@ function codeInlineEnclosingCalls(node) {
2066
2134
  return outer && astIsOneLine(outer) ? 1 + codeInlineEnclosingCalls(outer) : 0;
2067
2135
  }
2068
2136
  var codeInlineWrapped = new Set(["ConditionalExpression", "LogicalExpression", "BinaryExpression", "ArrowFunctionExpression"]);
2069
- var codeInline = defineRule14({
2137
+ var codeInlineEffects = new Set([
2138
+ "CallExpression",
2139
+ "NewExpression",
2140
+ "AssignmentExpression",
2141
+ "UpdateExpression",
2142
+ "AwaitExpression",
2143
+ "YieldExpression",
2144
+ "TaggedTemplateExpression",
2145
+ "ImportExpression"
2146
+ ]);
2147
+ function codeInlineEffectBetween(program, start, end) {
2148
+ return astSome(program, (node) => node.range[0] >= start && node.range[1] <= end && (codeInlineEffects.has(node.type) || node.type === "UnaryExpression" && node.operator === "delete"), (node) => node.range[1] > start && node.range[0] < end && !(node.range[0] >= start && (node.type === "FunctionDeclaration" || astIsInlineFunction(node))));
2149
+ }
2150
+ var codeInline = defineRule15({
2070
2151
  meta: {
2071
2152
  type: "suggestion",
2072
2153
  schema: [
@@ -2104,6 +2185,9 @@ var codeInline = defineRule14({
2104
2185
  return;
2105
2186
  }
2106
2187
  if (uses.length === 1) {
2188
+ if (init && codeInlineEffectBetween(context.sourceCode.ast, node.range[1], uses[0].identifier.range[0])) {
2189
+ return;
2190
+ }
2107
2191
  context.report({
2108
2192
  node: id,
2109
2193
  messageId: "once",
@@ -2141,7 +2225,7 @@ var codeInline = defineRule14({
2141
2225
  deferred ||= current.type === "FunctionDeclaration" || astIsInlineFunction(current);
2142
2226
  }
2143
2227
  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)) {
2228
+ if (service || wrapped || deferred || codeInlineEnclosingCalls(use) + astCallDepth(init) > (options.nesting ?? 2) || line.length - id.name.length + context.sourceCode.getText(init).length > (options.max ?? 160) || codeInlineEffectBetween(context.sourceCode.ast, declarator.range[1], use.range[0])) {
2145
2229
  return;
2146
2230
  }
2147
2231
  context.report({
@@ -2176,8 +2260,8 @@ var codeInline = defineRule14({
2176
2260
  });
2177
2261
 
2178
2262
  // src/rules/code/code-justified-call.ts
2179
- import { defineRule as defineRule15 } from "@oxlint/plugins";
2180
- var codeJustifiedCall = defineRule15({
2263
+ import { defineRule as defineRule16 } from "@oxlint/plugins";
2264
+ var codeJustifiedCall = defineRule16({
2181
2265
  meta: {
2182
2266
  type: "problem",
2183
2267
  schema: [
@@ -2231,8 +2315,8 @@ var codeJustifiedCall = defineRule15({
2231
2315
  });
2232
2316
 
2233
2317
  // src/rules/code/code-literal-in.ts
2234
- import { defineRule as defineRule16 } from "@oxlint/plugins";
2235
- var codeLiteralIn = defineRule16({
2318
+ import { defineRule as defineRule17 } from "@oxlint/plugins";
2319
+ var codeLiteralIn = defineRule17({
2236
2320
  meta: {
2237
2321
  type: "suggestion",
2238
2322
  schema: [
@@ -2273,7 +2357,7 @@ var codeLiteralIn = defineRule16({
2273
2357
  });
2274
2358
 
2275
2359
  // src/rules/code/code-mutable-state.ts
2276
- import { defineRule as defineRule17 } from "@oxlint/plugins";
2360
+ import { defineRule as defineRule18 } from "@oxlint/plugins";
2277
2361
  var functionScopes = new Set([
2278
2362
  "FunctionDeclaration",
2279
2363
  "FunctionExpression",
@@ -2290,7 +2374,7 @@ function hasAncestor(node, test) {
2290
2374
  }
2291
2375
  return false;
2292
2376
  }
2293
- var codeMutableState = defineRule17({
2377
+ var codeMutableState = defineRule18({
2294
2378
  meta: {
2295
2379
  type: "problem",
2296
2380
  schema: [
@@ -2348,7 +2432,7 @@ var codeMutableState = defineRule17({
2348
2432
  });
2349
2433
 
2350
2434
  // src/rules/code/code-object-params.ts
2351
- import { defineRule as defineRule18 } from "@oxlint/plugins";
2435
+ import { defineRule as defineRule19 } from "@oxlint/plugins";
2352
2436
  function isObjectParam(node) {
2353
2437
  if (node.type === "ObjectPattern") {
2354
2438
  return true;
@@ -2362,7 +2446,7 @@ function isObjectParam(node) {
2362
2446
  const annotation = node.typeAnnotation?.typeAnnotation;
2363
2447
  return !annotation || annotation.type === "TSTypeReference" || annotation.type === "TSTypeLiteral" || annotation.type === "TSIntersectionType";
2364
2448
  }
2365
- var codeObjectParams = defineRule18({
2449
+ var codeObjectParams = defineRule19({
2366
2450
  meta: {
2367
2451
  type: "problem",
2368
2452
  schema: [
@@ -2411,8 +2495,8 @@ var codeObjectParams = defineRule18({
2411
2495
  });
2412
2496
 
2413
2497
  // src/rules/code/code-paired-call.ts
2414
- import { defineRule as defineRule19 } from "@oxlint/plugins";
2415
- var codePairedCall = defineRule19({
2498
+ import { defineRule as defineRule20 } from "@oxlint/plugins";
2499
+ var codePairedCall = defineRule20({
2416
2500
  meta: {
2417
2501
  type: "problem",
2418
2502
  schema: [
@@ -2443,7 +2527,6 @@ var codePairedCall = defineRule19({
2443
2527
  }
2444
2528
  },
2445
2529
  createOnce(context) {
2446
- const aliases = new Map;
2447
2530
  let pairs = [];
2448
2531
  return {
2449
2532
  before() {
@@ -2451,26 +2534,10 @@ var codePairedCall = defineRule19({
2451
2534
  const all = when && require2 ? [{ when, require: require2 }, ...configured] : configured;
2452
2535
  pairs = all.map((pair) => ({ ...pair, firstWhen: null, sawRequired: false }));
2453
2536
  },
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
2537
  CallExpression(node) {
2472
- const called = astCallName(node);
2473
- const name = called ? aliases.get(called) ?? called : undefined;
2538
+ const callee = node.callee;
2539
+ const binding = callee.type === "Identifier" ? boundariesDeclaredVariable(context.sourceCode.getScope(node), callee.name)?.defs.at(0)?.node : undefined;
2540
+ const name = binding?.type === "ImportSpecifier" ? astModuleName(binding.imported) : astCallName(node);
2474
2541
  for (const pair of pairs) {
2475
2542
  if (name === pair.when) {
2476
2543
  pair.firstWhen ??= node;
@@ -2496,7 +2563,7 @@ var codePairedCall = defineRule19({
2496
2563
  });
2497
2564
 
2498
2565
  // src/rules/code/code-promise-mutation.ts
2499
- import { defineRule as defineRule20 } from "@oxlint/plugins";
2566
+ import { defineRule as defineRule21 } from "@oxlint/plugins";
2500
2567
  function mutationBinding(node) {
2501
2568
  if (node.type === "Identifier") {
2502
2569
  return node;
@@ -2525,7 +2592,7 @@ function mutationBindings(node) {
2525
2592
  const binding = mutationBinding(node);
2526
2593
  return binding ? [binding] : [];
2527
2594
  }
2528
- var codePromiseMutation = defineRule20({
2595
+ var codePromiseMutation = defineRule21({
2529
2596
  meta: {
2530
2597
  type: "problem",
2531
2598
  schema: [
@@ -2618,8 +2685,8 @@ var codePromiseMutation = defineRule20({
2618
2685
  var codeSwitch = astSelectorRule("SwitchStatement", "Avoid switch statements.");
2619
2686
 
2620
2687
  // src/rules/code/code-traced.ts
2621
- import { defineRule as defineRule21 } from "@oxlint/plugins";
2622
- var codeTraced = defineRule21({
2688
+ import { defineRule as defineRule22 } from "@oxlint/plugins";
2689
+ var codeTraced = defineRule22({
2623
2690
  meta: {
2624
2691
  type: "problem",
2625
2692
  schema: [
@@ -2668,17 +2735,17 @@ var codeTraced = defineRule21({
2668
2735
  message = options.message ?? `An exported function or service method gets a span: replace ${[...untraced].join(", ")} with its traced form.`;
2669
2736
  },
2670
2737
  ExportNamedDeclaration(node) {
2671
- if (!exported || node.declaration?.type !== "VariableDeclaration") {
2738
+ if (!exported) {
2672
2739
  return;
2673
2740
  }
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
- }
2741
+ const declarators = node.declaration?.type === "VariableDeclaration" ? node.declaration.declarations.filter((declarator) => isUntraced(declarator.init)) : [];
2742
+ const specifiers = node.source ? [] : node.specifiers.filter((specifier) => resolves(specifier.local));
2743
+ for (const surface of [...declarators, ...specifiers]) {
2744
+ context.report({
2745
+ node: surface,
2746
+ messageId: "surface",
2747
+ data: { message }
2748
+ });
2682
2749
  }
2683
2750
  },
2684
2751
  CallExpression(node) {
@@ -2706,7 +2773,7 @@ var codeTraced = defineRule21({
2706
2773
  });
2707
2774
 
2708
2775
  // src/rules/code/code-trivial-functions.ts
2709
- import { defineRule as defineRule22 } from "@oxlint/plugins";
2776
+ import { defineRule as defineRule23 } from "@oxlint/plugins";
2710
2777
  var codeTrivialFunctionsBanned = [
2711
2778
  "isRecord",
2712
2779
  "isPlainObject",
@@ -2836,7 +2903,7 @@ function guardStatement(node, state) {
2836
2903
  return guardFallback(declaration.init);
2837
2904
  });
2838
2905
  }
2839
- var codeTrivialFunctions = defineRule22({
2906
+ var codeTrivialFunctions = defineRule23({
2840
2907
  meta: {
2841
2908
  type: "problem",
2842
2909
  schema: [
@@ -2967,8 +3034,8 @@ var codeTrivialFunctions = defineRule22({
2967
3034
  });
2968
3035
 
2969
3036
  // src/rules/code/code-yield-argument.ts
2970
- import { defineRule as defineRule23 } from "@oxlint/plugins";
2971
- var codeYieldArgument = defineRule23({
3037
+ import { defineRule as defineRule24 } from "@oxlint/plugins";
3038
+ var codeYieldArgument = defineRule24({
2972
3039
  meta: {
2973
3040
  type: "suggestion",
2974
3041
  messages: {
@@ -2990,7 +3057,7 @@ var codeYieldArgument = defineRule23({
2990
3057
  });
2991
3058
 
2992
3059
  // src/rules/comments/comments-code.ts
2993
- import { defineRule as defineRule24 } from "@oxlint/plugins";
3060
+ import { defineRule as defineRule25 } from "@oxlint/plugins";
2994
3061
  var commentsLineGap = /^[\t \r\n\u2028\u2029]*$/;
2995
3062
  var commentsLineTerminator = /\r\n|[\n\r\u2028\u2029]/;
2996
3063
  var commentsUnicodeTerminator = /[\u2028\u2029]/;
@@ -3041,7 +3108,7 @@ function commentsFollowsOpeningBrace(text, offset) {
3041
3108
  }
3042
3109
  return false;
3043
3110
  }
3044
- var commentsCode = defineRule24({
3111
+ var commentsCode = defineRule25({
3045
3112
  meta: {
3046
3113
  type: "suggestion",
3047
3114
  fixable: "code",
@@ -3050,7 +3117,7 @@ var commentsCode = defineRule24({
3050
3117
  type: "object",
3051
3118
  additionalProperties: false,
3052
3119
  properties: {
3053
- why: { type: "boolean", default: true },
3120
+ why: { anyOf: [{ type: "boolean" }, { type: "string", minLength: 1 }], default: true },
3054
3121
  allow: { type: "array", items: { type: "string" } },
3055
3122
  jsdoc: { type: "boolean", default: false },
3056
3123
  members: { type: "string", enum: ["off", "also", "only"] }
@@ -3064,6 +3131,7 @@ var commentsCode = defineRule24({
3064
3131
  },
3065
3132
  createOnce(context) {
3066
3133
  let allowWhy = true;
3134
+ let whyPrefix = "Why: ";
3067
3135
  let allowJsdoc = false;
3068
3136
  let allowed = [];
3069
3137
  let members = "off";
@@ -3088,7 +3156,8 @@ var commentsCode = defineRule24({
3088
3156
  return {
3089
3157
  before() {
3090
3158
  const options = optionsFirst(context, {});
3091
- allowWhy = options.why ?? true;
3159
+ allowWhy = options.why !== false;
3160
+ whyPrefix = `${typeof options.why === "string" ? options.why.trim() : "Why:"} `;
3092
3161
  allowJsdoc = options.jsdoc ?? false;
3093
3162
  allowed = (options.allow ?? []).map((pattern) => new RegExp(pattern));
3094
3163
  members = options.members ?? "off";
@@ -3117,7 +3186,7 @@ var commentsCode = defineRule24({
3117
3186
  mightContainJsxComment = true;
3118
3187
  }
3119
3188
  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;
3189
+ const isWhy = value.startsWith(whyPrefix) || comment.type === "Line" && whyContinuation && followsPrevious;
3121
3190
  if (/^(?:eslint|oxlint)-disable-next-line\b/.test(value)) {
3122
3191
  protectedLines.push(comment.loc.end.line + 1);
3123
3192
  }
@@ -3205,9 +3274,9 @@ var commentsCode = defineRule24({
3205
3274
  });
3206
3275
 
3207
3276
  // src/rules/comments/comments-directives.ts
3208
- import { defineRule as defineRule25 } from "@oxlint/plugins";
3277
+ import { defineRule as defineRule26 } from "@oxlint/plugins";
3209
3278
  var directive = /^\s*(?:oxlint|eslint)-disable(?:-next-line|-line)?\b(.*)$/s;
3210
- var commentsDirectives = defineRule25({
3279
+ var commentsDirectives = defineRule26({
3211
3280
  meta: {
3212
3281
  type: "problem",
3213
3282
  schema: [],
@@ -3235,9 +3304,9 @@ var commentsDirectives = defineRule25({
3235
3304
  });
3236
3305
 
3237
3306
  // src/rules/copy/copy-dashes.ts
3238
- import { defineRule as defineRule26 } from "@oxlint/plugins";
3307
+ import { defineRule as defineRule27 } from "@oxlint/plugins";
3239
3308
  var copyDashesMessage = "Use a comma, colon, period or parentheses instead.";
3240
- var copyDashes = defineRule26({
3309
+ var copyDashes = defineRule27({
3241
3310
  meta: {
3242
3311
  type: "suggestion",
3243
3312
  schema: [
@@ -3281,19 +3350,694 @@ var copyDashes = defineRule26({
3281
3350
  return false;
3282
3351
  }
3283
3352
  },
3284
- Literal(node) {
3285
- if (typeof node.value === "string") {
3286
- check(node);
3353
+ Literal(node) {
3354
+ if (typeof node.value === "string") {
3355
+ check(node);
3356
+ }
3357
+ },
3358
+ TemplateElement: check,
3359
+ JSXText: check
3360
+ };
3361
+ }
3362
+ });
3363
+
3364
+ // src/rules/effect/effect-layer-file.ts
3365
+ import { defineRule as defineRule28 } from "@oxlint/plugins";
3366
+
3367
+ // src/utils/effect.utils.ts
3368
+ var effectBuilders = ["Effect.gen", "Effect.fn", "Effect.fnUntraced"];
3369
+ function effectIsBuilder(node, builders) {
3370
+ const value = node ? astUnwrap(node) : undefined;
3371
+ if (value?.type !== "CallExpression") {
3372
+ return false;
3373
+ }
3374
+ const callee = value.callee.type === "CallExpression" ? value.callee.callee : value.callee;
3375
+ return builders.has(astDottedName(callee) ?? "");
3376
+ }
3377
+ function effectGenerator(node, builders) {
3378
+ const value = node ? astUnwrap(node) : undefined;
3379
+ return effectIsBuilder(value, builders) ? value.arguments.find((argument) => argument.type === "FunctionExpression") : undefined;
3380
+ }
3381
+ function effectMatches(node, patterns) {
3382
+ if (!node || node.type === "Super") {
3383
+ return false;
3384
+ }
3385
+ const value = astUnwrap(node);
3386
+ const target = value.type === "CallExpression" ? value.callee : value;
3387
+ const name = astDottedName(target);
3388
+ if (name !== undefined && optionsPatternsTest(patterns, name)) {
3389
+ return true;
3390
+ }
3391
+ return value.type === "CallExpression" && target.type === "MemberExpression" && astMemberName(target) === "pipe" && effectMatches(target.object, patterns);
3392
+ }
3393
+
3394
+ // src/rules/effect/effect-layer-file.ts
3395
+ var effectLayerFileDefaultLayers = ["^Layer\\.", "^HttpRouter\\.add(?:All)?$", "(?:^|\\.)layer[^.]*$"];
3396
+ var effectLayerFileDefaultServices = ["(?:^|\\.)of$", "^Context\\."];
3397
+ var effectLayerFileDefaultConstructors = [
3398
+ "Layer.effect",
3399
+ "Layer.effectContext",
3400
+ "Layer.effectDiscard",
3401
+ "Layer.scoped",
3402
+ "Layer.scopedContext",
3403
+ "Layer.scopedDiscard",
3404
+ "Layer.sync",
3405
+ "Layer.unwrap"
3406
+ ];
3407
+ var effectLayerFile = defineRule28({
3408
+ meta: {
3409
+ type: "problem",
3410
+ schema: [
3411
+ {
3412
+ type: "object",
3413
+ additionalProperties: false,
3414
+ properties: {
3415
+ layer: { type: "string" },
3416
+ make: { type: "string" },
3417
+ prefix: { type: "string" },
3418
+ builders: { type: "array", items: { type: "string" } },
3419
+ layers: optionsPatternSchema,
3420
+ services: optionsPatternSchema,
3421
+ constructors: { type: "array", items: { type: "string" } }
3422
+ }
3423
+ }
3424
+ ],
3425
+ messages: {
3426
+ missing: "A layer file exports `{{layer}}`.",
3427
+ notLayer: "`{{layer}}` is built with Layer: a Layer call, a Layer pipe, or a function returning one.",
3428
+ builderName: "The Effect that builds the service is named `{{make}}`, one per layer file.",
3429
+ moduleMethod: "Only `{{make}}` is an Effect at module level; move this into `{{make}}`, or into a utils file if two files use it.",
3430
+ moduleHelper: "A layer file holds only `{{make}}`, {{prefix}}<Role> layers and `{{layer}}`; move this into `{{make}}`, or into a utils file if two files use it.",
3431
+ localLayer: "A Layer other than `{{layer}}` is named {{prefix}}<Role> and is not exported.",
3432
+ makeReturn: "`{{make}}` returns the service through its tag (Tag.of({ … })), a Context, or a Layer.",
3433
+ inlineBuilder: "Build this layer from `{{make}}`, not an inline Effect: Layer.effect(Tag, {{make}}). One built layer per file."
3434
+ }
3435
+ },
3436
+ createOnce(context) {
3437
+ let layer = "layer";
3438
+ let make = "make";
3439
+ let prefix = "layer";
3440
+ let builders = new Set(effectBuilders);
3441
+ let layers = [];
3442
+ let services = [];
3443
+ let constructors = new Set(effectLayerFileDefaultConstructors);
3444
+ const returned = (node) => {
3445
+ const value = node ? astUnwrap(node) : node;
3446
+ if (!value || !astIsInlineFunction(value)) {
3447
+ return value;
3448
+ }
3449
+ return value.body?.type === "BlockStatement" ? returned(value.body.body.findLast((statement) => statement.type === "ReturnStatement")?.argument) : returned(value.body);
3450
+ };
3451
+ return {
3452
+ before() {
3453
+ const options = optionsFirst(context, {});
3454
+ layer = options.layer ?? "layer";
3455
+ make = options.make ?? "make";
3456
+ prefix = options.prefix ?? "layer";
3457
+ builders = new Set(options.builders ?? effectBuilders);
3458
+ constructors = new Set(options.constructors ?? effectLayerFileDefaultConstructors);
3459
+ layers = optionsPatterns(options.layers ?? effectLayerFileDefaultLayers);
3460
+ services = [...optionsPatterns(options.services ?? effectLayerFileDefaultServices), ...layers];
3461
+ },
3462
+ Program(program) {
3463
+ const data = { layer, make, prefix };
3464
+ const declarators = program.body.flatMap((statement) => {
3465
+ const declaration = astDeclaration(statement);
3466
+ const exported = statement.type === "ExportNamedDeclaration";
3467
+ return declaration?.type === "VariableDeclaration" ? declaration.declarations.map((node) => ({ exported, node, name: node.id.type === "Identifier" ? node.id.name : "" })) : [];
3468
+ });
3469
+ const exportedLayer = declarators.find(({ exported, name }) => exported && name === layer);
3470
+ if (!exportedLayer) {
3471
+ context.report({
3472
+ node: program,
3473
+ messageId: "missing",
3474
+ data
3475
+ });
3476
+ } else if (!effectMatches(returned(exportedLayer.node.init), layers)) {
3477
+ context.report({
3478
+ node: exportedLayer.node,
3479
+ messageId: "notLayer",
3480
+ data
3481
+ });
3482
+ }
3483
+ const wired = new Set;
3484
+ const wiring = declarators.filter((item) => item === exportedLayer || effectMatches(item.node.init, layers));
3485
+ astVisit(wiring.map(({ node }) => node.init), [], (call) => {
3486
+ if (call.type !== "CallExpression" || !optionsPatternsTest(layers, astDottedName(call.callee) ?? "")) {
3487
+ return;
3488
+ }
3489
+ for (const argument of call.arguments) {
3490
+ const target = argument.type === "CallExpression" ? argument.callee : argument;
3491
+ if (target.type === "Identifier") {
3492
+ wired.add(target.name);
3493
+ }
3494
+ }
3495
+ });
3496
+ const locals = declarators.filter(({ exported }) => !exported);
3497
+ const effects = locals.filter(({ node }) => effectIsBuilder(node.init, builders));
3498
+ const builder = effects.find(({ name }) => name === make) ?? effects.find(({ name }) => wired.has(name));
3499
+ if (builder && builder.name !== make) {
3500
+ context.report({
3501
+ node: builder.node,
3502
+ messageId: "builderName",
3503
+ data
3504
+ });
3505
+ }
3506
+ for (const { node, name, exported } of declarators) {
3507
+ const isLayer = effectMatches(node.init, layers);
3508
+ const isEffect = effectIsBuilder(node.init, builders);
3509
+ if (!exported && isEffect && name !== builder?.name) {
3510
+ context.report({
3511
+ node,
3512
+ messageId: "moduleMethod",
3513
+ data
3514
+ });
3515
+ }
3516
+ if (!exported && !isEffect && !isLayer && name !== make) {
3517
+ context.report({
3518
+ node,
3519
+ messageId: "moduleHelper",
3520
+ data
3521
+ });
3522
+ }
3523
+ if (isLayer && name !== layer && (exported || !name.startsWith(prefix) || !/^[A-Z]\w*$/.test(name.slice(prefix.length)))) {
3524
+ context.report({
3525
+ node,
3526
+ messageId: "localLayer",
3527
+ data
3528
+ });
3529
+ }
3530
+ }
3531
+ for (const statement of program.body) {
3532
+ if (statement.type === "FunctionDeclaration") {
3533
+ context.report({
3534
+ node: statement,
3535
+ messageId: "moduleHelper",
3536
+ data
3537
+ });
3538
+ }
3539
+ }
3540
+ const generator = builder?.name === make ? effectGenerator(builder.node.init, builders) : undefined;
3541
+ for (const statement of generator?.body?.body ?? []) {
3542
+ if (statement.type === "ReturnStatement" && !effectMatches(statement.argument, services)) {
3543
+ context.report({
3544
+ node: statement,
3545
+ messageId: "makeReturn",
3546
+ data
3547
+ });
3548
+ }
3549
+ }
3550
+ },
3551
+ CallExpression(node) {
3552
+ if (!constructors.has(astDottedName(node.callee) ?? "")) {
3553
+ return;
3554
+ }
3555
+ for (const argument of node.arguments) {
3556
+ if (argument.type !== "SpreadElement" && (astIsInlineFunction(astUnwrap(argument)) || effectIsBuilder(argument, builders))) {
3557
+ context.report({
3558
+ node: argument,
3559
+ messageId: "inlineBuilder",
3560
+ data: { layer, make, prefix }
3561
+ });
3562
+ }
3563
+ }
3564
+ }
3565
+ };
3566
+ }
3567
+ });
3568
+
3569
+ // src/rules/effect/effect-layers.ts
3570
+ import { defineRule as defineRule29 } from "@oxlint/plugins";
3571
+ var effectLayersDefaultMerge = ["Layer.mergeAll"];
3572
+ var effectLayersDefaultProvide = ["Effect.provide", "Layer.provide", "Layer.provideMerge"];
3573
+ var effectLayersDefaultMin = 2;
3574
+ var effectLayersGroupSchema = {
3575
+ type: "object",
3576
+ additionalProperties: false,
3577
+ properties: {
3578
+ callees: { type: "array", items: { type: "string" } },
3579
+ min: { type: "integer", minimum: 1 }
3580
+ }
3581
+ };
3582
+ var effectLayers = defineRule29({
3583
+ meta: {
3584
+ type: "layout",
3585
+ fixable: "whitespace",
3586
+ schema: [
3587
+ {
3588
+ type: "object",
3589
+ additionalProperties: false,
3590
+ properties: {
3591
+ merge: effectLayersGroupSchema,
3592
+ provide: effectLayersGroupSchema,
3593
+ indent: sourceIndentSchema
3594
+ }
3595
+ }
3596
+ ],
3597
+ messages: {
3598
+ merge: "Put every layer in this merge on its own line.",
3599
+ provide: "Put every layer in this array on its own line."
3600
+ }
3601
+ },
3602
+ createOnce(context) {
3603
+ let merge = new Set(effectLayersDefaultMerge);
3604
+ let provide = new Set(effectLayersDefaultProvide);
3605
+ let mergeMin = effectLayersDefaultMin;
3606
+ let provideMin = effectLayersDefaultMin;
3607
+ let indentUnit = sourceIndentUnit();
3608
+ const check = (node) => {
3609
+ const source = context.sourceCode;
3610
+ const call = node.type === "CallExpression";
3611
+ const items = call ? node.arguments : node.elements;
3612
+ const present = items.filter((item) => item !== null);
3613
+ const open = call ? source.getTokenAfter(node.typeArguments ?? node.callee) : source.getFirstToken(node);
3614
+ const close = source.getLastToken(node);
3615
+ if (open?.value !== (call ? "(" : "[") || close === null || present.length < (call ? mergeMin : provideMin)) {
3616
+ return;
3617
+ }
3618
+ const packed = [open, ...present].some((item, index) => item.loc.end.line === (present.at(index) ?? close).loc.start.line);
3619
+ if (!packed) {
3620
+ return;
3621
+ }
3622
+ const text = source.text;
3623
+ const newline = sourceNewline(text);
3624
+ const base = sourceLineIndent(text, open.range[0]);
3625
+ const gaps = sourceItemGaps(open.range[1], present, close.range[0]);
3626
+ context.report({
3627
+ node,
3628
+ messageId: call ? "merge" : "provide",
3629
+ fix: (fixer) => {
3630
+ if (present.length !== items.length) {
3631
+ return null;
3632
+ }
3633
+ return sourceRespace(fixer, text, gaps, (_, end) => `${newline}${base}${end ? "" : indentUnit}`);
3634
+ }
3635
+ });
3636
+ };
3637
+ return {
3638
+ before() {
3639
+ const options = optionsFirst(context, {});
3640
+ merge = new Set(options.merge?.callees ?? effectLayersDefaultMerge);
3641
+ provide = new Set(options.provide?.callees ?? effectLayersDefaultProvide);
3642
+ mergeMin = options.merge?.min ?? effectLayersDefaultMin;
3643
+ provideMin = options.provide?.min ?? effectLayersDefaultMin;
3644
+ indentUnit = sourceIndentUnit(options.indent);
3645
+ },
3646
+ CallExpression(node) {
3647
+ const name = astDottedName(node.callee);
3648
+ if (name !== undefined && merge.has(name)) {
3649
+ check(node);
3650
+ }
3651
+ if (name !== undefined && provide.has(name)) {
3652
+ for (const argument of node.arguments) {
3653
+ if (argument.type === "ArrayExpression") {
3654
+ check(argument);
3655
+ }
3656
+ }
3657
+ }
3658
+ }
3659
+ };
3660
+ }
3661
+ });
3662
+
3663
+ // src/rules/effect/effect-merge-pipe.ts
3664
+ import { defineRule as defineRule30 } from "@oxlint/plugins";
3665
+ var effectMergePipeDefaultMerge = ["Layer.mergeAll"];
3666
+ var effectMergePipeDefaultPipe = ["pipe", "Function.pipe"];
3667
+ var effectMergePipeDefaultMethods = ["pipe"];
3668
+ var effectMergePipe = defineRule30({
3669
+ meta: {
3670
+ type: "layout",
3671
+ schema: [
3672
+ {
3673
+ type: "object",
3674
+ additionalProperties: false,
3675
+ properties: {
3676
+ merge: { type: "array", items: { type: "string" } },
3677
+ pipe: { type: "array", items: { type: "string" } },
3678
+ methods: { type: "array", items: { type: "string" } }
3679
+ }
3680
+ }
3681
+ ],
3682
+ messages: {
3683
+ piped: "A merge that spans lines is never piped. Name it first, then pipe the name."
3684
+ }
3685
+ },
3686
+ createOnce(context) {
3687
+ let merge = new Set(effectMergePipeDefaultMerge);
3688
+ let pipe = new Set(effectMergePipeDefaultPipe);
3689
+ let methods = new Set(effectMergePipeDefaultMethods);
3690
+ const isMultilineMerge = (node) => node?.type === "CallExpression" && merge.has(astDottedName(node.callee) ?? "") && !astIsOneLine(node);
3691
+ return {
3692
+ before() {
3693
+ const options = optionsFirst(context, {});
3694
+ merge = new Set(options.merge ?? effectMergePipeDefaultMerge);
3695
+ pipe = new Set(options.pipe ?? effectMergePipeDefaultPipe);
3696
+ methods = new Set(options.methods ?? effectMergePipeDefaultMethods);
3697
+ },
3698
+ CallExpression(node) {
3699
+ const callee = node.callee;
3700
+ const first = node.arguments.at(0);
3701
+ if (callee.type === "MemberExpression" && methods.has(astMemberName(callee) ?? "") && isMultilineMerge(callee.object)) {
3702
+ context.report({ node: callee.property, messageId: "piped" });
3703
+ }
3704
+ if (pipe.has(astDottedName(callee) ?? "") && first !== undefined && isMultilineMerge(first)) {
3705
+ context.report({ node: first, messageId: "piped" });
3706
+ }
3707
+ }
3708
+ };
3709
+ }
3710
+ });
3711
+
3712
+ // src/rules/effect/effect-schema-first.ts
3713
+ import { defineRule as defineRule31 } from "@oxlint/plugins";
3714
+ var effectSchemaFirstDefaultRuntime = [
3715
+ "Effect",
3716
+ "Layer",
3717
+ "Stream",
3718
+ "Context",
3719
+ "Scope",
3720
+ "Kysely",
3721
+ "React",
3722
+ "Fiber",
3723
+ "Queue",
3724
+ "PubSub",
3725
+ "Ref",
3726
+ "Deferred",
3727
+ "ManagedRuntime",
3728
+ "Sink",
3729
+ "Channel",
3730
+ "Request",
3731
+ "Response",
3732
+ "Headers",
3733
+ "ReadableStream",
3734
+ "WritableStream",
3735
+ "Blob",
3736
+ "File",
3737
+ "AbortSignal",
3738
+ "WebSocket",
3739
+ "Socket",
3740
+ "Worker",
3741
+ "Uint8Array",
3742
+ "ArrayBuffer",
3743
+ "globalThis"
3744
+ ];
3745
+ var effectSchemaFirstSections = {
3746
+ values: ["VariableDeclaration", "ClassDeclaration"],
3747
+ interfaces: ["TSInterfaceDeclaration"],
3748
+ modules: ["TSModuleDeclaration"],
3749
+ aliases: ["TSTypeAliasDeclaration"]
3750
+ };
3751
+ var effectSchemaFirstLabels = {
3752
+ values: "Schemas and values",
3753
+ interfaces: "interfaces",
3754
+ modules: "`declare module`",
3755
+ aliases: "type aliases"
3756
+ };
3757
+ var effectSchemaFirstDefaultOrder = ["values", ["interfaces", "modules"], "aliases"];
3758
+ var effectSchemaFirstSectionSchema = { type: "string", enum: ["values", "interfaces", "modules", "aliases"] };
3759
+ function effectSchemaFirstTypeName(node) {
3760
+ if (node.type === "TSQualifiedName") {
3761
+ const left = effectSchemaFirstTypeName(node.left);
3762
+ return left === undefined ? undefined : `${left}.${node.right.name}`;
3763
+ }
3764
+ return astDottedName(node);
3765
+ }
3766
+ function effectSchemaFirstReference(node) {
3767
+ if (node.type === "TSTypeReference") {
3768
+ return effectSchemaFirstTypeName(node.typeName);
3769
+ }
3770
+ if (node.type === "TSTypeQuery") {
3771
+ return effectSchemaFirstTypeName(node.exprName);
3772
+ }
3773
+ return node.type === "TSInterfaceHeritage" ? astDottedName(node.expression) : undefined;
3774
+ }
3775
+ var effectSchemaFirst = defineRule31({
3776
+ meta: {
3777
+ type: "suggestion",
3778
+ schema: [
3779
+ {
3780
+ type: "object",
3781
+ additionalProperties: false,
3782
+ properties: {
3783
+ member: { type: "string" },
3784
+ keep: optionsPatternSchema,
3785
+ runtime: { type: "array", items: { type: "string" } },
3786
+ rows: {
3787
+ type: "object",
3788
+ additionalProperties: false,
3789
+ properties: { types: { type: "array", items: { type: "string" } }, suffix: { type: "string" } }
3790
+ },
3791
+ order: {
3792
+ type: "array",
3793
+ items: { anyOf: [effectSchemaFirstSectionSchema, { type: "array", items: effectSchemaFirstSectionSchema, minItems: 1 }] }
3794
+ },
3795
+ derived: {
3796
+ type: "object",
3797
+ additionalProperties: false,
3798
+ properties: { first: { type: "boolean" }, sorted: { type: "boolean" } }
3799
+ }
3800
+ }
3801
+ }
3802
+ ],
3803
+ messages: {
3804
+ order: "Order a types file as {{order}}.",
3805
+ schemaType: "Add `export type {{name}} = typeof {{name}}.{{member}}` to the final type block.",
3806
+ typeOrder: "Move {{name}}: the type block lists the `typeof` aliases first, in Schema order, then the other aliases.",
3807
+ dataInterface: "Every field of {{name}} fits a Schema: make it a Schema and derive its type.",
3808
+ rowItem: "{{name}} is built from a database row: make it a Schema; only a `…{{suffix}}` alias may read the row type."
3809
+ }
3810
+ },
3811
+ createOnce(context) {
3812
+ let member = "Type";
3813
+ let keep = [];
3814
+ let runtime = new Set(effectSchemaFirstDefaultRuntime);
3815
+ let rows;
3816
+ let suffix = "Record";
3817
+ let ranks = new Map;
3818
+ let orderText = "";
3819
+ let derivedFirst = true;
3820
+ let derivedSorted = true;
3821
+ const derives = (alias) => {
3822
+ const annotation = alias.typeAnnotation;
3823
+ const name = annotation.type === "TSTypeQuery" ? annotation.exprName : undefined;
3824
+ return name?.type === "TSQualifiedName" && name.left.type === "Identifier" && name.left.name === alias.id.name && name.right.name === member;
3825
+ };
3826
+ const refers = (node, names) => astSome(node, (child) => {
3827
+ if (child.type === "TSFunctionType" || child.type === "TSConstructorType" || child.type === "TSMethodSignature") {
3828
+ return true;
3829
+ }
3830
+ return (effectSchemaFirstReference(child)?.split(".") ?? []).some((name) => names.has(name));
3831
+ });
3832
+ const readsRow = (node) => astSome(node, (child) => Boolean(rows?.has(effectSchemaFirstReference(child) ?? "")));
3833
+ const checkOrder = (program) => {
3834
+ let highest = 0;
3835
+ for (const statement of program.body) {
3836
+ const rank = ranks.get(astDeclaration(statement)?.type ?? "") ?? 0;
3837
+ if (rank > 0 && rank < highest) {
3838
+ context.report({
3839
+ node: statement,
3840
+ messageId: "order",
3841
+ data: { order: orderText }
3842
+ });
3843
+ }
3844
+ highest = Math.max(highest, rank);
3845
+ }
3846
+ };
3847
+ const keptNames = (program, aliases, interfaces) => {
3848
+ const kept = new Set(aliases.filter((alias) => refers(alias.typeAnnotation, runtime)).map((alias) => alias.id.name));
3849
+ for (const statement of program.body) {
3850
+ if (statement.type === "ImportDeclaration" && statement.source.value.startsWith(".")) {
3851
+ for (const specifier of statement.specifiers) {
3852
+ if (statement.importKind === "type" || specifier.type === "ImportSpecifier" && specifier.importKind === "type") {
3853
+ kept.add(specifier.local.name);
3854
+ }
3855
+ }
3856
+ }
3857
+ }
3858
+ const names = new Set([...runtime, ...kept]);
3859
+ let grew = true;
3860
+ while (grew) {
3861
+ grew = false;
3862
+ for (const node of interfaces) {
3863
+ const stays = node.body.body.length === 0 || Boolean(node.typeParameters) || optionsPatternsTest(keep, node.id.name) || refers(node, names);
3864
+ if (!kept.has(node.id.name) && stays) {
3865
+ names.add(node.id.name);
3866
+ kept.add(node.id.name);
3867
+ grew = true;
3868
+ }
3869
+ }
3870
+ }
3871
+ return kept;
3872
+ };
3873
+ return {
3874
+ before() {
3875
+ const options = optionsFirst(context, {});
3876
+ member = options.member ?? "Type";
3877
+ keep = optionsPatterns(options.keep);
3878
+ runtime = new Set(options.runtime ?? effectSchemaFirstDefaultRuntime);
3879
+ rows = options.rows ? new Set(options.rows.types ?? ["Kysely.Selectable"]) : undefined;
3880
+ suffix = options.rows?.suffix ?? "Record";
3881
+ const order = (options.order ?? effectSchemaFirstDefaultOrder).map((group) => [group].flat());
3882
+ ranks = new Map(order.flatMap((group, index) => group.flatMap((section) => effectSchemaFirstSections[section].map((type) => [type, index + 1]))));
3883
+ orderText = order.map((group) => group.map((section) => effectSchemaFirstLabels[section]).join(" and ")).join(", then ");
3884
+ derivedFirst = options.derived?.first ?? true;
3885
+ derivedSorted = options.derived?.sorted ?? true;
3886
+ },
3887
+ Program(program) {
3888
+ checkOrder(program);
3889
+ const declarations = program.body.flatMap((statement) => astDeclaration(statement) ?? []);
3890
+ const aliases = declarations.filter((node) => node.type === "TSTypeAliasDeclaration");
3891
+ const interfaces = declarations.filter((node) => node.type === "TSInterfaceDeclaration");
3892
+ const schemas = declarations.flatMap((node) => node.type === "VariableDeclaration" ? node.declarations : []).map(({ id }) => id.type === "Identifier" ? id.name : "");
3893
+ const exported = program.body.flatMap((statement) => statement.type === "ExportNamedDeclaration" && statement.declaration ? [statement.declaration] : []);
3894
+ const typed = new Set(exported.flatMap((node) => node.type === "TSTypeAliasDeclaration" && derives(node) ? [node.id.name] : []));
3895
+ for (const node of exported.flatMap((declaration) => declaration.type === "VariableDeclaration" ? declaration.declarations : [])) {
3896
+ const isSchema = node.init?.type === "CallExpression" || node.init?.type === "MemberExpression";
3897
+ if (isSchema && node.id.type === "Identifier" && !typed.has(node.id.name)) {
3898
+ context.report({
3899
+ node,
3900
+ messageId: "schemaType",
3901
+ data: { name: node.id.name, member }
3902
+ });
3903
+ }
3904
+ }
3905
+ let order = -1;
3906
+ let other = false;
3907
+ for (const alias of aliases) {
3908
+ if (!derives(alias)) {
3909
+ other = true;
3910
+ continue;
3911
+ }
3912
+ const index = schemas.indexOf(alias.id.name);
3913
+ if (derivedFirst && other || derivedSorted && index < order) {
3914
+ context.report({
3915
+ node: alias,
3916
+ messageId: "typeOrder",
3917
+ data: { name: alias.id.name }
3918
+ });
3919
+ }
3920
+ order = Math.max(order, index);
3921
+ }
3922
+ const kept = keptNames(program, aliases, interfaces);
3923
+ for (const node of interfaces) {
3924
+ if (!kept.has(node.id.name)) {
3925
+ context.report({
3926
+ node,
3927
+ messageId: "dataInterface",
3928
+ data: { name: node.id.name }
3929
+ });
3930
+ }
3931
+ }
3932
+ for (const node of rows ? declarations : []) {
3933
+ const fromRow = node.type === "TSTypeAliasDeclaration" && !node.id.name.endsWith(suffix) && readsRow(node.typeAnnotation) || node.type === "TSInterfaceDeclaration" && node.extends.some(readsRow);
3934
+ if (fromRow) {
3935
+ context.report({
3936
+ node,
3937
+ messageId: "rowItem",
3938
+ data: { name: node.id.name, suffix }
3939
+ });
3940
+ }
3941
+ }
3942
+ }
3943
+ };
3944
+ }
3945
+ });
3946
+
3947
+ // src/rules/effect/effect-values-first.ts
3948
+ import { defineRule as defineRule32 } from "@oxlint/plugins";
3949
+ var effectValuesFirstDefaultPinned = [
3950
+ "^Layer\\.",
3951
+ "^Context\\.",
3952
+ "^HttpRouter\\.add(?:All)?$",
3953
+ "(?:^|\\.)of$",
3954
+ "(?:^|\\.)layer[^.]*$",
3955
+ "^Schema\\.(?:asserts|decode\\w*|encode\\w*|is|isSchema|isSchemaError|resolveAnnotations)$"
3956
+ ];
3957
+ var effectValuesFirst = defineRule32({
3958
+ meta: {
3959
+ type: "suggestion",
3960
+ schema: [
3961
+ {
3962
+ type: "object",
3963
+ additionalProperties: false,
3964
+ properties: {
3965
+ builders: { type: "array", items: { type: "string" } },
3966
+ pinned: optionsPatternSchema
3967
+ }
3968
+ }
3969
+ ],
3970
+ messages: {
3971
+ value: "`{{name}}` is a value declared after `{{after}}`: declare values first, then helpers and methods."
3972
+ }
3973
+ },
3974
+ createOnce(context) {
3975
+ let builders = new Set(effectBuilders);
3976
+ let pinned = [];
3977
+ const check = (statements) => {
3978
+ const tied = new Set;
3979
+ let first;
3980
+ let barrier = false;
3981
+ for (const statement of statements) {
3982
+ const declaration = astDeclaration(statement);
3983
+ if (declaration?.type === "FunctionDeclaration" && declaration.id) {
3984
+ tied.add(declaration.id.name);
3985
+ first ??= declaration.id.name;
3986
+ continue;
3987
+ }
3988
+ if (declaration?.type !== "VariableDeclaration") {
3989
+ barrier ||= statement.type === "ExpressionStatement" && first !== undefined;
3990
+ continue;
3991
+ }
3992
+ for (const declarator of declaration.declarations) {
3993
+ const init = declarator.init;
3994
+ const names = astBindings(declarator.id).map((binding) => binding.name);
3995
+ const helper = Boolean(init && (astIsInlineFunction(astUnwrap(init)) || effectIsBuilder(init, builders)));
3996
+ const kept = helper || effectMatches(init, pinned) || astSome(init, (node) => node.type === "Identifier" && tied.has(node.name));
3997
+ if (first !== undefined && !kept && !barrier) {
3998
+ context.report({
3999
+ node: declarator,
4000
+ messageId: "value",
4001
+ data: { name: names.join(", "), after: first }
4002
+ });
4003
+ }
4004
+ if (kept) {
4005
+ names.forEach((name) => tied.add(name));
4006
+ }
4007
+ barrier ||= kept && first !== undefined && astSome(init, (node) => node.type === "YieldExpression");
4008
+ first ??= helper ? names.join(", ") : undefined;
4009
+ }
4010
+ }
4011
+ };
4012
+ const body = (init) => {
4013
+ const value = init ? astUnwrap(init) : null;
4014
+ const target = value && astIsInlineFunction(value) ? value : effectGenerator(value, builders);
4015
+ return target?.body?.type === "BlockStatement" ? target.body.body : [];
4016
+ };
4017
+ return {
4018
+ before() {
4019
+ const options = optionsFirst(context, {});
4020
+ builders = new Set(options.builders ?? effectBuilders);
4021
+ pinned = optionsPatterns(options.pinned ?? effectValuesFirstDefaultPinned);
4022
+ },
4023
+ Program(program) {
4024
+ check(program.body);
4025
+ for (const statement of program.body) {
4026
+ const declaration = astDeclaration(statement);
4027
+ if (declaration?.type === "FunctionDeclaration") {
4028
+ check(declaration.body?.body ?? []);
4029
+ }
4030
+ if (declaration?.type === "VariableDeclaration") {
4031
+ declaration.declarations.forEach((declarator) => check(body(declarator.init)));
4032
+ }
3287
4033
  }
3288
- },
3289
- TemplateElement: check,
3290
- JSXText: check
4034
+ }
3291
4035
  };
3292
4036
  }
3293
4037
  });
3294
4038
 
3295
4039
  // src/rules/files/files-components-only.ts
3296
- import { defineRule as defineRule27 } from "@oxlint/plugins";
4040
+ import { defineRule as defineRule33 } from "@oxlint/plugins";
3297
4041
 
3298
4042
  // src/utils/react.utils.ts
3299
4043
  function reactReturnsOutput(program, node) {
@@ -3365,7 +4109,7 @@ function reactIsComponent(program, candidate) {
3365
4109
  function componentName(basename2) {
3366
4110
  return basename2.replace(/\.(tsx?|jsx?)$/, "").replaceAll(/(?:^[-.]*|[-.]+)(.?)/g, (_, char) => char.toUpperCase());
3367
4111
  }
3368
- var filesComponentsOnly = defineRule27({
4112
+ var filesComponentsOnly = defineRule33({
3369
4113
  meta: {
3370
4114
  type: "problem",
3371
4115
  schema: [
@@ -3373,7 +4117,7 @@ var filesComponentsOnly = defineRule27({
3373
4117
  type: "object",
3374
4118
  additionalProperties: false,
3375
4119
  properties: {
3376
- filename: { type: "boolean" },
4120
+ filename: { oneOf: [{ type: "boolean" }, { type: "string", enum: ["each"] }] },
3377
4121
  roles: { type: "array", items: { type: "string" } },
3378
4122
  types: { type: "boolean" },
3379
4123
  deny: optionsPatternSchema
@@ -3434,10 +4178,13 @@ var filesComponentsOnly = defineRule27({
3434
4178
  return;
3435
4179
  }
3436
4180
  const names = components.map((candidate) => candidate.name.toLowerCase());
3437
- if (expectedKeys.some((key) => names.includes(key) || names.every((name) => name.startsWith(key)))) {
4181
+ if (matchFileName !== "each" && expectedKeys.some((key) => names.includes(key) || names.every((name) => name.startsWith(key)))) {
3438
4182
  return;
3439
4183
  }
3440
4184
  for (const { name, node } of components) {
4185
+ if (matchFileName === "each" && expectedKeys.some((key) => name.toLowerCase().startsWith(key))) {
4186
+ continue;
4187
+ }
3441
4188
  context.report({
3442
4189
  node,
3443
4190
  messageId: "nameMismatch",
@@ -3450,7 +4197,7 @@ var filesComponentsOnly = defineRule27({
3450
4197
  });
3451
4198
 
3452
4199
  // src/rules/files/files-constants-only.ts
3453
- import { defineRule as defineRule28 } from "@oxlint/plugins";
4200
+ import { defineRule as defineRule34 } from "@oxlint/plugins";
3454
4201
  function unwrap(expression) {
3455
4202
  if (expression.type === "ParenthesizedExpression" || expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSTypeAssertion" || expression.type === "TSNonNullExpression" || expression.type === "TSInstantiationExpression") {
3456
4203
  return unwrap(expression.expression);
@@ -3505,7 +4252,7 @@ function collectBindings(program) {
3505
4252
  }
3506
4253
  return bindings;
3507
4254
  }
3508
- var filesConstantsOnly = defineRule28({
4255
+ var filesConstantsOnly = defineRule34({
3509
4256
  meta: {
3510
4257
  type: "problem",
3511
4258
  schema: [
@@ -3604,7 +4351,7 @@ var filesConstantsOnly = defineRule28({
3604
4351
  });
3605
4352
 
3606
4353
  // src/rules/files/files-domain-constants.ts
3607
- import { defineRule as defineRule29 } from "@oxlint/plugins";
4354
+ import { defineRule as defineRule35 } from "@oxlint/plugins";
3608
4355
  import { basename as basename2, dirname as dirname2 } from "node:path";
3609
4356
  function isData(expression) {
3610
4357
  const value = astUnwrap(expression);
@@ -3622,14 +4369,21 @@ function isData(expression) {
3622
4369
  }
3623
4370
  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
4371
  }
3625
- var filesDomainConstants = defineRule29({
4372
+ var filesDomainConstants = defineRule35({
3626
4373
  meta: {
3627
4374
  type: "problem",
3628
4375
  schema: [
3629
4376
  {
3630
4377
  type: "object",
3631
4378
  additionalProperties: false,
3632
- properties: { data: { type: "boolean" }, services: { type: "boolean" } }
4379
+ properties: {
4380
+ data: { type: "boolean" },
4381
+ services: { type: "boolean" },
4382
+ pattern: { ...optionsPatternSchema, default: "^[A-Z][A-Z0-9_]+$" },
4383
+ folders: { type: "array", items: { type: "string", minLength: 1 }, default: ["services"] },
4384
+ roles: { type: "array", items: { type: "string", minLength: 1 }, default: ["action", "query"] },
4385
+ file: { type: "string", minLength: 1, default: "{domain}.constants.ts" }
4386
+ }
3633
4387
  }
3634
4388
  ],
3635
4389
  messages: {
@@ -3638,11 +4392,19 @@ var filesDomainConstants = defineRule29({
3638
4392
  },
3639
4393
  createOnce(context) {
3640
4394
  let includeData = false;
4395
+ let names = [];
4396
+ let folders = [];
4397
+ let roles = [];
4398
+ let target = "";
3641
4399
  let serviceObjects = new Set;
3642
4400
  return {
3643
4401
  Program(program) {
3644
4402
  const options = optionsFirst(context, {});
3645
4403
  includeData = Boolean(options.data);
4404
+ names = optionsPatterns(options.pattern ?? "^[A-Z][A-Z0-9_]+$");
4405
+ folders = options.folders ?? ["services"];
4406
+ roles = options.roles ?? ["action", "query"];
4407
+ target = options.file ?? "{domain}.constants.ts";
3646
4408
  serviceObjects = new Set(options.services ? declarationsPublicServiceMethods(program).map((method) => method.binding) : []);
3647
4409
  },
3648
4410
  VariableDeclarator(node) {
@@ -3652,15 +4414,19 @@ var filesDomainConstants = defineRule29({
3652
4414
  return;
3653
4415
  }
3654
4416
  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))) {
4417
+ if ((!name || !optionsPatternsTest(names, name)) && (!includeData || !node.init || !isData(node.init))) {
3656
4418
  return;
3657
4419
  }
3658
4420
  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)$/, "");
4421
+ const folder = basename2(dirname2(file));
4422
+ const stem = basename2(file).split(".")[0];
4423
+ const role = roles.find((candidate) => stem.endsWith(`-${candidate}`));
4424
+ const domain = role ? stem.slice(0, -role.length - 1) : stem;
4425
+ const owner = folders.some((candidate) => file.includes(`/${candidate}/`) && folder !== candidate) ? folder : domain;
3660
4426
  context.report({
3661
4427
  node,
3662
4428
  messageId: "forbidden",
3663
- data: { name: name ?? "<destructured binding>", file: `${owner}.constants.ts` }
4429
+ data: { name: name ?? "<destructured binding>", file: target.replaceAll("{domain}", owner) }
3664
4430
  });
3665
4431
  }
3666
4432
  };
@@ -3668,11 +4434,11 @@ var filesDomainConstants = defineRule29({
3668
4434
  });
3669
4435
 
3670
4436
  // src/rules/files/files-exports.ts
3671
- import { defineRule as defineRule30 } from "@oxlint/plugins";
4437
+ import { defineRule as defineRule36 } from "@oxlint/plugins";
3672
4438
  function escapeRegExp(value) {
3673
4439
  return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
3674
4440
  }
3675
- var filesExports = defineRule30({
4441
+ var filesExports = defineRule36({
3676
4442
  meta: {
3677
4443
  type: "problem",
3678
4444
  schema: [
@@ -3753,11 +4519,12 @@ var filesExports = defineRule30({
3753
4519
  });
3754
4520
 
3755
4521
  // src/rules/files/files-externals.ts
3756
- import { defineRule as defineRule31 } from "@oxlint/plugins";
4522
+ import { defineRule as defineRule37 } from "@oxlint/plugins";
3757
4523
  import { existsSync as existsSync2, readFileSync } from "node:fs";
3758
4524
  import { builtinModules as builtinModules2 } from "node:module";
3759
4525
  import { dirname as dirname3, join as join2 } from "node:path";
3760
- var filesExternals = defineRule31({
4526
+ var filesExternalsConfigs = "^(?:vite|next)\\.config\\.[cm]?[jt]s$";
4527
+ var filesExternals = defineRule37({
3761
4528
  meta: {
3762
4529
  type: "problem",
3763
4530
  schema: [
@@ -3765,7 +4532,8 @@ var filesExternals = defineRule31({
3765
4532
  type: "object",
3766
4533
  additionalProperties: false,
3767
4534
  properties: {
3768
- packages: { type: "array", items: { type: "string", minLength: 1 } }
4535
+ packages: { type: "array", items: { type: "string", minLength: 1 } },
4536
+ files: { ...optionsPatternSchema, default: filesExternalsConfigs }
3769
4537
  }
3770
4538
  }
3771
4539
  ],
@@ -3821,7 +4589,8 @@ var filesExternals = defineRule31({
3821
4589
  }
3822
4590
  return {
3823
4591
  before() {
3824
- if (!/^(?:vite|next)\.config\.[cm]?[jt]s$/.test(namingBasename(context.filename))) {
4592
+ const files = optionsPatterns(optionsFirst(context, {}).files ?? filesExternalsConfigs);
4593
+ if (!optionsPatternsTest(files, namingBasename(context.filename))) {
3825
4594
  return false;
3826
4595
  }
3827
4596
  },
@@ -3848,7 +4617,7 @@ var filesExternals = defineRule31({
3848
4617
  });
3849
4618
 
3850
4619
  // src/rules/files/files-factory-keys.ts
3851
- import { defineRule as defineRule32 } from "@oxlint/plugins";
4620
+ import { defineRule as defineRule38 } from "@oxlint/plugins";
3852
4621
  function returnedObject(expression) {
3853
4622
  if (expression?.type === "TSAsExpression" || expression?.type === "TSSatisfiesExpression" || expression?.type === "TSTypeAssertion" || expression?.type === "TSNonNullExpression") {
3854
4623
  return returnedObject(expression.expression);
@@ -3859,7 +4628,7 @@ function compileKeys(pattern, keys, requireKeys) {
3859
4628
  const allowedList = keys.concat(requireKeys.filter((key) => !keys.includes(key)));
3860
4629
  return { patterns: optionsPatternsWithEmpty(pattern), allowed: new Set(allowedList), allowedList, required: requireKeys };
3861
4630
  }
3862
- var filesFactoryKeys = defineRule32({
4631
+ var filesFactoryKeys = defineRule38({
3863
4632
  meta: {
3864
4633
  type: "problem",
3865
4634
  schema: [
@@ -3977,8 +4746,8 @@ var filesFactoryKeys = defineRule32({
3977
4746
  });
3978
4747
 
3979
4748
  // src/rules/files/files-helpers.ts
3980
- import { defineRule as defineRule33 } from "@oxlint/plugins";
3981
- var filesHelpers = defineRule33({
4749
+ import { defineRule as defineRule39 } from "@oxlint/plugins";
4750
+ var filesHelpers = defineRule39({
3982
4751
  meta: {
3983
4752
  type: "problem",
3984
4753
  schema: [
@@ -4026,8 +4795,8 @@ var filesHelpers = defineRule33({
4026
4795
  });
4027
4796
 
4028
4797
  // src/rules/files/files-no-functions.ts
4029
- import { defineRule as defineRule34 } from "@oxlint/plugins";
4030
- var filesNoFunctions = defineRule34({
4798
+ import { defineRule as defineRule40 } from "@oxlint/plugins";
4799
+ var filesNoFunctions = defineRule40({
4031
4800
  meta: {
4032
4801
  type: "problem",
4033
4802
  schema: [
@@ -4081,8 +4850,8 @@ var filesNoFunctions = defineRule34({
4081
4850
  });
4082
4851
 
4083
4852
  // src/rules/files/files-restricted.ts
4084
- import { defineRule as defineRule35 } from "@oxlint/plugins";
4085
- var filesRestricted = defineRule35({
4853
+ import { defineRule as defineRule41 } from "@oxlint/plugins";
4854
+ var filesRestricted = defineRule41({
4086
4855
  meta: {
4087
4856
  type: "problem",
4088
4857
  schema: [
@@ -4114,7 +4883,7 @@ var filesRestricted = defineRule35({
4114
4883
  });
4115
4884
 
4116
4885
  // src/rules/files/files-route.ts
4117
- import { defineRule as defineRule36 } from "@oxlint/plugins";
4886
+ import { defineRule as defineRule42 } from "@oxlint/plugins";
4118
4887
  function inspectAdapter(context, item, bannedHooks, banIntrinsicJsx) {
4119
4888
  astVisit(item.node.body, [item.node], (node) => {
4120
4889
  if (banIntrinsicJsx && node.type === "JSXOpeningElement" && node.name.type === "JSXIdentifier" && /^[a-z]/.test(node.name.name)) {
@@ -4134,7 +4903,7 @@ function inspectAdapter(context, item, bannedHooks, banIntrinsicJsx) {
4134
4903
  }
4135
4904
  });
4136
4905
  }
4137
- var filesRoute = defineRule36({
4906
+ var filesRoute = defineRule42({
4138
4907
  meta: {
4139
4908
  type: "problem",
4140
4909
  schema: [
@@ -4204,7 +4973,7 @@ var filesRoute = defineRule36({
4204
4973
  });
4205
4974
 
4206
4975
  // src/rules/files/files-types-only.ts
4207
- import { defineRule as defineRule37 } from "@oxlint/plugins";
4976
+ import { defineRule as defineRule43 } from "@oxlint/plugins";
4208
4977
  function runtimeImportLocalNames(statement) {
4209
4978
  return statement.specifiers.flatMap((specifier) => specifier.type !== "ImportSpecifier" || specifier.importKind !== "type" ? [specifier.local.name] : []);
4210
4979
  }
@@ -4373,7 +5142,7 @@ function runtimeKind(node) {
4373
5142
  }
4374
5143
  return node.type === "FunctionDeclaration" ? "function" : "variable";
4375
5144
  }
4376
- var filesTypesOnly = defineRule37({
5145
+ var filesTypesOnly = defineRule43({
4377
5146
  meta: {
4378
5147
  type: "problem",
4379
5148
  schema: [
@@ -4481,8 +5250,8 @@ var filesTypesOnly = defineRule37({
4481
5250
  });
4482
5251
 
4483
5252
  // src/rules/layout/layout-arrow-body.ts
4484
- import { defineRule as defineRule38 } from "@oxlint/plugins";
4485
- var layoutArrowBody = defineRule38({
5253
+ import { defineRule as defineRule44 } from "@oxlint/plugins";
5254
+ var layoutArrowBody = defineRule44({
4486
5255
  meta: {
4487
5256
  type: "layout",
4488
5257
  fixable: "whitespace",
@@ -4534,12 +5303,12 @@ var layoutArrowBody = defineRule38({
4534
5303
  });
4535
5304
 
4536
5305
  // src/rules/layout/layout-call-arguments.ts
4537
- import { defineRule as defineRule39 } from "@oxlint/plugins";
5306
+ import { defineRule as defineRule45 } from "@oxlint/plugins";
4538
5307
  function layoutCallArgumentsHuggable(node) {
4539
5308
  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
5309
  }
4541
5310
  var layoutCallArgumentsMax = { type: "integer", minimum: 1 };
4542
- var layoutCallArguments = defineRule39({
5311
+ var layoutCallArguments = defineRule45({
4543
5312
  meta: {
4544
5313
  type: "layout",
4545
5314
  fixable: "whitespace",
@@ -4553,11 +5322,7 @@ var layoutCallArguments = defineRule39({
4553
5322
  minimum: 2,
4554
5323
  default: 3
4555
5324
  },
4556
- indent: {
4557
- type: "integer",
4558
- minimum: 1,
4559
- default: 2
4560
- },
5325
+ indent: { ...sourceIndentSchema, default: 2 },
4561
5326
  split: {
4562
5327
  type: "boolean",
4563
5328
  default: true
@@ -4575,6 +5340,7 @@ var layoutCallArguments = defineRule39({
4575
5340
  properties: {
4576
5341
  max: layoutCallArgumentsMax,
4577
5342
  nesting: layoutCallArgumentsMax,
5343
+ items: layoutCallArgumentsMax,
4578
5344
  keep: { type: "array", items: { type: "string" } }
4579
5345
  }
4580
5346
  }
@@ -4591,7 +5357,7 @@ var layoutCallArguments = defineRule39({
4591
5357
  createOnce(context) {
4592
5358
  let options = {};
4593
5359
  let minArguments = 3;
4594
- let indent = 2;
5360
+ let indentUnit = sourceIndentUnit();
4595
5361
  const split = (node) => {
4596
5362
  const args = node.arguments;
4597
5363
  const last = args.at(-1);
@@ -4615,7 +5381,7 @@ var layoutCallArguments = defineRule39({
4615
5381
  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
5382
  const baseIndent = sourceLineIndent(text, open);
4617
5383
  const newline = sourceNewline(text);
4618
- const argumentIndent = `${baseIndent}${" ".repeat(indent)}`;
5384
+ const argumentIndent = `${baseIndent}${indentUnit}`;
4619
5385
  context.report({
4620
5386
  node,
4621
5387
  messageId: "multiline",
@@ -4660,7 +5426,7 @@ var layoutCallArguments = defineRule39({
4660
5426
  }
4661
5427
  const newline = sourceNewline(text);
4662
5428
  const base = sourceLineIndent(text, open.range[0]);
4663
- const inner = `${base}${" ".repeat(indent)}`;
5429
+ const inner = `${base}${indentUnit}`;
4664
5430
  context.report({
4665
5431
  node,
4666
5432
  messageId: "hug",
@@ -4671,16 +5437,14 @@ var layoutCallArguments = defineRule39({
4671
5437
  const source = context.sourceCode;
4672
5438
  const text = source.text;
4673
5439
  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)) {
5440
+ 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
5441
  return;
4676
5442
  }
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)) {
5443
+ const gaps = sourceItemGaps(open.range[1], items, close.range[0]);
5444
+ const comma = text.slice(...gaps.at(-1) ?? [0, 0]).includes(",") ? "," : "";
5445
+ if (sourceJoinedWidth(text, open.range[1], `${items.map((item) => source.getText(item)).join(", ")}${comma}`, close.range[0]) > (options.join?.max ?? 0)) {
4681
5446
  return;
4682
5447
  }
4683
- const gaps = sourceItemGaps(open.range[1], items, close.range[0]);
4684
5448
  context.report({
4685
5449
  node,
4686
5450
  messageId: "join",
@@ -4712,7 +5476,7 @@ var layoutCallArguments = defineRule39({
4712
5476
  before() {
4713
5477
  options = optionsFirst(context, {});
4714
5478
  minArguments = options.min ?? 3;
4715
- indent = options.indent ?? 2;
5479
+ indentUnit = sourceIndentUnit(options.indent);
4716
5480
  },
4717
5481
  CallExpression: call,
4718
5482
  NewExpression: call,
@@ -4726,12 +5490,12 @@ var layoutCallArguments = defineRule39({
4726
5490
  });
4727
5491
 
4728
5492
  // src/rules/layout/layout-call-array.ts
4729
- import { defineRule as defineRule40 } from "@oxlint/plugins";
5493
+ import { defineRule as defineRule46 } from "@oxlint/plugins";
4730
5494
  var layoutCallArrayDefaultCallees = ["Promise.all", "Promise.allSettled"];
4731
5495
  function layoutCallArrayRoot(node) {
4732
5496
  return node.type === "MemberExpression" ? layoutCallArrayRoot(node.object) : node;
4733
5497
  }
4734
- var layoutCallArray = defineRule40({
5498
+ var layoutCallArray = defineRule46({
4735
5499
  meta: {
4736
5500
  type: "layout",
4737
5501
  fixable: "whitespace",
@@ -4847,7 +5611,7 @@ var layoutCallArray = defineRule40({
4847
5611
  });
4848
5612
 
4849
5613
  // src/rules/layout/layout-call-depth.ts
4850
- import { defineRule as defineRule41 } from "@oxlint/plugins";
5614
+ import { defineRule as defineRule47 } from "@oxlint/plugins";
4851
5615
  function layoutCallDepthOuter(node) {
4852
5616
  const parent = node.parent;
4853
5617
  if (!parent || parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression" || parent.type.endsWith("Statement")) {
@@ -4855,7 +5619,7 @@ function layoutCallDepthOuter(node) {
4855
5619
  }
4856
5620
  return astIsCall(parent) && parent.arguments.some((argument) => argument === node) ? parent : layoutCallDepthOuter(parent);
4857
5621
  }
4858
- var layoutCallDepth = defineRule41({
5622
+ var layoutCallDepth = defineRule47({
4859
5623
  meta: {
4860
5624
  type: "layout",
4861
5625
  schema: [
@@ -4894,8 +5658,8 @@ var layoutCallDepth = defineRule41({
4894
5658
  });
4895
5659
 
4896
5660
  // src/rules/layout/layout-chain.ts
4897
- import { defineRule as defineRule42 } from "@oxlint/plugins";
4898
- var layoutChain = defineRule42({
5661
+ import { defineRule as defineRule48 } from "@oxlint/plugins";
5662
+ var layoutChain = defineRule48({
4899
5663
  meta: {
4900
5664
  type: "layout",
4901
5665
  fixable: "whitespace",
@@ -5014,8 +5778,8 @@ var layoutChain = defineRule42({
5014
5778
  });
5015
5779
 
5016
5780
  // src/rules/layout/layout-items.ts
5017
- import { defineRule as defineRule43 } from "@oxlint/plugins";
5018
- var layoutItems = defineRule43({
5781
+ import { defineRule as defineRule49 } from "@oxlint/plugins";
5782
+ var layoutItems = defineRule49({
5019
5783
  meta: {
5020
5784
  type: "layout",
5021
5785
  fixable: "whitespace",
@@ -5070,8 +5834,8 @@ var layoutItems = defineRule43({
5070
5834
  });
5071
5835
 
5072
5836
  // src/rules/layout/layout-jsx-attributes.ts
5073
- import { defineRule as defineRule44 } from "@oxlint/plugins";
5074
- var layoutJsxAttributes = defineRule44({
5837
+ import { defineRule as defineRule50 } from "@oxlint/plugins";
5838
+ var layoutJsxAttributes = defineRule50({
5075
5839
  meta: {
5076
5840
  type: "layout",
5077
5841
  fixable: "whitespace",
@@ -5081,12 +5845,19 @@ var layoutJsxAttributes = defineRule44({
5081
5845
  additionalProperties: false,
5082
5846
  properties: {
5083
5847
  min: { type: "integer", minimum: 1 },
5084
- indent: sourceIndentSchema
5848
+ indent: sourceIndentSchema,
5849
+ join: {
5850
+ type: "object",
5851
+ additionalProperties: false,
5852
+ required: ["max"],
5853
+ properties: { max: { type: "integer", minimum: 1 } }
5854
+ }
5085
5855
  }
5086
5856
  }
5087
5857
  ],
5088
5858
  messages: {
5089
- multiline: "Put every JSX attribute and the closing token on its own line."
5859
+ multiline: "Put every JSX attribute and the closing token on its own line.",
5860
+ join: "This tag fits on one line, so keep it on one line."
5090
5861
  }
5091
5862
  },
5092
5863
  createOnce(context) {
@@ -5094,35 +5865,52 @@ var layoutJsxAttributes = defineRule44({
5094
5865
  let indentUnit = sourceIndentUnit();
5095
5866
  let newline = `
5096
5867
  `;
5868
+ let joinMax;
5097
5869
  return {
5098
5870
  before() {
5099
5871
  const options = optionsFirst(context, {});
5100
5872
  minAttributes = options.min ?? 3;
5101
5873
  indentUnit = sourceIndentUnit(options.indent);
5102
5874
  newline = sourceNewline(context.sourceCode.text);
5875
+ joinMax = options.join?.max;
5103
5876
  },
5104
5877
  JSXOpeningElement(node) {
5105
- if (node.attributes.length < minAttributes || node.loc.start.line !== node.loc.end.line) {
5106
- return;
5107
- }
5108
5878
  const source = context.sourceCode;
5109
5879
  const text = source.text;
5880
+ const attributes = node.attributes;
5881
+ const split = attributes.length >= minAttributes;
5882
+ if (split ? !astIsOneLine(node) : joinMax === undefined || astIsOneLine(node)) {
5883
+ return;
5884
+ }
5885
+ if (!split) {
5886
+ const start = (node.typeArguments ?? node.name).range[1];
5887
+ if (attributes.length === 0 || !attributes.every(astIsOneLine) || text.slice(node.range[0], start).includes(`
5888
+ `) || source.getCommentsInside(node).length > 0) {
5889
+ return;
5890
+ }
5891
+ const end = node.range[1] - (node.selfClosing ? 2 : 1);
5892
+ const ranges = sourceItemGaps(start, attributes, end);
5893
+ const closing = node.selfClosing ? " " : "";
5894
+ if (sourceJoinedWidth(text, start, ` ${attributes.map((attribute) => source.getText(attribute)).join(" ")}${closing}`, end) > (joinMax ?? 0)) {
5895
+ return;
5896
+ }
5897
+ context.report({
5898
+ node,
5899
+ messageId: "join",
5900
+ fix: (fixer) => ranges.some((range) => !/^\s*$/.test(text.slice(...range))) ? null : ranges.map((range, index) => fixer.replaceTextRange(range, index === ranges.length - 1 ? closing : " "))
5901
+ });
5902
+ return;
5903
+ }
5110
5904
  const baseIndent = sourceLineIndent(text, node.range[0]);
5111
5905
  const attributeIndent = baseIndent + indentUnit;
5112
- const attributes = node.attributes;
5113
5906
  context.report({
5114
5907
  node,
5115
5908
  messageId: "multiline",
5116
5909
  fix: (fixer) => {
5117
- const last = attributes.at(-1);
5118
- if (source.getCommentsInside(node).length > 0 || !last) {
5910
+ if (source.getCommentsInside(node).length > 0) {
5119
5911
  return null;
5120
5912
  }
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
- ];
5913
+ const ranges = sourceItemGaps((node.typeArguments ?? node.name).range[1], attributes, node.range[1] - (node.selfClosing ? 2 : 1));
5126
5914
  if (ranges.some((range) => !/^\s*$/.test(text.slice(...range)))) {
5127
5915
  return null;
5128
5916
  }
@@ -5135,8 +5923,8 @@ var layoutJsxAttributes = defineRule44({
5135
5923
  });
5136
5924
 
5137
5925
  // src/rules/layout/layout-key-value.ts
5138
- import { defineRule as defineRule45 } from "@oxlint/plugins";
5139
- var layoutKeyValue = defineRule45({
5926
+ import { defineRule as defineRule51 } from "@oxlint/plugins";
5927
+ var layoutKeyValue = defineRule51({
5140
5928
  meta: {
5141
5929
  type: "layout",
5142
5930
  fixable: "whitespace",
@@ -5172,11 +5960,15 @@ var layoutKeyValue = defineRule45({
5172
5960
  });
5173
5961
 
5174
5962
  // src/rules/layout/layout-object.ts
5175
- import { defineRule as defineRule46 } from "@oxlint/plugins";
5963
+ import { defineRule as defineRule52 } from "@oxlint/plugins";
5176
5964
  function layoutObjectIsWhitespace(text) {
5177
5965
  return /^[\t\n\r\f ]*$/.test(text);
5178
5966
  }
5179
- var layoutObject = defineRule46({
5967
+ function layoutObjectIsFlat(property) {
5968
+ const value = property.type === "SpreadElement" ? property.argument : property.value;
5969
+ return astIsOneLine(property) && (property.type === "SpreadElement" || !property.method && property.kind === "init") && value.type !== "ObjectExpression" && value.type !== "ArrayExpression" && !astIsInlineFunction(value);
5970
+ }
5971
+ var layoutObject = defineRule52({
5180
5972
  meta: {
5181
5973
  type: "layout",
5182
5974
  fixable: "whitespace",
@@ -5187,13 +5979,23 @@ var layoutObject = defineRule46({
5187
5979
  properties: {
5188
5980
  min: { type: "integer", minimum: 1, default: 3 },
5189
5981
  scope: { type: "string", enum: ["call-args", "all"], default: "call-args" },
5190
- indent: { ...sourceIndentSchema, default: 2 }
5982
+ indent: { ...sourceIndentSchema, default: 2 },
5983
+ join: {
5984
+ type: "object",
5985
+ additionalProperties: false,
5986
+ required: ["max"],
5987
+ properties: {
5988
+ max: { type: "integer", minimum: 1 },
5989
+ keep: { type: "array", items: { type: "string" } }
5990
+ }
5991
+ }
5191
5992
  }
5192
5993
  }
5193
5994
  ],
5194
5995
  messages: {
5195
5996
  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."
5997
+ always: "Object with {{min}}+ properties should have each property on its own line.",
5998
+ join: "This object fits on one line, so keep it on one line."
5197
5999
  }
5198
6000
  },
5199
6001
  createOnce(context) {
@@ -5202,6 +6004,7 @@ var layoutObject = defineRule46({
5202
6004
  let indentUnit = sourceIndentUnit();
5203
6005
  let newline = `
5204
6006
  `;
6007
+ let join3;
5205
6008
  return {
5206
6009
  before() {
5207
6010
  const options = optionsFirst(context, {});
@@ -5209,21 +6012,43 @@ var layoutObject = defineRule46({
5209
6012
  scope = options.scope ?? "call-args";
5210
6013
  indentUnit = sourceIndentUnit(options.indent);
5211
6014
  newline = sourceNewline(context.sourceCode.text);
6015
+ join3 = options.join;
5212
6016
  },
5213
6017
  ObjectExpression(node) {
5214
- if (node.properties.length < minProperties || node.loc.start.line !== node.loc.end.line) {
6018
+ const source = context.sourceCode;
6019
+ const text = source.text;
6020
+ const properties = node.properties;
6021
+ const split = properties.length >= minProperties;
6022
+ if (split ? !astIsOneLine(node) : !join3 || astIsOneLine(node)) {
5215
6023
  return;
5216
6024
  }
5217
6025
  const parent = node.parent;
5218
- if (scope === "call-args" && !((parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node))) {
6026
+ const call = (parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node) ? parent : undefined;
6027
+ if (scope === "call-args" && !call) {
6028
+ return;
6029
+ }
6030
+ if (!split) {
6031
+ const callee = call ? astDottedName(call.callee) : undefined;
6032
+ const kept = callee !== undefined && (join3?.keep ?? []).some((name) => callee === name || callee.endsWith(`.${name}`));
6033
+ if (kept || properties.length === 0 || !properties.every(layoutObjectIsFlat) || source.getCommentsInside(node).length > 0) {
6034
+ return;
6035
+ }
6036
+ const gaps = sourceItemGaps(node.range[0] + 1, properties, node.range[1] - 1);
6037
+ const comma = text.slice(...gaps.at(-1) ?? [0, 0]).includes(",") ? "," : "";
6038
+ const joined = ` ${properties.map((property) => source.getText(property)).join(", ")}${comma} `;
6039
+ if (sourceJoinedWidth(text, node.range[0] + 1, joined, node.range[1] - 1) > (join3?.max ?? 0)) {
6040
+ return;
6041
+ }
6042
+ context.report({
6043
+ node,
6044
+ messageId: "join",
6045
+ fix: (fixer) => sourceRespace(fixer, text, gaps, () => " ")
6046
+ });
5219
6047
  return;
5220
6048
  }
5221
- const source = context.sourceCode;
5222
- const text = source.text;
5223
6049
  const baseIndent = sourceLineIndent(text, node.range[0]);
5224
6050
  const propertyIndent = `${baseIndent}${indentUnit}`;
5225
6051
  const close = node.range[1] - 1;
5226
- const properties = node.properties;
5227
6052
  context.report({
5228
6053
  node,
5229
6054
  messageId: scope === "all" ? "always" : "callArgs",
@@ -5260,7 +6085,7 @@ var layoutObject = defineRule46({
5260
6085
  });
5261
6086
 
5262
6087
  // src/rules/layout/layout-padding.ts
5263
- import { defineRule as defineRule47 } from "@oxlint/plugins";
6088
+ import { defineRule as defineRule53 } from "@oxlint/plugins";
5264
6089
  var layoutPaddingControl = new Set([
5265
6090
  "IfStatement",
5266
6091
  "ForStatement",
@@ -5289,7 +6114,7 @@ function layoutPaddingKind(node) {
5289
6114
  function layoutPaddingIsMultilineVariable(node, kind) {
5290
6115
  return kind === "variable" && node.loc.start.line !== node.loc.end.line;
5291
6116
  }
5292
- var layoutPadding = defineRule47({
6117
+ var layoutPadding = defineRule53({
5293
6118
  meta: {
5294
6119
  type: "layout",
5295
6120
  fixable: "whitespace",
@@ -5378,8 +6203,8 @@ var layoutPadding = defineRule47({
5378
6203
  });
5379
6204
 
5380
6205
  // src/rules/layout/layout-pipe.ts
5381
- import { defineRule as defineRule48 } from "@oxlint/plugins";
5382
- var layoutPipe = defineRule48({
6206
+ import { defineRule as defineRule54 } from "@oxlint/plugins";
6207
+ var layoutPipe = defineRule54({
5383
6208
  meta: {
5384
6209
  type: "layout",
5385
6210
  messages: {
@@ -5410,7 +6235,7 @@ var layoutPipe = defineRule48({
5410
6235
  });
5411
6236
 
5412
6237
  // src/rules/layout/layout-ternary.ts
5413
- import { defineRule as defineRule49 } from "@oxlint/plugins";
6238
+ import { defineRule as defineRule55 } from "@oxlint/plugins";
5414
6239
  function layoutTernaryLines(node) {
5415
6240
  return node.loc.end.line - node.loc.start.line + 1;
5416
6241
  }
@@ -5418,7 +6243,7 @@ function layoutTernaryBreak(between, operator, newline) {
5418
6243
  const at = between.indexOf(operator);
5419
6244
  return `${between.slice(0, at).trimEnd()}${newline}${operator} ${between.slice(at + 1).trimStart()}`;
5420
6245
  }
5421
- var layoutTernary = defineRule49({
6246
+ var layoutTernary = defineRule55({
5422
6247
  meta: {
5423
6248
  type: "layout",
5424
6249
  fixable: "whitespace",
@@ -5432,11 +6257,7 @@ var layoutTernary = defineRule49({
5432
6257
  minimum: 1,
5433
6258
  default: 160
5434
6259
  },
5435
- indent: {
5436
- type: "integer",
5437
- minimum: 1,
5438
- default: 2
5439
- },
6260
+ indent: { ...sourceIndentSchema, default: 2 },
5440
6261
  fit: {
5441
6262
  type: "boolean",
5442
6263
  default: true
@@ -5459,7 +6280,7 @@ var layoutTernary = defineRule49({
5459
6280
  },
5460
6281
  createOnce(context) {
5461
6282
  let maxLength = 160;
5462
- let indent = 2;
6283
+ let indentUnit = sourceIndentUnit();
5463
6284
  let fit = true;
5464
6285
  let branchMax;
5465
6286
  const branch = (node, max) => {
@@ -5480,7 +6301,7 @@ var layoutTernary = defineRule49({
5480
6301
  before() {
5481
6302
  const options = optionsFirst(context, {});
5482
6303
  maxLength = options.max ?? 160;
5483
- indent = options.indent ?? 2;
6304
+ indentUnit = sourceIndentUnit(options.indent);
5484
6305
  fit = options.fit ?? true;
5485
6306
  branchMax = options.branch?.max;
5486
6307
  },
@@ -5520,7 +6341,7 @@ var layoutTernary = defineRule49({
5520
6341
  if (!onOneLine || interpolated || length <= maxLength) {
5521
6342
  return;
5522
6343
  }
5523
- const newline = `${sourceNewline(text)}${sourceLineIndent(text, node.range[0])}${" ".repeat(indent)}`;
6344
+ const newline = `${sourceNewline(text)}${sourceLineIndent(text, node.range[0])}${indentUnit}`;
5524
6345
  const question = [node.test.range[1], node.consequent.range[0]];
5525
6346
  const colon = [node.consequent.range[1], node.alternate.range[0]];
5526
6347
  context.report({
@@ -5538,9 +6359,9 @@ var layoutTernary = defineRule49({
5538
6359
  });
5539
6360
 
5540
6361
  // src/rules/naming/naming-declarations.ts
5541
- import { defineRule as defineRule50 } from "@oxlint/plugins";
6362
+ import { defineRule as defineRule56 } from "@oxlint/plugins";
5542
6363
  var namingDeclarationsKinds = ["function", "const", "let", "var", "class", "type", "interface", "enum"];
5543
- var namingDeclarations = defineRule50({
6364
+ var namingDeclarations = defineRule56({
5544
6365
  meta: {
5545
6366
  type: "problem",
5546
6367
  schema: [
@@ -5572,11 +6393,9 @@ var namingDeclarations = defineRule50({
5572
6393
  const declarator = node.type === "VariableDeclarator";
5573
6394
  const kind = declarationsKindOf(node, declarator ? [node.parent] : []);
5574
6395
  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)) {
6396
+ const loop = declarator ? node.parent.parent : null;
6397
+ const loopHeader = loop?.type === "ForStatement" ? loop.init === node.parent : (loop?.type === "ForInStatement" || loop?.type === "ForOfStatement") && loop.left === node.parent;
6398
+ if (!kind || !name || loopHeader || kinds && !kinds.has(kind) || optionsPatternsTest(allowed, name)) {
5580
6399
  return;
5581
6400
  }
5582
6401
  if (expected.length > 0) {
@@ -5619,7 +6438,7 @@ var namingDeclarations = defineRule50({
5619
6438
  });
5620
6439
 
5621
6440
  // src/rules/naming/naming-exports.ts
5622
- import { defineRule as defineRule51 } from "@oxlint/plugins";
6441
+ import { defineRule as defineRule57 } from "@oxlint/plugins";
5623
6442
  var namingExportsScopeProperties = {
5624
6443
  scope: { type: "string", enum: ["exports", "functions", "declarations"] },
5625
6444
  types: { type: "boolean" },
@@ -5645,7 +6464,7 @@ function matchTemplate(basename3, template, placeholder = "[a-z0-9-]+") {
5645
6464
  }
5646
6465
  return new RegExp(`^${pattern}$`).exec(basename3)?.groups ?? null;
5647
6466
  }
5648
- var namingExports = defineRule51({
6467
+ var namingExports = defineRule57({
5649
6468
  meta: {
5650
6469
  type: "problem",
5651
6470
  schema: {
@@ -5740,9 +6559,12 @@ var namingExports = defineRule51({
5740
6559
  }
5741
6560
  continue;
5742
6561
  }
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 }]);
6562
+ const names = scope === "declarations" ? [...declarations ??= declarationsNamed(program)] : (bindings ??= exportsCollect(program)).flatMap((binding) => {
6563
+ const name = binding.default ? binding.localName ?? binding.exportedName : binding.exportedName;
6564
+ return options.types && binding.typeOnly ? [] : [{ name, node: binding.node }];
6565
+ });
5744
6566
  if (scope === "functions") {
5745
- const exported = new Set(names.map((item) => item.name));
6567
+ const exported = new Set([...names.map((item) => item.name), ...(bindings ?? []).flatMap((binding) => binding.localName ?? [])]);
5746
6568
  names.push(...(declarations ??= declarationsNamed(program)).filter(({ name, node }) => !exported.has(name) && (node.type === "FunctionDeclaration" || node.type === "VariableDeclarator" && declarationsIsFunctionValue(node.init))));
5747
6569
  }
5748
6570
  if (options.services) {
@@ -5779,10 +6601,10 @@ var namingExports = defineRule51({
5779
6601
  });
5780
6602
 
5781
6603
  // src/rules/naming/naming-file.ts
5782
- import { defineRule as defineRule52 } from "@oxlint/plugins";
6604
+ import { defineRule as defineRule58 } from "@oxlint/plugins";
5783
6605
  import { readdirSync as readdirSync2 } from "node:fs";
5784
6606
  import { dirname as dirname4 } from "node:path";
5785
- var namingFile = defineRule52({
6607
+ var namingFile = defineRule58({
5786
6608
  meta: {
5787
6609
  type: "problem",
5788
6610
  schema: {
@@ -5852,9 +6674,9 @@ var namingFile = defineRule52({
5852
6674
  });
5853
6675
 
5854
6676
  // src/rules/naming/naming-folder.ts
5855
- import { defineRule as defineRule53 } from "@oxlint/plugins";
6677
+ import { defineRule as defineRule59 } from "@oxlint/plugins";
5856
6678
  import { posix as posix2 } from "node:path";
5857
- var namingFolder = defineRule53({
6679
+ var namingFolder = defineRule59({
5858
6680
  meta: {
5859
6681
  type: "problem",
5860
6682
  schema: [
@@ -5928,9 +6750,9 @@ var reactDynamicClasses = astSelectorRule(':matches(JSXAttribute[name.name="clas
5928
6750
  var reactEmptyEffect = astSelectorRule(':matches(CallExpression[callee.name=/^use(Layout)?Effect$/], CallExpression[callee.object.name="React"][callee.property.name=/^use(Layout)?Effect$/]) > :matches(ArrowFunctionExpression, FunctionExpression) > BlockStatement[body.length=0]', "Remove the empty effect.");
5929
6751
 
5930
6752
  // src/rules/react/react-memo.ts
5931
- import { defineRule as defineRule54 } from "@oxlint/plugins";
6753
+ import { defineRule as defineRule60 } from "@oxlint/plugins";
5932
6754
  var reactMemoNames = ["memo", "useMemo", "useCallback"];
5933
- var reactMemo = defineRule54({
6755
+ var reactMemo = defineRule60({
5934
6756
  meta: {
5935
6757
  type: "problem",
5936
6758
  schema: [],
@@ -5965,7 +6787,7 @@ var reactMemo = defineRule54({
5965
6787
  });
5966
6788
 
5967
6789
  // src/rules/react/react-props.ts
5968
- import { defineRule as defineRule55 } from "@oxlint/plugins";
6790
+ import { defineRule as defineRule61 } from "@oxlint/plugins";
5969
6791
  function reactPropsTarget(node) {
5970
6792
  if (node?.type === "FunctionDeclaration" || node?.type === "FunctionExpression" || node?.type === "ArrowFunctionExpression") {
5971
6793
  return { fn: node };
@@ -5992,7 +6814,7 @@ function reactIsPassThrough(type) {
5992
6814
  const argument = type.typeArguments?.params.length === 1 ? type.typeArguments.params[0] : undefined;
5993
6815
  return name.type === "Identifier" && name.name === "ComponentProps" && (argument?.type === "TSTypeQuery" || argument?.type === "TSLiteralType" && argument.literal.type === "Literal" && typeof argument.literal.value === "string");
5994
6816
  }
5995
- var reactProps = defineRule55({
6817
+ var reactProps = defineRule61({
5996
6818
  meta: {
5997
6819
  type: "problem",
5998
6820
  schema: [
@@ -6000,7 +6822,8 @@ var reactProps = defineRule55({
6000
6822
  type: "object",
6001
6823
  additionalProperties: false,
6002
6824
  properties: {
6003
- prefix: { type: "boolean" }
6825
+ prefix: { type: "boolean" },
6826
+ suffix: { type: "string", minLength: 1, default: "Props" }
6004
6827
  }
6005
6828
  }
6006
6829
  ],
@@ -6014,7 +6837,7 @@ var reactProps = defineRule55({
6014
6837
  createOnce(context) {
6015
6838
  return {
6016
6839
  Program(program) {
6017
- const { prefix: filePrefix = false } = optionsFirst(context, {});
6840
+ const { prefix: filePrefix = false, suffix = "Props" } = optionsFirst(context, {});
6018
6841
  const stem = namingFileStem(namingBasename(context.filename).replace(/\.(tsx?|jsx?)$/, ""));
6019
6842
  const prefix = namingNormalized(stem, "remove-separators");
6020
6843
  const exported = new Set(exportsCollect(program).flatMap((binding) => binding.typeOnly ? [] : [binding.localName]));
@@ -6039,7 +6862,7 @@ var reactProps = defineRule55({
6039
6862
  continue;
6040
6863
  }
6041
6864
  const type = target.typeArgument ?? reactParameterType(parameter);
6042
- const expected = `${candidate.name}Props`;
6865
+ const expected = `${candidate.name}${suffix}`;
6043
6866
  const data = { name: candidate.name, expected };
6044
6867
  if (filePrefix && !exported.has(candidate.name) && !candidate.name.toLowerCase().startsWith(prefix)) {
6045
6868
  context.report({
@@ -6090,7 +6913,7 @@ var reactProps = defineRule55({
6090
6913
  });
6091
6914
 
6092
6915
  // src/rules/schemas/schemas-inline.ts
6093
- import { defineRule as defineRule56 } from "@oxlint/plugins";
6916
+ import { defineRule as defineRule62 } from "@oxlint/plugins";
6094
6917
 
6095
6918
  // src/utils/schemas.utils.ts
6096
6919
  var schemasElementOptionsSchema = {
@@ -6126,7 +6949,7 @@ function schemasIsAllowedElement(node, allowScalars, namespaces, structuralMetho
6126
6949
  }
6127
6950
 
6128
6951
  // src/rules/schemas/schemas-inline.ts
6129
- var schemasInline = defineRule56({
6952
+ var schemasInline = defineRule62({
6130
6953
  meta: {
6131
6954
  type: "problem",
6132
6955
  schema: [schemasElementOptionsSchema],
@@ -6175,11 +6998,11 @@ var schemasInline = defineRule56({
6175
6998
  });
6176
6999
 
6177
7000
  // src/rules/schemas/schemas-local.ts
6178
- import { defineRule as defineRule57 } from "@oxlint/plugins";
7001
+ import { defineRule as defineRule63 } from "@oxlint/plugins";
6179
7002
  var schemasLocalImportMessage = "Do not import runtime schema constructors from {{source}}; import a named schema instead.";
6180
7003
  var schemasLocalConstructionMessage = "Do not construct schemas locally; import a named schema instead.";
6181
7004
  var schemasLocalCalls = ["config", "prettifyError", "treeifyError", "flattenError", "formatError"];
6182
- var schemasLocal = defineRule57({
7005
+ var schemasLocal = defineRule63({
6183
7006
  meta: {
6184
7007
  type: "problem",
6185
7008
  schema: [
@@ -6192,6 +7015,8 @@ var schemasLocal = defineRule57({
6192
7015
  calls: { type: "array", items: { type: "string" } },
6193
7016
  allow: { type: "array", items: { type: "string" } },
6194
7017
  patterns: { type: "array", items: { type: "string" } },
7018
+ imports: { type: "boolean" },
7019
+ values: { type: "boolean" },
6195
7020
  message: { type: "string" },
6196
7021
  importMessage: { type: "string" },
6197
7022
  constructionMessage: { type: "string" }
@@ -6209,10 +7034,34 @@ var schemasLocal = defineRule57({
6209
7034
  let packages = new Set;
6210
7035
  let calls = new Set;
6211
7036
  let pending = [];
6212
- let schemaUses = new Set;
6213
- let callUses = new Set;
6214
7037
  let importMessage = schemasLocalImportMessage;
6215
7038
  let constructionMessage = schemasLocalConstructionMessage;
7039
+ let imports = true;
7040
+ let values = false;
7041
+ let inline = new Set;
7042
+ function isInline(node) {
7043
+ if (inline.size === 0) {
7044
+ return false;
7045
+ }
7046
+ for (let parent = node.parent;parent; parent = parent.parent) {
7047
+ if (inline.has(parent)) {
7048
+ return true;
7049
+ }
7050
+ }
7051
+ return false;
7052
+ }
7053
+ function readsLocal(node) {
7054
+ const scope = context.sourceCode.getScope(node);
7055
+ return astSome(node, (child) => {
7056
+ if (child.type !== "Identifier") {
7057
+ return false;
7058
+ }
7059
+ const parent = child.parent;
7060
+ const named = parent.type === "MemberExpression" && parent.property === child && !parent.computed || parent.type === "Property" && parent.key === child && !parent.computed && !parent.shorthand;
7061
+ const type = named ? undefined : boundariesDeclaredVariable(scope, child.name)?.scope.type;
7062
+ return type !== undefined && type !== "module" && type !== "global";
7063
+ });
7064
+ }
6216
7065
  function isAllowedCall(node, member) {
6217
7066
  return member?.type === "MemberExpression" && member.object === node && !member.computed && member.property.type === "Identifier" && calls.has(member.property.name) && member.parent.type === "CallExpression" && member.parent.callee === member;
6218
7067
  }
@@ -6220,6 +7069,9 @@ var schemasLocal = defineRule57({
6220
7069
  return packages.has(source) || [...packages].some((name) => source.startsWith(`${name}/`));
6221
7070
  }
6222
7071
  function reportRuntimeImport(node, source) {
7072
+ if (!imports) {
7073
+ return;
7074
+ }
6223
7075
  context.report({
6224
7076
  node,
6225
7077
  messageId: "runtimeImport",
@@ -6242,10 +7094,11 @@ var schemasLocal = defineRule57({
6242
7094
  packages = new Set(options.packages);
6243
7095
  calls = new Set(options.calls ?? schemasLocalCalls);
6244
7096
  pending = [];
6245
- schemaUses = new Set;
6246
- callUses = new Set;
6247
7097
  importMessage = options.importMessage ?? options.message ?? schemasLocalImportMessage;
6248
7098
  constructionMessage = options.constructionMessage ?? options.message ?? schemasLocalConstructionMessage;
7099
+ imports = options.imports ?? true;
7100
+ values = options.values ?? false;
7101
+ inline = new Set;
6249
7102
  if ((options.allow ?? []).some((suffix) => filename.endsWith(suffix)) || (options.patterns ?? []).some((pattern) => new RegExp(pattern).test(filename))) {
6250
7103
  return false;
6251
7104
  }
@@ -6267,18 +7120,8 @@ var schemasLocal = defineRule57({
6267
7120
  }
6268
7121
  pending.push({ node, locals });
6269
7122
  },
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
7123
  "Program:exit"() {
6281
- const runtime = pending.filter(({ locals }) => locals.some((name) => schemaUses.has(name) || !callUses.has(name)));
7124
+ 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) && !isInline(reference.identifier))));
6282
7125
  for (const { node } of runtime) {
6283
7126
  reportRuntimeImport(node, node.source.value);
6284
7127
  }
@@ -6293,6 +7136,10 @@ var schemasLocal = defineRule57({
6293
7136
  if (root.type !== "Identifier" || !constructors.has(root.name) || node.callee.type === "MemberExpression" && node.callee.object === root && isAllowedCall(root, root.parent)) {
6294
7137
  return;
6295
7138
  }
7139
+ if (values && (isInline(node) || readsLocal(node))) {
7140
+ inline.add(node);
7141
+ return;
7142
+ }
6296
7143
  context.report({
6297
7144
  node,
6298
7145
  messageId: "construction",
@@ -6304,8 +7151,8 @@ var schemasLocal = defineRule57({
6304
7151
  });
6305
7152
 
6306
7153
  // src/rules/schemas/schemas-rederive.ts
6307
- import { defineRule as defineRule58 } from "@oxlint/plugins";
6308
- var schemasRederive = defineRule58({
7154
+ import { defineRule as defineRule64 } from "@oxlint/plugins";
7155
+ var schemasRederive = defineRule64({
6309
7156
  meta: {
6310
7157
  type: "problem",
6311
7158
  schema: [
@@ -6351,7 +7198,7 @@ var schemasRederive = defineRule58({
6351
7198
  });
6352
7199
 
6353
7200
  // src/rules/schemas/schemas-single-use.ts
6354
- import { defineRule as defineRule59 } from "@oxlint/plugins";
7201
+ import { defineRule as defineRule65 } from "@oxlint/plugins";
6355
7202
  function isScalarSchema(node, namespaces, structuralMethods) {
6356
7203
  if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") {
6357
7204
  return false;
@@ -6365,7 +7212,7 @@ function isScalarSchema(node, namespaces, structuralMethods) {
6365
7212
  }
6366
7213
  return node.callee.object.type === "CallExpression" && isScalarSchema(node.callee.object, namespaces, structuralMethods);
6367
7214
  }
6368
- var schemasSingleUse = defineRule59({
7215
+ var schemasSingleUse = defineRule65({
6369
7216
  meta: {
6370
7217
  type: "suggestion",
6371
7218
  schema: [schemasElementOptionsSchema],
@@ -6441,7 +7288,7 @@ var schemasSingleUse = defineRule59({
6441
7288
  });
6442
7289
 
6443
7290
  // src/rules/tests/tests-assertions.ts
6444
- import { defineRule as defineRule60 } from "@oxlint/plugins";
7291
+ import { defineRule as defineRule66 } from "@oxlint/plugins";
6445
7292
  var testsAssertionsModifiers = new Set(["for", "each", "concurrent", "sequential", "only", "skip"]);
6446
7293
  var testsAssertionsFunctions = [
6447
7294
  "expect",
@@ -6631,7 +7478,7 @@ function isAssertedTest(registration, sourceCode, state) {
6631
7478
  }
6632
7479
  return rows.length > 0 && rows.every((row) => hasAssertion(callback, row.bindings, new Set, state));
6633
7480
  }
6634
- var testsAssertions = defineRule60({
7481
+ var testsAssertions = defineRule66({
6635
7482
  meta: {
6636
7483
  type: "problem",
6637
7484
  schema: [
@@ -6757,17 +7604,36 @@ var testsAssertions = defineRule60({
6757
7604
  });
6758
7605
 
6759
7606
  // src/rules/tests/tests-concurrent-db.ts
6760
- import { defineRule as defineRule61 } from "@oxlint/plugins";
6761
- var testsConcurrentDb = defineRule61({
7607
+ import { defineRule as defineRule67 } from "@oxlint/plugins";
7608
+ var testsConcurrentDb = defineRule67({
6762
7609
  meta: {
6763
7610
  type: "problem",
6764
- schema: [{ type: "object", properties: { message: { type: "string" } }, additionalProperties: false }],
7611
+ schema: [
7612
+ {
7613
+ type: "object",
7614
+ additionalProperties: false,
7615
+ properties: {
7616
+ message: { type: "string" },
7617
+ fixtures: { type: "array", items: { type: "string", minLength: 1 }, default: ["db"] },
7618
+ callees: { type: "array", items: { type: "string", minLength: 1 }, default: ["test.concurrent"] }
7619
+ }
7620
+ }
7621
+ ],
6765
7622
  messages: { forbidden: "{{message}}" }
6766
7623
  },
6767
7624
  createOnce(context) {
7625
+ let message = "";
7626
+ let fixtures = new Set;
7627
+ let callees = new Set;
6768
7628
  return {
7629
+ before() {
7630
+ const options = optionsFirst(context, {});
7631
+ message = options.message ?? "Obtain an isolated database fixture for concurrent tests.";
7632
+ fixtures = new Set(options.fixtures ?? ["db"]);
7633
+ callees = new Set(options.callees ?? ["test.concurrent"]);
7634
+ },
6769
7635
  CallExpression(node) {
6770
- const callback = astDottedName(node.callee) === "test.concurrent" ? node.arguments.findLast(declarationsIsFunctionValue) : undefined;
7636
+ const callback = callees.has(astDottedName(node.callee) ?? "") ? node.arguments.findLast(declarationsIsFunctionValue) : undefined;
6771
7637
  const parameter = callback?.params.at(0);
6772
7638
  const patterns = parameter?.type === "ObjectPattern" ? [parameter] : [];
6773
7639
  if (callback && parameter?.type === "Identifier") {
@@ -6780,8 +7646,8 @@ var testsConcurrentDb = defineRule61({
6780
7646
  }
6781
7647
  }
6782
7648
  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, {});
7649
+ const key = property.type === "Property" && !property.computed ? property.key : undefined;
7650
+ if (fixtures.has((key?.type === "Identifier" ? key.name : key && astStaticString(key)) ?? "")) {
6785
7651
  context.report({
6786
7652
  node: property,
6787
7653
  messageId: "forbidden",
@@ -6795,10 +7661,10 @@ var testsConcurrentDb = defineRule61({
6795
7661
  });
6796
7662
 
6797
7663
  // src/rules/tests/tests-modifiers.ts
6798
- import { defineRule as defineRule62 } from "@oxlint/plugins";
7664
+ import { defineRule as defineRule68 } from "@oxlint/plugins";
6799
7665
  var testsModifiersNames = ["test", "it", "describe", "suite"];
6800
7666
  var testsModifiersPackages = new Set(["vitest", "bun:test", "@playwright/test", "@jest/globals"]);
6801
- var testsModifiersBanned = new Set(["only", "skip", "todo"]);
7667
+ var testsModifiersBanned = ["only", "skip", "todo"];
6802
7668
  var testsModifiersBuilders = new Set(["if", "runIf", "skipIf"]);
6803
7669
  function modifierName(node) {
6804
7670
  if (node.type === "Identifier") {
@@ -6814,14 +7680,17 @@ function modifierName(node) {
6814
7680
  const property = astMemberName(node);
6815
7681
  return object && property ? `${object}.${property}` : undefined;
6816
7682
  }
6817
- var testsModifiers = defineRule62({
7683
+ var testsModifiers = defineRule68({
6818
7684
  meta: {
6819
7685
  type: "problem",
6820
7686
  schema: [
6821
7687
  {
6822
7688
  type: "object",
6823
7689
  additionalProperties: false,
6824
- properties: { functions: { type: "array", items: { type: "string" } } }
7690
+ properties: {
7691
+ functions: { type: "array", items: { type: "string" } },
7692
+ modifiers: { type: "array", items: { type: "string", minLength: 1 }, default: testsModifiersBanned }
7693
+ }
6825
7694
  }
6826
7695
  ],
6827
7696
  messages: {
@@ -6830,12 +7699,12 @@ var testsModifiers = defineRule62({
6830
7699
  },
6831
7700
  createOnce(context) {
6832
7701
  let names = new Set;
7702
+ let banned = new Set;
6833
7703
  return {
6834
7704
  before() {
6835
- names = new Set([
6836
- ...testsModifiersNames,
6837
- ...optionsFirst(context, {}).functions ?? []
6838
- ]);
7705
+ const options = optionsFirst(context, {});
7706
+ names = new Set([...testsModifiersNames, ...options.functions ?? []]);
7707
+ banned = new Set(options.modifiers ?? testsModifiersBanned);
6839
7708
  },
6840
7709
  ImportDeclaration(node) {
6841
7710
  if (!testsModifiersPackages.has(node.source.value)) {
@@ -6853,7 +7722,7 @@ var testsModifiers = defineRule62({
6853
7722
  },
6854
7723
  MemberExpression(node) {
6855
7724
  const name = modifierName(node);
6856
- if (!name || !testsModifiersBanned.has(name.split(".").at(-1) ?? "")) {
7725
+ if (!name || !banned.has(name.split(".").at(-1) ?? "")) {
6857
7726
  return;
6858
7727
  }
6859
7728
  if ([...names].some((test) => name.startsWith(`${test}.`))) {
@@ -6865,9 +7734,9 @@ var testsModifiers = defineRule62({
6865
7734
  });
6866
7735
 
6867
7736
  // 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({
7737
+ import { defineRule as defineRule69 } from "@oxlint/plugins";
7738
+ var testsTitlesModifiers = ["only", "skip", "concurrent", "sequential", "todo", "each", "for"];
7739
+ var testsTitles = defineRule69({
6871
7740
  meta: {
6872
7741
  type: "suggestion",
6873
7742
  schema: [
@@ -6876,6 +7745,7 @@ var testsTitles = defineRule63({
6876
7745
  additionalProperties: false,
6877
7746
  properties: {
6878
7747
  callees: { type: "array", items: { type: "string" } },
7748
+ modifiers: { type: "array", items: { type: "string", minLength: 1 }, default: testsTitlesModifiers },
6879
7749
  deny: optionsPatternSchema,
6880
7750
  require: optionsPatternSchema,
6881
7751
  flags: { type: "string", pattern: "^[dgimsuvy]*$" }
@@ -6895,6 +7765,7 @@ var testsTitles = defineRule63({
6895
7765
  },
6896
7766
  createOnce(context) {
6897
7767
  let callees = [];
7768
+ let modifiers = new Set;
6898
7769
  let forbid = [];
6899
7770
  let required = [];
6900
7771
  let requiredLabel = "";
@@ -6903,6 +7774,7 @@ var testsTitles = defineRule63({
6903
7774
  const options = optionsFirst(context);
6904
7775
  const flags = options.flags ?? "";
6905
7776
  callees = options.callees;
7777
+ modifiers = new Set(options.modifiers ?? testsTitlesModifiers);
6906
7778
  forbid = options.deny === undefined ? [] : [options.deny].flat().map((source) => ({ regex: new RegExp(source, flags), pattern: source }));
6907
7779
  required = optionsPatternsWithEmpty(options.require, flags);
6908
7780
  requiredLabel = optionsPatternLabel(options.require);
@@ -6913,7 +7785,7 @@ var testsTitles = defineRule63({
6913
7785
  CallExpression(node) {
6914
7786
  const title = node.arguments.at(0);
6915
7787
  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);
7788
+ 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
7789
  if (!title || !configured) {
6918
7790
  return;
6919
7791
  }
@@ -6942,11 +7814,11 @@ var testsTitles = defineRule63({
6942
7814
  });
6943
7815
 
6944
7816
  // src/rules/types/types-declarations.ts
6945
- import { defineRule as defineRule64 } from "@oxlint/plugins";
7817
+ import { defineRule as defineRule70 } from "@oxlint/plugins";
6946
7818
  function isValuePattern(source) {
6947
7819
  return /^\^?=/.test(source);
6948
7820
  }
6949
- var typesDeclarations = defineRule64({
7821
+ var typesDeclarations = defineRule70({
6950
7822
  meta: {
6951
7823
  type: "problem",
6952
7824
  schema: [
@@ -6988,8 +7860,73 @@ var typesDeclarations = defineRule64({
6988
7860
  }
6989
7861
  });
6990
7862
 
7863
+ // src/rules/types/types-duplicate.ts
7864
+ import { defineRule as defineRule71 } from "@oxlint/plugins";
7865
+ var typesDuplicate = defineRule71({
7866
+ meta: {
7867
+ type: "suggestion",
7868
+ schema: [
7869
+ {
7870
+ type: "object",
7871
+ additionalProperties: false,
7872
+ properties: {
7873
+ structs: { type: "array", items: { type: "string" } },
7874
+ min: { type: "integer", minimum: 1 }
7875
+ }
7876
+ }
7877
+ ],
7878
+ messages: { duplicate: "{{name}} has the same members as {{first}}; use {{first}}, or derive one from the other." }
7879
+ },
7880
+ createOnce(context) {
7881
+ let structs = new Set(["Schema.Struct"]);
7882
+ let min = 2;
7883
+ let seen = new Map;
7884
+ const check = (node, name, kind, members) => {
7885
+ if (members.length < min) {
7886
+ return;
7887
+ }
7888
+ const signature = members.map((item) => context.sourceCode.getText(item).replaceAll(/\breadonly\s+/g, "").replaceAll(/\s+/g, "").replace(/[;,]$/, "")).sort().join(";");
7889
+ const key = `${kind}:${signature}`;
7890
+ const first = seen.get(key);
7891
+ if (first === undefined) {
7892
+ seen.set(key, name);
7893
+ return;
7894
+ }
7895
+ context.report({
7896
+ node,
7897
+ messageId: "duplicate",
7898
+ data: { name, first }
7899
+ });
7900
+ };
7901
+ return {
7902
+ before() {
7903
+ const options = optionsFirst(context, {});
7904
+ structs = new Set(options.structs ?? ["Schema.Struct"]);
7905
+ min = options.min ?? 2;
7906
+ seen = new Map;
7907
+ },
7908
+ TSInterfaceDeclaration(node) {
7909
+ const heritage = node.extends.map((item) => context.sourceCode.getText(item)).join(",");
7910
+ check(node, node.id.name, `type:${heritage}`, node.body.body);
7911
+ },
7912
+ TSTypeAliasDeclaration(node) {
7913
+ if (node.typeAnnotation.type === "TSTypeLiteral") {
7914
+ check(node, node.id.name, "type:", node.typeAnnotation.members);
7915
+ }
7916
+ },
7917
+ VariableDeclarator(node) {
7918
+ const init = node.init ? astUnwrap(node.init) : undefined;
7919
+ const fields = init?.type === "CallExpression" && structs.has(astDottedName(init.callee) ?? "") ? init.arguments[0] : undefined;
7920
+ if (fields?.type === "ObjectExpression" && node.id.type === "Identifier") {
7921
+ check(node, node.id.name, "struct", fields.properties);
7922
+ }
7923
+ }
7924
+ };
7925
+ }
7926
+ });
7927
+
6991
7928
  // src/rules/types/types-imported-alias.ts
6992
- import { defineRule as defineRule65 } from "@oxlint/plugins";
7929
+ import { defineRule as defineRule72 } from "@oxlint/plugins";
6993
7930
  function importedAliasTarget(declaration, imports) {
6994
7931
  if (declaration?.type !== "TSTypeAliasDeclaration") {
6995
7932
  return;
@@ -7001,7 +7938,7 @@ function importedAliasTarget(declaration, imports) {
7001
7938
  const { name } = type.typeName;
7002
7939
  return imports.has(name) && !declaration.typeParameters?.params.some((parameter) => parameter.name.name === name) ? name : undefined;
7003
7940
  }
7004
- var typesImportedAlias = defineRule65({
7941
+ var typesImportedAlias = defineRule72({
7005
7942
  meta: {
7006
7943
  type: "problem",
7007
7944
  schema: [],
@@ -7063,7 +8000,7 @@ var typesImportedAlias = defineRule65({
7063
8000
  });
7064
8001
 
7065
8002
  // src/rules/types/types-inline.ts
7066
- import { defineRule as defineRule66 } from "@oxlint/plugins";
8003
+ import { defineRule as defineRule73 } from "@oxlint/plugins";
7067
8004
  function parameterAnnotation(parameter) {
7068
8005
  if (parameter.type === "TSParameterProperty") {
7069
8006
  return parameterAnnotation(parameter.parameter);
@@ -7073,7 +8010,7 @@ function parameterAnnotation(parameter) {
7073
8010
  }
7074
8011
  return parameter.typeAnnotation;
7075
8012
  }
7076
- var typesInline = defineRule66({
8013
+ var typesInline = defineRule73({
7077
8014
  meta: {
7078
8015
  type: "problem",
7079
8016
  schema: [
@@ -7149,8 +8086,8 @@ var typesInline = defineRule66({
7149
8086
  var typesLocalAlias = astSelectorRule("TSTypeAliasDeclaration", "Local component/hook contracts use interfaces named for their owner. Derived or reusable aliases belong in domain types; do not substitute an empty interface.");
7150
8087
 
7151
8088
  // src/rules/types/types-namespace-import.ts
7152
- import { defineRule as defineRule67 } from "@oxlint/plugins";
7153
- var typesNamespaceImport = defineRule67({
8089
+ import { defineRule as defineRule74 } from "@oxlint/plugins";
8090
+ var typesNamespaceImport = defineRule74({
7154
8091
  meta: {
7155
8092
  type: "suggestion",
7156
8093
  schema: [
@@ -7192,26 +8129,50 @@ var typesNamespaceImport = defineRule67({
7192
8129
  });
7193
8130
 
7194
8131
  // src/rules/types/types-service.ts
7195
- import { defineRule as defineRule68 } from "@oxlint/plugins";
7196
- var typesService = defineRule68({
8132
+ import { defineRule as defineRule75 } from "@oxlint/plugins";
8133
+ var typesService = defineRule75({
7197
8134
  meta: {
7198
8135
  type: "problem",
7199
- schema: [],
8136
+ schema: [
8137
+ {
8138
+ type: "object",
8139
+ additionalProperties: false,
8140
+ properties: {
8141
+ suffix: { type: "string", minLength: 1, default: "Service" },
8142
+ factory: { type: "string", minLength: 1, default: "make" }
8143
+ }
8144
+ }
8145
+ ],
7200
8146
  messages: {
7201
- domain: "Move this type to its domain types file; only the matching factory-derived Service alias belongs here."
8147
+ domain: "Move this type to its domain types file; only the matching factory-derived {{suffix}} alias belongs here."
7202
8148
  }
7203
8149
  },
7204
8150
  createOnce(context) {
8151
+ let suffix = "Service";
8152
+ let factory = "make";
7205
8153
  return {
8154
+ before() {
8155
+ const options = optionsFirst(context, {});
8156
+ suffix = options.suffix ?? "Service";
8157
+ factory = options.factory ?? "make";
8158
+ },
7206
8159
  TSInterfaceDeclaration(node) {
7207
- context.report({ node, messageId: "domain" });
8160
+ context.report({
8161
+ node,
8162
+ messageId: "domain",
8163
+ data: { suffix }
8164
+ });
7208
8165
  },
7209
8166
  TSTypeAliasDeclaration(node) {
7210
8167
  const type = node.typeAnnotation;
7211
8168
  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}`;
8169
+ 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
8170
  if (!isFactoryService) {
7214
- context.report({ node, messageId: "domain" });
8171
+ context.report({
8172
+ node,
8173
+ messageId: "domain",
8174
+ data: { suffix }
8175
+ });
7215
8176
  }
7216
8177
  }
7217
8178
  };
@@ -7219,12 +8180,12 @@ var typesService = defineRule68({
7219
8180
  });
7220
8181
 
7221
8182
  // src/rules/types/types-unknown.ts
7222
- import { defineRule as defineRule69 } from "@oxlint/plugins";
8183
+ import { defineRule as defineRule76 } from "@oxlint/plugins";
7223
8184
  function isCatchName(node) {
7224
8185
  const key = "key" in node && !(("computed" in node) && node.computed) ? node.key : undefined;
7225
8186
  return key?.type === "Identifier" && key.name === "catch";
7226
8187
  }
7227
- var typesUnknown = defineRule69({
8188
+ var typesUnknown = defineRule76({
7228
8189
  meta: { type: "problem", schema: [], messages: { forbidden: "Use an owned domain type rather than unknown." } },
7229
8190
  createOnce(context) {
7230
8191
  return {
@@ -7279,6 +8240,11 @@ var src_default = eslintCompatPlugin({
7279
8240
  "comments-code": commentsCode,
7280
8241
  "comments-directives": commentsDirectives,
7281
8242
  "copy-dashes": copyDashes,
8243
+ "effect-layer-file": effectLayerFile,
8244
+ "effect-layers": effectLayers,
8245
+ "effect-merge-pipe": effectMergePipe,
8246
+ "effect-schema-first": effectSchemaFirst,
8247
+ "effect-values-first": effectValuesFirst,
7282
8248
  "files-components-only": filesComponentsOnly,
7283
8249
  "files-constants-only": filesConstantsOnly,
7284
8250
  "files-domain-constants": filesDomainConstants,
@@ -7320,6 +8286,7 @@ var src_default = eslintCompatPlugin({
7320
8286
  "tests-modifiers": testsModifiers,
7321
8287
  "tests-titles": testsTitles,
7322
8288
  "types-declarations": typesDeclarations,
8289
+ "types-duplicate": typesDuplicate,
7323
8290
  "types-imported-alias": typesImportedAlias,
7324
8291
  "types-inline": typesInline,
7325
8292
  "types-local-alias": typesLocalAlias,