Skip to content

Commit

Permalink
Add support for suppressing members (#4479)
Browse files Browse the repository at this point in the history
Fixes #4446
  • Loading branch information
JoshLove-msft authored Sep 19, 2024
1 parent ef37ead commit da0fbc2
Show file tree
Hide file tree
Showing 12 changed files with 459 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace Microsoft.Generator.CSharp
[ExportMetadata("PluginName", nameof(CodeModelPlugin))]
public abstract class CodeModelPlugin
{
private List<LibraryVisitor> _visitors = new();
private List<LibraryVisitor> _visitors = [new MemberRemoverVisitor()];
private static CodeModelPlugin? _instance;
internal static CodeModelPlugin Instance
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Generator.CSharp.Primitives;
using Microsoft.Generator.CSharp.Providers;
using Microsoft.Generator.CSharp.SourceInput;

namespace Microsoft.Generator.CSharp
{
internal class MemberRemoverVisitor : LibraryVisitor
{
protected override MethodProvider? Visit(MethodProvider methodProvider)
{
foreach (var attribute in GetMemberSuppressionAttributes(methodProvider.EnclosingType))
{
if (ShouldRemove(methodProvider.EnclosingType, methodProvider.Signature, attribute))
{
return null;
}
}

return methodProvider;
}

protected override ConstructorProvider? Visit(ConstructorProvider constructorProvider)
{
foreach (var attribute in GetMemberSuppressionAttributes(constructorProvider.EnclosingType))
{
if (ShouldRemove(constructorProvider.EnclosingType, constructorProvider.Signature, attribute))
{
return null;
}
}

return constructorProvider;
}

protected override PropertyProvider? Visit(PropertyProvider propertyProvider)
{
foreach (var attribute in GetMemberSuppressionAttributes(propertyProvider.EnclosingType))
{
if (ShouldRemove(propertyProvider, attribute))
{
return null;
}
}

return propertyProvider;
}

private static IEnumerable<AttributeData> GetMemberSuppressionAttributes(TypeProvider typeProvider)
=> typeProvider.CustomCodeView?.GetAttributes()?.Where(a => a.AttributeClass?.Name == CodeGenAttributes.CodeGenSuppressAttributeName) ?? [];

private static bool ShouldRemove(TypeProvider enclosingType, MethodSignatureBase signature, AttributeData attribute)
{
ValidateArguments(enclosingType, attribute);
var name = attribute.ConstructorArguments[0].Value as string;
if (name != signature.Name)
{
return false;
}

ISymbol?[]? parameterTypes;
if (attribute.ConstructorArguments.Length == 1)
{
parameterTypes = [];
}
else if (attribute.ConstructorArguments[1].Kind != TypedConstantKind.Array)
{
parameterTypes = [(ISymbol?) attribute.ConstructorArguments[1].Value];
}
else
{
parameterTypes = attribute.ConstructorArguments[1].Values.Select(v => (ISymbol?)v.Value).ToArray();
}
if (parameterTypes.Length != signature.Parameters.Count)
{
return false;
}

for (int i = 0; i < parameterTypes.Length; i++)
{
if (parameterTypes[i]?.Name != signature.Parameters[i].Type.Name)
{
return false;
}
}

return true;
}

private static bool ShouldRemove(PropertyProvider propertyProvider, AttributeData attribute)
{
ValidateArguments(propertyProvider.EnclosingType, attribute);
var name = attribute.ConstructorArguments[0].Value as string;
return name == propertyProvider.Name;
}

private static void ValidateArguments(TypeProvider type, AttributeData attributeData)
{
var arguments = attributeData.ConstructorArguments;
if (arguments.Length == 0)
{
throw new InvalidOperationException($"CodeGenSuppress attribute on {type.Name} must specify a method, constructor, or property name as its first argument.");
}

if (arguments[0].Kind != TypedConstantKind.Primitive || arguments[0].Value is not string)
{
var attribute = GetText(attributeData.ApplicationSyntaxReference);
throw new InvalidOperationException($"{attribute} attribute on {type.Name} must specify a method, constructor, or property name as its first argument.");
}

if (arguments.Length == 2 && arguments[1].Kind == TypedConstantKind.Array)
{
ValidateTypeArguments(type, attributeData, arguments[1].Values);
}
else
{
ValidateTypeArguments(type, attributeData, arguments.Skip(1));
}
}

private static void ValidateTypeArguments(TypeProvider type, AttributeData attributeData, IEnumerable<TypedConstant> arguments)
{
foreach (var argument in arguments)
{
if (argument.Kind == TypedConstantKind.Type)
{
if (argument.Value is IErrorTypeSymbol errorType)
{
var attribute = GetText(attributeData.ApplicationSyntaxReference);
var fileLinePosition = GetFileLinePosition(attributeData.ApplicationSyntaxReference);
var filePath = fileLinePosition.Path;
var line = fileLinePosition.StartLinePosition.Line + 1;
throw new InvalidOperationException($"The undefined type '{errorType.Name}' is referenced in the '{attribute}' attribute ({filePath}, line: {line}). Please define this type or remove it from the attribute.");
}
}
else
{
var attribute = GetText(attributeData.ApplicationSyntaxReference);
throw new InvalidOperationException($"Argument '{argument.ToCSharpString()}' in attribute '{attribute}' applied to '{type.Name}' must be a type.");
}
}
}

private static string GetText(SyntaxReference? syntaxReference)
=> syntaxReference?.SyntaxTree.GetText().ToString(syntaxReference.Span) ?? string.Empty;

private static FileLinePositionSpan GetFileLinePosition(SyntaxReference? syntaxReference)
=> syntaxReference?.SyntaxTree.GetLocation(syntaxReference.Span).GetLineSpan() ?? default;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ protected override MethodProvider[] BuildMethods()

protected override CSharpType BuildEnumUnderlyingType() => GetIsEnum() ? new CSharpType(typeof(int)) : throw new InvalidOperationException("This type is not an enum");

internal override IEnumerable<AttributeData> GetAttributes() => _namedTypeSymbol.GetAttributes();

private ParameterProvider ConvertToParameterProvider(IMethodSymbol methodSymbol, IParameterSymbol parameterSymbol)
{
return new ParameterProvider(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.Generator.CSharp.Expressions;
using Microsoft.Generator.CSharp.Primitives;
using Microsoft.Generator.CSharp.Statements;
Expand All @@ -24,6 +25,8 @@ protected TypeProvider()

public TypeProvider? CustomCodeView => _customCodeView.Value;

internal virtual IEnumerable<AttributeData>? GetAttributes() => null;

protected string? _deprecated;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ public void CanAddVisitors()
{
var plugin = new TestPlugin();
plugin.AddVisitor(new TestLibraryVisitor());
Assert.AreEqual(1, plugin.Visitors.Count);
// There is 1 default visitor added by the generator.
Assert.AreEqual(2, plugin.Visitors.Count);
}
}
}
Loading

0 comments on commit da0fbc2

Please sign in to comment.