# 诊断码与修法
诊断同一条结构供三处使用:`canvas_check` 的输出、tab 里的错误卡、host 日志。**先跑 `canvas_check`**——它只编译不渲染,能在你还没打开页面时就把问题说清楚。
~~~ts
type CanvasDiagnostic = {
code: string;
severity: "error" | "warning";
message: string;
line?: number;
col?: number;
hint?: string;
};
~~~
## 速查
| 码 | 级别 | 产出方 | 一句话 | 修法要点 |
|---|---|---|---|---|
| `E_PARSE` | error | sucrase | 语法 / TS 解析失败 | 看行列号,通常是 JSX 标签不配对 |
| `E_PARSE_IMPORT` | error | 扫描 | import 了非 `dsh/canvas` 的模块 | 只用 `dsh/canvas`(`import React` 也归这条) |
| `E_NO_DEFAULT` | error | 编译管线 | 缺 `export default` | 导出一个无参组件 |
| `E_SIDE_EFFECT` | error | 扫描 | 顶层副作用 | I/O 与定时器移出模块顶层 |
| `E_DYNAMIC` | error | 扫描 | `eval` / `new Function` / 动态 `import()` | 改成普通分支 |
| `E_EXTERNAL` | error | 扫描 | 远程资源 / iframe | 去掉;用内联 SVG |
| `E_DATA_NOT_LITERAL` | error | 抽取 | `DATA` 不是纯字面量 | 把值写成字面量,计算放渲染期 |
| `E_MERGE` | error | `canvas_state_merge` | 读不出 `DATA` | `DATA` 必须是对象字面量,且每行有 `id` |
| `E_TOO_LARGE` | error | 编译管线 | 超硬阈值,拒绝编译 | 拆画布 / 外置历史 |
| `W_NO_DATA` | warning | 抽取 | 没有 `export const DATA` | 把数据搬进 `DATA` |
| `W_LARGE_FILE` | warning | 编译管线 | 超软阈值 | 拆画布 / 用引用替代长字段 |
| `W_REMOTE_URL` | warning | 扫描 | 源码里出现 `http(s)://` | 画布不能拉远程内容;内联数据 |
| `W_MANY_ROWS` | warning | **渲染期文案(不是诊断)** | 行数被截断 | 加筛选器 |
---
## E_PARSE
**含义**:sucrase 无法解析源码(语法错误,或 TS 类型剥离失败)。行列号直接来自编译器。
**典型写法**
~~~tsx
hi
{/* 打开的是 Stack,关的却是 Card */}
~~~
**修法**:按错误卡给的行列号检查那一处的 JSX 配对与括号。JSX 要求关闭标签与最近的打开标签同名——把上面的 `` 改成 `` 即可。
> 真实案例:设计文档附录 A 的示例画布就有一处把 `` 写成了 ``,属于这个码。
---
## E_PARSE_IMPORT
**含义**:出现了非 `"dsh/canvas"` 的模块说明符。
**典型写法**
~~~tsx
import _ from "lodash"; // 第三方
import { helper } from "./helper"; // 相对路径
import React from "react"; // 同样归 E_PARSE_IMPORT(§21 D2 已删掉专用码)
import { LineChart } from "recharts"; // 图表库
~~~
**修法**:画布只允许 `dsh/canvas`。需要的工具函数就地写;需要的图表用套件里的 `BarChart` 或自己用内联 SVG 画。**画布之间也不能互相 import**——需要复用就复制那几行,或把逻辑提成 agent 侧的工具。
---
## E_NO_DEFAULT
**含义**:文件里没有 `export default`,或默认导出的不是组件。
**典型写法**
~~~tsx
export function Board() { return null; } // 只有具名导出
const Board = () => null; // 定义了但没导出
~~~
**修法**
~~~tsx
export default function Board() {
return ...;
}
~~~
具名导出(`export const DATA`、辅助函数)可以同时存在,宿主只取 default。
---
## E_SIDE_EFFECT
**含义**:模块顶层执行了有副作用或依赖运行环境的调用。模块在动态 import 时求值一次,顶层副作用会让"编译检查"和"渲染"都不再是纯函数。
**典型写法**
~~~tsx
console.log("canvas loaded"); // 顶层日志
const timer = setTimeout(() => {}, 1000); // 定时器
const base = window.location.pathname; // 环境依赖
localStorage.setItem("k", "v"); // 存储写入
const data = fetchRows(); // I/O
~~~
**修法**
- 数据 → 写进 `DATA`(字面量)。
- 需要随交互发生的动作 → 放进事件处理器,或经 `useCanvasAction`。
- 顶层**纯计算**(`const TASKS = DATA.tasks.filter(...)`、`DATA.tasks.map(...)`)是允许的:它不读环境、不写外部。
- 定时器与网络在画布里**本来也不可用**;不要试图绕过。
---
## E_DYNAMIC
**含义**:用了动态代码执行。
**典型写法**
~~~tsx
const f = new Function("return 1");
const v = eval("1 + 1");
const mod = await import(someUrl);
~~~
**修法**:改成显式分支或查表。需要"按类型渲染不同内容"就写 `switch` / 映射对象。
---
## E_EXTERNAL
**含义**:引用了画布之外的资源。
**典型写法**
~~~tsx
const url = "http://localhost:8080/data.json";
~~~
**修法**:去掉远程引用。图标用内联 SVG(`