diff options
Diffstat (limited to 'main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes')
23 files changed, 3401 insertions, 0 deletions
diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/AddImport/AbstractAddImportCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/AddImport/AbstractAddImportCodeFixProvider.cs new file mode 100644 index 0000000000..ca6de1af18 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/AddImport/AbstractAddImportCodeFixProvider.cs @@ -0,0 +1,523 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using ICSharpCode.NRefactory6.CSharp; +using ICSharpCode.NRefactory6.CSharp.Refactoring; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; + +namespace MonoDevelop.CSharp.CodeFixes +{ + internal abstract partial class AbstractAddImportCodeFixProvider : CodeFixProvider + { + protected abstract bool IgnoreCase { get; } + + protected abstract bool CanAddImport(SyntaxNode node, CancellationToken cancellationToken); + protected abstract bool CanAddImportForMethod(Diagnostic diagnostic, ref SyntaxNode node); + protected abstract bool CanAddImportForNamespace(Diagnostic diagnostic, ref SyntaxNode node); + protected abstract bool CanAddImportForQuery(Diagnostic diagnostic, ref SyntaxNode node); + protected abstract bool CanAddImportForType(Diagnostic diagnostic, ref SyntaxNode node); + + protected abstract ISet<INamespaceSymbol> GetNamespacesInScope(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken); + protected abstract ITypeSymbol GetQueryClauseInfo(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken); + protected abstract string GetDescription(INamespaceOrTypeSymbol symbol, SemanticModel semanticModel, SyntaxNode root); + protected abstract Task<Document> AddImportAsync(SyntaxNode contextNode, INamespaceOrTypeSymbol symbol, Document documemt, bool specialCaseSystem, CancellationToken cancellationToken); + protected abstract bool IsViableExtensionMethod(IMethodSymbol method, SyntaxNode expression, SemanticModel semanticModel, CancellationToken cancellationToken); + protected abstract IEnumerable<ITypeSymbol> GetProposedTypes(string name, List<ITypeSymbol> accessibleTypeSymbols, SemanticModel semanticModel, ISet<INamespaceSymbol> namespacesInScope); + internal abstract bool IsViableField(IFieldSymbol field, SyntaxNode expression, SemanticModel semanticModel, CancellationToken cancellationToken); + internal abstract bool IsViableProperty(IPropertySymbol property, SyntaxNode expression, SemanticModel semanticModel, CancellationToken cancellationToken); + internal abstract bool IsAddMethodContext(SyntaxNode node, SemanticModel semanticModel); + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var document = context.Document; + var span = context.Span; + var diagnostics = context.Diagnostics; + var cancellationToken = context.CancellationToken; + + var project = document.Project; + var diagnostic = diagnostics.First(); + var model = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait (false); + if (model.IsFromGeneratedCode (context.CancellationToken)) + return; + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var ancestors = root.FindToken(span.Start, findInsideTrivia: true).GetAncestors<SyntaxNode>(); + if (!ancestors.Any()) + { + return; + } + + var node = ancestors.FirstOrDefault(n => n.Span.Contains(span) && n != root); + if (node == null) + { + return; + } + + var placeSystemNamespaceFirst = true; //document.Project.Solution.Workspace.Options.GetOption(Microsoft.CodeAnalysis.Shared.Options.OrganizerOptions.PlaceSystemNamespaceFirst, document.Project.Language); + + if (!cancellationToken.IsCancellationRequested) + { + if (this.CanAddImport(node, cancellationToken)) + { + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var containingType = semanticModel.GetEnclosingNamedType(node.SpanStart, cancellationToken); + var containingTypeOrAssembly = containingType ?? (ISymbol)semanticModel.Compilation.Assembly; + var namespacesInScope = this.GetNamespacesInScope(semanticModel, node, cancellationToken); + + var matchingTypesNamespaces = await this.GetNamespacesForMatchingTypesAsync(project, diagnostic, node, semanticModel, namespacesInScope, cancellationToken).ConfigureAwait(false); + var matchingTypes = await this.GetMatchingTypesAsync(project, diagnostic, node, semanticModel, namespacesInScope, cancellationToken).ConfigureAwait(false); + var matchingNamespaces = await this.GetNamespacesForMatchingNamespacesAsync(project, diagnostic, node, semanticModel, namespacesInScope, cancellationToken).ConfigureAwait(false); + var matchingExtensionMethodsNamespaces = await this.GetNamespacesForMatchingExtensionMethodsAsync(project, diagnostic, node, semanticModel, namespacesInScope, cancellationToken).ConfigureAwait(false); + var matchingFieldsAndPropertiesAsync = await this.GetNamespacesForMatchingFieldsAndPropertiesAsync(project, diagnostic, node, semanticModel, namespacesInScope, cancellationToken).ConfigureAwait(false); + var queryPatternsNamespaces = await this.GetNamespacesForQueryPatternsAsync(project, diagnostic, node, semanticModel, namespacesInScope, cancellationToken).ConfigureAwait(false); + + if (matchingTypesNamespaces != null || matchingNamespaces != null || matchingExtensionMethodsNamespaces != null || matchingFieldsAndPropertiesAsync != null || queryPatternsNamespaces != null || matchingTypes != null) + { + matchingTypesNamespaces = matchingTypesNamespaces ?? SpecializedCollections.EmptyList<INamespaceSymbol>(); + matchingNamespaces = matchingNamespaces ?? SpecializedCollections.EmptyList<INamespaceSymbol>(); + matchingExtensionMethodsNamespaces = matchingExtensionMethodsNamespaces ?? SpecializedCollections.EmptyList<INamespaceSymbol>(); + matchingFieldsAndPropertiesAsync = matchingFieldsAndPropertiesAsync ?? SpecializedCollections.EmptyList<INamespaceSymbol>(); + queryPatternsNamespaces = queryPatternsNamespaces ?? SpecializedCollections.EmptyList<INamespaceSymbol>(); + matchingTypes = matchingTypes ?? SpecializedCollections.EmptyList<ITypeSymbol>(); + + var proposedImports = + matchingTypesNamespaces.Cast<INamespaceOrTypeSymbol> () + .Concat (matchingNamespaces.Cast<INamespaceOrTypeSymbol> ()) + .Concat (matchingExtensionMethodsNamespaces.Cast<INamespaceOrTypeSymbol> ()) + .Concat (matchingFieldsAndPropertiesAsync.Cast<INamespaceOrTypeSymbol> ()) + .Concat (queryPatternsNamespaces.Cast<INamespaceOrTypeSymbol> ()) + .Concat (matchingTypes.Cast<INamespaceOrTypeSymbol> ()) + .Distinct () + .Where (NotNull) + .Where (NotGlobalNamespace) + .ToList (); + proposedImports.Sort (INamespaceOrTypeSymbolExtensions.CompareNamespaceOrTypeSymbols); + proposedImports = proposedImports.Take (8).ToList (); + + if (proposedImports.Count > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + + foreach (var import in proposedImports) + { + var action = new DocumentChangeAction( + node.Span, + DiagnosticSeverity.Error, + this.GetDescription(import, semanticModel, node), + (c) => this.AddImportAsync(node, import, document, placeSystemNamespaceFirst, cancellationToken) + ); + + context.RegisterCodeFix(action, diagnostic); + } + } + } + } + } + } + + private async Task<IEnumerable<INamespaceSymbol>> GetNamespacesForMatchingTypesAsync( + Microsoft.CodeAnalysis.Project project, + Diagnostic diagnostic, + SyntaxNode node, + SemanticModel semanticModel, + ISet<INamespaceSymbol> namespacesInScope, + CancellationToken cancellationToken) + { + if (!this.CanAddImportForType(diagnostic, ref node)) + { + return null; + } + + string name; + int arity; + bool inAttributeContext, hasIncompleteParentMember; + CalculateContext(node, out name, out arity, out inAttributeContext, out hasIncompleteParentMember); + + var symbols = await GetTypeSymbols(project, node, semanticModel, name, inAttributeContext, cancellationToken).ConfigureAwait(false); + if (symbols == null) + { + return null; + } + + return GetNamespacesForMatchingTypesAsync(semanticModel, namespacesInScope, arity, inAttributeContext, hasIncompleteParentMember, symbols); + } + + private IEnumerable<INamespaceSymbol> GetNamespacesForMatchingTypesAsync(SemanticModel semanticModel, ISet<INamespaceSymbol> namespacesInScope, int arity, bool inAttributeContext, bool hasIncompleteParentMember, IEnumerable<ITypeSymbol> symbols) + { + var accessibleTypeSymbols = symbols + .Where(s => s.ContainingSymbol is INamespaceSymbol + && ArityAccessibilityAndAttributeContextAreCorrect( + semanticModel, s, arity, + inAttributeContext, hasIncompleteParentMember)) + .ToList(); + + return GetProposedNamespaces( + accessibleTypeSymbols.Select(s => s.ContainingNamespace), + semanticModel, + namespacesInScope); + } + + private async Task<IEnumerable<ITypeSymbol>> GetMatchingTypesAsync( + Microsoft.CodeAnalysis.Project project, + Diagnostic diagnostic, + SyntaxNode node, + SemanticModel semanticModel, + ISet<INamespaceSymbol> namespacesInScope, + CancellationToken cancellationToken) + { + if (!this.CanAddImportForType(diagnostic, ref node)) + { + return null; + } + + string name; + int arity; + bool inAttributeContext, hasIncompleteParentMember; + CalculateContext(node, out name, out arity, out inAttributeContext, out hasIncompleteParentMember); + + var symbols = await GetTypeSymbols(project, node, semanticModel, name, inAttributeContext, cancellationToken).ConfigureAwait(false); + if (symbols == null) + { + return null; + } + + return GetMatchingTypes(semanticModel, namespacesInScope, name, arity, inAttributeContext, symbols, hasIncompleteParentMember); + } + + private async Task<IEnumerable<INamespaceSymbol>> GetNamespacesForMatchingNamespacesAsync( + Microsoft.CodeAnalysis.Project project, + Diagnostic diagnostic, + SyntaxNode node, + SemanticModel semanticModel, + ISet<INamespaceSymbol> namespacesInScope, + CancellationToken cancellationToken) + { + if (!this.CanAddImportForNamespace(diagnostic, ref node)) + { + return null; + } + + string name; + int arity; + node.GetNameAndArityOfSimpleName(out name, out arity); + + if (ExpressionBinds(node, semanticModel, cancellationToken)) + { + return null; + } + + var symbols = await SymbolFinder.FindDeclarationsAsync( + project, name, this.IgnoreCase, SymbolFilter.Namespace, cancellationToken).ConfigureAwait(false); + + return GetProposedNamespaces( + symbols.OfType<INamespaceSymbol>().Select(n => n.ContainingNamespace), + semanticModel, + namespacesInScope); + } + + private async Task<IEnumerable<INamespaceSymbol>> GetNamespacesForMatchingExtensionMethodsAsync( + Microsoft.CodeAnalysis.Project project, + Diagnostic diagnostic, + SyntaxNode node, + SemanticModel semanticModel, + ISet<INamespaceSymbol> namespacesInScope, + CancellationToken cancellationToken) + { + if (!this.CanAddImportForMethod(diagnostic, ref node)) + { + return null; + } + + var expression = node.Parent; + + var extensionMethods = SpecializedCollections.EmptyEnumerable<INamespaceSymbol>(); + var symbols = await GetSymbolsAsync(project, node, semanticModel, cancellationToken).ConfigureAwait(false); + if (symbols != null) + { + extensionMethods = FilterForExtensionMethods(semanticModel, namespacesInScope, expression, symbols, cancellationToken); + } + + var addMethods = SpecializedCollections.EmptyEnumerable<INamespaceSymbol>(); + var methodSymbols = await GetAddMethodsAsync(project, diagnostic, node, semanticModel, namespacesInScope, expression, cancellationToken).ConfigureAwait(false); + if (methodSymbols != null) + { + addMethods = GetProposedNamespaces( + methodSymbols.Select(s => s.ContainingNamespace), + semanticModel, + namespacesInScope); + } + + return extensionMethods.Concat(addMethods); + } + + private async Task<IEnumerable<INamespaceSymbol>> GetNamespacesForMatchingFieldsAndPropertiesAsync( + Microsoft.CodeAnalysis.Project project, + Diagnostic diagnostic, + SyntaxNode node, + SemanticModel semanticModel, + ISet<INamespaceSymbol> namespacesInScope, + CancellationToken cancellationToken) + { + if (!this.CanAddImportForMethod(diagnostic, ref node)) + { + return null; + } + + var expression = node.Parent; + + var symbols = await GetSymbolsAsync(project, node, semanticModel, cancellationToken).ConfigureAwait(false); + + if (symbols != null) + { + return FilterForFieldsAndProperties(semanticModel, namespacesInScope, expression, symbols, cancellationToken); + } + + return null; + } + + private IEnumerable<INamespaceSymbol> FilterForFieldsAndProperties(SemanticModel semanticModel, ISet<INamespaceSymbol> namespacesInScope, SyntaxNode expression, IEnumerable<ISymbol> symbols, CancellationToken cancellationToken) + { + var propertySymbols = symbols + .OfType<IPropertySymbol>() + .Where(property => property.ContainingType?.IsAccessibleWithin(semanticModel.Compilation.Assembly) == true && + IsViableProperty(property, expression, semanticModel, cancellationToken)) + .ToList(); + + var fieldSymbols = symbols + .OfType<IFieldSymbol>() + .Where(field => field.ContainingType?.IsAccessibleWithin(semanticModel.Compilation.Assembly) == true && + IsViableField(field, expression, semanticModel, cancellationToken)) + .ToList(); + + return GetProposedNamespaces( + propertySymbols.Select(s => s.ContainingNamespace).Concat(fieldSymbols.Select(s => s.ContainingNamespace)), + semanticModel, + namespacesInScope); + } + + private Task<IEnumerable<ISymbol>> GetSymbolsAsync( + Microsoft.CodeAnalysis.Project project, + SyntaxNode node, + SemanticModel semanticModel, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + // See if the name binds. If it does, there's nothing further we need to do. + if (ExpressionBinds(node, semanticModel, cancellationToken, checkForExtensionMethods: true)) + { + return Task.FromResult (Enumerable.Empty<ISymbol>()); + } + + string name; + int arity; + node.GetNameAndArityOfSimpleName(out name, out arity); + if (name == null) + { + return Task.FromResult (Enumerable.Empty<ISymbol>()); + } + + return SymbolFinder.FindDeclarationsAsync(project, name, this.IgnoreCase, SymbolFilter.Member, cancellationToken); + } + + private async Task<IEnumerable<IMethodSymbol>> GetAddMethodsAsync( + Microsoft.CodeAnalysis.Project project, + Diagnostic diagnostic, + SyntaxNode node, + SemanticModel semanticModel, + ISet<INamespaceSymbol> namespacesInScope, + SyntaxNode expression, + CancellationToken cancellationToken) + { + string name; + int arity; + node.GetNameAndArityOfSimpleName(out name, out arity); + if (name != null) + { + return SpecializedCollections.EmptyEnumerable<IMethodSymbol>(); + } + + if (IsAddMethodContext(node, semanticModel)) + { + var symbols = await SymbolFinder.FindDeclarationsAsync(project, "Add", this.IgnoreCase, SymbolFilter.Member, cancellationToken).ConfigureAwait(false); + return symbols + .OfType<IMethodSymbol>() + .Where(method => method.IsExtensionMethod && + method.ContainingType?.IsAccessibleWithin(semanticModel.Compilation.Assembly) == true && + IsViableExtensionMethod(method, expression, semanticModel, cancellationToken)); + } + + return SpecializedCollections.EmptyEnumerable<IMethodSymbol>(); + } + + private IEnumerable<INamespaceSymbol> FilterForExtensionMethods(SemanticModel semanticModel, ISet<INamespaceSymbol> namespacesInScope, SyntaxNode expression, IEnumerable<ISymbol> symbols, CancellationToken cancellationToken) + { + var extensionMethodSymbols = symbols + .OfType<IMethodSymbol>() + .Where(method => method.IsExtensionMethod && + method.ContainingType?.IsAccessibleWithin(semanticModel.Compilation.Assembly) == true && + IsViableExtensionMethod(method, expression, semanticModel, cancellationToken)) + .ToList(); + + return GetProposedNamespaces( + extensionMethodSymbols.Select(s => s.ContainingNamespace), + semanticModel, + namespacesInScope); + } + + private async Task<IEnumerable<INamespaceSymbol>> GetNamespacesForQueryPatternsAsync( + Microsoft.CodeAnalysis.Project project, + Diagnostic diagnostic, + SyntaxNode node, + SemanticModel semanticModel, + ISet<INamespaceSymbol> namespacesInScope, + CancellationToken cancellationToken) + { + if (!this.CanAddImportForQuery(diagnostic, ref node)) + { + return null; + } + + ITypeSymbol type = this.GetQueryClauseInfo(semanticModel, node, cancellationToken); + if (type == null) + { + return null; + } + + // find extension methods named "Select" + var symbols = await SymbolFinder.FindDeclarationsAsync(project, "Select", this.IgnoreCase, SymbolFilter.Member, cancellationToken).ConfigureAwait(false); + + var extensionMethodSymbols = symbols + .OfType<IMethodSymbol>() + .Where(s => s.IsExtensionMethod && IsViableExtensionMethod(type, s)) + .ToList(); + + return GetProposedNamespaces( + extensionMethodSymbols.Select(s => s.ContainingNamespace), + semanticModel, + namespacesInScope); + } + + private bool IsViableExtensionMethod( + ITypeSymbol typeSymbol, + IMethodSymbol method) + { + return typeSymbol != null && method.ReduceExtensionMethod(typeSymbol) != null; + } + + private static bool ArityAccessibilityAndAttributeContextAreCorrect( + SemanticModel semanticModel, + ITypeSymbol symbol, + int arity, + bool inAttributeContext, + bool hasIncompleteParentMember) + { + return (arity == 0 || symbol.GetArity() == arity || hasIncompleteParentMember) + && symbol.IsAccessibleWithin(semanticModel.Compilation.Assembly) + && (!inAttributeContext || symbol.IsAttribute()); + } + + private async Task<IEnumerable<ITypeSymbol>> GetTypeSymbols( + Microsoft.CodeAnalysis.Project project, + SyntaxNode node, + SemanticModel semanticModel, + string name, + bool inAttributeContext, + CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return null; + } + + if (ExpressionBinds(node, semanticModel, cancellationToken)) + { + return null; + } + + var symbols = await SymbolFinder.FindDeclarationsAsync(project, name, this.IgnoreCase, SymbolFilter.Type, cancellationToken).ConfigureAwait(false); + + // also lookup type symbols with the "Attribute" suffix. + if (inAttributeContext) + { + symbols = symbols.Concat( + await SymbolFinder.FindDeclarationsAsync(project, name + "Attribute", this.IgnoreCase, SymbolFilter.Type, cancellationToken).ConfigureAwait(false)); + } + + return symbols.OfType<ITypeSymbol>(); + } + + private IEnumerable<ITypeSymbol> GetMatchingTypes(SemanticModel semanticModel, ISet<INamespaceSymbol> namespacesInScope, string name, int arity, bool inAttributeContext, IEnumerable<ITypeSymbol> symbols, bool hasIncompleteParentMember) + { + var accessibleTypeSymbols = symbols + .Where(s => ArityAccessibilityAndAttributeContextAreCorrect( + semanticModel, s, arity, + inAttributeContext, hasIncompleteParentMember)) + .ToList(); + + return GetProposedTypes( + name, + accessibleTypeSymbols, + semanticModel, + namespacesInScope); + } + + private static void CalculateContext(SyntaxNode node, out string name, out int arity, out bool inAttributeContext, out bool hasIncompleteParentMember) + { + // Has to be a simple identifier or generic name. + node.GetNameAndArityOfSimpleName(out name, out arity); + + inAttributeContext = node.IsAttributeName(); + hasIncompleteParentMember = node.HasIncompleteParentMember(); + } + + protected bool ExpressionBinds(SyntaxNode expression, SemanticModel semanticModel, CancellationToken cancellationToken, bool checkForExtensionMethods = false) + { + // See if the name binds to something other then the error type. If it does, there's nothing further we need to do. + // For extension methods, however, we will continue to search if there exists any better matched method. + cancellationToken.ThrowIfCancellationRequested(); + var symbolInfo = semanticModel.GetSymbolInfo(expression, cancellationToken); + if (symbolInfo.CandidateReason == CandidateReason.OverloadResolutionFailure && !checkForExtensionMethods) + { + return true; + } + + return symbolInfo.Symbol != null; + } + + protected IEnumerable<INamespaceSymbol> GetProposedNamespaces( + IEnumerable<INamespaceSymbol> namespaces, + SemanticModel semanticModel, + ISet<INamespaceSymbol> namespacesInScope) + { + // We only want to offer to add a using if we don't already have one. + return + namespaces.Where(n => !n.IsGlobalNamespace) + .Select(n => semanticModel.Compilation.GetCompilationNamespace(n) ?? n) + .Where(n => n != null && !namespacesInScope.Contains(n)); + } + + private static bool NotGlobalNamespace(INamespaceOrTypeSymbol symbol) + { + return symbol.IsNamespace ? !((INamespaceSymbol)symbol).IsGlobalNamespace : true; + } + + private static bool NotNull(INamespaceOrTypeSymbol symbol) + { + return symbol != null; + } + + + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/AddImport/CSharpAddImportCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/AddImport/CSharpAddImportCodeFixProvider.cs new file mode 100644 index 0000000000..994bd39571 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/AddImport/CSharpAddImportCodeFixProvider.cs @@ -0,0 +1,642 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; +using Roslyn.Utilities; +using MonoDevelop.CSharp.CodeFixes; +using ICSharpCode.NRefactory6.CSharp; + +namespace MonoDevelop.CSharp.CodeFixes +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.AddUsingOrImport), Shared] + class CSharpAddImportCodeFixProvider : AbstractAddImportCodeFixProvider + { + /// <summary> + /// name does not exist in context + /// </summary> + private const string CS0103 = "CS0103"; + + /// <summary> + /// type or namespace could not be found + /// </summary> + private const string CS0246 = "CS0246"; + + /// <summary> + /// wrong number of type args + /// </summary> + private const string CS0305 = "CS0305"; + + /// <summary> + /// type does not contain a definition of method or extension method + /// </summary> + private const string CS1061 = "CS1061"; + + /// <summary> + /// cannot find implementation of query pattern + /// </summary> + private const string CS1935 = "CS1935"; + + /// <summary> + /// The non-generic type 'A' cannot be used with type arguments + /// </summary> + private const string CS0308 = "CS0308"; + + /// <summary> + /// 'A' is inaccessible due to its protection level + /// </summary> + private const string CS0122 = "CS0122"; + + /// <summary> + /// The using alias 'A' cannot be used with type arguments + /// </summary> + private const string CS0307 = "CS0307"; + + /// <summary> + /// 'A' is not an attribute class + /// </summary> + private const string CS0616 = "CS0616"; + + /// <summary> + /// ; expected. + /// </summary> + private const string CS1002 = "CS1002"; + + /// <summary> + /// Syntax error, 'A' expected + /// </summary> + private const string CS1003 = "CS1003"; + + /// <summary> + /// cannot convert from 'int' to 'string' + /// </summary> + private const string CS1503 = "CS1503"; + + /// <summary> + /// XML comment on 'construct' has syntactically incorrect cref attribute 'name' + /// </summary> + private const string CS1574 = "CS1574"; + + /// <summary> + /// Invalid type for parameter 'parameter number' in XML comment cref attribute + /// </summary> + private const string CS1580 = "CS1580"; + + /// <summary> + /// Invalid return type in XML comment cref attribute + /// </summary> + private const string CS1581 = "CS1581"; + + /// <summary> + /// XML comment has syntactically incorrect cref attribute + /// </summary> + private const string CS1584 = "CS1584"; + + public override ImmutableArray<string> FixableDiagnosticIds + { + get + { + return ImmutableArray.Create( + CS0103, + CS0246, + CS0305, + CS1061, + CS1935, + CS0308, + CS0122, + CS0307, + CS0616, + CS1002, + CS1003, + CS1503, + CS1574, + CS1580, + CS1581, + CS1584); + } + } + + protected override bool IgnoreCase + { + get { return false; } + } + + protected override bool CanAddImport(SyntaxNode node, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return false; + } + + return node.CanAddUsingDirectives(cancellationToken); + } + + protected override bool CanAddImportForMethod(Diagnostic diagnostic, ref SyntaxNode node) + { + switch (diagnostic.Id) + { + case CS1061: + if (node.IsKind(SyntaxKind.ConditionalAccessExpression)) + { + node = (node as ConditionalAccessExpressionSyntax).WhenNotNull; + } + else if (node.IsKind(SyntaxKind.MemberBindingExpression)) + { + node = (node as MemberBindingExpressionSyntax).Name; + } + else if (node.Parent.IsKind(SyntaxKind.CollectionInitializerExpression)) + { + return true; + } + + break; + case CS0122: + break; + + case CS1503: + //// look up its corresponding method name + var parent = node.GetAncestor<InvocationExpressionSyntax>(); + if (parent == null) + { + return false; + } + + var method = parent.Expression as MemberAccessExpressionSyntax; + if (method != null) + { + node = method.Name; + } + + break; + + default: + return false; + } + + var simpleName = node as SimpleNameSyntax; + if (!simpleName.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) && + !simpleName.IsParentKind(SyntaxKind.MemberBindingExpression)) + { + return false; + } + + var memberAccess = simpleName.Parent as MemberAccessExpressionSyntax; + var memberBinding = simpleName.Parent as MemberBindingExpressionSyntax; + if (memberAccess.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) || + memberAccess.IsParentKind(SyntaxKind.ElementAccessExpression) || + memberBinding.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) || + memberBinding.IsParentKind(SyntaxKind.ElementAccessExpression)) + { + return false; + } + + if (!node.IsMemberAccessExpressionName()) + { + return false; + } + + return true; + } + + protected override bool CanAddImportForNamespace(Diagnostic diagnostic, ref SyntaxNode node) + { + return false; + } + + protected override bool CanAddImportForQuery(Diagnostic diagnostic, ref SyntaxNode node) + { + if (diagnostic.Id != CS1935) + { + return false; + } + + return node.AncestorsAndSelf().Any(n => n is QueryExpressionSyntax && !(n.Parent is QueryContinuationSyntax)); + } + + protected override bool CanAddImportForType(Diagnostic diagnostic, ref SyntaxNode node) + { + switch (diagnostic.Id) + { + case CS0103: + case CS0246: + case CS0305: + case CS0308: + case CS0122: + case CS0307: + case CS0616: + case CS1003: + case CS1580: + case CS1581: + break; + + case CS1002: + //// only lookup errors inside ParenthesizedLambdaExpression e.g., () => { ... } + if (node.Ancestors().OfType<ParenthesizedLambdaExpressionSyntax>().Any()) + { + if (node is SimpleNameSyntax) + { + break; + } + else if (node is BlockSyntax || node is MemberAccessExpressionSyntax || node is BinaryExpressionSyntax) + { + var last = node.DescendantNodes().OfType<SimpleNameSyntax>().LastOrDefault(); + if (!TryFindStandaloneType(ref node)) + { + node = node.DescendantNodes().OfType<SimpleNameSyntax>().FirstOrDefault(); + } + else + { + node = last; + } + } + } + else + { + return false; + } + + break; + + case CS1574: + case CS1584: + var cref = node as QualifiedCrefSyntax; + if (cref != null) + { + node = cref.Container; + } + + break; + + default: + return false; + } + + return TryFindStandaloneType(ref node); + } + + private static bool TryFindStandaloneType(ref SyntaxNode node) + { + var qn = node as QualifiedNameSyntax; + if (qn != null) + { + node = GetLeftMostSimpleName(qn); + } + + var simpleName = node as SimpleNameSyntax; + return simpleName.LooksLikeStandaloneTypeName(); + } + + private static SimpleNameSyntax GetLeftMostSimpleName(QualifiedNameSyntax qn) + { + while (qn != null) + { + var left = qn.Left; + var simpleName = left as SimpleNameSyntax; + if (simpleName != null) + { + return simpleName; + } + + qn = left as QualifiedNameSyntax; + } + + return null; + } + + protected override ISet<INamespaceSymbol> GetNamespacesInScope( + SemanticModel semanticModel, + SyntaxNode node, + CancellationToken cancellationToken) + { + return semanticModel.GetUsingNamespacesInScope(node); + } + + protected override ITypeSymbol GetQueryClauseInfo( + SemanticModel semanticModel, + SyntaxNode node, + CancellationToken cancellationToken) + { + var query = node.AncestorsAndSelf().OfType<QueryExpressionSyntax>().First(); + + if (InfoBoundSuccessfully(semanticModel.GetQueryClauseInfo(query.FromClause, cancellationToken))) + { + return null; + } + + foreach (var clause in query.Body.Clauses) + { + if (InfoBoundSuccessfully(semanticModel.GetQueryClauseInfo(clause, cancellationToken))) + { + return null; + } + } + + if (InfoBoundSuccessfully(semanticModel.GetSymbolInfo(query.Body.SelectOrGroup, cancellationToken))) + { + return null; + } + + var fromClause = query.FromClause; + return semanticModel.GetTypeInfo(fromClause.Expression, cancellationToken).Type; + } + + private bool InfoBoundSuccessfully(SymbolInfo symbolInfo) + { + return InfoBoundSuccessfully(symbolInfo.Symbol); + } + + private bool InfoBoundSuccessfully(QueryClauseInfo semanticInfo) + { + return InfoBoundSuccessfully(semanticInfo.OperationInfo); + } + + private static bool InfoBoundSuccessfully(ISymbol operation) + { + operation = operation.GetOriginalUnreducedDefinition(); + return operation != null; + } + + protected override string GetDescription(INamespaceOrTypeSymbol namespaceSymbol, SemanticModel semanticModel, SyntaxNode contextNode) + { + var root = GetCompilationUnitSyntaxNode(contextNode); + + // No localization necessary + string externAliasString; + if (TryGetExternAliasString(namespaceSymbol, semanticModel, root, out externAliasString)) + { + return string.Format ("extern alias {0};", externAliasString); + } + + string namespaceString; + if (TryGetNamespaceString(namespaceSymbol, root, false, null, out namespaceString)) + { + return string.Format ("using {0};", namespaceString); + } + + // If we get here then neither a namespace or a an extern alias can be added. + // There is no valid string to show to the user and there is + // likely a bug in that we should know about. + throw new InvalidOperationException (); + } + + protected override async Task<Document> AddImportAsync( + SyntaxNode contextNode, + INamespaceOrTypeSymbol namespaceSymbol, + Document document, + bool placeSystemNamespaceFirst, + CancellationToken cancellationToken) + { + var root = GetCompilationUnitSyntaxNode(contextNode, cancellationToken); + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var simpleUsingDirective = GetUsingDirective(root, namespaceSymbol, semanticModel, fullyQualify: false); + var externAliasUsingDirective = GetExternAliasUsingDirective(root, namespaceSymbol, semanticModel); + if (externAliasUsingDirective != null) + { + root = root.AddExterns( + externAliasUsingDirective + .WithAdditionalAnnotations(Formatter.Annotation)); + } + + if (simpleUsingDirective != null) + { + // Because of the way usings can be nested inside of namespace declarations, + // we need to check if the usings must be fully qualified so as not to be + // ambiguous with the containing namespace. + if (UsingsAreContainedInNamespace(contextNode)) + { + // When we add usings we try and place them, as best we can, where the user + // wants them according to their settings. This means we can't just add the fully- + // qualified usings and expect the simplifier to take care of it, the usings have to be + // simplified before we attempt to add them to the document. + // You might be tempted to think that we could call + // AddUsings -> Simplifier -> SortUsings + // But this will clobber the users using settings without asking. Instead we create a new + // Document and check if our using can be simplified. Worst case we need to back out the + // fully qualified change and reapply with the simple name. + var fullyQualifiedUsingDirective = GetUsingDirective(root, namespaceSymbol, semanticModel, fullyQualify: true); + SyntaxNode newRoot = root.AddUsingDirective( + fullyQualifiedUsingDirective, contextNode, placeSystemNamespaceFirst, + Formatter.Annotation); + var newDocument = document.WithSyntaxRoot(newRoot); + var newSemanticModel = await newDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newUsing = newRoot + .DescendantNodes().OfType<UsingDirectiveSyntax>().Where(uds => uds.IsEquivalentTo(fullyQualifiedUsingDirective, topLevel: true)).Single(); + var speculationAnalyzer = new SpeculationAnalyzer(newUsing.Name, simpleUsingDirective.Name, newSemanticModel, cancellationToken); + if (speculationAnalyzer.ReplacementChangesSemantics()) + { + // Not fully qualifying the using causes to refer to a different namespace so we need to keep it as is. + return newDocument; + } + else + { + // It does not matter if it is fully qualified or simple so lets return the simple name. + return document.WithSyntaxRoot(root.AddUsingDirective( + simpleUsingDirective, contextNode, placeSystemNamespaceFirst, + Formatter.Annotation)); + } + } + else + { + // simple form + return document.WithSyntaxRoot(root.AddUsingDirective( + simpleUsingDirective, contextNode, placeSystemNamespaceFirst, + Formatter.Annotation)); + } + } + + return document.WithSyntaxRoot(root); + } + + private static ExternAliasDirectiveSyntax GetExternAliasUsingDirective(CompilationUnitSyntax root, INamespaceOrTypeSymbol namespaceSymbol, SemanticModel semanticModel) + { + string externAliasString; + if (TryGetExternAliasString(namespaceSymbol, semanticModel, root, out externAliasString)) + { + return SyntaxFactory.ExternAliasDirective(SyntaxFactory.Identifier(externAliasString)); + } + + return null; + } + + private UsingDirectiveSyntax GetUsingDirective(CompilationUnitSyntax root, INamespaceOrTypeSymbol namespaceSymbol, SemanticModel semanticModel, bool fullyQualify) + { + string namespaceString; + string externAliasString; + TryGetExternAliasString(namespaceSymbol, semanticModel, root, out externAliasString); + if (externAliasString != null) + { + if (TryGetNamespaceString(namespaceSymbol, root, false, externAliasString, out namespaceString)) + { + return SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(namespaceString)); + } + + return null; + } + + if (TryGetNamespaceString(namespaceSymbol, root, fullyQualify, null, out namespaceString)) + { + return SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(namespaceString)); + } + + return null; + } + + private bool UsingsAreContainedInNamespace(SyntaxNode contextNode) + { + return contextNode.GetAncestor<NamespaceDeclarationSyntax>()?.DescendantNodes().OfType<UsingDirectiveSyntax>().FirstOrDefault() != null; + } + + private static bool TryGetExternAliasString(INamespaceOrTypeSymbol namespaceSymbol, SemanticModel semanticModel, CompilationUnitSyntax root, out string externAliasString) + { + externAliasString = null; + var metadataReference = semanticModel.Compilation.GetMetadataReference(namespaceSymbol.ContainingAssembly); + if (metadataReference == null) + { + return false; + } + + var properties = metadataReference.Properties; + var aliases = properties.Aliases; + if (aliases.IsDefaultOrEmpty) + { + return false; + } + + aliases = properties.Aliases.Where(a => a != MetadataReferenceProperties.GlobalAlias).ToImmutableArray(); + if (!aliases.Any()) + { + return false; + } + + externAliasString = aliases.First(); + return ShouldAddExternAlias(aliases, root); + } + + private static bool TryGetNamespaceString(INamespaceOrTypeSymbol namespaceSymbol, CompilationUnitSyntax root, bool fullyQualify, string alias, out string namespaceString) + { + namespaceString = fullyQualify + ? namespaceSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + : namespaceSymbol.ToDisplayString(); + + if (alias != null) + { + namespaceString = alias + "::" + namespaceString; + } + + return ShouldAddUsing(namespaceString, root); + } + + private static bool ShouldAddExternAlias(ImmutableArray<string> aliases, CompilationUnitSyntax root) + { + var identifiers = root.DescendantNodes().OfType<ExternAliasDirectiveSyntax>().Select(e => e.Identifier.ToString()); + var externAliases = aliases.Where(a => identifiers.Contains(a)); + return !externAliases.Any(); + } + + private static bool ShouldAddUsing(string usingDirective, CompilationUnitSyntax root) + { + return !root.Usings.Select(u => u.Name.ToString()).Contains(usingDirective); + } + + private static CompilationUnitSyntax GetCompilationUnitSyntaxNode(SyntaxNode contextNode, CancellationToken cancellationToken = default(CancellationToken)) + { + return (CompilationUnitSyntax)contextNode.SyntaxTree.GetRoot(cancellationToken); + } + + protected override bool IsViableExtensionMethod(IMethodSymbol method, SyntaxNode expression, SemanticModel semanticModel, CancellationToken cancellationToken) + { + var leftExpression = expression.GetExpressionOfMemberAccessExpression() ?? expression.GetExpressionOfConditionalMemberAccessExpression(); + if (leftExpression == null) + { + if (expression.IsKind(SyntaxKind.CollectionInitializerExpression)) + { + leftExpression = expression.GetAncestor<ObjectCreationExpressionSyntax>(); + } + else + { + return false; + } + } + + var semanticInfo = semanticModel.GetTypeInfo(leftExpression, cancellationToken); + var leftExpressionType = semanticInfo.Type; + + return leftExpressionType != null && method.ReduceExtensionMethod(leftExpressionType) != null; + } + + protected override IEnumerable<ITypeSymbol> GetProposedTypes(string name, List<ITypeSymbol> accessibleTypeSymbols, SemanticModel semanticModel, ISet<INamespaceSymbol> namespacesInScope) + { + if (accessibleTypeSymbols == null) + { + yield break; + } + + foreach (var typeSymbol in accessibleTypeSymbols) + { + if ((typeSymbol != null) && (typeSymbol.ContainingType != null) && typeSymbol.ContainingType.IsStatic) + { + yield return typeSymbol.ContainingType; + } + } + } + + internal override bool IsViableField(IFieldSymbol field, SyntaxNode expression, SemanticModel semanticModel, CancellationToken cancellationToken) + { + return IsViablePropertyOrField(field, expression, semanticModel, cancellationToken); + } + + internal override bool IsViableProperty(IPropertySymbol property, SyntaxNode expression, SemanticModel semanticModel, CancellationToken cancellationToken) + { + return IsViablePropertyOrField(property, expression, semanticModel, cancellationToken); + } + + private bool IsViablePropertyOrField(ISymbol propertyOrField, SyntaxNode expression, SemanticModel semanticModel, CancellationToken cancellationToken) + { + if (!propertyOrField.IsStatic) + { + return false; + } + + var leftName = (expression as MemberAccessExpressionSyntax)?.Expression as SimpleNameSyntax; + if (leftName == null) + { + return false; + } + + return string.Compare(propertyOrField.ContainingType.Name, leftName.Identifier.Text, this.IgnoreCase) == 0; + } + + internal override bool IsAddMethodContext(SyntaxNode node, SemanticModel semanticModel) + { + if (node.Parent.IsKind(SyntaxKind.CollectionInitializerExpression)) + { + var objectCreationExpressionSyntax = node.GetAncestor<ObjectCreationExpressionSyntax>(); + if (objectCreationExpressionSyntax == null) + { + return false; + } + + return true; + } + + return false; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/AbstractAsyncCodeFix.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/AbstractAsyncCodeFix.cs new file mode 100644 index 0000000000..2c62341b62 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/AbstractAsyncCodeFix.cs @@ -0,0 +1,87 @@ +// +// AbstractAsyncCodeFix.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Collections.Immutable; +using System.Linq; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.CodeActions; +using ICSharpCode.NRefactory6.CSharp; +using MonoDevelop.CSharp.CodeFixes; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace MonoDevelop.CSharp.CodeFixes +{ + + internal abstract partial class AbstractAsyncCodeFix : CodeFixProvider + { + protected abstract Task<CodeAction> GetCodeFix(SyntaxNode root, SyntaxNode node, Document document, Diagnostic diagnostic, CancellationToken cancellationToken); + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var model = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait (false); + if (model.IsFromGeneratedCode (context.CancellationToken)) + return; + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + SyntaxNode node; + if (!TryGetNode(root, context.Span, out node)) + { + return; + } + + var diagnostic = context.Diagnostics.FirstOrDefault(); + + var codeAction = await GetCodeFix(root, node, context.Document, diagnostic, context.CancellationToken).ConfigureAwait(false); + + if (codeAction != null) + { + context.RegisterCodeFix(codeAction, diagnostic); + } + } + + private bool TryGetNode(SyntaxNode root, Microsoft.CodeAnalysis.Text.TextSpan span, out SyntaxNode node) + { + node = null; + var ancestors = root.FindToken(span.Start).GetAncestors<SyntaxNode>(); + if (!ancestors.Any()) + { + return false; + } + + node = ancestors.FirstOrDefault(n => n.Span.Contains(span) && n != root); + return node != null; + } + } + +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpAddAsyncCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpAddAsyncCodeFixProvider.cs new file mode 100644 index 0000000000..11e5cf2d72 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpAddAsyncCodeFixProvider.cs @@ -0,0 +1,229 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.CodeActions; +using ICSharpCode.NRefactory6.CSharp.Refactoring; +using MonoDevelop.CSharp.CodeFixes; +using ICSharpCode.NRefactory6.CSharp; +using Microsoft.CodeAnalysis.CSharp; +using MonoDevelop.Core; + +namespace MonoDevelop.CSharp.CodeFixes +{ + internal abstract partial class AbstractAddAsyncAwaitCodeFixProvider : AbstractAsyncCodeFix + { + protected abstract string GetDescription(Diagnostic diagnostic, SyntaxNode node, SemanticModel semanticModel, CancellationToken cancellationToken); + protected abstract Task<SyntaxNode> GetNewRoot(SyntaxNode root, SyntaxNode oldNode, SemanticModel semanticModel, Diagnostic diagnostic, Document document, CancellationToken cancellationToken); + + protected override async Task<CodeAction> GetCodeFix(SyntaxNode root, SyntaxNode node, Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + { + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var newRoot = await this.GetNewRoot(root, node, semanticModel, diagnostic, document, cancellationToken).ConfigureAwait(false); + if (newRoot != null) + { + return new DocumentChangeAction(node.Span, DiagnosticSeverity.Error, + this.GetDescription(diagnostic, node, semanticModel, cancellationToken), + token => Task.FromResult(document.WithSyntaxRoot(newRoot))); + } + + return null; + } + + protected bool TryGetTypes( + SyntaxNode expression, + SemanticModel semanticModel, + out INamedTypeSymbol source, + out INamedTypeSymbol destination) + { + source = null; + destination = null; + + var info = semanticModel.GetSymbolInfo(expression); + var methodSymbol = info.Symbol as IMethodSymbol; + if (methodSymbol == null) + { + return false; + } + + var compilation = semanticModel.Compilation; + var taskType = compilation.GetTypeByMetadataName("System.Threading.Tasks.Task"); + if (taskType == null) + { + return false; + } + + var returnType = methodSymbol.ReturnType as INamedTypeSymbol; + if (returnType == null) + { + return false; + } + + source = taskType; + destination = returnType; + return true; + } + + } + + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.AddAsync), Shared] + internal class CSharpAddAsyncCodeFixProvider : AbstractAddAsyncCodeFixProvider + { + /// <summary> + /// The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'. + /// </summary> + private const string CS4032 = "CS4032"; + + /// <summary> + /// The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'. + /// </summary> + private const string CS4033 = "CS4033"; + + /// <summary> + /// The 'await' operator can only be used within an async lambda expression. Consider marking this method with the 'async' modifier. + /// </summary> + private const string CS4034 = "CS4034"; + + public override ImmutableArray<string> FixableDiagnosticIds + { + get { return ImmutableArray.Create(CS4032, CS4033, CS4034); } + } + + protected override string GetDescription(Diagnostic diagnostic, SyntaxNode node, SemanticModel semanticModel, CancellationToken cancellationToken) + { + return GettextCatalog.GetString ("Make async"); + } + + protected override async Task<SyntaxNode> GetNewRoot(SyntaxNode root, SyntaxNode oldNode, SemanticModel semanticModel, Diagnostic diagnostic, Document document, CancellationToken cancellationToken) + { + var methodNode = GetContainingMember(oldNode); + if (methodNode == null) + { + return null; + } + + var newMethodNode = await ConvertToAsync(methodNode, semanticModel, document, cancellationToken).ConfigureAwait(false); + if (newMethodNode != null) + { + return root.ReplaceNode(methodNode, newMethodNode); + } + + return null; + } + + private static SyntaxNode GetContainingMember(SyntaxNode oldNode) + { + var parenthesizedLambda = oldNode + .Ancestors() + .FirstOrDefault(n => + n.IsKind(SyntaxKind.ParenthesizedLambdaExpression)); + + if (parenthesizedLambda != null) + { + return parenthesizedLambda; + } + + var simpleLambda = oldNode + .Ancestors() + .FirstOrDefault(n => + n.IsKind(SyntaxKind.SimpleLambdaExpression)); + + if (simpleLambda != null) + { + return simpleLambda; + } + + return oldNode + .Ancestors() + .FirstOrDefault(n => + n.IsKind(SyntaxKind.MethodDeclaration)); + } + + private async Task<SyntaxNode> ConvertToAsync(SyntaxNode node, SemanticModel semanticModel, Document document, CancellationToken cancellationToken) + { + var methodNode = node as MethodDeclarationSyntax; + if (methodNode != null) + { + return await ConvertMethodToAsync(document, semanticModel, methodNode, cancellationToken).ConfigureAwait(false); + } + + var parenthesizedLambda = node as ParenthesizedLambdaExpressionSyntax; + if (parenthesizedLambda != null) + { + return ConvertParenthesizedLambdaToAsync(parenthesizedLambda); + } + + var simpleLambda = node as SimpleLambdaExpressionSyntax; + if (simpleLambda != null) + { + return ConvertSimpleLambdaToAsync(simpleLambda); + } + + return null; + } + + private static SyntaxNode ConvertParenthesizedLambdaToAsync(ParenthesizedLambdaExpressionSyntax parenthesizedLambda) + { + return SyntaxFactory.ParenthesizedLambdaExpression( + SyntaxFactory.Token(SyntaxKind.AsyncKeyword), + parenthesizedLambda.ParameterList, + parenthesizedLambda.ArrowToken, + parenthesizedLambda.Body) + .WithAdditionalAnnotations(Formatter.Annotation); + } + + private static SyntaxNode ConvertSimpleLambdaToAsync(SimpleLambdaExpressionSyntax simpleLambda) + { + return SyntaxFactory.SimpleLambdaExpression( + SyntaxFactory.Token(SyntaxKind.AsyncKeyword), + simpleLambda.Parameter, + simpleLambda.ArrowToken, + simpleLambda.Body) + .WithAdditionalAnnotations(Formatter.Annotation); + } + + protected override SyntaxNode AddAsyncKeyword(SyntaxNode node) + { + var methodNode = node as MethodDeclarationSyntax; + if (methodNode == null) + { + return null; + } + + return methodNode + .AddModifiers(SyntaxFactory.Token(SyntaxKind.AsyncKeyword)) + .WithAdditionalAnnotations(Formatter.Annotation); + } + + protected override SyntaxNode AddAsyncKeywordAndTaskReturnType(SyntaxNode node, ITypeSymbol existingReturnType, INamedTypeSymbol taskTypeSymbol) + { + var methodNode = node as MethodDeclarationSyntax; + if (methodNode == null) + { + return null; + } + + if (taskTypeSymbol == null) + { + return null; + } + + var returnType = taskTypeSymbol.Construct(existingReturnType).GenerateTypeSyntax(); + return AddAsyncKeyword(methodNode.WithReturnType(returnType)); + } + + protected override bool DoesConversionExist(Compilation compilation, ITypeSymbol source, ITypeSymbol destination) + { + return compilation.ClassifyConversion(source, destination).Exists; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpAddAwaitCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpAddAwaitCodeFixProvider.cs new file mode 100644 index 0000000000..9bcb3033d1 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpAddAwaitCodeFixProvider.cs @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Linq; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.CodeActions; +using ICSharpCode.NRefactory6.CSharp; +using MonoDevelop.CSharp.CodeFixes; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using MonoDevelop.Core; + +namespace MonoDevelop.CSharp.CodeFixes +{ + internal abstract partial class AbstractAddAsyncCodeFixProvider : AbstractAddAsyncAwaitCodeFixProvider + { + protected const string SystemThreadingTasksTask = "System.Threading.Tasks.Task"; + protected const string SystemThreadingTasksTaskT = "System.Threading.Tasks.Task`1"; + protected abstract SyntaxNode AddAsyncKeyword(SyntaxNode methodNode); + protected abstract SyntaxNode AddAsyncKeywordAndTaskReturnType(SyntaxNode methodNode, ITypeSymbol existingReturnType, INamedTypeSymbol taskTypeSymbol); + protected abstract bool DoesConversionExist(Compilation compilation, ITypeSymbol source, ITypeSymbol destination); + + protected async Task<SyntaxNode> ConvertMethodToAsync(Document document, SemanticModel semanticModel, SyntaxNode methodNode, CancellationToken cancellationToken) + { + var methodSymbol = semanticModel.GetDeclaredSymbol(methodNode, cancellationToken) as IMethodSymbol; + + if (methodSymbol.ReturnsVoid) + { + return AddAsyncKeyword(methodNode); + } + + var returnType = methodSymbol.ReturnType; + var compilation = semanticModel.Compilation; + + var taskSymbol = compilation.GetTypeByMetadataName(SystemThreadingTasksTask); + var genericTaskSymbol = compilation.GetTypeByMetadataName(SystemThreadingTasksTaskT); + if (taskSymbol == null) + { + return null; + } + + if (returnType is IErrorTypeSymbol) + { + // The return type of the method will not bind. This could happen for a lot of reasons. + // The type may not actually exist or the user could just be missing a using/import statement. + // We're going to try and see if there are any known types that have the same name as + // our return type, and then check if those are convertible to Task. If they are then + // we assume the user just has a missing using. If they are not, we wrap the return + // type in a generic Task. + var typeName = returnType.Name; + + var results = await SymbolFinder.FindDeclarationsAsync( + document.Project, typeName, ignoreCase: false, filter: SymbolFilter.Type, cancellationToken: cancellationToken).ConfigureAwait(false); + + if (results.OfType<ITypeSymbol>().Any(s => DoesConversionExist(compilation, s, taskSymbol))) + { + return AddAsyncKeyword(methodNode); + } + + return AddAsyncKeywordAndTaskReturnType(methodNode, returnType, genericTaskSymbol); + } + + if (DoesConversionExist(compilation, returnType, taskSymbol)) + { + return AddAsyncKeyword(methodNode); + } + + return AddAsyncKeywordAndTaskReturnType(methodNode, returnType, genericTaskSymbol); + } + } + + + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.AddAwait), Shared] + internal class CSharpAddAwaitCodeFixProvider : AbstractAddAsyncAwaitCodeFixProvider + { + /// <summary> + /// Since this is an async method, the return expression must be of type 'blah' rather than 'baz' + /// </summary> + private const string CS4014 = "CS4014"; + + /// <summary> + /// Because this call is not awaited, execution of the current method continues before the call is completed. + /// </summary> + private const string CS4016 = "CS4016"; + + public override ImmutableArray<string> FixableDiagnosticIds + { + get { return ImmutableArray.Create(CS4014, CS4016); } + } + + protected override string GetDescription(Diagnostic diagnostic, SyntaxNode node, SemanticModel semanticModel, CancellationToken cancellationToken) + { + return GettextCatalog.GetString ("Insert 'await'"); + } + + protected override Task<SyntaxNode> GetNewRoot(SyntaxNode root, SyntaxNode oldNode, SemanticModel semanticModel, Diagnostic diagnostic, Document document, CancellationToken cancellationToken) + { + var expression = oldNode as ExpressionSyntax; + + switch (diagnostic.Id) + { + case CS4014: + if (expression == null) + { + return Task.FromResult<SyntaxNode>(null); + } + + return Task.FromResult(root.ReplaceNode(oldNode, ConvertToAwaitExpression(expression))); + case CS4016: + if (expression == null) + { + return Task.FromResult (default (SyntaxNode)); + } + + if (!IsCorrectReturnType(expression, semanticModel)) + { + return Task.FromResult (default (SyntaxNode)); + } + + return Task.FromResult(root.ReplaceNode(oldNode, ConvertToAwaitExpression(expression))); + default: + return Task.FromResult (default (SyntaxNode)); + } + } + + private bool IsCorrectReturnType(ExpressionSyntax expression, SemanticModel semanticModel) + { + INamedTypeSymbol taskType = null; + INamedTypeSymbol returnType = null; + return TryGetTypes(expression, semanticModel, out taskType, out returnType) && + semanticModel.Compilation.ClassifyConversion(taskType, returnType).Exists; + } + + private static ExpressionSyntax ConvertToAwaitExpression(ExpressionSyntax expression) + { + return SyntaxFactory.AwaitExpression(expression) + .WithAdditionalAnnotations(Formatter.Annotation); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpConvertToAsyncMethodCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpConvertToAsyncMethodCodeFixProvider.cs new file mode 100644 index 0000000000..5bd232f594 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/Async/CSharpConvertToAsyncMethodCodeFixProvider.cs @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CodeActions; +using ICSharpCode.NRefactory6.CSharp.Refactoring; +using MonoDevelop.CSharp.CodeFixes; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using MonoDevelop.Core; + +namespace MonoDevelop.CSharp.CodeFixes +{ + internal abstract partial class AbstractChangeToAsyncCodeFixProvider : AbstractAsyncCodeFix + { + protected abstract Task<string> GetDescription(Diagnostic diagnostic, SyntaxNode node, SemanticModel semanticModel, CancellationToken cancellationToken); + protected abstract Task<Tuple<SyntaxTree, SyntaxNode>> GetRootInOtherSyntaxTree(SyntaxNode node, SemanticModel semanticModel, Diagnostic diagnostic, CancellationToken cancellationToken); + + protected override async Task<CodeAction> GetCodeFix(SyntaxNode root, SyntaxNode node, Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + { + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var result = await GetRootInOtherSyntaxTree(node, semanticModel, diagnostic, cancellationToken).ConfigureAwait(false); + if (result != null) + { + var syntaxTree = result.Item1; + var newRoot = result.Item2; + var otherDocument = document.Project.Solution.GetDocument(syntaxTree); + return new DocumentChangeAction(node.Span, DiagnosticSeverity.Error, + await this.GetDescription(diagnostic, node, semanticModel, cancellationToken).ConfigureAwait(false), + token => Task.FromResult(otherDocument.WithSyntaxRoot(newRoot))); + } + + return null; + } + } + + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.ConvertToAsync), Shared] + internal class CSharpConvertToAsyncMethodCodeFixProvider : AbstractChangeToAsyncCodeFixProvider + { + /// <summary> + /// Cannot await void. + /// </summary> + private const string CS4008 = "CS4008"; + + public override ImmutableArray<string> FixableDiagnosticIds + { + get { return ImmutableArray.Create(CS4008); } + } + + protected override async Task<string> GetDescription( + Diagnostic diagnostic, + SyntaxNode node, + SemanticModel semanticModel, + CancellationToken cancellationToken) + { + var methodNode = await GetMethodDeclaration(node, semanticModel, cancellationToken).ConfigureAwait(false); + return string.Format(GettextCatalog.GetString ("Make {0} return Task instead of void"), methodNode.WithBody(null)); + } + + protected override async Task<Tuple<SyntaxTree, SyntaxNode>> GetRootInOtherSyntaxTree( + SyntaxNode node, + SemanticModel semanticModel, + Diagnostic diagnostic, + CancellationToken cancellationToken) + { + var methodDeclaration = await GetMethodDeclaration(node, semanticModel, cancellationToken).ConfigureAwait(false); + if (methodDeclaration == null) + { + return null; + } + + var oldRoot = await methodDeclaration.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); + var newRoot = oldRoot.ReplaceNode(methodDeclaration, ConvertToAsyncFunction(methodDeclaration)); + return Tuple.Create(oldRoot.SyntaxTree, newRoot); + } + + private async Task<MethodDeclarationSyntax> GetMethodDeclaration( + SyntaxNode node, + SemanticModel semanticModel, + CancellationToken cancellationToken) + { + var invocationExpression = node.ChildNodes().FirstOrDefault(n => n.IsKind(SyntaxKind.InvocationExpression)); + var methodSymbol = semanticModel.GetSymbolInfo(invocationExpression, cancellationToken).Symbol as IMethodSymbol; + if (methodSymbol == null) + { + return null; + } + + var methodReference = methodSymbol.DeclaringSyntaxReferences.FirstOrDefault(); + if (methodReference == null) + { + return null; + } + + var methodDeclaration = (await methodReference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false)) as MethodDeclarationSyntax; + if (methodDeclaration == null) + { + return null; + } + + if (!methodDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.AsyncKeyword))) + { + return null; + } + + return methodDeclaration; + } + + private MethodDeclarationSyntax ConvertToAsyncFunction(MethodDeclarationSyntax methodDeclaration) + { + return methodDeclaration.WithReturnType( + SyntaxFactory.ParseTypeName("Task") + .WithLeadingTrivia(methodDeclaration.ReturnType.GetLeadingTrivia()) + .WithTrailingTrivia(methodDeclaration.ReturnType.GetTrailingTrivia())); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/FullyQualify/CSharpFullyQualifyCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/FullyQualify/CSharpFullyQualifyCodeFixProvider.cs new file mode 100644 index 0000000000..01fb9b6052 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/FullyQualify/CSharpFullyQualifyCodeFixProvider.cs @@ -0,0 +1,331 @@ +// +// CSharpFullyQualifyCodeFixProvider.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Collections.Immutable; +using System.Threading; +using Microsoft.CodeAnalysis.CodeFixes; +using ICSharpCode.NRefactory6.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using System.Threading.Tasks; +using System.Linq; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.FindSymbols; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.CodeActions; +using System; +using ICSharpCode.NRefactory6.CSharp.Refactoring; +using MonoDevelop.Ide.TypeSystem; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using MonoDevelop.Core; + +namespace MonoDevelop.CSharp.CodeFixes.FullyQualify +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = "Fully Qualify")] + [ExtensionOrder(After = PredefinedCodeFixProviderNames.AddUsingOrImport)] + class CSharpFullyQualifyCodeFixProvider : CodeFixProvider + { + /// <summary> + /// name does not exist in context + /// </summary> + private const string CS0103 = "CS0103"; + + /// <summary> + /// 'reference' is an ambiguous reference between 'identifier' and 'identifier' + /// </summary> + private const string CS0104 = "CS0104"; + + /// <summary> + /// type or namespace could not be found + /// </summary> + private const string CS0246 = "CS0246"; + + /// <summary> + /// wrong number of type args + /// </summary> + private const string CS0305 = "CS0305"; + + /// <summary> + /// The non-generic type 'A' cannot be used with type arguments + /// </summary> + private const string CS0308 = "CS0308"; + + public override ImmutableArray<string> FixableDiagnosticIds + { + get { return ImmutableArray.Create(CS0103, CS0104, CS0246, CS0305, CS0308); } + } + + protected bool IgnoreCase + { + get { return false; } + } + + protected bool CanFullyQualify(Diagnostic diagnostic, ref SyntaxNode node) + { + var simpleName = node as SimpleNameSyntax; + if (simpleName == null) + { + return false; + } + + if (!simpleName.LooksLikeStandaloneTypeName()) + { + return false; + } + + if (!simpleName.CanBeReplacedWithAnyName()) + { + return false; + } + + return true; + } + + protected SyntaxNode ReplaceNode(SyntaxNode node, string containerName, CancellationToken cancellationToken) + { + var simpleName = (SimpleNameSyntax)node; + + var leadingTrivia = simpleName.GetLeadingTrivia(); + var newName = simpleName.WithLeadingTrivia(SyntaxTriviaList.Empty); + + var qualifiedName = SyntaxFactory.QualifiedName( + SyntaxFactory.ParseName(containerName), newName); + + qualifiedName = qualifiedName.WithLeadingTrivia(leadingTrivia); + qualifiedName = qualifiedName.WithAdditionalAnnotations(Formatter.Annotation); + + var syntaxTree = simpleName.SyntaxTree; + return syntaxTree.GetRoot(cancellationToken).ReplaceNode((NameSyntax)simpleName, qualifiedName); + } + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var document = context.Document; + var span = context.Span; + var diagnostics = context.Diagnostics; + var cancellationToken = context.CancellationToken; + + var project = document.Project; + var diagnostic = diagnostics.First(); + var model = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait (false); + if (model.IsFromGeneratedCode (context.CancellationToken)) + return; + + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var node = root.FindToken(span.Start).GetAncestors<SyntaxNode>().First(n => n.Span.Contains(span)); + + // Has to be a simple identifier or generic name. + if (node != null && CanFullyQualify(diagnostic, ref node)) + { + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var matchingTypes = await this.GetMatchingTypesAsync(project, semanticModel, node, cancellationToken).ConfigureAwait(false); + var matchingNamespaces = await this.GetMatchingNamespacesAsync(project, semanticModel, node, cancellationToken).ConfigureAwait(false); + + if (matchingTypes != null || matchingNamespaces != null) + { + matchingTypes = matchingTypes ?? SpecializedCollections.EmptyEnumerable<ISymbol>(); + matchingNamespaces = matchingNamespaces ?? SpecializedCollections.EmptyEnumerable<ISymbol>(); + + var matchingTypeContainers = FilterAndSort(GetContainers(matchingTypes, semanticModel.Compilation)); + var matchingNamespaceContainers = FilterAndSort(GetContainers(matchingNamespaces, semanticModel.Compilation)); + + var proposedContainers = + matchingTypeContainers.Concat(matchingNamespaceContainers) + .Distinct() + .Take(8); + + foreach (var container in proposedContainers) + { + var containerName = container.ToMinimalDisplayString(semanticModel, node.SpanStart); + + string name; + int arity; + node.GetNameAndArityOfSimpleName(out name, out arity); + + // Actual member name might differ by case. + string memberName; + if (this.IgnoreCase) + { + var member = container.GetMembers(name).FirstOrDefault(); + memberName = member != null ? member.Name : name; + } + else + { + memberName = name; + } + + var codeAction = new DocumentChangeAction( + node.Span, + DiagnosticSeverity.Info, + string.Format(GettextCatalog.GetString ("Change '{0}' to '{1}.{2}'"), name, containerName, memberName), + (c) => + { + var newRoot = this.ReplaceNode(node, containerName, c); + return Task.FromResult(document.WithSyntaxRoot(newRoot)); + }); + + context.RegisterCodeFix(codeAction, diagnostic); + } + } + } + } + + internal async Task<IEnumerable<ISymbol>> GetMatchingTypesAsync( + Microsoft.CodeAnalysis.Project project, SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) + { + // Can't be on the right hand side of binary expression (like 'dot'). + cancellationToken.ThrowIfCancellationRequested(); + string name; + int arity; + node.GetNameAndArityOfSimpleName(out name, out arity); + + var symbols = await SymbolFinder.FindDeclarationsAsync(project, name, this.IgnoreCase, SymbolFilter.Type, cancellationToken).ConfigureAwait(false); + + // also lookup type symbols with the "Attribute" suffix. + var inAttributeContext = node.IsAttributeName(); + if (inAttributeContext) + { + symbols = symbols.Concat( + await SymbolFinder.FindDeclarationsAsync(project, name + "Attribute", this.IgnoreCase, SymbolFilter.Type, cancellationToken).ConfigureAwait(false)); + } + + var accessibleTypeSymbols = symbols + .OfType<INamedTypeSymbol>() + .Where(s => (arity == 0 || s.GetArity() == arity) + && s.IsAccessibleWithin(semanticModel.Compilation.Assembly) + && (!inAttributeContext || s.IsAttribute()) + && HasValidContainer(s)) + .ToList(); + + return accessibleTypeSymbols; + } + + private static bool HasValidContainer(ISymbol symbol) + { + var container = symbol.ContainingSymbol; + return container is INamespaceSymbol || + (container is INamedTypeSymbol && !((INamedTypeSymbol)container).IsGenericType); + } + + internal async Task<IEnumerable<ISymbol>> GetMatchingNamespacesAsync( + Microsoft.CodeAnalysis.Project project, + SemanticModel semanticModel, + SyntaxNode simpleName, + CancellationToken cancellationToken) + { + if (simpleName.IsAttributeName()) + { + return null; + } + + string name; + int arity; + simpleName.GetNameAndArityOfSimpleName(out name, out arity); + if (cancellationToken.IsCancellationRequested) + { + return null; + } + + var symbols = await SymbolFinder.FindDeclarationsAsync(project, name, this.IgnoreCase, SymbolFilter.Namespace, cancellationToken).ConfigureAwait(false); + + var namespaces = symbols + .OfType<INamespaceSymbol>() + .Where(n => !n.IsGlobalNamespace && + HasAccessibleTypes(n, semanticModel, cancellationToken)); + + return namespaces; + } + + private bool HasAccessibleTypes(INamespaceSymbol @namespace, SemanticModel model, CancellationToken cancellationToken) + { + return Enumerable.Any(@namespace.GetAllTypes(cancellationToken), t => t.IsAccessibleWithin(model.Compilation.Assembly)); + } + + private static IEnumerable<INamespaceOrTypeSymbol> GetContainers(IEnumerable<ISymbol> symbols, Compilation compilation) + { + foreach (var symbol in symbols) + { + var containingSymbol = symbol.ContainingSymbol as INamespaceOrTypeSymbol; + if (containingSymbol is INamespaceSymbol) + { + containingSymbol = compilation.GetCompilationNamespace((INamespaceSymbol)containingSymbol); + } + + if (containingSymbol != null) + { + yield return containingSymbol; + } + } + } + + private IEnumerable<INamespaceOrTypeSymbol> FilterAndSort(IEnumerable<INamespaceOrTypeSymbol> symbols) + { + symbols = symbols ?? SpecializedCollections.EmptyList<INamespaceOrTypeSymbol>(); + var list = symbols.Distinct ().Where<INamespaceOrTypeSymbol> (n => n is INamedTypeSymbol || !((INamespaceSymbol)n).IsGlobalNamespace).ToList (); + list.Sort (this.Compare); + return list; + } + + private static readonly ConditionalWeakTable<INamespaceOrTypeSymbol, IList<string>> s_symbolToNameMap = + new ConditionalWeakTable<INamespaceOrTypeSymbol, IList<string>>(); + private static readonly ConditionalWeakTable<INamespaceOrTypeSymbol, IList<string>>.CreateValueCallback s_getNameParts = GetNameParts; + + private static IList<string> GetNameParts(INamespaceOrTypeSymbol symbol) + { + return symbol.ToDisplayString(Ambience.NameFormat).Split('.'); + } + + private int Compare(INamespaceOrTypeSymbol n1, INamespaceOrTypeSymbol n2) + { + if (n1 is INamedTypeSymbol && n2 is INamespaceSymbol) + { + return -1; + } + else if (n1 is INamespaceSymbol && n2 is INamedTypeSymbol) + { + return 1; + } + + var names1 = s_symbolToNameMap.GetValue(n1, GetNameParts); + var names2 = s_symbolToNameMap.GetValue(n2, GetNameParts); + + for (var i = 0; i < Math.Min(names1.Count, names2.Count); i++) + { + var comp = names1[i].CompareTo(names2[i]); + if (comp != 0) + { + return comp; + } + } + + return names1.Count - names2.Count; + } + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateConstructor/AbstractGenerateMemberCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateConstructor/AbstractGenerateMemberCodeFixProvider.cs new file mode 100644 index 0000000000..a6ced93451 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateConstructor/AbstractGenerateMemberCodeFixProvider.cs @@ -0,0 +1,105 @@ +// +// AbstractGenerateMemberCodeFixProvider.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using ICSharpCode.NRefactory6.CSharp; + +namespace MonoDevelop.CSharp.CodeFixes.GenerateConstructor +{ + internal abstract class AbstractGenerateMemberCodeFixProvider : CodeFixProvider + { + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + // NOTE(DustinCa): Not supported in REPL for now. + if (context.Document.SourceCodeKind == SourceCodeKind.Interactive) + { + return; + } + var model = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait (false); + if (model.IsFromGeneratedCode (context.CancellationToken)) + return; + + var root = await model.SyntaxTree.GetRootAsync (context.CancellationToken).ConfigureAwait (false); + var names = GetTargetNodes(root, context.Span); + foreach (var name in names) + { + var codeActions = await GetCodeActionsAsync(context.Document, name, context.CancellationToken).ConfigureAwait(false); + if (codeActions == null) + { + continue; + } + foreach (var act in codeActions) + context.RegisterCodeFix (act, context.Diagnostics); + return; + } + } + + protected abstract Task<IEnumerable<CodeAction>> GetCodeActionsAsync(Document document, SyntaxNode node, CancellationToken cancellationToken); + + protected virtual SyntaxNode GetTargetNode(SyntaxNode node) + { + return node; + } + + protected virtual bool IsCandidate(SyntaxNode node) + { + return false; + } + + protected virtual IEnumerable<SyntaxNode> GetTargetNodes(SyntaxNode root, TextSpan span) + { + var token = root.FindToken(span.Start); + if (!token.Span.IntersectsWith(span)) + { + yield break; + } + + var nodes = token.GetAncestors<SyntaxNode>().Where(IsCandidate); + foreach (var node in nodes) + { + var name = GetTargetNode(node); + + if (name != null) + { + yield return name; + } + } + } + } + +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateConstructor/GenerateConstructorCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateConstructor/GenerateConstructorCodeFixProvider.cs new file mode 100644 index 0000000000..42853f44da --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateConstructor/GenerateConstructorCodeFixProvider.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis; +using ICSharpCode.NRefactory6.CSharp; +using System.Linq; +using ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateConstructor; +using System; + +namespace MonoDevelop.CSharp.CodeFixes.GenerateConstructor +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.GenerateConstructor), Shared] + [ExtensionOrder(After = PredefinedCodeFixProviderNames.FullyQualify)] + internal class GenerateConstructorCodeFixProvider : AbstractGenerateMemberCodeFixProvider + { + private const string CS0122 = "CS0122"; // CS0122: 'C' is inaccessible due to its protection level + private const string CS1729 = "CS1729"; // CS1729: 'C' does not contain a constructor that takes n arguments + private const string CS1739 = "CS1739"; // CS1739: The best overload for 'Program' does not have a parameter named 'v' + private const string CS1503 = "CS1503"; // CS1503: Argument 1: cannot convert from 'T1' to 'T2' + private const string CS7036 = "CS7036"; // CS7036: There is no argument given that corresponds to the required formal parameter 'v' of 'C.C(int)' + + public override ImmutableArray<string> FixableDiagnosticIds + { + get { return ImmutableArray.Create(CS0122, CS1729, CS1739, CS1503, CS7036); } + } + + protected override Task<IEnumerable<CodeAction>> GetCodeActionsAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) + { + return new CSharpGenerateConstructorService ().GenerateConstructorAsync (document, node, cancellationToken); + } + + protected override bool IsCandidate(SyntaxNode node) + { + return node is SimpleNameSyntax || node is ObjectCreationExpressionSyntax || node is ConstructorInitializerSyntax || node is AttributeSyntax; + } + + protected override SyntaxNode GetTargetNode(SyntaxNode node) + { + var objectCreationNode = node as ObjectCreationExpressionSyntax; + if (objectCreationNode != null) + { + return objectCreationNode.Type.GetRightmostName(); + } + + var attributeNode = node as AttributeSyntax; + if (attributeNode != null) + { + return attributeNode.Name; + } + + return node; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateEnumMember/GenerateEnumMemberCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateEnumMember/GenerateEnumMemberCodeFixProvider.cs new file mode 100644 index 0000000000..29823cc623 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateEnumMember/GenerateEnumMemberCodeFixProvider.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis; +using MonoDevelop.CSharp.CodeFixes.GenerateConstructor; +using ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateEnumMember; + +namespace MonoDevelop.CSharp.CodeFixes.GenerateEnumMember +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.GenerateEnumMember), Shared] + [ExtensionOrder(After = PredefinedCodeFixProviderNames.GenerateConstructor)] + internal class GenerateEnumMemberCodeFixProvider : AbstractGenerateMemberCodeFixProvider + { + private const string CS0117 = "CS0117"; // error CS0117: 'Color' does not contain a definition for 'Red' + + public override ImmutableArray<string> FixableDiagnosticIds + { + get { return ImmutableArray.Create(CS0117); } + } + static CSharpGenerateEnumMemberService service = new CSharpGenerateEnumMemberService(); + + protected override Task<IEnumerable<CodeAction>> GetCodeActionsAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) + { + return service.GenerateEnumMemberAsync(document, node, cancellationToken); + } + + protected override bool IsCandidate(SyntaxNode node) + { + return node is IdentifierNameSyntax; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateMethod/GenerateConversionCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateMethod/GenerateConversionCodeFixProvider.cs new file mode 100644 index 0000000000..4db9c211a7 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateMethod/GenerateConversionCodeFixProvider.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis; +using MonoDevelop.CSharp.CodeFixes.GenerateConstructor; +using Microsoft.CodeAnalysis.CSharp; +using ICSharpCode.NRefactory6.CSharp; +using ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateParameterizedMember; + +namespace MonoDevelop.CSharp.CodeFixes.GenerateMethod +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.GenerateConversion), Shared] + [ExtensionOrder(After = PredefinedCodeFixProviderNames.GenerateEnumMember)] + internal class GenerateConversionCodeFixProvider : AbstractGenerateMemberCodeFixProvider + { + private const string CS0029 = "CS0029"; // error CS0029: Cannot implicitly convert type 'type' to 'type' + private const string CS0030 = "CS0030"; // error CS0030: Cannot convert type 'type' to 'type' + + public override ImmutableArray<string> FixableDiagnosticIds + { + get { return ImmutableArray.Create(CS0029, CS0030); } + } + + protected override bool IsCandidate(SyntaxNode node) + { + return node.IsKind(SyntaxKind.IdentifierName) || + node.IsKind(SyntaxKind.MethodDeclaration) || + node.IsKind(SyntaxKind.InvocationExpression) || + node.IsKind(SyntaxKind.CastExpression) || + node is LiteralExpressionSyntax || + node is SimpleNameSyntax || + node is ExpressionSyntax; + } + + protected override SyntaxNode GetTargetNode(SyntaxNode node) + { + var invocation = node as InvocationExpressionSyntax; + if (invocation != null) + { + return invocation.Expression.GetRightmostName(); + } + + var memberBindingExpression = node as MemberBindingExpressionSyntax; + if (memberBindingExpression != null) + { + return memberBindingExpression.Name; + } + + return node; + } + static CSharpGenerateConversionService service = new CSharpGenerateConversionService (); + + protected override Task<IEnumerable<CodeAction>> GetCodeActionsAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) + { + return service.GenerateConversionAsync(document, node, cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateMethod/GenerateMethodCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateMethod/GenerateMethodCodeFixProvider.cs new file mode 100644 index 0000000000..c1c8396fa3 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateMethod/GenerateMethodCodeFixProvider.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis; +using MonoDevelop.CSharp.CodeFixes.GenerateConstructor; +using ICSharpCode.NRefactory6.CSharp.GenerateMember.GenerateParameterizedMember; +using Microsoft.CodeAnalysis.CSharp; +using ICSharpCode.NRefactory6.CSharp; + +namespace MonoDevelop.CSharp.CodeFixes.GenerateMethod +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.GenerateMethod), Shared] + [ExtensionOrder(After = PredefinedCodeFixProviderNames.GenerateEnumMember)] + internal class GenerateMethodCodeFixProvider : AbstractGenerateMemberCodeFixProvider + { + private const string CS0103 = "CS0103"; // error CS0103: Error The name 'Foo' does not exist in the current context + private const string CS1061 = "CS1061"; // error CS1061: Error 'Class' does not contain a definition for 'Foo' and no extension method 'Foo' + private const string CS0117 = "CS0117"; // error CS0117: 'Class' does not contain a definition for 'Foo' + private const string CS0122 = "CS0122"; // error CS0122: 'Class' is inaccessible due to its protection level. + private const string CS0539 = "CS0539"; // error CS0539: 'A.Foo<T>()' in explicit interface declaration is not a member of interface + private const string CS1501 = "CS1501"; // error CS1501: No overload for method 'M' takes 1 arguments + private const string CS1503 = "CS1503"; // error CS1503: Argument 1: cannot convert from 'double' to 'int' + private const string CS0305 = "CS0305"; // error CS0305: Using the generic method 'CA.M<V>()' requires 1 type arguments + private const string CS0308 = "CS0308"; // error CS0308: The non-generic method 'Program.Foo()' cannot be used with type arguments + private const string CS1660 = "CS1660"; // error CS1660: Cannot convert lambda expression to type 'string[]' because it is not a delegate type + private const string CS1739 = "CS1739"; // error CS1739: The best overload for 'M' does not have a parameter named 'x' + private const string CS7036 = "CS7036"; // error CS7036: There is no argument given that corresponds to the required formal parameter 'x' of 'C.M(int)' + + public override ImmutableArray<string> FixableDiagnosticIds + { + get { return ImmutableArray.Create(CS0103, CS1061, CS0117, CS0122, CS0539, CS1501, CS1503, CS0305, CS0308, CS1660, CS1739, CS7036); } + } + + protected override bool IsCandidate(SyntaxNode node) + { + return node.IsKind(SyntaxKind.IdentifierName) || + node.IsKind(SyntaxKind.MethodDeclaration) || + node.IsKind(SyntaxKind.InvocationExpression) || + node.IsKind(SyntaxKind.CastExpression) || + node is LiteralExpressionSyntax || + node is SimpleNameSyntax || + node is ExpressionSyntax; + } + + protected override SyntaxNode GetTargetNode(SyntaxNode node) + { + var invocation = node as InvocationExpressionSyntax; + if (invocation != null) + { + return invocation.Expression.GetRightmostName(); + } + + var memberBindingExpression = node as MemberBindingExpressionSyntax; + if (memberBindingExpression != null) + { + return memberBindingExpression.Name; + } + + return node; + } + static CSharpGenerateMethodService service = new CSharpGenerateMethodService (); + + protected override Task<IEnumerable<CodeAction>> GetCodeActionsAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) + { + return service.GenerateMethodAsync(document, node, cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateType/GenerateTypeCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateType/GenerateTypeCodeFixProvider.cs new file mode 100644 index 0000000000..6039a17dc7 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateType/GenerateTypeCodeFixProvider.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis; +using MonoDevelop.CSharp.CodeFixes.GenerateConstructor; +using ICSharpCode.NRefactory6.CSharp.GenerateType; +using Microsoft.CodeAnalysis.CSharp; +using ICSharpCode.NRefactory6.CSharp; + +namespace MonoDevelop.CSharp.CodeFixes.GenerateType +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.GenerateType), Shared] + [ExtensionOrder(After = PredefinedCodeFixProviderNames.GenerateVariable)] + internal class GenerateTypeCodeFixProvider : AbstractGenerateMemberCodeFixProvider + { + private const string CS0103 = "CS0103"; // error CS0103: The name 'Foo' does not exist in the current context + private const string CS0117 = "CS0117"; // error CS0117: 'x' does not contain a definition for 'y' + private const string CS0234 = "CS0234"; // error CS0234: The type or namespace name 'C' does not exist in the namespace 'N' (are you missing an assembly reference?) + private const string CS0246 = "CS0246"; // error CS0246: The type or namespace name 'T' could not be found (are you missing a using directive or an assembly reference?) + private const string CS0305 = "CS0305"; // error CS0305: Using the generic type 'C<T1>' requires 1 type arguments + private const string CS0308 = "CS0308"; // error CS0308: The non-generic type 'A' cannot be used with type arguments + private const string CS0426 = "CS0426"; // error CS0426: The type name 'S' does not exist in the type 'Program' + private const string CS0616 = "CS0616"; // error CS0616: 'x' is not an attribute class + + public override ImmutableArray<string> FixableDiagnosticIds + { + get { return ImmutableArray.Create(CS0103, CS0117, CS0234, CS0246, CS0305, CS0308, CS0426, CS0616); } + } + + protected override bool IsCandidate(SyntaxNode node) + { + var qualified = node as QualifiedNameSyntax; + if (qualified != null) + { + return true; + } + + var simple = node as SimpleNameSyntax; + if (simple != null) + { + return !simple.IsParentKind(SyntaxKind.QualifiedName); + } + + var memberAccess = node as MemberAccessExpressionSyntax; + if (memberAccess != null) + { + return true; + } + if (node.IsKind (SyntaxKind.ObjectCreationExpression)) + return true; + return false; + } + + protected override SyntaxNode GetTargetNode(SyntaxNode node) + { + if (node.IsKind (SyntaxKind.ObjectCreationExpression)) + node = ((ObjectCreationExpressionSyntax)node).Type; + return ((ExpressionSyntax)node).GetRightmostName(); + } + + static readonly CSharpGenerateTypeService service = new CSharpGenerateTypeService (); + + protected override Task<IEnumerable<CodeAction>> GetCodeActionsAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) + { + return service.GenerateTypeAsync(document, node, cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateVariable/GenerateVariableCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateVariable/GenerateVariableCodeFixProvider.cs new file mode 100644 index 0000000000..11e589fc05 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/GenerateVariable/GenerateVariableCodeFixProvider.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.GenerateMember.GenerateVariable; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; +using MonoDevelop.CSharp.CodeFixes.GenerateConstructor; + +namespace MonoDevelop.CSharp.CodeFixes.GenerateVariable +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.GenerateVariable), Shared] + [ExtensionOrder(After = PredefinedCodeFixProviderNames.GenerateMethod)] + internal class GenerateVariableCodeFixProvider : AbstractGenerateMemberCodeFixProvider + { + private const string CS1061 = "CS1061"; // error CS1061: 'C' does not contain a definition for 'Foo' and no extension method 'Foo' accepting a first argument of type 'C' could be found + private const string CS0103 = "CS0103"; // error CS0103: The name 'Foo' does not exist in the current context + private const string CS0117 = "CS0117"; // error CS0117: 'TestNs.Program' does not contain a definition for 'blah' + private const string CS0539 = "CS0539"; // error CS0539: 'Class.SomeProp' in explicit interface declaration is not a member of interface + private const string CS0246 = "CS0246"; // error CS0246: The type or namespace name 'Version' could not be found + + public override ImmutableArray<string> FixableDiagnosticIds + { + get { return ImmutableArray.Create(CS1061, CS0103, CS0117, CS0539, CS0246); } + } + + protected override bool IsCandidate(SyntaxNode node) + { + return node is SimpleNameSyntax || node is PropertyDeclarationSyntax || node is MemberBindingExpressionSyntax; + } + + protected override SyntaxNode GetTargetNode(SyntaxNode node) + { + if (node.IsKind(SyntaxKind.MemberBindingExpression)) + { + var nameNode = node.ChildNodes().FirstOrDefault(n => n.IsKind(SyntaxKind.IdentifierName)); + if (nameNode != null) + { + return nameNode; + } + } + + return base.GetTargetNode(node); + } + static readonly CSharpGenerateVariableService service = new CSharpGenerateVariableService (); + + protected override Task<IEnumerable<CodeAction>> GetCodeActionsAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) + { + return service.GenerateVariableAsync(document, node, cancellationToken); + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/ImplementAbstractClass/ImplementAbstractClassCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/ImplementAbstractClass/ImplementAbstractClassCodeFixProvider.cs new file mode 100644 index 0000000000..f821d0d4a6 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/ImplementAbstractClass/ImplementAbstractClassCodeFixProvider.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis; +using ICSharpCode.NRefactory6.CSharp.Features.ImplementAbstractClass; +using MonoDevelop.Core; +using ICSharpCode.NRefactory6.CSharp.Refactoring; +using ICSharpCode.NRefactory6.CSharp; + +namespace MonoDevelop.CSharp.CodeFixes.ImplementAbstractClass +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.ImplementAbstractClass), Shared] + [ExtensionOrder(After = PredefinedCodeFixProviderNames.GenerateType)] + internal class ImplementAbstractClassCodeFixProvider : CodeFixProvider + { + private const string CS0534 = "CS0534"; // 'Program' does not implement inherited abstract member 'Foo.bar()' + + public sealed override ImmutableArray<string> FixableDiagnosticIds + { + get { return ImmutableArray.Create(CS0534); } + } + + public sealed override FixAllProvider GetFixAllProvider() + { + return WellKnownFixAllProviders.BatchFixer; + } + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + var token = root.FindToken(context.Span.Start); + if (!token.Span.IntersectsWith(context.Span)) + { + return; + } + + var classNode = token.Parent as ClassDeclarationSyntax; + if (classNode == null) + { + return; + } + + var model = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (model.IsFromGeneratedCode (context.CancellationToken)) + return; + + foreach (var baseTypeSyntax in classNode.BaseList.Types) + { + var node = baseTypeSyntax.Type; + + if (service.CanImplementAbstractClass( + context.Document, + model, + node, + context.CancellationToken)) + { + var title = GettextCatalog.GetString ("Implement Abstract Class"); + var abstractType = model.GetTypeInfo(node, context.CancellationToken).Type; + var id = GetCodeActionId(abstractType.ContainingAssembly.Name, abstractType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + context.RegisterCodeFix( + new DocumentChangeAction(node.Span, DiagnosticSeverity.Error, title, + (c) => ImplementAbstractClassAsync(context.Document, node, c)), + context.Diagnostics); + return; + } + } + } + + // internal for testing purposes. + internal static string GetCodeActionId(string assemblyName, string abstractTypeFullyQualifiedName) + { + return "ImplementAbstractClass;" + + assemblyName + ";" + + abstractTypeFullyQualifiedName; + } + static CSharpImplementAbstractClassService service = new CSharpImplementAbstractClassService (); + + private async Task<Document> ImplementAbstractClassAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) + { + return await service.ImplementAbstractClassAsync( + document, + await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false), + node, + cancellationToken).ConfigureAwait(false); + } + + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/ImplementInterface/ImplementInterfaceCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/ImplementInterface/ImplementInterfaceCodeFixProvider.cs new file mode 100644 index 0000000000..a9eb6c8d2f --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/ImplementInterface/ImplementInterfaceCodeFixProvider.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis; +using ICSharpCode.NRefactory6.CSharp.Features.ImplementInterface; +using ICSharpCode.NRefactory6.CSharp; + +namespace MonoDevelop.CSharp.CodeFixes.ImplementInterface +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.ImplementInterface), Shared] + [ExtensionOrder(After = PredefinedCodeFixProviderNames.ImplementAbstractClass)] + internal class ImplementInterfaceCodeFixProvider : CodeFixProvider + { + private readonly Func<TypeSyntax, bool> _interfaceName = n => n.Parent is BaseTypeSyntax && n.Parent.Parent is BaseListSyntax && ((BaseTypeSyntax)n.Parent).Type == n; + private readonly Func<IEnumerable<CodeAction>, bool> _codeActionAvailable = actions => actions != null && actions.Any(); + + internal const string CS0535 = "CS0535"; // 'Program' does not implement interface member 'System.Collections.IEnumerable.GetEnumerator()' + internal const string CS0737 = "CS0737"; // 'Class' does not implement interface member 'IInterface.M()'. 'Class.M()' cannot implement an interface member because it is not public. + internal const string CS0738 = "CS0738"; // 'C' does not implement interface member 'I.Method1()'. 'B.Method1()' cannot implement 'I.Method1()' because it does not have the matching return type of 'void'. + + public sealed override ImmutableArray<string> FixableDiagnosticIds + { + get { return ImmutableArray.Create(CS0535, CS0737, CS0738); } + } + static CSharpImplementInterfaceService service = new CSharpImplementInterfaceService(); + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var document = context.Document; + var span = context.Span; + var cancellationToken = context.CancellationToken; + + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + var token = root.FindToken(span.Start); + if (!token.Span.IntersectsWith(span)) + { + return; + } + + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + if (model.IsFromGeneratedCode (cancellationToken)) + return; + + var actions = token.Parent.GetAncestorsOrThis<TypeSyntax>() + .Where(_interfaceName) + .Select(n => service.GetCodeActions(document, model, n, cancellationToken)) + .FirstOrDefault(_codeActionAvailable); + + if (_codeActionAvailable(actions)) + { + context.RegisterFixes(actions, context.Diagnostics); + } + } + + public sealed override FixAllProvider GetFixAllProvider() + { + return WellKnownFixAllProviders.BatchFixer; + } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/MoveTypeToFile/MoveTypeToFile.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/MoveTypeToFile/MoveTypeToFile.cs new file mode 100644 index 0000000000..c1b756a566 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/MoveTypeToFile/MoveTypeToFile.cs @@ -0,0 +1,213 @@ +// +// MoveTypeToFile.cs +// +// Author: +// Mike Krüger <mkrueger@novell.com> +// +// Copyright (c) 2011 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory6.CSharp.Refactoring; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using MonoDevelop.Core; +using MonoDevelop.Ide.Editor; +using MonoDevelop.Core.Text; +using MonoDevelop.Ide; +using System.Collections.Generic; +using MonoDevelop.Ide.StandardHeader; +using ICSharpCode.NRefactory6.CSharp; + +namespace MonoDevelop.CSharp.CodeFixes.MoveTypeToFile +{ + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = "Move type to file")] + class MoveTypeToFile : CodeRefactoringProvider + { + public async override Task ComputeRefactoringsAsync (CodeRefactoringContext context) + { + var document = context.Document; + var span = context.Span; + var cancellationToken = context.CancellationToken; + + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait (false); + if (model.IsFromGeneratedCode (cancellationToken)) + return; + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait (false); + var token = root.FindToken(span.Start); + + var type = token.Parent as BaseTypeDeclarationSyntax; + if (type == null) + return; + + if (Path.GetFileNameWithoutExtension (document.FilePath) == type.Identifier.ToString ()) + return; + + string title; + if (IsSingleType (root)) { + title = String.Format (GettextCatalog.GetString ("Rename file to '{0}'"), Path.GetFileName (GetCorrectFileName (document, type))); + } else { + title = String.Format (GettextCatalog.GetString ("Move type to file '{0}'"), Path.GetFileName (GetCorrectFileName (document, type))); + } + context.RegisterRefactoring (new MyCodeAction (document, title, root, type)); + } + + class MyCodeAction : CodeAction + { + readonly Document document; + readonly BaseTypeDeclarationSyntax type; + readonly SyntaxNode root; + + public MyCodeAction (Document document, string title, SyntaxNode root, BaseTypeDeclarationSyntax type) + { + this.root = root; + this.title = title; + this.type = type; + this.document = document; + + } + + string title; + public override string Title { + get { + return this.title; + } + } + + protected override Task<Document> GetChangedDocumentAsync (System.Threading.CancellationToken cancellationToken) + { + var correctFileName = GetCorrectFileName (document, type); + if (IsSingleType (root)) { + FileService.RenameFile (document.FilePath, correctFileName); + var doc = IdeApp.Workbench.ActiveDocument; + if (doc.HasProject) { + IdeApp.ProjectOperations.Save (doc.Project); + } + return Task.FromResult (document); + } + return Task.FromResult (CreateNewFile (type, correctFileName)); + } + + Document CreateNewFile (BaseTypeDeclarationSyntax type, string correctFileName) + { + var doc = IdeApp.Workbench.ActiveDocument; + var content = doc.Editor.Text; + + var types = new List<BaseTypeDeclarationSyntax> ( + root + .DescendantNodesAndSelf (n => !(n is BaseTypeDeclarationSyntax)) + .OfType<BaseTypeDeclarationSyntax> () + .Where (t => t.SpanStart != type.SpanStart) + ); + types.Sort ((x, y) => y.SpanStart.CompareTo (x.SpanStart)); + + foreach (var removeType in types) { + var bounds = CalcTypeBounds (removeType); + content = content.Remove (bounds.Offset, bounds.Length); + } + + if (doc.HasProject) { + string header = StandardHeaderService.GetHeader (doc.Project, correctFileName, true); + if (!string.IsNullOrEmpty (header)) + content = header + doc.Editor.GetEolMarker () + StripHeader (content); + } + content = StripDoubleBlankLines (content); + + File.WriteAllText (correctFileName, content); + if (doc.HasProject) { + doc.Project.AddFile (correctFileName); + IdeApp.ProjectOperations.Save (doc.Project); + } + + doc.Editor.RemoveText (CalcTypeBounds (type)); + + return document; + } + + ISegment CalcTypeBounds (BaseTypeDeclarationSyntax type) + { + int start = type.Span.Start; + int end = type.Span.End; + foreach (var trivia in type.GetLeadingTrivia ()) { + if (trivia.Kind () == SyntaxKind.SingleLineDocumentationCommentTrivia) { + start = trivia.FullSpan.Start; + } + } + + return TextSegment.FromBounds (start, end); + } + } + + static bool IsBlankLine (IReadonlyTextDocument doc, int i) + { + var line = doc.GetLine (i); + return line.Length == line.GetIndentation (doc).Length; + } + + static string StripDoubleBlankLines (string content) + { + var doc = TextEditorFactory.CreateNewDocument (new StringTextSource (content), "a.cs"); + for (int i = 1; i + 1 <= doc.LineCount; i++) { + if (IsBlankLine (doc, i) && IsBlankLine (doc, i + 1)) { + doc.RemoveText (doc.GetLine (i).SegmentIncludingDelimiter); + i--; + continue; + } + } + return doc.Text; + } + + static string StripHeader (string content) + { + var doc = TextEditorFactory.CreateNewDocument (new StringTextSource (content), ""); + while (true) { + string lineText = doc.GetLineText (1); + if (lineText == null) + break; + if (lineText.StartsWith ("//", StringComparison.Ordinal)) { + doc.RemoveText (doc.GetLine (1).SegmentIncludingDelimiter); + continue; + } + break; + } + return doc.Text; + } + + static bool IsSingleType (SyntaxNode root) + { + return root.DescendantNodesAndSelf (c => !(c is BaseTypeDeclarationSyntax)).OfType<BaseTypeDeclarationSyntax> ().Count () == 1; + } + + internal static string GetCorrectFileName (Document document, BaseTypeDeclarationSyntax type) + { + if (type == null) + return document.FilePath; + return Path.Combine (Path.GetDirectoryName (document.FilePath), type.Identifier + Path.GetExtension (document.FilePath)); + } + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/PredefinedCodeFixProviderNames.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/PredefinedCodeFixProviderNames.cs new file mode 100644 index 0000000000..71e0e7e62e --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/PredefinedCodeFixProviderNames.cs @@ -0,0 +1,64 @@ +// +// PredefinedCodeFixProviderNames.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using MonoDevelop.Core; + +namespace MonoDevelop.CSharp.CodeFixes +{ + static class PredefinedCodeFixProviderNames + { + public const string AddAwait = "Add Await For Expression"; + public const string AddAsync = "Add Async To Member"; + public const string ChangeReturnType = "Change Return Type"; + public const string ChangeToYield = "Change To Yield"; + public const string ConvertToAsync = "Convert To Async"; + public const string ConvertToIterator = "Convert To Iterator"; + public const string CorrectNextControlVariable = "Correct Next Control Variable"; + public const string AddMissingReference = "Add Missing Reference"; + public const string AddUsingOrImport = "Add Using or Import"; + public const string FullyQualify = "Fully Qualify"; + public const string FixIncorrectFunctionReturnType = "Fix Incorrect Function Return Type"; + public const string FixIncorrectExitContinue = "Fix Incorrect Exit Continue"; + public const string GenerateConstructor = "Generate Constructor"; + public const string GenerateEndConstruct = "Generate End Construct"; + public const string GenerateEnumMember = "Generate Enum Member"; + public const string GenerateEvent = "Generate Event"; + public const string GenerateVariable = "Generate Variable"; + public const string GenerateMethod = "Generate Method"; + public const string GenerateConversion = "Generate Conversion"; + public const string GenerateType = "Generate Type"; + public const string ImplementAbstractClass = "Implement Abstract Class"; + public const string ImplementInterface = "Implement Interface"; + public const string InsertMissingCast = "InsertMissingCast"; + public const string MoveToTopOfFile = "Move To Top Of File"; + public const string RemoveUnnecessaryCast = "Remove Unnecessary Casts"; + public const string RemoveUnnecessaryImports = "Remove Unnecessary Usings or Imports"; + public const string RenameTracking = "Rename Tracking"; + public const string SimplifyNames = "Simplify Names"; + public const string SpellCheck = "Spell Check"; + public const string Suppression = "Suppression"; + } +} + diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryCast/RemoveUnnecessaryCastCodeFixProvider.RemoveUnnecessaryCastFixAllProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryCast/RemoveUnnecessaryCastCodeFixProvider.RemoveUnnecessaryCastFixAllProvider.cs new file mode 100644 index 0000000000..ec7d818390 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryCast/RemoveUnnecessaryCastCodeFixProvider.RemoveUnnecessaryCastFixAllProvider.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace MonoDevelop.CSharp.CodeFixes.RemoveUnnecessaryCast +{ + internal partial class RemoveUnnecessaryCastCodeFixProvider : CodeFixProvider + { +// private class RemoveUnnecessaryCastFixAllProvider : BatchSimplificationFixAllProvider +// { +// internal static new readonly RemoveUnnecessaryCastFixAllProvider Instance = new RemoveUnnecessaryCastFixAllProvider(); +// +// protected override SyntaxNode GetNodeToSimplify(SyntaxNode root, SemanticModel model, Diagnostic diagnostic, Workspace workspace, out string codeActionId, CancellationToken cancellationToken) +// { +// codeActionId = null; +// return GetCastNode(root, model, diagnostic.Location.SourceSpan, cancellationToken); +// } +// +// protected override bool NeedsParentFixup +// { +// get +// { +// return true; +// } +// } +// +// protected override async Task<Document> AddSimplifyAnnotationsAsync(Document document, SyntaxNode nodeToSimplify, CancellationToken cancellationToken) +// { +// var cast = nodeToSimplify as CastExpressionSyntax; +// if (cast == null) +// { +// return null; +// } +// +// return await RemoveUnnecessaryCastAsync(document, cast, cancellationToken).ConfigureAwait(false); +// } +// } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryCast/RemoveUnnecessaryCastCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryCast/RemoveUnnecessaryCastCodeFixProvider.cs new file mode 100644 index 0000000000..63770abbdf --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryCast/RemoveUnnecessaryCastCodeFixProvider.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis; +using MonoDevelop.CSharp.Diagnostics; +using ICSharpCode.NRefactory6.CSharp; +using MonoDevelop.Core; +using ICSharpCode.NRefactory6.CSharp.Refactoring; +using Microsoft.CodeAnalysis.CSharp; + +namespace MonoDevelop.CSharp.CodeFixes.RemoveUnnecessaryCast +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.RemoveUnnecessaryCast), Shared] + [ExtensionOrder(After = PredefinedCodeFixProviderNames.ImplementInterface)] + internal partial class RemoveUnnecessaryCastCodeFixProvider : CodeFixProvider + { + public sealed override ImmutableArray<string> FixableDiagnosticIds + { + get { return ImmutableArray.Create(IDEDiagnosticIds.RemoveUnnecessaryCastDiagnosticId); } + } + +// public sealed override FixAllProvider GetFixAllProvider() +// { +// return RemoveUnnecessaryCastFixAllProvider.Instance; +// } +// + private static CastExpressionSyntax GetCastNode(SyntaxNode root, SemanticModel model, TextSpan span, CancellationToken cancellationToken) + { + var token = root.FindToken(span.Start); + if (!token.Span.IntersectsWith(span)) + { + return null; + } + + return token.GetAncestors<CastExpressionSyntax>() + .FirstOrDefault(c => c.Span.IntersectsWith(span) && c.IsUnnecessaryCast(model, cancellationToken)); + } + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var document = context.Document; + var span = context.Span; + var cancellationToken = context.CancellationToken; + + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var node = GetCastNode(root, model, span, cancellationToken); + if (node == null) + { + return; + } + + context.RegisterCodeFix( + new DocumentChangeAction(node.Span, DiagnosticSeverity.Warning, + GettextCatalog.GetString ("Remove Unnecessary Cast"), + (c) => RemoveUnnecessaryCastAsync(document, node, c)), + context.Diagnostics); + } + + private static async Task<Document> RemoveUnnecessaryCastAsync(Document document, CastExpressionSyntax cast, CancellationToken cancellationToken) + { + var annotatedCast = cast.WithAdditionalAnnotations(Simplifier.Annotation); + + if (annotatedCast.Expression is ParenthesizedExpressionSyntax) + { + annotatedCast = annotatedCast.WithExpression( + annotatedCast.Expression.WithAdditionalAnnotations(Simplifier.Annotation)); + } + else + { + annotatedCast = annotatedCast.WithExpression( + annotatedCast.Expression.Parenthesize()); + } + + ExpressionSyntax oldNode = cast; + ExpressionSyntax newNode = annotatedCast; + + // Ensure that we simplify any parenting parenthesized expressions not just on the syntax tree level but also on Token based + // Case 1: + // In the syntax, (((Task<Action>)x).Result)() + // oldNode = (Task<Action>)x + // newNode = (Task<Action>)(x) + // Final newNode will be (((Task<Action>)(x)).Result) + while (oldNode.Parent.IsKind(SyntaxKind.ParenthesizedExpression) || oldNode.GetFirstToken().GetPreviousToken().Parent.IsKind(SyntaxKind.ParenthesizedExpression)) + { + var parenthesizedExpression = (ParenthesizedExpressionSyntax)oldNode.GetFirstToken().GetPreviousToken().Parent; + newNode = parenthesizedExpression.ReplaceNode(oldNode, newNode) + .WithAdditionalAnnotations(Simplifier.Annotation); + oldNode = parenthesizedExpression; + } + + newNode = newNode.WithAdditionalAnnotations(Formatter.Annotation); + + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newRoot = root.ReplaceNode(oldNode, newNode); + + return document.WithSyntaxRoot(newRoot); + } +// +// private class MyCodeAction : CodeAction.DocumentChangeAction +// { +// public MyCodeAction(string title, Func<CancellationToken, Task<Document>> createChangedDocument) : +// base(title, createChangedDocument) +// { +// } +// } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryUsings/RemoveUnnecessaryUsingsCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryUsings/RemoveUnnecessaryUsingsCodeFixProvider.cs new file mode 100644 index 0000000000..d7cb42e0d2 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/RemoveUnnecessaryUsings/RemoveUnnecessaryUsingsCodeFixProvider.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis; +using ICSharpCode.NRefactory6.CSharp.Features.RemoveUnnecessaryImports; +using MonoDevelop.Core; +using ICSharpCode.NRefactory6.CSharp.Refactoring; +using Microsoft.CodeAnalysis.Text; +using MonoDevelop.CSharp.Diagnostics; + +namespace MonoDevelop.CSharp.CodeFixes.RemoveUnusedUsings +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.RemoveUnnecessaryImports), Shared] + [ExtensionOrder(After = PredefinedCodeFixProviderNames.AddMissingReference)] + internal class RemoveUnnecessaryUsingsCodeFixProvider : CodeFixProvider + { + public sealed override ImmutableArray<string> FixableDiagnosticIds + { + get { return ImmutableArray.Create(IDEDiagnosticIds.RemoveUnnecessaryImportsDiagnosticId); } + } + + public sealed override FixAllProvider GetFixAllProvider() + { + return WellKnownFixAllProviders.BatchFixer; + } + + static readonly CSharpRemoveUnnecessaryImportsService service = new CSharpRemoveUnnecessaryImportsService(); + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var document = context.Document; + var span = context.Span; + var cancellationToken = context.CancellationToken; + + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newDocument = service.RemoveUnnecessaryImports(document, model, root, cancellationToken); + if (newDocument == document || newDocument == null) + { + return; + } + + context.RegisterCodeFix( + new DocumentChangeAction(context.Diagnostics[0].Location.SourceSpan, DiagnosticSeverity.Warning, + GettextCatalog.GetString ("Remove Unnecessary Usings"), + (c) => Task.FromResult(newDocument)), + context.Diagnostics); + } + + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/SimplifyTypeNames/SimplifyTypeNamesCodeFixProvider.SimplifyTypeNamesFixAllProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/SimplifyTypeNames/SimplifyTypeNamesCodeFixProvider.SimplifyTypeNamesFixAllProvider.cs new file mode 100644 index 0000000000..543c9b3b38 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/SimplifyTypeNames/SimplifyTypeNamesCodeFixProvider.SimplifyTypeNamesFixAllProvider.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis; + +namespace MonoDevelop.CSharp.CodeFixes.SimplifyTypeNames +{ + internal partial class SimplifyTypeNamesCodeFixProvider : CodeFixProvider + { +// private class SimplifyTypeNamesFixAllProvider : BatchSimplificationFixAllProvider +// { +// internal static new readonly SimplifyTypeNamesFixAllProvider Instance = new SimplifyTypeNamesFixAllProvider(); +// +// protected override SyntaxNode GetNodeToSimplify(SyntaxNode root, SemanticModel model, Diagnostic diagnostic, Workspace workspace, out string codeActionId, CancellationToken cancellationToken) +// { +// codeActionId = null; +// string diagnosticId; +// var node = SimplifyTypeNamesCodeFixProvider.GetNodeToSimplify(root, model, diagnostic.Location.SourceSpan, workspace.Options, out diagnosticId, cancellationToken); +// if (node != null) +// { +// codeActionId = GetCodeActionId(diagnosticId, node.ToString()); +// } +// +// return node; +// } +// } + } +} diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/SimplifyTypeNames/SimplifyTypeNamesCodeFixProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/SimplifyTypeNames/SimplifyTypeNamesCodeFixProvider.cs new file mode 100644 index 0000000000..be3ccc4cd3 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/SimplifyTypeNames/SimplifyTypeNamesCodeFixProvider.cs @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis; +using MonoDevelop.CSharp.Diagnostics; +using ICSharpCode.NRefactory6.CSharp; +using MonoDevelop.Core; +using System; +using Microsoft.CodeAnalysis.CSharp; +using ICSharpCode.NRefactory6.CSharp.Refactoring; +using MonoDevelop.CSharp.Diagnostics.SimplifyTypeNames; + +namespace MonoDevelop.CSharp.CodeFixes.SimplifyTypeNames +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.SimplifyNames), Shared] + [ExtensionOrder(After = PredefinedCodeFixProviderNames.RemoveUnnecessaryCast)] + internal partial class SimplifyTypeNamesCodeFixProvider : CodeFixProvider + { + public sealed override ImmutableArray<string> FixableDiagnosticIds + { + get + { + return ImmutableArray.Create( + IDEDiagnosticIds.SimplifyNamesDiagnosticId, + IDEDiagnosticIds.SimplifyMemberAccessDiagnosticId, + IDEDiagnosticIds.SimplifyThisOrMeDiagnosticId); + } + } + +// public sealed override FixAllProvider GetFixAllProvider() +// { +// return SimplifyTypeNamesFixAllProvider.Instance; +// } + + internal static SyntaxNode GetNodeToSimplify(SyntaxNode root, SemanticModel model, TextSpan span, OptionSet optionSet, out string diagnosticId, CancellationToken cancellationToken) + { + diagnosticId = null; + var token = root.FindToken(span.Start, findInsideTrivia: true); + if (!token.Span.IntersectsWith(span)) + { + return null; + } + + foreach (var n in token.GetAncestors<SyntaxNode>()) + { + if (n.Span.IntersectsWith(span) && CanSimplifyTypeNameExpression(model, n, optionSet, span, out diagnosticId, cancellationToken)) + { + return n; + } + } + + return null; + } + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var document = context.Document; + var span = context.Span; + var cancellationToken = context.CancellationToken; + + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var optionSet = document.Project.Solution.Workspace.Options; + string diagnosticId; + var node = GetNodeToSimplify(root, model, span, optionSet, out diagnosticId, cancellationToken); + if (node == null) + { + return; + } + + var id = GetCodeActionId(diagnosticId, node.ToString()); + var title = id; + var codeAction = new DocumentChangeAction(node.Span, DiagnosticSeverity.Warning, title, + (c) => SimplifyTypeNameAsync(document, node, c)); + + context.RegisterCodeFix(codeAction, context.Diagnostics); + } + + // internal for testing purpose + internal static string GetCodeActionId(string diagnosticId, string nodeText) + { + switch (diagnosticId) + { + case IDEDiagnosticIds.SimplifyNamesDiagnosticId: + return string.Format(GettextCatalog.GetString ("Simplify name '{0}'"), nodeText); + + case IDEDiagnosticIds.SimplifyMemberAccessDiagnosticId: + return string.Format(GettextCatalog.GetString ("Simplify member access '{0}'"), nodeText); + + case IDEDiagnosticIds.SimplifyThisOrMeDiagnosticId: + return GettextCatalog.GetString ("Remove 'this' qualification"); + + default: + throw new InvalidOperationException(); + } + } + + private static bool CanSimplifyTypeNameExpression(SemanticModel model, SyntaxNode node, OptionSet optionSet, TextSpan span, out string diagnosticId, CancellationToken cancellationToken) + { + diagnosticId = null; + TextSpan issueSpan; + if (!CSharpSimplifyTypeNamesDiagnosticAnalyzer.IsCandidate(node) || + !CSharpSimplifyTypeNamesDiagnosticAnalyzer.CanSimplifyTypeNameExpression(model, node, optionSet, out issueSpan, out diagnosticId, cancellationToken)) + { + return false; + } + + return issueSpan.Equals(span); + } + + private async Task<Document> SimplifyTypeNameAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) + { + var expressionSyntax = node; + var annotatedexpressionSyntax = expressionSyntax.WithAdditionalAnnotations(Simplifier.Annotation, Formatter.Annotation); + + if (annotatedexpressionSyntax.Kind() == SyntaxKind.IsExpression || annotatedexpressionSyntax.Kind() == SyntaxKind.AsExpression) + { + var right = ((BinaryExpressionSyntax)annotatedexpressionSyntax).Right; + annotatedexpressionSyntax = annotatedexpressionSyntax.ReplaceNode(right, right.WithAdditionalAnnotations(Simplifier.Annotation)); + } + + SyntaxNode oldNode = expressionSyntax; + SyntaxNode newNode = annotatedexpressionSyntax; + + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newRoot = root.ReplaceNode(oldNode, newNode); + + return document.WithSyntaxRoot(newRoot); + } + } +} |