diff --git a/lib/modules/manager/bundler/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/bundler/__snapshots__/extract.spec.ts.snap index 740bd04292..ebda7e682b 100644 --- a/lib/modules/manager/bundler/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/bundler/__snapshots__/extract.spec.ts.snap @@ -542,12 +542,15 @@ exports[`modules/manager/bundler/extract extractPackageFile() parse mastodon Gem }, }, { + "currentDigest": "54b17ba8c7d8d20a16dfc65d1775241833219cf2", "currentValue": "'~> 0.6'", - "datasource": "rubygems", + "datasource": "git-refs", "depName": "http_parser.rb", "managerData": { "lineNumber": 57, }, + "packageName": "https://github.com/tmm1/http_parser.rb", + "sourceUrl": "https://github.com/tmm1/http_parser.rb", }, { "currentValue": "'~> 1.3'", diff --git a/lib/modules/manager/bundler/extract.spec.ts b/lib/modules/manager/bundler/extract.spec.ts index 78ae44e63f..2fcb2bc85a 100644 --- a/lib/modules/manager/bundler/extract.spec.ts +++ b/lib/modules/manager/bundler/extract.spec.ts @@ -171,14 +171,21 @@ describe('modules/manager/bundler/extract', () => { it('parses inline source in Gemfile', async () => { const sourceInlineGemfile = codeBlock` baz = 'https://gems.baz.com' + gem 'inline_gem' gem "inline_source_gem", source: 'https://gems.foo.com' gem 'inline_source_gem_with_version', "~> 1", source: 'https://gems.bar.com' gem 'inline_source_gem_with_variable_source', source: baz + gem "inline_source_gem_with_require_after", source: 'https://gems.foo.com', require: %w[inline_source_gem] + gem "inline_source_gem_with_require_before", require: %w[inline_source_gem], source: 'https://gems.foo.com' + gem "inline_source_gem_with_group_before", group: :production, source: 'https://gems.foo.com' `; fs.readLocalFile.mockResolvedValueOnce(sourceInlineGemfile); const res = await extractPackageFile(sourceInlineGemfile, 'Gemfile'); expect(res).toMatchObject({ deps: [ + { + depName: 'inline_gem', + }, { depName: 'inline_source_gem', registryUrls: ['https://gems.foo.com'], @@ -192,6 +199,18 @@ describe('modules/manager/bundler/extract', () => { depName: 'inline_source_gem_with_variable_source', registryUrls: ['https://gems.baz.com'], }, + { + depName: 'inline_source_gem_with_require_after', + registryUrls: ['https://gems.foo.com'], + }, + { + depName: 'inline_source_gem_with_require_before', + registryUrls: ['https://gems.foo.com'], + }, + { + depName: 'inline_source_gem_with_group_before', + registryUrls: ['https://gems.foo.com'], + }, ], }); }); @@ -231,4 +250,29 @@ describe('modules/manager/bundler/extract', () => { ], }); }); + + it('parses multiple current values Gemfile', async () => { + const multipleValuesGemfile = codeBlock` + gem 'gem_without_values' + gem 'gem_with_one_value', ">= 3.0.5" + gem 'gem_with_multiple_values', ">= 3.0.5", "< 3.2" + `; + fs.readLocalFile.mockResolvedValueOnce(multipleValuesGemfile); + const res = await extractPackageFile(multipleValuesGemfile, 'Gemfile'); + expect(res).toMatchObject({ + deps: [ + { + depName: 'gem_without_values', + }, + { + depName: 'gem_with_one_value', + currentValue: '">= 3.0.5"', + }, + { + depName: 'gem_with_multiple_values', + currentValue: '">= 3.0.5", "< 3.2"', + }, + ], + }); + }); }); diff --git a/lib/modules/manager/bundler/extract.ts b/lib/modules/manager/bundler/extract.ts index 2ae134a75c..72407254af 100644 --- a/lib/modules/manager/bundler/extract.ts +++ b/lib/modules/manager/bundler/extract.ts @@ -16,11 +16,14 @@ function formatContent(input: string): string { const variableMatchRegex = regEx( `^(?\\w+)\\s*=\\s*['"](?[^'"]+)['"]`, ); -const gemGitRefsMatchRegex = regEx( - `^\\s*gem\\s+(['"])(?[^'"]+)['"]((\\s*,\\s*git:\\s*['"](?[^'"]+)['"])|(\\s*,\\s*github:\\s*['"](?[^'"]+)['"]))(\\s*,\\s*branch:\\s*['"](?[^'"]+)['"])?(\\s*,\\s*ref:\\s*['"](?[^'"]+)['"])?(\\s*,\\s*tag:\\s*['"](?[^'"]+)['"])?`, -); const gemMatchRegex = regEx( - `^\\s*gem\\s+(['"])(?[^'"]+)(['"])(\\s*,\\s*(?(['"])[^'"]+['"](\\s*,\\s*['"][^'"]+['"])?))?(\\s*,\\s*source:\\s*(['"](?[^'"]+)['"]|(?[^'"]+)))?`, + `^\\s*gem\\s+(['"])(?[^'"]+)(['"])(\\s*,\\s*(?(['"])[^'"]+['"](\\s*,\\s*['"][^'"]+['"])?))?`, +); +const sourceMatchRegex = regEx( + `source:\\s*(['"](?[^'"]+)['"]|(?[^'"]+))?`, +); +const gitRefsMatchRegex = regEx( + `((git:\\s*['"](?[^'"]+)['"])|(\\s*,\\s*github:\\s*['"](?[^'"]+)['"]))(\\s*,\\s*branch:\\s*['"](?[^'"]+)['"])?(\\s*,\\s*ref:\\s*['"](?[^'"]+)['"])?(\\s*,\\s*tag:\\s*['"](?[^'"]+)['"])?`, ); export async function extractPackageFile( @@ -132,49 +135,51 @@ export async function extractPackageFile( } } - const gemGitRefsMatch = gemGitRefsMatchRegex.exec(line)?.groups; const gemMatch = gemMatchRegex.exec(line)?.groups; - if (gemGitRefsMatch) { - const dep: PackageDependency = { - depName: gemGitRefsMatch.depName, - managerData: { lineNumber }, - }; - if (gemGitRefsMatch.gitUrl) { - const gitUrl = gemGitRefsMatch.gitUrl; - dep.packageName = gitUrl; - - if (gitUrl.startsWith('https://')) { - dep.sourceUrl = gitUrl.replace(/\.git$/, ''); - } - } else if (gemGitRefsMatch.repoName) { - dep.packageName = `https://github.com/${gemGitRefsMatch.repoName}`; - dep.sourceUrl = dep.packageName; - } - if (gemGitRefsMatch.refName) { - dep.currentDigest = gemGitRefsMatch.refName; - } else if (gemGitRefsMatch.branchName) { - dep.currentValue = gemGitRefsMatch.branchName; - } else if (gemGitRefsMatch.tagName) { - dep.currentValue = gemGitRefsMatch.tagName; - } - dep.datasource = GitRefsDatasource.id; - res.deps.push(dep); - } else if (gemMatch) { + if (gemMatch) { const dep: PackageDependency = { depName: gemMatch.depName, managerData: { lineNumber }, + datasource: RubygemsDatasource.id, }; + if (gemMatch.currentValue) { const currentValue = gemMatch.currentValue; dep.currentValue = currentValue; } - if (gemMatch.registryUrl) { - dep.registryUrls = [gemMatch.registryUrl]; - } else if (gemMatch.sourceName) { - dep.registryUrls = [variables[gemMatch.sourceName]]; + + const sourceMatch = sourceMatchRegex.exec(line)?.groups; + if (sourceMatch) { + if (sourceMatch.registryUrl) { + dep.registryUrls = [sourceMatch.registryUrl]; + } else if (sourceMatch.sourceName) { + dep.registryUrls = [variables[sourceMatch.sourceName]]; + } + } + + const gitRefsMatch = gitRefsMatchRegex.exec(line)?.groups; + if (gitRefsMatch) { + if (gitRefsMatch.gitUrl) { + const gitUrl = gitRefsMatch.gitUrl; + dep.packageName = gitUrl; + + if (gitUrl.startsWith('https://')) { + dep.sourceUrl = gitUrl.replace(/\.git$/, ''); + } + } else if (gitRefsMatch.repoName) { + dep.packageName = `https://github.com/${gitRefsMatch.repoName}`; + dep.sourceUrl = dep.packageName; + } + if (gitRefsMatch.refName) { + dep.currentDigest = gitRefsMatch.refName; + } else if (gitRefsMatch.branchName) { + dep.currentValue = gitRefsMatch.branchName; + } else if (gitRefsMatch.tagName) { + dep.currentValue = gitRefsMatch.tagName; + } + dep.datasource = GitRefsDatasource.id; } - dep.datasource = RubygemsDatasource.id; res.deps.push(dep); }