Skip to content

Enhance sorting of installable files to prioritize shallower paths in getProjectInstallable function#1428

Draft
eleanorjboyd wants to merge 2 commits intomicrosoft:mainfrom
eleanorjboyd:implicit-boar
Draft

Enhance sorting of installable files to prioritize shallower paths in getProjectInstallable function#1428
eleanorjboyd wants to merge 2 commits intomicrosoft:mainfrom
eleanorjboyd:implicit-boar

Conversation

@eleanorjboyd
Copy link
Copy Markdown
Member

@eleanorjboyd eleanorjboyd commented Apr 3, 2026

helps with #1429

@eleanorjboyd eleanorjboyd self-assigned this Apr 3, 2026
@eleanorjboyd eleanorjboyd added the feature-request Request for new features or functionality label Apr 3, 2026
@eleanorjboyd eleanorjboyd requested a review from Copilot April 14, 2026 20:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Improves the ordering of detected installable files so that shallower (more top-level) dependency files are prioritized, addressing sorting issues in getProjectInstallable (helps with #1429).

Changes:

  • Add depth-based sorting for matched installable URIs in getProjectInstallable.
  • Add a unit test validating shallower paths are ordered before deeper ones.
Show a summary per file
File Description
src/managers/builtin/pipUtils.ts Replaces default sort with a comparator that prioritizes shallower filesystem paths.
src/test/managers/builtin/pipUtils.unit.test.ts Adds a regression test ensuring shallower requirements files appear first.

Copilot's findings

Comments suppressed due to low confidence (1)

src/managers/builtin/pipUtils.ts:337

  • The new depth sort implies the returned installables order matters, but this block mutates installable from concurrent async tasks. If any .toml entries are present, await tomlParse(...) can cause later push(...) calls to occur out of the sorted order, making the final installables ordering nondeterministic. To keep ordering deterministic, build per-URI results (e.g., return an Installable[] from each mapper) and then flatten in filtered order, or process filtered sequentially when ordering is required.
            await Promise.all(
                filtered.map(async (uri) => {
                    if (uri.fsPath.endsWith('.toml')) {
                        const toml = await tomlParse(uri.fsPath);

                        // Validate pyproject.toml
                        if (!validationError) {
                            const error = validatePyprojectToml(toml);
                            if (error) {
                                validationError = {
                                    message: error,
                                    fileUri: uri,
                                };
                            }
                        }

                        installable.push(...getTomlInstallable(toml, uri));
                    } else {
                        const name = path.basename(uri.fsPath);
                        installable.push({
                            name,
                            uri,
                            displayName: name,
                            group: 'Requirements',
                            args: ['-r', uri.fsPath],
                        });
                    }
                }),
            );
  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment on lines +298 to +307
.sort((a, b) => {
// Sort by path depth (shallowest first) so top-level files like
// requirements.txt appear before deeply nested ones.
const depthA = a.fsPath.split(path.sep).length;
const depthB = b.fsPath.split(path.sep).length;
if (depthA !== depthB) {
return depthA - depthB;
}
return a.fsPath.localeCompare(b.fsPath);
});
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

Depth is currently computed from the absolute fsPath. If multiple project roots are present, absolute path depth can differ due to the root’s location on disk (e.g., /mnt/x/project vs /x/project), which can reorder files across projects in a way unrelated to ‘shallower within the project’. To match the intent (‘top-level within a project’), compute depth relative to the owning project root (e.g., api.getPythonProject(uri)?.uri.fsPath) and count segments on the relative path (after normalization and trimming empty segments).

Copilot uses AI. Check for mistakes.
Comment on lines +205 to +233
findFilesStub.callsFake((pattern: string) => {
const workspacePath = Uri.file('/test/path/root').fsPath;
if (pattern === '**/*requirements*.txt') {
return Promise.resolve([
Uri.file(path.join(workspacePath, 'deep', 'nested', 'sub', 'requirements.txt')),
Uri.file(path.join(workspacePath, 'subdir', 'dev-requirements.txt')),
]);
} else if (pattern === '*requirements*.txt') {
return Promise.resolve([Uri.file(path.join(workspacePath, 'requirements.txt'))]);
} else if (pattern === '**/requirements/*.txt') {
return Promise.resolve([]);
} else if (pattern === '**/pyproject.toml') {
return Promise.resolve([]);
}
return Promise.resolve([]);
});

// Act
const workspacePath = Uri.file('/test/path/root').fsPath;
const projects = [{ name: 'workspace', uri: Uri.file(workspacePath) }];
const result = (await getProjectInstallable(mockApi as PythonEnvironmentApi, projects)).installables;

// Assert: root-level requirements.txt should come first
assert.strictEqual(result.length, 3);
const names = result.map((r) => r.name);
assert.strictEqual(names[0], 'requirements.txt', 'Root-level requirements.txt should be first');
assert.strictEqual(names[1], 'dev-requirements.txt', 'One-level-deep file should be second');
assert.strictEqual(names[2], 'requirements.txt', 'Deeply nested file should be last');
});
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

This test hard-codes a POSIX-style absolute path (/test/path/root), which can be brittle on Windows path handling. Also, asserting ordering via name is ambiguous when multiple entries share the same basename (requirements.txt). Consider deriving workspacePath from a platform-safe absolute base (e.g., temp dir), and asserting order using result.map(r => r.uri.fsPath) or path.relative(workspacePath, r.uri.fsPath) so the root vs deep requirements.txt are unambiguous.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature-request Request for new features or functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants