oxlint-plugin-vize 0.389.0 → 0.391.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
@@ -12,8 +12,8 @@ This package lets Oxlint execute Patina through Vize's native binding while stil
12
12
 
13
13
  - Runs Vize Patina rules inside Oxlint as `vize/*` diagnostics, so Vue-specific findings can live beside Oxlint core rules in one command.
14
14
  - Keeps Oxlint's existing rules and built-in `vue` plugin active. The bridge adds Vize rules; it does not replace `eqeqeq`, `no-console`, or your existing `vue/*` setup.
15
- - Ships preset rule maps for JS/TS Oxlint configs: `configs.recommended`, `configs.essential`, `configs.ecosystem`, `configs.opinionated`, `configs.nuxt`, `configs.all`, and type-aware opt-in variants.
16
- - Ships `createVizeLintConfig()` for the Vite+ `lint` block in `vite.config.ts`, which is the only Oxlint configuration `vp lint` and `vp check` read.
15
+ - Ships preset rule maps for JS/TS Oxlint configs: `configs.recommended`, `configs.happyPath`, `configs.essential`, `configs.ecosystem`, `configs.opinionated`, `configs.nuxt`, `configs.all`, and type-aware opt-in variants.
16
+ - Ships `createVizeLintConfig()`, `flatConfigs`, and `defineVizeLintConfig()` for the Vite+ `lint` block in `vite.config.ts`, which is the only Oxlint configuration `vp lint` and `vp check` read.
17
17
  - Supports runtime settings through `settings.vize`, including `locale`, `preset`, and `helpLevel`.
18
18
  - Provides the `oxlint-vize` CLI wrapper, which runs Oxlint with a scriptless-SFC workaround and rewrites temporary paths back to the original `.vue` files.
19
19
  - Resolves Vize native bindings through platform-specific optional dependencies, so published installs do not need a separate `@vizejs/native` package.
@@ -63,7 +63,7 @@ Which file you configure depends on which command you run.
63
63
 
64
64
  ### With `vp lint` (Vite+)
65
65
 
66
- `createVizeLintConfig()` returns the whole `lint` block, so the `jsPlugins` entry cannot go missing:
66
+ `createVizeLintConfig()` returns a complete `lint` block, so the `jsPlugins` entry cannot go missing. The default preset is `"happy-path"`/`"general-recommended"`: use it when you want a safe Vue baseline without taking a position on stronger style or framework choices.
67
67
 
68
68
  ```ts
69
69
  // vite.config.ts
@@ -72,7 +72,7 @@ import { createVizeLintConfig } from "oxlint-plugin-vize";
72
72
 
73
73
  export default defineConfig({
74
74
  lint: createVizeLintConfig({
75
- preset: "essential",
75
+ preset: "happy-path",
76
76
  rules: {
77
77
  "no-console": "warn",
78
78
  },
@@ -83,12 +83,18 @@ export default defineConfig({
83
83
  });
84
84
  ```
85
85
 
86
- `preset` drives both the emitted rule map and `settings.vize.preset`, so the two can never disagree.
87
- That matters because the bridge silently drops any `vize/*` rule outside the active preset: listing
88
- `vize/ecosystem/router-link-require-to` while the active preset is `general-recommended` reports
89
- nothing at all. `createVizeLintConfig` throws for that case, and for unknown `vize/*` ids, instead of
90
- leaving you with a config that looks enabled and reports nothing. Use `preset: "incremental"` when
91
- you want only the rules you list, and `preset: "all"` for every bundle at once.
86
+ `preset` drives both the emitted rule map and `settings.vize.preset` when a single bundle is
87
+ selected, so the two can never disagree. That matters because the bridge silently drops any `vize/*`
88
+ rule outside the active preset: listing `vize/ecosystem/router-link-require-to` while the active
89
+ preset is `general-recommended` reports nothing at all. `createVizeLintConfig` throws for that case,
90
+ and for unknown `vize/*` ids, instead of leaving you with a config that looks enabled and reports
91
+ nothing.
92
+
93
+ Use `preset: "incremental"` when you want only the rules you list, `preset: "all"` for every bundle
94
+ at once, and `preset: ["happy-path", "ecosystem"]` or `presets: ["happy-path", "ecosystem"]` when
95
+ you want several bundles without taking on all of them. Multiple-bundle configs emit
96
+ `settings.vize.preset: "incremental"` so the runtime gate cannot suppress one selected bundle's
97
+ rules while another bundle is active.
92
98
 
93
99
  The emitted block always enables Oxlint's built-in `vue` plugin. Pass `plugins` to keep the rest of
94
100
  your project's plugin list; they are merged with `vue`, never replaced, because narrowing the list
@@ -106,6 +112,27 @@ export default defineConfig({
106
112
  });
107
113
  ```
108
114
 
115
+ For Flat Config-style composition in `vite.config.ts`, use spreadable fragments and collapse them
116
+ back to Vite+'s object-shaped `lint` config:
117
+
118
+ ```ts
119
+ import { defineConfig } from "vite-plus";
120
+ import { defineVizeLintConfig, flatConfigs } from "oxlint-plugin-vize";
121
+
122
+ export default defineConfig({
123
+ lint: defineVizeLintConfig(...flatConfigs.recommended, ...flatConfigs.ecosystem, {
124
+ ignorePatterns: ["dist/**"],
125
+ rules: {
126
+ "no-console": "warn",
127
+ },
128
+ }),
129
+ });
130
+ ```
131
+
132
+ The exported fragments include `flatConfigs.recommended`, `flatConfigs.happyPath`,
133
+ `flatConfigs.essential`, `flatConfigs.ecosystem`, `flatConfigs.nuxt`, `flatConfigs.opinionated`,
134
+ and `flatConfigs.all`, plus the same `*WithTypeAware` variants as `configs`.
135
+
109
136
  ### With `oxlint` or `oxlint-vize`
110
137
 
111
138
  Enable Oxlint's built-in `vue` plugin as well as this JS plugin:
@@ -144,7 +171,7 @@ export default {
144
171
  };
145
172
  ```
146
173
 
147
- `configs.recommended`, `configs.essential`, `configs.ecosystem`, `configs.opinionated`, `configs.nuxt`, and `configs.all` intentionally skip Vize's unstable type-aware rules for now. If you explicitly want those experimental rules too, use `configs.recommendedWithTypeAware`, `configs.ecosystemWithTypeAware`, `configs.opinionatedWithTypeAware`, or `createVizeRuleConfig({ includeTypeAware: true, preset: ... })`. Set `settings.vize.typeAware: true` to run the shared full-file Patina pass with Corsa enabled; explicitly configured `vize/type/*` rules also opt in when they are queried one by one.
174
+ `configs.recommended`, `configs.happyPath`, `configs.essential`, `configs.ecosystem`, `configs.opinionated`, `configs.nuxt`, and `configs.all` intentionally skip Vize's unstable type-aware rules for now. If you explicitly want those experimental rules too, use `configs.recommendedWithTypeAware`, `configs.happyPathWithTypeAware`, `configs.ecosystemWithTypeAware`, `configs.opinionatedWithTypeAware`, or `createVizeRuleConfig({ includeTypeAware: true, preset: ... })`. Set `settings.vize.typeAware: true` to run the shared full-file Patina pass with Corsa enabled; explicitly configured `vize/type/*` rules also opt in when they are queried one by one.
148
175
 
149
176
  You can pass Patina settings through `settings.vize`:
150
177
 
@@ -161,11 +188,12 @@ You can pass Patina settings through `settings.vize`:
161
188
  }
162
189
  ```
163
190
 
164
- - `preset` accepts `"general-recommended"`, `"essential"`, `"ecosystem"`, `"incremental"`, `"opinionated"`, or `"nuxt"`.
191
+ - `preset` accepts `"general-recommended"`/`"happy-path"`, `"essential"`, `"ecosystem"`, `"incremental"`, `"opinionated"`, `"nuxt"`, or `"all"`.
165
192
  - `preset` defaults to `"general-recommended"`.
166
193
  - Bundle presets keep out-of-bundle rules quiet even if they are still listed in `rules`.
167
194
  - `"incremental"` skips bundle gating and runs only the Vize rules you explicitly configure in Oxlint.
168
- - `"ecosystem"` enables Vize's Vue Router, Vue I18n, Pinia, Vue Test Utils, Nuxt, and Void Vue rules without taking on the full opinionated preset.
195
+ - `"all"` is accepted as a settings alias for `"incremental"`; use `configs.all` or `createVizeLintConfig({ preset: "all" })` when you also want every rule emitted.
196
+ - `"ecosystem"` enables Vize's Vue Router, Vue I18n, Pinia, Vue Test Utils, and Void Vue rules without taking on the full opinionated preset.
169
197
  - `"opinionated"` is the preset that enables Vize's built-in script rules such as `vize/script/no-options-api`.
170
198
  - Legacy aliases such as `"GeneralRecommended"`, `"Essential"`, `"Ecosystem"`, `"Incremental"`, `"Opinionated"`, `"Nuxt"`, and `"happy-path"` are still accepted for compatibility.
171
199
  - `helpLevel` accepts `"full"`, `"short"`, or `"none"`. Short help keeps one actionable sentence and removes inline examples and trailing rationale.
package/dist/index.d.mts CHANGED
@@ -16,10 +16,12 @@ interface PatinaSettings {
16
16
  type OxlintRuleSeverity = "error" | "warn" | "off";
17
17
  type OxlintRuleEntry = OxlintRuleSeverity | [OxlintRuleSeverity, ...unknown[]];
18
18
  type OxlintRuleConfig = Record<string, OxlintRuleEntry>;
19
- type VizeRuleConfigPreset = Exclude<PatinaPreset, "incremental"> | "all";
19
+ type VizeRuleConfigPreset = Exclude<PatinaPreset, "incremental"> | "happy-path" | "all";
20
+ type VizeRuleConfigPresetInput = VizeRuleConfigPreset | readonly VizeRuleConfigPreset[];
20
21
  interface VizeRuleConfigOptions {
21
22
  includeTypeAware?: boolean;
22
- preset?: VizeRuleConfigPreset;
23
+ preset?: VizeRuleConfigPresetInput;
24
+ presets?: readonly VizeRuleConfigPreset[];
23
25
  }
24
26
  declare function createVizeRuleConfig(options?: VizeRuleConfigOptions): OxlintRuleConfig;
25
27
  declare const configs: {
@@ -27,6 +29,8 @@ declare const configs: {
27
29
  readonly ecosystem: OxlintRuleConfig;
28
30
  readonly ecosystemWithTypeAware: OxlintRuleConfig;
29
31
  readonly essential: OxlintRuleConfig;
32
+ readonly happyPath: OxlintRuleConfig;
33
+ readonly happyPathWithTypeAware: OxlintRuleConfig;
30
34
  readonly nuxt: OxlintRuleConfig;
31
35
  readonly opinionated: OxlintRuleConfig;
32
36
  readonly opinionatedWithTypeAware: OxlintRuleConfig;
@@ -47,11 +51,12 @@ type VitePlusLintPlugin = "eslint" | "import" | "jest" | "jsdoc" | "jsx-a11y" |
47
51
  /**
48
52
  * Rule bundles `createVizeLintConfig` accepts.
49
53
  *
50
- * `"all"` and `"incremental"` both disable preset gating in the bridge, which is
51
- * what makes them usable: `"all"` needs every bundle's rules to run at once, and
52
- * `"incremental"` needs only the rules the caller lists to run.
54
+ * `"all"`, `"incremental"`, and multiple-preset selections disable preset
55
+ * gating in the bridge. That is what makes them usable: the runtime only accepts
56
+ * one preset, while those shapes need exactly the emitted rule map to run.
53
57
  */
54
- type VizeLintPreset = PatinaPreset | "all";
58
+ type VizeLintPreset = PatinaPreset | "happy-path" | "all";
59
+ type VizeLintPresetInput = VizeLintPreset | readonly VizeLintPreset[];
55
60
  interface VizeLintConfigOptions {
56
61
  /**
57
62
  * Include Vize's unstable `vize/type/*` rules in the preset rule map. Off by
@@ -68,30 +73,38 @@ interface VizeLintConfigOptions {
68
73
  */
69
74
  plugins?: readonly VitePlusLintPlugin[];
70
75
  /**
71
- * Rule bundle to enable. Defaults to `"general-recommended"`, the bridge's own
72
- * default. `"incremental"` emits no preset rules, so only `rules` run.
76
+ * Rule bundle or bundles to enable. Defaults to `"general-recommended"`, the
77
+ * bridge's own default. `"incremental"` emits no preset rules, so only `rules`
78
+ * run.
73
79
  */
74
- preset?: VizeLintPreset;
80
+ preset?: VizeLintPresetInput;
81
+ /**
82
+ * Additional rule bundles to enable alongside `preset`. This is equivalent to
83
+ * passing an array to `preset`, but reads better when the base preset is kept
84
+ * separate from project-specific add-ons.
85
+ */
86
+ presets?: readonly VizeLintPreset[];
75
87
  /**
76
88
  * Extra Oxlint rules merged after the preset rules. Core Oxlint rule ids pass
77
89
  * through untouched; `vize/*` ids are validated against the rules the native
78
- * bridge registers and against the resolved preset.
90
+ * bridge registers and, for a single gated preset, against the resolved preset.
79
91
  */
80
92
  rules?: OxlintRuleConfig;
81
93
  /**
82
94
  * Patina runtime settings forwarded through `settings.vize`. `preset` is
83
- * intentionally absent: it is derived from `options.preset` so the rule map and
84
- * the bridge's runtime gate can never disagree.
95
+ * intentionally absent: it is derived from `options.preset`/`options.presets`
96
+ * so the rule map and the bridge's runtime gate can never disagree.
85
97
  */
86
98
  settings?: Omit<PatinaSettings, "preset">;
87
99
  }
88
- interface VizeLintConfig {
100
+ interface VizeLintConfigSettings extends Record<string, unknown> {
101
+ vize: PatinaSettings;
102
+ }
103
+ interface VizeLintConfig extends Record<string, unknown> {
89
104
  jsPlugins: string[];
90
105
  plugins: VitePlusLintPlugin[];
91
106
  rules: OxlintRuleConfig;
92
- settings: {
93
- vize: PatinaSettings;
94
- };
107
+ settings: VizeLintConfigSettings;
95
108
  }
96
109
  /**
97
110
  * Builds the Oxlint configuration block that runs Patina through this bridge.
@@ -119,14 +132,57 @@ interface VizeLintConfig {
119
132
  * - An unknown `vize/*` id only produces Oxlint's
120
133
  * `Rule '...' not found in plugin 'vize'` error when Oxlint actually reads the
121
134
  * config. A typo in a config Oxlint never reads reports nothing at all.
122
- * - A `vize/*` id that is outside the active preset is dropped by the bridge's
123
- * runtime preset gate (`plugin.ts`), so it stays listed in `rules` and reports
124
- * nothing. `preset: "incremental"` is the supported way to run an arbitrary
125
- * subset.
135
+ * - A `vize/*` id that is outside a single active preset is dropped by the
136
+ * bridge's runtime preset gate (`plugin.ts`), so it stays listed in `rules` and
137
+ * reports nothing. `preset: "incremental"` is the supported way to run an
138
+ * arbitrary subset.
126
139
  *
127
140
  * @throws {Error} When `options.rules` names a `vize/*` id the native bridge does
128
- * not register, or one the resolved preset would silently suppress.
141
+ * not register, or one a single resolved preset would silently suppress.
129
142
  */
130
143
  declare function createVizeLintConfig(options?: VizeLintConfigOptions): VizeLintConfig;
131
144
  //#endregion
132
- export { type OxlintRuleConfig, type OxlintRuleEntry, type OxlintRuleSeverity, VIZE_JS_PLUGIN_SPECIFIER, type VitePlusLintPlugin, type VizeLintConfig, type VizeLintConfigOptions, type VizeLintPreset, type VizeRuleConfigOptions, configs, createVizeLintConfig, createVizeRuleConfig, _default as default };
145
+ //#region src/vite-plus-flat-config.d.ts
146
+ interface VizeLintConfigFragment extends Record<string, unknown> {
147
+ extends?: readonly (VizeLintConfigFragment | VizeLintFlatConfig)[];
148
+ ignorePatterns?: readonly string[];
149
+ jsPlugins?: readonly string[];
150
+ overrides?: readonly unknown[];
151
+ plugins?: readonly VitePlusLintPlugin[];
152
+ rules?: VizeLintConfig["rules"];
153
+ settings?: Record<string, unknown> & {
154
+ vize?: PatinaSettings | Record<string, unknown>;
155
+ };
156
+ }
157
+ type VizeLintFlatConfig = readonly VizeLintConfigFragment[];
158
+ /**
159
+ * Returns spreadable Oxlint/Vite+ config fragments for Flat Config style use.
160
+ *
161
+ * The fragments intentionally set `settings.vize.preset` to `"incremental"` so
162
+ * multiple fragments can be composed without one preset's runtime gate
163
+ * suppressing rules emitted by another fragment.
164
+ */
165
+ declare function createVizeLintFlatConfig(options?: VizeLintConfigOptions): VizeLintFlatConfig;
166
+ declare const flatConfigs: {
167
+ all: VizeLintFlatConfig;
168
+ ecosystem: VizeLintFlatConfig;
169
+ ecosystemWithTypeAware: VizeLintFlatConfig;
170
+ essential: VizeLintFlatConfig;
171
+ happyPath: VizeLintFlatConfig;
172
+ happyPathWithTypeAware: VizeLintFlatConfig;
173
+ nuxt: VizeLintFlatConfig;
174
+ opinionated: VizeLintFlatConfig;
175
+ opinionatedWithTypeAware: VizeLintFlatConfig;
176
+ recommended: VizeLintFlatConfig;
177
+ recommendedWithTypeAware: VizeLintFlatConfig;
178
+ };
179
+ /**
180
+ * Collapses Flat Config-style fragments into the object shape Vite+ accepts.
181
+ *
182
+ * `vp lint` currently expects a single Oxlint config object under `lint`, so this
183
+ * helper lets `vite.config.ts` keep a flat, spread-friendly authoring style while
184
+ * still handing Vite+ the object it can read.
185
+ */
186
+ declare function defineVizeLintConfig(...entries: readonly (VizeLintConfigFragment | VizeLintFlatConfig)[]): VizeLintConfig;
187
+ //#endregion
188
+ export { type OxlintRuleConfig, type OxlintRuleEntry, type OxlintRuleSeverity, VIZE_JS_PLUGIN_SPECIFIER, type VitePlusLintPlugin, type VizeLintConfig, type VizeLintConfigFragment, type VizeLintConfigOptions, type VizeLintConfigSettings, type VizeLintFlatConfig, type VizeLintPreset, type VizeLintPresetInput, type VizeRuleConfigOptions, type VizeRuleConfigPreset, type VizeRuleConfigPresetInput, configs, createVizeLintConfig, createVizeLintFlatConfig, createVizeRuleConfig, _default as default, defineVizeLintConfig, flatConfigs };
package/dist/index.mjs CHANGED
@@ -194,10 +194,10 @@ const PRESET_ALIASES = new Map([
194
194
  ["ecosystem", "ecosystem"],
195
195
  ["eco", "ecosystem"],
196
196
  ["incremental", "incremental"],
197
+ ["all", "incremental"],
197
198
  ["opinionated", "opinionated"],
198
199
  ["opnionated", "opinionated"],
199
200
  ["strict", "opinionated"],
200
- ["all", "opinionated"],
201
201
  ["nuxt", "nuxt"]
202
202
  ]);
203
203
  function getVizeSettings(context) {
@@ -271,6 +271,7 @@ if (import.meta.vitest) {
271
271
  it("normalizes preset aliases", () => {
272
272
  expect(parseVizeSettings({ preset: "recommended" })).toEqual({ preset: "general-recommended" });
273
273
  expect(parseVizeSettings({ preset: "incremental" })).toEqual({ preset: "incremental" });
274
+ expect(parseVizeSettings({ preset: "all" })).toEqual({ preset: "incremental" });
274
275
  expect(parseVizeSettings({ preset: "ecosystem" })).toEqual({ preset: "ecosystem" });
275
276
  expect(parseVizeSettings({ preset: "strict" })).toEqual({ preset: "opinionated" });
276
277
  expect(parseVizeSettings({ preset: "Opnionated" })).toEqual({ preset: "opinionated" });
@@ -583,11 +584,11 @@ var plugin_default = definePlugin({
583
584
  //#region src/configs.ts
584
585
  const TYPE_AWARE_RULE_PREFIX = "type/";
585
586
  function createVizeRuleConfig(options = {}) {
586
- const preset = options.preset ?? "general-recommended";
587
+ const presets = resolveRuleConfigPresets(options);
587
588
  const includeTypeAware = options.includeTypeAware ?? false;
588
589
  const rules = {};
589
590
  for (const ruleMeta of getPatinaRules()) {
590
- if (!matchesPreset(ruleMeta, preset)) continue;
591
+ if (!matchesPresets(ruleMeta, presets)) continue;
591
592
  if (!includeTypeAware && isTypeAwareRule(ruleMeta)) continue;
592
593
  rules[`vize/${ruleMeta.name}`] = toOxlintSeverity(ruleMeta.defaultSeverity);
593
594
  }
@@ -601,6 +602,11 @@ const configs = {
601
602
  preset: "ecosystem"
602
603
  }),
603
604
  essential: createVizeRuleConfig({ preset: "essential" }),
605
+ happyPath: createVizeRuleConfig({ preset: "happy-path" }),
606
+ happyPathWithTypeAware: createVizeRuleConfig({
607
+ includeTypeAware: true,
608
+ preset: "happy-path"
609
+ }),
604
610
  nuxt: createVizeRuleConfig({ preset: "nuxt" }),
605
611
  opinionated: createVizeRuleConfig({ preset: "opinionated" }),
606
612
  opinionatedWithTypeAware: createVizeRuleConfig({
@@ -613,8 +619,18 @@ const configs = {
613
619
  preset: "general-recommended"
614
620
  })
615
621
  };
616
- function matchesPreset(ruleMeta, preset) {
617
- return preset === "all" || ruleMeta.presets.includes(preset);
622
+ function normalizeRuleConfigPreset(preset) {
623
+ return preset === "happy-path" ? "general-recommended" : preset;
624
+ }
625
+ function resolveRuleConfigPresets(options) {
626
+ const requestedPresets = [...toPresetArray$1((options.preset === void 0 && options.presets === void 0 ? "general-recommended" : options.preset) ?? []), ...options.presets ?? []].map(normalizeRuleConfigPreset);
627
+ return [...new Set(requestedPresets)];
628
+ }
629
+ function toPresetArray$1(preset) {
630
+ return Array.isArray(preset) ? preset : [preset];
631
+ }
632
+ function matchesPresets(ruleMeta, presets) {
633
+ return presets.includes("all") || presets.some((preset) => preset !== "all" && ruleMeta.presets.includes(preset));
618
634
  }
619
635
  function isTypeAwareRule(ruleMeta) {
620
636
  return ruleMeta.name.startsWith(TYPE_AWARE_RULE_PREFIX);
@@ -628,10 +644,41 @@ if (import.meta.vitest) {
628
644
  it("normalizes Vize warning severity to Oxlint's warn", () => {
629
645
  expect(configs.recommended["vize/vue/no-multi-spaces"]).toBe("warn");
630
646
  });
647
+ it("accepts the CLI happy-path preset alias", () => {
648
+ expect(createVizeRuleConfig({ preset: "happy-path" })).toEqual(configs.recommended);
649
+ expect(configs.happyPath).toEqual(configs.recommended);
650
+ });
651
+ it("unions multiple presets", () => {
652
+ const config = createVizeRuleConfig({ presets: ["happy-path", "ecosystem"] });
653
+ expect(config["vize/script/valid-define-props"]).toBe("error");
654
+ expect(config["vize/ecosystem/router-link-require-to"]).toBe("error");
655
+ expect(config["vize/script/no-options-api"]).toBeUndefined();
656
+ });
657
+ it("accepts multiple presets through the preset option", () => {
658
+ expect(createVizeRuleConfig({ preset: ["happy-path", "ecosystem"] })).toEqual(createVizeRuleConfig({ presets: ["happy-path", "ecosystem"] }));
659
+ });
631
660
  it("filters rules by preset", () => {
632
661
  expect(configs.essential["vize/vue/require-v-for-key"]).toBe("error");
633
662
  expect(configs.essential["vize/vue/require-scoped-style"]).toBeUndefined();
634
663
  });
664
+ it("enables script correctness rules in essential and happy-path", () => {
665
+ expect(configs.essential["vize/script/valid-define-props"]).toBe("error");
666
+ expect(configs.essential["vize/script/no-import-compiler-macros"]).toBe("error");
667
+ expect(configs.recommended["vize/script/valid-define-props"]).toBe("error");
668
+ expect(configs.recommended["vize/script/no-import-compiler-macros"]).toBe("error");
669
+ });
670
+ it("keeps low-noise happy-path script warnings out of essential", () => {
671
+ expect(configs.recommended["vize/script/no-duplicate-attr-inheritance"]).toBe("warn");
672
+ expect(configs.recommended["vize/script/no-reactive-destructure"]).toBeUndefined();
673
+ expect(configs.essential["vize/script/no-duplicate-attr-inheritance"]).toBeUndefined();
674
+ expect(configs.recommended["vize/script/no-unused-emit-declarations"]).toBeUndefined();
675
+ expect(configs.essential["vize/script/no-unused-emit-declarations"]).toBeUndefined();
676
+ });
677
+ it("keeps opinionated script style out of happy-path", () => {
678
+ expect(configs.recommended["vize/script/no-options-api"]).toBeUndefined();
679
+ expect(configs.recommended["vize/script/define-props-declaration"]).toBeUndefined();
680
+ expect(configs.opinionated["vize/script/no-options-api"]).toBe("error");
681
+ });
635
682
  it("skips unstable type-aware rules by default", () => {
636
683
  expect(configs.opinionated["vize/type/require-typed-props"]).toBeUndefined();
637
684
  expect(configs.opinionatedWithTypeAware["vize/type/require-typed-props"]).toBe("warn");
@@ -677,6 +724,7 @@ if (import.meta.vitest) {
677
724
  */
678
725
  const VIZE_JS_PLUGIN_SPECIFIER = "oxlint-plugin-vize";
679
726
  const VIZE_RULE_PREFIX = "vize/";
727
+ const DEFAULT_VIZE_LINT_PRESET = "general-recommended";
680
728
  /**
681
729
  * Builds the Oxlint configuration block that runs Patina through this bridge.
682
730
  *
@@ -703,55 +751,74 @@ const VIZE_RULE_PREFIX = "vize/";
703
751
  * - An unknown `vize/*` id only produces Oxlint's
704
752
  * `Rule '...' not found in plugin 'vize'` error when Oxlint actually reads the
705
753
  * config. A typo in a config Oxlint never reads reports nothing at all.
706
- * - A `vize/*` id that is outside the active preset is dropped by the bridge's
707
- * runtime preset gate (`plugin.ts`), so it stays listed in `rules` and reports
708
- * nothing. `preset: "incremental"` is the supported way to run an arbitrary
709
- * subset.
754
+ * - A `vize/*` id that is outside a single active preset is dropped by the
755
+ * bridge's runtime preset gate (`plugin.ts`), so it stays listed in `rules` and
756
+ * reports nothing. `preset: "incremental"` is the supported way to run an
757
+ * arbitrary subset.
710
758
  *
711
759
  * @throws {Error} When `options.rules` names a `vize/*` id the native bridge does
712
- * not register, or one the resolved preset would silently suppress.
760
+ * not register, or one a single resolved preset would silently suppress.
713
761
  */
714
762
  function createVizeLintConfig(options = {}) {
715
- const preset = options.preset ?? "general-recommended";
763
+ return buildVizeLintConfig(options, { forceIncrementalRuntime: false });
764
+ }
765
+ function buildVizeLintConfig(options, buildOptions) {
766
+ const presets = resolveLintPresets(options);
767
+ const runtimePreset = buildOptions.forceIncrementalRuntime ? "incremental" : toRuntimePreset(presets);
716
768
  const extraRules = options.rules ?? {};
717
- assertUsableVizeRules(extraRules, preset);
769
+ assertUsableVizeRules(extraRules, presets, runtimePreset);
718
770
  return {
719
771
  jsPlugins: [VIZE_JS_PLUGIN_SPECIFIER],
720
772
  plugins: [...new Set(["vue", ...options.plugins ?? []])],
721
773
  rules: {
722
- ...createPresetRules(preset, options.includeTypeAware),
774
+ ...createPresetRules(presets, options.includeTypeAware),
723
775
  ...extraRules
724
776
  },
725
777
  settings: { vize: {
726
778
  ...options.settings,
727
- preset: toRuntimePreset(preset)
779
+ preset: runtimePreset
728
780
  } }
729
781
  };
730
782
  }
731
- function createPresetRules(preset, includeTypeAware) {
732
- return preset === "incremental" ? {} : createVizeRuleConfig({
783
+ function createPresetRules(presets, includeTypeAware) {
784
+ const ruleConfigPresets = presets.filter(isRuleConfigPreset);
785
+ if (ruleConfigPresets.length === 0) return {};
786
+ return createVizeRuleConfig({
733
787
  includeTypeAware,
734
- preset
788
+ presets: ruleConfigPresets
735
789
  });
736
790
  }
791
+ function resolveLintPresets(options) {
792
+ const requestedPresets = [...toPresetArray((options.preset === void 0 && options.presets === void 0 ? DEFAULT_VIZE_LINT_PRESET : options.preset) ?? []), ...options.presets ?? []].map(normalizeLintPreset);
793
+ return [...new Set(requestedPresets)];
794
+ }
795
+ function toPresetArray(preset) {
796
+ return Array.isArray(preset) ? preset : [preset];
797
+ }
798
+ function normalizeLintPreset(preset) {
799
+ return preset === "happy-path" ? DEFAULT_VIZE_LINT_PRESET : preset;
800
+ }
801
+ function isRuleConfigPreset(preset) {
802
+ return preset !== "incremental";
803
+ }
737
804
  /**
738
- * Maps a rule bundle to the `settings.vize.preset` the bridge must run with.
805
+ * Maps rule bundles to the `settings.vize.preset` the bridge must run with.
739
806
  *
740
- * `"all"` becomes `"incremental"` because the bridge gates each rule on preset
741
- * membership; gating an all-bundles rule map by any single preset would suppress
742
- * the rules that only belong to the other bundles.
807
+ * `"all"` and multiple concrete presets become `"incremental"` because the
808
+ * bridge gates each rule on preset membership; gating a combined rule map by any
809
+ * single preset would suppress the rules that only belong to the other bundles.
743
810
  */
744
- function toRuntimePreset(preset) {
745
- return preset === "all" ? "incremental" : preset;
811
+ function toRuntimePreset(presets) {
812
+ if (presets.length === 1 && presets[0] !== "all") return presets[0];
813
+ return "incremental";
746
814
  }
747
- function assertUsableVizeRules(rules, preset) {
815
+ function assertUsableVizeRules(rules, presets, runtimePreset) {
748
816
  const ruleMetaById = new Map(getPatinaRules().map((ruleMeta) => [`${VIZE_RULE_PREFIX}${ruleMeta.name}`, ruleMeta]));
749
817
  const configuredIds = Object.keys(rules).filter((ruleId) => ruleId.startsWith(VIZE_RULE_PREFIX)).sort();
750
818
  const unknownIds = configuredIds.filter((ruleId) => !ruleMetaById.has(ruleId));
751
819
  if (unknownIds.length > 0) throw new Error(`Unknown Vize rule ${pluralizeIds(unknownIds)}: ${unknownIds.join(", ")}. Check the id against the rules oxlint-plugin-vize registers.`);
752
- const runtimePreset = toRuntimePreset(preset);
753
820
  const suppressedIds = configuredIds.filter((ruleId) => isSuppressedByPreset(ruleMetaById.get(ruleId), runtimePreset));
754
- if (suppressedIds.length > 0) throw new Error(`Vize rule ${pluralizeIds(suppressedIds)} outside the "${preset}" preset: ${suppressedIds.join(", ")}. Use preset: "incremental" to run an explicit rule subset, or pick the preset that owns these rules.`);
821
+ if (suppressedIds.length > 0) throw new Error(`Vize rule ${pluralizeIds(suppressedIds)} outside ${describePresetSelection(presets)}: ${suppressedIds.join(", ")}. Use preset: "incremental" to run an explicit rule subset, or pick the preset that owns these rules.`);
755
822
  }
756
823
  /**
757
824
  * Mirrors the runtime gate in `plugin.ts`: rules with no preset membership are
@@ -760,8 +827,139 @@ function assertUsableVizeRules(rules, preset) {
760
827
  function isSuppressedByPreset(ruleMeta, runtimePreset) {
761
828
  return ruleMeta.presets.length > 0 && runtimePreset !== "incremental" && !ruleMeta.presets.includes(runtimePreset);
762
829
  }
830
+ function describePresetSelection(presets) {
831
+ const names = presets.map((preset) => preset === DEFAULT_VIZE_LINT_PRESET ? "happy-path" : preset);
832
+ return `the "${names.length === 0 ? "empty" : names.join(", ")}" ${names.length === 1 ? "preset" : "presets"}`;
833
+ }
763
834
  function pluralizeIds(ids) {
764
835
  return ids.length === 1 ? "id" : "ids";
765
836
  }
766
837
  //#endregion
767
- export { VIZE_JS_PLUGIN_SPECIFIER, configs, createVizeLintConfig, createVizeRuleConfig, plugin_default as default };
838
+ //#region src/vite-plus-flat-config.ts
839
+ /**
840
+ * Returns spreadable Oxlint/Vite+ config fragments for Flat Config style use.
841
+ *
842
+ * The fragments intentionally set `settings.vize.preset` to `"incremental"` so
843
+ * multiple fragments can be composed without one preset's runtime gate
844
+ * suppressing rules emitted by another fragment.
845
+ */
846
+ function createVizeLintFlatConfig(options = {}) {
847
+ return [buildVizeLintConfig(options, { forceIncrementalRuntime: true })];
848
+ }
849
+ const flatConfigs = {
850
+ all: createVizeLintFlatConfig({ preset: "all" }),
851
+ ecosystem: createVizeLintFlatConfig({ preset: "ecosystem" }),
852
+ ecosystemWithTypeAware: createVizeLintFlatConfig({
853
+ includeTypeAware: true,
854
+ preset: "ecosystem"
855
+ }),
856
+ essential: createVizeLintFlatConfig({ preset: "essential" }),
857
+ happyPath: createVizeLintFlatConfig({ preset: "happy-path" }),
858
+ happyPathWithTypeAware: createVizeLintFlatConfig({
859
+ includeTypeAware: true,
860
+ preset: "happy-path"
861
+ }),
862
+ nuxt: createVizeLintFlatConfig({ preset: "nuxt" }),
863
+ opinionated: createVizeLintFlatConfig({ preset: "opinionated" }),
864
+ opinionatedWithTypeAware: createVizeLintFlatConfig({
865
+ includeTypeAware: true,
866
+ preset: "opinionated"
867
+ }),
868
+ recommended: createVizeLintFlatConfig({ preset: "general-recommended" }),
869
+ recommendedWithTypeAware: createVizeLintFlatConfig({
870
+ includeTypeAware: true,
871
+ preset: "general-recommended"
872
+ })
873
+ };
874
+ /**
875
+ * Collapses Flat Config-style fragments into the object shape Vite+ accepts.
876
+ *
877
+ * `vp lint` currently expects a single Oxlint config object under `lint`, so this
878
+ * helper lets `vite.config.ts` keep a flat, spread-friendly authoring style while
879
+ * still handing Vite+ the object it can read.
880
+ */
881
+ function defineVizeLintConfig(...entries) {
882
+ const config = {
883
+ jsPlugins: [],
884
+ plugins: [],
885
+ rules: {},
886
+ settings: { vize: {} }
887
+ };
888
+ const runtimePresets = [];
889
+ for (const entry of entries) mergeFlatConfigEntry(config, runtimePresets, entry);
890
+ config.jsPlugins = [...new Set(config.jsPlugins)];
891
+ config.plugins = [...new Set(config.plugins)];
892
+ if (runtimePresets.includes("incremental")) config.settings.vize.preset = "incremental";
893
+ else {
894
+ const uniquePresets = [...new Set(runtimePresets)];
895
+ if (uniquePresets.length === 1) config.settings.vize.preset = uniquePresets[0];
896
+ else if (uniquePresets.length > 1) config.settings.vize.preset = "incremental";
897
+ }
898
+ return config;
899
+ }
900
+ function mergeFlatConfigEntry(target, runtimePresets, entry) {
901
+ if (Array.isArray(entry)) {
902
+ for (const item of entry) mergeFlatConfigEntry(target, runtimePresets, item);
903
+ return;
904
+ }
905
+ for (const extended of entry.extends ?? []) mergeFlatConfigEntry(target, runtimePresets, extended);
906
+ mergeFlatConfigItem(target, runtimePresets, entry);
907
+ }
908
+ function mergeFlatConfigItem(target, runtimePresets, item) {
909
+ if (item.jsPlugins !== void 0) target.jsPlugins.push(...item.jsPlugins);
910
+ if (item.plugins !== void 0) target.plugins.push(...item.plugins);
911
+ if (item.rules !== void 0) target.rules = {
912
+ ...target.rules,
913
+ ...item.rules
914
+ };
915
+ if (item.ignorePatterns !== void 0) target.ignorePatterns = [...new Set([...readStringArray(target.ignorePatterns) ?? [], ...item.ignorePatterns])];
916
+ if (item.overrides !== void 0) target.overrides = [...readUnknownArray(target.overrides) ?? [], ...item.overrides];
917
+ if (item.settings !== void 0) mergeSettings(target.settings, runtimePresets, item.settings);
918
+ for (const [key, value] of Object.entries(item)) {
919
+ if (value === void 0 || key === "extends" || key === "ignorePatterns" || key === "jsPlugins" || key === "overrides" || key === "plugins" || key === "rules" || key === "settings") continue;
920
+ target[key] = value;
921
+ }
922
+ }
923
+ function mergeSettings(target, runtimePresets, settings) {
924
+ for (const [key, value] of Object.entries(settings)) if (key !== "vize") target[key] = value;
925
+ if (isRecord(settings.vize)) {
926
+ const runtimePreset = normalizeRuntimePresetSetting(settings.vize.preset);
927
+ if (runtimePreset !== null) runtimePresets.push(runtimePreset);
928
+ target.vize = {
929
+ ...target.vize,
930
+ ...settings.vize
931
+ };
932
+ }
933
+ }
934
+ function normalizeRuntimePresetSetting(value) {
935
+ if (typeof value !== "string") return null;
936
+ switch (value.replaceAll(/[-_\s]/gu, "").toLowerCase()) {
937
+ case "generalrecommended":
938
+ case "happypath":
939
+ case "happy":
940
+ case "default":
941
+ case "recommended": return "general-recommended";
942
+ case "essential": return "essential";
943
+ case "ecosystem":
944
+ case "eco": return "ecosystem";
945
+ case "incremental":
946
+ case "all": return "incremental";
947
+ case "opinionated":
948
+ case "opnionated":
949
+ case "strict": return "opinionated";
950
+ case "nuxt": return "nuxt";
951
+ default: return null;
952
+ }
953
+ }
954
+ function readStringArray(value) {
955
+ if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) return null;
956
+ return value;
957
+ }
958
+ function readUnknownArray(value) {
959
+ return Array.isArray(value) ? value : null;
960
+ }
961
+ function isRecord(value) {
962
+ return typeof value === "object" && value !== null && !Array.isArray(value);
963
+ }
964
+ //#endregion
965
+ export { VIZE_JS_PLUGIN_SPECIFIER, configs, createVizeLintConfig, createVizeLintFlatConfig, createVizeRuleConfig, plugin_default as default, defineVizeLintConfig, flatConfigs };
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "devDependencies": {
13
13
  "@tsdown/css": "0.22.14",
14
14
  "@types/node": "25.9.2",
15
- "@vizejs/native": "0.389.0",
15
+ "@vizejs/native": "0.391.0",
16
16
  "tsdown": "0.22.14",
17
17
  "typescript": "6.0.3",
18
18
  "vite": "npm:@voidzero-dev/vite-plus-core@0.1.24",
@@ -43,14 +43,14 @@
43
43
  "main": "./dist/index.mjs",
44
44
  "name": "oxlint-plugin-vize",
45
45
  "optionalDependencies": {
46
- "@vizejs/native-darwin-arm64": "0.389.0",
47
- "@vizejs/native-darwin-x64": "0.389.0",
48
- "@vizejs/native-linux-arm64-gnu": "0.389.0",
49
- "@vizejs/native-linux-arm64-musl": "0.389.0",
50
- "@vizejs/native-linux-x64-gnu": "0.389.0",
51
- "@vizejs/native-linux-x64-musl": "0.389.0",
52
- "@vizejs/native-win32-arm64-msvc": "0.389.0",
53
- "@vizejs/native-win32-x64-msvc": "0.389.0"
46
+ "@vizejs/native-darwin-arm64": "0.391.0",
47
+ "@vizejs/native-darwin-x64": "0.391.0",
48
+ "@vizejs/native-linux-arm64-gnu": "0.391.0",
49
+ "@vizejs/native-linux-arm64-musl": "0.391.0",
50
+ "@vizejs/native-linux-x64-gnu": "0.391.0",
51
+ "@vizejs/native-linux-x64-musl": "0.391.0",
52
+ "@vizejs/native-win32-arm64-msvc": "0.391.0",
53
+ "@vizejs/native-win32-x64-msvc": "0.391.0"
54
54
  },
55
55
  "publishConfig": {
56
56
  "access": "public"
@@ -62,7 +62,7 @@
62
62
  },
63
63
  "type": "module",
64
64
  "types": "./dist/index.d.mts",
65
- "version": "0.389.0",
65
+ "version": "0.391.0",
66
66
  "scripts": {
67
67
  "build": "vp pack",
68
68
  "check": "vp check src vite.config.ts",