oxlint-plugin-arch 0.13.0 → 0.13.1

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.
@@ -73,6 +73,10 @@ export declare const presetEffectImports: {
73
73
  'ts-pattern': string;
74
74
  dayjs: string;
75
75
  };
76
+ export declare const presetEffectServerImports: {
77
+ ai: string;
78
+ '@ai-sdk/**': string;
79
+ };
76
80
  export declare const presetRecipeMark: unique symbol;
77
81
  export declare const presetDetectBackend: string[];
78
82
  export declare const presetDetectSkip: string[];
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
4
  // src/preset/preset.ts
5
- import { globSync } from "node:fs";
5
+ import { globSync as globSync2 } from "node:fs";
6
6
  import { createRequire as createRequire3 } from "node:module";
7
7
  import { matchesGlob as matchesGlob2, posix as posix9 } from "node:path";
8
8
 
@@ -511,6 +511,10 @@ var presetEffectImports = {
511
511
  "ts-pattern": "Use effect/Match.",
512
512
  dayjs: "Use effect/DateTime."
513
513
  };
514
+ var presetEffectServerImports = {
515
+ ai: "Use effect/ai.",
516
+ "@ai-sdk/**": "Use effect/ai."
517
+ };
514
518
  var presetRecipeMark = Symbol("recipe");
515
519
  var presetDetectBackend = ["@orpc/server", "hono", "kysely", "drizzle-orm", "express", "fastify", "elysia"];
516
520
  var presetDetectSkip = ["node_modules", "dist", "build", "coverage", "public", ".output", ".next", ".vinext", ".turbo", ".wrangler", "target"];
@@ -588,7 +592,7 @@ var presetTemplates = {
588
592
  };
589
593
 
590
594
  // src/preset/preset.utils.ts
591
- import { existsSync, readFileSync, statSync } from "node:fs";
595
+ import { existsSync, globSync, readFileSync, statSync } from "node:fs";
592
596
  import { createRequire as createRequire2 } from "node:module";
593
597
  import { posix, resolve } from "node:path";
594
598
  class PresetError extends Error {
@@ -703,6 +707,23 @@ function presetIntersect(glob, scope) {
703
707
  function presetGlobEscape(path) {
704
708
  return path.replaceAll(/[[\]]/g, (bracket) => `[${bracket}]`);
705
709
  }
710
+ function presetFileOverlaps(root, build, keys) {
711
+ const matched = new Map;
712
+ for (const key of keys) {
713
+ for (const path of globSync(key.pattern, { cwd: root, exclude: (name) => posix.basename(name) === "node_modules" })) {
714
+ matched.set(path, [...matched.get(path) ?? [], key]);
715
+ }
716
+ }
717
+ const reach = new Map(keys.map((key) => [key, [...matched.values()].filter((found) => found.includes(key)).length]));
718
+ const merged = [...matched].flatMap(([path, found]) => {
719
+ if (found.length < 2) {
720
+ return [];
721
+ }
722
+ const ordered = found.toSorted((left, right) => (reach.get(right) ?? 0) - (reach.get(left) ?? 0));
723
+ return [build(presetGlobEscape(path), ordered.reduce((folder, key) => Object(presetMerge(folder, key.folder)), {}))];
724
+ });
725
+ return [...keys, ...merged];
726
+ }
706
727
  function presetAliases(root, aliases) {
707
728
  return Object.fromEntries(Object.entries(aliases ?? {}).map(([alias, path]) => [alias.replace(/\/$/, ""), resolve(root, path)]));
708
729
  }
@@ -848,7 +869,7 @@ function presetEffectServerKeys(at) {
848
869
  rules: {
849
870
  "arch/types-declarations": ["error", { modules: true }],
850
871
  "arch/types-inline": ["error", { variables: true, assertions: true, arguments: true }],
851
- "arch/comments-code": ["error", { why: false, jsdoc: true, allow: ["^\\s*SAFETY: "] }]
872
+ "arch/comments-code": ["error", { why: false, jsdoc: true, allow: ["^\\s*SAFETY: "], steps: true }]
852
873
  }
853
874
  },
854
875
  [at("domains")]: {
@@ -884,8 +905,8 @@ function presetEffectServerKeys(at) {
884
905
  rules: { "arch/files-exports": ["error", { stem: "folder", patterns: ["{domain}Query[A-Z]\\w*"], types: true }] }
885
906
  },
886
907
  [at("domains/*/*.constants.ts")]: { rules: { "arch/files-no-functions": ["error", { reexports: false }] } },
887
- [at("domains/*/*.layer.ts")]: { rules: { "arch/effect-layer-file": "error" } },
888
- [at("domains/*/*.driver.ts")]: { rules: { "arch/effect-layer-file": "error" } },
908
+ [at("domains/*/*.layer.ts")]: { rules: { "arch/effect-layer-file": ["error", { suffix: "Layer" }] } },
909
+ [at("domains/*/*.driver.ts")]: { rules: { "arch/effect-layer-file": ["error", { suffix: "Layer" }] } },
889
910
  [at("domains/*/*.workflows.ts")]: {
890
911
  deny: {
891
912
  calls: presetEffectWorkflowCalls,
@@ -956,9 +977,10 @@ function presetEffectUnit(root, key, value) {
956
977
  const keys = server || contract ? presetFilled(server ? presetEffectServerKeys(at) : presetEffectContractKeys(at), presetEffectExists(root, key)) : {};
957
978
  const errors = Object.fromEntries(presetEffectErrors.map((name) => [name, "Tagged errors live in <d>.errors.ts."]));
958
979
  const placement = server || contract ? { types: { placement: false } } : {};
980
+ const imports = server || contract ? { ...presetEffectImports, ...presetEffectServerImports } : presetEffectImports;
959
981
  return {
960
982
  ...keys,
961
- ".": presetMerge({ ...placement, deny: { imports: presetEffectImports, calls: errors } }, keys["."] ?? {}),
983
+ ".": presetMerge({ ...placement, deny: { imports, calls: errors } }, keys["."] ?? {}),
962
984
  [at("**/*.errors.ts")]: { deny: { calls: Object.fromEntries(presetEffectErrors.map((name) => [name, false])) } }
963
985
  };
964
986
  }
@@ -1500,17 +1522,19 @@ var effectGroup = (project, settings) => {
1500
1522
  "arch/code-switch": ["error", { message: "Branch with Match." }],
1501
1523
  "arch/naming-exports": "off",
1502
1524
  "arch/code-function-nesting": ["error", { max: 4 }],
1503
- "arch/code-inline": ["error", { max, nesting: 2, keep: ["^[A-Z]", "^layer"], allow: "^(?:decode|encode)[A-Z]", services: ["of"] }],
1525
+ "arch/code-inline": ["error", { max, nesting: 2, callbacks: true, keep: ["^[A-Z]", "^layer"], allow: "^(?:decode|encode)[A-Z]", services: ["of"] }],
1504
1526
  "arch/code-yield-argument": "error",
1505
1527
  "arch/effect-layers": "error",
1506
1528
  "arch/effect-merge-pipe": "error",
1507
1529
  "arch/effect-values-first": "error",
1508
1530
  "arch/layout-arrow-body": ["error", { max }],
1509
- "arch/layout-call-arguments": ["error", { split: false, hug: { max }, join: { max, nesting: 2, keep: ["Layer.mergeAll"] } }],
1510
- "arch/layout-call-depth": ["error", { max: 2 }],
1531
+ "arch/layout-call-arguments": ["error", { split: false, pipe: { min: 2 }, hug: { max }, join: { max, nesting: 2, keep: ["Layer.mergeAll"] } }],
1532
+ "arch/layout-call-depth": ["error", { max: 2, callbacks: true }],
1511
1533
  "arch/layout-items": "error",
1534
+ "arch/layout-object": ["error", { min: 3, scope: "call-args", params: true, nested: true, siblings: true, inner: 2 }],
1512
1535
  "arch/layout-pipe": "error",
1513
- "arch/layout-ternary": ["error", { fit: false, branch: { max: 3 } }]
1536
+ "arch/layout-ternary": ["error", { max: 100, branch: { max: 3 } }],
1537
+ "arch/layout-type-arguments": ["error", { min: 3 }]
1514
1538
  }),
1515
1539
  presetOverride(files, {
1516
1540
  "arch/code-justified-call": [
@@ -1978,7 +2002,10 @@ var shadcnGroup = (project) => {
1978
2002
 
1979
2003
  // src/preset/groups/sql.ts
1980
2004
  var sqlGroup = (project) => [
1981
- presetOverride(presetServiceScopes(project.serviceFolders, project.serviceRoots, "**/*.ts"), { "arch/backend-sql-like": "error" })
2005
+ presetOverride(presetServiceScopes(project.serviceFolders, project.serviceRoots, "**/*.ts"), {
2006
+ "arch/backend-sql-like": "error",
2007
+ "arch/layout-chain": ["error", { groups: [{ min: 3, methods: ["selectFrom", "insertInto", "updateTable", "deleteFrom", "mergeInto", "with"] }] }]
2008
+ })
1982
2009
  ];
1983
2010
 
1984
2011
  // src/preset/groups/style.ts
@@ -2079,6 +2106,9 @@ function optionsAssertMockingApis(patterns) {
2079
2106
  throw new OptionsError("Test mocking APIs must be `*` or `<vi|jest|*>.<method|*>`.");
2080
2107
  }
2081
2108
  }
2109
+ function optionsEscape(value) {
2110
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
2111
+ }
2082
2112
 
2083
2113
  // src/preset/groups/tests.ts
2084
2114
  var testsGroup = (project, settings) => {
@@ -2528,7 +2558,11 @@ function presetProject(config) {
2528
2558
  if (dead) {
2529
2559
  throw new PresetError(`Folder \`${dead.key}\` sits inside the skipped folder \`${dead.parent?.key}\`.`);
2530
2560
  }
2531
- const fileKeys = fileEntries.flatMap(([key, value]) => {
2561
+ const keyFor = (pattern, folder) => {
2562
+ const reached = live.filter((scope) => presetIntersect(pattern, scope.key).length > 0).map((scope) => ({ key: scope.key, parent: scope, depth: scope.depth, folder, effective: presetEffective(folder, scope.effective) }));
2563
+ return { pattern, folder, scopes: reached };
2564
+ };
2565
+ const fileKeys = presetFileOverlaps(root, keyFor, fileEntries.flatMap(([key, value]) => {
2532
2566
  if (value === false) {
2533
2567
  return [];
2534
2568
  }
@@ -2540,10 +2574,8 @@ function presetProject(config) {
2540
2574
  if (Object(folder.layout).operations !== undefined) {
2541
2575
  throw new PresetError(`Set layout.operations on an app or package folder, not on \`${key}\`.`);
2542
2576
  }
2543
- const pattern = presetGlobEscape(key);
2544
- const reached = live.filter((scope) => presetIntersect(pattern, scope.key).length > 0).map((scope) => ({ key: scope.key, parent: scope, depth: scope.depth, folder, effective: presetEffective(folder, scope.effective) }));
2545
- return [{ pattern, folder, scopes: reached }];
2546
- });
2577
+ return [keyFor(presetGlobEscape(key), folder)];
2578
+ }));
2547
2579
  const units = presetUnits(root, scopes, ignored);
2548
2580
  const unitKeys = new Set(units.map((unit) => unit.scope.key));
2549
2581
  const misplaced = live.find((scope) => !unitKeys.has(scope.key) && Object(scope.folder.layout).operations !== undefined);
@@ -2845,7 +2877,7 @@ function preset(config) {
2845
2877
  const level = presetLevel(root);
2846
2878
  const expanded = new Map;
2847
2879
  const expand = (fileKey) => {
2848
- const paths = expanded.get(fileKey.pattern) ?? globSync(fileKey.pattern, { cwd: project.root, exclude: (path) => posix9.basename(path) === "node_modules" });
2880
+ const paths = expanded.get(fileKey.pattern) ?? globSync2(fileKey.pattern, { cwd: project.root, exclude: (path) => posix9.basename(path) === "node_modules" });
2849
2881
  expanded.set(fileKey.pattern, paths);
2850
2882
  return paths;
2851
2883
  };
@@ -11,6 +11,7 @@ export declare function presetSeverity(rules: PresetTypes.PresetRules, level: Pr
11
11
  export declare function presetPackageName(root: string, directory: string): string | undefined;
12
12
  export declare function presetIntersect(glob: string, scope: string): string[];
13
13
  export declare function presetGlobEscape(path: string): string;
14
+ export declare function presetFileOverlaps(root: string, build: (pattern: string, folder: PresetTypes.PresetFolder) => PresetTypes.PresetFileKey, keys: PresetTypes.PresetFileKey[]): PresetTypes.PresetFileKey[];
14
15
  export declare function presetAliases(root: string, aliases: Record<string, string> | undefined): Record<string, string>;
15
16
  export declare function presetRuntime(project: PresetTypes.PresetProject, settings: Required<PresetTypes.PresetFrameworkOptions>, framework: PresetTypes.PresetFramework, rule: string): OxlintOverride[];
16
17
  export declare function presetManifest(root: string, directory: string): PresetTypes.PresetManifest;
@@ -0,0 +1 @@
1
+ export declare const layoutTypeArguments: import("@oxlint/plugins").Rule;
@@ -6,13 +6,27 @@ export interface BoundariesOptions {
6
6
  roles?: string[];
7
7
  surfaces?: string[];
8
8
  services?: string[];
9
- portable?: boolean;
9
+ portable?: boolean | string;
10
+ conventions?: BoundariesConventions;
10
11
  backendPackages: string[];
11
12
  appPackages: string[];
12
13
  aliases: Record<string, string>;
13
14
  entrypoints: string[];
14
15
  uiPackages?: string[];
15
16
  }
17
+ export interface BoundariesConventions {
18
+ services?: string;
19
+ registry?: string;
20
+ types?: string;
21
+ lib?: string;
22
+ contracts?: string[];
23
+ implementations?: string[];
24
+ implementationFolders?: string[];
25
+ operations?: string[];
26
+ operationFolders?: string[];
27
+ domainFiles?: string[];
28
+ clients?: string[];
29
+ }
16
30
  export interface BoundariesImport {
17
31
  options: BoundariesOptions;
18
32
  file: string;
@@ -25,7 +39,18 @@ export interface BoundariesImport {
25
39
  web?: string;
26
40
  backendImport: boolean;
27
41
  }
42
+ export interface BoundariesRuntimeConventions {
43
+ server?: string[];
44
+ rsc?: string[];
45
+ actions?: string[];
46
+ client?: string[];
47
+ clientOnly?: string[];
48
+ hooks?: string[];
49
+ shared?: string[];
50
+ lib?: string;
51
+ }
28
52
  export interface BoundariesNextOptions extends Partial<Pick<BoundariesOptions, 'web' | 'backend' | 'aliases' | 'backendPackages'>> {
53
+ conventions?: BoundariesRuntimeConventions;
29
54
  server?: string[];
30
55
  client?: string[];
31
56
  dynamic?: string[];
@@ -69,6 +69,7 @@ export interface CodeTrivialFunctionsGuardState {
69
69
  export interface CodeInlineOptions {
70
70
  max?: number;
71
71
  nesting?: number;
72
+ callbacks?: boolean;
72
73
  allow?: OptionsPattern;
73
74
  keep?: OptionsPattern;
74
75
  services?: string[];
@@ -4,6 +4,7 @@ export interface CommentsOptions {
4
4
  allow?: string[];
5
5
  jsdoc?: boolean;
6
6
  members?: 'off' | 'also' | 'only';
7
+ steps?: boolean;
7
8
  }
8
9
  export interface CommentsLines {
9
10
  starts: number[];
@@ -53,3 +53,9 @@ export interface DillonAntiSlopConstBinding {
53
53
  readonly declarator: ESTree.VariableDeclarator;
54
54
  readonly init: ESTree.Expression;
55
55
  }
56
+ export interface DillonAntiSlopSymbolNamesOptions {
57
+ terms?: string[];
58
+ }
59
+ export interface DillonAntiSlopSafetyCommentOptions {
60
+ comment?: string;
61
+ }
@@ -22,6 +22,7 @@ export interface EffectLayerFileOptions {
22
22
  layer?: string;
23
23
  make?: string;
24
24
  prefix?: string;
25
+ suffix?: string;
25
26
  builders?: string[];
26
27
  layers?: OptionsPattern;
27
28
  services?: OptionsPattern;
@@ -9,6 +9,10 @@ export interface LayoutCallArgumentsOptions {
9
9
  min?: number;
10
10
  indent?: SourceIndent;
11
11
  split?: boolean;
12
+ pipe?: {
13
+ min: number;
14
+ methods?: string[];
15
+ };
12
16
  hug?: {
13
17
  max: number;
14
18
  };
@@ -55,6 +59,10 @@ export interface LayoutObjectOptions {
55
59
  min?: number;
56
60
  scope?: LayoutObjectScope;
57
61
  indent?: SourceIndent;
62
+ params?: boolean;
63
+ nested?: boolean;
64
+ siblings?: boolean;
65
+ inner?: number;
58
66
  join?: {
59
67
  max: number;
60
68
  keep?: string[];
@@ -66,6 +74,13 @@ export interface LayoutPaddingOptions {
66
74
  multiline?: boolean;
67
75
  }
68
76
  export type LayoutPaddingKind = 'variable' | 'block' | 'control' | 'return' | 'other';
77
+ export interface LayoutPipeOptions {
78
+ methods?: string[];
79
+ }
80
+ export interface LayoutTypeArgumentsOptions {
81
+ min?: number;
82
+ indent?: SourceIndent;
83
+ }
69
84
  export interface LayoutTernaryOptions {
70
85
  max?: number;
71
86
  indent?: SourceIndent;
@@ -79,6 +94,7 @@ export interface LayoutItemsOptions {
79
94
  }
80
95
  export interface LayoutCallDepthOptions {
81
96
  max?: number;
97
+ callbacks?: boolean;
82
98
  }
83
99
  export interface LayoutArrowBodyOptions {
84
100
  max?: number;
@@ -49,4 +49,8 @@ export interface NamingFolderOptions {
49
49
  prefixes?: string[];
50
50
  after?: string;
51
51
  }
52
+ export interface NamingTypesOptions {
53
+ pattern?: OptionsPattern;
54
+ message?: string;
55
+ }
52
56
  export {};
@@ -17,3 +17,16 @@ export interface ReactPropsTarget {
17
17
  fn: AstFunction;
18
18
  typeArgument?: ESTree.TSType;
19
19
  }
20
+ export interface ReactDynamicClassesOptions {
21
+ attributes?: string[];
22
+ message?: string;
23
+ }
24
+ export interface ReactEmptyEffectOptions {
25
+ hooks?: string[];
26
+ message?: string;
27
+ }
28
+ export interface ReactMemoOptions {
29
+ names?: string[];
30
+ sources?: string[];
31
+ message?: string;
32
+ }
@@ -30,3 +30,7 @@ export interface TypesDuplicateOptions {
30
30
  structs?: string[];
31
31
  min?: number;
32
32
  }
33
+ export interface TypesLocalAliasOptions {
34
+ allow?: OptionsPattern;
35
+ message?: string;
36
+ }
@@ -20,6 +20,5 @@ export declare function astSelectorRule(selector: string, message: string): impo
20
20
  export declare function astIsOneLine(node: ESTree.Node): boolean;
21
21
  export declare function astIsInlineFunction(node: ESTree.Node): node is AstFunction;
22
22
  export declare function astIsCall(node: ESTree.Node): node is AstCall;
23
- export declare function astIsPipe(node: AstCall): boolean;
24
23
  export declare function astSome(value: unknown, test: (node: ESTree.Node) => boolean, enter?: (node: ESTree.Node) => boolean): boolean;
25
- export declare function astCallDepth(value: unknown): number;
24
+ export declare function astCallDepth(value: unknown, callbacks?: boolean): number;
@@ -1,5 +1,10 @@
1
- import type { BoundariesOptions } from '../types/boundaries.types.ts';
1
+ import type { BoundariesOptions, BoundariesRuntimeConventions } from '../types/boundaries.types.ts';
2
2
  import type * as PluginsTypes from '@oxlint/plugins';
3
+ export declare function boundariesConventionsFor(options: {
4
+ conventions?: BoundariesRuntimeConventions;
5
+ }): Required<BoundariesRuntimeConventions>;
6
+ export declare function boundariesHasSuffix(suffixes: readonly string[], path: string, optionalExtension?: boolean): boolean;
7
+ export declare function boundariesIsClientOnly(conventions: Required<BoundariesRuntimeConventions>, target: string): boolean;
3
8
  export declare const boundariesServerGlobals: string[];
4
9
  export declare const boundariesBrowserGlobals: string[];
5
10
  export declare const boundariesRuntimeProperties: {
@@ -63,6 +68,65 @@ export declare const boundariesRuntimeProperties: {
63
68
  type: string;
64
69
  };
65
70
  };
71
+ conventions: {
72
+ type: string;
73
+ additionalProperties: boolean;
74
+ properties: {
75
+ server: {
76
+ type: string;
77
+ items: {
78
+ type: string;
79
+ minLength: number;
80
+ };
81
+ };
82
+ rsc: {
83
+ type: string;
84
+ items: {
85
+ type: string;
86
+ minLength: number;
87
+ };
88
+ };
89
+ actions: {
90
+ type: string;
91
+ items: {
92
+ type: string;
93
+ minLength: number;
94
+ };
95
+ };
96
+ client: {
97
+ type: string;
98
+ items: {
99
+ type: string;
100
+ minLength: number;
101
+ };
102
+ };
103
+ clientOnly: {
104
+ type: string;
105
+ items: {
106
+ type: string;
107
+ minLength: number;
108
+ };
109
+ };
110
+ hooks: {
111
+ type: string;
112
+ items: {
113
+ type: string;
114
+ minLength: number;
115
+ };
116
+ };
117
+ shared: {
118
+ type: string;
119
+ items: {
120
+ type: string;
121
+ minLength: number;
122
+ };
123
+ };
124
+ lib: {
125
+ type: string;
126
+ minLength: number;
127
+ };
128
+ };
129
+ };
66
130
  };
67
131
  export declare const boundariesServerPackages: string[];
68
132
  export declare const boundariesRuntimeMessages: {
@@ -75,11 +139,15 @@ export declare function boundariesExportVisitors(check: (node: PluginsTypes.ESTr
75
139
  };
76
140
  export declare function boundariesLiteralSource(node: PluginsTypes.ESTree.ImportExpression): string | undefined;
77
141
  export declare function boundariesReport(context: PluginsTypes.Context, node: PluginsTypes.ESTree.Node, source: string, reason: string): void;
78
- export declare function boundariesImportExpression(context: PluginsTypes.Context, node: PluginsTypes.ESTree.ImportExpression, allowComputed: boolean, check: (source: string) => void): void;
79
- export declare function boundariesIsSharedFile(options: Pick<BoundariesOptions, 'web'>, file: string): boolean;
142
+ export declare function boundariesRuntimeImportVisitors(context: PluginsTypes.Context, check: (node: PluginsTypes.ESTree.Node, source: string, typeOnly: boolean, reExport: boolean) => void, allowComputed: () => boolean): {
143
+ ExportNamedDeclaration(node: PluginsTypes.ESTree.ExportNamedDeclaration): void;
144
+ ExportAllDeclaration(node: PluginsTypes.ESTree.ExportAllDeclaration): void;
145
+ ImportExpression(node: PluginsTypes.ESTree.ImportExpression): void;
146
+ };
147
+ export declare function boundariesIsSharedFile(options: Pick<BoundariesOptions, 'web'>, conventions: Required<BoundariesRuntimeConventions>, file: string): boolean;
80
148
  export declare function boundariesDeclaredVariable(start: PluginsTypes.Scope | null, name: string): PluginsTypes.Variable | null;
81
149
  export declare function boundariesMatchesImport(prefixes: string[] | undefined, source: string): boolean;
82
150
  export declare function boundariesIsBackendImport(options: Pick<BoundariesOptions, 'backend' | 'backendPackages'>, target: string, source: string): boolean;
83
151
  export declare function boundariesIsServerPlatformImport(source: string, packages: readonly string[]): boolean;
84
152
  export declare function boundariesMemberName(node: PluginsTypes.ESTree.Node | null | undefined): string | null;
85
- export declare function boundariesResolveTarget(options: Pick<BoundariesOptions, 'web' | 'backend' | 'aliases'>, file: string, source: string): string;
153
+ export declare function boundariesResolveTarget(options: Pick<BoundariesOptions, 'web' | 'backend' | 'aliases'>, file: string, source: string, services?: string): string;
@@ -9,3 +9,4 @@ export declare function optionsPatternsTest(patterns: RegExp[], value: string):
9
9
  export declare function optionsGlob(pattern: string): RegExp;
10
10
  export declare function optionsPatternLabel(pattern: OptionsPattern | undefined): string;
11
11
  export declare function optionsAssertMockingApis(patterns: string[]): void;
12
+ export declare function optionsEscape(value: string): string;
@@ -152,6 +152,9 @@ function optionsAssertMockingApis(patterns) {
152
152
  throw new OptionsError("Test mocking APIs must be `*` or `<vi|jest|*>.<method|*>`.");
153
153
  }
154
154
  }
155
+ function optionsEscape(value) {
156
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
157
+ }
155
158
 
156
159
  // src/utils/ast.utils.ts
157
160
  var astSkippedKeys = new Set(["parent", "type", "start", "end", "range", "loc", "tokens", "comments"]);
@@ -339,9 +342,6 @@ function astIsInlineFunction(node) {
339
342
  function astIsCall(node) {
340
343
  return node.type === "CallExpression" || node.type === "NewExpression";
341
344
  }
342
- function astIsPipe(node) {
343
- return node.callee.type === "MemberExpression" && astMemberName(node.callee) === "pipe";
344
- }
345
345
  function astChildren(node) {
346
346
  return Object.entries(node).filter(([key]) => !astSkippedKeys.has(key)).map(([, child]) => child);
347
347
  }
@@ -351,17 +351,20 @@ function astSome(value, test, enter = () => true) {
351
351
  }
352
352
  return astIsNode(value) && (test(value) || enter(value) && astChildren(value).some((child) => astSome(child, test, enter)));
353
353
  }
354
- function astCallDepth(value) {
354
+ function astCallDepth(value, callbacks = false) {
355
355
  if (Array.isArray(value)) {
356
- return Math.max(0, ...value.map(astCallDepth));
356
+ return Math.max(0, ...value.map((item) => astCallDepth(item, callbacks)));
357
357
  }
358
- if (!astIsNode(value) || astIsInlineFunction(value)) {
358
+ if (!astIsNode(value)) {
359
359
  return 0;
360
360
  }
361
+ if (astIsInlineFunction(value)) {
362
+ return callbacks ? astCallDepth(value.body, callbacks) : 0;
363
+ }
361
364
  if (astIsCall(value)) {
362
- return Math.max(value.arguments.length === 0 ? 0 : 1 + astCallDepth(value.arguments), astCallDepth(value.callee));
365
+ return Math.max(value.arguments.length === 0 ? 0 : 1 + astCallDepth(value.arguments, callbacks), astCallDepth(value.callee, callbacks));
363
366
  }
364
- return astCallDepth(astChildren(value));
367
+ return astCallDepth(astChildren(value), callbacks);
365
368
  }
366
369
 
367
370
  // src/vendor/dillon-anti-slop/shared/type-reference-name.ts
@@ -1247,7 +1250,6 @@ var noRuntimeTypeofRule = defineRule8({
1247
1250
 
1248
1251
  // src/vendor/dillon-anti-slop/rules/no-shape-in-symbol-names.ts
1249
1252
  import { defineRule as defineRule9 } from "@oxlint/plugins";
1250
- var FORBIDDEN_SYMBOL_NAME = "shape";
1251
1253
  function namesForeignApi(node) {
1252
1254
  const parent = node.parent;
1253
1255
  if (!parent) {
@@ -1279,22 +1281,29 @@ var noForbiddenTermInSymbolNamesRule = defineRule9({
1279
1281
  docs: {
1280
1282
  description: 'Disallow the case-insensitive substring "shape" in JavaScript, TypeScript, private, and JSX symbol names.'
1281
1283
  },
1284
+ schema: [{ type: "object", additionalProperties: false, properties: { terms: { type: "array", items: { type: "string", minLength: 1 }, minItems: 1 } } }],
1282
1285
  messages: {
1283
- forbiddenSymbolName: 'Rename symbol "{{name}}" for its domain role; "shape" describes structure rather than ownership.'
1286
+ forbiddenSymbolName: 'Rename symbol "{{name}}" for its domain role; "{{term}}" describes structure rather than ownership.'
1284
1287
  }
1285
1288
  },
1286
1289
  createOnce(context) {
1290
+ let terms = [];
1287
1291
  const reportForbiddenSymbolName = (node) => {
1288
- if (!node.name.toLowerCase().includes(FORBIDDEN_SYMBOL_NAME) || namesForeignApi(node)) {
1292
+ const lower = node.name.toLowerCase();
1293
+ const term = terms.find((candidate) => lower.includes(candidate));
1294
+ if (term === undefined || namesForeignApi(node)) {
1289
1295
  return;
1290
1296
  }
1291
1297
  context.report({
1292
1298
  node,
1293
1299
  messageId: "forbiddenSymbolName",
1294
- data: { name: node.name }
1300
+ data: { name: node.name, term }
1295
1301
  });
1296
1302
  };
1297
1303
  return {
1304
+ before() {
1305
+ terms = (optionsFirst(context, {}).terms ?? ["shape"]).map((term) => term.toLowerCase());
1306
+ },
1298
1307
  Identifier: reportForbiddenSymbolName,
1299
1308
  PrivateIdentifier: reportForbiddenSymbolName,
1300
1309
  JSXIdentifier: reportForbiddenSymbolName
@@ -1750,18 +1759,22 @@ var requireSafetyCommentForTypeAssertionRule = defineRule15({
1750
1759
  docs: {
1751
1760
  description: "Require a nearby SAFETY comment for every TypeScript type assertion except const assertions."
1752
1761
  },
1762
+ schema: [{ type: "object", additionalProperties: false, properties: { comment: { type: "string", minLength: 1 } } }],
1753
1763
  messages: {
1754
- missingSafetyComment: "This type assertion has no `SAFETY:` justification. State the checked invariant immediately before the assertion or its containing statement."
1764
+ missingSafetyComment: "This type assertion has no `{{comment}}` justification. State the checked invariant immediately before the assertion or its containing statement."
1755
1765
  }
1756
1766
  },
1757
1767
  createOnce(context) {
1768
+ let comment = "SAFETY:";
1769
+ let marker = /(?<![\p{L}\p{N}_])SAFETY\s*:/u;
1758
1770
  const checkAssertion = (node) => {
1759
1771
  if (expressionsIsConstAssertion(node)) {
1760
1772
  return;
1761
1773
  }
1762
1774
  let current = node;
1775
+ const justified = (candidate) => candidate.end <= node.start && marker.test(candidate.value);
1763
1776
  for (;; ) {
1764
- if (context.sourceCode.getCommentsBefore(current).some((comment) => comment.end <= node.start && /\bSAFETY\s*:/u.test(comment.value))) {
1777
+ if (context.sourceCode.getCommentsBefore(current).some(justified)) {
1765
1778
  return;
1766
1779
  }
1767
1780
  const parent = current.parent;
@@ -1770,9 +1783,18 @@ var requireSafetyCommentForTypeAssertionRule = defineRule15({
1770
1783
  }
1771
1784
  current = parent;
1772
1785
  }
1773
- context.report({ node, messageId: "missingSafetyComment" });
1786
+ context.report({
1787
+ node,
1788
+ messageId: "missingSafetyComment",
1789
+ data: { comment }
1790
+ });
1774
1791
  };
1775
1792
  return {
1793
+ before() {
1794
+ comment = optionsFirst(context, {}).comment ?? "SAFETY:";
1795
+ const word = optionsEscape(comment.replace(/:\s*$/, ""));
1796
+ marker = new RegExp(`(?<![\\p{L}\\p{N}_])${word}\\s*:`, "u");
1797
+ },
1776
1798
  TSAsExpression: checkAssertion,
1777
1799
  TSTypeAssertion: checkAssertion
1778
1800
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-arch",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "description": "Configurable architecture rules for Oxlint JavaScript plugins.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,6 +14,9 @@ The TanStack preset selects the enabled rules; this directory implements them
14
14
  and is not a separate preset. Upstream intentionally distributes source for
15
15
  vendoring and does not publish an official npm package.
16
16
 
17
+ Local options: `terms` on `no-shape-in-symbol-names` and `comment` on
18
+ `require-safety-comment-for-type-assertion`; their defaults keep upstream behavior.
19
+
17
20
  Local behavior fixes recognize `SAFETY:` comments on exported declarations and
18
21
  export-default assertions, while retaining statement boundaries for other
19
22
  assertions.