diff --git a/crates/task/src/vscode_format.rs b/crates/task/src/vscode_format.rs index 15afd3c63a..e8941f0efc 100644 --- a/crates/task/src/vscode_format.rs +++ b/crates/task/src/vscode_format.rs @@ -59,6 +59,7 @@ impl<'de> serde::Deserialize<'de> for VsCodeTaskDefinition { enum Command { Npm { script: String, + path: Option, }, Shell { command: String, @@ -72,7 +73,7 @@ enum Command { fn generate_label(command: &Option) -> String { match command { - Some(Command::Npm { script }) => format!("npm: {}", script), + Some(Command::Npm { script, .. }) => format!("npm: {}", script), Some(Command::Gulp { task }) => format!("gulp: {}", task), Some(Command::Shell { command, .. }) => { if command.trim().is_empty() { @@ -105,10 +106,16 @@ impl VsCodeTaskDefinition { bail!("Missing `type` field in task"); }; - let (command, args) = match command { - Command::Npm { script } => ("npm".to_owned(), vec!["run".to_string(), script]), - Command::Shell { command, args } => (command, args), - Command::Gulp { task } => ("gulp".to_owned(), vec![task]), + let (command, args, cwd) = match command { + Command::Npm { script, path } => ( + "npm".to_owned(), + vec!["run".to_string(), script], + path.map(|path| { + format!("{}/{}", VariableName::WorktreeRoot.template_value(), path) + }), + ), + Command::Shell { command, args } => (command, args, None), + Command::Gulp { task } => ("gulp".to_owned(), vec![task], None), }; // Per VSC docs, only `command`, `args` and `options` support variable substitution. let command = replacer.replace(&command); @@ -117,10 +124,13 @@ impl VsCodeTaskDefinition { label: self.label, command, args, + cwd: cwd.map(|cwd| replacer.replace(&cwd)), ..TaskTemplate::default() }; if let Some(options) = self.options { - template.cwd = options.cwd.map(|cwd| replacer.replace(&cwd)); + if let Some(cwd) = options.cwd { + template.cwd = Some(replacer.replace(&cwd)); + } template.env = options.env; } Ok(Some(template)) @@ -213,49 +223,87 @@ mod tests { } #[test] - fn can_deserialize_ts_tasks() { - const TYPESCRIPT_TASKS: &str = include_str!("../test_data/typescript.json"); - let vscode_definitions: VsCodeTaskFile = - serde_json_lenient::from_str(TYPESCRIPT_TASKS).unwrap(); + fn can_deserialize_gulp_tasks() { + const GULP_TASKS: &str = include_str!("../test_data/tasks-gulp.json"); + let vscode_definitions: VsCodeTaskFile = serde_json_lenient::from_str(GULP_TASKS).unwrap(); + + let expected = vec![VsCodeTaskDefinition { + label: "gulp: build tests".to_string(), + command: Some(Command::Gulp { + task: "build".to_string(), + }), + other_attributes: Default::default(), + options: None, + }]; + + assert_eq!(vscode_definitions.tasks.len(), expected.len()); + vscode_definitions + .tasks + .iter() + .zip(expected) + .for_each(|(lhs, rhs)| compare_without_other_attributes(lhs.clone(), rhs)); + + let expected = vec![TaskTemplate { + label: "gulp: build tests".to_string(), + command: "gulp".to_string(), + args: vec!["build".to_string()], + ..Default::default() + }]; + + let tasks: TaskTemplates = vscode_definitions.try_into().unwrap(); + assert_eq!(tasks.0, expected); + } + + #[test] + fn can_deserialize_npm_tasks() { + const NPM_TASKS: &str = include_str!("../test_data/tasks-npm.json"); + let vscode_definitions: VsCodeTaskFile = serde_json_lenient::from_str(NPM_TASKS).unwrap(); let expected = vec![ VsCodeTaskDefinition { - label: "gulp: tests".to_string(), + label: "With env".to_string(), command: Some(Command::Npm { - script: "build:tests:notypecheck".to_string(), + script: "build".to_string(), + path: None, + }), + other_attributes: Default::default(), + options: Some(super::TaskOptions { + cwd: None, + env: HashMap::from_iter([("NODE_ENV".to_string(), "production".to_string())]), + }), + }, + VsCodeTaskDefinition { + label: "With path".to_string(), + command: Some(Command::Npm { + script: "build".to_string(), + path: Some("packages/components".to_string()), }), other_attributes: Default::default(), options: None, }, VsCodeTaskDefinition { - label: "tsc: watch ./src".to_string(), - command: Some(Command::Shell { - command: "node".to_string(), - args: vec![ - "${workspaceFolder}/node_modules/typescript/lib/tsc.js".to_string(), - "--build".to_string(), - "${workspaceFolder}/src".to_string(), - "--watch".to_string(), - ], + label: "With cwd".to_string(), + command: Some(Command::Npm { + script: "build".to_string(), + path: None, }), other_attributes: Default::default(), - options: None, + options: Some(super::TaskOptions { + cwd: Some("${workspaceFolder}/packages/app".to_string()), + env: Default::default(), + }), }, VsCodeTaskDefinition { - label: "npm: build:compiler".to_string(), + label: "With path and cwd".to_string(), command: Some(Command::Npm { - script: "build:compiler".to_string(), + script: "build".to_string(), + path: Some("packages/components".to_string()), }), other_attributes: Default::default(), - options: None, - }, - VsCodeTaskDefinition { - label: "npm: build:tests".to_string(), - command: Some(Command::Npm { - script: "build:tests:notypecheck".to_string(), + options: Some(super::TaskOptions { + cwd: Some("${workspaceFolder}/packages/app".to_string()), + env: Default::default(), }), - other_attributes: Default::default(), - options: None, }, ]; @@ -268,13 +316,87 @@ mod tests { let expected = vec![ TaskTemplate { - label: "gulp: tests".to_string(), + label: "With env".to_string(), command: "npm".to_string(), - args: vec!["run".to_string(), "build:tests:notypecheck".to_string()], + args: vec!["run".to_string(), "build".to_string()], + env: HashMap::from_iter([("NODE_ENV".to_string(), "production".to_string())]), ..Default::default() }, TaskTemplate { - label: "tsc: watch ./src".to_string(), + label: "With path".to_string(), + command: "npm".to_string(), + args: vec!["run".to_string(), "build".to_string()], + cwd: Some("$ZED_WORKTREE_ROOT/packages/components".to_string()), + ..Default::default() + }, + TaskTemplate { + label: "With cwd".to_string(), + command: "npm".to_string(), + args: vec!["run".to_string(), "build".to_string()], + cwd: Some("${ZED_WORKTREE_ROOT}/packages/app".to_string()), + ..Default::default() + }, + TaskTemplate { + label: "With path and cwd".to_string(), + command: "npm".to_string(), + args: vec!["run".to_string(), "build".to_string()], + cwd: Some("${ZED_WORKTREE_ROOT}/packages/app".to_string()), + ..Default::default() + }, + ]; + let tasks: TaskTemplates = vscode_definitions.try_into().unwrap(); + assert_eq!(tasks.0, expected); + } + + #[test] + fn can_deserialize_shell_tasks() { + const SHELL_TASKS: &str = include_str!("../test_data/tasks-shell.json"); + let vscode_definitions: VsCodeTaskFile = serde_json_lenient::from_str(SHELL_TASKS).unwrap(); + let expected = vec![ + VsCodeTaskDefinition { + label: "Without args".to_string(), + command: Some(Command::Shell { + command: "cargo build --package rust-analyzer".to_string(), + args: Default::default(), + }), + options: None, + other_attributes: Default::default(), + }, + VsCodeTaskDefinition { + label: "With args".to_string(), + command: Some(Command::Shell { + command: "node".to_string(), + args: vec![ + "${workspaceFolder}/node_modules/typescript/lib/tsc.js".to_string(), + "--build".to_string(), + "${workspaceFolder}/src".to_string(), + "--watch".to_string(), + ], + }), + options: None, + other_attributes: Default::default(), + }, + VsCodeTaskDefinition { + label: "Ignored task without type".to_string(), + command: None, + options: None, + other_attributes: Default::default(), + }, + ]; + assert_eq!(vscode_definitions.tasks.len(), expected.len()); + vscode_definitions + .tasks + .iter() + .zip(expected) + .for_each(|(lhs, rhs)| compare_without_other_attributes(lhs.clone(), rhs)); + let expected = vec![ + TaskTemplate { + label: "Without args".to_string(), + command: "cargo build --package rust-analyzer".to_string(), + ..Default::default() + }, + TaskTemplate { + label: "With args".to_string(), command: "node".to_string(), args: vec![ "${ZED_WORKTREE_ROOT}/node_modules/typescript/lib/tsc.js".to_string(), @@ -284,20 +406,7 @@ mod tests { ], ..Default::default() }, - TaskTemplate { - label: "npm: build:compiler".to_string(), - command: "npm".to_string(), - args: vec!["run".to_string(), "build:compiler".to_string()], - ..Default::default() - }, - TaskTemplate { - label: "npm: build:tests".to_string(), - command: "npm".to_string(), - args: vec!["run".to_string(), "build:tests:notypecheck".to_string()], - ..Default::default() - }, ]; - let tasks: TaskTemplates = vscode_definitions.try_into().unwrap(); assert_eq!(tasks.0, expected); } @@ -312,6 +421,7 @@ mod tests { label: "Build Extension in Background".to_string(), command: Some(Command::Npm { script: "watch".to_string(), + path: Some("editors/code/".to_string()), }), options: None, other_attributes: Default::default(), @@ -320,6 +430,7 @@ mod tests { label: "Build Extension".to_string(), command: Some(Command::Npm { script: "build".to_string(), + path: Some("editors/code/".to_string()), }), options: None, other_attributes: Default::default(), @@ -346,6 +457,7 @@ mod tests { label: "Pretest".to_string(), command: Some(Command::Npm { script: "pretest".to_string(), + path: Some("editors/code/".to_string()), }), options: None, other_attributes: Default::default(), @@ -374,12 +486,14 @@ mod tests { label: "Build Extension in Background".to_string(), command: "npm".to_string(), args: vec!["run".to_string(), "watch".to_string()], + cwd: Some("$ZED_WORKTREE_ROOT/editors/code/".to_string()), ..Default::default() }, TaskTemplate { label: "Build Extension".to_string(), command: "npm".to_string(), args: vec!["run".to_string(), "build".to_string()], + cwd: Some("$ZED_WORKTREE_ROOT/editors/code/".to_string()), ..Default::default() }, TaskTemplate { @@ -396,6 +510,7 @@ mod tests { label: "Pretest".to_string(), command: "npm".to_string(), args: vec!["run".to_string(), "pretest".to_string()], + cwd: Some("$ZED_WORKTREE_ROOT/editors/code/".to_string()), ..Default::default() }, ]; @@ -420,7 +535,8 @@ mod tests { fn test_generate_label() { assert_eq!( generate_label(&Some(Command::Npm { - script: "start".to_string() + script: "start".to_string(), + path: None, })), "npm: start" ); diff --git a/crates/task/test_data/tasks-gulp.json b/crates/task/test_data/tasks-gulp.json new file mode 100644 index 0000000000..ff965cf02a --- /dev/null +++ b/crates/task/test_data/tasks-gulp.json @@ -0,0 +1,10 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "gulp: build tests", + "type": "gulp", + "task": "build" + } + ] +} diff --git a/crates/task/test_data/tasks-npm.json b/crates/task/test_data/tasks-npm.json new file mode 100644 index 0000000000..c25a1c3532 --- /dev/null +++ b/crates/task/test_data/tasks-npm.json @@ -0,0 +1,38 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "With env", + "type": "npm", + "script": "build", + "options": { + "env": { + "NODE_ENV": "production" + } + } + }, + { + "label": "With path", + "type": "npm", + "script": "build", + "path": "packages/components" + }, + { + "label": "With cwd", + "type": "npm", + "script": "build", + "options": { + "cwd": "${workspaceFolder}/packages/app" + } + }, + { + "label": "With path and cwd", + "type": "npm", + "script": "build", + "path": "packages/components", + "options": { + "cwd": "${workspaceFolder}/packages/app" + } + } + ] +} diff --git a/crates/task/test_data/tasks-shell.json b/crates/task/test_data/tasks-shell.json new file mode 100644 index 0000000000..ceabab3ff7 --- /dev/null +++ b/crates/task/test_data/tasks-shell.json @@ -0,0 +1,29 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Without args", + "group": "build", + "type": "shell", + "command": "cargo build --package rust-analyzer", + "problemMatcher": "$rustc" + }, + { + "label": "With args", + "type": "shell", + "command": "node", + "args": [ + "${workspaceFolder}/node_modules/typescript/lib/tsc.js", + "--build", + "${workspaceFolder}/src", + "--watch" + ], + "group": "build" + }, + { + "label": "Ignored task without type", + "dependsOn": ["Without args", "With args"], + "problemMatcher": "$rustc" + } + ] +} diff --git a/crates/task/test_data/typescript.json b/crates/task/test_data/typescript.json deleted file mode 100644 index 91d2343682..0000000000 --- a/crates/task/test_data/typescript.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - // Kept for backwards compat for old launch.json files so it's - // less annoying if moving up to the new build or going back to - // the old build. - // - // This is first because the actual "npm: build:tests" task - // below has the same script value, and VS Code ignores labels - // and deduplicates them. - // https://github.com/microsoft/vscode/issues/93001 - "label": "gulp: tests", - "type": "npm", - "script": "build:tests:notypecheck", - "group": "build", - "hide": true, - "problemMatcher": ["$tsc"] - }, - { - "label": "tsc: watch ./src", - "type": "shell", - "command": "node", - "args": [ - "${workspaceFolder}/node_modules/typescript/lib/tsc.js", - "--build", - "${workspaceFolder}/src", - "--watch" - ], - "group": "build", - "isBackground": true, - "problemMatcher": ["$tsc-watch"] - }, - { - "label": "npm: build:compiler", - "type": "npm", - "script": "build:compiler", - "group": "build", - "problemMatcher": ["$tsc"] - }, - { - "label": "npm: build:tests", - "type": "npm", - "script": "build:tests:notypecheck", - "group": "build", - "problemMatcher": ["$tsc"] - } - ] -}