diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/CSharpGeneratedDemo.csproj b/samples/CSharp/SourceGenerators/GeneratedDemo/CSharpGeneratedDemo.csproj
index 4b3054e9f..650c9bad6 100644
--- a/samples/CSharp/SourceGenerators/GeneratedDemo/CSharpGeneratedDemo.csproj
+++ b/samples/CSharp/SourceGenerators/GeneratedDemo/CSharpGeneratedDemo.csproj
@@ -3,6 +3,7 @@
Exe
netcoreapp3.1
+ 10.0
diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/Program.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/Program.cs
index 297582c51..49f29005e 100644
--- a/samples/CSharp/SourceGenerators/GeneratedDemo/Program.cs
+++ b/samples/CSharp/SourceGenerators/GeneratedDemo/Program.cs
@@ -1,29 +1,28 @@
using System;
-namespace GeneratedDemo
+namespace GeneratedDemo;
+
+class Program
{
- class Program
+ static void Main()
{
- static void Main(string[] args)
- {
- // Run the various scenarios
- Console.WriteLine("Running HelloWorld:\n");
- UseHelloWorldGenerator.Run();
+ // Run the various scenarios
+ Console.WriteLine("Running HelloWorld:\n");
+ UseHelloWorldGenerator.Run();
- Console.WriteLine("\n\nRunning AutoNotify:\n");
- UseAutoNotifyGenerator.Run();
+ Console.WriteLine("\n\nRunning AutoNotify:\n");
+ UseAutoNotifyGenerator.Run();
- Console.WriteLine("\n\nRunning XmlSettings:\n");
- UseXmlSettingsGenerator.Run();
+ Console.WriteLine("\n\nRunning XmlSettings:\n");
+ UseXmlSettingsGenerator.Run();
- Console.WriteLine("\n\nRunning CsvGenerator:\n");
- UseCsvGenerator.Run();
+ Console.WriteLine("\n\nRunning CsvGenerator:\n");
+ UseCsvGenerator.Run();
- Console.WriteLine("\n\nRunning MustacheGenerator:\n");
- UseMustacheGenerator.Run();
+ Console.WriteLine("\n\nRunning MustacheGenerator:\n");
+ UseMustacheGenerator.Run();
- Console.WriteLine("\n\nRunning MathsGenerator:\n");
- UseMathsGenerator.Run();
- }
+ Console.WriteLine("\n\nRunning MathsGenerator:\n");
+ UseMathsGenerator.Run();
}
}
diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs
index 77e346c9a..0325b80b8 100644
--- a/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs
+++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs
@@ -1,39 +1,38 @@
using System;
using AutoNotify;
-namespace GeneratedDemo
+namespace GeneratedDemo;
+
+// The view model we'd like to augment
+public partial class ExampleViewModel
{
- // The view model we'd like to augment
- public partial class ExampleViewModel
- {
- [AutoNotify]
- private string _text = "private field text";
+ [AutoNotify]
+ private string _text = "private field text";
- [AutoNotify(PropertyName = "Count")]
- private int _amount = 5;
- }
+ [AutoNotify(PropertyName = "Count")]
+ private int _amount = 5;
+}
- public static class UseAutoNotifyGenerator
+public static class UseAutoNotifyGenerator
+{
+ public static void Run()
{
- 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
- }
+ 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/UseCsvGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseCsvGenerator.cs
index 7f7291d0e..b619a90c7 100644
--- a/samples/CSharp/SourceGenerators/GeneratedDemo/UseCsvGenerator.cs
+++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseCsvGenerator.cs
@@ -5,16 +5,15 @@
using static System.Console;
using CSV;
-namespace GeneratedDemo
+namespace GeneratedDemo;
+
+class UseCsvGenerator
{
- class UseCsvGenerator
+ public static void Run()
{
- public static void Run()
- {
- WriteLine("## CARS");
- Cars.All.ToList().ForEach(c => WriteLine($"{c.Brand}\t{c.Model}\t{c.Year}\t{c.Cc}"));
- WriteLine("\n## PEOPLE");
- People.All.ToList().ForEach(p => WriteLine($"{p.Name}\t{p.Address}\t{p._11Age}"));
- }
+ WriteLine("## CARS");
+ Cars.All.ToList().ForEach(c => WriteLine($"{c.Brand}\t{c.Model}\t{c.Year}\t{c.Cc}"));
+ WriteLine("\n## PEOPLE");
+ People.All.ToList().ForEach(p => WriteLine($"{p.Name}\t{p.Address}\t{p._11Age}"));
}
}
diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.cs
index 000328660..f40871fab 100644
--- a/samples/CSharp/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.cs
+++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.cs
@@ -1,11 +1,10 @@
-namespace GeneratedDemo
+namespace GeneratedDemo;
+
+public static class UseHelloWorldGenerator
{
- public static class UseHelloWorldGenerator
+ public static void Run()
{
- 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();
- }
+ // 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/UseMathsGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseMathsGenerator.cs
index 2c8b7ecf0..7523e3d1e 100644
--- a/samples/CSharp/SourceGenerators/GeneratedDemo/UseMathsGenerator.cs
+++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseMathsGenerator.cs
@@ -1,15 +1,14 @@
using static System.Console;
using Maths;
-namespace GeneratedDemo
+namespace GeneratedDemo;
+
+public static class UseMathsGenerator
{
- public static class UseMathsGenerator
+ public static void Run()
{
- public static void Run()
- {
- WriteLine($"The area of a (10, 5) rectangle is: {Formulas.AreaRectangle(10, 5)}");
- WriteLine($"The area of a (10) square is: {Formulas.AreaSquare(10)}");
- WriteLine($"The GoldHarmon of 3 is: {Formulas.GoldHarm(3)}");
- }
+ WriteLine($"The area of a (10, 5) rectangle is: {Formulas.AreaRectangle(10, 5)}");
+ WriteLine($"The area of a (10) square is: {Formulas.AreaSquare(10)}");
+ WriteLine($"The GoldHarmon of 3 is: {Formulas.GoldHarm(3)}");
}
}
diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseMustacheGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseMustacheGenerator.cs
index 992d56800..e749b1270 100644
--- a/samples/CSharp/SourceGenerators/GeneratedDemo/UseMustacheGenerator.cs
+++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseMustacheGenerator.cs
@@ -12,28 +12,28 @@
[assembly: Mustache("Section", t4, h4)]
[assembly: Mustache("NestedSection", t5, h5)]
-namespace GeneratedDemo
+namespace GeneratedDemo;
+
+class UseMustacheGenerator
{
- class UseMustacheGenerator
+ public static void Run()
{
- public static void Run()
- {
- WriteLine(Mustache.Constants.Lottery);
- WriteLine(Mustache.Constants.HR);
- WriteLine(Mustache.Constants.HTML);
- WriteLine(Mustache.Constants.Section);
- WriteLine(Mustache.Constants.NestedSection);
- }
+ WriteLine(Mustache.Constants.Lottery);
+ WriteLine(Mustache.Constants.HR);
+ WriteLine(Mustache.Constants.HTML);
+ WriteLine(Mustache.Constants.Section);
+ WriteLine(Mustache.Constants.NestedSection);
+ }
- // Mustache templates and hashes from the manual at https://mustache.github.io/mustache.1.html...
- public const string t1 = @"
+ // Mustache templates and hashes from the manual at https://mustache.github.io/mustache.1.html...
+ public const string t1 = @"
Hello {{name}}
You have just won {{value}} dollars!
{{#in_ca}}
Well, {{taxed_value}} dollars, after taxes.
{{/in_ca}}
";
- public const string h1 = @"
+ public const string h1 = @"
{
""name"": ""Chris"",
""value"": 10000,
@@ -41,35 +41,35 @@ You have just won {{value}} dollars!
""in_ca"": true
}
";
- public const string t2 = @"
+ public const string t2 = @"
* {{name}}
* {{age}}
* {{company}}
* {{{company}}}
";
- public const string h2 = @"
+ public const string h2 = @"
{
""name"": ""Chris"",
""company"": ""GitHub""
}
";
- public const string t3 = @"
+ public const string t3 = @"
Shown
{{#person}}
Never shown!
{{/person}}
";
- public const string h3 = @"
+ public const string h3 = @"
{
""person"": false
}
";
- public const string t4 = @"
+ public const string t4 = @"
{{#repo}}
{{name}}
{{/repo}}
";
- public const string h4 = @"
+ public const string h4 = @"
{
""repo"": [
{ ""name"": ""resque"" },
@@ -78,7 +78,7 @@ Never shown!
]
}
";
- public const string t5 = @"
+ public const string t5 = @"
{{#repo}}
{{name}}
{{#nested}}
@@ -86,7 +86,7 @@ Never shown!
{{/nested}}
{{/repo}}
";
- public const string h5 = @"
+ public const string h5 = @"
{
""repo"": [
{ ""name"": ""resque"", ""nested"":[{""name"":""nestedResque""}] },
@@ -96,5 +96,4 @@ Never shown!
}
";
- }
}
diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.cs
index 213181e51..d5631238e 100644
--- a/samples/CSharp/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.cs
+++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.cs
@@ -1,27 +1,26 @@
using System;
using AutoSettings;
-namespace GeneratedDemo
+namespace GeneratedDemo;
+
+public static class UseXmlSettingsGenerator
{
- public static class UseXmlSettingsGenerator
+ public static void Run()
{
- public static void 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
- XmlSettings.MainSettings main = 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
+ 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}");
+ // 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}");
+ 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
- }
+ // 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 958cf03f6..83fdc60ed 100644
--- a/samples/CSharp/SourceGenerators/README.md
+++ b/samples/CSharp/SourceGenerators/README.md
@@ -3,7 +3,7 @@
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/main/docs/features/source-generators.md).
+For more information on the Source Generators feature, see the [design document](https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.md).
Prerequisites
-----
diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/.editorconfig b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/.editorconfig
new file mode 100644
index 000000000..bb06d5e60
--- /dev/null
+++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/.editorconfig
@@ -0,0 +1,103 @@
+
+# Avoid "this." and "Me." if not necessary
+dotnet_style_qualification_for_field = true:error
+dotnet_style_qualification_for_property = true:error
+dotnet_style_qualification_for_method = true:error
+dotnet_style_qualification_for_event = true:error
+
+# Use language keywords instead of framework type names for type references
+dotnet_style_predefined_type_for_locals_parameters_members = true:error
+dotnet_style_predefined_type_for_member_access = true:error
+
+# Suggest more modern language features when available
+dotnet_style_object_initializer = true:error
+dotnet_style_collection_initializer = true:error
+dotnet_style_coalesce_expression = true:error
+dotnet_style_null_propagation = true:error
+dotnet_style_explicit_tuple_names = true:error
+
+# CSharp code style settings:
+[*.cs]
+# Prefer "var" everywhere
+csharp_style_var_for_built_in_types = true:silent
+csharp_style_var_when_type_is_apparent = true:silent
+csharp_style_var_elsewhere = true:silent
+
+# Suggest more modern language features when available
+csharp_style_pattern_matching_over_is_with_cast_check = true:error
+csharp_style_pattern_matching_over_as_with_null_check = true:error
+csharp_style_inlined_variable_declaration = true:error
+csharp_style_throw_expression = true:error
+csharp_style_conditional_delegate_call = true:error
+csharp_using_directive_placement = outside_namespace:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_prefer_braces = true:silent
+csharp_style_namespace_declarations = block_scoped:silent
+csharp_style_expression_bodied_methods = false:none
+csharp_style_expression_bodied_constructors = false:none
+csharp_style_expression_bodied_operators = false:none
+csharp_style_expression_bodied_properties = true:none
+csharp_style_expression_bodied_indexers = true:none
+csharp_style_expression_bodied_accessors = true:none
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_indent_labels = one_less_than_current
+csharp_style_deconstructed_variable_declaration = true:suggestion
+
+[*.{cs,vb}]
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+indent_size = 4
+end_of_line = crlf
+dotnet_style_collection_initializer = true:suggestion
diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs
index 3c8008f3e..7904ea4d2 100644
--- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs
+++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs
@@ -1,18 +1,9 @@
-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 SourceGeneratorSamples
+namespace SourceGeneratorSamples;
+
+[Generator]
+public class AutoNotifyGenerator : IIncrementalGenerator
{
- [Generator]
- public class AutoNotifyGenerator : ISourceGenerator
- {
- private const string attributeText = @"
+ private const string attributeText = @"
using System;
namespace AutoNotify
{
@@ -29,84 +20,55 @@ public AutoNotifyAttribute()
";
- public void Initialize(GeneratorInitializationContext context)
- {
- // Register the attribute source
- context.RegisterForPostInitialization((i) => i.AddSource("AutoNotifyAttribute.g.cs", attributeText));
-
- // Register a syntax receiver that will be created for each generation pass
- context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
- }
-
- public void Execute(GeneratorExecutionContext context)
- {
- // retrieve the populated receiver
- if (!(context.SyntaxContextReceiver is SyntaxReceiver receiver))
- return;
-
- // get the added attribute, and INotifyPropertyChanged
- INamedTypeSymbol attributeSymbol = context.Compilation.GetTypeByMetadataName("AutoNotify.AutoNotifyAttribute");
- INamedTypeSymbol notifySymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged");
-
- // group the fields by class, and generate the source
- foreach (IGrouping group in receiver.Fields.GroupBy(f => f.ContainingType, SymbolEqualityComparer.Default))
- {
- string classSource = ProcessClass(group.Key, group.ToList(), attributeSymbol, notifySymbol, context);
- context.AddSource($"{group.Key.Name}_autoNotify.g.cs", SourceText.From(classSource, Encoding.UTF8));
- }
- }
-
- private string ProcessClass(INamedTypeSymbol classSymbol, List fields, ISymbol attributeSymbol, ISymbol notifySymbol, GeneratorExecutionContext context)
- {
- if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default))
- {
- return null; //TODO: issue a diagnostic that it must be top level
- }
+ private string? ProcessClass(INamedTypeSymbol classSymbol, List fields, ISymbol attributeSymbol, ISymbol notifySymbol)
+ {
+ 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();
+ string namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
- // begin building the generated source
- StringBuilder source = new StringBuilder($@"
+ // begin building the generated source
+ StringBuilder source = new ($@"
namespace {namespaceName}
{{
public partial class {classSymbol.Name} : {notifySymbol.ToDisplayString()}
{{
");
- // if the class doesn't implement INotifyPropertyChanged already, add it
- if (!classSymbol.Interfaces.Contains(notifySymbol, SymbolEqualityComparer.Default))
- {
- source.Append("public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;");
- }
-
- // create properties for each field
- foreach (IFieldSymbol fieldSymbol in fields)
- {
- ProcessField(source, fieldSymbol, attributeSymbol);
- }
-
- source.Append("} }");
- return source.ToString();
+ // if the class doesn't implement INotifyPropertyChanged already, add it
+ if (!classSymbol.Interfaces.Contains(notifySymbol, SymbolEqualityComparer.Default))
+ {
+ source.Append("public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;");
}
- private void ProcessField(StringBuilder source, IFieldSymbol fieldSymbol, ISymbol attributeSymbol)
+ // create properties for each field
+ foreach (IFieldSymbol fieldSymbol in fields)
{
- // get the name and type of the field
- string fieldName = fieldSymbol.Name;
- ITypeSymbol fieldType = fieldSymbol.Type;
+ ProcessField(source, fieldSymbol, attributeSymbol);
+ }
- // 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;
+ source.Append("} }");
+ return source.ToString();
+ }
- string propertyName = chooseName(fieldName, overridenNameOpt);
- if (propertyName.Length == 0 || propertyName == fieldName)
- {
- //TODO: issue a diagnostic that we can't process this field
- return;
- }
+ 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;
- source.Append($@"
+ 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
@@ -123,52 +85,66 @@ private void ProcessField(StringBuilder source, IFieldSymbol fieldSymbol, ISymbo
");
- string chooseName(string fieldName, TypedConstant overridenNameOpt)
+ static string chooseName(string fieldName, TypedConstant overridenNameOpt)
+ {
+ if (!overridenNameOpt.IsNull)
{
- if (!overridenNameOpt.IsNull)
- {
- return overridenNameOpt.Value.ToString();
- }
-
- fieldName = fieldName.TrimStart('_');
- if (fieldName.Length == 0)
- return string.Empty;
+ return overridenNameOpt.Value!.ToString();
+ }
- if (fieldName.Length == 1)
- return fieldName.ToUpper();
+ fieldName = fieldName.TrimStart('_');
+ if (fieldName.Length == 0)
+ return string.Empty;
- return fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1);
- }
+ if (fieldName.Length == 1)
+ return fieldName.ToUpper();
+ return fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1);
}
- ///
- /// Created on demand before each generation pass
- ///
- class SyntaxReceiver : ISyntaxContextReceiver
+ }
+
+ public void GenerateCode(SourceProductionContext context, (ImmutableArray symbols, Compilation compilation) source)
+ {
+ // get the added attribute, and INotifyPropertyChanged
+ INamedTypeSymbol attributeSymbol = source.compilation.GetTypeByMetadataName("AutoNotify.AutoNotifyAttribute")!;
+ INamedTypeSymbol notifySymbol = source.compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged")!;
+
+ // group the fields by class, and generate the source
+ foreach (IGrouping group in source.symbols.GroupBy(f => f.ContainingType, SymbolEqualityComparer.Default))
{
- public List Fields { get; } = new List();
+ string? classSource = ProcessClass(group.Key, group.ToList(), attributeSymbol, notifySymbol);
+ if(classSource is not null)
+ context.AddSource($"{group.Key.Name}_autoNotify.g.cs", SourceText.From(classSource, Encoding.UTF8));
+ }
+ }
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ context.RegisterPostInitializationOutput((i) => i.AddSource("AutoNotifyAttribute", attributeText));
- ///
- /// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation
- ///
- public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
- {
- // any field with at least one attribute is a candidate for property generation
- if (context.Node is FieldDeclarationSyntax fieldDeclarationSyntax
- && fieldDeclarationSyntax.AttributeLists.Count > 0)
- {
- foreach (VariableDeclaratorSyntax variable in fieldDeclarationSyntax.Declaration.Variables)
- {
- // Get the symbol being declared by the field, and keep it if its annotated
- IFieldSymbol fieldSymbol = context.SemanticModel.GetDeclaredSymbol(variable) as IFieldSymbol;
- if (fieldSymbol.GetAttributes().Any(ad => ad.AttributeClass.ToDisplayString() == "AutoNotify.AutoNotifyAttribute"))
- {
- Fields.Add(fieldSymbol);
- }
- }
- }
- }
+ var fieldsEnum = context.SyntaxProvider.CreateSyntaxProvider(
+ predicate: (node, ct) => node is FieldDeclarationSyntax fieldDeclarationSyntax
+ && fieldDeclarationSyntax.AttributeLists.Count > 0,
+ transform: FieldsTransform);
+
+ var fields = fieldsEnum.SelectMany((en, _) => en);
+
+ var collected = fields.Collect()
+ .Combine(context.CompilationProvider);
+
+ context.RegisterSourceOutput(collected, GenerateCode);
+ }
+
+
+ IEnumerable FieldsTransform(GeneratorSyntaxContext context, CancellationToken ct)
+ {
+ var fieldDeclarationSyntax = (FieldDeclarationSyntax) context.Node;
+
+ foreach (VariableDeclaratorSyntax variable in fieldDeclarationSyntax.Declaration.Variables)
+ {
+ IFieldSymbol? fieldSymbol = context.SemanticModel.GetDeclaredSymbol(variable) as IFieldSymbol;
+ if (fieldSymbol!.GetAttributes().Any(ad => ad.AttributeClass!.ToDisplayString() == "AutoNotify.AutoNotifyAttribute"))
+ yield return fieldSymbol;
}
}
}
diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj
index 47c428cf5..3c1472887 100644
--- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj
+++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj
@@ -2,7 +2,8 @@
netstandard2.0
- 8.0
+ 10.0
+ enable
diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs
index 710d491da..ab1c834b8 100644
--- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs
+++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs
@@ -1,185 +1,174 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.Text;
-using NotVisualBasic.FileIO;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
-#nullable enable
-
-// CsvTextFileParser from https://github.com/22222/CsvTextFieldParser adding suppression rules for default VS config
+namespace CsvGenerator;
-namespace CsvGenerator
+[Generator]
+public class CSVGenerator : IIncrementalGenerator
{
- [Generator]
- public class CSVGenerator : ISourceGenerator
+ public enum CsvLoadType
{
- public enum CsvLoadType
- {
- Startup,
- OnDemand
- }
+ Startup,
+ OnDemand
+ }
+
+ // Guesses type of property for the object from the value of a csv field
+ public static string GetCsvFieldType(string exemplar) => exemplar switch
+ {
+ _ when bool.TryParse(exemplar, out _) => "bool",
+ _ when int.TryParse(exemplar, out _) => "int",
+ _ when double.TryParse(exemplar, out _) => "double",
+ _ => "string"
+ };
+
+ // 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 cas. If the file is completely empty, an error is generated.
+ public static (string[], string[], string[]?) ExtractProperties(CsvTextFieldParser parser)
+ {
+ string[]? headerFields = parser.ReadFields();
+ if (headerFields == null) throw new Exception("Empty csv file!");
- // Guesses type of property for the object from the value of a csv field
- public static string GetCsvFieldType(string exemplar) => exemplar switch
+ string[]? firstLineFields = parser.ReadFields();
+ if (firstLineFields == null)
{
- _ when bool.TryParse(exemplar, out _) => "bool",
- _ when int.TryParse(exemplar, out _) => "int",
- _ when double.TryParse(exemplar, out _) => "double",
- _ => "string"
- };
-
- // 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 cas. If the file is completely empty, an error is generated.
- public static (string[], string[], string[]?) ExtractProperties(CsvTextFieldParser parser)
+ return (Enumerable.Repeat("string", headerFields.Length).ToArray(), headerFields, firstLineFields);
+ }
+ else
{
- string[]? headerFields = parser.ReadFields();
- if (headerFields == null) throw new Exception("Empty csv file!");
-
- string[]? firstLineFields = parser.ReadFields();
- if (firstLineFields == null)
- {
- return (Enumerable.Repeat("string", headerFields.Length).ToArray(), headerFields, firstLineFields);
- }
- else
- {
- return (firstLineFields.Select(GetCsvFieldType).ToArray(), headerFields.Select(StringToValidPropertyName).ToArray(), firstLineFields);
- }
+ return (firstLineFields.Select(GetCsvFieldType).ToArray(), headerFields.Select(StringToValidPropertyName).ToArray(), firstLineFields);
}
+ }
- // 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 static string GenerateClassFile(string className, string csvText, CsvLoadType loadTime, bool cacheObjects)
- {
- StringBuilder sb = new StringBuilder();
- using CsvTextFieldParser parser = new CsvTextFieldParser(new StringReader(csvText));
+ // 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 static string GenerateClassFile(string className, string csvText, CsvLoadType loadTime, bool cacheObjects)
+ {
+ StringBuilder sb = new ();
+ using CsvTextFieldParser parser = new (new StringReader(csvText));
- //// Usings
- sb.Append(@"
+ //// Usings
+ sb.Append(@"
#nullable enable
namespace CSV {
using System.Collections.Generic;
");
- //// Class Definition
- sb.Append($" public class {className} {{\n");
+ //// Class Definition
+ sb.Append($" public class {className} {{\n");
- if (loadTime == CsvLoadType.Startup)
- {
- sb.Append(@$"
+ if (loadTime == CsvLoadType.Startup)
+ {
+ sb.Append(@$"
static {className}() {{ var x = All; }}
");
- }
- (string[] types, string[] names, string[]? fields) = ExtractProperties(parser);
- int minLen = Math.Min(types.Length, names.Length);
+ }
+ (string[] types, string[] names, string[]? fields) = ExtractProperties(parser);
+ int minLen = Math.Min(types.Length, names.Length);
- for (int i = 0; i < minLen; i++)
- {
- sb.AppendLine($" public {types[i]} {StringToValidPropertyName(names[i])} {{ get; set;}} = default!;");
- }
- sb.Append("\n");
+ for (int i = 0; i < minLen; i++)
+ {
+ sb.AppendLine($" public {types[i]} {StringToValidPropertyName(names[i])} {{ get; set;}} = default!;");
+ }
+ sb.Append("\n");
- //// Loading data
- sb.AppendLine($" static IEnumerable<{className}>? _all = null;");
- sb.Append($@"
+ //// Loading data
+ sb.AppendLine($" static IEnumerable<{className}>? _all = null;");
+ sb.Append($@"
public static IEnumerable<{className}> All {{
get {{");
- if (cacheObjects) sb.Append(@"
+ if (cacheObjects) sb.Append(@"
if(_all != null)
return _all;
");
- sb.Append(@$"
+ sb.Append(@$"
List<{className}> l = new List<{className}>();
{className} c;
");
- // This awkwardness comes from having to pre-read one row to figure out the types of props.
- do
+ // This awkwardness comes from having to pre-read one row to figure out the types of props.
+ do
+ {
+ if (fields == null) continue;
+ if (fields.Length < minLen) throw new Exception("Not enough fields in CSV file.");
+
+ sb.AppendLine($" c = new {className}();");
+ string value = "";
+ for (int i = 0; i < minLen; i++)
{
- if (fields == null) continue;
- if (fields.Length < minLen) throw new Exception("Not enough fields in CSV file.");
+ // Wrap strings in quotes.
+ value = GetCsvFieldType(fields[i]) == "string" ? $"\"{fields[i].Trim().Trim(new char[] { '"' })}\"" : fields[i];
+ sb.AppendLine($" c.{names[i]} = {value};");
+ }
+ sb.AppendLine(" l.Add(c);");
- sb.AppendLine($" c = new {className}();");
- string value = "";
- for (int i = 0; i < minLen; i++)
- {
- // Wrap strings in quotes.
- value = GetCsvFieldType(fields[i]) == "string" ? $"\"{fields[i].Trim().Trim(new char[] { '"' })}\"" : fields[i];
- sb.AppendLine($" c.{names[i]} = {value};");
- }
- sb.AppendLine(" l.Add(c);");
+ fields = parser.ReadFields();
+ } while (!(fields == null));
- fields = parser.ReadFields();
- } while (!(fields == null));
+ sb.AppendLine(" _all = l;");
+ sb.AppendLine(" return l;");
- sb.AppendLine(" _all = l;");
- sb.AppendLine(" return l;");
+ // Close things (property, class, namespace)
+ sb.Append(" }\n }\n }\n}\n");
+ return sb.ToString();
- // Close things (property, class, namespace)
- sb.Append(" }\n }\n }\n}\n");
- return sb.ToString();
+ }
- }
+ static string StringToValidPropertyName(string s)
+ {
+ s = s.Trim();
+ s = char.IsLetter(s[0]) ? char.ToUpper(s[0]) + s.Substring(1) : s;
+ s = char.IsDigit(s.Trim()[0]) ? "_" + s : s;
+ s = new string(s.Select(ch => char.IsDigit(ch) || char.IsLetter(ch) ? ch : '_').ToArray());
+ return s;
+ }
- static string StringToValidPropertyName(string s)
- {
- s = s.Trim();
- s = char.IsLetter(s[0]) ? char.ToUpper(s[0]) + s.Substring(1) : s;
- s = char.IsDigit(s.Trim()[0]) ? "_" + s : s;
- s = new string(s.Select(ch => char.IsDigit(ch) || char.IsLetter(ch) ? ch : '_').ToArray());
- return s;
- }
+ static IEnumerable<(string, string)> SourceFilesFromAdditionalFile(CsvLoadType loadTime, bool cacheObjects, AdditionalText file)
+ {
+ string className = Path.GetFileNameWithoutExtension(file.Path);
+ string csvText = file.GetText()!.ToString();
+ return new (string, string)[] { (className, GenerateClassFile(className, csvText, loadTime, cacheObjects)) };
+ }
- static IEnumerable<(string, string)> SourceFilesFromAdditionalFile(CsvLoadType loadTime, bool cacheObjects, AdditionalText file)
- {
- string className = Path.GetFileNameWithoutExtension(file.Path);
- string csvText = file.GetText()!.ToString();
- return new (string, string)[] { (className, GenerateClassFile(className, csvText, loadTime, cacheObjects)) };
- }
+ public (CsvLoadType, bool, AdditionalText) GetLoadOptionProvider(
+ (AdditionalText file, AnalyzerConfigOptionsProvider config) source,
+ CancellationToken ct)
+ {
+ var options = source.config.GetOptions(source.file);
+
+ options.TryGetValue("build_metadata.additionalfiles.CsvLoadType", out string? loadTimeString);
+ Enum.TryParse(loadTimeString, ignoreCase: true, out CsvLoadType loadType);
- static IEnumerable<(string, string)> SourceFilesFromAdditionalFiles(IEnumerable<(CsvLoadType loadTime, bool cacheObjects, AdditionalText file)> pathsData)
- => pathsData.SelectMany(d => SourceFilesFromAdditionalFile(d.loadTime, d.cacheObjects, d.file));
+ options.TryGetValue("build_metadata.additionalfiles.CacheObjects", out string? cacheObjectsString);
+ bool.TryParse(cacheObjectsString, out bool cacheObjects);
- static IEnumerable<(CsvLoadType, bool, AdditionalText)> GetLoadOptions(GeneratorExecutionContext context)
- {
- foreach (AdditionalText file in context.AdditionalFiles)
- {
- if (Path.GetExtension(file.Path).Equals(".csv", StringComparison.OrdinalIgnoreCase))
- {
- // are there any options for it?
- context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CsvLoadType", out string? loadTimeString);
- Enum.TryParse(loadTimeString, ignoreCase: true, out CsvLoadType loadType);
+ return (loadType, cacheObjects, source.file);
+ }
- context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CacheObjects", out string? cacheObjectsString);
- bool.TryParse(cacheObjectsString, out bool cacheObjects);
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ IncrementalValuesProvider files
+ = context.AdditionalTextsProvider.Where( static file => file.Path.EndsWith(".csv"));
- yield return (loadType, cacheObjects, file);
- }
- }
- }
+ IncrementalValuesProvider<(AdditionalText, AnalyzerConfigOptionsProvider)>
+ combined = files.Combine(context.AnalyzerConfigOptionsProvider);
- public void Execute(GeneratorExecutionContext context)
- {
- IEnumerable<(CsvLoadType, bool, AdditionalText)> options = GetLoadOptions(context);
- IEnumerable<(string, string)> nameCodeSequence = SourceFilesFromAdditionalFiles(options);
- foreach ((string name, string code) in nameCodeSequence)
- context.AddSource($"Csv_{name}.g.cs", SourceText.From(code, Encoding.UTF8));
- }
+ IncrementalValuesProvider<(CsvLoadType, bool, AdditionalText)> transformed
+ = combined.Select(GetLoadOptionProvider);
- public void Initialize(GeneratorInitializationContext context)
- {
- }
+ var selector = static ((CsvLoadType ltype, bool cache, AdditionalText text)source, CancellationToken ctoken)
+ => SourceFilesFromAdditionalFile(source.ltype, source.cache, source.text);
+ IncrementalValuesProvider<(string name, string code)> nameAndContent = transformed.SelectMany(selector);
+
+ context.RegisterSourceOutput(nameAndContent, (spc, nameAndContent) =>
+ spc.AddSource($"{nameAndContent.name}.g.cs", nameAndContent.code));
}
}
-#pragma warning restore IDE0008 // Use explicit type
diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Globals.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Globals.cs
new file mode 100644
index 000000000..61591c2c3
--- /dev/null
+++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Globals.cs
@@ -0,0 +1,21 @@
+global using System;
+global using System.Collections.Generic;
+global using System.Collections.Immutable;
+global using System.Text;
+global using System.Threading;
+global using Microsoft.CodeAnalysis;
+global using Microsoft.CodeAnalysis.CSharp.Syntax;
+global using Microsoft.CodeAnalysis.Text;
+global using System.Linq;
+global using Microsoft.CodeAnalysis.CSharp;
+global using System.IO;
+global using NotVisualBasic.FileIO;
+global using System.Text.RegularExpressions;
+global using Tokens = System.Collections.Generic.IEnumerable;
+global using SymTable = System.Collections.Generic.HashSet;
+global using System.Xml;
+
+global using static System.Console;
+
+
+
diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs
index 4a1b2b972..35a5c7476 100644
--- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs
+++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs
@@ -1,51 +1,55 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.Text;
+namespace SourceGeneratorSamples;
-namespace SourceGeneratorSamples
+[Generator]
+public class HelloWorldGenerator : IIncrementalGenerator
{
- [Generator]
- public class HelloWorldGenerator : ISourceGenerator
+ public void Initialize(IncrementalGeneratorInitializationContext context)
{
- public void Execute(GeneratorExecutionContext context)
- {
- // begin creating the source we'll inject into the users compilation
- StringBuilder sourceBuilder = new StringBuilder(@"
+ // Use this method to generate constant code (aka that doesn't depend on the syntax tree).
+ context.RegisterPostInitializationOutput(context =>
+ context.AddSource("AStaticFunc.g.cs", @"
+ namespace HelloWorldGenerated;
+ static class Printer {
+ internal static void PrintUpper(string s) => System.Console.WriteLine(s.ToUpper());
+ }")
+ );
+
+ // Dependency pipeline. Select class syntax nodes, transform to type symbols and collect their names.
+ var classNames = context.SyntaxProvider.CreateSyntaxProvider(
+ predicate: static (sn, c) => sn is ClassDeclarationSyntax,
+ transform: static (ct, c) => ct.SemanticModel.GetDeclaredSymbol(ct.Node, c))
+ .Where (static t => t is not null)
+ .Select (static (t, c) => t!.Name)
+ .Collect();
+
+ // Register a function to generate the code using the collected type symbols.
+ context.RegisterSourceOutput(classNames, GenerateSource);
+ }
+
+ // Main function to generate the source code.
+ private void GenerateSource(SourceProductionContext context, ImmutableArray typeNames)
+ {
+ // Begin creating the source we'll inject into the users compilation.
+ StringBuilder sourceBuilder = new(@"
using System;
-namespace HelloWorldGenerated
-{
+namespace HelloWorldGenerated;
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:"");
+ Printer.PrintUpper(""Hello from generated code!""); // Uses the Printer static class
+ Console.WriteLine(""The following classes 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;
+ // Print out each symbol name we find.
+ foreach (var name in typeNames)
+ sourceBuilder.AppendLine($"Console.WriteLine(\"{name}\");");
- // 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(@"
+ // Finish creating the source to inject.
+ sourceBuilder.Append(@"
}
+ }");
+ context.AddSource($"hello_world.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
}
-}");
-
- // inject the created source into the users compilation
- context.AddSource("helloWorldGenerated.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
- }
- public void Initialize(GeneratorInitializationContext context)
- {
- // No initialization required
- }
- }
}
diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs
index 0b7645a06..b000a70bd 100644
--- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs
+++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs
@@ -1,171 +1,138 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Text.RegularExpressions;
-using static System.Console;
-using Tokens = System.Collections.Generic.IEnumerable;
-using SymTable = System.Collections.Generic.HashSet;
-using System.Linq;
-using Microsoft.CodeAnalysis;
-using System.IO;
-using Microsoft.CodeAnalysis.Text;
-using System.Diagnostics;
+namespace MathsGenerator;
+
+public enum TokenType {
+ Number,
+ Identifier,
+ Operation,
+ OpenParens,
+ CloseParens,
+ Equal,
+ EOL,
+ EOF,
+ Spaces,
+ Comma,
+ Sum,
+ None
+}
-#pragma warning disable IDE0008 // Use explicit type
+public struct Token {
+ public TokenType Type;
+ public string Value;
+ public int Line;
+ public int Column;
+}
-namespace MathsGenerator
-{
- public enum TokenType
- {
- Number,
- Identifier,
- Operation,
- OpenParens,
- CloseParens,
- Equal,
- EOL,
- EOF,
- Spaces,
- Comma,
- Sum,
- None
- }
+public static class Lexer {
- public struct Token
- {
- public TokenType Type;
- public string Value;
- public int Line;
- public int Column;
+ public static void PrintTokens(IEnumerable tokens) {
+ foreach (var token in tokens) {
+ WriteLine($"{token.Line}, {token.Column}, {token.Type}, {token.Value}");
+ }
}
- public static class Lexer
- {
-
- public static void PrintTokens(IEnumerable tokens)
- {
- foreach (var token in tokens)
- {
- WriteLine($"{token.Line}, {token.Column}, {token.Type}, {token.Value}");
+ static readonly (TokenType, string)[] tokenStrings = {
+ (TokenType.EOL, @"(\r\n|\r|\n)"),
+ (TokenType.Spaces, @"\s+"),
+ (TokenType.Number, @"[+-]?((\d+\.?\d*)|(\.\d+))"),
+ (TokenType.Identifier, @"[_a-zA-Z][`'""_a-zA-Z0-9]*"),
+ (TokenType.Operation, @"[\+\-/\*]"),
+ (TokenType.OpenParens, @"[([{]"),
+ (TokenType.CloseParens, @"[)\]}]"),
+ (TokenType.Equal, @"="),
+ (TokenType.Comma, @","),
+ (TokenType.Sum, @"∑")
+ };
+
+ static readonly IEnumerable<(TokenType, Regex)> tokenExpressions =
+ tokenStrings.Select(
+ t => (t.Item1, new Regex($"^{t.Item2}", RegexOptions.Compiled | RegexOptions.Singleline)));
+
+ // Can be optimized with spans to avoid so many allocations ...
+ static public Tokens Tokenize(string source) {
+ var currentLine = 1;
+ var currentColumn = 1;
+
+ while (source.Length > 0) {
+
+ var matchLength = 0;
+ var tokenType = TokenType.None;
+ var value = "";
+
+ foreach (var (type, rule) in tokenExpressions) {
+ var match = rule.Match(source);
+ if(match.Success) {
+ matchLength = match.Length;
+ tokenType = type;
+ value = match.Value;
+ break;
+ }
}
- }
- static (TokenType, string)[] tokenStrings = {
- (TokenType.EOL, @"(\r\n|\r|\n)"),
- (TokenType.Spaces, @"\s+"),
- (TokenType.Number, @"[+-]?((\d+\.?\d*)|(\.\d+))"),
- (TokenType.Identifier, @"[_a-zA-Z][`'""_a-zA-Z0-9]*"),
- (TokenType.Operation, @"[\+\-/\*]"),
- (TokenType.OpenParens, @"[([{]"),
- (TokenType.CloseParens, @"[)\]}]"),
- (TokenType.Equal, @"="),
- (TokenType.Comma, @","),
- (TokenType.Sum, @"∑")
- };
-
- static IEnumerable<(TokenType, Regex)> tokenExpressions =
- tokenStrings.Select(
- t => (t.Item1, new Regex($"^{t.Item2}", RegexOptions.Compiled | RegexOptions.Singleline)));
-
- // Can be optimized with spans to avoid so many allocations ...
- static public Tokens Tokenize(string source)
- {
- var currentLine = 1;
- var currentColumn = 1;
-
- while (source.Length > 0)
- {
-
- var matchLength = 0;
- var tokenType = TokenType.None;
- var value = "";
-
- foreach (var (type, rule) in tokenExpressions)
- {
- var match = rule.Match(source);
- if (match.Success)
- {
- matchLength = match.Length;
- tokenType = type;
- value = match.Value;
- break;
- }
- }
+ if (matchLength == 0) {
- if (matchLength == 0)
- {
+ throw new Exception($"Unrecognized symbol '{source[currentLine - 1]}' at index {currentLine - 1} (line {currentLine}, column {currentColumn}).");
- throw new Exception($"Unrecognized symbol '{source[currentLine - 1]}' at index {currentLine - 1} (line {currentLine}, column {currentColumn}).");
+ } else {
+ if(tokenType != TokenType.Spaces)
+ yield return new Token {
+ Type = tokenType,
+ Value = value,
+ Line = currentLine,
+ Column = currentColumn
+ };
+
+ currentColumn += matchLength;
+ if(tokenType == TokenType.EOL) {
+ currentLine += 1;
+ currentColumn = 0;
}
- else
- {
-
- if (tokenType != TokenType.Spaces)
- yield return new Token
- {
- Type = tokenType,
- Value = value,
- Line = currentLine,
- Column = currentColumn
- };
-
- currentColumn += matchLength;
- if (tokenType == TokenType.EOL)
- {
- currentLine += 1;
- currentColumn = 0;
- }
-
- source = source.Substring(matchLength);
- }
- }
- yield return new Token
- {
- Type = TokenType.EOF,
- Line = currentLine,
- Column = currentColumn
- };
+ source = source.Substring(matchLength);
+ }
}
- }
-
- /* EBNF for the language
- lines = {line} EOF
- line = {EOL} identifier [lround args rround] equal expr EOL {EOL}
- args = identifier {comma identifier}
- expr = [plus|minus] term { (plus|minus) term }
- term = factor { (times|divide) factor };
- factor = number | var | func | sum | matrix | lround expr rround;
- var = identifier;
- func = identifier lround expr {comma expr} rround;
- sum = ∑ lround identifier comma expr comma expr comma expr rround;
- */
- public static class Parser
- {
+ yield return new Token {
+ Type = TokenType.EOF,
+ Line = currentLine,
+ Column = currentColumn
+ };
+ }
+}
- public static string Parse(Tokens tokens)
- {
- var globalSymbolTable = new SymTable();
- var symbolTable = new SymTable();
- var buffer = new StringBuilder();
-
- var en = tokens.GetEnumerator();
- en.MoveNext();
-
- buffer = Lines(new Context
- {
- tokens = en,
- globalSymbolTable = globalSymbolTable,
- symbolTable = symbolTable,
- buffer = buffer
+/* EBNF for the language
+ lines = {line} EOF
+ line = {EOL} identifier [lround args rround] equal expr EOL {EOL}
+ args = identifier {comma identifier}
+ expr = [plus|minus] term { (plus|minus) term }
+ term = factor { (times|divide) factor };
+ factor = number | var | func | sum | matrix | lround expr rround;
+ var = identifier;
+ func = identifier lround expr {comma expr} rround;
+ sum = ∑ lround identifier comma expr comma expr comma expr rround;
+*/
+public static class Parser {
+
+
+ public static string Parse(Tokens tokens) {
+ var globalSymbolTable = new SymTable();
+ var symbolTable = new SymTable();
+ var buffer = new StringBuilder();
+
+ var en = tokens.GetEnumerator();
+ en.MoveNext();
+
+ buffer = Lines(new Context {
+ tokens = en,
+ globalSymbolTable = globalSymbolTable,
+ symbolTable = symbolTable,
+ buffer = buffer
});
- return buffer.ToString();
+ return buffer.ToString();
- }
+ }
- private readonly static string Preamble = @"
+ private readonly static string Preamble = @"
using static System.Math;
using static Maths.FormulaHelpers;
@@ -173,300 +140,262 @@ namespace Maths {
public static partial class Formulas {
";
- private readonly static string Ending = @"
+ private readonly static string Ending = @"
}
}";
- private struct Context
- {
- public IEnumerator tokens;
- public SymTable globalSymbolTable;
- public SymTable symbolTable;
- public StringBuilder buffer;
- }
+ private struct Context {
+ public IEnumerator tokens;
+ public SymTable globalSymbolTable;
+ public SymTable symbolTable;
+ public StringBuilder buffer;
+ }
- private static StringBuilder Error(Token token, TokenType type, string value = "") =>
- throw new Exception($"Expected {type} {(value == "" ? "" : $" with {token.Value}")} at {token.Line},{token.Column} Instead found {token.Type} with value {token.Value}");
+ private static StringBuilder Error(Token token, TokenType type, string value = "") =>
+ throw new Exception($"Expected {type} {(value == "" ? "" : $" with {token.Value}")} at {token.Line},{token.Column} Instead found {token.Type} with value {token.Value}");
- static HashSet validFunctions =
- new HashSet(typeof(System.Math).GetMethods().Select(m => m.Name.ToLower()));
+ static readonly SymTable validFunctions =
+ new(typeof(System.Math).GetMethods().Select(m => m.Name.ToLower()));
- static Dictionary replacementStrings = new Dictionary {
- {"'''", "Third" }, {"''", "Second" }, {"'", "Prime"}
- };
+ static readonly Dictionary replacementStrings = new()
+ {
+ {"'''", "Third" }, {"''", "Second" }, {"'", "Prime"}
+ };
- private static StringBuilder EmitIdentifier(Context ctx, Token token)
- {
- var val = token.Value;
+ private static StringBuilder EmitIdentifier(Context ctx, Token token) {
+ var val = token.Value;
- if (val == "pi")
- {
- ctx.buffer.Append("PI"); // Doesn't follow pattern
- return ctx.buffer;
- }
+ if(val == "pi") {
+ ctx.buffer.Append("PI"); // Doesn't follow pattern
+ return ctx.buffer;
+ }
- if (validFunctions.Contains(val))
- {
- ctx.buffer.Append(char.ToUpper(val[0]) + val.Substring(1));
- return ctx.buffer;
- }
+ if(validFunctions.Contains(val)) {
+ ctx.buffer.Append(char.ToUpper(val[0]) + val.Substring(1));
+ return ctx.buffer;
+ }
- string id = token.Value;
- if (ctx.globalSymbolTable.Contains(token.Value) ||
- ctx.symbolTable.Contains(token.Value))
- {
- foreach (var r in replacementStrings)
- {
- id = id.Replace(r.Key, r.Value);
- }
- return ctx.buffer.Append(id);
- }
- else
- {
- throw new Exception($"{token.Value} not a known identifier or function.");
+ string id = token.Value;
+ if(ctx.globalSymbolTable.Contains(token.Value) ||
+ ctx.symbolTable.Contains(token.Value)) {
+ foreach (var r in replacementStrings) {
+ id = id.Replace(r.Key, r.Value);
}
+ return ctx.buffer.Append(id);
+ } else {
+ throw new Exception($"{token.Value} not a known identifier or function.");
}
+ }
- private static StringBuilder Emit(Context ctx, Token token) => token.Type switch
- {
- TokenType.EOL => ctx.buffer.Append("\n"),
- TokenType.CloseParens => ctx.buffer.Append(')'), // All parens become rounded
- TokenType.OpenParens => ctx.buffer.Append('('),
- TokenType.Equal => ctx.buffer.Append("=>"),
- TokenType.Comma => ctx.buffer.Append(token.Value),
-
- // Identifiers are normalized and checked for injection attacks
- TokenType.Identifier => EmitIdentifier(ctx, token),
- TokenType.Number => ctx.buffer.Append(token.Value),
- TokenType.Operation => ctx.buffer.Append(token.Value),
- TokenType.Sum => ctx.buffer.Append("MySum"),
- _ => Error(token, TokenType.None)
- };
-
- private static bool Peek(Context ctx, TokenType type, string value = "")
- {
- var token = ctx.tokens.Current;
-
- return (token.Type == type && value == "") ||
- (token.Type == type && value == token.Value);
- }
- private static Token NextToken(Context ctx)
- {
+ private static StringBuilder Emit(Context ctx, Token token) => token.Type switch
+ {
+ TokenType.EOL => ctx.buffer.Append("\n"),
+ TokenType.CloseParens => ctx.buffer.Append(')'), // All parens become rounded
+ TokenType.OpenParens => ctx.buffer.Append('('),
+ TokenType.Equal => ctx.buffer.Append("=>"),
+ TokenType.Comma => ctx.buffer.Append(token.Value),
+
+ // Identifiers are normalized and checked for injection attacks
+ TokenType.Identifier => EmitIdentifier(ctx, token),
+ TokenType.Number => ctx.buffer.Append(token.Value),
+ TokenType.Operation => ctx.buffer.Append(token.Value),
+ TokenType.Sum => ctx.buffer.Append("MySum"),
+ _ => Error(token, TokenType.None)
+ };
+
+ private static bool Peek(Context ctx, TokenType type, string value = "") {
+ var token = ctx.tokens.Current;
+
+ return (token.Type == type && value == "") ||
+ (token.Type == type && value == token.Value);
+ }
+ private static Token NextToken(Context ctx) {
- var token = ctx.tokens.Current;
- ctx.tokens.MoveNext();
- return token;
- }
- private static void Consume(Context ctx, TokenType type, string value = "")
- {
+ var token = ctx.tokens.Current;
+ ctx.tokens.MoveNext();
+ return token;
+ }
+ private static void Consume(Context ctx, TokenType type, string value = "") {
- var token = NextToken(ctx);
+ var token = NextToken(ctx);
- if ((token.Type == type && value == "") ||
- (token.Type == type && value == token.Value))
- {
+ if((token.Type == type && value == "") ||
+ (token.Type == type && value == token.Value)) {
- ctx.buffer.Append(" ");
- Emit(ctx, token);
- }
- else
- {
- Error(token, type, value);
- }
+ ctx.buffer.Append(" ");
+ Emit(ctx, token);
+ } else {
+ Error(token, type, value);
}
+ }
- private static StringBuilder Lines(Context ctx)
- {
- // lines = {line} EOF
+ private static StringBuilder Lines(Context ctx) {
+ // lines = {line} EOF
- ctx.buffer.Append(Preamble);
+ ctx.buffer.Append(Preamble);
- while (!Peek(ctx, TokenType.EOF))
- Line(ctx);
+ while(!Peek(ctx, TokenType.EOF))
+ Line(ctx);
- ctx.buffer.Append(Ending);
+ ctx.buffer.Append(Ending);
- return ctx.buffer;
- }
+ return ctx.buffer;
+ }
- private static void AddGlobalSymbol(Context ctx)
- {
- var token = ctx.tokens.Current;
- if (Peek(ctx, TokenType.Identifier))
- {
- ctx.globalSymbolTable.Add(token.Value);
- }
- else
- {
- Error(token, TokenType.Identifier);
- }
+ private static void AddGlobalSymbol(Context ctx) {
+ var token = ctx.tokens.Current;
+ if(Peek(ctx, TokenType.Identifier)) {
+ ctx.globalSymbolTable.Add(token.Value);
+ } else {
+ Error(token, TokenType.Identifier);
}
- private static void AddSymbol(Context ctx)
- {
- var token = ctx.tokens.Current;
- if (Peek(ctx, TokenType.Identifier))
- {
- ctx.symbolTable.Add(token.Value);
- }
- else
- {
- Error(token, TokenType.Identifier);
- }
+ }
+ private static void AddSymbol(Context ctx) {
+ var token = ctx.tokens.Current;
+ if(Peek(ctx, TokenType.Identifier)) {
+ ctx.symbolTable.Add(token.Value);
+ } else {
+ Error(token, TokenType.Identifier);
}
- private static void Line(Context ctx)
- {
- // line = {EOL} identifier [lround args rround] equal expr EOL {EOL}
+ }
+ private static void Line(Context ctx) {
+ // line = {EOL} identifier [lround args rround] equal expr EOL {EOL}
- ctx.symbolTable.Clear();
+ ctx.symbolTable.Clear();
- while (Peek(ctx, TokenType.EOL))
- Consume(ctx, TokenType.EOL);
+ while(Peek(ctx, TokenType.EOL))
+ Consume(ctx, TokenType.EOL);
- ctx.buffer.Append("\tpublic static double ");
+ ctx.buffer.Append("\tpublic static double ");
- AddGlobalSymbol(ctx);
- Consume(ctx, TokenType.Identifier);
+ AddGlobalSymbol(ctx);
+ Consume(ctx, TokenType.Identifier);
- if (Peek(ctx, TokenType.OpenParens, "("))
- {
- Consume(ctx, TokenType.OpenParens, "("); // Just round parens
- Args(ctx);
- Consume(ctx, TokenType.CloseParens, ")");
- }
+ if(Peek(ctx, TokenType.OpenParens, "(")) {
+ Consume(ctx, TokenType.OpenParens, "("); // Just round parens
+ Args(ctx);
+ Consume(ctx, TokenType.CloseParens, ")");
+ }
- Consume(ctx, TokenType.Equal);
- Expr(ctx);
- ctx.buffer.Append(" ;");
+ Consume(ctx, TokenType.Equal);
+ Expr(ctx);
+ ctx.buffer.Append(" ;");
+ Consume(ctx, TokenType.EOL);
+
+ while(Peek(ctx, TokenType.EOL))
Consume(ctx, TokenType.EOL);
+ }
+ private static void Args(Context ctx) {
+ // args = identifier {comma identifier}
+ // It doesn't make sense for a math function to have zero args (I think)
- while (Peek(ctx, TokenType.EOL))
- Consume(ctx, TokenType.EOL);
- }
- private static void Args(Context ctx)
- {
- // args = identifier {comma identifier}
- // It doesn't make sense for a math function to have zero args (I think)
+ ctx.buffer.Append("double ");
+ AddSymbol(ctx);
+ Consume(ctx, TokenType.Identifier);
+ while(Peek(ctx, TokenType.Comma)) {
+ Consume(ctx, TokenType.Comma);
ctx.buffer.Append("double ");
AddSymbol(ctx);
Consume(ctx, TokenType.Identifier);
-
- while (Peek(ctx, TokenType.Comma))
- {
- Consume(ctx, TokenType.Comma);
- ctx.buffer.Append("double ");
- AddSymbol(ctx);
- Consume(ctx, TokenType.Identifier);
- }
}
- private static Func IsOp = (ctx, op)
- => Peek(ctx, TokenType.Operation, op);
- private static Action ConsOp = (ctx, op)
- => Consume(ctx, TokenType.Operation, op);
+ }
+ private readonly static Func IsOp = (ctx, op)
+ => Peek(ctx, TokenType.Operation, op);
+ private static readonly Action ConsOp = (ctx, op)
+ => Consume(ctx, TokenType.Operation, op);
- private static void Expr(Context ctx)
- {
- // expr = [plus|minus] term { (plus|minus) term }
+ private static void Expr(Context ctx) {
+ // expr = [plus|minus] term { (plus|minus) term }
- if (IsOp(ctx, "+")) ConsOp(ctx, "+");
- if (IsOp(ctx, "-")) ConsOp(ctx, "-");
+ if(IsOp(ctx, "+")) ConsOp(ctx, "+");
+ if(IsOp(ctx, "-")) ConsOp(ctx, "-");
- Term(ctx);
+ Term(ctx);
- while (IsOp(ctx, "+") || IsOp(ctx, "-"))
- {
+ while(IsOp(ctx, "+") || IsOp(ctx, "-")) {
- if (IsOp(ctx, "+")) ConsOp(ctx, "+");
- if (IsOp(ctx, "-")) ConsOp(ctx, "-");
+ if(IsOp(ctx, "+")) ConsOp(ctx, "+");
+ if(IsOp(ctx, "-")) ConsOp(ctx, "-");
- Term(ctx);
- }
+ Term(ctx);
}
- private static void Term(Context ctx)
- {
- // term = factor { (times|divide) factor };
- Factor(ctx);
+ }
+ private static void Term(Context ctx) {
+ // term = factor { (times|divide) factor };
+ Factor(ctx);
- while (IsOp(ctx, "*") || IsOp(ctx, "/"))
- {
- if (IsOp(ctx, "*")) ConsOp(ctx, "*");
- if (IsOp(ctx, "/")) ConsOp(ctx, "/");
+ while(IsOp(ctx, "*") || IsOp(ctx, "/")) {
+ if(IsOp(ctx, "*")) ConsOp(ctx, "*");
+ if(IsOp(ctx, "/")) ConsOp(ctx, "/");
- Term(ctx);
- }
+ Term(ctx);
+ }
+ }
+ private static void Factor(Context ctx) {
+ // factor = number | var | func | lround expr rround;
+ if(Peek(ctx, TokenType.Number)) {
+ Consume(ctx, TokenType.Number);
+ return;
}
- private static void Factor(Context ctx)
- {
- // factor = number | var | func | lround expr rround;
- if (Peek(ctx, TokenType.Number))
- {
- Consume(ctx, TokenType.Number);
- return;
- }
- if (Peek(ctx, TokenType.Identifier))
- {
- Consume(ctx, TokenType.Identifier); // Is either var or func
- if (Peek(ctx, TokenType.OpenParens, "("))
- { // Is Func, but we already consumed its name
- Funct(ctx);
- }
- return;
- }
- if (Peek(ctx, TokenType.Sum))
- {
- Sum(ctx);
- return;
+ if(Peek(ctx, TokenType.Identifier)) {
+ Consume(ctx, TokenType.Identifier); // Is either var or func
+ if(Peek(ctx, TokenType.OpenParens, "(")) { // Is Func, but we already consumed its name
+ Funct(ctx);
}
- // Must be a parenthesized expression
- Consume(ctx, TokenType.OpenParens);
- Expr(ctx);
- Consume(ctx, TokenType.CloseParens);
+ return;
}
- private static void Sum(Context ctx)
- {
- // sum = ∑ lround identifier comma expr1 comma expr2 comma expr3 rround;
- // TODO: differentiate in the language between integer and double, but complicated for a sample.
- Consume(ctx, TokenType.Sum);
- Consume(ctx, TokenType.OpenParens, "(");
-
- AddSymbol(ctx);
- var varName = NextToken(ctx).Value;
- NextToken(ctx); // consume the first comma without emitting it
+ if(Peek(ctx, TokenType.Sum)) {
+ Sum(ctx);
+ return;
+ }
+ // Must be a parenthesized expression
+ Consume(ctx, TokenType.OpenParens);
+ Expr(ctx);
+ Consume(ctx, TokenType.CloseParens);
+ }
+ private static void Sum(Context ctx) {
+ // sum = ∑ lround identifier comma expr1 comma expr2 comma expr3 rround;
+ // TODO: differentiate in the language between integer and double, but complicated for a sample.
+ Consume(ctx, TokenType.Sum);
+ Consume(ctx, TokenType.OpenParens, "(");
- ctx.buffer.Append("(int)");
- Expr(ctx); // Start index
- Consume(ctx, TokenType.Comma);
+ AddSymbol(ctx);
+ var varName = NextToken(ctx).Value;
+ NextToken(ctx); // consume the first comma without emitting it
- ctx.buffer.Append("(int)");
- Expr(ctx); // End index
- Consume(ctx, TokenType.Comma);
+ ctx.buffer.Append("(int)");
+ Expr(ctx); // Start index
+ Consume(ctx, TokenType.Comma);
- ctx.buffer.Append($"{varName} => "); // It needs to be a lambda
+ ctx.buffer.Append("(int)");
+ Expr(ctx); // End index
+ Consume(ctx, TokenType.Comma);
- Expr(ctx); // expr to evaluate at each iteration
+ ctx.buffer.Append($"{varName} => "); // It needs to be a lambda
+
+ Expr(ctx); // expr to evaluate at each iteration
- Consume(ctx, TokenType.CloseParens, ")");
- }
- private static void Funct(Context ctx)
- {
- // func = identifier lround expr {comma expr} rround;
- Consume(ctx, TokenType.OpenParens, "(");
+ Consume(ctx, TokenType.CloseParens, ")");
+ }
+ private static void Funct(Context ctx) {
+ // func = identifier lround expr {comma expr} rround;
+ Consume(ctx, TokenType.OpenParens, "(");
+ Expr(ctx);
+ while(Peek(ctx, TokenType.Comma)) {
+ Consume(ctx, TokenType.Comma);
Expr(ctx);
- while (Peek(ctx, TokenType.Comma))
- {
- Consume(ctx, TokenType.Comma);
- Expr(ctx);
- }
- Consume(ctx, TokenType.CloseParens, ")");
}
+ Consume(ctx, TokenType.CloseParens, ")");
}
+}
- [Generator]
- public class MathsGenerator : ISourceGenerator
- {
- private const string libraryCode = @"
+[Generator]
+public class MathsGenerator : IIncrementalGenerator
+{
+ private const string libraryCode = @"
using System.Linq;
using System;
using System.Collections.Generic;
@@ -486,43 +415,41 @@ public static double MySum(int start, int end, Func f) =>
}
";
- public void Execute(GeneratorExecutionContext context)
- {
- foreach (AdditionalText file in context.AdditionalFiles)
- {
- if (Path.GetExtension(file.Path).Equals(".math", StringComparison.OrdinalIgnoreCase))
- {
- // Load formulas from .math files
- var mathText = file.GetText();
- var mathString = "";
-
- if (mathText != null)
- {
- mathString = mathText.ToString();
- }
- else
- {
- throw new Exception($"Cannot load file {file.Path}");
- }
-
- // Get name of generated namespace from file name
- string fileName = Path.GetFileNameWithoutExtension(file.Path);
-
- // Parse and gen the formulas functions
- var tokens = Lexer.Tokenize(mathString);
- var code = Parser.Parse(tokens);
-
- var codeFileName = $@"{fileName}.g.cs";
-
- context.AddSource(codeFileName, SourceText.From(code, Encoding.UTF8));
- }
- }
- }
+ public (string name, SourceText code) GetNameAndCode(AdditionalText file, CancellationToken _)
+ {
+ // Load formulas from .math files
+ var mathText = file.GetText();
+ string mathString;
- public void Initialize(GeneratorInitializationContext context)
+ if (mathText != null)
+ {
+ mathString = mathText.ToString();
+ }
+ else
{
- context.RegisterForPostInitialization((pi) => pi.AddSource("__MathLibrary__.g.cs", libraryCode));
+ throw new Exception($"Cannot load file {file.Path}");
}
+
+ // Get name of generated namespace from file name
+ string fileName = Path.GetFileNameWithoutExtension(file.Path);
+
+ // Parse and gen the formulas functions
+ var tokens = Lexer.Tokenize(mathString);
+ var code = Parser.Parse(tokens);
+
+ var codeFileName = $@"{fileName}.g.cs";
+
+ return (codeFileName, SourceText.From(code, Encoding.UTF8));
+ }
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ context.RegisterPostInitializationOutput((pi) => pi.AddSource("__MathLibrary__.g.cs", libraryCode));
+
+ var files = context.AdditionalTextsProvider.Where(file => Path.GetExtension(file.Path).Equals(".math", StringComparison.OrdinalIgnoreCase));
+ var nameAndCode = files.Select(GetNameAndCode);
+
+ context.RegisterSourceOutput(nameAndCode, (ctx, source) => ctx.AddSource(source.name, source.code));
}
}
diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs
index ffc4307ef..0363a5dc7 100644
--- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs
+++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs
@@ -1,18 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
+namespace Mustache;
-#nullable enable
-
-namespace Mustache
+[Generator]
+public class MustacheGenerator : IIncrementalGenerator
{
- [Generator]
- public class MustacheGenerator : ISourceGenerator
- {
- private const string attributeSource = @"
+ private const string attributeSource = @"
[System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple=true)]
internal sealed class MustacheAttribute: System.Attribute
{
@@ -24,24 +15,14 @@ public MustacheAttribute(string name, string template, string hash)
}
";
- public void Execute(GeneratorExecutionContext context)
- {
- SyntaxReceiver rx = (SyntaxReceiver)context.SyntaxContextReceiver!;
- foreach ((string name, string template, string hash) in rx.TemplateInfo)
- {
- string source = SourceFileFromMustachePath(name, template, hash);
- context.AddSource($"Mustache{name}.g.cs", source);
- }
- }
-
- static string SourceFileFromMustachePath(string name, string template, string hash)
- {
- Func