Skip to content

Commit 3070efc

Browse files
Kartik Rajwesm
Kartik Raj
authored andcommitted
Ensure string prototypes extension extends are unique enough (microsoft/vscode-python#18870)
1 parent 62b3036 commit 3070efc

File tree

32 files changed

+129
-88
lines changed

32 files changed

+129
-88
lines changed

extensions/positron-python/src/client/common/extensions.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ declare interface String {
2020
* Appropriately formats a string so it can be used as an argument for a command in a shell.
2121
* E.g. if an argument contains a space, then it will be enclosed within double quotes.
2222
*/
23-
toCommandArgument(): string;
23+
toCommandArgumentForPythonExt(): string;
2424
/**
2525
* Appropriately formats a a file path so it can be used as an argument for a command in a shell.
2626
* E.g. if an argument contains a space, then it will be enclosed within double quotes.
2727
*/
28-
fileToCommandArgument(): string;
28+
fileToCommandArgumentForPythonExt(): string;
2929
/**
3030
* String.format() implementation.
3131
* Tokens such as {0}, {1} will be replaced with corresponding positional arguments.
@@ -69,7 +69,7 @@ String.prototype.splitLines = function (
6969
* E.g. if an argument contains a space, then it will be enclosed within double quotes.
7070
* @param {String} value.
7171
*/
72-
String.prototype.toCommandArgument = function (this: string): string {
72+
String.prototype.toCommandArgumentForPythonExt = function (this: string): string {
7373
if (!this) {
7474
return this;
7575
}
@@ -82,11 +82,11 @@ String.prototype.toCommandArgument = function (this: string): string {
8282
* Appropriately formats a a file path so it can be used as an argument for a command in a shell.
8383
* E.g. if an argument contains a space, then it will be enclosed within double quotes.
8484
*/
85-
String.prototype.fileToCommandArgument = function (this: string): string {
85+
String.prototype.fileToCommandArgumentForPythonExt = function (this: string): string {
8686
if (!this) {
8787
return this;
8888
}
89-
return this.toCommandArgument().replace(/\\/g, '/');
89+
return this.toCommandArgumentForPythonExt().replace(/\\/g, '/');
9090
};
9191

9292
/**

extensions/positron-python/src/client/common/installer/condaInstaller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ export class CondaInstaller extends ModuleInstaller {
104104
if (info && info.name) {
105105
// If we have the name of the conda environment, then use that.
106106
args.push('--name');
107-
args.push(info.name.toCommandArgument());
107+
args.push(info.name.toCommandArgumentForPythonExt());
108108
} else if (info && info.path) {
109109
// Else provide the full path to the environment path.
110110
args.push('--prefix');
111-
args.push(info.path.fileToCommandArgument());
111+
args.push(info.path.fileToCommandArgumentForPythonExt());
112112
}
113113
if (flags & ModuleInstallFlags.updateDependencies) {
114114
args.push('--update-deps');

extensions/positron-python/src/client/common/installer/moduleInstaller.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,8 @@ export abstract class ModuleInstaller implements IModuleInstaller {
217217
const argv = [command, ...args];
218218
// Concat these together to make a set of quoted strings
219219
const quoted = argv.reduce(
220-
(p, c) => (p ? `${p} ${c.toCommandArgument()}` : `${c.toCommandArgument()}`),
220+
(p, c) =>
221+
p ? `${p} ${c.toCommandArgumentForPythonExt()}` : `${c.toCommandArgumentForPythonExt()}`,
221222
'',
222223
);
223224
await processService.shellExec(quoted);

extensions/positron-python/src/client/common/process/internal/scripts/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export function normalizeSelection(): [string[], (out: string) => string] {
9494
// printEnvVariables.py
9595

9696
export function printEnvVariables(): [string[], (out: string) => NodeJS.ProcessEnv] {
97-
const script = path.join(SCRIPTS_DIR, 'printEnvVariables.py').fileToCommandArgument();
97+
const script = path.join(SCRIPTS_DIR, 'printEnvVariables.py').fileToCommandArgumentForPythonExt();
9898
const args = [script];
9999

100100
function parse(out: string): NodeJS.ProcessEnv {
@@ -113,11 +113,11 @@ export function shell_exec(command: string, lockfile: string, shellArgs: string[
113113
// could be anything.
114114
return [
115115
script,
116-
command.fileToCommandArgument(),
116+
command.fileToCommandArgumentForPythonExt(),
117117
// The shell args must come after the command
118118
// but before the lockfile.
119119
...shellArgs,
120-
lockfile.fileToCommandArgument(),
120+
lockfile.fileToCommandArgumentForPythonExt(),
121121
];
122122
}
123123

extensions/positron-python/src/client/common/process/logger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export class ProcessLogger implements IProcessLogger {
2222
return;
2323
}
2424
let command = args
25-
? [fileOrCommand, ...args].map((e) => e.trimQuotes().toCommandArgument()).join(' ')
25+
? [fileOrCommand, ...args].map((e) => e.trimQuotes().toCommandArgumentForPythonExt()).join(' ')
2626
: fileOrCommand;
2727
const info = [`> ${this.getDisplayCommands(command)}`];
2828
if (options && options.cwd) {

extensions/positron-python/src/client/common/terminal/environmentActivationProviders/bash.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,6 @@ export class Bash extends VenvBaseActivationCommandProvider {
4646
if (!scriptFile) {
4747
return;
4848
}
49-
return [`source ${scriptFile.fileToCommandArgument()}`];
49+
return [`source ${scriptFile.fileToCommandArgumentForPythonExt()}`];
5050
}
5151
}

extensions/positron-python/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,12 @@ export class CommandPromptAndPowerShell extends VenvBaseActivationCommandProvide
6666
}
6767

6868
if (targetShell === TerminalShellType.commandPrompt && scriptFile.endsWith('activate.bat')) {
69-
return [scriptFile.fileToCommandArgument()];
69+
return [scriptFile.fileToCommandArgumentForPythonExt()];
7070
} else if (
7171
(targetShell === TerminalShellType.powershell || targetShell === TerminalShellType.powershellCore) &&
7272
scriptFile.endsWith('Activate.ps1')
7373
) {
74-
return [`& ${scriptFile.fileToCommandArgument()}`];
74+
return [`& ${scriptFile.fileToCommandArgumentForPythonExt()}`];
7575
} else if (targetShell === TerminalShellType.commandPrompt && scriptFile.endsWith('Activate.ps1')) {
7676
// lets not try to run the powershell file from command prompt (user may not have powershell)
7777
return [];

extensions/positron-python/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,11 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman
8383
const interpreterPath = await this.condaService.getInterpreterPathForEnvironment(envInfo);
8484
const condaPath = await this.condaService.getCondaFileFromInterpreter(interpreterPath, envInfo.name);
8585
if (condaPath) {
86-
const activatePath = path.join(path.dirname(condaPath), 'activate').fileToCommandArgument();
86+
const activatePath = path
87+
.join(path.dirname(condaPath), 'activate')
88+
.fileToCommandArgumentForPythonExt();
8789
const firstActivate = this.platform.isWindows ? activatePath : `source ${activatePath}`;
88-
return [firstActivate, `conda activate ${condaEnv.toCommandArgument()}`];
90+
return [firstActivate, `conda activate ${condaEnv.toCommandArgumentForPythonExt()}`];
8991
}
9092
}
9193
}
@@ -116,15 +118,15 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman
116118
const condaScriptsPath: string = path.dirname(condaExePath);
117119
// prefix the cmd with the found path, and ensure it's quoted properly
118120
activateCmd = path.join(condaScriptsPath, activateCmd);
119-
activateCmd = activateCmd.toCommandArgument();
121+
activateCmd = activateCmd.toCommandArgumentForPythonExt();
120122
}
121123

122124
return activateCmd;
123125
}
124126

125127
public async getWindowsCommands(condaEnv: string): Promise<string[] | undefined> {
126128
const activate = await this.getWindowsActivateCommand();
127-
return [`${activate} ${condaEnv.toCommandArgument()}`];
129+
return [`${activate} ${condaEnv.toCommandArgumentForPythonExt()}`];
128130
}
129131
}
130132

@@ -135,16 +137,16 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman
135137
* Extension will not attempt to work around issues by trying to setup shell for user.
136138
*/
137139
export async function _getPowershellCommands(condaEnv: string): Promise<string[] | undefined> {
138-
return [`conda activate ${condaEnv.toCommandArgument()}`];
140+
return [`conda activate ${condaEnv.toCommandArgumentForPythonExt()}`];
139141
}
140142

141143
async function getFishCommands(condaEnv: string, condaFile: string): Promise<string[] | undefined> {
142144
// https://github.com/conda/conda/blob/be8c08c083f4d5e05b06bd2689d2cd0d410c2ffe/shell/etc/fish/conf.d/conda.fish#L18-L28
143-
return [`${condaFile.fileToCommandArgument()} activate ${condaEnv.toCommandArgument()}`];
145+
return [`${condaFile.fileToCommandArgumentForPythonExt()} activate ${condaEnv.toCommandArgumentForPythonExt()}`];
144146
}
145147

146148
async function getUnixCommands(condaEnv: string, condaFile: string): Promise<string[] | undefined> {
147149
const condaDir = path.dirname(condaFile);
148150
const activateFile = path.join(condaDir, 'activate');
149-
return [`source ${activateFile.fileToCommandArgument()} ${condaEnv.toCommandArgument()}`];
151+
return [`source ${activateFile.fileToCommandArgumentForPythonExt()} ${condaEnv.toCommandArgumentForPythonExt()}`];
150152
}

extensions/positron-python/src/client/common/terminal/environmentActivationProviders/pipEnvActivationProvider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class PipEnvActivationCommandProvider implements ITerminalActivationComma
4141
}
4242
}
4343
const execName = this.pipEnvExecution.executable;
44-
return [`${execName.fileToCommandArgument()} shell`];
44+
return [`${execName.fileToCommandArgumentForPythonExt()} shell`];
4545
}
4646

4747
public async getActivationCommandsForInterpreter(pythonPath: string): Promise<string[] | undefined> {
@@ -51,6 +51,6 @@ export class PipEnvActivationCommandProvider implements ITerminalActivationComma
5151
}
5252

5353
const execName = this.pipEnvExecution.executable;
54-
return [`${execName.fileToCommandArgument()} shell`];
54+
return [`${execName.fileToCommandArgumentForPythonExt()} shell`];
5555
}
5656
}

extensions/positron-python/src/client/common/terminal/environmentActivationProviders/pyenvActivationProvider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class PyEnvActivationCommandProvider implements ITerminalActivationComman
2626
return;
2727
}
2828

29-
return [`pyenv shell ${interpreter.envName.toCommandArgument()}`];
29+
return [`pyenv shell ${interpreter.envName.toCommandArgumentForPythonExt()}`];
3030
}
3131

3232
public async getActivationCommandsForInterpreter(
@@ -40,6 +40,6 @@ export class PyEnvActivationCommandProvider implements ITerminalActivationComman
4040
return;
4141
}
4242

43-
return [`pyenv shell ${interpreter.envName.toCommandArgument()}`];
43+
return [`pyenv shell ${interpreter.envName.toCommandArgumentForPythonExt()}`];
4444
}
4545
}

extensions/positron-python/src/client/common/terminal/helper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ export class TerminalHelper implements ITerminalHelper {
6363
terminalShellType === TerminalShellType.powershell ||
6464
terminalShellType === TerminalShellType.powershellCore;
6565
const commandPrefix = isPowershell ? '& ' : '';
66-
const formattedArgs = args.map((a) => a.toCommandArgument());
66+
const formattedArgs = args.map((a) => a.toCommandArgumentForPythonExt());
6767

68-
return `${commandPrefix}${command.fileToCommandArgument()} ${formattedArgs.join(' ')}`.trim();
68+
return `${commandPrefix}${command.fileToCommandArgumentForPythonExt()} ${formattedArgs.join(' ')}`.trim();
6969
}
7070
public async getEnvironmentActivationCommands(
7171
terminalShellType: TerminalShellType,

extensions/positron-python/src/client/debugger/extension/adapter/remoteLaunchers.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ type RemoteDebugOptions = {
1818

1919
export function getDebugpyLauncherArgs(options: RemoteDebugOptions, debuggerPath: string = pathToDebugger) {
2020
const waitArgs = options.waitUntilDebuggerAttaches ? ['--wait-for-client'] : [];
21-
return [debuggerPath.fileToCommandArgument(), '--listen', `${options.host}:${options.port}`, ...waitArgs];
21+
return [
22+
debuggerPath.fileToCommandArgumentForPythonExt(),
23+
'--listen',
24+
`${options.host}:${options.port}`,
25+
...waitArgs,
26+
];
2227
}
2328

2429
export function getDebugpyPackagePath(): string {

extensions/positron-python/src/client/interpreter/activation/service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ export class EnvironmentActivationService implements IEnvironmentActivationServi
174174
let command: string | undefined;
175175
let [args, parse] = internalScripts.printEnvVariables();
176176
args.forEach((arg, i) => {
177-
args[i] = arg.toCommandArgument();
177+
args[i] = arg.toCommandArgumentForPythonExt();
178178
});
179179
interpreter = interpreter ?? (await this.interpreterService.getActiveInterpreter(resource));
180180
if (interpreter?.envType === EnvironmentType.Conda) {
@@ -185,7 +185,7 @@ export class EnvironmentActivationService implements IEnvironmentActivationServi
185185
});
186186
if (pythonArgv) {
187187
// Using environment prefix isn't needed as the marker script already takes care of it.
188-
command = [...pythonArgv, ...args].map((arg) => arg.toCommandArgument()).join(' ');
188+
command = [...pythonArgv, ...args].map((arg) => arg.toCommandArgumentForPythonExt()).join(' ');
189189
}
190190
}
191191
if (!command) {

extensions/positron-python/src/client/pythonEnvironments/base/info/interpreter.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ export async function getInterpreterInfo(
7575
const argv = [info.command, ...info.args];
7676

7777
// Concat these together to make a set of quoted strings
78-
const quoted = argv.reduce((p, c) => (p ? `${p} ${c.toCommandArgument()}` : `${c.toCommandArgument()}`), '');
78+
const quoted = argv.reduce(
79+
(p, c) => (p ? `${p} ${c.toCommandArgumentForPythonExt()}` : `${c.toCommandArgumentForPythonExt()}`),
80+
'',
81+
);
7982

8083
// Try shell execing the command, followed by the arguments. This will make node kill the process if it
8184
// takes too long.

extensions/positron-python/src/client/pythonEnvironments/info/executable.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ export async function getExecutablePath(
2222
const info = copyPythonExecInfo(python, args);
2323
const argv = [info.command, ...info.args];
2424
// Concat these together to make a set of quoted strings
25-
const quoted = argv.reduce((p, c) => (p ? `${p} ${c.toCommandArgument()}` : `${c.toCommandArgument()}`), '');
25+
const quoted = argv.reduce(
26+
(p, c) => (p ? `${p} ${c.toCommandArgumentForPythonExt()}` : `${c.toCommandArgumentForPythonExt()}`),
27+
'',
28+
);
2629
const result = await shellExec(quoted, { timeout: timeout ?? 15000 });
2730
const executable = parse(result.stdout.trim());
2831
if (executable === '') {

extensions/positron-python/src/client/terminals/codeExecution/djangoShellCodeExecution.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvi
5252
const workspaceRoot = workspaceUri ? workspaceUri.uri.fsPath : defaultWorkspace;
5353
const managePyPath = workspaceRoot.length === 0 ? 'manage.py' : path.join(workspaceRoot, 'manage.py');
5454

55-
return copyPythonExecInfo(info, [managePyPath.fileToCommandArgument(), 'shell']);
55+
return copyPythonExecInfo(info, [managePyPath.fileToCommandArgumentForPythonExt(), 'shell']);
5656
}
5757

5858
public async getExecuteFileArgs(resource?: Uri, executeArgs: string[] = []): Promise<PythonExecInfo> {

extensions/positron-python/src/client/terminals/codeExecution/terminalCodeExecution.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService {
3232

3333
public async executeFile(file: Uri) {
3434
await this.setCwdForFileExecution(file);
35-
const { command, args } = await this.getExecuteFileArgs(file, [file.fsPath.fileToCommandArgument()]);
35+
const x = file.fsPath;
36+
const hello = x.fileToCommandArgumentForPythonExt();
37+
const { command, args } = await this.getExecuteFileArgs(file, [hello]);
3638

3739
await this.getTerminalService(file).sendCommand(command, args);
3840
}
@@ -106,7 +108,7 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService {
106108
await this.getTerminalService(file).sendText(`${fileDrive}:`);
107109
}
108110
}
109-
await this.getTerminalService(file).sendText(`cd ${fileDirPath.fileToCommandArgument()}`);
111+
await this.getTerminalService(file).sendText(`cd ${fileDirPath.fileToCommandArgumentForPythonExt()}`);
110112
}
111113
}
112114
}

extensions/positron-python/src/test/api.functional.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,11 @@ suite('Extension API', () => {
8484
instance(serviceManager),
8585
instance(serviceContainer),
8686
).debug.getRemoteLauncherCommand(debuggerHost, debuggerPort, waitForAttach);
87-
const expectedArgs = [debuggerPath.fileToCommandArgument(), '--listen', `${debuggerHost}:${debuggerPort}`];
87+
const expectedArgs = [
88+
debuggerPath.fileToCommandArgumentForPythonExt(),
89+
'--listen',
90+
`${debuggerHost}:${debuggerPort}`,
91+
];
8892

8993
expect(args).to.be.deep.equal(expectedArgs);
9094
});
@@ -98,7 +102,7 @@ suite('Extension API', () => {
98102
instance(serviceContainer),
99103
).debug.getRemoteLauncherCommand(debuggerHost, debuggerPort, waitForAttach);
100104
const expectedArgs = [
101-
debuggerPath.fileToCommandArgument(),
105+
debuggerPath.fileToCommandArgumentForPythonExt(),
102106
'--listen',
103107
`${debuggerHost}:${debuggerPort}`,
104108
'--wait-for-client',

extensions/positron-python/src/test/common/extensions.unit.test.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,47 @@ import { asyncFilter } from '../../client/common/utils/arrayUtils';
66
suite('String Extensions', () => {
77
test('Should return empty string for empty arg', () => {
88
const argTotest = '';
9-
expect(argTotest.toCommandArgument()).to.be.equal('');
9+
expect(argTotest.toCommandArgumentForPythonExt()).to.be.equal('');
1010
});
1111
test('Should quote an empty space', () => {
1212
const argTotest = ' ';
13-
expect(argTotest.toCommandArgument()).to.be.equal('" "');
13+
expect(argTotest.toCommandArgumentForPythonExt()).to.be.equal('" "');
1414
});
1515
test('Should not quote command arguments without spaces', () => {
1616
const argTotest = 'one.two.three';
17-
expect(argTotest.toCommandArgument()).to.be.equal(argTotest);
17+
expect(argTotest.toCommandArgumentForPythonExt()).to.be.equal(argTotest);
1818
});
1919
test('Should quote command arguments with spaces', () => {
2020
const argTotest = 'one two three';
21-
expect(argTotest.toCommandArgument()).to.be.equal(`"${argTotest}"`);
21+
expect(argTotest.toCommandArgumentForPythonExt()).to.be.equal(`"${argTotest}"`);
2222
});
2323
test('Should quote command arguments containing ampersand', () => {
2424
const argTotest = 'one&twothree';
25-
expect(argTotest.toCommandArgument()).to.be.equal(`"${argTotest}"`);
25+
expect(argTotest.toCommandArgumentForPythonExt()).to.be.equal(`"${argTotest}"`);
2626
});
2727
test('Should return empty string for empty path', () => {
2828
const fileToTest = '';
29-
expect(fileToTest.fileToCommandArgument()).to.be.equal('');
29+
expect(fileToTest.fileToCommandArgumentForPythonExt()).to.be.equal('');
3030
});
3131
test('Should not quote file argument without spaces', () => {
3232
const fileToTest = 'users/test/one';
33-
expect(fileToTest.fileToCommandArgument()).to.be.equal(fileToTest);
33+
expect(fileToTest.fileToCommandArgumentForPythonExt()).to.be.equal(fileToTest);
3434
});
3535
test('Should quote file argument with spaces', () => {
3636
const fileToTest = 'one two three';
37-
expect(fileToTest.fileToCommandArgument()).to.be.equal(`"${fileToTest}"`);
37+
expect(fileToTest.fileToCommandArgumentForPythonExt()).to.be.equal(`"${fileToTest}"`);
3838
});
3939
test('Should replace all back slashes with forward slashes (irrespective of OS)', () => {
4040
const fileToTest = 'c:\\users\\user\\conda\\scripts\\python.exe';
41-
expect(fileToTest.fileToCommandArgument()).to.be.equal(fileToTest.replace(/\\/g, '/'));
41+
expect(fileToTest.fileToCommandArgumentForPythonExt()).to.be.equal(fileToTest.replace(/\\/g, '/'));
4242
});
4343
test('Should replace all back slashes with forward slashes (irrespective of OS) and quoted when file has spaces', () => {
4444
const fileToTest = 'c:\\users\\user namne\\conda path\\scripts\\python.exe';
45-
expect(fileToTest.fileToCommandArgument()).to.be.equal(`"${fileToTest.replace(/\\/g, '/')}"`);
45+
expect(fileToTest.fileToCommandArgumentForPythonExt()).to.be.equal(`"${fileToTest.replace(/\\/g, '/')}"`);
4646
});
4747
test('Should replace all back slashes with forward slashes (irrespective of OS) and quoted when file has spaces', () => {
4848
const fileToTest = 'c:\\users\\user namne\\conda path\\scripts\\python.exe';
49-
expect(fileToTest.fileToCommandArgument()).to.be.equal(`"${fileToTest.replace(/\\/g, '/')}"`);
49+
expect(fileToTest.fileToCommandArgumentForPythonExt()).to.be.equal(`"${fileToTest.replace(/\\/g, '/')}"`);
5050
});
5151
test('Should leave string unchanged', () => {
5252
expect('something {0}'.format()).to.be.equal('something {0}');

0 commit comments

Comments
 (0)