mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1082 lines
33 KiB
1082 lines
33 KiB
// 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.Diagnostics; |
|
using System.IO; |
|
using System.Text; |
|
using System.Threading; |
|
|
|
namespace ICSharpCode.SharpDevelop.Dom |
|
{ |
|
public class DefaultProjectContent : IProjectContent |
|
{ |
|
readonly List<IProjectContent> referencedContents = new List<IProjectContent>(); |
|
|
|
// we use a list of Dictionaries because we need multiple dictionaries: |
|
// each uses another StringComparer |
|
// (C#: StringComparer.InvariantCulture, VB: StringComparer.InvariantCultureCaseInsensitive) |
|
// new dictionaries are added to the list when required |
|
List<Dictionary<string, IClass>> classLists = new List<Dictionary<string, IClass>>(); |
|
List<Dictionary<string, NamespaceStruct>> namespaces = new List<Dictionary<string, NamespaceStruct>>(); |
|
|
|
XmlDoc xmlDoc = new XmlDoc(); |
|
IUsing defaultImports; |
|
bool isDisposed; |
|
|
|
public IUsing DefaultImports { |
|
get { |
|
return defaultImports; |
|
} |
|
set { |
|
defaultImports = value; |
|
} |
|
} |
|
|
|
public virtual object Project { |
|
get { |
|
return null; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets if the project content is representing the current version of the assembly. |
|
/// This property always returns true for ParseProjectContents but might return false |
|
/// for ReflectionProjectContent/CecilProjectContent if the file was changed. |
|
/// </summary> |
|
public virtual bool IsUpToDate { |
|
get { |
|
return true; |
|
} |
|
} |
|
|
|
public List<Dictionary<string, IClass>> ClassLists { |
|
get { |
|
if (classLists.Count == 0) { |
|
classLists.Add(new Dictionary<string, IClass>(language.NameComparer)); |
|
} |
|
return classLists; |
|
} |
|
} |
|
|
|
public ICollection<string> NamespaceNames { |
|
get { |
|
return Namespaces[0].Keys; |
|
} |
|
} |
|
|
|
protected List<Dictionary<string, NamespaceStruct>> Namespaces { |
|
get { |
|
if (namespaces.Count == 0) { |
|
namespaces.Add(new Dictionary<string, NamespaceStruct>(language.NameComparer)); |
|
} |
|
return namespaces; |
|
} |
|
} |
|
|
|
// NamespaceStruct behaves like a reference type because it only consists of readonly references! |
|
protected struct NamespaceStruct |
|
{ |
|
public readonly List<IClass> Classes; |
|
public readonly List<string> SubNamespaces; |
|
|
|
public NamespaceStruct(string name) // struct must have a parameter |
|
{ |
|
this.Classes = new List<IClass>(); |
|
this.SubNamespaces = new List<string>(); |
|
} |
|
|
|
public NamespaceStruct MergeWith(NamespaceStruct other) |
|
{ |
|
NamespaceStruct newStruct = new NamespaceStruct(null); |
|
newStruct.Classes.AddRange(this.Classes); |
|
newStruct.Classes.AddRange(other.Classes); |
|
newStruct.SubNamespaces.AddRange(this.SubNamespaces); |
|
newStruct.SubNamespaces.AddRange(other.SubNamespaces); |
|
return newStruct; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets the class dictionary that uses the name comparison rules of <paramref name="language"/>. |
|
/// </summary> |
|
protected Dictionary<string, IClass> GetClasses(LanguageProperties language) |
|
{ |
|
for (int i = 0; i < classLists.Count; ++i) { |
|
if (classLists[i].Comparer == language.NameComparer) |
|
return classLists[i]; |
|
} |
|
Dictionary<string, IClass> d; |
|
if (classLists.Count > 0) { |
|
Dictionary<string, IClass> oldList = classLists[0]; |
|
d = new Dictionary<string, IClass>(oldList.Count, language.NameComparer); |
|
foreach (KeyValuePair<string, IClass> pair in oldList) { |
|
// don't use d.Add(), the new name language might treat two names as equal |
|
// that were unequal in the old dictionary |
|
d[pair.Key] = pair.Value; |
|
} |
|
} else { |
|
d = new Dictionary<string, IClass>(language.NameComparer); |
|
} |
|
classLists.Add(d); |
|
return d; |
|
} |
|
|
|
/// <summary> |
|
/// Gets the namespace dictionary that uses the name comparison rules of <paramref name="language"/>. |
|
/// </summary> |
|
protected Dictionary<string, NamespaceStruct> GetNamespaces(LanguageProperties language) |
|
{ |
|
for (int i = 0; i < namespaces.Count; ++i) { |
|
if (namespaces[i].Comparer == language.NameComparer) |
|
return namespaces[i]; |
|
} |
|
Dictionary<string, NamespaceStruct> d; |
|
if (namespaces.Count > 0) { |
|
Dictionary<string, NamespaceStruct> oldList = namespaces[0]; |
|
d = new Dictionary<string, NamespaceStruct>(oldList.Count, language.NameComparer); |
|
foreach (KeyValuePair<string, NamespaceStruct> pair in oldList) { |
|
NamespaceStruct ns; |
|
if (d.TryGetValue(pair.Key, out ns)) { |
|
// we got a name conflict due to the new NameComparer. |
|
// This happens if a C# assembly contains the namespace "a" and "A", |
|
// and now we're trying to get a dictionary for use in VB. |
|
d[pair.Key] = ns.MergeWith(pair.Value); |
|
} else { |
|
d.Add(pair.Key, pair.Value); |
|
} |
|
} |
|
} else { |
|
d = new Dictionary<string, NamespaceStruct>(language.NameComparer); |
|
} |
|
namespaces.Add(d); |
|
return d; |
|
} |
|
|
|
public XmlDoc XmlDoc { |
|
get { |
|
return xmlDoc; |
|
} |
|
protected set { |
|
xmlDoc = value; |
|
} |
|
} |
|
|
|
public ICollection<IClass> Classes { |
|
get { |
|
lock (namespaces) { |
|
List<IClass> list = new List<IClass>(ClassLists[0].Count + 10); |
|
foreach (IClass c in ClassLists[0].Values) { |
|
if (c is GenericClassContainer) { |
|
GenericClassContainer gcc = (GenericClassContainer)c; |
|
list.AddRange(gcc.RealClasses); |
|
} else { |
|
list.Add(c); |
|
} |
|
} |
|
return list; |
|
} |
|
} |
|
} |
|
|
|
SystemTypes systemTypes; |
|
|
|
/// <summary> |
|
/// Gets a class that allows to conveniently access commonly used types in the system |
|
/// namespace. |
|
/// </summary> |
|
public virtual SystemTypes SystemTypes { |
|
get { |
|
if (systemTypes == null) { |
|
systemTypes = new SystemTypes(this); |
|
} |
|
return systemTypes; |
|
} |
|
} |
|
|
|
public ICollection<IProjectContent> ReferencedContents { |
|
get { |
|
return referencedContents; |
|
} |
|
} |
|
|
|
LanguageProperties language = LanguageProperties.CSharp; |
|
|
|
/// <summary> |
|
/// Gets/Sets the properties of the language this project content was written in. |
|
/// </summary> |
|
public LanguageProperties Language { |
|
[DebuggerStepThrough] |
|
get { |
|
return language; |
|
} |
|
set { |
|
if (value == null) throw new ArgumentNullException(); |
|
language = value; |
|
} |
|
} |
|
|
|
public string GetXmlDocumentation(string memberTag) |
|
{ |
|
CheckNotDisposed(); |
|
string desc = xmlDoc.GetDocumentation(memberTag); |
|
if (desc != null) { |
|
return desc; |
|
} |
|
lock (referencedContents) { |
|
foreach (IProjectContent referencedContent in referencedContents) { |
|
desc = referencedContent.XmlDoc.GetDocumentation(memberTag); |
|
if (desc != null) { |
|
return desc; |
|
} |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
public virtual void Dispose() |
|
{ |
|
xmlDoc.Dispose(); |
|
isDisposed = true; |
|
} |
|
|
|
[Conditional("DEBUG")] |
|
void CheckNotDisposed() |
|
{ |
|
// TODO: this is broken - we are accessing project contents even after |
|
// they have been unloaded, e.g. on other threads |
|
if (!isDisposed) { |
|
// throw new ObjectDisposedException(); |
|
} |
|
} |
|
|
|
public void AddClassToNamespaceList(IClass addClass) |
|
{ |
|
lock (namespaces) { |
|
AddClassToNamespaceListInternal(addClass); |
|
} |
|
DomCache.Clear(); |
|
} |
|
|
|
/// <summary> |
|
/// Container class that is used when multiple classes with different type parameter |
|
/// count have the same class name. |
|
/// |
|
/// The GenericClassContainer is only used internally to hold the class list, it is never returned by any public API. |
|
/// </summary> |
|
private sealed class GenericClassContainer : DefaultClass |
|
{ |
|
public GenericClassContainer(string fullyQualifiedName) : base(DefaultCompilationUnit.DummyCompilationUnit, fullyQualifiedName) {} |
|
|
|
IClass[] realClasses = new IClass[4]; |
|
|
|
public IEnumerable<IClass> RealClasses { |
|
get { |
|
foreach (IClass c in realClasses) { |
|
if (c != null) yield return c; |
|
} |
|
} |
|
} |
|
|
|
public int RealClassCount { |
|
get { |
|
int count = 0; |
|
foreach (IClass c in realClasses) { |
|
if (c != null) count += 1; |
|
} |
|
return count; |
|
} |
|
} |
|
|
|
public IClass Get(int typeParameterCount) |
|
{ |
|
if (realClasses.Length > typeParameterCount) |
|
return realClasses[typeParameterCount]; |
|
else |
|
return null; |
|
} |
|
|
|
public IClass GetBest(int typeParameterCount) |
|
{ |
|
IClass c; |
|
for (int i = typeParameterCount; i < realClasses.Length; i++) { |
|
c = Get(i); |
|
if (c != null) return c; |
|
} |
|
for (int i = typeParameterCount - 1; i >= 0; i--) { |
|
c = Get(i); |
|
if (c != null) return c; |
|
} |
|
return null; |
|
} |
|
|
|
public void Set(IClass c) |
|
{ |
|
int typeParameterCount = c.TypeParameters.Count; |
|
if (realClasses.Length <= typeParameterCount) { |
|
IClass[] newArray = new IClass[typeParameterCount + 2]; |
|
realClasses.CopyTo(newArray, 0); |
|
realClasses = newArray; |
|
} |
|
realClasses[typeParameterCount] = c; |
|
} |
|
|
|
public void Remove(int typeParameterCount) |
|
{ |
|
if (realClasses.Length > typeParameterCount) |
|
realClasses[typeParameterCount] = null; |
|
} |
|
} |
|
|
|
protected void AddClassToNamespaceListInternal(IClass addClass) |
|
{ |
|
// Freeze the class when adding it to the project content |
|
addClass.Freeze(); |
|
|
|
Debug.Assert(!(addClass is CompoundClass)); |
|
Debug.Assert(!addClass.HasCompoundClass); |
|
|
|
string fullyQualifiedName = addClass.FullyQualifiedName; |
|
IClass existingClass = GetClassInternal(fullyQualifiedName, addClass.TypeParameters.Count, language); |
|
if (existingClass != null && existingClass.TypeParameters.Count == addClass.TypeParameters.Count) { |
|
LoggingService.Debug("Adding existing class " + addClass.Name + " from " + Path.GetFileName(addClass.CompilationUnit.FileName)); |
|
CompoundClass compound = existingClass as CompoundClass; |
|
if (compound != null) { |
|
// mark the class as partial |
|
// (VB allows specifying the 'partial' modifier only on one part) |
|
addClass.HasCompoundClass = true; |
|
|
|
// add the new class to the compound class |
|
List<IClass> newParts = new List<IClass>(compound.Parts); |
|
newParts.Add(addClass); |
|
// construct a replacement CompoundClass with the new part list |
|
addClass = CompoundClass.Create(newParts); |
|
LoggingService.Debug("Added new part (old part count=" + compound.Parts.Count +", new part count=" + newParts.Count + ")"); |
|
} else { |
|
// Instead of overwriting a class with another, treat both parts as partial. |
|
// This fixes SD2-1217. |
|
|
|
if (!(addClass.IsPartial || language.ImplicitPartialClasses)) { |
|
LoggingService.Info("Duplicate class " + fullyQualifiedName + ", creating compound"); |
|
} else { |
|
LoggingService.Debug("Creating compound for " + fullyQualifiedName); |
|
} |
|
|
|
// Merge existing non-partial class with addClass |
|
addClass.HasCompoundClass = true; |
|
existingClass.HasCompoundClass = true; |
|
|
|
addClass = CompoundClass.Create(new[] { addClass, existingClass }); |
|
} |
|
} |
|
AddClassToNamespaceListInternal2(addClass); |
|
} |
|
|
|
void AddClassToNamespaceListInternal2(IClass addClass) |
|
{ |
|
bool isReplacingExistingClass = false; |
|
string fullyQualifiedName = addClass.FullyQualifiedName; |
|
IClass oldDictionaryClass; |
|
if (GetClasses(language).TryGetValue(fullyQualifiedName, out oldDictionaryClass)) { |
|
isReplacingExistingClass = true; |
|
GenericClassContainer gcc = oldDictionaryClass as GenericClassContainer; |
|
if (gcc != null) { |
|
gcc.Set(addClass); |
|
return; |
|
} else if (oldDictionaryClass.TypeParameters.Count != addClass.TypeParameters.Count) { |
|
gcc = new GenericClassContainer(fullyQualifiedName); |
|
gcc.Set(addClass); |
|
gcc.Set(oldDictionaryClass); |
|
addClass = gcc; |
|
} |
|
} |
|
|
|
foreach (Dictionary<string, IClass> classes in ClassLists) { |
|
classes[fullyQualifiedName] = addClass; |
|
} |
|
string nSpace = addClass.Namespace; |
|
if (nSpace == null) { |
|
nSpace = String.Empty; |
|
} |
|
CreateNamespace(nSpace); |
|
List<IClass> classList = GetNamespaces(this.language)[nSpace].Classes; |
|
if (isReplacingExistingClass) { |
|
for (int i = 0; i < classList.Count; i++) { |
|
if (classList[i].FullyQualifiedName == fullyQualifiedName) { |
|
classList[i] = addClass; |
|
return; |
|
} |
|
} |
|
} |
|
classList.Add(addClass); |
|
} |
|
|
|
void CreateNamespace(string nSpace) |
|
{ |
|
Dictionary<string, NamespaceStruct> dict = GetNamespaces(this.language); |
|
if (dict.ContainsKey(nSpace)) |
|
return; |
|
NamespaceStruct namespaceStruct = new NamespaceStruct(nSpace); |
|
dict.Add(nSpace, namespaceStruct); |
|
// use the same namespaceStruct for all dictionaries |
|
foreach (Dictionary<string, NamespaceStruct> otherDict in namespaces) { |
|
if (otherDict == dict) continue; |
|
NamespaceStruct existingNamespaceStruct; |
|
if (otherDict.TryGetValue(nSpace, out existingNamespaceStruct)) |
|
otherDict[nSpace] = existingNamespaceStruct.MergeWith(namespaceStruct); |
|
else |
|
otherDict.Add(nSpace, namespaceStruct); |
|
} |
|
if (nSpace.Length == 0) |
|
return; |
|
// add to parent namespace |
|
int pos = nSpace.LastIndexOf('.'); |
|
string parent; |
|
string subNs; |
|
if (pos < 0) { |
|
parent = ""; |
|
subNs = nSpace; |
|
} else { |
|
parent = nSpace.Substring(0, pos); |
|
subNs = nSpace.Substring(pos + 1); |
|
} |
|
CreateNamespace(parent); |
|
dict[parent].SubNamespaces.Add(subNs); |
|
} |
|
|
|
/// <summary> |
|
/// Removes the specified namespace from all namespace lists if the namespace is empty. |
|
/// </summary> |
|
void RemoveEmptyNamespace(string nSpace) |
|
{ |
|
if (nSpace == null || nSpace.Length == 0) return; |
|
Dictionary<string, NamespaceStruct> dict = GetNamespaces(this.language); |
|
if (!dict.ContainsKey(nSpace)) |
|
return; |
|
// remove only if really empty |
|
if (dict[nSpace].Classes.Count > 0 || dict[nSpace].SubNamespaces.Count > 0) |
|
return; |
|
// remove the namespace from all dictionaries |
|
foreach (Dictionary<string, NamespaceStruct> anyDict in namespaces) { |
|
anyDict.Remove(nSpace); |
|
} |
|
// remove the namespace from parent's SubNamespaces list |
|
int pos = nSpace.LastIndexOf('.'); |
|
string parent; |
|
string subNs; |
|
if (pos < 0) { |
|
parent = ""; |
|
subNs = nSpace; |
|
} else { |
|
parent = nSpace.Substring(0, pos); |
|
subNs = nSpace.Substring(pos + 1); |
|
} |
|
dict[parent].SubNamespaces.Remove(subNs); |
|
RemoveEmptyNamespace(parent); // remove parent if also empty |
|
} |
|
|
|
List<IAttribute> assemblyAttributes = new List<IAttribute>(); |
|
|
|
public virtual IList<IAttribute> GetAssemblyAttributes() |
|
{ |
|
lock (namespaces) { |
|
return assemblyAttributes.ToArray(); |
|
} |
|
} |
|
|
|
public void RemoveCompilationUnit(ICompilationUnit unit) |
|
{ |
|
lock (namespaces) { |
|
foreach (IClass c in unit.Classes) { |
|
RemoveClass(c); |
|
} |
|
foreach (IAttribute attr in unit.Attributes) |
|
assemblyAttributes.Remove(attr); |
|
} |
|
DomCache.Clear(); |
|
} |
|
|
|
public void UpdateCompilationUnit(ICompilationUnit oldUnit, ICompilationUnit parserOutput, string fileName) |
|
{ |
|
parserOutput.Freeze(); |
|
lock (namespaces) { |
|
if (oldUnit != null) { |
|
foreach (IClass c in oldUnit.Classes) |
|
RemoveClass(c); |
|
foreach (IAttribute attr in oldUnit.Attributes) |
|
assemblyAttributes.Remove(attr); |
|
} |
|
|
|
foreach (IClass c in parserOutput.Classes) { |
|
AddClassToNamespaceListInternal(c); |
|
} |
|
assemblyAttributes.AddRange(parserOutput.Attributes); |
|
} |
|
DomCache.Clear(); |
|
} |
|
|
|
protected void RemoveClass(IClass @class) |
|
{ |
|
string fullyQualifiedName = @class.FullyQualifiedName; |
|
int typeParameterCount = @class.TypeParameters.Count; |
|
if (@class.HasCompoundClass) { |
|
LoggingService.Debug("Removing part " + @class.CompilationUnit.FileName + " from compound class " + @class.FullyQualifiedName); |
|
|
|
// remove a part of a partial class |
|
// Use "as" cast to fix SD2-680: the stored class might be a part not marked as partial |
|
CompoundClass compound = GetClassInternal(fullyQualifiedName, typeParameterCount, language) as CompoundClass; |
|
if (compound == null) { |
|
LoggingService.Warn("compound class not found"); |
|
return; |
|
} |
|
typeParameterCount = compound.TypeParameters.Count; |
|
|
|
List<IClass> newParts = new List<IClass>(compound.Parts); |
|
newParts.Remove(@class); |
|
if (newParts.Count > 1) { |
|
LoggingService.Debug("Part removed, old part count = " + compound.Parts.Count + ", new part count=" + newParts.Count); |
|
AddClassToNamespaceListInternal2(CompoundClass.Create(newParts)); |
|
return; |
|
} else if (newParts.Count == 1) { |
|
LoggingService.Debug("Second-to-last part removed (old part count = " + compound.Parts.Count + "), overwriting compound with last part"); |
|
newParts[0].HasCompoundClass = false; |
|
AddClassToNamespaceListInternal2(newParts[0]); |
|
return; |
|
} else { // newParts.Count == 0 |
|
// this should not be possible, the compound should have been destroyed when there was only 1 part left |
|
LoggingService.Warn("All parts removed, remove compound"); |
|
@class = compound; // all parts removed, remove compound class |
|
} |
|
} |
|
|
|
IClass classInDictionary; |
|
if (!GetClasses(language).TryGetValue(fullyQualifiedName, out classInDictionary)) { |
|
return; |
|
} |
|
|
|
GenericClassContainer gcc = classInDictionary as GenericClassContainer; |
|
if (gcc != null) { |
|
gcc.Remove(typeParameterCount); |
|
if (gcc.RealClassCount > 0) { |
|
return; |
|
} |
|
} |
|
|
|
foreach (Dictionary<string, IClass> classes in ClassLists) { |
|
classes.Remove(fullyQualifiedName); |
|
} |
|
|
|
string nSpace = @class.Namespace; |
|
if (nSpace == null) { |
|
nSpace = String.Empty; |
|
} |
|
|
|
// Remove class from namespace lists |
|
List<IClass> classList = GetNamespaces(this.language)[nSpace].Classes; |
|
for (int i = 0; i < classList.Count; i++) { |
|
if (language.NameComparer.Equals(classList[i].FullyQualifiedName, fullyQualifiedName)) { |
|
classList.RemoveAt(i); |
|
break; |
|
} |
|
} |
|
if (classList.Count == 0) { |
|
RemoveEmptyNamespace(nSpace); |
|
} |
|
} |
|
|
|
#region Default Parser Layer dependent functions |
|
public IClass GetClass(string typeName, int typeParameterCount) |
|
{ |
|
return GetClass(typeName, typeParameterCount, language, GetClassOptions.Default); |
|
} |
|
|
|
protected IClass GetClassInternal(string typeName, int typeParameterCount, LanguageProperties language) |
|
{ |
|
CheckNotDisposed(); |
|
#if DEBUG |
|
if (System.Text.RegularExpressions.Regex.IsMatch (typeName, "`[0-9]+$")) |
|
Debug.Assert(false, "how did a Reflection type name get here?"); |
|
#endif |
|
lock (namespaces) { |
|
IClass c; |
|
if (GetClasses(language).TryGetValue(typeName, out c)) { |
|
GenericClassContainer gcc = c as GenericClassContainer; |
|
if (gcc != null) { |
|
return gcc.GetBest(typeParameterCount); |
|
} |
|
return c; |
|
} |
|
return null; |
|
} |
|
} |
|
|
|
bool IsAccessibleClass(IClass c) |
|
{ |
|
// check the outermost class (which is either public or internal) |
|
while (c.DeclaringType != null) |
|
c = c.DeclaringType; |
|
return c.IsPublic || c.ProjectContent.InternalsVisibleTo(this); |
|
} |
|
|
|
public IClass GetClass(string typeName, int typeParameterCount, LanguageProperties language, GetClassOptions options) |
|
{ |
|
IClass c = GetClassInternal(typeName, typeParameterCount, language); |
|
if (c != null && c.TypeParameters.Count == typeParameterCount) { |
|
return c; |
|
} |
|
|
|
// Search in references: |
|
if ((options & GetClassOptions.LookInReferences) != 0) { |
|
lock (referencedContents) { |
|
foreach (IProjectContent content in referencedContents) { |
|
// Look for the class in the referenced content. |
|
// Don't do a inner-class search in the recursive call - one search |
|
// done by this GetClass call is sufficient. |
|
IClass contentClass = content.GetClass( |
|
typeName, typeParameterCount, language, |
|
options & ~(GetClassOptions.LookInReferences | GetClassOptions.LookForInnerClass)); |
|
if (contentClass != null) { |
|
if (contentClass.TypeParameters.Count == typeParameterCount |
|
&& IsAccessibleClass(contentClass)) |
|
{ |
|
return contentClass; |
|
} else { |
|
c = contentClass; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
if ((options & GetClassOptions.LookForInnerClass) != 0) { |
|
// not found -> maybe nested type -> trying to find class that contains this one. |
|
int lastIndex = typeName.LastIndexOf('.'); |
|
if (lastIndex > 0) { |
|
string outerName = typeName.Substring(0, lastIndex); |
|
IClass upperClass = GetClass(outerName, typeParameterCount, language, options); |
|
if (upperClass != null) { |
|
foreach (IClass upperBaseClass in upperClass.ClassInheritanceTree) { |
|
IList<IClass> innerClasses = upperBaseClass.InnerClasses; |
|
if (innerClasses != null) { |
|
string innerName = typeName.Substring(lastIndex + 1); |
|
foreach (IClass innerClass in innerClasses) { |
|
if (language.NameComparer.Equals(innerClass.Name, innerName)) { |
|
if (innerClass.TypeParameters.Count == typeParameterCount) { |
|
return innerClass; |
|
} else { |
|
// store match |
|
c = innerClass; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
if ((options & GetClassOptions.ExactMatch) == GetClassOptions.ExactMatch) { |
|
return null; |
|
} else { |
|
// no matching class found - we'll return a class with different type paramter count |
|
return c; |
|
} |
|
} |
|
|
|
public List<ICompletionEntry> GetNamespaceContents(string nameSpace) |
|
{ |
|
List<ICompletionEntry> namespaceList = new List<ICompletionEntry>(); |
|
AddNamespaceContents(namespaceList, nameSpace, language, true); |
|
return namespaceList; |
|
} |
|
|
|
public List<ICompletionEntry> GetAllContents() |
|
{ |
|
List<ICompletionEntry> list = new List<ICompletionEntry>(); |
|
AddAllContents(list, this.language, true); |
|
return list; |
|
} |
|
|
|
/// <summary> |
|
/// Adds the contents of all namespaces in this project to the <paramref name="list"/>. |
|
/// </summary> |
|
/// <param name="lookInReferences">If true, contents of referenced projects will be added as well (not recursive - just 1 level deep).</param> |
|
public void AddAllContents(List<ICompletionEntry> list, LanguageProperties language, bool lookInReferences) |
|
{ |
|
if (lookInReferences) { |
|
lock (referencedContents) { |
|
foreach (IProjectContent content in referencedContents) { |
|
content.AddAllContents(list, language, false); |
|
} |
|
} |
|
} |
|
lock (namespaces) { |
|
Dictionary<string, NamespaceStruct> dict = GetNamespaces(language); |
|
foreach (var namespaceStruct in dict.Values) { |
|
AddNamespaceStructContents(list, namespaceStruct, language, lookInReferences); |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Adds the contents of the specified <paramref name="nameSpace"/> to the <paramref name="list"/>. |
|
/// </summary> |
|
/// <param name="lookInReferences">If true, contents of referenced projects will be added as well (not recursive - just 1 level deep).</param> |
|
public void AddNamespaceContents(List<ICompletionEntry> list, string nameSpace, LanguageProperties language, bool lookInReferences) |
|
{ |
|
if (nameSpace == null) { |
|
return; |
|
} |
|
|
|
if (lookInReferences) { |
|
lock (referencedContents) { |
|
foreach (IProjectContent content in referencedContents) { |
|
content.AddNamespaceContents(list, nameSpace, language, false); |
|
} |
|
} |
|
} |
|
|
|
lock (namespaces) { |
|
Dictionary<string, NamespaceStruct> dict = GetNamespaces(language); |
|
if (dict.ContainsKey(nameSpace)) { |
|
NamespaceStruct ns = dict[nameSpace]; |
|
AddNamespaceStructContents(list, ns, language, lookInReferences); |
|
} |
|
} |
|
} |
|
|
|
void AddNamespaceStructContents(List<ICompletionEntry> list, NamespaceStruct ns, LanguageProperties language, bool lookInReferences) |
|
{ |
|
int newCapacity = list.Count + ns.Classes.Count + ns.SubNamespaces.Count; |
|
if (list.Capacity < newCapacity) |
|
list.Capacity = Math.Max(list.Count * 2, newCapacity); |
|
foreach (IClass c in ns.Classes) { |
|
if (c is GenericClassContainer) { |
|
foreach (IClass realClass in ((GenericClassContainer)c).RealClasses) { |
|
AddNamespaceContentsClass(list, realClass, language, lookInReferences); |
|
} |
|
} else { |
|
AddNamespaceContentsClass(list, c, language, lookInReferences); |
|
} |
|
} |
|
foreach (string subns in ns.SubNamespaces) { |
|
NamespaceEntry subnsEntry = new NamespaceEntry(subns); |
|
if (!list.Contains(subnsEntry)) // PERF |
|
list.Add(subnsEntry); |
|
} |
|
} |
|
|
|
void AddNamespaceContentsClass(List<ICompletionEntry> list, IClass c, LanguageProperties language, bool lookInReferences) |
|
{ |
|
if (c.IsInternal && !lookInReferences) { |
|
// internal class and we are looking at it from another project content |
|
return; |
|
} |
|
if (language.ShowInNamespaceCompletion(c)) |
|
list.Add(c); |
|
if (language.ImportModules && c.ClassType == ClassType.Module) { |
|
foreach (IMember m in c.Methods) { |
|
if (m.IsAccessible(null, false)) |
|
list.Add(m); |
|
} |
|
foreach (IMember m in c.Events) { |
|
if (m.IsAccessible(null, false)) |
|
list.Add(m); |
|
} |
|
foreach (IMember m in c.Fields) { |
|
if (m.IsAccessible(null, false)) |
|
list.Add(m); |
|
} |
|
foreach (IMember m in c.Properties) { |
|
if (m.IsAccessible(null, false)) |
|
list.Add(m); |
|
} |
|
} |
|
} |
|
|
|
public bool NamespaceExists(string name) |
|
{ |
|
return NamespaceExists(name, language, true); |
|
} |
|
|
|
public bool NamespaceExists(string name, LanguageProperties language, bool lookInReferences) |
|
{ |
|
if (name == null) { |
|
return false; |
|
} |
|
|
|
if (lookInReferences) { |
|
lock (referencedContents) { |
|
foreach (IProjectContent content in referencedContents) { |
|
if (content.NamespaceExists(name, language, false)) { |
|
return true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
lock (namespaces) { |
|
return GetNamespaces(language).ContainsKey(name); |
|
} |
|
} |
|
|
|
bool MatchesRequest(SearchTypeRequest request, ref SearchTypeResult result) |
|
{ |
|
if (result.NamespaceResult != null) |
|
return request.TypeParameterCount == 0; |
|
else { |
|
IReturnType rt = result.Result; |
|
if (rt == null) |
|
return false; |
|
if (rt.TypeArgumentCount != request.TypeParameterCount) |
|
return false; |
|
IClass c = rt.GetUnderlyingClass(); |
|
if (c != null) |
|
return IsAccessibleClass(c); |
|
else |
|
return true; |
|
} |
|
} |
|
|
|
public SearchTypeResult SearchType(SearchTypeRequest request) |
|
{ |
|
string name = request.Name; |
|
if (string.IsNullOrEmpty(name)) { |
|
return SearchTypeResult.Empty; |
|
} |
|
|
|
// 'result' holds the fall-back result if no result with the right type parameter count is found. |
|
SearchTypeResult result = SearchTypeResult.Empty; |
|
|
|
if (name.IndexOf('.') < 0) { |
|
for (IClass outerClass = request.CurrentType; outerClass != null; outerClass = outerClass.DeclaringType) { |
|
// Try inner classes (in full inheritance tree) |
|
// Don't use loop with cur = cur.BaseType because of inheritance cycles |
|
foreach (IClass baseClass in outerClass.ClassInheritanceTree) { |
|
if (baseClass.ClassType == ClassType.Class || baseClass.ClassType == ClassType.Struct || baseClass.ClassType == ClassType.Module) { |
|
foreach (IClass innerClass in baseClass.InnerClasses) { |
|
if (language.NameComparer.Equals(innerClass.Name, name)) { |
|
result = new SearchTypeResult(innerClass); |
|
if (MatchesRequest(request, ref result)) { |
|
return result; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
for (IUsingScope usingScope = request.CurrentUsingScope; usingScope != null; usingScope = usingScope.Parent) { |
|
string fullname; |
|
if (string.IsNullOrEmpty(usingScope.NamespaceName)) { |
|
// Try if name is already the full type name |
|
fullname = name; |
|
} else { |
|
fullname = usingScope.NamespaceName + "." + name; |
|
} |
|
IClass c = GetClass(fullname, request.TypeParameterCount); |
|
if (c != null) { |
|
result = new SearchTypeResult(c); |
|
if (MatchesRequest(request, ref result)) { |
|
return result; |
|
} |
|
} |
|
if (NamespaceExists(fullname)) { |
|
result = new SearchTypeResult(fullname, null); |
|
if (MatchesRequest(request, ref result)) { |
|
return result; |
|
} |
|
} |
|
|
|
// prefer aliases over imported types |
|
foreach (IUsing u in usingScope.Usings) { |
|
if (u.HasAliases) { |
|
if (SearchTypeInUsing(u, request, ref result)) |
|
return result; |
|
} |
|
} |
|
foreach (IUsing u in usingScope.Usings) { |
|
if (!u.HasAliases) { |
|
if (SearchTypeInUsing(u, request, ref result)) |
|
return result; |
|
} |
|
} |
|
} |
|
|
|
if (defaultImports != null) { |
|
if (SearchTypeInUsing(defaultImports, request, ref result)) |
|
return result; |
|
} |
|
return result; |
|
} |
|
|
|
bool SearchTypeInUsing(IUsing u, SearchTypeRequest request, ref SearchTypeResult result) |
|
{ |
|
foreach (IReturnType r in u.SearchType(request.Name, request.TypeParameterCount)) { |
|
result = new SearchTypeResult(r, u); |
|
if (MatchesRequest(request, ref result)) { |
|
return true; |
|
} |
|
} |
|
string nsResult = u.SearchNamespace(request.Name); |
|
if (nsResult != null) { |
|
result = new SearchTypeResult(nsResult, null); |
|
if (MatchesRequest(request, ref result)) { |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
/// <summary> |
|
/// Gets the position of a member in this project content (not a referenced one). |
|
/// </summary> |
|
/// <param name="fullMemberName">The full class name in Reflection syntax (always case sensitive, ` for generics)</param> |
|
/// <param name="lookInReferences">Whether to search in referenced project contents.</param> |
|
public IClass GetClassByReflectionName(string className, bool lookInReferences) |
|
{ |
|
if (className == null) |
|
throw new ArgumentNullException("className"); |
|
int typeParameterCount; |
|
className = ReflectionLayer.ReflectionClass.ConvertReflectionNameToFullName(className, out typeParameterCount); |
|
GetClassOptions options = GetClassOptions.Default; |
|
if (!lookInReferences) |
|
options &= ~GetClassOptions.LookInReferences; |
|
return GetClass(className, typeParameterCount, LanguageProperties.CSharp, options); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the position of a member in this project content (not a referenced one). |
|
/// </summary> |
|
/// <param name="fullMemberName">The member name in Reflection syntax (always case sensitive, ` for generics). |
|
/// member name = [ExplicitInterface .] MemberName [`TypeArgumentCount] [(Parameters)]</param> |
|
public static IMember GetMemberByReflectionName(IClass curClass, string fullMemberName) |
|
{ |
|
if (curClass == null) |
|
return null; |
|
int pos = fullMemberName.IndexOf('('); |
|
if (pos > 0) { |
|
// is method call |
|
|
|
int colonPos = fullMemberName.LastIndexOf(':'); |
|
if (colonPos > 0) { |
|
fullMemberName = fullMemberName.Substring(0, colonPos); |
|
} |
|
|
|
string interfaceName = null; |
|
string memberName = fullMemberName.Substring(0, pos); |
|
int pos2 = memberName.LastIndexOf('.'); |
|
if (pos2 > 0) { |
|
interfaceName = memberName.Substring(0, pos2); |
|
memberName = memberName.Substring(pos2 + 1); |
|
} |
|
|
|
// put class name in front of full member name because we'll compare against it later |
|
fullMemberName = curClass.DotNetName + "." + fullMemberName; |
|
|
|
IMethod firstMethod = null; |
|
foreach (IMethod m in curClass.Methods) { |
|
if (m.Name == memberName) { |
|
if (firstMethod == null) firstMethod = m; |
|
StringBuilder dotnetName = new StringBuilder(m.DotNetName); |
|
dotnetName.Append('('); |
|
for (int i = 0; i < m.Parameters.Count; i++) { |
|
if (i > 0) dotnetName.Append(','); |
|
if (m.Parameters[i].ReturnType != null) { |
|
dotnetName.Append(m.Parameters[i].ReturnType.DotNetName); |
|
} |
|
} |
|
dotnetName.Append(')'); |
|
if (dotnetName.ToString() == fullMemberName) { |
|
return m; |
|
} |
|
} |
|
} |
|
return firstMethod; |
|
} else { |
|
string interfaceName = null; |
|
string memberName = fullMemberName; |
|
pos = memberName.LastIndexOf('.'); |
|
if (pos > 0) { |
|
interfaceName = memberName.Substring(0, pos); |
|
memberName = memberName.Substring(pos + 1); |
|
} |
|
// get first method with that name, but prefer method without parameters |
|
IMethod firstMethod = null; |
|
foreach (IMethod m in curClass.Methods) { |
|
if (m.Name == memberName) { |
|
if (firstMethod == null || m.Parameters.Count == 0) |
|
firstMethod = m; |
|
} |
|
} |
|
if (firstMethod != null) |
|
return firstMethod; |
|
return curClass.SearchMember(memberName, LanguageProperties.CSharp); |
|
} |
|
} |
|
|
|
public FilePosition GetPosition(IEntity d) |
|
{ |
|
IMember m = d as IMember; |
|
IClass c = d as IClass; |
|
if (m != null) { |
|
return new FilePosition(m.DeclaringType.CompilationUnit, m.Region.BeginLine, m.Region.BeginColumn); |
|
} else if (c != null) { |
|
return new FilePosition(c.CompilationUnit, c.Region.BeginLine, c.Region.BeginColumn); |
|
} else { |
|
return FilePosition.Empty; |
|
} |
|
} |
|
#endregion |
|
|
|
public void AddReferencedContent(IProjectContent pc) |
|
{ |
|
if (pc != null) { |
|
lock (this.ReferencedContents) { |
|
this.ReferencedContents.Add(pc); |
|
} |
|
} |
|
} |
|
|
|
public event EventHandler ReferencedContentsChanged; |
|
|
|
protected virtual void OnReferencedContentsChanged(EventArgs e) |
|
{ |
|
systemTypes = null; // re-create system types |
|
DomCache.Clear(); |
|
if (ReferencedContentsChanged != null) { |
|
ReferencedContentsChanged(this, e); |
|
} |
|
} |
|
|
|
public bool InternalsVisibleTo(IProjectContent otherProjectContent) |
|
{ |
|
return this == otherProjectContent; |
|
} |
|
|
|
public static readonly IProjectContent DummyProjectContent = new DummyContent(); |
|
|
|
private class DummyContent : DefaultProjectContent |
|
{ |
|
public override string ToString() |
|
{ |
|
return "[DummyProjectContent]"; |
|
} |
|
|
|
public override SystemTypes SystemTypes { |
|
get { |
|
return HostCallback.GetCurrentProjectContent().SystemTypes; |
|
} |
|
} |
|
} |
|
|
|
/// <inheritdoc/> |
|
public virtual string AssemblyName { |
|
get { |
|
return null; |
|
} |
|
} |
|
} |
|
}
|
|
|