diff options
Diffstat (limited to 'main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/AddImport')
2 files changed, 1165 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; + } + } +} |