ai-deslop 0.4.8 → 0.5.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.
- package/README.md +2 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +29 -17
- package/dist/rules/no-pass-through-schema.d.ts +2 -0
- package/dist/rules/no-pass-through-schema.js +200 -0
- package/dist/rules/no-poor-substring-tests.js +1 -1
- package/dist/rules/no-reexports-outside-entrypoints.js +1 -1
- package/dist/rules/no-repeated-expect-call.js +1 -1
- package/dist/rules/no-restricted-syntax.d.ts +1 -0
- package/dist/rules/no-restricted-syntax.js +39 -0
- package/dist/vendor/anti-slop/rules/no-array-filter-map.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-array-filter-map.js +30 -0
- package/dist/vendor/anti-slop/rules/no-chained-type-assertions.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-chained-type-assertions.js +60 -0
- package/dist/vendor/anti-slop/rules/no-conditional-empty-object-spread.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-conditional-empty-object-spread.js +40 -0
- package/dist/vendor/anti-slop/rules/no-known-value-widening.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-known-value-widening.js +300 -0
- package/dist/vendor/anti-slop/rules/no-module-mocking.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-module-mocking.js +69 -0
- package/dist/vendor/anti-slop/rules/no-object-parameters.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-object-parameters.js +56 -0
- package/dist/vendor/anti-slop/rules/no-reduce-accumulator-copy.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-reduce-accumulator-copy.js +99 -0
- package/dist/vendor/anti-slop/rules/no-reflect-apply.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-reflect-apply.js +25 -0
- package/dist/vendor/anti-slop/rules/no-reflect-get.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-reflect-get.js +25 -0
- package/dist/vendor/anti-slop/rules/no-runtime-typeof.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-runtime-typeof.js +64 -0
- package/dist/vendor/anti-slop/rules/no-shape-in-symbol-names.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-shape-in-symbol-names.js +40 -0
- package/dist/vendor/anti-slop/rules/no-unknown-parameters.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-unknown-parameters.js +51 -0
- package/dist/vendor/anti-slop/rules/no-unknown-returns.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-unknown-returns.js +58 -0
- package/dist/vendor/anti-slop/rules/no-unknown-type-aliases.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-unknown-type-aliases.js +40 -0
- package/dist/vendor/anti-slop/rules/no-unsafe-dictionary-type.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-unsafe-dictionary-type.js +134 -0
- package/dist/vendor/anti-slop/rules/no-widen-then-assert.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/no-widen-then-assert.js +270 -0
- package/dist/vendor/anti-slop/rules/require-safety-comment-for-type-assertion.d.ts +2 -0
- package/dist/vendor/anti-slop/rules/require-safety-comment-for-type-assertion.js +100 -0
- package/dist/vendor/anti-slop/shared/array-method.d.ts +12 -0
- package/dist/vendor/anti-slop/shared/array-method.js +84 -0
- package/dist/vendor/anti-slop/shared/dictionary-types.d.ts +20 -0
- package/dist/vendor/anti-slop/shared/dictionary-types.js +377 -0
- package/dist/vendor/anti-slop/shared/function-parameters.d.ts +8 -0
- package/dist/vendor/anti-slop/shared/function-parameters.js +40 -0
- package/dist/vendor/anti-slop/shared/lexical-type-parameters.d.ts +5 -0
- package/dist/vendor/anti-slop/shared/lexical-type-parameters.js +47 -0
- package/dist/vendor/anti-slop/shared/reflect-method.d.ts +3 -0
- package/dist/vendor/anti-slop/shared/reflect-method.js +20 -0
- package/dist/vendor/anti-slop/shared/scope.d.ts +3 -0
- package/dist/vendor/anti-slop/shared/scope.js +11 -0
- package/dist/vendor/anti-slop/shared/type-alias-resolution.d.ts +23 -0
- package/dist/vendor/anti-slop/shared/type-alias-resolution.js +162 -0
- package/package.json +1 -1
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. |
|
|
@@ -88,7 +89,7 @@ All rules below are enabled by the recommended config unless listed as opt-in.
|
|
|
88
89
|
|
|
89
90
|
## Credits
|
|
90
91
|
|
|
91
|
-
- Generic rules vendored verbatim from [dmmulroy/anti-slop](https://github.com/dmmulroy/anti-slop) (MIT, © Dillon Mulroy)
|
|
92
|
+
- Generic rules vendored verbatim from [dmmulroy/anti-slop](https://github.com/dmmulroy/anti-slop) (MIT, © Dillon Mulroy) in `src/vendor/anti-slop/`, including shared helpers.
|
|
92
93
|
|
|
93
94
|
## License
|
|
94
95
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { eslintCompatPlugin } from "@oxlint/plugins";
|
|
2
2
|
/** Every `deslop/*` rule, plus selected standard rules, each set to `"error"`. Spread into the `rules` field of an oxlint config. */
|
|
3
|
-
export declare const recommendedRules: Record<string, "error"
|
|
3
|
+
export declare const recommendedRules: Record<string, "error" | ["error", {
|
|
4
|
+
selector: string;
|
|
5
|
+
message: string;
|
|
6
|
+
}]>;
|
|
4
7
|
/** ESLint flat-config style configs. `recommended` enables every recommended `deslop/*` rule as `"error"` (excluding opt-in rules), plus standard `typescript/*` rules. The `typescript/*` rules require type-aware linting (`options.typeAware`), so `recommended` sets it. */
|
|
5
8
|
export declare const configs: {
|
|
6
9
|
recommended: {
|
|
@@ -8,7 +11,10 @@ export declare const configs: {
|
|
|
8
11
|
options: {
|
|
9
12
|
typeAware: boolean;
|
|
10
13
|
};
|
|
11
|
-
rules: Record<string, "error"
|
|
14
|
+
rules: Record<string, "error" | ["error", {
|
|
15
|
+
selector: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}]>;
|
|
12
18
|
};
|
|
13
19
|
};
|
|
14
20
|
/** Generic Oxlint rules that reject low-evidence and low-signal implementation patterns. */
|
package/dist/index.js
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
import { eslintCompatPlugin } from "@oxlint/plugins";
|
|
2
|
-
import { noArrayFilterMapRule } from "./rules/no-array-filter-map.js";
|
|
3
|
-
import { noReduceAccumulatorCopyRule } from "./rules/no-reduce-accumulator-copy.js";
|
|
4
|
-
import { noChainedTypeAssertionsRule } from "./rules/no-chained-type-assertions.js";
|
|
5
|
-
import { noConditionalEmptyObjectSpreadRule } from "./rules/no-conditional-empty-object-spread.js";
|
|
6
|
-
import { noKnownValueWideningRule } from "./rules/no-known-value-widening.js";
|
|
7
|
-
import { noModuleMockingRule } from "./rules/no-module-mocking.js";
|
|
8
|
-
import { noObjectParametersRule } from "./rules/no-object-parameters.js";
|
|
9
|
-
import { noReflectApplyRule } from "./rules/no-reflect-apply.js";
|
|
10
|
-
import { noReflectGetRule } from "./rules/no-reflect-get.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
2
|
+
import { noArrayFilterMapRule } from "./vendor/anti-slop/rules/no-array-filter-map.js";
|
|
3
|
+
import { noReduceAccumulatorCopyRule } from "./vendor/anti-slop/rules/no-reduce-accumulator-copy.js";
|
|
4
|
+
import { noChainedTypeAssertionsRule } from "./vendor/anti-slop/rules/no-chained-type-assertions.js";
|
|
5
|
+
import { noConditionalEmptyObjectSpreadRule } from "./vendor/anti-slop/rules/no-conditional-empty-object-spread.js";
|
|
6
|
+
import { noKnownValueWideningRule } from "./vendor/anti-slop/rules/no-known-value-widening.js";
|
|
7
|
+
import { noModuleMockingRule } from "./vendor/anti-slop/rules/no-module-mocking.js";
|
|
8
|
+
import { noObjectParametersRule } from "./vendor/anti-slop/rules/no-object-parameters.js";
|
|
9
|
+
import { noReflectApplyRule } from "./vendor/anti-slop/rules/no-reflect-apply.js";
|
|
10
|
+
import { noReflectGetRule } from "./vendor/anti-slop/rules/no-reflect-get.js";
|
|
11
|
+
import { noRestrictedSyntaxRule } from "./rules/no-restricted-syntax.js";
|
|
12
|
+
import { noRuntimeTypeofRule } from "./vendor/anti-slop/rules/no-runtime-typeof.js";
|
|
13
|
+
import { noForbiddenTermInSymbolNamesRule } from "./vendor/anti-slop/rules/no-shape-in-symbol-names.js";
|
|
14
|
+
import { noUnknownParametersRule } from "./vendor/anti-slop/rules/no-unknown-parameters.js";
|
|
15
|
+
import { noUnknownReturnsRule } from "./vendor/anti-slop/rules/no-unknown-returns.js";
|
|
16
|
+
import { noUnknownTypeAliasesRule } from "./vendor/anti-slop/rules/no-unknown-type-aliases.js";
|
|
17
|
+
import { noUnsafeDictionaryTypeRule } from "./vendor/anti-slop/rules/no-unsafe-dictionary-type.js";
|
|
18
|
+
import { noWidenThenAssertRule } from "./vendor/anti-slop/rules/no-widen-then-assert.js";
|
|
19
|
+
import { requireSafetyCommentForTypeAssertionRule } from "./vendor/anti-slop/rules/require-safety-comment-for-type-assertion.js";
|
|
19
20
|
import { noCallOnlyAssertionsRule } from "./rules/no-call-only-assertions.js";
|
|
20
21
|
import { noPoorSubstringTestsRule } from "./rules/no-poor-substring-tests.js";
|
|
21
22
|
import { noRepeatedExpectCallRule } from "./rules/no-repeated-expect-call.js";
|
|
22
23
|
import { noPassThroughTypeAliasRule } from "./rules/no-pass-through-type-alias.js";
|
|
24
|
+
import { noPassThroughSchemaRule } from "./rules/no-pass-through-schema.js";
|
|
23
25
|
import { noDuplicateTypesRule } from "./rules/no-duplicate-types.js";
|
|
24
26
|
import { noDuplicateConstsRule } from "./rules/no-duplicate-consts.js";
|
|
25
27
|
import { noRenamedImportsRule } from "./rules/no-renamed-imports.js";
|
|
@@ -29,6 +31,7 @@ import { canonicalClassNames } from "./tailwind/rule.js";
|
|
|
29
31
|
export const recommendedRules = {
|
|
30
32
|
"deslop/no-array-filter-map": "error",
|
|
31
33
|
"deslop/no-reduce-accumulator-copy": "error",
|
|
34
|
+
"oxc/no-accumulating-spread": "error",
|
|
32
35
|
"deslop/no-chained-type-assertions": "error",
|
|
33
36
|
"deslop/no-conditional-empty-object-spread": "error",
|
|
34
37
|
"deslop/no-known-value-widening": "error",
|
|
@@ -53,6 +56,13 @@ export const recommendedRules = {
|
|
|
53
56
|
"typescript/no-floating-promises": "error",
|
|
54
57
|
"typescript/no-unnecessary-condition": "error",
|
|
55
58
|
"no-unreachable": "error",
|
|
59
|
+
"deslop/no-restricted-syntax": [
|
|
60
|
+
"error",
|
|
61
|
+
{
|
|
62
|
+
selector: "NewExpression[callee.name='Map'][arguments.length=0]:not([typeArguments])",
|
|
63
|
+
message: "Bare new Map() infers Map<any, any>. Provide entries so types can be inferred.",
|
|
64
|
+
},
|
|
65
|
+
],
|
|
56
66
|
};
|
|
57
67
|
/** ESLint flat-config style configs. `recommended` enables every recommended `deslop/*` rule as `"error"` (excluding opt-in rules), plus standard `typescript/*` rules. The `typescript/*` rules require type-aware linting (`options.typeAware`), so `recommended` sets it. */
|
|
58
68
|
export const configs = {
|
|
@@ -75,6 +85,7 @@ const deslopPlugin = eslintCompatPlugin({
|
|
|
75
85
|
"no-object-parameters": noObjectParametersRule,
|
|
76
86
|
"no-reflect-apply": noReflectApplyRule,
|
|
77
87
|
"no-reflect-get": noReflectGetRule,
|
|
88
|
+
"no-restricted-syntax": noRestrictedSyntaxRule,
|
|
78
89
|
"no-runtime-typeof": noRuntimeTypeofRule,
|
|
79
90
|
"no-shape-in-symbol-names": noForbiddenTermInSymbolNamesRule,
|
|
80
91
|
"no-unknown-parameters": noUnknownParametersRule,
|
|
@@ -88,6 +99,7 @@ const deslopPlugin = eslintCompatPlugin({
|
|
|
88
99
|
"no-repeated-expect-call": noRepeatedExpectCallRule,
|
|
89
100
|
"no-constant-substring-tests": noPoorSubstringTestsRule,
|
|
90
101
|
"no-pass-through-type-alias": noPassThroughTypeAliasRule,
|
|
102
|
+
"no-pass-through-schema": noPassThroughSchemaRule,
|
|
91
103
|
"no-duplicate-types": noDuplicateTypesRule,
|
|
92
104
|
"no-duplicate-consts": noDuplicateConstsRule,
|
|
93
105
|
"no-renamed-imports": noRenamedImportsRule,
|
|
@@ -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
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { defineRule } from "@oxlint/plugins";
|
|
4
|
-
import { resolveVariable } from "../shared/scope.js";
|
|
4
|
+
import { resolveVariable } from "../vendor/anti-slop/shared/scope.js";
|
|
5
5
|
const SUBSTRING_MATCHERS = new Set([
|
|
6
6
|
"toContain",
|
|
7
7
|
"toInclude",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { matchesGlob, relative } from "node:path";
|
|
2
2
|
import { defineRule } from "@oxlint/plugins";
|
|
3
|
-
import { resolveVariable } from "../shared/scope.js";
|
|
3
|
+
import { resolveVariable } from "../vendor/anti-slop/shared/scope.js";
|
|
4
4
|
/** Keep re-exports in explicitly allowed entrypoint files. */
|
|
5
5
|
export const noReexportsOutsideEntrypointsRule = defineRule({
|
|
6
6
|
meta: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineRule } from "@oxlint/plugins";
|
|
2
|
-
import { resolveVariable } from "../shared/scope.js";
|
|
2
|
+
import { resolveVariable } from "../vendor/anti-slop/shared/scope.js";
|
|
3
3
|
const TEST_FILE = /(?:^|[/\\])(?:__tests__|tests?)(?:[/\\]|$)|(?:[._](?:test|spec))\.[cm]?[jt]sx?$/i;
|
|
4
4
|
const TEST_MODULES = new Set(["bun:test", "@jest/globals", "vitest"]);
|
|
5
5
|
const CLOSING_TOKENS = new Set([")", "]", "}"]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noRestrictedSyntaxRule: import("@oxlint/plugins").Rule;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defineRule } from "@oxlint/plugins";
|
|
2
|
+
export const noRestrictedSyntaxRule = defineRule({
|
|
3
|
+
meta: {
|
|
4
|
+
type: "suggestion",
|
|
5
|
+
docs: { description: "Disallow syntax matched by configured AST selectors." },
|
|
6
|
+
messages: { restrictedSyntax: "{{message}}" },
|
|
7
|
+
schema: {
|
|
8
|
+
type: "array",
|
|
9
|
+
items: {
|
|
10
|
+
anyOf: [
|
|
11
|
+
{ type: "string" },
|
|
12
|
+
{
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
selector: { type: "string" },
|
|
16
|
+
message: { type: "string" },
|
|
17
|
+
},
|
|
18
|
+
required: ["selector"],
|
|
19
|
+
additionalProperties: false,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
create(context) {
|
|
26
|
+
const visitors = Object.create(null);
|
|
27
|
+
for (const restriction of context.options) {
|
|
28
|
+
const selector = typeof restriction === "string" ? restriction : restriction.selector;
|
|
29
|
+
const message = (typeof restriction === "string" ? undefined : restriction.message)
|
|
30
|
+
?? `Using '${selector}' is not allowed.`;
|
|
31
|
+
const previous = visitors[selector];
|
|
32
|
+
visitors[selector] = (node) => {
|
|
33
|
+
previous?.(node);
|
|
34
|
+
context.report({ node, messageId: "restrictedSyntax", data: { message } });
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return visitors;
|
|
38
|
+
},
|
|
39
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineRule } from "@oxlint/plugins";
|
|
2
|
+
import { arrayMethodTarget, isKnownArrayExpression, unwrapArrayExpression } from "../shared/array-method.js";
|
|
3
|
+
/** Reject eager array filter/map pipelines; lazy iterator helpers remain allowed. */
|
|
4
|
+
export const noArrayFilterMapRule = defineRule({
|
|
5
|
+
meta: {
|
|
6
|
+
type: "suggestion",
|
|
7
|
+
docs: { description: "Disallow adjacent array filter/map passes in favor of lazy iterator helpers or a single transformation." },
|
|
8
|
+
messages: {
|
|
9
|
+
arrayFilterMap: "Avoid consecutive array `{{first}}` and `{{second}}` passes. Prefer `.values().{{first}}(...).{{second}}(...).toArray()` where iterator helpers are supported, or a single `flatMap`/mutating reducer. Preserve callback ordering, indexes, and filtering semantics.",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
createOnce(context) {
|
|
13
|
+
return {
|
|
14
|
+
CallExpression(node) {
|
|
15
|
+
const outer = arrayMethodTarget(node.callee);
|
|
16
|
+
if (outer === null || (outer.name !== "map" && outer.name !== "filter"))
|
|
17
|
+
return;
|
|
18
|
+
const innerCall = unwrapArrayExpression(outer.object);
|
|
19
|
+
if (innerCall.type !== "CallExpression")
|
|
20
|
+
return;
|
|
21
|
+
const inner = arrayMethodTarget(innerCall.callee);
|
|
22
|
+
if (inner === null || inner.name !== (outer.name === "map" ? "filter" : "map"))
|
|
23
|
+
return;
|
|
24
|
+
if (!isKnownArrayExpression(context.sourceCode, inner.object))
|
|
25
|
+
return;
|
|
26
|
+
context.report({ node, messageId: "arrayFilterMap", data: { first: inner.name, second: outer.name } });
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { defineRule } from "@oxlint/plugins";
|
|
2
|
+
function isTypeAssertionExpression(node) {
|
|
3
|
+
return node.type === "TSAsExpression" || node.type === "TSTypeAssertion";
|
|
4
|
+
}
|
|
5
|
+
function unwrapParenthesizedExpression(expression) {
|
|
6
|
+
let current = expression;
|
|
7
|
+
while (current.type === "ParenthesizedExpression") {
|
|
8
|
+
current = current.expression;
|
|
9
|
+
}
|
|
10
|
+
return current;
|
|
11
|
+
}
|
|
12
|
+
function isConstAssertion(node) {
|
|
13
|
+
const { typeAnnotation } = node;
|
|
14
|
+
return (typeAnnotation.type === "TSTypeReference" &&
|
|
15
|
+
typeAnnotation.typeName.type === "Identifier" &&
|
|
16
|
+
typeAnnotation.typeName.name === "const");
|
|
17
|
+
}
|
|
18
|
+
function isOutermostAssertionInChain(node) {
|
|
19
|
+
let current = node;
|
|
20
|
+
let parent = node.parent;
|
|
21
|
+
while (parent.type === "ParenthesizedExpression" && parent.expression === current) {
|
|
22
|
+
current = parent;
|
|
23
|
+
parent = parent.parent;
|
|
24
|
+
}
|
|
25
|
+
return !isTypeAssertionExpression(parent) || parent.expression !== current;
|
|
26
|
+
}
|
|
27
|
+
function isForbiddenAssertionChain(node) {
|
|
28
|
+
let assertionCount = 0;
|
|
29
|
+
let hasNonConstAssertion = false;
|
|
30
|
+
let current = node;
|
|
31
|
+
while (isTypeAssertionExpression(current)) {
|
|
32
|
+
assertionCount += 1;
|
|
33
|
+
hasNonConstAssertion ||= !isConstAssertion(current);
|
|
34
|
+
current = unwrapParenthesizedExpression(current.expression);
|
|
35
|
+
}
|
|
36
|
+
return assertionCount > 1 && hasNonConstAssertion;
|
|
37
|
+
}
|
|
38
|
+
/** Disallow nested TypeScript type assertions, while permitting chains made only of const assertions. */
|
|
39
|
+
export const noChainedTypeAssertionsRule = defineRule({
|
|
40
|
+
meta: {
|
|
41
|
+
type: "problem",
|
|
42
|
+
docs: {
|
|
43
|
+
description: "Disallow chained TypeScript as and angle-bracket assertions, including parenthesized chains.",
|
|
44
|
+
},
|
|
45
|
+
messages: {
|
|
46
|
+
chained: "This assertion chain discards type evidence. Keep the original precise type, or parse untrusted input at its boundary before narrowing it.",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
createOnce(context) {
|
|
50
|
+
const checkTypeAssertion = (node) => {
|
|
51
|
+
if (!isOutermostAssertionInChain(node) || !isForbiddenAssertionChain(node))
|
|
52
|
+
return;
|
|
53
|
+
context.report({ node, messageId: "chained" });
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
TSAsExpression: checkTypeAssertion,
|
|
57
|
+
TSTypeAssertion: checkTypeAssertion,
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { defineRule } from "@oxlint/plugins";
|
|
2
|
+
function unwrapParentheses(node) {
|
|
3
|
+
let current = node;
|
|
4
|
+
while (current.type === "ParenthesizedExpression") {
|
|
5
|
+
current = current.expression;
|
|
6
|
+
}
|
|
7
|
+
return current;
|
|
8
|
+
}
|
|
9
|
+
function isEmptyObjectExpression(node) {
|
|
10
|
+
return node.type === "ObjectExpression" && node.properties.length === 0;
|
|
11
|
+
}
|
|
12
|
+
function isConditionalEmptyObjectSpread(node) {
|
|
13
|
+
const conditional = unwrapParentheses(node);
|
|
14
|
+
return (conditional.type === "ConditionalExpression" &&
|
|
15
|
+
(isEmptyObjectExpression(conditional.consequent) ||
|
|
16
|
+
isEmptyObjectExpression(conditional.alternate)));
|
|
17
|
+
}
|
|
18
|
+
/** Ban conditional empty-object spreads without changing their omission semantics. */
|
|
19
|
+
export const noConditionalEmptyObjectSpreadRule = defineRule({
|
|
20
|
+
meta: {
|
|
21
|
+
type: "suggestion",
|
|
22
|
+
docs: {
|
|
23
|
+
description: "Disallow object spreads that conditionally spread an empty object to omit fields.",
|
|
24
|
+
},
|
|
25
|
+
messages: {
|
|
26
|
+
avoid: "This conditional spread hides property omission behind an empty object. Build the object in separate statements and add the property only when present.",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
createOnce(context) {
|
|
30
|
+
return {
|
|
31
|
+
SpreadElement(node) {
|
|
32
|
+
if (node.parent.type !== "ObjectExpression")
|
|
33
|
+
return;
|
|
34
|
+
if (isConditionalEmptyObjectSpread(node.argument)) {
|
|
35
|
+
context.report({ node, messageId: "avoid" });
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
});
|