Skip to content

Commit a520f95

Browse files
committed
add AttributeDeclarationOptions.OmitInaccessibleMembersInNullStateAttribute
1 parent aa7ee65 commit a520f95

File tree

4 files changed

+197
-1
lines changed

4 files changed

+197
-1
lines changed

src/Smdn.Reflection.ReverseGenerating/Smdn.Reflection.ReverseGenerating/Generator.Attributes.cs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,15 +242,63 @@ string ConvertAttributeName(CustomAttributeData attr)
242242

243243
IEnumerable<string> ConvertAttributeArguments(CustomAttributeData attr)
244244
{
245+
var omitInaccessibleMembersInNullStateAttribute =
246+
options.AttributeDeclaration.OmitInaccessibleMembersInNullStateAttribute &&
247+
IsMemberNullStateAttributeType(attr.GetAttributeType());
248+
245249
foreach (var param in attr.Constructor.GetParameters()) {
246250
var arg = attr.ConstructorArguments[param.Position];
247-
var convertedConstructorArgument = ConvertAttributeTypedArgument(arg);
251+
252+
if (
253+
omitInaccessibleMembersInNullStateAttribute &&
254+
string.Equals(param.Name, "member", StringComparison.Ordinal) &&
255+
arg.Value is string memberName &&
256+
!IsAccessibleMemberName(attributeProvider, memberName)
257+
) {
258+
// skip MemberNotNull(When)Attribute for non-public member
259+
continue;
260+
}
261+
262+
string convertedConstructorArgument;
263+
264+
if (
265+
omitInaccessibleMembersInNullStateAttribute &&
266+
string.Equals(param.Name, "members", StringComparison.Ordinal) &&
267+
arg.Value is IEnumerable<CustomAttributeTypedArgument> members
268+
) {
269+
// exclude non-public members from MemberNotNull(When)Attribute
270+
var filteredArgs = members
271+
.Where(member =>
272+
member.Value is string memberName &&
273+
IsAccessibleMemberName(attributeProvider, memberName)
274+
)
275+
.Select(static member => (string)member.Value!)
276+
.ToArray();
277+
278+
if (filteredArgs.Length == 0)
279+
// skip empty MemberNotNull(When)Attribute
280+
continue;
281+
282+
convertedConstructorArgument = CSharpFormatter.FormatValueDeclaration(
283+
val: filteredArgs,
284+
typeOfValue: filteredArgs.GetType(),
285+
options: CSharpFormatter.ValueFormatOptions.FromGeneratorOptions(
286+
options: options,
287+
tryFindConstantField: false
288+
)
289+
);
290+
}
291+
else {
292+
convertedConstructorArgument = ConvertAttributeTypedArgument(arg);
293+
}
248294

249295
yield return options.AttributeDeclaration.WithNamedArguments
250296
? string.Concat(param.Name, ": ", convertedConstructorArgument)
251297
: convertedConstructorArgument;
252298
}
253299

300+
// since the MemberNotNull(When)Attribute does not have writable fields,
301+
// there is no need to consider attribute's named arguments
254302
foreach (var namedArg in attr.NamedArguments) {
255303
yield return string.Concat(
256304
namedArg.MemberName,
@@ -269,6 +317,37 @@ string ConvertAttributeTypedArgument(CustomAttributeTypedArgument arg)
269317
tryFindConstantField: false
270318
)
271319
);
320+
321+
static bool IsMemberNullStateAttributeType(Type attributeType)
322+
{
323+
if (!string.Equals(attributeType.Namespace, "System.Diagnostics.CodeAnalysis", StringComparison.Ordinal))
324+
return false;
325+
326+
return attributeType.Name switch {
327+
"MemberNotNullAttribute" => true,
328+
"MemberNotNullWhenAttribute" => true,
329+
_ => false,
330+
};
331+
}
332+
333+
static bool IsAccessibleMemberName(ICustomAttributeProvider attributeProvider, string memberName)
334+
=>
335+
attributeProvider is MemberInfo attributeProvidingMember &&
336+
attributeProvidingMember.DeclaringType is Type declaringType &&
337+
declaringType
338+
.GetMember(
339+
name: memberName,
340+
bindingAttr: BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
341+
)
342+
.FirstOrDefault(static member => {
343+
var isPrivateOrAssembly = member switch {
344+
PropertyInfo property => property.GetMethod?.IsPrivateOrAssembly() ?? true /* if there's no accessor, consider to be inaccessible */,
345+
FieldInfo field => field.IsPrivateOrAssembly(),
346+
_ => true, // unexpected kind of member; consider to be inaccessible
347+
};
348+
return !isPrivateOrAssembly;
349+
})
350+
is not null;
272351
}
273352
#pragma warning restore CA1502, CA1506
274353
}

src/Smdn.Reflection.ReverseGenerating/Smdn.Reflection.ReverseGenerating/GeneratorOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public class AttributeDeclarationOptions {
7070
public bool WithDeclaringTypeName { get; set; } = false;
7171
public bool WithNamedArguments { get; set; } = false;
7272
public bool OmitAttributeSuffix { get; set; } = true;
73+
public bool OmitInaccessibleMembersInNullStateAttribute { get; set; } = true;
7374
public AttributeTypeFilter? TypeFilter { get; set; } = null;
7475
public AttributeSectionFormat AccessorFormat { get; set; } = AttributeSectionFormat.List;
7576
public AttributeSectionFormat AccessorParameterFormat { get; set; } = AttributeSectionFormat.List;

tests/Smdn.Reflection.ReverseGenerating/Smdn.Reflection.ReverseGenerating.GeneratorTestCases/AttributeList.GeneratorOptions.cs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
#pragma warning disable CS8597
44

55
using System;
6+
#if NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES
7+
using System.Diagnostics.CodeAnalysis;
8+
#endif
69

710
namespace Smdn.Reflection.ReverseGenerating.GeneratorTestCases.AttributeList.GeneratorOptions {
811
namespace WithNamespace {
@@ -62,4 +65,115 @@ public class GenericAttributeNameAttribute<T> : Attribute { }
6265
[GenericAttributeName<int>] public class GenericAttributeName { }
6366
#endif
6467
}
68+
69+
#if NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES
70+
#nullable enable
71+
namespace OmitInaccessibleMembersInNullStateAttribute {
72+
public class NullStateAttribute {
73+
#pragma warning disable CS0169
74+
private string? F1;
75+
protected string? F2;
76+
public string? F3;
77+
#pragma warning restore CS0169
78+
79+
private string? P1 { get; }
80+
protected string? P2 { get; }
81+
public string? P3 { get; }
82+
83+
#pragma warning disable CS8774
84+
[MemberNotNull(nameof(F1))]
85+
[AttributeListTestCase(@"[MemberNotNull(""F1"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
86+
[AttributeListTestCase(@"[MemberNotNull]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
87+
public void MemberNotNullWithNonPublicField() { }
88+
89+
[MemberNotNull(nameof(F2))]
90+
[AttributeListTestCase(@"[MemberNotNull(""F2"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
91+
[AttributeListTestCase(@"[MemberNotNull(""F2"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
92+
public void MemberNotNullWithPublicField_Protected() { }
93+
94+
[MemberNotNull(nameof(F3))]
95+
[AttributeListTestCase(@"[MemberNotNull(""F3"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
96+
[AttributeListTestCase(@"[MemberNotNull(""F3"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
97+
public void MemberNotNullWithPublicField() { }
98+
99+
[MemberNotNull(nameof(P1))]
100+
[AttributeListTestCase(@"[MemberNotNull(""P1"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
101+
[AttributeListTestCase(@"[MemberNotNull]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
102+
public void MemberNotNullWithNonPublicProperty() { }
103+
104+
[MemberNotNull(nameof(P2))]
105+
[AttributeListTestCase(@"[MemberNotNull(""P2"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
106+
[AttributeListTestCase(@"[MemberNotNull(""P2"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
107+
public void MemberNotNullWithPublicProperty_Protected() { }
108+
109+
[MemberNotNull(nameof(P3))]
110+
[AttributeListTestCase(@"[MemberNotNull(""P3"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
111+
[AttributeListTestCase(@"[MemberNotNull(""P3"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
112+
public void MemberNotNullWithPublicProperty() { }
113+
114+
[MemberNotNull(nameof(F1), nameof(F2), nameof(F3), nameof(P1), nameof(P2), nameof(P3))]
115+
[AttributeListTestCase(@"[MemberNotNull(new string[] { ""F1"", ""F2"", ""F3"", ""P1"", ""P2"", ""P3"" })]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
116+
[AttributeListTestCase(@"[MemberNotNull(new string[] { ""F2"", ""F3"", ""P2"", ""P3"" })]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
117+
public void MemberNotNullWithMembers() { }
118+
119+
[MemberNotNull(nameof(F1), nameof(P1))]
120+
[AttributeListTestCase(@"[MemberNotNull(new string[] { ""F1"", ""P1"" })]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
121+
[AttributeListTestCase(@"[MemberNotNull]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
122+
public bool MemberNotNullWithMembersAllNonPublic()
123+
=> true;
124+
#pragma warning restore CS8774
125+
126+
#pragma warning disable CS8775
127+
[MemberNotNullWhen(true, nameof(F1))]
128+
[AttributeListTestCase(@"[MemberNotNullWhen(true, ""F1"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
129+
[AttributeListTestCase(@"[MemberNotNullWhen(true)]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
130+
public bool MemberNotNullWhenWithNonPublicField()
131+
=> true;
132+
133+
[MemberNotNullWhen(true, nameof(F2))]
134+
[AttributeListTestCase(@"[MemberNotNullWhen(true, ""F2"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
135+
[AttributeListTestCase(@"[MemberNotNullWhen(true, ""F2"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
136+
public bool MemberNotNullWhenWithPublicField_Protected()
137+
=> true;
138+
139+
[MemberNotNullWhen(true, nameof(F3))]
140+
[AttributeListTestCase(@"[MemberNotNullWhen(true, ""F3"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
141+
[AttributeListTestCase(@"[MemberNotNullWhen(true, ""F3"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
142+
public bool MemberNotNullWhenWithPublicField()
143+
=> true;
144+
145+
[MemberNotNullWhen(true, nameof(P1))]
146+
[AttributeListTestCase(@"[MemberNotNullWhen(true, ""P1"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
147+
[AttributeListTestCase(@"[MemberNotNullWhen(true)]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
148+
public bool MemberNotNullWhenWithNonPublicProperty()
149+
=> true;
150+
151+
[MemberNotNullWhen(true, nameof(P2))]
152+
[AttributeListTestCase(@"[MemberNotNullWhen(true, ""P2"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
153+
[AttributeListTestCase(@"[MemberNotNullWhen(true, ""P2"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
154+
public bool MemberNotNullWhenWithPublicProperty_Protected()
155+
=> true;
156+
157+
[MemberNotNullWhen(true, nameof(P3))]
158+
[AttributeListTestCase(@"[MemberNotNullWhen(true, ""P3"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
159+
[AttributeListTestCase(@"[MemberNotNullWhen(true, ""P3"")]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
160+
public bool MemberNotNullWhenWithPublicProperty()
161+
=> true;
162+
163+
[MemberNotNullWhen(true, nameof(F1), nameof(F2), nameof(F3), nameof(P1), nameof(P2), nameof(P3))]
164+
[AttributeListTestCase(@"[MemberNotNullWhen(true, new string[] { ""F1"", ""F2"", ""F3"", ""P1"", ""P2"", ""P3"" })]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
165+
[AttributeListTestCase(@"[MemberNotNullWhen(true, new string[] { ""F2"", ""F3"", ""P2"", ""P3"" })]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
166+
public bool MemberNotNullWhenWithMembers()
167+
=> true;
168+
169+
[MemberNotNullWhen(true, nameof(F1), nameof(P1))]
170+
[AttributeListTestCase(@"[MemberNotNullWhen(true, new string[] { ""F1"", ""P1"" })]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = false)]
171+
[AttributeListTestCase(@"[MemberNotNullWhen(true)]", AttributeWithNamespace = false, AttributeOmitInaccessibleMembersInNullStateAttribute = true)]
172+
public bool MemberNotNullWhenWithMembersAllNonPublic()
173+
=> true;
174+
#pragma warning restore CS8775
175+
}
176+
}
177+
#nullable restore
178+
#endif
65179
}

tests/Smdn.Reflection.ReverseGenerating/Smdn.Reflection.ReverseGenerating/Generator.TestCaseAttributes.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ private static GeneratorOptions GetGeneratorOptions(GeneratorTestCaseAttribute t
5252
attributeDeclarationOptions.WithDeclaringTypeName = testCaseAttribute.AttributeWithDeclaringTypeName;
5353
attributeDeclarationOptions.WithNamedArguments = testCaseAttribute.AttributeWithNamedArguments;
5454
attributeDeclarationOptions.OmitAttributeSuffix = testCaseAttribute.AttributeOmitAttributeSuffix;
55+
attributeDeclarationOptions.OmitInaccessibleMembersInNullStateAttribute = testCaseAttribute.AttributeOmitInaccessibleMembersInNullStateAttribute;
5556
attributeDeclarationOptions.AccessorFormat = testCaseAttribute.AttributeAccessorFormat;
5657
attributeDeclarationOptions.AccessorParameterFormat = testCaseAttribute.AttributeAccessorParameterFormat;
5758
attributeDeclarationOptions.BackingFieldFormat = testCaseAttribute.AttributeBackingFieldFormat;
@@ -112,6 +113,7 @@ public abstract class GeneratorTestCaseAttribute : Attribute, ITestCaseAttribute
112113
public bool AttributeWithNamedArguments { get; set; } = false;
113114
public bool AttributeWithDeclaringTypeName { get; set; } = true;
114115
public bool AttributeOmitAttributeSuffix { get; set; } = true;
116+
public bool AttributeOmitInaccessibleMembersInNullStateAttribute { get; set; } = false;
115117
public AttributeSectionFormat AttributeAccessorFormat { get; set; } = AttributeSectionFormat.Discrete;
116118
public AttributeSectionFormat AttributeAccessorParameterFormat { get; set; } = AttributeSectionFormat.Discrete;
117119
public AttributeSectionFormat AttributeBackingFieldFormat { get; set; } = AttributeSectionFormat.Discrete;

0 commit comments

Comments
 (0)