diff --git a/src/client/pythonEnvironments/creation/provider/venvUtils.ts b/src/client/pythonEnvironments/creation/provider/venvUtils.ts index 1bfb2c96f224..c47ca1ccf373 100644 --- a/src/client/pythonEnvironments/creation/provider/venvUtils.ts +++ b/src/client/pythonEnvironments/creation/provider/venvUtils.ts @@ -50,7 +50,8 @@ export async function getPipRequirementsFiles( function tomlParse(content: string): tomljs.JsonMap { try { - return tomljs.parse(content); + // Normalize CRLF to LF to prevent @iarna/toml from throwing control-character errors on comments + return tomljs.parse(content.replace(/\r\n?/g, '\n')); } catch (err) { traceError('Failed to parse `pyproject.toml`:', err); } diff --git a/src/test/pythonEnvironments/creation/provider/venvUtils.unit.test.ts b/src/test/pythonEnvironments/creation/provider/venvUtils.unit.test.ts index 2c8ec2ebce87..3fee12b710c0 100644 --- a/src/test/pythonEnvironments/creation/provider/venvUtils.unit.test.ts +++ b/src/test/pythonEnvironments/creation/provider/venvUtils.unit.test.ts @@ -11,6 +11,7 @@ import * as workspaceApis from '../../../../client/common/vscodeApis/workspaceAp import { ExistingVenvAction, OPEN_REQUIREMENTS_BUTTON, + isPipInstallableToml, pickExistingVenvAction, pickPackagesToInstall, } from '../../../../client/pythonEnvironments/creation/provider/venvUtils'; @@ -93,6 +94,29 @@ suite('Venv Utils test', () => { ]); }); + test('Toml with CRLF line endings and comments parses correctly', async () => { + findFilesStub.resolves([]); + pathExistsStub.resolves(true); + readFileStub.resolves( + '[project]\r\n# Project comment\r\nname = "spam"\r\nversion = "2020.0.0"\r\n[build-system]\r\n# Build system comment\r\nrequires = ["setuptools ~= 58.0"]\r\n', + ); + + const actual = await pickPackagesToInstall(workspace1); + assert.isTrue(showQuickPickWithBackStub.notCalled); + assert.deepStrictEqual(actual, [ + { + installType: 'toml', + source: path.join(workspace1.uri.fsPath, 'pyproject.toml'), + }, + ]); + }); + + test('isPipInstallableToml handles CRLF and comments correctly', () => { + const crlfContent = + '[project]\r\n# Comment\r\nname = "spam"\r\n[build-system]\r\n# Another comment\r\nrequires = ["setuptools"]\r\n'; + assert.isTrue(isPipInstallableToml(crlfContent)); + }); + test('Toml found with deps, but user presses escape', async () => { findFilesStub.resolves([]); pathExistsStub.resolves(true);