I've successfuly added files programatically to my project using the following code:
var project = new Microsoft.Build.Evaluation.Project(projPath);
project.AddItem("Compile", filePath);
However, removing a file programatically is giving me a hard time.
Signature:
public bool RemoveItem(
ProjectItem item
)
How can I instantiate a ProjectItem? I couldn't find any examples.
Reference: https://msdn.microsoft.com/en-us/library/microsoft.build.evaluation.project.removeitem.aspx
you did
private static ProjectItem GetProjectItem(this Project project, string filePath)
{
var includePath = filePath.Substring(project.DirectoryPath.Length + 1);
var projectItem = project.GetItems(CompileType).FirstOrDefault(item => item.EvaluatedInclude.Equals(includePath));
return projectItem;
}
in your GetProjectItem method:
replace that:
var projectItem = project.GetItems(CompileType).FirstOrDefault(item => item.EvaluatedInclude.Equals(includePath));
with this:
var projectItem = project.GetItems("Compile").ToList()
.Where(item => item.EvaluatedInclude.Equals(includePath)).FirstOrDefault();
using .FirstOrDefault() will bring it to have just first item of all files. i used .ToList() and made it work with all my items which have same EvaluatedInclude. its totally worked for my.
This is the class I ended up writing. No simple solution for remove.
public static class SourceControlHelper
{
public static void CheckoutFile(string filePath)
{
TFSAction((workspace) => workspace.PendEdit(filePath), filePath);
}
public static void AddFile(this Project project, string filePath)
{
CheckoutFile(project.FullPath);
var projectItem = project.GetProjectItem(filePath);
if (projectItem != null)
{
return;
}
var includePath = filePath.Substring(project.DirectoryPath.Length + 1);
project.AddItem(CompileType, includePath);
project.Save();
TFSAction(workspace => workspace.PendAdd(filePath), filePath);
}
public static void DeleteFile(this Project project, string filePath)
{
CheckoutFile(project.FullPath);
var projectItem = project.GetProjectItem(filePath);
if (projectItem == null)
{
return;
}
project.RemoveItem(projectItem);
project.Save();
TFSAction(workspace => workspace.PendDelete(filePath), filePath);
}
private static ProjectItem GetProjectItem(this Project project, string filePath)
{
var includePath = filePath.Substring(project.DirectoryPath.Length + 1);
var projectItem = project.GetItems(CompileType).FirstOrDefault(item => item.EvaluatedInclude.Equals(includePath));
return projectItem;
}
private static void TFSAction(Action<Workspace> action, string filePath)
{
var workspaceInfo = Workstation.Current.GetLocalWorkspaceInfo(filePath);
if (workspaceInfo == null)
{
Console.WriteLine("Failed to initialize workspace info");
return;
}
using (var server = new TfsTeamProjectCollection(workspaceInfo.ServerUri))
{
var workspace = workspaceInfo.GetWorkspace(server);
action(workspace);
}
}
private static string CompileType
{
get { return CopyTool.Extension.Equals("ts") ? "TypeScriptCompile" : "Compile"; }
}
}
Related
I have a button that when clicked will start downloading multiple files (this button will also open a chrome://downloads tab and closes it immediately.
The page.download event handler for downloads will not fire.
The page.WaitForDownloadAsync() returns only one of these files.
I do not know the file names that will be downloaded, I also do not know if more than 1 file will be downloaded, there is always the possibility that only 1 file will be downloaded, but also the possibility that multiple files will be downloaded.
How can I handle this in playwright? I would like to return a list of all the downloaded files paths.
So I resolved this with the following logic.
I created two variables:
List<string> downloadedFiles = new List<string>();
List<string> fileDownloadSession = new();
I then created a method to add as a handler to the page.Download that looks like this:
private async void downloadHandler(object sender, IDownload download)
{
fileDownloadSession.Add("Downloading...");
var waiter = await download.PathAsync();
downloadedFiles.Add(waiter);
fileDownloadSession.Remove(fileDownloadSession.First());
}
Afterwards, I created a public method to get the downloaded files that looks like this:
public List<string> GetDownloadedFiles()
{
while (fileDownloadSession.Any())
{
}
var downloadedFilesList = downloadedFiles;
downloadedFiles = new List<string>();
return downloadedFilesList;
}
All these methods and planning are in a separate class of their own so that they can monitor the downloaded files properly, and also to freeze the main thread so it can grab all of the required files.
All in all it seems just as sketchy of a solution, similarly to how you would implement it in Selenium, nothing much has changed in terms of junkyard implementations in the new frameworks.
You can find my custom class here: https://paste.mod.gg/rztmzncvtagi/0, enjoy, there is no other topic that answers this specific question for playwright on C#.
Code here, in case it gets deleted from paste.mod.gg:
using System.Net;
using System.Runtime.InteropServices.JavaScript;
using Flanium;
using FlaUI.UIA3;
using Microsoft.Playwright;
using MoreLinq;
using Polly;
namespace Fight;
public class WebBrowser
{
private IBrowser _browser;
private IBrowserContext _context;
private IPage _page;
private bool _force;
private List<string> downloadedFiles = new List<string>();
private List<string> fileDownloadSession = new();
public void EagerMode()
{
_force = true;
}
public enum BrowserType
{
None,
Chrome,
Firefox,
}
public IPage GetPage()
{
return _page;
}
public WebBrowser(BrowserType browserType = BrowserType.Chrome, bool headlessMode = false)
{
var playwright = Playwright.CreateAsync().Result;
_browser = browserType switch
{
BrowserType.Chrome => playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions {Headless = headlessMode}).Result,
BrowserType.Firefox => playwright.Firefox.LaunchAsync(new BrowserTypeLaunchOptions {Headless = headlessMode}).Result,
_ => null
};
_context = _browser.NewContextAsync().Result;
_page = _context.NewPageAsync().Result;
_page.Download += downloadHandler;
Console.WriteLine("WebBrowser was successfully started.");
}
private async void downloadHandler(object sender, IDownload download)
{
fileDownloadSession.Add("Downloading...");
var waiter = await download.PathAsync();
downloadedFiles.Add(waiter);
fileDownloadSession.Remove(fileDownloadSession.First());
}
public List<string> GetDownloadedFiles()
{
while (fileDownloadSession.Any())
{
}
var downloadedFilesList = downloadedFiles;
downloadedFiles = new List<string>();
return downloadedFilesList;
}
public void Navigate(string url)
{
_page.GotoAsync(url).Wait();
}
public void Close(string containedURL)
{
var pages = _context.Pages.Where(x => x.Url.Contains(containedURL));
if (pages.Any())
pages.ForEach(x => x.CloseAsync().Wait());
}
public IElementHandle Click(string selector, int retries = 15, int retryInterval = 1)
{
var element = Policy.HandleResult<IElementHandle>(result => result == null)
.WaitAndRetry(retries, interval => TimeSpan.FromSeconds(retryInterval))
.Execute(() =>
{
var element = FindElement(selector);
if (element != null)
{
try
{
element.ClickAsync(new ElementHandleClickOptions() {Force = _force}).Wait();
element.DisposeAsync();
return element;
}
catch (Exception e)
{
return null;
}
}
return null;
});
return element;
}
public IElementHandle FindElement(string selector)
{
IElementHandle element = null;
var Pages = _context.Pages.ToArray();
foreach (var w in Pages)
{
//============================================================
element = w.QuerySelectorAsync(selector).Result;
if (element != null)
{
return element;
}
//============================================================
var iframes = w.Frames.ToList();
var index = 0;
for (; index < iframes.Count; index++)
{
var frame = iframes[index];
element = frame.QuerySelectorAsync(selector).Result;
if (element is not null)
{
return element;
}
var children = frame.ChildFrames;
if (children.Count > 0 && iframes.Any(x => children.Any(y => y.Equals(x))) == false)
{
iframes.InsertRange(index + 1, children);
index--;
}
}
}
return element;
}
}
`using Mono.Cecil; using Mono.Cecil.Cil;
using System; using System.Linq; using System.Collections.Generic;
namespace StormKittyBuilder {
internal sealed class build
{
private static Random random = new Random();
private static string RandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
public static Dictionary<string, string> ConfigValues = new Dictionary<string, string>
{
{ "Discord url", "" },
{ "Mutex", RandomString(20) },
};
// Read stub
private static AssemblyDefinition ReadStub()
{
return AssemblyDefinition.ReadAssembly("stub\\stub.exe");
}
// Write stub
private static void WriteStub(AssemblyDefinition definition, string filename)
{
definition.Write(filename);
}
// Replace values in config
private static string ReplaceConfigParams(string value)
{
foreach (KeyValuePair<string, string> config in ConfigValues)
if (value.Equals($"--- {config.Key} ---"))
return config.Value;
return value;
}
// Проходим по всем классам, строкам и заменяем значения.
public static AssemblyDefinition IterValues(AssemblyDefinition definition)
{
foreach (ModuleDefinition definition2 in definition.Modules)
foreach (TypeDefinition definition3 in definition2.Types)
if (definition3.Name.Equals("Config"))
foreach (MethodDefinition definition4 in definition3.Methods)
if (definition4.IsConstructor && definition4.HasBody)
{
IEnumerator<Instruction> enumerator;
enumerator = definition4.Body.Instructions.GetEnumerator();
while (enumerator.MoveNext())
{
var current = enumerator.Current;
if (current.OpCode.Code == Code.Ldstr & current.Operand is object)
{
string str = current.Operand.ToString();
if (str.StartsWith("---") && str.EndsWith("---"))
current.Operand = ReplaceConfigParams(str);
}
}
}
return definition;
}
public static string BuildStub()
{
var definition = ReadStub();
definition = IterValues(definition);
WriteStub(definition, "bot\\build.exe");
return "bot\\build.exe";
}
} }`
Working on my college project (a discord bot), I want an .exe file
assume named as builder.exe, which can overwrite another .exe - assume named as base.exe - and we get a new .exe named output.exe.
I tried this BUILDER.EXE Github repo to overwrite my base.exe with help of builder.exe created from this repo no doubt it worked but the help I need from the master reading my problem is that I want to embed the base.exe into my builder.exe (to get a single exe file) which can read and over write base.exe and produce output.exe.
In short a single .exe file (embedded with base.exe) which read and overwrites a reference (base.exe) and produces output.exe as final result.
I am currently developing a software that will be used by users that should not be able to access the back-end of it all but should still be able to easily change configuration/settings for the application.
I decided the best approach would be a custom "configuration file (.cfg)" located in the root of the final build.
Simple example of the .cfg file:
serveraddress='10.10.10.10'
serverport='1234'
servertimeout='15000'
Since I wanted the configuration file to easily be extended I decided to use some custom attributes and some simple LINQ.
This does work like I expect it to, but since I am still a novice in .net I am afraid I have not gone with the best approach and my question is therefor:
Is there anything I can do to improve this?
Or is there just generally a better approach for this?
This is my code for reading the configuration file and assigning the values to it's corresponding properties.
ConfigFileHandler.cs
public void ReadConfigFile()
{
var cfgFile = new ConfigFile();
var configLines = File.ReadAllLines("configfile.cfg");
var testList = configLines.Select(line => line.Split('='))
.Select(splitString => new Tuple<string, string>(splitString[0], splitString[1].Replace("'", "")))
.ToList();
foreach (var prop in typeof(ConfigFile).GetProperties())
{
var attrs = (ConfigFileFieldAttribute[])prop.GetCustomAttributes
(typeof(ConfigFileFieldAttribute), false);
foreach (var t in from attr in attrs from t in testList where t.Item1 == attr.Name select t)
{
prop.SetValue(cfgFile, t.Item2);
}
}
}
ConfigFile.cs
class ConfigFile
{
private static string _serverAddress;
private static int _serverPort;
private static int _serverTimeout;
[ConfigFileField(#"serveraddress")]
public string ServerAddress
{
get { return _serverAddress; }
set { _serverAddress= value; }
}
[ConfigFileField(#"serverport")]
public string ServerPort
{
get { return _serverPort.ToString(); }
set { _serverPort= int.Parse(value); }
}
[ConfigFileField(#"servertimeout")]
public string ServerTimeout
{
get { return _serverTimeout.ToString(); }
set { _serverTimeout= int.Parse(value); }
}
}
any tips on writing better looking code would be highly appreciated!
UPDATE:
Thanks for all the feedback.
Below is the final classes!
https://dotnetfiddle.net/bPMnJA for a live example
Please note, this is C# 6.0
ConfigFileHandler.cs
public class ConfigFileHandler
{
public void ReadConfigFile()
{
var configLines = File.ReadAllLines("configfile.cfg");
var configDictionary = configLines.Select(line => line.Split('='))
.Select(splitString => new Tuple<string, string>(splitString[0], splitString[1].Replace("'", "")))
.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2);
ConfigFile.SetDictionary(configDictionary);
}
}
ConfigFile.cs
public class ConfigFile
{
private static Dictionary<string, string> _configDictionary;
public string ServerAddress => PullValueFromConfig<string>("serveraddress", "10.1.1.10");
public int ServerPort => PullValueFromConfig<int>("serverport", "3306");
public long ServerTimeout => PullValueFromConfig<long>("servertimeout", "");
private static T PullValueFromConfig<T>(string key, string defaultValue)
{
string value;
if (_configDictionary.TryGetValue(key, out value) && value.Length > 0)
return (T) Convert.ChangeType(value, typeof (T));
return (T) Convert.ChangeType(defaultValue, typeof (T));
}
public static void SetDictionary(Dictionary<string, string> configValues)
{
_configDictionary = configValues;
}
}
You could keep the simplicity of your config file and get rid of the nested loops by loading the values into a dictionary and then passing that into your ConfigFile class.
public static void ReadConfigFile()
{
var configLines = File.ReadAllLines("configfile.cfg");
var testList = configLines.Select(line => line.Split('='))
.Select(splitString => new Tuple<string, string>(splitString[0], splitString[1].Replace("'", "")))
.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2);
var cfgFile = new ConfigFile(testList);
}
The new ConfigFile class:
class ConfigFile
{
private Dictionary<string, string> _configDictionary;
public ConfigFile(Dictionary<string, string> configValues)
{
_configDictionary = configValues;
}
public string ServerAddress
{
get { return PullValueFromConfig("serveraddress", "192.168.1.1"); }
}
public string ServerPort
{
get { return PullValueFromConfig("serverport", "80"); }
}
public string ServerTimeout
{
get { return PullValueFromConfig("servertimeout", "900"); }
}
private string PullValueFromConfig(string key, string defaultValue)
{
string value;
if (_configDictionary.TryGetValue(key, out value))
return value;
return defaultValue;
}
}
I decided to use a custom "configuration file (.cfg)" located in the root of the final build.
Good idea. For cleaner code, you could use JSON and JSON.NET for de/serialization and put the read/write into the ConfigFile class. Here is an example that is live as a fiddle.
The ConfigFile class is responsible for loading and saving itself and uses JSON.NET for de/serialization.
public class ConfigFile
{
private readonly static string path = "somePath.json";
public string ServerAddress { get; set; }
public string ServerPort { get; set; }
public string ServerTimeout { get; set; }
public void Save()
{
var json = JsonConvert.SerializeObject(this, Formatting.Indented);
File.WriteAllText(path, json)
}
public static ConfigFile Load()
{
var json = File.ReadAllText(path);
return JsonConvert.DeserializeObject<ConfigFile>(json);
}
}
Here is how you would use it to load the file, change its properties, and save.
ConfigFile f = ConfigFile.Load();
f.ServerAddress = "0.0.0.0";
f.ServerPort = "8080";
f.ServerTimeout = "400";
f.Save();
We use the .json file extension as a convention. You could still use .cfg because it's just plain text with a specific syntax. The resultant config file content from the above usage is this:
{
"ServerAddress":"0.0.0.0",
"ServerPort":"8080",
"ServerTimeout":"400"
}
You could just tell your clients to "change the numbers only". Your approach is fine, as far as I'm concerned. The above is just a cleaner implementation.
Firstly, I would do what Phil did, and store your testlist in a Dictionary.
var configLines = File.ReadAllLines("configfile.cfg");
var testDict = configLines.Select(line => line.Split('=', 2))
.ToDictionary(s => s[0], s => s[1].Replace("'", ""));
Then you can clean up the property assignment LINQ a bit:
foreach (var prop in typeof(ConfigFile).GetProperties())
{
var attr = prop.GetCustomAttributes(false)
.OfType<ConfigFileFieldAttribute>()
.FirstOrDefault();
string val;
if (attr != null && testDict.TryGetValue(attr.Name, out val))
prop.SetValue(cfgFile, val);
}
You might even be able to call:
var attr = prop.GetCustomAttributes<ConfigFileFieldAttribute>(false).FirstOrDefault();
Don't have an IDE on me so I can't check right now
Is there easier way to convert telerik orm entity list to csv format?
The following simple static class will help you in this task. Note that it will create a .csv file, which contains the values of the entity's properties without taking into account the navigation properties:
public static partial class EntitiesExporter
{
public static void ExportEntityList<T>(string fileLocation, IEnumerable<T> entityList, string seperator = " , ")
{
string content = CreateFileContent<T>(entityList, seperator);
SaveContentToFile(fileLocation, content);
}
private static string CreateFileContent<T>(IEnumerable<T> entityList, string seperator)
{
StringBuilder result = new StringBuilder();
List<PropertyInfo> properties = new List<PropertyInfo>();
foreach (PropertyInfo item in typeof(T).GetProperties())
{
if (item.CanWrite)
{
properties.Add(item);
}
}
foreach (T row in entityList)
{
var values = properties.Select(p => p.GetValue(row, null));
var line = string.Join(seperator, values);
result.AppendLine(line);
}
return result.ToString();
}
private static void SaveContentToFile(string fileLocation, string content)
{
using (StreamWriter writer = File.CreateText(fileLocation))
{
writer.Write(content);
writer.Close();
}
}
}
You can consume the class like this in your code:
using (EntitiesModel dbContext = new EntitiesModel())
{
IQueryable<Category> cats = dbContext.Categories;
string appDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string fileLocation = Path.Combine(appDir, "test.csv");
EntitiesExporter.ExportEntityList<Category>(fileLocation, cats);
}
I hope this helps.
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;
}
}