ai-deslop 0.4.6 → 0.4.7

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Opinionated Oxlint rules that reject low-evidence, low-signal, AI-slop code patterns.
4
4
 
5
- Rules are vendored from [dmmulroy/anti-slop](https://github.com/dmmulroy/anti-slop) (generic rules) alongside additional deslop rules, all renamed under the `deslop/*` namespace. The vendored source is MIT licensed; see Credits below.
5
+ Combines vendored anti-slop rules with additional rules under the `deslop/*` namespace. See Credits for attribution.
6
6
 
7
7
  ## Install
8
8
 
@@ -26,71 +26,65 @@ export default defineConfig({
26
26
  });
27
27
  ```
28
28
 
29
- ## Rules
30
-
31
- ### Type safety and evidence
32
-
33
- | Rule | Status | Description |
34
- | --- | --- | --- |
35
- | `no-chained-type-assertions` | Recommended | Rejects nested `as` / angle-bracket assertions that fabricate evidence. |
36
- | `no-conditional-empty-object-spread` | Recommended | Reports object spreads using a conditional `{}` branch to omit fields. |
37
- | `no-known-value-widening` | Recommended | Rejects known expressions flowing into explicit `unknown`, `object`, or open dictionaries. |
38
- | `no-object-parameters` | Recommended | Rejects `object` on function inputs. |
39
- | `no-runtime-typeof` | Recommended | Requires boundary parsing instead of ad hoc `typeof` narrowing. |
40
- | `no-unknown-parameters` | Recommended | Rejects `unknown` on function inputs. |
41
- | `no-unknown-returns` | Recommended | Rejects return contracts resolving to `unknown`. |
42
- | `no-unknown-type-aliases` | Recommended | Rejects aliases whose resolved type is `unknown`. |
43
- | `no-unsafe-dictionary-type` | Recommended | Rejects dictionary values based on unsafe escape hatches. |
44
- | `no-widen-then-assert` | Recommended | Rejects widening known evidence before narrowing assertions. |
45
- | `require-safety-comment-for-type-assertion` | Recommended | Requires an invariant justification for non-const assertions. |
46
- | `no-pass-through-type-alias` | Recommended | Disallows aliases that only rename another type. |
29
+ ## Rules
30
+
31
+ All rules below are enabled by the recommended config unless listed as opt-in.
32
+
33
+ ### Type safety and evidence
34
+
35
+ | Rule | Description |
36
+ | --- | --- |
37
+ | `no-chained-type-assertions` | Rejects nested `as` / angle-bracket assertions that fabricate evidence. |
38
+ | `no-conditional-empty-object-spread` | Reports object spreads using a conditional `{}` branch to omit fields. |
39
+ | `no-known-value-widening` | Rejects known expressions flowing into explicit `unknown`, `object`, or open dictionaries. |
40
+ | `no-object-parameters` | Rejects `object` on function inputs. |
41
+ | `no-runtime-typeof` | Requires boundary parsing instead of ad hoc `typeof` narrowing. |
42
+ | `no-unknown-parameters` | Rejects `unknown` on function inputs. |
43
+ | `no-unknown-returns` | Rejects return contracts resolving to `unknown`. |
44
+ | `no-unknown-type-aliases` | Rejects aliases whose resolved type is `unknown`. |
45
+ | `no-unsafe-dictionary-type` | Rejects dictionary values based on unsafe escape hatches. |
46
+ | `no-widen-then-assert` | Rejects widening known evidence before narrowing assertions. |
47
+ | `require-safety-comment-for-type-assertion` | Requires an invariant justification for non-const assertions. |
48
+ | `no-pass-through-type-alias` | Disallows aliases that only rename another type. |
47
49
 
48
50
  ### Code quality and APIs
49
51
 
50
- | Rule | Status | Description |
51
- | --- | --- | --- |
52
- | `no-array-filter-map` | Recommended | Rejects adjacent eager array filter/map passes; allows lazy iterator pipelines. |
53
- | `no-reduce-accumulator-copy` | Recommended | Rejects non-spread accumulator copies inside reducers. Enable native `oxc/no-accumulating-spread` alongside it. |
54
- | `no-reflect-apply` | Recommended | Rejects global `Reflect.apply`. |
55
- | `no-reflect-get` | Recommended | Rejects global `Reflect.get`. |
56
- | `no-shape-in-symbol-names` | Recommended | Rejects `shape` in locally owned symbol names. |
52
+ | Rule | Description |
53
+ | --- | --- |
54
+ | `no-array-filter-map` | Rejects adjacent eager array filter/map passes; allows lazy iterator pipelines. |
55
+ | `no-reduce-accumulator-copy` | Rejects non-spread accumulator copies inside reducers. Enable native `oxc/no-accumulating-spread` alongside it. |
56
+ | `no-reflect-apply` | Rejects global `Reflect.apply`. |
57
+ | `no-reflect-get` | Rejects global `Reflect.get`. |
58
+ | `no-shape-in-symbol-names` | Rejects `shape` in locally owned symbol names. |
57
59
 
58
60
  ### Tests
59
61
 
60
- | Rule | Status | Description |
61
- | --- | --- | --- |
62
- | `no-call-only-assertions` | Recommended | Flags tests whose assertions only check mock calls. |
63
- | `no-module-mocking` | Recommended | Rejects Vitest and Jest module mocks. |
64
- | `no-poor-substring-tests` | Recommended | Rejects testing that a constant string contains substrings. |
65
- | `no-repeated-expect-call` | Recommended | Flags adjacent assertions that repeat the same call in test files. Reuse the result instead. No fixer. |
62
+ | Rule | Description |
63
+ | --- | --- |
64
+ | `no-call-only-assertions` | Flags tests whose assertions only check mock calls. |
65
+ | `no-module-mocking` | Rejects Vitest and Jest module mocks. |
66
+ | `no-poor-substring-tests` | Rejects testing that a constant string contains substrings. |
67
+ | `no-repeated-expect-call` | Flags adjacent assertions that repeat the same call in test files. Reuse the result instead. No fixer. |
66
68
 
67
69
  ### Opt-in rules
68
70
 
69
- | Rule | Status | Description |
70
- | --- | --- | --- |
71
- | [`no-duplicate-consts`](docs/no-duplicate-consts.md) | Opt-in | Reports all-caps constants with the same static value in different files. |
72
- | [`no-duplicate-types`](docs/no-duplicate-types.md) | Opt-in | Reports exact duplicate object contracts. No fixer. |
73
- | [`no-renamed-imports`](docs/no-renamed-imports.md) | Opt-in | Fixes unnecessary named-import aliases when the original name has no module-level conflict. |
74
- | [`canonical-class-names`](docs/tailwind.md) | Opt-in | Fixes non-canonical Tailwind CSS v4 class spellings. |
71
+ | Rule | Description |
72
+ | --- | --- |
73
+ | [`no-duplicate-consts`](docs/no-duplicate-consts.md) | Reports all-caps constants with the same static value in different files. |
74
+ | [`no-duplicate-types`](docs/no-duplicate-types.md) | Reports exact duplicate object contracts. No fixer. |
75
+ | [`no-renamed-imports`](docs/no-renamed-imports.md) | Fixes unnecessary named-import aliases when the original name has no module-level conflict. |
76
+ | [`no-reexports-outside-entrypoints`](docs/no-reexports-outside-entrypoints.md) | Keeps re-exports in configured entrypoint files. No fixer. |
77
+ | [`canonical-class-names`](docs/tailwind.md) | Fixes non-canonical Tailwind CSS v4 class spellings. |
75
78
 
76
79
  ### Standard rules enabled by the recommended config
77
80
 
78
- | Standard rule | Status | Description |
79
- | --- | --- | --- |
80
- | `typescript/no-deprecated` | Recommended | Disallows using code marked `@deprecated`. Requires type-aware linting. |
81
- | `typescript/no-require-imports` | Recommended | Disallows CommonJS `require()` imports. |
82
- | `typescript/no-floating-promises` | Recommended | Disallows floating promises. Requires type-aware linting. |
83
- | `typescript/no-unnecessary-condition` | Recommended | Disallows always truthy/falsy/nullish conditions. Requires type-aware linting. |
84
- | `no-unreachable` | Recommended | Disallows unreachable code. |
85
-
86
- ## Guides
87
-
88
- | Guide | Contents |
89
- | --- | --- |
90
- | [No duplicate consts](docs/no-duplicate-consts.md) | Configuration and exact static-value comparison behavior. |
91
- | [No duplicate types](docs/no-duplicate-types.md) | Exact comparison behavior, worker scope, complete TypeScript/JSON examples, and every supported option. |
92
- | [No renamed imports](docs/no-renamed-imports.md) | When named-import aliases are fixed and how conflicts are handled. |
93
- | [Tailwind canonical class names](docs/tailwind.md) | Tailwind v4 setup, complete TypeScript/JSON examples, and every supported option. |
81
+ | Standard rule | Description |
82
+ | --- | --- |
83
+ | `typescript/no-deprecated` | Disallows using code marked `@deprecated`. Requires type-aware linting. |
84
+ | `typescript/no-require-imports` | Disallows CommonJS `require()` imports. |
85
+ | `typescript/no-floating-promises` | Disallows floating promises. Requires type-aware linting. |
86
+ | `typescript/no-unnecessary-condition` | Disallows always truthy/falsy/nullish conditions. Requires type-aware linting. |
87
+ | `no-unreachable` | Disallows unreachable code. |
94
88
 
95
89
  ## Credits
96
90
 
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ import { noPassThroughTypeAliasRule } from "./rules/no-pass-through-type-alias.j
23
23
  import { noDuplicateTypesRule } from "./rules/no-duplicate-types.js";
24
24
  import { noDuplicateConstsRule } from "./rules/no-duplicate-consts.js";
25
25
  import { noRenamedImportsRule } from "./rules/no-renamed-imports.js";
26
+ import { noReexportsOutsideEntrypointsRule } from "./rules/no-reexports-outside-entrypoints.js";
26
27
  import { canonicalClassNames } from "./tailwind/rule.js";
27
28
  /** Every `deslop/*` rule, plus selected standard rules, each set to `"error"`. Spread into the `rules` field of an oxlint config. */
28
29
  export const recommendedRules = {
@@ -90,6 +91,7 @@ const deslopPlugin = eslintCompatPlugin({
90
91
  "no-duplicate-types": noDuplicateTypesRule,
91
92
  "no-duplicate-consts": noDuplicateConstsRule,
92
93
  "no-renamed-imports": noRenamedImportsRule,
94
+ "no-reexports-outside-entrypoints": noReexportsOutsideEntrypointsRule,
93
95
  // Opt-in: NOT in `recommendedRules`. Requires a `cssPath` option, so users add it manually.
94
96
  "canonical-class-names": canonicalClassNames,
95
97
  },
@@ -0,0 +1,2 @@
1
+ /** Keep re-exports in explicitly allowed entrypoint files. */
2
+ export declare const noReexportsOutsideEntrypointsRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,51 @@
1
+ import { matchesGlob, relative } from "node:path";
2
+ import { defineRule } from "@oxlint/plugins";
3
+ import { resolveVariable } from "../shared/scope.js";
4
+ /** Keep re-exports in explicitly allowed entrypoint files. */
5
+ export const noReexportsOutsideEntrypointsRule = defineRule({
6
+ meta: {
7
+ type: "suggestion",
8
+ docs: { description: "Disallow re-exports outside configured entrypoint files" },
9
+ messages: {
10
+ reexport: "Re-export this symbol from a package entrypoint instead of this file.",
11
+ },
12
+ schema: [{
13
+ type: "object",
14
+ properties: {
15
+ entrypoints: { type: "array", items: { type: "string" } },
16
+ },
17
+ additionalProperties: false,
18
+ }],
19
+ },
20
+ createOnce(context) {
21
+ function isImported(node) {
22
+ return resolveVariable(context.sourceCode, node)?.defs.some((definition) => definition.type === "ImportBinding") ?? false;
23
+ }
24
+ return {
25
+ before() {
26
+ const options = context.options[0];
27
+ const filename = relative(context.cwd, context.filename).replaceAll("\\", "/");
28
+ return !(options?.entrypoints ?? ["**/index.ts"]).some((pattern) => matchesGlob(filename, pattern));
29
+ },
30
+ ExportNamedDeclaration(node) {
31
+ if (node.source !== null) {
32
+ context.report({ node, messageId: "reexport" });
33
+ return;
34
+ }
35
+ for (const specifier of node.specifiers) {
36
+ if (specifier.local.type === "Identifier" && isImported(specifier.local)) {
37
+ context.report({ node: specifier, messageId: "reexport" });
38
+ }
39
+ }
40
+ },
41
+ ExportAllDeclaration(node) {
42
+ context.report({ node, messageId: "reexport" });
43
+ },
44
+ ExportDefaultDeclaration(node) {
45
+ if (node.declaration.type === "Identifier" && isImported(node.declaration)) {
46
+ context.report({ node, messageId: "reexport" });
47
+ }
48
+ },
49
+ };
50
+ },
51
+ });
@@ -0,0 +1,24 @@
1
+ import type { ESTree, Visitor } from "@oxlint/plugins";
2
+ type Binding = {
3
+ readonly identifier: Extract<ESTree.Node, {
4
+ type: "Identifier";
5
+ }> | null;
6
+ readonly owner: ESTree.Node;
7
+ };
8
+ /** A conservative index: an absent name is unbound, and a unique root import
9
+ * cannot be shadowed. Ambiguous names still use the ordinary scope manager. */
10
+ export declare function createLexicalBindingIndex(): {
11
+ visitor: Visitor;
12
+ defer(check: () => void): void;
13
+ getBindings(name: string): readonly Binding[];
14
+ hasWrites(name: string): boolean;
15
+ uniqueVisibleBinding(identifier: ESTree.Node & {
16
+ type: "Identifier";
17
+ name: string;
18
+ }): Binding | null;
19
+ isUnbound(name: string): boolean;
20
+ hasImport(name: string, accepts: (specifier: ESTree.ImportSpecifier, declaration: ESTree.ImportDeclaration) => boolean): boolean;
21
+ isUnshadowedImport(name: string, accepts: (specifier: ESTree.ImportSpecifier, declaration: ESTree.ImportDeclaration) => boolean): boolean;
22
+ };
23
+ export type LexicalBindingIndex = ReturnType<typeof createLexicalBindingIndex>;
24
+ export {};
@@ -0,0 +1,252 @@
1
+ const indexes = new WeakMap();
2
+ function bindingScope(binding) {
3
+ const { owner, identifier } = binding;
4
+ if (owner.type === "ImportSpecifier" || owner.type === "ImportDefaultSpecifier" || owner.type === "ImportNamespaceSpecifier") {
5
+ return owner.parent.type === "ImportDeclaration" && owner.parent.parent.type === "Program" ? owner.parent.parent : null;
6
+ }
7
+ if (owner.type === "CatchClause")
8
+ return owner;
9
+ if (owner.type === "TSFunctionType" || owner.type === "TSConstructorType" || owner.type === "TSCallSignatureDeclaration" || owner.type === "TSConstructSignatureDeclaration" || owner.type === "TSMethodSignature")
10
+ return owner;
11
+ if (owner.type === "ArrowFunctionExpression" || owner.type === "FunctionExpression" || owner.type === "TSEmptyBodyFunctionExpression")
12
+ return owner;
13
+ if (owner.type === "FunctionDeclaration" || owner.type === "TSDeclareFunction") {
14
+ if (identifier !== owner.id)
15
+ return owner;
16
+ }
17
+ else if (owner.type !== "VariableDeclarator")
18
+ return null;
19
+ const isVar = owner.type === "VariableDeclarator" && owner.parent.type === "VariableDeclaration" && owner.parent.kind === "var";
20
+ let current = owner.parent;
21
+ while (current !== null) {
22
+ if (current.type === "Program" || current.type === "TSModuleBlock" || current.type === "StaticBlock")
23
+ return current;
24
+ if (!isVar && (current.type === "BlockStatement" || current.type === "SwitchStatement" || current.type === "ForStatement" || current.type === "ForInStatement" || current.type === "ForOfStatement"))
25
+ return current;
26
+ if (isVar && (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression"))
27
+ return current.body;
28
+ current = current.parent;
29
+ }
30
+ return null;
31
+ }
32
+ /** A conservative index: an absent name is unbound, and a unique root import
33
+ * cannot be shadowed. Ambiguous names still use the ordinary scope manager. */
34
+ export function createLexicalBindingIndex() {
35
+ let index;
36
+ let pending = [];
37
+ const addPattern = (node, owner) => {
38
+ if (node === null)
39
+ return;
40
+ if (node.type === "Identifier") {
41
+ const bindings = index.bindings.get(node.name) ?? [];
42
+ bindings.push({ identifier: node, owner });
43
+ index.bindings.set(node.name, bindings);
44
+ }
45
+ else if (node.type === "ObjectPattern") {
46
+ for (const property of node.properties)
47
+ addPattern(property.type === "RestElement" ? property.argument : property.value, owner);
48
+ }
49
+ else if (node.type === "ArrayPattern") {
50
+ for (const element of node.elements)
51
+ addPattern(element, owner);
52
+ }
53
+ else if (node.type === "AssignmentPattern") {
54
+ addPattern(node.left, owner);
55
+ }
56
+ else if (node.type === "RestElement") {
57
+ addPattern(node.argument, owner);
58
+ }
59
+ else if (node.type === "TSParameterProperty") {
60
+ addPattern(node.parameter, owner);
61
+ }
62
+ };
63
+ const buildBinding = (node) => {
64
+ switch (node.type) {
65
+ case "VariableDeclarator":
66
+ addPattern(node.id, node);
67
+ break;
68
+ case "ArrowFunctionExpression":
69
+ case "FunctionDeclaration":
70
+ case "FunctionExpression":
71
+ case "TSDeclareFunction":
72
+ case "TSEmptyBodyFunctionExpression":
73
+ addPattern(node.id, node);
74
+ for (const parameter of node.params)
75
+ addPattern(parameter, node);
76
+ break;
77
+ case "ClassDeclaration":
78
+ case "ClassExpression":
79
+ case "TSTypeAliasDeclaration":
80
+ case "TSInterfaceDeclaration":
81
+ case "TSEnumDeclaration":
82
+ case "TSModuleDeclaration":
83
+ case "TSImportEqualsDeclaration":
84
+ addPattern(node.id, node);
85
+ break;
86
+ case "TSFunctionType":
87
+ case "TSConstructorType":
88
+ case "TSCallSignatureDeclaration":
89
+ case "TSConstructSignatureDeclaration":
90
+ case "TSMethodSignature":
91
+ for (const parameter of node.params)
92
+ addPattern(parameter, node);
93
+ break;
94
+ case "CatchClause":
95
+ addPattern(node.param, node);
96
+ break;
97
+ case "ImportSpecifier":
98
+ case "ImportDefaultSpecifier":
99
+ case "ImportNamespaceSpecifier":
100
+ addPattern(node.local, node);
101
+ break;
102
+ case "TSTypeParameter":
103
+ addPattern(node.name, node);
104
+ break;
105
+ case "TSEnumMember":
106
+ if (node.id.type === "Identifier")
107
+ addPattern(node.id, node);
108
+ else {
109
+ const name = node.id.type === "Literal" ? node.id.value : node.id.expressions.length === 0 ? node.id.quasis[0]?.value.cooked : null;
110
+ if (typeof name === "string") {
111
+ const bindings = index.bindings.get(name) ?? [];
112
+ bindings.push({ identifier: null, owner: node });
113
+ index.bindings.set(name, bindings);
114
+ }
115
+ }
116
+ break;
117
+ case "TSMappedType":
118
+ addPattern(node.key, node);
119
+ break;
120
+ }
121
+ };
122
+ const collect = (node) => {
123
+ if (index.collected.has(node))
124
+ return;
125
+ index.collected.add(node);
126
+ index.declarations.push(node);
127
+ };
128
+ const initialize = () => {
129
+ if (index.initialized)
130
+ return;
131
+ index.initialized = true;
132
+ for (const declaration of index.declarations)
133
+ buildBinding(declaration);
134
+ index.declarations.length = 0;
135
+ };
136
+ const write = (node) => {
137
+ if (node.type === "Identifier")
138
+ index.writes.add(node.name);
139
+ else if (node.type === "ObjectPattern") {
140
+ for (const property of node.properties)
141
+ write(property.type === "RestElement" ? property.argument : property.value);
142
+ }
143
+ else if (node.type === "ArrayPattern") {
144
+ for (const element of node.elements)
145
+ if (element !== null)
146
+ write(element);
147
+ }
148
+ else if (node.type === "AssignmentPattern")
149
+ write(node.left);
150
+ else if (node.type === "RestElement")
151
+ write(node.argument);
152
+ else if (node.type === "ParenthesizedExpression" || node.type === "TSAsExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression")
153
+ write(node.expression);
154
+ };
155
+ const collectWrite = (node) => {
156
+ if (index.collectedWrites.has(node))
157
+ return;
158
+ index.collectedWrites.add(node);
159
+ index.writeTargets.push(node);
160
+ };
161
+ const visitor = {
162
+ Program(program) {
163
+ pending = [];
164
+ const cached = indexes.get(program);
165
+ index = cached ?? { bindings: new Map(), collected: new WeakSet(), writes: new Set(), declarations: [], writeTargets: [], collectedWrites: new WeakSet(), initialized: false, writesInitialized: false };
166
+ if (cached === undefined)
167
+ indexes.set(program, index);
168
+ },
169
+ "Program:exit"() {
170
+ for (const check of pending)
171
+ check();
172
+ pending = [];
173
+ },
174
+ AssignmentExpression(node) { collectWrite(node.left); },
175
+ UpdateExpression(node) { collectWrite(node.argument); },
176
+ ForInStatement(node) { if (node.left.type !== "VariableDeclaration")
177
+ collectWrite(node.left); },
178
+ ForOfStatement(node) { if (node.left.type !== "VariableDeclaration")
179
+ collectWrite(node.left); },
180
+ };
181
+ for (const event of ["VariableDeclarator", "ArrowFunctionExpression", "FunctionDeclaration", "FunctionExpression", "TSDeclareFunction", "TSEmptyBodyFunctionExpression", "TSFunctionType", "TSConstructorType", "TSCallSignatureDeclaration", "TSConstructSignatureDeclaration", "TSMethodSignature", "ClassDeclaration", "ClassExpression", "TSTypeAliasDeclaration", "TSInterfaceDeclaration", "TSEnumDeclaration", "TSEnumMember", "TSModuleDeclaration", "TSImportEqualsDeclaration", "CatchClause", "ImportSpecifier", "ImportDefaultSpecifier", "ImportNamespaceSpecifier", "TSTypeParameter", "TSMappedType"])
182
+ visitor[event] = collect;
183
+ return {
184
+ visitor,
185
+ defer(check) { pending.push(check); },
186
+ getBindings(name) { initialize(); return index.bindings.get(name) ?? []; },
187
+ hasWrites(name) {
188
+ if (!index.writesInitialized) {
189
+ index.writesInitialized = true;
190
+ for (const target of index.writeTargets)
191
+ write(target);
192
+ index.writeTargets.length = 0;
193
+ }
194
+ return index.writes.has(name);
195
+ },
196
+ uniqueVisibleBinding(identifier) {
197
+ if (identifier.name === "arguments")
198
+ return null;
199
+ initialize();
200
+ const bindings = index.bindings.get(identifier.name);
201
+ if (bindings === undefined)
202
+ return null;
203
+ const scoped = [];
204
+ for (const binding of bindings) {
205
+ const scope = bindingScope(binding);
206
+ if (scope === null)
207
+ return null;
208
+ scoped.push({ binding, scope });
209
+ }
210
+ // A function's parameters and body declarations can share one variable.
211
+ // Leave these merged-definition cases to the full scope manager.
212
+ for (const { scope } of scoped) {
213
+ if ((scope.type === "FunctionDeclaration" || scope.type === "FunctionExpression" || scope.type === "ArrowFunctionExpression") && scoped.some(candidate => candidate.scope === scope.body))
214
+ return null;
215
+ }
216
+ const distances = new Map();
217
+ let current = identifier;
218
+ while (current !== null) {
219
+ const parent = current.parent;
220
+ if ((parent?.type === "FunctionDeclaration" || parent?.type === "FunctionExpression" || parent?.type === "ArrowFunctionExpression") && current !== parent.body)
221
+ return null;
222
+ distances.set(current, distances.size);
223
+ current = parent;
224
+ }
225
+ let nearest = null;
226
+ let nearestDistance = Number.POSITIVE_INFINITY;
227
+ for (const { binding, scope } of scoped) {
228
+ const distance = distances.get(scope);
229
+ if (distance === undefined || distance > nearestDistance)
230
+ continue;
231
+ nearest = distance === nearestDistance ? null : binding;
232
+ nearestDistance = distance;
233
+ }
234
+ return nearest;
235
+ },
236
+ isUnbound(name) { initialize(); return name !== "arguments" && !index.bindings.has(name); },
237
+ hasImport(name, accepts) {
238
+ initialize();
239
+ return index.bindings.get(name)?.some(({ owner }) => owner.type === "ImportSpecifier" && owner.parent.type === "ImportDeclaration" && accepts(owner, owner.parent)) ?? false;
240
+ },
241
+ isUnshadowedImport(name, accepts) {
242
+ if (name === "arguments")
243
+ return false;
244
+ initialize();
245
+ const bindings = index.bindings.get(name);
246
+ if (bindings?.length !== 1)
247
+ return false;
248
+ const owner = bindings[0]?.owner;
249
+ return owner?.type === "ImportSpecifier" && owner.parent.type === "ImportDeclaration" && owner.parent.parent.type === "Program" && accepts(owner, owner.parent);
250
+ },
251
+ };
252
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-deslop",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
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",