private void AnalyzeNode(SyntaxNodeAnalysisContext context)
        {
            var cancellationToken = context.CancellationToken;
            var node          = context.Node;
            var semanticModel = context.SemanticModel;

            if (node.Parent is not TMemberAccessExpressionSyntax memberAccessExpression)
            {
                return;
            }

            var simplifierOptions = context.GetAnalyzerOptions().GetSimplifierOptions(Simplification);

            if (!this.Simplifier.ShouldSimplifyThisMemberAccessExpression(
                    memberAccessExpression, semanticModel, simplifierOptions, out var thisExpression, out var severity, cancellationToken))
            {
                return;
            }

            var builder = ImmutableDictionary.CreateBuilder <string, string?>();

            // used so we can provide a link in the preview to the options page. This value is
            // hard-coded there to be the one that will go to the code-style page.
            builder["OptionName"]     = nameof(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration);
            builder["OptionLanguage"] = semanticModel.Language;

            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         Descriptor, thisExpression.GetLocation(), severity,
                                         ImmutableArray.Create(memberAccessExpression.GetLocation()),
                                         builder.ToImmutable()));
        }
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var option = context.GetAnalyzerOptions().PreferIsNullCheckOverReferenceEqualityMethod;

            if (!option.Value)
            {
                return;
            }

            var binaryExpression = (BinaryExpressionSyntax)context.Node;
            var semanticModel    = context.SemanticModel;

            if (!IsObjectCastAndNullCheck(semanticModel, binaryExpression.Left, binaryExpression.Right) &&
                !IsObjectCastAndNullCheck(semanticModel, binaryExpression.Right, binaryExpression.Left))
            {
                return;
            }

            var severity   = option.Notification.Severity;
            var properties = binaryExpression.Kind() == SyntaxKind.EqualsExpression
                ? s_properties
                : s_NegatedProperties;

            context.ReportDiagnostic(
                DiagnosticHelper.Create(
                    Descriptor, binaryExpression.GetLocation(), severity, additionalLocations: null, properties));
        }
Exemple #3
0
        private void ReportDiagnosticsIfNeeded(NameEqualsSyntax nameEquals, SyntaxNodeAnalysisContext context)
        {
            if (!nameEquals.Parent.IsKind(SyntaxKind.AnonymousObjectMemberDeclarator, out AnonymousObjectMemberDeclaratorSyntax? anonCtor))
            {
                return;
            }

            var preference = context.GetAnalyzerOptions().PreferInferredAnonymousTypeMemberNames;

            if (!preference.Value ||
                !CSharpInferredMemberNameSimplifier.CanSimplifyAnonymousTypeMemberName(anonCtor))
            {
                return;
            }

            // Create a normal diagnostic
            var fadeSpan = TextSpan.FromBounds(nameEquals.Name.SpanStart, nameEquals.EqualsToken.Span.End);

            context.ReportDiagnostic(
                DiagnosticHelper.CreateWithLocationTags(
                    Descriptor,
                    nameEquals.GetLocation(),
                    preference.Notification.Severity,
                    additionalLocations: ImmutableArray <Location> .Empty,
                    additionalUnnecessaryLocations: ImmutableArray.Create(context.Node.SyntaxTree.GetLocation(fadeSpan))));
        }
Exemple #4
0
        private void ReportDiagnosticsIfNeeded(NameColonSyntax nameColon, SyntaxNodeAnalysisContext context)
        {
            if (!nameColon.Parent.IsKind(SyntaxKind.Argument, out ArgumentSyntax? argument))
            {
                return;
            }

            var syntaxTree   = context.Node.SyntaxTree;
            var parseOptions = (CSharpParseOptions)syntaxTree.Options;
            var preference   = context.GetAnalyzerOptions().PreferInferredTupleNames;

            if (!preference.Value ||
                !CSharpInferredMemberNameSimplifier.CanSimplifyTupleElementName(argument, parseOptions))
            {
                return;
            }

            // Create a normal diagnostic
            var fadeSpan = TextSpan.FromBounds(nameColon.Name.SpanStart, nameColon.ColonToken.Span.End);

            context.ReportDiagnostic(
                DiagnosticHelper.CreateWithLocationTags(
                    Descriptor,
                    nameColon.GetLocation(),
                    preference.Notification.Severity,
                    additionalLocations: ImmutableArray <Location> .Empty,
                    additionalUnnecessaryLocations: ImmutableArray.Create(syntaxTree.GetLocation(fadeSpan))));
        }
        private void AnalyzeCoalesceExpression(SyntaxNodeAnalysisContext context)
        {
            var cancellationToken = context.CancellationToken;
            var semanticModel     = context.SemanticModel;

            var coalesceExpression = (BinaryExpressionSyntax)context.Node;

            var option = context.GetAnalyzerOptions().PreferCompoundAssignment;

            // Bail immediately if the user has disabled this feature.
            if (!option.Value)
            {
                return;
            }

            var coalesceLeft  = coalesceExpression.Left;
            var coalesceRight = coalesceExpression.Right;

            if (coalesceRight is not ParenthesizedExpressionSyntax parenthesizedExpr)
            {
                return;
            }

            if (parenthesizedExpr.Expression is not AssignmentExpressionSyntax assignment)
            {
                return;
            }

            if (assignment.Kind() != SyntaxKind.SimpleAssignmentExpression)
            {
                return;
            }

            // have    x ?? (y = z)
            // ensure that 'x' and 'y' are suitably equivalent.
            var syntaxFacts = CSharpSyntaxFacts.Instance;

            if (!syntaxFacts.AreEquivalent(coalesceLeft, assignment.Left))
            {
                return;
            }

            // Syntactically looks promising.  But we can only safely do this if 'expr'
            // is side-effect-free since we will be changing the number of times it is
            // executed from twice to once.
            if (!UseCompoundAssignmentUtilities.IsSideEffectFree(
                    syntaxFacts, coalesceLeft, semanticModel, cancellationToken))
            {
                return;
            }

            // Good match.
            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         Descriptor,
                                         coalesceExpression.OperatorToken.GetLocation(),
                                         option.Notification.Severity,
                                         additionalLocations: ImmutableArray.Create(coalesceExpression.GetLocation()),
                                         properties: null));
        }
Exemple #6
0
        private void AnalyzeNode(SyntaxNodeAnalysisContext context, INamedTypeSymbol ienumerableType)
        {
            var semanticModel            = context.SemanticModel;
            var objectCreationExpression = (TObjectCreationExpressionSyntax)context.Node;
            var language          = objectCreationExpression.Language;
            var cancellationToken = context.CancellationToken;

            var option = context.GetAnalyzerOptions().PreferCollectionInitializer;

            if (!option.Value)
            {
                // not point in analyzing if the option is off.
                return;
            }

            // Object creation can only be converted to collection initializer if it
            // implements the IEnumerable type.
            var objectType = context.SemanticModel.GetTypeInfo(objectCreationExpression, cancellationToken);

            if (objectType.Type == null || !objectType.Type.AllInterfaces.Contains(ienumerableType))
            {
                return;
            }

            var matches = UseCollectionInitializerAnalyzer <TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TInvocationExpressionSyntax, TExpressionStatementSyntax, TVariableDeclaratorSyntax> .Analyze(
                semanticModel, GetSyntaxFacts(), objectCreationExpression, cancellationToken);

            if (matches == null || matches.Value.Length == 0)
            {
                return;
            }

            var containingStatement = objectCreationExpression.FirstAncestorOrSelf <TStatementSyntax>();

            if (containingStatement == null)
            {
                return;
            }

            var nodes       = ImmutableArray.Create <SyntaxNode>(containingStatement).AddRange(matches.Value);
            var syntaxFacts = GetSyntaxFacts();

            if (syntaxFacts.ContainsInterleavedDirective(nodes, cancellationToken))
            {
                return;
            }

            var locations = ImmutableArray.Create(objectCreationExpression.GetLocation());

            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         s_descriptor,
                                         objectCreationExpression.GetFirstToken().GetLocation(),
                                         option.Notification.Severity,
                                         additionalLocations: locations,
                                         properties: null));

            FadeOutCode(context, matches.Value, locations);
        }
        protected void AnalyzeSyntax(
            SyntaxNodeAnalysisContext context, INamedTypeSymbol ienumerableType, INamedTypeSymbol ienumerableOfTType)
        {
            var semanticModel     = context.SemanticModel;
            var cancellationToken = context.CancellationToken;

            if (context.Node is not TForEachStatementSyntax node)
            {
                return;
            }

            var option = context.GetAnalyzerOptions().ForEachExplicitCastInSource;

            Contract.ThrowIfFalse(option.Value is ForEachExplicitCastInSourcePreference.Always or ForEachExplicitCastInSourcePreference.WhenStronglyTyped);

            if (semanticModel.GetOperation(node, cancellationToken) is not IForEachLoopOperation loopOperation)
            {
                return;
            }

            if (loopOperation.LoopControlVariable is not IVariableDeclaratorOperation variableDeclarator ||
                variableDeclarator.Symbol.Type is not ITypeSymbol iterationType)
            {
                return;
            }

            var syntaxFacts    = this.SyntaxFacts;
            var collectionType = semanticModel.GetTypeInfo(syntaxFacts.GetExpressionOfForeachStatement(node), cancellationToken).Type;

            if (collectionType is null)
            {
                return;
            }

            var(conversion, collectionElementType) = GetForEachInfo(semanticModel, node);

            // Don't bother checking conversions that are problematic for other reasons.  The user will already have a
            // compiler error telling them the foreach is in error.
            if (!conversion.Exists)
            {
                return;
            }

            // If the conversion was implicit, then everything is ok.  Implicit conversions are safe and do not throw at runtime.
            if (conversion.IsImplicit)
            {
                return;
            }

            // An implicit legal conversion still shows up as explicit conversion in the object model.  But this is fine
            // to keep as is since being an implicit-conversion means the API indicates it should always be safe to
            // happen at runtime.
            if (conversion.IsUserDefined && conversion.MethodSymbol is { Name : WellKnownMemberNames.ImplicitConversionName })
        private void AnalyzeNode(SyntaxNodeAnalysisContext context)
        {
            var objectCreationExpression = (TObjectCreationExpressionSyntax)context.Node;
            var language = objectCreationExpression.Language;
            var option   = context.GetAnalyzerOptions().PreferObjectInitializer;

            if (!option.Value)
            {
                // not point in analyzing if the option is off.
                return;
            }

            var syntaxFacts = GetSyntaxFacts();
            var matches     = UseNamedMemberInitializerAnalyzer <TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TAssignmentStatementSyntax, TVariableDeclaratorSyntax> .Analyze(
                context.SemanticModel, syntaxFacts, objectCreationExpression, context.CancellationToken);

            if (matches == null || matches.Value.Length == 0)
            {
                return;
            }

            var containingStatement = objectCreationExpression.FirstAncestorOrSelf <TStatementSyntax>();

            if (containingStatement == null)
            {
                return;
            }

            if (!IsValidContainingStatement(containingStatement))
            {
                return;
            }

            var nodes = ImmutableArray.Create <SyntaxNode>(containingStatement).AddRange(matches.Value.Select(m => m.Statement));

            if (syntaxFacts.ContainsInterleavedDirective(nodes, context.CancellationToken))
            {
                return;
            }

            var locations = ImmutableArray.Create(objectCreationExpression.GetLocation());

            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         Descriptor,
                                         objectCreationExpression.GetFirstToken().GetLocation(),
                                         option.Notification.Severity,
                                         locations,
                                         properties: null));

            FadeOutCode(context, matches.Value, locations);
        }
        protected void AnalyzeNode(SyntaxNodeAnalysisContext context)
        {
            var options = context.GetAnalyzerOptions();

            // if the user never prefers this style, do not analyze at all.
            // we don't know the context of the node yet, so check all predefined type option preferences and bail early.
            if (!IsFrameworkTypePreferred(options.PreferPredefinedTypeKeywordInDeclaration) &&
                !IsFrameworkTypePreferred(options.PreferPredefinedTypeKeywordInMemberAccess))
            {
                return;
            }

            var predefinedTypeNode = (TPredefinedTypeSyntax)context.Node;

            // check if the predefined type is replaceable with an equivalent framework type.
            if (!IsPredefinedTypeReplaceableWithFrameworkType(predefinedTypeNode))
            {
                return;
            }

            // check we have a symbol so that the fixer can generate the right type syntax from it.
            if (context.SemanticModel.GetSymbolInfo(predefinedTypeNode, context.CancellationToken).Symbol is not ITypeSymbol)
            {
                return;
            }

            // earlier we did a context insensitive check to see if this style was preferred in *any* context at all.
            // now, we have to make a context sensitive check to see if options settings for our context requires us to report a diagnostic.
            var optionValue = IsInMemberAccessOrCrefReferenceContext(predefinedTypeNode) ?
                              options.PreferPredefinedTypeKeywordInMemberAccess :
                              options.PreferPredefinedTypeKeywordInDeclaration;

            if (IsFrameworkTypePreferred(optionValue))
            {
                context.ReportDiagnostic(DiagnosticHelper.Create(
                                             Descriptor, predefinedTypeNode.GetLocation(),
                                             optionValue.Notification.Severity, additionalLocations: null,
                                             PreferFrameworkTypeConstants.Properties));
            }
        private void AnalyzeSyntax(
            SyntaxNodeAnalysisContext context,
            INamedTypeSymbol?expressionTypeOpt,
            IMethodSymbol?referenceEqualsMethodOpt)
        {
            var conditionalExpression = (TConditionalExpressionSyntax)context.Node;

            var option = context.GetAnalyzerOptions().PreferNullPropagation;

            if (!option.Value)
            {
                return;
            }

            var syntaxFacts = GetSyntaxFacts();

            syntaxFacts.GetPartsOfConditionalExpression(
                conditionalExpression, out var conditionNode, out var whenTrueNode, out var whenFalseNode);

            conditionNode = syntaxFacts.WalkDownParentheses(conditionNode);
            whenTrueNode  = syntaxFacts.WalkDownParentheses(whenTrueNode);
            whenFalseNode = syntaxFacts.WalkDownParentheses(whenFalseNode);

            var conditionIsNegated = false;

            if (syntaxFacts.IsLogicalNotExpression(conditionNode))
            {
                conditionIsNegated = true;
                conditionNode      = syntaxFacts.WalkDownParentheses(
                    syntaxFacts.GetOperandOfPrefixUnaryExpression(conditionNode));
            }

            if (!TryAnalyzeCondition(
                    context, syntaxFacts, referenceEqualsMethodOpt, conditionNode,
                    out var conditionPartToCheck, out var isEquals))
            {
                return;
            }

            if (conditionIsNegated)
            {
                isEquals = !isEquals;
            }

            // Needs to be of the form:
            //      x == null ? null : ...    or
            //      x != null ? ...  : null;
            if (isEquals && !syntaxFacts.IsNullLiteralExpression(whenTrueNode))
            {
                return;
            }

            if (!isEquals && !syntaxFacts.IsNullLiteralExpression(whenFalseNode))
            {
                return;
            }

            var whenPartToCheck = isEquals ? whenFalseNode : whenTrueNode;

            var semanticModel = context.SemanticModel;
            var whenPartMatch = GetWhenPartMatch(syntaxFacts, semanticModel, conditionPartToCheck, whenPartToCheck);

            if (whenPartMatch == null)
            {
                return;
            }

            // ?. is not available in expression-trees.  Disallow the fix in that case.

            var type = semanticModel.GetTypeInfo(conditionalExpression).Type;

            if (type?.IsValueType == true)
            {
                if (type is not INamedTypeSymbol namedType || namedType.ConstructedFrom.SpecialType != SpecialType.System_Nullable_T)
                {
                    // User has something like:  If(str is nothing, nothing, str.Length)
                    // In this case, converting to str?.Length changes the type of this from
                    // int to int?
                    return;
                }
                // But for a nullable type, such as  If(c is nothing, nothing, c.nullable)
                // converting to c?.nullable doesn't affect the type
            }

            if (IsInExpressionTree(semanticModel, conditionNode, expressionTypeOpt, context.CancellationToken))
            {
                return;
            }

            var locations = ImmutableArray.Create(
                conditionalExpression.GetLocation(),
                conditionPartToCheck.GetLocation(),
                whenPartToCheck.GetLocation());

            var properties         = ImmutableDictionary <string, string?> .Empty;
            var whenPartIsNullable = semanticModel.GetTypeInfo(whenPartMatch).Type?.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T;

            if (whenPartIsNullable)
            {
                properties = properties.Add(UseNullPropagationConstants.WhenPartIsNullable, "");
            }

            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         Descriptor,
                                         conditionalExpression.GetLocation(),
                                         option.Notification.Severity,
                                         locations,
                                         properties));
        }
Exemple #11
0
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var conditionalExpression = (TConditionalExpressionSyntax)context.Node;

            var cancellationToken = context.CancellationToken;

            var option = context.GetAnalyzerOptions().PreferCoalesceExpression;

            if (!option.Value)
            {
                return;
            }

            var syntaxFacts = GetSyntaxFacts();

            syntaxFacts.GetPartsOfConditionalExpression(
                conditionalExpression, out var conditionNode, out var whenTrueNodeHigh, out var whenFalseNodeHigh);

            conditionNode = syntaxFacts.WalkDownParentheses(conditionNode);
            var whenTrueNodeLow  = syntaxFacts.WalkDownParentheses(whenTrueNodeHigh);
            var whenFalseNodeLow = syntaxFacts.WalkDownParentheses(whenFalseNodeHigh);

            var notHasValueExpression = false;

            if (syntaxFacts.IsLogicalNotExpression(conditionNode))
            {
                notHasValueExpression = true;
                conditionNode         = syntaxFacts.GetOperandOfPrefixUnaryExpression(conditionNode);
            }

            if (conditionNode is not TMemberAccessExpression conditionMemberAccess)
            {
                return;
            }

            syntaxFacts.GetPartsOfMemberAccessExpression(conditionMemberAccess, out var conditionExpression, out var conditionSimpleName);
            syntaxFacts.GetNameAndArityOfSimpleName(conditionSimpleName, out var conditionName, out _);

            if (conditionName != nameof(Nullable <int> .HasValue))
            {
                return;
            }

            var whenPartToCheck = notHasValueExpression ? whenFalseNodeLow : whenTrueNodeLow;

            if (whenPartToCheck is not TMemberAccessExpression whenPartMemberAccess)
            {
                return;
            }

            syntaxFacts.GetPartsOfMemberAccessExpression(whenPartMemberAccess, out var whenPartExpression, out var whenPartSimpleName);
            syntaxFacts.GetNameAndArityOfSimpleName(whenPartSimpleName, out var whenPartName, out _);

            if (whenPartName != nameof(Nullable <int> .Value))
            {
                return;
            }

            if (!syntaxFacts.AreEquivalent(conditionExpression, whenPartExpression))
            {
                return;
            }

            // Syntactically this looks like something we can simplify.  Make sure we're
            // actually looking at something Nullable (and not some type that uses a similar
            // syntactic pattern).
            var semanticModel = context.SemanticModel;
            var nullableType  = semanticModel.Compilation.GetTypeByMetadataName(typeof(Nullable <>).FullName !);

            if (nullableType == null)
            {
                return;
            }

            var type = semanticModel.GetTypeInfo(conditionExpression, cancellationToken);

            if (!nullableType.Equals(type.Type?.OriginalDefinition))
            {
                return;
            }

            var whenPartToKeep = notHasValueExpression ? whenTrueNodeHigh : whenFalseNodeHigh;
            var locations      = ImmutableArray.Create(
                conditionalExpression.GetLocation(),
                conditionExpression.GetLocation(),
                whenPartToKeep.GetLocation());

            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         Descriptor,
                                         conditionalExpression.GetLocation(),
                                         option.Notification.Severity,
                                         locations,
                                         properties: null));
        }
Exemple #12
0
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol referenceEqualsMethod, bool unconstraintedGenericSupported)
        {
            var cancellationToken = context.CancellationToken;

            var semanticModel = context.SemanticModel;

            var option = context.GetAnalyzerOptions().PreferIsNullCheckOverReferenceEqualityMethod;

            if (!option.Value)
            {
                return;
            }

            var invocation  = context.Node;
            var syntaxFacts = GetSyntaxFacts();

            var expression = syntaxFacts.GetExpressionOfInvocationExpression(invocation);
            var nameNode   = syntaxFacts.IsIdentifierName(expression)
                ? expression
                : syntaxFacts.IsSimpleMemberAccessExpression(expression)
                    ? syntaxFacts.GetNameOfMemberAccessExpression(expression)
                    : null;

            if (!syntaxFacts.IsIdentifierName(nameNode))
            {
                return;
            }

            syntaxFacts.GetNameAndArityOfSimpleName(nameNode, out var name, out _);
            if (!syntaxFacts.StringComparer.Equals(name, nameof(ReferenceEquals)))
            {
                return;
            }

            var arguments = syntaxFacts.GetArgumentsOfInvocationExpression(invocation);

            if (arguments.Count != 2)
            {
                return;
            }

            if (!MatchesPattern(syntaxFacts, arguments[0], arguments[1]) &&
                !MatchesPattern(syntaxFacts, arguments[1], arguments[0]))
            {
                return;
            }

            var symbol = semanticModel.GetSymbolInfo(invocation, cancellationToken).Symbol;

            if (!referenceEqualsMethod.Equals(symbol))
            {
                return;
            }

            var properties = ImmutableDictionary <string, string?> .Empty.Add(
                UseIsNullConstants.Kind, UseIsNullConstants.ReferenceEqualsKey);

            var genericParameterSymbol = GetGenericParameterSymbol(syntaxFacts, semanticModel, arguments[0], arguments[1], cancellationToken);

            if (genericParameterSymbol != null)
            {
                if (genericParameterSymbol.IsValueType)
                {
                    // 'is null' would generate error CS0403: Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.
                    // '== null' would generate error CS0019: Operator '==' cannot be applied to operands of type 'T' and '<null>'
                    // 'Is Nothing' would generate error BC30020: 'Is' operator does not accept operands of type 'T'. Operands must be reference or nullable types.
                    return;
                }

                // HasReferenceTypeConstraint returns false for base type constraint.
                // IsReferenceType returns true.

                if (!genericParameterSymbol.IsReferenceType && !unconstraintedGenericSupported)
                {
                    // Needs special casing for C# as long as
                    // 'is null' over unconstrained generic is implemented in C# 8.
                    properties = properties.Add(UseIsNullConstants.UnconstrainedGeneric, "");
                }
            }

            var additionalLocations = ImmutableArray.Create(invocation.GetLocation());

            var negated = syntaxFacts.IsLogicalNotExpression(invocation.Parent);

            if (negated)
            {
                properties = properties.Add(UseIsNullConstants.Negated, "");
            }

            var severity = option.Notification.Severity;

            context.ReportDiagnostic(
                DiagnosticHelper.Create(
                    Descriptor, nameNode.GetLocation(),
                    severity,
                    additionalLocations, properties));
        }
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var cancellationToken       = context.CancellationToken;
            var parenthesizedExpression = (TParenthesizedExpressionSyntax)context.Node;

            if (!CanRemoveParentheses(parenthesizedExpression, context.SemanticModel, cancellationToken,
                                      out var precedence, out var clarifiesPrecedence))
            {
                return;
            }

            // Do not remove parentheses from these expressions when there are different kinds
            // between the parent and child of the parenthesized expr..  This is because removing
            // these parens can significantly decrease readability and can confuse many people
            // (including several people quizzed on Roslyn).  For example, most people see
            // "1 + 2 << 3" as "1 + (2 << 3)", when it's actually "(1 + 2) << 3".  To avoid
            // making code bases more confusing, we just do not touch parens for these constructs
            // unless both the child and parent have the same kinds.
            switch (precedence)
            {
            case PrecedenceKind.Shift:
            case PrecedenceKind.Bitwise:
            case PrecedenceKind.Coalesce:
                var syntaxFacts = GetSyntaxFacts();
                var child       = syntaxFacts.GetExpressionOfParenthesizedExpression(parenthesizedExpression);

                var parentKind = parenthesizedExpression.Parent?.RawKind;
                var childKind  = child.RawKind;
                if (parentKind != childKind)
                {
                    return;
                }

                // Ok to remove if it was the exact same kind.  i.e. ```(a | b) | c```
                // not ok to remove if kinds changed.  i.e. ```(a + b) << c```
                break;
            }

            var options    = context.GetAnalyzerOptions();
            var preference = GetLanguageOption(options, precedence);

            if (preference.Notification.Severity == ReportDiagnostic.Suppress)
            {
                // User doesn't care about these parens.  So nothing for us to do.
                return;
            }

            if (preference.Value == ParenthesesPreference.AlwaysForClarity &&
                clarifiesPrecedence)
            {
                // User wants these parens if they clarify precedence, and these parens
                // clarify precedence.  So keep these around.
                return;
            }

            // either they don't want unnecessary parentheses, or they want them only for
            // clarification purposes and this does not make things clear.
            Debug.Assert(preference.Value == ParenthesesPreference.NeverIfUnnecessary ||
                         !clarifiesPrecedence);

            var severity = preference.Notification.Severity;

            var additionalLocations = ImmutableArray.Create(
                parenthesizedExpression.GetLocation());
            var additionalUnnecessaryLocations = ImmutableArray.Create(
                parenthesizedExpression.GetFirstToken().GetLocation(),
                parenthesizedExpression.GetLastToken().GetLocation());

            context.ReportDiagnostic(DiagnosticHelper.CreateWithLocationTags(
                                         Descriptor,
                                         AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer <TLanguageKindEnum, TParenthesizedExpressionSyntax> .GetDiagnosticSquiggleLocation(parenthesizedExpression, cancellationToken),
                                         severity,
                                         additionalLocations,
                                         additionalUnnecessaryLocations));
        }
    private void AnalyzeIfStatement(
        SyntaxNodeAnalysisContext context,
        IMethodSymbol?referenceEqualsMethod)
    {
        var cancellationToken = context.CancellationToken;
        var option            = context.GetAnalyzerOptions().PreferNullPropagation;

        if (!option.Value)
        {
            return;
        }

        var syntaxFacts = GetSyntaxFacts();
        var ifStatement = (TIfStatementSyntax)context.Node;

        // The true-statement if the if-statement has to be a statement of the form `<expr1>.Name(...)`;
        if (!TryGetPartsOfIfStatement(ifStatement, out var condition, out var trueStatement))
        {
            return;
        }

        if (trueStatement is not TExpressionStatementSyntax expressionStatement)
        {
            return;
        }

        // Now see if the `if (<condition>)` looks like an appropriate null check.
        if (!TryAnalyzeCondition(context, syntaxFacts, referenceEqualsMethod, condition, out var conditionPartToCheck, out var isEquals))
        {
            return;
        }

        // Ok, we have `if (<expr2> == null)` or `if (<expr2> != null)` (or some similar form of that.  `conditionPartToCheck` will be `<expr2>` here.
        // We only support `if (<expr2> != null)`.  Fail out if we have the alternate form.
        if (isEquals)
        {
            return;
        }

        var semanticModel = context.SemanticModel;
        var whenPartMatch = GetWhenPartMatch(
            syntaxFacts, semanticModel, conditionPartToCheck,
            (TExpressionSyntax)syntaxFacts.GetExpressionOfExpressionStatement(expressionStatement),
            cancellationToken);

        if (whenPartMatch == null)
        {
            return;
        }

        // can't use ?. on a pointer
        var whenPartType = semanticModel.GetTypeInfo(whenPartMatch, cancellationToken).Type;

        if (whenPartType is IPointerTypeSymbol)
        {
            return;
        }

        var whenPartIsNullable = semanticModel.GetTypeInfo(whenPartMatch).Type?.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T;
        var properties         = whenPartIsNullable
            ? s_whenPartIsNullableProperties
            : ImmutableDictionary <string, string?> .Empty;

        context.ReportDiagnostic(DiagnosticHelper.Create(
                                     Descriptor,
                                     ifStatement.GetFirstToken().GetLocation(),
                                     option.Notification.Severity,
                                     ImmutableArray.Create(
                                         ifStatement.GetLocation(),
                                         trueStatement.GetLocation(),
                                         whenPartMatch.GetLocation()),
                                     properties));
    }
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var cancellationToken     = context.CancellationToken;
            var conditionalExpression = (TConditionalExpressionSyntax)context.Node;

            var option = context.GetAnalyzerOptions().PreferCoalesceExpression;

            if (!option.Value)
            {
                return;
            }

            var syntaxFacts = GetSyntaxFacts();

            syntaxFacts.GetPartsOfConditionalExpression(
                conditionalExpression, out var conditionNode, out var whenTrueNodeHigh, out var whenFalseNodeHigh);

            conditionNode = syntaxFacts.WalkDownParentheses(conditionNode);
            var whenTrueNodeLow  = syntaxFacts.WalkDownParentheses(whenTrueNodeHigh);
            var whenFalseNodeLow = syntaxFacts.WalkDownParentheses(whenFalseNodeHigh);

            if (conditionNode is not TBinaryExpressionSyntax condition)
            {
                return;
            }

            var syntaxKinds = syntaxFacts.SyntaxKinds;
            var isEquals    = syntaxKinds.ReferenceEqualsExpression == condition.RawKind;
            var isNotEquals = syntaxKinds.ReferenceNotEqualsExpression == condition.RawKind;

            if (!isEquals && !isNotEquals)
            {
                return;
            }

            syntaxFacts.GetPartsOfBinaryExpression(condition, out var conditionLeftHigh, out var conditionRightHigh);

            var conditionLeftLow  = syntaxFacts.WalkDownParentheses(conditionLeftHigh);
            var conditionRightLow = syntaxFacts.WalkDownParentheses(conditionRightHigh);

            var conditionLeftIsNull  = syntaxFacts.IsNullLiteralExpression(conditionLeftLow);
            var conditionRightIsNull = syntaxFacts.IsNullLiteralExpression(conditionRightLow);

            if (conditionRightIsNull && conditionLeftIsNull)
            {
                // null == null    nothing to do here.
                return;
            }

            if (!conditionRightIsNull && !conditionLeftIsNull)
            {
                return;
            }

            if (!syntaxFacts.AreEquivalent(
                    conditionRightIsNull ? conditionLeftLow : conditionRightLow,
                    isEquals ? whenFalseNodeLow : whenTrueNodeLow))
            {
                return;
            }

            // Coalesce expression cannot be target typed.  So if we had a ternary that was target typed
            // that means the individual parts themselves had no best common type, which would not work
            // for a coalesce expression.
            var semanticModel = context.SemanticModel;

            if (IsTargetTyped(semanticModel, conditionalExpression, cancellationToken))
            {
                return;
            }

            var conditionType = semanticModel.GetTypeInfo(
                conditionLeftIsNull ? conditionRightLow : conditionLeftLow, cancellationToken).Type;

            if (conditionType != null &&
                !conditionType.IsReferenceType)
            {
                // Note: it is intentional that we do not support nullable types here.  If you have:
                //
                //  int? x;
                //  var z = x == null ? y : x;
                //
                // then that's not the same as:   x ?? y.   ?? will unwrap the nullable, producing a
                // int and not an int? like we have in the above code.
                //
                // Note: we could look for:  x == null ? y : x.Value, and simplify that in the future.
                return;
            }

            var conditionPartToCheck = conditionRightIsNull ? conditionLeftHigh : conditionRightHigh;
            var whenPartToCheck      = isEquals ? whenTrueNodeHigh : whenFalseNodeHigh;
            var locations            = ImmutableArray.Create(
                conditionalExpression.GetLocation(),
                conditionPartToCheck.GetLocation(),
                whenPartToCheck.GetLocation());

            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         Descriptor,
                                         conditionalExpression.GetLocation(),
                                         option.Notification.Severity,
                                         locations,
                                         properties: null));
        }
Exemple #16
0
        private void AnalyzeConditionalExpression(SyntaxNodeAnalysisContext context)
        {
            var styleOption = context.GetAnalyzerOptions().PreferSimplifiedBooleanExpressions;

            if (!styleOption.Value)
            {
                // Bail immediately if the user has disabled this feature.
                return;
            }

            var semanticModel         = context.SemanticModel;
            var cancellationToken     = context.CancellationToken;
            var conditionalExpression = (TConditionalExpressionSyntax)context.Node;

            SyntaxFacts.GetPartsOfConditionalExpression(
                conditionalExpression, out var conditionNode, out var whenTrueNode, out var whenFalseNode);
            var condition = (TExpressionSyntax)conditionNode;
            var whenTrue  = (TExpressionSyntax)whenTrueNode;
            var whenFalse = (TExpressionSyntax)whenFalseNode;

            // Only offer when everything is a basic boolean type.  That way we don't have to worry
            // about any sort of subtle cases with implicit or bool conversions.
            if (!IsSimpleBooleanType(condition) ||
                !IsSimpleBooleanType(whenTrue) ||
                !IsSimpleBooleanType(whenFalse))
            {
                return;
            }

            var whenTrue_isTrue  = IsTrue(whenTrue);
            var whenTrue_isFalse = IsFalse(whenTrue);

            var whenFalse_isTrue  = IsTrue(whenFalse);
            var whenFalse_isFalse = IsFalse(whenFalse);

            if (whenTrue_isTrue && whenFalse_isFalse)
            {
                // c ? true : false     =>      c
                ReportDiagnostic(s_takeCondition);
            }
            else if (whenTrue_isFalse && whenFalse_isTrue)
            {
                // c ? false : true     =>      !c
                ReportDiagnostic(s_negateCondition);
            }
            else if (whenTrue_isFalse && whenFalse_isFalse)
            {
                // c ? false : false      =>      c && false
                // Note: this is a slight optimization over the when `c ? false : wf`
                // case below.  It allows to generate `c && false` instead of `!c && false`
                ReportDiagnostic(s_takeConditionAndWhenFalse);
            }
            else if (whenTrue_isTrue)
            {
                // c ? true : wf        =>      c || wf
                ReportDiagnostic(s_takeConditionOrWhenFalse);
            }
            else if (whenTrue_isFalse)
            {
                // c ? false : wf       =>      !c && wf
                ReportDiagnostic(s_negateConditionAndWhenFalse);
            }
            else if (whenFalse_isTrue)
            {
                // c ? wt : true        =>      !c or wt
                ReportDiagnostic(s_negateConditionOrWhenTrue);
            }
            else if (whenFalse_isFalse)
            {
                // c ? wt : false       =>      c && wt
                ReportDiagnostic(s_takeConditionAndWhenTrue);
            }

            return;

            // local functions

            void ReportDiagnostic(ImmutableDictionary <string, string?> properties)
            => context.ReportDiagnostic(DiagnosticHelper.Create(
                                            Descriptor,
                                            conditionalExpression.GetLocation(),
                                            styleOption.Notification.Severity,
                                            additionalLocations: null,
                                            properties));

            bool IsSimpleBooleanType(TExpressionSyntax node)
            {
                var typeInfo   = semanticModel.GetTypeInfo(node, cancellationToken);
                var conversion = GetConversion(semanticModel, node, cancellationToken);

                return
                    (conversion.MethodSymbol == null &&
                     typeInfo.Type?.SpecialType == SpecialType.System_Boolean &&
                     typeInfo.ConvertedType?.SpecialType == SpecialType.System_Boolean);
            }

            bool IsTrue(TExpressionSyntax node) => IsBoolValue(node, true);
            bool IsFalse(TExpressionSyntax node) => IsBoolValue(node, false);

            bool IsBoolValue(TExpressionSyntax node, bool value)
            {
                var constantValue = semanticModel.GetConstantValue(node, cancellationToken);

                return(constantValue.HasValue && constantValue.Value is bool b && b == value);
            }
        }