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

AndreuCodina / CrossValidation / 5660476106

25 Jul 2023 06:32PM UTC coverage: 95.769% (+0.02%) from 95.749%
5660476106

Pull #58

github

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

423 of 468 branches covered (90.38%)

Branch coverage included in aggregate %.

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

1433 of 1470 relevant lines covered (97.48%)

171.86 hits per line

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

91.45
/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 void GenerateParentClassesEnd(StringBuilder code, int numberOfParentClasses)
230
    {
231

232
        for (var i = 0; i < numberOfParentClasses; i++)
6✔
233
        {
234
            if (numberOfParentClasses > 0)
2✔
235
            {
236
                code.AppendLine();
2✔
237
            }
238
            
239
            code.AppendLine("}");
2✔
240
        }
241
    }
1✔
242

243
    private static void GenerateParentClassesStart(StringBuilder code, ParentClassInformation? parentClass,
244
        ref int numberOfParentClasses)
245
    {
246
        var parentClassIterated = parentClass;
1✔
247
        
248
        while (parentClassIterated is not null)
3✔
249
        {
250
            code.AppendLine(
2✔
251
                $$"""
2✔
252
                  {{parentClassIterated.Modifiers}} {{parentClassIterated.StructureType}} {{parentClassIterated.NameWithRestrictions}}
2✔
253
                  {
2✔
254
                  """);
2✔
255
            numberOfParentClasses++;
2✔
256
            parentClassIterated = parentClassIterated.Child;
2✔
257
        }
258
    }
1✔
259

260
    private static void GenerateNamespacesStart(StringBuilder code, string? @namespace)
261
    {
262
        if (@namespace is null)
1!
263
        {
264
            return;
×
265
        }
266

267
        code.AppendLine(
1✔
268
            $$"""
1✔
269
              namespace {{@namespace}}
1✔
270
              {
1✔
271
              """);
1✔
272
    }
1✔
273
    
274
    private static void GenerateNamespacesEnd(StringBuilder code, string? @namespace)
275
    {
276
        if (@namespace is null)
1!
277
        {
278
            return;
×
279
        }
280

281
        code.AppendLine("}");
1✔
282
    }
1✔
283
    
284
    private static ParentClassInformation? GetParentClass(BaseTypeDeclarationSyntax typeSyntax)
285
    {
286
        TypeDeclarationSyntax? parentSyntax = typeSyntax.Parent as TypeDeclarationSyntax;
1✔
287
        ParentClassInformation? parentClassInfo = null;
1✔
288

289
        while (parentSyntax != null
3!
290
               && (parentSyntax.IsKind(SyntaxKind.ClassDeclaration)
3✔
291
               || parentSyntax.IsKind(SyntaxKind.StructDeclaration)
3✔
292
               || parentSyntax.IsKind(SyntaxKind.RecordDeclaration)))
3✔
293
        {
294
            parentClassInfo = new ParentClassInformation(
2✔
295
                child: parentClassInfo,
2✔
296
                modifiers: parentSyntax.Modifiers.ToString(),
2✔
297
                structureType: parentSyntax.Keyword.ValueText,
2✔
298
                name: parentSyntax.Identifier.ToString(),
2✔
299
                nameWithRestrictions:
2✔
300
                    parentSyntax.Identifier.ToString()
2✔
301
                    + parentSyntax.TypeParameterList
2✔
302
                    + " "
2✔
303
                    + parentSyntax.ConstraintClauses.ToString());
2✔
304

305
            parentSyntax = parentSyntax.Parent as TypeDeclarationSyntax;
2✔
306
        }
307

308
        return parentClassInfo;
1✔
309
    }
310
}
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