Skip to content

Commit 91360e1

Browse files
committed
fix: guard ^ floor comparison against cross-major parent declarations
When the override is ^4.2.0 and a parent declares ^5.0.0, coerceVersion strips the operator and compareVersions(5.0.0, 4.2.0) returns >= 0, causing OA009 to fire. But ^4.2.0 means >=4.2.0 <5.0.0 and ^5.0.0 means >=5.0.0 <6.0.0 - the ranges do not overlap, so removing the override would change the resolved version from 4.x to 5.x. Added a majorOf() guard: when the override uses ^, each parent must also be in the same major before it counts as meeting the floor. Added a test case covering the cross-major non-firing case.
1 parent a3be8fc commit 91360e1

2 files changed

Lines changed: 18 additions & 0 deletions

File tree

src/overrides/detectors/oa009-stale-floor.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import { looksLikeVersion, coerceVersion, compareVersions } from "../../utils/ve
55

66
const RULE_ID = "OA009" as const;
77

8+
function majorOf(version: string): number {
9+
return parseInt(version.split(".")[0] ?? "0", 10);
10+
}
11+
812
export function detect(ctx: OverrideContext): OverrideFinding[] {
913
if (ctx.skippedDetectors.some((s) => s.ruleId === RULE_ID)) return [];
1014

@@ -24,9 +28,13 @@ export function detect(ctx: OverrideContext): OverrideFinding[] {
2428
const parents = ctx.parentDeclarations.get(entry.packageName) ?? [];
2529
if (parents.length === 0) continue;
2630

31+
const floorMajor = pin.startsWith("^") ? majorOf(floor) : -1;
2732
const allParentsMeetFloor = parents.every((parent) => {
2833
const parentFloor = coerceVersion(parent.declaredValue);
2934
if (!parentFloor) return false;
35+
// For ^ ranges the allowed range is [floor, nextMajor). A parent in a
36+
// different major cannot overlap, so the override is not redundant.
37+
if (floorMajor >= 0 && majorOf(parentFloor) !== floorMajor) return false;
3038
return compareVersions(parentFloor, floor) >= 0;
3139
});
3240
if (!allParentsMeetFloor) continue;

tests/overrides/detectors/oa009.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@ describe('OA009-STALE-FLOOR', () => {
141141
expect(detect(ctxOf([nested], [], []))).toEqual([]);
142142
});
143143

144+
it('does NOT fire when ^ override and parent are in different major (ranges do not overlap)', () => {
145+
// Override ^4.2.0 means >=4.2.0 <5.0.0; parent ^5.0.0 means >=5.0.0 <6.0.0.
146+
// Removing the override would change npm's resolution from 4.x to 5.x.
147+
expect(detect(ctxOf(
148+
[e('semver', '^4.2.0')],
149+
[['semver', '4.6.3']],
150+
[['semver', [p('some-lib', '^5.0.0')]]],
151+
))).toEqual([]);
152+
});
153+
144154
it('is conservative when parent declaredValue cannot be parsed as a version', () => {
145155
expect(detect(ctxOf(
146156
[e('js-yaml', '>=4.2.0')],

0 commit comments

Comments
 (0)