oxlint-plugin-arch 0.6.0 → 0.6.2

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +47 -1
  3. package/dist/index.js +122 -122
  4. package/dist/presets/index.js +28 -9
  5. package/dist/presets/tanstack-start-react-modules-preset.js +28 -9
  6. package/dist/rules/dillon-anti-slop/index.d.ts +2 -0
  7. package/dist/rules/dillon-anti-slop/index.js +2 -2
  8. package/dist/rules/dillon-anti-slop/rules/no-chained-type-assertions.d.ts +1 -0
  9. package/dist/rules/dillon-anti-slop/rules/no-conditional-empty-object-spread.d.ts +1 -0
  10. package/dist/rules/dillon-anti-slop/rules/no-known-value-widening.d.ts +1 -0
  11. package/dist/rules/dillon-anti-slop/rules/no-module-mocking.d.ts +1 -0
  12. package/dist/rules/dillon-anti-slop/rules/no-object-parameters.d.ts +1 -0
  13. package/dist/rules/dillon-anti-slop/rules/no-reflect-apply.d.ts +1 -0
  14. package/dist/rules/dillon-anti-slop/rules/no-reflect-get.d.ts +1 -0
  15. package/dist/rules/dillon-anti-slop/rules/no-runtime-typeof.d.ts +1 -0
  16. package/dist/rules/dillon-anti-slop/rules/no-shape-in-symbol-names.d.ts +1 -0
  17. package/dist/rules/dillon-anti-slop/rules/no-unknown-parameters.d.ts +1 -0
  18. package/dist/rules/dillon-anti-slop/rules/no-unknown-returns.d.ts +1 -0
  19. package/dist/rules/dillon-anti-slop/rules/no-unknown-type-aliases.d.ts +1 -0
  20. package/dist/rules/dillon-anti-slop/rules/no-unsafe-dictionary-type.d.ts +1 -0
  21. package/dist/rules/dillon-anti-slop/rules/no-widen-then-assert.d.ts +1 -0
  22. package/dist/rules/dillon-anti-slop/rules/require-safety-comment-for-type-assertion.d.ts +1 -0
  23. package/dist/rules/dillon-anti-slop/shared/dictionary-types.d.ts +19 -0
  24. package/dist/rules/dillon-anti-slop/shared/lexical-type-parameters.d.ts +4 -0
  25. package/dist/rules/dillon-anti-slop/shared/reflect-method.d.ts +2 -0
  26. package/dist/rules/module-policy/domain-constants.d.ts +2 -0
  27. package/dist/rules/module-policy/import-boundaries.d.ts +2 -0
  28. package/dist/rules/module-policy/index.d.ts +2 -0
  29. package/dist/rules/module-policy/index.js +49 -49
  30. package/dist/rules/module-policy/memoization.d.ts +2 -0
  31. package/dist/rules/module-policy/reasoned-directives.d.ts +2 -0
  32. package/dist/rules/module-policy/service-functions.d.ts +2 -0
  33. package/dist/rules/module-policy/service-types.d.ts +2 -0
  34. package/dist/rules/module-policy/tanstack-runtime.d.ts +2 -0
  35. package/dist/rules/module-policy/test-assertions.d.ts +2 -0
  36. package/dist/rules/module-policy/test-modifiers.d.ts +2 -0
  37. package/dist/types/module-policy.types.d.ts +34 -0
  38. package/dist/types/test-analysis.types.d.ts +11 -0
  39. package/dist/utils/helpers/ast.d.ts +12 -0
  40. package/dist/utils/helpers/boundaries.d.ts +3 -0
  41. package/dist/utils/helpers/comments.d.ts +6 -0
  42. package/dist/utils/helpers/declarations.d.ts +35 -0
  43. package/dist/utils/helpers/exports.d.ts +18 -0
  44. package/dist/utils/helpers/factories.d.ts +9 -0
  45. package/dist/utils/helpers/guards.d.ts +11 -0
  46. package/dist/utils/helpers/index.d.ts +16 -0
  47. package/dist/utils/helpers/layout.d.ts +15 -0
  48. package/dist/utils/helpers/naming.d.ts +13 -0
  49. package/dist/utils/helpers/options.d.ts +22 -0
  50. package/dist/utils/helpers/orpc.d.ts +3 -0
  51. package/dist/utils/helpers/react-components.d.ts +13 -0
  52. package/dist/utils/helpers/routes.d.ts +5 -0
  53. package/dist/utils/helpers/sanitizers.d.ts +3 -0
  54. package/dist/utils/helpers/schemas.d.ts +10 -0
  55. package/dist/utils/helpers/syntax.d.ts +1 -0
  56. package/dist/utils/helpers/test-tables.d.ts +8 -0
  57. package/dist/utils/helpers/trivial.d.ts +6 -0
  58. package/dist/utils/index.d.ts +1 -0
  59. package/package.json +9 -2
@@ -1042,15 +1042,35 @@ function presetReactConfig(context) {
1042
1042
  ]))));
1043
1043
  }
1044
1044
  if (Object.keys(css).length && options.shadcn !== false) {
1045
+ const allow = ["layout", "opacity", "truncate"];
1046
+ const padding = ["p", "px", "py", "ps", "pe", "pt", "pr", "pb", "pl"];
1047
+ const gap = ["gap", "gap-x", "gap-y"];
1048
+ const safeArea = padding.flatMap((group) => ["top", "right", "bottom", "left"].flatMap((side) => [
1049
+ `${group}-[env(safe-area-inset-${side})]`,
1050
+ ...group === "p" ? [] : [`${group}-[max(--spacing(*),env(safe-area-inset-${side}))]`]
1051
+ ]));
1045
1052
  policies.shadcn = [
1046
1053
  presetOverride(webFiles, {
1047
- "shadcn/no-restyle": ["error", { allow: ["layout", "opacity"] }],
1048
- "shadcn/no-arbitrary-values": ["error", { allow: ["layout"] }],
1054
+ "shadcn/no-restyle": [
1055
+ "error",
1056
+ {
1057
+ allow,
1058
+ contracts: [
1059
+ { pattern: "^(CardContent|PopoverContent)$", allow: [...allow, ...padding] },
1060
+ { pattern: "^TabsContent$", allow: [...allow, ...gap, ...padding] },
1061
+ { pattern: "^(Tabs|HoverCardContent|SidebarHeader|BreadcrumbList)$", allow: [...allow, ...gap] }
1062
+ ]
1063
+ }
1064
+ ],
1065
+ "shadcn/no-arbitrary-values": ["error", { allow: ["layout", ...safeArea] }],
1049
1066
  "shadcn/require-static-classes": "error",
1050
1067
  "modules/dynamic-classes": "error"
1051
1068
  }, ui),
1052
1069
  presetOverride(webFiles, {
1053
1070
  "shadcn/no-unknown-classes": ["error", { allow: [] }]
1071
+ }),
1072
+ presetOverride(presetScopes(web, "components/ui/sonner.tsx"), {
1073
+ "shadcn/no-unknown-classes": ["error", { allow: ["toaster", "toast"] }]
1054
1074
  })
1055
1075
  ];
1056
1076
  }
@@ -1119,7 +1139,6 @@ function presetBoundariesConfig(context) {
1119
1139
 
1120
1140
  // src/configs/plugins.ts
1121
1141
  import { createRequire } from "node:module";
1122
- import { fileURLToPath } from "node:url";
1123
1142
  var require2 = createRequire(import.meta.url);
1124
1143
  function presetPluginsConfig(context) {
1125
1144
  const { options, web, css } = context;
@@ -1136,15 +1155,15 @@ function presetPluginsConfig(context) {
1136
1155
  require2.resolve("@eslint-community/eslint-plugin-eslint-comments"),
1137
1156
  {
1138
1157
  name: "arch",
1139
- specifier: fileURLToPath(new URL("../index.js", import.meta.url))
1158
+ specifier: require2.resolve("oxlint-plugin-arch")
1140
1159
  },
1141
1160
  {
1142
1161
  name: "dillon-anti-slop",
1143
- specifier: fileURLToPath(new URL("../rules/dillon-anti-slop/index.js", import.meta.url))
1162
+ specifier: require2.resolve("oxlint-plugin-arch/rules/dillon-anti-slop")
1144
1163
  },
1145
1164
  {
1146
1165
  name: "modules",
1147
- specifier: fileURLToPath(new URL("../rules/module-policy/index.js", import.meta.url))
1166
+ specifier: require2.resolve("oxlint-plugin-arch/rules/module-policy")
1148
1167
  }
1149
1168
  ];
1150
1169
  const settings = {
@@ -1213,9 +1232,9 @@ function presetResolveContext(options) {
1213
1232
  throw new Error("Folder layout mode must be flat or domain.");
1214
1233
  layout[folder] = mode;
1215
1234
  }
1216
- const folders = Object.keys(layout);
1217
- for (const [index, folder] of folders.entries()) {
1218
- const overlaps = folders.slice(0, index).some((other) => folder.startsWith(other + "/") || other.startsWith(folder + "/"));
1235
+ const folders2 = Object.keys(layout);
1236
+ for (const [index, folder] of folders2.entries()) {
1237
+ const overlaps = folders2.slice(0, index).some((other) => folder.startsWith(other + "/") || other.startsWith(folder + "/"));
1219
1238
  if (overlaps) {
1220
1239
  throw new Error("Layout folders must not overlap.");
1221
1240
  }
@@ -1042,15 +1042,35 @@ function presetReactConfig(context) {
1042
1042
  ]))));
1043
1043
  }
1044
1044
  if (Object.keys(css).length && options.shadcn !== false) {
1045
+ const allow = ["layout", "opacity", "truncate"];
1046
+ const padding = ["p", "px", "py", "ps", "pe", "pt", "pr", "pb", "pl"];
1047
+ const gap = ["gap", "gap-x", "gap-y"];
1048
+ const safeArea = padding.flatMap((group) => ["top", "right", "bottom", "left"].flatMap((side) => [
1049
+ `${group}-[env(safe-area-inset-${side})]`,
1050
+ ...group === "p" ? [] : [`${group}-[max(--spacing(*),env(safe-area-inset-${side}))]`]
1051
+ ]));
1045
1052
  policies.shadcn = [
1046
1053
  presetOverride(webFiles, {
1047
- "shadcn/no-restyle": ["error", { allow: ["layout", "opacity"] }],
1048
- "shadcn/no-arbitrary-values": ["error", { allow: ["layout"] }],
1054
+ "shadcn/no-restyle": [
1055
+ "error",
1056
+ {
1057
+ allow,
1058
+ contracts: [
1059
+ { pattern: "^(CardContent|PopoverContent)$", allow: [...allow, ...padding] },
1060
+ { pattern: "^TabsContent$", allow: [...allow, ...gap, ...padding] },
1061
+ { pattern: "^(Tabs|HoverCardContent|SidebarHeader|BreadcrumbList)$", allow: [...allow, ...gap] }
1062
+ ]
1063
+ }
1064
+ ],
1065
+ "shadcn/no-arbitrary-values": ["error", { allow: ["layout", ...safeArea] }],
1049
1066
  "shadcn/require-static-classes": "error",
1050
1067
  "modules/dynamic-classes": "error"
1051
1068
  }, ui),
1052
1069
  presetOverride(webFiles, {
1053
1070
  "shadcn/no-unknown-classes": ["error", { allow: [] }]
1071
+ }),
1072
+ presetOverride(presetScopes(web, "components/ui/sonner.tsx"), {
1073
+ "shadcn/no-unknown-classes": ["error", { allow: ["toaster", "toast"] }]
1054
1074
  })
1055
1075
  ];
1056
1076
  }
@@ -1119,7 +1139,6 @@ function presetBoundariesConfig(context) {
1119
1139
 
1120
1140
  // src/configs/plugins.ts
1121
1141
  import { createRequire } from "node:module";
1122
- import { fileURLToPath } from "node:url";
1123
1142
  var require2 = createRequire(import.meta.url);
1124
1143
  function presetPluginsConfig(context) {
1125
1144
  const { options, web, css } = context;
@@ -1136,15 +1155,15 @@ function presetPluginsConfig(context) {
1136
1155
  require2.resolve("@eslint-community/eslint-plugin-eslint-comments"),
1137
1156
  {
1138
1157
  name: "arch",
1139
- specifier: fileURLToPath(new URL("../index.js", import.meta.url))
1158
+ specifier: require2.resolve("oxlint-plugin-arch")
1140
1159
  },
1141
1160
  {
1142
1161
  name: "dillon-anti-slop",
1143
- specifier: fileURLToPath(new URL("../rules/dillon-anti-slop/index.js", import.meta.url))
1162
+ specifier: require2.resolve("oxlint-plugin-arch/rules/dillon-anti-slop")
1144
1163
  },
1145
1164
  {
1146
1165
  name: "modules",
1147
- specifier: fileURLToPath(new URL("../rules/module-policy/index.js", import.meta.url))
1166
+ specifier: require2.resolve("oxlint-plugin-arch/rules/module-policy")
1148
1167
  }
1149
1168
  ];
1150
1169
  const settings = {
@@ -1213,9 +1232,9 @@ function presetResolveContext(options) {
1213
1232
  throw new Error("Folder layout mode must be flat or domain.");
1214
1233
  layout[folder] = mode;
1215
1234
  }
1216
- const folders = Object.keys(layout);
1217
- for (const [index, folder] of folders.entries()) {
1218
- const overlaps = folders.slice(0, index).some((other) => folder.startsWith(other + "/") || other.startsWith(folder + "/"));
1235
+ const folders2 = Object.keys(layout);
1236
+ for (const [index, folder] of folders2.entries()) {
1237
+ const overlaps = folders2.slice(0, index).some((other) => folder.startsWith(other + "/") || other.startsWith(folder + "/"));
1219
1238
  if (overlaps) {
1220
1239
  throw new Error("Layout folders must not overlap.");
1221
1240
  }
@@ -0,0 +1,2 @@
1
+ declare const dillonAntiSlopPlugin: import("@oxlint/plugins").Plugin;
2
+ export default dillonAntiSlopPlugin;
@@ -349,8 +349,8 @@ function classifyWideningTarget(type, environment) {
349
349
  if (alias === undefined)
350
350
  return null;
351
351
  if ((alias.typeParameters?.params.length ?? 0) > 0) {
352
- const substitutions = aliasSubstitution(alias, unwrapped, new Map);
353
- return substitutions !== null && resolvesToDictionary(alias.typeAnnotation, environment, substitutions, new Set([name])) ? { kind: "generic container" } : null;
352
+ const substitutions2 = aliasSubstitution(alias, unwrapped, new Map);
353
+ return substitutions2 !== null && resolvesToDictionary(alias.typeAnnotation, environment, substitutions2, new Set([name])) ? { kind: "generic container" } : null;
354
354
  }
355
355
  const substitutions = aliasSubstitution(alias, unwrapped, new Map);
356
356
  if (substitutions === null)
@@ -0,0 +1 @@
1
+ export declare const noChainedTypeAssertionsRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1 @@
1
+ export declare const noConditionalEmptyObjectSpreadRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1 @@
1
+ export declare const noKnownValueWideningRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1 @@
1
+ export declare const noModuleMockingRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1 @@
1
+ export declare const noObjectParametersRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1 @@
1
+ export declare const noReflectApplyRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1 @@
1
+ export declare const noReflectGetRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1 @@
1
+ export declare const noRuntimeTypeofRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1 @@
1
+ export declare const noForbiddenTermInSymbolNamesRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1 @@
1
+ export declare const noUnknownParametersRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1 @@
1
+ export declare const noUnknownReturnsRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1 @@
1
+ export declare const noUnknownTypeAliasesRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1 @@
1
+ export declare const noUnsafeDictionaryTypeRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1 @@
1
+ export declare const noWidenThenAssertRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1 @@
1
+ export declare const requireSafetyCommentForTypeAssertionRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,19 @@
1
+ import type { ESTree } from "@oxlint/plugins";
2
+ export type UnsafeDictionary = {
3
+ readonly kind: "unsafe-dictionary";
4
+ readonly unsafeValue: "any" | "empty-object" | "object" | "union" | "unknown";
5
+ };
6
+ export type WideningTargetKind = "anonymous object" | "generic container" | "object" | "open dictionary" | "unknown";
7
+ export type WideningTarget = {
8
+ readonly kind: WideningTargetKind;
9
+ };
10
+ export type TypeEnvironment = {
11
+ readonly aliases: ReadonlyMap<string, ESTree.TSTypeAliasDeclaration>;
12
+ readonly interfaces: ReadonlyMap<string, readonly ESTree.TSInterfaceDeclaration[]>;
13
+ readonly shadowedBuiltIns: ReadonlySet<string>;
14
+ };
15
+ export declare function createTypeEnvironment(program: ESTree.Program): TypeEnvironment;
16
+ export declare function classifyUnsafeDictionaryValue(valueType: ESTree.TSType, environment: TypeEnvironment): UnsafeDictionary | null;
17
+ export declare function classifyUnsafeDictionary(type: ESTree.TSType, environment: TypeEnvironment): UnsafeDictionary | null;
18
+ export declare function classifyWideningTarget(type: ESTree.TSType, environment: TypeEnvironment): WideningTarget | null;
19
+ export declare function isKnownEvidenceExpression(expression: ESTree.Expression): boolean;
@@ -0,0 +1,4 @@
1
+ import type { ESTree } from "@oxlint/plugins";
2
+ type VisitorKeys = Readonly<Record<string, readonly string[]>>;
3
+ export declare function lexicalTypeParameterNames(node: ESTree.Node, visitorKeys: VisitorKeys): ReadonlySet<string>;
4
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { ESTree, SourceCode } from "@oxlint/plugins";
2
+ export declare function isGlobalReflectMethodCall(sourceCode: SourceCode, callee: ESTree.Expression, methodName: string): boolean;
@@ -0,0 +1,2 @@
1
+ /** Move module data to domain constants; for example, a service's label table belongs in chat.constants.ts. */
2
+ export declare const domainConstants: import("@oxlint/plugins").Rule;
@@ -0,0 +1,2 @@
1
+ /** Reject imports across configured application boundaries, including type-only imports. */
2
+ export declare const importBoundaries: import("@oxlint/plugins").Rule;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("@oxlint/plugins").Plugin;
2
+ export default _default;
@@ -162,7 +162,7 @@ function trivialExpression(node) {
162
162
  }
163
163
  if (!node.body || node.body.type !== "BlockStatement")
164
164
  return null;
165
- const statements = node.body.body.filter((statement) => statement.type !== "EmptyStatement" && !(statement.type === "ExpressionStatement" && statement.directive));
165
+ const statements = node.body.body.filter((statement2) => statement2.type !== "EmptyStatement" && !(statement2.type === "ExpressionStatement" && statement2.directive));
166
166
  if (statements.length !== 1)
167
167
  return null;
168
168
  const statement = statements[0];
@@ -305,7 +305,7 @@ function declarationsIsTrivialFunction(node) {
305
305
  if (!node.body || node.body.type !== "BlockStatement") {
306
306
  return false;
307
307
  }
308
- const statements = node.body.body.filter((statement) => statement.type !== "EmptyStatement" && !(statement.type === "ExpressionStatement" && statement.directive));
308
+ const statements = node.body.body.filter((statement2) => statement2.type !== "EmptyStatement" && !(statement2.type === "ExpressionStatement" && statement2.directive));
309
309
  if (statements.length === 0) {
310
310
  return true;
311
311
  }
@@ -367,8 +367,8 @@ function declarationsInsideForLoop(node) {
367
367
  }
368
368
  function declarationsNamedFromNode(node) {
369
369
  if (node.type === "VariableDeclarator") {
370
- const kind = declarationsNamedKind(node, [node.parent]);
371
- return kind && node.id.type === "Identifier" && !declarationsInsideForLoop(node) ? { name: node.id.name, node, kind } : undefined;
370
+ const kind2 = declarationsNamedKind(node, [node.parent]);
371
+ return kind2 && node.id.type === "Identifier" && !declarationsInsideForLoop(node) ? { name: node.id.name, node, kind: kind2 } : undefined;
372
372
  }
373
373
  const kind = declarationsNamedKind(node, []);
374
374
  const name = "id" in node && node.id && "name" in node.id ? node.id.name : undefined;
@@ -1081,32 +1081,32 @@ var serviceFunctions = defineRule3({
1081
1081
  }
1082
1082
  return {
1083
1083
  Program(program) {
1084
- const options = optionsFirst(context, {});
1084
+ const options2 = optionsFirst(context, {});
1085
1085
  const candidates = declarationsFileLevelFunctionCandidates(program);
1086
1086
  const exported = exportsCollectFunctions(program);
1087
1087
  operation = exported.length === 1 ? exported[0].node : undefined;
1088
- operations = new Set(options.frontend && !options.singleExport ? exported.map((entry) => entry.node) : operation ? [operation] : []);
1089
- if (options.frontend && !options.singleExport) {
1088
+ operations = new Set(options2.frontend && !options2.singleExport ? exported.map((entry) => entry.node) : operation ? [operation] : []);
1089
+ if (options2.frontend && !options2.singleExport) {
1090
1090
  for (const method of declarationsPublicServiceMethods(program))
1091
1091
  operations.add(method.node);
1092
1092
  }
1093
- allowLocalHelpers = options.frontend || (options.allowLocalHelpers ?? false);
1094
- allowReturnedMethods = options.allowReturnedMethods ?? false;
1095
- message = options.message ?? (options.frontend ? "Frontend service modules expose public domain operations. Keep operation-only helpers inside their public operation and genuinely shared helpers in domain .utils.ts; file-level private functions are forbidden." : allowLocalHelpers ? "Keep this helper inside the one exported operation; domain .utils.ts is only for genuinely shared logic." : "This service contract does not allow private helpers. Keep its existing factory/operation contract; domain .utils.ts is only for genuinely shared logic. Do not add a fake factory or public helper export.");
1096
- if (options.singleExport) {
1093
+ allowLocalHelpers = options2.frontend || (options2.allowLocalHelpers ?? false);
1094
+ allowReturnedMethods = options2.allowReturnedMethods ?? false;
1095
+ message = options2.message ?? (options2.frontend ? "Frontend service modules expose public domain operations. Keep operation-only helpers inside their public operation and genuinely shared helpers in domain .utils.ts; file-level private functions are forbidden." : allowLocalHelpers ? "Keep this helper inside the one exported operation; domain .utils.ts is only for genuinely shared logic." : "This service contract does not allow private helpers. Keep its existing factory/operation contract; domain .utils.ts is only for genuinely shared logic. Do not add a fake factory or public helper export.");
1096
+ if (options2.singleExport) {
1097
1097
  const bindings = exportsCollect(program);
1098
1098
  if (bindings.length !== 1 || !operation || bindings[0].default || bindings[0].reExport || bindings[0].typeOnly) {
1099
1099
  context.report({
1100
1100
  node: program,
1101
1101
  messageId: "exports",
1102
1102
  data: {
1103
- message: options.message ?? "Export exactly one named operation. Do not export helpers, types, constants or re-exports."
1103
+ message: options2.message ?? "Export exactly one named operation. Do not export helpers, types, constants or re-exports."
1104
1104
  }
1105
1105
  });
1106
1106
  }
1107
1107
  }
1108
1108
  for (const candidate of candidates) {
1109
- if ((options.frontend && !options.singleExport || candidates.length === 1) && exported.some((entry) => entry.name === candidate.name)) {
1109
+ if ((options2.frontend && !options2.singleExport || candidates.length === 1) && exported.some((entry) => entry.name === candidate.name)) {
1110
1110
  continue;
1111
1111
  }
1112
1112
  context.report({ node: candidate.node, messageId: "helper", data: { message, name: candidate.name } });
@@ -1188,8 +1188,8 @@ var domainConstants = defineRule4({
1188
1188
  const declaration = node.parent;
1189
1189
  if (declaration.type !== "VariableDeclaration" || declaration.kind !== "const" || declaration.parent.type !== "Program" && declaration.parent.type !== "ExportNamedDeclaration")
1190
1190
  return;
1191
- const options = optionsFirst(context, {});
1192
- if (node.id.type === "Identifier" && /^[A-Z][A-Z0-9_]+$/.test(node.id.name) || options.includeData && node.init && data(node.init)) {
1191
+ const options2 = optionsFirst(context, {});
1192
+ if (node.id.type === "Identifier" && /^[A-Z][A-Z0-9_]+$/.test(node.id.name) || options2.includeData && node.init && data(node.init)) {
1193
1193
  const file = context.filename.replaceAll("\\", "/");
1194
1194
  const owner = file.includes("/services/") && dirname2(file).split("/").at(-1) !== "services" ? basename(dirname2(file)) : basename(file).split(".")[0].replace(/-(?:action|query)$/, "");
1195
1195
  context.report({
@@ -1285,9 +1285,9 @@ function testTablesRegistration(call, names, functions) {
1285
1285
  if (callee.type === "CallExpression") {
1286
1286
  const builder = astDottedName(callee.callee);
1287
1287
  if (builder?.endsWith(".for") || builder?.endsWith(".each")) {
1288
- const argument = callee.arguments[0];
1289
- if (argument && argument.type !== "SpreadElement")
1290
- table = argument;
1288
+ const argument2 = callee.arguments[0];
1289
+ if (argument2 && argument2.type !== "SpreadElement")
1290
+ table = argument2;
1291
1291
  }
1292
1292
  callee = callee.callee;
1293
1293
  }
@@ -1408,20 +1408,20 @@ var testAssertions = defineRule6({
1408
1408
  createOnce(context) {
1409
1409
  return {
1410
1410
  "Program:exit"(program) {
1411
- const options = optionsFirst(context, {});
1412
- const tests = ["test", "it", ...options.additionalTestFunctions ?? []];
1411
+ const options2 = optionsFirst(context, {});
1412
+ const tests = ["test", "it", ...options2.additionalTestFunctions ?? []];
1413
1413
  const functions = new Map;
1414
1414
  const values = new Map;
1415
1415
  const patterns = [
1416
1416
  ...testAssertionFunctions,
1417
1417
  "assert.*",
1418
- ...options.additionalAssertionFunctions ?? []
1418
+ ...options2.additionalAssertionFunctions ?? []
1419
1419
  ].map(testTablesAssertionPattern);
1420
1420
  const aliases = new Map;
1421
1421
  const shadowed = new Set;
1422
1422
  const calls = [];
1423
1423
  const allowed = new Set;
1424
- const assertionRoots = new Set([...testAssertionFunctions, ...options.additionalAssertionFunctions ?? []].map((name) => name.split(".")[0]));
1424
+ const assertionRoots = new Set([...testAssertionFunctions, ...options2.additionalAssertionFunctions ?? []].map((name) => name.split(".")[0]));
1425
1425
  astVisit(program, [], (node) => {
1426
1426
  if (node.type === "CallExpression")
1427
1427
  calls.push(node);
@@ -1617,14 +1617,14 @@ var tanstackRuntime = defineRule8({
1617
1617
  }
1618
1618
  },
1619
1619
  createOnce(context) {
1620
- let options;
1620
+ let options2;
1621
1621
  let file = "";
1622
1622
  let serverFile = false;
1623
1623
  let rscFile = false;
1624
1624
  let shared = false;
1625
1625
  const runtimeIdentifiers = new Set;
1626
1626
  const imports = new Map;
1627
- const boundaries = [];
1627
+ const boundaries2 = [];
1628
1628
  function imported(node) {
1629
1629
  const entry = imports.get(node.name);
1630
1630
  if (!entry)
@@ -1652,12 +1652,12 @@ var tanstackRuntime = defineRule8({
1652
1652
  return;
1653
1653
  }
1654
1654
  function boundary(position) {
1655
- return boundaries.filter((entry) => position >= entry.start && position < entry.end).sort((a, b) => b.start - a.start)[0];
1655
+ return boundaries2.filter((entry) => position >= entry.start && position < entry.end).sort((a, b) => b.start - a.start)[0];
1656
1656
  }
1657
1657
  function serverPosition(position, staticImport = false) {
1658
1658
  const entry = boundary(position);
1659
1659
  if (entry)
1660
- return entry.side === "server" && (!staticImport || entry.kind !== "createIsomorphicFn" || options.rpcClients.includes(file));
1660
+ return entry.side === "server" && (!staticImport || entry.kind !== "createIsomorphicFn" || options2.rpcClients.includes(file));
1661
1661
  return serverFile;
1662
1662
  }
1663
1663
  function report(node, source, reason) {
@@ -1666,11 +1666,11 @@ var tanstackRuntime = defineRule8({
1666
1666
  function check(node, source, typeOnly, reExport = false, renderer) {
1667
1667
  if (typeOnly)
1668
1668
  return;
1669
- const target = boundaryResolveTarget(options, file, source);
1669
+ const target = boundaryResolveTarget(options2, file, source);
1670
1670
  const matches = (values) => values?.some((value) => source === value || source.startsWith(value + "/"));
1671
- const backend = options.backend.some((path) => target.startsWith(path + "/")) || options.backendPackages.some((name) => source === name || source.startsWith(name + "/") && /\/(?:server|rpc)(?:\/|$)/.test(source.slice(name.length)));
1672
- const server = Boolean(renderer) || builtinModules.includes(source) || source === "@orpc/server" || backend || /\.server(?:\.[cm]?[jt]sx?)?$/.test(target) || /^(?:node:|bun(?::|$)|cloudflare:)/.test(source) || /^@tanstack\/(?:react|solid|vue)-start\/(?:server|server-only)(?:\/|$)/.test(source) || /^react-dom\/server(?:\.|\/|$)/.test(source) || matches(options.serverImports);
1673
- const client = /(?:\/hooks\/|\.client-only(?:\.|$))/.test(target) || /^(?:react-dom\/client|@tanstack\/(?:react|solid|vue)-start\/client-only)(?:\/|$)/.test(source) || matches(options.clientImports);
1671
+ const backend = options2.backend.some((path) => target.startsWith(path + "/")) || options2.backendPackages.some((name) => source === name || source.startsWith(name + "/") && /\/(?:server|rpc)(?:\/|$)/.test(source.slice(name.length)));
1672
+ const server = Boolean(renderer) || builtinModules.includes(source) || source === "@orpc/server" || backend || /\.server(?:\.[cm]?[jt]sx?)?$/.test(target) || /^(?:node:|bun(?::|$)|cloudflare:)/.test(source) || /^@tanstack\/(?:react|solid|vue)-start\/(?:server|server-only)(?:\/|$)/.test(source) || /^react-dom\/server(?:\.|\/|$)/.test(source) || matches(options2.serverImports);
1673
+ const client = /(?:\/hooks\/|\.client-only(?:\.|$))/.test(target) || /^(?:react-dom\/client|@tanstack\/(?:react|solid|vue)-start\/client-only)(?:\/|$)/.test(source) || matches(options2.clientImports);
1674
1674
  if (client && (serverFile || rscFile || shared) && boundary(node.start)?.side !== "client") {
1675
1675
  const references = node.type === "ImportDeclaration" ? context.sourceCode.getDeclaredVariables(node).flatMap((variable) => variable.references.filter((reference) => runtimeIdentifiers.has(reference.identifier.range[0]))) : [];
1676
1676
  if (!shared && references.length && references.every((reference) => boundary(reference.identifier.range[0])?.side === "client"))
@@ -1745,14 +1745,14 @@ var tanstackRuntime = defineRule8({
1745
1745
  }
1746
1746
  return {
1747
1747
  Program(program) {
1748
- options = optionsFirst(context);
1748
+ options2 = optionsFirst(context);
1749
1749
  file = context.filename.replaceAll("\\", "/");
1750
1750
  serverFile = /\.server\.[cm]?[jt]sx?$/.test(file);
1751
1751
  rscFile = /\.rsc\.[cm]?[jt]sx?$/.test(file);
1752
- shared = /\.(?:utils|constants)\.[cm]?[jt]sx?$/.test(file) || options.web.some((path) => file.startsWith(path + "/lib/"));
1752
+ shared = /\.(?:utils|constants)\.[cm]?[jt]sx?$/.test(file) || options2.web.some((path) => file.startsWith(path + "/lib/"));
1753
1753
  runtimeIdentifiers.clear();
1754
1754
  imports.clear();
1755
- boundaries.length = 0;
1755
+ boundaries2.length = 0;
1756
1756
  for (const statement of program.body) {
1757
1757
  if (statement.type !== "ImportDeclaration" || statement.importKind === "type")
1758
1758
  continue;
@@ -1795,7 +1795,7 @@ var tanstackRuntime = defineRule8({
1795
1795
  const side = kind === "createServerOnlyFn" || kind === "createServerFn" && method === "handler" || ["createIsomorphicFn", "createMiddleware"].includes(kind ?? "") && method === "server" ? "server" : kind === "createClientOnlyFn" || ["createIsomorphicFn", "createMiddleware"].includes(kind ?? "") && method === "client" ? "client" : undefined;
1796
1796
  const callback = node.arguments[0];
1797
1797
  if (kind && side && callback && (callback.type === "FunctionExpression" || callback.type === "ArrowFunctionExpression"))
1798
- boundaries.push({ start: callback.start, end: callback.end, side, kind });
1798
+ boundaries2.push({ start: callback.start, end: callback.end, side, kind });
1799
1799
  });
1800
1800
  },
1801
1801
  ImportDeclaration(node) {
@@ -1829,7 +1829,7 @@ var tanstackRuntime = defineRule8({
1829
1829
  ImportExpression(node) {
1830
1830
  if (node.source.type === "Literal" && typeof node.source.value === "string")
1831
1831
  check(node, node.source.value, false);
1832
- else if (!options.allowComputedImportsIn?.includes(file))
1832
+ else if (!options2.allowComputedImportsIn?.includes(file))
1833
1833
  context.report({ node, messageId: "computed" });
1834
1834
  },
1835
1835
  MemberExpression(node) {
@@ -1874,19 +1874,19 @@ var importBoundaries = defineRule9({
1874
1874
  },
1875
1875
  createOnce(context) {
1876
1876
  function check(node, source, typeOnly, reExport = false) {
1877
- const options = optionsFirst(context);
1877
+ const options2 = optionsFirst(context);
1878
1878
  const file = context.filename.replaceAll("\\", "/");
1879
1879
  const contains = (directory, candidate) => candidate === directory || candidate.startsWith(`${directory}/`);
1880
- const roots = [...options.web, ...options.backend].sort((a, b) => b.length - a.length);
1880
+ const roots = [...options2.web, ...options2.backend].sort((a, b) => b.length - a.length);
1881
1881
  const owner = roots.find((path) => contains(path, file));
1882
- const web = owner && options.web.includes(owner) ? owner : undefined;
1883
- const target = boundaryResolveTarget(options, file, source);
1884
- const backendImport = options.backend.some((path) => contains(path, target)) || options.backendPackages.some((name) => source === name || source.startsWith(`${name}/`));
1882
+ const web = owner && options2.web.includes(owner) ? owner : undefined;
1883
+ const target = boundaryResolveTarget(options2, file, source);
1884
+ const backendImport = options2.backend.some((path) => contains(path, target)) || options2.backendPackages.some((name) => source === name || source.startsWith(`${name}/`));
1885
1885
  let reason;
1886
- if (web && backendImport && !options.publicEntrypoints.includes(source)) {
1886
+ if (web && backendImport && !options2.publicEntrypoints.includes(source)) {
1887
1887
  reason = "frontend code uses only public backend entry points";
1888
1888
  }
1889
- if ((Array.isArray(options.packages) ? options.packages : [options.packages]).some((path) => path && contains(path, file)) && (backendImport || options.web.some((path) => contains(path, target)) || options.appPackages.some((name) => source === name || source.startsWith(`${name}/`)))) {
1889
+ if ((Array.isArray(options2.packages) ? options2.packages : [options2.packages]).some((path) => path && contains(path, file)) && (backendImport || options2.web.some((path) => contains(path, target)) || options2.appPackages.some((name) => source === name || source.startsWith(`${name}/`)))) {
1890
1890
  reason = "shared packages do not import app internals";
1891
1891
  }
1892
1892
  if (file.includes("/services/") && /\/services\/service(?:\.ts)?$/.test(target) && !typeOnly) {
@@ -1898,7 +1898,7 @@ var importBoundaries = defineRule9({
1898
1898
  if (file.endsWith(".client.ts") && !typeOnly && /^(react|react-dom)(\/|$)/.test(source)) {
1899
1899
  reason = "client services are React-free; use hooks for React behavior";
1900
1900
  }
1901
- if (options.fileRoles?.length) {
1901
+ if (options2.fileRoles?.length) {
1902
1902
  const targetOwner = roots.find((path) => contains(path, target));
1903
1903
  const domain = (path, appRoot) => {
1904
1904
  const relative = path.slice(appRoot.length + 1);
@@ -1911,7 +1911,7 @@ var importBoundaries = defineRule9({
1911
1911
  };
1912
1912
  const sameDomain = owner && targetOwner && owner === targetOwner && domain(file, owner) && domain(file, owner) === domain(target, targetOwner);
1913
1913
  const targetStem = target.replace(/\.[cm]?[jt]sx?$/, "");
1914
- const concept = options.fileRoles.some((role) => targetStem.endsWith(`.${role}`));
1914
+ const concept = options2.fileRoles.some((role) => targetStem.endsWith(`.${role}`));
1915
1915
  const publicSurface = /\.(?:service|client|server|rsc|utils)$/.test(targetStem) && !/\/(?:[^/]+)-(?:action|query)\./.test(targetStem);
1916
1916
  const privateTarget = concept || targetOwner && domain(target, targetOwner) && !publicSurface && !/\.(?:types|constants)$/.test(targetStem);
1917
1917
  if (privateTarget && !sameDomain) {
@@ -1921,11 +1921,11 @@ var importBoundaries = defineRule9({
1921
1921
  reason = "public service and utility surfaces must not re-export private domain internals";
1922
1922
  }
1923
1923
  }
1924
- if (options.portableLib && web && contains(`${web}/lib`, file)) {
1924
+ if (options2.portableLib && web && contains(`${web}/lib`, file)) {
1925
1925
  const portableTarget = target.startsWith("/") ? resolve2(target).replaceAll("\\", "/") : target;
1926
1926
  const appService = roots.some((path) => contains(`${path}/services`, portableTarget));
1927
- const backendTarget = options.backend.some((path) => contains(path, portableTarget));
1928
- const appPackage = options.appPackages.some((name) => source === name || source.startsWith(`${name}/`));
1927
+ const backendTarget = options2.backend.some((path) => contains(path, portableTarget));
1928
+ const appPackage = options2.appPackages.some((name) => source === name || source.startsWith(`${name}/`));
1929
1929
  const appType = roots.some((path) => contains(`${path}/types`, portableTarget));
1930
1930
  const stem = basename2(file).replace(/\.[cm]?[jt]sx?$/, "").replace(/\.utils$/, "");
1931
1931
  const ownType = typeOnly && !reExport && portableTarget.replace(/\.[cm]?[jt]sx?$/, "") === `${web}/types/${stem}.types`;
@@ -1972,9 +1972,9 @@ var importBoundaries = defineRule9({
1972
1972
  }
1973
1973
  },
1974
1974
  TSImportType(node) {
1975
- const options = optionsFirst(context);
1975
+ const options2 = optionsFirst(context);
1976
1976
  const file = context.filename.replaceAll("\\", "/");
1977
- if (options.portableLib && options.web.some((path) => file.startsWith(`${path}/lib/`))) {
1977
+ if (options2.portableLib && options2.web.some((path) => file.startsWith(`${path}/lib/`))) {
1978
1978
  check(node, node.source.value, true);
1979
1979
  }
1980
1980
  }
@@ -0,0 +1,2 @@
1
+ /** Reject React memoization imports and member calls while leaving unrelated cache.memo calls alone. */
2
+ export declare const memoization: import("@oxlint/plugins").Rule;
@@ -0,0 +1,2 @@
1
+ /** Require a rule name and a reason in lint disables, such as no-console -- CLI output. */
2
+ export declare const directives: import("@oxlint/plugins").Rule;
@@ -0,0 +1,2 @@
1
+ /** Keep backend operations strict; frontend modules may expose several operations with helpers nested inside them. */
2
+ export declare const serviceFunctions: import("@oxlint/plugins").Rule;
@@ -0,0 +1,2 @@
1
+ /** Only the matching factory-derived Service alias stays local; ChatDeps belongs in domain types. */
2
+ export declare const serviceTypes: import("@oxlint/plugins").Rule;
@@ -0,0 +1,2 @@
1
+ /** Check direct runtime dependencies and imported TanStack boundaries; compilation still owns graph isolation. */
2
+ export declare const tanstackRuntime: import("@oxlint/plugins").Rule;
@@ -0,0 +1,2 @@
1
+ /** Check assertion presence and placement, including assertions called through static table rows. */
2
+ export declare const testAssertions: import("@oxlint/plugins").Rule;
@@ -0,0 +1,2 @@
1
+ /** Reject focused, skipped and placeholder tests, including renamed imports such as check.only. */
2
+ export declare const testModifiers: import("@oxlint/plugins").Rule;
@@ -0,0 +1,34 @@
1
+ export interface ModuleBoundaryOptions {
2
+ web: string[];
3
+ backend: string[];
4
+ packages: string | string[];
5
+ fileRoles?: string[];
6
+ portableLib?: boolean;
7
+ backendPackages: string[];
8
+ appPackages: string[];
9
+ aliases: Record<string, string>;
10
+ publicEntrypoints: string[];
11
+ }
12
+ export interface ModuleServiceFunctionsOptions {
13
+ frontend?: boolean;
14
+ allowLocalHelpers?: boolean;
15
+ allowReturnedMethods?: boolean;
16
+ singleExport?: boolean;
17
+ message?: string;
18
+ }
19
+ export interface ModuleDomainConstantsOptions {
20
+ allowServiceMethods?: boolean;
21
+ includeData?: boolean;
22
+ }
23
+ export interface ModuleRuntimeOptions extends Pick<ModuleBoundaryOptions, 'web' | 'backend' | 'aliases' | 'backendPackages'> {
24
+ rpcClients: string[];
25
+ serverImports?: string[];
26
+ clientImports?: string[];
27
+ allowComputedImportsIn?: string[];
28
+ }
29
+ export interface ModuleTestModifiersOptions {
30
+ additionalTestFunctions?: string[];
31
+ }
32
+ export interface ModuleTestAssertionsOptions extends ModuleTestModifiersOptions {
33
+ additionalAssertionFunctions?: string[];
34
+ }
@@ -0,0 +1,11 @@
1
+ import type { ESTree } from '@oxlint/plugins';
2
+ import type { AstRuntimeFunction } from '../utils/helpers/ast.ts';
3
+ export interface TestRegistration {
4
+ call: ESTree.CallExpression;
5
+ callback: AstRuntimeFunction;
6
+ table?: ESTree.Expression;
7
+ }
8
+ export interface TestTableRow {
9
+ bindings: Map<ESTree.Node, AstRuntimeFunction>;
10
+ callbacks: AstRuntimeFunction[];
11
+ }