Task 问题匹配器与运行
任务运行后会输出大段文本,人工盯日志找错误效率太低。ProblemMatcher(问题匹配器)让任务输出自动转成诊断,直接汇入问题面板。本篇深入匹配器规则与任务运行控制。
问题匹配器是什么
ProblemMatcher 用正则从任务输出中提取"文件名、行号、列号、消息、严重级别",转成 Diagnostic 进入问题面板。它有两种来源:
| 来源 | 说明 |
|---|---|
$tsc、$gcc 等内置匹配器 | VS Code 内置的常见编译器匹配规则,直接用 |
| 自定义 ProblemMatcher | 在 package.json 或 tasks.json 中声明正则规则 |
package.json 声明任务与匹配器
在 package.json 的 contributes 中声明任务类型与匹配器,用户就能在 tasks.json 中直接复用:
{
"contributes": {
"taskDefinitions": [
{
"type": "mytool",
"required": ["task"],
"properties": {
"task": {
"type": "string",
"description": "要执行的子命令"
},
"flags": {
"type": "string",
"description": "额外参数"
}
}
}
],
"problemMatchers": [
{
"name": "mytool-compile",
"owner": "mytool",
"fileLocation": ["relative", "${workspaceFolder}"],
"pattern": {
"regexp": "^\\[ERROR\\] (.+?)\\((\\d+),(\\d+)\\): (.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4,
"severity": 0
},
"severity": "error"
}
]
}
}taskDefinitions 字段
| 字段 | 说明 |
|---|---|
type | 任务类型标识,与 registerTaskProvider 对应 |
required | 必填属性列表 |
properties | 属性的 JSON Schema,提供补全与校验 |
problemMatchers 字段
| 字段 | 说明 |
|---|---|
name | 匹配器名称,任务中引用 $name |
owner | 诊断归属的语言 ID(用于去重与过滤) |
pattern | 正则匹配规则 |
severity | 默认严重级别 |
fileLocation | 文件路径的解析方式 |
background | 后台任务匹配配置 |
pattern 正则组
pattern 是匹配器的核心,用捕获组把输出文本映射到诊断字段:
{
"pattern": {
"regexp": "^(.*)\\((\\d+),(\\d+)\\): (error|warning)\\s*: (.+)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
}捕获组映射
| 字段 | 对应捕获组 | 说明 |
|---|---|---|
file | 第 N 组 | 文件名 |
line | 第 N 组 | 行号(从 1 开始) |
column | 第 N 组 | 列号 |
endLine / endColumn | 第 N 组 | 结束位置 |
message | 第 N 组 | 诊断消息 |
severity | 第 N 组 | 级别文本(error/warning/info) |
code | 第 N 组 | 错误码 |
severity 组值可以是数字(对应枚举)或文本。若输出里没有级别信息,用顶层 severity 兜底。
多行 pattern
某些编译器的错误格式跨多行(第一行是位置,后续行是详细消息),用 pattern 数组实现多行匹配:
{
"problemMatchers": [
{
"name": "mytool-multiline",
"owner": "mytool",
"pattern": [
{
"regexp": "^ERROR: (.+):(\\d+):(\\d+)$",
"file": 1,
"line": 2,
"column": 3
},
{
"regexp": "^\\s*message: (.+)$",
"message": 1
}
]
}
]
}多行 pattern 必须包含一个 regexp 主行(提供 file/line 等)加若干 pattern 后续行;后续行若未提供某字段,则继承主行。所有 pattern 都不匹配时,进行中的诊断会被丢弃。
空匹配与 seek 行为
默认情况下,输出中每行都会被匹配,直到某行同时匹配不了主行和后续行时丢弃当前诊断。这种"持续匹配直到失败"的策略适合大多数编译器输出。
fileLocation 路径解析
fileLocation 决定匹配到的文件名如何转成绝对路径:
| 值 | 行为 |
|---|---|
["relative", "${workspaceFolder}"] | 相对路径,以工作区根目录为基准拼接 |
["absolute"] | 按绝对路径处理 |
["autoDetect"] | 自动检测(先绝对后相对) |
{
"fileLocation": ["relative", "${workspaceFolder}/src"]
}使用 ${workspaceFolder} 等变量时,路径会相对对应工作区解析,适合多根工作区场景。
severity 与 owner
severity 级别
{
"severity": "error",
"pattern": { "severity": 4 }
}severity 支持的值:error、warning、info、hint。若 pattern 中不提供级别,所有诊断都用顶层 severity;都不给则默认 error。
owner 语言归属
owner 把诊断挂到指定语言名下,影响问题面板的过滤与 languages.getDiagnostics 的查询。建议使用插件名或语言 ID,避免与内置诊断混淆。
background 后台任务
编译监听类任务(如 tsc -w)不会退出,输出分"准备阶段"和"持续阶段"。background 配置让匹配器识别这两个阶段:
{
"problemMatchers": [
{
"name": "mytool-watch",
"owner": "mytool",
"background": {
"activeOnStart": true,
"beginsPattern": "开始编译",
"endsPattern": "编译完成|等待文件变更",
"problemMatcher": {
"owner": "mytool",
"pattern": {
"regexp": "^(.+)\\((\\d+),(\\d+)\\): (.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4
}
}
}
}
]
}| 字段 | 说明 |
|---|---|
activeOnStart | 任务启动即进入"活动"状态(诊断开始生效) |
beginsPattern | 匹配到后,输出中的问题开始被收集 |
endsPattern | 匹配到后,问题收集结束并立即报告 |
problemMatcher | 后台阶段实际使用的匹配规则 |
简单场景:用顶层 isBackground: true + 常规 pattern,配合 beginsPattern/endsPattern 即可。
TaskGroup 任务分组
TaskGroup 把任务归类,显示在任务快速选择中,并支持默认任务:
import * as vscode from 'vscode';
function createTasks(folder: vscode.WorkspaceFolder): vscode.Task[] {
const build = new vscode.Task(
{ type: 'mytool', task: 'build' },
folder.uri, '构建', 'mytool',
new vscode.ShellExecution('npm run build')
);
// 分组 + 默认标记:Ctrl+Shift+B 直接运行
build.group = { id: vscode.TaskGroup.Build.id, isDefault: true };
const test = new vscode.Task(
{ type: 'mytool', task: 'test' },
folder.uri, '测试', 'mytool',
new vscode.ShellExecution('npm test')
);
test.group = { id: vscode.TaskGroup.Test.id, isDefault: true };
const clean = new vscode.Task(
{ type: 'mytool', task: 'clean' },
folder.uri, '清理', 'mytool',
new vscode.ShellExecution('npm run clean')
);
clean.group = vscode.TaskGroup.Clean;
const rebuild = new vscode.Task(
{ type: 'mytool', task: 'rebuild' },
folder.uri, '重建', 'mytool',
new vscode.ShellExecution('npm run build && npm run build:extra')
);
rebuild.group = vscode.TaskGroup.Rebuild;
return [build, test, clean, rebuild];
}| 分组 | 默认快捷键/入口 | 说明 |
|---|---|---|
Build | Ctrl+Shift+B | 构建任务 |
Test | 任务菜单 | 测试任务 |
Clean | 任务菜单 | 清理任务 |
Rebuild | 任务菜单 | 重建任务 |
任务行为配置
reveal / focus / promptOnClose
TaskPresentationOptions 控制终端呈现;runOptions 控制重复运行;还有几个高频行为字段:
import * as vscode from 'vscode';
const task = new vscode.Task(
{ type: 'mytool', task: 'build' },
vscode.workspace.workspaceFolders?.[0]?.uri!,
'构建', 'mytool',
new vscode.ShellExecution('npm run build')
);
// 终端呈现
task.presentationOptions = {
reveal: vscode.TaskRevealKind.Silent, // 失败才显示终端
panel: vscode.TaskPanelKind.Dedicated,
clear: false,
focus: false
};
// 运行策略
task.runOptions = {
rerunStrategy: vscode.TaskReRunStrategy.ReuseExisting
};注意:focus 与 reveal 在 TaskPresentationOptions 中提供;promptOnClose 是终端层面的行为(终端有活动进程时关闭会提示),对应 terminal.integrated.confirmOnExit 相关设置,插件内不做直接控制。
常用 reveal 策略
| 值 | 行为 | 适用 |
|---|---|---|
Always | 总是显示终端 | 交互式命令 |
Silent | 输出有问题(匹配到错误)才显示 | 常规构建 |
Never | 从不显示 | 完全后台 |
任务输出与问题面板联动
任务输出经匹配器进入问题面板后,插件可以用语言 API 读取诊断做进一步处理:
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
// 任务结束后统计 mytool 类型的诊断
context.subscriptions.push(
vscode.tasks.onDidEndTaskProcess((e) => {
if (e.execution.task.source !== 'mytool') return;
if (e.exitCode !== 0) {
collectAndShowDiagnostics(e.execution.task.name);
}
})
);
}
function collectAndShowDiagnostics(taskName: string) {
const root = vscode.workspace.workspaceFolders?.[0]?.uri;
if (!root) return;
// 任务执行期间产生的诊断由匹配器写入,这里读取全部
const all = vscode.languages.getDiagnostics();
const ours = all.filter(([uri]) => uri.scheme === 'file')
.map(([uri, diags]) => ({ uri, diags }))
.filter(({ diags }) => diags.some((d) => d.source === 'mytool'));
let count = 0;
for (const { diags } of ours) {
count += diags.filter((d) => d.severity === vscode.DiagnosticSeverity.Error).length;
}
if (count > 0) {
vscode.window.showErrorMessage(
`${taskName} 失败,问题面板中还有 ${count} 个错误`,
'打开问题面板'
).then((choice) => {
if (choice === '打开问题面板') {
vscode.commands.executeCommand('workbench.actions.view.problems');
}
});
}
}匹配器调试
匹配器正则写错很难直接发现,两个调试手段:
| 手段 | 做法 |
|---|---|
| 输出通道日志 | vscode.window.createOutputChannel 打印任务原始输出,核对正则 |
| 简化输出 | 先用 echo 打印固定格式文本,验证匹配规则 |
import * as vscode from 'vscode';
// 用输出通道检查匹配器是否生效
export function debugMatcher(root: vscode.Uri) {
const testTask = new vscode.Task(
{ type: 'mytool', task: 'debug' },
root, '匹配器调试', 'mytool',
// 输出固定格式:直接验证正则
new vscode.ShellExecution('echo "[ERROR] src/index.ts(12,5): 类型不匹配"')
);
testTask.problemMatchers = ['$mytool-compile'];
vscode.tasks.executeTask(testTask);
}综合示例:完整任务 + 匹配器 + 问题面板
import * as vscode from 'vscode';
import * as path from 'path';
export function activate(context: vscode.ExtensionContext) {
// 1. 注册任务提供者
const provider: vscode.TaskProvider = {
provideTasks(): vscode.Task[] {
const folder = vscode.workspace.workspaceFolders?.[0];
if (!folder) return [];
const root = folder.uri.fsPath;
const compile = new vscode.Task(
{ type: 'demo-lint', task: 'compile' },
folder.uri, '编译', 'demo-lint',
new vscode.ShellExecution('node tools/compile.js', { cwd: root })
);
compile.group = { id: vscode.TaskGroup.Build.id, isDefault: true };
// 引用 package.json 中声明的匹配器
compile.problemMatchers = ['$demo-lint-compile'];
compile.presentationOptions = {
reveal: vscode.TaskRevealKind.Silent,
panel: vscode.TaskPanelKind.Dedicated
};
const watch = new vscode.Task(
{ type: 'demo-lint', task: 'watch' },
folder.uri, '监听编译', 'demo-lint',
new vscode.ShellExecution('node tools/compile.js --watch', { cwd: root })
);
watch.isBackground = true;
watch.problemMatchers = ['$demo-lint-watch'];
watch.runOptions = { rerunStrategy: vscode.TaskReRunStrategy.ReuseExisting };
return [compile, watch];
}
};
context.subscriptions.push(
vscode.tasks.registerTaskProvider('demo-lint', provider)
);
// 2. 任务结束:汇总错误到状态栏
context.subscriptions.push(
vscode.tasks.onDidEndTask((e) => {
if (e.execution.task.source !== 'demo-lint') return;
const errors = vscode.languages.getDiagnostics()
.flatMap(([, diags]) => diags)
.filter((d) => d.source === 'demo-lint' && d.severity === vscode.DiagnosticSeverity.Error);
const bar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 50);
bar.text = errors.length > 0
? `$(error) ${errors.length} 个编译错误`
: '$(check) 编译通过';
bar.show();
setTimeout(() => bar.dispose(), 5000);
})
);
// 3. 命令:手动触发"编译并聚焦问题"
context.subscriptions.push(
vscode.commands.registerCommand('demo-lint.compile', async () => {
const tasks = await vscode.tasks.fetchTasks({ type: 'demo-lint' });
const compile = tasks.find((t) => t.name === '编译');
if (compile) {
await vscode.tasks.executeTask(compile);
vscode.commands.executeCommand('workbench.actions.view.problems');
}
})
);
}
export function deactivate() {}流程串联
任务执行(ShellExecution/ProcessExecution)
↓ 输出文本
ProblemMatcher 正则逐行匹配
↓ 提取 file/line/column/message/severity
诊断写入问题面板(按 owner 归属)
↓
onDidEndTask 触发 → 插件读取诊断做汇总/跳转匹配器是任务系统的"翻译官":把命令行输出翻译成结构化的诊断。配置好 pattern 正则、fileLocation 路径解析与 background 阶段识别,任务输出就能无缝汇入问题面板,实现"一键构建、自动报错、点击定位"的完整闭环。