Repository: microsoft/vscode-edge-devtools
Pull request #221 (2020-10-16 03:16:37)

Fixing ${workspaceFolder} when overriding a map.
**Summary**
Absolute path mapping was working but using `${workspaceFolder}` placeholder was not.

**Root:**
There were different issues causing the same problem:
- The API we were using to cast {workspace} stopped working.
- Test should have caught this, but the tests were mocking this API.
- There was a missing mock needed for testing the actual execution path( fse.pathExists.)

**Fix:**
- Using `${workspaceFolder}` when overriding a path works correctly now.
- Changed the tests to validate the following 3 scenarios:
  - Get the default path mapping
  - Override path mapping with ${workspaceFolder} placeholder.
  - Override  mapping with an absolute path.



Patch:
diff --git a/src/utils.ts b/src/utils.ts
index 13efa70..842d3db 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -356,15 +356,25 @@ export function getRuntimeConfig(config: Partial<IUserConfig> = {}): IRuntimeCon
             const replacePatternValue = replaceWebRootInSourceMapPathOverridesEntry(
                 webRoot, sourceMapPathOverrides[pattern]);
 
-            resolvedOverrides[replacePattern] = replacePatternValue;
+            resolvedOverrides[replacePattern] = replaceWorkSpaceFolderPlaceholder(replacePatternValue);
         }
     }
 
+    // replace workspaceFolder with local paths
+    const resolvedMappingOverrides: IStringDictionary<string> = {};
+    for (const customPathMapped in pathMapping) {
+        if (pathMapping.hasOwnProperty(customPathMapped)) {
+            resolvedMappingOverrides[customPathMapped] =
+                replaceWorkSpaceFolderPlaceholder(pathMapping[customPathMapped])
+        }
+    }
+
+    const resolvedWebRoot = replaceWorkSpaceFolderPlaceholder(webRoot);
     return {
-        pathMapping,
+        pathMapping: resolvedMappingOverrides,
         sourceMapPathOverrides: resolvedOverrides,
         sourceMaps,
-        webRoot,
+        webRoot: resolvedWebRoot,
     };
 }
 
@@ -429,10 +439,7 @@ export function applyPathMapping(
         const wildcardValue = overridePatternMatches[1];
         let mappedPath = rightPattern.replace(/\*/g, wildcardValue);
         mappedPath = debugCore.utils.properJoin(mappedPath); // Fix any ..'s
-        mappedPath = mappedPath.replace(
-            "${workspaceFolder}",
-            vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.toString() : "" || "");
-
+        mappedPath = replaceWorkSpaceFolderPlaceholder(mappedPath);
         return mappedPath;
     }
 
@@ -448,6 +455,27 @@ function isHeadlessEnabled() {
     return headless;
 }
 
+/**
+ * Replaces the workspaceFolder placeholder in a specified path, returns the
+ * given path with file disk path.
+ * @param customPath The path that will be replaced.
+ */
+function replaceWorkSpaceFolderPlaceholder(customPath: string) {
+    let parsedPath = customPath;
+    if (vscode.workspace.workspaceFolders &&
+        vscode.workspace.workspaceFolders[0].uri.toString()) {
+        /**
+         * vscode can have several workspaceFolders, the first one is the
+         * one currently open by the user.
+         */
+        parsedPath = vscode.workspace.workspaceFolders[0].uri.toString();
+        const replacedPath = customPath.replace("${workspaceFolder}", parsedPath);
+        return debugCore.utils.canonicalizeUrl(replacedPath);
+    } else{
+        return parsedPath;
+    }
+}
+
 /**
  * Verifies and returns if the browser for the current session exists in the
  * desired flavor and platform. Providing a "default" flavor will scan for the


Test patch:
diff --git a/src/test/helpers.ts b/src/test/helpers.ts
index 890e90f..9afa89b 100644
--- a/src/test/helpers.ts
+++ b/src/test/helpers.ts
@@ -59,15 +59,21 @@ export function createFakeVSCode() {
         workspace: {
             getConfiguration: jest.fn(() => {
                 return { get: (name: string) => {
-                    if ("enableNetwork" === name) {
-                        return true;
-                    } else {
-                        return "";
+                    switch(name) {
+                        case "enableNetwork":
+                            return true;
+                        default:
+                            return undefined;
                     }
                 } };
             }),
             onDidChangeConfiguration: jest.fn(),
             openTextDocument: jest.fn().mockResolvedValue(null),
+            workspaceFolders: [
+                {
+                    uri:  'file:///g%3A/GIT/testPage'
+                }
+            ]
         },
     };
 }
diff --git a/src/utils.test.ts b/src/utils.test.ts
index e7aa08b..0da7358 100644
--- a/src/utils.test.ts
+++ b/src/utils.test.ts
@@ -680,41 +680,46 @@ describe("utils", () => {
     });
 
     describe("getRuntimeConfig", () => {
-        it("returns the stored settings", async () => {
+        let fse: Mocked<typeof import("fs-extra")>;
+        beforeEach(() => {
+            jest.resetModules();
+            fse = jest.requireMock("fs-extra");
+            fse.pathExists.mockImplementation(() => Promise.resolve(true));
+        });
+
+        it("returns the default settings", async () => {
+            const testPath = "g:\\GIT\\testPage";
             const expected = {
                 pathMapping: {
-                    "/app": "${workspaceFolder}/dist",
+                    "/": testPath,
                 },
                 sourceMapPathOverrides: {
                     "webpack:///./*": "${webRoot}/*",
                 },
-                sourceMaps: false,
-                webRoot: "/out",
+                sourceMaps: true,
+                webRoot: testPath,
             };
 
             const expectedResolvedOverride = {
-                "webpack:///./*": "/out/*",
-            };
-
-            // Override the configuration mock to return our custom test values
-            const configMock = {
-                get: (name: string) => (expected as any)[name],
+                "meteor://💻app/*": `${testPath}\\*`,
+                "webpack:///./*": `${testPath}\\*`,
+                "webpack:///./~/*": `${testPath}\\node_modules\\*`,
+                "webpack:///*": "*",
+                "webpack:///src/*": `${testPath}\\*`
             };
-            const vscodeMock = await jest.requireMock("vscode");
-            vscodeMock.workspace.getConfiguration.mockImplementationOnce(() => configMock);
 
             // Ensure the new values are returned
             const { pathMapping, sourceMapPathOverrides, sourceMaps, webRoot } = utils.getRuntimeConfig();
-            expect(pathMapping).toBe(expected.pathMapping);
+            expect(pathMapping).toEqual(expected.pathMapping);
             expect(sourceMapPathOverrides).toEqual(expectedResolvedOverride);
             expect(sourceMaps).toBe(expected.sourceMaps);
             expect(webRoot).toBe(expected.webRoot);
         });
 
-        it("uses user config", async () => {
+        it("uses user config with workspace", async () => {
             const config = {
                 pathMapping: {
-                    "/app": "${workspaceFolder}/dist",
+                    "/app": "${workspaceFolder}/out",
                 },
                 sourceMapPathOverrides: {
                     "webpack:///./*": "${webRoot}/*",
@@ -723,38 +728,52 @@ describe("utils", () => {
                 webRoot: "/out",
             };
 
-            const expectedResolvedOverride = {
-                "webpack:///./*": "/out/*",
+            const expectedConfig = {
+                pathMapping: {
+                    "/app": `g:\\GIT\\testPage\\out`,
+                },
+                sourceMapPathOverrides: {
+                    "webpack:///./*": "/out/*",
+                },
+                sourceMaps: false,
+                webRoot: "/out",
             };
 
             const { pathMapping, sourceMapPathOverrides, sourceMaps, webRoot } = utils.getRuntimeConfig(config);
-            expect(pathMapping).toBe(config.pathMapping);
-            expect(sourceMapPathOverrides).toEqual(expectedResolvedOverride);
-            expect(sourceMaps).toBe(config.sourceMaps);
-            expect(webRoot).toBe(config.webRoot);
+            expect(pathMapping).toEqual(expectedConfig.pathMapping);
+            expect(sourceMapPathOverrides).toEqual(expectedConfig.sourceMapPathOverrides);
+            expect(sourceMaps).toEqual(expectedConfig.sourceMaps);
+            expect(webRoot).toEqual(expectedConfig.webRoot);
         });
 
-        it("uses correct fallbacks on failure", async () => {
-            // Override the configuration mock to return our custom test values
-            const configMock = {
-                get: (name: string) => undefined,
+        it("uses user config with an absolute path", async () => {
+            const config = {
+                pathMapping: {
+                    "/app": "c:/user/test/out",
+                },
+                sourceMapPathOverrides: {
+                    "webpack:///./*": "${webRoot}/*",
+                },
+                sourceMaps: false,
+                webRoot: "/out",
             };
-            const vscodeMock = await jest.requireMock("vscode");
-            vscodeMock.workspace.getConfiguration.mockImplementationOnce(() => configMock);
 
-            const expectedResolvedOverrides = utils.SETTINGS_DEFAULT_PATH_OVERRIDES;
-            for (const i in expectedResolvedOverrides) {
-                if (expectedResolvedOverrides.hasOwnProperty(i)) {
-                    expectedResolvedOverrides[i] =
-                        expectedResolvedOverrides[i].replace("${webRoot}", utils.SETTINGS_DEFAULT_WEB_ROOT);
-                }
-            }
+            const expectedConfig = {
+                pathMapping: {
+                    "/app": `c:\\user\\test\\out`,
+                },
+                sourceMapPathOverrides: {
+                    "webpack:///./*": "/out/*",
+                },
+                sourceMaps: false,
+                webRoot: "/out",
+            };
 
-            const { pathMapping, sourceMapPathOverrides, sourceMaps, webRoot } = utils.getRuntimeConfig();
-            expect(pathMapping).toBe(utils.SETTINGS_DEFAULT_PATH_MAPPING);
-            expect(sourceMapPathOverrides).toEqual(expectedResolvedOverrides);
-            expect(sourceMaps).toBe(utils.SETTINGS_DEFAULT_SOURCE_MAPS);
-            expect(webRoot).toBe(utils.SETTINGS_DEFAULT_WEB_ROOT);
+            const { pathMapping, sourceMapPathOverrides, sourceMaps, webRoot } = utils.getRuntimeConfig(config);
+            expect(pathMapping).toEqual(expectedConfig.pathMapping);
+            expect(sourceMapPathOverrides).toEqual(expectedConfig.sourceMapPathOverrides);
+            expect(sourceMaps).toEqual(expectedConfig.sourceMaps);
+            expect(webRoot).toEqual(expectedConfig.webRoot);
         });
     });