Skip to content

Commit

Permalink
Variable error proposal (#157788)
Browse files Browse the repository at this point in the history
* Variable error proposal

* Fully implement VariableError
  • Loading branch information
alexr00 committed Aug 11, 2022
1 parent 7e4bdc7 commit ec5da8a
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 38 deletions.
Original file line number Diff line number Diff line change
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);
}
}
Original file line number Diff line number Diff line change
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

0 comments on commit ec5da8a

Please sign in to comment.