From 3daeb8baa431d70b4eebce49ba792aa0eb8cefde Mon Sep 17 00:00:00 2001 From: Ufuk Kayserilioglu Date: Thu, 25 Jan 2024 02:46:45 +0200 Subject: [PATCH] Make Ruby version detection fall through each version source Currently Ruby version detection fails early when it is able to find a particular kind of Ruby version file, but is unable to extract a valid Ruby version from the file. This commit makes it so that Ruby version detection falls through each version source until it finds a valid Ruby version, or fails with a message that no valid Ruby version could be found. --- package.json | 2 + src/ruby.ts | 60 ++++++++--------------- src/test/suite/ruby.test.ts | 49 +++++++++++++++++++ yarn.lock | 97 ++++++++++++++++++++++++++++++++++++- 4 files changed, 168 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index 1dc219fb..7d4a27cb 100644 --- a/package.json +++ b/package.json @@ -463,6 +463,7 @@ "@types/glob": "^8.1.0", "@types/mocha": "^10.0.6", "@types/node": "20.x", + "@types/sinon": "^17.0.3", "@types/vscode": "^1.68.0", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", @@ -476,6 +477,7 @@ "mocha": "^10.2.0", "ovsx": "^0.8.3", "prettier": "^3.2.2", + "sinon": "^17.0.1", "typescript": "^5.3.3", "vscode-oniguruma": "^2.0.1", "vscode-textmate": "^9.0.0" diff --git a/src/ruby.ts b/src/ruby.ts index afb9920d..95725347 100644 --- a/src/ruby.ts +++ b/src/ruby.ts @@ -209,22 +209,8 @@ export class Ruby implements RubyInterface { engine?: string; version: string; }> { - let contents = await this.searchAndReadFile("dev.yml", false); - if (contents) { - const match = /- ruby: ('|")?(\d\.\d\.\d)/.exec(contents); - const version = match && match[2]; - - if (!version) { - throw new Error( - "Found dev.yml file, but it did not define a Ruby version", - ); - } - - return { version }; - } - // Try to find a Ruby version in `.ruby-version`. We search parent directories until we find it or hit the root - contents = await this.searchAndReadFile(".ruby-version", true); + let contents = await this.searchAndReadFile(".ruby-version", true); // rbenv allows setting a global Ruby version in `~/.rbenv/version`. If we couldn't find a project specific // `.ruby-version` file, then we need to check for the global one @@ -239,13 +225,20 @@ export class Ruby implements RubyInterface { contents, ); - if (!match || !match.groups) { - throw new Error( - "Found .ruby-version file, but it did not define a valid Ruby version", - ); + if (match && match.groups) { + return { engine: match.groups.engine, version: match.groups.version }; } + } - return { engine: match.groups.engine, version: match.groups.version }; + // Try to find a Ruby version in `dev.yml`. We search parent directories until we find it or hit the root + contents = await this.searchAndReadFile("dev.yml", false); + if (contents) { + const match = /- ruby: ('|")?(\d\.\d\.\d)/.exec(contents); + const version = match && match[2]; + + if (version) { + return { version }; + } } // Try to find a Ruby version in `.tool-versions`. We search parent directories until we find it or hit the root @@ -254,13 +247,9 @@ export class Ruby implements RubyInterface { const match = /ruby (\d\.\d\.\d(-[A-Za-z0-9]+)?)/.exec(contents); const version = match && match[1]; - if (!version) { - throw new Error( - "Found .tool-versions file, but it did not define a Ruby version", - ); + if (version) { + return { version }; } - - return { version }; } // Try to find a Ruby version in `.rtx.toml`. Note: rtx has been renamed to mise, which is handled below. We will @@ -270,13 +259,9 @@ export class Ruby implements RubyInterface { const match = /ruby\s+=\s+("|')(.*)("|')/.exec(contents); const version = match && match[2]; - if (!version) { - throw new Error( - "Found .rtx.toml file, but it did not define a Ruby version", - ); + if (version) { + return { version }; } - - return { version }; } // Try to find a Ruby version in `.mise.toml` @@ -285,17 +270,14 @@ export class Ruby implements RubyInterface { const match = /ruby\s+=\s+("|')(.*)("|')/.exec(contents); const version = match && match[2]; - if (!version) { - throw new Error( - "Found .mise.toml file, but it did not define a Ruby version", - ); + if (version) { + return { version }; } - - return { version }; } throw new Error( - "Could not find a configured Ruby version. Searched for .ruby-version and .tools-versions", + "Could not find a valid Ruby version in any of `.ruby-version`, `.tool-versions`, `.rtx.toml` " + + "or `.mise.toml` files", ); } diff --git a/src/test/suite/ruby.test.ts b/src/test/suite/ruby.test.ts index 6fedb7c4..c3cba386 100644 --- a/src/test/suite/ruby.test.ts +++ b/src/test/suite/ruby.test.ts @@ -3,6 +3,7 @@ import * as fs from "fs"; import * as path from "path"; import * as os from "os"; +import * as sinon from "sinon"; import * as vscode from "vscode"; import { Ruby } from "../../ruby"; @@ -33,6 +34,37 @@ suite("Ruby environment activation", () => { } as unknown as vscode.ExtensionContext; const outputChannel = new WorkspaceChannel("fake", LOG_CHANNEL); + test("falls through all Ruby environment detection methods", async () => { + const tmpPath = fs.mkdtempSync(path.join(os.tmpdir(), "ruby-lsp-test-")); + const ruby = new Ruby( + { + uri: { fsPath: tmpPath }, + } as vscode.WorkspaceFolder, + context, + outputChannel, + ); + fs.writeFileSync(path.join(tmpPath, "dev.yml"), "- ruby"); + fs.writeFileSync(path.join(tmpPath, ".ruby-version"), ""); + fs.writeFileSync(path.join(tmpPath, ".tool-versions"), ""); + fs.writeFileSync(path.join(tmpPath, ".rtx.toml"), ""); + fs.writeFileSync(path.join(tmpPath, ".mise.toml"), ""); + + const errorMessage = + "Could not find a valid Ruby version in any of " + + "`.ruby-version`, `.tool-versions`, `.rtx.toml` or `.mise.toml` files"; + const mock = sinon.mock(ruby); + mock + .expects("showRubyFallbackDialog") + .withArgs(errorMessage) + .once() + .returns("3.2.2"); + + await ruby.activate(); + + mock.verify(); + fs.rmSync(tmpPath, { recursive: true, force: true }); + }); + test("fetches Ruby environment for .ruby-version", async () => { const tmpPath = fs.mkdtempSync(path.join(os.tmpdir(), "ruby-lsp-test-")); const ruby = new Ruby( @@ -135,6 +167,23 @@ suite("Ruby environment activation", () => { fs.rmSync(tmpPath, { recursive: true, force: true }); }); + test("falls back to parsing Ruby environment from .ruby-version if dev.yml doesn't contain version", async () => { + const tmpPath = fs.mkdtempSync(path.join(os.tmpdir(), "ruby-lsp-test-")); + const ruby = new Ruby( + { + uri: { fsPath: tmpPath }, + } as vscode.WorkspaceFolder, + context, + outputChannel, + ); + fs.writeFileSync(path.join(tmpPath, "dev.yml"), "- ruby"); + fs.writeFileSync(path.join(tmpPath, ".ruby-version"), "3.2.2"); + const rubyEnv = await ruby.activate(); + + assertRubyEnv(rubyEnv); + fs.rmSync(tmpPath, { recursive: true, force: true }); + }); + test("fetches Ruby environment for .tool-versions", async () => { const tmpPath = fs.mkdtempSync(path.join(os.tmpdir(), "ruby-lsp-test-")); const ruby = new Ruby( diff --git a/yarn.lock b/yarn.lock index 0e89fcb9..c14d6053 100644 --- a/yarn.lock +++ b/yarn.lock @@ -506,6 +506,41 @@ resolved "https://registry.yarnpkg.com/@shopify/prettier-config/-/prettier-config-1.1.2.tgz#612f87c0cd1196e8b43c85425e587d0fa7f1172d" integrity sha512-5ugCL9sPGzmOaZjeRGaWUWhHgAbemrS6z+R7v6gwiD+BiqSeoFhIY+imLpfdFCVpuOGalpHeCv6o3gv++EHs0A== +"@sinonjs/commons@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" + integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== + dependencies: + type-detect "4.0.8" + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^11.2.2": + version "11.2.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz#50063cc3574f4a27bd8453180a04171c85cc9699" + integrity sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@sinonjs/samsam@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.0.tgz#0d488c91efb3fa1442e26abea81759dfc8b5ac60" + integrity sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew== + dependencies: + "@sinonjs/commons" "^2.0.0" + lodash.get "^4.4.2" + type-detect "^4.0.8" + +"@sinonjs/text-encoding@^0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz#5981a8db18b56ba38ef0efb7d995b12aa7b51918" + integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ== + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -551,6 +586,18 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339" integrity sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A== +"@types/sinon@^17.0.3": + version "17.0.3" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-17.0.3.tgz#9aa7e62f0a323b9ead177ed23a36ea757141a5fa" + integrity sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw== + dependencies: + "@types/sinonjs__fake-timers" "*" + +"@types/sinonjs__fake-timers@*": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz#5fd3592ff10c1e9695d377020c033116cc2889f2" + integrity sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ== + "@types/vscode@^1.68.0": version "1.85.0" resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.85.0.tgz#46beb07f0f626665b52d1e2294382b2bc63b602e" @@ -1390,6 +1437,11 @@ diff@5.0.0: resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== +diff@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" + integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -2739,6 +2791,11 @@ jszip@^3.10.1: readable-stream "~2.3.6" setimmediate "^1.0.5" +just-extend@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-6.2.0.tgz#b816abfb3d67ee860482e7401564672558163947" + integrity sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw== + keytar@^7.7.0: version "7.9.0" resolved "https://registry.yarnpkg.com/keytar/-/keytar-7.9.0.tgz#4c6225708f51b50cbf77c5aae81721964c2918cb" @@ -2800,6 +2857,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -3005,6 +3067,17 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +nise@^5.1.5: + version "5.1.7" + resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.7.tgz#03ca96539efb306612eb60a8c5d6beeb208e27e5" + integrity sha512-wWtNUhkT7k58uvWTB/Gy26eA/EJKtPZFVAhEilN5UYVmmGRYOURbejRUyKm0Uu9XVEW7K5nBOZfR8VMB4QR2RQ== + dependencies: + "@sinonjs/commons" "^3.0.0" + "@sinonjs/fake-timers" "^11.2.2" + "@sinonjs/text-encoding" "^0.7.2" + just-extend "^6.2.0" + path-to-regexp "^6.2.1" + no-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" @@ -3301,6 +3374,11 @@ path-scurry@^1.10.1: lru-cache "^9.1.1 || ^10.0.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +path-to-regexp@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5" + integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw== + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -3691,6 +3769,18 @@ simple-get@^4.0.0: once "^1.3.1" simple-concat "^1.0.0" +sinon@^17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-17.0.1.tgz#26b8ef719261bf8df43f925924cccc96748e407a" + integrity sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g== + dependencies: + "@sinonjs/commons" "^3.0.0" + "@sinonjs/fake-timers" "^11.2.2" + "@sinonjs/samsam" "^8.0.0" + diff "^5.1.0" + nise "^5.1.5" + supports-color "^7.2.0" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -3832,7 +3922,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0: +supports-color@^7.1.0, supports-color@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -3958,6 +4048,11 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-detect@4.0.8, type-detect@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"