private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var syntaxTree = context.Node.SyntaxTree;

            // Not available prior to C# 9.
            if (syntaxTree.Options.LanguageVersion() < LanguageVersion.CSharp9)
            {
                return;
            }

            var styleOption = context.GetCSharpAnalyzerOptions().ImplicitObjectCreationWhenTypeIsApparent;

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

            // type is apparent if we the object creation location is closely tied (spatially) to the explicit type.  Specifically:
            //
            // 1. Variable declarations.    i.e. `List<int> list = new ...`.  Note: we will suppress ourselves if this
            //    is a field and the 'var' preferences would lead to preferring this as `var list = ...`
            // 2. Expression-bodied constructs with an explicit return type.  i.e. `List<int> Prop => new ...` or
            //    `List<int> GetValue(...) => ...` The latter doesn't necessarily have the object creation spatially next to
            //    the type.  However, the type is always in a very easy to ascertain location in C#, so it is treated as
            //    apparent.

            var objectCreation = (ObjectCreationExpressionSyntax)context.Node;

            TypeSyntax?typeNode;
            var        semanticModel     = context.SemanticModel;
            var        cancellationToken = context.CancellationToken;

            if (objectCreation.Parent.IsKind(SyntaxKind.EqualsValueClause) &&
                objectCreation.Parent.Parent.IsKind(SyntaxKind.VariableDeclarator) &&
                objectCreation.Parent.Parent.Parent is VariableDeclarationSyntax variableDeclaration &&
                !variableDeclaration.Type.IsVar)
            {
                typeNode = variableDeclaration.Type;

                var helper = CSharpUseImplicitTypeHelper.Instance;
                if (helper.ShouldAnalyzeVariableDeclaration(variableDeclaration, cancellationToken))
                {
                    var simplifierOptions = context.GetCSharpAnalyzerOptions().GetSimplifierOptions();

                    if (helper.AnalyzeTypeName(typeNode, semanticModel, simplifierOptions, cancellationToken).IsStylePreferred)
                    {
                        // this is a case where the user would prefer 'var'.  don't offer to use an implicit object here.
                        return;
                    }
                }
            }
        private void AnalyzeSubpattern(SyntaxNodeAnalysisContext syntaxContext)
        {
            // Bail immediately if the user has disabled this feature.
            var styleOption = syntaxContext.GetCSharpAnalyzerOptions().PreferExtendedPropertyPattern;

            if (!styleOption.Value)
            {
                return;
            }

            var severity   = styleOption.Notification.Severity;
            var subpattern = (SubpatternSyntax)syntaxContext.Node;

            if (!SimplifyPropertyPatternHelpers.IsSimplifiable(subpattern, out _, out var expressionColon))
            {
                return;
            }

            // 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,
                                               expressionColon.GetLocation(),
                                               severity,
                                               ImmutableArray.Create(subpattern.GetLocation()),
                                               properties: null));
        }
Пример #3
0
        private void HandleVariableDeclaration(SyntaxNodeAnalysisContext context)
        {
            var declarationStatement = context.Node;
            var cancellationToken    = context.CancellationToken;

            var semanticModel = context.SemanticModel;
            var declaredType  = Helper.FindAnalyzableType(declarationStatement, semanticModel, cancellationToken);

            if (declaredType == null)
            {
                return;
            }

            var simplifierOptions = context.GetCSharpAnalyzerOptions().GetSimplifierOptions();

            var typeStyle = Helper.AnalyzeTypeName(
                declaredType, semanticModel, simplifierOptions, cancellationToken);

            if (!typeStyle.IsStylePreferred || !typeStyle.CanConvert())
            {
                return;
            }

            // The severity preference is not Hidden, as indicated by IsStylePreferred.
            var descriptor = Descriptor;

            context.ReportDiagnostic(CreateDiagnostic(descriptor, declarationStatement, declaredType.StripRefIfNeeded().Span, typeStyle.Severity));
        }
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var cancellationToken = context.CancellationToken;
            var syntaxTree        = context.Node.SyntaxTree;
            var preference        = context.GetCSharpAnalyzerOptions().PreferSimpleDefaultExpression;

            var parseOptions      = (CSharpParseOptions)syntaxTree.Options;
            var defaultExpression = (DefaultExpressionSyntax)context.Node;

            if (!defaultExpression.CanReplaceWithDefaultLiteral(parseOptions, preference.Value, context.SemanticModel, cancellationToken))
            {
                return;
            }

            var fadeSpan = TextSpan.FromBounds(defaultExpression.OpenParenToken.SpanStart, defaultExpression.CloseParenToken.Span.End);

            // Create a normal diagnostic that covers the entire default expression.
            context.ReportDiagnostic(
                DiagnosticHelper.CreateWithLocationTags(
                    Descriptor,
                    defaultExpression.GetLocation(),
                    preference.Notification.Severity,
                    additionalLocations: ImmutableArray <Location> .Empty,
                    additionalUnnecessaryLocations: ImmutableArray.Create(defaultExpression.SyntaxTree.GetLocation(fadeSpan))));
        }
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var localFunction = (LocalFunctionStatementSyntax)context.Node;

            if (localFunction.Modifiers.Any(SyntaxKind.StaticKeyword))
            {
                return;
            }

            var option = context.GetCSharpAnalyzerOptions().PreferStaticLocalFunction;

            if (!option.Value)
            {
                return;
            }

            var semanticModel = context.SemanticModel;

            if (MakeLocalFunctionStaticHelper.CanMakeLocalFunctionStaticBecauseNoCaptures(localFunction, semanticModel))
            {
                context.ReportDiagnostic(DiagnosticHelper.Create(
                                             Descriptor,
                                             localFunction.Identifier.GetLocation(),
                                             option.Notification.Severity,
                                             additionalLocations: ImmutableArray.Create(localFunction.GetLocation()),
                                             properties: null));
            }
        }
Пример #6
0
        private void AnalyzeNamespace(SyntaxNodeAnalysisContext context)
        {
            var namespaceDeclaration = (FileScopedNamespaceDeclarationSyntax)context.Node;

            var diagnostic = AnalyzeNamespace(context.GetCSharpAnalyzerOptions().NamespaceDeclarations, namespaceDeclaration);

            if (diagnostic != null)
            {
                context.ReportDiagnostic(diagnostic);
            }
        }
        private void AnalyzeNamespace(SyntaxNodeAnalysisContext context)
        {
            var namespaceDeclaration = (NamespaceDeclarationSyntax)context.Node;
            var syntaxTree           = namespaceDeclaration.SyntaxTree;

            var cancellationToken = context.CancellationToken;
            var root = (CompilationUnitSyntax)syntaxTree.GetRoot(cancellationToken);

            var diagnostic = AnalyzeNamespace(context.GetCSharpAnalyzerOptions().NamespaceDeclarations, root, namespaceDeclaration);

            if (diagnostic != null)
            {
                context.ReportDiagnostic(diagnostic);
            }
        }
        private void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext)
        {
            // Bail immediately if the user has disabled this feature.
            var styleOption = syntaxContext.GetCSharpAnalyzerOptions().PreferNotPattern;

            if (!styleOption.Value)
            {
                return;
            }

            // Look for the form: !(...)
            var node = syntaxContext.Node;

            if (node is not PrefixUnaryExpressionSyntax(SyntaxKind.LogicalNotExpression)
            {
                Operand : ParenthesizedExpressionSyntax parenthesizedExpression
            })
Пример #9
0
        private void ProcessCompilationUnit(SyntaxNodeAnalysisContext context)
        {
            // Don't want to suggest moving if the user doesn't have a preference for top-level-statements.
            var option = context.GetCSharpAnalyzerOptions().PreferTopLevelStatements;

            if (!CanOfferUseTopLevelStatements(option, forAnalyzer: true))
            {
                return;
            }

            var cancellationToken = context.CancellationToken;
            var semanticModel     = context.SemanticModel;
            var compilation       = semanticModel.Compilation;
            var mainTypeName      = GetMainTypeName(compilation);

            // Ok, the user does like top level statements.  Check if we can find a suitable hit in this type that
            // indicates we're on the entrypoint of the program.
            var root = (CompilationUnitSyntax)context.Node;
            var methodDeclarations = root.DescendantNodes(n => n is CompilationUnitSyntax or BaseNamespaceDeclarationSyntax or ClassDeclarationSyntax).OfType <MethodDeclarationSyntax>();

            foreach (var methodDeclaration in methodDeclarations)
            {
                if (IsProgramMainMethod(
                        semanticModel, methodDeclaration, mainTypeName, cancellationToken, out var canConvertToTopLevelStatement))
                {
                    if (canConvertToTopLevelStatement)
                    {
                        // Looks good.  Let the user know this type/method can be converted to a top level program.
                        var severity = option.Notification.Severity;
                        context.ReportDiagnostic(DiagnosticHelper.Create(
                                                     this.Descriptor,
                                                     GetUseTopLevelStatementsDiagnosticLocation(
                                                         methodDeclaration, isHidden: severity.WithDefaultSeverity(DiagnosticSeverity.Hidden) == ReportDiagnostic.Hidden),
                                                     severity,
                                                     ImmutableArray.Create(methodDeclaration.GetLocation()),
                                                     ImmutableDictionary <string, string?> .Empty));
                    }

                    // We found the main method, but it's not convertible, bail out as we have nothing else to do.
                    return;
                }
            }
        }
Пример #10
0
        private void ProcessCompilationUnit(SyntaxNodeAnalysisContext context)
        {
            var root   = (CompilationUnitSyntax)context.Node;
            var option = context.GetCSharpAnalyzerOptions().PreferTopLevelStatements;

            if (!CanOfferUseProgramMain(option, root, context.Compilation, forAnalyzer: true))
            {
                return;
            }

            var severity = option.Notification.Severity;

            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         this.Descriptor,
                                         GetUseProgramMainDiagnosticLocation(
                                             root, isHidden: severity.WithDefaultSeverity(DiagnosticSeverity.Hidden) == ReportDiagnostic.Hidden),
                                         severity,
                                         ImmutableArray <Location> .Empty,
                                         ImmutableDictionary <string, string?> .Empty));
        }
Пример #11
0
        private void AnalyzeNode(SyntaxNodeAnalysisContext context)
        {
            var option = context.GetCSharpAnalyzerOptions().PreferDeconstructedVariableDeclaration;

            if (!option.Value)
            {
                return;
            }

            switch (context.Node)
            {
            case VariableDeclarationSyntax variableDeclaration:
                AnalyzeVariableDeclaration(context, variableDeclaration, option.Notification.Severity);
                return;

            case ForEachStatementSyntax forEachStatement:
                AnalyzeForEachStatement(context, forEachStatement, option.Notification.Severity);
                return;
            }
        }
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var styleOption = context.GetCSharpAnalyzerOptions().PreferSwitchExpression;

            if (!styleOption.Value)
            {
                // User has disabled this feature.
                return;
            }

            var switchStatement = context.Node;

            if (switchStatement.GetDiagnostics().Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error))
            {
                return;
            }

            var(nodeToGenerate, declaratorToRemoveOpt) =
                Analyzer.Analyze(
                    (SwitchStatementSyntax)switchStatement,
                    context.SemanticModel,
                    out var shouldRemoveNextStatement);
            if (nodeToGenerate == default)
            {
                return;
            }

            var additionalLocations = ArrayBuilder <Location> .GetInstance();

            additionalLocations.Add(switchStatement.GetLocation());
            additionalLocations.AddOptional(declaratorToRemoveOpt?.GetLocation());

            context.ReportDiagnostic(DiagnosticHelper.Create(Descriptor,
                                                             // Report the diagnostic on the "switch" keyword.
                                                             location: switchStatement.GetFirstToken().GetLocation(),
                                                             effectiveSeverity: styleOption.Notification.Severity,
                                                             additionalLocations: additionalLocations.ToArrayAndFree(),
                                                             properties: ImmutableDictionary <string, string?> .Empty
                                                             .Add(Constants.NodeToGenerateKey, ((int)nodeToGenerate).ToString(CultureInfo.InvariantCulture))
                                                             .Add(Constants.ShouldRemoveNextStatementKey, shouldRemoveNextStatement.ToString(CultureInfo.InvariantCulture))));
        }
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var options = context.GetCSharpAnalyzerOptions().GetCodeGenerationOptions();

            var nodeKind = context.Node.Kind();

            // Don't offer a fix on an accessor, if we would also offer it on the property/indexer.
            if (UseExpressionBodyForAccessorsHelper.Instance.SyntaxKinds.Contains(nodeKind))
            {
                var grandparent = context.Node.GetRequiredParent().GetRequiredParent();

                if (grandparent.Kind() == SyntaxKind.PropertyDeclaration &&
                    AnalyzeSyntax(options, grandparent, UseExpressionBodyForPropertiesHelper.Instance) != null)
                {
                    return;
                }

                if (grandparent.Kind() == SyntaxKind.IndexerDeclaration &&
                    AnalyzeSyntax(options, grandparent, UseExpressionBodyForIndexersHelper.Instance) != null)
                {
                    return;
                }
            }

            foreach (var helper in _helpers)
            {
                if (helper.SyntaxKinds.Contains(nodeKind))
                {
                    var diagnostic = AnalyzeSyntax(options, context.Node, helper);
                    if (diagnostic != null)
                    {
                        context.ReportDiagnostic(diagnostic);
                        return;
                    }
                }
            }
        }
        private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, INamedTypeSymbol?expressionType)
        {
            var cancellationToken = context.CancellationToken;
            var semanticModel     = context.SemanticModel;
            var syntaxTree        = semanticModel.SyntaxTree;

            var preference = context.GetCSharpAnalyzerOptions().PreferMethodGroupConversion;

            if (preference.Notification.Severity == ReportDiagnostic.Suppress)
            {
                // User doesn't care about this rule.
                return;
            }

            var anonymousFunction = (AnonymousFunctionExpressionSyntax)context.Node;

            // Syntax checks first.

            // Don't simplify static lambdas.  The user made them explicitly static to make it clear it must only cause
            // a single allocation for the cached delegate. If we get rid of the lambda (and thus the static-keyword) it
            // won't be clear anymore if the member-group-conversion allocation is cached or not.
            if (anonymousFunction.Modifiers.Any(SyntaxKind.StaticKeyword))
            {
                return;
            }

            if (!TryGetAnonymousFunctionInvocation(anonymousFunction, out var invocation, out var wasAwaited))
            {
                return;
            }

            // If we had an async function, but we didn't await the expression inside then we can't convert this. The
            // underlying value was wrapped into a task, and that won't work if directly referencing the function.
            if (wasAwaited != anonymousFunction.Modifiers.Any(SyntaxKind.AsyncKeyword))
            {
                return;
            }

            // We have to have an invocation in the lambda like `() => X()` or `() => expr.X()`.
            var invokedExpression = invocation.Expression;

            if (invokedExpression is not SimpleNameSyntax and not MemberAccessExpressionSyntax)
            {
                return;
            }

            // lambda and invocation have to agree on number of parameters.
            var parameters = GetParameters(anonymousFunction);

            if (parameters.Count != invocation.ArgumentList.Arguments.Count)
            {
                return;
            }

            // parameters must be passed 1:1 from lambda to invocation.
            for (int i = 0, n = parameters.Count; i < n; i++)
            {
                var parameter = parameters[i];
                var argument  = invocation.ArgumentList.Arguments[i];

                if (argument.Expression is not IdentifierNameSyntax argumentIdentifier)
                {
                    return;
                }

                if (parameter.Identifier.ValueText != argumentIdentifier.Identifier.ValueText)
                {
                    return;
                }
            }

            // if we have `() => new C().X()` then converting to `new C().X` very much changes the meaning.
            if (MayHaveSideEffects(invokedExpression))
            {
                return;
            }

            // Looks like a reasonable candidate to simplify.  Now switch to semantics to check for sure.

            if (CSharpSemanticFacts.Instance.IsInExpressionTree(semanticModel, anonymousFunction, expressionType, cancellationToken))
            {
                return;
            }

            // If we have `object obj = x => Goo(x);` we don't want to simplify.  The compiler warns if you write
            // `object obj = Goo;` because of the conversion to a non-delegate type. While we could insert a cast here
            // to make this work, that goes against the spirit of this analyzer/fixer just removing code.
            var lambdaTypeInfo = semanticModel.GetTypeInfo(anonymousFunction, cancellationToken);

            if (lambdaTypeInfo.ConvertedType == null || lambdaTypeInfo.ConvertedType.SpecialType is SpecialType.System_Object)
            {
                return;
            }

            var lambdaSymbolInfo = semanticModel.GetSymbolInfo(anonymousFunction, cancellationToken);

            if (lambdaSymbolInfo.Symbol is not IMethodSymbol lambdaMethod)
            {
                return;
            }

            var invokedSymbolInfo = semanticModel.GetSymbolInfo(invokedExpression, cancellationToken);

            if (invokedSymbolInfo.Symbol is not IMethodSymbol invokedMethod)
            {
                return;
            }

            // If we're calling a generic method, we have to have supplied type arguments.  They cannot be inferred once
            // we remove the arguments during simplification.
            var invokedTypeArguments = invokedExpression.GetRightmostName() is GenericNameSyntax genericName
                ? genericName.TypeArgumentList.Arguments
                : default;

            if (invokedMethod.TypeArguments.Length != invokedTypeArguments.Count)
            {
                return;
            }

            // Methods have to be complimentary.  That means the same number of parameters, with proper
            // co-contravariance for the parameters and return type.
            if (lambdaMethod.Parameters.Length != invokedMethod.Parameters.Length)
            {
                return;
            }

            var compilation = semanticModel.Compilation;

            // Must be able to convert the invoked method return type to the lambda's return type.
            if (!IsIdentityOrImplicitConversion(compilation, invokedMethod.ReturnType, lambdaMethod.ReturnType))
            {
                return;
            }

            for (int i = 0, n = lambdaMethod.Parameters.Length; i < n; i++)
            {
                var lambdaParameter  = lambdaMethod.Parameters[i];
                var invokedParameter = invokedMethod.Parameters[i];

                if (lambdaParameter.RefKind != invokedParameter.RefKind)
                {
                    return;
                }

                // All the lambda parameters must be convertible to the invoked method parameters.
                if (!IsIdentityOrImplicitConversion(compilation, lambdaParameter.Type, invokedParameter.Type))
                {
                    return;
                }
            }

            // Semantically, this looks good to go.  Now, do an actual speculative replacement to ensure that the
            // non-invoked method reference refers to the same method symbol, and that it converts to the same type that
            // the lambda was.
            var analyzer = new SpeculationAnalyzer(anonymousFunction, invokedExpression, semanticModel, cancellationToken);

            var rewrittenExpression    = analyzer.ReplacedExpression;
            var rewrittenSemanticModel = analyzer.SpeculativeSemanticModel;

            var rewrittenSymbolInfo = rewrittenSemanticModel.GetSymbolInfo(rewrittenExpression, cancellationToken);

            if (rewrittenSymbolInfo.Symbol is not IMethodSymbol rewrittenMethod ||
                !invokedMethod.Equals(rewrittenMethod))
            {
                return;
            }

            var rewrittenConvertedType = rewrittenSemanticModel.GetTypeInfo(rewrittenExpression, cancellationToken).ConvertedType;

            if (!lambdaTypeInfo.ConvertedType.Equals(rewrittenConvertedType))
            {
                return;
            }

            if (OverloadsChanged(
                    semanticModel, anonymousFunction.GetRequiredParent(),
                    rewrittenSemanticModel, rewrittenExpression.GetRequiredParent(), cancellationToken))
            {
                return;
            }

            var startReportSpan = TextSpan.FromBounds(anonymousFunction.SpanStart, invokedExpression.SpanStart);
            var endReportSpan   = TextSpan.FromBounds(invokedExpression.Span.End, anonymousFunction.Span.End);

            context.ReportDiagnostic(DiagnosticHelper.CreateWithLocationTags(
                                         Descriptor,
                                         syntaxTree.GetLocation(startReportSpan),
                                         preference.Notification.Severity,
                                         additionalLocations: ImmutableArray.Create(anonymousFunction.GetLocation()),
                                         additionalUnnecessaryLocations: ImmutableArray.Create(
                                             syntaxTree.GetLocation(startReportSpan),
                                             syntaxTree.GetLocation(endReportSpan))));
        }
        private void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext)
        {
            var styleOption = syntaxContext.GetCSharpAnalyzerOptions().PreferConditionalDelegateCall;

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

            // look for the form "if (a != null)" or "if (null != a)"
            var ifStatement = (IfStatementSyntax)syntaxContext.Node;

            // ?. is only available in C# 6.0 and above.  Don't offer this refactoring
            // in projects targeting a lesser version.
            if (ifStatement.SyntaxTree.Options.LanguageVersion() < LanguageVersion.CSharp6)
            {
                return;
            }

            if (!ifStatement.Condition.IsKind(SyntaxKind.NotEqualsExpression))
            {
                return;
            }

            if (ifStatement.Else != null)
            {
                return;
            }

            // Check for both:  "if (...) { a(); }" and "if (...) a();"
            var innerStatement = ifStatement.Statement;

            if (innerStatement.IsKind(SyntaxKind.Block, out BlockSyntax? block))
            {
                if (block.Statements.Count != 1)
                {
                    return;
                }

                innerStatement = block.Statements[0];
            }

            if (!innerStatement.IsKind(SyntaxKind.ExpressionStatement, out ExpressionStatementSyntax? expressionStatement))
            {
                return;
            }

            // Check that it's of the form: "if (a != null) { a(); }
            if (expressionStatement.Expression is not InvocationExpressionSyntax invocationExpression)
            {
                return;
            }

            var severity  = styleOption.Notification.Severity;
            var condition = (BinaryExpressionSyntax)ifStatement.Condition;

            if (TryCheckVariableAndIfStatementForm(
                    syntaxContext, ifStatement, condition,
                    expressionStatement, invocationExpression,
                    severity))
            {
                return;
            }

            TryCheckSingleIfStatementForm(
                syntaxContext, ifStatement, condition,
                expressionStatement, invocationExpression,
                severity);
        }
Пример #16
0
        private void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext, INamedTypeSymbol?expressionType)
        {
            var styleOption = syntaxContext.GetCSharpAnalyzerOptions().PreferLocalOverAnonymousFunction;

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

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

            var semanticModel = syntaxContext.SemanticModel;

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

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

            if (localDeclaration.Parent is not 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 cancellationToken = syntaxContext.CancellationToken;
            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, expressionType, 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 (anonymousFunctionStatement != null && localDeclaration != anonymousFunctionStatement)
                {
                    syntaxContext.ReportDiagnostic(DiagnosticHelper.Create(
                                                       Descriptor,
                                                       anonymousFunctionStatement.GetLocation(),
                                                       severity,
                                                       additionalLocations,
                                                       properties: null));
                }
            }
        }
Пример #17
0
        private void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context, INamedTypeSymbol?expressionType)
        {
            var syntaxTree = context.Node.SyntaxTree;
            var csOptions  = (CSharpParseOptions)syntaxTree.Options;

            if (csOptions.LanguageVersion < LanguageVersion.CSharp7)
            {
                // out-vars are not supported prior to C# 7.0.
                return;
            }

            var option = context.GetCSharpAnalyzerOptions().PreferInlinedVariableDeclaration;

            if (!option.Value)
            {
                // Don't bother doing any work if the user doesn't even have this preference set.
                return;
            }

            var argumentNode = (ArgumentSyntax)context.Node;

            if (argumentNode.RefOrOutKeyword.Kind() != SyntaxKind.OutKeyword)
            {
                // Immediately bail if this is not an out-argument.  If it's not an out-argument
                // we clearly can't convert it to be an out-variable-declaration.
                return;
            }

            var argumentExpression = argumentNode.Expression;

            if (!argumentExpression.IsKind(SyntaxKind.IdentifierName, out IdentifierNameSyntax? identifierName))
            {
                // has to be exactly the form "out i".  i.e. "out this.i" or "out v[i]" are legal
                // cases for out-arguments, but could not be converted to an out-variable-declaration.
                return;
            }

            if (argumentNode.Parent is not ArgumentListSyntax argumentList)
            {
                return;
            }

            var invocationOrCreation = argumentList.Parent;

            if (!invocationOrCreation.IsKind(SyntaxKind.InvocationExpression) &&
                !invocationOrCreation.IsKind(SyntaxKind.ObjectCreationExpression))
            {
                // Out-variables are only legal with invocations and object creations.
                // If we don't have one of those bail.  Note: we need hte parent to be
                // one of these forms so we can accurately verify that inlining the
                // variable doesn't change semantics.
                return;
            }

            // Don't offer to inline variables named "_".  It can cause is to create a discard symbol
            // which would cause a break.
            if (identifierName.Identifier.ValueText == "_")
            {
                return;
            }

            var containingStatement = argumentExpression.FirstAncestorOrSelf <StatementSyntax>();

            if (containingStatement == null)
            {
                return;
            }

            var cancellationToken = context.CancellationToken;

            var semanticModel = context.SemanticModel;

            if (semanticModel.GetSymbolInfo(argumentExpression, cancellationToken).Symbol is not ILocalSymbol outLocalSymbol)
            {
                // The out-argument wasn't referencing a local.  So we don't have an local
                // declaration that we can attempt to inline here.
                return;
            }

            // Ensure that the local-symbol actually belongs to LocalDeclarationStatement.
            // Trying to do things like inline a var-decl in a for-statement is just too
            // esoteric and would make us have to write a lot more complex code to support
            // that scenario.
            var localReference = outLocalSymbol.DeclaringSyntaxReferences.FirstOrDefault();

            if (localReference?.GetSyntax(cancellationToken) is not VariableDeclaratorSyntax localDeclarator)
            {
                return;
            }

            var localDeclaration = localDeclarator.Parent as VariableDeclarationSyntax;

            if (localDeclaration?.Parent is not LocalDeclarationStatementSyntax localStatement)
            {
                return;
            }

            if (localDeclarator.SpanStart >= argumentNode.SpanStart)
            {
                // We have an error situation where the local was declared after the out-var.
                // Don't even bother offering anything here.
                return;
            }

            // If the local has an initializer, only allow the refactoring if it is initialized
            // with a simple literal or 'default' expression.  i.e. it's ok to inline "var v = 0"
            // since there are no side-effects of the initialization.  However something like
            // "var v = M()" should not be inlined as that could break program semantics.
            if (localDeclarator.Initializer != null)
            {
                if (localDeclarator.Initializer.Value is not LiteralExpressionSyntax and
                    not DefaultExpressionSyntax)
                {
                    return;
                }
            }

            // Get the block that the local is scoped inside of.  We'll search that block
            // for references to the local to make sure that no reads/writes happen before
            // the out-argument.  If there are any reads/writes we can't inline as those
            // accesses will become invalid.
            if (localStatement.Parent is not BlockSyntax enclosingBlockOfLocalStatement)
            {
                return;
            }

            if (argumentExpression.IsInExpressionTree(semanticModel, expressionType, cancellationToken))
            {
                // out-vars are not allowed inside expression-trees.  So don't offer to
                // fix if we're inside one.
                return;
            }

            // Find the scope that the out-declaration variable will live in after we
            // rewrite things.
            var outArgumentScope = GetOutArgumentScope(argumentExpression);

            if (outArgumentScope == null)
            {
                return;
            }

            if (!outLocalSymbol.CanSafelyMoveLocalToBlock(enclosingBlockOfLocalStatement, outArgumentScope))
            {
                return;
            }

            // Make sure that variable is not accessed outside of that scope.
            var dataFlow = semanticModel.AnalyzeDataFlow(outArgumentScope);

            if (dataFlow.ReadOutside.Contains(outLocalSymbol) || dataFlow.WrittenOutside.Contains(outLocalSymbol))
            {
                // The variable is read or written from outside the block that the new variable
                // would be scoped in.  This would cause a break.
                //
                // Note(cyrusn): We could still offer the refactoring, but just show an error in the
                // preview in this case.
                return;
            }

            // Make sure the variable isn't ever accessed before the usage in this out-var.
            if (IsAccessed(semanticModel, outLocalSymbol, enclosingBlockOfLocalStatement,
                           localStatement, argumentNode, cancellationToken))
            {
                return;
            }

            // See if inlining this variable would make it so that some variables were no
            // longer definitely assigned.
            if (WouldCauseDefiniteAssignmentErrors(semanticModel, localStatement,
                                                   enclosingBlockOfLocalStatement, outLocalSymbol))
            {
                return;
            }

            // Collect some useful nodes for the fix provider to use so it doesn't have to
            // find them again.
            var allLocations = ImmutableArray.Create(
                localDeclarator.GetLocation(),
                identifierName.GetLocation(),
                invocationOrCreation.GetLocation());

            // If the local variable only has one declarator, then report the suggestion on the whole
            // declaration.  Otherwise, return the suggestion only on the single declarator.
            var reportNode = localDeclaration.Variables.Count == 1
                ? (SyntaxNode)localDeclaration
                : localDeclarator;

            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         Descriptor,
                                         reportNode.GetLocation(),
                                         option.Notification.Severity,
                                         additionalLocations: allLocations,
                                         properties: null));
        }
        private void AnalyzeLocalDeclarationStatement(SyntaxNodeAnalysisContext syntaxContext)
        {
            var styleOption = syntaxContext.GetCSharpAnalyzerOptions().PreferTupleSwap;

            if (!styleOption.Value)
            {
                return;
            }

            var severity = styleOption.Notification.Severity;

            // `var expr_temp = expr_a`;
            var localDeclarationStatement = (LocalDeclarationStatementSyntax)syntaxContext.Node;

            if (localDeclarationStatement.UsingKeyword != default ||
                localDeclarationStatement.AwaitKeyword != default)
            {
                return;
            }

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

            var variableDeclarator    = localDeclarationStatement.Declaration.Variables.First();
            var localDeclarationExprA = variableDeclarator.Initializer?.Value.WalkDownParentheses();

            if (localDeclarationExprA == null)
            {
                return;
            }

            // `expr_a = expr_b`;
            var firstAssignmentStatement = localDeclarationStatement.GetNextStatement();

            if (!IsSimpleAssignment(firstAssignmentStatement, out var firstAssignmentExprA, out var firstAssignmentExprB))
            {
                return;
            }

            // `expr_b = expr_temp;`
            var secondAssignmentStatement = firstAssignmentStatement.GetNextStatement();

            if (!IsSimpleAssignment(secondAssignmentStatement, out var secondAssignmentExprB, out var secondAssignmentExprTemp))
            {
                return;
            }

            if (!localDeclarationExprA.IsEquivalentTo(firstAssignmentExprA, topLevel: false))
            {
                return;
            }

            if (!firstAssignmentExprB.IsEquivalentTo(secondAssignmentExprB, topLevel: false))
            {
                return;
            }

            if (secondAssignmentExprTemp is not IdentifierNameSyntax {
                Identifier : var secondAssignmentExprTempIdentifier
            })
        public void AnalyzeNode(SyntaxNodeAnalysisContext context)
        {
            var statement = context.Node;

            var option = context.GetCSharpAnalyzerOptions().PreferBraces;

            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, context.CancellationToken))
            {
                return;
            }

            var firstToken = statement.GetFirstToken();

            context.ReportDiagnostic(DiagnosticHelper.Create(
                                         Descriptor,
                                         firstToken.GetLocation(),
                                         option.Notification.Severity,
                                         additionalLocations: null,
                                         properties: null,
                                         SyntaxFacts.GetText(firstToken.Kind())));
        }
        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 (syntaxTree.Options.LanguageVersion() < LanguageVersion.CSharp7)
            {
                return;
            }

            var styleOption = syntaxContext.GetCSharpAnalyzerOptions().PreferPatternMatchingOverAsWithNullCheck;

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

            var comparison = (ExpressionSyntax)node;

            var(comparisonLeft, comparisonRight) = comparison switch
            {
                BinaryExpressionSyntax binaryExpression => (binaryExpression.Left, (SyntaxNode)binaryExpression.Right),
                IsPatternExpressionSyntax isPattern => (isPattern.Expression, isPattern.Pattern),
                _ => throw ExceptionUtilities.Unreachable,
            };
            var operand = GetNullCheckOperand(comparisonLeft, comparison.Kind(), comparisonRight)?.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?.SpecialType == SpecialType.System_Object)
                {
                    operand = castExpression.Expression;
                }
            }

            var cancellationToken = syntaxContext.CancellationToken;

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

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

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

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

            // Don't convert if the as is part of a using statement
            // eg using (var x = y as MyObject) { }
            if (localStatement is UsingStatementSyntax)
            {
                return;
            }

            // Don't convert if the as is part of a local declaration with a using keyword
            // eg using var x = y as MyObject;
            if (localStatement is LocalDeclarationStatementSyntax localDecl && localDecl.UsingKeyword != default)
            {
                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(semanticModel, cancellationToken))
                    {
                        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 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));
        }