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

AndreuCodina / CrossValidation / 5660450732

25 Jul 2023 06:29PM UTC coverage: 95.131% (-0.6%) from 95.749%
5660450732

Pull #58

github

web-flow
Merge 9151f7cb9 into 36639f125
Pull Request #58: Reduce allocations in source generator

423 of 472 branches covered (89.62%)

Branch coverage included in aggregate %.

50 of 50 new or added lines in 1 file covered. (100.0%)

1433 of 1479 relevant lines covered (96.89%)

170.81 hits per line

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

86.64
/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
3✔
15
            .CreateSyntaxProvider(
3✔
16
                predicate: static (syntaxNode, _) => IsResxBusinessException(syntaxNode),
87✔
17
                transform: GetResxBusinessExceptionOrNull)
3✔
18
            .Where(static syntax => syntax is not null)
2✔
19
            .Select(static (syntax, _) => syntax!.Value)
1✔
20
            .Collect();
3✔
21
        context.RegisterSourceOutput(businessExceptionSyntaxes, GenerateCode);
3✔
22
    }
3✔
23

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

36
        var isPartialClassDefinedSeveralTimes = symbol.DeclaringSyntaxReferences.Length > 1;
2✔
37
        
38
        if (isPartialClassDefinedSeveralTimes)
2✔
39
        {
40
            return null;
1✔
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)
87✔
54
        {
55
            return false;
81✔
56
        }
57

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

66
        var baseList = classSyntax.BaseList;
5✔
67

68
        if (baseList is null)
5✔
69
        {
70
            return false;
3✔
71
        }
72
        var baseIdentifierNameSyntax = (IdentifierNameSyntax)baseList.Types[0].Type;
2✔
73
        var baseClassName = baseIdentifierNameSyntax.Identifier.Text;
2✔
74
        return baseClassName is "ResxBusinessException" or "FrontBusinessException";
2!
75
    }
76
    
77
    private static void GenerateCode(
78
        SourceProductionContext context,
79
        ImmutableArray<ExtractedResxBusinessExceptionSyntax> enumerations)
80
    {
81
        if (enumerations.IsDefaultOrEmpty)
3✔
82
        {
83
            return;
2✔
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 constructorParameters = constructorParameterList?.ChildNodes()
1!
147
            .Cast<ParameterSyntax>()
1✔
148
            .ToArray();
1✔
149
        var code = new StringBuilder();
1✔
150
        GenerateDirectivesStart(code);
1✔
151
        GenerateNamespacesStart(code, @namespace);
1✔
152
        GenerateParentClassesStart(code, parentClass, ref numberOfParentClasses);
1✔
153
        GenerateClassStart(code, modifiers, classNameWithRestrictions);
1✔
154
        GenerateProperties(code, constructorParameters);
1✔
155
        GenerateAddParametersAsPlaceholderValuesMethod(code, constructorParameterNames);
1✔
156
        GenerateClassEnd(code);
1✔
157
        GenerateParentClassesEnd(code, numberOfParentClasses);
1✔
158
        GenerateNamespacesEnd(code, @namespace);
1✔
159
        GenerateDirectivesEnd(code);
1✔
160
        return code.ToString();
1✔
161
    }
162

163
    private static void GenerateAddParametersAsPlaceholderValuesMethod(StringBuilder code, string[]? constructorParameterNames)
164
    {
165
        if (constructorParameterNames is null)
1!
166
        {
167
            return;
×
168
        }
169
        
170
        code.AppendLine(
1✔
171
            """
1✔
172
            public override void AddParametersAsPlaceholderValues()
1✔
173
            {
1✔
174
            """);
1✔
175
        
176
        foreach (var constructorParameterName in constructorParameterNames)
8✔
177
        {
178
            code.AppendLine(
3✔
179
                $$"""
3✔
180
                  AddPlaceholderValue({{constructorParameterName}});
3✔
181
                  """);
3✔
182
        }
183

184
        code.AppendLine("}");
1✔
185
    }
1✔
186

187
    private static void GenerateClassEnd(StringBuilder code)
188
    {
189
        code.AppendLine("}");
1✔
190
    }
1✔
191

192
    private static void GenerateClassStart(StringBuilder code, string modifiers, string classNameWithRestrictions)
193
    {
194
        code.AppendLine(
1✔
195
            $$"""
1✔
196
              {{modifiers}} class {{classNameWithRestrictions}}
1✔
197
              {
1✔
198
              """);
1✔
199
    }
1✔
200

201
    private static void GenerateDirectivesEnd(StringBuilder code)
202
    {
203
        code.AppendLine("#nullable restore");
1✔
204
    }
1✔
205

206
    private static void GenerateDirectivesStart(StringBuilder code)
207
    {
208
        code.AppendLine(
1✔
209
            """
1✔
210
            // <auto-generated />
1✔
211
            #nullable enable
1✔
212
            """);
1✔
213
    }
1✔
214

215
    private static void GenerateProperties(StringBuilder code, ParameterSyntax[]? constructorParameters)
216
    {
217
        if (constructorParameters is null)
1!
218
        {
219
            return;
×
220
        }
221
        
222
        foreach (var parameter in constructorParameters)
8✔
223
        {
224
            var identifier = parameter.Identifier.ToString();
3✔
225
            code.AppendLine($"public {parameter.Type!.ToString()} {char.ToUpper(identifier[0])}{identifier.Substring(1)} => {identifier};");
3✔
226
        }
227
    }
1✔
228

229
    private static string GenerateAddParametersAsPlaceholderValuesBody(string[]? constructorParameterNames)
230
    {
231
        if (constructorParameterNames is null)
×
232
        {
233
            return "";
×
234
        }
235
        
236
        var stringBuilder = new StringBuilder();
×
237
        
238
        foreach (var constructorParameterName in constructorParameterNames)
×
239
        {
240
            stringBuilder.AppendLine(
×
241
                $$"""
×
242
                  AddPlaceholderValue({{constructorParameterName}});
×
243
                  """);
×
244
        }
245

246
        return stringBuilder.ToString();
×
247
    }
248

249
    private static void GenerateParentClassesEnd(StringBuilder code, int numberOfParentClasses)
250
    {
251

252
        for (var i = 0; i < numberOfParentClasses; i++)
6✔
253
        {
254
            if (numberOfParentClasses > 0)
2✔
255
            {
256
                code.AppendLine();
2✔
257
            }
258
            
259
            code.AppendLine("}");
2✔
260
        }
261
    }
1✔
262

263
    private static void GenerateParentClassesStart(StringBuilder code, ParentClassInformation? parentClass,
264
        ref int numberOfParentClasses)
265
    {
266
        var parentClassIterated = parentClass;
1✔
267
        
268
        while (parentClassIterated is not null)
3✔
269
        {
270
            code.AppendLine(
2✔
271
                $$"""
2✔
272
                  {{parentClassIterated.Modifiers}} {{parentClassIterated.StructureType}} {{parentClassIterated.NameWithRestrictions}}
2✔
273
                  {
2✔
274
                  """);
2✔
275
            numberOfParentClasses++;
2✔
276
            parentClassIterated = parentClassIterated.Child;
2✔
277
        }
278
    }
1✔
279

280
    private static void GenerateNamespacesStart(StringBuilder code, string? @namespace)
281
    {
282
        if (@namespace is null)
1!
283
        {
284
            return;
×
285
        }
286

287
        code.AppendLine(
1✔
288
            $$"""
1✔
289
              namespace {{@namespace}}
1✔
290
              {
1✔
291
              """);
1✔
292
    }
1✔
293
    
294
    private static void GenerateNamespacesEnd(StringBuilder code, string? @namespace)
295
    {
296
        if (@namespace is null)
1!
297
        {
298
            return;
×
299
        }
300

301
        code.AppendLine("}");
1✔
302
    }
1✔
303
    
304
    private static ParentClassInformation? GetParentClass(BaseTypeDeclarationSyntax typeSyntax)
305
    {
306
        TypeDeclarationSyntax? parentSyntax = typeSyntax.Parent as TypeDeclarationSyntax;
1✔
307
        ParentClassInformation? parentClassInfo = null;
1✔
308

309
        while (parentSyntax != null
3!
310
               && (parentSyntax.IsKind(SyntaxKind.ClassDeclaration)
3✔
311
               || parentSyntax.IsKind(SyntaxKind.StructDeclaration)
3✔
312
               || parentSyntax.IsKind(SyntaxKind.RecordDeclaration)))
3✔
313
        {
314
            parentClassInfo = new ParentClassInformation(
2✔
315
                child: parentClassInfo,
2✔
316
                modifiers: parentSyntax.Modifiers.ToString(),
2✔
317
                structureType: parentSyntax.Keyword.ValueText,
2✔
318
                name: parentSyntax.Identifier.ToString(),
2✔
319
                nameWithRestrictions:
2✔
320
                    parentSyntax.Identifier.ToString()
2✔
321
                    + parentSyntax.TypeParameterList
2✔
322
                    + " "
2✔
323
                    + parentSyntax.ConstraintClauses.ToString());
2✔
324

325
            parentSyntax = parentSyntax.Parent as TypeDeclarationSyntax;
2✔
326
        }
327

328
        return parentClassInfo;
1✔
329
    }
330
}
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