@rdjperron/lint 0.7.0 → 0.8.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.
package/README.md CHANGED
@@ -1,22 +1,24 @@
1
1
  # @rdjperron/lint
2
2
 
3
- One Oxlint JS plugin, `rdjperron`, with twenty rules against low-evidence code, the config data a
4
- project spreads into its own `vite.config.ts`, the check that proves every rule still fires, and
5
- the repository checks that hold a repo's documents to their own conventions.
3
+ One Oxlint JS plugin, `rdjperron`, with twenty-two rules against low-evidence code and unchecked
4
+ Convex functions, the config data a project spreads into its own `vite.config.ts`, the check that
5
+ proves every rule still fires, and the repository checks that hold a repo's documents to their own
6
+ conventions.
6
7
 
7
8
  | Entry | What it gives you |
8
9
  | ------------------------ | ---------------------------------------------------------------------------------- |
9
- | `@rdjperron/lint` | The plugin. One `jsPlugins` entry, one prefix, twenty rules. |
10
+ | `@rdjperron/lint` | The plugin. One `jsPlugins` entry, one prefix, twenty-two rules. |
10
11
  | `@rdjperron/lint/config` | `recommended`, the override builders, and the `fmt`, `ignore` and `staged` blocks. |
11
12
  | `@rdjperron/lint/repo` | `checkRepo`, three checks on a repo's Markdown links and research names. |
12
- | `rdjperron-lint-verify` | The command that proves every rule still fires. No config, no arguments. |
13
+ | `rdjperron-lint-verify` | The command that proves every rule still fires. No config, one optional flag. |
13
14
 
14
15
  Seventeen of the rules are a copy of [dmmulroy/anti-slop](https://github.com/dmmulroy/anti-slop),
15
16
  owned and edited here (`src/anti-slop/VENDORED.md` in this repository records the commit). The
16
- other three are written here.
17
+ other five are written here.
17
18
 
18
19
  Measured on vite-plus 0.3.0, 0.3.1, 0.3.2 and 0.3.3, which is oxlint 1.79.0 through 1.83.0
19
- (2026-09-19). All twenty rules fired on all four.
20
+ (2026-10-12). All twenty-two rules fired on all four, and so did Convex's own plugin loaded through
21
+ `convex()`.
20
22
 
21
23
  ## Install
22
24
 
@@ -28,6 +30,13 @@ That is the whole installation. `@oxlint/plugins`, which the rules import `defin
28
30
  types from, is an ordinary dependency here and arrives with the package; there is no version of it
29
31
  for you to pin or to move.
30
32
 
33
+ `@convex-dev/eslint-plugin` is an optional peer, `^5.0.0`. Install it beside this package in a
34
+ project that calls `convex()`, which loads it:
35
+
36
+ ```bash
37
+ npm install --save-dev @convex-dev/eslint-plugin
38
+ ```
39
+
31
40
  `vite-plus` is an optional peer, `>=0.3.0`, so a project on plain oxlint can still load the plugin.
32
41
  The floor is `react()`, not the rules: oxlint 1.77.0, which vite-plus 0.2.9 declares, has no
33
42
  `react/immutability`, `react/purity` or `react/refs`, and refuses to build a config that names them.
@@ -91,20 +100,22 @@ specifier inside an `extends` object is refused; a package name is what works.
91
100
 
92
101
  ### The config exports
93
102
 
94
- | Export | What it is |
95
- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
96
- | `plugin` | `{ name: "rdjperron", specifier: "@rdjperron/lint" }`. |
97
- | `RULE_IDS`, `RuleId` | The twenty prefixed ids, as a readonly tuple and as a type. |
98
- | `recommended` | The config object for `lint.extends`: the plugin entry, the rules below, the built-in choices, the plugin set, the categories and `options.typeAware`. |
99
- | `react(files, options?)` | The hooks rules, the compiler's rules and the globals. `options.env` is the environment those files run in, `{ browser: true }` by default, so a React Native consumer names its own. `options.useEffectAllowedIn` names the one file allowed to call `useEffect`, puts its name in every finding, and switches both `no-use-effect` and `react/exhaustive-deps` off for it. |
100
- | `i18n(files, options?)` | `rdjperron/no-hardcoded-jsx-text` at error. `options.pattern` sets the word pattern. |
101
- | `expo(files, options?)` | The React Native facts: `static-public-env` at error, `typescript/no-require-imports` relaxed for asset extensions, `unicorn/no-array-sort` off. `options.assetExts` adds your `metro.config.js` extensions, `options.reactFiles` adds the two `react/*` rules an Expo app needs. |
102
- | `restrictedImports(…)` | One `no-restricted-imports` override per file set, expanded from one declaration. See below. |
103
- | `tests(files)` | `any`, `!`, `no-runtime-typeof` and the safety-comment rule off, with the reason on each. |
104
- | `decoders(files, options?)` | `no-runtime-typeof` and `no-unknown-parameters` at error everywhere, each hinting at the files in `files`, and `no-runtime-typeof` off inside them. `options.hint` replaces the generated sentence. |
105
- | `fmt` | `printWidth` 100, double quotes, semicolons, two spaces. |
106
- | `ignore` | Dependencies, build output, the AI agent directories and the Vitest JSON report. |
107
- | `staged` | `vp check --fix` on JS and TS sources. |
103
+ | Export | What it is |
104
+ | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
105
+ | `plugin` | `{ name: "rdjperron", specifier: "@rdjperron/lint" }`. |
106
+ | `RULE_IDS`, `RuleId` | The twenty-two prefixed ids, as a readonly tuple and as a type. |
107
+ | `recommended` | The config object for `lint.extends`: the plugin entry, the rules below, the built-in choices, the plugin set, the categories and `options.typeAware`. |
108
+ | `react(files, options?)` | The hooks rules, the compiler's rules and the globals. `options.env` is the environment those files run in, `{ browser: true }` by default, so a React Native consumer names its own. `options.useEffectAllowedIn` names the one file allowed to call `useEffect`, puts its name in every finding, and switches both `no-use-effect` and `react/exhaustive-deps` off for it. |
109
+ | `i18n(files, options?)` | `rdjperron/no-hardcoded-jsx-text` at error. `options.pattern` sets the word pattern. |
110
+ | `expo(files, options?)` | The React Native facts: `static-public-env` at error, `typescript/no-require-imports` relaxed for asset extensions, `unicorn/no-array-sort` off. `options.assetExts` adds your `metro.config.js` extensions, `options.reactFiles` adds the two `react/*` rules an Expo app needs. |
111
+ | `restrictedImports(…)` | One `no-restricted-imports` override per file set, expanded from one declaration. See below. |
112
+ | `tests(files)` | `any`, `!`, `no-runtime-typeof` and the safety-comment rule off, with the reason on each. |
113
+ | `convex(files, options?)` | Convex's own rules from `@convex-dev/eslint-plugin`, loaded by the override itself, and the two `rdjperron/convex-*` rules. `options.wrapper` names the module that builds the checked constructors and `options.wrapperImports` its alias spellings, `options.returns: false` stops asking for `returns`, `options.tests` names test code that may `.collect()` and `.filter()`, `options.hint` adds the project's reason to each checked-constructor finding. See below. |
114
+ | `convexPlugin` | `{ name: "@convex-dev", specifier: "@convex-dev/eslint-plugin" }`, the entry `convex()` puts in its override. |
115
+ | `decoders(files, options?)` | `no-runtime-typeof` and `no-unknown-parameters` at error everywhere, each hinting at the files in `files`, and `no-runtime-typeof` off inside them. `options.hint` replaces the generated sentence. |
116
+ | `fmt` | `printWidth` 100, double quotes, semicolons, two spaces. |
117
+ | `ignore` | Dependencies, build output, the AI agent directories and the Vitest JSON report. |
118
+ | `staged` | `vp check --fix` on JS and TS sources. |
108
119
 
109
120
  `recommended` puts every rule at `error` except `no-hardcoded-jsx-text` and `static-public-env`,
110
121
  which belong to a translated app and to an Expo app and are left to `i18n()` and `expo()`. The
@@ -163,14 +174,115 @@ scope and the entry:
163
174
  The last two exist because both are a claim about the parent that is false, and a config that
164
175
  states something false about its parent is one rename away from lifting a ban.
165
176
 
177
+ ### `convex`, the backend's rules
178
+
179
+ ```ts
180
+ ...convex(["convex/**"], {
181
+ wrapper: "convex/lib/functions.ts",
182
+ tests: ["convex/**/*.test.ts", "convex/**/*.helpers.ts"],
183
+ }),
184
+ ```
185
+
186
+ That returns two overrides. The first, on `files`, loads `@convex-dev/eslint-plugin` through its
187
+ own `jsPlugins` and sets eleven of its rules, eight at error and three off. The second, on
188
+ `tests`, switches `no-collect-in-query` and `no-filter-in-query` off, because a test reads a
189
+ table it filled itself.
190
+
191
+ | Rule | `convex()` | Why |
192
+ | ----------------------------------------------- | ---------- | ------------------------------------------------------------------------------------ |
193
+ | `@convex-dev/no-old-registered-function-syntax` | error | A bare handler has nowhere to put a validator. |
194
+ | `@convex-dev/explicit-table-ids` | error | `db.get(id)` without its table is the old form; the table goes first. |
195
+ | `@convex-dev/no-filter-in-query` | error | `.filter()` reads every row an index would have skipped. |
196
+ | `@convex-dev/no-top-of-hour-crons` | error | Minute 0 UTC is the busiest point on the clock. |
197
+ | `@convex-dev/no-schema-import-cycle` | error | A file the schema imports that reads the schema finds it undefined. |
198
+ | `@convex-dev/no-duplicate-indexes` | error | An index that prefixes another answers no new query and slows every write. |
199
+ | `@convex-dev/no-process-env` | error | The typed `env` from `_generated/server` knows which variables exist. |
200
+ | `@convex-dev/no-collect-in-query` | error | `.collect()` fails once a table outgrows the query limits. |
201
+ | `@convex-dev/require-args-validator` | off | `rdjperron/convex-require-validators` reports the same omissions, on more functions. |
202
+ | `@convex-dev/import-wrong-runtime` | off | It reads `use node` anywhere in a file's first 100 characters, comments included. |
203
+ | `@convex-dev/require-access-control` | off | It cannot see a function built by a checked constructor. |
204
+
205
+ oxlint gives a JS plugin no type information, so `explicit-table-ids` and `no-collect-in-query`
206
+ run on their syntax fallbacks. They match `x.db.get(id)` and `x.db.query(...)...collect()` by
207
+ shape, and report without the autofix the type checker would give them. The measurements behind
208
+ each severity are beside it in `src/config.ts`.
209
+
210
+ `rdjperron/convex-require-validators` is on in every case, asking for `args` and `returns`. With
211
+ `wrapper`, `rdjperron/convex-checked-constructors` is on too, and both rules know the wrapper.
212
+
213
+ Both rules recognise a Convex function by where its constructor comes from, not by its name. A
214
+ name counts when its binding in the file leads to `_generated/server`, to the generic constructors
215
+ in `convex/server`, or to the wrapper. A `query` imported from any other module is not Convex's,
216
+ and a name no binding in the file declares is taken by its spelling, the way Convex's own rules
217
+ take it.
218
+
219
+ The analysis stays inside one file and reads syntax and bindings. It follows a renamed import; a
220
+ namespace import read member by member, destructured, collected with `...rest` or held in a
221
+ `const`; an object literal, its own keys and its spreads, in the order they run, so a later key
222
+ replaces an earlier one; and `await import()` of a string or a template with no expression. A
223
+ value it cannot read this way is unknown, and nothing unknown is reported.
224
+
225
+ - Outside the wrapper, `convex-checked-constructors` reports `query`, `mutation` and `action` from
226
+ `_generated/server`, the generic ones from `convex/server`, and `getAuthUserId` and
227
+ `getAuthSessionId` from `@convex-dev/auth/server`, however they reach the file. Copying one of
228
+ those modules whole, by spread or by `...rest`, reports too unless every such name was taken out
229
+ beside it. It also reports reading `auth` off a context. A context is the first parameter of
230
+ the handler Convex will run, the last one the definition sets, given inline, by the name of a
231
+ function or a `const`, or in a spread the file can read. A parameter named `ctx` anywhere is one
232
+ too, since that is Convex's own name for it, and so is a `const` alias of either.
233
+ - `convex-require-validators` checks every value an export can hold: a `const`, a `let` with each
234
+ value it is assigned, and an alias of either. A validator set to `undefined`, `void 0` or a
235
+ `const` holding `undefined` declares nothing, as Convex reads it. A spread or definition it
236
+ cannot read, or a computed key, leaves that function unchecked rather than guessed at.
237
+
238
+ Where a value is not definitely known, the rules stay silent rather than guess. The analysis
239
+ stops, by choice rather than for want of types, at these forms, which are left unchecked:
240
+
241
+ - a context passed into a helper call, even one in the same file, or kept in a class field;
242
+ - a constructor returned by a factory, or read from another module, a barrel included;
243
+ - a computed key or import specifier;
244
+ - an export taken by destructuring, such as `export const { f } = { f: query({ … }) }` or a
245
+ property of a call's result;
246
+ - a `...rest` copy of an object literal, such as `const { ...t } = { q }`;
247
+ - a `const` copy of a `let` that is assigned more than once, since which value it copied depends
248
+ on when it ran.
249
+
250
+ The wrapper is one file. An absolute path pins it. A relative one is looked for from the working
251
+ directory upward, and the first directory that holds it is the root, so `convex/lib/functions.ts`
252
+ is the same file whether `vp lint` runs from the root or from `convex/`, and
253
+ `other/convex/lib/functions.ts` is not it. oxlint tells a rule the process's working directory,
254
+ not the config's, so a monorepo, or any project that may lint from elsewhere, passes the absolute
255
+ path: `fileURLToPath(new URL("./convex/lib/functions.ts", import.meta.url))`.
256
+
257
+ An import names the wrapper when a relative path resolves to that file: spelled with its own
258
+ extension, with none, or with the `.js` TypeScript uses for a `.ts` file. A directory's `/index` is
259
+ never collapsed, so `./lib/functions/index` is a different module from `lib/functions.ts`. Any
260
+ other spelling, such as `@/convex/lib/functions` from a tsconfig alias, names it only when
261
+ `wrapperImports` lists it exactly as written: the rules cannot read a bundler's or a tsconfig's
262
+ aliases, and guessing from the path would make the answer depend on where `vp lint` runs. With
263
+ an absolute `wrapper`, nothing about either rule depends on the working directory.
264
+
265
+ A later override replaces the entry of each rule it names, options included, and leaves the
266
+ others alone, so an exemption goes after `convex()`. A probe that reads `ctx.auth` on purpose:
267
+
268
+ ```ts
269
+ ...convex(["convex/**"], { wrapper: "convex/lib/functions.ts" }),
270
+ { files: ["convex/probe.fixture.ts"], rules: { "rdjperron/convex-checked-constructors": "off" } },
271
+ ```
272
+
273
+ That second override needs no `jsPlugins` of its own, and neither does one that changes an
274
+ `@convex-dev/*` rule: once any override loads a JS plugin, its rules resolve in every override
275
+ (measured on vite-plus 0.3.0 to 0.3.3, 2026-10-12).
276
+
166
277
  ## Prove the rules still fire
167
278
 
168
279
  ```bash
169
280
  npx rdjperron-lint-verify
170
281
  ```
171
282
 
172
- No configuration and no arguments. A rule can go silently dead after a linter upgrade, and "reports
173
- nothing" looks exactly like "no violations", so nothing else in a repo notices. This asserts a
283
+ No configuration, and one optional flag, `--require-convex`. A rule can go silently dead after a
284
+ linter upgrade, and "reports nothing" looks exactly like "no violations", so nothing else in a repo
285
+ notices. This asserts a
174
286
  positive: every rule fired, each on the file named after it. It prints a table and exits non-zero on
175
287
  the first rule that stayed quiet.
176
288
 
@@ -180,6 +292,14 @@ generated config that puts every rule at error, with your `node_modules` symlink
180
292
  the whole directory afterwards whether the run passed or failed. Your own config, your ignore
181
293
  patterns and your severities take no part.
182
294
 
295
+ Where `@convex-dev/eslint-plugin` is installed, the same run lints `fixtures/convex/` under the
296
+ overrides `convex()` returns and asserts that the Convex rule each file is named after fired. That
297
+ plugin loads through the override, not through anything you wrote, so this is what turns red when
298
+ the load stops working. Installed means its directory is in `node_modules`: a broken package still
299
+ runs the check, and fails it. A package your `package.json` declares but that is not installed
300
+ fails the run. With no plugin at all the command says it skipped that part, and
301
+ `--require-convex` turns that skip into a failure, which is what a Convex project's CI wants.
302
+
183
303
  The linter it runs is the oxlint your local `vite-plus` depends on, so the run that matters is the
184
304
  one right after you move `vite-plus`, and in CI.
185
305
 
@@ -308,28 +428,30 @@ Reading the documents against the code is still a review's job.
308
428
  Each rule file opens with the reason the rule exists and the case where it is legitimate to
309
429
  scope it off; the messages, the options and the cases are in the rule and its tests.
310
430
 
311
- | Rule | What it rejects |
312
- | ----------------------------------------------------- | --------------------------------------------------------------------- |
313
- | `rdjperron/no-array-filter-map` | `filter().map()`, two passes and an intermediate array. |
314
- | `rdjperron/no-chained-type-assertions` | `x as unknown as T`, an assertion laundered through a second one. |
315
- | `rdjperron/no-conditional-empty-object-spread` | `...(cond ? { a } : {})`, omission hidden behind an empty object. |
316
- | `rdjperron/no-hardcoded-jsx-text` | Words written straight into JSX children. |
317
- | `rdjperron/no-known-value-widening` | An annotation that throws away what inference already knew. |
318
- | `rdjperron/no-module-mocking` | `vi.mock` and friends, a test of the mock rather than the module. |
319
- | `rdjperron/no-object-parameters` | The bare `object` type on a function input. |
320
- | `rdjperron/no-reduce-accumulator-copy` | Spreading the accumulator inside `reduce`, quadratic by construction. |
321
- | `rdjperron/no-reflect-apply` | `Reflect.apply`, a call the types cannot follow. |
322
- | `rdjperron/no-reflect-get` | `Reflect.get`, a property read the types cannot follow. |
323
- | `rdjperron/no-runtime-typeof` | `typeof` as a substitute for parsing input at its boundary. |
324
- | `rdjperron/no-shape-in-symbol-names` | The word "shape" in a symbol name, structure in place of a role. |
325
- | `rdjperron/no-unknown-parameters` | `unknown` on a function input. |
326
- | `rdjperron/no-unknown-returns` | `unknown` on a function output. |
327
- | `rdjperron/no-unknown-type-aliases` | A type alias that resolves to `unknown`. |
328
- | `rdjperron/no-unsafe-dictionary-type` | A dictionary whose values are `unknown`, `any` or `object`. |
329
- | `rdjperron/no-use-effect` | Every `useEffect` and `useLayoutEffect` call or import. |
330
- | `rdjperron/no-widen-then-assert` | Widening a value and asserting it back. |
331
- | `rdjperron/require-safety-comment-for-type-assertion` | An assertion with no `SAFETY:` line naming the invariant. |
332
- | `rdjperron/static-public-env` | A `process.env` read Expo's bundler does not replace at build time. |
431
+ | Rule | What it rejects |
432
+ | ----------------------------------------------------- | ------------------------------------------------------------------------------ |
433
+ | `rdjperron/convex-checked-constructors` | A raw public Convex constructor, or a read of the caller, outside the wrapper. |
434
+ | `rdjperron/convex-require-validators` | An exported Convex function without `args`, or without `returns`. |
435
+ | `rdjperron/no-array-filter-map` | `filter().map()`, two passes and an intermediate array. |
436
+ | `rdjperron/no-chained-type-assertions` | `x as unknown as T`, an assertion laundered through a second one. |
437
+ | `rdjperron/no-conditional-empty-object-spread` | `...(cond ? { a } : {})`, omission hidden behind an empty object. |
438
+ | `rdjperron/no-hardcoded-jsx-text` | Words written straight into JSX children. |
439
+ | `rdjperron/no-known-value-widening` | An annotation that throws away what inference already knew. |
440
+ | `rdjperron/no-module-mocking` | `vi.mock` and friends, a test of the mock rather than the module. |
441
+ | `rdjperron/no-object-parameters` | The bare `object` type on a function input. |
442
+ | `rdjperron/no-reduce-accumulator-copy` | Spreading the accumulator inside `reduce`, quadratic by construction. |
443
+ | `rdjperron/no-reflect-apply` | `Reflect.apply`, a call the types cannot follow. |
444
+ | `rdjperron/no-reflect-get` | `Reflect.get`, a property read the types cannot follow. |
445
+ | `rdjperron/no-runtime-typeof` | `typeof` as a substitute for parsing input at its boundary. |
446
+ | `rdjperron/no-shape-in-symbol-names` | The word "shape" in a symbol name, structure in place of a role. |
447
+ | `rdjperron/no-unknown-parameters` | `unknown` on a function input. |
448
+ | `rdjperron/no-unknown-returns` | `unknown` on a function output. |
449
+ | `rdjperron/no-unknown-type-aliases` | A type alias that resolves to `unknown`. |
450
+ | `rdjperron/no-unsafe-dictionary-type` | A dictionary whose values are `unknown`, `any` or `object`. |
451
+ | `rdjperron/no-use-effect` | Every `useEffect` and `useLayoutEffect` call or import. |
452
+ | `rdjperron/no-widen-then-assert` | Widening a value and asserting it back. |
453
+ | `rdjperron/require-safety-comment-for-type-assertion` | An assertion with no `SAFETY:` line naming the invariant. |
454
+ | `rdjperron/static-public-env` | A `process.env` read Expo's bundler does not replace at build time. |
333
455
 
334
456
  ### Options
335
457
 
@@ -344,6 +466,18 @@ scope it off; the messages, the options and the cases are in the rule and its te
344
466
  - `rdjperron/no-unknown-parameters` takes `{ hint?: string }`, appended to every finding, so a project
345
467
  can name the modules where the parse belongs. `decoders(files)` sets it.
346
468
  - `rdjperron/static-public-env` has no options.
469
+ - `rdjperron/convex-checked-constructors` takes
470
+ `{ wrapper?: string, wrapperImports?: string[], hint?: string }`. `wrapper` exempts that one
471
+ file and names it in every finding. An absolute path pins it; a relative one is looked for from
472
+ the working directory upward. `wrapperImports` lists the alias spellings that import it. Without
473
+ `wrapper` every file is outside the wrapper, so the rule reports the wrapper's own imports too. `internalQuery`,
474
+ `internalMutation`, `internalAction` and `httpAction` stay allowed everywhere.
475
+ - `rdjperron/convex-require-validators` takes
476
+ `{ wrapper?: string, wrapperImports?: string[], returns?: boolean, hint?: string }`. `returns`
477
+ defaults to true. Convex's docs call a return validator optional, but its own `convex-expert`
478
+ skill asks for one on every function, and bidraft, atlas and rummy already declare one on every
479
+ function they deploy (2026-10-12). `httpAction` is left alone, since it takes a request.
480
+ - Both convex rules skip a file under `_generated/`, which `convex codegen` writes.
347
481
 
348
482
  ## Name the file that holds the fix
349
483
 
@@ -367,6 +501,25 @@ Land a new rule at `warn`, fix or scope what fires, then promote it. When a rule
367
501
  code or add a narrowly scoped override with the measurement that justifies it. Never weaken the
368
502
  rule, add a cast, or rename a symbol to dodge the check.
369
503
 
504
+ ## Moving from 0.7
505
+
506
+ Nothing is removed and nothing changes for a project without Convex. 0.8.0 adds `convex()`, the two
507
+ `rdjperron/convex-*` rules and the optional peer `@convex-dev/eslint-plugin`.
508
+
509
+ A Convex project that bans the raw constructors by hand today deletes those bans when it adopts
510
+ `convex(files, { wrapper })`:
511
+
512
+ | You write by hand today | 0.8.0 gives you |
513
+ | --------------------------------------------------------------------------------------------------------- | --------------------------------------- |
514
+ | `query`, `mutation`, `action` from `_generated/server` in `no-restricted-imports`, lifted for the wrapper | `rdjperron/convex-checked-constructors` |
515
+ | `queryGeneric`, `mutationGeneric`, `actionGeneric` from `convex/server` | the same rule |
516
+ | `getAuthUserId`, `getAuthSessionId` from `@convex-dev/auth/server` | the same rule |
517
+ | `no-restricted-properties` on `ctx.auth` | the same rule |
518
+
519
+ The rule also sees what those bans missed: `const c = ctx; c.auth`, a handler whose context is
520
+ not named `ctx`, and a narrower override that restates `no-restricted-imports` and so drops the
521
+ Convex entries.
522
+
370
523
  ## Moving from 0.6
371
524
 
372
525
  0.7.0 takes the decision register out of the repository checks. `checkRepo` no longer reads a
@@ -10,6 +10,8 @@ const plugin = {
10
10
  };
11
11
  /** Every rule the plugin registers, prefixed. The verify bin puts all of them at error. */
12
12
  const RULE_IDS = [
13
+ "rdjperron/convex-checked-constructors",
14
+ "rdjperron/convex-require-validators",
13
15
  "rdjperron/no-array-filter-map",
14
16
  "rdjperron/no-chained-type-assertions",
15
17
  "rdjperron/no-conditional-empty-object-spread",
@@ -207,6 +209,60 @@ function expo(files, options = {}) {
207
209
  });
208
210
  return overrides;
209
211
  }
212
+ /** Convex's own plugin, loaded by the override `convex()` returns rather than at the top level. */
213
+ const convexPlugin = {
214
+ name: "@convex-dev",
215
+ specifier: "@convex-dev/eslint-plugin"
216
+ };
217
+ /**
218
+ * The Convex backend: Convex's own rules, loaded from `@convex-dev/eslint-plugin` through this
219
+ * override's `jsPlugins`, and the two `rdjperron/convex-*` rules.
220
+ *
221
+ * oxlint gives a JS plugin no type information, so `explicit-table-ids` and
222
+ * `no-collect-in-query` run on their syntax fallbacks: each reports without its autofix, and
223
+ * matches the `x.db.method(...)` and `x.db.query(...)...collect()` chains by shape.
224
+ */
225
+ function convex(files, options = {}) {
226
+ const { wrapper, wrapperImports, returns = true, tests: testFiles, hint } = options;
227
+ const located = wrapper === void 0 ? {} : wrapperImports === void 0 ? { wrapper } : {
228
+ wrapper,
229
+ wrapperImports
230
+ };
231
+ const rules = {
232
+ "@convex-dev/no-old-registered-function-syntax": "error",
233
+ "@convex-dev/explicit-table-ids": "error",
234
+ "@convex-dev/no-filter-in-query": "error",
235
+ "@convex-dev/no-top-of-hour-crons": "error",
236
+ "@convex-dev/no-schema-import-cycle": "error",
237
+ "@convex-dev/no-duplicate-indexes": "error",
238
+ "@convex-dev/no-process-env": "error",
239
+ "@convex-dev/no-collect-in-query": "error",
240
+ "@convex-dev/require-args-validator": "off",
241
+ "@convex-dev/import-wrong-runtime": "off",
242
+ "@convex-dev/require-access-control": "off",
243
+ "rdjperron/convex-require-validators": ["error", {
244
+ ...located,
245
+ returns
246
+ }]
247
+ };
248
+ if (wrapper !== void 0) rules["rdjperron/convex-checked-constructors"] = ["error", hint === void 0 ? located : {
249
+ ...located,
250
+ hint
251
+ }];
252
+ const overrides = [{
253
+ files,
254
+ jsPlugins: [convexPlugin],
255
+ rules
256
+ }];
257
+ if (testFiles !== void 0) overrides.push({
258
+ files: testFiles,
259
+ rules: {
260
+ "@convex-dev/no-collect-in-query": "off",
261
+ "@convex-dev/no-filter-in-query": "off"
262
+ }
263
+ });
264
+ return overrides;
265
+ }
210
266
  /** Test files: four rules whose premise a test does not hold. */
211
267
  function tests(files) {
212
268
  return [{
@@ -341,6 +397,6 @@ const ignore = [
341
397
  /** The pre-commit entry: `vp check --fix` is defined for JS and TS sources, nothing else. */
342
398
  const staged = { "*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}": "vp check --fix" };
343
399
  //#endregion
344
- export { i18n as a, react as c, staged as d, tests as f, fmt as i, recommended as l, decoders as n, ignore as o, expo as r, plugin as s, RULE_IDS as t, restrictedImports as u };
400
+ export { expo as a, ignore as c, recommended as d, restrictedImports as f, decoders as i, plugin as l, tests as m, convex as n, fmt as o, staged as p, convexPlugin as r, i18n as s, RULE_IDS as t, react as u };
345
401
 
346
- //# sourceMappingURL=config-CD2iCM6L.mjs.map
402
+ //# sourceMappingURL=config-BCudqv3_.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-BCudqv3_.mjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["/**\n * Config data for a consumer's `vite.config.ts` (or `.oxlintrc.ts`).\n *\n * The types below are declared locally rather than imported from `oxlint`:\n * `oxlint` is never a dependency of this package (`vp migrate` strips one, and a\n * direct dependency installs a second linter beside the Vite+ IDE stub), and a\n * consumer that installs only `vite-plus` has no guaranteed path to oxlint's own\n * `.d.ts`. They are structurally compatible with `OxlintConfig` and\n * `OxlintOverride`, checked by a consumer-shaped config in\n * `tests/consumer-config.ts`: arrays are mutable (oxlint's are), and `plugins` is\n * oxlint's own literal union, because a bare `string[]` is not assignable to it.\n */\n\n/** A severity as oxlint spells it in a config; the numeric forms are not used here. */\ntype Severity = \"off\" | \"warn\" | \"error\";\n\n/** A rule entry: a severity, or a severity followed by the rule's own options. */\ntype RuleEntry = Severity | [Severity, ...unknown[]];\n\ninterface Rules {\n [ruleId: string]: RuleEntry;\n}\n\n/** oxlint's built-in plugins, the values its `plugins` field accepts. */\ntype BuiltinPlugin =\n | \"eslint\"\n | \"import\"\n | \"jest\"\n | \"jsdoc\"\n | \"jsx-a11y\"\n | \"nextjs\"\n | \"node\"\n | \"oxc\"\n | \"promise\"\n | \"react\"\n | \"react-perf\"\n | \"typescript\"\n | \"unicorn\"\n | \"vitest\"\n | \"vue\";\n\n/** oxlint's rule categories, the keys its `categories` field accepts. */\ntype Category =\n | \"correctness\"\n | \"nursery\"\n | \"pedantic\"\n | \"perf\"\n | \"restriction\"\n | \"style\"\n | \"suspicious\";\n\ninterface JsPlugin {\n name: string;\n specifier: string;\n}\n\nexport interface Override {\n files: string[];\n excludeFiles?: string[];\n plugins?: BuiltinPlugin[];\n env?: { [name: string]: boolean };\n jsPlugins?: JsPlugin[];\n rules?: Rules;\n}\n\nexport interface Config {\n jsPlugins?: JsPlugin[];\n plugins?: BuiltinPlugin[];\n categories?: { [category in Category]?: Severity };\n rules?: Rules;\n overrides?: Override[];\n options?: { typeAware?: boolean; typeCheck?: boolean };\n}\n\n/**\n * The plugin entry. The alias is `rdjperron`, which is also the prefix in every\n * diagnostic and every disable comment, so a consumer that renames it renames\n * its own rule keys too.\n */\nexport const plugin: JsPlugin = { name: \"rdjperron\", specifier: \"@rdjperron/lint\" };\n\n/** Every rule the plugin registers, prefixed. The verify bin puts all of them at error. */\nexport const RULE_IDS = [\n \"rdjperron/convex-checked-constructors\",\n \"rdjperron/convex-require-validators\",\n \"rdjperron/no-array-filter-map\",\n \"rdjperron/no-chained-type-assertions\",\n \"rdjperron/no-conditional-empty-object-spread\",\n \"rdjperron/no-hardcoded-jsx-text\",\n \"rdjperron/no-known-value-widening\",\n \"rdjperron/no-module-mocking\",\n \"rdjperron/no-object-parameters\",\n \"rdjperron/no-reduce-accumulator-copy\",\n \"rdjperron/no-reflect-apply\",\n \"rdjperron/no-reflect-get\",\n \"rdjperron/no-runtime-typeof\",\n \"rdjperron/no-shape-in-symbol-names\",\n \"rdjperron/no-unknown-parameters\",\n \"rdjperron/no-unknown-returns\",\n \"rdjperron/no-unknown-type-aliases\",\n \"rdjperron/no-unsafe-dictionary-type\",\n \"rdjperron/no-use-effect\",\n \"rdjperron/no-widen-then-assert\",\n \"rdjperron/require-safety-comment-for-type-assertion\",\n \"rdjperron/static-public-env\",\n] as const;\n\nexport type RuleId = (typeof RULE_IDS)[number];\n\n/**\n * The base config, for `lint: { extends: [recommended] }`.\n *\n * Measured through `extends` on vite-plus 0.3.1 / oxlint 1.81.0 (2026-09-17):\n * `jsPlugins`, `rules`, `overrides`, `plugins`, `categories` and `options` are\n * inherited by the consumer's config, and the consumer's own entries win.\n * `env`, `settings` and `ignorePatterns` are not inherited, which is why this\n * object carries none of them and the consumer keeps writing its own.\n */\nexport const recommended: Config = {\n jsPlugins: [plugin],\n // Naming any plugin replaces oxlint's default set, so this is the whole set the\n // rules below need: `promise/*` and `typescript/*` are silently dropped without it.\n plugins: [\"typescript\", \"unicorn\", \"oxc\", \"import\", \"promise\", \"node\"],\n categories: {\n correctness: \"error\",\n suspicious: \"warn\",\n perf: \"warn\",\n },\n // `options` merges key by key through `extends`, so a consumer writing\n // `options: { typeCheck: false }` still gets `typeAware` (measured on vite-plus\n // 0.3.1 / oxlint 1.81.0, 2026-09-19). The type-aware rules below need it on.\n options: { typeAware: true },\n rules: {\n // The anti-slop rules. Measured on stack-lab 2026-08-20 and 2026-09-13, and\n // on rummy 2026-09-16 (vite-plus 0.3.1, oxlint 1.81.0): on real hand-written\n // code they fire only on the patterns they exist to reject, so a hit is the\n // finding. Land a new one at `warn` in the consumer, fix or scope what\n // fires, then let this default take over.\n \"rdjperron/no-array-filter-map\": \"error\",\n \"rdjperron/no-chained-type-assertions\": \"error\",\n \"rdjperron/no-conditional-empty-object-spread\": \"error\",\n \"rdjperron/no-known-value-widening\": \"error\",\n \"rdjperron/no-module-mocking\": \"error\",\n \"rdjperron/no-object-parameters\": \"error\",\n \"rdjperron/no-reduce-accumulator-copy\": \"error\",\n \"rdjperron/no-reflect-apply\": \"error\",\n \"rdjperron/no-reflect-get\": \"error\",\n // A `typeof` inside a type predicate is the decode the rule asks for, named\n // and reusable, and the pack already exempts a predicate's subject from\n // `no-unknown-parameters` (rummy, 2026-09-16).\n \"rdjperron/no-runtime-typeof\": [\"error\", { allowInTypeGuards: true }],\n \"rdjperron/no-shape-in-symbol-names\": \"error\",\n \"rdjperron/no-unknown-parameters\": \"error\",\n \"rdjperron/no-unknown-returns\": \"error\",\n \"rdjperron/no-unknown-type-aliases\": \"error\",\n \"rdjperron/no-unsafe-dictionary-type\": \"error\",\n \"rdjperron/no-widen-then-assert\": \"error\",\n \"rdjperron/require-safety-comment-for-type-assertion\": \"error\",\n // Derived state, event handlers and a reactive query cover every case; the\n // one sanctioned wrapper is named through `react(files, { useEffectAllowedIn })`.\n \"rdjperron/no-use-effect\": \"error\",\n\n // `no-hardcoded-jsx-text` and `static-public-env` are absent on purpose:\n // they belong to a translated app and to an Expo app. `i18n()` and `expo()`\n // switch them on for the files that want them.\n\n // Quadratic by construction, and the sibling of `no-reduce-accumulator-copy`,\n // which is already at error. `categories.perf` alone only warns. Measured\n // 2026-09-19 (vite-plus 0.3.1, oxlint 1.81.0): zero hits on this repo's 32\n // files, on rummy's 330 and on stack-lab's 57.\n \"oxc/no-accumulating-spread\": \"error\",\n\n // Type-aware rules, which `options.typeAware` above switches on.\n \"typescript/no-floating-promises\": \"error\",\n \"typescript/no-misused-promises\": \"error\",\n \"typescript/await-thenable\": \"error\",\n // Measured on rummy 2026-09-16 before the cleanup: 5, 5 and 1 hits, zero after.\n \"typescript/no-unnecessary-type-assertion\": \"error\",\n \"typescript/no-misused-spread\": \"error\",\n \"typescript/require-array-sort-compare\": \"error\",\n \"typescript/no-explicit-any\": \"warn\",\n\n \"prefer-const\": [\"error\", { destructuring: \"all\" }],\n \"no-unused-vars\": [\"error\", { argsIgnorePattern: \"^_\", varsIgnorePattern: \"^_\" }],\n // oxlint 1.77 moved this into `suspicious`, where it contradicts the `^_`\n // convention above. The prefix wins.\n \"no-underscore-dangle\": \"off\",\n eqeqeq: [\"error\", \"always\", { null: \"ignore\" }],\n\n // Deliberate off-switches, held by both consumers since the 2026-08-20 setup.\n \"typescript/no-unsafe-type-assertion\": \"off\",\n \"no-await-in-loop\": \"off\",\n \"unicorn/consistent-function-scoping\": \"off\",\n \"promise/always-return\": \"off\",\n },\n};\n\nexport interface ReactOptions {\n /**\n * The globals these files run with, as oxlint's `env`, defaulting to\n * `{ browser: true }`. A React Native app has no DOM: oxlint ships no\n * `react-native` environment, and `{ \"shared-node-browser\": true }` is the\n * closest one, the timers and the console both runtimes agree on.\n */\n env?: { [name: string]: boolean };\n /**\n * The one file allowed to call `useEffect`, as a path. Its name goes into the\n * `no-use-effect` hint for every React file, and the rule is off for it.\n */\n useEffectAllowedIn?: string;\n}\n\n/**\n * The React file set: the globals, the hooks rules, and the compiler's own\n * judgement. A consumer appends its own entries after these, because an override\n * that changes a `react/*` rule must repeat `plugins: [\"react\"]` or oxlint\n * silently drops the entry.\n */\nexport function react(files: string[], options: ReactOptions = {}): Override[] {\n const { env = { browser: true }, useEffectAllowedIn } = options;\n const rules: Rules = {\n \"react/rules-of-hooks\": \"error\",\n \"react/exhaustive-deps\": \"warn\",\n // React 19 and the automatic JSX runtime: nothing has to be in scope.\n \"react/react-in-jsx-scope\": \"off\",\n // The React Compiler's own rules: a component it silently skips is a\n // component that never memoizes.\n \"react/immutability\": \"error\",\n \"react/refs\": \"error\",\n \"react/purity\": \"error\",\n // A module that exports only components refreshes in place; any other\n // export re-runs every importer.\n \"react/only-export-components\": \"error\",\n };\n const overrides: Override[] = [{ files, plugins: [\"react\"], env, rules }];\n\n if (useEffectAllowedIn !== undefined) {\n rules[\"rdjperron/no-use-effect\"] = [\n \"error\",\n { hint: `The one sanctioned wrapper is ${useEffectAllowedIn}.` },\n ];\n overrides.push({\n // The sanctioned wrapper exists precisely so every other file can be clean.\n files: [useEffectAllowedIn],\n plugins: [\"react\"],\n rules: {\n \"rdjperron/no-use-effect\": \"off\",\n // The empty dependency array is this wrapper's contract, not an omission.\n \"react/exhaustive-deps\": \"off\",\n },\n });\n }\n\n return overrides;\n}\n\nexport interface I18nOptions {\n /**\n * The word pattern, a regex source compiled with `u`. The rule's default,\n * `\\p{L}{3,}`, reads three letters in a row as a word.\n */\n pattern?: string;\n}\n\n/** Files whose JSX text belongs in a catalogue rather than in the source. */\nexport function i18n(files: string[], options: I18nOptions = {}): Override[] {\n const { pattern } = options;\n return [\n {\n files,\n rules: {\n \"rdjperron/no-hardcoded-jsx-text\": pattern === undefined ? \"error\" : [\"error\", { pattern }],\n },\n },\n ];\n}\n\n/** Every extension Metro treats as an asset, the list eslint-config-expo 10 allowed. */\nconst ASSET_EXTENSIONS = [\n \"aac\",\n \"aiff\",\n \"avif\",\n \"bmp\",\n \"caf\",\n \"db\",\n \"gif\",\n \"heic\",\n \"html\",\n \"jpeg\",\n \"jpg\",\n \"json\",\n \"m4a\",\n \"m4v\",\n \"mov\",\n \"mp3\",\n \"mp4\",\n \"mpeg\",\n \"mpg\",\n \"otf\",\n \"pdf\",\n \"png\",\n \"psd\",\n \"svg\",\n \"ttf\",\n \"wav\",\n \"webm\",\n \"webp\",\n \"xml\",\n \"yaml\",\n \"yml\",\n \"zip\",\n];\n\nexport interface ExpoOptions {\n /** The extensions this app's `metro.config.js` adds to `resolver.assetExts`, without the dot. */\n assetExts?: string[];\n /**\n * The files React renders, if the app has any. They get the two `react/*` rules\n * an Expo app needs, on their own override because `react` is not in the\n * inherited plugin set.\n */\n reactFiles?: string[];\n}\n\n/**\n * The React Native side: what Expo's bundler inlines, what Metro bundles, and what\n * Hermes can be trusted to run.\n *\n * `typescript/no-require-imports` is relaxed here rather than switched on: the\n * repo-wide ban is the consumer's to write, and this is the one file set where\n * `require()` of an asset is how that asset enters the bundle.\n */\nexport function expo(files: string[], options: ExpoOptions = {}): Override[] {\n const { assetExts = [], reactFiles } = options;\n const extensions = [...new Set([...ASSET_EXTENSIONS, ...assetExts])].toSorted();\n const overrides: Override[] = [\n {\n files,\n rules: {\n \"rdjperron/static-public-env\": \"error\",\n // Measured on rummy's 330 files 2026-09-19 (vite-plus 0.3.1, oxlint 1.81.0):\n // zero hits, in a run that reported a non-asset `require()` from a probe file.\n \"typescript/no-require-imports\": [\"error\", { allow: [`\\\\.(${extensions.join(\"|\")})$`] }],\n // Hermes has no iterator helpers and its `toSorted` is unverified, so the copy\n // this rule asks for is not one the phone can be trusted to run.\n \"unicorn/no-array-sort\": \"off\",\n },\n },\n ];\n\n if (reactFiles !== undefined) {\n overrides.push({\n files: reactFiles,\n plugins: [\"react\"],\n rules: {\n // `Gesture.Pan()` reads as a component call to this rule; the React Compiler\n // compiles that component itself and is the judge.\n \"react/capitalized-calls\": \"off\",\n // expo-status-bar's `style` prop is a string by API design.\n \"react/style-prop-object\": [\"error\", { allow: [\"StatusBar\"] }],\n },\n });\n }\n\n return overrides;\n}\n\n/** Convex's own plugin, loaded by the override `convex()` returns rather than at the top level. */\nexport const convexPlugin: JsPlugin = {\n name: \"@convex-dev\",\n specifier: \"@convex-dev/eslint-plugin\",\n};\n\nexport interface ConvexOptions {\n /**\n * The module that builds the project's checked constructors, such as\n * `convex/lib/functions.ts`. It switches `convex-checked-constructors` on, exempts that one\n * file, and makes every name imported from it a constructor `convex-require-validators`\n * follows. A relative path is looked for from the working directory upward; an absolute one\n * pins the file.\n */\n wrapper?: string;\n /**\n * Spellings other than a relative path that import the wrapper, such as\n * `@/convex/lib/functions` from a tsconfig `paths` alias. The rules cannot read a bundler's or\n * tsconfig's aliases, so only the spellings listed here, compared exactly as written, name the\n * wrapper.\n */\n wrapperImports?: string[];\n /** Whether every function must also declare `returns`. On by default. */\n returns?: boolean;\n /**\n * Test code, where a query reads a table the test filled itself, so `.collect()` and\n * `.filter()` cost nothing.\n */\n tests?: string[];\n /** The project's own reason for its checked constructors, added to each `convex-checked-constructors` finding. */\n hint?: string;\n}\n\n/**\n * The Convex backend: Convex's own rules, loaded from `@convex-dev/eslint-plugin` through this\n * override's `jsPlugins`, and the two `rdjperron/convex-*` rules.\n *\n * oxlint gives a JS plugin no type information, so `explicit-table-ids` and\n * `no-collect-in-query` run on their syntax fallbacks: each reports without its autofix, and\n * matches the `x.db.method(...)` and `x.db.query(...)...collect()` chains by shape.\n */\nexport function convex(files: string[], options: ConvexOptions = {}): Override[] {\n const { wrapper, wrapperImports, returns = true, tests: testFiles, hint } = options;\n const located =\n wrapper === undefined\n ? {}\n : wrapperImports === undefined\n ? { wrapper }\n : { wrapper, wrapperImports };\n const validators = { ...located, returns };\n // Measured 2026-10-12 with @convex-dev/eslint-plugin 5.0.0 over `convex/**` of bidraft\n // (vite-plus 0.3.3, oxlint 1.83.0), atlas (0.3.3, 1.83.0) and rummy (0.3.1, 1.81.0), counts in\n // that order. Every rule at error found nothing or found real faults, with two exceptions noted\n // on their line that keep a disable comment. Measured again the same day after each round of\n // review fixes, bidraft at 5e216e9c, e58015ef and 8f9191a7: every count unchanged except\n // bidraft's collects in test code, 111 from e58015ef on as its own tests grew.\n const rules: Rules = {\n // 0, 0, 0.\n \"@convex-dev/no-old-registered-function-syntax\": \"error\",\n // 0, 1, 1, both a one-argument `ctx.db.delete(row._id)` in a loop over table names: the\n // table-scoped overload refuses a union, so that call keeps a disable comment.\n \"@convex-dev/explicit-table-ids\": \"error\",\n // 0, 0, 0 in deployed code; 1 in a bidraft test, which `tests` exempts.\n \"@convex-dev/no-filter-in-query\": \"error\",\n // 2, 1, 0, every one a daily cron at minute 0.\n \"@convex-dev/no-top-of-hour-crons\": \"error\",\n // 0, 0, 0.\n \"@convex-dev/no-schema-import-cycle\": \"error\",\n // 1, 2, 1. bidraft's is the `sessionId` index of `authRefreshTokens`, which @convex-dev/auth\n // queries by name, so it keeps a disable comment. The other three are real.\n \"@convex-dev/no-duplicate-indexes\": \"error\",\n // 0, 0, 1: rummy's `auth.config.ts` still reads `process.env`, the two others read `env`.\n \"@convex-dev/no-process-env\": \"error\",\n // 0, 27, 0 in deployed code, and 102, 35, 60 in tests and test helpers, which `tests`\n // exempts. atlas lands it at warn until each of its 27 is bounded or carries its reason.\n \"@convex-dev/no-collect-in-query\": \"error\",\n // `convex-require-validators` reports every omission this one does, on more functions, so\n // with both on one missing `args` would be two findings.\n \"@convex-dev/require-args-validator\": \"off\",\n // 2, 1, 1, all false: the rule looks for `use node` in the first 100 characters of the\n // imported file, and each hit was a comment saying the file has no `\"use node\"`. None of the\n // three repos has a `\"use node\"` file.\n \"@convex-dev/import-wrong-runtime\": \"off\",\n // 0, 0, 26. It sees only the raw registrars, so a function built by a checked constructor is\n // invisible to it, and it wants a top-level call named like `requireUser`: the five of rummy's\n // 26 read so far check the caller through `currentUser`, `getAuthUserId` or an internal query.\n // `convex-checked-constructors` is the stronger check where a project has a wrapper.\n \"@convex-dev/require-access-control\": \"off\",\n // 0, 3, 0 with `returns` required: bidraft and rummy declare both validators on every\n // function, and atlas's 3 are in a probe file that never deploys.\n \"rdjperron/convex-require-validators\": [\"error\", validators],\n };\n if (wrapper !== undefined) {\n // 2, 0, no wrapper: bidraft's 2 are its probe, which reads `ctx.auth` on purpose.\n rules[\"rdjperron/convex-checked-constructors\"] = [\n \"error\",\n hint === undefined ? located : { ...located, hint },\n ];\n }\n const overrides: Override[] = [{ files, jsPlugins: [convexPlugin], rules }];\n // No `jsPlugins` here: once any override names a JS plugin, its rules resolve in every override\n // (measured on vite-plus 0.3.0 to 0.3.3, 2026-10-12).\n if (testFiles !== undefined) {\n overrides.push({\n files: testFiles,\n rules: {\n \"@convex-dev/no-collect-in-query\": \"off\",\n \"@convex-dev/no-filter-in-query\": \"off\",\n },\n });\n }\n return overrides;\n}\n\n/** Test files: four rules whose premise a test does not hold. */\nexport function tests(files: string[]): Override[] {\n return [\n {\n files,\n rules: {\n // A fixture is allowed to be the wrong type on purpose.\n \"typescript/no-explicit-any\": \"off\",\n \"typescript/no-non-null-assertion\": \"off\",\n // `expect(typeof x).toBe(\"function\")` reads as an unparsed boundary, but\n // asserting the shape of your own public API is the point of the assertion.\n \"rdjperron/no-runtime-typeof\": \"off\",\n // A justification on every assertion in a test is noise (53 measured on\n // rummy, 2026-09-16).\n \"rdjperron/require-safety-comment-for-type-assertion\": \"off\",\n },\n },\n ];\n}\n\n/** One banned module or named import, as `no-restricted-imports` spells a path. */\nexport interface RestrictedPath {\n name: string;\n importNames?: string[];\n allowImportNames?: string[];\n message: string;\n}\n\n/** One banned glob group, as `no-restricted-imports` spells a pattern. */\nexport interface RestrictedPattern {\n group: string[];\n message: string;\n importNames?: string[];\n allowImportNames?: string[];\n caseSensitive?: boolean;\n}\n\n/**\n * One file set and the restrictions it carries: either the whole list by name, or\n * another scope's list plus and minus named entries.\n */\nexport type RestrictionScope<Name extends string, Parent extends string> =\n | { files: string[]; use: Name[]; from?: never; add?: never; drop?: never }\n | { files: string[]; use?: never; from: Parent; add?: Name[]; drop?: Name[] };\n\n/**\n * `no-restricted-imports` for every file set at once. Two overrides matching one\n * file do not merge, so each scope needs its whole list written out; a consumer\n * doing that by hand restates the same list per scope and lifts a ban by\n * forgetting one. Here each scope says which scope it starts `from` and what it\n * `add`s and `drop`s, and this expands them.\n *\n * One override per scope comes out in declaration order, so the consumer declares\n * broad to narrow. The broadest scope is an ordinary override on `files: [\"**\"]`,\n * which behaves exactly like the top-level rule (measured on vite-plus 0.3.1 and\n * oxlint 1.81.0, 2026-09-19).\n *\n * TypeScript rejects a name that is no key of `paths` or `patterns`, a `from`\n * naming no scope at all, and `use` beside `from`. A `from` naming a scope\n * declared later, a `drop` of a name the parent does not restrict and an `add` of\n * one it already does throw here, because resolution order is what decides them.\n */\nexport function restrictedImports<\n Paths extends { [name: string]: RestrictedPath },\n Patterns extends { [name: string]: RestrictedPattern },\n Scopes extends {\n [scope in keyof Scopes]: RestrictionScope<\n Extract<keyof Paths | keyof Patterns, string>,\n Extract<keyof Scopes, string>\n >;\n },\n>(config: { paths: Paths; patterns: Patterns; scopes: Scopes }): Override[] {\n const { paths, patterns, scopes } = config;\n const known = new Set([...Object.keys(paths), ...Object.keys(patterns)]);\n const resolved = new Map<string, Set<string>>();\n const overrides: Override[] = [];\n\n const entries: [string, RestrictionScope<string, string>][] = Object.entries(scopes);\n for (const [scope, spec] of entries) {\n const named = spec.use ?? [...(spec.add ?? []), ...(spec.drop ?? [])];\n for (const name of named) {\n if (!known.has(name)) {\n throw new Error(\n `restrictedImports: scope \"${scope}\" names \"${name}\", which is neither a path nor a pattern.`,\n );\n }\n }\n\n let chosen: Set<string>;\n if (spec.use !== undefined) {\n chosen = new Set(spec.use);\n } else {\n const parent = resolved.get(spec.from);\n if (parent === undefined) {\n throw new Error(\n `restrictedImports: scope \"${scope}\" starts from \"${spec.from}\", which is not a scope declared before it.`,\n );\n }\n chosen = new Set(parent);\n for (const name of spec.drop ?? []) {\n if (!chosen.delete(name)) {\n throw new Error(\n `restrictedImports: scope \"${scope}\" drops \"${name}\", which \"${spec.from}\" does not restrict.`,\n );\n }\n }\n for (const name of spec.add ?? []) {\n if (chosen.has(name)) {\n throw new Error(\n `restrictedImports: scope \"${scope}\" adds \"${name}\", which \"${spec.from}\" already restricts.`,\n );\n }\n chosen.add(name);\n }\n }\n\n resolved.set(scope, chosen);\n overrides.push({\n files: spec.files,\n rules: {\n \"no-restricted-imports\": [\n \"error\",\n { paths: chosenFrom(paths, chosen), patterns: chosenFrom(patterns, chosen) },\n ],\n },\n });\n }\n\n return overrides;\n}\n\n/** In the order the entry was declared in, so two scopes' lists read side by side. */\nfunction chosenFrom<Entry>(table: { [name: string]: Entry }, chosen: ReadonlySet<string>): Entry[] {\n return Object.entries(table).flatMap(([name, entry]) => (chosen.has(name) ? [entry] : []));\n}\n\nexport interface DecodersOptions {\n /**\n * The sentence appended to both findings. The default names the files in\n * `files`, which is what a reader has to know to act on either message.\n */\n hint?: string;\n}\n\n/** `a`, `a or b`, `a, b or c`. */\nfunction orList(names: string[]): string {\n const last = names.at(-1) ?? \"\";\n return names.length > 1 ? `${names.slice(0, -1).join(\", \")} or ${last}` : last;\n}\n\n/**\n * The decoder modules, the one place per kind of outside value where the parse\n * belongs. Every file gets `no-runtime-typeof` and `no-unknown-parameters` at\n * error carrying a hint that names these files, because moving the check into one\n * of them is the fix in both cases, and `no-runtime-typeof` is off inside them.\n *\n * `no-unknown-parameters` and `no-unsafe-dictionary-type` stay on in the decoders.\n * A decoder that needs either off says so in its own override with its own\n * measured count, and one switch for the whole set would cover the files that\n * never needed it.\n *\n * Place it before `tests()`, which switches `no-runtime-typeof` off for test files.\n */\nexport function decoders(files: string[], options: DecodersOptions = {}): Override[] {\n const { hint = `Decode the value in ${orList(files)}, then call that decoder from here.` } =\n options;\n return [\n {\n files: [\"**\"],\n rules: {\n // A later override replaces a rule's whole entry, options included (measured\n // on vite-plus 0.3.1 / oxlint 1.81.0, 2026-09-19), so `allowInTypeGuards` is\n // restated rather than inherited from `recommended`.\n \"rdjperron/no-runtime-typeof\": [\"error\", { allowInTypeGuards: true, hint }],\n \"rdjperron/no-unknown-parameters\": [\"error\", { hint }],\n },\n },\n {\n // Inside a decoder a `typeof` is the parse, not a narrowing in place of one.\n files,\n rules: { \"rdjperron/no-runtime-typeof\": \"off\" },\n },\n ];\n}\n\n/** The formatting both consumers settled on. Spread it and add what is yours. */\nexport const fmt = {\n printWidth: 100,\n singleQuote: false,\n semi: true,\n tabWidth: 2,\n useTabs: false,\n};\n\n/**\n * Paths no linter and no formatter should walk: build output, dependencies, the\n * directories AI coding agents scribble in, and the Vitest JSON report. Spread\n * it into `ignorePatterns` and append the repo's own.\n */\nexport const ignore: string[] = [\n \"node_modules/**\",\n \"**/node_modules/**\",\n \"dist/**\",\n \"**/dist/**\",\n ...[\n \".agents\",\n \".claude\",\n \".codex\",\n \".continue\",\n \".cursor\",\n \".gemini\",\n \".opencode\",\n \".pi\",\n \".roo\",\n \".windsurf\",\n ].flatMap((dir) => [`${dir}/**`, `**/${dir}/**`]),\n \"**/test-results.json\",\n];\n\n/** The pre-commit entry: `vp check --fix` is defined for JS and TS sources, nothing else. */\nexport const staged = {\n \"*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}\": \"vp check --fix\",\n};\n"],"mappings":";;;;;;AA+EA,MAAa,SAAmB;CAAE,MAAM;CAAa,WAAW;AAAkB;;AAGlF,MAAa,WAAW;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;;;;AAaA,MAAa,cAAsB;CACjC,WAAW,CAAC,MAAM;CAGlB,SAAS;EAAC;EAAc;EAAW;EAAO;EAAU;EAAW;CAAM;CACrE,YAAY;EACV,aAAa;EACb,YAAY;EACZ,MAAM;CACR;CAIA,SAAS,EAAE,WAAW,KAAK;CAC3B,OAAO;EAML,iCAAiC;EACjC,wCAAwC;EACxC,gDAAgD;EAChD,qCAAqC;EACrC,+BAA+B;EAC/B,kCAAkC;EAClC,wCAAwC;EACxC,8BAA8B;EAC9B,4BAA4B;EAI5B,+BAA+B,CAAC,SAAS,EAAE,mBAAmB,KAAK,CAAC;EACpE,sCAAsC;EACtC,mCAAmC;EACnC,gCAAgC;EAChC,qCAAqC;EACrC,uCAAuC;EACvC,kCAAkC;EAClC,uDAAuD;EAGvD,2BAA2B;EAU3B,8BAA8B;EAG9B,mCAAmC;EACnC,kCAAkC;EAClC,6BAA6B;EAE7B,4CAA4C;EAC5C,gCAAgC;EAChC,yCAAyC;EACzC,8BAA8B;EAE9B,gBAAgB,CAAC,SAAS,EAAE,eAAe,MAAM,CAAC;EAClD,kBAAkB,CAAC,SAAS;GAAE,mBAAmB;GAAM,mBAAmB;EAAK,CAAC;EAGhF,wBAAwB;EACxB,QAAQ;GAAC;GAAS;GAAU,EAAE,MAAM,SAAS;EAAC;EAG9C,uCAAuC;EACvC,oBAAoB;EACpB,uCAAuC;EACvC,yBAAyB;CAC3B;AACF;;;;;;;AAuBA,SAAgB,MAAM,OAAiB,UAAwB,CAAC,GAAe;CAC7E,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,GAAG,uBAAuB;CACxD,MAAM,QAAe;EACnB,wBAAwB;EACxB,yBAAyB;EAEzB,4BAA4B;EAG5B,sBAAsB;EACtB,cAAc;EACd,gBAAgB;EAGhB,gCAAgC;CAClC;CACA,MAAM,YAAwB,CAAC;EAAE;EAAO,SAAS,CAAC,OAAO;EAAG;EAAK;CAAM,CAAC;CAExE,IAAI,uBAAuB,KAAA,GAAW;EACpC,MAAM,6BAA6B,CACjC,SACA,EAAE,MAAM,iCAAiC,mBAAmB,GAAG,CACjE;EACA,UAAU,KAAK;GAEb,OAAO,CAAC,kBAAkB;GAC1B,SAAS,CAAC,OAAO;GACjB,OAAO;IACL,2BAA2B;IAE3B,yBAAyB;GAC3B;EACF,CAAC;CACH;CAEA,OAAO;AACT;;AAWA,SAAgB,KAAK,OAAiB,UAAuB,CAAC,GAAe;CAC3E,MAAM,EAAE,YAAY;CACpB,OAAO,CACL;EACE;EACA,OAAO,EACL,mCAAmC,YAAY,KAAA,IAAY,UAAU,CAAC,SAAS,EAAE,QAAQ,CAAC,EAC5F;CACF,CACF;AACF;;AAGA,MAAM,mBAAmB;CACvB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;;;AAqBA,SAAgB,KAAK,OAAiB,UAAuB,CAAC,GAAe;CAC3E,MAAM,EAAE,YAAY,CAAC,GAAG,eAAe;CAEvC,MAAM,YAAwB,CAC5B;EACE;EACA,OAAO;GACL,+BAA+B;GAG/B,iCAAiC,CAAC,SAAS,EAAE,OAAO,CAAC,OARxC,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,kBAAkB,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,SAQM,CAAC,CAAC,KAAK,GAAG,EAAE,GAAG,EAAE,CAAC;GAGvF,yBAAyB;EAC3B;CACF,CACF;CAEA,IAAI,eAAe,KAAA,GACjB,UAAU,KAAK;EACb,OAAO;EACP,SAAS,CAAC,OAAO;EACjB,OAAO;GAGL,2BAA2B;GAE3B,2BAA2B,CAAC,SAAS,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;EAC/D;CACF,CAAC;CAGH,OAAO;AACT;;AAGA,MAAa,eAAyB;CACpC,MAAM;CACN,WAAW;AACb;;;;;;;;;AAqCA,SAAgB,OAAO,OAAiB,UAAyB,CAAC,GAAe;CAC/E,MAAM,EAAE,SAAS,gBAAgB,UAAU,MAAM,OAAO,WAAW,SAAS;CAC5E,MAAM,UACJ,YAAY,KAAA,IACR,CAAC,IACD,mBAAmB,KAAA,IACjB,EAAE,QAAQ,IACV;EAAE;EAAS;CAAe;CAQlC,MAAM,QAAe;EAEnB,iDAAiD;EAGjD,kCAAkC;EAElC,kCAAkC;EAElC,oCAAoC;EAEpC,sCAAsC;EAGtC,oCAAoC;EAEpC,8BAA8B;EAG9B,mCAAmC;EAGnC,sCAAsC;EAItC,oCAAoC;EAKpC,sCAAsC;EAGtC,uCAAuC,CAAC,SAAS;GAzC9B,GAAG;GAAS;EAyC2B,CAAC;CAC7D;CACA,IAAI,YAAY,KAAA,GAEd,MAAM,2CAA2C,CAC/C,SACA,SAAS,KAAA,IAAY,UAAU;EAAE,GAAG;EAAS;CAAK,CACpD;CAEF,MAAM,YAAwB,CAAC;EAAE;EAAO,WAAW,CAAC,YAAY;EAAG;CAAM,CAAC;CAG1E,IAAI,cAAc,KAAA,GAChB,UAAU,KAAK;EACb,OAAO;EACP,OAAO;GACL,mCAAmC;GACnC,kCAAkC;EACpC;CACF,CAAC;CAEH,OAAO;AACT;;AAGA,SAAgB,MAAM,OAA6B;CACjD,OAAO,CACL;EACE;EACA,OAAO;GAEL,8BAA8B;GAC9B,oCAAoC;GAGpC,+BAA+B;GAG/B,uDAAuD;EACzD;CACF,CACF;AACF;;;;;;;;;;;;;;;;;;AA4CA,SAAgB,kBASd,QAA0E;CAC1E,MAAM,EAAE,OAAO,UAAU,WAAW;CACpC,MAAM,wBAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,KAAK,GAAG,GAAG,OAAO,KAAK,QAAQ,CAAC,CAAC;CACvE,MAAM,2BAAW,IAAI,IAAyB;CAC9C,MAAM,YAAwB,CAAC;CAE/B,MAAM,UAAwD,OAAO,QAAQ,MAAM;CACnF,KAAK,MAAM,CAAC,OAAO,SAAS,SAAS;EACnC,MAAM,QAAQ,KAAK,OAAO,CAAC,GAAI,KAAK,OAAO,CAAC,GAAI,GAAI,KAAK,QAAQ,CAAC,CAAE;EACpE,KAAK,MAAM,QAAQ,OACjB,IAAI,CAAC,MAAM,IAAI,IAAI,GACjB,MAAM,IAAI,MACR,6BAA6B,MAAM,WAAW,KAAK,0CACrD;EAIJ,IAAI;EACJ,IAAI,KAAK,QAAQ,KAAA,GACf,SAAS,IAAI,IAAI,KAAK,GAAG;OACpB;GACL,MAAM,SAAS,SAAS,IAAI,KAAK,IAAI;GACrC,IAAI,WAAW,KAAA,GACb,MAAM,IAAI,MACR,6BAA6B,MAAM,iBAAiB,KAAK,KAAK,4CAChE;GAEF,SAAS,IAAI,IAAI,MAAM;GACvB,KAAK,MAAM,QAAQ,KAAK,QAAQ,CAAC,GAC/B,IAAI,CAAC,OAAO,OAAO,IAAI,GACrB,MAAM,IAAI,MACR,6BAA6B,MAAM,WAAW,KAAK,YAAY,KAAK,KAAK,qBAC3E;GAGJ,KAAK,MAAM,QAAQ,KAAK,OAAO,CAAC,GAAG;IACjC,IAAI,OAAO,IAAI,IAAI,GACjB,MAAM,IAAI,MACR,6BAA6B,MAAM,UAAU,KAAK,YAAY,KAAK,KAAK,qBAC1E;IAEF,OAAO,IAAI,IAAI;GACjB;EACF;EAEA,SAAS,IAAI,OAAO,MAAM;EAC1B,UAAU,KAAK;GACb,OAAO,KAAK;GACZ,OAAO,EACL,yBAAyB,CACvB,SACA;IAAE,OAAO,WAAW,OAAO,MAAM;IAAG,UAAU,WAAW,UAAU,MAAM;GAAE,CAC7E,EACF;EACF,CAAC;CACH;CAEA,OAAO;AACT;;AAGA,SAAS,WAAkB,OAAkC,QAAsC;CACjG,OAAO,OAAO,QAAQ,KAAK,CAAC,CAAC,SAAS,CAAC,MAAM,WAAY,OAAO,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,CAAE;AAC3F;;AAWA,SAAS,OAAO,OAAyB;CACvC,MAAM,OAAO,MAAM,GAAG,EAAE,KAAK;CAC7B,OAAO,MAAM,SAAS,IAAI,GAAG,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,MAAM,SAAS;AAC5E;;;;;;;;;;;;;;AAeA,SAAgB,SAAS,OAAiB,UAA2B,CAAC,GAAe;CACnF,MAAM,EAAE,OAAO,uBAAuB,OAAO,KAAK,EAAE,yCAClD;CACF,OAAO,CACL;EACE,OAAO,CAAC,IAAI;EACZ,OAAO;GAIL,+BAA+B,CAAC,SAAS;IAAE,mBAAmB;IAAM;GAAK,CAAC;GAC1E,mCAAmC,CAAC,SAAS,EAAE,KAAK,CAAC;EACvD;CACF,GACA;EAEE;EACA,OAAO,EAAE,+BAA+B,MAAM;CAChD,CACF;AACF;;AAGA,MAAa,MAAM;CACjB,YAAY;CACZ,aAAa;CACb,MAAM;CACN,UAAU;CACV,SAAS;AACX;;;;;;AAOA,MAAa,SAAmB;CAC9B;CACA;CACA;CACA;CACA,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC,CAAC,SAAS,QAAQ,CAAC,GAAG,IAAI,MAAM,MAAM,IAAI,IAAI,CAAC;CAChD;AACF;;AAGA,MAAa,SAAS,EACpB,qCAAqC,iBACvC"}
package/dist/config.d.mts CHANGED
@@ -54,7 +54,7 @@ export interface Config {
54
54
  */
55
55
  export declare const plugin: JsPlugin;
56
56
  /** Every rule the plugin registers, prefixed. The verify bin puts all of them at error. */
57
- export declare const RULE_IDS: readonly ["rdjperron/no-array-filter-map", "rdjperron/no-chained-type-assertions", "rdjperron/no-conditional-empty-object-spread", "rdjperron/no-hardcoded-jsx-text", "rdjperron/no-known-value-widening", "rdjperron/no-module-mocking", "rdjperron/no-object-parameters", "rdjperron/no-reduce-accumulator-copy", "rdjperron/no-reflect-apply", "rdjperron/no-reflect-get", "rdjperron/no-runtime-typeof", "rdjperron/no-shape-in-symbol-names", "rdjperron/no-unknown-parameters", "rdjperron/no-unknown-returns", "rdjperron/no-unknown-type-aliases", "rdjperron/no-unsafe-dictionary-type", "rdjperron/no-use-effect", "rdjperron/no-widen-then-assert", "rdjperron/require-safety-comment-for-type-assertion", "rdjperron/static-public-env"];
57
+ export declare const RULE_IDS: readonly ["rdjperron/convex-checked-constructors", "rdjperron/convex-require-validators", "rdjperron/no-array-filter-map", "rdjperron/no-chained-type-assertions", "rdjperron/no-conditional-empty-object-spread", "rdjperron/no-hardcoded-jsx-text", "rdjperron/no-known-value-widening", "rdjperron/no-module-mocking", "rdjperron/no-object-parameters", "rdjperron/no-reduce-accumulator-copy", "rdjperron/no-reflect-apply", "rdjperron/no-reflect-get", "rdjperron/no-runtime-typeof", "rdjperron/no-shape-in-symbol-names", "rdjperron/no-unknown-parameters", "rdjperron/no-unknown-returns", "rdjperron/no-unknown-type-aliases", "rdjperron/no-unsafe-dictionary-type", "rdjperron/no-use-effect", "rdjperron/no-widen-then-assert", "rdjperron/require-safety-comment-for-type-assertion", "rdjperron/static-public-env"];
58
58
  export type RuleId = (typeof RULE_IDS)[number];
59
59
  /**
60
60
  * The base config, for `lint: { extends: [recommended] }`.
@@ -117,6 +117,43 @@ export interface ExpoOptions {
117
117
  * `require()` of an asset is how that asset enters the bundle.
118
118
  */
119
119
  export declare function expo(files: string[], options?: ExpoOptions): Override[];
120
+ /** Convex's own plugin, loaded by the override `convex()` returns rather than at the top level. */
121
+ export declare const convexPlugin: JsPlugin;
122
+ export interface ConvexOptions {
123
+ /**
124
+ * The module that builds the project's checked constructors, such as
125
+ * `convex/lib/functions.ts`. It switches `convex-checked-constructors` on, exempts that one
126
+ * file, and makes every name imported from it a constructor `convex-require-validators`
127
+ * follows. A relative path is looked for from the working directory upward; an absolute one
128
+ * pins the file.
129
+ */
130
+ wrapper?: string;
131
+ /**
132
+ * Spellings other than a relative path that import the wrapper, such as
133
+ * `@/convex/lib/functions` from a tsconfig `paths` alias. The rules cannot read a bundler's or
134
+ * tsconfig's aliases, so only the spellings listed here, compared exactly as written, name the
135
+ * wrapper.
136
+ */
137
+ wrapperImports?: string[];
138
+ /** Whether every function must also declare `returns`. On by default. */
139
+ returns?: boolean;
140
+ /**
141
+ * Test code, where a query reads a table the test filled itself, so `.collect()` and
142
+ * `.filter()` cost nothing.
143
+ */
144
+ tests?: string[];
145
+ /** The project's own reason for its checked constructors, added to each `convex-checked-constructors` finding. */
146
+ hint?: string;
147
+ }
148
+ /**
149
+ * The Convex backend: Convex's own rules, loaded from `@convex-dev/eslint-plugin` through this
150
+ * override's `jsPlugins`, and the two `rdjperron/convex-*` rules.
151
+ *
152
+ * oxlint gives a JS plugin no type information, so `explicit-table-ids` and
153
+ * `no-collect-in-query` run on their syntax fallbacks: each reports without its autofix, and
154
+ * matches the `x.db.method(...)` and `x.db.query(...)...collect()` chains by shape.
155
+ */
156
+ export declare function convex(files: string[], options?: ConvexOptions): Override[];
120
157
  /** Test files: four rules whose premise a test does not hold. */
121
158
  export declare function tests(files: string[]): Override[];
122
159
  /** One banned module or named import, as `no-restricted-imports` spells a path. */
package/dist/config.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as i18n, c as react, d as staged, f as tests, i as fmt, l as recommended, n as decoders, o as ignore, r as expo, s as plugin, t as RULE_IDS, u as restrictedImports } from "./config-CD2iCM6L.mjs";
2
- export { RULE_IDS, decoders, expo, fmt, i18n, ignore, plugin, react, recommended, restrictedImports, staged, tests };
1
+ import { a as expo, c as ignore, d as recommended, f as restrictedImports, i as decoders, l as plugin, m as tests, n as convex, o as fmt, p as staged, r as convexPlugin, s as i18n, t as RULE_IDS, u as react } from "./config-BCudqv3_.mjs";
2
+ export { RULE_IDS, convex, convexPlugin, decoders, expo, fmt, i18n, ignore, plugin, react, recommended, restrictedImports, staged, tests };