diff --git a/lib/modules/manager/bundler/__fixtures__/Gemfile.sourceBlockWithGroups b/lib/modules/manager/bundler/__fixtures__/Gemfile.sourceBlockWithGroups new file mode 100644 index 00000000000000..2b0dc5ec239fd4 --- /dev/null +++ b/lib/modules/manager/bundler/__fixtures__/Gemfile.sourceBlockWithGroups @@ -0,0 +1,12 @@ +source 'https://hub.tech.my.domain.de/artifactory/api/gems/my-gems-prod-local/' do + gem 'sfn_my_dep1', "~> 1" + gem 'sfn_my_dep2', "~> 1" + + group :test, :development do + gem 'internal_test_gem', "~> 1" + end + + group :production do + gem 'internal_production_gem', "~> 1" + end +end diff --git a/lib/modules/manager/bundler/extract.spec.ts b/lib/modules/manager/bundler/extract.spec.ts index fe7916baeebcf1..cbee5d033f7c3e 100644 --- a/lib/modules/manager/bundler/extract.spec.ts +++ b/lib/modules/manager/bundler/extract.spec.ts @@ -26,6 +26,9 @@ const sourceBlockWithNewLinesGemfileLock = Fixtures.get( const sourceBlockWithNewLinesGemfile = Fixtures.get( 'Gemfile.sourceBlockWithNewLines', ); +const sourceBlockWithGroupsGemfile = Fixtures.get( + 'Gemfile.sourceBlockWithGroups', +); describe('modules/manager/bundler/extract', () => { describe('extractPackageFile()', () => { @@ -124,4 +127,18 @@ describe('modules/manager/bundler/extract', () => { expect(res).toMatchSnapshot(); expect(res?.deps).toHaveLength(2); }); + + it('parses source blocks with groups in Gemfile', async () => { + fs.readLocalFile.mockResolvedValueOnce(sourceBlockWithGroupsGemfile); + const res = await extractPackageFile( + sourceBlockWithGroupsGemfile, + 'Gemfile', + ); + expect(res?.deps).toMatchObject([ + { depName: 'internal_test_gem', currentValue: '"~> 1"' }, + { depName: 'internal_production_gem', currentValue: '"~> 1"' }, + { depName: 'sfn_my_dep1', currentValue: '"~> 1"' }, + { depName: 'sfn_my_dep2', currentValue: '"~> 1"' }, + ]); + }); }); diff --git a/lib/modules/manager/bundler/extract.ts b/lib/modules/manager/bundler/extract.ts index 167dcaeccb9600..b204056b5db61f 100644 --- a/lib/modules/manager/bundler/extract.ts +++ b/lib/modules/manager/bundler/extract.ts @@ -16,12 +16,70 @@ export async function extractPackageFile( content: string, packageFile?: string, ): Promise { + let lineNumber: number; + async function processGroupBlock( + line: string, + repositoryUrl?: string, + trimGroupLine: boolean = false, + ): Promise { + const groupMatch = regEx(/^group\s+(.*?)\s+do/).exec(line); + if (groupMatch) { + const depTypes = groupMatch[1] + .split(',') + .map((group) => group.trim()) + .map((group) => group.replace(regEx(/^:/), '')); + + const groupLineNumber = lineNumber; + let groupContent = ''; + let groupLine = ''; + + while ( + lineNumber < lines.length && + (trimGroupLine ? groupLine.trim() !== 'end' : groupLine !== 'end') + ) { + lineNumber += 1; + groupLine = lines[lineNumber]; + + // istanbul ignore if + if (!is.string(groupLine)) { + logger.debug( + { content, packageFile, type: 'groupLine' }, + 'Bundler parsing error', + ); + groupLine = 'end'; + } + if (trimGroupLine ? groupLine.trim() !== 'end' : groupLine !== 'end') { + groupContent += formatContent(groupLine); + } + } + + const groupRes = await extractPackageFile(groupContent); + if (groupRes) { + res.deps = res.deps.concat( + groupRes.deps.map((dep) => { + const depObject = { + ...dep, + depTypes, + managerData: { + lineNumber: + Number(dep.managerData?.lineNumber) + groupLineNumber + 1, + }, + }; + if (repositoryUrl) { + depObject.registryUrls = [repositoryUrl]; + } + return depObject; + }), + ); + } + } + } const res: PackageFileContent = { registryUrls: [], deps: [], }; const lines = content.split(newlineRegex); - for (let lineNumber = 0; lineNumber < lines.length; lineNumber += 1) { + for (lineNumber = 0; lineNumber < lines.length; lineNumber += 1) { const line = lines[lineNumber]; let sourceMatch: RegExpMatchArray | null = null; for (const delimiter of delimiters) { @@ -61,44 +119,9 @@ export async function extractPackageFile( dep.datasource = RubyGemsDatasource.id; res.deps.push(dep); } - const groupMatch = regEx(/^group\s+(.*?)\s+do/).exec(line); - if (groupMatch) { - const depTypes = groupMatch[1] - .split(',') - .map((group) => group.trim()) - .map((group) => group.replace(regEx(/^:/), '')); - const groupLineNumber = lineNumber; - let groupContent = ''; - let groupLine = ''; - while (lineNumber < lines.length && groupLine !== 'end') { - lineNumber += 1; - groupLine = lines[lineNumber]; - // istanbul ignore if - if (!is.string(groupLine)) { - logger.debug( - { content, packageFile, type: 'groupLine' }, - 'Bundler parsing error', - ); - groupLine = 'end'; - } - if (groupLine !== 'end') { - groupContent += formatContent(groupLine); - } - } - const groupRes = await extractPackageFile(groupContent); - if (groupRes) { - res.deps = res.deps.concat( - groupRes.deps.map((dep) => ({ - ...dep, - depTypes, - managerData: { - lineNumber: - Number(dep.managerData?.lineNumber) + groupLineNumber + 1, - }, - })), - ); - } - } + + await processGroupBlock(line); + for (const delimiter of delimiters) { const sourceBlockMatch = regEx( `^source\\s+${delimiter}(.*?)${delimiter}\\s+do`, @@ -108,6 +131,7 @@ export async function extractPackageFile( const sourceLineNumber = lineNumber; let sourceContent = ''; let sourceLine = ''; + while (lineNumber < lines.length && sourceLine.trim() !== 'end') { lineNumber += 1; sourceLine = lines[lineNumber]; @@ -119,11 +143,16 @@ export async function extractPackageFile( ); sourceLine = 'end'; } - if (sourceLine !== 'end') { + + await processGroupBlock(sourceLine.trim(), repositoryUrl, true); + + if (sourceLine.trim() !== 'end') { sourceContent += formatContent(sourceLine); } } + const sourceRes = await extractPackageFile(sourceContent); + if (sourceRes) { res.deps = res.deps.concat( sourceRes.deps.map((dep) => ({