ai-deslop 0.2.0 → 0.2.2
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 +35 -106
- package/dist/index.d.ts +18 -0
- package/dist/index.js +84 -0
- package/dist/rules/no-array-filter-map.d.ts +2 -0
- package/dist/rules/no-array-filter-map.js +30 -0
- package/dist/rules/no-call-only-assertions.d.ts +2 -0
- package/dist/rules/no-call-only-assertions.js +87 -0
- package/dist/rules/no-chained-type-assertions.d.ts +2 -0
- package/dist/rules/no-chained-type-assertions.js +60 -0
- package/dist/rules/no-conditional-empty-object-spread.d.ts +2 -0
- package/dist/rules/no-conditional-empty-object-spread.js +40 -0
- package/dist/rules/no-duplicate-types.d.ts +2 -0
- package/dist/rules/no-duplicate-types.js +143 -0
- package/dist/rules/no-known-value-widening.d.ts +2 -0
- package/dist/rules/no-known-value-widening.js +300 -0
- package/dist/rules/no-module-mocking.d.ts +2 -0
- package/dist/rules/no-module-mocking.js +69 -0
- package/dist/rules/no-object-parameters.d.ts +2 -0
- package/dist/rules/no-object-parameters.js +56 -0
- package/dist/rules/no-pass-through-type-alias.d.ts +2 -0
- package/dist/rules/no-pass-through-type-alias.js +41 -0
- package/dist/rules/no-reduce-accumulator-copy.d.ts +2 -0
- package/dist/rules/no-reduce-accumulator-copy.js +99 -0
- package/dist/rules/no-reflect-apply.d.ts +2 -0
- package/dist/rules/no-reflect-apply.js +25 -0
- package/dist/rules/no-reflect-get.d.ts +2 -0
- package/dist/rules/no-reflect-get.js +25 -0
- package/dist/rules/no-runtime-typeof.d.ts +2 -0
- package/dist/rules/no-runtime-typeof.js +64 -0
- package/dist/rules/no-shape-in-symbol-names.d.ts +2 -0
- package/dist/rules/no-shape-in-symbol-names.js +40 -0
- package/dist/rules/no-unknown-parameters.d.ts +2 -0
- package/dist/rules/no-unknown-parameters.js +51 -0
- package/dist/rules/no-unknown-returns.d.ts +2 -0
- package/dist/rules/no-unknown-returns.js +58 -0
- package/dist/rules/no-unknown-type-aliases.d.ts +2 -0
- package/dist/rules/no-unknown-type-aliases.js +40 -0
- package/dist/rules/no-unsafe-dictionary-type.d.ts +2 -0
- package/dist/rules/no-unsafe-dictionary-type.js +134 -0
- package/dist/rules/no-widen-then-assert.d.ts +2 -0
- package/dist/rules/no-widen-then-assert.js +270 -0
- package/dist/rules/require-safety-comment-for-type-assertion.d.ts +2 -0
- package/dist/rules/require-safety-comment-for-type-assertion.js +100 -0
- package/dist/shared/array-method.d.ts +12 -0
- package/dist/shared/array-method.js +84 -0
- package/dist/shared/dictionary-types.d.ts +20 -0
- package/dist/shared/dictionary-types.js +377 -0
- package/dist/shared/function-parameters.d.ts +8 -0
- package/dist/shared/function-parameters.js +40 -0
- package/dist/shared/lexical-type-parameters.d.ts +5 -0
- package/dist/shared/lexical-type-parameters.js +47 -0
- package/dist/shared/reflect-method.d.ts +3 -0
- package/dist/shared/reflect-method.js +20 -0
- package/dist/shared/scope.d.ts +3 -0
- package/dist/shared/scope.js +11 -0
- package/dist/shared/type-alias-resolution.d.ts +23 -0
- package/dist/shared/type-alias-resolution.js +162 -0
- package/dist/tailwind/candidates.d.ts +14 -0
- package/dist/tailwind/candidates.js +24 -0
- package/dist/tailwind/design-system.d.ts +3 -0
- package/dist/tailwind/design-system.js +41 -0
- package/dist/tailwind/rule.d.ts +3 -0
- package/dist/tailwind/rule.js +133 -0
- package/dist/tailwind/worker.d.ts +1 -0
- package/dist/tailwind/worker.js +13 -0
- package/package.json +16 -5
- package/src/index.ts +0 -87
- package/src/rules/no-array-filter-map.ts +0 -28
- package/src/rules/no-call-only-assertions.ts +0 -94
- package/src/rules/no-chained-type-assertions.ts +0 -77
- package/src/rules/no-conditional-empty-object-spread.ts +0 -49
- package/src/rules/no-known-value-widening.ts +0 -415
- package/src/rules/no-module-mocking.ts +0 -80
- package/src/rules/no-object-parameters.ts +0 -83
- package/src/rules/no-pass-through-type-alias.ts +0 -52
- package/src/rules/no-reduce-accumulator-copy.ts +0 -109
- package/src/rules/no-reflect-apply.ts +0 -28
- package/src/rules/no-reflect-get.ts +0 -28
- package/src/rules/no-runtime-typeof.ts +0 -77
- package/src/rules/no-shape-in-symbol-names.ts +0 -46
- package/src/rules/no-unknown-parameters.ts +0 -69
- package/src/rules/no-unknown-returns.ts +0 -82
- package/src/rules/no-unknown-type-aliases.ts +0 -54
- package/src/rules/no-unsafe-dictionary-type.ts +0 -154
- package/src/rules/no-widen-then-assert.ts +0 -366
- package/src/rules/require-safety-comment-for-type-assertion.ts +0 -131
- package/src/shared/array-method.ts +0 -94
- package/src/shared/dictionary-types.ts +0 -515
- package/src/shared/function-parameters.ts +0 -49
- package/src/shared/lexical-type-parameters.ts +0 -61
- package/src/shared/reflect-method.ts +0 -24
- package/src/shared/scope.ts +0 -15
- package/src/shared/type-alias-resolution.ts +0 -250
- package/src/tailwind/candidates.ts +0 -38
- package/src/tailwind/design-system.ts +0 -59
- package/src/tailwind/rule.ts +0 -126
- package/src/tailwind/worker.ts +0 -38
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { createTypeAliasEnvironment, hasVisibleTypeBinding, visibleTypeAlias, } from "./type-alias-resolution.js";
|
|
2
|
+
const BUILT_INS = new Set([
|
|
3
|
+
"Record",
|
|
4
|
+
"Readonly",
|
|
5
|
+
"Partial",
|
|
6
|
+
"Required",
|
|
7
|
+
"Pick",
|
|
8
|
+
"Omit",
|
|
9
|
+
"PropertyKey",
|
|
10
|
+
"NonNullable",
|
|
11
|
+
]);
|
|
12
|
+
const TRANSPARENT_WRAPPERS = new Set(["Readonly", "Partial", "Required", "NonNullable"]);
|
|
13
|
+
function declaredStatement(statement) {
|
|
14
|
+
return statement.type === "ExportNamedDeclaration" ||
|
|
15
|
+
statement.type === "ExportDefaultDeclaration"
|
|
16
|
+
? (statement.declaration ?? null)
|
|
17
|
+
: statement;
|
|
18
|
+
}
|
|
19
|
+
export function createTypeEnvironment(program, visitorKeys) {
|
|
20
|
+
const interfaces = new Map();
|
|
21
|
+
for (const statement of program.body) {
|
|
22
|
+
const declaration = declaredStatement(statement);
|
|
23
|
+
if (declaration?.type !== "TSInterfaceDeclaration")
|
|
24
|
+
continue;
|
|
25
|
+
const declarations = interfaces.get(declaration.id.name) ?? [];
|
|
26
|
+
declarations.push(declaration);
|
|
27
|
+
interfaces.set(declaration.id.name, declarations);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
interfaces,
|
|
31
|
+
typeAliases: createTypeAliasEnvironment(program, visitorKeys),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function typeReferenceName(type) {
|
|
35
|
+
return type.typeName.type === "Identifier" ? type.typeName.name : null;
|
|
36
|
+
}
|
|
37
|
+
function isBuiltIn(name, use, environment) {
|
|
38
|
+
return (BUILT_INS.has(name) &&
|
|
39
|
+
!hasVisibleTypeBinding(name, use, environment.typeAliases));
|
|
40
|
+
}
|
|
41
|
+
function isUnappliedReferenceTo(type, name) {
|
|
42
|
+
const unwrapped = unwrapTransparentType(type);
|
|
43
|
+
return (unwrapped.type === "TSTypeReference" &&
|
|
44
|
+
typeReferenceName(unwrapped) === name &&
|
|
45
|
+
(unwrapped.typeArguments === null ||
|
|
46
|
+
unwrapped.typeArguments === undefined ||
|
|
47
|
+
unwrapped.typeArguments.params.length === 0));
|
|
48
|
+
}
|
|
49
|
+
function unwrapTransparentType(type) {
|
|
50
|
+
let current = type;
|
|
51
|
+
while (current.type === "TSParenthesizedType" ||
|
|
52
|
+
(current.type === "TSTypeOperator" && current.operator === "readonly")) {
|
|
53
|
+
current = current.typeAnnotation;
|
|
54
|
+
}
|
|
55
|
+
return current;
|
|
56
|
+
}
|
|
57
|
+
function isNeverType(type) {
|
|
58
|
+
return unwrapTransparentType(type).type === "TSNeverKeyword";
|
|
59
|
+
}
|
|
60
|
+
function isEffectivelyEmptyMember(member) {
|
|
61
|
+
return (member.type === "TSPropertySignature" &&
|
|
62
|
+
member.optional === true &&
|
|
63
|
+
member.typeAnnotation !== null &&
|
|
64
|
+
member.typeAnnotation !== undefined &&
|
|
65
|
+
isNeverType(member.typeAnnotation.typeAnnotation));
|
|
66
|
+
}
|
|
67
|
+
function isEffectivelyEmptyTypeLiteral(type) {
|
|
68
|
+
return type.members.length === 0 || type.members.every(isEffectivelyEmptyMember);
|
|
69
|
+
}
|
|
70
|
+
function isEffectivelyEmptyInterface(declarations) {
|
|
71
|
+
if (declarations.length !== 1)
|
|
72
|
+
return false;
|
|
73
|
+
const [type] = declarations;
|
|
74
|
+
return (type !== undefined &&
|
|
75
|
+
type.extends.length === 0 &&
|
|
76
|
+
(type.body.body.length === 0 || type.body.body.every(isEffectivelyEmptyMember)));
|
|
77
|
+
}
|
|
78
|
+
function resolvedSubstitutionArgument(type, base, resolving = new Set()) {
|
|
79
|
+
const unwrapped = unwrapTransparentType(type);
|
|
80
|
+
if (unwrapped.type !== "TSTypeReference")
|
|
81
|
+
return type;
|
|
82
|
+
const name = typeReferenceName(unwrapped);
|
|
83
|
+
if (name === null || resolving.has(name))
|
|
84
|
+
return type;
|
|
85
|
+
const substitution = base.get(name);
|
|
86
|
+
if (substitution === undefined)
|
|
87
|
+
return type;
|
|
88
|
+
const nextResolving = new Set(resolving);
|
|
89
|
+
nextResolving.add(name);
|
|
90
|
+
return resolvedSubstitutionArgument(substitution, base, nextResolving);
|
|
91
|
+
}
|
|
92
|
+
function aliasSubstitution(alias, type, base) {
|
|
93
|
+
const parameters = alias.typeParameters?.params ?? [];
|
|
94
|
+
const arguments_ = type.typeArguments?.params ?? [];
|
|
95
|
+
const next = new Map(base);
|
|
96
|
+
for (const [index, parameter] of parameters.entries()) {
|
|
97
|
+
const argument = arguments_[index] ?? parameter.default;
|
|
98
|
+
if (argument === null || argument === undefined)
|
|
99
|
+
return null;
|
|
100
|
+
next.set(parameter.name.name, resolvedSubstitutionArgument(argument, next));
|
|
101
|
+
}
|
|
102
|
+
return next;
|
|
103
|
+
}
|
|
104
|
+
function unsafeDirectValue(type, environment, substitutions, resolvingAliases) {
|
|
105
|
+
const unwrapped = unwrapTransparentType(type);
|
|
106
|
+
if (unwrapped.type === "TSUnknownKeyword")
|
|
107
|
+
return "unknown";
|
|
108
|
+
if (unwrapped.type === "TSAnyKeyword")
|
|
109
|
+
return "any";
|
|
110
|
+
if (unwrapped.type === "TSObjectKeyword")
|
|
111
|
+
return "object";
|
|
112
|
+
if (unwrapped.type === "TSTypeLiteral" && isEffectivelyEmptyTypeLiteral(unwrapped))
|
|
113
|
+
return "empty-object";
|
|
114
|
+
if (unwrapped.type === "TSUnionType") {
|
|
115
|
+
return unwrapped.types.some((member) => unsafeDirectValue(member, environment, substitutions, resolvingAliases) !== null)
|
|
116
|
+
? "union"
|
|
117
|
+
: null;
|
|
118
|
+
}
|
|
119
|
+
if (unwrapped.type === "TSIntersectionType") {
|
|
120
|
+
const unsafeMembers = unwrapped.types.map((member) => unsafeDirectValue(member, environment, substitutions, resolvingAliases));
|
|
121
|
+
if (unsafeMembers.includes("any"))
|
|
122
|
+
return "any";
|
|
123
|
+
return unsafeMembers.length > 0 && unsafeMembers.every((member) => member !== null)
|
|
124
|
+
? unsafeMembers[0]
|
|
125
|
+
: null;
|
|
126
|
+
}
|
|
127
|
+
if (unwrapped.type !== "TSTypeReference")
|
|
128
|
+
return null;
|
|
129
|
+
const name = typeReferenceName(unwrapped);
|
|
130
|
+
if (name === null)
|
|
131
|
+
return null;
|
|
132
|
+
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, unwrapped, environment)) {
|
|
133
|
+
const wrapped = unwrapped.typeArguments?.params[0];
|
|
134
|
+
return wrapped === undefined
|
|
135
|
+
? null
|
|
136
|
+
: unsafeDirectValue(wrapped, environment, substitutions, resolvingAliases);
|
|
137
|
+
}
|
|
138
|
+
const substitution = substitutions.get(name);
|
|
139
|
+
if (substitution !== undefined) {
|
|
140
|
+
return isUnappliedReferenceTo(substitution, name)
|
|
141
|
+
? null
|
|
142
|
+
: unsafeDirectValue(substitution, environment, substitutions, resolvingAliases);
|
|
143
|
+
}
|
|
144
|
+
const interfaceDeclarations = environment.interfaces.get(name);
|
|
145
|
+
if (interfaceDeclarations !== undefined) {
|
|
146
|
+
return isEffectivelyEmptyInterface(interfaceDeclarations) ? "empty-object" : null;
|
|
147
|
+
}
|
|
148
|
+
const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
|
|
149
|
+
if (alias === null || resolvingAliases.has(name))
|
|
150
|
+
return null;
|
|
151
|
+
const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
|
|
152
|
+
if (nextSubstitutions === null)
|
|
153
|
+
return null;
|
|
154
|
+
const nextResolving = new Set(resolvingAliases);
|
|
155
|
+
nextResolving.add(name);
|
|
156
|
+
return unsafeDirectValue(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
|
|
157
|
+
}
|
|
158
|
+
function dictionaryValueTypes(type, environment, substitutions, resolvingAliases) {
|
|
159
|
+
const unwrapped = unwrapTransparentType(type);
|
|
160
|
+
if (unwrapped.type === "TSTypeLiteral") {
|
|
161
|
+
return unwrapped.members.flatMap((member) => member.type === "TSIndexSignature" && member.typeAnnotation !== null
|
|
162
|
+
? [{ type: member.typeAnnotation.typeAnnotation, substitutions }]
|
|
163
|
+
: []);
|
|
164
|
+
}
|
|
165
|
+
if (unwrapped.type === "TSMappedType") {
|
|
166
|
+
return unwrapped.typeAnnotation === null
|
|
167
|
+
? []
|
|
168
|
+
: [{ type: unwrapped.typeAnnotation, substitutions }];
|
|
169
|
+
}
|
|
170
|
+
if (unwrapped.type !== "TSTypeReference")
|
|
171
|
+
return [];
|
|
172
|
+
const name = typeReferenceName(unwrapped);
|
|
173
|
+
if (name === null)
|
|
174
|
+
return [];
|
|
175
|
+
const substitution = substitutions.get(name);
|
|
176
|
+
if (substitution !== undefined) {
|
|
177
|
+
return isUnappliedReferenceTo(substitution, name)
|
|
178
|
+
? []
|
|
179
|
+
: dictionaryValueTypes(substitution, environment, substitutions, resolvingAliases);
|
|
180
|
+
}
|
|
181
|
+
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, unwrapped, environment)) {
|
|
182
|
+
const wrapped = unwrapped.typeArguments?.params[0];
|
|
183
|
+
return wrapped === undefined
|
|
184
|
+
? []
|
|
185
|
+
: dictionaryValueTypes(wrapped, environment, substitutions, resolvingAliases);
|
|
186
|
+
}
|
|
187
|
+
if (name === "Record" && isBuiltIn(name, unwrapped, environment)) {
|
|
188
|
+
const value = unwrapped.typeArguments?.params[1] ?? null;
|
|
189
|
+
return value === null ? [] : [{ type: value, substitutions }];
|
|
190
|
+
}
|
|
191
|
+
if ((name === "Pick" || name === "Omit") &&
|
|
192
|
+
isBuiltIn(name, unwrapped, environment)) {
|
|
193
|
+
const source = unwrapped.typeArguments?.params[0];
|
|
194
|
+
return source === undefined
|
|
195
|
+
? []
|
|
196
|
+
: dictionaryValueTypes(source, environment, substitutions, resolvingAliases);
|
|
197
|
+
}
|
|
198
|
+
const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
|
|
199
|
+
if (alias === null || resolvingAliases.has(name))
|
|
200
|
+
return [];
|
|
201
|
+
const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
|
|
202
|
+
if (nextSubstitutions === null)
|
|
203
|
+
return [];
|
|
204
|
+
const nextResolving = new Set(resolvingAliases);
|
|
205
|
+
nextResolving.add(name);
|
|
206
|
+
return dictionaryValueTypes(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
|
|
207
|
+
}
|
|
208
|
+
export function classifyUnsafeDictionaryValue(valueType, environment) {
|
|
209
|
+
const unsafeValue = unsafeDirectValue(valueType, environment, new Map(), new Set());
|
|
210
|
+
return unsafeValue === null ? null : { kind: "unsafe-dictionary", unsafeValue };
|
|
211
|
+
}
|
|
212
|
+
export function classifyUnsafeDictionary(type, environment) {
|
|
213
|
+
for (const valueType of dictionaryValueTypes(type, environment, new Map(), new Set())) {
|
|
214
|
+
const unsafeValue = unsafeDirectValue(valueType.type, environment, valueType.substitutions, new Set());
|
|
215
|
+
if (unsafeValue !== null)
|
|
216
|
+
return { kind: "unsafe-dictionary", unsafeValue };
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
export function classifyWideningTarget(type, environment) {
|
|
221
|
+
const unwrapped = unwrapTransparentType(type);
|
|
222
|
+
if (unwrapped.type === "TSUnknownKeyword")
|
|
223
|
+
return { kind: "unknown" };
|
|
224
|
+
if (unwrapped.type === "TSObjectKeyword")
|
|
225
|
+
return { kind: "object" };
|
|
226
|
+
if (unwrapped.type === "TSTypeLiteral") {
|
|
227
|
+
return unwrapped.members.some((member) => member.type === "TSIndexSignature")
|
|
228
|
+
? { kind: "open dictionary" }
|
|
229
|
+
: unwrapped.members.length > 0
|
|
230
|
+
? { kind: "anonymous object" }
|
|
231
|
+
: null;
|
|
232
|
+
}
|
|
233
|
+
if (unwrapped.type === "TSMappedType")
|
|
234
|
+
return { kind: "open dictionary" };
|
|
235
|
+
if (unwrapped.type !== "TSTypeReference")
|
|
236
|
+
return null;
|
|
237
|
+
const name = typeReferenceName(unwrapped);
|
|
238
|
+
if (name === null)
|
|
239
|
+
return null;
|
|
240
|
+
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, unwrapped, environment)) {
|
|
241
|
+
const wrapped = unwrapped.typeArguments?.params[0];
|
|
242
|
+
return wrapped === undefined ? null : classifyWideningTarget(wrapped, environment);
|
|
243
|
+
}
|
|
244
|
+
if (name === "Record" && isBuiltIn(name, unwrapped, environment)) {
|
|
245
|
+
return hasBroadRecordKey(unwrapped, environment, new Map())
|
|
246
|
+
? { kind: "open dictionary" }
|
|
247
|
+
: null;
|
|
248
|
+
}
|
|
249
|
+
const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
|
|
250
|
+
if (alias === null)
|
|
251
|
+
return null;
|
|
252
|
+
if ((alias.typeParameters?.params.length ?? 0) > 0) {
|
|
253
|
+
const substitutions = aliasSubstitution(alias, unwrapped, new Map());
|
|
254
|
+
const resolved = substitutions === null
|
|
255
|
+
? null
|
|
256
|
+
: classifyAliasBroadTarget(alias.typeAnnotation, environment, substitutions, new Set([name]));
|
|
257
|
+
return resolved?.kind === "open dictionary" ? { kind: "generic container" } : null;
|
|
258
|
+
}
|
|
259
|
+
const substitutions = aliasSubstitution(alias, unwrapped, new Map());
|
|
260
|
+
if (substitutions === null)
|
|
261
|
+
return null;
|
|
262
|
+
const resolved = classifyAliasBroadTarget(alias.typeAnnotation, environment, substitutions, new Set([name]));
|
|
263
|
+
return resolved;
|
|
264
|
+
}
|
|
265
|
+
function hasBroadRecordKey(type, environment, substitutions) {
|
|
266
|
+
const key = type.typeArguments?.params[0];
|
|
267
|
+
return key === undefined || isBroadMappedKey(key, environment, substitutions);
|
|
268
|
+
}
|
|
269
|
+
function isBroadMappedKey(type, environment, substitutions, visitedAliases = new Set()) {
|
|
270
|
+
const unwrapped = unwrapTransparentType(type);
|
|
271
|
+
if (unwrapped.type === "TSStringKeyword" ||
|
|
272
|
+
unwrapped.type === "TSNumberKeyword" ||
|
|
273
|
+
unwrapped.type === "TSSymbolKeyword") {
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
if (unwrapped.type === "TSUnionType") {
|
|
277
|
+
return unwrapped.types.some((member) => isBroadMappedKey(member, environment, substitutions, visitedAliases));
|
|
278
|
+
}
|
|
279
|
+
if (unwrapped.type !== "TSTypeReference")
|
|
280
|
+
return false;
|
|
281
|
+
const name = typeReferenceName(unwrapped);
|
|
282
|
+
if (name === null)
|
|
283
|
+
return false;
|
|
284
|
+
const substitution = substitutions.get(name);
|
|
285
|
+
if (substitution !== undefined && !isUnappliedReferenceTo(substitution, name)) {
|
|
286
|
+
return isBroadMappedKey(substitution, environment, substitutions, visitedAliases);
|
|
287
|
+
}
|
|
288
|
+
if (name === "PropertyKey" && isBuiltIn(name, unwrapped, environment))
|
|
289
|
+
return true;
|
|
290
|
+
const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
|
|
291
|
+
if (alias === null ||
|
|
292
|
+
(alias.typeParameters?.params.length ?? 0) > 0 ||
|
|
293
|
+
visitedAliases.has(name)) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
const nextVisited = new Set(visitedAliases);
|
|
297
|
+
nextVisited.add(name);
|
|
298
|
+
return isBroadMappedKey(alias.typeAnnotation, environment, substitutions, nextVisited);
|
|
299
|
+
}
|
|
300
|
+
function classifyAliasBroadTarget(type, environment, substitutions, resolvingAliases) {
|
|
301
|
+
const unwrapped = unwrapTransparentType(type);
|
|
302
|
+
if (unwrapped.type === "TSUnknownKeyword")
|
|
303
|
+
return { kind: "unknown" };
|
|
304
|
+
if (unwrapped.type === "TSObjectKeyword")
|
|
305
|
+
return { kind: "object" };
|
|
306
|
+
if (unwrapped.type === "TSTypeLiteral") {
|
|
307
|
+
return unwrapped.members.some((member) => member.type === "TSIndexSignature")
|
|
308
|
+
? { kind: "open dictionary" }
|
|
309
|
+
: null;
|
|
310
|
+
}
|
|
311
|
+
if (unwrapped.type === "TSMappedType") {
|
|
312
|
+
return isBroadMappedKey(unwrapped.constraint, environment, substitutions)
|
|
313
|
+
? { kind: "open dictionary" }
|
|
314
|
+
: null;
|
|
315
|
+
}
|
|
316
|
+
if (unwrapped.type !== "TSTypeReference")
|
|
317
|
+
return null;
|
|
318
|
+
const name = typeReferenceName(unwrapped);
|
|
319
|
+
if (name === null)
|
|
320
|
+
return null;
|
|
321
|
+
const substitution = substitutions.get(name);
|
|
322
|
+
if (substitution !== undefined) {
|
|
323
|
+
return isUnappliedReferenceTo(substitution, name)
|
|
324
|
+
? null
|
|
325
|
+
: classifyAliasBroadTarget(substitution, environment, substitutions, resolvingAliases);
|
|
326
|
+
}
|
|
327
|
+
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, unwrapped, environment)) {
|
|
328
|
+
const wrapped = unwrapped.typeArguments?.params[0];
|
|
329
|
+
return wrapped === undefined
|
|
330
|
+
? null
|
|
331
|
+
: classifyAliasBroadTarget(wrapped, environment, substitutions, resolvingAliases);
|
|
332
|
+
}
|
|
333
|
+
if (name === "Record" && isBuiltIn(name, unwrapped, environment)) {
|
|
334
|
+
return hasBroadRecordKey(unwrapped, environment, substitutions)
|
|
335
|
+
? { kind: "open dictionary" }
|
|
336
|
+
: null;
|
|
337
|
+
}
|
|
338
|
+
const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
|
|
339
|
+
if (alias === null || resolvingAliases.has(name))
|
|
340
|
+
return null;
|
|
341
|
+
const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
|
|
342
|
+
if (nextSubstitutions === null)
|
|
343
|
+
return null;
|
|
344
|
+
const nextResolving = new Set(resolvingAliases);
|
|
345
|
+
nextResolving.add(name);
|
|
346
|
+
return classifyAliasBroadTarget(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
|
|
347
|
+
}
|
|
348
|
+
export function isPopulatedObjectExpression(expression) {
|
|
349
|
+
let current = expression;
|
|
350
|
+
while (current.type === "ParenthesizedExpression" ||
|
|
351
|
+
current.type === "TSAsExpression" ||
|
|
352
|
+
current.type === "TSTypeAssertion" ||
|
|
353
|
+
current.type === "TSNonNullExpression") {
|
|
354
|
+
current = current.expression;
|
|
355
|
+
}
|
|
356
|
+
return current.type === "ObjectExpression" && current.properties.length > 0;
|
|
357
|
+
}
|
|
358
|
+
export function isKnownEvidenceExpression(expression) {
|
|
359
|
+
let current = expression;
|
|
360
|
+
while (current.type === "ParenthesizedExpression" ||
|
|
361
|
+
current.type === "TSAsExpression" ||
|
|
362
|
+
current.type === "TSTypeAssertion" ||
|
|
363
|
+
current.type === "TSNonNullExpression" ||
|
|
364
|
+
current.type === "TSSatisfiesExpression") {
|
|
365
|
+
current = current.expression;
|
|
366
|
+
}
|
|
367
|
+
if (current.type === "ObjectExpression")
|
|
368
|
+
return true;
|
|
369
|
+
return (current.type === "ArrayExpression" ||
|
|
370
|
+
current.type === "ArrowFunctionExpression" ||
|
|
371
|
+
current.type === "ClassExpression" ||
|
|
372
|
+
current.type === "FunctionExpression" ||
|
|
373
|
+
current.type === "NewExpression" ||
|
|
374
|
+
current.type === "Literal" ||
|
|
375
|
+
current.type === "TemplateLiteral" ||
|
|
376
|
+
current.type === "UnaryExpression");
|
|
377
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ESTree, SourceCode } from "@oxlint/plugins";
|
|
2
|
+
export type FunctionParameter = ESTree.ParamPattern;
|
|
3
|
+
/** Return whether a type is or contains TypeScript's absorbing unknown top type. */
|
|
4
|
+
export declare function containsUnknownType(type: ESTree.TSType): boolean;
|
|
5
|
+
/** Return the TypeScript annotation attached to a function parameter or its wrapped binding. */
|
|
6
|
+
export declare function functionParameterTypeAnnotation(parameter: FunctionParameter): ESTree.TSTypeAnnotation | null | undefined;
|
|
7
|
+
/** Return only a function parameter's local binding, excluding its annotation and default value. */
|
|
8
|
+
export declare function functionParameterBindingName(parameter: FunctionParameter, sourceCode: SourceCode): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Return whether a type is or contains TypeScript's absorbing unknown top type. */
|
|
2
|
+
export function containsUnknownType(type) {
|
|
3
|
+
if (type.type === "TSUnknownKeyword")
|
|
4
|
+
return true;
|
|
5
|
+
if (type.type === "TSParenthesizedType")
|
|
6
|
+
return containsUnknownType(type.typeAnnotation);
|
|
7
|
+
return type.type === "TSUnionType" && type.types.some(containsUnknownType);
|
|
8
|
+
}
|
|
9
|
+
/** Return the TypeScript annotation attached to a function parameter or its wrapped binding. */
|
|
10
|
+
export function functionParameterTypeAnnotation(parameter) {
|
|
11
|
+
if (parameter.type === "TSParameterProperty") {
|
|
12
|
+
return functionParameterTypeAnnotation(parameter.parameter);
|
|
13
|
+
}
|
|
14
|
+
if (parameter.type === "RestElement") {
|
|
15
|
+
return parameter.typeAnnotation ?? functionParameterTypeAnnotation(parameter.argument);
|
|
16
|
+
}
|
|
17
|
+
if (parameter.type === "AssignmentPattern") {
|
|
18
|
+
return parameter.typeAnnotation ?? functionParameterTypeAnnotation(parameter.left);
|
|
19
|
+
}
|
|
20
|
+
return parameter.typeAnnotation;
|
|
21
|
+
}
|
|
22
|
+
/** Return only a function parameter's local binding, excluding its annotation and default value. */
|
|
23
|
+
export function functionParameterBindingName(parameter, sourceCode) {
|
|
24
|
+
if (parameter.type === "TSParameterProperty") {
|
|
25
|
+
return functionParameterBindingName(parameter.parameter, sourceCode);
|
|
26
|
+
}
|
|
27
|
+
if (parameter.type === "AssignmentPattern") {
|
|
28
|
+
return functionParameterBindingName(parameter.left, sourceCode);
|
|
29
|
+
}
|
|
30
|
+
if (parameter.type === "RestElement") {
|
|
31
|
+
return functionParameterBindingName(parameter.argument, sourceCode);
|
|
32
|
+
}
|
|
33
|
+
if (parameter.type === "Identifier")
|
|
34
|
+
return parameter.name;
|
|
35
|
+
const sourceText = sourceCode.getText(parameter);
|
|
36
|
+
const annotationStart = parameter.typeAnnotation?.start;
|
|
37
|
+
return annotationStart === undefined
|
|
38
|
+
? sourceText
|
|
39
|
+
: sourceText.slice(0, annotationStart - parameter.start).trimEnd();
|
|
40
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ESTree } from "@oxlint/plugins";
|
|
2
|
+
type VisitorKeys = Readonly<Record<string, readonly string[]>>;
|
|
3
|
+
/** Collect type binders that are in scope at a node and can shadow module aliases. */
|
|
4
|
+
export declare function lexicalTypeParameterNames(node: ESTree.Node, visitorKeys: VisitorKeys): ReadonlySet<string>;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
function isNode(value) {
|
|
2
|
+
return (typeof value === "object" &&
|
|
3
|
+
value !== null &&
|
|
4
|
+
"type" in value &&
|
|
5
|
+
typeof value.type === "string");
|
|
6
|
+
}
|
|
7
|
+
function collectInferTypeParameterNames(node, visitorKeys, names) {
|
|
8
|
+
if (node.type === "TSInferType")
|
|
9
|
+
names.add(node.typeParameter.name.name);
|
|
10
|
+
const record = node;
|
|
11
|
+
for (const key of visitorKeys[node.type] ?? []) {
|
|
12
|
+
const value = record[key];
|
|
13
|
+
if (isNode(value)) {
|
|
14
|
+
collectInferTypeParameterNames(value, visitorKeys, names);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (!Array.isArray(value))
|
|
18
|
+
continue;
|
|
19
|
+
for (const child of value) {
|
|
20
|
+
if (isNode(child))
|
|
21
|
+
collectInferTypeParameterNames(child, visitorKeys, names);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Collect type binders that are in scope at a node and can shadow module aliases. */
|
|
26
|
+
export function lexicalTypeParameterNames(node, visitorKeys) {
|
|
27
|
+
const names = new Set();
|
|
28
|
+
let descendant = node;
|
|
29
|
+
let current = node;
|
|
30
|
+
while (current !== null && current.type !== "Program") {
|
|
31
|
+
if ("typeParameters" in current) {
|
|
32
|
+
for (const parameter of current.typeParameters?.params ?? []) {
|
|
33
|
+
names.add(parameter.name.name);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (current.type === "TSMappedType" &&
|
|
37
|
+
(descendant === current.nameType || descendant === current.typeAnnotation)) {
|
|
38
|
+
names.add(current.key.name);
|
|
39
|
+
}
|
|
40
|
+
if (current.type === "TSConditionalType" && descendant === current.trueType) {
|
|
41
|
+
collectInferTypeParameterNames(current.extendsType, visitorKeys, names);
|
|
42
|
+
}
|
|
43
|
+
descendant = current;
|
|
44
|
+
current = current.parent;
|
|
45
|
+
}
|
|
46
|
+
return names;
|
|
47
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ESTree, SourceCode } from "@oxlint/plugins";
|
|
2
|
+
/** Reports whether a call target names one method on the global Reflect object. */
|
|
3
|
+
export declare function isGlobalReflectMethodCall(sourceCode: SourceCode, callee: ESTree.Expression, methodName: string): boolean;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { resolveVariable } from "./scope.js";
|
|
2
|
+
function isGlobalReflect(sourceCode, expression) {
|
|
3
|
+
if (expression.type !== "Identifier" || expression.name !== "Reflect")
|
|
4
|
+
return false;
|
|
5
|
+
if (sourceCode.isGlobalReference(expression))
|
|
6
|
+
return true;
|
|
7
|
+
const variable = resolveVariable(sourceCode, expression);
|
|
8
|
+
return variable === null || variable.defs.length === 0;
|
|
9
|
+
}
|
|
10
|
+
/** Reports whether a call target names one method on the global Reflect object. */
|
|
11
|
+
export function isGlobalReflectMethodCall(sourceCode, callee, methodName) {
|
|
12
|
+
if (!("property" in callee) || !("object" in callee) || !("computed" in callee))
|
|
13
|
+
return false;
|
|
14
|
+
if (!isGlobalReflect(sourceCode, callee.object))
|
|
15
|
+
return false;
|
|
16
|
+
const property = callee.property;
|
|
17
|
+
return callee.computed
|
|
18
|
+
? property.type === "Literal" && property.value === methodName
|
|
19
|
+
: property.type === "Identifier" && property.name === methodName;
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Resolve an identifier to its binding by walking lexical scopes upward. */
|
|
2
|
+
export function resolveVariable(sourceCode, identifier) {
|
|
3
|
+
let scope = sourceCode.getScope(identifier);
|
|
4
|
+
while (scope !== null) {
|
|
5
|
+
const variable = scope.set.get(identifier.name);
|
|
6
|
+
if (variable !== undefined)
|
|
7
|
+
return variable;
|
|
8
|
+
scope = scope.upper;
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ESTree } from "@oxlint/plugins";
|
|
2
|
+
type VisitorKeys = Readonly<Record<string, readonly string[]>>;
|
|
3
|
+
type TypeScope = ESTree.Node;
|
|
4
|
+
type TypeBinding = {
|
|
5
|
+
readonly alias: ESTree.TSTypeAliasDeclaration | null;
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly scope: TypeScope;
|
|
8
|
+
};
|
|
9
|
+
export type TypeAliasEnvironment = {
|
|
10
|
+
readonly aliases: readonly ESTree.TSTypeAliasDeclaration[];
|
|
11
|
+
readonly bindingsByName: ReadonlyMap<string, readonly TypeBinding[]>;
|
|
12
|
+
readonly visitorKeys: VisitorKeys;
|
|
13
|
+
};
|
|
14
|
+
export type ResolvedTypeMatcher = (type: ESTree.TSType, matches: (child: ESTree.TSType) => boolean) => boolean;
|
|
15
|
+
/** Collect every lexical type alias and competing type binding in a program. */
|
|
16
|
+
export declare function createTypeAliasEnvironment(program: ESTree.Program, visitorKeys: VisitorKeys): TypeAliasEnvironment;
|
|
17
|
+
/** Resolve the nearest visible alias with this name, respecting lexical shadowing. */
|
|
18
|
+
export declare function visibleTypeAlias(name: string, use: ESTree.Node, environment: TypeAliasEnvironment): ESTree.TSTypeAliasDeclaration | null;
|
|
19
|
+
/** Return whether a local declaration shadows a built-in type at this use. */
|
|
20
|
+
export declare function hasVisibleTypeBinding(name: string, use: ESTree.Node, environment: TypeAliasEnvironment): boolean;
|
|
21
|
+
/** Match a type after resolving visible aliases and substituting their type parameters. */
|
|
22
|
+
export declare function resolvedTypeMatches(type: ESTree.TSType, environment: TypeAliasEnvironment, matcher: ResolvedTypeMatcher): boolean;
|
|
23
|
+
export {};
|