Skip to content
This repository has been archived by the owner on Sep 24, 2020. It is now read-only.

Resolving members with CSHarpTypeResolveContext #507

Open
W3L-Sebastian-Heimfarth opened this issue Dec 15, 2015 · 0 comments
Open

Resolving members with CSHarpTypeResolveContext #507

W3L-Sebastian-Heimfarth opened this issue Dec 15, 2015 · 0 comments

Comments

@W3L-Sebastian-Heimfarth

Hi!

We are using NRefactory for c# code completion and most times it works great :) Recently we found a problem which we think is caused by not finding the correct CSHarpTypeResolveContext. For a given code snippet and a given cursorPosition we calculate the CSHarpTypeResolveContext as follows (this is a simplified version of our production code):

public class TypeResolveContextDetermination
    {
        /// <summary>
        /// Resolves TypeResolveContext like this:
        /// (single) main-assembly -> current usingScope -> innermostTypeDefinition -> currentMember (e.g. Method, Ctor, etc.)
        /// </summary>
        /// <param name="sourceCode"></param>
        /// <param name="cursorIndex"></param>
        /// <returns></returns>
        public CSharpTypeResolveContext DetermineTypeResolveContext(string sourceCode, int cursorIndex)
        {
            var document = new ReadOnlyDocument(sourceCode);
            IProjectContent projectContent;
            CSharpUnresolvedFile unresolvedFile;

            var compilation = CreateCompilation(sourceCode, out projectContent, out unresolvedFile);
            var cursorLocation = cursorIndex > 0 ? document.GetLocation(cursorIndex) : new TextLocation(1, 1);


            var typeResolveContext = new CSharpTypeResolveContext(compilation.MainAssembly);
            typeResolveContext = typeResolveContext.WithUsingScope(unresolvedFile.GetUsingScope(cursorLocation).Resolve(compilation));

            var currentTypeDefinition = unresolvedFile.GetInnermostTypeDefinition(cursorLocation);
            if (currentTypeDefinition != null)
            {
                var resolvedDef = currentTypeDefinition.Resolve(typeResolveContext).GetDefinition();
                typeResolveContext = typeResolveContext.WithCurrentTypeDefinition(resolvedDef);
                var curMember =
                    resolvedDef.Members.FirstOrDefault(m => m.Region.Begin <= cursorLocation && cursorLocation < m.BodyRegion.End);
                if (curMember != null) // for testcase CodeB curMember is null, despite resolvedDef.Members contains MethodA (see tests at the bottom)
                {
                    typeResolveContext = typeResolveContext.WithCurrentMember(curMember);
                }
            }

            return typeResolveContext;
        }

        private ICompilation CreateCompilation(string sourceCode, out IProjectContent projectContent, out CSharpUnresolvedFile unresolvedFile)
        {
            projectContent = new CSharpProjectContent();
            projectContent = AddFileToProjectContent(projectContent, sourceCode, "FileOfInterest.cs", out unresolvedFile);

            projectContent.AddAssemblyReferences(new DefaultAssemblyReference(typeof(List<>).Assembly.GetName().Name));

            var compilation = projectContent.CreateCompilation();
            return compilation;
        }

        private IProjectContent AddFileToProjectContent(
            IProjectContent projectContent,
            string sourceCode,
            string fileName,
            out CSharpUnresolvedFile unresolvedFile)
        {
            SyntaxTree syntaxTree = new CSharpParser().Parse(sourceCode, fileName);
            syntaxTree.Freeze();

            unresolvedFile = syntaxTree.ToTypeSystem();
            return projectContent.AddOrUpdateFiles(unresolvedFile);
        }
    }

We think, the code above should satisfy the following four test cases. But for code snippet CodeB we can not determine the correct context.

namespace NRefactoryTests
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using ICSharpCode.NRefactory;
    using ICSharpCode.NRefactory.CSharp;
    using ICSharpCode.NRefactory.CSharp.TypeSystem;
    using ICSharpCode.NRefactory.Editor;
    using ICSharpCode.NRefactory.TypeSystem;
    using ICSharpCode.NRefactory.TypeSystem.Implementation;
    using NUnit.Framework;

    public class TypeContextTest
    {
        private TypeResolveContextDetermination typeResolveContextDetermination;

        private const string CodeA = 
@"namespace TestNamespace
{
               public class TestClass 
                {
                              public virtual void MethodA()
                              {
                                              TestClass test = new TestClass();
                                               this.$$$
                                               TestClass test2 = new TestClass();
                              }

                               public virtual void MethodB()
                              {

                               }
               }
}";

        private const string CodeB =
@"namespace TestNamespace
{
               public class TestClass 
                {
                              public virtual void MethodA()
                              {
                                              TestClass test = new TestClass();
                                               this.M$$$
                                               TestClass test2 = new TestClass();
                              }

                               public virtual void MethodB()
                              {

                               }
               }
}";

        private const string CodeC =
@"namespace TestNamespace
{
               public class TestClass 
                {
                              public virtual void MethodA()
                              {
                                              TestClass test = new TestClass();
                                               this.M$$$
                              }

                               public virtual void MethodB()
                              {

                               }
               }
}";

        private const string CodeD =
@"namespace TestNamespace
{
               public class TestClass 
                {
                              public virtual void MethodA()
                              {
                                               this.M$$$
                                               TestClass test2 = new TestClass();
                              }

                               public virtual void MethodB()
                              {

                               }
               }
}";


        [SetUp]
        public void Setup()
        {
            typeResolveContextDetermination = new TypeResolveContextDetermination();
        }

        [Test]
        public void ResolveTypeContextWithNRefactory_ContextShouldBeMethodA(
            [Values(CodeA, CodeB, CodeC, CodeD)] string code)
        {
            int cursorPosition = code.IndexOf("$$$", StringComparison.Ordinal);
            code = code.Replace("$$$", string.Empty);

            var context = typeResolveContextDetermination.DetermineTypeResolveContext(code, cursorPosition);

            Assert.NotNull(context);
            Assert.NotNull(context.CurrentMember, "CurrentMember of the context should be MethodA");
            Assert.AreEqual("TestNamespace.TestClass.MethodA", context.CurrentMember.FullName, "CurrentMember of the context should be MethodA");
        }
    }

The problem with CodeB is, that we can not find a member:

var curMember =
                    resolvedDef.Members.FirstOrDefault(m => m.Region.Begin <= cursorLocation && cursorLocation < m.BodyRegion.End);

Is this actually a bug or are we doing something wrong?

Thanks for your help!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant