Solution.Parser 4.0.0-alpha.17

This is a prerelease version of Solution.Parser.
There is a newer version of this package available.
See the version list below for details.
dotnet add package Solution.Parser --version 4.0.0-alpha.17
                    
NuGet\Install-Package Solution.Parser -Version 4.0.0-alpha.17
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Solution.Parser" Version="4.0.0-alpha.17" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Solution.Parser" Version="4.0.0-alpha.17" />
                    
Directory.Packages.props
<PackageReference Include="Solution.Parser" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Solution.Parser --version 4.0.0-alpha.17
                    
#r "nuget: Solution.Parser, 4.0.0-alpha.17"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Solution.Parser@4.0.0-alpha.17
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Solution.Parser&version=4.0.0-alpha.17&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Solution.Parser&version=4.0.0-alpha.17&prerelease
                    
Install as a Cake Tool

Solution Parser

Packages

The library is split so that a consumer only pays for the part it uses. Solution.Parser still pulls everything, so an existing reference needs no change.

Package What it parses What it drags in
Solution.Parser.Core shared contracts, nothing on its own
Solution.Parser.CSharp C# files into syntax trees Roslyn
Solution.Parser.Xaml XAML markup nothing but Core
Solution.Parser.Project csproj, old and SDK style nothing but Core
Solution.Parser.Sln sln and slnx, and their projects MSBuild
Solution.Parser.Nuspec nuspec files and NuGet folders nothing but Core
Solution.Parser.AspNet controllers, routes, response types NuGet client libraries
Solution.Parser meta package, pulls all of the above all of the above

Solution.Parser.Project deliberately knows no language: ProjectFile.SourceFiles is a plain file list, and the typed views come from whichever language package you reference.

project.SourceFiles.CSharpFiles()   // needs Solution.Parser.CSharp
project.SourceFiles.XamlFiles()     // needs Solution.Parser.Xaml

That is what keeps XAML out of a C# only consumer and the other way round.

Solution formats

Both the classic .sln and the new xml based .slnx format are supported:

// classic
var solutionFileInfo = new SolutionFileName("MySolution.sln").FindSolutionFileReverseFrom(startUpDirectory);

// slnx
var solutionFileInfo = new SolutionFileName("MySolution.slnx").FindSolutionFileReverseFrom(startUpDirectory);

// either of both, '.slnx' wins when both files exist side by side
var solutionFileInfo = SolutionFileName.WithAnySolutionFormat("MySolution").FindSolutionFileReverseFrom(startUpDirectory);

SolutionFileInfo.IsSlnx tells which format was found. Everything behind Parse() is format agnostic.

Sample

 [TestClass]
 public abstract class MsTestBase
 {
     protected static IImmutableList<CSharpSyntaxTree> TestCode { get; private set; } = ImmutableList<CSharpSyntaxTree>.Empty;

     protected static IImmutableList<CSharpSyntaxTree> AllSyntaxTrees { get; private set; } = ImmutableList<CSharpSyntaxTree>.Empty;

     protected static IImmutableList<CSharpSyntaxTree> ProductiveCode { get; private set; } = ImmutableList<CSharpSyntaxTree>.Empty;

     protected static IImmutableList<CSharpSyntaxTree> ProductiveCodeToAnaylze { get; private set; } = ImmutableList<CSharpSyntaxTree>.Empty;

     protected static SolutionFile Solution { get; private set; } = null!;
      
     [AssemblyInitialize]
     public static void Init(TestContext _)
     {
         var sSolutionFileInfo = new SolutionFileName("MySolution.sln").FindSolutionFileReverseFrom(new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory));
         Throw.IfNull(sSolutionFileInfo);

         Solution = sSolutionFileInfo.Parse();

         ProductiveCode = Solution.ProductiveProjects.SelectMany(p => p.CSharpFileInfos)                                 
												     .Select(c => c.Parse())
												     .ToImmutableList();

         TestCode = Solution.UnitTestProjects.SelectMany(p => p.CSharpFileInfos)
											 .Select(c => c.Parse())
											 .ToImmutableList();

         AllSyntaxTrees = ProductiveCode.Concat(TestCode).ToImmutableList();        
     }
 }

CodeRule sample

[TestCategory("Coding-Rules")]
[TestCategory("Coding-Rules Records")]
[TestClass]
public class Records : MsTestBase // <-- MsTestBase is a base class that provides the ProductiveCode property
{
    [TestMethod]
    public void Record_Properties_Have_To_Be_Be_Immutable()
    {
        var mutableProperties = (from syntaxTree in ProductiveCode
                                 from @record in syntaxTree.Records
                                 from property in @record.Properties
                                 where property.IsReadOnly.IsFalse()
                                 select new
                                 {
                                     Error = $@"

Please do not use mutable properties
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
FullName:  {@record.FullQualifiedName}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Property:  {property.Type} {property.Name}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Should be: {property.Type} {{get; init;}}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
"
                                    }).ToImmutableList();

        Assert.IsTrue(mutableProperties.IsEmpty(),
            $"Properties must be immutable. Findings: '{mutableProperties.Count}':{Environment.NewLine}{mutableProperties.Select(error => error.Error).Flatten(Environment.NewLine)}{Environment.NewLine}");
        }

What the model gives you

Every declaration carries a Location, so a finding points at the source instead of only naming the type. Location.ToString() renders path(line,column), which test runners and IDEs turn into a link:

var findings = from tree in ProductiveCode
               from type in tree.AllTypes()
               from property in type.Properties
               where property.IsReadOnly.IsFalse()
               select $"{property.Location}: {type.FullQualifiedName}.{property.Name} is mutable";

// D:\repo\src\Person.cs(12,5): My.Sample.Person.Name is mutable

Beyond that, a declaration reports its Accessibility (including the default that applies when no modifier is written), its Documentation (the XML comment), its Modifiers (now complete, including sealed, virtual, override, new, async and readonly), its TypeParameters with their constraints, and its Attributes split into positional and named arguments. HasAttribute("Obsolete") matches [Obsolete], [ObsoleteAttribute] and [System.ObsoleteAttribute] alike.

Types also report Indexers, Operators, Delegates and Finalizers, and TypeKind tells a record from a record struct. tree.Diagnostics surfaces what Roslyn reported while parsing, so a rule can assert that no file has a syntax error rather than silently trusting an incomplete model.

Nesting

A member belongs to the type that declares it. type.Methods holds the methods of that type, and the methods of a nested type belong to the nested type:

tree.Types              // the types declared at file level
type.NestedTypes        // the types declared directly inside this type
type.Methods            // the methods declared directly in this type

To walk everything, ask for it:

tree.AllTypes()               // every type of the file, nested ones included
tree.AllMethods()             // every method of the file
type.DescendantTypes()        // every type declared inside this one, at any depth
type.AllMethods()             // this type and every type nested in it
ProductiveCode.AllTypes()     // the same across a whole solution

Predicates that read the way a rule means: IsPublic(), IsStatic(), IsSealed(), IsAbstract(), IsPartial(), IsOverride(), IsVirtual(), HasAttribute(name), Implements(name), InheritsFrom(name).

XAML

.xaml files parse into the same shape of model, so a rule can reach across markup and code behind:

var tree = new XAMLFileInfo(@"D:\repo\src\MainWindow.xaml").Parse();

tree.FullQualifiedName          // "My.Sample.MainWindow", the x:Class
tree.Root                       // a Window, UserControl, Page, Application,
                                // ResourceDictionaryRoot or ControlRoot
tree.Diagnostics                // what could not be read, instead of a lost file

For markup that is not on disk, for example in a test:

var tree = xamlContent.ParseXaml(@"D:\repo\src\MainWindow.xaml");

Children holds what an element declares itself, and a property element such as <Grid.RowDefinitions> is a property of the Grid rather than a child of it, which is what XAML means by it:

element.Children                // the elements written directly inside this one
element.Properties              // attributes, attached properties and property elements
element.Resources               // what <X.Resources> declares
element["Grid.Row"]             // an attached property, by the name it is written with
element.Content                 // the text of <Button>Click me</Button>
element.Parent                  // the element this one is written in
element.Location                // path(line,column), clickable in a test runner

To walk everything, ask for it, the same way as on the C# side:

tree.AllElements()              // every element of the file
tree.AllStyles()                // every Style, however deep in the resource dictionaries
tree.AllTemplates()             // DataTemplate, ControlTemplate, ItemsPanelTemplate, …
tree.AllBindings()              // every Binding, nested ones included
tree.AllMarkupExtensions()      // every {…}, nested ones included
tree.FindByName("Save")         // by x:Name
tree.FindByKey("OkButton")      // by x:Key
tree.OfTypeName("Button")       // every element written as <Button>
element.Ancestors()             // up the tree, nearest first

Values keep the shape they were written in, so a rule reads the part it cares about rather than the raw string:

var binding = textBlock["Text"]?.PropertyValue as Binding;

binding.Path?.ValueText                          // "Total"
(binding.Converter as StaticResource)?.ResourceKey   // "MoneyConverter"
binding.RelativeSource?.AncestorType             // "Window"
binding.StringFormat?.ValueText                  // "{0:#,##0.00} EUR"

Binding, MultiBinding, StaticResource, DynamicResource, TemplateBinding, RelativeSource, XTypeMarkupExtension, XStaticMarkupExtension and NullExtension are all MarkupExtension, and an extension the parser has no model for keeps its name and arguments instead of being reduced to text. A value is markup only when it starts with an unescaped { followed by a name, so a pack URI, a caption with a colon and a {}{0:N2} escape are all plain text.

Style, Setter, Trigger, Template, ResourceDictionaryElement and Control are siblings below ElementBase with an ElementKind; Window, UserControl, Page, Application, ResourceDictionaryRoot and ControlRoot are siblings below Root with a RootKind.

Sample application

src/SampleApp.Wpf is a small but real WPF application: a window, a user control, a theme dictionary, view models and bindings. It is a fixture rather than a demo. Because it is a real UseWPF project, markup that WPF would reject cannot get in, so the parser tests always run against XAML that actually compiles. Nothing in the library references it.

src/SampleApp.Wpf.Test is what a consumer of the packages looks like: it references Solution.Parser.Xaml, .CSharp and .Sln, finds the application through the solution, and runs code rules over it. Two of them are worth reading as examples:

  • BindingRule - every {Binding Path=X} has to name a property that exists on the view model the view declares through d:DataContext. A typo there compiles, renders nothing and is caught by no compiler.
  • ResourceRule - every {StaticResource Key} used anywhere has to be declared somewhere, across files.

Each rule is also run against a deliberately broken view parsed from memory, because a rule that never fails proves nothing.

Migrating from 5.x

Before Now
type.Methods returned nested types' methods too type.AllMethods()
tree.Classes contained nested classes tree.AllTypes(), filtered by kind
type.NestedClasses contained grandchildren type.DescendantTypes()
Record : Class : Interface all siblings below TypeDeclaration, with Kind
class.Interfaces (was a duplicate of NestedInterfaces) BaseTypes, or Implements(name)
method.SyntaxTree returned the whole file it is now the method's own source; the file is tree.SyntaxTree
new Class(...) positional object initializer, new Class { Name = ..., ... }

Classes, Records, Interfaces, Structs and Enums still exist on the tree, and NestedClasses, NestedStructs, NestedInterfaces and NestedEnums still exist on a type; they are now filtered views over Types and NestedTypes. Method.MethodValue and Method.MethodBody are kept as the previous names for SyntaxTree and Body.

Packaging and projects

Before Now
one Solution.Parser package seven packages plus the meta package; see the table at the top
project.CSharpFileInfos project.SourceFiles.CSharpFiles()
project.XAMLFileInfos project.SourceFiles.XamlFiles()
namespace Solution.Parser.Common Solution.Parser.Core
namespace Solution.Parser.Solution Solution.Parser.Sln
namespace Solution.Parser.XAML Solution.Parser.Xaml
XAMLFileInfo XamlFileInfo

XAML

Before Now
element.Controls held every descendant, each duplicated element.Children, or tree.AllElements()
element.Parent was always null it points at the element this one is written in
element.Styles was always empty tree.AllStyles(), and <Style> is now a Style
element.DataTemplates tree.AllTemplates() / tree.AllDataTemplates()
element.DataContext was set on every element null unless the element sets one
element.LineNumber element.Location, with line, column and path
<Grid.RowDefinitions> was a control named Grid.RowDefinitions a PropertyElement in Properties
attached property named RowProperty Name is Row, FullQualifiedName is Grid.Row, DependencyPropertyName is RowProperty
x:Name and Name were both called Name Property.Prefix and Property.IsXamlDirective tell them apart
Window : UserControl, DynamicResource : StaticResource siblings, with RootKind and a shared ResourceReference
DataTemplate, ResourceDictionary, ResourceDictionaryControl Template, ResourceDictionaryRoot, ResourceDictionaryElement
new Control(...) positional object initializer, new Control { TypeName = ..., ... }
an unreadable value threw and lost the file tree.Diagnostics
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
5.0.0-alpha.8 24 9/22/2026
4.0.0 41,943 4/8/2026
4.0.0-alpha.17 46 9/15/2026
4.0.0-alpha.12 249 3/24/2026
3.0.8 13,860 1/9/2026
3.0.7 4,303 11/23/2025
3.0.6 268 11/21/2025
3.0.5 3,742 10/29/2025
3.0.4 54,644 9/8/2025
3.0.3 1,828 9/7/2025
3.0.2 16,054 2/4/2025
3.0.1 10,768 1/24/2025
3.0.0 13,663 11/20/2024
2.0.18 110,014 11/20/2024
2.0.17 13,253 10/17/2024
2.0.16 49,279 9/23/2024
2.0.15 5,837 9/6/2024
2.0.13 16,050 6/12/2024
2.0.12 5,427 5/26/2024
2.0.11 6,360 5/12/2024
Loading failed

C# breaking: member lists now hold only the members a type declares itself; use AllTypes(), DescendantTypes() and AllMethods() for the recursive walk. Class, Struct, Record, Interface and Enum are now siblings below TypeDeclaration with a TypeKind. Declarations carry Location, Accessibility, XML documentation and type parameters. Delegates, indexers, operators and finalizers are parsed. C# fixes: fully qualified names under a file scoped or nested namespace, SyntaxTree returning the whole file, only the first variable of a shared field declaration, missing modifiers such as sealed, virtual, override, new and async, IsNullable on a nullable type argument, IsReadOnly decided by text search, and line splitting on the platform line ending. Namespaces: Solution.Parser.XAML became Solution.Parser.Xaml and XAMLFileInfo became XamlFileInfo; Solution.Parser.Solution became Solution.Parser.Sln and Solution.Parser.Common became Solution.Parser.Core. ProjectFile.CSharpFileInfos and ProjectFile.XAMLFileInfos became the extension methods project.CSharpFiles() and project.XamlFiles(); the project directory is now enumerated once instead of once per file type. XAML breaking: the element tree is now built from direct children, so Controls became Children and holds only what an element declares itself; use AllElements(), DescendantElements(), AllStyles(), AllBindings() and FindByName() for the recursive walk. Property elements such as Grid.RowDefinitions are properties rather than children. Window, UserControl, Page, Application and ResourceDictionary are siblings below Root with a RootKind; Style, Setter, Trigger and Template are siblings below ElementBase with an ElementKind. Models are records with init properties, LineNumber became Location, and a file that cannot be read fully reports XamlSyntaxTree.Diagnostics instead of throwing. XAML fixes: nesting was flattened and duplicated and the parse was exponential in the nesting depth, Parent was always null, Styles was always empty, DataContext was never null, a plain value containing a colon and a StringFormat containing a comma each killed the whole file, the {} escape was mistaken for markup, x:Name was indistinguishable from Name, an attached property was named RowProperty so element["Grid.Row"] never matched, x:Static returned an x:Type, RelativeSource folded its ancestor type into its mode, and element text content was dropped. Support for the new xml based slnx solution format.