-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
297 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using SourceKit.Generators.Builder.Annotations; | ||
|
||
namespace SourceKit.Sample.Generators; | ||
|
||
public class ArrayQueryUsage | ||
{ | ||
public void A() | ||
{ | ||
var query = ArrayQuery1.Build(x => x.WithId(Guid.NewGuid())); | ||
} | ||
} | ||
|
||
[GenerateBuilder] | ||
public record ArrayQuery1(Guid[] Ids, [RequiredValue] string Value, string NotRequiredValue) | ||
{ | ||
public static ArrayQuery1 Build(Func<Builder, Builder> action) | ||
{ | ||
return action(new Builder()).Build(); | ||
} | ||
|
||
public sealed class Builder | ||
{ | ||
private readonly List<System.Guid> _ids; | ||
|
||
public Builder() | ||
{ | ||
_ids = new List<System.Guid>(); | ||
} | ||
|
||
[InitializesProperty(nameof(Ids))] | ||
public Builder WithId(Guid element) | ||
{ | ||
_ids.Add(element); | ||
return this; | ||
} | ||
|
||
[InitializesPropertyAttribute(nameof(Ids))] | ||
public Builder WithIds(IEnumerable<System.Guid> elements) | ||
{ | ||
_ids.AddRange(elements); | ||
return this; | ||
} | ||
|
||
public ArrayQuery1 Build() | ||
{ | ||
return new ArrayQuery1(_ids.Distinct().ToArray(), string.Empty, string.Empty); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace SourceKit.Extensions; | ||
|
||
public static class AttributeDataExtensions | ||
{ | ||
public static bool IsAttribute(this AttributeData data, INamedTypeSymbol attribute) | ||
=> data.AttributeClass?.Equals(attribute, SymbolEqualityComparer.Default) is true; | ||
|
||
public static bool HasAttribute(this IEnumerable<AttributeData> attributes, INamedTypeSymbol attribute) | ||
=> attributes.Any(x => x.IsAttribute(attribute)); | ||
} |
4 changes: 4 additions & 0 deletions
4
src/generators/SourceKit.Generators.Builder.Annotations/InitializesPropertyAttribute.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
namespace SourceKit.Generators.Builder.Annotations; | ||
|
||
[AttributeUsage(AttributeTargets.Method)] | ||
public class InitializesPropertyAttribute(string PropertyName) : Attribute; |
4 changes: 4 additions & 0 deletions
4
src/generators/SourceKit.Generators.Builder.Annotations/RequiredValueAttribute.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
namespace SourceKit.Generators.Builder.Annotations; | ||
|
||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)] | ||
public class RequiredValueAttribute : Attribute; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
src/generators/SourceKit.Generators.Builder/Analyzers/RequiredValueAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Operations; | ||
using SourceKit.Extensions; | ||
|
||
namespace SourceKit.Generators.Builder.Analyzers; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class RequiredValueAnalyzer : DiagnosticAnalyzer | ||
{ | ||
public const string DiagnosticId = "SK2100"; | ||
public const string Title = nameof(RequiredValueAnalyzer); | ||
|
||
public const string Format = """Reqired properties {0} must be initialized"""; | ||
|
||
public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( | ||
DiagnosticId, | ||
Title, | ||
Format, | ||
"Usage", | ||
DiagnosticSeverity.Error, | ||
true); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = | ||
ImmutableArray.Create(Descriptor); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.EnableConcurrentExecution(); | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | | ||
GeneratedCodeAnalysisFlags.ReportDiagnostics); | ||
|
||
context.RegisterOperationAction(AnalyzeOperation, OperationKind.Invocation); | ||
} | ||
|
||
private void AnalyzeOperation(OperationAnalysisContext context) | ||
{ | ||
var operation = (IInvocationOperation)context.Operation; | ||
|
||
if (operation.Instance is not null) | ||
return; | ||
|
||
var generateBuilderAttribute = context.Compilation.GetTypeByMetadataName( | ||
Constants.GenerateBuilderAttributeFullyQualifiedName); | ||
|
||
var requiredValueAttribute = context.Compilation.GetTypeByMetadataName( | ||
Constants.RequiredValueAttributeFullyQualifiedName); | ||
|
||
var initializesValueAttribute = context.Compilation.GetTypeByMetadataName( | ||
Constants.InitializesPropertyAttributeFullyQualifiedName); | ||
|
||
if (generateBuilderAttribute is null | ||
|| requiredValueAttribute is null | ||
|| initializesValueAttribute is null) | ||
{ | ||
return; | ||
} | ||
|
||
if (operation.Type is not INamedTypeSymbol modelType) | ||
return; | ||
|
||
var hasBuilderAttribute = operation.Type | ||
.GetAttributes() | ||
.Any(x => x.AttributeClass?.Equals(generateBuilderAttribute, SymbolEqualityComparer.Default) is true); | ||
|
||
if (hasBuilderAttribute is false) | ||
return; | ||
|
||
if (operation.TargetMethod.Name is not "Build") | ||
return; | ||
|
||
ImmutableArray<ISymbol> modelTypeMembers = modelType.GetMembers(); | ||
|
||
IEnumerable<string> requiredProperties = modelTypeMembers | ||
.OfType<IPropertySymbol>() | ||
.Where(property => property.GetAttributes().HasAttribute(requiredValueAttribute)) | ||
.Select(x => x.Name); | ||
|
||
IEnumerable<string> requiredParameters = modelType.Constructors | ||
.SelectMany(x => x.Parameters) | ||
.Where(x => x.GetAttributes().HasAttribute(requiredValueAttribute)) | ||
.Select(x => x.Name); | ||
|
||
IEnumerable<IInvocationOperation> descendantInvocations = modelTypeMembers | ||
.OfType<IInvocationOperation>(); | ||
|
||
IEnumerable<string> initializedPropertyNames = GetInitializedPropertyNames(descendantInvocations); | ||
|
||
var unintializedPropertyNames = requiredProperties | ||
.Union(requiredParameters) | ||
.Except(initializedPropertyNames) | ||
.ToArray(); | ||
|
||
if (unintializedPropertyNames is []) | ||
return; | ||
|
||
var location = operation.Syntax.GetLocation(); | ||
|
||
context.ReportDiagnostic( | ||
Diagnostic.Create(Descriptor, location, messageArgs: string.Join(", ", unintializedPropertyNames))); | ||
|
||
return; | ||
|
||
IEnumerable<string> GetInitializedPropertyNames(IEnumerable<IInvocationOperation> invocations) | ||
{ | ||
foreach (var invocation in invocations) | ||
{ | ||
var attribute = invocation.TargetMethod | ||
.GetAttributes() | ||
.SingleOrDefault(x => x.IsAttribute(initializesValueAttribute)); | ||
|
||
if (attribute?.ConstructorArguments is [{ Value: string parameterName }]) | ||
yield return parameterName; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
src/generators/SourceKit.Generators.Builder/Models/InitializesPropertyAttributeBuilder.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; | ||
|
||
namespace SourceKit.Generators.Builder.Models; | ||
|
||
public readonly record struct InitializesPropertyAttributeBuilder(string PropertyName) | ||
{ | ||
private static readonly AttributeSyntax AttributeValue = Attribute( | ||
IdentifierName(Constants.InitializesPropertyAttributeName)); | ||
|
||
public static implicit operator AttributeListSyntax(InitializesPropertyAttributeBuilder builder) | ||
{ | ||
var propertyName = Argument(IdentifierName(builder.PropertyName)); | ||
|
||
var nameofSyntax = IdentifierName(Identifier( | ||
TriviaList(), | ||
SyntaxKind.NameOfKeyword, | ||
"nameof", | ||
"nameof", | ||
TriviaList())); | ||
|
||
var argument = AttributeArgument(InvocationExpression(nameofSyntax).AddArgumentListArguments(propertyName)); | ||
var attribute = AttributeValue.AddArgumentListArguments(argument); | ||
|
||
return AttributeList(SingletonSeparatedList(attribute)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 0 additions & 1 deletion
1
...enerators/SourceKit.Generators.Builder/Receivers/BuilderAttributeSyntaxContextReceiver.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.