1
ļ»æ// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
3
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4
// software and associated documentation files (the "Software"), to deal in the Software
5
// without restriction, including without limitation the rights to use, copy, modify, merge,
6
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7
// to whom the Software is furnished to do so, subject to the following conditions:
9
// The above copyright notice and this permission notice shall be included in all copies or
10
// substantial portions of the Software.
12
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17
// DEALINGS IN THE SOFTWARE.
20
using System.Collections.Generic;
21
using System.Diagnostics;
23
using ICSharpCode.NRefactory.CSharp.Resolver;
24
using ICSharpCode.NRefactory.Semantics;
26
namespace ICSharpCode.NRefactory.CSharp.Refactoring
29
/// Helper methods for managing using declarations.
31
public class UsingHelper
34
/// Inserts 'using ns;' in the current scope, and then removes all explicit
35
/// usages of ns that were made redundant by the new using.
37
public static void InsertUsingAndRemoveRedundantNamespaceUsage(RefactoringContext context, Script script, string ns)
39
InsertUsing(context, script, new UsingDeclaration(ns));
40
// TODO: remove the usages that were made redundant
44
/// Inserts 'newUsing' in the current scope.
45
/// This method will try to insert new usings in the correct position (depending on
46
/// where the existing usings are; and maintaining the sort order).
48
public static void InsertUsing(RefactoringContext context, Script script, AstNode newUsing)
50
UsingInfo newUsingInfo = new UsingInfo(newUsing, context);
51
AstNode enclosingNamespace = context.GetNode<NamespaceDeclaration>() ?? context.RootNode;
52
// Find nearest enclosing parent that has usings:
53
AstNode usingParent = enclosingNamespace;
54
while (usingParent != null && !usingParent.Children.OfType<UsingDeclaration>().Any())
55
usingParent = usingParent.Parent;
56
if (usingParent == null) {
57
// No existing usings at all -> use the default location
58
if (script.FormattingOptions.UsingPlacement == UsingPlacement.TopOfFile) {
59
usingParent = context.RootNode;
61
usingParent = enclosingNamespace;
64
// Find the main block of using declarations in the chosen scope:
65
AstNode blockStart = usingParent.Children.FirstOrDefault(IsUsingDeclaration);
66
AstNode insertionPoint;
67
bool insertAfter = false;
68
if (blockStart == null) {
69
// no using declarations in the file
70
Debug.Assert(SyntaxTree.MemberRole == NamespaceDeclaration.MemberRole);
71
insertionPoint = usingParent.GetChildrenByRole(SyntaxTree.MemberRole).SkipWhile(CanAppearBeforeUsings).FirstOrDefault();
73
insertionPoint = blockStart;
74
while (IsUsingFollowing (ref insertionPoint) && newUsingInfo.CompareTo(new UsingInfo(insertionPoint, context)) > 0)
75
insertionPoint = insertionPoint.NextSibling;
76
if (!IsUsingDeclaration(insertionPoint)) {
77
// Insert after last using instead of before next node
78
// This affects where empty lines get placed.
79
insertionPoint = insertionPoint.PrevSibling;
83
if (insertionPoint != null) {
85
script.InsertAfter(insertionPoint, newUsing);
87
script.InsertBefore(insertionPoint, newUsing);
91
static bool IsUsingFollowing(ref AstNode insertionPoint)
93
var node = insertionPoint;
94
while (node != null && node.Role == Roles.NewLine)
95
node = node.NextSibling;
96
if (IsUsingDeclaration(node)) {
97
insertionPoint = node;
103
static bool IsUsingDeclaration(AstNode node)
105
return node is UsingDeclaration || node is UsingAliasDeclaration;
108
static bool CanAppearBeforeUsings(AstNode node)
110
if (node is ExternAliasDeclaration)
112
if (node is PreProcessorDirective)
114
if (node is NewLineNode)
116
Comment c = node as Comment;
118
return !c.IsDocumentation;
123
/// Sorts the specified usings.
125
public static IEnumerable<AstNode> SortUsingBlock(IEnumerable<AstNode> nodes, BaseRefactoringContext context)
127
var infos = nodes.Select(_ => new UsingInfo(_, context));
128
var orderedInfos = infos.OrderBy(_ => _);
129
var orderedNodes = orderedInfos.Select(_ => _.Node);
135
private sealed class UsingInfo : IComparable<UsingInfo>
143
public bool HasTypesFromOtherAssemblies;
144
public bool IsSystem;
146
public UsingInfo(AstNode node, BaseRefactoringContext context)
148
var importAndAlias = GetImportAndAlias(node);
152
Alias = importAndAlias.Item2;
153
Name = importAndAlias.Item1.ToString();
155
IsAlias = Alias != null;
158
if (node.Ancestors.Contains(context.RootNode)) {
159
rr = context.Resolve(importAndAlias.Item1);
161
// It's possible that we're looking at a new using that
162
// isn't part of the AST.
163
var resolver = new CSharpAstResolver(new CSharpResolver(context.Compilation), node);
164
rr = resolver.Resolve(importAndAlias.Item1);
167
var nrr = rr as NamespaceResolveResult;
168
HasTypesFromOtherAssemblies = nrr != null && nrr.Namespace.ContributingAssemblies.Any(a => !a.IsMainAssembly);
170
IsSystem = HasTypesFromOtherAssemblies && (Name == "System" || Name.StartsWith("System.", StringComparison.Ordinal));
173
private static Tuple<AstType, string> GetImportAndAlias(AstNode node)
175
var plainUsing = node as UsingDeclaration;
176
if (plainUsing != null)
177
return Tuple.Create(plainUsing.Import, (string)null);
179
var aliasUsing = node as UsingAliasDeclaration;
180
if (aliasUsing != null)
181
return Tuple.Create(aliasUsing.Import, aliasUsing.Alias);
183
throw new InvalidOperationException(string.Format("Invalid using node: {0}", node));
186
public int CompareTo(UsingInfo y)
189
if (x.IsAlias != y.IsAlias)
190
return x.IsAlias ? 1 : -1;
191
else if (x.HasTypesFromOtherAssemblies != y.HasTypesFromOtherAssemblies)
192
return x.HasTypesFromOtherAssemblies ? -1 : 1;
193
else if (x.IsSystem != y.IsSystem)
194
return x.IsSystem ? -1 : 1;
195
else if (x.Alias != y.Alias)
196
return StringComparer.Ordinal.Compare(x.Alias, y.Alias);
197
else if (x.Name != y.Name)
198
return StringComparer.Ordinal.Compare(x.Name, y.Name);