Ejemplo n.º 1
0
        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);
        }