@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
package/dist/index.js CHANGED
@@ -10,15 +10,28 @@ import { noAmbientClock } from "./rules/no-ambient-clock.js";
10
10
  import { noAmbientRandom } from "./rules/no-ambient-random.js";
11
11
  import { noComments } from "./rules/no-comments.js";
12
12
  import { noDeepTernary } from "./rules/no-deep-ternary.js";
13
+ import { noDerivedTestExpectation } from "./rules/no-derived-test-expectation.js";
14
+ import { noDisabledTlsVerification } from "./rules/no-disabled-tls-verification.js";
13
15
  import { noEditProofAssertions } from "./rules/no-edit-proof-assertions.js";
14
16
  import { noEnvAccess } from "./rules/no-env-access.js";
15
17
  import { noFixedTestDelay } from "./rules/no-fixed-test-delay.js";
16
18
  import { noForwardingModule } from "./rules/no-forwarding-module.js";
17
19
  import { noGenericFileSuffix } from "./rules/no-generic-file-suffix.js";
20
+ import { noGenericObjectGuard } from "./rules/no-generic-object-guard.js";
21
+ import { noGetPasswordForm } from "./rules/no-get-password-form.js";
22
+ import { noHardcodedCredential } from "./rules/no-hardcoded-credential.js";
23
+ import { noImportTimeEffects } from "./rules/no-import-time-effects.js";
24
+ import { noOptionalTestAction } from "./rules/no-optional-test-action.js";
18
25
  import { noPassThroughFunction } from "./rules/no-pass-through-function.js";
26
+ import { noSecretFallback } from "./rules/no-secret-fallback.js";
19
27
  import { noSwallowedErrors } from "./rules/no-swallowed-errors.js";
20
28
  import { noTestTimeoutOverrides } from "./rules/no-test-timeout-overrides.js";
29
+ import { noUncheckedFetchResponse } from "./rules/no-unchecked-fetch-response.js";
30
+ import { noUncheckedIndexSearch } from "./rules/no-unchecked-index-search.js";
31
+ import { noUncontrolledTestClock } from "./rules/no-uncontrolled-test-clock.js";
32
+ import { noUnsettledPromise } from "./rules/no-unsettled-promise.js";
21
33
  import { noVacuousTestAssertions } from "./rules/no-vacuous-test-assertions.js";
34
+ import { requireSuppressionReason } from "./rules/require-suppression-reason.js";
22
35
  import { serverActionGuard } from "./rules/server-action-guard.js";
23
36
  import { statementSpacing } from "./rules/statement-spacing.js";
24
37
  import { typeGuardName } from "./rules/type-guard-name.js";
@@ -66,29 +79,42 @@ const wyse = definePlugin({
66
79
  "no-comments": noComments,
67
80
  "no-conditional-empty-object-spread": noConditionalEmptyObjectSpread,
68
81
  "no-deep-ternary": noDeepTernary,
82
+ "no-derived-test-expectation": noDerivedTestExpectation,
83
+ "no-disabled-tls-verification": noDisabledTlsVerification,
69
84
  "no-edit-proof-assertions": noEditProofAssertions,
70
85
  "no-env-access": noEnvAccess,
71
86
  "no-fixed-test-delay": noFixedTestDelay,
72
87
  "no-forwarding-module": noForwardingModule,
73
88
  "no-generic-file-suffix": noGenericFileSuffix,
89
+ "no-generic-object-guard": noGenericObjectGuard,
90
+ "no-get-password-form": noGetPasswordForm,
91
+ "no-hardcoded-credential": noHardcodedCredential,
92
+ "no-import-time-effects": noImportTimeEffects,
74
93
  "no-known-value-widening": noKnownValueWidening,
75
94
  "no-manual-effect-error-tag": noManualEffectErrorTag,
76
95
  "no-module-mocking": noModuleMocking,
77
96
  "no-object-parameters": noObjectParameters,
97
+ "no-optional-test-action": noOptionalTestAction,
78
98
  "no-pass-through-function": noPassThroughFunction,
79
99
  "no-reduce-accumulator-copy": noReduceAccumulatorCopy,
80
100
  "no-reflect-apply": noReflectApply,
81
101
  "no-reflect-get": noReflectGet,
82
102
  "no-runtime-typeof": noRuntimeTypeof,
103
+ "no-secret-fallback": noSecretFallback,
83
104
  "no-service-constructor-imports": noServiceConstructorImports,
84
105
  "no-shape-in-symbol-names": noForbiddenTermInSymbolNames,
85
106
  "no-swallowed-errors": noSwallowedErrors,
86
107
  "no-test-timeout-overrides": noTestTimeoutOverrides,
108
+ "no-unchecked-fetch-response": noUncheckedFetchResponse,
109
+ "no-unchecked-index-search": noUncheckedIndexSearch,
110
+ "no-uncontrolled-test-clock": noUncontrolledTestClock,
87
111
  "no-unknown-returns": noUnknownReturns,
88
112
  "no-unknown-type-aliases": noUnknownTypeAliases,
89
113
  "no-unsafe-dictionary-type": noUnsafeDictionaryType,
114
+ "no-unsettled-promise": noUnsettledPromise,
90
115
  "no-vacuous-test-assertions": noVacuousTestAssertions,
91
116
  "no-widen-then-assert": noWidenThenAssert,
117
+ "require-suppression-reason": requireSuppressionReason,
92
118
  "server-action-guard": serverActionGuard,
93
119
  "statement-spacing": statementSpacing,
94
120
  "type-guard-name": typeGuardName,
package/dist/lint.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { DummyRuleMap, OxlintConfig, OxlintOverride } from "oxlint";
2
2
  export type FileScope = boolean | string[];
3
3
  export interface LintOptions {
4
+ root?: string;
4
5
  typeAware?: boolean;
5
6
  node?: FileScope;
6
7
  react?: FileScope;
@@ -23,6 +24,7 @@ export interface ImportBoundary {
23
24
  ignore?: string[];
24
25
  deny: string[];
25
26
  message: string;
27
+ types?: boolean;
26
28
  }
27
29
  export type SharedLintConfig = OxlintConfig & {
28
30
  ignorePatterns: string[];
@@ -30,4 +32,4 @@ export type SharedLintConfig = OxlintConfig & {
30
32
  rules: DummyRuleMap;
31
33
  };
32
34
  export declare const defaultIgnores: string[];
33
- export declare function createLintConfig({ typeAware, node, react, nextjs, vitest, jest, effect, tests, env, boundaries, serverActionGuards, clock, random, electron, }?: LintOptions): SharedLintConfig;
35
+ export declare function createLintConfig({ root, typeAware, node, react, nextjs, vitest, jest, effect, tests, env, boundaries, serverActionGuards, clock, random, electron, }?: LintOptions): SharedLintConfig;
package/dist/lint.js CHANGED
@@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  const require = createRequire(import.meta.url);
5
5
  const sourceFiles = ["**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"];
6
6
  const typeScriptFiles = ["**/*.{ts,tsx,mts,cts}"];
7
+ const javaScriptFiles = ["**/*.{js,jsx,mjs,cjs}"];
7
8
  const testFiles = [
8
9
  "**/*.{test,spec}.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
9
10
  "**/__tests__/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
@@ -35,12 +36,19 @@ const defaultExportFiles = [
35
36
  "**/*.stories.{js,jsx,mjs,ts,tsx,mts}",
36
37
  "**/.storybook/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
37
38
  ];
39
+ const programFiles = [
40
+ "**/{bin,cli,deploy,tools,test,tests,e2e,fixtures}/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
41
+ "**/.storybook/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
42
+ "**/{main,server,worker,instrumentation,instrumentation-client,healthcheck}.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
43
+ ];
38
44
  const endToEndFiles = ["**/e2e/**", "**/*.e2e.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"];
39
45
  const electronRendererFiles = ["src/renderer/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"];
40
46
  const testRules = {
41
47
  "wyse/no-fixed-test-delay": "error",
42
48
  "wyse/no-vacuous-test-assertions": "error",
43
49
  "wyse/no-edit-proof-assertions": "error",
50
+ "wyse/no-optional-test-action": "error",
51
+ "wyse/no-derived-test-expectation": "warn",
44
52
  "wyse/companion-has-owner": "warn",
45
53
  };
46
54
  const basePlugins = ["eslint", "unicorn", "oxc", "import", "promise", "typescript"];
@@ -70,6 +78,43 @@ function reactScope(react, nextjs) {
70
78
  const files = [...(react || []), ...(nextjs || [])];
71
79
  return files.length > 0 ? [...new Set(files)] : false;
72
80
  }
81
+ const nativeGuards = {
82
+ ...errors("eslint", [
83
+ "array-callback-return",
84
+ "no-array-constructor",
85
+ "no-case-declarations",
86
+ "no-constructor-return",
87
+ "no-fallthrough",
88
+ "no-loop-func",
89
+ "no-new-func",
90
+ "no-new-wrappers",
91
+ "no-proto",
92
+ "no-prototype-builtins",
93
+ "no-self-compare",
94
+ "no-unreachable-loop",
95
+ "no-useless-assignment",
96
+ "no-var",
97
+ "prefer-object-has-own",
98
+ "prefer-rest-params",
99
+ ]),
100
+ "eslint/prefer-regex-literals": ["error", { disallowRedundantWrapping: true }],
101
+ ...errors("unicorn", [
102
+ "error-message",
103
+ "new-for-builtins",
104
+ "no-instanceof-array",
105
+ "no-new-buffer",
106
+ "prefer-array-find",
107
+ "prefer-array-flat-map",
108
+ "prefer-array-some",
109
+ "prefer-import-meta-properties",
110
+ "prefer-node-protocol",
111
+ "prefer-response-static-json",
112
+ "prefer-string-trim-start-end",
113
+ ]),
114
+ "import/no-mutable-exports": "error",
115
+ "oxc/no-const-enum": "error",
116
+ };
117
+ const javaScriptOnlyRules = errors("eslint", ["no-throw-literal", "prefer-promise-reject-errors"]);
73
118
  const baseErrors = errors("wyse", [
74
119
  "no-array-filter-map",
75
120
  "no-reduce-accumulator-copy",
@@ -99,10 +144,23 @@ const typeAwareRules = {
99
144
  "no-unsafe-return",
100
145
  "no-unsafe-type-assertion",
101
146
  "only-throw-error",
147
+ "prefer-promise-reject-errors",
102
148
  "prefer-readonly",
149
+ "prefer-reduce-type-parameter",
103
150
  "return-await",
104
151
  "switch-exhaustiveness-check",
152
+ "use-unknown-in-catch-callback-variable",
105
153
  ]),
154
+ "typescript/restrict-plus-operands": [
155
+ "error",
156
+ {
157
+ allowAny: false,
158
+ allowBoolean: false,
159
+ allowNullish: false,
160
+ allowNumberAndString: false,
161
+ allowRegExp: false,
162
+ },
163
+ ],
106
164
  "typescript/no-floating-promises": [
107
165
  "error",
108
166
  {
@@ -250,17 +308,24 @@ const testRuleNames = [
250
308
  "no-duplicate-hooks",
251
309
  "no-focused-tests",
252
310
  "no-identical-title",
311
+ "no-interpolation-in-snapshots",
253
312
  "no-standalone-expect",
313
+ "no-unneeded-async-expect-function",
314
+ "prefer-comparison-matcher",
315
+ "prefer-equality-matcher",
316
+ "prefer-strict-equal",
317
+ "prefer-to-contain",
318
+ "prefer-to-have-length",
254
319
  "valid-describe-callback",
255
320
  "valid-expect",
256
321
  "valid-expect-in-promise",
257
322
  ];
258
- export function createLintConfig({ typeAware = true, node = false, react = false, nextjs = false, vitest = false, jest = false, effect = false, tests = [], env, boundaries = [], serverActionGuards, clock, random, electron = false, } = {}) {
259
- const nextRoots = filesFor(nextjs, ["."]).map((root) => {
260
- const normalized = root.replace(/^\.\//, "").replace(/\/+$/, "");
323
+ export function createLintConfig({ root, typeAware = true, node = false, react = false, nextjs = false, vitest = false, jest = false, effect = false, tests = [], env, boundaries = [], serverActionGuards, clock, random, electron = false, } = {}) {
324
+ const nextRoots = filesFor(nextjs, ["."]).map((appRoot) => {
325
+ const normalized = appRoot.replace(/^\.\//, "").replace(/\/+$/, "");
261
326
  return normalized === "." || normalized === "" ? "" : `${normalized}/`;
262
327
  });
263
- const nextFiles = nextRoots.flatMap((root) => sourceFiles.map((pattern) => `${root}${pattern}`));
328
+ const nextFiles = nextRoots.flatMap((appRoot) => sourceFiles.map((pattern) => `${appRoot}${pattern}`));
264
329
  const reactFiles = reactScope(react, nextFiles);
265
330
  const allTestFiles = [
266
331
  ...new Set([
@@ -273,13 +338,32 @@ export function createLintConfig({ typeAware = true, node = false, react = false
273
338
  const jsPlugins = [wysePlugin];
274
339
  const typescriptRules = {
275
340
  ...errors("typescript", [
276
- "ban-ts-comment",
277
341
  "consistent-type-imports",
278
342
  "no-explicit-any",
279
343
  "no-non-null-assertion",
280
344
  "no-empty-object-type",
281
345
  "no-unnecessary-type-constraint",
282
346
  ]),
347
+ "typescript/ban-ts-comment": [
348
+ "error",
349
+ {
350
+ "ts-expect-error": { descriptionFormat: "^\\s*-{2,}\\s+\\S" },
351
+ "ts-ignore": true,
352
+ "ts-nocheck": true,
353
+ "ts-check": false,
354
+ },
355
+ ],
356
+ "typescript/consistent-type-assertions": [
357
+ "error",
358
+ {
359
+ assertionStyle: "as",
360
+ objectLiteralTypeAssertions: "never",
361
+ arrayLiteralTypeAssertions: "never",
362
+ },
363
+ ],
364
+ "typescript/method-signature-style": ["error", "property"],
365
+ "typescript/no-import-type-side-effects": "error",
366
+ "typescript/no-unsafe-function-type": "error",
283
367
  "wyse/type-guard-name": "error",
284
368
  };
285
369
  const overrides = [
@@ -297,11 +381,28 @@ export function createLintConfig({ typeAware = true, node = false, react = false
297
381
  files: defaultExportFiles,
298
382
  rules: { "import/no-default-export": "off" },
299
383
  },
384
+ { files: javaScriptFiles, rules: javaScriptOnlyRules },
300
385
  { files: allTestFiles, rules: testRules },
301
386
  {
302
387
  files: allTestFiles,
303
388
  excludeFiles: endToEndFiles,
304
- rules: { "wyse/no-test-timeout-overrides": "warn" },
389
+ rules: {
390
+ "wyse/no-test-timeout-overrides": "warn",
391
+ "wyse/no-uncontrolled-test-clock": "error",
392
+ },
393
+ },
394
+ {
395
+ files: sourceFiles,
396
+ excludeFiles: allTestFiles,
397
+ rules: {
398
+ "wyse/no-unchecked-fetch-response": "error",
399
+ "wyse/no-unsettled-promise": "error",
400
+ },
401
+ },
402
+ {
403
+ files: sourceFiles,
404
+ excludeFiles: [...allTestFiles, ...entrypointFiles, ...programFiles],
405
+ rules: { "wyse/no-import-time-effects": "warn" },
305
406
  },
306
407
  ];
307
408
  if (env !== undefined && env.length > 0) {
@@ -325,9 +426,12 @@ export function createLintConfig({ typeAware = true, node = false, react = false
325
426
  ]
326
427
  : boundaries;
327
428
  if (importBoundaries.length > 0) {
429
+ if (root === undefined) {
430
+ throw new TypeError("Import boundaries resolve from the config file, so pass root: import.meta.dirname.");
431
+ }
328
432
  overrides.push({
329
433
  files: sourceFiles,
330
- rules: { "wyse/import-boundaries": ["error", { boundaries: importBoundaries }] },
434
+ rules: { "wyse/import-boundaries": ["error", { root, boundaries: importBoundaries }] },
331
435
  });
332
436
  }
333
437
  if (electron) {
@@ -348,7 +452,11 @@ export function createLintConfig({ typeAware = true, node = false, react = false
348
452
  });
349
453
  }
350
454
  if (typeAware) {
351
- overrides.unshift({ files: typeScriptFiles, rules: typeAwareRules });
455
+ overrides.unshift({ files: typeScriptFiles, rules: typeAwareRules }, {
456
+ files: typeScriptFiles,
457
+ excludeFiles: allTestFiles,
458
+ rules: { "typescript/no-deprecated": "warn" },
459
+ });
352
460
  }
353
461
  if (node) {
354
462
  overrides.push({
@@ -376,14 +484,38 @@ export function createLintConfig({ typeAware = true, node = false, react = false
376
484
  ...accessibilityRules,
377
485
  "react/exhaustive-deps": "error",
378
486
  "react/no-array-index-key": "error",
487
+ "wyse/no-get-password-form": "error",
379
488
  "react/react-in-jsx-scope": "off",
489
+ "jsx-a11y/control-has-associated-label": [
490
+ "error",
491
+ {
492
+ ignoreElements: [
493
+ "audio",
494
+ "canvas",
495
+ "embed",
496
+ "input",
497
+ "td",
498
+ "textarea",
499
+ "tr",
500
+ "video",
501
+ ],
502
+ },
503
+ ],
504
+ "jsx-a11y/label-has-associated-control": ["error", { depth: 25 }],
380
505
  "jsx-a11y/no-noninteractive-tabindex": ["error", { roles: ["region", "tabpanel"] }],
381
506
  "jsx-a11y/prefer-tag-over-role": "off",
507
+ "react/checked-requires-onchange-or-readonly": "error",
508
+ "react/jsx-no-useless-fragment": ["error", { allowExpressions: true }],
509
+ "react/no-unknown-property": "error",
382
510
  },
383
511
  }, {
384
512
  files: reactFiles,
385
513
  excludeFiles: allTestFiles,
386
514
  rules: { ...reactEffectRules, ...browserPerformanceRules },
515
+ }, {
516
+ files: reactFiles,
517
+ excludeFiles: [...endToEndFiles, "**/tests/**"],
518
+ rules: { "react/rules-of-hooks": "error" },
387
519
  });
388
520
  }
389
521
  if (nextFiles.length > 0) {
@@ -393,11 +525,11 @@ export function createLintConfig({ typeAware = true, node = false, react = false
393
525
  plugins: [...basePlugins, "react", "jsx-a11y", "nextjs"],
394
526
  rules: nextRules,
395
527
  }, {
396
- files: nextRoots.flatMap((root) => ["", "src/"].flatMap((source) => [
397
- `${root}${source}app/**/{page,layout,template,loading,error,global-error,not-found,global-not-found,forbidden,unauthorized,default}.{js,jsx,ts,tsx}`,
398
- `${root}${source}app/**/{sitemap,robots,manifest,icon,apple-icon,opengraph-image,twitter-image}.{js,jsx,ts,tsx}`,
399
- `${root}${source}pages/**/*.{js,jsx,ts,tsx}`,
400
- `${root}${source}{middleware,proxy}.{js,ts}`,
528
+ files: nextRoots.flatMap((appRoot) => ["", "src/"].flatMap((source) => [
529
+ `${appRoot}${source}app/**/{page,layout,template,loading,error,global-error,not-found,global-not-found,forbidden,unauthorized,default}.{js,jsx,ts,tsx}`,
530
+ `${appRoot}${source}app/**/{sitemap,robots,manifest,icon,apple-icon,opengraph-image,twitter-image}.{js,jsx,ts,tsx}`,
531
+ `${appRoot}${source}pages/**/*.{js,jsx,ts,tsx}`,
532
+ `${appRoot}${source}{middleware,proxy}.{js,ts}`,
401
533
  ])),
402
534
  excludeFiles: allTestFiles,
403
535
  rules: { "import/no-default-export": "off" },
@@ -414,7 +546,7 @@ export function createLintConfig({ typeAware = true, node = false, react = false
414
546
  plugins: [...basePlugins, name],
415
547
  env: { [name]: true },
416
548
  rules: {
417
- ...errors(name, testRuleNames),
549
+ ...errors(name, name === "jest" ? [...testRuleNames, "no-done-callback"] : testRuleNames),
418
550
  ...testRules,
419
551
  [`${name}/no-standalone-expect`]: [
420
552
  "error",
@@ -438,12 +570,19 @@ export function createLintConfig({ typeAware = true, node = false, react = false
438
570
  env: { es2022: true },
439
571
  ignorePatterns: defaultIgnores,
440
572
  rules: {
573
+ ...nativeGuards,
441
574
  ...baseErrors,
442
575
  ...typescriptRules,
443
576
  "wyse/no-runtime-typeof": ["warn", { allowInTypeGuards: true }],
444
577
  "wyse/no-comments": "error",
578
+ "wyse/require-suppression-reason": "error",
445
579
  "wyse/statement-spacing": "error",
446
580
  "wyse/no-generic-file-suffix": "error",
581
+ "wyse/no-generic-object-guard": "error",
582
+ "wyse/no-disabled-tls-verification": "error",
583
+ "wyse/no-hardcoded-credential": "error",
584
+ "wyse/no-secret-fallback": "error",
585
+ "wyse/no-unchecked-index-search": "error",
447
586
  "wyse/no-swallowed-errors": "warn",
448
587
  "wyse/explicit-api-states": "warn",
449
588
  "wyse/no-pass-through-function": "warn",
@@ -29,6 +29,12 @@ function isSetter(name, fn, parameter) {
29
29
  return (/^(?:set|update)\p{Lu}/u.test(name.slice(name.lastIndexOf(".") + 1)) &&
30
30
  fn.params.at(-1) === parameter);
31
31
  }
32
+ function isPredicateSubject(fn, identifier) {
33
+ const predicate = fn.returnType?.typeAnnotation;
34
+ return (predicate?.type === "TSTypePredicate" &&
35
+ predicate.parameterName.type === "Identifier" &&
36
+ predicate.parameterName.name === identifier.name);
37
+ }
32
38
  function functionValue(node) {
33
39
  return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression"
34
40
  ? node
@@ -92,7 +98,7 @@ export const explicitApiStates = defineRule({
92
98
  }
93
99
  for (const parameter of fn.params) {
94
100
  const identifier = booleanParameter(parameter);
95
- if (identifier && !isSetter(name, fn, parameter)) {
101
+ if (identifier && !isSetter(name, fn, parameter) && !isPredicateSubject(fn, identifier)) {
96
102
  context.report({
97
103
  node: identifier,
98
104
  messageId: "boolean",
@@ -1,6 +1,6 @@
1
1
  import { dirname, resolve as resolvePath } from "node:path";
2
2
  import { defineRule } from "@oxlint/plugins";
3
- import { projectPath, matchesAny } from "../shared/index.js";
3
+ import { importedBinding, isGlobal, matchesAny, projectPath, variableFor, } from "../shared/index.js";
4
4
  function isStringList(value) {
5
5
  return Array.isArray(value) && value.every((item) => typeof item === "string");
6
6
  }
@@ -8,24 +8,37 @@ function parseBoundary(value) {
8
8
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
9
9
  throw new TypeError("Each import boundary must be an object.");
10
10
  }
11
- const { files, ignore = [], deny, message } = value;
11
+ const { files, ignore = [], deny, message, types = false } = value;
12
12
  if (!isStringList(files) ||
13
13
  !isStringList(ignore) ||
14
14
  !isStringList(deny) ||
15
- typeof message !== "string") {
15
+ typeof message !== "string" ||
16
+ typeof types !== "boolean") {
16
17
  throw new TypeError("Each import boundary needs files, deny and message settings.");
17
18
  }
18
- return { files, ignore, deny, message };
19
+ return { files, ignore, deny, message, types };
19
20
  }
20
- function parseBoundaries(options) {
21
+ function parseSettings(options) {
21
22
  const [option] = options;
22
23
  if (typeof option !== "object" ||
23
24
  option === null ||
24
25
  Array.isArray(option) ||
26
+ typeof option.root !== "string" ||
25
27
  !Array.isArray(option.boundaries)) {
26
- throw new TypeError("Configure import boundaries with a boundaries array.");
28
+ throw new TypeError("Configure import boundaries with a root and a boundaries array.");
27
29
  }
28
- return option.boundaries.map(parseBoundary);
30
+ return { root: option.root, boundaries: option.boundaries.map(parseBoundary) };
31
+ }
32
+ const nodeModuleSources = new Set(["module", "node:module"]);
33
+ function isRequire(sourceCode, callee) {
34
+ if (isGlobal(sourceCode, callee, "require")) {
35
+ return true;
36
+ }
37
+ const definition = variableFor(sourceCode, callee)?.defs[0];
38
+ const init = definition?.node.type === "VariableDeclarator" ? definition.node.init : null;
39
+ const factory = init?.type === "CallExpression" ? init.callee : null;
40
+ const binding = factory === null ? null : importedBinding(sourceCode, factory);
41
+ return (binding !== null && nodeModuleSources.has(binding.source) && binding.name === "createRequire");
29
42
  }
30
43
  export const importBoundaries = defineRule({
31
44
  meta: {
@@ -35,6 +48,7 @@ export const importBoundaries = defineRule({
35
48
  {
36
49
  type: "object",
37
50
  properties: {
51
+ root: { type: "string" },
38
52
  boundaries: {
39
53
  type: "array",
40
54
  items: {
@@ -44,33 +58,35 @@ export const importBoundaries = defineRule({
44
58
  ignore: { type: "array", items: { type: "string" } },
45
59
  deny: { type: "array", items: { type: "string" } },
46
60
  message: { type: "string" },
61
+ types: { type: "boolean" },
47
62
  },
48
63
  required: ["files", "deny", "message"],
49
64
  additionalProperties: false,
50
65
  },
51
66
  },
52
67
  },
53
- required: ["boundaries"],
68
+ required: ["root", "boundaries"],
54
69
  additionalProperties: false,
55
70
  },
56
71
  ],
57
72
  messages: { boundary: "Do not import '{{specifier}}' here. {{message}}" },
58
73
  },
59
74
  create(context) {
60
- const file = projectPath(context.cwd, context.filename);
61
- const active = parseBoundaries(context.options).filter((boundary) => matchesAny(file, boundary.files) && !matchesAny(file, boundary.ignore));
75
+ const { root, boundaries } = parseSettings(context.options);
76
+ const file = projectPath(root, context.filename);
77
+ const active = boundaries.filter((boundary) => matchesAny(file, boundary.files) && !matchesAny(file, boundary.ignore));
62
78
  if (active.length === 0) {
63
79
  return {};
64
80
  }
65
81
  function check(source, typeOnly) {
66
- if (typeOnly || source?.type !== "Literal" || typeof source.value !== "string") {
82
+ if (source?.type !== "Literal" || typeof source.value !== "string") {
67
83
  return;
68
84
  }
69
85
  const specifier = source.value;
70
86
  const target = specifier.startsWith(".")
71
- ? projectPath(context.cwd, resolvePath(dirname(context.filename), specifier))
87
+ ? projectPath(root, resolvePath(dirname(context.filename), specifier))
72
88
  : specifier;
73
- const boundary = active.find(({ deny }) => matchesAny(target, deny));
89
+ const boundary = active.find(({ deny, types }) => (types || !typeOnly) && matchesAny(target, deny));
74
90
  if (boundary) {
75
91
  context.report({
76
92
  node: source,
@@ -94,6 +110,18 @@ export const importBoundaries = defineRule({
94
110
  ImportExpression(node) {
95
111
  check(node.source, false);
96
112
  },
113
+ TSImportType(node) {
114
+ check(node.source, true);
115
+ },
116
+ TSExternalModuleReference(node) {
117
+ check(node.expression, node.parent.type === "TSImportEqualsDeclaration" && node.parent.importKind === "type");
118
+ },
119
+ CallExpression(node) {
120
+ const [source] = node.arguments;
121
+ if (node.arguments.length === 1 && isRequire(context.sourceCode, node.callee)) {
122
+ check(source, false);
123
+ }
124
+ },
97
125
  };
98
126
  },
99
127
  });
@@ -0,0 +1 @@
1
+ export declare const noDerivedTestExpectation: import("@oxlint/plugins").Rule;