Skip to content
Snippets Groups Projects
Unverified Commit 18ea9c91 authored by Sébastien CROCQUESEL's avatar Sébastien CROCQUESEL Committed by GitHub
Browse files

feat(regex): allow to capture groups at each recursion (#12286)

parent e15f3277
Branches
Tags
No related merge requests found
......@@ -2027,6 +2027,8 @@ All matches of the first `matchStrings` pattern are detected, then each of these
If the next `matchStrings` pattern has multiple matches then it will split again.
This process will be followed as long there is a match plus a next `matchingStrings` pattern is available or a dependency is detected.
Matched groups will be available in subsequent matching layers.
This is an example how this can work.
The first regex manager will only upgrade `grafana/loki` as looks for the `backup` key then looks for the `test` key and then uses this result for extraction of necessary attributes.
However, the second regex manager will upgrade both definitions as its first `matchStrings` matches both `test` keys.
......
......@@ -311,6 +311,52 @@ exports[`manager/regex/index extracts with recursive strategy and fail because o
exports[`manager/regex/index extracts with recursive strategy and fail because there is no match 1`] = `null`;
exports[`manager/regex/index extracts with recursive strategy and merged groups 1`] = `
Object {
"depNameTemplate": "{{{ first }}}/{{{ second }}}/{{{ depName }}}",
"deps": Array [
Object {
"currentValue": "v2.19.0",
"datasource": "docker",
"depName": "group1/group1/prom/prometheus",
"replaceString": "\\"name\\": \\"prom/prometheus\\",
\\"type\\": \\"docker\\",
\\"value\\": \\"v2.19.0\\"",
},
Object {
"currentValue": "7.2.2",
"datasource": "docker",
"depName": "group2/group2/grafana/grafana",
"replaceString": "\\"name\\": \\"grafana/grafana\\",
\\"type\\": \\"docker\\",
\\"value\\": \\"7.2.2\\"",
},
Object {
"currentValue": "1.6.1",
"datasource": "docker",
"depName": "backup/backup/grafana/loki",
"replaceString": "\\"name\\": \\"grafana/loki\\",
\\"type\\": \\"docker\\",
\\"value\\": \\"1.6.1\\"",
},
Object {
"currentValue": "3.9.0",
"datasource": "docker",
"depName": "setup/setup/python",
"replaceString": "\\"name\\": \\"python\\",
\\"type\\": \\"docker\\",
\\"value\\": \\"3.9.0\\"",
},
],
"matchStrings": Array [
"\\"(?<first>[^\\"]*)\\":\\\\s*{[^}]*}",
"\\"(?<second>[^\\"]*)\\":\\\\s*\\\\{[^}]*}",
"\\"name\\":\\\\s*\\"(?<depName>.*)\\"[^\\"]*\\"type\\":\\\\s*\\"(?<datasource>.*)\\"[^\\"]*\\"value\\":\\\\s*\\"(?<currentValue>.*)\\"",
],
"matchStringsStrategy": "recursive",
}
`;
exports[`manager/regex/index extracts with recursive strategy and multiple layers 1`] = `
Object {
"deps": Array [
......
......@@ -350,4 +350,22 @@ describe('manager/regex/index', () => {
expect(res).toMatchSnapshot();
expect(res).toBeNull();
});
it('extracts with recursive strategy and merged groups', async () => {
const config: CustomExtractConfig = {
matchStrings: [
'"(?<first>[^"]*)":\\s*{[^}]*}',
'"(?<second>[^"]*)":\\s*\\{[^}]*}',
'"name":\\s*"(?<depName>.*)"[^"]*"type":\\s*"(?<datasource>.*)"[^"]*"value":\\s*"(?<currentValue>.*)"',
],
matchStringsStrategy: 'recursive',
depNameTemplate: '{{{ first }}}/{{{ second }}}/{{{ depName }}}',
};
const res = await extractPackageFile(
exampleJsonContent,
'example.json',
config
);
expect(res).toMatchSnapshot();
expect(res.deps).toHaveLength(4);
});
});
......@@ -121,21 +121,17 @@ function mergeGroups(
mergedGroup: Record<string, string>,
secondGroup: Record<string, string>
): Record<string, string> {
const resultGroup = {};
const resultGroup = Object.create(null); // prevent prototype pollution
Object.keys(mergedGroup)
.filter((key) => validMatchFields.includes(key)) // prevent prototype pollution
.forEach(
// eslint-disable-next-line no-return-assign
(key) => (resultGroup[key] = mergedGroup[key])
);
Object.keys(secondGroup)
.filter((key) => validMatchFields.includes(key)) // prevent prototype pollution
.forEach((key) => {
if (secondGroup[key] && secondGroup[key] !== '') {
resultGroup[key] = secondGroup[key];
}
});
Object.keys(mergedGroup).forEach(
// eslint-disable-next-line no-return-assign
(key) => (resultGroup[key] = mergedGroup[key])
);
Object.keys(secondGroup).forEach((key) => {
if (secondGroup[key] && secondGroup[key] !== '') {
resultGroup[key] = secondGroup[key];
}
});
return resultGroup;
}
......@@ -172,7 +168,8 @@ function handleRecursive(
content: string,
packageFile: string,
config: CustomExtractConfig,
index = 0
index = 0,
combinedGroups: Record<string, string> = {}
): PackageDependency[] {
const regexes = config.matchStrings.map((matchString) =>
regEx(matchString, 'g')
......@@ -184,9 +181,20 @@ function handleRecursive(
return regexMatchAll(regexes[index], content).flatMap((match) => {
// if we have a depName and a currentValue with have the minimal viable definition
if (match?.groups?.depName && match?.groups?.currentValue) {
return createDependency(match, null, config);
return createDependency(
match,
mergeGroups(combinedGroups, match.groups),
config
);
}
return handleRecursive(match[0], packageFile, config, index + 1);
return handleRecursive(
match[0],
packageFile,
config,
index + 1,
mergeGroups(combinedGroups, match.groups || {})
);
});
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment