private void AnalyzeCoalesceExpression(SyntaxNodeAnalysisContext context)
        {
            var cancellationToken = context.CancellationToken;
            var semanticModel     = context.SemanticModel;
            var options           = (CSharpParseOptions)semanticModel.SyntaxTree.Options;

            if (options.LanguageVersion < LanguageVersion.CSharp8)
            {
                return;
            }

            var coalesceExpression = (BinaryExpressionSyntax)context.Node;

            var option = context.GetOption(CodeStyleOptions2.PreferCompoundAssignment, coalesceExpression.Language);

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

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

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

            if (!(parenthesizedExpr.Expression is 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));
        }
Beispiel #2
0
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var syntaxTree        = context.SemanticModel.SyntaxTree;
            var cancellationToken = context.CancellationToken;
            var optionSet         = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();

            if (optionSet == null)
            {
                return;
            }

            var parenthesizedExpression = (TParenthesizedExpressionSyntax)context.Node;

            if (!CanRemoveParentheses(parenthesizedExpression, context.SemanticModel,
                                      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 = this.GetSyntaxFactsService();
                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 option     = GetLanguageOption(precedence);
            var preference = optionSet.GetOption(option, parenthesizedExpression.Language);

            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());

            // Fades the open parentheses character and reports the suggestion.
            context.ReportDiagnostic(Diagnostic.Create(s_diagnosticWithFade, parenthesizedExpression.GetFirstToken().GetLocation(), additionalLocations));

            // Generates diagnostic used to squiggle the parenthetical expression.
            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         s_diagnosticWithoutFade,
                                         GetDiagnosticSquiggleLocation(parenthesizedExpression, cancellationToken),
                                         severity,
                                         additionalLocations,
                                         properties: null));

            // Fades the close parentheses character.
            context.ReportDiagnostic(Diagnostic.Create(s_diagnosticWithFade, parenthesizedExpression.GetLastToken().GetLocation(), additionalLocations));
        }
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var conditionalExpression = (TConditionalExpressionSyntax)context.Node;

            var syntaxTree        = context.Node.SyntaxTree;
            var cancellationToken = context.CancellationToken;
            var optionSet         = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();

            if (optionSet == null)
            {
                return;
            }

            var option = optionSet.GetOption(CodeStyleOptions.PreferCoalesceExpression, conditionalExpression.Language);

            if (!option.Value)
            {
                return;
            }

            var syntaxFacts = this.GetSyntaxFactsService();

            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 condition = conditionNode as TBinaryExpressionSyntax;

            if (condition == null)
            {
                return;
            }

            var isEquals    = IsEquals(condition);
            var isNotEquals = IsNotEquals(condition);

            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;
            }

            var semanticModel = context.SemanticModel;
            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));
        }
        private void AnalyzeNode(SyntaxNodeAnalysisContext context)
        {
            var cancellationToken = context.CancellationToken;
            var node = (TMemberAccessExpressionSyntax)context.Node;

            var syntaxFacts = GetSyntaxFactsService();
            var expr        = syntaxFacts.GetExpressionOfMemberAccessExpression(node);

            if (!(expr is TThisExpressionSyntax))
            {
                return;
            }

            var analyzerOptions = context.Options;

            var syntaxTree = node.SyntaxTree;
            var optionSet  = analyzerOptions.GetAnalyzerOptionSet(syntaxTree, cancellationToken);

            var model = context.SemanticModel;

            if (!CanSimplifyTypeNameExpression(
                    model, node, optionSet, out var issueSpan, cancellationToken))
            {
                return;
            }

            if (model.SyntaxTree.OverlapsHiddenPosition(issueSpan, cancellationToken))
            {
                return;
            }

            var symbolInfo = model.GetSymbolInfo(node, cancellationToken);

            if (symbolInfo.Symbol == null)
            {
                return;
            }

            var applicableOption = QualifyMembersHelpers.GetApplicableOptionFromSymbolKind(symbolInfo.Symbol.Kind);
            var optionValue      = optionSet.GetOption(applicableOption, GetLanguageName());

            if (optionValue == null)
            {
                return;
            }

            var severity = optionValue.Notification.Severity;

            var descriptor = CreateUnnecessaryDescriptor(DescriptorId);

            if (descriptor == null)
            {
                return;
            }

            var tree    = model.SyntaxTree;
            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(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInDeclaration);
            builder["OptionLanguage"] = model.Language;

            var diagnostic = DiagnosticHelper.Create(
                descriptor, tree.GetLocation(issueSpan), severity,
                ImmutableArray.Create(node.GetLocation()), builder.ToImmutable());

            context.ReportDiagnostic(diagnostic);
        }
 private static Diagnostic CreateDiagnostic(DiagnosticDescriptor descriptor, SyntaxNode declaration, TextSpan diagnosticSpan, ReportDiagnostic severity)
 => DiagnosticHelper.Create(descriptor, declaration.SyntaxTree.GetLocation(diagnosticSpan), severity, additionalLocations: null, properties: null);
        private void AnalyzeInvokedMember(
            OperationAnalysisContext context, InfoCache infoCache,
            IOperation instance, IMethodSymbol targetMethodOpt, IOperation argumentValue,
            IPropertySymbol lengthLikePropertyOpt, CancellationToken cancellationToken)
        {
            // look for `s[s.Length - value]` or `s.Get(s.Length- value)`.

            // Needs to have the one arg for `s.Length - value`, and that arg needs to be
            // a subtraction.
            if (instance is null ||
                !IsSubtraction(argumentValue, out var subtraction))
            {
                return;
            }

            if (!(subtraction.Syntax is BinaryExpressionSyntax binaryExpression))
            {
                return;
            }

            // Only supported on C# 8 and above.
            var syntaxTree   = binaryExpression.SyntaxTree;
            var parseOptions = (CSharpParseOptions)syntaxTree.Options;

            if (parseOptions.LanguageVersion < LanguageVersion.CSharp8)
            {
                return;
            }

            // Don't bother analyzing if the user doesn't like using Index/Range operators.
            var optionSet = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();

            if (optionSet is null)
            {
                return;
            }

            var option = optionSet.GetOption(CSharpCodeStyleOptions.PreferIndexOperator);

            if (!option.Value)
            {
                return;
            }

            // Ok, looks promising.  We're indexing in with some subtraction expression. Examine the
            // type this indexer is in to see if there's another member that takes a System.Index
            // that we can convert to.
            //
            // Also ensure that the left side of the subtraction : `s.Length - value` is actually
            // getting the length off the same instance we're indexing into.

            lengthLikePropertyOpt ??= TryGetLengthLikeProperty(infoCache, targetMethodOpt);
            if (lengthLikePropertyOpt == null ||
                !IsInstanceLengthCheck(lengthLikePropertyOpt, instance, subtraction.LeftOperand))
            {
                return;
            }

            // Everything looks good.  We can update this to use the System.Index member instead.
            context.ReportDiagnostic(
                DiagnosticHelper.Create(
                    Descriptor,
                    binaryExpression.GetLocation(),
                    option.Notification.Severity,
                    ImmutableArray <Location> .Empty,
                    ImmutableDictionary <string, string> .Empty));
        }
        public void AnalyzeNode(SyntaxNodeAnalysisContext context)
        {
            var statement         = context.Node;
            var cancellationToken = context.CancellationToken;

            var option = context.Options.GetOption(CSharpCodeStyleOptions.PreferBraces, statement.SyntaxTree, cancellationToken);

            if (option.Value == PreferBracesPreference.None)
            {
                return;
            }

            var embeddedStatement = statement.GetEmbeddedStatement();

            Contract.ThrowIfNull(embeddedStatement);

            switch (embeddedStatement.Kind())
            {
            case SyntaxKind.Block:
                // The embedded statement already has braces, which is always allowed.
                return;

            case SyntaxKind.IfStatement when statement.Kind() == SyntaxKind.ElseClause:
                // Constructs like the following are always allowed:
                //
                //   if (something)
                //   {
                //   }
                //   else if (somethingElse) // <-- 'if' nested in an 'else' clause
                //   {
                //   }
                return;

            case SyntaxKind.LockStatement:
            case SyntaxKind.UsingStatement:
            case SyntaxKind.FixedStatement:
                // If we have something like this:
                //
                //    using (...)
                //    using (...)
                //    {
                //    }
                //
                // The first statement needs no block as it formatted with the same indentation.
                if (statement.Kind() == embeddedStatement.Kind())
                {
                    return;
                }

                break;
            }

            if (option.Value == PreferBracesPreference.WhenMultiline &&
                !IsConsideredMultiLine(statement, embeddedStatement) &&
                !RequiresBracesToMatchContext(statement))
            {
                return;
            }

            if (ContainsInterleavedDirective(statement, embeddedStatement, cancellationToken))
            {
                return;
            }

            var firstToken = statement.GetFirstToken();

            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         Descriptor,
                                         firstToken.GetLocation(),
                                         option.Notification.Severity,
                                         additionalLocations: null,
                                         properties: null,
                                         SyntaxFacts.GetText(firstToken.Kind())));
        }
Beispiel #8
0
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var conditionalExpression = (TConditionalExpressionSyntax)context.Node;

            var cancellationToken = context.CancellationToken;

            var option = context.GetOption(CodeStyleOptions2.PreferCoalesceExpression, conditionalExpression.Language);

            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 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 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));
        }
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, INamedTypeSymbol expressionTypeOpt, IMethodSymbol referenceEqualsMethodOpt)
        {
            var conditionalExpression = (TConditionalExpressionSyntax)context.Node;

            if (!ShouldAnalyze(conditionalExpression.SyntaxTree.Options))
            {
                return;
            }

            var syntaxTree        = conditionalExpression.SyntaxTree;
            var cancellationToken = context.CancellationToken;
            var optionSet         = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();

            if (optionSet == null)
            {
                return;
            }

            var option = optionSet.GetOption(CodeStyleOptions.PreferNullPropagation, conditionalExpression.Language);

            if (!option.Value)
            {
                return;
            }

            var syntaxFacts = this.GetSyntaxFactsService();

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

            conditionNode = syntaxFacts.WalkDownParentheses(conditionNode);

            var conditionIsNegated = false;

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

            var isEqualityLikeCondition = TryAnalyzeCondition(
                context, syntaxFacts, referenceEqualsMethodOpt, conditionNode,
                out var conditionPartToCheck, out var isEquals);

            if (!isEqualityLikeCondition)
            {
                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 semanticFacts = GetSemanticFactsService();
            var semanticModel = context.SemanticModel;
            var whenPartMatch = GetWhenPartMatch(syntaxFacts, semanticFacts, 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 INamedTypeSymbol namedType) || namedType.ConstructedFrom.SpecialType != SpecialType.core_Option_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 (semanticFacts.IsInExpressionTree(semanticModel, conditionNode, expressionTypeOpt, 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.core_Option_T;

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

            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         Descriptor,
                                         conditionalExpression.GetLocation(),
                                         option.Notification.Severity,
                                         locations,
                                         properties));
        }
Beispiel #10
0
        private void AnalyzeOperation(OperationAnalysisContext context, IOperation operation, IOperation instanceOperation)
        {
            // this is a static reference so we don't care if it's qualified
            if (instanceOperation == null)
            {
                return;
            }

            // if we're not referencing `this.` or `Me.` (e.g., a parameter, local, etc.)
            if (instanceOperation.Kind != OperationKind.InstanceReference)
            {
                return;
            }

            // We shouldn't qualify if it is inside a property pattern
            if (context.Operation.Parent.Kind == OperationKind.PropertySubpattern)
            {
                return;
            }

            // Initializer lists are IInvocationOperation which if passed to GetApplicableOptionFromSymbolKind
            // will incorrectly fetch the options for method call.
            // We still want to handle InstanceReferenceKind.ContainingTypeInstance
            if ((instanceOperation as IInstanceReferenceOperation)?.ReferenceKind == InstanceReferenceKind.ImplicitReceiver)
            {
                return;
            }

            // If we can't be qualified (e.g., because we're already qualified with `base.`), we're done.
            if (!CanMemberAccessBeQualified(context.ContainingSymbol, instanceOperation.Syntax))
            {
                return;
            }

            // if we can't find a member then we can't do anything.  Also, we shouldn't qualify
            // accesses to static members.
            if (IsStaticMemberOrIsLocalFunction(operation))
            {
                return;
            }

            if (!(instanceOperation.Syntax is TSimpleNameSyntax simpleName))
            {
                return;
            }

            var syntaxTree        = context.Operation.Syntax.SyntaxTree;
            var cancellationToken = context.CancellationToken;
            var optionSet         = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();

            if (optionSet == null)
            {
                return;
            }

            var applicableOption = QualifyMembersHelpers.GetApplicableOptionFromSymbolKind(operation);
            var optionValue      = optionSet.GetOption(applicableOption, context.Operation.Syntax.Language);

            var shouldOptionBePresent = optionValue.Value;
            var severity = optionValue.Notification.Severity;

            if (!shouldOptionBePresent || severity == ReportDiagnostic.Suppress)
            {
                return;
            }

            if (!IsAlreadyQualifiedMemberAccess(simpleName))
            {
                context.ReportDiagnostic(DiagnosticHelper.Create(
                                             Descriptor,
                                             GetLocation(operation),
                                             severity,
                                             additionalLocations: null,
                                             properties: null));
            }
        }
Beispiel #11
0
        protected bool TrySimplifyTypeNameExpression(SemanticModel model, SyntaxNode node, AnalyzerOptions analyzerOptions, out Diagnostic diagnostic, CancellationToken cancellationToken)
        {
            diagnostic = default;

            var syntaxTree = node.SyntaxTree;
            var optionSet  = analyzerOptions.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();

            if (optionSet == null)
            {
                return(false);
            }

            if (!CanSimplifyTypeNameExpressionCore(
                    model, node, optionSet,
                    out var issueSpan, out var diagnosticId, out var inDeclaration,
                    cancellationToken))
            {
                return(false);
            }

            if (model.SyntaxTree.OverlapsHiddenPosition(issueSpan, cancellationToken))
            {
                return(false);
            }

            PerLanguageOption <CodeStyleOption <bool> > option;
            DiagnosticDescriptor descriptor;
            ReportDiagnostic     severity;

            switch (diagnosticId)
            {
            case IDEDiagnosticIds.SimplifyNamesDiagnosticId:
                descriptor = s_descriptorSimplifyNames;
                severity   = descriptor.DefaultSeverity.ToReportDiagnostic();
                break;

            case IDEDiagnosticIds.SimplifyMemberAccessDiagnosticId:
                descriptor = s_descriptorSimplifyMemberAccess;
                severity   = descriptor.DefaultSeverity.ToReportDiagnostic();
                break;

            case IDEDiagnosticIds.PreferBuiltInOrFrameworkTypeDiagnosticId:
                option = inDeclaration
                        ? CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInDeclaration
                        : CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess;
                descriptor = s_descriptorPreferBuiltinOrFrameworkType;

                var optionValue = optionSet.GetOption(option, GetLanguageName());
                severity = optionValue.Notification.Severity;
                break;

            default:
                throw ExceptionUtilities.UnexpectedValue(diagnosticId);
            }

            if (descriptor == null)
            {
                return(false);
            }

            var tree    = model.SyntaxTree;
            var builder = ImmutableDictionary.CreateBuilder <string, string>();

            builder["OptionName"]     = nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess); // TODO: need the actual one
            builder["OptionLanguage"] = model.Language;
            diagnostic = DiagnosticHelper.Create(descriptor, tree.GetLocation(issueSpan), severity, additionalLocations: null, builder.ToImmutable());
            return(true);
        }
        private void AnalyzeOperation(OperationAnalysisContext context, IOperation operation, IOperation?instanceOperation)
        {
            // this is a static reference so we don't care if it's qualified
            if (instanceOperation == null)
            {
                return;
            }

            // if we're not referencing `this.` or `Me.` (e.g., a parameter, local, etc.)
            if (instanceOperation.Kind != OperationKind.InstanceReference)
            {
                return;
            }

            // We shouldn't qualify if it is inside a property pattern
            if (context.Operation.Parent?.Kind == OperationKind.PropertySubpattern)
            {
                return;
            }

            // Initializer lists are IInvocationOperation which if passed to GetApplicableOptionFromSymbolKind
            // will incorrectly fetch the options for method call.
            // We still want to handle InstanceReferenceKind.ContainingTypeInstance
            if ((instanceOperation as IInstanceReferenceOperation)?.ReferenceKind == InstanceReferenceKind.ImplicitReceiver)
            {
                return;
            }

            // If we can't be qualified (e.g., because we're already qualified with `base.`), we're done.
            if (!CanMemberAccessBeQualified(context.ContainingSymbol, instanceOperation.Syntax))
            {
                return;
            }

            // if we can't find a member then we can't do anything.  Also, we shouldn't qualify
            // accesses to static members.
            if (IsStaticMemberOrIsLocalFunction(operation))
            {
                return;
            }

            if (instanceOperation.Syntax is not TSimpleNameSyntax simpleName)
            {
                return;
            }

            var symbolKind = operation switch
            {
                IMemberReferenceOperation memberReferenceOperation => memberReferenceOperation.Member.Kind,
                IInvocationOperation invocationOperation => invocationOperation.TargetMethod.Kind,
                _ => throw ExceptionUtilities.UnexpectedValue(operation),
            };

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

            if (!simplifierOptions.TryGetQualifyMemberAccessOption(symbolKind, out var optionValue))
            {
                return;
            }

            var shouldOptionBePresent = optionValue.Value;
            var severity = optionValue.Notification.Severity;

            if (!shouldOptionBePresent || severity == ReportDiagnostic.Suppress)
            {
                return;
            }

            if (!IsAlreadyQualifiedMemberAccess(simpleName))
            {
                context.ReportDiagnostic(DiagnosticHelper.Create(
                                             Descriptor,
                                             GetLocation(operation),
                                             severity,
                                             additionalLocations: null,
                                             properties: null));
            }
        }
Beispiel #13
0
        protected override void InitializeWorker(AnalysisContext context)
        {
            context.RegisterCompilationStartAction(compilationStartContext =>
            {
                // State map for fields:
                //  'isCandidate' : Indicates whether the field is a candidate to be made readonly based on it's options.
                //  'written'     : Indicates if there are any writes to the field outside the constructor and field initializer.
                var fieldStateMap = new ConcurrentDictionary <IFieldSymbol, (bool isCandidate, bool written)>();

                // We register following actions in the compilation:
                // 1. A symbol action for field symbols to ensure the field state is initialized for every field in
                //    the compilation.
                // 2. An operation action for field references to detect if a candidate field is written outside
                //    constructor and field initializer, and update field state accordingly.
                // 3. A symbol start/end action for named types to report diagnostics for candidate fields that were
                //    not written outside constructor and field initializer.

                compilationStartContext.RegisterSymbolAction(AnalyzeFieldSymbol, SymbolKind.Field);

                compilationStartContext.RegisterSymbolStartAction(symbolStartContext =>
                {
                    symbolStartContext.RegisterOperationAction(AnalyzeOperation, OperationKind.FieldReference);
                    symbolStartContext.RegisterSymbolEndAction(OnSymbolEnd);
                }, SymbolKind.NamedType);

                return;

                // Local functions.
                void AnalyzeFieldSymbol(SymbolAnalysisContext symbolContext)
                {
                    _ = TryGetOrInitializeFieldState((IFieldSymbol)symbolContext.Symbol, symbolContext.Options, symbolContext.CancellationToken);
                }

                void AnalyzeOperation(OperationAnalysisContext operationContext)
                {
                    var fieldReference        = (IFieldReferenceOperation)operationContext.Operation;
                    var(isCandidate, written) = TryGetOrInitializeFieldState(fieldReference.Field, operationContext.Options, operationContext.CancellationToken);

                    // Ignore fields that are not candidates or have already been written outside the constructor/field initializer.
                    if (!isCandidate || written)
                    {
                        return;
                    }

                    // Check if this is a field write outside constructor and field initializer, and update field state accordingly.
                    if (IsFieldWrite(fieldReference, operationContext.ContainingSymbol))
                    {
                        UpdateFieldStateOnWrite(fieldReference.Field);
                    }
                }

                void OnSymbolEnd(SymbolAnalysisContext symbolEndContext)
                {
                    // Report diagnostics for candidate fields that are not written outside constructor and field initializer.
                    var members = ((INamedTypeSymbol)symbolEndContext.Symbol).GetMembers();
                    foreach (var member in members)
                    {
                        if (member is IFieldSymbol field && fieldStateMap.TryRemove(field, out var value))
                        {
                            var(isCandidate, written) = value;
                            if (isCandidate && !written)
                            {
                                var option     = GetCodeStyleOption(field, symbolEndContext.Options, symbolEndContext.CancellationToken);
                                var diagnostic = DiagnosticHelper.Create(
                                    Descriptor,
                                    field.Locations[0],
                                    option.Notification.Severity,
                                    additionalLocations: null,
                                    properties: null);
                                symbolEndContext.ReportDiagnostic(diagnostic);
                            }
                        }
                    }
                }

                static bool IsCandidateField(IFieldSymbol symbol) =>
                symbol.DeclaredAccessibility == Accessibility.Private &&
                !symbol.IsReadOnly &&
                !symbol.IsConst &&
                !symbol.IsImplicitlyDeclared &&
                symbol.Locations.Length == 1 &&
                symbol.Type.IsMutableValueType() == false &&
                !symbol.IsFixedSizeBuffer;

                // Method to update the field state for a candidate field written outside constructor and field initializer.
                void UpdateFieldStateOnWrite(IFieldSymbol field)
                {
                    Debug.Assert(IsCandidateField(field));
                    Debug.Assert(fieldStateMap.ContainsKey(field));

                    fieldStateMap[field] = (isCandidate: true, written: true);
                }

                // Method to get or initialize the field state.
                (bool isCandidate, bool written)TryGetOrInitializeFieldState(IFieldSymbol fieldSymbol, AnalyzerOptions options, CancellationToken cancellationToken)
                {
                    if (!IsCandidateField(fieldSymbol))
                    {
                        return(default);
        private void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext)
        {
            var node       = syntaxContext.Node;
            var syntaxTree = node.SyntaxTree;

            // "x is Type y" is only available in C# 7.0 and above. Don't offer this refactoring
            // in projects targeting a lesser version.
            if (((CSharpParseOptions)syntaxTree.Options).LanguageVersion < LanguageVersion.CSharp7)
            {
                return;
            }

            var options           = syntaxContext.Options;
            var cancellationToken = syntaxContext.CancellationToken;
            var optionSet         = options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();

            if (optionSet == null)
            {
                return;
            }

            var styleOption = optionSet.GetOption(CSharpCodeStyleOptions.PreferPatternMatchingOverAsWithNullCheck);

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

            var comparison = (BinaryExpressionSyntax)node;
            var operand    = GetNullCheckOperand(comparison.Left, comparison.Right)?.WalkDownParentheses();

            if (operand == null)
            {
                return;
            }

            var semanticModel = syntaxContext.SemanticModel;

            if (operand.IsKind(SyntaxKind.CastExpression, out CastExpressionSyntax castExpression))
            {
                // Unwrap object cast
                var castType = semanticModel.GetTypeInfo(castExpression.Type).Type;
                if (castType.IsObjectType())
                {
                    operand = castExpression.Expression;
                }
            }

            if (semanticModel.GetSymbolInfo(comparison).GetAnySymbol().IsUserDefinedOperator())
            {
                return;
            }

            if (!TryGetTypeCheckParts(semanticModel, operand,
                                      out VariableDeclaratorSyntax declarator,
                                      out BinaryExpressionSyntax asExpression,
                                      out ILocalSymbol localSymbol))
            {
                return;
            }

            var localStatement = declarator.Parent?.Parent;
            var enclosingBlock = localStatement?.Parent;

            if (localStatement == null ||
                enclosingBlock == null)
            {
                return;
            }

            var typeNode = asExpression.Right;
            var asType   = semanticModel.GetTypeInfo(typeNode, cancellationToken).Type;

            if (asType.IsNullable())
            {
                // Not legal to write "x is int? y"
                return;
            }

            if (asType?.TypeKind == TypeKind.Dynamic)
            {
                // Not legal to use dynamic in a pattern.
                return;
            }

            if (!localSymbol.Type.Equals(asType))
            {
                // We have something like:
                //
                //      BaseType b = x as DerivedType;
                //      if (b != null) { ... }
                //
                // It's not necessarily safe to convert this to:
                //
                //      if (x is DerivedType b) { ... }
                //
                // That's because there may be later code that wants to do something like assign a
                // 'BaseType' into 'b'.  As we've now claimed that it must be DerivedType, that
                // won't work.  This might also cause unintended changes like changing overload
                // resolution.  So, we conservatively do not offer the change in a situation like this.
                return;
            }

            // Check if the as operand is ever written up to the point of null check.
            //
            //      var s = field as string;
            //      field = null;
            //      if (s != null) { ... }
            //
            // It's no longer safe to use pattern-matching because 'field is string s' would never be true.
            //
            // Additionally, also bail out if the assigned local is referenced (i.e. read/write/nameof) up to the point of null check.
            //      var s = field as string;
            //      MethodCall(flag: s == null);
            //      if (s != null) { ... }
            //
            var asOperand           = semanticModel.GetSymbolInfo(asExpression.Left, cancellationToken).Symbol;
            var localStatementStart = localStatement.SpanStart;
            var comparisonSpanStart = comparison.SpanStart;

            foreach (var descendentNode in enclosingBlock.DescendantNodes())
            {
                var descendentNodeSpanStart = descendentNode.SpanStart;
                if (descendentNodeSpanStart <= localStatementStart)
                {
                    continue;
                }

                if (descendentNodeSpanStart >= comparisonSpanStart)
                {
                    break;
                }

                if (descendentNode.IsKind(SyntaxKind.IdentifierName, out IdentifierNameSyntax identifierName))
                {
                    // Check if this is a 'write' to the asOperand.
                    if (identifierName.Identifier.ValueText == asOperand?.Name &&
                        asOperand.Equals(semanticModel.GetSymbolInfo(identifierName, cancellationToken).Symbol) &&
                        identifierName.IsWrittenTo())
                    {
                        return;
                    }

                    // Check is a reference of any sort (i.e. read/write/nameof) to the local.
                    if (identifierName.Identifier.ValueText == localSymbol.Name)
                    {
                        return;
                    }
                }
            }

            if (!Analyzer.CanSafelyConvertToPatternMatching(
                    semanticModel, localSymbol, comparison, operand,
                    localStatement, enclosingBlock, cancellationToken))
            {
                return;
            }

            // Looks good!
            var additionalLocations = ImmutableArray.Create(
                declarator.GetLocation(),
                comparison.GetLocation(),
                asExpression.GetLocation());

            // Put a diagnostic with the appropriate severity on the declaration-statement itself.
            syntaxContext.ReportDiagnostic(DiagnosticHelper.Create(
                                               Descriptor,
                                               localStatement.GetLocation(),
                                               styleOption.Notification.Severity,
                                               additionalLocations,
                                               properties: null));
        }
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var outermostUsing = (UsingStatementSyntax)context.Node;

            var syntaxTree = context.Node.SyntaxTree;
            var options    = (CSharpParseOptions)syntaxTree.Options;

            if (options.LanguageVersion < LanguageVersion.CSharp8)
            {
                return;
            }

            if (!(outermostUsing.Parent is BlockSyntax parentBlock))
            {
                // Don't offer on a using statement that is parented by another using statement.
                // We'll just offer on the topmost using statement.
                return;
            }

            var innermostUsing = outermostUsing;

            // Check that all the immediately nested usings are convertible as well.
            // We don't want take a sequence of nested-using and only convert some of them.
            for (var current = outermostUsing; current != null; current = current.Statement as UsingStatementSyntax)
            {
                innermostUsing = current;
                if (current.Declaration == null)
                {
                    return;
                }
            }

            // Verify that changing this using-statement into a using-declaration will not
            // change semantics.
            if (!PreservesSemantics(parentBlock, outermostUsing, innermostUsing))
            {
                return;
            }

            var cancellationToken = context.CancellationToken;
            var optionSet         = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();

            if (optionSet == null)
            {
                return;
            }

            var option = optionSet.GetOption(CSharpCodeStyleOptions.PreferSimpleUsingStatement);

            if (!option.Value)
            {
                return;
            }

            // Good to go!
            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         Descriptor,
                                         outermostUsing.UsingKeyword.GetLocation(),
                                         option.Notification.Severity,
                                         additionalLocations: ImmutableArray.Create(outermostUsing.GetLocation()),
                                         properties: null));
        }
Beispiel #16
0
        private void AnalyzeOperation(OperationAnalysisContext context, INamedTypeSymbol expressionTypeOpt)
        {
            var syntaxTree = context.Operation.Syntax.SyntaxTree;

            if (!IsSupported(syntaxTree.Options))
            {
                return;
            }

            var cancellationToken = context.CancellationToken;

            var throwOperation       = (IThrowOperation)context.Operation;
            var throwStatementSyntax = throwOperation.Syntax;

            var compilation   = context.Compilation;
            var semanticModel = compilation.GetSemanticModel(throwStatementSyntax.SyntaxTree);

            var ifOperation = GetContainingIfOperation(
                semanticModel, throwOperation, cancellationToken);

            // This throw statement isn't parented by an if-statement.  Nothing to
            // do here.
            if (ifOperation == null)
            {
                return;
            }

            if (ifOperation.WhenFalse != null)
            {
                // Can't offer this if the 'if-statement' has an 'else-clause'.
                return;
            }

            var options   = context.Options;
            var optionSet = options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();

            if (optionSet == null)
            {
                return;
            }

            var option = optionSet.GetOption(CodeStyleOptions.PreferThrowExpression, throwStatementSyntax.Language);

            if (!option.Value)
            {
                return;
            }

            var semanticFacts = GetSemanticFactsService();

            if (semanticFacts.IsInExpressionTree(semanticModel, throwStatementSyntax, expressionTypeOpt, cancellationToken))
            {
                return;
            }

            var containingBlock = semanticModel.GetOperation(ifOperation.Syntax.Parent, cancellationToken) as IBlockOperation;

            if (containingBlock == null)
            {
                return;
            }

            if (!TryDecomposeIfCondition(ifOperation, out var localOrParameter))
            {
                return;
            }

            if (!TryFindAssignmentExpression(containingBlock, ifOperation, localOrParameter,
                                             out var expressionStatement, out var assignmentExpression))
            {
                return;
            }

            if (!localOrParameter.GetSymbolType().CanAddNullCheck())
            {
                return;
            }

            // We found an assignment using this local/parameter.  Now, just make sure there
            // were no intervening accesses between the check and the assignment.
            if (ValueIsAccessed(
                    semanticModel, ifOperation, containingBlock,
                    localOrParameter, expressionStatement, assignmentExpression))
            {
                return;
            }

            // Ok, there were no intervening writes or accesses.  This check+assignment can be simplified.
            var allLocations = ImmutableArray.Create(
                ifOperation.Syntax.GetLocation(),
                throwOperation.Exception.Syntax.GetLocation(),
                assignmentExpression.Value.Syntax.GetLocation());

            context.ReportDiagnostic(
                DiagnosticHelper.Create(Descriptor, throwStatementSyntax.GetLocation(), option.Notification.Severity, additionalLocations: allLocations, properties: null));

            // Fade out the rest of the if that surrounds the 'throw' exception.

            var tokenBeforeThrow = throwStatementSyntax.GetFirstToken().GetPreviousToken();
            var tokenAfterThrow  = throwStatementSyntax.GetLastToken().GetNextToken();

            context.ReportDiagnostic(
                Diagnostic.Create(UnnecessaryWithSuggestionDescriptor,
                                  Location.Create(syntaxTree, TextSpan.FromBounds(
                                                      ifOperation.Syntax.SpanStart,
                                                      tokenBeforeThrow.Span.End)),
                                  additionalLocations: allLocations));

            if (ifOperation.Syntax.Span.End > tokenAfterThrow.Span.Start)
            {
                context.ReportDiagnostic(
                    Diagnostic.Create(UnnecessaryWithSuggestionDescriptor,
                                      Location.Create(syntaxTree, TextSpan.FromBounds(
                                                          tokenAfterThrow.Span.Start,
                                                          ifOperation.Syntax.Span.End)),
                                      additionalLocations: allLocations));
            }
        }
Beispiel #17
0
        private void AnalyzeConditionalExpression(SyntaxNodeAnalysisContext context)
        {
            var semanticModel     = context.SemanticModel;
            var syntaxTree        = semanticModel.SyntaxTree;
            var options           = context.Options;
            var cancellationToken = context.CancellationToken;

            var styleOption = options.GetOption(
                CodeStyleOptions.PreferSimplifiedBooleanExpressions,
                semanticModel.Language, syntaxTree, cancellationToken);

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

            var conditionalExpression = (TConditionalExpressionSyntax)context.Node;

            this.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(
                                            this.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);
            }
        }
Beispiel #18
0
        internal static Diagnostic CreateDiagnostic(SemanticModel model, OptionSet optionSet, TextSpan issueSpan, string diagnosticId, bool inDeclaration)
        {
            PerLanguageOption <CodeStyleOption <bool> > option;
            DiagnosticDescriptor descriptor;
            ReportDiagnostic     severity;

            switch (diagnosticId)
            {
            case IDEDiagnosticIds.SimplifyNamesDiagnosticId:
                descriptor = s_descriptorSimplifyNames;
                severity   = descriptor.DefaultSeverity.ToReportDiagnostic();
                break;

            case IDEDiagnosticIds.SimplifyMemberAccessDiagnosticId:
                descriptor = s_descriptorSimplifyMemberAccess;
                severity   = descriptor.DefaultSeverity.ToReportDiagnostic();
                break;

            case IDEDiagnosticIds.PreferBuiltInOrFrameworkTypeDiagnosticId:
                option = inDeclaration
                        ? CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInDeclaration
                        : CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess;
                descriptor = s_descriptorPreferBuiltinOrFrameworkType;

                var optionValue = optionSet.GetOption(option, model.Language);
                severity = optionValue.Notification.Severity;
                break;

            default:
                throw ExceptionUtilities.UnexpectedValue(diagnosticId);
            }

            var tree    = model.SyntaxTree;
            var builder = ImmutableDictionary.CreateBuilder <string, string>();

            builder["OptionName"]     = nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess); // TODO: need the actual one
            builder["OptionLanguage"] = model.Language;
            var diagnostic = DiagnosticHelper.Create(descriptor, tree.GetLocation(issueSpan), severity, additionalLocations: null, builder.ToImmutable());

#if LOG
            var sourceText = tree.GetText();
            sourceText.GetLineAndOffset(issueSpan.Start, out var startLineNumber, out var startOffset);
            sourceText.GetLineAndOffset(issueSpan.End, out var endLineNumber, out var endOffset);
            var logLine = tree.FilePath + "," + startLineNumber + "\t" + diagnosticId + "\t" + inDeclaration + "\t";

            var leading = sourceText.ToString(TextSpan.FromBounds(
                                                  sourceText.Lines[startLineNumber].Start, issueSpan.Start));
            var mid      = sourceText.ToString(issueSpan);
            var trailing = sourceText.ToString(TextSpan.FromBounds(
                                                   issueSpan.End, sourceText.Lines[endLineNumber].End));

            var contents = leading + "[|" + s_newlinePattern.Replace(mid, " ") + "|]" + trailing;
            logLine += contents + "\r\n";

            lock (_logGate)
            {
                File.AppendAllText(_logFile, logLine);
            }
#endif

            return(diagnostic);
        }
Beispiel #19
0
        private void ProcessBlockLikeStatement(SyntaxTreeAnalysisContext context, ReportDiagnostic severity, SyntaxNode block)
        {
            // Don't examine broken blocks.
            var endToken = block.GetLastToken();

            if (endToken.IsMissing)
            {
                return;
            }

            // If the close brace itself doesn't have a newline, then ignore this.  This is a case of series of
            // statements on the same line.
            if (!endToken.TrailingTrivia.Any())
            {
                return;
            }

            if (!_syntaxFacts.IsEndOfLineTrivia(endToken.TrailingTrivia.Last()))
            {
                return;
            }

            // Grab whatever comes after the close brace.  If it's not the start of a statement, ignore it.
            var nextToken = endToken.GetNextToken();
            var nextTokenContainingStatement = nextToken.Parent?.FirstAncestorOrSelf <TExecutableStatementSyntax>();

            if (nextTokenContainingStatement == null)
            {
                return;
            }

            if (nextToken != nextTokenContainingStatement.GetFirstToken())
            {
                return;
            }

            // There has to be at least a blank line between the end of the block and the start of the next statement.

            foreach (var trivia in nextToken.LeadingTrivia)
            {
                // If there's a blank line between the brace and the next token, we're all set.
                if (_syntaxFacts.IsEndOfLineTrivia(trivia))
                {
                    return;
                }

                if (_syntaxFacts.IsWhitespaceTrivia(trivia))
                {
                    continue;
                }

                // got something that wasn't whitespace.  Bail out as we don't want to place any restrictions on this code.
                return;
            }

            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         this.Descriptor,
                                         GetDiagnosticLocation(block),
                                         severity,
                                         additionalLocations: ImmutableArray.Create(nextToken.GetLocation()),
                                         properties: null));
        }
Beispiel #20
0
                private void AnalyzeUnusedValueAssignments(
                    OperationBlockAnalysisContext context,
                    bool isComputingUnusedParams,
                    PooledHashSet <SymbolUsageResult> symbolUsageResultsBuilder,
                    out bool hasBlockWithAllUsedSymbolWrites)
                {
                    hasBlockWithAllUsedSymbolWrites = false;

                    foreach (var operationBlock in context.OperationBlocks)
                    {
                        if (!ShouldAnalyze(operationBlock, context.OwningSymbol))
                        {
                            continue;
                        }

                        // First perform the fast, aggressive, imprecise operation-tree based analysis.
                        // This analysis might flag some "used" symbol writes as "unused", but will not miss reporting any truly unused symbol writes.
                        // This initial pass helps us reduce the number of methods for which we perform the slower second pass.
                        // We perform the first fast pass only if there are no delegate creations/lambda methods.
                        // This is due to the fact that tracking which local/parameter points to which delegate creation target
                        // at any given program point needs needs flow analysis (second pass).
                        if (!_hasDelegateCreationOrAnonymousFunction)
                        {
                            var resultFromOperationBlockAnalysis = SymbolUsageAnalysis.Run(operationBlock, context.OwningSymbol, context.CancellationToken);
                            if (!resultFromOperationBlockAnalysis.HasUnreadSymbolWrites())
                            {
                                // Assert that even slow pass (dataflow analysis) would have yielded no unused symbol writes.
                                Debug.Assert(!SymbolUsageAnalysis.Run(context.GetControlFlowGraph(operationBlock), context.OwningSymbol, context.CancellationToken)
                                             .HasUnreadSymbolWrites());

                                hasBlockWithAllUsedSymbolWrites = true;
                                continue;
                            }
                        }

                        // Now perform the slower, precise, CFG based dataflow analysis to identify the actual unused symbol writes.
                        var controlFlowGraph  = context.GetControlFlowGraph(operationBlock);
                        var symbolUsageResult = SymbolUsageAnalysis.Run(controlFlowGraph, context.OwningSymbol, context.CancellationToken);
                        symbolUsageResultsBuilder.Add(symbolUsageResult);

                        foreach (var(symbol, unreadWriteOperation) in symbolUsageResult.GetUnreadSymbolWrites())
                        {
                            if (unreadWriteOperation == null)
                            {
                                // Null operation is used for initial write for the parameter from method declaration.
                                // So, the initial value of the parameter is never read in this operation block.
                                // However, we do not report this as an unused parameter here as a different operation block
                                // might be reading the initial parameter value.
                                // For example, a constructor with both a constructor initializer and body will have two different operation blocks
                                // and a parameter must be unused across both these blocks to be marked unused.

                                // However, we do report unused parameters for local function here.
                                // Local function parameters are completely scoped to this operation block, and should be reported per-operation block.
                                var unusedParameter = (IParameterSymbol)symbol;
                                if (isComputingUnusedParams &&
                                    unusedParameter.ContainingSymbol.IsLocalFunction())
                                {
                                    var hasReference = symbolUsageResult.SymbolsRead.Contains(unusedParameter);

                                    bool shouldReport;
                                    switch (unusedParameter.RefKind)
                                    {
                                    case RefKind.Out:
                                        // Do not report out parameters of local functions.
                                        // If they are unused in the caller, we will flag the
                                        // out argument at the local function callsite.
                                        shouldReport = false;
                                        break;

                                    case RefKind.Ref:
                                        // Report ref parameters only if they have no read/write references.
                                        // Note that we always have one write for the parameter input value from the caller.
                                        shouldReport = !hasReference && symbolUsageResult.GetSymbolWriteCount(unusedParameter) == 1;
                                        break;

                                    default:
                                        shouldReport = true;
                                        break;
                                    }

                                    if (shouldReport)
                                    {
                                        _symbolStartAnalyzer.ReportUnusedParameterDiagnostic(unusedParameter, hasReference, context.ReportDiagnostic, context.Options, context.CancellationToken);
                                    }
                                }

                                continue;
                            }

                            if (ShouldReportUnusedValueDiagnostic(symbol, unreadWriteOperation, symbolUsageResult, out var properties))
                            {
                                var diagnostic = DiagnosticHelper.Create(s_valueAssignedIsUnusedRule,
                                                                         _symbolStartAnalyzer._compilationAnalyzer.GetDefinitionLocationToFade(unreadWriteOperation),
                                                                         _options.UnusedValueAssignmentSeverity,
                                                                         additionalLocations: null,
                                                                         properties,
                                                                         symbol.Name);
                                context.ReportDiagnostic(diagnostic);
                            }
                        }
                    }

                    return;

                    // Local functions.
                    bool ShouldReportUnusedValueDiagnostic(
                        ISymbol symbol,
                        IOperation unreadWriteOperation,
                        SymbolUsageResult resultFromFlowAnalysis,
                        out ImmutableDictionary <string, string> properties)
                    {
                        Debug.Assert(!(symbol is ILocalSymbol local) || !local.IsRef);

                        properties = null;

                        // Bail out in following cases:
                        //   1. End user has configured the diagnostic to be suppressed.
                        //   2. Symbol has error type, hence the diagnostic could be noised
                        //   3. Static local symbols. Assignment to static locals
                        //      is not unnecessary as the assigned value can be used on the next invocation.
                        //   4. Ignore special discard symbol names (see https://github.com/dotnet/roslyn/issues/32923).
                        if (_options.UnusedValueAssignmentSeverity == ReportDiagnostic.Suppress ||
                            symbol.GetSymbolType().IsErrorType() ||
                            (symbol.IsStatic && symbol.Kind == SymbolKind.Local) ||
                            IsSymbolWithSpecialDiscardName(symbol))
                        {
                            return(false);
                        }

                        // Flag to indicate if the symbol has no reads.
                        var isUnusedLocalAssignment = symbol is ILocalSymbol localSymbol &&
                                                      !resultFromFlowAnalysis.SymbolsRead.Contains(localSymbol);

                        var isRemovableAssignment = IsRemovableAssignmentWithoutSideEffects(unreadWriteOperation);

                        if (isUnusedLocalAssignment &&
                            !isRemovableAssignment &&
                            _options.UnusedValueAssignmentPreference == UnusedValuePreference.UnusedLocalVariable)
                        {
                            // Meets current user preference of using unused local symbols for storing computation result.
                            // Skip reporting diagnostic.
                            return(false);
                        }

                        properties = s_propertiesMap[(_options.UnusedValueAssignmentPreference, isUnusedLocalAssignment, isRemovableAssignment)];
                        return(true);
                    }
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var outermostUsing = (UsingStatementSyntax)context.Node;

            var syntaxTree = context.Node.SyntaxTree;
            var options    = (CSharpParseOptions)syntaxTree.Options;

            if (options.LanguageVersion < LanguageVersion.CSharp8)
            {
                return;
            }

            if (outermostUsing.Parent is not BlockSyntax parentBlock)
            {
                // Don't offer on a using statement that is parented by another using statement.
                // We'll just offer on the topmost using statement.
                return;
            }

            var innermostUsing = outermostUsing;

            // Check that all the immediately nested usings are convertible as well.
            // We don't want take a sequence of nested-using and only convert some of them.
            for (var current = outermostUsing; current != null; current = current.Statement as UsingStatementSyntax)
            {
                innermostUsing = current;
                if (current.Declaration == null)
                {
                    return;
                }
            }

            // Verify that changing this using-statement into a using-declaration will not
            // change semantics.
            if (!PreservesSemantics(parentBlock, outermostUsing, innermostUsing))
            {
                return;
            }

            var cancellationToken = context.CancellationToken;

            // Converting a using-statement to a using-variable-declaration will cause the using's
            // variables to now be pushed up to the parent block's scope. This is also true for any
            // local variables in the innermost using's block. These may then collide with other
            // variables in the block, causing an error.  Check for that and bail if this happens.
            if (CausesVariableCollision(
                    context.SemanticModel, parentBlock,
                    outermostUsing, innermostUsing, cancellationToken))
            {
                return;
            }

            var option = context.GetCSharpAnalyzerOptions().PreferSimpleUsingStatement;

            if (!option.Value)
            {
                return;
            }

            // Good to go!
            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         Descriptor,
                                         outermostUsing.UsingKeyword.GetLocation(),
                                         option.Notification.Severity,
                                         additionalLocations: ImmutableArray.Create(outermostUsing.GetLocation()),
                                         properties: null));
        }
Beispiel #22
0
            private void OnSymbolEnd(SymbolAnalysisContext symbolEndContext, bool hasInvalidOperation)
            {
                // We bail out reporting diagnostics for named types which have any invalid operations, i.e. erroneous code.
                // We do so to ensure that we don't report false positives during editing scenarios in the IDE, where the user
                // is still editing code and fixing unresolved references to symbols, such as overload resolution errors.
                if (hasInvalidOperation)
                {
                    return;
                }

                // Report diagnostics for unused candidate members.
                var first = true;
                PooledHashSet <ISymbol> symbolsReferencedInDocComments = null;

                try
                {
                    var namedType = (INamedTypeSymbol)symbolEndContext.Symbol;
                    foreach (var member in namedType.GetMembers())
                    {
                        // Check if the underlying member is neither read nor a readable reference to the member is taken.
                        // If so, we flag the member as either unused (never written) or unread (written but not read).
                        if (TryRemove(member, out var valueUsageInfo) &&
                            !valueUsageInfo.ContainsReadOrReadableRef())
                        {
                            Debug.Assert(IsCandidateSymbol(member));
                            Debug.Assert(!member.IsImplicitlyDeclared);

                            if (first)
                            {
                                // Bail out if there are syntax errors in any of the declarations of the containing type.
                                // Note that we check this only for the first time that we report an unused or unread member for the containing type.
                                if (HasSyntaxErrors(namedType, symbolEndContext.CancellationToken))
                                {
                                    return;
                                }

                                // Compute the set of candidate symbols referenced in all the documentation comments within the named type declarations.
                                // This set is computed once and used for all the iterations of the loop.
                                symbolsReferencedInDocComments = GetCandidateSymbolsReferencedInDocComments(namedType, symbolEndContext.Compilation, symbolEndContext.CancellationToken);
                                first = false;
                            }

                            // Report IDE0051 or IDE0052 based on whether the underlying member has any Write/WritableRef/NonReadWriteRef references or not.
                            var rule = !valueUsageInfo.ContainsWriteOrWritableRef() && !valueUsageInfo.ContainsNonReadWriteRef() && !symbolsReferencedInDocComments.Contains(member)
                                ? _removeUnusedMembersRule
                                : _removeUnreadMembersRule;
                            var effectiveSeverity = rule.GetEffectiveSeverity(symbolEndContext.Compilation.Options);

                            // Most of the members should have a single location, except for partial methods.
                            // We report the diagnostic on the first location of the member.
                            var diagnostic = DiagnosticHelper.Create(
                                rule,
                                member.Locations[0],
                                effectiveSeverity,
                                additionalLocations: null,
                                properties: null,
                                member.ContainingType.Name,
                                member.Name);
                            symbolEndContext.ReportDiagnostic(diagnostic);
                        }
                    }
                }
                finally
                {
                    symbolsReferencedInDocComments?.Free();
                }

                return;
            }
Beispiel #23
0
        private void AnalyzeNode(
            SyntaxNodeAnalysisContext context,
            INamedTypeSymbol ienumerableType
            )
        {
            if (!AreCollectionInitializersSupported(context))
            {
                return;
            }

            var semanticModel            = context.SemanticModel;
            var objectCreationExpression = (TObjectCreationExpressionSyntax)context.Node;
            var language          = objectCreationExpression.Language;
            var cancellationToken = context.CancellationToken;

            var option = context.GetOption(CodeStyleOptions2.PreferCollectionInitializer, language);

            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 = ObjectCreationExpressionAnalyzer <
                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());

            var severity = option.Notification.Severity;

            context.ReportDiagnostic(
                DiagnosticHelper.Create(
                    Descriptor,
                    objectCreationExpression.GetLocation(),
                    severity,
                    additionalLocations: locations,
                    properties: null
                    )
                );

            FadeOutCode(context, matches.Value, locations);
        }
        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));
        }
            private void OnSymbolEnd(SymbolAnalysisContext symbolEndContext, bool hasUnsupportedOperation)
            {
                if (hasUnsupportedOperation)
                {
                    return;
                }

                if (symbolEndContext.Symbol.GetAttributes().Any(a => a.AttributeClass == _structLayoutAttributeType))
                {
                    // Bail out for types with 'StructLayoutAttribute' as the ordering of the members is critical,
                    // and removal of unused members might break semantics.
                    return;
                }

                // Report diagnostics for unused candidate members.
                var first = true;
                PooledHashSet <ISymbol> symbolsReferencedInDocComments    = null;
                ArrayBuilder <string>   debuggerDisplayAttributeArguments = null;

                try
                {
                    var namedType = (INamedTypeSymbol)symbolEndContext.Symbol;
                    foreach (var member in namedType.GetMembers())
                    {
                        // Check if the underlying member is neither read nor a readable reference to the member is taken.
                        // If so, we flag the member as either unused (never written) or unread (written but not read).
                        if (TryRemove(member, out var valueUsageInfo) &&
                            !valueUsageInfo.IsReadFrom())
                        {
                            Debug.Assert(IsCandidateSymbol(member));
                            Debug.Assert(!member.IsImplicitlyDeclared);

                            if (first)
                            {
                                // Bail out if there are syntax errors in any of the declarations of the containing type.
                                // Note that we check this only for the first time that we report an unused or unread member for the containing type.
                                if (HasSyntaxErrors(namedType, symbolEndContext.CancellationToken))
                                {
                                    return;
                                }

                                // Compute the set of candidate symbols referenced in all the documentation comments within the named type declarations.
                                // This set is computed once and used for all the iterations of the loop.
                                symbolsReferencedInDocComments = GetCandidateSymbolsReferencedInDocComments(namedType, symbolEndContext.Compilation, symbolEndContext.CancellationToken);

                                // Compute the set of string arguments to DebuggerDisplay attributes applied to any symbol within the named type declaration.
                                // These strings may have an embedded reference to the symbol.
                                // This set is computed once and used for all the iterations of the loop.
                                debuggerDisplayAttributeArguments = GetDebuggerDisplayAttributeArguments(namedType);

                                first = false;
                            }

                            // Simple heuristic for members referenced in DebuggerDisplayAttribute's string argument:
                            // bail out if any of the DebuggerDisplay string arguments contains the member name.
                            // In future, we can consider improving this heuristic to parse the embedded expression
                            // and resolve symbol references.
                            if (debuggerDisplayAttributeArguments.Any(arg => arg.Contains(member.Name)))
                            {
                                continue;
                            }

                            // Report IDE0051 or IDE0052 based on whether the underlying member has any Write/WritableRef/NonReadWriteRef references or not.
                            var rule = !valueUsageInfo.IsWrittenTo() && !valueUsageInfo.IsNameOnly() && !symbolsReferencedInDocComments.Contains(member)
                                ? s_removeUnusedMembersRule
                                : s_removeUnreadMembersRule;

                            // Do not flag write-only properties that are not read.
                            // Write-only properties are assumed to have side effects
                            // visible through other means than a property getter.
                            if (rule == s_removeUnreadMembersRule &&
                                member is IPropertySymbol property &&
                                property.IsWriteOnly)
                            {
                                continue;
                            }

                            // Most of the members should have a single location, except for partial methods.
                            // We report the diagnostic on the first location of the member.
                            var diagnostic = DiagnosticHelper.CreateWithMessage(
                                rule,
                                member.Locations[0],
                                rule.GetEffectiveSeverity(symbolEndContext.Compilation.Options),
                                additionalLocations: null,
                                properties: null,
                                GetMessage(rule, member));
                            symbolEndContext.ReportDiagnostic(diagnostic);
                        }
                    }
                }
                finally
                {
                    symbolsReferencedInDocComments?.Free();
                    debuggerDisplayAttributeArguments?.Free();
                }

                return;
            }
        private void ProcessMemberDeclaration(
            SyntaxTreeAnalysisContext context, SyntaxGenerator generator,
            CodeStyleOption <AccessibilityModifiersRequired> option, MemberDeclarationSyntax member)
        {
            if (member.IsKind(SyntaxKind.NamespaceDeclaration, out NamespaceDeclarationSyntax namespaceDeclaration))
            {
                ProcessMembers(context, generator, option, namespaceDeclaration.Members);
            }

            // If we have a class or struct, recurse inwards.
            if (member.IsKind(SyntaxKind.ClassDeclaration, out TypeDeclarationSyntax typeDeclaration) ||
                member.IsKind(SyntaxKind.StructDeclaration, out typeDeclaration))
            {
                ProcessMembers(context, generator, option, typeDeclaration.Members);
            }

#if false
            // Add this once we have the language version for C# that supports accessibility
            // modifiers on interface methods.
            if (option.Value == AccessibilityModifiersRequired.Always &&
                member.IsKind(SyntaxKind.InterfaceDeclaration, out typeDeclaration))
            {
                // Only recurse into an interface if the user wants accessibility modifiers on
                ProcessTypeDeclaration(context, generator, option, typeDeclaration);
            }
#endif

            // Have to have a name to report the issue on.
            var name = member.GetNameToken();
            if (name.Kind() == SyntaxKind.None)
            {
                return;
            }

            // Certain members never have accessibility. Don't bother reporting on them.
            if (!generator.CanHaveAccessibility(member))
            {
                return;
            }

            // This analyzer bases all of its decisions on the accessibility
            var accessibility = generator.GetAccessibility(member);

            // Omit will flag any accesibility values that exist and are default
            // The other options will remove or ignore accessibility
            var isOmit = option.Value == AccessibilityModifiersRequired.OmitIfDefault;

            if (isOmit)
            {
                if (accessibility == Accessibility.NotApplicable)
                {
                    return;
                }

                var parentKind = member.Parent.Kind();
                switch (parentKind)
                {
                // Check for default modifiers in namespace and outside of namespace
                case SyntaxKind.CompilationUnit:
                case SyntaxKind.NamespaceDeclaration:
                {
                    // Default is internal
                    if (accessibility != Accessibility.Internal)
                    {
                        return;
                    }
                }
                break;

                case SyntaxKind.ClassDeclaration:
                case SyntaxKind.StructDeclaration:
                {
                    // Inside a type, default is private
                    if (accessibility != Accessibility.Private)
                    {
                        return;
                    }
                }
                break;

                default:
                    return;     // Unknown parent kind, don't do anything
                }
            }
            else
            {
                // Mode is always, so we have to flag missing modifiers
                if (accessibility != Accessibility.NotApplicable)
                {
                    return;
                }
            }

            // Have an issue to flag, either add or remove. Report issue to user.
            var additionalLocations = ImmutableArray.Create(member.GetLocation());
            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         Descriptor,
                                         name.GetLocation(),
                                         option.Notification.Severity,
                                         additionalLocations: additionalLocations,
                                         properties: null));
        }
Beispiel #27
0
        private void AnalyzeOperation(OperationAnalysisContext context, INamedTypeSymbol?expressionTypeOpt)
        {
            var cancellationToken = context.CancellationToken;

            var throwOperation = (IThrowOperation)context.Operation;

            if (throwOperation.Exception == null)
            {
                return;
            }

            var throwStatementSyntax = throwOperation.Syntax;

            var semanticModel = context.Operation.SemanticModel;

            Contract.ThrowIfNull(semanticModel);

            var ifOperation = GetContainingIfOperation(
                semanticModel, throwOperation, cancellationToken);

            // This throw statement isn't parented by an if-statement.  Nothing to
            // do here.
            if (ifOperation == null)
            {
                return;
            }

            if (ifOperation.WhenFalse != null)
            {
                // Can't offer this if the 'if-statement' has an 'else-clause'.
                return;
            }

            var option = PreferThrowExpressionStyle(context);

            if (!option.Value)
            {
                return;
            }

            if (IsInExpressionTree(semanticModel, throwStatementSyntax, expressionTypeOpt, cancellationToken))
            {
                return;
            }

            if (ifOperation.Parent is not IBlockOperation containingBlock)
            {
                return;
            }

            if (!TryDecomposeIfCondition(ifOperation, out var localOrParameter))
            {
                return;
            }

            if (!TryFindAssignmentExpression(containingBlock, ifOperation, localOrParameter,
                                             out var expressionStatement, out var assignmentExpression))
            {
                return;
            }

            if (!localOrParameter.GetSymbolType().CanAddNullCheck())
            {
                return;
            }

            // We found an assignment using this local/parameter.  Now, just make sure there
            // were no intervening accesses between the check and the assignment.
            if (ValueIsAccessed(
                    semanticModel, ifOperation, containingBlock,
                    localOrParameter, expressionStatement, assignmentExpression))
            {
                return;
            }

            // Ok, there were no intervening writes or accesses.  This check+assignment can be simplified.
            var allLocations = ImmutableArray.Create(
                ifOperation.Syntax.GetLocation(),
                throwOperation.Exception.Syntax.GetLocation(),
                assignmentExpression.Value.Syntax.GetLocation());

            context.ReportDiagnostic(
                DiagnosticHelper.Create(Descriptor, throwStatementSyntax.GetLocation(), option.Notification.Severity, additionalLocations: allLocations, properties: null));
        }
Beispiel #28
0
        private void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext, INamedTypeSymbol expressionTypeOpt)
        {
            var options           = syntaxContext.Options;
            var syntaxTree        = syntaxContext.Node.SyntaxTree;
            var cancellationToken = syntaxContext.CancellationToken;
            var optionSet         = options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();

            if (optionSet == null)
            {
                return;
            }

            var styleOption = optionSet.GetOption(CSharpCodeStyleOptions.PreferLocalOverAnonymousFunction);

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

            // Local functions are only available in C# 7.0 and above.  Don't offer this refactoring
            // in projects targeting a lesser version.
            if (((CSharpParseOptions)syntaxTree.Options).LanguageVersion < LanguageVersion.CSharp7)
            {
                return;
            }

            var severity          = styleOption.Notification.Severity;
            var anonymousFunction = (AnonymousFunctionExpressionSyntax)syntaxContext.Node;

            var semanticModel = syntaxContext.SemanticModel;

            if (!CheckForPattern(semanticModel, anonymousFunction, cancellationToken,
                                 out var localDeclaration))
            {
                return;
            }

            if (localDeclaration.Declaration.Variables.Count != 1)
            {
                return;
            }

            if (!(localDeclaration.Parent is BlockSyntax block))
            {
                return;
            }

            // If there are compiler error on the declaration we can't reliably
            // tell that the refactoring will be accurate, so don't provide any
            // code diagnostics
            if (localDeclaration.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error))
            {
                return;
            }

            var local = semanticModel.GetDeclaredSymbol(localDeclaration.Declaration.Variables[0], cancellationToken);

            if (local == null)
            {
                return;
            }

            var delegateType = semanticModel.GetTypeInfo(anonymousFunction, cancellationToken).ConvertedType as INamedTypeSymbol;

            if (!delegateType.IsDelegateType() ||
                delegateType.DelegateInvokeMethod == null ||
                !CanReplaceDelegateWithLocalFunction(delegateType, localDeclaration, semanticModel, cancellationToken))
            {
                return;
            }

            if (!CanReplaceAnonymousWithLocalFunction(semanticModel, expressionTypeOpt, local, block, anonymousFunction, out var referenceLocations, cancellationToken))
            {
                return;
            }

            // Looks good!
            var additionalLocations = ImmutableArray.Create(
                localDeclaration.GetLocation(),
                anonymousFunction.GetLocation());

            additionalLocations = additionalLocations.AddRange(referenceLocations);

            if (severity.WithDefaultSeverity(DiagnosticSeverity.Hidden) < ReportDiagnostic.Hidden)
            {
                // If the diagnostic is not hidden, then just place the user visible part
                // on the local being initialized with the lambda.
                syntaxContext.ReportDiagnostic(DiagnosticHelper.Create(
                                                   Descriptor,
                                                   localDeclaration.Declaration.Variables[0].Identifier.GetLocation(),
                                                   severity,
                                                   additionalLocations,
                                                   properties: null));
            }
            else
            {
                // If the diagnostic is hidden, place it on the entire construct.
                syntaxContext.ReportDiagnostic(DiagnosticHelper.Create(
                                                   Descriptor,
                                                   localDeclaration.GetLocation(),
                                                   severity,
                                                   additionalLocations,
                                                   properties: null));

                var anonymousFunctionStatement = anonymousFunction.GetAncestor <StatementSyntax>();
                if (localDeclaration != anonymousFunctionStatement)
                {
                    syntaxContext.ReportDiagnostic(DiagnosticHelper.Create(
                                                       Descriptor,
                                                       anonymousFunctionStatement.GetLocation(),
                                                       severity,
                                                       additionalLocations,
                                                       properties: null));
                }
            }
        }
Beispiel #29
0
        private Diagnostic?TryGetDiagnostic(
            Compilation compilation,
            ISymbol symbol,
            AnalyzerOptions options,
            ConcurrentDictionary <Guid, ConcurrentDictionary <string, string?> > idToCachedResult,
            CancellationToken cancellationToken)
        {
            if (string.IsNullOrEmpty(symbol.Name))
            {
                return(null);
            }

            if (symbol is IMethodSymbol methodSymbol && methodSymbol.IsEntryPoint(compilation.TaskType(), compilation.TaskOfTType()))
            {
                return(null);
            }

            if (ShouldIgnore(symbol))
            {
                return(null);
            }

            if (symbol.IsSymbolWithSpecialDiscardName())
            {
                return(null);
            }

            var sourceTree = symbol.Locations.FirstOrDefault()?.SourceTree;

            if (sourceTree == null)
            {
                return(null);
            }

            var namingPreferences = options.GetAnalyzerOptions(sourceTree).NamingPreferences;
            var namingStyleRules  = namingPreferences.Rules;

            if (!namingStyleRules.TryGetApplicableRule(symbol, out var applicableRule) ||
                applicableRule.EnforcementLevel == ReportDiagnostic.Suppress)
            {
                return(null);
            }

            var cache = idToCachedResult.GetOrAdd(applicableRule.NamingStyle.ID, s_createCache);

            if (!cache.TryGetValue(symbol.Name, out var failureReason))
            {
                if (applicableRule.NamingStyle.IsNameCompliant(symbol.Name, out failureReason))
                {
                    failureReason = null;
                }

                cache.TryAdd(symbol.Name, failureReason);
            }

            if (failureReason == null)
            {
                return(null);
            }

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

            builder[nameof(NamingStyle)] = applicableRule.NamingStyle.CreateXElement().ToString();
            builder["OptionName"]        = nameof(NamingStyleOptions.NamingPreferences);
            builder["OptionLanguage"]    = compilation.Language;

            return(DiagnosticHelper.Create(Descriptor, symbol.Locations.First(), applicableRule.EnforcementLevel, additionalLocations: null, builder.ToImmutable(), failureReason));
        }
        private void ProcessUnreachableDiagnostic(
            SemanticModelAnalysisContext context,
            SyntaxNode root,
            TextSpan sourceSpan,
            bool fadeOutCode
            )
        {
            var node = root.FindNode(sourceSpan);

            // Note: this approach works as the language only supports the concept of
            // unreachable statements.  If we ever get unreachable subexpressions, then
            // we'll need to revise this code accordingly.
            var firstUnreachableStatement = node.FirstAncestorOrSelf <StatementSyntax>();

            if (
                firstUnreachableStatement == null ||
                firstUnreachableStatement.SpanStart != sourceSpan.Start
                )
            {
                return;
            }

            // At a high level, we can think about us wanting to fade out a "section" of unreachable
            // statements.  However, the compiler only reports the first statement in that "section".
            // We want to figure out what other statements are in that section and fade them all out
            // along with the first statement.  This is made somewhat tricky due to the fact that
            // subsequent sibling statements possibly being reachable due to explicit gotos+labels.
            //
            // On top of this, an unreachable section might not be contiguous.  This is possible
            // when there is unreachable code that contains a local function declaration in-situ.
            // This is legal, and the local function declaration may be called from other reachable code.
            //
            // As such, it's not possible to just get first unreachable statement, and the last, and
            // then report that whole region as unreachable.  Instead, when we are told about an
            // unreachable statement, we simply determine which other statements are also unreachable
            // and bucket them into contiguous chunks.
            //
            // We then fade each of these contiguous chunks, while also having each diagnostic we
            // report point back to the first unreachable statement so that we can easily determine
            // what to remove if the user fixes the issue.  (The fix itself has to go recompute this
            // as the total set of statements to remove may be larger than the actual faded code
            // that that diagnostic corresponds to).

            // Get the location of this first unreachable statement.  It will be given to all
            // the diagnostics we create off of this single compiler diagnostic so that we always
            // know how to find it regardless of which of our diagnostics the user invokes the
            // fix off of.
            var firstStatementLocation = root.SyntaxTree.GetLocation(
                firstUnreachableStatement.FullSpan
                );

            // 'additionalLocations' is how we always pass along the locaiton of the first unreachable
            // statement in this group.
            var additionalLocations = ImmutableArray.Create(firstStatementLocation);

            if (fadeOutCode)
            {
                context.ReportDiagnostic(
                    DiagnosticHelper.CreateWithLocationTags(
                        Descriptor,
                        firstStatementLocation,
                        ReportDiagnostic.Default,
                        additionalLocations: ImmutableArray <Location> .Empty,
                        additionalUnnecessaryLocations: additionalLocations
                        )
                    );
            }
            else
            {
                context.ReportDiagnostic(
                    Diagnostic.Create(Descriptor, firstStatementLocation, additionalLocations)
                    );
            }

            var sections = RemoveUnreachableCodeHelpers.GetSubsequentUnreachableSections(
                firstUnreachableStatement
                );

            foreach (var section in sections)
            {
                var span = TextSpan.FromBounds(
                    section[0].FullSpan.Start,
                    section.Last().FullSpan.End
                    );
                var location = root.SyntaxTree.GetLocation(span);
                var additionalUnnecessaryLocations = ImmutableArray <Location> .Empty;

                // Mark subsequent sections as being 'cascaded'.  We don't need to actually process them
                // when doing a fix-all as they'll be scooped up when we process the fix for the first
                // section.
                if (fadeOutCode)
                {
                    additionalUnnecessaryLocations = ImmutableArray.Create(location);
                }

                context.ReportDiagnostic(
                    DiagnosticHelper.CreateWithLocationTags(
                        Descriptor,
                        location,
                        ReportDiagnostic.Default,
                        additionalLocations,
                        additionalUnnecessaryLocations,
                        s_subsequentSectionProperties
                        )
                    );
            }
        }