Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<RoslynCompilerVersion>5.9.0</RoslynCompilerVersion>
<RoslynAnalyzersVersion>5.9.0</RoslynAnalyzersVersion>
<TUnitVersion>1.68.17</TUnitVersion>
<PurviewSourceGenFramework>1.0.0-prerelease.53</PurviewSourceGenFramework>
<PurviewSourceGenFramework>1.0.0-prerelease.54</PurviewSourceGenFramework>
<DotnetRuntimeVersion>10.0.12</DotnetRuntimeVersion>
</PropertyGroup>
<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zodsharp",
"version": "2.0.0-prerelease.27",
"version": "2.0.0-prerelease.28",
"private": true,
"license": "MIT",
"author": {
Expand Down
2 changes: 1 addition & 1 deletion src/src/SourceGenerators/Helpers/CustomRuleResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ static bool TryBuildArguments(
out string? unmappedParameterName
)
{
arguments = new(ImmutableArray<string>.Empty);
arguments = new([]);
unmappedParameterName = null;

var constructor = ruleType
Expand Down
18 changes: 8 additions & 10 deletions src/src/SourceGenerators/Helpers/ExternalSchemaResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,16 @@ namespace ZodSharp.SourceGenerators.Helpers;
/// the cache keys and values are value types only, so nothing from a compilation outlives the pass.
/// </para>
/// </remarks>
sealed class ExternalSchemaResolver
/// <remarks>
/// Initializes a new instance of the <see cref="ExternalSchemaResolver"/> class.
/// </remarks>
/// <param name="compilation">The compilation currently being generated.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="compilation"/> is null.</exception>
sealed class ExternalSchemaResolver(Compilation compilation)
{
readonly Compilation _compilation;
readonly Compilation _compilation = compilation ?? throw new ArgumentNullException(nameof(compilation));
readonly ConcurrentDictionary<(TypeIdentity Type, AssemblyIdentity Assembly), Resolution> _resolved = new();

/// <summary>
/// Initializes a new instance of the <see cref="ExternalSchemaResolver"/> class.
/// </summary>
/// <param name="compilation">The compilation currently being generated.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="compilation"/> is null.</exception>
public ExternalSchemaResolver(Compilation compilation) =>
_compilation = compilation ?? throw new ArgumentNullException(nameof(compilation));

/// <summary>
/// Determines whether the compilation currently being generated declares <paramref name="type"/>.
/// </summary>
Expand Down Expand Up @@ -107,6 +104,7 @@ public bool TryGetExistingSchema(INamedTypeSymbol type, out TypeIdentity schemaT
return null;
}

// The schema is accessible and was generated by the assembly declaring the type.
return schema;
}

Expand Down
7 changes: 6 additions & 1 deletion src/src/SourceGenerators/Helpers/TypeLibrary.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
namespace ZodSharp.SourceGenerators.Helpers;

// Deliberately not public (the default accessibility for a top-level type): the generated type
// library exposes Purview.SourceGeneratorFramework type identities, which the IL merge internalizes,
// so a public spec would leave the merged analyzer with a public member whose signature references
// an internal type. The BuildSdk generates InternalsVisibleTo for the matching unit-test assembly, so
// tests still use it.
[GenerateTypeLibrary]
public static partial class TypeLibraryGenerator
static partial class TypeLibraryGenerator
{
public const string ZodSharpNamespace = "ZodSharp";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ internal static GeneratorResult<ZodRefinementHookData> Resolve(INamedTypeSymbol
diagnostics.Add(
ReportableDiagnostic.Create(
DiagnosticLibrary.SyncRefinementMethodRetired,
true,
false,
retiredMethod.Locations.FirstOrDefault(static location => location.IsInSource)
?? GetTypeLocation(classSymbol),
retiredMethod.Name,
Expand Down Expand Up @@ -249,6 +249,7 @@ type is not INamedTypeSymbol named
return false;
}

// The RefineCtx<T> type argument must be the same as the containing type, but the containing type may be
return SymbolEqualityComparer.Default.Equals(named.TypeArguments[0], classSymbol);
}

Expand Down
2 changes: 1 addition & 1 deletion src/src/SourceGenerators/ZodSchemaAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ ExternalSchemaResolver externalSchemas
}
}

return reachable.ToImmutableHashSet();
return [.. reachable];
}

static IEnumerable<INamedTypeSymbol> EnumerateNamedTypes(INamespaceSymbol root)
Expand Down
2 changes: 2 additions & 0 deletions src/src/SourceGenerators/ZodSchemaGenerator.RuleAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ static string BuildInitializer(IParameterSymbol parameter)
return literal;
}

// If the parameter has no default value, we still need to provide an initializer for the attribute property.
return parameter.Type.IsValueType ? "default!" : "null!";
}

Expand Down Expand Up @@ -195,6 +196,7 @@ static bool IsSupportedAttributePropertyType(ITypeSymbol type)
if (unwrapped.ToDisplayString() == "System.Type")
return true;

// Only primitive types and string are supported as attribute properties.
return unwrapped.SpecialType
is SpecialType.System_Boolean
or SpecialType.System_Byte
Expand Down
1 change: 1 addition & 0 deletions src/src/ZodSharp/Core/ZodType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public ZodType<TOutput, TInput> Rule<TRule>(TRule rule)
if (rule is null)
throw new ArgumentNullException(nameof(rule));

// We can safely cast to IValidationRule<TOutput> because TRule is constrained to that interface.
return AddRule(rule);
}

Expand Down
1 change: 1 addition & 0 deletions src/src/ZodSharp/Rules/CreditCardRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public bool IsValid(in string value)
if (string.IsNullOrWhiteSpace(value))
return false;

// Use AsSpan to avoid allocations
return IsValid(value.AsSpan());
}

Expand Down
4 changes: 2 additions & 2 deletions src/src/ZodSharp/Schemas/ZodDiscriminatedUnion.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Linq.Expressions;
using System.Reflection;
Expand Down Expand Up @@ -28,7 +28,7 @@ ImmutableDictionary<string, IZodSchema<object, object>> options
static readonly ConditionalWeakTable<
Type,
ConcurrentDictionary<string, Func<object, string?>>
> DiscriminatorAccessors = new();
> DiscriminatorAccessors = [];

static readonly Func<object, string?> MissingDiscriminatorAccessor = static _ => null;

Expand Down
2 changes: 2 additions & 0 deletions src/src/ZodSharp/Schemas/ZodString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public ValidationResult<string> ValidateSpan(ReadOnlySpan<char> value)
if (!SupportsSpanRules)
return Validate(value.ToString());

// Every rule supports the span contract, so validate the span directly and return a string result.
return IsValidSpan(value, out var errors)
? ValidationResult<string>.Success(value.ToString())
: ValidationResult<string>.Failure(errors);
Expand All @@ -85,6 +86,7 @@ public ValidationResult<string> ValidateSpan(ReadOnlySpan<char> value)
/// This is the allocation-free span entry point: it validates the span directly when every rule
/// implements <see cref="IStringValidationRule"/>, and otherwise falls back to the string path once.
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1021:Avoid out parameters")]
public bool IsValidSpan(ReadOnlySpan<char> value, out ImmutableArray<ValidationError> errors)
{
errors = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Collections.Immutable;
using ZodSharp.Core;
using ZodSharp.SourceGenerators.Infra;

namespace ZodSharp.SourceGenerators;
Expand Down Expand Up @@ -75,8 +77,7 @@ CancellationToken cancellationToken
var isSuccess = (bool)result.GetType().GetProperty("IsSuccess")!.GetValue(result)!;
await Assert.That(isSuccess).IsFalse();

var errors = (System.Collections.Immutable.ImmutableArray<ZodSharp.Core.ValidationError>)
result.GetType().GetProperty("Errors")!.GetValue(result)!;
var errors = (ImmutableArray<ValidationError>)result.GetType().GetProperty("Errors")!.GetValue(result)!;
await Assert.That(errors).HasSingleItem();
await Assert.That(errors[0].Code).IsEqualTo("invalid_string");
await Assert.That(errors[0].Origin).IsEqualTo("string");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public async Task GenericRule_GivenScalarLikeStruct_FailsAtRuntimeForEmptyGuid(C
// Assert
await Assert.That((bool)emptyResult.GetType().GetProperty("IsSuccess")!.GetValue(emptyResult)!).IsFalse();

var errors = (System.Collections.Immutable.ImmutableArray<ZodSharp.Core.ValidationError>)
var errors = (System.Collections.Immutable.ImmutableArray<Core.ValidationError>)
emptyResult.GetType().GetProperty("Errors")!.GetValue(emptyResult)!;
await Assert.That(errors).HasSingleItem();
await Assert.That(errors[0].Code).IsEqualTo("invalid_asset_id");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ CancellationToken cancellationToken
// Assert
await Assert.That((bool)emptyResult.GetType().GetProperty("IsSuccess")!.GetValue(emptyResult)!).IsFalse();

var errors = (System.Collections.Immutable.ImmutableArray<ZodSharp.Core.ValidationError>)
var errors = (System.Collections.Immutable.ImmutableArray<Core.ValidationError>)
emptyResult.GetType().GetProperty("Errors")!.GetValue(emptyResult)!;
await Assert.That(errors).HasSingleItem();
await Assert.That(errors[0].Code).IsEqualTo("invalid_asset_id");
Expand Down
11 changes: 2 additions & 9 deletions src/tests/ZodSharp.UnitTests/Core/ValidationErrorTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Collections.Immutable;

namespace ZodSharp.Core;

public class ValidationErrorTests
Expand All @@ -25,12 +23,7 @@ public async Task Create_GivenCategory_PopulatesCategory()
const string category = "invalid_value";

// Act
var error = ValidationError.Create(
"invalid_tenant_id",
"The tenant id is invalid.",
ImmutableArray<string>.Empty,
category: category
);
var error = ValidationError.Create("invalid_tenant_id", "The tenant id is invalid.", [], category: category);

// Assert
await Assert.That(error.Category).IsEqualTo(category);
Expand All @@ -40,7 +33,7 @@ public async Task Create_GivenCategory_PopulatesCategory()
public async Task Create_GivenNoCategory_LeavesCategoryNull()
{
// Act
var error = ValidationError.Create("too_small", "Too small.", ImmutableArray<string>.Empty);
var error = ValidationError.Create("too_small", "Too small.", []);

// Assert
await Assert.That(error.Category).IsNull();
Expand Down
2 changes: 1 addition & 1 deletion src/tests/ZodSharp.UnitTests/Schemas/ZodStringSpanTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public async Task ValidateSpan_GivenEmptySpan_AppliesRules()
// Regression: an empty span previously bypassed the rules and always succeeded.
var schema = Z.String().Min(3);

var result = schema.ValidateSpan(ReadOnlySpan<char>.Empty);
var result = schema.ValidateSpan([]);

await Assert.That(result.IsSuccess).IsFalse();
}
Expand Down
Loading