oxlint-plugin-vize 0.435.0 → 0.439.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/cli.mjs CHANGED
@@ -1,8 +1,10 @@
1
- import { i as hasVueLikeExtension, s as isStandaloneHtmlFile, t as appendScriptlessWorkaround } from "./workaround-CcnA_oWy.mjs";
1
+ import { n as prepareWorkaroundSource, p as isStandaloneHtmlFile, u as hasVueLikeExtension } from "./workaround-r91ttnV_.mjs";
2
2
  import { spawn, spawnSync } from "node:child_process";
3
3
  import path from "node:path";
4
4
  import fs from "node:fs";
5
+ import { randomUUID } from "node:crypto";
5
6
  import os from "node:os";
7
+ import { pathToFileURL } from "node:url";
6
8
  //#region src/cli/args.ts
7
9
  const OPTION_NAMES_WITH_VALUES = new Set([
8
10
  "-A",
@@ -242,6 +244,101 @@ function escapeJsonStringSegment(value) {
242
244
  return JSON.stringify(value).slice(1, -1);
243
245
  }
244
246
  //#endregion
247
+ //#region src/cli/locations.ts
248
+ /** Restore ranges before temporary filenames are replaced or files are removed. */
249
+ function rewriteReportedLocations(output, locations) {
250
+ if (locations.size === 0 || output === "") return output;
251
+ let report;
252
+ try {
253
+ report = JSON.parse(output);
254
+ } catch {
255
+ return rewriteTextLocations(output, locations);
256
+ }
257
+ if (report == null || !Array.isArray(report.diagnostics)) return output;
258
+ const spans = report.diagnostics.flatMap((diagnostic) => (diagnostic.labels ?? []).map((label) => ({
259
+ original: label.span,
260
+ mapped: mapSpan(label.span, locations.get(diagnostic.filename ?? ""))
261
+ })));
262
+ let index = 0;
263
+ const rewritten = output.replace(/"span"\s*:\s*(\{[^{}]*\})/gu, (whole, raw) => {
264
+ const span = spans[index++];
265
+ if (span == null || JSON.stringify(JSON.parse(raw)) !== JSON.stringify(span.original)) throw new Error("Oxlint JSON label ownership changed; refusing a partial range rewrite.");
266
+ const mapped = span.mapped;
267
+ if (mapped == null) return whole;
268
+ return whole.replace(/("(offset|length|line|column)"\s*:\s*)\d+/gu, (_value, prefix, key) => `${prefix}${mapped[key]}`);
269
+ });
270
+ if (index !== spans.length) throw new Error("Oxlint JSON labels could not all be bound to their reported spans.");
271
+ return rewritten;
272
+ }
273
+ function mapSpan(span, map) {
274
+ if (map == null) return void 0;
275
+ const start = originalIndex(span.offset, map);
276
+ const end = originalIndex(span.offset + span.length, map);
277
+ if (start == null || end == null || end < start) return void 0;
278
+ const before = map.source.slice(0, start);
279
+ return {
280
+ offset: Buffer.byteLength(before, "utf8"),
281
+ length: Buffer.byteLength(map.source.slice(start, end), "utf8"),
282
+ line: before.split("\n").length,
283
+ column: start - before.lastIndexOf("\n")
284
+ };
285
+ }
286
+ function originalIndex(offset, map) {
287
+ if (!Number.isSafeInteger(offset) || offset < 0) return void 0;
288
+ if (offset >= map.scriptStart && offset <= map.scriptStart + map.source.length) {
289
+ const index = offset - map.scriptStart;
290
+ const previous = map.source.charCodeAt(index - 1);
291
+ const current = map.source.charCodeAt(index);
292
+ if (previous >= 55296 && previous <= 56319 && current >= 56320 && current <= 57343) return void 0;
293
+ return index;
294
+ }
295
+ const relative = offset - map.originalStart;
296
+ const bytes = Buffer.from(map.source, "utf8");
297
+ if (relative < 0 || relative > bytes.length) return void 0;
298
+ const before = bytes.subarray(0, relative).toString("utf8");
299
+ if (!Buffer.from(before, "utf8").equals(bytes.subarray(0, relative))) return void 0;
300
+ return before.length;
301
+ }
302
+ function rewriteTextLocations(output, locations) {
303
+ let active;
304
+ const ansi = String.raw`\u001B\[[0-9;]*m`;
305
+ const colors = new RegExp(ansi, "gu");
306
+ const stylishRow = new RegExp(String.raw`^(\s+(?:${ansi})*)(\d+):(\d+)((?:${ansi})*\s+(?:${ansi}|\s)*(?:error|warning)(?:${ansi})*\s+.*)$`, "u");
307
+ return output.split("\n").map((line) => {
308
+ if (line !== "" && line.trimStart() === line) active = locations.get(line.replace(colors, ""));
309
+ const frame = /^(.*╭─\[)(.*):(\d+):(\d+)(\].*)$/u.exec(line);
310
+ if (frame != null) {
311
+ const owner = frame[2].replace(colors, "");
312
+ const map = locations.get(owner);
313
+ const mapped = map == null ? void 0 : textPosition(Number(frame[3]), Number(frame[4]), map);
314
+ if (mapped != null) return `${frame[1]}${frame[2]}:${mapped.line}:${mapped.column}${frame[5]}`;
315
+ }
316
+ const stylish = stylishRow.exec(line);
317
+ if (stylish != null && active != null) {
318
+ const mapped = textPosition(Number(stylish[2]), Number(stylish[3]), active);
319
+ if (mapped != null) return `${stylish[1]}${mapped.line}:${mapped.column}${stylish[4]}`;
320
+ }
321
+ const plain = /^(.*?):(\d+):(\d+)(: )/u.exec(line);
322
+ const map = plain == null ? void 0 : locations.get(plain[1]);
323
+ if (plain != null && map != null) {
324
+ const mapped = textPosition(Number(plain[2]), Number(plain[3]), map);
325
+ if (mapped != null) return `${plain[1]}:${mapped.line}:${mapped.column}${plain[4]}${line.slice(plain[0].length)}`;
326
+ }
327
+ return line;
328
+ }).join("\n");
329
+ }
330
+ function textPosition(line, column, map) {
331
+ const lines = map.source.split("\n");
332
+ const originalLine = lines.length + 1;
333
+ const mappedLine = line >= originalLine ? line - originalLine + 1 : line;
334
+ const mappedColumn = line === 1 ? column - map.scriptStart : column;
335
+ if (mappedLine < 1 || mappedLine > lines.length || mappedColumn < 1 || mappedColumn > lines[mappedLine - 1].length + 1) return void 0;
336
+ return {
337
+ line: mappedLine,
338
+ column: mappedColumn
339
+ };
340
+ }
341
+ //#endregion
245
342
  //#region src/cli/workaround-files.ts
246
343
  function prepareScriptlessWorkaroundFiles(cwd, filenames) {
247
344
  const nodeModulesDir = path.join(cwd, "node_modules");
@@ -251,6 +348,7 @@ function prepareScriptlessWorkaroundFiles(cwd, filenames) {
251
348
  const ignoreArgs = [];
252
349
  const tempArgs = [];
253
350
  const pathReplacements = /* @__PURE__ */ new Map();
351
+ const locations = /* @__PURE__ */ new Map();
254
352
  let counter = 0;
255
353
  for (const filename of filenames) {
256
354
  const source = fs.readFileSync(filename, "utf8");
@@ -265,10 +363,12 @@ function prepareScriptlessWorkaroundFiles(cwd, filenames) {
265
363
  const tempFilename = path.join(tempDir, `${counter}-${tempBasename}`);
266
364
  counter += 1;
267
365
  fs.mkdirSync(path.dirname(tempFilename), { recursive: true });
268
- fs.writeFileSync(tempFilename, appendScriptlessWorkaround(source, filename));
366
+ const bridge = prepareWorkaroundSource(source, filename);
367
+ fs.writeFileSync(tempFilename, bridge.source);
269
368
  ignoreArgs.push("--ignore-pattern", toCliPath$1(relativeFilename));
270
369
  tempArgs.push(tempFilename);
271
370
  registerPathReplacementVariants(pathReplacements, cwd, tempFilename, filename);
371
+ registerLocationVariants(locations, cwd, tempFilename, bridge.locations);
272
372
  }
273
373
  return {
274
374
  appendedArgs: [...ignoreArgs, ...tempArgs],
@@ -283,9 +383,18 @@ function prepareScriptlessWorkaroundFiles(cwd, filenames) {
283
383
  if (createdNodeModules) removeEmptyDirectory(nodeModulesDir);
284
384
  },
285
385
  pathReplacements,
386
+ locations,
286
387
  usedScriptlessWorkaround: pathReplacements.size > 0
287
388
  };
288
389
  }
390
+ function registerLocationVariants(locations, cwd, filename, map) {
391
+ for (const name of [
392
+ filename,
393
+ toCliPath$1(filename),
394
+ path.relative(cwd, filename),
395
+ toCliPath$1(path.relative(cwd, filename))
396
+ ]) locations.set(name, map);
397
+ }
289
398
  function createWorkaroundTempDir(nodeModulesDir, tempRoot) {
290
399
  const createdNodeModules = !fs.existsSync(nodeModulesDir);
291
400
  fs.mkdirSync(tempRoot, { recursive: true });
@@ -328,38 +437,90 @@ function getReportedOriginalFilename(cwd, filename) {
328
437
  }
329
438
  //#endregion
330
439
  //#region src/cli/scoped-config.ts
440
+ const CONFIG_NAMES = [
441
+ ".oxlintrc.json",
442
+ ".oxlintrc.jsonc",
443
+ "oxlint.config.ts",
444
+ "oxlint.config.mts"
445
+ ];
331
446
  function readScopedConfig(cwd, args) {
332
447
  let file;
333
448
  for (let index = 0; index < args.length && args[index] !== "--"; index += 1) if (args[index] === "-c" || args[index] === "--config") file = args[++index];
334
449
  else if (args[index].startsWith("--config=")) file = args[index].slice(9);
335
- if (file == null || !file.endsWith(".json")) return void 0;
450
+ const discovered = file == null;
451
+ if (discovered) {
452
+ const found = CONFIG_NAMES.filter((name) => fs.existsSync(path.join(cwd, name)));
453
+ if (found.length !== 1) return void 0;
454
+ file = found[0];
455
+ }
456
+ if (file == null) return void 0;
457
+ const module = /\.(?:m?ts)$/u.test(file);
458
+ if (!module && !file.endsWith(".json")) return void 0;
336
459
  file = path.resolve(cwd, file);
337
460
  const bytes = fs.readFileSync(file, "utf8");
461
+ if (!fs.lstatSync(file).isFile() || fs.lstatSync(file).isSymbolicLink()) throw new Error(`Scoped Vue transport requires a regular config: ${file}`);
462
+ if (module) return {
463
+ file,
464
+ bytes,
465
+ value: {},
466
+ module: true,
467
+ discovered
468
+ };
338
469
  let value;
339
470
  try {
340
471
  value = JSON.parse(bytes);
341
472
  } catch {
342
473
  return;
343
474
  }
344
- if (!isRecord(value) || value.ignorePatterns == null && value.overrides == null) return void 0;
345
- if (!fs.lstatSync(file).isFile() || fs.lstatSync(file).isSymbolicLink()) throw new Error(`Scoped Vue transport requires a regular JSON config: ${file}`);
475
+ if (!isRecord$1(value)) return void 0;
346
476
  return {
347
477
  file,
348
478
  bytes,
479
+ value,
480
+ discovered
481
+ };
482
+ }
483
+ /** Match Oxlint's native Node import and JSON serialization of TS config objects. */
484
+ async function loadScopedConfig(config) {
485
+ if (!config.module) return config;
486
+ const loaded = await import(pathToFileURL(config.file).href);
487
+ if (!isRecord$1(loaded.default)) throw new Error("Scoped Vue transport requires a default config object.");
488
+ const value = JSON.parse(JSON.stringify(loaded.default));
489
+ if (!isRecord$1(value)) throw new Error("Scoped Vue transport requires a serializable config object.");
490
+ return {
491
+ ...config,
349
492
  value
350
493
  };
351
494
  }
495
+ function validateNestedConfigs(config, files) {
496
+ if (!config.discovered) return;
497
+ const root = path.dirname(config.file);
498
+ for (const file of files) {
499
+ let directory = path.dirname(file);
500
+ while (contains(root, directory) && directory !== root) {
501
+ if (CONFIG_NAMES.some((name) => fs.existsSync(path.join(directory, name)))) throw new Error("Scoped Vue transport cannot preserve nested configuration files; use an explicit config.");
502
+ directory = path.dirname(directory);
503
+ }
504
+ }
505
+ }
352
506
  function validateScopedConfig(config, args) {
353
507
  const value = config.value;
354
508
  if (path.parse(config.file).root !== "/") throw new Error("Scoped Vue transport cannot preserve non-POSIX filesystem roots.");
355
- if (value.extends != null && (!Array.isArray(value.extends) || value.extends.length !== 0)) throw new Error("Scoped Vue transport cannot preserve inherited configs; use a complete JSON config.");
509
+ if (!config.module && value.extends != null && (!Array.isArray(value.extends) || value.extends.length !== 0)) throw new Error("Scoped Vue transport cannot preserve inherited configs; use a complete JSON config.");
510
+ if (config.module && value.extends != null) {
511
+ if (!Array.isArray(value.extends) || value.extends.some((parent) => !isRecord$1(parent))) throw new Error("Scoped Vue transport requires config objects in TypeScript extends.");
512
+ for (const parent of value.extends) validateScopedConfig({
513
+ ...config,
514
+ value: parent
515
+ }, args);
516
+ }
356
517
  if (args.includes("--cwd") || args.some((arg) => arg.startsWith("--cwd="))) throw new Error("Scoped Vue transport requires the process cwd; remove --cwd.");
357
518
  if (args.some((arg) => [
358
519
  "--type-aware",
359
520
  "--type-check",
360
521
  "--type-check-only"
361
- ].includes(arg)) || isRecord(value.options) && (value.options.typeAware || value.options.typeCheck)) throw new Error("Scoped Vue transport cannot preserve Oxlint's type-aware project graph.");
362
- if (args.some((arg) => ["--import-plugin", "--tsconfig"].includes(arg) || arg.startsWith("--import-plugin=") || arg.startsWith("--tsconfig=")) || Array.isArray(value.plugins) && value.plugins.includes("import") || Array.isArray(value.overrides) && value.overrides.some((row) => isRecord(row) && Array.isArray(row.plugins) && row.plugins.includes("import"))) throw new Error("Scoped Vue transport cannot preserve Oxlint's import/project resolution.");
522
+ ].includes(arg)) || isRecord$1(value.options) && (value.options.typeAware || value.options.typeCheck)) throw new Error("Scoped Vue transport cannot preserve Oxlint's type-aware project graph.");
523
+ if (args.some((arg) => ["--import-plugin", "--tsconfig"].includes(arg) || arg.startsWith("--import-plugin=") || arg.startsWith("--tsconfig=")) || Array.isArray(value.plugins) && value.plugins.includes("import") || Array.isArray(value.overrides) && value.overrides.some((row) => isRecord$1(row) && Array.isArray(row.plugins) && row.plugins.includes("import"))) throw new Error("Scoped Vue transport cannot preserve Oxlint's import/project resolution.");
363
524
  }
364
525
  function validateSelectionPaths(cwd, candidates) {
365
526
  if (/[\r\n\\]/u.test(cwd) || [...candidates].some((file) => /[\r\n\\]/u.test(file))) throw new Error("Scoped Vue transport cannot preserve filenames containing CR, LF or backslash.");
@@ -368,7 +529,7 @@ function validateSelectionPaths(cwd, candidates) {
368
529
  function createScopedMirror(cwd, config, files) {
369
530
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "oxlint-vize-"));
370
531
  const configDir = path.dirname(config.file);
371
- const sibling = path.join(configDir, `${path.basename(root)}.json`);
532
+ const sibling = path.join(configDir, `${path.basename(root)}.${config.module ? "mts" : "json"}`);
372
533
  let ownsSibling = false;
373
534
  const cleanup = () => {
374
535
  try {
@@ -384,6 +545,7 @@ function createScopedMirror(cwd, config, files) {
384
545
  if (contains(configDir, root) || contains(cwd, root)) throw new Error("Scoped Vue transport requires a temporary directory outside the lint/config roots.");
385
546
  const originalsToCopies = /* @__PURE__ */ new Map();
386
547
  const pathReplacements = /* @__PURE__ */ new Map();
548
+ const locations = /* @__PURE__ */ new Map();
387
549
  for (const file of files) {
388
550
  if (isStandaloneHtmlFile(file)) throw new Error("Scoped Vue transport requires .vue targets; HTML filtering is not equivalent.");
389
551
  const relative = path.relative(configDir, file);
@@ -391,9 +553,11 @@ function createScopedMirror(cwd, config, files) {
391
553
  if (path.parse(file).root !== path.parse(configDir).root) throw new Error("Scoped Vue transport cannot preserve files on another filesystem root.");
392
554
  const source = fs.readFileSync(file, "utf8");
393
555
  fs.mkdirSync(path.dirname(copy), { recursive: true });
394
- fs.writeFileSync(copy, appendScriptlessWorkaround(source, file), { flag: "wx" });
556
+ const bridge = prepareWorkaroundSource(source, file);
557
+ fs.writeFileSync(copy, bridge.source, { flag: "wx" });
395
558
  originalsToCopies.set(file, copy);
396
559
  registerPathReplacementVariants(pathReplacements, cwd, copy, file);
560
+ registerLocationVariants(locations, cwd, copy, bridge.locations);
397
561
  }
398
562
  const copyPatterns = [...originalsToCopies.values()].map(literalGlob);
399
563
  const outside = [...originalsToCopies].filter(([file]) => !contains(configDir, file));
@@ -412,28 +576,32 @@ function createScopedMirror(cwd, config, files) {
412
576
  throw new Error(`Scoped Vue transport cannot preserve outside-root override pattern: ${pattern}`);
413
577
  });
414
578
  };
415
- const overrides = config.value.overrides;
416
- if (overrides != null && !Array.isArray(overrides)) throw new Error("Scoped Vue transport requires an override array.");
417
- const borrowed = {
418
- ...config.value,
419
- overrides: (overrides ?? []).flatMap((row) => {
420
- if (!isRecord(row)) throw new Error("Scoped Vue transport requires object overrides.");
421
- const files = mapPatterns(row.files);
422
- const excludes = row.excludeFiles == null ? [] : mapPatterns(row.excludeFiles);
423
- return [{
424
- ...row,
425
- excludeFiles: [...strings(row.excludeFiles), ...copyPatterns]
426
- }, {
427
- ...row,
428
- files,
429
- ...excludes.length ? { excludeFiles: excludes } : { excludeFiles: [] }
430
- }];
431
- })
579
+ const project = (value) => {
580
+ const overrides = value.overrides;
581
+ if (overrides != null && !Array.isArray(overrides)) throw new Error("Scoped Vue transport requires an override array.");
582
+ return {
583
+ ...value,
584
+ ...config.module && Array.isArray(value.extends) ? { extends: value.extends.map((parent) => project(parent)) } : {},
585
+ overrides: (overrides ?? []).flatMap((row) => {
586
+ if (!isRecord$1(row)) throw new Error("Scoped Vue transport requires object overrides.");
587
+ const files = mapPatterns(row.files);
588
+ const excludes = row.excludeFiles == null ? [] : mapPatterns(row.excludeFiles);
589
+ return [{
590
+ ...row,
591
+ excludeFiles: [...strings(row.excludeFiles), ...copyPatterns]
592
+ }, {
593
+ ...row,
594
+ files,
595
+ ...excludes.length ? { excludeFiles: excludes } : { excludeFiles: [] }
596
+ }];
597
+ })
598
+ };
432
599
  };
600
+ const borrowed = project(config.value);
433
601
  const descriptor = fs.openSync(sibling, "wx");
434
602
  ownsSibling = true;
435
603
  try {
436
- fs.writeFileSync(descriptor, `${JSON.stringify(borrowed, null, 2)}\n`);
604
+ fs.writeFileSync(descriptor, `${config.module ? "export default " : ""}${JSON.stringify(borrowed, null, 2)}${config.module ? ";" : ""}\n`);
437
605
  } finally {
438
606
  fs.closeSync(descriptor);
439
607
  }
@@ -442,6 +610,7 @@ function createScopedMirror(cwd, config, files) {
442
610
  sibling,
443
611
  originalsToCopies,
444
612
  pathReplacements,
613
+ locations,
445
614
  cleanup
446
615
  };
447
616
  } catch (error) {
@@ -449,7 +618,7 @@ function createScopedMirror(cwd, config, files) {
449
618
  throw error;
450
619
  }
451
620
  }
452
- function isRecord(value) {
621
+ function isRecord$1(value) {
453
622
  return typeof value === "object" && value != null && !Array.isArray(value);
454
623
  }
455
624
  function strings(value) {
@@ -468,11 +637,275 @@ function literalGlob(file) {
468
637
  return toCliPath(file).replaceAll(/[\\*?[\]{}]/gu, String.raw`\$&`);
469
638
  }
470
639
  //#endregion
640
+ //#region src/cli/project-checks.ts
641
+ const isRecord = (value) => typeof value === "object" && value != null && !Array.isArray(value);
642
+ const isVize = (name) => name.startsWith("vize/");
643
+ /** Native/project checks retain original paths; only Vize executes on the bridge. */
644
+ function splitProjectConfig(config) {
645
+ const visit = (value, bridge, override = false) => ({
646
+ ...value,
647
+ ...Array.isArray(value.extends) ? { extends: value.extends.map((parent) => visit(parent, bridge)) } : {},
648
+ ...Array.isArray(value.overrides) ? { overrides: value.overrides.map((row) => visit(row, bridge, true)) } : {},
649
+ rules: Object.fromEntries(Object.entries(isRecord(value.rules) ? value.rules : {}).flatMap(([name, setting]) => bridge ? isVize(name) ? [[name, setting]] : [] : [[name, isVize(name) ? "off" : setting]])),
650
+ ...bridge ? {
651
+ plugins: [],
652
+ ...!override ? { categories: Object.fromEntries(["correctness", ...Object.keys(isRecord(value.categories) ? value.categories : {})].map((category) => [category, "off"])) } : {},
653
+ ...isRecord(value.options) ? { options: {
654
+ ...value.options,
655
+ typeAware: false,
656
+ typeCheck: false
657
+ } } : {}
658
+ } : {}
659
+ });
660
+ return {
661
+ original: {
662
+ ...config,
663
+ value: visit(config.value, false)
664
+ },
665
+ bridge: {
666
+ ...config,
667
+ value: visit(config.value, true)
668
+ }
669
+ };
670
+ }
671
+ function replaceConfigArgs(options, file) {
672
+ const args = [...options];
673
+ let replaced = false;
674
+ for (let index = 0; index < args.length && args[index] !== "--"; index += 1) if (args[index] === "-c" || args[index] === "--config") {
675
+ args[++index] = file;
676
+ replaced = true;
677
+ } else if (args[index].startsWith("--config=")) {
678
+ args[index] = `--config=${file}`;
679
+ replaced = true;
680
+ }
681
+ if (!replaced) args.unshift("--config", file);
682
+ return args;
683
+ }
684
+ function bridgeProjectArgs(options) {
685
+ if (options.some((arg) => [
686
+ "-A",
687
+ "-D",
688
+ "-W",
689
+ "--allow",
690
+ "--deny",
691
+ "--warn",
692
+ "--max-warnings",
693
+ "--type-check-only",
694
+ "--report-unused-disable-directives",
695
+ "--report-unused-disable-directives-severity",
696
+ "--fix",
697
+ "--fix-suggestions",
698
+ "--fix-dangerously"
699
+ ].includes(arg) || /^(?:--allow|--deny|--warn|--max-warnings|--report-unused-disable-directives-severity)=/u.test(arg))) throw new Error("Original-path project checks require rule severity in the config; remove CLI severity/limit overrides and fix flags, and unused-disable reporting.");
700
+ const args = [];
701
+ for (let index = 0; index < options.length; index += 1) {
702
+ const arg = options[index];
703
+ if (arg === "--tsconfig") index += 1;
704
+ else if (![
705
+ "--import-plugin",
706
+ "--type-aware",
707
+ "--type-check"
708
+ ].includes(arg) && !arg.startsWith("--tsconfig=")) args.push(arg);
709
+ }
710
+ return args;
711
+ }
712
+ function writeOriginalProjectConfig(config) {
713
+ const file = path.join(path.dirname(config.file), `oxlint-vize-original-${randomUUID()}.${config.module ? "mts" : "json"}`);
714
+ fs.writeFileSync(file, `${config.module ? "export default " : ""}${JSON.stringify(config.value)}${config.module ? ";" : ""}\n`, { flag: "wx" });
715
+ return {
716
+ file,
717
+ cleanup: () => fs.unlinkSync(file)
718
+ };
719
+ }
720
+ //#endregion
721
+ //#region src/cli/project-json.ts
722
+ /** Locate top-level values in an already validated JSON document, retaining text. */
723
+ function values(text) {
724
+ const result = /* @__PURE__ */ new Map();
725
+ let cursor = 0;
726
+ const whitespace = () => {
727
+ while (/\s/u.test(text[cursor] ?? "")) cursor++;
728
+ };
729
+ const string = () => {
730
+ if (text[cursor++] !== "\"") throw new Error("Invalid Oxlint JSON property.");
731
+ while (cursor < text.length) if (text[cursor] === "\\") cursor += 2;
732
+ else if (text[cursor++] === "\"") return;
733
+ throw new Error("Incomplete Oxlint JSON string.");
734
+ };
735
+ const value = () => {
736
+ if (text[cursor] === "\"") return string();
737
+ if (text[cursor] === "[" || text[cursor] === "{") {
738
+ let depth = 0;
739
+ do
740
+ if (text[cursor] === "\"") string();
741
+ else {
742
+ if (text[cursor] === "[" || text[cursor] === "{") depth++;
743
+ if (text[cursor] === "]" || text[cursor] === "}") depth--;
744
+ cursor++;
745
+ }
746
+ while (depth > 0 && cursor < text.length);
747
+ if (depth !== 0) throw new Error("Incomplete Oxlint JSON value.");
748
+ return;
749
+ }
750
+ while (cursor < text.length && !/[\s,}]/u.test(text[cursor])) cursor++;
751
+ };
752
+ whitespace();
753
+ if (text[cursor++] !== "{") throw new Error("Oxlint JSON report must be an object.");
754
+ whitespace();
755
+ while (text[cursor] !== "}") {
756
+ const keyStart = cursor;
757
+ string();
758
+ const key = JSON.parse(text.slice(keyStart, cursor));
759
+ if (result.has(key)) throw new Error("Duplicate Oxlint JSON report property.");
760
+ whitespace();
761
+ if (text[cursor++] !== ":") throw new Error("Invalid Oxlint JSON property separator.");
762
+ whitespace();
763
+ const start = cursor;
764
+ value();
765
+ result.set(key, [start, cursor]);
766
+ whitespace();
767
+ if (text[cursor] === "}") break;
768
+ if (text[cursor++] !== ",") throw new Error("Invalid Oxlint JSON report separator.");
769
+ whitespace();
770
+ }
771
+ return result;
772
+ }
773
+ /** Keep the original reporter layout and both diagnostic arrays' authored bytes. */
774
+ function joinProjectJson(source, bridge, totals) {
775
+ const original = values(source);
776
+ const template = values(bridge);
777
+ const diagnostics = (text, fields) => {
778
+ const span = fields.get("diagnostics");
779
+ if (!span || text[span[0]] !== "[" || text[span[1] - 1] !== "]") throw new Error("Missing complete Oxlint diagnostics array.");
780
+ return text.slice(span[0] + 1, span[1] - 1);
781
+ };
782
+ const left = diagnostics(source, original), right = diagnostics(bridge, template);
783
+ const comma = left.trim() && right.trim() ? "," : "";
784
+ const replacements = {
785
+ ...totals,
786
+ diagnostics: `[${left}${comma}${right}]`
787
+ };
788
+ const patches = Object.entries(replacements).map(([key, replacement]) => {
789
+ if (typeof replacement === "number" && !Number.isFinite(replacement)) throw new Error(`Invalid Oxlint JSON report total: ${key}`);
790
+ const span = original.get(key);
791
+ if (!span) throw new Error(`Missing original Oxlint JSON report property: ${key}`);
792
+ return {
793
+ start: span[0],
794
+ end: span[1],
795
+ value: String(replacement)
796
+ };
797
+ });
798
+ for (const patch of patches.sort((left, right) => right.start - left.start)) source = source.slice(0, patch.start) + patch.value + source.slice(patch.end);
799
+ return source;
800
+ }
801
+ //#endregion
802
+ //#region src/cli/project-output.ts
803
+ function unavailableProjectTransport(original, error) {
804
+ return {
805
+ ...original,
806
+ status: Math.max(original.status ?? 1, 1),
807
+ stderr: original.stderr + `\nScript-safe Vue transport unavailable: ${error instanceof Error ? error.message : String(error)}\n`
808
+ };
809
+ }
810
+ function projectOutputFormat(args) {
811
+ let format = "default";
812
+ for (let index = 0; index < args.length && args[index] !== "--"; index += 1) if (args[index] === "-f" || args[index] === "--format") format = args[++index];
813
+ else if (args[index].startsWith("--format=")) format = args[index].slice(9);
814
+ if (![
815
+ "default",
816
+ "json",
817
+ "unix",
818
+ "stylish"
819
+ ].includes(format)) throw new Error("Original-path project checks support default, JSON, Unix and Stylish reports.");
820
+ return format;
821
+ }
822
+ function mergeProjectOutput(original, bridge, args) {
823
+ const format = projectOutputFormat(args);
824
+ const unavailable = (phase) => ({
825
+ stdout: original.stdout,
826
+ stderr: original.stderr + bridge.stderr + (phase === bridge ? bridge.stdout : ""),
827
+ status: Math.max(original.status ?? 1, bridge.status ?? 1)
828
+ });
829
+ let stdout = original.stdout + bridge.stdout;
830
+ if (format === "json") {
831
+ for (const phase of [original, bridge]) {
832
+ if (phase.stdout === "" && phase.status !== 0) return unavailable(phase);
833
+ if (phase.stdout === "") throw new Error("Original-path project checks received an empty successful JSON report.");
834
+ }
835
+ const reports = [];
836
+ for (const phase of [original, bridge]) try {
837
+ reports.push(JSON.parse(phase.stdout));
838
+ } catch (error) {
839
+ if (phase.status !== 0) return unavailable(phase);
840
+ throw error;
841
+ }
842
+ const [source, template] = reports;
843
+ if (!Array.isArray(source.diagnostics) || !Array.isArray(template.diagnostics) || typeof source.number_of_rules !== "number" || typeof template.number_of_rules !== "number" || typeof source.number_of_files !== "number" || typeof source.threads_count !== "number" || typeof template.threads_count !== "number" || typeof source.start_time !== "number" || typeof template.start_time !== "number") throw new Error("Original-path project checks require complete Oxlint JSON reports.");
844
+ const totals = {};
845
+ for (const key of new Set([...Object.keys(source), ...Object.keys(template)])) {
846
+ if (!/^number_of_|_count$/u.test(key) || [
847
+ "number_of_files",
848
+ "number_of_rules",
849
+ "threads_count"
850
+ ].includes(key)) continue;
851
+ if (typeof source[key] !== "number" || typeof template[key] !== "number") throw new Error(`Original-path project checks cannot combine the Oxlint report total: ${key}`);
852
+ totals[key] = source[key] + template[key];
853
+ }
854
+ stdout = joinProjectJson(original.stdout, bridge.stdout, {
855
+ ...totals,
856
+ number_of_rules: source.number_of_rules + template.number_of_rules,
857
+ threads_count: Math.max(source.threads_count, template.threads_count),
858
+ start_time: source.start_time + template.start_time
859
+ });
860
+ }
861
+ return {
862
+ stdout,
863
+ stderr: original.stderr + bridge.stderr,
864
+ status: Math.max(original.status ?? 1, bridge.status ?? 1)
865
+ };
866
+ }
867
+ //#endregion
471
868
  //#region src/cli/scoped-selection.ts
472
869
  async function prepareScopedSelection(cwd, originalArgs, vueFiles, run, candidates) {
473
870
  if (vueFiles.length === 0) return void 0;
474
- const config = readScopedConfig(cwd, originalArgs);
475
- if (config == null) return void 0;
871
+ if (vueFiles.every(isStandaloneHtmlFile)) return void 0;
872
+ let config = readScopedConfig(cwd, originalArgs);
873
+ if (config == null) {
874
+ const inspection = originalArgs.some((arg) => [
875
+ "-h",
876
+ "--help",
877
+ "-V",
878
+ "--version",
879
+ "--print-config",
880
+ "--rules",
881
+ "--debug"
882
+ ].includes(arg) || arg.startsWith("--debug="));
883
+ if (originalArgs.some((arg) => [
884
+ "--fix",
885
+ "--fix-suggestions",
886
+ "--fix-dangerously",
887
+ "--init",
888
+ "--suppress-all",
889
+ "--prune-suppressions",
890
+ "--lsp"
891
+ ].includes(arg))) throw new Error("Script-safe Vue transport cannot preserve writes or LSP execution.");
892
+ if (inspection) return { result: await run(originalArgs) };
893
+ const selection = await run([
894
+ "--debug",
895
+ "files",
896
+ ...originalArgs
897
+ ]);
898
+ if (selection.status !== 0) return { result: selection };
899
+ const selected = parseFiles(cwd, selection);
900
+ const original = await run(originalArgs);
901
+ if (vueFiles.some(isStandaloneHtmlFile)) return { result: unavailableProjectTransport(original, /* @__PURE__ */ new Error("Scoped Vue transport cannot combine standalone HTML and Vue targets.")) };
902
+ if (!vueFiles.some((file) => selected.includes(file))) return { result: original };
903
+ return { result: {
904
+ ...original,
905
+ status: Math.max(original.status ?? 1, 1),
906
+ stderr: original.stderr + "Script-safe Vue transport requires one regular JSON or TS/MTS object config; the original engine report is retained, but Vize transport is unqualified.\n"
907
+ } };
908
+ }
476
909
  const options = withoutLintTargets(originalArgs);
477
910
  if (options.some((arg) => [
478
911
  "--debug",
@@ -494,17 +927,43 @@ async function prepareScopedSelection(cwd, originalArgs, vueFiles, run, candidat
494
927
  ...originalArgs
495
928
  ]);
496
929
  if (selection.status !== 0) return { result: selection };
497
- validateScopedConfig(config, originalArgs);
930
+ config = await loadScopedConfig(config);
498
931
  const selected = parseFiles(cwd, selection);
932
+ validateNestedConfigs(config, selected);
499
933
  if (selected.some((file) => !candidates.has(file))) throw new Error("Scoped Vue transport cannot bind every selected file to an original candidate.");
500
934
  const selectedVue = vueFiles.filter((file) => selected.includes(file));
501
- if (selectedVue.length === 0) return { result: await run(originalArgs) };
502
- const mirror = createScopedMirror(cwd, config, selectedVue);
935
+ if (selectedVue.length === 0) {
936
+ const original = await run(originalArgs);
937
+ return { result: vueFiles.some(isStandaloneHtmlFile) ? unavailableProjectTransport(original, /* @__PURE__ */ new Error("Scoped Vue transport cannot combine standalone HTML and Vue targets.")) : original };
938
+ }
939
+ const bridgeOptions = bridgeProjectArgs(options);
940
+ projectOutputFormat(options);
941
+ const phases = splitProjectConfig(config);
942
+ validateScopedConfig(phases.bridge, bridgeOptions);
943
+ let originalResult;
944
+ let mirror;
503
945
  try {
504
- const args = [...options];
946
+ const source = writeOriginalProjectConfig(phases.original);
947
+ try {
948
+ const sourceArgs = replaceConfigArgs(originalArgs, source.file);
949
+ const sourceSelection = await run([
950
+ "--debug",
951
+ "files",
952
+ ...sourceArgs
953
+ ]);
954
+ if (sourceSelection.status !== 0 || !equalFiles(selected, parseFiles(cwd, sourceSelection))) throw new Error("Original-path project checks changed Oxlint's selected files.");
955
+ originalResult = await run(sourceArgs);
956
+ } finally {
957
+ source.cleanup();
958
+ }
959
+ config = phases.bridge;
960
+ if (vueFiles.some(isStandaloneHtmlFile)) throw new Error("Scoped Vue transport cannot combine standalone HTML and Vue targets.");
961
+ mirror = createScopedMirror(cwd, config, selectedVue);
962
+ const args = [...bridgeOptions];
963
+ if (config.discovered) args.unshift("--config", mirror.sibling);
505
964
  for (let index = 0; index < args.length && args[index] !== "--"; index += 1) if (args[index] === "-c" || args[index] === "--config") args[++index] = mirror.sibling;
506
965
  else if (args[index].startsWith("--config=")) args[index] = `--config=${mirror.sibling}`;
507
- const projected = selected.map((file) => mirror.originalsToCopies.get(file) ?? file).sort();
966
+ const projected = selectedVue.map((file) => mirror.originalsToCopies.get(file) ?? file).sort();
508
967
  args.push(...projected);
509
968
  const transported = await run([
510
969
  "--debug",
@@ -514,16 +973,24 @@ async function prepareScopedSelection(cwd, originalArgs, vueFiles, run, candidat
514
973
  if (transported.status !== 0 || transported.stderr !== "" || !equalFiles(projected, parseFiles(cwd, transported))) throw new Error("Scoped Vue transport changed Oxlint's selected files. The current ignore/config envelope cannot preserve original path ownership.\n" + transported.stdout + transported.stderr);
515
974
  return {
516
975
  args,
976
+ originalResult,
517
977
  prepared: {
518
978
  appendedArgs: [],
519
979
  pathReplacements: mirror.pathReplacements,
980
+ locations: mirror.locations,
520
981
  usedScriptlessWorkaround: true,
521
982
  cleanup: mirror.cleanup
522
983
  }
523
984
  };
524
985
  } catch (error) {
525
- mirror.cleanup();
526
- throw error;
986
+ let failure = error;
987
+ try {
988
+ mirror?.cleanup();
989
+ } catch (cleanupError) {
990
+ failure = new AggregateError([error, cleanupError], "Vue transport and cleanup failed.");
991
+ }
992
+ if (originalResult) return { result: unavailableProjectTransport(originalResult, failure) };
993
+ throw failure;
527
994
  }
528
995
  }
529
996
  function parseFiles(cwd, result) {
@@ -554,26 +1021,44 @@ async function main() {
554
1021
  const prepared = sourceResult ? prepareScriptlessWorkaroundFiles(cwd, []) : transport?.prepared ?? prepareScriptlessWorkaroundFiles(cwd, lintFiles);
555
1022
  const args = [oxlintEntrypoint, ...transport?.args ?? [...forwardedArgs, ...prepared.appendedArgs]];
556
1023
  try {
557
- const result = sourceResult ?? await runOxlint(process.execPath, args, cwd);
558
- const stdout = rewriteReportedPaths(result.stdout, prepared.pathReplacements);
559
- const stderr = rewriteReportedPaths(result.stderr, prepared.pathReplacements);
1024
+ let result;
1025
+ let merged;
1026
+ try {
1027
+ result = sourceResult ?? await runOxlint(process.execPath, args, cwd);
1028
+ const mappedStdout = rewriteReportedPaths(rewriteReportedLocations(result.stdout, prepared.locations), prepared.pathReplacements);
1029
+ const mappedStderr = rewriteReportedPaths(rewriteReportedLocations(result.stderr, prepared.locations), prepared.pathReplacements);
1030
+ merged = {
1031
+ ...result,
1032
+ stdout: mappedStdout,
1033
+ stderr: mappedStderr
1034
+ };
1035
+ if (transport?.originalResult) merged = mergeProjectOutput(transport.originalResult, merged, forwardedArgs);
1036
+ } catch (error) {
1037
+ if (transport?.originalResult) {
1038
+ process.exitCode = Math.max(transport.originalResult.status ?? 1, result?.status ?? 1, 1);
1039
+ await writeStream(process.stdout, transport.originalResult.stdout);
1040
+ await writeStream(process.stderr, transport.originalResult.stderr + (result?.stderr ?? "") + (result?.stdout ?? ""));
1041
+ }
1042
+ throw error;
1043
+ }
1044
+ const { stdout, stderr } = merged;
560
1045
  if (stdout) await writeStream(process.stdout, stdout);
561
1046
  if (stderr) await writeStream(process.stderr, stderr);
562
- if (result.status === 0 && stdout === "" && stderr === "" && expectsLintReport(forwardedArgs)) {
1047
+ if (merged.status === 0 && stdout === "" && stderr === "" && expectsLintReport(forwardedArgs)) {
563
1048
  await writeStream(process.stderr, `The oxlint run at ${oxlintEntrypoint} exited 0 but produced no report, although the requested format always emits one. Refusing to treat the silent run as a clean lint result.
564
1049
  `);
565
1050
  process.exitCode = 1;
566
1051
  return;
567
1052
  }
568
1053
  if (prepared.usedScriptlessWorkaround && (forwardedArgs.includes("--fix") || forwardedArgs.includes("--fix-suggestions"))) await writeStream(process.stderr, "\n[oxlint-plugin-vize] Temporary Vue workaround is active; fixes are not applied back to original files yet.\n");
569
- process.exitCode = result.status ?? 1;
1054
+ process.exitCode = merged.status ?? 1;
570
1055
  } finally {
571
1056
  prepared.cleanup();
572
1057
  }
573
1058
  }
574
1059
  main().catch((error) => {
575
1060
  process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
576
- process.exitCode = 1;
1061
+ process.exitCode = Math.max(Number(process.exitCode) || 1, 1);
577
1062
  });
578
1063
  function runOxlint(executable, args, cwd) {
579
1064
  return new Promise((resolve, reject) => {