diff --git a/mcp/CWM.RoslynNavigator/src/CWM.RoslynNavigator.csproj b/mcp/CWM.RoslynNavigator/src/CWM.RoslynNavigator.csproj
index 68f5663..80c8834 100644
--- a/mcp/CWM.RoslynNavigator/src/CWM.RoslynNavigator.csproj
+++ b/mcp/CWM.RoslynNavigator/src/CWM.RoslynNavigator.csproj
@@ -15,7 +15,7 @@
CWM.RoslynNavigator
- 0.7.2
+ 0.7.3
Mukesh Murugan
codewithmukesh
CWM.RoslynNavigator
@@ -40,6 +40,10 @@
+
+
+
+
diff --git a/mcp/CWM.RoslynNavigator/src/Tools/GetProjectGraphTool.cs b/mcp/CWM.RoslynNavigator/src/Tools/GetProjectGraphTool.cs
index 45ec3ba..d298cd3 100644
--- a/mcp/CWM.RoslynNavigator/src/Tools/GetProjectGraphTool.cs
+++ b/mcp/CWM.RoslynNavigator/src/Tools/GetProjectGraphTool.cs
@@ -1,5 +1,6 @@
using System.ComponentModel;
using System.Text.Json;
+using System.Text.RegularExpressions;
using System.Xml.Linq;
using CWM.RoslynNavigator.Responses;
using ModelContextProtocol.Server;
@@ -67,18 +68,48 @@ private static string DetectTargetFramework(Microsoft.CodeAnalysis.Project proje
}
// Strategy 2: Check preprocessor symbols (e.g., NET10_0, NET8_0)
- var preprocessorSymbol = project.ParseOptions?.PreprocessorSymbolNames
- .Where(s => s.StartsWith("NET"))
- .OrderByDescending(s => s.Length)
- .FirstOrDefault();
-
- if (preprocessorSymbol is not null)
+ if (project.ParseOptions?.PreprocessorSymbolNames is { } symbols)
{
- // Convert "NET10_0" to "net10.0", "NET8_0_OR_GREATER" stays as-is but we prefer exact match
- var exact = preprocessorSymbol.Replace("_OR_GREATER", "");
- return exact.ToLowerInvariant().Replace('_', '.');
+ var detected = DetectFromPreprocessorSymbols(symbols);
+ if (detected is not null)
+ return detected;
}
return "unknown";
}
+
+ // Exact TFM symbols: NET10_0, NETSTANDARD2_0, NETCOREAPP3_1. Anchored so compat
+ // symbols like NETCOREAPP1_0_OR_GREATER (issue #19) never match.
+ private static readonly Regex ExactTfmSymbol =
+ new(@"^(NETSTANDARD|NETCOREAPP|NET)(\d+)_(\d+)$", RegexOptions.Compiled);
+
+ // .NET Framework symbols have no underscore: NET48, NET472
+ private static readonly Regex FrameworkTfmSymbol = new(@"^NET(\d{2,3})$", RegexOptions.Compiled);
+
+ internal static string? DetectFromPreprocessorSymbols(IEnumerable symbols)
+ {
+ var symbolList = symbols.ToList();
+
+ // A compilation defines exactly one exact TFM symbol alongside many *_OR_GREATER
+ // compat symbols. Only the exact one names the real target framework.
+ var exact = symbolList
+ .Select(s => ExactTfmSymbol.Match(s))
+ .Where(m => m.Success)
+ .Select(m => (Prefix: m.Groups[1].Value, Major: int.Parse(m.Groups[2].Value), Minor: int.Parse(m.Groups[3].Value)))
+ .OrderByDescending(t => t.Major)
+ .ThenByDescending(t => t.Minor)
+ .Select(t => (string?)$"{t.Prefix.ToLowerInvariant()}{t.Major}.{t.Minor}")
+ .FirstOrDefault();
+
+ if (exact is not null)
+ return exact;
+
+ return symbolList
+ .Select(s => FrameworkTfmSymbol.Match(s))
+ .Where(m => m.Success)
+ .Select(m => m.Groups[1].Value)
+ .OrderByDescending(v => int.Parse(v.PadRight(3, '0')))
+ .Select(v => (string?)$"net{v}")
+ .FirstOrDefault();
+ }
}
diff --git a/mcp/CWM.RoslynNavigator/tests/Tools/DetectTargetFrameworkTests.cs b/mcp/CWM.RoslynNavigator/tests/Tools/DetectTargetFrameworkTests.cs
new file mode 100644
index 0000000..061db5c
--- /dev/null
+++ b/mcp/CWM.RoslynNavigator/tests/Tools/DetectTargetFrameworkTests.cs
@@ -0,0 +1,90 @@
+using CWM.RoslynNavigator.Tools;
+
+namespace CWM.RoslynNavigator.Tests.Tools;
+
+///
+/// Regression tests for issue #19: net10.0 projects were reported as "netcoreapp1.0"
+/// because the longest NET-prefixed preprocessor symbol (NETCOREAPP1_0_OR_GREATER)
+/// won over the exact TFM symbol (NET10_0).
+///
+public class DetectTargetFrameworkTests
+{
+ [Fact]
+ public void DetectFromPreprocessorSymbols_Net10_ReturnsNet10()
+ {
+ // Real symbol set the SDK defines for a net10.0 compilation
+ string[] symbols =
+ [
+ "TRACE", "DEBUG", "NET", "NET10_0", "NETCOREAPP",
+ "NET5_0_OR_GREATER", "NET6_0_OR_GREATER", "NET7_0_OR_GREATER",
+ "NET8_0_OR_GREATER", "NET9_0_OR_GREATER", "NET10_0_OR_GREATER",
+ "NETCOREAPP1_0_OR_GREATER", "NETCOREAPP1_1_OR_GREATER",
+ "NETCOREAPP2_0_OR_GREATER", "NETCOREAPP2_1_OR_GREATER",
+ "NETCOREAPP2_2_OR_GREATER", "NETCOREAPP3_0_OR_GREATER",
+ "NETCOREAPP3_1_OR_GREATER",
+ ];
+
+ Assert.Equal("net10.0", GetProjectGraphTool.DetectFromPreprocessorSymbols(symbols));
+ }
+
+ [Fact]
+ public void DetectFromPreprocessorSymbols_Net8_ReturnsNet8()
+ {
+ string[] symbols =
+ [
+ "TRACE", "NET", "NET8_0", "NETCOREAPP",
+ "NET5_0_OR_GREATER", "NET6_0_OR_GREATER", "NET7_0_OR_GREATER", "NET8_0_OR_GREATER",
+ "NETCOREAPP1_0_OR_GREATER", "NETCOREAPP2_0_OR_GREATER",
+ "NETCOREAPP3_0_OR_GREATER", "NETCOREAPP3_1_OR_GREATER",
+ ];
+
+ Assert.Equal("net8.0", GetProjectGraphTool.DetectFromPreprocessorSymbols(symbols));
+ }
+
+ [Fact]
+ public void DetectFromPreprocessorSymbols_NetStandard20_ReturnsNetStandard20()
+ {
+ string[] symbols =
+ [
+ "TRACE", "NETSTANDARD", "NETSTANDARD2_0",
+ "NETSTANDARD1_0_OR_GREATER", "NETSTANDARD1_1_OR_GREATER",
+ "NETSTANDARD1_6_OR_GREATER", "NETSTANDARD2_0_OR_GREATER",
+ ];
+
+ Assert.Equal("netstandard2.0", GetProjectGraphTool.DetectFromPreprocessorSymbols(symbols));
+ }
+
+ [Fact]
+ public void DetectFromPreprocessorSymbols_NetCoreApp31_ReturnsNetCoreApp31()
+ {
+ string[] symbols =
+ [
+ "TRACE", "NETCOREAPP", "NETCOREAPP3_1",
+ "NETCOREAPP1_0_OR_GREATER", "NETCOREAPP2_0_OR_GREATER",
+ "NETCOREAPP3_0_OR_GREATER", "NETCOREAPP3_1_OR_GREATER",
+ ];
+
+ Assert.Equal("netcoreapp3.1", GetProjectGraphTool.DetectFromPreprocessorSymbols(symbols));
+ }
+
+ [Fact]
+ public void DetectFromPreprocessorSymbols_NetFramework48_ReturnsNet48()
+ {
+ string[] symbols =
+ [
+ "TRACE", "NETFRAMEWORK", "NET48",
+ "NET20_OR_GREATER", "NET35_OR_GREATER", "NET40_OR_GREATER",
+ "NET45_OR_GREATER", "NET472_OR_GREATER", "NET48_OR_GREATER",
+ ];
+
+ Assert.Equal("net48", GetProjectGraphTool.DetectFromPreprocessorSymbols(symbols));
+ }
+
+ [Fact]
+ public void DetectFromPreprocessorSymbols_NoTfmSymbols_ReturnsNull()
+ {
+ string[] symbols = ["TRACE", "DEBUG", "MY_CUSTOM_FLAG"];
+
+ Assert.Null(GetProjectGraphTool.DetectFromPreprocessorSymbols(symbols));
+ }
+}