Skip to content

Commit

Permalink
feat: add pruning to yarn lock v1 dep graph builder
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesPatrickGill committed May 19, 2023
1 parent c8b07a4 commit e79c464
Show file tree
Hide file tree
Showing 102 changed files with 1,105 additions and 446 deletions.
24 changes: 24 additions & 0 deletions lib/dep-graph-builders/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,27 @@ export type YarnLockV2ProjectParseOptions = {
strictOutOfSync: boolean;
pruneWithinTopLevelDeps: boolean;
};

/*
* This chooses how much we prune:
* - `cycles`: only prunes cycles
* - `withinTopLevelDeps`: prunes everything within a top level dep
* - `none`: does not apply any pruning to the dep graph
*/
export type PruneLevel = 'cycles' | 'withinTopLevelDeps' | 'none';

export type YarnLockV1ProjectParseOptions = {
includeDevDeps: boolean;
includeOptionalDeps: boolean;
includePeerDeps: boolean;
strictOutOfSync: boolean;
pruneLevel: PruneLevel;
};

export type Yarn1DepGraphBuildOptions = {
includeDevDeps: boolean;
includeOptionalDeps: boolean;
includePeerDeps: boolean;
strictOutOfSync: boolean;
pruneWithinTopLevelDeps: boolean;
};
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const dfsVisit = async (
);
} else if (colorMap[childNode.id] === Color.GRAY) {
// cycle detected
childNode.id = `${childNode.id}|1`;
childNode.id = `${childNode.id}:pruned`;
addPkgNodeToGraph(depGraphBuilder, childNode, { isCyclic: true });
}

Expand Down
88 changes: 62 additions & 26 deletions lib/dep-graph-builders/yarn-lock-v1/build-depgraph-simple.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
import { DepGraphBuilder } from '@snyk/dep-graph';
import {
addPkgNodeToGraph,
getChildNode,
getTopLevelDeps,
PkgNode,
} from '../util';
import type { DepGraphBuildOptions } from '../types';
import { getChildNode, getTopLevelDeps, PkgNode } from '../util';
import type { Yarn1DepGraphBuildOptions } from '../types';
import type { NormalisedPkgs, PackageJsonBase } from '../types';
import { eventLoopSpinner } from 'event-loop-spinner';

export const buildDepGraphYarnLockV1Simple = async (
extractedYarnLockV1Pkgs: NormalisedPkgs,
pkgJson: PackageJsonBase,
options: DepGraphBuildOptions,
options: Yarn1DepGraphBuildOptions,
) => {
const { includeDevDeps, strictOutOfSync, includeOptionalDeps } = options;
const {
includeDevDeps,
includeOptionalDeps,
includePeerDeps,
strictOutOfSync,
pruneWithinTopLevelDeps,
} = options;

const depGraphBuilder = new DepGraphBuilder(
{ name: 'yarn' },
{ name: pkgJson.name, version: pkgJson.version },
);

const visitedMap: Set<string> = new Set();

const topLevelDeps = getTopLevelDeps(pkgJson, { includeDevDeps });
const topLevelDeps = getTopLevelDeps(pkgJson, {
includeDevDeps,
includePeerDeps,
includeOptionalDeps,
});

const rootNode: PkgNode = {
id: 'root-node',
Expand All @@ -36,10 +39,10 @@ export const buildDepGraphYarnLockV1Simple = async (
await dfsVisit(
depGraphBuilder,
rootNode,
visitedMap,
extractedYarnLockV1Pkgs,
strictOutOfSync,
includeOptionalDeps,
pruneWithinTopLevelDeps,
);

return depGraphBuilder.build();
Expand All @@ -54,17 +57,17 @@ export const buildDepGraphYarnLockV1Simple = async (
const dfsVisit = async (
depGraphBuilder: DepGraphBuilder,
node: PkgNode,
visitedMap: Set<string>,
extractedYarnLockV1Pkgs: NormalisedPkgs,
strictOutOfSync: boolean,
includeOptionalDeps: boolean,
pruneWithinTopLevel: boolean,
visited?: Set<string>,
): Promise<void> => {
visitedMap.add(node.id);

for (const [name, depInfo] of Object.entries(node.dependencies || {})) {
if (eventLoopSpinner.isStarving()) {
await eventLoopSpinner.spin();
}
const localVisited = visited || new Set<string>();
const childNode = getChildNode(
name,
depInfo,
Expand All @@ -73,18 +76,51 @@ const dfsVisit = async (
includeOptionalDeps,
);

if (!visitedMap.has(childNode.id)) {
addPkgNodeToGraph(depGraphBuilder, childNode, {});
await dfsVisit(
depGraphBuilder,
childNode,
visitedMap,
extractedYarnLockV1Pkgs,
strictOutOfSync,
includeOptionalDeps,
);
if (localVisited.has(childNode.id)) {
if (pruneWithinTopLevel) {
const prunedId = `${childNode.id}:pruned`;
depGraphBuilder.addPkgNode(
{ name: childNode.name, version: childNode.version },
prunedId,
{
labels: {
scope: node.isDev ? 'dev' : 'prod',
pruned: 'true',
...(node.missingLockFileEntry && {
missingLockFileEntry: 'true',
}),
},
},
);
depGraphBuilder.connectDep(node.id, prunedId);
} else {
depGraphBuilder.connectDep(node.id, childNode.id);
}
continue;
}

depGraphBuilder.addPkgNode(
{ name: childNode.name, version: childNode.version },
childNode.id,
{
labels: {
scope: node.isDev ? 'dev' : 'prod',
...(node.missingLockFileEntry && {
missingLockFileEntry: 'true',
}),
},
},
);
depGraphBuilder.connectDep(node.id, childNode.id);
localVisited.add(childNode.id);
await dfsVisit(
depGraphBuilder,
childNode,
extractedYarnLockV1Pkgs,
strictOutOfSync,
includeOptionalDeps,
pruneWithinTopLevel,
localVisited,
);
}
};
39 changes: 23 additions & 16 deletions lib/dep-graph-builders/yarn-lock-v1/simple.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
import { buildDepGraphYarnLockV1Simple } from '.';
import { PackageJsonBase } from '../types';
import { PackageJsonBase, YarnLockV1ProjectParseOptions } from '../types';
import { parsePkgJson } from '../util';
import { buildDepGraphYarnLockV1SimpleCyclesPruned } from './build-depgraph-simple-pruned';
import { extractPkgsFromYarnLockV1 } from './extract-yarnlock-v1-pkgs';
import { ProjectParseOptions } from '../types';

export const parseYarnLockV1Project = async (
pkgJsonContent: string,
yarnLockContent: string,
options: ProjectParseOptions,
options: YarnLockV1ProjectParseOptions,
) => {
const { includeDevDeps, includeOptionalDeps, pruneCycles, strictOutOfSync } =
options;
const {
includeDevDeps,
includeOptionalDeps,
includePeerDeps,
pruneLevel,
strictOutOfSync,
} = options;

const pkgs = extractPkgsFromYarnLockV1(yarnLockContent);

const pkgJson: PackageJsonBase = parsePkgJson(pkgJsonContent);

const depGraph = pruneCycles
? await buildDepGraphYarnLockV1SimpleCyclesPruned(pkgs, pkgJson, {
includeDevDeps,
strictOutOfSync,
includeOptionalDeps,
})
: await buildDepGraphYarnLockV1Simple(pkgs, pkgJson, {
includeDevDeps,
strictOutOfSync,
includeOptionalDeps,
});
const depGraph =
pruneLevel === 'cycles'
? await buildDepGraphYarnLockV1SimpleCyclesPruned(pkgs, pkgJson, {
includeDevDeps,
strictOutOfSync,
includeOptionalDeps,
})
: await buildDepGraphYarnLockV1Simple(pkgs, pkgJson, {
includeDevDeps,
includeOptionalDeps,
includePeerDeps,
strictOutOfSync,
pruneWithinTopLevelDeps: pruneLevel === 'withinTopLevelDeps',
});

return depGraph;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "empty",
"version": "1.0.0",
"main": "index.js",
"license": "MIT"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "repeat-node-in-different-top-level-chains",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"a": "1.0.0",
"d": "1.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


a@1.0.0:
version "1.0.0"
dependencies:
b "1.0.0"

b@1.0.0:
version "1.0.0"
dependencies:
c "1.0.0"

c@1.0.0:
version "1.0.0"

d@1.0.0:
version "1.0.0"
dependencies:
b "1.0.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "repeat-node-within-top-level-chain",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"a": "1.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


a@1.0.0:
version "1.0.0"
dependencies:
b "1.0.0"
d "1.0.0"

b@1.0.0:
version "1.0.0"
dependencies:
c "1.0.0"

c@1.0.0:
version "1.0.0"

d@1.0.0:
version "1.0.0"
dependencies:
b "1.0.0"

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "simple-chain",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"a": "1.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


a@1.0.0:
version "1.0.0"
dependencies:
b "1.0.0"

b@1.0.0:
version "1.0.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "simple-chain",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"a": "1.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


a@1.0.0:
version "1.0.0"
dependencies:
b "1.0.0"

b@1.0.0:
version "1.0.0"
dependencies:
a "1.0.0"
Loading

0 comments on commit e79c464

Please sign in to comment.