2015-02-08 50 views
6

我想使用Roslyn來分析Razor視圖內的C#代碼塊內的語義信息。獲取cshtml文件的語義模型?

是否有任何方法(在Visual Studio 2015中,甚至在單元測試中)獲取表示此代碼的SemanticModel

+0

什麼分析SemanticModel的目的是什麼? – 2015-02-08 08:46:46

+0

@ErikPhilips我正在創作[Visual Studio擴展](http://www.oz-code。com)需要能夠在調試器中給出語義問題(調用'GetSymbolInfo','GetTypeInfo'等)給出下一個語句('黃線')的上下文。目前,對於Razor Views而言,我一直無法做到這一點。 – 2015-02-08 12:02:32

+0

@OmerRaviv或許你的問題更接近*我如何獲得當前文檔的'SemanticModel'?*或者,你是否需要Visual Studio擴展?你不能將你的代碼添加爲Roslyn分析器嗎?例如。如果您關心某些類型的風格違規,則可以通過分析器而不是擴展來有效執行。在那個時候,擴展是不必要的。 – 2015-02-08 12:32:48

回答

5

Razor文件包含一個帶有生成的C#代碼的C#投影緩衝區(包括您自己不寫的部分)。該緩衝區具有完整的Roslyn服務,正是您所需要的。

您需要遍歷TextView的BufferGraph並找到CSharp緩衝區;然後您可以獲得它的Document和語義模型。

如果您從光標位置開始,只需將該位置映射到CSharp緩衝區。

請注意,對於包含多個緩衝區的多個CSharp TextView是完全合法的。 (雖然剃刀編輯器將永遠不會做到這一點)


如果您不是在一個TextView工作,你需要做的這一切自己;您需要通過Razor編譯器運行Razor源代碼以獲取生成的C#源代碼,然後使用Roslyn編譯該代碼以獲取語義模型。

6

使用RazorTemplateEngine.GenerateCodeCSharpCodeProvider.GenerateCodeFromCompileUnit(或如果要將中間源作爲VB.NET)提取表示來自Razor視圖文件的視圖的代碼(或者如果要將中間源視爲VB.NET,請提取VBCodeProvider)。然後你可以使用Roslyn來解析代碼。

有一個使用Roslyn和Razor查看文件here的示例。

注意到,GenerateCode攜帶一個警告:

此類型/成員支持.NET Framework基礎結構,不適合直接在代碼中使用。

+0

還有一些關於Roslyn和ASP.NET的信息[here](http://blogs.msdn.com/b/webdev/archive/2014/05/12/enabling-the-net-compiler-platform-roslyn- in-asp-net-applications.aspx) – 2015-02-08 09:00:26

+0

良好的信息 - 爲我工作得很好! – 2015-06-08 14:24:24

1

Roslyn只在cshtml文件打開時建模,但在此期間它們與Workspace模型中的其他源文件相似。

有沒有具體的事情你試過了,不行?

2

爲了防止其他人被困在此,我有迷你示例應用程序可能會有所幫助。

我有一個CMS類是這樣的:

public partial class CMS 
{ 
    public static string SomeKey 
    { 
     get { return (string) ResourceProvider.GetResource("some_key"); } 
    } 

    // ... and many more ... 
} 

...我想找出其中的這些都是在我的解決方案用於報告......進入羅斯林!

下面的程序將打印出計數的使用和未使用的引用:

using Microsoft.CodeAnalysis; 
using Microsoft.CodeAnalysis.FindSymbols; 
using Microsoft.CodeAnalysis.MSBuild; 
using Microsoft.CSharp; 
using System; 
using System.CodeDom.Compiler; 
using System.Collections.Generic; 
using System.IO; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Web.Razor; 

namespace TranslationSniffer 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      new Program().Go().Wait(); 
     }   

     public async Task Go() 
     { 
      // Roslyn! 
      var ws = MSBuildWorkspace.Create(); 

      // Store the translation keys... 
      List<string> used = new List<string>(); 
      List<string> delete = new List<string>(); 

      string solutionRoot = @"C:\_Code\PathToProject\"; 
      string sln = solutionRoot + "MySolution.sln"; 

      // Load the solution, and find all the cshtml Razor views... 
      var solution = await ws.OpenSolutionAsync(sln); 
      var mainProj = solution.Projects.Where(x => x.Name == "ConsumerWeb").Single(); 
      FileInfo[] cshtmls = new DirectoryInfo(solutionRoot).GetFiles("*.cshtml", SearchOption.AllDirectories); 

      // Go through each Razor View - generate the equivalent CS and add to the project for compilation. 
      var host = new RazorEngineHost(RazorCodeLanguage.Languages["cshtml"]); 
      var razor = new RazorTemplateEngine(host); 
      var cs = new CSharpCodeProvider(); 
      var csOptions = new CodeGeneratorOptions(); 
      foreach (var cshtml in cshtmls) 
      { 
       using (StreamReader re = new StreamReader(cshtml.FullName)) 
       { 
        try 
        { 
         // Let Razor do it's thang... 
         var compileUnit = razor.GenerateCode(re).GeneratedCode; 

         // Pull the code into a stringbuilder, and append to the main project: 
         StringBuilder sb = new StringBuilder(); 
         using (StringWriter rw = new StringWriter(sb)) 
         { 
          cs.GenerateCodeFromCompileUnit(compileUnit, rw, csOptions); 
         } 

         // Get the new immutable project 
         var doc = mainProj.AddDocument(cshtml.Name + ".cs", sb.ToString()); 
         mainProj = doc.Project; 
        } 
        catch(Exception ex) 
        { 
         Console.WriteLine("Compile fail for: {0}", cshtml.Name); 
         // throw; 
        } 

        continue; 
       } 
      } 

      // We now have a new immutable solution, as we have changed the project instance... 
      solution = mainProj.Solution; 

      // Pull out our application translation list (its in a static class called 'CMS'): 
      var mainCompile = await mainProj.GetCompilationAsync(); 
      var mainModel = mainCompile.GetTypeByMetadataName("Resources.CMS"); 
      var translations = mainModel.GetMembers().Where(x => x.Kind == SymbolKind.Property).ToList(); 

      foreach (var translation in translations) 
      { 
       var references = await SymbolFinder.FindReferencesAsync(translation, solution)     ; 

       if (!references.First().Locations.Any()) 
       { 
        Console.WriteLine("{0} translation is not used!", translation.Name); 
        delete.Add(translation.Name); 
       } 
       else 
       { 
        Console.WriteLine("{0} :in: {1}", translation.Name, references.First().Locations.First().Document.Name); 
        used.Add(translation.Name); 
       } 
      } 

      Console.WriteLine(); 
      Console.WriteLine("Used references {0}. Unused references: {1}", used.Count, delete.Count); 

      return; 
     } 
    } 
}