• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

AndreuCodina / CrossValidation / 5596407402

19 Jul 2023 07:46AM UTC coverage: 93.468%. First build
5596407402

Pull #49

github

web-flow
Merge 664634804 into d3b2ca8c3
Pull Request #49: First version: business exception source generator

392 of 436 branches covered (89.91%)

Branch coverage included in aggregate %.

516 of 516 new or added lines in 35 files covered. (100.0%)

1325 of 1401 relevant lines covered (94.58%)

169.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.71
/src/CrossValidation.SourceGenerators/ResxBusinessExceptionSourceGenerator.cs
1
using System.Collections.Immutable;
2
using System.Text;
3
using Microsoft.CodeAnalysis;
4
using Microsoft.CodeAnalysis.CSharp;
5
using Microsoft.CodeAnalysis.CSharp.Syntax;
6

7
namespace CrossValidation.SourceGenerators;
8

9
[Generator(LanguageNames.CSharp)]
10
public class ResxBusinessExceptionSourceGenerator : IIncrementalGenerator
11
{
12
    public void Initialize(IncrementalGeneratorInitializationContext context)
13
    {
14
        var businessExceptionSyntaxes = context.SyntaxProvider
1✔
15
            .CreateSyntaxProvider(
1✔
16
                predicate: static (syntaxNode, _) => IsResxBusinessException(syntaxNode),
40✔
17
                transform: GetResxBusinessExceptionOrNull)
1✔
18
            .Where(static syntax => syntax is not null)
1✔
19
            .Select(static (syntax, _) => syntax!.Value)
1✔
20
            .Collect();
1✔
21
        context.RegisterSourceOutput(businessExceptionSyntaxes, GenerateCode);
1✔
22
    }
1✔
23

24
    private static ExtractedResxBusinessExceptionSyntax? GetResxBusinessExceptionOrNull(
25
        GeneratorSyntaxContext context,
26
        CancellationToken cancellationToken)
27
    {
28
        var classSyntax = (ClassDeclarationSyntax)context.Node;
1✔
29
        ITypeSymbol? symbol = context.SemanticModel.GetDeclaredSymbol(classSyntax);
1✔
30
        
31
        if (symbol is null)
1!
32
        {
33
            return null;
×
34
        }
35

36
        var isPartialClassDefinedSeveralTimes = symbol.DeclaringSyntaxReferences.Length > 1;
1✔
37
        
38
        if (isPartialClassDefinedSeveralTimes)
1!
39
        {
40
            return null;
×
41
        }
42

43
        var attributeContainingTypeSymbol = symbol.ContainingNamespace;
1✔
44

45
        if (attributeContainingTypeSymbol is null) return null;
1!
46
        
47
        cancellationToken.ThrowIfCancellationRequested();
1✔
48
        return new ExtractedResxBusinessExceptionSyntax(classSyntax, symbol);
1✔
49
    }
50

51
    private static bool IsResxBusinessException(SyntaxNode syntaxNode)
52
    {
53
        if (syntaxNode is not ClassDeclarationSyntax classSyntax)
40✔
54
        {
55
            return false;
37✔
56
        }
57

58
        var isPartial = classSyntax.Modifiers
3✔
59
            .Any(x => x.IsKind(SyntaxKind.PartialKeyword));
9✔
60
        
61
        if (!isPartial)
3!
62
        {
63
            return false;
×
64
        }
65

66
        var baseList = classSyntax.BaseList;
3✔
67

68
        if (baseList is null)
3✔
69
        {
70
            return false;
2✔
71
        }
72
        var baseIdentifierNameSyntax = (IdentifierNameSyntax)baseList.Types[0].Type;
1✔
73
        var baseClassName = baseIdentifierNameSyntax.Identifier.Text;
1✔
74
        return baseClassName == "ResxBusinessException";
1✔
75
    }
76
    
77
    private static void GenerateCode(
78
        SourceProductionContext context,
79
        ImmutableArray<ExtractedResxBusinessExceptionSyntax> enumerations)
80
    {
81
        if (enumerations.IsDefaultOrEmpty)
1!
82
        {
83
            return;
×
84
        }
85

86
        foreach (var extractedResxBusinessExceptionSyntax in enumerations)
4✔
87
        {
88
            var typeNamespace = extractedResxBusinessExceptionSyntax.Symbol.ContainingNamespace.IsGlobalNamespace
1!
89
                ? null
1✔
90
                : $"{extractedResxBusinessExceptionSyntax.Symbol.ContainingNamespace}.";
1✔
91
            var parentClass = GetParentClass(extractedResxBusinessExceptionSyntax.ClassSyntax);
1✔
92
            var classPath = GetClassPath(parentClass);
1✔
93
            var code = CreateCode(extractedResxBusinessExceptionSyntax, parentClass);
1✔
94

95
            context.AddSource($"{typeNamespace}{classPath}.{extractedResxBusinessExceptionSyntax.Symbol.Name}.generated.cs", code);
1✔
96
        }
97
    }
1✔
98

99
    private static string GetClassPath(ParentClassInformation? parentClass)
100
    {
101
        var classPathStringBuilder = new StringBuilder();
1✔
102
        var currentParentClass = parentClass;
1✔
103
        var isFirstClassVisited = true;
1✔
104

105
        while (currentParentClass is not null)
3✔
106
        {
107
            if (isFirstClassVisited)
2✔
108
            {
109
                isFirstClassVisited = false;
1✔
110
            }
111
            else
112
            {
113
                classPathStringBuilder.Append(".");
1✔
114
            }
115

116
            classPathStringBuilder.Append(currentParentClass.Name);
2✔
117
            currentParentClass = currentParentClass.Child;
2✔
118
        }
119
        
120
        return classPathStringBuilder.ToString();
1✔
121
    }
122

123
    private static string CreateCode(
124
        ExtractedResxBusinessExceptionSyntax extractedResxBusinessExceptionSyntax,
125
        ParentClassInformation? parentClass)
126
    {
127
        var @namespace = extractedResxBusinessExceptionSyntax.Symbol.ContainingNamespace.IsGlobalNamespace
1!
128
            ? null
1✔
129
            : extractedResxBusinessExceptionSyntax.Symbol.ContainingNamespace.ToString();
1✔
130
        var modifiers = extractedResxBusinessExceptionSyntax.ClassSyntax.Modifiers.ToString();
1✔
131
        var classNameWithRestrictions =
1!
132
            extractedResxBusinessExceptionSyntax.ClassSyntax.Identifier.ToString()
1✔
133
            + extractedResxBusinessExceptionSyntax.ClassSyntax.TypeParameterList
1✔
134
            + " "
1✔
135
            + extractedResxBusinessExceptionSyntax.ClassSyntax.ConstraintClauses.ToString();
1✔
136
        var numberOfParentClasses = 0;
1✔
137
        var constructorParameterList = extractedResxBusinessExceptionSyntax
1✔
138
            .ClassSyntax
1✔
139
            .ChildNodes()
1✔
140
            .FirstOrDefault(x => x.IsKind(SyntaxKind.ParameterList));
3✔
141
        var constructorParameterNames = constructorParameterList?.ChildNodes()
1!
142
            .Cast<ParameterSyntax>()
1✔
143
            .Select(x => x.Identifier)
3✔
144
            .Select(x => x.Text)
3✔
145
            .ToArray();
1✔
146
        var result =
1✔
147
            $$"""
1✔
148
              // <auto-generated />
1✔
149
              #nullable enable
1✔
150
              {{GenerateNamespaceStart(@namespace)}}
1✔
151
              {{GenerateParentClassStart(parentClass, ref numberOfParentClasses)}}
1✔
152
                  {{modifiers}} class {{classNameWithRestrictions}}
1✔
153
                  {
1✔
154
                      public override void AddParametersAsPlaceholderValues()
1✔
155
                      {
1✔
156
                          {{GenerateAddParametersAsPlaceholderValuesBody(constructorParameterNames)}}
1✔
157
                      }
1✔
158
                  }
1✔
159
              {{GenerateParentClassEnd(numberOfParentClasses)}}
1✔
160
              {{GenerateNamespaceEnd(@namespace)}}
1✔
161
              #nullable restore
1✔
162
              """;
1✔
163
        return result;
1✔
164
    }
165

166
    private static string GenerateAddParametersAsPlaceholderValuesBody(string[]? constructorParameterNames)
167
    {
168
        if (constructorParameterNames is null)
1!
169
        {
170
            return "";
×
171
        }
172
        
173
        var stringBuilder = new StringBuilder();
1✔
174
        
175
        foreach (var constructorParameterName in constructorParameterNames)
8✔
176
        {
177
            stringBuilder.AppendLine(
3✔
178
                $$"""
3✔
179
                  AddPlaceholderValue({{constructorParameterName}});
3✔
180
                  """);
3✔
181
        }
182

183
        return stringBuilder.ToString();
1✔
184
    }
185

186
    private static string GenerateParentClassEnd(int numberOfParentClasses)
187
    {
188
        var generatedCodeBuilder = new StringBuilder();
1✔
189
        
190
        for (var i = 0; i < numberOfParentClasses; i++)
6✔
191
        {
192
            if (numberOfParentClasses > 0)
2✔
193
            {
194
                generatedCodeBuilder.AppendLine();
2✔
195
            }
196
            
197
            generatedCodeBuilder.Append("}");
2✔
198
        }
199

200
        return generatedCodeBuilder.ToString();
1✔
201
    }
202

203
    private static string GenerateParentClassStart(
204
        ParentClassInformation? parentClass,
205
        ref int numberOfParentClasses)
206
    {
207
        var generatedCodeBuilder = new StringBuilder();
1✔
208
        var parentClassIterated = parentClass;
1✔
209
        
210
        while (parentClassIterated is not null)
3✔
211
        {
212
            if (numberOfParentClasses > 0)
2✔
213
            {
214
                generatedCodeBuilder.AppendLine();
1✔
215
            }
216
            
217
            generatedCodeBuilder.Append(
2✔
218
                $$"""
2✔
219
                  {{parentClassIterated.Modifiers}} {{parentClassIterated.StructureType}} {{parentClassIterated.NameWithRestrictions}}
2✔
220
                  {
2✔
221
                  """);
2✔
222
            numberOfParentClasses++;
2✔
223
            parentClassIterated = parentClassIterated.Child;
2✔
224
        }
225

226
        return generatedCodeBuilder.ToString();
1✔
227
    }
228

229
    private static string? GenerateNamespaceStart(string? @namespace)
230
    {
231
        if (@namespace is null)
1!
232
        {
233
            return null;
×
234
        }
235

236
        return
1✔
237
            $$"""
1✔
238
              namespace {{@namespace}}
1✔
239
              {
1✔
240
              """;
1✔
241
    }
242
    
243
    private static string? GenerateNamespaceEnd(string? @namespace)
244
    {
245
        if (@namespace is null)
1!
246
        {
247
            return null;
×
248
        }
249

250
        return "}";
1✔
251
    }
252
    
253
    static ParentClassInformation? GetParentClass(BaseTypeDeclarationSyntax typeSyntax)
254
    {
255
        TypeDeclarationSyntax? parentSyntax = typeSyntax.Parent as TypeDeclarationSyntax;
1✔
256
        ParentClassInformation? parentClassInfo = null;
1✔
257

258
        while (parentSyntax != null
3!
259
               && (parentSyntax.IsKind(SyntaxKind.ClassDeclaration)
3✔
260
               || parentSyntax.IsKind(SyntaxKind.StructDeclaration)
3✔
261
               || parentSyntax.IsKind(SyntaxKind.RecordDeclaration)))
3✔
262
        {
263
            parentClassInfo = new ParentClassInformation(
2✔
264
                child: parentClassInfo,
2✔
265
                modifiers: parentSyntax.Modifiers.ToString(),
2✔
266
                structureType: parentSyntax.Keyword.ValueText,
2✔
267
                name: parentSyntax.Identifier.ToString(),
2✔
268
                nameWithRestrictions:
2✔
269
                    parentSyntax.Identifier.ToString()
2✔
270
                    + parentSyntax.TypeParameterList
2✔
271
                    + " "
2✔
272
                    + parentSyntax.ConstraintClauses.ToString());
2✔
273

274
            parentSyntax = parentSyntax.Parent as TypeDeclarationSyntax;
2✔
275
        }
276

277
        return parentClassInfo;
1✔
278
    }
279
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc