@rdjperron/lint 0.6.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 +283 -122
- package/dist/{config-CD2iCM6L.mjs → config-BCudqv3_.mjs} +58 -2
- package/dist/config-BCudqv3_.mjs.map +1 -0
- package/dist/config.d.mts +38 -1
- package/dist/config.mjs +2 -2
- package/dist/index.mjs +691 -7
- package/dist/index.mjs.map +1 -1
- package/dist/repo.d.mts +16 -27
- package/dist/repo.mjs +28 -164
- package/dist/repo.mjs.map +1 -1
- package/dist/verify.mjs +122 -50
- package/dist/verify.mjs.map +1 -1
- package/fixtures/README.md +5 -0
- package/fixtures/convex/explicit-table-ids.fixture.ts +4 -0
- package/fixtures/convex-checked-constructors.fixture.ts +4 -0
- package/fixtures/convex-require-validators.fixture.ts +4 -0
- package/package.json +8 -3
- package/dist/config-CD2iCM6L.mjs.map +0 -1
package/dist/verify.mjs
CHANGED
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { l as plugin, n as convex, r as convexPlugin, t as RULE_IDS } from "./config-BCudqv3_.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
-
import { cpSync, existsSync, mkdtempSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { cpSync, existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
+
//#region src/verify-convex.ts
|
|
10
|
+
const CONVEX_PROBES = ["explicit-table-ids"];
|
|
11
|
+
function readManifest(root) {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
14
|
+
} catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Whether Convex's plugin is there, judged by its directory in a `node_modules` Node would
|
|
20
|
+
* search, not by resolving it: a package whose `exports` hide `package.json`, or whose entry is
|
|
21
|
+
* broken, is installed all the same, and the lint run is what has to fail on it.
|
|
22
|
+
*/
|
|
23
|
+
function convexPluginState(root) {
|
|
24
|
+
const name = convexPlugin.specifier;
|
|
25
|
+
if ((createRequire(join(root, "package.json")).resolve.paths(name) ?? []).some((directory) => existsSync(join(directory, name)))) return "installed";
|
|
26
|
+
const manifest = readManifest(root);
|
|
27
|
+
return [
|
|
28
|
+
manifest.dependencies,
|
|
29
|
+
manifest.devDependencies,
|
|
30
|
+
manifest.peerDependencies
|
|
31
|
+
].some((table) => table?.[name] !== void 0) ? "declaredButMissing" : "absent";
|
|
32
|
+
}
|
|
33
|
+
function convexFixturesProblem(files) {
|
|
34
|
+
if (files.size === 0) return "fixtures/convex/ holds no fixture, so nothing would prove the load.";
|
|
35
|
+
const missing = CONVEX_PROBES.filter((rule) => !files.has(rule));
|
|
36
|
+
return missing.length === 0 ? null : `fixtures/convex/ has no fixture for ${missing.join(", ")}, the rule the check must see fire.`;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
9
39
|
//#region src/verify.ts
|
|
10
40
|
/**
|
|
11
41
|
* Proof that every rule of this plugin still fires.
|
|
@@ -22,16 +52,23 @@ import { fileURLToPath } from "node:url";
|
|
|
22
52
|
* takes no part, so the answer is about the plugin and the linter alone and a
|
|
23
53
|
* consumer has nothing to write.
|
|
24
54
|
*
|
|
55
|
+
* Where `@convex-dev/eslint-plugin` is installed too, the same project lints
|
|
56
|
+
* `fixtures/convex/` under the overrides `convex()` returns, and asserts that each
|
|
57
|
+
* Convex rule named there fired. That plugin is loaded by the override itself, so
|
|
58
|
+
* this is the check that the load still works.
|
|
59
|
+
*
|
|
25
60
|
* The linter is the oxlint the LOCAL `vite-plus` depends on, so it follows the
|
|
26
61
|
* lockfile rather than the `vp` on PATH (measured 2026-09-19: a global `vp`
|
|
27
62
|
* 0.3.1 over a local `vite-plus` 0.2.9 ran oxlint 1.77.0). The run that earns
|
|
28
63
|
* this check its place is therefore the commit that moves `vite-plus`, and CI.
|
|
29
64
|
*
|
|
30
|
-
* npx rdjperron-lint-verify
|
|
65
|
+
* npx rdjperron-lint-verify [--require-convex]
|
|
31
66
|
*/
|
|
32
67
|
const FIXTURE_SUFFIXES = [".fixture.ts", ".fixture.tsx"];
|
|
33
68
|
/** Relative to this module, so it resolves from `dist/verify.mjs` and from `src/verify.ts` alike. */
|
|
34
69
|
const FIXTURES_DIR = fileURLToPath(new URL("../fixtures", import.meta.url));
|
|
70
|
+
/** The files `convex()` is pointed at in the throwaway project, one per Convex rule they trip. */
|
|
71
|
+
const CONVEX_FIXTURES = "fixtures/convex";
|
|
35
72
|
/** Deterministic, locale-independent ordering for the rule and file name lists. */
|
|
36
73
|
function byName(left, right) {
|
|
37
74
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
@@ -113,10 +150,10 @@ function lintAsJson(binary, cwd, target) {
|
|
|
113
150
|
}
|
|
114
151
|
}
|
|
115
152
|
/** Rule name to fixture file name, taken from the files that ship with the package. */
|
|
116
|
-
function fixtureFiles() {
|
|
117
|
-
if (!existsSync(
|
|
153
|
+
function fixtureFiles(directory) {
|
|
154
|
+
if (!existsSync(directory)) throw new Error(`Fixture directory is missing: ${directory}`);
|
|
118
155
|
const files = /* @__PURE__ */ new Map();
|
|
119
|
-
for (const name of readdirSync(
|
|
156
|
+
for (const name of readdirSync(directory).toSorted(byName)) {
|
|
120
157
|
const suffix = FIXTURE_SUFFIXES.find((candidate) => name.endsWith(candidate));
|
|
121
158
|
if (suffix === void 0) continue;
|
|
122
159
|
files.set(name.slice(0, -suffix.length), name);
|
|
@@ -124,9 +161,10 @@ function fixtureFiles() {
|
|
|
124
161
|
return files;
|
|
125
162
|
}
|
|
126
163
|
/** The throwaway project: a config naming every rule at error, the fixtures, and the modules. */
|
|
127
|
-
function createProject(modules, specifier) {
|
|
164
|
+
function createProject(modules, specifier, withConvex) {
|
|
128
165
|
const dir = mkdtempSync(join(tmpdir(), "rdjperron-lint-verify-"));
|
|
129
166
|
const rules = Object.fromEntries(RULE_IDS.map((id) => [id, "error"]));
|
|
167
|
+
const overrides = withConvex ? convex([`${CONVEX_FIXTURES}/**`]) : [];
|
|
130
168
|
writeFileSync(join(dir, "package.json"), `${JSON.stringify({
|
|
131
169
|
name: "rdjperron-lint-verify",
|
|
132
170
|
private: true,
|
|
@@ -143,6 +181,7 @@ function createProject(modules, specifier) {
|
|
|
143
181
|
specifier
|
|
144
182
|
})}],`,
|
|
145
183
|
` rules: ${JSON.stringify(rules, null, 2).replaceAll("\n", "\n ")},`,
|
|
184
|
+
` overrides: ${JSON.stringify(overrides, null, 2).replaceAll("\n", "\n ")},`,
|
|
146
185
|
` },`,
|
|
147
186
|
`});`,
|
|
148
187
|
``
|
|
@@ -151,10 +190,10 @@ function createProject(modules, specifier) {
|
|
|
151
190
|
symlinkSync(modules, join(dir, "node_modules"), "junction");
|
|
152
191
|
return dir;
|
|
153
192
|
}
|
|
154
|
-
/** `rdjperron(no-reflect-get)` -> `no-reflect-get`; a diagnostic from
|
|
155
|
-
function ruleIdFor(code) {
|
|
156
|
-
const
|
|
157
|
-
return
|
|
193
|
+
/** `rdjperron(no-reflect-get)` -> `no-reflect-get`; a diagnostic from another plugin -> null. */
|
|
194
|
+
function ruleIdFor(code, name) {
|
|
195
|
+
const prefix = `${name}(`;
|
|
196
|
+
return code.startsWith(prefix) && code.endsWith(")") ? code.slice(prefix.length, -1) : null;
|
|
158
197
|
}
|
|
159
198
|
/** The rule names the loaded plugin registers, or null when it cannot be imported. */
|
|
160
199
|
async function registeredRules(specifier) {
|
|
@@ -173,20 +212,74 @@ function pluginSpecifier(argv) {
|
|
|
173
212
|
if (value === void 0) throw new Error("`--plugin` needs a path to the plugin module.");
|
|
174
213
|
return resolve(value);
|
|
175
214
|
}
|
|
215
|
+
/** Which rule fired on which fixture file, for every diagnostic whose code carries `name`. */
|
|
216
|
+
function firedBy(diagnostics, name) {
|
|
217
|
+
const fired = /* @__PURE__ */ new Map();
|
|
218
|
+
for (const diagnostic of diagnostics) {
|
|
219
|
+
const ruleId = ruleIdFor(diagnostic.code, name);
|
|
220
|
+
if (ruleId === null) continue;
|
|
221
|
+
const seen = fired.get(ruleId) ?? /* @__PURE__ */ new Set();
|
|
222
|
+
seen.add(basename(diagnostic.filename));
|
|
223
|
+
fired.set(ruleId, seen);
|
|
224
|
+
}
|
|
225
|
+
return fired;
|
|
226
|
+
}
|
|
227
|
+
/** One row per fixture: did the rule it is named after fire on it. */
|
|
228
|
+
function rowsFor(files, fired, prefix) {
|
|
229
|
+
return [...files].map(([rule, fixture]) => {
|
|
230
|
+
const seen = fired.get(rule) ?? /* @__PURE__ */ new Set();
|
|
231
|
+
const firedOnOwnFixture = seen.has(fixture);
|
|
232
|
+
return {
|
|
233
|
+
rule: `${prefix}${rule}`,
|
|
234
|
+
fixture,
|
|
235
|
+
firedOnOwnFixture,
|
|
236
|
+
note: firedOnOwnFixture ? "" : seen.size > 0 ? `fired only on: ${[...seen].toSorted(byName).join(", ")}` : "rule did not fire on any fixture"
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
function printRows(rows) {
|
|
241
|
+
const width = Math.max(...rows.map((row) => row.rule.length), 4);
|
|
242
|
+
console.log(`${"RULE".padEnd(width)} RESULT FIXTURE`);
|
|
243
|
+
console.log(`${"-".repeat(width)} ------ -------`);
|
|
244
|
+
for (const row of rows) {
|
|
245
|
+
const verdict = row.firedOnOwnFixture ? "PASS " : "FAIL ";
|
|
246
|
+
const suffix = row.note === "" ? "" : ` <-- ${row.note}`;
|
|
247
|
+
console.log(`${row.rule.padEnd(width)} ${verdict} ${row.fixture}${suffix}`);
|
|
248
|
+
}
|
|
249
|
+
console.log("");
|
|
250
|
+
}
|
|
176
251
|
async function main(argv) {
|
|
177
252
|
const specifier = pluginSpecifier(argv);
|
|
178
253
|
const binary = resolveViteplusBinary();
|
|
179
|
-
const
|
|
180
|
-
const
|
|
254
|
+
const root = process.cwd();
|
|
255
|
+
const modules = resolveConsumerModules(root);
|
|
256
|
+
const files = fixtureFiles(FIXTURES_DIR);
|
|
181
257
|
const expectedRules = [...files.keys()];
|
|
258
|
+
const convexState = convexPluginState(root);
|
|
259
|
+
const withConvex = convexState === "installed";
|
|
260
|
+
const convexFiles = withConvex ? fixtureFiles(join(FIXTURES_DIR, "convex")) : /* @__PURE__ */ new Map();
|
|
182
261
|
console.log(`${plugin.name} rule verification`);
|
|
183
262
|
console.log(` plugin: ${specifier}`);
|
|
184
263
|
console.log(` fixtures: ${FIXTURES_DIR}`);
|
|
264
|
+
console.log(` convex(): ${withConvex ? `${convexPlugin.specifier} is installed, so its load is checked too` : `skipped, ${convexPlugin.specifier} is not installed`}`);
|
|
265
|
+
if (convexState === "declaredButMissing") {
|
|
266
|
+
console.error(`\nFAIL: package.json declares ${convexPlugin.specifier} but it is not installed. Install it, or drop it.`);
|
|
267
|
+
return 1;
|
|
268
|
+
}
|
|
269
|
+
if (!withConvex && argv.includes("--require-convex")) {
|
|
270
|
+
console.error(`\nFAIL: --require-convex, and ${convexPlugin.specifier} is not installed beside vite-plus.`);
|
|
271
|
+
return 1;
|
|
272
|
+
}
|
|
273
|
+
const convexProblem = withConvex ? convexFixturesProblem(convexFiles) : null;
|
|
274
|
+
if (convexProblem !== null) {
|
|
275
|
+
console.error(`\nFAIL: ${convexProblem}`);
|
|
276
|
+
return 1;
|
|
277
|
+
}
|
|
185
278
|
if (expectedRules.length === 0) {
|
|
186
279
|
console.error(` No fixture files in ${FIXTURES_DIR}. Nothing to verify.\n`);
|
|
187
280
|
return 1;
|
|
188
281
|
}
|
|
189
|
-
const project = createProject(modules, specifier);
|
|
282
|
+
const project = createProject(modules, specifier, withConvex);
|
|
190
283
|
let report;
|
|
191
284
|
try {
|
|
192
285
|
console.log(` project: ${project}`);
|
|
@@ -202,40 +295,17 @@ async function main(argv) {
|
|
|
202
295
|
console.error("FAIL: `vp lint` matched none of the fixtures in the throwaway project.\nNothing there is ignored and the fixtures are copied in, so this is `vp lint` refusing\nthe generated config rather than a rule going quiet.\n");
|
|
203
296
|
return expectedRules.length;
|
|
204
297
|
}
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
for (const diagnostic of report.diagnostics) {
|
|
208
|
-
const ruleId = ruleIdFor(diagnostic.code);
|
|
209
|
-
if (ruleId === null) {
|
|
210
|
-
foreignDiagnostics.push(diagnostic);
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
const seen = firedByRule.get(ruleId) ?? /* @__PURE__ */ new Set();
|
|
214
|
-
seen.add(basename(diagnostic.filename));
|
|
215
|
-
firedByRule.set(ruleId, seen);
|
|
216
|
-
}
|
|
217
|
-
const rows = expectedRules.map((rule) => {
|
|
218
|
-
const seen = firedByRule.get(rule) ?? /* @__PURE__ */ new Set();
|
|
219
|
-
const fixture = files.get(rule) ?? rule;
|
|
220
|
-
const firedOnOwnFixture = seen.has(fixture);
|
|
221
|
-
return {
|
|
222
|
-
rule,
|
|
223
|
-
fixture,
|
|
224
|
-
firedOnOwnFixture,
|
|
225
|
-
note: firedOnOwnFixture ? "" : seen.size > 0 ? `fired only on: ${[...seen].toSorted(byName).join(", ")}` : "rule did not fire on any fixture"
|
|
226
|
-
};
|
|
227
|
-
});
|
|
228
|
-
const width = Math.max(...rows.map((row) => row.rule.length), 4);
|
|
229
|
-
console.log(`${"RULE".padEnd(width)} RESULT FIXTURE`);
|
|
230
|
-
console.log(`${"-".repeat(width)} ------ -------`);
|
|
231
|
-
for (const row of rows) {
|
|
232
|
-
const verdict = row.firedOnOwnFixture ? "PASS " : "FAIL ";
|
|
233
|
-
const suffix = row.note === "" ? "" : ` <-- ${row.note}`;
|
|
234
|
-
console.log(`${row.rule.padEnd(width)} ${verdict} ${row.fixture}${suffix}`);
|
|
235
|
-
}
|
|
236
|
-
console.log("");
|
|
298
|
+
const rows = rowsFor(files, firedBy(report.diagnostics, plugin.name), "");
|
|
299
|
+
printRows(rows);
|
|
237
300
|
const failures = rows.filter((row) => !row.firedOnOwnFixture);
|
|
238
301
|
console.log(`${rows.length - failures.length}/${rows.length} ${plugin.name} rules fired on their own fixture.`);
|
|
302
|
+
const convexRows = rowsFor(convexFiles, firedBy(report.diagnostics, convexPlugin.name), `${convexPlugin.name}/`);
|
|
303
|
+
const convexFailures = convexRows.filter((row) => !row.firedOnOwnFixture);
|
|
304
|
+
if (withConvex) {
|
|
305
|
+
console.log("");
|
|
306
|
+
printRows(convexRows);
|
|
307
|
+
console.log(`${convexRows.length - convexFailures.length}/${convexRows.length} ${convexPlugin.name} rules fired under convex().`);
|
|
308
|
+
}
|
|
239
309
|
const registered = await registeredRules(specifier);
|
|
240
310
|
if (registered !== null) {
|
|
241
311
|
const missingFixture = registered.filter((rule) => !expectedRules.includes(rule));
|
|
@@ -243,16 +313,18 @@ async function main(argv) {
|
|
|
243
313
|
if (missingFixture.length > 0) console.log(`\nWARN: registered by the ${plugin.name} plugin but has no fixture: ${missingFixture.join(", ")}`);
|
|
244
314
|
if (orphanFixture.length > 0) console.log(`\nWARN: fixture exists but the ${plugin.name} plugin registers no such rule: ${orphanFixture.join(", ")}`);
|
|
245
315
|
}
|
|
316
|
+
const foreignDiagnostics = report.diagnostics.filter((diagnostic) => ruleIdFor(diagnostic.code, plugin.name) === null && ruleIdFor(diagnostic.code, convexPlugin.name) === null);
|
|
246
317
|
if (foreignDiagnostics.length > 0) {
|
|
247
318
|
console.log(`\nWARN: ${foreignDiagnostics.length} non-${plugin.name} diagnostic(s) in the fixtures:`);
|
|
248
319
|
for (const diagnostic of foreignDiagnostics.slice(0, 20)) console.log(` ${basename(diagnostic.filename)}: ${diagnostic.code}`);
|
|
249
320
|
}
|
|
250
|
-
if (failures.length > 0) {
|
|
251
|
-
console.error(`\nFAIL: ${failures.length} ${plugin.name} rule(s) did not fire. See ${join(FIXTURES_DIR, "README.md")}.\nEvery rule was at error in the generated config, so a rule that stayed quiet is one this
|
|
321
|
+
if (failures.length > 0 || convexFailures.length > 0) {
|
|
322
|
+
if (failures.length > 0) console.error(`\nFAIL: ${failures.length} ${plugin.name} rule(s) did not fire. See ${join(FIXTURES_DIR, "README.md")}.\nEvery rule was at error in the generated config, so a rule that stayed quiet is one this
|
|
252
323
|
linter did not run, not one this config left off.`);
|
|
253
|
-
|
|
324
|
+
if (convexFailures.length > 0) console.error(`\nFAIL: ${convexFailures.length} ${convexPlugin.name} rule(s) did not fire under convex().\nconvex() loads ${convexPlugin.specifier} through its own override's \`jsPlugins\`, and\nthese rules were at error there, so this linter did not load the plugin that way.`);
|
|
325
|
+
return failures.length + convexFailures.length;
|
|
254
326
|
}
|
|
255
|
-
console.log(`\nOK: every ${plugin.name} rule still fires.`);
|
|
327
|
+
console.log(`\nOK: every ${plugin.name} rule still fires${withConvex ? `, and so do ${convexPlugin.name}'s under convex()` : ""}.`);
|
|
256
328
|
return 0;
|
|
257
329
|
}
|
|
258
330
|
let exitCode = 1;
|
package/dist/verify.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.mjs","names":[],"sources":["../src/verify.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Proof that every rule of this plugin still fires.\n *\n * A dead lint rule is silent: \"reports nothing\" reads exactly like \"no\n * violations\", so nothing else in a repo notices when a linter upgrade, a\n * JS-plugin API change or an ignore pattern takes a rule out. This lints one\n * deliberately-broken fixture per rule and asserts a positive: every rule fired,\n * each on the file named after it.\n *\n * It runs in a throwaway project under the system temp directory: its own\n * generated config with every rule at error, the shipped fixtures copied in, and\n * the caller's `node_modules` symlinked beside them. The caller's own config\n * takes no part, so the answer is about the plugin and the linter alone and a\n * consumer has nothing to write.\n *\n * The linter is the oxlint the LOCAL `vite-plus` depends on, so it follows the\n * lockfile rather than the `vp` on PATH (measured 2026-09-19: a global `vp`\n * 0.3.1 over a local `vite-plus` 0.2.9 ran oxlint 1.77.0). The run that earns\n * this check its place is therefore the commit that moves `vite-plus`, and CI.\n *\n * npx rdjperron-lint-verify\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport {\n cpSync,\n existsSync,\n mkdtempSync,\n readdirSync,\n rmSync,\n symlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { tmpdir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { plugin, RULE_IDS } from \"./config.ts\";\n\nconst FIXTURE_SUFFIXES = [\".fixture.ts\", \".fixture.tsx\"];\n\n/** Relative to this module, so it resolves from `dist/verify.mjs` and from `src/verify.ts` alike. */\nconst FIXTURES_DIR = fileURLToPath(new URL(\"../fixtures\", import.meta.url));\n\n/** Deterministic, locale-independent ordering for the rule and file name lists. */\nfunction byName(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n\nfunction basename(path: string): string {\n const parts = path.split(/[\\\\/]/u);\n return parts[parts.length - 1] ?? path;\n}\n\n/**\n * Locate the `vp` binary: PATH first, then the default install locations, the\n * XDG layout used since Vite+ 0.3.0 and the pre-0.3.0 single-root layout that\n * upgraded installs keep.\n */\nfunction resolveViteplusBinary(): string {\n const probe = spawnSync(\"vp\", [\"--version\"], { encoding: \"utf8\" });\n if (probe.status === 0) return \"vp\";\n const home = process.env.HOME ?? \"\";\n const fallbacks = [\n join(home, \".local\", \"share\", \"vite-plus\", \"bin\", \"vp\"),\n join(home, \".vite-plus\", \"bin\", \"vp\"),\n ];\n const found = fallbacks.find((candidate) => existsSync(candidate));\n if (found !== undefined) return found;\n throw new Error(\n \"Could not find the `vp` binary. Add it to PATH: \" +\n 'export PATH=\"$HOME/.local/share/vite-plus/bin:$PATH\" (or \"$HOME/.vite-plus/bin\" for a pre-0.3.0 install)',\n );\n}\n\n/** The `vite-plus` whose oxlint will run, and the `node_modules` the project borrows. */\nfunction resolveConsumerModules(root: string): string {\n const modules = join(root, \"node_modules\");\n if (!existsSync(modules)) {\n throw new Error(`No node_modules in ${root}. Install the dependencies and run this again.`);\n }\n try {\n createRequire(join(root, \"package.json\")).resolve(\"vite-plus/package.json\");\n } catch {\n throw new Error(\n `\\`vite-plus\\` is not installed in ${root}. The linter this check runs is the oxlint the ` +\n \"local `vite-plus` depends on, so without it there is nothing to prove the rules against.\",\n );\n }\n return modules;\n}\n\ninterface LintDiagnostic {\n code: string;\n filename: string;\n}\n\ninterface LintReport {\n numberOfFiles: number;\n diagnostics: LintDiagnostic[];\n}\n\n/** The two fields of oxlint's JSON report this reads, before any of them is trusted. */\ninterface RawReport {\n number_of_files?: unknown;\n diagnostics?: unknown;\n}\n\n/** The two fields of one raw diagnostic, before either of them is trusted. */\ninterface RawDiagnostic {\n code?: unknown;\n filename?: unknown;\n}\n\n/**\n * `vp lint -f json` is an outside value, so this is the one place its shape is\n * checked. Anything that does not look like a diagnostic is dropped rather than\n * guessed at.\n */\nfunction decodeReport(json: string): LintReport {\n const parsed: unknown = JSON.parse(json);\n if (typeof parsed !== \"object\" || parsed === null) {\n throw new Error(\"`vp lint -f json` did not return an object.\");\n }\n // SAFETY: the check above makes it a non-null object, and every field of\n // `RawReport` is `unknown`, so nothing is claimed about its contents yet.\n const report = parsed as RawReport;\n const numberOfFiles = typeof report.number_of_files === \"number\" ? report.number_of_files : 0;\n const rows = Array.isArray(report.diagnostics) ? report.diagnostics : [];\n const diagnostics: LintDiagnostic[] = [];\n for (const row of rows) {\n if (typeof row !== \"object\" || row === null) continue;\n // SAFETY: same check, one level down; both fields of `RawDiagnostic` are\n // `unknown` and each is typed by its own test on the next lines.\n const entry = row as RawDiagnostic;\n diagnostics.push({\n code: typeof entry.code === \"string\" ? entry.code : \"\",\n filename: typeof entry.filename === \"string\" ? entry.filename : \"\",\n });\n }\n return { numberOfFiles, diagnostics };\n}\n\n/** Run `vp lint <target> -f json` from `cwd` and return the decoded report. */\nfunction lintAsJson(binary: string, cwd: string, target: string): LintReport {\n const result = spawnSync(\n binary,\n [\"lint\", target, \"-f\", \"json\", \"--no-error-on-unmatched-pattern\"],\n { cwd, encoding: \"utf8\", maxBuffer: 64 * 1024 * 1024 },\n );\n\n if (result.error !== undefined && result.error !== null) throw result.error;\n\n // `--no-error-on-unmatched-pattern` keeps the \"No files found to lint.\" banner\n // off stdout, so stdout is pure JSON even when nothing matched.\n const stdout = result.stdout ?? \"\";\n const start = stdout.indexOf(\"{\");\n if (start === -1) {\n throw new Error(\n `\\`vp lint ${target} -f json\\` produced no JSON.\\nstdout: ${stdout}\\nstderr: ${result.stderr ?? \"\"}`,\n );\n }\n\n try {\n return decodeReport(stdout.slice(start));\n } catch (cause) {\n const reason = cause instanceof Error ? cause.message : String(cause);\n throw new Error(`Could not read \\`vp lint\\` JSON output: ${reason}\\nstdout: ${stdout}`, {\n cause,\n });\n }\n}\n\n/** Rule name to fixture file name, taken from the files that ship with the package. */\nfunction fixtureFiles(): Map<string, string> {\n if (!existsSync(FIXTURES_DIR)) throw new Error(`Fixture directory is missing: ${FIXTURES_DIR}`);\n const files = new Map<string, string>();\n for (const name of readdirSync(FIXTURES_DIR).toSorted(byName)) {\n const suffix = FIXTURE_SUFFIXES.find((candidate) => name.endsWith(candidate));\n if (suffix === undefined) continue;\n files.set(name.slice(0, -suffix.length), name);\n }\n return files;\n}\n\n/** The throwaway project: a config naming every rule at error, the fixtures, and the modules. */\nfunction createProject(modules: string, specifier: string): string {\n const dir = mkdtempSync(join(tmpdir(), \"rdjperron-lint-verify-\"));\n const rules = Object.fromEntries(RULE_IDS.map((id) => [id, \"error\"]));\n writeFileSync(\n join(dir, \"package.json\"),\n `${JSON.stringify({ name: \"rdjperron-lint-verify\", private: true, type: \"module\" }, null, 2)}\\n`,\n );\n writeFileSync(\n join(dir, \"vite.config.ts\"),\n [\n `import { defineConfig } from \"vite-plus\";`,\n ``,\n `export default defineConfig({`,\n ` lint: {`,\n ` plugins: [],`,\n ` jsPlugins: [${JSON.stringify({ name: plugin.name, specifier })}],`,\n ` rules: ${JSON.stringify(rules, null, 2).replaceAll(\"\\n\", \"\\n \")},`,\n ` },`,\n `});`,\n ``,\n ].join(\"\\n\"),\n );\n cpSync(FIXTURES_DIR, join(dir, \"fixtures\"), { recursive: true });\n // A junction is the one link Windows takes for node_modules, and copying it is out of the question.\n symlinkSync(modules, join(dir, \"node_modules\"), \"junction\");\n return dir;\n}\n\n/** `rdjperron(no-reflect-get)` -> `no-reflect-get`; a diagnostic from anything else -> null. */\nfunction ruleIdFor(code: string): string | null {\n const match = new RegExp(`^${plugin.name}\\\\(([^)]+)\\\\)$`, \"u\").exec(code);\n return match === null ? null : (match[1] ?? null);\n}\n\n/** The rule names the loaded plugin registers, or null when it cannot be imported. */\nasync function registeredRules(specifier: string): Promise<string[] | null> {\n try {\n const loaded: unknown = await import(specifier);\n // SAFETY: the plugin's default export is the object oxlint loads; `rules` is\n // still `unknown` here and is checked on the next line.\n const module_ = loaded as { default?: { rules?: unknown } };\n const rules = module_.default?.rules;\n return typeof rules === \"object\" && rules !== null ? Object.keys(rules).toSorted(byName) : null;\n } catch {\n return null;\n }\n}\n\ninterface Row {\n rule: string;\n fixture: string;\n firedOnOwnFixture: boolean;\n note: string;\n}\n\n/** `--plugin <path>` points the run at a plugin on disk, which is how this repo checks its source. */\nfunction pluginSpecifier(argv: string[]): string {\n const index = argv.indexOf(\"--plugin\");\n if (index === -1) return plugin.specifier;\n const value = argv[index + 1];\n if (value === undefined) throw new Error(\"`--plugin` needs a path to the plugin module.\");\n return resolve(value);\n}\n\nasync function main(argv: string[]): Promise<number> {\n const specifier = pluginSpecifier(argv);\n const binary = resolveViteplusBinary();\n const modules = resolveConsumerModules(process.cwd());\n const files = fixtureFiles();\n const expectedRules = [...files.keys()];\n\n console.log(`${plugin.name} rule verification`);\n console.log(` plugin: ${specifier}`);\n console.log(` fixtures: ${FIXTURES_DIR}`);\n\n if (expectedRules.length === 0) {\n console.error(` No fixture files in ${FIXTURES_DIR}. Nothing to verify.\\n`);\n return 1;\n }\n\n const project = createProject(modules, specifier);\n let report: LintReport;\n try {\n console.log(` project: ${project}`);\n report = lintAsJson(binary, project, \"fixtures\");\n } finally {\n rmSync(project, { recursive: true, force: true });\n }\n\n console.log(` files: ${report.numberOfFiles}\\n`);\n\n if (report.numberOfFiles === 0) {\n console.error(\n \"FAIL: `vp lint` matched none of the fixtures in the throwaway project.\\n\" +\n \"Nothing there is ignored and the fixtures are copied in, so this is `vp lint` refusing\\n\" +\n \"the generated config rather than a rule going quiet.\\n\",\n );\n return expectedRules.length;\n }\n\n const firedByRule = new Map<string, Set<string>>();\n const foreignDiagnostics: LintDiagnostic[] = [];\n\n for (const diagnostic of report.diagnostics) {\n const ruleId = ruleIdFor(diagnostic.code);\n if (ruleId === null) {\n foreignDiagnostics.push(diagnostic);\n continue;\n }\n const seen = firedByRule.get(ruleId) ?? new Set<string>();\n seen.add(basename(diagnostic.filename));\n firedByRule.set(ruleId, seen);\n }\n\n const rows: Row[] = expectedRules.map((rule) => {\n const seen = firedByRule.get(rule) ?? new Set<string>();\n const fixture = files.get(rule) ?? rule;\n const firedOnOwnFixture = seen.has(fixture);\n return {\n rule,\n fixture,\n firedOnOwnFixture,\n note: firedOnOwnFixture\n ? \"\"\n : seen.size > 0\n ? `fired only on: ${[...seen].toSorted(byName).join(\", \")}`\n : \"rule did not fire on any fixture\",\n };\n });\n\n const width = Math.max(...rows.map((row) => row.rule.length), 4);\n console.log(`${\"RULE\".padEnd(width)} RESULT FIXTURE`);\n console.log(`${\"-\".repeat(width)} ------ -------`);\n for (const row of rows) {\n const verdict = row.firedOnOwnFixture ? \"PASS \" : \"FAIL \";\n const suffix = row.note === \"\" ? \"\" : ` <-- ${row.note}`;\n console.log(`${row.rule.padEnd(width)} ${verdict} ${row.fixture}${suffix}`);\n }\n console.log(\"\");\n\n const failures = rows.filter((row) => !row.firedOnOwnFixture);\n console.log(\n `${rows.length - failures.length}/${rows.length} ${plugin.name} rules fired on their own fixture.`,\n );\n\n // Advisory: a release that adds a rule should also add a fixture.\n const registered = await registeredRules(specifier);\n if (registered !== null) {\n const missingFixture = registered.filter((rule) => !expectedRules.includes(rule));\n const orphanFixture = expectedRules.filter((rule) => !registered.includes(rule));\n if (missingFixture.length > 0) {\n console.log(\n `\\nWARN: registered by the ${plugin.name} plugin but has no fixture: ${missingFixture.join(\", \")}`,\n );\n }\n if (orphanFixture.length > 0) {\n console.log(\n `\\nWARN: fixture exists but the ${plugin.name} plugin registers no such rule: ${orphanFixture.join(\", \")}`,\n );\n }\n }\n\n // Advisory: fixtures are meant to trip exactly one rule each.\n if (foreignDiagnostics.length > 0) {\n console.log(\n `\\nWARN: ${foreignDiagnostics.length} non-${plugin.name} diagnostic(s) in the fixtures:`,\n );\n for (const diagnostic of foreignDiagnostics.slice(0, 20)) {\n console.log(` ${basename(diagnostic.filename)}: ${diagnostic.code}`);\n }\n }\n\n if (failures.length > 0) {\n console.error(\n `\\nFAIL: ${failures.length} ${plugin.name} rule(s) did not fire. See ${join(FIXTURES_DIR, \"README.md\")}.\\n` +\n \"Every rule was at error in the generated config, so a rule that stayed quiet is one this\\n\" +\n \"linter did not run, not one this config left off.\",\n );\n return failures.length;\n }\n\n console.log(`\\nOK: every ${plugin.name} rule still fires.`);\n return 0;\n}\n\nlet exitCode = 1;\ntry {\n exitCode = await main(process.argv.slice(2));\n} catch (error) {\n console.error(`\\nFAIL: ${error instanceof Error ? error.message : String(error)}`);\n}\nprocess.exit(exitCode === 0 ? 0 : 1);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"verify.mjs","names":[],"sources":["../src/verify-convex.ts","../src/verify.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { join } from \"node:path\";\n\nimport { convexPlugin } from \"./config.ts\";\n\nexport const CONVEX_PROBES = [\"explicit-table-ids\"];\n\nexport type ConvexPluginState = \"absent\" | \"installed\" | \"declaredButMissing\";\n\ninterface Manifest {\n dependencies?: { [name: string]: string };\n devDependencies?: { [name: string]: string };\n peerDependencies?: { [name: string]: string };\n}\n\nfunction readManifest(root: string): Manifest {\n try {\n // SAFETY: only the three dependency maps are read, and each lookup below tolerates absence.\n return JSON.parse(readFileSync(join(root, \"package.json\"), \"utf8\")) as Manifest;\n } catch {\n return {};\n }\n}\n\n/**\n * Whether Convex's plugin is there, judged by its directory in a `node_modules` Node would\n * search, not by resolving it: a package whose `exports` hide `package.json`, or whose entry is\n * broken, is installed all the same, and the lint run is what has to fail on it.\n */\nexport function convexPluginState(root: string): ConvexPluginState {\n const name = convexPlugin.specifier;\n const searched = createRequire(join(root, \"package.json\")).resolve.paths(name) ?? [];\n if (searched.some((directory) => existsSync(join(directory, name)))) return \"installed\";\n const manifest = readManifest(root);\n const declared = [\n manifest.dependencies,\n manifest.devDependencies,\n manifest.peerDependencies,\n ].some((table) => table?.[name] !== undefined);\n return declared ? \"declaredButMissing\" : \"absent\";\n}\n\nexport function convexFixturesProblem(files: ReadonlyMap<string, string>): string | null {\n if (files.size === 0)\n return \"fixtures/convex/ holds no fixture, so nothing would prove the load.\";\n const missing = CONVEX_PROBES.filter((rule) => !files.has(rule));\n return missing.length === 0\n ? null\n : `fixtures/convex/ has no fixture for ${missing.join(\", \")}, the rule the check must see fire.`;\n}\n","#!/usr/bin/env node\n/**\n * Proof that every rule of this plugin still fires.\n *\n * A dead lint rule is silent: \"reports nothing\" reads exactly like \"no\n * violations\", so nothing else in a repo notices when a linter upgrade, a\n * JS-plugin API change or an ignore pattern takes a rule out. This lints one\n * deliberately-broken fixture per rule and asserts a positive: every rule fired,\n * each on the file named after it.\n *\n * It runs in a throwaway project under the system temp directory: its own\n * generated config with every rule at error, the shipped fixtures copied in, and\n * the caller's `node_modules` symlinked beside them. The caller's own config\n * takes no part, so the answer is about the plugin and the linter alone and a\n * consumer has nothing to write.\n *\n * Where `@convex-dev/eslint-plugin` is installed too, the same project lints\n * `fixtures/convex/` under the overrides `convex()` returns, and asserts that each\n * Convex rule named there fired. That plugin is loaded by the override itself, so\n * this is the check that the load still works.\n *\n * The linter is the oxlint the LOCAL `vite-plus` depends on, so it follows the\n * lockfile rather than the `vp` on PATH (measured 2026-09-19: a global `vp`\n * 0.3.1 over a local `vite-plus` 0.2.9 ran oxlint 1.77.0). The run that earns\n * this check its place is therefore the commit that moves `vite-plus`, and CI.\n *\n * npx rdjperron-lint-verify [--require-convex]\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport {\n cpSync,\n existsSync,\n mkdtempSync,\n readdirSync,\n rmSync,\n symlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { tmpdir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { convex, convexPlugin, plugin, RULE_IDS } from \"./config.ts\";\nimport { convexFixturesProblem, convexPluginState } from \"./verify-convex.ts\";\n\nconst FIXTURE_SUFFIXES = [\".fixture.ts\", \".fixture.tsx\"];\n\n/** Relative to this module, so it resolves from `dist/verify.mjs` and from `src/verify.ts` alike. */\nconst FIXTURES_DIR = fileURLToPath(new URL(\"../fixtures\", import.meta.url));\n\n/** The files `convex()` is pointed at in the throwaway project, one per Convex rule they trip. */\nconst CONVEX_FIXTURES = \"fixtures/convex\";\n\n/** Deterministic, locale-independent ordering for the rule and file name lists. */\nfunction byName(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n\nfunction basename(path: string): string {\n const parts = path.split(/[\\\\/]/u);\n return parts[parts.length - 1] ?? path;\n}\n\n/**\n * Locate the `vp` binary: PATH first, then the default install locations, the\n * XDG layout used since Vite+ 0.3.0 and the pre-0.3.0 single-root layout that\n * upgraded installs keep.\n */\nfunction resolveViteplusBinary(): string {\n const probe = spawnSync(\"vp\", [\"--version\"], { encoding: \"utf8\" });\n if (probe.status === 0) return \"vp\";\n const home = process.env.HOME ?? \"\";\n const fallbacks = [\n join(home, \".local\", \"share\", \"vite-plus\", \"bin\", \"vp\"),\n join(home, \".vite-plus\", \"bin\", \"vp\"),\n ];\n const found = fallbacks.find((candidate) => existsSync(candidate));\n if (found !== undefined) return found;\n throw new Error(\n \"Could not find the `vp` binary. Add it to PATH: \" +\n 'export PATH=\"$HOME/.local/share/vite-plus/bin:$PATH\" (or \"$HOME/.vite-plus/bin\" for a pre-0.3.0 install)',\n );\n}\n\n/** The `vite-plus` whose oxlint will run, and the `node_modules` the project borrows. */\nfunction resolveConsumerModules(root: string): string {\n const modules = join(root, \"node_modules\");\n if (!existsSync(modules)) {\n throw new Error(`No node_modules in ${root}. Install the dependencies and run this again.`);\n }\n try {\n createRequire(join(root, \"package.json\")).resolve(\"vite-plus/package.json\");\n } catch {\n throw new Error(\n `\\`vite-plus\\` is not installed in ${root}. The linter this check runs is the oxlint the ` +\n \"local `vite-plus` depends on, so without it there is nothing to prove the rules against.\",\n );\n }\n return modules;\n}\n\ninterface LintDiagnostic {\n code: string;\n filename: string;\n}\n\ninterface LintReport {\n numberOfFiles: number;\n diagnostics: LintDiagnostic[];\n}\n\n/** The two fields of oxlint's JSON report this reads, before any of them is trusted. */\ninterface RawReport {\n number_of_files?: unknown;\n diagnostics?: unknown;\n}\n\n/** The two fields of one raw diagnostic, before either of them is trusted. */\ninterface RawDiagnostic {\n code?: unknown;\n filename?: unknown;\n}\n\n/**\n * `vp lint -f json` is an outside value, so this is the one place its shape is\n * checked. Anything that does not look like a diagnostic is dropped rather than\n * guessed at.\n */\nfunction decodeReport(json: string): LintReport {\n const parsed: unknown = JSON.parse(json);\n if (typeof parsed !== \"object\" || parsed === null) {\n throw new Error(\"`vp lint -f json` did not return an object.\");\n }\n // SAFETY: the check above makes it a non-null object, and every field of\n // `RawReport` is `unknown`, so nothing is claimed about its contents yet.\n const report = parsed as RawReport;\n const numberOfFiles = typeof report.number_of_files === \"number\" ? report.number_of_files : 0;\n const rows = Array.isArray(report.diagnostics) ? report.diagnostics : [];\n const diagnostics: LintDiagnostic[] = [];\n for (const row of rows) {\n if (typeof row !== \"object\" || row === null) continue;\n // SAFETY: same check, one level down; both fields of `RawDiagnostic` are\n // `unknown` and each is typed by its own test on the next lines.\n const entry = row as RawDiagnostic;\n diagnostics.push({\n code: typeof entry.code === \"string\" ? entry.code : \"\",\n filename: typeof entry.filename === \"string\" ? entry.filename : \"\",\n });\n }\n return { numberOfFiles, diagnostics };\n}\n\n/** Run `vp lint <target> -f json` from `cwd` and return the decoded report. */\nfunction lintAsJson(binary: string, cwd: string, target: string): LintReport {\n const result = spawnSync(\n binary,\n [\"lint\", target, \"-f\", \"json\", \"--no-error-on-unmatched-pattern\"],\n { cwd, encoding: \"utf8\", maxBuffer: 64 * 1024 * 1024 },\n );\n\n if (result.error !== undefined && result.error !== null) throw result.error;\n\n // `--no-error-on-unmatched-pattern` keeps the \"No files found to lint.\" banner\n // off stdout, so stdout is pure JSON even when nothing matched.\n const stdout = result.stdout ?? \"\";\n const start = stdout.indexOf(\"{\");\n if (start === -1) {\n throw new Error(\n `\\`vp lint ${target} -f json\\` produced no JSON.\\nstdout: ${stdout}\\nstderr: ${result.stderr ?? \"\"}`,\n );\n }\n\n try {\n return decodeReport(stdout.slice(start));\n } catch (cause) {\n const reason = cause instanceof Error ? cause.message : String(cause);\n throw new Error(`Could not read \\`vp lint\\` JSON output: ${reason}\\nstdout: ${stdout}`, {\n cause,\n });\n }\n}\n\n/** Rule name to fixture file name, taken from the files that ship with the package. */\nfunction fixtureFiles(directory: string): Map<string, string> {\n if (!existsSync(directory)) throw new Error(`Fixture directory is missing: ${directory}`);\n const files = new Map<string, string>();\n for (const name of readdirSync(directory).toSorted(byName)) {\n const suffix = FIXTURE_SUFFIXES.find((candidate) => name.endsWith(candidate));\n if (suffix === undefined) continue;\n files.set(name.slice(0, -suffix.length), name);\n }\n return files;\n}\n\n/** The throwaway project: a config naming every rule at error, the fixtures, and the modules. */\nfunction createProject(modules: string, specifier: string, withConvex: boolean): string {\n const dir = mkdtempSync(join(tmpdir(), \"rdjperron-lint-verify-\"));\n const rules = Object.fromEntries(RULE_IDS.map((id) => [id, \"error\"]));\n const overrides = withConvex ? convex([`${CONVEX_FIXTURES}/**`]) : [];\n writeFileSync(\n join(dir, \"package.json\"),\n `${JSON.stringify({ name: \"rdjperron-lint-verify\", private: true, type: \"module\" }, null, 2)}\\n`,\n );\n writeFileSync(\n join(dir, \"vite.config.ts\"),\n [\n `import { defineConfig } from \"vite-plus\";`,\n ``,\n `export default defineConfig({`,\n ` lint: {`,\n ` plugins: [],`,\n ` jsPlugins: [${JSON.stringify({ name: plugin.name, specifier })}],`,\n ` rules: ${JSON.stringify(rules, null, 2).replaceAll(\"\\n\", \"\\n \")},`,\n ` overrides: ${JSON.stringify(overrides, null, 2).replaceAll(\"\\n\", \"\\n \")},`,\n ` },`,\n `});`,\n ``,\n ].join(\"\\n\"),\n );\n cpSync(FIXTURES_DIR, join(dir, \"fixtures\"), { recursive: true });\n // A junction is the one link Windows takes for node_modules, and copying it is out of the question.\n symlinkSync(modules, join(dir, \"node_modules\"), \"junction\");\n return dir;\n}\n\n/** `rdjperron(no-reflect-get)` -> `no-reflect-get`; a diagnostic from another plugin -> null. */\nfunction ruleIdFor(code: string, name: string): string | null {\n const prefix = `${name}(`;\n return code.startsWith(prefix) && code.endsWith(\")\") ? code.slice(prefix.length, -1) : null;\n}\n\n/** The rule names the loaded plugin registers, or null when it cannot be imported. */\nasync function registeredRules(specifier: string): Promise<string[] | null> {\n try {\n const loaded: unknown = await import(specifier);\n // SAFETY: the plugin's default export is the object oxlint loads; `rules` is\n // still `unknown` here and is checked on the next line.\n const module_ = loaded as { default?: { rules?: unknown } };\n const rules = module_.default?.rules;\n return typeof rules === \"object\" && rules !== null ? Object.keys(rules).toSorted(byName) : null;\n } catch {\n return null;\n }\n}\n\ninterface Row {\n rule: string;\n fixture: string;\n firedOnOwnFixture: boolean;\n note: string;\n}\n\n/** `--plugin <path>` points the run at a plugin on disk, which is how this repo checks its source. */\nfunction pluginSpecifier(argv: string[]): string {\n const index = argv.indexOf(\"--plugin\");\n if (index === -1) return plugin.specifier;\n const value = argv[index + 1];\n if (value === undefined) throw new Error(\"`--plugin` needs a path to the plugin module.\");\n return resolve(value);\n}\n\n/** Which rule fired on which fixture file, for every diagnostic whose code carries `name`. */\nfunction firedBy(diagnostics: LintDiagnostic[], name: string): Map<string, Set<string>> {\n const fired = new Map<string, Set<string>>();\n for (const diagnostic of diagnostics) {\n const ruleId = ruleIdFor(diagnostic.code, name);\n if (ruleId === null) continue;\n const seen = fired.get(ruleId) ?? new Set<string>();\n seen.add(basename(diagnostic.filename));\n fired.set(ruleId, seen);\n }\n return fired;\n}\n\n/** One row per fixture: did the rule it is named after fire on it. */\nfunction rowsFor(\n files: Map<string, string>,\n fired: Map<string, Set<string>>,\n prefix: string,\n): Row[] {\n return [...files].map(([rule, fixture]) => {\n const seen = fired.get(rule) ?? new Set<string>();\n const firedOnOwnFixture = seen.has(fixture);\n return {\n rule: `${prefix}${rule}`,\n fixture,\n firedOnOwnFixture,\n note: firedOnOwnFixture\n ? \"\"\n : seen.size > 0\n ? `fired only on: ${[...seen].toSorted(byName).join(\", \")}`\n : \"rule did not fire on any fixture\",\n };\n });\n}\n\nfunction printRows(rows: Row[]): void {\n const width = Math.max(...rows.map((row) => row.rule.length), 4);\n console.log(`${\"RULE\".padEnd(width)} RESULT FIXTURE`);\n console.log(`${\"-\".repeat(width)} ------ -------`);\n for (const row of rows) {\n const verdict = row.firedOnOwnFixture ? \"PASS \" : \"FAIL \";\n const suffix = row.note === \"\" ? \"\" : ` <-- ${row.note}`;\n console.log(`${row.rule.padEnd(width)} ${verdict} ${row.fixture}${suffix}`);\n }\n console.log(\"\");\n}\n\nasync function main(argv: string[]): Promise<number> {\n const specifier = pluginSpecifier(argv);\n const binary = resolveViteplusBinary();\n const root = process.cwd();\n const modules = resolveConsumerModules(root);\n const files = fixtureFiles(FIXTURES_DIR);\n const expectedRules = [...files.keys()];\n const convexState = convexPluginState(root);\n const withConvex = convexState === \"installed\";\n const convexFiles = withConvex ? fixtureFiles(join(FIXTURES_DIR, \"convex\")) : new Map();\n\n console.log(`${plugin.name} rule verification`);\n console.log(` plugin: ${specifier}`);\n console.log(` fixtures: ${FIXTURES_DIR}`);\n console.log(\n ` convex(): ${withConvex ? `${convexPlugin.specifier} is installed, so its load is checked too` : `skipped, ${convexPlugin.specifier} is not installed`}`,\n );\n\n if (convexState === \"declaredButMissing\") {\n console.error(\n `\\nFAIL: package.json declares ${convexPlugin.specifier} but it is not installed. Install it, or drop it.`,\n );\n return 1;\n }\n if (!withConvex && argv.includes(\"--require-convex\")) {\n console.error(\n `\\nFAIL: --require-convex, and ${convexPlugin.specifier} is not installed beside vite-plus.`,\n );\n return 1;\n }\n const convexProblem = withConvex ? convexFixturesProblem(convexFiles) : null;\n if (convexProblem !== null) {\n console.error(`\\nFAIL: ${convexProblem}`);\n return 1;\n }\n\n if (expectedRules.length === 0) {\n console.error(` No fixture files in ${FIXTURES_DIR}. Nothing to verify.\\n`);\n return 1;\n }\n\n const project = createProject(modules, specifier, withConvex);\n let report: LintReport;\n try {\n console.log(` project: ${project}`);\n report = lintAsJson(binary, project, \"fixtures\");\n } finally {\n rmSync(project, { recursive: true, force: true });\n }\n\n console.log(` files: ${report.numberOfFiles}\\n`);\n\n if (report.numberOfFiles === 0) {\n console.error(\n \"FAIL: `vp lint` matched none of the fixtures in the throwaway project.\\n\" +\n \"Nothing there is ignored and the fixtures are copied in, so this is `vp lint` refusing\\n\" +\n \"the generated config rather than a rule going quiet.\\n\",\n );\n return expectedRules.length;\n }\n\n const rows = rowsFor(files, firedBy(report.diagnostics, plugin.name), \"\");\n printRows(rows);\n\n const failures = rows.filter((row) => !row.firedOnOwnFixture);\n console.log(\n `${rows.length - failures.length}/${rows.length} ${plugin.name} rules fired on their own fixture.`,\n );\n\n const convexRows = rowsFor(\n convexFiles,\n firedBy(report.diagnostics, convexPlugin.name),\n `${convexPlugin.name}/`,\n );\n const convexFailures = convexRows.filter((row) => !row.firedOnOwnFixture);\n if (withConvex) {\n console.log(\"\");\n printRows(convexRows);\n console.log(\n `${convexRows.length - convexFailures.length}/${convexRows.length} ${convexPlugin.name} rules fired under convex().`,\n );\n }\n\n // Advisory: a release that adds a rule should also add a fixture.\n const registered = await registeredRules(specifier);\n if (registered !== null) {\n const missingFixture = registered.filter((rule) => !expectedRules.includes(rule));\n const orphanFixture = expectedRules.filter((rule) => !registered.includes(rule));\n if (missingFixture.length > 0) {\n console.log(\n `\\nWARN: registered by the ${plugin.name} plugin but has no fixture: ${missingFixture.join(\", \")}`,\n );\n }\n if (orphanFixture.length > 0) {\n console.log(\n `\\nWARN: fixture exists but the ${plugin.name} plugin registers no such rule: ${orphanFixture.join(\", \")}`,\n );\n }\n }\n\n // Advisory: fixtures are meant to trip exactly one rule each.\n const foreignDiagnostics = report.diagnostics.filter(\n (diagnostic) =>\n ruleIdFor(diagnostic.code, plugin.name) === null &&\n ruleIdFor(diagnostic.code, convexPlugin.name) === null,\n );\n if (foreignDiagnostics.length > 0) {\n console.log(\n `\\nWARN: ${foreignDiagnostics.length} non-${plugin.name} diagnostic(s) in the fixtures:`,\n );\n for (const diagnostic of foreignDiagnostics.slice(0, 20)) {\n console.log(` ${basename(diagnostic.filename)}: ${diagnostic.code}`);\n }\n }\n\n if (failures.length > 0 || convexFailures.length > 0) {\n if (failures.length > 0) {\n console.error(\n `\\nFAIL: ${failures.length} ${plugin.name} rule(s) did not fire. See ${join(FIXTURES_DIR, \"README.md\")}.\\n` +\n \"Every rule was at error in the generated config, so a rule that stayed quiet is one this\\n\" +\n \"linter did not run, not one this config left off.\",\n );\n }\n if (convexFailures.length > 0) {\n console.error(\n `\\nFAIL: ${convexFailures.length} ${convexPlugin.name} rule(s) did not fire under convex().\\n` +\n `convex() loads ${convexPlugin.specifier} through its own override's \\`jsPlugins\\`, and\\n` +\n \"these rules were at error there, so this linter did not load the plugin that way.\",\n );\n }\n return failures.length + convexFailures.length;\n }\n\n console.log(\n `\\nOK: every ${plugin.name} rule still fires${withConvex ? `, and so do ${convexPlugin.name}'s under convex()` : \"\"}.`,\n );\n return 0;\n}\n\nlet exitCode = 1;\ntry {\n exitCode = await main(process.argv.slice(2));\n} catch (error) {\n console.error(`\\nFAIL: ${error instanceof Error ? error.message : String(error)}`);\n}\nprocess.exit(exitCode === 0 ? 0 : 1);\n"],"mappings":"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"}
|
package/fixtures/README.md
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
One deliberately violating file per rule, `<rule>.fixture.ts` or `.fixture.tsx`, flat. Each file
|
|
4
4
|
trips exactly the rule it is named after, and nothing else.
|
|
5
5
|
|
|
6
|
+
`convex/` is the one subdirectory. Its files are named after a rule of `@convex-dev/eslint-plugin`
|
|
7
|
+
that `convex()` puts at error, and the check lints them under the overrides `convex()` returns.
|
|
8
|
+
That plugin is loaded by the override itself, so these files are what turns red when the load
|
|
9
|
+
stops working. A consumer without `@convex-dev/eslint-plugin` installed skips them.
|
|
10
|
+
|
|
6
11
|
These ship in the published package (`files` in `package.json`), so a consumer proves the rules
|
|
7
12
|
fire through its own linter, not ours.
|
|
8
13
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rdjperron/lint",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "One Oxlint JS plugin, `rdjperron`:
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "One Oxlint JS plugin, `rdjperron`: 22 rules against low-evidence code and unchecked Convex functions, with the config data, the fixture that proves each rule fires, the command that checks it, and the repository checks that hold a repo's Markdown links and research names.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -36,15 +36,20 @@
|
|
|
36
36
|
"@oxlint/plugins": "^1.79.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
+
"@convex-dev/eslint-plugin": "^5.0.0",
|
|
39
40
|
"@types/node": "^24",
|
|
40
41
|
"typescript": "^7.0.2",
|
|
41
42
|
"vite": "npm:@voidzero-dev/vite-plus-core@0.3.1",
|
|
42
43
|
"vite-plus": "0.3.1"
|
|
43
44
|
},
|
|
44
45
|
"peerDependencies": {
|
|
46
|
+
"@convex-dev/eslint-plugin": "^5.0.0",
|
|
45
47
|
"vite-plus": ">=0.3.0"
|
|
46
48
|
},
|
|
47
49
|
"peerDependenciesMeta": {
|
|
50
|
+
"@convex-dev/eslint-plugin": {
|
|
51
|
+
"optional": true
|
|
52
|
+
},
|
|
48
53
|
"vite-plus": {
|
|
49
54
|
"optional": true
|
|
50
55
|
}
|
|
@@ -57,7 +62,7 @@
|
|
|
57
62
|
"test": "vp test",
|
|
58
63
|
"check": "vp check",
|
|
59
64
|
"typecheck": "tsc --noEmit",
|
|
60
|
-
"verify:lint-fixtures": "node src/verify.ts --plugin ./src/index.ts",
|
|
65
|
+
"verify:lint-fixtures": "node src/verify.ts --plugin ./src/index.ts --require-convex",
|
|
61
66
|
"ready": "vp check && vp test && vp run typecheck && vp run build && vp run verify:lint-fixtures && vp pm pack -- --dry-run"
|
|
62
67
|
}
|
|
63
68
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config-CD2iCM6L.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/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/** 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":"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"}
|