ai-deslop 0.4.6 → 0.4.8
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 +48 -54
- package/dist/index.js +2 -0
- package/dist/rules/no-reexports-outside-entrypoints.d.ts +2 -0
- package/dist/rules/no-reexports-outside-entrypoints.js +51 -0
- package/dist/shared/lexical-bindings.d.ts +24 -0
- package/dist/shared/lexical-bindings.js +252 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
| `no-
|
|
38
|
-
| `no-object-
|
|
39
|
-
| `no-
|
|
40
|
-
| `no-
|
|
41
|
-
| `no-
|
|
42
|
-
| `no-unknown-
|
|
43
|
-
| `no-
|
|
44
|
-
| `no-
|
|
45
|
-
| `
|
|
46
|
-
| `no-
|
|
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 |
|
|
51
|
-
| --- | --- |
|
|
52
|
-
| `no-array-filter-map` |
|
|
53
|
-
| `no-reduce-accumulator-copy` |
|
|
54
|
-
| `no-reflect-apply` |
|
|
55
|
-
| `no-reflect-get` |
|
|
56
|
-
| `no-shape-in-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 |
|
|
61
|
-
| --- | --- |
|
|
62
|
-
| `no-call-only-assertions` |
|
|
63
|
-
| `no-module-mocking` |
|
|
64
|
-
| `no-poor-substring-tests` |
|
|
65
|
-
| `no-repeated-expect-call` |
|
|
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 |
|
|
70
|
-
| --- | --- |
|
|
71
|
-
| [`no-duplicate-consts`](docs/no-duplicate-consts.md) |
|
|
72
|
-
| [`no-duplicate-types`](docs/no-duplicate-types.md) |
|
|
73
|
-
| [`no-renamed-imports`](docs/no-renamed-imports.md) |
|
|
74
|
-
| [`
|
|
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 |
|
|
79
|
-
| --- | --- |
|
|
80
|
-
| `typescript/no-deprecated` |
|
|
81
|
-
| `typescript/no-require-imports` |
|
|
82
|
-
| `typescript/no-floating-promises` |
|
|
83
|
-
| `typescript/no-unnecessary-condition` |
|
|
84
|
-
| `no-unreachable` |
|
|
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,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: "Import this symbol directly from its source, or re-export it from a package entrypoint for external consumers.",
|
|
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
|
+
}
|