oxlint-plugin-vize 0.390.0 → 0.404.8
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 +42 -14
- package/dist/index.d.mts +78 -22
- package/dist/index.mjs +409 -32
- package/package.json +11 -11
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.
|
|
@@ -29,7 +29,7 @@ The bridge is optimized around Oxlint's per-rule execution model:
|
|
|
29
29
|
|
|
30
30
|
## Installation
|
|
31
31
|
|
|
32
|
-
`oxlint-plugin-vize` targets Node 22 and Node 24+ (`^22 || >= 24`). In this repository, Vite+ reads
|
|
32
|
+
`oxlint-plugin-vize` targets Node 22 and Node 24+ (`^22 || >= 24`). In this repository, Vite+ reads `package.json#devEngines.runtime` for you, so the usual setup is:
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
vp install
|
|
@@ -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
|
|
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: "
|
|
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
|
|
87
|
-
That matters because the bridge silently drops any `vize/*`
|
|
88
|
-
`vize/ecosystem/router-link-require-to` while the active
|
|
89
|
-
nothing at all. `createVizeLintConfig` throws for that case,
|
|
90
|
-
leaving you with a config that looks enabled and reports
|
|
91
|
-
|
|
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 `"
|
|
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
|
-
- `"
|
|
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?:
|
|
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"
|
|
51
|
-
*
|
|
52
|
-
*
|
|
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
|
|
72
|
-
* default. `"incremental"` emits no preset rules, so only `rules`
|
|
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?:
|
|
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`
|
|
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
|
|
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
|
|
123
|
-
* runtime preset gate (`plugin.ts`), so it stays listed in `rules` and
|
|
124
|
-
* nothing. `preset: "incremental"` is the supported way to run an
|
|
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
|
|
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
|
-
|
|
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
|
@@ -95,7 +95,7 @@ if (import.meta.vitest) {
|
|
|
95
95
|
}
|
|
96
96
|
//#endregion
|
|
97
97
|
//#region src/binding.ts
|
|
98
|
-
function lintPatina(source, filename, settings, enabledRules) {
|
|
98
|
+
function lintPatina(source, filename, settings, enabledRules, ruleOptions) {
|
|
99
99
|
return loadBinding().lintPatinaSfc(source, {
|
|
100
100
|
filename,
|
|
101
101
|
locale: settings.locale,
|
|
@@ -103,7 +103,8 @@ function lintPatina(source, filename, settings, enabledRules) {
|
|
|
103
103
|
preset: settings.preset,
|
|
104
104
|
enabledRules: enabledRules ? [...enabledRules] : void 0,
|
|
105
105
|
typeAware: settings.typeAware,
|
|
106
|
-
corsaPath: settings.corsaPath
|
|
106
|
+
corsaPath: settings.corsaPath,
|
|
107
|
+
...ruleOptions
|
|
107
108
|
});
|
|
108
109
|
}
|
|
109
110
|
function getPatinaRules() {
|
|
@@ -194,10 +195,10 @@ const PRESET_ALIASES = new Map([
|
|
|
194
195
|
["ecosystem", "ecosystem"],
|
|
195
196
|
["eco", "ecosystem"],
|
|
196
197
|
["incremental", "incremental"],
|
|
198
|
+
["all", "incremental"],
|
|
197
199
|
["opinionated", "opinionated"],
|
|
198
200
|
["opnionated", "opinionated"],
|
|
199
201
|
["strict", "opinionated"],
|
|
200
|
-
["all", "opinionated"],
|
|
201
202
|
["nuxt", "nuxt"]
|
|
202
203
|
]);
|
|
203
204
|
function getVizeSettings(context) {
|
|
@@ -271,6 +272,7 @@ if (import.meta.vitest) {
|
|
|
271
272
|
it("normalizes preset aliases", () => {
|
|
272
273
|
expect(parseVizeSettings({ preset: "recommended" })).toEqual({ preset: "general-recommended" });
|
|
273
274
|
expect(parseVizeSettings({ preset: "incremental" })).toEqual({ preset: "incremental" });
|
|
275
|
+
expect(parseVizeSettings({ preset: "all" })).toEqual({ preset: "incremental" });
|
|
274
276
|
expect(parseVizeSettings({ preset: "ecosystem" })).toEqual({ preset: "ecosystem" });
|
|
275
277
|
expect(parseVizeSettings({ preset: "strict" })).toEqual({ preset: "opinionated" });
|
|
276
278
|
expect(parseVizeSettings({ preset: "Opnionated" })).toEqual({ preset: "opinionated" });
|
|
@@ -345,7 +347,8 @@ function getFileState(context) {
|
|
|
345
347
|
evictLeastRecentlyUsedFileState();
|
|
346
348
|
return state;
|
|
347
349
|
}
|
|
348
|
-
function getDiagnosticsForRule(context, state, ruleName) {
|
|
350
|
+
function getDiagnosticsForRule(context, state, ruleName, ruleOptions) {
|
|
351
|
+
if (ruleOptions != null) return getConfiguredRuleDiagnostics(context, state, ruleName, ruleOptions);
|
|
349
352
|
if (state.allDiagnosticsByRule) {
|
|
350
353
|
if (isTypeAwareRuleName(ruleName) && !state.allDiagnosticsIncludesTypeAware) {
|
|
351
354
|
const settings = getVizeSettings(context);
|
|
@@ -388,6 +391,47 @@ function getDiagnosticsForRule(context, state, ruleName) {
|
|
|
388
391
|
state.partialDiagnosticsByRule.clear();
|
|
389
392
|
return diagnosticsForRule(state, state.allDiagnosticsByRule, ruleName);
|
|
390
393
|
}
|
|
394
|
+
function getConfiguredRuleDiagnostics(context, state, ruleName, ruleOptions) {
|
|
395
|
+
const cacheKey = configuredRuleCacheKey(ruleName, ruleOptions);
|
|
396
|
+
const cached = state.partialDiagnosticsByRule.get(cacheKey);
|
|
397
|
+
if (cached) return cached;
|
|
398
|
+
const settings = getVizeSettings(context);
|
|
399
|
+
const ruleSettings = isTypeAwareRuleName(ruleName) ? {
|
|
400
|
+
...settings,
|
|
401
|
+
typeAware: true
|
|
402
|
+
} : settings;
|
|
403
|
+
const diagnostics = lintPatina(state.source, state.filename, ruleSettings, [ruleName], ruleOptions).diagnostics;
|
|
404
|
+
const ruleDiagnostics = diagnosticsForRule(state, indexDiagnosticsByRule(diagnostics), ruleName);
|
|
405
|
+
state.partialDiagnosticsByRule.set(cacheKey, ruleDiagnostics);
|
|
406
|
+
return ruleDiagnostics;
|
|
407
|
+
}
|
|
408
|
+
function configuredRuleCacheKey(ruleName, ruleOptions) {
|
|
409
|
+
return [
|
|
410
|
+
ruleName,
|
|
411
|
+
ruleOptions.componentNameInTemplateCasing ?? "",
|
|
412
|
+
ruleOptions.customEventNameCasing ?? "",
|
|
413
|
+
ruleOptions.noMutatingProps == null ? "" : noMutatingPropsCacheKey(ruleOptions.noMutatingProps),
|
|
414
|
+
ruleOptions.sfcElementOrder == null ? "" : sfcElementOrderCacheKey(ruleOptions.sfcElementOrder),
|
|
415
|
+
ruleOptions.htmlSelfClosing == null ? "" : htmlSelfClosingCacheKey(ruleOptions.htmlSelfClosing),
|
|
416
|
+
ruleOptions.vOnEventHyphenation ?? "",
|
|
417
|
+
ruleOptions.attributeHyphenation ?? ""
|
|
418
|
+
].join("\0");
|
|
419
|
+
}
|
|
420
|
+
function noMutatingPropsCacheKey(options) {
|
|
421
|
+
return options.shallowOnly === true ? "1" : "0";
|
|
422
|
+
}
|
|
423
|
+
function sfcElementOrderCacheKey(options) {
|
|
424
|
+
return (options.order ?? []).map((group) => Array.isArray(group) ? group.join("") : group).join("");
|
|
425
|
+
}
|
|
426
|
+
function htmlSelfClosingCacheKey(options) {
|
|
427
|
+
return [
|
|
428
|
+
options.html?.void ?? "",
|
|
429
|
+
options.html?.normal ?? "",
|
|
430
|
+
options.html?.component ?? "",
|
|
431
|
+
options.svg ?? "",
|
|
432
|
+
options.math ?? ""
|
|
433
|
+
].join("/");
|
|
434
|
+
}
|
|
391
435
|
function getScriptMap(state) {
|
|
392
436
|
if (state.scriptMap !== void 0) return state.scriptMap;
|
|
393
437
|
state.scriptMap = createSingleScriptMap(state.source, state.extractedScript, getSfcBlocks(state));
|
|
@@ -554,7 +598,8 @@ function createPatinaRule(ruleMeta) {
|
|
|
554
598
|
return defineRule({
|
|
555
599
|
meta: {
|
|
556
600
|
type: ruleMeta.defaultSeverity === "error" ? "problem" : "suggestion",
|
|
557
|
-
docs: { description: ruleMeta.description }
|
|
601
|
+
docs: { description: ruleMeta.description },
|
|
602
|
+
schema: ruleOptionsSchema(ruleMeta.name)
|
|
558
603
|
},
|
|
559
604
|
createOnce(context) {
|
|
560
605
|
return { Program() {
|
|
@@ -565,7 +610,8 @@ function createPatinaRule(ruleMeta) {
|
|
|
565
610
|
const helpLevel = settings.helpLevel ?? "full";
|
|
566
611
|
const state = getFileState(context);
|
|
567
612
|
const scriptMap = getScriptMap(state);
|
|
568
|
-
const
|
|
613
|
+
const ruleOptions = getRuleOptions(ruleMeta.name, contextOptions(context));
|
|
614
|
+
const diagnostics = getDiagnosticsForRule(context, state, ruleMeta.name, ruleOptions).filter((diagnostic) => shouldReportForCurrentProgram(diagnostic, state, scriptMap));
|
|
569
615
|
if (diagnostics.length === 0) return;
|
|
570
616
|
for (const diagnostic of diagnostics) {
|
|
571
617
|
if (!markDiagnosticAsReported(state, diagnostic)) continue;
|
|
@@ -575,6 +621,140 @@ function createPatinaRule(ruleMeta) {
|
|
|
575
621
|
}
|
|
576
622
|
});
|
|
577
623
|
}
|
|
624
|
+
function ruleOptionsSchema(ruleName) {
|
|
625
|
+
switch (ruleName) {
|
|
626
|
+
case "vue/component-name-in-template-casing": return [{ enum: ["PascalCase", "kebab-case"] }];
|
|
627
|
+
case "script/custom-event-name-casing": return [{ enum: ["camelCase", "kebab-case"] }];
|
|
628
|
+
case "vue/no-mutating-props": return [noMutatingPropsSchema()];
|
|
629
|
+
case "vue/sfc-element-order": return [sfcElementOrderSchema()];
|
|
630
|
+
case "vue/html-self-closing": return [htmlSelfClosingSchema()];
|
|
631
|
+
case "vue/v-on-event-hyphenation":
|
|
632
|
+
case "vue/attribute-hyphenation": return [{ enum: ["always", "never"] }];
|
|
633
|
+
default: return [];
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function noMutatingPropsSchema() {
|
|
637
|
+
return {
|
|
638
|
+
type: "object",
|
|
639
|
+
properties: { shallowOnly: { type: "boolean" } },
|
|
640
|
+
additionalProperties: false
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function sfcElementOrderSchema() {
|
|
644
|
+
const selector = { type: "string" };
|
|
645
|
+
return {
|
|
646
|
+
type: "object",
|
|
647
|
+
properties: { order: {
|
|
648
|
+
type: "array",
|
|
649
|
+
items: { oneOf: [selector, {
|
|
650
|
+
type: "array",
|
|
651
|
+
items: selector
|
|
652
|
+
}] }
|
|
653
|
+
} },
|
|
654
|
+
additionalProperties: false
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
function htmlSelfClosingSchema() {
|
|
658
|
+
const style = { enum: [
|
|
659
|
+
"always",
|
|
660
|
+
"never",
|
|
661
|
+
"any"
|
|
662
|
+
] };
|
|
663
|
+
return {
|
|
664
|
+
type: "object",
|
|
665
|
+
properties: {
|
|
666
|
+
html: {
|
|
667
|
+
type: "object",
|
|
668
|
+
properties: {
|
|
669
|
+
void: style,
|
|
670
|
+
normal: style,
|
|
671
|
+
component: style
|
|
672
|
+
},
|
|
673
|
+
additionalProperties: false
|
|
674
|
+
},
|
|
675
|
+
svg: style,
|
|
676
|
+
math: style
|
|
677
|
+
},
|
|
678
|
+
additionalProperties: false
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
function contextOptions(context) {
|
|
682
|
+
const options = context.options;
|
|
683
|
+
return Array.isArray(options) ? options : [];
|
|
684
|
+
}
|
|
685
|
+
function getRuleOptions(ruleName, options) {
|
|
686
|
+
const firstOption = options[0];
|
|
687
|
+
switch (ruleName) {
|
|
688
|
+
case "vue/component-name-in-template-casing":
|
|
689
|
+
if (isComponentNameInTemplateCasingOption(firstOption)) return { componentNameInTemplateCasing: firstOption };
|
|
690
|
+
break;
|
|
691
|
+
case "script/custom-event-name-casing":
|
|
692
|
+
if (isCustomEventNameCasingOption(firstOption)) return { customEventNameCasing: firstOption };
|
|
693
|
+
break;
|
|
694
|
+
case "vue/no-mutating-props":
|
|
695
|
+
if (isNoMutatingPropsOption(firstOption)) return { noMutatingProps: firstOption };
|
|
696
|
+
break;
|
|
697
|
+
case "vue/sfc-element-order":
|
|
698
|
+
if (isSfcElementOrderOption(firstOption)) return { sfcElementOrder: firstOption };
|
|
699
|
+
break;
|
|
700
|
+
case "vue/html-self-closing":
|
|
701
|
+
if (isHtmlSelfClosingOption(firstOption)) return { htmlSelfClosing: firstOption };
|
|
702
|
+
break;
|
|
703
|
+
case "vue/v-on-event-hyphenation":
|
|
704
|
+
if (isHyphenationStyle(firstOption)) return { vOnEventHyphenation: firstOption };
|
|
705
|
+
break;
|
|
706
|
+
case "vue/attribute-hyphenation":
|
|
707
|
+
if (isHyphenationStyle(firstOption)) return { attributeHyphenation: firstOption };
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
function isComponentNameInTemplateCasingOption(value) {
|
|
712
|
+
return value === "PascalCase" || value === "kebab-case";
|
|
713
|
+
}
|
|
714
|
+
function isCustomEventNameCasingOption(value) {
|
|
715
|
+
return value === "camelCase" || value === "kebab-case";
|
|
716
|
+
}
|
|
717
|
+
function isNoMutatingPropsOption(value) {
|
|
718
|
+
if (!isRecord$1(value)) return false;
|
|
719
|
+
return hasOnlyKeys(value, ["shallowOnly"]) && optionalBooleanField(value.shallowOnly);
|
|
720
|
+
}
|
|
721
|
+
function isSfcElementOrderOption(value) {
|
|
722
|
+
if (!isRecord$1(value)) return false;
|
|
723
|
+
return hasOnlyKeys(value, ["order"]) && (value.order === void 0 || Array.isArray(value.order) && value.order.every(isSfcElementOrderGroup));
|
|
724
|
+
}
|
|
725
|
+
function isSfcElementOrderGroup(value) {
|
|
726
|
+
return typeof value === "string" || Array.isArray(value) && value.every(isString);
|
|
727
|
+
}
|
|
728
|
+
function isHtmlSelfClosingOption(value) {
|
|
729
|
+
if (!isRecord$1(value)) return false;
|
|
730
|
+
return hasOnlyKeys(value, [
|
|
731
|
+
"html",
|
|
732
|
+
"svg",
|
|
733
|
+
"math"
|
|
734
|
+
]) && optionField(value.svg) && optionField(value.math) && (value.html === void 0 || isRecord$1(value.html) && hasOnlyKeys(value.html, [
|
|
735
|
+
"void",
|
|
736
|
+
"normal",
|
|
737
|
+
"component"
|
|
738
|
+
]) && optionField(value.html.void) && optionField(value.html.normal) && optionField(value.html.component));
|
|
739
|
+
}
|
|
740
|
+
function isHyphenationStyle(value) {
|
|
741
|
+
return value === "always" || value === "never";
|
|
742
|
+
}
|
|
743
|
+
function optionField(value) {
|
|
744
|
+
return value === void 0 || value === "always" || value === "never" || value === "any";
|
|
745
|
+
}
|
|
746
|
+
function optionalBooleanField(value) {
|
|
747
|
+
return value === void 0 || typeof value === "boolean";
|
|
748
|
+
}
|
|
749
|
+
function isString(value) {
|
|
750
|
+
return typeof value === "string";
|
|
751
|
+
}
|
|
752
|
+
function isRecord$1(value) {
|
|
753
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
754
|
+
}
|
|
755
|
+
function hasOnlyKeys(value, keys) {
|
|
756
|
+
return Object.keys(value).every((key) => keys.includes(key));
|
|
757
|
+
}
|
|
578
758
|
var plugin_default = definePlugin({
|
|
579
759
|
meta: { name: "vize" },
|
|
580
760
|
rules: Object.fromEntries(getPatinaRules().map((ruleMeta) => [ruleMeta.name, createPatinaRule(ruleMeta)]))
|
|
@@ -583,11 +763,11 @@ var plugin_default = definePlugin({
|
|
|
583
763
|
//#region src/configs.ts
|
|
584
764
|
const TYPE_AWARE_RULE_PREFIX = "type/";
|
|
585
765
|
function createVizeRuleConfig(options = {}) {
|
|
586
|
-
const
|
|
766
|
+
const presets = resolveRuleConfigPresets(options);
|
|
587
767
|
const includeTypeAware = options.includeTypeAware ?? false;
|
|
588
768
|
const rules = {};
|
|
589
769
|
for (const ruleMeta of getPatinaRules()) {
|
|
590
|
-
if (!
|
|
770
|
+
if (!matchesPresets(ruleMeta, presets)) continue;
|
|
591
771
|
if (!includeTypeAware && isTypeAwareRule(ruleMeta)) continue;
|
|
592
772
|
rules[`vize/${ruleMeta.name}`] = toOxlintSeverity(ruleMeta.defaultSeverity);
|
|
593
773
|
}
|
|
@@ -601,6 +781,11 @@ const configs = {
|
|
|
601
781
|
preset: "ecosystem"
|
|
602
782
|
}),
|
|
603
783
|
essential: createVizeRuleConfig({ preset: "essential" }),
|
|
784
|
+
happyPath: createVizeRuleConfig({ preset: "happy-path" }),
|
|
785
|
+
happyPathWithTypeAware: createVizeRuleConfig({
|
|
786
|
+
includeTypeAware: true,
|
|
787
|
+
preset: "happy-path"
|
|
788
|
+
}),
|
|
604
789
|
nuxt: createVizeRuleConfig({ preset: "nuxt" }),
|
|
605
790
|
opinionated: createVizeRuleConfig({ preset: "opinionated" }),
|
|
606
791
|
opinionatedWithTypeAware: createVizeRuleConfig({
|
|
@@ -613,8 +798,18 @@ const configs = {
|
|
|
613
798
|
preset: "general-recommended"
|
|
614
799
|
})
|
|
615
800
|
};
|
|
616
|
-
function
|
|
617
|
-
return preset === "
|
|
801
|
+
function normalizeRuleConfigPreset(preset) {
|
|
802
|
+
return preset === "happy-path" ? "general-recommended" : preset;
|
|
803
|
+
}
|
|
804
|
+
function resolveRuleConfigPresets(options) {
|
|
805
|
+
const requestedPresets = [...toPresetArray$1((options.preset === void 0 && options.presets === void 0 ? "general-recommended" : options.preset) ?? []), ...options.presets ?? []].map(normalizeRuleConfigPreset);
|
|
806
|
+
return [...new Set(requestedPresets)];
|
|
807
|
+
}
|
|
808
|
+
function toPresetArray$1(preset) {
|
|
809
|
+
return Array.isArray(preset) ? preset : [preset];
|
|
810
|
+
}
|
|
811
|
+
function matchesPresets(ruleMeta, presets) {
|
|
812
|
+
return presets.includes("all") || presets.some((preset) => preset !== "all" && ruleMeta.presets.includes(preset));
|
|
618
813
|
}
|
|
619
814
|
function isTypeAwareRule(ruleMeta) {
|
|
620
815
|
return ruleMeta.name.startsWith(TYPE_AWARE_RULE_PREFIX);
|
|
@@ -628,10 +823,41 @@ if (import.meta.vitest) {
|
|
|
628
823
|
it("normalizes Vize warning severity to Oxlint's warn", () => {
|
|
629
824
|
expect(configs.recommended["vize/vue/no-multi-spaces"]).toBe("warn");
|
|
630
825
|
});
|
|
826
|
+
it("accepts the CLI happy-path preset alias", () => {
|
|
827
|
+
expect(createVizeRuleConfig({ preset: "happy-path" })).toEqual(configs.recommended);
|
|
828
|
+
expect(configs.happyPath).toEqual(configs.recommended);
|
|
829
|
+
});
|
|
830
|
+
it("unions multiple presets", () => {
|
|
831
|
+
const config = createVizeRuleConfig({ presets: ["happy-path", "ecosystem"] });
|
|
832
|
+
expect(config["vize/script/valid-define-props"]).toBe("error");
|
|
833
|
+
expect(config["vize/ecosystem/router-link-require-to"]).toBe("error");
|
|
834
|
+
expect(config["vize/script/no-options-api"]).toBeUndefined();
|
|
835
|
+
});
|
|
836
|
+
it("accepts multiple presets through the preset option", () => {
|
|
837
|
+
expect(createVizeRuleConfig({ preset: ["happy-path", "ecosystem"] })).toEqual(createVizeRuleConfig({ presets: ["happy-path", "ecosystem"] }));
|
|
838
|
+
});
|
|
631
839
|
it("filters rules by preset", () => {
|
|
632
840
|
expect(configs.essential["vize/vue/require-v-for-key"]).toBe("error");
|
|
633
841
|
expect(configs.essential["vize/vue/require-scoped-style"]).toBeUndefined();
|
|
634
842
|
});
|
|
843
|
+
it("enables script correctness rules in essential and happy-path", () => {
|
|
844
|
+
expect(configs.essential["vize/script/valid-define-props"]).toBe("error");
|
|
845
|
+
expect(configs.essential["vize/script/no-import-compiler-macros"]).toBe("error");
|
|
846
|
+
expect(configs.recommended["vize/script/valid-define-props"]).toBe("error");
|
|
847
|
+
expect(configs.recommended["vize/script/no-import-compiler-macros"]).toBe("error");
|
|
848
|
+
});
|
|
849
|
+
it("keeps low-noise happy-path script warnings out of essential", () => {
|
|
850
|
+
expect(configs.recommended["vize/script/no-duplicate-attr-inheritance"]).toBe("warn");
|
|
851
|
+
expect(configs.recommended["vize/script/no-reactive-destructure"]).toBeUndefined();
|
|
852
|
+
expect(configs.essential["vize/script/no-duplicate-attr-inheritance"]).toBeUndefined();
|
|
853
|
+
expect(configs.recommended["vize/script/no-unused-emit-declarations"]).toBeUndefined();
|
|
854
|
+
expect(configs.essential["vize/script/no-unused-emit-declarations"]).toBeUndefined();
|
|
855
|
+
});
|
|
856
|
+
it("keeps opinionated script style out of happy-path", () => {
|
|
857
|
+
expect(configs.recommended["vize/script/no-options-api"]).toBeUndefined();
|
|
858
|
+
expect(configs.recommended["vize/script/define-props-declaration"]).toBeUndefined();
|
|
859
|
+
expect(configs.opinionated["vize/script/no-options-api"]).toBe("error");
|
|
860
|
+
});
|
|
635
861
|
it("skips unstable type-aware rules by default", () => {
|
|
636
862
|
expect(configs.opinionated["vize/type/require-typed-props"]).toBeUndefined();
|
|
637
863
|
expect(configs.opinionatedWithTypeAware["vize/type/require-typed-props"]).toBe("warn");
|
|
@@ -677,6 +903,7 @@ if (import.meta.vitest) {
|
|
|
677
903
|
*/
|
|
678
904
|
const VIZE_JS_PLUGIN_SPECIFIER = "oxlint-plugin-vize";
|
|
679
905
|
const VIZE_RULE_PREFIX = "vize/";
|
|
906
|
+
const DEFAULT_VIZE_LINT_PRESET = "general-recommended";
|
|
680
907
|
/**
|
|
681
908
|
* Builds the Oxlint configuration block that runs Patina through this bridge.
|
|
682
909
|
*
|
|
@@ -703,55 +930,74 @@ const VIZE_RULE_PREFIX = "vize/";
|
|
|
703
930
|
* - An unknown `vize/*` id only produces Oxlint's
|
|
704
931
|
* `Rule '...' not found in plugin 'vize'` error when Oxlint actually reads the
|
|
705
932
|
* config. A typo in a config Oxlint never reads reports nothing at all.
|
|
706
|
-
* - A `vize/*` id that is outside
|
|
707
|
-
* runtime preset gate (`plugin.ts`), so it stays listed in `rules` and
|
|
708
|
-
* nothing. `preset: "incremental"` is the supported way to run an
|
|
709
|
-
* subset.
|
|
933
|
+
* - A `vize/*` id that is outside a single active preset is dropped by the
|
|
934
|
+
* bridge's runtime preset gate (`plugin.ts`), so it stays listed in `rules` and
|
|
935
|
+
* reports nothing. `preset: "incremental"` is the supported way to run an
|
|
936
|
+
* arbitrary subset.
|
|
710
937
|
*
|
|
711
938
|
* @throws {Error} When `options.rules` names a `vize/*` id the native bridge does
|
|
712
|
-
* not register, or one
|
|
939
|
+
* not register, or one a single resolved preset would silently suppress.
|
|
713
940
|
*/
|
|
714
941
|
function createVizeLintConfig(options = {}) {
|
|
715
|
-
|
|
942
|
+
return buildVizeLintConfig(options, { forceIncrementalRuntime: false });
|
|
943
|
+
}
|
|
944
|
+
function buildVizeLintConfig(options, buildOptions) {
|
|
945
|
+
const presets = resolveLintPresets(options);
|
|
946
|
+
const runtimePreset = buildOptions.forceIncrementalRuntime ? "incremental" : toRuntimePreset(presets);
|
|
716
947
|
const extraRules = options.rules ?? {};
|
|
717
|
-
assertUsableVizeRules(extraRules,
|
|
948
|
+
assertUsableVizeRules(extraRules, presets, runtimePreset);
|
|
718
949
|
return {
|
|
719
950
|
jsPlugins: [VIZE_JS_PLUGIN_SPECIFIER],
|
|
720
951
|
plugins: [...new Set(["vue", ...options.plugins ?? []])],
|
|
721
952
|
rules: {
|
|
722
|
-
...createPresetRules(
|
|
953
|
+
...createPresetRules(presets, options.includeTypeAware),
|
|
723
954
|
...extraRules
|
|
724
955
|
},
|
|
725
956
|
settings: { vize: {
|
|
726
957
|
...options.settings,
|
|
727
|
-
preset:
|
|
958
|
+
preset: runtimePreset
|
|
728
959
|
} }
|
|
729
960
|
};
|
|
730
961
|
}
|
|
731
|
-
function createPresetRules(
|
|
732
|
-
|
|
962
|
+
function createPresetRules(presets, includeTypeAware) {
|
|
963
|
+
const ruleConfigPresets = presets.filter(isRuleConfigPreset);
|
|
964
|
+
if (ruleConfigPresets.length === 0) return {};
|
|
965
|
+
return createVizeRuleConfig({
|
|
733
966
|
includeTypeAware,
|
|
734
|
-
|
|
967
|
+
presets: ruleConfigPresets
|
|
735
968
|
});
|
|
736
969
|
}
|
|
970
|
+
function resolveLintPresets(options) {
|
|
971
|
+
const requestedPresets = [...toPresetArray((options.preset === void 0 && options.presets === void 0 ? DEFAULT_VIZE_LINT_PRESET : options.preset) ?? []), ...options.presets ?? []].map(normalizeLintPreset);
|
|
972
|
+
return [...new Set(requestedPresets)];
|
|
973
|
+
}
|
|
974
|
+
function toPresetArray(preset) {
|
|
975
|
+
return Array.isArray(preset) ? preset : [preset];
|
|
976
|
+
}
|
|
977
|
+
function normalizeLintPreset(preset) {
|
|
978
|
+
return preset === "happy-path" ? DEFAULT_VIZE_LINT_PRESET : preset;
|
|
979
|
+
}
|
|
980
|
+
function isRuleConfigPreset(preset) {
|
|
981
|
+
return preset !== "incremental";
|
|
982
|
+
}
|
|
737
983
|
/**
|
|
738
|
-
* Maps
|
|
984
|
+
* Maps rule bundles to the `settings.vize.preset` the bridge must run with.
|
|
739
985
|
*
|
|
740
|
-
* `"all"`
|
|
741
|
-
* membership; gating
|
|
742
|
-
* the rules that only belong to the other bundles.
|
|
986
|
+
* `"all"` and multiple concrete presets become `"incremental"` because the
|
|
987
|
+
* bridge gates each rule on preset membership; gating a combined rule map by any
|
|
988
|
+
* single preset would suppress the rules that only belong to the other bundles.
|
|
743
989
|
*/
|
|
744
|
-
function toRuntimePreset(
|
|
745
|
-
|
|
990
|
+
function toRuntimePreset(presets) {
|
|
991
|
+
if (presets.length === 1 && presets[0] !== "all") return presets[0];
|
|
992
|
+
return "incremental";
|
|
746
993
|
}
|
|
747
|
-
function assertUsableVizeRules(rules,
|
|
994
|
+
function assertUsableVizeRules(rules, presets, runtimePreset) {
|
|
748
995
|
const ruleMetaById = new Map(getPatinaRules().map((ruleMeta) => [`${VIZE_RULE_PREFIX}${ruleMeta.name}`, ruleMeta]));
|
|
749
996
|
const configuredIds = Object.keys(rules).filter((ruleId) => ruleId.startsWith(VIZE_RULE_PREFIX)).sort();
|
|
750
997
|
const unknownIds = configuredIds.filter((ruleId) => !ruleMetaById.has(ruleId));
|
|
751
998
|
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
999
|
const suppressedIds = configuredIds.filter((ruleId) => isSuppressedByPreset(ruleMetaById.get(ruleId), runtimePreset));
|
|
754
|
-
if (suppressedIds.length > 0) throw new Error(`Vize rule ${pluralizeIds(suppressedIds)} outside
|
|
1000
|
+
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
1001
|
}
|
|
756
1002
|
/**
|
|
757
1003
|
* Mirrors the runtime gate in `plugin.ts`: rules with no preset membership are
|
|
@@ -760,8 +1006,139 @@ function assertUsableVizeRules(rules, preset) {
|
|
|
760
1006
|
function isSuppressedByPreset(ruleMeta, runtimePreset) {
|
|
761
1007
|
return ruleMeta.presets.length > 0 && runtimePreset !== "incremental" && !ruleMeta.presets.includes(runtimePreset);
|
|
762
1008
|
}
|
|
1009
|
+
function describePresetSelection(presets) {
|
|
1010
|
+
const names = presets.map((preset) => preset === DEFAULT_VIZE_LINT_PRESET ? "happy-path" : preset);
|
|
1011
|
+
return `the "${names.length === 0 ? "empty" : names.join(", ")}" ${names.length === 1 ? "preset" : "presets"}`;
|
|
1012
|
+
}
|
|
763
1013
|
function pluralizeIds(ids) {
|
|
764
1014
|
return ids.length === 1 ? "id" : "ids";
|
|
765
1015
|
}
|
|
766
1016
|
//#endregion
|
|
767
|
-
|
|
1017
|
+
//#region src/vite-plus-flat-config.ts
|
|
1018
|
+
/**
|
|
1019
|
+
* Returns spreadable Oxlint/Vite+ config fragments for Flat Config style use.
|
|
1020
|
+
*
|
|
1021
|
+
* The fragments intentionally set `settings.vize.preset` to `"incremental"` so
|
|
1022
|
+
* multiple fragments can be composed without one preset's runtime gate
|
|
1023
|
+
* suppressing rules emitted by another fragment.
|
|
1024
|
+
*/
|
|
1025
|
+
function createVizeLintFlatConfig(options = {}) {
|
|
1026
|
+
return [buildVizeLintConfig(options, { forceIncrementalRuntime: true })];
|
|
1027
|
+
}
|
|
1028
|
+
const flatConfigs = {
|
|
1029
|
+
all: createVizeLintFlatConfig({ preset: "all" }),
|
|
1030
|
+
ecosystem: createVizeLintFlatConfig({ preset: "ecosystem" }),
|
|
1031
|
+
ecosystemWithTypeAware: createVizeLintFlatConfig({
|
|
1032
|
+
includeTypeAware: true,
|
|
1033
|
+
preset: "ecosystem"
|
|
1034
|
+
}),
|
|
1035
|
+
essential: createVizeLintFlatConfig({ preset: "essential" }),
|
|
1036
|
+
happyPath: createVizeLintFlatConfig({ preset: "happy-path" }),
|
|
1037
|
+
happyPathWithTypeAware: createVizeLintFlatConfig({
|
|
1038
|
+
includeTypeAware: true,
|
|
1039
|
+
preset: "happy-path"
|
|
1040
|
+
}),
|
|
1041
|
+
nuxt: createVizeLintFlatConfig({ preset: "nuxt" }),
|
|
1042
|
+
opinionated: createVizeLintFlatConfig({ preset: "opinionated" }),
|
|
1043
|
+
opinionatedWithTypeAware: createVizeLintFlatConfig({
|
|
1044
|
+
includeTypeAware: true,
|
|
1045
|
+
preset: "opinionated"
|
|
1046
|
+
}),
|
|
1047
|
+
recommended: createVizeLintFlatConfig({ preset: "general-recommended" }),
|
|
1048
|
+
recommendedWithTypeAware: createVizeLintFlatConfig({
|
|
1049
|
+
includeTypeAware: true,
|
|
1050
|
+
preset: "general-recommended"
|
|
1051
|
+
})
|
|
1052
|
+
};
|
|
1053
|
+
/**
|
|
1054
|
+
* Collapses Flat Config-style fragments into the object shape Vite+ accepts.
|
|
1055
|
+
*
|
|
1056
|
+
* `vp lint` currently expects a single Oxlint config object under `lint`, so this
|
|
1057
|
+
* helper lets `vite.config.ts` keep a flat, spread-friendly authoring style while
|
|
1058
|
+
* still handing Vite+ the object it can read.
|
|
1059
|
+
*/
|
|
1060
|
+
function defineVizeLintConfig(...entries) {
|
|
1061
|
+
const config = {
|
|
1062
|
+
jsPlugins: [],
|
|
1063
|
+
plugins: [],
|
|
1064
|
+
rules: {},
|
|
1065
|
+
settings: { vize: {} }
|
|
1066
|
+
};
|
|
1067
|
+
const runtimePresets = [];
|
|
1068
|
+
for (const entry of entries) mergeFlatConfigEntry(config, runtimePresets, entry);
|
|
1069
|
+
config.jsPlugins = [...new Set(config.jsPlugins)];
|
|
1070
|
+
config.plugins = [...new Set(config.plugins)];
|
|
1071
|
+
if (runtimePresets.includes("incremental")) config.settings.vize.preset = "incremental";
|
|
1072
|
+
else {
|
|
1073
|
+
const uniquePresets = [...new Set(runtimePresets)];
|
|
1074
|
+
if (uniquePresets.length === 1) config.settings.vize.preset = uniquePresets[0];
|
|
1075
|
+
else if (uniquePresets.length > 1) config.settings.vize.preset = "incremental";
|
|
1076
|
+
}
|
|
1077
|
+
return config;
|
|
1078
|
+
}
|
|
1079
|
+
function mergeFlatConfigEntry(target, runtimePresets, entry) {
|
|
1080
|
+
if (Array.isArray(entry)) {
|
|
1081
|
+
for (const item of entry) mergeFlatConfigEntry(target, runtimePresets, item);
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
for (const extended of entry.extends ?? []) mergeFlatConfigEntry(target, runtimePresets, extended);
|
|
1085
|
+
mergeFlatConfigItem(target, runtimePresets, entry);
|
|
1086
|
+
}
|
|
1087
|
+
function mergeFlatConfigItem(target, runtimePresets, item) {
|
|
1088
|
+
if (item.jsPlugins !== void 0) target.jsPlugins.push(...item.jsPlugins);
|
|
1089
|
+
if (item.plugins !== void 0) target.plugins.push(...item.plugins);
|
|
1090
|
+
if (item.rules !== void 0) target.rules = {
|
|
1091
|
+
...target.rules,
|
|
1092
|
+
...item.rules
|
|
1093
|
+
};
|
|
1094
|
+
if (item.ignorePatterns !== void 0) target.ignorePatterns = [...new Set([...readStringArray(target.ignorePatterns) ?? [], ...item.ignorePatterns])];
|
|
1095
|
+
if (item.overrides !== void 0) target.overrides = [...readUnknownArray(target.overrides) ?? [], ...item.overrides];
|
|
1096
|
+
if (item.settings !== void 0) mergeSettings(target.settings, runtimePresets, item.settings);
|
|
1097
|
+
for (const [key, value] of Object.entries(item)) {
|
|
1098
|
+
if (value === void 0 || key === "extends" || key === "ignorePatterns" || key === "jsPlugins" || key === "overrides" || key === "plugins" || key === "rules" || key === "settings") continue;
|
|
1099
|
+
target[key] = value;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
function mergeSettings(target, runtimePresets, settings) {
|
|
1103
|
+
for (const [key, value] of Object.entries(settings)) if (key !== "vize") target[key] = value;
|
|
1104
|
+
if (isRecord(settings.vize)) {
|
|
1105
|
+
const runtimePreset = normalizeRuntimePresetSetting(settings.vize.preset);
|
|
1106
|
+
if (runtimePreset !== null) runtimePresets.push(runtimePreset);
|
|
1107
|
+
target.vize = {
|
|
1108
|
+
...target.vize,
|
|
1109
|
+
...settings.vize
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
function normalizeRuntimePresetSetting(value) {
|
|
1114
|
+
if (typeof value !== "string") return null;
|
|
1115
|
+
switch (value.replaceAll(/[-_\s]/gu, "").toLowerCase()) {
|
|
1116
|
+
case "generalrecommended":
|
|
1117
|
+
case "happypath":
|
|
1118
|
+
case "happy":
|
|
1119
|
+
case "default":
|
|
1120
|
+
case "recommended": return "general-recommended";
|
|
1121
|
+
case "essential": return "essential";
|
|
1122
|
+
case "ecosystem":
|
|
1123
|
+
case "eco": return "ecosystem";
|
|
1124
|
+
case "incremental":
|
|
1125
|
+
case "all": return "incremental";
|
|
1126
|
+
case "opinionated":
|
|
1127
|
+
case "opnionated":
|
|
1128
|
+
case "strict": return "opinionated";
|
|
1129
|
+
case "nuxt": return "nuxt";
|
|
1130
|
+
default: return null;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
function readStringArray(value) {
|
|
1134
|
+
if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) return null;
|
|
1135
|
+
return value;
|
|
1136
|
+
}
|
|
1137
|
+
function readUnknownArray(value) {
|
|
1138
|
+
return Array.isArray(value) ? value : null;
|
|
1139
|
+
}
|
|
1140
|
+
function isRecord(value) {
|
|
1141
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1142
|
+
}
|
|
1143
|
+
//#endregion
|
|
1144
|
+
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.
|
|
15
|
+
"@vizejs/native": "0.404.8",
|
|
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.
|
|
47
|
-
"@vizejs/native-darwin-x64": "0.
|
|
48
|
-
"@vizejs/native-linux-arm64-gnu": "0.
|
|
49
|
-
"@vizejs/native-linux-arm64-musl": "0.
|
|
50
|
-
"@vizejs/native-linux-x64-gnu": "0.
|
|
51
|
-
"@vizejs/native-linux-x64-musl": "0.
|
|
52
|
-
"@vizejs/native-win32-arm64-msvc": "0.
|
|
53
|
-
"@vizejs/native-win32-x64-msvc": "0.
|
|
46
|
+
"@vizejs/native-darwin-arm64": "0.404.8",
|
|
47
|
+
"@vizejs/native-darwin-x64": "0.404.8",
|
|
48
|
+
"@vizejs/native-linux-arm64-gnu": "0.404.8",
|
|
49
|
+
"@vizejs/native-linux-arm64-musl": "0.404.8",
|
|
50
|
+
"@vizejs/native-linux-x64-gnu": "0.404.8",
|
|
51
|
+
"@vizejs/native-linux-x64-musl": "0.404.8",
|
|
52
|
+
"@vizejs/native-win32-arm64-msvc": "0.404.8",
|
|
53
|
+
"@vizejs/native-win32-x64-msvc": "0.404.8"
|
|
54
54
|
},
|
|
55
55
|
"publishConfig": {
|
|
56
56
|
"access": "public"
|
|
@@ -62,12 +62,12 @@
|
|
|
62
62
|
},
|
|
63
63
|
"type": "module",
|
|
64
64
|
"types": "./dist/index.d.mts",
|
|
65
|
-
"version": "0.
|
|
65
|
+
"version": "0.404.8",
|
|
66
66
|
"scripts": {
|
|
67
67
|
"build": "vp pack",
|
|
68
68
|
"check": "vp check src vite.config.ts",
|
|
69
69
|
"check:fix": "vp check --fix src vite.config.ts",
|
|
70
70
|
"fmt": "vp fmt --write src vite.config.ts",
|
|
71
|
-
"test": "vp pack && vp test run src/file-state.test.ts && node src/test.ts && node src/script-location.test.ts"
|
|
71
|
+
"test": "vp pack && vp test run src/file-state.test.ts && node src/test.ts && node src/script-location.test.ts && node src/casing-options.test.ts"
|
|
72
72
|
}
|
|
73
73
|
}
|