Skip to content

Commit ae3ebb8

Browse files
committed
Replace old use cases
1 parent fb4b650 commit ae3ebb8

11 files changed

Lines changed: 53 additions & 123 deletions

File tree

src/common/extVersion.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
import { PYTHON_EXTENSION_ID } from './constants';
22
import { getExtension } from './extension.apis';
33
import { traceError } from './logging';
4+
import { PEP440Version } from './utils/pep440Version';
45

56
export function ensureCorrectVersion() {
67
const extension = getExtension(PYTHON_EXTENSION_ID);
78
if (!extension) {
89
return;
910
}
1011

11-
const version = extension.packageJSON.version;
12-
const parts = version.split('.');
13-
const major = parseInt(parts[0]);
14-
const minor = parseInt(parts[1]);
15-
if (major >= 2025 || (major === 2024 && minor >= 23)) {
12+
const version = PEP440Version.parse(extension.packageJSON.version);
13+
const minVersion = PEP440Version.parse('2024.23.0');
14+
if (version && minVersion && PEP440Version.compare(version, minVersion) >= 0) {
1615
return;
1716
}
1817
traceError('Incompatible Python extension. Please update `ms-python.python` to version 2024.23 or later.');

src/managers/builtin/pipUtils.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { PackageManagementOptions, PythonEnvironment, PythonEnvironmentApi, Pyth
66
import { EXTENSION_ROOT_DIR } from '../../common/constants';
77
import { PackageManagement, Pickers, VenvManagerStrings } from '../../common/localize';
88
import { traceInfo } from '../../common/logging';
9+
import { PEP440Version } from '../../common/utils/pep440Version';
910
import { showQuickPickWithButtons, withProgress } from '../../common/window.apis';
1011
import { findFiles } from '../../common/workspace.apis';
1112
import { selectFromCommonPackagesToInstall, selectFromInstallableToInstall } from '../common/pickers';
@@ -56,13 +57,7 @@ export function validatePyprojectToml(toml: PyprojectToml): string | undefined {
5657
if (version.length === 0) {
5758
return l10n.t('Version cannot be empty in pyproject.toml.');
5859
}
59-
// PEP 440 version regex. Versions must follow PEP 440 format (e.g., "1.0.0", "2.1a3").
60-
// See https://peps.python.org/pep-0440/
61-
// This regex is adapted from the official python 'packaging' library:
62-
// https://github.com/pypa/packaging/blob/main/src/packaging/version.py
63-
const versionRegex =
64-
/^v?([0-9]+!)?([0-9]+(?:\.[0-9]+)*)(?:[-_.]?(a|b|c|rc|alpha|beta|pre|preview)[-_.]?([0-9]+)?)?(?:(?:-([0-9]+))|(?:[-_.]?(post|rev|r)[-_.]?([0-9]+)?))?(?:[-_.]?(dev)[-_.]?([0-9]+)?)?(?:\+([a-z0-9]+(?:[-_.][a-z0-9]+)*))?$/i;
65-
if (!versionRegex.test(version)) {
60+
if (!PEP440Version.parse(version)) {
6661
return l10n.t('Invalid version "{0}" in pyproject.toml.', version);
6762
}
6863
}

src/managers/builtin/utils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { getExtension } from '../../common/extension.apis';
1313
import { Common, PixiStrings, SysManagerStrings } from '../../common/localize';
1414
import { traceInfo, traceVerbose } from '../../common/logging';
1515
import { getGlobalPersistentState } from '../../common/persistentState';
16+
import { PEP440Version } from '../../common/utils/pep440Version';
1617
import { showInformationMessage, withProgress } from '../../common/window.apis';
1718
import { openExtension } from '../../common/workbenchCommands';
1819
import {
@@ -21,7 +22,7 @@ import {
2122
NativePythonEnvironmentKind,
2223
NativePythonFinder,
2324
} from '../common/nativePythonFinder';
24-
import { shortVersion, sortEnvironments } from '../common/utils';
25+
import { sortEnvironments } from '../common/utils';
2526
import { runPython, runUV, shouldUseUv } from './helpers';
2627
import { parsePipListJson, PipPackage } from './pipListUtils';
2728

@@ -80,7 +81,7 @@ function getKindName(kind: NativePythonEnvironmentKind | undefined): string | un
8081
function getPythonInfo(env: NativeEnvInfo): PythonEnvironmentInfo {
8182
if (env.executable && env.version && env.prefix) {
8283
const kindName = getKindName(env.kind);
83-
const sv = shortVersion(env.version);
84+
const sv = PEP440Version.shortenVersionString(env.version);
8485
const name = kindName ? `Python ${sv} (${kindName})` : `Python ${sv}`;
8586
const displayName = kindName ? `Python ${sv} (${kindName})` : `Python ${sv}`;
8687
const shortDisplayName = kindName ? `${sv} (${kindName})` : `${sv}`;

src/managers/builtin/venvManager.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ import { VenvManagerStrings } from '../../common/localize';
3333
import { traceError, traceWarn } from '../../common/logging';
3434
import { createDeferred, Deferred } from '../../common/utils/deferred';
3535
import { normalizePath } from '../../common/utils/pathUtils';
36+
import { PEP440Version } from '../../common/utils/pep440Version';
3637
import { showErrorMessage, showInformationMessage, withProgress } from '../../common/window.apis';
3738
import { findParentIfFile } from '../../features/envCommands';
3839
import { getProjectFsPathForScope, tryFastPathGet } from '../common/fastPath';
3940
import { NativePythonFinder } from '../common/nativePythonFinder';
40-
import { getLatest, shortVersion, sortEnvironments } from '../common/utils';
41+
import { getLatest, sortEnvironments } from '../common/utils';
4142
import { promptInstallPythonViaUv } from './uvPythonInstaller';
4243
import {
4344
clearVenvCache,
@@ -117,7 +118,7 @@ export class VenvManager implements EnvironmentManager {
117118
description: l10n.t('Create a virtual environment in workspace root'),
118119
detail: l10n.t(
119120
'Uses Python version {0} and installs workspace dependencies.',
120-
shortVersion(this.globalEnv.version),
121+
PEP440Version.shortenVersionString(this.globalEnv.version),
121122
),
122123
};
123124
}

src/managers/builtin/venvUtils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { getWorkspacePersistentState } from '../../common/persistentState';
1010
import { EventNames } from '../../common/telemetry/constants';
1111
import { sendTelemetryEvent } from '../../common/telemetry/sender';
1212
import { normalizePath } from '../../common/utils/pathUtils';
13+
import { PEP440Version } from '../../common/utils/pep440Version';
1314
import {
1415
showErrorMessage,
1516
showOpenDialog,
@@ -24,7 +25,7 @@ import {
2425
NativePythonEnvironmentKind,
2526
NativePythonFinder,
2627
} from '../common/nativePythonFinder';
27-
import { getShellActivationCommands, shortVersion, sortEnvironments } from '../common/utils';
28+
import { getShellActivationCommands, sortEnvironments } from '../common/utils';
2829
import { runPython, runUV, shouldUseUv } from './helpers';
2930
import { getProjectInstallable, PipPackages, shouldProceedAfterPyprojectValidation } from './pipUtils';
3031
import { resolveSystemPythonEnvironmentPath } from './utils';
@@ -164,7 +165,7 @@ async function getPythonInfo(env: NativeEnvInfo): Promise<PythonEnvironmentInfo>
164165

165166
if (env.executable && env.version && env.prefix) {
166167
const venvName = env.name ?? getName(env.executable);
167-
const sv = shortVersion(env.version);
168+
const sv = PEP440Version.shortenVersionString(env.version);
168169
const name = `${venvName} (${sv})`;
169170
let description = undefined;
170171
if (env.kind === NativePythonEnvironmentKind.venvUv) {

src/managers/common/utils.ts

Lines changed: 10 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import path from 'path';
33
import { commands, ConfigurationTarget, l10n, window, workspace } from 'vscode';
44
import { PythonCommandRunConfiguration, PythonEnvironment, PythonEnvironmentApi } from '../../api';
55
import { traceLog, traceVerbose } from '../../common/logging';
6+
import { PEP440Version } from '../../common/utils/pep440Version';
67
import { isWindows } from '../../common/utils/platformUtils';
78
import { ShellConstants } from '../../features/common/shellConstants';
89
import { getDefaultEnvManagerSetting, setDefaultEnvManagerBroken } from '../../features/settings/settingHelpers';
@@ -21,51 +22,6 @@ export function isNumber(obj: unknown): obj is number {
2122
return typeof obj === 'number' && !isNaN(obj);
2223
}
2324

24-
export function shortVersion(version: string): string {
25-
const pattern = /(\d)\.(\d+)(?:\.(\d+)?)?/gm;
26-
const match = pattern.exec(version);
27-
if (match) {
28-
if (match[3]) {
29-
return `${match[1]}.${match[2]}.${match[3]}`;
30-
}
31-
return `${match[1]}.${match[2]}.x`;
32-
}
33-
return version;
34-
}
35-
36-
export function isGreater(a: string | undefined, b: string | undefined): boolean {
37-
if (!a && !b) {
38-
return false;
39-
}
40-
if (!a) {
41-
return false;
42-
}
43-
if (!b) {
44-
return true;
45-
}
46-
47-
try {
48-
const aParts = a.split('.');
49-
const bParts = b.split('.');
50-
for (let i = 0; i < aParts.length; i++) {
51-
if (i >= bParts.length) {
52-
return true;
53-
}
54-
const aPart = parseInt(aParts[i], 10);
55-
const bPart = parseInt(bParts[i], 10);
56-
if (aPart > bPart) {
57-
return true;
58-
}
59-
if (aPart < bPart) {
60-
return false;
61-
}
62-
}
63-
} catch {
64-
return false;
65-
}
66-
return false;
67-
}
68-
6925
export function sortEnvironments(collection: PythonEnvironment[]): PythonEnvironment[] {
7026
return collection.sort((a, b) => {
7127
// Environments with errors should be sorted to the end
@@ -76,7 +32,12 @@ export function sortEnvironments(collection: PythonEnvironment[]): PythonEnviron
7632
return -1;
7733
}
7834
if (a.version !== b.version) {
79-
return isGreater(a.version, b.version) ? -1 : 1;
35+
const av = PEP440Version.parse(a.version);
36+
const bv = PEP440Version.parse(b.version);
37+
if (av && bv) {
38+
return PEP440Version.compare(bv, av); // descending
39+
}
40+
return a.version ? 1 : -1;
8041
}
8142
const value = a.name.localeCompare(b.name);
8243
if (value !== 0) {
@@ -96,7 +57,9 @@ export function getLatest(collection: PythonEnvironment[]): PythonEnvironment |
9657

9758
let latest = candidates[0];
9859
for (const env of candidates) {
99-
if (isGreater(env.version, latest.version)) {
60+
const av = PEP440Version.parse(env.version);
61+
const bv = PEP440Version.parse(latest.version);
62+
if (av && bv && PEP440Version.compare(av, bv) > 0) {
10063
latest = env;
10164
}
10265
}
@@ -114,31 +77,6 @@ export function pathForGitBash(binPath: string): string {
11477
return isWindows() ? binPath.replace(/\\/g, '/').replace(/^([a-zA-Z]):/, '/$1') : binPath;
11578
}
11679

117-
/**
118-
* Compares two semantic version strings. Support sonly simple 1.1.1 style versions.
119-
* @param version1 First version
120-
* @param version2 Second version
121-
* @returns -1 if version1 < version2, 0 if equal, 1 if version1 > version2
122-
*/
123-
export function compareVersions(version1: string, version2: string): number {
124-
const v1Parts = version1.split('.').map(Number);
125-
const v2Parts = version2.split('.').map(Number);
126-
127-
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
128-
const v1Part = v1Parts[i] || 0;
129-
const v2Part = v2Parts[i] || 0;
130-
131-
if (v1Part > v2Part) {
132-
return 1;
133-
}
134-
if (v1Part < v2Part) {
135-
return -1;
136-
}
137-
}
138-
139-
return 0;
140-
}
141-
14280
function buildPwshActivationCommands(ps1Path: string): PythonCommandRunConfiguration[] {
14381
const commands: PythonCommandRunConfiguration[] = [];
14482
if (isWindows()) {

src/managers/conda/condaStepBasedFlow.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as path from 'path';
33
import { l10n, LogOutputChannel, QuickInputButtons, QuickPickItem, Uri } from 'vscode';
44
import { EnvironmentManager, PythonEnvironment, PythonEnvironmentApi } from '../../api';
55
import { CondaStrings } from '../../common/localize';
6+
import { PEP440Version } from '../../common/utils/pep440Version';
67
import { showInputBoxWithButtons, showQuickPickWithButtons } from '../../common/window.apis';
78
import {
89
createNamedCondaEnvironment,
@@ -113,19 +114,14 @@ async function selectPythonVersion(state: CondaCreationState): Promise<StepFunct
113114
),
114115
);
115116

116-
// Sort versions by major version (descending), ignoring minor/patch for simplicity
117-
const parseMajorMinor = (v: string) => {
118-
const m = v.match(/^(\\d+)(?:\\.(\\d+))?/);
119-
return { major: m ? Number(m[1]) : 0, minor: m && m[2] ? Number(m[2]) : 0 };
120-
};
121-
117+
// Sort versions descending using PEP 440 comparison
122118
versions = versions.sort((a, b) => {
123-
const pa = parseMajorMinor(a as string);
124-
const pb = parseMajorMinor(b as string);
125-
if (pa.major !== pb.major) {
126-
return pb.major - pa.major;
127-
} // desc by major
128-
return pb.minor - pa.minor; // desc by minor
119+
const av = PEP440Version.parse(a as string);
120+
const bv = PEP440Version.parse(b as string);
121+
if (!av || !bv) {
122+
return 0;
123+
}
124+
return PEP440Version.compare(bv, av); // descending
129125
});
130126

131127
if (!versions || versions.length === 0) {

src/managers/conda/condaUtils.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { pickProject } from '../../common/pickers/projects';
3434
import { StopWatch } from '../../common/stopWatch';
3535
import { createDeferred } from '../../common/utils/deferred';
3636
import { untildify } from '../../common/utils/pathUtils';
37+
import { PEP440Version } from '../../common/utils/pep440Version';
3738
import { isWindows } from '../../common/utils/platformUtils';
3839
import {
3940
showErrorMessage,
@@ -53,7 +54,7 @@ import {
5354
} from '../common/nativePythonFinder';
5455
import { selectFromCommonPackagesToInstall } from '../common/pickers';
5556
import { Installable } from '../common/types';
56-
import { shortVersion, sortEnvironments } from '../common/utils';
57+
import { sortEnvironments } from '../common/utils';
5758
import { CondaEnvManager } from './condaEnvManager';
5859
import { getCondaHookPs1Path, getLocalActivationScript, ShellCondaInitStatus } from './condaSourcingUtils';
5960
import { createStepBasedCondaFlow } from './condaStepBasedFlow';
@@ -357,7 +358,7 @@ export async function getNamedCondaPythonInfo(
357358
envManager: EnvironmentManager,
358359
): Promise<PythonEnvironmentInfo> {
359360
const { shellActivation, shellDeactivation } = await buildShellActivationMapForConda(prefix, envManager, name);
360-
const sv = shortVersion(version);
361+
const sv = PEP440Version.shortenVersionString(version);
361362

362363
return {
363364
name: name,
@@ -399,7 +400,7 @@ export async function getPrefixesCondaPythonInfo(
399400
conda: string,
400401
envManager: EnvironmentManager,
401402
): Promise<PythonEnvironmentInfo> {
402-
const sv = shortVersion(version);
403+
const sv = PEP440Version.shortenVersionString(version);
403404

404405
const { shellActivation, shellDeactivation } = await buildShellActivationMapForConda(prefix, envManager);
405406

@@ -993,19 +994,14 @@ export async function pickPythonVersion(
993994
),
994995
);
995996

996-
// Sort versions by major version (descending), ignoring minor/patch for simplicity
997-
const parseMajorMinor = (v: string) => {
998-
const m = v.match(/^(\d+)(?:\.(\d+))?/);
999-
return { major: m ? Number(m[1]) : 0, minor: m && m[2] ? Number(m[2]) : 0 };
1000-
};
1001-
997+
// Sort versions descending using PEP 440 comparison
1002998
versions = versions.sort((a, b) => {
1003-
const pa = parseMajorMinor(a);
1004-
const pb = parseMajorMinor(b);
1005-
if (pa.major !== pb.major) {
1006-
return pb.major - pa.major;
1007-
} // desc by major
1008-
return pb.minor - pa.minor; // desc by minor
999+
const av = PEP440Version.parse(a);
1000+
const bv = PEP440Version.parse(b);
1001+
if (!av || !bv) {
1002+
return 0;
1003+
}
1004+
return PEP440Version.compare(bv, av); // descending
10091005
});
10101006

10111007
if (!versions || versions.length === 0) {

src/managers/pipenv/pipenvUtils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { ENVS_EXTENSION_ID } from '../../common/constants';
1717
import { traceError, traceInfo, traceVerbose } from '../../common/logging';
1818
import { getWorkspacePersistentState } from '../../common/persistentState';
1919
import { untildify } from '../../common/utils/pathUtils';
20+
import { PEP440Version } from '../../common/utils/pep440Version';
2021
import { getSettingWorkspaceScope } from '../../features/settings/settingHelpers';
2122
import {
2223
isNativeEnvInfo,
@@ -25,7 +26,7 @@ import {
2526
NativePythonEnvironmentKind,
2627
NativePythonFinder,
2728
} from '../common/nativePythonFinder';
28-
import { getShellActivationCommands, shortVersion } from '../common/utils';
29+
import { getShellActivationCommands } from '../common/utils';
2930

3031
export const PIPENV_PATH_KEY = `${ENVS_EXTENSION_ID}:pipenv:PIPENV_PATH`;
3132
export const PIPENV_WORKSPACE_KEY = `${ENVS_EXTENSION_ID}:pipenv:WORKSPACE_SELECTED`;
@@ -115,7 +116,7 @@ async function nativeToPythonEnv(
115116
return undefined;
116117
}
117118

118-
const sv = shortVersion(info.version);
119+
const sv = PEP440Version.shortenVersionString(info.version);
119120
const folderName = path.basename(info.prefix);
120121
const name = info.name || info.displayName || folderName;
121122
const displayName = info.displayName || `${folderName} (${sv})`;

src/managers/poetry/poetryUtils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ENVS_EXTENSION_ID } from '../../common/constants';
88
import { traceError, traceInfo } from '../../common/logging';
99
import { getWorkspacePersistentState } from '../../common/persistentState';
1010
import { getUserHomeDir, normalizePath, untildify } from '../../common/utils/pathUtils';
11+
import { PEP440Version } from '../../common/utils/pep440Version';
1112
import { isMac, isWindows } from '../../common/utils/platformUtils';
1213
import { getSettingWorkspaceScope } from '../../features/settings/settingHelpers';
1314
import {
@@ -17,7 +18,7 @@ import {
1718
NativePythonEnvironmentKind,
1819
NativePythonFinder,
1920
} from '../common/nativePythonFinder';
20-
import { getShellActivationCommands, shortVersion, sortEnvironments } from '../common/utils';
21+
import { getShellActivationCommands, sortEnvironments } from '../common/utils';
2122

2223
/**
2324
* Checks if the POETRY_VIRTUALENVS_IN_PROJECT environment variable is set to a truthy value.
@@ -341,7 +342,7 @@ export async function nativeToPythonEnv(
341342
return undefined;
342343
}
343344

344-
const sv = shortVersion(info.version);
345+
const sv = PEP440Version.shortenVersionString(info.version);
345346
const name = info.name || info.displayName || path.basename(info.prefix);
346347
const displayName = info.displayName || `poetry (${sv})`;
347348

0 commit comments

Comments
 (0)