ai-deslop 0.4.7 → 0.4.9

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/README.md CHANGED
@@ -72,6 +72,7 @@ All rules below are enabled by the recommended config unless listed as opt-in.
72
72
  | --- | --- |
73
73
  | [`no-duplicate-consts`](docs/no-duplicate-consts.md) | Reports all-caps constants with the same static value in different files. |
74
74
  | [`no-duplicate-types`](docs/no-duplicate-types.md) | Reports exact duplicate object contracts. No fixer. |
75
+ | [`no-pass-through-schema`](docs/no-pass-through-schema.md) | Reports const aliases that only rename known schemas. Supports Zod, ArkType, and configured imports. No fixer. |
75
76
  | [`no-renamed-imports`](docs/no-renamed-imports.md) | Fixes unnecessary named-import aliases when the original name has no module-level conflict. |
76
77
  | [`no-reexports-outside-entrypoints`](docs/no-reexports-outside-entrypoints.md) | Keeps re-exports in configured entrypoint files. No fixer. |
77
78
  | [`canonical-class-names`](docs/tailwind.md) | Fixes non-canonical Tailwind CSS v4 class spellings. |
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ import { noCallOnlyAssertionsRule } from "./rules/no-call-only-assertions.js";
20
20
  import { noPoorSubstringTestsRule } from "./rules/no-poor-substring-tests.js";
21
21
  import { noRepeatedExpectCallRule } from "./rules/no-repeated-expect-call.js";
22
22
  import { noPassThroughTypeAliasRule } from "./rules/no-pass-through-type-alias.js";
23
+ import { noPassThroughSchemaRule } from "./rules/no-pass-through-schema.js";
23
24
  import { noDuplicateTypesRule } from "./rules/no-duplicate-types.js";
24
25
  import { noDuplicateConstsRule } from "./rules/no-duplicate-consts.js";
25
26
  import { noRenamedImportsRule } from "./rules/no-renamed-imports.js";
@@ -88,6 +89,7 @@ const deslopPlugin = eslintCompatPlugin({
88
89
  "no-repeated-expect-call": noRepeatedExpectCallRule,
89
90
  "no-constant-substring-tests": noPoorSubstringTestsRule,
90
91
  "no-pass-through-type-alias": noPassThroughTypeAliasRule,
92
+ "no-pass-through-schema": noPassThroughSchemaRule,
91
93
  "no-duplicate-types": noDuplicateTypesRule,
92
94
  "no-duplicate-consts": noDuplicateConstsRule,
93
95
  "no-renamed-imports": noRenamedImportsRule,
@@ -0,0 +1,2 @@
1
+ /** Disallow const aliases that only rename a known schema. */
2
+ export declare const noPassThroughSchemaRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,200 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ // Only known schema-producing APIs belong here. Parsing and inspection APIs do not.
3
+ const zodFactories = new Set([
4
+ "string", "number", "bigint", "boolean", "date", "symbol", "undefined", "null", "void",
5
+ "any", "unknown", "never", "literal", "enum", "nativeEnum", "object", "strictObject",
6
+ "looseObject", "array", "tuple", "record", "map", "set", "union", "discriminatedUnion",
7
+ "intersection", "optional", "nullable", "nullish", "lazy", "promise", "custom",
8
+ "preprocess", "instanceof", "templateLiteral", "file", "json",
9
+ ]);
10
+ const zodMethods = new Set([
11
+ "optional", "nullable", "nullish", "array", "or", "and", "refine", "superRefine", "check",
12
+ "transform", "pipe", "default", "prefault", "catch", "brand", "readonly", "describe", "meta",
13
+ "extend", "safeExtend", "merge", "pick", "omit", "partial", "required", "keyof",
14
+ "strict", "strip", "passthrough", "catchall", "nonoptional", "unwrap",
15
+ "min", "max", "length", "nonempty", "email", "url", "uuid", "regex", "includes",
16
+ "startsWith", "endsWith", "trim", "toLowerCase", "toUpperCase", "int", "positive",
17
+ "negative", "nonnegative", "nonpositive", "finite", "multipleOf", "safe", "gt", "gte", "lt", "lte",
18
+ ]);
19
+ const arkMethods = new Set([
20
+ "array", "or", "and", "pipe", "narrow", "brand", "describe", "configure", "optional",
21
+ "default", "pick", "omit", "partial", "required", "merge", "keyof", "extract", "exclude",
22
+ "atLeast", "atMost", "moreThan", "lessThan", "atLeastLength", "atMostLength", "exactlyLength",
23
+ "matching", "divisibleBy", "onUndeclaredKey", "onDeepUndeclaredKey",
24
+ ]);
25
+ function memberName(node) {
26
+ if (!node.computed && node.property.type === "Identifier")
27
+ return node.property.name;
28
+ if (node.computed && node.property.type === "Literal" && typeof node.property.value === "string")
29
+ return node.property.value;
30
+ return null;
31
+ }
32
+ function memberKind(owner, name) {
33
+ if (name === null)
34
+ return null;
35
+ if ((owner === "zod-module" || owner === "zod-namespace") && name === "z")
36
+ return "zod-namespace";
37
+ if ((owner === "zod-module" || owner === "zod-namespace") && zodFactories.has(name))
38
+ return "zod-factory";
39
+ if (owner === "ark-module" && name === "type")
40
+ return "ark-factory";
41
+ return null;
42
+ }
43
+ /** Disallow const aliases that only rename a known schema. */
44
+ export const noPassThroughSchemaRule = defineRule({
45
+ meta: {
46
+ type: "suggestion",
47
+ docs: { description: "Disallow pass-through aliases of known schemas" },
48
+ messages: { passThroughSchema: "Use {{schemaName}} directly instead of creating a pass-through schema alias." },
49
+ schema: [{
50
+ type: "object",
51
+ properties: {
52
+ libraries: { type: "array", items: { enum: ["zod", "arktype"] }, uniqueItems: true },
53
+ allowExportedAliases: { type: "boolean" },
54
+ schemaImports: {
55
+ type: "array",
56
+ items: {
57
+ type: "object",
58
+ properties: { from: { type: "string" }, names: { type: "array", items: { type: "string" } } },
59
+ required: ["from", "names"],
60
+ additionalProperties: false,
61
+ },
62
+ },
63
+ },
64
+ additionalProperties: false,
65
+ }],
66
+ },
67
+ createOnce(context) {
68
+ let options;
69
+ const candidates = [];
70
+ const exported = new Set();
71
+ function variableFor(node) {
72
+ let scope = context.sourceCode.getScope(node);
73
+ while (true) {
74
+ const variable = scope.set.get(node.name);
75
+ if (variable !== undefined)
76
+ return variable;
77
+ if (scope.upper === null)
78
+ return null;
79
+ scope = scope.upper;
80
+ }
81
+ }
82
+ function kind(node, seen = new Set()) {
83
+ if (node.type === "Identifier") {
84
+ const variable = variableFor(node);
85
+ if (variable === null || seen.has(variable) || variable.defs.length !== 1)
86
+ return null;
87
+ seen.add(variable);
88
+ const definition = variable.defs[0];
89
+ if (definition.type === "ImportBinding") {
90
+ const specifier = definition.node;
91
+ const declaration = specifier.parent;
92
+ if (declaration?.type !== "ImportDeclaration" || declaration.importKind === "type")
93
+ return null;
94
+ if (specifier.type === "ImportSpecifier" && specifier.importKind === "type")
95
+ return null;
96
+ const source = declaration.source.value;
97
+ const name = specifier.type === "ImportSpecifier"
98
+ ? specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value
99
+ : specifier.type === "ImportDefaultSpecifier" ? "default" : null;
100
+ if (name !== null && options.schemaImports?.some((entry) => entry.from === source && entry.names.includes(name)))
101
+ return "schema";
102
+ const libraries = options.libraries ?? ["zod", "arktype"];
103
+ if (libraries.includes("zod") && ["zod", "zod/v3", "zod/v4", "zod/v4/classic", "zod/v4/mini", "zod/mini"].includes(source)) {
104
+ if (name === null)
105
+ return "zod-module";
106
+ if (name === "z" || name === "default")
107
+ return "zod-namespace";
108
+ if (zodFactories.has(name))
109
+ return "zod-factory";
110
+ }
111
+ if (libraries.includes("arktype") && source === "arktype") {
112
+ if (name === null)
113
+ return "ark-module";
114
+ if (name === "type")
115
+ return "ark-factory";
116
+ }
117
+ return null;
118
+ }
119
+ if (definition.type !== "Variable" || definition.node.type !== "VariableDeclarator")
120
+ return null;
121
+ const declaration = definition.node;
122
+ if (declaration.parent.type !== "VariableDeclaration" || declaration.parent.kind !== "const" || declaration.id.type !== "Identifier" || declaration.init === null)
123
+ return null;
124
+ return kind(declaration.init, seen);
125
+ }
126
+ if (node.type === "MemberExpression") {
127
+ return memberKind(kind(node.object, seen), memberName(node));
128
+ }
129
+ if (node.type !== "CallExpression")
130
+ return null;
131
+ let factory;
132
+ if (node.callee.type === "MemberExpression") {
133
+ const owner = kind(node.callee.object, seen);
134
+ const name = memberName(node.callee);
135
+ if (owner === "zod-schema" && name === "meta" && node.arguments.length === 0)
136
+ return null;
137
+ if (name !== null && owner === "zod-schema" && zodMethods.has(name))
138
+ return owner;
139
+ if (name !== null && owner === "ark-schema" && arkMethods.has(name))
140
+ return owner;
141
+ factory = memberKind(owner, name);
142
+ }
143
+ else {
144
+ factory = kind(node.callee, seen);
145
+ }
146
+ if (factory === "zod-factory")
147
+ return "zod-schema";
148
+ if (factory === "ark-factory")
149
+ return "ark-schema";
150
+ return null;
151
+ }
152
+ function markExport(node) {
153
+ const variable = variableFor(node);
154
+ if (variable !== null)
155
+ exported.add(variable);
156
+ }
157
+ return {
158
+ before() {
159
+ options = context.options[0] ?? {};
160
+ candidates.length = 0;
161
+ exported.clear();
162
+ },
163
+ VariableDeclarator(node) {
164
+ if (node.parent.type === "VariableDeclaration" && node.parent.kind === "const" && node.id.type === "Identifier" && node.init?.type === "Identifier")
165
+ candidates.push(node);
166
+ },
167
+ ExportNamedDeclaration(node) {
168
+ if (node.exportKind === "type")
169
+ return;
170
+ if (node.declaration?.type === "VariableDeclaration") {
171
+ for (const declaration of node.declaration.declarations)
172
+ if (declaration.id.type === "Identifier")
173
+ markExport(declaration.id);
174
+ }
175
+ if (node.source !== null)
176
+ return;
177
+ for (const specifier of node.specifiers)
178
+ if (specifier.exportKind !== "type" && specifier.local.type === "Identifier")
179
+ markExport(specifier.local);
180
+ },
181
+ ExportDefaultDeclaration(node) {
182
+ if (node.declaration.type === "Identifier")
183
+ markExport(node.declaration);
184
+ },
185
+ "Program:exit"() {
186
+ for (const node of candidates) {
187
+ if (node.id.type !== "Identifier" || node.init?.type !== "Identifier")
188
+ continue;
189
+ const target = kind(node.init);
190
+ if (target !== "schema" && target !== "zod-schema" && target !== "ark-schema")
191
+ continue;
192
+ const variable = variableFor(node.id);
193
+ if (options.allowExportedAliases && variable !== null && exported.has(variable))
194
+ continue;
195
+ context.report({ node, messageId: "passThroughSchema", data: { schemaName: node.init.name } });
196
+ }
197
+ },
198
+ };
199
+ },
200
+ });
@@ -7,7 +7,7 @@ export const noReexportsOutsideEntrypointsRule = defineRule({
7
7
  type: "suggestion",
8
8
  docs: { description: "Disallow re-exports outside configured entrypoint files" },
9
9
  messages: {
10
- reexport: "Re-export this symbol from a package entrypoint instead of this file.",
10
+ reexport: "Import this symbol directly from its source, or re-export it from a package entrypoint for external consumers.",
11
11
  },
12
12
  schema: [{
13
13
  type: "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-deslop",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "Opinionated Oxlint rules that reject low-evidence, low-signal, AI-slop code patterns.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",