From f3a533f3aad2539d11edd3cfd6102ab23bc5d416 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Fri, 10 Apr 2020 09:24:45 -0700 Subject: [PATCH 01/12] Add initial source generator samples --- .../AutoNotifyGenerator.cs | 174 ++++++++++++++++++ .../Resources.Designer.cs | 90 +++++++++ .../SourceGeneratorSamples/Resources.resx | 132 +++++++++++++ .../SettingsXmlGenerator.cs | 115 ++++++++++++ .../SourceGeneratorSamples.csproj | 46 +++++ .../SourceGeneratorSamples/tools/install.ps1 | 58 ++++++ .../tools/uninstall.ps1 | 65 +++++++ 7 files changed, 680 insertions(+) create mode 100644 samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs create mode 100644 samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.Designer.cs create mode 100644 samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.resx create mode 100644 samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs create mode 100644 samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj create mode 100644 samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/install.ps1 create mode 100644 samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/uninstall.ps1 diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs new file mode 100644 index 0000000000..b9cb05b80c --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Analyzer1 +{ + [Generator] + public class AutoNotifyGenerator : ISourceGenerator + { + private const string attributeText = @" +using System; +namespace AutoNotify +{ + [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] + sealed class AutoNotifyAttribute : Attribute + { + public AutoNotifyAttribute() + { + } + public string PropertyName { get; set; } + } +} +"; + + public void Initialize(InitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + } + + public void Execute(SourceGeneratorContext context) + { + // add the attribute text + context.AddSource("AutoNotifyAttribute", SourceText.From(attributeText, Encoding.UTF8)); + + if (!(context.SyntaxReceiver is SyntaxReceiver receiver)) + return; + + // we're going to create a new compilation that contains the attribute. + // TODO: we should allow source generators to provide source during initialize, so that this step isn't required. + CSharpParseOptions options = (context.Compilation as CSharpCompilation).SyntaxTrees[0].Options as CSharpParseOptions; + Compilation compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(attributeText, Encoding.UTF8), options)); + + // get the newly bound attribute, and INotifyPropertyChanged + INamedTypeSymbol attributeSymbol = compilation.GetTypeByMetadataName("AutoNotify.AutoNotifyAttribute"); + INamedTypeSymbol notifySymbol = compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged"); + + // loop over the candidate fields, and keep the ones that are actually annotated + List fieldSymbols = new List(); + foreach (FieldDeclarationSyntax field in receiver.CandidateFields) + { + SemanticModel model = compilation.GetSemanticModel(field.SyntaxTree); + foreach (VariableDeclaratorSyntax variable in field.Declaration.Variables) + { + // Get the symbol being decleared by the field, and keep it if its annotated + IFieldSymbol fieldSymbol = model.GetDeclaredSymbol(variable) as IFieldSymbol; + if (fieldSymbol.GetAttributes().Any(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default))) + { + fieldSymbols.Add(fieldSymbol); + } + } + } + + // group the fields by class, and generate the source + foreach (IGrouping group in fieldSymbols.GroupBy(f => f.ContainingType)) + { + string classSource = ProcessClass(group.Key, group.ToList(), attributeSymbol, notifySymbol, context); + context.AddSource($"{group.Key.Name}_autoNotify.cs", SourceText.From(classSource, Encoding.UTF8)); + } + } + + private string ProcessClass(INamedTypeSymbol classSymbol, List fields, ISymbol attributeSymbol, ISymbol notifySymbol, SourceGeneratorContext context) + { + if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default)) + { + return null; //TODO: issue a diagnostic that it must be top level + } + + string namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); + + // begin building the generated source + StringBuilder source = new StringBuilder($@" +namespace {namespaceName} +{{ + public partial class {classSymbol.Name} : {notifySymbol.ToDisplayString()} + {{ +"); + + // if the class doesn't implement INotifyPropertyChanged already, add it + if (!classSymbol.Interfaces.Contains(notifySymbol)) + { + source.Append("public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;"); + } + + foreach (IFieldSymbol fieldSymbol in fields) + { + ProcessField(source, fieldSymbol, attributeSymbol); + } + + source.Append("} }"); + return source.ToString(); + } + + private void ProcessField(StringBuilder source, IFieldSymbol fieldSymbol, ISymbol attributeSymbol) + { + string fieldName = fieldSymbol.Name; + ITypeSymbol fieldType = fieldSymbol.Type; + + AttributeData attributeData = fieldSymbol.GetAttributes().Single(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default)); + TypedConstant overridenNameOpt = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "PropertyName").Value; + + string propertyName = chooseName(fieldName, overridenNameOpt); + if (propertyName.Length == 0 || propertyName == fieldName) + { + //TODO: issue a diagnostic that we can't process this field + return; + } + + source.Append($@" +public {fieldType} {propertyName} +{{ + get + {{ + return this.{fieldName}; + }} + + set + {{ + this.{fieldName} = value; + this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof({propertyName}))); + }} +}} + +"); + + string chooseName(string fieldName, TypedConstant overridenNameOpt) + { + if (!overridenNameOpt.IsNull) + { + return overridenNameOpt.Value.ToString(); + } + + fieldName = fieldName.TrimStart('_'); + if (fieldName.Length == 0) + return string.Empty; + + if (fieldName.Length == 1) + return fieldName.ToUpper(); + + return fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1); + } + + } + + class SyntaxReceiver : ISyntaxReceiver + { + public List CandidateFields { get; } = new List(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + // any field with at least one attribute is a candidate + if (syntaxNode is FieldDeclarationSyntax fieldDeclarationSyntax + && fieldDeclarationSyntax.AttributeLists.Count > 0) + { + CandidateFields.Add(fieldDeclarationSyntax); + } + } + } + } +} diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.Designer.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.Designer.cs new file mode 100644 index 0000000000..24f3bacc79 --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace SourceGeneratorSamples { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SourceGeneratorSamples.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Type names should be all uppercase.. + /// + internal static string AnalyzerDescription { + get { + return ResourceManager.GetString("AnalyzerDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type name '{0}' contains lowercase letters. + /// + internal static string AnalyzerMessageFormat { + get { + return ResourceManager.GetString("AnalyzerMessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type name contains lowercase letters. + /// + internal static string AnalyzerTitle { + get { + return ResourceManager.GetString("AnalyzerTitle", resourceCulture); + } + } + } +} diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.resx b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.resx new file mode 100644 index 0000000000..410edccd7c --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Type names should be all uppercase. + An optional longer localizable description of the diagnostic. + + + Type name '{0}' contains lowercase letters + The format-able message the diagnostic displays. + + + Type name contains lowercase letters + The title of the diagnostic. + + \ No newline at end of file diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs new file mode 100644 index 0000000000..95d252abe6 --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs @@ -0,0 +1,115 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; + +namespace Analyzer1 +{ + [Generator] + public class SettingsXmlGenerator : ISourceGenerator + { + private const string SettingsFileString = @" +namespace XmlSettings +{ + public partial class XmlSettings + { + + } +} +"; + + public object PathxmlFile { get; private set; } + + public void Execute(SourceGeneratorContext context) + { + IEnumerable settingsFiles = context.AdditionalFiles.Where(at => at.Path.EndsWith(".xmlsettings")); + foreach (AdditionalText settingsFile in settingsFiles) + { + ProcessSettingsFile(settingsFile, context); + } + } + + private void ProcessSettingsFile(AdditionalText xmlFile, SourceGeneratorContext context) + { + XmlDocument xmlDoc = new XmlDocument(); + string text = xmlFile.GetText(context.CancellationToken).ToString(); + try + { + xmlDoc.LoadXml(text); + } + catch + { + //TODO: issue a diagnostic that says we couldn't parse it + return; + } + + string fileName = Path.GetFileName(xmlFile.Path); + string name = xmlDoc.DocumentElement.GetAttribute("name"); + + StringBuilder sb = new StringBuilder($@" +namespace AutoSettings +{{ + using System; + using System.Xml; + + public partial class XmlSettings + {{ + + public static {name}Settings {name} {{ get; }} = new {name}Settings(""{fileName}""); + + public class {name}Settings + {{ + + XmlDocument xmlDoc = new XmlDocument(); + + internal {name}Settings(string fileName) + {{ + xmlDoc.Load(fileName); + }} +"); + + for(int i = 0; i < xmlDoc.DocumentElement.ChildNodes.Count; i++) + { + XmlElement setting = (XmlElement)xmlDoc.DocumentElement.ChildNodes[i]; + string settingName = setting.GetAttribute("name"); + string settingType = setting.GetAttribute("type"); + string defaultValue = setting.GetAttribute("defaultValue"); + + sb.Append($@" + +public {settingType} {settingName} +{{ + get + {{ + return ({settingType}) Convert.ChangeType(((XmlElement)xmlDoc.DocumentElement.ChildNodes[{i}]).InnerText, typeof({settingType})); + }} +}} +"); + } + + sb.Append("} } }"); + + context.AddSource($"Settings_{name}", SourceText.From(sb.ToString(), Encoding.UTF8)); + } + + public void Initialize(InitializationContext context) + { + } + + public partial class XmlSettings + { + private XmlDocument xmlDoc = new XmlDocument(); + + private XmlSettings(string fileName) + { + xmlDoc.Load(fileName); + } + + + } + } +} diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj new file mode 100644 index 0000000000..515e46eb41 --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj @@ -0,0 +1,46 @@ + + + + netstandard2.0 + false + true + True + 8.0 + + + + SourceGeneratorSamples + 1.0.0.0 + Chris + http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE + http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE + http://ICON_URL_HERE_OR_DELETE_THIS_LINE + http://REPOSITORY_URL_HERE_OR_DELETE_THIS_LINE + false + SourceGenerators + Summary of changes made in this release of the package. + Copyright + SourceGenerators, analyzers + true + + + + https://dotnet.myget.org/F/roslyn/api/v3/index.json ;$(RestoreAdditionalProjectSources) + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/install.ps1 b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/install.ps1 new file mode 100644 index 0000000000..c1c3d88223 --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/install.ps1 @@ -0,0 +1,58 @@ +param($installPath, $toolsPath, $package, $project) + +if($project.Object.SupportsPackageDependencyResolution) +{ + if($project.Object.SupportsPackageDependencyResolution()) + { + # Do not install analyzers via install.ps1, instead let the project system handle it. + return + } +} + +$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve + +foreach($analyzersPath in $analyzersPaths) +{ + if (Test-Path $analyzersPath) + { + # Install the language agnostic analyzers. + foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) + } + } + } +} + +# $project.Type gives the language name like (C# or VB.NET) +$languageFolder = "" +if($project.Type -eq "C#") +{ + $languageFolder = "cs" +} +if($project.Type -eq "VB.NET") +{ + $languageFolder = "vb" +} +if($languageFolder -eq "") +{ + return +} + +foreach($analyzersPath in $analyzersPaths) +{ + # Install language specific analyzers. + $languageAnalyzersPath = join-path $analyzersPath $languageFolder + if (Test-Path $languageAnalyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) + } + } + } +} \ No newline at end of file diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/uninstall.ps1 b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/uninstall.ps1 new file mode 100644 index 0000000000..65a8623703 --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/uninstall.ps1 @@ -0,0 +1,65 @@ +param($installPath, $toolsPath, $package, $project) + +if($project.Object.SupportsPackageDependencyResolution) +{ + if($project.Object.SupportsPackageDependencyResolution()) + { + # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. + return + } +} + +$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve + +foreach($analyzersPath in $analyzersPaths) +{ + # Uninstall the language agnostic analyzers. + if (Test-Path $analyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) + } + } + } +} + +# $project.Type gives the language name like (C# or VB.NET) +$languageFolder = "" +if($project.Type -eq "C#") +{ + $languageFolder = "cs" +} +if($project.Type -eq "VB.NET") +{ + $languageFolder = "vb" +} +if($languageFolder -eq "") +{ + return +} + +foreach($analyzersPath in $analyzersPaths) +{ + # Uninstall language specific analyzers. + $languageAnalyzersPath = join-path $analyzersPath $languageFolder + if (Test-Path $languageAnalyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + try + { + $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) + } + catch + { + + } + } + } + } +} \ No newline at end of file From e7b4ef56c92d2238f6875d3bb5b0d57923b8e7b2 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 14 Apr 2020 17:15:52 -0700 Subject: [PATCH 02/12] Add hello world generator, and clean up samples --- Samples.sln | 10 ++++ .../AutoNotifyGenerator.cs | 13 ++++- .../HelloWorldGenerator.cs | 51 +++++++++++++++++++ .../SettingsXmlGenerator.cs | 19 ++----- 4 files changed, 77 insertions(+), 16 deletions(-) create mode 100644 samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs diff --git a/Samples.sln b/Samples.sln index 9e8c364861..2ae3291176 100644 --- a/Samples.sln +++ b/Samples.sln @@ -111,6 +111,10 @@ Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "VisualBasicToCSharpConverte EndProject Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "VisualBasicToCSharpConverter.UnitTests", "samples\VisualBasic\VisualBasicToCSharpConverter\VisualBasicToCSharpConverter.Test\VisualBasicToCSharpConverter.UnitTests.vbproj", "{5B7D7569-B5EE-4C01-9AFA-BC1958588160}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceGenerators", "SourceGenerators", "{14D18F51-6B59-49D5-9AB7-08B38417A459}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGeneratorSamples", "samples\CSharp\SourceGenerators\SourceGeneratorSamples\SourceGeneratorSamples.csproj", "{2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -269,6 +273,10 @@ Global {5B7D7569-B5EE-4C01-9AFA-BC1958588160}.Debug|Any CPU.Build.0 = Debug|Any CPU {5B7D7569-B5EE-4C01-9AFA-BC1958588160}.Release|Any CPU.ActiveCfg = Release|Any CPU {5B7D7569-B5EE-4C01-9AFA-BC1958588160}.Release|Any CPU.Build.0 = Release|Any CPU + {2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -325,6 +333,8 @@ Global {8E1C9AEC-6EF1-43A8-A378-52C5C0E40532} = {CDA94F62-E35A-4913-8045-D9D42416513C} {ECB83742-8023-4609-B139-D7B78DD66ED9} = {8E1C9AEC-6EF1-43A8-A378-52C5C0E40532} {5B7D7569-B5EE-4C01-9AFA-BC1958588160} = {8E1C9AEC-6EF1-43A8-A378-52C5C0E40532} + {14D18F51-6B59-49D5-9AB7-08B38417A459} = {C3FB27E9-C8EE-4F76-B0AA-7CD67A7E652B} + {2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045} = {14D18F51-6B59-49D5-9AB7-08B38417A459} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B849838B-3D7A-4B6B-BE07-285DCB1588F4} diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs index b9cb05b80c..94be4a9c8d 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs @@ -29,6 +29,7 @@ public AutoNotifyAttribute() public void Initialize(InitializationContext context) { + // Register a syntax receiver that will be created for each generation pass context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); } @@ -37,6 +38,7 @@ public void Execute(SourceGeneratorContext context) // add the attribute text context.AddSource("AutoNotifyAttribute", SourceText.From(attributeText, Encoding.UTF8)); + // retreive the populated receiver if (!(context.SyntaxReceiver is SyntaxReceiver receiver)) return; @@ -96,6 +98,7 @@ public partial class {classSymbol.Name} : {notifySymbol.ToDisplayString()} source.Append("public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;"); } + // create properties for each field foreach (IFieldSymbol fieldSymbol in fields) { ProcessField(source, fieldSymbol, attributeSymbol); @@ -107,9 +110,11 @@ public partial class {classSymbol.Name} : {notifySymbol.ToDisplayString()} private void ProcessField(StringBuilder source, IFieldSymbol fieldSymbol, ISymbol attributeSymbol) { + // get the name and type of the field string fieldName = fieldSymbol.Name; ITypeSymbol fieldType = fieldSymbol.Type; + // get the AutoNotify attribute from the field, and any associated data AttributeData attributeData = fieldSymbol.GetAttributes().Single(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default)); TypedConstant overridenNameOpt = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "PropertyName").Value; @@ -156,13 +161,19 @@ string chooseName(string fieldName, TypedConstant overridenNameOpt) } + /// + /// Created on demand before each generation pass + /// class SyntaxReceiver : ISyntaxReceiver { public List CandidateFields { get; } = new List(); + /// + /// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation + /// public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { - // any field with at least one attribute is a candidate + // any field with at least one attribute is a candidate for property generation if (syntaxNode is FieldDeclarationSyntax fieldDeclarationSyntax && fieldDeclarationSyntax.AttributeLists.Count > 0) { diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs new file mode 100644 index 0000000000..384ef8e1d8 --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace SourceGeneratorSamples +{ + [Generator] + public class HelloWorldGenerator : ISourceGenerator + { + public void Execute(SourceGeneratorContext context) + { + // begin creating the source we'll inject into the users compilation + StringBuilder sourceBuilder = new StringBuilder(@" +using System; +namespace HelloWorldGenerator +{ + public static class HelloWorld + { + public static void SayHello() + { + Console.WriteLine(""Hello from generated code!""); + Console.WriteLine(""The following syntax trees existed in the compilation that created this program:""); +"); + + // using the context, get a list of syntax trees in the users compilation + IEnumerable syntaxTrees = context.Compilation.SyntaxTrees; + + // add the filepath of each tree to the class we're building + foreach (SyntaxTree tree in syntaxTrees) + { + sourceBuilder.AppendLine($@"Console.WriteLine(@"" - {tree.FilePath}"");"); + } + + // finish creating the source to inject + sourceBuilder.Append(@" + } + } +}"); + + // inject the created source into the users compilation + context.AddSource("helloWorldGenerator", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + } + + public void Initialize(InitializationContext context) + { + // No initialization required + } + } +} diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs index 95d252abe6..83cb7039ab 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs @@ -21,11 +21,9 @@ public partial class XmlSettings } } "; - - public object PathxmlFile { get; private set; } - public void Execute(SourceGeneratorContext context) { + // Using the context, get any additional files that end in .xmlsettings IEnumerable settingsFiles = context.AdditionalFiles.Where(at => at.Path.EndsWith(".xmlsettings")); foreach (AdditionalText settingsFile in settingsFiles) { @@ -35,6 +33,7 @@ public void Execute(SourceGeneratorContext context) private void ProcessSettingsFile(AdditionalText xmlFile, SourceGeneratorContext context) { + // try and load the settings file XmlDocument xmlDoc = new XmlDocument(); string text = xmlFile.GetText(context.CancellationToken).ToString(); try @@ -47,6 +46,8 @@ private void ProcessSettingsFile(AdditionalText xmlFile, SourceGeneratorContext return; } + + // create a class in the XmlSetting class that represnts this entry, and a static field that contains a singleton instance. string fileName = Path.GetFileName(xmlFile.Path); string name = xmlDoc.DocumentElement.GetAttribute("name"); @@ -99,17 +100,5 @@ public class {name}Settings public void Initialize(InitializationContext context) { } - - public partial class XmlSettings - { - private XmlDocument xmlDoc = new XmlDocument(); - - private XmlSettings(string fileName) - { - xmlDoc.Load(fileName); - } - - - } } } From 0e8d3ebdbb75747187a0fe51558ded9e9775768d Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Wed, 22 Apr 2020 17:20:37 -0700 Subject: [PATCH 03/12] Add WIP Readme --- samples/CSharp/SourceGenerators/README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 samples/CSharp/SourceGenerators/README.md diff --git a/samples/CSharp/SourceGenerators/README.md b/samples/CSharp/SourceGenerators/README.md new file mode 100644 index 0000000000..804eccc6e6 --- /dev/null +++ b/samples/CSharp/SourceGenerators/README.md @@ -0,0 +1,6 @@ +🚧 Work In Progress +======== + +These samples are for an in-progress feature of Roslyn. As such they may change or break as the feature is developed, and no level of support is implied. + +For more infomation on the Source Generators feature, see the [design document](https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.md). \ No newline at end of file From 09053bbdbdab2f25a550be05d51d08f1e4329d96 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Fri, 24 Apr 2020 16:13:03 -0700 Subject: [PATCH 04/12] Clean up project + remove warning --- .../Resources.Designer.cs | 90 ------------ .../SourceGeneratorSamples/Resources.resx | 132 ------------------ .../SourceGeneratorSamples.csproj | 28 +--- .../SourceGeneratorSamples/tools/install.ps1 | 58 -------- .../tools/uninstall.ps1 | 65 --------- 5 files changed, 1 insertion(+), 372 deletions(-) delete mode 100644 samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.Designer.cs delete mode 100644 samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.resx delete mode 100644 samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/install.ps1 delete mode 100644 samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/uninstall.ps1 diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.Designer.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.Designer.cs deleted file mode 100644 index 24f3bacc79..0000000000 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.Designer.cs +++ /dev/null @@ -1,90 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace SourceGeneratorSamples { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SourceGeneratorSamples.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Type names should be all uppercase.. - /// - internal static string AnalyzerDescription { - get { - return ResourceManager.GetString("AnalyzerDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type name '{0}' contains lowercase letters. - /// - internal static string AnalyzerMessageFormat { - get { - return ResourceManager.GetString("AnalyzerMessageFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type name contains lowercase letters. - /// - internal static string AnalyzerTitle { - get { - return ResourceManager.GetString("AnalyzerTitle", resourceCulture); - } - } - } -} diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.resx b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.resx deleted file mode 100644 index 410edccd7c..0000000000 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Resources.resx +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Type names should be all uppercase. - An optional longer localizable description of the diagnostic. - - - Type name '{0}' contains lowercase letters - The format-able message the diagnostic displays. - - - Type name contains lowercase letters - The title of the diagnostic. - - \ No newline at end of file diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj index 515e46eb41..a347787658 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj @@ -8,39 +8,13 @@ 8.0 - - SourceGeneratorSamples - 1.0.0.0 - Chris - http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE - http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE - http://ICON_URL_HERE_OR_DELETE_THIS_LINE - http://REPOSITORY_URL_HERE_OR_DELETE_THIS_LINE - false - SourceGenerators - Summary of changes made in this release of the package. - Copyright - SourceGenerators, analyzers - true - - https://dotnet.myget.org/F/roslyn/api/v3/index.json ;$(RestoreAdditionalProjectSources) - + - - - - - - - \ No newline at end of file diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/install.ps1 b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/install.ps1 deleted file mode 100644 index c1c3d88223..0000000000 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/install.ps1 +++ /dev/null @@ -1,58 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -if($project.Object.SupportsPackageDependencyResolution) -{ - if($project.Object.SupportsPackageDependencyResolution()) - { - # Do not install analyzers via install.ps1, instead let the project system handle it. - return - } -} - -$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve - -foreach($analyzersPath in $analyzersPaths) -{ - if (Test-Path $analyzersPath) - { - # Install the language agnostic analyzers. - foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) - } - } - } -} - -# $project.Type gives the language name like (C# or VB.NET) -$languageFolder = "" -if($project.Type -eq "C#") -{ - $languageFolder = "cs" -} -if($project.Type -eq "VB.NET") -{ - $languageFolder = "vb" -} -if($languageFolder -eq "") -{ - return -} - -foreach($analyzersPath in $analyzersPaths) -{ - # Install language specific analyzers. - $languageAnalyzersPath = join-path $analyzersPath $languageFolder - if (Test-Path $languageAnalyzersPath) - { - foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) - } - } - } -} \ No newline at end of file diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/uninstall.ps1 b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/uninstall.ps1 deleted file mode 100644 index 65a8623703..0000000000 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/tools/uninstall.ps1 +++ /dev/null @@ -1,65 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -if($project.Object.SupportsPackageDependencyResolution) -{ - if($project.Object.SupportsPackageDependencyResolution()) - { - # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. - return - } -} - -$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve - -foreach($analyzersPath in $analyzersPaths) -{ - # Uninstall the language agnostic analyzers. - if (Test-Path $analyzersPath) - { - foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) - } - } - } -} - -# $project.Type gives the language name like (C# or VB.NET) -$languageFolder = "" -if($project.Type -eq "C#") -{ - $languageFolder = "cs" -} -if($project.Type -eq "VB.NET") -{ - $languageFolder = "vb" -} -if($languageFolder -eq "") -{ - return -} - -foreach($analyzersPath in $analyzersPaths) -{ - # Uninstall language specific analyzers. - $languageAnalyzersPath = join-path $analyzersPath $languageFolder - if (Test-Path $languageAnalyzersPath) - { - foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - try - { - $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) - } - catch - { - - } - } - } - } -} \ No newline at end of file From 6ebd7b7f8bd21edd278f7ec07373a7daccfc8dcd Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Fri, 24 Apr 2020 17:30:36 -0700 Subject: [PATCH 05/12] Expand samples: - Add demo consumption project - Update readme - Lock samples to correct .net version via global.json --- .../GeneratedDemo/GeneratedDemo.csproj | 25 +++++++++++ .../GeneratedDemo/MainSettings.xmlsettings | 5 +++ .../SourceGenerators/GeneratedDemo/Program.cs | 20 +++++++++ .../GeneratedDemo/UseAutoNotifyGenerator.cs | 42 +++++++++++++++++++ .../GeneratedDemo/UseHelloWorldGenerator.cs | 11 +++++ .../GeneratedDemo/UseXmlSettingsGenerator.cs | 27 ++++++++++++ samples/CSharp/SourceGenerators/README.md | 31 +++++++++++++- .../HelloWorldGenerator.cs | 4 +- .../SettingsXmlGenerator.cs | 6 ++- .../SourceGeneratorSamples.csproj | 3 -- samples/CSharp/SourceGenerators/global.json | 12 ++++++ 11 files changed, 179 insertions(+), 7 deletions(-) create mode 100644 samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj create mode 100644 samples/CSharp/SourceGenerators/GeneratedDemo/MainSettings.xmlsettings create mode 100644 samples/CSharp/SourceGenerators/GeneratedDemo/Program.cs create mode 100644 samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs create mode 100644 samples/CSharp/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.cs create mode 100644 samples/CSharp/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.cs create mode 100644 samples/CSharp/SourceGenerators/global.json diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj b/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj new file mode 100644 index 0000000000..84dc0af73f --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj @@ -0,0 +1,25 @@ + + + + Exe + netcoreapp3.1 + preview + + + + + + Always + + + + + + + + + + + + + diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/MainSettings.xmlsettings b/samples/CSharp/SourceGenerators/GeneratedDemo/MainSettings.xmlsettings new file mode 100644 index 0000000000..457d6b508d --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/MainSettings.xmlsettings @@ -0,0 +1,5 @@ + + + false + 1234 + \ No newline at end of file diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/Program.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/Program.cs new file mode 100644 index 0000000000..56ee7a8019 --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/Program.cs @@ -0,0 +1,20 @@ +using System; + +namespace GeneratedDemo +{ + class Program + { + static void Main(string[] args) + { + // Run the various scenarios + Console.WriteLine("Running HelloWorld:\n"); + UseHelloWorldGenerator.Run(); + + Console.WriteLine("\n\nRunning AutoNotify:\n"); + UseAutoNotifyGenerator.Run(); + + Console.WriteLine("\n\nRunning XmlSettings:\n"); + UseXmlSettingsGenerator.Run(); + } + } +} diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs new file mode 100644 index 0000000000..dffce6dc1b --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs @@ -0,0 +1,42 @@ +using System; +using AutoNotify; + +namespace GeneratedDemo +{ + // The view model we'd like to augment + public partial class ExampleViewModel + { + [AutoNotify] + private string _text = "private field text"; + + [AutoNotify(PropertyName = "Count")] + private int _amount = 5; + } + + public static class UseAutoNotifyGenerator + { + public static void Run() + { + ExampleViewModel vm = new ExampleViewModel(); + + // we didn't explicitly create the 'Text' property, it was generated for us + string text = vm.Text; + Console.WriteLine($"Text = {text}"); + + // Properties can have differnt names generated based on the PropertyName argument of the attribute + int count = vm.Count; + Console.WriteLine($"Count = {count}"); + + // the viewmodel will automatically implement INotifyPropertyChanged + vm.PropertyChanged += (o, e) => Console.WriteLine($"Property {e.PropertyName} was changed"); + vm.Text = "abc"; + vm.Count = 123; + + // Try adding fields to the ExampleViewModel class above and tagging them with the [AutoNotify] attribute + // You'll see the matching generated properties visibile in IntelliSense in realtime + } + + } + + +} diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.cs new file mode 100644 index 0000000000..000328660c --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.cs @@ -0,0 +1,11 @@ +namespace GeneratedDemo +{ + public static class UseHelloWorldGenerator + { + public static void Run() + { + // The static call below is generated at build time, and will list the syntax trees used in the compilation + HelloWorldGenerated.HelloWorld.SayHello(); + } + } +} diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.cs new file mode 100644 index 0000000000..213181e514 --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.cs @@ -0,0 +1,27 @@ +using System; +using AutoSettings; + +namespace GeneratedDemo +{ + public static class UseXmlSettingsGenerator + { + public static void Run() + { + // This XmlSettings generator makes a static property in the XmlSettings class for each .xmlsettings file + + // here we have the 'Main' settings file from MainSettings.xmlsettings + // the name is determined by the 'name' attribute of the root settings element + XmlSettings.MainSettings main = XmlSettings.Main; + Console.WriteLine($"Reading settings from {main.GetLocation()}"); + + // settings are strongly typed and can be read directly from the static instance + bool firstRun = XmlSettings.Main.FirstRun; + Console.WriteLine($"Setting firstRun = {firstRun}"); + + int cacheSize = XmlSettings.Main.CacheSize; + Console.WriteLine($"Setting cacheSize = {cacheSize}"); + + // Try adding some keys to the settings file and see the settings become available to read from + } + } +} diff --git a/samples/CSharp/SourceGenerators/README.md b/samples/CSharp/SourceGenerators/README.md index 804eccc6e6..56976224ad 100644 --- a/samples/CSharp/SourceGenerators/README.md +++ b/samples/CSharp/SourceGenerators/README.md @@ -3,4 +3,33 @@ These samples are for an in-progress feature of Roslyn. As such they may change or break as the feature is developed, and no level of support is implied. -For more infomation on the Source Generators feature, see the [design document](https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.md). \ No newline at end of file +For more infomation on the Source Generators feature, see the [design document](https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.md). + +Prerequisites +----- + +These samples require Visual Studio 16.6 or higher. + +Building the samples +----- +Open `SourceGenerators.sln` in Visual Studio or run `dotnet build` from the `\SourceGenerators` directory. + +Running the samples +----- + +The generators must be run as part of another build, as they inject source into the project being built. This repo contains a sample project `GeneratorDemo` that relies of the sample generators to add code to it's compilation. + +Run `GeneratedDemo` in Visual studio or run `dotnet run` from the `GeneratorDemo` directory. + +Using the samples in your project +----- + +You can add the sample generators to your own project by adding an item group containing an analyzer reference: + +```xml + + + +``` + +You may need to close and reopen the solution in Visual Studio for the change to take effect. diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs index 384ef8e1d8..2beb0aebe4 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs @@ -14,7 +14,7 @@ public void Execute(SourceGeneratorContext context) // begin creating the source we'll inject into the users compilation StringBuilder sourceBuilder = new StringBuilder(@" using System; -namespace HelloWorldGenerator +namespace HelloWorldGenerated { public static class HelloWorld { @@ -40,7 +40,7 @@ public static void SayHello() }"); // inject the created source into the users compilation - context.AddSource("helloWorldGenerator", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + context.AddSource("helloWorldGenerated", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); } public void Initialize(InitializationContext context) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs index 83cb7039ab..e277d2cf84 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs @@ -66,9 +66,14 @@ public class {name}Settings {{ XmlDocument xmlDoc = new XmlDocument(); + + private string fileName; + + public string GetLocation() => fileName; internal {name}Settings(string fileName) {{ + this.fileName = fileName; xmlDoc.Load(fileName); }} "); @@ -78,7 +83,6 @@ public class {name}Settings XmlElement setting = (XmlElement)xmlDoc.DocumentElement.ChildNodes[i]; string settingName = setting.GetAttribute("name"); string settingType = setting.GetAttribute("type"); - string defaultValue = setting.GetAttribute("defaultValue"); sb.Append($@" diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj index a347787658..e66e22427d 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj @@ -2,9 +2,6 @@ netstandard2.0 - false - true - True 8.0 diff --git a/samples/CSharp/SourceGenerators/global.json b/samples/CSharp/SourceGenerators/global.json new file mode 100644 index 0000000000..4c896525f6 --- /dev/null +++ b/samples/CSharp/SourceGenerators/global.json @@ -0,0 +1,12 @@ +{ + "tools": { + "dotnet": "3.1.300-preview-015115", + "vs": { + "version": "16.6" + }, + "xcopy-msbuild": "16.3.0-alpha" + }, + "msbuild-sdks": { + "Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.20117.3" + } +} From 61d81ab9ec5c1871ce0ed42309e9182a05efec8c Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Fri, 24 Apr 2020 17:34:22 -0700 Subject: [PATCH 06/12] Whitespace --- .../CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj | 1 - .../SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs | 3 --- 2 files changed, 4 deletions(-) diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj b/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj index 84dc0af73f..56b01b634d 100644 --- a/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj @@ -21,5 +21,4 @@ - diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs index dffce6dc1b..77e346c9a8 100644 --- a/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs @@ -35,8 +35,5 @@ public static void Run() // Try adding fields to the ExampleViewModel class above and tagging them with the [AutoNotify] attribute // You'll see the matching generated properties visibile in IntelliSense in realtime } - } - - } From 8c4859ac3be1fb83af712c424f11381bf690a333 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Mon, 27 Apr 2020 11:17:36 -0700 Subject: [PATCH 07/12] Add SourceGenerators.sln --- .../SourceGenerators/SourceGenerators.sln | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 samples/CSharp/SourceGenerators/SourceGenerators.sln diff --git a/samples/CSharp/SourceGenerators/SourceGenerators.sln b/samples/CSharp/SourceGenerators/SourceGenerators.sln new file mode 100644 index 0000000000..1e5b737dca --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGenerators.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30022.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGeneratorSamples", "SourceGeneratorSamples\SourceGeneratorSamples.csproj", "{B452269D-856C-4FE6-8900-3D81461AF864}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeneratedDemo", "GeneratedDemo\GeneratedDemo.csproj", "{DB138C8B-7C34-4AC7-A443-A0B29D1CE8A5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B452269D-856C-4FE6-8900-3D81461AF864}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B452269D-856C-4FE6-8900-3D81461AF864}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B452269D-856C-4FE6-8900-3D81461AF864}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B452269D-856C-4FE6-8900-3D81461AF864}.Release|Any CPU.Build.0 = Release|Any CPU + {DB138C8B-7C34-4AC7-A443-A0B29D1CE8A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB138C8B-7C34-4AC7-A443-A0B29D1CE8A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB138C8B-7C34-4AC7-A443-A0B29D1CE8A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB138C8B-7C34-4AC7-A443-A0B29D1CE8A5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {623C84B6-B8A4-4F29-8E68-4ED37D4529D5} + EndGlobalSection +EndGlobal From c41a4963fbb40bf35621ef58e1f0e32cc672db7d Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Mon, 27 Apr 2020 11:20:19 -0700 Subject: [PATCH 08/12] Add generated project to main samples SLN --- Samples.sln | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Samples.sln b/Samples.sln index 2ae3291176..349b0c8cda 100644 --- a/Samples.sln +++ b/Samples.sln @@ -115,6 +115,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceGenerators", "SourceG EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGeneratorSamples", "samples\CSharp\SourceGenerators\SourceGeneratorSamples\SourceGeneratorSamples.csproj", "{2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeneratedDemo", "samples\CSharp\SourceGenerators\GeneratedDemo\GeneratedDemo.csproj", "{EC4DB63B-C2B4-4D06-AF98-15253035C6D5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -277,6 +279,10 @@ Global {2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045}.Debug|Any CPU.Build.0 = Debug|Any CPU {2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045}.Release|Any CPU.ActiveCfg = Release|Any CPU {2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045}.Release|Any CPU.Build.0 = Release|Any CPU + {EC4DB63B-C2B4-4D06-AF98-15253035C6D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC4DB63B-C2B4-4D06-AF98-15253035C6D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC4DB63B-C2B4-4D06-AF98-15253035C6D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC4DB63B-C2B4-4D06-AF98-15253035C6D5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -335,6 +341,7 @@ Global {5B7D7569-B5EE-4C01-9AFA-BC1958588160} = {8E1C9AEC-6EF1-43A8-A378-52C5C0E40532} {14D18F51-6B59-49D5-9AB7-08B38417A459} = {C3FB27E9-C8EE-4F76-B0AA-7CD67A7E652B} {2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045} = {14D18F51-6B59-49D5-9AB7-08B38417A459} + {EC4DB63B-C2B4-4D06-AF98-15253035C6D5} = {14D18F51-6B59-49D5-9AB7-08B38417A459} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B849838B-3D7A-4B6B-BE07-285DCB1588F4} From c034ef84d9f25c53b4778458fd119989bcd4cf9f Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 28 Apr 2020 14:06:19 -0700 Subject: [PATCH 09/12] Downgrade generated demo to netcoreapp3.0 --- .../CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj b/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj index 56b01b634d..de710bb936 100644 --- a/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + netcoreapp3.0 preview From af2f5c2ce060a7e094ff304b3a6178f420fa9f63 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 28 Apr 2020 14:59:17 -0700 Subject: [PATCH 10/12] Update global.json Remove special source generators one --- global.json | 4 ++-- samples/CSharp/SourceGenerators/global.json | 12 ------------ 2 files changed, 2 insertions(+), 14 deletions(-) delete mode 100644 samples/CSharp/SourceGenerators/global.json diff --git a/global.json b/global.json index f5c64ea514..4c896525f6 100644 --- a/global.json +++ b/global.json @@ -1,8 +1,8 @@ { "tools": { - "dotnet": "3.0.101", + "dotnet": "3.1.300-preview-015115", "vs": { - "version": "16.3" + "version": "16.6" }, "xcopy-msbuild": "16.3.0-alpha" }, diff --git a/samples/CSharp/SourceGenerators/global.json b/samples/CSharp/SourceGenerators/global.json deleted file mode 100644 index 4c896525f6..0000000000 --- a/samples/CSharp/SourceGenerators/global.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "tools": { - "dotnet": "3.1.300-preview-015115", - "vs": { - "version": "16.6" - }, - "xcopy-msbuild": "16.3.0-alpha" - }, - "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.20117.3" - } -} From b15fee6a03675c49cf51d986042f72f562be6840 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 28 Apr 2020 15:47:15 -0700 Subject: [PATCH 11/12] Bump arcade version too --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 4c896525f6..264f27e2ea 100644 --- a/global.json +++ b/global.json @@ -7,6 +7,6 @@ "xcopy-msbuild": "16.3.0-alpha" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.20117.3" + "Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.20224.11" } } From 5985a805de76bfc312ba9ea598356e3533889527 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 28 Apr 2020 16:01:38 -0700 Subject: [PATCH 12/12] Restore netcoreapp3.1 fix reference in release --- .../SourceGenerators/GeneratedDemo/GeneratedDemo.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj b/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj index de710bb936..dce2692064 100644 --- a/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.0 + netcoreapp3.1 preview @@ -14,7 +14,7 @@ - +