continue;
}
- var membersToValidate = GetMembersToValidate(modelType, true);
+ Location lowerLocationInCompilation = _compilation.ContainsSyntaxTree(modelType.GetLocation().SourceTree)
+ ? modelType.GetLocation()
+ : syntax.GetLocation();
+
+ var membersToValidate = GetMembersToValidate(modelType, true, lowerLocationInCompilation);
if (membersToValidate.Count == 0)
{
// this type lacks any eligible members
return null;
}
- private List<ValidatedMember> GetMembersToValidate(ITypeSymbol modelType, bool speculate)
+ private List<ValidatedMember> GetMembersToValidate(ITypeSymbol modelType, bool speculate, Location lowerLocationInCompilation)
{
// make a list of the most derived members in the model type
var membersToValidate = new List<ValidatedMember>();
foreach (var member in members)
{
- var memberInfo = GetMemberInfo(member, speculate);
+ Location location = _compilation.ContainsSyntaxTree(member.GetLocation().SourceTree)
+ ? member.GetLocation()
+ : lowerLocationInCompilation;
+
+ var memberInfo = GetMemberInfo(member, speculate, location);
if (memberInfo is not null)
{
if (member.DeclaredAccessibility != Accessibility.Public && member.DeclaredAccessibility != Accessibility.Internal)
return membersToValidate;
}
- private ValidatedMember? GetMemberInfo(ISymbol member, bool speculate)
+ private ValidatedMember? GetMemberInfo(ISymbol member, bool speculate, Location location)
{
ITypeSymbol memberType;
switch (member)
if (transValidatorTypeName == null)
{
transValidatorIsSynthetic = true;
- transValidatorTypeName = AddSynthesizedValidator(memberType, member);
+ transValidatorTypeName = AddSynthesizedValidator(memberType, member, location);
}
// pop the stack
if (enumerationValidatorTypeName == null)
{
enumerationValidatorIsSynthetic = true;
- enumerationValidatorTypeName = AddSynthesizedValidator(enumeratedType, member);
+ enumerationValidatorTypeName = AddSynthesizedValidator(enumeratedType, member, location);
}
// pop the stack
// generate a warning if the member is const/static and has a validation attribute applied
if (validationAttributeIsApplied)
{
- Diag(DiagDescriptors.CantValidateStaticOrConstMember, member.GetLocation(), member.Name);
+ Diag(DiagDescriptors.CantValidateStaticOrConstMember, location, member.Name);
}
// don't validate the member in any case
{
if (!HasOpenGenerics(memberType, out var genericType))
{
- var membersToValidate = GetMembersToValidate(memberType, false);
+ var membersToValidate = GetMembersToValidate(memberType, false, location);
if (membersToValidate.Count > 0)
{
- Diag(DiagDescriptors.PotentiallyMissingTransitiveValidation, member.GetLocation(), memberType.Name, member.Name);
+ Diag(DiagDescriptors.PotentiallyMissingTransitiveValidation, location, memberType.Name, member.Name);
}
}
}
{
if (!HasOpenGenerics(enumeratedType, out var genericType))
{
- var membersToValidate = GetMembersToValidate(enumeratedType, false);
+ var membersToValidate = GetMembersToValidate(enumeratedType, false, location);
if (membersToValidate.Count > 0)
{
- Diag(DiagDescriptors.PotentiallyMissingEnumerableValidation, member.GetLocation(), enumeratedType.Name, member.Name);
+ Diag(DiagDescriptors.PotentiallyMissingEnumerableValidation, location, enumeratedType.Name, member.Name);
}
}
}
return null;
}
- private string? AddSynthesizedValidator(ITypeSymbol modelType, ISymbol member)
+ private string? AddSynthesizedValidator(ITypeSymbol modelType, ISymbol member, Location location)
{
var mt = modelType.WithNullableAnnotation(NullableAnnotation.None);
if (mt.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
return "global::" + validator.Namespace + "." + validator.Name;
}
- var membersToValidate = GetMembersToValidate(mt, true);
+ var membersToValidate = GetMembersToValidate(mt, true, location);
if (membersToValidate.Count == 0)
{
// this type lacks any eligible members
- Diag(DiagDescriptors.NoEligibleMember, member.GetLocation(), mt.ToString(), member.ToString());
+ Diag(DiagDescriptors.NoEligibleMember, location, mt.ToString(), member.ToString());
return null;
}
return sb.ToString();
}
- private void Diag(DiagnosticDescriptor desc, Location? location)
- {
+ private void Diag(DiagnosticDescriptor desc, Location? location) =>
_reportDiagnostic(Diagnostic.Create(desc, location, Array.Empty<object?>()));
- }
- private void Diag(DiagnosticDescriptor desc, Location? location, params object?[]? messageArgs)
- {
+ private void Diag(DiagnosticDescriptor desc, Location? location, params object?[]? messageArgs) =>
_reportDiagnostic(Diagnostic.Create(desc, location, messageArgs));
- }
}
}
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Emit;
+using Microsoft.DotNet.RemoteExecutor;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Options.Generators;
using SourceGenerators.Tests;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
+using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+ public void ProduceDiagnosticFromOtherAssemblyTest()
+ {
+ string source = """
+ using System.ComponentModel.DataAnnotations;
+
+ #nullable enable
+
+ namespace AnotherAssembly;
+
+ public class ClassInAnotherAssembly
+ {
+ [Required]
+ public string? Foo { get; set; }
+
+ // line below causes the generator to emit a warning "SYSLIB1212" but the original location is outside of its compilation (SyntaxTree).
+ // The generator should emit this diagnostics pointing at the closest location of the failure inside the compilation.
+ public SecondClassInAnotherAssembly? TransitiveProperty { get; set; }
+ }
+
+ public class SecondClassInAnotherAssembly
+ {
+ [Required]
+ public string? Bar { get; set; }
+ }
+ """;
+
+ string assemblyName = Path.GetRandomFileName();
+ string assemblyPath = Path.Combine(Path.GetTempPath(), assemblyName + ".dll");
+
+ var compilation = CSharpCompilation
+ .Create(assemblyName, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
+ .AddReferences(MetadataReference.CreateFromFile(AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == "System.Runtime").Location))
+ .AddReferences(MetadataReference.CreateFromFile(typeof(string).Assembly.Location))
+ .AddReferences(MetadataReference.CreateFromFile(typeof(RequiredAttribute).Assembly.Location))
+ .AddReferences(MetadataReference.CreateFromFile(typeof(OptionsValidatorAttribute).Assembly.Location))
+ .AddReferences(MetadataReference.CreateFromFile(typeof(IValidateOptions<object>).Assembly.Location))
+ .AddSyntaxTrees(CSharpSyntaxTree.ParseText(source));
+
+ EmitResult emitResult = compilation.Emit(assemblyPath);
+ Assert.True(emitResult.Success);
+
+ RemoteExecutor.Invoke(async (assemblyFullPath) => {
+ string source1 = """
+ using Microsoft.Extensions.Options;
+
+ namespace MyAssembly;
+
+ [OptionsValidator]
+ public partial class MyOptionsValidator : IValidateOptions<MyOptions>
+ {
+ }
+
+ public class MyOptions
+ {
+ [ValidateObjectMembers]
+ public AnotherAssembly.ClassInAnotherAssembly? TransitiveProperty { get; set; }
+ }
+ """;
+
+ Assembly assembly = Assembly.LoadFrom(assemblyFullPath);
+
+ var (diagnostics, generatedSources) = await RoslynTestUtils.RunGenerator(
+ new Generator(),
+ new[]
+ {
+ assembly,
+ Assembly.GetAssembly(typeof(RequiredAttribute)),
+ Assembly.GetAssembly(typeof(OptionsValidatorAttribute)),
+ Assembly.GetAssembly(typeof(IValidateOptions<object>)),
+ },
+ new List<string> { source1 })
+ .ConfigureAwait(false);
+
+ _ = Assert.Single(generatedSources);
+ var diag = Assert.Single(diagnostics);
+ Assert.Equal(DiagDescriptors.PotentiallyMissingTransitiveValidation.Id, diag.Id);
+
+ // validate the location is inside the MyOptions class and not outside the compilation which is in the referenced assembly
+ Assert.StartsWith("src-0.cs: (12,", diag.Location.GetLineSpan().ToString());
+ }, assemblyPath).Dispose();
+
+ File.Delete(assemblyPath); // cleanup
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
public async Task CantValidateOpenGenericMembersInEnumeration()
{
var (diagnostics, _) = await RunGenerator(@"