feat(code-lens): support package version upgrade#32
Conversation
c235f90 to
5d190ad
Compare
📝 WalkthroughWalkthroughThis pull request introduces a new version lens feature for displaying upgrade availability as CodeLens annotations for package dependencies. The implementation includes a new configuration option ( Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (6)
src/commands/internal/replace-text.ts (1)
4-8: Consider whether empty string replacement should be supported.The check
!textwill reject empty strings, preventing text deletion via this command. If replacing with an empty string is a valid use case, consider changing the condition totext === undefined || text === nullortext == null.For the current version lens feature, this is likely acceptable since replacing a version with an empty string is not intended behaviour.
💡 Optional fix if deletion should be supported
export const replaceText: TextEditorCommandCallback = (_: TextEditor, edit: TextEditorEdit, range?: Range, text?: string) => { - if (!range || !text) + if (!range || text == null) return edit.replace(range, text) }src/utils/package.ts (1)
15-19: Consider adding an explicit return type annotation.The function can return
null(from the early return or frommaxSatisfying), but this isn't immediately clear from the signature. Adding an explicit return type improves readability and helps catch unintended changes.💡 Proposed fix
-export function resolveExactVersion(pkg: PackageInfo | undefined, version: string) { +export function resolveExactVersion(pkg: PackageInfo | undefined, version: string): string | null { if (!pkg?.distTags) return null return pkg.distTags[version] ?? maxSatisfying(Object.keys(pkg.versionsMeta ?? {}), version) }tests/utils/package.test.ts (1)
14-17: Consider adding more test cases forresolveExactVersion.The current test covers the
undefinedpackage case. Additional test scenarios would improve confidence in the function:
- Package with
distTagswhere version matches a tag name (e.g.,'latest').- Package with
versionsMetawhere a semver range can be satisfied.- Package where neither
distTagsnorversionsMetacontains a matching version.💡 Example additional test cases
it('should resolve dist-tag to exact version', () => { const pkg = { distTags: { latest: '2.0.0' }, versionsMeta: { '1.0.0': {}, '2.0.0': {} }, } as PackageInfo expect(resolveExactVersion(pkg, 'latest')).toBe('2.0.0') }) it('should resolve semver range via maxSatisfying', () => { const pkg = { distTags: { latest: '2.0.0' }, versionsMeta: { '1.0.0': {}, '1.5.0': {}, '2.0.0': {} }, } as PackageInfo expect(resolveExactVersion(pkg, '^1.0.0')).toBe('1.5.0') })src/utils/upgrade.ts (1)
22-31: Potential non-determinism when multiple dist-tags match the prerelease identifier.If multiple dist-tags share the same prerelease identifier (e.g.,
next: 3.0.0-alpha.5andcanary: 3.0.0-alpha.7), the function returns the first match encountered duringObject.entriesiteration. Object property order in modern JS is generally insertion-order for string keys, but this may not yield the highest available version.Consider sorting candidates by version descending and returning the greatest, or documenting that only the first match is returned.
♻️ Optional: return the highest matching prerelease version
+import semverGt from 'semver/functions/gt' + + let best: string | undefined for (const [tag, tagVersion] of Object.entries(pkg.distTags)) { if (tag === 'latest') continue if (prerelease(tagVersion)?.[0] !== currentPreId) continue if (lte(tagVersion, exactVersion)) continue - return tagVersion + if (!best || semverGt(tagVersion, best)) + best = tagVersion } + return best }tests/utils/upgrade.test.ts (1)
18-26: Consider adding edge-case tests for completeness.The parameterised tests cover the main scenarios well. Consider adding tests for:
- Empty
distTagsobject ({}) — ensures graceful handling when no tags are published.- Missing
latesttag — verifies behaviour when only prerelease tags exist.🧪 Optional: additional edge-case tests
it('should return undefined when distTags is empty', () => { const emptyPkg = createPackageInfo({}) expect(resolveUpgradeTargetVersion(emptyPkg, '1.0.0')).toBeUndefined() }) it('should return undefined when only prerelease tags exist and version is stable', () => { const preOnlyPkg = createPackageInfo({ next: '2.0.0-alpha.1' }) expect(resolveUpgradeTargetVersion(preOnlyPkg, '1.0.0')).toBeUndefined() })src/providers/code-lens/version.ts (1)
55-60: Consider handling rejected promises to avoid stale "checking..." state.If
getPackageInforeturns a Promise that rejects, the lens will remain in the "checking..." state until the document changes. Usingfinallytriggers a refresh regardless of success or failure, which is good, but the user may see "checking..." followed by "unknown" repeatedly if the network is flaky.This is minor since the refresh will eventually update the state, but you may want to add explicit error handling or logging for debugging purposes.
♻️ Optional: add error handling for rejected promises
if (pkg instanceof Promise) { lens.command = { title: '$(sync~spin) checking...', command: '' } - pkg.finally(() => this.scheduleRefresh()) + pkg + .catch(() => { /* Network error handled on next refresh */ }) + .finally(() => this.scheduleRefresh()) return lens }
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
README.mdeslint.config.tspackage.jsonplayground/.vscode/settings.jsonsrc/commands/internal/index.tssrc/commands/internal/replace-text.tssrc/index.tssrc/providers/code-lens/index.tssrc/providers/code-lens/version.tssrc/providers/diagnostics/rules/upgrade.tssrc/state.tssrc/utils/package.tssrc/utils/upgrade.tstests/diagnostics/upgrade.test.tstests/utils/package.test.tstests/utils/upgrade.test.ts
It would be great if this kind of prompt could also be clicked to upgrade.
This is a really good point. I think we should move in that direction. |
Yes, but as far as I know, that's not possible right now. |
No. The spec |



Closes #2