Skip to content

Commit 98ab2cc

Browse files
committed
Add support for
ImplicitElementsAccess in NewClass DefaultExpressions InitializerExpression NewClass creation with initializer syntax Yield statements Pattern matching in Is, SwitchExpression, SwitchStatement Foreach with tuples Fixed/Using/Checked/Unsafe Major refactoring to simplify use of Type attestation. Move from using extension methods and dynamic method hacks to partial class definitions with generic interfaces to help with using more specific return types in deriving classes
1 parent 90a65ba commit 98ab2cc

File tree

292 files changed

+16633
-3488
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

292 files changed

+16633
-3488
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,4 @@ src/main/generated/
286286

287287
## vscode
288288
.vscode/
289+
**/*.v3.ncrunchproject

Rewrite/Rewrite.v3.ncrunchsolution

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<SolutionConfiguration>
2+
<Settings>
3+
<AllowParallelTestExecution>True</AllowParallelTestExecution>
4+
<EnableRDI>True</EnableRDI>
5+
<RdiConfigured>True</RdiConfigured>
6+
<SolutionConfigured>True</SolutionConfigured>
7+
</Settings>
8+
</SolutionConfiguration>
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
using Microsoft.CodeAnalysis.Text;
11+
12+
namespace Rewrite.Analyzers;
13+
14+
[Generator]
15+
public class BuilderSourceGenerator : ISourceGenerator
16+
{
17+
public void Initialize(GeneratorInitializationContext context)
18+
{
19+
20+
}
21+
22+
public void Execute(GeneratorExecutionContext context)
23+
{
24+
25+
// using the context, get a list of syntax trees in the users compilation
26+
foreach (var syntaxTree in context.Compilation.SyntaxTrees)
27+
{
28+
var classBuilders = GenerateBuilder(syntaxTree, context.Compilation);
29+
// add the filepath of each tree to the class we're building
30+
foreach (var classBuilder in classBuilders)
31+
{
32+
context.AddSource($"{classBuilder.Key}.Builder.g.cs", SourceText.From(classBuilder.Value, Encoding.UTF8));
33+
}
34+
35+
}
36+
37+
38+
// inject the created source into the users compilation
39+
40+
}
41+
42+
public static Dictionary<string, string> GenerateBuilder(SyntaxTree syntaxTree, Compilation compilation)
43+
{
44+
45+
var classToBuilder = new Dictionary<string, string>();
46+
var cu = (CompilationUnitSyntax)syntaxTree.GetRoot();
47+
var classesWithAttribute = cu
48+
.DescendantNodes()
49+
.OfType<ClassDeclarationSyntax>()
50+
.Where(cds => cds.AttributeLists.HasAttribute(nameof(GenerateBuilderAttribute)))
51+
.ToList();
52+
53+
if (classesWithAttribute.Count == 0)
54+
return classToBuilder;
55+
56+
var model = compilation.GetSemanticModel(syntaxTree, true);
57+
58+
foreach (var classDeclaration in classesWithAttribute)
59+
{
60+
string className = null!;
61+
try
62+
{
63+
className = classDeclaration.Identifier.Text;
64+
var diag = new StringBuilder();
65+
66+
var namespaceName = ModelExtensions.GetDeclaredSymbol(compilation.GetSemanticModel(syntaxTree), classDeclaration)?
67+
.ContainingNamespace.Name;
68+
var namespaceDeclaration = !string.IsNullOrEmpty(namespaceName) ? $"namespace {namespaceName};" : "";
69+
70+
var properties = classDeclaration.DescendantNodes()
71+
.OfType<PropertyDeclarationSyntax>()
72+
.Where(x => x.Modifiers.All(m => m.ToString() != "static") && x.DescendantNodes().OfType<AccessorDeclarationSyntax>().Any(d => d.IsKind(SyntaxKind.SetAccessorDeclaration) || d.IsKind(SyntaxKind.InitAccessorDeclaration)))
73+
.Select(x => new BuilderPropertyInfo(x, model))
74+
.ToList();
75+
var builderName = $"{className}Builder";
76+
77+
78+
var requiredProperties = properties.Where(x => x.IsRequired).ToList();
79+
//language=csharp
80+
var builderTemplate =
81+
$$"""
82+
83+
{{@namespaceDeclaration}}
84+
{{cu.Usings}}
85+
partial class {{@className}}
86+
{
87+
public {{@builderName}} Builder => new {{@builderName}}(this);
88+
public struct {{@builderName}}
89+
{
90+
private byte _set;
91+
{{@className}} _original;
92+
93+
public {{@builderName}}({{@className}} original)
94+
{
95+
_original = original;
96+
}
97+
98+
{{properties.Render((p, i) => $$"""
99+
private {{p.Type}} {{p.BackingFieldName}};
100+
public {{@builderName}} With{{p.Name}}({{p.Type}} {{p.ParameterName}})
101+
{
102+
{{p.BackingFieldName}} = {{p.ParameterName}};
103+
_set |= {{i + 1}};
104+
return this;
105+
}
106+
private bool Is{{p.Name}}Set => (_set & {{i + 1}}) == {{i + 1}};
107+
108+
109+
""").Ident(2)}}
110+
111+
public {{@className}} Build()
112+
{
113+
if(_original == null)
114+
{
115+
if({{requiredProperties.Render(r => $"!Is{r.Name}Set", " || ")}})
116+
{
117+
var message = $"The following required properties have not been set: {{requiredProperties.Render(r => $$"""{(!Is{{r.Name}}Set ? "{{r.Name}}" : "")}, """)}}";
118+
throw new InvalidOperationException(message.TrimEnd(',',' '));
119+
}
120+
121+
return new {{@className}}
122+
{
123+
{{properties.Render(p => $$"""
124+
{{p.Name}} = {{p.BackingFieldName}}
125+
""", ",").Ident(4)}}
126+
};
127+
}
128+
129+
{{properties.Render(p => /*language=csharp*/ $$"""
130+
if(Is{{p.Name}}Set && !object.Equals({{p.BackingFieldName}}, _original.{{p.Name}}))
131+
{
132+
goto clone;
133+
}
134+
135+
""").Ident(3)}}
136+
return _original;
137+
clone:
138+
return new {{@className}}
139+
{
140+
{{properties.Render(p => /*language=csharp*/$$"""
141+
{{p.Name}} = Is{{p.Name}}Set ? {{p.BackingFieldName}} : _original.{{p.Name}}
142+
""", ",").Ident(3)}}
143+
};
144+
}
145+
}
146+
}
147+
""";
148+
classToBuilder[className] = builderTemplate;
149+
}
150+
catch (Exception e)
151+
{
152+
classToBuilder[className] = $"/*\n{e.ToString()}\n/*";
153+
}
154+
}
155+
156+
157+
return classToBuilder;
158+
}
159+
160+
161+
struct BuilderPropertyInfo
162+
{
163+
164+
public BuilderPropertyInfo(PropertyDeclarationSyntax property, SemanticModel model) : this()
165+
{
166+
IsRequired = property.Modifiers.Any(x => x.ToString() == "required");
167+
Type = model.GetDeclaredSymbol(property)!.Type.ToString();
168+
Name = property.Identifier.ToString();
169+
ParameterName = Name.ToCamelCase().EnsureSafeIdentifier();
170+
BackingFieldName = $"_{ParameterName}";
171+
}
172+
173+
public bool IsRequired { get; set; }
174+
public string Name { get; set; }
175+
public string Type { get; set; }
176+
public string ParameterName { get; set; }
177+
public string BackingFieldName { get; set; }
178+
}
179+
180+
}
181+

Rewrite/src/Rewrite.Analyzers/DebugAnalyzer.sln

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rewrite.Analyzers", "Rewrit
44
EndProject
55
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rewrite.CSharp", "..\Rewrite.CSharp\Rewrite.CSharp.csproj", "{E18B9AF4-A15B-4A68-AD48-6F4795DAFA38}"
66
EndProject
7+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rewrite.Java", "..\Rewrite.Java\Rewrite.Java.csproj", "{81F751A7-18A3-4092-BAB9-C0C9DFE47197}"
8+
EndProject
79
Global
810
GlobalSection(SolutionConfigurationPlatforms) = preSolution
911
Debug|Any CPU = Debug|Any CPU
@@ -17,5 +19,8 @@ Global
1719
{E18B9AF4-A15B-4A68-AD48-6F4795DAFA38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1820
{E18B9AF4-A15B-4A68-AD48-6F4795DAFA38}.Release|Any CPU.ActiveCfg = Release|Any CPU
1921
{E18B9AF4-A15B-4A68-AD48-6F4795DAFA38}.Release|Any CPU.Build.0 = Release|Any CPU
22+
{81F751A7-18A3-4092-BAB9-C0C9DFE47197}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23+
{81F751A7-18A3-4092-BAB9-C0C9DFE47197}.Release|Any CPU.ActiveCfg = Release|Any CPU
24+
{81F751A7-18A3-4092-BAB9-C0C9DFE47197}.Release|Any CPU.Build.0 = Release|Any CPU
2025
EndGlobalSection
2126
EndGlobal

Rewrite/src/Rewrite.Analyzers/EnumGenerator.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public void Execute(GeneratorExecutionContext context)
2525
var csContainerSrc = $$"""
2626
namespace Rewrite.RewriteCSharp.Tree;
2727
28-
public interface CsContainer
28+
public partial interface CsContainer
2929
{
3030
public record Location(CsSpace.Location BeforeLocation, CsRightPadded.Location ElementLocation)
3131
{
@@ -38,7 +38,7 @@ public record Location(CsSpace.Location BeforeLocation, CsRightPadded.Location E
3838
var csSpaceSrc = $$"""
3939
namespace Rewrite.RewriteCSharp.Tree;
4040
41-
public interface CsSpace
41+
public partial interface CsSpace
4242
{
4343
public record Location
4444
{
@@ -52,7 +52,7 @@ public record Location
5252
var csRightPaddedSrc = $$"""
5353
namespace Rewrite.RewriteCSharp.Tree;
5454
55-
public interface CsRightPadded
55+
public partial interface CsRightPadded
5656
{
5757
public record Location(CsSpace.Location AfterLocation)
5858
{
@@ -66,7 +66,7 @@ public record Location(CsSpace.Location AfterLocation)
6666
var csLeftPaddedSrc = $$"""
6767
namespace Rewrite.RewriteCSharp.Tree;
6868
69-
public interface CsLeftPadded
69+
public partial interface CsLeftPadded
7070
{
7171
public record Location(CsSpace.Location BeforeLocation)
7272
{

Rewrite/src/Rewrite.Analyzers/Extensions.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp.Syntax;
23

34
namespace Rewrite.Analyzers;
45

56
public static class Extensions
67
{
8+
private static HashSet<string> _reservedWords =
9+
[
10+
"abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern",
11+
"false", "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", "operator", "out", "override",
12+
"params", "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint",
13+
"ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void", "volatile", "while"
14+
];
15+
public static string EnsureSafeIdentifier(this string s) => $"{s[0].ToString().ToLower()}{s.Remove(0, 1)}";
16+
public static string ToCamelCase(this string s) => $"{s[0].ToString().ToLower()}{s.Remove(0, 1)}";
17+
718
public static bool InheritsFrom(this INamedTypeSymbol symbol, string type)
819
{
920
var current = symbol.BaseType;
@@ -22,6 +33,35 @@ public static string Ident(this object source, int identLevels)
2233
return string.Join("\n", lines.Select((x, i) => $"""{ (i > 0 ? ident : "") }{x}"""));
2334
}
2435

36+
public static bool HasAttribute(this SyntaxList<AttributeListSyntax> attributes, string name)
37+
{
38+
string fullname, shortname;
39+
var attrLen = "Attribute".Length;
40+
if (name.EndsWith("Attribute"))
41+
{
42+
fullname = name;
43+
shortname = name.Remove(name.Length - attrLen, attrLen);
44+
}
45+
else
46+
{
47+
fullname = name + "Attribute";
48+
shortname = name;
49+
}
50+
51+
return attributes.Any(al => al.Attributes.Any(a => a.Name.ToString() == shortname || a.Name.ToString() == fullname));
52+
}
53+
54+
public static T? FindParent<T>(this SyntaxNode node) where T : class
55+
{
56+
var current = node;
57+
while(true)
58+
{
59+
current = current.Parent;
60+
if (current == null || current is T)
61+
return current as T;
62+
}
63+
}
64+
2565
public static string Render<T>(this IEnumerable<T> source, Func<T, string> template, string separator = "", string openToken = "", string closeToken = "", bool renderEmpty = true)
2666
{
2767
if (!renderEmpty && source.Count() == 0)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Rewrite.Analyzers;
2+
3+
public class GenerateBuilderAttribute : Attribute
4+
{
5+
6+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Text;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
using Microsoft.CodeAnalysis.Text;
5+
6+
namespace Rewrite.Analyzers;
7+
8+
[Generator]
9+
public class LstInterfaceImplementationsGenerator : ISourceGenerator
10+
{
11+
public void Initialize(GeneratorInitializationContext context)
12+
{
13+
context.RegisterForSyntaxNotifications(() => new LstLocator());
14+
}
15+
16+
public void Execute(GeneratorExecutionContext context)
17+
{
18+
var scanner = (LstLocator)context.SyntaxReceiver!;
19+
20+
}
21+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp.Syntax;
3+
4+
namespace Rewrite.Analyzers;
5+
6+
public class LstLocator : ISyntaxReceiver
7+
{
8+
public Dictionary<string, LstInfo> LstClasses { get; } = new();
9+
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
10+
{
11+
if (syntaxNode is ClassDeclarationSyntax c && c.Identifier.Text == "Kind")
12+
{
13+
Console.WriteLine("");
14+
}
15+
if(syntaxNode is ClassDeclarationSyntax {Parent: TypeDeclarationSyntax parent} classDeclaration
16+
&& (classDeclaration.BaseList?.Types.OfType<SimpleBaseTypeSyntax>().Any(x => x.ToString() is "Cs" or "J" or "Expression" or "Tree" or "Statement") ?? false)
17+
&& classDeclaration.SyntaxTree.FilePath.EndsWith(".g.cs"))
18+
// if (syntaxNode is ClassDeclarationSyntax
19+
// {
20+
// BaseList.Types: [SimpleBaseTypeSyntax
21+
// {
22+
// Type: IdentifierNameSyntax
23+
// {
24+
// Identifier.Text: "Cs" or "J" or "Expression" or "Tree" or "Statement"
25+
// }
26+
// }],
27+
// Parent: TypeDeclarationSyntax parent
28+
// // ,
29+
// // Parent: InterfaceDeclarationSyntax
30+
// // {
31+
// // Identifier.Text: "Cs" or "J" ,
32+
// // } parent,
33+
// } classDeclaration && classDeclaration.SyntaxTree.FilePath.EndsWith(".g.cs"))
34+
{
35+
LstClasses.Add($"{parent.Identifier.Text}.{classDeclaration.Identifier.Text}", new LstInfo(classDeclaration));
36+
37+
}
38+
}
39+
}
40+
41+
public class LstInfo(ClassDeclarationSyntax classDeclarationSyntax)
42+
{
43+
public ClassDeclarationSyntax Class => classDeclarationSyntax;
44+
public string ClassName => classDeclarationSyntax.Identifier.Text;
45+
public TypeDeclarationSyntax OwningType => (TypeDeclarationSyntax)Class.Parent!;
46+
public string OwningInterfaceName => OwningType.Identifier.Text;
47+
}

0 commit comments

Comments
 (0)