Browse Source

added introduce method refactoring (not working in all cases yet)

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5406 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
pull/1/head
Siegfried Pammer 16 years ago
parent
commit
1e9c0b6781
  1. 2
      SharpDevelop.sln
  2. 5
      src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
  3. 40
      src/Main/Base/Project/Src/Gui/Dialogs/IntroduceMethodDialog.xaml
  4. 92
      src/Main/Base/Project/Src/Gui/Dialogs/IntroduceMethodDialog.xaml.cs
  5. 182
      src/Main/Base/Project/Src/Services/RefactoringService/RefactoringMenuBuilder.cs
  6. 12
      src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/NRefactoryResolver/NRefactoryResolver.cs
  7. 31
      src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/NRefactoryResolver/ResolveVisitor.cs
  8. 6
      src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Refactoring/CodeGenerator.cs
  9. 49
      src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/ResolveResult.cs

2
SharpDevelop.sln

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
# SharpDevelop 4.0.0.5293
# SharpDevelop 4.0.0.5401
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Main", "Main", "{256F5C28-532C-44C0-8AB8-D8EC5E492E01}"
ProjectSection(SolutionItems) = postProject
EndProjectSection

5
src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj

@ -175,6 +175,10 @@ @@ -175,6 +175,10 @@
<Compile Include="Src\Gui\Dialogs\GotoDialog.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Src\Gui\Dialogs\IntroduceMethodDialog.xaml.cs">
<DependentUpon>IntroduceMethodDialog.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Src\Gui\Dialogs\NewProjectDialog.cs" />
<Compile Include="Src\Gui\Dialogs\NewProjectDialog.Designer.cs">
<DependentUpon>NewProjectDialog.cs</DependentUpon>
@ -807,6 +811,7 @@ @@ -807,6 +811,7 @@
<Page Include="Src\Gui\Dialogs\GotoDialog.xaml">
<DependentUpon>GotoDialog.cs</DependentUpon>
</Page>
<Page Include="Src\Gui\Dialogs\IntroduceMethodDialog.xaml" />
<Page Include="Src\Gui\Dialogs\TabbedOptionsDialog.xaml" />
<Page Include="Src\Gui\Dialogs\TreeViewOptionsDialog.xaml" />
<Page Include="Src\Gui\Workbench\WpfWorkbench.xaml" />

40
src/Main/Base/Project/Src/Gui/Dialogs/IntroduceMethodDialog.xaml

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
<Window x:Class="ICSharpCode.SharpDevelop.Gui.IntroduceMethodDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gui="clr-namespace:ICSharpCode.SharpDevelop.Gui"
xmlns:addin="clr-namespace:ICSharpCode.SharpDevelop.Refactoring"
xmlns:sd="http://icsharpcode.net/sharpdevelop/core"
WindowStartupLocation="CenterScreen" WindowStyle="ToolWindow" ResizeMode="NoResize"
Title="Introduce method" Height="400" Width="600">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" HorizontalAlignment="Center">
<Button Content="{sd:Localize Global.OKButtonText}" Margin="3" Click="OKButtonClick" IsDefault="True" />
<Button Content="{sd:Localize Global.CancelButtonText}" Margin="3" Click="CancelButtonClick" IsCancel="True" />
</StackPanel>
<TextBlock Text="{sd:Localize Dialog.Refactoring.IntroduceMethod.Description}" DockPanel.Dock="Top" Margin="3" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<RadioButton Margin="3" Content="Use existing class:" Name="rbExisting" IsChecked="True" GroupName="group" Grid.Column="0" Grid.Row="0" />
<ListBox Margin="3" Grid.Column="0" Grid.Row="1" Name="classList" IsEnabled="{Binding IsChecked, ElementName=rbExisting}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FullyQualifiedName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<RadioButton Margin="3" Content="Create new class:" Name="rbNew" GroupName="group" Grid.Column="0" Grid.Row="2" />
<DockPanel Grid.Column="0" Grid.Row="3" IsEnabled="{Binding IsChecked, ElementName=rbNew}">
<Label DockPanel.Dock="Left" Content="Name:" Margin="3" />
<TextBox Name="newClassName" Text="Extensions" Margin="3" />
</DockPanel>
</Grid>
</DockPanel>
</Window>

92
src/Main/Base/Project/Src/Gui/Dialogs/IntroduceMethodDialog.xaml.cs

@ -0,0 +1,92 @@ @@ -0,0 +1,92 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Siegfried Pammer" email="siegfriedpammer@gmail.com" />
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Dom.Refactoring;
using ICSharpCode.SharpDevelop.Editor;
using ICSharpCode.SharpDevelop.Project;
namespace ICSharpCode.SharpDevelop.Gui
{
/// <summary>
/// Interaction logic for IntroduceMethodDialog.xaml
/// </summary>
public partial class IntroduceMethodDialog : Window
{
public IClass CallingClass { get; private set; }
ITextEditor editor;
MethodDeclaration method;
public IntroduceMethodDialog(IClass callingClass, MethodDeclaration method, ITextEditor editor)
{
InitializeComponent();
CallingClass = callingClass;
this.editor = editor;
this.method = method;
var classes = GetAllStaticClasses(callingClass.ProjectContent.Project as IProject);
if (!classes.Any())
rbNew.IsChecked = true;
else {
classList.ItemsSource = classes;
classList.SelectedItem = classes.FirstOrDefault(c => c.Name.Contains("Extensions")) ?? classes.First();
}
}
IEnumerable<IClass> GetAllStaticClasses(IProject project)
{
IProjectContent projectContent = ParserService.GetProjectContent(project);
if (projectContent != null) {
foreach (IClass c in projectContent.Classes) {
if (c.IsStatic)
yield return c;
}
}
}
void OKButtonClick(object sender, RoutedEventArgs e)
{
CodeGenerator gen = CallingClass.ProjectContent.Language.CodeGenerator;
if (rbExisting.IsChecked == true) {
IClass c = (IClass)classList.SelectedItem;
gen.InsertCodeAtEnd(c.BodyRegion, new RefactoringDocumentAdapter(editor.Document), method);
}
if (rbNew.IsChecked == true) {
TypeDeclaration type = new TypeDeclaration(Modifiers.Static, null);
type.Name = newClassName.Text;
type.AddChild(method);
gen.InsertCodeAfter(CallingClass, new RefactoringDocumentAdapter(editor.Document), type);
}
Close();
}
void CancelButtonClick(object sender, RoutedEventArgs e)
{
// do nothing
Close();
}
}
}

182
src/Main/Base/Project/Src/Services/RefactoringService/RefactoringMenuBuilder.cs

@ -16,12 +16,16 @@ using System.Windows.Input; @@ -16,12 +16,16 @@ using System.Windows.Input;
using ICSharpCode.Core;
using ICSharpCode.Core.Presentation;
using ICSharpCode.NRefactory.Visitors;
using ICSharpCode.SharpDevelop.Bookmarks;
using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Dom.NRefactoryResolver;
using ICSharpCode.SharpDevelop.Dom.Refactoring;
using ICSharpCode.SharpDevelop.Editor;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Gui.ClassBrowser;
using ICSharpCode.SharpDevelop.Project;
using Ast = ICSharpCode.NRefactory.Ast;
namespace ICSharpCode.SharpDevelop.Refactoring
{
@ -129,6 +133,9 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -129,6 +133,9 @@ namespace ICSharpCode.SharpDevelop.Refactoring
} else if (rr is UnknownConstructorCallResolveResult) {
item = MakeItemForResolveError((UnknownConstructorCallResolveResult)rr, expressionResult.Context, textEditor);
insertIndex = 0; // Insert menu item at the topmost position.
} else if (rr is UnknownMethodResolveResult) {
item = MakeItemForResolveError((UnknownMethodResolveResult)rr, expressionResult.Context, textEditor);
insertIndex = 0; // Insert menu item at the topmost position.
}
if (item != null) {
resultItems.Insert(insertIndex, item);
@ -173,6 +180,181 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -173,6 +180,181 @@ namespace ICSharpCode.SharpDevelop.Refactoring
return MakeItemForUnknownClass(rr.CallingClass, rr.TypeName, textArea);
}
MenuItem MakeItemForResolveError(UnknownMethodResolveResult rr, ExpressionContext context, ITextEditor editor)
{
// TODO : make easy testable (hide dialog in test mode)
// TODO : add unit tests
if (rr.Target == null)
return null;
MenuItem item = new MenuItem() {
Header = "Introduce method " + rr.CallName + " in " + rr.Target.FullyQualifiedName,
Icon = ClassBrowserIconService.GotoArrow.CreateImage()
};
IClass targetClass = rr.Target.GetUnderlyingClass();
CodeGenerator gen = targetClass.ProjectContent.Language.CodeGenerator;
IAmbience ambience = targetClass.ProjectContent.Language.GetAmbience();
ClassFinder finder = new ClassFinder(rr.CallingMember);
ModifierEnum modifiers = ModifierEnum.None;
if (rr.CallingClass == targetClass) {
if (rr.CallingMember != null)
modifiers |= (rr.CallingMember.Modifiers & ModifierEnum.Static);
} else {
modifiers |= ModifierEnum.Public;
if (rr.IsStaticContext)
modifiers |= ModifierEnum.Static;
}
NRefactoryResolver resolver = new NRefactoryResolver(rr.CallingClass.ProjectContent.Language);
resolver.Initialize(ParserService.GetParseInformation(editor.FileName), editor.Caret.Line, editor.Caret.Column);
Ast.INode node = resolver.ParseCurrentMember(editor.Document.Text);
resolver.RunLookupTableVisitor(node);
InvocationExpressionLookupVisitor visitor = new InvocationExpressionLookupVisitor(editor);
node.AcceptVisitor(visitor, null);
if (visitor.Expression == null)
return null;
IReturnType type = resolver.GetExpectedTypeFromContext(visitor.Expression);
Ast.TypeReference typeRef = CodeGenerator.ConvertType(type, finder);
if (typeRef.IsNull) {
if (visitor.Expression.Parent is Ast.ExpressionStatement)
typeRef = new Ast.TypeReference("void", true);
else
typeRef = new Ast.TypeReference("object", true);
}
item.Click += delegate {
Ast.MethodDeclaration method = new Ast.MethodDeclaration {
Name = rr.CallName,
Modifier = CodeGenerator.ConvertModifier(modifiers, finder),
TypeReference = typeRef,
Parameters = CreateParameters(rr, finder, visitor.Expression).ToList(),
Body = CodeGenerator.CreateNotImplementedBlock()
};
if (targetClass.BodyRegion.IsEmpty) {
method.Parameters.Insert(0, new Ast.ParameterDeclarationExpression(CodeGenerator.ConvertType(rr.Target, finder), "thisInstance"));
method.IsExtensionMethod = true;
method.Modifier |= Ast.Modifiers.Static;
// TODO : combine code from IntroduceMethodDialog to remove code duplication
IntroduceMethodDialog dialog = new IntroduceMethodDialog(rr.CallingClass, method, editor);
dialog.Owner = WorkbenchSingleton.MainWindow;
dialog.ShowDialog();
return;
}
gen.InsertCodeAtEnd(targetClass.BodyRegion, new RefactoringDocumentAdapter(editor.Document), method);
ParserService.ParseCurrentViewContent();
// does not work yet, wrong method selected -> new method not yet present
// TODO : need to retrieve updated IClass instance
//IMethod newMember = targetClass.Methods.Last();
//IDocumentLine line = editor.Document.GetLine(newMember.BodyRegion.BeginLine + 1);
//int indentLength = DocumentUtilitites.GetWhitespaceAfter(editor.Document, line.Offset).Length;
//editor.Select(line.Offset + indentLength, "new NotImplementedException();".Length);
};
return item;
}
IEnumerable<Ast.ParameterDeclarationExpression> CreateParameters(UnknownMethodResolveResult rr, ClassFinder context, Ast.InvocationExpression invocation)
{
List<string> usedNames = new List<string>();
for (int i = 0; i < rr.Arguments.Count; i++) {
IReturnType type = rr.Arguments[i];
var typeRef = CodeGenerator.ConvertType(type, context);
typeRef = typeRef.IsNull ? new Ast.TypeReference("object", true) : typeRef;
Ast.Expression ex = invocation.Arguments[i];
string paramName = IsNumericType(type) ? "num" + i : type.Name + i.ToString();
if (ex is Ast.IdentifierExpression) {
paramName = (ex as Ast.IdentifierExpression).Identifier;
}
if (ex is Ast.MemberReferenceExpression) {
paramName = (ex as Ast.MemberReferenceExpression).MemberName;
}
Ast.ParameterModifiers mod = Ast.ParameterModifiers.None;
if (ex is Ast.DirectionExpression) {
var dex = ex as Ast.DirectionExpression;
if (dex.Expression is Ast.IdentifierExpression) {
paramName = (dex.Expression as Ast.IdentifierExpression).Identifier;
}
if (dex.Expression is Ast.MemberReferenceExpression) {
paramName = (dex.Expression as Ast.MemberReferenceExpression).MemberName;
}
mod = dex.FieldDirection == Ast.FieldDirection.Out ? Ast.ParameterModifiers.Out : (dex.FieldDirection == Ast.FieldDirection.Ref ? Ast.ParameterModifiers.Ref : Ast.ParameterModifiers.None);
}
paramName = rr.CallingClass.ProjectContent.Language.CodeGenerator.GetParameterName(paramName);
if (usedNames.Contains(paramName))
paramName += i.ToString();
usedNames.Add(paramName);
yield return new Ast.ParameterDeclarationExpression(typeRef, paramName) {
ParamModifier = mod
};
}
}
bool IsNumericType(IReturnType type)
{
return type.FullyQualifiedName == "System.Int32" ||
type.FullyQualifiedName == "System.Int16" ||
type.FullyQualifiedName == "System.Int64" ||
type.FullyQualifiedName == "System.Single" ||
type.FullyQualifiedName == "System.Double" ||
type.FullyQualifiedName == "System.UInt16" ||
type.FullyQualifiedName == "System.UInt32" ||
type.FullyQualifiedName == "System.UInt64";
}
class InvocationExpressionLookupVisitor : AbstractAstVisitor
{
ITextEditor editor;
Ast.InvocationExpression expression;
public Ast.InvocationExpression Expression {
get { return expression; }
}
public InvocationExpressionLookupVisitor(ITextEditor editor)
{
this.editor = editor;
this.expression = null;
}
public override object VisitInvocationExpression(Ast.InvocationExpression invocationExpression, object data)
{
int startOffset = editor.Document.PositionToOffset(invocationExpression.TargetObject.StartLocation.Line, invocationExpression.TargetObject.StartLocation.Column);
int endOffset = editor.Document.PositionToOffset(invocationExpression.EndLocation.Line, invocationExpression.EndLocation.Column);
int offset = editor.Caret.Offset;
if (offset >= startOffset && offset <= endOffset)
expression = invocationExpression;
return base.VisitInvocationExpression(invocationExpression, data);
}
}
MenuItem MakeItemForUnknownClass(IClass callingClass, string unknownClassName, ITextEditor textArea)
{
if (callingClass == null)

12
src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/NRefactoryResolver/NRefactoryResolver.cs

@ -1347,6 +1347,18 @@ namespace ICSharpCode.SharpDevelop.Dom.NRefactoryResolver @@ -1347,6 +1347,18 @@ namespace ICSharpCode.SharpDevelop.Dom.NRefactoryResolver
collectionType = null;
if (collectionType != null)
return new ElementReturnType(projectContent, collectionType);
} else if (expr.Parent is IndexerExpression) {
IndexerExpression indexerExpr = expr.Parent as IndexerExpression;
MemberResolveResult indexer = ResolveInternal(indexerExpr, ExpressionContext.Default)
as MemberResolveResult;
if (indexer != null && indexer.ResolvedMember is IProperty) {
IProperty p = indexer.ResolvedMember as IProperty;
int position = indexerExpr.Indexes.IndexOf(expr);
if (position < 0 || position >= p.Parameters.Count)
return null;
return p.Parameters[position].ReturnType;
}
}
return null;
}

31
src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/NRefactoryResolver/ResolveVisitor.cs

@ -324,7 +324,33 @@ namespace ICSharpCode.SharpDevelop.Dom.NRefactoryResolver @@ -324,7 +324,33 @@ namespace ICSharpCode.SharpDevelop.Dom.NRefactoryResolver
if (resolver.Language == SupportedLanguage.VBNet) {
return CreateMemberResolveResult(GetVisualBasicIndexer(invocationExpression));
}
return null;
return CreateUnknownMethodResolveResult(invocationExpression);
}
UnknownMethodResolveResult CreateUnknownMethodResolveResult(InvocationExpression invocationExpression)
{
var arguments = invocationExpression.Arguments.Select(a => Resolve(a).ResolvedType).ToList();
IReturnType target = resolver.CallingClass.DefaultReturnType;
string methodName = "";
bool isStatic = false;
if (invocationExpression.TargetObject is IdentifierExpression) {
IdentifierExpression ie = invocationExpression.TargetObject as IdentifierExpression;
methodName = ie.Identifier;
isStatic = resolver.CallingMember.Modifiers.HasFlag(ModifierEnum.Static);
}
if (invocationExpression.TargetObject is MemberReferenceExpression) {
MemberReferenceExpression mre = invocationExpression.TargetObject as MemberReferenceExpression;
var rr = Resolve(mre.TargetObject);
isStatic = rr is TypeResolveResult;
target = rr.ResolvedType;
methodName = mre.MemberName;
}
return new UnknownMethodResolveResult(resolver.CallingClass, resolver.CallingMember, target, methodName, isStatic, arguments);
}
ResolveResult FallbackResolveMethod(InvocationExpression invocation, MethodGroupResolveResult mgrr, IReturnType[] argumentTypes)
@ -342,8 +368,7 @@ namespace ICSharpCode.SharpDevelop.Dom.NRefactoryResolver @@ -342,8 +368,7 @@ namespace ICSharpCode.SharpDevelop.Dom.NRefactoryResolver
}
}
// TODO: method still not found, now return invalid ResolveResult describing the expected method
return null;
return CreateUnknownMethodResolveResult(invocation);
}
public override object VisitLambdaExpression(LambdaExpression lambdaExpression, object data)

6
src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Refactoring/CodeGenerator.cs

@ -298,6 +298,12 @@ namespace ICSharpCode.SharpDevelop.Dom.Refactoring @@ -298,6 +298,12 @@ namespace ICSharpCode.SharpDevelop.Dom.Refactoring
}
#region Code generation / insertion
public virtual void InsertCodeAfter(IClass @class, IRefactoringDocument document, params AbstractNode[] nodes)
{
InsertCodeAfter(@class.BodyRegion.EndLine, document,
GetIndentation(document, @class.BodyRegion.BeginLine), nodes);
}
public virtual void InsertCodeAfter(IMember member, IRefactoringDocument document, params AbstractNode[] nodes)
{
if (member is IMethodOrProperty) {

49
src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/ResolveResult.cs

@ -9,6 +9,8 @@ using System; @@ -9,6 +9,8 @@ using System;
using System.Collections;
using System.Collections.Generic;
using ICSharpCode.NRefactory.Ast;
namespace ICSharpCode.SharpDevelop.Dom
{
#region ResolveResult
@ -941,6 +943,53 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -941,6 +943,53 @@ namespace ICSharpCode.SharpDevelop.Dom
}
#endregion
#region UnknownMethodResolveResult
/// <summary>
/// Used for calls to unknown methods.
/// </summary>
public class UnknownMethodResolveResult : ResolveResult
{
string callName;
bool isStaticContext;
List<IReturnType> arguments;
IReturnType target;
public UnknownMethodResolveResult(IClass callingClass, IMember callingMember, IReturnType target, string callName, bool isStaticContext, List<IReturnType> arguments)
: base(callingClass, callingMember, null)
{
this.target = target == null ? callingClass.DefaultReturnType : target;
this.callName = callName;
this.arguments = arguments;
this.isStaticContext = isStaticContext;
}
public bool IsStaticContext {
get { return isStaticContext; }
}
public string CallName {
get { return callName; }
}
public IReturnType Target {
get { return target; }
}
public List<IReturnType> Arguments {
get { return arguments; }
}
public override bool IsValid {
get { return false; }
}
public override ResolveResult Clone()
{
return new UnknownMethodResolveResult(this.CallingClass, this.CallingMember, this.target, this.callName, this.isStaticContext, this.arguments);
}
}
#endregion
#region UnknownConstructorCallResolveResult
/// <summary>
/// Used for constructor calls on unknown types.

Loading…
Cancel
Save