Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/analyze/duplicate-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ interface Version {
parents: string[];
}

function hasResolvedVersion(
pkg: Pick<ParsedLockFile['packages'][number], 'version'>
): pkg is Pick<ParsedLockFile['packages'][number], 'version'> & {
version: string;
} {
return typeof pkg.version === 'string' && pkg.version.length > 0;
}

Comment on lines +10 to +17
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is logically fine but a bit hard to read, and tying it to Pick<ParsedLockFile['packages'][number], 'version'> is redundant in practice (packages is ParsedDependency[] in lockparse). i’d drop the helper and inline an early continue, e.g.

if (typeof pkg.version !== 'string' || pkg.version.length === 0) continue

/**
* Outputs packages with duplicate versions and suggest possible fixes
* @param context
Expand Down Expand Up @@ -35,6 +43,13 @@ function resolveDuplicateDependencies(
): Map<string, Version[]> {
const resolvedDependencies: Map<string, Version[]> = new Map();
for (const pkg of lockfile.packages) {
// npm workspace links appear in package-lock.json without a version.
// They are not real installed versions and should not participate in
// duplicate-version analysis.
if (!hasResolvedVersion(pkg)) {
continue;
}

const entry: Version = {
version: pkg.version,
parents: []
Expand Down
26 changes: 26 additions & 0 deletions src/test/__snapshots__/duplicate-dependencies.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,32 @@ exports[`Duplicate Dependency Detection > should detect multiple versions 1`] =
}
`;

exports[`Duplicate Dependency Detection > should ignore workspace link entries without versions 1`] = `
{
"messages": [
{
"message": "[duplicate dependency] shared-lib has 2 installed versions:
1.0.0 via the following 1 package(s) package-a@1.0.0
2.0.0 via the following 1 package(s) package-b@1.0.0
💡 Suggestions
- Consider upgrading consuming packages as this may resolve this duplicate version.
",
"score": 0,
"severity": "warning",
},
],
"stats": {
"extraStats": [
{
"label": "Duplicate Dependency Count",
"name": "duplicateDependencyCount",
"value": 1,
},
],
},
}
`;

exports[`Duplicate Dependency Detection > should not detect duplicates when there are none 1`] = `
{
"messages": [],
Expand Down
77 changes: 77 additions & 0 deletions src/test/duplicate-dependencies.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,81 @@ describe('Duplicate Dependency Detection', () => {

expect(stats).toMatchSnapshot();
});

it('should ignore workspace link entries without versions', async () => {
const sharedLibv1: ParsedDependency = {
name: 'shared-lib',
version: '1.0.0',
dependencies: [],
devDependencies: [],
optionalDependencies: [],
peerDependencies: []
};
const sharedLibv2: ParsedDependency = {
name: 'shared-lib',
version: '2.0.0',
dependencies: [],
devDependencies: [],
optionalDependencies: [],
peerDependencies: []
};
const workspaceLink = {
name: 'react-native-dotgrid',
dependencies: [],
devDependencies: [],
optionalDependencies: [],
peerDependencies: []
} as unknown as ParsedDependency;
const packageA: ParsedDependency = {
name: 'package-a',
version: '1.0.0',
dependencies: [sharedLibv1],
devDependencies: [],
peerDependencies: [],
optionalDependencies: []
};
const packageB: ParsedDependency = {
name: 'package-b',
version: '1.0.0',
dependencies: [sharedLibv2],
devDependencies: [],
peerDependencies: [],
optionalDependencies: []
};

context = {
fs: fileSystem,
root: '.',
messages: [],
stats: {
name: 'unknown',
version: 'unknown',
dependencyCount: {
production: 0,
development: 0
},
extraStats: []
},
lockfile: {
type: 'npm',
packages: [workspaceLink, packageA, packageB, sharedLibv1, sharedLibv2],
root: {
name: 'root-package',
version: '1.0.0',
dependencies: [workspaceLink, packageA, packageB],
devDependencies: [],
optionalDependencies: [],
peerDependencies: []
}
},
packageFile: {
name: 'test-package',
version: '1.0.0'
}
};

const stats = await runDuplicateDependencyAnalysis(context);

expect(stats).toMatchSnapshot();
});
});
Loading