diff --git a/extensions/ql-vscode/src/common/config-template.ts b/extensions/ql-vscode/src/common/config-template.ts new file mode 100644 index 000000000..dfc948d9b --- /dev/null +++ b/extensions/ql-vscode/src/common/config-template.ts @@ -0,0 +1,57 @@ +// Based on https://github.com/microsoft/vscode/blob/edfd5b8ba54d50f3f5c2ebee877af088803def88/src/vs/base/common/labels.ts#L316C1-L400 + +/** + * Helper to insert values for specific template variables into the string. E.g. "this ${is} a ${template}" can be + * passed to this function together with an object that maps "is" and "template" to strings to have them replaced. + * + * @param template string to which template is applied + * @param values the values of the templates to use + */ +export function substituteConfigVariables( + template: string, + values: { + [key: string]: string | undefined | null; + }, +): string { + const segments: string[] = []; + + let inVariable = false; + let currentValue = ""; + for (const char of template) { + // Beginning of variable + if (char === "$" || (inVariable && char === "{")) { + if (currentValue) { + segments.push(currentValue); + } + + currentValue = ""; + inVariable = true; + } + + // End of variable + else if (char === "}" && inVariable) { + const resolved = values[currentValue]; + + // Variable + if (resolved && resolved.length > 0) { + segments.push(resolved); + } + // If the variable, doesn't exist, we discard it (i.e. replace it by the empty string) + + currentValue = ""; + inVariable = false; + } + + // Text or Variable Name + else { + currentValue += char; + } + } + + // Tail + if (currentValue && !inVariable) { + segments.push(currentValue); + } + + return segments.join(""); +} diff --git a/extensions/ql-vscode/test/unit-tests/common/config-template.test.ts b/extensions/ql-vscode/test/unit-tests/common/config-template.test.ts new file mode 100644 index 000000000..f829c27d2 --- /dev/null +++ b/extensions/ql-vscode/test/unit-tests/common/config-template.test.ts @@ -0,0 +1,79 @@ +import { substituteConfigVariables } from "../../../src/common/config-template"; + +describe("substituteConfigVariables", () => { + const values = { + userHome: "/home/your-username", + workspaceFolder: "/home/your-username/your-project", + workspaceFolderBasename: "your-project", + pathSeparator: "/", + owner: "github", + name: "vscode-codeql", + language: "java", + }; + + const testCases = [ + { + template: ".github/codeql/extensions/${name}-${language}", + expected: ".github/codeql/extensions/vscode-codeql-java", + }, + { + template: "${owner}/${name}-${language}", + expected: "github/vscode-codeql-java", + }, + { + template: "models/${group}.model.yml", + expected: "models/.model.yml", + }, + { + template: + "${workspaceFolder}${pathSeparator}.github/workflows/codeql-analysis.yml", + expected: + "/home/your-username/your-project/.github/workflows/codeql-analysis.yml", + }, + { + template: + "${workspaceFolder/.github/codeql/extensions/${name}-${language}", + expected: "workspaceFolder/.github/codeql/extensions/vscode-codeql-java", + }, + { + template: "}${workspaceFolder}/.github/workflows/codeql-analysis.yml", + expected: + "}/home/your-username/your-project/.github/workflows/codeql-analysis.yml", + }, + { + template: "Foo Bar", + expected: "Foo Bar", + }, + { + template: "Foo${}Bar", + expected: "FooBar", + }, + { + template: "$FooBar", + expected: "", + }, + { + template: "}FooBar", + expected: "}FooBar", + }, + { + template: "Foo ${name} Bar", + expected: "Foo vscode-codeql Bar", + }, + { + template: "Foo ${name} Bar ${owner}", + expected: "Foo vscode-codeql Bar github", + }, + { + template: "Foo ${nmae} Bar ${owner}", + expected: "Foo Bar github", + }, + ]; + + test.each(testCases)( + "result of $template is $expected", + ({ template, expected }) => { + expect(substituteConfigVariables(template, values)).toEqual(expected); + }, + ); +});