Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Variable error proposal #157788

Merged
merged 2 commits into from Aug 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -79,3 +79,38 @@ export interface CommandInputInfo {
}

export type ConfiguredInput = PromptStringInputInfo | PickStringInputInfo | CommandInputInfo;

export enum VariableKind {
Unknown = 'unknown',

Env = 'env',
Config = 'config',
Command = 'command',
Input = 'input',
ExtensionInstallFolder = 'extensionInstallFolder',

WorkspaceFolder = 'workspaceFolder',
Cwd = 'cwd',
WorkspaceFolderBasename = 'workspaceFolderBasename',
UserHome = 'userHome',
LineNumber = 'lineNumber',
SelectedText = 'selectedText',
File = 'file',
FileWorkspaceFolder = 'fileWorkspaceFolder',
RelativeFile = 'relativeFile',
RelativeFileDirname = 'relativeFileDirname',
FileDirname = 'fileDirname',
FileExtname = 'fileExtname',
FileBasename = 'fileBasename',
FileBasenameNoExtension = 'fileBasenameNoExtension',
FileDirnameBasename = 'fileDirnameBasename',
ExecPath = 'execPath',
ExecInstallFolder = 'execInstallFolder',
PathSeparator = 'pathSeparator'
}

export class VariableError extends Error {
constructor(public readonly variable: VariableKind, message?: string) {
super(message);
}
}
Expand Up @@ -12,7 +12,7 @@ import { IProcessEnvironment, isWindows, isMacintosh, isLinux } from 'vs/base/co
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { localize } from 'vs/nls';
import { URI as uri } from 'vs/base/common/uri';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IConfigurationResolverService, VariableError, VariableKind } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ILabelService } from 'vs/platform/label/common/label';
import { replaceAsync } from 'vs/base/common/strings';
Expand Down Expand Up @@ -190,47 +190,47 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe
}

// common error handling for all variables that require an open editor
const getFilePath = (): string => {
const getFilePath = (variableKind: VariableKind): string => {

const filePath = this._context.getFilePath();
if (filePath) {
return filePath;
}
throw new Error(localize('canNotResolveFile', "Variable {0} can not be resolved. Please open an editor.", match));
throw new VariableError(variableKind, (localize('canNotResolveFile', "Variable {0} can not be resolved. Please open an editor.", match)));
};

// common error handling for all variables that require an open editor
const getFolderPathForFile = (): string => {
const getFolderPathForFile = (variableKind: VariableKind): string => {

const filePath = getFilePath(); // throws error if no editor open
const filePath = getFilePath(variableKind); // throws error if no editor open
if (this._context.getWorkspaceFolderPathForFile) {
const folderPath = this._context.getWorkspaceFolderPathForFile();
if (folderPath) {
return folderPath;
}
}
throw new Error(localize('canNotResolveFolderForFile', "Variable {0}: can not find workspace folder of '{1}'.", match, paths.basename(filePath)));
throw new VariableError(variableKind, localize('canNotResolveFolderForFile', "Variable {0}: can not find workspace folder of '{1}'.", match, paths.basename(filePath)));
};

// common error handling for all variables that require an open folder and accept a folder name argument
const getFolderUri = (): uri => {
const getFolderUri = (variableKind: VariableKind): uri => {

if (argument) {
const folder = this._context.getFolderUri(argument);
if (folder) {
return folder;
}
throw new Error(localize('canNotFindFolder', "Variable {0} can not be resolved. No such folder '{1}'.", match, argument));
throw new VariableError(variableKind, localize('canNotFindFolder', "Variable {0} can not be resolved. No such folder '{1}'.", match, argument));
}

if (folderUri) {
return folderUri;
}

if (this._context.getWorkspaceFolderCount() > 1) {
throw new Error(localize('canNotResolveWorkspaceFolderMultiRoot', "Variable {0} can not be resolved in a multi folder workspace. Scope this variable using ':' and a workspace folder name.", match));
throw new VariableError(variableKind, localize('canNotResolveWorkspaceFolderMultiRoot', "Variable {0} can not be resolved in a multi folder workspace. Scope this variable using ':' and a workspace folder name.", match));
}
throw new Error(localize('canNotResolveWorkspaceFolder', "Variable {0} can not be resolved. Please open a folder.", match));
throw new VariableError(variableKind, localize('canNotResolveWorkspaceFolder', "Variable {0} can not be resolved. Please open a folder.", match));
};


Expand All @@ -248,107 +248,107 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe
// For `env` we should do the same as a normal shell does - evaluates undefined envs to an empty string #46436
return '';
}
throw new Error(localize('missingEnvVarName', "Variable {0} can not be resolved because no environment variable name is given.", match));
throw new VariableError(VariableKind.Env, localize('missingEnvVarName', "Variable {0} can not be resolved because no environment variable name is given.", match));

case 'config':
if (argument) {
const config = this._context.getConfigurationValue(folderUri, argument);
if (types.isUndefinedOrNull(config)) {
throw new Error(localize('configNotFound', "Variable {0} can not be resolved because setting '{1}' not found.", match, argument));
throw new VariableError(VariableKind.Config, localize('configNotFound', "Variable {0} can not be resolved because setting '{1}' not found.", match, argument));
}
if (types.isObject(config)) {
throw new Error(localize('configNoString', "Variable {0} can not be resolved because '{1}' is a structured value.", match, argument));
throw new VariableError(VariableKind.Config, localize('configNoString', "Variable {0} can not be resolved because '{1}' is a structured value.", match, argument));
}
return config;
}
throw new Error(localize('missingConfigName', "Variable {0} can not be resolved because no settings name is given.", match));
throw new VariableError(VariableKind.Config, localize('missingConfigName', "Variable {0} can not be resolved because no settings name is given.", match));

case 'command':
return this.resolveFromMap(match, argument, commandValueMapping, 'command');
return this.resolveFromMap(VariableKind.Command, match, argument, commandValueMapping, 'command');

case 'input':
return this.resolveFromMap(match, argument, commandValueMapping, 'input');
return this.resolveFromMap(VariableKind.Input, match, argument, commandValueMapping, 'input');

case 'extensionInstallFolder':
if (argument) {
const ext = await this._context.getExtension(argument);
if (!ext) {
throw new Error(localize('extensionNotInstalled', "Variable {0} can not be resolved because the extension {1} is not installed.", match, argument));
throw new VariableError(VariableKind.ExtensionInstallFolder, localize('extensionNotInstalled', "Variable {0} can not be resolved because the extension {1} is not installed.", match, argument));
}
return this.fsPath(ext.extensionLocation);
}
throw new Error(localize('missingExtensionName', "Variable {0} can not be resolved because no extension name is given.", match));
throw new VariableError(VariableKind.ExtensionInstallFolder, localize('missingExtensionName', "Variable {0} can not be resolved because no extension name is given.", match));

default: {

switch (variable) {
case 'workspaceRoot':
case 'workspaceFolder':
return normalizeDriveLetter(this.fsPath(getFolderUri()));
return normalizeDriveLetter(this.fsPath(getFolderUri(VariableKind.WorkspaceFolder)));

case 'cwd':
return ((folderUri || argument) ? normalizeDriveLetter(this.fsPath(getFolderUri())) : process.cwd());
return ((folderUri || argument) ? normalizeDriveLetter(this.fsPath(getFolderUri(VariableKind.Cwd))) : process.cwd());

case 'workspaceRootFolderName':
case 'workspaceFolderBasename':
return paths.basename(this.fsPath(getFolderUri()));
return paths.basename(this.fsPath(getFolderUri(VariableKind.WorkspaceFolderBasename)));

case 'userHome': {
if (environment.userHome) {
return environment.userHome;
}
throw new Error(localize('canNotResolveUserHome', "Variable {0} can not be resolved. UserHome path is not defined", match));
throw new VariableError(VariableKind.UserHome, localize('canNotResolveUserHome', "Variable {0} can not be resolved. UserHome path is not defined", match));
}

case 'lineNumber': {
const lineNumber = this._context.getLineNumber();
if (lineNumber) {
return lineNumber;
}
throw new Error(localize('canNotResolveLineNumber', "Variable {0} can not be resolved. Make sure to have a line selected in the active editor.", match));
throw new VariableError(VariableKind.LineNumber, localize('canNotResolveLineNumber', "Variable {0} can not be resolved. Make sure to have a line selected in the active editor.", match));
}
case 'selectedText': {
const selectedText = this._context.getSelectedText();
if (selectedText) {
return selectedText;
}
throw new Error(localize('canNotResolveSelectedText', "Variable {0} can not be resolved. Make sure to have some text selected in the active editor.", match));
throw new VariableError(VariableKind.SelectedText, localize('canNotResolveSelectedText', "Variable {0} can not be resolved. Make sure to have some text selected in the active editor.", match));
}
case 'file':
return getFilePath();
return getFilePath(VariableKind.File);

case 'fileWorkspaceFolder':
return getFolderPathForFile();
return getFolderPathForFile(VariableKind.FileWorkspaceFolder);

case 'relativeFile':
if (folderUri || argument) {
return paths.relative(this.fsPath(getFolderUri()), getFilePath());
return paths.relative(this.fsPath(getFolderUri(VariableKind.RelativeFile)), getFilePath(VariableKind.RelativeFile));
}
return getFilePath();
return getFilePath(VariableKind.RelativeFile);

case 'relativeFileDirname': {
const dirname = paths.dirname(getFilePath());
const dirname = paths.dirname(getFilePath(VariableKind.RelativeFileDirname));
if (folderUri || argument) {
const relative = paths.relative(this.fsPath(getFolderUri()), dirname);
const relative = paths.relative(this.fsPath(getFolderUri(VariableKind.RelativeFileDirname)), dirname);
return relative.length === 0 ? '.' : relative;
}
return dirname;
}
case 'fileDirname':
return paths.dirname(getFilePath());
return paths.dirname(getFilePath(VariableKind.FileDirname));

case 'fileExtname':
return paths.extname(getFilePath());
return paths.extname(getFilePath(VariableKind.FileExtname));

case 'fileBasename':
return paths.basename(getFilePath());
return paths.basename(getFilePath(VariableKind.FileBasename));

case 'fileBasenameNoExtension': {
const basename = paths.basename(getFilePath());
const basename = paths.basename(getFilePath(VariableKind.FileBasenameNoExtension));
return (basename.slice(0, basename.length - paths.extname(basename).length));
}
case 'fileDirnameBasename':
return paths.basename(paths.dirname(getFilePath()));
return paths.basename(paths.dirname(getFilePath(VariableKind.FileDirnameBasename)));

case 'execPath': {
const ep = this._context.getExecPath();
Expand All @@ -370,7 +370,7 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe
default:
try {
const key = argument ? `${variable}:${argument}` : variable;
return this.resolveFromMap(match, key, commandValueMapping, undefined);
return this.resolveFromMap(VariableKind.Unknown, match, key, commandValueMapping, undefined);
} catch (error) {
return match;
}
Expand All @@ -379,13 +379,13 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe
}
}

private resolveFromMap(match: string, argument: string | undefined, commandValueMapping: IStringDictionary<string> | undefined, prefix: string | undefined): string {
private resolveFromMap(variableKind: VariableKind, match: string, argument: string | undefined, commandValueMapping: IStringDictionary<string> | undefined, prefix: string | undefined): string {
if (argument && commandValueMapping) {
const v = (prefix === undefined) ? commandValueMapping[argument] : commandValueMapping[prefix + ':' + argument];
if (typeof v === 'string') {
return v;
}
throw new Error(localize('noValueForCommand', "Variable {0} can not be resolved because the command has no value.", match));
throw new VariableError(variableKind, localize('noValueForCommand', "Variable {0} can not be resolved because the command has no value.", match));
}
return match;
}
Expand Down