public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SingleSyntaxTreeAnalysisContext analysisContext)
 {
     return(syntaxTree.GetRoot()
            .DescendantNodes()
            .OfType <AccessorDeclarationSyntax>()
            .Where
                (accessor =>
                accessor.Keyword.IsKind(SyntaxKind.GetKeyword) &&
                ((AccessorListSyntax)accessor.Parent).Accessors.Count == 1              // We must have only the get-accessor.
                &&
                accessor.FirstAncestorOrSelf <TBasePropertyDeclarationSyntax>() != null // We must be either in a property or in an indexer.
                &&
                (
                    // We have a get accessor with a body.
                    (
                        accessor.Body != null &&
                        accessor.Body.Statements.Count == 1 &&
                        accessor.Body.Statements[0].IsKind(SyntaxKind.ReturnStatement)
                    )
                    ||
                    // We have an expression bodied get accessor.
                    accessor.ExpressionBody != null
                )
                )
            .Select(accessor => new AnalysisResult
                    (
                        this,
                        analysisContext,
                        syntaxTree.FilePath,
                        accessor.Keyword,
                        accessor.FirstAncestorOrSelf <TBasePropertyDeclarationSyntax>()
                    )));
 }
Exemplo n.º 2
0
 public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SingleSyntaxTreeAnalysisContext analysisContext)
 {
     return(syntaxTree.GetRoot()
            .DescendantNodes()
            .OfType <DestructorDeclarationSyntax>()
            .Where
                (destructor =>
                destructor.Body != null &&
                destructor.Body.Statements.Count == 1 &&
                destructor.Body.Statements[0].IsKind(SyntaxKind.ExpressionStatement)
                )
            .Select(destructor => new AnalysisResult
                    (
                        this,
                        analysisContext,
                        syntaxTree.FilePath,
                        destructor.TildeToken,
                        destructor
                    )));
 }
Exemplo n.º 3
0
 public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SingleSyntaxTreeAnalysisContext analysisContext)
 {
     return(syntaxTree.GetRoot()
            .DescendantNodes()
            .OfType <AccessorDeclarationSyntax>()
            .Where
                (accessor =>
                accessor.Keyword.IsKind(SyntaxKind.SetKeyword) &&
                accessor.Body != null &&
                accessor.Body.Statements.Count == 1 &&
                accessor.Body.Statements[0].IsKind(SyntaxKind.ExpressionStatement) &&
                accessor.FirstAncestorOrSelf <TBasePropertyDeclarationSyntax>() != null // We must be either in a property or in an indexer.
                )
            .Select(accessor => new AnalysisResult
                    (
                        this,
                        analysisContext,
                        syntaxTree.FilePath,
                        accessor.Keyword,
                        accessor.FirstAncestorOrSelf <TBasePropertyDeclarationSyntax>()
                    )));
 }
 public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
 {
     return(Enumerable.Empty <AnalysisResult>());
 }
Exemplo n.º 5
0
        public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
        {
            return(syntaxTree.GetRoot()
                   .DescendantNodes()
                   .OfType <ThrowStatementSyntax>()
                   .Select(@throw => new
            {
                ThrowStatement = @throw,
                ParameterNameArgument = GetParameterNameArgumentIfThrowThrowsArgumentExceptionWithProperParameterName(@throw)
            })
                   .Where(throwWithParameterName => throwWithParameterName.ParameterNameArgument != null)
                   .Select(@throw => new AnalysisResult
                           (
                               this,
                               analysisContext,
                               syntaxTree.FilePath,
                               @throw.ParameterNameArgument.GetFirstToken(),
                               @throw.ThrowStatement.Expression
                           )));

            ArgumentSyntax GetParameterNameArgumentIfThrowThrowsArgumentExceptionWithProperParameterName(ThrowStatementSyntax throwStatementSyntax)
            {
                // Let's first do fast checks that quickly and cheaply eliminate obvious non-candidates.
                // We want to use the semantic model only if we are sure that we have candidates that are
                // very likely throw statements that we are looking for.
                if (!(throwStatementSyntax.Expression is ObjectCreationExpressionSyntax objectCreationExpression))
                {
                    return(null);
                }

                if (!KnownArgumentExceptions.Select(exception => exception.TypeName).Any(name =>
                                                                                         objectCreationExpression.Type.ToString().EndsWith(name, StringComparison.Ordinal)))
                {
                    return(null);
                }

                var argumentsThatCouldBeParameterNames = objectCreationExpression.ArgumentList.Arguments
                                                         .Where(argument =>
                                                                argument.Expression.IsKind(SyntaxKind.StringLiteralExpression) &&
                                                                ((LiteralExpressionSyntax)argument.Expression).Token.ValueText.CouldBePotentialParameterName()
                                                                )
                                                         .ToList();

                if (!argumentsThatCouldBeParameterNames.Any())
                {
                    return(null);
                }

                var parameterNamesAvailableInScopeOfThrow = throwStatementSyntax.GetParameterNamesVisibleInScope();

                var argumentsThatAreParameterNames = argumentsThatCouldBeParameterNames
                                                     .Where(argument => parameterNamesAvailableInScopeOfThrow.Contains(((LiteralExpressionSyntax)argument.Expression).Token.ValueText))
                                                     .ToList();

                if (!argumentsThatAreParameterNames.Any())
                {
                    return(null);
                }

                // If we reach this point it means that we have a throw statement that has a constructor call
                // that creates an exception whose name pretty much surely fits the name of the argument exceptions and whose
                // constructor argument, any or more of them, are strings that appear in the surrounding parameters names.
                // Which means we are in a real case most likely 99% percent sure that we have a fit. Cool :-)
                // Still, we have to check for those 1% since we could have a something like this:
                // throw new AlmostArgumentNullException("A message, although the first parameter should be parameter name ;-)", "parameterName");
                // For that check, we need semantic analysis.

                var constructorSymbol = (IMethodSymbol)semanticModel.GetSymbolInfo(objectCreationExpression).Symbol;

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

                // 1. Check that the created object is 100% one of the argument exceptions.
                var argumentException = KnownArgumentExceptions
                                        .FirstOrDefault(exception => exception.RepresentsType(constructorSymbol.ContainingType));

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

                // 2. Check that the constructor arguments that are strings with enclosing parameter names
                //    are passed as proper constructor parameters.
                return(argumentsThatAreParameterNames.FirstOrDefault(argument =>
                                                                     GetParameterNameBehindTheArgument(argument) == argumentException.ParameterName));


                string GetParameterNameBehindTheArgument(ArgumentSyntax argument)
                {
                    // If we are lucky and have a named argument ;-)
                    if (argument.NameColon != null)
                    {
                        var parameterNameText = argument.NameColon.Name?.Identifier.ValueText;
                        return(parameterNameText ?? string.Empty);
                    }

                    // We were nor lucky. We have a normal positional argument in the list of arguments.
                    var index = ((ArgumentListSyntax)argument.Parent).Arguments.IndexOf(argument);

                    return(index < 0 || index >= constructorSymbol.Parameters.Length
                        ? string.Empty
                        : constructorSymbol.Parameters[index].Name);
                }
            }
        }
 public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
 {
     return(syntaxTree.GetRoot()
            .DescendantNodes()
            .OfType <LocalFunctionStatementSyntax>()
            .Where
                (localFunction =>
                localFunction.Body != null &&
                localFunction.Body.Statements.Count == 1 &&
                localFunction.Body.Statements[0].IsKind(SyntaxKind.ExpressionStatement)
                )
            .Select(localFunction => new AnalysisResult
                    (
                        this,
                        analysisContext,
                        syntaxTree.FilePath,
                        localFunction.Identifier,
                        localFunction
                    )));
 }
Exemplo n.º 7
0
        public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
        {
            return(syntaxTree.GetRoot()
                   .DescendantNodes()
                   .OfKind(SyntaxKind.CoalesceExpression)
                   .Where(node =>
                          node.Parent?.IsKind(SyntaxKind.SimpleAssignmentExpression) == true &&
                          node.Parent is AssignmentExpressionSyntax assignment &&
                          assignment.Right == node &&
                          node is BinaryExpressionSyntax coalesceExpressionNode &&
                          assignment.Left != null && coalesceExpressionNode.Left != null &&
                          AssignmentTargetIsSameAsCoalesceOperatorLeftSide(assignment.Left, coalesceExpressionNode.Left) &&
                          // Ignore situations like: X = X ?? throw new Exception().
                          coalesceExpressionNode.Right?.IsKind(SyntaxKind.ThrowExpression) == false &&
                          // Don't offer suggestion if this is `X = X ?? ...` inside an object initializer like e.g. `new SomeClass { X = X ?? ... }`.
                          !assignment.Left.IsObjectInitializerNamedAssignmentIdentifier()
                          )
                   .Select(coalesceExpressionNode => new AnalysisResult
                           (
                               IsSideEffectFree(((AssignmentExpressionSyntax)coalesceExpressionNode.Parent).Left, true)
                        ? (ISharpenSuggestion)UseNullCoalescingAssignmentOperatorInsteadOfAssigningResultOfTheNullCoalescingOperator.Instance
                        : ConsiderUsingNullCoalescingAssignmentOperatorInsteadOfAssigningResultOfTheNullCoalescingOperator.Instance,
                               analysisContext,
                               syntaxTree.FilePath,
                               coalesceExpressionNode.Parent.GetFirstToken(),
                               coalesceExpressionNode.Parent
                           )));

            // We are changing the number of times the expression is executed from
            // twice to ones. This is potentially not side-effect-free in certain cases.
            // E.g. when expression looks like `SomeProperty.SomeOtherProperty.AgainSomeOtherProperty`
            // and there is an arbitrary logic running behind each of the properties.
            // Even in a simple case like `SomeProperty = SomeProperty ?? ...`
            // we can have side effects. In the above case, the setter is always called,
            // while `SomeProperty ??= ...` calls the setter only if SomeProperty is null.
            bool IsSideEffectFree(SyntaxNode expression, bool isTopLevel)
            {
                // This algorithm works "backwards". E.g for "this.a.b.c" the check goes
                // from "c" towards "this".

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

                // Expression basically has to be of the form "a.b.c", where all parts are locals,
                // parameters or fields. Basically, nothing that can cause arbitrary user code
                // execution when being evaluated by the compiler.

                // Referencing this or base like e.g. this.a.b.c causes no side effects itself.
                if (expression.IsThisExpression() || expression.IsBaseExpression())
                {
                    return(true);
                }

                if (expression.IsIdentifierName())
                {
                    return(IsSideEffectFreeSymbol(expression, isTopLevel));
                }

                if (expression.IsParenthesizedExpression())
                {
                    expression.GetPartsOfParenthesizedExpression(out _, out var parenthesizedExpression, out _);

                    return(IsSideEffectFree(parenthesizedExpression, isTopLevel));
                }

                if (expression.IsSimpleMemberAccessExpression())
                {
                    expression.GetPartsOfMemberAccessExpression(out var subExpression, out _, out _);

                    return(IsSideEffectFree(subExpression, isTopLevel: false) &&
                           IsSideEffectFreeSymbol(expression, isTopLevel));
                }

                if (expression.IsConditionalAccessExpression())
                {
                    expression.GetPartsOfConditionalAccessExpression(out var accessExpression, out _, out var whenNotNull);

                    return(IsSideEffectFree(accessExpression, isTopLevel: false) &&
                           IsSideEffectFree(whenNotNull, isTopLevel: false));
                }

                // Something we don't explicitly handle. Assume this may have side effects.
                return(false);

                bool IsSideEffectFreeSymbol(SyntaxNode node, bool isTopLevelNode)
                {
                    var symbolInfo = semanticModel.GetSymbolInfo(node);

                    if (symbolInfo.CandidateSymbols.Length > 0 || symbolInfo.Symbol == null)
                    {
                        // Couldn't bind successfully, assume that this might have side-effects.
                        return(false);
                    }

                    var symbol = symbolInfo.Symbol;

                    switch (symbol.Kind)
                    {
                    case SymbolKind.Namespace:
                    case SymbolKind.NamedType:
                    case SymbolKind.Field:
                    case SymbolKind.Parameter:
                    case SymbolKind.Local:
                        return(true);
                    }

                    if (symbol.Kind == SymbolKind.Property && isTopLevelNode)
                    {
                        // TODO: Check this. It looks like a bug for ??.
                        //       Will the setter be called?
                        //       What when it is ref-property?

                        // Note, this doesn't apply if the property is a ref-property. In that case, we'd
                        // go from a read and a write to just a read (and a write to it's returned ref
                        // value).
                        var property = (IPropertySymbol)symbol;
                        // TODO: There is no RefKind in this version of Roslyn that we use.
                        //       Investigate how to implement the below line in this version.
                        //       I guess that should be possible with the available RefCustomModifiers and ReturnsByRef.
                        //       So far we wil just ignore it.
                        //if (property.RefKind == RefKind.None)
                        //{
                        //    return true;
                        //}
                    }

                    return(false);
                }
            }

            bool AssignmentTargetIsSameAsCoalesceOperatorLeftSide(SyntaxNode assignmentTarget, SyntaxNode coalesceOperatorLeftSide)
            {
                // TODO: Implement symbol and not only text equality. E.g. this.x = x ?? ...
                return(SyntaxNodeFacts.AreEquivalent(assignmentTarget, coalesceOperatorLeftSide));
            }
        }
        public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
        {
            // TODO: At the moment, if an identifier is used in different syntaxTrees
            //       (very very very likely :-)) it will be reported several times in
            //       the result, once per syntax tree. So far we can live with that.
            //       Implement proper removal of duplicates.

            // TODO: What to do when a variable is declared by using the var keyword?
            //       At the moment we will simply pretend that the feature is there.
            //       It is planned and will be implemented one day.
            return(syntaxTree.GetRoot()
                   .DescendantNodes()
                   // TODO: Parameter declaration with null as default value: string parameter = null.
                   // TODO: Variable declaration with initialization to null: string variable = null.
                   // TODO: Property declaration with initialization to null: public string Property { get; } = null.
                   .OfAnyOfKinds
                   (
                       SyntaxKind.SimpleAssignmentExpression,  // identifier = null;
                       SyntaxKind.EqualsExpression,            // identifier == null;
                       SyntaxKind.NotEqualsExpression,         // identifier != null
                       SyntaxKind.VariableDeclarator,          // object _fieldIdentifier = null; object localVariableIdentifier = null;
                       SyntaxKind.ConditionalAccessExpression, // identifier?.Something;
                       SyntaxKind.CoalesceExpression           // identifier ?? something;
                   )
                   .Select(GetNullableIdentifierSymbol)
                   .Where(symbol => symbol?.IsImplicitlyDeclared == false)
                   .Distinct()
                   .Select(symbol => (symbol, declaringNode: GetSymbolDeclaringNode(symbol)))
                   .Where(NullableContextCanBeEnabledForIdentifier)
                   .Select(GetAnalysisResultInfo)
                   .Where(analysisResultInfo => analysisResultInfo.suggestion != null)
                   .Select(analysisResultInfo => new AnalysisResult
                           (
                               analysisResultInfo.suggestion,
                               // TODO-IG: This is actually wrong, a bug to be completely honest.
                               //          In a usual case, the project in which the filePath
                               //          really is, is different then the project in which
                               //          the currently analyzed syntax tree resides.
                               //          Which means that the information in the analysisContext
                               //          does not fit to filePath.
                               //          Luckily the consequences are not so tragical, the
                               //          whole path to the file will be wrong.
                               //          The file will be misplaced, essentially the file name
                               //          of the filePath will be placed with the LogicalFolderPath
                               //          of the analysisContext.
                               //          Know bug, let's leave it so far, we have more urgent things
                               //          to do in order to have a reasonable release before the
                               //          talk in Bangalore.
                               analysisContext,
                               analysisResultInfo.filePath,
                               analysisResultInfo.startingToken,
                               analysisResultInfo.displayTextNode
                           )));

            bool NullableContextCanBeEnabledForIdentifier((ISymbol symbol, SyntaxNode declaringNode) symbolAndDeclaringNode)
            {
                var(symbol, declaringNode) = symbolAndDeclaringNode;
                var filePath = declaringNode?.SyntaxTree?.FilePath;

                if (string.IsNullOrEmpty(filePath))
                {
                    return(false);
                }

                // We skip the check for IsGeneratedAssemblyInfo() so far.
                // To get the Document object at this point requires
                // a bit of refactoring. Plus, the chance that the declaring
                // node be in the generated AssemblyInfo is zero.
                // TODO-IG: Think of adding the check later.
                if (GeneratedCodeDetection.IsGeneratedFile(filePath) ||
                    declaringNode.SyntaxTree.BeginsWithAutoGeneratedComment())
                {
                    return(false);
                }

                // If the nullable context is already enabled we do not
                // want to show the suggestion. In that case the compiler
                // will provide the appropriate warnings.
                // The main idea behind this suggestion is to motive
                // programmers to enable nullable context and start using
                // nullable reference types!
                if (NullableContextIsAlreadyEnabled())
                {
                    return(false);
                }

                var typeSymbol = GetTypeSymbol();

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

                // For this, we want to have a special suggestion
                // that suggest to mark the type parameter as nullable.
                // So far, just ignore this case.
                // TODO-IG: Implement suggestion for marking unconstrained type parameters as nullable.
                if (typeSymbol.IsUnconstrainedTypeParameter())
                {
                    return(false);
                }

                if (IdentifierIsAlreadyNullable())
                {
                    return(false);
                }

                return(true);

                bool IdentifierIsAlreadyNullable()
                {
                    // This is VS2017 implementation.
                    // If the identifier is value type and is
                    // surely nullable it can only by Nullable<T> (T?).
                    // So, it is already nullable if it is a
                    // value type.
                    // TODO-IG: Solve how to approach to support for VS2017 and VS2019
                    //          at the same time and implement properly the VS2019
                    //          version of this logic.
                    //          Also, add the negative-case smoke tests to the
                    //          CSharp80.VS2019.csproj.
                    //          In VS2019 (means C# 8.0) we have nullable reference types ;-)
                    return(typeSymbol.IsValueType);
                }

                ITypeSymbol GetTypeSymbol()
                {
                    switch (symbol)
                    {
                    case IFieldSymbol fieldSymbol: return(fieldSymbol.Type);

                    case IPropertySymbol propertySymbol: return(propertySymbol.Type);

                    case IParameterSymbol parameterSymbol: return(parameterSymbol.Type);

                    case ILocalSymbol localSymbol: return(localSymbol.Type);

                    default: return(null);
                    }
                }

                bool NullableContextIsAlreadyEnabled()
                {
                    // TODO-IG: This is the implementation for VS2017.
                    //          Solve how to approach to support for VS2017 and VS2019
                    //          at the same time and implement properly the VS2019
                    //          version of this method.
                    //          Also, add the negative-case smoke tests to the
                    //          CSharp80.VS2019.csproj.
                    return(false);
                }
            }

            (ISharpenSuggestion suggestion,
             string filePath,
             SyntaxToken startingToken,
             SyntaxNode displayTextNode) GetAnalysisResultInfo((ISymbol symbol, SyntaxNode declaringNode) symbolAndDeclaringNode)
            {
                return
                    (
                    GetSuggestion(symbolAndDeclaringNode.symbol),
                    symbolAndDeclaringNode.declaringNode.SyntaxTree.FilePath,
                    GetStartingToken(symbolAndDeclaringNode.declaringNode),
                    symbolAndDeclaringNode.declaringNode
                    );
            }

            SyntaxToken GetStartingToken(SyntaxNode declaringNode)
            {
                // If we have more then one variable declared in the
                // declaration, we want to position the cursor to that variable.
                if (declaringNode.IsKind(SyntaxKind.VariableDeclarator) && // To avoid expensive type based checks.
                    declaringNode is VariableDeclaratorSyntax variableDeclarator &&
                    variableDeclarator.Parent is VariableDeclarationSyntax variableDeclaration &&
                    variableDeclaration.Variables.Count != 1)
                {
                    return(variableDeclarator.Identifier);
                }

                // In general, we would like to position the cursor
                // to the type part of the declaration, because that's
                // what should be changed in code (by adding ?).
                // But to identify it in general case is a bit tricky,
                // and case by case implementation is time consuming.
                // Therefore at the moment we will just position to the
                // beginning of the declaration.
                // TODO-IG: Position to type part of the declaration.
                return(declaringNode.GetFirstToken());
            }

            ISharpenSuggestion GetSuggestion(ISymbol symbol)
            {
                switch (symbol)
                {
                case IFieldSymbol _: return(EnableNullableContextAndDeclareReferenceFieldAsNullable.Instance);

                case IPropertySymbol _: return(EnableNullableContextAndDeclareReferencePropertyAsNullable.Instance);

                case IParameterSymbol _: return(EnableNullableContextAndDeclareReferenceParameterAsNullable.Instance);

                case ILocalSymbol _: return(EnableNullableContextAndDeclareReferenceVariableAsNullable.Instance);

                default: return(null);
                }
            }

            SyntaxNode GetSymbolDeclaringNode(ISymbol symbol)
            {
                return(symbol.DeclaringSyntaxReferences.Length != 1
                    ? null
                    : symbol.DeclaringSyntaxReferences[0].GetSyntax());
            }

            ISymbol GetNullableIdentifierSymbol(SyntaxNode node)
            {
                // TODO: Ignore the case where the comparison with null is actually a null guard.
                //       E.g.: if (identifier == null) throw ArgumentNullException(...);
                //       E.g.: identifier ?? throw ArgumentNullException(...);
                // TODO: If the "value" in the property setter is checked for null
                //       we could assume that the property is nullable. Think about it.
                switch (node)
                {
                case AssignmentExpressionSyntax assignment:
                    return(IsSurelyNullable(assignment.Right) &&
                           IsIdentifierOrPropertyAccess(assignment.Left)
                            ? semanticModel.GetSymbolInfo(assignment.Left).Symbol
                            : null);

                case BinaryExpressionSyntax binaryExpression:
                    switch (binaryExpression.Kind())
                    {
                    case SyntaxKind.EqualsExpression:
                    case SyntaxKind.NotEqualsExpression:
                        if (IsSurelyNullable(binaryExpression.Right) &&
                            IsIdentifierOrPropertyAccess(binaryExpression.Left))
                        {
                            return(semanticModel.GetSymbolInfo(binaryExpression.Left).Symbol);
                        }
                        if (IsSurelyNullable(binaryExpression.Left) &&
                            IsIdentifierOrPropertyAccess(binaryExpression.Right))
                        {
                            return(semanticModel.GetSymbolInfo(binaryExpression.Right).Symbol);
                        }
                        return(null);

                    case SyntaxKind.CoalesceExpression:
                        return(IsIdentifierOrPropertyAccess(binaryExpression.Left)
                                    ? semanticModel.GetSymbolInfo(binaryExpression.Left).Symbol
                                    : null);

                    default: return(null);
                    }

                case VariableDeclaratorSyntax variableDeclarator:
                    return(IsSurelyNullable(variableDeclarator.Initializer?.Value) &&
                           variableDeclarator.Identifier.IsKind(SyntaxKind.IdentifierToken)
                            ? semanticModel.GetDeclaredSymbol(variableDeclarator)
                            : null);

                case ConditionalAccessExpressionSyntax conditionalAccess:
                    return(IsIdentifierOrPropertyAccess(conditionalAccess.Expression)
                            ? semanticModel.GetSymbolInfo(conditionalAccess.Expression).Symbol
                            : null);

                default: return(null);
                }

                bool IsSurelyNullable(SyntaxNode potentiallyNullableNode)
                {
                    // We do not do any data flow analysis here, of course :-)
                    // Just identifying the obvious cases.

                    return(potentiallyNullableNode?.IsKind(SyntaxKind.NullLiteralExpression) == true
                           ||
                           potentiallyNullableNode?.IsKind(SyntaxKind.AsExpression) == true);
                }

                bool IsIdentifierOrPropertyAccess(SyntaxNode syntaxNode)
                {
                    return(syntaxNode?.IsAnyOfKinds(SyntaxKind.IdentifierName, SyntaxKind.SimpleMemberAccessExpression) == true);
                }
            }
        }
Exemplo n.º 9
0
 public FilePathTreeViewItem(BaseTreeViewItem parent, IAnalysisResultTreeViewBuilder treeViewBuilder, int numberOfItems, SingleSyntaxTreeAnalysisContext analysisContext, string filePath)
     : base(parent, treeViewBuilder, numberOfItems, null)
 {
     this.analysisContext = analysisContext;
     FilePath             = filePath;
 }
        public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
        {
            var descendantNodes = syntaxTree.GetRoot().DescendantNodes();

            // In unit test we of course expect people to test synchronous
            // versions of their methods. Moreover, we expect them to write
            // all kind of crazy acrobatics in tests in general.
            // Every suggestion to replace any synchronous method with their
            // async counterparts would very likely be totally
            // misplaced and misleading in unit tests. So we will simply
            // ignore these suggestions in unit tests.

            // It's fine to re-enumerate the nodes tree. It is a materialized structure.
            // ReSharper disable once PossibleMultipleEnumeration
            if (descendantNodes.ContainUnitTestingCode())
            {
                return(Enumerable.Empty <AnalysisResult>());
            }

            // ReSharper disable once PossibleMultipleEnumeration
            return(descendantNodes
                   .OfType <YieldStatementSyntax>()
                   .Select(yieldStatement => yieldStatement.FirstAncestorOrSelf <MethodDeclarationSyntax>())
                   // TODO: Yield must be in the method itself, not nested within a local function, or lambda, or delegate.
                   // TODO: Make it work also for local functions and not only for methods.
                   .Where(method => method != null)
                   .Distinct() // Because we can have more than one yield statement in a method.
                   .SelectMany(method => method.DescendantNodes().OfType <InvocationExpressionSyntax>())
                   .Where(InvokedMethodHasAsynchronousEquivalent)
                   .Select(invocation => new AnalysisResult(
                               ConsiderAwaitingEquivalentAsynchronousMethodAndYieldingIAsyncEnumerable.Instance,
                               analysisContext,
                               syntaxTree.FilePath,
                               GetStartingSyntaxNode(invocation).GetFirstToken(),
                               invocation
                               )));

            bool InvokedMethodHasAsynchronousEquivalent(InvocationExpressionSyntax invocation)
            {
                return(asynchronousMethodFinder.EquivalentAsynchronousCandidateExistsFor(invocation, semanticModel, EquivalentAsynchronousMethodFinder.CallerAsyncStatus.CallerMustNotBeAsync));
            }

            SyntaxNode GetStartingSyntaxNode(InvocationExpressionSyntax invocation)
            {
                if (!(invocation.Expression is MemberAccessExpressionSyntax memberAccess))
                {
                    return(invocation.Expression);
                }
                return(memberAccess.Name ?? (SyntaxNode)memberAccess);
            }
        }
Exemplo n.º 11
0
        public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
        {
            return(syntaxTree.GetRoot()
                   .DescendantNodes()
                   .OfAnyOfKinds(SyntaxKind.MethodDeclaration, SyntaxKind.LocalFunctionStatement)
                   .Where(methodOrLocalFunction => methodOrLocalFunction.IsAsync())
                   .SelectMany(methodOrLocalFunction => methodOrLocalFunction
                               .DescendantNodes()
                               // We can have both known methods like e.g. Wait() (invocation)
                               // and known properties like e.g. Result (simple member access).
                               .OfAnyOfKinds(SyntaxKind.InvocationExpression, SyntaxKind.SimpleMemberAccessExpression)
                               .Select(node =>
                                       (
                                           caller: methodOrLocalFunction,
                                           node
                                       )))
                   .Where(callerAndNode =>
                          !callerAndNode.node.IsWithinLambdaOrAnonymousMethod()
                          &&
                          InvocationIsDirectlyWithinTheCaller(callerAndNode.node, callerAndNode.caller)
                          &&
                          (
                              callerAndNode.node.IsKind(SyntaxKind.InvocationExpression) &&
                              ((InvocationExpressionSyntax)callerAndNode.node).GetInvokedMemberName() == replacementInfo.SynchronousMemberName
                              ||
                              callerAndNode.node.IsKind(SyntaxKind.SimpleMemberAccessExpression) &&
                              callerAndNode.node.Parent?.IsKind(SyntaxKind.InvocationExpression) == false &&
                              ((MemberAccessExpressionSyntax)callerAndNode.node).Name.Identifier.ValueText == replacementInfo.SynchronousMemberName
                          )
                          &&
                          InvocationHasAsynchronousEquivalentThatCanBeAwaited(callerAndNode.node)
                          )
                   .Select(callerAndNode => new AnalysisResult(
                               this,
                               analysisContext,
                               syntaxTree.FilePath,
                               GetStartingSyntaxNode(callerAndNode.node).GetFirstToken(),
                               callerAndNode.node.FirstAncestorOrSelf <StatementSyntax>() ?? callerAndNode.node
                               )));

            bool InvocationIsDirectlyWithinTheCaller(SyntaxNode invocation, SyntaxNode caller)
            {
                // We have to check that the invocation node is directly within
                // the caller, means it is not within some local function within the
                // caller.
                // In other words, there must not be any LocalFunctionStatementSyntax node
                // between the invocation and the caller.
                return(invocation.FirstAncestorOrSelfWithinEnclosingNode <LocalFunctionStatementSyntax>(caller, false) == null);
            }

            bool InvocationHasAsynchronousEquivalentThatCanBeAwaited(SyntaxNode invocation)
            {
                var invokedMember = semanticModel.GetSymbolInfo(invocation).Symbol;

                if (invokedMember?.ContainingType == null)
                {
                    return(false);
                }

                return(invokedMember.Name == replacementInfo.SynchronousMemberName &&
                       invokedMember.ContainingType.FullNameIsEqualTo(replacementInfo.SynchronousMemberTypeNamespace, replacementInfo.SynchronousMemberTypeName));
            }

            SyntaxNode GetStartingSyntaxNode(SyntaxNode node)
            {
                MemberAccessExpressionSyntax memberAccess;

                if (node.IsKind(SyntaxKind.InvocationExpression))
                {
                    var invocation = (InvocationExpressionSyntax)node;
                    memberAccess = invocation.Expression as MemberAccessExpressionSyntax;
                    if (memberAccess == null)
                    {
                        return(invocation.Expression);
                    }
                }
                else
                {
                    memberAccess = (MemberAccessExpressionSyntax)node;
                }

                return(memberAccess.Name ?? (SyntaxNode)memberAccess);
            }
        }
Exemplo n.º 12
0
        public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
        {
            var descendantNodes = syntaxTree.GetRoot().DescendantNodes();

            // In unit test we of course expect people to test synchronous
            // versions of their methods. Moreover, we expect them to write
            // all kind of crazy acrobatics in tests in general.
            // Every suggestion to replace any synchronous method with their
            // async counterparts would very likely be totally
            // misplaced and misleading in unit tests. So we will simply
            // ignore these suggestions in unit tests.

            // It's fine to re-enumerate the nodes tree. It is a materialized structure.
            // ReSharper disable once PossibleMultipleEnumeration
            if (descendantNodes.ContainUnitTestingCode())
            {
                return(Enumerable.Empty <AnalysisResult>());
            }

            // ReSharper disable once PossibleMultipleEnumeration
            return(descendantNodes
                   .OfType <InvocationExpressionSyntax>()
                   .Where(InvokedMethodHasAsynchronousEquivalent)
                   .Select(invocation => new AnalysisResult(
                               this,
                               analysisContext,
                               syntaxTree.FilePath,
                               GetStartingSyntaxNode(invocation).GetFirstToken(),
                               invocation
                               )));

            bool InvokedMethodHasAsynchronousEquivalent(InvocationExpressionSyntax invocation)
            {
                return(asynchronousMethodFinder.EquivalentAsynchronousCandidateExistsFor(invocation, semanticModel, callerAsyncStatus, callerYieldingStatus));
            }

            SyntaxNode GetStartingSyntaxNode(InvocationExpressionSyntax invocation)
            {
                if (!(invocation.Expression is MemberAccessExpressionSyntax memberAccess))
                {
                    return(invocation.Expression);
                }
                return(memberAccess.Name ?? (SyntaxNode)memberAccess);
            }
        }
Exemplo n.º 13
0
        public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
        {
            return(syntaxTree.GetRoot()
                   .DescendantNodes()
                   .OfType <VariableDeclarationSyntax>()
                   .Where(declaration =>
                          declaration.Parent?.IsAnyOfKinds(SyntaxKind.LocalDeclarationStatement, SyntaxKind.UsingStatement) == true
                          &&
                          DeclarationDoesNotUseVarKeyword(declaration)
                          &&
                          DeclarationDeclaresExactlyOneVariable(declaration)
                          &&
                          InitializationContainsOnlyObjectCreation(declaration)
                          &&
                          LeftSideTypeIsExactlyTheSameAsRightSideType(declaration)
                          // TODO-IG: Check that there is no type named var declared in the scope.
                          // TODO-IG: Check that there is now generic parameter named var in the scope.
                          )
                   .Select(declaration => new AnalysisResult
                           (
                               this,
                               analysisContext,
                               syntaxTree.FilePath,
                               declaration.GetFirstToken(),
                               declaration
                           )));

            bool DeclarationDoesNotUseVarKeyword(VariableDeclarationSyntax declaration)
            {
                return(declaration.Type?.IsVar == false);
            }

            bool DeclarationDeclaresExactlyOneVariable(VariableDeclarationSyntax declaration)
            {
                return(declaration.Variables.Count == 1);
            }

            bool InitializationContainsOnlyObjectCreation(VariableDeclarationSyntax declaration)
            {
                return(declaration.Variables[0]
                       .Initializer?.Value?.IsKind(SyntaxKind.ObjectCreationExpression) == true);
            }

            bool LeftSideTypeIsExactlyTheSameAsRightSideType(VariableDeclarationSyntax declaration)
            {
                var leftSideType = semanticModel.GetTypeInfo(declaration.Type).Type;

                if (leftSideType == null || leftSideType is IErrorTypeSymbol)
                {
                    return(false);
                }

                var objectCreationExpression = (ObjectCreationExpressionSyntax)declaration.Variables[0].Initializer.Value;
                var rightSideType            = semanticModel.GetTypeInfo(objectCreationExpression).Type;

                if (rightSideType == null || rightSideType is IErrorTypeSymbol)
                {
                    return(false);
                }

                return(leftSideType.Equals(rightSideType));
            }
        }
        public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
        {
            // TODO-IG: We can have more than one case section pointing to the same statements ;-)
            //          E.g. case 1: case 2: case 3: ...
            //          Implement also this case properly.
            //          One possibility could be, depending e.g. on the number of the cases pointing to
            //          the same statements to offer a "Consider" suggestion.

            return(syntaxTree.GetRoot()
                   .DescendantNodes()
                   .OfType <SwitchStatementSyntax>()
                   .Select(GetSwitchStatementPotentialReplaceabilityInfo)
                   .Where(replaceabilityInfo => replaceabilityInfo.switchStatement != null)
                   .Select(replaceabilityInfo => new AnalysisResult
                           (
                               GetSuggestion(replaceabilityInfo.category, replaceabilityInfo.isSurelyExhaustive),
                               analysisContext,
                               syntaxTree.FilePath,
                               replaceabilityInfo.switchStatement.SwitchKeyword,
                               replaceabilityInfo.switchStatement
                           )));

            ISharpenSuggestion GetSuggestion(SwitchStatementSectionsCategory category, bool isSurelyExhaustive)
            {
                // TODO-IG: It will be so nice to switch to switch statement one day we move Sharpen to C# 8.0 :-)
                if (category == SwitchStatementSectionsCategory.AllSwitchSectionsAreAssignmentsToTheSameIdentifier)
                {
                    return(isSurelyExhaustive
                        ? (ISharpenSuggestion)ReplaceSwitchStatementContainingOnlyAssignmentsWithSwitchExpression.Instance
                        : ConsiderReplacingSwitchStatementContainingOnlyAssignmentsWithSwitchExpression.Instance);
                }
                else
                {
                    return(isSurelyExhaustive
                        ? (ISharpenSuggestion)ReplaceSwitchStatementContainingOnlyReturnsWithSwitchExpression.Instance
                        : ConsiderReplacingSwitchStatementContainingOnlyReturnsWithSwitchExpression.Instance);
                }
            }

            (SwitchStatementSyntax switchStatement, SwitchStatementSectionsCategory category, bool isSurelyExhaustive) GetSwitchStatementPotentialReplaceabilityInfo(SwitchStatementSyntax switchStatement)
            {
                // We have to have at least one switch section (case or default).
                if (switchStatement.Sections.Count <= 0)
                {
                    return(null, SwitchStatementSectionsCategory.None, false);
                }

                // If we have the default section it is surely exhaustive.
                // Otherwise we cannot be sure. We will of course not do any
                // proper check that the compiler does.
                bool isSurelyExhaustive = switchStatement.Sections.Any(switchSection =>
                                                                       switchSection.Labels.Any(label => label.IsKind(SyntaxKind.DefaultSwitchLabel)));

                if (AllSwitchSectionsAreAssignmentsToTheSameIdentifier(switchStatement.Sections))
                {
                    return(switchStatement, SwitchStatementSectionsCategory.AllSwitchSectionsAreAssignmentsToTheSameIdentifier, isSurelyExhaustive);
                }

                if (AllSwitchSectionsAreReturnStatements(switchStatement.Sections))
                {
                    return(switchStatement, SwitchStatementSectionsCategory.AllSwitchSectionsAreReturnStatements, isSurelyExhaustive);
                }

                return(null, SwitchStatementSectionsCategory.None, false);

                bool AllSwitchSectionsAreAssignmentsToTheSameIdentifier(SyntaxList <SwitchSectionSyntax> switchSections)
                {
                    string previousIdentifier = null;

                    foreach (var switchSection in switchSections)
                    {
                        // TODO-IG: Valid case is also throwing an exception instead of assigning to an identifier.
                        // We expect exactly two statements, the assignment and the break;
                        if (switchSection.Statements.Count != 2)
                        {
                            return(false);
                        }
                        if (!switchSection.Statements[1].IsKind(SyntaxKind.BreakStatement))
                        {
                            return(false);
                        }

                        if (!(switchSection.Statements[0] is ExpressionStatementSyntax expression))
                        {
                            return(false);
                        }

                        if (!(expression.Expression is AssignmentExpressionSyntax assignment))
                        {
                            return(false);
                        }

                        // TODO-IG: Compare by symbol and not by name/text because we can have this._field, or something.Something, or a static access or similar.
                        var currentIdentifier = assignment.Left?.ToString();
                        if (previousIdentifier != null && previousIdentifier != currentIdentifier)
                        {
                            return(false);
                        }

                        previousIdentifier = currentIdentifier;
                    }

                    return(true);
                }

                bool AllSwitchSectionsAreReturnStatements(SyntaxList <SwitchSectionSyntax> switchSections)
                {
                    foreach (var switchSection in switchSections)
                    {
                        // TODO-IG: Valid case is also throwing an exception instead of assigning to an identifier.
                        // We expect exactly one statement, the return statement;
                        if (switchSection.Statements.Count != 1)
                        {
                            return(false);
                        }

                        if (!(switchSection.Statements[0] is ReturnStatementSyntax @return))
                        {
                            return(false);
                        }

                        if (@return.Expression == null)
                        {
                            return(false);
                        }
                    }

                    return(true);
                }
            }
        }
Exemplo n.º 15
0
        public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
        {
            return(syntaxTree.GetRoot()
                   .DescendantNodes()
                   .OfType <FieldDeclarationSyntax>()
                   .Select(GetPropertyNameArgumentsIfFieldDeclarationIsDependencyPropertyDeclaration)
                   .Where(propertyNames => propertyNames.Length > 0)
                   .SelectMany(propertyNames => propertyNames)
                   .Select(propertyName => new AnalysisResult
                           (
                               this,
                               analysisContext,
                               syntaxTree.FilePath,
                               propertyName.GetFirstToken(),
                               propertyName,
                               GetRegisterMethodDisplayText
                           )));

            ArgumentSyntax[] GetPropertyNameArgumentsIfFieldDeclarationIsDependencyPropertyDeclaration(FieldDeclarationSyntax fieldDeclaration)
            {
                // Let's first do fast checks that quickly and cheaply eliminate obvious non-candidates.
                // We want to use the semantic model only if we are sure that we have candidates that are
                // very likely field declarations that we are looking for.

                if (!(RequiredDependencyPropertyFieldModifiers.All(modifier =>
                                                                   fieldDeclaration.Modifiers.Select(token => token.Kind()).Contains(modifier)) &&
                      fieldDeclaration.Declaration.Type.GetLastToken().ValueText == "DependencyProperty"))
                {
                    return(Array.Empty <ArgumentSyntax>());
                }

                return(fieldDeclaration
                       .Declaration
                       .Variables
                       .Where(variableDeclaration =>
                              variableDeclaration.Identifier.ValueText.EndsWith("Property", StringComparison.Ordinal) &&
                              variableDeclaration.Initializer != null &&
                              variableDeclaration.Initializer.Value.IsKind(SyntaxKind.InvocationExpression))
                       .Select(variableDeclaration => new
                {
                    FieldName = variableDeclaration.Identifier.ValueText,
                    Invocation = (InvocationExpressionSyntax)variableDeclaration.Initializer.Value
                })
                       .Where(fieldNameAndInvocation =>
                              fieldNameAndInvocation.Invocation.Expression.GetLastToken().ValueText == "Register" &&
                              fieldNameAndInvocation.Invocation.ArgumentList.Arguments.Count > 0 &&
                              fieldNameAndInvocation.Invocation.ArgumentList.Arguments[0].Expression.IsKind(SyntaxKind.StringLiteralExpression) &&
                              fieldNameAndInvocation.Invocation.ArgumentList.Arguments[0].Expression.GetLastToken().ValueText is string propertyName &&
                              fieldNameAndInvocation.FieldName.StartsWith(propertyName, StringComparison.Ordinal) &&
                              fieldNameAndInvocation.FieldName.Length == propertyName.Length + "Property".Length && // Check that they are equal without creating temporary strings.
                              InstancePropertyWithPropertyNameExists(propertyName, fieldNameAndInvocation.Invocation)
                              &&
                              // If we reach this point it means that we have a field declaration that in a real case most likely
                              // 99.9999 % percent sure represents a dependency property declaration. Cool :-)
                              // Still, we have to check for those 0.00001% since we could have a situation sketched
                              // in the smoke tests, where the Register method or the DependencyProperty class are not the "real" one.
                              // I am asking myself now if this level of paranoia is really appropriate considering the cost of
                              // this additional analysis. Hmmm.
                              // TODO-THINK: We can provide an analysis option like "Use optimistic analysis" that would skip the below test.
                              semanticModel.GetSymbolInfo(fieldNameAndInvocation.Invocation).Symbol is IMethodSymbol method &&
                              method.ContainingType?.Name == "DependencyProperty" &&
                              (method.ContainingType.ContainingType == null || method.ContainingType.ContainingType.IsNamespace) && // It must not be nested in another type.
                              method.ContainingType.ContainingNamespace.FullNameIsEqualTo("System.Windows")
                                                                                                                                    // To be really sure, we should check here that the DependencyProperty type is
                                                                                                                                    // really the System.Windows.DependencyProperty. And this check would add a whole
                                                                                                                                    // bunch of super crazy paranoia on already paranoid check ;-)
                                                                                                                                    // Basically, the only possibility for this check to fail would be if someone
                                                                                                                                    // introduces it's own type named DependencyProperty that implicitly converts
                                                                                                                                    // the System.Windows.DependencyProperty to itself. This is demonstrated in
                                                                                                                                    // smoke tests but it is completely crazy. Who would ever do that?
                                                                                                                                    // And since this check would be quite complex to implement, we will simply skip it.
                              )
                       .Select(fieldNameAndInvocation => fieldNameAndInvocation.Invocation.ArgumentList.Arguments[0])
                       .ToArray());

                bool InstancePropertyWithPropertyNameExists(string propertyName, SyntaxNode anyChildNodeWithinTypeDeclaration)
                {
                    var typeDeclaration = anyChildNodeWithinTypeDeclaration.FirstAncestorOrSelf <TypeDeclarationSyntax>();

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

                    // We could cache the information about non static properties per type declaration
                    // to avoid traversing the tree every time.
                    // But in the real world the number of dependency properties per type is very moderate.
                    // This would most likely be a premature optimization.
                    // So let's leave it this way.
                    return(typeDeclaration
                           .DescendantNodes()
                           .OfType <PropertyDeclarationSyntax>(
                               )
                           .Any(property =>
                                property.Modifiers.All(token => !token.IsKind(SyntaxKind.StaticKeyword)) &&
                                property.Identifier.ValueText == propertyName
                                ));
                }
            }
        }
Exemplo n.º 16
0
 public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
 {
     return(syntaxTree.GetRoot()
            .DescendantNodes()
            .OfType <DefaultExpressionSyntax>()
            .Where(expression =>
                   expression.FirstAncestorOrSelf <ParameterSyntax>() != null &&
                   expression.FirstAncestorOrSelf <TBaseMethodDeclarationSyntax>() != null
                   )
            .Select(expression => new
     {
         Parameter = expression.FirstAncestorOrSelf <ParameterSyntax>(),
         DefaultExpressionType = semanticModel.GetTypeInfo(expression).Type,
         DefaultKeywordToken = expression.Keyword
     }
                    )
            .Where(types => types.DefaultExpressionType.Equals(semanticModel.GetTypeInfo(types.Parameter.Type).Type))
            .Select(types => new AnalysisResult
                    (
                        this,
                        analysisContext,
                        syntaxTree.FilePath,
                        types.DefaultKeywordToken,
                        types.Parameter
                    )));
 }
 public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
 {
     return(syntaxTree.GetRoot()
            .DescendantNodes()
            .OfType <ArgumentSyntax>()
            .Where(argument =>
                   argument.RefOrOutKeyword.IsKind(SyntaxKind.OutKeyword) &&
                   argument.Expression.IsKind(SyntaxKind.IdentifierName) &&
                   GetLocalVariableDeclaratorForIdentifier(argument, ((IdentifierNameSyntax)argument.Expression).Identifier) is VariableDeclaratorSyntax declarator &&
                   VariableIsNotUsedBeforePassedAsOutArgument(semanticModel, declarator, argument)
                   )
            .Select(argument => new AnalysisResult
                    (
                        this,
                        analysisContext,
                        syntaxTree.FilePath,
                        argument.RefOrOutKeyword,
                        argument.FirstAncestorOrSelf <InvocationExpressionSyntax>()
                    )));
 }
Exemplo n.º 18
0
 public IEnumerable<AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
 {
     return syntaxTree.GetRoot()
         .DescendantNodes()
         .OfKind(SyntaxKind.CoalesceExpression)
     );
 }
Exemplo n.º 19
0
        public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
        {
            bool shouldVarBeUsed(VariableDeclarationSyntax declaration)
            {
                var LHSType = semanticModel.GetTypeInfo(declaration.ChildNodes()?
                                                        .FirstOrDefault(syntax =>
                                                                        syntax is PredefinedTypeSyntax ||
                                                                        syntax is GenericNameSyntax ||
                                                                        syntax is QualifiedNameSyntax ||
                                                                        syntax is IdentifierNameSyntax)).Type;

                int totalDeclarationsInLine = declaration.DescendantNodes().Count(x => x is VariableDeclaratorSyntax);
                var RHSType = totalDeclarationsInLine > 1 ? null :
                              semanticModel.GetTypeInfo(
                    declaration.DescendantNodes()
                    .FirstOrDefault(node =>
                                    node is ObjectCreationExpressionSyntax).ChildNodes()?
                    .FirstOrDefault(syntax =>
                                    syntax is QualifiedNameSyntax ||
                                    syntax is GenericNameSyntax ||
                                    syntax is PredefinedTypeSyntax ||
                                    syntax is IdentifierNameSyntax
                                    ).Parent

                    ).Type;


                return((LHSType != null && RHSType != null) && (LHSType.Equals(RHSType)));
            }

            return(syntaxTree.GetRoot().DescendantNodes().OfType <VariableDeclarationSyntax>()
                   .Where(shouldVarBeUsed)
                   .Select(declaration => new AnalysisResult(
                               this,
                               analysisContext,
                               syntaxTree.FilePath,
                               declaration.GetFirstToken(),
                               declaration)));
        }
Exemplo n.º 20
0
 public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
 {
     return(syntaxTree.GetRoot()
            .DescendantNodes()
            .OfType <ConstructorDeclarationSyntax>()
            .Where
                (constructor =>
                constructor.Body != null &&
                constructor.Body.Statements.Count == 1 &&
                constructor.Body.Statements[0].IsKind(SyntaxKind.ExpressionStatement)
                )
            .Select(constructor => new AnalysisResult
                    (
                        this,
                        analysisContext,
                        syntaxTree.FilePath,
                        constructor.Identifier,
                        constructor
                    )));
 }
        public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
        {
            // TODO: What to do when a variable is declared by using the var keyword?
            return(syntaxTree.GetRoot()
                   .DescendantNodes()
                   .Where(node => node.IsAnyOfKinds
                          (
                              // TODO: Parameter declaration with null as default value: string parameter = null.
                              // TODO: Variable declaration with initialization to null: string variable = null.
                              // TODO: Field declaration with initialization to null: private string _field = null.
                              // TODO: Property declaration with initialization to null: public string Property { get; } = null.
                              // TODO: Usage of ??: x = identifier ?? something.
                              // TODO: Usage of ?.: identifier?.Something.
                              // TODO: Usage of as: identifier = something as Something.
                              SyntaxKind.SimpleAssignmentExpression,
                              SyntaxKind.EqualsExpression,
                              SyntaxKind.NotEqualsExpression
                          ))
                   .Select(GetNullableIdentifierNode)
                   .Where(identifier => identifier != null)
                   .Select(identifier => semanticModel.GetSymbolInfo(identifier).Symbol)
                   .Where(symbol => symbol?.IsImplicitlyDeclared == false)
                   .Distinct()
                   .Select(symbol => (symbol, declaringNode: GetSymbolDeclaringNode(symbol)))
                   .Where(symbolAndDeclaringNode => !string.IsNullOrEmpty(symbolAndDeclaringNode.declaringNode?.SyntaxTree?.FilePath))
                   .Select(symbolAndDeclaringNode =>
            {
                var(startingToken, displayTextNode) = GetStartingTokenAndDisplayTextNode(symbolAndDeclaringNode.declaringNode);

                return new AnalysisResult
                (
                    GetSuggestion(symbolAndDeclaringNode.symbol),
                    analysisContext,
                    symbolAndDeclaringNode.declaringNode.SyntaxTree.FilePath,
                    startingToken,
                    displayTextNode
                );
            }));

            (SyntaxToken startingToken, SyntaxNode displayTextNode) GetStartingTokenAndDisplayTextNode(SyntaxNode symbolDeclaringNode)
            {
                return(symbolDeclaringNode.GetFirstToken(), symbolDeclaringNode);
            }

            ISharpenSuggestion GetSuggestion(ISymbol symbol)
            {
                switch (symbol)
                {
                case IFieldSymbol _: return(EnableNullableContextAndDeclareReferenceFieldAsNullable.Instance);

                case IPropertySymbol _: return(EnableNullableContextAndDeclareReferencePropertyAsNullable.Instance);

                case IParameterSymbol _: return(EnableNullableContextAndDeclareReferenceParameterAsNullable.Instance);

                case ILocalSymbol _: return(EnableNullableContextAndDeclareReferenceVariableAsNullable.Instance);

                // TODO: The default case should never happen. See what to return.
                default: return(EnableNullableContextAndDeclareReferenceFieldAsNullable.Instance);
                }
            }

            SyntaxNode GetSymbolDeclaringNode(ISymbol symbol)
            {
                return(symbol.DeclaringSyntaxReferences.Length != 1
                    ? null
                    : symbol.DeclaringSyntaxReferences[0].GetSyntax());
            }

            SyntaxNode GetNullableIdentifierNode(SyntaxNode node)
            {
                // TODO: Ignore the case where the comparison with null is actually a null guard.
                //       E.g.: if (identifier == null) throw ArgumentNullException(...);
                //       E.g.: identifier ?? throw ArgumentNullException(...);
                // TODO: If the "value" in the property setter is checked for null
                //       we could assume that the property is nullable. Think about it.
                switch (node)
                {
                case AssignmentExpressionSyntax assignment:
                    return(assignment.Right?.IsKind(SyntaxKind.NullLiteralExpression) == true &&
                           assignment.Left?.IsAnyOfKinds(SyntaxKind.IdentifierName, SyntaxKind.SimpleMemberAccessExpression) == true
                            ? assignment.Left
                            : null);

                case BinaryExpressionSyntax comparison:
                    return(comparison.Right?.IsKind(SyntaxKind.NullLiteralExpression) == true &&
                           comparison.Left?.IsAnyOfKinds(SyntaxKind.IdentifierName, SyntaxKind.SimpleMemberAccessExpression) == true
                            ? comparison.Left
                            : null);

                default: return(null);
                }
            }
        }
Exemplo n.º 22
0
 public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
 {
     return(syntaxTree.GetRoot()
            .DescendantNodes()
            .OfType <DefaultExpressionSyntax>()
            .Where(expression => expression.Parent.IsKind(SyntaxKind.ReturnStatement))
            .Select(expression => new
     {
         ReturnStatement = (ReturnStatementSyntax)expression.Parent,
         DefaultExpressionType = semanticModel.GetTypeInfo(expression).Type,
         EnclosingDeclarationReturnType = GetEnclosingDeclarationReturnType(expression, semanticModel)
     }
                    )
            // DefaultExpressionType is never null and the EnclosingDeclarationReturnType can so far be null.
            // That's why it is important to call Equals exactly on the DefaultExpressionType.
            .Where(types => types.DefaultExpressionType.Equals(types.EnclosingDeclarationReturnType))
            .Select(types => new AnalysisResult
                    (
                        this,
                        analysisContext,
                        syntaxTree.FilePath,
                        types.ReturnStatement.ReturnKeyword,
                        types.ReturnStatement
                    )));
 }
Exemplo n.º 23
0
        public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
        {
            return(syntaxTree.GetRoot()
                   .DescendantNodes()
                   .OfType <SwitchStatementSyntax>()
                   .Select(GetSwitchStatementPotentialReplaceabilityInfo)
                   .Where(replaceabilityInfo => replaceabilityInfo.suggestion != null)
                   .Select(replaceabilityInfo => new AnalysisResult
                           (
                               replaceabilityInfo.suggestion,
                               analysisContext,
                               syntaxTree.FilePath,
                               replaceabilityInfo.switchStatement.SwitchKeyword,
                               replaceabilityInfo.switchStatement
                           )));

            (ISharpenSuggestion suggestion,
             SwitchStatementSyntax switchStatement)
            GetSwitchStatementPotentialReplaceabilityInfo(SwitchStatementSyntax switchStatement)
            {
                // We have to have at least one switch section (case or default).
                if (switchStatement.Sections.Count <= 0)
                {
                    return(null, null);
                }

                // TODO-IG: We can have more than one case label within a single section.
                //          E.g. case 1: case 2: case 3: ...
                //          At the moment we will simply not support this case.
                //          We insist so far on a single label.
                //          Implement this case in the future by offering a "Consider" suggestion.
                if (switchStatement.Sections.Any(switchSection => switchSection.Labels.Count != 1))
                {
                    return(null, null);
                }

                // If we have the default section it is surely exhaustive.
                // Otherwise we cannot be sure. We will, of course, not do any
                // check for exhaustiveness that the compiler does.
                bool isSurelyExhaustive = switchStatement.Sections.Any(switchSection =>
                                                                       switchSection.Labels.Any(label => label.IsKind(SyntaxKind.DefaultSwitchLabel)));

                if (AllSwitchSectionsAreAssignmentsToTheSameIdentifier(switchStatement.Sections))
                {
                    return
                        (
                        isSurelyExhaustive
                            ? (ISharpenSuggestion)ReplaceSwitchStatementContainingOnlyAssignmentsWithSwitchExpression.Instance
                            : ConsiderReplacingSwitchStatementContainingOnlyAssignmentsWithSwitchExpression.Instance,
                        switchStatement
                        );
                }

                if (AllSwitchSectionsAreReturnStatements(switchStatement.Sections))
                {
                    return
                        (
                        isSurelyExhaustive
                            ? (ISharpenSuggestion)ReplaceSwitchStatementContainingOnlyReturnsWithSwitchExpression.Instance
                            : ConsiderReplacingSwitchStatementContainingOnlyReturnsWithSwitchExpression.Instance,
                        switchStatement
                        );
                }

                return(null, null);

                bool AllSwitchSectionsAreAssignmentsToTheSameIdentifier(SyntaxList <SwitchSectionSyntax> switchSections)
                {
                    ISymbol previousIdentifierSymbol = null;

                    foreach (var switchSection in switchSections)
                    {
                        // We can have two cases that are valid.
                        switch (switchSection.Statements.Count)
                        {
                        // We have only one statement which then must be exception throwing.
                        case 1:
                            if (!switchSection.Statements[0].IsKind(SyntaxKind.ThrowStatement))
                            {
                                return(false);
                            }
                            break;

                        // We have two statements, which then must be an assignment immediately followed by break.
                        case 2:
                            if (!switchSection.Statements[1].IsKind(SyntaxKind.BreakStatement))
                            {
                                return(false);
                            }
                            if (!(switchSection.Statements[0] is ExpressionStatementSyntax expression))
                            {
                                return(false);
                            }
                            if (!(expression.Expression is AssignmentExpressionSyntax assignment))
                            {
                                return(false);
                            }

                            // TODO-IG: At the moment we do not support compound assignments (+=, *=, -=, /=, ...).
                            //          We insist so far on the simple assignment operator (=).
                            //          Implement this case in the future by offering a "Consider" suggestion.
                            if (!assignment.IsKind(SyntaxKind.SimpleAssignmentExpression))
                            {
                                return(false);
                            }

                            if (assignment.Left == null)
                            {
                                return(false);
                            }

                            var currentIdentifierSymbol = semanticModel.GetSymbolInfo(assignment.Left).Symbol;
                            if (currentIdentifierSymbol == null)
                            {
                                return(false);
                            }

                            if (previousIdentifierSymbol != null && !previousIdentifierSymbol.Equals(currentIdentifierSymbol))
                            {
                                return(false);
                            }

                            previousIdentifierSymbol = currentIdentifierSymbol;
                            break;

                        default: return(false);
                        }
                    }

                    return(true);
                }

                bool AllSwitchSectionsAreReturnStatements(SyntaxList <SwitchSectionSyntax> switchSections)
                {
                    foreach (var switchSection in switchSections)
                    {
                        // Valid cases are either throwing an exception or having return.
                        // In both cases we expect exactly one statement.
                        if (switchSection.Statements.Count != 1)
                        {
                            return(false);
                        }

                        switch (switchSection.Statements[0].Kind())
                        {
                        case SyntaxKind.ReturnStatement:
                            var returnStatement = (ReturnStatementSyntax)switchSection.Statements[0];
                            // The statement must return something.
                            // We are not interested in checking the type
                            // of the returned expression, checking that
                            // they are always the same. We assume that the switch
                            // statement is already valid.
                            if (returnStatement.Expression == null)
                            {
                                return(false);
                            }
                            break;

                        case SyntaxKind.ThrowStatement:
                            // This is just fine.
                            break;

                        default: return(false);
                        }
                    }

                    return(true);
                }
            }
        }
        public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
        {
            return(syntaxTree.GetRoot()
                   .DescendantNodes()
                   .OfType <UsingStatementSyntax>()
                   .Select(GetUsingStatementPotentialReplaceabilityInfo)
                   .Where(replaceabilityInfo => replaceabilityInfo.suggestion != null)
                   .Select(replaceabilityInfo => new AnalysisResult
                           (
                               replaceabilityInfo.suggestion,
                               analysisContext,
                               syntaxTree.FilePath,
                               replaceabilityInfo.usingStatement.UsingKeyword,
                               replaceabilityInfo.usingStatement
                           )));

            (ISharpenSuggestion suggestion,
             UsingStatementSyntax usingStatement)
            GetUsingStatementPotentialReplaceabilityInfo(UsingStatementSyntax usingStatement)
            {
                var outermostUsing = usingStatement;

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

                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(null, null);
                    }
                }

                // Verify that changing this using-statement into a using-declaration will not
                // change semantics.
                var parentStatements = parentBlock.Statements;
                var index            = parentStatements.IndexOf(outermostUsing);

                // TODO: At the moment, if we have jumps (OMG!) we will simply not deal with it at all.
                if (!UsingStatementDoesNotInvolveJumps())
                {
                    return(null, null);
                }

                // Everything is fine, the using statement *could* be replaced
                // with the using declaration, the potential leakage of the using value
                // determines if it is 100% save to do it or not.
                return
                    (
                    UsingValueDoesNotLeakToFollowingStatements()
                        ? (ISharpenSuggestion)ReplaceUsingStatementWithUsingDeclaration.Instance
                        : ConsiderReplacingUsingStatementWithUsingDeclaration.Instance,
                    usingStatement
                    );

                bool UsingValueDoesNotLeakToFollowingStatements()
                {
                    // Has to be one of the following forms:
                    // 1. Using statement is the last statement in the parent.
                    // 2. Using statement is not the last statement in parent, but is followed by
                    //    something that is unaffected by simplifying the using statement.  I.e.
                    //    `return`/`break`/`continue`.  *Note*.  `return expr` would *not* be ok.
                    //    In that case, `expr` would now be evaluated *before* the using disposed
                    //    the resource, instead of afterwards.  Effectively, the statement following
                    //    cannot actually execute any code that might depend on the Dispose() method
                    //    being called or not.

                    // Very last statement in the block. Can be converted.
                    if (index == parentStatements.Count - 1)
                    {
                        return(true);
                    }

                    // Not the last statement, get the next statement and examine that.
                    var nextStatement = parentStatements[index + 1];

                    // Using statement followed by break/continue. Can convert this as executing
                    // the break/continue will cause the code to exit the using scope, causing
                    // Dispose to be called at the same place as before.
                    if (nextStatement is BreakStatementSyntax ||
                        nextStatement is ContinueStatementSyntax)
                    {
                        return(true);
                    }

                    // Using statement followed by `return`. Can convert this as executing
                    // the `return` will cause the code to exit the using scope, causing
                    // Dispose() to be called at the same place as before.

                    // Note: the expr has to be null.  If it was non-null, then the expr would
                    // now execute before the using called Dispose() instead of after, potentially
                    // changing semantics.
                    if (nextStatement is ReturnStatementSyntax returnStatement &&
                        returnStatement.Expression == null)
                    {
                        return(true);
                    }

                    // TODO-IG: I've just discovered a bug in the Roslyn implementation :-)
                    //          Local function declarations should also be allowed after the
                    //          using since they are not executed. Open issue on Roslyn on
                    //          GitHub and also provide a fix.

                    return(false);
                }

                bool UsingStatementDoesNotInvolveJumps()
                {
                    // Jumps are not allowed to cross a using declaration in the forward direction,
                    // and can't go back unless there is a curly brace between the using and the label.
                    // We conservatively implement this by disallowing the change if there are gotos/labels
                    // in the containing block, or inside the using body.

                    // TODO-IG: Check for after as well in the case of "Consider" suggestion where we will have statements after the using.
                    // Note: we only have to check up to the `using`, since the checks below in
                    // UsingValueDoesNotLeakToFollowingStatements ensure that there would be no
                    // labels/gotos *after* the using statement.
                    for (int i = 0; i < index; i++)
                    {
                        var priorStatement = parentStatements[i];
                        if (IsGotoOrLabeledStatement(priorStatement))
                        {
                            return(false);
                        }
                    }

                    var innerStatements = innermostUsing.Statement is BlockSyntax block
                        ? block.Statements
                                          // TODO-IG: See how to create a SyntaxList. This version of Roslyn does not support the constructor with parameters.
                        : new SyntaxList <StatementSyntax>().Add(innermostUsing.Statement);

                    foreach (var statement in innerStatements)
                    {
                        if (IsGotoOrLabeledStatement(statement))
                        {
                            return(false);
                        }
                    }

                    return(true);

                    bool IsGotoOrLabeledStatement(StatementSyntax priorStatement)
                    => priorStatement.Kind() == SyntaxKind.GotoStatement ||
                    priorStatement.Kind() == SyntaxKind.LabeledStatement;
                }
            }
        }
Exemplo n.º 25
0
 public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
 {
     return(syntaxTree.GetRoot()
            .DescendantNodes()
            .OfType <ArgumentSyntax>()
            .Where(argument =>
                   argument.RefOrOutKeyword.IsKind(SyntaxKind.OutKeyword) &&
                   argument.Expression.IsKind(SyntaxKind.IdentifierName) &&
                   OutArgumentCanBecomeOutVariableOrCanBeDiscarded(semanticModel, argument, outArgumentCanBeDiscarded)
                   )
            .Select(argument => new AnalysisResult
                    (
                        this,
                        analysisContext,
                        syntaxTree.FilePath,
                        argument.RefOrOutKeyword,
                        argument.FirstAncestorOrSelf <TEnclosingExpressionSyntax>()
                    )));
 }
Exemplo n.º 26
0
 public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
 {
     return(syntaxTree.GetRoot()
            .DescendantNodes()
            .OfType <AccessorDeclarationSyntax>()
            .Where
                (accessor =>
                accessor.Keyword.IsKind(SyntaxKind.GetKeyword) &&
                accessor.Body != null &&
                accessor.Body.Statements.Count == 1 &&
                accessor.Body.Statements[0].IsKind(SyntaxKind.ReturnStatement) &&
                ((AccessorListSyntax)accessor.Parent).Accessors.Count > 1 &&            // We must have the set-accessor as well (see [1]).
                accessor.FirstAncestorOrSelf <TBasePropertyDeclarationSyntax>() != null // We must be either in a property or in an indexer.
                )
            .Select(accessor => new AnalysisResult
                    (
                        this,
                        analysisContext,
                        syntaxTree.FilePath,
                        accessor.Keyword,
                        accessor.FirstAncestorOrSelf <TBasePropertyDeclarationSyntax>()
                    )));
 }
        public IEnumerable <AnalysisResult> Analyze(SyntaxTree syntaxTree, SemanticModel semanticModel, SingleSyntaxTreeAnalysisContext analysisContext)
        {
            return(syntaxTree.GetRoot()
                   .DescendantNodes()
                   .OfType <MethodDeclarationSyntax>()
                   .Where(method => method.IsAsync())
                   .SelectMany(method => method.DescendantNodes())
                   // We can have both methods (invocation) and properties (simple member access).
                   // Unfortunately, it is not possible to combine them under a single umbrella.
                   .Where(node =>
                          (
                              node.IsKind(SyntaxKind.InvocationExpression) &&
                              !node.IsWithinLambdaOrAnonymousMethod() && // See [1].
                              ((InvocationExpressionSyntax)node).GetInvokedMemberName() == replacementInfo.SynchronousMemberName
                              ||
                              node.IsKind(SyntaxKind.SimpleMemberAccessExpression) &&
                              !node.IsWithinLambdaOrAnonymousMethod() && // See [1].
                              node.Parent?.IsKind(SyntaxKind.InvocationExpression) == false &&
                              ((MemberAccessExpressionSyntax)node).Name.Identifier.ValueText == replacementInfo.SynchronousMemberName
                          )
                          &&
                          InvokedMemberHasAsynchronousEquivalentThatCanBeAwaited(node)
                          )
                   .Select(node => new AnalysisResult(
                               this,
                               analysisContext,
                               syntaxTree.FilePath,
                               GetStartingSyntaxNode(node).GetFirstToken(),
                               node.FirstAncestorOrSelf <StatementSyntax>() ?? node
                               )));

            bool InvokedMemberHasAsynchronousEquivalentThatCanBeAwaited(SyntaxNode invocation)
            {
                var invokedMember = semanticModel.GetSymbolInfo(invocation).Symbol;

                if (invokedMember?.ContainingType == null)
                {
                    return(false);
                }

                return(invokedMember.Name == replacementInfo.SynchronousMemberName &&
                       invokedMember.ContainingType.FullNameIsEqualTo(replacementInfo.SynchronousMemberTypeNamespace, replacementInfo.SynchronousMemberTypeName));
            }

            SyntaxNode GetStartingSyntaxNode(SyntaxNode node)
            {
                MemberAccessExpressionSyntax memberAccess;

                if (node.IsKind(SyntaxKind.InvocationExpression))
                {
                    var invocation = (InvocationExpressionSyntax)node;
                    memberAccess = invocation.Expression as MemberAccessExpressionSyntax;
                    if (memberAccess == null)
                    {
                        return(invocation.Expression);
                    }
                }
                else
                {
                    memberAccess = (MemberAccessExpressionSyntax)node;
                }

                return(memberAccess.Name ?? (SyntaxNode)memberAccess);
            }
        }