@rdjperron/lint 0.3.0 → 0.6.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/dist/verify.mjs CHANGED
@@ -1,52 +1,37 @@
1
1
  #!/usr/bin/env node
2
+ import { s as plugin, t as RULE_IDS } from "./config-CD2iCM6L.mjs";
2
3
  import { createRequire } from "node:module";
4
+ import { cpSync, existsSync, mkdtempSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
5
+ import { join, resolve } from "node:path";
3
6
  import { spawnSync } from "node:child_process";
4
- import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, rmdirSync, symlinkSync } from "node:fs";
5
- import { dirname, join, resolve } from "node:path";
7
+ import { tmpdir } from "node:os";
6
8
  import { fileURLToPath } from "node:url";
7
9
  //#region src/verify.ts
8
10
  /**
9
- * Proof that every lint rule still fires.
11
+ * Proof that every rule of this plugin still fires.
10
12
  *
11
- * The linter lives inside the `vp` binary, so it moves independently of any
12
- * lockfile, and the JS-plugin bridge it loads these rules through is alpha. A
13
- * rule can stop firing with no diff anywhere, and a dead lint rule is silent:
14
- * "reports nothing" looks exactly like "no violations". This is the thing that
15
- * notices.
13
+ * A dead lint rule is silent: "reports nothing" reads exactly like "no
14
+ * violations", so nothing else in a repo notices when a linter upgrade, a
15
+ * JS-plugin API change or an ignore pattern takes a rule out. This lints one
16
+ * deliberately-broken fixture per rule and asserts a positive: every rule fired,
17
+ * each on the file named after it.
16
18
  *
17
- * A suite is a directory holding one deliberately-broken `<rule>.fixture.ts(x)`
18
- * per rule. The check asserts a positive: every rule fired, each on the file
19
- * named after it.
19
+ * It runs in a throwaway project under the system temp directory: its own
20
+ * generated config with every rule at error, the shipped fixtures copied in, and
21
+ * the caller's `node_modules` symlinked beside them. The caller's own config
22
+ * takes no part, so the answer is about the plugin and the linter alone and a
23
+ * consumer has nothing to write.
20
24
  *
21
- * Run the package's own suite from a consumer's repo root:
25
+ * The linter is the oxlint the LOCAL `vite-plus` depends on, so it follows the
26
+ * lockfile rather than the `vp` on PATH (measured 2026-09-19: a global `vp`
27
+ * 0.3.1 over a local `vite-plus` 0.2.9 ran oxlint 1.77.0). The run that earns
28
+ * this check its place is therefore the commit that moves `vite-plus`, and CI.
22
29
  *
23
30
  * npx rdjperron-lint-verify
24
- *
25
- * ---------------------------------------------------------------------------
26
- * Why the symlink dance
27
- * ---------------------------------------------------------------------------
28
- * The fixtures ship inside `node_modules`, which oxlint never walks, and a
29
- * consumer's own fixture directories are usually in `lint.ignorePatterns` so a
30
- * plain `vp check` stays quiet. Measured on Vite+ 0.2.9 / oxlint 1.77.0 and
31
- * again on 0.3.1 / oxlint 1.81.0: no flag re-includes an ignored path.
32
- * `--no-ignore`, `--ignore-pattern '!<dir>/**'`, an explicit file, an absolute
33
- * path, a cwd inside the directory and `-W <rule>` all answer "No files found
34
- * to lint." `--no-ignore` only disables `.eslintignore`, `--ignore-path` and
35
- * `--ignore-pattern`; it does not touch the config's own `ignorePatterns`, and
36
- * `vp lint` has no `--config` flag.
37
- *
38
- * What does work: oxlint matches ignore patterns against the path it walked,
39
- * not the realpath. Linting through a symlink outside the ignored prefix
40
- * reaches the fixtures normally, plugin and all. This creates that view per
41
- * suite, lints through it, and removes it in a `finally`.
42
- *
43
- * The severities cannot come from the command line either: `-A`, `-W` and `-D`
44
- * reach built-in rules only and leave every JS plugin rule exactly as the config
45
- * left it (measured on vite-plus 0.3.1 / oxlint 1.81.0, with `vp lint` and with
46
- * the bare binary). That is why the consumer's config must carry the
47
- * `fixtures()` override from `@rdjperron/lint/config`.
48
31
  */
49
- const DEFAULT_SUFFIXES = [".fixture.ts", ".fixture.tsx"];
32
+ const FIXTURE_SUFFIXES = [".fixture.ts", ".fixture.tsx"];
33
+ /** Relative to this module, so it resolves from `dist/verify.mjs` and from `src/verify.ts` alike. */
34
+ const FIXTURES_DIR = fileURLToPath(new URL("../fixtures", import.meta.url));
50
35
  /** Deterministic, locale-independent ordering for the rule and file name lists. */
51
36
  function byName(left, right) {
52
37
  return left < right ? -1 : left > right ? 1 : 0;
@@ -67,6 +52,17 @@ function resolveViteplusBinary() {
67
52
  if (found !== void 0) return found;
68
53
  throw new Error("Could not find the `vp` binary. Add it to PATH: export PATH=\"$HOME/.local/share/vite-plus/bin:$PATH\" (or \"$HOME/.vite-plus/bin\" for a pre-0.3.0 install)");
69
54
  }
55
+ /** The `vite-plus` whose oxlint will run, and the `node_modules` the project borrows. */
56
+ function resolveConsumerModules(root) {
57
+ const modules = join(root, "node_modules");
58
+ if (!existsSync(modules)) throw new Error(`No node_modules in ${root}. Install the dependencies and run this again.`);
59
+ try {
60
+ createRequire(join(root, "package.json")).resolve("vite-plus/package.json");
61
+ } catch {
62
+ throw new Error(`\`vite-plus\` is not installed in ${root}. The linter this check runs is the oxlint the local \`vite-plus\` depends on, so without it there is nothing to prove the rules against.`);
63
+ }
64
+ return modules;
65
+ }
70
66
  /**
71
67
  * `vp lint -f json` is an outside value, so this is the one place its shape is
72
68
  * checked. Anything that does not look like a diagnostic is dropped rather than
@@ -92,8 +88,8 @@ function decodeReport(json) {
92
88
  diagnostics
93
89
  };
94
90
  }
95
- /** Run `vp lint <target> -f json` from `root` and return the decoded report. */
96
- function lintAsJson(binary, root, target) {
91
+ /** Run `vp lint <target> -f json` from `cwd` and return the decoded report. */
92
+ function lintAsJson(binary, cwd, target) {
97
93
  const result = spawnSync(binary, [
98
94
  "lint",
99
95
  target,
@@ -101,7 +97,7 @@ function lintAsJson(binary, root, target) {
101
97
  "json",
102
98
  "--no-error-on-unmatched-pattern"
103
99
  ], {
104
- cwd: root,
100
+ cwd,
105
101
  encoding: "utf8",
106
102
  maxBuffer: 67108864
107
103
  });
@@ -116,106 +112,100 @@ function lintAsJson(binary, root, target) {
116
112
  throw new Error(`Could not read \`vp lint\` JSON output: ${reason}\nstdout: ${stdout}`, { cause });
117
113
  }
118
114
  }
119
- /**
120
- * Why a path is gitignored, or null. oxlint honours `.gitignore`, so a
121
- * gitignored view yields "0 files" instead of a useful diagnostic.
122
- */
123
- function gitIgnoreReason(root, relPath) {
124
- const result = spawnSync("git", [
125
- "check-ignore",
126
- "-v",
127
- relPath
128
- ], {
129
- cwd: root,
130
- encoding: "utf8"
131
- });
132
- const output = result.status === 0 ? result.stdout.trim() : "";
133
- return output === "" ? null : output.split(" ")[0] ?? null;
134
- }
135
- /** Rule name to fixture file name, taken from the files that exist on disk. */
136
- function fixtureFiles(root, suite) {
137
- const dir = join(root, suite.fixtureDir);
138
- if (!existsSync(dir)) throw new Error(`Fixture directory is missing: ${dir}`);
139
- const suffixes = suite.suffixes ?? DEFAULT_SUFFIXES;
115
+ /** Rule name to fixture file name, taken from the files that ship with the package. */
116
+ function fixtureFiles() {
117
+ if (!existsSync(FIXTURES_DIR)) throw new Error(`Fixture directory is missing: ${FIXTURES_DIR}`);
140
118
  const files = /* @__PURE__ */ new Map();
141
- for (const name of readdirSync(dir).toSorted(byName)) {
142
- const suffix = suffixes.find((candidate) => name.endsWith(candidate));
119
+ for (const name of readdirSync(FIXTURES_DIR).toSorted(byName)) {
120
+ const suffix = FIXTURE_SUFFIXES.find((candidate) => name.endsWith(candidate));
143
121
  if (suffix === void 0) continue;
144
122
  files.set(name.slice(0, -suffix.length), name);
145
123
  }
146
124
  return files;
147
125
  }
148
- /** Create the un-ignored view; prefer a symlink, fall back to a copy on Windows. */
149
- function createFixtureView(root, suite) {
150
- removeFixtureView(root, suite);
151
- const viewPath = join(root, suite.viewDir);
152
- mkdirSync(dirname(viewPath), { recursive: true });
126
+ /** The throwaway project: a config naming every rule at error, the fixtures, and the modules. */
127
+ function createProject(modules, specifier) {
128
+ const dir = mkdtempSync(join(tmpdir(), "rdjperron-lint-verify-"));
129
+ const rules = Object.fromEntries(RULE_IDS.map((id) => [id, "error"]));
130
+ writeFileSync(join(dir, "package.json"), `${JSON.stringify({
131
+ name: "rdjperron-lint-verify",
132
+ private: true,
133
+ type: "module"
134
+ }, null, 2)}\n`);
135
+ writeFileSync(join(dir, "vite.config.ts"), [
136
+ `import { defineConfig } from "vite-plus";`,
137
+ ``,
138
+ `export default defineConfig({`,
139
+ ` lint: {`,
140
+ ` plugins: [],`,
141
+ ` jsPlugins: [${JSON.stringify({
142
+ name: plugin.name,
143
+ specifier
144
+ })}],`,
145
+ ` rules: ${JSON.stringify(rules, null, 2).replaceAll("\n", "\n ")},`,
146
+ ` },`,
147
+ `});`,
148
+ ``
149
+ ].join("\n"));
150
+ cpSync(FIXTURES_DIR, join(dir, "fixtures"), { recursive: true });
151
+ symlinkSync(modules, join(dir, "node_modules"), "junction");
152
+ return dir;
153
+ }
154
+ /** `rdjperron(no-reflect-get)` -> `no-reflect-get`; a diagnostic from anything else -> null. */
155
+ function ruleIdFor(code) {
156
+ const match = new RegExp(`^${plugin.name}\\(([^)]+)\\)$`, "u").exec(code);
157
+ return match === null ? null : match[1] ?? null;
158
+ }
159
+ /** The rule names the loaded plugin registers, or null when it cannot be imported. */
160
+ async function registeredRules(specifier) {
153
161
  try {
154
- symlinkSync(join(root, suite.fixtureDir), viewPath, "junction");
155
- return "symlink";
162
+ const rules = (await import(specifier)).default?.rules;
163
+ return typeof rules === "object" && rules !== null ? Object.keys(rules).toSorted(byName) : null;
156
164
  } catch {
157
- cpSync(join(root, suite.fixtureDir), viewPath, { recursive: true });
158
- return "copy";
165
+ return null;
159
166
  }
160
167
  }
161
- function removeFixtureView(root, suite) {
162
- const viewPath = join(root, suite.viewDir);
163
- rmSync(viewPath, {
164
- recursive: true,
165
- force: true
166
- });
167
- const top = resolve(root);
168
- for (let dir = dirname(viewPath); dir !== top; dir = dirname(dir)) {
169
- if (!existsSync(dir) || readdirSync(dir).length > 0) break;
170
- rmdirSync(dir);
171
- }
172
- }
173
- /** `rdjperron(no-reflect-get)` -> `no-reflect-get` for this suite's plugin; anything else -> null. */
174
- function ruleIdFor(plugin, code) {
175
- const match = new RegExp(`^${plugin}\\(([^)]+)\\)$`, "u").exec(code);
176
- return match === null ? null : match[1] ?? null;
168
+ /** `--plugin <path>` points the run at a plugin on disk, which is how this repo checks its source. */
169
+ function pluginSpecifier(argv) {
170
+ const index = argv.indexOf("--plugin");
171
+ if (index === -1) return plugin.specifier;
172
+ const value = argv[index + 1];
173
+ if (value === void 0) throw new Error("`--plugin` needs a path to the plugin module.");
174
+ return resolve(value);
177
175
  }
178
- /** Lint one suite and print its table. Returns the number of rules that did not fire. */
179
- async function verifySuite(binary, root, suite) {
180
- const files = fixtureFiles(root, suite);
176
+ async function main(argv) {
177
+ const specifier = pluginSpecifier(argv);
178
+ const binary = resolveViteplusBinary();
179
+ const modules = resolveConsumerModules(process.cwd());
180
+ const files = fixtureFiles();
181
181
  const expectedRules = [...files.keys()];
182
- console.log(`${suite.plugin} rule verification`);
183
- console.log(` fixtures: ${suite.fixtureDir}`);
182
+ console.log(`${plugin.name} rule verification`);
183
+ console.log(` plugin: ${specifier}`);
184
+ console.log(` fixtures: ${FIXTURES_DIR}`);
184
185
  if (expectedRules.length === 0) {
185
- console.error(` No fixture files found in ${suite.fixtureDir}. Nothing to verify.\n`);
186
+ console.error(` No fixture files in ${FIXTURES_DIR}. Nothing to verify.\n`);
186
187
  return 1;
187
188
  }
188
- let mechanism = "direct path";
189
- let report = suite.alwaysView === true ? {
190
- numberOfFiles: 0,
191
- diagnostics: []
192
- } : lintAsJson(binary, root, suite.fixtureDir);
193
- if (report.numberOfFiles === 0) {
194
- const kind = createFixtureView(root, suite);
195
- try {
196
- mechanism = `${kind} at ${suite.viewDir} (\`${suite.fixtureDir}\` is not walked by \`vp lint\`)`;
197
- report = lintAsJson(binary, root, suite.viewDir);
198
- } finally {
199
- removeFixtureView(root, suite);
200
- }
189
+ const project = createProject(modules, specifier);
190
+ let report;
191
+ try {
192
+ console.log(` project: ${project}`);
193
+ report = lintAsJson(binary, project, "fixtures");
194
+ } finally {
195
+ rmSync(project, {
196
+ recursive: true,
197
+ force: true
198
+ });
201
199
  }
202
- console.log(` lint via: ${mechanism}`);
203
- console.log(` files: ${report.numberOfFiles}`);
204
- console.log("");
200
+ console.log(` files: ${report.numberOfFiles}\n`);
205
201
  if (report.numberOfFiles === 0) {
206
- const ignoredBy = gitIgnoreReason(root, suite.viewDir);
207
- console.error("FAIL: `vp lint` matched 0 files through both the direct path and the un-ignored view.\n" + (ignoredBy === null ? `Check that the config ignores the fixture directory itself and not a pattern such as
208
- "**/*.fixture.ts", which would also hide the view at "${suite.viewDir}".\nCheck too that the config carries the \`fixtures()\` override from @rdjperron/lint/config,
209
- on the view path: no command-line flag can switch a JS plugin rule on.
210
- ` : `CAUSE: "${suite.viewDir}" is gitignored (${ignoredBy}). oxlint skips gitignored paths,\nso the view this lints through is invisible to it. Remove that ignore rule; the view is
211
- deleted in a \`finally\` block anyway.
212
- `));
202
+ 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");
213
203
  return expectedRules.length;
214
204
  }
215
205
  const firedByRule = /* @__PURE__ */ new Map();
216
206
  const foreignDiagnostics = [];
217
207
  for (const diagnostic of report.diagnostics) {
218
- const ruleId = ruleIdFor(suite.plugin, diagnostic.code);
208
+ const ruleId = ruleIdFor(diagnostic.code);
219
209
  if (ruleId === null) {
220
210
  foreignDiagnostics.push(diagnostic);
221
211
  continue;
@@ -245,156 +235,34 @@ deleted in a \`finally\` block anyway.
245
235
  }
246
236
  console.log("");
247
237
  const failures = rows.filter((row) => !row.firedOnOwnFixture);
248
- console.log(`${rows.length - failures.length}/${rows.length} ${suite.plugin} rules fired on their own fixture.`);
249
- const registered = suite.registeredRules === void 0 ? null : await suite.registeredRules();
238
+ console.log(`${rows.length - failures.length}/${rows.length} ${plugin.name} rules fired on their own fixture.`);
239
+ const registered = await registeredRules(specifier);
250
240
  if (registered !== null) {
251
241
  const missingFixture = registered.filter((rule) => !expectedRules.includes(rule));
252
242
  const orphanFixture = expectedRules.filter((rule) => !registered.includes(rule));
253
- if (missingFixture.length > 0) console.log(`\nWARN: registered by the ${suite.plugin} plugin but has no fixture: ${missingFixture.join(", ")}`);
254
- if (orphanFixture.length > 0) console.log(`\nWARN: fixture exists but the ${suite.plugin} plugin registers no such rule: ${orphanFixture.join(", ")}`);
243
+ if (missingFixture.length > 0) console.log(`\nWARN: registered by the ${plugin.name} plugin but has no fixture: ${missingFixture.join(", ")}`);
244
+ if (orphanFixture.length > 0) console.log(`\nWARN: fixture exists but the ${plugin.name} plugin registers no such rule: ${orphanFixture.join(", ")}`);
255
245
  }
256
246
  if (foreignDiagnostics.length > 0) {
257
- console.log(`\nWARN: ${foreignDiagnostics.length} non-${suite.plugin} diagnostic(s) in the fixtures:`);
247
+ console.log(`\nWARN: ${foreignDiagnostics.length} non-${plugin.name} diagnostic(s) in the fixtures:`);
258
248
  for (const diagnostic of foreignDiagnostics.slice(0, 20)) console.log(` ${basename(diagnostic.filename)}: ${diagnostic.code}`);
259
249
  }
260
250
  if (failures.length > 0) {
261
- const where = suite.readme === void 0 ? "" : ` See ${suite.readme}.`;
262
- console.error(`\nFAIL: ${failures.length} ${suite.plugin} rule(s) did not fire.${where}\nFirst thing to check: the config's override on "${suite.viewDir}" has to put every rule\nat error. For this plugin that override is \`fixtures()\` from @rdjperron/lint/config; no
263
- command-line flag can switch a JS plugin rule on.`);
264
- }
265
- console.log("");
266
- return failures.length;
267
- }
268
- /**
269
- * Lint every suite and print one table each. Returns the number of rules that
270
- * did not fire, so zero means every rule of every suite is alive.
271
- */
272
- async function verifySuites(suites, options = {}) {
273
- const root = resolve(options.root ?? process.cwd());
274
- const binary = resolveViteplusBinary();
275
- let failures = 0;
276
- try {
277
- for (const suite of suites) failures += await verifySuite(binary, root, suite);
278
- } finally {
279
- for (const suite of suites) removeFixtureView(root, suite);
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
252
+ linter did not run, not one this config left off.`);
253
+ return failures.length;
280
254
  }
281
- if (failures > 0) {
282
- console.error(`FAIL: ${failures} rule(s) did not fire across ${suites.length} suite(s).`);
283
- return failures;
284
- }
285
- console.log(`OK: every rule of every suite (${suites.map((suite) => suite.plugin).join(", ")}) still fires.`);
255
+ console.log(`\nOK: every ${plugin.name} rule still fires.`);
286
256
  return 0;
287
257
  }
288
- const PLUGINS_PACKAGE = "@oxlint/plugins";
289
- function readManifest(path) {
290
- const parsed = JSON.parse(readFileSync(path, "utf8"));
291
- if (typeof parsed !== "object" || parsed === null) throw new Error(`Not a manifest: ${path}`);
292
- const entry = parsed;
293
- return {
294
- version: typeof entry.version === "string" ? entry.version : "",
295
- declaredPlugins: entry.dependencies?.[PLUGINS_PACKAGE] ?? "",
296
- peerPlugins: entry.peerDependencies?.[PLUGINS_PACKAGE] ?? ""
297
- };
298
- }
299
- /** The nearest `package.json` at or above a resolved module file. */
300
- function manifestNear(modulePath) {
301
- let dir = dirname(modulePath);
302
- for (;;) {
303
- const candidate = join(dir, "package.json");
304
- if (existsSync(candidate)) return candidate;
305
- const parent = dirname(dir);
306
- if (parent === dir) return null;
307
- dir = parent;
308
- }
309
- }
310
- /** `^x.y.z` is satisfied by the same major, at or above the base. */
311
- function caretAccepts(range, version) {
312
- const match = /^\^(\d+)\.(\d+)\.(\d+)$/u.exec(range);
313
- if (match === null) throw new Error(`Expected a caret range, got "${range}"`);
314
- const [major, minor, patch] = match.slice(1).map(Number);
315
- const [candidateMajor, candidateMinor, candidatePatch] = version.split(".").map(Number);
316
- if (candidateMajor !== major) return false;
317
- if (candidateMinor !== minor) return (candidateMinor ?? 0) > (minor ?? 0);
318
- return (candidatePatch ?? 0) >= (patch ?? 0);
319
- }
320
- /**
321
- * Two promises about the plugin API, held here as a failure instead.
322
- *
323
- * - the installed `@oxlint/plugins` is the exact version `vite-plus` declares;
324
- * - this package's peer range accepts that version.
325
- *
326
- * The rules import `@oxlint/plugins`, so the installed one must be the one the
327
- * linter was built against, or the rules compile against one plugin API and run
328
- * under another with no error anywhere. Returns the failures, empty when the
329
- * pins agree or when `vite-plus` is not installed (a consumer on another
330
- * toolchain has nothing to pin against).
331
- */
332
- function verifyPluginApiPin(root = process.cwd()) {
333
- const requireFromRoot = createRequire(join(resolve(root), "package.json"));
334
- const requireFromHere = createRequire(import.meta.url);
335
- let vitePlus;
336
- try {
337
- vitePlus = readManifest(requireFromRoot.resolve("vite-plus/package.json"));
338
- } catch {
339
- console.log("plugin API pin: skipped, `vite-plus` is not installed here.");
340
- return [];
341
- }
342
- const declared = vitePlus.declaredPlugins.replace(/^=/u, "");
343
- if (!/^\d+\.\d+\.\d+$/u.test(declared)) return [`vite-plus ${vitePlus.version} no longer declares an exact @oxlint/plugins version.`];
344
- let installed;
345
- try {
346
- const manifest = manifestNear(requireFromRoot.resolve("@oxlint/plugins"));
347
- if (manifest === null) throw new Error("no package.json above @oxlint/plugins");
348
- installed = readManifest(manifest);
349
- } catch {
350
- return ["`@oxlint/plugins` is not installed. It is a peer dependency of @rdjperron/lint."];
351
- }
352
- const self = readManifest(requireFromHere.resolve("../package.json"));
353
- const peer = self.peerPlugins;
354
- const failures = [];
355
- if (installed.version !== declared) failures.push(`@oxlint/plugins ${installed.version} is installed and vite-plus ${vitePlus.version} declares ${declared}. Move the pin: install @oxlint/plugins@${declared} exactly.`);
356
- if (!caretAccepts(peer, declared)) failures.push(`@rdjperron/lint ${self.version} wants @oxlint/plugins ${peer} and vite-plus ${vitePlus.version} declares ${declared}. Move one of the two pins.`);
357
- if (failures.length === 0) console.log(`plugin API pin: @oxlint/plugins ${installed.version}, what vite-plus ${vitePlus.version} declares.`);
358
- return failures;
359
- }
360
- /** The suite for this package's own fixtures, as they sit in a consumer's `node_modules`. */
361
- function packageSuite(root = process.cwd()) {
362
- const packageDir = "node_modules/@rdjperron/lint";
363
- return {
364
- plugin: "rdjperron",
365
- fixtureDir: `${packageDir}/fixtures`,
366
- viewDir: "scripts/.lint-fixtures/rdjperron",
367
- alwaysView: true,
368
- readme: `${packageDir}/fixtures/README.md`,
369
- registeredRules: async () => {
370
- try {
371
- const rules = (await import(createRequire(join(resolve(root), "package.json")).resolve("@rdjperron/lint"))).default?.rules;
372
- return typeof rules === "object" && rules !== null ? Object.keys(rules).toSorted(byName) : null;
373
- } catch {
374
- return null;
375
- }
376
- }
377
- };
378
- }
379
- /** The bin: this package's suite, plus the plugin API pin, from the consumer's repo root. */
380
- async function main(root = process.cwd()) {
381
- const pinFailures = verifyPluginApiPin(root);
382
- for (const failure of pinFailures) console.error(`FAIL: ${failure}`);
383
- console.log("");
384
- const ruleFailures = await verifySuites([packageSuite(root)], { root });
385
- return pinFailures.length + ruleFailures > 0 ? 1 : 0;
386
- }
387
- const invoked = process.argv[1];
388
- if (invoked !== void 0 && existsSync(invoked) && realpathSync(invoked) === fileURLToPath(import.meta.url)) {
389
- let exitCode = 1;
390
- try {
391
- exitCode = await main();
392
- } catch (error) {
393
- console.error(`\nFAIL: ${error instanceof Error ? error.message : String(error)}`);
394
- }
395
- process.exit(exitCode);
258
+ let exitCode = 1;
259
+ try {
260
+ exitCode = await main(process.argv.slice(2));
261
+ } catch (error) {
262
+ console.error(`\nFAIL: ${error instanceof Error ? error.message : String(error)}`);
396
263
  }
264
+ process.exit(exitCode === 0 ? 0 : 1);
397
265
  //#endregion
398
- export { main, packageSuite, resolveViteplusBinary, verifyPluginApiPin, verifySuites };
266
+ export {};
399
267
 
400
268
  //# sourceMappingURL=verify.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"verify.mjs","names":[],"sources":["../src/verify.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Proof that every lint rule still fires.\n *\n * The linter lives inside the `vp` binary, so it moves independently of any\n * lockfile, and the JS-plugin bridge it loads these rules through is alpha. A\n * rule can stop firing with no diff anywhere, and a dead lint rule is silent:\n * \"reports nothing\" looks exactly like \"no violations\". This is the thing that\n * notices.\n *\n * A suite is a directory holding one deliberately-broken `<rule>.fixture.ts(x)`\n * per rule. The check asserts a positive: every rule fired, each on the file\n * named after it.\n *\n * Run the package's own suite from a consumer's repo root:\n *\n * npx rdjperron-lint-verify\n *\n * ---------------------------------------------------------------------------\n * Why the symlink dance\n * ---------------------------------------------------------------------------\n * The fixtures ship inside `node_modules`, which oxlint never walks, and a\n * consumer's own fixture directories are usually in `lint.ignorePatterns` so a\n * plain `vp check` stays quiet. Measured on Vite+ 0.2.9 / oxlint 1.77.0 and\n * again on 0.3.1 / oxlint 1.81.0: no flag re-includes an ignored path.\n * `--no-ignore`, `--ignore-pattern '!<dir>/**'`, an explicit file, an absolute\n * path, a cwd inside the directory and `-W <rule>` all answer \"No files found\n * to lint.\" `--no-ignore` only disables `.eslintignore`, `--ignore-path` and\n * `--ignore-pattern`; it does not touch the config's own `ignorePatterns`, and\n * `vp lint` has no `--config` flag.\n *\n * What does work: oxlint matches ignore patterns against the path it walked,\n * not the realpath. Linting through a symlink outside the ignored prefix\n * reaches the fixtures normally, plugin and all. This creates that view per\n * suite, lints through it, and removes it in a `finally`.\n *\n * The severities cannot come from the command line either: `-A`, `-W` and `-D`\n * reach built-in rules only and leave every JS plugin rule exactly as the config\n * left it (measured on vite-plus 0.3.1 / oxlint 1.81.0, with `vp lint` and with\n * the bare binary). That is why the consumer's config must carry the\n * `fixtures()` override from `@rdjperron/lint/config`.\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport {\n cpSync,\n existsSync,\n mkdirSync,\n readdirSync,\n rmdirSync,\n readFileSync,\n realpathSync,\n rmSync,\n symlinkSync,\n} from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport interface Suite {\n /** The plugin alias the diagnostics carry, e.g. `rdjperron` in `rdjperron(no-reflect-get)`. */\n plugin: string;\n /** The fixture directory, relative to `root`. */\n fixtureDir: string;\n /** Where the un-ignored view is created, relative to `root`. */\n viewDir: string;\n /** Fixture file endings; the part before one of them is the rule name. */\n suffixes?: string[];\n /** Skip the direct path and always lint through the view (fixtures under `node_modules`). */\n alwaysView?: boolean;\n /** Advisory: the rule names the plugin registers, so a new rule without a fixture is named. */\n registeredRules?: () => Promise<string[] | null>;\n /** Where the failure message sends the reader. */\n readme?: string;\n}\n\nexport interface VerifyOptions {\n /** The repo root the paths are relative to and `vp lint` runs from. Defaults to the cwd. */\n root?: string;\n}\n\nconst DEFAULT_SUFFIXES = [\".fixture.ts\", \".fixture.tsx\"];\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 */\nexport function 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\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 `root` and return the decoded report. */\nfunction lintAsJson(binary: string, root: string, target: string): LintReport {\n const result = spawnSync(\n binary,\n [\"lint\", target, \"-f\", \"json\", \"--no-error-on-unmatched-pattern\"],\n {\n cwd: root,\n encoding: \"utf8\",\n maxBuffer: 64 * 1024 * 1024,\n },\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/**\n * Why a path is gitignored, or null. oxlint honours `.gitignore`, so a\n * gitignored view yields \"0 files\" instead of a useful diagnostic.\n */\nfunction gitIgnoreReason(root: string, relPath: string): string | null {\n const result = spawnSync(\"git\", [\"check-ignore\", \"-v\", relPath], { cwd: root, encoding: \"utf8\" });\n const output = result.status === 0 ? result.stdout.trim() : \"\";\n return output === \"\" ? null : (output.split(\"\\t\")[0] ?? null);\n}\n\n/** Rule name to fixture file name, taken from the files that exist on disk. */\nfunction fixtureFiles(root: string, suite: Suite): Map<string, string> {\n const dir = join(root, suite.fixtureDir);\n if (!existsSync(dir)) throw new Error(`Fixture directory is missing: ${dir}`);\n const suffixes = suite.suffixes ?? DEFAULT_SUFFIXES;\n const files = new Map<string, string>();\n for (const name of readdirSync(dir).toSorted(byName)) {\n const suffix = 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/** Create the un-ignored view; prefer a symlink, fall back to a copy on Windows. */\nfunction createFixtureView(root: string, suite: Suite): string {\n removeFixtureView(root, suite);\n const viewPath = join(root, suite.viewDir);\n mkdirSync(dirname(viewPath), { recursive: true });\n try {\n symlinkSync(join(root, suite.fixtureDir), viewPath, \"junction\");\n return \"symlink\";\n } catch {\n cpSync(join(root, suite.fixtureDir), viewPath, { recursive: true });\n return \"copy\";\n }\n}\n\nfunction removeFixtureView(root: string, suite: Suite): void {\n const viewPath = join(root, suite.viewDir);\n rmSync(viewPath, { recursive: true, force: true });\n // The parents were created for the view alone when nothing else lives there, so they go too.\n const top = resolve(root);\n for (let dir = dirname(viewPath); dir !== top; dir = dirname(dir)) {\n if (!existsSync(dir) || readdirSync(dir).length > 0) break;\n rmdirSync(dir);\n }\n}\n\n/** `rdjperron(no-reflect-get)` -> `no-reflect-get` for this suite's plugin; anything else -> null. */\nfunction ruleIdFor(plugin: string, code: string): string | null {\n const match = new RegExp(`^${plugin}\\\\(([^)]+)\\\\)$`, \"u\").exec(code);\n return match === null ? null : (match[1] ?? null);\n}\n\ninterface Row {\n rule: string;\n fixture: string;\n firedOnOwnFixture: boolean;\n note: string;\n}\n\n/** Lint one suite and print its table. Returns the number of rules that did not fire. */\nasync function verifySuite(binary: string, root: string, suite: Suite): Promise<number> {\n const files = fixtureFiles(root, suite);\n const expectedRules = [...files.keys()];\n\n console.log(`${suite.plugin} rule verification`);\n console.log(` fixtures: ${suite.fixtureDir}`);\n\n if (expectedRules.length === 0) {\n console.error(` No fixture files found in ${suite.fixtureDir}. Nothing to verify.\\n`);\n return 1;\n }\n\n // 1. Try the fixture directory directly. Works only while it is un-ignored, so\n // a suite under node_modules skips it: oxlint never walks in there.\n let mechanism = \"direct path\";\n let report: LintReport =\n suite.alwaysView === true\n ? { numberOfFiles: 0, diagnostics: [] }\n : lintAsJson(binary, root, suite.fixtureDir);\n\n // 2. Ignored by the config or under node_modules: lint through an un-ignored view.\n if (report.numberOfFiles === 0) {\n const kind = createFixtureView(root, suite);\n try {\n mechanism = `${kind} at ${suite.viewDir} (\\`${suite.fixtureDir}\\` is not walked by \\`vp lint\\`)`;\n report = lintAsJson(binary, root, suite.viewDir);\n } finally {\n // Never leave the view behind: a stale one would show up in `vp check`.\n removeFixtureView(root, suite);\n }\n }\n\n console.log(` lint via: ${mechanism}`);\n console.log(` files: ${report.numberOfFiles}`);\n console.log(\"\");\n\n if (report.numberOfFiles === 0) {\n const ignoredBy = gitIgnoreReason(root, suite.viewDir);\n console.error(\n \"FAIL: `vp lint` matched 0 files through both the direct path and the un-ignored view.\\n\" +\n (ignoredBy === null\n ? \"Check that the config ignores the fixture directory itself and not a pattern such as\\n\" +\n `\"**/*.fixture.ts\", which would also hide the view at \"${suite.viewDir}\".\\n` +\n \"Check too that the config carries the `fixtures()` override from @rdjperron/lint/config,\\n\" +\n \"on the view path: no command-line flag can switch a JS plugin rule on.\\n\"\n : `CAUSE: \"${suite.viewDir}\" is gitignored (${ignoredBy}). oxlint skips gitignored paths,\\n` +\n \"so the view this lints through is invisible to it. Remove that ignore rule; the view is\\n\" +\n \"deleted in a `finally` block anyway.\\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(suite.plugin, 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} ${suite.plugin} rules fired on their own fixture.`,\n );\n\n // Advisory: a plugin bump that adds a rule should also add a fixture.\n const registered = suite.registeredRules === undefined ? null : await suite.registeredRules();\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 ${suite.plugin} plugin but has no fixture: ${missingFixture.join(\", \")}`,\n );\n }\n if (orphanFixture.length > 0) {\n console.log(\n `\\nWARN: fixture exists but the ${suite.plugin} 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-${suite.plugin} 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 const where = suite.readme === undefined ? \"\" : ` See ${suite.readme}.`;\n console.error(\n `\\nFAIL: ${failures.length} ${suite.plugin} rule(s) did not fire.${where}\\n` +\n `First thing to check: the config's override on \"${suite.viewDir}\" has to put every rule\\n` +\n \"at error. For this plugin that override is `fixtures()` from @rdjperron/lint/config; no\\n\" +\n \"command-line flag can switch a JS plugin rule on.\",\n );\n }\n console.log(\"\");\n return failures.length;\n}\n\n/**\n * Lint every suite and print one table each. Returns the number of rules that\n * did not fire, so zero means every rule of every suite is alive.\n */\nexport async function verifySuites(suites: Suite[], options: VerifyOptions = {}): Promise<number> {\n const root = resolve(options.root ?? process.cwd());\n const binary = resolveViteplusBinary();\n let failures = 0;\n try {\n for (const suite of suites) {\n failures += await verifySuite(binary, root, suite);\n }\n } finally {\n for (const suite of suites) removeFixtureView(root, suite);\n }\n if (failures > 0) {\n console.error(`FAIL: ${failures} rule(s) did not fire across ${suites.length} suite(s).`);\n return failures;\n }\n console.log(\n `OK: every rule of every suite (${suites.map((suite) => suite.plugin).join(\", \")}) still fires.`,\n );\n return 0;\n}\n\nconst PLUGINS_PACKAGE = \"@oxlint/plugins\";\n\n/** What this needs from a `package.json`: its version, and what it says about the plugin API. */\ninterface Manifest {\n version: string;\n /** The `@oxlint/plugins` spec in `dependencies`, or \"\". */\n declaredPlugins: string;\n /** The `@oxlint/plugins` spec in `peerDependencies`, or \"\". */\n peerPlugins: string;\n}\n\n/** npm's manifest schema: both tables map a package name to a version spec. */\ninterface DependencyTable {\n [name: string]: string | undefined;\n}\n\ninterface RawManifest {\n version?: unknown;\n dependencies?: DependencyTable;\n peerDependencies?: DependencyTable;\n}\n\nfunction readManifest(path: string): Manifest {\n const parsed: unknown = JSON.parse(readFileSync(path, \"utf8\"));\n if (typeof parsed !== \"object\" || parsed === null) throw new Error(`Not a manifest: ${path}`);\n // SAFETY: the check above makes it a non-null object, and npm's own schema makes\n // both dependency tables name-to-spec; a package.json that breaks that is not\n // installable, and a non-string spec reaching here simply fails the comparisons.\n const entry = parsed as RawManifest;\n return {\n version: typeof entry.version === \"string\" ? entry.version : \"\",\n declaredPlugins: entry.dependencies?.[PLUGINS_PACKAGE] ?? \"\",\n peerPlugins: entry.peerDependencies?.[PLUGINS_PACKAGE] ?? \"\",\n };\n}\n\n/** The nearest `package.json` at or above a resolved module file. */\nfunction manifestNear(modulePath: string): string | null {\n let dir = dirname(modulePath);\n for (;;) {\n const candidate = join(dir, \"package.json\");\n if (existsSync(candidate)) return candidate;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/** `^x.y.z` is satisfied by the same major, at or above the base. */\nfunction caretAccepts(range: string, version: string): boolean {\n const match = /^\\^(\\d+)\\.(\\d+)\\.(\\d+)$/u.exec(range);\n if (match === null) throw new Error(`Expected a caret range, got \"${range}\"`);\n const [major, minor, patch] = match.slice(1).map(Number);\n const [candidateMajor, candidateMinor, candidatePatch] = version.split(\".\").map(Number);\n if (candidateMajor !== major) return false;\n if (candidateMinor !== minor) return (candidateMinor ?? 0) > (minor ?? 0);\n return (candidatePatch ?? 0) >= (patch ?? 0);\n}\n\n/**\n * Two promises about the plugin API, held here as a failure instead.\n *\n * - the installed `@oxlint/plugins` is the exact version `vite-plus` declares;\n * - this package's peer range accepts that version.\n *\n * The rules import `@oxlint/plugins`, so the installed one must be the one the\n * linter was built against, or the rules compile against one plugin API and run\n * under another with no error anywhere. Returns the failures, empty when the\n * pins agree or when `vite-plus` is not installed (a consumer on another\n * toolchain has nothing to pin against).\n */\nexport function verifyPluginApiPin(root = process.cwd()): string[] {\n const requireFromRoot = createRequire(join(resolve(root), \"package.json\"));\n const requireFromHere = createRequire(import.meta.url);\n\n let vitePlus: Manifest;\n try {\n vitePlus = readManifest(requireFromRoot.resolve(\"vite-plus/package.json\"));\n } catch {\n console.log(\"plugin API pin: skipped, `vite-plus` is not installed here.\");\n return [];\n }\n\n const declared = vitePlus.declaredPlugins.replace(/^=/u, \"\");\n if (!/^\\d+\\.\\d+\\.\\d+$/u.test(declared)) {\n return [`vite-plus ${vitePlus.version} no longer declares an exact @oxlint/plugins version.`];\n }\n\n let installed: Manifest;\n try {\n const entry = requireFromRoot.resolve(\"@oxlint/plugins\");\n const manifest = manifestNear(entry);\n if (manifest === null) throw new Error(\"no package.json above @oxlint/plugins\");\n installed = readManifest(manifest);\n } catch {\n return [\"`@oxlint/plugins` is not installed. It is a peer dependency of @rdjperron/lint.\"];\n }\n\n const self = readManifest(requireFromHere.resolve(\"../package.json\"));\n const peer = self.peerPlugins;\n const failures: string[] = [];\n\n if (installed.version !== declared) {\n failures.push(\n `@oxlint/plugins ${installed.version} is installed and vite-plus ${vitePlus.version} declares ` +\n `${declared}. Move the pin: install @oxlint/plugins@${declared} exactly.`,\n );\n }\n if (!caretAccepts(peer, declared)) {\n failures.push(\n `@rdjperron/lint ${self.version} wants @oxlint/plugins ${peer} and vite-plus ${vitePlus.version} ` +\n `declares ${declared}. Move one of the two pins.`,\n );\n }\n if (failures.length === 0) {\n console.log(\n `plugin API pin: @oxlint/plugins ${installed.version}, what vite-plus ${vitePlus.version} declares.`,\n );\n }\n return failures;\n}\n\n/** The suite for this package's own fixtures, as they sit in a consumer's `node_modules`. */\nexport function packageSuite(root = process.cwd()): Suite {\n const packageDir = \"node_modules/@rdjperron/lint\";\n return {\n plugin: \"rdjperron\",\n fixtureDir: `${packageDir}/fixtures`,\n viewDir: \"scripts/.lint-fixtures/rdjperron\",\n alwaysView: true,\n readme: `${packageDir}/fixtures/README.md`,\n registeredRules: async () => {\n try {\n const requireFromRoot = createRequire(join(resolve(root), \"package.json\"));\n const loaded: unknown = await import(requireFromRoot.resolve(\"@rdjperron/lint\"));\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\n ? Object.keys(rules).toSorted(byName)\n : null;\n } catch {\n return null;\n }\n },\n };\n}\n\n/** The bin: this package's suite, plus the plugin API pin, from the consumer's repo root. */\nexport async function main(root = process.cwd()): Promise<number> {\n const pinFailures = verifyPluginApiPin(root);\n for (const failure of pinFailures) console.error(`FAIL: ${failure}`);\n console.log(\"\");\n const ruleFailures = await verifySuites([packageSuite(root)], { root });\n return pinFailures.length + ruleFailures > 0 ? 1 : 0;\n}\n\nconst invoked = process.argv[1];\nconst isBin =\n invoked !== undefined &&\n existsSync(invoked) &&\n realpathSync(invoked) === fileURLToPath(import.meta.url);\n\nif (isBin) {\n let exitCode = 1;\n try {\n exitCode = await main();\n } catch (error) {\n console.error(`\\nFAIL: ${error instanceof Error ? error.message : String(error)}`);\n }\n process.exit(exitCode);\n}\n"],"mappings":"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"}
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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"}