Skip to content

Validate mainClass and projectName before launch - #368

Merged
Jinbo Wang (testforstephen) merged 12 commits into
masterfrom
jinbo_validate
Aug 28, 2018
Merged

Jinbo Wang (testforstephen) merged 12 commits into
masterfrom
jinbo_validate

Conversation

@testforstephen

@testforstephen Jinbo Wang (testforstephen) commented Aug 15, 2018 •

Copy link
Copy Markdown
Contributor

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

Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
Comment thread src/configurationProvider.ts Outdated

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):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/configurationProvider.ts Outdated

if (validationResponse.proposals && validationResponse.proposals.length) {
const answer = await utility.showErrorMessageWithTroubleshooting({
message: errors.join("\n"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use platform dependent separator

Comment thread src/configurationProvider.ts Outdated
}
}

private findDebugConfig(rawConfigs: vscode.DebugConfiguration[], config: vscode.DebugConfiguration): number {

@yaohaizh Yaohai Zheng (yaohaizh) Aug 15, 2018 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are Java debugger, use the type to filter out unnecessary DebugConfig.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use lodash utility to search the debug config instead.

Comment thread src/configurationProvider.ts Outdated
return -1;
}

private compareDebugConfig(config1: object, config2: object): boolean {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method is discarded.


interface IValidationResult {
readonly passed: boolean;
readonly message?: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

message ==> errorMessage?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after discussion, we think the neutral message name is more appropriate, because message could be for both valid and invalid case.

@yaohaizh Yaohai Zheng (yaohaizh) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chose wrong result...

Comment thread src/configurationProvider.ts Outdated

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):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest 'resolveLaunchConfig' because a fix is not happening in every case.

Comment thread src/configurationProvider.ts Outdated
const selection = pickItems.length > 1 ?
await vscode.window.showQuickPick(pickItems, { placeHolder: "Select main class<project name>" })

const selection = alwaysShowQuickPickBox || pickItems.length > 1 ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/configurationProvider.ts Outdated
}

interface IValidationResult {
readonly passed: boolean;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

passed -> isValid

Comment thread src/configurationProvider.ts Outdated
return true;
}

private async chooseMainClass(folder: vscode.Uri | undefined): Promise<IMainClassOption | undefined> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chooseMainClass -> promptMainClass

Comment thread src/configurationProvider.ts Outdated
const answer = await utility.showErrorMessageWithTroubleshooting({
message: errors.join("\n"),
type: Type.USAGEERROR,
}, "Quick Fix");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'Quick Fix' -> 'Fix'

Comment thread src/configurationProvider.ts Outdated
type: Type.USAGEERROR,
}, "Quick Fix");
if (answer === "Quick Fix") {
const quickFix: IMainClassOption = await this.showMainClassQuickPick(validationResponse.proposals,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if user cancels and returns nothing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user chooses to cancel the fix QuickPick box, then the debugger exits.

Comment thread src/configurationProvider.ts Outdated
}
}

private persistLaunchConfig(folder: vscode.Uri | undefined, config: vscode.DebugConfiguration, change: IMainClassOption): void {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/configurationProvider.ts Outdated
}

private findDebugConfig(rawConfigs: vscode.DebugConfiguration[], config: vscode.DebugConfiguration): number {
const candidates = [];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Comment thread src/configurationProvider.ts Outdated
readonly passed: boolean;
readonly message?: string;
readonly isValid: boolean;
readonly errorMessage?: string;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message field can be neutral because there could message for both valid and invalid case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Have reverted the field back to message.

import * as _ from "lodash";
import * as os from "os";
import * as vscode from "vscode";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice to have extra line to separate 3rd party dependencies and local dependencies.

}

if (validationResponse.proposals && validationResponse.proposals.length) {
const answer = await utility.showErrorMessageWithTroubleshooting({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dup with line 257, you can keep the promise of utility.showErrorMessageWithTroubleshooting and await it if validationResponse.proposals have values.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/configurationProvider.ts Outdated
config.projectName = userSelection.projectName;

const mainClassOption = await this.resolveLaunchConfig(folder ? folder.uri : undefined, config);
if (!mainClassOption || !mainClassOption.mainClass) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use try/catch block to handle the rejected/exception case.

Comment thread src/configurationProvider.ts Outdated

private async chooseMainClass(folder: vscode.WorkspaceFolder | undefined): Promise<IMainClassOption | undefined> {
const res = await resolveMainClass(folder ? folder.uri : undefined);
private isEmptyArray(configItems: any): boolean {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use _.isEmpty() instead.

return !Array.isArray(configItems) || !configItems.length;
}

private async resolveLaunchConfig(folder: vscode.Uri | undefined, config: vscode.DebugConfiguration): Promise<IMainClassOption> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@testforstephen Jinbo Wang (testforstephen) Aug 27, 2018 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/configurationProvider.ts Outdated
private async showMainClassQuickPick(pickItems: IMainClassQuickPickItem[], quickPickHintMessage: string, autoPick: boolean = true):
Promise<IMainClassOption | undefined> {
if (pickItems.length === 0) {
return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if user cancels by pressing Esc?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selected = undefined if user cancels by ESC.

};
}

private async fixMainClass(folder: vscode.Uri | undefined, config: vscode.DebugConfiguration, validationResponse: ILaunchValidationResponse):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment regarding optional params.

return;
}

private async persistMainClassOption(folder: vscode.Uri | undefined, oldConfig: vscode.DebugConfiguration, change: IMainClassOption):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment regarding optional params and sequence.

}
}

private async promptMainClass(folder: vscode.Uri | undefined): Promise<IMainClassOption | undefined> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment regarding optional params.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants