c# - 使用 Roslyn CTP API 的代码差异

标签 c# .net roslyn

我正在尝试使用 Roslyn API 做一些基本的代码差异,但遇到了一些意想不到的问题。本质上,我有两段相同的代码,只是添加了一行。这应该只返回更改文本的行,但出于某种原因,它告诉我一切都已更改。我也试过只编辑一行而不是添加一行,但我得到了相同的结果。我希望能够将其应用于源文件的两个版本以识别两者之间的差异。这是我目前使用的代码:

        SyntaxTree tree = SyntaxTree.ParseCompilationUnit(
            @"using System;
            using System.Collections.Generic;
            using System.Linq;
            using System.Text;

            namespace HelloWorld
            {
                class Program
                {
                    static void Main(string[] args)
                    {
                        Console.WriteLine(""Hello, World!"");
                    }
                }
            }");

        var root = (CompilationUnitSyntax)tree.Root;

        var compilation = Compilation.Create("HelloWorld")
                                     .AddReferences(
                                        new AssemblyFileReference(
                                            typeof(object).Assembly.Location))
                                     .AddSyntaxTrees(tree);

        var model = compilation.GetSemanticModel(tree);
        var nameInfo = model.GetSemanticInfo(root.Usings[0].Name);
        var systemSymbol = (NamespaceSymbol)nameInfo.Symbol;

        SyntaxTree tree2 = SyntaxTree.ParseCompilationUnit(
            @"using System;
            using System.Collections.Generic;
            using System.Linq;
            using System.Text;

            namespace HelloWorld
            {
                class Program
                {
                    static void Main(string[] args)
                    {
                        Console.WriteLine(""Hello, World!"");
                        Console.WriteLine(""jjfjjf"");
                    }
                }
            }");

        var root2 = (CompilationUnitSyntax)tree2.Root;

        var compilation2 = Compilation.Create("HelloWorld")
                                     .AddReferences(
                                        new AssemblyFileReference(
                                            typeof(object).Assembly.Location))
                                     .AddSyntaxTrees(tree2);

        var model2 = compilation2.GetSemanticModel(tree2);
        var nameInfo2 = model2.GetSemanticInfo(root2.Usings[0].Name);
        var systemSymbol2 = (NamespaceSymbol)nameInfo2.Symbol;

        foreach (TextSpan t in tree2.GetChangedSpans(tree))
        {
            Console.WriteLine(tree2.Text.GetText(t));
        }

这是我得到的输出:
System
                using System
Collections
Generic
                using System
Linq
                using System
Text

                namespace HelloWorld
                {
                    class Program
                    {
                        static
Main
args
                        {
                            Console
WriteLine
"Hello, World!"
                            Console.WriteLine("jjfjjf");
                        }
                    }
                }
Press any key to continue . . .

有趣的是,它似乎将每一行显示为每一行的标记,除了添加的行,它在不分解的情况下显示该行。有谁知道如何隔离实际更改?

最佳答案

布鲁斯·鲍顿的猜测是正确的。 GetChangedSpans 方法并不是一种通用的语法差异机制,用于在没有共享历史记录的两个语法树之间进行区分。相反,它旨在将通过编辑产生的两棵树变成公共(public)树,并确定树的哪些部分因编辑而不同。

如果您使用第一个解析树并将新语句作为编辑插入其中,那么您会看到一组小得多的更改。

如果我简要描述 Roslyn 词法分析器和解析器的工作原理,可能会有所帮助。

基本思想是词法生成的“语法标记”和解析器生成的“语法树”是不可变的。他们永远不会改变。因为它们永远不会改变,我们可以在新的解析树中重用以前的解析树的部分。 (具有此属性的数据结构通常称为“持久”数据结构。)

因为我们可以重用现有的部分,例如,我们可以对给定 token 的每个实例使用相同的值,比如 class ,出现在程序中。每个class的长度和内容token 完全一样;唯一区分两种不同的东西 class标记是它们的琐事,(围绕它们的间距和注释)和它们的位置,以及它们的父节点——包含标记的更大的语法节点。

当你解析一个文本块时,我们会以一种持久的、不可变的形式生成语法标记和语法树,我们称之为“绿色”形式。然后我们将绿色节点包裹在“红色”层中。绿色层对位置、 parent 等一无所知。红色层可以。 (这些异想天开的名字是因为当我们第一次在白板上绘制这个数据结构时,那些是我们使用的颜色。)当您对给定的语法树进行编辑时,我们会查看之前的语法树,识别更改的节点,然后仅在更改的主干上构建新节点。绿树的所有其他分支保持不变。

当比较两棵树时,基本上我们所做的是取绿色节点的集合差。如果其中一棵树是通过编辑另一棵树生成的,那么几乎所有的绿色节点都将相同,因为只重建了脊椎。树差异算法将识别更改的节点并计算出受影响的跨度。

如果两棵树没有共同的历史,那么它们唯一共同的绿色节点就是单个 token ,正如我之前所说,它们在任何地方都可以重复使用。每个更高级别的绿色语法节点将是不同的绿色节点,因此被树差异引擎视为不同,即使其文本相同。

此方法的目的是允许编辑器代码快速对文本缓冲区的哪些部分需要进行保守的猜测,例如,在编辑或撤消或诸如此类的事情之后重新着色。假设是这些树具有历史关系。目的不是提供通用的文本差异机制;已经有很多很棒的工具了。

例如,假设您已将第一个程序粘贴到编辑器中,然后突出显示整个内容,然后将第二个程序粘贴到编辑器中。人们可以合理地期望编辑器不会浪费时间试图找出粘贴的代码的哪些部分恰好与先前粘贴的代码相同。这可能非常昂贵,答案很可能是“不多”。相反,编辑器做出了保守的假设,即整个粘贴区域是全新的且完全不同的代码。它不会花任何时间尝试在旧代码和新代码之间进行对应;它重新解析并因此重新着色整个事物。

另一方面,如果您刚刚粘贴了单个不同的语句,那么编辑引擎会简单地将编辑插入到正确的位置。解析树将在可能的情况下重新使用现有的绿色节点重新生成,差异引擎将识别需要重新着色的跨度:具有不同绿色节点的跨度。

这一切都有意义吗?

更新:

哈,显然凯文和我都在相邻的办公室同时输入相同的答案。有点重复的努力,但我认为这两个答案对这种情况都有很好的看法。 :-)

关于c# - 使用 Roslyn CTP API 的代码差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8311671/

相关文章:

c# - 如何对字符串中的位置执行 HitTest

c# - 查找基本类型的属性

c# - .Net Frameworks 4.0 应用程序在 Windows XP SP3 上崩溃了吗?

c# - 优化 sqlite 查询并能够比较对象和数据库

.net - Windows 和 .NET 之间的时区是否同步

c# - netsh http 通过组策略 (GPO) 添加 urlacl

c# - 自定义 xmlWriter 跳过某个元素?

c# - netstandard 代码生成器 Microsoft.Data.SqlClient 抛出 FileNotFound System.Security.Principal.Windows,Version=4.1.1.0

c# - Roslyn AdhocWorkspace 要求引用 System.Runtime

c# - 在 C# 中运行 Javascript 的最可靠方法?