private static void Handle(SyntaxNodeAnalysisContext context)
        {
            if (context.ContainingSymbol is IMethodSymbol method &&
                context.Node is MethodDeclarationSyntax methodDeclaration &&
                Disposable.IsAssignableFrom(method.ReturnType, context.Compilation))
            {
                using var walker = ReturnValueWalker.Borrow(methodDeclaration, ReturnValueSearch.RecursiveInside, context.SemanticModel, context.CancellationToken);
                if (walker.TryFirst(x => IsCreated(x), out _) &&
                    walker.TryFirst(x => IsCachedOrInjected(x) && !IsNop(x), out _))
                {
                    context.ReportDiagnostic(Diagnostic.Create(Descriptors.IDISP015DoNotReturnCachedAndCreated, methodDeclaration.Identifier.GetLocation()));
                }
            }

            bool IsCreated(ExpressionSyntax expression)
            {
                return(Disposable.IsCreation(expression, context.SemanticModel, context.CancellationToken) == Result.Yes);
            }

            bool IsCachedOrInjected(ExpressionSyntax expression)
            {
                return(Disposable.IsCachedOrInjected(expression, expression, context.SemanticModel, context.CancellationToken));
            }

            bool IsNop(ExpressionSyntax expression)
            {
                return(Disposable.IsNop(expression, context.SemanticModel, context.CancellationToken));
            }
        }
        private static bool IsDisposableReturnTypeOrIgnored(ITypeSymbol type, Compilation compilation)
        {
            if (type == null ||
                type == KnownSymbol.Void)
            {
                return(true);
            }

            if (Disposable.IsAssignableFrom(type, compilation))
            {
                return(true);
            }

            if (type == KnownSymbol.IEnumerator)
            {
                return(true);
            }

            if (type == KnownSymbol.Task)
            {
                var namedType = type as INamedTypeSymbol;
                return(namedType?.IsGenericType == true &&
                       Disposable.IsAssignableFrom(namedType.TypeArguments[0], compilation));
            }

            if (type == KnownSymbol.Func)
            {
                var namedType = type as INamedTypeSymbol;
                return(namedType?.IsGenericType == true &&
                       Disposable.IsAssignableFrom(namedType.TypeArguments[namedType.TypeArguments.Length - 1], compilation));
            }

            return(false);
        }
Example #3
0
        protected override async Task RegisterCodeFixesAsync(DocumentEditorCodeFixContext context)
        {
            var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken)
                             .ConfigureAwait(false);

            var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken)
                                .ConfigureAwait(false);

            foreach (var diagnostic in context.Diagnostics)
            {
                if (IsSupportedDiagnostic(diagnostic) &&
                    syntaxRoot.TryFindNodeOrAncestor(diagnostic, out TypeDeclarationSyntax? typeDeclaration))
                {
                    if (diagnostic.Id == Descriptors.IDISP009IsIDisposable.Id)
                    {
                        context.RegisterCodeFix(
                            "Add IDisposable interface",
                            (editor, cancellationToken) => editor.AddInterfaceType(typeDeclaration, IDisposableFactory.SystemIDisposable),
                            "add interface",
                            diagnostic);
                    }
                    else if (typeDeclaration is StructDeclarationSyntax structDeclaration)
                    {
                        context.RegisterCodeFix(
                            "Implement IDisposable.",
                            (editor, _) => editor.AddMethod(structDeclaration, MethodFactory.Dispose()),
                            "Struct",
                            diagnostic);
                    }
                    else if (typeDeclaration is ClassDeclarationSyntax classDeclaration &&
                             semanticModel.TryGetNamedType(classDeclaration, context.CancellationToken, out var type))
                    {
                        if (Disposable.IsAssignableFrom(type, semanticModel.Compilation) &&
                            type.TryFindFirstMethodRecursive("Dispose", x => x.IsVirtual, out var baseDispose))
                        {
                            context.RegisterCodeFix(
                                $"override {baseDispose}",
                                (editor, cancellationToken) => OverrideDisposeAsync(editor, cancellationToken),
                                "override",
                                diagnostic);

                            async Task OverrideDisposeAsync(DocumentEditor editor, CancellationToken cancellationToken)
                            {
                                var disposed = await editor.AddFieldAsync(
                                    classDeclaration,
                                    "disposed",
                                    Accessibility.Private,
                                    DeclarationModifiers.None,
                                    SyntaxFactory.ParseTypeName("bool"),
                                    cancellationToken).ConfigureAwait(false);

                                _ = editor.AddMethod(classDeclaration, MethodFactory.OverrideDispose(disposed, baseDispose !))
                                    .AddThrowIfDisposed(classDeclaration, disposed, cancellationToken);
                            }
                        }
                        else if (CanImplement())
                        {
                            switch (type)
                            {
                            case { IsSealed: true } :
Example #4
0
        private static void Handle(SyntaxNodeAnalysisContext context)
        {
            if (!context.IsExcludedFromAnalysis() &&
                context.Node is AssignmentExpressionSyntax {
                Left : { } left, Right : { } right
            } assignment&&
                !left.IsKind(SyntaxKind.ElementAccessExpression) &&
                context.SemanticModel.TryGetSymbol(left, context.CancellationToken, out var assignedSymbol))
            {
                if (LocalOrParameter.TryCreate(assignedSymbol, out var localOrParameter) &&
                    Disposable.IsCreation(right, context.SemanticModel, context.CancellationToken).IsEither(Result.Yes, Result.AssumeYes) &&
                    Disposable.ShouldDispose(localOrParameter, context.SemanticModel, context.CancellationToken))
                {
                    context.ReportDiagnostic(Diagnostic.Create(Descriptors.IDISP001DisposeCreated, assignment.GetLocation()));
                }

                if (IsReassignedWithCreated(assignment, context))
                {
                    context.ReportDiagnostic(Diagnostic.Create(Descriptors.IDISP003DisposeBeforeReassigning, assignment.GetLocation()));
                }

                if (assignedSymbol is IParameterSymbol {
                    RefKind : RefKind.Ref
                } refParameter&&
                    refParameter.ContainingSymbol.DeclaredAccessibility != Accessibility.Private &&
                    context.SemanticModel.TryGetType(right, context.CancellationToken, out var type) &&
                    Disposable.IsAssignableFrom(type, context.Compilation))
                {
                    context.ReportDiagnostic(Diagnostic.Create(Descriptors.IDISP008DoNotMixInjectedAndCreatedForMember, context.Node.GetLocation()));
                }
            }
        }
Example #5
0
        internal static StatementSyntax DisposeStatement(ISymbol member, SemanticModel semanticModel, CancellationToken cancellationToken)
        {
            var prefix = member.IsEitherKind(SymbolKind.Field, SymbolKind.Property) &&
                         !semanticModel.UnderscoreFields()
                    ? "this."
                    : string.Empty;
            var type = MemberType(member);

            if (!Disposable.IsAssignableFrom(type, semanticModel.Compilation) ||
                IsExplicit(type, semanticModel.Compilation))
            {
                return(SyntaxFactory.ParseStatement($"({prefix}{member.Name} as System.IDisposable)?.Dispose();")
                       .WithLeadingTrivia(SyntaxFactory.ElasticMarker)
                       .WithTrailingTrivia(SyntaxFactory.ElasticMarker).WithSimplifiedNames());
            }

            if (IsNeverNull(member, semanticModel, cancellationToken))
            {
                return(SyntaxFactory.ParseStatement($"{prefix}{member.Name}.Dispose();")
                       .WithLeadingTrivia(SyntaxFactory.ElasticMarker)
                       .WithTrailingTrivia(SyntaxFactory.ElasticMarker));
            }

            return(SyntaxFactory.ParseStatement($"{prefix}{member.Name}?.Dispose();")
                   .WithLeadingTrivia(SyntaxFactory.ElasticMarker)
                   .WithTrailingTrivia(SyntaxFactory.ElasticMarker));
        }
        internal static bool IsDisposed(FieldOrProperty member, MethodDeclarationSyntax disposeMethod, SemanticModel semanticModel, CancellationToken cancellationToken)
        {
            using var walker = DisposeWalker.Borrow(disposeMethod, semanticModel, cancellationToken);
            if (Disposable.IsAssignableFrom(member.Type, semanticModel.Compilation))
            {
                foreach (var candidate in walker.Invocations)
                {
                    if (DisposeCall.IsDisposing(candidate, member.Symbol, semanticModel, cancellationToken))
                    {
                        return(true);
                    }
                }
            }

            foreach (var candidate in walker.Identifiers)
            {
                if (candidate.Identifier.Text == member.Name &&
                    semanticModel.TryGetSymbol(candidate, cancellationToken, out var candidateSymbol) &&
                    candidateSymbol.OriginalDefinition.Equals(member.Symbol))
                {
                    return(true);
                }
            }

            return(false);
        }
Example #7
0
        /// <inheritdoc/>
        public override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken)
                             .ConfigureAwait(false);

            if (syntaxRoot == null)
            {
                return;
            }

            var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken)
                                .ConfigureAwait(false);

            foreach (var diagnostic in context.Diagnostics)
            {
                if (!IsSupportedDiagnostic(diagnostic))
                {
                    continue;
                }

                var token = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start);
                if (string.IsNullOrEmpty(token.ValueText) ||
                    token.IsMissing)
                {
                    continue;
                }

                var typeDeclaration = syntaxRoot.FindNode(diagnostic.Location.SourceSpan)
                                      .FirstAncestorOrSelf <TypeDeclarationSyntax>();
                if (diagnostic.Id == IDISP009IsIDisposable.DiagnosticId)
                {
                    context.RegisterCodeFix(
                        CodeAction.Create(
                            "Add IDisposable interface",
                            cancellationToken =>
                            AddInterfaceAsync(
                                context,
                                cancellationToken,
                                typeDeclaration),
                            nameof(ImplementIDisposableFix) + "add interface"),
                        diagnostic);
                    continue;
                }

                if (typeDeclaration is StructDeclarationSyntax structDeclaration)
                {
                    context.RegisterCodeFix(
                        CodeAction.Create(
                            "Implement IDisposable.",
                            cancellationToken =>
                            ImplementIDisposableStructAsync(
                                context,
                                cancellationToken,
                                structDeclaration),
                            nameof(ImplementIDisposableFix) + "Struct"),
                        diagnostic);
                }
                else if (typeDeclaration is ClassDeclarationSyntax classDeclaration)
                {
                    var type = semanticModel.GetDeclaredSymbolSafe(typeDeclaration, context.CancellationToken);
                    if (Disposable.IsAssignableFrom(type, semanticModel.Compilation) &&
                        DisposeMethod.TryFindBaseVirtual(type, out var baseDispose))
                    {
                        context.RegisterCodeFix(
                            CodeAction.Create(
                                "override Dispose(bool)",
                                cancellationToken =>
                                OverrideDisposeAsync(
                                    context,
                                    classDeclaration,
                                    baseDispose,
                                    cancellationToken),
                                nameof(ImplementIDisposableFix) + "override"),
                            diagnostic);
                        continue;
                    }

                    if (type.TryFindSingleMethodRecursive("Dispose", out var disposeMethod) &&
                        !disposeMethod.IsStatic &&
                        disposeMethod.ReturnsVoid &&
                        disposeMethod.Parameters.Length == 0)
                    {
                        continue;
                    }

                    if (type.TryFindFieldRecursive("disposed", out _) ||
                        type.TryFindFieldRecursive("_disposed", out _))
                    {
                        return;
                    }

                    if (type.IsSealed)
                    {
                        context.RegisterCodeFix(
                            CodeAction.Create(
                                "Implement IDisposable.",
                                cancellationToken =>
                                ImplementIDisposableSealedAsync(
                                    context,
                                    cancellationToken,
                                    classDeclaration),
                                nameof(ImplementIDisposableFix) + "Sealed"),
                            diagnostic);
                        continue;
                    }

                    if (type.IsAbstract)
                    {
                        context.RegisterCodeFix(
                            CodeAction.Create(
                                "Implement IDisposable with virtual dispose method.",
                                cancellationToken =>
                                ImplementIDisposableVirtualAsync(
                                    context,
                                    cancellationToken,
                                    classDeclaration),
                                nameof(ImplementIDisposableFix) + "Virtual"),
                            diagnostic);
                        continue;
                    }

                    context.RegisterCodeFix(
                        CodeAction.Create(
                            "Implement IDisposable and make class sealed.",
                            cancellationToken =>
                            ImplementIDisposableSealedAsync(
                                context,
                                cancellationToken,
                                classDeclaration),
                            nameof(ImplementIDisposableFix) + "Sealed"),
                        diagnostic);

                    context.RegisterCodeFix(
                        CodeAction.Create(
                            "Implement IDisposable with virtual dispose method.",
                            cancellationToken =>
                            ImplementIDisposableVirtualAsync(
                                context,
                                cancellationToken,
                                classDeclaration),
                            nameof(ImplementIDisposableFix) + "Virtual"),
                        diagnostic);
                }
            }
        }