oxlint-plugin-arch 0.6.2 → 0.7.0

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.
@@ -935,6 +935,49 @@ function routesInspectFunction(context, item, bannedHooks, banIntrinsicJsx) {
935
935
  }
936
936
  });
937
937
  }
938
+ // src/utils/helpers/runtime.ts
939
+ import { builtinModules } from "node:module";
940
+ var runtimeServerGlobals = ["process", "Bun", "Deno", "Buffer", "__dirname", "__filename", "require"];
941
+ var runtimeBrowserGlobals = [
942
+ "window",
943
+ "document",
944
+ "navigator",
945
+ "localStorage",
946
+ "sessionStorage",
947
+ "location",
948
+ "history",
949
+ "screen",
950
+ "HTMLElement",
951
+ "Element",
952
+ "HTMLInputElement",
953
+ "ResizeObserver",
954
+ "MutationObserver",
955
+ "IntersectionObserver",
956
+ "FileReader",
957
+ "matchMedia",
958
+ "getComputedStyle",
959
+ "requestAnimationFrame",
960
+ "cancelAnimationFrame",
961
+ "alert",
962
+ "confirm",
963
+ "prompt"
964
+ ];
965
+ function runtimeMatchesImport(values, source) {
966
+ return Boolean(values?.some((value) => source === value || source.startsWith(value + "/")));
967
+ }
968
+ function runtimeIsBackendImport(options, target, source) {
969
+ return options.backend.some((path) => target.startsWith(path + "/")) || options.backendPackages.some((name) => source === name || source.startsWith(name + "/") && /\/(?:server|rpc)(?:\/|$)/.test(source.slice(name.length)));
970
+ }
971
+ function runtimeIsServerPlatformImport(source) {
972
+ return builtinModules.includes(source) || source === "@orpc/server" || /^(?:node:|bun(?::|$)|cloudflare:)/.test(source) || /^react-dom\/server(?:\.|\/|$)/.test(source);
973
+ }
974
+ function runtimeMemberName(node) {
975
+ if (node?.type !== "MemberExpression")
976
+ return null;
977
+ if (!node.computed && node.property.type === "Identifier")
978
+ return node.property.name;
979
+ return node.computed && node.property.type === "Literal" && typeof node.property.value === "string" ? node.property.value : null;
980
+ }
938
981
  // src/utils/helpers/sanitizers.ts
939
982
  function sanitizersCallsConfigured(node, names) {
940
983
  let found = false;
@@ -1589,7 +1632,6 @@ var directives = defineRule7({
1589
1632
  });
1590
1633
 
1591
1634
  // src/rules/module-policy/tanstack-runtime.ts
1592
- import { builtinModules } from "node:module";
1593
1635
  import { defineRule as defineRule8 } from "@oxlint/plugins";
1594
1636
  var tanstackRuntime = defineRule8({
1595
1637
  meta: {
@@ -1667,10 +1709,8 @@ var tanstackRuntime = defineRule8({
1667
1709
  if (typeOnly)
1668
1710
  return;
1669
1711
  const target = boundaryResolveTarget(options2, file, source);
1670
- const matches = (values) => values?.some((value) => source === value || source.startsWith(value + "/"));
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);
1712
+ const server = Boolean(renderer) || runtimeIsServerPlatformImport(source) || runtimeIsBackendImport(options2, target, source) || /\.server(?:\.[cm]?[jt]sx?)?$/.test(target) || /^@tanstack\/(?:react|solid|vue)-start\/(?:server|server-only)(?:\/|$)/.test(source) || runtimeMatchesImport(options2.serverImports, source);
1713
+ const client = /(?:\/hooks\/|\.client-only(?:\.|$))/.test(target) || /^(?:react-dom\/client|@tanstack\/(?:react|solid|vue)-start\/client-only)(?:\/|$)/.test(source) || runtimeMatchesImport(options2.clientImports, source);
1674
1714
  if (client && (serverFile || rscFile || shared) && boundary(node.start)?.side !== "client") {
1675
1715
  const references = node.type === "ImportDeclaration" ? context.sourceCode.getDeclaredVariables(node).flatMap((variable) => variable.references.filter((reference) => runtimeIdentifiers.has(reference.identifier.range[0]))) : [];
1676
1716
  if (!shared && references.length && references.every((reference) => boundary(reference.identifier.range[0])?.side === "client"))
@@ -1699,34 +1739,9 @@ var tanstackRuntime = defineRule8({
1699
1739
  report(node, source, "use an inline imported TanStack server boundary; static isomorphic server imports belong only to the configured RPC owner");
1700
1740
  }
1701
1741
  function global(node) {
1702
- const serverNames = ["process", "Bun", "Deno", "Buffer", "__dirname", "__filename", "require"];
1703
- const browserNames = [
1704
- "window",
1705
- "document",
1706
- "navigator",
1707
- "localStorage",
1708
- "sessionStorage",
1709
- "location",
1710
- "history",
1711
- "screen",
1712
- "HTMLElement",
1713
- "Element",
1714
- "HTMLInputElement",
1715
- "ResizeObserver",
1716
- "MutationObserver",
1717
- "IntersectionObserver",
1718
- "FileReader",
1719
- "matchMedia",
1720
- "getComputedStyle",
1721
- "requestAnimationFrame",
1722
- "cancelAnimationFrame",
1723
- "alert",
1724
- "confirm",
1725
- "prompt"
1726
- ];
1727
1742
  const member = node.parent.type === "MemberExpression" && node.parent.object === node && node.name === "globalThis" ? node.parent : undefined;
1728
- const name = member && !member.computed && member.property.type === "Identifier" ? member.property.name : member?.computed && member.property.type === "Literal" && typeof member.property.value === "string" ? member.property.value : node.name;
1729
- if (!serverNames.includes(name) && !browserNames.includes(name))
1743
+ const name = runtimeMemberName(member) ?? node.name;
1744
+ if (!runtimeServerGlobals.includes(name) && !runtimeBrowserGlobals.includes(name))
1730
1745
  return;
1731
1746
  let scope = context.sourceCode.getScope(node);
1732
1747
  while (scope) {
@@ -1738,9 +1753,9 @@ var tanstackRuntime = defineRule8({
1738
1753
  const references = context.sourceCode.getScope(node).references;
1739
1754
  if (!references.some((reference) => reference.identifier.range[0] === node.start && runtimeIdentifiers.has(reference.identifier.range[0])))
1740
1755
  return;
1741
- if (serverNames.includes(name) && (shared || !serverPosition(node.start)))
1756
+ if (runtimeServerGlobals.includes(name) && (shared || !serverPosition(node.start)))
1742
1757
  report(node, name, "server globals require a server execution boundary");
1743
- if (browserNames.includes(name) && (serverFile || rscFile) && boundary(node.start)?.side !== "client")
1758
+ if (runtimeBrowserGlobals.includes(name) && (serverFile || rscFile) && boundary(node.start)?.side !== "client")
1744
1759
  report(node, name, "browser globals are unavailable during server execution");
1745
1760
  }
1746
1761
  return {
@@ -2023,6 +2038,158 @@ var memoization = defineRule10({
2023
2038
  }
2024
2039
  });
2025
2040
 
2041
+ // src/rules/module-policy/next-runtime.ts
2042
+ import { defineRule as defineRule11 } from "@oxlint/plugins";
2043
+ var nextRuntime = defineRule11({
2044
+ meta: {
2045
+ type: "problem",
2046
+ schema: [
2047
+ {
2048
+ type: "object",
2049
+ additionalProperties: false,
2050
+ properties: {
2051
+ web: { type: "array", items: { type: "string" } },
2052
+ backend: { type: "array", items: { type: "string" } },
2053
+ aliases: { type: "object", additionalProperties: { type: "string" } },
2054
+ backendPackages: { type: "array", items: { type: "string" } },
2055
+ serverImports: { type: "array", items: { type: "string" } },
2056
+ clientImports: { type: "array", items: { type: "string" } },
2057
+ allowComputedImportsIn: { type: "array", items: { type: "string" } }
2058
+ },
2059
+ required: ["web", "backend", "aliases", "backendPackages"]
2060
+ }
2061
+ ],
2062
+ messages: {
2063
+ runtime: "{{source}} is not valid here: {{reason}}.",
2064
+ directive: "'{{directive}}' does not match this {{side}} module; rename the file or remove the directive.",
2065
+ action: "Server actions modules must start with 'use server'.",
2066
+ computed: "Use a literal dynamic import so runtime ownership can be checked, or declare this exact file in allowComputedImportsIn after reviewing its targets."
2067
+ }
2068
+ },
2069
+ createOnce(context) {
2070
+ let options2;
2071
+ let file = "";
2072
+ let server = false;
2073
+ let client = false;
2074
+ let shared = false;
2075
+ const reactNamespaces = new Set;
2076
+ function report(node, source, reason) {
2077
+ context.report({ node, messageId: "runtime", data: { source, reason } });
2078
+ }
2079
+ function check(node, source, typeOnly) {
2080
+ if (typeOnly)
2081
+ return;
2082
+ const target = boundaryResolveTarget(options2, file, source);
2083
+ const serverOnly = runtimeIsServerPlatformImport(source) || runtimeIsBackendImport(options2, target, source) || /\.(?:server|rsc)(?:\.[cm]?[jt]sx?)?$/.test(target) || /^(?:server-only|next\/headers|next\/cache|next\/server)$/.test(source) || runtimeMatchesImport(options2.serverImports, source);
2084
+ const clientOnly = /(?:\/hooks\/|\.client-only(?:\.|$))/.test(target) || /^(?:client-only|react-dom\/client)$/.test(source) || runtimeMatchesImport(options2.clientImports, source);
2085
+ if (clientOnly && (server || shared)) {
2086
+ report(node, source, "browser-only dependencies do not belong in server, RSC or shared modules");
2087
+ } else if (serverOnly && !server) {
2088
+ report(node, source, shared ? "shared utilities/constants/lib cannot import server-only dependencies" : "server-only dependencies belong in .server, .rsc or server action modules");
2089
+ }
2090
+ }
2091
+ function resolve3(node) {
2092
+ let scope = context.sourceCode.getScope(node);
2093
+ while (scope) {
2094
+ const variable = scope.set.get(node.name);
2095
+ if (variable?.defs.length)
2096
+ return variable;
2097
+ scope = scope.upper;
2098
+ }
2099
+ return null;
2100
+ }
2101
+ function clientHook(name) {
2102
+ return /^use[A-Z]/.test(name) && !["useId", "use"].includes(name);
2103
+ }
2104
+ function inType(node) {
2105
+ return ["TSTypeQuery", "TSQualifiedName", "TSTypeReference"].includes(node.parent?.type ?? "");
2106
+ }
2107
+ function globalReferences() {
2108
+ const scope = context.sourceCode.scopeManager.scopes[0];
2109
+ if (!scope)
2110
+ return [];
2111
+ const implicit = [...scope.set.values()].filter((variable) => !variable.defs.length);
2112
+ return [...scope.through, ...implicit.flatMap((variable) => variable.references)].map((reference) => reference.identifier).filter((node) => node.type === "Identifier" && !inType(node));
2113
+ }
2114
+ function global(reference) {
2115
+ const owned = reference.name === "globalThis" ? runtimeMemberName(reference.parent) : null;
2116
+ const name = owned ?? reference.name;
2117
+ const serverGlobal = runtimeServerGlobals.includes(name);
2118
+ const browserGlobal = runtimeBrowserGlobals.includes(name);
2119
+ if (!serverGlobal && !browserGlobal)
2120
+ return;
2121
+ const node = owned ? reference.parent : reference;
2122
+ const env = node.parent;
2123
+ const key = env?.parent;
2124
+ const publicEnv = name === "process" && runtimeMemberName(env) === "env" && env?.type === "MemberExpression" && env.object === node && key?.type === "MemberExpression" && key.object === env && (runtimeMemberName(key) ?? "").startsWith("NEXT_PUBLIC_");
2125
+ if (serverGlobal && !server && !publicEnv && (client || shared))
2126
+ report(node, name, "server globals are unavailable in browser and shared modules");
2127
+ if (browserGlobal && server)
2128
+ report(node, name, "browser globals are unavailable during server execution");
2129
+ }
2130
+ return {
2131
+ Program(program) {
2132
+ options2 = optionsFirst(context);
2133
+ reactNamespaces.clear();
2134
+ file = context.filename.replaceAll("\\", "/");
2135
+ const directives2 = program.body.filter((statement) => statement.type === "ExpressionStatement" && ("directive" in statement) && Boolean(statement.directive)).map((statement) => ({ node: statement, value: statement.directive ?? "" }));
2136
+ const useServer = directives2.find((entry) => entry.value === "use server");
2137
+ const useClient = directives2.find((entry) => entry.value === "use client");
2138
+ const serverName = /\.(?:server|rsc|action)\.[cm]?[jt]sx?$/.test(file);
2139
+ const clientName = /\.client\.[cm]?[jt]sx?$/.test(file);
2140
+ shared = /\.(?:utils|constants)\.[cm]?[jt]sx?$/.test(file) || options2.web.some((path) => file.startsWith(path + "/lib/"));
2141
+ server = serverName || Boolean(useServer);
2142
+ client = clientName || Boolean(useClient);
2143
+ if (useClient && serverName)
2144
+ context.report({ node: useClient.node, messageId: "directive", data: { directive: "use client", side: "server" } });
2145
+ if (useServer && clientName)
2146
+ context.report({ node: useServer.node, messageId: "directive", data: { directive: "use server", side: "client" } });
2147
+ if (/\.action\.[cm]?[jt]sx?$/.test(file) && !useServer)
2148
+ context.report({ node: program, messageId: "action" });
2149
+ if (useClient)
2150
+ server = false;
2151
+ },
2152
+ ImportDeclaration(node) {
2153
+ const typeOnly = node.importKind === "type" || node.specifiers.length > 0 && node.specifiers.every((specifier) => specifier.type === "ImportSpecifier" && specifier.importKind === "type");
2154
+ check(node, node.source.value, typeOnly);
2155
+ if (typeOnly || !(server || shared) || node.source.value !== "react")
2156
+ return;
2157
+ for (const specifier of node.specifiers) {
2158
+ if (specifier.type !== "ImportSpecifier")
2159
+ reactNamespaces.add(specifier);
2160
+ else if (specifier.importKind !== "type" && specifier.imported.type === "Identifier" && clientHook(specifier.imported.name))
2161
+ report(specifier, specifier.imported.name, "client hooks do not belong in server, RSC or shared modules");
2162
+ }
2163
+ },
2164
+ MemberExpression(node) {
2165
+ const name = runtimeMemberName(node);
2166
+ if (!name || !clientHook(name) || node.object.type !== "Identifier")
2167
+ return;
2168
+ const binding = resolve3(node.object)?.defs[0]?.node;
2169
+ if (binding && reactNamespaces.has(binding))
2170
+ report(node, `${node.object.name}.${name}`, "client hooks do not belong in server, RSC or shared modules");
2171
+ },
2172
+ ExportNamedDeclaration(node) {
2173
+ if (node.source)
2174
+ check(node, node.source.value, node.exportKind === "type" || node.specifiers.length > 0 && node.specifiers.every((specifier) => specifier.exportKind === "type"));
2175
+ },
2176
+ ExportAllDeclaration(node) {
2177
+ check(node, node.source.value, node.exportKind === "type");
2178
+ },
2179
+ ImportExpression(node) {
2180
+ if (node.source.type === "Literal" && typeof node.source.value === "string")
2181
+ check(node, node.source.value, false);
2182
+ else if (!options2.allowComputedImportsIn?.includes(file))
2183
+ context.report({ node, messageId: "computed" });
2184
+ },
2185
+ "Program:exit"() {
2186
+ for (const reference of globalReferences())
2187
+ global(reference);
2188
+ }
2189
+ };
2190
+ }
2191
+ });
2192
+
2026
2193
  // src/rules/module-policy/index.ts
2027
2194
  var module_policy_default = eslintCompatPlugin({
2028
2195
  meta: { name: "modules" },
@@ -2039,6 +2206,7 @@ var module_policy_default = eslintCompatPlugin({
2039
2206
  "local-type-alias": syntaxRule("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."),
2040
2207
  "no-unknown": syntaxRule("TSUnknownKeyword", "Use an owned domain type rather than unknown."),
2041
2208
  memoization,
2209
+ "next-runtime": nextRuntime,
2042
2210
  "pascal-interface": syntaxRule("TSInterfaceDeclaration:not([id.name=/^[A-Z][a-zA-Z0-9]*$/])", "Use PascalCase for interfaces."),
2043
2211
  "reasoned-directives": directives,
2044
2212
  "rpc-database": syntaxRule("CallExpression[callee.property.name=/^(selectFrom|insertInto|updateTable|deleteFrom|mergeInto|executeQuery)$/]", "RPC handlers delegate database work to services."),
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Checks Next.js runtime ownership from file names and directives: `.server`/`.rsc` and `'use server'` modules stay off
3
+ * the browser, `.client` and `'use client'` modules stay off server-only APIs, and shared modules use neither.
4
+ *
5
+ * Example: `chat.client.ts` importing `next/headers` fails; `chat.server.ts` importing it passes.
6
+ */
7
+ export declare const nextRuntime: import("@oxlint/plugins").Rule;
@@ -26,6 +26,11 @@ export interface ModuleRuntimeOptions extends Pick<ModuleBoundaryOptions, 'web'
26
26
  clientImports?: string[];
27
27
  allowComputedImportsIn?: string[];
28
28
  }
29
+ export interface ModuleNextRuntimeOptions extends Pick<ModuleBoundaryOptions, 'web' | 'backend' | 'aliases' | 'backendPackages'> {
30
+ serverImports?: string[];
31
+ clientImports?: string[];
32
+ allowComputedImportsIn?: string[];
33
+ }
29
34
  export interface ModuleTestModifiersOptions {
30
35
  additionalTestFunctions?: string[];
31
36
  }
@@ -3,14 +3,21 @@ export type PresetPolicy = boolean | ((current: OxlintOverride[]) => OxlintOverr
3
3
  export type PresetPolicyName = 'typePlacement' | 'typeSafety' | 'serviceStructure' | 'fileLayout' | 'naming' | 'comments' | 'formatting' | 'statementLayout' | 'imports' | 'react' | 'effects' | 'memoization' | 'routes' | 'forms' | 'schemas' | 'boundaries' | 'tests' | 'trivialFunctions' | 'mutableState' | 'backend' | 'tailwind' | 'shadcn' | 'rpcClientOwnership';
4
4
  export type PresetRole = 'web' | 'server' | 'runner' | 'scripts' | 'packages';
5
5
  export type PresetFolderLayout = 'flat' | 'domain';
6
+ export type PresetFramework = 'tanstack-start' | 'next';
6
7
  export interface PresetArchitectureEntry {
7
8
  role: PresetRole;
8
9
  layout?: Record<string, PresetFolderLayout>;
10
+ /** Web roots only: selects the route and runtime-boundary policies. */
11
+ framework?: PresetFramework;
12
+ /** Web, server and runner roots only: the source folder inside the root; `src` when omitted, `.` for the root itself. */
13
+ sourceDir?: string;
9
14
  }
10
15
  export type PresetArchitecture = Record<string, PresetRole | PresetArchitectureEntry>;
11
16
  export interface PresetResolvedRoot {
12
17
  role: PresetRole;
13
18
  layout: Record<string, PresetFolderLayout>;
19
+ framework?: PresetFramework;
20
+ sourceDir?: string;
14
21
  }
15
22
  export interface PresetFolderScope {
16
23
  path: string;
@@ -19,8 +26,13 @@ export interface PresetFolderScope {
19
26
  }
20
27
  export interface PresetOptions {
21
28
  root?: string;
29
+ /** Default framework for web roots that do not set their own; `tanstack-start` when omitted. */
30
+ framework?: PresetFramework;
22
31
  architecture?: PresetArchitecture;
23
32
  severity?: 'error' | 'warn';
33
+ /** Per-rule levels applied after `severity`, for rules the preset enables. */
34
+ ruleSeverities?: Record<string, 'error' | 'warn' | 'off'>;
35
+ formatting?: PresetFormattingOptions;
24
36
  limits?: PresetLimitsOptions;
25
37
  modules?: PresetModulesOptions;
26
38
  imports?: PresetImportsOptions;
@@ -34,11 +46,16 @@ export interface PresetOptions {
34
46
  ruleExclusions?: Record<string, string[]>;
35
47
  cliFiles?: string[];
36
48
  tanstackStart?: PresetTanstackStartOptions;
49
+ next?: PresetNextOptions;
37
50
  tailwind?: false | PresetTailwindOptions;
38
51
  shadcn?: false | PresetShadcnOptions;
39
52
  }
40
53
  export interface PresetPolicyOptions extends Partial<Record<PresetPolicyName, PresetPolicy>> {
41
54
  }
55
+ export interface PresetFormattingOptions {
56
+ semi?: 'always' | 'never';
57
+ quotes?: 'single' | 'double';
58
+ }
42
59
  export interface PresetLimitsOptions {
43
60
  maxFileLines?: number;
44
61
  maxFunctionComplexity?: number;
@@ -46,6 +63,8 @@ export interface PresetLimitsOptions {
46
63
  export interface PresetModulesOptions {
47
64
  customFileRoles?: string[];
48
65
  migrationFiles?: string[];
66
+ /** Type files allowed outside `types/`; they follow the backend `types/` contract. */
67
+ colocatedTypeFiles?: string[];
49
68
  }
50
69
  export interface PresetTestsOptions {
51
70
  profile?: 'standard' | 'fixtures';
@@ -77,6 +96,11 @@ export interface PresetTanstackStartOptions {
77
96
  additionalClientOnlyImports?: string[];
78
97
  computedImportAllowedFiles?: string[];
79
98
  }
99
+ export interface PresetNextOptions {
100
+ additionalServerOnlyImports?: string[];
101
+ additionalClientOnlyImports?: string[];
102
+ computedImportAllowedFiles?: string[];
103
+ }
80
104
  export interface PresetTailwindOptions {
81
105
  cssEntryPoint?: string;
82
106
  cssEntryPointsByRoot?: Record<string, string>;
@@ -107,7 +131,12 @@ export interface PresetContext {
107
131
  css: Record<string, string>;
108
132
  baseImports: PresetImportRestriction[];
109
133
  web: string[];
134
+ /** Web `src` roots per framework. */
135
+ tanstackWeb: string[];
136
+ nextWeb: string[];
110
137
  backend: string[];
138
+ /** Server and runner architecture roots, without their source folder. */
139
+ backendRoots: string[];
111
140
  app: string[];
112
141
  appFiles: string[];
113
142
  webFiles: string[];
@@ -14,7 +14,7 @@ export declare function declarationsRuntimeName(node: DeclarationsRuntime): stri
14
14
  export declare function declarationsRuntimeKind(node: DeclarationsRuntime): 'class' | 'enum' | 'function' | 'variable';
15
15
  export declare function declarationsIsTrivialExpression(node: ESTree.Expression): boolean;
16
16
  export declare function declarationsIsTrivialFunction(node: AstRuntimeFunction): boolean;
17
- export declare const declarationsKinds: readonly ["function", "const", "let", "var", "class", "type", "interface", "enum"];
17
+ export declare const declarationsKinds: readonly ['function', 'const', 'let', 'var', 'class', 'type', 'interface', 'enum'];
18
18
  export type DeclarationsKind = (typeof declarationsKinds)[number];
19
19
  export interface DeclarationsNamed {
20
20
  name: string;
@@ -10,6 +10,7 @@ export * from './options.ts';
10
10
  export * from './orpc.ts';
11
11
  export * from './react-components.ts';
12
12
  export * from './routes.ts';
13
+ export * from './runtime.ts';
13
14
  export * from './sanitizers.ts';
14
15
  export * from './schemas.ts';
15
16
  export * from './trivial.ts';
@@ -5,9 +5,9 @@ export declare const layoutIndentSchema: {
5
5
  minimum: number;
6
6
  enum?: undefined;
7
7
  } | {
8
+ minimum?: undefined;
8
9
  type: string;
9
10
  enum: string[];
10
- minimum?: undefined;
11
11
  })[];
12
12
  };
13
13
  export declare function layoutLineIndent(text: string, offset: number): string;
@@ -6,7 +6,7 @@ export declare function namingFolderPrefixes(folders: string[], separator: strin
6
6
  export declare function namingPascalCase(value: string): string;
7
7
  export declare function namingCamelCase(value: string): string;
8
8
  export declare function namingMatchTemplate(basename: string, template: string, placeholderPattern?: string): Record<string, string> | null;
9
- export declare const namingStemModes: readonly ["before-first-dot", "full-basename"];
9
+ export declare const namingStemModes: readonly ['before-first-dot', 'full-basename'];
10
10
  export type NamingStemMode = (typeof namingStemModes)[number];
11
11
  export declare function namingNormalized(value: string, normalize: 'remove-separators' | 'none'): string;
12
12
  export declare function namingFileStem(basename: string, mode?: NamingStemMode, trailingRoles?: readonly string[], roleSeparators?: readonly string[]): string;
@@ -2,5 +2,6 @@ import type { OxlintOverride } from 'oxlint';
2
2
  import type { PresetRules } from '../../types/preset.types.ts';
3
3
  export declare function presetScopes(roots: string[], suffix?: string): string[];
4
4
  export declare function presetOverride(files: string[], rules: PresetRules, excludeFiles?: string[]): OxlintOverride;
5
+ export declare function presetRuleSeverities(rules: PresetRules, severities: Record<string, 'error' | 'warn' | 'off'>): PresetRules;
5
6
  export declare function presetPackageName(root: string, directory: string): string | undefined;
6
7
  export declare function presetSeverity(rules: PresetRules, level: 'error' | 'warn'): PresetRules;
@@ -0,0 +1,14 @@
1
+ import type { ESTree } from '@oxlint/plugins';
2
+ import type { ModuleBoundaryOptions } from '../../types/module-policy.types.ts';
3
+ /** Globals that only exist on the server runtime. */
4
+ export declare const runtimeServerGlobals: string[];
5
+ /** Globals that only exist in the browser. */
6
+ export declare const runtimeBrowserGlobals: string[];
7
+ /** Whether `source` is one of the configured module prefixes or a subpath of one. */
8
+ export declare function runtimeMatchesImport(values: string[] | undefined, source: string): boolean;
9
+ /** Whether an import reaches a configured backend root or a backend package's server/RPC entry. */
10
+ export declare function runtimeIsBackendImport(options: Pick<ModuleBoundaryOptions, 'backend' | 'backendPackages'>, target: string, source: string): boolean;
11
+ /** Whether an import is a server platform module: Node built-ins, Bun, Cloudflare, oRPC server or React DOM server. */
12
+ export declare function runtimeIsServerPlatformImport(source: string): boolean;
13
+ /** The static name of a member access: `a.b` and `a['b']` give `b`; anything else gives `null`. */
14
+ export declare function runtimeMemberName(node: ESTree.Node | null | undefined): string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-arch",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "description": "Configurable architecture rules for Oxlint JavaScript plugins.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -10,6 +10,14 @@
10
10
  "types": "./dist/presets/index.d.ts",
11
11
  "default": "./dist/presets/index.js"
12
12
  },
13
+ "./presets/react-modules-preset": {
14
+ "types": "./dist/presets/react-modules-preset.d.ts",
15
+ "default": "./dist/presets/react-modules-preset.js"
16
+ },
17
+ "./presets/next-react-modules-preset": {
18
+ "types": "./dist/presets/next-react-modules-preset.d.ts",
19
+ "default": "./dist/presets/next-react-modules-preset.js"
20
+ },
13
21
  "./presets/tanstack-start-react-modules-preset": {
14
22
  "types": "./dist/presets/tanstack-start-react-modules-preset.d.ts",
15
23
  "default": "./dist/presets/tanstack-start-react-modules-preset.js"
@@ -30,12 +38,12 @@
30
38
  "dist"
31
39
  ],
32
40
  "scripts": {
33
- "build": "bun build src/index.ts src/presets/index.ts src/presets/tanstack-start-react-modules-preset.ts src/rules/dillon-anti-slop/index.ts src/rules/module-policy/index.ts --root src --outdir dist --target node --format esm --packages external && tsc --project tsconfig.build.json",
41
+ "build": "bun build src/index.ts src/presets/index.ts src/presets/react-modules-preset.ts src/presets/next-react-modules-preset.ts src/presets/tanstack-start-react-modules-preset.ts src/rules/dillon-anti-slop/index.ts src/rules/module-policy/index.ts --root src --outdir dist --target node --format esm --packages external && tsc --project tsconfig.build.json",
34
42
  "check": "bun run lint && bun run build && bun run typecheck && bun run test && bun run check:examples",
35
43
  "lint": "oxlint src examples scripts",
36
44
  "test": "vitest run src/tests",
37
- "typecheck": "tsc --noEmit",
38
- "check:examples": "oxlint examples/minimal.oxlint.config.ts -c examples/minimal.oxlint.config.ts --disable-nested-config && oxlint examples/full.oxlint.config.ts -c examples/full.oxlint.config.ts --disable-nested-config && oxlint examples/dist.oxlint.config.ts -c examples/dist.oxlint.config.ts --disable-nested-config && oxlint examples/monorepo.oxlint.config.ts -c examples/monorepo.oxlint.config.ts --disable-nested-config && oxlint --print-config -c examples/preset.oxlint.config.ts > /dev/null",
45
+ "typecheck": "tsrs --noEmit",
46
+ "check:examples": "oxlint examples/minimal.oxlint.config.ts -c examples/minimal.oxlint.config.ts --disable-nested-config && oxlint examples/full.oxlint.config.ts -c examples/full.oxlint.config.ts --disable-nested-config && oxlint examples/dist.oxlint.config.ts -c examples/dist.oxlint.config.ts --disable-nested-config && oxlint examples/monorepo.oxlint.config.ts -c examples/monorepo.oxlint.config.ts --disable-nested-config && oxlint --print-config -c examples/preset.oxlint.config.ts > /dev/null && oxlint --print-config -c examples/mixed-frameworks.oxlint.config.ts > /dev/null",
39
47
  "prepublishOnly": "bun run check",
40
48
  "release": "bash scripts/release.sh",
41
49
  "release:patch": "bash scripts/release.sh patch --yes",
@@ -44,36 +52,37 @@
44
52
  },
45
53
  "dependencies": {
46
54
  "@eslint-community/eslint-plugin-eslint-comments": "4.8.1",
47
- "@oxlint/plugins": "1.85.0",
55
+ "@oxlint/plugins": "1.89.0",
48
56
  "@regru/eslint-plugin-prefer-early-return": "1.0.0",
49
- "@shadcn/lint": "0.1.5",
57
+ "@shadcn/lint": "0.2.3",
50
58
  "eslint-plugin-import": "2.32.0",
51
- "eslint-plugin-perfectionist": "5.12.1",
59
+ "eslint-plugin-perfectionist": "5.13.0",
52
60
  "eslint-plugin-react": "7.37.5",
53
61
  "eslint-plugin-react-you-might-not-need-an-effect": "1.0.2",
54
62
  "eslint-plugin-svg-jsx": "1.3.0",
55
- "eslint-plugin-tsdoc": "0.5.3",
63
+ "eslint-plugin-tsdoc": "0.5.4",
56
64
  "eslint-plugin-unused-imports": "4.4.1",
57
- "oxlint-plugin-eslint": "1.85.0",
58
- "oxlint-tailwindcss": "1.14.0"
65
+ "oxlint-plugin-eslint": "1.89.0",
66
+ "oxlint-tailwindcss": "1.16.0"
59
67
  },
60
68
  "peerDependencies": {
61
69
  "oxlint": ">=1.85.0",
62
70
  "oxlint-tsgolint": ">=7.0.2003"
63
71
  },
64
72
  "devDependencies": {
65
- "@orpc/server": "1.15.4",
66
- "@tanstack/react-router": "1.170.41",
67
- "@types/node": "26.0.1",
73
+ "@orpc/server": "1.16.1",
74
+ "@tanstack/react-router": "1.170.45",
75
+ "@types/node": "26.6.5",
68
76
  "@types/react": "19.3.0",
69
- "bumpp": "10.4.1",
70
- "oxlint": "1.85.0",
71
- "oxlint-tsgolint": "7.0.2003",
72
- "react": "19.3.0",
77
+ "bumpp": "12.3.0",
78
+ "oxlint": "1.89.0",
79
+ "oxlint-tsgolint": "7.0.2004",
80
+ "react": "19.3.1",
73
81
  "tailwindcss": "4.3.3",
74
- "typescript": "6.0.3",
75
- "vitest": "4.1.9",
76
- "zod": "4.6.5"
82
+ "tsrs": "0.18.0-ts7.1.0-dev.20260929",
83
+ "typescript": "7.0.2",
84
+ "vitest": "5.0.5",
85
+ "zod": "4.6.7"
77
86
  },
78
87
  "packageManager": "bun@1.3.14",
79
88
  "license": "MIT",