diff --git a/Samples.sln b/Samples.sln index 349b0c8cd..71d51bee6 100644 --- a/Samples.sln +++ b/Samples.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28606.18 @@ -113,9 +113,15 @@ Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "VisualBasicToCSharpConverte 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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpSourceGeneratorSamples", "samples\CSharp\SourceGenerators\SourceGeneratorSamples\CSharpSourceGeneratorSamples.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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpGeneratedDemo", "samples\CSharp\SourceGenerators\GeneratedDemo\CSharpGeneratedDemo.csproj", "{EC4DB63B-C2B4-4D06-AF98-15253035C6D5}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "VisualBasicGeneratedDemo", "samples\VisualBasic\SourceGenerators\GeneratedDemo\VisualBasicGeneratedDemo.vbproj", "{DA924876-9CF5-47E0-AA01-ADAF47653D39}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "VisualBasicSourceGeneratorSamples", "samples\VisualBasic\SourceGenerators\SourceGeneratorSamples\VisualBasicSourceGeneratorSamples.vbproj", "{8322B6E4-0CB1-4EC1-A2CC-2E4DB02C834A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceGenerators", "SourceGenerators", "{E79B07C8-0859-4B5C-9650-68D855833C6E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -283,6 +289,14 @@ Global {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 + {DA924876-9CF5-47E0-AA01-ADAF47653D39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA924876-9CF5-47E0-AA01-ADAF47653D39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA924876-9CF5-47E0-AA01-ADAF47653D39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA924876-9CF5-47E0-AA01-ADAF47653D39}.Release|Any CPU.Build.0 = Release|Any CPU + {8322B6E4-0CB1-4EC1-A2CC-2E4DB02C834A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8322B6E4-0CB1-4EC1-A2CC-2E4DB02C834A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8322B6E4-0CB1-4EC1-A2CC-2E4DB02C834A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8322B6E4-0CB1-4EC1-A2CC-2E4DB02C834A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -342,6 +356,9 @@ Global {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} + {DA924876-9CF5-47E0-AA01-ADAF47653D39} = {E79B07C8-0859-4B5C-9650-68D855833C6E} + {8322B6E4-0CB1-4EC1-A2CC-2E4DB02C834A} = {E79B07C8-0859-4B5C-9650-68D855833C6E} + {E79B07C8-0859-4B5C-9650-68D855833C6E} = {CDA94F62-E35A-4913-8045-D9D42416513C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B849838B-3D7A-4B6B-BE07-285DCB1588F4} diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj b/samples/CSharp/SourceGenerators/GeneratedDemo/CSharpGeneratedDemo.csproj similarity index 82% rename from samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj rename to samples/CSharp/SourceGenerators/GeneratedDemo/CSharpGeneratedDemo.csproj index b95bde39c..4b3054e9f 100644 --- a/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/CSharpGeneratedDemo.csproj @@ -13,7 +13,7 @@ - + diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj similarity index 100% rename from samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj rename to samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/Program.vb b/samples/VisualBasic/SourceGenerators/GeneratedDemo/Program.vb index 154f716e0..fd209d06b 100644 --- a/samples/VisualBasic/SourceGenerators/GeneratedDemo/Program.vb +++ b/samples/VisualBasic/SourceGenerators/GeneratedDemo/Program.vb @@ -4,36 +4,30 @@ Option Infer On Module Program - Public Sub Main() + Public Sub Main() - Console.WriteLine("Running HelloWorld: + Console.WriteLine("Running HelloWorld: ") - UseHelloWorldGenerator.Run() + UseHelloWorldGenerator.Run() - Console.WriteLine(" + Console.WriteLine(" Running AutoNotify: ") - UseAutoNotifyGenerator.Run() + UseAutoNotifyGenerator.Run() - Console.WriteLine(" + Console.WriteLine(" Running XmlSettings: ") - UseXmlSettingsGenerator.Run() + UseXmlSettingsGenerator.Run() - Console.WriteLine(" + Console.WriteLine(" Running CsvGenerator: ") - UseCsvGenerator.Run() + UseCsvGenerator.Run() - Console.WriteLine(" + End Sub -Running MustacheGenerator: -") - UseMustacheGenerator.Run() - - End Sub - -End Module \ No newline at end of file +End Module diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.vb b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.vb index ea1a1a3d8..f810bd4a6 100644 --- a/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.vb +++ b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.vb @@ -7,36 +7,36 @@ Imports AutoNotify ' The view model we'd like to augment Partial Public Class ExampleViewModel - - Private _text As String = "private field text" + + Private _text As String = "private field text" - - Private _amount As Integer = 5 + + Private _amount As Integer = 5 End Class Public Module UseAutoNotifyGenerator - Public Sub Run() + Public Sub Run() - Dim vm As New ExampleViewModel() + Dim vm As New ExampleViewModel() - ' we didn't explicitly create the 'Text' property, it was generated for us - Dim text = vm.Text - Console.WriteLine($"Text = {text}") + ' we didn't explicitly create the 'Text' property, it was generated for us + Dim text = vm.Text + Console.WriteLine($"Text = {text}") - ' Properties can have differnt names generated based on the PropertyName argument of the attribute - Dim count = vm.Count - Console.WriteLine($"Count = {count}") + ' Properties can have differnt names generated based on the PropertyName argument of the attribute + Dim count = vm.Count + Console.WriteLine($"Count = {count}") - ' the viewmodel will automatically implement INotifyPropertyChanged - AddHandler vm.PropertyChanged, Sub(o, e) Console.WriteLine($"Property {e.PropertyName} was changed") - vm.Text = "abc" - vm.Count = 123 + ' the viewmodel will automatically implement INotifyPropertyChanged + AddHandler vm.PropertyChanged, Sub(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 attribute - ' You'll see the matching generated properties visibile in IntelliSense in realtime + ' Try adding fields to the ExampleViewModel class above and tagging them with the attribute + ' You'll see the matching generated properties visibile in IntelliSense in realtime - End Sub + End Sub -End Module \ No newline at end of file +End Module diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseCsvGenerator.vb b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseCsvGenerator.vb index 78c199326..f4c055835 100644 --- a/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseCsvGenerator.vb +++ b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseCsvGenerator.vb @@ -6,13 +6,13 @@ Imports CSV Friend Class UseCsvGenerator - Public Shared Sub Run() + Public Shared Sub Run() - Console.WriteLine("## CARS") - Cars.All.ToList().ForEach(Sub(c) Console.WriteLine(c.Brand & vbTab & c.Model & vbTab & c.Year & vbTab & c.Cc & vbTab & c.Favorite)) - Console.WriteLine(vbCr & "## PEOPLE") - People.All.ToList().ForEach(Sub(p) Console.WriteLine(p.Name & vbTab & p.Address & vbTab & p._11Age)) + Console.WriteLine("## CARS") + Cars.All.ToList().ForEach(Sub(c) Console.WriteLine(c.Brand & vbTab & c.Model & vbTab & c.Year & vbTab & c.Cc & vbTab & c.Favorite)) + Console.WriteLine(vbCr & "## PEOPLE") + People.All.ToList().ForEach(Sub(p) Console.WriteLine(p.Name & vbTab & p.Address & vbTab & p._11Age)) - End Sub + End Sub -End Class \ No newline at end of file +End Class diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.vb b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.vb index 78a173ca7..89fa11b47 100644 --- a/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.vb +++ b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.vb @@ -1,8 +1,8 @@ Public Module UseHelloWorldGenerator - Public Sub Run() - ' The static call below is generated at build time, and will list the syntax trees used in the compilation - HelloWorldGenerated.HelloWorld.SayHello() - End Sub + Public Sub Run() + ' The static call below is generated at build time, and will list the syntax trees used in the compilation + HelloWorldGenerated.HelloWorld.SayHello() + End Sub End Module diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseMustacheGenerator.vb b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseMustacheGenerator.vb deleted file mode 100644 index 3c9a97c41..000000000 --- a/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseMustacheGenerator.vb +++ /dev/null @@ -1,94 +0,0 @@ -Option Explicit On -Option Strict On -Option Infer On - -Imports GeneratedDemo.UseMustacheGenerator - - - - - - - -Friend Class UseMustacheGenerator - - Public Shared Sub Run() - Console.WriteLine(Mustache.Constants.Lottery) - Console.WriteLine(Mustache.Constants.HR) - Console.WriteLine(Mustache.Constants.HTML) - Console.WriteLine(Mustache.Constants.Section) - Console.WriteLine(Mustache.Constants.NestedSection) - End Sub - - ' Mustache templates and hashes from the manual at https://mustache.github.io/mustache.1.html... - Public Const t1 As String = " -Hello {{name}} -You have just won {{value}} dollars! -{{#in_ca}} -Well, {{taxed_value}} dollars, after taxes. -{{/in_ca}} -" - Public Const h1 As String = " -{ - ""name"": ""Chris"", - ""value"": 10000, - ""taxed_value"": 5000, - ""in_ca"": true -} -" - Public Const t2 As String = " -* {{name}} -* {{age}} -* {{company}} -* {{{company}}} -" - Public Const h2 As String = " -{ - ""name"": ""Chris"", - ""company"": ""GitHub"" -} -" - Public Const t3 As String = " - Shown - {{#person}} - Never shown! - {{/person}} - " - Public Const h3 As String = " -{ - ""person"": false -} -" - Public Const t4 As String = " -{{#repo}} - {{name}} -{{/repo}} -" - Public Const h4 As String = " -{ - ""repo"": [ - { ""name"": ""resque"" }, - { ""name"": ""hub"" }, - { ""name"": ""rip"" } - ] -} -" - Public Const t5 As String = " -{{#repo}} - {{name}} - {{#nested}} - NestedName: {{name}} - {{/nested}} -{{/repo}} -" - Public Const h5 As String = " -{ - ""repo"": [ - { ""name"": ""resque"", ""nested"":[{""name"":""nestedResque""}] }, - { ""name"": ""hub"" }, - { ""name"": ""rip"" } - ] -} -" - -End Class \ No newline at end of file diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.vb b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.vb index 0ace59e74..eb9a8e730 100644 --- a/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.vb +++ b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.vb @@ -2,24 +2,24 @@ Public Module UseXmlSettingsGenerator - Public Sub Run() + Public Sub Run() - ' This XmlSettings generator makes a static property in the XmlSettings class for each .xmlsettings file + ' 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 - Dim main As XmlSettings.MainSettings = XmlSettings.Main - Console.WriteLine($"Reading settings from {main.GetLocation()}") + ' here we have the 'Main' settings file from MainSettings.xmlsettings + ' the name is determined by the 'name' attribute of the root settings element + Dim main As XmlSettings.MainSettings = XmlSettings.Main + Console.WriteLine($"Reading settings from {main.GetLocation()}") - ' settings are strongly typed and can be read directly from the static instance - Dim firstRun As Boolean = XmlSettings.Main.FirstRun - Console.WriteLine($"Setting firstRun = {firstRun}") + ' settings are strongly typed and can be read directly from the static instance + Dim firstRun As Boolean = XmlSettings.Main.FirstRun + Console.WriteLine($"Setting firstRun = {firstRun}") - Dim cacheSize As Integer = XmlSettings.Main.CacheSize - Console.WriteLine($"Setting cacheSize = {cacheSize}") + Dim cacheSize As Integer = 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 + ' Try adding some keys to the settings file and see the settings become available to read from - End Sub + End Sub -End Module \ No newline at end of file +End Module diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/GeneratedDemo.vbproj b/samples/VisualBasic/SourceGenerators/GeneratedDemo/VisualBasicGeneratedDemo.vbproj similarity index 84% rename from samples/VisualBasic/SourceGenerators/GeneratedDemo/GeneratedDemo.vbproj rename to samples/VisualBasic/SourceGenerators/GeneratedDemo/VisualBasicGeneratedDemo.vbproj index 08120ed6f..6df6a390c 100644 --- a/samples/VisualBasic/SourceGenerators/GeneratedDemo/GeneratedDemo.vbproj +++ b/samples/VisualBasic/SourceGenerators/GeneratedDemo/VisualBasicGeneratedDemo.vbproj @@ -14,7 +14,7 @@ - + diff --git a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.vb b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.vb index 981ae965d..4c1ca4af8 100644 --- a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.vb +++ b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.vb @@ -1,4 +1,4 @@ -Option Explicit On +Option Explicit On Option Infer On Option Strict On @@ -11,11 +11,11 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace SourceGeneratorSamples - - Public Class AutoNotifyGenerator - Implements ISourceGenerator + + Public Class AutoNotifyGenerator + Implements ISourceGenerator - Private Const ATTRIBUTE_TEXT As String = " + Private Const ATTRIBUTE_TEXT As String = " Imports System Namespace Global.AutoNotify @@ -31,68 +31,68 @@ Namespace Global.AutoNotify End Namespace " - Public Sub Initialize(context As GeneratorInitializationContext) Implements ISourceGenerator.Initialize - ' Register a syntax receiver that will be created for each generation pass - context.RegisterForSyntaxNotifications(Function() As ISyntaxReceiver - Return New SyntaxReceiver - End Function) - End Sub - - Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute - - ' add the attribute text - context.AddSource("AutoNotifyAttribute", SourceText.From(ATTRIBUTE_TEXT, Encoding.UTF8)) - - ' retreive the populated receiver - Dim tempVar = TypeOf context.SyntaxReceiver Is SyntaxReceiver - Dim receiver = TryCast(context.SyntaxReceiver, SyntaxReceiver) - If Not tempVar Then - Return - End If - - ' 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. - Dim options1 = context.Compilation.SyntaxTrees.First().Options - Dim compilation1 = context.Compilation.AddSyntaxTrees(VisualBasicSyntaxTree.ParseText(SourceText.From(ATTRIBUTE_TEXT, Encoding.UTF8), CType(options1, VisualBasicParseOptions))) - - ' get the newly bound attribute, and INotifyPropertyChanged - Dim attributeSymbol = compilation1.GetTypeByMetadataName("AutoNotify.AutoNotifyAttribute") - Dim notifySymbol = compilation1.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged") - - ' loop over the candidate fields, and keep the ones that are actually annotated - Dim fieldSymbols As New List(Of IFieldSymbol) - - For Each field In receiver.CandidateFields - Dim model = compilation1.GetSemanticModel(field.SyntaxTree) - For Each variable In field.Declarators - For Each name In variable.Names - ' Get the symbol being decleared by the field, and keep it if its annotated - Dim fieldSymbol = TryCast(model.GetDeclaredSymbol(name), IFieldSymbol) - If fieldSymbol.GetAttributes().Any(Function(ad) ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.[Default])) Then - fieldSymbols.Add(fieldSymbol) + Public Sub Initialize(context As GeneratorInitializationContext) Implements ISourceGenerator.Initialize + ' Register a syntax receiver that will be created for each generation pass + context.RegisterForSyntaxNotifications(Function() As ISyntaxReceiver + Return New SyntaxReceiver + End Function) + End Sub + + Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute + + ' add the attribute text + context.AddSource("AutoNotifyAttribute", SourceText.From(ATTRIBUTE_TEXT, Encoding.UTF8)) + + ' retreive the populated receiver + Dim tempVar = TypeOf context.SyntaxReceiver Is SyntaxReceiver + Dim receiver = TryCast(context.SyntaxReceiver, SyntaxReceiver) + If Not tempVar Then + Return End If - Next - Next - Next - ' group the fields by class, and generate the source - For Each group In fieldSymbols.GroupBy(Function(f) f.ContainingType) - Dim classSource = ProcessClass(group.Key, group.ToList(), attributeSymbol, notifySymbol) - context.AddSource($"{group.Key.Name}_AutoNotify.vb", SourceText.From(classSource, Encoding.UTF8)) - Next + ' 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. + Dim options1 = context.Compilation.SyntaxTrees.First().Options + Dim compilation1 = context.Compilation.AddSyntaxTrees(VisualBasicSyntaxTree.ParseText(SourceText.From(ATTRIBUTE_TEXT, Encoding.UTF8), CType(options1, VisualBasicParseOptions))) + + ' get the newly bound attribute, and INotifyPropertyChanged + Dim attributeSymbol = compilation1.GetTypeByMetadataName("AutoNotify.AutoNotifyAttribute") + Dim notifySymbol = compilation1.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged") + + ' loop over the candidate fields, and keep the ones that are actually annotated + Dim fieldSymbols As New List(Of IFieldSymbol) + + For Each field In receiver.CandidateFields + Dim model = compilation1.GetSemanticModel(field.SyntaxTree) + For Each variable In field.Declarators + For Each name In variable.Names + ' Get the symbol being decleared by the field, and keep it if its annotated + Dim fieldSymbol = TryCast(model.GetDeclaredSymbol(name), IFieldSymbol) + If fieldSymbol.GetAttributes().Any(Function(ad) ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.[Default])) Then + fieldSymbols.Add(fieldSymbol) + End If + Next + Next + Next + + ' group the fields by class, and generate the source + For Each group In fieldSymbols.GroupBy(Function(f) f.ContainingType, SymbolEqualityComparer.Default) + Dim classSource = ProcessClass(CType(group.Key, INamedTypeSymbol), group.ToList(), attributeSymbol, notifySymbol) + context.AddSource($"{group.Key.Name}_AutoNotify.vb", SourceText.From(classSource, Encoding.UTF8)) + Next - End Sub + End Sub - Private Function ProcessClass(classSymbol As INamedTypeSymbol, fields As List(Of IFieldSymbol), attributeSymbol As ISymbol, notifySymbol As ISymbol) As String + Private Function ProcessClass(classSymbol As INamedTypeSymbol, fields As List(Of IFieldSymbol), attributeSymbol As ISymbol, notifySymbol As ISymbol) As String - If Not classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.[Default]) Then - Return Nothing 'TODO: issue a diagnostic that it must be top level - End If + If Not classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.[Default]) Then + Return Nothing 'TODO: issue a diagnostic that it must be top level + End If - Dim namespaceName = classSymbol.ContainingNamespace.ToDisplayString() + Dim namespaceName = classSymbol.ContainingNamespace.ToDisplayString() - ' begin building the generated source - Dim source = New StringBuilder($"Option Explicit On + ' begin building the generated source + Dim source = New StringBuilder($"Option Explicit On Option Strict On Option Infer On @@ -103,63 +103,63 @@ Namespace Global.{namespaceName} ") - ' if the class doesn't implement INotifyPropertyChanged already, add it - If Not classSymbol.Interfaces.Contains(CType(notifySymbol, INamedTypeSymbol)) Then - source.Append(" Public Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged + ' if the class doesn't implement INotifyPropertyChanged already, add it + If Not classSymbol.Interfaces.Contains(CType(notifySymbol, INamedTypeSymbol)) Then + source.Append(" Public Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged ") - End If + End If - ' create properties for each field - For Each fieldSymbol In fields - ProcessField(source, fieldSymbol, attributeSymbol) - Next + ' create properties for each field + For Each fieldSymbol In fields + ProcessField(source, fieldSymbol, attributeSymbol) + Next - source.Append(" + source.Append(" End Class End Namespace") - Return source.ToString() + Return source.ToString() - End Function + End Function - Private Sub ProcessField(source As StringBuilder, fieldSymbol As IFieldSymbol, attributeSymbol As ISymbol) + Private Sub ProcessField(source As StringBuilder, fieldSymbol As IFieldSymbol, attributeSymbol As ISymbol) - Dim chooseName As Func(Of String, TypedConstant, String) = + Dim chooseName As Func(Of String, TypedConstant, String) = Function(fieldName1 As String, overridenNameOpt1 As TypedConstant) As String - If Not overridenNameOpt1.IsNull Then - Return overridenNameOpt1.Value.ToString() - End If + If Not overridenNameOpt1.IsNull Then + Return overridenNameOpt1.Value.ToString() + End If - fieldName1 = fieldName1.TrimStart("_"c) - If fieldName1.Length = 0 Then - Return String.Empty - End If + fieldName1 = fieldName1.TrimStart("_"c) + If fieldName1.Length = 0 Then + Return String.Empty + End If - If fieldName1.Length = 1 Then - Return fieldName1.ToUpper() - End If + If fieldName1.Length = 1 Then + Return fieldName1.ToUpper() + End If - Return fieldName1.Substring(0, 1).ToUpper() & fieldName1.Substring(1) + Return fieldName1.Substring(0, 1).ToUpper() & fieldName1.Substring(1) End Function - ' get the name and type of the field - Dim fieldName = fieldSymbol.Name - Dim fieldType = fieldSymbol.Type + ' get the name and type of the field + Dim fieldName = fieldSymbol.Name + Dim fieldType = fieldSymbol.Type - ' get the AutoNotify attribute from the field, and any associated data - Dim attributeData = fieldSymbol.GetAttributes().[Single](Function(ad) ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.[Default])) - Dim overridenNameOpt = attributeData.NamedArguments.SingleOrDefault(Function(kvp) kvp.Key = "PropertyName").Value + ' get the AutoNotify attribute from the field, and any associated data + Dim attributeData = fieldSymbol.GetAttributes().[Single](Function(ad) ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.[Default])) + Dim overridenNameOpt = attributeData.NamedArguments.SingleOrDefault(Function(kvp) kvp.Key = "PropertyName").Value - Dim propertyName = chooseName(fieldName, overridenNameOpt) - If propertyName.Length = 0 OrElse propertyName = fieldName Then - 'TODO: issue a diagnostic that we can't process this field - Return - End If + Dim propertyName = chooseName(fieldName, overridenNameOpt) + If propertyName.Length = 0 OrElse propertyName = fieldName Then + 'TODO: issue a diagnostic that we can't process this field + Return + End If - source.Append($" + source.Append($" Public Property {propertyName} As {fieldType} Get Return Me.{fieldName} @@ -171,31 +171,31 @@ End Namespace") End Property ") - End Sub - - ''' - ''' Created on demand before each generation pass - ''' - Class SyntaxReceiver - Implements ISyntaxReceiver - - Public ReadOnly Property CandidateFields As List(Of FieldDeclarationSyntax) = New List(Of FieldDeclarationSyntax) - - ''' - ''' Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation - ''' - Public Sub OnVisitSyntaxNode(syntaxNode As SyntaxNode) Implements ISyntaxReceiver.OnVisitSyntaxNode - ' any field with at least one attribute is a candidate for property generation - If TypeOf syntaxNode Is FieldDeclarationSyntax Then - Dim fieldDeclarationSyntax = TryCast(syntaxNode, FieldDeclarationSyntax) - If fieldDeclarationSyntax.AttributeLists.Count > 0 Then - CandidateFields.Add(fieldDeclarationSyntax) - End If - End If - End Sub + End Sub + + ''' + ''' Created on demand before each generation pass + ''' + Class SyntaxReceiver + Implements ISyntaxReceiver + + Public ReadOnly Property CandidateFields As List(Of FieldDeclarationSyntax) = New List(Of FieldDeclarationSyntax) + + ''' + ''' Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation + ''' + Public Sub OnVisitSyntaxNode(syntaxNode As SyntaxNode) Implements ISyntaxReceiver.OnVisitSyntaxNode + ' any field with at least one attribute is a candidate for property generation + If TypeOf syntaxNode Is FieldDeclarationSyntax Then + Dim fieldDeclarationSyntax = TryCast(syntaxNode, FieldDeclarationSyntax) + If fieldDeclarationSyntax.AttributeLists.Count > 0 Then + CandidateFields.Add(fieldDeclarationSyntax) + End If + End If + End Sub + + End Class End Class - End Class - -End Namespace \ No newline at end of file +End Namespace diff --git a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/CsvGenerator.vb b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/CsvGenerator.vb index d474fdca2..fa7d13b90 100644 --- a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/CsvGenerator.vb +++ b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/CsvGenerator.vb @@ -14,72 +14,72 @@ Imports NotVisualBasic.FileIO Namespace SourceGeneratorSamples - - Public Class CsvGenerator - Implements ISourceGenerator - - Public Enum CsvLoadType - Startup - OnDemand - End Enum - - Public Sub Initialize(context As GeneratorInitializationContext) Implements Microsoft.CodeAnalysis.ISourceGenerator.Initialize - - End Sub - - Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute - Dim options As IEnumerable(Of (CsvLoadType, Boolean, AdditionalText)) = GetLoadOptions(context) - Dim nameCodeSequence As IEnumerable(Of (Name As String, Code As String)) = SourceFilesFromAdditionalFiles(options) - For Each entry In nameCodeSequence - context.AddSource($"Csv_{entry.Name}", SourceText.From(entry.Code, Encoding.UTF8)) - Next - End Sub - - ' Guesses type of property for the object from the value of a csv field - Public Shared Function GetCsvFieldType(exemplar As String) As String - Dim garbageBoolean As Boolean - Dim garbageInteger As Integer - Dim garbageDouble As Double - Select Case True - Case Boolean.TryParse(exemplar, garbageBoolean) : Return "Boolean" - Case Integer.TryParse(exemplar, garbageInteger) : Return "Integer" - Case Double.TryParse(exemplar, garbageDouble) : Return "Double" - Case Else : Return "String" - End Select - End Function - - ' Examines the header row and the first row in the csv file to gather all header types and names - ' Also it returns the first row of data, because it must be read to figure out the types, - ' As the CsvTextFieldParser cannot 'Peek' ahead of one line. If there is no first line, - ' it consider all properties as strings. The generator returns an empty list of properly - ' typed objects in such case. If the file is completely empty, an error is generated. - Public Shared Function ExtractProperties(parser As CsvTextFieldParser) As (Types As String(), Names As String(), Fields As String()) - - Dim headerFields = parser.ReadFields() - If headerFields Is Nothing Then - Throw New Exception("Empty csv file!") - End If - - Dim firstLineFields = parser.ReadFields() - If firstLineFields Is Nothing Then - Return (Enumerable.Repeat("String", headerFields.Length).ToArray(), headerFields, firstLineFields) - Else - Return (firstLineFields.[Select](Function(field) GetCsvFieldType(field)).ToArray(), headerFields.[Select](New Func(Of String, String)(AddressOf StringToValidPropertyName)).ToArray(), firstLineFields) - End If - - End Function - - ' Adds a class to the `CSV` namespace for each `csv` file passed in. The class has a static property - ' named `All` that returns the list of strongly typed objects generated on demand at first access. - ' There is the slight chance of a race condition in a multi-thread program, but the result is relatively benign - ' , loading the collection multiple times instead of once. Measures could be taken to avoid that. - Public Shared Function GenerateClassFile(className As String, csvText As String, loadTime As CsvLoadType, cacheObjects As Boolean) As String - - Dim sb As New StringBuilder - Dim parser As New CsvTextFieldParser(New StringReader(csvText)) - - ''' Imports - sb.Append("Option Explicit On + + Public Class CsvGenerator + Implements ISourceGenerator + + Public Enum CsvLoadType + Startup + OnDemand + End Enum + + Public Sub Initialize(context As GeneratorInitializationContext) Implements Microsoft.CodeAnalysis.ISourceGenerator.Initialize + + End Sub + + Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute + Dim options As IEnumerable(Of (CsvLoadType, Boolean, AdditionalText)) = GetLoadOptions(context) + Dim nameCodeSequence As IEnumerable(Of (Name As String, Code As String)) = SourceFilesFromAdditionalFiles(options) + For Each entry In nameCodeSequence + context.AddSource($"Csv_{entry.Name}", SourceText.From(entry.Code, Encoding.UTF8)) + Next + End Sub + + ' Guesses type of property for the object from the value of a csv field + Public Shared Function GetCsvFieldType(exemplar As String) As String + Dim garbageBoolean As Boolean + Dim garbageInteger As Integer + Dim garbageDouble As Double + Select Case True + Case Boolean.TryParse(exemplar, garbageBoolean) : Return "Boolean" + Case Integer.TryParse(exemplar, garbageInteger) : Return "Integer" + Case Double.TryParse(exemplar, garbageDouble) : Return "Double" + Case Else : Return "String" + End Select + End Function + + ' Examines the header row and the first row in the csv file to gather all header types and names + ' Also it returns the first row of data, because it must be read to figure out the types, + ' As the CsvTextFieldParser cannot 'Peek' ahead of one line. If there is no first line, + ' it consider all properties as strings. The generator returns an empty list of properly + ' typed objects in such case. If the file is completely empty, an error is generated. + Public Shared Function ExtractProperties(parser As CsvTextFieldParser) As (Types As String(), Names As String(), Fields As String()) + + Dim headerFields = parser.ReadFields() + If headerFields Is Nothing Then + Throw New Exception("Empty csv file!") + End If + + Dim firstLineFields = parser.ReadFields() + If firstLineFields Is Nothing Then + Return (Enumerable.Repeat("String", headerFields.Length).ToArray(), headerFields, firstLineFields) + Else + Return (firstLineFields.[Select](Function(field) GetCsvFieldType(field)).ToArray(), headerFields.[Select](New Func(Of String, String)(AddressOf StringToValidPropertyName)).ToArray(), firstLineFields) + End If + + End Function + + ' Adds a class to the `CSV` namespace for each `csv` file passed in. The class has a static property + ' named `All` that returns the list of strongly typed objects generated on demand at first access. + ' There is the slight chance of a race condition in a multi-thread program, but the result is relatively benign + ' , loading the collection multiple times instead of once. Measures could be taken to avoid that. + Public Shared Function GenerateClassFile(className As String, csvText As String, loadTime As CsvLoadType, cacheObjects As Boolean) As String + + Dim sb As New StringBuilder + Dim parser As New CsvTextFieldParser(New StringReader(csvText)) + + ''' Imports + sb.Append("Option Explicit On Option Strict On Option Infer On @@ -88,121 +88,121 @@ Imports System.Collections.Generic Namespace Global.CSV ") - ''' Class Definition - sb.Append($" + ''' Class Definition + sb.Append($" Public Class {className} ") - If loadTime = CsvLoadType.Startup Then - sb.Append($" Shared Sub New() + If loadTime = CsvLoadType.Startup Then + sb.Append($" Shared Sub New() Dim x = All End Sub ") - End If + End If - Dim tupleTemp = ExtractProperties(parser) : Dim types = tupleTemp.Types, names = tupleTemp.Names, fields = tupleTemp.Fields - Dim minLen = Math.Min(types.Length, names.Length) + Dim tupleTemp = ExtractProperties(parser) : Dim types = tupleTemp.Types, names = tupleTemp.Names, fields = tupleTemp.Fields + Dim minLen = Math.Min(types.Length, names.Length) - For i = 0 To minLen - 1 - sb.AppendLine($" Public Property {StringToValidPropertyName(names(i))} As {types(i)}") - Next + For i = 0 To minLen - 1 + sb.AppendLine($" Public Property {StringToValidPropertyName(names(i))} As {types(i)}") + Next - ''' Loading data - sb.Append($" + ''' Loading data + sb.Append($" Private Shared m_all As IEnumerable(Of {className}) Public Shared ReadOnly Property All As IEnumerable(Of {className}) Get ") - If cacheObjects Then - sb.Append(" If m_all IsNot Nothing Then + If cacheObjects Then + sb.Append(" If m_all IsNot Nothing Then Return m_all End If ") - End If + End If - sb.Append($" Dim l As New List(Of {className})() + sb.Append($" Dim l As New List(Of {className})() Dim c As {className} ") - Do + Do - If fields Is Nothing Then - Continue Do - End If - If fields.Length < minLen Then - Throw New Exception("Not enough fields in CSV file.") - End If + If fields Is Nothing Then + Continue Do + End If + If fields.Length < minLen Then + Throw New Exception("Not enough fields in CSV file.") + End If - sb.AppendLine($" c = New {className}()") + sb.AppendLine($" c = New {className}()") - Dim value As String '= "" - For i As Integer = 0 To minLen - 1 - ' Wrap strings in quotes. - value = If(GetCsvFieldType(fields(i)) = "String", $"""{fields(i).Trim().Trim(New Char() {""""c})}""", fields(i)) - sb.AppendLine($" c.{names(i)} = {value}") - Next + Dim value As String '= "" + For i As Integer = 0 To minLen - 1 + ' Wrap strings in quotes. + value = If(GetCsvFieldType(fields(i)) = "String", $"""{fields(i).Trim().Trim(New Char() {""""c})}""", fields(i)) + sb.AppendLine($" c.{names(i)} = {value}") + Next - sb.AppendLine(" l.Add(c)") + sb.AppendLine(" l.Add(c)") - fields = parser.ReadFields() + fields = parser.ReadFields() - Loop While fields IsNot Nothing + Loop While fields IsNot Nothing - sb.Append($" m_all = l + sb.Append($" m_all = l Return l ") - ' Close things (property, class, namespace) - sb.Append(" End Get + ' Close things (property, class, namespace) + sb.Append(" End Get End Property End Class End Namespace") - Return sb.ToString() - - End Function - - Private Shared Function StringToValidPropertyName(s As String) As String - s = s.Trim() - s = If(Char.IsLetter(s(0)), Char.ToUpper(s(0)) & s.Substring(1), s) - s = If(Char.IsDigit(s.Trim()(0)), "_" & s, s) - s = New String(s.[Select](Function(ch) If(Char.IsDigit(ch) OrElse Char.IsLetter(ch), ch, "_"c)).ToArray()) - Return s - End Function - - Private Shared Function SourceFilesFromAdditionalFile(loadType As CsvLoadType, cacheObjects As Boolean, file As AdditionalText) As IEnumerable(Of (Name As String, Code As String)) - Dim className = Path.GetFileNameWithoutExtension(file.Path) - Dim csvText = file.GetText().ToString() - Return New(String, String)() {(className, GenerateClassFile(className, csvText, loadType, cacheObjects))} - End Function - - Private Shared Function SourceFilesFromAdditionalFiles(pathsData As IEnumerable(Of (LoadType As CsvLoadType, CacheObjects As Boolean, File As AdditionalText))) As IEnumerable(Of (Name As String, Code As String)) - Return pathsData.SelectMany(Function(d) SourceFilesFromAdditionalFile(d.LoadType, d.CacheObjects, d.File)) - End Function - - Private Shared Iterator Function GetLoadOptions(context As GeneratorExecutionContext) As IEnumerable(Of (LoadType As CsvLoadType, CacheObjects As Boolean, File As AdditionalText)) - For Each file In context.AdditionalFiles - If Path.GetExtension(file.Path).Equals(".csv", StringComparison.OrdinalIgnoreCase) Then - ' are there any options for it? - Dim loadTimeString As String = Nothing - context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CsvLoadType", loadTimeString) - Dim loadType As CsvLoadType = Nothing - [Enum].TryParse(loadTimeString, ignoreCase:=True, loadType) - Dim cacheObjectsString As String = Nothing - context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CacheObjects", cacheObjectsString) - Dim cacheObjects As Boolean = Nothing - Boolean.TryParse(cacheObjectsString, cacheObjects) - Yield (loadType, cacheObjects, file) - End If - Next - End Function - - End Class - -End Namespace \ No newline at end of file + Return sb.ToString() + + End Function + + Private Shared Function StringToValidPropertyName(s As String) As String + s = s.Trim() + s = If(Char.IsLetter(s(0)), Char.ToUpper(s(0)) & s.Substring(1), s) + s = If(Char.IsDigit(s.Trim()(0)), "_" & s, s) + s = New String(s.[Select](Function(ch) If(Char.IsDigit(ch) OrElse Char.IsLetter(ch), ch, "_"c)).ToArray()) + Return s + End Function + + Private Shared Function SourceFilesFromAdditionalFile(loadType As CsvLoadType, cacheObjects As Boolean, file As AdditionalText) As IEnumerable(Of (Name As String, Code As String)) + Dim className = Path.GetFileNameWithoutExtension(file.Path) + Dim csvText = file.GetText().ToString() + Return New(String, String)() {(className, GenerateClassFile(className, csvText, loadType, cacheObjects))} + End Function + + Private Shared Function SourceFilesFromAdditionalFiles(pathsData As IEnumerable(Of (LoadType As CsvLoadType, CacheObjects As Boolean, File As AdditionalText))) As IEnumerable(Of (Name As String, Code As String)) + Return pathsData.SelectMany(Function(d) SourceFilesFromAdditionalFile(d.LoadType, d.CacheObjects, d.File)) + End Function + + Private Shared Iterator Function GetLoadOptions(context As GeneratorExecutionContext) As IEnumerable(Of (LoadType As CsvLoadType, CacheObjects As Boolean, File As AdditionalText)) + For Each file In context.AdditionalFiles + If Path.GetExtension(file.Path).Equals(".csv", StringComparison.OrdinalIgnoreCase) Then + ' are there any options for it? + Dim loadTimeString As String = Nothing + context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CsvLoadType", loadTimeString) + Dim loadType As CsvLoadType = Nothing + [Enum].TryParse(loadTimeString, ignoreCase:=True, loadType) + Dim cacheObjectsString As String = Nothing + context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CacheObjects", cacheObjectsString) + Dim cacheObjects As Boolean = Nothing + Boolean.TryParse(cacheObjectsString, cacheObjects) + Yield (loadType, cacheObjects, file) + End If + Next + End Function + + End Class + +End Namespace diff --git a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.vb b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.vb index 792c3b62a..ff4c51ba2 100644 --- a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.vb +++ b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.vb @@ -9,19 +9,19 @@ Imports Microsoft.CodeAnalysis.Text Namespace SourceGeneratorSamples - - Public Class HelloWorldGenerator - Implements ISourceGenerator + + Public Class HelloWorldGenerator + Implements ISourceGenerator - Public Sub Initialize(context As GeneratorInitializationContext) Implements ISourceGenerator.Initialize - ' No initialization required - End Sub + Public Sub Initialize(context As GeneratorInitializationContext) Implements ISourceGenerator.Initialize + ' No initialization required + End Sub - Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute + Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute - ' begin creating the source we'll inject into the users compilation + ' begin creating the source we'll inject into the users compilation - Dim sourceBuilder = New StringBuilder("Option Explicit On + Dim sourceBuilder = New StringBuilder("Option Explicit On Option Strict On Option Infer On @@ -35,19 +35,19 @@ Namespace Global.HelloWorldGenerated Console.WriteLine(""The following syntax trees existed in the compilation that created this program:"") ") - ' for testing... let's include a comment with the current date/time. - sourceBuilder.AppendLine($" ' Generated at {DateTime.Now}") + ' for testing... let's include a comment with the current date/time. + sourceBuilder.AppendLine($" ' Generated at {DateTime.Now}") - ' using the context, get a list of syntax trees in the users compilation - ' add the filepath of each tree to the class we're building + ' using the context, get a list of syntax trees in the users compilation + ' add the filepath of each tree to the class we're building - For Each tree In context.Compilation.SyntaxTrees - sourceBuilder.AppendLine($" Console.WriteLine("" - {tree.FilePath}"")") - Next + For Each tree In context.Compilation.SyntaxTrees + sourceBuilder.AppendLine($" Console.WriteLine("" - {tree.FilePath}"")") + Next - ' finish creating the source to inject + ' finish creating the source to inject - sourceBuilder.Append(" + sourceBuilder.Append(" End Sub @@ -55,12 +55,12 @@ Namespace Global.HelloWorldGenerated End Namespace") - ' inject the created source into the users compilation + ' inject the created source into the users compilation - context.AddSource("HelloWorldGenerated", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)) + context.AddSource("HelloWorldGenerated", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)) - End Sub + End Sub - End Class + End Class -End Namespace \ No newline at end of file +End Namespace diff --git a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.vb b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.vb deleted file mode 100644 index 38004dbe7..000000000 --- a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.vb +++ /dev/null @@ -1,146 +0,0 @@ -Option Explicit On -Option Infer On -Option Strict On - -Imports System.Collections.Immutable -Imports System.Text - -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Text - -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax - -Namespace SourceGeneratorSamples - - - Public Class MustacheGenerator - Implements ISourceGenerator - - Public Sub Initialize(context As GeneratorInitializationContext) Implements ISourceGenerator.Initialize - ' No initialization required - End Sub - - Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute - - Dim attributeSource = "Option Explicit On -Option Strict On -Option Infer On - -Namespace Global - - - Friend NotInheritable Class MustacheAttribute - Inherits System.Attribute - - Public ReadOnly Property Name As String - Public ReadOnly Property Template As String - Public ReadOnly Property Hash As String - - Public Sub New(name As String, template As String, hash As String) - Me.Name = name - Me.Template = template - Me.Hash = hash - End Sub - - End Class - -End Namespace -" - - context.AddSource("Mustache_MainAttributes__", SourceText.From(attributeSource, Encoding.UTF8)) - - Dim compilation = context.Compilation - - Dim options = GetMustacheOptions(compilation) - Dim namesSources = SourceFilesFromMustachePaths(options) - - For Each entry In namesSources - context.AddSource($"Mustache{entry.Item1}", SourceText.From(entry.Item2, Encoding.UTF8)) - Next - - End Sub - - Private Shared Iterator Function GetMustacheOptions(compilation As Compilation) As IEnumerable(Of (String, String, String)) - - ' Get all Mustache attributes - - Dim allNodes = compilation.SyntaxTrees.SelectMany(Function(s) s.GetRoot().DescendantNodes()) - - - Dim allAttributes = allNodes.Where(Function(d) d.IsKind(SyntaxKind.Attribute)).OfType(Of AttributeSyntax)() - Dim attributes = allAttributes.Where(Function(d) d.Name.ToString() = "Mustache").ToImmutableArray() - - Dim models = compilation.SyntaxTrees.[Select](Function(st) compilation.GetSemanticModel(st)) - For Each att In attributes - - Dim mustacheName = "" - Dim template = "" - Dim hash = "" - Dim index = 0 - - If att.ArgumentList Is Nothing Then - Throw New Exception("Can't be null here") - End If - - Dim m = compilation.GetSemanticModel(att.SyntaxTree) - - For Each arg In att.ArgumentList.Arguments - - Dim expr As ExpressionSyntax = Nothing - If TypeOf arg Is SimpleArgumentSyntax Then - expr = TryCast(arg, SimpleArgumentSyntax).Expression - End If - If expr Is Nothing Then - Continue For - End If - - Dim t = m.GetTypeInfo(expr) - Dim v = m.GetConstantValue(expr) - If index = 0 Then - mustacheName = v.ToString() - ElseIf index = 1 Then - template = v.ToString() - Else - hash = v.ToString() - End If - index += 1 - - Next - - Yield (mustacheName, template, hash) - - Next - - End Function - - Private Shared Function SourceFileFromMustachePath(name As String, template As String, hash As String) As String - Dim tree = HandlebarsDotNet.Handlebars.Compile(template) - Dim o = Newtonsoft.Json.JsonConvert.DeserializeObject(hash) - Dim mustacheText = tree(o) - Return GenerateMustacheClass(name, mustacheText) - End Function - - Private Shared Iterator Function SourceFilesFromMustachePaths(pathsData As IEnumerable(Of (Name As String, Template As String, Hash As String))) As IEnumerable(Of (Name As String, Code As String)) - For Each entry In pathsData - Yield (entry.Name, SourceFileFromMustachePath(entry.Name, entry.Template, entry.Hash)) - Next - End Function - - Private Shared Function GenerateMustacheClass(className As String, mustacheText As String) As String - Return $" - -Namespace Global.Mustache - - Partial Public Module Constants - - Public Const {className} As String = ""{mustacheText.Replace("""", """""")}"" - - End Module - -End Namespace" - End Function - - End Class - -End Namespace \ No newline at end of file diff --git a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.vb b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.vb index 1754315d8..d1ecd628f 100644 --- a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.vb +++ b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.vb @@ -11,38 +11,38 @@ Imports Microsoft.CodeAnalysis.Text Namespace SourceGeneratorSamples - - Public Class SettingsXmlGenerator - Implements ISourceGenerator - - Public Sub Initialize(context As GeneratorInitializationContext) Implements ISourceGenerator.Initialize - - End Sub - - Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute - ' Using the context, get any additional files that end in .xmlsettings - For Each settingsFile In context.AdditionalFiles.Where(Function(at) at.Path.EndsWith(".xmlsettings")) - ProcessSettingsFile(settingsFile, context) - Next - End Sub - - Private Sub ProcessSettingsFile(xmlFile As AdditionalText, context As GeneratorExecutionContext) - - ' try and load the settings file - Dim xmlDoc As New XmlDocument - Dim text = xmlFile.GetText(context.CancellationToken).ToString() - Try - xmlDoc.LoadXml(text) - Catch - 'TODO: issue a diagnostic that says we couldn't parse it - Return - End Try - - ' create a class in the XmlSetting class that represnts this entry, and a static field that contains a singleton instance. - Dim fileName = Path.GetFileName(xmlFile.Path) - Dim name = xmlDoc.DocumentElement.GetAttribute("name") - - Dim sb = New StringBuilder($"Option Explicit On + + Public Class SettingsXmlGenerator + Implements ISourceGenerator + + Public Sub Initialize(context As GeneratorInitializationContext) Implements ISourceGenerator.Initialize + + End Sub + + Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute + ' Using the context, get any additional files that end in .xmlsettings + For Each settingsFile In context.AdditionalFiles.Where(Function(at) at.Path.EndsWith(".xmlsettings")) + ProcessSettingsFile(settingsFile, context) + Next + End Sub + + Private Sub ProcessSettingsFile(xmlFile As AdditionalText, context As GeneratorExecutionContext) + + ' try and load the settings file + Dim xmlDoc As New XmlDocument + Dim text = xmlFile.GetText(context.CancellationToken).ToString() + Try + xmlDoc.LoadXml(text) + Catch + 'TODO: issue a diagnostic that says we couldn't parse it + Return + End Try + + ' create a class in the XmlSetting class that represnts this entry, and a static field that contains a singleton instance. + Dim fileName = Path.GetFileName(xmlFile.Path) + Dim name = xmlDoc.DocumentElement.GetAttribute("name") + + Dim sb = New StringBuilder($"Option Explicit On Option Strict On Option Infer On @@ -69,13 +69,13 @@ Namespace Global.AutoSettings Return m_fileName End Function") - For i = 0 To xmlDoc.DocumentElement.ChildNodes.Count - 1 + For i = 0 To xmlDoc.DocumentElement.ChildNodes.Count - 1 - Dim setting = CType(xmlDoc.DocumentElement.ChildNodes(i), XmlElement) - Dim settingName = setting.GetAttribute("name") - Dim settingType = setting.GetAttribute("type") + Dim setting = CType(xmlDoc.DocumentElement.ChildNodes(i), XmlElement) + Dim settingName = setting.GetAttribute("name") + Dim settingType = setting.GetAttribute("type") - sb.Append($" + sb.Append($" Public ReadOnly Property {settingName} As {settingType} Get @@ -83,9 +83,9 @@ Namespace Global.AutoSettings End Get End Property") - Next + Next - sb.Append(" + sb.Append(" End Class @@ -93,10 +93,10 @@ Namespace Global.AutoSettings End Namespace") - context.AddSource($"Settings_{name}", SourceText.From(sb.ToString(), Encoding.UTF8)) + context.AddSource($"Settings_{name}", SourceText.From(sb.ToString(), Encoding.UTF8)) - End Sub + End Sub - End Class + End Class -End Namespace \ No newline at end of file +End Namespace diff --git a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.vbproj b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/VisualBasicSourceGeneratorSamples.vbproj similarity index 61% rename from samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.vbproj rename to samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/VisualBasicSourceGeneratorSamples.vbproj index f719a9496..9e9f12565 100644 --- a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.vbproj +++ b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/VisualBasicSourceGeneratorSamples.vbproj @@ -5,15 +5,13 @@ - + - - @@ -23,8 +21,6 @@ - -