zh CN Tutorial Editor Lint - chiba233/yume-dsl-token-walker GitHub Wiki
教程:编辑器 Lint + 自动修复
构建一个完整的 lint 管线——校验 DSL 源码、上报带源码位置的诊断、自动修复常见错误。这正是你接入 CodeMirror / Monaco 编辑器或 CI 检查时需要的东西。
你将学到:
- 用
LintRule和LintContext写 lint 规则 - 给诊断附加自动修复
- 用
applyLintFixes原子化应用修复 - 按项目覆盖 severity、禁用规则
- 用
onRuleError和failFast处理规则错误 - 让 lint 和运行时 parser 共享配置
前置条件: npm install yume-dsl-rich-text yume-dsl-token-walker
第 1 步:最简规则——禁止空标签
最基础的规则:标记 $$bold()$$(零子节点的 inline 标签)为 warning。
import { type LintRule } from "yume-dsl-token-walker";
export const noEmptyTag: LintRule = {
id: "no-empty-tag",
severity: "warning",
check: (ctx) => {
ctx.walk(ctx.tree, (node) => {
if (node.type === "inline" && node.children.length === 0 && node.position) {
ctx.report({
message: `空的 inline 标签: $$${node.tag}()$$`,
span: node.position,
node,
});
}
});
},
};
ctx.walk 带深度/parent 上下文遍历结构树的每个节点。ctx.report 上报诊断。span 是源码范围——编辑器波浪线和自动修复都需要它。
第 2 步:带自动修复的规则
给诊断加一个 fix——applyLintFixes 会用它:
export const noEmptyTagWithFix: LintRule = {
id: "no-empty-tag",
severity: "warning",
check: (ctx) => {
ctx.walk(ctx.tree, (node) => {
if (node.type === "inline" && node.children.length === 0 && node.position) {
ctx.report({
message: `空的 inline 标签: $$${node.tag}()$$`,
span: node.position,
node,
fix: {
description: "删除空标签",
edits: [{ span: node.position, newText: "" }],
},
});
}
});
},
};
每个 Fix 有一个或多个 TextEdit。每条 edit 用新文本替换一段源码范围。newText 为空 = 删除。
第 3 步:嵌套深度规则
const MAX_DEPTH = 3;
export const maxNestingDepth: LintRule = {
id: "max-nesting-depth",
severity: "error",
check: (ctx) => {
ctx.walk(ctx.tree, (node, visitCtx) => {
if (
visitCtx.depth >= MAX_DEPTH &&
(node.type === "inline" || node.type === "block") &&
node.position
) {
ctx.report({
message: `标签嵌套过深(深度 ${visitCtx.depth},上限 ${MAX_DEPTH})`,
span: node.position,
node,
});
}
});
},
};
第 4 步:运行 lint 并应用修复
import { lintStructural, applyLintFixes } from "yume-dsl-token-walker";
const source = `Hello $$bold()$$ world $$italic($$underline($$strike($$code(deep)$$)$$)$$)$$`;
// 运行所有规则
const diagnostics = lintStructural(source, {
rules: [noEmptyTagWithFix, maxNestingDepth],
});
// 应用所有可修复的诊断
const fixed = applyLintFixes(source, diagnostics);
// 空的 $$bold()$$ 被删除;嵌套错误没有 fix 所以保持原样
applyLintFixes 工作原理:
- 只处理带
fix字段的诊断 - 修复是原子化 per-fix 的——如果一个 fix 的任何一条 edit 和之前已接受的 edit 重叠,整个 fix 被跳过
- 按源码偏移先到先得
- 已接受的 edit 按倒序应用,保证前面的 offset 不失效
第 5 步:和运行时 parser 共享配置
如果你的运行时 parser 用了自定义语法或 handler,lint 也要传同一份配置——否则 lint 可能接受你的 parser 会拒绝的结构:
import { createParser, createSimpleInlineHandlers } from "yume-dsl-rich-text";
const parserOptions = {
handlers: createSimpleInlineHandlers(["bold", "italic"]),
allowForms: ["inline"] as const,
};
const parser = createParser(parserOptions);
const diagnostics = lintStructural(source, {
rules: [noEmptyTagWithFix],
parseOptions: parserOptions, // 和 parser 同一份配置
});
第 6 步:severity 覆盖和禁用规则
const diagnostics = lintStructural(source, {
rules: [noEmptyTagWithFix, maxNestingDepth],
overrides: {
"no-empty-tag": "off", // 完全禁用
"max-nesting-depth": "warning", // 从 error 降级为 warning
},
});
第 7 步:错误处理
如果规则在 check 中抛异常,默认静默跳过,其他规则继续。
// 观察错误
const diagnostics = lintStructural(source, {
rules,
onRuleError: ({ ruleId, error }) => {
console.warn(`规则 "${ruleId}" 崩了:`, error);
},
});
// 或者 fail fast——第一个错误立即中止
try {
const diagnostics = lintStructural(source, { rules, failFast: true });
} catch (error) {
// error.message 包含 rule id
// error.cause 是原始错误(当原始错误是 Error 实例时)
}
failFast 优先级高于 onRuleError。
完整示例:CI lint 检查
import { lintStructural, type LintRule } from "yume-dsl-token-walker";
const rules: LintRule[] = [noEmptyTagWithFix, maxNestingDepth];
function lintFile(source: string): boolean {
const diagnostics = lintStructural(source, { rules, failFast: true });
for (const d of diagnostics) {
const pos = d.span.start;
console.log(
`${d.severity.toUpperCase()} [${d.ruleId}] ${d.message} (${pos.line}:${pos.column})`,
);
}
const hasErrors = diagnostics.some((d) => d.severity === "error");
return !hasErrors;
}
const ok = lintFile(source);
process.exit(ok ? 0 : 1);
下一步
- Lint API 参考 — LintContext、LintOptions、Diagnostic、Fix、TextEdit
- 教程:博客渲染器 — 渲染管线
- 教程:游戏对话引擎 — 类型化指令输出