diff --git a/.agents/agents/sdk-consumer-setup.md b/.agents/agents/sdk-consumer-setup.md
new file mode 100644
index 0000000..b356a41
--- /dev/null
+++ b/.agents/agents/sdk-consumer-setup.md
@@ -0,0 +1,34 @@
+# sdk-consumer-setup (generic agent spec)
+
+## Goal
+
+Help a consuming repository adopt or troubleshoot `Purview.DotNetProjectSdk` correctly, without breaking existing build behaviour.
+
+## Workflow
+
+1. Confirm the SDK is imported in `Directory.Build.props`/`Directory.Build.targets` via
+ `` and the matching `Sdk.targets` import.
+2. Check pre-import bootstrap properties are set **before** the `Sdk.props` import when they must affect
+ evaluation: `NamespacePrefix`, `UsePackageJsonVersion`, `RootPackageJson`.
+3. If version resolution looks wrong, verify `package.json` discovery: explicit `RootPackageJson`, then CI
+ variables, `.git` root, or a nearby `package.json`. `UsePackageJsonVersion=Strict` fails fast instead of
+ silently skipping resolution.
+4. If the bundled `.agents/**` content isn't appearing in the repo root, check `EnableAgentFolderInPackage`
+ (default `true`) and `AgentPackDestinationFolder` (default `.agents`) — the copy runs before build via
+ `EnsureAgentFolderInPackageTarget`.
+5. For test-framework or project-shape questions, confirm the project follows repo naming and placement
+ conventions the SDK expects, rather than introducing bespoke structure.
+6. Re-run `dotnet build` (or the repo's canonical build command) after each configuration change to confirm
+ the fix.
+
+## Constraints
+
+- Prefer minimal, targeted property changes over broad `Directory.Build.props` rewrites.
+- Do not disable `PurviewAutoSdkPack` or `EnableAgentFolderInPackage` unless the consumer explicitly asks to
+ opt out.
+- Do not duplicate SDK-managed properties in individual project files unless the scenario is intentionally
+ project-specific.
+
+## Related skill
+
+See `../skills/sdk-configuration-reference/SKILL.md` for the full property reference.
diff --git a/.agents/agents/source-generator-framework-writer.agent.md b/.agents/agents/source-generator-framework-writer.agent.md
new file mode 100644
index 0000000..80fec8c
--- /dev/null
+++ b/.agents/agents/source-generator-framework-writer.agent.md
@@ -0,0 +1,55 @@
+---
+name: Source Generator Framework Writer
+description: "Specialist for Purview.SourceGeneratorFramework generation code using CodeWriter and XmlCodeWriter-style XML doc extensions; ideal for creating or refactoring generator emitters."
+tools:
+ [
+ "search/codebase",
+ "edit/editFiles",
+ "search",
+ "execute/getTerminalOutput",
+ "execute/runInTerminal",
+ "read/terminalLastCommand",
+ "read/terminalSelection",
+ "execute/createAndRunTask",
+ "execute/runTask",
+ "read/getTaskOutput",
+ "vscodeTasks/createAndRunTask",
+ "vscodeTasks/getTaskOutput",
+ "vscodeTasks/runTask",
+ ]
+---
+
+You are a specialist for `Purview.SourceGeneratorFramework` emitter authoring.
+
+## Primary objective
+
+Produce clear, deterministic, maintainable source-generator emission code using `CodeWriter` and XML extension helpers from `XmlCommentWriter`.
+
+## Must-follow rules
+
+1. Prefer structured declaration APIs over handwritten declaration strings.
+2. Prefer XML helper extensions (`XmlSummary`, `XmlParam`, etc.) over raw `///` output.
+3. Keep `CodeWriter` instances output-scoped; never cache in incremental provider state.
+4. Preserve semantic behavior while modernizing implementation style.
+5. Keep edits minimal and localized to emitter concerns.
+
+## Refactoring posture
+
+When modernizing legacy code:
+
+- Replace manual indentation/braces with scope APIs.
+- Replace signature text with declaration option records.
+- Replace ad-hoc XML tags with helper APIs.
+- Preserve diagnostics and emitted symbol names.
+
+## Quality gates
+
+- Build/tests pass for impacted projects.
+- No scope leaks when materializing generated source.
+- Generated artifacts remain deterministic and reviewable.
+
+## Skill routing
+
+When relevant, first load and apply:
+
+- `source-generator-codewriter-modernization`
diff --git a/.agents/prompts/refactor-source-generator-to-codewriter.prompt.md b/.agents/prompts/refactor-source-generator-to-codewriter.prompt.md
new file mode 100644
index 0000000..9cf531b
--- /dev/null
+++ b/.agents/prompts/refactor-source-generator-to-codewriter.prompt.md
@@ -0,0 +1,52 @@
+---
+agent: ask
+description: "Refactor a legacy source generator emitter from string/StringBuilder to CodeWriter + XmlCodeWriter-style XML extensions with behavior parity."
+---
+
+You are modernizing a source generator implementation in this repository.
+
+## Inputs
+
+- Target file(s): `${input:targetFiles:Path(s) to emitter file(s)}`
+- Generator type name: `${input:generatorName:Generator class name}`
+- Generator version: `${input:generatorVersion:Version string (for generated attributes/header)}`
+- Keep output byte-identical where possible: `${input:preserveFormatting:true|false}`
+
+## Task
+
+Refactor the selected legacy emitter implementation from manual `string` / `StringBuilder` output construction to `CodeWriter` and XML documentation extension helpers from `XmlCommentWriter` (XmlCodeWriter-style API usage).
+
+### Requirements
+
+1. Use structured declaration APIs where applicable:
+ - `WriteClass/WriteStruct/WriteRecordClass/WriteInterface/WriteEnum`
+ - `WriteMethod`, `WriteProperty`, `WriteField`, `WriteConstructor`
+2. Use XML helper extensions instead of raw `///` composition:
+ - `XmlSummary`, `XmlParam`, `XmlReturn`, `XmlRemarks`, `XmlCode` or `XmlCodeBlock`
+3. Use `TypeReferenceOptions` when type text becomes complex (nullability, generics, arrays).
+4. Ensure writer lifetime is output-scoped (`generationContext.CreateCodeWriter()` inside callback).
+5. Preserve behavior, diagnostics, and generated names.
+6. Keep changes minimal and focused; do not reformat unrelated logic.
+
+### Migration strategy
+
+- Identify emitter phases: header, namespace, type declarations, member declarations.
+- Replace indentation/braces with scoped APIs.
+- Replace signature strings with declaration options.
+- Replace XML comments with XmlCommentWriter extension methods.
+- Keep semantic equivalence; call out any intentional deltas.
+
+### Verification
+
+- Run relevant tests.
+- Confirm generated files still compile.
+- Confirm no `CodeWriter` scope leaks (`OpenScopeCount == 0` when materialized).
+
+### Output format
+
+Return:
+
+1. Files changed
+2. Why each change was necessary
+3. Risks/behavior differences (if any)
+4. Verification performed
diff --git a/.agents/prompts/sdk-diagnose-agent-folder-copy.md b/.agents/prompts/sdk-diagnose-agent-folder-copy.md
new file mode 100644
index 0000000..49ad1c2
--- /dev/null
+++ b/.agents/prompts/sdk-diagnose-agent-folder-copy.md
@@ -0,0 +1,26 @@
+# sdk-diagnose-agent-folder-copy (generic prompt spec)
+
+Diagnose why the bundled `.agents/**` folder from `Purview.DotNetProjectSdk` did not appear at the expected
+destination in a consuming repository.
+
+## Required behaviour
+
+1. Confirm the NuGet package actually contains `.agents/**` content (inspect the `.nupkg` if available).
+2. Confirm the consuming project is packable/buildable and imports the SDK via
+ `Sdk.props`/`Sdk.targets`, since the copy runs in `EnsureAgentFolderInPackageTarget` before build.
+3. Check `EnableAgentFolderInPackage` is not set to `false` anywhere in the build (project file,
+ `Directory.Build.props`, or command-line `-p:` overrides).
+4. Confirm the destination folder: default is `.agents` at the repo root, overridable per-build with
+ `-p:AgentPackDestinationFolder=`.
+5. Verify repo-root discovery succeeded: explicit `RepoRoot`, then a nearby `AGENTS.md`, then source-control
+ root metadata.
+6. Re-run the build and confirm the destination folder now contains the copied files (including the
+ generated `.gitignore` for skill/prompt/agent subfolders).
+
+## Suggested output
+
+- A short root-cause explanation (missing import, disabled flag, wrong destination override, or repo-root
+ discovery miss).
+- The exact command used to reproduce/verify the fix (for example
+ `dotnet build -p:AgentPackDestinationFolder=`).
+- Confirmation that the expected files exist at the resolved destination path.
diff --git a/.agents/skills/source-generator-codewriter-modernization/.gitignore b/.agents/skills/source-generator-codewriter-modernization/.gitignore
new file mode 100644
index 0000000..2799754
--- /dev/null
+++ b/.agents/skills/source-generator-codewriter-modernization/.gitignore
@@ -0,0 +1,8 @@
+# Ignore all files
+*
+
+# Don't ignore directories, so Git can traverse them
+!*/
+
+# Keep this file
+!.gitignore
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 3700f14..22cf3a2 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -8,8 +8,8 @@
-->
4.13.0
[5.6.0,)
- [1.65.0,)
- [1.0.0-prerelease.23,)
+ [1.65.68,)
+ [1.0.0-prerelease.25,)
diff --git a/global.json b/global.json
index f6570c9..7c80d95 100644
--- a/global.json
+++ b/global.json
@@ -5,7 +5,7 @@
"allowPrerelease": false
},
"msbuild-sdks": {
- "Purview.DotNetProjectSdk": "1.0.0-prerelease.39"
+ "Purview.DotNetProjectSdk": "1.0.0-prerelease.40"
},
"test": {
"runner": "Microsoft.Testing.Platform"
diff --git a/src/src/SourceGenerators/Extensions/Purview/SourceGeneratorFramework/Helpers/TypeHelpersExtensions.cs b/src/src/SourceGenerators/Extensions/Purview/SourceGeneratorFramework/Helpers/TypeHelpersExtensions.cs
index e6a7283..8767969 100644
--- a/src/src/SourceGenerators/Extensions/Purview/SourceGeneratorFramework/Helpers/TypeHelpersExtensions.cs
+++ b/src/src/SourceGenerators/Extensions/Purview/SourceGeneratorFramework/Helpers/TypeHelpersExtensions.cs
@@ -129,10 +129,10 @@ public static bool IsNamedType(ITypeSymbol type, string fullyQualifiedMetadataNa
&& string.Equals(namedType.ToDisplayString(), fullyQualifiedMetadataName, StringComparison.Ordinal);
}
- public static bool HasAttribute(IEnumerable attributes, TypeValueObject attribute) =>
+ public static bool HasAttribute(IEnumerable attributes, TypeIdentity attribute) =>
attributes.Any(attribute.Equals);
- public static bool IsOrImplements(ITypeSymbol type, TypeValueObject interfaceType)
+ public static bool IsOrImplements(ITypeSymbol type, TypeIdentity interfaceType)
{
var unwrapped = StripNullableAnnotations(type);
return TypeHelpers.IsNamedType(unwrapped, interfaceType.MetadataFullName)
diff --git a/src/src/SourceGenerators/Helpers/AttributeGenHelper.cs b/src/src/SourceGenerators/Helpers/AttributeGenHelper.cs
index d73f939..10348c4 100644
--- a/src/src/SourceGenerators/Helpers/AttributeGenHelper.cs
+++ b/src/src/SourceGenerators/Helpers/AttributeGenHelper.cs
@@ -1,5 +1,4 @@
using Microsoft.CodeAnalysis.Text;
-
using ZodSharp.SourceGenerators.Models;
namespace ZodSharp.SourceGenerators.Helpers;
@@ -13,7 +12,7 @@ static class AttributeGenHelper
static SourceText ZodSchemaAttribute()
{
- CodeWriter writer = new(typeof(ZodSchemaGenerator).FullName, AssemblyInfo.Version);
+ CodeWriter writer = new(GenerationSettings.Create());
writer.WriteAutoGeneratedHeader().WriteFileScopedNamespace(TypeLibrary.ZodSchemaAttribute);
@@ -32,7 +31,11 @@ static SourceText ZodSchemaAttribute()
"If not specified, uses \"{ClassName}Schema\"."
)
.WriteProperty(
- new(nameof(ZodSchemaAttributeData.SchemaName), PurviewTypeLibrary.System.String.AsTypeReference().Nullable())
+ new(
+ nameof(ZodSchemaAttributeData.SchemaName),
+ PurviewTypeLibrary.System.String.AsTypeReference().Nullable(),
+ TypeDeclarationAccessibility.Public
+ )
{
IsInitOnly = true,
}
@@ -40,9 +43,12 @@ static SourceText ZodSchemaAttribute()
body.XmlSummary("Whether to generate a static Validate method.", "Default is true.")
.WriteProperty(
- new(nameof(ZodSchemaAttributeData.GenerateValidateMethod), PurviewTypeLibrary.System.Boolean)
+ new(
+ nameof(ZodSchemaAttributeData.GenerateValidateMethod),
+ PurviewTypeLibrary.System.Boolean,
+ TypeDeclarationAccessibility.Public
+ )
{
- Accessibility = TypeDeclarationAccessibility.Public,
IsInitOnly = true,
Initializer = "true",
}
@@ -53,9 +59,12 @@ static SourceText ZodSchemaAttribute()
$"Default is true."
)
.WriteProperty(
- new(nameof(ZodSchemaAttributeData.GenerateParseMethod), PurviewTypeLibrary.System.Boolean)
+ new(
+ nameof(ZodSchemaAttributeData.GenerateParseMethod),
+ PurviewTypeLibrary.System.Boolean,
+ TypeDeclarationAccessibility.Public
+ )
{
- Accessibility = TypeDeclarationAccessibility.Public,
IsInitOnly = true,
Initializer = "true",
}
@@ -66,9 +75,12 @@ static SourceText ZodSchemaAttribute()
$"Default is true."
)
.WriteProperty(
- new(nameof(ZodSchemaAttributeData.EnableComposition), PurviewTypeLibrary.System.Boolean)
+ new(
+ nameof(ZodSchemaAttributeData.EnableComposition),
+ PurviewTypeLibrary.System.Boolean,
+ TypeDeclarationAccessibility.Public
+ )
{
- Accessibility = TypeDeclarationAccessibility.Public,
IsInitOnly = true,
Initializer = "true",
}
@@ -83,9 +95,12 @@ static SourceText ZodSchemaAttribute()
"No diagnostic is reported when the default name has no matching method."
)
.WriteProperty(
- new(nameof(ZodSchemaAttributeData.CustomValidationMethodName), PurviewTypeLibrary.System.String.AsTypeReference().Nullable())
+ new(
+ nameof(ZodSchemaAttributeData.CustomValidationMethodName),
+ PurviewTypeLibrary.System.String.AsTypeReference().Nullable(),
+ TypeDeclarationAccessibility.Public
+ )
{
- Accessibility = TypeDeclarationAccessibility.Public,
IsInitOnly = true,
}
);
diff --git a/src/src/SourceGenerators/Helpers/CodeGenHelpers.cs b/src/src/SourceGenerators/Helpers/CodeGenHelpers.cs
index ef11cdd..04a40b7 100644
--- a/src/src/SourceGenerators/Helpers/CodeGenHelpers.cs
+++ b/src/src/SourceGenerators/Helpers/CodeGenHelpers.cs
@@ -21,8 +21,8 @@ string errorMessage
"));",
bodyWriter =>
{
- bodyWriter.Write(errorCode.Surround()).WriteLine(",");
- bodyWriter.Write(errorMessage.Surround()).WriteLine(",");
+ bodyWriter.Write(Quote(errorCode)).WriteLine(",");
+ bodyWriter.Write(Quote(errorMessage)).WriteLine(",");
bodyWriter.WriteLine($"new[] {{ \"{propertyName}\" }}");
}
);
diff --git a/src/src/SourceGenerators/Helpers/SourceGenLibrary.cs b/src/src/SourceGenerators/Helpers/SourceGenLibrary.cs
index 3f26c7c..6a274b8 100644
--- a/src/src/SourceGenerators/Helpers/SourceGenLibrary.cs
+++ b/src/src/SourceGenerators/Helpers/SourceGenLibrary.cs
@@ -1,6 +1,5 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using ZodSharp.SourceGenerators.Models;
@@ -12,50 +11,121 @@ public static IncrementalValueProvider GetGeneratorValueP
IncrementalGeneratorInitializationContext context
)
{
- var outputContext = IncrementalPipeline.GenerationContextValueProvider(
+ var generationContext = IncrementalPipeline.GenerationContextValueProvider<
+ SchemaGenerationCapabilities,
+ ZodSchemaGenerator
+ >(
context,
- typeof(ZodSchemaGenerator).FullName,
- AssemblyInfo.Version,
- (compilation, generatorSettings, logger, _) => new(compilation, generatorSettings, logger),
+ static (compilation, _, _, _) =>
+ new(compilation)
+ {
+ HasRequiredAttribute = TypeHelpers.HasType(
+ compilation,
+ TypeLibrary.DataAnnotations.RequiredAttribute
+ ),
+ },
PropertyLibrary.DisableZodSharpSourceGeneratorProperty
);
- var zodSchemas = IncrementalPipeline.ForAttributeWithMetadataName(
+ var schemaSets = IncrementalPipeline.ForAttributeWithMetadataName(
context,
TypeLibrary.ZodSchemaAttribute,
- predicate: static (s, _) =>
- s is ClassDeclarationSyntax or StructDeclarationSyntax or RecordDeclarationSyntax,
- transform: static (ctx, ct) => GetZodSchemaTargetForGeneration(ctx, ct)
+ predicate: static (node, _) => node is TypeDeclarationSyntax,
+ transform: static (attributeContext, cancellationToken) =>
+ GetSchemasForGeneration(attributeContext, cancellationToken)
);
- return outputContext.CollectWith(
- zodSchemas,
- static (generationContext, zodSchemas, _) => new SchemaGenerationModel(generationContext, zodSchemas),
+ return generationContext.CollectWith(
+ schemaSets,
+ static (outputContext, sets, _) =>
+ new SchemaGenerationModel(outputContext) { ZodSchemas = Deduplicate(sets) },
"CollectZodSchemas"
);
}
- static GeneratorResult GetZodSchemaTargetForGeneration(
+ static GeneratorResult GetSchemasForGeneration(
GeneratorAttributeSyntaxContext context,
CancellationToken cancellationToken
)
{
- var declaration = (TypeDeclarationSyntax)context.TargetNode;
- if (context.SemanticModel.GetDeclaredSymbol(declaration, cancellationToken) is not INamedTypeSymbol symbol)
- return GeneratorResult.Empty;
+ if (context.SemanticModel.GetDeclaredSymbol(context.TargetNode, cancellationToken) is not INamedTypeSymbol root)
+ return GeneratorResult.Empty;
- ZodSchemaDescriptor result = new(symbol, new(symbol), GetZodProperties(symbol));
+ var schemas = ImmutableArray.CreateBuilder();
+ var seen = new HashSet();
+ var queue = new Queue<(INamedTypeSymbol Symbol, bool IsPrimary)>();
+ queue.Enqueue((root, true));
- return GeneratorResult.Ok(result);
+ while (queue.Count > 0)
+ {
+ var (symbol, isPrimary) = queue.Dequeue();
+ var identity = new TypeIdentity(symbol);
+ if (!seen.Add(identity))
+ continue;
+
+ schemas.Add(new(identity, GetDeclarationFingerprint(symbol, cancellationToken), isPrimary));
+ foreach (var property in GetZodProperties(symbol))
+ {
+ if (TryGetNestedSchemaType(property, out var nested))
+ queue.Enqueue((nested, false));
+ }
+ }
+
+ return GeneratorResult.Ok(new(schemas.ToImmutable()));
}
- public static ImmutableArray GetZodProperties(INamedTypeSymbol symbol)
+ static EquatableArray> Deduplicate(
+ ImmutableArray> sets
+ )
+ {
+ var results = ImmutableArray.CreateBuilder>();
+ var seen = new HashSet();
+ foreach (var set in sets)
+ {
+ if (!set.ShouldProcess)
+ continue;
+
+ foreach (var schema in set.Value.Schemas)
+ {
+ if (seen.Add(schema.SchemaType))
+ results.Add(GeneratorResult.Ok(schema));
+ }
+ }
+
+ return new(results.ToImmutable());
+ }
+
+ static string GetDeclarationFingerprint(INamedTypeSymbol symbol, CancellationToken cancellationToken) =>
+ string.Join(
+ "\n",
+ symbol
+ .DeclaringSyntaxReferences.Select(reference => reference.GetSyntax(cancellationToken).ToFullString())
+ .OrderBy(static text => text, StringComparer.Ordinal)
+ );
+
+ static ImmutableArray GetZodProperties(INamedTypeSymbol symbol) =>
+ [
+ .. symbol
+ .GetMembers()
+ .OfType()
+ .Where(static property => property.DeclaredAccessibility == Accessibility.Public && !property.IsStatic),
+ ];
+
+ static bool TryGetNestedSchemaType(IPropertySymbol property, out INamedTypeSymbol nested)
{
- var properties = symbol
- .GetMembers()
- .OfType()
- .Where(p => p.DeclaredAccessibility == Accessibility.Public)
- .Select(p => new ZodPropertyDescriptor(p));
+ var propertyType = TypeHelpers.UnwrapNullableType(property.Type);
+ if (propertyType is IArrayTypeSymbol array)
+ propertyType = array.ElementType;
+ else if (propertyType is INamedTypeSymbol named)
+ {
+ var enumerable = named.AllInterfaces.FirstOrDefault(TypeLibrary.Collections.IEnumerableT.Equals);
+ if (enumerable is not null)
+ propertyType = enumerable.TypeArguments[0];
+ }
- return [.. properties];
+ propertyType = TypeHelpers.UnwrapNullableType(propertyType);
+ nested = propertyType as INamedTypeSymbol ?? null!;
+ return nested is not null
+ && nested.Locations.Any(static location => location.IsInSource)
+ && !ZodSchemaGenerator.IsScalarType(nested);
}
}
diff --git a/src/src/SourceGenerators/Helpers/TypeLibrary.DataAnnotations.cs b/src/src/SourceGenerators/Helpers/TypeLibrary.DataAnnotations.cs
index 6b57617..38f9dac 100644
--- a/src/src/SourceGenerators/Helpers/TypeLibrary.DataAnnotations.cs
+++ b/src/src/SourceGenerators/Helpers/TypeLibrary.DataAnnotations.cs
@@ -6,29 +6,29 @@ public static class DataAnnotations
{
public const string Namespace = "System.ComponentModel.DataAnnotations";
- public static readonly TypeValueObject DisplayAttribute = new(nameof(DisplayAttribute), Namespace);
- public static readonly TypeValueObject RequiredAttribute = new(nameof(RequiredAttribute), Namespace);
+ public static readonly TypeIdentity DisplayAttribute = new(nameof(DisplayAttribute), Namespace);
+ public static readonly TypeIdentity RequiredAttribute = new(nameof(RequiredAttribute), Namespace);
- public static readonly TypeValueObject EmailAddressAttribute = new(nameof(EmailAddressAttribute), Namespace);
- public static readonly TypeValueObject StringLengthAttribute = new(nameof(StringLengthAttribute), Namespace);
- public static readonly TypeValueObject MinLengthAttribute = new(nameof(MinLengthAttribute), Namespace);
- public static readonly TypeValueObject MaxLengthAttribute = new(nameof(MaxLengthAttribute), Namespace);
- public static readonly TypeValueObject RangeAttribute = new(nameof(RangeAttribute), Namespace);
- public static readonly TypeValueObject LengthAttribute = new(nameof(LengthAttribute), Namespace);
- public static readonly TypeValueObject RegularExpressionAttribute = new(
+ public static readonly TypeIdentity EmailAddressAttribute = new(nameof(EmailAddressAttribute), Namespace);
+ public static readonly TypeIdentity StringLengthAttribute = new(nameof(StringLengthAttribute), Namespace);
+ public static readonly TypeIdentity MinLengthAttribute = new(nameof(MinLengthAttribute), Namespace);
+ public static readonly TypeIdentity MaxLengthAttribute = new(nameof(MaxLengthAttribute), Namespace);
+ public static readonly TypeIdentity RangeAttribute = new(nameof(RangeAttribute), Namespace);
+ public static readonly TypeIdentity LengthAttribute = new(nameof(LengthAttribute), Namespace);
+ public static readonly TypeIdentity RegularExpressionAttribute = new(
nameof(RegularExpressionAttribute),
Namespace
);
- public static readonly TypeValueObject AllowedValuesAttribute = new(nameof(AllowedValuesAttribute), Namespace);
- public static readonly TypeValueObject DeniedValuesAttribute = new(nameof(DeniedValuesAttribute), Namespace);
- public static readonly TypeValueObject UrlAttribute = new(nameof(UrlAttribute), Namespace);
- public static readonly TypeValueObject PhoneAttribute = new(nameof(PhoneAttribute), Namespace);
- public static readonly TypeValueObject CreditCardAttribute = new(nameof(CreditCardAttribute), Namespace);
- public static readonly TypeValueObject CompareAttribute = new(nameof(CompareAttribute), Namespace);
- public static readonly TypeValueObject Base64StringAttribute = new(nameof(Base64StringAttribute), Namespace);
+ public static readonly TypeIdentity AllowedValuesAttribute = new(nameof(AllowedValuesAttribute), Namespace);
+ public static readonly TypeIdentity DeniedValuesAttribute = new(nameof(DeniedValuesAttribute), Namespace);
+ public static readonly TypeIdentity UrlAttribute = new(nameof(UrlAttribute), Namespace);
+ public static readonly TypeIdentity PhoneAttribute = new(nameof(PhoneAttribute), Namespace);
+ public static readonly TypeIdentity CreditCardAttribute = new(nameof(CreditCardAttribute), Namespace);
+ public static readonly TypeIdentity CompareAttribute = new(nameof(CompareAttribute), Namespace);
+ public static readonly TypeIdentity Base64StringAttribute = new(nameof(Base64StringAttribute), Namespace);
// This is abstract and the base class to all the other validation attributes,
// so we can use it to get the base properties
- public static readonly TypeValueObject ValidationAttribute = new(nameof(ValidationAttribute), Namespace);
+ public static readonly TypeIdentity ValidationAttribute = new(nameof(ValidationAttribute), Namespace);
}
}
diff --git a/src/src/SourceGenerators/Helpers/TypeLibrary.Others.cs b/src/src/SourceGenerators/Helpers/TypeLibrary.Others.cs
index d249f3c..d4bf0e9 100644
--- a/src/src/SourceGenerators/Helpers/TypeLibrary.Others.cs
+++ b/src/src/SourceGenerators/Helpers/TypeLibrary.Others.cs
@@ -5,24 +5,24 @@ namespace ZodSharp.SourceGenerators.Helpers;
partial class TypeLibrary
{
- public static readonly TypeValueObject CancellationToken = new(typeof(CancellationToken));
+ public static readonly TypeIdentity CancellationToken = new(typeof(CancellationToken));
- public static readonly TypeValueObject ValueTask = new(typeof(ValueTask));
+ public static readonly TypeIdentity ValueTask = new(typeof(ValueTask));
public static class Collections
{
- public static readonly TypeValueObject ImmutableArray = new(typeof(ImmutableArray));
+ public static readonly TypeIdentity ImmutableArray = new(typeof(ImmutableArray));
- public static readonly TypeValueObject List = new(typeof(List<>));
+ public static readonly TypeIdentity List = new(typeof(List<>));
- public static readonly TypeValueObject ICollection = new(typeof(ICollection));
+ public static readonly TypeIdentity ICollection = new(typeof(ICollection));
- public static readonly TypeValueObject IEnumerable = new(typeof(IEnumerable));
+ public static readonly TypeIdentity IEnumerable = new(typeof(IEnumerable));
- public static readonly TypeValueObject ICollectionT = new(typeof(ICollection<>));
+ public static readonly TypeIdentity ICollectionT = new(typeof(ICollection<>));
- public static readonly TypeValueObject IEnumerableT = new(typeof(IEnumerable<>));
+ public static readonly TypeIdentity IEnumerableT = new(typeof(IEnumerable<>));
- public static readonly TypeValueObject IReadOnlyCollectionT = new(typeof(IReadOnlyCollection<>));
+ public static readonly TypeIdentity IReadOnlyCollectionT = new(typeof(IReadOnlyCollection<>));
}
}
diff --git a/src/src/SourceGenerators/Helpers/TypeLibrary.cs b/src/src/SourceGenerators/Helpers/TypeLibrary.cs
index 359d735..605ef70 100644
--- a/src/src/SourceGenerators/Helpers/TypeLibrary.cs
+++ b/src/src/SourceGenerators/Helpers/TypeLibrary.cs
@@ -10,20 +10,20 @@ static partial class TypeLibrary
public const string DefaultCustomValidationMethodName = "CustomValidationAsync";
// This matches the name of the class, just so we can use the `nameof` for later...
- public static readonly TypeValueObject ZodSchemaAttribute = new(nameof(ZodSchemaAttribute), ZodSharpNamespace);
+ public static readonly TypeIdentity ZodSchemaAttribute = new(nameof(ZodSchemaAttribute), ZodSharpNamespace);
- public static readonly TypeValueObject ZodSchemaGeneratedAttribute = new(
+ public static readonly TypeIdentity ZodSchemaGeneratedAttribute = new(
nameof(ZodSchemaGeneratedAttribute),
ZodSharpCoreNamespace
);
// Other ZodSharp types...
- public static readonly TypeValueObject ValidationResult = new(nameof(ValidationResult), ZodSharpCoreNamespace);
+ public static readonly TypeIdentity ValidationResult = new(nameof(ValidationResult), ZodSharpCoreNamespace);
- public static readonly TypeValueObject ValidationResultMetadataName = new(
+ public static readonly TypeIdentity ValidationResultMetadataName = new(
nameof(ValidationResultMetadataName),
ZodSharpCoreNamespace
);
- public static readonly TypeValueObject ValidationError = new(nameof(ValidationError), ZodSharpCoreNamespace);
+ public static readonly TypeIdentity ValidationError = new(nameof(ValidationError), ZodSharpCoreNamespace);
}
diff --git a/src/src/SourceGenerators/Models/DataAttributes/RangeAttributeData.cs b/src/src/SourceGenerators/Models/DataAttributes/RangeAttributeData.cs
index 66d514e..5a778df 100644
--- a/src/src/SourceGenerators/Models/DataAttributes/RangeAttributeData.cs
+++ b/src/src/SourceGenerators/Models/DataAttributes/RangeAttributeData.cs
@@ -1,6 +1,5 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
-using Purview.SourceGeneratorFramework.Extensions;
using ZodSharp.SourceGenerators.Helpers;
namespace ZodSharp.SourceGenerators.Models.DataAttributes;
diff --git a/src/src/SourceGenerators/Models/SchemaGenerationModel.cs b/src/src/SourceGenerators/Models/SchemaGenerationModel.cs
index 23a5457..7d12eef 100644
--- a/src/src/SourceGenerators/Models/SchemaGenerationModel.cs
+++ b/src/src/SourceGenerators/Models/SchemaGenerationModel.cs
@@ -1,43 +1,31 @@
-using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
-using ZodSharp.SourceGenerators.Helpers;
namespace ZodSharp.SourceGenerators.Models;
-sealed record SchemaGenerationModel(SchemaGenerationContext GenerationContext, ImmutableArray> ZodSchemas)
+sealed record SchemaGenerationModel(GenerationContext Context)
{
- public ImmutableArray Diagnostics { get; set; } = [];
+ public EquatableArray> ZodSchemas { get; init; } = [];
+
+ public EquatableArray Diagnostics { get; init; } = [];
}
-sealed class SchemaGenerationContext : GenerationContext
+sealed record SchemaGenerationCapabilities(Compilation Compilation) : IGenerationCapabilities
{
- public SchemaGenerationContext(Compilation compilation, GenerationSettings settings, ISourceGenLogger? logger)
- : base(compilation, settings, logger)
- {
- RequiredAttribute = GetTypeByMetadataName(TypeLibrary.DataAnnotations.RequiredAttribute);
- }
-
- public INamedTypeSymbol? RequiredAttribute { get; }
+ public bool HasRequiredAttribute { get; init; }
}
// This is recreated outside of the pipeline to avoid the state
// of the CodeWriter being shared across multiple source outputs.
-sealed class SchemaGenerationOutputContext(SchemaGenerationContext generationContext) : ISourceGenLogger
+sealed record SchemaGenerationOutputContext(GenerationContext Context) : ISourceGenLogger
{
- public SchemaGenerationContext Generation { get; } = generationContext;
-
- public CodeWriter Writer { get; private set; } = generationContext.CreateCodeWriter();
+ public CodeWriter Writer { get; private set; } = Context.CreateCodeWriter();
- public CodeWriter CreateCodeWriter() => Writer = Generation.CreateCodeWriter();
+ public CodeWriter CreateCodeWriter() => Writer = Context.CreateCodeWriter();
public void Log(SourceGenLogLevel level, int indentation, string message, params object[] args) =>
- Generation.Log(level, indentation, message, args);
+ Context.Log(level, indentation, message, args);
}
-readonly record struct ZodSchemaDescriptor(
- INamedTypeSymbol Symbol,
- TypeValueObject SchemaType,
- ImmutableArray Properties
-);
+readonly record struct ZodSchemaDescriptor(TypeIdentity SchemaType, string DeclarationFingerprint, bool IsPrimary);
-readonly record struct ZodPropertyDescriptor(IPropertySymbol Symbol);
+readonly record struct SchemaSet(EquatableArray Schemas);
diff --git a/src/src/SourceGenerators/ZodSchemaGenerator.BuildSchema.cs b/src/src/SourceGenerators/ZodSchemaGenerator.BuildSchema.cs
index 26a6d7a..d1ad503 100644
--- a/src/src/SourceGenerators/ZodSchemaGenerator.BuildSchema.cs
+++ b/src/src/SourceGenerators/ZodSchemaGenerator.BuildSchema.cs
@@ -12,22 +12,23 @@ namespace ZodSharp.SourceGenerators;
partial class ZodSchemaGenerator
{
static void BuildSchema(
- ZodSchemaDescriptor schemaDescriptor,
+ INamedTypeSymbol schemaSymbol,
+ TypeIdentity schemaType,
SchemaGenerationOutputContext outputContext,
SourceProductionContext context,
bool isPrimary
)
{
- outputContext.Info($"Building schema for {schemaDescriptor.Symbol.Name}");
+ outputContext.Info($"Building schema for {schemaSymbol.Name}");
try
{
List diagnostics = [];
- var source = GenerateSchemaClass(schemaDescriptor.Symbol, outputContext, diagnostics, isPrimary);
+ var source = GenerateSchemaClass(schemaSymbol, outputContext, diagnostics, isPrimary);
if (diagnostics.Count > 0)
ReportDiagnostics(context, diagnostics, outputContext);
- var fileName = $"{schemaDescriptor.Symbol.Name}Schema.g.cs";
+ var fileName = $"{schemaSymbol.Name}Schema.g.cs";
context.AddSource(fileName, SourceText.From(source, Encoding.UTF8));
// Emit module-level registration attribute for DI discovery.
@@ -35,21 +36,21 @@ bool isPrimary
// Skip registration for nested types because private nested types are inaccessible at
// assembly scope and public/internal nested types are best discovered through their
// containing type.
- if (isPrimary && schemaDescriptor.Symbol.ContainingType is null)
+ if (isPrimary && schemaSymbol.ContainingType is null)
{
// Create a new writer for the assembly-level attribute to avoid polluting the schema class file.
- var writer = outputContext.Generation.CreateCodeWriter();
+ var writer = outputContext.Context.CreateCodeWriter();
writer.WriteAutoGeneratedHeader();
writer
.Write("[assembly: global::")
.Write(TypeLibrary.ZodSchemaGeneratedAttribute.MetadataFullName)
.Write("(typeof(")
- .Write(schemaDescriptor.SchemaType)
+ .Write(schemaType)
.WriteLine("))]");
context.AddSource(
- $"{schemaDescriptor.Symbol.Name}SchemaRegistration.g.cs",
+ $"{schemaSymbol.Name}SchemaRegistration.g.cs",
SourceText.From(writer.ToString(), Encoding.UTF8)
);
}
@@ -65,8 +66,8 @@ bool isPrimary
// Report diagnostic if generation fails
var diagnostic = DiagnosticInfo.Create(
DiagnosticLibrary.UnhandledException,
- schemaDescriptor.Symbol.Locations.FirstOrDefault(),
- schemaDescriptor.Symbol.Name,
+ schemaSymbol,
+ schemaSymbol.Name,
ex.Message
);
@@ -74,81 +75,7 @@ bool isPrimary
}
}
- static ImmutableArray<(ZodSchemaDescriptor Descriptor, bool IsPrimary)> DiscoverAllSchemas(
- ImmutableArray> primarySchemas
- )
- {
- var builder = ImmutableArray.CreateBuilder<(ZodSchemaDescriptor Descriptor, bool IsPrimary)>();
-
- builder.AddRange(primarySchemas.Select(p => (p.Value, true)));
- var seen = new HashSet(
- primarySchemas.Select(p => p.Value.Symbol),
- SymbolEqualityComparer.Default
- );
- Queue queue = new(primarySchemas.Select(p => p.Value.Symbol));
-
- while (queue.Count > 0)
- {
- var current = queue.Dequeue();
- foreach (
- var property in current
- .GetMembers()
- .OfType()
- .Where(static p => p.DeclaredAccessibility == Accessibility.Public && !p.IsStatic)
- )
- {
- var propertyType = TypeHelpers.UnwrapNullableType(property.Type);
-
- // Discover element types of arrays and generic collections.
- if (propertyType is IArrayTypeSymbol arrayType)
- {
- propertyType = arrayType.ElementType;
- }
- else if (propertyType is INamedTypeSymbol namedType)
- {
- var enumerableInterface = namedType.AllInterfaces.FirstOrDefault(
- TypeLibrary.Collections.IEnumerableT.Equals
- );
- if (enumerableInterface is not null)
- propertyType = enumerableInterface.TypeArguments[0];
- }
-
- if (propertyType is not INamedTypeSymbol namedPropertyType)
- continue;
-
- propertyType = TypeHelpers.UnwrapNullableType(namedPropertyType);
- if (propertyType is not INamedTypeSymbol unwrappedPropertyType)
- continue;
-
- // Only generate schemas for source-defined types in the same compilation.
- if (!unwrappedPropertyType.Locations.Any(static l => l.IsInSource))
- continue;
-
- // Skip scalar types that have their own validators.
- if (IsScalarType(unwrappedPropertyType))
- continue;
-
- if (seen.Add(unwrappedPropertyType))
- {
- builder.Add(
- (
- new(
- unwrappedPropertyType,
- new(unwrappedPropertyType),
- SourceGenLibrary.GetZodProperties(unwrappedPropertyType)
- ),
- false
- )
- );
- queue.Enqueue(unwrappedPropertyType);
- }
- }
- }
-
- return builder.ToImmutableArray();
- }
-
- static bool IsScalarType(INamedTypeSymbol type) =>
+ internal static bool IsScalarType(INamedTypeSymbol type) =>
type.SpecialType
is SpecialType.System_Boolean
or SpecialType.System_Char
@@ -234,7 +161,10 @@ bool isPrimary
)
{
writer.WriteField(
- new("EmptyPath", TypeLibrary.Collections.ImmutableArray.MakeGeneric(PurviewTypeLibrary.System.String))
+ new(
+ "EmptyPath",
+ TypeLibrary.Collections.ImmutableArray.MakeGeneric(PurviewTypeLibrary.System.String)
+ )
{
Accessibility = TypeDeclarationAccessibility.Private,
IsStatic = true,
@@ -434,8 +364,8 @@ static void GenerateValidateMethod(
List diagnostics
)
{
- outputContext.Writer
- .XmlSummary("Validates an instance of the target type.")
+ outputContext
+ .Writer.XmlSummary("Validates an instance of the target type.")
.WriteLine(
$"public static {TypeLibrary.ValidationResult.MakeGeneric(fullTypeName)} Validate({fullTypeName} value)"
);
@@ -467,8 +397,8 @@ List diagnostics
);
}
- outputContext.Writer
- .NewLine()
+ outputContext
+ .Writer.NewLine()
.WriteLine($"{TypeLibrary.Collections.List.MakeGeneric(TypeLibrary.ValidationError)}? errors = null;");
var properties = classSymbol
@@ -482,10 +412,14 @@ List diagnostics
using (outputContext.Writer.OpenBlockScope("if (errors is not null)"))
{
- outputContext.Writer.WriteLine($"return {TypeLibrary.ValidationResult}<{fullTypeName}>.Failure(errors);");
+ outputContext.Writer.WriteLine(
+ $"return {TypeLibrary.ValidationResult}<{fullTypeName}>.Failure(errors);"
+ );
}
- outputContext.Writer.WriteLine().WriteLine($"return {TypeLibrary.ValidationResult}<{fullTypeName}>.Success(value);");
+ outputContext
+ .Writer.WriteLine()
+ .WriteLine($"return {TypeLibrary.ValidationResult}<{fullTypeName}>.Success(value);");
}
outputContext.Writer.NewLine();
@@ -531,25 +465,11 @@ List diagnostics
outputContext.Writer.WriteRule(propertyName, comparision, "missing_field", errorMessage);
block = outputContext.Writer.OpenBlockScope("else");
- GenerateValueSetValidations(
- outputContext,
- property,
- property.Type,
- propertyName,
- attributes,
- diagnostics
- );
+ GenerateValueSetValidations(outputContext, property, property.Type, propertyName, attributes, diagnostics);
}
else
{
- GenerateValueSetValidations(
- outputContext,
- property,
- property.Type,
- propertyName,
- attributes,
- diagnostics
- );
+ GenerateValueSetValidations(outputContext, property, property.Type, propertyName, attributes, diagnostics);
if (canBeNull)
block = outputContext.Writer.OpenBlockScope($"if (value.{propertyName} != null)");
@@ -661,14 +581,7 @@ rangeAttributeData is not null
}
else if (TypeHelpers.IsNumericType(propertyType))
{
- GenerateNumericValidations(
- outputContext,
- property,
- propertyType,
- propertyName,
- attributes,
- diagnostics
- );
+ GenerateNumericValidations(outputContext, property, propertyType, propertyName, attributes, diagnostics);
}
else if (
propertyType is IArrayTypeSymbol
@@ -676,14 +589,7 @@ propertyType is IArrayTypeSymbol
|| TypeHelpers.Implements(propertyType, TypeLibrary.Collections.IEnumerableT)
)
{
- GenerateCollectionValidations(
- outputContext,
- property,
- propertyType,
- propertyName,
- attributes,
- diagnostics
- );
+ GenerateCollectionValidations(outputContext, property, propertyType, propertyName, attributes, diagnostics);
var elementType = GetCollectionElementType(propertyType);
if (
@@ -708,7 +614,7 @@ static void CheckUnsupportedStringOnlyAttribute(
ImmutableArray attributes,
string propertyName,
ITypeSymbol propertyType,
- TypeValueObject attributeType,
+ TypeIdentity attributeType,
string attributeName
)
{
@@ -908,9 +814,7 @@ bool canBeNull
}
else
{
- outputContext.Writer.WriteLine(
- $"var {nestedResultName} = {schemaTypeName}.Validate({propertyValueName});"
- );
+ outputContext.Writer.WriteLine($"var {nestedResultName} = {schemaTypeName}.Validate({propertyValueName});");
using (outputContext.Writer.OpenBlockScope($"if (!{nestedResultName}.IsSuccess)"))
{
outputContext.Writer.WriteLine(
@@ -995,23 +899,11 @@ out var maximumExpression
)
{
var fqTypeName = GetFullyQualifiedTypeName(propertyType);
- outputContext.Writer.WriteField(
- new(GetRangeMinimumFieldName(property.Name), fqTypeName)
- {
- IsReadOnly = true,
- IsStatic = true,
- Accessibility = TypeDeclarationAccessibility.Private,
- Initializer = minimumExpression,
- }
+ outputContext.Writer.WriteLine(
+ $"private static readonly {fqTypeName} {GetRangeMinimumFieldName(property.Name)} = {minimumExpression};"
);
- outputContext.Writer.WriteField(
- new(GetRangeMaximumFieldName(property.Name), fqTypeName)
- {
- IsReadOnly = true,
- IsStatic = true,
- Accessibility = TypeDeclarationAccessibility.Private,
- Initializer = maximumExpression,
- }
+ outputContext.Writer.WriteLine(
+ $"private static readonly {fqTypeName} {GetRangeMaximumFieldName(property.Name)} = {maximumExpression};"
);
}
}
diff --git a/src/src/SourceGenerators/ZodSchemaGenerator.CollectionValidators.cs b/src/src/SourceGenerators/ZodSchemaGenerator.CollectionValidators.cs
index eb18b44..dcdb63d 100644
--- a/src/src/SourceGenerators/ZodSchemaGenerator.CollectionValidators.cs
+++ b/src/src/SourceGenerators/ZodSchemaGenerator.CollectionValidators.cs
@@ -90,11 +90,7 @@ List diagnostics
lengthAttr.MinimumLength.ToString(CultureInfo.InvariantCulture)
);
- using (
- outputContext.Writer.OpenBlockScope(
- $"if ({propertyLengthName} < {lengthAttr.MinimumLength})"
- )
- )
+ using (outputContext.Writer.OpenBlockScope($"if ({propertyLengthName} < {lengthAttr.MinimumLength})"))
{
WriteValidationError(
outputContext,
@@ -107,9 +103,7 @@ List diagnostics
}
using (
- outputContext.Writer.OpenBlockScope(
- $"else if ({propertyLengthName} > {lengthAttr.MaximumLength})"
- )
+ outputContext.Writer.OpenBlockScope($"else if ({propertyLengthName} > {lengthAttr.MaximumLength})")
)
{
WriteValidationError(
@@ -136,9 +130,7 @@ List diagnostics
minLengthAttr.Length.ToString(CultureInfo.InvariantCulture)
);
- using (
- outputContext.Writer.OpenBlockScope($"if ({propertyLengthName} < {minLengthAttr.Length})")
- )
+ using (outputContext.Writer.OpenBlockScope($"if ({propertyLengthName} < {minLengthAttr.Length})"))
{
WriteValidationError(
outputContext,
@@ -164,9 +156,7 @@ List diagnostics
maxLengthAttr.Length.ToString(CultureInfo.InvariantCulture)
);
- using (
- outputContext.Writer.OpenBlockScope($"if ({propertyLengthName} > {maxLengthAttr.Length})")
- )
+ using (outputContext.Writer.OpenBlockScope($"if ({propertyLengthName} > {maxLengthAttr.Length})"))
{
WriteValidationError(
outputContext,
diff --git a/src/src/SourceGenerators/ZodSchemaGenerator.CustomValidation.cs b/src/src/SourceGenerators/ZodSchemaGenerator.CustomValidation.cs
index d098a05..90f6842 100644
--- a/src/src/SourceGenerators/ZodSchemaGenerator.CustomValidation.cs
+++ b/src/src/SourceGenerators/ZodSchemaGenerator.CustomValidation.cs
@@ -369,7 +369,7 @@ param.RefKind is RefKind.Ref or RefKind.In or RefKind.Out
/// Constructs the expected ValueTask<ValidationResult<T>> symbol
/// from the compilation's framework symbols.
///
- static TypeValueObject GetExpectedReturnType(INamedTypeSymbol classSymbol) =>
+ static TypeIdentity GetExpectedReturnType(INamedTypeSymbol classSymbol) =>
TypeLibrary.ValueTask.MakeGeneric(
TypeLibrary.ValidationResult.MakeGeneric(
classSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
diff --git a/src/src/SourceGenerators/ZodSchemaGenerator.StringValidators.cs b/src/src/SourceGenerators/ZodSchemaGenerator.StringValidators.cs
index 48d68c0..61abb98 100644
--- a/src/src/SourceGenerators/ZodSchemaGenerator.StringValidators.cs
+++ b/src/src/SourceGenerators/ZodSchemaGenerator.StringValidators.cs
@@ -325,9 +325,7 @@ List diagnostics
outputContext.Writer.WriteLine($"var {propertyValueName} = value.{propertyName};");
using (outputContext.Writer.OpenBlockScope($"if ({propertyValueName} is not null)"))
{
- outputContext.Writer.WriteLine(
- $"var {propertyLengthName} = {propertyValueName}.Length;"
- );
+ outputContext.Writer.WriteLine($"var {propertyLengthName} = {propertyValueName}.Length;");
using (
outputContext.Writer.OpenBlockScope(
$"if ({propertyLengthName} < {lengthAttr.MinimumLength})"
@@ -416,9 +414,7 @@ List diagnostics
}
using (
- outputContext.Writer.OpenBlockScope(
- $"if ({propertyLengthName} > {stringLengthAttr.MaximumLength})"
- )
+ outputContext.Writer.OpenBlockScope($"if ({propertyLengthName} > {stringLengthAttr.MaximumLength})")
)
{
WriteValidationError(
@@ -453,9 +449,7 @@ List diagnostics
outputContext.Writer.WriteLine($"var {propertyValueName} = value.{propertyName};");
outputContext.Writer.WriteLine($"var {propertyLengthName} = {propertyValueName}.Length;");
- using (
- outputContext.Writer.OpenBlockScope($"if ({propertyLengthName} < {minLengthAttr.Length})")
- )
+ using (outputContext.Writer.OpenBlockScope($"if ({propertyLengthName} < {minLengthAttr.Length})"))
{
WriteValidationError(
outputContext,
@@ -489,9 +483,7 @@ List diagnostics
outputContext.Writer.WriteLine($"var {propertyValueName} = value.{propertyName};");
outputContext.Writer.WriteLine($"var {propertyLengthName} = {propertyValueName}.Length;");
- using (
- outputContext.Writer.OpenBlockScope($"if ({propertyLengthName} > {maxLengthAttr.Length})")
- )
+ using (outputContext.Writer.OpenBlockScope($"if ({propertyLengthName} > {maxLengthAttr.Length})"))
{
WriteValidationError(
outputContext,
diff --git a/src/src/SourceGenerators/ZodSchemaGenerator.ValueValidators.cs b/src/src/SourceGenerators/ZodSchemaGenerator.ValueValidators.cs
index 2d12d2f..de28a5f 100644
--- a/src/src/SourceGenerators/ZodSchemaGenerator.ValueValidators.cs
+++ b/src/src/SourceGenerators/ZodSchemaGenerator.ValueValidators.cs
@@ -18,22 +18,8 @@ static void GenerateValueSetValidations(
List diagnostics
)
{
- GenerateAllowedValuesValidation(
- outputContext,
- property,
- propertyType,
- propertyName,
- attributes,
- diagnostics
- );
- GenerateDeniedValuesValidation(
- outputContext,
- property,
- propertyType,
- propertyName,
- attributes,
- diagnostics
- );
+ GenerateAllowedValuesValidation(outputContext, property, propertyType, propertyName, attributes, diagnostics);
+ GenerateDeniedValuesValidation(outputContext, property, propertyType, propertyName, attributes, diagnostics);
}
static void GenerateAllowedValuesValidation(
diff --git a/src/src/SourceGenerators/ZodSchemaGenerator.cs b/src/src/SourceGenerators/ZodSchemaGenerator.cs
index a539a24..9862bb4 100644
--- a/src/src/SourceGenerators/ZodSchemaGenerator.cs
+++ b/src/src/SourceGenerators/ZodSchemaGenerator.cs
@@ -13,13 +13,13 @@ public sealed partial class ZodSchemaGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
- context.RegisterEmbeddedAttribute(AssemblyInfo.AssemblyName, AssemblyInfo.Version);
-
- context.RegisterPostInitializationOutput(static ctx =>
- {
- foreach (var (HintName, SourceText) in AttributeGenHelper.GenerateMarkers())
- ctx.AddSource($"{HintName}.g.cs", SourceText);
- });
+ context
+ .RegisterEmbeddedAttribute()
+ .RegisterPostInitializationOutput(static ctx =>
+ {
+ foreach (var (HintName, SourceText) in AttributeGenHelper.GenerateMarkers())
+ ctx.AddSource($"{HintName}.g.cs", SourceText);
+ });
var generationValueProviders = SourceGenLibrary.GetGeneratorValueProviders(context);
@@ -28,31 +28,26 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
generationValueProviders,
(spc, model) =>
{
- if (model.GenerationContext.Settings.IsSourceGeneratorDisabled)
+ if (model.Context.Settings.IsSourceGeneratorDisabled)
return;
- var isFatal = false;
foreach (var schema in model.ZodSchemas)
{
if (schema.HasDiagnostics)
- {
- ReportDiagnostics(spc, schema.Diagnostics, model.GenerationContext);
- }
+ spc.ReportDiagnostics(schema.Diagnostics);
- if (schema.IsFatal)
- isFatal = true;
- }
+ if (!schema.ShouldProcess)
+ continue;
- if (isFatal)
- return;
+ var symbol = SymbolResolver.Resolve(
+ model.Context.Capabilities.Compilation,
+ schema.Value.SchemaType
+ );
+ if (symbol is null)
+ continue;
- SchemaGenerationOutputContext outputContext = new(model.GenerationContext);
- var allSchemas = DiscoverAllSchemas(model.ZodSchemas);
- foreach (var (descriptor, isPrimary) in allSchemas)
- {
- // We'll create a new new code writer for each schema.
- outputContext.CreateCodeWriter();
- BuildSchema(descriptor, outputContext, spc, isPrimary);
+ SchemaGenerationOutputContext outputContext = new(model.Context);
+ BuildSchema(symbol, schema.Value.SchemaType, outputContext, spc, schema.Value.IsPrimary);
}
}
);
diff --git a/src/src/ZodSharp/ZodSharp.csproj b/src/src/ZodSharp/ZodSharp.csproj
index db60d42..c821f61 100644
--- a/src/src/ZodSharp/ZodSharp.csproj
+++ b/src/src/ZodSharp/ZodSharp.csproj
@@ -1,8 +1,4 @@
-
- true
-
-
true
ZodSharp
@@ -11,6 +7,7 @@
Features zero-allocation validation, struct-based rules, fluent API,
and source generator support for maximum performance.
+ true
zod;validation;schema
$(NoWarn);NU5118
diff --git a/src/tests/SourceGenerators.UnitTests/Infra/IncrementalSourceGeneratorTestBase.cs b/src/tests/SourceGenerators.UnitTests/Infra/IncrementalSourceGeneratorTestBase.cs
deleted file mode 100644
index 072a784..0000000
--- a/src/tests/SourceGenerators.UnitTests/Infra/IncrementalSourceGeneratorTestBase.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Microsoft.CodeAnalysis;
-
-namespace ZodSharp.SourceGenerators.Infra;
-
-public abstract class IncrementalSourceGeneratorTestBase
- : TUnitSourceGeneratorTestBase
- where TGenerator : class, IIncrementalGenerator, new()
-{
- protected static readonly int ExpectedFileCount = ZodSharpGeneratorTestOptions.GeneratedAttributes.Length;
-
- protected static readonly int ExpectedFileCountPlusGen = ExpectedFileCount + 1;
-
- protected const int HintNameHashHexLength = 16;
-
- protected const string GeneratedSourceFileSuffix = ".g.cs";
-}
diff --git a/src/tests/SourceGenerators.UnitTests/Infra/ZodSharpGeneratorTestOptions.cs b/src/tests/SourceGenerators.UnitTests/Infra/ZodSharpGeneratorTestOptions.cs
deleted file mode 100644
index 1f5f521..0000000
--- a/src/tests/SourceGenerators.UnitTests/Infra/ZodSharpGeneratorTestOptions.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System.Collections.Immutable;
-using System.ComponentModel.DataAnnotations;
-using ZodSharp.SourceGenerators.Helpers;
-
-namespace ZodSharp.SourceGenerators.Infra;
-
-public record class ZodSharpGeneratorTestOptions : SourceGeneratorTestOptions
-{
- static readonly Type[] DefaultAssemblyTypes =
-[
- typeof(Z),
- typeof(ImmutableArray),
- typeof(RequiredAttribute),
- typeof(System.Text.Json.JsonSerializer),
- typeof(System.Text.RegularExpressions.Regex),
- ];
-
- public static readonly string[] GeneratedAttributes = ["EmbeddedAttribute.cs", "ZodSchemaAttribute.g.cs"];
-
- public ZodSharpGeneratorTestOptions()
- {
- IncludeDefaultNamespaces = true;
- ThrowOnGenerationException = true;
- DisableSourceGeneratorPropertyName = PropertyLibrary.DisableZodSharpSourceGeneratorProperty;
- CompileToAssembly = true;
-
- AdditionalNamespaces = [TypeLibrary.ZodSharpNamespace];
- AdditionalAssemblyTypes = ImmutableArray.Create(DefaultAssemblyTypes);
- ExcludeGeneratedSourceHintNames = [.. GeneratedAttributes];
- }
-}
diff --git a/src/tests/SourceGenerators.UnitTests/Infra/ZodSharpSourceGeneratorTestBase.cs b/src/tests/SourceGenerators.UnitTests/Infra/ZodSharpSourceGeneratorTestBase.cs
new file mode 100644
index 0000000..302fb68
--- /dev/null
+++ b/src/tests/SourceGenerators.UnitTests/Infra/ZodSharpSourceGeneratorTestBase.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis;
+
+namespace ZodSharp.SourceGenerators.Infra;
+
+public abstract class ZodSharpSourceGeneratorTestBase
+ : TUnitSourceGeneratorTestBase
+ where TGenerator : class, IIncrementalGenerator, new()
+{
+ public static readonly string[] GeneratedAttributes = ["EmbeddedAttribute.cs", "ZodSchemaAttribute.g.cs"];
+
+ public static readonly int ExpectedFileCount = GeneratedAttributes.Length;
+
+ public static readonly int ExpectedFileCountPlusGen = ExpectedFileCount + 1;
+
+ public const int HintNameHashHexLength = 16;
+
+ public const string GeneratedSourceFileSuffix = ".g.cs";
+}
diff --git a/src/tests/SourceGenerators.UnitTests/Infra/ZodSourceGeneratorTestOptions.cs b/src/tests/SourceGenerators.UnitTests/Infra/ZodSourceGeneratorTestOptions.cs
new file mode 100644
index 0000000..1ad1922
--- /dev/null
+++ b/src/tests/SourceGenerators.UnitTests/Infra/ZodSourceGeneratorTestOptions.cs
@@ -0,0 +1,25 @@
+using System.Collections.Immutable;
+using System.ComponentModel.DataAnnotations;
+using ZodSharp.SourceGenerators.Helpers;
+
+namespace ZodSharp.SourceGenerators.Infra;
+
+public sealed record ZodSourceGeneratorTestOptions : SourceGeneratorTestOptions
+{
+ public ZodSourceGeneratorTestOptions()
+ {
+ AdditionalAssemblyTypes =
+ [
+ typeof(Z),
+ typeof(ImmutableArray),
+ typeof(RequiredAttribute),
+ typeof(System.Text.Json.JsonSerializer),
+ typeof(System.Text.RegularExpressions.Regex),
+ ];
+ AdditionalNamespaces = [TypeLibrary.ZodSharpNamespace];
+ ExcludeGeneratedSourceHintNames = ["EmbeddedAttribute", "ZodSchemaAttribute"];
+ DisableSourceGeneratorPropertyName = PropertyLibrary.DisableZodSharpSourceGeneratorProperty;
+ }
+
+ public static readonly ZodSourceGeneratorTestOptions NoValidation = new() { ThrowOnGenerationException = false };
+}
diff --git a/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.AttributeGen.cs b/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.AttributeGen.cs
index 7045ce6..3af46f4 100644
--- a/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.AttributeGen.cs
+++ b/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.AttributeGen.cs
@@ -25,7 +25,7 @@ public class Empty { }
var driverResult = await GenerateAsync(source, cancellationToken);
// Assert
- await Assert.That(driverResult.AllSyntaxTrees).Count().IsEqualTo(ExpectedFileCount);
+ await Assert.That(driverResult.AllSyntaxTrees.Length).IsEqualTo(ExpectedFileCount);
}
[Test]
diff --git a/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.BasicGen.cs b/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.BasicGen.cs
index 3ef6ebf..4d908ba 100644
--- a/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.BasicGen.cs
+++ b/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.BasicGen.cs
@@ -47,9 +47,9 @@ namespace Testing
// Act
var driverResult = await GenerateAsync(source, cancellationToken);
- var generatedSource = GetSchemaGeneratedSource(driverResult);
+ var generatedSource = driverResult.GetSource();
// Assert — generated file starts with auto-generated header
- await Assert.That(generatedSource).Contains(expectation);
+ await Assert.That(generatedSource).ContainsGeneratedCode(expectation);
}
}
diff --git a/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.CompositionMethods.cs b/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.CompositionMethods.cs
index 3fc1cec..476858d 100644
--- a/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.CompositionMethods.cs
+++ b/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.CompositionMethods.cs
@@ -20,13 +20,11 @@ public class ComposeModel
// Act
var driverResult = await GenerateAsync(source, cancellationToken);
- var generatedSource = GetSchemaGeneratedSource(driverResult, "ComposeModelSchema");
+ var generatedSource = driverResult.GetSource("ComposeModelSchema");
- await Assert.That(generatedSource).Contains("ApplyAnd");
- await Assert.That(generatedSource).Contains("ApplyOr");
- await Assert.That(generatedSource).Contains("ApplyRefine");
- // Compilation succeeding proves the previously-buggy Or emission (a stray
- // dollar sign before the return type) is fixed.
+ await Assert.That(generatedSource).ContainsGeneratedCode("ApplyAnd");
+ await Assert.That(generatedSource).ContainsGeneratedCode("ApplyOr");
+ await Assert.That(generatedSource).ContainsGeneratedCode("ApplyRefine");
}
[Test]
diff --git a/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.CustomValidation.cs b/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.CustomValidation.cs
index c1bf7e3..3fac531 100644
--- a/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.CustomValidation.cs
+++ b/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.CustomValidation.cs
@@ -1,4 +1,5 @@
using ZodSharp.SourceGenerators.Helpers;
+using ZodSharp.SourceGenerators.Infra;
namespace ZodSharp.SourceGenerators;
@@ -18,13 +19,16 @@ public class NoCustom { public string? Name { get; set; } }
}";
var driverResult = await GenerateAsync(source, cancellationToken);
- var generated = GetSchemaGeneratedSource(driverResult, "NoCustomSchema");
+ var generated = driverResult.GetSource("NoCustomSchema");
// No async state machine
- await Assert.That(generated).Contains("ValueTask.FromResult").Because("Should use FromResult, not async");
await Assert
- .That(generated.Contains("async ", StringComparison.Ordinal))
- .IsFalse()
+ .That(generated)
+ .ContainsGeneratedCode("ValueTask.FromResult")
+ .Because("Should use FromResult, not async");
+ await Assert
+ .That(generated)
+ .DoesNotContain("async ", StringComparison.Ordinal)
.Because("Should not generate async when no custom method");
}
@@ -51,15 +55,15 @@ internal static ValueTask> CustomValidationAsync(
}";
var driverResult = await GenerateAsync(source, cancellationToken);
- var generated = GetSchemaGeneratedSource(driverResult, "WithDefaultSchema");
+ var generated = driverResult.GetSource("WithDefaultSchema");
await Assert
- .That(generated.Contains("async ", StringComparison.Ordinal))
- .IsTrue()
+ .That(generated)
+ .Contains("async ", StringComparison.Ordinal)
.Because("Should generate async when custom method exists");
- await Assert.That(generated).Contains("await global::Testing.WithDefault.CustomValidationAsync");
- await Assert.That(generated).Contains(".ConfigureAwait(false)");
- await Assert.That(generated).Contains(".Merge(syncResult, customResult)");
+ await Assert.That(generated).ContainsGeneratedCode("await global::Testing.WithDefault.CustomValidationAsync");
+ await Assert.That(generated).ContainsGeneratedCode(".ConfigureAwait(false)");
+ await Assert.That(generated).ContainsGeneratedCode(".Merge(syncResult, customResult)");
}
[Test]
@@ -85,9 +89,9 @@ internal static ValueTask> ValidateRulesAsync(
}";
var driverResult = await GenerateAsync(source, cancellationToken);
- var generated = GetSchemaGeneratedSource(driverResult, "WithOverrideSchema");
+ var generated = driverResult.GetSource("WithOverrideSchema");
- await Assert.That(generated).Contains("await global::Testing.WithOverride.ValidateRulesAsync");
+ await Assert.That(generated).ContainsGeneratedCode("await global::Testing.WithOverride.ValidateRulesAsync");
}
[Test]
@@ -101,7 +105,7 @@ namespace Testing
public class MissingMethod { public string? Name { get; set; } }
}";
- var driverResult = await GenerateAsync(source, cancellationToken);
+ var driverResult = await GenerateAsync(source, ZodSourceGeneratorTestOptions.NoValidation, cancellationToken);
await Assert.That(driverResult).HasDiagnostic(DiagnosticLibrary.CustomValidationMethodNotFound);
}
@@ -128,7 +132,7 @@ internal static Task> CustomValidationAsync(
}
}";
- var driverResult = await GenerateAsync(source, cancellationToken);
+ var driverResult = await GenerateAsync(source, ZodSourceGeneratorTestOptions.NoValidation, cancellationToken);
await Assert.That(driverResult).HasDiagnostic(DiagnosticLibrary.CustomValidationInvalidReturnType);
}
@@ -154,7 +158,7 @@ internal static ValueTask> CustomValidationAsync(
}
}";
- var driverResult = await GenerateAsync(source, cancellationToken);
+ var driverResult = await GenerateAsync(source, ZodSourceGeneratorTestOptions.NoValidation, cancellationToken);
await Assert.That(driverResult).HasDiagnostic(DiagnosticLibrary.CustomValidationInvalidParameterCount);
}
@@ -182,7 +186,7 @@ internal static ValueTask> CustomValidationAsync(
}
}";
- var driverResult = await GenerateAsync(source, cancellationToken);
+ var driverResult = await GenerateAsync(source, ZodSourceGeneratorTestOptions.NoValidation, cancellationToken);
await Assert.That(driverResult).HasDiagnostic(DiagnosticLibrary.CustomValidationInvalidModelParameter);
}
@@ -210,7 +214,7 @@ internal static ValueTask> CustomValidationAsync(
}
}";
- var driverResult = await GenerateAsync(source, cancellationToken);
+ var driverResult = await GenerateAsync(source, ZodSourceGeneratorTestOptions.NoValidation, cancellationToken);
await Assert.That(driverResult).HasDiagnostic(DiagnosticLibrary.CustomValidationInvalidCancellationToken);
}
@@ -236,7 +240,7 @@ internal static ValueTask> CustomValidationAsync(
}
}";
- var driverResult = await GenerateAsync(source, cancellationToken);
+ var driverResult = await GenerateAsync(source, ZodSourceGeneratorTestOptions.NoValidation, cancellationToken);
await Assert.That(driverResult).HasDiagnostic(DiagnosticLibrary.CustomValidationGenericMethod);
}
@@ -262,7 +266,7 @@ internal ValueTask> CustomValidationAsync(
}
}";
- var driverResult = await GenerateAsync(source, cancellationToken);
+ var driverResult = await GenerateAsync(source, ZodSourceGeneratorTestOptions.NoValidation, cancellationToken);
await Assert.That(driverResult).HasDiagnostic(DiagnosticLibrary.CustomValidationInvalidStaticInstance);
}
@@ -288,7 +292,7 @@ private static ValueTask> CustomValidationAsync(
}
}";
- var driverResult = await GenerateAsync(source, cancellationToken);
+ var driverResult = await GenerateAsync(source, ZodSourceGeneratorTestOptions.NoValidation, cancellationToken);
await Assert.That(driverResult).HasDiagnostic(DiagnosticLibrary.CustomValidationInaccessible);
}
@@ -338,7 +342,7 @@ namespace Testing
public class BadName { public string? Name { get; set; } }
}";
- var driverResult = await GenerateAsync(source, cancellationToken);
+ var driverResult = await GenerateAsync(source, ZodSourceGeneratorTestOptions.NoValidation, cancellationToken);
await Assert.That(driverResult).HasDiagnostic(DiagnosticLibrary.CustomValidationInvalidMethodName);
}
@@ -364,7 +368,7 @@ internal static ValueTask> CustomValidationAsync(
}
}";
- var driverResult = await GenerateAsync(source, cancellationToken);
+ var driverResult = await GenerateAsync(source, ZodSourceGeneratorTestOptions.NoValidation, cancellationToken);
await Assert.That(driverResult).HasDiagnostic(DiagnosticLibrary.CustomValidationInvalidParameterModifier);
}
@@ -398,7 +402,7 @@ internal static ValueTask> CustomValidationA
// The first has wrong return type, the second has 3 params (wrong count).
// Neither is valid — should get diagnostics but no ambiguity.
- var driverResult = await GenerateAsync(source, cancellationToken);
+ var driverResult = await GenerateAsync(source, ZodSourceGeneratorTestOptions.NoValidation, cancellationToken);
await Assert.That(driverResult).HasDiagnostic(DiagnosticLibrary.CustomValidationInvalidReturnType);
await Assert.That(driverResult).HasDiagnostic(DiagnosticLibrary.CustomValidationInvalidParameterCount);
diff --git a/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.DataAnnotations.cs b/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.DataAnnotations.cs
index 20f1d08..efced9a 100644
--- a/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.DataAnnotations.cs
+++ b/src/tests/SourceGenerators.UnitTests/ZodSchemaGeneratorTests.DataAnnotations.cs
@@ -1,5 +1,6 @@
using System.Reflection;
using ZodSharp.SourceGenerators.Helpers;
+using ZodSharp.SourceGenerators.Infra;
namespace ZodSharp.SourceGenerators;
@@ -76,17 +77,20 @@ CancellationToken cancellationToken
)
{
var driverResult = await GenerateAsync(UserSource, cancellationToken);
+
var generatedSource = driverResult.GetSource("UserSchema");
- await Assert.That(generatedSource).Contains("value.Name == null");
- await Assert.That(generatedSource).Contains("var nameValue = value.Name;");
- await Assert.That(generatedSource).Contains("var nameLength = nameValue.Length;");
- await Assert.That(generatedSource).Contains("nameLength < 3");
- await Assert.That(generatedSource).Contains("nameLength > 50");
- await Assert.That(generatedSource).Contains("static readonly int RangeMinimum_Age = 0;");
- await Assert.That(generatedSource).Contains("static readonly int RangeMaximum_Age = 120;");
- await Assert.That(generatedSource).Contains("ageValue < RangeMinimum_Age || ageValue > RangeMaximum_Age");
- await Assert.That(generatedSource).Contains("EmailRegex.IsMatch(value.Email)");
+ await Assert.That(generatedSource).ContainsGeneratedCode("value.Name == null");
+ await Assert.That(generatedSource).ContainsGeneratedCode("var nameValue = value.Name;");
+ await Assert.That(generatedSource).ContainsGeneratedCode("var nameLength = nameValue.Length;");
+ await Assert.That(generatedSource).ContainsGeneratedCode("nameLength < 3");
+ await Assert.That(generatedSource).ContainsGeneratedCode("nameLength > 50");
+ await Assert.That(generatedSource).ContainsGeneratedCode("static readonly int RangeMinimum_Age = 0;");
+ await Assert.That(generatedSource).ContainsGeneratedCode("static readonly int RangeMaximum_Age = 120;");
+ await Assert
+ .That(generatedSource)
+ .ContainsGeneratedCode("ageValue < RangeMinimum_Age || ageValue > RangeMaximum_Age");
+ await Assert.That(generatedSource).ContainsGeneratedCode("EmailRegex.IsMatch(value.Email)");
}
[Test]
@@ -124,12 +128,13 @@ public sealed class LengthExamples
";
var driverResult = await GenerateAsync(source, cancellationToken);
+
var generatedSource = driverResult.GetSource("LengthExamplesSchema");
- await Assert.That(generatedSource).Contains("propertyValue.Length");
- await Assert.That(generatedSource).Contains("propertyValue.Count");
- await Assert.That(generatedSource).Contains("CollectionCountHelper.GetCount(propertyValue)");
- await Assert.That(generatedSource).Contains("else if");
+ await Assert.That(generatedSource).ContainsGeneratedCode("propertyValue.Length");
+ await Assert.That(generatedSource).ContainsGeneratedCode("propertyValue.Count");
+ await Assert.That(generatedSource).ContainsGeneratedCode("CollectionCountHelper.GetCount(propertyValue)");
+ await Assert.That(generatedSource).ContainsGeneratedCode("else if");
}
[Test]
@@ -163,15 +168,17 @@ public sealed class AttributeExamples
";
var driverResult = await GenerateAsync(source, cancellationToken);
- var generatedSource = GetSchemaGeneratedSource(driverResult, "AttributeExamplesSchema");
+ var generatedSource = driverResult.GetSource("AttributeExamplesSchema");
await Assert
.That(generatedSource)
- .Contains("static readonly global::System.Text.RegularExpressions.Regex Regex_CountryCode");
- await Assert.That(generatedSource).Contains("EqualityComparer.Default.Equals(statusValue, \"open\")");
- await Assert.That(generatedSource).Contains("EqualityComparer.Default.Equals(codeValue, 13)");
- await Assert.That(generatedSource).Contains("static readonly decimal RangeMinimum_Price");
- await Assert.That(generatedSource).Contains("Decimal.Parse(\"1.5\"");
+ .ContainsGeneratedCode("static readonly global::System.Text.RegularExpressions.Regex Regex_CountryCode");
+ await Assert
+ .That(generatedSource)
+ .ContainsGeneratedCode("EqualityComparer.Default.Equals(statusValue, \"open\")");
+ await Assert.That(generatedSource).ContainsGeneratedCode("EqualityComparer.Default.Equals(codeValue, 13)");
+ await Assert.That(generatedSource).ContainsGeneratedCode("static readonly decimal RangeMinimum_Price");
+ await Assert.That(generatedSource).ContainsGeneratedCode("Decimal.Parse(\"1.5\"");
}
[Test]
@@ -423,7 +430,7 @@ public sealed class InvalidLengthModel
}
";
- var driverResult = await GenerateAsync(source, cancellationToken);
+ var driverResult = await GenerateAsync(source, ZodSourceGeneratorTestOptions.NoValidation, cancellationToken);
await Assert.That(driverResult).HasDiagnostic(DiagnosticLibrary.InvalidLengthAttribute);
}
@@ -446,7 +453,7 @@ public sealed class UnsupportedLengthModel
}
";
- var driverResult = await GenerateAsync(source, cancellationToken);
+ var driverResult = await GenerateAsync(source, ZodSourceGeneratorTestOptions.NoValidation, cancellationToken);
await Assert.That(driverResult).HasDiagnostic(DiagnosticLibrary.UnsupportedLengthAttributeTarget);
}
@@ -474,11 +481,11 @@ public sealed class UnsupportedAnnotationsModel
}
";
- var driverResult = await GenerateAsync(source, cancellationToken);
+ var driverResult = await GenerateAsync(source, ZodSourceGeneratorTestOptions.NoValidation, cancellationToken);
- await Assert
- .That(driverResult.DriverResult.Diagnostics.Count(static d => d.Id == DiagnosticLibrary.UnsupportedDataAnnotationsUsage.Id))
- .IsGreaterThanOrEqualTo(2);
+ await Assert.That(driverResult).HasDiagnostics("ZODSGEN006", 2);
+ //.And
+ //.IsGreaterThanOrEqualTo(2);
}
[Test]
@@ -499,7 +506,7 @@ public sealed class InvalidResourceModel
}
";
- var driverResult = await GenerateAsync(source, cancellationToken);
+ var driverResult = await GenerateAsync(source, ZodSourceGeneratorTestOptions.NoValidation, cancellationToken);
await Assert.That(driverResult).HasDiagnostic(DiagnosticLibrary.InvalidDataAnnotationsErrorMessage);
}
@@ -539,19 +546,23 @@ public sealed class StringFormatModel
";
var driverResult = await GenerateAsync(source, cancellationToken);
- var generatedSource = GetSchemaGeneratedSource(driverResult, "StringFormatModelSchema");
+ var generatedSource = driverResult.GetSource("StringFormatModelSchema");
- await Assert.That(generatedSource).Contains("new global::ZodSharp.Rules.UrlRule().IsValid(websiteValue)");
- await Assert.That(generatedSource).Contains("new global::ZodSharp.Rules.PhoneRule().IsValid(phoneNumberValue)");
await Assert
.That(generatedSource)
- .Contains("new global::ZodSharp.Rules.CreditCardRule().IsValid(cardNumberValue)");
+ .ContainsGeneratedCode("new global::ZodSharp.Rules.UrlRule().IsValid(websiteValue)");
+ await Assert
+ .That(generatedSource)
+ .ContainsGeneratedCode("new global::ZodSharp.Rules.PhoneRule().IsValid(phoneNumberValue)");
await Assert
.That(generatedSource)
- .Contains("new global::ZodSharp.Rules.Base64StringRule().IsValid(encodedValue)");
+ .ContainsGeneratedCode("new global::ZodSharp.Rules.CreditCardRule().IsValid(cardNumberValue)");
await Assert
.That(generatedSource)
- .Contains("EqualityComparer