public override void Initialize(AnalysisContext context) { context.RegisterCompilationStartAction(compilationContext => { var compilation = compilationContext.Compilation; var classDataType = compilation.GetTypeByMetadataName(Constants.Types.XunitClassDataAttribute); if (classDataType == null) { return; } var iEnumerableOfObjectArray = TypeSymbolFactory.IEnumerableOfObjectArray(compilation); compilationContext.RegisterSyntaxNodeAction(syntaxNodeContext => { var attribute = syntaxNodeContext.Node as AttributeSyntax; var semanticModel = syntaxNodeContext.SemanticModel; if (semanticModel.GetTypeInfo(attribute).Type != classDataType) { return; } var argumentExpression = attribute.ArgumentList?.Arguments.FirstOrDefault()?.Expression as TypeOfExpressionSyntax; if (argumentExpression == null) { return; } var classType = (INamedTypeSymbol)semanticModel.GetTypeInfo(argumentExpression.Type).Type; if (classType == null || classType.Kind == SymbolKind.ErrorType) { return; } var missingInterface = !iEnumerableOfObjectArray.IsAssignableFrom(classType); var isAbstract = classType.IsAbstract; var noValidConstructor = !classType.InstanceConstructors.Any(c => c.Parameters.IsEmpty && c.DeclaredAccessibility == Accessibility.Public); if (missingInterface || isAbstract || noValidConstructor) { syntaxNodeContext.ReportDiagnostic(Diagnostic.Create( Descriptors.X1007_ClassDataAttributeMustPointAtValidClass, argumentExpression.Type.GetLocation(), classType.Name)); } }, SyntaxKind.Attribute); }); }
private async Task <Solution> FixClass(Solution solution, INamedTypeSymbol typeSymbol, CancellationToken cancellationToken) { var symbolEditor = SymbolEditor.Create(solution); await symbolEditor.EditOneDeclarationAsync(typeSymbol, async (editor, declaration, ct) => { var classDeclaration = (ClassDeclarationSyntax)declaration; var compilation = editor.SemanticModel.Compilation; var generator = editor.Generator; if (typeSymbol.IsAbstract) { editor.SetModifiers(declaration, DeclarationModifiers.From(typeSymbol).WithIsAbstract(false)); } var ctor = typeSymbol.InstanceConstructors.FirstOrDefault(c => c.Parameters.Length == 0); if (ctor == null) { editor.AddMember(classDeclaration, generator.ConstructorDeclaration(accessibility: Accessibility.Public)); } else if (ctor.DeclaredAccessibility != Accessibility.Public) { // Make constructor public unless it's implicit and the class was abstract. Making the class non-abstract will make the implicit constructor public if (!(ctor.IsImplicitlyDeclared && typeSymbol.IsAbstract)) { var ctorSyntaxRef = ctor.DeclaringSyntaxReferences.FirstOrDefault(); editor.SetAccessibility(await ctorSyntaxRef.GetSyntaxAsync(ct).ConfigureAwait(false), Accessibility.Public); } } var iEnumerableOfObjectArray = TypeSymbolFactory.IEnumerableOfObjectArray(compilation); if (!iEnumerableOfObjectArray.IsAssignableFrom(typeSymbol)) { editor.AddInterfaceType(classDeclaration, generator.TypeExpression(iEnumerableOfObjectArray)); } }, cancellationToken).ConfigureAwait(false); return(symbolEditor.ChangedSolution); }
internal override void AnalyzeCompilation(CompilationStartAnalysisContext context, XunitContext xunitContext) { var compilation = context.Compilation; var iEnumerableOfObjectArrayType = TypeSymbolFactory.IEnumerableOfObjectArray(compilation); var supportsNameofOperator = compilation is CSharpCompilation cSharpCompilation && cSharpCompilation.LanguageVersion >= LanguageVersion.CSharp6; context.RegisterSyntaxNodeAction(context => { var attribute = (AttributeSyntax)context.Node; var memberNameArgument = attribute.ArgumentList?.Arguments.FirstOrDefault(); if (memberNameArgument == null) { return; } var semanticModel = context.SemanticModel; if (!Equals(semanticModel.GetTypeInfo(attribute, context.CancellationToken).Type, xunitContext.Core.MemberDataAttributeType)) { return; } var constantValue = semanticModel.GetConstantValue(memberNameArgument.Expression, context.CancellationToken); if (!(constantValue.Value is string memberName)) { return; } var memberTypeArgument = attribute.ArgumentList.Arguments.FirstOrDefault(a => a.NameEquals?.Name.Identifier.ValueText == "MemberType"); ITypeSymbol memberTypeSymbol = null; if (memberTypeArgument?.Expression is TypeOfExpressionSyntax typeofExpression) { var typeSyntax = typeofExpression.Type; memberTypeSymbol = semanticModel.GetTypeInfo(typeSyntax, context.CancellationToken).Type; } var testClassTypeSymbol = semanticModel.GetDeclaredSymbol(attribute.FirstAncestorOrSelf <ClassDeclarationSyntax>()); var declaredMemberTypeSymbol = memberTypeSymbol ?? testClassTypeSymbol; var memberSymbol = FindMemberSymbol(memberName, declaredMemberTypeSymbol); if (memberSymbol == null) { context.ReportDiagnostic( Diagnostic.Create( Descriptors.X1015_MemberDataMustReferenceExistingMember, attribute.GetLocation(), memberName, SymbolDisplay.ToDisplayString(declaredMemberTypeSymbol))); } else { if (memberSymbol.Kind != SymbolKind.Field && memberSymbol.Kind != SymbolKind.Property && memberSymbol.Kind != SymbolKind.Method) { context.ReportDiagnostic( Diagnostic.Create( Descriptors.X1018_MemberDataMustReferenceValidMemberKind, attribute.GetLocation())); } else { if (supportsNameofOperator && memberNameArgument.Expression.IsKind(SyntaxKind.StringLiteralExpression)) { var builder = ImmutableDictionary.CreateBuilder <string, string>(); if (!Equals(memberSymbol.ContainingType, testClassTypeSymbol)) { builder.Add("DeclaringType", memberSymbol.ContainingType.ToDisplayString()); } context.ReportDiagnostic( Diagnostic.Create( Descriptors.X1014_MemberDataShouldUseNameOfOperator, memberNameArgument.Expression.GetLocation(), builder.ToImmutable(), memberName, memberSymbol.ContainingType.ToDisplayString())); } var memberProperties = new Dictionary <string, string> { { "DeclaringType", declaredMemberTypeSymbol.ToDisplayString() }, { "MemberName", memberName } }.ToImmutableDictionary(); if (memberSymbol.DeclaredAccessibility != Accessibility.Public) { context.ReportDiagnostic( Diagnostic.Create( Descriptors.X1016_MemberDataMustReferencePublicMember, attribute.GetLocation(), memberProperties)); } if (!memberSymbol.IsStatic) { context.ReportDiagnostic( Diagnostic.Create( Descriptors.X1017_MemberDataMustReferenceStaticMember, attribute.GetLocation(), memberProperties)); } var memberType = GetMemberType(memberSymbol); if (!iEnumerableOfObjectArrayType.IsAssignableFrom(memberType)) { context.ReportDiagnostic( Diagnostic.Create( Descriptors.X1019_MemberDataMustReferenceMemberOfValidType, attribute.GetLocation(), memberProperties, SymbolDisplay.ToDisplayString(iEnumerableOfObjectArrayType), SymbolDisplay.ToDisplayString(memberType))); } if (memberSymbol.Kind == SymbolKind.Property && ((IPropertySymbol)memberSymbol).GetMethod == null) { context.ReportDiagnostic( Diagnostic.Create( Descriptors.X1020_MemberDataPropertyMustHaveGetter, attribute.GetLocation())); } var extraArguments = attribute.ArgumentList.Arguments.Skip(1).TakeWhile(a => a.NameEquals == null).ToList(); if (memberSymbol.Kind == SymbolKind.Property || memberSymbol.Kind == SymbolKind.Field) { if (extraArguments.Any()) { var span = TextSpan.FromBounds(extraArguments.First().Span.Start, extraArguments.Last().Span.End); context.ReportDiagnostic( Diagnostic.Create( Descriptors.X1021_MemberDataNonMethodShouldNotHaveParameters, Location.Create(attribute.SyntaxTree, span))); } } if (memberSymbol.Kind == SymbolKind.Method) { // TODO: handle method parameter type matching, model after InlineDataMustMatchTheoryParameter } } } }, SyntaxKind.Attribute); }