Skip to content

Commit 30f9794

Browse files
Merge pull request #142 from augustocdias/unique_taskid
Enforce unique task ids
2 parents 2e214f6 + fa7bd1a commit 30f9794

5 files changed

Lines changed: 196 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [Unreleased] xxxx-xx-xx
44

55
- Make input history persistent (#140)
6+
- Enforce unique taskId (#131)
67

78
## [1.16.0] 2025-02-04
89

src/lib/CommandHandler.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,30 @@ test("Command variable interop", async () => {
190190
);
191191
});
192192

193+
test("It should detect duplicate taskIds", async () => {
194+
const testDataPath = path.join(__dirname, "../test/testData/duplicateTaskid");
195+
196+
const tasksJson = await import(path.join(testDataPath, ".vscode/tasks.json"));
197+
const mockData = (await import(path.join(testDataPath, "mockData.ts"))).default;
198+
199+
mockVscode.setMockData(mockData);
200+
const input = tasksJson.inputs[0].args;
201+
const context = mockExtensionContext as unknown as vscode.ExtensionContext;
202+
const handler = new CommandHandler(
203+
{...input, useFirstResult: true},
204+
new UserInputContext(context),
205+
context,
206+
child_process,
207+
);
208+
209+
await handler.handle();
210+
211+
expect(execFileSpy).toHaveBeenCalledTimes(0);
212+
expect(execSpy).toHaveBeenCalledTimes(1);
213+
expect(mockVscode.window.getShowWarningMessageCalls().length).toBe(1);
214+
expect(mockVscode.window.getShowWarningMessageCalls()[0]).toBe("Found duplicate 'taskIds'. This field must be unique. Expect strange behaviour. If you are trying to share a remembered value between tasks, please use 'rememberAs'. Duplicate taskIds: inputTest");
215+
});
216+
193217
test("commandArgs", async () => {
194218
const testDataPath = path.join(__dirname, "../test/testData/commandArgs");
195219
const filePath = `${testDataPath}/.vscode/tasks.json`;

src/lib/CommandHandler.ts

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -425,13 +425,62 @@ export class CommandHandler {
425425
yield* getSectionInputs("tasks");
426426
}
427427

428+
const allInputs = [...getAllInputs()].map(
429+
({ args: { command, ...args }, ...rest }) => ({
430+
args: {
431+
command: CommandHandler.resolveCommand(command),
432+
...args,
433+
},
434+
...rest,
435+
}));
436+
437+
let result: Input | undefined = undefined;
438+
const taskIdMap: Record<string, Input> = {};
439+
const duplicateTaskIds = new Set<string>();
440+
441+
function isTaskIdUnique(input: Input) {
442+
const taskId = input.args.taskId;
443+
444+
if (undefined === taskId) {
445+
return true;
446+
}
447+
448+
const other = taskIdMap[taskId];
449+
taskIdMap[taskId] = input;
450+
451+
if (undefined === other) {
452+
return true;
453+
}
454+
455+
// Inputs are not marked as duplicate if they have the same command
456+
// and command args. It can happen that we see the same input twice
457+
// because of workspaceFolders. We cannot detect this.
458+
return input.args.command === other.args.command &&
459+
CommandHandler.compareCommandArgs(input.args.commandArgs,
460+
other.args.commandArgs);
461+
}
462+
428463
// Go through the generator and return the first match
429-
for (const input of getAllInputs()) {
430-
const command = CommandHandler.resolveCommand(input?.args?.command);
431-
if (command === this.command && input?.args?.taskId === taskId &&
432-
CommandHandler.compareCommandArgs(this.commandArgs, input?.args?.commandArgs)) {
433-
return input;
464+
for (const input of allInputs) {
465+
if (false === isTaskIdUnique(input)) {
466+
duplicateTaskIds.add(input.args.taskId);
434467
}
468+
469+
if (input.args.command === this.command &&
470+
input?.args?.taskId === taskId &&
471+
CommandHandler.compareCommandArgs(this.commandArgs,
472+
input?.args?.commandArgs)) {
473+
result = input;
474+
}
475+
}
476+
477+
if (0 < duplicateTaskIds.size) {
478+
vscode.window.showWarningMessage(
479+
`Found duplicate 'taskIds'. This field must be unique. Expect strange behaviour. If you are trying to share a remembered value between tasks, please use 'rememberAs'. Duplicate taskIds: ${[...duplicateTaskIds].join(", ")}`);
480+
}
481+
482+
if (undefined !== result) {
483+
return result;
435484
}
436485

437486
throw new ShellCommandException(`Could not find input with command '${this.command}' and taskId '${taskId}'.`);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "Echo Project File",
6+
"type": "shell",
7+
"command": "echo ${input:inputTest}",
8+
"problemMatcher": []
9+
}
10+
],
11+
"inputs": [
12+
{
13+
"id": "inputTest2",
14+
"type": "command",
15+
"command": "shellCommand.execute",
16+
"args": {
17+
"taskId": "inputTest",
18+
"command": "echo inputTest2",
19+
"multiselect": true,
20+
"cwd": "${workspaceFolder}",
21+
"env": {
22+
"WORKSPACE": "${workspaceFolder[0]}",
23+
"FILE": "${file}",
24+
"PROJECT": "${workspaceFolderBasename}"
25+
}
26+
}
27+
},
28+
{
29+
"id": "inputTest",
30+
"type": "command",
31+
"command": "shellCommand.execute",
32+
"args": {
33+
"taskId": "inputTest",
34+
"rememberAs": "inputTest",
35+
"rememberPrevious": true,
36+
"command": "echo inputTest",
37+
"cwd": "${workspaceFolder}",
38+
"env": {
39+
"WORKSPACE": "${workspaceFolder[0]}",
40+
"FILE": "${file}",
41+
"PROJECT": "${workspaceFolderBasename}"
42+
}
43+
}
44+
}
45+
]
46+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
export default {
2+
"staticData": {
3+
"workspace.workspaceFolders": [
4+
{
5+
"uri": {
6+
"$mid": 1,
7+
"fsPath": __dirname,
8+
"external": `file://${__dirname}`,
9+
"path": __dirname,
10+
"scheme": "file"
11+
},
12+
"name": "duplicateTaskid",
13+
"index": 0
14+
}
15+
],
16+
"window.activeTextEditor.document.fileName": `${__dirname}/.vscode/tasks.json`,
17+
"window.activeTextEditor.document.uri.fsPath": `${__dirname}/.vscode/tasks.json`,
18+
"window.activeTextEditor.selection.active.line": 7
19+
},
20+
"calls": {
21+
"workspace.getConfiguration().inspect()": {
22+
"[\"launch\",null,\"options\"]": {
23+
"key": "launch.options"
24+
},
25+
"[\"launch\",null,\"inputs\"]": {
26+
"key": "launch.inputs"
27+
},
28+
"[\"tasks\",null,\"options\"]": {
29+
"key": "tasks.options"
30+
},
31+
"[\"tasks\",null,\"inputs\"]": {
32+
"key": "tasks.inputs",
33+
"workspaceValue": [
34+
{
35+
"id": "inputTest2",
36+
"type": "command",
37+
"command": "shellCommand.execute",
38+
"args": {
39+
"taskId": "inputTest",
40+
"command": "echo inputTest2",
41+
"multiselect": true,
42+
"cwd": "${workspaceFolder}",
43+
"env": {
44+
"WORKSPACE": "${workspaceFolder[0]}",
45+
"FILE": "${file}",
46+
"PROJECT": "${workspaceFolderBasename}"
47+
}
48+
}
49+
},
50+
{
51+
"id": "inputTest",
52+
"type": "command",
53+
"command": "shellCommand.execute",
54+
"args": {
55+
"taskId": "inputTest",
56+
"rememberAs": "inputTest",
57+
"rememberPrevious": true,
58+
"command": "echo inputTest",
59+
"cwd": "${workspaceFolder}",
60+
"env": {
61+
"WORKSPACE": "${workspaceFolder[0]}",
62+
"FILE": "${file}",
63+
"PROJECT": "${workspaceFolderBasename}"
64+
}
65+
}
66+
}
67+
]
68+
}
69+
}
70+
}
71+
};

0 commit comments

Comments
 (0)