oxlint-plugin-vize 0.442.0 → 0.454.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,6 +47,36 @@ vp install -D oxlint oxlint-plugin-vize
47
47
 
48
48
  ## Usage
49
49
 
50
+ ### With ESLint
51
+
52
+ Use the independent `oxlint-plugin-vize/eslint` entry with the project's Vue parser:
53
+
54
+ ```js
55
+ import vize from "oxlint-plugin-vize/eslint";
56
+ import vueParser from "vue-eslint-parser";
57
+
58
+ export default [
59
+ {
60
+ files: ["**/*.vue"],
61
+ languageOptions: { parser: vueParser },
62
+ plugins: { vize },
63
+ settings: { vize: { preset: "incremental" } },
64
+ rules: { "vize/vue/require-v-for-key": "error" },
65
+ },
66
+ ];
67
+ ```
68
+
69
+ This entry lints the parser's complete source, including an editor's unsaved text.
70
+ It reports original template, script, and style locations and batches the enabled
71
+ Vize rules once per file. Native text edits support ESLint's `--fix`; suggestions
72
+ are not invented from help text. Install ESLint and
73
+ `vue-eslint-parser` in the project, and keep the plugin and native binding versions
74
+ together. The default package entry remains the Oxlint bridge.
75
+ Unpaired UTF-16 surrogates are refused before native conversion. Invalid native
76
+ ranges and inconsistent locations are errors. Files containing
77
+ lone CR or Unicode line separators currently require a matching native location
78
+ convention; the entry refuses mismatched positions rather than moving reports.
79
+
50
80
  Which file you configure depends on which command you run.
51
81
 
52
82
  | Command | Reads |
@@ -231,8 +261,13 @@ vp exec oxlint-vize -c .oxlintrc.json -f stylish src
231
261
 
232
262
  `oxlint-vize` appends a temporary `<script setup>` location bridge for Vue and HTML files, including files with existing scripts. This lets Patina report the original SFC line and column through Oxlint's JS plugin pipeline. The wrapper rewrites temporary paths back to the original files; `-f json` retains those original positions for machine-readable diagnostics.
233
263
 
264
+ ### Oxlint type-aware rules in Vue files
265
+
266
+ Oxlint's own type-aware rules (`typescript/no-floating-promises`, `typescript/no-misused-promises` and the rest of the tsgolint set) do not look inside `.vue` files. When you run `oxlint-vize --type-aware`, or your `.oxlintrc.json` sets `options.typeAware: true`, the wrapper runs those rules on each SFC's `<script>` and `<script setup>` blocks too, with the severities from your resolved configuration, and reports them at the original `.vue` lines and columns. Imports between `.vue` and `.ts` files resolve as they do for TypeScript. Template expressions are not part of this pass; Vize's `vize/type/*` rules cover them.
267
+
234
268
  ## Limitations
235
269
 
270
+ - The type-aware pass for Vue script blocks uses your root configuration's resolved rules; per-file `overrides` and nested configs do not change which type-aware rules run on Vue files. Your `tsconfig.json` must include the `.vue` files' directories with a `**/*.ts` pattern, because each SFC is checked through a temporary `Component.vue.ts` sibling that is removed after the run.
236
271
  - Raw `oxlint` still misses files without `<script>` or `<script setup>`. It also anchors template diagnostics to an extracted script instead of the original template location. Use `oxlint-vize` when linting Vue and HTML files.
237
272
  - The wrapper's temporary files are not copied back when Oxlint fix flags are used; it warns when `--fix` or `--fix-suggestions` is passed.
238
273
  - With Oxlint 1.78, complete explicit/discovered JSON configs containing `ignorePatterns` or `overrides`, and root TS/MTS configs with object `extends`, the wrapper retains original file selection and ordered Vue overrides. This mode supports POSIX paths; inherited JSON configs, import/type-aware project resolution, suppression state, ambiguous filenames, unsupported outside-root patterns and unknown option forms produce actionable errors.
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { m as loadBinding, n as prepareWorkaroundSource, p as isStandaloneHtmlFile, u as hasVueLikeExtension } from "./workaround-CiQUFJw0.mjs";
1
+ import { m as loadBinding, n as prepareWorkaroundSource, p as isStandaloneHtmlFile, s as extractSfcBlocks, u as hasVueLikeExtension } from "./workaround-CiQUFJw0.mjs";
2
2
  import { spawn, spawnSync } from "node:child_process";
3
3
  import path from "node:path";
4
4
  import fs from "node:fs";
@@ -1119,7 +1119,11 @@ const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
1119
1119
  function prepareHtmlContext(cwd, args, entrypoint, version, candidates) {
1120
1120
  const context = { environment: presentationContext() };
1121
1121
  try {
1122
- if (!["1.78.0", "1.86.0"].includes(version)) throw new Error("original HTML requires a qualified Oxlint 1.78.0 or 1.86.0 host");
1122
+ if (![
1123
+ "1.78.0",
1124
+ "1.81.0",
1125
+ "1.86.0"
1126
+ ].includes(version)) throw new Error("original HTML requires a qualified Oxlint 1.78.0, 1.81.0 or 1.86.0 host");
1123
1127
  const targets = getLintTargets(args);
1124
1128
  if (targets.length !== 1) throw new Error("original HTML requires one literal original target");
1125
1129
  const config = readScopedConfig(cwd, args);
@@ -1394,6 +1398,229 @@ function runOxlint(executable, args, cwd) {
1394
1398
  });
1395
1399
  }
1396
1400
  //#endregion
1401
+ //#region src/cli/type-aware-vue.ts
1402
+ /**
1403
+ * Runs oxlint's type-aware rules on Vue SFC script blocks (#8937).
1404
+ *
1405
+ * oxlint's type-aware engine (tsgolint) never sees `.vue` files, so rules such
1406
+ * as `typescript/no-floating-promises` stay silent inside `<script setup>`.
1407
+ * After the normal run, this pass writes one position-preserving script shim
1408
+ * next to each SFC (`Foo.vue.ts`: every character outside the script blocks
1409
+ * is blanked, newlines are kept), lints only those shims with a configuration
1410
+ * that enables nothing but the user's type-aware rules, and rewrites the shim
1411
+ * paths back to the `.vue` files. Lines and columns already match the SFC, and
1412
+ * `import Foo from "./Foo.vue"` resolves to the shim the way TypeScript
1413
+ * resolves any extensionless sibling, so imported types flow between files.
1414
+ * Template expressions stay with Vize's own `type/*` rules.
1415
+ */
1416
+ const CATEGORIES = [
1417
+ "correctness",
1418
+ "suspicious",
1419
+ "pedantic",
1420
+ "perf",
1421
+ "style",
1422
+ "restriction",
1423
+ "nursery"
1424
+ ];
1425
+ /** Options that would re-enable or re-select rules, or change what is linted. */
1426
+ const DROPPED_WITH_VALUE = new Set([
1427
+ "-A",
1428
+ "-D",
1429
+ "-W",
1430
+ "-c",
1431
+ "--allow",
1432
+ "--deny",
1433
+ "--warn",
1434
+ "--config",
1435
+ "--ignore-path",
1436
+ "--ignore-pattern",
1437
+ "--max-warnings"
1438
+ ]);
1439
+ const DROPPED_FLAGS = new Set([
1440
+ "--fix",
1441
+ "--fix-suggestions",
1442
+ "--fix-dangerously",
1443
+ "--print-config",
1444
+ "--rules",
1445
+ "--type-aware",
1446
+ "--type-check",
1447
+ "--type-check-only",
1448
+ "--suppress-all",
1449
+ "--prune-suppressions"
1450
+ ]);
1451
+ /** Whether the run asks oxlint for type-aware linting (flag or `options.typeAware`). */
1452
+ function requestsTypeAware(args, cwd) {
1453
+ const end = args.includes("--") ? args.indexOf("--") : args.length;
1454
+ const options = args.slice(0, end);
1455
+ if (options.includes("--type-aware")) return true;
1456
+ let configPath = path.join(cwd, ".oxlintrc.json");
1457
+ for (let index = 0; index < options.length; index += 1) if (options[index] === "-c" || options[index] === "--config") configPath = path.resolve(cwd, options[index + 1] ?? "");
1458
+ else if (options[index].startsWith("--config=")) configPath = path.resolve(cwd, options[index].slice(9));
1459
+ if (!configPath.endsWith(".json") || !fs.existsSync(configPath)) return false;
1460
+ try {
1461
+ return JSON.parse(stripJsonComments(fs.readFileSync(configPath, "utf8"))).options?.typeAware === true;
1462
+ } catch {
1463
+ return false;
1464
+ }
1465
+ }
1466
+ /** Blank everything outside the script blocks, keeping every offset in place. */
1467
+ function buildScriptShim(source) {
1468
+ const lineStarts = [0];
1469
+ for (let index = 0; index < source.length; index += 1) if (source.charCodeAt(index) === 10) lineStarts.push(index + 1);
1470
+ const chars = source.replace(/[^\r\n]/gu, " ").split("");
1471
+ let lang = "";
1472
+ let found = false;
1473
+ for (const block of extractSfcBlocks(source)) {
1474
+ if (block.kind !== "script" && block.kind !== "script-setup") continue;
1475
+ const start = lineStarts[block.contentStart.line - 1] + block.contentStart.column - 1;
1476
+ const openTag = source.slice(source.lastIndexOf("<script", start), start);
1477
+ if (/\bsrc\s*=/u.test(openTag)) continue;
1478
+ const blockLang = /\blang\s*=\s*["']?([a-z]+)/iu.exec(openTag)?.[1]?.toLowerCase() ?? "js";
1479
+ if (lang === "" || blockLang.startsWith("t")) lang = blockLang;
1480
+ for (let offset = 0; offset < block.content.length; offset += 1) chars[start + offset] = block.content[offset];
1481
+ found = true;
1482
+ }
1483
+ if (!found) return null;
1484
+ const extension = lang === "tsx" ? ".tsx" : lang === "ts" ? ".ts" : lang === "jsx" ? ".jsx" : ".js";
1485
+ return {
1486
+ text: chars.join(""),
1487
+ extension
1488
+ };
1489
+ }
1490
+ /** The user's resolved config, reduced to its enabled type-aware rules. */
1491
+ function typeAwareOnlyConfig(printed, typeAwareRules) {
1492
+ const rules = {};
1493
+ for (const [name, value] of Object.entries(printed.rules ?? {})) {
1494
+ const severity = Array.isArray(value) ? value[0] : value;
1495
+ if (!typeAwareRules.has(name) || severity === "off" || severity === "allow" || severity === 0) continue;
1496
+ rules[name] = value;
1497
+ }
1498
+ if (Object.keys(rules).length === 0) return null;
1499
+ return {
1500
+ plugins: printed.plugins ?? [],
1501
+ categories: Object.fromEntries(CATEGORIES.map((category) => [category, "off"])),
1502
+ rules,
1503
+ settings: printed.settings ?? {},
1504
+ env: printed.env ?? {},
1505
+ globals: printed.globals ?? {}
1506
+ };
1507
+ }
1508
+ /** Lint the SFC script shims with the user's type-aware rules; undefined when nothing applies. */
1509
+ async function runVueTypeAwarePass(cwd, entrypoint, forwardedArgs, vueFiles, run) {
1510
+ const sfcFiles = vueFiles.filter((file) => file.endsWith(".vue") && !isStandaloneHtmlFile(file));
1511
+ if (sfcFiles.length === 0) return void 0;
1512
+ const options = withoutLintTargets(forwardedArgs);
1513
+ const configOptions = keepConfigSelection(options);
1514
+ const ruleList = await run([
1515
+ entrypoint,
1516
+ "--rules",
1517
+ "--format",
1518
+ "json"
1519
+ ]);
1520
+ const printed = await run([
1521
+ entrypoint,
1522
+ ...configOptions,
1523
+ "--print-config"
1524
+ ]);
1525
+ if (ruleList.status !== 0 || printed.status !== 0) throw new Error(`oxlint could not list its rules or print the config: ${printed.stderr}`);
1526
+ const typeAwareRules = new Set(JSON.parse(ruleList.stdout).filter((rule) => rule.type_aware === true).map((rule) => `${rule.scope}/${rule.value}`));
1527
+ const config = typeAwareOnlyConfig(JSON.parse(printed.stdout), typeAwareRules);
1528
+ if (config == null) return void 0;
1529
+ const created = [];
1530
+ const replacements = /* @__PURE__ */ new Map();
1531
+ const configDir = fs.mkdtempSync(path.join(os.tmpdir(), "oxlint-vize-type-aware-"));
1532
+ const cleanup = () => {
1533
+ for (const file of created) fs.rmSync(file, { force: true });
1534
+ fs.rmSync(configDir, {
1535
+ force: true,
1536
+ recursive: true
1537
+ });
1538
+ };
1539
+ process.once("exit", cleanup);
1540
+ try {
1541
+ for (const file of sfcFiles) {
1542
+ const shim = buildScriptShim(fs.readFileSync(file, "utf8"));
1543
+ if (shim == null) continue;
1544
+ const shimPath = `${file}${shim.extension}`;
1545
+ if (fs.existsSync(shimPath)) continue;
1546
+ fs.writeFileSync(shimPath, shim.text, { flag: "wx" });
1547
+ created.push(shimPath);
1548
+ registerPathReplacementVariants(replacements, cwd, shimPath, file);
1549
+ }
1550
+ if (created.length === 0) return void 0;
1551
+ const configPath = path.join(configDir, ".oxlintrc.json");
1552
+ fs.writeFileSync(configPath, JSON.stringify(config));
1553
+ const result = await run([
1554
+ entrypoint,
1555
+ ...passOptions(options),
1556
+ "-c",
1557
+ configPath,
1558
+ "--type-aware",
1559
+ ...created
1560
+ ]);
1561
+ const stdout = rewriteReportedPaths(result.stdout, replacements);
1562
+ const stderr = rewriteReportedPaths(result.stderr, replacements);
1563
+ return {
1564
+ ...result,
1565
+ stdout,
1566
+ stderr,
1567
+ rawStdout: void 0,
1568
+ rawStderr: void 0
1569
+ };
1570
+ } finally {
1571
+ cleanup();
1572
+ process.removeListener("exit", cleanup);
1573
+ }
1574
+ }
1575
+ function keepConfigSelection(options) {
1576
+ const kept = [];
1577
+ for (let index = 0; index < options.length; index += 1) {
1578
+ const option = options[index];
1579
+ if (option === "-c" || option === "--config" || option === "--tsconfig") kept.push(option, options[++index]);
1580
+ else if (option.startsWith("--config=") || option.startsWith("--tsconfig=")) kept.push(option);
1581
+ else if (option === "--disable-nested-config") kept.push(option);
1582
+ }
1583
+ return kept;
1584
+ }
1585
+ function passOptions(options) {
1586
+ const kept = [];
1587
+ for (let index = 0; index < options.length; index += 1) {
1588
+ const option = options[index];
1589
+ if (option === "--") break;
1590
+ const name = option.includes("=") ? option.slice(0, option.indexOf("=")) : option;
1591
+ if (DROPPED_WITH_VALUE.has(name)) {
1592
+ if (!option.includes("=")) index += 1;
1593
+ continue;
1594
+ }
1595
+ if (DROPPED_FLAGS.has(name)) continue;
1596
+ kept.push(option);
1597
+ }
1598
+ return kept;
1599
+ }
1600
+ function stripJsonComments(text) {
1601
+ let result = "";
1602
+ let inString = false;
1603
+ for (let index = 0; index < text.length; index += 1) {
1604
+ const char = text[index];
1605
+ if (inString) {
1606
+ result += char;
1607
+ if (char === "\\") result += text[++index] ?? "";
1608
+ else if (char === "\"") inString = false;
1609
+ } else if (char === "\"") {
1610
+ inString = true;
1611
+ result += char;
1612
+ } else if (char === "/" && text[index + 1] === "/") {
1613
+ while (index < text.length && text[index] !== "\n") index += 1;
1614
+ result += "\n";
1615
+ } else if (char === "/" && text[index + 1] === "*") {
1616
+ index = text.indexOf("*/", index + 2);
1617
+ if (index === -1) break;
1618
+ index += 1;
1619
+ } else result += char;
1620
+ }
1621
+ return result;
1622
+ }
1623
+ //#endregion
1397
1624
  //#region src/cli.ts
1398
1625
  async function main() {
1399
1626
  const cwd = process.cwd();
@@ -1440,6 +1667,7 @@ async function main() {
1440
1667
  } catch (error) {
1441
1668
  merged = unavailableHtml(merged, error);
1442
1669
  }
1670
+ if (requestsTypeAware(forwardedArgs, cwd)) merged = await withVueTypeAwarePass(merged, cwd, oxlintEntrypoint, forwardedArgs, vueFiles);
1443
1671
  } catch (error) {
1444
1672
  if (transport?.originalResult) {
1445
1673
  process.exitCode = Math.max(transport.originalResult.status ?? 1, result?.status ?? 1, 1);
@@ -1471,6 +1699,21 @@ main().catch((error) => {
1471
1699
  process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
1472
1700
  process.exitCode = Math.max(Number(process.exitCode) || 1, 1);
1473
1701
  });
1702
+ /** Adds oxlint's type-aware rules on Vue script blocks; a failure is reported, never hidden. */
1703
+ async function withVueTypeAwarePass(merged, cwd, entrypoint, forwardedArgs, vueFiles) {
1704
+ try {
1705
+ const pass = await runVueTypeAwarePass(cwd, entrypoint, forwardedArgs, vueFiles, (args) => runOxlint(process.execPath, args, cwd));
1706
+ return pass ? mergeProjectOutput(merged, pass, forwardedArgs) : merged;
1707
+ } catch (error) {
1708
+ const notice = `\n[oxlint-plugin-vize] Type-aware rules could not run on Vue script blocks: ${error instanceof Error ? error.message : String(error)}\n`;
1709
+ return {
1710
+ ...merged,
1711
+ stderr: merged.stderr + notice,
1712
+ rawStderr: void 0,
1713
+ status: Math.max(merged.status ?? 1, 1)
1714
+ };
1715
+ }
1716
+ }
1474
1717
  function writeStream(stream, text) {
1475
1718
  return new Promise((resolve, reject) => {
1476
1719
  stream.write(text, (error) => {
@@ -0,0 +1,40 @@
1
+ //#region src/model.d.ts
2
+ type HelpLevel = "none" | "short" | "full";
3
+ type PatinaPreset = "general-recommended" | "essential" | "ecosystem" | "incremental" | "opinionated" | "nuxt";
4
+ interface PatinaSettings {
5
+ locale?: string;
6
+ helpLevel?: HelpLevel;
7
+ preset?: PatinaPreset;
8
+ typeAware?: boolean;
9
+ corsaPath?: string;
10
+ /** Rules to evaluate together. Oxlint's own rules still control reporting. */
11
+ rules?: readonly string[] | Record<string, unknown>;
12
+ }
13
+ //#endregion
14
+ //#region src/configs.d.ts
15
+ type OxlintRuleSeverity = "error" | "warn" | "off";
16
+ type OxlintRuleEntry = OxlintRuleSeverity | [OxlintRuleSeverity, ...unknown[]];
17
+ type OxlintRuleConfig = Record<string, OxlintRuleEntry>;
18
+ type VizeRuleConfigPreset = Exclude<PatinaPreset, "incremental"> | "happy-path" | "all";
19
+ type VizeRuleConfigPresetInput = VizeRuleConfigPreset | readonly VizeRuleConfigPreset[];
20
+ interface VizeRuleConfigOptions {
21
+ includeTypeAware?: boolean;
22
+ preset?: VizeRuleConfigPresetInput;
23
+ presets?: readonly VizeRuleConfigPreset[];
24
+ }
25
+ declare function createVizeRuleConfig(options?: VizeRuleConfigOptions): OxlintRuleConfig;
26
+ declare const configs: {
27
+ readonly all: OxlintRuleConfig;
28
+ readonly ecosystem: OxlintRuleConfig;
29
+ readonly ecosystemWithTypeAware: OxlintRuleConfig;
30
+ readonly essential: OxlintRuleConfig;
31
+ readonly happyPath: OxlintRuleConfig;
32
+ readonly happyPathWithTypeAware: OxlintRuleConfig;
33
+ readonly nuxt: OxlintRuleConfig;
34
+ readonly opinionated: OxlintRuleConfig;
35
+ readonly opinionatedWithTypeAware: OxlintRuleConfig;
36
+ readonly recommended: OxlintRuleConfig;
37
+ readonly recommendedWithTypeAware: OxlintRuleConfig;
38
+ };
39
+ //#endregion
40
+ export { VizeRuleConfigPreset as a, createVizeRuleConfig as c, VizeRuleConfigOptions as i, PatinaPreset as l, OxlintRuleEntry as n, VizeRuleConfigPresetInput as o, OxlintRuleSeverity as r, configs as s, OxlintRuleConfig as t, PatinaSettings as u };