Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/monodevelop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/FullyQualify/CSharpFullyQualifyCodeFixProvider.cs')
-rw-r--r--main/src/addins/CSharpBinding/MonoDevelop.CSharp.CodeFixes/FullyQualify/CSharpFullyQualifyCodeFixProvider.cs331
1 files changed, 331 insertions, 0 deletions
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