@jameswyse/oxc-config 0.1.0 → 0.3.0

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 (50) hide show
  1. package/README.md +30 -163
  2. package/REFERENCE.md +105 -132
  3. package/dist/format.d.ts +5 -3
  4. package/dist/index.js +26 -0
  5. package/dist/lint.d.ts +3 -1
  6. package/dist/lint.js +153 -14
  7. package/dist/rules/explicit-api-states.js +7 -1
  8. package/dist/rules/import-boundaries.js +41 -13
  9. package/dist/rules/no-derived-test-expectation.d.ts +1 -0
  10. package/dist/rules/no-derived-test-expectation.js +136 -0
  11. package/dist/rules/no-disabled-tls-verification.d.ts +1 -0
  12. package/dist/rules/no-disabled-tls-verification.js +42 -0
  13. package/dist/rules/no-fixed-test-delay.js +46 -5
  14. package/dist/rules/no-generic-object-guard.d.ts +1 -0
  15. package/dist/rules/no-generic-object-guard.js +279 -0
  16. package/dist/rules/no-get-password-form.d.ts +1 -0
  17. package/dist/rules/no-get-password-form.js +113 -0
  18. package/dist/rules/no-hardcoded-credential.d.ts +1 -0
  19. package/dist/rules/no-hardcoded-credential.js +45 -0
  20. package/dist/rules/no-import-time-effects.d.ts +1 -0
  21. package/dist/rules/no-import-time-effects.js +90 -0
  22. package/dist/rules/no-optional-test-action.d.ts +1 -0
  23. package/dist/rules/no-optional-test-action.js +45 -0
  24. package/dist/rules/no-secret-fallback.d.ts +1 -0
  25. package/dist/rules/no-secret-fallback.js +64 -0
  26. package/dist/rules/no-swallowed-errors.js +131 -16
  27. package/dist/rules/no-test-timeout-overrides.js +22 -9
  28. package/dist/rules/no-unchecked-fetch-response.d.ts +1 -0
  29. package/dist/rules/no-unchecked-fetch-response.js +91 -0
  30. package/dist/rules/no-unchecked-index-search.d.ts +1 -0
  31. package/dist/rules/no-unchecked-index-search.js +41 -0
  32. package/dist/rules/no-uncontrolled-test-clock.d.ts +1 -0
  33. package/dist/rules/no-uncontrolled-test-clock.js +112 -0
  34. package/dist/rules/no-unsettled-promise.d.ts +1 -0
  35. package/dist/rules/no-unsettled-promise.js +64 -0
  36. package/dist/rules/require-suppression-reason.d.ts +1 -0
  37. package/dist/rules/require-suppression-reason.js +32 -0
  38. package/dist/shared/index.d.ts +9 -0
  39. package/dist/shared/index.js +70 -0
  40. package/dist/vendor/anti-slop/effect/rules/no-service-constructor-imports.js +10 -2
  41. package/dist/vendor/anti-slop/rules/no-known-value-widening.js +1 -1
  42. package/dist/vendor/anti-slop/rules/no-module-mocking.js +56 -1
  43. package/dist/vendor/anti-slop/rules/no-runtime-typeof.js +30 -1
  44. package/dist/vendor/anti-slop/rules/no-shape-in-symbol-names.js +28 -1
  45. package/dist/vendor/anti-slop/rules/no-unknown-returns.js +1 -1
  46. package/dist/vendor/anti-slop/rules/no-unsafe-dictionary-type.js +2 -2
  47. package/dist/vendor/anti-slop/shared/dictionary-types.js +72 -89
  48. package/dist/vendor/anti-slop/shared/type-alias-resolution.d.ts +8 -2
  49. package/dist/vendor/anti-slop/shared/type-alias-resolution.js +2 -2
  50. package/package.json +7 -7
@@ -1,4 +1,4 @@
1
- import { createTypeAliasEnvironment, hasVisibleTypeBinding, visibleTypeAlias, } from "./type-alias-resolution.js";
1
+ import { aliasSubstitutions, createTypeAliasEnvironment, hasVisibleTypeBinding, visibleTypeAlias, } from "./type-alias-resolution.js";
2
2
  const BUILT_INS = new Set([
3
3
  "Record",
4
4
  "Readonly",
@@ -35,6 +35,9 @@ function typeReferenceName(type) {
35
35
  function isBuiltIn(name, use, environment) {
36
36
  return BUILT_INS.has(name) && !hasVisibleTypeBinding(name, use, environment.typeAliases);
37
37
  }
38
+ function mappedKey(type) {
39
+ return type.nameType ?? type.constraint;
40
+ }
38
41
  function unwrapTransparentType(type) {
39
42
  let current = type;
40
43
  while (current.type === "TSParenthesizedType" ||
@@ -43,12 +46,6 @@ function unwrapTransparentType(type) {
43
46
  }
44
47
  return current;
45
48
  }
46
- function isUnappliedReferenceTo(type, name) {
47
- const unwrapped = unwrapTransparentType(type);
48
- return (unwrapped.type === "TSTypeReference" &&
49
- typeReferenceName(unwrapped) === name &&
50
- (unwrapped.typeArguments === null || unwrapped.typeArguments.params.length === 0));
51
- }
52
49
  function isNeverType(type) {
53
50
  return unwrapTransparentType(type).type === "TSNeverKeyword";
54
51
  }
@@ -70,36 +67,6 @@ function isEffectivelyEmptyInterface(declarations) {
70
67
  type.extends.length === 0 &&
71
68
  (type.body.body.length === 0 || type.body.body.every(isEffectivelyEmptyMember)));
72
69
  }
73
- function resolvedSubstitutionArgument(type, base, resolving = new Set()) {
74
- const unwrapped = unwrapTransparentType(type);
75
- if (unwrapped.type !== "TSTypeReference") {
76
- return type;
77
- }
78
- const name = typeReferenceName(unwrapped);
79
- if (name === null || resolving.has(name)) {
80
- return type;
81
- }
82
- const substitution = base.get(name);
83
- if (substitution === void 0) {
84
- return type;
85
- }
86
- const nextResolving = new Set(resolving);
87
- nextResolving.add(name);
88
- return resolvedSubstitutionArgument(substitution, base, nextResolving);
89
- }
90
- function aliasSubstitution(alias, type, base) {
91
- const parameters = alias.typeParameters?.params ?? [];
92
- const arguments_ = type.typeArguments?.params ?? [];
93
- const next = new Map(base);
94
- for (const [index, parameter] of parameters.entries()) {
95
- const argument = arguments_[index] ?? parameter.default;
96
- if (argument === null) {
97
- return null;
98
- }
99
- next.set(parameter.name.name, resolvedSubstitutionArgument(argument, next));
100
- }
101
- return next;
102
- }
103
70
  function unsafeDirectValue(type, environment, substitutions, resolvingAliases) {
104
71
  const unwrapped = unwrapTransparentType(type);
105
72
  if (unwrapped.type === "TSUnknownKeyword") {
@@ -144,9 +111,7 @@ function unsafeDirectValue(type, environment, substitutions, resolvingAliases) {
144
111
  }
145
112
  const substitution = substitutions.get(name);
146
113
  if (substitution !== void 0) {
147
- return isUnappliedReferenceTo(substitution, name)
148
- ? null
149
- : unsafeDirectValue(substitution, environment, substitutions, resolvingAliases);
114
+ return unsafeDirectValue(substitution.type, environment, substitution.substitutions, resolvingAliases);
150
115
  }
151
116
  const interfaceDeclarations = environment.interfaces.get(name);
152
117
  if (interfaceDeclarations !== void 0) {
@@ -156,7 +121,7 @@ function unsafeDirectValue(type, environment, substitutions, resolvingAliases) {
156
121
  if (alias === null || resolvingAliases.has(name)) {
157
122
  return null;
158
123
  }
159
- const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
124
+ const nextSubstitutions = aliasSubstitutions(alias, unwrapped, substitutions);
160
125
  if (nextSubstitutions === null) {
161
126
  return null;
162
127
  }
@@ -164,6 +129,50 @@ function unsafeDirectValue(type, environment, substitutions, resolvingAliases) {
164
129
  nextResolving.add(name);
165
130
  return unsafeDirectValue(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
166
131
  }
132
+ function isBroadMappedKey(type, environment, substitutions, visitedAliases = new Set()) {
133
+ const unwrapped = unwrapTransparentType(type);
134
+ if (unwrapped.type === "TSStringKeyword" ||
135
+ unwrapped.type === "TSNumberKeyword" ||
136
+ unwrapped.type === "TSSymbolKeyword") {
137
+ return true;
138
+ }
139
+ if (unwrapped.type === "TSUnionType") {
140
+ return unwrapped.types.some((member) => isBroadMappedKey(member, environment, substitutions, visitedAliases));
141
+ }
142
+ if (unwrapped.type !== "TSTypeReference") {
143
+ return false;
144
+ }
145
+ const name = typeReferenceName(unwrapped);
146
+ if (name === null) {
147
+ return false;
148
+ }
149
+ const substitution = substitutions.get(name);
150
+ if (substitution !== void 0) {
151
+ return isBroadMappedKey(substitution.type, environment, substitution.substitutions, visitedAliases);
152
+ }
153
+ if (name === "PropertyKey" && isBuiltIn(name, unwrapped, environment)) {
154
+ return true;
155
+ }
156
+ const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
157
+ if (alias === null ||
158
+ (alias.typeParameters?.params.length ?? 0) > 0 ||
159
+ visitedAliases.has(name)) {
160
+ return false;
161
+ }
162
+ const nextVisited = new Set(visitedAliases);
163
+ nextVisited.add(name);
164
+ return isBroadMappedKey(alias.typeAnnotation, environment, substitutions, nextVisited);
165
+ }
166
+ function keptDictionarySource(type, environment, substitutions) {
167
+ const [source, keys] = type.typeArguments?.params ?? [];
168
+ if (source === void 0) {
169
+ return null;
170
+ }
171
+ return typeReferenceName(type) === "Omit" ||
172
+ (keys !== void 0 && isBroadMappedKey(keys, environment, substitutions))
173
+ ? source
174
+ : null;
175
+ }
167
176
  function dictionaryValueTypes(type, environment, substitutions, resolvingAliases) {
168
177
  const unwrapped = unwrapTransparentType(type);
169
178
  if (unwrapped.type === "TSTypeLiteral") {
@@ -185,9 +194,7 @@ function dictionaryValueTypes(type, environment, substitutions, resolvingAliases
185
194
  }
186
195
  const substitution = substitutions.get(name);
187
196
  if (substitution !== void 0) {
188
- return isUnappliedReferenceTo(substitution, name)
189
- ? []
190
- : dictionaryValueTypes(substitution, environment, substitutions, resolvingAliases);
197
+ return dictionaryValueTypes(substitution.type, environment, substitution.substitutions, resolvingAliases);
191
198
  }
192
199
  if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, unwrapped, environment)) {
193
200
  const wrapped = unwrapped.typeArguments?.params[0];
@@ -200,8 +207,8 @@ function dictionaryValueTypes(type, environment, substitutions, resolvingAliases
200
207
  return value === null ? [] : [{ type: value, substitutions }];
201
208
  }
202
209
  if ((name === "Pick" || name === "Omit") && isBuiltIn(name, unwrapped, environment)) {
203
- const source = unwrapped.typeArguments?.params[0];
204
- return source === void 0
210
+ const source = keptDictionarySource(unwrapped, environment, substitutions);
211
+ return source === null
205
212
  ? []
206
213
  : dictionaryValueTypes(source, environment, substitutions, resolvingAliases);
207
214
  }
@@ -209,7 +216,7 @@ function dictionaryValueTypes(type, environment, substitutions, resolvingAliases
209
216
  if (alias === null || resolvingAliases.has(name)) {
210
217
  return [];
211
218
  }
212
- const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
219
+ const nextSubstitutions = aliasSubstitutions(alias, unwrapped, substitutions);
213
220
  if (nextSubstitutions === null) {
214
221
  return [];
215
222
  }
@@ -230,40 +237,6 @@ function classifyUnsafeDictionary(type, environment) {
230
237
  }
231
238
  return null;
232
239
  }
233
- function isBroadMappedKey(type, environment, substitutions, visitedAliases = new Set()) {
234
- const unwrapped = unwrapTransparentType(type);
235
- if (unwrapped.type === "TSStringKeyword" ||
236
- unwrapped.type === "TSNumberKeyword" ||
237
- unwrapped.type === "TSSymbolKeyword") {
238
- return true;
239
- }
240
- if (unwrapped.type === "TSUnionType") {
241
- return unwrapped.types.some((member) => isBroadMappedKey(member, environment, substitutions, visitedAliases));
242
- }
243
- if (unwrapped.type !== "TSTypeReference") {
244
- return false;
245
- }
246
- const name = typeReferenceName(unwrapped);
247
- if (name === null) {
248
- return false;
249
- }
250
- const substitution = substitutions.get(name);
251
- if (substitution !== void 0 && !isUnappliedReferenceTo(substitution, name)) {
252
- return isBroadMappedKey(substitution, environment, substitutions, visitedAliases);
253
- }
254
- if (name === "PropertyKey" && isBuiltIn(name, unwrapped, environment)) {
255
- return true;
256
- }
257
- const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
258
- if (alias === null ||
259
- (alias.typeParameters?.params.length ?? 0) > 0 ||
260
- visitedAliases.has(name)) {
261
- return false;
262
- }
263
- const nextVisited = new Set(visitedAliases);
264
- nextVisited.add(name);
265
- return isBroadMappedKey(alias.typeAnnotation, environment, substitutions, nextVisited);
266
- }
267
240
  function hasBroadRecordKey(type, environment, substitutions) {
268
241
  const key = type.typeArguments?.params[0];
269
242
  return key === void 0 || isBroadMappedKey(key, environment, substitutions);
@@ -282,7 +255,7 @@ function classifyAliasBroadTarget(type, environment, substitutions, resolvingAli
282
255
  : null;
283
256
  }
284
257
  if (unwrapped.type === "TSMappedType") {
285
- return isBroadMappedKey(unwrapped.constraint, environment, substitutions)
258
+ return isBroadMappedKey(mappedKey(unwrapped), environment, substitutions)
286
259
  ? { kind: "open dictionary" }
287
260
  : null;
288
261
  }
@@ -295,9 +268,7 @@ function classifyAliasBroadTarget(type, environment, substitutions, resolvingAli
295
268
  }
296
269
  const substitution = substitutions.get(name);
297
270
  if (substitution !== void 0) {
298
- return isUnappliedReferenceTo(substitution, name)
299
- ? null
300
- : classifyAliasBroadTarget(substitution, environment, substitutions, resolvingAliases);
271
+ return classifyAliasBroadTarget(substitution.type, environment, substitution.substitutions, resolvingAliases);
301
272
  }
302
273
  if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, unwrapped, environment)) {
303
274
  const wrapped = unwrapped.typeArguments?.params[0];
@@ -310,11 +281,18 @@ function classifyAliasBroadTarget(type, environment, substitutions, resolvingAli
310
281
  ? { kind: "open dictionary" }
311
282
  : null;
312
283
  }
284
+ if ((name === "Pick" || name === "Omit") && isBuiltIn(name, unwrapped, environment)) {
285
+ const source = keptDictionarySource(unwrapped, environment, substitutions);
286
+ const target = source === null
287
+ ? null
288
+ : classifyAliasBroadTarget(source, environment, substitutions, resolvingAliases);
289
+ return target?.kind === "open dictionary" ? target : null;
290
+ }
313
291
  const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
314
292
  if (alias === null || resolvingAliases.has(name)) {
315
293
  return null;
316
294
  }
317
- const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
295
+ const nextSubstitutions = aliasSubstitutions(alias, unwrapped, substitutions);
318
296
  if (nextSubstitutions === null) {
319
297
  return null;
320
298
  }
@@ -338,7 +316,9 @@ function classifyWideningTarget(type, environment) {
338
316
  : null;
339
317
  }
340
318
  if (unwrapped.type === "TSMappedType") {
341
- return { kind: "open dictionary" };
319
+ return isBroadMappedKey(mappedKey(unwrapped), environment, new Map())
320
+ ? { kind: "open dictionary" }
321
+ : null;
342
322
  }
343
323
  if (unwrapped.type !== "TSTypeReference") {
344
324
  return null;
@@ -356,18 +336,21 @@ function classifyWideningTarget(type, environment) {
356
336
  ? { kind: "open dictionary" }
357
337
  : null;
358
338
  }
339
+ if ((name === "Pick" || name === "Omit") && isBuiltIn(name, unwrapped, environment)) {
340
+ return classifyAliasBroadTarget(unwrapped, environment, new Map(), new Set());
341
+ }
359
342
  const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
360
343
  if (alias === null) {
361
344
  return null;
362
345
  }
363
346
  if ((alias.typeParameters?.params.length ?? 0) > 0) {
364
- const substitutions2 = aliasSubstitution(alias, unwrapped, new Map());
347
+ const substitutions2 = aliasSubstitutions(alias, unwrapped, new Map());
365
348
  const resolved2 = substitutions2 === null
366
349
  ? null
367
350
  : classifyAliasBroadTarget(alias.typeAnnotation, environment, substitutions2, new Set([name]));
368
351
  return resolved2?.kind === "open dictionary" ? { kind: "generic container" } : null;
369
352
  }
370
- const substitutions = aliasSubstitution(alias, unwrapped, new Map());
353
+ const substitutions = aliasSubstitutions(alias, unwrapped, new Map());
371
354
  if (substitutions === null) {
372
355
  return null;
373
356
  }
@@ -7,6 +7,11 @@ type DeclaredTypeBinding = {
7
7
  type TypeBinding = DeclaredTypeBinding & {
8
8
  readonly scope: ESTree.Node;
9
9
  };
10
+ type Substitution = {
11
+ readonly type: ESTree.TSType;
12
+ readonly substitutions: Substitutions;
13
+ };
14
+ type Substitutions = ReadonlyMap<string, Substitution>;
10
15
  type TypeAliasEnvironment = {
11
16
  readonly aliases: readonly ESTree.TSTypeAliasDeclaration[];
12
17
  readonly bindingsByName: ReadonlyMap<string, readonly TypeBinding[]>;
@@ -16,6 +21,7 @@ type ResolvedTypeMatcher = (type: ESTree.TSType, matches: (child: ESTree.TSType)
16
21
  declare function createTypeAliasEnvironment(program: ESTree.Program, visitorKeys: VisitorKeys): TypeAliasEnvironment;
17
22
  declare function visibleTypeAlias(name: string, use: ESTree.Node, environment: TypeAliasEnvironment): ESTree.TSTypeAliasDeclaration | null;
18
23
  declare function hasVisibleTypeBinding(name: string, use: ESTree.Node, environment: TypeAliasEnvironment): boolean;
24
+ declare function aliasSubstitutions(alias: ESTree.TSTypeAliasDeclaration, reference: ESTree.TSTypeReference, base: Substitutions): Substitutions | null;
19
25
  declare function resolvedTypeMatches(type: ESTree.TSType, environment: TypeAliasEnvironment, matcher: ResolvedTypeMatcher): boolean;
20
- export type { ResolvedTypeMatcher, TypeAliasEnvironment };
21
- export { createTypeAliasEnvironment, hasVisibleTypeBinding, resolvedTypeMatches, visibleTypeAlias };
26
+ export type { ResolvedTypeMatcher, Substitution, Substitutions, TypeAliasEnvironment };
27
+ export { aliasSubstitutions, createTypeAliasEnvironment, hasVisibleTypeBinding, resolvedTypeMatches, visibleTypeAlias, };
@@ -105,7 +105,7 @@ function typeReferenceName(type) {
105
105
  function aliasSubstitutions(alias, reference, base) {
106
106
  const parameters = alias.typeParameters?.params ?? [];
107
107
  const arguments_ = reference.typeArguments?.params ?? [];
108
- const next = new Map(base);
108
+ const next = new Map();
109
109
  for (const [index, parameter] of parameters.entries()) {
110
110
  const explicitArgument = arguments_[index];
111
111
  const argument = explicitArgument ?? parameter.default;
@@ -144,4 +144,4 @@ function resolvedTypeMatches(type, environment, matcher) {
144
144
  };
145
145
  return evaluate(type, new Map(), new Set());
146
146
  }
147
- export { createTypeAliasEnvironment, hasVisibleTypeBinding, resolvedTypeMatches, visibleTypeAlias };
147
+ export { aliasSubstitutions, createTypeAliasEnvironment, hasVisibleTypeBinding, resolvedTypeMatches, visibleTypeAlias, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jameswyse/oxc-config",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Opinionated Oxlint and Oxfmt configs with custom rules for TypeScript and JavaScript projects",
5
5
  "keywords": [
6
6
  "config",
@@ -39,15 +39,15 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@e18e/eslint-plugin": "0.8.1",
42
- "@oxlint/plugins": "1.86.0",
42
+ "@oxlint/plugins": "1.88.0",
43
43
  "eslint-plugin-react-you-might-not-need-an-effect": "1.0.2"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@changesets/cli": "3.0.3",
47
47
  "@types/node": "^22.20.5",
48
- "oxfmt": "0.71.0",
49
- "oxlint": "1.86.0",
50
- "oxlint-tsgolint": "7.0.2003",
48
+ "oxfmt": "0.73.0",
49
+ "oxlint": "1.88.0",
50
+ "oxlint-tsgolint": "7.0.2004",
51
51
  "typescript": "7.0.2"
52
52
  },
53
53
  "peerDependencies": {
@@ -66,11 +66,11 @@
66
66
  "devEngines": {
67
67
  "packageManager": {
68
68
  "name": "pnpm",
69
- "version": "12.6.0"
69
+ "version": "12.13.0"
70
70
  },
71
71
  "runtime": {
72
72
  "name": "node",
73
- "version": "24.18.0",
73
+ "version": "24.22.0",
74
74
  "onFail": "download"
75
75
  }
76
76
  },