@commonplaces/oxlint-plugin 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +130 -2
- package/dist/alchemy.d.ts +2 -0
- package/dist/alchemy.js +196 -0
- package/dist/anti-slop/effect/index.d.ts +3 -0
- package/dist/anti-slop/effect/index.js +10 -0
- package/dist/anti-slop/effect/rules/no-service-constructor-imports.d.ts +2 -0
- package/dist/anti-slop/effect/rules/no-service-constructor-imports.js +45 -0
- package/dist/anti-slop/index.d.ts +3 -0
- package/dist/anti-slop/index.js +38 -0
- package/dist/anti-slop/rules/no-chained-type-assertions.d.ts +2 -0
- package/dist/anti-slop/rules/no-chained-type-assertions.js +57 -0
- package/dist/anti-slop/rules/no-conditional-empty-object-spread.d.ts +2 -0
- package/dist/anti-slop/rules/no-conditional-empty-object-spread.js +36 -0
- package/dist/anti-slop/rules/no-known-value-widening.d.ts +2 -0
- package/dist/anti-slop/rules/no-known-value-widening.js +171 -0
- package/dist/anti-slop/rules/no-module-mocking.d.ts +2 -0
- package/dist/anti-slop/rules/no-module-mocking.js +73 -0
- package/dist/anti-slop/rules/no-object-parameters.d.ts +2 -0
- package/dist/anti-slop/rules/no-object-parameters.js +86 -0
- package/dist/anti-slop/rules/no-reflect-apply.d.ts +2 -0
- package/dist/anti-slop/rules/no-reflect-apply.js +25 -0
- package/dist/anti-slop/rules/no-reflect-get.d.ts +2 -0
- package/dist/anti-slop/rules/no-reflect-get.js +25 -0
- package/dist/anti-slop/rules/no-runtime-typeof.d.ts +2 -0
- package/dist/anti-slop/rules/no-runtime-typeof.js +47 -0
- package/dist/anti-slop/rules/no-shape-in-symbol-names.d.ts +2 -0
- package/dist/anti-slop/rules/no-shape-in-symbol-names.js +33 -0
- package/dist/anti-slop/rules/no-unknown-parameters.d.ts +2 -0
- package/dist/anti-slop/rules/no-unknown-parameters.js +69 -0
- package/dist/anti-slop/rules/no-unknown-returns.d.ts +2 -0
- package/dist/anti-slop/rules/no-unknown-returns.js +77 -0
- package/dist/anti-slop/rules/no-unknown-type-aliases.d.ts +2 -0
- package/dist/anti-slop/rules/no-unknown-type-aliases.js +58 -0
- package/dist/anti-slop/rules/no-unsafe-dictionary-type.d.ts +2 -0
- package/dist/anti-slop/rules/no-unsafe-dictionary-type.js +111 -0
- package/dist/anti-slop/rules/no-widen-then-assert.d.ts +2 -0
- package/dist/anti-slop/rules/no-widen-then-assert.js +255 -0
- package/dist/anti-slop/rules/require-safety-comment-for-type-assertion.d.ts +2 -0
- package/dist/anti-slop/rules/require-safety-comment-for-type-assertion.js +51 -0
- package/dist/anti-slop/shared/dictionary-types.d.ts +21 -0
- package/dist/anti-slop/shared/dictionary-types.js +374 -0
- package/dist/anti-slop/shared/lexical-type-parameters.d.ts +3 -0
- package/dist/anti-slop/shared/lexical-type-parameters.js +25 -0
- package/dist/anti-slop/shared/reflect-method.d.ts +3 -0
- package/dist/anti-slop/shared/reflect-method.js +24 -0
- package/dist/bun.d.ts +9 -0
- package/dist/bun.js +30 -0
- package/dist/effect.d.ts +17 -0
- package/dist/effect.js +352 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +62 -0
- package/dist/package.d.ts +9 -0
- package/dist/package.js +50 -0
- package/dist/strict-effect.d.ts +2 -0
- package/dist/strict-effect.js +2381 -0
- package/dist/syntax.d.ts +5 -0
- package/dist/syntax.js +26 -0
- package/package.json +77 -4
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { Array, HashMap, HashSet, Option } from "effect";
|
|
2
|
+
const BUILT_INS = HashSet.fromIterable([
|
|
3
|
+
"Record",
|
|
4
|
+
"Readonly",
|
|
5
|
+
"Partial",
|
|
6
|
+
"Required",
|
|
7
|
+
"Pick",
|
|
8
|
+
"Omit",
|
|
9
|
+
"PropertyKey",
|
|
10
|
+
"NonNullable",
|
|
11
|
+
]);
|
|
12
|
+
const TRANSPARENT_WRAPPERS = HashSet.fromIterable([
|
|
13
|
+
"Readonly",
|
|
14
|
+
"Partial",
|
|
15
|
+
"Required",
|
|
16
|
+
"NonNullable",
|
|
17
|
+
]);
|
|
18
|
+
function declaredStatement(statement) {
|
|
19
|
+
return statement.type === "ExportNamedDeclaration" ||
|
|
20
|
+
statement.type === "ExportDefaultDeclaration"
|
|
21
|
+
? (statement.declaration ?? null)
|
|
22
|
+
: statement;
|
|
23
|
+
}
|
|
24
|
+
export function createTypeEnvironment(program) {
|
|
25
|
+
const empty = {
|
|
26
|
+
aliases: HashMap.empty(),
|
|
27
|
+
interfaces: HashMap.empty(),
|
|
28
|
+
shadowedBuiltIns: HashSet.empty(),
|
|
29
|
+
};
|
|
30
|
+
return Array.reduce(program.body, empty, (environment, statement) => {
|
|
31
|
+
const declaration = declaredStatement(statement);
|
|
32
|
+
if (declaration?.type === "ImportDeclaration") {
|
|
33
|
+
return {
|
|
34
|
+
...environment,
|
|
35
|
+
shadowedBuiltIns: HashSet.union(environment.shadowedBuiltIns, HashSet.fromIterable(Array.filter(Array.map(declaration.specifiers, (specifier) => specifier.local.name), (name) => HashSet.has(BUILT_INS, name)))),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (declaration?.type === "TSTypeAliasDeclaration") {
|
|
39
|
+
const name = declaration.id.name;
|
|
40
|
+
const duplicate = HashMap.has(environment.aliases, name);
|
|
41
|
+
return {
|
|
42
|
+
...environment,
|
|
43
|
+
aliases: duplicate
|
|
44
|
+
? environment.aliases
|
|
45
|
+
: HashMap.set(environment.aliases, name, declaration),
|
|
46
|
+
shadowedBuiltIns: duplicate || HashSet.has(BUILT_INS, name)
|
|
47
|
+
? HashSet.add(environment.shadowedBuiltIns, name)
|
|
48
|
+
: environment.shadowedBuiltIns,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (declaration?.type === "TSInterfaceDeclaration") {
|
|
52
|
+
const name = declaration.id.name;
|
|
53
|
+
const declarations = Option.getOrElse(HashMap.get(environment.interfaces, name), () => []);
|
|
54
|
+
return {
|
|
55
|
+
...environment,
|
|
56
|
+
interfaces: HashMap.set(environment.interfaces, name, [...declarations, declaration]),
|
|
57
|
+
shadowedBuiltIns: HashSet.has(BUILT_INS, name)
|
|
58
|
+
? HashSet.add(environment.shadowedBuiltIns, name)
|
|
59
|
+
: environment.shadowedBuiltIns,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if ((declaration?.type === "TSEnumDeclaration" ||
|
|
63
|
+
declaration?.type === "ClassDeclaration" ||
|
|
64
|
+
declaration?.type === "FunctionDeclaration") &&
|
|
65
|
+
declaration.id !== null &&
|
|
66
|
+
HashSet.has(BUILT_INS, declaration.id.name)) {
|
|
67
|
+
return {
|
|
68
|
+
...environment,
|
|
69
|
+
shadowedBuiltIns: HashSet.add(environment.shadowedBuiltIns, declaration.id.name),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return environment;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function typeReferenceName(type) {
|
|
76
|
+
return type.typeName.type === "Identifier" ? type.typeName.name : null;
|
|
77
|
+
}
|
|
78
|
+
function isBuiltIn(name, environment) {
|
|
79
|
+
return HashSet.has(BUILT_INS, name) && !HashSet.has(environment.shadowedBuiltIns, name);
|
|
80
|
+
}
|
|
81
|
+
function isUnappliedReferenceTo(type, name) {
|
|
82
|
+
const unwrapped = unwrapTransparentType(type);
|
|
83
|
+
return (unwrapped.type === "TSTypeReference" &&
|
|
84
|
+
typeReferenceName(unwrapped) === name &&
|
|
85
|
+
(unwrapped.typeArguments === null ||
|
|
86
|
+
unwrapped.typeArguments === undefined ||
|
|
87
|
+
unwrapped.typeArguments.params.length === 0));
|
|
88
|
+
}
|
|
89
|
+
function unwrapTransparentType(type) {
|
|
90
|
+
const current = type;
|
|
91
|
+
if (current.type === "TSParenthesizedType" ||
|
|
92
|
+
(current.type === "TSTypeOperator" && current.operator === "readonly")) {
|
|
93
|
+
return unwrapTransparentType(current.typeAnnotation);
|
|
94
|
+
}
|
|
95
|
+
return current;
|
|
96
|
+
}
|
|
97
|
+
function isNeverType(type) {
|
|
98
|
+
return unwrapTransparentType(type).type === "TSNeverKeyword";
|
|
99
|
+
}
|
|
100
|
+
function isEffectivelyEmptyMember(member) {
|
|
101
|
+
return (member.type === "TSPropertySignature" &&
|
|
102
|
+
member.optional === true &&
|
|
103
|
+
member.typeAnnotation !== null &&
|
|
104
|
+
member.typeAnnotation !== undefined &&
|
|
105
|
+
isNeverType(member.typeAnnotation.typeAnnotation));
|
|
106
|
+
}
|
|
107
|
+
function isEffectivelyEmptyTypeLiteral(type) {
|
|
108
|
+
return type.members.length === 0 || Array.every(type.members, isEffectivelyEmptyMember);
|
|
109
|
+
}
|
|
110
|
+
function isEffectivelyEmptyInterface(declarations) {
|
|
111
|
+
if (declarations.length !== 1)
|
|
112
|
+
return false;
|
|
113
|
+
const [type] = declarations;
|
|
114
|
+
return (type !== undefined &&
|
|
115
|
+
type.extends.length === 0 &&
|
|
116
|
+
(type.body.body.length === 0 || Array.every(type.body.body, isEffectivelyEmptyMember)));
|
|
117
|
+
}
|
|
118
|
+
function resolvedSubstitutionArgument(type, base, resolving = HashSet.empty()) {
|
|
119
|
+
const unwrapped = unwrapTransparentType(type);
|
|
120
|
+
if (unwrapped.type !== "TSTypeReference")
|
|
121
|
+
return type;
|
|
122
|
+
const name = typeReferenceName(unwrapped);
|
|
123
|
+
if (name === null || HashSet.has(resolving, name))
|
|
124
|
+
return type;
|
|
125
|
+
const substitution = Option.getOrUndefined(HashMap.get(base, name));
|
|
126
|
+
if (substitution === undefined)
|
|
127
|
+
return type;
|
|
128
|
+
const nextResolving = HashSet.add(resolving, name);
|
|
129
|
+
return resolvedSubstitutionArgument(substitution, base, nextResolving);
|
|
130
|
+
}
|
|
131
|
+
function aliasSubstitution(alias, type, base) {
|
|
132
|
+
const parameters = alias.typeParameters?.params ?? [];
|
|
133
|
+
const arguments_ = type.typeArguments?.params ?? [];
|
|
134
|
+
return Option.getOrNull(Array.reduce(parameters, Option.some(base), (result, parameter, index) => Option.flatMap(result, (next) => Option.fromNullishOr(Option.getOrUndefined(Array.get(arguments_, index)) ?? parameter.default).pipe(Option.map((argument) => HashMap.set(next, parameter.name.name, resolvedSubstitutionArgument(argument, next)))))));
|
|
135
|
+
}
|
|
136
|
+
function unsafeDirectValue(type, environment, substitutions, resolvingAliases) {
|
|
137
|
+
const unwrapped = unwrapTransparentType(type);
|
|
138
|
+
if (unwrapped.type === "TSUnknownKeyword")
|
|
139
|
+
return "unknown";
|
|
140
|
+
if (unwrapped.type === "TSAnyKeyword")
|
|
141
|
+
return "any";
|
|
142
|
+
if (unwrapped.type === "TSObjectKeyword")
|
|
143
|
+
return "object";
|
|
144
|
+
if (unwrapped.type === "TSTypeLiteral" && isEffectivelyEmptyTypeLiteral(unwrapped))
|
|
145
|
+
return "empty-object";
|
|
146
|
+
if (unwrapped.type === "TSUnionType") {
|
|
147
|
+
return Array.some(unwrapped.types, (member) => unsafeDirectValue(member, environment, substitutions, resolvingAliases) !== null)
|
|
148
|
+
? "union"
|
|
149
|
+
: null;
|
|
150
|
+
}
|
|
151
|
+
if (unwrapped.type === "TSIntersectionType") {
|
|
152
|
+
const unsafeMembers = Array.map(unwrapped.types, (member) => unsafeDirectValue(member, environment, substitutions, resolvingAliases));
|
|
153
|
+
if (Array.contains(unsafeMembers, "any"))
|
|
154
|
+
return "any";
|
|
155
|
+
return unsafeMembers.length > 0 && Array.every(unsafeMembers, (member) => member !== null)
|
|
156
|
+
? (Option.getOrUndefined(Array.head(unsafeMembers)) ?? null)
|
|
157
|
+
: null;
|
|
158
|
+
}
|
|
159
|
+
if (unwrapped.type !== "TSTypeReference")
|
|
160
|
+
return null;
|
|
161
|
+
const name = typeReferenceName(unwrapped);
|
|
162
|
+
if (name === null)
|
|
163
|
+
return null;
|
|
164
|
+
if (HashSet.has(TRANSPARENT_WRAPPERS, name) && isBuiltIn(name, environment)) {
|
|
165
|
+
const wrapped = Option.getOrUndefined(Array.head(unwrapped.typeArguments?.params ?? []));
|
|
166
|
+
return wrapped === undefined
|
|
167
|
+
? null
|
|
168
|
+
: unsafeDirectValue(wrapped, environment, substitutions, resolvingAliases);
|
|
169
|
+
}
|
|
170
|
+
const substitution = Option.getOrUndefined(HashMap.get(substitutions, name));
|
|
171
|
+
if (substitution !== undefined) {
|
|
172
|
+
return isUnappliedReferenceTo(substitution, name)
|
|
173
|
+
? null
|
|
174
|
+
: unsafeDirectValue(substitution, environment, substitutions, resolvingAliases);
|
|
175
|
+
}
|
|
176
|
+
const interfaceDeclarations = Option.getOrUndefined(HashMap.get(environment.interfaces, name));
|
|
177
|
+
if (interfaceDeclarations !== undefined) {
|
|
178
|
+
return isEffectivelyEmptyInterface(interfaceDeclarations) ? "empty-object" : null;
|
|
179
|
+
}
|
|
180
|
+
const alias = Option.getOrUndefined(HashMap.get(environment.aliases, name));
|
|
181
|
+
if (alias === undefined || HashSet.has(resolvingAliases, name))
|
|
182
|
+
return null;
|
|
183
|
+
const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
|
|
184
|
+
if (nextSubstitutions === null)
|
|
185
|
+
return null;
|
|
186
|
+
const nextResolving = HashSet.add(resolvingAliases, name);
|
|
187
|
+
return unsafeDirectValue(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
|
|
188
|
+
}
|
|
189
|
+
function dictionaryValueTypes(type, environment, substitutions, resolvingAliases) {
|
|
190
|
+
const unwrapped = unwrapTransparentType(type);
|
|
191
|
+
if (unwrapped.type === "TSTypeLiteral") {
|
|
192
|
+
return Array.flatMap(unwrapped.members, (member) => member.type === "TSIndexSignature" && member.typeAnnotation !== null
|
|
193
|
+
? [{ type: member.typeAnnotation.typeAnnotation, substitutions }]
|
|
194
|
+
: []);
|
|
195
|
+
}
|
|
196
|
+
if (unwrapped.type === "TSMappedType") {
|
|
197
|
+
return unwrapped.typeAnnotation === null
|
|
198
|
+
? []
|
|
199
|
+
: [{ type: unwrapped.typeAnnotation, substitutions }];
|
|
200
|
+
}
|
|
201
|
+
if (unwrapped.type !== "TSTypeReference")
|
|
202
|
+
return [];
|
|
203
|
+
const name = typeReferenceName(unwrapped);
|
|
204
|
+
if (name === null)
|
|
205
|
+
return [];
|
|
206
|
+
const substitution = Option.getOrUndefined(HashMap.get(substitutions, name));
|
|
207
|
+
if (substitution !== undefined) {
|
|
208
|
+
return isUnappliedReferenceTo(substitution, name)
|
|
209
|
+
? []
|
|
210
|
+
: dictionaryValueTypes(substitution, environment, substitutions, resolvingAliases);
|
|
211
|
+
}
|
|
212
|
+
if (HashSet.has(TRANSPARENT_WRAPPERS, name) && isBuiltIn(name, environment)) {
|
|
213
|
+
const wrapped = Option.getOrUndefined(Array.head(unwrapped.typeArguments?.params ?? []));
|
|
214
|
+
return wrapped === undefined
|
|
215
|
+
? []
|
|
216
|
+
: dictionaryValueTypes(wrapped, environment, substitutions, resolvingAliases);
|
|
217
|
+
}
|
|
218
|
+
if (name === "Record" && isBuiltIn(name, environment)) {
|
|
219
|
+
const value = Option.getOrUndefined(Array.get(unwrapped.typeArguments?.params ?? [], 1)) ?? null;
|
|
220
|
+
return value === null ? [] : [{ type: value, substitutions }];
|
|
221
|
+
}
|
|
222
|
+
if ((name === "Pick" || name === "Omit") && isBuiltIn(name, environment)) {
|
|
223
|
+
const source = Option.getOrUndefined(Array.head(unwrapped.typeArguments?.params ?? []));
|
|
224
|
+
return source === undefined
|
|
225
|
+
? []
|
|
226
|
+
: dictionaryValueTypes(source, environment, substitutions, resolvingAliases);
|
|
227
|
+
}
|
|
228
|
+
const alias = Option.getOrUndefined(HashMap.get(environment.aliases, name));
|
|
229
|
+
if (alias === undefined || HashSet.has(resolvingAliases, name))
|
|
230
|
+
return [];
|
|
231
|
+
const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
|
|
232
|
+
if (nextSubstitutions === null)
|
|
233
|
+
return [];
|
|
234
|
+
const nextResolving = HashSet.add(resolvingAliases, name);
|
|
235
|
+
return dictionaryValueTypes(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
|
|
236
|
+
}
|
|
237
|
+
export function classifyUnsafeDictionaryValue(valueType, environment) {
|
|
238
|
+
const unsafeValue = unsafeDirectValue(valueType, environment, HashMap.empty(), HashSet.empty());
|
|
239
|
+
return unsafeValue === null ? null : { kind: "unsafe-dictionary", unsafeValue };
|
|
240
|
+
}
|
|
241
|
+
export function classifyUnsafeDictionary(type, environment) {
|
|
242
|
+
return Option.getOrNull(Array.findFirst(Array.map(dictionaryValueTypes(type, environment, HashMap.empty(), HashSet.empty()), (valueType) => Option.map(Option.fromNullishOr(unsafeDirectValue(valueType.type, environment, valueType.substitutions, HashSet.empty())), (unsafeValue) => ({ kind: "unsafe-dictionary", unsafeValue }))), Option.isSome).pipe(Option.flatten));
|
|
243
|
+
}
|
|
244
|
+
function resolvesToDictionary(type, environment, substitutions, resolvingAliases) {
|
|
245
|
+
return dictionaryValueTypes(type, environment, substitutions, resolvingAliases).length > 0;
|
|
246
|
+
}
|
|
247
|
+
export function classifyWideningTarget(type, environment) {
|
|
248
|
+
const unwrapped = unwrapTransparentType(type);
|
|
249
|
+
if (unwrapped.type === "TSUnknownKeyword")
|
|
250
|
+
return { kind: "unknown" };
|
|
251
|
+
if (unwrapped.type === "TSObjectKeyword")
|
|
252
|
+
return { kind: "object" };
|
|
253
|
+
if (unwrapped.type === "TSTypeLiteral") {
|
|
254
|
+
return Array.some(unwrapped.members, (member) => member.type === "TSIndexSignature")
|
|
255
|
+
? { kind: "open dictionary" }
|
|
256
|
+
: unwrapped.members.length > 0
|
|
257
|
+
? { kind: "anonymous object" }
|
|
258
|
+
: null;
|
|
259
|
+
}
|
|
260
|
+
if (unwrapped.type === "TSMappedType")
|
|
261
|
+
return { kind: "open dictionary" };
|
|
262
|
+
if (unwrapped.type !== "TSTypeReference")
|
|
263
|
+
return null;
|
|
264
|
+
const name = typeReferenceName(unwrapped);
|
|
265
|
+
if (name === null)
|
|
266
|
+
return null;
|
|
267
|
+
if (HashSet.has(TRANSPARENT_WRAPPERS, name) && isBuiltIn(name, environment)) {
|
|
268
|
+
const wrapped = Option.getOrUndefined(Array.head(unwrapped.typeArguments?.params ?? []));
|
|
269
|
+
return wrapped === undefined ? null : classifyWideningTarget(wrapped, environment);
|
|
270
|
+
}
|
|
271
|
+
if (name === "Record" && isBuiltIn(name, environment))
|
|
272
|
+
return { kind: "open dictionary" };
|
|
273
|
+
const alias = Option.getOrUndefined(HashMap.get(environment.aliases, name));
|
|
274
|
+
if (alias === undefined)
|
|
275
|
+
return null;
|
|
276
|
+
if ((alias.typeParameters?.params.length ?? 0) > 0) {
|
|
277
|
+
const substitutions = aliasSubstitution(alias, unwrapped, HashMap.empty());
|
|
278
|
+
return substitutions !== null &&
|
|
279
|
+
resolvesToDictionary(alias.typeAnnotation, environment, substitutions, HashSet.fromIterable([name]))
|
|
280
|
+
? { kind: "generic container" }
|
|
281
|
+
: null;
|
|
282
|
+
}
|
|
283
|
+
const substitutions = aliasSubstitution(alias, unwrapped, HashMap.empty());
|
|
284
|
+
if (substitutions === null)
|
|
285
|
+
return null;
|
|
286
|
+
const resolved = classifyAliasBroadTarget(alias.typeAnnotation, environment, substitutions, HashSet.fromIterable([name]));
|
|
287
|
+
return resolved;
|
|
288
|
+
}
|
|
289
|
+
function isBroadMappedKey(type, environment, substitutions) {
|
|
290
|
+
const unwrapped = unwrapTransparentType(type);
|
|
291
|
+
if (unwrapped.type === "TSStringKeyword" ||
|
|
292
|
+
unwrapped.type === "TSNumberKeyword" ||
|
|
293
|
+
unwrapped.type === "TSSymbolKeyword") {
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
if (unwrapped.type === "TSUnionType") {
|
|
297
|
+
return Array.every(unwrapped.types, (member) => isBroadMappedKey(member, environment, substitutions));
|
|
298
|
+
}
|
|
299
|
+
if (unwrapped.type !== "TSTypeReference")
|
|
300
|
+
return false;
|
|
301
|
+
const name = typeReferenceName(unwrapped);
|
|
302
|
+
if (name === null)
|
|
303
|
+
return false;
|
|
304
|
+
const substitution = Option.getOrUndefined(HashMap.get(substitutions, name));
|
|
305
|
+
if (substitution !== undefined && !isUnappliedReferenceTo(substitution, name)) {
|
|
306
|
+
return isBroadMappedKey(substitution, environment, substitutions);
|
|
307
|
+
}
|
|
308
|
+
return name === "PropertyKey" && isBuiltIn(name, environment);
|
|
309
|
+
}
|
|
310
|
+
function classifyAliasBroadTarget(type, environment, substitutions, resolvingAliases) {
|
|
311
|
+
const unwrapped = unwrapTransparentType(type);
|
|
312
|
+
if (unwrapped.type === "TSUnknownKeyword")
|
|
313
|
+
return { kind: "unknown" };
|
|
314
|
+
if (unwrapped.type === "TSObjectKeyword")
|
|
315
|
+
return { kind: "object" };
|
|
316
|
+
if (unwrapped.type === "TSTypeLiteral") {
|
|
317
|
+
return Array.some(unwrapped.members, (member) => member.type === "TSIndexSignature")
|
|
318
|
+
? { kind: "open dictionary" }
|
|
319
|
+
: null;
|
|
320
|
+
}
|
|
321
|
+
if (unwrapped.type === "TSMappedType") {
|
|
322
|
+
return isBroadMappedKey(unwrapped.constraint, environment, substitutions)
|
|
323
|
+
? { kind: "open dictionary" }
|
|
324
|
+
: null;
|
|
325
|
+
}
|
|
326
|
+
if (unwrapped.type !== "TSTypeReference")
|
|
327
|
+
return null;
|
|
328
|
+
const name = typeReferenceName(unwrapped);
|
|
329
|
+
if (name === null)
|
|
330
|
+
return null;
|
|
331
|
+
const substitution = Option.getOrUndefined(HashMap.get(substitutions, name));
|
|
332
|
+
if (substitution !== undefined) {
|
|
333
|
+
return isUnappliedReferenceTo(substitution, name)
|
|
334
|
+
? null
|
|
335
|
+
: classifyAliasBroadTarget(substitution, environment, substitutions, resolvingAliases);
|
|
336
|
+
}
|
|
337
|
+
if (HashSet.has(TRANSPARENT_WRAPPERS, name) && isBuiltIn(name, environment)) {
|
|
338
|
+
const wrapped = Option.getOrUndefined(Array.head(unwrapped.typeArguments?.params ?? []));
|
|
339
|
+
return wrapped === undefined
|
|
340
|
+
? null
|
|
341
|
+
: classifyAliasBroadTarget(wrapped, environment, substitutions, resolvingAliases);
|
|
342
|
+
}
|
|
343
|
+
if (name === "Record" && isBuiltIn(name, environment)) {
|
|
344
|
+
return { kind: "open dictionary" };
|
|
345
|
+
}
|
|
346
|
+
const alias = Option.getOrUndefined(HashMap.get(environment.aliases, name));
|
|
347
|
+
if (alias === undefined || HashSet.has(resolvingAliases, name))
|
|
348
|
+
return null;
|
|
349
|
+
const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
|
|
350
|
+
if (nextSubstitutions === null)
|
|
351
|
+
return null;
|
|
352
|
+
const nextResolving = HashSet.add(resolvingAliases, name);
|
|
353
|
+
return classifyAliasBroadTarget(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
|
|
354
|
+
}
|
|
355
|
+
export function isKnownEvidenceExpression(expression) {
|
|
356
|
+
const current = expression;
|
|
357
|
+
if (current.type === "ParenthesizedExpression" ||
|
|
358
|
+
current.type === "TSAsExpression" ||
|
|
359
|
+
current.type === "TSTypeAssertion" ||
|
|
360
|
+
current.type === "TSNonNullExpression" ||
|
|
361
|
+
current.type === "TSSatisfiesExpression") {
|
|
362
|
+
return isKnownEvidenceExpression(current.expression);
|
|
363
|
+
}
|
|
364
|
+
if (current.type === "ObjectExpression")
|
|
365
|
+
return true;
|
|
366
|
+
return (current.type === "ArrayExpression" ||
|
|
367
|
+
current.type === "ArrowFunctionExpression" ||
|
|
368
|
+
current.type === "ClassExpression" ||
|
|
369
|
+
current.type === "FunctionExpression" ||
|
|
370
|
+
current.type === "NewExpression" ||
|
|
371
|
+
current.type === "Literal" ||
|
|
372
|
+
current.type === "TemplateLiteral" ||
|
|
373
|
+
current.type === "UnaryExpression");
|
|
374
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Array } from "effect";
|
|
2
|
+
import { childNodes } from "../../syntax.js";
|
|
3
|
+
const inferNames = (context, node) => [
|
|
4
|
+
...(node.type === "TSInferType" ? [node.typeParameter.name.name] : []),
|
|
5
|
+
...Array.flatMap(childNodes(context, node), (child) => inferNames(context, child)),
|
|
6
|
+
];
|
|
7
|
+
const namesAt = (context, current, descendant) => {
|
|
8
|
+
if (current === null || current.type === "Program")
|
|
9
|
+
return [];
|
|
10
|
+
return [
|
|
11
|
+
...("typeParameters" in current
|
|
12
|
+
? Array.map(current.typeParameters?.params ?? [], (parameter) => parameter.name.name)
|
|
13
|
+
: []),
|
|
14
|
+
...(current.type === "TSMappedType" &&
|
|
15
|
+
(descendant === current.nameType || descendant === current.typeAnnotation)
|
|
16
|
+
? [current.key.name]
|
|
17
|
+
: []),
|
|
18
|
+
...(current.type === "TSConditionalType" && descendant === current.trueType
|
|
19
|
+
? inferNames(context, current.extendsType)
|
|
20
|
+
: []),
|
|
21
|
+
...namesAt(context, current.parent, current),
|
|
22
|
+
];
|
|
23
|
+
};
|
|
24
|
+
/** Type binders in scope at a node, including mapped and inferred parameters. */
|
|
25
|
+
export const lexicalTypeParameterNames = (node, context) => namesAt(context, node, node);
|
|
@@ -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,24 @@
|
|
|
1
|
+
import { Option } from "effect";
|
|
2
|
+
import { variableInScope } from "../../syntax.js";
|
|
3
|
+
function resolveVariable(sourceCode, identifier) {
|
|
4
|
+
return Option.getOrNull(variableInScope(sourceCode.getScope(identifier), identifier.name));
|
|
5
|
+
}
|
|
6
|
+
function isGlobalReflect(sourceCode, expression) {
|
|
7
|
+
if (expression.type !== "Identifier" || expression.name !== "Reflect")
|
|
8
|
+
return false;
|
|
9
|
+
if (sourceCode.isGlobalReference(expression))
|
|
10
|
+
return true;
|
|
11
|
+
const variable = resolveVariable(sourceCode, expression);
|
|
12
|
+
return variable === null || variable.defs.length === 0;
|
|
13
|
+
}
|
|
14
|
+
/** Reports whether a call target names one method on the global Reflect object. */
|
|
15
|
+
export function isGlobalReflectMethodCall(sourceCode, callee, methodName) {
|
|
16
|
+
if (!("property" in callee) || !("object" in callee) || !("computed" in callee))
|
|
17
|
+
return false;
|
|
18
|
+
if (!isGlobalReflect(sourceCode, callee.object))
|
|
19
|
+
return false;
|
|
20
|
+
const property = callee.property;
|
|
21
|
+
return callee.computed
|
|
22
|
+
? property.type === "Literal" && property.value === methodName
|
|
23
|
+
: property.type === "Identifier" && property.name === methodName;
|
|
24
|
+
}
|
package/dist/bun.d.ts
ADDED
package/dist/bun.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineRule } from "@oxlint/plugins";
|
|
2
|
+
import { Array, Option } from "effect";
|
|
3
|
+
import { propertyName } from "./syntax.js";
|
|
4
|
+
const ambientMethods = ["argv", "env", "file", "spawn", "spawnSync", "write"];
|
|
5
|
+
const noAmbientHostApi = defineRule({
|
|
6
|
+
create(context) {
|
|
7
|
+
return {
|
|
8
|
+
MemberExpression(node) {
|
|
9
|
+
if (node.object?.type === "Identifier" &&
|
|
10
|
+
node.object.name === "Bun" &&
|
|
11
|
+
Option.exists(propertyName(node.property), (name) => Array.contains(ambientMethods, name))) {
|
|
12
|
+
context.report({ messageId: "noAmbientHostApi", node });
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
meta: {
|
|
18
|
+
docs: { description: "Disallow direct Bun host APIs in Effect-owned TypeScript." },
|
|
19
|
+
messages: {
|
|
20
|
+
noAmbientHostApi: "Use decoded input and Effect platform services instead of direct Bun environment, file or process APIs.",
|
|
21
|
+
},
|
|
22
|
+
type: "problem",
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
export default {
|
|
26
|
+
meta: { name: "bun" },
|
|
27
|
+
rules: {
|
|
28
|
+
"no-ambient-host-api": noAmbientHostApi,
|
|
29
|
+
},
|
|
30
|
+
};
|
package/dist/effect.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
meta: {
|
|
3
|
+
name: string;
|
|
4
|
+
};
|
|
5
|
+
rules: {
|
|
6
|
+
"no-ambient-process": import("@oxlint/plugins").Rule;
|
|
7
|
+
"no-async-await-promise": import("@oxlint/plugins").Rule;
|
|
8
|
+
"no-console": import("@oxlint/plugins").Rule;
|
|
9
|
+
"no-instanceof": import("@oxlint/plugins").Rule;
|
|
10
|
+
"no-manual-tag": import("@oxlint/plugins").Rule;
|
|
11
|
+
"no-raw-fetch": import("@oxlint/plugins").Rule;
|
|
12
|
+
"no-runtime-execution-outside-main": import("@oxlint/plugins").Rule;
|
|
13
|
+
"no-unchecked-json": import("@oxlint/plugins").Rule;
|
|
14
|
+
"require-service-operation-instrumentation": import("@oxlint/plugins").Rule;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export default _default;
|