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); }
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 } :
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())); } } }
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); }
/// <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); } } }