From 4d23be2cd9758fc4a12423e721ffce6d1c47c122 Mon Sep 17 00:00:00 2001 From: raika-dev Date: Sun, 24 May 2020 11:01:34 -0700 Subject: [PATCH] Handle semantically scoped @ modules when finding dupes. --- src/resolve/duplicateModules.test.ts | 35 ++++++++++++++++++++- src/resolve/duplicateModules.ts | 47 +++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/resolve/duplicateModules.test.ts b/src/resolve/duplicateModules.test.ts index 3e2704f..940bc8c 100644 --- a/src/resolve/duplicateModules.test.ts +++ b/src/resolve/duplicateModules.test.ts @@ -1,4 +1,17 @@ -import { findDuplicateModules } from "./duplicateModules"; +import { + findDuplicateModules, + splitBySemanticModulePath +} from "./duplicateModules"; + +it("handles non-scoped packages", () => { + const ret = splitBySemanticModulePath("foo/node_modules/zap/tap"); + expect(ret).toEqual(["foo", "node_modules", "zap", "tap"]); +}); + +it("handles @scoped packages", () => { + const ret = splitBySemanticModulePath("foo/node_modules/@zap/tap"); + expect(ret).toEqual(["foo", "node_modules", "@zap/tap"]); +}); it("handles one module", () => { const ret = findDuplicateModules(["foo/zap/node_modules/tap"]); @@ -15,6 +28,26 @@ it("handles multiple modules", () => { expect(ret.length).toBe(0); }); +it("correctly splits @ scoped modules", () => { + const ret = findDuplicateModules([ + "foo/zap/node_modules/@tap/wow", + "foo/zap/node_modules/no/node_modules/@tap/zap" + ]); + + expect(ret.length).toBe(0); +}); + +it("correctly find duplicated @ scoped modules", () => { + const ret = findDuplicateModules([ + "foo/zap/node_modules/@tap/wow", + "foo/zap/node_modules/no/node_modules/@tap/wow" + ]); + + expect(ret.length).toBe(1); + expect(ret[0].key).toBe("@tap/wow"); + expect(ret[0].value.sort()).toEqual(["", "no"]); +}); + it("handles duplicate modules", () => { const ret = findDuplicateModules([ "foo/zap/node_modules/tap", diff --git a/src/resolve/duplicateModules.ts b/src/resolve/duplicateModules.ts index 4f2d0b9..f56d0d5 100644 --- a/src/resolve/duplicateModules.ts +++ b/src/resolve/duplicateModules.ts @@ -1,3 +1,48 @@ +/** + * Splits the folder path by semantically scoped package path. + * + * For example + * node_modules/@foo/zap becomes ["node_modules", "@foo/zap"] + * + * vs: + * node_modules/foo/zap becomes ["node_modules", "foo", "zap"] + * + * @param path file path to split + */ +export function splitBySemanticModulePath(path: string): string[] { + let folderSplit = path.split("/"); + + const ret: string[] = []; + let scopedPackage = ""; + let lastNodeModules = false; + + for (const p of folderSplit) { + if (p === "node_modules") { + lastNodeModules = true; + ret.push(p); + continue; + } + + if (lastNodeModules && p[0] === "@") { + scopedPackage = p; + lastNodeModules = false; + continue; + } + + lastNodeModules = false; + + if (scopedPackage.length) { + ret.push(`${scopedPackage}/${p}`); + scopedPackage = ""; + continue; + } + + ret.push(p); + } + + return ret; +} + /** * Given a list of files find duplicate node modules and the dependencies that * brought them into the project. @@ -17,7 +62,7 @@ export function findDuplicateModules( v => v.indexOf("node_modules") > -1 ); const explodedPaths = containsNodeModules - .map(v => v.split("/")) + .map(v => splitBySemanticModulePath(v)) .map(splitPath => { return { nodeModulePreamables: splitPath