Skip to content

Commit 7e55ae3

Browse files
committed
[FIX] npm translator: Deduplicate subtrees of pending dependencies
1 parent 345d8f5 commit 7e55ae3

File tree

9 files changed

+176
-16
lines changed

9 files changed

+176
-16
lines changed

lib/translators/npm.js

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -222,16 +222,13 @@ class NpmTranslator {
222222
// Ignore this dependency
223223
return null;
224224
} else {
225-
// Add module marked as deduped
225+
// Create deduped project
226226
const pkg = await pPkg;
227-
228-
return {
227+
return this.createDedupedProject({
229228
id: moduleName,
230229
version: pkg.version,
231-
path: modulePath,
232-
dependencies: [],
233-
deduped: true
234-
};
230+
path: modulePath
231+
});
235232
}
236233
}
237234

@@ -338,6 +335,11 @@ class NpmTranslator {
338335
}
339336

340337
processPendingDeps(tree) {
338+
if (Object.keys(this.pendingDeps).length === 0) {
339+
// No pending deps => nothing to do
340+
log.verbose("No pending (optional) dependencies to process");
341+
return tree;
342+
}
341343
const queue = [tree];
342344
const visited = new Set();
343345

@@ -360,21 +362,28 @@ class NpmTranslator {
360362
// This is a loop
361363
log.verbose(`Deduping pending dependency ${project.id} with parent path ${parent.path}`);
362364
if (this.includeDeduped) {
363-
// Add module marked as deduped
364-
const dedupedProject = {
365+
// Create project marked as deduped
366+
const dedupedProject = this.createDedupedProject({
365367
id: project.id,
366368
version: project.version,
367-
path: project.path,
368-
dependencies: [],
369-
deduped: true
370-
};
369+
path: project.path
370+
});
371371
parent.project.dependencies.push(dedupedProject);
372372
} // else: do nothing
373373
} else {
374-
parent.project.dependencies.push(project);
374+
if (log.isLevelEnabled("silly")) {
375+
log.silly(`Adding optional dependency ${project.id} to project ${parent.project.id} ` +
376+
`(parent path: ${parent.path})...`);
377+
}
378+
const dedupedProject = this.dedupeTree(project, parent.path);
379+
parent.project.dependencies.push(dedupedProject);
375380
}
376381
}
377382
this.pendingDeps[project.id] = null;
383+
384+
if (log.isLevelEnabled("silly")) {
385+
log.silly(`${Object.keys(this.pendingDeps).length} pending dependencies left`);
386+
}
378387
}
379388

380389
if (project.dependencies) {
@@ -438,6 +447,45 @@ class NpmTranslator {
438447
pkg.collection = JSON.parse(JSON.stringify(pkgCollectionShims[moduleName]));
439448
}*/
440449
}
450+
451+
dedupeTree(tree, parentPath) {
452+
const projectsToDedupe = new Set(parentPath.slice(1, -1).split(":"));
453+
const clonedTree = JSON.parse(JSON.stringify(tree));
454+
const queue = [{project: clonedTree}];
455+
// BFS
456+
while (queue.length) {
457+
const {project, parent} = queue.shift(); // Get and remove first entry from queue
458+
459+
if (parent && projectsToDedupe.has(project.id)) {
460+
log.silly(`In tree "${tree.id}" (parent path "${parentPath}"): Deduplicating project ${project.id} `+
461+
`(child of ${parent.id})`);
462+
463+
const idx = parent.dependencies.indexOf(project);
464+
const dedupedProject = this.createDedupedProject(project);
465+
parent.dependencies.splice(idx, 1, dedupedProject);
466+
}
467+
468+
if (project.dependencies) {
469+
queue.push(...project.dependencies.map((dependency) => {
470+
return {
471+
project: dependency,
472+
parent: project
473+
};
474+
}));
475+
}
476+
}
477+
return clonedTree;
478+
}
479+
480+
createDedupedProject({id, version, path}) {
481+
return {
482+
id,
483+
version,
484+
path,
485+
dependencies: [],
486+
deduped: true
487+
};
488+
}
441489
}
442490

443491
/**

test/fixtures/cyclic-deps/node_modules/application.cycle.d/webapp/index.html

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/cyclic-deps/node_modules/application.cycle.e/package.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/cyclic-deps/node_modules/application.cycle.e/ui5.yaml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/cyclic-deps/node_modules/application.cycle.e/webapp/index.html

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/cyclic-deps/node_modules/application.cycle.e/webapp/test.js

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/cyclic-deps/node_modules/module.l/package.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/cyclic-deps/node_modules/module.m/package.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/lib/translators/npm.js

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ test("AppCycleD: cyclic npm deps - Cycles everywhere", (t) => {
100100
});
101101
});
102102

103+
test("AppCycleE: cyclic npm deps - Cycle via devDependency", (t) => {
104+
const applicationCycleEPath = path.join(cycleDepsBasePath, "application.cycle.e");
105+
return npmTranslator.generateDependencyTree(applicationCycleEPath, {includeDeduped: true}).then((parsedTree) => {
106+
t.deepEqual(parsedTree, applicationCycleETree, "Parsed correctly");
107+
});
108+
});
109+
103110
test("Error: missing package.json", async (t) => {
104111
const dir = path.parse(__dirname).root;
105112
const error = await t.throws(npmTranslator.generateDependencyTree(dir));
@@ -401,7 +408,8 @@ const applicationCycleBTree = {
401408
"id": "module.e",
402409
"version": "1.0.0",
403410
"path": path.join(cycleDepsBasePath, "module.e"),
404-
"dependencies": []
411+
"dependencies": [],
412+
"deduped": true
405413
}
406414
]
407415
}
@@ -850,3 +858,53 @@ const applicationCycleDTree = {
850858
}
851859
]
852860
};
861+
862+
const applicationCycleETree = {
863+
"id": "application.cycle.e",
864+
"version": "1.0.0",
865+
"path": path.join(cycleDepsBasePath, "application.cycle.e"),
866+
"dependencies": [
867+
{
868+
"id": "module.l",
869+
"version": "1.0.0",
870+
"path": path.join(cycleDepsBasePath, "module.l"),
871+
"dependencies": [
872+
{
873+
"id": "module.m",
874+
"version": "1.0.0",
875+
"path": path.join(cycleDepsBasePath, "module.m"),
876+
"dependencies": [
877+
{
878+
"id": "module.l",
879+
"version": "1.0.0",
880+
"path": path.join(cycleDepsBasePath, "module.l"),
881+
"dependencies": [],
882+
"deduped": true
883+
}
884+
]
885+
}
886+
]
887+
},
888+
{
889+
"id": "module.m",
890+
"version": "1.0.0",
891+
"path": path.join(cycleDepsBasePath, "module.m"),
892+
"dependencies": [
893+
{
894+
"id": "module.l",
895+
"version": "1.0.0",
896+
"path": path.join(cycleDepsBasePath, "module.l"),
897+
"dependencies": [
898+
{
899+
"id": "module.m",
900+
"version": "1.0.0",
901+
"path": path.join(cycleDepsBasePath, "module.m"),
902+
"dependencies": [],
903+
"deduped": true
904+
}
905+
]
906+
}
907+
]
908+
}
909+
]
910+
};

0 commit comments

Comments
 (0)