Skip to content

Commit 5222873

Browse files
authored
Feature/563 source generator should respect the accessibility of the partial struct that it is assigned to (#581)
* Added the ability to override the accessibility. * Updated the source generator to respect the accessibility of the type. * Updated README and version number
1 parent fd9b51a commit 5222873

19 files changed

+235
-29
lines changed

GitVersion.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ branches:
2020
- feature
2121
- support
2222
- hotfix
23-
next-version: "4.2"
23+
next-version: "4.3"
2424

README.md

+25
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,31 @@ Benchmark suites for various components.
404404

405405
The Source Generator which generates types from Json Schema.
406406

407+
## V4.3.0 Updates
408+
409+
### Type Accessibility using the Source Generator
410+
411+
The source generator now respects the accessibility of the model type.
412+
413+
For example
414+
415+
```csharp
416+
[JsonSchemaTypeGenerator("../test.json#/$defs/FlimFlam")]
417+
internal readonly partial struct FlimFlam
418+
{
419+
}
420+
```
421+
422+
Any nested types will be generated with `public` accessibility.
423+
424+
Only `internal` and `public` are supported. The source generator will fail for an unsupported accessiblity declaration.
425+
426+
You can override the default accessibility for all generated types with a build property:
427+
428+
`<CorvusJsonSchemaDefaultAccessibility>Internal</CorvusJsonSchemaDefaultAccessibility>`
429+
430+
Note that you can still generate code that will not compile if you incorrectly mix-and-match `public` and `internal`. It is your responsibility to ensure that your types have compatible accessibility.
431+
407432
## V4.2.0 Updates
408433

409434
### Breaking change

Solutions/Corvus.Json.CodeGeneration.CSharp/Corvus.Json.CodeGeneration/CSharp/CSharpLanguageProvider.cs

+35-2
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,12 @@ public void SetNamesBeforeSubschema(TypeDeclaration typeDeclaration, string fall
245245
typeDeclaration.SetDotnetNamespace(ns);
246246
}
247247

248+
// Set the accessibility, if it has been explicitly overridden
249+
if (typeName.Accessibility is GeneratedTypeAccessibility accessibility)
250+
{
251+
typeDeclaration.SetDotnetAccessibility(accessibility);
252+
}
253+
248254
return;
249255
}
250256

@@ -539,7 +545,8 @@ private IEnumerable<INameHeuristic> GetOrderedNameAfterSubschemaHeuristics()
539545
/// <param name="reference">The reference to the schema.</param>
540546
/// <param name="dotnetTypeName">The .NET type name to use.</param>
541547
/// <param name="dotnetNamespace">The (optional) .NET namespace to use.</param>
542-
public readonly struct NamedType(JsonReference reference, string dotnetTypeName, string? dotnetNamespace = null)
548+
/// <param name="accessibility">The (optional) accessibility for the type.</param>
549+
public readonly struct NamedType(JsonReference reference, string dotnetTypeName, string? dotnetNamespace = null, GeneratedTypeAccessibility? accessibility = null)
543550
{
544551
/// <summary>
545552
/// Gets the reference to schema with an explicit name.
@@ -560,6 +567,25 @@ public readonly struct NamedType(JsonReference reference, string dotnetTypeName,
560567
/// as a child of its parent.
561568
/// </remarks>
562569
internal string? DotnetNamespace { get; } = dotnetNamespace;
570+
571+
/// <summary>
572+
/// Gets the (optional) accessibility to use for the type.
573+
/// </summary>
574+
/// <remarks>
575+
/// <para>
576+
/// Providing a value for this property will ensure that the type is generated
577+
/// with the given accessibility.
578+
/// </para>
579+
/// <para>
580+
/// Any types for which this is the parent will inherit the accessibility of this
581+
/// type. However, care must be taken not to reference/expose types which have
582+
/// a more restricted accessibility than the type itself.
583+
/// </para>
584+
/// <para>You should consider using <see cref="Options.DefaultAccessibility"/>
585+
/// to set the accessibility for all generated types rather overriding a specific type.
586+
/// </para>
587+
/// </remarks>
588+
internal GeneratedTypeAccessibility? Accessibility { get; } = accessibility;
563589
}
564590

565591
/// <summary>
@@ -595,6 +621,7 @@ public readonly struct Namespace(JsonReference baseUri, string dotnetNamespace)
595621
/// <param name="useImplicitOperatorString">If true, then the string conversion will be implicit.</param>
596622
/// <param name="lineEndSequence">The line-end sequence. Defaults to <c>\r\n</c>.</param>
597623
/// <param name="addExplicitUsings">If true, then the generated files will include using statements for the standard implicit usings. You should use this when your project does not use implicit usings.</param>
624+
/// <param name="defaultAccessibility">Defines the accessibility of the generated types. Defaults to <see cref="GeneratedTypeAccessibility.Public"/>.</param>
598625
public class Options(
599626
string defaultNamespace,
600627
NamedType[]? namedTypes = null,
@@ -606,7 +633,8 @@ public class Options(
606633
string fileExtension = ".cs",
607634
bool useImplicitOperatorString = false,
608635
string lineEndSequence = "\r\n",
609-
bool addExplicitUsings = false)
636+
bool addExplicitUsings = false,
637+
GeneratedTypeAccessibility defaultAccessibility = GeneratedTypeAccessibility.Public)
610638
{
611639
private readonly FrozenDictionary<string, NamedType> namedTypeMap = namedTypes?.ToFrozenDictionary(kvp => kvp.Reference, kvp => kvp) ?? FrozenDictionary<string, NamedType>.Empty;
612640
private readonly FrozenDictionary<string, string> namespaceMap = namespaces?.ToFrozenDictionary(kvp => kvp.BaseUri, kvp => kvp.DotnetNamespace) ?? FrozenDictionary<string, string>.Empty;
@@ -664,6 +692,11 @@ public class Options(
664692
/// </remarks>
665693
internal bool AddExplicitUsings { get; } = addExplicitUsings;
666694

695+
/// <summary>
696+
/// Gets the default accessibility of the generated types.
697+
/// </summary>
698+
internal GeneratedTypeAccessibility DefaultAccessibility { get; } = defaultAccessibility;
699+
667700
/// <summary>
668701
/// Gets the namespace for the base URI.
669702
/// </summary>

Solutions/Corvus.Json.CodeGeneration.CSharp/Corvus.Json.CodeGeneration/CSharp/CodeFileBuilders/ArrayPartial.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ public CodeGenerator EmitFile(CodeGenerator generator, TypeDeclaration typeDecla
5252
.BeginTypeDeclarationNesting(typeDeclaration)
5353
.AppendDocumentation(typeDeclaration)
5454
.AppendDotnet80OrGreaterCollectionBuilderAttribute(typeDeclaration)
55-
.BeginPublicReadonlyPartialStructDeclaration(
55+
.BeginReadonlyPartialStructDeclaration(
56+
typeDeclaration.DotnetAccessibility(),
5657
typeDeclaration.DotnetTypeName(),
5758
interfaces:
5859
[

Solutions/Corvus.Json.CodeGeneration.CSharp/Corvus.Json.CodeGeneration/CSharp/CodeFileBuilders/BooleanPartial.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ public CodeGenerator EmitFile(CodeGenerator generator, TypeDeclaration typeDecla
4949
.AppendLine()
5050
.BeginTypeDeclarationNesting(typeDeclaration)
5151
.AppendDocumentation(typeDeclaration)
52-
.BeginPublicReadonlyPartialStructDeclaration(
52+
.BeginReadonlyPartialStructDeclaration(
53+
typeDeclaration.DotnetAccessibility(),
5354
typeDeclaration.DotnetTypeName(),
5455
interfaces:
5556
[

Solutions/Corvus.Json.CodeGeneration.CSharp/Corvus.Json.CodeGeneration/CSharp/CodeFileBuilders/CorePartial.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ public CodeGenerator EmitFile(CodeGenerator generator, TypeDeclaration typeDecla
4747
.BeginTypeDeclarationNesting(typeDeclaration)
4848
.AppendDocumentation(typeDeclaration)
4949
.AppendJsonConverterAttribute(typeDeclaration)
50-
.BeginPublicReadonlyPartialStructDeclaration(
50+
.BeginReadonlyPartialStructDeclaration(
51+
typeDeclaration.DotnetAccessibility(),
5152
typeDeclaration.DotnetTypeName(),
5253
interfaces: [
5354
JsonAnyType(typeDeclaration)

Solutions/Corvus.Json.CodeGeneration.CSharp/Corvus.Json.CodeGeneration/CSharp/CodeFileBuilders/NumberPartial.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ public CodeGenerator EmitFile(CodeGenerator generator, TypeDeclaration typeDecla
5050
.AppendLine()
5151
.BeginTypeDeclarationNesting(typeDeclaration)
5252
.AppendDocumentation(typeDeclaration)
53-
.BeginPublicReadonlyPartialStructDeclaration(
53+
.BeginReadonlyPartialStructDeclaration(
54+
typeDeclaration.DotnetAccessibility(),
5455
typeDeclaration.DotnetTypeName(),
5556
interfaces:
5657
[

Solutions/Corvus.Json.CodeGeneration.CSharp/Corvus.Json.CodeGeneration/CSharp/CodeFileBuilders/ObjectPartial.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ public CodeGenerator EmitFile(CodeGenerator generator, TypeDeclaration typeDecla
5151
.AppendLine()
5252
.BeginTypeDeclarationNesting(typeDeclaration)
5353
.AppendDocumentation(typeDeclaration)
54-
.BeginPublicReadonlyPartialStructDeclaration(
54+
.BeginReadonlyPartialStructDeclaration(
55+
typeDeclaration.DotnetAccessibility(),
5556
typeDeclaration.DotnetTypeName(),
5657
interfaces:
5758
[

Solutions/Corvus.Json.CodeGeneration.CSharp/Corvus.Json.CodeGeneration/CSharp/CodeFileBuilders/StringPartial.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ public CodeGenerator EmitFile(CodeGenerator generator, TypeDeclaration typeDecla
5050
.AppendLine()
5151
.BeginTypeDeclarationNesting(typeDeclaration)
5252
.AppendDocumentation(typeDeclaration)
53-
.BeginPublicReadonlyPartialStructDeclaration(
53+
.BeginReadonlyPartialStructDeclaration(
54+
typeDeclaration.DotnetAccessibility(),
5455
typeDeclaration.DotnetTypeName(),
5556
interfaces:
5657
[

Solutions/Corvus.Json.CodeGeneration.CSharp/Corvus.Json.CodeGeneration/CSharp/CodeFileBuilders/ValidatePartial.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,14 @@ public CodeGenerator EmitFile(CodeGenerator generator, TypeDeclaration typeDecla
4242
new("global::System.Threading.Tasks", addExplicitUsings),
4343
"System.Runtime.CompilerServices",
4444
"System.Text.Json",
45-
RequiresRegularExressions(typeDeclaration) ? "System.Text.RegularExpressions" : ConditionalCodeSpecification.DoNotEmit,
45+
RequiresRegularExpressions(typeDeclaration) ? "System.Text.RegularExpressions" : ConditionalCodeSpecification.DoNotEmit,
4646
new("Corvus.Json", EmitIfNotCorvusJsonExtendedType(typeDeclaration)))
4747
.AppendLine()
4848
.BeginTypeDeclarationNesting(typeDeclaration)
4949
.AppendDocumentation(typeDeclaration)
50-
.BeginPublicReadonlyPartialStructDeclaration(typeDeclaration.DotnetTypeName());
50+
.BeginReadonlyPartialStructDeclaration(
51+
typeDeclaration.DotnetAccessibility(),
52+
typeDeclaration.DotnetTypeName());
5153

5254
// This is a core type
5355
if (typeDeclaration.IsCorvusJsonExtendedType())
@@ -85,7 +87,7 @@ static FrameworkType EmitIfNotCorvusJsonExtendedType(TypeDeclaration typeDeclara
8587
}
8688
}
8789

88-
private static bool RequiresRegularExressions(TypeDeclaration typeDeclaration)
90+
private static bool RequiresRegularExpressions(TypeDeclaration typeDeclaration)
8991
{
9092
return typeDeclaration.ValidationRegularExpressions() is IReadOnlyDictionary<IValidationRegexProviderKeyword, IReadOnlyList<string>> regexes && regexes.Count != 0;
9193
}

Solutions/Corvus.Json.CodeGeneration.CSharp/Corvus.Json.CodeGeneration/CSharp/CodeGeneratorExtensions.cs

+13-3
Original file line numberDiff line numberDiff line change
@@ -3695,7 +3695,8 @@ public static CodeGenerator BeginTypeDeclarationNesting(
36953695
generator
36963696
.AppendSeparatorLine()
36973697
.AppendDocumentation(parent)
3698-
.BeginPublicReadonlyPartialStructDeclaration(
3698+
.BeginReadonlyPartialStructDeclaration(
3699+
parent.DotnetAccessibility(),
36993700
parent.DotnetTypeName());
37003701
}
37013702

@@ -3990,11 +3991,13 @@ public static CodeGenerator GenericTypeOf(
39903991
/// Emits the start of a partial struct declaration.
39913992
/// </summary>
39923993
/// <param name="generator">The generator to which to append the beginning of the struct declaration.</param>
3994+
/// <param name="accessibility">The accessibility for the generated type.</param>
39933995
/// <param name="dotnetTypeName">The .NET type name for the partial struct.</param>
39943996
/// <param name="interfaces">Interfaces to implement.</param>
39953997
/// <returns>A reference to the generator having completed the operation.</returns>
3996-
public static CodeGenerator BeginPublicReadonlyPartialStructDeclaration(
3998+
public static CodeGenerator BeginReadonlyPartialStructDeclaration(
39973999
this CodeGenerator generator,
4000+
GeneratedTypeAccessibility accessibility,
39984001
string dotnetTypeName,
39994002
ConditionalCodeSpecification[]? interfaces = null)
40004003
{
@@ -4003,9 +4006,16 @@ public static CodeGenerator BeginPublicReadonlyPartialStructDeclaration(
40034006
return generator;
40044007
}
40054008

4009+
string accessibilityString = accessibility switch
4010+
{
4011+
GeneratedTypeAccessibility.Public => "public",
4012+
GeneratedTypeAccessibility.Internal => "internal",
4013+
_ => throw new ArgumentOutOfRangeException(nameof(accessibility)),
4014+
};
4015+
40064016
generator.ReserveNameIfNotReserved(dotnetTypeName);
40074017
generator
4008-
.AppendIndent("public readonly partial struct ")
4018+
.AppendIndent(accessibilityString, " readonly partial struct ")
40094019
.AppendLine(dotnetTypeName);
40104020

40114021
if (interfaces is ConditionalCodeSpecification[] conditionalSpecifications)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// <copyright file="GeneratedTypeAccessibility.cs" company="Endjin Limited">
2+
// Copyright (c) Endjin Limited. All rights reserved.
3+
// </copyright>
4+
5+
namespace Corvus.Json.CodeGeneration.CSharp;
6+
7+
/// <summary>
8+
/// Defines the accessibility of the generated types.
9+
/// </summary>
10+
public enum GeneratedTypeAccessibility
11+
{
12+
/// <summary>
13+
/// The generated types should be <see langword="public"/>.
14+
/// </summary>
15+
Public,
16+
17+
/// <summary>
18+
/// The generated types should be <see langword="internal"/>.
19+
/// </summary>
20+
Internal,
21+
}

Solutions/Corvus.Json.CodeGeneration.CSharp/Corvus.Json.CodeGeneration/CSharp/TypeDeclarationExtensions.cs

+46
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public static class TypeDeclarationExtensions
2424
private const string PreferredBinaryJsonNumberKindKey = "CSharp_LanguageProvider_PreferredBinaryJsonNumberKind";
2525
private const string UseImplicitOperatorStringKey = "CSharp_LanguageProvider_UseImplicitOperatorString";
2626
private const string AddExplicitUsingsKey = "CSharp_LanguageProvider_AddExplicitUsings";
27+
private const string DefaultAccessibilityKey = "CSharp_LanguageProvider_DefaultAccessibilityKey";
28+
private const string AccessibilityKey = "CSharp_LanguageProvider_AccessibilityKey";
2729

2830
/// <summary>
2931
/// Sets the relevant metadata from the <see cref="CSharpLanguageProvider.Options"/>.
@@ -36,6 +38,7 @@ public static void SetCSharpOptions(this TypeDeclaration typeDeclaration, CSharp
3638
typeDeclaration.SetMetadata(OptionalAsNullableKey, options.OptionalAsNullable);
3739
typeDeclaration.SetMetadata(UseImplicitOperatorStringKey, options.UseImplicitOperatorString);
3840
typeDeclaration.SetMetadata(AddExplicitUsingsKey, options.AddExplicitUsings);
41+
typeDeclaration.SetMetadata(DefaultAccessibilityKey, options.DefaultAccessibility);
3942
}
4043

4144
/// <summary>
@@ -602,6 +605,49 @@ public static TypeDeclaration SetDotnetNamespace(this TypeDeclaration typeDeclar
602605
return typeDeclaration;
603606
}
604607

608+
/// <summary>
609+
/// Sets the .NET accessibility.
610+
/// </summary>
611+
/// <param name="typeDeclaration">The type declaration.</param>
612+
/// <param name="accessibility">The <see cref="GeneratedTypeAccessibility"/>.</param>
613+
/// <returns>A reference to the type declaration after the operation has completed.</returns>
614+
public static TypeDeclaration SetDotnetAccessibility(this TypeDeclaration typeDeclaration, GeneratedTypeAccessibility accessibility)
615+
{
616+
typeDeclaration.SetMetadata(AccessibilityKey, accessibility);
617+
return typeDeclaration;
618+
}
619+
620+
/// <summary>
621+
/// Gets the .NET accessibility.
622+
/// </summary>
623+
/// <param name="typeDeclaration">The type declaration.</param>
624+
/// <returns>
625+
/// The <see cref="GeneratedTypeAccessibility"/> for the type. If this has been set explicitly, it will return the accessibility for this type.
626+
/// If it has a <c>Parent</c>, it will be <see cref="GeneratedTypeAccessibility.Public"/>. Otherwise, it will fall back to the default accessibility
627+
/// for the code generation context (which defaults to <see cref="GeneratedTypeAccessibility.Public"/>).
628+
/// </returns>
629+
public static GeneratedTypeAccessibility DotnetAccessibility(this TypeDeclaration typeDeclaration)
630+
{
631+
if (typeDeclaration.TryGetMetadata(AccessibilityKey, out GeneratedTypeAccessibility? typeAccessibility) &&
632+
typeAccessibility is GeneratedTypeAccessibility value)
633+
{
634+
return value;
635+
}
636+
637+
if (typeDeclaration.Parent() is not null)
638+
{
639+
return GeneratedTypeAccessibility.Public;
640+
}
641+
642+
if (typeDeclaration.TryGetMetadata(DefaultAccessibilityKey, out GeneratedTypeAccessibility? defaultTypeAccessibility) &&
643+
defaultTypeAccessibility is GeneratedTypeAccessibility defaultValue)
644+
{
645+
return defaultValue;
646+
}
647+
648+
return GeneratedTypeAccessibility.Public;
649+
}
650+
605651
/// <summary>
606652
/// Sets the .NET type name.
607653
/// </summary>

Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.props

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<CompilerVisibleProperty Include="CorvusJsonSchemaUseOptionalNameHeuristics" />
66
<CompilerVisibleProperty Include="CorvusJsonSchemaAlwaysAssertFormat" />
77
<CompilerVisibleProperty Include="CorvusJsonSchemaDisabledNamingHeuristics" />
8+
<CompilerVisibleProperty Include="CorvusJsonSchemaDefaultAccessibility" />
89
</ItemGroup>
910

1011
<PropertyGroup>

0 commit comments

Comments
 (0)