Skip to content

Commit

Permalink
Merge pull request #159 from snyk/feat/switch-to-official-parser-for-…
Browse files Browse the repository at this point in the history
…yarn-lock-v1

feat: use official parser for yarn lock v1
  • Loading branch information
JamesPatrickGill authored Aug 31, 2022
2 parents 31e2d2e + d496eef commit ecdd228
Show file tree
Hide file tree
Showing 11 changed files with 54 additions and 162 deletions.
1 change: 1 addition & 0 deletions lib/dep-graph-builders/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
buildDepGraphYarnLockV1Workspace,
extractPkgsFromYarnLockV1,
} from './yarn-lock-v1';

export {
parseYarnLockV1Project,
buildDepGraphYarnLockV1Workspace,
Expand Down
24 changes: 18 additions & 6 deletions lib/dep-graph-builders/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,12 @@ export const getChildNode = (
depInfo: { version: string; isDev: boolean },
pkgs: YarnLockPackages,
strictOutOfSync: boolean,
includeOptionalDeps: boolean,
) => {
const childNodeKey = `${name}@${depInfo.version}`;
let childNode: PkgNode;

if (!pkgs.hasOwnProperty(childNodeKey)) {
if (!pkgs[childNodeKey]) {
if (strictOutOfSync && !/^file:/.test(depInfo.version)) {
throw new OutOfSyncError(childNodeKey, LockfileType.yarn);
} else {
Expand All @@ -110,14 +111,18 @@ export const getChildNode = (
}
} else {
const depData = pkgs[childNodeKey];
const dependencies = getGraphDependencies(
depData.dependencies || {},
depInfo.isDev,
);
const optionalDependencies = includeOptionalDeps
? getGraphDependencies(depData.optionalDependencies || {}, depInfo.isDev)
: {};
childNode = {
id: `${name}@${depData.version}`,
name: name,
version: depData.version,
dependencies: getGraphDependencies(
depData.dependencies || {},
depInfo.isDev,
),
dependencies: { ...dependencies, ...optionalDependencies },
isDev: depInfo.isDev,
};
}
Expand All @@ -131,6 +136,7 @@ export const getChildNodeWorkspace = (
workspacePkgNameToVersion: Record<string, string>,
pkgs: YarnLockPackages,
strictOutOfSync: boolean,
includeOptionalDeps: boolean,
) => {
let childNode: PkgNode;

Expand All @@ -151,7 +157,13 @@ export const getChildNodeWorkspace = (
isDev: depInfo.isDev,
};
} else {
childNode = getChildNode(name, depInfo, pkgs, strictOutOfSync);
childNode = getChildNode(
name,
depInfo,
pkgs,
strictOutOfSync,
includeOptionalDeps,
);
}

return childNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const buildDepGraphYarnLockV1SimpleCyclesPruned = (
pkgJson: PackageJsonBase,
options: DepGraphBuildOptions,
) => {
const { includeDevDeps, strictOutOfSync } = options;
const { includeDevDeps, strictOutOfSync, includeOptionalDeps } = options;

const depGraphBuilder = new DepGraphBuilder(
{ name: 'yarn' },
Expand All @@ -43,6 +43,7 @@ export const buildDepGraphYarnLockV1SimpleCyclesPruned = (
colorMap,
extractedYarnLockV1Pkgs,
strictOutOfSync,
includeOptionalDeps,
);

return depGraphBuilder.build();
Expand All @@ -63,6 +64,7 @@ const dfsVisit = (
colorMap: Record<string, Color>,
extractedYarnLockV1Pkgs: YarnLockPackages,
strictOutOfSync: boolean,
includeOptionalDeps: boolean,
): void => {
colorMap[node.id] = Color.GRAY;

Expand All @@ -72,6 +74,8 @@ const dfsVisit = (
depInfo,
extractedYarnLockV1Pkgs,
strictOutOfSync,

includeOptionalDeps,
);

if (!colorMap.hasOwnProperty(childNode.id)) {
Expand All @@ -82,6 +86,7 @@ const dfsVisit = (
colorMap,
extractedYarnLockV1Pkgs,
strictOutOfSync,
includeOptionalDeps,
);
} else if (colorMap[childNode.id] === Color.GRAY) {
// cycle detected
Expand Down
6 changes: 5 additions & 1 deletion lib/dep-graph-builders/yarn-lock-v1/build-depgraph-simple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const buildDepGraphYarnLockV1Simple = (
pkgJson: PackageJsonBase,
options: DepGraphBuildOptions,
) => {
const { includeDevDeps, strictOutOfSync } = options;
const { includeDevDeps, strictOutOfSync, includeOptionalDeps } = options;

const depGraphBuilder = new DepGraphBuilder(
{ name: 'yarn' },
Expand All @@ -38,6 +38,7 @@ export const buildDepGraphYarnLockV1Simple = (
visitedMap,
extractedYarnLockV1Pkgs,
strictOutOfSync,
includeOptionalDeps,
);

return depGraphBuilder.build();
Expand All @@ -55,6 +56,7 @@ const dfsVisit = (
visitedMap: Set<string>,
extractedYarnLockV1Pkgs: YarnLockPackages,
strictOutOfSync: boolean,
includeOptionalDeps: boolean,
): void => {
visitedMap.add(node.id);

Expand All @@ -64,6 +66,7 @@ const dfsVisit = (
depInfo,
extractedYarnLockV1Pkgs,
strictOutOfSync,
includeOptionalDeps,
);

if (!visitedMap.has(childNode.id)) {
Expand All @@ -74,6 +77,7 @@ const dfsVisit = (
visitedMap,
extractedYarnLockV1Pkgs,
strictOutOfSync,
includeOptionalDeps,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const buildDepGraphYarnLockV1WorkspaceCyclesPruned = (
workspacePkgNameToVersion: Record<string, string>,
options: DepGraphBuildOptions,
) => {
const { includeDevDeps, strictOutOfSync } = options;
const { includeDevDeps, strictOutOfSync, includeOptionalDeps } = options;

const depGraphBuilder = new DepGraphBuilder(
{ name: 'yarn' },
Expand All @@ -48,6 +48,7 @@ export const buildDepGraphYarnLockV1WorkspaceCyclesPruned = (
extractedYarnLockV1Pkgs,
workspacePkgNameToVersion,
strictOutOfSync,
includeOptionalDeps,
);

return depGraphBuilder.build();
Expand All @@ -70,6 +71,7 @@ const dfsVisit = (
extractedYarnLockV1Pkgs: YarnLockPackages,
workspacePkgNameToVersion: Record<string, string>,
strictOutOfSync: boolean,
includeOptionalDeps: boolean,
): void => {
colorMap[node.id] = Color.GRAY;

Expand All @@ -82,6 +84,7 @@ const dfsVisit = (
workspacePkgNameToVersion,
extractedYarnLockV1Pkgs,
strictOutOfSync,
includeOptionalDeps,
);

if (!colorMap.hasOwnProperty(childNode.id)) {
Expand All @@ -97,6 +100,7 @@ const dfsVisit = (
extractedYarnLockV1Pkgs,
workspacePkgNameToVersion,
strictOutOfSync,
includeOptionalDeps,
);
} else {
colorMap[childNode.id] = Color.BLACK;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const buildDepGraphYarnLockV1Workspace = (
workspacePkgNameToVersion: Record<string, string>,
options: DepGraphBuildOptions,
) => {
const { includeDevDeps, strictOutOfSync } = options;
const { includeDevDeps, strictOutOfSync, includeOptionalDeps } = options;

const depGraphBuilder = new DepGraphBuilder(
{ name: 'yarn' },
Expand All @@ -40,6 +40,7 @@ export const buildDepGraphYarnLockV1Workspace = (
extractedYarnLockV1Pkgs,
workspacePkgNameToVersion,
strictOutOfSync,
includeOptionalDeps,
);

return depGraphBuilder.build();
Expand All @@ -62,6 +63,7 @@ const dfsVisit = (
extractedYarnLockV1Pkgs: YarnLockPackages,
workspacePkgNameToVersion: Record<string, string>,
strictOutOfSync: boolean,
includeOptionalDeps: boolean,
): void => {
visitedMap.add(node.id);

Expand All @@ -74,6 +76,7 @@ const dfsVisit = (
workspacePkgNameToVersion,
extractedYarnLockV1Pkgs,
strictOutOfSync,
includeOptionalDeps,
);

if (!visitedMap.has(childNode.id)) {
Expand All @@ -89,6 +92,7 @@ const dfsVisit = (
extractedYarnLockV1Pkgs,
workspacePkgNameToVersion,
strictOutOfSync,
includeOptionalDeps,
);
}
}
Expand Down
147 changes: 5 additions & 142 deletions lib/dep-graph-builders/yarn-lock-v1/extract-yarnlock-v1-pkgs.ts
Original file line number Diff line number Diff line change
@@ -1,145 +1,8 @@
import { eventLoopSpinner } from 'event-loop-spinner';
import type { LockFileParseOptions, YarnLockPackages } from './types';
import * as yarnLockfileParser from '@yarnpkg/lockfile';
import type { YarnLockPackages } from './types';

export const extractPkgsFromYarnLockV1 = async (
export const extractPkgsFromYarnLockV1 = (
yarnLockContent: string,
options: LockFileParseOptions,
): Promise<YarnLockPackages> => {
const { includeOptionalDeps } = options;

let allDeps: YarnLockPackages = {};

for (const newDeps of pkgDependencyGenerator(yarnLockContent, {
includeOptionalDeps,
})) {
if (eventLoopSpinner.isStarving()) {
await eventLoopSpinner.spin();
}
allDeps = { ...allDeps, ...newDeps };
}

return allDeps;
): YarnLockPackages => {
return yarnLockfileParser.parse(yarnLockContent).object;
};

function* pkgDependencyGenerator(
yarnLockContent: string,
options: { includeOptionalDeps: boolean },
) {
const lines = yarnLockContent.split('\n');

/*
This checks three things to make sure this is the
first line of the dep definition:
1. Line is non empty
2. Line does not start with white space
3. Line is not a comment (they are present in lockfiles)
*/
const isLineStartOfDepBlock = (line: string) => {
return line && !/\s/.test(line[0]) && line[0] !== '#';
};

/*
This checks if we are on the version line
*/
const isLineContainingVersion = (line: string) => {
return line.trimStart().startsWith('version');
};

/*
This checks if we are on the line starting the dependency
definitions
*/
const isLineStartOfDependencies = (line: string) => {
return line.includes('dependencies:');
};
/*
This checks if we are on the line starting the optional dependency
definitions
*/
const isLineStartOfOptDependencies = (line: string) => {
return line.includes('optionalDependencies:');
};

/*
This checks if current line has a matching indent size
*/
const matchesFrontWhitespace = (line: string, whitespaceCount: number) => {
return line.search(/\S/) === whitespaceCount;
};

while (lines.length) {
const line = lines.shift() as string;

// Once we find a block we look at the next lines until we
// get to the next dep block. We also store the keys from
// the line itself
if (isLineStartOfDepBlock(line)) {
const dependencyKeys = line.split(',').map((key) => {
return key
.trim()
.replace(new RegExp(':', 'g'), (_match, offset, string) => {
if (offset === string.length - 1) {
return '';
}
return ':';
})
.replace(new RegExp('"', 'g'), '');
});
let version: string = '';
const dependencies: Record<string, string> = {};

while (lines.length && !isLineStartOfDepBlock(lines[0])) {
const lineInDepBlock = lines.shift() as string;

if (isLineContainingVersion(lineInDepBlock)) {
const resolvedVersion = lineInDepBlock
.replace(new RegExp('version', 'g'), '')
.replace(new RegExp('"', 'g'), '')
.trim();
version = resolvedVersion;
}

if (isLineStartOfDependencies(lineInDepBlock)) {
const dependencyFrontWhitespaceCount = lines[0].search(/\S/);
while (
lines.length &&
matchesFrontWhitespace(lines[0], dependencyFrontWhitespaceCount)
) {
const dependencyLine = lines.shift() as string;
const [dependencyName, dependencyVersionWithQualifiers] =
dependencyLine
.trimStart()
.replace(new RegExp('"', 'g'), '')
.split(/(?<=^\S+)\s/);
dependencies[dependencyName] = dependencyVersionWithQualifiers;
}
}
if (
options.includeOptionalDeps &&
isLineStartOfOptDependencies(lineInDepBlock)
) {
const dependencyFrontWhitespaceCount = lines[0].search(/\S/);
while (
lines.length &&
matchesFrontWhitespace(lines[0], dependencyFrontWhitespaceCount)
) {
const dependencyLine = lines.shift() as string;
const [dependencyName, dependencyVersionWithQualifiers] =
dependencyLine
.trimStart()
.replace(new RegExp('"', 'g'), '')
.split(/(?<=^\S+)\s/);
dependencies[dependencyName] = dependencyVersionWithQualifiers;
}
}
}

const uniqueDepEntries = dependencyKeys.reduce((acc, key) => {
return { ...acc, [key]: { version, dependencies } };
}, {});

yield uniqueDepEntries;
continue;
}
}
}
Loading

0 comments on commit ecdd228

Please sign in to comment.