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.
- package/CHANGELOG.md +31 -0
- package/README.md +2 -2
- package/dist/index.js +707 -201
- package/dist/preset/preset.constants.d.ts +4 -0
- package/dist/preset/preset.js +49 -17
- package/dist/preset/preset.utils.d.ts +1 -0
- package/dist/rules/layout/layout-type-arguments.d.ts +1 -0
- package/dist/types/boundaries.types.d.ts +26 -1
- package/dist/types/code.types.d.ts +1 -0
- package/dist/types/comments.types.d.ts +1 -0
- package/dist/types/dillon-anti-slop.types.d.ts +6 -0
- package/dist/types/effect.types.d.ts +1 -0
- package/dist/types/layout.types.d.ts +16 -0
- package/dist/types/naming.types.d.ts +4 -0
- package/dist/types/react.types.d.ts +13 -0
- package/dist/types/types.types.d.ts +4 -0
- package/dist/utils/ast.utils.d.ts +1 -2
- package/dist/utils/boundaries.utils.d.ts +72 -4
- package/dist/utils/options.utils.d.ts +1 -0
- package/dist/vendor/dillon-anti-slop/index.js +37 -15
- package/package.json +1 -1
- package/src/vendor/dillon-anti-slop/UPSTREAM.md +3 -0
|
@@ -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[];
|
package/dist/preset/preset.js
CHANGED
|
@@ -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
|
|
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", {
|
|
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"), {
|
|
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
|
|
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
|
-
|
|
2544
|
-
|
|
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) ??
|
|
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[];
|
|
@@ -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
|
+
}
|
|
@@ -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;
|
|
@@ -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
|
+
}
|
|
@@ -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
|
|
79
|
-
|
|
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)
|
|
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; "
|
|
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
|
-
|
|
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 `
|
|
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(
|
|
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({
|
|
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
|
@@ -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.
|