Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ ComponentDetection/src/Microsoft.ComponentDetection.Loader/Properties/launchSett
node_modules/
dist/
dist-nuget/
.nuget/

# Build results
[Dd]ebug/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,20 +274,46 @@ private bool IsApplication(string assemblyPath)

if (this.fileUtilityService.Exists(globalJsonPath))
{
sdkVersion = await this.RunDotNetVersionAsync(projectDirectory, cancellationToken);

if (string.IsNullOrWhiteSpace(sdkVersion))
// Read the version declared in global.json first
string? globalJsonVersion = null;
using (var globalJsonDoc = await JsonDocument.ParseAsync(this.fileUtilityService.MakeFileStream(globalJsonPath), cancellationToken: cancellationToken, options: this.jsonDocumentOptions).ConfigureAwait(false))
{
var globalJson = await JsonDocument.ParseAsync(this.fileUtilityService.MakeFileStream(globalJsonPath), cancellationToken: cancellationToken, options: this.jsonDocumentOptions).ConfigureAwait(false);
if (globalJson.RootElement.TryGetProperty("sdk", out var sdk))
if (globalJsonDoc.RootElement.TryGetProperty("sdk", out var sdk))
{
if (sdk.TryGetProperty("version", out var version))
{
sdkVersion = version.GetString();
globalJsonVersion = version.GetString();
}
}
}

// Try to get the version actually resolved by the SDK
var resolvedVersion = await this.RunDotNetVersionAsync(projectDirectory, cancellationToken);

if (!string.IsNullOrWhiteSpace(resolvedVersion))
{
sdkVersion = resolvedVersion;

// Only register against global.json when the resolved version matches what global.json declares.
// If there is a mismatch (e.g. roll-forward selected a newer SDK), the component should not be
// attributed to global.json because changing that file would not fix the reported version.
if (!string.IsNullOrWhiteSpace(globalJsonVersion) &&
!sdkVersion.Equals(globalJsonVersion, StringComparison.OrdinalIgnoreCase))
{
this.Logger.LogInformation(
"Resolved SDK version {ResolvedVersion} does not match global.json version {GlobalJsonVersion} in {GlobalJsonPath}. Not registering component against global.json.",
resolvedVersion,
globalJsonVersion,
globalJsonPath);
return sdkVersion;
}
Comment on lines +297 to +309
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

When global.json exists but doesn't specify a version (globalJsonVersion is null/empty), and dotnet --version succeeds (resolvedVersion is not null/empty), the condition at line 300 will be false due to the null check on globalJsonVersion. This causes the code to skip the mismatch check and fall through to line 317, where it will register the resolved version against global.json even though global.json didn't specify any SDK version.

To fix this, the version match check should only allow registration when globalJsonVersion is actually specified. Consider adding an additional condition to ensure globalJsonVersion is not null/empty before registering, or move the early return outside the mismatch condition.

Copilot uses AI. Check for mistakes.
}
else
{
// dotnet --version failed; fall back to the version declared in global.json
sdkVersion = globalJsonVersion;
}

if (!string.IsNullOrWhiteSpace(sdkVersion))
{
var globalJsonComponent = new DetectedComponent(new DotNetComponent(sdkVersion));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,11 @@ public async Task TestDotNetDetectorGlobalJsonWithoutVersion()
}

[TestMethod]
public async Task TestDotNetDetectorGlobalJsonRollForward_ReturnsSDKVersion()
public async Task TestDotNetDetectorGlobalJsonRollForward_DoesNotRegisterAgainstGlobalJson()
{
// When dotnet resolves a different SDK version than declared in global.json (roll-forward),
// the component should NOT be registered against global.json because the user would need to
// change their build environment, not global.json.
var projectPath = Path.Combine(RootDir, "path", "to", "project");
var projectAssets = ProjectAssets("projectName", "does-not-exist", projectPath, "net8.0");
var globalJson = GlobalJson("8.0.100");
Expand All @@ -383,11 +386,38 @@ public async Task TestDotNetDetectorGlobalJsonRollForward_ReturnsSDKVersion()
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);

var detectedComponents = componentRecorder.GetDetectedComponents();
detectedComponents.Should().HaveCount(2);
detectedComponents.Should().ContainSingle();

var discoveredComponents = detectedComponents.ToArray();
discoveredComponents.Where(component => component.Component.Id == "8.0.808 unknown unknown - DotNet").Should().ContainSingle();
discoveredComponents.Where(component => component.Component.Id == "8.0.808 net8.0 unknown - DotNet").Should().ContainSingle();
discoveredComponents.Where(component => component.Component.Id == "8.0.100 unknown unknown - DotNet").Should().BeEmpty();
discoveredComponents.Where(component => component.Component.Id == "8.0.808 unknown unknown - DotNet").Should().BeEmpty();
}

[TestMethod]
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Add a test case for when global.json exists without a version specification but dotnet --version succeeds. This scenario is currently not covered by tests. The expected behavior should be that the SDK version is returned but NOT registered against global.json (since global.json didn't specify a version).

Suggested change
[TestMethod]
[TestMethod]
public async Task TestDotNetDetectorGlobalJsonWithoutVersion_UsesDotNetVersionAndDoesNotRegisterAgainstGlobalJson()
{
// When global.json exists without an SDK version but dotnet --version succeeds,
// the SDK version should be inferred from dotnet --version but NOT registered
// against global.json (since global.json didn't specify a version).
var projectPath = Path.Combine(RootDir, "path", "to", "project");
var projectAssets = ProjectAssets("projectName", "does-not-exist", projectPath, "net8.0");
var globalJson = @"{ ""sdk"": { } }";
this.AddFile(projectPath, null);
this.AddFile(Path.Combine(RootDir, "path", "global.json"), globalJson);
this.SetCommandResult(0, "8.0.808");
var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("project.assets.json", projectAssets)
.ExecuteDetectorAsync();
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
var detectedComponents = componentRecorder.GetDetectedComponents();
detectedComponents.Should().ContainSingle();
var discoveredComponents = detectedComponents.ToArray();
discoveredComponents.Where(component => component.Component.Id == "8.0.808 net8.0 unknown - DotNet").Should().ContainSingle();
discoveredComponents.Where(component => component.Component.Id == "8.0.808 unknown unknown - DotNet").Should().BeEmpty();
}
[TestMethod]

Copilot uses AI. Check for mistakes.
public async Task TestDotNetDetectorGlobalJsonMatchingVersion_RegistersAgainstGlobalJson()
{
// When dotnet resolves the same SDK version as declared in global.json,
// the component SHOULD be registered against global.json.
var projectPath = Path.Combine(RootDir, "path", "to", "project");
var projectAssets = ProjectAssets("projectName", "does-not-exist", projectPath, "net8.0");
var globalJson = GlobalJson("8.0.100");
this.AddFile(projectPath, null);
this.AddFile(Path.Combine(RootDir, "path", "global.json"), globalJson);
this.SetCommandResult(0, "8.0.100");

var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("project.assets.json", projectAssets)
.ExecuteDetectorAsync();

scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);

var detectedComponents = componentRecorder.GetDetectedComponents();
detectedComponents.Should().HaveCount(2);

var discoveredComponents = detectedComponents.ToArray();
discoveredComponents.Where(component => component.Component.Id == "8.0.100 unknown unknown - DotNet").Should().ContainSingle();
discoveredComponents.Where(component => component.Component.Id == "8.0.100 net8.0 unknown - DotNet").Should().ContainSingle();
}

[TestMethod]
Expand Down
Loading