oxlint-plugin-arch 0.6.3 → 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.
@@ -18,6 +18,12 @@ export declare const presetTailwindRules: {
18
18
  export declare const presetImportRules: PresetRules;
19
19
  export declare const presetNamingRules: PresetRules;
20
20
  export declare const presetTestRules: PresetRules;
21
+ /** HTTP methods a Next `route.ts` handler may export. */
22
+ export declare const presetNextHandlerMethods: string[];
23
+ /** Next route segment config exports, shared by pages, layouts and route handlers. */
24
+ export declare const presetNextSegmentConfig: string[];
25
+ /** Exports a Next page or layout may add next to its default component. */
26
+ export declare const presetNextSegmentExports: string[];
21
27
  export declare const presetExportPrefix: {
22
28
  stem: string;
23
29
  trailingRoles: string[];
package/dist/index.js CHANGED
@@ -894,6 +894,49 @@ function routesInspectFunction(context, item, bannedHooks, banIntrinsicJsx) {
894
894
  }
895
895
  });
896
896
  }
897
+ // src/utils/helpers/runtime.ts
898
+ import { builtinModules } from "node:module";
899
+ var runtimeServerGlobals = ["process", "Bun", "Deno", "Buffer", "__dirname", "__filename", "require"];
900
+ var runtimeBrowserGlobals = [
901
+ "window",
902
+ "document",
903
+ "navigator",
904
+ "localStorage",
905
+ "sessionStorage",
906
+ "location",
907
+ "history",
908
+ "screen",
909
+ "HTMLElement",
910
+ "Element",
911
+ "HTMLInputElement",
912
+ "ResizeObserver",
913
+ "MutationObserver",
914
+ "IntersectionObserver",
915
+ "FileReader",
916
+ "matchMedia",
917
+ "getComputedStyle",
918
+ "requestAnimationFrame",
919
+ "cancelAnimationFrame",
920
+ "alert",
921
+ "confirm",
922
+ "prompt"
923
+ ];
924
+ function runtimeMatchesImport(values, source) {
925
+ return Boolean(values?.some((value) => source === value || source.startsWith(value + "/")));
926
+ }
927
+ function runtimeIsBackendImport(options, target, source) {
928
+ return options.backend.some((path) => target.startsWith(path + "/")) || options.backendPackages.some((name) => source === name || source.startsWith(name + "/") && /\/(?:server|rpc)(?:\/|$)/.test(source.slice(name.length)));
929
+ }
930
+ function runtimeIsServerPlatformImport(source) {
931
+ return builtinModules.includes(source) || source === "@orpc/server" || /^(?:node:|bun(?::|$)|cloudflare:)/.test(source) || /^react-dom\/server(?:\.|\/|$)/.test(source);
932
+ }
933
+ function runtimeMemberName(node) {
934
+ if (node?.type !== "MemberExpression")
935
+ return null;
936
+ if (!node.computed && node.property.type === "Identifier")
937
+ return node.property.name;
938
+ return node.computed && node.property.type === "Literal" && typeof node.property.value === "string" ? node.property.value : null;
939
+ }
897
940
  // src/utils/helpers/sanitizers.ts
898
941
  function sanitizersCallsConfigured(node, names) {
899
942
  let found = false;
@@ -2378,8 +2421,10 @@ var noFileLevelHelpers = defineRule16({
2378
2421
  } = optionsFirst(context, {});
2379
2422
  const allowed = optionsOptionalPatterns(allowPattern);
2380
2423
  const hooks = optionsPatterns(hookPattern);
2424
+ const exported = exportsCollect(program);
2425
+ const allowedExport = (name) => exported.some((binding) => binding.localName === name && optionsPatternsTest(allowed, binding.exportedName));
2381
2426
  for (const candidate of declarationsFileLevelFunctionCandidates(program)) {
2382
- if (detectComponents && reactComponentsIsLike(program, candidate) || optionsPatternsTest(hooks, candidate.name) || optionsPatternsTest(allowed, candidate.name)) {
2427
+ if (detectComponents && reactComponentsIsLike(program, candidate) || optionsPatternsTest(hooks, candidate.name) || optionsPatternsTest(allowed, candidate.name) || allowedExport(candidate.name)) {
2383
2428
  continue;
2384
2429
  }
2385
2430
  context.report({
@@ -4271,6 +4316,7 @@ var onlyExportComponents = defineRule36({
4271
4316
  additionalProperties: false,
4272
4317
  properties: {
4273
4318
  matchFileName: { type: "boolean" },
4319
+ trailingRoles: { type: "array", items: { type: "string" } },
4274
4320
  allowTypeExports: { type: "boolean" },
4275
4321
  denyTypePattern: optionsPatternSchema
4276
4322
  }
@@ -4288,10 +4334,15 @@ var onlyExportComponents = defineRule36({
4288
4334
  Program(program) {
4289
4335
  const {
4290
4336
  matchFileName = true,
4337
+ trailingRoles = [],
4291
4338
  allowTypeExports = true,
4292
4339
  denyTypePattern
4293
4340
  } = optionsFirst(context, {});
4294
- const expected = reactComponentsPascalFromBasename(namingFileBasename(context.filename));
4341
+ const basename = namingFileBasename(context.filename);
4342
+ const extension = basename.slice(basename.lastIndexOf("."));
4343
+ const role = trailingRoles.find((candidate) => basename.endsWith(`.${candidate}${extension}`));
4344
+ const expected = reactComponentsPascalFromBasename(role ? basename.replace(`.${role}${extension}`, extension) : basename);
4345
+ const expectedKeys = [expected, ...role ? [reactComponentsPascalFromBasename(basename)] : []].map((name) => name.toLowerCase());
4295
4346
  const denied = optionsOptionalPatterns(denyTypePattern);
4296
4347
  const components = [];
4297
4348
  for (const candidate of reactComponentsExportCandidates(program)) {
@@ -4324,10 +4375,9 @@ var onlyExportComponents = defineRule36({
4324
4375
  if (components.length === 0 || !matchFileName) {
4325
4376
  return;
4326
4377
  }
4327
- const expectedKey = expected.toLowerCase();
4328
- const exact = components.some((candidate) => candidate.name.toLowerCase() === expectedKey);
4329
- const allPrefixed = components.every((candidate) => candidate.name.toLowerCase().startsWith(expectedKey));
4330
- if (!exact && !allPrefixed) {
4378
+ const names = components.map((candidate) => candidate.name.toLowerCase());
4379
+ const matches = expectedKeys.some((expectedKey) => names.includes(expectedKey) || names.every((name) => name.startsWith(expectedKey)));
4380
+ if (!matches) {
4331
4381
  for (const candidate of components) {
4332
4382
  context.report({
4333
4383
  node: candidate.node,
@@ -4994,13 +5044,21 @@ var routeSurface = defineRule44({
4994
5044
  additionalProperties: false,
4995
5045
  properties: {
4996
5046
  exportName: { type: "string" },
5047
+ allowedExports: { type: "array", items: { type: "string" } },
5048
+ inspectExports: { type: "boolean" },
4997
5049
  bannedHooks: { type: "array", items: { type: "string" } },
4998
5050
  banIntrinsicJsx: { type: "boolean" }
4999
5051
  }
5000
5052
  }
5001
5053
  ],
5002
5054
  defaultOptions: [
5003
- { exportName: "Route", bannedHooks: ["useState", "useEffect", "useMutation"], banIntrinsicJsx: true }
5055
+ {
5056
+ exportName: "Route",
5057
+ allowedExports: [],
5058
+ inspectExports: false,
5059
+ bannedHooks: ["useState", "useEffect", "useMutation"],
5060
+ banIntrinsicJsx: true
5061
+ }
5004
5062
  ],
5005
5063
  messages: {
5006
5064
  extraExport: "Route files may only export '{{expected}}'; found '{{actual}}'.",
@@ -5011,20 +5069,34 @@ var routeSurface = defineRule44({
5011
5069
  createOnce(context) {
5012
5070
  return {
5013
5071
  Program(program) {
5014
- const { exportName, bannedHooks, banIntrinsicJsx } = optionsFirst(context);
5015
- for (const binding of exportsCollect(program)) {
5016
- if (binding.exportedName !== exportName) {
5072
+ const { exportName, allowedExports = [], inspectExports = false, bannedHooks, banIntrinsicJsx } = optionsFirst(context);
5073
+ const allowed = new Set([exportName, ...allowedExports]);
5074
+ const bindings = exportsCollect(program);
5075
+ for (const binding of bindings) {
5076
+ if (!allowed.has(binding.exportedName)) {
5017
5077
  context.report({
5018
5078
  node: binding.node,
5019
5079
  messageId: "extraExport",
5020
- data: { expected: exportName, actual: binding.exportedName }
5080
+ data: { expected: [...allowed].join("', '"), actual: binding.exportedName }
5021
5081
  });
5022
5082
  }
5023
5083
  }
5024
5084
  const banned = new Set(bannedHooks);
5085
+ const inspected = new Set;
5025
5086
  for (const item of declarationsTopLevelUnexportedFunctions(program)) {
5087
+ inspected.add(item.node.start);
5026
5088
  routesInspectFunction(context, item, banned, banIntrinsicJsx);
5027
5089
  }
5090
+ if (!inspectExports)
5091
+ return;
5092
+ const names = new Set(bindings.filter((binding) => binding.exportedName === exportName).map((binding) => binding.localName ?? binding.exportedName));
5093
+ const defaultExport = program.body.find((statement) => statement.type === "ExportDefaultDeclaration");
5094
+ const declaration = defaultExport?.declaration;
5095
+ const defaultFunctions = declaration?.type === "ArrowFunctionExpression" || declaration?.type === "FunctionExpression" ? [{ name: "default", node: declaration }] : [];
5096
+ for (const item of [...exportsCollectFunctions(program), ...defaultFunctions]) {
5097
+ if (names.has(item.name) && !inspected.has(item.node.start))
5098
+ routesInspectFunction(context, item, banned, banIntrinsicJsx);
5099
+ }
5028
5100
  }
5029
5101
  };
5030
5102
  }
@@ -1,2 +1,4 @@
1
+ export { default as reactModulesPreset } from './react-modules-preset.ts';
2
+ export { default as nextReactModulesPreset } from './next-react-modules-preset.ts';
1
3
  export { default as tanstackStartReactModulesPreset } from './tanstack-start-react-modules-preset.ts';
2
- export type { PresetOptions, PresetArchitecture, PresetArchitectureEntry, PresetFolderLayout, PresetTanstackStartOptions, PresetLimitsOptions, PresetModulesOptions, PresetImportsOptions, PresetOrpcOptions, PresetSqlOptions, PresetFormsOptions, PresetReactOptions, PresetTestsOptions, PresetPolicyOptions, PresetPolicy, PresetPolicyName, PresetTailwindOptions, PresetShadcnOptions, } from '../types/preset.types.ts';
4
+ export type { PresetOptions, PresetArchitecture, PresetArchitectureEntry, PresetFolderLayout, PresetFormattingOptions, PresetFramework, PresetNextOptions, PresetTanstackStartOptions, PresetLimitsOptions, PresetModulesOptions, PresetImportsOptions, PresetOrpcOptions, PresetSqlOptions, PresetFormsOptions, PresetReactOptions, PresetTestsOptions, PresetPolicyOptions, PresetPolicy, PresetPolicyName, PresetTailwindOptions, PresetShadcnOptions, } from '../types/preset.types.ts';