Vize

跨文件规则

本页集中列出所有相关规则的用途、配置、错误示例和正确示例,无需跳转页面查看对照。高亮行标示修改;复制代码时保留完整源代码。每条规则注明适用范围和当前支持限制;规范示例并不保证当前实现会产生诊断。

本页完整展示 66 项项目检查的用途、共享项目文件,以及错误和正确示例的全部修改。两组示例都需使用各项提供的共享文件,并遵循依赖及版本说明。项目检查需要完整的已分析组件图。60 个公开跨文件诊断代码具有不同支持边界:19 个属于 CLI 分析遍(18 组已验证的源代码对照,以及一项附响应式流图的说明性 Vue 项目);16 个有实验性 Rust 分析器实现,但该 CLI 分析遍不会单独发出这些代码;25 个仅是目前没有诊断实现的公开约定。说明性源代码和保留图的验证条件分别注明;启用规则 ID 不会激活尚不可用的实现。

公共 CLI 可通过 vize lint --cross-file 执行同一检查。显示的 vize:croquis/cf/* 代码在 vue.linter.rules 中写为 croquis/cf/*(省略 vize:)。信息和提示诊断会转换为 CLI 警告。相关位置说明来源与消费者之间的关系。

规则 示例 用途 当前支持情况
ecosystem/vue-router-extra-param 错误示例 · 正确示例 路由未声明 tab;Vue Router 会丢弃它。 项目专属 lint ID
ecosystem/vue-router-missing-param 错误示例 · 正确示例 缺少必需的 postId;依赖当前路由并不稳健。 项目专属 lint ID
ecosystem/vue-router-param-type 错误示例 · 正确示例 postId 不是可重复参数,因此数组无效。 项目专属 lint ID
ecosystem/vue-router-unknown-route 错误示例 · 正确示例 名称不在完整且已安装的路由器中。 项目专属 lint ID
html/cross-component-nesting 错误示例 · 正确示例 检查导入组件组合后的实际 HTML 嵌套。 项目专属 lint ID
vize:croquis/cf/array-mutation 错误示例 · 正确示例 通过索引修改数组,无法被该响应式数组跟踪。 诊断约定;目前没有实现
vize:croquis/cf/async-boundary 错误示例 · 正确示例 响应式状态跨越异步边界,可能被观察为过期值。 CLI
vize:croquis/cf/async-no-suspense 错误示例 · 正确示例 异步组件在没有 Suspense 边界的情况下渲染。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/browser-api-ssr 错误示例 · 正确示例 在组件可能于服务端渲染的位置使用了浏览器专用 API。 CLI
vize:croquis/cf/circular-dep 错误示例 · 正确示例 组件相互导入,形成循环。 诊断约定;目前没有实现
vize:croquis/cf/circular-reactive-dependency 错误示例 · 正确示例 响应式计算相互依赖,形成循环。 CLI
vize:croquis/cf/closure-captures-reactive 错误示例 · 正确示例 闭包捕获了响应式值,无法看到后续更新。 诊断约定;目前没有实现
vize:croquis/cf/composable-outside-setup 错误示例 · 正确示例 组合式函数在 setup 外被调用。 诊断约定;目前没有实现
vize:croquis/cf/computed-side-effects 错误示例 · 正确示例 计算 getter 写入状态或执行其他副作用。 诊断约定;目前没有实现
vize:croquis/cf/deep-import 错误示例 · 正确示例 导入链超过项目允许的深度。 诊断约定;目前没有实现
vize:croquis/cf/destructuring-breaks-reactivity 错误示例 · 正确示例 解构响应式对象会复制字段并丢失跟踪。 CLI
vize:croquis/cf/di-outside-setup 错误示例 · 正确示例 provide 或 inject 在 setup 外被调用。 诊断约定;目前没有实现
vize:croquis/cf/dom-access-without-next-tick 错误示例 · 正确示例 在 Vue 完成更新刷新之前读取 DOM。 诊断约定;目前没有实现
vize:croquis/cf/duplicate-id 错误示例 · 正确示例 多个组件使用同一个元素 id。 CLI
vize:croquis/cf/event-listener-leak 错误示例 · 正确示例 注册了事件监听器,却从未移除。 诊断约定;目前没有实现
vize:croquis/cf/event-modifier 错误示例 · 正确示例 事件监听器使用了发出事件不支持的修饰符。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/hydration-risk 错误示例 · 正确示例 此诊断代码归类多种响应性问题,包括将 prop 复制到 ref。它并不表示跨文件分析遍会检测所有 Date.now() 表达式。 CLI
vize:croquis/cf/inherit-attrs-unused 错误示例 · 正确示例 设置了 inheritAttrs: false,但组件从不读取属性。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/inject-without-symbol 错误示例 · 正确示例 inject 使用普通键,而不是 InjectionKey symbol。 CLI
vize:croquis/cf/injected-async-mutation-race 错误示例 · 正确示例 注入值被可能发生竞态的异步任务修改。 CLI
vize:croquis/cf/lifecycle-outside-setup 错误示例 · 正确示例 生命周期钩子在 setup 外注册。 诊断约定;目前没有实现
vize:croquis/cf/lifecycle-without-cleanup 错误示例 · 正确示例 生命周期钩子启动任务,却从不清理。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/missing-required-prop 错误示例 · 正确示例 未传入必需的 prop。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/missing-suspense 错误示例 · 正确示例 异步依赖在 Suspense 边界之外使用。 诊断约定;目前没有实现
vize:croquis/cf/module-scope-reactive 错误示例 · 正确示例 响应式状态在模块作用域创建,被所有调用者共享。 诊断约定;目前没有实现
vize:croquis/cf/multi-root-attrs 错误示例 · 正确示例 多根节点组件接收了属性,却没有放置目标。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/mutated-after-escape 错误示例 · 正确示例 响应式对象逸出其所有者后被修改。 诊断约定;目前没有实现
vize:croquis/cf/non-reactive-provide 错误示例 · 正确示例 提供的值不具响应性,后代无法看到更新。 CLI
vize:croquis/cf/non-unique-id 错误示例 · 正确示例 循环内的元素 id 对每个列表项并不唯一。 CLI
vize:croquis/cf/object-identity-comparison 错误示例 · 正确示例 响应式对象按身份比较,解包后身份会改变。 诊断约定;目前没有实现
vize:croquis/cf/pinia-getter 错误示例 · 正确示例 Pinia getter 未通过 storeToRefs 读取,无法保持响应性。 诊断约定;目前没有实现
vize:croquis/cf/prop-type-mismatch 错误示例 · 正确示例 传入的 prop 值与声明类型不匹配。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/provide-inject-type 错误示例 · 正确示例 提供值与其 inject 类型不一致。 CLI
vize:croquis/cf/provide-without-symbol 错误示例 · 正确示例 provide 使用普通键,而不是 InjectionKey symbol。 CLI
vize:croquis/cf/reactive-export 错误示例 · 正确示例 响应式状态从模块导出。 诊断约定;目前没有实现
vize:croquis/cf/reactivity-outside-setup 错误示例 · 正确示例 响应式 API 在 setup 外被调用。 诊断约定;目前没有实现
vize:croquis/cf/reassignment-breaks-reactivity 错误示例 · 正确示例 重新赋值响应式绑定,将其替换为普通值。 CLI
vize:croquis/cf/reference-escapes-scope 错误示例 · 正确示例 响应式引用逸出拥有其生命周期的作用域。 诊断约定;目前没有实现
vize:croquis/cf/setup-context-violation 错误示例 · 正确示例 setup 上下文被以 Vue 不允许的方式使用。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/shallow-deep-access 错误示例 · 正确示例 读取 shallowReactive 或 shallowRef 的深层属性,却假定它受到跟踪。 诊断约定;目前没有实现
vize:croquis/cf/spread-breaks-reactivity 错误示例 · 正确示例 展开响应式对象会复制值并丢失跟踪。 CLI
vize:croquis/cf/suspense-no-fallback 错误示例 · 正确示例 <Suspense> 没有后备内容。 诊断约定;目前没有实现
vize:croquis/cf/template-ref-timing 错误示例 · 正确示例 模板 ref 在组件挂载之前被读取。 诊断约定;目前没有实现
vize:croquis/cf/toraw-mutation 错误示例 · 正确示例 使用 toRaw 后修改了原始对象。 诊断约定;目前没有实现
vize:croquis/cf/uncaught-error 错误示例 · 正确示例 组件可能抛错,却没有错误边界捕获。 CLI
vize:croquis/cf/undeclared-emit 错误示例 · 正确示例 组件发出了未声明的事件。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/undeclared-prop 错误示例 · 正确示例 父组件传入了子组件未声明的 prop。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/undefined-slot 错误示例 · 正确示例 父组件填充了子组件未暴露的插槽。 诊断约定;目前没有实现
vize:croquis/cf/unhandled-event 错误示例 · 正确示例 子组件发出的事件没有父组件处理。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/unmatched-inject 错误示例 · 正确示例 inject 指定的键没有任何祖先提供。 CLI
vize:croquis/cf/unmatched-listener 错误示例 · 正确示例 父组件监听了子组件不发出的事件。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/unregistered-component 错误示例 · 正确示例 模板使用了未注册或导入的组件。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/unresolved-import 错误示例 · 正确示例 导入无法解析为模块。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/unused-attrs 错误示例 · 正确示例 透传属性传给了未使用它们的多根节点组件。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/unused-emit 错误示例 · 正确示例 已声明的 emit 从未使用。 实验性 Rust 分析器;CLI 不会单独发出此代码
vize:croquis/cf/unused-provide 错误示例 · 正确示例 提供的键从未被注入。 CLI
vize:croquis/cf/value-extraction-breaks-reactivity 错误示例 · 正确示例 将响应式值读取到局部变量后丢失后续更新。 CLI
vize:croquis/cf/watch-can-be-computed 错误示例 · 正确示例 侦听器只将值复制到状态,可以改为计算属性。 诊断约定;目前没有实现
vize:croquis/cf/watcheffect-async 错误示例 · 正确示例 watchEffect 启动异步任务,却无法清理上一轮执行。 CLI
vize:croquis/cf/watcher-outside-setup 错误示例 · 正确示例 watch 或 watchEffect 在 setup 外被调用。 诊断约定;目前没有实现
vue/cross-file-attrs-fallthrough 错误示例 · 正确示例 父组件传入属性,但已解析子组件的根无法继承它们,也没有显式使用 $attrs。 项目专属 lint ID

全部规则 · 规则选项 · ESLint 迁移对应表 · 项目检查 · 组件间属性传递

ecosystem/vue-router-extra-param

路由未声明 tab;Vue Router 会丢弃它。

默认严重程度: error
适用范围: 可达的项目声明及导入组件
选项: crossFile;规则严重程度(off/warn/error)
自动修复: 无

完整且已安装的路由器必须从应用的 createApp(...).use(router) 可达。未知或动态路由表不能证明未知名称诊断。缺失参数属于警告,因为导航可能从当前路由继承值。

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "ecosystem/vue-router-extra-param": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

index.html

<div id="app"></div>
<script type="module" src="/src/main.ts"></script>

src/main.ts

import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
createApp(App).use(router).mount("#app");

src/App.vue

<script setup lang="ts">
import { RouterView } from "vue-router";
</script>
<template><RouterView /></template>

src/router.ts

import { createRouter, createWebHistory } from "vue-router";
import UserPost from "./UserPost.vue";
export const router = createRouter({
  history: createWebHistory(),
  routes: [{ path: "/users/:userId/posts/:postId", name: "user-post", component: UserPost }],
});

错误示例

user-post 路径声明 userId 和 postId,但导航还将未声明的 tab 作为路径参数传入。

src/UserPost.vue

<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: "2", tab: "a" } });
</script>
<template><p>Post</p></template>

正确示例

从 params 移除 tab,只保留路由路径中的键。应用需要选择标签页时,应另行使用 query。

src/UserPost.vue

<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: "2" } });
</script>
<template><p>Post</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

跨文件规则索引

ecosystem/vue-router-missing-param

缺少必需的 postId;依赖当前路由并不稳健。

默认严重程度: warning
适用范围: 可达的项目声明及导入组件
选项: crossFile;规则严重程度(off/warn/error)
自动修复: 无

完整且已安装的路由器必须从应用的 createApp(...).use(router) 可达。未知或动态路由表不能证明未知名称诊断。缺失参数属于警告,因为导航可能从当前路由继承值。

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "ecosystem/vue-router-missing-param": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

index.html

<div id="app"></div>
<script type="module" src="/src/main.ts"></script>

src/main.ts

import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
createApp(App).use(router).mount("#app");

src/App.vue

<script setup lang="ts">
import { RouterView } from "vue-router";
</script>
<template><RouterView /></template>

src/router.ts

import { createRouter, createWebHistory } from "vue-router";
import UserPost from "./UserPost.vue";
export const router = createRouter({
  history: createWebHistory(),
  routes: [{ path: "/users/:userId/posts/:postId", name: "user-post", component: UserPost }],
});

错误示例

导航省略了 user-post 路径必需的 postId。这是警告,因为 Vue Router 可能从当前路由继承该值。

src/UserPost.vue

<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1" } });
</script>
<template><p>Post</p></template>

正确示例

显式传入 userId 和 postId,使导航不依赖当前路由的参数状态。

src/UserPost.vue

<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: "2" } });
</script>
<template><p>Post</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

跨文件规则索引

ecosystem/vue-router-param-type

postId 不是可重复参数,因此数组无效。

默认严重程度: error
适用范围: 可达的项目声明及导入组件
选项: crossFile;规则严重程度(off/warn/error)
自动修复: 无

完整且已安装的路由器必须从应用的 createApp(...).use(router) 可达。未知或动态路由表不能证明未知名称诊断。缺失参数属于警告,因为导航可能从当前路由继承值。

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "ecosystem/vue-router-param-type": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

index.html

<div id="app"></div>
<script type="module" src="/src/main.ts"></script>

src/main.ts

import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
createApp(App).use(router).mount("#app");

src/App.vue

<script setup lang="ts">
import { RouterView } from "vue-router";
</script>
<template><RouterView /></template>

src/router.ts

import { createRouter, createWebHistory } from "vue-router";
import UserPost from "./UserPost.vue";
export const router = createRouter({
  history: createWebHistory(),
  routes: [{ path: "/users/:userId/posts/:postId", name: "user-post", component: UserPost }],
});

错误示例

postId 是标量路径参数,但导航传入了数组 ["2"]。

src/UserPost.vue

<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: ["2"] } });
</script>
<template><p>Post</p></template>

正确示例

为不可重复的 postId 路径段传入标量 "2"。

src/UserPost.vue

<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: "2" } });
</script>
<template><p>Post</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

跨文件规则索引

ecosystem/vue-router-unknown-route

名称不在完整且已安装的路由器中。

默认严重程度: error
适用范围: 可达的项目声明及导入组件
选项: crossFile;规则严重程度(off/warn/error)
自动修复: 无

完整且已安装的路由器必须从应用的 createApp(...).use(router) 可达。未知或动态路由表不能证明未知名称诊断。缺失参数属于警告,因为导航可能从当前路由继承值。

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "ecosystem/vue-router-unknown-route": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

index.html

<div id="app"></div>
<script type="module" src="/src/main.ts"></script>

src/main.ts

import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
createApp(App).use(router).mount("#app");

src/App.vue

<script setup lang="ts">
import { RouterView } from "vue-router";
</script>
<template><RouterView /></template>

src/router.ts

import { createRouter, createWebHistory } from "vue-router";
import UserPost from "./UserPost.vue";
export const router = createRouter({
  history: createWebHistory(),
  routes: [{ path: "/users/:userId/posts/:postId", name: "user-post", component: UserPost }],
});

错误示例

可达且已安装的路由器声明了 user-post,但导航使用拼写错误的 user-posts。

src/UserPost.vue

<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-posts", params: { userId: "1", postId: "2" } });
</script>
<template><p>Post</p></template>

正确示例

使用已注册的 user-post 名称,并保留两个已声明的路径参数。

src/UserPost.vue

<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: "2" } });
</script>
<template><p>Post</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

跨文件规则索引

html/cross-component-nesting

检查导入组件组合后的实际 HTML 嵌套。

默认严重程度: warning
适用范围: 可达的项目声明及导入组件
选项: crossFile;规则严重程度(off/warn/error)
自动修复: 无

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "html/cross-component-nesting": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

父组件的 <p> 包含根为 <div> 的已解析子组件,组合后形成无效的段落与块级元素嵌套。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><p><Child /></p></template>

Child.vue

<template><div>Block content</div></template>

正确示例

改用可容纳子组件块级元素的 <section> 容器;子组件保持不变。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><section><Child /></section></template>

Child.vue

<template><div>Block content</div></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

跨文件规则索引

vize:croquis/cf/array-mutation

通过索引修改数组,无法被该响应式数组跟踪。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

仅适用于历史 Vue 2.7:此情形须使用匹配的 Vue 2.7 和 SFC 编译器依赖。Vue 3 代理跟踪数组索引赋值,因此 items[0] = next 在 Vue 3 中具有响应性,并非 Vue 3 缺陷。此公开诊断代码目前没有实现。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import Vue from 'vue';
import App from './App.vue';
new Vue({ render: h => h(App) }).$mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

App.vue

<script lang="ts">
import Vue from 'vue';
import { replaceFirst } from './replace-first';
export default Vue.extend({
  data() { return { items: ['Before'] }; },
  methods: { replace() { replaceFirst(this.items, 'After'); } },
});
</script>
<template><section><p>{{ items[0] }}</p><button @click="replace">Replace</button></section></template>

错误示例

在此历史 Vue 2.7 项目中,items[0] = next 修改数组却不通知 Vue 2 数组观察器,因此显示的首项不一定更新。

replace-first.ts

export function replaceFirst(items: string[], next: string): void {
  items[0] = next;
}

正确示例

splice(0, 1, next) 使用 Vue 2 可观察的数组修改方法,使同样的替换能更新视图。

replace-first.ts

export function replaceFirst(items: string[], next: string): void {
  items.splice(0, 1, next);
}

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/async-boundary

响应式状态跨越异步边界,可能被观察为过期值。

默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/async-boundary": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./SearchPage.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

api.ts

export interface Result { items: string[]; }
export async function load(query: string, options?: { signal?: AbortSignal }): Promise<Result> {
  const response = await fetch(`/search?q=${encodeURIComponent(query)}`, options);
  return response.json();
}

错误示例

较慢的旧查询可能在新查询之后完成并覆盖 result,因为侦听器没有失效清理。

SearchPage.vue

<script setup lang="ts">
import { ref } from "vue";
import SearchResults from "./SearchResults.vue";

const query = ref("");
</script>

<template>
  <SearchResults :query="query" />
</template>

SearchResults.vue

<script setup lang="ts">
import { load, type Result } from "./api";
import { ref, watch } from "vue";

const props = defineProps<{ query: string }>();
const result = ref<Result | null>(null);

watch(
  () => props.query,
  async (value) => {
    result.value = await load(value);
  },
);
</script>

正确示例

在等待之前注册清理:中止旧请求并将其 active 标志设为失效,只赋值仍有效的响应。

SearchPage.vue

<script setup lang="ts">
import { ref } from "vue";
import SearchResults from "./SearchResults.vue";

const query = ref("");
</script>

<template>
  <SearchResults :query="query" />
</template>

SearchResults.vue

<script setup lang="ts">
import { load, type Result } from "./api";
import { ref, watch } from "vue";

const props = defineProps<{ query: string }>();
const result = ref<Result | null>(null);

watch(
  () => props.query,
  async (value, _oldValue, onCleanup) => {
    const controller = new AbortController();
    let active = true;

    onCleanup(() => {
      active = false;
      controller.abort();
    });

    const next = await load(value, { signal: controller.signal });
    if (active) result.value = next;
  },
);
</script>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/async-no-suspense

异步组件在没有 Suspense 边界的情况下渲染。

默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

当前支持情况: no-source-async-fact

边界诊断实现读取 macros.is_async(),但当前源代码解析将顶层 await 记录在 script-setup 作用域。因此,下方完整的错误与正确源代码对照不会通过当前 CLI 产生 async-no-suspense 诊断。它解释 Suspense 规范;补充缺失宏事实属于后续实现工作。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

子组件具有顶层 await,但父组件未提供 <Suspense> 边界。当前源代码解析未提供发出此诊断代码所需的宏事实。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>

Child.vue

<script setup lang="ts">
const greeting = await Promise.resolve("Hello");
</script>
<template><p>{{ greeting }}</p></template>

正确示例

父组件将同一异步子组件包在 <Suspense> 中,并提供加载后备内容。这展示规范;当前分析遍对两种源代码形式都不发出该诊断。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Suspense><Child /><template #fallback><p>Loading</p></template></Suspense></template>

Child.vue

<script setup lang="ts">
const greeting = await Promise.resolve("Hello");
</script>
<template><p>{{ greeting }}</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/browser-api-ssr

在组件可能于服务端渲染的位置使用了浏览器专用 API。

默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/browser-api-ssr": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

window.innerWidth 在 setup 期间运行,但 SSR 环境没有浏览器 window。

App.vue

<script setup lang="ts">
const width = window.innerWidth;
</script>
<template><p>Content</p></template>

正确示例

将 ref 初始化为服务端安全的值,并在客户端挂载后运行的 onMounted 中读取 window。

App.vue

<script setup lang="ts">
import { onMounted, ref } from "vue";
const width = ref(0);
onMounted(() => { width.value = window.innerWidth; });
</script>
<template><p>Content</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/circular-dep

组件相互导入,形成循环。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

此示例展示具体的立即初始化循环。递归 Vue 组件或任意循环导入并不自动构成错误。目前没有实现发出此诊断约定代码。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

App.vue

<script setup lang="ts">
import { aLabel } from './a';
</script>

<template>
<p>{{ aLabel }}</p>
</template>

labels.ts

export const aPrefix = 'A';
export const bPrefix = 'B';

错误示例

a.ts 导入 b.ts,后者又导入 a.ts。两者都立即读取另一模块尚未初始化的常量来初始化自身常量,导致暂时性死区错误。

a.ts

import { bLabel } from './b';
export const aLabel = 'A' + bLabel;

b.ts

import { aLabel } from './a';
export const bLabel = 'B' + aLabel;

正确示例

两个模块都从独立的 labels.ts 读取已初始化前缀,移除循环及立即发生的交叉读取。

a.ts

import { bPrefix } from './labels';
export const aLabel = 'A' + bPrefix;

b.ts

import { aPrefix } from './labels';
export const bLabel = 'B' + aPrefix;

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/circular-reactive-dependency

响应式计算相互依赖,形成循环。

默认严重程度: 依上下文而定
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/circular-reactive-dependency": "warn" },
    },
  },
});
vp run lint

示例验证状态: illustrative-source-pair

下方完整 Vue 项目展示更新反馈及其修复,但不构成已验证的 CLI 诊断复现:诊断实现需要附图所示的保留响应式流引用身份和边。这些源代码不能证明当前源代码路径会发出此确切代码。专门的跟踪 ID 图诊断对照仍与源代码语法检查分开。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

count-key.ts

import type { InjectionKey, Ref } from 'vue';
export const countKey: InjectionKey<Ref<number>> = Symbol('count');

App.vue

<script setup lang="ts">
import { provide, ref } from 'vue';
import { countKey } from './count-key';
import CycleView from './CycleView.vue';
const count = ref(1); // A: the provider-owned source.
provide(countKey, count);
</script>
<template>
  <button @click="count++">Increment</button>
  <CycleView />
</template>

错误示例

App 拥有并提供 count(A)。CycleView 派生 nextCount(B),随后立即将每个派生值写回同一注入的 count。每次写入再次改变计算输入,形成 A → B → A 更新反馈。下方保留图中的身份代表这两个引用,而非仅名称相同的无关绑定。

CycleView.vue

<script setup lang="ts">
import { computed, inject, watch } from 'vue';
import { countKey } from './count-key';
const count = inject(countKey)!; // App provides this same A reference.
const nextCount = computed(() => count.value + 1); // B: the derived consumer.
watch(nextCount, value => { count.value = value; }, { immediate: true });
</script>
<template><p>{{ nextCount }}</p></template>
Tracked references: A = provider source; B = consumer reference
Tracked flows: A -> B; B -> A

正确示例

移除将 B 写回 A 的侦听器。App 保留 count 的所有权,仅通过显式 Increment 操作改变它;CycleView 读取派生 nextCount,不将结果反馈回去。同样的引用只保留 A → B 依赖。

CycleView.vue

<script setup lang="ts">
import { computed, inject } from 'vue';
import { countKey } from './count-key';
const count = inject(countKey)!; // App provides this same A reference.
const nextCount = computed(() => count.value + 1); // B: the derived consumer.
</script>
<template><p>{{ nextCount }}</p></template>
Tracked references: A = provider source; B = consumer reference
Tracked flows: A -> B

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/closure-captures-reactive

闭包捕获了响应式值,无法看到后续更新。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

App.vue

<script setup lang="ts">
import { computed, ref } from 'vue';
import { makeReader } from './reader';
const count = ref(0);
const read = makeReader(count);
const shown = computed(read);
</script>

<template>
<button @click="count++">Increment {{ count }}</button><p>{{ shown }}</p>
</template>

错误示例

makeReader 在创建闭包前复制 count.value。计算读取器随后返回初始数字,没有读取响应式依赖。

reader.ts

import type { Ref } from 'vue';
export function makeReader(count: Ref<number>): () => number {
  const captured = count.value;
  return () => captured;
}

正确示例

闭包在调用时读取 count.value,使计算 getter 能跟踪 ref,并在递增后更新 shown。

reader.ts

import type { Ref } from 'vue';
export function makeReader(count: Ref<number>): () => number {
  return () => count.value;
}

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/composable-outside-setup

组合式函数在 setup 外被调用。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

问题针对这个依赖生命周期的组合式函数,并非全面禁止普通工具函数或 setup 外的所有 Composition API 调用。此诊断约定目前没有实现。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

App.vue

<script setup lang="ts">
import { useTitle } from './use-title';
const title = useTitle();
</script>

<template>
<h1>{{ title }}</h1>
</template>

错误示例

导入 use-title.ts 时,在组件 setup 激活前注册了 onMounted。稍后调用导出函数仅返回模块级 ref,无法补救错失的生命周期所有权。

use-title.ts

import { onMounted, ref } from 'vue';
const title = ref('Before mount');
onMounted(() => { title.value = 'Mounted'; });
export function useTitle() { return title; }

正确示例

状态创建和钩子注册都移到 useTitle,由 App 在 setup 中同步调用。挂载钩子现在属于该 App 实例。

use-title.ts

import { onMounted, ref } from 'vue';
export function useTitle() {
  const title = ref('Before mount');
  onMounted(() => { title.value = 'Mounted'; });
  return title;
}

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/computed-side-effects

计算 getter 写入状态或执行其他副作用。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

App.vue

<script setup lang="ts">
import { useDouble } from './use-double';
const { count, doubled, lastCalculated } = useDouble();
</script>

<template>
<button @click="count++">Increment {{ count }}</button><p>{{ doubled }} / {{ lastCalculated }}</p>
</template>

错误示例

求值 doubled 会写入 lastCalculated,因此读取计算值也会修改独立状态,将副作用与惰性 getter 的读取时机耦合。

use-double.ts

import { computed, ref } from 'vue';
export function useDouble() {
  const count = ref(0);
  const lastCalculated = ref(0);
  const doubled = computed(() => {
    const next = count.value * 2;
    lastCalculated.value = next;
    return next;
  });
  return { count, doubled, lastCalculated };
}

正确示例

getter 只返回派生数字。独立侦听器负责在 count 变化时写入 lastCalculated,也包括初始值。

use-double.ts

import { computed, ref, watch } from 'vue';
export function useDouble() {
  const count = ref(0);
  const lastCalculated = ref(0);
  const doubled = computed(() => count.value * 2);
  watch(count, next => { lastCalculated.value = next * 2; }, { immediate: true });
  return { count, doubled, lastCalculated };
}

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/deep-import

导入链超过项目允许的深度。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

这是显式选择的项目布局策略,并未宣称存在受支持的深度阈值或选项。此诊断约定目前没有实现。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

App.vue

<script setup lang="ts">
import { label } from './entry';
</script>

<template>
<p>{{ label }}</p>
</template>

value.ts

export const label = 'Notice';

level-one.ts

export { label } from './level-two';

level-two.ts

export { label } from './level-three';

level-three.ts

export { label } from './value';

public-api.ts

export { label } from './value';

错误示例

入口让简单值经过 level-one、level-two 和 level-three,对期望浅层公共边界的项目形成不必要的深导入链。

entry.ts

export { label } from './level-one';

正确示例

入口使用直接重新导出该值的 public-api.ts。消费者保留相同导入名称,导入链变短。

entry.ts

export { label } from './public-api';

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/destructuring-breaks-reactivity

解构响应式对象会复制字段并丢失跟踪。

默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/destructuring-breaks-reactivity": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./UserPage.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

普通解构 props 对象会复制当前 item 值;这与 Vue 3.5 直接解构 defineProps() 不同。

UserPage.vue

<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";

const user = reactive({ name: "Ada" });
</script>

<template>
  <UserSummary :item="user" />
</template>

UserSummary.vue

<script setup lang="ts">
const props = defineProps<{ item: { name: string } }>();
const { item } = props;
</script>

正确示例

toRef(props, "item") 保留与 props 属性的联系。

UserPage.vue

<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";

const user = reactive({ name: "Ada" });
</script>

<template>
  <UserSummary :item="user" />
</template>

UserSummary.vue

<script setup lang="ts">
import { toRef } from "vue";

const props = defineProps<{ item: { name: string } }>();
const item = toRef(props, "item");
</script>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

诊断实现

诊断实现

跨文件规则索引

vize:croquis/cf/di-outside-setup

provide 或 inject 在 setup 外被调用。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

此示例使用组件 provide/inject。app.provide 和受支持的 app.runWithContext 注入是不同且有效的所有权接口,不受此情形禁止。目前没有实现发出此诊断约定代码。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

theme.ts

import { inject, provide } from 'vue';
import type { InjectionKey } from 'vue';
export const ThemeKey: InjectionKey<string> = Symbol('theme');
export function provideTheme() { provide(ThemeKey, 'dark'); }
export function useTheme() { return inject(ThemeKey, 'light'); }

ThemedText.vue

<script setup lang="ts">
import { useTheme } from './theme';
const theme = useTheme();
</script>

<template>
<p>{{ theme }}</p>
</template>

错误示例

main.ts 在没有激活组件实例时调用组件 provide。子组件的 inject 无法获取预期祖先值,因此使用 light。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
import { provideTheme } from './theme';
provideTheme();
createApp(App).mount('#app');

App.vue

<script setup lang="ts">
import ThemedText from './ThemedText.vue';
</script>

<template>
<ThemedText />
</template>

正确示例

App 在渲染子组件前从 setup 调用提供者。子组件现在从组件祖先继承 dark。

App.vue

<script setup lang="ts">
import ThemedText from './ThemedText.vue';
import { provideTheme } from './theme';
provideTheme();
</script>

<template>
<ThemedText />
</template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/dom-access-without-next-tick

在 Vue 完成更新刷新之前读取 DOM。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

read-label.ts

export function readLabel(node: HTMLElement | null): string {
  return node?.textContent ?? '';
}

错误示例

点击处理器递增 count 后立即读取渲染段落,此时 Vue 尚未刷新计划中的 DOM 更新。sampled 可能包含之前的计数。

App.vue

<script setup lang="ts">
import { ref } from 'vue';
import { readLabel } from './read-label';
const count = ref(0);
const label = ref<HTMLElement | null>(null);
const sampled = ref('');
function increment() {
  count.value++;
  sampled.value = readLabel(label.value);
}
</script>

<template>
<button @click="increment">Increment</button><p ref="label">{{ count }}</p><p>DOM sample: {{ sampled }}</p>
</template>

正确示例

写入状态后等待 nextTick(),让 Vue 先更新段落,再由 readLabel 读取文本。

App.vue

<script setup lang="ts">
import { nextTick, ref } from 'vue';
import { readLabel } from './read-label';
const count = ref(0);
const label = ref<HTMLElement | null>(null);
const sampled = ref('');
async function increment() {
  count.value++;
  await nextTick();
  sampled.value = readLabel(label.value);
}
</script>

<template>
<button @click="increment">Increment</button><p ref="label">{{ count }}</p><p>DOM sample: {{ sampled }}</p>
</template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/duplicate-id

多个组件使用同一个元素 id。

默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/duplicate-id": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./CheckoutForm.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

可达的配送与账单组件都渲染 id="postal-code",使其标签共享不明确的文档目标。

CheckoutForm.vue

<script setup lang="ts">
import BillingAddress from "./BillingAddress.vue";
import ShippingAddress from "./ShippingAddress.vue";
</script>

<template>
  <ShippingAddress />
  <BillingAddress />
</template>

ShippingAddress.vue

<template>
  <label for="postal-code">Shipping postal code</label>
  <input id="postal-code" />
</template>

BillingAddress.vue

<template>
  <label for="postal-code">Billing postal code</label>
  <input id="postal-code" />
</template>

正确示例

每个组件调用 useId(),将自身的值同时绑定给标签和输入框,在不重复字面量 ID 的情况下保留关联。

CheckoutForm.vue

<script setup lang="ts">
import BillingAddress from "./BillingAddress.vue";
import ShippingAddress from "./ShippingAddress.vue";
</script>

<template>
  <ShippingAddress />
  <BillingAddress />
</template>

ShippingAddress.vue

<script setup lang="ts">
import { useId } from "vue";

const postalCodeId = useId();
</script>

<template>
  <label :for="postalCodeId">Shipping postal code</label>
  <input :id="postalCodeId" />
</template>

BillingAddress.vue

<script setup lang="ts">
import { useId } from "vue";

const postalCodeId = useId();
</script>

<template>
  <label :for="postalCodeId">Billing postal code</label>
  <input :id="postalCodeId" />
</template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/event-listener-leak

注册了事件监听器,却从未移除。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

App.vue

<script setup lang="ts">
import { useWidth } from './use-width';
const width = useWidth();
</script>

<template>
<p>{{ width }}</p>
</template>

错误示例

挂载时添加的 window resize 监听器捕获组件 width ref,但卸载时从不移除。重复挂载可能保留未使用的监听器和状态。

use-width.ts

import { onMounted, ref } from 'vue';
export function useWidth() {
  const width = ref(0);
  const resize = () => { width.value = window.innerWidth; };
  onMounted(() => { resize(); window.addEventListener('resize', resize); });
  return width;
}

正确示例

onUnmounted 移除挂载时注册的同一个 resize 函数,结束该实例外部监听器的生命周期。

use-width.ts

import { onMounted, onUnmounted, ref } from 'vue';
export function useWidth() {
  const width = ref(0);
  const resize = () => { width.value = window.innerWidth; };
  onMounted(() => { resize(); window.addEventListener('resize', resize); });
  onUnmounted(() => { window.removeEventListener('resize', resize); });
  return width;
}

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/event-modifier

事件监听器使用了发出事件不支持的修饰符。

默认严重程度: info
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

.stop 假定子组件自定义 save 事件具有原生事件的传播方法,但其载荷不一定是 DOM Event。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child @save.stop="() => {}" /></template>

Child.vue

<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>

正确示例

从自定义事件监听器移除 .stop;需要时在实际 DOM 监听器处理原生传播。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child @save="() => {}" /></template>

Child.vue

<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/hydration-risk

此诊断代码归类多种响应性问题,包括将 prop 复制到 ref。它并不表示跨文件分析遍会检测所有 Date.now() 表达式。

默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/hydration-risk": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

子组件仅初始化一次 ref(props.count),因此局部 count 不再跟随后续父组件 prop 变化。这是当前的 prop 到 ref 诊断产生逻辑,不是普遍的不确定 SSR 示例。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child :count="0" /></template>

Child.vue

<script setup lang="ts">
import { ref } from "vue";
const props = defineProps<{ count: number }>();
const count = ref(props.count);
</script>
<template><p>{{ count }}</p></template>

正确示例

toRef(props, "count") 指向 prop,不再将初始值复制到独立状态。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child :count="0" /></template>

Child.vue

<script setup lang="ts">
import { toRef } from "vue";
const props = defineProps<{ count: number }>();
const count = toRef(props, "count");
</script>
<template><p>{{ count }}</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

诊断实现

跨文件规则索引

vize:croquis/cf/inherit-attrs-unused

设置了 inheritAttrs: false,但组件从不读取属性。

默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

子组件设置 inheritAttrs: false,却从不转发父组件的 class="notice" 属性。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>

Child.vue

<script setup lang="ts">
defineOptions({ inheritAttrs: false });
</script>
<template><main>Content</main></template>

正确示例

保留显式继承控制,将 $attrs 绑定到预期的 <main> 目标。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>

Child.vue

<script setup lang="ts">
defineOptions({ inheritAttrs: false });
</script>
<template><main v-bind="$attrs">Content</main></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/inject-without-symbol

inject 使用普通键,而不是 InjectionKey symbol。

默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/inject-without-symbol": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./ThemeProvider.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

keys/theme.ts

import type { InjectionKey, Ref } from "vue";
export interface Theme { color: string; }
export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");

错误示例

消费者注入无类型字符串键 "theme",没有与提供者共享的 symbol 身份。

ThemeProvider.vue

<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";

const theme = ref({ color: "blue" });
provide("theme", theme);
</script>

<template>
  <ThemeLabel />
</template>

ThemeLabel.vue

<script setup lang="ts">
import { inject } from "vue";

const theme = inject("theme");
</script>

正确示例

消费者和提供者导入同一个 ThemeKey,不重复字符串名称。

ThemeProvider.vue

<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
import { ThemeKey } from "./keys/theme";

const theme = ref({ color: "blue" });
provide(ThemeKey, theme);
</script>

<template>
  <ThemeLabel />
</template>

ThemeLabel.vue

<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";

const theme = inject(ThemeKey);
</script>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/injected-async-mutation-race

注入值被可能发生竞态的异步任务修改。

默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/injected-async-mutation-race": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./StoreProvider.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

api.ts

export async function loadCount(query: string, options?: { signal?: AbortSignal }): Promise<number> {
  const response = await fetch(`/count?q=${encodeURIComponent(query)}`, options);
  return Number(await response.text());
}

CountSummary.vue

<script setup lang="ts">
import { inject } from "vue";
import { StoreKey } from "./keys/store";
const store = inject(StoreKey)!;
</script>
<template><p>{{ store.count }}</p></template>

错误示例

CountLoader.vue 将等待得到的结果直接写入与 CountSummary.vue 共享的注入 store,使过期任务影响两个消费者。

keys/store.ts

import type { InjectionKey } from "vue";

export interface Store {
  count: number;
}

export const StoreKey: InjectionKey<Store> = Symbol("store");

StoreProvider.vue

<script setup lang="ts">
import { provide, reactive } from "vue";
import CountLoader from "./CountLoader.vue";
import CountSummary from "./CountSummary.vue";
import { StoreKey, type Store } from "./keys/store";

const store = reactive<Store>({ count: 0 });
provide(StoreKey, store);
</script>

<template>
  <CountLoader />
  <CountSummary />
</template>

CountLoader.vue

<script setup lang="ts">
import { loadCount } from "./api";
import { inject, ref, watch } from "vue";
import { StoreKey } from "./keys/store";

const store = inject(StoreKey)!;
const query = ref("");

watch(query, async (value) => {
  store.count = await loadCount(value);
});
</script>

正确示例

加载器取消失效任务,只发出有效结果。提供者通过 applyLoadedCount 拥有 store 修改权。

keys/store.ts

import type { InjectionKey } from "vue";

export interface Store {
  count: number;
}

export const StoreKey: InjectionKey<Store> = Symbol("store");

StoreProvider.vue

<script setup lang="ts">
import { provide, reactive } from "vue";
import CountLoader from "./CountLoader.vue";
import CountSummary from "./CountSummary.vue";
import { StoreKey, type Store } from "./keys/store";

const store = reactive<Store>({ count: 0 });
provide(StoreKey, store);

function applyLoadedCount(count: number) {
  store.count = count;
}
</script>

<template>
  <CountLoader @loaded="applyLoadedCount" />
  <CountSummary />
</template>

CountLoader.vue

<script setup lang="ts">
import { loadCount } from "./api";
import { ref, watch } from "vue";

const emit = defineEmits<{ loaded: [count: number] }>();
const query = ref("");

watch(query, async (value, _oldValue, onCleanup) => {
  const controller = new AbortController();
  let active = true;

  onCleanup(() => {
    active = false;
    controller.abort();
  });

  const count = await loadCount(value, { signal: controller.signal });
  if (active) emit("loaded", count);
});
</script>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/lifecycle-outside-setup

生命周期钩子在 setup 外注册。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

install-title.ts

import { onMounted } from 'vue';
export function installTitle() {
  onMounted(() => { document.title = 'Mounted application'; });
}

错误示例

入口在挂载应用前调用 installTitle(),因此 onMounted 在没有激活组件 setup 上下文时注册。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
import { installTitle } from './install-title';
installTitle();
createApp(App).mount('#app');

App.vue

<script setup lang="ts">

</script>

<template>
<p>Application</p>
</template>

正确示例

从 App 的 setup 同步调用同一辅助函数,将生命周期回调关联到该实例的挂载。

App.vue

<script setup lang="ts">
import { installTitle } from './install-title';
installTitle();
</script>

<template>
<p>Application</p>
</template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/lifecycle-without-cleanup

生命周期钩子启动任务,却从不清理。

默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

挂载时注册 window resize 监听器,卸载时却不移除同一回调。

App.vue

<script setup lang="ts">
import { onMounted } from "vue";
const resize = () => {};
onMounted(() => { window.addEventListener("resize", resize); });
</script>
<template><p>Content</p></template>

正确示例

onUnmounted 使用与 addEventListener 相同的事件名称和函数身份移除监听器。

App.vue

<script setup lang="ts">
import { onMounted, onUnmounted } from "vue";
const resize = () => {};
onMounted(() => { window.addEventListener("resize", resize); });
onUnmounted(() => { window.removeEventListener("resize", resize); });
</script>
<template><p>Content</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/missing-required-prop

未传入必需的 prop。

默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

父组件渲染 <Child />,没有提供子组件必需的 title: string prop。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>

Child.vue

<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>

正确示例

title="Hello" 提供已解析子组件声明的必需 prop。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child title="Hello" /></template>

Child.vue

<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/missing-suspense

异步依赖在 Suspense 边界之外使用。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

AsyncCard.vue

<script setup lang="ts">
const message = await Promise.resolve('Ready');
</script>

<template>
<p>{{ message }}</p>
</template>

错误示例

AsyncCard 具有顶层 await,使其 setup 异步,但 App 没有使用 Suspense 边界协调该依赖。

App.vue

<script setup lang="ts">
import AsyncCard from './AsyncCard.vue';
</script>

<template>
<AsyncCard />
</template>

正确示例

App 将异步子组件包在 Suspense 内,在子组件 setup 完成前提供加载后备内容。

App.vue

<script setup lang="ts">
import { Suspense } from 'vue';
import AsyncCard from './AsyncCard.vue';
</script>

<template>
<Suspense><AsyncCard /><template #fallback><p>Loading…</p></template></Suspense>
</template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/module-scope-reactive

响应式状态在模块作用域创建,被所有调用者共享。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

有意共享的应用 store 在模块作用域创建响应式状态是合法的。此示例假定组件与请求隔离;此公开诊断约定目前没有实现。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

App.vue

<script setup lang="ts">
import Counter from './Counter.vue';
</script>

<template>
<Counter /><Counter />
</template>

Counter.vue

<script setup lang="ts">
import { createCounter } from './counter';
const { count } = createCounter();
</script>

<template>
<button @click="count++">{{ count }}</button>
</template>

错误示例

模块仅初始化一次 count,两个 Counter 实例收到同一个 ref。虽然示例期望独立实例状态,点击一个却会改变两个计数器。

counter.ts

import { ref } from 'vue';
const count = ref(0);
export function createCounter() { return { count }; }

正确示例

在 createCounter 内创建 ref,为每个同步 setup 调用提供独立状态对象,使每个按钮拥有自己的计数器。

counter.ts

import { ref } from 'vue';
export function createCounter() {
  const count = ref(0);
  return { count };
}

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/multi-root-attrs

多根节点组件接收了属性,却没有放置目标。

默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

子组件具有 <main> 和 <aside> 两个根节点,Vue 没有可自动接收父组件 class 的唯一根节点。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>

Child.vue

<template><main>Content</main><aside>Help</aside></template>

正确示例

显式将 $attrs 转发到 <main>,同时保留第二个根节点。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>

Child.vue

<template><main v-bind="$attrs">Content</main><aside>Help</aside></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/mutated-after-escape

响应式对象逸出其所有者后被修改。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

这是显式的不可变历史所有权策略,并非普遍禁止传递或随后修改响应式对象。目前没有实现发出此诊断约定。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

archive.ts

export interface Profile { name: string }
const records: Readonly<Profile>[] = [];
export function publish(profile: Readonly<Profile>): void { records.push(profile); }
export function latestName(): string { return records.at(-1)?.name ?? ''; }

App.vue

<script setup lang="ts">
import { publishProfile } from './profile';
import { latestName } from './archive';
publishProfile();
const archivedName = latestName();
</script>

<template>
<p>Archived name: {{ archivedName }}</p>
</template>

错误示例

归档保留传给 publish 的同一对象,所有者随后修改其名称,使本应属于历史的记录也变成 Grace。TypeScript 的 Readonly 参数不会复制对象。

profile.ts

import { reactive } from 'vue';
import { publish } from './archive';
export function publishProfile(): void {
  const profile = reactive({ name: 'Ada' });
  publish(profile);
  profile.name = 'Grace';
}

正确示例

发布普通副本,将归档的 Ada 记录与之后对响应式 profile 的编辑分开,维护归档快照策略。

profile.ts

import { reactive } from 'vue';
import { publish } from './archive';
export function publishProfile(): void {
  const profile = reactive({ name: 'Ada' });
  publish({ ...profile });
  profile.name = 'Grace';
}

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/non-reactive-provide

提供的值不具响应性,后代无法看到更新。

默认严重程度: 依上下文而定
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/non-reactive-provide": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./ThemeProvider.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

ThemeProvider.vue 提供普通对象。修改其字段不会为注入消费者建立 Vue 响应式依赖。

keys/theme.ts

export const ThemeKey = Symbol("theme");

ThemeProvider.vue

<script setup lang="ts">
import { provide } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
import { ThemeKey } from "./keys/theme";

const theme = { color: "blue" };
provide(ThemeKey, theme);
</script>

<template>
  <ThemeLabel />
</template>

ThemeLabel.vue

<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";

const theme = inject(ThemeKey);
</script>

正确示例

提供者将 theme 包装在 ref 内;同一个注入引用可以跟踪后续变化。

keys/theme.ts

export const ThemeKey = Symbol("theme");

ThemeProvider.vue

<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
import { ThemeKey } from "./keys/theme";

const theme = ref({ color: "blue" });
provide(ThemeKey, theme);
</script>

<template>
  <ThemeLabel />
</template>

ThemeLabel.vue

<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";

const theme = inject(ThemeKey);
</script>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/non-unique-id

循环内的元素 id 对每个列表项并不唯一。

默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/non-unique-id": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./ResultsList.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

每次 v-for 迭代都渲染相同的 result-title 字面量 ID;循环的 key 不会让 DOM ID 唯一。

ResultsList.vue

<script setup lang="ts">
const results = [{ id: "first", title: "First result" }, { id: "second", title: "Second result" }];
</script>
<template>
  <article v-for="result in results" :key="result.id">
    <h2 id="result-title">{{ result.title }}</h2>
  </article>
</template>

正确示例

标题 ID 包含结果的稳定 ID,为每个列表项产生不同的文档标识符。

ResultsList.vue

<script setup lang="ts">
const results = [{ id: "first", title: "First result" }, { id: "second", title: "Second result" }];
</script>
<template>
  <article v-for="result in results" :key="result.id">
    <h2 :id="`result-${result.id}-title`">{{ result.title }}</h2>
  </article>
</template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/object-identity-comparison

响应式对象按身份比较,解包后身份会改变。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

此示例假定 ID 唯一标识记录。比较指向同一响应式代理的两个引用仍然有效;此诊断约定目前没有实现。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

user.ts

import { reactive } from 'vue';
export function makeUser() {
  const raw = { id: 7, name: 'Ada' };
  return { raw, proxy: reactive(raw) };
}

错误示例

proxy === raw 比较包装对象身份,因此即使二者代表同一用户记录,结果也是 false。应用希望比较记录身份,而非包装对象身份。

App.vue

<script setup lang="ts">
import { makeUser } from './user';
const { raw, proxy } = makeUser();
const sameRecord = proxy === raw;
</script>

<template>
<p>Same record: {{ sameRecord }}</p>
</template>

正确示例

比较稳定的记录 id,回答预期问题,不依赖对象是原始对象还是代理。

App.vue

<script setup lang="ts">
import { makeUser } from './user';
const { raw, proxy } = makeUser();
const sameRecord = proxy.id === raw.id;
</script>

<template>
<p>Same record: {{ sameRecord }}</p>
</template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/pinia-getter

Pinia getter 未通过 storeToRefs 读取,无法保持响应性。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

必须安装 Pinia,且 main.ts 须在挂载前安装其插件。在受跟踪的计算或模板内直接读取 store.doubled 是有效的;此处问题是取普通快照。该诊断约定目前没有实现。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
createApp(App).use(createPinia()).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

counter-store.ts

import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: { doubled: state => state.count * 2 },
});

错误示例

const doubled = store.doubled 在 setup 时复制 getter 的当前数字。复制的数字不会跟随后续 store.count 更新。

App.vue

<script setup lang="ts">
import { useCounterStore } from './counter-store';
const store = useCounterStore();
const doubled = store.doubled;
</script>

<template>
<button @click="store.count++">{{ store.count }}</button><p>{{ doubled }}</p>
</template>

正确示例

storeToRefs(store) 提供响应式 getter ref,可被解构并由模板解包,同时继续关联 store。

App.vue

<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useCounterStore } from './counter-store';
const store = useCounterStore();
const { doubled } = storeToRefs(store);
</script>

<template>
<button @click="store.count++">{{ store.count }}</button><p>{{ doubled }}</p>
</template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/prop-type-mismatch

传入的 prop 值与声明类型不匹配。

默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

父组件将数字表达式 42 传给已解析子组件的 title: string prop。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child :title="42" /></template>

Child.vue

<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>

正确示例

字面量 title="Hello" 提供符合子组件声明的字符串。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child title="Hello" /></template>

Child.vue

<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/provide-inject-type

提供值与其 inject 类型不一致。

默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/provide-inject-type": "warn" },
    },
  },
});
vp run lint

此检查比较提供者与消费者的显式类型注解,而非推断的字面量值类型。保留示例中提供者的 as string 注解。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

提供者将 title 显式注解为 string,后代却为同一键请求 inject<number>。

App.vue

<script setup lang="ts">
import { provide } from "vue";
import Child from "./Child.vue";
provide("title", "Hello" as string);
</script>
<template><Child /></template>

Child.vue

<script setup lang="ts">
import { inject } from "vue";
const title = inject<number>("title");
</script>
<template><p>Content</p></template>

正确示例

消费者显式的 inject<string> 与提供者注解一致。保留 as string:此诊断产生逻辑比较显式注解,而非推断的字面量类型。

App.vue

<script setup lang="ts">
import { provide } from "vue";
import Child from "./Child.vue";
provide("title", "Hello" as string);
</script>
<template><Child /></template>

Child.vue

<script setup lang="ts">
import { inject } from "vue";
const title = inject<string>("title");
</script>
<template><p>Content</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/provide-without-symbol

provide 使用普通键,而不是 InjectionKey symbol。

默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/provide-without-symbol": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./ThemeProvider.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

两个组件都使用字符串 "theme",无关功能可能意外复用此键。

ThemeProvider.vue

<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";

const theme = ref({ color: "blue" });
provide("theme", theme);
</script>

<template>
  <ThemeLabel />
</template>

ThemeLabel.vue

<script setup lang="ts">
import { inject } from "vue";

const theme = inject("theme");
</script>

正确示例

导出一个带类型的 ThemeKey symbol,并在 provide 和 inject 处导入同一值。创建描述相同但独立的 symbols 并不能建立联系。

ThemeProvider.vue

<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
import { ThemeKey, type Theme } from "./keys/theme";

const theme = ref<Theme>({ color: "blue" });
provide(ThemeKey, theme);
</script>

<template>
  <ThemeLabel />
</template>

ThemeLabel.vue

<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";

const theme = inject(ThemeKey);
</script>

keys/theme.ts

import type { InjectionKey, Ref } from "vue";

export interface Theme {
  color: string;
}

export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/reactive-export

响应式状态从模块导出。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

有意共享的应用 store 可以导出响应式状态。此情形要求状态隔离,并未声称所有响应式导出都无效。目前没有实现发出此诊断约定。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

错误示例

模块导出一个已初始化的响应式对象,所有导入者都收到同一个 count。在请求间共享的 SSR 模块中,这违背示例要求的实例与请求状态隔离。

state.ts

import { reactive } from 'vue';
export const state = reactive({ count: 0 });

App.vue

<script setup lang="ts">
import { state } from './state';
</script>

<template>
<button @click="state.count++">{{ state.count }}</button>
</template>

正确示例

模块导出工厂,由 App 在 setup 内调用。每个实例获得新的响应式 count,而非导出的单例。

state.ts

import { reactive } from 'vue';
export function createState() { return reactive({ count: 0 }); }

App.vue

<script setup lang="ts">
import { createState } from './state';
const state = createState();
</script>

<template>
<button @click="state.count++">{{ state.count }}</button>
</template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/reactivity-outside-setup

响应式 API 在 setup 外被调用。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

Vue 允许在组件 setup 外使用 ref/reactive/computed。此处风险是在显式实例隔离策略下产生非预期所有权与共享,并非 API 不合法。此诊断约定目前没有实现。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

App.vue

<script setup lang="ts">
import Counter from './Counter.vue';
</script>

<template>
<Counter /><Counter />
</template>

Counter.vue

<script setup lang="ts">
import { useCounter } from './use-counter';
const { count, doubled } = useCounter();
</script>

<template>
<button @click="count++">{{ count }}</button><p>{{ doubled }}</p>
</template>

错误示例

两个响应式 API 都在模块加载时运行,因此两个 Counter 实例共享一个 ref 和计算值,违背独立计数器的预期。

use-counter.ts

import { computed, ref } from 'vue';
const count = ref(0);
const doubled = computed(() => count.value * 2);
export function useCounter() { return { count, doubled }; }

正确示例

useCounter 在每次组件 setup 调用中同步创建 ref 和计算值,为每个控件提供自己的状态及受跟踪的派生值。

use-counter.ts

import { computed, ref } from 'vue';
export function useCounter() {
  const count = ref(0);
  const doubled = computed(() => count.value * 2);
  return { count, doubled };
}

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/reassignment-breaks-reactivity

重新赋值响应式绑定,将其替换为普通值。

默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/reassignment-breaks-reactivity": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./UserPage.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

子组件创建 prop ref 后用 props.user 覆盖变量,丢弃该 ref 联系。

UserPage.vue

<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";

const user = reactive({ name: "Ada" });
</script>

<template>
  <UserSummary :user="user" />
</template>

UserSummary.vue

<script setup lang="ts">
import { toRef } from "vue";

const props = defineProps<{ user: { name: string } }>();
let user = toRef(props, "user");

user = props.user;
</script>

正确示例

将 toRef 保留在 const 绑定中,移除替换它的重新赋值。

UserPage.vue

<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";

const user = reactive({ name: "Ada" });
</script>

<template>
  <UserSummary :user="user" />
</template>

UserSummary.vue

<script setup lang="ts">
import { toRef } from "vue";

const props = defineProps<{ user: { name: string } }>();
const user = toRef(props, "user");
</script>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/reference-escapes-scope

响应式引用逸出拥有其生命周期的作用域。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

Refs 可以合法地从组合式函数返回或跨作用域共享。此示例显式要求快照缓存,并未声称卸载会使 ref 失效。目前没有实现发出此诊断约定。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

saved.ts

import type { Ref } from 'vue';
let saved: Ref<number> | number | undefined;
export function remember(value: Ref<number> | number): void { saved = value; }
export function remembered(): Ref<number> | number | undefined { return saved; }

错误示例

进程级缓存保留组件的活 count ref,可能让实例状态在卸载后仍可达,并观察后续编辑,但此缓存原本用于存储快照。

App.vue

<script setup lang="ts">
import { ref } from 'vue';
import { remember } from './saved';
const count = ref(0);
remember(count);
</script>

<template>
<button @click="count++">{{ count }}</button>
</template>

正确示例

缓存接收当前普通数字,保留快照而不保留组件拥有的 ref。

App.vue

<script setup lang="ts">
import { ref } from 'vue';
import { remember } from './saved';
const count = ref(0);
remember(count.value);
</script>

<template>
<button @click="count++">{{ count }}</button>
</template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/setup-context-violation

setup 上下文被以 Vue 不允许的方式使用。

默认严重程度: 依上下文而定
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

ref(0) 在普通脚本模块作用域创建,位于此分析器情形所表示的按实例 setup 上下文之外。

App.vue

<script lang="ts">
import { ref } from "vue";
const count = ref(0);
export default {};
</script>
<template><p>Count</p></template>

正确示例

将绑定移入 script setup,使每个组件实例拥有自己的 count,模板也能读取它。

App.vue

<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
</script>
<template><p>{{ count }}</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/shallow-deep-access

读取 shallowReactive 或 shallowRef 的深层属性,却假定它受到跟踪。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

App.vue

<script setup lang="ts">
import { makeProfile } from './profile';
const profile = makeProfile();
</script>

<template>
<p>{{ profile.user.name }}</p><button @click="profile.user.name = 'Grace'">Rename</button>
</template>

错误示例

shallowReactive 跟踪根 user 属性,但嵌套对象仍是原始对象。修改 profile.user.name 不会作为受跟踪的深层修改通知模板。

profile.ts

import { shallowReactive } from 'vue';
export function makeProfile() { return shallowReactive({ user: { name: 'Ada' } }); }

正确示例

深层 reactive 包装嵌套 user 对象,使同样的名称赋值能触发显示名称更新。

profile.ts

import { reactive } from 'vue';
export function makeProfile() { return reactive({ user: { name: 'Ada' } }); }

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/spread-breaks-reactivity

展开响应式对象会复制值并丢失跟踪。

默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/spread-breaks-reactivity": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./UserPage.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

UserSummary.vue 将 props.user 展开为新对象,取得传入响应式数据的快照。

UserPage.vue

<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";

const user = reactive({ name: "Ada", role: "admin" });
</script>

<template>
  <UserSummary :user="user" />
</template>

UserSummary.vue

<script setup lang="ts">
const props = defineProps<{ user: { name: string; role: string } }>();
const copiedUser = { ...props.user };
</script>

正确示例

toRef(props, "user") 保留对传入 prop 的引用,不复制其字段。

UserPage.vue

<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";

const user = reactive({ name: "Ada", role: "admin" });
</script>

<template>
  <UserSummary :user="user" />
</template>

UserSummary.vue

<script setup lang="ts">
import { toRef } from "vue";

const props = defineProps<{ user: { name: string; role: string } }>();
const user = toRef(props, "user");
</script>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/suspense-no-fallback

<Suspense> 没有后备内容。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

没有后备内容的 Suspense 是有效的 Vue 语法。这是选定的加载界面规范,并非编译器错误;此公开诊断约定目前没有实现。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

AsyncCard.vue

<script setup lang="ts">
const message = await Promise.resolve('Ready');
</script>

<template>
<p>{{ message }}</p>
</template>

错误示例

Suspense 边界有异步子组件,却没有后备内容,因此此示例的等待状态没有加载内容。

App.vue

<script setup lang="ts">
import { Suspense } from 'vue';
import AsyncCard from './AsyncCard.vue';
</script>

<template>
<Suspense><AsyncCard /></Suspense>
</template>

正确示例

#fallback 插槽在异步子组件完成前提供显式的加载段落。

App.vue

<script setup lang="ts">
import { Suspense } from 'vue';
import AsyncCard from './AsyncCard.vue';
</script>

<template>
<Suspense><AsyncCard /><template #fallback><p>Loading…</p></template></Suspense>
</template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/template-ref-timing

模板 ref 在组件挂载之前被读取。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

focus-input.ts

export function focusInput(input: HTMLInputElement | null): void { input?.focus(); }

错误示例

setup 在挂载前读取模板 ref,此时值仍为 null,因此可选的 focus 调用不会执行聚焦操作。

App.vue

<script setup lang="ts">
import { ref } from 'vue';
import { focusInput } from './focus-input';
const input = ref<HTMLInputElement | null>(null);
focusInput(input.value);
</script>

<template>
<input ref="input" aria-label="Name" />
</template>

正确示例

onMounted 将读取延迟到 Vue 为模板 ref 赋入输入元素之后,使聚焦辅助函数能作用于该元素。

App.vue

<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { focusInput } from './focus-input';
const input = ref<HTMLInputElement | null>(null);
onMounted(() => { focusInput(input.value); });
</script>

<template>
<input ref="input" aria-label="Name" />
</template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/toraw-mutation

使用 toRaw 后修改了原始对象。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

App.vue

<script setup lang="ts">
import { makeProfile, rename } from './profile';
const profile = makeProfile();
</script>

<template>
<p>{{ profile.name }}</p><button @click="rename(profile)">Rename</button>
</template>

错误示例

rename 获取原始目标并写入 raw.name,绕过本应通知显示名称更新的代理 setter。

profile.ts

import { reactive, toRaw } from 'vue';
export function makeProfile() { return reactive({ name: 'Ada' }); }
export function rename(profile: { name: string }): void {
  const raw = toRaw(profile);
  raw.name = 'Grace';
}

正确示例

通过传入的响应式代理写入 profile.name,保留相同重命名行为,同时通知依赖项。

profile.ts

import { reactive } from 'vue';
export function makeProfile() { return reactive({ name: 'Ada' }); }
export function rename(profile: { name: string }): void {
  profile.name = 'Grace';
}

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/uncaught-error

组件可能抛错,却没有错误边界捕获。

默认严重程度: info
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/uncaught-error": "warn" },
    },
  },
});
vp run lint

当前诊断实现扫描 JSON.parse(input) 等模板表达式,不会报告仅存在于 script 块中的 throw 语句。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

子组件模板对格式错误的输入调用 JSON.parse,可达父组件没有错误捕获边界。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>

Child.vue

<script setup lang="ts">
const input = "{";
</script>
<template><button @click="JSON.parse(input)">Parse</button></template>

正确示例

父组件为该子组件注册 onErrorCaptured。返回 false 会停止传播;生产环境边界还应提供有用的恢复界面。

App.vue

<script setup lang="ts">
import { onErrorCaptured } from "vue";
import Child from "./Child.vue";
onErrorCaptured(() => false);
</script>
<template><Child /></template>

Child.vue

<script setup lang="ts">
const input = "{";
</script>
<template><button @click="JSON.parse(input)">Parse</button></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/undeclared-emit

组件发出了未声明的事件。

默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

子组件调用 emit("save"),但 defineEmits 约定只声明了 cancel。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>

Child.vue

<script setup lang="ts">
const emit = defineEmits<{ cancel: [] }>();
emit("save");
</script>
<template><p>Content</p></template>

正确示例

以空参数元组声明 save,使发出的事件符合组件约定。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>

Child.vue

<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/undeclared-prop

父组件传入了子组件未声明的 prop。

默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

父组件传入 typo,但已解析子组件只声明 title。此分析器规范独立于 Vue 通常的属性透传行为。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child title="Hello" :typo="true" /></template>

Child.vue

<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>

正确示例

移除意外的 typo 绑定,保留已声明的 title prop。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child title="Hello" /></template>

Child.vue

<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/undefined-slot

父组件填充了子组件未暴露的插槽。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

Card.vue

<script setup lang="ts">
defineSlots<{ header(): unknown }>();
</script>

<template>
<article><header><slot name="header" /></header></article>
</template>

错误示例

App 提供 footer 插槽,但 Card 仅声明和渲染 header,传入的 Notice 内容在此子组件中没有匹配的插槽出口。

App.vue

<script setup lang="ts">
import Card from './Card.vue';
</script>

<template>
<Card><template #footer>Notice</template></Card>
</template>

正确示例

App 提供 header,与子组件类型化插槽声明和渲染出口一致,因此 Notice 在该处显示。

App.vue

<script setup lang="ts">
import Card from './Card.vue';
</script>

<template>
<Card><template #header>Notice</template></Card>
</template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/unhandled-event

子组件发出的事件没有父组件处理。

默认严重程度: info
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

Child.vue 发出 save,直属包装组件却没有监听;组件事件不会自动穿过包装组件冒泡。

App.vue

<script setup lang="ts">
import Wrapper from "./Wrapper.vue";
</script>
<template><Wrapper /></template>

Wrapper.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>

Child.vue

<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>

正确示例

Wrapper.vue 为直接子组件附加 save 监听器。空回调展示此规则认可的处理形式,并不是完整保存实现。

App.vue

<script setup lang="ts">
import Wrapper from "./Wrapper.vue";
</script>
<template><Wrapper /></template>

Wrapper.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child @save="() => {}" /></template>

Child.vue

<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/unmatched-inject

inject 指定的键没有任何祖先提供。

默认严重程度: error / warning(具有默认值时)
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/unmatched-inject": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

ThemeLabel.vue 注入 ThemeKey,但其可达祖先 App.vue 从未提供该键。

keys/theme.ts

import type { InjectionKey, Ref } from "vue";

export interface Theme {
  color: string;
}

export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");

App.vue

<script setup lang="ts">
import ThemeLabel from "./ThemeLabel.vue";
</script>

<template>
  <ThemeLabel />
</template>

ThemeLabel.vue

<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";

const theme = inject(ThemeKey);
</script>

正确示例

App.vue 在渲染注入的后代之前,使用同一导出的 ThemeKey 提供响应式 theme。

keys/theme.ts

import type { InjectionKey, Ref } from "vue";

export interface Theme {
  color: string;
}

export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");

App.vue

<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
import { ThemeKey, type Theme } from "./keys/theme";

const theme = ref<Theme>({ color: "blue" });
provide(ThemeKey, theme);
</script>

<template>
  <ThemeLabel />
</template>

ThemeLabel.vue

<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";

const theme = inject(ThemeKey);
</script>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/unmatched-listener

父组件监听了子组件不发出的事件。

默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

父组件监听 save,已解析子组件却只声明 cancel。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child @save="() => {}" /></template>

Child.vue

<script setup lang="ts">
const emit = defineEmits<{ cancel: [] }>();
</script>
<template><p>Content</p></template>

正确示例

子组件声明并发出 save,与父组件监听器名称一致。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child @save="() => {}" /></template>

Child.vue

<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/unregistered-component

模板使用了未注册或导入的组件。

默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

虽然存在 Child.vue 文件,父组件既没有导入它,也没有为模板以其他方式注册 Child。

App.vue

<template><Child /></template>

Child.vue

<template><p>Child</p></template>

正确示例

在父组件 script setup 中导入 Child,使模板能解析组件绑定。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>

Child.vue

<template><p>Child</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/unresolved-import

导入无法解析为模块。

默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

父组件导入 ./Missing.vue,但项目包含的是 Child.vue,并非该路径。

App.vue

<script setup lang="ts">
import Child from "./Missing.vue";
</script>
<template><Child /></template>

Child.vue

<template><p>Child</p></template>

正确示例

将导入指向已存在的 ./Child.vue 文件,保留相同模板绑定。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>

Child.vue

<template><p>Child</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/unused-attrs

透传属性传给了未使用它们的多根节点组件。

默认严重程度: info
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

父组件的 tracking-code 既未被多根节点子组件作为 prop 使用,也未被转发。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child tracking-code="notice" /></template>

Child.vue

<template><main>Content</main><aside>Help</aside></template>

正确示例

在 <main> 上绑定 $attrs,为该透传属性提供显式目标。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child tracking-code="notice" /></template>

Child.vue

<template><main v-bind="$attrs">Content</main><aside>Help</aside></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/unused-emit

已声明的 emit 从未使用。

默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

子组件声明了 save,却从未以该名称调用事件发出函数。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>

Child.vue

<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
</script>
<template><p>Content</p></template>

正确示例

示例调用 emit("save"),使声明事件得到使用。真实交互应在对应操作发生时发出它。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>

Child.vue

<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/unused-provide

提供的键从未被注入。

默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/unused-provide": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

keys/theme.ts

import type { InjectionKey, Ref } from "vue";
export interface Theme { color: string; }
export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");

错误示例

App.vue 提供 ThemeKey,但渲染的 Dashboard.vue 子树没有该键的消费者。

App.vue

<script setup lang="ts">
import { provide, ref } from "vue";
import Dashboard from "./Dashboard.vue";
import { ThemeKey, type Theme } from "./keys/theme";

const theme = ref<Theme>({ color: "blue" });
provide(ThemeKey, theme);
</script>

<template>
  <Dashboard />
</template>

Dashboard.vue

<template>
  <h1>Dashboard</h1>
</template>

正确示例

仪表盘现在渲染 ThemeLabel.vue,后者注入与祖先完全相同身份的 ThemeKey。

App.vue

<script setup lang="ts">
import { provide, ref } from "vue";
import Dashboard from "./Dashboard.vue";
import { ThemeKey, type Theme } from "./keys/theme";

const theme = ref<Theme>({ color: "blue" });
provide(ThemeKey, theme);
</script>

<template>
  <Dashboard />
</template>

Dashboard.vue

<script setup lang="ts">
import ThemeLabel from "./ThemeLabel.vue";
</script>

<template>
  <ThemeLabel />
</template>

ThemeLabel.vue

<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";

const theme = inject(ThemeKey);
</script>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/value-extraction-breaks-reactivity

将响应式值读取到局部变量后丢失后续更新。

默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/value-extraction-breaks-reactivity": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./UserPage.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

Vue 3.5 响应式解构的 item 被一次性读取到 itemSnapshot,后续 prop 替换不会更新该快照。

UserPage.vue

<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";

const user = reactive({ name: "Ada" });
</script>

<template>
  <UserSummary :item="user" />
</template>

UserSummary.vue

<script setup lang="ts">
const { item } = defineProps<{ item: { name: string } }>();
const itemSnapshot = item;
</script>

正确示例

在 computed 内读取 item,让 Vue 的响应式 props 解构转换跟踪每次求值。

UserPage.vue

<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";

const user = reactive({ name: "Ada" });
</script>

<template>
  <UserSummary :item="user" />
</template>

UserSummary.vue

<script setup lang="ts">
import { computed } from "vue";

const { item } = defineProps<{ item: { name: string } }>();
const itemView = computed(() => item);
</script>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/watch-can-be-computed

侦听器只将值复制到状态,可以改为计算属性。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

此示例展示公开的纯派生状态偏好。侦听器仍适用于外部副作用或独立可写状态;目前没有实现发出此诊断约定。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

App.vue

<script setup lang="ts">
import { useDouble } from './use-double';
const { count, doubled } = useDouble();
</script>

<template>
<button @click="count++">{{ count }}</button><p>{{ doubled }}</p>
</template>

错误示例

侦听器没有外部副作用,只将第二个可写 ref 同步为 count 的两倍。示例没有对该派生值的独立写入。

use-double.ts

import { ref, watch } from 'vue';
export function useDouble() {
  const count = ref(0);
  const doubled = ref(0);
  watch(count, next => { doubled.value = next * 2; }, { immediate: true });
  return { count, doubled };
}

正确示例

计算 getter 直接表达相同派生关系,移除手动同步及额外可写状态。

use-double.ts

import { computed, ref } from 'vue';
export function useDouble() {
  const count = ref(0);
  const doubled = computed(() => count.value * 2);
  return { count, doubled };
}

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vize:croquis/cf/watcheffect-async

watchEffect 启动异步任务,却无法清理上一轮执行。

默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/watcheffect-async": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./SearchPage.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

api.ts

export interface Result { items: string[]; }
export async function load(query: string, options?: { signal?: AbortSignal }): Promise<Result> {
  const response = await fetch(`/search?q=${encodeURIComponent(query)}`, options);
  return response.json();
}

错误示例

异步 watchEffect 将隐式依赖收集与等待请求混合,且没有失效保护。

SearchPage.vue

<script setup lang="ts">
import { ref } from "vue";
import SearchResults from "./SearchResults.vue";

const query = ref("");
</script>

<template>
  <SearchResults :query="query" />
</template>

SearchResults.vue

<script setup lang="ts">
import { load, type Result } from "./api";
import { ref, watchEffect } from "vue";

const props = defineProps<{ query: string }>();
const result = ref<Result | null>(null);

watchEffect(async () => {
  result.value = await load(props.query);
});
</script>

正确示例

显式的 watch(() => props.query, ...) 声明来源、注册请求清理,并在失效后拒绝过期响应。

SearchPage.vue

<script setup lang="ts">
import { ref } from "vue";
import SearchResults from "./SearchResults.vue";

const query = ref("");
</script>

<template>
  <SearchResults :query="query" />
</template>

SearchResults.vue

<script setup lang="ts">
import { load, type Result } from "./api";
import { ref, watch } from "vue";

const props = defineProps<{ query: string }>();
const result = ref<Result | null>(null);

watch(
  () => props.query,
  async (value, _oldValue, onCleanup) => {
    const controller = new AbortController();
    let active = true;

    onCleanup(() => {
      active = false;
      controller.abort();
    });

    const next = await load(value, { signal: controller.signal });
    if (active) result.value = next;
  },
);
</script>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

诊断实现

跨文件规则索引

vize:croquis/cf/watcher-outside-setup

watch 或 watchEffect 在 setup 外被调用。

默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度

这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。

所有者保留并调用停止句柄,或有意赋予应用级生命周期时,模块作用域侦听器是有效的。此示例要求组件拥有生命周期;该诊断约定目前没有实现。

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>

App.vue

<script setup lang="ts">
import Observer from './Observer.vue';
</script>

<template>
<Observer /><Observer />
</template>

Observer.vue

<script setup lang="ts">
import { useObserver } from './use-observer';
const { count, observed } = useObserver();
</script>

<template>
<button @click="count++">{{ count }}</button><p>{{ observed }}</p>
</template>

错误示例

侦听器在模块加载时创建,位于两个 Observer 的 setup 之外,两实例共享其 refs。某个 Observer 卸载时不会自动停止它。

use-observer.ts

import { ref, watch } from 'vue';
const count = ref(0);
const observed = ref(0);
watch(count, next => { observed.value = next; });
export function useObserver() { return { count, observed }; }

正确示例

每个同步 setup 调用在 useObserver 中创建自身 refs 和侦听器。Vue 将该侦听器与调用组件的生命周期关联。

use-observer.ts

import { ref, watch } from 'vue';
export function useObserver() {
  const count = ref(0);
  const observed = ref(0);
  watch(count, next => { observed.value = next; });
  return { count, observed };
}

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

公开说明

跨文件规则索引

vue/cross-file-attrs-fallthrough

父组件传入属性,但已解析子组件的根无法继承它们,也没有显式使用 $attrs。

默认严重程度: warning
适用范围: 可达的项目声明及导入组件
选项: crossFile;规则严重程度(off/warn/error)
自动修复: 无

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: { preset: "incremental", crossFile: true,
      rules: { "vue/cross-file-attrs-fallthrough": "warn" },
    },
  },
});
vp run lint

共享项目文件

错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。

main.ts

import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");

index.html

<div id="app"></div>
<script type="module" src="/main.ts"></script>

错误示例

父组件将 class="notice" 传给已解析的片段子组件;该子组件没有自动属性目标,也从不读取 $attrs。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>

Child.vue

<template><main>Content</main><aside>Help</aside></template>

正确示例

子组件将 $attrs 绑定在 <main> 上,选择它作为目标;同级 <aside> 保持独立。

App.vue

<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>

Child.vue

<template><main v-bind="$attrs">Content</main><aside>Help</aside></template>

正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。

跨文件规则索引