How can I get all the classes from a Roslyn compilation?
var sln = Path.Combine(path, "xxx.sln");
var workspace = MSBuildWorkspace.Create();
var solution = await workspace.OpenSolutionAsync(sln);
Project project = solution.Projects.First(x => x.Name == "bbb");
var compilation = await project.GetCompilationAsync();
This is how I visit all the classes in my solution.
class ClassVirtualizationVisitor : CSharpSyntaxRewriter
{
List<string> classes = new List<String>();
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
{
node = (ClassDeclarationSyntax) base.VisitClassDeclaration(node);
string className = node.Identifier.ValueText;
classes.Add(className); // save your visited classes
return node;
}
}
Now use the visited classes:
var classVisitor = new ClassVirtualizationVisitor();
classVisitor.Visit(semanticModel.SyntaxTree.GetRoot());
var classes = classVisitor.classes; // list of classes in your solution
This now is my working Code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;
using System.Threading.Tasks;
namespace Kardex.LC3xx.CreateApiDokumentation
{
//Patch to work with VS2013
// https://support.microsoft.com/en-us/kb/2971005
class Program
{
private static void Main(string[] args)
{
Run(args).Wait();
Console.ReadLine();
}
private async static Task Run(string[] args)
{
var path = Path.GetDirectoryName(typeof (Program).Assembly.Location);
var sln = Path.Combine(path, "xxx.sln");
var workspace = MSBuildWorkspace.Create();
var solution = await workspace.OpenSolutionAsync(sln);
Project project = solution.Projects.First(x => x.Name == "bbbb");
var compilation = await project.GetCompilationAsync();
foreach (var #class in compilation.GlobalNamespace.GetNamespaceMembers().SelectMany(x=>x.GetMembers()))
{
Console.WriteLine(#class.Name);
Console.WriteLine(#class.ContainingNamespace.Name);
}
var classVisitor = new ClassVirtualizationVisitor();
foreach (var syntaxTree in compilation.SyntaxTrees)
{
classVisitor.Visit(syntaxTree.GetRoot());
}
var classes = classVisitor.Classes;
}
class ClassVirtualizationVisitor : CSharpSyntaxRewriter
{
public ClassVirtualizationVisitor()
{
Classes = new List<ClassDeclarationSyntax>();
}
public List<ClassDeclarationSyntax> Classes { get; set; }
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
{
node = (ClassDeclarationSyntax)base.VisitClassDeclaration(node);
Classes.Add(node); // save your visited classes
return node;
}
}
}
}
Create a SymbolVisitor that overrides VisitNamedType to process each type (which may not be a class).
Then, pass it to compilation.Assembly.Accept().
Related
I am trying to make a source generator for mapping columns from the google bigquery api client to class properties. I'm having trouble getting custom column names from a ColumnAttribute on the properties. ConstructorArguments is always empty and columnAttribute.AttributeClass in this sample is always an ErrorTypeSymbol. If I try to load that type using compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.Schema.ColumnAttribute") the result is always null.
using System.Collections.Immutable;
using System.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace BigQueryMapping;
[Generator]
public class BigQueryMapperGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// add marker attribute
context.RegisterPostInitializationOutput(ctx =>
ctx.AddSource("BigQueryMappedAttribute.g.cs", SourceText.From(Attribute, Encoding.UTF8)));
// add static interface
context.RegisterPostInitializationOutput(ctx =>
ctx.AddSource("BigQueryMappedInterface.g.cs", SourceText.From(Interface, Encoding.UTF8)));
// get classes
IncrementalValuesProvider<ClassDeclarationSyntax> classDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (s, _) => s is ClassDeclarationSyntax c && c.AttributeLists.Any(),
transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx)
)
.Where(static m => m is not null)!;
IncrementalValueProvider<(Compilation Compilation, ImmutableArray<ClassDeclarationSyntax>Syntaxes)>
compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
context.RegisterSourceOutput(compilationAndClasses,
static (spc, source) => Execute(source.Compilation, source.Syntaxes, spc));
static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
{
var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
foreach (var attributeListSyntax in classDeclarationSyntax.AttributeLists)
{
foreach (var attributeSyntax in attributeListSyntax.Attributes)
{
var fullName = context.SemanticModel.GetTypeInfo(attributeSyntax).Type?.ToDisplayString();
if (fullName == "BigQueryMapping.BigQueryMappedAttribute")
return classDeclarationSyntax;
}
}
return null;
}
static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax> classes,
SourceProductionContext context)
{
try
{
if (classes.IsDefaultOrEmpty)
return;
var distinctClasses = classes.Distinct();
var classesToGenerate = GetTypesToGenerate(compilation, distinctClasses, context.CancellationToken);
foreach (var classToGenerate in classesToGenerate)
{
var result = GeneratePartialClass(classToGenerate);
context.AddSource($"{classToGenerate.RowClass.Name}.g.cs", SourceText.From(result, Encoding.UTF8));
}
}
catch (Exception e)
{
var descriptor = new DiagnosticDescriptor(id: "BQD001",
title: "Error creating bigquery mapper",
messageFormat: "{0} {1}",
category: "BigQueryMapperGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
context.ReportDiagnostic(Diagnostic.Create(descriptor, null, e.Message, e.StackTrace));
}
}
}
static IEnumerable<ClassToGenerate> GetTypesToGenerate(Compilation compilation,
IEnumerable<ClassDeclarationSyntax> classes,
CancellationToken ct)
{
var columnAttributeSymbol =
compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.Schema.ColumnAttribute");
foreach (var #class in classes)
{
Debug.WriteLine($"Checking class {#class}");
ct.ThrowIfCancellationRequested();
var semanticModel = compilation.GetSemanticModel(#class.SyntaxTree);
if (semanticModel.GetDeclaredSymbol(#class) is not INamedTypeSymbol classSymbol)
continue;
var info = new ClassToGenerate(classSymbol, new());
foreach (var member in classSymbol.GetMembers())
{
if (member is IPropertySymbol propertySymbol)
{
if (propertySymbol.DeclaredAccessibility == Accessibility.Public)
{
if (propertySymbol.SetMethod is not null)
{
var columnName = propertySymbol.Name;
var columnAttribute = propertySymbol.GetAttributes().FirstOrDefault(a =>
a.AttributeClass!.ToDisplayString() == "Column");
if (columnAttribute is not null)
{
if (!columnAttribute.ConstructorArguments.IsDefaultOrEmpty)
{
var nameArg = columnAttribute.ConstructorArguments.First();
if (nameArg.Value is string name)
{
columnName = name;
}
}
}
info.Properties.Add((columnName, propertySymbol));
}
}
}
}
yield return info;
}
}
static string GeneratePartialClass(ClassToGenerate c)
{
var sb = new StringBuilder();
sb.Append($#"// <auto-generated/>
namespace {c.RowClass.ContainingNamespace.ToDisplayString()}
{{
public partial class {c.RowClass.Name} : BigQueryMapping.IBigQueryGenerated<{c.RowClass.Name}>
{{
public static {c.RowClass.Name} FromBigQueryRow(Google.Cloud.BigQuery.V2.BigQueryRow row)
{{
return new {c.RowClass.Name}
{{");
foreach (var (columnName, property) in c.Properties)
{
// would like to check if key exists but don't see any sort of ContainsKey implemented on BigQueryRow
var tempName = $"___{property.Name}";
var basePropertyType = property.Type.WithNullableAnnotation(NullableAnnotation.None).ToDisplayString();
if (basePropertyType.EndsWith("?"))
{
basePropertyType = basePropertyType.Substring(default, basePropertyType.Length - 1);
}
sb.Append($#"
{property.Name} = row[""{columnName}""] is {basePropertyType} {tempName} ? {tempName} : default,");
}
sb.Append($#"
}};
}}
}}
}}");
return sb.ToString();
}
private record struct ClassToGenerate(INamedTypeSymbol RowClass,
List<(string ColumnName, IPropertySymbol Property)> Properties);
public const string Attribute = /* lang=csharp */ #"// <auto-generated/>
namespace BigQueryMapping {
[System.AttributeUsage(System.AttributeTargets.Class)]
public class BigQueryMappedAttribute : System.Attribute
{
}
}";
public const string Interface = /* lang=csharp */ #"// <auto-generated/>
namespace BigQueryMapping {
public interface IBigQueryGenerated<TRow> {
static TRow FromBigQueryRow(Google.Cloud.BigQuery.V2.BigQueryRow row) => throw new System.NotImplementedException();
}
}";
}
I have tried this with both System.ComponentModel.DataAnnotations.Schema.ColumnAttribute and a custom attribute injected via context.RegisterPostInitializationOutput to similar results. I have also tried rewriting this to use ISourceGenerator instead of IIncrementalGenerator and gotten the same behavior. Am wondering what I need to do to get columnAttribute loading correctly.
Thanks for any help in advance
It's hard to psychic debug the code but this does jump out:
var columnAttribute = propertySymbol.GetAttributes().FirstOrDefault(a =>
a.AttributeClass!.ToDisplayString() == "Column");
I'd guess the class here would be the fully qualified name. You already fetched the ColumnAttribute type earlier, so what'd be even better is to compare the AttributeClass to that type rather than doing string checks like this.
As a semi-related comment, if you're looking for types/members that are annotated with a specific attribute, rather than doing it yourself we have SyntaxValueProvider.ForAttributeWithMetadataName which is pretty heavily optimized to reduce the performance impact on your Visual Studio. It requires 17.3 or higher, but as long as you're OK with that it'll generally help performance.
When I'm getting IInstanceReferenceExpression operation for type Instance() => this; instance kind is Explicit, but I expect that kind would be This. Am I missing something or this is a bug in Roslyn?
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Semantics;
using System;
using System.Linq;
internal static class so39495857
{
private static void Main()
{
var tree = CSharpSyntaxTree.ParseText(#"
class c
{
c Instance() => this;
static void Main() {}
}
");
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var compilation = CSharpCompilation.Create(null, new[] { tree }, new[] { mscorlib });
var model = compilation.GetSemanticModel(tree);
var node = tree.GetRoot().DescendantNodes().OfType<ArrowExpressionClauseSyntax>().First();
var operation = model.GetOperation(node);
var block = (IBlockStatement)operation;
var #return = (IReturnStatement)block.Statements.First();
var #this = (IInstanceReferenceExpression)#return.ReturnedValue;
Console.WriteLine(#this.InstanceReferenceKind);
}
}
Project on github - https://github.com/isanych/so-39495857
That's what Explicit means.
ThisClass is only for VB's MyClass keyword.
I need to query and get the test results trx file from TFS 2013 so that I can modify that and use it for report generation. I couldn't find any method in IBuildServer that allows me to query the trx file. Is it posible to read the file using BuildDetail.DropLocation where I have the string that tells me the location of trx file. I have written few lines as below
TfsSerice = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(tfsUri));
TfsSerice.EnsureAuthenticated();
var buildServer = (IBuildServer)TfsSerice.GetService(typeof(IBuildServer));
BuildDetail = buildServer.QueryBuilds("MyUserLibrary").FirstOrDefault();
You can use code below to read .trx test result file:
TfsTeamProjectCollection tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri("http://tfsservername:8080/tfs/DefaultCollection"));
ITestManagementTeamProject project = tfs.GetService<ITestManagementService>().GetTeamProject("teamprojectname");
foreach (ITestRun tRun in project.TestRuns.ByBuild(new Uri(("vstfs:///Build/Build/531"))))
{
foreach (ITestCaseResult tr in tRun.QueryResults())
{
Console.WriteLine(tr.TestCaseTitle.ToString() + ":" + tr.Outcome.ToString());
}
}
Be note that, you need to choose to replace vstfs:///Build/Build/531 with your own buildUri. To get it, use the following code:
IBuildServer buildServer = (IBuildServer)tfs.GetService(typeof(IBuildServer));
var buildDefinitions = buildServer.QueryBuildDefinitions("TeamProject");
foreach (var buildDefinition in buildDefinitions)
{
if (buildDefinition.Name == "BuildDefinitionName")
{
var builds = buildDefinition.QueryBuilds();
foreach (var build in builds)
{
Console.WriteLine(build.Uri);
}
}
Figured that out. I added a TFS service wrapper as below which grabs the latest build's TRX file. Below is the code, Enjoy!!!!
using System.Linq;
using System.Net;
using System.Xml;
using System.Xml.Linq;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.TestManagement.Client;
using System.Configuration;
using System;
namespace TfsService
{
public class TfsServiceWrapper
{
public TfsTeamProjectCollection TeamProjectCollection { get; private set; }
public string TeamProject { get; private set; }
public string BuildName { get; private set; }
public Uri TfsUri { get; private set; }
public TfsServiceWrapper()
{
TfsUri = new Uri(ConfigurationManager.AppSettings["tfsUri"]);
TeamProject = ConfigurationManager.AppSettings["teamProject"];
BuildName = ConfigurationManager.AppSettings["buildName"];
ConnectToTeamProjectCollection();
}
public TfsServiceWrapper(Uri tfsUri, string teamProject, string buildName)
{
TfsUri = tfsUri;
TeamProject = teamProject;
BuildName = buildName;
ConnectToTeamProjectCollection();
}
private void ConnectToTeamProjectCollection()
{
TeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(TfsUri);
TeamProjectCollection.EnsureAuthenticated();
}
public IBuildDetail LatestBuildDetail
{
get
{
var spec = BuildServer.CreateBuildDetailSpec(TeamProject, BuildName);
spec.MaxBuildsPerDefinition = 1;
spec.QueryOrder = BuildQueryOrder.FinishTimeDescending;
return BuildServer.QueryBuilds(spec).Builds.FirstOrDefault();
}
}
public IBuildServer BuildServer
{
get
{
return (IBuildServer)TeamProjectCollection.GetService(typeof(IBuildServer));
}
}
public ITestManagementService TestManagementService
{
get
{
return (ITestManagementService)TeamProjectCollection.GetService(typeof(ITestManagementService));
}
}
public XDocument LatestTestResultFile
{
get
{
var latestRun = TestManagementService.GetTeamProject(TeamProject).TestRuns.ByBuild(LatestBuildDetail.Uri).First(run => run.QueryResults().Any());
var resolver = new XmlUrlResolver {Credentials = CredentialCache.DefaultCredentials};
var settings = new XmlReaderSettings {XmlResolver = resolver};
var reader = XmlReader.Create(latestRun.Attachments[0].Uri.ToString(), settings);
return XDocument.Load(reader);
}
}
}
}
i try to use CompileAssemblyFromSource to change 1 value at my main class.
But when i compile i get error "Could not load file or assembly or one of its dependencies" and this only happens when i try change static value of other class. But if i return some output or wrote anything at Console from this FooClass than all work's fine. But how can i change value of other class?
using System;
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;
namespace stringToCode
{
class Program
{
public static int q = 0;
static void Main(string[] args)
{
string source = "namespace stringToCode { public class FooClass { public void Execute() { Program.q = 1; } } }";
Console.WriteLine("q=" + q);
using (var foo = new CSharpCodeProvider())
{
var parameters = new CompilerParameters();
parameters.GenerateInMemory = true;
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
string location = assembly.Location;
if (!String.IsNullOrEmpty(location))
{
parameters.ReferencedAssemblies.Add(location);
}
}
catch (NotSupportedException)
{}
}
var res = foo.CompileAssemblyFromSource(parameters ,source);
var type = res.CompiledAssembly.GetType("FooClass"); //<- here i has error
var obj = Activator.CreateInstance(type);
var output = type.GetMethod("Execute").Invoke(obj, new object[] { });
Console.WriteLine("q=" + q);
Console.ReadLine();
}
}
}
}
You can't find the type because you have compilation error in your code.You can't access the classes in your current code in this manner. You should at least reference the current assembly in your in-memory assembly.
UPDATE
You have two issues in your code. First, you have to make the class Program public. Then you should specify the full name of type in GetType method.
This code works fine:
using System;
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;
namespace stringToCode
{
public class Program
{
public static int q = 0;
static void Main(string[] args)
{
string source = "namespace stringToCode { public class FooClass { public void Execute() { Program.q = 1; } } }";
Console.WriteLine("q=" + q);
using (var foo = new CSharpCodeProvider())
{
var parameters = new CompilerParameters();
parameters.GenerateInMemory = true;
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
string location = assembly.Location;
if (!String.IsNullOrEmpty(location))
{
parameters.ReferencedAssemblies.Add(location);
}
}
catch (NotSupportedException)
{}
}
var res = foo.CompileAssemblyFromSource(parameters ,source);
var type = res.CompiledAssembly.GetType("stringToCode.FooClass"); //<- here i has error
var obj = Activator.CreateInstance(type);
var output = type.GetMethod("Execute").Invoke(obj, new object[] { });
Console.WriteLine("q=" + q);
Console.ReadLine();
}
}
}
}
On first stage I am adding annotations to syntax nodes and replace nodes with new generated nodes.
On second stage when I am analysing modified document (same syntax tree with added annotations) but references in SymbolInfo still refers to unmodified syntax nodes (without annotations).
Is it possible to update or reparse solution or project and update SymbolInfo after adding annotations?
Create simple solution with one C# file:
class ะก
{
void g()
{ }
void f()
{
g();
}
}
And try to parse it with program:
using System.Collections.Generic;
using Roslyn.Compilers;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;
namespace RoslynExample2
{
class Program
{
static void Main(string[] args)
{
var workspace = Workspace.LoadSolution(#"..\..\..\..\RoslynExampleTest\RoslynExampleTest.sln");
var solution = workspace.CurrentSolution;
foreach (var project in solution.Projects)
{
Annotator annotator = new Annotator();
foreach (var document in project.Documents)
{
CompilationUnitSyntax compilationUnit = (CompilationUnitSyntax)document.GetSyntaxRoot();
var mcu = annotator.AddAnnotations(compilationUnit);
document.UpdateSyntaxRoot(mcu);
}
}
foreach (var project in solution.Projects)
{
foreach (var document in project.Documents)
{
var compilationUnit = document.GetSyntaxRoot();
var semanticModel = document.GetSemanticModel();
MySyntaxWalker sw = new MySyntaxWalker(semanticModel);
sw.Visit((SyntaxNode)compilationUnit);
}
}
}
}
internal class Annotator
{
internal struct SyntaxNodeTuple
{
internal SyntaxNode Origin;
internal SyntaxNode Modified;
internal SyntaxNodeTuple(SyntaxNode origin, SyntaxNode modified)
{
Origin = origin;
Modified = modified;
}
}
private SyntaxNodeTuple AddAnnotation(SyntaxNode s)
{
SyntaxNodeTuple t;
switch (s.Kind)
{
case SyntaxKind.ClassDeclaration:
t = AddAnnotations((ClassDeclarationSyntax)s);
break;
case SyntaxKind.MethodDeclaration:
t = AddAnnotations((MethodDeclarationSyntax)s);
break;
default:
t = new SyntaxNodeTuple();
break;
}
return t;
}
private static T ReplaceNodes<T>(T d, List<SyntaxNodeTuple> tuples)
where T : SyntaxNode
{
T d2 = d;
foreach (var t in tuples)
{
d2 = d2.ReplaceNode(t.Origin, t.Modified);
}
return d2;
}
private void AddAnnotationsToList(SyntaxList<MemberDeclarationSyntax> list, List<SyntaxNodeTuple> tuples)
{
foreach (var m in list)
{
tuples.Add(AddAnnotation(m));
}
}
internal CompilationUnitSyntax AddAnnotations(CompilationUnitSyntax d)
{
List<SyntaxNodeTuple> tuples = new List<SyntaxNodeTuple>();
AddAnnotationsToList(d.Members, tuples);
var d2 = ReplaceNodes(d, tuples);
return d2;
}
internal SyntaxNodeTuple AddAnnotations(ClassDeclarationSyntax d)
{
List<SyntaxNodeTuple> tuples = new List<SyntaxNodeTuple>();
AddAnnotationsToList(d.Members, tuples);
var d2 = ReplaceNodes(d, tuples);
d2 = d2.WithAdditionalAnnotations(new MyAnnotation());
return new SyntaxNodeTuple(d, d2);
}
internal SyntaxNodeTuple AddAnnotations(MethodDeclarationSyntax d)
{
var d2 = d.WithAdditionalAnnotations(new MyAnnotation());
bool hasAnnotation = d2.HasAnnotations(typeof(MyAnnotation)); // annotation exists
return new SyntaxNodeTuple(d, d2);
}
}
class MyAnnotation : SyntaxAnnotation
{ }
partial class MySyntaxWalker : SyntaxWalker
{
private ISemanticModel _semanticModel;
public MySyntaxWalker(ISemanticModel semanticModel)
{
_semanticModel = semanticModel;
}
public override void VisitInvocationExpression(InvocationExpressionSyntax decl)
{
var si = _semanticModel.GetSymbolInfo(decl);
var dsns = si.Symbol.DeclaringSyntaxNodes;
var dsn0 = dsns[0];
bool hasAnnotation = dsn0.HasAnnotations(typeof(MyAnnotation)); // annotation doesn't exists
}
}
}
updated variant:
using System;
using System.Diagnostics;
using Roslyn.Compilers;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;
namespace RoslynExample2
{
class Program
{
static void Main(string[] args)
{
var workspace = Workspace.LoadSolution(#"..\..\..\..\RoslynExampleTest\RoslynExampleTest.sln");
var solution = workspace.CurrentSolution;
foreach (var projectId in solution.ProjectIds)
{
var project = solution.GetProject(projectId);
foreach (var documentId in project.DocumentIds)
{
var document = project.GetDocument(documentId);
CompilationUnitSyntax compilationUnit = (CompilationUnitSyntax)document.GetSyntaxRoot();
Debug.WriteLine(String.Format("compilationUnit={0} before", compilationUnit.GetHashCode()));
Debug.WriteLine(String.Format("project={0} before", project.GetHashCode()));
Debug.WriteLine(String.Format("solution={0} before", solution.GetHashCode()));
var mcu = new AnnotatorSyntaxRewritter().Visit(compilationUnit);
var project2 = document.UpdateSyntaxRoot(mcu).Project;
if (mcu != compilationUnit)
{
solution = project2.Solution;
}
Debug.WriteLine(String.Format("compilationUnit={0} after", mcu.GetHashCode()));
Debug.WriteLine(String.Format("project={0} after", project2.GetHashCode()));
Debug.WriteLine(String.Format("solution={0} after", solution.GetHashCode()));
}
}
foreach (var projectId in solution.ProjectIds)
{
var project = solution.GetProject(projectId);
foreach (var documentId in project.DocumentIds)
{
var document = project.GetDocument(documentId);
var compilationUnit = document.GetSyntaxRoot();
var semanticModel = document.GetSemanticModel();
Debug.WriteLine(String.Format("compilationUnit={0} stage", compilationUnit.GetHashCode()));
Debug.WriteLine(String.Format("project={0} stage", project.GetHashCode()));
Debug.WriteLine(String.Format("solution={0}", solution.GetHashCode()));
MySyntaxWalker sw = new MySyntaxWalker(semanticModel);
sw.Visit((SyntaxNode)compilationUnit);
}
}
}
}
class AnnotatorSyntaxRewritter : SyntaxRewriter
{
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
{
node = node.WithAdditionalAnnotations(new MyAnnotation());
return base.VisitMethodDeclaration(node);
}
}
class MyAnnotation : SyntaxAnnotation
{ }
partial class MySyntaxWalker : SyntaxWalker
{
private ISemanticModel _semanticModel;
public MySyntaxWalker(ISemanticModel semanticModel)
{
_semanticModel = semanticModel;
}
public override void VisitMethodDeclaration(MethodDeclarationSyntax decl)
{
bool hasAnnotation = decl.HasAnnotations(typeof(MyAnnotation));
Debug.Assert(hasAnnotation);
}
}
}
The problem in your example is that the variable solution is an immutable object which refers to the solution when it is first loaded. In your code where you call document.UpdateSyntaxRoot(mcu), that actually creates and returns a new IDocument which is in a new IProject, which is in a new ISolution.
Try changing that bit of code to:
Annotator annotator = new Annotator();
foreach (var projectId in solution.ProjectIds)
{
foreach (var documentId in solution.GetProject(projectId).DocumentIds)
{
var document = solution.GetProject(projectId).GetDocument(documentId);
CompilationUnitSyntax compilationUnit = (CompilationUnitSyntax)document.GetSyntaxRoot();
var mcu = annotator.AddAnnotations(compilationUnit);
solution = document.UpdateSyntaxRoot(mcu).Project.Solution;
}
}