oxlint-plugin-vize
v0.435.0
Published
Oxlint JS plugin bridge for Vize Patina
Readme
oxlint-plugin-vize
Oxlint JS plugin bridge for Vize Patina.
This package lets Oxlint execute Patina through Vize's native binding while still using Oxlint's JS plugin model and rule configuration.
[!IMPORTANT]
oxlint-plugin-vizeis a terminal-first Vue SFC linting package. Until upstream Vue support in Oxlint matures, useoxlint-vizefor original SFC locations in terminal and JSON output. Directoxlintstill has JS plugin location limits.
Main Features
- Runs Vize Patina rules inside Oxlint as
vize/*diagnostics, so Vue-specific findings can live beside Oxlint core rules in one command. - Keeps Oxlint's existing rules and built-in
vueplugin active. The bridge adds Vize rules; it does not replaceeqeqeq,no-console, or your existingvue/*setup. - 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. - Ships
createVizeLintConfig(),flatConfigs, anddefineVizeLintConfig()for the Vite+lintblock invite.config.ts, which is the only Oxlint configurationvp lintandvp checkread. - Supports runtime settings through
settings.vize, includinglocale,preset, andhelpLevel. - Provides the
oxlint-vizeCLI wrapper, which runs Oxlint with a temporary location bridge for Vue and HTML files and reports their original paths and Patina locations. - Resolves Vize native bindings through platform-specific optional dependencies, so published installs do not need a separate
@vizejs/nativepackage. - Caches file contents and native rule results for the lifetime of the Oxlint process, reducing duplicate work when several Vize rules are enabled for the same file.
Performance
The bridge is optimized around Oxlint's per-rule execution model:
- The first enabled Patina rule on a file runs native linting for that rule only.
- If a second Patina rule is encountered on the same file, the bridge upgrades to one shared full-file Patina pass and reuses that result for the remaining Patina rules.
- Exact source revisions and rule results are cached for up to 128 recently used file/settings pairs; edits replace revision-local diagnostics and reporting state.
Installation
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:
vp install
vp run --filter './npm/native' build
vp run --filter './npm/oxlint' buildInstall vp once from the Vite+ install guide, then add it with:
vp install -D oxlint oxlint-plugin-vizeoxlint-plugin-vize pulls the appropriate Vize native binding for the current platform through optional dependencies, so no separate @vizejs/native install is required for published builds.
Usage
Which file you configure depends on which command you run.
| Command | Reads |
| ----------------------- | ------------------------------------ |
| vp lint, vp check | the lint block in vite.config.ts |
| oxlint, oxlint-vize | .oxlintrc.json (or -c <path>) |
[!IMPORTANT] Vite+ never reads
.oxlintrc.json. A.oxlintrc.jsoncarryingjsPluginsandvize/*rules looks configured, butvp lintignores the file, so Oxlint never sees avize/*rule id and reports zero Vize diagnostics while exiting0.vp lint --initdoes not migrate an existing.oxlintrc.jsoneither: it writes a freshlintblock and leaves the old file in place. If you usevp lint, configure thelintblock.
With vp lint (Vite+)
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.
// vite.config.ts
import { defineConfig } from "vite-plus";
import { createVizeLintConfig } from "oxlint-plugin-vize";
export default defineConfig({
lint: createVizeLintConfig({
preset: "happy-path",
rules: {
"no-console": "warn",
},
settings: {
helpLevel: "short",
},
}),
});preset drives both the emitted rule map and settings.vize.preset when a single bundle is
selected, so the two can never disagree. That matters because the bridge silently drops any vize/*
rule outside the active preset: listing vize/ecosystem/router-link-require-to while the active
preset is general-recommended reports nothing at all. createVizeLintConfig throws for that case,
and for unknown vize/* ids, instead of leaving you with a config that looks enabled and reports
nothing.
Use preset: "incremental" when you want only the rules you list, preset: "all" for every bundle
at once, and preset: ["happy-path", "ecosystem"] or presets: ["happy-path", "ecosystem"] when
you want several bundles without taking on all of them. Multiple-bundle configs emit
settings.vize.preset: "incremental" so the runtime gate cannot suppress one selected bundle's
rules while another bundle is active.
The emitted block always enables Oxlint's built-in vue plugin. Pass plugins to keep the rest of
your project's plugin list; they are merged with vue, never replaced, because narrowing the list
would silently drop everything those plugins report. A create-vue project keeps its generated set
like this:
export default defineConfig({
lint: {
...createVizeLintConfig({
plugins: ["eslint", "typescript", "unicorn", "oxc"],
}),
ignorePatterns: ["dist/**"],
},
});For Flat Config-style composition in vite.config.ts, use spreadable fragments and collapse them
back to Vite+'s object-shaped lint config:
import { defineConfig } from "vite-plus";
import { defineVizeLintConfig, flatConfigs } from "oxlint-plugin-vize";
export default defineConfig({
lint: defineVizeLintConfig(...flatConfigs.recommended, ...flatConfigs.ecosystem, {
ignorePatterns: ["dist/**"],
rules: {
"no-console": "warn",
},
}),
});The exported fragments include flatConfigs.recommended, flatConfigs.happyPath,
flatConfigs.essential, flatConfigs.ecosystem, flatConfigs.nuxt, flatConfigs.opinionated,
and flatConfigs.all, plus the same *WithTypeAware variants as configs.
With oxlint or oxlint-vize
Enable Oxlint's built-in vue plugin as well as this JS plugin:
{
"plugins": ["vue"],
"jsPlugins": ["oxlint-plugin-vize"],
"rules": {
"eqeqeq": "error",
"no-console": "warn",
"vize/vue/require-v-for-key": "error",
"vize/vue/no-v-html": "warn"
}
}This bridge only adds the vize/* rules. Oxlint's existing core rules and built-in plugin rules still run as configured, so checks like eqeqeq, no-console, or your existing vue/* setup continue to report normally.
If you want a lower-config JS/TS Oxlint setup, the package also exports preset rule maps:
import { configs } from "oxlint-plugin-vize";
export default {
plugins: ["vue"],
jsPlugins: ["oxlint-plugin-vize"],
settings: {
vize: {
helpLevel: "short",
preset: "opinionated",
typeAware: true,
},
},
rules: configs.opinionatedWithTypeAware,
};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.
You can pass Patina settings through settings.vize:
{
"settings": {
"vize": {
"locale": "ja",
"preset": "essential",
"helpLevel": "short",
"typeAware": true
}
}
}presetaccepts"general-recommended"/"happy-path","essential","ecosystem","incremental","opinionated","nuxt", or"all".presetdefaults to"general-recommended".- Bundle presets keep out-of-bundle rules quiet even if they are still listed in
rules. "incremental"skips bundle gating and runs only the Vize rules you explicitly configure in Oxlint."all"is accepted as a settings alias for"incremental"; useconfigs.allorcreateVizeLintConfig({ preset: "all" })when you also want every rule emitted."ecosystem"enables Vize's Vue Router, Vue I18n, Pinia, Vue Test Utils, and Void Vue rules without taking on the full opinionated preset."opinionated"is the preset that enables Vize's built-in script rules such asvize/script/no-options-api.- Legacy aliases such as
"GeneralRecommended","Essential","Ecosystem","Incremental","Opinionated","Nuxt", and"happy-path"are still accepted for compatibility. helpLevelaccepts"full","short", or"none". Short help keeps one actionable sentence and removes inline examples and trailing rationale.helpLevel: "full"only expands the Patina remediation text. It does not restore original-SFC formatter anchors or machine-readable range fidelity.typeAware: trueenables Corsa-backedvize/type/*rules during shared Patina passes.corsaPathselects a Corsa or TypeScript 7 native executable for type-aware linting. Omit it to use Vize's normal resolver.showHelpis still accepted for backward compatibility, buthelpLevelis the preferred setting.
For example, this keeps Oxlint focused on correctness-only Vize diagnostics while still allowing your existing Oxlint rules to run unchanged:
{
"settings": {
"vize": {
"preset": "essential",
"helpLevel": "short"
}
},
"rules": {
"vize/vue/require-v-for-key": "error",
"vize/vue/require-scoped-style": "error"
}
}In that config, vize/vue/require-v-for-key can report, while vize/vue/require-scoped-style stays silent because it belongs to the broader "general-recommended" preset.
If you want to adopt Vize one rule at a time, use "preset": "incremental". In that mode, preset membership no longer suppresses configured rules, so only the Vize rules you list under rules will run.
For day-to-day terminal runs, the recommended command today is:
vp exec oxlint-vize -c .oxlintrc.json -f stylish srcoxlint-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.
Limitations
- Raw
oxlintstill misses files without<script>or<script setup>. It also anchors template diagnostics to an extracted script instead of the original template location. Useoxlint-vizewhen linting Vue and HTML files. - The wrapper's temporary files are not copied back when Oxlint fix flags are used; it warns when
--fixor--fix-suggestionsis passed. - With Oxlint 1.78 and an explicit complete JSON config containing
ignorePatternsoroverrides, the wrapper retains original file selection and ordered Vue overrides. This mode supports POSIX paths; inherited configs, import/type-aware project resolution, suppression state, ambiguous filenames, unsupported outside-root patterns and unknown option forms produce actionable errors. - Discovered, nested and dynamic configurations keep the existing wrapper path; the scoped JSON mode does not establish equivalent filtering for those configurations or other Oxlint versions.
- Oxlint core rules that need JavaScript bindings extracted from Vue templates, such as template-aware unused-variable checks, still depend on upstream work in Oxc's Better Vue Support.
- Direct
oxlintoriginal-SFC ranges depend on the JS plugin reporting work tracked in oxc-project/oxc#20465. - Type-aware Vize rules are experimental and excluded from the default exported configs. Opt into them explicitly with
configs.recommendedWithTypeAware,configs.opinionatedWithTypeAware, orcreateVizeRuleConfig({ includeTypeAware: true, preset: ... }), and usesettings.vize.typeAware: truewhen you want the shared full-file pass to run them eagerly.
Current expectations
- This release is meant for terminal-first workflows.
- The
oxlint-vizewrapper reports original Patina positions instylishandjson; directoxlintremains limited by its extracted-script JS plugin input. - Once Oxlint preserves original Vue positions for JS plugins, the temporary location bridge can be removed.
