@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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +130 -2
  3. package/dist/alchemy.d.ts +2 -0
  4. package/dist/alchemy.js +196 -0
  5. package/dist/anti-slop/effect/index.d.ts +3 -0
  6. package/dist/anti-slop/effect/index.js +10 -0
  7. package/dist/anti-slop/effect/rules/no-service-constructor-imports.d.ts +2 -0
  8. package/dist/anti-slop/effect/rules/no-service-constructor-imports.js +45 -0
  9. package/dist/anti-slop/index.d.ts +3 -0
  10. package/dist/anti-slop/index.js +38 -0
  11. package/dist/anti-slop/rules/no-chained-type-assertions.d.ts +2 -0
  12. package/dist/anti-slop/rules/no-chained-type-assertions.js +57 -0
  13. package/dist/anti-slop/rules/no-conditional-empty-object-spread.d.ts +2 -0
  14. package/dist/anti-slop/rules/no-conditional-empty-object-spread.js +36 -0
  15. package/dist/anti-slop/rules/no-known-value-widening.d.ts +2 -0
  16. package/dist/anti-slop/rules/no-known-value-widening.js +171 -0
  17. package/dist/anti-slop/rules/no-module-mocking.d.ts +2 -0
  18. package/dist/anti-slop/rules/no-module-mocking.js +73 -0
  19. package/dist/anti-slop/rules/no-object-parameters.d.ts +2 -0
  20. package/dist/anti-slop/rules/no-object-parameters.js +86 -0
  21. package/dist/anti-slop/rules/no-reflect-apply.d.ts +2 -0
  22. package/dist/anti-slop/rules/no-reflect-apply.js +25 -0
  23. package/dist/anti-slop/rules/no-reflect-get.d.ts +2 -0
  24. package/dist/anti-slop/rules/no-reflect-get.js +25 -0
  25. package/dist/anti-slop/rules/no-runtime-typeof.d.ts +2 -0
  26. package/dist/anti-slop/rules/no-runtime-typeof.js +47 -0
  27. package/dist/anti-slop/rules/no-shape-in-symbol-names.d.ts +2 -0
  28. package/dist/anti-slop/rules/no-shape-in-symbol-names.js +33 -0
  29. package/dist/anti-slop/rules/no-unknown-parameters.d.ts +2 -0
  30. package/dist/anti-slop/rules/no-unknown-parameters.js +69 -0
  31. package/dist/anti-slop/rules/no-unknown-returns.d.ts +2 -0
  32. package/dist/anti-slop/rules/no-unknown-returns.js +77 -0
  33. package/dist/anti-slop/rules/no-unknown-type-aliases.d.ts +2 -0
  34. package/dist/anti-slop/rules/no-unknown-type-aliases.js +58 -0
  35. package/dist/anti-slop/rules/no-unsafe-dictionary-type.d.ts +2 -0
  36. package/dist/anti-slop/rules/no-unsafe-dictionary-type.js +111 -0
  37. package/dist/anti-slop/rules/no-widen-then-assert.d.ts +2 -0
  38. package/dist/anti-slop/rules/no-widen-then-assert.js +255 -0
  39. package/dist/anti-slop/rules/require-safety-comment-for-type-assertion.d.ts +2 -0
  40. package/dist/anti-slop/rules/require-safety-comment-for-type-assertion.js +51 -0
  41. package/dist/anti-slop/shared/dictionary-types.d.ts +21 -0
  42. package/dist/anti-slop/shared/dictionary-types.js +374 -0
  43. package/dist/anti-slop/shared/lexical-type-parameters.d.ts +3 -0
  44. package/dist/anti-slop/shared/lexical-type-parameters.js +25 -0
  45. package/dist/anti-slop/shared/reflect-method.d.ts +3 -0
  46. package/dist/anti-slop/shared/reflect-method.js +24 -0
  47. package/dist/bun.d.ts +9 -0
  48. package/dist/bun.js +30 -0
  49. package/dist/effect.d.ts +17 -0
  50. package/dist/effect.js +352 -0
  51. package/dist/index.d.ts +57 -0
  52. package/dist/index.js +62 -0
  53. package/dist/package.d.ts +9 -0
  54. package/dist/package.js +50 -0
  55. package/dist/strict-effect.d.ts +2 -0
  56. package/dist/strict-effect.js +2381 -0
  57. package/dist/syntax.d.ts +5 -0
  58. package/dist/syntax.js +26 -0
  59. 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,3 @@
1
+ import type { Context, ESTree } from "@oxlint/plugins";
2
+ /** Type binders in scope at a node, including mapped and inferred parameters. */
3
+ export declare const lexicalTypeParameterNames: (node: ESTree.Node, context: Context) => readonly string[];
@@ -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
@@ -0,0 +1,9 @@
1
+ declare const _default: {
2
+ meta: {
3
+ name: string;
4
+ };
5
+ rules: {
6
+ "no-ambient-host-api": import("@oxlint/plugins").Rule;
7
+ };
8
+ };
9
+ export default _default;
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
+ };
@@ -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;