@jameswyse/oxc-config 0.2.0 → 0.4.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 (52) hide show
  1. package/README.md +30 -186
  2. package/REFERENCE.md +105 -132
  3. package/dist/index.js +26 -0
  4. package/dist/lint.d.ts +1 -0
  5. package/dist/lint.js +124 -4
  6. package/dist/rules/electron-no-remote-module.js +3 -6
  7. package/dist/rules/explicit-api-states.js +7 -1
  8. package/dist/rules/import-boundaries.js +31 -6
  9. package/dist/rules/no-ambient-clock.js +104 -8
  10. package/dist/rules/no-derived-test-expectation.d.ts +1 -0
  11. package/dist/rules/no-derived-test-expectation.js +136 -0
  12. package/dist/rules/no-disabled-tls-verification.d.ts +1 -0
  13. package/dist/rules/no-disabled-tls-verification.js +42 -0
  14. package/dist/rules/no-env-access.js +62 -5
  15. package/dist/rules/no-fixed-test-delay.js +46 -5
  16. package/dist/rules/no-generic-object-guard.d.ts +1 -0
  17. package/dist/rules/no-generic-object-guard.js +279 -0
  18. package/dist/rules/no-get-password-form.d.ts +1 -0
  19. package/dist/rules/no-get-password-form.js +113 -0
  20. package/dist/rules/no-hardcoded-credential.d.ts +1 -0
  21. package/dist/rules/no-hardcoded-credential.js +45 -0
  22. package/dist/rules/no-import-time-effects.d.ts +1 -0
  23. package/dist/rules/no-import-time-effects.js +90 -0
  24. package/dist/rules/no-optional-test-action.d.ts +1 -0
  25. package/dist/rules/no-optional-test-action.js +45 -0
  26. package/dist/rules/no-secret-fallback.d.ts +1 -0
  27. package/dist/rules/no-secret-fallback.js +64 -0
  28. package/dist/rules/no-swallowed-errors.js +131 -16
  29. package/dist/rules/no-test-timeout-overrides.js +22 -9
  30. package/dist/rules/no-unchecked-fetch-response.d.ts +1 -0
  31. package/dist/rules/no-unchecked-fetch-response.js +91 -0
  32. package/dist/rules/no-unchecked-index-search.d.ts +1 -0
  33. package/dist/rules/no-unchecked-index-search.js +41 -0
  34. package/dist/rules/no-uncontrolled-test-clock.d.ts +1 -0
  35. package/dist/rules/no-uncontrolled-test-clock.js +112 -0
  36. package/dist/rules/no-unsettled-promise.d.ts +1 -0
  37. package/dist/rules/no-unsettled-promise.js +64 -0
  38. package/dist/rules/require-suppression-reason.d.ts +1 -0
  39. package/dist/rules/require-suppression-reason.js +32 -0
  40. package/dist/shared/index.d.ts +14 -1
  41. package/dist/shared/index.js +231 -91
  42. package/dist/vendor/anti-slop/effect/rules/no-service-constructor-imports.js +10 -2
  43. package/dist/vendor/anti-slop/rules/no-known-value-widening.js +1 -1
  44. package/dist/vendor/anti-slop/rules/no-module-mocking.js +56 -1
  45. package/dist/vendor/anti-slop/rules/no-runtime-typeof.js +30 -1
  46. package/dist/vendor/anti-slop/rules/no-shape-in-symbol-names.js +28 -1
  47. package/dist/vendor/anti-slop/rules/no-unknown-returns.js +1 -1
  48. package/dist/vendor/anti-slop/rules/no-unsafe-dictionary-type.js +2 -2
  49. package/dist/vendor/anti-slop/shared/dictionary-types.js +72 -89
  50. package/dist/vendor/anti-slop/shared/type-alias-resolution.d.ts +8 -2
  51. package/dist/vendor/anti-slop/shared/type-alias-resolution.js +2 -2
  52. package/package.json +7 -7
package/REFERENCE.md CHANGED
@@ -1,134 +1,107 @@
1
1
  # Rule reference
2
2
 
3
- ## Defaults
4
-
5
- The base config enables Oxlint's correctness and suspicious categories as errors. It also enables strict equality, mandatory braces, `prefer-const`, no parameter reassignment, unused-variable checks that ignore `_`-prefixed bindings, import ordering and duplicate checks, the accumulating-spread check, named exports and no console logging. Unused suppression comments are errors. `no-underscore-dangle` is off so schema-owned fields such as `_id` and `__typename` remain valid.
6
-
7
- Warnings do not fail the run. The warning-level rules mark patterns that are usually worth changing but have legitimate uses that no syntax separates; each was measured on real codebases before it was added.
8
-
9
- ## Standards and rules
10
-
11
- | Standard | Implemented automation | Limit |
12
- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13
- | No `any`; keep unknown external values at a seam | `typescript/no-explicit-any`; type-aware `no-unsafe-*`; `no-unsafe-dictionary-type`, `no-known-value-widening`, `no-unknown-returns` and `no-unknown-type-aliases` | Legitimate parsers may need narrow exceptions; imported domain types require type-aware analysis |
14
- | Parse external data at a seam instead of probing it | `wyse/no-runtime-typeof` warns about `typeof` outside type guards; `typeof x === "undefined"` probes are allowed | Narrowing a known primitive union with `typeof` is legitimate; the warning prompts a schema without blocking |
15
- | Prefer validated narrowing and `satisfies` | Native `no-non-null-assertion`; type-aware `no-unsafe-type-assertion`, `no-unnecessary-type-assertion`; `no-chained-type-assertions` and `no-widen-then-assert` | No syntactic rule proves a runtime schema or invariant correct |
16
- | Guards have truthful, recognisable names | Custom `wyse/type-guard-name` | Names do not prove predicate correctness |
17
- | Handle every union case | Type-aware `typescript/switch-exhaustiveness-check` | The compiler still owns `never` assignments and valid-state modelling |
18
- | Remove redundant guards and dead code | Correctness/suspicious categories; unused variables/private members; type-aware `no-unnecessary-condition` | Whether validation belongs at another seam needs review |
19
- | Keep branches readable | Custom `wyse/no-deep-ternary`; native `curly` | Complexity, line count and parameter count are not used as design proxies |
20
- | Handle asynchronous work and errors | Native correctness/promise checks, `no-empty`, `no-useless-catch`; typed floating/misused promises, `await-thenable`, `return-await`, `only-throw-error` | Meaningful recovery, domain results, retry policy and error context need review |
21
- | Represent expected failures instead of silencing them | Custom `wyse/no-swallowed-errors` (warning) | Parser, filesystem-probe and existing-promise catches are allowed; other expected failures need a suppression with a reason |
22
- | Avoid boolean control parameters | Custom `wyse/explicit-api-states` (warning) | Final boolean parameters of `set*`/`update*` functions are allowed; a boolean that is domain data still warns |
23
- | Named exports and imports from an owner | `import/no-default-export`, `first`, `no-duplicates`, `no-self-import`, `no-unassigned-import`; warnings from `import/no-cycle` and `oxc/no-barrel-file` | Internal barrel ownership and public package entrypoints need architectural context; the barrel rule counts only `export *` fan-out above its default threshold |
24
- | Name files after their concept | Custom `wyse/no-generic-file-suffix` | Files named after a domain export, such as `agentPolicy.ts` exporting `AgentPolicy`, and standard header policies are allowed |
25
- | Import from the authoritative module | Custom `wyse/no-forwarding-module` (warning) | Package entrypoints and framework route files are allowed; other frameworks' mandated re-export files may need an exception |
26
- | Remove middle men | Custom `wyse/no-pass-through-function` (warning) | Only identifier callees are checked, because method facades usually hide an instance |
27
- | No shipped console logging | `eslint/no-console`; narrow config/script exceptions | Redaction, sensitive fields and sufficient structured context need review |
28
- | Never add code comments | Custom `wyse/no-comments`; tool directives and JavaScript type JSDoc remain allowed | Projects can allow further directive patterns; the rule cannot tell whether the removed intent reached a name, type, test or commit |
29
- | Parse configuration at startup | Custom `wyse/no-env-access` with the project's `env` owners | Owners must be named by the project; the rule does not check that the owner validates what it reads |
30
- | Keep privileged modules out of other runtimes | Custom `wyse/import-boundaries` from the project's `boundaries` | Only static, dynamic-literal and re-export specifiers are checked; `require` and computed imports are not |
31
- | Enforce authorisation at the protected operation | Custom `wyse/server-action-guard` with the project's `serverActionGuards` | A guard's name cannot prove it authorises the operation or checks the right resource |
32
- | Control time and randomness | Custom `wyse/no-ambient-clock` and `wyse/no-ambient-random` with the project's `clock` and `random` owners | Production code only; owners must be named by the project |
33
- | Harden Electron apps | Custom `wyse/electron-*` rules and a renderer import boundary in the `electron` profile | Checks literal `webPreferences`, `remote` usage and raw `ipcRenderer` exposure through `contextBridge` |
34
- | Prefer local state and avoid accidental mutation | `prefer-const`, `no-param-reassign`; React immutability checks; typed `prefer-readonly` | Local accumulator mutation is allowed; ownership is not inferred from a variable name |
35
- | Avoid repeated growing copies | Native `oxc/no-accumulating-spread`; `no-array-filter-map` and `no-reduce-accumulator-copy`; optional e18e profile | No performance improvement is claimed without measurement |
36
- | Avoid duplicate React state and effect chains | React and React Compiler native rules; `react-you-might-not-need-an-effect` rules | Product interactions still need runtime verification |
37
- | Basic accessible JSX | Native `jsx-a11y` correctness/suspicious rules in the React profile | Keyboard behaviour, accessible names through custom components and visual contrast require application evidence |
38
- | Tests execute assertions and are not silently skipped | Native Jest/Vitest focused/disabled/conditional/standalone/valid expectation checks | Valid helper-based assertions are allowed; assertion-count rules are not enabled blindly |
39
- | Tests wait for observable outcomes | Custom `wyse/no-fixed-test-delay` | Genuine timer tests need a scoped exception; unknown wait helpers are not guessed |
40
- | Tests have a non-vacuous oracle | Custom `wyse/no-vacuous-test-assertions` | Independent expected results and meaningful behavioural coverage still need review |
41
- | Tests protect behaviour, not edits | Custom `wyse/no-edit-proof-assertions` | Only existence checks of imports and `typeof … "function"` checks are recognised |
42
- | Tests do not hide slow or flaky work | Custom `wyse/no-test-timeout-overrides` (warning, unit tests only) | End-to-end suites legitimately need longer timeouts and are excluded |
43
- | Tests sit beside the module they cover | Custom `wyse/companion-has-owner` (warning) | Cross-module tests should be named `<behaviour>.integration.test` or live under `tests/` |
44
- | Avoid module mocking of runnable dependencies | `wyse/no-module-mocking` | External systems can justify a file-scoped exception |
45
- | Consistent layout and imports | Oxfmt; custom `wyse/statement-spacing` | Blank lines are only required around imports, multiline blocks, multiline declarations and returns, so deliberate groups of one-line statements stay together |
46
-
47
- `no-unnecessary-condition` is a nursery rule in Oxlint 1.86.0. It is enabled only with `typeAware`. The usual native nested-ternary rule conflicts with Oxfmt 0.71.0 because one tool adds parentheses and the other removes them. The custom depth rule allows one nested level and reports the third ternary level, without prescribing parentheses.
48
-
49
- ## Custom rules
50
-
51
- All rules use Oxlint's supported AST and lexical scopes and their tests sit beside their source, in `src/rules` or, for rules adapted from anti-slop, `src/vendor/anti-slop`. Only `statement-spacing` has an autofix, which inserts blank lines; its engine is adapted from ESLint Stylistic's `padding-line-between-statements`. `companion-has-owner` and `no-forwarding-module` read sibling files and the nearest `package.json`. The others are dependency-free.
52
-
53
- | Rule | Rejected pattern | Allowed or outside scope |
54
- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
55
- | `type-guard-name` | A named predicate such as `checkUser(value): value is User` | `isUser`, `hasId`, assertion functions, anonymous callbacks and computed method names |
56
- | `no-deep-ternary` | A conditional expression nested inside two conditional expressions | One nested level; a separate function's expressions start a new depth |
57
- | `no-fixed-test-delay` | Awaited literal timer delays and known Playwright fixture `waitForTimeout` calls in recognised tests | Local functions that shadow timer/test names, nonliteral delays, and unknown custom helpers |
58
- | `no-vacuous-test-assertions` | Literal-to-literal equality expectations or the same identifier on both sides of `toBe`, `toEqual`, `toStrictEqual` | Computed values, property getters, reversed meaningful expectations and promise resolution matchers |
59
- | `no-comments` | Line, block and JSDoc comments, including JSDoc in TypeScript files | Shebangs, JSDoc with type tags in JavaScript files, and tool directives: lint suppressions, `@ts-expect-error`, triple-slash references, `#__PURE__`, formatter and coverage ignores, bundler magic comments, test-environment and JSX pragmas; extra patterns through the `allow` option |
60
- | `no-env-access` | `process.env`, `import.meta.env` and destructuring `env` from either, outside `env` owners, config files and scripts | Local bindings named `process` and unrelated `env` properties |
61
- | `import-boundaries` | A value import, re-export or literal dynamic import from a configured scope whose specifier or resolved relative path matches a `deny` glob | Type-only imports and files outside the boundary's scope |
62
- | `server-action-guard` | An exported action in a `"use server"` module, or an inline `"use server"` function, whose first statement does not await a configured guard; indirect or default exports from server-action modules | Non-exported helpers in server-action modules and test files |
63
- | `statement-spacing` | A missing blank line after the imports, around a multiline block or a multiline declaration, including exported ones, or before `return` | Consecutive imports, consecutive one-line statements, and a type alias directly after the multiline declaration it derives from |
64
- | `no-edit-proof-assertions` | `toBeDefined`, `toBeTruthy` and similar on an imported binding or namespace member; `typeof X` or `toBeTypeOf` against a string on imports; `toBeTypeOf("function")` on anything; `toHaveProperty` on a namespace | Assertions on values produced by running code |
65
- | `no-test-timeout-overrides` | Per-test or per-suite timeout and retry options, `test.setTimeout`, `test.slow()`, `describe.configure`, `vi.setConfig` and `jest.setTimeout` in unit tests | End-to-end files under `e2e/` or named `*.e2e.*` |
66
- | `companion-has-owner` | A `.test`, `.spec` or `.bench` file whose name prefix matches no sibling module or directory, nor its own directory | Files with an `integration` or `e2e` segment and files under `tests/`, `test/`, `__tests__/` or `e2e/` |
67
- | `no-generic-file-suffix` | A file named `*Contract`, `*Policy`, `policy` or `contract` in camel, kebab, snake or dot form | Test and story files, standard header policies such as content security policy, and files exporting a binding with the file's name |
68
- | `no-forwarding-module` | A module made only of re-exports, or imports re-exported unchanged | Package entrypoints in the nearest `package.json`, Next.js `app` special files and `pages`, root `middleware`/`proxy`/`instrumentation`, modules with directives or side-effect imports |
69
- | `no-pass-through-function` | A named function whose body only calls an identifier with exactly its own parameters, in order | Zero-parameter functions, method callees, type predicates and `useEffectEvent` callees |
70
- | `explicit-api-states` | A `boolean`, optional `boolean`, or boolean-defaulted parameter on an exported function, exported class constructor or public method | Destructured parameters, accessors, private members, overloads and the final boolean of a `set*`/`update*` function |
71
- | `no-swallowed-errors` | A `catch` whose body only returns a fallback literal, and a `.catch` callback that is empty or returns one | Built-in parser and filesystem-probe try blocks, `response.json()`, and `.catch` on a promise another caller also receives |
72
- | `no-ambient-clock` | `Date.now()` and argument-less `new Date()`, including through `globalThis`/`window` | Parameter defaults, uncalled `Date.now` and provider functions that only return the read |
73
- | `no-ambient-random` | `Math.random()`, `crypto.randomUUID()`, and `randomUUID`/`randomInt` from `node:crypto` | The same injection seams; `randomBytes` and `getRandomValues`, which generate secrets |
74
- | `electron-no-remote-module` | `remote` from `electron`, and `@electron/remote` by import, `require`, dynamic import or re-export | Other Electron APIs |
75
- | `electron-secure-web-preferences` | `webPreferences` without literal `contextIsolation: true`, `sandbox: true` and `nodeIntegration: false`, or with insecure web security, insecure content, experimental or Blink features; overrides through later keys, spreads or computed keys | Preferences passed from a variable the rule cannot resolve |
76
- | `electron-no-raw-ipc-renderer-exposure` | Exposing `ipcRenderer` or its methods through `contextBridge`, forwarding renderer-chosen channels or arguments, or passing renderer callbacks straight to `on`/`once` | Narrow, named preload functions with fixed channels |
77
-
78
- Test-specific custom rules run on the default test globs, `tests` paths and any explicit Jest or Vitest profile scopes. Their import and binding recognition is deliberately narrower than unrestricted text matching. The tests are the executable reference for supported aliases, shadowing and callback forms.
79
-
80
- ## Type, data and test-double rules
81
-
82
- The base config also enables these rules as errors:
83
-
84
- ```text
85
- no-array-filter-map
86
- no-reduce-accumulator-copy
87
- no-chained-type-assertions
88
- no-conditional-empty-object-spread
89
- no-known-value-widening
90
- no-module-mocking
91
- no-object-parameters
92
- no-reflect-apply
93
- no-reflect-get
94
- no-shape-in-symbol-names
95
- no-unknown-returns
96
- no-unknown-type-aliases
97
- no-unsafe-dictionary-type
98
- no-widen-then-assert
99
- ```
100
-
101
- `no-runtime-typeof` is a warning with `allowInTypeGuards` enabled. Banning `typeof` outright blocks legitimate narrowing of known unions, while the warning still points towards parsing external data with a schema. Projects can turn it off at a validated input seam.
102
-
103
- The optional `effect` profile enables `no-manual-effect-error-tag` and `no-service-constructor-imports`.
104
-
105
- These rules do not have cross-file type knowledge. Their [source files](src/vendor/anti-slop/rules) and tests define the supported same-file alias, scope and inference cases. There is no blanket ban on legitimate `unknown` values or invariant-preserving `as const` expressions.
106
-
107
- ## Project-specific automation
108
-
109
- These policies are enforced once a project names the actual owners. They are intentionally not universal defaults:
110
-
111
- | Policy | Mechanism | Required project information |
112
- | --------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------- |
113
- | Parse configuration at startup | `env` option | Modules that own configuration parsing |
114
- | Keep privileged modules out of other runtimes | `boundaries` option | Each runtime's file scope, and the packages, aliases and paths it must not import |
115
- | Require access checks before server actions | `serverActionGuards` option | The guard functions that perform authorisation |
116
- | Control time and randomness | `clock` and `random` options | Modules that own the clock and ID or random generation |
117
- | Harden Electron apps | `electron` option | Renderer globs when they are not `src/renderer/**` |
118
- | Generated assets and style imports | Shared ignores plus local patterns; Oxfmt Tailwind configuration | Actual generators, CSS preprocessors, Tailwind config or stylesheet |
119
-
120
- ## Compiler settings and review work
121
-
122
- For new TypeScript projects, use `strict`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `noImplicitOverride` and `noFallthroughCasesInSwitch`. The package exports them as `@jameswyse/oxc-config/tsconfig` for a project's `tsconfig.json` to extend. Lint configuration never applies them, because changing them in an existing project is a deliberate migration.
123
-
124
- Lint cannot establish correct authorisation, tenant isolation, schema validation, secret redaction, transaction boundaries, idempotency, crash recovery, concurrency ownership, dependency justification, useful module depth, constructive domain modelling or independent test oracles. These remain review and verification work. File naming, positional parameters, boolean inputs, `Partial<T>`, wrappers and abstraction counts have legitimate domain/framework exceptions and are not rejected through universal heuristics.
125
-
126
- ## Oxc API references
127
-
128
- The implementation follows the current [Oxlint configuration API](https://oxc.rs/docs/guide/usage/linter/config.html), [JS plugin API](https://oxc.rs/docs/guide/usage/linter/writing-js-plugins.html), [type-aware setup](https://oxc.rs/docs/guide/usage/linter/type-aware.html), and [Oxfmt options](https://oxc.rs/docs/guide/usage/formatter/config-file-reference.html). Absolute plugin specifiers keep imported configs independent of the consumer's working directory. Side-effect import sorting is explicitly disabled.
129
-
130
- ## Rules considered and dropped
131
-
132
- `no-call-count-assertions` would have rejected `toHaveBeenCalledTimes`, `toHaveBeenNthCalledWith` and `mock.calls` counting. On three codebases, 36 of 40 sampled findings were legitimate: the count was the behaviour under test, such as caching, retry, deduplication or firing once. No syntax separated them from incidental counts, so it was dropped.
133
-
134
- Rules for safety comments on type assertions, a blank line between every top-level statement, and rejecting `unknown` parameters are not included. The first conflicts with `no-comments`, the second split deliberate groups of related declarations, and the third rejected legitimate unparsed input seams. Effect rules that reject `_tag` comparisons, prefer `Match` or reject literal tagged construction are not included either, because discriminant checks are the preferred way to narrow a union and TypeScript already checks literal variants.
3
+ Rules are errors, which fail the run, unless marked as warnings. Warnings flag patterns that have legitimate uses, so they report without failing. Custom rules use the `wyse/` prefix. Each one's tests sit beside it in `src/rules` or `src/vendor/anti-slop/rules` and show exactly what it reports.
4
+
5
+ Framework profiles and project options add rules only when enabled. The [configuration guide](docs/configuration.md) explains how to turn them on.
6
+
7
+ ## Base rules
8
+
9
+ Oxlint's correctness and suspicious categories are errors. Unused variables are errors unless their names start with `_`, and suppression comments that no longer suppress anything are errors too. `no-underscore-dangle` is off, so fields such as `_id` and `__typename` stay valid.
10
+
11
+ Native rules also catch legacy and error-prone built-ins: `no-var`, `no-proto`, `no-new-func`, `no-new-wrappers`, `no-array-constructor`, `no-prototype-builtins`, `prefer-object-has-own`, `prefer-rest-params`, `prefer-regex-literals`, `import/no-mutable-exports`, `oxc/no-const-enum`, and Unicorn's `new-for-builtins`, `no-instanceof-array`, `no-new-buffer`, `prefer-node-protocol`, `prefer-import-meta-properties`, `prefer-response-static-json`, `prefer-string-trim-start-end`, `prefer-array-find`, `prefer-array-flat-map` and `prefer-array-some`.
12
+
13
+ ## Types and external data
14
+
15
+ Native rules: `typescript/no-explicit-any`, `no-non-null-assertion`, `no-unsafe-function-type` and `no-import-type-side-effects`. `consistent-type-assertions` allows only `as` assertions and rejects them on object and array literals, and `method-signature-style` requires property signatures such as `run: () => void`. `ban-ts-comment` rejects `@ts-ignore` and `@ts-nocheck`, and requires a reason after `--` on `@ts-expect-error`.
16
+
17
+ Type-aware checking, which is on by default, adds the `no-unsafe-*` rules, `no-unsafe-type-assertion`, `no-unnecessary-type-assertion`, `switch-exhaustiveness-check`, `prefer-reduce-type-parameter`, `restrict-plus-operands` with every mixed-type allowance turned off, and `no-unnecessary-condition`, a nursery rule in Oxlint 1.86. `typescript/no-deprecated` is a warning outside tests.
18
+
19
+ - `wyse/no-runtime-typeof` (warning): `typeof` outside a type guard. `typeof x === "undefined"` passes, as does `typeof` on a variable or parameter declared in the same file with a union type that has no `unknown` or `any` member. Turn it off in modules that validate external input.
20
+ - `wyse/no-generic-object-guard`: a named helper such as `isRecord` whose whole body proves only that a value is a non-null object, through `typeof`, `instanceof Object`, prototype or `Object.prototype.toString` checks. Guards that also check a field pass, as do inline callbacks. It also reports any guard that takes `unknown` or `any` and claims a specific type while only checking that the value exists, or always returns `true`.
21
+ - `wyse/no-hardcoded-credential`: a string containing a private key or a token in a well-known format, such as an AWS access key or a GitHub, Stripe live, Slack, Google, Anthropic or OpenAI key. Placeholders containing `example`, `fake`, `dummy`, `placeholder`, `redacted` or a run such as `abcdefgh` pass.
22
+ - `wyse/no-disabled-tls-verification`: `rejectUnauthorized: false`, or setting `NODE_TLS_REJECT_UNAUTHORIZED` to `"0"`.
23
+ - `wyse/no-secret-fallback`: a string fallback for an environment variable whose name contains `SECRET`, `TOKEN`, `PASSWORD`, `PRIVATE`, `CREDENTIAL`, `SALT`, `SIGNING` or `API_KEY`, or ends in `KEY`, such as `process.env.SESSION_SECRET ?? "dev"` or a destructuring default.
24
+ - `wyse/type-guard-name`: a named type predicate such as `checkUser(value): value is User`. Names such as `isUser` and `hasId` pass, as do assertion functions and anonymous callbacks.
25
+ - `wyse/no-unknown-returns`: a declared return type of `unknown` or `Promise<unknown>`.
26
+ - `wyse/no-unknown-type-aliases`: a type alias that resolves to `unknown`.
27
+ - `wyse/no-unsafe-dictionary-type`: a dictionary type whose values are `unknown`, `any`, `object` or `{}`.
28
+ - `wyse/no-object-parameters`: a parameter typed `object`, directly or through an alias.
29
+ - `wyse/no-known-value-widening`: a known value stored under a broader declared type, such as `const value: unknown = {}`.
30
+ - `wyse/no-widen-then-assert`: widening a known value, then asserting it to a narrower type.
31
+ - `wyse/no-chained-type-assertions`: chained assertions such as `value as unknown as User`.
32
+ - `wyse/no-reflect-apply` and `wyse/no-reflect-get`: `Reflect.apply` and `Reflect.get`.
33
+
34
+ These custom rules check one file at a time, without type information from other files. None of them bans `unknown` outright or rejects `as const`.
35
+
36
+ ## Control flow and errors
37
+
38
+ Native rules: `curly`, `eqeqeq`, `no-empty`, `no-useless-catch`, Oxlint's promise checks, `array-callback-return`, `no-case-declarations`, `no-constructor-return`, `no-fallthrough`, `no-loop-func`, `no-self-compare`, `no-unreachable-loop`, `no-useless-assignment` and `unicorn/error-message`. JavaScript files also get `no-throw-literal` and `prefer-promise-reject-errors`. Type-aware checking adds floating and misused promise checks, `await-thenable`, `return-await`, `only-throw-error`, `prefer-promise-reject-errors` and `use-unknown-in-catch-callback-variable`.
39
+
40
+ - `wyse/no-deep-ternary`: a ternary nested inside two others. It replaces Oxlint's nested-ternary rule, which conflicts with Oxfmt over parentheses.
41
+ - `wyse/no-swallowed-errors` (warning): a `catch` that only returns a fallback literal, a `.catch` callback or `.then` rejection callback that is empty or only returns one, and `Promise.allSettled` results that are never read. Effect's `orElseSucceed`, `orElse`, `catchAll` and `catchAllCause` count when they recover to a fallback literal or `Effect.void`. Built-in parser and filesystem-probe `try` blocks, `response.json()`, `.catch` on a promise another caller also receives, and Effect's `catchTag` all pass.
42
+ - `wyse/explicit-api-states` (warning): a boolean parameter on an exported function, exported class constructor or public method. Destructured parameters, accessors, private members, overloads, the subject of an `asserts` or `is` return type, and the final boolean of a `set*` or `update*` function pass.
43
+ - `wyse/no-unsettled-promise`: a `new Promise` executor that runs code but never calls `resolve` or `reject`, or calls them only inside an optional call such as `server?.close(resolve)`. Executors that do nothing, such as `() => {}`, pass. Test files aren't checked.
44
+ - `wyse/no-unchecked-index-search`: an `indexOf`, `lastIndexOf`, `findIndex` or `findLastIndex` result passed straight to `splice`, `toSpliced`, `with`, `at` or `slice`, where -1 counts from the end.
45
+ - `wyse/no-unchecked-fetch-response`: reading a `fetch` response body with `json()`, `text()` or a similar method without checking `ok` or `status`. A response passed to another function passes. Test files aren't checked.
46
+
47
+ ## Modules, files and names
48
+
49
+ Native rules: `import/first`, `import/no-default-export`, `import/no-duplicates`, `import/no-self-import` and `import/no-unassigned-import`. `import/no-cycle` and `oxc/no-barrel-file` are warnings. Default exports are allowed in the files that tools load them from, listed in the [configuration guide](docs/configuration.md#select-framework-and-runtime-profiles).
50
+
51
+ - `wyse/no-forwarding-module` (warning): a module made only of re-exports, or one that re-exports its imports unchanged. Package entrypoints, Next.js `app` special files and `pages`, root `middleware`, `proxy` and `instrumentation` files, and modules with directives or side-effect imports pass.
52
+ - `wyse/no-import-time-effects` (warning): starting a timer, `fetch`, top-level `await`, opening a `BroadcastChannel`, `WebSocket`, `EventSource` or `Worker`, calling `addEventListener` or `listen`, or registering `process.on` handlers when a module is imported, including inside immediately invoked functions and static class members. Config files, scripts, tests, and entrypoints such as `main`, `server`, `worker` and `instrumentation` files and `bin`, `cli`, `deploy`, `tools`, `test`, `e2e` and `fixtures` directories pass.
53
+ - `wyse/no-pass-through-function` (warning): a named function whose body only calls another function with its own parameters, in order. Zero-parameter functions, method calls, type predicates and `useEffectEvent` callees pass.
54
+ - `wyse/no-generic-file-suffix`: a file named `*Contract`, `*Policy`, `policy` or `contract`, in any case style. Test and story files pass, as do header policies such as a content security policy and files that export a binding with the file's name, such as `agentPolicy.ts` exporting `AgentPolicy`.
55
+ - `wyse/no-shape-in-symbol-names`: a name containing "shape", in any letter case.
56
+
57
+ ## Comments, suppressions and logging
58
+
59
+ - `wyse/no-comments`: line, block and JSDoc comments. Shebangs, JSDoc type tags in JavaScript files and tool directives pass. The directives include lint suppressions, `@ts-expect-error`, triple-slash references, `#__PURE__`, formatter and coverage ignores, bundler magic comments, and test-environment and JSX pragmas. The `allow` option adds more patterns.
60
+ - `wyse/require-suppression-reason`: an `oxlint-disable` or `eslint-disable` directive that names no rule, or gives no reason after `--`. Oxlint applies a blanket `oxlint-disable` or `oxlint-disable-line` to this rule too, so it can't report those two forms. It does report a blanket `oxlint-disable-next-line`.
61
+ - `eslint/no-console`: console calls outside config files and scripts.
62
+
63
+ ## Mutation and copying
64
+
65
+ Native rules: `prefer-const`, `no-param-reassign` and `oxc/no-accumulating-spread`. Type-aware checking adds `prefer-readonly`.
66
+
67
+ - `wyse/no-array-filter-map`: adjacent `filter` and `map` passes over an array.
68
+ - `wyse/no-reduce-accumulator-copy`: copying a reducer's accumulator with `Object.assign`, `Array.from` or an array copy method.
69
+ - `wyse/no-conditional-empty-object-spread`: spreading a conditional empty object to omit fields, such as `...(id ? { id } : {})`.
70
+
71
+ ## Project ownership
72
+
73
+ These rules run only once the project names its owners through the [project boundary options](docs/configuration.md#enforce-project-boundaries).
74
+
75
+ - `wyse/no-env-access` (`env`): reading `process.env` or `import.meta.env` outside the owners, config files and scripts. That includes destructuring `env`, `globalThis.process.env`, `global.process.env`, `process["env"]` and `Reflect.get(process, "env")`, including with `"env" as const`, importing `env` from `node:process` or `process`, reading `env` from those modules' default, namespace or literal dynamic import, and re-exporting `env`, the default or the whole module from them with `export { ... } from` or `export * from`. Other `node:process` imports and re-exports, such as `argv`, type-only re-exports, and a `process` property on `window` or `self` pass. It doesn't check that the owner validates what it reads.
76
+ - `wyse/import-boundaries` (`boundaries`): a value import, re-export, literal dynamic import, or `require` call, including a loader made with `createRequire` from `node:module`, that matches a `deny` glob. Type-only imports and `import("…")` types pass unless the boundary sets `types: true`. It doesn't check computed imports.
77
+ - `wyse/server-action-guard` (`serverActionGuards`): a `"use server"` action that doesn't start by awaiting a guard, and indirect or default exports from server-action modules. Non-exported helpers and test files pass.
78
+ - `wyse/no-ambient-clock` (`clock`): `Date.now()`, `Date()` and argument-less `new Date()`, including through `globalThis` or `window`. It also reports argument-less `new TZDate()` and `new TZDateMini()` and one-argument `TZDate.tz(zone)` from `@date-fns/tz`, `@date-fns/tz/date` or `@date-fns/tz/date/mini`, and calls to the date-fns helpers that read the current time: `constructNow`, the `startOf` and `endOf` helpers for today, tomorrow and yesterday, `isToday`, `isTomorrow`, `isYesterday`, `isPast`, `isFuture`, the `isThis*` helpers, `formatDistanceToNow`, `formatDistanceToNowStrict`, `isMatch`, and `isMatch` and `isMatchWithOptions` from `date-fns/fp` at the call that supplies their last curried argument. Helpers count whether they're imported by name, through a namespace, from a `date-fns/<name>` subpath or by literal dynamic import. Re-exporting one of these helpers with `export { ... } from`, or a module that contains one, such as `date-fns` or `date-fns/fp`, with `export * from`, also reports, because calls through the re-export no longer resolve to date-fns. Helpers that compare with a given date, such as `isSameDay`, partly applied `date-fns/fp` helpers, and re-exports of `TZDate`, `TZDateMini`, other date-fns helpers or types pass. Parameter defaults, uncalled `Date.now`, provider functions that only return the read, and test files pass. Parentheses, `as`, `satisfies` and `!` don't change the result, so `(Date.now as typeof Date.now)()` reports and `{ now: () => startOfToday() as Date }` passes.
79
+ - `wyse/no-ambient-random` (`random`): `Math.random()`, `crypto.randomUUID()`, and `randomUUID` or `randomInt` from `node:crypto`. The same exceptions and wrapper handling apply. `randomBytes` and `getRandomValues` pass because they generate secrets.
80
+
81
+ ## Tests
82
+
83
+ These rules run on the default test globs, the `tests` paths and the Jest and Vitest profile scopes. The `vitest` and `jest` profiles add native checks for focused, disabled and conditional tests, standalone expectations, valid `expect` calls, interpolated snapshots and redundant async `expect` callbacks. They also require the specific matcher, such as `toStrictEqual` over `toEqual`, `toHaveLength`, `toContain`, and comparison or equality matchers over `toBe(true)`. The `jest` profile rejects `done` callbacks.
84
+
85
+ - `wyse/no-fixed-test-delay`: an awaited literal timer delay, Playwright's `waitForTimeout`, or `yield* Effect.sleep` with a fixed duration. Genuine timer tests need a scoped exception.
86
+ - `wyse/no-vacuous-test-assertions`: an expectation that compares two literals, or the same identifier on both sides of `toBe`, `toEqual` or `toStrictEqual`.
87
+ - `wyse/no-edit-proof-assertions`: an assertion that only proves an import exists or is a function, such as `toBeDefined` on an import, `toBeTypeOf("function")` or `toHaveProperty` on a namespace.
88
+ - `wyse/no-optional-test-action`: a statement directly in a test body that an optional call can skip, such as `button?.click()` or `await dismiss?.click()`. Hooks and callbacks inside the test pass.
89
+ - `wyse/no-derived-test-expectation` (warning): an expected value that calls a function imported from production code, such as `toBe(formatLabel(input))`, directly, through a namespace import or through a `const` such as `expected`. `.not` assertions, functions the actual value also calls, and imports from test, fixture, e2e and helper paths pass.
90
+ - `wyse/no-uncontrolled-test-clock`: `Date.now()` or argument-less `new Date()` in a test file that never calls `useFakeTimers`, `setSystemTime`, `mock.timers.enable`, `clock.install` or `clock.setFixedTime`. Fake timers set up with `apis`, `toFake` or `doNotFake` options that leave `Date` real don't count, and neither does Effect's `TestClock`, which doesn't control `Date`. Playwright and end-to-end files pass.
91
+ - `wyse/no-test-timeout-overrides` (warning): timeout and retry overrides such as `test.setTimeout`, `test.slow()`, `describe.configure`, `vi.setConfig`, `jest.setTimeout` and `jest.retryTimes`. Setting retries to 0 passes. End-to-end files under `e2e/` or named `*.e2e.*` pass.
92
+ - `wyse/companion-has-owner` (warning): a `.test`, `.spec` or `.bench` file with no matching sibling module or directory. Files with an `integration` or `e2e` segment, and files under `tests/`, `test/`, `__tests__/` or `e2e/`, pass.
93
+ - `wyse/no-module-mocking`: Vitest, Jest and `node:test` module mocking. Replace dependencies through real interfaces, or add a file-scoped exception for an external system.
94
+
95
+ ## Framework profiles
96
+
97
+ - `react` adds React and React Compiler rules, `jsx-a11y` correctness and suspicious rules, `react-you-might-not-need-an-effect`, and e18e performance rules outside tests. It includes `react/rules-of-hooks` outside end-to-end and `tests` paths, `no-unknown-property`, `checked-requires-onchange-or-readonly` and `jsx-no-useless-fragment`. It also adds `wyse/no-get-password-form`: a `<form>` with a password or one-time-code input that can submit by GET. An input belongs to the form it sits inside, or to the form its `form` attribute names. The form submits by GET when its `action` is missing or a string and its `method` is missing or anything but `post` or `dialog`, or when one of its submit buttons sets `formMethod` to anything but `post` or `dialog`.
98
+ - `nextjs` adds the React rules plus Next.js rules.
99
+ - `effect` adds `wyse/no-manual-effect-error-tag` and `wyse/no-service-constructor-imports`. The second reports a project-local `make*` import used as an uncalled value, such as `yield* makeIssueService`. Calling a `make*` factory with arguments passes.
100
+ - `electron` adds a renderer import boundary and these rules:
101
+ - `wyse/electron-no-remote-module`: `remote` from `electron`, or `@electron/remote`, by import, `require`, dynamic import or re-export.
102
+ - `wyse/electron-secure-web-preferences`: `webPreferences` without literal `contextIsolation: true`, `sandbox: true` and `nodeIntegration: false`, or with web security disabled, insecure content, or experimental or Blink features enabled. Later keys, spreads and computed keys that override these settings are reported too. Preferences passed from a variable the rule can't resolve pass.
103
+ - `wyse/electron-no-raw-ipc-renderer-exposure`: exposing `ipcRenderer` or its methods through `contextBridge`, forwarding renderer-chosen channels or arguments, or passing renderer callbacks straight to `on` or `once`.
104
+
105
+ ## Formatting
106
+
107
+ Oxfmt formats the code. `wyse/statement-spacing` requires a blank line after the imports, around multiline blocks and declarations, and before `return`. Consecutive imports, consecutive one-line statements, and a type alias directly after the multiline declaration it derives from stay together. It's the only custom rule with an autofix.
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
@@ -24,6 +24,7 @@ export interface ImportBoundary {
24
24
  ignore?: string[];
25
25
  deny: string[];
26
26
  message: string;
27
+ types?: boolean;
27
28
  }
28
29
  export type SharedLintConfig = OxlintConfig & {
29
30
  ignorePatterns: string[];