Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 42 additions & 42 deletions vscode/src/ruby.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,11 @@ export class Ruby implements RubyInterface {
this.outputChannel = outputChannel;
this.telemetry = telemetry;

const customBundleGemfile: string = vscode.workspace.getConfiguration("rubyLsp").get("bundleGemfile")!;
const rawBundleGemfile: string = vscode.workspace.getConfiguration("rubyLsp").get("bundleGemfile")!;
const customBundleGemfile = rawBundleGemfile.replace(/\$\{workspaceFolder\}/g, this.workspaceFolder.uri.fsPath);

if (customBundleGemfile.length > 0) {
this.customBundleGemfile = path.isAbsolute(customBundleGemfile)
? customBundleGemfile
: path.resolve(path.join(this.workspaceFolder.uri.fsPath, customBundleGemfile));
this.customBundleGemfile = path.resolve(this.workspaceFolder.uri.fsPath, customBundleGemfile);
}
}

Expand Down Expand Up @@ -120,6 +119,14 @@ export class Ruby implements RubyInterface {
this.versionManager = versionManager;
this._error = false;

if (this.customBundleGemfile) {
try {
await vscode.workspace.fs.stat(vscode.Uri.file(this.customBundleGemfile));
} catch (_error: any) {
throw new Error(`The configured bundle gemfile ${this.customBundleGemfile} does not exist`);
}
}

const workspaceRubyPath = await this.cachedWorkspaceRubyPath();

if (workspaceRubyPath) {
Expand All @@ -131,6 +138,7 @@ export class Ruby implements RubyInterface {
this.context,
this.manuallySelectRuby.bind(this),
workspaceRubyPath,
this.customBundleGemfile,
),
);
} else {
Expand Down Expand Up @@ -165,6 +173,7 @@ export class Ruby implements RubyInterface {
this.context,
this.manuallySelectRuby.bind(this),
globalRubyPath,
this.customBundleGemfile,
),
);
} else {
Expand All @@ -183,7 +192,7 @@ export class Ruby implements RubyInterface {

if (!this.error) {
this.fetchRubyVersionInfo();
await this.setupBundlePath();
this.setupBundlePath();
}
}

Expand Down Expand Up @@ -288,72 +297,63 @@ export class Ruby implements RubyInterface {
}

private async runManagerActivation() {
const manuallySelectRuby = this.manuallySelectRuby.bind(this);
const args = [
this.workspaceFolder,
this.outputChannel,
this.context,
manuallySelectRuby,
this.customBundleGemfile,
] as const;

switch (this.versionManager.identifier) {
case ManagerIdentifier.Asdf:
await this.runActivation(
new Asdf(this.workspaceFolder, this.outputChannel, this.context, this.manuallySelectRuby.bind(this)),
);
await this.runActivation(new Asdf(...args));
break;
case ManagerIdentifier.Chruby:
await this.runActivation(
new Chruby(this.workspaceFolder, this.outputChannel, this.context, this.manuallySelectRuby.bind(this)),
);
await this.runActivation(new Chruby(...args));
break;
case ManagerIdentifier.Rbenv:
await this.runActivation(
new Rbenv(this.workspaceFolder, this.outputChannel, this.context, this.manuallySelectRuby.bind(this)),
);
await this.runActivation(new Rbenv(...args));
break;
case ManagerIdentifier.Rvm:
await this.runActivation(
new Rvm(this.workspaceFolder, this.outputChannel, this.context, this.manuallySelectRuby.bind(this)),
);
await this.runActivation(new Rvm(...args));
break;
case ManagerIdentifier.Mise:
await this.runActivation(
new Mise(this.workspaceFolder, this.outputChannel, this.context, this.manuallySelectRuby.bind(this)),
);
await this.runActivation(new Mise(...args));
break;
case ManagerIdentifier.Rv:
await this.runActivation(
new Rv(this.workspaceFolder, this.outputChannel, this.context, this.manuallySelectRuby.bind(this)),
);
await this.runActivation(new Rv(...args));
break;
case ManagerIdentifier.RubyInstaller:
await this.runActivation(
new RubyInstaller(this.workspaceFolder, this.outputChannel, this.context, this.manuallySelectRuby.bind(this)),
);
await this.runActivation(new RubyInstaller(...args));
break;
case ManagerIdentifier.Custom:
await this.runActivation(
new Custom(this.workspaceFolder, this.outputChannel, this.context, this.manuallySelectRuby.bind(this)),
);
await this.runActivation(new Custom(...args));
break;
case ManagerIdentifier.None:
await this.runActivation(
new None(this.workspaceFolder, this.outputChannel, this.context, this.manuallySelectRuby.bind(this)),
new None(
this.workspaceFolder,
this.outputChannel,
this.context,
manuallySelectRuby,
undefined,
this.customBundleGemfile,
),
);
break;
default:
await this.runActivation(
new Shadowenv(this.workspaceFolder, this.outputChannel, this.context, this.manuallySelectRuby.bind(this)),
);
await this.runActivation(new Shadowenv(...args));
break;
}
}

private async setupBundlePath() {
private setupBundlePath() {
// Some users like to define a completely separate Gemfile for development tools. We allow them to use
// `rubyLsp.bundleGemfile` to configure that and need to inject it into the environment
if (!this.customBundleGemfile) {
return;
}

try {
await vscode.workspace.fs.stat(vscode.Uri.file(this.customBundleGemfile));
if (this.customBundleGemfile) {
this._env.BUNDLE_GEMFILE = this.customBundleGemfile;
} catch (_error: any) {
throw new Error(`The configured bundle gemfile ${this.customBundleGemfile} does not exist`);
}
}

Expand Down
3 changes: 2 additions & 1 deletion vscode/src/ruby/chruby.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ export class Chruby extends VersionManager {
outputChannel: WorkspaceChannel,
context: vscode.ExtensionContext,
manuallySelectRuby: () => Promise<void>,
customBundleGemfile?: string,
) {
super(workspaceFolder, outputChannel, context, manuallySelectRuby);
super(workspaceFolder, outputChannel, context, manuallySelectRuby, customBundleGemfile);

const configuredRubies = vscode.workspace
.getConfiguration("rubyLsp")
Expand Down
3 changes: 2 additions & 1 deletion vscode/src/ruby/none.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ export class None extends VersionManager {
context: vscode.ExtensionContext,
manuallySelectRuby: () => Promise<void>,
rubyPath?: string,
customBundleGemfile?: string,
) {
super(workspaceFolder, outputChannel, context, manuallySelectRuby);
super(workspaceFolder, outputChannel, context, manuallySelectRuby, customBundleGemfile);
this.rubyPath = rubyPath ?? "ruby";
}

Expand Down
9 changes: 2 additions & 7 deletions vscode/src/ruby/versionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,13 @@ export abstract class VersionManager {
outputChannel: WorkspaceChannel,
context: vscode.ExtensionContext,
manuallySelectRuby: () => Promise<void>,
customBundleGemfile?: string,
) {
this.workspaceFolder = workspaceFolder;
this.outputChannel = outputChannel;
this.context = context;
this.manuallySelectRuby = manuallySelectRuby;
const customBundleGemfile: string = vscode.workspace.getConfiguration("rubyLsp").get("bundleGemfile")!;

if (customBundleGemfile.length > 0) {
this.customBundleGemfile = path.isAbsolute(customBundleGemfile)
? customBundleGemfile
: path.resolve(path.join(this.workspaceFolder.uri.fsPath, customBundleGemfile));
}
this.customBundleGemfile = customBundleGemfile;

this.bundleUri = this.customBundleGemfile
? vscode.Uri.file(path.dirname(this.customBundleGemfile))
Expand Down
2 changes: 1 addition & 1 deletion vscode/src/test/suite/common.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import sinon from "sinon";

import { featureEnabled, FEATURE_FLAGS } from "../../common";

suite("Common", () => {
suite("featureEnabled", () => {
let sandbox: sinon.SinonSandbox;

setup(() => {
Expand Down
79 changes: 79 additions & 0 deletions vscode/src/test/suite/ruby.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as assert from "assert";
import * as path from "path";
import * as os from "os";
import * as fs from "fs";

import * as vscode from "vscode";
import sinon from "sinon";
Expand Down Expand Up @@ -169,4 +171,81 @@ suite("Ruby environment activation", () => {

assert.strictEqual(context.workspaceState.get(`rubyLsp.workspaceRubyPath.${workspaceFolder.name}`), undefined);
});

// eslint-disable-next-line no-template-curly-in-string
test("Expands ${workspaceFolder} in bundleGemfile setting", async () => {
const tmpPath = fs.mkdtempSync(path.join(os.tmpdir(), "ruby-lsp-test-"));
// Use the URI's fsPath to normalize the drive letter casing on Windows (e.g. c: -> C:)
const normalizedTmpPath = vscode.Uri.file(tmpPath).fsPath;
const gemfilePath = path.resolve(normalizedTmpPath, "Gemfile");
fs.writeFileSync(gemfilePath, "");

const tmpWorkspaceFolder: vscode.WorkspaceFolder = {
uri: vscode.Uri.file(tmpPath),
name: path.basename(tmpPath),
index: 0,
};

sandbox.stub(vscode.workspace, "getConfiguration").returns({
get: (name: string) => {
if (name === "rubyVersionManager") {
return { identifier: ManagerIdentifier.None };
} else if (name === "bundleGemfile") {
// eslint-disable-next-line no-template-curly-in-string
return "${workspaceFolder}/Gemfile";
}

return undefined;
},
} as unknown as vscode.WorkspaceConfiguration);

const envStub = [
"3.3.5",
"~/.gem/ruby/3.3.5,/opt/rubies/3.3.5/lib/ruby/gems/3.3.0",
"true",
`ANY${VALUE_SEPARATOR}true`,
].join(FIELD_SEPARATOR);

sandbox.stub(common, "asyncExec").resolves({
stdout: "",
stderr: `${ACTIVATION_SEPARATOR}${envStub}${ACTIVATION_SEPARATOR}`,
});

const ruby = new Ruby(context, tmpWorkspaceFolder, outputChannel, FAKE_TELEMETRY);
await ruby.activateRuby();

assert.strictEqual(ruby.env.BUNDLE_GEMFILE, gemfilePath);
fs.rmSync(tmpPath, { recursive: true, force: true });
});

test("Raises an error if the configured bundleGemfile does not exist", async () => {
const tmpPath = fs.mkdtempSync(path.join(os.tmpdir(), "ruby-lsp-test-"));
const tmpWorkspaceFolder: vscode.WorkspaceFolder = {
uri: vscode.Uri.file(tmpPath),
name: path.basename(tmpPath),
index: 0,
};

const nonExistentGemfile = path.join(tmpPath, "nonexistent", "Gemfile");

sandbox.stub(vscode.workspace, "getConfiguration").returns({
get: (name: string) => {
if (name === "bundleGemfile") {
return nonExistentGemfile;
} else if (name === "rubyVersionManager") {
return { identifier: ManagerIdentifier.None };
}

return undefined;
},
} as unknown as vscode.WorkspaceConfiguration);

const ruby = new Ruby(context, tmpWorkspaceFolder, outputChannel, FAKE_TELEMETRY);

await assert.rejects(() => ruby.activateRuby(), {
message: `The configured bundle gemfile ${nonExistentGemfile} does not exist`,
});

fs.rmSync(tmpPath, { recursive: true, force: true });
});
});