Skip to content

Commit 71de6ed

Browse files
authored
Fix VSCode tasks.json parsing for tasks without explicit labels (zed-industries#47754)
Implements automatic label generation for VSCode tasks that don't have explicit 'label' fields, matching VSCode's behavior. Changes: - Made label field optional in VsCodeTaskDefinition deserialization - Implemented custom deserializer to auto-generate labels: - npm tasks: 'npm: {script}' (e.g., 'npm: start') - shell tasks: first word of command (e.g., 'echo') - gulp tasks: 'gulp: {task}' (e.g., 'gulp: build') - fallback: 'Untitled Task' - Added test data file with tasks without labels - Added test cases Closes zed-industries#47749 Release Notes: - Fixed: VSCode tasks.json files with tasks missing explicit `label` fields now parse correctly. Labels are auto-generated matching VSCode's behavior (e.g., "npm: start").
1 parent b229520 commit 71de6ed

2 files changed

Lines changed: 129 additions & 5 deletions

File tree

‎crates/task/src/vscode_format.rs‎

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,46 @@ struct TaskOptions {
1313
env: HashMap<String, String>,
1414
}
1515

16-
#[derive(Clone, Debug, Deserialize, PartialEq)]
17-
#[serde(rename_all = "camelCase")]
16+
#[derive(Clone, Debug, PartialEq)]
1817
struct VsCodeTaskDefinition {
1918
label: String,
20-
#[serde(flatten)]
2119
command: Option<Command>,
22-
#[serde(flatten)]
2320
other_attributes: HashMap<String, serde_json_lenient::Value>,
2421
options: Option<TaskOptions>,
2522
}
2623

24+
impl<'de> serde::Deserialize<'de> for VsCodeTaskDefinition {
25+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
26+
where
27+
D: serde::Deserializer<'de>,
28+
{
29+
#[derive(Deserialize)]
30+
#[serde(rename_all = "camelCase")]
31+
struct TaskHelper {
32+
#[serde(default)]
33+
label: Option<String>,
34+
#[serde(flatten)]
35+
command: Option<Command>,
36+
#[serde(flatten)]
37+
other_attributes: HashMap<String, serde_json_lenient::Value>,
38+
options: Option<TaskOptions>,
39+
}
40+
41+
let helper = TaskHelper::deserialize(deserializer)?;
42+
43+
let label = helper
44+
.label
45+
.unwrap_or_else(|| generate_label(&helper.command));
46+
47+
Ok(VsCodeTaskDefinition {
48+
label,
49+
command: helper.command,
50+
other_attributes: helper.other_attributes,
51+
options: helper.options,
52+
})
53+
}
54+
}
55+
2756
#[derive(Clone, Deserialize, PartialEq, Debug)]
2857
#[serde(tag = "type")]
2958
#[serde(rename_all = "camelCase")]
@@ -41,6 +70,21 @@ enum Command {
4170
},
4271
}
4372

73+
fn generate_label(command: &Option<Command>) -> String {
74+
match command {
75+
Some(Command::Npm { script }) => format!("npm: {}", script),
76+
Some(Command::Gulp { task }) => format!("gulp: {}", task),
77+
Some(Command::Shell { command, .. }) => {
78+
if command.trim().is_empty() {
79+
"shell".to_string()
80+
} else {
81+
command.clone()
82+
}
83+
}
84+
None => "Untitled Task".to_string(),
85+
}
86+
}
87+
4488
impl VsCodeTaskDefinition {
4589
fn into_zed_format(
4690
self,
@@ -128,7 +172,7 @@ mod tests {
128172
vscode_format::{Command, VsCodeTaskDefinition},
129173
};
130174

131-
use super::EnvVariableReplacer;
175+
use super::{EnvVariableReplacer, generate_label};
132176

133177
fn compare_without_other_attributes(lhs: VsCodeTaskDefinition, rhs: VsCodeTaskDefinition) {
134178
assert_eq!(
@@ -358,4 +402,62 @@ mod tests {
358402
let tasks: TaskTemplates = vscode_definitions.try_into().unwrap();
359403
assert_eq!(tasks.0, expected);
360404
}
405+
406+
#[test]
407+
fn can_deserialize_tasks_without_labels() {
408+
const TASKS_WITHOUT_LABELS: &str = include_str!("../test_data/tasks-without-labels.json");
409+
let vscode_definitions: VsCodeTaskFile =
410+
serde_json_lenient::from_str(TASKS_WITHOUT_LABELS).unwrap();
411+
412+
assert_eq!(vscode_definitions.tasks.len(), 4);
413+
assert_eq!(vscode_definitions.tasks[0].label, "npm: start");
414+
assert_eq!(vscode_definitions.tasks[1].label, "Explicit Label");
415+
assert_eq!(vscode_definitions.tasks[2].label, "gulp: build");
416+
assert_eq!(vscode_definitions.tasks[3].label, "echo hello");
417+
}
418+
419+
#[test]
420+
fn test_generate_label() {
421+
assert_eq!(
422+
generate_label(&Some(Command::Npm {
423+
script: "start".to_string()
424+
})),
425+
"npm: start"
426+
);
427+
assert_eq!(
428+
generate_label(&Some(Command::Gulp {
429+
task: "build".to_string()
430+
})),
431+
"gulp: build"
432+
);
433+
assert_eq!(
434+
generate_label(&Some(Command::Shell {
435+
command: "echo hello".to_string(),
436+
args: vec![]
437+
})),
438+
"echo hello"
439+
);
440+
assert_eq!(
441+
generate_label(&Some(Command::Shell {
442+
command: "cargo build --release".to_string(),
443+
args: vec![]
444+
})),
445+
"cargo build --release"
446+
);
447+
assert_eq!(
448+
generate_label(&Some(Command::Shell {
449+
command: " ".to_string(),
450+
args: vec![]
451+
})),
452+
"shell"
453+
);
454+
assert_eq!(
455+
generate_label(&Some(Command::Shell {
456+
command: "".to_string(),
457+
args: vec![]
458+
})),
459+
"shell"
460+
);
461+
assert_eq!(generate_label(&None), "Untitled Task");
462+
}
361463
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"type": "npm",
6+
"script": "start"
7+
},
8+
{
9+
"label": "Explicit Label",
10+
"type": "npm",
11+
"script": "test"
12+
},
13+
{
14+
"type": "gulp",
15+
"task": "build"
16+
},
17+
{
18+
"type": "shell",
19+
"command": "echo hello"
20+
}
21+
]
22+
}

0 commit comments

Comments
 (0)