簡體   English   中英

使用Roslyn CTP API的代碼差異

[英]Code diff using Roslyn CTP API

我正在嘗試使用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 . . .

有趣的是,它似乎將每一行顯示為每行的標記,除了添加的行,它顯示行而不會分解它。 有誰知道如何隔離實際的變化?

Bruce Boughton的猜測是正確的。 GetChangedSpans方法不是一種通用語法差異機制,用於區分兩個沒有共享歷史記錄的語法樹。 相反,它旨在將通過編輯生成的兩棵樹帶到一棵公共樹,並確定由於編輯而樹的哪些部分是不同的。

如果您已經使用了第一個解析樹並將新語句作為編輯插入其中,那么您將看到一組更小的更改。

如果我在高層次上簡要描述Roslyn詞法分析器和解析器的工作方式,可能會有所幫助。

基本思想是詞法分析器生成的“語法標記”和解析器生成的“語法樹”是不可變的 他們從不改變。 因為它們永遠不會改變,所以我們可以在新的解析樹中重復使用先前解析樹的部分。 (具有此屬性的數據結構通常稱為“持久性”數據結構。)

因為我們可以重用現有的部分,所以我們可以為程序中出現的給定標記(例如class每個實例使用相同的值。 每個class令牌的長度和內容完全相同; 區分兩個不同class令牌的唯一因素是它們的瑣事 (它們之間的間距和注釋)及它們的位置 ,以及它們的父級 - 更大的語法節點包含令牌。

當你解析一個文本塊時,我們生成一個永久的,不可變的形式的語法標記和語法樹,我們稱之為“綠色”形式。 然后,我們將綠色節點包裝在“紅色”層中。 綠色層對位置,父母等一無所知。 紅色層確實如此。 (異想天開的名稱是由於我們首次在白板上繪制此數據結構時,這些是我們使用的顏色。)當您創建對給定語法樹的編輯時,我們會查看以前的語法樹,識別更改的節點,然后僅在更改的主干上構建新節點。 綠樹的所有其他分支保持不變。

在區分兩棵樹時,基本上我們所做的就是采用綠色節點的設定差異 如果通過編輯另一個樹來生成其中一個樹,則幾乎所有綠色節點都將是相同的,因為只重建了脊椎。 樹差異算法將識別已更改的節點並計算出受影響的跨度。

如果這兩棵樹沒有共同的歷史,那么它們共同的唯一綠色節點就是單個令牌,正如我之前所說,它們在任何地方都被重復使用。 每個更高級別的綠色語法節點將是不同的綠色節點,因此即使其文本相同,也會被樹差異引擎視為不同。

此方法的目的是允許編輯器代碼快速做出保守猜測,即文本緩沖區的哪些部分需要,例如,重新編輯,編輯或撤消,或某些此類事物。 假設樹木具有歷史關系。 目的不是提供通用的文本差異機制; 已經有很多很棒的工具。

例如,想象一下,您已將第一個程序粘貼到編輯器中,然后突出顯示整個程序,然后將第二個程序粘貼到編輯器中。 人們可以合理地期望編輯不會浪費時間來弄清楚粘貼代碼的哪些部分恰好與之前粘貼的代碼相同。 這可能非常昂貴,答案很可能“不多”。 相反,編輯保守地假設整個粘貼區域是全新且完全不同的代碼。 它不會花費任何時間嘗試在舊代碼和新代碼之間建立對應關系; 它重新解析,因此重新整理了整個事物。

另一方面,如果您剛剛粘貼在單個不同的語句中,那么編輯引擎只會將編輯插入到正確的位置。 在可能的情況下 ,將使用現有的綠色節點重新生成解析樹,並且差異引擎將識別需要重新着色的跨度:具有不同綠色節點的跨度。

這一切都有意義嗎?

更新:

哈,顯然凱文和我都在同一時間在相鄰的辦公室打出相同的答案。 有點重復的努力,但我認為這兩個答案對情況都有很好的看法。 :-)

@bruceboughton是對的, GetChangedSpans旨在發現增量解析器所做的更改。 使用以下代碼,我得到更好的輸出:

        var code = 
        @"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 text = new StringText(code);
        SyntaxTree tree = SyntaxTree.ParseCompilationUnit(text);

        var index = code.IndexOf("}");
        var added = @"    Console.WriteLine(""jjfjjf""); 
                      ";

        var code2 = code.Substring(0, index) + 
                    added +
                    code.Substring(index);

        var text2 = new StringText(code2);

        var tree2 = tree.WithChange(text2, new [] { new TextChangeRange(new TextSpan(index, 0), added.Length) } );

        foreach (var span in tree2.GetChangedSpans(tree))
        {
            Console.WriteLine(text2.GetText(span));
        }

但是,一般來說,GetChangedSpans意味着快速而又保守的差異。 為了更好地控制差異,以及更准確的結果,您可能希望實現自己的樹差異算法,您可以調整以滿足您的需求。

在上面的代碼中,如果您使用的是VS,編輯器內置了更改報告和文本差異,這將允許您輕松構建TextChangeRange對象,但如果您願意,您可能仍需要至少一個文本差異算法能夠將更改傳遞給增量解析器。

我猜想GetChangedSpans旨在比較樹和樹之間的變化,這些變化是根據原始樹的變化而不是兩棵任意樹之間的變化。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM