Repository navigation
Validate mainClass and projectName before launch - #368
Conversation
Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
|
|
||
| private async chooseMainClass(folder: vscode.WorkspaceFolder | undefined): Promise<IMainClassOption | undefined> { | ||
| const res = await resolveMainClass(folder ? folder.uri : undefined); | ||
| private async quickFixLaunchConfig(folder: vscode.Uri | undefined, config: vscode.DebugConfiguration): |
There was a problem hiding this comment.
I don't think this function is to fix the launch config; fix is only part of the functionality after failed to choose mainclass. More like populate the launchConfig.
There was a problem hiding this comment.
IMO, when the mainClass and projectName is empty, and we prompt users to choose one, this is also one kind of fix. And the difference is we don't persist them in launch.json.
There was a problem hiding this comment.
Fix means something is wrong and you want to correct it. For user configuration, the mainClass and projectName is correct and doesn't need fix them after validation.
|
|
||
| if (validationResponse.proposals && validationResponse.proposals.length) { | ||
| const answer = await utility.showErrorMessageWithTroubleshooting({ | ||
| message: errors.join("\n"), |
There was a problem hiding this comment.
Use platform dependent separator
| } | ||
| } | ||
|
|
||
| private findDebugConfig(rawConfigs: vscode.DebugConfiguration[], config: vscode.DebugConfiguration): number { |
There was a problem hiding this comment.
Since we are Java debugger, use the type to filter out unnecessary DebugConfig.
There was a problem hiding this comment.
Use lodash utility to search the debug config instead.
| return -1; | ||
| } | ||
|
|
||
| private compareDebugConfig(config1: object, config2: object): boolean { |
There was a problem hiding this comment.
The convention of semantic for compare result is -1, 0, 1 but not boolean value. Please keep this convention as possible.
Use equals*** for boolean result.
There was a problem hiding this comment.
this method is discarded.
|
|
||
| interface IValidationResult { | ||
| readonly passed: boolean; | ||
| readonly message?: string; |
There was a problem hiding this comment.
message ==> errorMessage?
There was a problem hiding this comment.
after discussion, we think the neutral message name is more appropriate, because message could be for both valid and invalid case.
Yaohai Zheng (yaohaizh)
left a comment
There was a problem hiding this comment.
Chose wrong result...
|
|
||
| private async chooseMainClass(folder: vscode.WorkspaceFolder | undefined): Promise<IMainClassOption | undefined> { | ||
| const res = await resolveMainClass(folder ? folder.uri : undefined); | ||
| private async quickFixLaunchConfig(folder: vscode.Uri | undefined, config: vscode.DebugConfiguration): |
There was a problem hiding this comment.
I would suggest 'resolveLaunchConfig' because a fix is not happening in every case.
| const selection = pickItems.length > 1 ? | ||
| await vscode.window.showQuickPick(pickItems, { placeHolder: "Select main class<project name>" }) | ||
|
|
||
| const selection = alwaysShowQuickPickBox || pickItems.length > 1 ? |
There was a problem hiding this comment.
Does alwaysShowQuickPickBox mean we need to keep the box visible when focus moves?
If that's the case then the QuickPickOptions.ignoreFocusOut needs to be set to true to achieve that.
I'm confused by the variable name and its usage here.
There was a problem hiding this comment.
The flag is not about focus thing. Just to indicate whether to show the QuickPick box when only one available pick item.
In previous logic, when there is only one mainClass available, then pick it silently. But in the Fix prompt, even only one available mainClass, i want to show the QuickPick box to keep user consent about the ongoing fix replacement.
| } | ||
|
|
||
| interface IValidationResult { | ||
| readonly passed: boolean; |
| return true; | ||
| } | ||
|
|
||
| private async chooseMainClass(folder: vscode.Uri | undefined): Promise<IMainClassOption | undefined> { |
There was a problem hiding this comment.
chooseMainClass -> promptMainClass
| const answer = await utility.showErrorMessageWithTroubleshooting({ | ||
| message: errors.join("\n"), | ||
| type: Type.USAGEERROR, | ||
| }, "Quick Fix"); |
| type: Type.USAGEERROR, | ||
| }, "Quick Fix"); | ||
| if (answer === "Quick Fix") { | ||
| const quickFix: IMainClassOption = await this.showMainClassQuickPick(validationResponse.proposals, |
There was a problem hiding this comment.
What if user cancels and returns nothing?
There was a problem hiding this comment.
If the user chooses to cancel the fix QuickPick box, then the debugger exits.
| } | ||
| } | ||
|
|
||
| private persistLaunchConfig(folder: vscode.Uri | undefined, config: vscode.DebugConfiguration, change: IMainClassOption): void { |
There was a problem hiding this comment.
The function looks generic from its name and signature. But the data type of chagne is restricted to IMainClassOption which makes it only accept main class changes.
To workaround this, the signature should look like (folder, oldConfig, newConfig) to me, newConfig is with change applied. oldConfig is for us to look up the actual section in the file.
| } | ||
|
|
||
| private findDebugConfig(rawConfigs: vscode.DebugConfiguration[], config: vscode.DebugConfiguration): number { | ||
| const candidates = []; |
There was a problem hiding this comment.
lodash.isEqual does deep comparison.
_.findIndex(rawConfigs, _.isEqual) can get you the index.
1. Better naming about validate and fix mainClass and ProjectName. 2. Use lodash library to find the debug configuration index from the workspaceConfiguration list.
| readonly passed: boolean; | ||
| readonly message?: string; | ||
| readonly isValid: boolean; | ||
| readonly errorMessage?: string; |
There was a problem hiding this comment.
The message field can be neutral because there could message for both valid and invalid case.
There was a problem hiding this comment.
Agree. Have reverted the field back to message.
| import * as _ from "lodash"; | ||
| import * as os from "os"; | ||
| import * as vscode from "vscode"; | ||
|
|
There was a problem hiding this comment.
Nice to have extra line to separate 3rd party dependencies and local dependencies.
| } | ||
|
|
||
| if (validationResponse.proposals && validationResponse.proposals.length) { | ||
| const answer = await utility.showErrorMessageWithTroubleshooting({ |
There was a problem hiding this comment.
dup with line 257, you can keep the promise of utility.showErrorMessageWithTroubleshooting and await it if validationResponse.proposals have values.
There was a problem hiding this comment.
There is a tiny difference between if/else conditions here. For the case of having proposals, the UI shows a Fix button, and the debug session will wait the user to click it. For the case of no having proposals, the UI just shows a Learn More button, no need to wait the user's click behavior, and the debug session will terminate directly.
| config.projectName = userSelection.projectName; | ||
|
|
||
| const mainClassOption = await this.resolveLaunchConfig(folder ? folder.uri : undefined, config); | ||
| if (!mainClassOption || !mainClassOption.mainClass) { |
There was a problem hiding this comment.
Use try/catch block to handle the rejected/exception case.
|
|
||
| private async chooseMainClass(folder: vscode.WorkspaceFolder | undefined): Promise<IMainClassOption | undefined> { | ||
| const res = await resolveMainClass(folder ? folder.uri : undefined); | ||
| private isEmptyArray(configItems: any): boolean { |
There was a problem hiding this comment.
Use _.isEmpty() instead.
| return !Array.isArray(configItems) || !configItems.length; | ||
| } | ||
|
|
||
| private async resolveLaunchConfig(folder: vscode.Uri | undefined, config: vscode.DebugConfiguration): Promise<IMainClassOption> { |
There was a problem hiding this comment.
If the first parameter is optional, then we could write it as folder?: vscode.Uri. The question mark after the param name will assign undefined value when the param is not omitted. "default to undefined" is not necessariy by the nature of ts/js.
I suggest that you place config param as the first param because it's required. And folder to the second because it's optional.
There was a problem hiding this comment.
Actually the pattern is inherited from VSCode API. See the original VSCode API defintion public resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken):
There was a problem hiding this comment.
It indicates the folder parameter is not optional. Just represent it could be undefined value when no workspace is opened.
| } | ||
|
|
||
| private async fixMainClass(folder: vscode.Uri | undefined, config: vscode.DebugConfiguration, validationResponse: ILaunchValidationResponse): | ||
| Promise<IMainClassOption | undefined> { |
There was a problem hiding this comment.
A promise should be resolved with real results instead of undefined. In the case that we are not able to resolve some real results, it looks like an exception case.
There was a problem hiding this comment.
Here is a case for undefined. when user cancels the fix dropdown, should return undefined instead of an exception. because it's user consent behavior.
| private async showMainClassQuickPick(pickItems: IMainClassQuickPickItem[], quickPickHintMessage: string, autoPick: boolean = true): | ||
| Promise<IMainClassOption | undefined> { | ||
| if (pickItems.length === 0) { | ||
| return; |
There was a problem hiding this comment.
return nothing means resolving with undefined. This does not seem right. I would recommend through an exception because an empty candidate list is an error case.
There was a problem hiding this comment.
will remove the empty check logic, because the check has been done in the caller side. No need to duplicate it.
| } | ||
|
|
||
| const selected = (pickItems.length === 1 && autoPick) ? | ||
| pickItems[0] : await vscode.window.showQuickPick(pickItems, { placeHolder: quickPickHintMessage }); |
There was a problem hiding this comment.
What if user cancels by pressing Esc?
There was a problem hiding this comment.
selected = undefined if user cancels by ESC.
| }; | ||
| } | ||
|
|
||
| private async fixMainClass(folder: vscode.Uri | undefined, config: vscode.DebugConfiguration, validationResponse: ILaunchValidationResponse): |
There was a problem hiding this comment.
Same comment regarding optional params.
| return; | ||
| } | ||
|
|
||
| private async persistMainClassOption(folder: vscode.Uri | undefined, oldConfig: vscode.DebugConfiguration, change: IMainClassOption): |
There was a problem hiding this comment.
Same comment regarding optional params and sequence.
| return this.persistLaunchConfig(folder, oldConfig, newConfig); | ||
| } | ||
|
|
||
| private async persistLaunchConfig(folder: vscode.Uri | undefined, oldConfig: vscode.DebugConfiguration, newConfig: vscode.DebugConfiguration): |
There was a problem hiding this comment.
Same comment regarding optional params and sequence.
| } | ||
| } | ||
|
|
||
| private async promptMainClass(folder: vscode.Uri | undefined): Promise<IMainClassOption | undefined> { |
There was a problem hiding this comment.
Same comment regarding optional params.
Signed-off-by: Jinbo Wang jinbwan@microsoft.com
See issue #355 for the requirement.
The validation work is done on java code side, see the peer PR: microsoft/java-debug#205