23 changed files with 1085 additions and 9 deletions
@ -0,0 +1,164 @@
@@ -0,0 +1,164 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using System.Collections; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Collections.Specialized; |
||||
using System.Linq; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Dom |
||||
{ |
||||
/// <summary>
|
||||
/// A <see cref="IModelCollection{T}"/> that works by concatening multiple
|
||||
/// other model collections.
|
||||
/// </summary>
|
||||
public sealed class ConcatModelCollection<T> : IModelCollection<T> |
||||
{ |
||||
sealed class InputCollection : Collection<IModelCollection<T>> |
||||
{ |
||||
readonly ConcatModelCollection<T> owner; |
||||
|
||||
public InputCollection(ConcatModelCollection<T> owner) |
||||
{ |
||||
this.owner = owner; |
||||
} |
||||
|
||||
protected override void ClearItems() |
||||
{ |
||||
if (owner.collectionChanged != null) { |
||||
foreach (var input in Items) { |
||||
input.CollectionChanged -= owner.OnInputCollectionChanged; |
||||
} |
||||
} |
||||
base.ClearItems(); |
||||
owner.RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); |
||||
} |
||||
|
||||
protected override void InsertItem(int index, IModelCollection<T> item) |
||||
{ |
||||
if (owner.collectionChanged != null) |
||||
item.CollectionChanged += owner.OnInputCollectionChanged; |
||||
base.InsertItem(index, item); |
||||
owner.RaiseCollectionChanged(new NotifyCollectionChangedEventArgs( |
||||
NotifyCollectionChangedAction.Add, item.ToArray(), owner.GetCount(index))); |
||||
} |
||||
|
||||
protected override void RemoveItem(int index) |
||||
{ |
||||
if (owner.collectionChanged != null) |
||||
Items[index].CollectionChanged -= owner.OnInputCollectionChanged; |
||||
var oldItems = Items[index].ToArray(); |
||||
base.RemoveItem(index); |
||||
owner.RaiseCollectionChanged(new NotifyCollectionChangedEventArgs( |
||||
NotifyCollectionChangedAction.Remove, oldItems, owner.GetCount(index))); |
||||
} |
||||
|
||||
protected override void SetItem(int index, IModelCollection<T> item) |
||||
{ |
||||
RemoveItem(index); |
||||
InsertItem(index, item); |
||||
} |
||||
} |
||||
|
||||
InputCollection inputs; |
||||
|
||||
public ConcatModelCollection() |
||||
{ |
||||
this.inputs = new InputCollection(this); |
||||
} |
||||
|
||||
public IList<IModelCollection<T>> Inputs { |
||||
get { return inputs; } |
||||
} |
||||
|
||||
NotifyCollectionChangedEventHandler collectionChanged; |
||||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged { |
||||
add { |
||||
var oldEventHandlers = collectionChanged; |
||||
collectionChanged += value; |
||||
if (oldEventHandlers == null && collectionChanged != null) { |
||||
foreach (var input in inputs) |
||||
input.CollectionChanged += OnInputCollectionChanged; |
||||
} |
||||
} |
||||
remove { |
||||
var oldEventHandlers = collectionChanged; |
||||
collectionChanged -= value; |
||||
if (oldEventHandlers != null && collectionChanged == null) { |
||||
foreach (var input in inputs) |
||||
input.CollectionChanged -= OnInputCollectionChanged; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e) |
||||
{ |
||||
if (collectionChanged != null) |
||||
collectionChanged(this, e); |
||||
} |
||||
|
||||
void OnInputCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) |
||||
{ |
||||
int inputIndex = inputs.IndexOf((IModelCollection<T>)sender); |
||||
int startIndex = GetCount(inputIndex); |
||||
NotifyCollectionChangedEventArgs newEventArgs; |
||||
switch (e.Action) { |
||||
case NotifyCollectionChangedAction.Add: |
||||
newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.NewItems, startIndex + e.NewStartingIndex); |
||||
break; |
||||
case NotifyCollectionChangedAction.Remove: |
||||
newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.OldItems, startIndex + e.OldStartingIndex); |
||||
break; |
||||
case NotifyCollectionChangedAction.Replace: |
||||
newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.OldItems, e.NewItems, startIndex + e.OldStartingIndex); |
||||
break; |
||||
case NotifyCollectionChangedAction.Move: |
||||
newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.OldItems, startIndex + e.OldStartingIndex, startIndex + e.NewStartingIndex); |
||||
break; |
||||
case NotifyCollectionChangedAction.Reset: |
||||
newEventArgs = new NotifyCollectionChangedEventArgs(e.Action); |
||||
break; |
||||
default: |
||||
throw new NotSupportedException("Invalid value for NotifyCollectionChangedAction"); |
||||
} |
||||
collectionChanged(this, newEventArgs); |
||||
} |
||||
|
||||
public int Count { |
||||
get { return GetCount(inputs.Count); } |
||||
} |
||||
|
||||
int GetCount(int inputIndex) |
||||
{ |
||||
int count = 0; |
||||
for (int i = 0; i < inputIndex; i++) { |
||||
count += inputs[i].Count; |
||||
} |
||||
return count; |
||||
} |
||||
|
||||
public IEnumerator<T> GetEnumerator() |
||||
{ |
||||
return inputs.SelectMany(i => i).GetEnumerator(); |
||||
} |
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() |
||||
{ |
||||
return GetEnumerator(); |
||||
} |
||||
|
||||
public T this[int index] { |
||||
get { |
||||
int inputIndex = 0; |
||||
while (index >= inputs[inputIndex].Count) { |
||||
index -= inputs[inputIndex].Count; |
||||
inputIndex++; |
||||
} |
||||
return inputs[inputIndex][index]; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using System.ComponentModel; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
using ICSharpCode.SharpDevelop.Parser; |
||||
using ICSharpCode.SharpDevelop.Project; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Dom |
||||
{ |
||||
/// <summary>
|
||||
/// An NRefactory entity as a model.
|
||||
/// </summary>
|
||||
public interface IEntityModel : INotifyPropertyChanged |
||||
{ |
||||
/// <summary>
|
||||
/// Gets the parent project that contains this entity.
|
||||
/// May return null if the entity is not part of a project.
|
||||
/// </summary>
|
||||
IProject ParentProject { get; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the region where this entity is defined.
|
||||
/// </summary>
|
||||
DomRegion Region { get; } |
||||
|
||||
/// <summary>
|
||||
/// Resolves the entity in the current solution snapshot.
|
||||
/// Returns null if the entity could not be resolved.
|
||||
/// </summary>
|
||||
IEntity Resolve(); |
||||
|
||||
/// <summary>
|
||||
/// Resolves the entity in the specified solution snapshot.
|
||||
/// Returns null if the entity could not be resolved.
|
||||
/// </summary>
|
||||
IEntity Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot); |
||||
} |
||||
} |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
using ICSharpCode.SharpDevelop.Parser; |
||||
using ICSharpCode.SharpDevelop.Project; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Dom |
||||
{ |
||||
/// <summary>
|
||||
/// The context for an entity model.
|
||||
/// This may be a reference to a project, or a compilation provider for a single stand-alone code file.
|
||||
/// </summary>
|
||||
public interface IEntityModelContext |
||||
{ |
||||
/// <summary>
|
||||
/// Used for <see cref="IEntityModel.ParentProject"/>.
|
||||
/// </summary>
|
||||
IProject Project { get; } |
||||
|
||||
/// <summary>
|
||||
/// Used for <see cref="IEntityModel.Resolve()"/>.
|
||||
/// </summary>
|
||||
/// <param name="solutionSnapshot">
|
||||
/// The solution snapshot provided to <see cref="IEntityModel.Resolve(ISolutionSnapshotWithProjectMapping)"/>,
|
||||
/// or null if the <see cref="IEntityModel.Resolve()"/> overload was used.
|
||||
/// </param>
|
||||
ICompilation GetCompilation(ISolutionSnapshotWithProjectMapping solutionSnapshot); |
||||
|
||||
/// <summary>
|
||||
/// Returns true if part1 is considered a better candidate for the primary part than part2.
|
||||
/// </summary>
|
||||
bool IsBetterPart(IUnresolvedTypeDefinition part1, IUnresolvedTypeDefinition part2); |
||||
} |
||||
|
||||
public class ProjectEntityModelContext : IEntityModelContext |
||||
{ |
||||
readonly IProject project; |
||||
readonly string primaryCodeFileExtension; |
||||
|
||||
public ProjectEntityModelContext(IProject project, string primaryCodeFileExtension) |
||||
{ |
||||
if (project == null) |
||||
throw new ArgumentNullException("project"); |
||||
this.project = project; |
||||
this.primaryCodeFileExtension = primaryCodeFileExtension; |
||||
} |
||||
|
||||
public IProject Project { |
||||
get { return project; } |
||||
} |
||||
|
||||
public ICompilation GetCompilation(ISolutionSnapshotWithProjectMapping solutionSnapshot) |
||||
{ |
||||
if (solutionSnapshot != null) |
||||
return solutionSnapshot.GetCompilation(project); |
||||
else |
||||
return SD.ParserService.GetCompilation(project); |
||||
} |
||||
|
||||
public bool IsBetterPart(IUnresolvedTypeDefinition part1, IUnresolvedTypeDefinition part2) |
||||
{ |
||||
IUnresolvedFile file1 = part1.UnresolvedFile; |
||||
IUnresolvedFile file2 = part2.UnresolvedFile; |
||||
if (file1 != null && file2 == null) |
||||
return true; |
||||
if (file1 == null) |
||||
return false; |
||||
bool file1HasExtension = file1.FileName.EndsWith(primaryCodeFileExtension, StringComparison.OrdinalIgnoreCase); |
||||
bool file2HasExtension = file2.FileName.EndsWith(primaryCodeFileExtension, StringComparison.OrdinalIgnoreCase); |
||||
if (file1HasExtension && !file2HasExtension) |
||||
return true; |
||||
if (!file1HasExtension && file2HasExtension) |
||||
return false; |
||||
return file1.FileName.Length < file2.FileName.Length; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
using ICSharpCode.SharpDevelop.Parser; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Dom |
||||
{ |
||||
/// <summary>
|
||||
/// Observable model for a member.
|
||||
/// </summary>
|
||||
public interface IMemberModel : IEntityModel |
||||
{ |
||||
/// <summary>
|
||||
/// Resolves the member in the current solution snapshot.
|
||||
/// Returns null if the member could not be resolved.
|
||||
/// </summary>
|
||||
IMember Resolve(); |
||||
|
||||
/// <summary>
|
||||
/// Resolves the member in the specified solution snapshot.
|
||||
/// Returns null if the member could not be resolved.
|
||||
/// </summary>
|
||||
IMember Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot); |
||||
} |
||||
} |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Dom |
||||
{ |
||||
/// <summary>
|
||||
/// Service that enables lookup of model objects from NRefactory objects.
|
||||
/// </summary>
|
||||
public interface IModelService |
||||
{ |
||||
} |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
using ICSharpCode.SharpDevelop.Parser; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Dom |
||||
{ |
||||
/// <summary>
|
||||
/// Observable model for a type definition.
|
||||
/// </summary>
|
||||
public interface ITypeDefinitionModel : IEntityModel |
||||
{ |
||||
FullTypeName FullTypeName { get; } |
||||
IModelCollection<ITypeDefinitionModel> NestedTypes { get; } |
||||
IModelCollection<IMemberModel> Members { get; } |
||||
|
||||
/// <summary>
|
||||
/// Resolves the type definition in the current solution snapshot.
|
||||
/// Returns null if the type definition could not be resolved.
|
||||
/// </summary>
|
||||
new ITypeDefinition Resolve(); |
||||
|
||||
/// <summary>
|
||||
/// Resolves the type definition in the specified solution snapshot.
|
||||
/// Returns null if the type definition could not be resolved.
|
||||
/// </summary>
|
||||
new ITypeDefinition Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot); |
||||
} |
||||
} |
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using System.Collections; |
||||
using System.Collections.ObjectModel; |
||||
using System.Collections.Generic; |
||||
using System.Collections.Specialized; |
||||
using System.Linq; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Dom |
||||
{ |
||||
/// <summary>
|
||||
/// A collection of type definition models with the ability to look them up by name.
|
||||
/// </summary>
|
||||
public interface ITypeDefinitionModelCollection : IModelCollection<ITypeDefinitionModel> |
||||
{ |
||||
/// <summary>
|
||||
/// Gets the type definition model with the specified type name.
|
||||
/// Returns null if no such model object exists.
|
||||
/// </summary>
|
||||
ITypeDefinitionModel this[FullTypeName fullTypeName] { get; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the type definition model with the specified type name.
|
||||
/// Returns null if no such model object exists.
|
||||
/// </summary>
|
||||
ITypeDefinitionModel this[TopLevelTypeName topLevelTypeName] { get; } |
||||
} |
||||
|
||||
public sealed class EmptyTypeDefinitionModelCollection : ITypeDefinitionModelCollection |
||||
{ |
||||
public static readonly EmptyTypeDefinitionModelCollection Instance = new EmptyTypeDefinitionModelCollection(); |
||||
|
||||
event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged { |
||||
add { } |
||||
remove { } |
||||
} |
||||
|
||||
ITypeDefinitionModel ITypeDefinitionModelCollection.this[FullTypeName name] { |
||||
get { return null; } |
||||
} |
||||
|
||||
ITypeDefinitionModel ITypeDefinitionModelCollection.this[TopLevelTypeName name] { |
||||
get { return null; } |
||||
} |
||||
|
||||
ITypeDefinitionModel IReadOnlyList<ITypeDefinitionModel>.this[int index] { |
||||
get { |
||||
throw new ArgumentOutOfRangeException(); |
||||
} |
||||
} |
||||
|
||||
int IReadOnlyCollection<ITypeDefinitionModel>.Count { |
||||
get { return 0; } |
||||
} |
||||
|
||||
IEnumerator<ITypeDefinitionModel> IEnumerable<ITypeDefinitionModel>.GetEnumerator() |
||||
{ |
||||
return Enumerable.Empty<ITypeDefinitionModel>().GetEnumerator(); |
||||
} |
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() |
||||
{ |
||||
return Enumerable.Empty<ITypeDefinitionModel>().GetEnumerator(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using System.Collections.ObjectModel; |
||||
using System.Collections.Specialized; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Dom |
||||
{ |
||||
/// <summary>
|
||||
/// Observable KeyedCollection.
|
||||
/// </summary>
|
||||
public abstract class KeyedModelCollection<TKey, TItem> : KeyedCollection<TKey, TItem>, IModelCollection<TItem> |
||||
{ |
||||
protected override void ClearItems() |
||||
{ |
||||
base.ClearItems(); |
||||
if (CollectionChanged != null) |
||||
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); |
||||
} |
||||
|
||||
protected override void InsertItem(int index, TItem item) |
||||
{ |
||||
base.InsertItem(index, item); |
||||
if (CollectionChanged != null) |
||||
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); |
||||
} |
||||
|
||||
protected override void RemoveItem(int index) |
||||
{ |
||||
var oldItem = Items[index]; |
||||
base.RemoveItem(index); |
||||
if (CollectionChanged != null) |
||||
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItem, index)); |
||||
} |
||||
|
||||
protected override void SetItem(int index, TItem item) |
||||
{ |
||||
var oldItem = Items[index]; |
||||
base.SetItem(index, item); |
||||
if (CollectionChanged != null) |
||||
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItem, index)); |
||||
} |
||||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged; |
||||
} |
||||
} |
@ -0,0 +1,82 @@
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
using ICSharpCode.SharpDevelop.Parser; |
||||
using ICSharpCode.SharpDevelop.Project; |
||||
using NUnit.Framework; |
||||
using Rhino.Mocks; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Dom |
||||
{ |
||||
[TestFixture] |
||||
public class CSharpModelTests |
||||
{ |
||||
IProject project; |
||||
IProjectContent projectContent; |
||||
IEntityModelContext context; |
||||
TopLevelTypeDefinitionModelCollection topLevelTypeModels; |
||||
|
||||
#region SetUp and other helper methods
|
||||
[SetUp] |
||||
public virtual void SetUp() |
||||
{ |
||||
SD.InitializeForUnitTests(); |
||||
SD.Services.AddStrictMockService<IParserService>(); |
||||
project = MockRepository.GenerateStrictMock<IProject>(); |
||||
projectContent = new CSharpProjectContent().AddAssemblyReferences(AssemblyLoader.Corlib); |
||||
context = new ProjectEntityModelContext(project, ".cs"); |
||||
topLevelTypeModels = new TopLevelTypeDefinitionModelCollection(context); |
||||
|
||||
SD.ParserService.Stub(p => p.GetCompilation(project)).WhenCalled(c => c.ReturnValue = projectContent.CreateCompilation()); |
||||
} |
||||
|
||||
[TearDown] |
||||
public virtual void TearDown() |
||||
{ |
||||
SD.TearDownForUnitTests(); |
||||
} |
||||
|
||||
protected void AddCodeFile(string fileName, string code) |
||||
{ |
||||
var oldFile = projectContent.GetFile(fileName); |
||||
Assert.IsNull(oldFile); |
||||
var newFile = Parse(fileName, code); |
||||
projectContent = projectContent.AddOrUpdateFiles(newFile); |
||||
topLevelTypeModels.NotifyParseInformationChanged(oldFile, newFile); |
||||
} |
||||
|
||||
IUnresolvedFile Parse(string fileName, string code) |
||||
{ |
||||
var parser = new CSharpParser(); |
||||
var syntaxTree = parser.Parse(code, fileName); |
||||
Assert.IsFalse(parser.HasErrors); |
||||
return syntaxTree.ToTypeSystem(); |
||||
} |
||||
|
||||
protected void UpdateCodeFile(string fileName, string code) |
||||
{ |
||||
var oldFile = projectContent.GetFile(fileName); |
||||
Assert.IsNotNull(oldFile); |
||||
var newFile = Parse(fileName, code); |
||||
projectContent = projectContent.AddOrUpdateFiles(newFile); |
||||
topLevelTypeModels.NotifyParseInformationChanged(oldFile, newFile); |
||||
} |
||||
|
||||
protected void RemoveCodeFile(string fileName) |
||||
{ |
||||
var oldFile = projectContent.GetFile(fileName); |
||||
projectContent = projectContent.RemoveFiles(fileName); |
||||
topLevelTypeModels.NotifyParseInformationChanged(oldFile, null); |
||||
} |
||||
#endregion
|
||||
|
||||
[Test] |
||||
public void EmptyProject() |
||||
{ |
||||
Assert.AreEqual(0, topLevelTypeModels.Count); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
using ICSharpCode.SharpDevelop.Project; |
||||
using NUnit.Framework; |
||||
using Rhino.Mocks; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Dom |
||||
{ |
||||
[TestFixture] |
||||
public class ProjectEntityModelContextTests |
||||
{ |
||||
IUnresolvedTypeDefinition CreateMockTypeDefinition(string fileName) |
||||
{ |
||||
var typeDefinition = MockRepository.GenerateStrictMock<IUnresolvedTypeDefinition>(); |
||||
var file = MockRepository.GenerateStrictMock<IUnresolvedFile>(); |
||||
file.Stub(f => f.FileName).Return(fileName); |
||||
typeDefinition.Stub(td => td.UnresolvedFile).Return(file); |
||||
return typeDefinition; |
||||
} |
||||
|
||||
[Test] |
||||
public void XamlCodeBehindIsBetterThanXaml() |
||||
{ |
||||
var context = new ProjectEntityModelContext(MockRepository.GenerateStrictMock<IProject>(), ".cs"); |
||||
Assert.IsTrue(context.IsBetterPart(CreateMockTypeDefinition("Window.xaml.cs"), CreateMockTypeDefinition("Window.xaml"))); |
||||
Assert.IsTrue(context.IsBetterPart(CreateMockTypeDefinition("Window.cs"), CreateMockTypeDefinition("Window.xaml"))); |
||||
Assert.IsFalse(context.IsBetterPart(CreateMockTypeDefinition("Window.xaml"), CreateMockTypeDefinition("Window.xaml.cs"))); |
||||
Assert.IsFalse(context.IsBetterPart(CreateMockTypeDefinition("Window.xaml"), CreateMockTypeDefinition("Window.cs"))); |
||||
} |
||||
|
||||
[Test] |
||||
public void MainPartIsBetterThanDesigner() |
||||
{ |
||||
var context = new ProjectEntityModelContext(MockRepository.GenerateStrictMock<IProject>(), ".cs"); |
||||
Assert.IsTrue(context.IsBetterPart(CreateMockTypeDefinition("Form.cs"), CreateMockTypeDefinition("Form.Designer.cs"))); |
||||
Assert.IsFalse(context.IsBetterPart(CreateMockTypeDefinition("Form.Designer.cs"), CreateMockTypeDefinition("Form.cs"))); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
|
||||
namespace ICSharpCode.SharpDevelop |
||||
{ |
||||
public static class AssemblyLoader |
||||
{ |
||||
public static readonly IUnresolvedAssembly Corlib = new CecilLoader { LazyLoad = true }.LoadAssemblyFile(typeof(object).Assembly.Location); |
||||
} |
||||
} |
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using System.ComponentModel; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
using ICSharpCode.SharpDevelop.Parser; |
||||
using ICSharpCode.SharpDevelop.Project; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Dom |
||||
{ |
||||
/// <summary>
|
||||
/// A mutable class that can track a member as the solution is being changed.
|
||||
/// </summary>
|
||||
sealed class MemberModel : IMemberModel |
||||
{ |
||||
/// <summary>
|
||||
/// A strong reference to the parent TypeDefinitionModel.Members collection.
|
||||
/// This is necessary to prevent the garbage collector from
|
||||
/// freeing the weak reference to the collection while one of the member models
|
||||
/// is still in use.
|
||||
/// If we don't prevent this, the type definition model might create multiple
|
||||
/// MemberModels for the same member.
|
||||
/// </summary>
|
||||
internal IModelCollection<MemberModel> strongParentCollectionReference; |
||||
|
||||
readonly IEntityModelContext context; |
||||
IUnresolvedMember member; |
||||
|
||||
public MemberModel(IEntityModelContext context, IUnresolvedMember member) |
||||
{ |
||||
if (context == null) |
||||
throw new ArgumentNullException("context"); |
||||
if (member == null) |
||||
throw new ArgumentNullException("member"); |
||||
this.context = context; |
||||
this.member = member; |
||||
} |
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged { add {} remove {} } |
||||
|
||||
public void Update(IUnresolvedMember newMember) |
||||
{ |
||||
if (newMember == null) |
||||
throw new ArgumentNullException("newMember"); |
||||
this.member = newMember; |
||||
} |
||||
|
||||
public IProject ParentProject { |
||||
get { return context.Project; } |
||||
} |
||||
|
||||
public IUnresolvedMember UnresolvedMember { |
||||
get { return member; } |
||||
} |
||||
|
||||
public EntityType EntityType { |
||||
get { return member.EntityType; } |
||||
} |
||||
|
||||
public DomRegion Region { |
||||
get { return member.Region; } |
||||
} |
||||
|
||||
public string Name { |
||||
get { return member.Name; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets the full type name of the type that declares this member.
|
||||
/// </summary>
|
||||
public FullTypeName DeclaringTypeName { |
||||
get { |
||||
if (member.DeclaringTypeDefinition != null) |
||||
return member.DeclaringTypeDefinition.FullTypeName; |
||||
else |
||||
return new TopLevelTypeName(string.Empty, string.Empty); |
||||
} |
||||
} |
||||
|
||||
#region Resolve
|
||||
public IMember Resolve() |
||||
{ |
||||
var compilation = context.GetCompilation(null); |
||||
return member.Resolve(new SimpleTypeResolveContext(compilation.MainAssembly)); |
||||
} |
||||
|
||||
public IMember Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot) |
||||
{ |
||||
var compilation = context.GetCompilation(solutionSnapshot); |
||||
return member.Resolve(new SimpleTypeResolveContext(compilation.MainAssembly)); |
||||
} |
||||
|
||||
IEntity IEntityModel.Resolve() |
||||
{ |
||||
return Resolve(); |
||||
} |
||||
|
||||
IEntity IEntityModel.Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot) |
||||
{ |
||||
return Resolve(solutionSnapshot); |
||||
} |
||||
#endregion
|
||||
} |
||||
} |
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using System.Collections; |
||||
using System.Collections.ObjectModel; |
||||
using System.Collections.Generic; |
||||
using System.Collections.Specialized; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Dom |
||||
{ |
||||
/// <summary>
|
||||
/// A TypeDefinitionModel-collection that holds models for all top-level types in a project content.
|
||||
/// </summary>
|
||||
sealed class TopLevelTypeDefinitionModelCollection : KeyedModelCollection<TopLevelTypeName, TypeDefinitionModel> |
||||
{ |
||||
readonly IEntityModelContext context; |
||||
|
||||
public TopLevelTypeDefinitionModelCollection(IEntityModelContext context) |
||||
{ |
||||
if (context == null) |
||||
throw new ArgumentNullException("context"); |
||||
this.context = context; |
||||
} |
||||
|
||||
public TypeDefinitionModel this[FullTypeName fullTypeName] { |
||||
get { |
||||
TypeDefinitionModel model = base[fullTypeName.TopLevelTypeName]; |
||||
for (int i = 0; i < fullTypeName.NestingLevel; i++) { |
||||
throw new NotImplementedException(); |
||||
} |
||||
return model; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Updates the parse information.
|
||||
/// </summary>
|
||||
public void NotifyParseInformationChanged(IUnresolvedFile oldFile, IUnresolvedFile newFile) |
||||
{ |
||||
if (oldFile != null) { |
||||
|
||||
} |
||||
} |
||||
|
||||
protected override TopLevelTypeName GetKeyForItem(TypeDefinitionModel item) |
||||
{ |
||||
return item.FullTypeName.TopLevelTypeName; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,264 @@
@@ -0,0 +1,264 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using System.Collections; |
||||
using System.Collections.Generic; |
||||
using System.Collections.Specialized; |
||||
using System.ComponentModel; |
||||
using System.Linq; |
||||
|
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
using ICSharpCode.SharpDevelop.Parser; |
||||
using ICSharpCode.SharpDevelop.Project; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Dom |
||||
{ |
||||
/// <summary>
|
||||
/// A mutable class that can track a type definition as the solution is being changed.
|
||||
/// </summary>
|
||||
sealed class TypeDefinitionModel : ITypeDefinitionModel |
||||
{ |
||||
readonly IEntityModelContext context; |
||||
readonly FullTypeName fullTypeName; |
||||
List<IUnresolvedTypeDefinition> parts; |
||||
|
||||
public TypeDefinitionModel(IEntityModelContext context, params IUnresolvedTypeDefinition[] parts) |
||||
{ |
||||
if (context == null) |
||||
throw new ArgumentNullException("context"); |
||||
if (parts.Length == 0) |
||||
throw new ArgumentException("Number of parts must not be zero"); |
||||
this.context = context; |
||||
this.parts = new List<IUnresolvedTypeDefinition>(parts); |
||||
MovePrimaryPartToFront(); |
||||
this.fullTypeName = parts[0].FullTypeName; |
||||
} |
||||
|
||||
void MovePrimaryPartToFront() |
||||
{ |
||||
int bestPartIndex = 0; |
||||
for (int i = 1; i < parts.Count; i++) { |
||||
if (context.IsBetterPart(parts[i], parts[bestPartIndex])) |
||||
bestPartIndex = i; |
||||
} |
||||
IUnresolvedTypeDefinition primaryPart = parts[bestPartIndex]; |
||||
for (int i = bestPartIndex; i > 0; i--) { |
||||
parts[i] = parts[i - 1]; |
||||
} |
||||
parts[0] = primaryPart; |
||||
} |
||||
|
||||
/* |
||||
/// <summary>
|
||||
/// Updates the type definition model by removing the old parts and adding the new ones.
|
||||
/// </summary>
|
||||
public void Update(IReadOnlyList<IUnresolvedTypeDefinition> removedParts, IReadOnlyList<IUnresolvedTypeDefinition> newParts) |
||||
{ |
||||
SD.MainThread.VerifyAccess(); |
||||
if (removedParts != null) |
||||
foreach (var p in removedParts) |
||||
parts.Remove(p); |
||||
if (newParts != null) |
||||
parts.AddRange(newParts); |
||||
|
||||
MemberModelCollection members; |
||||
if (membersWeakReference.TryGetTarget(out members)) { |
||||
members.Update(parts); |
||||
} |
||||
}*/ |
||||
|
||||
public IProject ParentProject { |
||||
get { return context.Project; } |
||||
} |
||||
|
||||
public FullTypeName FullTypeName { |
||||
get { return fullTypeName; } |
||||
} |
||||
|
||||
public DomRegion Region { |
||||
get { return parts[0].Region; } |
||||
} |
||||
|
||||
public string Name { |
||||
get { return fullTypeName.Name; } |
||||
} |
||||
|
||||
#region Resolve
|
||||
public ITypeDefinition Resolve() |
||||
{ |
||||
var compilation = context.GetCompilation(null); |
||||
return compilation.MainAssembly.GetTypeDefinition(fullTypeName); |
||||
} |
||||
|
||||
public ITypeDefinition Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot) |
||||
{ |
||||
var compilation = context.GetCompilation(solutionSnapshot); |
||||
return compilation.MainAssembly.GetTypeDefinition(fullTypeName); |
||||
} |
||||
|
||||
IEntity IEntityModel.Resolve() |
||||
{ |
||||
return Resolve(); |
||||
} |
||||
|
||||
IEntity IEntityModel.Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot) |
||||
{ |
||||
return Resolve(solutionSnapshot); |
||||
} |
||||
#endregion
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged { add {} remove {} } |
||||
|
||||
#region Members collection
|
||||
sealed class MemberCollection : IModelCollection<MemberModel> |
||||
{ |
||||
readonly TypeDefinitionModel parent; |
||||
List<List<MemberModel>> lists = new List<List<MemberModel>>(); |
||||
|
||||
public MemberCollection(TypeDefinitionModel parent) |
||||
{ |
||||
this.parent = parent; |
||||
} |
||||
|
||||
public void Insert(int partIndex, IUnresolvedTypeDefinition newPart) |
||||
{ |
||||
List<MemberModel> newItems = new List<MemberModel>(newPart.Members.Count); |
||||
foreach (var newMember in newPart.Members) { |
||||
newItems.Add(new MemberModel(parent.context, newMember) { strongParentCollectionReference = this }); |
||||
} |
||||
lists.Insert(partIndex, newItems); |
||||
if (collectionChanged != null) |
||||
collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems, GetCount(partIndex))); |
||||
} |
||||
|
||||
public void Remove(int partIndex) |
||||
{ |
||||
var oldItems = lists[partIndex]; |
||||
lists.RemoveAt(partIndex); |
||||
if (collectionChanged != null) |
||||
collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, GetCount(partIndex))); |
||||
} |
||||
|
||||
public void Update(int partIndex, IUnresolvedTypeDefinition newPart) |
||||
{ |
||||
List<MemberModel> list = lists[partIndex]; |
||||
var newMembers = newPart.Members; |
||||
int startPos = 0; |
||||
// Look at the initial members and update them if they're matching
|
||||
while (startPos < list.Count && startPos < newMembers.Count && IsMatch(list[startPos], newMembers[startPos])) { |
||||
list[startPos].Update(newMembers[startPos]); |
||||
startPos++; |
||||
} |
||||
// Look at the final members
|
||||
int endPosOld = list.Count - 1; |
||||
int endPosNew = newMembers.Count - 1; |
||||
while (endPosOld >= startPos && endPosNew >= startPos && IsMatch(list[endPosOld], newMembers[endPosNew])) { |
||||
list[endPosOld--].Update(newMembers[endPosNew--]); |
||||
} |
||||
// [startPos, endPos] is the middle portion that contains all the changes
|
||||
// Add one to endPos so that it's the exclusive end of the middle portion:
|
||||
endPosOld++; |
||||
endPosNew++; |
||||
// [startPos, endPos)
|
||||
|
||||
// Now we still need to update the members in between.
|
||||
// We might try to be clever here and find a LCS so that we only update the members that were actually changed,
|
||||
// or we might consider moving members around (INotifyCollectionChanged supports moves)
|
||||
// However, the easiest solution by far is to just remove + readd the whole middle portion.
|
||||
var oldItems = collectionChanged != null ? list.GetRange(startPos, endPosOld - startPos) : null; |
||||
list.RemoveRange(startPos, endPosOld - startPos); |
||||
var newItems = new MemberModel[endPosNew - startPos]; |
||||
for (int i = 0; i < newItems.Length; i++) { |
||||
newItems[i] = new MemberModel(parent.context, newMembers[startPos + i]); |
||||
newItems[i].strongParentCollectionReference = this; |
||||
} |
||||
list.InsertRange(startPos, newItems); |
||||
if (collectionChanged != null) |
||||
collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems, GetCount(partIndex) + startPos)); |
||||
} |
||||
|
||||
static bool IsMatch(MemberModel memberModel, IUnresolvedMember newMember) |
||||
{ |
||||
return memberModel.EntityType == newMember.EntityType && memberModel.Name == newMember.Name; |
||||
} |
||||
|
||||
NotifyCollectionChangedEventHandler collectionChanged; |
||||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged { |
||||
add { |
||||
collectionChanged += value; |
||||
// Set strong reference to collection while there are event listeners
|
||||
if (collectionChanged != null) |
||||
parent.membersStrongReference = this; |
||||
} |
||||
remove { |
||||
collectionChanged -= value; |
||||
if (collectionChanged == null) |
||||
parent.membersStrongReference = null; |
||||
} |
||||
} |
||||
|
||||
int GetCount(int partIndex) |
||||
{ |
||||
int count = 0; |
||||
for (int i = 0; i < partIndex; i++) { |
||||
count += lists[i].Count; |
||||
} |
||||
return count; |
||||
} |
||||
|
||||
public int Count { |
||||
get { return GetCount(lists.Count); } |
||||
} |
||||
|
||||
public IEnumerator<MemberModel> GetEnumerator() |
||||
{ |
||||
return lists.SelectMany(i => i).GetEnumerator(); |
||||
} |
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() |
||||
{ |
||||
return GetEnumerator(); |
||||
} |
||||
|
||||
public MemberModel this[int index] { |
||||
get { |
||||
int inputIndex = 0; |
||||
while (index >= lists[inputIndex].Count) { |
||||
index -= lists[inputIndex].Count; |
||||
inputIndex++; |
||||
} |
||||
return lists[inputIndex][index]; |
||||
} |
||||
} |
||||
} |
||||
|
||||
WeakReference<MemberCollection> membersWeakReference = new WeakReference<MemberCollection>(null); |
||||
|
||||
/// <summary>
|
||||
/// Used to keep the member model collection alive while there are event listeners.
|
||||
/// This is necessary to prevent us from creating multiple member models for the same member.
|
||||
/// </summary>
|
||||
MemberCollection membersStrongReference; |
||||
|
||||
public IModelCollection<IMemberModel> Members { |
||||
get { |
||||
SD.MainThread.VerifyAccess(); |
||||
MemberCollection members; |
||||
if (!membersWeakReference.TryGetTarget(out members)) { |
||||
members = new MemberCollection(this); |
||||
membersWeakReference.SetTarget(members); |
||||
} |
||||
return members; |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
public IModelCollection<ITypeDefinitionModel> NestedTypes { |
||||
get { |
||||
throw new NotImplementedException(); |
||||
} |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue